From 4f94c7cab67355cf1cd306b5ef55102d350adede Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Fri, 3 Feb 2023 17:47:09 +0100 Subject: [PATCH] Add thread WS API (#87307) --- homeassistant/components/thread/__init__.py | 2 + .../components/thread/websocket_api.py | 45 +++++++++++++++++++ tests/components/thread/__init__.py | 18 ++++++++ tests/components/thread/test_dataset_store.py | 20 +-------- tests/components/thread/test_websocket_api.py | 44 ++++++++++++++++++ 5 files changed, 111 insertions(+), 18 deletions(-) create mode 100644 homeassistant/components/thread/websocket_api.py create mode 100644 tests/components/thread/test_websocket_api.py diff --git a/homeassistant/components/thread/__init__.py b/homeassistant/components/thread/__init__.py index a6e5b17d8a8..074c4b2daf8 100644 --- a/homeassistant/components/thread/__init__.py +++ b/homeassistant/components/thread/__init__.py @@ -7,6 +7,7 @@ from homeassistant.helpers.typing import ConfigType from .const import DOMAIN from .dataset_store import DatasetEntry, async_add_dataset +from .websocket_api import async_setup as async_setup_ws_api __all__ = [ "DOMAIN", @@ -23,6 +24,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: DOMAIN, context={"source": SOURCE_IMPORT} ) ) + async_setup_ws_api(hass) hass.data[DOMAIN] = {} return True diff --git a/homeassistant/components/thread/websocket_api.py b/homeassistant/components/thread/websocket_api.py new file mode 100644 index 00000000000..3da8ec26420 --- /dev/null +++ b/homeassistant/components/thread/websocket_api.py @@ -0,0 +1,45 @@ +"""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 + + +@callback +def async_setup(hass: HomeAssistant) -> None: + """Set up the sensor websocket API.""" + websocket_api.async_register_command(hass, ws_add_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"]) diff --git a/tests/components/thread/__init__.py b/tests/components/thread/__init__.py index 4643d876d9e..e186d8ea0a6 100644 --- a/tests/components/thread/__init__.py +++ b/tests/components/thread/__init__.py @@ -1 +1,19 @@ """Tests for the Thread integration.""" + +DATASET_1 = ( + "0E080000000000010000000300000F35060004001FFFE0020811111111222222220708FDAD70BF" + "E5AA15DD051000112233445566778899AABBCCDDEEFF030E4F70656E54687265616444656D6F01" + "0212340410445F2B5CA6F2A93A55CE570A70EFEECB0C0402A0F7F8" +) + +DATASET_2 = ( + "0E080000000000010000000300000F35060004001FFFE0020811111111222222220708FDAD70BF" + "E5AA15DD051000112233445566778899AABBCCDDEEFF030E486f6d65417373697374616e742101" + "0212340410445F2B5CA6F2A93A55CE570A70EFEECB0C0402A0F7F8" +) + +DATASET_3 = ( + "0E080000000000010000000300000F35060004001FFFE0020811111111222222220708FDAD70BF" + "E5AA15DD051000112233445566778899AABBCCDDEEFF030E7ef09f90a3f09f90a5f09f90a47e01" + "0212340410445F2B5CA6F2A93A55CE570A70EFEECB0C0402A0F7F8" +) diff --git a/tests/components/thread/test_dataset_store.py b/tests/components/thread/test_dataset_store.py index ff1be3e0193..02fd90124f8 100644 --- a/tests/components/thread/test_dataset_store.py +++ b/tests/components/thread/test_dataset_store.py @@ -5,13 +5,9 @@ from python_otbr_api.tlv_parser import TLVError from homeassistant.components.thread import dataset_store from homeassistant.core import HomeAssistant -from tests.common import flush_store +from . import DATASET_1, DATASET_2, DATASET_3 -DATASET_1 = ( - "0E080000000000010000000300000F35060004001FFFE0020811111111222222220708FDAD70BF" - "E5AA15DD051000112233445566778899AABBCCDDEEFF030E4F70656E54687265616444656D6F01" - "0212340410445F2B5CA6F2A93A55CE570A70EFEECB0C0402A0F7F8" -) +from tests.common import flush_store # Same as DATASET_1, but PAN ID moved to the end DATASET_1_REORDERED = ( @@ -20,18 +16,6 @@ DATASET_1_REORDERED = ( "10445F2B5CA6F2A93A55CE570A70EFEECB0C0402A0F7F801021234" ) -DATASET_2 = ( - "0E080000000000010000000300000F35060004001FFFE0020811111111222222220708FDAD70BF" - "E5AA15DD051000112233445566778899AABBCCDDEEFF030E486f6d65417373697374616e742101" - "0212340410445F2B5CA6F2A93A55CE570A70EFEECB0C0402A0F7F8" -) - -DATASET_3 = ( - "0E080000000000010000000300000F35060004001FFFE0020811111111222222220708FDAD70BF" - "E5AA15DD051000112233445566778899AABBCCDDEEFF030E7ef09f90a3f09f90a5f09f90a47e01" - "0212340410445F2B5CA6F2A93A55CE570A70EFEECB0C0402A0F7F8" -) - async def test_add_invalid_dataset(hass: HomeAssistant) -> None: """Test adding an invalid dataset.""" diff --git a/tests/components/thread/test_websocket_api.py b/tests/components/thread/test_websocket_api.py new file mode 100644 index 00000000000..8955e876c4d --- /dev/null +++ b/tests/components/thread/test_websocket_api.py @@ -0,0 +1,44 @@ +"""Test the thread websocket API.""" + +from homeassistant.components.thread import dataset_store +from homeassistant.components.thread.const import DOMAIN +from homeassistant.core import HomeAssistant +from homeassistant.setup import async_setup_component + +from . import DATASET_1 + + +async def test_add_dataset(hass: HomeAssistant, hass_ws_client) -> None: + """Test we can add a dataset.""" + assert await async_setup_component(hass, DOMAIN, {}) + await hass.async_block_till_done() + + client = await hass_ws_client(hass) + + await client.send_json( + {"id": 1, "type": "thread/add_dataset_tlv", "source": "test", "tlv": DATASET_1} + ) + msg = await client.receive_json() + assert msg["success"] + assert msg["result"] is None + + store = await dataset_store._async_get_store(hass) + assert len(store.datasets) == 1 + dataset = next(iter(store.datasets.values())) + assert dataset.source == "test" + assert dataset.tlv == DATASET_1 + + +async def test_add_invalid_dataset(hass: HomeAssistant, hass_ws_client) -> None: + """Test adding an invalid dataset.""" + assert await async_setup_component(hass, DOMAIN, {}) + await hass.async_block_till_done() + + client = await hass_ws_client(hass) + + await client.send_json( + {"id": 1, "type": "thread/add_dataset_tlv", "source": "test", "tlv": "DEADBEEF"} + ) + msg = await client.receive_json() + assert not msg["success"] + assert msg["error"] == {"code": "invalid_format", "message": "unknown type 222"}