From 489437b483d7bb40179b41472f21df9f0c419c8d Mon Sep 17 00:00:00 2001 From: sdb9696 Date: Fri, 16 Feb 2024 17:12:25 +0000 Subject: [PATCH 01/23] Autogenerate supported devices docs --- README.md | 108 +-------------- SUPPORTED.md | 11 ++ devtools/check_readme_vs_fixtures.py | 4 +- devtools/dump_devinfo.py | 194 ++++++++++++++++++++++++++- docs/source/SUPPORTED.md | 5 + docs/source/index.rst | 1 + 6 files changed, 207 insertions(+), 116 deletions(-) create mode 100644 SUPPORTED.md create mode 100644 docs/source/SUPPORTED.md diff --git a/README.md b/README.md index d5db1cfcc..a2d44ad13 100644 --- a/README.md +++ b/README.md @@ -214,115 +214,9 @@ Note, that this works currently only on kasa-branded devices which use port 9999 In principle, most kasa-branded devices that are locally controllable using the official Kasa mobile app work with this library. -The following lists the devices that have been manually verified to work. +The following [link of devices](SUPPORTED.md) lists the devices that have been manually verified to work. **If your device is unlisted but working, please open a pull request to update the list and add a fixture file (use `python -m devtools.dump_devinfo` to generate one).** -### Plugs - -* HS100 -* HS103 -* HS105 -* HS107 -* HS110 -* KP100 -* KP105 -* KP115 -* KP125 -* KP125M [See note below](#newer-kasa-branded-devices) -* KP401 -* EP10 -* EP25 [See note below](#newer-kasa-branded-devices) - -### Power Strips - -* EP40 -* HS300 -* KP303 -* KP200 (in wall) -* KP400 -* KP405 (dimmer) - -### Wall switches - -* ES20M -* HS200 -* HS210 -* HS220 -* KS200M (partial support, no motion, no daylight detection) -* KS220M (partial support, no motion, no daylight detection) -* KS230 - -### Bulbs - -* LB100 -* LB110 -* LB120 -* LB130 -* LB230 -* KL50 -* KL60 -* KL110 -* KL120 -* KL125 -* KL130 -* KL135 - -### Light strips - -* KL400L5 -* KL420L5 -* KL430 - -### Tapo branded devices - -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: - -#### Plugs - -* Tapo P110 -* Tapo P125M -* Tapo P135 (dimming not yet supported) -* Tapo TP15 - -#### Bulbs - -* Tapo L510B -* Tapo L510E -* Tapo L530E - -#### Light strips - -* Tapo L900-5 -* Tapo L900-10 -* Tapo L920-5 -* Tapo L930-5 - -#### Wall switches - -* Tapo S500D -* Tapo S505 - -#### Power strips - -* Tapo P300 -* Tapo TP25 - - -### Newer Kasa branded devices - -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) -* Kasa KS205 (Wifi/Matter Wall Switch) -* Kasa KS225 (Wifi/Matter Wall Dimmer Switch) - - -**If your device is unlisted but working, please open a pull request to update the list and add a fixture file (use `python -m devtools.dump_devinfo` to generate one).** ## Resources diff --git a/SUPPORTED.md b/SUPPORTED.md new file mode 100644 index 000000000..6df04ca66 --- /dev/null +++ b/SUPPORTED.md @@ -0,0 +1,11 @@ +▸  Expand to see tested versions
+*  Model requires authentication
+** Newer versions require authentication + +| **Type** | **Kasa** | **Tapo** \* | +|--- |--- |--- | +| Plugs |
EP10Hardware 1.0(US) Firmare 1.0.2
EP25 *Hardware 2.6(US) Firmare 1.0.1 *
Hardware 2.6(US) Firmare 1.0.2 *
ES20MHardware 1.0(US) Firmare 1.0.8
HS100 **Hardware 2.0(US) Firmare 1.5.6
Hardware 4.1(UK) Firmare 1.1.0 *
Hardware 1.0(US) Firmare 1.2.5
Hardware 1.0(UK) Firmare 1.2.6
HS103Hardware 1.0(US) Firmare 1.5.7
Hardware 2.1(US) Firmare 1.1.4
Hardware 2.1(US) Firmare 1.1.2
HS105Hardware 1.0(US) Firmare 1.2.9
Hardware 1.0(US) Firmare 1.5.6
HS110Hardware 2.0(EU) Firmare 1.5.2
Hardware 4.0(EU) Firmare 1.0.4
Hardware 1.0(EU) Firmare 1.2.5
Hardware 1.0(US) Firmare 1.0.8
HS220Hardware 1.0(US) Firmare 1.5.7
Hardware 2.0(US) Firmare 1.0.3
Hardware 1.0(US) Firmare 1.5.7
KP100Hardware 3.0(US) Firmare 1.0.1
KP105Hardware 1.0(UK) Firmare 1.0.7
Hardware 1.0(UK) Firmare 1.0.5
KP115Hardware 1.0(US) Firmare 1.0.21
Hardware 1.0(US) Firmare 1.0.17
Hardware 1.0(EU) Firmare 1.0.16
KP125Hardware 1.0(US) Firmare 1.0.6
KP125M *Hardware 1.0(US) Firmare 1.1.3 *
KP401Hardware 1.0(US) Firmare 1.0.0
KP405Hardware 1.0(US) Firmare 1.0.5
KS220MHardware 1.0(US) Firmare 1.0.4
KS230Hardware 1.0(US) Firmare 1.0.14
|
P100Hardware 1.0.0 Firmare 1.3.7
Hardware 1.0.0 Firmare 1.1.3
P110Hardware 1.0(EU) Firmare 1.2.3
Hardware 1.0(UK) Firmare 1.3.0
P125MHardware 1.0(US) Firmare 1.1.0
P135Hardware 1.0(US) Firmare 1.0.5
TP15Hardware 1.0(US) Firmare 1.0.3
| +| Power Strips |
EP40Hardware 1.0(US) Firmare 1.0.2
HS107Hardware 1.0(US) Firmare 1.0.8
HS300Hardware 1.0(US) Firmare 1.0.10
Hardware 2.0(US) Firmare 1.0.3
Hardware 2.0(US) Firmare 1.0.12
KP200Hardware 3.0(US) Firmare 1.0.3
KP303Hardware 2.0(US) Firmare 1.0.3
Hardware 1.0(UK) Firmare 1.0.3
KP400Hardware 1.0(US) Firmare 1.0.10
Hardware 2.0(US) Firmare 1.0.6
|
P300Hardware 1.0(EU) Firmare 1.0.7
Hardware 1.0(EU) Firmare 1.0.13
TP25Hardware 1.0(US) Firmare 1.0.2
| +| Wall Switches |
HS200Hardware 5.0(US) Firmare 1.0.2
Hardware 2.0(US) Firmare 1.5.7
Hardware 1.0(US) Firmare 1.1.0
HS210Hardware 1.0(US) Firmare 1.5.8
KS200MHardware 1.0(US) Firmare 1.0.8
KS205 *Hardware 1.0(US) Firmare 1.0.2 *
KS225 *Hardware 1.0(US) Firmare 1.0.2 *
|
S500DHardware 1.0(US) Firmare 1.0.5
S505Hardware 1.0(US) Firmare 1.0.2
| +| Bulbs |
KL110Hardware 1.0(US) Firmare 1.8.11
KL120Hardware 1.0(US) Firmare 1.8.6
KL125Hardware 4.0(US) Firmare 1.0.5
Hardware 2.0(US) Firmare 1.0.7
Hardware 1.20(US) Firmare 1.0.5
KL130Hardware 1.0(US) Firmare 1.8.11
Hardware 1.0(EU) Firmare 1.8.8
KL135Hardware 1.0(US) Firmare 1.0.6
KL50Hardware 1.0(US) Firmare 1.1.13
KL60Hardware 1.0(US) Firmare 1.1.13
Hardware 1.0(UN) Firmare 1.1.4
LB100Hardware 1.0(US) Firmare 1.4.3
LB110Hardware 1.0(US) Firmare 1.8.11
LB120Hardware 1.0(US) Firmare 1.1.0
LB130Hardware 1.0(US) Firmare 1.6.0
|
L510BHardware 3.0(EU) Firmare 1.0.5
L510EHardware 3.0(US) Firmare 1.0.5
Hardware 3.0(US) Firmare 1.1.2
L530EHardware 3.0(EU) Firmare 1.1.0
Hardware 3.0(EU) Firmare 1.0.6
Hardware 2.0(US) Firmare 1.1.0
| +| Light Strips |
KL400L5Hardware 1.0(US) Firmare 1.0.8
Hardware 1.0(US) Firmare 1.0.5
KL420L5Hardware 1.0(US) Firmare 1.0.2
KL430Hardware 2.0(US) Firmare 1.0.11
Hardware 2.0(US) Firmare 1.0.8
Hardware 1.0(US) Firmare 1.0.10
Hardware 2.0(US) Firmare 1.0.9
Hardware 2.0(UN) Firmare 1.0.8
|
L900-10Hardware 1.0(US) Firmare 1.0.11
Hardware 1.0(EU) Firmare 1.0.17
L900-5Hardware 1.0(EU) Firmare 1.1.0
Hardware 1.0(EU) Firmare 1.0.17
L920-5Hardware 1.0(US) Firmare 1.1.3
Hardware 1.0(US) Firmare 1.1.0
L930-5Hardware 1.0(US) Firmare 1.1.2
| diff --git a/devtools/check_readme_vs_fixtures.py b/devtools/check_readme_vs_fixtures.py index 88663621a..3266d4417 100644 --- a/devtools/check_readme_vs_fixtures.py +++ b/devtools/check_readme_vs_fixtures.py @@ -11,7 +11,7 @@ STRIPS, ) -with open("README.md") as f: +with open("SUPPORTED.md") as f: readme = f.read() typemap = { @@ -33,7 +33,7 @@ def _get_device_type(dev, typemap): found_unlisted = False for dev in ALL_DEVICES: - regex = rf"^\*.*\s{dev}" + regex = dev match = re.search(regex, readme, re.MULTILINE) if match is None: print(f"{dev} not listed in {_get_device_type(dev, typemap)}") diff --git a/devtools/dump_devinfo.py b/devtools/dump_devinfo.py index a227a50df..98bd876c5 100644 --- a/devtools/dump_devinfo.py +++ b/devtools/dump_devinfo.py @@ -14,6 +14,8 @@ import re import traceback from collections import defaultdict, namedtuple +from os import listdir +from os.path import isfile, join from pathlib import Path from pprint import pprint from typing import Dict, List, Union @@ -33,11 +35,24 @@ from kasa.exceptions import SmartErrorCode from kasa.smart import SmartDevice +SupportedVersion = namedtuple("SupportedVersion", "region hw fw auth") + + Call = namedtuple("Call", "module method") SmartCall = namedtuple("SmartCall", "module request should_succeed") _LOGGER = logging.getLogger(__name__) +IOT_FOLDER = "kasa/tests/fixtures/" +SMART_FOLDER = "kasa/tests/fixtures/smart/" +SUPPORTED_FILENAME = "SUPPORTED.md" + +PLUGS = "Plugs" +POWER_STRIPS = "Power Strips" +WALL_SWITCHES = "Wall Switches" +BULBS = "Bulbs" +LIGHT_STRIPS = "Light Strips" + def scrub(res): """Remove identifiers from the given dict.""" @@ -122,7 +137,7 @@ def default_to_regular(d): return d -async def handle_device(basedir, autosave, device: Device, batch_size: int): +async def handle_device(basedir, autosave, device: Device, batch_size: int) -> bool: """Create a fixture for a single device instance.""" if isinstance(device, SmartDevice): filename, copy_folder, final = await get_smart_fixture(device, batch_size) @@ -144,11 +159,13 @@ async def handle_device(basedir, autosave, device: Device, batch_size: int): with open(save_filename, "w") as f: json.dump(final, f, sort_keys=True, indent=4) f.write("\n") + return True else: click.echo("Not saving.") + return False -@click.command() +@click.group(invoke_without_command=True) @click.option("--host", required=False, help="Target host.") @click.option( "--target", @@ -176,7 +193,10 @@ async def handle_device(basedir, autosave, device: Device, batch_size: int): "--batch-size", default=5, help="Number of batched requests to send at once" ) @click.option("-d", "--debug", is_flag=True) -async def cli(host, target, basedir, autosave, debug, username, password, batch_size): +@click.pass_context +async def cli( + ctx, host, target, basedir, autosave, debug, username, password, batch_size +): """Generate devinfo files for devices. Use --host (for a single device) or --target (for a complete network). @@ -184,11 +204,16 @@ async def cli(host, target, basedir, autosave, debug, username, password, batch_ if debug: logging.basicConfig(level=logging.DEBUG) + if ctx.invoked_subcommand == "generate-supported": + return + credentials = Credentials(username=username, password=password) + file_created = False if host is not None: click.echo("Host given, performing discovery on %s." % host) device = await Discover.discover_single(host, credentials=credentials) - await handle_device(basedir, autosave, device, batch_size) + if await handle_device(basedir, autosave, device, batch_size): + file_created = True else: click.echo( "No --host given, performing discovery on %s. Use --target to override." @@ -197,7 +222,10 @@ async def cli(host, target, basedir, autosave, debug, username, password, batch_ devices = await Discover.discover(target=target, credentials=credentials) click.echo("Detected %s devices" % len(devices)) for dev in devices.values(): - await handle_device(basedir, autosave, dev, batch_size) + if await handle_device(basedir, autosave, dev, batch_size): + file_created = True + if file_created: + generate_supported() async def get_legacy_fixture(device): @@ -265,7 +293,7 @@ async def get_legacy_fixture(device): sw_version = sysinfo["sw_ver"] sw_version = sw_version.split(" ", maxsplit=1)[0] save_filename = f"{model}_{hw_version}_{sw_version}.json" - copy_folder = "kasa/tests/fixtures/" + copy_folder = IOT_FOLDER return save_filename, copy_folder, final @@ -452,9 +480,161 @@ async def get_smart_fixture(device: SmartDevice, batch_size: int): sw_version = sw_version.split(" ", maxsplit=1)[0] save_filename = f"{model}_{hw_version}_{sw_version}.json" - copy_folder = "kasa/tests/fixtures/smart/" + copy_folder = SMART_FOLDER return save_filename, copy_folder, final +@cli.command(name="generate-supported") +async def generate_supported(): + """Generate the SUPPORTED.md from the fixtures.""" + brands = {"kasa": {}, "tapo": {}} + supported = { + PLUGS: {"kasa": {}, "tapo": {}}, + POWER_STRIPS: {"kasa": {}, "tapo": {}}, + WALL_SWITCHES: {"kasa": {}, "tapo": {}}, + BULBS: {"kasa": {}, "tapo": {}}, + LIGHT_STRIPS: {"kasa": {}, "tapo": {}}, + } + _get_iot_supported(supported) + _get_smart_supported(supported) + + lines = [ + "▸  Expand to see tested versions
\n", + "*  Model requires authentication
\n", + "** Newer versions require authentication\n", + "\n", + "| **Type** | **Kasa** | **Tapo** \* |\n", + "|--- |--- |--- |\n", + ] + for type_, brands in supported.items(): + models_text = {"kasa": "", "tapo": ""} + for brand, models in brands.items(): + for model, versions in sorted(models.items()): + version_list = [] + auth_count = 0 + auth_symbol = "" + for version in versions: + region_text = f"({version.region})" if version.region else "" + version_auth = ( + " *" if version.auth and brand == "kasa" else "" + ) + version_text = ( + f"Hardware {version.hw}{region_text} " + + f"Firmare {version.fw}{version_auth}" + ) + version_list.append(version_text) + if version.auth: + auth_symbol = " **" + auth_count += 1 + versions_text = "
".join(version_list) + auth_symbol = " *" if auth_count == len(versions) else auth_symbol + auth_text = f"{auth_symbol}" if brand == "kasa" else "" + models_text[brand] += ( + f"
{model}{auth_text}" + + f"{versions_text}
" + ) + line = ( + f"| {type_} | {models_text.get('kasa')} | {models_text.get('tapo')} |\n" + ) + lines.append(line) + with open(SUPPORTED_FILENAME, "w") as the_file: + for line in lines: + the_file.write(line) + + +def _get_smart_supported(supported): + smart_files = [ + f + for f in listdir(SMART_FOLDER) + if isfile(join(SMART_FOLDER, f)) and f.endswith(".json") + ] + for smart_file in smart_files: + with open(join(SMART_FOLDER, smart_file)) as f: + fixture_data = json.load(f) + + model, _, region = fixture_data["discovery_result"]["device_model"].partition( + "(" + ) + # P100 doesn't have region HW + region = region.replace(")", "") if region else "" + device_type = fixture_data["discovery_result"]["device_type"] + if device_type[:10] == "SMART.KASA": + brand = "kasa" + elif device_type[:10] == "SMART.TAPO": + brand = "tapo" + else: + click.echo( + click.style( + f"FAIL {smart_file} does not have a " + + f"supported device_type {device_type}", + fg="red", + ) + ) + continue + components = [ + component["id"] + for component in fixture_data["component_nego"]["component_list"] + ] + if device_type[10:] == "BULB": + supported_type = LIGHT_STRIPS if "light_strip" in components else BULBS + elif device_type[10:] == "PLUG": + supported_type = POWER_STRIPS if "child_device" in components else PLUGS + elif device_type[10:] == "SWITCH": + supported_type = WALL_SWITCHES + else: + click.echo( + click.style( + f"FAIL {smart_file} does not have a " + + f"supported device_type {device_type}", + fg="red", + ) + ) + continue + + hw_version = fixture_data["get_device_info"]["hw_ver"] + fw_version = fixture_data["get_device_info"]["fw_ver"] + fw_version = fw_version.split(" ", maxsplit=1)[0] + + smodel = supported[supported_type][brand].setdefault(model, []) + smodel.append( + SupportedVersion(region=region, hw=hw_version, fw=fw_version, auth=True) + ) + + +def _get_iot_supported(supported): + iot_files = [ + f + for f in listdir(IOT_FOLDER) + if isfile(join(IOT_FOLDER, f)) and f.endswith(".json") + ] + for iot_file in iot_files: + with open(join(IOT_FOLDER, iot_file)) as f: + fixture_data = json.load(f) + sysinfo = fixture_data["system"]["get_sysinfo"] + model, _, region = sysinfo["model"][:-1].partition("(") + auth = "discovery_result" in fixture_data + type_ = sysinfo.get("type", sysinfo.get("mic_type")) + if type_ == "IOT.SMARTBULB": + supported_type = LIGHT_STRIPS if "length" in sysinfo else BULBS + else: + if "children" in sysinfo: + supported_type = POWER_STRIPS + else: + if "dev_name" not in sysinfo: + click.echo( + click.style(f"FAIL {iot_file} does not have dev_name", fg="red") + ) + continue + if "light" in sysinfo["dev_name"].lower(): + supported_type = WALL_SWITCHES + else: + supported_type = PLUGS + smodel = supported[supported_type]["kasa"].setdefault(model, []) + fw = sysinfo["sw_ver"].split(" ", maxsplit=1)[0] + smodel.append( + SupportedVersion(region=region, hw=sysinfo["hw_ver"], fw=fw, auth=auth) + ) + + if __name__ == "__main__": cli() diff --git a/docs/source/SUPPORTED.md b/docs/source/SUPPORTED.md new file mode 100644 index 000000000..65ad23a22 --- /dev/null +++ b/docs/source/SUPPORTED.md @@ -0,0 +1,5 @@ +# Supported devices + +```{include} ../../SUPPORTED.md +:relative-docs: doc/source +``` diff --git a/docs/source/index.rst b/docs/source/index.rst index 346c53d08..9dc648a9c 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -15,3 +15,4 @@ smartdimmer smartstrip smartlightstrip + SUPPORTED From 255d936cb679214ed52d6f1b6a44a4352ea2c167 Mon Sep 17 00:00:00 2001 From: sdb9696 Date: Fri, 16 Feb 2024 18:40:36 +0000 Subject: [PATCH 02/23] Test --- test.rst | 4 ++++ test2.rst | 2 ++ 2 files changed, 6 insertions(+) create mode 100644 test.rst create mode 100644 test2.rst diff --git a/test.rst b/test.rst new file mode 100644 index 000000000..e2cede97b --- /dev/null +++ b/test.rst @@ -0,0 +1,4 @@ +Test file +========= + +.. include:: test2.rst diff --git a/test2.rst b/test2.rst new file mode 100644 index 000000000..1376f2953 --- /dev/null +++ b/test2.rst @@ -0,0 +1,2 @@ +Test file 2 +=========== From a3fb0ec7f89f728c02b26972b324c2ece309fe3a Mon Sep 17 00:00:00 2001 From: sdb9696 Date: Mon, 19 Feb 2024 11:38:20 +0000 Subject: [PATCH 03/23] Add summary view to readme and drop table --- README.md | 25 +++++++- SUPPORTED.md | 127 +++++++++++++++++++++++++++++++++++---- devtools/dump_devinfo.py | 74 +++++++++++++++++------ test.rst | 4 -- test2.rst | 2 - 5 files changed, 193 insertions(+), 39 deletions(-) delete mode 100644 test.rst delete mode 100644 test2.rst diff --git a/README.md b/README.md index a2d44ad13..e460fd11c 100644 --- a/README.md +++ b/README.md @@ -212,11 +212,30 @@ Note, that this works currently only on kasa-branded devices which use port 9999 ## Supported devices -In principle, most kasa-branded devices that are locally controllable using the official Kasa mobile app work with this library. +The following devices have been tested and confirmed as working. If your device is unlisted but working, please open a pull request to update the list and add a fixture file (use `python -m devtools.dump_devinfo` to generate one). -The following [link of devices](SUPPORTED.md) lists the devices that have been manually verified to work. -**If your device is unlisted but working, please open a pull request to update the list and add a fixture file (use `python -m devtools.dump_devinfo` to generate one).** + +### Supported Kasa devices +- **Plugs** - EP10, EP25\*, ES20M, HS100\*\*, HS103, HS105, HS110, HS220, KP100, KP105, KP115, KP125, KP125M\*, KP401, KP405, KS220M, KS230 +- **Power Strips** - EP40, HS107, HS300, KP200, KP303, KP400 +- **Wall Switches** - HS200, HS210, KS200M, KS205\*, KS225\* +- **Bulbs** - KL110, KL120, KL125, KL130, KL135, KL50, KL60, LB100, LB110, LB120, LB130 +- **Light Strips** - KL400L5, KL420L5, KL430 + +### Supported Tapo\* devices + +- **Plugs** - P100, P110, P125M, P135, TP15 +- **Power Strips** - P300, TP25 +- **Wall Switches** - S500D, S505 +- **Bulbs** - L510B, L510E, L530E +- **Light Strips** - L900-10, L900-5, L920-5, L930-5 + + +*  Model requires authentication
+** Newer versions require authentication + +The following [supported devices link](SUPPORTED.md) lists the detailed device hardware and software versions that have been manually verified to work. ## Resources diff --git a/SUPPORTED.md b/SUPPORTED.md index 6df04ca66..377536f28 100644 --- a/SUPPORTED.md +++ b/SUPPORTED.md @@ -1,11 +1,116 @@ -▸  Expand to see tested versions
-*  Model requires authentication
-** Newer versions require authentication - -| **Type** | **Kasa** | **Tapo** \* | -|--- |--- |--- | -| Plugs |
EP10Hardware 1.0(US) Firmare 1.0.2
EP25 *Hardware 2.6(US) Firmare 1.0.1 *
Hardware 2.6(US) Firmare 1.0.2 *
ES20MHardware 1.0(US) Firmare 1.0.8
HS100 **Hardware 2.0(US) Firmare 1.5.6
Hardware 4.1(UK) Firmare 1.1.0 *
Hardware 1.0(US) Firmare 1.2.5
Hardware 1.0(UK) Firmare 1.2.6
HS103Hardware 1.0(US) Firmare 1.5.7
Hardware 2.1(US) Firmare 1.1.4
Hardware 2.1(US) Firmare 1.1.2
HS105Hardware 1.0(US) Firmare 1.2.9
Hardware 1.0(US) Firmare 1.5.6
HS110Hardware 2.0(EU) Firmare 1.5.2
Hardware 4.0(EU) Firmare 1.0.4
Hardware 1.0(EU) Firmare 1.2.5
Hardware 1.0(US) Firmare 1.0.8
HS220Hardware 1.0(US) Firmare 1.5.7
Hardware 2.0(US) Firmare 1.0.3
Hardware 1.0(US) Firmare 1.5.7
KP100Hardware 3.0(US) Firmare 1.0.1
KP105Hardware 1.0(UK) Firmare 1.0.7
Hardware 1.0(UK) Firmare 1.0.5
KP115Hardware 1.0(US) Firmare 1.0.21
Hardware 1.0(US) Firmare 1.0.17
Hardware 1.0(EU) Firmare 1.0.16
KP125Hardware 1.0(US) Firmare 1.0.6
KP125M *Hardware 1.0(US) Firmare 1.1.3 *
KP401Hardware 1.0(US) Firmare 1.0.0
KP405Hardware 1.0(US) Firmare 1.0.5
KS220MHardware 1.0(US) Firmare 1.0.4
KS230Hardware 1.0(US) Firmare 1.0.14
|
P100Hardware 1.0.0 Firmare 1.3.7
Hardware 1.0.0 Firmare 1.1.3
P110Hardware 1.0(EU) Firmare 1.2.3
Hardware 1.0(UK) Firmare 1.3.0
P125MHardware 1.0(US) Firmare 1.1.0
P135Hardware 1.0(US) Firmare 1.0.5
TP15Hardware 1.0(US) Firmare 1.0.3
| -| Power Strips |
EP40Hardware 1.0(US) Firmare 1.0.2
HS107Hardware 1.0(US) Firmare 1.0.8
HS300Hardware 1.0(US) Firmare 1.0.10
Hardware 2.0(US) Firmare 1.0.3
Hardware 2.0(US) Firmare 1.0.12
KP200Hardware 3.0(US) Firmare 1.0.3
KP303Hardware 2.0(US) Firmare 1.0.3
Hardware 1.0(UK) Firmare 1.0.3
KP400Hardware 1.0(US) Firmare 1.0.10
Hardware 2.0(US) Firmare 1.0.6
|
P300Hardware 1.0(EU) Firmare 1.0.7
Hardware 1.0(EU) Firmare 1.0.13
TP25Hardware 1.0(US) Firmare 1.0.2
| -| Wall Switches |
HS200Hardware 5.0(US) Firmare 1.0.2
Hardware 2.0(US) Firmare 1.5.7
Hardware 1.0(US) Firmare 1.1.0
HS210Hardware 1.0(US) Firmare 1.5.8
KS200MHardware 1.0(US) Firmare 1.0.8
KS205 *Hardware 1.0(US) Firmare 1.0.2 *
KS225 *Hardware 1.0(US) Firmare 1.0.2 *
|
S500DHardware 1.0(US) Firmare 1.0.5
S505Hardware 1.0(US) Firmare 1.0.2
| -| Bulbs |
KL110Hardware 1.0(US) Firmare 1.8.11
KL120Hardware 1.0(US) Firmare 1.8.6
KL125Hardware 4.0(US) Firmare 1.0.5
Hardware 2.0(US) Firmare 1.0.7
Hardware 1.20(US) Firmare 1.0.5
KL130Hardware 1.0(US) Firmare 1.8.11
Hardware 1.0(EU) Firmare 1.8.8
KL135Hardware 1.0(US) Firmare 1.0.6
KL50Hardware 1.0(US) Firmare 1.1.13
KL60Hardware 1.0(US) Firmare 1.1.13
Hardware 1.0(UN) Firmare 1.1.4
LB100Hardware 1.0(US) Firmare 1.4.3
LB110Hardware 1.0(US) Firmare 1.8.11
LB120Hardware 1.0(US) Firmare 1.1.0
LB130Hardware 1.0(US) Firmare 1.6.0
|
L510BHardware 3.0(EU) Firmare 1.0.5
L510EHardware 3.0(US) Firmare 1.0.5
Hardware 3.0(US) Firmare 1.1.2
L530EHardware 3.0(EU) Firmare 1.1.0
Hardware 3.0(EU) Firmare 1.0.6
Hardware 2.0(US) Firmare 1.1.0
| -| Light Strips |
KL400L5Hardware 1.0(US) Firmare 1.0.8
Hardware 1.0(US) Firmare 1.0.5
KL420L5Hardware 1.0(US) Firmare 1.0.2
KL430Hardware 2.0(US) Firmare 1.0.11
Hardware 2.0(US) Firmare 1.0.8
Hardware 1.0(US) Firmare 1.0.10
Hardware 2.0(US) Firmare 1.0.9
Hardware 2.0(UN) Firmare 1.0.8
|
L900-10Hardware 1.0(US) Firmare 1.0.11
Hardware 1.0(EU) Firmare 1.0.17
L900-5Hardware 1.0(EU) Firmare 1.1.0
Hardware 1.0(EU) Firmare 1.0.17
L920-5Hardware 1.0(US) Firmare 1.1.3
Hardware 1.0(US) Firmare 1.1.0
L930-5Hardware 1.0(US) Firmare 1.1.2
| +# Supported devices + +**If your device is unlisted but working, please open a pull request to update the list and add a fixture file (use `python -m devtools.dump_devinfo` to generate one).** + +## Tested device versions + +▸  Expand to see tested versions
+*  Model requires authentication
+** Newer versions require authentication + + +### Supported Kasa devices + +**Plugs** + +
EP10Hardware 1.0(US) Firmare 1.0.2
+
EP25 *Hardware 2.6(US) Firmare 1.0.1 *
Hardware 2.6(US) Firmare 1.0.2 *
+
ES20MHardware 1.0(US) Firmare 1.0.8
+
HS100 **Hardware 2.0(US) Firmare 1.5.6
Hardware 4.1(UK) Firmare 1.1.0 *
Hardware 1.0(US) Firmare 1.2.5
Hardware 1.0(UK) Firmare 1.2.6
+
HS103Hardware 1.0(US) Firmare 1.5.7
Hardware 2.1(US) Firmare 1.1.4
Hardware 2.1(US) Firmare 1.1.2
+
HS105Hardware 1.0(US) Firmare 1.2.9
Hardware 1.0(US) Firmare 1.5.6
+
HS110Hardware 2.0(EU) Firmare 1.5.2
Hardware 4.0(EU) Firmare 1.0.4
Hardware 1.0(EU) Firmare 1.2.5
Hardware 1.0(US) Firmare 1.0.8
+
HS220Hardware 1.0(US) Firmare 1.5.7
Hardware 2.0(US) Firmare 1.0.3
Hardware 1.0(US) Firmare 1.5.7
+
KP100Hardware 3.0(US) Firmare 1.0.1
+
KP105Hardware 1.0(UK) Firmare 1.0.7
Hardware 1.0(UK) Firmare 1.0.5
+
KP115Hardware 1.0(US) Firmare 1.0.21
Hardware 1.0(US) Firmare 1.0.17
Hardware 1.0(EU) Firmare 1.0.16
+
KP125Hardware 1.0(US) Firmare 1.0.6
+
KP125M *Hardware 1.0(US) Firmare 1.1.3 *
+
KP401Hardware 1.0(US) Firmare 1.0.0
+
KP405Hardware 1.0(US) Firmare 1.0.5
+
KS220MHardware 1.0(US) Firmare 1.0.4
+
KS230Hardware 1.0(US) Firmare 1.0.14
+
+ +**Power Strips** + +
EP40Hardware 1.0(US) Firmare 1.0.2
+
HS107Hardware 1.0(US) Firmare 1.0.8
+
HS300Hardware 1.0(US) Firmare 1.0.10
Hardware 2.0(US) Firmare 1.0.3
Hardware 2.0(US) Firmare 1.0.12
+
KP200Hardware 3.0(US) Firmare 1.0.3
+
KP303Hardware 2.0(US) Firmare 1.0.3
Hardware 1.0(UK) Firmare 1.0.3
+
KP400Hardware 1.0(US) Firmare 1.0.10
Hardware 2.0(US) Firmare 1.0.6
+
+ +**Wall Switches** + +
HS200Hardware 5.0(US) Firmare 1.0.2
Hardware 2.0(US) Firmare 1.5.7
Hardware 1.0(US) Firmare 1.1.0
+
HS210Hardware 1.0(US) Firmare 1.5.8
+
KS200MHardware 1.0(US) Firmare 1.0.8
+
KS205 *Hardware 1.0(US) Firmare 1.0.2 *
+
KS225 *Hardware 1.0(US) Firmare 1.0.2 *
+
+ +**Bulbs** + +
KL110Hardware 1.0(US) Firmare 1.8.11
+
KL120Hardware 1.0(US) Firmare 1.8.6
+
KL125Hardware 4.0(US) Firmare 1.0.5
Hardware 2.0(US) Firmare 1.0.7
Hardware 1.20(US) Firmare 1.0.5
+
KL130Hardware 1.0(US) Firmare 1.8.11
Hardware 1.0(EU) Firmare 1.8.8
+
KL135Hardware 1.0(US) Firmare 1.0.6
+
KL50Hardware 1.0(US) Firmare 1.1.13
+
KL60Hardware 1.0(US) Firmare 1.1.13
Hardware 1.0(UN) Firmare 1.1.4
+
LB100Hardware 1.0(US) Firmare 1.4.3
+
LB110Hardware 1.0(US) Firmare 1.8.11
+
LB120Hardware 1.0(US) Firmare 1.1.0
+
LB130Hardware 1.0(US) Firmare 1.6.0
+
+ +**Light Strips** + +
KL400L5Hardware 1.0(US) Firmare 1.0.8
Hardware 1.0(US) Firmare 1.0.5
+
KL420L5Hardware 1.0(US) Firmare 1.0.2
+
KL430Hardware 2.0(US) Firmare 1.0.11
Hardware 2.0(US) Firmare 1.0.8
Hardware 1.0(US) Firmare 1.0.10
Hardware 2.0(US) Firmare 1.0.9
Hardware 2.0(UN) Firmare 1.0.8
+
+ + +### Supported Tapo * devices + +**Plugs** + +
P100Hardware 1.0.0 Firmare 1.3.7
Hardware 1.0.0 Firmare 1.1.3
+
P110Hardware 1.0(EU) Firmare 1.2.3
Hardware 1.0(UK) Firmare 1.3.0
+
P125MHardware 1.0(US) Firmare 1.1.0
+
P135Hardware 1.0(US) Firmare 1.0.5
+
TP15Hardware 1.0(US) Firmare 1.0.3
+
+ +**Power Strips** + +
P300Hardware 1.0(EU) Firmare 1.0.7
Hardware 1.0(EU) Firmare 1.0.13
+
TP25Hardware 1.0(US) Firmare 1.0.2
+
+ +**Wall Switches** + +
S500DHardware 1.0(US) Firmare 1.0.5
+
S505Hardware 1.0(US) Firmare 1.0.2
+
+ +**Bulbs** + +
L510BHardware 3.0(EU) Firmare 1.0.5
+
L510EHardware 3.0(US) Firmare 1.0.5
Hardware 3.0(US) Firmare 1.1.2
+
L530EHardware 3.0(EU) Firmare 1.1.0
Hardware 3.0(EU) Firmare 1.0.6
Hardware 2.0(US) Firmare 1.1.0
+
+ +**Light Strips** + +
L900-10Hardware 1.0(US) Firmare 1.0.11
Hardware 1.0(EU) Firmare 1.0.17
+
L900-5Hardware 1.0(EU) Firmare 1.1.0
Hardware 1.0(EU) Firmare 1.0.17
+
L920-5Hardware 1.0(US) Firmare 1.1.3
Hardware 1.0(US) Firmare 1.1.0
+
L930-5Hardware 1.0(US) Firmare 1.1.2
+
+ + + diff --git a/devtools/dump_devinfo.py b/devtools/dump_devinfo.py index 98bd876c5..6512855e9 100644 --- a/devtools/dump_devinfo.py +++ b/devtools/dump_devinfo.py @@ -46,6 +46,7 @@ IOT_FOLDER = "kasa/tests/fixtures/" SMART_FOLDER = "kasa/tests/fixtures/smart/" SUPPORTED_FILENAME = "SUPPORTED.md" +README_FILENAME = "README.md" PLUGS = "Plugs" POWER_STRIPS = "Power Strips" @@ -487,7 +488,6 @@ async def get_smart_fixture(device: SmartDevice, batch_size: int): @cli.command(name="generate-supported") async def generate_supported(): """Generate the SUPPORTED.md from the fixtures.""" - brands = {"kasa": {}, "tapo": {}} supported = { PLUGS: {"kasa": {}, "tapo": {}}, POWER_STRIPS: {"kasa": {}, "tapo": {}}, @@ -498,17 +498,47 @@ async def generate_supported(): _get_iot_supported(supported) _get_smart_supported(supported) + _update_supported_file(SUPPORTED_FILENAME, supported, False) + _update_supported_file(README_FILENAME, supported, True) + + +def _update_supported_file(filename, supported, summary): + with open(filename) as f: + contents = f.readlines() + + start_index = end_index = None + for index, line in enumerate(contents): + if line == "\n": + start_index = index + 1 + if line == "\n": + end_index = index + + new_contents = contents[:start_index] + end_contents = contents[end_index:] + new_contents.extend(_generate_supported(supported, "Kasa", summary)) + new_contents.append("\n") + new_contents.extend(_generate_supported(supported, "Tapo", summary)) + new_contents.append("\n") + new_contents.extend(end_contents) + + with open(filename, "w") as f: + new_contents = "".join(new_contents) + f.write(new_contents) + + +def _generate_supported(supported, target_brand, summary): + single_star = "\*" if summary else " *" + double_star = "\*\*" if summary else " **" + brand_auth = single_star if target_brand.lower() == "tapo" else "" lines = [ - "▸  Expand to see tested versions
\n", - "*  Model requires authentication
\n", - "** Newer versions require authentication\n", + f"### Supported {target_brand}{brand_auth} devices\n", "\n", - "| **Type** | **Kasa** | **Tapo** \* |\n", - "|--- |--- |--- |\n", ] for type_, brands in supported.items(): - models_text = {"kasa": "", "tapo": ""} + models_list = [] for brand, models in brands.items(): + if brand != target_brand.lower(): + continue for model, versions in sorted(models.items()): version_list = [] auth_count = 0 @@ -516,7 +546,7 @@ async def generate_supported(): for version in versions: region_text = f"({version.region})" if version.region else "" version_auth = ( - " *" if version.auth and brand == "kasa" else "" + single_star if version.auth and brand == "kasa" else "" ) version_text = ( f"Hardware {version.hw}{region_text} " @@ -524,22 +554,28 @@ async def generate_supported(): ) version_list.append(version_text) if version.auth: - auth_symbol = " **" auth_count += 1 + auth_symbol = double_star + auth_symbol = ( + single_star if auth_count == len(versions) else auth_symbol + ) + auth_symbol = auth_symbol if brand == "kasa" else "" versions_text = "
".join(version_list) - auth_symbol = " *" if auth_count == len(versions) else auth_symbol - auth_text = f"{auth_symbol}" if brand == "kasa" else "" - models_text[brand] += ( - f"
{model}{auth_text}" + detail_model_text = ( + f"
{model}{auth_symbol}" + f"{versions_text}
" ) - line = ( - f"| {type_} | {models_text.get('kasa')} | {models_text.get('tapo')} |\n" - ) + summary_model_text = model + auth_symbol + model_text = summary_model_text if summary else detail_model_text + models_list.append(model_text) + if summary: + models_text = ", ".join(models_list) + line = f"- **{type_}** - {models_text}\n" + else: + models_text = "\n".join(models_list) + line = f"**{type_}**\n\n{models_text}\n
\n\n" lines.append(line) - with open(SUPPORTED_FILENAME, "w") as the_file: - for line in lines: - the_file.write(line) + return lines def _get_smart_supported(supported): diff --git a/test.rst b/test.rst deleted file mode 100644 index e2cede97b..000000000 --- a/test.rst +++ /dev/null @@ -1,4 +0,0 @@ -Test file -========= - -.. include:: test2.rst diff --git a/test2.rst b/test2.rst deleted file mode 100644 index 1376f2953..000000000 --- a/test2.rst +++ /dev/null @@ -1,2 +0,0 @@ -Test file 2 -=========== From b1bc30bfa9da9de025d7075e66e524f731b7036f Mon Sep 17 00:00:00 2001 From: sdb9696 Date: Fri, 23 Feb 2024 10:50:37 +0000 Subject: [PATCH 04/23] Update post review --- README.md | 22 +-- SUPPORTED.md | 298 +++++++++++++++++++++++++-------------- devtools/dump_devinfo.py | 66 ++++----- 3 files changed, 228 insertions(+), 158 deletions(-) diff --git a/README.md b/README.md index 13802b024..3a214501c 100644 --- a/README.md +++ b/README.md @@ -225,25 +225,25 @@ The following devices have been tested and confirmed as working. If your device ### Supported Kasa devices -- **Plugs** - EP10, EP25\*, ES20M, HS100\*\*, HS103, HS105, HS110, HS220, KP100, KP105, KP115, KP125, KP125M\*, KP401, KP405, KS220M, KS230 -- **Power Strips** - EP40, HS107, HS300, KP200, KP303, KP400 -- **Wall Switches** - HS200, HS210, KS200M, KS205\*, KS225\* -- **Bulbs** - KL110, KL120, KL125, KL130, KL135, KL50, KL60, LB100, LB110, LB120, LB130 -- **Light Strips** - KL400L5, KL420L5, KL430 +- **Plugs**: EP10, EP25\*, ES20M, HS100\*\*, HS103, HS105, HS110, HS220, KP100, KP105, KP115, KP125, KP125M\*, KP401, KP405, KS220M, KS230 +- **Power Strips**: EP40, HS107, HS300, KP200, KP303, KP400 +- **Wall Switches**: HS200, HS210, KS200M, KS205\*, KS225\* +- **Bulbs**: KL110, KL120, KL125, KL130, KL135, KL50, KL60, LB100, LB110, LB120, LB130 +- **Light Strips**: KL400L5, KL420L5, KL430 ### Supported Tapo\* devices -- **Plugs** - P100, P110, P125M, P135, TP15 -- **Power Strips** - P300, TP25 -- **Wall Switches** - S500D, S505 -- **Bulbs** - L510B, L510E, L530E -- **Light Strips** - L900-10, L900-5, L920-5, L930-5 +- **Plugs**: P100, P110, P125M, P135, TP15 +- **Power Strips**: P300, TP25 +- **Wall Switches**: S500D, S505 +- **Bulbs**: L510B, L510E, L530E +- **Light Strips**: L900-10, L900-5, L920-5, L930-5 *  Model requires authentication
** Newer versions require authentication -The following [supported devices link](SUPPORTED.md) lists the detailed device hardware and software versions that have been manually verified to work. +See [supported devices in our documentation](SUPPORTED.md) for more detailed information about tested hardware and software versions. ## Resources diff --git a/SUPPORTED.md b/SUPPORTED.md index 377536f28..56d82d48f 100644 --- a/SUPPORTED.md +++ b/SUPPORTED.md @@ -1,116 +1,200 @@ # Supported devices -**If your device is unlisted but working, please open a pull request to update the list and add a fixture file (use `python -m devtools.dump_devinfo` to generate one).** +The following devices have been tested and confirmed as working. If your device is unlisted but working, please open a pull request to update the list and add a fixture file (use `python -m devtools.dump_devinfo` to generate one). -## Tested device versions - -▸  Expand to see tested versions
*  Model requires authentication
** Newer versions require authentication -### Supported Kasa devices - -**Plugs** - -
EP10Hardware 1.0(US) Firmare 1.0.2
-
EP25 *Hardware 2.6(US) Firmare 1.0.1 *
Hardware 2.6(US) Firmare 1.0.2 *
-
ES20MHardware 1.0(US) Firmare 1.0.8
-
HS100 **Hardware 2.0(US) Firmare 1.5.6
Hardware 4.1(UK) Firmare 1.1.0 *
Hardware 1.0(US) Firmare 1.2.5
Hardware 1.0(UK) Firmare 1.2.6
-
HS103Hardware 1.0(US) Firmare 1.5.7
Hardware 2.1(US) Firmare 1.1.4
Hardware 2.1(US) Firmare 1.1.2
-
HS105Hardware 1.0(US) Firmare 1.2.9
Hardware 1.0(US) Firmare 1.5.6
-
HS110Hardware 2.0(EU) Firmare 1.5.2
Hardware 4.0(EU) Firmare 1.0.4
Hardware 1.0(EU) Firmare 1.2.5
Hardware 1.0(US) Firmare 1.0.8
-
HS220Hardware 1.0(US) Firmare 1.5.7
Hardware 2.0(US) Firmare 1.0.3
Hardware 1.0(US) Firmare 1.5.7
-
KP100Hardware 3.0(US) Firmare 1.0.1
-
KP105Hardware 1.0(UK) Firmare 1.0.7
Hardware 1.0(UK) Firmare 1.0.5
-
KP115Hardware 1.0(US) Firmare 1.0.21
Hardware 1.0(US) Firmare 1.0.17
Hardware 1.0(EU) Firmare 1.0.16
-
KP125Hardware 1.0(US) Firmare 1.0.6
-
KP125M *Hardware 1.0(US) Firmare 1.1.3 *
-
KP401Hardware 1.0(US) Firmare 1.0.0
-
KP405Hardware 1.0(US) Firmare 1.0.5
-
KS220MHardware 1.0(US) Firmare 1.0.4
-
KS230Hardware 1.0(US) Firmare 1.0.14
-
- -**Power Strips** - -
EP40Hardware 1.0(US) Firmare 1.0.2
-
HS107Hardware 1.0(US) Firmare 1.0.8
-
HS300Hardware 1.0(US) Firmare 1.0.10
Hardware 2.0(US) Firmare 1.0.3
Hardware 2.0(US) Firmare 1.0.12
-
KP200Hardware 3.0(US) Firmare 1.0.3
-
KP303Hardware 2.0(US) Firmare 1.0.3
Hardware 1.0(UK) Firmare 1.0.3
-
KP400Hardware 1.0(US) Firmare 1.0.10
Hardware 2.0(US) Firmare 1.0.6
-
- -**Wall Switches** - -
HS200Hardware 5.0(US) Firmare 1.0.2
Hardware 2.0(US) Firmare 1.5.7
Hardware 1.0(US) Firmare 1.1.0
-
HS210Hardware 1.0(US) Firmare 1.5.8
-
KS200MHardware 1.0(US) Firmare 1.0.8
-
KS205 *Hardware 1.0(US) Firmare 1.0.2 *
-
KS225 *Hardware 1.0(US) Firmare 1.0.2 *
-
- -**Bulbs** - -
KL110Hardware 1.0(US) Firmare 1.8.11
-
KL120Hardware 1.0(US) Firmare 1.8.6
-
KL125Hardware 4.0(US) Firmare 1.0.5
Hardware 2.0(US) Firmare 1.0.7
Hardware 1.20(US) Firmare 1.0.5
-
KL130Hardware 1.0(US) Firmare 1.8.11
Hardware 1.0(EU) Firmare 1.8.8
-
KL135Hardware 1.0(US) Firmare 1.0.6
-
KL50Hardware 1.0(US) Firmare 1.1.13
-
KL60Hardware 1.0(US) Firmare 1.1.13
Hardware 1.0(UN) Firmare 1.1.4
-
LB100Hardware 1.0(US) Firmare 1.4.3
-
LB110Hardware 1.0(US) Firmare 1.8.11
-
LB120Hardware 1.0(US) Firmare 1.1.0
-
LB130Hardware 1.0(US) Firmare 1.6.0
-
- -**Light Strips** - -
KL400L5Hardware 1.0(US) Firmare 1.0.8
Hardware 1.0(US) Firmare 1.0.5
-
KL420L5Hardware 1.0(US) Firmare 1.0.2
-
KL430Hardware 2.0(US) Firmare 1.0.11
Hardware 2.0(US) Firmare 1.0.8
Hardware 1.0(US) Firmare 1.0.10
Hardware 2.0(US) Firmare 1.0.9
Hardware 2.0(UN) Firmare 1.0.8
-
- - -### Supported Tapo * devices - -**Plugs** - -
P100Hardware 1.0.0 Firmare 1.3.7
Hardware 1.0.0 Firmare 1.1.3
-
P110Hardware 1.0(EU) Firmare 1.2.3
Hardware 1.0(UK) Firmare 1.3.0
-
P125MHardware 1.0(US) Firmare 1.1.0
-
P135Hardware 1.0(US) Firmare 1.0.5
-
TP15Hardware 1.0(US) Firmare 1.0.3
-
- -**Power Strips** - -
P300Hardware 1.0(EU) Firmare 1.0.7
Hardware 1.0(EU) Firmare 1.0.13
-
TP25Hardware 1.0(US) Firmare 1.0.2
-
- -**Wall Switches** - -
S500DHardware 1.0(US) Firmare 1.0.5
-
S505Hardware 1.0(US) Firmare 1.0.2
-
- -**Bulbs** - -
L510BHardware 3.0(EU) Firmare 1.0.5
-
L510EHardware 3.0(US) Firmare 1.0.5
Hardware 3.0(US) Firmare 1.1.2
-
L530EHardware 3.0(EU) Firmare 1.1.0
Hardware 3.0(EU) Firmare 1.0.6
Hardware 2.0(US) Firmare 1.1.0
-
- -**Light Strips** - -
L900-10Hardware 1.0(US) Firmare 1.0.11
Hardware 1.0(EU) Firmare 1.0.17
-
L900-5Hardware 1.0(EU) Firmare 1.1.0
Hardware 1.0(EU) Firmare 1.0.17
-
L920-5Hardware 1.0(US) Firmare 1.1.3
Hardware 1.0(US) Firmare 1.1.0
-
L930-5Hardware 1.0(US) Firmare 1.1.2
-
- +## Kasa devices + + +### Plugs + +- **EP10** + - Hardware 1.0 (US) Firmare 1.0.2 +- **EP25\*** + - Hardware 2.6 (US) Firmare 1.0.1\* + - Hardware 2.6 (US) Firmare 1.0.2\* +- **ES20M** + - Hardware 1.0 (US) Firmare 1.0.8 +- **HS100\*\*** + - Hardware 2.0 (US) Firmare 1.5.6 + - Hardware 4.1 (UK) Firmare 1.1.0\* + - Hardware 1.0 (US) Firmare 1.2.5 + - Hardware 1.0 (UK) Firmare 1.2.6 +- **HS103** + - Hardware 1.0 (US) Firmare 1.5.7 + - Hardware 2.1 (US) Firmare 1.1.4 + - Hardware 2.1 (US) Firmare 1.1.2 +- **HS105** + - Hardware 1.0 (US) Firmare 1.2.9 + - Hardware 1.0 (US) Firmare 1.5.6 +- **HS110** + - Hardware 2.0 (EU) Firmare 1.5.2 + - Hardware 4.0 (EU) Firmare 1.0.4 + - Hardware 1.0 (EU) Firmare 1.2.5 + - Hardware 1.0 (US) Firmare 1.0.8 +- **HS220** + - Hardware 1.0 (US) Firmare 1.5.7 + - Hardware 2.0 (US) Firmare 1.0.3 + - Hardware 1.0 (US) Firmare 1.5.7 +- **KP100** + - Hardware 3.0 (US) Firmare 1.0.1 +- **KP105** + - Hardware 1.0 (UK) Firmare 1.0.7 + - Hardware 1.0 (UK) Firmare 1.0.5 +- **KP115** + - Hardware 1.0 (US) Firmare 1.0.21 + - Hardware 1.0 (US) Firmare 1.0.17 + - Hardware 1.0 (EU) Firmare 1.0.16 +- **KP125** + - Hardware 1.0 (US) Firmare 1.0.6 +- **KP125M\*** + - Hardware 1.0 (US) Firmare 1.1.3\* +- **KP401** + - Hardware 1.0 (US) Firmare 1.0.0 +- **KP405** + - Hardware 1.0 (US) Firmare 1.0.5 +- **KS220M** + - Hardware 1.0 (US) Firmare 1.0.4 +- **KS230** + - Hardware 1.0 (US) Firmare 1.0.14 + +### Power Strips + +- **EP40** + - Hardware 1.0 (US) Firmare 1.0.2 +- **HS107** + - Hardware 1.0 (US) Firmare 1.0.8 +- **HS300** + - Hardware 1.0 (US) Firmare 1.0.10 + - Hardware 2.0 (US) Firmare 1.0.3 + - Hardware 2.0 (US) Firmare 1.0.12 +- **KP200** + - Hardware 3.0 (US) Firmare 1.0.3 +- **KP303** + - Hardware 2.0 (US) Firmare 1.0.3 + - Hardware 1.0 (UK) Firmare 1.0.3 +- **KP400** + - Hardware 1.0 (US) Firmare 1.0.10 + - Hardware 2.0 (US) Firmare 1.0.6 + +### Wall Switches + +- **HS200** + - Hardware 5.0 (US) Firmare 1.0.2 + - Hardware 2.0 (US) Firmare 1.5.7 + - Hardware 1.0 (US) Firmare 1.1.0 +- **HS210** + - Hardware 1.0 (US) Firmare 1.5.8 +- **KS200M** + - Hardware 1.0 (US) Firmare 1.0.8 +- **KS205\*** + - Hardware 1.0 (US) Firmare 1.0.2\* +- **KS225\*** + - Hardware 1.0 (US) Firmare 1.0.2\* + +### Bulbs + +- **KL110** + - Hardware 1.0 (US) Firmare 1.8.11 +- **KL120** + - Hardware 1.0 (US) Firmare 1.8.6 +- **KL125** + - Hardware 4.0 (US) Firmare 1.0.5 + - Hardware 2.0 (US) Firmare 1.0.7 + - Hardware 1.20 (US) Firmare 1.0.5 +- **KL130** + - Hardware 1.0 (US) Firmare 1.8.11 + - Hardware 1.0 (EU) Firmare 1.8.8 +- **KL135** + - Hardware 1.0 (US) Firmare 1.0.6 +- **KL50** + - Hardware 1.0 (US) Firmare 1.1.13 +- **KL60** + - Hardware 1.0 (US) Firmare 1.1.13 + - Hardware 1.0 (UN) Firmare 1.1.4 +- **LB100** + - Hardware 1.0 (US) Firmare 1.4.3 +- **LB110** + - Hardware 1.0 (US) Firmare 1.8.11 +- **LB120** + - Hardware 1.0 (US) Firmare 1.1.0 +- **LB130** + - Hardware 1.0 (US) Firmare 1.6.0 + +### Light Strips + +- **KL400L5** + - Hardware 1.0 (US) Firmare 1.0.8 + - Hardware 1.0 (US) Firmare 1.0.5 +- **KL420L5** + - Hardware 1.0 (US) Firmare 1.0.2 +- **KL430** + - Hardware 2.0 (US) Firmare 1.0.11 + - Hardware 2.0 (US) Firmare 1.0.8 + - Hardware 1.0 (US) Firmare 1.0.10 + - Hardware 2.0 (US) Firmare 1.0.9 + - Hardware 2.0 (UN) Firmare 1.0.8 + +## Tapo\* devices + + +### Plugs + +- **P100** + - Hardware 1.0.0 Firmare 1.3.7 + - Hardware 1.0.0 Firmare 1.1.3 +- **P110** + - Hardware 1.0 (EU) Firmare 1.2.3 + - Hardware 1.0 (UK) Firmare 1.3.0 +- **P125M** + - Hardware 1.0 (US) Firmare 1.1.0 +- **P135** + - Hardware 1.0 (US) Firmare 1.0.5 +- **TP15** + - Hardware 1.0 (US) Firmare 1.0.3 + +### Power Strips + +- **P300** + - Hardware 1.0 (EU) Firmare 1.0.7 + - Hardware 1.0 (EU) Firmare 1.0.13 +- **TP25** + - Hardware 1.0 (US) Firmare 1.0.2 + +### Wall Switches + +- **S500D** + - Hardware 1.0 (US) Firmare 1.0.5 +- **S505** + - Hardware 1.0 (US) Firmare 1.0.2 + +### Bulbs + +- **L510B** + - Hardware 3.0 (EU) Firmare 1.0.5 +- **L510E** + - Hardware 3.0 (US) Firmare 1.0.5 + - Hardware 3.0 (US) Firmare 1.1.2 +- **L530E** + - Hardware 3.0 (EU) Firmare 1.1.0 + - Hardware 3.0 (EU) Firmare 1.0.6 + - Hardware 2.0 (US) Firmare 1.1.0 + +### Light Strips + +- **L900-10** + - Hardware 1.0 (US) Firmare 1.0.11 + - Hardware 1.0 (EU) Firmare 1.0.17 +- **L900-5** + - Hardware 1.0 (EU) Firmare 1.1.0 + - Hardware 1.0 (EU) Firmare 1.0.17 +- **L920-5** + - Hardware 1.0 (US) Firmare 1.1.3 + - Hardware 1.0 (US) Firmare 1.1.0 +- **L930-5** + - Hardware 1.0 (US) Firmare 1.1.2 diff --git a/devtools/dump_devinfo.py b/devtools/dump_devinfo.py index c00b46cd0..101e985d1 100644 --- a/devtools/dump_devinfo.py +++ b/devtools/dump_devinfo.py @@ -14,8 +14,6 @@ import re import traceback from collections import defaultdict, namedtuple -from os import listdir -from os.path import isfile, join from pathlib import Path from pprint import pprint from typing import Dict, List, Union @@ -497,11 +495,11 @@ async def generate_supported(): _get_iot_supported(supported) _get_smart_supported(supported) - _update_supported_file(SUPPORTED_FILENAME, supported, False) - _update_supported_file(README_FILENAME, supported, True) + _update_supported_file(SUPPORTED_FILENAME, supported, summary=False) + _update_supported_file(README_FILENAME, supported, summary=True) -def _update_supported_file(filename, supported, summary): +def _update_supported_file(filename, supported, *, summary): with open(filename) as f: contents = f.readlines() @@ -514,9 +512,9 @@ def _update_supported_file(filename, supported, summary): new_contents = contents[:start_index] end_contents = contents[end_index:] - new_contents.extend(_generate_supported(supported, "Kasa", summary)) + new_contents.extend(_generate_supported(supported, "Kasa", summary=summary)) new_contents.append("\n") - new_contents.extend(_generate_supported(supported, "Tapo", summary)) + new_contents.extend(_generate_supported(supported, "Tapo", summary=summary)) new_contents.append("\n") new_contents.extend(end_contents) @@ -525,12 +523,13 @@ def _update_supported_file(filename, supported, summary): f.write(new_contents) -def _generate_supported(supported, target_brand, summary): - single_star = "\*" if summary else " *" - double_star = "\*\*" if summary else " **" +def _generate_supported(supported, target_brand, *, summary): + single_star = "\*" + double_star = "\*\*" brand_auth = single_star if target_brand.lower() == "tapo" else "" + header_start = "### Supported " if summary else "## " lines = [ - f"### Supported {target_brand}{brand_auth} devices\n", + f"{header_start}{target_brand}{brand_auth} devices\n", "\n", ] for type_, brands in supported.items(): @@ -543,12 +542,12 @@ def _generate_supported(supported, target_brand, summary): auth_count = 0 auth_symbol = "" for version in versions: - region_text = f"({version.region})" if version.region else "" + region_text = f" ({version.region})" if version.region else "" version_auth = ( single_star if version.auth and brand == "kasa" else "" ) version_text = ( - f"Hardware {version.hw}{region_text} " + f" - Hardware {version.hw}{region_text} " + f"Firmare {version.fw}{version_auth}" ) version_list.append(version_text) @@ -559,32 +558,25 @@ def _generate_supported(supported, target_brand, summary): single_star if auth_count == len(versions) else auth_symbol ) auth_symbol = auth_symbol if brand == "kasa" else "" - versions_text = "
".join(version_list) - detail_model_text = ( - f"
{model}{auth_symbol}" - + f"{versions_text}
" - ) + versions_text = "\n".join(version_list) + detail_model_text = f"- **{model}{auth_symbol}**" + f"\n{versions_text}" summary_model_text = model + auth_symbol model_text = summary_model_text if summary else detail_model_text models_list.append(model_text) if summary: models_text = ", ".join(models_list) - line = f"- **{type_}** - {models_text}\n" + line = f"- **{type_}**: {models_text}\n" else: models_text = "\n".join(models_list) - line = f"**{type_}**\n\n{models_text}\n
\n\n" + line = f"\n### {type_}\n\n{models_text}\n" lines.append(line) return lines def _get_smart_supported(supported): - smart_files = [ - f - for f in listdir(SMART_FOLDER) - if isfile(join(SMART_FOLDER, f)) and f.endswith(".json") - ] + smart_files = [f for f in Path(SMART_FOLDER).glob("*.json")] for smart_file in smart_files: - with open(join(SMART_FOLDER, smart_file)) as f: + with open(smart_file) as f: fixture_data = json.load(f) model, _, region = fixture_data["discovery_result"]["device_model"].partition( @@ -593,11 +585,9 @@ def _get_smart_supported(supported): # P100 doesn't have region HW region = region.replace(")", "") if region else "" device_type = fixture_data["discovery_result"]["device_type"] - if device_type[:10] == "SMART.KASA": - brand = "kasa" - elif device_type[:10] == "SMART.TAPO": - brand = "tapo" - else: + _protocol, devicetype = device_type.split(".") + brand, type_ = devicetype[:4].lower(), devicetype[4:] + if brand not in ["kasa", "tapo"]: click.echo( click.style( f"FAIL {smart_file} does not have a " @@ -610,11 +600,11 @@ def _get_smart_supported(supported): component["id"] for component in fixture_data["component_nego"]["component_list"] ] - if device_type[10:] == "BULB": + if type_ == "BULB": supported_type = LIGHT_STRIPS if "light_strip" in components else BULBS - elif device_type[10:] == "PLUG": + elif type_ == "PLUG": supported_type = POWER_STRIPS if "child_device" in components else PLUGS - elif device_type[10:] == "SWITCH": + elif type_ == "SWITCH": supported_type = WALL_SWITCHES else: click.echo( @@ -637,13 +627,9 @@ def _get_smart_supported(supported): def _get_iot_supported(supported): - iot_files = [ - f - for f in listdir(IOT_FOLDER) - if isfile(join(IOT_FOLDER, f)) and f.endswith(".json") - ] + iot_files = [f for f in Path(IOT_FOLDER).glob("*.json")] for iot_file in iot_files: - with open(join(IOT_FOLDER, iot_file)) as f: + with open(iot_file) as f: fixture_data = json.load(f) sysinfo = fixture_data["system"]["get_sysinfo"] model, _, region = sysinfo["model"][:-1].partition("(") From 729ac711b67f4ed45b930670d7454dc7cab6cb88 Mon Sep 17 00:00:00 2001 From: sdb9696 Date: Tue, 27 Feb 2024 17:57:55 +0000 Subject: [PATCH 05/23] Remove star from tapo versions --- SUPPORTED.md | 56 ++++++++++++++++++++-------------------- devtools/dump_devinfo.py | 1 + 2 files changed, 29 insertions(+), 28 deletions(-) diff --git a/SUPPORTED.md b/SUPPORTED.md index 2b0c7aa2a..f923152a0 100644 --- a/SUPPORTED.md +++ b/SUPPORTED.md @@ -145,65 +145,65 @@ The following devices have been tested and confirmed as working. If your device ### Hubs - **H100** - - Hardware 1.0 (EU) Firmare 1.5.5\* - - Hardware 1.0 (EU) Firmare 1.2.3\* + - Hardware 1.0 (EU) Firmare 1.5.5 + - Hardware 1.0 (EU) Firmare 1.2.3 ### Bulbs - **L510B** - - Hardware 3.0 (EU) Firmare 1.0.5\* + - Hardware 3.0 (EU) Firmare 1.0.5 - **L510E** - - Hardware 3.0 (US) Firmare 1.0.5\* - - Hardware 3.0 (US) Firmare 1.1.2\* + - Hardware 3.0 (US) Firmare 1.0.5 + - Hardware 3.0 (US) Firmare 1.1.2 - **L530E** - - Hardware 3.0 (EU) Firmare 1.1.0\* - - Hardware 3.0 (EU) Firmare 1.1.6\* - - Hardware 3.0 (EU) Firmare 1.0.6\* - - Hardware 2.0 (US) Firmare 1.1.0\* + - Hardware 3.0 (EU) Firmare 1.1.0 + - Hardware 3.0 (EU) Firmare 1.1.6 + - Hardware 3.0 (EU) Firmare 1.0.6 + - Hardware 2.0 (US) Firmare 1.1.0 ### Light Strips - **L900-10** - - Hardware 1.0 (US) Firmare 1.0.11\* - - Hardware 1.0 (EU) Firmare 1.0.17\* + - Hardware 1.0 (US) Firmare 1.0.11 + - Hardware 1.0 (EU) Firmare 1.0.17 - **L900-5** - - Hardware 1.0 (EU) Firmare 1.1.0\* - - Hardware 1.0 (EU) Firmare 1.0.17\* + - Hardware 1.0 (EU) Firmare 1.1.0 + - Hardware 1.0 (EU) Firmare 1.0.17 - **L920-5** - - Hardware 1.0 (US) Firmare 1.1.3\* - - Hardware 1.0 (US) Firmare 1.1.0\* + - Hardware 1.0 (US) Firmare 1.1.3 + - Hardware 1.0 (US) Firmare 1.1.0 - **L930-5** - - Hardware 1.0 (US) Firmare 1.1.2\* + - Hardware 1.0 (US) Firmare 1.1.2 ### Plugs - **P100** - - Hardware 1.0.0 Firmare 1.3.7\* - - Hardware 1.0.0 Firmare 1.1.3\* + - Hardware 1.0.0 Firmare 1.3.7 + - Hardware 1.0.0 Firmare 1.1.3 - **P110** - - Hardware 1.0 (EU) Firmare 1.2.3\* - - Hardware 1.0 (UK) Firmare 1.3.0\* + - Hardware 1.0 (EU) Firmare 1.2.3 + - Hardware 1.0 (UK) Firmare 1.3.0 - **P125M** - - Hardware 1.0 (US) Firmare 1.1.0\* + - Hardware 1.0 (US) Firmare 1.1.0 - **P135** - - Hardware 1.0 (US) Firmare 1.0.5\* + - Hardware 1.0 (US) Firmare 1.0.5 - **TP15** - - Hardware 1.0 (US) Firmare 1.0.3\* + - Hardware 1.0 (US) Firmare 1.0.3 ### Power Strips - **P300** - - Hardware 1.0 (EU) Firmare 1.0.7\* - - Hardware 1.0 (EU) Firmare 1.0.13\* + - Hardware 1.0 (EU) Firmare 1.0.7 + - Hardware 1.0 (EU) Firmare 1.0.13 - **TP25** - - Hardware 1.0 (US) Firmare 1.0.2\* + - Hardware 1.0 (US) Firmare 1.0.2 ### Wall Switches - **S500D** - - Hardware 1.0 (US) Firmare 1.0.5\* + - Hardware 1.0 (US) Firmare 1.0.5 - **S505** - - Hardware 1.0 (US) Firmare 1.0.2\* + - Hardware 1.0 (US) Firmare 1.0.2 diff --git a/devtools/dump_devinfo.py b/devtools/dump_devinfo.py index 89e6bd777..f34a714fa 100644 --- a/devtools/dump_devinfo.py +++ b/devtools/dump_devinfo.py @@ -560,6 +560,7 @@ def _supported_text( region_text = f" ({version.region})" if version.region else "" auth_count += 1 if version.auth else 0 vauth_flag = "\*" if version.auth else "" + vauth_flag = "" if brand == "tapo" else vauth_flag if version_template: versions_text += versst.substitute( hw=version.hw, From 207876d2bbe064f8375b43811608cf4ad2527e99 Mon Sep 17 00:00:00 2001 From: sdb9696 Date: Wed, 28 Feb 2024 10:15:05 +0000 Subject: [PATCH 06/23] Move generate-supported and run as pre-commit hook --- .github/workflows/ci.yml | 4 +- .pre-commit-config.yaml | 11 ++ README.md | 2 - SUPPORTED.md | 218 ++++++++++++------------- devtools/check_readme_vs_fixtures.py | 43 ----- devtools/dump_devinfo.py | 232 +------------------------- devtools/generate_supported.py | 236 +++++++++++++++++++++++++++ docs/source/SUPPORTED.md | 2 - poetry.lock | 30 +++- pyproject.toml | 3 +- 10 files changed, 389 insertions(+), 392 deletions(-) delete mode 100644 devtools/check_readme_vs_fixtures.py create mode 100644 devtools/generate_supported.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 779f6b19c..6389dee8c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -47,9 +47,9 @@ jobs: - name: "Run check-ast" run: | poetry run pre-commit run check-ast --all-files - - name: "Check README for supported models" + - name: "Check supported device md files are up to date" run: | - poetry run python -m devtools.check_readme_vs_fixtures + poetry run pre-commit run generate-supported --all-files tests: diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 4bbfd8c51..6085bc7dd 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -27,3 +27,14 @@ repos: hooks: - id: doc8 additional_dependencies: [tomli] + +- repo: local + hooks: + - id: generate-supported + name: Generate supported devices + description: This hook generates the supported device sections of README.md and SUPPORTED.md + entry: generate-supported + language: system # Required or pre-commit creates a new venv + verbose: true # Show output on success + types: [json] + pass_filenames: false # passing filenames causes the hook to run in batches against all-files diff --git a/README.md b/README.md index 188fada0c..dea220cfe 100644 --- a/README.md +++ b/README.md @@ -224,7 +224,6 @@ The following devices have been tested and confirmed as working. If your device - ### Supported Kasa devices - **Plugs**: EP10, EP25\*, ES20M, HS100\*\*, HS103, HS105, HS110, HS220, KP100, KP105, KP115, KP125, KP125M\*, KP401, KP405, KS220M, KS230 @@ -242,7 +241,6 @@ The following devices have been tested and confirmed as working. If your device - **Power Strips**: P300, TP25 - **Wall Switches**: S500D, S505 - *  Model requires authentication
** Newer versions require authentication diff --git a/SUPPORTED.md b/SUPPORTED.md index f923152a0..67cdb69e4 100644 --- a/SUPPORTED.md +++ b/SUPPORTED.md @@ -2,209 +2,207 @@ The following devices have been tested and confirmed as working. If your device is unlisted but working, please open a pull request to update the list and add a fixture file (use `python -m devtools.dump_devinfo` to generate one). -*  Model requires authentication
-** Newer versions require authentication +*  Kasa model requires authentication
+All Tapo devices require authentication - ## Kasa devices ### Plugs - **EP10** - - Hardware 1.0 (US) Firmare 1.0.2 -- **EP25\*** - - Hardware 2.6 (US) Firmare 1.0.1\* - - Hardware 2.6 (US) Firmare 1.0.2\* + - Hardware: 1.0 (US) / Firmware: 1.0.2 +- **EP25** + - Hardware: 2.6 (US) / Firmware: 1.0.1\* + - Hardware: 2.6 (US) / Firmware: 1.0.2\* - **ES20M** - - Hardware 1.0 (US) Firmare 1.0.8 -- **HS100\*\*** - - Hardware 2.0 (US) Firmare 1.5.6 - - Hardware 4.1 (UK) Firmare 1.1.0\* - - Hardware 1.0 (US) Firmare 1.2.5 - - Hardware 1.0 (UK) Firmare 1.2.6 + - Hardware: 1.0 (US) / Firmware: 1.0.8 +- **HS100** + - Hardware: 2.0 (US) / Firmware: 1.5.6 + - Hardware: 4.1 (UK) / Firmware: 1.1.0\* + - Hardware: 1.0 (US) / Firmware: 1.2.5 + - Hardware: 1.0 (UK) / Firmware: 1.2.6 - **HS103** - - Hardware 1.0 (US) Firmare 1.5.7 - - Hardware 2.1 (US) Firmare 1.1.4 - - Hardware 2.1 (US) Firmare 1.1.2 + - Hardware: 1.0 (US) / Firmware: 1.5.7 + - Hardware: 2.1 (US) / Firmware: 1.1.4 + - Hardware: 2.1 (US) / Firmware: 1.1.2 - **HS105** - - Hardware 1.0 (US) Firmare 1.2.9 - - Hardware 1.0 (US) Firmare 1.5.6 + - Hardware: 1.0 (US) / Firmware: 1.2.9 + - Hardware: 1.0 (US) / Firmware: 1.5.6 - **HS110** - - Hardware 2.0 (EU) Firmare 1.5.2 - - Hardware 4.0 (EU) Firmare 1.0.4 - - Hardware 1.0 (EU) Firmare 1.2.5 - - Hardware 1.0 (US) Firmare 1.0.8 + - Hardware: 2.0 (EU) / Firmware: 1.5.2 + - Hardware: 4.0 (EU) / Firmware: 1.0.4 + - Hardware: 1.0 (EU) / Firmware: 1.2.5 + - Hardware: 1.0 (US) / Firmware: 1.0.8 - **HS220** - - Hardware 1.0 (US) Firmare 1.5.7 - - Hardware 2.0 (US) Firmare 1.0.3 - - Hardware 1.0 (US) Firmare 1.5.7 + - Hardware: 1.0 (US) / Firmware: 1.5.7 + - Hardware: 2.0 (US) / Firmware: 1.0.3 + - Hardware: 1.0 (US) / Firmware: 1.5.7 - **KP100** - - Hardware 3.0 (US) Firmare 1.0.1 + - Hardware: 3.0 (US) / Firmware: 1.0.1 - **KP105** - - Hardware 1.0 (UK) Firmare 1.0.7 - - Hardware 1.0 (UK) Firmare 1.0.5 + - Hardware: 1.0 (UK) / Firmware: 1.0.7 + - Hardware: 1.0 (UK) / Firmware: 1.0.5 - **KP115** - - Hardware 1.0 (US) Firmare 1.0.21 - - Hardware 1.0 (US) Firmare 1.0.17 - - Hardware 1.0 (EU) Firmare 1.0.16 + - Hardware: 1.0 (US) / Firmware: 1.0.21 + - Hardware: 1.0 (US) / Firmware: 1.0.17 + - Hardware: 1.0 (EU) / Firmware: 1.0.16 - **KP125** - - Hardware 1.0 (US) Firmare 1.0.6 -- **KP125M\*** - - Hardware 1.0 (US) Firmare 1.1.3\* + - Hardware: 1.0 (US) / Firmware: 1.0.6 +- **KP125M** + - Hardware: 1.0 (US) / Firmware: 1.1.3\* - **KP401** - - Hardware 1.0 (US) Firmare 1.0.0 + - Hardware: 1.0 (US) / Firmware: 1.0.0 - **KP405** - - Hardware 1.0 (US) Firmare 1.0.5 + - Hardware: 1.0 (US) / Firmware: 1.0.5 - **KS220M** - - Hardware 1.0 (US) Firmare 1.0.4 + - Hardware: 1.0 (US) / Firmware: 1.0.4 - **KS230** - - Hardware 1.0 (US) Firmare 1.0.14 + - Hardware: 1.0 (US) / Firmware: 1.0.14 ### Bulbs - **KL110** - - Hardware 1.0 (US) Firmare 1.8.11 + - Hardware: 1.0 (US) / Firmware: 1.8.11 - **KL120** - - Hardware 1.0 (US) Firmare 1.8.6 + - Hardware: 1.0 (US) / Firmware: 1.8.6 - **KL125** - - Hardware 4.0 (US) Firmare 1.0.5 - - Hardware 2.0 (US) Firmare 1.0.7 - - Hardware 1.20 (US) Firmare 1.0.5 + - Hardware: 4.0 (US) / Firmware: 1.0.5 + - Hardware: 2.0 (US) / Firmware: 1.0.7 + - Hardware: 1.20 (US) / Firmware: 1.0.5 - **KL130** - - Hardware 1.0 (US) Firmare 1.8.11 - - Hardware 1.0 (EU) Firmare 1.8.8 + - Hardware: 1.0 (US) / Firmware: 1.8.11 + - Hardware: 1.0 (EU) / Firmware: 1.8.8 - **KL135** - - Hardware 1.0 (US) Firmare 1.0.6 + - Hardware: 1.0 (US) / Firmware: 1.0.6 - **KL50** - - Hardware 1.0 (US) Firmare 1.1.13 + - Hardware: 1.0 (US) / Firmware: 1.1.13 - **KL60** - - Hardware 1.0 (US) Firmare 1.1.13 - - Hardware 1.0 (UN) Firmare 1.1.4 + - Hardware: 1.0 (US) / Firmware: 1.1.13 + - Hardware: 1.0 (UN) / Firmware: 1.1.4 - **LB100** - - Hardware 1.0 (US) Firmare 1.4.3 + - Hardware: 1.0 (US) / Firmware: 1.4.3 - **LB110** - - Hardware 1.0 (US) Firmare 1.8.11 + - Hardware: 1.0 (US) / Firmware: 1.8.11 - **LB120** - - Hardware 1.0 (US) Firmare 1.1.0 + - Hardware: 1.0 (US) / Firmware: 1.1.0 - **LB130** - - Hardware 1.0 (US) Firmare 1.6.0 + - Hardware: 1.0 (US) / Firmware: 1.6.0 ### Light Strips - **KL400L5** - - Hardware 1.0 (US) Firmare 1.0.8 - - Hardware 1.0 (US) Firmare 1.0.5 + - Hardware: 1.0 (US) / Firmware: 1.0.8 + - Hardware: 1.0 (US) / Firmware: 1.0.5 - **KL420L5** - - Hardware 1.0 (US) Firmare 1.0.2 + - Hardware: 1.0 (US) / Firmware: 1.0.2 - **KL430** - - Hardware 2.0 (US) Firmare 1.0.11 - - Hardware 2.0 (US) Firmare 1.0.8 - - Hardware 1.0 (US) Firmare 1.0.10 - - Hardware 2.0 (US) Firmare 1.0.9 - - Hardware 2.0 (UN) Firmare 1.0.8 + - Hardware: 2.0 (US) / Firmware: 1.0.11 + - Hardware: 2.0 (US) / Firmware: 1.0.8 + - Hardware: 1.0 (US) / Firmware: 1.0.10 + - Hardware: 2.0 (US) / Firmware: 1.0.9 + - Hardware: 2.0 (UN) / Firmware: 1.0.8 ### Power Strips - **EP40** - - Hardware 1.0 (US) Firmare 1.0.2 + - Hardware: 1.0 (US) / Firmware: 1.0.2 - **HS107** - - Hardware 1.0 (US) Firmare 1.0.8 + - Hardware: 1.0 (US) / Firmware: 1.0.8 - **HS300** - - Hardware 1.0 (US) Firmare 1.0.10 - - Hardware 2.0 (US) Firmare 1.0.3 - - Hardware 2.0 (US) Firmare 1.0.12 + - Hardware: 1.0 (US) / Firmware: 1.0.10 + - Hardware: 2.0 (US) / Firmware: 1.0.3 + - Hardware: 2.0 (US) / Firmware: 1.0.12 - **KP200** - - Hardware 3.0 (US) Firmare 1.0.3 + - Hardware: 3.0 (US) / Firmware: 1.0.3 - **KP303** - - Hardware 2.0 (US) Firmare 1.0.3 - - Hardware 1.0 (UK) Firmare 1.0.3 + - Hardware: 2.0 (US) / Firmware: 1.0.3 + - Hardware: 1.0 (UK) / Firmware: 1.0.3 - **KP400** - - Hardware 1.0 (US) Firmare 1.0.10 - - Hardware 2.0 (US) Firmare 1.0.6 + - Hardware: 1.0 (US) / Firmware: 1.0.10 + - Hardware: 2.0 (US) / Firmware: 1.0.6 ### Wall Switches - **HS200** - - Hardware 5.0 (US) Firmare 1.0.2 - - Hardware 2.0 (US) Firmare 1.5.7 - - Hardware 1.0 (US) Firmare 1.1.0 + - Hardware: 5.0 (US) / Firmware: 1.0.2 + - Hardware: 2.0 (US) / Firmware: 1.5.7 + - Hardware: 1.0 (US) / Firmware: 1.1.0 - **HS210** - - Hardware 1.0 (US) Firmare 1.5.8 + - Hardware: 1.0 (US) / Firmware: 1.5.8 - **KS200M** - - Hardware 1.0 (US) Firmare 1.0.8 -- **KS205\*** - - Hardware 1.0 (US) Firmare 1.0.2\* -- **KS225\*** - - Hardware 1.0 (US) Firmare 1.0.2\* + - Hardware: 1.0 (US) / Firmware: 1.0.8 +- **KS205** + - Hardware: 1.0 (US) / Firmware: 1.0.2\* +- **KS225** + - Hardware: 1.0 (US) / Firmware: 1.0.2\* -## Tapo\* devices +## Tapo devices ### Hubs - **H100** - - Hardware 1.0 (EU) Firmare 1.5.5 - - Hardware 1.0 (EU) Firmare 1.2.3 + - Hardware: 1.0 (EU) / Firmware: 1.5.5 + - Hardware: 1.0 (EU) / Firmware: 1.2.3 ### Bulbs - **L510B** - - Hardware 3.0 (EU) Firmare 1.0.5 + - Hardware: 3.0 (EU) / Firmware: 1.0.5 - **L510E** - - Hardware 3.0 (US) Firmare 1.0.5 - - Hardware 3.0 (US) Firmare 1.1.2 + - Hardware: 3.0 (US) / Firmware: 1.0.5 + - Hardware: 3.0 (US) / Firmware: 1.1.2 - **L530E** - - Hardware 3.0 (EU) Firmare 1.1.0 - - Hardware 3.0 (EU) Firmare 1.1.6 - - Hardware 3.0 (EU) Firmare 1.0.6 - - Hardware 2.0 (US) Firmare 1.1.0 + - Hardware: 3.0 (EU) / Firmware: 1.1.0 + - Hardware: 3.0 (EU) / Firmware: 1.1.6 + - Hardware: 3.0 (EU) / Firmware: 1.0.6 + - Hardware: 2.0 (US) / Firmware: 1.1.0 ### Light Strips - **L900-10** - - Hardware 1.0 (US) Firmare 1.0.11 - - Hardware 1.0 (EU) Firmare 1.0.17 + - Hardware: 1.0 (US) / Firmware: 1.0.11 + - Hardware: 1.0 (EU) / Firmware: 1.0.17 - **L900-5** - - Hardware 1.0 (EU) Firmare 1.1.0 - - Hardware 1.0 (EU) Firmare 1.0.17 + - Hardware: 1.0 (EU) / Firmware: 1.1.0 + - Hardware: 1.0 (EU) / Firmware: 1.0.17 - **L920-5** - - Hardware 1.0 (US) Firmare 1.1.3 - - Hardware 1.0 (US) Firmare 1.1.0 + - Hardware: 1.0 (US) / Firmware: 1.1.3 + - Hardware: 1.0 (US) / Firmware: 1.1.0 - **L930-5** - - Hardware 1.0 (US) Firmare 1.1.2 + - Hardware: 1.0 (US) / Firmware: 1.1.2 ### Plugs - **P100** - - Hardware 1.0.0 Firmare 1.3.7 - - Hardware 1.0.0 Firmare 1.1.3 + - Hardware: 1.0.0 / Firmware: 1.3.7 + - Hardware: 1.0.0 / Firmware: 1.1.3 - **P110** - - Hardware 1.0 (EU) Firmare 1.2.3 - - Hardware 1.0 (UK) Firmare 1.3.0 + - Hardware: 1.0 (EU) / Firmware: 1.2.3 + - Hardware: 1.0 (UK) / Firmware: 1.3.0 - **P125M** - - Hardware 1.0 (US) Firmare 1.1.0 + - Hardware: 1.0 (US) / Firmware: 1.1.0 - **P135** - - Hardware 1.0 (US) Firmare 1.0.5 + - Hardware: 1.0 (US) / Firmware: 1.0.5 - **TP15** - - Hardware 1.0 (US) Firmare 1.0.3 + - Hardware: 1.0 (US) / Firmware: 1.0.3 ### Power Strips - **P300** - - Hardware 1.0 (EU) Firmare 1.0.7 - - Hardware 1.0 (EU) Firmare 1.0.13 + - Hardware: 1.0 (EU) / Firmware: 1.0.7 + - Hardware: 1.0 (EU) / Firmware: 1.0.13 - **TP25** - - Hardware 1.0 (US) Firmare 1.0.2 + - Hardware: 1.0 (US) / Firmware: 1.0.2 ### Wall Switches - **S500D** - - Hardware 1.0 (US) Firmare 1.0.5 + - Hardware: 1.0 (US) / Firmware: 1.0.5 - **S505** - - Hardware 1.0 (US) Firmare 1.0.2 - + - Hardware: 1.0 (US) / Firmware: 1.0.2 diff --git a/devtools/check_readme_vs_fixtures.py b/devtools/check_readme_vs_fixtures.py deleted file mode 100644 index 3266d4417..000000000 --- a/devtools/check_readme_vs_fixtures.py +++ /dev/null @@ -1,43 +0,0 @@ -"""Script that checks if README.md is missing devices that have fixtures.""" -import re -import sys - -from kasa.tests.conftest import ( - ALL_DEVICES, - BULBS, - DIMMERS, - LIGHT_STRIPS, - PLUGS, - STRIPS, -) - -with open("SUPPORTED.md") as f: - readme = f.read() - -typemap = { - "light strips": LIGHT_STRIPS, - "bulbs": BULBS, - "plugs": PLUGS, - "strips": STRIPS, - "dimmers": DIMMERS, -} - - -def _get_device_type(dev, typemap): - for typename, devs in typemap.items(): - if dev in devs: - return typename - else: - return "Unknown type" - - -found_unlisted = False -for dev in ALL_DEVICES: - regex = dev - match = re.search(regex, readme, re.MULTILINE) - if match is None: - print(f"{dev} not listed in {_get_device_type(dev, typemap)}") - found_unlisted = True - -if found_unlisted: - sys.exit(-1) diff --git a/devtools/dump_devinfo.py b/devtools/dump_devinfo.py index f34a714fa..8e0126061 100644 --- a/devtools/dump_devinfo.py +++ b/devtools/dump_devinfo.py @@ -16,7 +16,6 @@ from collections import defaultdict, namedtuple from pathlib import Path from pprint import pprint -from string import Template from typing import Dict, List, Union import asyncclick as click @@ -34,26 +33,11 @@ from kasa.exceptions import SmartErrorCode from kasa.smart import SmartDevice -SupportedVersion = namedtuple("SupportedVersion", "region hw fw auth") - - Call = namedtuple("Call", "module method") SmartCall = namedtuple("SmartCall", "module request should_succeed") _LOGGER = logging.getLogger(__name__) -IOT_FOLDER = "kasa/tests/fixtures/" -SMART_FOLDER = "kasa/tests/fixtures/smart/" -SUPPORTED_FILENAME = "SUPPORTED.md" -README_FILENAME = "README.md" - -PLUGS = "Plugs" -POWER_STRIPS = "Power Strips" -WALL_SWITCHES = "Wall Switches" -BULBS = "Bulbs" -LIGHT_STRIPS = "Light Strips" -HUBS = "Hubs" - def scrub(res): """Remove identifiers from the given dict.""" @@ -143,7 +127,7 @@ def default_to_regular(d): return d -async def handle_device(basedir, autosave, device: Device, batch_size: int) -> bool: +async def handle_device(basedir, autosave, device: Device, batch_size: int): """Create a fixture for a single device instance.""" if isinstance(device, SmartDevice): filename, copy_folder, final = await get_smart_fixture(device, batch_size) @@ -165,13 +149,11 @@ async def handle_device(basedir, autosave, device: Device, batch_size: int) -> b with open(save_filename, "w") as f: json.dump(final, f, sort_keys=True, indent=4) f.write("\n") - return True else: click.echo("Not saving.") - return False -@click.group(invoke_without_command=True) +@click.command() @click.option("--host", required=False, help="Target host.") @click.option( "--target", @@ -199,10 +181,7 @@ async def handle_device(basedir, autosave, device: Device, batch_size: int) -> b "--batch-size", default=5, help="Number of batched requests to send at once" ) @click.option("-d", "--debug", is_flag=True) -@click.pass_context -async def cli( - ctx, host, target, basedir, autosave, debug, username, password, batch_size -): +async def cli(host, target, basedir, autosave, debug, username, password, batch_size): """Generate devinfo files for devices. Use --host (for a single device) or --target (for a complete network). @@ -210,16 +189,11 @@ async def cli( if debug: logging.basicConfig(level=logging.DEBUG) - if ctx.invoked_subcommand == "generate-supported": - return - credentials = Credentials(username=username, password=password) - file_created = False if host is not None: click.echo("Host given, performing discovery on %s." % host) device = await Discover.discover_single(host, credentials=credentials) - if await handle_device(basedir, autosave, device, batch_size): - file_created = True + await handle_device(basedir, autosave, device, batch_size) else: click.echo( "No --host given, performing discovery on %s. Use --target to override." @@ -228,10 +202,7 @@ async def cli( devices = await Discover.discover(target=target, credentials=credentials) click.echo("Detected %s devices" % len(devices)) for dev in devices.values(): - if await handle_device(basedir, autosave, dev, batch_size): - file_created = True - if file_created: - generate_supported() + await handle_device(basedir, autosave, dev, batch_size) async def get_legacy_fixture(device): @@ -299,7 +270,7 @@ async def get_legacy_fixture(device): sw_version = sysinfo["sw_ver"] sw_version = sw_version.split(" ", maxsplit=1)[0] save_filename = f"{model}_{hw_version}_{sw_version}.json" - copy_folder = IOT_FOLDER + copy_folder = "kasa/tests/fixtures/" return save_filename, copy_folder, final @@ -483,198 +454,9 @@ async def get_smart_fixture(device: SmartDevice, batch_size: int): sw_version = sw_version.split(" ", maxsplit=1)[0] save_filename = f"{model}_{hw_version}_{sw_version}.json" - copy_folder = SMART_FOLDER + copy_folder = "kasa/tests/fixtures/smart/" return save_filename, copy_folder, final -@cli.command(name="generate-supported") -async def generate_supported(): - """Generate the SUPPORTED.md from the fixtures.""" - supported = {"kasa": {}, "tapo": {}} - - _get_iot_supported(supported) - _get_smart_supported(supported) - - _update_supported_file(README_FILENAME, _supported_summary(supported)) - _update_supported_file(SUPPORTED_FILENAME, _supported_detail(supported)) - - -def _update_supported_file(filename, supported_text): - with open(filename) as f: - contents = f.readlines() - - start_index = end_index = None - for index, line in enumerate(contents): - if line == "\n": - start_index = index + 1 - if line == "\n": - end_index = index - - new_contents = contents[:start_index] - end_contents = contents[end_index:] - new_contents.append("\n") - new_contents.append(supported_text) - new_contents.append("\n") - new_contents.extend(end_contents) - - with open(filename, "w") as f: - new_contents = "".join(new_contents) - f.write(new_contents) - - -def _supported_summary(supported): - return _supported_text( - supported, "### Supported $brand devices\n\n$types\n", "- **$type_**: $models\n" - ) - - -def _supported_detail(supported): - return _supported_text( - supported, - "## $brand devices\n\n$types\n", - "### $type_\n\n$models\n", - "- **$model$auth_flag**\n$versions", - " - Hardware $hw$region Firmare $fw$auth_flag\n", - ) - - -def _supported_text( - supported, brand_template, types_template, model_template="", version_template="" -): - brandt = Template(brand_template) - typest = Template(types_template) - modelt = Template(model_template) - versst = Template(version_template) - brands = "" - version: SupportedVersion - for brand, types in supported.items(): - brand_text = brand.capitalize() + ("\*" if brand == "tapo" else "") - types_text = "" - for type_, models in types.items(): - models_list = [] - models_text = "" - for model, versions in sorted(models.items()): - auth_count = 0 - versions_text = "" - for version in versions: - region_text = f" ({version.region})" if version.region else "" - auth_count += 1 if version.auth else 0 - vauth_flag = "\*" if version.auth else "" - vauth_flag = "" if brand == "tapo" else vauth_flag - if version_template: - versions_text += versst.substitute( - hw=version.hw, - fw=version.fw, - region=region_text, - auth_flag=vauth_flag, - ) - auth_flag = ( - "\*" - if auth_count == len(versions) - else "\*\*" - if auth_count > 0 - else "" - ) - auth_flag = "" if brand == "tapo" else auth_flag - if model_template: - models_text += modelt.substitute( - model=model, versions=versions_text, auth_flag=auth_flag - ) - else: - models_list.append(f"{model}{auth_flag}") - models_text = models_text if models_text else ", ".join(models_list) - types_text += typest.substitute(type_=type_, models=models_text) - brands += brandt.substitute(brand=brand_text, types=types_text) - return brands - - -def _get_smart_supported(supported): - smart_files = [f for f in Path(SMART_FOLDER).glob("*.json")] - for smart_file in smart_files: - with open(smart_file) as f: - fixture_data = json.load(f) - - model, _, region = fixture_data["discovery_result"]["device_model"].partition( - "(" - ) - # P100 doesn't have region HW - region = region.replace(")", "") if region else "" - device_type = fixture_data["discovery_result"]["device_type"] - _protocol, devicetype = device_type.split(".") - brand, type_ = devicetype[:4].lower(), devicetype[4:] - if brand not in ["kasa", "tapo"]: - click.echo( - click.style( - f"FAIL {smart_file} does not have a " - + f"supported device_type {device_type}", - fg="red", - ) - ) - continue - components = [ - component["id"] - for component in fixture_data["component_nego"]["component_list"] - ] - if type_ == "BULB": - supported_type = LIGHT_STRIPS if "light_strip" in components else BULBS - elif type_ == "PLUG": - supported_type = POWER_STRIPS if "child_device" in components else PLUGS - elif type_ == "SWITCH": - supported_type = WALL_SWITCHES - elif type_ == "HUB": - supported_type = HUBS - else: - click.echo( - click.style( - f"FAIL {smart_file} does not have a " - + f"supported device_type {device_type}", - fg="red", - ) - ) - continue - - hw_version = fixture_data["get_device_info"]["hw_ver"] - fw_version = fixture_data["get_device_info"]["fw_ver"] - fw_version = fw_version.split(" ", maxsplit=1)[0] - - stype = supported[brand].setdefault(supported_type, {}) - smodel = stype.setdefault(model, []) - smodel.append( - SupportedVersion(region=region, hw=hw_version, fw=fw_version, auth=True) - ) - - -def _get_iot_supported(supported): - iot_files = [f for f in Path(IOT_FOLDER).glob("*.json")] - for iot_file in iot_files: - with open(iot_file) as f: - fixture_data = json.load(f) - sysinfo = fixture_data["system"]["get_sysinfo"] - model, _, region = sysinfo["model"][:-1].partition("(") - auth = "discovery_result" in fixture_data - type_ = sysinfo.get("type", sysinfo.get("mic_type")) - if type_ == "IOT.SMARTBULB": - supported_type = LIGHT_STRIPS if "length" in sysinfo else BULBS - else: - if "children" in sysinfo: - supported_type = POWER_STRIPS - else: - if "dev_name" not in sysinfo: - click.echo( - click.style(f"FAIL {iot_file} does not have dev_name", fg="red") - ) - continue - if "light" in sysinfo["dev_name"].lower(): - supported_type = WALL_SWITCHES - else: - supported_type = PLUGS - stype = supported["kasa"].setdefault(supported_type, {}) - smodel = stype.setdefault(model, []) - fw = sysinfo["sw_ver"].split(" ", maxsplit=1)[0] - smodel.append( - SupportedVersion(region=region, hw=sysinfo["hw_ver"], fw=fw, auth=auth) - ) - - if __name__ == "__main__": cli() diff --git a/devtools/generate_supported.py b/devtools/generate_supported.py new file mode 100644 index 000000000..4bdb5af2c --- /dev/null +++ b/devtools/generate_supported.py @@ -0,0 +1,236 @@ +"""Script that checks supported devices and updates README.md and SUPPORTED.md.""" +import json +import sys +from pathlib import Path +from string import Template +from typing import NamedTuple + + +class SupportedVersion(NamedTuple): + """Supported version.""" + + region: str + hw: str + fw: str + auth: bool + + +SUPPORTED_FILENAME = "SUPPORTED.md" +README_FILENAME = "README.md" + +PLUGS = "Plugs" +POWER_STRIPS = "Power Strips" +WALL_SWITCHES = "Wall Switches" +BULBS = "Bulbs" +LIGHT_STRIPS = "Light Strips" +HUBS = "Hubs" + + +IOT_FOLDER = "kasa/tests/fixtures/" +SMART_FOLDER = "kasa/tests/fixtures/smart/" + + +def generate_supported(args): + """Generate the SUPPORTED.md from the fixtures.""" + print("Generating supported devices") + + supported = {"kasa": {}, "tapo": {}} + + _get_iot_supported(supported) + _get_smart_supported(supported) + + readme_updated = _update_supported_file( + README_FILENAME, _supported_summary(supported) + ) + supported_updated = _update_supported_file( + SUPPORTED_FILENAME, _supported_detail(supported) + ) + if not readme_updated and not supported_updated: + print("Supported devices unchanged.") + + +def _update_supported_file(filename, supported_text) -> bool: + with open(filename) as f: + contents = f.readlines() + + start_index = end_index = None + for index, line in enumerate(contents): + if line == "\n": + start_index = index + 1 + if line == "\n": + end_index = index + + current_text = "".join(contents[start_index:end_index]) + if current_text != supported_text: + print( + f"{filename} has been modified with updated " + + "supported devices, add file to commit." + ) + new_contents = contents[:start_index] + end_contents = contents[end_index:] + new_contents.append(supported_text) + new_contents.extend(end_contents) + + with open(filename, "w") as f: + new_contents_text = "".join(new_contents) + f.write(new_contents_text) + return True + return False + + +def _supported_summary(supported): + return _supported_text( + supported, + "### Supported $brand$auth devices\n\n$types\n", + "- **$type_**: $models\n", + ) + + +def _supported_detail(supported): + return _supported_text( + supported, + "## $brand devices\n\n$types\n", + "### $type_\n\n$models\n", + "- **$model**\n$versions", + " - Hardware: $hw$region / Firmware: $fw$auth_flag\n", + ) + + +def _supported_text( + supported, brand_template, types_template, model_template="", version_template="" +): + brandt = Template(brand_template) + typest = Template(types_template) + modelt = Template(model_template) + versst = Template(version_template) + brands = "" + version: SupportedVersion + for brand, types in supported.items(): + brand_text = brand.capitalize() + brand_auth = "\*" if brand == "tapo" else "" + types_text = "" + for type_, models in types.items(): + models_list = [] + models_text = "" + for model, versions in sorted(models.items()): + auth_count = 0 + versions_text = "" + for version in versions: + region_text = f" ({version.region})" if version.region else "" + auth_count += 1 if version.auth else 0 + vauth_flag = "\*" if version.auth else "" + vauth_flag = "" if brand == "tapo" else vauth_flag + if version_template: + versions_text += versst.substitute( + hw=version.hw, + fw=version.fw, + region=region_text, + auth_flag=vauth_flag, + ) + auth_flag = ( + "\*" + if auth_count == len(versions) + else "\*\*" + if auth_count > 0 + else "" + ) + auth_flag = "" if brand == "tapo" else auth_flag + if model_template: + models_text += modelt.substitute( + model=model, versions=versions_text, auth_flag=auth_flag + ) + else: + models_list.append(f"{model}{auth_flag}") + models_text = models_text if models_text else ", ".join(models_list) + types_text += typest.substitute(type_=type_, models=models_text) + brands += brandt.substitute(brand=brand_text, types=types_text, auth=brand_auth) + return brands + + +def _get_smart_supported(supported): + smart_files = [f for f in Path(SMART_FOLDER).glob("*.json")] + for smart_file in smart_files: + with open(smart_file) as f: + fixture_data = json.load(f) + + model, _, region = fixture_data["discovery_result"]["device_model"].partition( + "(" + ) + # P100 doesn't have region HW + region = region.replace(")", "") if region else "" + device_type = fixture_data["discovery_result"]["device_type"] + _protocol, devicetype = device_type.split(".") + brand, type_ = devicetype[:4].lower(), devicetype[4:] + if brand not in ["kasa", "tapo"]: + print( + f"FAIL {smart_file} does not have a " + + f"supported device_type {device_type}" + ) + continue + components = [ + component["id"] + for component in fixture_data["component_nego"]["component_list"] + ] + if type_ == "BULB": + supported_type = LIGHT_STRIPS if "light_strip" in components else BULBS + elif type_ == "PLUG": + supported_type = POWER_STRIPS if "child_device" in components else PLUGS + elif type_ == "SWITCH": + supported_type = WALL_SWITCHES + elif type_ == "HUB": + supported_type = HUBS + else: + print( + f"FAIL {smart_file} does not have a " + + f"supported device_type {device_type}" + ) + continue + + hw_version = fixture_data["get_device_info"]["hw_ver"] + fw_version = fixture_data["get_device_info"]["fw_ver"] + fw_version = fw_version.split(" ", maxsplit=1)[0] + + stype = supported[brand].setdefault(supported_type, {}) + smodel = stype.setdefault(model, []) + smodel.append( + SupportedVersion(region=region, hw=hw_version, fw=fw_version, auth=True) + ) + + +def _get_iot_supported(supported): + iot_files = [f for f in Path(IOT_FOLDER).glob("*.json")] + for iot_file in iot_files: + with open(iot_file) as f: + fixture_data = json.load(f) + sysinfo = fixture_data["system"]["get_sysinfo"] + model, _, region = sysinfo["model"][:-1].partition("(") + auth = "discovery_result" in fixture_data + type_ = sysinfo.get("type", sysinfo.get("mic_type")) + if type_ == "IOT.SMARTBULB": + supported_type = LIGHT_STRIPS if "length" in sysinfo else BULBS + else: + if "children" in sysinfo: + supported_type = POWER_STRIPS + else: + if "dev_name" not in sysinfo: + print(f"FAIL {iot_file} does not have dev_name") + continue + if "light" in sysinfo["dev_name"].lower(): + supported_type = WALL_SWITCHES + else: + supported_type = PLUGS + stype = supported["kasa"].setdefault(supported_type, {}) + smodel = stype.setdefault(model, []) + fw = sysinfo["sw_ver"].split(" ", maxsplit=1)[0] + smodel.append( + SupportedVersion(region=region, hw=sysinfo["hw_ver"], fw=fw, auth=auth) + ) + + +def main(): + """Entry point to module.""" + generate_supported(sys.argv[1:]) + + +if __name__ == "__main__": + generate_supported(sys.argv[1:]) diff --git a/docs/source/SUPPORTED.md b/docs/source/SUPPORTED.md index 65ad23a22..3ebfbeb29 100644 --- a/docs/source/SUPPORTED.md +++ b/docs/source/SUPPORTED.md @@ -1,5 +1,3 @@ -# Supported devices - ```{include} ../../SUPPORTED.md :relative-docs: doc/source ``` diff --git a/poetry.lock b/poetry.lock index 6195a6c52..eafa0b29c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1726,20 +1726,22 @@ test = ["cython", "html5lib", "pytest (>=4.6)", "typed_ast"] [[package]] name = "sphinx-rtd-theme" -version = "0.5.1" +version = "2.0.0" description = "Read the Docs theme for Sphinx" optional = true -python-versions = "*" +python-versions = ">=3.6" files = [ - {file = "sphinx_rtd_theme-0.5.1-py2.py3-none-any.whl", hash = "sha256:fa6bebd5ab9a73da8e102509a86f3fcc36dec04a0b52ea80e5a033b2aba00113"}, - {file = "sphinx_rtd_theme-0.5.1.tar.gz", hash = "sha256:eda689eda0c7301a80cf122dad28b1861e5605cbf455558f3775e1e8200e83a5"}, + {file = "sphinx_rtd_theme-2.0.0-py2.py3-none-any.whl", hash = "sha256:ec93d0856dc280cf3aee9a4c9807c60e027c7f7b461b77aeffed682e68f0e586"}, + {file = "sphinx_rtd_theme-2.0.0.tar.gz", hash = "sha256:bd5d7b80622406762073a04ef8fadc5f9151261563d47027de09910ce03afe6b"}, ] [package.dependencies] -sphinx = "*" +docutils = "<0.21" +sphinx = ">=5,<8" +sphinxcontrib-jquery = ">=4,<5" [package.extras] -dev = ["bump2version", "sphinxcontrib-httpdomain", "transifex-client"] +dev = ["bump2version", "sphinxcontrib-httpdomain", "transifex-client", "wheel"] [[package]] name = "sphinxcontrib-applehelp" @@ -1786,6 +1788,20 @@ files = [ lint = ["docutils-stubs", "flake8", "mypy"] test = ["html5lib", "pytest"] +[[package]] +name = "sphinxcontrib-jquery" +version = "4.1" +description = "Extension to include jQuery on newer Sphinx releases" +optional = true +python-versions = ">=2.7" +files = [ + {file = "sphinxcontrib-jquery-4.1.tar.gz", hash = "sha256:1620739f04e36a2c779f1a131a2dfd49b2fd07351bf1968ced074365933abc7a"}, + {file = "sphinxcontrib_jquery-4.1-py2.py3-none-any.whl", hash = "sha256:f936030d7d0147dd026a4f2b5a57343d233f1fc7b363f68b3d4f1cb0993878ae"}, +] + +[package.dependencies] +Sphinx = ">=1.8" + [[package]] name = "sphinxcontrib-jsmath" version = "1.0.1" @@ -2130,4 +2146,4 @@ speedups = ["kasa-crypt", "orjson"] [metadata] lock-version = "2.0" python-versions = "^3.8" -content-hash = "aadbdc97219e5282f614f834c1318bbf8430fe769030f0a262e1922c5d7523b8" +content-hash = "fecc8870f967cc6da9d6e1fde0e9a9acd261d28c4ba57476250d17234dc2c876" diff --git a/pyproject.toml b/pyproject.toml index a35f4b90c..c80635407 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,6 +19,7 @@ include = [ [tool.poetry.scripts] kasa = "kasa.cli:cli" +generate-supported = "devtools.generate_supported:main" [tool.poetry.dependencies] python = "^3.8" @@ -35,7 +36,7 @@ kasa-crypt = { "version" = ">=0.2.0", optional = true } # required only for docs sphinx = { version = "^5", optional = true } -sphinx_rtd_theme = { version = "^0", optional = true } +sphinx_rtd_theme = { version = "^2", optional = true } sphinxcontrib-programoutput = { version = "^0", optional = true } myst-parser = { version = "*", optional = true } docutils = { version = ">=0.17", optional = true } From 4093283c53da4b635d6aeeb259dd4aba0b7330eb Mon Sep 17 00:00:00 2001 From: sdb9696 Date: Wed, 28 Feb 2024 10:59:21 +0000 Subject: [PATCH 07/23] Move device type logic into kasa --- SUPPORTED.md | 1 + devtools/generate_supported.py | 64 +++++++++------------------------- kasa/device_factory.py | 44 ++++++++++++++++++++++- kasa/device_type.py | 11 ++++++ 4 files changed, 72 insertions(+), 48 deletions(-) diff --git a/SUPPORTED.md b/SUPPORTED.md index 67cdb69e4..10a928479 100644 --- a/SUPPORTED.md +++ b/SUPPORTED.md @@ -181,6 +181,7 @@ All Tapo devices require authentication - Hardware: 1.0.0 / Firmware: 1.1.3 - **P110** - Hardware: 1.0 (EU) / Firmware: 1.2.3 + - Hardware: 1.0 (EU) / Firmware: 1.0.7 - Hardware: 1.0 (UK) / Firmware: 1.3.0 - **P125M** - Hardware: 1.0 (US) / Firmware: 1.1.0 diff --git a/devtools/generate_supported.py b/devtools/generate_supported.py index 4bdb5af2c..d5a4e3f08 100644 --- a/devtools/generate_supported.py +++ b/devtools/generate_supported.py @@ -5,6 +5,11 @@ from string import Template from typing import NamedTuple +from kasa.device_factory import ( + get_device_supported_type_from_components, + get_device_supported_type_from_sysinfo, +) + class SupportedVersion(NamedTuple): """Supported version.""" @@ -18,14 +23,6 @@ class SupportedVersion(NamedTuple): SUPPORTED_FILENAME = "SUPPORTED.md" README_FILENAME = "README.md" -PLUGS = "Plugs" -POWER_STRIPS = "Power Strips" -WALL_SWITCHES = "Wall Switches" -BULBS = "Bulbs" -LIGHT_STRIPS = "Light Strips" -HUBS = "Hubs" - - IOT_FOLDER = "kasa/tests/fixtures/" SMART_FOLDER = "kasa/tests/fixtures/smart/" @@ -107,7 +104,7 @@ def _supported_text( version: SupportedVersion for brand, types in supported.items(): brand_text = brand.capitalize() - brand_auth = "\*" if brand == "tapo" else "" + brand_auth = r"\*" if brand == "tapo" else "" types_text = "" for type_, models in types.items(): models_list = [] @@ -118,7 +115,7 @@ def _supported_text( for version in versions: region_text = f" ({version.region})" if version.region else "" auth_count += 1 if version.auth else 0 - vauth_flag = "\*" if version.auth else "" + vauth_flag = r"\*" if version.auth else "" vauth_flag = "" if brand == "tapo" else vauth_flag if version_template: versions_text += versst.substitute( @@ -128,9 +125,9 @@ def _supported_text( auth_flag=vauth_flag, ) auth_flag = ( - "\*" + r"\*" if auth_count == len(versions) - else "\*\*" + else r"\*\*" if auth_count > 0 else "" ) @@ -160,31 +157,15 @@ def _get_smart_supported(supported): region = region.replace(")", "") if region else "" device_type = fixture_data["discovery_result"]["device_type"] _protocol, devicetype = device_type.split(".") - brand, type_ = devicetype[:4].lower(), devicetype[4:] - if brand not in ["kasa", "tapo"]: - print( - f"FAIL {smart_file} does not have a " - + f"supported device_type {device_type}" - ) - continue + brand = devicetype[:4].lower() components = [ component["id"] for component in fixture_data["component_nego"]["component_list"] ] - if type_ == "BULB": - supported_type = LIGHT_STRIPS if "light_strip" in components else BULBS - elif type_ == "PLUG": - supported_type = POWER_STRIPS if "child_device" in components else PLUGS - elif type_ == "SWITCH": - supported_type = WALL_SWITCHES - elif type_ == "HUB": - supported_type = HUBS - else: - print( - f"FAIL {smart_file} does not have a " - + f"supported device_type {device_type}" - ) - continue + supported_device_type = get_device_supported_type_from_components( + components, device_type + ) + supported_type = supported_device_type.value hw_version = fixture_data["get_device_info"]["hw_ver"] fw_version = fixture_data["get_device_info"]["fw_ver"] @@ -203,22 +184,11 @@ def _get_iot_supported(supported): with open(iot_file) as f: fixture_data = json.load(f) sysinfo = fixture_data["system"]["get_sysinfo"] + supported_device_type = get_device_supported_type_from_sysinfo(sysinfo) + supported_type = supported_device_type.value + model, _, region = sysinfo["model"][:-1].partition("(") auth = "discovery_result" in fixture_data - type_ = sysinfo.get("type", sysinfo.get("mic_type")) - if type_ == "IOT.SMARTBULB": - supported_type = LIGHT_STRIPS if "length" in sysinfo else BULBS - else: - if "children" in sysinfo: - supported_type = POWER_STRIPS - else: - if "dev_name" not in sysinfo: - print(f"FAIL {iot_file} does not have dev_name") - continue - if "light" in sysinfo["dev_name"].lower(): - supported_type = WALL_SWITCHES - else: - supported_type = PLUGS stype = supported["kasa"].setdefault(supported_type, {}) smodel = stype.setdefault(model, []) fw = sysinfo["sw_ver"].split(" ", maxsplit=1)[0] diff --git a/kasa/device_factory.py b/kasa/device_factory.py index 2e8ba0c98..08605dbda 100755 --- a/kasa/device_factory.py +++ b/kasa/device_factory.py @@ -1,10 +1,11 @@ """Device creation via DeviceConfig.""" import logging import time -from typing import Any, Dict, Optional, Tuple, Type +from typing import Any, Dict, List, Optional, Tuple, Type from .aestransport import AesTransport from .device import Device +from .device_type import SupportedDeviceType from .deviceconfig import DeviceConfig from .exceptions import KasaException, UnsupportedDeviceError from .iot import IotBulb, IotDevice, IotDimmer, IotLightStrip, IotPlug, IotStrip @@ -170,3 +171,44 @@ def get_protocol( protocol_transport_key ) # type: ignore return protocol_class(transport=transport_class(config=config)) + + +def get_device_supported_type_from_sysinfo( + sysinfo: Dict[str, Any] +) -> SupportedDeviceType: + """Find type to be displayed as a supported device category.""" + type_ = sysinfo.get("type", sysinfo.get("mic_type")) + if type_ == "IOT.SMARTBULB": + if "length" in sysinfo: + return SupportedDeviceType.LightStrips + return SupportedDeviceType.Bulbs + if "children" in sysinfo: + return SupportedDeviceType.PowerStrips + if "dev_name" not in sysinfo: + raise KasaException("No 'dev_name' in sysinfo") + if "light" in sysinfo["dev_name"].lower(): + return SupportedDeviceType.WallSwitches + return SupportedDeviceType.Plugs + + +def get_device_supported_type_from_components( + components: List[str], device_type: str +) -> SupportedDeviceType: + """Find type to be displayed as a supported device category.""" + protocol, devicetype = device_type.split(".") + brand, type_ = devicetype[:4], devicetype[4:] + if brand not in ["KASA", "TAPO"] or protocol not in ["SMART", "IOT"]: + raise KasaException(f"Unknown device type {device_type}") + if type_ == "BULB": + if "light_strip" in components: + return SupportedDeviceType.LightStrips + return SupportedDeviceType.Bulbs + if type_ == "PLUG": + if "child_device" in components: + return SupportedDeviceType.PowerStrips + return SupportedDeviceType.Plugs + if type_ == "SWITCH": + return SupportedDeviceType.WallSwitches + if type_ == "HUB": + return SupportedDeviceType.Hubs + raise KasaException(f"Unknown device type {device_type}") diff --git a/kasa/device_type.py b/kasa/device_type.py index a44efffa8..e26c2e2b7 100755 --- a/kasa/device_type.py +++ b/kasa/device_type.py @@ -25,3 +25,14 @@ def from_value(name: str) -> "DeviceType": if device_type.value == name: return device_type return DeviceType.Unknown + + +class SupportedDeviceType(Enum): + """Supported device type enum.""" + + Plugs = "Plugs" + PowerStrips = "Power Strips" + WallSwitches = "Wall Switches" + Bulbs = "Bulbs" + LightStrips = "Light Strips" + Hubs = "Hubs" From d865d9522a80021652accc190e1405274f51667a Mon Sep 17 00:00:00 2001 From: sdb9696 Date: Wed, 28 Feb 2024 11:03:41 +0000 Subject: [PATCH 08/23] Fix --- .github/workflows/ci.yml | 6 +++--- .pre-commit-config.yaml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6389dee8c..110d452ed 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,6 +26,9 @@ jobs: run: | python -m pip install --upgrade pip poetry poetry install + - name: "Check supported device md files are up to date" + run: | + poetry run pre-commit run generate-supported --all-files - name: "Linting and code formating (ruff)" run: | poetry run pre-commit run ruff --all-files @@ -47,9 +50,6 @@ jobs: - name: "Run check-ast" run: | poetry run pre-commit run check-ast --all-files - - name: "Check supported device md files are up to date" - run: | - poetry run pre-commit run generate-supported --all-files tests: diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6085bc7dd..badc0532a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -35,6 +35,6 @@ repos: description: This hook generates the supported device sections of README.md and SUPPORTED.md entry: generate-supported language: system # Required or pre-commit creates a new venv - verbose: true # Show output on success + verbose: false # Show output on success types: [json] pass_filenames: false # passing filenames causes the hook to run in batches against all-files From 5d8bcff8946145a925384b45c5cebb0e318d57aa Mon Sep 17 00:00:00 2001 From: sdb9696 Date: Wed, 28 Feb 2024 11:14:18 +0000 Subject: [PATCH 09/23] Fix --- devtools/generate_supported.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/devtools/generate_supported.py b/devtools/generate_supported.py index d5a4e3f08..cae7fe063 100644 --- a/devtools/generate_supported.py +++ b/devtools/generate_supported.py @@ -63,6 +63,10 @@ def _update_supported_file(filename, supported_text) -> bool: f"{filename} has been modified with updated " + "supported devices, add file to commit." ) + print("##CURRENT##") + print(current_text) + print("##NEW##") + print(supported_text) new_contents = contents[:start_index] end_contents = contents[end_index:] new_contents.append(supported_text) From 0b89b35800bfbe3456e5bf376ac5937557faf66f Mon Sep 17 00:00:00 2001 From: sdb9696 Date: Wed, 28 Feb 2024 11:21:23 +0000 Subject: [PATCH 10/23] Fix --- README.md | 4 +- SUPPORTED.md | 98 +++++++++++++++++----------------- devtools/generate_supported.py | 2 +- 3 files changed, 52 insertions(+), 52 deletions(-) diff --git a/README.md b/README.md index dea220cfe..75425dba0 100644 --- a/README.md +++ b/README.md @@ -226,16 +226,16 @@ The following devices have been tested and confirmed as working. If your device ### Supported Kasa devices -- **Plugs**: EP10, EP25\*, ES20M, HS100\*\*, HS103, HS105, HS110, HS220, KP100, KP105, KP115, KP125, KP125M\*, KP401, KP405, KS220M, KS230 - **Bulbs**: KL110, KL120, KL125, KL130, KL135, KL50, KL60, LB100, LB110, LB120, LB130 - **Light Strips**: KL400L5, KL420L5, KL430 +- **Plugs**: EP10, EP25\*, ES20M, HS100\*\*, HS103, HS105, HS110, HS220, KP100, KP105, KP115, KP125, KP125M\*, KP401, KP405, KS220M, KS230 - **Power Strips**: EP40, HS107, HS300, KP200, KP303, KP400 - **Wall Switches**: HS200, HS210, KS200M, KS205\*, KS225\* ### Supported Tapo\* devices -- **Hubs**: H100 - **Bulbs**: L510B, L510E, L530E +- **Hubs**: H100 - **Light Strips**: L900-10, L900-5, L920-5, L930-5 - **Plugs**: P100, P110, P125M, P135, TP15 - **Power Strips**: P300, TP25 diff --git a/SUPPORTED.md b/SUPPORTED.md index 10a928479..60f73c14c 100644 --- a/SUPPORTED.md +++ b/SUPPORTED.md @@ -9,6 +9,49 @@ All Tapo devices require authentication ## Kasa devices +### Bulbs + +- **KL110** + - Hardware: 1.0 (US) / Firmware: 1.8.11 +- **KL120** + - Hardware: 1.0 (US) / Firmware: 1.8.6 +- **KL125** + - Hardware: 4.0 (US) / Firmware: 1.0.5 + - Hardware: 2.0 (US) / Firmware: 1.0.7 + - Hardware: 1.20 (US) / Firmware: 1.0.5 +- **KL130** + - Hardware: 1.0 (US) / Firmware: 1.8.11 + - Hardware: 1.0 (EU) / Firmware: 1.8.8 +- **KL135** + - Hardware: 1.0 (US) / Firmware: 1.0.6 +- **KL50** + - Hardware: 1.0 (US) / Firmware: 1.1.13 +- **KL60** + - Hardware: 1.0 (US) / Firmware: 1.1.13 + - Hardware: 1.0 (UN) / Firmware: 1.1.4 +- **LB100** + - Hardware: 1.0 (US) / Firmware: 1.4.3 +- **LB110** + - Hardware: 1.0 (US) / Firmware: 1.8.11 +- **LB120** + - Hardware: 1.0 (US) / Firmware: 1.1.0 +- **LB130** + - Hardware: 1.0 (US) / Firmware: 1.6.0 + +### Light Strips + +- **KL400L5** + - Hardware: 1.0 (US) / Firmware: 1.0.8 + - Hardware: 1.0 (US) / Firmware: 1.0.5 +- **KL420L5** + - Hardware: 1.0 (US) / Firmware: 1.0.2 +- **KL430** + - Hardware: 2.0 (US) / Firmware: 1.0.11 + - Hardware: 2.0 (US) / Firmware: 1.0.8 + - Hardware: 1.0 (US) / Firmware: 1.0.10 + - Hardware: 2.0 (US) / Firmware: 1.0.9 + - Hardware: 2.0 (UN) / Firmware: 1.0.8 + ### Plugs - **EP10** @@ -61,49 +104,6 @@ All Tapo devices require authentication - **KS230** - Hardware: 1.0 (US) / Firmware: 1.0.14 -### Bulbs - -- **KL110** - - Hardware: 1.0 (US) / Firmware: 1.8.11 -- **KL120** - - Hardware: 1.0 (US) / Firmware: 1.8.6 -- **KL125** - - Hardware: 4.0 (US) / Firmware: 1.0.5 - - Hardware: 2.0 (US) / Firmware: 1.0.7 - - Hardware: 1.20 (US) / Firmware: 1.0.5 -- **KL130** - - Hardware: 1.0 (US) / Firmware: 1.8.11 - - Hardware: 1.0 (EU) / Firmware: 1.8.8 -- **KL135** - - Hardware: 1.0 (US) / Firmware: 1.0.6 -- **KL50** - - Hardware: 1.0 (US) / Firmware: 1.1.13 -- **KL60** - - Hardware: 1.0 (US) / Firmware: 1.1.13 - - Hardware: 1.0 (UN) / Firmware: 1.1.4 -- **LB100** - - Hardware: 1.0 (US) / Firmware: 1.4.3 -- **LB110** - - Hardware: 1.0 (US) / Firmware: 1.8.11 -- **LB120** - - Hardware: 1.0 (US) / Firmware: 1.1.0 -- **LB130** - - Hardware: 1.0 (US) / Firmware: 1.6.0 - -### Light Strips - -- **KL400L5** - - Hardware: 1.0 (US) / Firmware: 1.0.8 - - Hardware: 1.0 (US) / Firmware: 1.0.5 -- **KL420L5** - - Hardware: 1.0 (US) / Firmware: 1.0.2 -- **KL430** - - Hardware: 2.0 (US) / Firmware: 1.0.11 - - Hardware: 2.0 (US) / Firmware: 1.0.8 - - Hardware: 1.0 (US) / Firmware: 1.0.10 - - Hardware: 2.0 (US) / Firmware: 1.0.9 - - Hardware: 2.0 (UN) / Firmware: 1.0.8 - ### Power Strips - **EP40** @@ -141,12 +141,6 @@ All Tapo devices require authentication ## Tapo devices -### Hubs - -- **H100** - - Hardware: 1.0 (EU) / Firmware: 1.5.5 - - Hardware: 1.0 (EU) / Firmware: 1.2.3 - ### Bulbs - **L510B** @@ -160,6 +154,12 @@ All Tapo devices require authentication - Hardware: 3.0 (EU) / Firmware: 1.0.6 - Hardware: 2.0 (US) / Firmware: 1.1.0 +### Hubs + +- **H100** + - Hardware: 1.0 (EU) / Firmware: 1.5.5 + - Hardware: 1.0 (EU) / Firmware: 1.2.3 + ### Light Strips - **L900-10** diff --git a/devtools/generate_supported.py b/devtools/generate_supported.py index cae7fe063..79200b1b0 100644 --- a/devtools/generate_supported.py +++ b/devtools/generate_supported.py @@ -110,7 +110,7 @@ def _supported_text( brand_text = brand.capitalize() brand_auth = r"\*" if brand == "tapo" else "" types_text = "" - for type_, models in types.items(): + for type_, models in sorted(types.items()): models_list = [] models_text = "" for model, versions in sorted(models.items()): From 3949412bc4178596577bbba59bfec517249f5cad Mon Sep 17 00:00:00 2001 From: sdb9696 Date: Wed, 28 Feb 2024 11:24:26 +0000 Subject: [PATCH 11/23] Fix --- SUPPORTED.md | 54 +++++++++++++++++----------------- devtools/generate_supported.py | 2 +- 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/SUPPORTED.md b/SUPPORTED.md index 60f73c14c..f77e16084 100644 --- a/SUPPORTED.md +++ b/SUPPORTED.md @@ -16,19 +16,19 @@ All Tapo devices require authentication - **KL120** - Hardware: 1.0 (US) / Firmware: 1.8.6 - **KL125** - - Hardware: 4.0 (US) / Firmware: 1.0.5 - - Hardware: 2.0 (US) / Firmware: 1.0.7 - Hardware: 1.20 (US) / Firmware: 1.0.5 + - Hardware: 2.0 (US) / Firmware: 1.0.7 + - Hardware: 4.0 (US) / Firmware: 1.0.5 - **KL130** - - Hardware: 1.0 (US) / Firmware: 1.8.11 - Hardware: 1.0 (EU) / Firmware: 1.8.8 + - Hardware: 1.0 (US) / Firmware: 1.8.11 - **KL135** - Hardware: 1.0 (US) / Firmware: 1.0.6 - **KL50** - Hardware: 1.0 (US) / Firmware: 1.1.13 - **KL60** - - Hardware: 1.0 (US) / Firmware: 1.1.13 - Hardware: 1.0 (UN) / Firmware: 1.1.4 + - Hardware: 1.0 (US) / Firmware: 1.1.13 - **LB100** - Hardware: 1.0 (US) / Firmware: 1.4.3 - **LB110** @@ -41,16 +41,16 @@ All Tapo devices require authentication ### Light Strips - **KL400L5** - - Hardware: 1.0 (US) / Firmware: 1.0.8 - Hardware: 1.0 (US) / Firmware: 1.0.5 + - Hardware: 1.0 (US) / Firmware: 1.0.8 - **KL420L5** - Hardware: 1.0 (US) / Firmware: 1.0.2 - **KL430** + - Hardware: 2.0 (UN) / Firmware: 1.0.8 + - Hardware: 1.0 (US) / Firmware: 1.0.10 - Hardware: 2.0 (US) / Firmware: 1.0.11 - Hardware: 2.0 (US) / Firmware: 1.0.8 - - Hardware: 1.0 (US) / Firmware: 1.0.10 - Hardware: 2.0 (US) / Firmware: 1.0.9 - - Hardware: 2.0 (UN) / Firmware: 1.0.8 ### Plugs @@ -62,35 +62,35 @@ All Tapo devices require authentication - **ES20M** - Hardware: 1.0 (US) / Firmware: 1.0.8 - **HS100** - - Hardware: 2.0 (US) / Firmware: 1.5.6 + - Hardware: 1.0 (UK) / Firmware: 1.2.6 - Hardware: 4.1 (UK) / Firmware: 1.1.0\* - Hardware: 1.0 (US) / Firmware: 1.2.5 - - Hardware: 1.0 (UK) / Firmware: 1.2.6 + - Hardware: 2.0 (US) / Firmware: 1.5.6 - **HS103** - Hardware: 1.0 (US) / Firmware: 1.5.7 - - Hardware: 2.1 (US) / Firmware: 1.1.4 - Hardware: 2.1 (US) / Firmware: 1.1.2 + - Hardware: 2.1 (US) / Firmware: 1.1.4 - **HS105** - Hardware: 1.0 (US) / Firmware: 1.2.9 - Hardware: 1.0 (US) / Firmware: 1.5.6 - **HS110** + - Hardware: 1.0 (EU) / Firmware: 1.2.5 - Hardware: 2.0 (EU) / Firmware: 1.5.2 - Hardware: 4.0 (EU) / Firmware: 1.0.4 - - Hardware: 1.0 (EU) / Firmware: 1.2.5 - Hardware: 1.0 (US) / Firmware: 1.0.8 - **HS220** - Hardware: 1.0 (US) / Firmware: 1.5.7 - - Hardware: 2.0 (US) / Firmware: 1.0.3 - Hardware: 1.0 (US) / Firmware: 1.5.7 + - Hardware: 2.0 (US) / Firmware: 1.0.3 - **KP100** - Hardware: 3.0 (US) / Firmware: 1.0.1 - **KP105** - - Hardware: 1.0 (UK) / Firmware: 1.0.7 - Hardware: 1.0 (UK) / Firmware: 1.0.5 + - Hardware: 1.0 (UK) / Firmware: 1.0.7 - **KP115** - - Hardware: 1.0 (US) / Firmware: 1.0.21 - - Hardware: 1.0 (US) / Firmware: 1.0.17 - Hardware: 1.0 (EU) / Firmware: 1.0.16 + - Hardware: 1.0 (US) / Firmware: 1.0.17 + - Hardware: 1.0 (US) / Firmware: 1.0.21 - **KP125** - Hardware: 1.0 (US) / Firmware: 1.0.6 - **KP125M** @@ -112,13 +112,13 @@ All Tapo devices require authentication - Hardware: 1.0 (US) / Firmware: 1.0.8 - **HS300** - Hardware: 1.0 (US) / Firmware: 1.0.10 - - Hardware: 2.0 (US) / Firmware: 1.0.3 - Hardware: 2.0 (US) / Firmware: 1.0.12 + - Hardware: 2.0 (US) / Firmware: 1.0.3 - **KP200** - Hardware: 3.0 (US) / Firmware: 1.0.3 - **KP303** - - Hardware: 2.0 (US) / Firmware: 1.0.3 - Hardware: 1.0 (UK) / Firmware: 1.0.3 + - Hardware: 2.0 (US) / Firmware: 1.0.3 - **KP400** - Hardware: 1.0 (US) / Firmware: 1.0.10 - Hardware: 2.0 (US) / Firmware: 1.0.6 @@ -126,9 +126,9 @@ All Tapo devices require authentication ### Wall Switches - **HS200** - - Hardware: 5.0 (US) / Firmware: 1.0.2 - - Hardware: 2.0 (US) / Firmware: 1.5.7 - Hardware: 1.0 (US) / Firmware: 1.1.0 + - Hardware: 2.0 (US) / Firmware: 1.5.7 + - Hardware: 5.0 (US) / Firmware: 1.0.2 - **HS210** - Hardware: 1.0 (US) / Firmware: 1.5.8 - **KS200M** @@ -149,39 +149,39 @@ All Tapo devices require authentication - Hardware: 3.0 (US) / Firmware: 1.0.5 - Hardware: 3.0 (US) / Firmware: 1.1.2 - **L530E** + - Hardware: 3.0 (EU) / Firmware: 1.0.6 - Hardware: 3.0 (EU) / Firmware: 1.1.0 - Hardware: 3.0 (EU) / Firmware: 1.1.6 - - Hardware: 3.0 (EU) / Firmware: 1.0.6 - Hardware: 2.0 (US) / Firmware: 1.1.0 ### Hubs - **H100** - - Hardware: 1.0 (EU) / Firmware: 1.5.5 - Hardware: 1.0 (EU) / Firmware: 1.2.3 + - Hardware: 1.0 (EU) / Firmware: 1.5.5 ### Light Strips - **L900-10** - - Hardware: 1.0 (US) / Firmware: 1.0.11 - Hardware: 1.0 (EU) / Firmware: 1.0.17 + - Hardware: 1.0 (US) / Firmware: 1.0.11 - **L900-5** - - Hardware: 1.0 (EU) / Firmware: 1.1.0 - Hardware: 1.0 (EU) / Firmware: 1.0.17 + - Hardware: 1.0 (EU) / Firmware: 1.1.0 - **L920-5** - - Hardware: 1.0 (US) / Firmware: 1.1.3 - Hardware: 1.0 (US) / Firmware: 1.1.0 + - Hardware: 1.0 (US) / Firmware: 1.1.3 - **L930-5** - Hardware: 1.0 (US) / Firmware: 1.1.2 ### Plugs - **P100** - - Hardware: 1.0.0 / Firmware: 1.3.7 - Hardware: 1.0.0 / Firmware: 1.1.3 + - Hardware: 1.0.0 / Firmware: 1.3.7 - **P110** - - Hardware: 1.0 (EU) / Firmware: 1.2.3 - Hardware: 1.0 (EU) / Firmware: 1.0.7 + - Hardware: 1.0 (EU) / Firmware: 1.2.3 - Hardware: 1.0 (UK) / Firmware: 1.3.0 - **P125M** - Hardware: 1.0 (US) / Firmware: 1.1.0 @@ -193,8 +193,8 @@ All Tapo devices require authentication ### Power Strips - **P300** - - Hardware: 1.0 (EU) / Firmware: 1.0.7 - Hardware: 1.0 (EU) / Firmware: 1.0.13 + - Hardware: 1.0 (EU) / Firmware: 1.0.7 - **TP25** - Hardware: 1.0 (US) / Firmware: 1.0.2 diff --git a/devtools/generate_supported.py b/devtools/generate_supported.py index 79200b1b0..bdeb56fe1 100644 --- a/devtools/generate_supported.py +++ b/devtools/generate_supported.py @@ -116,7 +116,7 @@ def _supported_text( for model, versions in sorted(models.items()): auth_count = 0 versions_text = "" - for version in versions: + for version in sorted(versions): region_text = f" ({version.region})" if version.region else "" auth_count += 1 if version.auth else 0 vauth_flag = r"\*" if version.auth else "" From bb13939f765546c596b78ffb9ff67a8deacc02bb Mon Sep 17 00:00:00 2001 From: sdb9696 Date: Wed, 28 Feb 2024 16:25:25 +0000 Subject: [PATCH 12/23] Consolidate device type logic --- .github/workflows/ci.yml | 2 +- .pre-commit-config.yaml | 14 ++- README.md | 12 +-- SUPPORTED.md | 168 +++++++++++++++--------------- devtools/generate_supported.py | 35 ++++--- kasa/cli.py | 12 ++- kasa/device.py | 5 + kasa/device_factory.py | 74 +++++++------ kasa/device_type.py | 15 +++ kasa/iot/__init__.py | 3 +- kasa/iot/iotplug.py | 16 ++- kasa/smart/smartdevice.py | 47 +++++---- kasa/tests/device_fixtures.py | 44 ++++++-- kasa/tests/test_device_factory.py | 22 +++- kasa/tests/test_discovery.py | 12 ++- kasa/tests/test_plug.py | 6 +- 16 files changed, 303 insertions(+), 184 deletions(-) mode change 100644 => 100755 devtools/generate_supported.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 110d452ed..d68148a30 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,7 +28,7 @@ jobs: poetry install - name: "Check supported device md files are up to date" run: | - poetry run pre-commit run generate-supported --all-files + poetry run pre-commit run generate-supported --hook-stage manual --all-files - name: "Linting and code formating (ruff)" run: | poetry run pre-commit run ruff --all-files diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index badc0532a..3a44e2d7e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -33,8 +33,18 @@ repos: - id: generate-supported name: Generate supported devices description: This hook generates the supported device sections of README.md and SUPPORTED.md - entry: generate-supported + entry: devtools/generate_supported.py language: system # Required or pre-commit creates a new venv - verbose: false # Show output on success + verbose: true # Show output on success types: [json] pass_filenames: false # passing filenames causes the hook to run in batches against all-files + stages: ["pre-commit"] + - id: generate-supported + name: Generate supported devices + description: This hook generates the supported device sections of README.md and SUPPORTED.md + entry: generate-supported + language: system + types: [json] + args: [--print-diffs] + pass_filenames: false + stages: ["manual"] diff --git a/README.md b/README.md index 75425dba0..7ffda4c73 100644 --- a/README.md +++ b/README.md @@ -226,20 +226,20 @@ The following devices have been tested and confirmed as working. If your device ### Supported Kasa devices +- **Plugs**: EP10, EP25\*, HS100\*\*, HS103, HS105, HS110, KP100, KP105, KP115, KP125, KP125M\*, KP401 +- **Power Strips**: EP40, HS107, HS300, KP200, KP303, KP400 +- **Wall Switches**: ES20M, HS200, HS210, HS220, KP405, KS200M, KS205\*, KS220M, KS225\*, KS230 - **Bulbs**: KL110, KL120, KL125, KL130, KL135, KL50, KL60, LB100, LB110, LB120, LB130 - **Light Strips**: KL400L5, KL420L5, KL430 -- **Plugs**: EP10, EP25\*, ES20M, HS100\*\*, HS103, HS105, HS110, HS220, KP100, KP105, KP115, KP125, KP125M\*, KP401, KP405, KS220M, KS230 -- **Power Strips**: EP40, HS107, HS300, KP200, KP303, KP400 -- **Wall Switches**: HS200, HS210, KS200M, KS205\*, KS225\* ### Supported Tapo\* devices -- **Bulbs**: L510B, L510E, L530E -- **Hubs**: H100 -- **Light Strips**: L900-10, L900-5, L920-5, L930-5 - **Plugs**: P100, P110, P125M, P135, TP15 - **Power Strips**: P300, TP25 - **Wall Switches**: S500D, S505 +- **Bulbs**: L510B, L510E, L530E +- **Light Strips**: L900-10, L900-5, L920-5, L930-5 +- **Hubs**: H100 *  Model requires authentication
diff --git a/SUPPORTED.md b/SUPPORTED.md index f77e16084..58625d09e 100644 --- a/SUPPORTED.md +++ b/SUPPORTED.md @@ -9,49 +9,6 @@ All Tapo devices require authentication ## Kasa devices -### Bulbs - -- **KL110** - - Hardware: 1.0 (US) / Firmware: 1.8.11 -- **KL120** - - Hardware: 1.0 (US) / Firmware: 1.8.6 -- **KL125** - - Hardware: 1.20 (US) / Firmware: 1.0.5 - - Hardware: 2.0 (US) / Firmware: 1.0.7 - - Hardware: 4.0 (US) / Firmware: 1.0.5 -- **KL130** - - Hardware: 1.0 (EU) / Firmware: 1.8.8 - - Hardware: 1.0 (US) / Firmware: 1.8.11 -- **KL135** - - Hardware: 1.0 (US) / Firmware: 1.0.6 -- **KL50** - - Hardware: 1.0 (US) / Firmware: 1.1.13 -- **KL60** - - Hardware: 1.0 (UN) / Firmware: 1.1.4 - - Hardware: 1.0 (US) / Firmware: 1.1.13 -- **LB100** - - Hardware: 1.0 (US) / Firmware: 1.4.3 -- **LB110** - - Hardware: 1.0 (US) / Firmware: 1.8.11 -- **LB120** - - Hardware: 1.0 (US) / Firmware: 1.1.0 -- **LB130** - - Hardware: 1.0 (US) / Firmware: 1.6.0 - -### Light Strips - -- **KL400L5** - - Hardware: 1.0 (US) / Firmware: 1.0.5 - - Hardware: 1.0 (US) / Firmware: 1.0.8 -- **KL420L5** - - Hardware: 1.0 (US) / Firmware: 1.0.2 -- **KL430** - - Hardware: 2.0 (UN) / Firmware: 1.0.8 - - Hardware: 1.0 (US) / Firmware: 1.0.10 - - Hardware: 2.0 (US) / Firmware: 1.0.11 - - Hardware: 2.0 (US) / Firmware: 1.0.8 - - Hardware: 2.0 (US) / Firmware: 1.0.9 - ### Plugs - **EP10** @@ -59,8 +16,6 @@ All Tapo devices require authentication - **EP25** - Hardware: 2.6 (US) / Firmware: 1.0.1\* - Hardware: 2.6 (US) / Firmware: 1.0.2\* -- **ES20M** - - Hardware: 1.0 (US) / Firmware: 1.0.8 - **HS100** - Hardware: 1.0 (UK) / Firmware: 1.2.6 - Hardware: 4.1 (UK) / Firmware: 1.1.0\* @@ -78,10 +33,6 @@ All Tapo devices require authentication - Hardware: 2.0 (EU) / Firmware: 1.5.2 - Hardware: 4.0 (EU) / Firmware: 1.0.4 - Hardware: 1.0 (US) / Firmware: 1.0.8 -- **HS220** - - Hardware: 1.0 (US) / Firmware: 1.5.7 - - Hardware: 1.0 (US) / Firmware: 1.5.7 - - Hardware: 2.0 (US) / Firmware: 1.0.3 - **KP100** - Hardware: 3.0 (US) / Firmware: 1.0.1 - **KP105** @@ -97,12 +48,6 @@ All Tapo devices require authentication - Hardware: 1.0 (US) / Firmware: 1.1.3\* - **KP401** - Hardware: 1.0 (US) / Firmware: 1.0.0 -- **KP405** - - Hardware: 1.0 (US) / Firmware: 1.0.5 -- **KS220M** - - Hardware: 1.0 (US) / Firmware: 1.0.4 -- **KS230** - - Hardware: 1.0 (US) / Firmware: 1.0.14 ### Power Strips @@ -125,54 +70,76 @@ All Tapo devices require authentication ### Wall Switches +- **ES20M** + - Hardware: 1.0 (US) / Firmware: 1.0.8 - **HS200** - Hardware: 1.0 (US) / Firmware: 1.1.0 - Hardware: 2.0 (US) / Firmware: 1.5.7 - Hardware: 5.0 (US) / Firmware: 1.0.2 - **HS210** - Hardware: 1.0 (US) / Firmware: 1.5.8 +- **HS220** + - Hardware: 1.0 (US) / Firmware: 1.5.7 + - Hardware: 1.0 (US) / Firmware: 1.5.7 + - Hardware: 2.0 (US) / Firmware: 1.0.3 +- **KP405** + - Hardware: 1.0 (US) / Firmware: 1.0.5 - **KS200M** - Hardware: 1.0 (US) / Firmware: 1.0.8 - **KS205** - Hardware: 1.0 (US) / Firmware: 1.0.2\* +- **KS220M** + - Hardware: 1.0 (US) / Firmware: 1.0.4 - **KS225** - Hardware: 1.0 (US) / Firmware: 1.0.2\* - - -## Tapo devices +- **KS230** + - Hardware: 1.0 (US) / Firmware: 1.0.14 ### Bulbs -- **L510B** - - Hardware: 3.0 (EU) / Firmware: 1.0.5 -- **L510E** - - Hardware: 3.0 (US) / Firmware: 1.0.5 - - Hardware: 3.0 (US) / Firmware: 1.1.2 -- **L530E** - - Hardware: 3.0 (EU) / Firmware: 1.0.6 - - Hardware: 3.0 (EU) / Firmware: 1.1.0 - - Hardware: 3.0 (EU) / Firmware: 1.1.6 - - Hardware: 2.0 (US) / Firmware: 1.1.0 +- **KL110** + - Hardware: 1.0 (US) / Firmware: 1.8.11 +- **KL120** + - Hardware: 1.0 (US) / Firmware: 1.8.6 +- **KL125** + - Hardware: 1.20 (US) / Firmware: 1.0.5 + - Hardware: 2.0 (US) / Firmware: 1.0.7 + - Hardware: 4.0 (US) / Firmware: 1.0.5 +- **KL130** + - Hardware: 1.0 (EU) / Firmware: 1.8.8 + - Hardware: 1.0 (US) / Firmware: 1.8.11 +- **KL135** + - Hardware: 1.0 (US) / Firmware: 1.0.6 +- **KL50** + - Hardware: 1.0 (US) / Firmware: 1.1.13 +- **KL60** + - Hardware: 1.0 (UN) / Firmware: 1.1.4 + - Hardware: 1.0 (US) / Firmware: 1.1.13 +- **LB100** + - Hardware: 1.0 (US) / Firmware: 1.4.3 +- **LB110** + - Hardware: 1.0 (US) / Firmware: 1.8.11 +- **LB120** + - Hardware: 1.0 (US) / Firmware: 1.1.0 +- **LB130** + - Hardware: 1.0 (US) / Firmware: 1.6.0 -### Hubs +### Light Strips -- **H100** - - Hardware: 1.0 (EU) / Firmware: 1.2.3 - - Hardware: 1.0 (EU) / Firmware: 1.5.5 +- **KL400L5** + - Hardware: 1.0 (US) / Firmware: 1.0.5 + - Hardware: 1.0 (US) / Firmware: 1.0.8 +- **KL420L5** + - Hardware: 1.0 (US) / Firmware: 1.0.2 +- **KL430** + - Hardware: 2.0 (UN) / Firmware: 1.0.8 + - Hardware: 1.0 (US) / Firmware: 1.0.10 + - Hardware: 2.0 (US) / Firmware: 1.0.11 + - Hardware: 2.0 (US) / Firmware: 1.0.8 + - Hardware: 2.0 (US) / Firmware: 1.0.9 -### Light Strips -- **L900-10** - - Hardware: 1.0 (EU) / Firmware: 1.0.17 - - Hardware: 1.0 (US) / Firmware: 1.0.11 -- **L900-5** - - Hardware: 1.0 (EU) / Firmware: 1.0.17 - - Hardware: 1.0 (EU) / Firmware: 1.1.0 -- **L920-5** - - Hardware: 1.0 (US) / Firmware: 1.1.0 - - Hardware: 1.0 (US) / Firmware: 1.1.3 -- **L930-5** - - Hardware: 1.0 (US) / Firmware: 1.1.2 +## Tapo devices ### Plugs @@ -205,5 +172,38 @@ All Tapo devices require authentication - **S505** - Hardware: 1.0 (US) / Firmware: 1.0.2 +### Bulbs + +- **L510B** + - Hardware: 3.0 (EU) / Firmware: 1.0.5 +- **L510E** + - Hardware: 3.0 (US) / Firmware: 1.0.5 + - Hardware: 3.0 (US) / Firmware: 1.1.2 +- **L530E** + - Hardware: 3.0 (EU) / Firmware: 1.0.6 + - Hardware: 3.0 (EU) / Firmware: 1.1.0 + - Hardware: 3.0 (EU) / Firmware: 1.1.6 + - Hardware: 2.0 (US) / Firmware: 1.1.0 + +### Light Strips + +- **L900-10** + - Hardware: 1.0 (EU) / Firmware: 1.0.17 + - Hardware: 1.0 (US) / Firmware: 1.0.11 +- **L900-5** + - Hardware: 1.0 (EU) / Firmware: 1.0.17 + - Hardware: 1.0 (EU) / Firmware: 1.1.0 +- **L920-5** + - Hardware: 1.0 (US) / Firmware: 1.1.0 + - Hardware: 1.0 (US) / Firmware: 1.1.3 +- **L930-5** + - Hardware: 1.0 (US) / Firmware: 1.1.2 + +### Hubs + +- **H100** + - Hardware: 1.0 (EU) / Firmware: 1.2.3 + - Hardware: 1.0 (EU) / Firmware: 1.5.5 + diff --git a/devtools/generate_supported.py b/devtools/generate_supported.py old mode 100644 new mode 100755 index bdeb56fe1..ca91120b3 --- a/devtools/generate_supported.py +++ b/devtools/generate_supported.py @@ -1,3 +1,4 @@ +#!/usr/bin/env python """Script that checks supported devices and updates README.md and SUPPORTED.md.""" import json import sys @@ -9,6 +10,7 @@ get_device_supported_type_from_components, get_device_supported_type_from_sysinfo, ) +from kasa.device_type import SupportedDeviceType class SupportedVersion(NamedTuple): @@ -29,6 +31,7 @@ class SupportedVersion(NamedTuple): def generate_supported(args): """Generate the SUPPORTED.md from the fixtures.""" + print_diffs = "--print-diffs" in args print("Generating supported devices") supported = {"kasa": {}, "tapo": {}} @@ -37,16 +40,16 @@ def generate_supported(args): _get_smart_supported(supported) readme_updated = _update_supported_file( - README_FILENAME, _supported_summary(supported) + README_FILENAME, _supported_summary(supported), print_diffs ) supported_updated = _update_supported_file( - SUPPORTED_FILENAME, _supported_detail(supported) + SUPPORTED_FILENAME, _supported_detail(supported), print_diffs ) if not readme_updated and not supported_updated: print("Supported devices unchanged.") -def _update_supported_file(filename, supported_text) -> bool: +def _update_supported_file(filename, supported_text, print_diffs) -> bool: with open(filename) as f: contents = f.readlines() @@ -63,10 +66,12 @@ def _update_supported_file(filename, supported_text) -> bool: f"{filename} has been modified with updated " + "supported devices, add file to commit." ) - print("##CURRENT##") - print(current_text) - print("##NEW##") - print(supported_text) + if print_diffs: + print("##CURRENT##") + print(current_text) + print("##NEW##") + print(supported_text) + new_contents = contents[:start_index] end_contents = contents[end_index:] new_contents.append(supported_text) @@ -110,7 +115,11 @@ def _supported_text( brand_text = brand.capitalize() brand_auth = r"\*" if brand == "tapo" else "" types_text = "" - for type_, models in sorted(types.items()): + for supported_type, models in sorted( + # Sort by device type order in the enum + types.items(), + key=lambda st: list(SupportedDeviceType).index(st[0]), + ): models_list = [] models_text = "" for model, versions in sorted(models.items()): @@ -143,7 +152,9 @@ def _supported_text( else: models_list.append(f"{model}{auth_flag}") models_text = models_text if models_text else ", ".join(models_list) - types_text += typest.substitute(type_=type_, models=models_text) + types_text += typest.substitute( + type_=supported_type.value, models=models_text + ) brands += brandt.substitute(brand=brand_text, types=types_text, auth=brand_auth) return brands @@ -166,10 +177,9 @@ def _get_smart_supported(supported): component["id"] for component in fixture_data["component_nego"]["component_list"] ] - supported_device_type = get_device_supported_type_from_components( + supported_type = get_device_supported_type_from_components( components, device_type ) - supported_type = supported_device_type.value hw_version = fixture_data["get_device_info"]["hw_ver"] fw_version = fixture_data["get_device_info"]["fw_ver"] @@ -188,8 +198,7 @@ def _get_iot_supported(supported): with open(iot_file) as f: fixture_data = json.load(f) sysinfo = fixture_data["system"]["get_sysinfo"] - supported_device_type = get_device_supported_type_from_sysinfo(sysinfo) - supported_type = supported_device_type.value + supported_type = get_device_supported_type_from_sysinfo(fixture_data) model, _, region = sysinfo["model"][:-1].partition("(") auth = "discovery_result" in fixture_data diff --git a/kasa/cli.py b/kasa/cli.py index 83980ec20..178680a7c 100755 --- a/kasa/cli.py +++ b/kasa/cli.py @@ -26,7 +26,15 @@ UnsupportedDeviceError, ) from kasa.discover import DiscoveryResult -from kasa.iot import IotBulb, IotDevice, IotDimmer, IotLightStrip, IotPlug, IotStrip +from kasa.iot import ( + IotBulb, + IotDevice, + IotDimmer, + IotLightStrip, + IotPlug, + IotStrip, + IotSwitch, +) from kasa.smart import SmartBulb, SmartDevice try: @@ -63,11 +71,13 @@ def wrapper(message=None, *args, **kwargs): TYPE_TO_CLASS = { "plug": IotPlug, + "switch": IotSwitch, "bulb": IotBulb, "dimmer": IotDimmer, "strip": IotStrip, "lightstrip": IotLightStrip, "iot.plug": IotPlug, + "iot.switch": IotSwitch, "iot.bulb": IotBulb, "iot.dimmer": IotDimmer, "iot.strip": IotStrip, diff --git a/kasa/device.py b/kasa/device.py index 72967ee2d..d56505935 100644 --- a/kasa/device.py +++ b/kasa/device.py @@ -212,6 +212,11 @@ def is_plug(self) -> bool: """Return True if the device is a plug.""" return self.device_type == DeviceType.Plug + @property + def is_switch(self) -> bool: + """Return True if the device is a plug.""" + return self.device_type == DeviceType.Switch + @property def is_strip(self) -> bool: """Return True if the device is a strip.""" diff --git a/kasa/device_factory.py b/kasa/device_factory.py index 08605dbda..20efaf3c5 100755 --- a/kasa/device_factory.py +++ b/kasa/device_factory.py @@ -5,10 +5,18 @@ from .aestransport import AesTransport from .device import Device -from .device_type import SupportedDeviceType +from .device_type import DEVICE_TYPE_TO_SUPPORTED, DeviceType, SupportedDeviceType from .deviceconfig import DeviceConfig from .exceptions import KasaException, UnsupportedDeviceError -from .iot import IotBulb, IotDevice, IotDimmer, IotLightStrip, IotPlug, IotStrip +from .iot import ( + IotBulb, + IotDevice, + IotDimmer, + IotLightStrip, + IotPlug, + IotStrip, + IotSwitch, +) from .iotprotocol import IotProtocol from .klaptransport import KlapTransport, KlapTransportV2 from .protocol import ( @@ -16,6 +24,7 @@ BaseTransport, ) from .smart import SmartBulb, SmartDevice +from .smart.smartdevice import _get_device_type_from_components from .smartprotocol import SmartProtocol from .xortransport import XorTransport @@ -106,7 +115,7 @@ def _perf_log(has_params, perf_type): ) -def get_device_class_from_sys_info(info: Dict[str, Any]) -> Type[IotDevice]: +def _get_device_type_from_sys_info(info: Dict[str, Any]) -> DeviceType: """Find SmartDevice subclass for device described by passed data.""" if "system" not in info or "get_sysinfo" not in info["system"]: raise KasaException("No 'system' or 'get_sysinfo' in response") @@ -117,22 +126,36 @@ def get_device_class_from_sys_info(info: Dict[str, Any]) -> Type[IotDevice]: raise KasaException("Unable to find the device type field!") if "dev_name" in sysinfo and "Dimmer" in sysinfo["dev_name"]: - return IotDimmer + return DeviceType.Dimmer if "smartplug" in type_.lower(): if "children" in sysinfo: - return IotStrip - - return IotPlug + return DeviceType.Strip + if (dev_name := sysinfo.get("dev_name")) and "light" in dev_name.lower(): + return DeviceType.Switch + return DeviceType.Plug if "smartbulb" in type_.lower(): if "length" in sysinfo: # strips have length - return IotLightStrip + return DeviceType.LightStrip - return IotBulb + return DeviceType.Bulb raise UnsupportedDeviceError("Unknown device type: %s" % type_) +def get_device_class_from_sys_info(sysinfo: Dict[str, Any]) -> Type[IotDevice]: + """Find SmartDevice subclass for device described by passed data.""" + TYPE_TO_CLASS = { + DeviceType.Bulb: IotBulb, + DeviceType.Plug: IotPlug, + DeviceType.Dimmer: IotDimmer, + DeviceType.Strip: IotStrip, + DeviceType.Switch: IotSwitch, + DeviceType.LightStrip: IotLightStrip, + } + return TYPE_TO_CLASS[_get_device_type_from_sys_info(sysinfo)] + + def get_device_class_from_family(device_type: str) -> Optional[Type[Device]]: """Return the device class from the type name.""" supported_device_types: Dict[str, Type[Device]] = { @@ -177,38 +200,13 @@ def get_device_supported_type_from_sysinfo( sysinfo: Dict[str, Any] ) -> SupportedDeviceType: """Find type to be displayed as a supported device category.""" - type_ = sysinfo.get("type", sysinfo.get("mic_type")) - if type_ == "IOT.SMARTBULB": - if "length" in sysinfo: - return SupportedDeviceType.LightStrips - return SupportedDeviceType.Bulbs - if "children" in sysinfo: - return SupportedDeviceType.PowerStrips - if "dev_name" not in sysinfo: - raise KasaException("No 'dev_name' in sysinfo") - if "light" in sysinfo["dev_name"].lower(): - return SupportedDeviceType.WallSwitches - return SupportedDeviceType.Plugs + dt = _get_device_type_from_sys_info(sysinfo) + return DEVICE_TYPE_TO_SUPPORTED[dt] def get_device_supported_type_from_components( components: List[str], device_type: str ) -> SupportedDeviceType: """Find type to be displayed as a supported device category.""" - protocol, devicetype = device_type.split(".") - brand, type_ = devicetype[:4], devicetype[4:] - if brand not in ["KASA", "TAPO"] or protocol not in ["SMART", "IOT"]: - raise KasaException(f"Unknown device type {device_type}") - if type_ == "BULB": - if "light_strip" in components: - return SupportedDeviceType.LightStrips - return SupportedDeviceType.Bulbs - if type_ == "PLUG": - if "child_device" in components: - return SupportedDeviceType.PowerStrips - return SupportedDeviceType.Plugs - if type_ == "SWITCH": - return SupportedDeviceType.WallSwitches - if type_ == "HUB": - return SupportedDeviceType.Hubs - raise KasaException(f"Unknown device type {device_type}") + dt = _get_device_type_from_components(components, device_type) + return DEVICE_TYPE_TO_SUPPORTED[dt] diff --git a/kasa/device_type.py b/kasa/device_type.py index e26c2e2b7..d85d0437d 100755 --- a/kasa/device_type.py +++ b/kasa/device_type.py @@ -11,6 +11,7 @@ class DeviceType(Enum): Plug = "plug" Bulb = "bulb" Strip = "strip" + Switch = "switch" StripSocket = "stripsocket" Dimmer = "dimmer" LightStrip = "lightstrip" @@ -36,3 +37,17 @@ class SupportedDeviceType(Enum): Bulbs = "Bulbs" LightStrips = "Light Strips" Hubs = "Hubs" + Sensors = "Sensors" + + +DEVICE_TYPE_TO_SUPPORTED = { + DeviceType.Plug: SupportedDeviceType.Plugs, + DeviceType.Bulb: SupportedDeviceType.Bulbs, + DeviceType.Strip: SupportedDeviceType.PowerStrips, + DeviceType.StripSocket: SupportedDeviceType.PowerStrips, + DeviceType.Dimmer: SupportedDeviceType.WallSwitches, + DeviceType.Switch: SupportedDeviceType.WallSwitches, + DeviceType.LightStrip: SupportedDeviceType.LightStrips, + DeviceType.Sensor: SupportedDeviceType.Sensors, + DeviceType.Hub: SupportedDeviceType.Hubs, +} diff --git a/kasa/iot/__init__.py b/kasa/iot/__init__.py index 2ee03d694..16bc4bfc9 100644 --- a/kasa/iot/__init__.py +++ b/kasa/iot/__init__.py @@ -3,7 +3,7 @@ from .iotdevice import IotDevice from .iotdimmer import IotDimmer from .iotlightstrip import IotLightStrip -from .iotplug import IotPlug +from .iotplug import IotPlug, IotSwitch from .iotstrip import IotStrip __all__ = [ @@ -13,4 +13,5 @@ "IotStrip", "IotDimmer", "IotLightStrip", + "IotSwitch", ] diff --git a/kasa/iot/iotplug.py b/kasa/iot/iotplug.py index e408bb3ce..3e6030150 100644 --- a/kasa/iot/iotplug.py +++ b/kasa/iot/iotplug.py @@ -13,7 +13,7 @@ class IotPlug(IotDevice): - r"""Representation of a TP-Link Smart Switch. + r"""Representation of a TP-Link Smart Plug. To initialize, you have to await :func:`update()` at least once. This will allow accessing the properties using the exposed properties. @@ -101,3 +101,17 @@ async def set_led(self, state: bool): def state_information(self) -> Dict[str, Any]: """Return switch-specific state information.""" return {} + + +class IotSwitch(IotPlug): + """Representation of a TP-Link Smart Switch.""" + + def __init__( + self, + host: str, + *, + config: Optional[DeviceConfig] = None, + protocol: Optional[BaseProtocol] = None, + ) -> None: + super().__init__(host=host, config=config, protocol=protocol) + self._device_type = DeviceType.Switch diff --git a/kasa/smart/smartdevice.py b/kasa/smart/smartdevice.py index 66db2c58c..9f64a6ab0 100644 --- a/kasa/smart/smartdevice.py +++ b/kasa/smart/smartdevice.py @@ -63,12 +63,6 @@ async def _initialize_children(self): ) for child_info in children } - # TODO: This may not be the best approach, but it allows distinguishing - # between power strips and hubs for the time being. - if all(child.is_plug for child in self._children.values()): - self._device_type = DeviceType.Strip - else: - self._device_type = DeviceType.Hub @property def children(self) -> Sequence["SmartDevice"]: @@ -519,21 +513,30 @@ def device_type(self) -> DeviceType: if self._device_type is not DeviceType.Unknown: return self._device_type - if self.children: - if "SMART.TAPOHUB" in self.sys_info["type"]: - self._device_type = DeviceType.Hub - else: - self._device_type = DeviceType.Strip - elif "light_strip" in self._components: - self._device_type = DeviceType.LightStrip - elif "dimmer_calibration" in self._components: - self._device_type = DeviceType.Dimmer - elif "brightness" in self._components: - self._device_type = DeviceType.Bulb - elif "PLUG" in self.sys_info["type"]: - self._device_type = DeviceType.Plug - else: - _LOGGER.warning("Unknown device type, falling back to plug") - self._device_type = DeviceType.Plug + self._device_type = _get_device_type_from_components( + list(self._components.keys()), self._info["type"] + ) return self._device_type + + +def _get_device_type_from_components( + components: List[str], device_type: str +) -> DeviceType: + """Find type to be displayed as a supported device category.""" + if "HUB" in device_type: + return DeviceType.Hub + if "PLUG" in device_type: + if "child_device" in components: + return DeviceType.Strip + return DeviceType.Plug + if "light_strip" in components: + return DeviceType.LightStrip + if "dimmer_calibration" in components: + return DeviceType.Dimmer + if "brightness" in components: + return DeviceType.Bulb + if "SWITCH" in device_type: + return DeviceType.Switch + _LOGGER.warning("Unknown device type, falling back to plug") + return DeviceType.Plug diff --git a/kasa/tests/device_fixtures.py b/kasa/tests/device_fixtures.py index e4f513ffc..9c9826e18 100644 --- a/kasa/tests/device_fixtures.py +++ b/kasa/tests/device_fixtures.py @@ -7,7 +7,7 @@ Device, Discover, ) -from kasa.iot import IotBulb, IotDimmer, IotLightStrip, IotPlug, IotStrip +from kasa.iot import IotBulb, IotDimmer, IotLightStrip, IotPlug, IotStrip, IotSwitch from kasa.smart import SmartBulb, SmartDevice from .fakeprotocol_iot import FakeIotProtocol @@ -60,15 +60,12 @@ "HS103", "HS105", "HS110", - "HS200", - "HS210", "EP10", "KP100", "KP105", "KP115", "KP125", "KP401", - "KS200M", } # P135 supports dimming, but its not currently support # by the library @@ -77,15 +74,25 @@ "P110", "KP125M", "EP25", - "KS205", "P125M", - "S505", "TP15", } PLUGS = { *PLUGS_IOT, *PLUGS_SMART, } +SWITCHES_IOT = { + "HS200", + "HS210", + "KS200M", +} +SWITCHES_SMART = { + "KS205", + "KS225", + "S500D", + "S505", +} +SWITCHES = {*SWITCHES_IOT, *SWITCHES_SMART} STRIPS_IOT = {"HS107", "HS300", "KP303", "KP200", "KP400", "EP40"} STRIPS_SMART = {"P300", "TP25"} STRIPS = {*STRIPS_IOT, *STRIPS_SMART} @@ -105,12 +112,15 @@ DIMMABLE = {*BULBS, *DIMMERS} -ALL_DEVICES_IOT = BULBS_IOT.union(PLUGS_IOT).union(STRIPS_IOT).union(DIMMERS_IOT) +ALL_DEVICES_IOT = ( + BULBS_IOT.union(PLUGS_IOT).union(STRIPS_IOT).union(DIMMERS_IOT).union(SWITCHES_IOT) +) ALL_DEVICES_SMART = ( BULBS_SMART.union(PLUGS_SMART) .union(STRIPS_SMART) .union(DIMMERS_SMART) .union(HUBS_SMART) + .union(SWITCHES_SMART) ) ALL_DEVICES = ALL_DEVICES_IOT.union(ALL_DEVICES_SMART) @@ -160,7 +170,10 @@ def parametrize( ) bulb = parametrize("bulbs", model_filter=BULBS, protocol_filter={"SMART", "IOT"}) -plug = parametrize("plugs", model_filter=PLUGS, protocol_filter={"IOT"}) +plug = parametrize("plugs", model_filter=PLUGS, protocol_filter={"IOT", "SMART"}) +plug_iot = parametrize("plugs", model_filter=PLUGS, protocol_filter={"IOT"}) +switch = parametrize("plugs", model_filter=SWITCHES, protocol_filter={"IOT", "SMART"}) +switch_iot = parametrize("plugs", model_filter=SWITCHES, protocol_filter={"IOT"}) strip = parametrize("strips", model_filter=STRIPS, protocol_filter={"SMART", "IOT"}) dimmer = parametrize("dimmers", model_filter=DIMMERS, protocol_filter={"IOT"}) lightstrip = parametrize( @@ -239,8 +252,8 @@ def check_categories(): + strip.args[1] + plug.args[1] + bulb.args[1] + + switch.args[1] + lightstrip.args[1] - + plug_smart.args[1] + bulb_smart.args[1] + dimmers_smart.args[1] + hubs_smart.args[1] @@ -263,6 +276,9 @@ def device_for_fixture_name(model, protocol): for d in PLUGS_SMART: if d in model: return SmartDevice + for d in SWITCHES_SMART: + if d in model: + return SmartDevice for d in BULBS_SMART: if d in model: return SmartBulb @@ -283,6 +299,9 @@ def device_for_fixture_name(model, protocol): for d in PLUGS_IOT: if d in model: return IotPlug + for d in SWITCHES_IOT: + if d in model: + return IotSwitch # Light strips are recognized also as bulbs, so this has to go first for d in BULBS_IOT_LIGHT_STRIP: @@ -325,6 +344,13 @@ async def get_device_for_fixture(fixture_data: FixtureInfo): d.protocol = FakeSmartProtocol(fixture_data.data, fixture_data.name) else: d.protocol = FakeIotProtocol(fixture_data.data) + if "discovery_result" in fixture_data.data: + discovery_data = {"result": fixture_data.data["discovery_result"]} + else: + discovery_data = { + "system": {"get_sysinfo": fixture_data.data["system"]["get_sysinfo"]} + } + d.update_from_discover_info(discovery_data) await _update_and_close(d) return d diff --git a/kasa/tests/test_device_factory.py b/kasa/tests/test_device_factory.py index 1519ca5f2..a6a9c4139 100644 --- a/kasa/tests/test_device_factory.py +++ b/kasa/tests/test_device_factory.py @@ -10,7 +10,13 @@ Discover, KasaException, ) -from kasa.device_factory import connect, get_protocol +from kasa.device_factory import ( + connect, + get_device_supported_type_from_components, + get_device_supported_type_from_sysinfo, + get_protocol, +) +from kasa.device_type import DEVICE_TYPE_TO_SUPPORTED from kasa.deviceconfig import ( ConnectionType, DeviceConfig, @@ -18,6 +24,7 @@ EncryptType, ) from kasa.discover import DiscoveryResult +from kasa.smart.smartdevice import SmartDevice def _get_connection_type_device_class(discovery_info): @@ -146,3 +153,16 @@ async def test_connect_http_client(discovery_data, mocker): assert dev.protocol._transport._http_client.client == http_client await dev.disconnect() await http_client.close() + + +async def test_device_supported_types(dev: Device): + await dev.update() + if isinstance(dev, SmartDevice): + device_type = dev._discovery_info["result"]["device_type"] + res = get_device_supported_type_from_components( + dev._components.keys(), device_type + ) + else: + res = get_device_supported_type_from_sysinfo(dev._last_update) + + assert DEVICE_TYPE_TO_SUPPORTED[dev.device_type] == res diff --git a/kasa/tests/test_discovery.py b/kasa/tests/test_discovery.py index 897d91d81..8a3cd420b 100644 --- a/kasa/tests/test_discovery.py +++ b/kasa/tests/test_discovery.py @@ -29,8 +29,9 @@ dimmer, lightstrip, new_discovery, - plug, + plug_iot, strip_iot, + switch_iot, ) UNSUPPORTED = { @@ -55,7 +56,14 @@ } -@plug +@switch_iot +async def test_type_detection_switch(dev: Device): + d = Discover._get_device_class(dev._last_update)("localhost") + assert d.is_switch + assert d.device_type == DeviceType.Switch + + +@plug_iot async def test_type_detection_plug(dev: Device): d = Discover._get_device_class(dev._last_update)("localhost") assert d.is_plug diff --git a/kasa/tests/test_plug.py b/kasa/tests/test_plug.py index 64c420f9d..10dd39db2 100644 --- a/kasa/tests/test_plug.py +++ b/kasa/tests/test_plug.py @@ -1,6 +1,6 @@ from kasa import DeviceType -from .conftest import plug, plug_smart +from .conftest import plug_iot, plug_smart from .test_smartdevice import SYSINFO_SCHEMA # these schemas should go to the mainlib as @@ -8,7 +8,7 @@ # as well as to check that faked devices are operating properly. -@plug +@plug_iot async def test_plug_sysinfo(dev): assert dev.sys_info is not None SYSINFO_SCHEMA(dev.sys_info) @@ -19,7 +19,7 @@ async def test_plug_sysinfo(dev): assert dev.is_plug or dev.is_strip -@plug +@plug_iot async def test_led(dev): original = dev.led From 673f3c78edd0151604cedc0dd40d920ea9e12f7e Mon Sep 17 00:00:00 2001 From: sdb9696 Date: Wed, 28 Feb 2024 16:26:34 +0000 Subject: [PATCH 13/23] Fix --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 3a44e2d7e..66372916f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -42,7 +42,7 @@ repos: - id: generate-supported name: Generate supported devices description: This hook generates the supported device sections of README.md and SUPPORTED.md - entry: generate-supported + entry: devtools/generate_supported.py language: system types: [json] args: [--print-diffs] From b2da548dfe4248d6df34acd7218b7a47b00fa3e8 Mon Sep 17 00:00:00 2001 From: sdb9696 Date: Wed, 28 Feb 2024 16:41:27 +0000 Subject: [PATCH 14/23] Remove script from pyproject.toml --- pyproject.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index c80635407..f3fa470e2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,7 +19,6 @@ include = [ [tool.poetry.scripts] kasa = "kasa.cli:cli" -generate-supported = "devtools.generate_supported:main" [tool.poetry.dependencies] python = "^3.8" From e5cc5438486ab41d53ba7c7ce1b173f5c9d6daae Mon Sep 17 00:00:00 2001 From: sdb9696 Date: Wed, 28 Feb 2024 17:10:52 +0000 Subject: [PATCH 15/23] Move device type roll ups out of kasa --- devtools/generate_supported.py | 42 ++++++++++++++++++++++++------- kasa/device_factory.py | 21 ++-------------- kasa/tests/test_device_factory.py | 16 +++++------- 3 files changed, 41 insertions(+), 38 deletions(-) diff --git a/devtools/generate_supported.py b/devtools/generate_supported.py index ca91120b3..ed90a9814 100755 --- a/devtools/generate_supported.py +++ b/devtools/generate_supported.py @@ -2,15 +2,14 @@ """Script that checks supported devices and updates README.md and SUPPORTED.md.""" import json import sys +from enum import Enum from pathlib import Path from string import Template from typing import NamedTuple -from kasa.device_factory import ( - get_device_supported_type_from_components, - get_device_supported_type_from_sysinfo, -) -from kasa.device_type import SupportedDeviceType +from kasa.device_factory import _get_device_type_from_sys_info +from kasa.device_type import DeviceType +from kasa.smart.smartdevice import _get_device_type_from_components class SupportedVersion(NamedTuple): @@ -22,6 +21,31 @@ class SupportedVersion(NamedTuple): auth: bool +class SupportedDeviceType(Enum): + """Supported device type enum.""" + + Plugs = "Plugs" + PowerStrips = "Power Strips" + WallSwitches = "Wall Switches" + Bulbs = "Bulbs" + LightStrips = "Light Strips" + Hubs = "Hubs" + Sensors = "Sensors" + + +DEVICE_TYPE_TO_SUPPORTED = { + DeviceType.Plug: SupportedDeviceType.Plugs, + DeviceType.Bulb: SupportedDeviceType.Bulbs, + DeviceType.Strip: SupportedDeviceType.PowerStrips, + DeviceType.StripSocket: SupportedDeviceType.PowerStrips, + DeviceType.Dimmer: SupportedDeviceType.WallSwitches, + DeviceType.Switch: SupportedDeviceType.WallSwitches, + DeviceType.LightStrip: SupportedDeviceType.LightStrips, + DeviceType.Sensor: SupportedDeviceType.Sensors, + DeviceType.Hub: SupportedDeviceType.Hubs, +} + + SUPPORTED_FILENAME = "SUPPORTED.md" README_FILENAME = "README.md" @@ -177,9 +201,8 @@ def _get_smart_supported(supported): component["id"] for component in fixture_data["component_nego"]["component_list"] ] - supported_type = get_device_supported_type_from_components( - components, device_type - ) + dt = _get_device_type_from_components(components, device_type) + supported_type = DEVICE_TYPE_TO_SUPPORTED[dt] hw_version = fixture_data["get_device_info"]["hw_ver"] fw_version = fixture_data["get_device_info"]["fw_ver"] @@ -198,7 +221,8 @@ def _get_iot_supported(supported): with open(iot_file) as f: fixture_data = json.load(f) sysinfo = fixture_data["system"]["get_sysinfo"] - supported_type = get_device_supported_type_from_sysinfo(fixture_data) + dt = _get_device_type_from_sys_info(fixture_data) + supported_type = DEVICE_TYPE_TO_SUPPORTED[dt] model, _, region = sysinfo["model"][:-1].partition("(") auth = "discovery_result" in fixture_data diff --git a/kasa/device_factory.py b/kasa/device_factory.py index 20efaf3c5..f31dd8f80 100755 --- a/kasa/device_factory.py +++ b/kasa/device_factory.py @@ -1,11 +1,11 @@ """Device creation via DeviceConfig.""" import logging import time -from typing import Any, Dict, List, Optional, Tuple, Type +from typing import Any, Dict, Optional, Tuple, Type from .aestransport import AesTransport from .device import Device -from .device_type import DEVICE_TYPE_TO_SUPPORTED, DeviceType, SupportedDeviceType +from .device_type import DeviceType from .deviceconfig import DeviceConfig from .exceptions import KasaException, UnsupportedDeviceError from .iot import ( @@ -24,7 +24,6 @@ BaseTransport, ) from .smart import SmartBulb, SmartDevice -from .smart.smartdevice import _get_device_type_from_components from .smartprotocol import SmartProtocol from .xortransport import XorTransport @@ -194,19 +193,3 @@ def get_protocol( protocol_transport_key ) # type: ignore return protocol_class(transport=transport_class(config=config)) - - -def get_device_supported_type_from_sysinfo( - sysinfo: Dict[str, Any] -) -> SupportedDeviceType: - """Find type to be displayed as a supported device category.""" - dt = _get_device_type_from_sys_info(sysinfo) - return DEVICE_TYPE_TO_SUPPORTED[dt] - - -def get_device_supported_type_from_components( - components: List[str], device_type: str -) -> SupportedDeviceType: - """Find type to be displayed as a supported device category.""" - dt = _get_device_type_from_components(components, device_type) - return DEVICE_TYPE_TO_SUPPORTED[dt] diff --git a/kasa/tests/test_device_factory.py b/kasa/tests/test_device_factory.py index a6a9c4139..0bd9f833c 100644 --- a/kasa/tests/test_device_factory.py +++ b/kasa/tests/test_device_factory.py @@ -11,12 +11,10 @@ KasaException, ) from kasa.device_factory import ( + _get_device_type_from_sys_info, connect, - get_device_supported_type_from_components, - get_device_supported_type_from_sysinfo, get_protocol, ) -from kasa.device_type import DEVICE_TYPE_TO_SUPPORTED from kasa.deviceconfig import ( ConnectionType, DeviceConfig, @@ -24,7 +22,7 @@ EncryptType, ) from kasa.discover import DiscoveryResult -from kasa.smart.smartdevice import SmartDevice +from kasa.smart.smartdevice import SmartDevice, _get_device_type_from_components def _get_connection_type_device_class(discovery_info): @@ -155,14 +153,12 @@ async def test_connect_http_client(discovery_data, mocker): await http_client.close() -async def test_device_supported_types(dev: Device): +async def test_device_types(dev: Device): await dev.update() if isinstance(dev, SmartDevice): device_type = dev._discovery_info["result"]["device_type"] - res = get_device_supported_type_from_components( - dev._components.keys(), device_type - ) + res = _get_device_type_from_components(dev._components.keys(), device_type) else: - res = get_device_supported_type_from_sysinfo(dev._last_update) + res = _get_device_type_from_sys_info(dev._last_update) - assert DEVICE_TYPE_TO_SUPPORTED[dev.device_type] == res + assert dev.device_type == res From 89004424f4523c229fee6a42cca1d82dcfbc2826 Mon Sep 17 00:00:00 2001 From: sdb9696 Date: Wed, 28 Feb 2024 17:19:14 +0000 Subject: [PATCH 16/23] Missed DeviceType update --- kasa/device_type.py | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/kasa/device_type.py b/kasa/device_type.py index d85d0437d..c01c0904d 100755 --- a/kasa/device_type.py +++ b/kasa/device_type.py @@ -26,28 +26,3 @@ def from_value(name: str) -> "DeviceType": if device_type.value == name: return device_type return DeviceType.Unknown - - -class SupportedDeviceType(Enum): - """Supported device type enum.""" - - Plugs = "Plugs" - PowerStrips = "Power Strips" - WallSwitches = "Wall Switches" - Bulbs = "Bulbs" - LightStrips = "Light Strips" - Hubs = "Hubs" - Sensors = "Sensors" - - -DEVICE_TYPE_TO_SUPPORTED = { - DeviceType.Plug: SupportedDeviceType.Plugs, - DeviceType.Bulb: SupportedDeviceType.Bulbs, - DeviceType.Strip: SupportedDeviceType.PowerStrips, - DeviceType.StripSocket: SupportedDeviceType.PowerStrips, - DeviceType.Dimmer: SupportedDeviceType.WallSwitches, - DeviceType.Switch: SupportedDeviceType.WallSwitches, - DeviceType.LightStrip: SupportedDeviceType.LightStrips, - DeviceType.Sensor: SupportedDeviceType.Sensors, - DeviceType.Hub: SupportedDeviceType.Hubs, -} From 63e8fae69157418c2c66aea909930eeb809122d5 Mon Sep 17 00:00:00 2001 From: sdb9696 Date: Wed, 28 Feb 2024 17:31:57 +0000 Subject: [PATCH 17/23] Add info to the two hook definitions --- .pre-commit-config.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 66372916f..34e0e313c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -39,12 +39,12 @@ repos: types: [json] pass_filenames: false # passing filenames causes the hook to run in batches against all-files stages: ["pre-commit"] - - id: generate-supported - name: Generate supported devices + - id: generate-supported # This hook is tied to manual stage so will be used by the CI + name: Generate supported devices (print-diff) description: This hook generates the supported device sections of README.md and SUPPORTED.md entry: devtools/generate_supported.py language: system types: [json] - args: [--print-diffs] + args: [--print-diffs] # The CI will print diffs to explain failues. pass_filenames: false stages: ["manual"] From cd5eff524062af30ba9ad3514f3280398ed42656 Mon Sep 17 00:00:00 2001 From: sdb9696 Date: Wed, 28 Feb 2024 17:56:01 +0000 Subject: [PATCH 18/23] Detect if running in CI --- devtools/generate_supported.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/devtools/generate_supported.py b/devtools/generate_supported.py index ed90a9814..70a6224d3 100755 --- a/devtools/generate_supported.py +++ b/devtools/generate_supported.py @@ -1,6 +1,7 @@ #!/usr/bin/env python """Script that checks supported devices and updates README.md and SUPPORTED.md.""" import json +import os import sys from enum import Enum from pathlib import Path @@ -56,7 +57,11 @@ class SupportedDeviceType(Enum): def generate_supported(args): """Generate the SUPPORTED.md from the fixtures.""" print_diffs = "--print-diffs" in args + running_in_ci = "CI" in os.environ print("Generating supported devices") + if running_in_ci: + print_diffs = True + print("Detected running in CI") supported = {"kasa": {}, "tapo": {}} From 7e075a3019093374313cff963143b29416c1c612 Mon Sep 17 00:00:00 2001 From: sdb9696 Date: Wed, 28 Feb 2024 17:58:50 +0000 Subject: [PATCH 19/23] Fix --- .pre-commit-config.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 34e0e313c..a6d66ac44 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -45,6 +45,7 @@ repos: entry: devtools/generate_supported.py language: system types: [json] + verbose: true args: [--print-diffs] # The CI will print diffs to explain failues. pass_filenames: false stages: ["manual"] From 74158968c9e19d39c3073937a8e0535961d6ccfc Mon Sep 17 00:00:00 2001 From: sdb9696 Date: Wed, 28 Feb 2024 18:03:58 +0000 Subject: [PATCH 20/23] Remove extra hook --- .github/workflows/ci.yml | 2 +- .pre-commit-config.yaml | 11 ----------- 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d68148a30..110d452ed 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,7 +28,7 @@ jobs: poetry install - name: "Check supported device md files are up to date" run: | - poetry run pre-commit run generate-supported --hook-stage manual --all-files + poetry run pre-commit run generate-supported --all-files - name: "Linting and code formating (ruff)" run: | poetry run pre-commit run ruff --all-files diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a6d66ac44..4d1f0a4c6 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -38,14 +38,3 @@ repos: verbose: true # Show output on success types: [json] pass_filenames: false # passing filenames causes the hook to run in batches against all-files - stages: ["pre-commit"] - - id: generate-supported # This hook is tied to manual stage so will be used by the CI - name: Generate supported devices (print-diff) - description: This hook generates the supported device sections of README.md and SUPPORTED.md - entry: devtools/generate_supported.py - language: system - types: [json] - verbose: true - args: [--print-diffs] # The CI will print diffs to explain failues. - pass_filenames: false - stages: ["manual"] From 54c0e5418df9b4797967e48a46cb633e4257dcf3 Mon Sep 17 00:00:00 2001 From: sdb9696 Date: Wed, 28 Feb 2024 18:37:00 +0000 Subject: [PATCH 21/23] Update post review --- SUPPORTED.md | 7 +++--- devtools/generate_supported.py | 25 ++++++++++-------- kasa/device.py | 2 +- kasa/smart/smartdevice.py | 42 +++++++++++++++---------------- kasa/tests/device_fixtures.py | 11 +++++--- kasa/tests/test_device_factory.py | 6 +++-- kasa/tests/test_plug.py | 38 ++++++++++++++++++++++++++-- 7 files changed, 89 insertions(+), 42 deletions(-) diff --git a/SUPPORTED.md b/SUPPORTED.md index 58625d09e..97b9e2613 100644 --- a/SUPPORTED.md +++ b/SUPPORTED.md @@ -2,13 +2,12 @@ The following devices have been tested and confirmed as working. If your device is unlisted but working, please open a pull request to update the list and add a fixture file (use `python -m devtools.dump_devinfo` to generate one). -*  Kasa model requires authentication
-All Tapo devices require authentication - ## Kasa devices +*  Kasa model requires authentication + ### Plugs - **EP10** @@ -141,6 +140,8 @@ All Tapo devices require authentication ## Tapo devices +All Tapo devices require authentication + ### Plugs - **P100** diff --git a/devtools/generate_supported.py b/devtools/generate_supported.py index 70a6224d3..88d6877f0 100755 --- a/devtools/generate_supported.py +++ b/devtools/generate_supported.py @@ -10,7 +10,7 @@ from kasa.device_factory import _get_device_type_from_sys_info from kasa.device_type import DeviceType -from kasa.smart.smartdevice import _get_device_type_from_components +from kasa.smart.smartdevice import SmartDevice class SupportedVersion(NamedTuple): @@ -124,7 +124,7 @@ def _supported_summary(supported): def _supported_detail(supported): return _supported_text( supported, - "## $brand devices\n\n$types\n", + "## $brand devices\n\n$preamble\n\n$types\n", "### $type_\n\n$models\n", "- **$model**\n$versions", " - Hardware: $hw$region / Firmware: $fw$auth_flag\n", @@ -141,6 +141,11 @@ def _supported_text( brands = "" version: SupportedVersion for brand, types in supported.items(): + preamble_text = ( + "*  Kasa model requires authentication" + if brand == "kasa" + else "All Tapo devices require authentication" + ) brand_text = brand.capitalize() brand_auth = r"\*" if brand == "tapo" else "" types_text = "" @@ -184,14 +189,15 @@ def _supported_text( types_text += typest.substitute( type_=supported_type.value, models=models_text ) - brands += brandt.substitute(brand=brand_text, types=types_text, auth=brand_auth) + brands += brandt.substitute( + brand=brand_text, types=types_text, auth=brand_auth, preamble=preamble_text + ) return brands def _get_smart_supported(supported): - smart_files = [f for f in Path(SMART_FOLDER).glob("*.json")] - for smart_file in smart_files: - with open(smart_file) as f: + for file in Path(SMART_FOLDER).glob("*.json"): + with file.open() as f: fixture_data = json.load(f) model, _, region = fixture_data["discovery_result"]["device_model"].partition( @@ -206,7 +212,7 @@ def _get_smart_supported(supported): component["id"] for component in fixture_data["component_nego"]["component_list"] ] - dt = _get_device_type_from_components(components, device_type) + dt = SmartDevice._get_device_type_from_components(components, device_type) supported_type = DEVICE_TYPE_TO_SUPPORTED[dt] hw_version = fixture_data["get_device_info"]["hw_ver"] @@ -221,9 +227,8 @@ def _get_smart_supported(supported): def _get_iot_supported(supported): - iot_files = [f for f in Path(IOT_FOLDER).glob("*.json")] - for iot_file in iot_files: - with open(iot_file) as f: + for file in Path(IOT_FOLDER).glob("*.json"): + with file.open() as f: fixture_data = json.load(f) sysinfo = fixture_data["system"]["get_sysinfo"] dt = _get_device_type_from_sys_info(fixture_data) diff --git a/kasa/device.py b/kasa/device.py index d56505935..67e389b92 100644 --- a/kasa/device.py +++ b/kasa/device.py @@ -214,7 +214,7 @@ def is_plug(self) -> bool: @property def is_switch(self) -> bool: - """Return True if the device is a plug.""" + """Return True if the device is a switch.""" return self.device_type == DeviceType.Switch @property diff --git a/kasa/smart/smartdevice.py b/kasa/smart/smartdevice.py index 9f64a6ab0..31143ccc2 100644 --- a/kasa/smart/smartdevice.py +++ b/kasa/smart/smartdevice.py @@ -513,30 +513,30 @@ def device_type(self) -> DeviceType: if self._device_type is not DeviceType.Unknown: return self._device_type - self._device_type = _get_device_type_from_components( + self._device_type = self._get_device_type_from_components( list(self._components.keys()), self._info["type"] ) return self._device_type - -def _get_device_type_from_components( - components: List[str], device_type: str -) -> DeviceType: - """Find type to be displayed as a supported device category.""" - if "HUB" in device_type: - return DeviceType.Hub - if "PLUG" in device_type: - if "child_device" in components: - return DeviceType.Strip + @staticmethod + def _get_device_type_from_components( + components: List[str], device_type: str + ) -> DeviceType: + """Find type to be displayed as a supported device category.""" + if "HUB" in device_type: + return DeviceType.Hub + if "PLUG" in device_type: + if "child_device" in components: + return DeviceType.Strip + return DeviceType.Plug + if "light_strip" in components: + return DeviceType.LightStrip + if "dimmer_calibration" in components: + return DeviceType.Dimmer + if "brightness" in components: + return DeviceType.Bulb + if "SWITCH" in device_type: + return DeviceType.Switch + _LOGGER.warning("Unknown device type, falling back to plug") return DeviceType.Plug - if "light_strip" in components: - return DeviceType.LightStrip - if "dimmer_calibration" in components: - return DeviceType.Dimmer - if "brightness" in components: - return DeviceType.Bulb - if "SWITCH" in device_type: - return DeviceType.Switch - _LOGGER.warning("Unknown device type, falling back to plug") - return DeviceType.Plug diff --git a/kasa/tests/device_fixtures.py b/kasa/tests/device_fixtures.py index 9c9826e18..b76a76468 100644 --- a/kasa/tests/device_fixtures.py +++ b/kasa/tests/device_fixtures.py @@ -171,9 +171,11 @@ def parametrize( bulb = parametrize("bulbs", model_filter=BULBS, protocol_filter={"SMART", "IOT"}) plug = parametrize("plugs", model_filter=PLUGS, protocol_filter={"IOT", "SMART"}) -plug_iot = parametrize("plugs", model_filter=PLUGS, protocol_filter={"IOT"}) -switch = parametrize("plugs", model_filter=SWITCHES, protocol_filter={"IOT", "SMART"}) -switch_iot = parametrize("plugs", model_filter=SWITCHES, protocol_filter={"IOT"}) +plug_iot = parametrize("plugs iot", model_filter=PLUGS, protocol_filter={"IOT"}) +switch = parametrize( + "switches", model_filter=SWITCHES, protocol_filter={"IOT", "SMART"} +) +switch_iot = parametrize("switches iot", model_filter=SWITCHES, protocol_filter={"IOT"}) strip = parametrize("strips", model_filter=STRIPS, protocol_filter={"SMART", "IOT"}) dimmer = parametrize("dimmers", model_filter=DIMMERS, protocol_filter={"IOT"}) lightstrip = parametrize( @@ -226,6 +228,9 @@ def parametrize( plug_smart = parametrize( "plug devices smart", model_filter=PLUGS_SMART, protocol_filter={"SMART"} ) +switch_smart = parametrize( + "switch devices smart", model_filter=SWITCHES_SMART, protocol_filter={"SMART"} +) bulb_smart = parametrize( "bulb devices smart", model_filter=BULBS_SMART, protocol_filter={"SMART"} ) diff --git a/kasa/tests/test_device_factory.py b/kasa/tests/test_device_factory.py index 0bd9f833c..dc5144854 100644 --- a/kasa/tests/test_device_factory.py +++ b/kasa/tests/test_device_factory.py @@ -22,7 +22,7 @@ EncryptType, ) from kasa.discover import DiscoveryResult -from kasa.smart.smartdevice import SmartDevice, _get_device_type_from_components +from kasa.smart.smartdevice import SmartDevice def _get_connection_type_device_class(discovery_info): @@ -157,7 +157,9 @@ async def test_device_types(dev: Device): await dev.update() if isinstance(dev, SmartDevice): device_type = dev._discovery_info["result"]["device_type"] - res = _get_device_type_from_components(dev._components.keys(), device_type) + res = SmartDevice._get_device_type_from_components( + dev._components.keys(), device_type + ) else: res = _get_device_type_from_sys_info(dev._last_update) diff --git a/kasa/tests/test_plug.py b/kasa/tests/test_plug.py index 10dd39db2..c8052e27d 100644 --- a/kasa/tests/test_plug.py +++ b/kasa/tests/test_plug.py @@ -1,6 +1,6 @@ from kasa import DeviceType -from .conftest import plug_iot, plug_smart +from .conftest import plug_iot, plug_smart, switch_iot, switch_smart from .test_smartdevice import SYSINFO_SCHEMA # these schemas should go to the mainlib as @@ -19,8 +19,34 @@ async def test_plug_sysinfo(dev): assert dev.is_plug or dev.is_strip +@switch_iot +async def test_switch_sysinfo(dev): + assert dev.sys_info is not None + SYSINFO_SCHEMA(dev.sys_info) + + assert dev.model is not None + + assert dev.device_type == DeviceType.Switch + assert dev.is_switch + + @plug_iot -async def test_led(dev): +async def test_plug_led(dev): + original = dev.led + + await dev.set_led(False) + await dev.update() + assert not dev.led + + await dev.set_led(True) + await dev.update() + assert dev.led + + await dev.set_led(original) + + +@switch_iot +async def test_switch_led(dev): original = dev.led await dev.set_led(False) @@ -40,3 +66,11 @@ async def test_plug_device_info(dev): assert dev.model is not None assert dev.device_type == DeviceType.Plug or dev.device_type == DeviceType.Strip + + +@switch_smart +async def test_switch_device_info(dev): + assert dev._info is not None + assert dev.model is not None + + assert dev.device_type == DeviceType.Switch or dev.device_type == DeviceType.Dimmer From 591fb251d8a4a38a796e757867930716e8b612e4 Mon Sep 17 00:00:00 2001 From: sdb9696 Date: Fri, 1 Mar 2024 16:39:42 +0000 Subject: [PATCH 22/23] Tweak the wording and drop the supported enum --- SUPPORTED.md | 4 +- devtools/generate_supported.py | 70 ++++++++++++++-------------------- 2 files changed, 31 insertions(+), 43 deletions(-) diff --git a/SUPPORTED.md b/SUPPORTED.md index 97b9e2613..9a740d6a8 100644 --- a/SUPPORTED.md +++ b/SUPPORTED.md @@ -6,7 +6,7 @@ The following devices have been tested and confirmed as working. If your device ## Kasa devices -*  Kasa model requires authentication +Some newer Kasa devices require authentication. These are marked with * in the list below. ### Plugs @@ -140,7 +140,7 @@ The following devices have been tested and confirmed as working. If your device ## Tapo devices -All Tapo devices require authentication +All Tapo devices require authentication. ### Plugs diff --git a/devtools/generate_supported.py b/devtools/generate_supported.py index 88d6877f0..3dfb3e517 100755 --- a/devtools/generate_supported.py +++ b/devtools/generate_supported.py @@ -3,7 +3,6 @@ import json import os import sys -from enum import Enum from pathlib import Path from string import Template from typing import NamedTuple @@ -22,28 +21,17 @@ class SupportedVersion(NamedTuple): auth: bool -class SupportedDeviceType(Enum): - """Supported device type enum.""" - - Plugs = "Plugs" - PowerStrips = "Power Strips" - WallSwitches = "Wall Switches" - Bulbs = "Bulbs" - LightStrips = "Light Strips" - Hubs = "Hubs" - Sensors = "Sensors" - - -DEVICE_TYPE_TO_SUPPORTED = { - DeviceType.Plug: SupportedDeviceType.Plugs, - DeviceType.Bulb: SupportedDeviceType.Bulbs, - DeviceType.Strip: SupportedDeviceType.PowerStrips, - DeviceType.StripSocket: SupportedDeviceType.PowerStrips, - DeviceType.Dimmer: SupportedDeviceType.WallSwitches, - DeviceType.Switch: SupportedDeviceType.WallSwitches, - DeviceType.LightStrip: SupportedDeviceType.LightStrips, - DeviceType.Sensor: SupportedDeviceType.Sensors, - DeviceType.Hub: SupportedDeviceType.Hubs, +# The order of devices in this dict drives the display order +DEVICE_TYPE_TO_PRODUCT_GROUP = { + DeviceType.Plug: "Plugs", + DeviceType.Strip: "Power Strips", + DeviceType.StripSocket: "Power Strips", + DeviceType.Dimmer: "Wall Switches", + DeviceType.Switch: "Wall Switches", + DeviceType.Bulb: "Bulbs", + DeviceType.LightStrip: "Light Strips", + DeviceType.Hub: "Hubs", + DeviceType.Sensor: "Sensors", } @@ -142,9 +130,10 @@ def _supported_text( version: SupportedVersion for brand, types in supported.items(): preamble_text = ( - "*  Kasa model requires authentication" + "Some newer Kasa devices require authentication. " + + "These are marked with * in the list below." if brand == "kasa" - else "All Tapo devices require authentication" + else "All Tapo devices require authentication." ) brand_text = brand.capitalize() brand_auth = r"\*" if brand == "tapo" else "" @@ -152,7 +141,7 @@ def _supported_text( for supported_type, models in sorted( # Sort by device type order in the enum types.items(), - key=lambda st: list(SupportedDeviceType).index(st[0]), + key=lambda st: list(DEVICE_TYPE_TO_PRODUCT_GROUP.values()).index(st[0]), ): models_list = [] models_text = "" @@ -162,8 +151,9 @@ def _supported_text( for version in sorted(versions): region_text = f" ({version.region})" if version.region else "" auth_count += 1 if version.auth else 0 - vauth_flag = r"\*" if version.auth else "" - vauth_flag = "" if brand == "tapo" else vauth_flag + vauth_flag = ( + r"\*" if version.auth and brand == "kasa" else "" + ) if version_template: versions_text += versst.substitute( hw=version.hw, @@ -171,14 +161,14 @@ def _supported_text( region=region_text, auth_flag=vauth_flag, ) - auth_flag = ( - r"\*" - if auth_count == len(versions) - else r"\*\*" - if auth_count > 0 - else "" - ) - auth_flag = "" if brand == "tapo" else auth_flag + if brand == "kasa" and auth_count > 0: + auth_flag = ( + r"\*" + if auth_count == len(versions) + else r"\*\*" + ) + else: + auth_flag = "" if model_template: models_text += modelt.substitute( model=model, versions=versions_text, auth_flag=auth_flag @@ -186,9 +176,7 @@ def _supported_text( else: models_list.append(f"{model}{auth_flag}") models_text = models_text if models_text else ", ".join(models_list) - types_text += typest.substitute( - type_=supported_type.value, models=models_text - ) + types_text += typest.substitute(type_=supported_type, models=models_text) brands += brandt.substitute( brand=brand_text, types=types_text, auth=brand_auth, preamble=preamble_text ) @@ -213,7 +201,7 @@ def _get_smart_supported(supported): for component in fixture_data["component_nego"]["component_list"] ] dt = SmartDevice._get_device_type_from_components(components, device_type) - supported_type = DEVICE_TYPE_TO_SUPPORTED[dt] + supported_type = DEVICE_TYPE_TO_PRODUCT_GROUP[dt] hw_version = fixture_data["get_device_info"]["hw_ver"] fw_version = fixture_data["get_device_info"]["fw_ver"] @@ -232,7 +220,7 @@ def _get_iot_supported(supported): fixture_data = json.load(f) sysinfo = fixture_data["system"]["get_sysinfo"] dt = _get_device_type_from_sys_info(fixture_data) - supported_type = DEVICE_TYPE_TO_SUPPORTED[dt] + supported_type = DEVICE_TYPE_TO_PRODUCT_GROUP[dt] model, _, region = sysinfo["model"][:-1].partition("(") auth = "discovery_result" in fixture_data From 6a1c4cf2b93d8406a66e602b96fa92a2b5c5776a Mon Sep 17 00:00:00 2001 From: sdb9696 Date: Fri, 1 Mar 2024 17:55:58 +0000 Subject: [PATCH 23/23] Rename switch to wall switch --- devtools/generate_supported.py | 2 +- kasa/cli.py | 6 +++--- kasa/device.py | 4 ++-- kasa/device_factory.py | 6 +++--- kasa/device_type.py | 2 +- kasa/iot/__init__.py | 4 ++-- kasa/iot/iotplug.py | 6 +++--- kasa/smart/smartdevice.py | 2 +- kasa/tests/device_fixtures.py | 14 ++++++++------ kasa/tests/test_discovery.py | 8 ++++---- kasa/tests/test_plug.py | 14 ++++++++------ 11 files changed, 36 insertions(+), 32 deletions(-) diff --git a/devtools/generate_supported.py b/devtools/generate_supported.py index 3dfb3e517..85dc3992e 100755 --- a/devtools/generate_supported.py +++ b/devtools/generate_supported.py @@ -27,7 +27,7 @@ class SupportedVersion(NamedTuple): DeviceType.Strip: "Power Strips", DeviceType.StripSocket: "Power Strips", DeviceType.Dimmer: "Wall Switches", - DeviceType.Switch: "Wall Switches", + DeviceType.WallSwitch: "Wall Switches", DeviceType.Bulb: "Bulbs", DeviceType.LightStrip: "Light Strips", DeviceType.Hub: "Hubs", diff --git a/kasa/cli.py b/kasa/cli.py index 178680a7c..78553ebf2 100755 --- a/kasa/cli.py +++ b/kasa/cli.py @@ -33,7 +33,7 @@ IotLightStrip, IotPlug, IotStrip, - IotSwitch, + IotWallSwitch, ) from kasa.smart import SmartBulb, SmartDevice @@ -71,13 +71,13 @@ def wrapper(message=None, *args, **kwargs): TYPE_TO_CLASS = { "plug": IotPlug, - "switch": IotSwitch, + "switch": IotWallSwitch, "bulb": IotBulb, "dimmer": IotDimmer, "strip": IotStrip, "lightstrip": IotLightStrip, "iot.plug": IotPlug, - "iot.switch": IotSwitch, + "iot.switch": IotWallSwitch, "iot.bulb": IotBulb, "iot.dimmer": IotDimmer, "iot.strip": IotStrip, diff --git a/kasa/device.py b/kasa/device.py index 67e389b92..cebec582c 100644 --- a/kasa/device.py +++ b/kasa/device.py @@ -213,9 +213,9 @@ def is_plug(self) -> bool: return self.device_type == DeviceType.Plug @property - def is_switch(self) -> bool: + def is_wallswitch(self) -> bool: """Return True if the device is a switch.""" - return self.device_type == DeviceType.Switch + return self.device_type == DeviceType.WallSwitch @property def is_strip(self) -> bool: diff --git a/kasa/device_factory.py b/kasa/device_factory.py index f31dd8f80..d35df09c4 100755 --- a/kasa/device_factory.py +++ b/kasa/device_factory.py @@ -15,7 +15,7 @@ IotLightStrip, IotPlug, IotStrip, - IotSwitch, + IotWallSwitch, ) from .iotprotocol import IotProtocol from .klaptransport import KlapTransport, KlapTransportV2 @@ -131,7 +131,7 @@ def _get_device_type_from_sys_info(info: Dict[str, Any]) -> DeviceType: if "children" in sysinfo: return DeviceType.Strip if (dev_name := sysinfo.get("dev_name")) and "light" in dev_name.lower(): - return DeviceType.Switch + return DeviceType.WallSwitch return DeviceType.Plug if "smartbulb" in type_.lower(): @@ -149,7 +149,7 @@ def get_device_class_from_sys_info(sysinfo: Dict[str, Any]) -> Type[IotDevice]: DeviceType.Plug: IotPlug, DeviceType.Dimmer: IotDimmer, DeviceType.Strip: IotStrip, - DeviceType.Switch: IotSwitch, + DeviceType.WallSwitch: IotWallSwitch, DeviceType.LightStrip: IotLightStrip, } return TYPE_TO_CLASS[_get_device_type_from_sys_info(sysinfo)] diff --git a/kasa/device_type.py b/kasa/device_type.py index c01c0904d..80a816443 100755 --- a/kasa/device_type.py +++ b/kasa/device_type.py @@ -11,7 +11,7 @@ class DeviceType(Enum): Plug = "plug" Bulb = "bulb" Strip = "strip" - Switch = "switch" + WallSwitch = "wallswitch" StripSocket = "stripsocket" Dimmer = "dimmer" LightStrip = "lightstrip" diff --git a/kasa/iot/__init__.py b/kasa/iot/__init__.py index 16bc4bfc9..e1e4b5760 100644 --- a/kasa/iot/__init__.py +++ b/kasa/iot/__init__.py @@ -3,7 +3,7 @@ from .iotdevice import IotDevice from .iotdimmer import IotDimmer from .iotlightstrip import IotLightStrip -from .iotplug import IotPlug, IotSwitch +from .iotplug import IotPlug, IotWallSwitch from .iotstrip import IotStrip __all__ = [ @@ -13,5 +13,5 @@ "IotStrip", "IotDimmer", "IotLightStrip", - "IotSwitch", + "IotWallSwitch", ] diff --git a/kasa/iot/iotplug.py b/kasa/iot/iotplug.py index 3e6030150..3f776b985 100644 --- a/kasa/iot/iotplug.py +++ b/kasa/iot/iotplug.py @@ -103,8 +103,8 @@ def state_information(self) -> Dict[str, Any]: return {} -class IotSwitch(IotPlug): - """Representation of a TP-Link Smart Switch.""" +class IotWallSwitch(IotPlug): + """Representation of a TP-Link Smart Wall Switch.""" def __init__( self, @@ -114,4 +114,4 @@ def __init__( protocol: Optional[BaseProtocol] = None, ) -> None: super().__init__(host=host, config=config, protocol=protocol) - self._device_type = DeviceType.Switch + self._device_type = DeviceType.WallSwitch diff --git a/kasa/smart/smartdevice.py b/kasa/smart/smartdevice.py index 31143ccc2..8b0236c37 100644 --- a/kasa/smart/smartdevice.py +++ b/kasa/smart/smartdevice.py @@ -537,6 +537,6 @@ def _get_device_type_from_components( if "brightness" in components: return DeviceType.Bulb if "SWITCH" in device_type: - return DeviceType.Switch + return DeviceType.WallSwitch _LOGGER.warning("Unknown device type, falling back to plug") return DeviceType.Plug diff --git a/kasa/tests/device_fixtures.py b/kasa/tests/device_fixtures.py index b76a76468..73d171d23 100644 --- a/kasa/tests/device_fixtures.py +++ b/kasa/tests/device_fixtures.py @@ -7,7 +7,7 @@ Device, Discover, ) -from kasa.iot import IotBulb, IotDimmer, IotLightStrip, IotPlug, IotStrip, IotSwitch +from kasa.iot import IotBulb, IotDimmer, IotLightStrip, IotPlug, IotStrip, IotWallSwitch from kasa.smart import SmartBulb, SmartDevice from .fakeprotocol_iot import FakeIotProtocol @@ -172,10 +172,12 @@ def parametrize( bulb = parametrize("bulbs", model_filter=BULBS, protocol_filter={"SMART", "IOT"}) plug = parametrize("plugs", model_filter=PLUGS, protocol_filter={"IOT", "SMART"}) plug_iot = parametrize("plugs iot", model_filter=PLUGS, protocol_filter={"IOT"}) -switch = parametrize( - "switches", model_filter=SWITCHES, protocol_filter={"IOT", "SMART"} +wallswitch = parametrize( + "wall switches", model_filter=SWITCHES, protocol_filter={"IOT", "SMART"} +) +wallswitch_iot = parametrize( + "wall switches iot", model_filter=SWITCHES, protocol_filter={"IOT"} ) -switch_iot = parametrize("switches iot", model_filter=SWITCHES, protocol_filter={"IOT"}) strip = parametrize("strips", model_filter=STRIPS, protocol_filter={"SMART", "IOT"}) dimmer = parametrize("dimmers", model_filter=DIMMERS, protocol_filter={"IOT"}) lightstrip = parametrize( @@ -257,7 +259,7 @@ def check_categories(): + strip.args[1] + plug.args[1] + bulb.args[1] - + switch.args[1] + + wallswitch.args[1] + lightstrip.args[1] + bulb_smart.args[1] + dimmers_smart.args[1] @@ -306,7 +308,7 @@ def device_for_fixture_name(model, protocol): return IotPlug for d in SWITCHES_IOT: if d in model: - return IotSwitch + return IotWallSwitch # Light strips are recognized also as bulbs, so this has to go first for d in BULBS_IOT_LIGHT_STRIP: diff --git a/kasa/tests/test_discovery.py b/kasa/tests/test_discovery.py index 8a3cd420b..eb0391444 100644 --- a/kasa/tests/test_discovery.py +++ b/kasa/tests/test_discovery.py @@ -31,7 +31,7 @@ new_discovery, plug_iot, strip_iot, - switch_iot, + wallswitch_iot, ) UNSUPPORTED = { @@ -56,11 +56,11 @@ } -@switch_iot +@wallswitch_iot async def test_type_detection_switch(dev: Device): d = Discover._get_device_class(dev._last_update)("localhost") - assert d.is_switch - assert d.device_type == DeviceType.Switch + assert d.is_wallswitch + assert d.device_type == DeviceType.WallSwitch @plug_iot diff --git a/kasa/tests/test_plug.py b/kasa/tests/test_plug.py index c8052e27d..9ccf3d043 100644 --- a/kasa/tests/test_plug.py +++ b/kasa/tests/test_plug.py @@ -1,6 +1,6 @@ from kasa import DeviceType -from .conftest import plug_iot, plug_smart, switch_iot, switch_smart +from .conftest import plug_iot, plug_smart, switch_smart, wallswitch_iot from .test_smartdevice import SYSINFO_SCHEMA # these schemas should go to the mainlib as @@ -19,15 +19,15 @@ async def test_plug_sysinfo(dev): assert dev.is_plug or dev.is_strip -@switch_iot +@wallswitch_iot async def test_switch_sysinfo(dev): assert dev.sys_info is not None SYSINFO_SCHEMA(dev.sys_info) assert dev.model is not None - assert dev.device_type == DeviceType.Switch - assert dev.is_switch + assert dev.device_type == DeviceType.WallSwitch + assert dev.is_wallswitch @plug_iot @@ -45,7 +45,7 @@ async def test_plug_led(dev): await dev.set_led(original) -@switch_iot +@wallswitch_iot async def test_switch_led(dev): original = dev.led @@ -73,4 +73,6 @@ async def test_switch_device_info(dev): assert dev._info is not None assert dev.model is not None - assert dev.device_type == DeviceType.Switch or dev.device_type == DeviceType.Dimmer + assert ( + dev.device_type == DeviceType.WallSwitch or dev.device_type == DeviceType.Dimmer + )