From 0f0df481a0e5f397d053fb57c69898b64209df63 Mon Sep 17 00:00:00 2001 From: Teemu R Date: Mon, 13 Jan 2020 00:17:45 +0100 Subject: [PATCH] Add device_id property, rename context to child_id (#15) For regular devices, device_id is the mac address and for child devices it is a combination of the mac address and the child_id. --- kasa/smartbulb.py | 4 +-- kasa/smartdevice.py | 72 ++++++++++++++++++++++----------------------- kasa/smartplug.py | 16 ++++------ kasa/smartstrip.py | 2 +- 4 files changed, 43 insertions(+), 51 deletions(-) diff --git a/kasa/smartbulb.py b/kasa/smartbulb.py index e075e031..eacac577 100644 --- a/kasa/smartbulb.py +++ b/kasa/smartbulb.py @@ -76,7 +76,7 @@ class SmartBulb(SmartDevice): self, host: str, protocol: TPLinkSmartHomeProtocol = None, - context: str = None, + child_id: str = None, cache_ttl: int = 3, *, ioloop=None, @@ -85,7 +85,7 @@ class SmartBulb(SmartDevice): self, host=host, protocol=protocol, - context=context, + child_id=child_id, cache_ttl=cache_ttl, ioloop=ioloop, ) diff --git a/kasa/smartdevice.py b/kasa/smartdevice.py index 9d6dba77..cda61a7f 100755 --- a/kasa/smartdevice.py +++ b/kasa/smartdevice.py @@ -111,7 +111,7 @@ class SmartDevice: self, host: str, protocol: Optional[TPLinkSmartHomeProtocol] = None, - context: str = None, + child_id: str = None, cache_ttl: int = 3, *, ioloop=None, @@ -119,20 +119,19 @@ class SmartDevice: """Create a new SmartDevice instance. :param str host: host name or ip address on which the device listens - :param context: optional child ID for context in a parent device + :param child_id: optional child ID for context in a parent device """ self.host = host if protocol is None: # pragma: no cover protocol = TPLinkSmartHomeProtocol() self.protocol = protocol self.emeter_type = "emeter" - self.context = context - self.num_children = 0 + self.child_id = child_id self.cache_ttl = timedelta(seconds=cache_ttl) _LOGGER.debug( - "Initializing %s using context %s and cache ttl %s", + "Initializing %s using child_id %s and cache ttl %s", self.host, - self.context, + self.child_id, self.cache_ttl, ) self.cache = defaultdict(lambda: defaultdict(lambda: None)) # type: ignore @@ -189,8 +188,8 @@ class SmartDevice: :raises SmartDeviceException: if command was not executed correctly """ request: Dict[str, Any] = {target: {cmd: arg}} - if self.context is not None: - request = {"context": {"child_ids": [self.context]}, target: {cmd: arg}} + if self.child_id is not None: + request = {"context": {"child_ids": [self.child_id]}, target: {cmd: arg}} try: response = self._result_from_cache(target, cmd) @@ -219,6 +218,16 @@ class SmartDevice: return result + def _get_child_info(self) -> Dict: + """Return the child information dict, if available. + + :raises SmartDeviceException: if there is no child or it cannot be found. + """ + for plug in self.sys_info["children"]: + if plug["id"] == self.child_id: + return plug + raise SmartDeviceException("Unable to find children %s") + def has_emeter(self) -> bool: """Return if device has an energy meter. @@ -614,6 +623,18 @@ class SmartDevice: """ raise NotImplementedError("Device subclass needs to implement this.") + @property # type: ignore + @requires_update + def device_id(self) -> str: + """Return unique ID for the device. + + For regular devices this is the MAC address of the device, + for child devices a combination of MAC and child's ID. + """ + if self.is_child_device: + return f"{self.mac}_{self.child_id}" + return self.mac + @property def device_type(self) -> DeviceType: """Return the device type.""" @@ -635,7 +656,7 @@ class SmartDevice: return self._device_type == DeviceType.Strip @property - def is_dimmable(self): + def is_dimmable(self) -> bool: """Return True if the device is dimmable.""" return False @@ -644,6 +665,11 @@ class SmartDevice: """Return True if the device supports color temperature.""" return False + @property + def is_child_device(self) -> bool: + """Return True if the device is a child device of another device.""" + return self.child_id is not None + def __repr__(self): return "<{} model {} at {} ({}), is_on: {} - dev specific: {}>".format( self.__class__.__name__, @@ -653,31 +679,3 @@ class SmartDevice: self.is_on, self.state_information, ) - - -class SyncSmartDevice: - """A synchronous SmartDevice speaker class. - - This has the same methods as `SyncSmartDevice`, however, it wraps all async - methods and call them in a blocking way. - - Taken from https://github.com/basnijholt/media_player.kef/ - """ - - def __init__(self, async_device, ioloop): - self.async_device = async_device - self.ioloop = ioloop - - def __getattr__(self, attr): - method = getattr(self.async_device, attr) - if method is None: - raise AttributeError(f"'SyncSmartDevice' object has no attribute '{attr}.'") - if inspect.iscoroutinefunction(method): - - @functools.wraps(method) - def wrapped(*args, **kwargs): - return self.ioloop.run_until_complete(method(*args, **kwargs)) - - return wrapped - else: - return method diff --git a/kasa/smartplug.py b/kasa/smartplug.py index 6e7ee6aa..31ef8454 100644 --- a/kasa/smartplug.py +++ b/kasa/smartplug.py @@ -40,12 +40,12 @@ class SmartPlug(SmartDevice): self, host: str, protocol: "TPLinkSmartHomeProtocol" = None, - context: str = None, + child_id: str = None, cache_ttl: int = 3, *, ioloop=None, ) -> None: - SmartDevice.__init__(self, host, protocol, context, cache_ttl, ioloop=ioloop) + SmartDevice.__init__(self, host, protocol, child_id, cache_ttl, ioloop=ioloop) self.emeter_type = "emeter" self._device_type = DeviceType.Plug @@ -90,12 +90,6 @@ class SmartPlug(SmartDevice): else: raise ValueError("Brightness value %s is not valid." % value) - def _get_child_info(self): - for plug in self.sys_info["children"]: - if plug["id"] == self.context: - return plug - raise SmartDeviceException("Unable to find children %s") - @property # type: ignore @requires_update def alias(self) -> str: @@ -104,7 +98,7 @@ class SmartPlug(SmartDevice): :return: Device name aka alias. :rtype: str """ - if self.context: + if self.is_child_device: info = self._get_child_info() return info["alias"] else: @@ -140,7 +134,7 @@ class SmartPlug(SmartDevice): :return: True if device is on, False otherwise """ - if self.context: + if self.is_child_device: info = self._get_child_info() return info["state"] @@ -192,7 +186,7 @@ class SmartPlug(SmartDevice): :rtype: datetime """ sys_info = self.sys_info - if self.context: + if self.is_child_device: info = self._get_child_info() on_time = info["on_time"] else: diff --git a/kasa/smartstrip.py b/kasa/smartstrip.py index 2efe0cf8..161cc651 100755 --- a/kasa/smartstrip.py +++ b/kasa/smartstrip.py @@ -79,7 +79,7 @@ class SmartStrip(SmartPlug): SmartPlug( self.host, self.protocol, - context=child["id"], + child_id=child["id"], cache_ttl=self.cache_ttl.total_seconds(), ioloop=self.ioloop, )