From ae6f3517c711597eb8f9c0ad1093840e3a0d2984 Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Wed, 20 May 2020 12:51:44 +0200 Subject: [PATCH 01/35] restructure and improve gateway subdevices --- miio/gateway.py | 319 +++++++++++++++++++++++++++++++++++------------- 1 file changed, 237 insertions(+), 82 deletions(-) diff --git a/miio/gateway.py b/miio/gateway.py index b790bac35..8f024a411 100644 --- a/miio/gateway.py +++ b/miio/gateway.py @@ -1,4 +1,5 @@ import logging +import sys from datetime import datetime from enum import Enum, IntEnum from typing import Optional @@ -25,6 +26,7 @@ class DeviceType(IntEnum): + Unknown = -1 Gateway = 0 Switch = 1 Motion = 2 @@ -44,18 +46,6 @@ class DeviceType(IntEnum): AqaraRelayTwoChannels = 54 AqaraSquareButton = 62 - -class AqaraRelayToggleValue(Enum): - toggle = "toggle" - on = "on" - off = "off" - - -class AqaraRelayChannel(Enum): - first = "channel_0" - second = "channel_1" - - class Gateway(Device): """Main class representing the Xiaomi Gateway. @@ -104,6 +94,7 @@ def __init__( self._radio = GatewayRadio(self) self._zigbee = GatewayZigbee(self) self._light = GatewayLight(self) + self._devices = [] @property def alarm(self) -> "GatewayAlarm": @@ -126,58 +117,48 @@ def light(self) -> "GatewayLight": """Return light control interface.""" return self._light - @command() + @property def devices(self): - """Return list of devices.""" - # from https://github.com/aholstenson/miio/issues/26 - devices_raw = self.send("get_device_prop", ["lumi.0", "device_list"]) - devices = [ - SubDevice(self, *devices_raw[x : x + 5]) # noqa: E203 - for x in range(0, len(devices_raw), 5) - ] - - return devices + """Return a list of the already discovered devices.""" + return self._devices - @command(click.argument("sid"), click.argument("property")) - def get_device_prop(self, sid, property): + @command() + def discover_devices(self): + """Discovers SubDevices and returns a list of the discovered devices.""" + # from https://github.com/aholstenson/miio/issues/26 + devices_raw = self.get_gateway_prop("device_list") + self._devices = [] + thismodule = sys.modules[__name__] + + for x in range(0, len(devices_raw), 5): + try: + Device_name = DeviceType(devices_raw[x+1]).name + except ValueError: + _LOGGER.warning("Unknown subdevice type '%i' discovered, of Xiaomi gateway with ip: %s", devices_raw[x+1], self.ip) + Device_name = DeviceType(-1).name + + SubDeviceClass = getattr(thismodule, Device_name, SubDevice) + if SubDeviceClass == SubDevice and Device_name != DeviceType(-1).name: + _LOGGER.warning("Gateway device type '%s' does not have device specific methods defined, only basic default methods will be available", Device_name) + + self._devices.append(SubDeviceClass(self, *devices_raw[x : x + 5])) + + return self._devices + + @command(click.argument("property")) + def get_gateway_prop(self, property): """Get the value of a property for given sid.""" - return self.send("get_device_prop", [sid, property]) + return self.send("get_device_prop", ["lumi.0", property]) - @command(click.argument("sid"), click.argument("properties", nargs=-1)) - def get_device_prop_exp(self, sid, properties): + @command(click.argument("properties", nargs=-1)) + def get_gateway_prop_exp(self, properties): """Get the value of a bunch of properties for given sid.""" - return self.send("get_device_prop_exp", [[sid] + list(properties)]) + return self.send("get_device_prop_exp", [["lumi.0"] + list(properties)]) - @command(click.argument("sid"), click.argument("property"), click.argument("value")) - def set_device_prop(self, sid, property, value): + @command(click.argument("property"), click.argument("value")) + def set_gateway_prop(self, property, value): """Set the device property.""" - return self.send("set_device_prop", {"sid": sid, property: value}) - - @command( - click.argument("sid"), - click.argument("channel", type=EnumType(AqaraRelayChannel)), - click.argument("value", type=EnumType(AqaraRelayToggleValue)), - ) - def relay_toggle(self, sid, channel, value): - """Toggle Aqara Wireless Relay 2ch""" - return self.send( - "toggle_ctrl_neutral", - [channel.value, value.value], - extra_parameters={"sid": sid}, - )[0] - - @command( - click.argument("sid"), - click.argument("channel", type=EnumType(AqaraRelayChannel)), - ) - def relay_get_state(self, sid, channel): - """Get the state of Aqara Wireless Relay 2ch for given sid""" - return self.send("get_device_prop_exp", [[sid, channel.value]])[0][0] - - @command(click.argument("sid")) - def relay_get_load_power(self, sid): - """Get the the load power of Aqara Wireless Relay 2ch for given sid""" - return self.send("get_device_prop_exp", [[sid, "load_power"]])[0][0] + return self.send("set_device_prop", {"sid": "lumi.0", property: value}) @command() def clock(self): @@ -202,12 +183,12 @@ def set_developer_key(self, key): @command() def timezone(self): """Get current timezone.""" - return self.send("get_device_prop", ["lumi.0", "tzone_sec"]) + return self.get_gateway_prop("tzone_sec") @command() def get_illumination(self): """Get illumination. In lux?""" - return self.send("get_illumination")[0] + return self.send("get_illumination").pop() class GatewayAlarm(Device): @@ -247,28 +228,24 @@ def set_arming_time(self, seconds): def triggering_time(self) -> int: """Return the time in seconds the alarm is going off when triggered""" # Response: 30, 60, etc. - return self._device.send("get_device_prop", ["lumi.0", "alarm_time_len"]).pop() + return self._device.get_gateway_prop("alarm_time_len").pop() @command(click.argument("seconds")) def set_triggering_time(self, seconds): """Set the time in seconds the alarm is going off when triggered""" - return self._device.send( - "set_device_prop", {"sid": "lumi.0", "alarm_time_len": seconds} - ) + return self._device.set_gateway_prop("alarm_time_len", seconds) @command() def triggering_light(self) -> int: """Return the time the gateway light blinks when the alarm is triggerd""" # Response: 0=do not blink, 1=always blink, x>1=blink for x seconds - return self._device.send("get_device_prop", ["lumi.0", "en_alarm_light"]).pop() + return self._device.get_gateway_prop("en_alarm_light").pop() @command(click.argument("seconds")) def set_triggering_light(self, seconds): """Set the time the gateway light blinks when the alarm is triggerd""" # values: 0=do not blink, 1=always blink, x>1=blink for x seconds - return self._device.send( - "set_device_prop", {"sid": "lumi.0", "en_alarm_light": seconds} - ) + return self._device.set_gateway_prop("en_alarm_light", seconds) @command() def triggering_volume(self) -> int: @@ -536,38 +513,216 @@ class SubDevice: def __init__(self, gw, sid, type_, _, __, ___): self.gw = gw self.sid = sid - self.type = DeviceType(type_) + self._battery = None + try: + self.type = DeviceType(type_) + except ValueError: + self.type = DeviceType(-1) - def unpair(self): - return self.gw.send("remove_device", [self.sid]) + def __repr__(self): + return "" % ( + self.device_type, + self.sid, + self.get_firmware_version(), + self.get_battery(), + ) + @property + def device_type(self): + return self.type.name + + @property def battery(self): - return self.gw.send("get_battery", [self.sid])[0] + return self._battery - def get_firmware_version(self) -> Optional[int]: - """Returns firmware version""" + @command() + def subdevice_send(self, command): + """Send a command/query to the subdevice""" + try: + return self.gw.send(command, [self.sid]) + except Exception as ex: + _LOGGER.error( + "Got an exception while sending command %s: %s", command, ex, exc_info=True + ) + return None + + @command() + def subdevice_send_arg(self, command, arguments): + """Send a command/query including arguments to the subdevice""" try: - return self.gw.send("get_device_prop", [self.sid, "fw_ver"])[0] + return self.gw.send(command, arguments, extra_parameters={"sid": self.sid}) + except Exception as ex: + _LOGGER.error( + "Got an exception while sending command '%s' with arguments '%s': %s", command, str(arguments), ex, exc_info=True + ) + return None + + @command(click.argument("property")) + def get_subdevice_prop(self, property): + """Get the value of a property of the subdevice.""" + try: + return self.gw.send("get_device_prop", [self.sid, property]) except Exception as ex: _LOGGER.debug( - "Got an exception while fetching fw_ver: %s", ex, exc_info=True + "Got an exception while fetching property %s: %s", property, ex, exc_info=True ) return None - def __repr__(self): - return "" % ( - self.type, - self.sid, - self.get_firmware_version(), - self.battery(), - ) + @command(click.argument("properties", nargs=-1)) + def get_subdevice_prop_exp(self, properties): + """Get the value of a bunch of properties of the subdevice.""" + try: + return self.gw.send("get_device_prop_exp", [[self.sid] + list(properties)]).pop() + except Exception as ex: + _LOGGER.debug( + "Got an exception while fetching properties %s: %s", properties, ex, exc_info=True + ) + return None + + @command(click.argument("property"), click.argument("value")) + def set_subdevice_prop(self, property, value): + """Set a device property of the subdevice.""" + try: + return self.gw.send("set_device_prop", {"sid": self.sid, property: value}) + except Exception as ex: + _LOGGER.debug( + "Got an exception while setting propertie %s to value %s: %s", property, str(value), ex, exc_info=True + ) + return None + + @command() + def unpair(self): + return self.subdevice_send("remove_device") + + @command() + def get_battery(self): + self._battery = self.subdevice_send("get_battery").pop() + return self._battery + + @command() + def get_firmware_version(self) -> Optional[int]: + """Returns firmware version""" + return self.get_subdevice_prop("fw_ver").pop() + + +class AqaraHT(SubDevice): + _temperature = None + _humidity = None + _pressure = None + + @property + def temperature(self): + """Return the temperature in degrees celsius""" + return self._temperature + + @property + def humidity(self): + """Return the humidity in %""" + return self._humidity + + @property + def pressure(self): + """Return the pressure in hPa""" + return self._pressure + + @command() + def update(self): + """Update all device properties""" + values = self.get_subdevice_prop_exp(["temperature", "humidity", "pressure"]) + if len(values) == 3: + self._temperature = values[0]/100 + self._humidity = values[1]/100 + self._pressure = values[2]/100 + return class SensorHT(SubDevice): accessor = "get_prop_sensor_ht" properties = ["temperature", "humidity"] + _temperature = None + _humidity = None + @property + def temperature(self): + return self._temperature + + @property + def humidity(self): + return self._humidity + + @command() + def update(self): + values = self.get_subdevice_prop_exp(properties) + if len(values) == 2: + self._temperature = values[0] + self._humidity = values[1] + return class Plug(SubDevice): accessor = "get_prop_plug" properties = ["power", "neutral_0"] + _power = None + _status = None + + @property + def power(self): + return self._power + + @property + def status(self): + return self._status + + @command() + def update(self): + values = self.get_subdevice_prop_exp(properties) + if len(values) == 2: + self._power = values[0] + self._status = values[1] + return + +class AqaraRelayTwoChannels(SubDevice): + _status_ch0 = None + _status_ch1 = None + _load_power = None + + class AqaraRelayToggleValue(Enum): + toggle = "toggle" + on = "on" + off = "off" + + class AqaraRelayChannel(Enum): + first = "channel_0" + second = "channel_1" + + @property + def status_ch0(self): + """Return the state of channel 0""" + return self._status_ch0 + + @property + def status_ch1(self): + """Return the state of channel 1""" + return self._status_ch1 + + @property + def load_power(self): + """Return the load power""" + return self._load_power + + @command() + def update(self): + """Update all device properties""" + values = self.get_subdevice_prop_exp(["load_power", "channel_0", "channel_1"]) + if len(values) == 3: + self._load_power = values[0] + self._status_ch0 = values[1] + self._status_ch1 = values[2] + return + + @command( + click.argument("channel", type=EnumType(AqaraRelayChannel)), + click.argument("value", type=EnumType(AqaraRelayToggleValue)), + ) + def toggle(self, sid, channel, value): + """Toggle Aqara Wireless Relay 2ch""" + return self.subdevice_send_arg("toggle_ctrl_neutral", [channel.value, value.value]).pop() From 5f1cf144a356e1d283f88064105f8a4eb42daa32 Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Sat, 23 May 2020 15:03:44 +0200 Subject: [PATCH 02/35] Update gateway.py --- miio/gateway.py | 103 +++++++++++++++++++++++++++--------------------- 1 file changed, 58 insertions(+), 45 deletions(-) diff --git a/miio/gateway.py b/miio/gateway.py index 8f024a411..5684deebd 100644 --- a/miio/gateway.py +++ b/miio/gateway.py @@ -1,5 +1,4 @@ import logging -import sys from datetime import datetime from enum import Enum, IntEnum from typing import Optional @@ -8,10 +7,12 @@ from .click_common import EnumType, command, format_output from .device import Device +from .exceptions import DeviceException from .utils import brightness_and_color_to_int, int_to_brightness, int_to_rgb _LOGGER = logging.getLogger(__name__) + color_map = { "red": (255, 0, 0), "green": (0, 255, 0), @@ -24,6 +25,8 @@ "purple": (128, 0, 128), } +class GatewayException(DeviceException): + """Exception for the Xioami Gateway communication.""" class DeviceType(IntEnum): Unknown = -1 @@ -45,6 +48,8 @@ class DeviceType(IntEnum): AqaraMagnet = 53 AqaraRelayTwoChannels = 54 AqaraSquareButton = 62 + RemoteSwitchSingle = 134 + RemoteSwitchDouble = 135 class Gateway(Device): """Main class representing the Xiaomi Gateway. @@ -126,22 +131,23 @@ def devices(self): def discover_devices(self): """Discovers SubDevices and returns a list of the discovered devices.""" # from https://github.com/aholstenson/miio/issues/26 + device_type_mapping = {"AqaraRelayTwoChannels": AqaraRelayTwoChannels, "Plug": Plug, "SensorHT": SensorHT, "AqaraHT": AqaraHT, "AqaraMagnet": AqaraMagnet} devices_raw = self.get_gateway_prop("device_list") self._devices = [] - thismodule = sys.modules[__name__] for x in range(0, len(devices_raw), 5): try: Device_name = DeviceType(devices_raw[x+1]).name except ValueError: - _LOGGER.warning("Unknown subdevice type '%i' discovered, of Xiaomi gateway with ip: %s", devices_raw[x+1], self.ip) + _LOGGER.warning("Unknown subdevice type '%i': %s discovered, of Xiaomi gateway with ip: %s", devices_raw[x+1], devices_raw[x], self.ip) Device_name = DeviceType(-1).name - SubDeviceClass = getattr(thismodule, Device_name, SubDevice) - if SubDeviceClass == SubDevice and Device_name != DeviceType(-1).name: + SubDeviceClass = device_type_mapping.get(Device_name, SubDevice) + if SubDeviceClass == SubDevice and Device_name != DeviceType(-1).name and Device_name != DeviceType(0).name: _LOGGER.warning("Gateway device type '%s' does not have device specific methods defined, only basic default methods will be available", Device_name) - self._devices.append(SubDeviceClass(self, *devices_raw[x : x + 5])) + if(devices_raw[x]!='lumi.0'): + self._devices.append(SubDeviceClass(self, *devices_raw[x : x + 5])) return self._devices @@ -552,32 +558,37 @@ def subdevice_send_arg(self, command, arguments): try: return self.gw.send(command, arguments, extra_parameters={"sid": self.sid}) except Exception as ex: - _LOGGER.error( - "Got an exception while sending command '%s' with arguments '%s': %s", command, str(arguments), ex, exc_info=True - ) - return None + raise GatewayException("Got an exception while sending command '%s' with arguments '%s': %s" % (command, str(arguments), ex)) @command(click.argument("property")) def get_subdevice_prop(self, property): """Get the value of a property of the subdevice.""" try: - return self.gw.send("get_device_prop", [self.sid, property]) + response = self.gw.send("get_device_prop", [self.sid, property]) except Exception as ex: - _LOGGER.debug( - "Got an exception while fetching property %s: %s", property, ex, exc_info=True - ) - return None + raise GatewayException("Got an exception while fetching property %s: %s" % (property, ex)) + + if not response: + raise GatewayException("Empty response while fetching property %s: %s" % (property, response)) + + return response @command(click.argument("properties", nargs=-1)) def get_subdevice_prop_exp(self, properties): """Get the value of a bunch of properties of the subdevice.""" try: - return self.gw.send("get_device_prop_exp", [[self.sid] + list(properties)]).pop() + response = self.gw.send("get_device_prop_exp", [[self.sid] + list(properties)]).pop() except Exception as ex: - _LOGGER.debug( - "Got an exception while fetching properties %s: %s", properties, ex, exc_info=True - ) - return None + raise GatewayException("Got an exception while fetching properties %s: %s" % (properties, ex)) + + if len(list(properties)) != len(response): + raise GatewayException("unexpected result while fetching properties %s: %s" % (properties, response)) + + for item in response: + if not item: + raise GatewayException("One or more empty results while fetching properties %s: %s" % (properties, response)) + + return response @command(click.argument("property"), click.argument("value")) def set_subdevice_prop(self, property, value): @@ -585,10 +596,7 @@ def set_subdevice_prop(self, property, value): try: return self.gw.send("set_device_prop", {"sid": self.sid, property: value}) except Exception as ex: - _LOGGER.debug( - "Got an exception while setting propertie %s to value %s: %s", property, str(value), ex, exc_info=True - ) - return None + raise GatewayException("Got an exception while setting propertie %s to value %s: %s" % (property, str(value), ex)) @command() def unpair(self): @@ -629,12 +637,9 @@ def pressure(self): def update(self): """Update all device properties""" values = self.get_subdevice_prop_exp(["temperature", "humidity", "pressure"]) - if len(values) == 3: - self._temperature = values[0]/100 - self._humidity = values[1]/100 - self._pressure = values[2]/100 - return - + self._temperature = values[0]/100 + self._humidity = values[1]/100 + self._pressure = values[2]/100 class SensorHT(SubDevice): accessor = "get_prop_sensor_ht" @@ -652,11 +657,22 @@ def humidity(self): @command() def update(self): - values = self.get_subdevice_prop_exp(properties) - if len(values) == 2: - self._temperature = values[0] - self._humidity = values[1] - return + values = self.get_subdevice_prop_exp(self.properties) + self._temperature = values[0] + self._humidity = values[1] + +class AqaraMagnet(SubDevice): + _status = None + + @property + def status(self): + """Returns 'open' or 'closed'""" + return self._status + + @command() + def update(self): + values = self.get_subdevice_prop_exp(["unkown"]) + self._status = values[0] class Plug(SubDevice): accessor = "get_prop_plug" @@ -674,11 +690,9 @@ def status(self): @command() def update(self): - values = self.get_subdevice_prop_exp(properties) - if len(values) == 2: - self._power = values[0] - self._status = values[1] - return + values = self.get_subdevice_prop_exp(self.properties) + self._power = values[0] + self._status = values[1] class AqaraRelayTwoChannels(SubDevice): _status_ch0 = None @@ -713,11 +727,9 @@ def load_power(self): def update(self): """Update all device properties""" values = self.get_subdevice_prop_exp(["load_power", "channel_0", "channel_1"]) - if len(values) == 3: - self._load_power = values[0] - self._status_ch0 = values[1] - self._status_ch1 = values[2] - return + self._load_power = values[0] + self._status_ch0 = values[1] + self._status_ch1 = values[2] @command( click.argument("channel", type=EnumType(AqaraRelayChannel)), @@ -726,3 +738,4 @@ def update(self): def toggle(self, sid, channel, value): """Toggle Aqara Wireless Relay 2ch""" return self.subdevice_send_arg("toggle_ctrl_neutral", [channel.value, value.value]).pop() + From dbf986d1f8b15a1c1b55b3d674b1d3f9114f81d7 Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Sat, 23 May 2020 16:11:55 +0200 Subject: [PATCH 03/35] fix cli --- miio/gateway.py | 186 ++++++++++++++++++++++++++++++------------------ 1 file changed, 117 insertions(+), 69 deletions(-) diff --git a/miio/gateway.py b/miio/gateway.py index 5684deebd..74a62078d 100644 --- a/miio/gateway.py +++ b/miio/gateway.py @@ -95,10 +95,10 @@ def __init__( lazy_discover: bool = True, ) -> None: super().__init__(ip, token, start_id, debug, lazy_discover) - self._alarm = GatewayAlarm(self) - self._radio = GatewayRadio(self) - self._zigbee = GatewayZigbee(self) - self._light = GatewayLight(self) + self._alarm = GatewayAlarm(parent=self) + self._radio = GatewayRadio(parent=self) + self._zigbee = GatewayZigbee(parent=self) + self._light = GatewayLight(parent=self) self._devices = [] @property @@ -199,126 +199,150 @@ def get_illumination(self): class GatewayAlarm(Device): """Class representing the Xiaomi Gateway Alarm.""" - - def __init__(self, parent) -> None: - self._device = parent + + def __init__( + self, + parent: Gateway = None, + ip: str = None, + token: str = None, + start_id: int = 0, + debug: int = 0, + lazy_discover: bool = True, + ) -> None: + if parent is not None: + self._gateway = parent + else: + self._gateway = Device(ip, token, start_id, debug, lazy_discover) + _LOGGER.debug("Creating new device instance, only use this for cli interface") @command(default_output=format_output("[alarm_status]")) def status(self) -> str: """Return the alarm status from the device.""" # Response: 'on', 'off', 'oning' - return self._device.send("get_arming").pop() + return self._gateway.send("get_arming").pop() @command(default_output=format_output("Turning alarm on")) def on(self): """Turn alarm on.""" - return self._device.send("set_arming", ["on"]) + return self._gateway.send("set_arming", ["on"]) @command(default_output=format_output("Turning alarm off")) def off(self): """Turn alarm off.""" - return self._device.send("set_arming", ["off"]) + return self._gateway.send("set_arming", ["off"]) @command() def arming_time(self) -> int: """Return time in seconds the alarm stays 'oning' before transitioning to 'on'""" # Response: 5, 15, 30, 60 - return self._device.send("get_arm_wait_time").pop() + return self._gateway.send("get_arm_wait_time").pop() @command(click.argument("seconds")) def set_arming_time(self, seconds): """Set time the alarm stays at 'oning' before transitioning to 'on'""" - return self._device.send("set_arm_wait_time", [seconds]) + return self._gateway.send("set_arm_wait_time", [seconds]) @command() def triggering_time(self) -> int: """Return the time in seconds the alarm is going off when triggered""" # Response: 30, 60, etc. - return self._device.get_gateway_prop("alarm_time_len").pop() + return self._gateway.get_gateway_prop("alarm_time_len").pop() @command(click.argument("seconds")) def set_triggering_time(self, seconds): """Set the time in seconds the alarm is going off when triggered""" - return self._device.set_gateway_prop("alarm_time_len", seconds) + return self._gateway.set_gateway_prop("alarm_time_len", seconds) @command() def triggering_light(self) -> int: """Return the time the gateway light blinks when the alarm is triggerd""" # Response: 0=do not blink, 1=always blink, x>1=blink for x seconds - return self._device.get_gateway_prop("en_alarm_light").pop() + return self._gateway.get_gateway_prop("en_alarm_light").pop() @command(click.argument("seconds")) def set_triggering_light(self, seconds): """Set the time the gateway light blinks when the alarm is triggerd""" # values: 0=do not blink, 1=always blink, x>1=blink for x seconds - return self._device.set_gateway_prop("en_alarm_light", seconds) + return self._gateway.set_gateway_prop("en_alarm_light", seconds) @command() def triggering_volume(self) -> int: """Return the volume level at which alarms go off [0-100]""" - return self._device.send("get_alarming_volume").pop() + return self._gateway.send("get_alarming_volume").pop() @command(click.argument("volume")) def set_triggering_volume(self, volume): """Set the volume level at which alarms go off [0-100]""" - return self._device.send("set_alarming_volume", [volume]) + return self._gateway.send("set_alarming_volume", [volume]) @command() def last_status_change_time(self): """Return the last time the alarm changed status, type datetime.datetime""" - return datetime.fromtimestamp(self._device.send("get_arming_time").pop()) + return datetime.fromtimestamp(self._gateway.send("get_arming_time").pop()) class GatewayZigbee(Device): """Zigbee controls.""" - def __init__(self, parent) -> None: - self._device = parent + def __init__( + self, + parent: Gateway = None, + ip: str = None, + token: str = None, + start_id: int = 0, + debug: int = 0, + lazy_discover: bool = True, + ) -> None: + if parent is not None: + self._gateway = parent + else: + self._gateway = Device(ip, token, start_id, debug, lazy_discover) + _LOGGER.debug("Creating new device instance, only use this for cli interface") @command() def get_zigbee_version(self): """timeouts on device""" - return self._device.send("get_zigbee_device_version") + return self._gateway.send("get_zigbee_device_version") @command() def get_zigbee_channel(self): """Return currently used zigbee channel.""" - return self._device.send("get_zigbee_channel")[0] + return self._gateway.send("get_zigbee_channel")[0] @command(click.argument("channel")) def set_zigbee_channel(self, channel): """Set zigbee channel.""" - return self._device.send("set_zigbee_channel", [channel]) + return self._gateway.send("set_zigbee_channel", [channel]) @command(click.argument("timeout", type=int)) def zigbee_pair(self, timeout): """Start pairing, use 0 to disable""" - return self._device.send("start_zigbee_join", [timeout]) + return self._gateway.send("start_zigbee_join", [timeout]) def send_to_zigbee(self): """How does this differ from writing? Unknown.""" raise NotImplementedError() - return self._device.send("send_to_zigbee") + return self._gateway.send("send_to_zigbee") def read_zigbee_eep(self): """Read eeprom?""" raise NotImplementedError() - return self._device.send("read_zig_eep", [0]) # 'ok' + return self._gateway.send("read_zig_eep", [0]) # 'ok' def read_zigbee_attribute(self): """Read zigbee data?""" raise NotImplementedError() - return self._device.send("read_zigbee_attribute", [0x0000, 0x0080]) + return self._gateway.send("read_zigbee_attribute", [0x0000, 0x0080]) def write_zigbee_attribute(self): """Unknown parameters.""" raise NotImplementedError() - return self._device.send("write_zigbee_attribute") + return self._gateway.send("write_zigbee_attribute") @command() def zigbee_unpair_all(self): """Unpair all devices.""" - return self._device.send("remove_all_device") + return self._gateway.send("remove_all_device") def zigbee_unpair(self, sid): """Unpair a device.""" @@ -329,18 +353,30 @@ def zigbee_unpair(self, sid): class GatewayRadio(Device): """Radio controls for the gateway.""" - def __init__(self, parent) -> None: - self._device = parent + def __init__( + self, + parent: Gateway = None, + ip: str = None, + token: str = None, + start_id: int = 0, + debug: int = 0, + lazy_discover: bool = True, + ) -> None: + if parent is not None: + self._gateway = parent + else: + self._gateway = Device(ip, token, start_id, debug, lazy_discover) + _LOGGER.debug("Creating new device instance, only use this for cli interface") @command() def get_radio_info(self): """Radio play info.""" - return self._device.send("get_prop_fm") + return self._gateway.send("get_prop_fm") @command(click.argument("volume")) def set_radio_volume(self, volume): """Set radio volume""" - return self._device.send("set_fm_volume", [volume]) + return self._gateway.send("set_fm_volume", [volume]) def play_music_new(self): """Unknown.""" @@ -353,79 +389,79 @@ def play_specify_fm(self): raise NotImplementedError() # {"from": "4", "id": 65055, "method": "play_specify_fm", # "params": {"id": 764, "type": 0, "url": "http://live.xmcdn.com/live/764/64.m3u8"}} - return self._device.send("play_specify_fm") + return self._gateway.send("play_specify_fm") def play_fm(self): """radio on/off?""" raise NotImplementedError() # play_fm","params":["off"]} - return self._device.send("play_fm") + return self._gateway.send("play_fm") def volume_ctrl_fm(self): """Unknown.""" raise NotImplementedError() - return self._device.send("volume_ctrl_fm") + return self._gateway.send("volume_ctrl_fm") def get_channels(self): """Unknown.""" raise NotImplementedError() # "method": "get_channels", "params": {"start": 0}} - return self._device.send("get_channels") + return self._gateway.send("get_channels") def add_channels(self): """Unknown.""" raise NotImplementedError() - return self._device.send("add_channels") + return self._gateway.send("add_channels") def remove_channels(self): """Unknown.""" raise NotImplementedError() - return self._device.send("remove_channels") + return self._gateway.send("remove_channels") def get_default_music(self): """seems to timeout (w/o internet)""" # params [0,1,2] raise NotImplementedError() - return self._device.send("get_default_music") + return self._gateway.send("get_default_music") @command() def get_music_info(self): """Unknown.""" - info = self._device.send("get_music_info") + info = self._gateway.send("get_music_info") click.echo("info: %s" % info) - free_space = self._device.send("get_music_free_space") + free_space = self._gateway.send("get_music_free_space") click.echo("free space: %s" % free_space) @command() def get_mute(self): """mute of what?""" - return self._device.send("get_mute") + return self._gateway.send("get_mute") def download_music(self): """Unknown""" raise NotImplementedError() - return self._device.send("download_music") + return self._gateway.send("download_music") def delete_music(self): """delete music""" raise NotImplementedError() - return self._device.send("delete_music") + return self._gateway.send("delete_music") def download_user_music(self): """Unknown.""" raise NotImplementedError() - return self._device.send("download_user_music") + return self._gateway.send("download_user_music") def get_download_progress(self): """progress for music downloads or updates?""" # returns [':0'] raise NotImplementedError() - return self._device.send("get_download_progress") + return self._gateway.send("get_download_progress") @command() def set_sound_playing(self): """stop playing?""" - return self._device.send("set_sound_playing", ["off"]) + return self._gateway.send("set_sound_playing", ["off"]) def set_default_music(self): raise NotImplementedError() @@ -435,8 +471,20 @@ def set_default_music(self): class GatewayLight(Device): """Light controls for the gateway.""" - def __init__(self, parent) -> None: - self._device = parent + def __init__( + self, + parent: Gateway = None, + ip: str = None, + token: str = None, + start_id: int = 0, + debug: int = 0, + lazy_discover: bool = True, + ) -> None: + if parent is not None: + self._gateway = parent + else: + self._gateway = Device(ip, token, start_id, debug, lazy_discover) + _LOGGER.debug("Creating new device instance, only use this for cli interface") @command() def get_night_light_rgb(self): @@ -445,7 +493,7 @@ def get_night_light_rgb(self): # looks like this is the same as get_rgb # id': 65064, 'method': 'set_night_light_rgb', 'params': [419407616]} # {'method': 'props', 'params': {'light': 'on', 'from.light': '4,,,'}, 'id': 88457} ?! - return self.send("get_night_light_rgb") + return self._gateway.send("get_night_light_rgb") @command(click.argument("color_name", type=str)) def set_night_light_color(self, color_name): @@ -456,11 +504,11 @@ def set_night_light_color(self, color_name): color=color_name, colors=color_map.keys() ) ) - current_brightness = int_to_brightness(self.send("get_night_light_rgb")[0]) + current_brightness = int_to_brightness(self._gateway.send("get_night_light_rgb")[0]) brightness_and_color = brightness_and_color_to_int( current_brightness, color_map[color_name] ) - return self.send("set_night_light_rgb", [brightness_and_color]) + return self._gateway.send("set_night_light_rgb", [brightness_and_color]) @command(click.argument("color_name", type=str)) def set_color(self, color_name): @@ -471,30 +519,30 @@ def set_color(self, color_name): color=color_name, colors=color_map.keys() ) ) - current_brightness = int_to_brightness(self.send("get_rgb")[0]) + current_brightness = int_to_brightness(self._gateway.send("get_rgb")[0]) brightness_and_color = brightness_and_color_to_int( current_brightness, color_map[color_name] ) - return self.send("set_rgb", [brightness_and_color]) + return self._gateway.send("set_rgb", [brightness_and_color]) @command(click.argument("brightness", type=int)) def set_brightness(self, brightness): """Set gateway lamp brightness (0-100).""" if 100 < brightness < 0: raise Exception("Brightness must be between 0 and 100") - current_color = int_to_rgb(self.send("get_rgb")[0]) + current_color = int_to_rgb(self._gateway.send("get_rgb")[0]) brightness_and_color = brightness_and_color_to_int(brightness, current_color) - return self.send("set_rgb", [brightness_and_color]) + return self._gateway.send("set_rgb", [brightness_and_color]) @command(click.argument("brightness", type=int)) def set_night_light_brightness(self, brightness): """Set night light brightness (0-100).""" if 100 < brightness < 0: raise Exception("Brightness must be between 0 and 100") - current_color = int_to_rgb(self.send("get_night_light_rgb")[0]) + current_color = int_to_rgb(self._gateway.send("get_night_light_rgb")[0]) brightness_and_color = brightness_and_color_to_int(brightness, current_color) print(brightness, current_color) - return self.send("set_night_light_rgb", [brightness_and_color]) + return self._gateway.send("set_night_light_rgb", [brightness_and_color]) @command( click.argument("color_name", type=str), click.argument("brightness", type=int) @@ -512,16 +560,16 @@ def set_light(self, color_name, brightness): brightness_and_color = brightness_and_color_to_int( brightness, color_map[color_name] ) - return self.send("set_rgb", [brightness_and_color]) + return self._gateway.send("set_rgb", [brightness_and_color]) class SubDevice: - def __init__(self, gw, sid, type_, _, __, ___): - self.gw = gw + def __init__(self, gw, sid, type, _, __, ___): + self._gw = gw self.sid = sid self._battery = None try: - self.type = DeviceType(type_) + self.type = DeviceType(type) except ValueError: self.type = DeviceType(-1) @@ -545,7 +593,7 @@ def battery(self): def subdevice_send(self, command): """Send a command/query to the subdevice""" try: - return self.gw.send(command, [self.sid]) + return self._gw.send(command, [self.sid]) except Exception as ex: _LOGGER.error( "Got an exception while sending command %s: %s", command, ex, exc_info=True @@ -556,7 +604,7 @@ def subdevice_send(self, command): def subdevice_send_arg(self, command, arguments): """Send a command/query including arguments to the subdevice""" try: - return self.gw.send(command, arguments, extra_parameters={"sid": self.sid}) + return self._gw.send(command, arguments, extra_parameters={"sid": self.sid}) except Exception as ex: raise GatewayException("Got an exception while sending command '%s' with arguments '%s': %s" % (command, str(arguments), ex)) @@ -564,7 +612,7 @@ def subdevice_send_arg(self, command, arguments): def get_subdevice_prop(self, property): """Get the value of a property of the subdevice.""" try: - response = self.gw.send("get_device_prop", [self.sid, property]) + response = self._gw.send("get_device_prop", [self.sid, property]) except Exception as ex: raise GatewayException("Got an exception while fetching property %s: %s" % (property, ex)) @@ -577,7 +625,7 @@ def get_subdevice_prop(self, property): def get_subdevice_prop_exp(self, properties): """Get the value of a bunch of properties of the subdevice.""" try: - response = self.gw.send("get_device_prop_exp", [[self.sid] + list(properties)]).pop() + response = self._gw.send("get_device_prop_exp", [[self.sid] + list(properties)]).pop() except Exception as ex: raise GatewayException("Got an exception while fetching properties %s: %s" % (properties, ex)) @@ -594,7 +642,7 @@ def get_subdevice_prop_exp(self, properties): def set_subdevice_prop(self, property, value): """Set a device property of the subdevice.""" try: - return self.gw.send("set_device_prop", {"sid": self.sid, property: value}) + return self._gw.send("set_device_prop", {"sid": self.sid, property: value}) except Exception as ex: raise GatewayException("Got an exception while setting propertie %s to value %s: %s" % (property, str(value), ex)) From 8ffb765adb2ad9b3d5cf835ead8df3503e92667e Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Sat, 23 May 2020 17:36:05 +0200 Subject: [PATCH 04/35] black formatting --- miio/gateway.py | 182 ++++++++++++++++++++++++++++++++++++------------ 1 file changed, 138 insertions(+), 44 deletions(-) diff --git a/miio/gateway.py b/miio/gateway.py index 74a62078d..d09927ff6 100644 --- a/miio/gateway.py +++ b/miio/gateway.py @@ -25,9 +25,11 @@ "purple": (128, 0, 128), } + class GatewayException(DeviceException): """Exception for the Xioami Gateway communication.""" + class DeviceType(IntEnum): Unknown = -1 Gateway = 0 @@ -51,6 +53,7 @@ class DeviceType(IntEnum): RemoteSwitchSingle = 134 RemoteSwitchDouble = 135 + class Gateway(Device): """Main class representing the Xiaomi Gateway. @@ -131,24 +134,39 @@ def devices(self): def discover_devices(self): """Discovers SubDevices and returns a list of the discovered devices.""" # from https://github.com/aholstenson/miio/issues/26 - device_type_mapping = {"AqaraRelayTwoChannels": AqaraRelayTwoChannels, "Plug": Plug, "SensorHT": SensorHT, "AqaraHT": AqaraHT, "AqaraMagnet": AqaraMagnet} + device_type_mapping = { + "AqaraRelayTwoChannels": AqaraRelayTwoChannels, + "Plug": AqaraPlug, + "SensorHT": SensorHT, + "AqaraHT": AqaraHT, + "AqaraMagnet": AqaraMagnet, + } devices_raw = self.get_gateway_prop("device_list") self._devices = [] - + for x in range(0, len(devices_raw), 5): try: - Device_name = DeviceType(devices_raw[x+1]).name + device_name = DeviceType(devices_raw[x + 1]).name except ValueError: - _LOGGER.warning("Unknown subdevice type '%i': %s discovered, of Xiaomi gateway with ip: %s", devices_raw[x+1], devices_raw[x], self.ip) - Device_name = DeviceType(-1).name - - SubDeviceClass = device_type_mapping.get(Device_name, SubDevice) - if SubDeviceClass == SubDevice and Device_name != DeviceType(-1).name and Device_name != DeviceType(0).name: - _LOGGER.warning("Gateway device type '%s' does not have device specific methods defined, only basic default methods will be available", Device_name) - - if(devices_raw[x]!='lumi.0'): - self._devices.append(SubDeviceClass(self, *devices_raw[x : x + 5])) - + _LOGGER.warning( + "Unknown subdevice type '%i': %s discovered, of Xiaomi gateway with ip: %s", + devices_raw[x + 1], + devices_raw[x], + self.ip, + ) + device_name = DeviceType(-1).name + + subdevice_cls = device_type_mapping.get(device_name) + if subdevice_cls is None: + subdevice_cls = SubDevice + _LOGGER.info( + "Gateway device type '%s' does not have device specific methods defined, only basic default methods will be available", + device_name, + ) + + if devices_raw[x] != "lumi.0": + self._devices.append(subdevice_cls(self, *devices_raw[x : x + 5])) + return self._devices @command(click.argument("property")) @@ -199,7 +217,7 @@ def get_illumination(self): class GatewayAlarm(Device): """Class representing the Xiaomi Gateway Alarm.""" - + def __init__( self, parent: Gateway = None, @@ -213,7 +231,9 @@ def __init__( self._gateway = parent else: self._gateway = Device(ip, token, start_id, debug, lazy_discover) - _LOGGER.debug("Creating new device instance, only use this for cli interface") + _LOGGER.debug( + "Creating new device instance, only use this for cli interface" + ) @command(default_output=format_output("[alarm_status]")) def status(self) -> str: @@ -297,7 +317,9 @@ def __init__( self._gateway = parent else: self._gateway = Device(ip, token, start_id, debug, lazy_discover) - _LOGGER.debug("Creating new device instance, only use this for cli interface") + _LOGGER.debug( + "Creating new device instance, only use this for cli interface" + ) @command() def get_zigbee_version(self): @@ -366,7 +388,9 @@ def __init__( self._gateway = parent else: self._gateway = Device(ip, token, start_id, debug, lazy_discover) - _LOGGER.debug("Creating new device instance, only use this for cli interface") + _LOGGER.debug( + "Creating new device instance, only use this for cli interface" + ) @command() def get_radio_info(self): @@ -464,6 +488,7 @@ def set_sound_playing(self): return self._gateway.send("set_sound_playing", ["off"]) def set_default_music(self): + """Unknown.""" raise NotImplementedError() # method":"set_default_music","params":[0,"2"]} @@ -484,7 +509,9 @@ def __init__( self._gateway = parent else: self._gateway = Device(ip, token, start_id, debug, lazy_discover) - _LOGGER.debug("Creating new device instance, only use this for cli interface") + _LOGGER.debug( + "Creating new device instance, only use this for cli interface" + ) @command() def get_night_light_rgb(self): @@ -504,7 +531,9 @@ def set_night_light_color(self, color_name): color=color_name, colors=color_map.keys() ) ) - current_brightness = int_to_brightness(self._gateway.send("get_night_light_rgb")[0]) + current_brightness = int_to_brightness( + self._gateway.send("get_night_light_rgb")[0] + ) brightness_and_color = brightness_and_color_to_int( current_brightness, color_map[color_name] ) @@ -563,8 +592,32 @@ def set_light(self, color_name, brightness): return self._gateway.send("set_rgb", [brightness_and_color]) -class SubDevice: - def __init__(self, gw, sid, type, _, __, ___): +class SubDevice(Device): + def __init__( + self, + gw: Gateway = None, + sid: str = None, + type: int = None, + _: int = None, + __: int = None, + ___: int = None, + ip: str = None, + token: str = None, + start_id: int = 0, + debug: int = 0, + lazy_discover: bool = True, + ) -> None: + if gw is not None: + self._gw = gw + else: + self._gw = Device(ip, token, start_id, debug, lazy_discover) + _LOGGER.debug( + "Creating new device instance, only use this for cli interface" + ) + + if sid is None: + raise Exception("sid of the subdevice needs to be specified") + self._gw = gw self.sid = sid self._battery = None @@ -583,10 +636,12 @@ def __repr__(self): @property def device_type(self): + """Return the device type name.""" return self.type.name @property def battery(self): + """Return the battery level.""" return self._battery @command() @@ -596,7 +651,10 @@ def subdevice_send(self, command): return self._gw.send(command, [self.sid]) except Exception as ex: _LOGGER.error( - "Got an exception while sending command %s: %s", command, ex, exc_info=True + "Got an exception while sending command %s: %s", + command, + ex, + exc_info=True, ) return None @@ -606,7 +664,10 @@ def subdevice_send_arg(self, command, arguments): try: return self._gw.send(command, arguments, extra_parameters={"sid": self.sid}) except Exception as ex: - raise GatewayException("Got an exception while sending command '%s' with arguments '%s': %s" % (command, str(arguments), ex)) + raise GatewayException( + "Got an exception while sending command '%s' with arguments '%s': %s" + % (command, str(arguments), ex) + ) @command(click.argument("property")) def get_subdevice_prop(self, property): @@ -614,28 +675,42 @@ def get_subdevice_prop(self, property): try: response = self._gw.send("get_device_prop", [self.sid, property]) except Exception as ex: - raise GatewayException("Got an exception while fetching property %s: %s" % (property, ex)) - + raise GatewayException( + "Got an exception while fetching property %s: %s" % (property, ex) + ) + if not response: - raise GatewayException("Empty response while fetching property %s: %s" % (property, response)) - + raise GatewayException( + "Empty response while fetching property %s: %s" % (property, response) + ) + return response @command(click.argument("properties", nargs=-1)) def get_subdevice_prop_exp(self, properties): """Get the value of a bunch of properties of the subdevice.""" try: - response = self._gw.send("get_device_prop_exp", [[self.sid] + list(properties)]).pop() + response = self._gw.send( + "get_device_prop_exp", [[self.sid] + list(properties)] + ).pop() except Exception as ex: - raise GatewayException("Got an exception while fetching properties %s: %s" % (properties, ex)) + raise GatewayException( + "Got an exception while fetching properties %s: %s" % (properties, ex) + ) if len(list(properties)) != len(response): - raise GatewayException("unexpected result while fetching properties %s: %s" % (properties, response)) - + raise GatewayException( + "unexpected result while fetching properties %s: %s" + % (properties, response) + ) + for item in response: if not item: - raise GatewayException("One or more empty results while fetching properties %s: %s" % (properties, response)) - + raise GatewayException( + "One or more empty results while fetching properties %s: %s" + % (properties, response) + ) + return response @command(click.argument("property"), click.argument("value")) @@ -644,14 +719,19 @@ def set_subdevice_prop(self, property, value): try: return self._gw.send("set_device_prop", {"sid": self.sid, property: value}) except Exception as ex: - raise GatewayException("Got an exception while setting propertie %s to value %s: %s" % (property, str(value), ex)) + raise GatewayException( + "Got an exception while setting propertie %s to value %s: %s" + % (property, str(value), ex) + ) @command() def unpair(self): + """Unpair this device from the gateway.""" return self.subdevice_send("remove_device") @command() def get_battery(self): + """Update the battery level and return the new value.""" self._battery = self.subdevice_send("get_battery").pop() return self._battery @@ -685,9 +765,10 @@ def pressure(self): def update(self): """Update all device properties""" values = self.get_subdevice_prop_exp(["temperature", "humidity", "pressure"]) - self._temperature = values[0]/100 - self._humidity = values[1]/100 - self._pressure = values[2]/100 + self._temperature = values[0] / 100 + self._humidity = values[1] / 100 + self._pressure = values[2] / 100 + class SensorHT(SubDevice): accessor = "get_prop_sensor_ht" @@ -697,17 +778,21 @@ class SensorHT(SubDevice): @property def temperature(self): + """Return the temperature in degrees celsius""" return self._temperature @property def humidity(self): + """Return the humidity in %""" return self._humidity - + @command() def update(self): + """Update all device properties""" values = self.get_subdevice_prop_exp(self.properties) - self._temperature = values[0] - self._humidity = values[1] + self._temperature = values[0] / 100 + self._humidity = values[1] / 100 + class AqaraMagnet(SubDevice): _status = None @@ -719,10 +804,12 @@ def status(self): @command() def update(self): + """Update all device properties""" values = self.get_subdevice_prop_exp(["unkown"]) self._status = values[0] -class Plug(SubDevice): + +class AqaraPlug(SubDevice): accessor = "get_prop_plug" properties = ["power", "neutral_0"] _power = None @@ -730,29 +817,35 @@ class Plug(SubDevice): @property def power(self): + """Return the power consumption""" return self._power @property def status(self): + """Return the status of the plug: on/off""" return self._status - + @command() def update(self): + """Update all device properties""" values = self.get_subdevice_prop_exp(self.properties) self._power = values[0] self._status = values[1] + class AqaraRelayTwoChannels(SubDevice): _status_ch0 = None _status_ch1 = None _load_power = None class AqaraRelayToggleValue(Enum): + """Options to control the relay""" toggle = "toggle" on = "on" off = "off" class AqaraRelayChannel(Enum): + """Options to select wich relay to control""" first = "channel_0" second = "channel_1" @@ -785,5 +878,6 @@ def update(self): ) def toggle(self, sid, channel, value): """Toggle Aqara Wireless Relay 2ch""" - return self.subdevice_send_arg("toggle_ctrl_neutral", [channel.value, value.value]).pop() - + return self.subdevice_send_arg( + "toggle_ctrl_neutral", [channel.value, value.value] + ).pop() From 692ebfce9249655982e97fa4c8285f7439f0d653 Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Sat, 23 May 2020 17:42:38 +0200 Subject: [PATCH 05/35] filter out gateway --- miio/gateway.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/miio/gateway.py b/miio/gateway.py index d09927ff6..1d4c0b673 100644 --- a/miio/gateway.py +++ b/miio/gateway.py @@ -157,7 +157,7 @@ def discover_devices(self): device_name = DeviceType(-1).name subdevice_cls = device_type_mapping.get(device_name) - if subdevice_cls is None: + if subdevice_cls is None and device_name != DeviceType(0).name: subdevice_cls = SubDevice _LOGGER.info( "Gateway device type '%s' does not have device specific methods defined, only basic default methods will be available", From c4c0699b5793ae9c5ceab35b3f01f323b8cd4c3a Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Sat, 23 May 2020 17:51:19 +0200 Subject: [PATCH 06/35] better error handeling --- miio/gateway.py | 36 +++++++++++++++++------------------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/miio/gateway.py b/miio/gateway.py index 1d4c0b673..143e52753 100644 --- a/miio/gateway.py +++ b/miio/gateway.py @@ -220,12 +220,12 @@ class GatewayAlarm(Device): def __init__( self, - parent: Gateway = None, ip: str = None, token: str = None, start_id: int = 0, debug: int = 0, lazy_discover: bool = True, + parent: Gateway = None, ) -> None: if parent is not None: self._gateway = parent @@ -306,12 +306,12 @@ class GatewayZigbee(Device): def __init__( self, - parent: Gateway = None, ip: str = None, token: str = None, start_id: int = 0, debug: int = 0, lazy_discover: bool = True, + parent: Gateway = None, ) -> None: if parent is not None: self._gateway = parent @@ -377,12 +377,12 @@ class GatewayRadio(Device): def __init__( self, - parent: Gateway = None, ip: str = None, token: str = None, start_id: int = 0, debug: int = 0, lazy_discover: bool = True, + parent: Gateway = None, ) -> None: if parent is not None: self._gateway = parent @@ -498,12 +498,12 @@ class GatewayLight(Device): def __init__( self, - parent: Gateway = None, ip: str = None, token: str = None, start_id: int = 0, debug: int = 0, lazy_discover: bool = True, + parent: Gateway = None, ) -> None: if parent is not None: self._gateway = parent @@ -650,12 +650,10 @@ def subdevice_send(self, command): try: return self._gw.send(command, [self.sid]) except Exception as ex: - _LOGGER.error( - "Got an exception while sending command %s: %s", - command, - ex, - exc_info=True, - ) + raise GatewayException( + "Got an exception while sending command %s" + % (command) + ) from ex return None @command() @@ -665,9 +663,9 @@ def subdevice_send_arg(self, command, arguments): return self._gw.send(command, arguments, extra_parameters={"sid": self.sid}) except Exception as ex: raise GatewayException( - "Got an exception while sending command '%s' with arguments '%s': %s" - % (command, str(arguments), ex) - ) + "Got an exception while sending command '%s' with arguments '%s'" + % (command, str(arguments)) + ) from ex @command(click.argument("property")) def get_subdevice_prop(self, property): @@ -676,8 +674,8 @@ def get_subdevice_prop(self, property): response = self._gw.send("get_device_prop", [self.sid, property]) except Exception as ex: raise GatewayException( - "Got an exception while fetching property %s: %s" % (property, ex) - ) + "Got an exception while fetching property %s" % (property) + ) from ex if not response: raise GatewayException( @@ -695,8 +693,8 @@ def get_subdevice_prop_exp(self, properties): ).pop() except Exception as ex: raise GatewayException( - "Got an exception while fetching properties %s: %s" % (properties, ex) - ) + "Got an exception while fetching properties %s: %s" % (properties) + ) from ex if len(list(properties)) != len(response): raise GatewayException( @@ -721,8 +719,8 @@ def set_subdevice_prop(self, property, value): except Exception as ex: raise GatewayException( "Got an exception while setting propertie %s to value %s: %s" - % (property, str(value), ex) - ) + % (property, str(value)) + ) from ex @command() def unpair(self): From 581dcb3f5b79dbf96360df5be3e22cbc0304a75b Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Sat, 23 May 2020 17:59:20 +0200 Subject: [PATCH 07/35] common GatewayDevice class for __init__ --- miio/gateway.py | 67 ++++++++----------------------------------------- 1 file changed, 10 insertions(+), 57 deletions(-) diff --git a/miio/gateway.py b/miio/gateway.py index 143e52753..add20fdb5 100644 --- a/miio/gateway.py +++ b/miio/gateway.py @@ -214,9 +214,8 @@ def get_illumination(self): """Get illumination. In lux?""" return self.send("get_illumination").pop() - -class GatewayAlarm(Device): - """Class representing the Xiaomi Gateway Alarm.""" +class GatewayDevice(Device): + """GatewayDevice class to specify the init method for all gateway device functionalities.""" def __init__( self, @@ -235,6 +234,9 @@ def __init__( "Creating new device instance, only use this for cli interface" ) +class GatewayAlarm(GatewayDevice): + """Class representing the Xiaomi Gateway Alarm.""" + @command(default_output=format_output("[alarm_status]")) def status(self) -> str: """Return the alarm status from the device.""" @@ -301,26 +303,9 @@ def last_status_change_time(self): return datetime.fromtimestamp(self._gateway.send("get_arming_time").pop()) -class GatewayZigbee(Device): +class GatewayZigbee(GatewayDevice): """Zigbee controls.""" - def __init__( - self, - ip: str = None, - token: str = None, - start_id: int = 0, - debug: int = 0, - lazy_discover: bool = True, - parent: Gateway = None, - ) -> None: - if parent is not None: - self._gateway = parent - else: - self._gateway = Device(ip, token, start_id, debug, lazy_discover) - _LOGGER.debug( - "Creating new device instance, only use this for cli interface" - ) - @command() def get_zigbee_version(self): """timeouts on device""" @@ -372,26 +357,9 @@ def zigbee_unpair(self, sid): raise NotImplementedError() -class GatewayRadio(Device): +class GatewayRadio(GatewayDevice): """Radio controls for the gateway.""" - def __init__( - self, - ip: str = None, - token: str = None, - start_id: int = 0, - debug: int = 0, - lazy_discover: bool = True, - parent: Gateway = None, - ) -> None: - if parent is not None: - self._gateway = parent - else: - self._gateway = Device(ip, token, start_id, debug, lazy_discover) - _LOGGER.debug( - "Creating new device instance, only use this for cli interface" - ) - @command() def get_radio_info(self): """Radio play info.""" @@ -493,26 +461,9 @@ def set_default_music(self): # method":"set_default_music","params":[0,"2"]} -class GatewayLight(Device): +class GatewayLight(GatewayDevice): """Light controls for the gateway.""" - def __init__( - self, - ip: str = None, - token: str = None, - start_id: int = 0, - debug: int = 0, - lazy_discover: bool = True, - parent: Gateway = None, - ) -> None: - if parent is not None: - self._gateway = parent - else: - self._gateway = Device(ip, token, start_id, debug, lazy_discover) - _LOGGER.debug( - "Creating new device instance, only use this for cli interface" - ) - @command() def get_night_light_rgb(self): """Unknown.""" @@ -593,6 +544,8 @@ def set_light(self, color_name, brightness): class SubDevice(Device): + """Base class for all subdevices of the gateway that are connected through zigbee.""" + def __init__( self, gw: Gateway = None, From 27a89d3856a43283aba20c510141b64dfcd5d6bd Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Sat, 23 May 2020 18:14:21 +0200 Subject: [PATCH 08/35] remove duplicate "gateway" from method name --- miio/gateway.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/miio/gateway.py b/miio/gateway.py index add20fdb5..2d146d347 100644 --- a/miio/gateway.py +++ b/miio/gateway.py @@ -141,7 +141,7 @@ def discover_devices(self): "AqaraHT": AqaraHT, "AqaraMagnet": AqaraMagnet, } - devices_raw = self.get_gateway_prop("device_list") + devices_raw = self.get_prop("device_list") self._devices = [] for x in range(0, len(devices_raw), 5): @@ -170,17 +170,17 @@ def discover_devices(self): return self._devices @command(click.argument("property")) - def get_gateway_prop(self, property): + def get_prop(self, property): """Get the value of a property for given sid.""" return self.send("get_device_prop", ["lumi.0", property]) @command(click.argument("properties", nargs=-1)) - def get_gateway_prop_exp(self, properties): + def get_prop_exp(self, properties): """Get the value of a bunch of properties for given sid.""" return self.send("get_device_prop_exp", [["lumi.0"] + list(properties)]) @command(click.argument("property"), click.argument("value")) - def set_gateway_prop(self, property, value): + def set_prop(self, property, value): """Set the device property.""" return self.send("set_device_prop", {"sid": "lumi.0", property: value}) @@ -207,7 +207,7 @@ def set_developer_key(self, key): @command() def timezone(self): """Get current timezone.""" - return self.get_gateway_prop("tzone_sec") + return self.get_prop("tzone_sec") @command() def get_illumination(self): @@ -268,24 +268,24 @@ def set_arming_time(self, seconds): def triggering_time(self) -> int: """Return the time in seconds the alarm is going off when triggered""" # Response: 30, 60, etc. - return self._gateway.get_gateway_prop("alarm_time_len").pop() + return self._gateway.get_prop("alarm_time_len").pop() @command(click.argument("seconds")) def set_triggering_time(self, seconds): """Set the time in seconds the alarm is going off when triggered""" - return self._gateway.set_gateway_prop("alarm_time_len", seconds) + return self._gateway.set_prop("alarm_time_len", seconds) @command() def triggering_light(self) -> int: """Return the time the gateway light blinks when the alarm is triggerd""" # Response: 0=do not blink, 1=always blink, x>1=blink for x seconds - return self._gateway.get_gateway_prop("en_alarm_light").pop() + return self._gateway.get_prop("en_alarm_light").pop() @command(click.argument("seconds")) def set_triggering_light(self, seconds): """Set the time the gateway light blinks when the alarm is triggerd""" # values: 0=do not blink, 1=always blink, x>1=blink for x seconds - return self._gateway.set_gateway_prop("en_alarm_light", seconds) + return self._gateway.set_prop("en_alarm_light", seconds) @command() def triggering_volume(self) -> int: From d18c65fa3d568c8e51c277e193d87a8993ddf03d Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Sat, 23 May 2020 18:26:35 +0200 Subject: [PATCH 09/35] use device_type instead of device_name for mapping --- miio/gateway.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/miio/gateway.py b/miio/gateway.py index 2d146d347..5230ac840 100644 --- a/miio/gateway.py +++ b/miio/gateway.py @@ -135,18 +135,18 @@ def discover_devices(self): """Discovers SubDevices and returns a list of the discovered devices.""" # from https://github.com/aholstenson/miio/issues/26 device_type_mapping = { - "AqaraRelayTwoChannels": AqaraRelayTwoChannels, - "Plug": AqaraPlug, - "SensorHT": SensorHT, - "AqaraHT": AqaraHT, - "AqaraMagnet": AqaraMagnet, + DeviceType.AqaraRelayTwoChannels: AqaraRelayTwoChannels, + DeviceType.Plug: AqaraPlug, + DeviceType.SensorHT: SensorHT, + DeviceType.AqaraHT: AqaraHT, + DeviceType.AqaraMagnet: AqaraMagnet, } devices_raw = self.get_prop("device_list") self._devices = [] for x in range(0, len(devices_raw), 5): try: - device_name = DeviceType(devices_raw[x + 1]).name + device_type = DeviceType(devices_raw[x + 1]) except ValueError: _LOGGER.warning( "Unknown subdevice type '%i': %s discovered, of Xiaomi gateway with ip: %s", @@ -154,14 +154,14 @@ def discover_devices(self): devices_raw[x], self.ip, ) - device_name = DeviceType(-1).name + device_type = DeviceType(-1) - subdevice_cls = device_type_mapping.get(device_name) - if subdevice_cls is None and device_name != DeviceType(0).name: + subdevice_cls = device_type_mapping.get(device_type) + if subdevice_cls is None and device_type != DeviceType(0): subdevice_cls = SubDevice _LOGGER.info( "Gateway device type '%s' does not have device specific methods defined, only basic default methods will be available", - device_name, + device_type.name, ) if devices_raw[x] != "lumi.0": From a21e3819e12384ef0f37bbe489704060503c9762 Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Sat, 23 May 2020 18:30:59 +0200 Subject: [PATCH 10/35] add comments --- miio/gateway.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/miio/gateway.py b/miio/gateway.py index 5230ac840..6276feee8 100644 --- a/miio/gateway.py +++ b/miio/gateway.py @@ -145,6 +145,7 @@ def discover_devices(self): self._devices = [] for x in range(0, len(devices_raw), 5): + # Construct DeviceType try: device_type = DeviceType(devices_raw[x + 1]) except ValueError: @@ -155,7 +156,8 @@ def discover_devices(self): self.ip, ) device_type = DeviceType(-1) - + + # Obtain the correct subdevice class, ignoring the gateway itself subdevice_cls = device_type_mapping.get(device_type) if subdevice_cls is None and device_type != DeviceType(0): subdevice_cls = SubDevice @@ -163,7 +165,8 @@ def discover_devices(self): "Gateway device type '%s' does not have device specific methods defined, only basic default methods will be available", device_type.name, ) - + + # Initialize and save the subdevice, ignoring the gateway itself if devices_raw[x] != "lumi.0": self._devices.append(subdevice_cls(self, *devices_raw[x : x + 5])) From 064ffe808c322f55f10199c1885fdde42a53f8be Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Sat, 23 May 2020 18:32:05 +0200 Subject: [PATCH 11/35] use DeviceType.Gateway Co-authored-by: Teemu R. --- miio/gateway.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/miio/gateway.py b/miio/gateway.py index 6276feee8..867b288aa 100644 --- a/miio/gateway.py +++ b/miio/gateway.py @@ -159,7 +159,7 @@ def discover_devices(self): # Obtain the correct subdevice class, ignoring the gateway itself subdevice_cls = device_type_mapping.get(device_type) - if subdevice_cls is None and device_type != DeviceType(0): + if subdevice_cls is None and device_type != DeviceType.Gateway: subdevice_cls = SubDevice _LOGGER.info( "Gateway device type '%s' does not have device specific methods defined, only basic default methods will be available", From 96efd885f5e75e24eb6e72d6bf596f24a6787faf Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Sat, 23 May 2020 18:34:57 +0200 Subject: [PATCH 12/35] Update gateway.py --- miio/gateway.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/miio/gateway.py b/miio/gateway.py index 867b288aa..06596f383 100644 --- a/miio/gateway.py +++ b/miio/gateway.py @@ -167,7 +167,7 @@ def discover_devices(self): ) # Initialize and save the subdevice, ignoring the gateway itself - if devices_raw[x] != "lumi.0": + if device_type != DeviceType.Gateway: self._devices.append(subdevice_cls(self, *devices_raw[x : x + 5])) return self._devices From 651664f304614ff9606b44451a0e8eb4ad436fc1 Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Sat, 23 May 2020 19:02:49 +0200 Subject: [PATCH 13/35] improve discovered info --- miio/gateway.py | 38 ++++++++++++++++++++++++++------------ 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/miio/gateway.py b/miio/gateway.py index 06596f383..19ef2ed84 100644 --- a/miio/gateway.py +++ b/miio/gateway.py @@ -1,4 +1,5 @@ import logging +from dataclasses import dataclass from datetime import datetime from enum import Enum, IntEnum from typing import Optional @@ -54,6 +55,15 @@ class DeviceType(IntEnum): RemoteSwitchDouble = 135 +@dataclass +class SubDeviceInfo: + sid: str + type_id: int + unknown: int + unknown2: int + fw_ver: int + + class Gateway(Device): """Main class representing the Xiaomi Gateway. @@ -145,14 +155,22 @@ def discover_devices(self): self._devices = [] for x in range(0, len(devices_raw), 5): + # Extract discovered information + dev_info = SubDeviceInfo + dev_info.sid = devices_raw[x] + dev_info.type_id = devices_raw[x+1] + dev_info.unknown = devices_raw[x+2] + dev_info.unknown2 = devices_raw[x+3] + dev_info.fw_ver = devices_raw[x+4] + # Construct DeviceType try: - device_type = DeviceType(devices_raw[x + 1]) + device_type = DeviceType(dev_info.type_id) except ValueError: _LOGGER.warning( "Unknown subdevice type '%i': %s discovered, of Xiaomi gateway with ip: %s", - devices_raw[x + 1], - devices_raw[x], + dev_info.type_id, + dev_info.sid, self.ip, ) device_type = DeviceType(-1) @@ -168,7 +186,7 @@ def discover_devices(self): # Initialize and save the subdevice, ignoring the gateway itself if device_type != DeviceType.Gateway: - self._devices.append(subdevice_cls(self, *devices_raw[x : x + 5])) + self._devices.append(subdevice_cls(self, dev_info)) return self._devices @@ -552,11 +570,7 @@ class SubDevice(Device): def __init__( self, gw: Gateway = None, - sid: str = None, - type: int = None, - _: int = None, - __: int = None, - ___: int = None, + dev_info: SubDeviceInfo = None, ip: str = None, token: str = None, start_id: int = 0, @@ -571,14 +585,14 @@ def __init__( "Creating new device instance, only use this for cli interface" ) - if sid is None: + if dev_info is None: raise Exception("sid of the subdevice needs to be specified") self._gw = gw - self.sid = sid + self.sid = dev_info.sid self._battery = None try: - self.type = DeviceType(type) + self.type = DeviceType(dev_info.type_id) except ValueError: self.type = DeviceType(-1) From 25e52e6479fe132947b4fb45a29b67538caaca7f Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Sat, 23 May 2020 19:19:58 +0200 Subject: [PATCH 14/35] fix formatting --- miio/gateway.py | 145 +++++++++++++++++++++++++++++++++--------------- 1 file changed, 101 insertions(+), 44 deletions(-) diff --git a/miio/gateway.py b/miio/gateway.py index 19ef2ed84..0a80ae74f 100644 --- a/miio/gateway.py +++ b/miio/gateway.py @@ -70,7 +70,8 @@ class Gateway(Device): Use the given property getters to access specific functionalities such as `alarm` (for alarm controls) or `light` (for lights). - Commands whose functionality or parameters are unknown, feel free to implement! + Commands whose functionality or parameters are unknown, + feel free to implement! * toggle_device * toggle_plug * remove_all_bind @@ -142,7 +143,10 @@ def devices(self): @command() def discover_devices(self): - """Discovers SubDevices and returns a list of the discovered devices.""" + """ + Discovers SubDevices + and returns a list of the discovered devices. + """ # from https://github.com/aholstenson/miio/issues/26 device_type_mapping = { DeviceType.AqaraRelayTwoChannels: AqaraRelayTwoChannels, @@ -158,32 +162,35 @@ def discover_devices(self): # Extract discovered information dev_info = SubDeviceInfo dev_info.sid = devices_raw[x] - dev_info.type_id = devices_raw[x+1] - dev_info.unknown = devices_raw[x+2] - dev_info.unknown2 = devices_raw[x+3] - dev_info.fw_ver = devices_raw[x+4] + dev_info.type_id = devices_raw[x + 1] + dev_info.unknown = devices_raw[x + 2] + dev_info.unknown2 = devices_raw[x + 3] + dev_info.fw_ver = devices_raw[x + 4] # Construct DeviceType try: device_type = DeviceType(dev_info.type_id) except ValueError: _LOGGER.warning( - "Unknown subdevice type '%i': %s discovered, of Xiaomi gateway with ip: %s", + "Unknown subdevice type '%i': %s discovered, " + "of Xiaomi gateway with ip: %s", dev_info.type_id, dev_info.sid, self.ip, ) device_type = DeviceType(-1) - + # Obtain the correct subdevice class, ignoring the gateway itself subdevice_cls = device_type_mapping.get(device_type) if subdevice_cls is None and device_type != DeviceType.Gateway: subdevice_cls = SubDevice _LOGGER.info( - "Gateway device type '%s' does not have device specific methods defined, only basic default methods will be available", + "Gateway device type '%s' " + "does not have device specific methods defined, " + "only basic default methods will be available", device_type.name, ) - + # Initialize and save the subdevice, ignoring the gateway itself if device_type != DeviceType.Gateway: self._devices.append(subdevice_cls(self, dev_info)) @@ -198,7 +205,9 @@ def get_prop(self, property): @command(click.argument("properties", nargs=-1)) def get_prop_exp(self, properties): """Get the value of a bunch of properties for given sid.""" - return self.send("get_device_prop_exp", [["lumi.0"] + list(properties)]) + return self.send( + "get_device_prop_exp", [["lumi.0"] + list(properties)] + ) @command(click.argument("property"), click.argument("value")) def set_prop(self, property, value): @@ -208,7 +217,8 @@ def set_prop(self, property, value): @command() def clock(self): """Alarm clock""" - # payload of clock volume ("get_clock_volume") already in get_clock response + # payload of clock volume ("get_clock_volume") + # already in get_clock response return self.send("get_clock") # Developer key @@ -235,8 +245,12 @@ def get_illumination(self): """Get illumination. In lux?""" return self.send("get_illumination").pop() + class GatewayDevice(Device): - """GatewayDevice class to specify the init method for all gateway device functionalities.""" + """ + GatewayDevice class + Specifies the init method for all gateway device functionalities. + """ def __init__( self, @@ -255,6 +269,7 @@ def __init__( "Creating new device instance, only use this for cli interface" ) + class GatewayAlarm(GatewayDevice): """Class representing the Xiaomi Gateway Alarm.""" @@ -276,7 +291,10 @@ def off(self): @command() def arming_time(self) -> int: - """Return time in seconds the alarm stays 'oning' before transitioning to 'on'""" + """ + Return time in seconds the alarm stays 'oning' + before transitioning to 'on' + """ # Response: 5, 15, 30, 60 return self._gateway.send("get_arm_wait_time").pop() @@ -298,7 +316,10 @@ def set_triggering_time(self, seconds): @command() def triggering_light(self) -> int: - """Return the time the gateway light blinks when the alarm is triggerd""" + """ + Return the time the gateway light blinks + when the alarm is triggerd + """ # Response: 0=do not blink, 1=always blink, x>1=blink for x seconds return self._gateway.get_prop("en_alarm_light").pop() @@ -320,8 +341,13 @@ def set_triggering_volume(self, volume): @command() def last_status_change_time(self): - """Return the last time the alarm changed status, type datetime.datetime""" - return datetime.fromtimestamp(self._gateway.send("get_arming_time").pop()) + """ + Return the last time the alarm changed status, + type datetime.datetime + """ + return datetime.fromtimestamp( + self._gateway.send("get_arming_time").pop() + ) class GatewayZigbee(GatewayDevice): @@ -393,15 +419,18 @@ def set_radio_volume(self, volume): def play_music_new(self): """Unknown.""" - # {'from': '4', 'id': 9514, 'method': 'set_default_music', 'params': [2, '21']} - # {'from': '4', 'id': 9515, 'method': 'play_music_new', 'params': ['21', 0]} + # {'from': '4', 'id': 9514, + # 'method': 'set_default_music', 'params': [2, '21']} + # {'from': '4', 'id': 9515, + # 'method': 'play_music_new', 'params': ['21', 0]} raise NotImplementedError() def play_specify_fm(self): """play specific stream?""" raise NotImplementedError() # {"from": "4", "id": 65055, "method": "play_specify_fm", - # "params": {"id": 764, "type": 0, "url": "http://live.xmcdn.com/live/764/64.m3u8"}} + # "params": {"id": 764, "type": 0, + # "url": "http://live.xmcdn.com/live/764/64.m3u8"}} return self._gateway.send("play_specify_fm") def play_fm(self): @@ -491,7 +520,8 @@ def get_night_light_rgb(self): # Returns 0 when light is off?""" # looks like this is the same as get_rgb # id': 65064, 'method': 'set_night_light_rgb', 'params': [419407616]} - # {'method': 'props', 'params': {'light': 'on', 'from.light': '4,,,'}, 'id': 88457} ?! + # {'method': 'props', 'params': + # {'light': 'on', 'from.light': '4,,,'}, 'id': 88457} ?! return self._gateway.send("get_night_light_rgb") @command(click.argument("color_name", type=str)) @@ -509,7 +539,9 @@ def set_night_light_color(self, color_name): brightness_and_color = brightness_and_color_to_int( current_brightness, color_map[color_name] ) - return self._gateway.send("set_night_light_rgb", [brightness_and_color]) + return self._gateway.send( + "set_night_light_rgb", [brightness_and_color] + ) @command(click.argument("color_name", type=str)) def set_color(self, color_name): @@ -520,7 +552,9 @@ def set_color(self, color_name): color=color_name, colors=color_map.keys() ) ) - current_brightness = int_to_brightness(self._gateway.send("get_rgb")[0]) + current_brightness = int_to_brightness( + self._gateway.send("get_rgb")[0] + ) brightness_and_color = brightness_and_color_to_int( current_brightness, color_map[color_name] ) @@ -532,7 +566,9 @@ def set_brightness(self, brightness): if 100 < brightness < 0: raise Exception("Brightness must be between 0 and 100") current_color = int_to_rgb(self._gateway.send("get_rgb")[0]) - brightness_and_color = brightness_and_color_to_int(brightness, current_color) + brightness_and_color = brightness_and_color_to_int( + brightness, current_color + ) return self._gateway.send("set_rgb", [brightness_and_color]) @command(click.argument("brightness", type=int)) @@ -540,13 +576,20 @@ def set_night_light_brightness(self, brightness): """Set night light brightness (0-100).""" if 100 < brightness < 0: raise Exception("Brightness must be between 0 and 100") - current_color = int_to_rgb(self._gateway.send("get_night_light_rgb")[0]) - brightness_and_color = brightness_and_color_to_int(brightness, current_color) + current_color = int_to_rgb( + self._gateway.send("get_night_light_rgb")[0] + ) + brightness_and_color = brightness_and_color_to_int( + brightness, current_color + ) print(brightness, current_color) - return self._gateway.send("set_night_light_rgb", [brightness_and_color]) + return self._gateway.send( + "set_night_light_rgb", [brightness_and_color] + ) @command( - click.argument("color_name", type=str), click.argument("brightness", type=int) + click.argument("color_name", type=str), + click.argument("brightness", type=int), ) def set_light(self, color_name, brightness): """Set color (using color name) and brightness (0-100).""" @@ -565,8 +608,11 @@ def set_light(self, color_name, brightness): class SubDevice(Device): - """Base class for all subdevices of the gateway that are connected through zigbee.""" - + """ + Base class for all subdevices of the gateway + these devices are connected through zigbee. + """ + def __init__( self, gw: Gateway = None, @@ -621,20 +667,21 @@ def subdevice_send(self, command): return self._gw.send(command, [self.sid]) except Exception as ex: raise GatewayException( - "Got an exception while sending command %s" - % (command) - ) from ex + "Got an exception while sending command %s" % (command) + ) from ex return None @command() def subdevice_send_arg(self, command, arguments): """Send a command/query including arguments to the subdevice""" try: - return self._gw.send(command, arguments, extra_parameters={"sid": self.sid}) + return self._gw.send( + command, arguments, extra_parameters={"sid": self.sid} + ) except Exception as ex: raise GatewayException( - "Got an exception while sending command '%s' with arguments '%s'" - % (command, str(arguments)) + "Got an exception while sending " + "command '%s' with arguments '%s'" % (command, str(arguments)) ) from ex @command(click.argument("property")) @@ -649,7 +696,8 @@ def get_subdevice_prop(self, property): if not response: raise GatewayException( - "Empty response while fetching property %s: %s" % (property, response) + "Empty response while fetching property %s: %s" + % (property, response) ) return response @@ -663,7 +711,8 @@ def get_subdevice_prop_exp(self, properties): ).pop() except Exception as ex: raise GatewayException( - "Got an exception while fetching properties %s: %s" % (properties) + "Got an exception while fetching properties %s: %s" + % (properties) ) from ex if len(list(properties)) != len(response): @@ -675,8 +724,8 @@ def get_subdevice_prop_exp(self, properties): for item in response: if not item: raise GatewayException( - "One or more empty results while fetching properties %s: %s" - % (properties, response) + "One or more empty results while " + "fetching properties %s: %s" % (properties, response) ) return response @@ -685,10 +734,12 @@ def get_subdevice_prop_exp(self, properties): def set_subdevice_prop(self, property, value): """Set a device property of the subdevice.""" try: - return self._gw.send("set_device_prop", {"sid": self.sid, property: value}) + return self._gw.send( + "set_device_prop", {"sid": self.sid, property: value} + ) except Exception as ex: raise GatewayException( - "Got an exception while setting propertie %s to value %s: %s" + "Got an exception while setting propertie %s to value %s" % (property, str(value)) ) from ex @@ -732,7 +783,9 @@ def pressure(self): @command() def update(self): """Update all device properties""" - values = self.get_subdevice_prop_exp(["temperature", "humidity", "pressure"]) + values = self.get_subdevice_prop_exp( + ["temperature", "humidity", "pressure"] + ) self._temperature = values[0] / 100 self._humidity = values[1] / 100 self._pressure = values[2] / 100 @@ -808,12 +861,14 @@ class AqaraRelayTwoChannels(SubDevice): class AqaraRelayToggleValue(Enum): """Options to control the relay""" + toggle = "toggle" on = "on" off = "off" class AqaraRelayChannel(Enum): """Options to select wich relay to control""" + first = "channel_0" second = "channel_1" @@ -835,7 +890,9 @@ def load_power(self): @command() def update(self): """Update all device properties""" - values = self.get_subdevice_prop_exp(["load_power", "channel_0", "channel_1"]) + values = self.get_subdevice_prop_exp( + ["load_power", "channel_0", "channel_1"] + ) self._load_power = values[0] self._status_ch0 = values[1] self._status_ch1 = values[2] From d4da84e9bdcc4d7625756326bc844a8b9a79b61e Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Sat, 23 May 2020 19:23:50 +0200 Subject: [PATCH 15/35] better use of dev_info --- miio/gateway.py | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/miio/gateway.py b/miio/gateway.py index 0a80ae74f..bead7b4fa 100644 --- a/miio/gateway.py +++ b/miio/gateway.py @@ -160,22 +160,16 @@ def discover_devices(self): for x in range(0, len(devices_raw), 5): # Extract discovered information - dev_info = SubDeviceInfo - dev_info.sid = devices_raw[x] - dev_info.type_id = devices_raw[x + 1] - dev_info.unknown = devices_raw[x + 2] - dev_info.unknown2 = devices_raw[x + 3] - dev_info.fw_ver = devices_raw[x + 4] + dev_info = SubDeviceInfo(*devices_raw[x : x + 5]) # Construct DeviceType try: device_type = DeviceType(dev_info.type_id) except ValueError: _LOGGER.warning( - "Unknown subdevice type '%i': %s discovered, " + "Unknown subdevice type %s discovered, " "of Xiaomi gateway with ip: %s", - dev_info.type_id, - dev_info.sid, + dev_info, self.ip, ) device_type = DeviceType(-1) From b2bb69dbf175228f92aa0ab1589e88ad913b18f7 Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Sat, 23 May 2020 19:27:23 +0200 Subject: [PATCH 16/35] final black formatting --- miio/gateway.py | 57 +++++++++++++------------------------------------ 1 file changed, 15 insertions(+), 42 deletions(-) diff --git a/miio/gateway.py b/miio/gateway.py index bead7b4fa..d2ffe3aa6 100644 --- a/miio/gateway.py +++ b/miio/gateway.py @@ -199,9 +199,7 @@ def get_prop(self, property): @command(click.argument("properties", nargs=-1)) def get_prop_exp(self, properties): """Get the value of a bunch of properties for given sid.""" - return self.send( - "get_device_prop_exp", [["lumi.0"] + list(properties)] - ) + return self.send("get_device_prop_exp", [["lumi.0"] + list(properties)]) @command(click.argument("property"), click.argument("value")) def set_prop(self, property, value): @@ -339,9 +337,7 @@ def last_status_change_time(self): Return the last time the alarm changed status, type datetime.datetime """ - return datetime.fromtimestamp( - self._gateway.send("get_arming_time").pop() - ) + return datetime.fromtimestamp(self._gateway.send("get_arming_time").pop()) class GatewayZigbee(GatewayDevice): @@ -533,9 +529,7 @@ def set_night_light_color(self, color_name): brightness_and_color = brightness_and_color_to_int( current_brightness, color_map[color_name] ) - return self._gateway.send( - "set_night_light_rgb", [brightness_and_color] - ) + return self._gateway.send("set_night_light_rgb", [brightness_and_color]) @command(click.argument("color_name", type=str)) def set_color(self, color_name): @@ -546,9 +540,7 @@ def set_color(self, color_name): color=color_name, colors=color_map.keys() ) ) - current_brightness = int_to_brightness( - self._gateway.send("get_rgb")[0] - ) + current_brightness = int_to_brightness(self._gateway.send("get_rgb")[0]) brightness_and_color = brightness_and_color_to_int( current_brightness, color_map[color_name] ) @@ -560,9 +552,7 @@ def set_brightness(self, brightness): if 100 < brightness < 0: raise Exception("Brightness must be between 0 and 100") current_color = int_to_rgb(self._gateway.send("get_rgb")[0]) - brightness_and_color = brightness_and_color_to_int( - brightness, current_color - ) + brightness_and_color = brightness_and_color_to_int(brightness, current_color) return self._gateway.send("set_rgb", [brightness_and_color]) @command(click.argument("brightness", type=int)) @@ -570,20 +560,13 @@ def set_night_light_brightness(self, brightness): """Set night light brightness (0-100).""" if 100 < brightness < 0: raise Exception("Brightness must be between 0 and 100") - current_color = int_to_rgb( - self._gateway.send("get_night_light_rgb")[0] - ) - brightness_and_color = brightness_and_color_to_int( - brightness, current_color - ) + current_color = int_to_rgb(self._gateway.send("get_night_light_rgb")[0]) + brightness_and_color = brightness_and_color_to_int(brightness, current_color) print(brightness, current_color) - return self._gateway.send( - "set_night_light_rgb", [brightness_and_color] - ) + return self._gateway.send("set_night_light_rgb", [brightness_and_color]) @command( - click.argument("color_name", type=str), - click.argument("brightness", type=int), + click.argument("color_name", type=str), click.argument("brightness", type=int), ) def set_light(self, color_name, brightness): """Set color (using color name) and brightness (0-100).""" @@ -669,9 +652,7 @@ def subdevice_send(self, command): def subdevice_send_arg(self, command, arguments): """Send a command/query including arguments to the subdevice""" try: - return self._gw.send( - command, arguments, extra_parameters={"sid": self.sid} - ) + return self._gw.send(command, arguments, extra_parameters={"sid": self.sid}) except Exception as ex: raise GatewayException( "Got an exception while sending " @@ -690,8 +671,7 @@ def get_subdevice_prop(self, property): if not response: raise GatewayException( - "Empty response while fetching property %s: %s" - % (property, response) + "Empty response while fetching property %s: %s" % (property, response) ) return response @@ -705,8 +685,7 @@ def get_subdevice_prop_exp(self, properties): ).pop() except Exception as ex: raise GatewayException( - "Got an exception while fetching properties %s: %s" - % (properties) + "Got an exception while fetching properties %s: %s" % (properties) ) from ex if len(list(properties)) != len(response): @@ -728,9 +707,7 @@ def get_subdevice_prop_exp(self, properties): def set_subdevice_prop(self, property, value): """Set a device property of the subdevice.""" try: - return self._gw.send( - "set_device_prop", {"sid": self.sid, property: value} - ) + return self._gw.send("set_device_prop", {"sid": self.sid, property: value}) except Exception as ex: raise GatewayException( "Got an exception while setting propertie %s to value %s" @@ -777,9 +754,7 @@ def pressure(self): @command() def update(self): """Update all device properties""" - values = self.get_subdevice_prop_exp( - ["temperature", "humidity", "pressure"] - ) + values = self.get_subdevice_prop_exp(["temperature", "humidity", "pressure"]) self._temperature = values[0] / 100 self._humidity = values[1] / 100 self._pressure = values[2] / 100 @@ -884,9 +859,7 @@ def load_power(self): @command() def update(self): """Update all device properties""" - values = self.get_subdevice_prop_exp( - ["load_power", "channel_0", "channel_1"] - ) + values = self.get_subdevice_prop_exp(["load_power", "channel_0", "channel_1"]) self._load_power = values[0] self._status_ch0 = values[1] self._status_ch1 = values[2] From 194a57aa6d59b76183522ce3f9ebdbf5040681dc Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Mon, 25 May 2020 00:01:00 +0200 Subject: [PATCH 17/35] process revieuw --- miio/gateway.py | 43 +++++++++++++++++++++++++------------------ 1 file changed, 25 insertions(+), 18 deletions(-) diff --git a/miio/gateway.py b/miio/gateway.py index d2ffe3aa6..c30d085a3 100644 --- a/miio/gateway.py +++ b/miio/gateway.py @@ -614,6 +614,7 @@ def __init__( self._gw = gw self.sid = dev_info.sid self._battery = None + self._fw_ver = dev_info.fw_ver try: self.type = DeviceType(dev_info.type_id) except ValueError: @@ -638,7 +639,7 @@ def battery(self): return self._battery @command() - def subdevice_send(self, command): + def send(self, command): """Send a command/query to the subdevice""" try: return self._gw.send(command, [self.sid]) @@ -646,10 +647,9 @@ def subdevice_send(self, command): raise GatewayException( "Got an exception while sending command %s" % (command) ) from ex - return None @command() - def subdevice_send_arg(self, command, arguments): + def send_arg(self, command, arguments): """Send a command/query including arguments to the subdevice""" try: return self._gw.send(command, arguments, extra_parameters={"sid": self.sid}) @@ -660,7 +660,7 @@ def subdevice_send_arg(self, command, arguments): ) from ex @command(click.argument("property")) - def get_subdevice_prop(self, property): + def get_property(self, property): """Get the value of a property of the subdevice.""" try: response = self._gw.send("get_device_prop", [self.sid, property]) @@ -669,15 +669,15 @@ def get_subdevice_prop(self, property): "Got an exception while fetching property %s" % (property) ) from ex - if not response: + if response == []: raise GatewayException( - "Empty response while fetching property %s: %s" % (property, response) + "Empty response while fetching property '%s': %s" % (property, response) ) return response @command(click.argument("properties", nargs=-1)) - def get_subdevice_prop_exp(self, properties): + def get_property_exp(self, properties): """Get the value of a bunch of properties of the subdevice.""" try: response = self._gw.send( @@ -695,7 +695,7 @@ def get_subdevice_prop_exp(self, properties): ) for item in response: - if not item: + if item == '': raise GatewayException( "One or more empty results while " "fetching properties %s: %s" % (properties, response) @@ -704,7 +704,7 @@ def get_subdevice_prop_exp(self, properties): return response @command(click.argument("property"), click.argument("value")) - def set_subdevice_prop(self, property, value): + def set_property(self, property, value): """Set a device property of the subdevice.""" try: return self._gw.send("set_device_prop", {"sid": self.sid, property: value}) @@ -717,21 +717,26 @@ def set_subdevice_prop(self, property, value): @command() def unpair(self): """Unpair this device from the gateway.""" - return self.subdevice_send("remove_device") + return self.send("remove_device") @command() def get_battery(self): """Update the battery level and return the new value.""" - self._battery = self.subdevice_send("get_battery").pop() + self._battery = self.send("get_battery").pop() return self._battery @command() def get_firmware_version(self) -> Optional[int]: """Returns firmware version""" - return self.get_subdevice_prop("fw_ver").pop() + try: + self._fw_ver = self.get_property("fw_ver").pop() + except: + _LOGGER.info("get_firmware_version failed, returning firmware version from discovery info") + return self._fw_ver class AqaraHT(SubDevice): + properties = ["temperature", "humidity", "pressure"] _temperature = None _humidity = None _pressure = None @@ -754,7 +759,7 @@ def pressure(self): @command() def update(self): """Update all device properties""" - values = self.get_subdevice_prop_exp(["temperature", "humidity", "pressure"]) + values = self.get_property_exp(self.properties) self._temperature = values[0] / 100 self._humidity = values[1] / 100 self._pressure = values[2] / 100 @@ -779,12 +784,13 @@ def humidity(self): @command() def update(self): """Update all device properties""" - values = self.get_subdevice_prop_exp(self.properties) + values = self.get_property_exp(self.properties) self._temperature = values[0] / 100 self._humidity = values[1] / 100 class AqaraMagnet(SubDevice): + properties = ["unkown"] _status = None @property @@ -795,7 +801,7 @@ def status(self): @command() def update(self): """Update all device properties""" - values = self.get_subdevice_prop_exp(["unkown"]) + values = self.get_property_exp(self.properties) self._status = values[0] @@ -818,12 +824,13 @@ def status(self): @command() def update(self): """Update all device properties""" - values = self.get_subdevice_prop_exp(self.properties) + values = self.get_property_exp(self.properties) self._power = values[0] self._status = values[1] class AqaraRelayTwoChannels(SubDevice): + properties = ["load_power", "channel_0", "channel_1"] _status_ch0 = None _status_ch1 = None _load_power = None @@ -859,7 +866,7 @@ def load_power(self): @command() def update(self): """Update all device properties""" - values = self.get_subdevice_prop_exp(["load_power", "channel_0", "channel_1"]) + values = self.get_property_exp(self.properties) self._load_power = values[0] self._status_ch0 = values[1] self._status_ch1 = values[2] @@ -870,6 +877,6 @@ def update(self): ) def toggle(self, sid, channel, value): """Toggle Aqara Wireless Relay 2ch""" - return self.subdevice_send_arg( + return self.send_arg( "toggle_ctrl_neutral", [channel.value, value.value] ).pop() From e85b8855cacf47472d35799cb175c319851485cf Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Mon, 25 May 2020 01:13:03 +0200 Subject: [PATCH 18/35] generilize properties of subdevices --- miio/gateway.py | 82 +++++++++++++++++++++++-------------------------- 1 file changed, 39 insertions(+), 43 deletions(-) diff --git a/miio/gateway.py b/miio/gateway.py index c30d085a3..500478d3a 100644 --- a/miio/gateway.py +++ b/miio/gateway.py @@ -1,5 +1,5 @@ import logging -from dataclasses import dataclass +from dataclasses import dataclass, asdict as dataclasses_asdict from datetime import datetime from enum import Enum, IntEnum from typing import Optional @@ -594,23 +594,7 @@ def __init__( self, gw: Gateway = None, dev_info: SubDeviceInfo = None, - ip: str = None, - token: str = None, - start_id: int = 0, - debug: int = 0, - lazy_discover: bool = True, ) -> None: - if gw is not None: - self._gw = gw - else: - self._gw = Device(ip, token, start_id, debug, lazy_discover) - _LOGGER.debug( - "Creating new device instance, only use this for cli interface" - ) - - if dev_info is None: - raise Exception("sid of the subdevice needs to be specified") - self._gw = gw self.sid = dev_info.sid self._battery = None @@ -621,13 +605,23 @@ def __init__( self.type = DeviceType(-1) def __repr__(self): - return "" % ( + return "" % ( self.device_type, self.sid, self.get_firmware_version(), self.get_battery(), + self.status, ) + @property + def status(self): + """Return sub-device status as a dict containing all properties.""" + if hasattr(self, '_props'): + return dataclasses_asdict(self._props) + else: + _LOGGER.error("Subdevice '%s' does not have device specific properties defined", self.device_type) + return {} + @property def device_type(self): """Return the device type name.""" @@ -737,32 +731,28 @@ def get_firmware_version(self) -> Optional[int]: class AqaraHT(SubDevice): properties = ["temperature", "humidity", "pressure"] - _temperature = None - _humidity = None - _pressure = None - - @property - def temperature(self): - """Return the temperature in degrees celsius""" - return self._temperature - @property - def humidity(self): - """Return the humidity in %""" - return self._humidity + @dataclass + class props: + temperature: int # in degrees celsius + humidity: int # in % + pressure: int # in hPa - @property - def pressure(self): - """Return the pressure in hPa""" - return self._pressure + def __init__( + self, + gw: Gateway = None, + dev_info: SubDeviceInfo = None + ): + super().__init__(gw, dev_info) + self._props = self.props(None, None, None) @command() def update(self): """Update all device properties""" values = self.get_property_exp(self.properties) - self._temperature = values[0] / 100 - self._humidity = values[1] / 100 - self._pressure = values[2] / 100 + self._props.temperature = values[0] / 100 + self._props.humidity = values[1] / 100 + self._props.pressure = values[2] / 100 class SensorHT(SubDevice): @@ -791,18 +781,24 @@ def update(self): class AqaraMagnet(SubDevice): properties = ["unkown"] - _status = None - @property - def status(self): - """Returns 'open' or 'closed'""" - return self._status + @dataclass + class props: + status: str # 'open' or 'closed' + + def __init__( + self, + gw: Gateway = None, + dev_info: SubDeviceInfo = None + ): + super().__init__(gw, dev_info) + self._props = self.props(None) @command() def update(self): """Update all device properties""" values = self.get_property_exp(self.properties) - self._status = values[0] + self._props.status = values[0] class AqaraPlug(SubDevice): From 040e2c1ab261703f7eae1ccfa7e582d6c87fa495 Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Mon, 25 May 2020 01:17:14 +0200 Subject: [PATCH 19/35] Subdevice schould not derive from Device --- miio/gateway.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/miio/gateway.py b/miio/gateway.py index 500478d3a..3c114a078 100644 --- a/miio/gateway.py +++ b/miio/gateway.py @@ -584,7 +584,7 @@ def set_light(self, color_name, brightness): return self._gateway.send("set_rgb", [brightness_and_color]) -class SubDevice(Device): +class SubDevice(): """ Base class for all subdevices of the gateway these devices are connected through zigbee. From fc8f8a7701f3e596ee160e27ac3d42134b295dec Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Tue, 26 May 2020 19:21:48 +0200 Subject: [PATCH 20/35] simplify dataclass props for subdevices --- miio/gateway.py | 100 +++++++++++++++--------------------------------- 1 file changed, 31 insertions(+), 69 deletions(-) diff --git a/miio/gateway.py b/miio/gateway.py index 3c114a078..e06caa3d6 100644 --- a/miio/gateway.py +++ b/miio/gateway.py @@ -332,10 +332,9 @@ def set_triggering_volume(self, volume): return self._gateway.send("set_alarming_volume", [volume]) @command() - def last_status_change_time(self): + def last_status_change_time(self) -> datetime: """ - Return the last time the alarm changed status, - type datetime.datetime + Return the last time the alarm changed status """ return datetime.fromtimestamp(self._gateway.send("get_arming_time").pop()) @@ -603,6 +602,10 @@ def __init__( self.type = DeviceType(dev_info.type_id) except ValueError: self.type = DeviceType(-1) + + # if dataclass props is defined, initialize an instance + if hasattr(self, 'props'): + self._props = self.props() def __repr__(self): return "" % ( @@ -734,17 +737,9 @@ class AqaraHT(SubDevice): @dataclass class props: - temperature: int # in degrees celsius - humidity: int # in % - pressure: int # in hPa - - def __init__( - self, - gw: Gateway = None, - dev_info: SubDeviceInfo = None - ): - super().__init__(gw, dev_info) - self._props = self.props(None, None, None) + temperature: int = None # in degrees celsius + humidity: int = None # in % + pressure: int = None # in hPa @command() def update(self): @@ -758,25 +753,18 @@ def update(self): class SensorHT(SubDevice): accessor = "get_prop_sensor_ht" properties = ["temperature", "humidity"] - _temperature = None - _humidity = None - - @property - def temperature(self): - """Return the temperature in degrees celsius""" - return self._temperature - @property - def humidity(self): - """Return the humidity in %""" - return self._humidity + @dataclass + class props: + temperature: int = None # in degrees celsius + humidity: int = None # in % @command() def update(self): """Update all device properties""" values = self.get_property_exp(self.properties) - self._temperature = values[0] / 100 - self._humidity = values[1] / 100 + self._props.temperature = values[0] / 100 + self._props.humidity = values[1] / 100 class AqaraMagnet(SubDevice): @@ -784,15 +772,7 @@ class AqaraMagnet(SubDevice): @dataclass class props: - status: str # 'open' or 'closed' - - def __init__( - self, - gw: Gateway = None, - dev_info: SubDeviceInfo = None - ): - super().__init__(gw, dev_info) - self._props = self.props(None) + status: str = None # 'open' or 'closed' @command() def update(self): @@ -804,25 +784,18 @@ def update(self): class AqaraPlug(SubDevice): accessor = "get_prop_plug" properties = ["power", "neutral_0"] - _power = None - _status = None - @property - def power(self): - """Return the power consumption""" - return self._power - - @property - def status(self): - """Return the status of the plug: on/off""" - return self._status + @dataclass + class props: + status: str = None # 'on' / 'off' + load_power: int = None # power consumption in ?unit? @command() def update(self): """Update all device properties""" values = self.get_property_exp(self.properties) - self._power = values[0] - self._status = values[1] + self._props.load_power = values[0] + self._props.status = values[1] class AqaraRelayTwoChannels(SubDevice): @@ -831,41 +804,30 @@ class AqaraRelayTwoChannels(SubDevice): _status_ch1 = None _load_power = None + @dataclass + class props: + status_ch0: str = None # 'on' / 'off' + status_ch1: str = None # 'on' / 'off' + load_power: int = None # power consumption in ?unit? + class AqaraRelayToggleValue(Enum): """Options to control the relay""" - toggle = "toggle" on = "on" off = "off" class AqaraRelayChannel(Enum): """Options to select wich relay to control""" - first = "channel_0" second = "channel_1" - @property - def status_ch0(self): - """Return the state of channel 0""" - return self._status_ch0 - - @property - def status_ch1(self): - """Return the state of channel 1""" - return self._status_ch1 - - @property - def load_power(self): - """Return the load power""" - return self._load_power - @command() def update(self): """Update all device properties""" values = self.get_property_exp(self.properties) - self._load_power = values[0] - self._status_ch0 = values[1] - self._status_ch1 = values[2] + self._props.load_power = values[0] + self._props.status_ch0 = values[1] + self._props.status_ch1 = values[2] @command( click.argument("channel", type=EnumType(AqaraRelayChannel)), From 1f016abeac88367dca92a7ea7607c181c2cfdb7d Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Tue, 26 May 2020 19:58:45 +0200 Subject: [PATCH 21/35] optimization --- miio/gateway.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/miio/gateway.py b/miio/gateway.py index e06caa3d6..5d26a0ddd 100644 --- a/miio/gateway.py +++ b/miio/gateway.py @@ -611,7 +611,7 @@ def __repr__(self): return "" % ( self.device_type, self.sid, - self.get_firmware_version(), + self._fw_ver, self.get_battery(), self.status, ) @@ -622,7 +622,7 @@ def status(self): if hasattr(self, '_props'): return dataclasses_asdict(self._props) else: - _LOGGER.error("Subdevice '%s' does not have device specific properties defined", self.device_type) + _LOGGER.debug("Subdevice '%s' does not have device specific properties defined", self.device_type) return {} @property From 2b471c24ddcd05649a8e867e4b5dd362b38320fb Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Tue, 26 May 2020 22:37:47 +0200 Subject: [PATCH 22/35] remove empty checks and futher simplify dataclass --- miio/gateway.py | 64 +++++++++++++++++-------------------------------- 1 file changed, 22 insertions(+), 42 deletions(-) diff --git a/miio/gateway.py b/miio/gateway.py index 5d26a0ddd..cd50cb96a 100644 --- a/miio/gateway.py +++ b/miio/gateway.py @@ -151,7 +151,7 @@ def discover_devices(self): device_type_mapping = { DeviceType.AqaraRelayTwoChannels: AqaraRelayTwoChannels, DeviceType.Plug: AqaraPlug, - DeviceType.SensorHT: SensorHT, + DeviceType.SensorHT: AqaraHT, DeviceType.AqaraHT: AqaraHT, DeviceType.AqaraMagnet: AqaraMagnet, } @@ -589,6 +589,10 @@ class SubDevice(): these devices are connected through zigbee. """ + @dataclass + class props: + """Defines properties of the specific device""" + def __init__( self, gw: Gateway = None, @@ -598,14 +602,11 @@ def __init__( self.sid = dev_info.sid self._battery = None self._fw_ver = dev_info.fw_ver + self._props = self.props() try: self.type = DeviceType(dev_info.type_id) except ValueError: self.type = DeviceType(-1) - - # if dataclass props is defined, initialize an instance - if hasattr(self, 'props'): - self._props = self.props() def __repr__(self): return "" % ( @@ -619,11 +620,7 @@ def __repr__(self): @property def status(self): """Return sub-device status as a dict containing all properties.""" - if hasattr(self, '_props'): - return dataclasses_asdict(self._props) - else: - _LOGGER.debug("Subdevice '%s' does not have device specific properties defined", self.device_type) - return {} + return dataclasses_asdict(self._props) @property def device_type(self): @@ -635,6 +632,11 @@ def battery(self): """Return the battery level.""" return self._battery + @command() + def update(self): + """Update the device-specific properties.""" + _LOGGER.debug("Subdevice '%s' does not have a device specific update method defined", self.device_type) + @command() def send(self, command): """Send a command/query to the subdevice""" @@ -666,11 +668,6 @@ def get_property(self, property): "Got an exception while fetching property %s" % (property) ) from ex - if response == []: - raise GatewayException( - "Empty response while fetching property '%s': %s" % (property, response) - ) - return response @command(click.argument("properties", nargs=-1)) @@ -691,13 +688,6 @@ def get_property_exp(self, properties): % (properties, response) ) - for item in response: - if item == '': - raise GatewayException( - "One or more empty results while " - "fetching properties %s: %s" % (properties, response) - ) - return response @command(click.argument("property"), click.argument("value")) @@ -733,6 +723,7 @@ def get_firmware_version(self) -> Optional[int]: class AqaraHT(SubDevice): + accessor = "get_prop_sensor_ht" properties = ["temperature", "humidity", "pressure"] @dataclass @@ -745,26 +736,15 @@ class props: def update(self): """Update all device properties""" values = self.get_property_exp(self.properties) - self._props.temperature = values[0] / 100 - self._props.humidity = values[1] / 100 - self._props.pressure = values[2] / 100 - - -class SensorHT(SubDevice): - accessor = "get_prop_sensor_ht" - properties = ["temperature", "humidity"] - - @dataclass - class props: - temperature: int = None # in degrees celsius - humidity: int = None # in % - - @command() - def update(self): - """Update all device properties""" - values = self.get_property_exp(self.properties) - self._props.temperature = values[0] / 100 - self._props.humidity = values[1] / 100 + try: + self._props.temperature = values[0] / 100 + self._props.humidity = values[1] / 100 + self._props.pressure = values[2] / 100 + except Exception as ex: + raise GatewayException( + "One or more unexpected results while " + "fetching properties %s: %s" % (self.properties, values) + ) from ex class AqaraMagnet(SubDevice): From 2b7e1b729d085e3c24c359ee166229b6d33e3461 Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Tue, 26 May 2020 22:54:24 +0200 Subject: [PATCH 23/35] Update gateway.py --- miio/gateway.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/miio/gateway.py b/miio/gateway.py index cd50cb96a..d00b49a29 100644 --- a/miio/gateway.py +++ b/miio/gateway.py @@ -606,7 +606,7 @@ def __init__( try: self.type = DeviceType(dev_info.type_id) except ValueError: - self.type = DeviceType(-1) + self.type = DeviceType.Unknown def __repr__(self): return "" % ( From 9a4eef756d84e313d7feb3a4787c9ba1ad96f936 Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Tue, 26 May 2020 22:56:49 +0200 Subject: [PATCH 24/35] add back SensorHT --- miio/gateway.py | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/miio/gateway.py b/miio/gateway.py index d00b49a29..f59cab3e1 100644 --- a/miio/gateway.py +++ b/miio/gateway.py @@ -151,7 +151,7 @@ def discover_devices(self): device_type_mapping = { DeviceType.AqaraRelayTwoChannels: AqaraRelayTwoChannels, DeviceType.Plug: AqaraPlug, - DeviceType.SensorHT: AqaraHT, + DeviceType.SensorHT: SensorHT, DeviceType.AqaraHT: AqaraHT, DeviceType.AqaraMagnet: AqaraMagnet, } @@ -746,6 +746,29 @@ def update(self): "fetching properties %s: %s" % (self.properties, values) ) from ex +class SensorHT(SubDevice): + accessor = "get_prop_sensor_ht" + properties = ["temperature", "humidity", "pressure"] + + @dataclass + class props: + temperature: int = None # in degrees celsius + humidity: int = None # in % + pressure: int = None # in hPa + + @command() + def update(self): + """Update all device properties""" + values = self.get_property_exp(self.properties) + try: + self._props.temperature = values[0] / 100 + self._props.humidity = values[1] / 100 + self._props.pressure = values[2] / 100 + except Exception as ex: + raise GatewayException( + "One or more unexpected results while " + "fetching properties %s: %s" % (self.properties, values) + ) from ex class AqaraMagnet(SubDevice): properties = ["unkown"] From f73b167cb309929e21211021783e85598c7235d9 Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Tue, 26 May 2020 22:59:49 +0200 Subject: [PATCH 25/35] add back Empty response --- miio/gateway.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/miio/gateway.py b/miio/gateway.py index f59cab3e1..bbdcc152d 100644 --- a/miio/gateway.py +++ b/miio/gateway.py @@ -668,6 +668,11 @@ def get_property(self, property): "Got an exception while fetching property %s" % (property) ) from ex + if not response: + raise GatewayException( + "Empty response while fetching property '%s': %s" % (property, response) + ) + return response @command(click.argument("properties", nargs=-1)) From 8b841636660cf0c1d775cb2e1c07ecdd4ec9dc6c Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Tue, 26 May 2020 23:01:32 +0200 Subject: [PATCH 26/35] black formatting --- miio/gateway.py | 39 +++++++++++++++++++++------------------ 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/miio/gateway.py b/miio/gateway.py index bbdcc152d..6f8cbdb97 100644 --- a/miio/gateway.py +++ b/miio/gateway.py @@ -583,7 +583,7 @@ def set_light(self, color_name, brightness): return self._gateway.send("set_rgb", [brightness_and_color]) -class SubDevice(): +class SubDevice: """ Base class for all subdevices of the gateway these devices are connected through zigbee. @@ -593,11 +593,7 @@ class SubDevice(): class props: """Defines properties of the specific device""" - def __init__( - self, - gw: Gateway = None, - dev_info: SubDeviceInfo = None, - ) -> None: + def __init__(self, gw: Gateway = None, dev_info: SubDeviceInfo = None,) -> None: self._gw = gw self.sid = dev_info.sid self._battery = None @@ -635,7 +631,10 @@ def battery(self): @command() def update(self): """Update the device-specific properties.""" - _LOGGER.debug("Subdevice '%s' does not have a device specific update method defined", self.device_type) + _LOGGER.debug( + "Subdevice '%s' does not have a device specific update method defined", + self.device_type, + ) @command() def send(self, command): @@ -723,7 +722,9 @@ def get_firmware_version(self) -> Optional[int]: try: self._fw_ver = self.get_property("fw_ver").pop() except: - _LOGGER.info("get_firmware_version failed, returning firmware version from discovery info") + _LOGGER.info( + "get_firmware_version failed, returning firmware version from discovery info" + ) return self._fw_ver @@ -733,9 +734,9 @@ class AqaraHT(SubDevice): @dataclass class props: - temperature: int = None # in degrees celsius - humidity: int = None # in % - pressure: int = None # in hPa + temperature: int = None # in degrees celsius + humidity: int = None # in % + pressure: int = None # in hPa @command() def update(self): @@ -751,15 +752,16 @@ def update(self): "fetching properties %s: %s" % (self.properties, values) ) from ex + class SensorHT(SubDevice): accessor = "get_prop_sensor_ht" properties = ["temperature", "humidity", "pressure"] @dataclass class props: - temperature: int = None # in degrees celsius - humidity: int = None # in % - pressure: int = None # in hPa + temperature: int = None # in degrees celsius + humidity: int = None # in % + pressure: int = None # in hPa @command() def update(self): @@ -775,6 +777,7 @@ def update(self): "fetching properties %s: %s" % (self.properties, values) ) from ex + class AqaraMagnet(SubDevice): properties = ["unkown"] @@ -795,7 +798,7 @@ class AqaraPlug(SubDevice): @dataclass class props: - status: str = None # 'on' / 'off' + status: str = None # 'on' / 'off' load_power: int = None # power consumption in ?unit? @command() @@ -820,12 +823,14 @@ class props: class AqaraRelayToggleValue(Enum): """Options to control the relay""" + toggle = "toggle" on = "on" off = "off" class AqaraRelayChannel(Enum): """Options to select wich relay to control""" + first = "channel_0" second = "channel_1" @@ -843,6 +848,4 @@ def update(self): ) def toggle(self, sid, channel, value): """Toggle Aqara Wireless Relay 2ch""" - return self.send_arg( - "toggle_ctrl_neutral", [channel.value, value.value] - ).pop() + return self.send_arg("toggle_ctrl_neutral", [channel.value, value.value]).pop() From 91fb301d8be3ca41918d9a3dcd5545c272ae9a60 Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Tue, 26 May 2020 23:12:45 +0200 Subject: [PATCH 27/35] add missing docstrings --- miio/gateway.py | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/miio/gateway.py b/miio/gateway.py index 6f8cbdb97..5f9e346f0 100644 --- a/miio/gateway.py +++ b/miio/gateway.py @@ -1,3 +1,5 @@ +"""Xiaomi Aqara Gateway implementation using Miio protecol.""" + import logging from dataclasses import dataclass, asdict as dataclasses_asdict from datetime import datetime @@ -32,6 +34,8 @@ class GatewayException(DeviceException): class DeviceType(IntEnum): + """DeviceType matching using the values provided by Xiaomi.""" + Unknown = -1 Gateway = 0 Switch = 1 @@ -57,6 +61,8 @@ class DeviceType(IntEnum): @dataclass class SubDeviceInfo: + """SubDevice discovery info.""" + sid: str type_id: int unknown: int @@ -729,11 +735,15 @@ def get_firmware_version(self) -> Optional[int]: class AqaraHT(SubDevice): + """Subdevice AqaraHT specific properties and methods""" + accessor = "get_prop_sensor_ht" properties = ["temperature", "humidity", "pressure"] @dataclass class props: + """Device specific properties""" + temperature: int = None # in degrees celsius humidity: int = None # in % pressure: int = None # in hPa @@ -754,11 +764,15 @@ def update(self): class SensorHT(SubDevice): + """Subdevice SensorHT specific properties and methods""" + accessor = "get_prop_sensor_ht" properties = ["temperature", "humidity", "pressure"] @dataclass class props: + """Device specific properties""" + temperature: int = None # in degrees celsius humidity: int = None # in % pressure: int = None # in hPa @@ -779,10 +793,14 @@ def update(self): class AqaraMagnet(SubDevice): + """Subdevice AqaraMagnet specific properties and methods""" + properties = ["unkown"] @dataclass class props: + """Device specific properties""" + status: str = None # 'open' or 'closed' @command() @@ -793,11 +811,15 @@ def update(self): class AqaraPlug(SubDevice): + """Subdevice AqaraPlug specific properties and methods""" + accessor = "get_prop_plug" properties = ["power", "neutral_0"] @dataclass class props: + """Device specific properties""" + status: str = None # 'on' / 'off' load_power: int = None # power consumption in ?unit? @@ -810,6 +832,8 @@ def update(self): class AqaraRelayTwoChannels(SubDevice): + """Subdevice AqaraRelayTwoChannels specific properties and methods""" + properties = ["load_power", "channel_0", "channel_1"] _status_ch0 = None _status_ch1 = None @@ -817,6 +841,8 @@ class AqaraRelayTwoChannels(SubDevice): @dataclass class props: + """Device specific properties""" + status_ch0: str = None # 'on' / 'off' status_ch1: str = None # 'on' / 'off' load_power: int = None # power consumption in ?unit? @@ -846,6 +872,6 @@ def update(self): click.argument("channel", type=EnumType(AqaraRelayChannel)), click.argument("value", type=EnumType(AqaraRelayToggleValue)), ) - def toggle(self, sid, channel, value): + def toggle(self, channel, value): """Toggle Aqara Wireless Relay 2ch""" return self.send_arg("toggle_ctrl_neutral", [channel.value, value.value]).pop() From 630ade92c7fa519d6b511ae04573fd428538963a Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Tue, 26 May 2020 23:22:28 +0200 Subject: [PATCH 28/35] fix except --- miio/gateway.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/miio/gateway.py b/miio/gateway.py index 5f9e346f0..0b905bda3 100644 --- a/miio/gateway.py +++ b/miio/gateway.py @@ -727,10 +727,10 @@ def get_firmware_version(self) -> Optional[int]: """Returns firmware version""" try: self._fw_ver = self.get_property("fw_ver").pop() - except: + except Exception as ex: _LOGGER.info( "get_firmware_version failed, returning firmware version from discovery info" - ) + ) from ex return self._fw_ver From f25a726cd92261c70109c2570336124ce041f886 Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Tue, 26 May 2020 23:25:54 +0200 Subject: [PATCH 29/35] fix black --- miio/gateway.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/miio/gateway.py b/miio/gateway.py index 0b905bda3..f0b41a7d7 100644 --- a/miio/gateway.py +++ b/miio/gateway.py @@ -1,7 +1,8 @@ """Xiaomi Aqara Gateway implementation using Miio protecol.""" import logging -from dataclasses import dataclass, asdict as dataclasses_asdict +from dataclasses import asdict as dataclasses_asdict +from dataclasses import dataclass from datetime import datetime from enum import Enum, IntEnum from typing import Optional @@ -729,8 +730,9 @@ def get_firmware_version(self) -> Optional[int]: self._fw_ver = self.get_property("fw_ver").pop() except Exception as ex: _LOGGER.info( - "get_firmware_version failed, returning firmware version from discovery info" - ) from ex + "get_firmware_version failed, returning firmware version from discovery info: %s", + ex, + ) return self._fw_ver From b6104d10ff9012e2e7f3c6e79ddd5694f1798674 Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Tue, 26 May 2020 23:51:10 +0200 Subject: [PATCH 30/35] Update pyproject.toml --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 5b60eb1f7..de2ce5a8f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,6 +33,7 @@ tqdm = "^4.45.0" netifaces = "^0.10.9" android_backup = { version = "^0.2", optional = true } importlib_metadata = "^1.6.0" +dataclasses = "^0.7" [tool.poetry.dev-dependencies] pytest = "^5.4.1" From a1143861e27cde5f27df0cf1d338ffba33adda5f Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Tue, 26 May 2020 23:54:06 +0200 Subject: [PATCH 31/35] Update pyproject.toml --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index de2ce5a8f..6adfd5e63 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,7 +33,7 @@ tqdm = "^4.45.0" netifaces = "^0.10.9" android_backup = { version = "^0.2", optional = true } importlib_metadata = "^1.6.0" -dataclasses = "^0.7" +dataclasses = "^0.6" [tool.poetry.dev-dependencies] pytest = "^5.4.1" From e99accbf1e79283d8daafe7be11f1406ca60edbf Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Wed, 27 May 2020 00:23:23 +0200 Subject: [PATCH 32/35] fix dataclasses --- poetry.lock | 118 +++++++++++++++++++++++++++---------------------- pyproject.toml | 4 +- 2 files changed, 67 insertions(+), 55 deletions(-) diff --git a/poetry.lock b/poetry.lock index e31598025..88318ae69 100644 --- a/poetry.lock +++ b/poetry.lock @@ -20,7 +20,7 @@ description = "A small Python module for determining appropriate platform-specif name = "appdirs" optional = false python-versions = "*" -version = "1.4.3" +version = "1.4.4" [[package]] category = "dev" @@ -149,6 +149,15 @@ idna = ["idna (>=2.1)"] pep8test = ["flake8", "flake8-import-order", "pep8-naming"] test = ["pytest (>=3.6.0,<3.9.0 || >3.9.0,<3.9.1 || >3.9.1,<3.9.2 || >3.9.2)", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,<3.79.2 || >3.79.2)"] +[[package]] +category = "main" +description = "A backport of the dataclasses module for Python 3.6" +marker = "python_version >= \"3.6\" and python_version < \"3.7\"" +name = "dataclasses" +optional = false +python-versions = ">=3.6, <3.7" +version = "0.7" + [[package]] category = "dev" description = "Distribution utilities" @@ -194,7 +203,7 @@ description = "File identification library for Python" name = "identify" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" -version = "1.4.15" +version = "1.4.17" [package.extras] license = ["editdistance"] @@ -301,7 +310,7 @@ description = "More routines for operating on iterables, beyond itertools" name = "more-itertools" optional = false python-versions = ">=3.5" -version = "8.2.0" +version = "8.3.0" [[package]] category = "main" @@ -325,7 +334,7 @@ description = "Core utilities for Python packages" name = "packaging" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "20.3" +version = "20.4" [package.dependencies] pyparsing = ">=2.0.2" @@ -361,7 +370,7 @@ description = "A framework for managing and maintaining multi-language pre-commi name = "pre-commit" optional = false python-versions = ">=3.6.1" -version = "2.3.0" +version = "2.4.0" [package.dependencies] cfgv = ">=2.0.0" @@ -369,7 +378,7 @@ identify = ">=1.0.0" nodeenv = ">=0.11.1" pyyaml = ">=5.1" toml = "*" -virtualenv = ">=15.2" +virtualenv = ">=20.0.8" [package.dependencies.importlib-metadata] python = "<3.8" @@ -417,7 +426,7 @@ description = "pytest: simple powerful testing with Python" name = "pytest" optional = false python-versions = ">=3.5" -version = "5.4.1" +version = "5.4.2" [package.dependencies] atomicwrites = ">=1.0" @@ -442,15 +451,15 @@ category = "dev" description = "Pytest plugin for measuring coverage." name = "pytest-cov" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "2.8.1" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +version = "2.9.0" [package.dependencies] coverage = ">=4.4" pytest = ">=3.6" [package.extras] -testing = ["fields", "hunter", "process-tests (2.0.2)", "six", "virtualenv"] +testing = ["fields", "hunter", "process-tests (2.0.2)", "six", "pytest-xdist", "virtualenv"] [[package]] category = "dev" @@ -517,7 +526,7 @@ description = "Python 2 and 3 compatibility utilities" name = "six" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" -version = "1.14.0" +version = "1.15.0" [[package]] category = "dev" @@ -533,7 +542,7 @@ description = "Python documentation generator" name = "sphinx" optional = false python-versions = ">=3.5" -version = "3.0.3" +version = "3.0.4" [package.dependencies] Jinja2 = ">=2.3" @@ -660,7 +669,7 @@ description = "Python Library for Tom's Obvious, Minimal Language" name = "toml" optional = false python-versions = "*" -version = "0.10.0" +version = "0.10.1" [[package]] category = "dev" @@ -668,15 +677,15 @@ description = "tox is a generic virtualenv management and test command line tool name = "tox" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" -version = "3.14.6" +version = "3.15.1" [package.dependencies] colorama = ">=0.4.1" -filelock = ">=3.0.0,<4" +filelock = ">=3.0.0" packaging = ">=14" -pluggy = ">=0.12.0,<1" -py = ">=1.4.17,<2" -six = ">=1.14.0,<2" +pluggy = ">=0.12.0" +py = ">=1.4.17" +six = ">=1.14.0" toml = ">=0.9.4" virtualenv = ">=16.0.0,<20.0.0 || >20.0.0,<20.0.1 || >20.0.1,<20.0.2 || >20.0.2,<20.0.3 || >20.0.3,<20.0.4 || >20.0.4,<20.0.5 || >20.0.5,<20.0.6 || >20.0.6,<20.0.7 || >20.0.7" @@ -685,8 +694,8 @@ python = "<3.8" version = ">=0.12,<2" [package.extras] -docs = ["sphinx (>=2.0.0,<3)", "towncrier (>=18.5.0)", "pygments-github-lexers (>=0.0.5)", "sphinxcontrib-autoprogram (>=0.1.5)"] -testing = ["freezegun (>=0.3.11,<1)", "pathlib2 (>=2.3.3,<3)", "pytest (>=4.0.0,<6)", "pytest-cov (>=2.5.1,<3)", "pytest-mock (>=1.10.0,<2)", "pytest-xdist (>=1.22.2,<2)", "pytest-randomly (>=1.0.0,<4)", "flaky (>=3.4.0,<4)", "psutil (>=5.6.1,<6)"] +docs = ["sphinx (>=2.0.0)", "towncrier (>=18.5.0)", "pygments-github-lexers (>=0.0.5)", "sphinxcontrib-autoprogram (>=0.1.5)"] +testing = ["freezegun (>=0.3.11)", "pathlib2 (>=2.3.3)", "pytest (>=4.0.0)", "pytest-cov (>=2.5.1)", "pytest-mock (>=1.10.0)", "pytest-xdist (>=1.22.2)", "pytest-randomly (>=1.0.0)", "flaky (>=3.4.0)", "psutil (>=5.6.1)"] [[package]] category = "main" @@ -694,7 +703,7 @@ description = "Fast, Extensible Progress Meter" name = "tqdm" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*" -version = "4.45.0" +version = "4.46.0" [package.extras] dev = ["py-make (>=0.1.0)", "twine", "argopt", "pydoc-markdown"] @@ -718,7 +727,7 @@ description = "Virtual Python Environment builder" name = "virtualenv" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" -version = "20.0.18" +version = "20.0.21" [package.dependencies] appdirs = ">=1.4.3,<2" @@ -735,8 +744,8 @@ python = "<3.7" version = ">=1.0,<2" [package.extras] -docs = ["sphinx (>=2.0.0,<3)", "sphinx-argparse (>=0.2.5,<1)", "sphinx-rtd-theme (>=0.4.3,<1)", "towncrier (>=19.9.0rc1)", "proselint (>=0.10.2,<1)"] -testing = ["pytest (>=4.0.0,<6)", "coverage (>=4.5.1,<6)", "pytest-mock (>=2.0.0,<3)", "pytest-env (>=0.6.2,<1)", "pytest-timeout (>=1.3.4,<2)", "packaging (>=20.0)", "xonsh (>=0.9.16,<1)"] +docs = ["sphinx (>=3)", "sphinx-argparse (>=0.2.5)", "sphinx-rtd-theme (>=0.4.3)", "towncrier (>=19.9.0rc1)", "proselint (>=0.10.2)"] +testing = ["pytest (>=4)", "coverage (>=5)", "coverage-enable-subprocess (>=1)", "pytest-xdist (>=1.31.0)", "pytest-mock (>=2)", "pytest-env (>=0.6.2)", "pytest-randomly (>=1)", "pytest-timeout", "packaging (>=20.0)", "xonsh (>=0.9.16)"] [[package]] category = "dev" @@ -778,8 +787,8 @@ docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] testing = ["jaraco.itertools", "func-timeout"] [metadata] -content-hash = "b98a7e593b3056a65f54cd76077740dc99c40b58a1f6be44e3711da60de716c9" -python-versions = "^3.6.5" +content-hash = "e3af78bb76f972288cf237db99365695dc58f1907fd96a5f7e69016e91e6c09b" +python-versions = ">=3.6.5,<4.0.0" [metadata.files] alabaster = [ @@ -790,8 +799,8 @@ android-backup = [ {file = "android_backup-0.2.0.tar.gz", hash = "sha256:864b6a9f8e2dda7a3af3726df7439052d35781c5f7d50dd771d709293d158b97"}, ] appdirs = [ - {file = "appdirs-1.4.3-py2.py3-none-any.whl", hash = "sha256:d8b24664561d0d34ddfaec54636d502d7cea6e29c3eaf68f3df6180863e2166e"}, - {file = "appdirs-1.4.3.tar.gz", hash = "sha256:9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92"}, + {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"}, + {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, ] atomicwrites = [ {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, @@ -912,6 +921,10 @@ cryptography = [ {file = "cryptography-2.9.2-cp38-cp38-win_amd64.whl", hash = "sha256:1dfa985f62b137909496e7fc182dac687206d8d089dd03eaeb28ae16eec8e7d5"}, {file = "cryptography-2.9.2.tar.gz", hash = "sha256:a0c30272fb4ddda5f5ffc1089d7405b7a71b0b0f51993cb4e5dbb4590b2fc229"}, ] +dataclasses = [ + {file = "dataclasses-0.7-py3-none-any.whl", hash = "sha256:3459118f7ede7c8bea0fe795bff7c6c2ce287d01dd226202f7c9ebc0610a7836"}, + {file = "dataclasses-0.7.tar.gz", hash = "sha256:494a6dcae3b8bcf80848eea2ef64c0cc5cd307ffc263e17cdf42f3e5420808e6"}, +] distlib = [ {file = "distlib-0.3.0.zip", hash = "sha256:2e166e231a26b36d6dfe35a48c4464346620f8645ed0ace01ee31822b288de21"}, ] @@ -928,8 +941,8 @@ filelock = [ {file = "filelock-3.0.12.tar.gz", hash = "sha256:18d82244ee114f543149c66a6e0c14e9c4f8a1044b5cdaadd0f82159d6a6ff59"}, ] identify = [ - {file = "identify-1.4.15-py2.py3-none-any.whl", hash = "sha256:88ed90632023e52a6495749c6732e61e08ec9f4f04e95484a5c37b9caf40283c"}, - {file = "identify-1.4.15.tar.gz", hash = "sha256:23c18d97bb50e05be1a54917ee45cc61d57cb96aedc06aabb2b02331edf0dbf0"}, + {file = "identify-1.4.17-py2.py3-none-any.whl", hash = "sha256:ef6fa3d125c27516f8d1aaa2038c3263d741e8723825eb38350cdc0288ab35eb"}, + {file = "identify-1.4.17.tar.gz", hash = "sha256:be66b9673d59336acd18a3a0e0c10d35b8a780309561edf16c46b6b74b83f6af"}, ] idna = [ {file = "idna-2.9-py2.py3-none-any.whl", hash = "sha256:a068a21ceac8a4d63dbfd964670474107f541babbd2250d61922f029858365fa"}, @@ -994,8 +1007,8 @@ markupsafe = [ {file = "MarkupSafe-1.1.1.tar.gz", hash = "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b"}, ] more-itertools = [ - {file = "more-itertools-8.2.0.tar.gz", hash = "sha256:b1ddb932186d8a6ac451e1d95844b382f55e12686d51ca0c68b6f61f2ab7a507"}, - {file = "more_itertools-8.2.0-py3-none-any.whl", hash = "sha256:5dd8bcf33e5f9513ffa06d5ad33d78f31e1931ac9a18f33d37e77a180d393a7c"}, + {file = "more-itertools-8.3.0.tar.gz", hash = "sha256:558bb897a2232f5e4f8e2399089e35aecb746e1f9191b6584a151647e89267be"}, + {file = "more_itertools-8.3.0-py3-none-any.whl", hash = "sha256:7818f596b1e87be009031c7653d01acc46ed422e6656b394b0f765ce66ed4982"}, ] netifaces = [ {file = "netifaces-0.10.9-cp27-cp27m-macosx_10_13_x86_64.whl", hash = "sha256:b2ff3a0a4f991d2da5376efd3365064a43909877e9fabfa801df970771161d29"}, @@ -1025,8 +1038,8 @@ nodeenv = [ {file = "nodeenv-1.3.5-py2.py3-none-any.whl", hash = "sha256:5b2438f2e42af54ca968dd1b374d14a1194848955187b0e5e4be1f73813a5212"}, ] packaging = [ - {file = "packaging-20.3-py2.py3-none-any.whl", hash = "sha256:82f77b9bee21c1bafbf35a84905d604d5d1223801d639cf3ed140bd651c08752"}, - {file = "packaging-20.3.tar.gz", hash = "sha256:3c292b474fda1671ec57d46d739d072bfd495a4f51ad01a055121d81e952b7a3"}, + {file = "packaging-20.4-py2.py3-none-any.whl", hash = "sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181"}, + {file = "packaging-20.4.tar.gz", hash = "sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8"}, ] pbr = [ {file = "pbr-5.4.5-py2.py3-none-any.whl", hash = "sha256:579170e23f8e0c2f24b0de612f71f648eccb79fb1322c814ae6b3c07b5ba23e8"}, @@ -1037,8 +1050,8 @@ pluggy = [ {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, ] pre-commit = [ - {file = "pre_commit-2.3.0-py2.py3-none-any.whl", hash = "sha256:979b53dab1af35063a483bfe13b0fcbbf1a2cf8c46b60e0a9a8d08e8269647a1"}, - {file = "pre_commit-2.3.0.tar.gz", hash = "sha256:f3e85e68c6d1cbe7828d3471896f1b192cfcf1c4d83bf26e26beeb5941855257"}, + {file = "pre_commit-2.4.0-py2.py3-none-any.whl", hash = "sha256:5559e09afcac7808933951ffaf4ff9aac524f31efbc3f24d021540b6c579813c"}, + {file = "pre_commit-2.4.0.tar.gz", hash = "sha256:703e2e34cbe0eedb0d319eff9f7b83e2022bb5a3ab5289a6a8841441076514d0"}, ] py = [ {file = "py-1.8.1-py2.py3-none-any.whl", hash = "sha256:c20fdd83a5dbc0af9efd622bee9a5564e278f6380fffcacc43ba6f43db2813b0"}, @@ -1057,12 +1070,12 @@ pyparsing = [ {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, ] pytest = [ - {file = "pytest-5.4.1-py3-none-any.whl", hash = "sha256:0e5b30f5cb04e887b91b1ee519fa3d89049595f428c1db76e73bd7f17b09b172"}, - {file = "pytest-5.4.1.tar.gz", hash = "sha256:84dde37075b8805f3d1f392cc47e38a0e59518fb46a431cfdaf7cf1ce805f970"}, + {file = "pytest-5.4.2-py3-none-any.whl", hash = "sha256:95c710d0a72d91c13fae35dce195633c929c3792f54125919847fdcdf7caa0d3"}, + {file = "pytest-5.4.2.tar.gz", hash = "sha256:eb2b5e935f6a019317e455b6da83dd8650ac9ffd2ee73a7b657a30873d67a698"}, ] pytest-cov = [ - {file = "pytest-cov-2.8.1.tar.gz", hash = "sha256:cc6742d8bac45070217169f5f72ceee1e0e55b0221f54bcf24845972d3a47f2b"}, - {file = "pytest_cov-2.8.1-py2.py3-none-any.whl", hash = "sha256:cdbdef4f870408ebdbfeb44e63e07eb18bb4619fae852f6e760645fa36172626"}, + {file = "pytest-cov-2.9.0.tar.gz", hash = "sha256:b6a814b8ed6247bd81ff47f038511b57fe1ce7f4cc25b9106f1a4b106f1d9322"}, + {file = "pytest_cov-2.9.0-py2.py3-none-any.whl", hash = "sha256:c87dfd8465d865655a8213859f1b4749b43448b5fae465cb981e16d52a811424"}, ] pytest-mock = [ {file = "pytest-mock-3.1.0.tar.gz", hash = "sha256:ce610831cedeff5331f4e2fc453a5dd65384303f680ab34bee2c6533855b431c"}, @@ -1093,16 +1106,16 @@ restructuredtext-lint = [ {file = "restructuredtext_lint-1.3.0.tar.gz", hash = "sha256:97b3da356d5b3a8514d8f1f9098febd8b41463bed6a1d9f126cf0a048b6fd908"}, ] six = [ - {file = "six-1.14.0-py2.py3-none-any.whl", hash = "sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c"}, - {file = "six-1.14.0.tar.gz", hash = "sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a"}, + {file = "six-1.15.0-py2.py3-none-any.whl", hash = "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"}, + {file = "six-1.15.0.tar.gz", hash = "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259"}, ] snowballstemmer = [ {file = "snowballstemmer-2.0.0-py2.py3-none-any.whl", hash = "sha256:209f257d7533fdb3cb73bdbd24f436239ca3b2fa67d56f6ff88e86be08cc5ef0"}, {file = "snowballstemmer-2.0.0.tar.gz", hash = "sha256:df3bac3df4c2c01363f3dd2cfa78cce2840a79b9f1c2d2de9ce8d31683992f52"}, ] sphinx = [ - {file = "Sphinx-3.0.3-py3-none-any.whl", hash = "sha256:f5505d74cf9592f3b997380f9bdb2d2d0320ed74dd69691e3ee0644b956b8d83"}, - {file = "Sphinx-3.0.3.tar.gz", hash = "sha256:62edfd92d955b868d6c124c0942eba966d54b5f3dcb4ded39e65f74abac3f572"}, + {file = "Sphinx-3.0.4-py3-none-any.whl", hash = "sha256:779a519adbd3a70fc7c468af08c5e74829868b0a5b34587b33340e010291856c"}, + {file = "Sphinx-3.0.4.tar.gz", hash = "sha256:ea64df287958ee5aac46be7ac2b7277305b0381d213728c3a49d8bb9b8415807"}, ] sphinx-click = [ {file = "sphinx-click-2.3.2.tar.gz", hash = "sha256:1b649ebe9f7a85b78ef6545d1dc258da5abca850ac6375be104d484a6334a728"}, @@ -1137,25 +1150,24 @@ stevedore = [ {file = "stevedore-1.32.0.tar.gz", hash = "sha256:18afaf1d623af5950cc0f7e75e70f917784c73b652a34a12d90b309451b5500b"}, ] toml = [ - {file = "toml-0.10.0-py2.7.egg", hash = "sha256:f1db651f9657708513243e61e6cc67d101a39bad662eaa9b5546f789338e07a3"}, - {file = "toml-0.10.0-py2.py3-none-any.whl", hash = "sha256:235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e"}, - {file = "toml-0.10.0.tar.gz", hash = "sha256:229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c"}, + {file = "toml-0.10.1-py2.py3-none-any.whl", hash = "sha256:bda89d5935c2eac546d648028b9901107a595863cb36bae0c73ac804a9b4ce88"}, + {file = "toml-0.10.1.tar.gz", hash = "sha256:926b612be1e5ce0634a2ca03470f95169cf16f939018233a670519cb4ac58b0f"}, ] tox = [ - {file = "tox-3.14.6-py2.py3-none-any.whl", hash = "sha256:b2c4b91c975ea5c11463d9ca00bebf82654439c5df0f614807b9bdec62cc9471"}, - {file = "tox-3.14.6.tar.gz", hash = "sha256:a4a6689045d93c208d77230853b28058b7513f5123647b67bf012f82fa168303"}, + {file = "tox-3.15.1-py2.py3-none-any.whl", hash = "sha256:322dfdf007d7d53323f767badcb068a5cfa7c44d8aabb698d131b28cf44e62c4"}, + {file = "tox-3.15.1.tar.gz", hash = "sha256:8c9ad9b48659d291c5bc78bcabaa4d680d627687154b812fa52baedaa94f9f83"}, ] tqdm = [ - {file = "tqdm-4.45.0-py2.py3-none-any.whl", hash = "sha256:ea9e3fd6bd9a37e8783d75bfc4c1faf3c6813da6bd1c3e776488b41ec683af94"}, - {file = "tqdm-4.45.0.tar.gz", hash = "sha256:00339634a22c10a7a22476ee946bbde2dbe48d042ded784e4d88e0236eca5d81"}, + {file = "tqdm-4.46.0-py2.py3-none-any.whl", hash = "sha256:acdafb20f51637ca3954150d0405ff1a7edde0ff19e38fb99a80a66210d2a28f"}, + {file = "tqdm-4.46.0.tar.gz", hash = "sha256:4733c4a10d0f2a4d098d801464bdaf5240c7dadd2a7fde4ee93b0a0efd9fb25e"}, ] urllib3 = [ {file = "urllib3-1.25.9-py2.py3-none-any.whl", hash = "sha256:88206b0eb87e6d677d424843ac5209e3fb9d0190d0ee169599165ec25e9d9115"}, {file = "urllib3-1.25.9.tar.gz", hash = "sha256:3018294ebefce6572a474f0604c2021e33b3fd8006ecd11d62107a5d2a963527"}, ] virtualenv = [ - {file = "virtualenv-20.0.18-py2.py3-none-any.whl", hash = "sha256:5021396e8f03d0d002a770da90e31e61159684db2859d0ba4850fbea752aa675"}, - {file = "virtualenv-20.0.18.tar.gz", hash = "sha256:ac53ade75ca189bc97b6c1d9ec0f1a50efe33cbf178ae09452dcd9fd309013c1"}, + {file = "virtualenv-20.0.21-py2.py3-none-any.whl", hash = "sha256:a730548b27366c5e6cbdf6f97406d861cccece2e22275e8e1a757aeff5e00c70"}, + {file = "virtualenv-20.0.21.tar.gz", hash = "sha256:a116629d4e7f4d03433b8afa27f43deba09d48bc48f5ecefa4f015a178efb6cf"}, ] voluptuous = [ {file = "voluptuous-0.11.7.tar.gz", hash = "sha256:2abc341dbc740c5e2302c7f9b8e2e243194fb4772585b991931cb5b22e9bf456"}, diff --git a/pyproject.toml b/pyproject.toml index 6adfd5e63..4645412dc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,7 +21,7 @@ miio-extract-tokens = "miio.extract_tokens:main" miiocli = "miio.cli:create_cli" [tool.poetry.dependencies] -python = "^3.6.5" +python = ">=3.6.5,<4.0.0" click = "^7.1.1" cryptography = "^2.9" construct = "^2.10.56" @@ -33,7 +33,7 @@ tqdm = "^4.45.0" netifaces = "^0.10.9" android_backup = { version = "^0.2", optional = true } importlib_metadata = "^1.6.0" -dataclasses = "^0.6" +dataclasses = { version = "^0.7", python = ">=3.6, <3.7" } [tool.poetry.dev-dependencies] pytest = "^5.4.1" From eaba3581176ceb0268f36b5de7f48f42bc260b2d Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Wed, 27 May 2020 08:55:16 +0200 Subject: [PATCH 33/35] replace dataclasses by attr --- miio/gateway.py | 19 ++++---- poetry.lock | 118 ++++++++++++++++++++++-------------------------- pyproject.toml | 3 +- 3 files changed, 63 insertions(+), 77 deletions(-) diff --git a/miio/gateway.py b/miio/gateway.py index f0b41a7d7..f908bbce5 100644 --- a/miio/gateway.py +++ b/miio/gateway.py @@ -1,12 +1,11 @@ """Xiaomi Aqara Gateway implementation using Miio protecol.""" import logging -from dataclasses import asdict as dataclasses_asdict -from dataclasses import dataclass from datetime import datetime from enum import Enum, IntEnum from typing import Optional +import attr import click from .click_common import EnumType, command, format_output @@ -60,7 +59,7 @@ class DeviceType(IntEnum): RemoteSwitchDouble = 135 -@dataclass +@attr.s(auto_attribs=True) class SubDeviceInfo: """SubDevice discovery info.""" @@ -596,7 +595,7 @@ class SubDevice: these devices are connected through zigbee. """ - @dataclass + @attr.s(auto_attribs=True) class props: """Defines properties of the specific device""" @@ -623,7 +622,7 @@ def __repr__(self): @property def status(self): """Return sub-device status as a dict containing all properties.""" - return dataclasses_asdict(self._props) + return attr.asdict(self._props) @property def device_type(self): @@ -742,7 +741,7 @@ class AqaraHT(SubDevice): accessor = "get_prop_sensor_ht" properties = ["temperature", "humidity", "pressure"] - @dataclass + @attr.s(auto_attribs=True) class props: """Device specific properties""" @@ -771,7 +770,7 @@ class SensorHT(SubDevice): accessor = "get_prop_sensor_ht" properties = ["temperature", "humidity", "pressure"] - @dataclass + @attr.s(auto_attribs=True) class props: """Device specific properties""" @@ -799,7 +798,7 @@ class AqaraMagnet(SubDevice): properties = ["unkown"] - @dataclass + @attr.s(auto_attribs=True) class props: """Device specific properties""" @@ -818,7 +817,7 @@ class AqaraPlug(SubDevice): accessor = "get_prop_plug" properties = ["power", "neutral_0"] - @dataclass + @attr.s(auto_attribs=True) class props: """Device specific properties""" @@ -841,7 +840,7 @@ class AqaraRelayTwoChannels(SubDevice): _status_ch1 = None _load_power = None - @dataclass + @attr.s(auto_attribs=True) class props: """Device specific properties""" diff --git a/poetry.lock b/poetry.lock index 88318ae69..e31598025 100644 --- a/poetry.lock +++ b/poetry.lock @@ -20,7 +20,7 @@ description = "A small Python module for determining appropriate platform-specif name = "appdirs" optional = false python-versions = "*" -version = "1.4.4" +version = "1.4.3" [[package]] category = "dev" @@ -149,15 +149,6 @@ idna = ["idna (>=2.1)"] pep8test = ["flake8", "flake8-import-order", "pep8-naming"] test = ["pytest (>=3.6.0,<3.9.0 || >3.9.0,<3.9.1 || >3.9.1,<3.9.2 || >3.9.2)", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,<3.79.2 || >3.79.2)"] -[[package]] -category = "main" -description = "A backport of the dataclasses module for Python 3.6" -marker = "python_version >= \"3.6\" and python_version < \"3.7\"" -name = "dataclasses" -optional = false -python-versions = ">=3.6, <3.7" -version = "0.7" - [[package]] category = "dev" description = "Distribution utilities" @@ -203,7 +194,7 @@ description = "File identification library for Python" name = "identify" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" -version = "1.4.17" +version = "1.4.15" [package.extras] license = ["editdistance"] @@ -310,7 +301,7 @@ description = "More routines for operating on iterables, beyond itertools" name = "more-itertools" optional = false python-versions = ">=3.5" -version = "8.3.0" +version = "8.2.0" [[package]] category = "main" @@ -334,7 +325,7 @@ description = "Core utilities for Python packages" name = "packaging" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "20.4" +version = "20.3" [package.dependencies] pyparsing = ">=2.0.2" @@ -370,7 +361,7 @@ description = "A framework for managing and maintaining multi-language pre-commi name = "pre-commit" optional = false python-versions = ">=3.6.1" -version = "2.4.0" +version = "2.3.0" [package.dependencies] cfgv = ">=2.0.0" @@ -378,7 +369,7 @@ identify = ">=1.0.0" nodeenv = ">=0.11.1" pyyaml = ">=5.1" toml = "*" -virtualenv = ">=20.0.8" +virtualenv = ">=15.2" [package.dependencies.importlib-metadata] python = "<3.8" @@ -426,7 +417,7 @@ description = "pytest: simple powerful testing with Python" name = "pytest" optional = false python-versions = ">=3.5" -version = "5.4.2" +version = "5.4.1" [package.dependencies] atomicwrites = ">=1.0" @@ -451,15 +442,15 @@ category = "dev" description = "Pytest plugin for measuring coverage." name = "pytest-cov" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "2.9.0" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "2.8.1" [package.dependencies] coverage = ">=4.4" pytest = ">=3.6" [package.extras] -testing = ["fields", "hunter", "process-tests (2.0.2)", "six", "pytest-xdist", "virtualenv"] +testing = ["fields", "hunter", "process-tests (2.0.2)", "six", "virtualenv"] [[package]] category = "dev" @@ -526,7 +517,7 @@ description = "Python 2 and 3 compatibility utilities" name = "six" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" -version = "1.15.0" +version = "1.14.0" [[package]] category = "dev" @@ -542,7 +533,7 @@ description = "Python documentation generator" name = "sphinx" optional = false python-versions = ">=3.5" -version = "3.0.4" +version = "3.0.3" [package.dependencies] Jinja2 = ">=2.3" @@ -669,7 +660,7 @@ description = "Python Library for Tom's Obvious, Minimal Language" name = "toml" optional = false python-versions = "*" -version = "0.10.1" +version = "0.10.0" [[package]] category = "dev" @@ -677,15 +668,15 @@ description = "tox is a generic virtualenv management and test command line tool name = "tox" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" -version = "3.15.1" +version = "3.14.6" [package.dependencies] colorama = ">=0.4.1" -filelock = ">=3.0.0" +filelock = ">=3.0.0,<4" packaging = ">=14" -pluggy = ">=0.12.0" -py = ">=1.4.17" -six = ">=1.14.0" +pluggy = ">=0.12.0,<1" +py = ">=1.4.17,<2" +six = ">=1.14.0,<2" toml = ">=0.9.4" virtualenv = ">=16.0.0,<20.0.0 || >20.0.0,<20.0.1 || >20.0.1,<20.0.2 || >20.0.2,<20.0.3 || >20.0.3,<20.0.4 || >20.0.4,<20.0.5 || >20.0.5,<20.0.6 || >20.0.6,<20.0.7 || >20.0.7" @@ -694,8 +685,8 @@ python = "<3.8" version = ">=0.12,<2" [package.extras] -docs = ["sphinx (>=2.0.0)", "towncrier (>=18.5.0)", "pygments-github-lexers (>=0.0.5)", "sphinxcontrib-autoprogram (>=0.1.5)"] -testing = ["freezegun (>=0.3.11)", "pathlib2 (>=2.3.3)", "pytest (>=4.0.0)", "pytest-cov (>=2.5.1)", "pytest-mock (>=1.10.0)", "pytest-xdist (>=1.22.2)", "pytest-randomly (>=1.0.0)", "flaky (>=3.4.0)", "psutil (>=5.6.1)"] +docs = ["sphinx (>=2.0.0,<3)", "towncrier (>=18.5.0)", "pygments-github-lexers (>=0.0.5)", "sphinxcontrib-autoprogram (>=0.1.5)"] +testing = ["freezegun (>=0.3.11,<1)", "pathlib2 (>=2.3.3,<3)", "pytest (>=4.0.0,<6)", "pytest-cov (>=2.5.1,<3)", "pytest-mock (>=1.10.0,<2)", "pytest-xdist (>=1.22.2,<2)", "pytest-randomly (>=1.0.0,<4)", "flaky (>=3.4.0,<4)", "psutil (>=5.6.1,<6)"] [[package]] category = "main" @@ -703,7 +694,7 @@ description = "Fast, Extensible Progress Meter" name = "tqdm" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*" -version = "4.46.0" +version = "4.45.0" [package.extras] dev = ["py-make (>=0.1.0)", "twine", "argopt", "pydoc-markdown"] @@ -727,7 +718,7 @@ description = "Virtual Python Environment builder" name = "virtualenv" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" -version = "20.0.21" +version = "20.0.18" [package.dependencies] appdirs = ">=1.4.3,<2" @@ -744,8 +735,8 @@ python = "<3.7" version = ">=1.0,<2" [package.extras] -docs = ["sphinx (>=3)", "sphinx-argparse (>=0.2.5)", "sphinx-rtd-theme (>=0.4.3)", "towncrier (>=19.9.0rc1)", "proselint (>=0.10.2)"] -testing = ["pytest (>=4)", "coverage (>=5)", "coverage-enable-subprocess (>=1)", "pytest-xdist (>=1.31.0)", "pytest-mock (>=2)", "pytest-env (>=0.6.2)", "pytest-randomly (>=1)", "pytest-timeout", "packaging (>=20.0)", "xonsh (>=0.9.16)"] +docs = ["sphinx (>=2.0.0,<3)", "sphinx-argparse (>=0.2.5,<1)", "sphinx-rtd-theme (>=0.4.3,<1)", "towncrier (>=19.9.0rc1)", "proselint (>=0.10.2,<1)"] +testing = ["pytest (>=4.0.0,<6)", "coverage (>=4.5.1,<6)", "pytest-mock (>=2.0.0,<3)", "pytest-env (>=0.6.2,<1)", "pytest-timeout (>=1.3.4,<2)", "packaging (>=20.0)", "xonsh (>=0.9.16,<1)"] [[package]] category = "dev" @@ -787,8 +778,8 @@ docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] testing = ["jaraco.itertools", "func-timeout"] [metadata] -content-hash = "e3af78bb76f972288cf237db99365695dc58f1907fd96a5f7e69016e91e6c09b" -python-versions = ">=3.6.5,<4.0.0" +content-hash = "b98a7e593b3056a65f54cd76077740dc99c40b58a1f6be44e3711da60de716c9" +python-versions = "^3.6.5" [metadata.files] alabaster = [ @@ -799,8 +790,8 @@ android-backup = [ {file = "android_backup-0.2.0.tar.gz", hash = "sha256:864b6a9f8e2dda7a3af3726df7439052d35781c5f7d50dd771d709293d158b97"}, ] appdirs = [ - {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"}, - {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, + {file = "appdirs-1.4.3-py2.py3-none-any.whl", hash = "sha256:d8b24664561d0d34ddfaec54636d502d7cea6e29c3eaf68f3df6180863e2166e"}, + {file = "appdirs-1.4.3.tar.gz", hash = "sha256:9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92"}, ] atomicwrites = [ {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, @@ -921,10 +912,6 @@ cryptography = [ {file = "cryptography-2.9.2-cp38-cp38-win_amd64.whl", hash = "sha256:1dfa985f62b137909496e7fc182dac687206d8d089dd03eaeb28ae16eec8e7d5"}, {file = "cryptography-2.9.2.tar.gz", hash = "sha256:a0c30272fb4ddda5f5ffc1089d7405b7a71b0b0f51993cb4e5dbb4590b2fc229"}, ] -dataclasses = [ - {file = "dataclasses-0.7-py3-none-any.whl", hash = "sha256:3459118f7ede7c8bea0fe795bff7c6c2ce287d01dd226202f7c9ebc0610a7836"}, - {file = "dataclasses-0.7.tar.gz", hash = "sha256:494a6dcae3b8bcf80848eea2ef64c0cc5cd307ffc263e17cdf42f3e5420808e6"}, -] distlib = [ {file = "distlib-0.3.0.zip", hash = "sha256:2e166e231a26b36d6dfe35a48c4464346620f8645ed0ace01ee31822b288de21"}, ] @@ -941,8 +928,8 @@ filelock = [ {file = "filelock-3.0.12.tar.gz", hash = "sha256:18d82244ee114f543149c66a6e0c14e9c4f8a1044b5cdaadd0f82159d6a6ff59"}, ] identify = [ - {file = "identify-1.4.17-py2.py3-none-any.whl", hash = "sha256:ef6fa3d125c27516f8d1aaa2038c3263d741e8723825eb38350cdc0288ab35eb"}, - {file = "identify-1.4.17.tar.gz", hash = "sha256:be66b9673d59336acd18a3a0e0c10d35b8a780309561edf16c46b6b74b83f6af"}, + {file = "identify-1.4.15-py2.py3-none-any.whl", hash = "sha256:88ed90632023e52a6495749c6732e61e08ec9f4f04e95484a5c37b9caf40283c"}, + {file = "identify-1.4.15.tar.gz", hash = "sha256:23c18d97bb50e05be1a54917ee45cc61d57cb96aedc06aabb2b02331edf0dbf0"}, ] idna = [ {file = "idna-2.9-py2.py3-none-any.whl", hash = "sha256:a068a21ceac8a4d63dbfd964670474107f541babbd2250d61922f029858365fa"}, @@ -1007,8 +994,8 @@ markupsafe = [ {file = "MarkupSafe-1.1.1.tar.gz", hash = "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b"}, ] more-itertools = [ - {file = "more-itertools-8.3.0.tar.gz", hash = "sha256:558bb897a2232f5e4f8e2399089e35aecb746e1f9191b6584a151647e89267be"}, - {file = "more_itertools-8.3.0-py3-none-any.whl", hash = "sha256:7818f596b1e87be009031c7653d01acc46ed422e6656b394b0f765ce66ed4982"}, + {file = "more-itertools-8.2.0.tar.gz", hash = "sha256:b1ddb932186d8a6ac451e1d95844b382f55e12686d51ca0c68b6f61f2ab7a507"}, + {file = "more_itertools-8.2.0-py3-none-any.whl", hash = "sha256:5dd8bcf33e5f9513ffa06d5ad33d78f31e1931ac9a18f33d37e77a180d393a7c"}, ] netifaces = [ {file = "netifaces-0.10.9-cp27-cp27m-macosx_10_13_x86_64.whl", hash = "sha256:b2ff3a0a4f991d2da5376efd3365064a43909877e9fabfa801df970771161d29"}, @@ -1038,8 +1025,8 @@ nodeenv = [ {file = "nodeenv-1.3.5-py2.py3-none-any.whl", hash = "sha256:5b2438f2e42af54ca968dd1b374d14a1194848955187b0e5e4be1f73813a5212"}, ] packaging = [ - {file = "packaging-20.4-py2.py3-none-any.whl", hash = "sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181"}, - {file = "packaging-20.4.tar.gz", hash = "sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8"}, + {file = "packaging-20.3-py2.py3-none-any.whl", hash = "sha256:82f77b9bee21c1bafbf35a84905d604d5d1223801d639cf3ed140bd651c08752"}, + {file = "packaging-20.3.tar.gz", hash = "sha256:3c292b474fda1671ec57d46d739d072bfd495a4f51ad01a055121d81e952b7a3"}, ] pbr = [ {file = "pbr-5.4.5-py2.py3-none-any.whl", hash = "sha256:579170e23f8e0c2f24b0de612f71f648eccb79fb1322c814ae6b3c07b5ba23e8"}, @@ -1050,8 +1037,8 @@ pluggy = [ {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, ] pre-commit = [ - {file = "pre_commit-2.4.0-py2.py3-none-any.whl", hash = "sha256:5559e09afcac7808933951ffaf4ff9aac524f31efbc3f24d021540b6c579813c"}, - {file = "pre_commit-2.4.0.tar.gz", hash = "sha256:703e2e34cbe0eedb0d319eff9f7b83e2022bb5a3ab5289a6a8841441076514d0"}, + {file = "pre_commit-2.3.0-py2.py3-none-any.whl", hash = "sha256:979b53dab1af35063a483bfe13b0fcbbf1a2cf8c46b60e0a9a8d08e8269647a1"}, + {file = "pre_commit-2.3.0.tar.gz", hash = "sha256:f3e85e68c6d1cbe7828d3471896f1b192cfcf1c4d83bf26e26beeb5941855257"}, ] py = [ {file = "py-1.8.1-py2.py3-none-any.whl", hash = "sha256:c20fdd83a5dbc0af9efd622bee9a5564e278f6380fffcacc43ba6f43db2813b0"}, @@ -1070,12 +1057,12 @@ pyparsing = [ {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, ] pytest = [ - {file = "pytest-5.4.2-py3-none-any.whl", hash = "sha256:95c710d0a72d91c13fae35dce195633c929c3792f54125919847fdcdf7caa0d3"}, - {file = "pytest-5.4.2.tar.gz", hash = "sha256:eb2b5e935f6a019317e455b6da83dd8650ac9ffd2ee73a7b657a30873d67a698"}, + {file = "pytest-5.4.1-py3-none-any.whl", hash = "sha256:0e5b30f5cb04e887b91b1ee519fa3d89049595f428c1db76e73bd7f17b09b172"}, + {file = "pytest-5.4.1.tar.gz", hash = "sha256:84dde37075b8805f3d1f392cc47e38a0e59518fb46a431cfdaf7cf1ce805f970"}, ] pytest-cov = [ - {file = "pytest-cov-2.9.0.tar.gz", hash = "sha256:b6a814b8ed6247bd81ff47f038511b57fe1ce7f4cc25b9106f1a4b106f1d9322"}, - {file = "pytest_cov-2.9.0-py2.py3-none-any.whl", hash = "sha256:c87dfd8465d865655a8213859f1b4749b43448b5fae465cb981e16d52a811424"}, + {file = "pytest-cov-2.8.1.tar.gz", hash = "sha256:cc6742d8bac45070217169f5f72ceee1e0e55b0221f54bcf24845972d3a47f2b"}, + {file = "pytest_cov-2.8.1-py2.py3-none-any.whl", hash = "sha256:cdbdef4f870408ebdbfeb44e63e07eb18bb4619fae852f6e760645fa36172626"}, ] pytest-mock = [ {file = "pytest-mock-3.1.0.tar.gz", hash = "sha256:ce610831cedeff5331f4e2fc453a5dd65384303f680ab34bee2c6533855b431c"}, @@ -1106,16 +1093,16 @@ restructuredtext-lint = [ {file = "restructuredtext_lint-1.3.0.tar.gz", hash = "sha256:97b3da356d5b3a8514d8f1f9098febd8b41463bed6a1d9f126cf0a048b6fd908"}, ] six = [ - {file = "six-1.15.0-py2.py3-none-any.whl", hash = "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"}, - {file = "six-1.15.0.tar.gz", hash = "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259"}, + {file = "six-1.14.0-py2.py3-none-any.whl", hash = "sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c"}, + {file = "six-1.14.0.tar.gz", hash = "sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a"}, ] snowballstemmer = [ {file = "snowballstemmer-2.0.0-py2.py3-none-any.whl", hash = "sha256:209f257d7533fdb3cb73bdbd24f436239ca3b2fa67d56f6ff88e86be08cc5ef0"}, {file = "snowballstemmer-2.0.0.tar.gz", hash = "sha256:df3bac3df4c2c01363f3dd2cfa78cce2840a79b9f1c2d2de9ce8d31683992f52"}, ] sphinx = [ - {file = "Sphinx-3.0.4-py3-none-any.whl", hash = "sha256:779a519adbd3a70fc7c468af08c5e74829868b0a5b34587b33340e010291856c"}, - {file = "Sphinx-3.0.4.tar.gz", hash = "sha256:ea64df287958ee5aac46be7ac2b7277305b0381d213728c3a49d8bb9b8415807"}, + {file = "Sphinx-3.0.3-py3-none-any.whl", hash = "sha256:f5505d74cf9592f3b997380f9bdb2d2d0320ed74dd69691e3ee0644b956b8d83"}, + {file = "Sphinx-3.0.3.tar.gz", hash = "sha256:62edfd92d955b868d6c124c0942eba966d54b5f3dcb4ded39e65f74abac3f572"}, ] sphinx-click = [ {file = "sphinx-click-2.3.2.tar.gz", hash = "sha256:1b649ebe9f7a85b78ef6545d1dc258da5abca850ac6375be104d484a6334a728"}, @@ -1150,24 +1137,25 @@ stevedore = [ {file = "stevedore-1.32.0.tar.gz", hash = "sha256:18afaf1d623af5950cc0f7e75e70f917784c73b652a34a12d90b309451b5500b"}, ] toml = [ - {file = "toml-0.10.1-py2.py3-none-any.whl", hash = "sha256:bda89d5935c2eac546d648028b9901107a595863cb36bae0c73ac804a9b4ce88"}, - {file = "toml-0.10.1.tar.gz", hash = "sha256:926b612be1e5ce0634a2ca03470f95169cf16f939018233a670519cb4ac58b0f"}, + {file = "toml-0.10.0-py2.7.egg", hash = "sha256:f1db651f9657708513243e61e6cc67d101a39bad662eaa9b5546f789338e07a3"}, + {file = "toml-0.10.0-py2.py3-none-any.whl", hash = "sha256:235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e"}, + {file = "toml-0.10.0.tar.gz", hash = "sha256:229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c"}, ] tox = [ - {file = "tox-3.15.1-py2.py3-none-any.whl", hash = "sha256:322dfdf007d7d53323f767badcb068a5cfa7c44d8aabb698d131b28cf44e62c4"}, - {file = "tox-3.15.1.tar.gz", hash = "sha256:8c9ad9b48659d291c5bc78bcabaa4d680d627687154b812fa52baedaa94f9f83"}, + {file = "tox-3.14.6-py2.py3-none-any.whl", hash = "sha256:b2c4b91c975ea5c11463d9ca00bebf82654439c5df0f614807b9bdec62cc9471"}, + {file = "tox-3.14.6.tar.gz", hash = "sha256:a4a6689045d93c208d77230853b28058b7513f5123647b67bf012f82fa168303"}, ] tqdm = [ - {file = "tqdm-4.46.0-py2.py3-none-any.whl", hash = "sha256:acdafb20f51637ca3954150d0405ff1a7edde0ff19e38fb99a80a66210d2a28f"}, - {file = "tqdm-4.46.0.tar.gz", hash = "sha256:4733c4a10d0f2a4d098d801464bdaf5240c7dadd2a7fde4ee93b0a0efd9fb25e"}, + {file = "tqdm-4.45.0-py2.py3-none-any.whl", hash = "sha256:ea9e3fd6bd9a37e8783d75bfc4c1faf3c6813da6bd1c3e776488b41ec683af94"}, + {file = "tqdm-4.45.0.tar.gz", hash = "sha256:00339634a22c10a7a22476ee946bbde2dbe48d042ded784e4d88e0236eca5d81"}, ] urllib3 = [ {file = "urllib3-1.25.9-py2.py3-none-any.whl", hash = "sha256:88206b0eb87e6d677d424843ac5209e3fb9d0190d0ee169599165ec25e9d9115"}, {file = "urllib3-1.25.9.tar.gz", hash = "sha256:3018294ebefce6572a474f0604c2021e33b3fd8006ecd11d62107a5d2a963527"}, ] virtualenv = [ - {file = "virtualenv-20.0.21-py2.py3-none-any.whl", hash = "sha256:a730548b27366c5e6cbdf6f97406d861cccece2e22275e8e1a757aeff5e00c70"}, - {file = "virtualenv-20.0.21.tar.gz", hash = "sha256:a116629d4e7f4d03433b8afa27f43deba09d48bc48f5ecefa4f015a178efb6cf"}, + {file = "virtualenv-20.0.18-py2.py3-none-any.whl", hash = "sha256:5021396e8f03d0d002a770da90e31e61159684db2859d0ba4850fbea752aa675"}, + {file = "virtualenv-20.0.18.tar.gz", hash = "sha256:ac53ade75ca189bc97b6c1d9ec0f1a50efe33cbf178ae09452dcd9fd309013c1"}, ] voluptuous = [ {file = "voluptuous-0.11.7.tar.gz", hash = "sha256:2abc341dbc740c5e2302c7f9b8e2e243194fb4772585b991931cb5b22e9bf456"}, diff --git a/pyproject.toml b/pyproject.toml index 4645412dc..5b60eb1f7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,7 +21,7 @@ miio-extract-tokens = "miio.extract_tokens:main" miiocli = "miio.cli:create_cli" [tool.poetry.dependencies] -python = ">=3.6.5,<4.0.0" +python = "^3.6.5" click = "^7.1.1" cryptography = "^2.9" construct = "^2.10.56" @@ -33,7 +33,6 @@ tqdm = "^4.45.0" netifaces = "^0.10.9" android_backup = { version = "^0.2", optional = true } importlib_metadata = "^1.6.0" -dataclasses = { version = "^0.7", python = ">=3.6, <3.7" } [tool.poetry.dev-dependencies] pytest = "^5.4.1" From 0ab1dc7002dac02f0ed3310f80f07caf7569643c Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Wed, 27 May 2020 09:45:47 +0200 Subject: [PATCH 34/35] add get_local_status command --- miio/gateway.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/miio/gateway.py b/miio/gateway.py index f908bbce5..aa87f3cf0 100644 --- a/miio/gateway.py +++ b/miio/gateway.py @@ -243,6 +243,13 @@ def get_illumination(self): """Get illumination. In lux?""" return self.send("get_illumination").pop() + @command() + def get_local_status(self): + """ + Get infromation about the gateway and connected devices, + currently responds but response cannot be parsed "Resp invalid json". + """ + return self.get_prop("local.status") class GatewayDevice(Device): """ From 0f05432c4f7a13789bf8309925dd2418f467eb36 Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Wed, 27 May 2020 11:47:19 +0200 Subject: [PATCH 35/35] remove local.status again --- miio/gateway.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/miio/gateway.py b/miio/gateway.py index aa87f3cf0..f908bbce5 100644 --- a/miio/gateway.py +++ b/miio/gateway.py @@ -243,13 +243,6 @@ def get_illumination(self): """Get illumination. In lux?""" return self.send("get_illumination").pop() - @command() - def get_local_status(self): - """ - Get infromation about the gateway and connected devices, - currently responds but response cannot be parsed "Resp invalid json". - """ - return self.get_prop("local.status") class GatewayDevice(Device): """