mirror of
https://github.com/python-kasa/python-kasa.git
synced 2024-12-22 19:23:34 +00:00
Change state_information to return feature values (#804)
This changes `state_information` to return the names and values of all defined features. It was originally a "temporary" hack to show some extra, device-specific information in the cli tool, but now that we have device-defined features we can leverage them.
This commit is contained in:
parent
d63f43a230
commit
35dbda7049
11
kasa/cli.py
11
kasa/cli.py
@ -609,16 +609,7 @@ async def state(ctx, dev: Device):
|
|||||||
echo(f"\tMAC (rssi): {dev.mac} ({dev.rssi})")
|
echo(f"\tMAC (rssi): {dev.mac} ({dev.rssi})")
|
||||||
echo(f"\tLocation: {dev.location}")
|
echo(f"\tLocation: {dev.location}")
|
||||||
|
|
||||||
echo("\n\t[bold]== Device specific information ==[/bold]")
|
echo("\n\t[bold]== Device-specific information == [/bold]")
|
||||||
for info_name, info_data in dev.state_information.items():
|
|
||||||
if isinstance(info_data, list):
|
|
||||||
echo(f"\t{info_name}:")
|
|
||||||
for item in info_data:
|
|
||||||
echo(f"\t\t{item}")
|
|
||||||
else:
|
|
||||||
echo(f"\t{info_name}: {info_data}")
|
|
||||||
|
|
||||||
echo("\n\t[bold]== Features == [/bold]")
|
|
||||||
for id_, feature in dev.features.items():
|
for id_, feature in dev.features.items():
|
||||||
echo(f"\t{feature.name} ({id_}): {feature.value}")
|
echo(f"\t{feature.name} ({id_}): {feature.value}")
|
||||||
|
|
||||||
|
@ -304,9 +304,9 @@ class Device(ABC):
|
|||||||
"""Return all the internal state data."""
|
"""Return all the internal state data."""
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@abstractmethod
|
|
||||||
def state_information(self) -> Dict[str, Any]:
|
def state_information(self) -> Dict[str, Any]:
|
||||||
"""Return the key state information."""
|
"""Return available features and their values."""
|
||||||
|
return {feat.name: feat.value for feat in self._features.values()}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def features(self) -> Dict[str, Feature]:
|
def features(self) -> Dict[str, Feature]:
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import Any, Dict, List, Optional, cast
|
from typing import Dict, List, Optional, cast
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from pydantic.v1 import BaseModel, Field, root_validator
|
from pydantic.v1 import BaseModel, Field, root_validator
|
||||||
@ -462,23 +462,6 @@ class IotBulb(IotDevice, Bulb):
|
|||||||
light_state = {"brightness": brightness}
|
light_state = {"brightness": brightness}
|
||||||
return await self.set_light_state(light_state, transition=transition)
|
return await self.set_light_state(light_state, transition=transition)
|
||||||
|
|
||||||
@property # type: ignore
|
|
||||||
@requires_update
|
|
||||||
def state_information(self) -> Dict[str, Any]:
|
|
||||||
"""Return bulb-specific state information."""
|
|
||||||
info: Dict[str, Any] = {
|
|
||||||
"Brightness": self.brightness,
|
|
||||||
"Is dimmable": self.is_dimmable,
|
|
||||||
}
|
|
||||||
if self.is_variable_color_temp:
|
|
||||||
info["Color temperature"] = self.color_temp
|
|
||||||
info["Valid temperature range"] = self.valid_temperature_range
|
|
||||||
if self.is_color:
|
|
||||||
info["HSV"] = self.hsv
|
|
||||||
info["Presets"] = self.presets
|
|
||||||
|
|
||||||
return info
|
|
||||||
|
|
||||||
@property # type: ignore
|
@property # type: ignore
|
||||||
@requires_update
|
@requires_update
|
||||||
def is_on(self) -> bool:
|
def is_on(self) -> bool:
|
||||||
|
@ -615,12 +615,6 @@ class IotDevice(Device):
|
|||||||
|
|
||||||
return datetime.now().replace(microsecond=0) - timedelta(seconds=on_time)
|
return datetime.now().replace(microsecond=0) - timedelta(seconds=on_time)
|
||||||
|
|
||||||
@property # type: ignore
|
|
||||||
@requires_update
|
|
||||||
def state_information(self) -> Dict[str, Any]:
|
|
||||||
"""Return device-type specific, end-user friendly state information."""
|
|
||||||
raise NotImplementedError("Device subclass needs to implement this.")
|
|
||||||
|
|
||||||
@property # type: ignore
|
@property # type: ignore
|
||||||
@requires_update
|
@requires_update
|
||||||
def device_id(self) -> str:
|
def device_id(self) -> str:
|
||||||
|
@ -232,12 +232,3 @@ class IotDimmer(IotPlug):
|
|||||||
"""Whether the switch supports brightness changes."""
|
"""Whether the switch supports brightness changes."""
|
||||||
sys_info = self.sys_info
|
sys_info = self.sys_info
|
||||||
return "brightness" in sys_info
|
return "brightness" in sys_info
|
||||||
|
|
||||||
@property # type: ignore
|
|
||||||
@requires_update
|
|
||||||
def state_information(self) -> Dict[str, Any]:
|
|
||||||
"""Return switch-specific state information."""
|
|
||||||
info = super().state_information
|
|
||||||
info["Brightness"] = self.brightness
|
|
||||||
|
|
||||||
return info
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
"""Module for light strips (KL430)."""
|
"""Module for light strips (KL430)."""
|
||||||
from typing import Any, Dict, List, Optional
|
from typing import Dict, List, Optional
|
||||||
|
|
||||||
from ..device_type import DeviceType
|
from ..device_type import DeviceType
|
||||||
from ..deviceconfig import DeviceConfig
|
from ..deviceconfig import DeviceConfig
|
||||||
@ -84,18 +84,6 @@ class IotLightStrip(IotBulb):
|
|||||||
"""
|
"""
|
||||||
return EFFECT_NAMES_V1 if self.has_effects else None
|
return EFFECT_NAMES_V1 if self.has_effects else None
|
||||||
|
|
||||||
@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
|
|
||||||
if self.has_effects:
|
|
||||||
info["Effect"] = self.effect["name"]
|
|
||||||
|
|
||||||
return info
|
|
||||||
|
|
||||||
@requires_update
|
@requires_update
|
||||||
async def set_effect(
|
async def set_effect(
|
||||||
self,
|
self,
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
"""Module for smart plugs (HS100, HS110, ..)."""
|
"""Module for smart plugs (HS100, HS110, ..)."""
|
||||||
import logging
|
import logging
|
||||||
from typing import Any, Dict, Optional
|
from typing import Optional
|
||||||
|
|
||||||
from ..device_type import DeviceType
|
from ..device_type import DeviceType
|
||||||
from ..deviceconfig import DeviceConfig
|
from ..deviceconfig import DeviceConfig
|
||||||
@ -99,12 +99,6 @@ class IotPlug(IotDevice):
|
|||||||
"system", "set_led_off", {"off": int(not state)}
|
"system", "set_led_off", {"off": int(not state)}
|
||||||
)
|
)
|
||||||
|
|
||||||
@property # type: ignore
|
|
||||||
@requires_update
|
|
||||||
def state_information(self) -> Dict[str, Any]:
|
|
||||||
"""Return switch-specific state information."""
|
|
||||||
return {}
|
|
||||||
|
|
||||||
|
|
||||||
class IotWallSwitch(IotPlug):
|
class IotWallSwitch(IotPlug):
|
||||||
"""Representation of a TP-Link Smart Wall Switch."""
|
"""Representation of a TP-Link Smart Wall Switch."""
|
||||||
|
@ -154,19 +154,6 @@ class IotStrip(IotDevice):
|
|||||||
"""Set the state of the led (night mode)."""
|
"""Set the state of the led (night mode)."""
|
||||||
await self._query_helper("system", "set_led_off", {"off": int(not state)})
|
await self._query_helper("system", "set_led_off", {"off": int(not state)})
|
||||||
|
|
||||||
@property # type: ignore
|
|
||||||
@requires_update
|
|
||||||
def state_information(self) -> Dict[str, Any]:
|
|
||||||
"""Return strip-specific state information.
|
|
||||||
|
|
||||||
:return: Strip information dict, keys in user-presentable form.
|
|
||||||
"""
|
|
||||||
return {
|
|
||||||
"LED state": self.led,
|
|
||||||
"Childs count": len(self.children),
|
|
||||||
"On since": self.on_since,
|
|
||||||
}
|
|
||||||
|
|
||||||
async def current_consumption(self) -> float:
|
async def current_consumption(self) -> float:
|
||||||
"""Get the current power consumption in watts."""
|
"""Get the current power consumption in watts."""
|
||||||
return sum([await plug.current_consumption() for plug in self.children])
|
return sum([await plug.current_consumption() for plug in self.children])
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
"""Module for tapo-branded smart bulbs (L5**)."""
|
"""Module for tapo-branded smart bulbs (L5**)."""
|
||||||
from typing import Any, Dict, List, Optional
|
from typing import Dict, List, Optional
|
||||||
|
|
||||||
from ..bulb import Bulb
|
from ..bulb import Bulb
|
||||||
from ..exceptions import KasaException
|
from ..exceptions import KasaException
|
||||||
@ -238,25 +238,6 @@ class SmartBulb(SmartDevice, Bulb):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@property # type: ignore
|
|
||||||
def state_information(self) -> Dict[str, Any]:
|
|
||||||
"""Return bulb-specific state information."""
|
|
||||||
info: Dict[str, Any] = {
|
|
||||||
# TODO: re-enable after we don't inherit from smartbulb
|
|
||||||
# **super().state_information
|
|
||||||
"Is dimmable": self.is_dimmable,
|
|
||||||
}
|
|
||||||
if self.is_dimmable:
|
|
||||||
info["Brightness"] = self.brightness
|
|
||||||
if self.is_variable_color_temp:
|
|
||||||
info["Color temperature"] = self.color_temp
|
|
||||||
info["Valid temperature range"] = self.valid_temperature_range
|
|
||||||
if self.is_color:
|
|
||||||
info["HSV"] = self.hsv
|
|
||||||
info["Presets"] = self.presets
|
|
||||||
|
|
||||||
return info
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def presets(self) -> List[BulbPreset]:
|
def presets(self) -> List[BulbPreset]:
|
||||||
"""Return a list of available bulb setting presets."""
|
"""Return a list of available bulb setting presets."""
|
||||||
|
@ -334,15 +334,6 @@ class SmartDevice(Device):
|
|||||||
ssid = base64.b64decode(ssid).decode() if ssid else "No SSID"
|
ssid = base64.b64decode(ssid).decode() if ssid else "No SSID"
|
||||||
return ssid
|
return ssid
|
||||||
|
|
||||||
@property
|
|
||||||
def state_information(self) -> Dict[str, Any]:
|
|
||||||
"""Return the key state information."""
|
|
||||||
return {
|
|
||||||
"overheated": self._info.get("overheated"),
|
|
||||||
"signal_level": self._info.get("signal_level"),
|
|
||||||
"SSID": self.ssid,
|
|
||||||
}
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def has_emeter(self) -> bool:
|
def has_emeter(self) -> bool:
|
||||||
"""Return if the device has emeter."""
|
"""Return if the device has emeter."""
|
||||||
|
@ -121,6 +121,61 @@ TIME_MODULE = {
|
|||||||
"set_timezone": None,
|
"set_timezone": None,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CLOUD_MODULE = {
|
||||||
|
"get_info": {
|
||||||
|
"username": "",
|
||||||
|
"server": "devs.tplinkcloud.com",
|
||||||
|
"binded": 0,
|
||||||
|
"cld_connection": 0,
|
||||||
|
"illegalType": -1,
|
||||||
|
"stopConnect": -1,
|
||||||
|
"tcspStatus": -1,
|
||||||
|
"fwDlPage": "",
|
||||||
|
"tcspInfo": "",
|
||||||
|
"fwNotifyType": 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
AMBIENT_MODULE = {
|
||||||
|
"get_current_brt": {"value": 26, "err_code": 0},
|
||||||
|
"get_config": {
|
||||||
|
"devs": [
|
||||||
|
{
|
||||||
|
"hw_id": 0,
|
||||||
|
"enable": 0,
|
||||||
|
"dark_index": 1,
|
||||||
|
"min_adc": 0,
|
||||||
|
"max_adc": 2450,
|
||||||
|
"level_array": [
|
||||||
|
{"name": "cloudy", "adc": 490, "value": 20},
|
||||||
|
{"name": "overcast", "adc": 294, "value": 12},
|
||||||
|
{"name": "dawn", "adc": 222, "value": 9},
|
||||||
|
{"name": "twilight", "adc": 222, "value": 9},
|
||||||
|
{"name": "total darkness", "adc": 111, "value": 4},
|
||||||
|
{"name": "custom", "adc": 2400, "value": 97},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ver": "1.0",
|
||||||
|
"err_code": 0,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
MOTION_MODULE = {
|
||||||
|
"get_config": {
|
||||||
|
"enable": 0,
|
||||||
|
"version": "1.0",
|
||||||
|
"trigger_index": 2,
|
||||||
|
"cold_time": 60000,
|
||||||
|
"min_adc": 0,
|
||||||
|
"max_adc": 4095,
|
||||||
|
"array": [80, 50, 20, 0],
|
||||||
|
"err_code": 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class FakeIotProtocol(IotProtocol):
|
class FakeIotProtocol(IotProtocol):
|
||||||
def __init__(self, info):
|
def __init__(self, info):
|
||||||
@ -306,8 +361,10 @@ class FakeIotProtocol(IotProtocol):
|
|||||||
"set_brightness": set_hs220_brightness,
|
"set_brightness": set_hs220_brightness,
|
||||||
"set_dimmer_transition": set_hs220_dimmer_transition,
|
"set_dimmer_transition": set_hs220_dimmer_transition,
|
||||||
},
|
},
|
||||||
"smartlife.iot.LAS": {},
|
"smartlife.iot.LAS": AMBIENT_MODULE,
|
||||||
"smartlife.iot.PIR": {},
|
"smartlife.iot.PIR": MOTION_MODULE,
|
||||||
|
"cnCloud": CLOUD_MODULE,
|
||||||
|
"smartlife.iot.common.cloud": CLOUD_MODULE,
|
||||||
}
|
}
|
||||||
|
|
||||||
async def query(self, request, port=9999):
|
async def query(self, request, port=9999):
|
||||||
|
@ -42,11 +42,8 @@ async def test_bulb_sysinfo(dev: Bulb):
|
|||||||
|
|
||||||
@bulb
|
@bulb
|
||||||
async def test_state_attributes(dev: Bulb):
|
async def test_state_attributes(dev: Bulb):
|
||||||
assert "Brightness" in dev.state_information
|
assert "Cloud connection" in dev.state_information
|
||||||
assert dev.state_information["Brightness"] == dev.brightness
|
assert isinstance(dev.state_information["Cloud connection"], bool)
|
||||||
|
|
||||||
assert "Is dimmable" in dev.state_information
|
|
||||||
assert dev.state_information["Is dimmable"] == dev.is_dimmable
|
|
||||||
|
|
||||||
|
|
||||||
@bulb_iot
|
@bulb_iot
|
||||||
@ -114,6 +111,7 @@ async def test_invalid_hsv(dev: Bulb, turn_on):
|
|||||||
|
|
||||||
|
|
||||||
@color_bulb
|
@color_bulb
|
||||||
|
@pytest.mark.skip("requires color feature")
|
||||||
async def test_color_state_information(dev: Bulb):
|
async def test_color_state_information(dev: Bulb):
|
||||||
assert "HSV" in dev.state_information
|
assert "HSV" in dev.state_information
|
||||||
assert dev.state_information["HSV"] == dev.hsv
|
assert dev.state_information["HSV"] == dev.hsv
|
||||||
@ -130,6 +128,7 @@ async def test_hsv_on_non_color(dev: Bulb):
|
|||||||
|
|
||||||
|
|
||||||
@variable_temp
|
@variable_temp
|
||||||
|
@pytest.mark.skip("requires colortemp module")
|
||||||
async def test_variable_temp_state_information(dev: Bulb):
|
async def test_variable_temp_state_information(dev: Bulb):
|
||||||
assert "Color temperature" in dev.state_information
|
assert "Color temperature" in dev.state_information
|
||||||
assert dev.state_information["Color temperature"] == dev.color_temp
|
assert dev.state_information["Color temperature"] == dev.color_temp
|
||||||
|
@ -28,7 +28,6 @@ async def test_effects_lightstrip_set_effect(dev: IotLightStrip):
|
|||||||
|
|
||||||
await dev.set_effect("Candy Cane")
|
await dev.set_effect("Candy Cane")
|
||||||
assert dev.effect["name"] == "Candy Cane"
|
assert dev.effect["name"] == "Candy Cane"
|
||||||
assert dev.state_information["Effect"] == "Candy Cane"
|
|
||||||
|
|
||||||
|
|
||||||
@lightstrip
|
@lightstrip
|
||||||
|
Loading…
Reference in New Issue
Block a user