"""The thread websocket API.""" from __future__ import annotations from typing import Any from python_otbr_api.tlv_parser import TLVError import voluptuous as vol from homeassistant.components import websocket_api from homeassistant.core import HomeAssistant, callback from . import dataset_store, discovery @callback def async_setup(hass: HomeAssistant) -> None: """Set up the sensor websocket API.""" websocket_api.async_register_command(hass, ws_add_dataset) websocket_api.async_register_command(hass, ws_delete_dataset) websocket_api.async_register_command(hass, ws_discover_routers) websocket_api.async_register_command(hass, ws_get_dataset) websocket_api.async_register_command(hass, ws_list_datasets) websocket_api.async_register_command(hass, ws_set_preferred_border_agent_id) websocket_api.async_register_command(hass, ws_set_preferred_dataset) @websocket_api.require_admin @websocket_api.websocket_command( { vol.Required("type"): "thread/add_dataset_tlv", vol.Required("source"): str, vol.Required("tlv"): str, } ) @websocket_api.async_response async def ws_add_dataset( hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg: dict[str, Any] ) -> None: """Add a thread dataset.""" source = msg["source"] tlv = msg["tlv"] try: await dataset_store.async_add_dataset(hass, source, tlv) except TLVError as exc: connection.send_error( msg["id"], websocket_api.const.ERR_INVALID_FORMAT, str(exc) ) return connection.send_result(msg["id"]) @websocket_api.require_admin @websocket_api.websocket_command( { vol.Required("type"): "thread/set_preferred_border_agent_id", vol.Required("dataset_id"): str, vol.Required("border_agent_id"): str, } ) @websocket_api.async_response async def ws_set_preferred_border_agent_id( hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg: dict[str, Any] ) -> None: """Set the preferred border agent ID.""" dataset_id = msg["dataset_id"] border_agent_id = msg["border_agent_id"] store = await dataset_store.async_get_store(hass) store.async_set_preferred_border_agent_id(dataset_id, border_agent_id) connection.send_result(msg["id"]) @websocket_api.require_admin @websocket_api.websocket_command( { vol.Required("type"): "thread/set_preferred_dataset", vol.Required("dataset_id"): str, } ) @websocket_api.async_response async def ws_set_preferred_dataset( hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg: dict[str, Any] ) -> None: """Add a thread dataset.""" dataset_id = msg["dataset_id"] store = await dataset_store.async_get_store(hass) try: store.preferred_dataset = dataset_id except KeyError: connection.send_error( msg["id"], websocket_api.const.ERR_NOT_FOUND, "unknown dataset" ) return connection.send_result(msg["id"]) @websocket_api.require_admin @websocket_api.websocket_command( { vol.Required("type"): "thread/delete_dataset", vol.Required("dataset_id"): str, } ) @websocket_api.async_response async def ws_delete_dataset( hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg: dict[str, Any] ) -> None: """Delete a thread dataset.""" dataset_id = msg["dataset_id"] store = await dataset_store.async_get_store(hass) try: store.async_delete(dataset_id) except KeyError as exc: connection.send_error(msg["id"], websocket_api.const.ERR_NOT_FOUND, str(exc)) return except dataset_store.DatasetPreferredError as exc: connection.send_error(msg["id"], websocket_api.const.ERR_NOT_ALLOWED, str(exc)) return connection.send_result(msg["id"]) @websocket_api.require_admin @websocket_api.websocket_command( { vol.Required("type"): "thread/get_dataset_tlv", vol.Required("dataset_id"): str, } ) @websocket_api.async_response async def ws_get_dataset( hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg: dict[str, Any] ) -> None: """Get a thread dataset in TLV format.""" dataset_id = msg["dataset_id"] store = await dataset_store.async_get_store(hass) if not (dataset := store.async_get(dataset_id)): connection.send_error( msg["id"], websocket_api.const.ERR_NOT_FOUND, "unknown dataset" ) return connection.send_result(msg["id"], {"tlv": dataset.tlv}) @websocket_api.require_admin @websocket_api.websocket_command( { vol.Required("type"): "thread/list_datasets", } ) @websocket_api.async_response async def ws_list_datasets( hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg: dict[str, Any] ) -> None: """Get a list of thread datasets.""" store = await dataset_store.async_get_store(hass) result = [] preferred_dataset = store.preferred_dataset for dataset in store.datasets.values(): result.append( { "channel": dataset.channel, "created": dataset.created, "dataset_id": dataset.id, "extended_pan_id": dataset.extended_pan_id, "network_name": dataset.network_name, "pan_id": dataset.pan_id, "preferred": dataset.id == preferred_dataset, "preferred_border_agent_id": dataset.preferred_border_agent_id, "source": dataset.source, } ) connection.send_result(msg["id"], {"datasets": result}) @websocket_api.require_admin @websocket_api.websocket_command( { vol.Required("type"): "thread/discover_routers", } ) @websocket_api.async_response async def ws_discover_routers( hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg: dict[str, Any] ) -> None: """Discover Thread routers.""" @callback def router_discovered(key: str, data: discovery.ThreadRouterDiscoveryData) -> None: """Forward router discovery or update to websocket.""" connection.send_message( websocket_api.event_message( msg["id"], { "type": "router_discovered", "key": key, "data": data, }, ) ) @callback def router_removed(key: str) -> None: """Forward router removed to websocket.""" connection.send_message( websocket_api.event_message( msg["id"], { "type": "router_removed", "key": key, }, ) ) @callback def stop_discovery() -> None: """Stop discovery.""" hass.async_create_task(thread_discovery.async_stop()) # Start Thread router discovery thread_discovery = discovery.ThreadRouterDiscovery( hass, router_discovered, router_removed ) await thread_discovery.async_start() connection.subscriptions[msg["id"]] = stop_discovery connection.send_message(websocket_api.result_message(msg["id"]))