pyvesync 2.1.18__tar.gz → 3.0.0b0__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.
Files changed (72) hide show
  1. pyvesync-3.0.0b0/PKG-INFO +527 -0
  2. pyvesync-3.0.0b0/README.md +496 -0
  3. pyvesync-3.0.0b0/pyproject.toml +335 -0
  4. pyvesync-3.0.0b0/requirements.txt +3 -0
  5. pyvesync-3.0.0b0/setup.cfg +12 -0
  6. pyvesync-3.0.0b0/setup.py +6 -0
  7. pyvesync-3.0.0b0/src/pyvesync/base_devices/__init__.py +35 -0
  8. pyvesync-3.0.0b0/src/pyvesync/base_devices/bulb_base.py +348 -0
  9. pyvesync-3.0.0b0/src/pyvesync/base_devices/fan_base.py +398 -0
  10. pyvesync-3.0.0b0/src/pyvesync/base_devices/fryer_base.py +69 -0
  11. pyvesync-3.0.0b0/src/pyvesync/base_devices/humidifier_base.py +460 -0
  12. pyvesync-3.0.0b0/src/pyvesync/base_devices/outlet_base.py +315 -0
  13. pyvesync-3.0.0b0/src/pyvesync/base_devices/purifier_base.py +518 -0
  14. pyvesync-3.0.0b0/src/pyvesync/base_devices/switch_base.py +270 -0
  15. pyvesync-3.0.0b0/src/pyvesync/base_devices/thermostat_base.py +277 -0
  16. pyvesync-3.0.0b0/src/pyvesync/base_devices/vesyncbasedevice.py +580 -0
  17. pyvesync-3.0.0b0/src/pyvesync/const.py +749 -0
  18. pyvesync-3.0.0b0/src/pyvesync/device_container.py +360 -0
  19. pyvesync-3.0.0b0/src/pyvesync/device_map.py +1090 -0
  20. pyvesync-3.0.0b0/src/pyvesync/devices/__init__.py +1 -0
  21. pyvesync-3.0.0b0/src/pyvesync/devices/vesyncbulb.py +1254 -0
  22. pyvesync-3.0.0b0/src/pyvesync/devices/vesyncfan.py +238 -0
  23. pyvesync-3.0.0b0/src/pyvesync/devices/vesynchumidifier.py +785 -0
  24. {pyvesync-2.1.18/src/pyvesync → pyvesync-3.0.0b0/src/pyvesync/devices}/vesynckitchen.py +286 -277
  25. pyvesync-3.0.0b0/src/pyvesync/devices/vesyncoutlet.py +962 -0
  26. pyvesync-3.0.0b0/src/pyvesync/devices/vesyncpurifier.py +951 -0
  27. pyvesync-3.0.0b0/src/pyvesync/devices/vesyncswitch.py +449 -0
  28. pyvesync-3.0.0b0/src/pyvesync/devices/vesyncthermostat.py +175 -0
  29. pyvesync-3.0.0b0/src/pyvesync/models/__init__.py +11 -0
  30. pyvesync-3.0.0b0/src/pyvesync/models/base_models.py +117 -0
  31. pyvesync-3.0.0b0/src/pyvesync/models/bulb_models.py +173 -0
  32. pyvesync-3.0.0b0/src/pyvesync/models/bypass_models.py +264 -0
  33. pyvesync-3.0.0b0/src/pyvesync/models/fan_models.py +46 -0
  34. pyvesync-3.0.0b0/src/pyvesync/models/fryer_models.py +28 -0
  35. pyvesync-3.0.0b0/src/pyvesync/models/home_models.py +158 -0
  36. pyvesync-3.0.0b0/src/pyvesync/models/humidifier_models.py +187 -0
  37. pyvesync-3.0.0b0/src/pyvesync/models/outlet_models.py +194 -0
  38. pyvesync-3.0.0b0/src/pyvesync/models/purifier_models.py +204 -0
  39. pyvesync-3.0.0b0/src/pyvesync/models/switch_models.py +89 -0
  40. pyvesync-3.0.0b0/src/pyvesync/models/thermostat_models.py +122 -0
  41. pyvesync-3.0.0b0/src/pyvesync/models/vesync_models.py +252 -0
  42. pyvesync-3.0.0b0/src/pyvesync/utils/__init__.py +1 -0
  43. pyvesync-3.0.0b0/src/pyvesync/utils/colors.py +248 -0
  44. pyvesync-3.0.0b0/src/pyvesync/utils/device_mixins.py +366 -0
  45. pyvesync-3.0.0b0/src/pyvesync/utils/enum_utils.py +25 -0
  46. pyvesync-3.0.0b0/src/pyvesync/utils/errors.py +859 -0
  47. pyvesync-3.0.0b0/src/pyvesync/utils/helpers.py +696 -0
  48. pyvesync-3.0.0b0/src/pyvesync/utils/logs.py +500 -0
  49. pyvesync-3.0.0b0/src/pyvesync/vesync.py +445 -0
  50. pyvesync-3.0.0b0/src/pyvesync.egg-info/PKG-INFO +527 -0
  51. pyvesync-3.0.0b0/src/pyvesync.egg-info/SOURCES.txt +56 -0
  52. pyvesync-3.0.0b0/src/pyvesync.egg-info/requires.txt +15 -0
  53. pyvesync-2.1.18/PKG-INFO +0 -1109
  54. pyvesync-2.1.18/README.md +0 -1070
  55. pyvesync-2.1.18/requirements.txt +0 -1
  56. pyvesync-2.1.18/setup.cfg +0 -26
  57. pyvesync-2.1.18/setup.py +0 -41
  58. pyvesync-2.1.18/src/pyvesync/helpers.py +0 -854
  59. pyvesync-2.1.18/src/pyvesync/vesync.py +0 -450
  60. pyvesync-2.1.18/src/pyvesync/vesyncbasedevice.py +0 -202
  61. pyvesync-2.1.18/src/pyvesync/vesyncbulb.py +0 -1416
  62. pyvesync-2.1.18/src/pyvesync/vesyncfan.py +0 -3218
  63. pyvesync-2.1.18/src/pyvesync/vesyncoutlet.py +0 -806
  64. pyvesync-2.1.18/src/pyvesync/vesyncswitch.py +0 -412
  65. pyvesync-2.1.18/src/pyvesync.egg-info/PKG-INFO +0 -1109
  66. pyvesync-2.1.18/src/pyvesync.egg-info/SOURCES.txt +0 -20
  67. pyvesync-2.1.18/src/pyvesync.egg-info/requires.txt +0 -7
  68. {pyvesync-2.1.18 → pyvesync-3.0.0b0}/LICENSE +0 -0
  69. {pyvesync-2.1.18 → pyvesync-3.0.0b0}/MANIFEST.in +0 -0
  70. {pyvesync-2.1.18 → pyvesync-3.0.0b0}/src/pyvesync/__init__.py +0 -0
  71. {pyvesync-2.1.18 → pyvesync-3.0.0b0}/src/pyvesync.egg-info/dependency_links.txt +0 -0
  72. {pyvesync-2.1.18 → pyvesync-3.0.0b0}/src/pyvesync.egg-info/top_level.txt +0 -0
@@ -0,0 +1,527 @@
1
+ Metadata-Version: 2.4
2
+ Name: pyvesync
3
+ Version: 3.0.0b0
4
+ Summary: pyvesync is a library to manage Etekcity Devices, Cosori Air Fryers, and Levoit Air Purifiers run on the VeSync app.
5
+ Author: Mark Perdue
6
+ Author-email: Joe Trabulsy <webdjoe@gmail.com>
7
+ License: MIT
8
+ Project-URL: Home Page, https://github.com/webdjoe/pyvesync
9
+ Keywords: iot,vesync,levoit,etekcity,cosori,valceno
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Natural Language :: English
13
+ Classifier: Topic :: Home Automation
14
+ Requires-Python: >=3.11
15
+ Description-Content-Type: text/markdown
16
+ License-File: LICENSE
17
+ Requires-Dist: aiohttp>=3.8.1
18
+ Requires-Dist: mashumaro[orjson]>=3.13.1
19
+ Requires-Dist: python-dateutil>=2.8.1
20
+ Provides-Extra: dev
21
+ Requires-Dist: pytest; extra == "dev"
22
+ Requires-Dist: pytest-cov; extra == "dev"
23
+ Requires-Dist: pyyaml; extra == "dev"
24
+ Requires-Dist: tox; extra == "dev"
25
+ Provides-Extra: docs
26
+ Requires-Dist: mkdocstrings-python; extra == "docs"
27
+ Requires-Dist: mkdocs; extra == "docs"
28
+ Requires-Dist: mkdocs-material; extra == "docs"
29
+ Requires-Dist: mkdocstrings; extra == "docs"
30
+ Dynamic: license-file
31
+
32
+ # 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 -->
33
+
34
+ pyvesync is a library to manage VeSync compatible [smart home devices](#supported-devices)
35
+
36
+ <a href="https://webdjoe.github.io/pyvesync/latest"><img src="./docs/assets/docs.svg"></a>
37
+
38
+ **Check out the new [pyvesync documentation](https://webdjoe.github.io/pyvesync/) for usage and full API details.**
39
+
40
+ ## Supported Product Types
41
+
42
+ 1. Outlets
43
+ 2. Switches
44
+ 3. Fans
45
+ 4. Air Purifiers
46
+ 5. Humidifiers
47
+ 6. Bulbs
48
+ 7. Air Fryers
49
+ 8. Thermostats
50
+
51
+ See the [supported devices](https://webdjoe.github.io/pyvesync/latest/supported_devices/) page for a complete list of supported devices and device types.
52
+
53
+ ## What's new in pyvesync 3.0
54
+
55
+ **BREAKING CHANGES** - The release of pyvesync 3.0 comes with many improvements and new features, but as a result there are many breaking changes. The structure has been completely refactored, so please read through the README and thoroughly test before deploying.
56
+
57
+ The goal is to standardize the library across all devices to allow easier and consistent maintainability moving forward. The original library was created 8 years ago for supporting only a few outlets, it was not designed for supporting 20+ different devices.
58
+
59
+ Some of the changes are:
60
+
61
+ - Asynchronous network requests with aiohttp.
62
+ - Strong typing of all network requests and responses.
63
+ - Created base classes for all devices for easier `isinstance` checks.
64
+ - Separated the instantiated devices to a `DeviceContainer` class that acts as a mutable set with convenience methods.
65
+ - Standardized the API for all device to follow a common naming convention. No more devices with different names for the same functionality.
66
+ - Implemented custom exceptions and error (code) handling for API responses.
67
+ - `const` module to hold all library constants
68
+ - Built the `DeviceMap` class to hold the mapping and features of devices.
69
+ - COMING SOON: Use API to pull device modes and operating features.
70
+
71
+ See [pyvesync V3](https://webdjoe.github.io/pyvesync/latest/pyvesync3/) for more information on the changes.
72
+
73
+ ### Asynchronous operation
74
+
75
+ Library is now asynchronous, using aiohttp as a replacement for requests. The `pyvesync.VeSync` class is an asynchronous context manager. A `aiohttp.ClientSession` can be passed or created internally.
76
+
77
+ ```python
78
+ import asyncio
79
+ import aiohttp
80
+ from pyvesync.vesync import VeSync
81
+
82
+ async def main():
83
+ async with VeSync("user", "password") as manager:
84
+
85
+ # To enable debug mode - prints request and response content for
86
+ # api calls that return an error code
87
+ manager.debug = True
88
+ # Redact mode is enabled by default, set to False to disable
89
+ manager.redact = False
90
+
91
+ # To print request & response content for all API calls enable verbose mode
92
+ manager.verbose = True
93
+
94
+ # To print logs to file
95
+ manager.log_to_file("pyvesync.log")
96
+
97
+ await manager.login() # Still returns true
98
+ if not manager.enabled:
99
+ print("Not logged in.")
100
+ return
101
+ await manager.get_devices() # Instantiates supported devices in device list
102
+ await manager.update() # Updates the state of all devices
103
+
104
+ # manager.devices is a DeviceContainer object
105
+ # manager.devices.outlets is a list of VeSyncOutlet objects
106
+ # manager.devices.switches is a list of VeSyncSwitch objects
107
+ # manager.devices.fans is a list of VeSyncFan objects
108
+ # manager.devices.bulbs is a list of VeSyncBulb objects
109
+ # manager.devices.humidifiers is a list of VeSyncHumid objects
110
+ # manager.devices.air_purifiers is a list of VeSyncAir objects
111
+ # manager.devices.air_fryers is a list of VeSyncAirFryer objects
112
+ # manager.devices.thermostats is a list of VeSyncThermostat objects
113
+
114
+ for outlet in manager.devices.outlets:
115
+ # The outlet object contain all action methods and static device attributes
116
+ await outlet.update()
117
+ await outlet.turn_off()
118
+ outlet.display() # Print static device information, name, type, CID, etc.
119
+
120
+ # State of object held in `device.state` attribute
121
+ print(outlet.state)
122
+ state_json = outlet.dumps() # Returns JSON string of device state
123
+ state_bytes = orjson.dumps(outlet.state) # Returns bytes of device state
124
+
125
+ # to view the response information of the last API call
126
+ print(outlet.last_response)
127
+ # Prints a ResponseInfo object containing error code,
128
+ # and other response information
129
+
130
+
131
+ # Or use your own session
132
+ session = aiohttp.ClientSession()
133
+
134
+ async def main():
135
+ async with VeSync("user", "password", session=session):
136
+ await manager.login()
137
+ await manager.update()
138
+
139
+
140
+
141
+ if __name__ == "__main__":
142
+ asyncio.run(main())
143
+ ```
144
+
145
+ If using `async with` is not ideal, the `__aenter__()` and `__aexit__()` methods need to be called manually:
146
+
147
+ ```python
148
+ manager = VeSync(user, password)
149
+
150
+ await manager.__aenter__()
151
+
152
+ ...
153
+
154
+ await manager.__aexit__(None, None, None)
155
+ ```
156
+
157
+ pvesync will close the `ClientSession` that was created by the library on `__aexit__`. If a session is passed in as an argument, the library does not close it. If a session is passed in and not closed, aiohttp will generate an error on exit:
158
+
159
+ ```text
160
+ 2025-02-16 14:41:07 - ERROR - asyncio - Unclosed client session
161
+ 2025-02-16 14:41:07 - ERROR - asyncio - Unclosed connector
162
+ ```
163
+
164
+ ### VeSync Class Signature
165
+
166
+ The VeSync signature is:
167
+
168
+ ```python
169
+ VeSync(
170
+ username: str,
171
+ password: str,
172
+ session: ClientSession | None = None,
173
+ time_zone: str = DEFAULT_TZ # America/New_York
174
+ )
175
+ ```
176
+
177
+ The VeSync class no longer accepts a `debug` or `redact` argument. To set debug the library set `manager.debug = True` to the instance and `manager.redact = True`.
178
+
179
+ ### Product Types
180
+
181
+ There is a new nomenclature for product types that defines the device class. The
182
+ `device.product_type` attribute defines the product type based on the VeSync API. The product type is used to determine the device class and module. The currently supported product types are:
183
+
184
+ 1. `outlet` - Outlet devices
185
+ 2. `switch` - Wall switches
186
+ 3. `fan` - Fans (not air purifiers or humidifiers)
187
+ 4. `purifier` - Air purifiers (not humidifiers)
188
+ 5. `humidifier` - Humidifiers (not air purifiers)
189
+ 6. `bulb` - Light bulbs (not dimmers or switches)
190
+ 7. `airfryer` - Air fryers
191
+
192
+ See [Supported Devices](#supported-devices) for a complete list of supported devices and models.
193
+
194
+ ### Custom Exceptions
195
+
196
+ Exceptions are no longer caught by the library and must be handled by the user. Exceptions are raised by server errors and aiohttp connection errors.
197
+
198
+ Errors that occur at the aiohttp level are raised automatically and propogated to the user. That means exceptions raised by aiohttp that inherit from `aiohttp.ClientError` are propogated.
199
+
200
+ When the connection to the VeSync API succeeds but returns an error code that prevents the library from functioning a custom exception inherrited from `pyvesync.logs.VeSyncError` is raised.
201
+
202
+ Custom Exceptions raised by all API calls:
203
+
204
+ - `pyvesync.logs.VeSyncServerError` - The API connected and returned a code indicated there is a server-side error.
205
+ - `pyvesync.logs.VeSyncRateLimitError` - The API's rate limit has been exceeded.
206
+ - `pyvesync.logs.VeSyncAPIStatusCodeError` - The API returned a non-200 status code.
207
+ - `pyvesync.logs.VeSyncAPIResponseError` - The response from the API was not in an expected format.
208
+
209
+ Login API Exceptions
210
+
211
+ - `pyvesync.logs.VeSyncLoginError` - The username or password is incorrect.
212
+
213
+ See [errors](https://webdjoe.github.io/pyvesync/latest/development/utils/errors) documentation for a complete list of error codes and exceptions.
214
+
215
+ The [raise_api_errors()](https://webdjoe.github.io/pyvesync/latest/development/utils/errors/#pyvesync.utils.errors.raise_api_errors) function is called for every API call and checks for general response errors. It can raise the following exceptions:
216
+
217
+ - `VeSyncServerError` - The API connected and returned a code indicated there is a server-side error.
218
+ - `VeSyncRateLimitError` - The API's rate limit has been exceeded.
219
+ - `VeSyncAPIStatusCodeError` - The API returned a non-200 status code.
220
+ - `VeSyncTokenError` - The API returned a token error and requires `login()` to be called again.
221
+ - `VeSyncLoginError` - The user name or password is incorrect.
222
+
223
+ ## Installation
224
+
225
+ Install the latest version from pip:
226
+
227
+ ```bash
228
+ pip install pyvesync
229
+ ```
230
+
231
+ <!--SUPPORTED DEVICES START-->
232
+
233
+ ## Supported Devices
234
+
235
+ <!--SUPPORTED OUTLETS START-->
236
+
237
+ ### Etekcity Outlets
238
+
239
+ 1. Voltson Smart WiFi Outlet- Round (7A model ESW01-USA)
240
+ 2. Voltson Smart WiFi Outlet - Round (10A model ESW01-EU)
241
+ 3. Voltson Smart Wifi Outlet - Round (10A model ESW03-USA)
242
+ 4. Voltson Smart Wifi Outlet - Round (10A model ESW10-USA)
243
+ 5. Voltson Smart WiFi Outlet - Rectangle (15A model ESW15-USA)
244
+ 6. Two Plug Outdoor Outlet (ESO15-TB) (Each plug is a separate `VeSyncOutlet` object, energy readings are for both plugs combined)
245
+
246
+ <!--SUPPORTED OUTLETS END-->
247
+
248
+ <!--SUPPORTED SWITCHES START-->
249
+
250
+ ### Wall Switches
251
+
252
+ 1. Etekcity Smart WiFi Light Switch (model ESWL01)
253
+ 2. Etekcity Wifi Dimmer Switch (ESD16)
254
+
255
+ <!--SUPPORTED SWITCHES END-->
256
+
257
+ ### Levoit Air Purifiers
258
+
259
+ 1. LV-PUR131S
260
+ 2. Core 200S
261
+ 3. Core 300S
262
+ 4. Core 400S
263
+ 5. Core 600S
264
+ 6. Vital 100S
265
+ 7. Vital 200S
266
+ 8. Everest Air
267
+
268
+ ### Etekcity Bulbs
269
+
270
+ 1. Soft White Dimmable Smart Bulb (ESL100)
271
+ 2. Cool to Soft White Tunable Dimmable Bulb (ESL100CW)
272
+
273
+ ### Valceno Bulbs
274
+
275
+ 1. Valceno Multicolor Bulb (XYD0001)
276
+
277
+ ### Levoit Humidifiers
278
+
279
+ 1. Dual 200S
280
+ 2. Classic 300S
281
+ 3. LV600S
282
+ 4. OasisMist 450S
283
+ 5. OasisMist 600S
284
+ 6. OasisMist 1000S
285
+
286
+ ### Cosori Air Fryer
287
+
288
+ 1. Cosori 3.7 and 5.8 Quart Air Fryer
289
+
290
+ ### Fans
291
+
292
+ 1. 42 in. Tower Fan
293
+
294
+ <!--SUPPORTED DEVICES END-->
295
+
296
+ ## Usage
297
+
298
+ ```python
299
+ import asyncio
300
+ from pyvesync import VeSync
301
+ from pyvesync.logs import VeSyncLoginError
302
+
303
+ # VeSync is an asynchronous context manager
304
+ # VeSync(username, password, debug=False, redact=True, session=None)
305
+
306
+ async def main():
307
+ async with VeSync("user", "password") as manager:
308
+ await manager.login() # Still returns true
309
+ await manager.update()
310
+
311
+ # Acts as a set of device instances
312
+ device_container = manager.devices
313
+
314
+ outlets = device_container.outlets # List of outlet instances
315
+ outlet = outlets[0]
316
+ await outlet.update()
317
+ await outlet.turn_off()
318
+ outlet.display()
319
+
320
+ # Iterate of entire device list
321
+ for devices in device_container:
322
+ device.display()
323
+
324
+
325
+ if __name__ == "__main__":
326
+ asyncio.run(main())
327
+ ```
328
+
329
+ Devices are stored in the respective lists in the instantiated `VeSync` class:
330
+
331
+ ```python
332
+ await manager.login() # Asynchronous
333
+ await manager.update() # Asynchronous
334
+
335
+
336
+ # Acts as set with properties that return product type lists
337
+ manager.devices = DeviceContainer instance
338
+
339
+ manager.devices.outlets = [VeSyncOutletInstances]
340
+ manager.devices.switches = [VeSyncSwitchInstances]
341
+ manager.devices.fans = [VeSyncFanInstances]
342
+ manager.devices.bulbs = [VeSyncBulbInstances]
343
+ manager.devices.purifiers = [VeSyncPurifierInstances]
344
+ manager.devices.humidifiers = [VeSyncHumidifierInstances]
345
+ manager.devices.air_fryers = [VeSyncAirFryerInstances]
346
+ managers.devices.thermostats = [VeSyncThermostatInstances]
347
+
348
+ # Get device by device name
349
+ dev_name = "My Device"
350
+ for device in manager.devices:
351
+ if device.device_name == dev_name:
352
+ my_device = device
353
+ device.display()
354
+
355
+ # Turn on switch by switch name
356
+ switch_name = "My Switch"
357
+ for switch in manager.devices.switches:
358
+ if switch.device_name == switch_name:
359
+ await switch.turn_on() # Asynchronous
360
+ ```
361
+
362
+ See the [device documentation](https://webdjoe.github.io/pyvesync/latest/devices/) for more information on the device classes and their methods/states.
363
+
364
+ ## Debug mode and redact
365
+
366
+ To make it easier to debug, there is a `debug` argument in the `VeSync` method. This prints out your device list and any other debug log messages.
367
+
368
+ The `redact` argument removes any tokens and account identifiers from the output to allow for easier sharing. The `redact` argument has no impact if `debug` is not `True`.
369
+
370
+ ```python
371
+ import asyncio
372
+ import aiohttp
373
+ from pyvesync.vesync import VeSync
374
+
375
+ async def main():
376
+ async with VeSync("user", "password") as manager:
377
+ manager.debug = True
378
+ manager.redact = True # True by default
379
+ await manager.login() # Still returns true
380
+ await manager.update()
381
+
382
+ outlet = manager.outlets[0]
383
+ await outlet.update()
384
+ await outlet.turn_off()
385
+ outlet.display()
386
+
387
+
388
+ if __name__ == "__main__":
389
+ asyncio.run(main())
390
+ ```
391
+
392
+ ## Feature Requests
393
+
394
+ Before filing an issue to request a new feature or device, please ensure that you will take the time to test the feature throuroughly. New features cannot be simply tested on Home Assistant. A separate integration must be created which is not part of this library. In order to test a new feature, clone the branch and install into a new virtual environment.
395
+
396
+ ```bash
397
+ mkdir python_test && cd python_test
398
+
399
+ # Check Python version is 3.11 or higher
400
+ python3 --version # or python --version or python3.8 --version
401
+ # Create a new venv
402
+ python3 -m venv pyvesync-venv
403
+ # Activate the venv on linux
404
+ source pyvesync-venv/bin/activate
405
+ # or ....
406
+ pyvesync-venv\Scripts\activate.ps1 # on powershell
407
+ pyvesync-venv\Scripts\activate.bat # on command prompt
408
+
409
+ # Install branch to be tested into new virtual environment:
410
+ pip install git+https://github.com/webdjoe/pyvesync.git@BRANCHNAME
411
+
412
+ # Install a PR that has not been merged:
413
+ pip install git+https://github.com/webdjoe/pyvesync.git@refs/pull/PR_NUMBER/head
414
+ ```
415
+
416
+ Test functionality with a script, please adjust methods and logging statements to the device you are testing.
417
+
418
+ `test.py`
419
+
420
+ ```python
421
+ import asyncio
422
+ import sys
423
+ import logging
424
+ import json
425
+ from functool import chain
426
+ from pyvesync import VeSync
427
+
428
+ logger = logging.getLogger(__name__)
429
+ logger.setLevel(logging.DEBUG)
430
+
431
+ USERNAME = "YOUR USERNAME"
432
+ PASSWORD = "YOUR PASSWORD"
433
+
434
+ DEVICE_NAME = "Device" # Device to test
435
+
436
+ async def test_device():
437
+ # Instantiate VeSync class and login
438
+ async with VeSync(USERNAME, PASSWORD, debug=True, redact=True) as manager:
439
+ login = await manager.login()
440
+
441
+ # Pull and update devices
442
+ await manager.update()
443
+
444
+ for dev in manager.devices:
445
+ # Print all device info
446
+ logger.debug(dev.device_name + "\n")
447
+ logger.debug(dev.display())
448
+
449
+ # Find correct device
450
+ if dev.device_name.lower() != DEVICE_NAME.lower():
451
+ logger.debug("%s is not %s, continuing", self.device_name, DEVICE_NAME)
452
+ continue
453
+
454
+ logger.debug('--------------%s-----------------' % dev.device_name)
455
+ logger.debug(dev.display())
456
+ logger.debug(dev.displayJSON())
457
+ # Test all device methods and functionality
458
+ # Test Properties
459
+ logger.debug("Fan is on - %s", dev.is_on)
460
+ logger.debug("Modes - %s", dev.modes)
461
+ logger.debug("Fan Level - %s", dev.fan_level)
462
+ logger.debug("Fan Air Quality - %s", dev.air_quality)
463
+ logger.debug("Screen Status - %s", dev.screen_status)
464
+
465
+ logger.debug("Turning on")
466
+ await fan.turn_on()
467
+ logger.debug("Device is on %s", dev.is_on)
468
+
469
+ logger.debug("Turning off")
470
+ await fan.turn_off()
471
+ logger.debug("Device is on %s", dev.is_on)
472
+
473
+ logger.debug("Sleep mode")
474
+ fan.sleep_mode()
475
+ logger.debug("Current mode - %s", dev.details['mode'])
476
+
477
+ fan.auto_mode()
478
+
479
+ logger.debug("Set Fan Speed - %s", dev.set_fan_speed)
480
+ logger.debug("Current Fan Level - %s", dev.fan_level)
481
+ logger.debug("Current mode - %s", dev.mode)
482
+
483
+ # Display all device info
484
+ logger.debug(dev.display(state=True))
485
+ logger.debug(dev.to_json(state=True, indent=True))
486
+ dev_dict = dev.to_dict(state=True)
487
+
488
+ if __name__ == "__main__":
489
+ logger.debug("Testing device")
490
+ asyncio.run(test_device())
491
+ ...
492
+
493
+ ```
494
+
495
+ ## Device Requests
496
+
497
+ SSL pinning makes capturing packets much harder. In order to be able to capture packets, SSL pinning needs to be disabled before running an SSL proxy. Use an Android emulator such as Android Studio, which is available for Windows and Linux for free. Download the APK from APKPure or a similiar site and use [Objection](https://github.com/sensepost/objection) or [Frida](https://frida.re/docs/gadget/). Followed by capturing the packets with Charles Proxy or another SSL proxy application.
498
+
499
+ Be sure to capture all packets from the device list and each of the possible device menus and actions. Please redact the `accountid` and `token` from the captured packets. If you feel you must redact other keys, please do not delete them entirely. Replace letters with "A" and numbers with "1", leave all punctuation intact and maintain length.
500
+
501
+ For example:
502
+
503
+ Before:
504
+
505
+ ```json
506
+ {
507
+ "tk": "abc123abc123==3rf",
508
+ "accountId": "123456789",
509
+ "cid": "abcdef12-3gh-ij"
510
+ }
511
+ ```
512
+
513
+ After:
514
+
515
+ ```json
516
+ {
517
+ "tk": "AAA111AAA111==1AA",
518
+ "accountId": "111111111",
519
+ "cid": "AAAAAA11-1AA-AA"
520
+ }
521
+ ```
522
+
523
+ ## Contributing
524
+
525
+ All [contributions](CONTRIBUTING.md) are welcome.
526
+
527
+ This project is licensed under [MIT](LICENSE).