wyzeapy.services.camera_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
  8import time
  9from threading import Thread
 10from typing import Any, List, Optional, Dict, Callable, Tuple
 11
 12from aiohttp import ClientOSError, ContentTypeError
 13
 14from ..exceptions import UnknownApiError
 15from .base_service import BaseService
 16from .update_manager import DeviceUpdater
 17from ..types import Device, DeviceTypes, Event, PropertyIDs, DeviceMgmtToggleProps
 18from ..utils import return_event_for_device, create_pid_pair
 19
 20_LOGGER = logging.getLogger(__name__)
 21
 22# NOTE: Make sure to also define props in devicemgmt_create_capabilities_payload()
 23DEVICEMGMT_API_MODELS = ["LD_CFP", "AN_RSCW", "GW_GC1"] # Floodlight pro, battery cam pro, and OG use a diffrent api (devicemgmt)
 24
 25
 26class Camera(Device):
 27    def __init__(self, dictionary: Dict[Any, Any]):
 28        super().__init__(dictionary)
 29
 30        self.last_event: Optional[Event] = None
 31        self.last_event_ts: int = int(time.time() * 1000)
 32        self.on: bool = True
 33        self.siren: bool = False
 34        self.floodlight: bool = False
 35        self.garage: bool = False
 36
 37
 38class CameraService(BaseService):
 39    _updater_thread: Optional[Thread] = None
 40    _subscribers: List[Tuple[Camera, Callable[[Camera], None]]] = []
 41
 42    async def update(self, camera: Camera):
 43        # Get updated device_params
 44        async with BaseService._update_lock:
 45            camera.device_params = await self.get_updated_params(camera.mac)
 46
 47        # Get camera events
 48        response = await self._get_event_list(10)
 49        raw_events = response['data']['event_list']
 50        latest_events = [Event(raw_event) for raw_event in raw_events]
 51
 52        if (event := return_event_for_device(camera, latest_events)) is not None:
 53            camera.last_event = event
 54            camera.last_event_ts = event.event_ts
 55
 56        # Update camera state
 57        if (camera.product_model in DEVICEMGMT_API_MODELS): # New api
 58            state_response: Dict[str, Any] = await self._get_iot_prop_devicemgmt(camera)
 59            for propCategory in state_response['data']['capabilities']:
 60                if propCategory['name'] == "camera":
 61                    camera.motion = propCategory['properties']['motion-detect-recording']
 62                if propCategory['name'] == "floodlight" or propCategory['name'] == "spotlight":
 63                    camera.floodlight = propCategory['properties']['on']
 64                if propCategory['name'] == "siren":
 65                    camera.siren = propCategory['properties']['state']
 66                if propCategory['name'] == "iot-device":
 67                    camera.notify = propCategory['properties']['push-switch']
 68                    camera.on = propCategory['properties']['iot-power']
 69                    camera.available = propCategory['properties']['iot-state']
 70
 71        else: # All other cam types (old api?)
 72            state_response: List[Tuple[PropertyIDs, Any]] = await self._get_property_list(camera)
 73            for property, value in state_response:
 74                if property is PropertyIDs.AVAILABLE:
 75                    camera.available = value == "1"
 76                if property is PropertyIDs.ON:
 77                    camera.on = value == "1"
 78                if property is PropertyIDs.CAMERA_SIREN:
 79                    camera.siren = value == "1"
 80                if property is PropertyIDs.ACCESSORY:
 81                    camera.floodlight = value == "1"
 82                    if camera.device_params["dongle_product_model"] == "HL_CGDC":
 83                        camera.garage = value == "1" # 1 = open, 2 = closed by automation or smart platform (Alexa, Google Home, Rules), 0 = closed by app
 84                if property is PropertyIDs.NOTIFICATION:
 85                    camera.notify = value == "1"
 86                if property is PropertyIDs.MOTION_DETECTION:
 87                    camera.motion = value == "1"
 88
 89        return camera
 90
 91    async def register_for_updates(self, camera: Camera, callback: Callable[[Camera], None]):
 92        loop = asyncio.get_event_loop()
 93        if not self._updater_thread:
 94            self._updater_thread = Thread(target=self.update_worker, args=[loop, ], daemon=True)
 95            self._updater_thread.start()
 96
 97        self._subscribers.append((camera, callback))
 98
 99    async def deregister_for_updates(self, camera: Camera):
100        self._subscribers = [(cam, callback) for cam, callback in self._subscribers if cam.mac != camera.mac]
101
102    def update_worker(self, loop):
103        while True:
104            if len(self._subscribers) < 1:
105                time.sleep(0.1)
106            else:
107                for camera, callback in self._subscribers:
108                    try:
109                        callback(asyncio.run_coroutine_threadsafe(self.update(camera), loop).result())
110                    except UnknownApiError as e:
111                        _LOGGER.warning(f"The update method detected an UnknownApiError: {e}")
112                    except ClientOSError as e:
113                        _LOGGER.error(f"A network error was detected: {e}")
114                    except ContentTypeError as e:
115                        _LOGGER.error(f"Server returned unexpected ContentType: {e}")
116
117    async def get_cameras(self) -> List[Camera]:
118        if self._devices is None:
119            self._devices = await self.get_object_list()
120
121        cameras = [device for device in self._devices if device.type is DeviceTypes.CAMERA]
122
123        return [Camera(camera.raw_dict) for camera in cameras]
124
125    async def turn_on(self, camera: Camera):
126        if (camera.product_model in DEVICEMGMT_API_MODELS): await self._run_action_devicemgmt(camera, "power", "wakeup") # Some camera models use a diffrent api
127        else: await self._run_action(camera, "power_on")
128
129    async def turn_off(self, camera: Camera):
130        if (camera.product_model in DEVICEMGMT_API_MODELS): await self._run_action_devicemgmt(camera, "power", "sleep") # Some camera models use a diffrent api
131        else: await self._run_action(camera, "power_off")
132
133    async def siren_on(self, camera: Camera):
134        if (camera.product_model in DEVICEMGMT_API_MODELS): await self._run_action_devicemgmt(camera, "siren", "siren-on") # Some camera models use a diffrent api
135        else: await self._run_action(camera, "siren_on")
136
137    async def siren_off(self, camera: Camera):
138        if (camera.product_model in DEVICEMGMT_API_MODELS): await self._run_action_devicemgmt(camera, "siren", "siren-off") # Some camera models use a diffrent api
139        else: await self._run_action(camera, "siren_off")
140
141    # Also controls lamp socket and BCP spotlight
142    async def floodlight_on(self, camera: Camera):
143        if (camera.product_model == "AN_RSCW"): await self._run_action_devicemgmt(camera, "spotlight", "1") # Battery cam pro integrated spotlight is controllable
144        elif (camera.product_model in DEVICEMGMT_API_MODELS): await self._run_action_devicemgmt(camera, "floodlight", "1") # Some camera models use a diffrent api
145        else: await self._set_property(camera, PropertyIDs.ACCESSORY.value, "1")
146
147    # Also controls lamp socket and BCP spotlight
148    async def floodlight_off(self, camera: Camera):
149        if (camera.product_model == "AN_RSCW"): await self._run_action_devicemgmt(camera, "spotlight", "0") # Battery cam pro integrated spotlight is controllable
150        elif (camera.product_model in DEVICEMGMT_API_MODELS): await self._run_action_devicemgmt(camera, "floodlight", "0") # Some camera models use a diffrent api
151        else: await self._set_property(camera, PropertyIDs.ACCESSORY.value, "2")
152
153    # Garage door trigger uses run action on all models
154    async def garage_door_open(self, camera: Camera):
155        await self._run_action(camera, "garage_door_trigger")
156    
157    async def garage_door_close(self, camera: Camera):
158        await self._run_action(camera, "garage_door_trigger")
159
160    async def turn_on_notifications(self, camera: Camera):
161        if (camera.product_model in DEVICEMGMT_API_MODELS): await self._set_toggle(camera, DeviceMgmtToggleProps.NOTIFICATION_TOGGLE.value, "1")
162        else: await self._set_property(camera, PropertyIDs.NOTIFICATION.value, "1")
163
164    async def turn_off_notifications(self, camera: Camera):
165        if (camera.product_model in DEVICEMGMT_API_MODELS): await self._set_toggle(camera, DeviceMgmtToggleProps.NOTIFICATION_TOGGLE.value, "0")
166        else: await self._set_property(camera, PropertyIDs.NOTIFICATION.value, "0")
167
168    # Both properties need to be set on newer cams, older cameras seem to only react
169    # to the first property but it doesnt hurt to set both
170    async def turn_on_motion_detection(self, camera: Camera):
171        if (camera.product_model in DEVICEMGMT_API_MODELS): await self._set_toggle(camera, DeviceMgmtToggleProps.EVENT_RECORDING_TOGGLE.value, "1")
172        elif (camera.product_model in ["WVOD1", "HL_WCO2"]): await self._set_property_list(camera, [create_pid_pair(PropertyIDs.WCO_MOTION_DETECTION, "1")])
173        else:
174            await self._set_property(camera, PropertyIDs.MOTION_DETECTION.value, "1")
175            await self._set_property(camera, PropertyIDs.MOTION_DETECTION_TOGGLE.value, "1")
176
177    async def turn_off_motion_detection(self, camera: Camera):
178        if (camera.product_model in DEVICEMGMT_API_MODELS): await self._set_toggle(camera, DeviceMgmtToggleProps.EVENT_RECORDING_TOGGLE.value, "0")
179        elif (camera.product_model in ["WVOD1", "HL_WCO2"]): await self._set_property_list(camera, [create_pid_pair(PropertyIDs.WCO_MOTION_DETECTION, "0")])
180        else:
181            await self._set_property(camera, PropertyIDs.MOTION_DETECTION.value, "0")
182            await self._set_property(camera, PropertyIDs.MOTION_DETECTION_TOGGLE.value, "0")
DEVICEMGMT_API_MODELS = ['LD_CFP', 'AN_RSCW', 'GW_GC1']
class Camera(wyzeapy.types.Device):
27class Camera(Device):
28    def __init__(self, dictionary: Dict[Any, Any]):
29        super().__init__(dictionary)
30
31        self.last_event: Optional[Event] = None
32        self.last_event_ts: int = int(time.time() * 1000)
33        self.on: bool = True
34        self.siren: bool = False
35        self.floodlight: bool = False
36        self.garage: bool = False
Camera(dictionary: Dict[Any, Any])
28    def __init__(self, dictionary: Dict[Any, Any]):
29        super().__init__(dictionary)
30
31        self.last_event: Optional[Event] = None
32        self.last_event_ts: int = int(time.time() * 1000)
33        self.on: bool = True
34        self.siren: bool = False
35        self.floodlight: bool = False
36        self.garage: bool = False
last_event: Optional[wyzeapy.types.Event]
last_event_ts: int
on: bool
siren: bool
floodlight: bool
garage: bool
class CameraService(wyzeapy.services.base_service.BaseService):
 39class CameraService(BaseService):
 40    _updater_thread: Optional[Thread] = None
 41    _subscribers: List[Tuple[Camera, Callable[[Camera], None]]] = []
 42
 43    async def update(self, camera: Camera):
 44        # Get updated device_params
 45        async with BaseService._update_lock:
 46            camera.device_params = await self.get_updated_params(camera.mac)
 47
 48        # Get camera events
 49        response = await self._get_event_list(10)
 50        raw_events = response['data']['event_list']
 51        latest_events = [Event(raw_event) for raw_event in raw_events]
 52
 53        if (event := return_event_for_device(camera, latest_events)) is not None:
 54            camera.last_event = event
 55            camera.last_event_ts = event.event_ts
 56
 57        # Update camera state
 58        if (camera.product_model in DEVICEMGMT_API_MODELS): # New api
 59            state_response: Dict[str, Any] = await self._get_iot_prop_devicemgmt(camera)
 60            for propCategory in state_response['data']['capabilities']:
 61                if propCategory['name'] == "camera":
 62                    camera.motion = propCategory['properties']['motion-detect-recording']
 63                if propCategory['name'] == "floodlight" or propCategory['name'] == "spotlight":
 64                    camera.floodlight = propCategory['properties']['on']
 65                if propCategory['name'] == "siren":
 66                    camera.siren = propCategory['properties']['state']
 67                if propCategory['name'] == "iot-device":
 68                    camera.notify = propCategory['properties']['push-switch']
 69                    camera.on = propCategory['properties']['iot-power']
 70                    camera.available = propCategory['properties']['iot-state']
 71
 72        else: # All other cam types (old api?)
 73            state_response: List[Tuple[PropertyIDs, Any]] = await self._get_property_list(camera)
 74            for property, value in state_response:
 75                if property is PropertyIDs.AVAILABLE:
 76                    camera.available = value == "1"
 77                if property is PropertyIDs.ON:
 78                    camera.on = value == "1"
 79                if property is PropertyIDs.CAMERA_SIREN:
 80                    camera.siren = value == "1"
 81                if property is PropertyIDs.ACCESSORY:
 82                    camera.floodlight = value == "1"
 83                    if camera.device_params["dongle_product_model"] == "HL_CGDC":
 84                        camera.garage = value == "1" # 1 = open, 2 = closed by automation or smart platform (Alexa, Google Home, Rules), 0 = closed by app
 85                if property is PropertyIDs.NOTIFICATION:
 86                    camera.notify = value == "1"
 87                if property is PropertyIDs.MOTION_DETECTION:
 88                    camera.motion = value == "1"
 89
 90        return camera
 91
 92    async def register_for_updates(self, camera: Camera, callback: Callable[[Camera], None]):
 93        loop = asyncio.get_event_loop()
 94        if not self._updater_thread:
 95            self._updater_thread = Thread(target=self.update_worker, args=[loop, ], daemon=True)
 96            self._updater_thread.start()
 97
 98        self._subscribers.append((camera, callback))
 99
100    async def deregister_for_updates(self, camera: Camera):
101        self._subscribers = [(cam, callback) for cam, callback in self._subscribers if cam.mac != camera.mac]
102
103    def update_worker(self, loop):
104        while True:
105            if len(self._subscribers) < 1:
106                time.sleep(0.1)
107            else:
108                for camera, callback in self._subscribers:
109                    try:
110                        callback(asyncio.run_coroutine_threadsafe(self.update(camera), loop).result())
111                    except UnknownApiError as e:
112                        _LOGGER.warning(f"The update method detected an UnknownApiError: {e}")
113                    except ClientOSError as e:
114                        _LOGGER.error(f"A network error was detected: {e}")
115                    except ContentTypeError as e:
116                        _LOGGER.error(f"Server returned unexpected ContentType: {e}")
117
118    async def get_cameras(self) -> List[Camera]:
119        if self._devices is None:
120            self._devices = await self.get_object_list()
121
122        cameras = [device for device in self._devices if device.type is DeviceTypes.CAMERA]
123
124        return [Camera(camera.raw_dict) for camera in cameras]
125
126    async def turn_on(self, camera: Camera):
127        if (camera.product_model in DEVICEMGMT_API_MODELS): await self._run_action_devicemgmt(camera, "power", "wakeup") # Some camera models use a diffrent api
128        else: await self._run_action(camera, "power_on")
129
130    async def turn_off(self, camera: Camera):
131        if (camera.product_model in DEVICEMGMT_API_MODELS): await self._run_action_devicemgmt(camera, "power", "sleep") # Some camera models use a diffrent api
132        else: await self._run_action(camera, "power_off")
133
134    async def siren_on(self, camera: Camera):
135        if (camera.product_model in DEVICEMGMT_API_MODELS): await self._run_action_devicemgmt(camera, "siren", "siren-on") # Some camera models use a diffrent api
136        else: await self._run_action(camera, "siren_on")
137
138    async def siren_off(self, camera: Camera):
139        if (camera.product_model in DEVICEMGMT_API_MODELS): await self._run_action_devicemgmt(camera, "siren", "siren-off") # Some camera models use a diffrent api
140        else: await self._run_action(camera, "siren_off")
141
142    # Also controls lamp socket and BCP spotlight
143    async def floodlight_on(self, camera: Camera):
144        if (camera.product_model == "AN_RSCW"): await self._run_action_devicemgmt(camera, "spotlight", "1") # Battery cam pro integrated spotlight is controllable
145        elif (camera.product_model in DEVICEMGMT_API_MODELS): await self._run_action_devicemgmt(camera, "floodlight", "1") # Some camera models use a diffrent api
146        else: await self._set_property(camera, PropertyIDs.ACCESSORY.value, "1")
147
148    # Also controls lamp socket and BCP spotlight
149    async def floodlight_off(self, camera: Camera):
150        if (camera.product_model == "AN_RSCW"): await self._run_action_devicemgmt(camera, "spotlight", "0") # Battery cam pro integrated spotlight is controllable
151        elif (camera.product_model in DEVICEMGMT_API_MODELS): await self._run_action_devicemgmt(camera, "floodlight", "0") # Some camera models use a diffrent api
152        else: await self._set_property(camera, PropertyIDs.ACCESSORY.value, "2")
153
154    # Garage door trigger uses run action on all models
155    async def garage_door_open(self, camera: Camera):
156        await self._run_action(camera, "garage_door_trigger")
157    
158    async def garage_door_close(self, camera: Camera):
159        await self._run_action(camera, "garage_door_trigger")
160
161    async def turn_on_notifications(self, camera: Camera):
162        if (camera.product_model in DEVICEMGMT_API_MODELS): await self._set_toggle(camera, DeviceMgmtToggleProps.NOTIFICATION_TOGGLE.value, "1")
163        else: await self._set_property(camera, PropertyIDs.NOTIFICATION.value, "1")
164
165    async def turn_off_notifications(self, camera: Camera):
166        if (camera.product_model in DEVICEMGMT_API_MODELS): await self._set_toggle(camera, DeviceMgmtToggleProps.NOTIFICATION_TOGGLE.value, "0")
167        else: await self._set_property(camera, PropertyIDs.NOTIFICATION.value, "0")
168
169    # Both properties need to be set on newer cams, older cameras seem to only react
170    # to the first property but it doesnt hurt to set both
171    async def turn_on_motion_detection(self, camera: Camera):
172        if (camera.product_model in DEVICEMGMT_API_MODELS): await self._set_toggle(camera, DeviceMgmtToggleProps.EVENT_RECORDING_TOGGLE.value, "1")
173        elif (camera.product_model in ["WVOD1", "HL_WCO2"]): await self._set_property_list(camera, [create_pid_pair(PropertyIDs.WCO_MOTION_DETECTION, "1")])
174        else:
175            await self._set_property(camera, PropertyIDs.MOTION_DETECTION.value, "1")
176            await self._set_property(camera, PropertyIDs.MOTION_DETECTION_TOGGLE.value, "1")
177
178    async def turn_off_motion_detection(self, camera: Camera):
179        if (camera.product_model in DEVICEMGMT_API_MODELS): await self._set_toggle(camera, DeviceMgmtToggleProps.EVENT_RECORDING_TOGGLE.value, "0")
180        elif (camera.product_model in ["WVOD1", "HL_WCO2"]): await self._set_property_list(camera, [create_pid_pair(PropertyIDs.WCO_MOTION_DETECTION, "0")])
181        else:
182            await self._set_property(camera, PropertyIDs.MOTION_DETECTION.value, "0")
183            await self._set_property(camera, PropertyIDs.MOTION_DETECTION_TOGGLE.value, "0")

Base service class for interacting with Wyze devices.

async def update(self, camera: Camera):
43    async def update(self, camera: Camera):
44        # Get updated device_params
45        async with BaseService._update_lock:
46            camera.device_params = await self.get_updated_params(camera.mac)
47
48        # Get camera events
49        response = await self._get_event_list(10)
50        raw_events = response['data']['event_list']
51        latest_events = [Event(raw_event) for raw_event in raw_events]
52
53        if (event := return_event_for_device(camera, latest_events)) is not None:
54            camera.last_event = event
55            camera.last_event_ts = event.event_ts
56
57        # Update camera state
58        if (camera.product_model in DEVICEMGMT_API_MODELS): # New api
59            state_response: Dict[str, Any] = await self._get_iot_prop_devicemgmt(camera)
60            for propCategory in state_response['data']['capabilities']:
61                if propCategory['name'] == "camera":
62                    camera.motion = propCategory['properties']['motion-detect-recording']
63                if propCategory['name'] == "floodlight" or propCategory['name'] == "spotlight":
64                    camera.floodlight = propCategory['properties']['on']
65                if propCategory['name'] == "siren":
66                    camera.siren = propCategory['properties']['state']
67                if propCategory['name'] == "iot-device":
68                    camera.notify = propCategory['properties']['push-switch']
69                    camera.on = propCategory['properties']['iot-power']
70                    camera.available = propCategory['properties']['iot-state']
71
72        else: # All other cam types (old api?)
73            state_response: List[Tuple[PropertyIDs, Any]] = await self._get_property_list(camera)
74            for property, value in state_response:
75                if property is PropertyIDs.AVAILABLE:
76                    camera.available = value == "1"
77                if property is PropertyIDs.ON:
78                    camera.on = value == "1"
79                if property is PropertyIDs.CAMERA_SIREN:
80                    camera.siren = value == "1"
81                if property is PropertyIDs.ACCESSORY:
82                    camera.floodlight = value == "1"
83                    if camera.device_params["dongle_product_model"] == "HL_CGDC":
84                        camera.garage = value == "1" # 1 = open, 2 = closed by automation or smart platform (Alexa, Google Home, Rules), 0 = closed by app
85                if property is PropertyIDs.NOTIFICATION:
86                    camera.notify = value == "1"
87                if property is PropertyIDs.MOTION_DETECTION:
88                    camera.motion = value == "1"
89
90        return camera
async def register_for_updates( self, camera: Camera, callback: Callable[[Camera], NoneType]):
92    async def register_for_updates(self, camera: Camera, callback: Callable[[Camera], None]):
93        loop = asyncio.get_event_loop()
94        if not self._updater_thread:
95            self._updater_thread = Thread(target=self.update_worker, args=[loop, ], daemon=True)
96            self._updater_thread.start()
97
98        self._subscribers.append((camera, callback))
async def deregister_for_updates(self, camera: Camera):
100    async def deregister_for_updates(self, camera: Camera):
101        self._subscribers = [(cam, callback) for cam, callback in self._subscribers if cam.mac != camera.mac]
def update_worker(self, loop):
103    def update_worker(self, loop):
104        while True:
105            if len(self._subscribers) < 1:
106                time.sleep(0.1)
107            else:
108                for camera, callback in self._subscribers:
109                    try:
110                        callback(asyncio.run_coroutine_threadsafe(self.update(camera), loop).result())
111                    except UnknownApiError as e:
112                        _LOGGER.warning(f"The update method detected an UnknownApiError: {e}")
113                    except ClientOSError as e:
114                        _LOGGER.error(f"A network error was detected: {e}")
115                    except ContentTypeError as e:
116                        _LOGGER.error(f"Server returned unexpected ContentType: {e}")
async def get_cameras(self) -> List[Camera]:
118    async def get_cameras(self) -> List[Camera]:
119        if self._devices is None:
120            self._devices = await self.get_object_list()
121
122        cameras = [device for device in self._devices if device.type is DeviceTypes.CAMERA]
123
124        return [Camera(camera.raw_dict) for camera in cameras]
async def turn_on(self, camera: Camera):
126    async def turn_on(self, camera: Camera):
127        if (camera.product_model in DEVICEMGMT_API_MODELS): await self._run_action_devicemgmt(camera, "power", "wakeup") # Some camera models use a diffrent api
128        else: await self._run_action(camera, "power_on")
async def turn_off(self, camera: Camera):
130    async def turn_off(self, camera: Camera):
131        if (camera.product_model in DEVICEMGMT_API_MODELS): await self._run_action_devicemgmt(camera, "power", "sleep") # Some camera models use a diffrent api
132        else: await self._run_action(camera, "power_off")
async def siren_on(self, camera: Camera):
134    async def siren_on(self, camera: Camera):
135        if (camera.product_model in DEVICEMGMT_API_MODELS): await self._run_action_devicemgmt(camera, "siren", "siren-on") # Some camera models use a diffrent api
136        else: await self._run_action(camera, "siren_on")
async def siren_off(self, camera: Camera):
138    async def siren_off(self, camera: Camera):
139        if (camera.product_model in DEVICEMGMT_API_MODELS): await self._run_action_devicemgmt(camera, "siren", "siren-off") # Some camera models use a diffrent api
140        else: await self._run_action(camera, "siren_off")
async def floodlight_on(self, camera: Camera):
143    async def floodlight_on(self, camera: Camera):
144        if (camera.product_model == "AN_RSCW"): await self._run_action_devicemgmt(camera, "spotlight", "1") # Battery cam pro integrated spotlight is controllable
145        elif (camera.product_model in DEVICEMGMT_API_MODELS): await self._run_action_devicemgmt(camera, "floodlight", "1") # Some camera models use a diffrent api
146        else: await self._set_property(camera, PropertyIDs.ACCESSORY.value, "1")
async def floodlight_off(self, camera: Camera):
149    async def floodlight_off(self, camera: Camera):
150        if (camera.product_model == "AN_RSCW"): await self._run_action_devicemgmt(camera, "spotlight", "0") # Battery cam pro integrated spotlight is controllable
151        elif (camera.product_model in DEVICEMGMT_API_MODELS): await self._run_action_devicemgmt(camera, "floodlight", "0") # Some camera models use a diffrent api
152        else: await self._set_property(camera, PropertyIDs.ACCESSORY.value, "2")
async def garage_door_open(self, camera: Camera):
155    async def garage_door_open(self, camera: Camera):
156        await self._run_action(camera, "garage_door_trigger")
async def garage_door_close(self, camera: Camera):
158    async def garage_door_close(self, camera: Camera):
159        await self._run_action(camera, "garage_door_trigger")
async def turn_on_notifications(self, camera: Camera):
161    async def turn_on_notifications(self, camera: Camera):
162        if (camera.product_model in DEVICEMGMT_API_MODELS): await self._set_toggle(camera, DeviceMgmtToggleProps.NOTIFICATION_TOGGLE.value, "1")
163        else: await self._set_property(camera, PropertyIDs.NOTIFICATION.value, "1")
async def turn_off_notifications(self, camera: Camera):
165    async def turn_off_notifications(self, camera: Camera):
166        if (camera.product_model in DEVICEMGMT_API_MODELS): await self._set_toggle(camera, DeviceMgmtToggleProps.NOTIFICATION_TOGGLE.value, "0")
167        else: await self._set_property(camera, PropertyIDs.NOTIFICATION.value, "0")
async def turn_on_motion_detection(self, camera: Camera):
171    async def turn_on_motion_detection(self, camera: Camera):
172        if (camera.product_model in DEVICEMGMT_API_MODELS): await self._set_toggle(camera, DeviceMgmtToggleProps.EVENT_RECORDING_TOGGLE.value, "1")
173        elif (camera.product_model in ["WVOD1", "HL_WCO2"]): await self._set_property_list(camera, [create_pid_pair(PropertyIDs.WCO_MOTION_DETECTION, "1")])
174        else:
175            await self._set_property(camera, PropertyIDs.MOTION_DETECTION.value, "1")
176            await self._set_property(camera, PropertyIDs.MOTION_DETECTION_TOGGLE.value, "1")
async def turn_off_motion_detection(self, camera: Camera):
178    async def turn_off_motion_detection(self, camera: Camera):
179        if (camera.product_model in DEVICEMGMT_API_MODELS): await self._set_toggle(camera, DeviceMgmtToggleProps.EVENT_RECORDING_TOGGLE.value, "0")
180        elif (camera.product_model in ["WVOD1", "HL_WCO2"]): await self._set_property_list(camera, [create_pid_pair(PropertyIDs.WCO_MOTION_DETECTION, "0")])
181        else:
182            await self._set_property(camera, PropertyIDs.MOTION_DETECTION.value, "0")
183            await self._set_property(camera, PropertyIDs.MOTION_DETECTION_TOGGLE.value, "0")