wyzeapy.services.base_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 json
  8import logging
  9import time
 10from typing import List, Tuple, Any, Dict, Optional
 11
 12import aiohttp
 13
 14from .update_manager import DeviceUpdater, UpdateManager
 15from ..const import PHONE_SYSTEM_TYPE, APP_VERSION, APP_VER, PHONE_ID, APP_NAME, OLIVE_APP_ID, APP_INFO, SC, SV, APP_PLATFORM, SOURCE
 16from ..crypto import olive_create_signature
 17from ..payload_factory import olive_create_hms_patch_payload, olive_create_hms_payload, \
 18    olive_create_hms_get_payload, ford_create_payload, olive_create_get_payload, olive_create_post_payload, \
 19    olive_create_user_info_payload, devicemgmt_create_capabilities_payload, devicemgmt_get_iot_props_list
 20from ..types import PropertyIDs, Device, DeviceMgmtToggleType
 21from ..utils import check_for_errors_standard, check_for_errors_hms, check_for_errors_lock, \
 22    check_for_errors_iot, wyze_encrypt, check_for_errors_devicemgmt
 23from ..wyze_auth_lib import WyzeAuthLib
 24
 25_LOGGER = logging.getLogger(__name__)
 26
 27
 28class BaseService:
 29    """Base service class for interacting with Wyze devices."""
 30    _devices: Optional[List[Device]] = None
 31    _last_updated_time: time = 0  # preload a value of 0 so that comparison will succeed on the first run
 32    _min_update_time = 1200  # lets let the device_params update every 20 minutes for now. This could probably reduced signicficantly.
 33    _update_lock: asyncio.Lock = asyncio.Lock() # fmt: skip
 34    _update_manager: UpdateManager = UpdateManager()
 35    _update_loop = None
 36    _updater: DeviceUpdater = None
 37    _updater_dict = {}
 38
 39    def __init__(self, auth_lib: WyzeAuthLib):
 40        """Initialize the base service."""
 41        self._auth_lib = auth_lib
 42
 43    @staticmethod
 44    async def start_update_manager():
 45        """Start the update manager."""
 46        if BaseService._update_loop is None:
 47            BaseService._update_loop = asyncio.get_event_loop()
 48            BaseService._update_loop.create_task(BaseService._update_manager.update_next())
 49
 50    def register_updater(self, device: Device, interval):
 51        """
 52        Register a device to be updated at a specific interval.
 53
 54        Parameters
 55        ----------
 56        device : Device
 57            The device to register.
 58        interval : int
 59            The interval in seconds.
 60        """
 61        self._updater = DeviceUpdater(self, device, interval)
 62        BaseService._update_manager.add_updater(self._updater)
 63        self._updater_dict[self._updater.device] = self._updater
 64
 65    def unregister_updater(self, device: Device):
 66        if self._updater:
 67            BaseService._update_manager.del_updater(self._updater_dict[device])
 68            del self._updater_dict[device]
 69
 70    async def set_push_info(self, on: bool) -> None:
 71        await self._auth_lib.refresh_if_should()
 72
 73        url = "https://api.wyzecam.com/app/user/set_push_info"
 74        payload = {
 75            "phone_system_type": PHONE_SYSTEM_TYPE,
 76            "app_version": APP_VERSION,
 77            "app_ver": APP_VER,
 78            "push_switch": "1" if on else "2",
 79            "sc": SC,
 80            "ts": int(time.time()),
 81            "sv": SV,
 82            "access_token": self._auth_lib.token.access_token,
 83            "phone_id": PHONE_ID,
 84            "app_name": APP_NAME
 85        }
 86
 87        response_json = await self._auth_lib.post(url, json=payload)
 88
 89        check_for_errors_standard(self, response_json)
 90
 91    async def get_user_profile(self) -> Dict[Any, Any]:
 92        await self._auth_lib.refresh_if_should()
 93
 94        payload = olive_create_user_info_payload()
 95        signature = olive_create_signature(payload, self._auth_lib.token.access_token)
 96        headers = {
 97            'Accept-Encoding': 'gzip',
 98            'User-Agent': 'myapp',
 99            'appid': OLIVE_APP_ID,
100            'appinfo': APP_INFO,
101            'phoneid': PHONE_ID,
102            'access_token': self._auth_lib.token.access_token,
103            'signature2': signature
104        }
105
106        url = 'https://wyze-platform-service.wyzecam.com/app/v2/platform/get_user_profile'
107
108        response_json = await self._auth_lib.get(url, headers=headers, params=payload)
109
110        return response_json
111
112    async def get_object_list(self) -> List[Device]:
113        """
114        Wraps the api.wyzecam.com/app/v2/home_page/get_object_list endpoint
115
116        :return: List of devices
117        """
118        await self._auth_lib.refresh_if_should()
119
120        payload = {
121            "phone_system_type": PHONE_SYSTEM_TYPE,
122            "app_version": APP_VERSION,
123            "app_ver": APP_VER,
124            "sc": "9f275790cab94a72bd206c8876429f3c",
125            "ts": int(time.time()),
126            "sv": "9d74946e652647e9b6c9d59326aef104",
127            "access_token": self._auth_lib.token.access_token,
128            "phone_id": PHONE_ID,
129            "app_name": APP_NAME
130        }
131
132        response_json = await self._auth_lib.post("https://api.wyzecam.com/app/v2/home_page/get_object_list",
133                                                  json=payload)
134
135        check_for_errors_standard(self, response_json)
136        # Cache the devices so that update calls can pull more recent device_params
137        BaseService._devices = [Device(device) for device in response_json['data']['device_list']]
138
139        return BaseService._devices
140
141    async def get_updated_params(self, device_mac: str = None) -> Dict[str, Optional[Any]]:
142        if time.time() - BaseService._last_updated_time >= BaseService._min_update_time:
143            await self.get_object_list()
144            BaseService._last_updated_time = time.time()
145        ret_params = {}
146        for dev in BaseService._devices:
147            if dev.mac == device_mac:
148                ret_params = dev.device_params
149        return ret_params
150
151    async def _get_property_list(self, device: Device) -> List[Tuple[PropertyIDs, Any]]:
152        """
153        Wraps the api.wyzecam.com/app/v2/device/get_property_list endpoint
154
155        :param device: Device to get properties for
156        :return: List of PropertyIDs and values
157        """
158
159        await self._auth_lib.refresh_if_should()
160
161        payload = {
162            "phone_system_type": PHONE_SYSTEM_TYPE,
163            "app_version": APP_VERSION,
164            "app_ver": APP_VER,
165            "sc": "9f275790cab94a72bd206c8876429f3c",
166            "ts": int(time.time()),
167            "sv": "9d74946e652647e9b6c9d59326aef104",
168            "access_token": self._auth_lib.token.access_token,
169            "phone_id": PHONE_ID,
170            "app_name": APP_NAME,
171            "device_model": device.product_model,
172            "device_mac": device.mac,
173            "target_pid_list": []
174        }
175
176        response_json = await self._auth_lib.post("https://api.wyzecam.com/app/v2/device/get_property_list",
177                                                  json=payload)
178
179        check_for_errors_standard(self, response_json)
180        properties = response_json['data']['property_list']
181        property_list = []
182        for prop in properties:
183            try:
184                property_id = PropertyIDs(prop['pid'])
185                property_list.append((
186                    property_id,
187                    prop['value']
188                ))
189            except ValueError:
190                pass
191
192        return property_list
193
194    async def _set_property_list(self, device: Device, plist: List[Dict[str, str]]) -> None:
195        """
196        Wraps the api.wyzecam.com/app/v2/device/set_property_list endpoint
197
198        :param device: The device for which to set the property(ies)
199        :param plist: A list of properties [{"pid": pid, "pvalue": pvalue},...]
200        :return:
201        """
202
203        await self._auth_lib.refresh_if_should()
204
205        payload = {
206            "phone_system_type": PHONE_SYSTEM_TYPE,
207            "app_version": APP_VERSION,
208            "app_ver": APP_VER,
209            "sc": "9f275790cab94a72bd206c8876429f3c",
210            "ts": int(time.time()),
211            "sv": "9d74946e652647e9b6c9d59326aef104",
212            "access_token": self._auth_lib.token.access_token,
213            "phone_id": PHONE_ID,
214            "app_name": APP_NAME,
215            "property_list": plist,
216            "device_model": device.product_model,
217            "device_mac": device.mac
218        }
219
220        response_json = await self._auth_lib.post("https://api.wyzecam.com/app/v2/device/set_property_list",
221                                                  json=payload)
222
223        check_for_errors_standard(self, response_json)
224
225    async def _run_action_list(self, device: Device, plist: List[Dict[Any, Any]]) -> None:
226        """
227        Wraps the api.wyzecam.com/app/v2/auto/run_action_list endpoint
228
229        :param device: The device for which to run the action list
230        :param plist: A list of properties [{"pid": pid, "pvalue": pvalue},...]
231        """
232        await self._auth_lib.refresh_if_should()
233
234        payload = {
235            "phone_system_type": PHONE_SYSTEM_TYPE,
236            "app_version": APP_VERSION,
237            "app_ver": APP_VER,
238            "sc": "9f275790cab94a72bd206c8876429f3c",
239            "ts": int(time.time()),
240            "sv": "9d74946e652647e9b6c9d59326aef104",
241            "access_token": self._auth_lib.token.access_token,
242            "phone_id": PHONE_ID,
243            "app_name": APP_NAME,
244            "action_list": [
245                {
246                    "instance_id": device.mac,
247                    "action_params": {
248                        "list": [
249                            {
250                                "mac": device.mac,
251                                "plist": plist
252                            }
253                        ]
254                    },
255                    "provider_key": device.product_model,
256                    "action_key": "set_mesh_property"
257                }
258            ]
259        }
260
261        response_json = await self._auth_lib.post("https://api.wyzecam.com/app/v2/auto/run_action_list",
262                                                  json=payload)
263
264        check_for_errors_standard(self, response_json)
265
266    async def _get_event_list(self, count: int) -> Dict[Any, Any]:
267        """
268        Wraps the api.wyzecam.com/app/v2/device/get_event_list endpoint
269
270        :param count: Number of events to gather
271        :return: Response from the server
272        """
273
274        await self._auth_lib.refresh_if_should()
275
276        payload = {
277            "phone_id": PHONE_ID,
278            "begin_time": int((time.time() - (60 * 60)) * 1000),
279            "event_type": "",
280            "app_name": APP_NAME,
281            "count": count,
282            "app_version": APP_VERSION,
283            "order_by": 2,
284            "event_value_list": [
285                "1",
286                "13",
287                "10",
288                "12"
289            ],
290            "sc": "9f275790cab94a72bd206c8876429f3c",
291            "device_mac_list": [],
292            "event_tag_list": [],
293            "sv": "782ced6909a44d92a1f70d582bbe88be",
294            "end_time": int(time.time() * 1000),
295            "phone_system_type": PHONE_SYSTEM_TYPE,
296            "app_ver": APP_VER,
297            "ts": 1623612037763,
298            "device_mac": "",
299            "access_token": self._auth_lib.token.access_token
300        }
301
302        response_json = await self._auth_lib.post("https://api.wyzecam.com/app/v2/device/get_event_list",
303                                                  json=payload)
304
305        check_for_errors_standard(self, response_json)
306        return response_json
307
308    async def _run_action(self, device: Device, action: str) -> None:
309        """
310        Wraps the api.wyzecam.com/app/v2/auto/run_action endpoint
311
312        :param device: The device for which to run the action
313        :param action: The action to run
314        :return:
315        """
316
317        await self._auth_lib.refresh_if_should()
318
319        payload = {
320            "phone_system_type": PHONE_SYSTEM_TYPE,
321            "app_version": APP_VERSION,
322            "app_ver": APP_VER,
323            "sc": "9f275790cab94a72bd206c8876429f3c",
324            "ts": int(time.time()),
325            "sv": "9d74946e652647e9b6c9d59326aef104",
326            "access_token": self._auth_lib.token.access_token,
327            "phone_id": PHONE_ID,
328            "app_name": APP_NAME,
329            "provider_key": device.product_model,
330            "instance_id": device.mac,
331            "action_key": action,
332            "action_params": {},
333            "custom_string": "",
334        }
335
336        response_json = await self._auth_lib.post("https://api.wyzecam.com/app/v2/auto/run_action",
337                                                  json=payload)
338
339        check_for_errors_standard(self, response_json)
340    
341    async def _run_action_devicemgmt(self, device: Device, type: str, value: str) -> None:
342        """
343        Wraps the devicemgmt-service-beta.wyze.com/device-management/api/action/run_action endpoint
344
345        :param device: The device for which to run the action
346        :param state: on or off
347        :return:
348        """
349
350        await self._auth_lib.refresh_if_should()
351
352        capabilities = devicemgmt_create_capabilities_payload(type, value)
353
354        payload = {
355            "capabilities": [
356                capabilities
357            ],
358            "nonce": int(time.time() * 1000),
359            "targetInfo": {
360                "id": device.mac,
361                "productModel": device.product_model,
362                "type": "DEVICE"
363            },
364            "transactionId": "0a5b20591fedd4du1b93f90743ba0csd" # OG cam needs this (doesn't matter what the value is)
365        }
366
367        headers = {
368            "authorization": self._auth_lib.token.access_token,
369        }
370
371        response_json = await self._auth_lib.post("https://devicemgmt-service-beta.wyze.com/device-management/api/action/run_action",
372                                                  json=payload, headers=headers)
373
374        check_for_errors_iot(self, response_json)
375    
376    async def _set_toggle(self, device: Device, toggleType: DeviceMgmtToggleType, state: str) -> None:
377        """
378        Wraps the ai-subscription-service-beta.wyzecam.com/v4/subscription-service/toggle-management endpoint
379
380        :param device: The device for which to get the state
381        :param toggleType: Enum for the toggle type
382        :param state: String state to set for the toggle
383        """
384
385        await self._auth_lib.refresh_if_should()
386
387        payload = {
388            "data": [
389                {
390                    "device_firmware": "1234567890",
391                    "device_id": device.mac,
392                    "device_model": device.product_model,
393                    "page_id": [
394                        toggleType.pageId
395                    ],
396                    "toggle_update": [
397                        {
398                            "toggle_id": toggleType.toggleId,
399                            "toggle_status": state
400                        }
401                    ]
402                }
403            ],
404            "nonce": str(int(time.time() * 1000))
405        }
406
407
408        signature = olive_create_signature(payload, self._auth_lib.token.access_token)
409        headers = {
410            "access_token": self._auth_lib.token.access_token,
411            "timestamp": str(int(time.time() * 1000)),
412            "appid": OLIVE_APP_ID,
413            "source": SOURCE,
414            "signature2": signature,
415            "appplatform": APP_PLATFORM,
416            "appversion": APP_VERSION,
417            "requestid": "35374158s4s313b9a2be7c057f2da5d1"
418        }
419
420        response_json = await self._auth_lib.put("https://ai-subscription-service-beta.wyzecam.com/v4/subscription-service/toggle-management",
421                                                  json=payload, headers=headers)
422        
423        check_for_errors_devicemgmt(self, response_json)
424    
425    async def _get_iot_prop_devicemgmt(self, device: Device) -> Dict[str, Any]:
426        """
427        Wraps the devicemgmt-service-beta.wyze.com/device-management/api/device-property/get_iot_prop endpoint
428
429        :param device: The device for which to get the state
430        :return:
431        """
432
433        await self._auth_lib.refresh_if_should()
434
435        payload = {
436            "capabilities": devicemgmt_get_iot_props_list(device.product_model),
437            "nonce": int(time.time() * 1000),
438            "targetInfo": {
439                "id": device.mac,
440                "productModel": device.product_model,
441                "type": "DEVICE"
442            }
443        }
444
445        headers = {
446            "authorization": self._auth_lib.token.access_token,
447        }
448
449        response_json = await self._auth_lib.post("https://devicemgmt-service-beta.wyze.com/device-management/api/device-property/get_iot_prop",
450                                                  json=payload, headers=headers)
451        
452        check_for_errors_iot(self, response_json)
453
454        return response_json
455
456    async def _set_property(self, device: Device, pid: str, pvalue: str) -> None:
457        """
458        Wraps the api.wyzecam.com/app/v2/device/set_property endpoint
459
460        :param device: The device for which to set the property
461        :param pid: The property id
462        :param pvalue: The property value
463        """
464        await self._auth_lib.refresh_if_should()
465
466        payload = {
467            "phone_system_type": PHONE_SYSTEM_TYPE,
468            "app_version": APP_VERSION,
469            "app_ver": APP_VER,
470            "sc": "9f275790cab94a72bd206c8876429f3c",
471            "ts": int(time.time()),
472            "sv": "9d74946e652647e9b6c9d59326aef104",
473            "access_token": self._auth_lib.token.access_token,
474            "phone_id": PHONE_ID,
475            "app_name": APP_NAME,
476            "pvalue": pvalue,
477            "pid": pid,
478            "device_model": device.product_model,
479            "device_mac": device.mac
480        }
481
482        response_json = await self._auth_lib.post("https://api.wyzecam.com/app/v2/device/set_property",
483                                                  json=payload)
484
485        check_for_errors_standard(self, response_json)
486
487    async def _monitoring_profile_active(self, hms_id: str, home: int, away: int) -> None:
488        """
489        Wraps the hms.api.wyze.com/api/v1/monitoring/v1/profile/active endpoint
490
491        :param hms_id: The hms id
492        :param home: 1 for home 0 for not
493        :param away: 1 for away 0 for not
494        :return:
495        """
496        await self._auth_lib.refresh_if_should()
497
498        url = "https://hms.api.wyze.com/api/v1/monitoring/v1/profile/active"
499        query = olive_create_hms_patch_payload(hms_id)
500        signature = olive_create_signature(query, self._auth_lib.token.access_token)
501        headers = {
502            'Accept-Encoding': 'gzip',
503            'User-Agent': 'myapp',
504            'appid': OLIVE_APP_ID,
505            'appinfo': APP_INFO,
506            'phoneid': PHONE_ID,
507            'access_token': self._auth_lib.token.access_token,
508            'signature2': signature,
509            'Authorization': self._auth_lib.token.access_token
510        }
511        payload = [
512            {
513                "state": "home",
514                "active": home
515            },
516            {
517                "state": "away",
518                "active": away
519            }
520        ]
521        response_json = await self._auth_lib.patch(url, headers=headers, params=query, json=payload)
522        check_for_errors_hms(self, response_json)
523
524    async def _get_plan_binding_list_by_user(self) -> Dict[Any, Any]:
525        """
526        Wraps the wyze-membership-service.wyzecam.com/platform/v2/membership/get_plan_binding_list_by_user endpoint
527
528        :return: The response to gathering the plan for the current user
529        """
530
531        if self._auth_lib.should_refresh:
532            await self._auth_lib.refresh()
533
534        url = "https://wyze-membership-service.wyzecam.com/platform/v2/membership/get_plan_binding_list_by_user"
535        payload = olive_create_hms_payload()
536        signature = olive_create_signature(payload, self._auth_lib.token.access_token)
537        headers = {
538            'Accept-Encoding': 'gzip',
539            'User-Agent': 'myapp',
540            'appid': OLIVE_APP_ID,
541            'appinfo': APP_INFO,
542            'phoneid': PHONE_ID,
543            'access_token': self._auth_lib.token.access_token,
544            'signature2': signature
545        }
546
547        response_json = await self._auth_lib.get(url, headers=headers, params=payload)
548        check_for_errors_hms(self, response_json)
549        return response_json
550
551    async def _disable_reme_alarm(self, hms_id: str) -> None:
552        """
553        Wraps the hms.api.wyze.com/api/v1/reme-alarm endpoint
554
555        :param hms_id: The hms_id for the account
556        """
557        await self._auth_lib.refresh_if_should()
558
559        url = "https://hms.api.wyze.com/api/v1/reme-alarm"
560        payload = {
561            "hms_id": hms_id,
562            "remediation_id": "emergency"
563        }
564        headers = {
565            "Authorization": self._auth_lib.token.access_token
566        }
567
568        response_json = await self._auth_lib.delete(url, headers=headers, json=payload)
569
570        check_for_errors_hms(self, response_json)
571
572    async def _monitoring_profile_state_status(self, hms_id: str) -> Dict[Any, Any]:
573        """
574        Wraps the hms.api.wyze.com/api/v1/monitoring/v1/profile/state-status endpoint
575
576        :param hms_id: The hms_id
577        :return: The response that includes the status
578        """
579        if self._auth_lib.should_refresh:
580            await self._auth_lib.refresh()
581
582        url = "https://hms.api.wyze.com/api/v1/monitoring/v1/profile/state-status"
583        query = olive_create_hms_get_payload(hms_id)
584        signature = olive_create_signature(query, self._auth_lib.token.access_token)
585        headers = {
586            'User-Agent': 'myapp',
587            'appid': OLIVE_APP_ID,
588            'appinfo': APP_INFO,
589            'phoneid': PHONE_ID,
590            'access_token': self._auth_lib.token.access_token,
591            'signature2': signature,
592            'Authorization': self._auth_lib.token.access_token,
593            'Content-Type': "application/json"
594        }
595
596        response_json = await self._auth_lib.get(url, headers=headers, params=query)
597
598        check_for_errors_hms(self, response_json)
599        return response_json
600
601    async def _lock_control(self, device: Device, action: str) -> None:
602        await self._auth_lib.refresh_if_should()
603
604        url_path = "/openapi/lock/v1/control"
605
606        device_uuid = device.mac.split(".")[-1]
607
608        payload = {
609            "uuid": device_uuid,
610            "action": action  # "remoteLock" or "remoteUnlock"
611        }
612        payload = ford_create_payload(self._auth_lib.token.access_token, payload, url_path, "post")
613
614        url = "https://yd-saas-toc.wyzecam.com/openapi/lock/v1/control"
615
616        response_json = await self._auth_lib.post(url, json=payload)
617
618        check_for_errors_lock(self, response_json)
619
620    async def _get_lock_info(self, device: Device) -> Dict[str, Optional[Any]]:
621        await self._auth_lib.refresh_if_should()
622
623        url_path = "/openapi/lock/v1/info"
624
625        device_uuid = device.mac.split(".")[-1]
626
627        payload = {
628            "uuid": device_uuid,
629            "with_keypad": "1"
630        }
631
632        payload = ford_create_payload(self._auth_lib.token.access_token, payload, url_path, "get")
633
634        url = "https://yd-saas-toc.wyzecam.com/openapi/lock/v1/info"
635
636        response_json = await self._auth_lib.get(url, params=payload)
637
638        check_for_errors_lock(self, response_json)
639
640        return response_json
641
642    async def _get_lock_ble_token(self, device: Device) -> Dict[str, Optional[Any]]:
643        await self._auth_lib.refresh_if_should()
644
645        url_path = "/openapi/lock/v1/ble/token"
646
647        payload = {
648            "uuid": device.mac
649        }
650
651        payload = ford_create_payload(self._auth_lib.token.access_token, payload, url_path, "get")
652
653        url = f"https://yd-saas-toc.wyzecam.com{url_path}"
654
655        response_json = await self._auth_lib.get(url, params=payload)
656
657        check_for_errors_lock(self, response_json)
658
659        return response_json
660
661    async def _get_device_info(self, device: Device) -> Dict[Any, Any]:
662        await self._auth_lib.refresh_if_should()
663
664        payload = {
665            "phone_system_type": PHONE_SYSTEM_TYPE,
666            "app_version": APP_VERSION,
667            "app_ver": APP_VER,
668            "device_mac": device.mac,
669            "sc": "9f275790cab94a72bd206c8876429f3c",
670            "ts": int(time.time()),
671            "device_model": device.product_model,
672            "sv": "c86fa16fc99d4d6580f82ef3b942e586",
673            "access_token": self._auth_lib.token.access_token,
674            "phone_id": PHONE_ID,
675            "app_name": APP_NAME
676        }
677
678        response_json = await self._auth_lib.post("https://api.wyzecam.com/app/v2/device/get_device_Info",
679                                                  json=payload)
680
681        check_for_errors_standard(self, response_json)
682
683        return response_json
684
685    async def _get_iot_prop(self, url: str, device: Device, keys: str) -> Dict[Any, Any]:
686        await self._auth_lib.refresh_if_should()
687
688        payload = olive_create_get_payload(device.mac, keys)
689        signature = olive_create_signature(payload, self._auth_lib.token.access_token)
690        headers = {
691            'Accept-Encoding': 'gzip',
692            'User-Agent': 'myapp',
693            'appid': OLIVE_APP_ID,
694            'appinfo': APP_INFO,
695            'phoneid': PHONE_ID,
696            'access_token': self._auth_lib.token.access_token,
697            'signature2': signature
698        }
699
700        response_json = await self._auth_lib.get(url, headers=headers, params=payload)
701
702        check_for_errors_iot(self, response_json)
703
704        return response_json
705
706    async def _set_iot_prop(self, url: str, device: Device, prop_key: str, value: Any) -> None:
707        await self._auth_lib.refresh_if_should()
708
709        payload = olive_create_post_payload(device.mac, device.product_model, prop_key, value)
710        signature = olive_create_signature(json.dumps(payload, separators=(',', ':')),
711                                           self._auth_lib.token.access_token)
712        headers = {
713            'Accept-Encoding': 'gzip',
714            'Content-Type': 'application/json',
715            'User-Agent': 'myapp',
716            'appid': OLIVE_APP_ID,
717            'appinfo': APP_INFO,
718            'phoneid': PHONE_ID,
719            'access_token': self._auth_lib.token.access_token,
720            'signature2': signature
721        }
722
723        payload_str = json.dumps(payload, separators=(',', ':'))
724
725        response_json = await self._auth_lib.post(url, headers=headers, data=payload_str)
726
727        check_for_errors_iot(self, response_json)
728
729    async def _local_bulb_command(self, bulb, plist):
730        # await self._auth_lib.refresh_if_should()
731
732        characteristics = {
733            "mac": bulb.mac.upper(),
734            "index": "1",
735            "ts": str(int(time.time_ns() // 1000000)),
736            "plist": plist
737        }
738
739        characteristics_str = json.dumps(characteristics, separators=(',', ':'))
740        characteristics_enc = wyze_encrypt(bulb.enr, characteristics_str)
741
742        payload = {
743            "request": "set_status",
744            "isSendQueue": 0,
745            "characteristics": characteristics_enc
746        }
747
748        # JSON likes to add a second \ so we have to remove it for the bulb to be happy
749        payload_str = json.dumps(payload, separators=(',', ':')).replace('\\\\', '\\')
750
751        url = "http://%s:88/device_request" % bulb.ip
752
753        try:
754            async with aiohttp.ClientSession() as session:
755                async with session.post(url, data=payload_str) as response:
756                    print(await response.text())
757        except aiohttp.ClientConnectionError:
758            _LOGGER.warning("Failed to connect to bulb %s, reverting to cloud." % bulb.mac)
759            await self._run_action_list(bulb, plist)
760            bulb.cloud_fallback = True
761
762    async def _get_plug_history(
763        self, device: Device, start_time, end_time
764    ) -> Dict[Any, Any]:
765        """Wraps the https://api.wyzecam.com/app/v2/plug/usage_record_list endpoint"""
766
767        await self._auth_lib.refresh_if_should()
768
769        payload = {
770            "phone_id": PHONE_ID,
771            "date_begin": start_time,
772            "date_end": end_time,
773            "app_name": APP_NAME,
774            "app_version": APP_VERSION,
775            "sc": SC,
776            "device_mac": device.mac,
777            "sv": SV,
778            "phone_system_type": PHONE_SYSTEM_TYPE,
779            "app_ver": APP_VER,
780            "ts": int(time.time()),
781            "access_token": self._auth_lib.token.access_token,
782        }
783
784        response_json = await self._auth_lib.post(
785            "https://api.wyzecam.com/app/v2/plug/usage_record_list", json=payload
786        )
787
788        check_for_errors_standard(self, response_json)
789
790        return response_json["data"]["usage_record_list"]
class BaseService:
 29class BaseService:
 30    """Base service class for interacting with Wyze devices."""
 31    _devices: Optional[List[Device]] = None
 32    _last_updated_time: time = 0  # preload a value of 0 so that comparison will succeed on the first run
 33    _min_update_time = 1200  # lets let the device_params update every 20 minutes for now. This could probably reduced signicficantly.
 34    _update_lock: asyncio.Lock = asyncio.Lock() # fmt: skip
 35    _update_manager: UpdateManager = UpdateManager()
 36    _update_loop = None
 37    _updater: DeviceUpdater = None
 38    _updater_dict = {}
 39
 40    def __init__(self, auth_lib: WyzeAuthLib):
 41        """Initialize the base service."""
 42        self._auth_lib = auth_lib
 43
 44    @staticmethod
 45    async def start_update_manager():
 46        """Start the update manager."""
 47        if BaseService._update_loop is None:
 48            BaseService._update_loop = asyncio.get_event_loop()
 49            BaseService._update_loop.create_task(BaseService._update_manager.update_next())
 50
 51    def register_updater(self, device: Device, interval):
 52        """
 53        Register a device to be updated at a specific interval.
 54
 55        Parameters
 56        ----------
 57        device : Device
 58            The device to register.
 59        interval : int
 60            The interval in seconds.
 61        """
 62        self._updater = DeviceUpdater(self, device, interval)
 63        BaseService._update_manager.add_updater(self._updater)
 64        self._updater_dict[self._updater.device] = self._updater
 65
 66    def unregister_updater(self, device: Device):
 67        if self._updater:
 68            BaseService._update_manager.del_updater(self._updater_dict[device])
 69            del self._updater_dict[device]
 70
 71    async def set_push_info(self, on: bool) -> None:
 72        await self._auth_lib.refresh_if_should()
 73
 74        url = "https://api.wyzecam.com/app/user/set_push_info"
 75        payload = {
 76            "phone_system_type": PHONE_SYSTEM_TYPE,
 77            "app_version": APP_VERSION,
 78            "app_ver": APP_VER,
 79            "push_switch": "1" if on else "2",
 80            "sc": SC,
 81            "ts": int(time.time()),
 82            "sv": SV,
 83            "access_token": self._auth_lib.token.access_token,
 84            "phone_id": PHONE_ID,
 85            "app_name": APP_NAME
 86        }
 87
 88        response_json = await self._auth_lib.post(url, json=payload)
 89
 90        check_for_errors_standard(self, response_json)
 91
 92    async def get_user_profile(self) -> Dict[Any, Any]:
 93        await self._auth_lib.refresh_if_should()
 94
 95        payload = olive_create_user_info_payload()
 96        signature = olive_create_signature(payload, self._auth_lib.token.access_token)
 97        headers = {
 98            'Accept-Encoding': 'gzip',
 99            'User-Agent': 'myapp',
100            'appid': OLIVE_APP_ID,
101            'appinfo': APP_INFO,
102            'phoneid': PHONE_ID,
103            'access_token': self._auth_lib.token.access_token,
104            'signature2': signature
105        }
106
107        url = 'https://wyze-platform-service.wyzecam.com/app/v2/platform/get_user_profile'
108
109        response_json = await self._auth_lib.get(url, headers=headers, params=payload)
110
111        return response_json
112
113    async def get_object_list(self) -> List[Device]:
114        """
115        Wraps the api.wyzecam.com/app/v2/home_page/get_object_list endpoint
116
117        :return: List of devices
118        """
119        await self._auth_lib.refresh_if_should()
120
121        payload = {
122            "phone_system_type": PHONE_SYSTEM_TYPE,
123            "app_version": APP_VERSION,
124            "app_ver": APP_VER,
125            "sc": "9f275790cab94a72bd206c8876429f3c",
126            "ts": int(time.time()),
127            "sv": "9d74946e652647e9b6c9d59326aef104",
128            "access_token": self._auth_lib.token.access_token,
129            "phone_id": PHONE_ID,
130            "app_name": APP_NAME
131        }
132
133        response_json = await self._auth_lib.post("https://api.wyzecam.com/app/v2/home_page/get_object_list",
134                                                  json=payload)
135
136        check_for_errors_standard(self, response_json)
137        # Cache the devices so that update calls can pull more recent device_params
138        BaseService._devices = [Device(device) for device in response_json['data']['device_list']]
139
140        return BaseService._devices
141
142    async def get_updated_params(self, device_mac: str = None) -> Dict[str, Optional[Any]]:
143        if time.time() - BaseService._last_updated_time >= BaseService._min_update_time:
144            await self.get_object_list()
145            BaseService._last_updated_time = time.time()
146        ret_params = {}
147        for dev in BaseService._devices:
148            if dev.mac == device_mac:
149                ret_params = dev.device_params
150        return ret_params
151
152    async def _get_property_list(self, device: Device) -> List[Tuple[PropertyIDs, Any]]:
153        """
154        Wraps the api.wyzecam.com/app/v2/device/get_property_list endpoint
155
156        :param device: Device to get properties for
157        :return: List of PropertyIDs and values
158        """
159
160        await self._auth_lib.refresh_if_should()
161
162        payload = {
163            "phone_system_type": PHONE_SYSTEM_TYPE,
164            "app_version": APP_VERSION,
165            "app_ver": APP_VER,
166            "sc": "9f275790cab94a72bd206c8876429f3c",
167            "ts": int(time.time()),
168            "sv": "9d74946e652647e9b6c9d59326aef104",
169            "access_token": self._auth_lib.token.access_token,
170            "phone_id": PHONE_ID,
171            "app_name": APP_NAME,
172            "device_model": device.product_model,
173            "device_mac": device.mac,
174            "target_pid_list": []
175        }
176
177        response_json = await self._auth_lib.post("https://api.wyzecam.com/app/v2/device/get_property_list",
178                                                  json=payload)
179
180        check_for_errors_standard(self, response_json)
181        properties = response_json['data']['property_list']
182        property_list = []
183        for prop in properties:
184            try:
185                property_id = PropertyIDs(prop['pid'])
186                property_list.append((
187                    property_id,
188                    prop['value']
189                ))
190            except ValueError:
191                pass
192
193        return property_list
194
195    async def _set_property_list(self, device: Device, plist: List[Dict[str, str]]) -> None:
196        """
197        Wraps the api.wyzecam.com/app/v2/device/set_property_list endpoint
198
199        :param device: The device for which to set the property(ies)
200        :param plist: A list of properties [{"pid": pid, "pvalue": pvalue},...]
201        :return:
202        """
203
204        await self._auth_lib.refresh_if_should()
205
206        payload = {
207            "phone_system_type": PHONE_SYSTEM_TYPE,
208            "app_version": APP_VERSION,
209            "app_ver": APP_VER,
210            "sc": "9f275790cab94a72bd206c8876429f3c",
211            "ts": int(time.time()),
212            "sv": "9d74946e652647e9b6c9d59326aef104",
213            "access_token": self._auth_lib.token.access_token,
214            "phone_id": PHONE_ID,
215            "app_name": APP_NAME,
216            "property_list": plist,
217            "device_model": device.product_model,
218            "device_mac": device.mac
219        }
220
221        response_json = await self._auth_lib.post("https://api.wyzecam.com/app/v2/device/set_property_list",
222                                                  json=payload)
223
224        check_for_errors_standard(self, response_json)
225
226    async def _run_action_list(self, device: Device, plist: List[Dict[Any, Any]]) -> None:
227        """
228        Wraps the api.wyzecam.com/app/v2/auto/run_action_list endpoint
229
230        :param device: The device for which to run the action list
231        :param plist: A list of properties [{"pid": pid, "pvalue": pvalue},...]
232        """
233        await self._auth_lib.refresh_if_should()
234
235        payload = {
236            "phone_system_type": PHONE_SYSTEM_TYPE,
237            "app_version": APP_VERSION,
238            "app_ver": APP_VER,
239            "sc": "9f275790cab94a72bd206c8876429f3c",
240            "ts": int(time.time()),
241            "sv": "9d74946e652647e9b6c9d59326aef104",
242            "access_token": self._auth_lib.token.access_token,
243            "phone_id": PHONE_ID,
244            "app_name": APP_NAME,
245            "action_list": [
246                {
247                    "instance_id": device.mac,
248                    "action_params": {
249                        "list": [
250                            {
251                                "mac": device.mac,
252                                "plist": plist
253                            }
254                        ]
255                    },
256                    "provider_key": device.product_model,
257                    "action_key": "set_mesh_property"
258                }
259            ]
260        }
261
262        response_json = await self._auth_lib.post("https://api.wyzecam.com/app/v2/auto/run_action_list",
263                                                  json=payload)
264
265        check_for_errors_standard(self, response_json)
266
267    async def _get_event_list(self, count: int) -> Dict[Any, Any]:
268        """
269        Wraps the api.wyzecam.com/app/v2/device/get_event_list endpoint
270
271        :param count: Number of events to gather
272        :return: Response from the server
273        """
274
275        await self._auth_lib.refresh_if_should()
276
277        payload = {
278            "phone_id": PHONE_ID,
279            "begin_time": int((time.time() - (60 * 60)) * 1000),
280            "event_type": "",
281            "app_name": APP_NAME,
282            "count": count,
283            "app_version": APP_VERSION,
284            "order_by": 2,
285            "event_value_list": [
286                "1",
287                "13",
288                "10",
289                "12"
290            ],
291            "sc": "9f275790cab94a72bd206c8876429f3c",
292            "device_mac_list": [],
293            "event_tag_list": [],
294            "sv": "782ced6909a44d92a1f70d582bbe88be",
295            "end_time": int(time.time() * 1000),
296            "phone_system_type": PHONE_SYSTEM_TYPE,
297            "app_ver": APP_VER,
298            "ts": 1623612037763,
299            "device_mac": "",
300            "access_token": self._auth_lib.token.access_token
301        }
302
303        response_json = await self._auth_lib.post("https://api.wyzecam.com/app/v2/device/get_event_list",
304                                                  json=payload)
305
306        check_for_errors_standard(self, response_json)
307        return response_json
308
309    async def _run_action(self, device: Device, action: str) -> None:
310        """
311        Wraps the api.wyzecam.com/app/v2/auto/run_action endpoint
312
313        :param device: The device for which to run the action
314        :param action: The action to run
315        :return:
316        """
317
318        await self._auth_lib.refresh_if_should()
319
320        payload = {
321            "phone_system_type": PHONE_SYSTEM_TYPE,
322            "app_version": APP_VERSION,
323            "app_ver": APP_VER,
324            "sc": "9f275790cab94a72bd206c8876429f3c",
325            "ts": int(time.time()),
326            "sv": "9d74946e652647e9b6c9d59326aef104",
327            "access_token": self._auth_lib.token.access_token,
328            "phone_id": PHONE_ID,
329            "app_name": APP_NAME,
330            "provider_key": device.product_model,
331            "instance_id": device.mac,
332            "action_key": action,
333            "action_params": {},
334            "custom_string": "",
335        }
336
337        response_json = await self._auth_lib.post("https://api.wyzecam.com/app/v2/auto/run_action",
338                                                  json=payload)
339
340        check_for_errors_standard(self, response_json)
341    
342    async def _run_action_devicemgmt(self, device: Device, type: str, value: str) -> None:
343        """
344        Wraps the devicemgmt-service-beta.wyze.com/device-management/api/action/run_action endpoint
345
346        :param device: The device for which to run the action
347        :param state: on or off
348        :return:
349        """
350
351        await self._auth_lib.refresh_if_should()
352
353        capabilities = devicemgmt_create_capabilities_payload(type, value)
354
355        payload = {
356            "capabilities": [
357                capabilities
358            ],
359            "nonce": int(time.time() * 1000),
360            "targetInfo": {
361                "id": device.mac,
362                "productModel": device.product_model,
363                "type": "DEVICE"
364            },
365            "transactionId": "0a5b20591fedd4du1b93f90743ba0csd" # OG cam needs this (doesn't matter what the value is)
366        }
367
368        headers = {
369            "authorization": self._auth_lib.token.access_token,
370        }
371
372        response_json = await self._auth_lib.post("https://devicemgmt-service-beta.wyze.com/device-management/api/action/run_action",
373                                                  json=payload, headers=headers)
374
375        check_for_errors_iot(self, response_json)
376    
377    async def _set_toggle(self, device: Device, toggleType: DeviceMgmtToggleType, state: str) -> None:
378        """
379        Wraps the ai-subscription-service-beta.wyzecam.com/v4/subscription-service/toggle-management endpoint
380
381        :param device: The device for which to get the state
382        :param toggleType: Enum for the toggle type
383        :param state: String state to set for the toggle
384        """
385
386        await self._auth_lib.refresh_if_should()
387
388        payload = {
389            "data": [
390                {
391                    "device_firmware": "1234567890",
392                    "device_id": device.mac,
393                    "device_model": device.product_model,
394                    "page_id": [
395                        toggleType.pageId
396                    ],
397                    "toggle_update": [
398                        {
399                            "toggle_id": toggleType.toggleId,
400                            "toggle_status": state
401                        }
402                    ]
403                }
404            ],
405            "nonce": str(int(time.time() * 1000))
406        }
407
408
409        signature = olive_create_signature(payload, self._auth_lib.token.access_token)
410        headers = {
411            "access_token": self._auth_lib.token.access_token,
412            "timestamp": str(int(time.time() * 1000)),
413            "appid": OLIVE_APP_ID,
414            "source": SOURCE,
415            "signature2": signature,
416            "appplatform": APP_PLATFORM,
417            "appversion": APP_VERSION,
418            "requestid": "35374158s4s313b9a2be7c057f2da5d1"
419        }
420
421        response_json = await self._auth_lib.put("https://ai-subscription-service-beta.wyzecam.com/v4/subscription-service/toggle-management",
422                                                  json=payload, headers=headers)
423        
424        check_for_errors_devicemgmt(self, response_json)
425    
426    async def _get_iot_prop_devicemgmt(self, device: Device) -> Dict[str, Any]:
427        """
428        Wraps the devicemgmt-service-beta.wyze.com/device-management/api/device-property/get_iot_prop endpoint
429
430        :param device: The device for which to get the state
431        :return:
432        """
433
434        await self._auth_lib.refresh_if_should()
435
436        payload = {
437            "capabilities": devicemgmt_get_iot_props_list(device.product_model),
438            "nonce": int(time.time() * 1000),
439            "targetInfo": {
440                "id": device.mac,
441                "productModel": device.product_model,
442                "type": "DEVICE"
443            }
444        }
445
446        headers = {
447            "authorization": self._auth_lib.token.access_token,
448        }
449
450        response_json = await self._auth_lib.post("https://devicemgmt-service-beta.wyze.com/device-management/api/device-property/get_iot_prop",
451                                                  json=payload, headers=headers)
452        
453        check_for_errors_iot(self, response_json)
454
455        return response_json
456
457    async def _set_property(self, device: Device, pid: str, pvalue: str) -> None:
458        """
459        Wraps the api.wyzecam.com/app/v2/device/set_property endpoint
460
461        :param device: The device for which to set the property
462        :param pid: The property id
463        :param pvalue: The property value
464        """
465        await self._auth_lib.refresh_if_should()
466
467        payload = {
468            "phone_system_type": PHONE_SYSTEM_TYPE,
469            "app_version": APP_VERSION,
470            "app_ver": APP_VER,
471            "sc": "9f275790cab94a72bd206c8876429f3c",
472            "ts": int(time.time()),
473            "sv": "9d74946e652647e9b6c9d59326aef104",
474            "access_token": self._auth_lib.token.access_token,
475            "phone_id": PHONE_ID,
476            "app_name": APP_NAME,
477            "pvalue": pvalue,
478            "pid": pid,
479            "device_model": device.product_model,
480            "device_mac": device.mac
481        }
482
483        response_json = await self._auth_lib.post("https://api.wyzecam.com/app/v2/device/set_property",
484                                                  json=payload)
485
486        check_for_errors_standard(self, response_json)
487
488    async def _monitoring_profile_active(self, hms_id: str, home: int, away: int) -> None:
489        """
490        Wraps the hms.api.wyze.com/api/v1/monitoring/v1/profile/active endpoint
491
492        :param hms_id: The hms id
493        :param home: 1 for home 0 for not
494        :param away: 1 for away 0 for not
495        :return:
496        """
497        await self._auth_lib.refresh_if_should()
498
499        url = "https://hms.api.wyze.com/api/v1/monitoring/v1/profile/active"
500        query = olive_create_hms_patch_payload(hms_id)
501        signature = olive_create_signature(query, self._auth_lib.token.access_token)
502        headers = {
503            'Accept-Encoding': 'gzip',
504            'User-Agent': 'myapp',
505            'appid': OLIVE_APP_ID,
506            'appinfo': APP_INFO,
507            'phoneid': PHONE_ID,
508            'access_token': self._auth_lib.token.access_token,
509            'signature2': signature,
510            'Authorization': self._auth_lib.token.access_token
511        }
512        payload = [
513            {
514                "state": "home",
515                "active": home
516            },
517            {
518                "state": "away",
519                "active": away
520            }
521        ]
522        response_json = await self._auth_lib.patch(url, headers=headers, params=query, json=payload)
523        check_for_errors_hms(self, response_json)
524
525    async def _get_plan_binding_list_by_user(self) -> Dict[Any, Any]:
526        """
527        Wraps the wyze-membership-service.wyzecam.com/platform/v2/membership/get_plan_binding_list_by_user endpoint
528
529        :return: The response to gathering the plan for the current user
530        """
531
532        if self._auth_lib.should_refresh:
533            await self._auth_lib.refresh()
534
535        url = "https://wyze-membership-service.wyzecam.com/platform/v2/membership/get_plan_binding_list_by_user"
536        payload = olive_create_hms_payload()
537        signature = olive_create_signature(payload, self._auth_lib.token.access_token)
538        headers = {
539            'Accept-Encoding': 'gzip',
540            'User-Agent': 'myapp',
541            'appid': OLIVE_APP_ID,
542            'appinfo': APP_INFO,
543            'phoneid': PHONE_ID,
544            'access_token': self._auth_lib.token.access_token,
545            'signature2': signature
546        }
547
548        response_json = await self._auth_lib.get(url, headers=headers, params=payload)
549        check_for_errors_hms(self, response_json)
550        return response_json
551
552    async def _disable_reme_alarm(self, hms_id: str) -> None:
553        """
554        Wraps the hms.api.wyze.com/api/v1/reme-alarm endpoint
555
556        :param hms_id: The hms_id for the account
557        """
558        await self._auth_lib.refresh_if_should()
559
560        url = "https://hms.api.wyze.com/api/v1/reme-alarm"
561        payload = {
562            "hms_id": hms_id,
563            "remediation_id": "emergency"
564        }
565        headers = {
566            "Authorization": self._auth_lib.token.access_token
567        }
568
569        response_json = await self._auth_lib.delete(url, headers=headers, json=payload)
570
571        check_for_errors_hms(self, response_json)
572
573    async def _monitoring_profile_state_status(self, hms_id: str) -> Dict[Any, Any]:
574        """
575        Wraps the hms.api.wyze.com/api/v1/monitoring/v1/profile/state-status endpoint
576
577        :param hms_id: The hms_id
578        :return: The response that includes the status
579        """
580        if self._auth_lib.should_refresh:
581            await self._auth_lib.refresh()
582
583        url = "https://hms.api.wyze.com/api/v1/monitoring/v1/profile/state-status"
584        query = olive_create_hms_get_payload(hms_id)
585        signature = olive_create_signature(query, self._auth_lib.token.access_token)
586        headers = {
587            'User-Agent': 'myapp',
588            'appid': OLIVE_APP_ID,
589            'appinfo': APP_INFO,
590            'phoneid': PHONE_ID,
591            'access_token': self._auth_lib.token.access_token,
592            'signature2': signature,
593            'Authorization': self._auth_lib.token.access_token,
594            'Content-Type': "application/json"
595        }
596
597        response_json = await self._auth_lib.get(url, headers=headers, params=query)
598
599        check_for_errors_hms(self, response_json)
600        return response_json
601
602    async def _lock_control(self, device: Device, action: str) -> None:
603        await self._auth_lib.refresh_if_should()
604
605        url_path = "/openapi/lock/v1/control"
606
607        device_uuid = device.mac.split(".")[-1]
608
609        payload = {
610            "uuid": device_uuid,
611            "action": action  # "remoteLock" or "remoteUnlock"
612        }
613        payload = ford_create_payload(self._auth_lib.token.access_token, payload, url_path, "post")
614
615        url = "https://yd-saas-toc.wyzecam.com/openapi/lock/v1/control"
616
617        response_json = await self._auth_lib.post(url, json=payload)
618
619        check_for_errors_lock(self, response_json)
620
621    async def _get_lock_info(self, device: Device) -> Dict[str, Optional[Any]]:
622        await self._auth_lib.refresh_if_should()
623
624        url_path = "/openapi/lock/v1/info"
625
626        device_uuid = device.mac.split(".")[-1]
627
628        payload = {
629            "uuid": device_uuid,
630            "with_keypad": "1"
631        }
632
633        payload = ford_create_payload(self._auth_lib.token.access_token, payload, url_path, "get")
634
635        url = "https://yd-saas-toc.wyzecam.com/openapi/lock/v1/info"
636
637        response_json = await self._auth_lib.get(url, params=payload)
638
639        check_for_errors_lock(self, response_json)
640
641        return response_json
642
643    async def _get_lock_ble_token(self, device: Device) -> Dict[str, Optional[Any]]:
644        await self._auth_lib.refresh_if_should()
645
646        url_path = "/openapi/lock/v1/ble/token"
647
648        payload = {
649            "uuid": device.mac
650        }
651
652        payload = ford_create_payload(self._auth_lib.token.access_token, payload, url_path, "get")
653
654        url = f"https://yd-saas-toc.wyzecam.com{url_path}"
655
656        response_json = await self._auth_lib.get(url, params=payload)
657
658        check_for_errors_lock(self, response_json)
659
660        return response_json
661
662    async def _get_device_info(self, device: Device) -> Dict[Any, Any]:
663        await self._auth_lib.refresh_if_should()
664
665        payload = {
666            "phone_system_type": PHONE_SYSTEM_TYPE,
667            "app_version": APP_VERSION,
668            "app_ver": APP_VER,
669            "device_mac": device.mac,
670            "sc": "9f275790cab94a72bd206c8876429f3c",
671            "ts": int(time.time()),
672            "device_model": device.product_model,
673            "sv": "c86fa16fc99d4d6580f82ef3b942e586",
674            "access_token": self._auth_lib.token.access_token,
675            "phone_id": PHONE_ID,
676            "app_name": APP_NAME
677        }
678
679        response_json = await self._auth_lib.post("https://api.wyzecam.com/app/v2/device/get_device_Info",
680                                                  json=payload)
681
682        check_for_errors_standard(self, response_json)
683
684        return response_json
685
686    async def _get_iot_prop(self, url: str, device: Device, keys: str) -> Dict[Any, Any]:
687        await self._auth_lib.refresh_if_should()
688
689        payload = olive_create_get_payload(device.mac, keys)
690        signature = olive_create_signature(payload, self._auth_lib.token.access_token)
691        headers = {
692            'Accept-Encoding': 'gzip',
693            'User-Agent': 'myapp',
694            'appid': OLIVE_APP_ID,
695            'appinfo': APP_INFO,
696            'phoneid': PHONE_ID,
697            'access_token': self._auth_lib.token.access_token,
698            'signature2': signature
699        }
700
701        response_json = await self._auth_lib.get(url, headers=headers, params=payload)
702
703        check_for_errors_iot(self, response_json)
704
705        return response_json
706
707    async def _set_iot_prop(self, url: str, device: Device, prop_key: str, value: Any) -> None:
708        await self._auth_lib.refresh_if_should()
709
710        payload = olive_create_post_payload(device.mac, device.product_model, prop_key, value)
711        signature = olive_create_signature(json.dumps(payload, separators=(',', ':')),
712                                           self._auth_lib.token.access_token)
713        headers = {
714            'Accept-Encoding': 'gzip',
715            'Content-Type': 'application/json',
716            'User-Agent': 'myapp',
717            'appid': OLIVE_APP_ID,
718            'appinfo': APP_INFO,
719            'phoneid': PHONE_ID,
720            'access_token': self._auth_lib.token.access_token,
721            'signature2': signature
722        }
723
724        payload_str = json.dumps(payload, separators=(',', ':'))
725
726        response_json = await self._auth_lib.post(url, headers=headers, data=payload_str)
727
728        check_for_errors_iot(self, response_json)
729
730    async def _local_bulb_command(self, bulb, plist):
731        # await self._auth_lib.refresh_if_should()
732
733        characteristics = {
734            "mac": bulb.mac.upper(),
735            "index": "1",
736            "ts": str(int(time.time_ns() // 1000000)),
737            "plist": plist
738        }
739
740        characteristics_str = json.dumps(characteristics, separators=(',', ':'))
741        characteristics_enc = wyze_encrypt(bulb.enr, characteristics_str)
742
743        payload = {
744            "request": "set_status",
745            "isSendQueue": 0,
746            "characteristics": characteristics_enc
747        }
748
749        # JSON likes to add a second \ so we have to remove it for the bulb to be happy
750        payload_str = json.dumps(payload, separators=(',', ':')).replace('\\\\', '\\')
751
752        url = "http://%s:88/device_request" % bulb.ip
753
754        try:
755            async with aiohttp.ClientSession() as session:
756                async with session.post(url, data=payload_str) as response:
757                    print(await response.text())
758        except aiohttp.ClientConnectionError:
759            _LOGGER.warning("Failed to connect to bulb %s, reverting to cloud." % bulb.mac)
760            await self._run_action_list(bulb, plist)
761            bulb.cloud_fallback = True
762
763    async def _get_plug_history(
764        self, device: Device, start_time, end_time
765    ) -> Dict[Any, Any]:
766        """Wraps the https://api.wyzecam.com/app/v2/plug/usage_record_list endpoint"""
767
768        await self._auth_lib.refresh_if_should()
769
770        payload = {
771            "phone_id": PHONE_ID,
772            "date_begin": start_time,
773            "date_end": end_time,
774            "app_name": APP_NAME,
775            "app_version": APP_VERSION,
776            "sc": SC,
777            "device_mac": device.mac,
778            "sv": SV,
779            "phone_system_type": PHONE_SYSTEM_TYPE,
780            "app_ver": APP_VER,
781            "ts": int(time.time()),
782            "access_token": self._auth_lib.token.access_token,
783        }
784
785        response_json = await self._auth_lib.post(
786            "https://api.wyzecam.com/app/v2/plug/usage_record_list", json=payload
787        )
788
789        check_for_errors_standard(self, response_json)
790
791        return response_json["data"]["usage_record_list"]

Base service class for interacting with Wyze devices.

BaseService(auth_lib: wyzeapy.wyze_auth_lib.WyzeAuthLib)
40    def __init__(self, auth_lib: WyzeAuthLib):
41        """Initialize the base service."""
42        self._auth_lib = auth_lib

Initialize the base service.

@staticmethod
async def start_update_manager():
44    @staticmethod
45    async def start_update_manager():
46        """Start the update manager."""
47        if BaseService._update_loop is None:
48            BaseService._update_loop = asyncio.get_event_loop()
49            BaseService._update_loop.create_task(BaseService._update_manager.update_next())

Start the update manager.

def register_updater(self, device: wyzeapy.types.Device, interval):
51    def register_updater(self, device: Device, interval):
52        """
53        Register a device to be updated at a specific interval.
54
55        Parameters
56        ----------
57        device : Device
58            The device to register.
59        interval : int
60            The interval in seconds.
61        """
62        self._updater = DeviceUpdater(self, device, interval)
63        BaseService._update_manager.add_updater(self._updater)
64        self._updater_dict[self._updater.device] = self._updater

Register a device to be updated at a specific interval.

Parameters

device : Device The device to register. interval : int The interval in seconds.

def unregister_updater(self, device: wyzeapy.types.Device):
66    def unregister_updater(self, device: Device):
67        if self._updater:
68            BaseService._update_manager.del_updater(self._updater_dict[device])
69            del self._updater_dict[device]
async def set_push_info(self, on: bool) -> None:
71    async def set_push_info(self, on: bool) -> None:
72        await self._auth_lib.refresh_if_should()
73
74        url = "https://api.wyzecam.com/app/user/set_push_info"
75        payload = {
76            "phone_system_type": PHONE_SYSTEM_TYPE,
77            "app_version": APP_VERSION,
78            "app_ver": APP_VER,
79            "push_switch": "1" if on else "2",
80            "sc": SC,
81            "ts": int(time.time()),
82            "sv": SV,
83            "access_token": self._auth_lib.token.access_token,
84            "phone_id": PHONE_ID,
85            "app_name": APP_NAME
86        }
87
88        response_json = await self._auth_lib.post(url, json=payload)
89
90        check_for_errors_standard(self, response_json)
async def get_user_profile(self) -> Dict[Any, Any]:
 92    async def get_user_profile(self) -> Dict[Any, Any]:
 93        await self._auth_lib.refresh_if_should()
 94
 95        payload = olive_create_user_info_payload()
 96        signature = olive_create_signature(payload, self._auth_lib.token.access_token)
 97        headers = {
 98            'Accept-Encoding': 'gzip',
 99            'User-Agent': 'myapp',
100            'appid': OLIVE_APP_ID,
101            'appinfo': APP_INFO,
102            'phoneid': PHONE_ID,
103            'access_token': self._auth_lib.token.access_token,
104            'signature2': signature
105        }
106
107        url = 'https://wyze-platform-service.wyzecam.com/app/v2/platform/get_user_profile'
108
109        response_json = await self._auth_lib.get(url, headers=headers, params=payload)
110
111        return response_json
async def get_object_list(self) -> List[wyzeapy.types.Device]:
113    async def get_object_list(self) -> List[Device]:
114        """
115        Wraps the api.wyzecam.com/app/v2/home_page/get_object_list endpoint
116
117        :return: List of devices
118        """
119        await self._auth_lib.refresh_if_should()
120
121        payload = {
122            "phone_system_type": PHONE_SYSTEM_TYPE,
123            "app_version": APP_VERSION,
124            "app_ver": APP_VER,
125            "sc": "9f275790cab94a72bd206c8876429f3c",
126            "ts": int(time.time()),
127            "sv": "9d74946e652647e9b6c9d59326aef104",
128            "access_token": self._auth_lib.token.access_token,
129            "phone_id": PHONE_ID,
130            "app_name": APP_NAME
131        }
132
133        response_json = await self._auth_lib.post("https://api.wyzecam.com/app/v2/home_page/get_object_list",
134                                                  json=payload)
135
136        check_for_errors_standard(self, response_json)
137        # Cache the devices so that update calls can pull more recent device_params
138        BaseService._devices = [Device(device) for device in response_json['data']['device_list']]
139
140        return BaseService._devices

Wraps the api.wyzecam.com/app/v2/home_page/get_object_list endpoint

Returns

List of devices

async def get_updated_params(self, device_mac: str = None) -> Dict[str, Optional[Any]]:
142    async def get_updated_params(self, device_mac: str = None) -> Dict[str, Optional[Any]]:
143        if time.time() - BaseService._last_updated_time >= BaseService._min_update_time:
144            await self.get_object_list()
145            BaseService._last_updated_time = time.time()
146        ret_params = {}
147        for dev in BaseService._devices:
148            if dev.mac == device_mac:
149                ret_params = dev.device_params
150        return ret_params