wyzeapy 0.5.23__py3-none-any.whl → 0.5.25__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
wyzeapy/const.py CHANGED
@@ -15,6 +15,9 @@ PHONE_ID = str(uuid.uuid4())
15
15
  APP_INFO = 'wyze_android_2.19.14' # Required for the thermostat
16
16
  SC = "9f275790cab94a72bd206c8876429f3c"
17
17
  SV = "9d74946e652647e9b6c9d59326aef104"
18
+ CLIENT_VER = "2"
19
+ SOURCE = "ios/WZCameraSDK"
20
+ APP_PLATFORM = "ios"
18
21
 
19
22
  # Crypto secrets
20
23
  OLIVE_SIGNING_SECRET = 'wyze_app_secret_key_132' # Required for the thermostat
wyzeapy/exceptions.py CHANGED
@@ -26,8 +26,7 @@ class LoginError(Exception):
26
26
 
27
27
 
28
28
  class UnknownApiError(Exception):
29
- def __init__(self, response_json: Dict[str, Any]):
30
- super(UnknownApiError, self).__init__(str(response_json))
29
+ pass
31
30
 
32
31
 
33
32
  class TwoFactorAuthenticationEnabled(Exception):
@@ -72,3 +72,499 @@ def olive_create_hms_patch_payload(hms_id: str) -> Dict[str, Any]:
72
72
  return {
73
73
  "hms_id": hms_id
74
74
  }
75
+
76
+
77
+ def devicemgmt_create_capabilities_payload(type: str, value: str):
78
+ match type:
79
+ case "floodlight":
80
+ return {
81
+ "iid": 4,
82
+ "name": "floodlight",
83
+ "properties": [
84
+ {
85
+ "prop": "on",
86
+ "value": value
87
+ }
88
+ ]
89
+ }
90
+ case "spotlight":
91
+ return {
92
+ "iid": 5,
93
+ "name": "spotlight",
94
+ "properties": [
95
+ {
96
+ "prop": "on",
97
+ "value": value
98
+ }
99
+ ]
100
+ }
101
+ case "power":
102
+ return {
103
+ "functions": [
104
+ {
105
+ "in": {
106
+ "wakeup-live-view": "1"
107
+ },
108
+ "name": value
109
+ }
110
+ ],
111
+ "iid": 1,
112
+ "name": "iot-device"
113
+ }
114
+ case "siren":
115
+ return {
116
+ "functions": [
117
+ {
118
+ "in": {},
119
+ "name": value
120
+ }
121
+ ],
122
+ "name": "siren"
123
+ }
124
+ case _:
125
+ raise NotImplementedError(f"No action of type ({type}) has been implemented.")
126
+
127
+
128
+ def devicemgmt_get_iot_props_list(model: str):
129
+ match model:
130
+ case "LD_CFP": # Floodlight Pro
131
+ return [
132
+ {
133
+ "iid": 2,
134
+ "name": "camera",
135
+ "properties": [
136
+ "motion-detect",
137
+ "resolution",
138
+ "bit-rate",
139
+ "live-stream-mode",
140
+ "recording-mode",
141
+ "frame-rate",
142
+ "night-shot",
143
+ "night-shot-state",
144
+ "rotate-angle",
145
+ "time-watermark",
146
+ "logo-watermark",
147
+ "recording-trigger-source",
148
+ "recording-content-type",
149
+ "motion-push",
150
+ "speaker",
151
+ "microphone",
152
+ "unusual-sound-push",
153
+ "flip",
154
+ "motion-detect-recording",
155
+ "cool-down-interval",
156
+ "infrared-mode",
157
+ "sound-collection-on",
158
+ "live-stream-protocol",
159
+ "ai-push",
160
+ "voice-template",
161
+ "motion-category"
162
+ ]
163
+ },
164
+ {
165
+ "iid": 3,
166
+ "name": "device-info",
167
+ "properties": [
168
+ "device-id",
169
+ "device-model",
170
+ "firmware-ver",
171
+ "mac",
172
+ "timezone",
173
+ "lat",
174
+ "ip",
175
+ "lon",
176
+ "hardware-ver",
177
+ "public-ip"
178
+ ]
179
+ },
180
+ {
181
+ "iid": 1,
182
+ "name": "iot-device",
183
+ "properties": [
184
+ "iot-state",
185
+ "iot-power",
186
+ "push-switch"
187
+ ]
188
+ },
189
+ {
190
+ "iid": 9,
191
+ "name": "camera-ai",
192
+ "properties": [
193
+ "smart-detection-type",
194
+ "on"
195
+ ]
196
+ },
197
+ {
198
+ "iid": 4,
199
+ "name": "floodlight",
200
+ "properties": [
201
+ "on",
202
+ "enabled",
203
+ "mode",
204
+ "trigger-source",
205
+ "brightness",
206
+ "light-on-duration",
207
+ "voice-template",
208
+ "motion-warning-switch",
209
+ "motion-activate-light-switch",
210
+ "motion-activate-light-schedule",
211
+ "motion-activate-brightness",
212
+ "ambient-light-switch",
213
+ "ambient-light-schedule",
214
+ "ambient-light-brightness",
215
+ "motion-tag",
216
+ "light-model",
217
+ "flash-with-siren"
218
+ ]
219
+ },
220
+ {
221
+ "iid": 11,
222
+ "name": "indicator-light",
223
+ "properties": [
224
+ "on",
225
+ "mode",
226
+ "brightness",
227
+ "color",
228
+ "color-temperature"
229
+ ]
230
+ },
231
+ {
232
+ "iid": 8,
233
+ "name": "memory-card-management",
234
+ "properties": [
235
+ "storage-used-space",
236
+ "storage-total-space",
237
+ "storage-status",
238
+ "sd-card-playback-enabled"
239
+ ]
240
+ },
241
+ {
242
+ "iid": 6,
243
+ "name": "motion-detection",
244
+ "properties": [
245
+ "sensitivity-motion",
246
+ "on",
247
+ "motion-zone",
248
+ "motion-zone-selected-block",
249
+ "motion-zone-block-size",
250
+ "motion-tag",
251
+ "edge-detection-type",
252
+ "motion-warning-switch",
253
+ "motion-warning-tone",
254
+ "motion-warning-interval",
255
+ "motion-warning-schedule",
256
+ "motion-warning-sound",
257
+ "motion-warning-trigger-setting"
258
+ ]
259
+ },
260
+ {
261
+ "iid": 7,
262
+ "name": "siren",
263
+ "properties": [
264
+ "state"
265
+ ]
266
+ },
267
+ {
268
+ "iid": 5,
269
+ "name": "wifi",
270
+ "properties": [
271
+ "on",
272
+ "signal-strength",
273
+ "wifi-ssid",
274
+ "wifi-encrypted-password"
275
+ ]
276
+ }
277
+ ]
278
+ case "AN_RSCW": # Battery Cam pro
279
+ return [
280
+ {
281
+ "iid": 2,
282
+ "name": "camera",
283
+ "properties": [
284
+ "motion-detect",
285
+ "resolution",
286
+ "bit-rate",
287
+ "live-stream-mode",
288
+ "recording-mode",
289
+ "frame-rate",
290
+ "night-shot",
291
+ "night-shot-state",
292
+ "time-watermark",
293
+ "logo-watermark",
294
+ "cool-down-interval",
295
+ "recording-content-type",
296
+ "video-length-limit",
297
+ "motion-push",
298
+ "speaker",
299
+ "unusual-sound-push",
300
+ "microphone",
301
+ "infrared-mode",
302
+ "motion-detect-recording",
303
+ "live-stream-protocol",
304
+ "recording-resolution",
305
+ "recording-start-time",
306
+ "recording-schedule-duration",
307
+ "voice-template",
308
+ "rotate-angle",
309
+ "sound-collection-on",
310
+ "ai-push"
311
+ ]
312
+ },
313
+ {
314
+ "iid": 3,
315
+ "name": "device-info",
316
+ "properties": [
317
+ "device-id",
318
+ "device-model",
319
+ "firmware-ver",
320
+ "mac",
321
+ "timezone",
322
+ "lat",
323
+ "ip",
324
+ "lon",
325
+ "company-code",
326
+ "device-setting-channel",
327
+ "network-connection-mode",
328
+ "hardware-ver",
329
+ "public-ip"
330
+ ]
331
+ },
332
+ {
333
+ "iid": 1,
334
+ "name": "iot-device",
335
+ "properties": [
336
+ "iot-state",
337
+ "iot-power",
338
+ "push-switch",
339
+ "mqtt-check"
340
+ ]
341
+ },
342
+ {
343
+ "iid": 7,
344
+ "name": "battery",
345
+ "properties": [
346
+ "battery-level",
347
+ "low-battery-push",
348
+ "power-source",
349
+ "charging-status",
350
+ "power-saving"
351
+ ]
352
+ },
353
+ {
354
+ "iid": 12,
355
+ "name": "camera-ai",
356
+ "properties": [
357
+ "smart-detection-type",
358
+ "on"
359
+ ]
360
+ },
361
+ {
362
+ "iid": 8,
363
+ "name": "indicator-light",
364
+ "properties": [
365
+ "on",
366
+ "mode"
367
+ ]
368
+ },
369
+ {
370
+ "iid": 6,
371
+ "name": "memory-card-management",
372
+ "properties": [
373
+ "storage-used-space",
374
+ "storage-total-space",
375
+ "storage-status",
376
+ "sd-card-playback-enabled"
377
+ ]
378
+ },
379
+ {
380
+ "iid": 11,
381
+ "name": "motion-detection",
382
+ "properties": [
383
+ "sensitivity-motion",
384
+ "on",
385
+ "area-length",
386
+ "motion-zone",
387
+ "motion-zone-block-size",
388
+ "motion-zone-selected-block",
389
+ "edge-detection-type",
390
+ "motion-tag"
391
+ ]
392
+ },
393
+ {
394
+ "iid": 4,
395
+ "name": "siren",
396
+ "properties": [
397
+ "state",
398
+ "siren-on-ts"
399
+ ]
400
+ },
401
+ {
402
+ "iid": 14,
403
+ "name": "solar-panel",
404
+ "properties": [
405
+ "enabled",
406
+ "on"
407
+ ]
408
+ },
409
+ {
410
+ "iid": 5,
411
+ "name": "spotlight",
412
+ "properties": [
413
+ "on",
414
+ "enabled",
415
+ "brightness",
416
+ "motion-activate-light-switch",
417
+ "sunset-to-sunrise",
418
+ "motion-activate-light-schedule",
419
+ "trigger-source"
420
+ ]
421
+ },
422
+ {
423
+ "iid": 9,
424
+ "name": "wifi",
425
+ "properties": [
426
+ "on",
427
+ "signal-strength",
428
+ "wifi-ssid",
429
+ "wifi-encrypted-password"
430
+ ]
431
+ }
432
+ ]
433
+ case "GW_GC1": # OG
434
+ return [
435
+ {
436
+ "iid": 2,
437
+ "name": "camera",
438
+ "properties": [
439
+ "motion-detect",
440
+ "resolution",
441
+ "bit-rate",
442
+ "live-stream-mode",
443
+ "recording-mode",
444
+ "frame-rate",
445
+ "night-shot",
446
+ "night-shot-state",
447
+ "time-watermark",
448
+ "logo-watermark",
449
+ "cool-down-interval",
450
+ "recording-content-type",
451
+ "video-length-limit",
452
+ "motion-push",
453
+ "speaker",
454
+ "unusual-sound-push",
455
+ "microphone",
456
+ "infrared-mode",
457
+ "motion-detect-recording",
458
+ "live-stream-protocol",
459
+ "recording-resolution",
460
+ "recording-start-time",
461
+ "recording-schedule-duration",
462
+ "voice-template",
463
+ "rotate-angle",
464
+ "sound-collection-on",
465
+ "ai-push"
466
+ ]
467
+ },
468
+ {
469
+ "iid": 3,
470
+ "name": "device-info",
471
+ "properties": [
472
+ "device-id",
473
+ "device-model",
474
+ "firmware-ver",
475
+ "mac",
476
+ "timezone",
477
+ "lat",
478
+ "ip",
479
+ "lon",
480
+ "company-code",
481
+ "device-setting-channel",
482
+ "network-connection-mode",
483
+ "hardware-ver",
484
+ "public-ip"
485
+ ]
486
+ },
487
+ {
488
+ "iid": 1,
489
+ "name": "iot-device",
490
+ "properties": [
491
+ "iot-state",
492
+ "iot-power",
493
+ "push-switch",
494
+ "mqtt-check"
495
+ ]
496
+ },
497
+ {
498
+ "iid": 12,
499
+ "name": "camera-ai",
500
+ "properties": [
501
+ "smart-detection-type",
502
+ "on"
503
+ ]
504
+ },
505
+ {
506
+ "iid": 8,
507
+ "name": "indicator-light",
508
+ "properties": [
509
+ "on",
510
+ "mode"
511
+ ]
512
+ },
513
+ {
514
+ "iid": 6,
515
+ "name": "memory-card-management",
516
+ "properties": [
517
+ "storage-used-space",
518
+ "storage-total-space",
519
+ "storage-status",
520
+ "sd-card-playback-enabled"
521
+ ]
522
+ },
523
+ {
524
+ "iid": 11,
525
+ "name": "motion-detection",
526
+ "properties": [
527
+ "sensitivity-motion",
528
+ "on",
529
+ "area-length",
530
+ "motion-zone",
531
+ "motion-zone-block-size",
532
+ "motion-zone-selected-block",
533
+ "edge-detection-type",
534
+ "motion-tag"
535
+ ]
536
+ },
537
+ {
538
+ "iid": 4,
539
+ "name": "siren",
540
+ "properties": [
541
+ "state",
542
+ "siren-on-ts"
543
+ ]
544
+ },
545
+ {
546
+ "iid": 5,
547
+ "name": "spotlight",
548
+ "properties": [
549
+ "on",
550
+ "enabled",
551
+ "brightness",
552
+ "motion-activate-light-switch",
553
+ "sunset-to-sunrise",
554
+ "motion-activate-light-schedule",
555
+ "trigger-source"
556
+ ]
557
+ },
558
+ {
559
+ "iid": 9,
560
+ "name": "wifi",
561
+ "properties": [
562
+ "on",
563
+ "signal-strength",
564
+ "wifi-ssid",
565
+ "wifi-encrypted-password"
566
+ ]
567
+ }
568
+ ]
569
+ case _:
570
+ raise NotImplementedError(f"No iot props for model ({model}) have been defined.")
@@ -12,14 +12,14 @@ from typing import List, Tuple, Any, Dict, Optional
12
12
  import aiohttp
13
13
 
14
14
  from .update_manager import DeviceUpdater, UpdateManager
15
- from ..const import PHONE_SYSTEM_TYPE, APP_VERSION, APP_VER, PHONE_ID, APP_NAME, OLIVE_APP_ID, APP_INFO, SC, SV
15
+ from ..const import PHONE_SYSTEM_TYPE, APP_VERSION, APP_VER, PHONE_ID, APP_NAME, OLIVE_APP_ID, APP_INFO, SC, SV, APP_PLATFORM, SOURCE
16
16
  from ..crypto import olive_create_signature
17
17
  from ..payload_factory import olive_create_hms_patch_payload, olive_create_hms_payload, \
18
18
  olive_create_hms_get_payload, ford_create_payload, olive_create_get_payload, olive_create_post_payload, \
19
- olive_create_user_info_payload
20
- from ..types import PropertyIDs, Device
19
+ olive_create_user_info_payload, devicemgmt_create_capabilities_payload, devicemgmt_get_iot_props_list
20
+ from ..types import PropertyIDs, Device, DeviceMgmtToggleType
21
21
  from ..utils import check_for_errors_standard, check_for_errors_hms, check_for_errors_lock, \
22
- check_for_errors_iot, wyze_encrypt
22
+ check_for_errors_iot, wyze_encrypt, check_for_errors_devicemgmt
23
23
  from ..wyze_auth_lib import WyzeAuthLib
24
24
 
25
25
  _LOGGER = logging.getLogger(__name__)
@@ -326,6 +326,121 @@ class BaseService:
326
326
  json=payload)
327
327
 
328
328
  check_for_errors_standard(self, response_json)
329
+
330
+ async def _run_action_devicemgmt(self, device: Device, type: str, value: str) -> None:
331
+ """
332
+ Wraps the devicemgmt-service-beta.wyze.com/device-management/api/action/run_action endpoint
333
+
334
+ :param device: The device for which to run the action
335
+ :param state: on or off
336
+ :return:
337
+ """
338
+
339
+ await self._auth_lib.refresh_if_should()
340
+
341
+ capabilities = devicemgmt_create_capabilities_payload(type, value)
342
+
343
+ payload = {
344
+ "capabilities": [
345
+ capabilities
346
+ ],
347
+ "nonce": int(time.time() * 1000),
348
+ "targetInfo": {
349
+ "id": device.mac,
350
+ "productModel": device.product_model,
351
+ "type": "DEVICE"
352
+ },
353
+ "transactionId": "0a5b20591fedd4du1b93f90743ba0csd" # OG cam needs this (doesn't matter what the value is)
354
+ }
355
+
356
+ headers = {
357
+ "authorization": self._auth_lib.token.access_token,
358
+ }
359
+
360
+ response_json = await self._auth_lib.post("https://devicemgmt-service-beta.wyze.com/device-management/api/action/run_action",
361
+ json=payload, headers=headers)
362
+
363
+ check_for_errors_iot(self, response_json)
364
+
365
+ async def _set_toggle(self, device: Device, toggleType: DeviceMgmtToggleType, state: str) -> None:
366
+ """
367
+ Wraps the ai-subscription-service-beta.wyzecam.com/v4/subscription-service/toggle-management endpoint
368
+
369
+ :param device: The device for which to get the state
370
+ :param toggleType: Enum for the toggle type
371
+ :param state: String state to set for the toggle
372
+ """
373
+
374
+ await self._auth_lib.refresh_if_should()
375
+
376
+ payload = {
377
+ "data": [
378
+ {
379
+ "device_firmware": "1234567890",
380
+ "device_id": device.mac,
381
+ "device_model": device.product_model,
382
+ "page_id": [
383
+ toggleType.pageId
384
+ ],
385
+ "toggle_update": [
386
+ {
387
+ "toggle_id": toggleType.toggleId,
388
+ "toggle_status": state
389
+ }
390
+ ]
391
+ }
392
+ ],
393
+ "nonce": str(int(time.time() * 1000))
394
+ }
395
+
396
+
397
+ signature = olive_create_signature(payload, self._auth_lib.token.access_token)
398
+ headers = {
399
+ "access_token": self._auth_lib.token.access_token,
400
+ "timestamp": str(int(time.time() * 1000)),
401
+ "appid": OLIVE_APP_ID,
402
+ "source": SOURCE,
403
+ "signature2": signature,
404
+ "appplatform": APP_PLATFORM,
405
+ "appversion": APP_VERSION,
406
+ "requestid": "35374158s4s313b9a2be7c057f2da5d1"
407
+ }
408
+
409
+ response_json = await self._auth_lib.put("https://ai-subscription-service-beta.wyzecam.com/v4/subscription-service/toggle-management",
410
+ json=payload, headers=headers)
411
+
412
+ check_for_errors_devicemgmt(self, response_json)
413
+
414
+ async def _get_iot_prop_devicemgmt(self, device: Device) -> Dict[str, Any]:
415
+ """
416
+ Wraps the devicemgmt-service-beta.wyze.com/device-management/api/device-property/get_iot_prop endpoint
417
+
418
+ :param device: The device for which to get the state
419
+ :return:
420
+ """
421
+
422
+ await self._auth_lib.refresh_if_should()
423
+
424
+ payload = {
425
+ "capabilities": devicemgmt_get_iot_props_list(device.product_model),
426
+ "nonce": int(time.time() * 1000),
427
+ "targetInfo": {
428
+ "id": device.mac,
429
+ "productModel": device.product_model,
430
+ "type": "DEVICE"
431
+ }
432
+ }
433
+
434
+ headers = {
435
+ "authorization": self._auth_lib.token.access_token,
436
+ }
437
+
438
+ response_json = await self._auth_lib.post("https://devicemgmt-service-beta.wyze.com/device-management/api/device-property/get_iot_prop",
439
+ json=payload, headers=headers)
440
+
441
+ check_for_errors_iot(self, response_json)
442
+
443
+ return response_json
329
444
 
330
445
  async def _set_property(self, device: Device, pid: str, pvalue: str) -> None:
331
446
  """
@@ -14,11 +14,14 @@ from aiohttp import ClientOSError, ContentTypeError
14
14
  from ..exceptions import UnknownApiError
15
15
  from .base_service import BaseService
16
16
  from .update_manager import DeviceUpdater
17
- from ..types import Device, DeviceTypes, Event, PropertyIDs
17
+ from ..types import Device, DeviceTypes, Event, PropertyIDs, DeviceMgmtToggleProps
18
18
  from ..utils import return_event_for_device, create_pid_pair
19
19
 
20
20
  _LOGGER = logging.getLogger(__name__)
21
21
 
22
+ # NOTE: Make sure to also define props in devicemgmt_create_capabilities_payload()
23
+ DEVICEMGMT_API_MODELS = ["LD_CFP", "AN_RSCW", "GW_GC1"] # Floodlight pro, battery cam pro, and OG use a diffrent api (devicemgmt)
24
+
22
25
 
23
26
  class Camera(Device):
24
27
  def __init__(self, dictionary: Dict[Any, Any]):
@@ -50,20 +53,35 @@ class CameraService(BaseService):
50
53
  camera.last_event_ts = event.event_ts
51
54
 
52
55
  # Update camera state
53
- state_response: List[Tuple[PropertyIDs, Any]] = await self._get_property_list(camera)
54
- for property, value in state_response:
55
- if property is PropertyIDs.AVAILABLE:
56
- camera.available = value == "1"
57
- if property is PropertyIDs.ON:
58
- camera.on = value == "1"
59
- if property is PropertyIDs.CAMERA_SIREN:
60
- camera.siren = value == "1"
61
- if property is PropertyIDs.FLOOD_LIGHT:
62
- camera.floodlight = value == "1"
63
- if property is PropertyIDs.NOTIFICATION:
64
- camera.notify = value == "1"
65
- if property is PropertyIDs.MOTION_DETECTION:
66
- camera.motion = value == "1"
56
+ if (camera.product_model in DEVICEMGMT_API_MODELS): # New api
57
+ state_response: Dict[str, Any] = await self._get_iot_prop_devicemgmt(camera)
58
+ for propCategory in state_response['data']['capabilities']:
59
+ if propCategory['name'] == "camera":
60
+ camera.motion = propCategory['properties']['motion-detect-recording']
61
+ if propCategory['name'] == "floodlight" or propCategory['name'] == "spotlight":
62
+ camera.floodlight = propCategory['properties']['on']
63
+ if propCategory['name'] == "siren":
64
+ camera.siren = propCategory['properties']['state']
65
+ if propCategory['name'] == "iot-device":
66
+ camera.notify = propCategory['properties']['push-switch']
67
+ camera.on = propCategory['properties']['iot-power']
68
+ camera.available = propCategory['properties']['iot-state']
69
+
70
+ else: # All other cam types (old api?)
71
+ state_response: List[Tuple[PropertyIDs, Any]] = await self._get_property_list(camera)
72
+ for property, value in state_response:
73
+ if property is PropertyIDs.AVAILABLE:
74
+ camera.available = value == "1"
75
+ if property is PropertyIDs.ON:
76
+ camera.on = value == "1"
77
+ if property is PropertyIDs.CAMERA_SIREN:
78
+ camera.siren = value == "1"
79
+ if property is PropertyIDs.FLOOD_LIGHT:
80
+ camera.floodlight = value == "1"
81
+ if property is PropertyIDs.NOTIFICATION:
82
+ camera.notify = value == "1"
83
+ if property is PropertyIDs.MOTION_DETECTION:
84
+ camera.motion = value == "1"
67
85
 
68
86
  return camera
69
87
 
@@ -102,35 +120,53 @@ class CameraService(BaseService):
102
120
  return [Camera(camera.raw_dict) for camera in cameras]
103
121
 
104
122
  async def turn_on(self, camera: Camera):
105
- await self._run_action(camera, "power_on")
123
+ if (camera.product_model in DEVICEMGMT_API_MODELS): await self._run_action_devicemgmt(camera, "power", "wakeup") # Some camera models use a diffrent api
124
+ else: await self._run_action(camera, "power_on")
106
125
 
107
126
  async def turn_off(self, camera: Camera):
108
- await self._run_action(camera, "power_off")
127
+ if (camera.product_model in DEVICEMGMT_API_MODELS): await self._run_action_devicemgmt(camera, "power", "sleep") # Some camera models use a diffrent api
128
+ else: await self._run_action(camera, "power_off")
109
129
 
110
130
  async def siren_on(self, camera: Camera):
111
- await self._run_action(camera, "siren_on")
131
+ if (camera.product_model in DEVICEMGMT_API_MODELS): await self._run_action_devicemgmt(camera, "siren", "siren-on") # Some camera models use a diffrent api
132
+ else: await self._run_action(camera, "siren_on")
112
133
 
113
134
  async def siren_off(self, camera: Camera):
114
- await self._run_action(camera, "siren_off")
135
+ if (camera.product_model in DEVICEMGMT_API_MODELS): await self._run_action_devicemgmt(camera, "siren", "siren-off") # Some camera models use a diffrent api
136
+ else: await self._run_action(camera, "siren_off")
115
137
 
138
+ # Also controls lamp socket and BCP spotlight
116
139
  async def floodlight_on(self, camera: Camera):
117
- await self._set_property(camera, PropertyIDs.FLOOD_LIGHT.value, "1")
140
+ if (camera.product_model == "AN_RSCW"): await self._run_action_devicemgmt(camera, "spotlight", "1") # Battery cam pro integrated spotlight is controllable
141
+ elif (camera.product_model in DEVICEMGMT_API_MODELS): await self._run_action_devicemgmt(camera, "floodlight", "1") # Some camera models use a diffrent api
142
+ else: await self._set_property(camera, PropertyIDs.FLOOD_LIGHT.value, "1")
118
143
 
144
+ # Also controls lamp socket and BCP spotlight
119
145
  async def floodlight_off(self, camera: Camera):
120
- await self._set_property(camera, PropertyIDs.FLOOD_LIGHT.value, "2")
146
+ if (camera.product_model == "AN_RSCW"): await self._run_action_devicemgmt(camera, "spotlight", "0") # Battery cam pro integrated spotlight is controllable
147
+ elif (camera.product_model in DEVICEMGMT_API_MODELS): await self._run_action_devicemgmt(camera, "floodlight", "0") # Some camera models use a diffrent api
148
+ else: await self._set_property(camera, PropertyIDs.FLOOD_LIGHT.value, "2")
121
149
 
122
150
  async def turn_on_notifications(self, camera: Camera):
123
- await self._set_property(camera, PropertyIDs.NOTIFICATION.value, "1")
151
+ if (camera.product_model in DEVICEMGMT_API_MODELS): await self._set_toggle(camera, DeviceMgmtToggleProps.NOTIFICATION_TOGGLE.value, "1")
152
+ else: await self._set_property(camera, PropertyIDs.NOTIFICATION.value, "1")
124
153
 
125
154
  async def turn_off_notifications(self, camera: Camera):
126
- await self._set_property(camera, PropertyIDs.NOTIFICATION.value, "0")
155
+ if (camera.product_model in DEVICEMGMT_API_MODELS): await self._set_toggle(camera, DeviceMgmtToggleProps.NOTIFICATION_TOGGLE.value, "0")
156
+ else: await self._set_property(camera, PropertyIDs.NOTIFICATION.value, "0")
127
157
 
128
158
  # Both properties need to be set on newer cams, older cameras seem to only react
129
159
  # to the first property but it doesnt hurt to set both
130
160
  async def turn_on_motion_detection(self, camera: Camera):
131
- await self._set_property(camera, PropertyIDs.MOTION_DETECTION.value, "1")
132
- await self._set_property(camera, PropertyIDs.MOTION_DETECTION_TOGGLE.value, "1")
161
+ if (camera.product_model in DEVICEMGMT_API_MODELS): await self._set_toggle(camera, DeviceMgmtToggleProps.EVENT_RECORDING_TOGGLE.value, "1")
162
+ elif (camera.product_model in ["WVOD1", "HL_WCO2"]): await self._set_property_list(camera, [create_pid_pair(PropertyIDs.WCO_MOTION_DETECTION, "1")])
163
+ else:
164
+ await self._set_property(camera, PropertyIDs.MOTION_DETECTION.value, "1")
165
+ await self._set_property(camera, PropertyIDs.MOTION_DETECTION_TOGGLE.value, "1")
133
166
 
134
167
  async def turn_off_motion_detection(self, camera: Camera):
135
- await self._set_property(camera, PropertyIDs.MOTION_DETECTION.value, "0")
136
- await self._set_property(camera, PropertyIDs.MOTION_DETECTION_TOGGLE.value, "0")
168
+ if (camera.product_model in DEVICEMGMT_API_MODELS): await self._set_toggle(camera, DeviceMgmtToggleProps.EVENT_RECORDING_TOGGLE.value, "0")
169
+ elif (camera.product_model in ["WVOD1", "HL_WCO2"]): await self._set_property_list(camera, [create_pid_pair(PropertyIDs.WCO_MOTION_DETECTION, "0")])
170
+ else:
171
+ await self._set_property(camera, PropertyIDs.MOTION_DETECTION.value, "0")
172
+ await self._set_property(camera, PropertyIDs.MOTION_DETECTION_TOGGLE.value, "0")
@@ -74,7 +74,6 @@ class WallSwitchService(BaseService):
74
74
  return [WallSwitch(switch.raw_dict) for switch in switches]
75
75
 
76
76
  async def turn_on(self, switch: WallSwitch):
77
- logging.warn("%s", switch.single_press_type)
78
77
  if switch.single_press_type == SinglePressType.IOT:
79
78
  await self.iot_on(switch)
80
79
  else:
wyzeapy/types.py CHANGED
@@ -102,10 +102,11 @@ class PropertyIDs(Enum):
102
102
  CONTACT_STATE = "P1301"
103
103
  MOTION_STATE = "P1302"
104
104
  CAMERA_SIREN = "P1049"
105
- FLOOD_LIGHT = "P1056"
105
+ FLOOD_LIGHT = "P1056" # Also lamp socket on v3/v4 with lamp socket accessory
106
106
  SUN_MATCH = "P1528"
107
107
  MOTION_DETECTION = "P1047" # Current Motion Detection State of the Camera
108
108
  MOTION_DETECTION_TOGGLE = "P1001" # This toggles Camera Motion Detection On/Off
109
+ WCO_MOTION_DETECTION = "P1029" # Wyze cam outdoor requires both P1047 and P1029 to be set. P1029 is set via set_property_list
109
110
 
110
111
 
111
112
  class WallSwitchProps(Enum):
@@ -207,10 +208,13 @@ class HMSStatus(Enum):
207
208
  AWAY = 'away'
208
209
 
209
210
 
211
+ class DeviceMgmtToggleType:
212
+ def __init__(self, pageId, toggleId):
213
+ self.pageId = pageId
214
+ self.toggleId = toggleId
210
215
 
211
216
 
212
-
213
-
214
-
215
-
217
+ class DeviceMgmtToggleProps(Enum):
218
+ EVENT_RECORDING_TOGGLE = DeviceMgmtToggleType("cam_event_recording", "ge.motion_detect_recording")
219
+ NOTIFICATION_TOGGLE = DeviceMgmtToggleType("cam_device_notify", "ge.push_switch")
216
220
 
wyzeapy/utils.py CHANGED
@@ -73,22 +73,23 @@ def create_password(password: str) -> str:
73
73
 
74
74
 
75
75
  def check_for_errors_standard(service, response_json: Dict[str, Any]) -> None:
76
- if response_json['code'] != ResponseCodes.SUCCESS.value:
77
- if response_json['code'] == ResponseCodes.PARAMETER_ERROR.value:
78
- raise ParameterError(response_json)
79
- elif response_json['code'] == ResponseCodes.ACCESS_TOKEN_ERROR.value:
76
+ response_code = response_json['code']
77
+ if response_code != ResponseCodes.SUCCESS.value:
78
+ if response_code == ResponseCodes.PARAMETER_ERROR.value:
79
+ raise ParameterError(response_code, response_json['msg'])
80
+ elif response_code == ResponseCodes.ACCESS_TOKEN_ERROR.value:
80
81
  service._auth_lib.token.expired = True
81
- raise AccessTokenError("Access Token expired, attempting to refresh")
82
- elif response_json['code'] == ResponseCodes.DEVICE_OFFLINE.value:
82
+ raise AccessTokenError(response_code, "Access Token expired, attempting to refresh")
83
+ elif response_code == ResponseCodes.DEVICE_OFFLINE.value:
83
84
  return
84
85
  else:
85
- raise UnknownApiError(response_json)
86
+ raise UnknownApiError(response_code, response_json['msg'])
86
87
 
87
88
 
88
89
  def check_for_errors_lock(service, response_json: Dict[str, Any]) -> None:
89
90
  if response_json['ErrNo'] != 0:
90
91
  if response_json.get('code') == ResponseCodes.PARAMETER_ERROR.value:
91
- raise ParameterError
92
+ raise ParameterError(response_json)
92
93
  elif response_json.get('code') == ResponseCodes.ACCESS_TOKEN_ERROR.value:
93
94
  service._auth_lib.token.expired = True
94
95
  raise AccessTokenError("Access Token expired, attempting to refresh")
@@ -96,6 +97,15 @@ def check_for_errors_lock(service, response_json: Dict[str, Any]) -> None:
96
97
  raise UnknownApiError(response_json)
97
98
 
98
99
 
100
+ def check_for_errors_devicemgmt(service, response_json: Dict[Any, Any]) -> None:
101
+ if response_json['status'] != 200:
102
+ if "InvalidTokenError>" in response_json['response']['errors'][0]['message']:
103
+ service._auth_lib.token.expired = True
104
+ raise AccessTokenError("Access Token expired, attempting to refresh")
105
+ else:
106
+ raise UnknownApiError(response_json)
107
+
108
+
99
109
  def check_for_errors_iot(service, response_json: Dict[Any, Any]) -> None:
100
110
  if response_json['code'] != 1:
101
111
  if str(response_json['code']) == ResponseCodes.ACCESS_TOKEN_ERROR.value:
wyzeapy/wyze_auth_lib.py CHANGED
@@ -267,6 +267,23 @@ class WyzeAuthLib:
267
267
  except ContentTypeError:
268
268
  _LOGGER.debug(f"Response: {response}")
269
269
  return await response.json()
270
+
271
+ async def put(self, url, json=None, headers=None, data=None) -> Dict[Any, Any]:
272
+ async with ClientSession(connector=TCPConnector(ttl_dns_cache=(30 * 60))) as _session:
273
+ response = await _session.put(url, json=json, headers=headers, data=data)
274
+ # Relocated these below as the sanitization seems to modify the data before it goes to the post.
275
+ _LOGGER.debug("Request:")
276
+ _LOGGER.debug(f"url: {url}")
277
+ _LOGGER.debug(f"json: {self.sanitize(json)}")
278
+ _LOGGER.debug(f"headers: {self.sanitize(headers)}")
279
+ _LOGGER.debug(f"data: {self.sanitize(data)}")
280
+ # Log the response.json() if it exists, if not log the response.
281
+ try:
282
+ response_json = await response.json()
283
+ _LOGGER.debug(f"Response Json: {self.sanitize(response_json)}")
284
+ except ContentTypeError:
285
+ _LOGGER.debug(f"Response: {response}")
286
+ return await response.json()
270
287
 
271
288
  async def get(self, url, headers=None, params=None) -> Dict[Any, Any]:
272
289
  async with ClientSession(connector=TCPConnector(ttl_dns_cache=(30 * 60))) as _session:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: wyzeapy
3
- Version: 0.5.23
3
+ Version: 0.5.25
4
4
  Summary: A library for interacting with Wyze devices
5
5
  License: GPL-3.0-only
6
6
  Author: Katie Mulliken
@@ -10,6 +10,6 @@ Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
10
10
  Classifier: Programming Language :: Python :: 3
11
11
  Classifier: Programming Language :: Python :: 3.11
12
12
  Classifier: Programming Language :: Python :: 3.12
13
- Requires-Dist: aiodns (>=3.0.0,<4.0.0)
14
- Requires-Dist: aiohttp (>=3.7,<4.0)
15
- Requires-Dist: pycryptodome (>=3.12.0,<4.0.0)
13
+ Requires-Dist: aiodns (>=3.2.0,<4.0.0)
14
+ Requires-Dist: aiohttp (>=3.10.8,<4.0.0)
15
+ Requires-Dist: pycryptodome (>=3.21.0,<4.0.0)
@@ -0,0 +1,23 @@
1
+ wyzeapy/__init__.py,sha256=GqTfzIPOJLdexQMtjvUsDR60oPB-rJGVSK_93FcCSuw,8918
2
+ wyzeapy/const.py,sha256=dm8-tXt_Tl75QXwr_DFfS6nQxQD8lkepWU7RveFVFkI,1054
3
+ wyzeapy/crypto.py,sha256=EI9WkKq4jS0nZS5CIEOSYD_9WtM7kEzrhqEg6YdPUIo,1342
4
+ wyzeapy/exceptions.py,sha256=BVRF7GMMC2T_ZSfGoIVHQsEjVQ3URd5xT6OYqfNxkY0,759
5
+ wyzeapy/payload_factory.py,sha256=PtnxN9awH4Wh7FPXrGhfHvyGPPYLLZc4XQ2ldZIZZjk,18897
6
+ wyzeapy/services/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
+ wyzeapy/services/base_service.py,sha256=6wRppqI5WD0KRHQiCek39PRNh6Ze7dd2bLN1nz5-uMI,28140
8
+ wyzeapy/services/bulb_service.py,sha256=n2Pvuunz5d7Iv275vDQjKxByowBDkVoVrztBLyDAOZE,6840
9
+ wyzeapy/services/camera_service.py,sha256=mco8YvhybiNUifBaMIf8oTGKc12D73EEAPydtB8ZhQA,9379
10
+ wyzeapy/services/hms_service.py,sha256=3Fhqlmk96dm8LFajjW6P40DCIYMudfN3YIDoVIonqGw,2459
11
+ wyzeapy/services/lock_service.py,sha256=cQ3A8nuovqPoofVBoo4dnnP-3GAkrf5Kgn0dPJWyhc0,1780
12
+ wyzeapy/services/sensor_service.py,sha256=xI--Zr4Dm6DuZrV5pNxBVA1C6rrfhG4T0AFkvXCxVP8,3252
13
+ wyzeapy/services/switch_service.py,sha256=8LGZ1MloLGEXLVy8PaUwfoEzCXdwefLIyGUc2aJ4yPc,2205
14
+ wyzeapy/services/thermostat_service.py,sha256=3yJ8Jx7WPROTkiD2868QrfALFR1QB6_JI4LqcMzOOVc,5228
15
+ wyzeapy/services/update_manager.py,sha256=036ClmJMFzXjD03mfg5DIyjB3xwqHoWkmpU2tcquc5Q,6132
16
+ wyzeapy/services/wall_switch_service.py,sha256=gc6RDDJ5mceiKQUUs0n1wUKM2Y9Hj4_V2SLX9PxRa0g,4262
17
+ wyzeapy/types.py,sha256=22v06L9tugwo2BS-lzM7fRbQHkk4a_FQhzTNlpA7tPA,6366
18
+ wyzeapy/utils.py,sha256=vxwshLArRvP7g3qpFBdFcXBCM2741ItNSSsfdeHD5GU,4755
19
+ wyzeapy/wyze_auth_lib.py,sha256=ERDuTQiTpbcNH5-Z38dGFTbLynvkejghp_eCYtb7OQQ,13712
20
+ wyzeapy-0.5.25.dist-info/LICENSES/GPL-3.0-only.txt,sha256=tqi_Y64slbCqJW7ndGgNe9GPIfRX2nVGb3YQs7FqzE4,34670
21
+ wyzeapy-0.5.25.dist-info/METADATA,sha256=OxS2dfid9lEGAW4xD-NvfvU8EYS8ICcTfC4z3wRsj98,562
22
+ wyzeapy-0.5.25.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
23
+ wyzeapy-0.5.25.dist-info/RECORD,,
@@ -1,23 +0,0 @@
1
- wyzeapy/__init__.py,sha256=GqTfzIPOJLdexQMtjvUsDR60oPB-rJGVSK_93FcCSuw,8918
2
- wyzeapy/const.py,sha256=HqihFV6Hb9yZmUzxrhcrX93cJ2urvtCyUz9s8jP-IVU,989
3
- wyzeapy/crypto.py,sha256=EI9WkKq4jS0nZS5CIEOSYD_9WtM7kEzrhqEg6YdPUIo,1342
4
- wyzeapy/exceptions.py,sha256=4eBi7YRWafJlG5M7Cqr_swGKqBggm0Iomf5-Gv6ecgo,871
5
- wyzeapy/payload_factory.py,sha256=bWRo-xMFfhtxzUcsYd2Eo56MqXmWYuDi8J865Uj_L0E,1920
6
- wyzeapy/services/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
- wyzeapy/services/base_service.py,sha256=37747wJ9_Sy-rQzNetGYlJiGlnv4uWq7DqFCMveaIj8,23794
8
- wyzeapy/services/bulb_service.py,sha256=n2Pvuunz5d7Iv275vDQjKxByowBDkVoVrztBLyDAOZE,6840
9
- wyzeapy/services/camera_service.py,sha256=bkvd5Na-mh55xoR0CsTLTHI8685j5EoBKn1H_4mwBO8,5694
10
- wyzeapy/services/hms_service.py,sha256=3Fhqlmk96dm8LFajjW6P40DCIYMudfN3YIDoVIonqGw,2459
11
- wyzeapy/services/lock_service.py,sha256=cQ3A8nuovqPoofVBoo4dnnP-3GAkrf5Kgn0dPJWyhc0,1780
12
- wyzeapy/services/sensor_service.py,sha256=xI--Zr4Dm6DuZrV5pNxBVA1C6rrfhG4T0AFkvXCxVP8,3252
13
- wyzeapy/services/switch_service.py,sha256=8LGZ1MloLGEXLVy8PaUwfoEzCXdwefLIyGUc2aJ4yPc,2205
14
- wyzeapy/services/thermostat_service.py,sha256=3yJ8Jx7WPROTkiD2868QrfALFR1QB6_JI4LqcMzOOVc,5228
15
- wyzeapy/services/update_manager.py,sha256=036ClmJMFzXjD03mfg5DIyjB3xwqHoWkmpU2tcquc5Q,6132
16
- wyzeapy/services/wall_switch_service.py,sha256=1PmDyKh6WR8ZVVHsj9CAiIDOyG18D0wmH64U2XluT5I,4315
17
- wyzeapy/types.py,sha256=rUjW2n21ArbxxRpFQGZ5pUIt4bb6JJAkTXElSFHXLiU,5828
18
- wyzeapy/utils.py,sha256=t2rktlOBXJ5O8YDwVkDwbEpBCwNA7TpiwVVeiDdpzu8,4267
19
- wyzeapy/wyze_auth_lib.py,sha256=NWlWEMMNeEuCYLo7rxWd1EpJWPsP3huhJatfKTCkKm0,12713
20
- wyzeapy-0.5.23.dist-info/LICENSES/GPL-3.0-only.txt,sha256=tqi_Y64slbCqJW7ndGgNe9GPIfRX2nVGb3YQs7FqzE4,34670
21
- wyzeapy-0.5.23.dist-info/METADATA,sha256=0kXEypIc1o-__Yrfu0yj-MI_5-dUvGAUq5GxOWFy-5o,557
22
- wyzeapy-0.5.23.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
23
- wyzeapy-0.5.23.dist-info/RECORD,,