1 """LCN Websocket API."""
3 from __future__
import annotations
5 from collections.abc
import Awaitable, Callable
6 from functools
import wraps
7 from typing
import TYPE_CHECKING, Any, Final
9 import lcn_frontend
as lcn_panel
10 import voluptuous
as vol
29 ADD_ENTITIES_CALLBACKS,
37 from .helpers
import (
39 async_update_device_config,
42 get_device_connection,
44 purge_device_registry,
45 purge_entity_registry,
46 register_lcn_address_devices,
48 from .schemas
import (
50 DOMAIN_DATA_BINARY_SENSOR,
62 type AsyncLcnWebSocketCommandHandler = Callable[
63 [HomeAssistant, ActiveConnection, dict[str, Any], ConfigEntry], Awaitable[
None]
66 URL_BASE: Final =
"/lcn_static"
70 """Register the LCN Panel and Websocket API."""
71 websocket_api.async_register_command(hass, websocket_get_device_configs)
72 websocket_api.async_register_command(hass, websocket_get_entity_configs)
73 websocket_api.async_register_command(hass, websocket_scan_devices)
74 websocket_api.async_register_command(hass, websocket_add_device)
75 websocket_api.async_register_command(hass, websocket_delete_device)
76 websocket_api.async_register_command(hass, websocket_add_entity)
77 websocket_api.async_register_command(hass, websocket_delete_entity)
79 if DOMAIN
not in hass.data.get(
"frontend_panels", {}):
80 await hass.http.async_register_static_paths(
84 path=lcn_panel.locate_dir(),
85 cache_headers=lcn_panel.is_prod_build,
89 await panel_custom.async_register_panel(
91 frontend_url_path=DOMAIN,
92 webcomponent_name=lcn_panel.webcomponent_name,
93 config_panel_domain=DOMAIN,
94 module_url=f
"{URL_BASE}/{lcn_panel.entrypoint_js}",
101 func: AsyncLcnWebSocketCommandHandler,
102 ) -> AsyncWebSocketCommandHandler:
103 """Websocket decorator to ensure the config_entry exists and return it."""
109 """Get config_entry."""
110 if not (config_entry := hass.config_entries.async_get_entry(msg[
"entry_id"])):
111 connection.send_result(msg[
"id"],
False)
113 await func(hass, connection, msg, config_entry)
118 @websocket_api.require_admin
119 @websocket_api.websocket_command(
{vol.Required("type"):
"lcn/devices", vol.Required(
"entry_id"): cv.string}
121 @websocket_api.async_response
127 config_entry: ConfigEntry,
129 """Get device configs."""
130 connection.send_result(msg[
"id"], config_entry.data[CONF_DEVICES])
133 @websocket_api.require_admin
134 @websocket_api.websocket_command(
{
vol.Required("type"):
"lcn/entities",
135 vol.Required(
"entry_id"): cv.string,
136 vol.Optional(CONF_ADDRESS): ADDRESS_SCHEMA,
139 @websocket_api.async_response
145 config_entry: ConfigEntry,
147 """Get entities configs."""
148 if CONF_ADDRESS
in msg:
151 for entity_config
in config_entry.data[CONF_ENTITIES]
152 if tuple(entity_config[CONF_ADDRESS]) == msg[CONF_ADDRESS]
155 entity_configs = config_entry.data[CONF_ENTITIES]
157 result_entity_configs = [
158 {**entity_config, CONF_NAME: entity.name
or entity.original_name}
159 for entity_config
in entity_configs[:]
160 if (entity :=
get_entity_entry(hass, entity_config, config_entry))
is not None
163 connection.send_result(msg[
"id"], result_entity_configs)
166 @websocket_api.require_admin
167 @websocket_api.websocket_command(
{vol.Required("type"):
"lcn/devices/scan", vol.Required(
"entry_id"): cv.string}
169 @websocket_api.async_response
175 config_entry: ConfigEntry,
177 """Scan for new devices."""
178 host_connection = hass.data[DOMAIN][config_entry.entry_id][CONNECTION]
179 await host_connection.scan_modules()
181 for device_connection
in host_connection.address_conns.values():
182 if not device_connection.is_group:
184 hass, device_connection, config_entry
190 connection.send_result(msg[
"id"], config_entry.data[CONF_DEVICES])
193 @websocket_api.require_admin
194 @websocket_api.websocket_command(
{
vol.Required("type"):
"lcn/devices/add",
195 vol.Required(
"entry_id"): cv.string,
196 vol.Required(CONF_ADDRESS): ADDRESS_SCHEMA,
199 @websocket_api.async_response
205 config_entry: ConfigEntry,
209 connection.send_result(
215 CONF_ADDRESS: msg[CONF_ADDRESS],
217 CONF_HARDWARE_SERIAL: -1,
218 CONF_SOFTWARE_SERIAL: -1,
219 CONF_HARDWARE_TYPE: -1,
227 device_configs = [*config_entry.data[CONF_DEVICES], device_config]
228 data = {**config_entry.data, CONF_DEVICES: device_configs}
229 hass.config_entries.async_update_entry(config_entry, data=data)
234 connection.send_result(msg[
"id"],
True)
237 @websocket_api.require_admin
238 @websocket_api.websocket_command(
{
vol.Required("type"):
"lcn/devices/delete",
239 vol.Required(
"entry_id"): cv.string,
240 vol.Required(CONF_ADDRESS): ADDRESS_SCHEMA,
243 @websocket_api.async_response
249 config_entry: ConfigEntry,
251 """Delete a device."""
254 device_registry = dr.async_get(hass)
258 device = device_registry.async_get_device(identifiers, set())
260 if not (device
and device_config):
261 connection.send_result(msg[
"id"],
False)
266 dc
for dc
in config_entry.data[CONF_DEVICES]
if dc != device_config
268 data = {**config_entry.data, CONF_DEVICES: device_configs}
269 hass.config_entries.async_update_entry(config_entry, data=data)
272 for entity_config
in data[CONF_ENTITIES][:]:
273 if tuple(entity_config[CONF_ADDRESS]) == msg[CONF_ADDRESS]:
274 data[CONF_ENTITIES].
remove(entity_config)
276 hass.config_entries.async_update_entry(config_entry, data=data)
283 connection.send_result(msg[
"id"])
286 @websocket_api.require_admin
287 @websocket_api.websocket_command(
{
vol.Required("type"):
"lcn/entities/add",
288 vol.Required(
"entry_id"): cv.string,
289 vol.Required(CONF_ADDRESS): ADDRESS_SCHEMA,
290 vol.Required(CONF_NAME): cv.string,
291 vol.Required(CONF_DOMAIN): cv.string,
292 vol.Required(CONF_DOMAIN_DATA): vol.Any(
293 DOMAIN_DATA_BINARY_SENSOR,
303 @websocket_api.async_response
309 config_entry: ConfigEntry,
313 connection.send_result(msg[
"id"],
False)
316 domain_name = msg[CONF_DOMAIN]
317 domain_data = msg[CONF_DOMAIN_DATA]
318 resource =
get_resource(domain_name, domain_data).lower()
320 config_entry.entry_id,
321 device_config[CONF_ADDRESS],
325 entity_registry = er.async_get(hass)
326 if entity_registry.async_get_entity_id(msg[CONF_DOMAIN], DOMAIN, unique_id):
327 connection.send_result(msg[
"id"],
False)
331 CONF_ADDRESS: msg[CONF_ADDRESS],
332 CONF_NAME: msg[CONF_NAME],
333 CONF_RESOURCE: resource,
334 CONF_DOMAIN: domain_name,
335 CONF_DOMAIN_DATA: domain_data,
339 add_entities = hass.data[DOMAIN][msg[
"entry_id"]][ADD_ENTITIES_CALLBACKS][
345 entity_configs = [*config_entry.data[CONF_ENTITIES], entity_config]
346 data = {**config_entry.data, CONF_ENTITIES: entity_configs}
349 hass.config_entries.async_update_entry(config_entry, data=data)
351 connection.send_result(msg[
"id"],
True)
354 @websocket_api.require_admin
355 @websocket_api.websocket_command(
{
vol.Required("type"):
"lcn/entities/delete",
356 vol.Required(
"entry_id"): cv.string,
357 vol.Required(CONF_ADDRESS): ADDRESS_SCHEMA,
358 vol.Required(CONF_DOMAIN): cv.string,
359 vol.Required(CONF_RESOURCE): cv.string,
362 @websocket_api.async_response
368 config_entry: ConfigEntry,
370 """Delete an entity."""
371 entity_config = next(
374 for entity_config
in config_entry.data[CONF_ENTITIES]
376 tuple(entity_config[CONF_ADDRESS]) == msg[CONF_ADDRESS]
377 and entity_config[CONF_DOMAIN] == msg[CONF_DOMAIN]
378 and entity_config[CONF_RESOURCE] == msg[CONF_RESOURCE]
384 if entity_config
is None:
385 connection.send_result(msg[
"id"],
False)
389 ec
for ec
in config_entry.data[CONF_ENTITIES]
if ec != entity_config
391 data = {**config_entry.data, CONF_ENTITIES: entity_configs}
393 hass.config_entries.async_update_entry(config_entry, data=data)
399 connection.send_result(msg[
"id"])
404 device_connection: DeviceConnectionType,
405 config_entry: ConfigEntry,
407 """Create or update device in config_entry according to given device_connection."""
409 device_connection.seg_id,
410 device_connection.addr_id,
411 device_connection.is_group,
414 device_configs = [*config_entry.data[CONF_DEVICES]]
415 data = {**config_entry.data, CONF_DEVICES: device_configs}
416 for device_config
in data[CONF_DEVICES]:
417 if tuple(device_config[CONF_ADDRESS]) == address:
422 CONF_ADDRESS: address,
424 CONF_HARDWARE_SERIAL: -1,
425 CONF_SOFTWARE_SERIAL: -1,
426 CONF_HARDWARE_TYPE: -1,
428 data[CONF_DEVICES].append(device_config)
433 hass.config_entries.async_update_entry(config_entry, data=data)
437 hass: HomeAssistant, entity_config: dict, config_entry: ConfigEntry
438 ) -> er.RegistryEntry |
None:
439 """Get entity RegistryEntry from entity_config."""
440 entity_registry = er.async_get(hass)
441 domain_name = entity_config[CONF_DOMAIN]
442 domain_data = entity_config[CONF_DOMAIN_DATA]
443 resource =
get_resource(domain_name, domain_data).lower()
445 config_entry.entry_id,
446 entity_config[CONF_ADDRESS],
450 entity_id := entity_registry.async_get_entity_id(domain_name, DOMAIN, unique_id)
453 return entity_registry.async_get(entity_id)
454
None add_entities(AsusWrtRouter router, AddEntitiesCallback async_add_entities, set[str] tracked)
bool remove(self, _T matcher)
config_entries.ConfigEntry|None get_entry(HomeAssistant hass, websocket_api.ActiveConnection connection, str entry_id, int msg_id)
str generate_unique_id(list[int] dev_id, int channel)
ConfigType|None get_device_config(AddressType address, ConfigEntry config_entry)
None purge_device_registry(HomeAssistant hass, str entry_id, ConfigType imported_entry_data)
None async_update_device_config(DeviceConnectionType device_connection, ConfigType device_config)
None register_lcn_address_devices(HomeAssistant hass, ConfigEntry config_entry)
None purge_entity_registry(HomeAssistant hass, str entry_id, ConfigType imported_entry_data)
DeviceConnectionType get_device_connection(HomeAssistant hass, AddressType address, ConfigEntry config_entry)
str get_resource(str domain_name, ConfigType domain_data)
None websocket_get_entity_configs(HomeAssistant hass, websocket_api.ActiveConnection connection, dict msg, ConfigEntry config_entry)
None websocket_add_device(HomeAssistant hass, websocket_api.ActiveConnection connection, dict msg, ConfigEntry config_entry)
AsyncWebSocketCommandHandler get_config_entry(AsyncLcnWebSocketCommandHandler func)
None websocket_delete_device(HomeAssistant hass, websocket_api.ActiveConnection connection, dict msg, ConfigEntry config_entry)
None websocket_add_entity(HomeAssistant hass, websocket_api.ActiveConnection connection, dict msg, ConfigEntry config_entry)
None register_panel_and_ws_api(HomeAssistant hass)
None async_create_or_update_device_in_config_entry(HomeAssistant hass, DeviceConnectionType device_connection, ConfigEntry config_entry)
er.RegistryEntry|None get_entity_entry(HomeAssistant hass, dict entity_config, ConfigEntry config_entry)
None websocket_get_device_configs(HomeAssistant hass, websocket_api.ActiveConnection connection, dict msg, ConfigEntry config_entry)
None websocket_scan_devices(HomeAssistant hass, websocket_api.ActiveConnection connection, dict msg, ConfigEntry config_entry)
None websocket_delete_entity(HomeAssistant hass, websocket_api.ActiveConnection connection, dict msg, ConfigEntry config_entry)