forked from platypush/platypush
[WIP] Converted switch.tplink
plugin.
`switch.tplink` converted to a `RunnablePlugin` that implements `SwitchEntityManager`.
This commit is contained in:
parent
be3b99326f
commit
3db9c58d31
3 changed files with 92 additions and 42 deletions
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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:
|
||||||
|
|
Loading…
Reference in a new issue