mirror of
https://github.com/python-kasa/python-kasa.git
synced 2025-08-09 20:24:02 +00:00
Add childsetup module to smartcam hubs (#1469)
Add the `childsetup` module for `smartcam` hubs to allow pairing and unpairing child devices.
This commit is contained in:
@@ -153,7 +153,33 @@ class FakeSmartCamTransport(BaseTransport):
|
||||
"setup_code": "00000000000",
|
||||
"setup_payload": "00:0000000-0000.00.000",
|
||||
},
|
||||
)
|
||||
),
|
||||
"getSupportChildDeviceCategory": (
|
||||
"childQuickSetup",
|
||||
{
|
||||
"device_category_list": [
|
||||
{"category": "ipcamera"},
|
||||
{"category": "subg.trv"},
|
||||
{"category": "subg.trigger"},
|
||||
{"category": "subg.plugswitch"},
|
||||
]
|
||||
},
|
||||
),
|
||||
"getScanChildDeviceList": (
|
||||
"childQuickSetup",
|
||||
{
|
||||
"child_device_list": [
|
||||
{
|
||||
"device_id": "0000000000000000000000000000000000000000",
|
||||
"category": "subg.trigger.button",
|
||||
"device_model": "S200B",
|
||||
"name": "I01BU0tFRF9OQU1FIw====",
|
||||
}
|
||||
],
|
||||
"scan_wait_time": 55,
|
||||
"scan_status": "scanning",
|
||||
},
|
||||
),
|
||||
}
|
||||
# Setters for when there's not a simple mapping of setters to getters
|
||||
SETTERS = {
|
||||
@@ -179,6 +205,17 @@ class FakeSmartCamTransport(BaseTransport):
|
||||
],
|
||||
}
|
||||
|
||||
def _hub_remove_device(self, info, params):
|
||||
"""Remove hub device."""
|
||||
items_to_remove = [dev["device_id"] for dev in params["child_device_list"]]
|
||||
children = info["getChildDeviceList"]["child_device_list"]
|
||||
new_children = [
|
||||
dev for dev in children if dev["device_id"] not in items_to_remove
|
||||
]
|
||||
info["getChildDeviceList"]["child_device_list"] = new_children
|
||||
|
||||
return {"result": {}, "error_code": 0}
|
||||
|
||||
@staticmethod
|
||||
def _get_second_key(request_dict: dict[str, Any]) -> str:
|
||||
assert (
|
||||
@@ -269,6 +306,14 @@ class FakeSmartCamTransport(BaseTransport):
|
||||
return {**result, "error_code": 0}
|
||||
else:
|
||||
return {"error_code": -1}
|
||||
elif method == "removeChildDeviceList":
|
||||
return self._hub_remove_device(info, request_dict["params"]["childControl"])
|
||||
# actions
|
||||
elif method in [
|
||||
"addScanChildDeviceList",
|
||||
"startScanChildDevice",
|
||||
]:
|
||||
return {"result": {}, "error_code": 0}
|
||||
|
||||
# smartcam child devices do not make requests for getDeviceInfo as they
|
||||
# get updated from the parent's query. If this is being called from a
|
||||
|
103
tests/smartcam/modules/test_childsetup.py
Normal file
103
tests/smartcam/modules/test_childsetup.py
Normal file
@@ -0,0 +1,103 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
|
||||
import pytest
|
||||
from pytest_mock import MockerFixture
|
||||
|
||||
from kasa import Feature, Module, SmartDevice
|
||||
|
||||
from ...device_fixtures import parametrize
|
||||
|
||||
childsetup = parametrize(
|
||||
"supports pairing", component_filter="childQuickSetup", protocol_filter={"SMARTCAM"}
|
||||
)
|
||||
|
||||
|
||||
@childsetup
|
||||
async def test_childsetup_features(dev: SmartDevice):
|
||||
"""Test the exposed features."""
|
||||
cs = dev.modules[Module.ChildSetup]
|
||||
|
||||
assert "pair" in cs._module_features
|
||||
pair = cs._module_features["pair"]
|
||||
assert pair.type == Feature.Type.Action
|
||||
|
||||
|
||||
@childsetup
|
||||
async def test_childsetup_pair(
|
||||
dev: SmartDevice, mocker: MockerFixture, caplog: pytest.LogCaptureFixture
|
||||
):
|
||||
"""Test device pairing."""
|
||||
caplog.set_level(logging.INFO)
|
||||
mock_query_helper = mocker.spy(dev, "_query_helper")
|
||||
mocker.patch("asyncio.sleep")
|
||||
|
||||
cs = dev.modules[Module.ChildSetup]
|
||||
|
||||
await cs.pair()
|
||||
|
||||
mock_query_helper.assert_has_awaits(
|
||||
[
|
||||
mocker.call(
|
||||
"startScanChildDevice",
|
||||
params={
|
||||
"childControl": {
|
||||
"category": [
|
||||
"camera",
|
||||
"subg.trv",
|
||||
"subg.trigger",
|
||||
"subg.plugswitch",
|
||||
]
|
||||
}
|
||||
},
|
||||
),
|
||||
mocker.call(
|
||||
"getScanChildDeviceList",
|
||||
{
|
||||
"childControl": {
|
||||
"category": [
|
||||
"camera",
|
||||
"subg.trv",
|
||||
"subg.trigger",
|
||||
"subg.plugswitch",
|
||||
]
|
||||
}
|
||||
},
|
||||
),
|
||||
mocker.call(
|
||||
"addScanChildDeviceList",
|
||||
{
|
||||
"childControl": {
|
||||
"child_device_list": [
|
||||
{
|
||||
"device_id": "0000000000000000000000000000000000000000",
|
||||
"category": "subg.trigger.button",
|
||||
"device_model": "S200B",
|
||||
"name": "I01BU0tFRF9OQU1FIw====",
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
),
|
||||
]
|
||||
)
|
||||
assert "Discovery done" in caplog.text
|
||||
|
||||
|
||||
@childsetup
|
||||
async def test_childsetup_unpair(
|
||||
dev: SmartDevice, mocker: MockerFixture, caplog: pytest.LogCaptureFixture
|
||||
):
|
||||
"""Test unpair."""
|
||||
mock_query_helper = mocker.spy(dev, "_query_helper")
|
||||
DUMMY_ID = "dummy_id"
|
||||
|
||||
cs = dev.modules[Module.ChildSetup]
|
||||
|
||||
await cs.unpair(DUMMY_ID)
|
||||
|
||||
mock_query_helper.assert_awaited_with(
|
||||
"removeChildDeviceList",
|
||||
params={"childControl": {"child_device_list": [{"device_id": DUMMY_ID}]}},
|
||||
)
|
@@ -267,7 +267,11 @@ async def test_raw_command(dev, mocker, runner):
|
||||
from kasa.smart import SmartDevice
|
||||
|
||||
if isinstance(dev, SmartCamDevice):
|
||||
params = ["na", "getDeviceInfo"]
|
||||
params = [
|
||||
"na",
|
||||
"getDeviceInfo",
|
||||
'{"device_info": {"name": ["basic_info", "info"]}}',
|
||||
]
|
||||
elif isinstance(dev, SmartDevice):
|
||||
params = ["na", "get_device_info"]
|
||||
else:
|
||||
|
@@ -191,12 +191,12 @@ async def test_feature_setters(dev: Device, mocker: MockerFixture):
|
||||
exceptions = []
|
||||
for feat in dev.features.values():
|
||||
try:
|
||||
prot = (
|
||||
feat.container._device.protocol
|
||||
if feat.container
|
||||
else feat.device.protocol
|
||||
)
|
||||
with patch.object(prot, "query", name=feat.id) as query:
|
||||
patch_dev = feat.container._device if feat.container else feat.device
|
||||
with (
|
||||
patch.object(patch_dev.protocol, "query", name=feat.id) as query,
|
||||
# patch update in case feature setter does an update
|
||||
patch.object(patch_dev, "update"),
|
||||
):
|
||||
await _test_feature(feat, query)
|
||||
# we allow our own exceptions to avoid mocking valid responses
|
||||
except KasaException:
|
||||
|
Reference in New Issue
Block a user