mirror of
https://github.com/python-kasa/python-kasa.git
synced 2024-12-22 11:13:34 +00:00
Add WallSwitch device type and autogenerate supported devices docs (#758)
This commit is contained in:
parent
0306e05fb9
commit
fcad0d2344
6
.github/workflows/ci.yml
vendored
6
.github/workflows/ci.yml
vendored
@ -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 README for supported models"
|
||||
run: |
|
||||
poetry run python -m devtools.check_readme_vs_fixtures
|
||||
|
||||
|
||||
tests:
|
||||
|
@ -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: devtools/generate_supported.py
|
||||
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
|
||||
|
128
README.md
128
README.md
@ -220,120 +220,32 @@ 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 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).**
|
||||
<!--Do not edit text inside the SUPPORTED section below -->
|
||||
<!--SUPPORTED_START-->
|
||||
### Supported Kasa devices
|
||||
|
||||
### Plugs
|
||||
- **Plugs**: EP10, EP25<sup>\*</sup>, HS100<sup>\*\*</sup>, HS103, HS105, HS110, KP100, KP105, KP115, KP125, KP125M<sup>\*</sup>, KP401
|
||||
- **Power Strips**: EP40, HS107, HS300, KP200, KP303, KP400
|
||||
- **Wall Switches**: ES20M, HS200, HS210, HS220, KP405, KS200M, KS205<sup>\*</sup>, KS220M, KS225<sup>\*</sup>, KS230
|
||||
- **Bulbs**: KL110, KL120, KL125, KL130, KL135, KL50, KL60, LB100, LB110, LB120, LB130
|
||||
- **Light Strips**: KL400L5, KL420L5, KL430
|
||||
|
||||
* 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)
|
||||
### Supported Tapo<sup>\*</sup> devices
|
||||
|
||||
### Power Strips
|
||||
- **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
|
||||
|
||||
* EP40
|
||||
* HS300
|
||||
* KP303
|
||||
* KP200 (in wall)
|
||||
* KP400
|
||||
* KP405 (dimmer)
|
||||
<!--SUPPORTED_END-->
|
||||
<sup>*</sup> Model requires authentication<br>
|
||||
<sup>**</sup> Newer versions require authentication
|
||||
|
||||
### 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
|
||||
|
||||
#### Hubs
|
||||
|
||||
* Tapo H100
|
||||
|
||||
### 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).**
|
||||
See [supported devices in our documentation](SUPPORTED.md) for more detailed information about tested hardware and software versions.
|
||||
|
||||
## Resources
|
||||
|
||||
|
210
SUPPORTED.md
Normal file
210
SUPPORTED.md
Normal file
@ -0,0 +1,210 @@
|
||||
# Supported devices
|
||||
|
||||
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).
|
||||
|
||||
<!--Do not edit text inside the SUPPORTED section below -->
|
||||
<!--SUPPORTED_START-->
|
||||
## Kasa devices
|
||||
|
||||
Some newer Kasa devices require authentication. These are marked with <sup>*</sup> in the list below.
|
||||
|
||||
### Plugs
|
||||
|
||||
- **EP10**
|
||||
- Hardware: 1.0 (US) / Firmware: 1.0.2
|
||||
- **EP25**
|
||||
- Hardware: 2.6 (US) / Firmware: 1.0.1<sup>\*</sup>
|
||||
- Hardware: 2.6 (US) / Firmware: 1.0.2<sup>\*</sup>
|
||||
- **HS100**
|
||||
- Hardware: 1.0 (UK) / Firmware: 1.2.6
|
||||
- Hardware: 4.1 (UK) / Firmware: 1.1.0<sup>\*</sup>
|
||||
- Hardware: 1.0 (US) / Firmware: 1.2.5
|
||||
- Hardware: 2.0 (US) / Firmware: 1.5.6
|
||||
- **HS103**
|
||||
- Hardware: 1.0 (US) / Firmware: 1.5.7
|
||||
- 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 (US) / Firmware: 1.0.8
|
||||
- **KP100**
|
||||
- Hardware: 3.0 (US) / Firmware: 1.0.1
|
||||
- **KP105**
|
||||
- Hardware: 1.0 (UK) / Firmware: 1.0.5
|
||||
- Hardware: 1.0 (UK) / Firmware: 1.0.7
|
||||
- **KP115**
|
||||
- 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**
|
||||
- Hardware: 1.0 (US) / Firmware: 1.1.3<sup>\*</sup>
|
||||
- **KP401**
|
||||
- Hardware: 1.0 (US) / Firmware: 1.0.0
|
||||
|
||||
### Power Strips
|
||||
|
||||
- **EP40**
|
||||
- Hardware: 1.0 (US) / Firmware: 1.0.2
|
||||
- **HS107**
|
||||
- Hardware: 1.0 (US) / Firmware: 1.0.8
|
||||
- **HS300**
|
||||
- Hardware: 1.0 (US) / Firmware: 1.0.10
|
||||
- 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: 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
|
||||
|
||||
### 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<sup>\*</sup>
|
||||
- **KS220M**
|
||||
- Hardware: 1.0 (US) / Firmware: 1.0.4
|
||||
- **KS225**
|
||||
- Hardware: 1.0 (US) / Firmware: 1.0.2<sup>\*</sup>
|
||||
- **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: 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
|
||||
|
||||
|
||||
## Tapo devices
|
||||
|
||||
All Tapo devices require authentication.
|
||||
|
||||
### Plugs
|
||||
|
||||
- **P100**
|
||||
- Hardware: 1.0.0 / Firmware: 1.1.3
|
||||
- Hardware: 1.0.0 / Firmware: 1.3.7
|
||||
- **P110**
|
||||
- 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
|
||||
- **P135**
|
||||
- Hardware: 1.0 (US) / Firmware: 1.0.5
|
||||
- **TP15**
|
||||
- Hardware: 1.0 (US) / Firmware: 1.0.3
|
||||
|
||||
### Power Strips
|
||||
|
||||
- **P300**
|
||||
- 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
|
||||
|
||||
### Wall Switches
|
||||
|
||||
- **S500D**
|
||||
- Hardware: 1.0 (US) / Firmware: 1.0.5
|
||||
- **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
|
||||
|
||||
|
||||
<!--SUPPORTED_END-->
|
@ -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("README.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 = rf"^\*.*\s{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)
|
241
devtools/generate_supported.py
Executable file
241
devtools/generate_supported.py
Executable file
@ -0,0 +1,241 @@
|
||||
#!/usr/bin/env python
|
||||
"""Script that checks supported devices and updates README.md and SUPPORTED.md."""
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from string import Template
|
||||
from typing import NamedTuple
|
||||
|
||||
from kasa.device_factory import _get_device_type_from_sys_info
|
||||
from kasa.device_type import DeviceType
|
||||
from kasa.smart.smartdevice import SmartDevice
|
||||
|
||||
|
||||
class SupportedVersion(NamedTuple):
|
||||
"""Supported version."""
|
||||
|
||||
region: str
|
||||
hw: str
|
||||
fw: str
|
||||
auth: bool
|
||||
|
||||
|
||||
# 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.WallSwitch: "Wall Switches",
|
||||
DeviceType.Bulb: "Bulbs",
|
||||
DeviceType.LightStrip: "Light Strips",
|
||||
DeviceType.Hub: "Hubs",
|
||||
DeviceType.Sensor: "Sensors",
|
||||
}
|
||||
|
||||
|
||||
SUPPORTED_FILENAME = "SUPPORTED.md"
|
||||
README_FILENAME = "README.md"
|
||||
|
||||
IOT_FOLDER = "kasa/tests/fixtures/"
|
||||
SMART_FOLDER = "kasa/tests/fixtures/smart/"
|
||||
|
||||
|
||||
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": {}}
|
||||
|
||||
_get_iot_supported(supported)
|
||||
_get_smart_supported(supported)
|
||||
|
||||
readme_updated = _update_supported_file(
|
||||
README_FILENAME, _supported_summary(supported), print_diffs
|
||||
)
|
||||
supported_updated = _update_supported_file(
|
||||
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, print_diffs) -> bool:
|
||||
with open(filename) as f:
|
||||
contents = f.readlines()
|
||||
|
||||
start_index = end_index = None
|
||||
for index, line in enumerate(contents):
|
||||
if line == "<!--SUPPORTED_START-->\n":
|
||||
start_index = index + 1
|
||||
if line == "<!--SUPPORTED_END-->\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."
|
||||
)
|
||||
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)
|
||||
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$preamble\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():
|
||||
preamble_text = (
|
||||
"Some newer Kasa devices require authentication. "
|
||||
+ "These are marked with <sup>*</sup> in the list below."
|
||||
if brand == "kasa"
|
||||
else "All Tapo devices require authentication."
|
||||
)
|
||||
brand_text = brand.capitalize()
|
||||
brand_auth = r"<sup>\*</sup>" if brand == "tapo" else ""
|
||||
types_text = ""
|
||||
for supported_type, models in sorted(
|
||||
# Sort by device type order in the enum
|
||||
types.items(),
|
||||
key=lambda st: list(DEVICE_TYPE_TO_PRODUCT_GROUP.values()).index(st[0]),
|
||||
):
|
||||
models_list = []
|
||||
models_text = ""
|
||||
for model, versions in sorted(models.items()):
|
||||
auth_count = 0
|
||||
versions_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"<sup>\*</sup>" if version.auth and brand == "kasa" else ""
|
||||
)
|
||||
if version_template:
|
||||
versions_text += versst.substitute(
|
||||
hw=version.hw,
|
||||
fw=version.fw,
|
||||
region=region_text,
|
||||
auth_flag=vauth_flag,
|
||||
)
|
||||
if brand == "kasa" and auth_count > 0:
|
||||
auth_flag = (
|
||||
r"<sup>\*</sup>"
|
||||
if auth_count == len(versions)
|
||||
else r"<sup>\*\*</sup>"
|
||||
)
|
||||
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_=supported_type, models=models_text)
|
||||
brands += brandt.substitute(
|
||||
brand=brand_text, types=types_text, auth=brand_auth, preamble=preamble_text
|
||||
)
|
||||
return brands
|
||||
|
||||
|
||||
def _get_smart_supported(supported):
|
||||
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(
|
||||
"("
|
||||
)
|
||||
# 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 = devicetype[:4].lower()
|
||||
components = [
|
||||
component["id"]
|
||||
for component in fixture_data["component_nego"]["component_list"]
|
||||
]
|
||||
dt = SmartDevice._get_device_type_from_components(components, device_type)
|
||||
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"]
|
||||
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):
|
||||
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)
|
||||
supported_type = DEVICE_TYPE_TO_PRODUCT_GROUP[dt]
|
||||
|
||||
model, _, region = sysinfo["model"][:-1].partition("(")
|
||||
auth = "discovery_result" in fixture_data
|
||||
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:])
|
3
docs/source/SUPPORTED.md
Normal file
3
docs/source/SUPPORTED.md
Normal file
@ -0,0 +1,3 @@
|
||||
```{include} ../../SUPPORTED.md
|
||||
:relative-docs: doc/source
|
||||
```
|
@ -15,3 +15,4 @@
|
||||
smartdimmer
|
||||
smartstrip
|
||||
smartlightstrip
|
||||
SUPPORTED
|
||||
|
12
kasa/cli.py
12
kasa/cli.py
@ -26,7 +26,15 @@ from kasa import (
|
||||
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,
|
||||
IotWallSwitch,
|
||||
)
|
||||
from kasa.smart import SmartBulb, SmartDevice
|
||||
|
||||
try:
|
||||
@ -63,11 +71,13 @@ echo = _do_echo
|
||||
|
||||
TYPE_TO_CLASS = {
|
||||
"plug": IotPlug,
|
||||
"switch": IotWallSwitch,
|
||||
"bulb": IotBulb,
|
||||
"dimmer": IotDimmer,
|
||||
"strip": IotStrip,
|
||||
"lightstrip": IotLightStrip,
|
||||
"iot.plug": IotPlug,
|
||||
"iot.switch": IotWallSwitch,
|
||||
"iot.bulb": IotBulb,
|
||||
"iot.dimmer": IotDimmer,
|
||||
"iot.strip": IotStrip,
|
||||
|
@ -212,6 +212,11 @@ class Device(ABC):
|
||||
"""Return True if the device is a plug."""
|
||||
return self.device_type == DeviceType.Plug
|
||||
|
||||
@property
|
||||
def is_wallswitch(self) -> bool:
|
||||
"""Return True if the device is a switch."""
|
||||
return self.device_type == DeviceType.WallSwitch
|
||||
|
||||
@property
|
||||
def is_strip(self) -> bool:
|
||||
"""Return True if the device is a strip."""
|
||||
|
@ -5,9 +5,18 @@ from typing import Any, Dict, Optional, Tuple, Type
|
||||
|
||||
from .aestransport import AesTransport
|
||||
from .device import Device
|
||||
from .device_type import DeviceType
|
||||
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,
|
||||
IotWallSwitch,
|
||||
)
|
||||
from .iotprotocol import IotProtocol
|
||||
from .klaptransport import KlapTransport, KlapTransportV2
|
||||
from .protocol import (
|
||||
@ -105,7 +114,7 @@ async def _connect(config: DeviceConfig, protocol: BaseProtocol) -> "Device":
|
||||
)
|
||||
|
||||
|
||||
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")
|
||||
@ -116,22 +125,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.WallSwitch
|
||||
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.WallSwitch: IotWallSwitch,
|
||||
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]] = {
|
||||
|
@ -11,6 +11,7 @@ class DeviceType(Enum):
|
||||
Plug = "plug"
|
||||
Bulb = "bulb"
|
||||
Strip = "strip"
|
||||
WallSwitch = "wallswitch"
|
||||
StripSocket = "stripsocket"
|
||||
Dimmer = "dimmer"
|
||||
LightStrip = "lightstrip"
|
||||
|
@ -3,7 +3,7 @@ from .iotbulb import IotBulb
|
||||
from .iotdevice import IotDevice
|
||||
from .iotdimmer import IotDimmer
|
||||
from .iotlightstrip import IotLightStrip
|
||||
from .iotplug import IotPlug
|
||||
from .iotplug import IotPlug, IotWallSwitch
|
||||
from .iotstrip import IotStrip
|
||||
|
||||
__all__ = [
|
||||
@ -13,4 +13,5 @@ __all__ = [
|
||||
"IotStrip",
|
||||
"IotDimmer",
|
||||
"IotLightStrip",
|
||||
"IotWallSwitch",
|
||||
]
|
||||
|
@ -13,7 +13,7 @@ _LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
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 @@ class IotPlug(IotDevice):
|
||||
def state_information(self) -> Dict[str, Any]:
|
||||
"""Return switch-specific state information."""
|
||||
return {}
|
||||
|
||||
|
||||
class IotWallSwitch(IotPlug):
|
||||
"""Representation of a TP-Link Smart Wall 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.WallSwitch
|
||||
|
@ -63,12 +63,6 @@ class SmartDevice(Device):
|
||||
)
|
||||
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 @@ class SmartDevice(Device):
|
||||
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 = self._get_device_type_from_components(
|
||||
list(self._components.keys()), self._info["type"]
|
||||
)
|
||||
|
||||
return self._device_type
|
||||
|
||||
@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.WallSwitch
|
||||
_LOGGER.warning("Unknown device type, falling back to plug")
|
||||
return DeviceType.Plug
|
||||
|
@ -7,7 +7,7 @@ from kasa import (
|
||||
Device,
|
||||
Discover,
|
||||
)
|
||||
from kasa.iot import IotBulb, IotDimmer, IotLightStrip, IotPlug, IotStrip
|
||||
from kasa.iot import IotBulb, IotDimmer, IotLightStrip, IotPlug, IotStrip, IotWallSwitch
|
||||
from kasa.smart import SmartBulb, SmartDevice
|
||||
|
||||
from .fakeprotocol_iot import FakeIotProtocol
|
||||
@ -60,15 +60,12 @@ PLUGS_IOT = {
|
||||
"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 @@ PLUGS_SMART = {
|
||||
"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 @@ WITH_EMETER = {*WITH_EMETER_IOT, *WITH_EMETER_SMART}
|
||||
|
||||
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,14 @@ no_emeter_iot = 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 iot", model_filter=PLUGS, protocol_filter={"IOT"})
|
||||
wallswitch = parametrize(
|
||||
"wall switches", model_filter=SWITCHES, protocol_filter={"IOT", "SMART"}
|
||||
)
|
||||
wallswitch_iot = parametrize(
|
||||
"wall 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(
|
||||
@ -213,6 +230,9 @@ strip_smart = 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"}
|
||||
)
|
||||
@ -239,8 +259,8 @@ def check_categories():
|
||||
+ strip.args[1]
|
||||
+ plug.args[1]
|
||||
+ bulb.args[1]
|
||||
+ wallswitch.args[1]
|
||||
+ lightstrip.args[1]
|
||||
+ plug_smart.args[1]
|
||||
+ bulb_smart.args[1]
|
||||
+ dimmers_smart.args[1]
|
||||
+ hubs_smart.args[1]
|
||||
@ -263,6 +283,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 +306,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 IotWallSwitch
|
||||
|
||||
# Light strips are recognized also as bulbs, so this has to go first
|
||||
for d in BULBS_IOT_LIGHT_STRIP:
|
||||
@ -325,6 +351,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
|
||||
|
||||
|
@ -10,7 +10,11 @@ from kasa import (
|
||||
Discover,
|
||||
KasaException,
|
||||
)
|
||||
from kasa.device_factory import connect, get_protocol
|
||||
from kasa.device_factory import (
|
||||
_get_device_type_from_sys_info,
|
||||
connect,
|
||||
get_protocol,
|
||||
)
|
||||
from kasa.deviceconfig import (
|
||||
ConnectionType,
|
||||
DeviceConfig,
|
||||
@ -18,6 +22,7 @@ from kasa.deviceconfig import (
|
||||
EncryptType,
|
||||
)
|
||||
from kasa.discover import DiscoveryResult
|
||||
from kasa.smart.smartdevice import SmartDevice
|
||||
|
||||
|
||||
def _get_connection_type_device_class(discovery_info):
|
||||
@ -146,3 +151,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_types(dev: Device):
|
||||
await dev.update()
|
||||
if isinstance(dev, SmartDevice):
|
||||
device_type = dev._discovery_info["result"]["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)
|
||||
|
||||
assert dev.device_type == res
|
||||
|
@ -29,8 +29,9 @@ from .conftest import (
|
||||
dimmer,
|
||||
lightstrip,
|
||||
new_discovery,
|
||||
plug,
|
||||
plug_iot,
|
||||
strip_iot,
|
||||
wallswitch_iot,
|
||||
)
|
||||
|
||||
UNSUPPORTED = {
|
||||
@ -55,7 +56,14 @@ UNSUPPORTED = {
|
||||
}
|
||||
|
||||
|
||||
@plug
|
||||
@wallswitch_iot
|
||||
async def test_type_detection_switch(dev: Device):
|
||||
d = Discover._get_device_class(dev._last_update)("localhost")
|
||||
assert d.is_wallswitch
|
||||
assert d.device_type == DeviceType.WallSwitch
|
||||
|
||||
|
||||
@plug_iot
|
||||
async def test_type_detection_plug(dev: Device):
|
||||
d = Discover._get_device_class(dev._last_update)("localhost")
|
||||
assert d.is_plug
|
||||
|
@ -1,6 +1,6 @@
|
||||
from kasa import DeviceType
|
||||
|
||||
from .conftest import plug, plug_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
|
||||
@ -8,7 +8,7 @@ from .test_smartdevice import SYSINFO_SCHEMA
|
||||
# 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,8 +19,34 @@ async def test_plug_sysinfo(dev):
|
||||
assert dev.is_plug or dev.is_strip
|
||||
|
||||
|
||||
@plug
|
||||
async def test_led(dev):
|
||||
@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.WallSwitch
|
||||
assert dev.is_wallswitch
|
||||
|
||||
|
||||
@plug_iot
|
||||
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)
|
||||
|
||||
|
||||
@wallswitch_iot
|
||||
async def test_switch_led(dev):
|
||||
original = dev.led
|
||||
|
||||
await dev.set_led(False)
|
||||
@ -40,3 +66,13 @@ 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.WallSwitch or dev.device_type == DeviceType.Dimmer
|
||||
)
|
||||
|
30
poetry.lock
generated
30
poetry.lock
generated
@ -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"
|
||||
|
@ -35,7 +35,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 }
|
||||
|
Loading…
Reference in New Issue
Block a user