diff --git a/devtools/dump_devinfo.py b/devtools/dump_devinfo.py index 02aebae7..101ed682 100644 --- a/devtools/dump_devinfo.py +++ b/devtools/dump_devinfo.py @@ -68,6 +68,10 @@ ENCRYPT_TYPES = [encrypt_type.value for encrypt_type in DeviceEncryptionType] _LOGGER = logging.getLogger(__name__) +SMART_SINGLE_ONLY_CALLS = { + "getConnectStatus", +} + def _wrap_redactors(redactors: dict[str, Callable[[Any], Any] | None]): """Wrap the redactors for dump_devinfo. @@ -233,6 +237,12 @@ async def handle_device( type=bool, help="Set flag if the device encryption uses https.", ) +@click.option( + "--timeout", + required=False, + default=15, + help="Timeout for queries.", +) @click.option("--port", help="Port override", type=int) async def cli( host, @@ -250,6 +260,7 @@ async def cli( device_family, login_version, port, + timeout, ): """Generate devinfo files for devices. @@ -280,6 +291,7 @@ async def cli( connection_type=connection_type, port_override=port, credentials=credentials, + timeout=timeout, ) device = await Device.connect(config=dc) await handle_device( @@ -301,6 +313,7 @@ async def cli( port_override=port, credentials=credentials, connection_type=ctype, + timeout=timeout, ) if protocol := get_protocol(config): await handle_device(basedir, autosave, protocol, batch_size=batch_size) @@ -315,11 +328,12 @@ async def cli( credentials=credentials, port=port, discovery_timeout=discovery_timeout, + timeout=timeout, on_discovered_raw=capture_raw, ) discovery_info = raw_discovery[device.host] if decrypted_data := device._discovery_info.get("decrypted_data"): - discovery_info["decrypted_data"] = decrypted_data + discovery_info["result"]["decrypted_data"] = decrypted_data await handle_device( basedir, autosave, @@ -336,13 +350,14 @@ async def cli( target=target, credentials=credentials, discovery_timeout=discovery_timeout, + timeout=timeout, on_discovered_raw=capture_raw, ) click.echo(f"Detected {len(devices)} devices") for dev in devices.values(): discovery_info = raw_discovery[dev.host] if decrypted_data := dev._discovery_info.get("decrypted_data"): - discovery_info["decrypted_data"] = decrypted_data + discovery_info["result"]["decrypted_data"] = decrypted_data await handle_device( basedir, @@ -591,7 +606,11 @@ async def get_smart_camera_test_calls(protocol: SmartProtocol): request=request, should_succeed=True, child_device_id="", - supports_multiple=(method != "get"), + supports_multiple=( + method != "get" + and method[:3] == "get" + and method not in SMART_SINGLE_ONLY_CALLS + ), ) ) @@ -925,6 +944,7 @@ async def get_smart_fixtures( and (child_model := response["get_device_info"].get("model")) and child_model != parent_model ): + response = redact_data(response, _wrap_redactors(SMART_REDACTORS)) fixture_results.append(get_smart_child_fixture(response)) else: cd = final.setdefault("child_devices", {}) @@ -940,13 +960,16 @@ async def get_smart_fixtures( child["device_id"] = scrubbed_device_ids[device_id] # Scrub the device ids in the parent for the smart camera protocol - if gc := final.get("getChildDeviceList"): - for child in gc["child_device_list"]: + if gc := final.get("getChildDeviceComponentList"): + for child in gc["child_component_list"]: + device_id = child["device_id"] + child["device_id"] = scrubbed_device_ids[device_id] + for child in final["getChildDeviceList"]["child_device_list"]: if device_id := child.get("device_id"): child["device_id"] = scrubbed_device_ids[device_id] continue - if device_id := child.get("dev_id"): - child["dev_id"] = scrubbed_device_ids[device_id] + elif dev_id := child.get("dev_id"): + child["dev_id"] = scrubbed_device_ids[dev_id] continue _LOGGER.error("Could not find a device for the child device: %s", child) diff --git a/devtools/helpers/smartcamrequests.py b/devtools/helpers/smartcamrequests.py index 074b5774..5759a44b 100644 --- a/devtools/helpers/smartcamrequests.py +++ b/devtools/helpers/smartcamrequests.py @@ -60,4 +60,7 @@ SMARTCAM_REQUESTS: list[dict] = [ {"get": {"motor": {"name": ["capability"]}}}, {"get": {"audio_capability": {"name": ["device_speaker", "device_microphone"]}}}, {"get": {"audio_config": {"name": ["speaker", "microphone"]}}}, + {"getMatterSetupInfo": {"matter": {}}}, + {"getConnectStatus": {"onboarding": {"get_connect_status": {}}}}, + {"scanApList": {"onboarding": {"scan": {}}}}, ] diff --git a/kasa/protocols/smartprotocol.py b/kasa/protocols/smartprotocol.py index 3d74650a..03ad847c 100644 --- a/kasa/protocols/smartprotocol.py +++ b/kasa/protocols/smartprotocol.py @@ -246,14 +246,18 @@ class SmartProtocol(BaseProtocol): responses = response_step["result"]["responses"] for response in responses: - # smartcam devices sometimes do not populate method if there's - # only one item in the request - if ( - not (method := response.get("method")) - and "result" in response - and len(requests) == 1 - ): - method = next(iter(requests)) + # some smartcam devices calls do not populate the method key + # which we can only handle if there's a single request. + if not (method := response.get("method")): + if len(requests) == 1: + method = next(iter(requests)) + else: + _LOGGER.debug( + "No method key in response for %s, skipping: %s", + self._host, + response, + ) + continue self._handle_response_error_code( response, method, raise_on_error=raise_on_error diff --git a/tests/fixtures/smart/child/S200B(EU)_1.0_1.11.0.json b/tests/fixtures/smart/child/S200B(EU)_1.0_1.11.0.json index 9df75fd7..9f49bf20 100644 --- a/tests/fixtures/smart/child/S200B(EU)_1.0_1.11.0.json +++ b/tests/fixtures/smart/child/S200B(EU)_1.0_1.11.0.json @@ -66,10 +66,10 @@ "category": "subg.trigger.button", "device_id": "SCRUBBED_CHILD_DEVICE_ID_1", "fw_ver": "1.11.0 Build 230821 Rel.113553", - "hw_id": "00000000000000000000000000000000", + "hw_id": "9A1EB033BE24834E2A7A6A4E4CF405E8", "hw_ver": "1.0", - "jamming_rssi": -120, - "jamming_signal_level": 1, + "jamming_rssi": -110, + "jamming_signal_level": 2, "lastOnboardingTimestamp": 1714016798, "mac": "202351000000", "model": "S200B", @@ -78,7 +78,7 @@ "parent_device_id": "0000000000000000000000000000000000000000", "region": "Europe/London", "report_interval": 16, - "rssi": -55, + "rssi": -56, "signal_level": 3, "specs": "EU", "status": "online", @@ -99,7 +99,7 @@ }, "get_latest_fw": { "fw_ver": "1.12.0 Build 231121 Rel.092444", - "hw_id": "00000000000000000000000000000000", + "hw_id": "9A1EB033BE24834E2A7A6A4E4CF405E8", "need_to_upgrade": true, "oem_id": "00000000000000000000000000000000", "release_date": "2024-04-02", diff --git a/tests/fixtures/smartcam/H200(EU)_1.0_1.3.2.json b/tests/fixtures/smartcam/H200(EU)_1.0_1.3.2.json index 9ccaa7e0..fd8511b3 100644 --- a/tests/fixtures/smartcam/H200(EU)_1.0_1.3.2.json +++ b/tests/fixtures/smartcam/H200(EU)_1.0_1.3.2.json @@ -26,6 +26,7 @@ "firmware_version": "1.3.2 Build 20240424 rel.75425", "hardware_version": "1.0", "ip": "127.0.0.123", + "isResetWiFi": false, "is_support_iot_cloud": true, "mac": "A8-6E-84-00-00-00", "mgt_encrypt_schm": { @@ -212,9 +213,9 @@ "category": "subg.trigger.button", "device_id": "SCRUBBED_CHILD_DEVICE_ID_1", "fw_ver": "1.11.0 Build 230821 Rel.113553", - "hw_id": "00000000000000000000000000000000", + "hw_id": "9A1EB033BE24834E2A7A6A4E4CF405E8", "hw_ver": "1.0", - "jamming_rssi": -108, + "jamming_rssi": -110, "jamming_signal_level": 2, "lastOnboardingTimestamp": 1714016798, "mac": "202351000000", @@ -224,7 +225,7 @@ "parent_device_id": "0000000000000000000000000000000000000000", "region": "Europe/London", "report_interval": 16, - "rssi": -66, + "rssi": -56, "signal_level": 3, "specs": "EU", "status": "online", @@ -245,8 +246,19 @@ "getClockStatus": { "system": { "clock_status": { - "local_time": "2024-11-01 13:56:27", - "seconds_from_1970": 1730469387 + "local_time": "2024-12-13 18:34:09", + "seconds_from_1970": 1734114849 + } + } + }, + "getConnectStatus": { + "getConnectStatus": { + "onboarding": { + "get_connect_status": { + "current_ssid": "", + "err_code": 0, + "status": 0 + } } } }, @@ -266,7 +278,7 @@ "device_name": "#MASKED_NAME#", "device_type": "SMART.TAPOHUB", "has_set_location_info": 1, - "hw_id": "00000000000000000000000000000000", + "hw_id": "99A36151C79A3A5D72823C98BC0FDA5F", "hw_version": "1.0", "latitude": 0, "longitude": 0, @@ -289,7 +301,7 @@ "device_name": "#MASKED_NAME#", "device_type": "SMART.TAPOHUB", "has_set_location_info": 1, - "hw_id": "00000000000000000000000000000000", + "hw_id": "99A36151C79A3A5D72823C98BC0FDA5F", "hw_version": "1.0", "latitude": 0, "longitude": 0, @@ -329,6 +341,10 @@ } } }, + "getMatterSetupInfo": { + "setup_code": "00000000000", + "setup_payload": "00:000000-000000000000" + }, "getMediaEncrypt": { "cet": { "media_encrypt": { @@ -353,7 +369,7 @@ "getSirenConfig": { "duration": 300, "siren_type": "Doorbell Ring 1", - "volume": "6" + "volume": "1" }, "getSirenStatus": { "status": "off", @@ -389,5 +405,121 @@ "zone_id": "Europe/London" } } + }, + "scanApList": { + "scanApList": { + "onboarding": { + "scan": { + "ap_list": [ + { + "auth": 3, + "bssid": "000000000000", + "encryption": 2, + "rssi": 2, + "ssid": "I01BU0tFRF9TU0lEIw==" + }, + { + "auth": 3, + "bssid": "000000000000", + "encryption": 2, + "rssi": 1, + "ssid": "I01BU0tFRF9TU0lEIw==" + }, + { + "auth": 3, + "bssid": "000000000000", + "encryption": 2, + "rssi": 1, + "ssid": "I01BU0tFRF9TU0lEIw==" + }, + { + "auth": 0, + "bssid": "000000000000", + "encryption": 0, + "rssi": 1, + "ssid": "I01BU0tFRF9TU0lEIw==" + }, + { + "auth": 3, + "bssid": "000000000000", + "encryption": 2, + "rssi": 1, + "ssid": "I01BU0tFRF9TU0lEIw==" + }, + { + "auth": 3, + "bssid": "000000000000", + "encryption": 2, + "rssi": 0, + "ssid": "I01BU0tFRF9TU0lEIw==" + }, + { + "auth": 3, + "bssid": "000000000000", + "encryption": 2, + "rssi": 3, + "ssid": "I01BU0tFRF9TU0lEIw==" + }, + { + "auth": 3, + "bssid": "000000000000", + "encryption": 2, + "rssi": 3, + "ssid": "I01BU0tFRF9TU0lEIw==" + }, + { + "auth": 4, + "bssid": "000000000000", + "encryption": 2, + "rssi": 0, + "ssid": "I01BU0tFRF9TU0lEIw==" + }, + { + "auth": 0, + "bssid": "000000000000", + "encryption": 0, + "rssi": 0, + "ssid": "I01BU0tFRF9TU0lEIw==" + }, + { + "auth": 3, + "bssid": "000000000000", + "encryption": 2, + "rssi": 0, + "ssid": "I01BU0tFRF9TU0lEIw==" + }, + { + "auth": 3, + "bssid": "000000000000", + "encryption": 2, + "rssi": 1, + "ssid": "I01BU0tFRF9TU0lEIw==" + }, + { + "auth": 4, + "bssid": "000000000000", + "encryption": 3, + "rssi": 2, + "ssid": "I01BU0tFRF9TU0lEIw==" + }, + { + "auth": 3, + "bssid": "000000000000", + "encryption": 2, + "rssi": 1, + "ssid": "I01BU0tFRF9TU0lEIw==" + }, + { + "auth": 3, + "bssid": "000000000000", + "encryption": 2, + "rssi": 1, + "ssid": "I01BU0tFRF9TU0lEIw==" + } + ], + "wpa3_supported": "false" + } + } + } } }