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