Added native support for switch entities to the smartthings plugin

This commit is contained in:
Fabio Manganiello 2022-04-04 22:41:04 +02:00
parent b9c78ad913
commit 9d9ec1dc59
Signed by: blacklight
GPG Key ID: D90FBA7F76362774
1 changed files with 94 additions and 51 deletions

View File

@ -18,7 +18,7 @@ class SmartthingsPlugin(SwitchPlugin):
""" """
_timeout = aiohttp.ClientTimeout(total=20.) _timeout = aiohttp.ClientTimeout(total=20.0)
def __init__(self, access_token: str, **kwargs): def __init__(self, access_token: str, **kwargs):
""" """
@ -44,45 +44,30 @@ class SmartthingsPlugin(SwitchPlugin):
async def _refresh_locations(self, api): async def _refresh_locations(self, api):
self._locations = await api.locations() self._locations = await api.locations()
self._locations_by_id = { self._locations_by_id = {loc.location_id: loc for loc in self._locations}
loc.location_id: loc
for loc in self._locations
}
self._locations_by_name = { self._locations_by_name = {loc.name: loc for loc in self._locations}
loc.name: loc
for loc in self._locations
}
async def _refresh_devices(self, api): async def _refresh_devices(self, api):
self._devices = await api.devices() self._devices = await api.devices()
self._devices_by_id = { self._devices_by_id = {dev.device_id: dev for dev in self._devices}
dev.device_id: dev
for dev in self._devices
}
self._devices_by_name = { self._devices_by_name = {dev.label: dev for dev in self._devices}
dev.label: dev
for dev in self._devices
}
async def _refresh_rooms(self, api, location_id: str): async def _refresh_rooms(self, api, location_id: str):
self._rooms_by_location[location_id] = await api.rooms(location_id=location_id) self._rooms_by_location[location_id] = await api.rooms(location_id=location_id)
self._rooms_by_id.update(**{ self._rooms_by_id.update(
room.room_id: room **{room.room_id: room for room in self._rooms_by_location[location_id]}
for room in self._rooms_by_location[location_id] )
})
self._rooms_by_location_and_id[location_id] = { self._rooms_by_location_and_id[location_id] = {
room.room_id: room room.room_id: room for room in self._rooms_by_location[location_id]
for room in self._rooms_by_location[location_id]
} }
self._rooms_by_location_and_name[location_id] = { self._rooms_by_location_and_name[location_id] = {
room.name: room room.name: room for room in self._rooms_by_location[location_id]
for room in self._rooms_by_location[location_id]
} }
async def _refresh_info(self): async def _refresh_info(self):
@ -127,7 +112,7 @@ class SmartthingsPlugin(SwitchPlugin):
'rooms': { 'rooms': {
room.room_id: self._room_to_dict(room) room.room_id: self._room_to_dict(room)
for room in self._rooms_by_location.get(location.location_id, {}) for room in self._rooms_by_location.get(location.location_id, {})
} },
} }
@staticmethod @staticmethod
@ -257,12 +242,18 @@ class SmartthingsPlugin(SwitchPlugin):
""" """
self.refresh_info() self.refresh_info()
return { return {
'locations': {loc.location_id: self._location_to_dict(loc) for loc in self._locations}, 'locations': {
'devices': {dev.device_id: self._device_to_dict(dev) for dev in self._devices}, loc.location_id: self._location_to_dict(loc) for loc in self._locations
},
'devices': {
dev.device_id: self._device_to_dict(dev) for dev in self._devices
},
} }
@action @action
def get_location(self, location_id: Optional[str] = None, name: Optional[str] = None) -> dict: def get_location(
self, location_id: Optional[str] = None, name: Optional[str] = None
) -> dict:
""" """
Get the info of a location by ID or name. Get the info of a location by ID or name.
@ -296,10 +287,15 @@ class SmartthingsPlugin(SwitchPlugin):
""" """
assert location_id or name, 'Specify either location_id or name' assert location_id or name, 'Specify either location_id or name'
if location_id not in self._locations_by_id or name not in self._locations_by_name: if (
location_id not in self._locations_by_id
or name not in self._locations_by_name
):
self.refresh_info() self.refresh_info()
location = self._locations_by_id.get(location_id, self._locations_by_name.get(name)) location = self._locations_by_id.get(
location_id, self._locations_by_name.get(name)
)
assert location, 'Location {} not found'.format(location_id or name) assert location, 'Location {} not found'.format(location_id or name)
return self._location_to_dict(location) return self._location_to_dict(location)
@ -340,24 +336,41 @@ class SmartthingsPlugin(SwitchPlugin):
device = self._get_device(device) device = self._get_device(device)
return self._device_to_dict(device) return self._device_to_dict(device)
async def _execute(self, device_id: str, capability: str, command, component_id: str, args: Optional[list]): async def _execute(
self,
device_id: str,
capability: str,
command,
component_id: str,
args: Optional[list],
):
import pysmartthings 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)
ret = await device.command(component_id=component_id, capability=capability, command=command, args=args) ret = await device.command(
component_id=component_id,
capability=capability,
command=command,
args=args,
)
assert ret, 'The command {capability}={command} failed on device {device}'.format( assert (
capability=capability, command=command, device=device_id) ret
), 'The command {capability}={command} failed on device {device}'.format(
capability=capability, command=command, device=device_id
)
@action @action
def execute(self, def execute(
device: str, self,
capability: str, device: str,
command, capability: str,
component_id: str = 'main', command,
args: Optional[list] = None): component_id: str = 'main',
args: Optional[list] = None,
):
""" """
Execute a command on a device. Execute a command on a device.
@ -388,17 +401,39 @@ class SmartthingsPlugin(SwitchPlugin):
loop = asyncio.new_event_loop() loop = asyncio.new_event_loop()
try: try:
asyncio.set_event_loop(loop) asyncio.set_event_loop(loop)
loop.run_until_complete(self._execute( loop.run_until_complete(
device_id=device.device_id, capability=capability, command=command, self._execute(
component_id=component_id, args=args)) device_id=device.device_id,
capability=capability,
command=command,
component_id=component_id,
args=args,
)
)
finally: finally:
loop.stop() loop.stop()
@staticmethod async def _get_device_status(self, api, device_id: str) -> dict:
async def _get_device_status(api, device_id: str) -> dict: from platypush.entities.switches import Switch
device = await api.device(device_id) device = await api.device(device_id)
await device.status.refresh() await device.status.refresh()
if 'switch' in device.capabilities:
self.publish_entities(
[ # type: ignore
Switch(
id=device_id,
name=device.label,
state=device.status.switch,
data={
'location_id': getattr(device, 'location_id', None),
'room_id': getattr(device, 'room_id', None),
},
)
]
)
return { return {
'device_id': device_id, 'device_id': device_id,
'name': device.label, 'name': device.label,
@ -407,7 +442,7 @@ class SmartthingsPlugin(SwitchPlugin):
for cap in device.capabilities for cap in device.capabilities
if hasattr(device.status, cap) if hasattr(device.status, cap)
and not callable(getattr(device.status, cap)) and not callable(getattr(device.status, cap))
} },
} }
async def _refresh_status(self, devices: List[str]) -> List[dict]: async def _refresh_status(self, devices: List[str]) -> List[dict]:
@ -434,7 +469,9 @@ class SmartthingsPlugin(SwitchPlugin):
parse_device_id(dev) parse_device_id(dev)
# Fail if some devices haven't been found after refreshing # Fail if some devices haven't been found after refreshing
assert not missing_device_ids, 'Could not find the following devices: {}'.format(list(missing_device_ids)) assert (
not missing_device_ids
), 'Could not find the following devices: {}'.format(list(missing_device_ids))
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)
@ -529,13 +566,19 @@ class SmartthingsPlugin(SwitchPlugin):
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)
dev = await api.device(device_id) dev = await api.device(device_id)
assert 'switch' in dev.capabilities, 'The device {} has no switch capability'.format(dev.label) assert (
'switch' in dev.capabilities
), 'The device {} has no switch capability'.format(dev.label)
await dev.status.refresh() await dev.status.refresh()
state = 'off' if dev.status.switch else 'on' state = 'off' if dev.status.switch else 'on'
ret = await dev.command(component_id='main', capability='switch', command=state, args=args) ret = await dev.command(
component_id='main', capability='switch', command=state, args=args
)
assert ret, 'The command switch={state} failed on device {device}'.format(state=state, device=dev.label) assert ret, 'The command switch={state} failed on device {device}'.format(
state=state, device=dev.label
)
return not dev.status.switch return not dev.status.switch
with self._refresh_lock: with self._refresh_lock: