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.
This commit is contained in:
Teemu R 2020-01-13 00:17:45 +01:00 committed by GitHub
parent 524d28abbc
commit 0f0df481a0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 43 additions and 51 deletions

View File

@ -76,7 +76,7 @@ class SmartBulb(SmartDevice):
self, self,
host: str, host: str,
protocol: TPLinkSmartHomeProtocol = None, protocol: TPLinkSmartHomeProtocol = None,
context: str = None, child_id: str = None,
cache_ttl: int = 3, cache_ttl: int = 3,
*, *,
ioloop=None, ioloop=None,
@ -85,7 +85,7 @@ class SmartBulb(SmartDevice):
self, self,
host=host, host=host,
protocol=protocol, protocol=protocol,
context=context, child_id=child_id,
cache_ttl=cache_ttl, cache_ttl=cache_ttl,
ioloop=ioloop, ioloop=ioloop,
) )

View File

@ -111,7 +111,7 @@ class SmartDevice:
self, self,
host: str, host: str,
protocol: Optional[TPLinkSmartHomeProtocol] = None, protocol: Optional[TPLinkSmartHomeProtocol] = None,
context: str = None, child_id: str = None,
cache_ttl: int = 3, cache_ttl: int = 3,
*, *,
ioloop=None, ioloop=None,
@ -119,20 +119,19 @@ class SmartDevice:
"""Create a new SmartDevice instance. """Create a new SmartDevice instance.
:param str host: host name or ip address on which the device listens :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 self.host = host
if protocol is None: # pragma: no cover if protocol is None: # pragma: no cover
protocol = TPLinkSmartHomeProtocol() protocol = TPLinkSmartHomeProtocol()
self.protocol = protocol self.protocol = protocol
self.emeter_type = "emeter" self.emeter_type = "emeter"
self.context = context self.child_id = child_id
self.num_children = 0
self.cache_ttl = timedelta(seconds=cache_ttl) self.cache_ttl = timedelta(seconds=cache_ttl)
_LOGGER.debug( _LOGGER.debug(
"Initializing %s using context %s and cache ttl %s", "Initializing %s using child_id %s and cache ttl %s",
self.host, self.host,
self.context, self.child_id,
self.cache_ttl, self.cache_ttl,
) )
self.cache = defaultdict(lambda: defaultdict(lambda: None)) # type: ignore self.cache = defaultdict(lambda: defaultdict(lambda: None)) # type: ignore
@ -189,8 +188,8 @@ class SmartDevice:
:raises SmartDeviceException: if command was not executed correctly :raises SmartDeviceException: if command was not executed correctly
""" """
request: Dict[str, Any] = {target: {cmd: arg}} request: Dict[str, Any] = {target: {cmd: arg}}
if self.context is not None: if self.child_id is not None:
request = {"context": {"child_ids": [self.context]}, target: {cmd: arg}} request = {"context": {"child_ids": [self.child_id]}, target: {cmd: arg}}
try: try:
response = self._result_from_cache(target, cmd) response = self._result_from_cache(target, cmd)
@ -219,6 +218,16 @@ class SmartDevice:
return result 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: def has_emeter(self) -> bool:
"""Return if device has an energy meter. """Return if device has an energy meter.
@ -614,6 +623,18 @@ class SmartDevice:
""" """
raise NotImplementedError("Device subclass needs to implement this.") 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 @property
def device_type(self) -> DeviceType: def device_type(self) -> DeviceType:
"""Return the device type.""" """Return the device type."""
@ -635,7 +656,7 @@ class SmartDevice:
return self._device_type == DeviceType.Strip return self._device_type == DeviceType.Strip
@property @property
def is_dimmable(self): def is_dimmable(self) -> bool:
"""Return True if the device is dimmable.""" """Return True if the device is dimmable."""
return False return False
@ -644,6 +665,11 @@ class SmartDevice:
"""Return True if the device supports color temperature.""" """Return True if the device supports color temperature."""
return False 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): def __repr__(self):
return "<{} model {} at {} ({}), is_on: {} - dev specific: {}>".format( return "<{} model {} at {} ({}), is_on: {} - dev specific: {}>".format(
self.__class__.__name__, self.__class__.__name__,
@ -653,31 +679,3 @@ class SmartDevice:
self.is_on, self.is_on,
self.state_information, 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

View File

@ -40,12 +40,12 @@ class SmartPlug(SmartDevice):
self, self,
host: str, host: str,
protocol: "TPLinkSmartHomeProtocol" = None, protocol: "TPLinkSmartHomeProtocol" = None,
context: str = None, child_id: str = None,
cache_ttl: int = 3, cache_ttl: int = 3,
*, *,
ioloop=None, ioloop=None,
) -> 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.emeter_type = "emeter"
self._device_type = DeviceType.Plug self._device_type = DeviceType.Plug
@ -90,12 +90,6 @@ class SmartPlug(SmartDevice):
else: else:
raise ValueError("Brightness value %s is not valid." % value) 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 @property # type: ignore
@requires_update @requires_update
def alias(self) -> str: def alias(self) -> str:
@ -104,7 +98,7 @@ class SmartPlug(SmartDevice):
:return: Device name aka alias. :return: Device name aka alias.
:rtype: str :rtype: str
""" """
if self.context: if self.is_child_device:
info = self._get_child_info() info = self._get_child_info()
return info["alias"] return info["alias"]
else: else:
@ -140,7 +134,7 @@ class SmartPlug(SmartDevice):
:return: True if device is on, False otherwise :return: True if device is on, False otherwise
""" """
if self.context: if self.is_child_device:
info = self._get_child_info() info = self._get_child_info()
return info["state"] return info["state"]
@ -192,7 +186,7 @@ class SmartPlug(SmartDevice):
:rtype: datetime :rtype: datetime
""" """
sys_info = self.sys_info sys_info = self.sys_info
if self.context: if self.is_child_device:
info = self._get_child_info() info = self._get_child_info()
on_time = info["on_time"] on_time = info["on_time"]
else: else:

View File

@ -79,7 +79,7 @@ class SmartStrip(SmartPlug):
SmartPlug( SmartPlug(
self.host, self.host,
self.protocol, self.protocol,
context=child["id"], child_id=child["id"],
cache_ttl=self.cache_ttl.total_seconds(), cache_ttl=self.cache_ttl.total_seconds(),
ioloop=self.ioloop, ioloop=self.ioloop,
) )