wyzeapy 0.5.20__py3-none-any.whl → 0.5.22.post1__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/__init__.py +12 -5
- wyzeapy/const.py +1 -1
- wyzeapy/crypto.py +1 -1
- wyzeapy/exceptions.py +1 -1
- wyzeapy/payload_factory.py +1 -1
- wyzeapy/services/base_service.py +48 -18
- wyzeapy/services/bulb_service.py +1 -1
- wyzeapy/services/camera_service.py +7 -15
- wyzeapy/services/hms_service.py +1 -1
- wyzeapy/services/lock_service.py +10 -2
- wyzeapy/services/sensor_service.py +1 -1
- wyzeapy/services/switch_service.py +18 -1
- wyzeapy/services/thermostat_service.py +1 -1
- wyzeapy/services/wall_switch_service.py +1 -1
- wyzeapy/types.py +1 -1
- wyzeapy/utils.py +20 -11
- wyzeapy/wyze_auth_lib.py +15 -7
- {wyzeapy-0.5.20.dist-info → wyzeapy-0.5.22.post1.dist-info}/METADATA +3 -7
- wyzeapy-0.5.22.post1.dist-info/RECORD +23 -0
- {wyzeapy-0.5.20.dist-info → wyzeapy-0.5.22.post1.dist-info}/WHEEL +1 -1
- wyzeapy-0.5.20.dist-info/RECORD +0 -23
- {wyzeapy-0.5.20.dist-info → wyzeapy-0.5.22.post1.dist-info}/LICENSES/GPL-3.0-only.txt +0 -0
wyzeapy/__init__.py
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
# You may use, distribute and modify this code under the terms
|
|
3
3
|
# of the attached license. You should have received a copy of
|
|
4
4
|
# the license with this file. If not, please write to:
|
|
5
|
-
#
|
|
5
|
+
# katie@mulliken.net to receive a copy
|
|
6
6
|
import logging
|
|
7
7
|
from inspect import iscoroutinefunction
|
|
8
8
|
from typing import List, Optional, Set, Callable
|
|
@@ -17,10 +17,9 @@ from .services.camera_service import CameraService
|
|
|
17
17
|
from .services.hms_service import HMSService
|
|
18
18
|
from .services.lock_service import LockService
|
|
19
19
|
from .services.sensor_service import SensorService
|
|
20
|
-
from .services.switch_service import SwitchService
|
|
20
|
+
from .services.switch_service import SwitchService, SwitchUsageService
|
|
21
21
|
from .services.thermostat_service import ThermostatService
|
|
22
22
|
from .services.wall_switch_service import WallSwitchService
|
|
23
|
-
from .utils import check_for_errors_standard
|
|
24
23
|
from .wyze_auth_lib import WyzeAuthLib, Token
|
|
25
24
|
|
|
26
25
|
_LOGGER = logging.getLogger(__name__)
|
|
@@ -40,6 +39,7 @@ class Wyzeapy:
|
|
|
40
39
|
self._lock_service = None
|
|
41
40
|
self._sensor_service = None
|
|
42
41
|
self._wall_switch_service = None
|
|
42
|
+
self._switch_usage_service = None
|
|
43
43
|
self._email = None
|
|
44
44
|
self._password = None
|
|
45
45
|
self._key_id = None
|
|
@@ -83,8 +83,8 @@ class Wyzeapy:
|
|
|
83
83
|
email, password, key_id, api_key, token, self.execute_token_callbacks
|
|
84
84
|
)
|
|
85
85
|
if token:
|
|
86
|
-
# User token supplied,
|
|
87
|
-
await self._auth_lib.
|
|
86
|
+
# User token supplied, refresh on startup
|
|
87
|
+
await self._auth_lib.refresh()
|
|
88
88
|
else:
|
|
89
89
|
await self._auth_lib.get_token_with_username_password(
|
|
90
90
|
email, password, key_id, api_key
|
|
@@ -255,3 +255,10 @@ class Wyzeapy:
|
|
|
255
255
|
if self._wall_switch_service is None:
|
|
256
256
|
self._wall_switch_service = WallSwitchService(self._auth_lib)
|
|
257
257
|
return self._wall_switch_service
|
|
258
|
+
|
|
259
|
+
@property
|
|
260
|
+
async def switch_usage_service(self) -> SwitchUsageService:
|
|
261
|
+
"""Returns an instance of the switch usage service"""
|
|
262
|
+
if self._switch_usage_service is None:
|
|
263
|
+
self._switch_usage_service = SwitchUsageService(self._auth_lib)
|
|
264
|
+
return self._switch_usage_service
|
wyzeapy/const.py
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
# You may use, distribute and modify this code under the terms
|
|
3
3
|
# of the attached license. You should have received a copy of
|
|
4
4
|
# the license with this file. If not, please write to:
|
|
5
|
-
#
|
|
5
|
+
# katie@mulliken.net to receive a copy
|
|
6
6
|
import uuid
|
|
7
7
|
|
|
8
8
|
# Here is where all the *magic* lives
|
wyzeapy/crypto.py
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
# You may use, distribute and modify this code under the terms
|
|
3
3
|
# of the attached license. You should have received a copy of
|
|
4
4
|
# the license with this file. If not, please write to:
|
|
5
|
-
#
|
|
5
|
+
# katie@mulliken.net to receive a copy
|
|
6
6
|
import hashlib
|
|
7
7
|
import hmac
|
|
8
8
|
import urllib.parse
|
wyzeapy/exceptions.py
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
# You may use, distribute and modify this code under the terms
|
|
3
3
|
# of the attached license. You should have received a copy of
|
|
4
4
|
# the license with this file. If not, please write to:
|
|
5
|
-
#
|
|
5
|
+
# katie@mulliken.net to receive a copy
|
|
6
6
|
from typing import Dict, Any
|
|
7
7
|
|
|
8
8
|
|
wyzeapy/payload_factory.py
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
# You may use, distribute and modify this code under the terms
|
|
3
3
|
# of the attached license. You should have received a copy of
|
|
4
4
|
# the license with this file. If not, please write to:
|
|
5
|
-
#
|
|
5
|
+
# katie@mulliken.net to receive a copy
|
|
6
6
|
import time
|
|
7
7
|
from typing import Any, Dict
|
|
8
8
|
|
wyzeapy/services/base_service.py
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
# You may use, distribute and modify this code under the terms
|
|
3
3
|
# of the attached license. You should have received a copy of
|
|
4
4
|
# the license with this file. If not, please write to:
|
|
5
|
-
#
|
|
5
|
+
# katie@mulliken.net to receive a copy
|
|
6
6
|
import asyncio
|
|
7
7
|
import json
|
|
8
8
|
import logging
|
|
@@ -73,7 +73,7 @@ class BaseService:
|
|
|
73
73
|
|
|
74
74
|
response_json = await self._auth_lib.post(url, json=payload)
|
|
75
75
|
|
|
76
|
-
check_for_errors_standard(response_json)
|
|
76
|
+
check_for_errors_standard(self, response_json)
|
|
77
77
|
|
|
78
78
|
async def get_user_profile(self) -> Dict[Any, Any]:
|
|
79
79
|
await self._auth_lib.refresh_if_should()
|
|
@@ -119,7 +119,7 @@ class BaseService:
|
|
|
119
119
|
response_json = await self._auth_lib.post("https://api.wyzecam.com/app/v2/home_page/get_object_list",
|
|
120
120
|
json=payload)
|
|
121
121
|
|
|
122
|
-
check_for_errors_standard(response_json)
|
|
122
|
+
check_for_errors_standard(self, response_json)
|
|
123
123
|
|
|
124
124
|
# Cache the devices so that update calls can pull more recent device_params
|
|
125
125
|
BaseService._devices = [Device(device) for device in response_json['data']['device_list']]
|
|
@@ -164,7 +164,7 @@ class BaseService:
|
|
|
164
164
|
response_json = await self._auth_lib.post("https://api.wyzecam.com/app/v2/device/get_property_list",
|
|
165
165
|
json=payload)
|
|
166
166
|
|
|
167
|
-
check_for_errors_standard(response_json)
|
|
167
|
+
check_for_errors_standard(self, response_json)
|
|
168
168
|
properties = response_json['data']['property_list']
|
|
169
169
|
|
|
170
170
|
property_list = []
|
|
@@ -209,7 +209,7 @@ class BaseService:
|
|
|
209
209
|
response_json = await self._auth_lib.post("https://api.wyzecam.com/app/v2/device/set_property_list",
|
|
210
210
|
json=payload)
|
|
211
211
|
|
|
212
|
-
check_for_errors_standard(response_json)
|
|
212
|
+
check_for_errors_standard(self, response_json)
|
|
213
213
|
|
|
214
214
|
async def _run_action_list(self, device: Device, plist: List[Dict[Any, Any]]) -> None:
|
|
215
215
|
"""
|
|
@@ -250,7 +250,7 @@ class BaseService:
|
|
|
250
250
|
response_json = await self._auth_lib.post("https://api.wyzecam.com/app/v2/auto/run_action_list",
|
|
251
251
|
json=payload)
|
|
252
252
|
|
|
253
|
-
check_for_errors_standard(response_json)
|
|
253
|
+
check_for_errors_standard(self, response_json)
|
|
254
254
|
|
|
255
255
|
async def _get_event_list(self, count: int) -> Dict[Any, Any]:
|
|
256
256
|
"""
|
|
@@ -291,7 +291,7 @@ class BaseService:
|
|
|
291
291
|
response_json = await self._auth_lib.post("https://api.wyzecam.com/app/v2/device/get_event_list",
|
|
292
292
|
json=payload)
|
|
293
293
|
|
|
294
|
-
check_for_errors_standard(response_json)
|
|
294
|
+
check_for_errors_standard(self, response_json)
|
|
295
295
|
return response_json
|
|
296
296
|
|
|
297
297
|
async def _run_action(self, device: Device, action: str) -> None:
|
|
@@ -325,7 +325,7 @@ class BaseService:
|
|
|
325
325
|
response_json = await self._auth_lib.post("https://api.wyzecam.com/app/v2/auto/run_action",
|
|
326
326
|
json=payload)
|
|
327
327
|
|
|
328
|
-
check_for_errors_standard(response_json)
|
|
328
|
+
check_for_errors_standard(self, response_json)
|
|
329
329
|
|
|
330
330
|
async def _set_property(self, device: Device, pid: str, pvalue: str) -> None:
|
|
331
331
|
"""
|
|
@@ -356,7 +356,7 @@ class BaseService:
|
|
|
356
356
|
response_json = await self._auth_lib.post("https://api.wyzecam.com/app/v2/device/set_property",
|
|
357
357
|
json=payload)
|
|
358
358
|
|
|
359
|
-
check_for_errors_standard(response_json)
|
|
359
|
+
check_for_errors_standard(self, response_json)
|
|
360
360
|
|
|
361
361
|
async def _monitoring_profile_active(self, hms_id: str, home: int, away: int) -> None:
|
|
362
362
|
"""
|
|
@@ -393,7 +393,7 @@ class BaseService:
|
|
|
393
393
|
}
|
|
394
394
|
]
|
|
395
395
|
response_json = await self._auth_lib.patch(url, headers=headers, params=query, json=payload)
|
|
396
|
-
check_for_errors_hms(response_json)
|
|
396
|
+
check_for_errors_hms(self, response_json)
|
|
397
397
|
|
|
398
398
|
async def _get_plan_binding_list_by_user(self) -> Dict[Any, Any]:
|
|
399
399
|
"""
|
|
@@ -419,7 +419,7 @@ class BaseService:
|
|
|
419
419
|
}
|
|
420
420
|
|
|
421
421
|
response_json = await self._auth_lib.get(url, headers=headers, params=payload)
|
|
422
|
-
check_for_errors_hms(response_json)
|
|
422
|
+
check_for_errors_hms(self, response_json)
|
|
423
423
|
return response_json
|
|
424
424
|
|
|
425
425
|
async def _disable_reme_alarm(self, hms_id: str) -> None:
|
|
@@ -441,7 +441,7 @@ class BaseService:
|
|
|
441
441
|
|
|
442
442
|
response_json = await self._auth_lib.delete(url, headers=headers, json=payload)
|
|
443
443
|
|
|
444
|
-
check_for_errors_hms(response_json)
|
|
444
|
+
check_for_errors_hms(self, response_json)
|
|
445
445
|
|
|
446
446
|
async def _monitoring_profile_state_status(self, hms_id: str) -> Dict[Any, Any]:
|
|
447
447
|
"""
|
|
@@ -469,7 +469,7 @@ class BaseService:
|
|
|
469
469
|
|
|
470
470
|
response_json = await self._auth_lib.get(url, headers=headers, params=query)
|
|
471
471
|
|
|
472
|
-
check_for_errors_hms(response_json)
|
|
472
|
+
check_for_errors_hms(self, response_json)
|
|
473
473
|
return response_json
|
|
474
474
|
|
|
475
475
|
async def _lock_control(self, device: Device, action: str) -> None:
|
|
@@ -489,7 +489,7 @@ class BaseService:
|
|
|
489
489
|
|
|
490
490
|
response_json = await self._auth_lib.post(url, json=payload)
|
|
491
491
|
|
|
492
|
-
check_for_errors_lock(response_json)
|
|
492
|
+
check_for_errors_lock(self, response_json)
|
|
493
493
|
|
|
494
494
|
async def _get_lock_info(self, device: Device) -> Dict[str, Optional[Any]]:
|
|
495
495
|
await self._auth_lib.refresh_if_should()
|
|
@@ -509,7 +509,7 @@ class BaseService:
|
|
|
509
509
|
|
|
510
510
|
response_json = await self._auth_lib.get(url, params=payload)
|
|
511
511
|
|
|
512
|
-
check_for_errors_lock(response_json)
|
|
512
|
+
check_for_errors_lock(self, response_json)
|
|
513
513
|
|
|
514
514
|
return response_json
|
|
515
515
|
|
|
@@ -533,7 +533,7 @@ class BaseService:
|
|
|
533
533
|
response_json = await self._auth_lib.post("https://api.wyzecam.com/app/v2/device/get_device_Info",
|
|
534
534
|
json=payload)
|
|
535
535
|
|
|
536
|
-
check_for_errors_standard(response_json)
|
|
536
|
+
check_for_errors_standard(self, response_json)
|
|
537
537
|
|
|
538
538
|
return response_json
|
|
539
539
|
|
|
@@ -554,7 +554,7 @@ class BaseService:
|
|
|
554
554
|
|
|
555
555
|
response_json = await self._auth_lib.get(url, headers=headers, params=payload)
|
|
556
556
|
|
|
557
|
-
check_for_errors_iot(response_json)
|
|
557
|
+
check_for_errors_iot(self, response_json)
|
|
558
558
|
|
|
559
559
|
return response_json
|
|
560
560
|
|
|
@@ -579,7 +579,7 @@ class BaseService:
|
|
|
579
579
|
|
|
580
580
|
response_json = await self._auth_lib.post(url, headers=headers, data=payload_str)
|
|
581
581
|
|
|
582
|
-
check_for_errors_iot(response_json)
|
|
582
|
+
check_for_errors_iot(self, response_json)
|
|
583
583
|
|
|
584
584
|
async def _local_bulb_command(self, bulb, plist):
|
|
585
585
|
# await self._auth_lib.refresh_if_should()
|
|
@@ -613,3 +613,33 @@ class BaseService:
|
|
|
613
613
|
_LOGGER.warning("Failed to connect to bulb %s, reverting to cloud." % bulb.mac)
|
|
614
614
|
await self._run_action_list(bulb, plist)
|
|
615
615
|
bulb.cloud_fallback = True
|
|
616
|
+
|
|
617
|
+
async def _get_plug_history(
|
|
618
|
+
self, device: Device, start_time, end_time
|
|
619
|
+
) -> Dict[Any, Any]:
|
|
620
|
+
"""Wraps the https://api.wyzecam.com/app/v2/plug/usage_record_list endpoint"""
|
|
621
|
+
|
|
622
|
+
await self._auth_lib.refresh_if_should()
|
|
623
|
+
|
|
624
|
+
payload = {
|
|
625
|
+
"phone_id": PHONE_ID,
|
|
626
|
+
"date_begin": start_time,
|
|
627
|
+
"date_end": end_time,
|
|
628
|
+
"app_name": APP_NAME,
|
|
629
|
+
"app_version": APP_VERSION,
|
|
630
|
+
"sc": SC,
|
|
631
|
+
"device_mac": device.mac,
|
|
632
|
+
"sv": SV,
|
|
633
|
+
"phone_system_type": PHONE_SYSTEM_TYPE,
|
|
634
|
+
"app_ver": APP_VER,
|
|
635
|
+
"ts": int(time.time()),
|
|
636
|
+
"access_token": self._auth_lib.token.access_token,
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
response_json = await self._auth_lib.post(
|
|
640
|
+
"https://api.wyzecam.com/app/v2/plug/usage_record_list", json=payload
|
|
641
|
+
)
|
|
642
|
+
|
|
643
|
+
check_for_errors_standard(self, response_json)
|
|
644
|
+
|
|
645
|
+
return response_json["data"]["usage_record_list"]
|
wyzeapy/services/bulb_service.py
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
# You may use, distribute and modify this code under the terms
|
|
3
3
|
# of the attached license. You should have received a copy of
|
|
4
4
|
# the license with this file. If not, please write to:
|
|
5
|
-
#
|
|
5
|
+
# katie@mulliken.net to receive a copy
|
|
6
6
|
import logging
|
|
7
7
|
import re
|
|
8
8
|
from typing import Any, Dict, Optional, List
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
# You may use, distribute and modify this code under the terms
|
|
3
3
|
# of the attached license. You should have received a copy of
|
|
4
4
|
# the license with this file. If not, please write to:
|
|
5
|
-
#
|
|
5
|
+
# katie@mulliken.net to receive a copy
|
|
6
6
|
import asyncio
|
|
7
7
|
import logging
|
|
8
8
|
import time
|
|
@@ -120,25 +120,17 @@ class CameraService(BaseService):
|
|
|
120
120
|
await self._set_property(camera, PropertyIDs.FLOOD_LIGHT.value, "2")
|
|
121
121
|
|
|
122
122
|
async def turn_on_notifications(self, camera: Camera):
|
|
123
|
-
|
|
124
|
-
create_pid_pair(PropertyIDs.NOTIFICATION, "1")
|
|
125
|
-
]
|
|
126
|
-
|
|
127
|
-
await self._set_property_list(camera, plist)
|
|
123
|
+
await self._set_property(camera, PropertyIDs.NOTIFICATION.value, "1")
|
|
128
124
|
|
|
129
125
|
async def turn_off_notifications(self, camera: Camera):
|
|
130
|
-
|
|
131
|
-
create_pid_pair(PropertyIDs.NOTIFICATION, "0")
|
|
132
|
-
]
|
|
133
|
-
|
|
134
|
-
await self._set_property_list(camera, plist)
|
|
126
|
+
await self._set_property(camera, PropertyIDs.NOTIFICATION.value, "0")
|
|
135
127
|
|
|
136
|
-
#
|
|
137
|
-
#
|
|
128
|
+
# Both properties need to be set on newer cams, older cameras seem to only react
|
|
129
|
+
# to the first property but it doesnt hurt to set both
|
|
138
130
|
async def turn_on_motion_detection(self, camera: Camera):
|
|
139
|
-
await self._set_property(camera, PropertyIDs.
|
|
131
|
+
await self._set_property(camera, PropertyIDs.MOTION_DETECTION.value, "1")
|
|
140
132
|
await self._set_property(camera, PropertyIDs.MOTION_DETECTION_TOGGLE.value, "1")
|
|
141
133
|
|
|
142
134
|
async def turn_off_motion_detection(self, camera: Camera):
|
|
143
|
-
await self._set_property(camera, PropertyIDs.
|
|
135
|
+
await self._set_property(camera, PropertyIDs.MOTION_DETECTION.value, "0")
|
|
144
136
|
await self._set_property(camera, PropertyIDs.MOTION_DETECTION_TOGGLE.value, "0")
|
wyzeapy/services/hms_service.py
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
# You may use, distribute and modify this code under the terms
|
|
3
3
|
# of the attached license. You should have received a copy of
|
|
4
4
|
# the license with this file. If not, please write to:
|
|
5
|
-
#
|
|
5
|
+
# katie@mulliken.net to receive a copy
|
|
6
6
|
from enum import Enum
|
|
7
7
|
from typing import Optional
|
|
8
8
|
|
wyzeapy/services/lock_service.py
CHANGED
|
@@ -2,13 +2,15 @@
|
|
|
2
2
|
# You may use, distribute and modify this code under the terms
|
|
3
3
|
# of the attached license. You should have received a copy of
|
|
4
4
|
# the license with this file. If not, please write to:
|
|
5
|
-
#
|
|
5
|
+
# katie@mulliken.net to receive a copy
|
|
6
6
|
from .base_service import BaseService
|
|
7
7
|
from ..types import Device, DeviceTypes
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
class Lock(Device):
|
|
11
11
|
unlocked = False
|
|
12
|
+
locking = False
|
|
13
|
+
unlocking = False
|
|
12
14
|
door_open = False
|
|
13
15
|
trash_mode = False
|
|
14
16
|
|
|
@@ -26,7 +28,13 @@ class LockService(BaseService):
|
|
|
26
28
|
locker_status = lock.raw_dict.get("locker_status")
|
|
27
29
|
# Check if the door is locked
|
|
28
30
|
lock.unlocked = locker_status.get("hardlock") == 2
|
|
29
|
-
|
|
31
|
+
|
|
32
|
+
# Reset unlocking and locking if needed
|
|
33
|
+
if lock.unlocked and lock.unlocking:
|
|
34
|
+
lock.unlocking = False
|
|
35
|
+
if not lock.unlocked and lock.locking:
|
|
36
|
+
lock.locking = False
|
|
37
|
+
|
|
30
38
|
return lock
|
|
31
39
|
|
|
32
40
|
async def get_locks(self):
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
# You may use, distribute and modify this code under the terms
|
|
3
3
|
# of the attached license. You should have received a copy of
|
|
4
4
|
# the license with this file. If not, please write to:
|
|
5
|
-
#
|
|
5
|
+
# katie@mulliken.net to receive a copy
|
|
6
6
|
import asyncio
|
|
7
7
|
import logging
|
|
8
8
|
from threading import Thread
|
|
@@ -2,11 +2,12 @@
|
|
|
2
2
|
# You may use, distribute and modify this code under the terms
|
|
3
3
|
# of the attached license. You should have received a copy of
|
|
4
4
|
# the license with this file. If not, please write to:
|
|
5
|
-
#
|
|
5
|
+
# katie@mulliken.net to receive a copy
|
|
6
6
|
from typing import List, Dict, Any
|
|
7
7
|
|
|
8
8
|
from .base_service import BaseService
|
|
9
9
|
from ..types import Device, DeviceTypes, PropertyIDs
|
|
10
|
+
from datetime import timedelta, datetime
|
|
10
11
|
|
|
11
12
|
|
|
12
13
|
class Switch(Device):
|
|
@@ -44,3 +45,19 @@ class SwitchService(BaseService):
|
|
|
44
45
|
|
|
45
46
|
async def turn_off(self, switch: Switch):
|
|
46
47
|
await self._set_property(switch, PropertyIDs.ON.value, "0")
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class SwitchUsageService(SwitchService):
|
|
51
|
+
"""Class to retrieve the last 25 hours of usage data."""
|
|
52
|
+
|
|
53
|
+
async def update(self, device: Device):
|
|
54
|
+
start_time = int(
|
|
55
|
+
datetime.timestamp((datetime.now() - timedelta(hours=25))) * 1000
|
|
56
|
+
)
|
|
57
|
+
end_time = int(datetime.timestamp(datetime.now()) * 1000)
|
|
58
|
+
|
|
59
|
+
device.usage_history = await self._get_plug_history(
|
|
60
|
+
device, start_time, end_time
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
return device
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
# You may use, distribute and modify this code under the terms
|
|
3
3
|
# of the attached license. You should have received a copy of
|
|
4
4
|
# the license with this file. If not, please write to:
|
|
5
|
-
#
|
|
5
|
+
# katie@mulliken.net to receive a copy
|
|
6
6
|
import logging
|
|
7
7
|
from enum import Enum
|
|
8
8
|
from typing import Any, Dict, List
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
# You may use, distribute and modify this code under the terms
|
|
3
3
|
# of the attached license. You should have received a copy of
|
|
4
4
|
# the license with this file. If not, please write to:
|
|
5
|
-
#
|
|
5
|
+
# katie@mulliken.net to receive a copy
|
|
6
6
|
import logging
|
|
7
7
|
from enum import Enum
|
|
8
8
|
from typing import Any, Dict, List
|
wyzeapy/types.py
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
# You may use, distribute and modify this code under the terms
|
|
3
3
|
# of the attached license. You should have received a copy of
|
|
4
4
|
# the license with this file. If not, please write to:
|
|
5
|
-
#
|
|
5
|
+
# katie@mulliken.net to receive a copy
|
|
6
6
|
from enum import Enum
|
|
7
7
|
from typing import Union, List, Dict, Any
|
|
8
8
|
|
wyzeapy/utils.py
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
# You may use, distribute and modify this code under the terms
|
|
3
3
|
# of the attached license. You should have received a copy of
|
|
4
4
|
# the license with this file. If not, please write to:
|
|
5
|
-
#
|
|
5
|
+
# katie@mulliken.net to receive a copy
|
|
6
6
|
import base64
|
|
7
7
|
import hashlib
|
|
8
8
|
from typing import Dict, Any, List, Optional
|
|
@@ -72,36 +72,45 @@ def create_password(password: str) -> str:
|
|
|
72
72
|
return hashlib.md5(hex2.encode()).hexdigest()
|
|
73
73
|
|
|
74
74
|
|
|
75
|
-
def check_for_errors_standard(response_json: Dict[str, Any]) -> None:
|
|
75
|
+
def check_for_errors_standard(service, response_json: Dict[str, Any]) -> None:
|
|
76
76
|
if response_json['code'] != ResponseCodes.SUCCESS.value:
|
|
77
77
|
if response_json['code'] == ResponseCodes.PARAMETER_ERROR.value:
|
|
78
78
|
raise ParameterError(response_json)
|
|
79
79
|
elif response_json['code'] == ResponseCodes.ACCESS_TOKEN_ERROR.value:
|
|
80
|
-
|
|
80
|
+
service._auth_lib.token.expired = True
|
|
81
|
+
raise AccessTokenError("Access Token expired, attempting to refresh")
|
|
81
82
|
elif response_json['code'] == ResponseCodes.DEVICE_OFFLINE.value:
|
|
82
83
|
return
|
|
83
84
|
else:
|
|
84
85
|
raise UnknownApiError(response_json)
|
|
85
86
|
|
|
86
87
|
|
|
87
|
-
def check_for_errors_lock(response_json: Dict[str, Any]) -> None:
|
|
88
|
+
def check_for_errors_lock(service, response_json: Dict[str, Any]) -> None:
|
|
88
89
|
if response_json['ErrNo'] != 0:
|
|
89
90
|
if response_json.get('code') == ResponseCodes.PARAMETER_ERROR.value:
|
|
90
91
|
raise ParameterError
|
|
91
92
|
elif response_json.get('code') == ResponseCodes.ACCESS_TOKEN_ERROR.value:
|
|
92
|
-
|
|
93
|
+
service._auth_lib.token.expired = True
|
|
94
|
+
raise AccessTokenError("Access Token expired, attempting to refresh")
|
|
93
95
|
else:
|
|
94
96
|
raise UnknownApiError(response_json)
|
|
95
97
|
|
|
96
98
|
|
|
97
|
-
def check_for_errors_iot(response_json: Dict[Any, Any]) -> None:
|
|
99
|
+
def check_for_errors_iot(service, response_json: Dict[Any, Any]) -> None:
|
|
98
100
|
if response_json['code'] != 1:
|
|
99
|
-
|
|
100
|
-
|
|
101
|
+
if str(response_json['code']) == ResponseCodes.ACCESS_TOKEN_ERROR.value:
|
|
102
|
+
service._auth_lib.token.expired = True
|
|
103
|
+
raise AccessTokenError("Access Token expired, attempting to refresh")
|
|
104
|
+
else:
|
|
105
|
+
raise UnknownApiError(response_json)
|
|
101
106
|
|
|
102
|
-
def check_for_errors_hms(response_json: Dict[Any, Any]) -> None:
|
|
103
|
-
if response_json['
|
|
104
|
-
|
|
107
|
+
def check_for_errors_hms(service, response_json: Dict[Any, Any]) -> None:
|
|
108
|
+
if response_json['code'] != 1:
|
|
109
|
+
if str(response_json['code']) == ResponseCodes.ACCESS_TOKEN_ERROR.value:
|
|
110
|
+
service._auth_lib.token.expired = True
|
|
111
|
+
raise AccessTokenError("Access Token expired, attempting to refresh")
|
|
112
|
+
else:
|
|
113
|
+
raise UnknownApiError(response_json)
|
|
105
114
|
|
|
106
115
|
|
|
107
116
|
def return_event_for_device(device: Device, events: List[Event]) -> Optional[Event]:
|
wyzeapy/wyze_auth_lib.py
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
# You may use, distribute and modify this code under the terms
|
|
3
3
|
# of the attached license. You should have received a copy of
|
|
4
4
|
# the license with this file. If not, please write to:
|
|
5
|
-
#
|
|
5
|
+
# katie@mulliken.net to receive a copy
|
|
6
6
|
import asyncio
|
|
7
7
|
import logging
|
|
8
8
|
import time
|
|
@@ -11,19 +11,24 @@ from typing import Dict, Any, Optional
|
|
|
11
11
|
from aiohttp import TCPConnector, ClientSession, ContentTypeError
|
|
12
12
|
|
|
13
13
|
from .const import API_KEY, PHONE_ID, APP_NAME, APP_VERSION, SC, SV, PHONE_SYSTEM_TYPE, APP_VER, APP_INFO
|
|
14
|
-
from .exceptions import
|
|
14
|
+
from .exceptions import (
|
|
15
|
+
UnknownApiError,
|
|
16
|
+
TwoFactorAuthenticationEnabled,
|
|
17
|
+
AccessTokenError,
|
|
18
|
+
)
|
|
15
19
|
from .utils import create_password, check_for_errors_standard
|
|
16
20
|
|
|
17
21
|
_LOGGER = logging.getLogger(__name__)
|
|
18
22
|
|
|
19
23
|
|
|
20
24
|
class Token:
|
|
21
|
-
# Token is good for
|
|
22
|
-
REFRESH_INTERVAL =
|
|
25
|
+
# Token is apparently good for 24 hours, so refresh after 23
|
|
26
|
+
REFRESH_INTERVAL = 82800
|
|
23
27
|
|
|
24
28
|
def __init__(self, access_token, refresh_token, refresh_time: float = None):
|
|
25
29
|
self._access_token: str = access_token
|
|
26
30
|
self._refresh_token: str = refresh_token
|
|
31
|
+
self.expired = False
|
|
27
32
|
if refresh_time:
|
|
28
33
|
self._refresh_time: float = refresh_time
|
|
29
34
|
else:
|
|
@@ -135,6 +140,8 @@ class WyzeAuthLib:
|
|
|
135
140
|
|
|
136
141
|
if response_json.get('errorCode') is not None:
|
|
137
142
|
_LOGGER.error(f"Unable to login with response from Wyze: {response_json}")
|
|
143
|
+
if response_json["errorCode"] == 1000:
|
|
144
|
+
raise AccessTokenError
|
|
138
145
|
raise UnknownApiError(response_json)
|
|
139
146
|
|
|
140
147
|
if response_json.get('mfa_options') is not None:
|
|
@@ -200,9 +207,9 @@ class WyzeAuthLib:
|
|
|
200
207
|
return time.time() >= self.token.refresh_time
|
|
201
208
|
|
|
202
209
|
async def refresh_if_should(self):
|
|
203
|
-
if self.should_refresh:
|
|
210
|
+
if self.should_refresh or self.token.expired:
|
|
204
211
|
async with self.refresh_lock:
|
|
205
|
-
if self.should_refresh:
|
|
212
|
+
if self.should_refresh or self.token.expired:
|
|
206
213
|
_LOGGER.debug("Should refresh. Refreshing...")
|
|
207
214
|
await self.refresh()
|
|
208
215
|
|
|
@@ -227,11 +234,12 @@ class WyzeAuthLib:
|
|
|
227
234
|
response = await _session.post("https://api.wyzecam.com/app/user/refresh_token", headers=headers,
|
|
228
235
|
json=payload)
|
|
229
236
|
response_json = await response.json()
|
|
230
|
-
check_for_errors_standard(response_json)
|
|
237
|
+
check_for_errors_standard(self, response_json)
|
|
231
238
|
|
|
232
239
|
self.token.access_token = response_json['data']['access_token']
|
|
233
240
|
self.token.refresh_token = response_json['data']['refresh_token']
|
|
234
241
|
await self.token_callback(self.token)
|
|
242
|
+
self.token.expired = False
|
|
235
243
|
|
|
236
244
|
def sanitize(self, data):
|
|
237
245
|
if data and type(data) is dict:
|
|
@@ -1,19 +1,15 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: wyzeapy
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.22.post1
|
|
4
4
|
Summary: A library for interacting with Wyze devices
|
|
5
5
|
License: GPL-3.0-only
|
|
6
6
|
Author: Katie Mulliken
|
|
7
7
|
Author-email: katie@mulliken.net
|
|
8
|
-
Requires-Python: >=3.
|
|
8
|
+
Requires-Python: >=3.11.0
|
|
9
9
|
Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
|
|
10
10
|
Classifier: Programming Language :: Python :: 3
|
|
11
|
-
Classifier: Programming Language :: Python :: 3.6
|
|
12
|
-
Classifier: Programming Language :: Python :: 3.7
|
|
13
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
14
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
15
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
16
11
|
Classifier: Programming Language :: Python :: 3.11
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
13
|
Requires-Dist: aiodns (>=3.0.0,<4.0.0)
|
|
18
14
|
Requires-Dist: aiohttp (>=3.7,<4.0)
|
|
19
15
|
Requires-Dist: pycryptodome (>=3.12.0,<4.0.0)
|
|
@@ -0,0 +1,23 @@
|
|
|
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=yKbZljDi__B745N3mo2RdYRSKUsw_IsW8cX1kcitbPw,4413
|
|
19
|
+
wyzeapy/wyze_auth_lib.py,sha256=NWlWEMMNeEuCYLo7rxWd1EpJWPsP3huhJatfKTCkKm0,12713
|
|
20
|
+
wyzeapy-0.5.22.post1.dist-info/LICENSES/GPL-3.0-only.txt,sha256=tqi_Y64slbCqJW7ndGgNe9GPIfRX2nVGb3YQs7FqzE4,34670
|
|
21
|
+
wyzeapy-0.5.22.post1.dist-info/METADATA,sha256=zlpAY0QmjikGMu-hrXIwlAxRwIzmc6VtTETMlGLOS1A,563
|
|
22
|
+
wyzeapy-0.5.22.post1.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
|
23
|
+
wyzeapy-0.5.22.post1.dist-info/RECORD,,
|
wyzeapy-0.5.20.dist-info/RECORD
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
wyzeapy/__init__.py,sha256=ml7YKqvUaTYOKoITUbwy6G5aMpdN_wYSIhZ1FhodnBo,8652
|
|
2
|
-
wyzeapy/const.py,sha256=H5ACdBcrxTDcLHS83fhPKRqCbGenYvy8HvYA9fZVOrc,990
|
|
3
|
-
wyzeapy/crypto.py,sha256=AThxXAke2hYTjZARb6Sf_qSUB6GqNyY3YON0z5-CJwo,1343
|
|
4
|
-
wyzeapy/exceptions.py,sha256=Ix197FSnNvZDwMJKHKp7GyhMuhdYvd5qqG9RlEosUxU,872
|
|
5
|
-
wyzeapy/payload_factory.py,sha256=sm6wB7p8uU6dAQkHgpNJ9bOX1dagk1NG2e9hu7OeVnc,1921
|
|
6
|
-
wyzeapy/services/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
7
|
-
wyzeapy/services/base_service.py,sha256=CvIx2MOQIC_QJtsqn4FIbNPuaBUvlz7x24SAAVuwXDA,22711
|
|
8
|
-
wyzeapy/services/bulb_service.py,sha256=5HtYekKn6eMS4Mgmgo6UbtREymrUfbbdEO3e9OcjFfs,6841
|
|
9
|
-
wyzeapy/services/camera_service.py,sha256=sYofsa_ZJLNa4_0JIB1AXzLxpxUzSrGNuxYYZmh3-Bc,5851
|
|
10
|
-
wyzeapy/services/hms_service.py,sha256=hF8IJ4XaUwpLravquYYFJ8xo7U8qdHbFKY-FRbVFoNE,2460
|
|
11
|
-
wyzeapy/services/lock_service.py,sha256=QD7yE8OCSNPSaC74Dt3TuRNyfWGplmqMzZNQUr4BZc8,1514
|
|
12
|
-
wyzeapy/services/sensor_service.py,sha256=YpEAlB_GS0kxASISI8wcePUq3pxJI4PHzkL-lAXhYgQ,3253
|
|
13
|
-
wyzeapy/services/switch_service.py,sha256=OydpGnu2o6pDSNfX-FJNPTzgzx2EA1b-HG8a2AvhF-I,1700
|
|
14
|
-
wyzeapy/services/thermostat_service.py,sha256=XvdFG1q5mE_Td8KHJn3qlUxeR5fG01sAghCb8COK_uI,5229
|
|
15
|
-
wyzeapy/services/update_manager.py,sha256=036ClmJMFzXjD03mfg5DIyjB3xwqHoWkmpU2tcquc5Q,6132
|
|
16
|
-
wyzeapy/services/wall_switch_service.py,sha256=eoSVj2cyNviWHETYxAkWGoUxVKxd82iomxt1Bo0Xn0g,4316
|
|
17
|
-
wyzeapy/types.py,sha256=_YNjw-wGTYkCOC5Jdf9zPRiLfiAqm52K1ec-wgK0FXk,5829
|
|
18
|
-
wyzeapy/utils.py,sha256=I3I73XEjrq5GjkyY_49QkE9HVtQgXaJKEfOItd9EDOU,3711
|
|
19
|
-
wyzeapy/wyze_auth_lib.py,sha256=OTtIDoUtSxrII80FzX6pth20vlQxDxloG4m5giVQIFY,12516
|
|
20
|
-
wyzeapy-0.5.20.dist-info/LICENSES/GPL-3.0-only.txt,sha256=tqi_Y64slbCqJW7ndGgNe9GPIfRX2nVGb3YQs7FqzE4,34670
|
|
21
|
-
wyzeapy-0.5.20.dist-info/METADATA,sha256=V1PPywixY8fT0UOYY56olo3-EAi51yA1HgrK8hQk28c,759
|
|
22
|
-
wyzeapy-0.5.20.dist-info/WHEEL,sha256=Zb28QaM1gQi8f4VCBhsUklF61CTlNYfs9YAZn-TOGFk,88
|
|
23
|
-
wyzeapy-0.5.20.dist-info/RECORD,,
|
|
File without changes
|