wyzeapy.services.sensor_service

 1#  Copyright (c) 2021. Mulliken, LLC - All Rights Reserved
 2#  You may use, distribute and modify this code under the terms
 3#  of the attached license. You should have received a copy of
 4#  the license with this file. If not, please write to:
 5#  katie@mulliken.net to receive a copy
 6import asyncio
 7import logging
 8from threading import Thread
 9from typing import List, Callable, Tuple, Optional
10
11from aiohttp import ClientOSError, ContentTypeError
12
13from ..exceptions import UnknownApiError
14from .base_service import BaseService
15from ..types import Device, PropertyIDs, DeviceTypes
16
17_LOGGER = logging.getLogger(__name__)
18
19
20class Sensor(Device):
21    detected: bool = False
22
23
24class SensorService(BaseService):
25    _updater_thread: Optional[Thread] = None
26    _subscribers: List[Tuple[Sensor, Callable[[Sensor], None]]] = []
27
28    async def update(self, sensor: Sensor) -> Sensor:
29        # Get updated device_params
30        async with BaseService._update_lock:
31            sensor.device_params = await self.get_updated_params(sensor.mac)
32        properties = await self._get_device_info(sensor)
33
34        for property in properties['data']['property_list']:
35            pid = property['pid']
36            value = property['value']
37
38            try:
39                if PropertyIDs(pid) == PropertyIDs.CONTACT_STATE:
40                    sensor.detected = value == "1"
41                if PropertyIDs(pid) == PropertyIDs.MOTION_STATE:
42                    sensor.detected = value == "1"
43            except ValueError:
44                pass
45
46        return sensor
47
48    async def register_for_updates(self, sensor: Sensor, callback: Callable[[Sensor], None]):
49        _LOGGER.debug(f"Registering sensor: {sensor.nickname} for updates")
50        loop = asyncio.get_event_loop()
51        if not self._updater_thread:
52            self._updater_thread = Thread(target=self.update_worker, args=[loop, ], daemon=True)
53            self._updater_thread.start()
54
55        self._subscribers.append((sensor, callback))
56
57    async def deregister_for_updates(self, sensor: Sensor):
58        self._subscribers = [(sense, callback) for sense, callback in self._subscribers if sense.mac != sensor.mac]
59
60    def update_worker(self, loop):
61        while True:
62            for sensor, callback in self._subscribers:
63                _LOGGER.debug(f"Providing update for {sensor.nickname}")
64                try:
65                    callback(asyncio.run_coroutine_threadsafe(self.update(sensor), loop).result())
66                except UnknownApiError as e:
67                    _LOGGER.warning(f"The update method detected an UnknownApiError: {e}")
68                except ClientOSError as e:
69                    _LOGGER.error(f"A network error was detected: {e}")
70                except ContentTypeError as e:
71                    _LOGGER.error(f"Server returned unexpected ContentType: {e}")
72
73    async def get_sensors(self) -> List[Sensor]:
74        if self._devices is None:
75            self._devices = await self.get_object_list()
76
77        sensors = [Sensor(device.raw_dict) for device in self._devices if
78                   device.type is DeviceTypes.MOTION_SENSOR or
79                   device.type is DeviceTypes.CONTACT_SENSOR]
80        return [Sensor(sensor.raw_dict) for sensor in sensors]
class Sensor(wyzeapy.types.Device):
21class Sensor(Device):
22    detected: bool = False
detected: bool = False
class SensorService(wyzeapy.services.base_service.BaseService):
25class SensorService(BaseService):
26    _updater_thread: Optional[Thread] = None
27    _subscribers: List[Tuple[Sensor, Callable[[Sensor], None]]] = []
28
29    async def update(self, sensor: Sensor) -> Sensor:
30        # Get updated device_params
31        async with BaseService._update_lock:
32            sensor.device_params = await self.get_updated_params(sensor.mac)
33        properties = await self._get_device_info(sensor)
34
35        for property in properties['data']['property_list']:
36            pid = property['pid']
37            value = property['value']
38
39            try:
40                if PropertyIDs(pid) == PropertyIDs.CONTACT_STATE:
41                    sensor.detected = value == "1"
42                if PropertyIDs(pid) == PropertyIDs.MOTION_STATE:
43                    sensor.detected = value == "1"
44            except ValueError:
45                pass
46
47        return sensor
48
49    async def register_for_updates(self, sensor: Sensor, callback: Callable[[Sensor], None]):
50        _LOGGER.debug(f"Registering sensor: {sensor.nickname} for updates")
51        loop = asyncio.get_event_loop()
52        if not self._updater_thread:
53            self._updater_thread = Thread(target=self.update_worker, args=[loop, ], daemon=True)
54            self._updater_thread.start()
55
56        self._subscribers.append((sensor, callback))
57
58    async def deregister_for_updates(self, sensor: Sensor):
59        self._subscribers = [(sense, callback) for sense, callback in self._subscribers if sense.mac != sensor.mac]
60
61    def update_worker(self, loop):
62        while True:
63            for sensor, callback in self._subscribers:
64                _LOGGER.debug(f"Providing update for {sensor.nickname}")
65                try:
66                    callback(asyncio.run_coroutine_threadsafe(self.update(sensor), loop).result())
67                except UnknownApiError as e:
68                    _LOGGER.warning(f"The update method detected an UnknownApiError: {e}")
69                except ClientOSError as e:
70                    _LOGGER.error(f"A network error was detected: {e}")
71                except ContentTypeError as e:
72                    _LOGGER.error(f"Server returned unexpected ContentType: {e}")
73
74    async def get_sensors(self) -> List[Sensor]:
75        if self._devices is None:
76            self._devices = await self.get_object_list()
77
78        sensors = [Sensor(device.raw_dict) for device in self._devices if
79                   device.type is DeviceTypes.MOTION_SENSOR or
80                   device.type is DeviceTypes.CONTACT_SENSOR]
81        return [Sensor(sensor.raw_dict) for sensor in sensors]

Base service class for interacting with Wyze devices.

async def update( self, sensor: Sensor) -> Sensor:
29    async def update(self, sensor: Sensor) -> Sensor:
30        # Get updated device_params
31        async with BaseService._update_lock:
32            sensor.device_params = await self.get_updated_params(sensor.mac)
33        properties = await self._get_device_info(sensor)
34
35        for property in properties['data']['property_list']:
36            pid = property['pid']
37            value = property['value']
38
39            try:
40                if PropertyIDs(pid) == PropertyIDs.CONTACT_STATE:
41                    sensor.detected = value == "1"
42                if PropertyIDs(pid) == PropertyIDs.MOTION_STATE:
43                    sensor.detected = value == "1"
44            except ValueError:
45                pass
46
47        return sensor
async def register_for_updates( self, sensor: Sensor, callback: Callable[[Sensor], NoneType]):
49    async def register_for_updates(self, sensor: Sensor, callback: Callable[[Sensor], None]):
50        _LOGGER.debug(f"Registering sensor: {sensor.nickname} for updates")
51        loop = asyncio.get_event_loop()
52        if not self._updater_thread:
53            self._updater_thread = Thread(target=self.update_worker, args=[loop, ], daemon=True)
54            self._updater_thread.start()
55
56        self._subscribers.append((sensor, callback))
async def deregister_for_updates(self, sensor: Sensor):
58    async def deregister_for_updates(self, sensor: Sensor):
59        self._subscribers = [(sense, callback) for sense, callback in self._subscribers if sense.mac != sensor.mac]
def update_worker(self, loop):
61    def update_worker(self, loop):
62        while True:
63            for sensor, callback in self._subscribers:
64                _LOGGER.debug(f"Providing update for {sensor.nickname}")
65                try:
66                    callback(asyncio.run_coroutine_threadsafe(self.update(sensor), loop).result())
67                except UnknownApiError as e:
68                    _LOGGER.warning(f"The update method detected an UnknownApiError: {e}")
69                except ClientOSError as e:
70                    _LOGGER.error(f"A network error was detected: {e}")
71                except ContentTypeError as e:
72                    _LOGGER.error(f"Server returned unexpected ContentType: {e}")
async def get_sensors(self) -> List[Sensor]:
74    async def get_sensors(self) -> List[Sensor]:
75        if self._devices is None:
76            self._devices = await self.get_object_list()
77
78        sensors = [Sensor(device.raw_dict) for device in self._devices if
79                   device.type is DeviceTypes.MOTION_SENSOR or
80                   device.type is DeviceTypes.CONTACT_SENSOR]
81        return [Sensor(sensor.raw_dict) for sensor in sensors]