Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings
Merged
7 changes: 7 additions & 0 deletions 7 README.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,15 +112,18 @@ or the `parse_pcap.py` script contained inside the `devtools` directory.
* HS110

### Power Strips

* HS300
* KP303

### Wall switches

* HS200
* HS210
* HS220

### Bulbs

* LB100
* LB110
* LB120
Expand All @@ -131,6 +134,10 @@ or the `parse_pcap.py` script contained inside the `devtools` directory.
* KL120
* KL130

### Light strips

* KL430

**Contributions (be it adding missing features, fixing bugs or improving documentation) are more than welcome, feel free to submit pull requests!**

### Resources
Expand Down
1 change: 1 addition & 0 deletions 1 docs/source/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@ python-kasa documentation
smartplug
smartdimmer
smartstrip
smartlightstrip
6 changes: 6 additions & 0 deletions 6 docs/source/smartlightstrip.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Light strips
============

.. autoclass:: kasa.SmartLightStrip
:members:
:undoc-members:
2 changes: 2 additions & 0 deletions 2 kasa/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from kasa.smartbulb import SmartBulb
from kasa.smartdevice import DeviceType, EmeterStatus, SmartDevice
from kasa.smartdimmer import SmartDimmer
from kasa.smartlightstrip import SmartLightStrip
from kasa.smartplug import SmartPlug
from kasa.smartstrip import SmartStrip

Expand All @@ -35,4 +36,5 @@
"SmartPlug",
"SmartStrip",
"SmartDimmer",
"SmartLightStrip",
]
16 changes: 13 additions & 3 deletions 16 kasa/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,14 @@

import asyncclick as click

from kasa import Discover, SmartBulb, SmartDevice, SmartPlug, SmartStrip
from kasa import (
Discover,
SmartBulb,
SmartDevice,
SmartLightStrip,
SmartPlug,
SmartStrip,
)

click.anyio_backend = "asyncio"

Expand Down Expand Up @@ -37,10 +44,11 @@
@click.option("-d", "--debug", default=False, is_flag=True)
@click.option("--bulb", default=False, is_flag=True)
@click.option("--plug", default=False, is_flag=True)
@click.option("--lightstrip", default=False, is_flag=True)
@click.option("--strip", default=False, is_flag=True)
@click.version_option()
@click.pass_context
async def cli(ctx, host, alias, target, debug, bulb, plug, strip):
async def cli(ctx, host, alias, target, debug, bulb, plug, lightstrip, strip):
"""A tool for controlling TP-Link smart home devices.""" # noqa
if debug:
logging.basicConfig(level=logging.DEBUG)
Expand All @@ -64,7 +72,7 @@ async def cli(ctx, host, alias, target, debug, bulb, plug, strip):
await ctx.invoke(discover)
return
else:
if not bulb and not plug and not strip:
if not bulb and not plug and not strip and not lightstrip:
click.echo("No --strip nor --bulb nor --plug given, discovering..")
dev = await Discover.discover_single(host)
elif bulb:
Expand All @@ -73,6 +81,8 @@ async def cli(ctx, host, alias, target, debug, bulb, plug, strip):
dev = SmartPlug(host)
elif strip:
dev = SmartStrip(host)
elif lightstrip:
dev = SmartLightStrip(host)
else:
click.echo("Unable to detect type, use --strip or --bulb or --plug!")
return
Expand Down
9 changes: 9 additions & 0 deletions 9 kasa/discover.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from kasa.smartbulb import SmartBulb
from kasa.smartdevice import SmartDevice, SmartDeviceException
from kasa.smartdimmer import SmartDimmer
from kasa.smartlightstrip import SmartLightStrip
from kasa.smartplug import SmartPlug
from kasa.smartstrip import SmartStrip

Expand Down Expand Up @@ -227,11 +228,19 @@ def _get_device_class(info: dict) -> Type[SmartDevice]:
and "get_dimmer_parameters" in info["smartlife.iot.dimmer"]
):
return SmartDimmer

elif "smartplug" in type_.lower() and "children" in sysinfo:
return SmartStrip

elif "smartplug" in type_.lower():
if "children" in sysinfo:
return SmartStrip

return SmartPlug
elif "smartbulb" in type_.lower():
if "length" in sysinfo: # strips have length
return SmartLightStrip

return SmartBulb

raise SmartDeviceException("Unknown device type: %s", type_)
Expand Down
4 changes: 3 additions & 1 deletion 4 kasa/smartbulb.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"KL130": (2500, 9000),
r"KL120\(EU\)": (2700, 6500),
r"KL120\(US\)": (2700, 5000),
r"KL430\(US\)": (2500, 9000),
}


Expand Down Expand Up @@ -89,6 +90,7 @@ class SmartBulb(SmartDevice):
"""

LIGHT_SERVICE = "smartlife.iot.smartbulb.lightingservice"
SET_LIGHT_METHOD = "transition_light_state"

def __init__(self, host: str) -> None:
super().__init__(host=host)
Expand Down Expand Up @@ -190,7 +192,7 @@ async def set_light_state(self, state: Dict, *, transition: int = None) -> Dict:
state["ignore_default"] = 1

light_state = await self._query_helper(
self.LIGHT_SERVICE, "transition_light_state", state
self.LIGHT_SERVICE, self.SET_LIGHT_METHOD, state
)
return light_state

Expand Down
6 changes: 6 additions & 0 deletions 6 kasa/smartdevice.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ class DeviceType(Enum):
Bulb = 2
Strip = 3
Dimmer = 4
LightStrip = 5
Unknown = -1


Expand Down Expand Up @@ -702,6 +703,11 @@ def is_bulb(self) -> bool:
"""Return True if the device is a bulb."""
return self._device_type == DeviceType.Bulb

@property
def is_light_strip(self) -> bool:
"""Return True if the device is a led strip."""
return self._device_type == DeviceType.LightStrip

@property
def is_plug(self) -> bool:
"""Return True if the device is a plug."""
Expand Down
75 changes: 75 additions & 0 deletions 75 kasa/smartlightstrip.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
"""Module for light strips (KL430)."""
from typing import Any, Dict

from .smartbulb import SmartBulb
from .smartdevice import DeviceType, requires_update


class SmartLightStrip(SmartBulb):
"""Representation of a TP-Link Smart light strip.

Light strips work similarly to bulbs, but use a different service for controlling,
and expose some extra information (such as length and active effect).
This class extends :class:`SmartBulb` interface.

Examples:
>>> import asyncio
>>> strip = SmartLightStrip("127.0.0.1")
>>> asyncio.run(strip.update())
>>> print(strip.alias)
KL430 pantry lightstrip

Getting the length of the strip:

>>> strip.length
16

Currently active effect:

>>> strip.effect
{'brightness': 50, 'custom': 0, 'enable': 0, 'id': '', 'name': ''}

.. note::
The device supports some features that are not currently implemented,
feel free to find out how to control them and create a PR!


See :class:`SmartBulb` for more examples.
"""

LIGHT_SERVICE = "smartlife.iot.lightStrip"
SET_LIGHT_METHOD = "set_light_state"

def __init__(self, host: str) -> None:
super().__init__(host)
self._device_type = DeviceType.LightStrip

@property # type: ignore
@requires_update
def length(self) -> int:
"""Return length of the strip."""
return self.sys_info["length"]

@property # type: ignore
@requires_update
def effect(self) -> Dict:
"""Return effect state.

Example:
{'brightness': 50,
'custom': 0,
'enable': 0,
'id': '',
'name': ''}
"""
return self.sys_info["lighting_effect_state"]

@property # type: ignore
@requires_update
def state_information(self) -> Dict[str, Any]:
"""Return strip specific state information."""
info = super().state_information

info["Length"] = self.length

return info
30 changes: 25 additions & 5 deletions 30 kasa/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,14 @@

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

Expand All @@ -17,9 +24,11 @@
)


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"}
Expand Down Expand Up @@ -65,9 +74,12 @@ def name_for_filename(x):
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:
Expand Down Expand Up @@ -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
Expand Down
70 changes: 70 additions & 0 deletions 70 kasa/tests/fixtures/KL430(US)_1.0.json
Original file line number Diff line number Diff line change
@@ -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"
}
}
}
Loading
Morty Proxy This is a proxified and sanitized view of the page, visit original site.