1 """API calls to manage Insteon configuration changes."""
3 from __future__
import annotations
5 from typing
import Any, TypedDict
7 from pyinsteon
import async_close, async_connect, devices
8 from pyinsteon.address
import Address
9 from pyinsteon.aldb.aldb_record
import ALDBRecord
10 from pyinsteon.constants
import LinkStatus
11 from pyinsteon.managers.link_manager
import get_broken_links
12 import voluptuous
as vol
13 import voluptuous_serialize
30 SIGNAL_ADD_DEVICE_OVERRIDE,
31 SIGNAL_ADD_X10_DEVICE,
32 SIGNAL_REMOVE_DEVICE_OVERRIDE,
35 from ..schemas
import (
36 build_device_override_schema,
38 build_plm_manual_schema,
41 from ..utils
import async_device_name, async_get_usb_ports
51 """X10 Device Configuration Definition."""
60 """X10 Device Configuration Definition."""
62 address: Address | str
68 """Return the Insteon configuration entry."""
69 return hass.config_entries.async_entries(DOMAIN)[0]
73 """Add an X10 device to the Insteon integration."""
76 x10_config = config_entry.options.get(CONF_X10, [])
78 device[CONF_HOUSECODE] == x10_device[
"housecode"]
79 and device[CONF_UNITCODE] == x10_device[
"unitcode"]
80 for device
in x10_config
82 raise ValueError(
"Duplicate X10 device")
84 hass.config_entries.async_update_entry(
86 options=config_entry.options | {CONF_X10: [*x10_config, x10_device]},
92 """Remove an X10 device from the config."""
95 new_options = {**config_entry.options}
98 for existing_device
in config_entry.options.get(CONF_X10, [])
99 if existing_device[CONF_HOUSECODE].lower() != housecode.lower()
100 or existing_device[CONF_UNITCODE] != unitcode
103 new_options[CONF_X10] = new_x10
104 hass.config_entries.async_update_entry(entry=config_entry, options=new_options)
108 """Add an Insteon device override."""
111 override_config = config_entry.options.get(CONF_OVERRIDE, [])
112 address = Address(override[CONF_ADDRESS])
114 Address(existing_override[CONF_ADDRESS]) == address
115 for existing_override
in override_config
117 raise ValueError(
"Duplicate override")
119 hass.config_entries.async_update_entry(
121 options=config_entry.options | {CONF_OVERRIDE: [*override_config, override]},
127 """Remove a device override from config."""
130 new_options = {**config_entry.options}
134 for existing_override
in config_entry.options.get(CONF_OVERRIDE, [])
135 if Address(existing_override[CONF_ADDRESS]) != address
137 new_options[CONF_OVERRIDE] = new_overrides
138 hass.config_entries.async_update_entry(entry=config_entry, options=new_options)
142 address: Address, record: ALDBRecord, dev_registry: dr.DeviceRegistry, status=
None
143 ) -> dict[str, str | int]:
144 """Convert a link to a dictionary."""
145 link_dict: dict[str, str | int] = {}
148 link_dict[
"address"] =
str(address)
149 link_dict[
"device_name"] = device_name
if device_name
else str(address)
150 link_dict[
"mem_addr"] = record.mem_addr
151 link_dict[
"in_use"] = record.is_in_use
152 link_dict[
"group"] = record.group
153 link_dict[
"is_controller"] = record.is_controller
154 link_dict[
"highwater"] = record.is_high_water_mark
155 link_dict[
"target"] =
str(record.target)
156 link_dict[
"target_name"] = target_name
if target_name
else str(record.target)
157 link_dict[
"data1"] = record.data1
158 link_dict[
"data2"] = record.data2
159 link_dict[
"data3"] = record.data3
161 link_dict[
"status"] = status.name.lower()
166 """Connect to the Insteon modem."""
170 await async_connect(**kwargs)
171 except ConnectionError:
176 @websocket_api.websocket_command({vol.Required(TYPE):
"insteon/config/get"})
177 @websocket_api.require_admin
178 @websocket_api.async_response
184 """Get Insteon configuration."""
186 modem_config = config_entry.data
187 options_config = config_entry.options
188 x10_config = options_config.get(CONF_X10)
189 override_config = options_config.get(CONF_OVERRIDE)
190 connection.send_result(
193 "modem_config": {**modem_config},
194 "x10_config": x10_config,
195 "override_config": override_config,
200 @websocket_api.websocket_command(
{
vol.Required(TYPE):
"insteon/config/get_modem_schema",
203 @websocket_api.require_admin
204 @websocket_api.async_response
210 """Get the schema for the modem configuration."""
212 config_data = config_entry.data
213 if device := config_data.get(CONF_DEVICE):
215 plm_schema = voluptuous_serialize.convert(
218 connection.send_result(msg[ID], plm_schema)
221 connection.send_result(msg[ID], hub_schema)
224 @websocket_api.websocket_command(
{
vol.Required(TYPE):
"insteon/config/update_modem_config",
225 vol.Required(
"config"): vol.Any(PLM_SCHEMA, HUB_V2_SCHEMA, HUB_V1_SCHEMA),
228 @websocket_api.require_admin
229 @websocket_api.async_response
235 """Get the schema for the modem configuration."""
236 config = msg[
"config"]
238 is_connected = devices.modem
is not None and devices.modem.connected
241 connection.send_error(
242 msg_id=msg[ID], code=
"connection_failed", message=
"Connection failed"
249 hass.config_entries.async_update_entry(
253 connection.send_result(msg[ID], {
"status":
"success"})
256 @websocket_api.websocket_command(
{
vol.Required(TYPE):
"insteon/config/device_override/add",
257 vol.Required(OVERRIDE): DEVICE_OVERRIDE_SCHEMA,
260 @websocket_api.require_admin
261 @websocket_api.async_response
267 """Get the schema for the modem configuration."""
268 override = msg[OVERRIDE]
272 connection.send_error(msg[ID],
"duplicate",
"Duplicate device address")
274 connection.send_result(msg[ID])
277 @websocket_api.websocket_command(
{
vol.Required(TYPE):
"insteon/config/device_override/remove",
278 vol.Required(DEVICE_ADDRESS): str,
281 @websocket_api.require_admin
282 @websocket_api.async_response
288 """Get the schema for the modem configuration."""
289 address = Address(msg[DEVICE_ADDRESS])
292 connection.send_result(msg[ID])
295 @websocket_api.websocket_command(
{vol.Required(TYPE):
"insteon/config/get_broken_links"}
297 @websocket_api.require_admin
298 @websocket_api.async_response
304 """Get any broken links between devices."""
305 broken_links = get_broken_links(devices=devices)
306 dev_registry = dr.async_get(hass)
307 broken_links_list = [
309 for address, record, status
in broken_links
310 if status != LinkStatus.MISSING_TARGET
312 connection.send_result(msg[ID], broken_links_list)
315 @websocket_api.websocket_command(
{vol.Required(TYPE):
"insteon/config/get_unknown_devices"}
317 @websocket_api.require_admin
318 @websocket_api.async_response
324 """Get any broken links between devices."""
325 broken_links = get_broken_links(devices=devices)
328 for _, record, status
in broken_links
329 if status == LinkStatus.MISSING_TARGET
331 connection.send_result(msg[ID], unknown_devices)
332
None websocket_get_modem_schema(HomeAssistant hass, websocket_api.connection.ActiveConnection connection, dict[str, Any] msg)
dict[str, str|int] async_link_to_dict(Address address, ALDBRecord record, dr.DeviceRegistry dev_registry, status=None)
None websocket_update_modem_config(HomeAssistant hass, websocket_api.connection.ActiveConnection connection, dict[str, Any] msg)
def remove_x10_device(HomeAssistant hass, str housecode, int unitcode)
def remove_device_override(HomeAssistant hass, Address address)
None websocket_remove_device_override(HomeAssistant hass, websocket_api.connection.ActiveConnection connection, dict[str, Any] msg)
None websocket_get_broken_links(HomeAssistant hass, websocket_api.connection.ActiveConnection connection, dict[str, Any] msg)
def _async_connect(**kwargs)
ConfigEntry get_insteon_config_entry(HomeAssistant hass)
def add_x10_device(HomeAssistant hass, X10DeviceConfig x10_device)
def add_device_overide(HomeAssistant hass, DeviceOverride override)
None websocket_get_config(HomeAssistant hass, websocket_api.connection.ActiveConnection connection, dict[str, Any] msg)
None websocket_get_unknown_devices(HomeAssistant hass, websocket_api.connection.ActiveConnection connection, dict[str, Any] msg)
None websocket_add_device_override(HomeAssistant hass, websocket_api.connection.ActiveConnection connection, dict[str, Any] msg)
def build_hub_schema(hub_version, host=vol.UNDEFINED, port=vol.UNDEFINED, username=vol.UNDEFINED, password=vol.UNDEFINED)
def build_plm_manual_schema(device=vol.UNDEFINED)
def build_device_override_schema(address=vol.UNDEFINED, cat=vol.UNDEFINED, subcat=vol.UNDEFINED, firmware=vol.UNDEFINED)
def build_plm_schema(dict[str, str] ports, device=vol.UNDEFINED)
str async_device_name(dr.DeviceRegistry dev_registry, Address address)
dict[str, str] async_get_usb_ports(HomeAssistant hass)
None async_dispatcher_send(HomeAssistant hass, str signal, *Any args)