mirror of
https://github.com/python-kasa/python-kasa.git
synced 2025-08-06 18:54:08 +00:00
Add support for lightstrips (KL430) (#74)
* Preliminary support for light strips * Add color temperature range and cleanup, thanks to @darkoppressor * Use lightstrip instead of {led,light}strip consistently everywhere * The cli flag is now --lightstrip * add apidocs * Add fixture file for KL430 Signed-off-by: Kevin Wells <darkoppressor@gmail.com> * Add discovery support, expose effect and length of the strip * use set_light_state instead of transition_light_state * Add tests for lightstrip * add doctests * Add KL430 to supported devices in README Co-authored-by: Kevin Wells <darkoppressor@gmail.com>
This commit is contained in:
@@ -8,7 +8,14 @@ from unittest.mock import MagicMock
|
||||
|
||||
import pytest # type: ignore # see https://github.com/pytest-dev/pytest/issues/3342
|
||||
|
||||
from kasa import Discover, SmartBulb, SmartDimmer, SmartPlug, SmartStrip
|
||||
from kasa import (
|
||||
Discover,
|
||||
SmartBulb,
|
||||
SmartDimmer,
|
||||
SmartLightStrip,
|
||||
SmartPlug,
|
||||
SmartStrip,
|
||||
)
|
||||
|
||||
from .newfakes import FakeTransportProtocol
|
||||
|
||||
@@ -17,9 +24,11 @@ SUPPORTED_DEVICES = glob.glob(
|
||||
)
|
||||
|
||||
|
||||
BULBS = {"KL60", "LB100", "LB120", "LB130", "KL120", "KL130"}
|
||||
VARIABLE_TEMP = {"LB120", "LB130", "KL120", "KL130"}
|
||||
COLOR_BULBS = {"LB130", "KL130"}
|
||||
LIGHT_STRIPS = {"KL430"}
|
||||
BULBS = {"KL60", "LB100", "LB120", "LB130", "KL120", "KL130", *LIGHT_STRIPS}
|
||||
VARIABLE_TEMP = {"LB120", "LB130", "KL120", "KL130", "KL430", *LIGHT_STRIPS}
|
||||
COLOR_BULBS = {"LB130", "KL130", *LIGHT_STRIPS}
|
||||
|
||||
|
||||
PLUGS = {"HS100", "HS103", "HS105", "HS110", "HS200", "HS210"}
|
||||
STRIPS = {"HS107", "HS300", "KP303", "KP400"}
|
||||
@@ -65,9 +74,12 @@ bulb = parametrize("bulbs", BULBS, ids=name_for_filename)
|
||||
plug = parametrize("plugs", PLUGS, ids=name_for_filename)
|
||||
strip = parametrize("strips", STRIPS, ids=name_for_filename)
|
||||
dimmer = parametrize("dimmers", DIMMERS, ids=name_for_filename)
|
||||
lightstrip = parametrize("lightstrips", LIGHT_STRIPS, ids=name_for_filename)
|
||||
|
||||
# This ensures that every single file inside fixtures/ is being placed in some category
|
||||
categorized_fixtures = set(dimmer.args[1] + strip.args[1] + plug.args[1] + bulb.args[1])
|
||||
categorized_fixtures = set(
|
||||
dimmer.args[1] + strip.args[1] + plug.args[1] + bulb.args[1] + lightstrip.args[1]
|
||||
)
|
||||
diff = set(SUPPORTED_DEVICES) - set(categorized_fixtures)
|
||||
if diff:
|
||||
for file in diff:
|
||||
@@ -105,12 +117,20 @@ def device_for_file(model):
|
||||
for d in STRIPS:
|
||||
if d in model:
|
||||
return SmartStrip
|
||||
|
||||
for d in PLUGS:
|
||||
if d in model:
|
||||
return SmartPlug
|
||||
|
||||
# Light strips are recognized also as bulbs, so this has to go first
|
||||
for d in LIGHT_STRIPS:
|
||||
if d in model:
|
||||
return SmartLightStrip
|
||||
|
||||
for d in BULBS:
|
||||
if d in model:
|
||||
return SmartBulb
|
||||
|
||||
for d in DIMMERS:
|
||||
if d in model:
|
||||
return SmartDimmer
|
||||
|
70
kasa/tests/fixtures/KL430(US)_1.0.json
vendored
Normal file
70
kasa/tests/fixtures/KL430(US)_1.0.json
vendored
Normal file
@@ -0,0 +1,70 @@
|
||||
{
|
||||
"emeter": {
|
||||
"err_code": -1,
|
||||
"err_msg": "module not support"
|
||||
},
|
||||
"smartlife.iot.common.emeter": {
|
||||
"get_realtime": {
|
||||
"current_ma": 0,
|
||||
"err_code": 0,
|
||||
"power_mw": 8729,
|
||||
"total_wh": 21,
|
||||
"voltage_mv": 0
|
||||
}
|
||||
},
|
||||
"smartlife.iot.dimmer": {
|
||||
"err_code": -1,
|
||||
"err_msg": "module not support"
|
||||
},
|
||||
"smartlife.iot.smartbulb.lightingservice": {
|
||||
"err_code": -1,
|
||||
"err_msg": "module not support"
|
||||
},
|
||||
"system": {
|
||||
"get_sysinfo": {
|
||||
"active_mode": "none",
|
||||
"alias": "KL430 pantry lightstrip",
|
||||
"ctrl_protocols": {
|
||||
"name": "Linkie",
|
||||
"version": "1.0"
|
||||
},
|
||||
"description": "Kasa Smart Light Strip, Multicolor",
|
||||
"dev_state": "normal",
|
||||
"deviceId": "0000000000000000000000000000000000000000",
|
||||
"disco_ver": "1.0",
|
||||
"err_code": 0,
|
||||
"hwId": "00000000000000000000000000000000",
|
||||
"hw_ver": "1.0",
|
||||
"is_color": 1,
|
||||
"is_dimmable": 1,
|
||||
"is_factory": false,
|
||||
"is_variable_color_temp": 1,
|
||||
"latitude_i": 0,
|
||||
"length": 16,
|
||||
"light_state": {
|
||||
"brightness": 50,
|
||||
"color_temp": 3630,
|
||||
"hue": 0,
|
||||
"mode": "normal",
|
||||
"on_off": 1,
|
||||
"saturation": 0
|
||||
},
|
||||
"lighting_effect_state": {
|
||||
"brightness": 50,
|
||||
"custom": 0,
|
||||
"enable": 0,
|
||||
"id": "",
|
||||
"name": ""
|
||||
},
|
||||
"longitude_i": 0,
|
||||
"mic_mac": "CC32E5230F55",
|
||||
"mic_type": "IOT.SMARTBULB",
|
||||
"model": "KL430(US)",
|
||||
"oemId": "00000000000000000000000000000000",
|
||||
"preferred_state": [],
|
||||
"rssi": -56,
|
||||
"status": "new",
|
||||
"sw_ver": "1.0.10 Build 200522 Rel.104340"
|
||||
}
|
||||
}
|
||||
}
|
@@ -355,7 +355,8 @@ class FakeTransportProtocol(TPLinkSmartHomeProtocol):
|
||||
light_state = self.proto["system"]["get_sysinfo"]["light_state"]
|
||||
# Our tests have light state off, so we simply return the dft_on_state when device is on.
|
||||
_LOGGER.debug("reporting light state: %s", light_state)
|
||||
if light_state["on_off"]:
|
||||
# TODO: hack to go around KL430 fixture differences
|
||||
if light_state["on_off"] and "dft_on_state" in light_state:
|
||||
return light_state["dft_on_state"]
|
||||
else:
|
||||
return light_state
|
||||
@@ -385,6 +386,11 @@ class FakeTransportProtocol(TPLinkSmartHomeProtocol):
|
||||
"get_light_state": light_state,
|
||||
"transition_light_state": transition_light_state,
|
||||
},
|
||||
# lightstrip follows the same payloads but uses different module & method
|
||||
"smartlife.iot.lightStrip": {
|
||||
"set_light_state": transition_light_state,
|
||||
"get_light_state": light_state,
|
||||
},
|
||||
"time": {
|
||||
"get_time": {
|
||||
"year": 2017,
|
||||
|
@@ -24,8 +24,10 @@ async def test_bulb_sysinfo(dev):
|
||||
|
||||
assert dev.model is not None
|
||||
|
||||
assert dev.device_type == DeviceType.Bulb
|
||||
assert dev.is_bulb
|
||||
# TODO: remove special handling for lightstrip
|
||||
if not dev.is_light_strip:
|
||||
assert dev.device_type == DeviceType.Bulb
|
||||
assert dev.is_bulb
|
||||
|
||||
|
||||
@bulb
|
||||
|
@@ -3,7 +3,7 @@ import pytest # type: ignore # https://github.com/pytest-dev/pytest/issues/3342
|
||||
|
||||
from kasa import DeviceType, Discover, SmartDevice, SmartDeviceException
|
||||
|
||||
from .conftest import bulb, dimmer, plug, pytestmark, strip
|
||||
from .conftest import bulb, dimmer, lightstrip, plug, pytestmark, strip
|
||||
|
||||
|
||||
@plug
|
||||
@@ -16,8 +16,10 @@ async def test_type_detection_plug(dev: SmartDevice):
|
||||
@bulb
|
||||
async def test_type_detection_bulb(dev: SmartDevice):
|
||||
d = Discover._get_device_class(dev.protocol.discovery_data)("localhost")
|
||||
assert d.is_bulb
|
||||
assert d.device_type == DeviceType.Bulb
|
||||
# TODO: light_strip is a special case for now to force bulb tests on it
|
||||
if not d.is_light_strip:
|
||||
assert d.is_bulb
|
||||
assert d.device_type == DeviceType.Bulb
|
||||
|
||||
|
||||
@strip
|
||||
@@ -34,6 +36,13 @@ async def test_type_detection_dimmer(dev: SmartDevice):
|
||||
assert d.device_type == DeviceType.Dimmer
|
||||
|
||||
|
||||
@lightstrip
|
||||
async def test_type_detection_lightstrip(dev: SmartDevice):
|
||||
d = Discover._get_device_class(dev.protocol.discovery_data)("localhost")
|
||||
assert d.is_light_strip
|
||||
assert d.device_type == DeviceType.LightStrip
|
||||
|
||||
|
||||
async def test_type_unknown():
|
||||
invalid_info = {"system": {"get_sysinfo": {"type": "nosuchtype"}}}
|
||||
with pytest.raises(SmartDeviceException):
|
||||
|
@@ -51,6 +51,15 @@ def test_dimmer_examples(mocker):
|
||||
assert not res["failed"]
|
||||
|
||||
|
||||
def test_lightstrip_examples(mocker):
|
||||
"""Test lightstrip examples."""
|
||||
p = get_device_for_file("KL430(US)_1.0.json")
|
||||
mocker.patch("kasa.smartlightstrip.SmartLightStrip", return_value=p)
|
||||
mocker.patch("kasa.smartlightstrip.SmartLightStrip.update")
|
||||
res = xdoctest.doctest_module("kasa.smartlightstrip", "all")
|
||||
assert not res["failed"]
|
||||
|
||||
|
||||
@pytest.mark.skipif(
|
||||
sys.version_info < (3, 8), reason="3.7 handles asyncio.run differently"
|
||||
)
|
||||
|
Reference in New Issue
Block a user