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 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
- # joshua@mulliken.net to receive a copy
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, lets go ahead and use it and refresh the access token if needed.
87
- await self._auth_lib.refresh_if_should()
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
- # joshua@mulliken.net to receive a copy
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
- # joshua@mulliken.net to receive a copy
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
- # joshua@mulliken.net to receive a copy
5
+ # katie@mulliken.net to receive a copy
6
6
  from typing import Dict, Any
7
7
 
8
8
 
@@ -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
- # joshua@mulliken.net to receive a copy
5
+ # katie@mulliken.net to receive a copy
6
6
  import time
7
7
  from typing import Any, Dict
8
8
 
@@ -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
- # joshua@mulliken.net to receive a copy
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"]
@@ -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
- # joshua@mulliken.net to receive a copy
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
- # joshua@mulliken.net to receive a copy
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
- plist = [
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
- plist = [
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
- # For whatever reason, this property isn't always in line with the status property,
137
- # so having both commands makes sure the state is actually toggled.
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.MOTION_DETECTION_TOGGLE.value, "0")
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.MOTION_DETECTION_TOGGLE.value, "1")
135
+ await self._set_property(camera, PropertyIDs.MOTION_DETECTION.value, "0")
144
136
  await self._set_property(camera, PropertyIDs.MOTION_DETECTION_TOGGLE.value, "0")
@@ -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
- # joshua@mulliken.net to receive a copy
5
+ # katie@mulliken.net to receive a copy
6
6
  from enum import Enum
7
7
  from typing import Optional
8
8
 
@@ -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
- # joshua@mulliken.net to receive a copy
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
- # joshua@mulliken.net to receive a copy
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
- # joshua@mulliken.net to receive a copy
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
- # joshua@mulliken.net to receive a copy
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
- # joshua@mulliken.net to receive a copy
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
- # joshua@mulliken.net to receive a copy
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
- # joshua@mulliken.net to receive a copy
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
- raise AccessTokenError
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
- raise AccessTokenError
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
- raise UnknownApiError(response_json)
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['message'] is None:
104
- raise AccessTokenError
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
- # joshua@mulliken.net to receive a copy
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 UnknownApiError, TwoFactorAuthenticationEnabled
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 216,000 seconds (60 hours) but 48 hours seems like a reasonable refresh interval
22
- REFRESH_INTERVAL = 172800
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.20
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.6,<4.0
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,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry-core 1.6.1
2
+ Generator: poetry-core 1.9.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -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,,