[WIP] Converted `switch.tplink` plugin.

`switch.tplink` converted to a `RunnablePlugin` that implements
`SwitchEntityManager`.
This commit is contained in:
Fabio Manganiello 2023-02-03 02:20:20 +01:00
parent be3b99326f
commit 3db9c58d31
Signed by: blacklight
GPG Key ID: D90FBA7F76362774
3 changed files with 92 additions and 42 deletions

View File

@ -471,9 +471,9 @@ class SmartthingsPlugin(
loop.stop() loop.stop()
@staticmethod @staticmethod
def _property_to_entity_name( def _property_to_entity_name( # pylint: disable=redefined-builtin
property: str, property: str,
) -> str: # pylint: disable=redefined-builtin ) -> str:
return ' '.join( return ' '.join(
[ [
t[:1].upper() + t[1:] t[:1].upper() + t[1:]
@ -753,10 +753,8 @@ class SmartthingsPlugin(
def _set_switch(self, device: str, value: Optional[bool] = None): def _set_switch(self, device: str, value: Optional[bool] = None):
( (
device, device,
property, property, # pylint: disable=redefined-builtin
) = self._to_device_and_property( # pylint: disable=redefined-builtin ) = self._to_device_and_property(device)
device
)
if not property: if not property:
property = Attribute.switch property = Attribute.switch

View File

@ -1,12 +1,10 @@
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from typing import List, Union from typing import List, Union
from platypush.entities import manages
from platypush.entities.switches import Switch
from platypush.plugins import Plugin, action from platypush.plugins import Plugin, action
@manages(Switch) # TODO REMOVE ME
class SwitchPlugin(Plugin, ABC): class SwitchPlugin(Plugin, ABC):
""" """
Abstract class for interacting with switch devices Abstract class for interacting with switch devices
@ -18,19 +16,19 @@ class SwitchPlugin(Plugin, ABC):
@action @action
@abstractmethod @abstractmethod
def on(self, device, *args, **kwargs): def on(self, device, *args, **kwargs):
""" Turn the device on """ """Turn the device on"""
raise NotImplementedError() raise NotImplementedError()
@action @action
@abstractmethod @abstractmethod
def off(self, device, *args, **kwargs): def off(self, device, *args, **kwargs):
""" Turn the device off """ """Turn the device off"""
raise NotImplementedError() raise NotImplementedError()
@action @action
@abstractmethod @abstractmethod
def toggle(self, device, *args, **kwargs): def toggle(self, device, *args, **kwargs):
""" Toggle the device status (on/off) """ """Toggle the device status (on/off)"""
raise NotImplementedError() raise NotImplementedError()
@action @action
@ -43,7 +41,11 @@ class SwitchPlugin(Plugin, ABC):
""" """
devices = self.switches devices = self.switches
if device: if device:
devices = [d for d in self.switches if d.get('id') == device or d.get('name') == device] devices = [
d
for d in self.switches
if d.get('id') == device or d.get('name') == device
]
return devices[0] if devices else [] return devices[0] if devices else []
return devices return devices

View File

@ -1,4 +1,11 @@
from typing import Union, Mapping, List, Collection, Optional from typing import (
Collection,
Dict,
List,
Mapping,
Optional,
Union,
)
from pyHS100 import ( from pyHS100 import (
SmartDevice, SmartDevice,
@ -9,12 +16,11 @@ from pyHS100 import (
SmartDeviceException, SmartDeviceException,
) )
from platypush.entities import Entity from platypush.entities import Entity, SwitchEntityManager
from platypush.plugins import action from platypush.plugins import RunnablePlugin, action
from platypush.plugins.switch import SwitchPlugin
class SwitchTplinkPlugin(SwitchPlugin): class SwitchTplinkPlugin(RunnablePlugin, SwitchEntityManager):
""" """
Plugin to interact with TP-Link smart switches/plugs like the HS100 Plugin to interact with TP-Link smart switches/plugs like the HS100
(https://www.tp-link.com/us/products/details/cat-5516_HS100.html). (https://www.tp-link.com/us/products/details/cat-5516_HS100.html).
@ -25,15 +31,15 @@ class SwitchTplinkPlugin(SwitchPlugin):
""" """
_ip_to_dev = {} _ip_to_dev: Dict[str, SmartDevice] = {}
_alias_to_dev = {} _alias_to_dev: Dict[str, SmartDevice] = {}
def __init__( def __init__(
self, self,
plugs: Optional[Union[Mapping[str, str], List[str]]] = None, plugs: Optional[Union[Mapping[str, str], List[str]]] = None,
bulbs: Optional[Union[Mapping[str, str], List[str]]] = None, bulbs: Optional[Union[Mapping[str, str], List[str]]] = None,
strips: Optional[Union[Mapping[str, str], List[str]]] = None, strips: Optional[Union[Mapping[str, str], List[str]]] = None,
**kwargs **kwargs,
): ):
""" """
:param plugs: Optional list of IP addresses or name->address mapping if you have a static list of :param plugs: Optional list of IP addresses or name->address mapping if you have a static list of
@ -75,28 +81,30 @@ class SwitchTplinkPlugin(SwitchPlugin):
self._update_devices() self._update_devices()
def _update_devices(self, devices: Optional[Mapping[str, SmartDevice]] = None): def _update_devices(
self,
devices: Optional[Mapping[str, SmartDevice]] = None,
publish_entities: bool = True,
):
for (addr, info) in self._static_devices.items(): for (addr, info) in self._static_devices.items():
try: try:
dev = info['type'](addr) dev = info['type'](addr)
self._alias_to_dev[info.get('name', dev.alias)] = dev self._alias_to_dev[info.get('name', dev.alias)] = dev
self._ip_to_dev[addr] = dev self._ip_to_dev[addr] = dev
except SmartDeviceException as e: except SmartDeviceException as e:
self.logger.warning( self.logger.warning('Could not communicate with device %s: %s', addr, e)
'Could not communicate with device {}: {}'.format(addr, str(e))
)
for (ip, dev) in (devices or {}).items(): for (ip, dev) in (devices or {}).items():
self._ip_to_dev[ip] = dev self._ip_to_dev[ip] = dev
self._alias_to_dev[dev.alias] = dev self._alias_to_dev[dev.alias] = dev
if devices: if devices and publish_entities:
self.publish_entities(devices.values()) # type: ignore self.publish_entities(devices.values())
def transform_entities(self, devices: Collection[SmartDevice]): def transform_entities(self, entities: Collection[SmartDevice]):
from platypush.entities.switches import Switch from platypush.entities.switches import Switch
return super().transform_entities( # type: ignore return super().transform_entities(
[ [
Switch( Switch(
id=dev.host, id=dev.host,
@ -109,13 +117,13 @@ class SwitchTplinkPlugin(SwitchPlugin):
'hw_info': dev.hw_info, 'hw_info': dev.hw_info,
}, },
) )
for dev in (devices or []) for dev in (entities or [])
] ]
) )
def _scan(self): def _scan(self, publish_entities: bool = True) -> Dict[str, SmartDevice]:
devices = Discover.discover() devices = Discover.discover()
self._update_devices(devices) self._update_devices(devices, publish_entities=publish_entities)
return devices return devices
def _get_device(self, device, use_cache=True): def _get_device(self, device, use_cache=True):
@ -133,18 +141,21 @@ class SwitchTplinkPlugin(SwitchPlugin):
if use_cache: if use_cache:
return self._get_device(device, use_cache=False) return self._get_device(device, use_cache=False)
else: raise RuntimeError(f'Device {device} not found')
raise RuntimeError('Device {} not found'.format(device))
def _set(self, device: SmartDevice, state: bool): def _set(self, device: SmartDevice, state: bool):
action_name = 'turn_on' if state else 'turn_off' action_name = 'turn_on' if state else 'turn_off'
action = getattr(device, action_name) act = getattr(device, action_name, None)
action() assert act, (
self.publish_entities([device]) # type: ignore f'No such action available on the device "{device.alias}": '
f'"{action_name}"'
)
act()
self.publish_entities([device])
return self._serialize(device) return self._serialize(device)
@action @action
def on(self, device, **_): def on(self, device, **_): # pylint: disable=arguments-differ
""" """
Turn on a device Turn on a device
@ -156,7 +167,7 @@ class SwitchTplinkPlugin(SwitchPlugin):
return self._set(device, True) return self._set(device, True)
@action @action
def off(self, device, **_): def off(self, device, **_): # pylint: disable=arguments-differ
""" """
Turn off a device Turn off a device
@ -168,7 +179,7 @@ class SwitchTplinkPlugin(SwitchPlugin):
return self._set(device, False) return self._set(device, False)
@action @action
def toggle(self, device, **_): def toggle(self, device, **_): # pylint: disable=arguments-differ
""" """
Toggle the state of a device (on/off) Toggle the state of a device (on/off)
@ -191,9 +202,48 @@ class SwitchTplinkPlugin(SwitchPlugin):
'on': device.is_on, 'on': device.is_on,
} }
@property @action
def switches(self) -> List[dict]: def status(self, *_, **__) -> List[dict]:
"""
Retrieve the current status of the devices. Return format:
.. code-block:: json
[
{
"current_consumption": 0.5,
"id": "192.168.1.123",
"ip": "192.168.1.123",
"host": "192.168.1.123",
"hw_info": "00:11:22:33:44:55",
"name": "My Switch",
"on": true,
}
]
"""
return [self._serialize(dev) for dev in self._scan().values()] return [self._serialize(dev) for dev in self._scan().values()]
def main(self):
devices = {ip: self._serialize(dev) for ip, dev in self._ip_to_dev}
while not self.should_stop():
new_devices = self._scan(publish_entities=False)
new_serialized_devices = {
ip: self._serialize(dev) for ip, dev in new_devices.items()
}
updated_devices = {
ip: new_devices[ip]
for ip, dev in new_serialized_devices.items()
if any(v != devices.get(ip, {}).get(k) for k, v in dev.items())
}
if updated_devices:
self.publish_entities(updated_devices.values())
devices = new_serialized_devices
self.wait_stop(self.poll_interval)
# vim:sw=4:ts=4:et: # vim:sw=4:ts=4:et: