forked from platypush/platypush
Added polling/RunnablePlugin logic to smartthings
This commit is contained in:
parent
dabbe031ab
commit
ddd516a677
1 changed files with 88 additions and 19 deletions
|
@ -4,6 +4,8 @@ import aiohttp
|
||||||
from threading import RLock
|
from threading import RLock
|
||||||
from typing import Optional, Dict, List, Set, Tuple, Type, Union, Iterable
|
from typing import Optional, Dict, List, Set, Tuple, Type, Union, Iterable
|
||||||
|
|
||||||
|
import pysmartthings
|
||||||
|
|
||||||
from platypush.entities import Entity, manages
|
from platypush.entities import Entity, manages
|
||||||
|
|
||||||
# TODO Check battery support
|
# TODO Check battery support
|
||||||
|
@ -11,13 +13,15 @@ from platypush.entities import Entity, manages
|
||||||
from platypush.entities.devices import Device
|
from platypush.entities.devices import Device
|
||||||
from platypush.entities.dimmers import Dimmer
|
from platypush.entities.dimmers import Dimmer
|
||||||
from platypush.entities.lights import Light
|
from platypush.entities.lights import Light
|
||||||
from platypush.entities.sensors import BinarySensor, Sensor
|
from platypush.entities.motion import MotionSensor
|
||||||
|
from platypush.entities.sensors import Sensor
|
||||||
from platypush.entities.switches import Switch
|
from platypush.entities.switches import Switch
|
||||||
from platypush.plugins import Plugin, action
|
from platypush.plugins import RunnablePlugin, action
|
||||||
|
from platypush.utils import camel_case_to_snake_case
|
||||||
|
|
||||||
|
|
||||||
@manages(Device, Dimmer, Sensor, Switch, Light)
|
@manages(Device, Dimmer, Sensor, Switch, Light)
|
||||||
class SmartthingsPlugin(Plugin):
|
class SmartthingsPlugin(RunnablePlugin):
|
||||||
"""
|
"""
|
||||||
Plugin to interact with devices and locations registered to a Samsung SmartThings account.
|
Plugin to interact with devices and locations registered to a Samsung SmartThings account.
|
||||||
|
|
||||||
|
@ -29,11 +33,14 @@ class SmartthingsPlugin(Plugin):
|
||||||
|
|
||||||
_timeout = aiohttp.ClientTimeout(total=20.0)
|
_timeout = aiohttp.ClientTimeout(total=20.0)
|
||||||
|
|
||||||
def __init__(self, access_token: str, **kwargs):
|
def __init__(
|
||||||
|
self, access_token: str, poll_interval: Optional[float] = 20.0, **kwargs
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
:param access_token: SmartThings API access token - you can get one at https://account.smartthings.com/tokens.
|
:param access_token: SmartThings API access token - you can get one at https://account.smartthings.com/tokens.
|
||||||
|
:param poll_interval: How often the plugin should poll for changes, in seconds (default: 20).
|
||||||
"""
|
"""
|
||||||
super().__init__(**kwargs)
|
super().__init__(poll_interval=poll_interval, **kwargs)
|
||||||
self._access_token = access_token
|
self._access_token = access_token
|
||||||
self._refresh_lock = RLock()
|
self._refresh_lock = RLock()
|
||||||
self._execute_lock = RLock()
|
self._execute_lock = RLock()
|
||||||
|
@ -76,7 +83,6 @@ class SmartthingsPlugin(Plugin):
|
||||||
}
|
}
|
||||||
|
|
||||||
async def _refresh_info(self):
|
async def _refresh_info(self):
|
||||||
import pysmartthings
|
|
||||||
|
|
||||||
async with aiohttp.ClientSession(timeout=self._timeout) as session:
|
async with aiohttp.ClientSession(timeout=self._timeout) as session:
|
||||||
api = pysmartthings.SmartThings(session, self._access_token)
|
api = pysmartthings.SmartThings(session, self._access_token)
|
||||||
|
@ -369,8 +375,6 @@ class SmartthingsPlugin(Plugin):
|
||||||
component_id: str,
|
component_id: str,
|
||||||
args: Optional[list],
|
args: Optional[list],
|
||||||
):
|
):
|
||||||
import pysmartthings
|
|
||||||
|
|
||||||
async with aiohttp.ClientSession(timeout=self._timeout) as session:
|
async with aiohttp.ClientSession(timeout=self._timeout) as session:
|
||||||
api = pysmartthings.SmartThings(session, self._access_token)
|
api = pysmartthings.SmartThings(session, self._access_token)
|
||||||
device = await api.device(device_id)
|
device = await api.device(device_id)
|
||||||
|
@ -517,7 +521,7 @@ class SmartthingsPlugin(Plugin):
|
||||||
if 'motionSensor' in cls._get_capabilities(device):
|
if 'motionSensor' in cls._get_capabilities(device):
|
||||||
sensors.append(
|
sensors.append(
|
||||||
cls._to_entity(
|
cls._to_entity(
|
||||||
BinarySensor,
|
MotionSensor,
|
||||||
device,
|
device,
|
||||||
value=device.status.motion,
|
value=device.status.motion,
|
||||||
)
|
)
|
||||||
|
@ -551,11 +555,22 @@ class SmartthingsPlugin(Plugin):
|
||||||
|
|
||||||
return super().transform_entities(compatible_entities) # type: ignore
|
return super().transform_entities(compatible_entities) # type: ignore
|
||||||
|
|
||||||
async def _get_device_status(self, api, device_id: str) -> dict:
|
async def _get_device_status(
|
||||||
|
self, api, device_id: str, publish_entities: bool
|
||||||
|
) -> dict:
|
||||||
device = await api.device(device_id)
|
device = await api.device(device_id)
|
||||||
|
assert device, f'No such device: {device_id}'
|
||||||
await device.status.refresh()
|
await device.status.refresh()
|
||||||
|
if publish_entities:
|
||||||
self.publish_entities([device]) # type: ignore
|
self.publish_entities([device]) # type: ignore
|
||||||
|
|
||||||
|
self._devices_by_id[device_id] = device
|
||||||
|
self._devices_by_name[device.label] = device
|
||||||
|
for i, dev in enumerate(self._devices):
|
||||||
|
if dev.device_id == device_id:
|
||||||
|
self._devices[i] = device
|
||||||
|
break
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'device_id': device_id,
|
'device_id': device_id,
|
||||||
'name': device.label,
|
'name': device.label,
|
||||||
|
@ -567,9 +582,9 @@ class SmartthingsPlugin(Plugin):
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
async def _refresh_status(self, devices: List[str]) -> List[dict]:
|
async def _refresh_status(
|
||||||
import pysmartthings
|
self, devices: List[str], publish_entities: bool = True
|
||||||
|
) -> List[dict]:
|
||||||
device_ids = []
|
device_ids = []
|
||||||
missing_device_ids = set()
|
missing_device_ids = set()
|
||||||
|
|
||||||
|
@ -598,7 +613,11 @@ class SmartthingsPlugin(Plugin):
|
||||||
async with aiohttp.ClientSession(timeout=self._timeout) as session:
|
async with aiohttp.ClientSession(timeout=self._timeout) as session:
|
||||||
api = pysmartthings.SmartThings(session, self._access_token)
|
api = pysmartthings.SmartThings(session, self._access_token)
|
||||||
status_tasks = [
|
status_tasks = [
|
||||||
asyncio.ensure_future(self._get_device_status(api, device_id))
|
asyncio.ensure_future(
|
||||||
|
self._get_device_status(
|
||||||
|
api, device_id, publish_entities=publish_entities
|
||||||
|
)
|
||||||
|
)
|
||||||
for device_id in device_ids
|
for device_id in device_ids
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -606,7 +625,9 @@ class SmartthingsPlugin(Plugin):
|
||||||
return await asyncio.gather(*status_tasks)
|
return await asyncio.gather(*status_tasks)
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def status(self, device: Optional[Union[str, List[str]]] = None) -> List[dict]:
|
def status(
|
||||||
|
self, device: Optional[Union[str, List[str]]] = None, publish_entities=True
|
||||||
|
) -> List[dict]:
|
||||||
"""
|
"""
|
||||||
Refresh and return the status of one or more devices.
|
Refresh and return the status of one or more devices.
|
||||||
|
|
||||||
|
@ -643,7 +664,11 @@ class SmartthingsPlugin(Plugin):
|
||||||
loop = asyncio.new_event_loop()
|
loop = asyncio.new_event_loop()
|
||||||
try:
|
try:
|
||||||
asyncio.set_event_loop(loop)
|
asyncio.set_event_loop(loop)
|
||||||
return loop.run_until_complete(self._refresh_status(list(devices)))
|
return loop.run_until_complete(
|
||||||
|
self._refresh_status(
|
||||||
|
list(devices), publish_entities=publish_entities
|
||||||
|
)
|
||||||
|
)
|
||||||
finally:
|
finally:
|
||||||
loop.stop()
|
loop.stop()
|
||||||
|
|
||||||
|
@ -677,8 +702,6 @@ class SmartthingsPlugin(Plugin):
|
||||||
:param device: Device name or ID.
|
:param device: Device name or ID.
|
||||||
:return: Device status
|
:return: Device status
|
||||||
"""
|
"""
|
||||||
import pysmartthings
|
|
||||||
|
|
||||||
device = self._get_device(device)
|
device = self._get_device(device)
|
||||||
device_id = device.device_id
|
device_id = device.device_id
|
||||||
|
|
||||||
|
@ -794,5 +817,51 @@ class SmartthingsPlugin(Plugin):
|
||||||
if err:
|
if err:
|
||||||
raise err
|
raise err
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _device_status_to_dict(status: pysmartthings.DeviceStatus) -> dict:
|
||||||
|
status_dict = {}
|
||||||
|
for attr in status.attributes.keys():
|
||||||
|
attr = camel_case_to_snake_case(attr)
|
||||||
|
if hasattr(status, attr):
|
||||||
|
status_dict[attr] = getattr(status, attr)
|
||||||
|
|
||||||
|
return status_dict
|
||||||
|
|
||||||
|
def _get_devices_status_dict(self) -> Dict[str, dict]:
|
||||||
|
return {
|
||||||
|
device_id: self._device_status_to_dict(device.status)
|
||||||
|
for device_id, device in self._devices_by_id.items()
|
||||||
|
}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _has_status_changed(status: dict, new_status: dict) -> bool:
|
||||||
|
if not status and new_status:
|
||||||
|
return True
|
||||||
|
|
||||||
|
for attr, value in status.items():
|
||||||
|
if attr in new_status:
|
||||||
|
new_value = new_status[attr]
|
||||||
|
if value != new_value:
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
def main(self):
|
||||||
|
while not self.should_stop():
|
||||||
|
updated_devices = {}
|
||||||
|
devices = self._get_devices_status_dict()
|
||||||
|
self.status(publish_entities=False)
|
||||||
|
new_devices = self._get_devices_status_dict()
|
||||||
|
|
||||||
|
updated_devices = {
|
||||||
|
device_id: self._devices_by_id[device_id]
|
||||||
|
for device_id, new_status in new_devices.items()
|
||||||
|
if self._has_status_changed(devices.get(device_id, {}), new_status)
|
||||||
|
}
|
||||||
|
|
||||||
|
self.publish_entities(updated_devices.values()) # type: ignore
|
||||||
|
devices = new_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