pyvesync 2.1.18__tar.gz → 3.0.0__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 (75) hide show
  1. pyvesync-3.0.0/PKG-INFO +567 -0
  2. pyvesync-3.0.0/README.md +521 -0
  3. pyvesync-3.0.0/pyproject.toml +372 -0
  4. pyvesync-3.0.0/requirements.txt +3 -0
  5. pyvesync-3.0.0/setup.cfg +12 -0
  6. pyvesync-3.0.0/setup.py +5 -0
  7. pyvesync-3.0.0/src/pyvesync/__init__.py +5 -0
  8. pyvesync-3.0.0/src/pyvesync/base_devices/__init__.py +35 -0
  9. pyvesync-3.0.0/src/pyvesync/base_devices/bulb_base.py +330 -0
  10. pyvesync-3.0.0/src/pyvesync/base_devices/fan_base.py +392 -0
  11. pyvesync-3.0.0/src/pyvesync/base_devices/fryer_base.py +68 -0
  12. pyvesync-3.0.0/src/pyvesync/base_devices/humidifier_base.py +462 -0
  13. pyvesync-3.0.0/src/pyvesync/base_devices/outlet_base.py +327 -0
  14. pyvesync-3.0.0/src/pyvesync/base_devices/purifier_base.py +525 -0
  15. pyvesync-3.0.0/src/pyvesync/base_devices/switch_base.py +271 -0
  16. pyvesync-3.0.0/src/pyvesync/base_devices/thermostat_base.py +283 -0
  17. pyvesync-3.0.0/src/pyvesync/base_devices/vesyncbasedevice.py +572 -0
  18. pyvesync-3.0.0/src/pyvesync/const.py +825 -0
  19. pyvesync-3.0.0/src/pyvesync/device_container.py +373 -0
  20. pyvesync-3.0.0/src/pyvesync/device_map.py +1172 -0
  21. pyvesync-3.0.0/src/pyvesync/devices/__init__.py +1 -0
  22. pyvesync-3.0.0/src/pyvesync/devices/vesyncbulb.py +1126 -0
  23. pyvesync-3.0.0/src/pyvesync/devices/vesyncfan.py +233 -0
  24. pyvesync-3.0.0/src/pyvesync/devices/vesynchumidifier.py +808 -0
  25. pyvesync-3.0.0/src/pyvesync/devices/vesynckitchen.py +651 -0
  26. pyvesync-3.0.0/src/pyvesync/devices/vesyncoutlet.py +954 -0
  27. pyvesync-3.0.0/src/pyvesync/devices/vesyncpurifier.py +990 -0
  28. pyvesync-3.0.0/src/pyvesync/devices/vesyncswitch.py +415 -0
  29. pyvesync-3.0.0/src/pyvesync/devices/vesyncthermostat.py +171 -0
  30. pyvesync-3.0.0/src/pyvesync/models/__init__.py +11 -0
  31. pyvesync-3.0.0/src/pyvesync/models/base_models.py +129 -0
  32. pyvesync-3.0.0/src/pyvesync/models/bulb_models.py +197 -0
  33. pyvesync-3.0.0/src/pyvesync/models/bypass_models.py +289 -0
  34. pyvesync-3.0.0/src/pyvesync/models/fan_models.py +47 -0
  35. pyvesync-3.0.0/src/pyvesync/models/fryer_models.py +34 -0
  36. pyvesync-3.0.0/src/pyvesync/models/home_models.py +169 -0
  37. pyvesync-3.0.0/src/pyvesync/models/humidifier_models.py +202 -0
  38. pyvesync-3.0.0/src/pyvesync/models/outlet_models.py +220 -0
  39. pyvesync-3.0.0/src/pyvesync/models/purifier_models.py +261 -0
  40. pyvesync-3.0.0/src/pyvesync/models/switch_models.py +101 -0
  41. pyvesync-3.0.0/src/pyvesync/models/thermostat_models.py +134 -0
  42. pyvesync-3.0.0/src/pyvesync/models/vesync_models.py +439 -0
  43. pyvesync-3.0.0/src/pyvesync/utils/__init__.py +1 -0
  44. pyvesync-3.0.0/src/pyvesync/utils/colors.py +250 -0
  45. pyvesync-3.0.0/src/pyvesync/utils/device_mixins.py +362 -0
  46. pyvesync-3.0.0/src/pyvesync/utils/enum_utils.py +26 -0
  47. pyvesync-3.0.0/src/pyvesync/utils/errors.py +867 -0
  48. pyvesync-3.0.0/src/pyvesync/utils/helpers.py +700 -0
  49. pyvesync-3.0.0/src/pyvesync/utils/logs.py +525 -0
  50. pyvesync-3.0.0/src/pyvesync/vesync.py +638 -0
  51. pyvesync-3.0.0/src/pyvesync/vesynchome.py +152 -0
  52. pyvesync-3.0.0/src/pyvesync.egg-info/PKG-INFO +567 -0
  53. pyvesync-3.0.0/src/pyvesync.egg-info/SOURCES.txt +57 -0
  54. pyvesync-3.0.0/src/pyvesync.egg-info/requires.txt +29 -0
  55. pyvesync-2.1.18/PKG-INFO +0 -1109
  56. pyvesync-2.1.18/README.md +0 -1070
  57. pyvesync-2.1.18/requirements.txt +0 -1
  58. pyvesync-2.1.18/setup.cfg +0 -26
  59. pyvesync-2.1.18/setup.py +0 -41
  60. pyvesync-2.1.18/src/pyvesync/__init__.py +0 -11
  61. pyvesync-2.1.18/src/pyvesync/helpers.py +0 -854
  62. pyvesync-2.1.18/src/pyvesync/vesync.py +0 -450
  63. pyvesync-2.1.18/src/pyvesync/vesyncbasedevice.py +0 -202
  64. pyvesync-2.1.18/src/pyvesync/vesyncbulb.py +0 -1416
  65. pyvesync-2.1.18/src/pyvesync/vesyncfan.py +0 -3218
  66. pyvesync-2.1.18/src/pyvesync/vesynckitchen.py +0 -655
  67. pyvesync-2.1.18/src/pyvesync/vesyncoutlet.py +0 -806
  68. pyvesync-2.1.18/src/pyvesync/vesyncswitch.py +0 -412
  69. pyvesync-2.1.18/src/pyvesync.egg-info/PKG-INFO +0 -1109
  70. pyvesync-2.1.18/src/pyvesync.egg-info/SOURCES.txt +0 -20
  71. pyvesync-2.1.18/src/pyvesync.egg-info/requires.txt +0 -7
  72. {pyvesync-2.1.18 → pyvesync-3.0.0}/LICENSE +0 -0
  73. {pyvesync-2.1.18 → pyvesync-3.0.0}/MANIFEST.in +0 -0
  74. {pyvesync-2.1.18 → pyvesync-3.0.0}/src/pyvesync.egg-info/dependency_links.txt +0 -0
  75. {pyvesync-2.1.18 → pyvesync-3.0.0}/src/pyvesync.egg-info/top_level.txt +0 -0
@@ -0,0 +1,567 @@
1
+ Metadata-Version: 2.4
2
+ Name: pyvesync
3
+ Version: 3.0.0
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
+ Project-URL: Documentation, https://webdjoe.github.io/pyvesync/latest/
10
+ Keywords: iot,vesync,levoit,etekcity,cosori,valceno
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Natural Language :: English
14
+ Classifier: Topic :: Home Automation
15
+ Requires-Python: >=3.11
16
+ Description-Content-Type: text/markdown
17
+ License-File: LICENSE
18
+ Requires-Dist: aiohttp>=3.8.1
19
+ Requires-Dist: mashumaro[orjson]>=3.13.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: pylint; extra == "dev"
25
+ Requires-Dist: mypy; extra == "dev"
26
+ Requires-Dist: ruff; extra == "dev"
27
+ Requires-Dist: pre-commit; extra == "dev"
28
+ Requires-Dist: tox; extra == "dev"
29
+ Requires-Dist: requests; extra == "dev"
30
+ Requires-Dist: aiofiles; extra == "dev"
31
+ Provides-Extra: docs
32
+ Requires-Dist: mkdocstrings-python; extra == "docs"
33
+ Requires-Dist: mkdocs; extra == "docs"
34
+ Requires-Dist: ruff; extra == "docs"
35
+ Requires-Dist: Pygments; extra == "docs"
36
+ Requires-Dist: mkdocs-material; extra == "docs"
37
+ Requires-Dist: mkdocstrings; extra == "docs"
38
+ Requires-Dist: mkdocs-include-markdown-plugin; extra == "docs"
39
+ Requires-Dist: mkdocs-material-extensions; extra == "docs"
40
+ Requires-Dist: pymdown-extensions; extra == "docs"
41
+ Requires-Dist: mike; extra == "docs"
42
+ Requires-Dist: mkdocs-git-revision-date-localized-plugin; extra == "docs"
43
+ Requires-Dist: docstring-inheritance; extra == "docs"
44
+ Requires-Dist: mkdocs-autoref; extra == "docs"
45
+ Dynamic: license-file
46
+
47
+ # 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 -->
48
+
49
+ pyvesync is a library to manage VeSync compatible [smart home devices](#supported-devices)
50
+
51
+ <a href="https://webdjoe.github.io/pyvesync/latest"><img src="./docs/assets/docs.svg"></a>
52
+
53
+ **Check out the new [pyvesync documentation](https://webdjoe.github.io/pyvesync/) for usage and full API details.**
54
+
55
+ ## Supported Product Types
56
+
57
+ 1. Outlets
58
+ 2. Switches
59
+ 3. Fans
60
+ 4. Air Purifiers
61
+ 5. Humidifiers
62
+ 6. Bulbs
63
+ 7. Air Fryers
64
+ 8. Thermostats
65
+
66
+ See the [supported devices](https://webdjoe.github.io/pyvesync/latest/supported_devices/) page for a complete list of supported devices and device types.
67
+
68
+ ## What's new in pyvesync 3.0
69
+
70
+ **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.
71
+
72
+ 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.
73
+
74
+ Some of the changes are:
75
+
76
+ - Asynchronous network requests with aiohttp.
77
+ - Strong typing of all network requests and responses.
78
+ - Created base classes for all devices for easier `isinstance` checks.
79
+ - Separated the instantiated devices to a `DeviceContainer` class that acts as a mutable set with convenience methods.
80
+ - Standardized the API for all device to follow a common naming convention. No more devices with different names for the same functionality.
81
+ - Implemented custom exceptions and error (code) handling for API responses.
82
+ - `const` module to hold all library constants
83
+ - Built the `DeviceMap` class to hold the mapping and features of devices.
84
+ - COMING SOON: Use API to pull device modes and operating features.
85
+
86
+ See [pyvesync V3](https://webdjoe.github.io/pyvesync/latest/pyvesync3/) for more information on the changes.
87
+
88
+ ### Asynchronous operation
89
+
90
+ 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.
91
+
92
+ ```python
93
+ import asyncio
94
+ import aiohttp
95
+ from pyvesync.vesync import VeSync
96
+
97
+ async def main():
98
+ async with VeSync(
99
+ username="user",
100
+ password="password",
101
+ country_code="US", # Optional - country Code to select correct server
102
+ session=session, # Optional - aiohttp.ClientSession
103
+ time_zone="America/New_York", # Optional - Timezone, defaults to America/New_York
104
+ debug=False, # Optional - Debug output
105
+ redact=True # Optional - Redact sensitive information from logs
106
+ ) as manager:
107
+
108
+ # To enable debug mode - prints request and response content for
109
+ # api calls that return an error code
110
+ manager.debug = True
111
+ # Redact mode is enabled by default, set to False to disable
112
+ manager.redact = False
113
+
114
+ # To print request & response content for all API calls enable verbose mode
115
+ manager.verbose = True
116
+
117
+ # To print logs to file
118
+ manager.log_to_file("pyvesync.log")
119
+
120
+ await manager.login()
121
+ if not manager.enabled:
122
+ print("Not logged in.")
123
+ return
124
+ await manager.get_devices() # Instantiates supported devices in device list, automatically called by login, only needed if you would like updates
125
+ await manager.update() # Updates the state of all devices
126
+
127
+ # manager.devices is a DeviceContainer object
128
+ # manager.devices.outlets is a list of VeSyncOutlet objects
129
+ # manager.devices.switches is a list of VeSyncSwitch objects
130
+ # manager.devices.fans is a list of VeSyncFan objects
131
+ # manager.devices.bulbs is a list of VeSyncBulb objects
132
+ # manager.devices.humidifiers is a list of VeSyncHumid objects
133
+ # manager.devices.air_purifiers is a list of VeSyncAir objects
134
+ # manager.devices.air_fryers is a list of VeSyncAirFryer objects
135
+ # manager.devices.thermostats is a list of VeSyncThermostat objects
136
+
137
+ for outlet in manager.devices.outlets:
138
+ # The outlet object contain all action methods and static device attributes
139
+ await outlet.update()
140
+ await outlet.turn_off()
141
+ outlet.display() # Print static device information, name, type, CID, etc.
142
+
143
+ # State of object held in `device.state` attribute
144
+ print(outlet.state)
145
+ state_json = outlet.dumps() # Returns JSON string of device state
146
+ state_bytes = orjson.dumps(outlet.state) # Returns bytes of device state
147
+
148
+ # to view the response information of the last API call
149
+ print(outlet.last_response)
150
+ # Prints a ResponseInfo object containing error code,
151
+ # and other response information
152
+
153
+
154
+ # Or use your own session
155
+ session = aiohttp.ClientSession()
156
+
157
+ async def main():
158
+ async with VeSync("user", "password", session=session):
159
+ await manager.login()
160
+ await manager.update()
161
+
162
+
163
+
164
+ if __name__ == "__main__":
165
+ asyncio.run(main())
166
+ ```
167
+
168
+ If using `async with` is not ideal, the `__aenter__()` and `__aexit__()` methods need to be called manually:
169
+
170
+ ```python
171
+ manager = VeSync(user, password)
172
+
173
+ await manager.__aenter__()
174
+
175
+ ...
176
+
177
+ await manager.__aexit__(None, None, None)
178
+ ```
179
+
180
+ 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:
181
+
182
+ ```text
183
+ 2025-02-16 14:41:07 - ERROR - asyncio - Unclosed client session
184
+ 2025-02-16 14:41:07 - ERROR - asyncio - Unclosed connector
185
+ ```
186
+
187
+ ### VeSync Class Signature
188
+
189
+ The VeSync signature is:
190
+
191
+ ```python
192
+ VeSync(
193
+ username: str,
194
+ password: str,
195
+ session: ClientSession | None = None,
196
+ time_zone: str = DEFAULT_TZ # America/New_York
197
+ )
198
+ ```
199
+
200
+ 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`.
201
+
202
+ ### Product Types
203
+
204
+ There is a new nomenclature for product types that defines the device class. The
205
+ `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:
206
+
207
+ 1. `outlet` - Outlet devices
208
+ 2. `switch` - Wall switches
209
+ 3. `fan` - Fans (not air purifiers or humidifiers)
210
+ 4. `purifier` - Air purifiers (not humidifiers)
211
+ 5. `humidifier` - Humidifiers (not air purifiers)
212
+ 6. `bulb` - Light bulbs (not dimmers or switches)
213
+ 7. `airfryer` - Air fryers
214
+
215
+ See [Supported Devices](#supported-devices) for a complete list of supported devices and models.
216
+
217
+ ### Custom Exceptions
218
+
219
+ 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.
220
+
221
+ 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.
222
+
223
+ 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.
224
+
225
+ Custom Exceptions raised by all API calls:
226
+
227
+ - `pyvesync.logs.VeSyncServerError` - The API connected and returned a code indicated there is a server-side error.
228
+ - `pyvesync.logs.VeSyncRateLimitError` - The API's rate limit has been exceeded.
229
+ - `pyvesync.logs.VeSyncAPIStatusCodeError` - The API returned a non-200 status code.
230
+ - `pyvesync.logs.VeSyncAPIResponseError` - The response from the API was not in an expected format.
231
+
232
+ Login API Exceptions
233
+
234
+ - `pyvesync.logs.VeSyncLoginError` - The username or password is incorrect.
235
+
236
+ See [errors](https://webdjoe.github.io/pyvesync/latest/development/utils/errors) documentation for a complete list of error codes and exceptions.
237
+
238
+ 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:
239
+
240
+ - `VeSyncServerError` - The API connected and returned a code indicated there is a server-side error.
241
+ - `VeSyncRateLimitError` - The API's rate limit has been exceeded.
242
+ - `VeSyncAPIStatusCodeError` - The API returned a non-200 status code.
243
+ - `VeSyncTokenError` - The API returned a token error and requires `login()` to be called again.
244
+ - `VeSyncLoginError` - The username or password is incorrect.
245
+
246
+ ## Installation
247
+
248
+ Install the latest version from pip:
249
+
250
+ ```bash
251
+ pip install pyvesync
252
+ ```
253
+
254
+ <!--SUPPORTED DEVICES START-->
255
+
256
+ ## Supported Devices
257
+
258
+ <!--SUPPORTED OUTLETS START-->
259
+
260
+ ### Etekcity Outlets
261
+
262
+ 1. Voltson Smart WiFi Outlet- Round (7A model ESW01-USA)
263
+ 2. Voltson Smart WiFi Outlet - Round (10A model ESW01-EU)
264
+ 3. Voltson Smart Wifi Outlet - Round (10A model ESW03-USA)
265
+ 4. Voltson Smart Wifi Outlet - Round (10A model ESW10-USA)
266
+ 5. Voltson Smart WiFi Outlet - Rectangle (15A model ESW15-USA)
267
+ 6. Two Plug Outdoor Outlet (ESO15-TB) (Each plug is a separate `VeSyncOutlet` object, energy readings are for both plugs combined)
268
+
269
+ <!--SUPPORTED OUTLETS END-->
270
+
271
+ <!--SUPPORTED SWITCHES START-->
272
+
273
+ ### Wall Switches
274
+
275
+ 1. Etekcity Smart WiFi Light Switch (model ESWL01)
276
+ 2. Etekcity Wifi Dimmer Switch (ESD16)
277
+
278
+ <!--SUPPORTED SWITCHES END-->
279
+
280
+ ### Levoit Air Purifiers
281
+
282
+ 1. LV-PUR131S
283
+ 2. Core 200S
284
+ 3. Core 300S
285
+ 4. Core 400S
286
+ 5. Core 600S
287
+ 6. Vital 100S
288
+ 7. Vital 200S
289
+ 8. Everest Air
290
+
291
+ ### Etekcity Bulbs
292
+
293
+ 1. Soft White Dimmable Smart Bulb (ESL100)
294
+ 2. Cool to Soft White Tunable Dimmable Bulb (ESL100CW)
295
+
296
+ ### Valceno Bulbs
297
+
298
+ 1. Valceno Multicolor Bulb (XYD0001)
299
+
300
+ ### Levoit Humidifiers
301
+
302
+ 1. Dual 200S
303
+ 2. Classic 300S
304
+ 3. LV600S
305
+ 4. OasisMist 450S
306
+ 5. OasisMist 600S
307
+ 6. OasisMist 1000S
308
+
309
+ ### Cosori Air Fryer
310
+
311
+ 1. Cosori 3.7 and 5.8 Quart Air Fryer
312
+
313
+ ### Fans
314
+
315
+ 1. 42 in. Tower Fan
316
+
317
+ <!--SUPPORTED DEVICES END-->
318
+
319
+ ## Usage
320
+
321
+ ```python
322
+ import asyncio
323
+ from pyvesync import VeSync
324
+ from pyvesync.logs import VeSyncLoginError
325
+
326
+ # VeSync is an asynchronous context manager
327
+ # VeSync(username, password, debug=False, redact=True, session=None)
328
+
329
+ async def main():
330
+ async with VeSync("user", "password") as manager:
331
+ await manager.login()
332
+ await manager.update()
333
+
334
+ # Acts as a set of device instances
335
+ device_container = manager.devices
336
+
337
+ outlets = device_container.outlets # List of outlet instances
338
+ outlet = outlets[0]
339
+ await outlet.update()
340
+ await outlet.turn_off()
341
+ outlet.display()
342
+
343
+ # Iterate of entire device list
344
+ for devices in device_container:
345
+ device.display()
346
+
347
+
348
+ if __name__ == "__main__":
349
+ asyncio.run(main())
350
+ ```
351
+
352
+ Devices are stored in the respective lists in the instantiated `VeSync` class:
353
+
354
+ ```python
355
+ await manager.login() # Asynchronous
356
+ await manager.update() # Asynchronous
357
+
358
+
359
+ # Acts as set with properties that return product type lists
360
+ manager.devices = DeviceContainer instance
361
+
362
+ manager.devices.outlets = [VeSyncOutletInstances]
363
+ manager.devices.switches = [VeSyncSwitchInstances]
364
+ manager.devices.fans = [VeSyncFanInstances]
365
+ manager.devices.bulbs = [VeSyncBulbInstances]
366
+ manager.devices.air_purifiers = [VeSyncPurifierInstances]
367
+ manager.devices.humidifiers = [VeSyncHumidifierInstances]
368
+ manager.devices.air_fryers = [VeSyncAirFryerInstances]
369
+ managers.devices.thermostats = [VeSyncThermostatInstances]
370
+
371
+ # Get device by device name
372
+ dev_name = "My Device"
373
+ for device in manager.devices:
374
+ if device.device_name == dev_name:
375
+ my_device = device
376
+ device.display()
377
+
378
+ # Turn on switch by switch name
379
+ switch_name = "My Switch"
380
+ for switch in manager.devices.switches:
381
+ if switch.device_name == switch_name:
382
+ await switch.turn_on() # Asynchronous
383
+ ```
384
+
385
+ See the [device documentation](https://webdjoe.github.io/pyvesync/latest/devices/) for more information on the device classes and their methods/states.
386
+
387
+ ## Debug mode and redact
388
+
389
+ 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.
390
+
391
+ 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`.
392
+
393
+ ```python
394
+ import asyncio
395
+ import aiohttp
396
+ from pyvesync.vesync import VeSync
397
+
398
+ async def main():
399
+ async with VeSync("user", "password") as manager:
400
+ manager.debug = True
401
+ manager.redact = True # True by default
402
+ await manager.login()
403
+ await manager.update()
404
+
405
+ outlet = manager.outlets[0]
406
+ await outlet.update()
407
+ await outlet.turn_off()
408
+ outlet.display()
409
+
410
+
411
+ if __name__ == "__main__":
412
+ asyncio.run(main())
413
+ ```
414
+
415
+ ## Feature Requests
416
+
417
+ 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.
418
+
419
+ ```bash
420
+ mkdir python_test && cd python_test
421
+
422
+ # Check Python version is 3.11 or higher
423
+ python3 --version # or python --version or python3.8 --version
424
+ # Create a new venv
425
+ python3 -m venv pyvesync-venv
426
+ # Activate the venv on linux
427
+ source pyvesync-venv/bin/activate
428
+ # or ....
429
+ pyvesync-venv\Scripts\activate.ps1 # on powershell
430
+ pyvesync-venv\Scripts\activate.bat # on command prompt
431
+
432
+ # Install branch to be tested into new virtual environment:
433
+ pip install git+https://github.com/webdjoe/pyvesync.git@BRANCHNAME
434
+
435
+ # Install a PR that has not been merged:
436
+ pip install git+https://github.com/webdjoe/pyvesync.git@refs/pull/PR_NUMBER/head
437
+ ```
438
+
439
+ Test functionality with a script, please adjust methods and logging statements to the device you are testing.
440
+
441
+ `test.py`
442
+
443
+ ```python
444
+ import asyncio
445
+ import sys
446
+ import logging
447
+ import json
448
+ from functool import chain
449
+ from pyvesync import VeSync
450
+
451
+ logger = logging.getLogger(__name__)
452
+ logger.setLevel(logging.DEBUG)
453
+
454
+ USERNAME = "YOUR USERNAME"
455
+ PASSWORD = "YOUR PASSWORD"
456
+
457
+ DEVICE_NAME = "Device" # Device to test
458
+
459
+ async def test_device():
460
+ # Instantiate VeSync class and login
461
+ async with VeSync(USERNAME, PASSWORD, debug=True, redact=True) as manager:
462
+ await manager.login()
463
+
464
+ # Pull and update devices
465
+ await manager.update()
466
+
467
+ for dev in manager.devices:
468
+ # Print all device info
469
+ logger.debug(dev.device_name + "\n")
470
+ logger.debug(dev.display())
471
+
472
+ # Find correct device
473
+ if dev.device_name.lower() != DEVICE_NAME.lower():
474
+ logger.debug("%s is not %s, continuing", self.device_name, DEVICE_NAME)
475
+ continue
476
+
477
+ logger.debug('--------------%s-----------------' % dev.device_name)
478
+ logger.debug(dev.display())
479
+ logger.debug(dev.displayJSON())
480
+ # Test all device methods and functionality
481
+ # Test Properties
482
+ logger.debug("Fan is on - %s", dev.is_on)
483
+ logger.debug("Modes - %s", dev.modes)
484
+ logger.debug("Fan Level - %s", dev.fan_level)
485
+ logger.debug("Fan Air Quality - %s", dev.air_quality)
486
+ logger.debug("Screen Status - %s", dev.screen_status)
487
+
488
+ logger.debug("Turning on")
489
+ await fan.turn_on()
490
+ logger.debug("Device is on %s", dev.is_on)
491
+
492
+ logger.debug("Turning off")
493
+ await fan.turn_off()
494
+ logger.debug("Device is on %s", dev.is_on)
495
+
496
+ logger.debug("Sleep mode")
497
+ fan.sleep_mode()
498
+ logger.debug("Current mode - %s", dev.details['mode'])
499
+
500
+ fan.auto_mode()
501
+
502
+ logger.debug("Set Fan Speed - %s", dev.set_fan_speed)
503
+ logger.debug("Current Fan Level - %s", dev.fan_level)
504
+ logger.debug("Current mode - %s", dev.mode)
505
+
506
+ # Display all device info
507
+ logger.debug(dev.display(state=True))
508
+ logger.debug(dev.to_json(state=True, indent=True))
509
+ dev_dict = dev.to_dict(state=True)
510
+
511
+ if __name__ == "__main__":
512
+ logger.debug("Testing device")
513
+ asyncio.run(test_device())
514
+ ...
515
+
516
+ ```
517
+
518
+ ## Device Requests
519
+
520
+ 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.
521
+
522
+ 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.
523
+
524
+ For example:
525
+
526
+ Before:
527
+
528
+ ```json
529
+ {
530
+ "tk": "abc123abc123==3rf",
531
+ "accountId": "123456789",
532
+ "cid": "abcdef12-3gh-ij"
533
+ }
534
+ ```
535
+
536
+ After:
537
+
538
+ ```json
539
+ {
540
+ "tk": "AAA111AAA111==1AA",
541
+ "accountId": "111111111",
542
+ "cid": "AAAAAA11-1AA-AA"
543
+ }
544
+ ```
545
+
546
+ ## Contributing
547
+
548
+ All [contributions](CONTRIBUTING.md) are welcome.
549
+
550
+ This project is licensed under [MIT](LICENSE).
551
+
552
+ ## Contributors
553
+
554
+ This is an open source project and cannot exist without the contributions of its community. Thank you to all the contributors who have helped make this project better!
555
+
556
+ A special thanks for helping with V3 go live:
557
+
558
+ | [<img src="https://github.com/cdninja.png" width="60px;"/><br /><sub><a href="https://github.com/cdninja">cdninja</a></sub>](https://github.com/cdninja) | [<img src="https://github.com/sapuseven.png" width="60px;"/><br /><sub><a href="https://github.com/sapuseven">sapuseven</a></sub>](https://github.com/sapuseven) | [<img src="https://github.com/sdrapha.png" width="60px;"/><br /><sub><a href="https://github.com/sdrapha">sdrapha</a></sub>](https://github.com/sdrapha) |
559
+ | :------- | :------- | :------- |
560
+
561
+ And to all of those that contributed to the project:
562
+
563
+ <a href="https://github.com/webdjoe/pyvesync/graphs/contributors">
564
+ <img src="https://contrib.rocks/image?repo=webdjoe/pyvesync" />
565
+ </a>
566
+
567
+ Made with [contrib.rocks](https://contrib.rocks).