diff --git a/SUPPORTED.md b/SUPPORTED.md index 0146bb6a..0457d8a4 100644 --- a/SUPPORTED.md +++ b/SUPPORTED.md @@ -190,8 +190,10 @@ Some newer Kasa devices require authentication. These are marked with [^1] in th - **KE100** - Hardware: 1.0 (EU) / Firmware: 2.4.0[^1] + - Hardware: 1.0 (EU) / Firmware: 2.6.0[^1] - Hardware: 1.0 (EU) / Firmware: 2.8.0[^1] - Hardware: 1.0 (UK) / Firmware: 2.15.0[^1] + - Hardware: 1.0 (UK) / Firmware: 2.6.0[^1] - Hardware: 1.0 (UK) / Firmware: 2.8.0[^1] - **KE110** - Hardware: 1.0 (EU) / Firmware: 1.4.0[^1] diff --git a/devtools/dump_devinfo.py b/devtools/dump_devinfo.py index fff57370..40162455 100644 --- a/devtools/dump_devinfo.py +++ b/devtools/dump_devinfo.py @@ -878,6 +878,13 @@ def scrub_child_device_ids( _LOGGER.error( "Cannot find device id in child get device info: %s", child_id ) + # Thermostats can reference another child as their temperature source + if (correction := response.get("get_temperature_correction")) and ( + sensor_id := correction.get("temp_sensor_device_id") + ): + correction["temp_sensor_device_id"] = scrubbed_child_id_map.get( + sensor_id, "SCRUBBED_DEVICE_ID" + ) # Scrub the device ids in the parent for smart protocol if gc := main_response.get("get_child_device_component_list"): diff --git a/devtools/helpers/smartrequests.py b/devtools/helpers/smartrequests.py index 1ff37916..3f3a00b1 100644 --- a/devtools/helpers/smartrequests.py +++ b/devtools/helpers/smartrequests.py @@ -426,6 +426,17 @@ COMPONENT_REQUESTS = { ) ], "temp_humidity_record": [SmartRequest.get_raw_request("get_temp_humidity_records")], + "temp_record": [SmartRequest.get_raw_request("get_temperature_records")], + "temperature_correction": [ + SmartRequest.get_raw_request("get_temperature_correction") + ], + "smart_control_schedule": [ + SmartRequest.get_raw_request("get_sc_schedule_info"), + SmartRequest.get_raw_request( + "get_sc_schedule_rules", + SmartRequest.GetScheduleRulesParams(schedule_mode="everyday"), + ), + ], "double_click": [SmartRequest.get_raw_request("get_double_click_info")], "child_device": [ SmartRequest.get_raw_request("get_child_device_list"), diff --git a/kasa/protocols/smartprotocol.py b/kasa/protocols/smartprotocol.py index e10a8538..96f88185 100644 --- a/kasa/protocols/smartprotocol.py +++ b/kasa/protocols/smartprotocol.py @@ -55,6 +55,7 @@ REDACTORS: dict[str, Callable[[Any], Any] | None] = { "lo": lambda x: 0, # lon on ks240 "device_id": lambda x: "REDACTED_" + x[9::], "parent_device_id": lambda x: "REDACTED_" + x[9::], # Hub attached children + "temp_sensor_device_id": lambda x: "REDACTED_" + x[9::], # Thermostat source "original_device_id": lambda x: "REDACTED_" + x[9::], # Strip children "nickname": lambda x: "I01BU0tFRF9OQU1FIw==" if x else "", "mac": mask_mac, diff --git a/tests/fixtures/smart/child/KE100(EU)_1.0_2.6.0.json b/tests/fixtures/smart/child/KE100(EU)_1.0_2.6.0.json new file mode 100644 index 00000000..ede63dc0 --- /dev/null +++ b/tests/fixtures/smart/child/KE100(EU)_1.0_2.6.0.json @@ -0,0 +1,322 @@ +{ + "component_nego": { + "component_list": [ + { + "id": "device", + "ver_code": 2 + }, + { + "id": "quick_setup", + "ver_code": 3 + }, + { + "id": "time", + "ver_code": 1 + }, + { + "id": "device_local_time", + "ver_code": 1 + }, + { + "id": "schedule", + "ver_code": 2 + }, + { + "id": "countdown", + "ver_code": 2 + }, + { + "id": "account", + "ver_code": 1 + }, + { + "id": "synchronize", + "ver_code": 1 + }, + { + "id": "sunrise_sunset", + "ver_code": 1 + }, + { + "id": "cloud_connect", + "ver_code": 1 + }, + { + "id": "iot_cloud", + "ver_code": 1 + }, + { + "id": "frost_protection", + "ver_code": 1 + }, + { + "id": "child_protection", + "ver_code": 1 + }, + { + "id": "temperature", + "ver_code": 1 + }, + { + "id": "remove_scale", + "ver_code": 1 + }, + { + "id": "early_start", + "ver_code": 1 + }, + { + "id": "screen_setting", + "ver_code": 1 + }, + { + "id": "night_mode", + "ver_code": 1 + }, + { + "id": "firmware", + "ver_code": 1 + }, + { + "id": "battery_detect", + "ver_code": 1 + }, + { + "id": "temp_record", + "ver_code": 1 + }, + { + "id": "progress_calibration", + "ver_code": 1 + }, + { + "id": "temperature_correction", + "ver_code": 1 + }, + { + "id": "window_open_detect", + "ver_code": 2 + }, + { + "id": "shutdown_mode", + "ver_code": 1 + }, + { + "id": "smart_control_schedule", + "ver_code": 1 + }, + { + "id": "temp_control", + "ver_code": 1 + } + ] + }, + "get_connect_cloud_state": { + "status": 0 + }, + "get_device_info": { + "at_low_battery": false, + "avatar": "", + "battery_percentage": 100, + "bind_count": 1, + "category": "subg.trv", + "child_protection": false, + "current_temp": 23.9, + "device_id": "SCRUBBED_CHILD_DEVICE_ID_4", + "frost_protection_on": false, + "fw_ver": "2.6.0 Build 231027 Rel.121035", + "hidden": false, + "hw_id": "00000000000000000000000000000000", + "hw_ver": "1.0", + "jamming_rssi": -121, + "jamming_signal_level": 1, + "lastOnboardingTimestamp": 1792686811, + "location": "", + "mac": "B8FBB3000000", + "max_control_temp": 30, + "min_control_temp": 5, + "model": "KE100", + "nickname": "I01BU0tFRF9OQU1FIw==", + "oem_id": "00000000000000000000000000000000", + "parent_device_id": "0000000000000000000000000000000000000000", + "region": "Europe/London", + "rssi": -43, + "signal_level": 3, + "specs": "EU", + "status": "online", + "target_temp": 20.0, + "temp_offset": 0, + "temp_unit": "celsius", + "trv_states": [], + "type": "SMART.KASAENERGY" + }, + "get_fw_download_state": { + "cloud_cache_seconds": 1, + "download_progress": 0, + "reboot_time": 5, + "status": 0, + "upgrade_time": 5 + }, + "get_latest_fw": { + "fw_ver": "2.15.0 Build 260212 Rel.072732", + "hw_id": "00000000000000000000000000000000", + "need_to_upgrade": true, + "oem_id": "00000000000000000000000000000000", + "release_date": "2026-03-12", + "release_note": "Modifications and Bug Fixes:\n1. Added support for Standard, Powerful and Gentle valve control modes in Device Settings -> Valve Control, for better adaptation to various usage scenarios.\n2. Added support for 0.5 degrees Celsius precise temperature adjustment.\n3. Optimized device performance.", + "type": 2 + }, + "get_sc_schedule_info": { + "enable": true, + "mode": "everyday" + }, + "get_sc_schedule_rules": { + "day_segment": 6, + "schedule_list": [ + { + "id": "everyday:everyday", + "rule_list": [ + { + "desired_states": { + "frost_protection_on": false, + "target_temp": 18, + "target_temp_float": 180, + "temp_unit": "celsius" + }, + "end_min": 419, + "start_min": 0 + }, + { + "desired_states": { + "frost_protection_on": false, + "target_temp": 20, + "target_temp_float": 200, + "temp_unit": "celsius" + }, + "end_min": 1319, + "start_min": 420 + }, + { + "desired_states": { + "frost_protection_on": false, + "target_temp": 18, + "target_temp_float": 180, + "temp_unit": "celsius" + }, + "end_min": 1439, + "start_min": 1320 + } + ] + } + ], + "schedule_mode": "everyday", + "start_index": 0, + "sum": 1 + }, + "get_temperature_correction": { + "offset": 0, + "temp_sensor_device_id": "SCRUBBED_CHILD_DEVICE_ID_4", + "use_other_temp_sensor": false, + "using_other_temp_sensor": false + }, + "get_temperature_records": { + "temp_data": [ + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + 267, + 266, + 256, + 241, + 229, + 237, + 243, + 240, + 230, + 236, + 242, + 243 + ] + } +} diff --git a/tests/fixtures/smart/child/KE100(UK)_1.0_2.6.0.json b/tests/fixtures/smart/child/KE100(UK)_1.0_2.6.0.json new file mode 100644 index 00000000..3d2dac3d --- /dev/null +++ b/tests/fixtures/smart/child/KE100(UK)_1.0_2.6.0.json @@ -0,0 +1,334 @@ +{ + "component_nego": { + "component_list": [ + { + "id": "device", + "ver_code": 2 + }, + { + "id": "quick_setup", + "ver_code": 3 + }, + { + "id": "time", + "ver_code": 1 + }, + { + "id": "device_local_time", + "ver_code": 1 + }, + { + "id": "schedule", + "ver_code": 2 + }, + { + "id": "countdown", + "ver_code": 2 + }, + { + "id": "account", + "ver_code": 1 + }, + { + "id": "synchronize", + "ver_code": 1 + }, + { + "id": "sunrise_sunset", + "ver_code": 1 + }, + { + "id": "cloud_connect", + "ver_code": 1 + }, + { + "id": "iot_cloud", + "ver_code": 1 + }, + { + "id": "frost_protection", + "ver_code": 1 + }, + { + "id": "child_protection", + "ver_code": 1 + }, + { + "id": "temperature", + "ver_code": 1 + }, + { + "id": "remove_scale", + "ver_code": 1 + }, + { + "id": "early_start", + "ver_code": 1 + }, + { + "id": "screen_setting", + "ver_code": 1 + }, + { + "id": "night_mode", + "ver_code": 1 + }, + { + "id": "firmware", + "ver_code": 1 + }, + { + "id": "battery_detect", + "ver_code": 1 + }, + { + "id": "temp_record", + "ver_code": 1 + }, + { + "id": "progress_calibration", + "ver_code": 1 + }, + { + "id": "temperature_correction", + "ver_code": 1 + }, + { + "id": "window_open_detect", + "ver_code": 2 + }, + { + "id": "shutdown_mode", + "ver_code": 1 + }, + { + "id": "smart_control_schedule", + "ver_code": 1 + }, + { + "id": "temp_control", + "ver_code": 1 + } + ] + }, + "get_connect_cloud_state": { + "status": 0 + }, + "get_device_info": { + "at_low_battery": false, + "avatar": "", + "battery_percentage": 100, + "bind_count": 1, + "category": "subg.trv", + "child_protection": false, + "current_temp": 22.9, + "device_id": "SCRUBBED_CHILD_DEVICE_ID_2", + "frost_protection_on": false, + "fw_ver": "2.6.0 Build 231027 Rel.121035", + "hidden": false, + "hw_id": "00000000000000000000000000000000", + "hw_ver": "1.0", + "jamming_rssi": -116, + "jamming_signal_level": 1, + "lastOnboardingTimestamp": 1792687045, + "location": "", + "mac": "0CEF15000000", + "max_control_temp": 30, + "min_control_temp": 5, + "model": "KE100", + "nickname": "I01BU0tFRF9OQU1FIw==", + "oem_id": "00000000000000000000000000000000", + "parent_device_id": "0000000000000000000000000000000000000000", + "region": "Europe/London", + "rssi": -54, + "signal_level": 3, + "specs": "UK", + "status": "online", + "target_temp": 23.0, + "temp_offset": 0, + "temp_unit": "celsius", + "trv_states": [ + "heating" + ], + "type": "SMART.KASAENERGY" + }, + "get_fw_download_state": { + "cloud_cache_seconds": 1, + "download_progress": 0, + "reboot_time": 5, + "status": 0, + "upgrade_time": 5 + }, + "get_latest_fw": { + "fw_ver": "2.15.0 Build 260212 Rel.072732", + "hw_id": "00000000000000000000000000000000", + "need_to_upgrade": true, + "oem_id": "00000000000000000000000000000000", + "release_date": "2026-03-12", + "release_note": "Modifications and Bug Fixes:\n1. Added support for Standard, Powerful and Gentle valve control modes in Device Settings -> Valve Control, for better adaptation to various usage scenarios.\n2. Added support for 0.5 degrees Celsius precise temperature adjustment.\n3. Optimized device performance.", + "type": 2 + }, + "get_sc_schedule_info": { + "enable": true, + "mode": "everyday" + }, + "get_sc_schedule_rules": { + "day_segment": 6, + "schedule_list": [ + { + "id": "everyday:everyday", + "rule_list": [ + { + "desired_states": { + "frost_protection_on": false, + "target_temp": 18, + "target_temp_float": 180, + "temp_unit": "celsius" + }, + "end_min": 479, + "start_min": 0 + }, + { + "desired_states": { + "frost_protection_on": false, + "target_temp": 23, + "target_temp_float": 230, + "temp_unit": "celsius" + }, + "end_min": 1319, + "start_min": 1081 + }, + { + "desired_states": { + "frost_protection_on": false, + "target_temp": 18, + "target_temp_float": 180, + "temp_unit": "celsius" + }, + "end_min": 1439, + "start_min": 1320 + }, + { + "desired_states": { + "frost_protection_on": false, + "target_temp": 20, + "target_temp_float": 200, + "temp_unit": "celsius" + }, + "end_min": 1080, + "start_min": 480 + } + ] + } + ], + "schedule_mode": "everyday", + "start_index": 0, + "sum": 1 + }, + "get_temperature_correction": { + "offset": 0, + "temp_sensor_device_id": "SCRUBBED_CHILD_DEVICE_ID_2", + "use_other_temp_sensor": false, + "using_other_temp_sensor": false + }, + "get_temperature_records": { + "temp_data": [ + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + 231, + 230, + 225, + 222, + 227, + 231, + 229, + 227, + 236, + 240, + 238, + 235 + ] + } +} diff --git a/tests/test_devtools.py b/tests/test_devtools.py index 57ffe348..07cb554c 100644 --- a/tests/test_devtools.py +++ b/tests/test_devtools.py @@ -8,6 +8,7 @@ from devtools.dump_devinfo import ( _wrap_redactors, get_legacy_fixture, get_smart_fixtures, + scrub_child_device_ids, ) from kasa.iot import IotDevice from kasa.protocols import IotProtocol @@ -157,3 +158,25 @@ async def test_iot_fixtures(fixture_info: FixtureInfo) -> None: key: val for key, val in fixture_info.data.items() if "err_code" not in val } assert saved_fixture == created_fixture + + +def test_scrub_child_temperature_sensor_ids(): + """Test thermostat temperature sources are mapped to scrubbed child ids.""" + children = { + "trv": { + "get_device_info": {"device_id": "trv"}, + "get_temperature_correction": {"temp_sensor_device_id": "sensor"}, + }, + "sensor": {"get_device_info": {"device_id": "sensor"}}, + "other": { + "get_device_info": {"device_id": "other"}, + "get_temperature_correction": {"temp_sensor_device_id": "not-a-child"}, + }, + } + + id_map = scrub_child_device_ids({}, children) + + trv_source = children["trv"]["get_temperature_correction"] + assert trv_source["temp_sensor_device_id"] == id_map["sensor"] + other_source = children["other"]["get_temperature_correction"] + assert other_source["temp_sensor_device_id"] == "SCRUBBED_DEVICE_ID"