diff --git a/README.md b/README.md index 6c151b34..64450aa4 100644 --- a/README.md +++ b/README.md @@ -228,9 +228,10 @@ The following lists the devices that have been manually verified to work. * KP105 * KP115 * KP125 -* KP125M +* KP125M [See note below](#tapo-and-newer-kasa-branded-devices) * KP401 * EP10 +* EP25 [See note below](#tapo-and-newer-kasa-branded-devices) ### Power Strips @@ -268,18 +269,25 @@ The following lists the devices that have been manually verified to work. ### Light strips -* KL400 -* KL420 +* KL400L5 +* KL420L5 * KL430 -### Tapo-branded devices +### Tapo and newer Kasa branded devices -The library has recently added a limited supported for devices that carry tapo branding. +The library has recently added a limited supported for devices that carry Tapo branding. At the moment, the following devices have been confirmed to work: * Tapo P110 (plug) -* Tapo L530 (bulb) +* Tapo L530E (bulb) + +Some newer hardware versions of Kasa branded devices are now using the same protocol as +Tapo branded devices. Support for these devices is currently limited as per TAPO branded +devices: + +* Kasa EP25 (plug) hw_version 2.6 +* Kasa KP125M (plug) **If your device is unlisted but working, please open a pull request to update the list and add a fixture file (use `devtools/dump_devinfo.py` to generate one).** @@ -301,6 +309,12 @@ At the moment, the following devices have been confirmed to work: ### TP-Link Tapo support +This library has recently added a limited supported for devices that carry Tapo branding. +That support is currently limited to the cli. The package `kasa.tapo` is in flux and if you +use it directly you should expect it could break in future releases until this statement is removed. + +Other TAPO libraries are: + * [PyTapo - Python library for communication with Tapo Cameras](https://github.com/JurajNyiri/pytapo) * [Tapo P100 (Tapo P105/P100 plugs, Tapo L510E bulbs)](https://github.com/fishbigger/TapoP100) * [Home Assistant integration](https://github.com/fishbigger/HomeAssistant-Tapo-P100-Control) diff --git a/kasa/tests/conftest.py b/kasa/tests/conftest.py index 53e2c494..f84083e2 100644 --- a/kasa/tests/conftest.py +++ b/kasa/tests/conftest.py @@ -43,8 +43,8 @@ SUPPORTED_SMART_DEVICES = [ SUPPORTED_DEVICES = SUPPORTED_IOT_DEVICES + SUPPORTED_SMART_DEVICES # Tapo bulbs -BULBS_SMART_VARIABLE_TEMP = {"L530"} -BULBS_SMART_COLOR = {"L530"} +BULBS_SMART_VARIABLE_TEMP = {"L530E"} +BULBS_SMART_COLOR = {"L530E"} BULBS_SMART_LIGHT_STRIP: Set[str] = set() BULBS_SMART_DIMMABLE: Set[str] = set() BULBS_SMART = ( @@ -54,7 +54,7 @@ BULBS_SMART = ( ) # Kasa (IOT-prefixed) bulbs -BULBS_IOT_LIGHT_STRIP = {"KL400", "KL430", "KL420"} +BULBS_IOT_LIGHT_STRIP = {"KL400L5", "KL430", "KL420L5"} BULBS_IOT_VARIABLE_TEMP = { "LB120", "LB130", @@ -83,7 +83,7 @@ BULBS = { } -PLUGS = { +PLUGS_IOT = { "HS100", "HS103", "HS105", @@ -95,22 +95,35 @@ PLUGS = { "KP105", "KP115", "KP125", - "KP125M", "KP401", "KS200M", } +PLUGS_SMART = {"P110", "KP125M", "EP25"} +PLUGS = { + *PLUGS_IOT, + *PLUGS_SMART, +} +STRIPS_IOT = {"HS107", "HS300", "KP303", "KP200", "KP400", "EP40"} +STRIPS_SMART = {} # type: ignore[var-annotated] +STRIPS = {*STRIPS_IOT, *STRIPS_SMART} -STRIPS = {"HS107", "HS300", "KP303", "KP200", "KP400", "EP40"} -DIMMERS = {"ES20M", "HS220", "KS220M", "KS230", "KP405"} +DIMMERS_IOT = {"ES20M", "HS220", "KS220M", "KS230", "KP405"} +DIMMERS_SMART = {} # type: ignore[var-annotated] +DIMMERS = { + *DIMMERS_IOT, + *DIMMERS_SMART, +} + +WITH_EMETER_IOT = {"HS110", "HS300", "KP115", "KP125", *BULBS_IOT} +WITH_EMETER_SMART = {*PLUGS_SMART} +WITH_EMETER = {*WITH_EMETER_IOT, *WITH_EMETER_SMART} DIMMABLE = {*BULBS, *DIMMERS} -WITH_EMETER = {"HS110", "HS300", "KP115", "KP125", "KP125M", *BULBS} - -ALL_DEVICES_IOT = BULBS.union(PLUGS).union(STRIPS).union(DIMMERS) - -PLUGS_SMART = {"P110", "KP125M"} -ALL_DEVICES_SMART = BULBS_SMART.union(PLUGS_SMART) +ALL_DEVICES_IOT = BULBS_IOT.union(PLUGS_IOT).union(STRIPS_IOT).union(DIMMERS_IOT) +ALL_DEVICES_SMART = ( + BULBS_SMART.union(PLUGS_SMART).union(STRIPS_SMART).union(DIMMERS_SMART) +) ALL_DEVICES = ALL_DEVICES_IOT.union(ALL_DEVICES_SMART) IP_MODEL_CACHE: Dict[str, str] = {} @@ -126,14 +139,15 @@ def idgenerator(paramtuple): def filter_model(desc, model_filter, protocol_filter=None): - if not protocol_filter: - protocol_filter = {"IOT"} + if protocol_filter is None: + protocol_filter = {"IOT", "SMART"} filtered = list() for file, protocol in SUPPORTED_DEVICES: if protocol in protocol_filter: - file_model = basename(file).split("_")[0] + file_model_region = basename(file).split("_")[0] + file_model = file_model_region.split("(")[0] for model in model_filter: - if model in file_model: + if model == file_model: filtered.append((file, protocol)) filtered_basenames = [basename(f) + "-" + p for f, p in filtered] @@ -151,30 +165,40 @@ def parametrize(desc, devices, protocol_filter=None, ids=None): ) -has_emeter = parametrize("has emeter", WITH_EMETER) -no_emeter = parametrize("no emeter", ALL_DEVICES_IOT - WITH_EMETER) +has_emeter = parametrize("has emeter", WITH_EMETER_IOT, protocol_filter={"IOT"}) +no_emeter = parametrize( + "no emeter", ALL_DEVICES_IOT - WITH_EMETER_IOT, protocol_filter={"SMART", "IOT"} +) bulb = parametrize("bulbs", BULBS, protocol_filter={"SMART", "IOT"}) -plug = parametrize("plugs", PLUGS) -strip = parametrize("strips", STRIPS) -dimmer = parametrize("dimmers", DIMMERS) -lightstrip = parametrize("lightstrips", LIGHT_STRIPS) +plug = parametrize("plugs", PLUGS, protocol_filter={"IOT"}) +strip = parametrize("strips", STRIPS, protocol_filter={"IOT"}) +dimmer = parametrize("dimmers", DIMMERS, protocol_filter={"IOT"}) +lightstrip = parametrize("lightstrips", LIGHT_STRIPS, protocol_filter={"IOT"}) # bulb types -dimmable = parametrize("dimmable", DIMMABLE) -non_dimmable = parametrize("non-dimmable", BULBS - DIMMABLE) +dimmable = parametrize("dimmable", DIMMABLE, protocol_filter={"IOT"}) +non_dimmable = parametrize("non-dimmable", BULBS - DIMMABLE, protocol_filter={"IOT"}) variable_temp = parametrize( - "variable color temp", BULBS_VARIABLE_TEMP, {"SMART", "IOT"} + "variable color temp", BULBS_VARIABLE_TEMP, protocol_filter={"SMART", "IOT"} ) non_variable_temp = parametrize( - "non-variable color temp", BULBS - BULBS_VARIABLE_TEMP, {"SMART", "IOT"} + "non-variable color temp", + BULBS - BULBS_VARIABLE_TEMP, + protocol_filter={"SMART", "IOT"}, +) +color_bulb = parametrize("color bulbs", BULBS_COLOR, protocol_filter={"SMART", "IOT"}) +non_color_bulb = parametrize( + "non-color bulbs", BULBS - BULBS_COLOR, protocol_filter={"SMART", "IOT"} ) -color_bulb = parametrize("color bulbs", BULBS_COLOR, {"SMART", "IOT"}) -non_color_bulb = parametrize("non-color bulbs", BULBS - BULBS_COLOR, {"SMART", "IOT"}) -color_bulb_iot = parametrize("color bulbs iot", BULBS_COLOR, {"IOT"}) -variable_temp_iot = parametrize("variable color temp iot", BULBS_VARIABLE_TEMP, {"IOT"}) -bulb_iot = parametrize("bulb devices iot", BULBS_IOT) +color_bulb_iot = parametrize( + "color bulbs iot", BULBS_IOT_COLOR, protocol_filter={"IOT"} +) +variable_temp_iot = parametrize( + "variable color temp iot", BULBS_IOT_VARIABLE_TEMP, protocol_filter={"IOT"} +) +bulb_iot = parametrize("bulb devices iot", BULBS_IOT, protocol_filter={"IOT"}) plug_smart = parametrize("plug devices smart", PLUGS_SMART, protocol_filter={"SMART"}) bulb_smart = parametrize("bulb devices smart", BULBS_SMART, protocol_filter={"SMART"}) @@ -209,7 +233,9 @@ def filter_fixtures(desc, root_filter): if root_filter in val: filtered[key] = val - print(f"{desc}: {filtered.keys()}") + print(f"# {desc}") + for key in filtered: + print(f"\t{key}") return filtered @@ -268,11 +294,11 @@ def device_for_file(model, protocol): if d in model: return TapoBulb else: - for d in STRIPS: + for d in STRIPS_IOT: if d in model: return SmartStrip - for d in PLUGS: + for d in PLUGS_IOT: if d in model: return SmartPlug @@ -281,11 +307,11 @@ def device_for_file(model, protocol): if d in model: return SmartLightStrip - for d in BULBS: + for d in BULBS_IOT: if d in model: return SmartBulb - for d in DIMMERS: + for d in DIMMERS_IOT: if d in model: return SmartDimmer diff --git a/kasa/tests/fixtures/smart/EP25(US)_2.6_1.0.1.json b/kasa/tests/fixtures/smart/EP25(US)_2.6_1.0.1.json new file mode 100644 index 00000000..a9c41228 --- /dev/null +++ b/kasa/tests/fixtures/smart/EP25(US)_2.6_1.0.1.json @@ -0,0 +1,175 @@ +{ + "component_nego": { + "component_list": [ + { + "id": "device", + "ver_code": 2 + }, + { + "id": "firmware", + "ver_code": 2 + }, + { + "id": "quick_setup", + "ver_code": 3 + }, + { + "id": "time", + "ver_code": 1 + }, + { + "id": "wireless", + "ver_code": 1 + }, + { + "id": "schedule", + "ver_code": 2 + }, + { + "id": "countdown", + "ver_code": 2 + }, + { + "id": "antitheft", + "ver_code": 1 + }, + { + "id": "account", + "ver_code": 1 + }, + { + "id": "synchronize", + "ver_code": 1 + }, + { + "id": "sunrise_sunset", + "ver_code": 1 + }, + { + "id": "led", + "ver_code": 1 + }, + { + "id": "cloud_connect", + "ver_code": 1 + }, + { + "id": "iot_cloud", + "ver_code": 1 + }, + { + "id": "device_local_time", + "ver_code": 1 + }, + { + "id": "default_states", + "ver_code": 1 + }, + { + "id": "auto_off", + "ver_code": 2 + }, + { + "id": "energy_monitoring", + "ver_code": 2 + }, + { + "id": "power_protection", + "ver_code": 1 + }, + { + "id": "homekit", + "ver_code": 2 + } + ] + }, + "discovery_result": { + "device_id": "00000000000000000000000000000000", + "device_model": "EP25(US)", + "device_type": "SMART.KASAPLUG", + "factory_default": false, + "ip": "127.0.0.123", + "is_support_iot_cloud": true, + "mac": "00-00-00-00-00-00", + "mgt_encrypt_schm": { + "encrypt_type": "AES", + "http_port": 80, + "is_support_https": false, + "lv": 2 + }, + "obd_src": "apple", + "owner": "00000000000000000000000000000000" + }, + "get_current_power": { + "current_power": 715 + }, + "get_device_info": { + "auto_off_remain_time": 0, + "auto_off_status": "off", + "avatar": "plug", + "default_states": { + "state": {}, + "type": "last_states" + }, + "device_id": "0000000000000000000000000000000000000000", + "device_on": true, + "fw_id": "00000000000000000000000000000000", + "fw_ver": "1.0.1 Build 230614 Rel.150219", + "has_set_location_info": false, + "hw_id": "00000000000000000000000000000000", + "hw_ver": "2.6", + "ip": "127.0.0.123", + "lang": "en_US", + "latitude": 0, + "longitude": 0, + "mac": "00-00-00-00-00-00", + "model": "EP25", + "nickname": "emVlaw==", + "oem_id": "00000000000000000000000000000000", + "on_time": 177938, + "overheated": false, + "power_protection_status": "normal", + "region": "America/Los_Angeles", + "rssi": -58, + "signal_level": 2, + "specs": "", + "ssid": "IyNNQVNLRUROQU1FIyM=", + "time_diff": -480, + "type": "SMART.KASAPLUG" + }, + "get_device_time": { + "region": "America/Los_Angeles", + "time_diff": -480, + "timestamp": 1701455103 + }, + "get_device_usage": { + "power_usage": { + "past30": 100856, + "past7": 56030, + "today": 3965 + }, + "saved_power": { + "past30": 0, + "past7": 0, + "today": 0 + }, + "time_usage": { + "past30": 19678, + "past7": 9265, + "today": 625 + } + }, + "get_energy_usage": { + "current_power": 715092, + "electricity_charge": [ + 0, + 0, + 0 + ], + "local_time": "2023-12-01 10:25:03", + "month_energy": 3965, + "month_runtime": 625, + "today_energy": 3965, + "today_runtime": 625 + } +}