Skip to content

Commit 56967b5

Browse files
authored
Add support for config_manager.lookup_device (#1189)
* Add support for `config_manager.lookup_device` * fix test * lint * PR comments * fix type
1 parent aabcd85 commit 56967b5

File tree

7 files changed

+160
-0
lines changed

7 files changed

+160
-0
lines changed

test/conftest.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,12 @@ def lock_ultraloq_ubolt_pro_state_fixture():
127127
return json.loads(load_fixture("lock_ultraloq_ubolt_pro_state.json"))
128128

129129

130+
@pytest.fixture(name="device_config", scope="session")
131+
def device_config_fixture() -> dict[str, Any]:
132+
"""Load the device config fixture data."""
133+
return json.loads(load_fixture("device_config.json"))
134+
135+
130136
@pytest.fixture(name="client_session")
131137
def client_session_fixture(ws_client: AsyncMock) -> AsyncMock:
132138
"""Mock an aiohttp client session."""

test/fixtures/device_config.json

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"filename": "test_device.json",
3+
"manufacturer": "Test Manufacturer",
4+
"manufacturerId": "0x1234",
5+
"label": "Test Device",
6+
"description": "Test Device Description",
7+
"devices": [
8+
{
9+
"productType": "0x5678",
10+
"productId": "0x9ABC"
11+
}
12+
],
13+
"firmwareVersion": {
14+
"min": "1.0",
15+
"max": "2.0"
16+
},
17+
"associations": {},
18+
"paramInformation": {},
19+
"supportsZWavePlus": true,
20+
"proprietary": {},
21+
"compat": {}
22+
}

test/model/test_config_manager.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
"""Test the config manager."""
2+
3+
from zwave_js_server.client import Client
4+
from zwave_js_server.model.config_manager import ConfigManager
5+
from zwave_js_server.model.device_config import DeviceConfigDataType
6+
7+
from ..common import MockCommandProtocol
8+
9+
10+
async def test_lookup_device(
11+
client: Client,
12+
mock_command: MockCommandProtocol,
13+
device_config: DeviceConfigDataType,
14+
) -> None:
15+
"""Test the lookup_device command."""
16+
# Test successful lookup
17+
mock_command(
18+
{
19+
"command": "config_manager.lookup_device",
20+
"manufacturerId": 0x1234,
21+
"productType": 0x5678,
22+
"productId": 0x9ABC,
23+
},
24+
{"config": device_config},
25+
)
26+
27+
config_manager = ConfigManager(client)
28+
result = await config_manager.lookup_device(0x1234, 0x5678, 0x9ABC)
29+
30+
assert result is not None
31+
assert result.manufacturer == "Test Manufacturer"
32+
assert result.manufacturer_id == "0x1234"
33+
assert result.label == "Test Device"
34+
assert result.description == "Test Device Description"
35+
assert len(result.devices) == 1
36+
assert result.devices[0].product_type == "0x5678"
37+
assert result.devices[0].product_id == "0x9ABC"
38+
assert result.firmware_version.min == "1.0"
39+
assert result.firmware_version.max == "2.0"
40+
41+
# Test lookup with firmware version
42+
mock_command(
43+
{
44+
"command": "config_manager.lookup_device",
45+
"manufacturerId": 0x1234,
46+
"productType": 0x5678,
47+
"productId": 0x9ABC,
48+
"firmwareVersion": "1.5",
49+
},
50+
{"config": device_config},
51+
)
52+
53+
result = await config_manager.lookup_device(0x1234, 0x5678, 0x9ABC, "1.5")
54+
55+
assert result is not None
56+
assert result.manufacturer == "Test Manufacturer"
57+
58+
assert result.to_dict() == device_config
59+
60+
# Test device not found
61+
mock_command(
62+
{
63+
"command": "config_manager.lookup_device",
64+
"manufacturerId": 0x4321,
65+
"productType": 0x8765,
66+
"productId": 0xCBA9,
67+
},
68+
{"config": None},
69+
)
70+
71+
result = await config_manager.lookup_device(0x4321, 0x8765, 0xCBA9)
72+
73+
assert result is None

test/model/test_driver.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -390,3 +390,9 @@ async def test_all_nodes_ready_event(driver):
390390
"""Test that the all nodes ready event is succesfully validated by pydantic."""
391391
event = Event("all nodes ready", {"source": "driver", "event": "all nodes ready"})
392392
driver.receive_event(event)
393+
394+
395+
def test_config_manager(driver):
396+
"""Test the driver has the config manager property."""
397+
assert driver.config_manager is not None
398+
assert driver.config_manager._client is driver.client
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
"""
2+
Model for Z-Wave JS config manager.
3+
4+
https://zwave-js.github.io/node-zwave-js/#/api/config-manager
5+
"""
6+
7+
from __future__ import annotations
8+
9+
from typing import TYPE_CHECKING, Any, Optional
10+
11+
from ..device_config import DeviceConfig
12+
13+
if TYPE_CHECKING:
14+
from ...client import Client
15+
16+
17+
class ConfigManager:
18+
"""Model for the Z-Wave JS config manager."""
19+
20+
def __init__(self, client: Client) -> None:
21+
"""Initialize."""
22+
self._client = client
23+
24+
async def lookup_device(
25+
self,
26+
manufacturer_id: int,
27+
product_type: int,
28+
product_id: int,
29+
firmware_version: Optional[str] = None,
30+
) -> DeviceConfig | None:
31+
"""Look up the definition of a given device in the configuration DB."""
32+
cmd: dict[str, Any] = {
33+
"command": "config_manager.lookup_device",
34+
"manufacturerId": manufacturer_id,
35+
"productType": product_type,
36+
"productId": product_id,
37+
}
38+
39+
if firmware_version is not None:
40+
cmd["firmwareVersion"] = firmware_version
41+
42+
data = await self._client.async_send_command(cmd)
43+
44+
if not data or not (config := data.get("config")):
45+
return None
46+
47+
return DeviceConfig(config)

zwave_js_server/model/device_config.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,3 +222,7 @@ def metadata(self) -> DeviceMetadata:
222222
def is_embedded(self) -> bool | None:
223223
"""Return whether device config is embedded in zwave-js-server."""
224224
return self.data.get("isEmbedded")
225+
226+
def to_dict(self) -> DeviceConfigDataType:
227+
"""Return dict representation of device config."""
228+
return self.data.copy()

zwave_js_server/model/driver.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from typing import TYPE_CHECKING, Any, Literal, cast
66

77
from ..event import BaseEventModel, Event, EventBase
8+
from .config_manager import ConfigManager
89
from .controller import Controller
910
from .log_config import LogConfig, LogConfigDataType
1011
from .log_message import LogMessage, LogMessageDataType
@@ -79,6 +80,7 @@ def __init__(
7980
self.client = client
8081
self.controller = Controller(client, state)
8182
self.log_config = LogConfig.from_dict(log_config)
83+
self.config_manager = ConfigManager(client)
8284

8385
def __hash__(self) -> int:
8486
"""Return the hash."""

0 commit comments

Comments
 (0)