diff --git a/aiohasupervisor/discovery.py b/aiohasupervisor/discovery.py new file mode 100644 index 0000000..4fe002a --- /dev/null +++ b/aiohasupervisor/discovery.py @@ -0,0 +1,32 @@ +"""Discovery client for supervisor.""" + +from uuid import UUID + +from .client import _SupervisorComponentClient +from .const import ResponseType +from .models.discovery import Discovery, DiscoveryConfig, DiscoveryList, SetDiscovery + + +class DiscoveryClient(_SupervisorComponentClient): + """Handles discovery access in supervisor.""" + + async def list(self) -> list[Discovery]: + """List discovered active services.""" + result = await self._client.get("discovery") + return DiscoveryList.from_dict(result.data).discovery + + async def get(self, uuid: UUID) -> Discovery: + """Get discovery details for a service.""" + result = await self._client.get(f"discovery/{uuid.hex}") + return Discovery.from_dict(result.data) + + async def delete(self, uuid: UUID) -> None: + """Remove discovery for a service.""" + await self._client.delete(f"discovery/{uuid.hex}") + + async def set(self, config: DiscoveryConfig) -> UUID: + """Inform supervisor of an available service.""" + result = await self._client.post( + "discovery", json=config.to_dict(), response_type=ResponseType.JSON + ) + return SetDiscovery.from_dict(result.data).uuid diff --git a/aiohasupervisor/models/__init__.py b/aiohasupervisor/models/__init__.py index bf97016..e659125 100644 --- a/aiohasupervisor/models/__init__.py +++ b/aiohasupervisor/models/__init__.py @@ -40,6 +40,10 @@ PartialBackupOptions, PartialRestoreOptions, ) +from aiohasupervisor.models.discovery import ( + Discovery, + DiscoveryConfig, +) from aiohasupervisor.models.homeassistant import ( HomeAssistantInfo, HomeAssistantOptions, @@ -170,4 +174,7 @@ "NewBackup", "PartialBackupOptions", "PartialRestoreOptions", + "Discovery", + "DiscoveryConfig", + "DiscoveryList", ] diff --git a/aiohasupervisor/models/discovery.py b/aiohasupervisor/models/discovery.py new file mode 100644 index 0000000..f46cffc --- /dev/null +++ b/aiohasupervisor/models/discovery.py @@ -0,0 +1,39 @@ +"""Models for discovery component.""" + +from dataclasses import dataclass +from typing import Any +from uuid import UUID + +from .base import Request, ResponseData + + +@dataclass(frozen=True, slots=True) +class DiscoveryConfig(Request): + """DiscoveryConfig model.""" + + service: str + config: dict[str, Any] + + +@dataclass(frozen=True, slots=True) +class Discovery(ResponseData): + """Discovery model.""" + + addon: str + service: str + uuid: UUID + config: dict[str, Any] + + +@dataclass(frozen=True, slots=True) +class DiscoveryList(ResponseData): + """DiscoveryList model.""" + + discovery: list[Discovery] + + +@dataclass(frozen=True, slots=True) +class SetDiscovery(ResponseData): + """SetDiscovery model.""" + + uuid: UUID diff --git a/aiohasupervisor/root.py b/aiohasupervisor/root.py index c905212..45052dc 100644 --- a/aiohasupervisor/root.py +++ b/aiohasupervisor/root.py @@ -7,6 +7,7 @@ from .addons import AddonsClient from .backups import BackupsClient from .client import _SupervisorClient +from .discovery import DiscoveryClient from .homeassistant import HomeAssistantClient from .models.root import AvailableUpdate, AvailableUpdates, RootInfo from .os import OSClient @@ -30,6 +31,7 @@ def __init__( self._addons = AddonsClient(self._client) self._os = OSClient(self._client) self._backups = BackupsClient(self._client) + self._discovery = DiscoveryClient(self._client) self._resolution = ResolutionClient(self._client) self._store = StoreClient(self._client) self._supervisor = SupervisorManagementClient(self._client) @@ -55,6 +57,11 @@ def backups(self) -> BackupsClient: """Get backups component client.""" return self._backups + @property + def discovery(self) -> DiscoveryClient: + """Get discovery component client.""" + return self._discovery + @property def resolution(self) -> ResolutionClient: """Get resolution center component client.""" diff --git a/tests/fixtures/discovery_get.json b/tests/fixtures/discovery_get.json new file mode 100644 index 0000000..67283ce --- /dev/null +++ b/tests/fixtures/discovery_get.json @@ -0,0 +1,16 @@ +{ + "result": "ok", + "data": { + "addon": "core_mosquitto", + "service": "mqtt", + "uuid": "889ca604cff84004894e53d181655b3a", + "config": { + "host": "core-mosquitto", + "port": 1883, + "ssl": false, + "protocol": "3.1.1", + "username": "homeassistant", + "password": "abc123" + } + } +} diff --git a/tests/fixtures/discovery_list.json b/tests/fixtures/discovery_list.json new file mode 100644 index 0000000..5f5f819 --- /dev/null +++ b/tests/fixtures/discovery_list.json @@ -0,0 +1,45 @@ +{ + "result": "ok", + "data": { + "discovery": [ + { + "addon": "core_mosquitto", + "service": "mqtt", + "uuid": "889ca604cff84004894e53d181655b3a", + "config": { + "host": "core-mosquitto", + "port": 1883, + "ssl": false, + "protocol": "3.1.1", + "username": "homeassistant", + "password": "abc123" + } + } + ], + "services": { + "vlc_telnet": ["core_vlc"], + "esphome": [ + "5c53de3b_esphome-beta", + "5c53de3b_esphome-dev", + "5c53de3b_esphome" + ], + "zwave_js": [ + "core_zwave_js", + "a0d7b954_zwavejs2mqtt", + "77f1785d_zwave_mock_server" + ], + "matter": ["core_matter_server"], + "wyoming": [ + "core_whisper", + "core_assist_microphone", + "core_piper", + "core_openwakeword" + ], + "otbr": ["core_openthread_border_router", "core_silabs_multiprotocol"], + "mqtt": ["core_mosquitto"], + "motioneye": ["a0d7b954_motioneye"], + "deconz": ["core_deconz"], + "adguard": ["a0d7b954_adguard"] + } + } +} diff --git a/tests/fixtures/discovery_set.json b/tests/fixtures/discovery_set.json new file mode 100644 index 0000000..2b33a2c --- /dev/null +++ b/tests/fixtures/discovery_set.json @@ -0,0 +1,6 @@ +{ + "result": "ok", + "data": { + "uuid": "889ca604cff84004894e53d181655b3a" + } +} diff --git a/tests/test_discovery.py b/tests/test_discovery.py new file mode 100644 index 0000000..ee1e622 --- /dev/null +++ b/tests/test_discovery.py @@ -0,0 +1,73 @@ +"""Test discovery supervisor client.""" + +from uuid import UUID + +from aioresponses import aioresponses +from yarl import URL + +from aiohasupervisor import SupervisorClient +from aiohasupervisor.models import DiscoveryConfig + +from . import load_fixture +from .const import SUPERVISOR_URL + + +async def test_discovery_list( + responses: aioresponses, supervisor_client: SupervisorClient +) -> None: + """Test Discovery list API.""" + responses.get( + f"{SUPERVISOR_URL}/discovery", + status=200, + body=load_fixture("discovery_list.json"), + ) + disc_list = await supervisor_client.discovery.list() + assert disc_list[0].addon == "core_mosquitto" + assert disc_list[0].service == "mqtt" + assert disc_list[0].uuid.hex == "889ca604cff84004894e53d181655b3a" + assert disc_list[0].config["host"] == "core-mosquitto" + + +async def test_get_discovery( + responses: aioresponses, supervisor_client: SupervisorClient +) -> None: + """Test Discovery get API.""" + uuid = UUID("889ca604cff84004894e53d181655b3a") + responses.get( + f"{SUPERVISOR_URL}/discovery/{uuid.hex}", + status=200, + body=load_fixture("discovery_get.json"), + ) + discovery = await supervisor_client.discovery.get(uuid) + assert discovery.addon == "core_mosquitto" + assert discovery.service == "mqtt" + assert discovery.uuid == uuid + assert discovery.config["host"] == "core-mosquitto" + assert discovery.config["port"] == 1883 + assert discovery.config["ssl"] is False + + +async def test_delete_discovery( + responses: aioresponses, supervisor_client: SupervisorClient +) -> None: + """Test Discovery delete API.""" + uuid = UUID("889ca604cff84004894e53d181655b3a") + responses.delete(f"{SUPERVISOR_URL}/discovery/{uuid.hex}", status=200) + assert await supervisor_client.discovery.delete(uuid) is None + assert responses.requests.keys() == { + ("DELETE", URL(f"{SUPERVISOR_URL}/discovery/{uuid.hex}")) + } + + +async def test_set_discovery( + responses: aioresponses, supervisor_client: SupervisorClient +) -> None: + """Test Discovery set API.""" + responses.post( + f"{SUPERVISOR_URL}/discovery", + status=200, + body=load_fixture("discovery_set.json"), + ) + assert await supervisor_client.discovery.set( + DiscoveryConfig(service="mqtt", config={}) + ) == UUID("889ca604cff84004894e53d181655b3a")