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.
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.
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
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