pyvesync 2.1.10__tar.gz → 2.1.12__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pyvesync
3
- Version: 2.1.10
3
+ Version: 2.1.12
4
4
  Summary: pyvesync is a library to manage Etekcity Devices, Cosori Air Fryers and Levoit Air Purifiers run on the VeSync app.
5
5
  Home-page: https://github.com/webdjoe/pyvesync
6
6
  Author: Mark Perdue, Joe Trabulsy
@@ -12,10 +12,15 @@ Classifier: Intended Audience :: Developers
12
12
  Classifier: Intended Audience :: End Users/Desktop
13
13
  Classifier: Natural Language :: English
14
14
  Classifier: Programming Language :: Python :: 3.8
15
- Requires-Python: >=3.8
15
+ Requires-Python: >=3.9
16
16
  Description-Content-Type: text/markdown
17
- Provides-Extra: dev
18
17
  License-File: LICENSE
18
+ Requires-Dist: requests>=2.20.0
19
+ Provides-Extra: dev
20
+ Requires-Dist: pytest; extra == "dev"
21
+ Requires-Dist: pytest-cov; extra == "dev"
22
+ Requires-Dist: yaml; extra == "dev"
23
+ Requires-Dist: tox; extra == "dev"
19
24
 
20
25
  # pyvesync [![build status](https://img.shields.io/pypi/v/pyvesync.svg)](https://pypi.python.org/pypi/pyvesync) [![Build Status](https://dev.azure.com/webdjoe/pyvesync/_apis/build/status/webdjoe.pyvesync?branchName=master)](https://dev.azure.com/webdjoe/pyvesync/_build/latest?definitionId=4&branchName=master) [![Open Source? Yes!](https://badgen.net/badge/Open%20Source%20%3F/Yes%21/blue?icon=github)](https://github.com/Naereen/badges/) [![PyPI license](https://img.shields.io/pypi/l/ansicolortags.svg)](https://pypi.python.org/pypi/ansicolortags/) <!-- omit in toc -->
21
26
 
@@ -48,9 +53,9 @@ pyvesync is a library to manage VeSync compatible [smart home devices](#supporte
48
53
  - [Standard Air Purifier Properties \& Methods](#standard-air-purifier-properties--methods)
49
54
  - [Air Purifier Properties](#air-purifier-properties)
50
55
  - [Air Purifier Methods](#air-purifier-methods)
51
- - [Levoit Purifier Core200S/300S/400S and Vital 100S/200S Properties](#levoit-purifier-core200s300s400s-and-vital-100s200s-properties)
52
- - [Levoit Purifier Core200S/300S/400S and Vital 100S/200S Methods](#levoit-purifier-core200s300s400s-and-vital-100s200s-methods)
53
- - [Levoit Vital 100S/200S Properties and Methods](#levoit-vital-100s200s-properties-and-methods)
56
+ - [Levoit Purifier Core200S/300S/400S and Vital 100S/200S Properties](#levoit-purifier-core200s300s400s-vital-100s200s--everest-air-properties)
57
+ - [Levoit Purifier Core200S/300S/400S, Vital 100S/200S & Everest Air Methods](#levoit-purifier-core200s300s400s-vital-100s200s--everest-air-methods)
58
+ - [Levoit Vital 100S/200S Properties and Methods](#levoit-vital-100s200s--everest-air-properties-and-methods)
54
59
  - [Lights API Methods \& Properties](#lights-api-methods--properties)
55
60
  - [Brightness Light Bulb Method and Properties](#brightness-light-bulb-method-and-properties)
56
61
  - [Light Bulb Color Temperature Methods and Properties](#light-bulb-color-temperature-methods-and-properties)
@@ -113,6 +118,7 @@ pip install pyvesync
113
118
  5. Core 600S
114
119
  6. Vital 100S
115
120
  7. Vital 200S
121
+ 8. Everest Air
116
122
 
117
123
  ### Etekcity Bulbs
118
124
 
@@ -349,13 +355,13 @@ Compatible levels for each model:
349
355
  - PUR131S [1, 2, 3]
350
356
  - Vital 100S/200S [1, 2, 3, 4]
351
357
 
352
- #### Levoit Purifier Core200S/300S/400S and Vital 100S/200S Properties
358
+ #### Levoit Purifier Core200S/300S/400S, Vital 100S/200S & Everest Air Properties
353
359
 
354
360
  `VeSyncFan.child_lock` - Return the state of the child lock (True=On/False=off)
355
361
 
356
362
  `VeSyncAir.night_light` - Return the state of the night light (on/dim/off) **Not available on Vital 100S/200S**
357
363
 
358
- #### Levoit Purifier Core200S/300S/400S and Vital 100S/200S Methods
364
+ #### Levoit Purifier Core200S/300S/400S, Vital 100S/200S & Everest Air Methods
359
365
 
360
366
  `VeSyncFan.child_lock_on()` Enable child lock
361
367
 
@@ -367,13 +373,15 @@ Compatible levels for each model:
367
373
 
368
374
  `VeSyncFan.set_night_light('on'|'dim'|'off')` - Set night light brightness
369
375
 
370
- `VeSyncFan.get_timer()` - Get any running timers, stores Timer DataClass in `VeSyncFan.timer`
376
+ `VeSyncFan.get_timer()` - Get any running timers, stores Timer DataClass in `VeSyncFan.timer`. See [Timer Dataclass](#timer-dataclass)
371
377
 
372
378
  `VeSyncFan.set_timer(timer_duration=3000)` - Set a timer for the device, only turns device off. Timer DataClass stored in `VeSyncFan.timer`
373
379
 
374
380
  `VeSyncFan.clear_timer()` - Cancel any running timer
375
381
 
376
- #### Levoit Vital 100S/200S Properties and Methods
382
+ `VeSyncFan.reset_filter()` - Reset filter to 100% **NOTE: Only available on Core200S**
383
+
384
+ #### Levoit Vital 100S/200S & Everest Air Properties and Methods
377
385
 
378
386
  The Levoit Vital 100S/200S has additional features not available on other models.
379
387
 
@@ -393,6 +401,23 @@ The Levoit Vital 100S/200S has additional features not available on other models
393
401
 
394
402
  `VeSyncFan.set_light_detection_off()` - Turn off light detection mode
395
403
 
404
+ #### Levoit Everest Air Properties & Methods
405
+
406
+ `VeSyncFan.turbo_mode()` - Set turbo mode
407
+
408
+ Additional properties in the `VeSyncFan['details']` dictionary:
409
+
410
+ ```python
411
+ VeSyncFan['Details'] = {
412
+ 'pm1': 0, # air quality reading of particulates 1.0 microns
413
+ 'pm10': 10, # air quality reading of particulates 10 microns
414
+ 'fan_rotate_angle': 45, # angle of fan vents
415
+ 'aq_percent': 45, # Air Quality percentage reading
416
+ 'filter_open_state': False # returns bool of filter open
417
+ }
418
+
419
+ ```
420
+
396
421
  ### Lights API Methods & Properties
397
422
 
398
423
  #### Brightness Light Bulb Method and Properties
@@ -1046,7 +1071,7 @@ After:
1046
1071
  }
1047
1072
  ```
1048
1073
 
1049
- # Contributing
1074
+ ## Contributing
1050
1075
 
1051
1076
  All [contributions](CONTRIBUTING.md) are welcome.
1052
1077
 
@@ -29,9 +29,9 @@ pyvesync is a library to manage VeSync compatible [smart home devices](#supporte
29
29
  - [Standard Air Purifier Properties \& Methods](#standard-air-purifier-properties--methods)
30
30
  - [Air Purifier Properties](#air-purifier-properties)
31
31
  - [Air Purifier Methods](#air-purifier-methods)
32
- - [Levoit Purifier Core200S/300S/400S and Vital 100S/200S Properties](#levoit-purifier-core200s300s400s-and-vital-100s200s-properties)
33
- - [Levoit Purifier Core200S/300S/400S and Vital 100S/200S Methods](#levoit-purifier-core200s300s400s-and-vital-100s200s-methods)
34
- - [Levoit Vital 100S/200S Properties and Methods](#levoit-vital-100s200s-properties-and-methods)
32
+ - [Levoit Purifier Core200S/300S/400S and Vital 100S/200S Properties](#levoit-purifier-core200s300s400s-vital-100s200s--everest-air-properties)
33
+ - [Levoit Purifier Core200S/300S/400S, Vital 100S/200S & Everest Air Methods](#levoit-purifier-core200s300s400s-vital-100s200s--everest-air-methods)
34
+ - [Levoit Vital 100S/200S Properties and Methods](#levoit-vital-100s200s--everest-air-properties-and-methods)
35
35
  - [Lights API Methods \& Properties](#lights-api-methods--properties)
36
36
  - [Brightness Light Bulb Method and Properties](#brightness-light-bulb-method-and-properties)
37
37
  - [Light Bulb Color Temperature Methods and Properties](#light-bulb-color-temperature-methods-and-properties)
@@ -94,6 +94,7 @@ pip install pyvesync
94
94
  5. Core 600S
95
95
  6. Vital 100S
96
96
  7. Vital 200S
97
+ 8. Everest Air
97
98
 
98
99
  ### Etekcity Bulbs
99
100
 
@@ -330,13 +331,13 @@ Compatible levels for each model:
330
331
  - PUR131S [1, 2, 3]
331
332
  - Vital 100S/200S [1, 2, 3, 4]
332
333
 
333
- #### Levoit Purifier Core200S/300S/400S and Vital 100S/200S Properties
334
+ #### Levoit Purifier Core200S/300S/400S, Vital 100S/200S & Everest Air Properties
334
335
 
335
336
  `VeSyncFan.child_lock` - Return the state of the child lock (True=On/False=off)
336
337
 
337
338
  `VeSyncAir.night_light` - Return the state of the night light (on/dim/off) **Not available on Vital 100S/200S**
338
339
 
339
- #### Levoit Purifier Core200S/300S/400S and Vital 100S/200S Methods
340
+ #### Levoit Purifier Core200S/300S/400S, Vital 100S/200S & Everest Air Methods
340
341
 
341
342
  `VeSyncFan.child_lock_on()` Enable child lock
342
343
 
@@ -348,13 +349,15 @@ Compatible levels for each model:
348
349
 
349
350
  `VeSyncFan.set_night_light('on'|'dim'|'off')` - Set night light brightness
350
351
 
351
- `VeSyncFan.get_timer()` - Get any running timers, stores Timer DataClass in `VeSyncFan.timer`
352
+ `VeSyncFan.get_timer()` - Get any running timers, stores Timer DataClass in `VeSyncFan.timer`. See [Timer Dataclass](#timer-dataclass)
352
353
 
353
354
  `VeSyncFan.set_timer(timer_duration=3000)` - Set a timer for the device, only turns device off. Timer DataClass stored in `VeSyncFan.timer`
354
355
 
355
356
  `VeSyncFan.clear_timer()` - Cancel any running timer
356
357
 
357
- #### Levoit Vital 100S/200S Properties and Methods
358
+ `VeSyncFan.reset_filter()` - Reset filter to 100% **NOTE: Only available on Core200S**
359
+
360
+ #### Levoit Vital 100S/200S & Everest Air Properties and Methods
358
361
 
359
362
  The Levoit Vital 100S/200S has additional features not available on other models.
360
363
 
@@ -374,6 +377,23 @@ The Levoit Vital 100S/200S has additional features not available on other models
374
377
 
375
378
  `VeSyncFan.set_light_detection_off()` - Turn off light detection mode
376
379
 
380
+ #### Levoit Everest Air Properties & Methods
381
+
382
+ `VeSyncFan.turbo_mode()` - Set turbo mode
383
+
384
+ Additional properties in the `VeSyncFan['details']` dictionary:
385
+
386
+ ```python
387
+ VeSyncFan['Details'] = {
388
+ 'pm1': 0, # air quality reading of particulates 1.0 microns
389
+ 'pm10': 10, # air quality reading of particulates 10 microns
390
+ 'fan_rotate_angle': 45, # angle of fan vents
391
+ 'aq_percent': 45, # Air Quality percentage reading
392
+ 'filter_open_state': False # returns bool of filter open
393
+ }
394
+
395
+ ```
396
+
377
397
  ### Lights API Methods & Properties
378
398
 
379
399
  #### Brightness Light Bulb Method and Properties
@@ -1027,7 +1047,7 @@ After:
1027
1047
  }
1028
1048
  ```
1029
1049
 
1030
- # Contributing
1050
+ ## Contributing
1031
1051
 
1032
1052
  All [contributions](CONTRIBUTING.md) are welcome.
1033
1053
 
@@ -10,7 +10,7 @@ with open(path.join(this_directory, 'README.md'), encoding='utf-8') as f:
10
10
 
11
11
  setup(
12
12
  name='pyvesync',
13
- version='2.1.10',
13
+ version='2.1.12',
14
14
  description='pyvesync is a library to manage Etekcity\
15
15
  Devices, Cosori Air Fryers and Levoit Air \
16
16
  Purifiers run on the VeSync app.',
@@ -34,5 +34,5 @@ setup(
34
34
  extras_require={
35
35
  'dev': ['pytest', 'pytest-cov', 'yaml', 'tox']
36
36
  },
37
- python_requires='>=3.8',
37
+ python_requires='>=3.9',
38
38
  )
@@ -6,7 +6,7 @@ import time
6
6
  import json
7
7
  import colorsys
8
8
  from dataclasses import dataclass, field, InitVar
9
- from typing import NamedTuple, Optional, Union
9
+ from typing import Any, Dict, NamedTuple, Optional, Union
10
10
  import re
11
11
  import requests
12
12
 
@@ -38,7 +38,7 @@ class Helpers:
38
38
  """VeSync Helper Functions."""
39
39
 
40
40
  @staticmethod
41
- def req_headers(manager) -> dict:
41
+ def req_headers(manager) -> Dict[str, str]:
42
42
  """Build header for api requests."""
43
43
  headers = {
44
44
  'accept-language': 'en',
@@ -51,7 +51,7 @@ class Helpers:
51
51
  return headers
52
52
 
53
53
  @staticmethod
54
- def req_header_bypass() -> dict:
54
+ def req_header_bypass() -> Dict[str, str]:
55
55
  """Build header for api requests on 'bypass' endpoint."""
56
56
  return {
57
57
  'Content-Type': 'application/json; charset=UTF-8',
@@ -59,17 +59,17 @@ class Helpers:
59
59
  }
60
60
 
61
61
  @staticmethod
62
- def req_body_base(manager) -> dict:
62
+ def req_body_base(manager) -> Dict[str, str]:
63
63
  """Return universal keys for body of api requests."""
64
64
  return {'timeZone': manager.time_zone, 'acceptLanguage': 'en'}
65
65
 
66
66
  @staticmethod
67
- def req_body_auth(manager) -> dict:
67
+ def req_body_auth(manager) -> Dict[str, str]:
68
68
  """Keys for authenticating api requests."""
69
69
  return {'accountID': manager.account_id, 'token': manager.token}
70
70
 
71
71
  @staticmethod
72
- def req_body_details() -> dict:
72
+ def req_body_details() -> Dict[str, str]:
73
73
  """Detail keys for api requests."""
74
74
  return {
75
75
  'appVersion': APP_VERSION,
@@ -79,83 +79,57 @@ class Helpers:
79
79
  }
80
80
 
81
81
  @classmethod
82
- def req_body(cls, manager, type_) -> dict:
82
+ def req_body(cls, manager, type_) -> Dict[str, Any]:
83
83
  """Builder for body of api requests."""
84
- body = {}
84
+ body = cls.req_body_base(manager)
85
85
 
86
86
  if type_ == 'login':
87
- body = {**cls.req_body_base(manager),
88
- **cls.req_body_details()}
89
- body['email'] = manager.username
90
- body['password'] = cls.hash_password(manager.password)
91
- body['devToken'] = ''
92
- body['userType'] = USER_TYPE
93
- body['method'] = 'login'
94
- elif type_ == 'devicedetail':
95
- body = {
96
- **cls.req_body_base(manager),
97
- **cls.req_body_auth(manager),
98
- **cls.req_body_details(),
99
- }
100
- body['method'] = 'devicedetail'
101
- body['mobileId'] = MOBILE_ID
102
- elif type_ == 'devicelist':
103
- body = {
104
- **cls.req_body_base(manager),
105
- **cls.req_body_auth(manager),
106
- **cls.req_body_details(),
107
- }
87
+ body |= cls.req_body_details() # type: ignore
88
+ body |= {
89
+ 'email': manager.username,
90
+ 'password': cls.hash_password(manager.password),
91
+ 'devToken': '',
92
+ 'userType': USER_TYPE,
93
+ 'method': 'login'
94
+ } # type: ignore
95
+ return body
96
+
97
+ body |= cls.req_body_auth(manager) # type: ignore
98
+
99
+ if type_ == 'devicestatus':
100
+ return body
101
+
102
+ body |= cls.req_body_details() # type: ignore
103
+
104
+ if type_ == 'devicelist':
108
105
  body['method'] = 'devices'
109
106
  body['pageNo'] = '1'
110
107
  body['pageSize'] = '100'
111
- elif type_ == 'devicestatus':
112
- body = {**cls.req_body_base(manager),
113
- **cls.req_body_auth(manager)}
108
+
109
+ elif type_ == 'devicedetail':
110
+ body['method'] = 'devicedetail'
111
+ body['mobileId'] = MOBILE_ID
112
+
114
113
  elif type_ == 'energy_week':
115
- body = {
116
- **cls.req_body_base(manager),
117
- **cls.req_body_auth(manager),
118
- **cls.req_body_details(),
119
- }
120
114
  body['method'] = 'energyweek'
121
115
  body['mobileId'] = MOBILE_ID
116
+
122
117
  elif type_ == 'energy_month':
123
- body = {
124
- **cls.req_body_base(manager),
125
- **cls.req_body_auth(manager),
126
- **cls.req_body_details(),
127
- }
128
118
  body['method'] = 'energymonth'
129
119
  body['mobileId'] = MOBILE_ID
120
+
130
121
  elif type_ == 'energy_year':
131
- body = {
132
- **cls.req_body_base(manager),
133
- **cls.req_body_auth(manager),
134
- **cls.req_body_details(),
135
- }
136
122
  body['method'] = 'energyyear'
137
123
  body['mobileId'] = MOBILE_ID
124
+
138
125
  elif type_ == 'bypass':
139
- body = {
140
- **cls.req_body_base(manager),
141
- **cls.req_body_auth(manager),
142
- **cls.req_body_details(),
143
- }
144
126
  body['method'] = 'bypass'
127
+
145
128
  elif type_ == 'bypassV2':
146
- body = {
147
- **cls.req_body_base(manager),
148
- **cls.req_body_auth(manager),
149
- **cls.req_body_details(),
150
- }
151
129
  body['deviceRegion'] = DEFAULT_REGION
152
130
  body['method'] = 'bypassV2'
131
+
153
132
  elif type_ == 'bypass_config':
154
- body = {
155
- **cls.req_body_base(manager),
156
- **cls.req_body_auth(manager),
157
- **cls.req_body_details(),
158
- }
159
133
  body['method'] = 'firmwareUpdateInfo'
160
134
 
161
135
  return body
@@ -237,6 +211,8 @@ class Helpers:
237
211
  API_BASE_URL + api, json=json_object, headers=headers,
238
212
  timeout=API_TIMEOUT
239
213
  )
214
+ else:
215
+ raise NameError(f'Invalid method {method}')
240
216
  except requests.exceptions.RequestException as e:
241
217
  logger.debug(e)
242
218
  except Exception as e: # pylint: disable=broad-except
@@ -74,11 +74,45 @@ def object_factory(dev_type, config, manager) -> Tuple[str, VeSyncBaseDevice]:
74
74
 
75
75
 
76
76
  class VeSync: # pylint: disable=function-redefined
77
- """VeSync API functions."""
77
+ """VeSync Manager Class."""
78
78
 
79
79
  def __init__(self, username, password, time_zone=DEFAULT_TZ,
80
80
  debug=False, redact=True):
81
- """Initialize VeSync class with username, password and time zone."""
81
+ """Initialize VeSync Manager.
82
+
83
+ This class is used as the manager for all VeSync objects, all methods and
84
+ API calls are performed from this class. Time zone, debug and redact are
85
+ optional. Time zone must be a string of an IANA time zone format. Once
86
+ class is instantiated, call `manager.login()` to log in to VeSync servers,
87
+ which returns `True` if successful. Once logged in, call `manager.update()`
88
+ to retrieve devices and update device details.
89
+
90
+ Parameters:
91
+ -----------
92
+ username : str
93
+ VeSync account username (usually email address)
94
+ password : str
95
+ VeSync account password
96
+ time_zone : str, optional
97
+ Time zone for device from IANA database, by default DEFAULT_TZ
98
+ debug : bool, optional
99
+ Enable debug logging, by default False
100
+ redact : bool, optional
101
+ Redact sensitive information in logs, by default True
102
+
103
+ Attributes
104
+ ----------
105
+ fans : list
106
+ List of VeSyncFan objects for humidifiers and air purifiers
107
+ outlets : list
108
+ List of VeSyncOutlet objects for smart plugs
109
+ switches : list
110
+ List of VeSyncSwitch objects for wall switches
111
+ bulbs : list
112
+ List of VeSyncBulb objects for smart bulbs
113
+ kitchen : list
114
+ List of VeSyncKitchen objects for smart kitchen appliances
115
+ """
82
116
  self.debug = debug
83
117
  if debug: # pragma: no cover
84
118
  logger.setLevel(logging.DEBUG)
@@ -308,7 +342,15 @@ class VeSync: # pylint: disable=function-redefined
308
342
  return proc_return
309
343
 
310
344
  def login(self) -> bool:
311
- """Return True if log in request succeeds."""
345
+ """Log into VeSync server.
346
+
347
+ Username and password are provided when class is instantiated.
348
+
349
+ Returns
350
+ -------
351
+ bool
352
+ True if login successful, False if not
353
+ """
312
354
  user_check = isinstance(self.username, str) and len(self.username) > 0
313
355
  pass_check = isinstance(self.password, str) and len(self.password) > 0
314
356
  if user_check is False:
@@ -346,7 +388,16 @@ class VeSync: # pylint: disable=function-redefined
346
388
  return False
347
389
 
348
390
  def update(self) -> None:
349
- """Fetch updated information about devices."""
391
+ """Fetch updated information about devices.
392
+
393
+ Pulls devices list from VeSync and instantiates any new devices. Devices
394
+ are stored in the instance attributes `outlets`, `switches`, `fans`, and
395
+ `bulbs`. The `_device_list` attribute is a dictionary of these attributes.
396
+
397
+ Returns
398
+ -------
399
+ None
400
+ """
350
401
  if self.device_time_check():
351
402
 
352
403
  if not self.enabled: