"""API interface to get an Insteon device.""" from typing import Any from pyinsteon import devices from pyinsteon.address import Address from pyinsteon.constants import DeviceAction import voluptuous as vol from homeassistant.components import websocket_api from homeassistant.core import HomeAssistant, callback from homeassistant.helpers import device_registry as dr from homeassistant.helpers.dispatcher import async_dispatcher_send from ..const import ( DEVICE_ADDRESS, DEVICE_ID, DOMAIN, HA_DEVICE_NOT_FOUND, ID, INSTEON_DEVICE_NOT_FOUND, MULTIPLE, SIGNAL_REMOVE_HA_DEVICE, SIGNAL_REMOVE_INSTEON_DEVICE, SIGNAL_REMOVE_X10_DEVICE, TYPE, ) from ..schemas import build_x10_schema from .config import add_x10_device, remove_device_override, remove_x10_device X10_DEVICE = "x10_device" X10_DEVICE_SCHEMA = build_x10_schema() REMOVE_ALL_REFS = "remove_all_refs" def compute_device_name(ha_device): """Return the HA device name.""" return ha_device.name_by_user if ha_device.name_by_user else ha_device.name async def async_add_devices(address, multiple): """Add one or more Insteon devices.""" async for _ in devices.async_add_device(address=address, multiple=multiple): pass def get_insteon_device_from_ha_device(ha_device): """Return the Insteon device from an HA device.""" for identifier in ha_device.identifiers: if len(identifier) > 1 and identifier[0] == DOMAIN and devices[identifier[1]]: return devices[identifier[1]] return None async def async_device_name(dev_registry, address): """Get the Insteon device name from a device registry id.""" ha_device = dev_registry.async_get_device(identifiers={(DOMAIN, str(address))}) if not ha_device: if device := devices[address]: return f"{device.description} ({device.model})" return "" return compute_device_name(ha_device) def notify_device_not_found(connection, msg, text): """Notify the caller that the device was not found.""" connection.send_message( websocket_api.error_message(msg[ID], websocket_api.const.ERR_NOT_FOUND, text) ) @websocket_api.websocket_command( {vol.Required(TYPE): "insteon/device/get", vol.Required(DEVICE_ID): str} ) @websocket_api.require_admin @websocket_api.async_response async def websocket_get_device( hass: HomeAssistant, connection: websocket_api.connection.ActiveConnection, msg: dict[str, Any], ) -> None: """Get an Insteon device.""" dev_registry = dr.async_get(hass) if not (ha_device := dev_registry.async_get(msg[DEVICE_ID])): notify_device_not_found(connection, msg, HA_DEVICE_NOT_FOUND) return if not (device := get_insteon_device_from_ha_device(ha_device)): notify_device_not_found(connection, msg, INSTEON_DEVICE_NOT_FOUND) return ha_name = compute_device_name(ha_device) device_info = { "name": ha_name, "address": str(device.address), "is_battery": device.is_battery, "aldb_status": str(device.aldb.status), } connection.send_result(msg[ID], device_info) @websocket_api.websocket_command( { vol.Required(TYPE): "insteon/device/add", vol.Required(MULTIPLE): bool, vol.Optional(DEVICE_ADDRESS): str, } ) @websocket_api.require_admin @websocket_api.async_response async def websocket_add_device( hass: HomeAssistant, connection: websocket_api.connection.ActiveConnection, msg: dict[str, Any], ) -> None: """Add one or more Insteon devices.""" @callback def linking_complete(address: str, action: DeviceAction): """Forward device events to websocket.""" if action == DeviceAction.COMPLETED: forward_data = {"type": "linking_stopped", "address": ""} else: return connection.send_message(websocket_api.event_message(msg["id"], forward_data)) @callback def async_cleanup() -> None: """Remove signal listeners.""" devices.unsubscribe(linking_complete) connection.subscriptions[msg["id"]] = async_cleanup devices.subscribe(linking_complete) async for address in devices.async_add_device( address=msg.get(DEVICE_ADDRESS), multiple=msg[MULTIPLE] ): forward_data = {"type": "device_added", "address": str(address)} connection.send_message(websocket_api.event_message(msg["id"], forward_data)) connection.send_result(msg[ID]) @websocket_api.websocket_command({vol.Required(TYPE): "insteon/device/add/cancel"}) @websocket_api.require_admin @websocket_api.async_response async def websocket_cancel_add_device( hass: HomeAssistant, connection: websocket_api.connection.ActiveConnection, msg: dict[str, Any], ) -> None: """Cancel the Insteon all-linking process.""" await devices.async_cancel_all_linking() connection.send_result(msg[ID]) @websocket_api.websocket_command( { vol.Required(TYPE): "insteon/device/remove", vol.Required(DEVICE_ADDRESS): str, vol.Required(REMOVE_ALL_REFS): bool, } ) @websocket_api.require_admin @websocket_api.async_response async def websocket_remove_device( hass: HomeAssistant, connection: websocket_api.connection.ActiveConnection, msg: dict[str, Any], ) -> None: """Remove an Insteon device.""" address = msg[DEVICE_ADDRESS] remove_all_refs = msg[REMOVE_ALL_REFS] if address.startswith("X10"): _, housecode, unitcode = address.split(".") unitcode = int(unitcode) async_dispatcher_send(hass, SIGNAL_REMOVE_X10_DEVICE, housecode, unitcode) remove_x10_device(hass, housecode, unitcode) else: address = Address(address) remove_device_override(hass, address) async_dispatcher_send(hass, SIGNAL_REMOVE_HA_DEVICE, address) async_dispatcher_send( hass, SIGNAL_REMOVE_INSTEON_DEVICE, address, remove_all_refs ) connection.send_result(msg[ID]) @websocket_api.websocket_command( { vol.Required(TYPE): "insteon/device/add_x10", vol.Required(X10_DEVICE): X10_DEVICE_SCHEMA, } ) @websocket_api.require_admin @websocket_api.async_response async def websocket_add_x10_device( hass: HomeAssistant, connection: websocket_api.connection.ActiveConnection, msg: dict[str, Any], ) -> None: """Get the schema for the X10 devices configuration.""" x10_device = msg[X10_DEVICE] try: add_x10_device(hass, x10_device) except ValueError: connection.send_error(msg[ID], code="duplicate", message="Duplicate X10 device") return connection.send_result(msg[ID])