mirror of
https://github.com/python-kasa/python-kasa.git
synced 2025-04-26 16:46:23 +00:00
implement methods that depend on sys_info as properties and add update
In the case of smartbulb.update, light_state is also updated.
This commit is contained in:
parent
8c25590186
commit
72d5443f1a
@ -151,7 +151,7 @@ def find_host_from_alias(alias, target="255.255.255.255", timeout=1, attempts=3)
|
|||||||
click.echo(f"Attempt {attempt} of {attempts}")
|
click.echo(f"Attempt {attempt} of {attempts}")
|
||||||
found_devs = Discover.discover(target=target, timeout=timeout).items()
|
found_devs = Discover.discover(target=target, timeout=timeout).items()
|
||||||
for ip, dev in found_devs:
|
for ip, dev in found_devs:
|
||||||
if dev.sync.get_alias().lower() == alias.lower():
|
if dev.alias.lower() == alias.lower():
|
||||||
host = dev.host
|
host = dev.host
|
||||||
return host
|
return host
|
||||||
return None
|
return None
|
||||||
@ -161,8 +161,9 @@ def find_host_from_alias(alias, target="255.255.255.255", timeout=1, attempts=3)
|
|||||||
@pass_dev
|
@pass_dev
|
||||||
def sysinfo(dev):
|
def sysinfo(dev):
|
||||||
"""Print out full system information."""
|
"""Print out full system information."""
|
||||||
|
dev.sync.update()
|
||||||
click.echo(click.style("== System info ==", bold=True))
|
click.echo(click.style("== System info ==", bold=True))
|
||||||
click.echo(pf(dev.sync.get_sys_info()))
|
click.echo(pf(dev.sys_info))
|
||||||
|
|
||||||
|
|
||||||
@cli.command()
|
@cli.command()
|
||||||
@ -170,23 +171,20 @@ def sysinfo(dev):
|
|||||||
@click.pass_context
|
@click.pass_context
|
||||||
def state(ctx, dev: SmartDevice):
|
def state(ctx, dev: SmartDevice):
|
||||||
"""Print out device state and versions."""
|
"""Print out device state and versions."""
|
||||||
click.echo(
|
dev.sync.update()
|
||||||
click.style(
|
click.echo(click.style("== {} - {} ==".format(dev.alias, dev.model), bold=True))
|
||||||
"== {} - {} ==".format(dev.sync.get_alias(), dev.sync.get_model()),
|
|
||||||
bold=True,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
click.echo(
|
click.echo(
|
||||||
click.style(
|
click.style(
|
||||||
"Device state: {}".format("ON" if dev.sync.is_on() else "OFF"),
|
"Device state: {}".format("ON" if dev.is_on else "OFF"),
|
||||||
fg="green" if dev.sync.is_on() else "red",
|
fg="green" if dev.is_on else "red",
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
if dev.is_strip:
|
if dev.is_strip:
|
||||||
for plug in dev.plugs: # type: ignore
|
for plug in dev.plugs: # type: ignore
|
||||||
is_on = plug.sync.get_is_on()
|
plug.sync.update()
|
||||||
alias = plug.sync.get_alias()
|
is_on = plug.is_on
|
||||||
|
alias = plug.alias
|
||||||
click.echo(
|
click.echo(
|
||||||
click.style(
|
click.style(
|
||||||
" * {} state: {}".format(alias, ("ON" if is_on else "OFF")),
|
" * {} state: {}".format(alias, ("ON" if is_on else "OFF")),
|
||||||
@ -195,15 +193,14 @@ def state(ctx, dev: SmartDevice):
|
|||||||
)
|
)
|
||||||
|
|
||||||
click.echo(f"Host/IP: {dev.host}")
|
click.echo(f"Host/IP: {dev.host}")
|
||||||
for k, v in dev.sync.get_state_information().items():
|
for k, v in dev.state_information.items():
|
||||||
click.echo(f"{k}: {v}")
|
click.echo(f"{k}: {v}")
|
||||||
hw_info = dev.sync.get_hw_info()
|
|
||||||
click.echo(click.style("== Generic information ==", bold=True))
|
click.echo(click.style("== Generic information ==", bold=True))
|
||||||
click.echo("Time: {}".format(dev.sync.get_time()))
|
click.echo("Time: {}".format(dev.sync.get_time()))
|
||||||
click.echo("Hardware: {}".format(hw_info["hw_ver"]))
|
click.echo("Hardware: {}".format(dev.hw_info["hw_ver"]))
|
||||||
click.echo("Software: {}".format(hw_info["sw_ver"]))
|
click.echo("Software: {}".format(dev.hw_info["sw_ver"]))
|
||||||
click.echo("MAC (rssi): {} ({})".format(dev.sync.get_mac(), dev.sync.get_rssi()))
|
click.echo("MAC (rssi): {} ({})".format(dev.mac, dev.rssi))
|
||||||
click.echo("Location: {}".format(dev.sync.get_location()))
|
click.echo("Location: {}".format(dev.location))
|
||||||
|
|
||||||
ctx.invoke(emeter)
|
ctx.invoke(emeter)
|
||||||
|
|
||||||
@ -217,7 +214,7 @@ def alias(dev, new_alias):
|
|||||||
click.echo(f"Setting alias to {new_alias}")
|
click.echo(f"Setting alias to {new_alias}")
|
||||||
dev.sync.set_alias(new_alias)
|
dev.sync.set_alias(new_alias)
|
||||||
|
|
||||||
click.echo(f"Alias: {dev.sync.get_alias()}")
|
click.echo(f"Alias: {dev.alias}")
|
||||||
|
|
||||||
|
|
||||||
@cli.command()
|
@cli.command()
|
||||||
@ -232,6 +229,7 @@ def raw_command(dev: SmartDevice, module, command, parameters):
|
|||||||
if parameters is not None:
|
if parameters is not None:
|
||||||
parameters = ast.literal_eval(parameters)
|
parameters = ast.literal_eval(parameters)
|
||||||
res = dev.sync._query_helper(module, command, parameters)
|
res = dev.sync._query_helper(module, command, parameters)
|
||||||
|
dev.sync.update()
|
||||||
click.echo(res)
|
click.echo(res)
|
||||||
|
|
||||||
|
|
||||||
@ -243,7 +241,8 @@ def raw_command(dev: SmartDevice, module, command, parameters):
|
|||||||
def emeter(dev, year, month, erase):
|
def emeter(dev, year, month, erase):
|
||||||
"""Query emeter for historical consumption."""
|
"""Query emeter for historical consumption."""
|
||||||
click.echo(click.style("== Emeter ==", bold=True))
|
click.echo(click.style("== Emeter ==", bold=True))
|
||||||
if not dev.sync.get_has_emeter():
|
dev.sync.update()
|
||||||
|
if not dev.has_emeter:
|
||||||
click.echo("Device has no emeter")
|
click.echo("Device has no emeter")
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -274,11 +273,12 @@ def emeter(dev, year, month, erase):
|
|||||||
@pass_dev
|
@pass_dev
|
||||||
def brightness(dev, brightness):
|
def brightness(dev, brightness):
|
||||||
"""Get or set brightness."""
|
"""Get or set brightness."""
|
||||||
if not dev.sync.is_dimmable():
|
dev.sync.update()
|
||||||
|
if not dev.is_dimmable:
|
||||||
click.echo("This device does not support brightness.")
|
click.echo("This device does not support brightness.")
|
||||||
return
|
return
|
||||||
if brightness is None:
|
if brightness is None:
|
||||||
click.echo("Brightness: %s" % dev.sync.get_brightness())
|
click.echo("Brightness: %s" % dev.brightness)
|
||||||
else:
|
else:
|
||||||
click.echo("Setting brightness to %s" % brightness)
|
click.echo("Setting brightness to %s" % brightness)
|
||||||
dev.sync.set_brightness(brightness)
|
dev.sync.set_brightness(brightness)
|
||||||
@ -292,14 +292,14 @@ def brightness(dev, brightness):
|
|||||||
def temperature(dev: SmartBulb, temperature):
|
def temperature(dev: SmartBulb, temperature):
|
||||||
"""Get or set color temperature."""
|
"""Get or set color temperature."""
|
||||||
if temperature is None:
|
if temperature is None:
|
||||||
click.echo(f"Color temperature: {dev.sync.get_color_temp()}")
|
click.echo(f"Color temperature: {dev.color_temp}")
|
||||||
valid_temperature_range = dev.sync.get_valid_temperature_range()
|
valid_temperature_range = dev.valid_temperature_range
|
||||||
if valid_temperature_range != (0, 0):
|
if valid_temperature_range != (0, 0):
|
||||||
click.echo("(min: {}, max: {})".format(*valid_temperature_range))
|
click.echo("(min: {}, max: {})".format(*valid_temperature_range))
|
||||||
else:
|
else:
|
||||||
click.echo(
|
click.echo(
|
||||||
"Temperature range unknown, please open a github issue"
|
"Temperature range unknown, please open a github issue"
|
||||||
f" or a pull request for model '{dev.sync.get_model()}'"
|
f" or a pull request for model '{dev.model}'"
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
click.echo(f"Setting color temperature to {temperature}")
|
click.echo(f"Setting color temperature to {temperature}")
|
||||||
@ -315,7 +315,7 @@ def temperature(dev: SmartBulb, temperature):
|
|||||||
def hsv(dev, ctx, h, s, v):
|
def hsv(dev, ctx, h, s, v):
|
||||||
"""Get or set color in HSV. (Bulb only)."""
|
"""Get or set color in HSV. (Bulb only)."""
|
||||||
if h is None or s is None or v is None:
|
if h is None or s is None or v is None:
|
||||||
click.echo("Current HSV: %s %s %s" % dev.sync.get_hsv())
|
click.echo("Current HSV: %s %s %s" % dev.hsv)
|
||||||
elif s is None or v is None:
|
elif s is None or v is None:
|
||||||
raise click.BadArgumentUsage("Setting a color requires 3 values.", ctx)
|
raise click.BadArgumentUsage("Setting a color requires 3 values.", ctx)
|
||||||
else:
|
else:
|
||||||
@ -332,7 +332,7 @@ def led(dev, state):
|
|||||||
click.echo("Turning led to %s" % state)
|
click.echo("Turning led to %s" % state)
|
||||||
dev.sync.set_led(state)
|
dev.sync.set_led(state)
|
||||||
else:
|
else:
|
||||||
click.echo("LED state: %s" % dev.sync.get_led())
|
click.echo("LED state: %s" % dev.led)
|
||||||
|
|
||||||
|
|
||||||
@cli.command()
|
@cli.command()
|
||||||
|
@ -1,9 +1,14 @@
|
|||||||
"""Module for bulbs."""
|
"""Module for bulbs."""
|
||||||
import re
|
import re
|
||||||
from typing import Any, Dict, Tuple
|
from typing import Any, Dict, Optional, Tuple
|
||||||
|
|
||||||
from pyHS100.protocol import TPLinkSmartHomeProtocol
|
from pyHS100.protocol import TPLinkSmartHomeProtocol
|
||||||
from pyHS100.smartdevice import DeviceType, SmartDevice, SmartDeviceException
|
from pyHS100.smartdevice import (
|
||||||
|
DeviceType,
|
||||||
|
SmartDevice,
|
||||||
|
SmartDeviceException,
|
||||||
|
requires_update,
|
||||||
|
)
|
||||||
|
|
||||||
TPLINK_KELVIN = {
|
TPLINK_KELVIN = {
|
||||||
"LB130": (2500, 9000),
|
"LB130": (2500, 9000),
|
||||||
@ -24,14 +29,14 @@ class SmartBulb(SmartDevice):
|
|||||||
p = SmartBulb("192.168.1.105")
|
p = SmartBulb("192.168.1.105")
|
||||||
|
|
||||||
# print the devices alias
|
# print the devices alias
|
||||||
print(p.sync.get_alias())
|
print(p.sync.alias)
|
||||||
|
|
||||||
# change state of bulb
|
# change state of bulb
|
||||||
p.sync.turn_on()
|
p.sync.turn_on()
|
||||||
p.sync.turn_off()
|
p.sync.turn_off()
|
||||||
|
|
||||||
# query and print current state of plug
|
# query and print current state of plug
|
||||||
print(p.sync.get_state_information())
|
print(p.sync.state_information())
|
||||||
|
|
||||||
# check whether the bulb supports color changes
|
# check whether the bulb supports color changes
|
||||||
if p.sync.is_color():
|
if p.sync.is_color():
|
||||||
@ -40,7 +45,7 @@ class SmartBulb(SmartDevice):
|
|||||||
p.sync.set_hsv(180, 100, 100)
|
p.sync.set_hsv(180, 100, 100)
|
||||||
|
|
||||||
# get the current HSV value
|
# get the current HSV value
|
||||||
print(p.sync.get_hsv())
|
print(p.sync.hsv())
|
||||||
|
|
||||||
# check whether the bulb supports setting color temperature
|
# check whether the bulb supports setting color temperature
|
||||||
if p.sync.is_variable_color_temp():
|
if p.sync.is_variable_color_temp():
|
||||||
@ -48,16 +53,16 @@ class SmartBulb(SmartDevice):
|
|||||||
p.sync.set_color_temp(3000)
|
p.sync.set_color_temp(3000)
|
||||||
|
|
||||||
# get the current color temperature
|
# get the current color temperature
|
||||||
print(p.sync.get_color_temp())
|
print(p.sync.color_temp)
|
||||||
|
|
||||||
# check whether the bulb is dimmable
|
# check whether the bulb is dimmable
|
||||||
if p.sync.is_dimmable():
|
if p.is_dimmable:
|
||||||
|
|
||||||
# set the bulb to 50% brightness
|
# set the bulb to 50% brightness
|
||||||
p.sync.set_brightness(50)
|
p.sync.set_brightness(50)
|
||||||
|
|
||||||
# check the current brightness
|
# check the current brightness
|
||||||
print(p.sync.get_brightness())
|
print(p.brightness)
|
||||||
```
|
```
|
||||||
|
|
||||||
Omit the `sync` attribute to get coroutines.
|
Omit the `sync` attribute to get coroutines.
|
||||||
@ -87,70 +92,94 @@ class SmartBulb(SmartDevice):
|
|||||||
)
|
)
|
||||||
self.emeter_type = "smartlife.iot.common.emeter"
|
self.emeter_type = "smartlife.iot.common.emeter"
|
||||||
self._device_type = DeviceType.Bulb
|
self._device_type = DeviceType.Bulb
|
||||||
|
self._light_state = None
|
||||||
|
|
||||||
async def is_color(self) -> bool:
|
@property
|
||||||
|
@requires_update
|
||||||
|
def is_color(self) -> bool:
|
||||||
"""Whether the bulb supports color changes.
|
"""Whether the bulb supports color changes.
|
||||||
|
|
||||||
:return: True if the bulb supports color changes, False otherwise
|
:return: True if the bulb supports color changes, False otherwise
|
||||||
:rtype: bool
|
:rtype: bool
|
||||||
"""
|
"""
|
||||||
sys_info = await self.get_sys_info()
|
sys_info = self.sys_info
|
||||||
return bool(sys_info["is_color"])
|
return bool(sys_info["is_color"])
|
||||||
|
|
||||||
async def is_dimmable(self) -> bool:
|
@property
|
||||||
|
@requires_update
|
||||||
|
def is_dimmable(self) -> bool:
|
||||||
"""Whether the bulb supports brightness changes.
|
"""Whether the bulb supports brightness changes.
|
||||||
|
|
||||||
:return: True if the bulb supports brightness changes, False otherwise
|
:return: True if the bulb supports brightness changes, False otherwise
|
||||||
:rtype: bool
|
:rtype: bool
|
||||||
"""
|
"""
|
||||||
sys_info = await self.get_sys_info()
|
sys_info = self.sys_info
|
||||||
return bool(sys_info["is_dimmable"])
|
return bool(sys_info["is_dimmable"])
|
||||||
|
|
||||||
async def is_variable_color_temp(self) -> bool:
|
@property
|
||||||
|
@requires_update
|
||||||
|
def is_variable_color_temp(self) -> bool:
|
||||||
"""Whether the bulb supports color temperature changes.
|
"""Whether the bulb supports color temperature changes.
|
||||||
|
|
||||||
:return: True if the bulb supports color temperature changes, False
|
:return: True if the bulb supports color temperature changes, False
|
||||||
otherwise
|
otherwise
|
||||||
:rtype: bool
|
:rtype: bool
|
||||||
"""
|
"""
|
||||||
sys_info = await self.get_sys_info()
|
sys_info = self.sys_info
|
||||||
return bool(sys_info["is_variable_color_temp"])
|
return bool(sys_info["is_variable_color_temp"])
|
||||||
|
|
||||||
async def get_valid_temperature_range(self) -> Tuple[int, int]:
|
@property
|
||||||
|
@requires_update
|
||||||
|
def valid_temperature_range(self) -> Tuple[int, int]:
|
||||||
"""Return the device-specific white temperature range (in Kelvin).
|
"""Return the device-specific white temperature range (in Kelvin).
|
||||||
|
|
||||||
:return: White temperature range in Kelvin (minimun, maximum)
|
:return: White temperature range in Kelvin (minimun, maximum)
|
||||||
:rtype: tuple
|
:rtype: tuple
|
||||||
"""
|
"""
|
||||||
if not await self.is_variable_color_temp():
|
if not self.is_variable_color_temp:
|
||||||
return (0, 0)
|
return (0, 0)
|
||||||
for model, temp_range in TPLINK_KELVIN.items():
|
for model, temp_range in TPLINK_KELVIN.items():
|
||||||
sys_info = await self.get_sys_info()
|
sys_info = self.sys_info
|
||||||
if re.match(model, sys_info["model"]):
|
if re.match(model, sys_info["model"]):
|
||||||
return temp_range
|
return temp_range
|
||||||
return (0, 0)
|
return (0, 0)
|
||||||
|
|
||||||
async def get_light_state(self) -> Dict:
|
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
|
||||||
|
@requires_update
|
||||||
|
def light_state(self) -> Optional[Dict[str, Dict]]:
|
||||||
|
"""Query the light state."""
|
||||||
|
return self._light_state
|
||||||
|
|
||||||
|
async def get_light_state(self) -> Dict[str, Dict]:
|
||||||
"""Query the light state."""
|
"""Query the light state."""
|
||||||
return await self._query_helper(self.LIGHT_SERVICE, "get_light_state")
|
return await self._query_helper(self.LIGHT_SERVICE, "get_light_state")
|
||||||
|
|
||||||
async def set_light_state(self, state: Dict) -> Dict:
|
async def set_light_state(self, state: Dict) -> Dict:
|
||||||
"""Set the light state."""
|
"""Set the light state."""
|
||||||
return await self._query_helper(
|
light_state = await self._query_helper(
|
||||||
self.LIGHT_SERVICE, "transition_light_state", state
|
self.LIGHT_SERVICE, "transition_light_state", state
|
||||||
)
|
)
|
||||||
|
await self.update()
|
||||||
|
return light_state
|
||||||
|
|
||||||
async def get_hsv(self) -> Tuple[int, int, int]:
|
@property
|
||||||
|
@requires_update
|
||||||
|
def hsv(self) -> Tuple[int, int, int]:
|
||||||
"""Return the current HSV state of the bulb.
|
"""Return the current HSV state of the bulb.
|
||||||
|
|
||||||
:return: hue, saturation and value (degrees, %, %)
|
:return: hue, saturation and value (degrees, %, %)
|
||||||
:rtype: tuple
|
:rtype: tuple
|
||||||
"""
|
"""
|
||||||
if not await self.is_color():
|
if not self.is_color:
|
||||||
raise SmartDeviceException("Bulb does not support color.")
|
raise SmartDeviceException("Bulb does not support color.")
|
||||||
|
|
||||||
light_state = await self.get_light_state()
|
light_state = self.light_state
|
||||||
if not await self.is_on():
|
if not self.is_on:
|
||||||
hue = light_state["dft_on_state"]["hue"]
|
hue = light_state["dft_on_state"]["hue"]
|
||||||
saturation = light_state["dft_on_state"]["saturation"]
|
saturation = light_state["dft_on_state"]["saturation"]
|
||||||
value = light_state["dft_on_state"]["brightness"]
|
value = light_state["dft_on_state"]["brightness"]
|
||||||
@ -167,6 +196,7 @@ class SmartBulb(SmartDevice):
|
|||||||
"Invalid brightness value: {} " "(valid range: 0-100%)".format(value)
|
"Invalid brightness value: {} " "(valid range: 0-100%)".format(value)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@requires_update
|
||||||
async def set_hsv(self, hue: int, saturation: int, value: int):
|
async def set_hsv(self, hue: int, saturation: int, value: int):
|
||||||
"""Set new HSV.
|
"""Set new HSV.
|
||||||
|
|
||||||
@ -174,7 +204,7 @@ class SmartBulb(SmartDevice):
|
|||||||
:param int saturation: saturation in percentage [0,100]
|
:param int saturation: saturation in percentage [0,100]
|
||||||
:param int value: value in percentage [0, 100]
|
:param int value: value in percentage [0, 100]
|
||||||
"""
|
"""
|
||||||
if not await self.is_color():
|
if not self.is_color:
|
||||||
raise SmartDeviceException("Bulb does not support color.")
|
raise SmartDeviceException("Bulb does not support color.")
|
||||||
|
|
||||||
if not isinstance(hue, int) or not (0 <= hue <= 360):
|
if not isinstance(hue, int) or not (0 <= hue <= 360):
|
||||||
@ -198,30 +228,33 @@ class SmartBulb(SmartDevice):
|
|||||||
}
|
}
|
||||||
await self.set_light_state(light_state)
|
await self.set_light_state(light_state)
|
||||||
|
|
||||||
async def get_color_temp(self) -> int:
|
@property
|
||||||
|
@requires_update
|
||||||
|
def color_temp(self) -> int:
|
||||||
"""Return color temperature of the device.
|
"""Return color temperature of the device.
|
||||||
|
|
||||||
:return: Color temperature in Kelvin
|
:return: Color temperature in Kelvin
|
||||||
:rtype: int
|
:rtype: int
|
||||||
"""
|
"""
|
||||||
if not await self.is_variable_color_temp():
|
if not self.is_variable_color_temp:
|
||||||
raise SmartDeviceException("Bulb does not support colortemp.")
|
raise SmartDeviceException("Bulb does not support colortemp.")
|
||||||
|
|
||||||
light_state = await self.get_light_state()
|
light_state = self.light_state
|
||||||
if not await self.is_on():
|
if not self.is_on:
|
||||||
return int(light_state["dft_on_state"]["color_temp"])
|
return int(light_state["dft_on_state"]["color_temp"])
|
||||||
else:
|
else:
|
||||||
return int(light_state["color_temp"])
|
return int(light_state["color_temp"])
|
||||||
|
|
||||||
|
@requires_update
|
||||||
async def set_color_temp(self, temp: int) -> None:
|
async def set_color_temp(self, temp: int) -> None:
|
||||||
"""Set the color temperature of the device.
|
"""Set the color temperature of the device.
|
||||||
|
|
||||||
:param int temp: The new color temperature, in Kelvin
|
:param int temp: The new color temperature, in Kelvin
|
||||||
"""
|
"""
|
||||||
if not await self.is_variable_color_temp():
|
if not self.is_variable_color_temp:
|
||||||
raise SmartDeviceException("Bulb does not support colortemp.")
|
raise SmartDeviceException("Bulb does not support colortemp.")
|
||||||
|
|
||||||
valid_temperature_range = await self.get_valid_temperature_range()
|
valid_temperature_range = self.valid_temperature_range
|
||||||
if temp < valid_temperature_range[0] or temp > valid_temperature_range[1]:
|
if temp < valid_temperature_range[0] or temp > valid_temperature_range[1]:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
"Temperature should be between {} "
|
"Temperature should be between {} "
|
||||||
@ -231,27 +264,30 @@ class SmartBulb(SmartDevice):
|
|||||||
light_state = {"color_temp": temp}
|
light_state = {"color_temp": temp}
|
||||||
await self.set_light_state(light_state)
|
await self.set_light_state(light_state)
|
||||||
|
|
||||||
async def get_brightness(self) -> int:
|
@property
|
||||||
|
@requires_update
|
||||||
|
def brightness(self) -> int:
|
||||||
"""Return the current brightness.
|
"""Return the current brightness.
|
||||||
|
|
||||||
:return: brightness in percent
|
:return: brightness in percent
|
||||||
:rtype: int
|
:rtype: int
|
||||||
"""
|
"""
|
||||||
if not await self.is_dimmable(): # pragma: no cover
|
if not self.is_dimmable: # pragma: no cover
|
||||||
raise SmartDeviceException("Bulb is not dimmable.")
|
raise SmartDeviceException("Bulb is not dimmable.")
|
||||||
|
|
||||||
light_state = await self.get_light_state()
|
light_state = self.light_state
|
||||||
if not await self.is_on():
|
if not self.is_on:
|
||||||
return int(light_state["dft_on_state"]["brightness"])
|
return int(light_state["dft_on_state"]["brightness"])
|
||||||
else:
|
else:
|
||||||
return int(light_state["brightness"])
|
return int(light_state["brightness"])
|
||||||
|
|
||||||
|
@requires_update
|
||||||
async def set_brightness(self, brightness: int) -> None:
|
async def set_brightness(self, brightness: int) -> None:
|
||||||
"""Set the brightness.
|
"""Set the brightness.
|
||||||
|
|
||||||
:param int brightness: brightness in percent
|
:param int brightness: brightness in percent
|
||||||
"""
|
"""
|
||||||
if not await self.is_dimmable(): # pragma: no cover
|
if not self.is_dimmable: # pragma: no cover
|
||||||
raise SmartDeviceException("Bulb is not dimmable.")
|
raise SmartDeviceException("Bulb is not dimmable.")
|
||||||
|
|
||||||
self._raise_for_invalid_brightness(brightness)
|
self._raise_for_invalid_brightness(brightness)
|
||||||
@ -259,27 +295,31 @@ class SmartBulb(SmartDevice):
|
|||||||
light_state = {"brightness": brightness}
|
light_state = {"brightness": brightness}
|
||||||
await self.set_light_state(light_state)
|
await self.set_light_state(light_state)
|
||||||
|
|
||||||
async def get_state_information(self) -> Dict[str, Any]:
|
@property
|
||||||
|
@requires_update
|
||||||
|
def state_information(self) -> Dict[str, Any]:
|
||||||
"""Return bulb-specific state information.
|
"""Return bulb-specific state information.
|
||||||
|
|
||||||
:return: Bulb information dict, keys in user-presentable form.
|
:return: Bulb information dict, keys in user-presentable form.
|
||||||
:rtype: dict
|
:rtype: dict
|
||||||
"""
|
"""
|
||||||
info: Dict[str, Any] = {
|
info: Dict[str, Any] = {
|
||||||
"Brightness": await self.get_brightness(),
|
"Brightness": self.brightness,
|
||||||
"Is dimmable": await self.is_dimmable(),
|
"Is dimmable": self.is_dimmable,
|
||||||
}
|
}
|
||||||
if await self.is_variable_color_temp():
|
if self.is_variable_color_temp:
|
||||||
info["Color temperature"] = await self.get_color_temp()
|
info["Color temperature"] = self.color_temp
|
||||||
info["Valid temperature range"] = await self.get_valid_temperature_range()
|
info["Valid temperature range"] = self.valid_temperature_range
|
||||||
if await self.is_color():
|
if self.is_color:
|
||||||
info["HSV"] = await self.get_hsv()
|
info["HSV"] = self.hsv
|
||||||
|
|
||||||
return info
|
return info
|
||||||
|
|
||||||
async def is_on(self) -> bool:
|
@property
|
||||||
|
@requires_update
|
||||||
|
def is_on(self) -> bool:
|
||||||
"""Return whether the device is on."""
|
"""Return whether the device is on."""
|
||||||
light_state = await self.get_light_state()
|
light_state = self.light_state
|
||||||
return bool(light_state["on_off"])
|
return bool(light_state["on_off"])
|
||||||
|
|
||||||
async def turn_off(self) -> None:
|
async def turn_off(self) -> None:
|
||||||
@ -290,6 +330,8 @@ class SmartBulb(SmartDevice):
|
|||||||
"""Turn the bulb on."""
|
"""Turn the bulb on."""
|
||||||
await self.set_light_state({"on_off": 1})
|
await self.set_light_state({"on_off": 1})
|
||||||
|
|
||||||
async def get_has_emeter(self) -> bool:
|
@property
|
||||||
|
@requires_update
|
||||||
|
def has_emeter(self) -> bool:
|
||||||
"""Return that the bulb has an emeter."""
|
"""Return that the bulb has an emeter."""
|
||||||
return True
|
return True
|
||||||
|
@ -79,6 +79,26 @@ class EmeterStatus(dict):
|
|||||||
raise SmartDeviceException("Unable to find a value for '%s'" % item)
|
raise SmartDeviceException("Unable to find a value for '%s'" % item)
|
||||||
|
|
||||||
|
|
||||||
|
def requires_update(f):
|
||||||
|
"""Indicate that `update` should be called before accessing this method.""" # noqa: D202
|
||||||
|
if inspect.iscoroutinefunction(f):
|
||||||
|
@functools.wraps(f)
|
||||||
|
async def wrapped(*args, **kwargs):
|
||||||
|
self = args[0]
|
||||||
|
assert self._sys_info is not None
|
||||||
|
return await f(*args, **kwargs)
|
||||||
|
|
||||||
|
else:
|
||||||
|
@functools.wraps(f)
|
||||||
|
def wrapped(*args, **kwargs):
|
||||||
|
self = args[0]
|
||||||
|
assert self._sys_info is not None
|
||||||
|
return f(*args, **kwargs)
|
||||||
|
|
||||||
|
f.requires_update = True
|
||||||
|
return wrapped
|
||||||
|
|
||||||
|
|
||||||
class SmartDevice:
|
class SmartDevice:
|
||||||
"""Base class for all supported device types."""
|
"""Base class for all supported device types."""
|
||||||
|
|
||||||
@ -117,6 +137,7 @@ class SmartDevice:
|
|||||||
self._device_type = DeviceType.Unknown
|
self._device_type = DeviceType.Unknown
|
||||||
self.ioloop = ioloop or asyncio.get_event_loop()
|
self.ioloop = ioloop or asyncio.get_event_loop()
|
||||||
self.sync = SyncSmartDevice(self, ioloop=self.ioloop)
|
self.sync = SyncSmartDevice(self, ioloop=self.ioloop)
|
||||||
|
self._sys_info = None
|
||||||
|
|
||||||
def _result_from_cache(self, target, cmd) -> Optional[Dict]:
|
def _result_from_cache(self, target, cmd) -> Optional[Dict]:
|
||||||
"""Return query result from cache if still fresh.
|
"""Return query result from cache if still fresh.
|
||||||
@ -197,7 +218,7 @@ class SmartDevice:
|
|||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
async def get_has_emeter(self) -> bool:
|
def has_emeter(self) -> bool:
|
||||||
"""Return if device has an energy meter.
|
"""Return if device has an energy meter.
|
||||||
|
|
||||||
:return: True if energey meter is available
|
:return: True if energey meter is available
|
||||||
@ -214,23 +235,45 @@ class SmartDevice:
|
|||||||
"""
|
"""
|
||||||
return await self._query_helper("system", "get_sysinfo")
|
return await self._query_helper("system", "get_sysinfo")
|
||||||
|
|
||||||
async def get_model(self) -> str:
|
async def update(self):
|
||||||
|
"""Update some of the attributes.
|
||||||
|
|
||||||
|
Needed for methods that are decorated with `requires_update`.
|
||||||
|
"""
|
||||||
|
self._sys_info = await self.get_sys_info()
|
||||||
|
|
||||||
|
@property
|
||||||
|
@requires_update
|
||||||
|
def sys_info(self) -> Dict[str, Any]:
|
||||||
|
"""Retrieve system information.
|
||||||
|
|
||||||
|
:return: sysinfo
|
||||||
|
:rtype dict
|
||||||
|
:raises SmartDeviceException: on error
|
||||||
|
"""
|
||||||
|
return self._sys_info
|
||||||
|
|
||||||
|
@property
|
||||||
|
@requires_update
|
||||||
|
def model(self) -> str:
|
||||||
"""Return device model.
|
"""Return device model.
|
||||||
|
|
||||||
:return: device model
|
:return: device model
|
||||||
:rtype: str
|
:rtype: str
|
||||||
:raises SmartDeviceException: on error
|
:raises SmartDeviceException: on error
|
||||||
"""
|
"""
|
||||||
sys_info = await self.get_sys_info()
|
sys_info = self.sys_info
|
||||||
return str(sys_info["model"])
|
return str(sys_info["model"])
|
||||||
|
|
||||||
async def get_alias(self) -> str:
|
@property
|
||||||
|
@requires_update
|
||||||
|
def alias(self) -> str:
|
||||||
"""Return device name (alias).
|
"""Return device name (alias).
|
||||||
|
|
||||||
:return: Device name aka alias.
|
:return: Device name aka alias.
|
||||||
:rtype: str
|
:rtype: str
|
||||||
"""
|
"""
|
||||||
sys_info = await self.get_sys_info()
|
sys_info = self.sys_info
|
||||||
return str(sys_info["alias"])
|
return str(sys_info["alias"])
|
||||||
|
|
||||||
async def set_alias(self, alias: str) -> None:
|
async def set_alias(self, alias: str) -> None:
|
||||||
@ -240,6 +283,7 @@ class SmartDevice:
|
|||||||
:raises SmartDeviceException: on error
|
:raises SmartDeviceException: on error
|
||||||
"""
|
"""
|
||||||
await self._query_helper("system", "set_dev_alias", {"alias": alias})
|
await self._query_helper("system", "set_dev_alias", {"alias": alias})
|
||||||
|
await self.update()
|
||||||
|
|
||||||
async def get_icon(self) -> Dict:
|
async def get_icon(self) -> Dict:
|
||||||
"""Return device icon.
|
"""Return device icon.
|
||||||
@ -329,7 +373,9 @@ class SmartDevice:
|
|||||||
"""
|
"""
|
||||||
return await self._query_helper("time", "get_timezone")
|
return await self._query_helper("time", "get_timezone")
|
||||||
|
|
||||||
async def get_hw_info(self) -> Dict:
|
@property
|
||||||
|
@requires_update
|
||||||
|
def hw_info(self) -> Dict:
|
||||||
"""Return hardware information.
|
"""Return hardware information.
|
||||||
|
|
||||||
:return: Information about hardware
|
:return: Information about hardware
|
||||||
@ -347,47 +393,53 @@ class SmartDevice:
|
|||||||
"oemId",
|
"oemId",
|
||||||
"dev_name",
|
"dev_name",
|
||||||
]
|
]
|
||||||
sys_info = await self.get_sys_info()
|
sys_info = self.sys_info
|
||||||
return {key: sys_info[key] for key in keys if key in sys_info}
|
return {key: sys_info[key] for key in keys if key in sys_info}
|
||||||
|
|
||||||
async def get_location(self) -> Dict:
|
@property
|
||||||
|
@requires_update
|
||||||
|
def location(self) -> Dict:
|
||||||
"""Return geographical location.
|
"""Return geographical location.
|
||||||
|
|
||||||
:return: latitude and longitude
|
:return: latitude and longitude
|
||||||
:rtype: dict
|
:rtype: dict
|
||||||
"""
|
"""
|
||||||
info = await self.get_sys_info()
|
sys_info = self.sys_info
|
||||||
loc = {"latitude": None, "longitude": None}
|
loc = {"latitude": None, "longitude": None}
|
||||||
|
|
||||||
if "latitude" in info and "longitude" in info:
|
if "latitude" in sys_info and "longitude" in sys_info:
|
||||||
loc["latitude"] = info["latitude"]
|
loc["latitude"] = sys_info["latitude"]
|
||||||
loc["longitude"] = info["longitude"]
|
loc["longitude"] = sys_info["longitude"]
|
||||||
elif "latitude_i" in info and "longitude_i" in info:
|
elif "latitude_i" in sys_info and "longitude_i" in sys_info:
|
||||||
loc["latitude"] = info["latitude_i"]
|
loc["latitude"] = sys_info["latitude_i"]
|
||||||
loc["longitude"] = info["longitude_i"]
|
loc["longitude"] = sys_info["longitude_i"]
|
||||||
else:
|
else:
|
||||||
_LOGGER.warning("Unsupported device location.")
|
_LOGGER.warning("Unsupported device location.")
|
||||||
|
|
||||||
return loc
|
return loc
|
||||||
|
|
||||||
async def get_rssi(self) -> Optional[int]:
|
@property
|
||||||
|
@requires_update
|
||||||
|
def rssi(self) -> Optional[int]:
|
||||||
"""Return WiFi signal strenth (rssi).
|
"""Return WiFi signal strenth (rssi).
|
||||||
|
|
||||||
:return: rssi
|
:return: rssi
|
||||||
:rtype: int
|
:rtype: int
|
||||||
"""
|
"""
|
||||||
sys_info = await self.get_sys_info()
|
sys_info = self.sys_info
|
||||||
if "rssi" in sys_info:
|
if "rssi" in sys_info:
|
||||||
return int(sys_info["rssi"])
|
return int(sys_info["rssi"])
|
||||||
return None
|
return None
|
||||||
|
|
||||||
async def get_mac(self) -> str:
|
@property
|
||||||
|
@requires_update
|
||||||
|
def mac(self) -> str:
|
||||||
"""Return mac address.
|
"""Return mac address.
|
||||||
|
|
||||||
:return: mac address in hexadecimal with colons, e.g. 01:23:45:67:89:ab
|
:return: mac address in hexadecimal with colons, e.g. 01:23:45:67:89:ab
|
||||||
:rtype: str
|
:rtype: str
|
||||||
"""
|
"""
|
||||||
sys_info = await self.get_sys_info()
|
sys_info = self.sys_info
|
||||||
|
|
||||||
if "mac" in sys_info:
|
if "mac" in sys_info:
|
||||||
return str(sys_info["mac"])
|
return str(sys_info["mac"])
|
||||||
@ -407,7 +459,9 @@ class SmartDevice:
|
|||||||
:raises SmartDeviceException: on error
|
:raises SmartDeviceException: on error
|
||||||
"""
|
"""
|
||||||
await self._query_helper("system", "set_mac_addr", {"mac": mac})
|
await self._query_helper("system", "set_mac_addr", {"mac": mac})
|
||||||
|
await self.update()
|
||||||
|
|
||||||
|
@requires_update
|
||||||
async def get_emeter_realtime(self) -> EmeterStatus:
|
async def get_emeter_realtime(self) -> EmeterStatus:
|
||||||
"""Retrieve current energy readings.
|
"""Retrieve current energy readings.
|
||||||
|
|
||||||
@ -415,11 +469,12 @@ class SmartDevice:
|
|||||||
:rtype: dict, None
|
:rtype: dict, None
|
||||||
:raises SmartDeviceException: on error
|
:raises SmartDeviceException: on error
|
||||||
"""
|
"""
|
||||||
if not await self.get_has_emeter():
|
if not self.has_emeter:
|
||||||
raise SmartDeviceException("Device has no emeter")
|
raise SmartDeviceException("Device has no emeter")
|
||||||
|
|
||||||
return EmeterStatus(await self._query_helper(self.emeter_type, "get_realtime"))
|
return EmeterStatus(await self._query_helper(self.emeter_type, "get_realtime"))
|
||||||
|
|
||||||
|
@requires_update
|
||||||
async def get_emeter_daily(
|
async def get_emeter_daily(
|
||||||
self, year: int = None, month: int = None, kwh: bool = True
|
self, year: int = None, month: int = None, kwh: bool = True
|
||||||
) -> Dict:
|
) -> Dict:
|
||||||
@ -433,7 +488,7 @@ class SmartDevice:
|
|||||||
:rtype: dict
|
:rtype: dict
|
||||||
:raises SmartDeviceException: on error
|
:raises SmartDeviceException: on error
|
||||||
"""
|
"""
|
||||||
if not await self.get_has_emeter():
|
if not self.has_emeter:
|
||||||
raise SmartDeviceException("Device has no emeter")
|
raise SmartDeviceException("Device has no emeter")
|
||||||
|
|
||||||
if year is None:
|
if year is None:
|
||||||
@ -454,6 +509,7 @@ class SmartDevice:
|
|||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
@requires_update
|
||||||
async def get_emeter_monthly(self, year: int = None, kwh: bool = True) -> Dict:
|
async def get_emeter_monthly(self, year: int = None, kwh: bool = True) -> Dict:
|
||||||
"""Retrieve monthly statistics for a given year.
|
"""Retrieve monthly statistics for a given year.
|
||||||
|
|
||||||
@ -463,7 +519,7 @@ class SmartDevice:
|
|||||||
:rtype: dict
|
:rtype: dict
|
||||||
:raises SmartDeviceException: on error
|
:raises SmartDeviceException: on error
|
||||||
"""
|
"""
|
||||||
if not await self.get_has_emeter():
|
if not self.has_emeter:
|
||||||
raise SmartDeviceException("Device has no emeter")
|
raise SmartDeviceException("Device has no emeter")
|
||||||
|
|
||||||
if year is None:
|
if year is None:
|
||||||
@ -480,24 +536,27 @@ class SmartDevice:
|
|||||||
|
|
||||||
return {entry["month"]: entry[key] for entry in response}
|
return {entry["month"]: entry[key] for entry in response}
|
||||||
|
|
||||||
|
@requires_update
|
||||||
async def erase_emeter_stats(self):
|
async def erase_emeter_stats(self):
|
||||||
"""Erase energy meter statistics.
|
"""Erase energy meter statistics.
|
||||||
|
|
||||||
:return: True if statistics were deleted
|
:return: True if statistics were deleted
|
||||||
:raises SmartDeviceException: on error
|
:raises SmartDeviceException: on error
|
||||||
"""
|
"""
|
||||||
if not await self.get_has_emeter():
|
if not self.has_emeter:
|
||||||
raise SmartDeviceException("Device has no emeter")
|
raise SmartDeviceException("Device has no emeter")
|
||||||
|
|
||||||
await self._query_helper(self.emeter_type, "erase_emeter_stat", None)
|
await self._query_helper(self.emeter_type, "erase_emeter_stat", None)
|
||||||
|
await self.update()
|
||||||
|
|
||||||
|
@requires_update
|
||||||
async def current_consumption(self) -> float:
|
async def current_consumption(self) -> float:
|
||||||
"""Get the current power consumption in Watt.
|
"""Get the current power consumption in Watt.
|
||||||
|
|
||||||
:return: the current power consumption in Watts.
|
:return: the current power consumption in Watts.
|
||||||
:raises SmartDeviceException: on error
|
:raises SmartDeviceException: on error
|
||||||
"""
|
"""
|
||||||
if not await self.get_has_emeter():
|
if not self.has_emeter:
|
||||||
raise SmartDeviceException("Device has no emeter")
|
raise SmartDeviceException("Device has no emeter")
|
||||||
|
|
||||||
response = EmeterStatus(await self.get_emeter_realtime())
|
response = EmeterStatus(await self.get_emeter_realtime())
|
||||||
@ -518,19 +577,23 @@ class SmartDevice:
|
|||||||
"""Turn off the device."""
|
"""Turn off the device."""
|
||||||
raise NotImplementedError("Device subclass needs to implement this.")
|
raise NotImplementedError("Device subclass needs to implement this.")
|
||||||
|
|
||||||
async def is_off(self) -> bool:
|
@property
|
||||||
|
@requires_update
|
||||||
|
def is_off(self) -> bool:
|
||||||
"""Return True if device is off.
|
"""Return True if device is off.
|
||||||
|
|
||||||
:return: True if device is off, False otherwise.
|
:return: True if device is off, False otherwise.
|
||||||
:rtype: bool
|
:rtype: bool
|
||||||
"""
|
"""
|
||||||
return not await self.is_on()
|
return not self.is_on
|
||||||
|
|
||||||
async def turn_on(self) -> None:
|
async def turn_on(self) -> None:
|
||||||
"""Turn device on."""
|
"""Turn device on."""
|
||||||
raise NotImplementedError("Device subclass needs to implement this.")
|
raise NotImplementedError("Device subclass needs to implement this.")
|
||||||
|
|
||||||
async def is_on(self) -> bool:
|
@property
|
||||||
|
@requires_update
|
||||||
|
def is_on(self) -> bool:
|
||||||
"""Return if the device is on.
|
"""Return if the device is on.
|
||||||
|
|
||||||
:return: True if the device is on, False otherwise.
|
:return: True if the device is on, False otherwise.
|
||||||
@ -539,7 +602,9 @@ class SmartDevice:
|
|||||||
"""
|
"""
|
||||||
raise NotImplementedError("Device subclass needs to implement this.")
|
raise NotImplementedError("Device subclass needs to implement this.")
|
||||||
|
|
||||||
async def get_state_information(self) -> Dict[str, Any]:
|
@property
|
||||||
|
@requires_update
|
||||||
|
def state_information(self) -> Dict[str, Any]:
|
||||||
"""Return device-type specific, end-user friendly state information.
|
"""Return device-type specific, end-user friendly state information.
|
||||||
|
|
||||||
:return: dict with state information.
|
:return: dict with state information.
|
||||||
@ -567,22 +632,25 @@ class SmartDevice:
|
|||||||
"""Return True if the device is a strip."""
|
"""Return True if the device is a strip."""
|
||||||
return self._device_type == DeviceType.Strip
|
return self._device_type == DeviceType.Strip
|
||||||
|
|
||||||
async def is_dimmable(self):
|
@property
|
||||||
|
def is_dimmable(self):
|
||||||
"""Return True if the device is dimmable."""
|
"""Return True if the device is dimmable."""
|
||||||
return False
|
return False
|
||||||
|
|
||||||
async def is_variable_color_temp(self) -> bool:
|
@property
|
||||||
|
def is_variable_color_temp(self) -> bool:
|
||||||
"""Return True if the device supports color temperature."""
|
"""Return True if the device supports color temperature."""
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
|
self.sync.update()
|
||||||
return "<{} model {} at {} ({}), is_on: {} - dev specific: {}>".format(
|
return "<{} model {} at {} ({}), is_on: {} - dev specific: {}>".format(
|
||||||
self.__class__.__name__,
|
self.__class__.__name__,
|
||||||
self.sync.get_model(),
|
self.model,
|
||||||
self.host,
|
self.host,
|
||||||
self.sync.get_alias(),
|
self.alias,
|
||||||
self.sync.is_on(),
|
self.is_on,
|
||||||
self.sync.get_state_information(),
|
self.sync.state_information,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -4,7 +4,12 @@ import logging
|
|||||||
from typing import Any, Dict
|
from typing import Any, Dict
|
||||||
|
|
||||||
from pyHS100.protocol import TPLinkSmartHomeProtocol
|
from pyHS100.protocol import TPLinkSmartHomeProtocol
|
||||||
from pyHS100.smartdevice import DeviceType, SmartDevice, SmartDeviceException
|
from pyHS100.smartdevice import (
|
||||||
|
DeviceType,
|
||||||
|
SmartDevice,
|
||||||
|
SmartDeviceException,
|
||||||
|
requires_update,
|
||||||
|
)
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -17,14 +22,14 @@ class SmartPlug(SmartDevice):
|
|||||||
p = SmartPlug("192.168.1.105")
|
p = SmartPlug("192.168.1.105")
|
||||||
|
|
||||||
# print the devices alias
|
# print the devices alias
|
||||||
print(p.sync.get_alias())
|
print(p.sync.alias)
|
||||||
|
|
||||||
# change state of plug
|
# change state of plug
|
||||||
p.sync.turn_on()
|
p.sync.turn_on()
|
||||||
p.sync.turn_off()
|
p.sync.turn_off()
|
||||||
|
|
||||||
# query and print current state of plug
|
# query and print current state of plug
|
||||||
print(p.sync.get_state_information())
|
print(p.sync.state_information)
|
||||||
```
|
```
|
||||||
|
|
||||||
Omit the `sync` attribute to get coroutines.
|
Omit the `sync` attribute to get coroutines.
|
||||||
@ -46,7 +51,9 @@ class SmartPlug(SmartDevice):
|
|||||||
self.emeter_type = "emeter"
|
self.emeter_type = "emeter"
|
||||||
self._device_type = DeviceType.Plug
|
self._device_type = DeviceType.Plug
|
||||||
|
|
||||||
async def get_brightness(self) -> int:
|
@property
|
||||||
|
@requires_update
|
||||||
|
def brightness(self) -> int:
|
||||||
"""Return current brightness on dimmers.
|
"""Return current brightness on dimmers.
|
||||||
|
|
||||||
Will return a range between 0 - 100.
|
Will return a range between 0 - 100.
|
||||||
@ -54,12 +61,13 @@ class SmartPlug(SmartDevice):
|
|||||||
:returns: integer
|
:returns: integer
|
||||||
:rtype: int
|
:rtype: int
|
||||||
"""
|
"""
|
||||||
if not await self.is_dimmable():
|
if not self.is_dimmable:
|
||||||
raise SmartDeviceException("Device is not dimmable.")
|
raise SmartDeviceException("Device is not dimmable.")
|
||||||
|
|
||||||
sys_info = await self.get_sys_info()
|
sys_info = self.sys_info
|
||||||
return int(sys_info["brightness"])
|
return int(sys_info["brightness"])
|
||||||
|
|
||||||
|
@requires_update
|
||||||
async def set_brightness(self, value: int):
|
async def set_brightness(self, value: int):
|
||||||
"""Set the new dimmer brightness level.
|
"""Set the new dimmer brightness level.
|
||||||
|
|
||||||
@ -70,7 +78,7 @@ class SmartPlug(SmartDevice):
|
|||||||
:param value: integer between 1 and 100
|
:param value: integer between 1 and 100
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if not await self.is_dimmable():
|
if not self.is_dimmable:
|
||||||
raise SmartDeviceException("Device is not dimmable.")
|
raise SmartDeviceException("Device is not dimmable.")
|
||||||
|
|
||||||
if not isinstance(value, int):
|
if not isinstance(value, int):
|
||||||
@ -80,34 +88,41 @@ class SmartPlug(SmartDevice):
|
|||||||
await self._query_helper(
|
await self._query_helper(
|
||||||
"smartlife.iot.dimmer", "set_brightness", {"brightness": value}
|
"smartlife.iot.dimmer", "set_brightness", {"brightness": value}
|
||||||
)
|
)
|
||||||
|
await self.update()
|
||||||
else:
|
else:
|
||||||
raise ValueError("Brightness value %s is not valid." % value)
|
raise ValueError("Brightness value %s is not valid." % value)
|
||||||
|
|
||||||
async def is_dimmable(self):
|
@property
|
||||||
|
@requires_update
|
||||||
|
def is_dimmable(self):
|
||||||
"""Whether the switch supports brightness changes.
|
"""Whether the switch supports brightness changes.
|
||||||
|
|
||||||
:return: True if switch supports brightness changes, False otherwise
|
:return: True if switch supports brightness changes, False otherwise
|
||||||
:rtype: bool
|
:rtype: bool
|
||||||
"""
|
"""
|
||||||
sys_info = await self.get_sys_info()
|
sys_info = self.sys_info
|
||||||
return "brightness" in sys_info
|
return "brightness" in sys_info
|
||||||
|
|
||||||
async def get_has_emeter(self):
|
@property
|
||||||
|
@requires_update
|
||||||
|
def has_emeter(self):
|
||||||
"""Return whether device has an energy meter.
|
"""Return whether device has an energy meter.
|
||||||
|
|
||||||
:return: True if energy meter is available
|
:return: True if energy meter is available
|
||||||
False otherwise
|
False otherwise
|
||||||
"""
|
"""
|
||||||
sys_info = await self.get_sys_info()
|
sys_info = self.sys_info
|
||||||
features = sys_info["feature"].split(":")
|
features = sys_info["feature"].split(":")
|
||||||
return "ENE" in features
|
return "ENE" in features
|
||||||
|
|
||||||
async def is_on(self) -> bool:
|
@property
|
||||||
|
@requires_update
|
||||||
|
def is_on(self) -> bool:
|
||||||
"""Return whether device is on.
|
"""Return whether device is on.
|
||||||
|
|
||||||
:return: True if device is on, False otherwise
|
:return: True if device is on, False otherwise
|
||||||
"""
|
"""
|
||||||
sys_info = await self.get_sys_info()
|
sys_info = self.sys_info
|
||||||
return bool(sys_info["relay_state"])
|
return bool(sys_info["relay_state"])
|
||||||
|
|
||||||
async def turn_on(self):
|
async def turn_on(self):
|
||||||
@ -116,6 +131,7 @@ class SmartPlug(SmartDevice):
|
|||||||
:raises SmartDeviceException: on error
|
:raises SmartDeviceException: on error
|
||||||
"""
|
"""
|
||||||
await self._query_helper("system", "set_relay_state", {"state": 1})
|
await self._query_helper("system", "set_relay_state", {"state": 1})
|
||||||
|
await self.update()
|
||||||
|
|
||||||
async def turn_off(self):
|
async def turn_off(self):
|
||||||
"""Turn the switch off.
|
"""Turn the switch off.
|
||||||
@ -123,14 +139,17 @@ class SmartPlug(SmartDevice):
|
|||||||
:raises SmartDeviceException: on error
|
:raises SmartDeviceException: on error
|
||||||
"""
|
"""
|
||||||
await self._query_helper("system", "set_relay_state", {"state": 0})
|
await self._query_helper("system", "set_relay_state", {"state": 0})
|
||||||
|
await self.update()
|
||||||
|
|
||||||
async def get_led(self) -> bool:
|
@property
|
||||||
|
@requires_update
|
||||||
|
def led(self) -> bool:
|
||||||
"""Return the state of the led.
|
"""Return the state of the led.
|
||||||
|
|
||||||
:return: True if led is on, False otherwise
|
:return: True if led is on, False otherwise
|
||||||
:rtype: bool
|
:rtype: bool
|
||||||
"""
|
"""
|
||||||
sys_info = await self.get_sys_info()
|
sys_info = self.sys_info
|
||||||
return bool(1 - sys_info["led_off"])
|
return bool(1 - sys_info["led_off"])
|
||||||
|
|
||||||
async def set_led(self, state: bool):
|
async def set_led(self, state: bool):
|
||||||
@ -140,14 +159,17 @@ class SmartPlug(SmartDevice):
|
|||||||
:raises SmartDeviceException: on error
|
:raises SmartDeviceException: on error
|
||||||
"""
|
"""
|
||||||
await self._query_helper("system", "set_led_off", {"off": int(not state)})
|
await self._query_helper("system", "set_led_off", {"off": int(not state)})
|
||||||
|
await self.update()
|
||||||
|
|
||||||
async def get_on_since(self) -> datetime.datetime:
|
@property
|
||||||
|
@requires_update
|
||||||
|
def on_since(self) -> datetime.datetime:
|
||||||
"""Return pretty-printed on-time.
|
"""Return pretty-printed on-time.
|
||||||
|
|
||||||
:return: datetime for on since
|
:return: datetime for on since
|
||||||
:rtype: datetime
|
:rtype: datetime
|
||||||
"""
|
"""
|
||||||
sys_info = await self.get_sys_info()
|
sys_info = self.sys_info
|
||||||
if self.context:
|
if self.context:
|
||||||
for plug in sys_info["children"]:
|
for plug in sys_info["children"]:
|
||||||
if plug["id"] == self.context:
|
if plug["id"] == self.context:
|
||||||
@ -158,16 +180,15 @@ class SmartPlug(SmartDevice):
|
|||||||
|
|
||||||
return datetime.datetime.now() - datetime.timedelta(seconds=on_time)
|
return datetime.datetime.now() - datetime.timedelta(seconds=on_time)
|
||||||
|
|
||||||
async def get_state_information(self) -> Dict[str, Any]:
|
@property
|
||||||
|
@requires_update
|
||||||
|
def state_information(self) -> Dict[str, Any]:
|
||||||
"""Return switch-specific state information.
|
"""Return switch-specific state information.
|
||||||
|
|
||||||
:return: Switch information dict, keys in user-presentable form.
|
:return: Switch information dict, keys in user-presentable form.
|
||||||
:rtype: dict
|
:rtype: dict
|
||||||
"""
|
"""
|
||||||
info = {
|
info = {"LED state": self.led, "On since": self.on_since}
|
||||||
"LED state": await self.get_led(),
|
if self.is_dimmable:
|
||||||
"On since": await self.get_on_since(),
|
info["Brightness"] = self.brightness
|
||||||
}
|
|
||||||
if await self.is_dimmable():
|
|
||||||
info["Brightness"] = await self.get_brightness()
|
|
||||||
return info
|
return info
|
||||||
|
@ -8,7 +8,7 @@ from collections import defaultdict
|
|||||||
from typing import Any, DefaultDict, Dict, List
|
from typing import Any, DefaultDict, Dict, List
|
||||||
|
|
||||||
from pyHS100.protocol import TPLinkSmartHomeProtocol
|
from pyHS100.protocol import TPLinkSmartHomeProtocol
|
||||||
from pyHS100.smartdevice import DeviceType
|
from pyHS100.smartdevice import DeviceType, requires_update
|
||||||
from pyHS100.smartplug import SmartPlug
|
from pyHS100.smartplug import SmartPlug
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
@ -22,7 +22,7 @@ class SmartStrip(SmartPlug):
|
|||||||
p = SmartStrip("192.168.1.105")
|
p = SmartStrip("192.168.1.105")
|
||||||
|
|
||||||
# query the state of the strip
|
# query the state of the strip
|
||||||
print(p.sync.is_on())
|
print(p.is_on)
|
||||||
|
|
||||||
# change state of all outlets
|
# change state of all outlets
|
||||||
p.sync.turn_on()
|
p.sync.turn_on()
|
||||||
@ -30,7 +30,7 @@ class SmartStrip(SmartPlug):
|
|||||||
|
|
||||||
# individual outlets are accessible through plugs variable
|
# individual outlets are accessible through plugs variable
|
||||||
for plug in p.plugs:
|
for plug in p.plugs:
|
||||||
print(f"{p}: {p.sync.is_on()}")
|
print(f"{p}: {p.is_on}")
|
||||||
|
|
||||||
# change state of a single outlet
|
# change state of a single outlet
|
||||||
p.plugs[0].sync.turn_on()
|
p.plugs[0].sync.turn_on()
|
||||||
@ -66,20 +66,32 @@ class SmartStrip(SmartPlug):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
async def is_on(self) -> bool:
|
@property
|
||||||
|
@requires_update
|
||||||
|
def is_on(self) -> bool:
|
||||||
"""Return if any of the outlets are on."""
|
"""Return if any of the outlets are on."""
|
||||||
for plug in self.plugs:
|
for plug in self.plugs:
|
||||||
is_on = await plug.is_on()
|
is_on = plug.is_on
|
||||||
if is_on:
|
if is_on:
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
async def update(self):
|
||||||
|
"""Update some of the attributes.
|
||||||
|
|
||||||
|
Needed for methods that are decorated with `requires_update`.
|
||||||
|
"""
|
||||||
|
await super().update()
|
||||||
|
for plug in self.plugs:
|
||||||
|
await plug.update()
|
||||||
|
|
||||||
async def turn_on(self):
|
async def turn_on(self):
|
||||||
"""Turn the strip on.
|
"""Turn the strip on.
|
||||||
|
|
||||||
:raises SmartDeviceException: on error
|
:raises SmartDeviceException: on error
|
||||||
"""
|
"""
|
||||||
await self._query_helper("system", "set_relay_state", {"state": 1})
|
await self._query_helper("system", "set_relay_state", {"state": 1})
|
||||||
|
await self.update()
|
||||||
|
|
||||||
async def turn_off(self):
|
async def turn_off(self):
|
||||||
"""Turn the strip off.
|
"""Turn the strip off.
|
||||||
@ -87,21 +99,26 @@ class SmartStrip(SmartPlug):
|
|||||||
:raises SmartDeviceException: on error
|
:raises SmartDeviceException: on error
|
||||||
"""
|
"""
|
||||||
await self._query_helper("system", "set_relay_state", {"state": 0})
|
await self._query_helper("system", "set_relay_state", {"state": 0})
|
||||||
|
await self.update()
|
||||||
|
|
||||||
async def get_on_since(self) -> datetime.datetime:
|
@property
|
||||||
|
@requires_update
|
||||||
|
def on_since(self) -> datetime.datetime:
|
||||||
"""Return the maximum on-time of all outlets."""
|
"""Return the maximum on-time of all outlets."""
|
||||||
return max([await plug.get_on_since() for plug in self.plugs])
|
return max(plug.on_since for plug in self.plugs)
|
||||||
|
|
||||||
async def state_information(self) -> Dict[str, Any]:
|
@property
|
||||||
|
@requires_update
|
||||||
|
def state_information(self) -> Dict[str, Any]:
|
||||||
"""Return strip-specific state information.
|
"""Return strip-specific state information.
|
||||||
|
|
||||||
:return: Strip information dict, keys in user-presentable form.
|
:return: Strip information dict, keys in user-presentable form.
|
||||||
:rtype: dict
|
:rtype: dict
|
||||||
"""
|
"""
|
||||||
state: Dict[str, Any] = {"LED state": await self.get_led()}
|
state: Dict[str, Any] = {"LED state": self.led}
|
||||||
for plug in self.plugs:
|
for plug in self.plugs:
|
||||||
if await plug.is_on():
|
if plug.is_on:
|
||||||
state["Plug %s on since" % str(plug)] = await plug.get_on_since()
|
state["Plug %s on since" % str(plug)] = self.on_since
|
||||||
|
|
||||||
return state
|
return state
|
||||||
|
|
||||||
@ -116,7 +133,7 @@ class SmartStrip(SmartPlug):
|
|||||||
|
|
||||||
return consumption
|
return consumption
|
||||||
|
|
||||||
async def icon(self) -> Dict:
|
async def get_icon(self) -> Dict:
|
||||||
"""Icon for the device.
|
"""Icon for the device.
|
||||||
|
|
||||||
Overriden to keep the API, as the SmartStrip and children do not
|
Overriden to keep the API, as the SmartStrip and children do not
|
||||||
@ -132,6 +149,7 @@ class SmartStrip(SmartPlug):
|
|||||||
"""
|
"""
|
||||||
return await super().set_alias(alias)
|
return await super().set_alias(alias)
|
||||||
|
|
||||||
|
@requires_update
|
||||||
async def get_emeter_daily(
|
async def get_emeter_daily(
|
||||||
self, year: int = None, month: int = None, kwh: bool = True
|
self, year: int = None, month: int = None, kwh: bool = True
|
||||||
) -> Dict:
|
) -> Dict:
|
||||||
@ -154,6 +172,7 @@ class SmartStrip(SmartPlug):
|
|||||||
emeter_daily[day] += value
|
emeter_daily[day] += value
|
||||||
return emeter_daily
|
return emeter_daily
|
||||||
|
|
||||||
|
@requires_update
|
||||||
async def get_emeter_monthly(self, year: int = None, kwh: bool = True) -> Dict:
|
async def get_emeter_monthly(self, year: int = None, kwh: bool = True) -> Dict:
|
||||||
"""Retrieve monthly statistics for a given year.
|
"""Retrieve monthly statistics for a given year.
|
||||||
|
|
||||||
@ -170,6 +189,7 @@ class SmartStrip(SmartPlug):
|
|||||||
emeter_monthly[month] += value
|
emeter_monthly[month] += value
|
||||||
return emeter_monthly
|
return emeter_monthly
|
||||||
|
|
||||||
|
@requires_update
|
||||||
async def erase_emeter_stats(self):
|
async def erase_emeter_stats(self):
|
||||||
"""Erase energy meter statistics for all plugs.
|
"""Erase energy meter statistics for all plugs.
|
||||||
|
|
||||||
|
@ -32,10 +32,11 @@ from .newfakes import (
|
|||||||
|
|
||||||
@plug
|
@plug
|
||||||
def test_plug_sysinfo(dev):
|
def test_plug_sysinfo(dev):
|
||||||
assert dev.sync.get_sys_info() is not None
|
dev.sync.update()
|
||||||
PLUG_SCHEMA(dev.sync.get_sys_info())
|
assert dev.sys_info is not None
|
||||||
|
PLUG_SCHEMA(dev.sys_info)
|
||||||
|
|
||||||
assert dev.sync.get_model() is not None
|
assert dev.model is not None
|
||||||
|
|
||||||
assert dev.device_type == DeviceType.Plug or dev.device_type == DeviceType.Strip
|
assert dev.device_type == DeviceType.Plug or dev.device_type == DeviceType.Strip
|
||||||
assert dev.is_plug or dev.is_strip
|
assert dev.is_plug or dev.is_strip
|
||||||
@ -43,23 +44,26 @@ def test_plug_sysinfo(dev):
|
|||||||
|
|
||||||
@bulb
|
@bulb
|
||||||
def test_bulb_sysinfo(dev):
|
def test_bulb_sysinfo(dev):
|
||||||
assert dev.sync.get_sys_info() is not None
|
dev.sync.update()
|
||||||
BULB_SCHEMA(dev.sync.get_sys_info())
|
assert dev.sys_info is not None
|
||||||
|
BULB_SCHEMA(dev.sys_info)
|
||||||
|
|
||||||
assert dev.sync.get_model() is not None
|
assert dev.model is not None
|
||||||
|
|
||||||
assert dev.device_type == DeviceType.Bulb
|
assert dev.device_type == DeviceType.Bulb
|
||||||
assert dev.is_bulb
|
assert dev.is_bulb
|
||||||
|
|
||||||
|
|
||||||
def test_state_info(dev):
|
def test_state_info(dev):
|
||||||
assert isinstance(dev.sync.get_state_information(), dict)
|
dev.sync.update()
|
||||||
|
assert isinstance(dev.sync.state_information, dict)
|
||||||
|
|
||||||
|
|
||||||
def test_invalid_connection(dev):
|
def test_invalid_connection(dev):
|
||||||
with patch.object(FakeTransportProtocol, "query", side_effect=SmartDeviceException):
|
with patch.object(FakeTransportProtocol, "query", side_effect=SmartDeviceException):
|
||||||
with pytest.raises(SmartDeviceException):
|
with pytest.raises(SmartDeviceException):
|
||||||
dev.sync.is_on()
|
dev.sync.update()
|
||||||
|
dev.is_on
|
||||||
|
|
||||||
|
|
||||||
def test_query_helper(dev):
|
def test_query_helper(dev):
|
||||||
@ -71,26 +75,28 @@ def test_query_helper(dev):
|
|||||||
@turn_on
|
@turn_on
|
||||||
def test_state(dev, turn_on):
|
def test_state(dev, turn_on):
|
||||||
handle_turn_on(dev, turn_on)
|
handle_turn_on(dev, turn_on)
|
||||||
orig_state = dev.sync.is_on()
|
dev.sync.update()
|
||||||
|
orig_state = dev.is_on
|
||||||
if orig_state:
|
if orig_state:
|
||||||
dev.sync.turn_off()
|
dev.sync.turn_off()
|
||||||
assert not dev.sync.is_on()
|
assert not dev.is_on
|
||||||
assert dev.sync.is_off()
|
assert dev.is_off
|
||||||
dev.sync.turn_on()
|
dev.sync.turn_on()
|
||||||
assert dev.sync.is_on()
|
assert dev.is_on
|
||||||
assert not dev.sync.is_off()
|
assert not dev.is_off
|
||||||
else:
|
else:
|
||||||
dev.sync.turn_on()
|
dev.sync.turn_on()
|
||||||
assert dev.sync.is_on()
|
assert dev.is_on
|
||||||
assert not dev.sync.is_off()
|
assert not dev.is_off
|
||||||
dev.sync.turn_off()
|
dev.sync.turn_off()
|
||||||
assert not dev.sync.is_on()
|
assert not dev.is_on
|
||||||
assert dev.sync.is_off()
|
assert dev.is_off
|
||||||
|
|
||||||
|
|
||||||
@no_emeter
|
@no_emeter
|
||||||
def test_no_emeter(dev):
|
def test_no_emeter(dev):
|
||||||
assert not dev.sync.get_has_emeter()
|
dev.sync.update()
|
||||||
|
assert not dev.has_emeter
|
||||||
|
|
||||||
with pytest.raises(SmartDeviceException):
|
with pytest.raises(SmartDeviceException):
|
||||||
dev.sync.get_emeter_realtime()
|
dev.sync.get_emeter_realtime()
|
||||||
@ -104,10 +110,11 @@ def test_no_emeter(dev):
|
|||||||
|
|
||||||
@has_emeter
|
@has_emeter
|
||||||
def test_get_emeter_realtime(dev):
|
def test_get_emeter_realtime(dev):
|
||||||
|
dev.sync.update()
|
||||||
if dev.is_strip:
|
if dev.is_strip:
|
||||||
pytest.skip("Disabled for HS300 temporarily")
|
pytest.skip("Disabled for HS300 temporarily")
|
||||||
|
|
||||||
assert dev.sync.get_has_emeter()
|
assert dev.has_emeter
|
||||||
|
|
||||||
current_emeter = dev.sync.get_emeter_realtime()
|
current_emeter = dev.sync.get_emeter_realtime()
|
||||||
CURRENT_CONSUMPTION_SCHEMA(current_emeter)
|
CURRENT_CONSUMPTION_SCHEMA(current_emeter)
|
||||||
@ -115,10 +122,11 @@ def test_get_emeter_realtime(dev):
|
|||||||
|
|
||||||
@has_emeter
|
@has_emeter
|
||||||
def test_get_emeter_daily(dev):
|
def test_get_emeter_daily(dev):
|
||||||
|
dev.sync.update()
|
||||||
if dev.is_strip:
|
if dev.is_strip:
|
||||||
pytest.skip("Disabled for HS300 temporarily")
|
pytest.skip("Disabled for HS300 temporarily")
|
||||||
|
|
||||||
assert dev.sync.get_has_emeter()
|
assert dev.has_emeter
|
||||||
|
|
||||||
assert dev.sync.get_emeter_daily(year=1900, month=1) == {}
|
assert dev.sync.get_emeter_daily(year=1900, month=1) == {}
|
||||||
|
|
||||||
@ -137,10 +145,11 @@ def test_get_emeter_daily(dev):
|
|||||||
|
|
||||||
@has_emeter
|
@has_emeter
|
||||||
def test_get_emeter_monthly(dev):
|
def test_get_emeter_monthly(dev):
|
||||||
|
dev.sync.update()
|
||||||
if dev.is_strip:
|
if dev.is_strip:
|
||||||
pytest.skip("Disabled for HS300 temporarily")
|
pytest.skip("Disabled for HS300 temporarily")
|
||||||
|
|
||||||
assert dev.sync.get_has_emeter()
|
assert dev.has_emeter
|
||||||
|
|
||||||
assert dev.sync.get_emeter_monthly(year=1900) == {}
|
assert dev.sync.get_emeter_monthly(year=1900) == {}
|
||||||
|
|
||||||
@ -159,10 +168,11 @@ def test_get_emeter_monthly(dev):
|
|||||||
|
|
||||||
@has_emeter
|
@has_emeter
|
||||||
def test_emeter_status(dev):
|
def test_emeter_status(dev):
|
||||||
|
dev.sync.update()
|
||||||
if dev.is_strip:
|
if dev.is_strip:
|
||||||
pytest.skip("Disabled for HS300 temporarily")
|
pytest.skip("Disabled for HS300 temporarily")
|
||||||
|
|
||||||
assert dev.sync.get_has_emeter()
|
assert dev.has_emeter
|
||||||
|
|
||||||
d = dev.sync.get_emeter_realtime()
|
d = dev.sync.get_emeter_realtime()
|
||||||
|
|
||||||
@ -181,17 +191,19 @@ def test_emeter_status(dev):
|
|||||||
@pytest.mark.skip("not clearing your stats..")
|
@pytest.mark.skip("not clearing your stats..")
|
||||||
@has_emeter
|
@has_emeter
|
||||||
def test_erase_emeter_stats(dev):
|
def test_erase_emeter_stats(dev):
|
||||||
assert dev.sync.get_has_emeter()
|
dev.sync.update()
|
||||||
|
assert dev.has_emeter
|
||||||
|
|
||||||
dev.sync.erase_emeter()
|
dev.sync.erase_emeter()
|
||||||
|
|
||||||
|
|
||||||
@has_emeter
|
@has_emeter
|
||||||
def test_current_consumption(dev):
|
def test_current_consumption(dev):
|
||||||
|
dev.sync.update()
|
||||||
if dev.is_strip:
|
if dev.is_strip:
|
||||||
pytest.skip("Disabled for HS300 temporarily")
|
pytest.skip("Disabled for HS300 temporarily")
|
||||||
|
|
||||||
if dev.sync.get_has_emeter():
|
if dev.has_emeter:
|
||||||
x = dev.sync.current_consumption()
|
x = dev.sync.current_consumption()
|
||||||
assert isinstance(x, float)
|
assert isinstance(x, float)
|
||||||
assert x >= 0.0
|
assert x >= 0.0
|
||||||
@ -200,34 +212,36 @@ def test_current_consumption(dev):
|
|||||||
|
|
||||||
|
|
||||||
def test_alias(dev):
|
def test_alias(dev):
|
||||||
|
dev.sync.update()
|
||||||
test_alias = "TEST1234"
|
test_alias = "TEST1234"
|
||||||
original = dev.sync.get_alias()
|
original = dev.sync.alias
|
||||||
|
|
||||||
assert isinstance(original, str)
|
assert isinstance(original, str)
|
||||||
|
|
||||||
dev.sync.set_alias(test_alias)
|
dev.sync.set_alias(test_alias)
|
||||||
assert dev.sync.get_alias() == test_alias
|
assert dev.sync.alias == test_alias
|
||||||
|
|
||||||
dev.sync.set_alias(original)
|
dev.sync.set_alias(original)
|
||||||
assert dev.sync.get_alias() == original
|
assert dev.sync.alias == original
|
||||||
|
|
||||||
|
|
||||||
@plug
|
@plug
|
||||||
def test_led(dev):
|
def test_led(dev):
|
||||||
original = dev.sync.get_led()
|
dev.sync.update()
|
||||||
|
original = dev.led
|
||||||
|
|
||||||
dev.sync.set_led(False)
|
dev.sync.set_led(False)
|
||||||
assert not dev.sync.get_led()
|
assert not dev.led
|
||||||
dev.sync.set_led(True)
|
|
||||||
|
|
||||||
assert dev.sync.get_led()
|
dev.sync.set_led(True)
|
||||||
|
assert dev.led
|
||||||
|
|
||||||
dev.sync.set_led(original)
|
dev.sync.set_led(original)
|
||||||
|
|
||||||
|
|
||||||
@plug
|
@plug
|
||||||
def test_on_since(dev):
|
def test_on_since(dev):
|
||||||
assert isinstance(dev.sync.get_on_since(), datetime.datetime)
|
dev.sync.update()
|
||||||
|
assert isinstance(dev.on_since, datetime.datetime)
|
||||||
|
|
||||||
|
|
||||||
def test_icon(dev):
|
def test_icon(dev):
|
||||||
@ -244,35 +258,41 @@ def test_timezone(dev):
|
|||||||
|
|
||||||
|
|
||||||
def test_hw_info(dev):
|
def test_hw_info(dev):
|
||||||
PLUG_SCHEMA(dev.sync.get_hw_info())
|
dev.sync.update()
|
||||||
|
PLUG_SCHEMA(dev.hw_info)
|
||||||
|
|
||||||
|
|
||||||
def test_location(dev):
|
def test_location(dev):
|
||||||
PLUG_SCHEMA(dev.sync.get_location())
|
dev.sync.update()
|
||||||
|
PLUG_SCHEMA(dev.location)
|
||||||
|
|
||||||
|
|
||||||
def test_rssi(dev):
|
def test_rssi(dev):
|
||||||
PLUG_SCHEMA({"rssi": dev.sync.get_rssi()}) # wrapping for vol
|
dev.sync.update()
|
||||||
|
PLUG_SCHEMA({"rssi": dev.rssi}) # wrapping for vol
|
||||||
|
|
||||||
|
|
||||||
def test_mac(dev):
|
def test_mac(dev):
|
||||||
PLUG_SCHEMA({"mac": dev.sync.get_mac()}) # wrapping for val
|
dev.sync.update()
|
||||||
|
PLUG_SCHEMA({"mac": dev.mac}) # wrapping for val
|
||||||
# TODO check setting?
|
# TODO check setting?
|
||||||
|
|
||||||
|
|
||||||
@non_variable_temp
|
@non_variable_temp
|
||||||
def test_temperature_on_nonsupporting(dev):
|
def test_temperature_on_nonsupporting(dev):
|
||||||
assert dev.sync.get_valid_temperature_range() == (0, 0)
|
dev.sync.update()
|
||||||
|
assert dev.valid_temperature_range == (0, 0)
|
||||||
|
|
||||||
# TODO test when device does not support temperature range
|
# TODO test when device does not support temperature range
|
||||||
with pytest.raises(SmartDeviceException):
|
with pytest.raises(SmartDeviceException):
|
||||||
dev.sync.set_color_temp(2700)
|
dev.sync.set_color_temp(2700)
|
||||||
with pytest.raises(SmartDeviceException):
|
with pytest.raises(SmartDeviceException):
|
||||||
print(dev.sync.get_color_temp())
|
print(dev.sync.color_temp)
|
||||||
|
|
||||||
|
|
||||||
@variable_temp
|
@variable_temp
|
||||||
def test_out_of_range_temperature(dev):
|
def test_out_of_range_temperature(dev):
|
||||||
|
dev.sync.update()
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
dev.sync.set_color_temp(1000)
|
dev.sync.set_color_temp(1000)
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
@ -281,10 +301,11 @@ def test_out_of_range_temperature(dev):
|
|||||||
|
|
||||||
@non_dimmable
|
@non_dimmable
|
||||||
def test_non_dimmable(dev):
|
def test_non_dimmable(dev):
|
||||||
assert not dev.sync.is_dimmable()
|
dev.sync.update()
|
||||||
|
assert not dev.is_dimmable
|
||||||
|
|
||||||
with pytest.raises(SmartDeviceException):
|
with pytest.raises(SmartDeviceException):
|
||||||
assert dev.sync.get_brightness() == 0
|
assert dev.brightness == 0
|
||||||
with pytest.raises(SmartDeviceException):
|
with pytest.raises(SmartDeviceException):
|
||||||
dev.sync.set_brightness(100)
|
dev.sync.set_brightness(100)
|
||||||
|
|
||||||
@ -293,13 +314,14 @@ def test_non_dimmable(dev):
|
|||||||
@turn_on
|
@turn_on
|
||||||
def test_dimmable_brightness(dev, turn_on):
|
def test_dimmable_brightness(dev, turn_on):
|
||||||
handle_turn_on(dev, turn_on)
|
handle_turn_on(dev, turn_on)
|
||||||
assert dev.sync.is_dimmable()
|
dev.sync.update()
|
||||||
|
assert dev.is_dimmable
|
||||||
|
|
||||||
dev.sync.set_brightness(50)
|
dev.sync.set_brightness(50)
|
||||||
assert dev.sync.get_brightness() == 50
|
assert dev.brightness == 50
|
||||||
|
|
||||||
dev.sync.set_brightness(10)
|
dev.sync.set_brightness(10)
|
||||||
assert dev.sync.get_brightness() == 10
|
assert dev.brightness == 10
|
||||||
|
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
dev.sync.set_brightness("foo")
|
dev.sync.set_brightness("foo")
|
||||||
@ -307,7 +329,8 @@ def test_dimmable_brightness(dev, turn_on):
|
|||||||
|
|
||||||
@dimmable
|
@dimmable
|
||||||
def test_invalid_brightness(dev):
|
def test_invalid_brightness(dev):
|
||||||
assert dev.sync.is_dimmable()
|
dev.sync.update()
|
||||||
|
assert dev.is_dimmable
|
||||||
|
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
dev.sync.set_brightness(110)
|
dev.sync.set_brightness(110)
|
||||||
@ -320,16 +343,17 @@ def test_invalid_brightness(dev):
|
|||||||
@turn_on
|
@turn_on
|
||||||
def test_hsv(dev, turn_on):
|
def test_hsv(dev, turn_on):
|
||||||
handle_turn_on(dev, turn_on)
|
handle_turn_on(dev, turn_on)
|
||||||
assert dev.sync.is_color()
|
dev.sync.update()
|
||||||
|
assert dev.is_color
|
||||||
|
|
||||||
hue, saturation, brightness = dev.sync.get_hsv()
|
hue, saturation, brightness = dev.hsv
|
||||||
assert 0 <= hue <= 255
|
assert 0 <= hue <= 255
|
||||||
assert 0 <= saturation <= 100
|
assert 0 <= saturation <= 100
|
||||||
assert 0 <= brightness <= 100
|
assert 0 <= brightness <= 100
|
||||||
|
|
||||||
dev.sync.set_hsv(hue=1, saturation=1, value=1)
|
dev.sync.set_hsv(hue=1, saturation=1, value=1)
|
||||||
|
|
||||||
hue, saturation, brightness = dev.sync.get_hsv()
|
hue, saturation, brightness = dev.hsv
|
||||||
assert hue == 1
|
assert hue == 1
|
||||||
assert saturation == 1
|
assert saturation == 1
|
||||||
assert brightness == 1
|
assert brightness == 1
|
||||||
@ -339,8 +363,8 @@ def test_hsv(dev, turn_on):
|
|||||||
@turn_on
|
@turn_on
|
||||||
def test_invalid_hsv(dev, turn_on):
|
def test_invalid_hsv(dev, turn_on):
|
||||||
handle_turn_on(dev, turn_on)
|
handle_turn_on(dev, turn_on)
|
||||||
|
dev.sync.update()
|
||||||
assert dev.sync.is_color()
|
assert dev.is_color
|
||||||
|
|
||||||
for invalid_hue in [-1, 361, 0.5]:
|
for invalid_hue in [-1, 361, 0.5]:
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
@ -357,67 +381,79 @@ def test_invalid_hsv(dev, turn_on):
|
|||||||
|
|
||||||
@non_color_bulb
|
@non_color_bulb
|
||||||
def test_hsv_on_non_color(dev):
|
def test_hsv_on_non_color(dev):
|
||||||
assert not dev.sync.is_color()
|
dev.sync.update()
|
||||||
|
assert not dev.is_color
|
||||||
|
|
||||||
with pytest.raises(SmartDeviceException):
|
with pytest.raises(SmartDeviceException):
|
||||||
dev.sync.set_hsv(0, 0, 0)
|
dev.sync.set_hsv(0, 0, 0)
|
||||||
with pytest.raises(SmartDeviceException):
|
with pytest.raises(SmartDeviceException):
|
||||||
print(dev.sync.get_hsv())
|
print(dev.hsv)
|
||||||
|
|
||||||
|
|
||||||
@variable_temp
|
@variable_temp
|
||||||
@turn_on
|
@turn_on
|
||||||
def test_try_set_colortemp(dev, turn_on):
|
def test_try_set_colortemp(dev, turn_on):
|
||||||
|
dev.sync.update()
|
||||||
handle_turn_on(dev, turn_on)
|
handle_turn_on(dev, turn_on)
|
||||||
|
|
||||||
dev.sync.set_color_temp(2700)
|
dev.sync.set_color_temp(2700)
|
||||||
assert dev.sync.get_color_temp() == 2700
|
assert dev.sync.color_temp == 2700
|
||||||
|
|
||||||
|
|
||||||
@non_variable_temp
|
@non_variable_temp
|
||||||
def test_non_variable_temp(dev):
|
def test_non_variable_temp(dev):
|
||||||
with pytest.raises(SmartDeviceException):
|
with pytest.raises(SmartDeviceException):
|
||||||
|
dev.sync.update()
|
||||||
dev.sync.set_color_temp(2700)
|
dev.sync.set_color_temp(2700)
|
||||||
|
|
||||||
|
|
||||||
@strip
|
@strip
|
||||||
@turn_on
|
@turn_on
|
||||||
def test_children_change_state(dev, turn_on):
|
def test_children_change_state(dev, turn_on):
|
||||||
|
dev.sync.update()
|
||||||
handle_turn_on(dev, turn_on)
|
handle_turn_on(dev, turn_on)
|
||||||
for plug in dev.plugs:
|
for plug in dev.plugs:
|
||||||
orig_state = plug.sync.is_on()
|
plug.sync.update()
|
||||||
|
orig_state = plug.is_on
|
||||||
if orig_state:
|
if orig_state:
|
||||||
plug.turn_off()
|
plug.turn_off()
|
||||||
assert not plug.sync.is_on()
|
plug.sync.update()
|
||||||
assert plug.sync.is_off()
|
assert not plug.is_on
|
||||||
|
assert plug.is_off
|
||||||
|
|
||||||
plug.sync.turn_on()
|
plug.sync.turn_on()
|
||||||
assert plug.sync.is_on()
|
plug.sync.update()
|
||||||
assert not plug.sync.is_off()
|
assert plug.is_on
|
||||||
|
assert not plug.is_off
|
||||||
else:
|
else:
|
||||||
plug.sync.turn_on()
|
plug.sync.turn_on()
|
||||||
assert plug.sync.is_on()
|
plug.sync.update()
|
||||||
assert not plug.sync.is_off()
|
assert plug.is_on
|
||||||
|
assert not plug.is_off
|
||||||
plug.sync.turn_off()
|
plug.sync.turn_off()
|
||||||
assert not plug.sync.is_on()
|
plug.sync.update()
|
||||||
assert plug.sync.is_off()
|
assert not plug.is_on
|
||||||
|
assert plug.is_off
|
||||||
|
|
||||||
|
|
||||||
@strip
|
@strip
|
||||||
def test_children_alias(dev):
|
def test_children_alias(dev):
|
||||||
test_alias = "TEST1234"
|
test_alias = "TEST1234"
|
||||||
for plug in dev.plugs:
|
for plug in dev.plugs:
|
||||||
original = plug.sync.get_alias()
|
plug.sync.update()
|
||||||
|
original = plug.alias
|
||||||
plug.sync.set_alias(alias=test_alias)
|
plug.sync.set_alias(alias=test_alias)
|
||||||
assert plug.sync.get_alias() == test_alias
|
plug.sync.update()
|
||||||
|
assert plug.alias == test_alias
|
||||||
plug.sync.set_alias(alias=original)
|
plug.sync.set_alias(alias=original)
|
||||||
assert plug.sync.get_alias() == original
|
plug.sync.update()
|
||||||
|
assert plug.alias == original
|
||||||
|
|
||||||
|
|
||||||
@strip
|
@strip
|
||||||
def test_children_on_since(dev):
|
def test_children_on_since(dev):
|
||||||
for plug in dev.plugs:
|
for plug in dev.plugs:
|
||||||
assert plug.sync.get_on_since()
|
plug.sync.update()
|
||||||
|
assert plug.on_since
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skip("this test will wear out your relays")
|
@pytest.mark.skip("this test will wear out your relays")
|
||||||
@ -435,7 +471,7 @@ def test_all_binary_states(dev):
|
|||||||
dev.sync.turn_off(index=plug_index)
|
dev.sync.turn_off(index=plug_index)
|
||||||
|
|
||||||
# check state map applied
|
# check state map applied
|
||||||
for index, state in dev.sync.get_is_on().items():
|
for index, state in dev.is_on.items():
|
||||||
assert state_map[index] == state
|
assert state_map[index] == state
|
||||||
|
|
||||||
# toggle each outlet with state map applied
|
# toggle each outlet with state map applied
|
||||||
@ -448,7 +484,7 @@ def test_all_binary_states(dev):
|
|||||||
dev.sync.turn_on(index=plug_index)
|
dev.sync.turn_on(index=plug_index)
|
||||||
|
|
||||||
# only target outlet should have state changed
|
# only target outlet should have state changed
|
||||||
for index, state in dev.sync.get_is_on().items():
|
for index, state in dev.is_on.items():
|
||||||
if index == plug_index:
|
if index == plug_index:
|
||||||
assert state != state_map[index]
|
assert state != state_map[index]
|
||||||
else:
|
else:
|
||||||
@ -461,15 +497,17 @@ def test_all_binary_states(dev):
|
|||||||
dev.sync.turn_off(index=plug_index)
|
dev.sync.turn_off(index=plug_index)
|
||||||
|
|
||||||
# original state map should be restored
|
# original state map should be restored
|
||||||
for index, state in dev.sync.get_is_on().items():
|
for index, state in dev.is_on.items():
|
||||||
assert state == state_map[index]
|
assert state == state_map[index]
|
||||||
|
|
||||||
|
|
||||||
@strip
|
@strip
|
||||||
def test_children_get_emeter_realtime(dev):
|
def test_children_get_emeter_realtime(dev):
|
||||||
assert dev.sync.get_has_emeter()
|
dev.sync.update()
|
||||||
|
assert dev.has_emeter
|
||||||
# test with index
|
# test with index
|
||||||
for plug in dev.plugs:
|
for plug in dev.plugs:
|
||||||
|
plug.sync.update()
|
||||||
emeter = plug.sync.get_emeter_realtime()
|
emeter = plug.sync.get_emeter_realtime()
|
||||||
CURRENT_CONSUMPTION_SCHEMA(emeter)
|
CURRENT_CONSUMPTION_SCHEMA(emeter)
|
||||||
|
|
||||||
@ -482,9 +520,11 @@ def test_children_get_emeter_realtime(dev):
|
|||||||
|
|
||||||
@strip
|
@strip
|
||||||
def test_children_get_emeter_daily(dev):
|
def test_children_get_emeter_daily(dev):
|
||||||
assert dev.sync.get_has_emeter()
|
dev.sync.update()
|
||||||
|
assert dev.has_emeter
|
||||||
# test individual emeters
|
# test individual emeters
|
||||||
for plug in dev.plugs:
|
for plug in dev.plugs:
|
||||||
|
plug.sync.update()
|
||||||
emeter = plug.sync.get_emeter_daily(year=1900, month=1)
|
emeter = plug.sync.get_emeter_daily(year=1900, month=1)
|
||||||
assert emeter == {}
|
assert emeter == {}
|
||||||
|
|
||||||
@ -505,9 +545,11 @@ def test_children_get_emeter_daily(dev):
|
|||||||
|
|
||||||
@strip
|
@strip
|
||||||
def test_children_get_emeter_monthly(dev):
|
def test_children_get_emeter_monthly(dev):
|
||||||
assert dev.sync.get_has_emeter()
|
dev.sync.update()
|
||||||
|
assert dev.has_emeter
|
||||||
# test individual emeters
|
# test individual emeters
|
||||||
for plug in dev.plugs:
|
for plug in dev.plugs:
|
||||||
|
plug.sync.update()
|
||||||
emeter = plug.sync.get_emeter_monthly(year=1900)
|
emeter = plug.sync.get_emeter_monthly(year=1900)
|
||||||
assert emeter == {}
|
assert emeter == {}
|
||||||
|
|
||||||
@ -539,9 +581,9 @@ def test_children_get_emeter_monthly(dev):
|
|||||||
# if dev.is_strip:
|
# if dev.is_strip:
|
||||||
# CHECK_COUNT = 0
|
# CHECK_COUNT = 0
|
||||||
|
|
||||||
# dev.sync.get_sys_info()
|
# dev.sys_info
|
||||||
# assert query_mock.call_count == CHECK_COUNT
|
# assert query_mock.call_count == CHECK_COUNT
|
||||||
# dev.sync.get_sys_info()
|
# dev.sys_info
|
||||||
# assert query_mock.call_count == CHECK_COUNT
|
# assert query_mock.call_count == CHECK_COUNT
|
||||||
|
|
||||||
|
|
||||||
@ -553,15 +595,14 @@ def test_children_get_emeter_monthly(dev):
|
|||||||
# with patch.object(
|
# with patch.object(
|
||||||
# FakeTransportProtocol, "query", wraps=dev.protocol.query
|
# FakeTransportProtocol, "query", wraps=dev.protocol.query
|
||||||
# ) as query_mock:
|
# ) as query_mock:
|
||||||
# dev.sync.get_sys_info()
|
# dev.sys_info
|
||||||
# assert query_mock.call_count == 1
|
# assert query_mock.call_count == 1
|
||||||
# dev.sync.get_sys_info()
|
# dev.sys_info
|
||||||
# assert query_mock.call_count == 2
|
# assert query_mock.call_count == 2
|
||||||
# # assert query_mock.called_once()
|
# # assert query_mock.called_once()
|
||||||
|
|
||||||
|
|
||||||
def test_representation(dev):
|
def test_representation(dev):
|
||||||
import re
|
import re
|
||||||
|
|
||||||
pattern = re.compile("<.* model .* at .* (.*), is_on: .* - dev specific: .*>")
|
pattern = re.compile("<.* model .* at .* (.*), is_on: .* - dev specific: .*>")
|
||||||
assert pattern.match(str(dev))
|
assert pattern.match(str(dev))
|
||||||
|
Loading…
x
Reference in New Issue
Block a user