1 """The Hyperion component."""
3 from __future__
import annotations
6 from collections.abc
import Callable
7 from contextlib
import suppress
9 from typing
import Any, cast
11 from awesomeversion
import AwesomeVersion
12 from hyperion
import client, const
as hyperion_const
20 async_dispatcher_connect,
21 async_dispatcher_send,
25 CONF_INSTANCE_CLIENTS,
30 HYPERION_RELEASES_URL,
31 HYPERION_VERSION_WARN_CUTOFF,
33 SIGNAL_INSTANCE_REMOVE,
36 PLATFORMS = [Platform.CAMERA, Platform.LIGHT, Platform.SENSOR, Platform.SWITCH]
38 _LOGGER = logging.getLogger(__name__)
67 """Get a unique_id for a Hyperion instance."""
68 return f
"{server_id}_{instance}_{name}"
72 """Get an id for a Hyperion device/instance."""
73 return f
"{server_id}_{instance}"
77 """Split a unique_id into a (server_id, instance, type) tuple."""
78 data =
tuple(unique_id.split(
"_", 2))
82 return (data[0],
int(data[1]), data[2])
90 ) -> client.HyperionClient:
91 """Create a Hyperion Client."""
92 return client.HyperionClient(*args, **kwargs)
98 ) -> client.HyperionClient |
None:
99 """Create and connect a Hyperion Client."""
102 if not await hyperion_client.async_client_connect():
104 return hyperion_client
110 config_entry: ConfigEntry,
112 remove_func: Callable,
114 """Listen for instance additions/removals."""
116 hass.data[DOMAIN][config_entry.entry_id][CONF_ON_UNLOAD].extend(
120 SIGNAL_INSTANCE_ADD.format(config_entry.entry_id),
125 SIGNAL_INSTANCE_REMOVE.format(config_entry.entry_id),
133 """Set up Hyperion from a config entry."""
134 host = entry.data[CONF_HOST]
135 port = entry.data[CONF_PORT]
136 token = entry.data.get(CONF_TOKEN)
139 host, port, token=token, raw_connection=
True
143 if not hyperion_client:
144 raise ConfigEntryNotReady
145 version = await hyperion_client.async_sysinfo_version()
146 if version
is not None:
147 with suppress(ValueError):
148 if AwesomeVersion(version) < AwesomeVersion(HYPERION_VERSION_WARN_CUTOFF):
151 "Using a Hyperion server version < %s is not recommended --"
152 " some features may be unavailable or may not function"
153 " correctly. Please consider upgrading: %s"
155 HYPERION_VERSION_WARN_CUTOFF,
156 HYPERION_RELEASES_URL,
160 auth_resp = await hyperion_client.async_is_auth_required()
162 auth_resp
is not None
163 and client.ResponseOK(auth_resp)
164 and auth_resp.get(hyperion_const.KEY_INFO, {}).
get(
165 hyperion_const.KEY_REQUIRED,
False
169 await hyperion_client.async_client_disconnect()
170 raise ConfigEntryAuthFailed
173 if not await hyperion_client.async_client_login():
174 await hyperion_client.async_client_disconnect()
175 raise ConfigEntryAuthFailed
179 not await hyperion_client.async_client_switch_instance()
180 or not client.ServerInfoResponseOK(await hyperion_client.async_get_serverinfo())
182 await hyperion_client.async_client_disconnect()
183 raise ConfigEntryNotReady
188 hass.data.setdefault(DOMAIN, {})
189 hass.data[DOMAIN][entry.entry_id] = {
190 CONF_ROOT_CLIENT: hyperion_client,
191 CONF_INSTANCE_CLIENTS: {},
195 async
def async_instances_to_clients(response: dict[str, Any]) ->
None:
196 """Convert instances to Hyperion clients."""
197 if not response
or hyperion_const.KEY_DATA
not in response:
199 await async_instances_to_clients_raw(response[hyperion_const.KEY_DATA])
201 async
def async_instances_to_clients_raw(instances: list[dict[str, Any]]) ->
None:
202 """Convert instances to Hyperion clients."""
203 device_registry = dr.async_get(hass)
204 running_instances: set[int] = set()
205 stopped_instances: set[int] = set()
206 existing_instances = hass.data[DOMAIN][entry.entry_id][CONF_INSTANCE_CLIENTS]
207 server_id = cast(str, entry.unique_id)
217 for instance
in instances:
218 instance_num = instance.get(hyperion_const.KEY_INSTANCE)
219 if instance_num
is None:
221 if not instance.get(hyperion_const.KEY_RUNNING,
False):
222 stopped_instances.add(instance_num)
224 running_instances.add(instance_num)
225 if instance_num
in existing_instances:
228 host, port, instance=instance_num, token=token
230 if not hyperion_client:
232 existing_instances[instance_num] = hyperion_client
233 instance_name = instance.get(hyperion_const.KEY_FRIENDLY_NAME, DEFAULT_NAME)
236 SIGNAL_INSTANCE_ADD.format(entry.entry_id),
242 for instance_num
in set(existing_instances) - running_instances:
243 del existing_instances[instance_num]
245 hass, SIGNAL_INSTANCE_REMOVE.format(entry.entry_id), instance_num
252 for instance_num
in running_instances | stopped_instances
254 for device_entry
in dr.async_entries_for_config_entry(
255 device_registry, entry.entry_id
257 for kind, key
in device_entry.identifiers:
258 if kind == DOMAIN
and key
in known_devices:
261 device_registry.async_remove_device(device_entry.id)
263 hyperion_client.set_callbacks(
265 f
"{hyperion_const.KEY_INSTANCE}-{hyperion_const.KEY_UPDATE}": async_instances_to_clients,
269 await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
270 assert hyperion_client
271 if hyperion_client.instances
is not None:
272 await async_instances_to_clients_raw(hyperion_client.instances)
273 hass.data[DOMAIN][entry.entry_id][CONF_ON_UNLOAD].append(
274 entry.add_update_listener(_async_entry_updated)
281 """Handle entry updates."""
282 await hass.config_entries.async_reload(config_entry.entry_id)
286 """Unload a config entry."""
287 unload_ok = await hass.config_entries.async_unload_platforms(
288 config_entry, PLATFORMS
290 if unload_ok
and config_entry.entry_id
in hass.data[DOMAIN]:
291 config_data = hass.data[DOMAIN].pop(config_entry.entry_id)
292 for func
in config_data[CONF_ON_UNLOAD]:
296 await asyncio.gather(
298 config_data[CONF_INSTANCE_CLIENTS][
300 ].async_client_disconnect()
301 for instance_num
in config_data[CONF_INSTANCE_CLIENTS]
306 root_client = config_data[CONF_ROOT_CLIENT]
307 await root_client.async_client_disconnect()
web.Response get(self, web.Request request, str config_key)
tuple[str, int, str]|None split_hyperion_unique_id(str unique_id)
client.HyperionClient|None async_create_connect_hyperion_client(*Any args, **Any kwargs)
str get_hyperion_device_id(str server_id, int instance)
client.HyperionClient create_hyperion_client(*Any args, **Any kwargs)
str get_hyperion_unique_id(str server_id, int instance, str name)
None listen_for_instance_updates(HomeAssistant hass, ConfigEntry config_entry, Callable add_func, Callable remove_func)
bool async_unload_entry(HomeAssistant hass, ConfigEntry config_entry)
bool async_setup_entry(HomeAssistant hass, ConfigEntry entry)
None _async_entry_updated(HomeAssistant hass, ConfigEntry config_entry)
Callable[[], None] async_dispatcher_connect(HomeAssistant hass, str signal, Callable[..., Any] target)
None async_dispatcher_send(HomeAssistant hass, str signal, *Any args)