wyzeapy 0.5.28__py3-none-any.whl → 0.5.29__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
@@ -7,10 +7,7 @@ import logging
7
7
  from inspect import iscoroutinefunction
8
8
  from typing import List, Optional, Set, Callable
9
9
 
10
- from .const import PHONE_SYSTEM_TYPE, APP_VERSION, SC, APP_VER, SV, PHONE_ID, APP_NAME, OLIVE_APP_ID, APP_INFO
11
- from .crypto import olive_create_signature
12
10
  from .exceptions import TwoFactorAuthenticationEnabled
13
- from .payload_factory import olive_create_user_info_payload
14
11
  from .services.base_service import BaseService
15
12
  from .services.bulb_service import BulbService
16
13
  from .services.camera_service import CameraService
@@ -26,7 +23,22 @@ _LOGGER = logging.getLogger(__name__)
26
23
 
27
24
 
28
25
  class Wyzeapy:
29
- """A module to assist developers in interacting with the Wyze service"""
26
+ """A Python module to assist developers in interacting with the Wyze service API.
27
+
28
+ This class provides methods for authentication, device management, and accessing
29
+ various Wyze device services including:
30
+
31
+ * **Bulbs** - Control brightness, color, and power state
32
+ * **Switches** - Toggle power and monitor usage
33
+ * **Cameras** - Access video streams and control settings
34
+ * **Thermostats** - Manage temperature settings and modes
35
+ * **Locks** - Control and monitor door locks
36
+ * **Sensors** - Monitor motion, contact, and environmental sensors
37
+ * **HMS** - Manage home monitoring system
38
+
39
+ Most interactions with Wyze devices should go through this class.
40
+ """
41
+
30
42
  # _client: Client
31
43
  _auth_lib: WyzeAuthLib
32
44
 
@@ -50,9 +62,13 @@ class Wyzeapy:
50
62
  @classmethod
51
63
  async def create(cls):
52
64
  """
53
- Creates the Wyzeapy class in an async way. Although this is not currently utilized
65
+ Creates and initializes the Wyzeapy class asynchronously.
54
66
 
55
- :return: An instance of the Wyzeapy class
67
+ This factory method provides a way to instantiate the class using async/await syntax,
68
+ though it's currently a simple implementation that may be expanded in the future.
69
+
70
+ **Returns:**
71
+ `Wyzeapy`: A new instance of the Wyzeapy class ready for authentication.
56
72
  """
57
73
  self = cls()
58
74
  return self
@@ -61,16 +77,21 @@ class Wyzeapy:
61
77
  self, email, password, key_id, api_key, token: Optional[Token] = None
62
78
  ):
63
79
  """
64
- Logs the user in and retrieves the users token
80
+ Authenticates with the Wyze API and retrieves the user's access token.
81
+
82
+ This method handles the authentication process, including token management
83
+ and service initialization. If two-factor authentication is enabled on the account,
84
+ it will raise an exception requiring the use of `login_with_2fa()` instead.
65
85
 
66
- :param email: Users email
67
- :param password: Users password
68
- :param key_id: Key ID for third-party API access
69
- :param api_key: API Key for third-party API access
70
- :param token: Users existing token from a previous session
86
+ **Args:**
87
+ * `email` (str): User's email address for Wyze account
88
+ * `password` (str): User's password for Wyze account
89
+ * `key_id` (str): Key ID for third-party API access
90
+ * `api_key` (str): API Key for third-party API access
91
+ * `token` (Optional[Token], optional): Existing token from a previous session. Defaults to None.
71
92
 
72
- :raises:
73
- TwoFactorAuthenticationEnabled: indicates that the account has 2fa enabled
93
+ **Raises:**
94
+ * `TwoFactorAuthenticationEnabled`: When the account has 2FA enabled and requires verification
74
95
  """
75
96
 
76
97
  self._email = email
@@ -95,10 +116,17 @@ class Wyzeapy:
95
116
 
96
117
  async def login_with_2fa(self, verification_code) -> Token:
97
118
  """
98
- Logs the user in and retrieves the users token
119
+ Completes the login process for accounts with two-factor authentication enabled.
120
+
121
+ This method should be called after receiving a `TwoFactorAuthenticationEnabled`
122
+ exception from the `login()` method. It completes the authentication process
123
+ using the verification code sent to the user.
99
124
 
100
- :param verification_code: Users 2fa verification code
125
+ **Args:**
126
+ * `verification_code` (str): The 2FA verification code received by the user
101
127
 
128
+ **Returns:**
129
+ * `Token`: The authenticated user token object
102
130
  """
103
131
 
104
132
  _LOGGER.debug(f"Verification Code: {verification_code}")
@@ -109,10 +137,13 @@ class Wyzeapy:
109
137
 
110
138
  async def execute_token_callbacks(self, token: Token):
111
139
  """
112
- Sends the token to the registered callback functions.
140
+ Sends the token to all registered callback functions.
113
141
 
114
- :param token: Users token object
142
+ This method is called internally whenever the token is refreshed or updated,
143
+ allowing external components to stay in sync with token changes.
115
144
 
145
+ **Args:**
146
+ * `token` (Token): The current user token object
116
147
  """
117
148
  for callback in self._token_callbacks:
118
149
  if iscoroutinefunction(callback):
@@ -122,27 +153,52 @@ class Wyzeapy:
122
153
 
123
154
  def register_for_token_callback(self, callback_function):
124
155
  """
125
- Register a callback to be called whenever the user's token is modified
156
+ Registers a callback function to be called whenever the user's token is modified.
126
157
 
127
- :param callback_function: A callback function which expects a token object
158
+ This allows external components to be notified of token changes for persistence
159
+ or other token-dependent operations.
128
160
 
161
+ **Args:**
162
+ * `callback_function`: A function that accepts a Token object as its parameter
163
+
164
+ **Example:**
165
+ ```python
166
+ def token_updated(token):
167
+ print(f"Token refreshed: {token.access_token[:10]}...")
168
+
169
+ wyze = Wyzeapy()
170
+ wyze.register_for_token_callback(token_updated)
171
+ ```
129
172
  """
130
173
  self._token_callbacks.append(callback_function)
131
174
 
132
175
  def unregister_for_token_callback(self, callback_function):
133
176
  """
134
- Register a callback to be called whenever the user's token is modified
177
+ Removes a previously registered token callback function.
135
178
 
136
- :param callback_function: A callback function which expects a token object
179
+ This stops the specified callback from receiving token updates.
137
180
 
181
+ **Args:**
182
+ * `callback_function`: The callback function to remove from the notification list
138
183
  """
139
184
  self._token_callbacks.remove(callback_function)
140
185
 
141
186
  @property
142
187
  async def unique_device_ids(self) -> Set[str]:
143
188
  """
144
- Returns a list of all device ids known to the server
145
- :return: A set containing the unique device ids
189
+ Retrieves a set of all unique device IDs known to the Wyze server.
190
+
191
+ This property fetches all devices associated with the account and
192
+ extracts their MAC addresses as unique identifiers.
193
+
194
+ **Returns:**
195
+ * `Set[str]`: A set containing all unique device IDs (MAC addresses)
196
+
197
+ **Example:**
198
+ ```python
199
+ device_ids = await wyze.unique_device_ids
200
+ print(f"Found {len(device_ids)} devices")
201
+ ```
146
202
  """
147
203
 
148
204
  devices = await self._service.get_object_list()
@@ -155,21 +211,45 @@ class Wyzeapy:
155
211
  @property
156
212
  async def notifications_are_on(self) -> bool:
157
213
  """
158
- Reports the status of the notifications
214
+ Checks if push notifications are enabled for the account.
215
+
216
+ This property queries the user profile to determine the current
217
+ notification settings status.
159
218
 
160
- :return: True if the notifications are enabled
219
+ **Returns:**
220
+ * `bool`: True if notifications are enabled, False otherwise
161
221
  """
162
222
 
163
223
  response_json = await self._service.get_user_profile()
164
- return response_json['data']['notification']
224
+ return response_json["data"]["notification"]
165
225
 
166
226
  async def enable_notifications(self):
167
- """Enables notifications on the account"""
227
+ """Enables push notifications for the Wyze account.
228
+
229
+ This method updates the user's profile to turn on push notifications
230
+ for all supported devices and events.
231
+
232
+ **Example:**
233
+ ```python
234
+ # Turn on notifications
235
+ await wyze.enable_notifications()
236
+ ```
237
+ """
168
238
 
169
239
  await self._service.set_push_info(True)
170
240
 
171
241
  async def disable_notifications(self):
172
- """Disables notifications on the account"""
242
+ """Disables push notifications for the Wyze account.
243
+
244
+ This method updates the user's profile to turn off push notifications
245
+ for all devices and events.
246
+
247
+ **Example:**
248
+ ```python
249
+ # Turn off notifications
250
+ await wyze.disable_notifications()
251
+ ```
252
+ """
173
253
 
174
254
  await self._service.set_push_info(False)
175
255
 
@@ -178,13 +258,29 @@ class Wyzeapy:
178
258
  cls, email: str, password: str, key_id: str, api_key: str
179
259
  ) -> bool:
180
260
  """
181
- Checks to see if a username and password return a valid login
182
-
183
- :param email: The users email
184
- :param password: The users password
185
- :param key_id: Key ID for third-party API access
186
- :param api_key: API Key for third-party API access
187
- :return: True if the account can connect
261
+ Validates if the provided credentials can successfully authenticate with the Wyze API.
262
+
263
+ This method attempts to log in with the provided credentials and returns whether
264
+ the authentication was successful. It's useful for validating credentials without
265
+ needing to handle the full login process.
266
+
267
+ **Args:**
268
+ * `email` (str): The user's email address
269
+ * `password` (str): The user's password
270
+ * `key_id` (str): Key ID for third-party API access
271
+ * `api_key` (str): API Key for third-party API access
272
+
273
+ **Returns:**
274
+ * `bool`: True if the credentials are valid and authentication succeeded
275
+
276
+ **Example:**
277
+ ```python
278
+ is_valid = await Wyzeapy.valid_login("user@example.com", "password123", "key_id", "api_key")
279
+ if is_valid:
280
+ print("Credentials are valid")
281
+ else:
282
+ print("Invalid credentials")
283
+ ```
188
284
  """
189
285
 
190
286
  self = cls()
@@ -194,7 +290,21 @@ class Wyzeapy:
194
290
 
195
291
  @property
196
292
  async def bulb_service(self) -> BulbService:
197
- """Returns an instance of the bulb service"""
293
+ """Provides access to the Wyze Bulb service.
294
+
295
+ This property lazily initializes and returns a BulbService instance
296
+ for controlling and monitoring Wyze bulbs.
297
+
298
+ **Returns:**
299
+ * `BulbService`: An instance of the bulb service for interacting with Wyze bulbs
300
+
301
+ **Example:**
302
+ ```python
303
+ # Get all bulbs
304
+ bulb_service = await wyze.bulb_service
305
+ bulbs = await bulb_service.get_bulbs()
306
+ ```
307
+ """
198
308
 
199
309
  if self._bulb_service is None:
200
310
  self._bulb_service = BulbService(self._auth_lib)
@@ -202,7 +312,21 @@ class Wyzeapy:
202
312
 
203
313
  @property
204
314
  async def switch_service(self) -> SwitchService:
205
- """Returns an instance of the switch service"""
315
+ """Provides access to the Wyze Switch service.
316
+
317
+ This property lazily initializes and returns a SwitchService instance
318
+ for controlling and monitoring Wyze plugs and switches.
319
+
320
+ **Returns:**
321
+ * `SwitchService`: An instance of the switch service for interacting with Wyze switches
322
+
323
+ **Example:**
324
+ ```python
325
+ # Get all switches
326
+ switch_service = await wyze.switch_service
327
+ switches = await switch_service.get_switches()
328
+ ```
329
+ """
206
330
 
207
331
  if self._switch_service is None:
208
332
  self._switch_service = SwitchService(self._auth_lib)
@@ -210,7 +334,21 @@ class Wyzeapy:
210
334
 
211
335
  @property
212
336
  async def camera_service(self) -> CameraService:
213
- """Returns an instance of the camera service"""
337
+ """Provides access to the Wyze Camera service.
338
+
339
+ This property lazily initializes and returns a CameraService instance
340
+ for controlling and monitoring Wyze cameras.
341
+
342
+ **Returns:**
343
+ * `CameraService`: An instance of the camera service for interacting with Wyze cameras
344
+
345
+ **Example:**
346
+ ```python
347
+ # Get all cameras
348
+ camera_service = await wyze.camera_service
349
+ cameras = await camera_service.get_cameras()
350
+ ```
351
+ """
214
352
 
215
353
  if self._camera_service is None:
216
354
  self._camera_service = CameraService(self._auth_lib)
@@ -218,7 +356,21 @@ class Wyzeapy:
218
356
 
219
357
  @property
220
358
  async def thermostat_service(self) -> ThermostatService:
221
- """Returns an instance of the thermostat service"""
359
+ """Provides access to the Wyze Thermostat service.
360
+
361
+ This property lazily initializes and returns a ThermostatService instance
362
+ for controlling and monitoring Wyze thermostats.
363
+
364
+ **Returns:**
365
+ * `ThermostatService`: An instance of the thermostat service for interacting with Wyze thermostats
366
+
367
+ **Example:**
368
+ ```python
369
+ # Get all thermostats
370
+ thermostat_service = await wyze.thermostat_service
371
+ thermostats = await thermostat_service.get_thermostats()
372
+ ```
373
+ """
222
374
 
223
375
  if self._thermostat_service is None:
224
376
  self._thermostat_service = ThermostatService(self._auth_lib)
@@ -226,7 +378,21 @@ class Wyzeapy:
226
378
 
227
379
  @property
228
380
  async def hms_service(self) -> HMSService:
229
- """Returns an instance of the hms service"""
381
+ """Provides access to the Wyze Home Monitoring Service (HMS).
382
+
383
+ This property lazily initializes and returns an HMSService instance
384
+ for controlling and monitoring the Wyze home security system.
385
+
386
+ **Returns:**
387
+ * `HMSService`: An instance of the HMS service for interacting with Wyze home monitoring
388
+
389
+ **Example:**
390
+ ```python
391
+ # Get HMS status
392
+ hms_service = await wyze.hms_service
393
+ status = await hms_service.get_hms_status()
394
+ ```
395
+ """
230
396
 
231
397
  if self._hms_service is None:
232
398
  self._hms_service = await HMSService.create(self._auth_lib)
@@ -234,7 +400,21 @@ class Wyzeapy:
234
400
 
235
401
  @property
236
402
  async def lock_service(self) -> LockService:
237
- """Returns an instance of the lock service"""
403
+ """Provides access to the Wyze Lock service.
404
+
405
+ This property lazily initializes and returns a LockService instance
406
+ for controlling and monitoring Wyze locks.
407
+
408
+ **Returns:**
409
+ * `LockService`: An instance of the lock service for interacting with Wyze locks
410
+
411
+ **Example:**
412
+ ```python
413
+ # Get all locks
414
+ lock_service = await wyze.lock_service
415
+ locks = await lock_service.get_locks()
416
+ ```
417
+ """
238
418
 
239
419
  if self._lock_service is None:
240
420
  self._lock_service = LockService(self._auth_lib)
@@ -242,7 +422,21 @@ class Wyzeapy:
242
422
 
243
423
  @property
244
424
  async def sensor_service(self) -> SensorService:
245
- """Returns an instance of the sensor service"""
425
+ """Provides access to the Wyze Sensor service.
426
+
427
+ This property lazily initializes and returns a SensorService instance
428
+ for monitoring Wyze sensors such as contact sensors, motion sensors, etc.
429
+
430
+ **Returns:**
431
+ * `SensorService`: An instance of the sensor service for interacting with Wyze sensors
432
+
433
+ **Example:**
434
+ ```python
435
+ # Get all sensors
436
+ sensor_service = await wyze.sensor_service
437
+ sensors = await sensor_service.get_sensors()
438
+ ```
439
+ """
246
440
 
247
441
  if self._sensor_service is None:
248
442
  self._sensor_service = SensorService(self._auth_lib)
@@ -250,7 +444,21 @@ class Wyzeapy:
250
444
 
251
445
  @property
252
446
  async def wall_switch_service(self) -> WallSwitchService:
253
- """Returns an instance of the switch service"""
447
+ """Provides access to the Wyze Wall Switch service.
448
+
449
+ This property lazily initializes and returns a WallSwitchService instance
450
+ for controlling and monitoring Wyze wall switches.
451
+
452
+ **Returns:**
453
+ * `WallSwitchService`: An instance of the wall switch service for interacting with Wyze wall switches
454
+
455
+ **Example:**
456
+ ```python
457
+ # Get all wall switches
458
+ wall_switch_service = await wyze.wall_switch_service
459
+ switches = await wall_switch_service.get_wall_switches()
460
+ ```
461
+ """
254
462
 
255
463
  if self._wall_switch_service is None:
256
464
  self._wall_switch_service = WallSwitchService(self._auth_lib)
@@ -258,7 +466,21 @@ class Wyzeapy:
258
466
 
259
467
  @property
260
468
  async def switch_usage_service(self) -> SwitchUsageService:
261
- """Returns an instance of the switch usage service"""
469
+ """Provides access to the Wyze Switch Usage service.
470
+
471
+ This property lazily initializes and returns a SwitchUsageService instance
472
+ for retrieving usage statistics from Wyze switches and plugs.
473
+
474
+ **Returns:**
475
+ * `SwitchUsageService`: An instance of the switch usage service for accessing Wyze switch usage data
476
+
477
+ **Example:**
478
+ ```python
479
+ # Get usage data for a switch
480
+ usage_service = await wyze.switch_usage_service
481
+ usage = await usage_service.get_usage_records(switch_mac)
482
+ ```
483
+ """
262
484
  if self._switch_usage_service is None:
263
485
  self._switch_usage_service = SwitchUsageService(self._auth_lib)
264
486
  return self._switch_usage_service
wyzeapy/const.py CHANGED
@@ -3,16 +3,21 @@
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
+ # Standard library imports
6
7
  import uuid
7
8
 
8
- # Here is where all the *magic* lives
9
+ # Module constants for application identification, API credentials, and crypto secrets
10
+ """
11
+ Configuration constants for Wyze API integration, including app metadata,
12
+ API keys, and cryptographic secrets.
13
+ """
9
14
  PHONE_SYSTEM_TYPE = "1"
10
15
  API_KEY = "WMXHYf79Nr5gIlt3r0r7p9Tcw5bvs6BB4U8O8nGJ"
11
16
  APP_VERSION = "2.18.43"
12
17
  APP_VER = "com.hualai.WyzeCam___2.18.43"
13
18
  APP_NAME = "com.hualai.WyzeCam"
14
19
  PHONE_ID = str(uuid.uuid4())
15
- APP_INFO = 'wyze_android_2.19.14' # Required for the thermostat
20
+ APP_INFO = "wyze_android_2.19.14" # Required for the thermostat
16
21
  SC = "9f275790cab94a72bd206c8876429f3c"
17
22
  SV = "9d74946e652647e9b6c9d59326aef104"
18
23
  CLIENT_VER = "2"
@@ -20,7 +25,7 @@ SOURCE = "ios/WZCameraSDK"
20
25
  APP_PLATFORM = "ios"
21
26
 
22
27
  # Crypto secrets
23
- OLIVE_SIGNING_SECRET = 'wyze_app_secret_key_132' # Required for the thermostat
24
- OLIVE_APP_ID = '9319141212m2ik' # Required for the thermostat
28
+ OLIVE_SIGNING_SECRET = "wyze_app_secret_key_132" # Required for the thermostat
29
+ OLIVE_APP_ID = "9319141212m2ik" # Required for the thermostat
25
30
  FORD_APP_KEY = "275965684684dbdaf29a0ed9" # Required for the locks
26
31
  FORD_APP_SECRET = "4deekof1ba311c5c33a9cb8e12787e8c" # Required for the locks
wyzeapy/crypto.py CHANGED
@@ -10,8 +10,24 @@ from typing import Dict, Union, Any
10
10
 
11
11
  from .const import FORD_APP_SECRET, OLIVE_SIGNING_SECRET
12
12
 
13
+ """
14
+ Cryptographic helper functions for creating API request signatures.
15
+ """
13
16
 
14
- def olive_create_signature(payload: Union[Dict[Any, Any], str], access_token: str) -> str:
17
+
18
+ def olive_create_signature(
19
+ payload: Union[Dict[Any, Any], str], access_token: str
20
+ ) -> str:
21
+ """
22
+ Compute the olive (Wyze) API request signature using HMAC-MD5.
23
+
24
+ Args:
25
+ payload: The request payload as a dict or raw string.
26
+ access_token: The access token string for signing.
27
+
28
+ Returns:
29
+ The computed signature as a hex string.
30
+ """
15
31
  if isinstance(payload, dict):
16
32
  body = ""
17
33
  for item in sorted(payload):
@@ -28,7 +44,20 @@ def olive_create_signature(payload: Union[Dict[Any, Any], str], access_token: st
28
44
  return hmac.new(secret.encode(), body.encode(), hashlib.md5).hexdigest()
29
45
 
30
46
 
31
- def ford_create_signature(url_path: str, request_method: str, payload: Dict[Any, Any]) -> str:
47
+ def ford_create_signature(
48
+ url_path: str, request_method: str, payload: Dict[Any, Any]
49
+ ) -> str:
50
+ """
51
+ Compute the ford (Lock) API request signature using MD5 of URL-encoded buffer.
52
+
53
+ Args:
54
+ url_path: The URL path of the request.
55
+ request_method: HTTP method (e.g., 'GET', 'POST').
56
+ payload: The request payload dict to include in the signature.
57
+
58
+ Returns:
59
+ The computed signature as a hex string.
60
+ """
32
61
  string_buf = request_method + url_path
33
62
  for entry in sorted(payload.keys()):
34
63
  string_buf += entry + "=" + payload[entry] + "&"
wyzeapy/exceptions.py CHANGED
@@ -3,31 +3,34 @@
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
- from typing import Dict, Any
6
+ """
7
+ Custom exception classes for Wyzeapy API interactions.
8
+ """
7
9
 
8
10
 
9
11
  class ActionNotSupported(Exception):
10
- def __init__(self, device_type: str):
11
- message = "The action specified is not supported by device type: {}".format(device_type)
12
+ """Raised when an unsupported action is requested for a device type."""
12
13
 
14
+ def __init__(self, device_type: str):
15
+ message = f"The action specified is not supported by device type: {device_type}"
13
16
  super().__init__(message)
14
17
 
15
18
 
16
19
  class ParameterError(Exception):
17
- pass
20
+ """Raised when invalid parameters are provided to an API call."""
18
21
 
19
22
 
20
23
  class AccessTokenError(Exception):
21
- pass
24
+ """Raised when the access token is invalid or has expired."""
22
25
 
23
26
 
24
27
  class LoginError(Exception):
25
- pass
28
+ """Raised during authentication or login failures."""
26
29
 
27
30
 
28
31
  class UnknownApiError(Exception):
29
- pass
32
+ """Raised for unexpected or generic API errors."""
30
33
 
31
34
 
32
35
  class TwoFactorAuthenticationEnabled(Exception):
33
- pass
36
+ """Raised when two-factor authentication is required for login."""