mirror of
https://github.com/python-kasa/python-kasa.git
synced 2025-03-13 20:11:32 +00:00
Optimize I/O access (#59)
* Optimize I/O access A single update() will now fetch information from all interesting modules, including the current device state and the emeter information. In practice, this will allow dropping the number of I/O reqs per homeassistant update cycle to 1, which is paramount at least for bulbs which are very picky about sequential accesses. This can be further extend to other modules/methods, if needed. Currently fetched data: * sysinfo * realtime, today's and this months emeter stats New properties: * emeter_realtime: return the most recent emeter information, update()-version of get_emeter_realtime() * emeter_today: returning today's energy consumption * emeter_this_month: same for this month Other changes: * Accessing @requires_update properties will cause SmartDeviceException if the device has not ever been update()d * Fix __repr__ for devices that haven't been updated * Smartbulb uses now the state data from get_sysinfo instead of separately querying the bulb service * SmartStrip's state_information no longer lists onsince for separate plugs * The above mentioned properties are now printed out by cli * Simplify is_on handling for bulbs * remove implicit updates, return device responses for actions, update README.md instructions. fixes #61
This commit is contained in:
parent
012436c494
commit
836f1701b9
19
README.md
19
README.md
@ -109,10 +109,13 @@ The commands are straightforward, so feel free to check `--help` for instruction
|
||||
# Library usage
|
||||
|
||||
The property accesses use the data obtained before by awaiting `update()`.
|
||||
The values are cached until the next update call.
|
||||
Each method changing the state of the device will automatically update the cached state.
|
||||
The values are cached until the next update call. In practice this means that property accesses do no I/O and are dependent, while I/O producing methods need to be awaited.
|
||||
|
||||
Errors are raised as `SmartDeviceException` instances for the user to handle.
|
||||
Methods changing the state of the device do not invalidate the cache (i.e., there is no implicit `update()`).
|
||||
You can assume that the operation has succeeded if no exception is raised.
|
||||
These methods will return the device response, which can be useful for some use cases.
|
||||
|
||||
Errors are raised as `SmartDeviceException` instances for the library user to handle.
|
||||
|
||||
## Discovering devices
|
||||
|
||||
@ -160,6 +163,13 @@ await plug.turn_on()
|
||||
```
|
||||
|
||||
## Getting emeter status (if applicable)
|
||||
The `update()` call will automatically fetch the following emeter information:
|
||||
* Current consumption (accessed through `emeter_realtime` property)
|
||||
* Today's consumption (`emeter_today`)
|
||||
* This month's consumption (`emeter_this_month`)
|
||||
|
||||
You can also request this information separately:
|
||||
|
||||
```python
|
||||
print("Current consumption: %s" % await plug.get_emeter_realtime())
|
||||
print("Per day: %s" % await plug.get_emeter_daily(year=2016, month=12))
|
||||
@ -182,6 +192,7 @@ asyncio.run(bulb.update())
|
||||
|
||||
if bulb.is_dimmable:
|
||||
asyncio.run(bulb.set_brightness(100))
|
||||
asyncio.run(bulb.update())
|
||||
print(bulb.brightness)
|
||||
```
|
||||
|
||||
@ -189,6 +200,7 @@ if bulb.is_dimmable:
|
||||
```python
|
||||
if bulb.is_variable_color_temp:
|
||||
await bulb.set_color_temp(3000)
|
||||
await bulb.update()
|
||||
print(bulb.color_temp)
|
||||
```
|
||||
|
||||
@ -199,6 +211,7 @@ Hue is given in degrees (0-360) and saturation and value in percentage.
|
||||
```python
|
||||
if bulb.is_color:
|
||||
await bulb.set_hsv(180, 100, 100) # set to cyan
|
||||
await bulb.update()
|
||||
print(bulb.hsv)
|
||||
```
|
||||
|
||||
|
14
kasa/cli.py
14
kasa/cli.py
@ -292,7 +292,7 @@ async def raw_command(dev: SmartDevice, module, command, parameters):
|
||||
@click.option("--year", type=click.DateTime(["%Y"]), default=None, required=False)
|
||||
@click.option("--month", type=click.DateTime(["%Y-%m"]), default=None, required=False)
|
||||
@click.option("--erase", is_flag=True)
|
||||
async def emeter(dev, year, month, erase):
|
||||
async def emeter(dev: SmartDevice, year, month, erase):
|
||||
"""Query emeter for historical consumption."""
|
||||
click.echo(click.style("== Emeter ==", bold=True))
|
||||
await dev.update()
|
||||
@ -311,17 +311,21 @@ async def emeter(dev, year, month, erase):
|
||||
elif month:
|
||||
click.echo(f"== For month {month.month} of {month.year} ==")
|
||||
emeter_status = await dev.get_emeter_daily(year=month.year, month=month.month)
|
||||
|
||||
else:
|
||||
emeter_status = await dev.get_emeter_realtime()
|
||||
click.echo("== Current State ==")
|
||||
emeter_status = dev.emeter_realtime
|
||||
|
||||
if isinstance(emeter_status, list):
|
||||
for plug in emeter_status:
|
||||
index = emeter_status.index(plug) + 1
|
||||
click.echo(f"Plug {index}: {plug}")
|
||||
else:
|
||||
click.echo(str(emeter_status))
|
||||
click.echo("Current: %s A" % emeter_status["current"])
|
||||
click.echo("Voltage: %s V" % emeter_status["voltage"])
|
||||
click.echo("Power: %s W" % emeter_status["power"])
|
||||
click.echo("Total consumption: %s kWh" % emeter_status["total"])
|
||||
|
||||
click.echo("Today: %s kWh" % dev.emeter_today)
|
||||
click.echo("This month: %s kWh" % dev.emeter_this_month)
|
||||
|
||||
|
||||
@cli.command()
|
||||
|
@ -1,6 +1,6 @@
|
||||
"""Module for bulbs."""
|
||||
import re
|
||||
from typing import Any, Dict, Optional, Tuple
|
||||
from typing import Any, Dict, Tuple, cast
|
||||
|
||||
from kasa.smartdevice import (
|
||||
DeviceType,
|
||||
@ -33,6 +33,7 @@ class SmartBulb(SmartDevice):
|
||||
|
||||
# change state of bulb
|
||||
await p.turn_on()
|
||||
await p.update()
|
||||
assert p.is_on
|
||||
await p.turn_off()
|
||||
|
||||
@ -44,6 +45,7 @@ class SmartBulb(SmartDevice):
|
||||
print("we got color!")
|
||||
# set the color to an HSV tuple
|
||||
await p.set_hsv(180, 100, 100)
|
||||
await p.update()
|
||||
# get the current HSV value
|
||||
print(p.hsv)
|
||||
|
||||
@ -51,6 +53,7 @@ class SmartBulb(SmartDevice):
|
||||
if p.is_variable_color_temp:
|
||||
# set the color temperature in Kelvin
|
||||
await p.set_color_temp(3000)
|
||||
await p.update()
|
||||
|
||||
# get the current color temperature
|
||||
print(p.color_temp)
|
||||
@ -59,6 +62,7 @@ class SmartBulb(SmartDevice):
|
||||
if p.is_dimmable:
|
||||
# set the bulb to 50% brightness
|
||||
await p.set_brightness(50)
|
||||
await p.update()
|
||||
|
||||
# check the current brightness
|
||||
print(p.brightness)
|
||||
@ -74,7 +78,6 @@ class SmartBulb(SmartDevice):
|
||||
super().__init__(host=host)
|
||||
self.emeter_type = "smartlife.iot.common.emeter"
|
||||
self._device_type = DeviceType.Bulb
|
||||
self._light_state = None
|
||||
|
||||
@property # type: ignore
|
||||
@requires_update
|
||||
@ -126,19 +129,28 @@ class SmartBulb(SmartDevice):
|
||||
return temp_range
|
||||
return (0, 0)
|
||||
|
||||
async def update(self):
|
||||
"""Update `sys_info and `light_state`."""
|
||||
self._sys_info = await self.get_sys_info()
|
||||
self._light_state = await self.get_light_state()
|
||||
|
||||
@property # type: ignore
|
||||
@requires_update
|
||||
def light_state(self) -> Optional[Dict[str, Dict]]:
|
||||
def light_state(self) -> Dict[str, str]:
|
||||
"""Query the light state."""
|
||||
return self._light_state
|
||||
light_state = self._last_update["system"]["get_sysinfo"]["light_state"]
|
||||
if light_state is None:
|
||||
raise SmartDeviceException(
|
||||
"The device has no light_state or you have not called update()"
|
||||
)
|
||||
|
||||
# if the bulb is off, its state is stored under a different key
|
||||
# as is_on property depends on on_off itself, we check it here manually
|
||||
is_on = light_state["on_off"]
|
||||
if not is_on:
|
||||
off_state = {**light_state["dft_on_state"], "on_off": is_on}
|
||||
return cast(dict, off_state)
|
||||
|
||||
return light_state
|
||||
|
||||
async def get_light_state(self) -> Dict[str, Dict]:
|
||||
"""Query the light state."""
|
||||
# TODO: add warning and refer to use light.state?
|
||||
return await self._query_helper(self.LIGHT_SERVICE, "get_light_state")
|
||||
|
||||
async def set_light_state(self, state: Dict) -> Dict:
|
||||
@ -146,7 +158,6 @@ class SmartBulb(SmartDevice):
|
||||
light_state = await self._query_helper(
|
||||
self.LIGHT_SERVICE, "transition_light_state", state
|
||||
)
|
||||
await self.update()
|
||||
return light_state
|
||||
|
||||
@property # type: ignore
|
||||
@ -160,15 +171,11 @@ class SmartBulb(SmartDevice):
|
||||
if not self.is_color:
|
||||
raise SmartDeviceException("Bulb does not support color.")
|
||||
|
||||
light_state = self.light_state
|
||||
if not self.is_on:
|
||||
hue = light_state["dft_on_state"]["hue"]
|
||||
saturation = light_state["dft_on_state"]["saturation"]
|
||||
value = light_state["dft_on_state"]["brightness"]
|
||||
else:
|
||||
hue = light_state["hue"]
|
||||
saturation = light_state["saturation"]
|
||||
value = light_state["brightness"]
|
||||
light_state = cast(dict, self.light_state)
|
||||
|
||||
hue = light_state["hue"]
|
||||
saturation = light_state["saturation"]
|
||||
value = light_state["brightness"]
|
||||
|
||||
return hue, saturation, value
|
||||
|
||||
@ -222,10 +229,7 @@ class SmartBulb(SmartDevice):
|
||||
raise SmartDeviceException("Bulb does not support colortemp.")
|
||||
|
||||
light_state = self.light_state
|
||||
if not self.is_on:
|
||||
return int(light_state["dft_on_state"]["color_temp"])
|
||||
else:
|
||||
return int(light_state["color_temp"])
|
||||
return int(light_state["color_temp"])
|
||||
|
||||
@requires_update
|
||||
async def set_color_temp(self, temp: int) -> None:
|
||||
@ -258,10 +262,7 @@ class SmartBulb(SmartDevice):
|
||||
raise SmartDeviceException("Bulb is not dimmable.")
|
||||
|
||||
light_state = self.light_state
|
||||
if not self.is_on:
|
||||
return int(light_state["dft_on_state"]["brightness"])
|
||||
else:
|
||||
return int(light_state["brightness"])
|
||||
return int(light_state["brightness"])
|
||||
|
||||
@requires_update
|
||||
async def set_brightness(self, brightness: int) -> None:
|
||||
|
@ -99,7 +99,10 @@ def requires_update(f):
|
||||
@functools.wraps(f)
|
||||
async def wrapped(*args, **kwargs):
|
||||
self = args[0]
|
||||
assert self._sys_info is not None
|
||||
if self._last_update is None:
|
||||
raise SmartDeviceException(
|
||||
"You need to await update() to access the data"
|
||||
)
|
||||
return await f(*args, **kwargs)
|
||||
|
||||
else:
|
||||
@ -107,7 +110,10 @@ def requires_update(f):
|
||||
@functools.wraps(f)
|
||||
def wrapped(*args, **kwargs):
|
||||
self = args[0]
|
||||
assert self._sys_info is not None
|
||||
if self._last_update is None:
|
||||
raise SmartDeviceException(
|
||||
"You need to await update() to access the data"
|
||||
)
|
||||
return f(*args, **kwargs)
|
||||
|
||||
f.requires_update = True
|
||||
@ -129,7 +135,20 @@ class SmartDevice:
|
||||
self.emeter_type = "emeter"
|
||||
_LOGGER.debug("Initializing %s of type %s", self.host, type(self))
|
||||
self._device_type = DeviceType.Unknown
|
||||
self._sys_info: Optional[Dict] = None
|
||||
# TODO: typing Any is just as using Optional[Dict] would require separate checks in
|
||||
# accessors. the @updated_required decorator does not ensure mypy that these
|
||||
# are not accessed incorrectly.
|
||||
self._last_update: Any = None
|
||||
self._sys_info: Any = None # TODO: this is here to avoid changing tests
|
||||
|
||||
def _create_request(
|
||||
self, target: str, cmd: str, arg: Optional[Dict] = None, child_ids=None
|
||||
):
|
||||
request: Dict[str, Any] = {target: {cmd: arg}}
|
||||
if child_ids is not None:
|
||||
request = {"context": {"child_ids": child_ids}, target: {cmd: arg}}
|
||||
|
||||
return request
|
||||
|
||||
async def _query_helper(
|
||||
self, target: str, cmd: str, arg: Optional[Dict] = None, child_ids=None
|
||||
@ -139,13 +158,12 @@ class SmartDevice:
|
||||
:param target: Target system {system, time, emeter, ..}
|
||||
:param cmd: Command to execute
|
||||
:param arg: JSON object passed as parameter to the command
|
||||
:param child_ids: ids of child devices
|
||||
:return: Unwrapped result for the call.
|
||||
:rtype: dict
|
||||
:raises SmartDeviceException: if command was not executed correctly
|
||||
"""
|
||||
request: Dict[str, Any] = {target: {cmd: arg}}
|
||||
if child_ids is not None:
|
||||
request = {"context": {"child_ids": child_ids}, target: {cmd: arg}}
|
||||
request = self._create_request(target, cmd, arg, child_ids)
|
||||
|
||||
try:
|
||||
response = await self.protocol.query(host=self.host, request=request)
|
||||
@ -196,7 +214,15 @@ class SmartDevice:
|
||||
|
||||
Needed for methods that are decorated with `requires_update`.
|
||||
"""
|
||||
self._sys_info = await self.get_sys_info()
|
||||
req = {}
|
||||
req.update(self._create_request("system", "get_sysinfo"))
|
||||
|
||||
# Check for emeter if we were never updated, or if the device has emeter
|
||||
if self._last_update is None or self.has_emeter:
|
||||
req.update(self._create_emeter_request())
|
||||
self._last_update = await self.protocol.query(self.host, req)
|
||||
# TODO: keep accessible for tests
|
||||
self._sys_info = self._last_update["system"]["get_sysinfo"]
|
||||
|
||||
@property # type: ignore
|
||||
@requires_update
|
||||
@ -207,8 +233,7 @@ class SmartDevice:
|
||||
:rtype dict
|
||||
:raises SmartDeviceException: on error
|
||||
"""
|
||||
assert self._sys_info is not None
|
||||
return self._sys_info
|
||||
return self._sys_info # type: ignore
|
||||
|
||||
@property # type: ignore
|
||||
@requires_update
|
||||
@ -239,8 +264,7 @@ class SmartDevice:
|
||||
:param alias: New alias (name)
|
||||
:raises SmartDeviceException: on error
|
||||
"""
|
||||
await self._query_helper("system", "set_dev_alias", {"alias": alias})
|
||||
await self.update()
|
||||
return await self._query_helper("system", "set_dev_alias", {"alias": alias})
|
||||
|
||||
async def get_icon(self) -> Dict:
|
||||
"""Return device icon.
|
||||
@ -415,10 +439,17 @@ class SmartDevice:
|
||||
:param str mac: mac in hexadecimal with colons, e.g. 01:23:45:67:89:ab
|
||||
:raises SmartDeviceException: on error
|
||||
"""
|
||||
await self._query_helper("system", "set_mac_addr", {"mac": mac})
|
||||
await self.update()
|
||||
return await self._query_helper("system", "set_mac_addr", {"mac": mac})
|
||||
|
||||
@property # type: ignore
|
||||
@requires_update
|
||||
def emeter_realtime(self) -> EmeterStatus:
|
||||
"""Return current emeter status."""
|
||||
if not self.has_emeter:
|
||||
raise SmartDeviceException("Device has no emeter")
|
||||
|
||||
return EmeterStatus(self._last_update[self.emeter_type]["get_realtime"])
|
||||
|
||||
async def get_emeter_realtime(self) -> EmeterStatus:
|
||||
"""Retrieve current energy readings.
|
||||
|
||||
@ -431,7 +462,83 @@ class SmartDevice:
|
||||
|
||||
return EmeterStatus(await self._query_helper(self.emeter_type, "get_realtime"))
|
||||
|
||||
def _create_emeter_request(self, year: int = None, month: int = None):
|
||||
"""Create a Internal method for building a request for all emeter statistics at once."""
|
||||
if year is None:
|
||||
year = datetime.now().year
|
||||
if month is None:
|
||||
month = datetime.now().month
|
||||
|
||||
import collections.abc
|
||||
|
||||
def update(d, u):
|
||||
"""Update dict recursively."""
|
||||
for k, v in u.items():
|
||||
if isinstance(v, collections.abc.Mapping):
|
||||
d[k] = update(d.get(k, {}), v)
|
||||
else:
|
||||
d[k] = v
|
||||
return d
|
||||
|
||||
req: Dict[str, Any] = {}
|
||||
update(req, self._create_request(self.emeter_type, "get_realtime"))
|
||||
update(
|
||||
req, self._create_request(self.emeter_type, "get_monthstat", {"year": year})
|
||||
)
|
||||
update(
|
||||
req,
|
||||
self._create_request(
|
||||
self.emeter_type, "get_daystat", {"month": month, "year": year}
|
||||
),
|
||||
)
|
||||
|
||||
return req
|
||||
|
||||
@property # type: ignore
|
||||
@requires_update
|
||||
def emeter_today(self) -> Optional[float]:
|
||||
"""Return today's energy consumption in kWh."""
|
||||
raw_data = self._last_update[self.emeter_type]["get_daystat"]["day_list"]
|
||||
data = self._emeter_convert_emeter_data(raw_data)
|
||||
today = datetime.now().day
|
||||
|
||||
if today in data:
|
||||
return data[today]
|
||||
|
||||
return None
|
||||
|
||||
@property # type: ignore
|
||||
@requires_update
|
||||
def emeter_this_month(self) -> Optional[float]:
|
||||
"""Return this month's energy consumption in kWh."""
|
||||
raw_data = self._last_update[self.emeter_type]["get_monthstat"]["month_list"]
|
||||
data = self._emeter_convert_emeter_data(raw_data)
|
||||
current_month = datetime.now().month
|
||||
|
||||
if current_month in data:
|
||||
return data[current_month]
|
||||
|
||||
return None
|
||||
|
||||
def _emeter_convert_emeter_data(self, data, kwh=True) -> Dict:
|
||||
"""Return emeter information keyed with the day/month.."""
|
||||
response = [EmeterStatus(**x) for x in data]
|
||||
|
||||
if not response:
|
||||
return {}
|
||||
|
||||
energy_key = "energy_wh"
|
||||
if kwh:
|
||||
energy_key = "energy"
|
||||
|
||||
entry_key = "month"
|
||||
if "day" in response[0]:
|
||||
entry_key = "day"
|
||||
|
||||
data = {entry[entry_key]: entry[energy_key] for entry in response}
|
||||
|
||||
return data
|
||||
|
||||
async def get_emeter_daily(
|
||||
self, year: int = None, month: int = None, kwh: bool = True
|
||||
) -> Dict:
|
||||
@ -456,15 +563,8 @@ class SmartDevice:
|
||||
response = await self._query_helper(
|
||||
self.emeter_type, "get_daystat", {"month": month, "year": year}
|
||||
)
|
||||
response = [EmeterStatus(**x) for x in response["day_list"]]
|
||||
|
||||
key = "energy_wh"
|
||||
if kwh:
|
||||
key = "energy"
|
||||
|
||||
data = {entry["day"]: entry[key] for entry in response}
|
||||
|
||||
return data
|
||||
return self._emeter_convert_emeter_data(response["day_list"], kwh)
|
||||
|
||||
@requires_update
|
||||
async def get_emeter_monthly(self, year: int = None, kwh: bool = True) -> Dict:
|
||||
@ -485,13 +585,8 @@ class SmartDevice:
|
||||
response = await self._query_helper(
|
||||
self.emeter_type, "get_monthstat", {"year": year}
|
||||
)
|
||||
response = [EmeterStatus(**x) for x in response["month_list"]]
|
||||
|
||||
key = "energy_wh"
|
||||
if kwh:
|
||||
key = "energy"
|
||||
|
||||
return {entry["month"]: entry[key] for entry in response}
|
||||
return self._emeter_convert_emeter_data(response["month_list"], kwh)
|
||||
|
||||
@requires_update
|
||||
async def erase_emeter_stats(self):
|
||||
@ -503,8 +598,7 @@ class SmartDevice:
|
||||
if not self.has_emeter:
|
||||
raise SmartDeviceException("Device has no emeter")
|
||||
|
||||
await self._query_helper(self.emeter_type, "erase_emeter_stat", None)
|
||||
await self.update()
|
||||
return await self._query_helper(self.emeter_type, "erase_emeter_stat", None)
|
||||
|
||||
@requires_update
|
||||
async def current_consumption(self) -> float:
|
||||
@ -673,11 +767,6 @@ class SmartDevice:
|
||||
return False
|
||||
|
||||
def __repr__(self):
|
||||
return "<{} model {} at {} ({}), is_on: {} - dev specific: {}>".format(
|
||||
self.__class__.__name__,
|
||||
self.model,
|
||||
self.host,
|
||||
self.alias,
|
||||
self.is_on,
|
||||
self.state_information,
|
||||
)
|
||||
if self._last_update is None:
|
||||
return f"<{self._device_type} at {self.host} - update() needed>"
|
||||
return f"<{self._device_type} model {self.model} at {self.host} ({self.alias}), is_on: {self.is_on} - dev specific: {self.state_information}>"
|
||||
|
@ -60,10 +60,9 @@ class SmartDimmer(SmartPlug):
|
||||
if not isinstance(value, int):
|
||||
raise ValueError("Brightness must be integer, " "not of %s.", type(value))
|
||||
elif 0 <= value <= 100:
|
||||
await self._query_helper(
|
||||
return await self._query_helper(
|
||||
"smartlife.iot.dimmer", "set_brightness", {"brightness": value}
|
||||
)
|
||||
await self.update()
|
||||
else:
|
||||
raise ValueError("Brightness value %s is not valid." % value)
|
||||
|
||||
|
@ -50,16 +50,14 @@ class SmartPlug(SmartDevice):
|
||||
|
||||
:raises SmartDeviceException: on error
|
||||
"""
|
||||
await self._query_helper("system", "set_relay_state", {"state": 1})
|
||||
await self.update()
|
||||
return await self._query_helper("system", "set_relay_state", {"state": 1})
|
||||
|
||||
async def turn_off(self):
|
||||
"""Turn the switch off.
|
||||
|
||||
:raises SmartDeviceException: on error
|
||||
"""
|
||||
await self._query_helper("system", "set_relay_state", {"state": 0})
|
||||
await self.update()
|
||||
return await self._query_helper("system", "set_relay_state", {"state": 0})
|
||||
|
||||
@property # type: ignore
|
||||
@requires_update
|
||||
@ -78,8 +76,9 @@ class SmartPlug(SmartDevice):
|
||||
:param bool state: True to set led on, False to set led off
|
||||
:raises SmartDeviceException: on error
|
||||
"""
|
||||
await self._query_helper("system", "set_led_off", {"off": int(not state)})
|
||||
await self.update()
|
||||
return await self._query_helper(
|
||||
"system", "set_led_off", {"off": int(not state)}
|
||||
)
|
||||
|
||||
@property # type: ignore
|
||||
@requires_update
|
||||
|
@ -146,12 +146,11 @@ class SmartStrip(SmartDevice):
|
||||
:return: Strip information dict, keys in user-presentable form.
|
||||
:rtype: dict
|
||||
"""
|
||||
state: Dict[str, Any] = {"LED state": self.led}
|
||||
for plug in self.plugs:
|
||||
if plug.is_on:
|
||||
state["Plug %s on since" % str(plug)] = self.on_since
|
||||
|
||||
return state
|
||||
return {
|
||||
"LED state": self.led,
|
||||
"Childs count": len(self.plugs),
|
||||
"On since": self.on_since,
|
||||
}
|
||||
|
||||
async def current_consumption(self) -> float:
|
||||
"""Get the current power consumption in watts.
|
||||
@ -218,6 +217,7 @@ class SmartStrip(SmartDevice):
|
||||
plug_emeter_monthly = await plug.get_emeter_monthly(year=year, kwh=kwh)
|
||||
for month, value in plug_emeter_monthly:
|
||||
emeter_monthly[month] += value
|
||||
|
||||
return emeter_monthly
|
||||
|
||||
@requires_update
|
||||
@ -245,7 +245,8 @@ class SmartStripPlug(SmartPlug):
|
||||
|
||||
self.parent = parent
|
||||
self.child_id = child_id
|
||||
self._sys_info = {**self.parent.sys_info, **self._get_child_info()}
|
||||
self._last_update = parent._last_update
|
||||
self._sys_info = parent._sys_info
|
||||
|
||||
async def update(self):
|
||||
"""Override the update to no-op and inform the user."""
|
||||
|
@ -313,9 +313,7 @@ class FakeTransportProtocol(TPLinkSmartHomeProtocol):
|
||||
|
||||
def transition_light_state(self, x, *args):
|
||||
_LOGGER.debug("Setting light state to %s", x)
|
||||
light_state = self.proto["smartlife.iot.smartbulb.lightingservice"][
|
||||
"get_light_state"
|
||||
]
|
||||
light_state = self.proto["system"]["get_sysinfo"]["light_state"]
|
||||
# The required change depends on the light state,
|
||||
# exception being turning the bulb on and off
|
||||
|
||||
@ -323,15 +321,11 @@ class FakeTransportProtocol(TPLinkSmartHomeProtocol):
|
||||
if x["on_off"] and not light_state["on_off"]: # turning on
|
||||
new_state = light_state["dft_on_state"]
|
||||
new_state["on_off"] = 1
|
||||
self.proto["smartlife.iot.smartbulb.lightingservice"][
|
||||
"get_light_state"
|
||||
] = new_state
|
||||
self.proto["system"]["get_sysinfo"]["light_state"] = new_state
|
||||
elif not x["on_off"] and light_state["on_off"]:
|
||||
new_state = {"dft_on_state": light_state, "on_off": 0}
|
||||
|
||||
self.proto["smartlife.iot.smartbulb.lightingservice"][
|
||||
"get_light_state"
|
||||
] = new_state
|
||||
self.proto["system"]["get_sysinfo"]["light_state"] = new_state
|
||||
|
||||
return
|
||||
|
||||
@ -343,11 +337,9 @@ class FakeTransportProtocol(TPLinkSmartHomeProtocol):
|
||||
light_state[key] = x[key]
|
||||
|
||||
def light_state(self, x, *args):
|
||||
light_state = self.proto["smartlife.iot.smartbulb.lightingservice"][
|
||||
"get_light_state"
|
||||
]
|
||||
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.info("reporting light state: %s", light_state)
|
||||
_LOGGER.debug("reporting light state: %s", light_state)
|
||||
if light_state["on_off"]:
|
||||
return light_state["dft_on_state"]
|
||||
else:
|
||||
|
@ -66,7 +66,7 @@ async def test_emeter(dev: SmartDevice, mocker):
|
||||
assert "Device has no emeter" in res.output
|
||||
return
|
||||
|
||||
assert "Current State" in res.output
|
||||
assert "== Emeter ==" in res.output
|
||||
|
||||
monthly = mocker.patch.object(dev, "get_emeter_monthly")
|
||||
res = await runner.invoke(emeter, ["--year", "1900"], obj=dev)
|
||||
|
@ -419,9 +419,11 @@ async def test_children_alias(dev):
|
||||
for plug in dev.plugs:
|
||||
original = plug.alias
|
||||
await plug.set_alias(alias=test_alias)
|
||||
await dev.update() # TODO: set_alias does not call parent's update()..
|
||||
assert plug.alias == test_alias
|
||||
|
||||
await plug.set_alias(alias=original)
|
||||
await dev.update() # TODO: set_alias does not call parent's update()..
|
||||
assert plug.alias == original
|
||||
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user