pyvesync 3.4.1__tar.gz → 3.5.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.
- {pyvesync-3.4.1/src/pyvesync.egg-info → pyvesync-3.5.0}/PKG-INFO +165 -153
- {pyvesync-3.4.1 → pyvesync-3.5.0}/README.md +161 -149
- {pyvesync-3.4.1 → pyvesync-3.5.0}/pyproject.toml +4 -4
- pyvesync-3.5.0/requirements.txt +2 -0
- {pyvesync-3.4.1 → pyvesync-3.5.0}/src/pyvesync/auth.py +43 -28
- pyvesync-3.5.0/src/pyvesync/base_devices/fryer_base.py +1337 -0
- {pyvesync-3.4.1 → pyvesync-3.5.0}/src/pyvesync/base_devices/humidifier_base.py +50 -0
- {pyvesync-3.4.1 → pyvesync-3.5.0}/src/pyvesync/base_devices/outlet_base.py +18 -0
- {pyvesync-3.4.1 → pyvesync-3.5.0}/src/pyvesync/const.py +527 -9
- {pyvesync-3.4.1 → pyvesync-3.5.0}/src/pyvesync/device_container.py +11 -2
- {pyvesync-3.4.1 → pyvesync-3.5.0}/src/pyvesync/device_map.py +766 -24
- {pyvesync-3.4.1 → pyvesync-3.5.0}/src/pyvesync/devices/vesynchumidifier.py +192 -3
- pyvesync-3.5.0/src/pyvesync/devices/vesynckitchen.py +1089 -0
- {pyvesync-3.4.1 → pyvesync-3.5.0}/src/pyvesync/devices/vesyncoutlet.py +10 -0
- {pyvesync-3.4.1 → pyvesync-3.5.0}/src/pyvesync/devices/vesyncthermostat.py +2 -2
- {pyvesync-3.4.1 → pyvesync-3.5.0}/src/pyvesync/models/bypass_models.py +2 -1
- pyvesync-3.5.0/src/pyvesync/models/fryer_models.py +312 -0
- {pyvesync-3.4.1 → pyvesync-3.5.0}/src/pyvesync/models/humidifier_models.py +15 -0
- {pyvesync-3.4.1 → pyvesync-3.5.0}/src/pyvesync/utils/colors.py +127 -0
- {pyvesync-3.4.1 → pyvesync-3.5.0}/src/pyvesync/utils/errors.py +47 -23
- {pyvesync-3.4.1 → pyvesync-3.5.0}/src/pyvesync/utils/helpers.py +10 -0
- {pyvesync-3.4.1 → pyvesync-3.5.0}/src/pyvesync/vesync.py +32 -7
- {pyvesync-3.4.1 → pyvesync-3.5.0/src/pyvesync.egg-info}/PKG-INFO +165 -153
- {pyvesync-3.4.1 → pyvesync-3.5.0}/src/pyvesync.egg-info/requires.txt +3 -3
- pyvesync-3.4.1/requirements.txt +0 -3
- pyvesync-3.4.1/src/pyvesync/base_devices/fryer_base.py +0 -68
- pyvesync-3.4.1/src/pyvesync/devices/vesynckitchen.py +0 -653
- pyvesync-3.4.1/src/pyvesync/models/fryer_models.py +0 -34
- {pyvesync-3.4.1 → pyvesync-3.5.0}/LICENSE +0 -0
- {pyvesync-3.4.1 → pyvesync-3.5.0}/MANIFEST.in +0 -0
- {pyvesync-3.4.1 → pyvesync-3.5.0}/setup.cfg +0 -0
- {pyvesync-3.4.1 → pyvesync-3.5.0}/setup.py +0 -0
- {pyvesync-3.4.1 → pyvesync-3.5.0}/src/pyvesync/__init__.py +0 -0
- {pyvesync-3.4.1 → pyvesync-3.5.0}/src/pyvesync/base_devices/__init__.py +0 -0
- {pyvesync-3.4.1 → pyvesync-3.5.0}/src/pyvesync/base_devices/bulb_base.py +0 -0
- {pyvesync-3.4.1 → pyvesync-3.5.0}/src/pyvesync/base_devices/fan_base.py +0 -0
- {pyvesync-3.4.1 → pyvesync-3.5.0}/src/pyvesync/base_devices/purifier_base.py +0 -0
- {pyvesync-3.4.1 → pyvesync-3.5.0}/src/pyvesync/base_devices/switch_base.py +0 -0
- {pyvesync-3.4.1 → pyvesync-3.5.0}/src/pyvesync/base_devices/thermostat_base.py +0 -0
- {pyvesync-3.4.1 → pyvesync-3.5.0}/src/pyvesync/base_devices/vesyncbasedevice.py +0 -0
- {pyvesync-3.4.1 → pyvesync-3.5.0}/src/pyvesync/devices/__init__.py +0 -0
- {pyvesync-3.4.1 → pyvesync-3.5.0}/src/pyvesync/devices/vesyncbulb.py +0 -0
- {pyvesync-3.4.1 → pyvesync-3.5.0}/src/pyvesync/devices/vesyncfan.py +0 -0
- {pyvesync-3.4.1 → pyvesync-3.5.0}/src/pyvesync/devices/vesyncpurifier.py +0 -0
- {pyvesync-3.4.1 → pyvesync-3.5.0}/src/pyvesync/devices/vesyncswitch.py +0 -0
- {pyvesync-3.4.1 → pyvesync-3.5.0}/src/pyvesync/models/__init__.py +0 -0
- {pyvesync-3.4.1 → pyvesync-3.5.0}/src/pyvesync/models/base_models.py +0 -0
- {pyvesync-3.4.1 → pyvesync-3.5.0}/src/pyvesync/models/bulb_models.py +0 -0
- {pyvesync-3.4.1 → pyvesync-3.5.0}/src/pyvesync/models/fan_models.py +0 -0
- {pyvesync-3.4.1 → pyvesync-3.5.0}/src/pyvesync/models/home_models.py +0 -0
- {pyvesync-3.4.1 → pyvesync-3.5.0}/src/pyvesync/models/outlet_models.py +0 -0
- {pyvesync-3.4.1 → pyvesync-3.5.0}/src/pyvesync/models/purifier_models.py +0 -0
- {pyvesync-3.4.1 → pyvesync-3.5.0}/src/pyvesync/models/switch_models.py +0 -0
- {pyvesync-3.4.1 → pyvesync-3.5.0}/src/pyvesync/models/thermostat_models.py +0 -0
- {pyvesync-3.4.1 → pyvesync-3.5.0}/src/pyvesync/models/vesync_models.py +0 -0
- {pyvesync-3.4.1 → pyvesync-3.5.0}/src/pyvesync/py.typed +0 -0
- {pyvesync-3.4.1 → pyvesync-3.5.0}/src/pyvesync/utils/__init__.py +0 -0
- {pyvesync-3.4.1 → pyvesync-3.5.0}/src/pyvesync/utils/device_mixins.py +0 -0
- {pyvesync-3.4.1 → pyvesync-3.5.0}/src/pyvesync/utils/enum_utils.py +0 -0
- {pyvesync-3.4.1 → pyvesync-3.5.0}/src/pyvesync/utils/logs.py +0 -0
- {pyvesync-3.4.1 → pyvesync-3.5.0}/src/pyvesync/vesynchome.py +0 -0
- {pyvesync-3.4.1 → pyvesync-3.5.0}/src/pyvesync.egg-info/SOURCES.txt +0 -0
- {pyvesync-3.4.1 → pyvesync-3.5.0}/src/pyvesync.egg-info/dependency_links.txt +0 -0
- {pyvesync-3.4.1 → pyvesync-3.5.0}/src/pyvesync.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pyvesync
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.5.0
|
|
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
|
Author: Mark Perdue
|
|
6
6
|
Author-email: Joe Trabulsy <webdjoe@gmail.com>
|
|
@@ -15,7 +15,7 @@ Classifier: Topic :: Home Automation
|
|
|
15
15
|
Requires-Python: >=3.11
|
|
16
16
|
Description-Content-Type: text/markdown
|
|
17
17
|
License-File: LICENSE
|
|
18
|
-
Requires-Dist: aiohttp>=3.
|
|
18
|
+
Requires-Dist: aiohttp>=3.14.4
|
|
19
19
|
Requires-Dist: mashumaro[orjson]>=3.13.1
|
|
20
20
|
Provides-Extra: dev
|
|
21
21
|
Requires-Dist: pytest; extra == "dev"
|
|
@@ -23,7 +23,7 @@ Requires-Dist: pytest-cov; extra == "dev"
|
|
|
23
23
|
Requires-Dist: pyyaml; extra == "dev"
|
|
24
24
|
Requires-Dist: pylint; extra == "dev"
|
|
25
25
|
Requires-Dist: mypy; extra == "dev"
|
|
26
|
-
Requires-Dist: ruff; extra == "dev"
|
|
26
|
+
Requires-Dist: ruff==0.15.21; extra == "dev"
|
|
27
27
|
Requires-Dist: pre-commit; extra == "dev"
|
|
28
28
|
Requires-Dist: tox; extra == "dev"
|
|
29
29
|
Requires-Dist: requests; extra == "dev"
|
|
@@ -31,7 +31,7 @@ Requires-Dist: aiofiles; extra == "dev"
|
|
|
31
31
|
Provides-Extra: docs
|
|
32
32
|
Requires-Dist: mkdocstrings-python; extra == "docs"
|
|
33
33
|
Requires-Dist: mkdocs; extra == "docs"
|
|
34
|
-
Requires-Dist: ruff; extra == "docs"
|
|
34
|
+
Requires-Dist: ruff==0.15.21; extra == "docs"
|
|
35
35
|
Requires-Dist: Pygments; extra == "docs"
|
|
36
36
|
Requires-Dist: mkdocs-material; extra == "docs"
|
|
37
37
|
Requires-Dist: mkdocstrings; extra == "docs"
|
|
@@ -46,13 +46,13 @@ Requires-Dist: griffe-inherited-docstrings; extra == "docs"
|
|
|
46
46
|
Requires-Dist: griffe_warnings_deprecated; extra == "docs"
|
|
47
47
|
Dynamic: license-file
|
|
48
48
|
|
|
49
|
-
# pyvesync [](https://pypi.python.org/pypi/pyvesync) [](https://pypi.python.org/pypi/pyvesync) [](https://github.com/Naereen/badges/) [](https://pypi.python.org/pypi/ansicolortags/) <!-- omit in toc -->
|
|
50
50
|
|
|
51
51
|
pyvesync is a library to manage VeSync compatible [smart home devices](#supported-devices)
|
|
52
52
|
|
|
53
53
|
<a href="https://webdjoe.github.io/pyvesync/latest"><img src="./docs/assets/docs.svg"></a>
|
|
54
54
|
|
|
55
|
-
**Check out the new [pyvesync documentation](https://webdjoe.github.io/pyvesync/) for usage and full API details.**
|
|
55
|
+
**Check out the new [pyvesync documentation](https://webdjoe.github.io/pyvesync/latest/) for usage and full API details.**
|
|
56
56
|
|
|
57
57
|
## Supported Product Types
|
|
58
58
|
|
|
@@ -67,7 +67,7 @@ pyvesync is a library to manage VeSync compatible [smart home devices](#supporte
|
|
|
67
67
|
|
|
68
68
|
See the [supported devices](https://webdjoe.github.io/pyvesync/latest/supported_devices/) page for a complete list of supported devices and device types.
|
|
69
69
|
|
|
70
|
-
##
|
|
70
|
+
## pyvesync v3 Features
|
|
71
71
|
|
|
72
72
|
**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.
|
|
73
73
|
|
|
@@ -83,7 +83,6 @@ Some of the changes are:
|
|
|
83
83
|
- Implemented custom exceptions and error (code) handling for API responses.
|
|
84
84
|
- `const` module to hold all library constants
|
|
85
85
|
- Built the `DeviceMap` class to hold the mapping and features of devices.
|
|
86
|
-
- COMING SOON: Use API to pull device modes and operating features.
|
|
87
86
|
|
|
88
87
|
See [pyvesync V3](https://webdjoe.github.io/pyvesync/latest/pyvesync3/) for more information on the changes.
|
|
89
88
|
|
|
@@ -94,7 +93,7 @@ Library is now asynchronous, using aiohttp as a replacement for requests. The `p
|
|
|
94
93
|
```python
|
|
95
94
|
import asyncio
|
|
96
95
|
import aiohttp
|
|
97
|
-
from pyvesync
|
|
96
|
+
from pyvesync import VeSync
|
|
98
97
|
|
|
99
98
|
async def main():
|
|
100
99
|
async with VeSync(
|
|
@@ -109,9 +108,6 @@ async def main():
|
|
|
109
108
|
# Redact mode is enabled by default, set to False to disable
|
|
110
109
|
manager.redact = False
|
|
111
110
|
|
|
112
|
-
# To print request & response content for all API calls enable verbose mode
|
|
113
|
-
manager.verbose = True
|
|
114
|
-
|
|
115
111
|
# To print logs to file
|
|
116
112
|
manager.log_to_file("pyvesync.log")
|
|
117
113
|
|
|
@@ -119,17 +115,17 @@ async def main():
|
|
|
119
115
|
if not manager.enabled:
|
|
120
116
|
print("Not logged in.")
|
|
121
117
|
return
|
|
122
|
-
await manager.get_devices() # Instantiates supported devices in device list
|
|
118
|
+
await manager.get_devices() # Instantiates supported devices in device list
|
|
123
119
|
await manager.update() # Updates the state of all devices
|
|
124
120
|
|
|
125
121
|
# manager.devices is a DeviceContainer object
|
|
126
122
|
# manager.devices.outlets is a list of VeSyncOutlet objects
|
|
127
123
|
# manager.devices.switches is a list of VeSyncSwitch objects
|
|
128
|
-
# manager.devices.fans is a list of
|
|
124
|
+
# manager.devices.fans is a list of VeSyncFanBase objects
|
|
129
125
|
# manager.devices.bulbs is a list of VeSyncBulb objects
|
|
130
|
-
# manager.devices.humidifiers is a list of
|
|
131
|
-
# manager.devices.air_purifiers is a list of
|
|
132
|
-
# manager.devices.air_fryers is a list of
|
|
126
|
+
# manager.devices.humidifiers is a list of VeSyncHumidifier objects
|
|
127
|
+
# manager.devices.air_purifiers is a list of VeSyncPurifier objects
|
|
128
|
+
# manager.devices.air_fryers is a list of VeSyncFryer objects
|
|
133
129
|
# manager.devices.thermostats is a list of VeSyncThermostat objects
|
|
134
130
|
|
|
135
131
|
for outlet in manager.devices.outlets:
|
|
@@ -140,8 +136,8 @@ async def main():
|
|
|
140
136
|
|
|
141
137
|
# State of object held in `device.state` attribute
|
|
142
138
|
print(outlet.state)
|
|
143
|
-
state_json = outlet.
|
|
144
|
-
state_bytes =
|
|
139
|
+
state_json = outlet.to_json(state=True) # Returns JSON string of device state
|
|
140
|
+
state_bytes = outlet.to_jsonb(state=True) # Returns JSON bytes of device state
|
|
145
141
|
|
|
146
142
|
# to view the response information of the last API call
|
|
147
143
|
print(outlet.last_response)
|
|
@@ -153,7 +149,7 @@ async def main():
|
|
|
153
149
|
session = aiohttp.ClientSession()
|
|
154
150
|
|
|
155
151
|
async def main():
|
|
156
|
-
async with VeSync("user", "password", session=session):
|
|
152
|
+
async with VeSync("user", "password", session=session) as manager:
|
|
157
153
|
await manager.login()
|
|
158
154
|
await manager.update()
|
|
159
155
|
|
|
@@ -166,7 +162,7 @@ if __name__ == "__main__":
|
|
|
166
162
|
If using `async with` is not ideal, the `__aenter__()` and `__aexit__()` methods need to be called manually:
|
|
167
163
|
|
|
168
164
|
```python
|
|
169
|
-
manager = VeSync(user, password)
|
|
165
|
+
manager = VeSync("user", "password")
|
|
170
166
|
|
|
171
167
|
await manager.__aenter__()
|
|
172
168
|
|
|
@@ -175,7 +171,7 @@ await manager.__aenter__()
|
|
|
175
171
|
await manager.__aexit__(None, None, None)
|
|
176
172
|
```
|
|
177
173
|
|
|
178
|
-
|
|
174
|
+
pyvesync 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:
|
|
179
175
|
|
|
180
176
|
```text
|
|
181
177
|
2025-02-16 14:41:07 - ERROR - asyncio - Unclosed client session
|
|
@@ -190,9 +186,9 @@ The VeSync signature is:
|
|
|
190
186
|
VeSync(
|
|
191
187
|
username: str,
|
|
192
188
|
password: str,
|
|
193
|
-
country_code: str =
|
|
189
|
+
country_code: str = DEFAULT_REGION, # "US"
|
|
194
190
|
session: ClientSession | None = None,
|
|
195
|
-
time_zone: str = DEFAULT_TZ # America/New_York
|
|
191
|
+
time_zone: str = DEFAULT_TZ, # "America/New_York"
|
|
196
192
|
redact: bool = True,
|
|
197
193
|
)
|
|
198
194
|
```
|
|
@@ -208,7 +204,8 @@ There is a new nomenclature for product types that defines the device class. The
|
|
|
208
204
|
4. `purifier` - Air purifiers (not humidifiers)
|
|
209
205
|
5. `humidifier` - Humidifiers (not air purifiers)
|
|
210
206
|
6. `bulb` - Light bulbs (not dimmers or switches)
|
|
211
|
-
7. `
|
|
207
|
+
7. `air fryer` - Air fryers
|
|
208
|
+
8. `thermostat` - Thermostats
|
|
212
209
|
|
|
213
210
|
See [Supported Devices](#supported-devices) for a complete list of supported devices and models.
|
|
214
211
|
|
|
@@ -218,22 +215,22 @@ Exceptions are no longer caught by the library and must be handled by the user.
|
|
|
218
215
|
|
|
219
216
|
Errors that occur at the aiohttp level are raised automatically and propagated to the user. That means exceptions raised by aiohttp that inherit from `aiohttp.ClientError` are propagated.
|
|
220
217
|
|
|
221
|
-
When the connection to the VeSync API succeeds but returns an error code that prevents the library from functioning a custom exception inherited from `pyvesync.
|
|
218
|
+
When the connection to the VeSync API succeeds but returns an error code that prevents the library from functioning a custom exception inherited from `pyvesync.utils.errors.VeSyncError` is raised.
|
|
222
219
|
|
|
223
220
|
Custom Exceptions raised by all API calls:
|
|
224
221
|
|
|
225
|
-
- `pyvesync.
|
|
226
|
-
- `pyvesync.
|
|
227
|
-
- `pyvesync.
|
|
228
|
-
- `pyvesync.
|
|
222
|
+
- `pyvesync.utils.errors.VeSyncServerError` - The API connected and returned a code indicated there is a server-side error.
|
|
223
|
+
- `pyvesync.utils.errors.VeSyncRateLimitError` - The API's rate limit has been exceeded.
|
|
224
|
+
- `pyvesync.utils.errors.VeSyncAPIStatusCodeError` - The API returned a non-200 status code.
|
|
225
|
+
- `pyvesync.utils.errors.VeSyncAPIResponseError` - The response from the API was not in an expected format.
|
|
229
226
|
|
|
230
227
|
Login API Exceptions
|
|
231
228
|
|
|
232
|
-
- `pyvesync.
|
|
229
|
+
- `pyvesync.utils.errors.VeSyncLoginError` - The username or password is incorrect.
|
|
233
230
|
|
|
234
|
-
See [errors](https://webdjoe.github.io/pyvesync/latest/development/utils/errors) documentation for a complete list of error codes and exceptions.
|
|
231
|
+
See the [errors](https://webdjoe.github.io/pyvesync/latest/development/utils/errors/) documentation for a complete list of error codes and exceptions.
|
|
235
232
|
|
|
236
|
-
The
|
|
233
|
+
The `raise_api_errors()` function is called for every API call and checks for general response errors. It can raise the following exceptions:
|
|
237
234
|
|
|
238
235
|
- `VeSyncServerError` - The API connected and returned a code indicated there is a server-side error.
|
|
239
236
|
- `VeSyncRateLimitError` - The API's rate limit has been exceeded.
|
|
@@ -257,12 +254,15 @@ pip install pyvesync
|
|
|
257
254
|
|
|
258
255
|
### Etekcity Outlets
|
|
259
256
|
|
|
260
|
-
1. Voltson Smart WiFi Outlet- Round (7A model ESW01-USA)
|
|
257
|
+
1. Voltson Smart WiFi Outlet - Round (7A model ESW01-USA)
|
|
261
258
|
2. Voltson Smart WiFi Outlet - Round (10A model ESW01-EU)
|
|
262
|
-
3. Voltson Smart
|
|
263
|
-
4. Voltson Smart
|
|
259
|
+
3. Voltson Smart WiFi Outlet - Round (10A model ESW03-USA)
|
|
260
|
+
4. Voltson Smart WiFi Outlet - Round (10A model ESW10-USA)
|
|
264
261
|
5. Voltson Smart WiFi Outlet - Rectangle (15A model ESW15-USA)
|
|
265
262
|
6. Two Plug Outdoor Outlet (ESO15-TB) (Each plug is a separate `VeSyncOutlet` object, energy readings are for both plugs combined)
|
|
263
|
+
7. BSDOG / Greensun Smart Outlet Series (BSDOG01, BSDOG02, WYSMTOD16A, WM-PLUG and more)
|
|
264
|
+
8. WHOGPLUG / Greensun Smart Plug
|
|
265
|
+
9. WYLDR Smart Plug (WYLDR16A1081) (Supports energy monitoring but not energy history)
|
|
266
266
|
|
|
267
267
|
<!--SUPPORTED OUTLETS END-->
|
|
268
268
|
|
|
@@ -271,46 +271,58 @@ pip install pyvesync
|
|
|
271
271
|
### Wall Switches
|
|
272
272
|
|
|
273
273
|
1. Etekcity Smart WiFi Light Switch (model ESWL01)
|
|
274
|
-
2. Etekcity
|
|
274
|
+
2. Etekcity WiFi 3-Way Switch (model ESWL03)
|
|
275
|
+
3. Etekcity WiFi Dimmer Switch (model ESWD16)
|
|
275
276
|
|
|
276
277
|
<!--SUPPORTED SWITCHES END-->
|
|
277
278
|
|
|
278
279
|
### Levoit Air Purifiers
|
|
279
280
|
|
|
280
281
|
1. LV-PUR131S
|
|
281
|
-
2.
|
|
282
|
-
3. Core
|
|
283
|
-
4. Core
|
|
284
|
-
5. Core
|
|
285
|
-
6.
|
|
286
|
-
7. Vital 200S
|
|
282
|
+
2. LV-RH131S
|
|
283
|
+
3. Core 200S
|
|
284
|
+
4. Core 300S
|
|
285
|
+
5. Core 400S
|
|
286
|
+
6. Core 600S
|
|
287
|
+
7. Vital 100S / 200S
|
|
287
288
|
8. Everest Air
|
|
289
|
+
9. Sprout Air Purifier
|
|
288
290
|
|
|
289
|
-
### Etekcity Bulbs
|
|
291
|
+
### Etekcity / Valceno Bulbs
|
|
290
292
|
|
|
291
293
|
1. Soft White Dimmable Smart Bulb (ESL100)
|
|
292
294
|
2. Cool to Soft White Tunable Dimmable Bulb (ESL100CW)
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
1. Valceno Multicolor Bulb (XYD0001)
|
|
295
|
+
3. Multicolor Dimmable Bulb (ESL100MC)
|
|
296
|
+
4. Valceno Multicolor Bulb (XYD0001)
|
|
297
297
|
|
|
298
298
|
### Levoit Humidifiers
|
|
299
299
|
|
|
300
|
-
1.
|
|
301
|
-
2.
|
|
302
|
-
3.
|
|
303
|
-
4.
|
|
304
|
-
5. OasisMist 600S
|
|
300
|
+
1. Classic 200S
|
|
301
|
+
2. Dual 200S
|
|
302
|
+
3. Classic 300S
|
|
303
|
+
4. LV600S / LV603S
|
|
304
|
+
5. OasisMist 450S / 600S
|
|
305
305
|
6. OasisMist 1000S
|
|
306
|
+
7. Superior 6000S
|
|
307
|
+
8. Sprout Humidifier
|
|
308
|
+
|
|
309
|
+
### Levoit Fans
|
|
306
310
|
|
|
307
|
-
|
|
311
|
+
1. 42 in. Tower Fan (LTF-F422S Series)
|
|
312
|
+
2. Pedestal Fan (LPF-R432S Series)
|
|
308
313
|
|
|
309
|
-
|
|
314
|
+
### Cosori Air Fryers
|
|
310
315
|
|
|
311
|
-
|
|
316
|
+
1. Cosori 3.7 Quart Air Fryer (CS137-AF)
|
|
317
|
+
2. Cosori 5.8 Quart Air Fryer (CS158-AF)
|
|
318
|
+
3. Cosori TurboBlaze 6 Quart Air Fryer (CAF-DC601S)
|
|
319
|
+
4. Cosori Dual Blaze 6.8 Quart Air Fryer (CAF-P583S)
|
|
320
|
+
5. Cosori Dual Air Fryer (CAF-TF101S)
|
|
321
|
+
6. Cosori Turbo Tower Pro Smart Air Fryer (CAF-DC111S-AEU)
|
|
312
322
|
|
|
313
|
-
|
|
323
|
+
### Thermostats
|
|
324
|
+
|
|
325
|
+
1. Aura Thermostat (LTM-A401S-WUS)
|
|
314
326
|
|
|
315
327
|
<!--SUPPORTED DEVICES END-->
|
|
316
328
|
|
|
@@ -319,10 +331,6 @@ pip install pyvesync
|
|
|
319
331
|
```python
|
|
320
332
|
import asyncio
|
|
321
333
|
from pyvesync import VeSync
|
|
322
|
-
from pyvesync.logs import VeSyncLoginError
|
|
323
|
-
|
|
324
|
-
# VeSync is an asynchronous context manager
|
|
325
|
-
# VeSync(username, password, redact=True, session=None)
|
|
326
334
|
|
|
327
335
|
async def main():
|
|
328
336
|
async with VeSync("user", "password") as manager:
|
|
@@ -332,14 +340,14 @@ async def main():
|
|
|
332
340
|
# Acts as a set of device instances
|
|
333
341
|
device_container = manager.devices
|
|
334
342
|
|
|
335
|
-
outlets = device_container.outlets
|
|
343
|
+
outlets = device_container.outlets # List of outlet instances
|
|
336
344
|
outlet = outlets[0]
|
|
337
345
|
await outlet.update()
|
|
338
346
|
await outlet.turn_off()
|
|
339
347
|
outlet.display()
|
|
340
348
|
|
|
341
|
-
# Iterate
|
|
342
|
-
for
|
|
349
|
+
# Iterate over entire device list
|
|
350
|
+
for device in device_container:
|
|
343
351
|
device.display()
|
|
344
352
|
|
|
345
353
|
|
|
@@ -347,52 +355,54 @@ if __name__ == "__main__":
|
|
|
347
355
|
asyncio.run(main())
|
|
348
356
|
```
|
|
349
357
|
|
|
350
|
-
If you want to reuse your token and account_id between runs. The `VeSync.auth` object holds the credentials and helper methods to save and load credentials.
|
|
358
|
+
If you want to reuse your token and account_id between runs. The `VeSync.auth` object holds the credentials and helper methods to save and load credentials. See the [Authentication Documentation](https://webdjoe.github.io/pyvesync/latest/authentication) for more details.
|
|
351
359
|
|
|
352
360
|
```python
|
|
353
361
|
import asyncio
|
|
354
362
|
from pyvesync import VeSync
|
|
355
|
-
from pyvesync.logs import VeSyncLoginError
|
|
356
|
-
|
|
357
|
-
# VeSync is an asynchronous context manager
|
|
358
|
-
# VeSync(username, password, redact=True, session=None)
|
|
359
363
|
|
|
360
364
|
async def main():
|
|
361
365
|
async with VeSync("user", "password") as manager:
|
|
362
366
|
|
|
363
367
|
# If credentials are stored in a file, it can be loaded
|
|
364
|
-
#
|
|
368
|
+
# with no path, .vesync_auth is read from the current directory,
|
|
369
|
+
# then from the home directory
|
|
365
370
|
await manager.load_credentials_from_file()
|
|
366
371
|
# or the file path can be passed
|
|
367
372
|
await manager.load_credentials_from_file("/path/to/token_file")
|
|
368
373
|
|
|
369
374
|
# Or credentials can be passed directly
|
|
370
|
-
manager.set_credentials(
|
|
371
|
-
|
|
372
|
-
|
|
375
|
+
manager.set_credentials(
|
|
376
|
+
token="your_token",
|
|
377
|
+
account_id="your_account_id",
|
|
378
|
+
country_code="US",
|
|
379
|
+
region="US",
|
|
380
|
+
)
|
|
381
|
+
|
|
382
|
+
# No login needed if token is valid
|
|
373
383
|
# await manager.login()
|
|
374
384
|
|
|
375
385
|
# To store credentials to a file after login
|
|
376
|
-
await manager.save_credentials() # Saves to default location ~/.vesync_token
|
|
377
|
-
# or pass a file path
|
|
378
386
|
await manager.save_credentials("/path/to/token_file")
|
|
379
387
|
|
|
380
|
-
# Output
|
|
381
|
-
|
|
388
|
+
# Output credentials as JSON string
|
|
389
|
+
print(manager.output_credentials_json())
|
|
390
|
+
# Output credentials as dictionary
|
|
391
|
+
print(manager.output_credentials_dict())
|
|
382
392
|
|
|
383
393
|
await manager.update()
|
|
384
394
|
|
|
385
395
|
# Acts as a set of device instances
|
|
386
396
|
device_container = manager.devices
|
|
387
397
|
|
|
388
|
-
outlets = device_container.outlets
|
|
398
|
+
outlets = device_container.outlets # List of outlet instances
|
|
389
399
|
outlet = outlets[0]
|
|
390
400
|
await outlet.update()
|
|
391
401
|
await outlet.turn_off()
|
|
392
402
|
outlet.display()
|
|
393
403
|
|
|
394
|
-
# Iterate
|
|
395
|
-
for
|
|
404
|
+
# Iterate over entire device list
|
|
405
|
+
for device in device_container:
|
|
396
406
|
device.display()
|
|
397
407
|
|
|
398
408
|
|
|
@@ -417,7 +427,7 @@ manager.devices.bulbs = [VeSyncBulbInstances]
|
|
|
417
427
|
manager.devices.air_purifiers = [VeSyncPurifierInstances]
|
|
418
428
|
manager.devices.humidifiers = [VeSyncHumidifierInstances]
|
|
419
429
|
manager.devices.air_fryers = [VeSyncAirFryerInstances]
|
|
420
|
-
|
|
430
|
+
manager.devices.thermostats = [VeSyncThermostatInstances]
|
|
421
431
|
|
|
422
432
|
# Get device by device name
|
|
423
433
|
dev_name = "My Device"
|
|
@@ -437,30 +447,35 @@ See the [device documentation](https://webdjoe.github.io/pyvesync/latest/devices
|
|
|
437
447
|
|
|
438
448
|
## Debug mode and redact
|
|
439
449
|
|
|
440
|
-
|
|
450
|
+
There are three independent knobs that control logging output:
|
|
441
451
|
|
|
442
|
-
|
|
452
|
+
- **Debug logging** - set the `pyvesync` logger level to `DEBUG`. The read-only `manager.debug` property reflects whether debug logging is currently active.
|
|
453
|
+
- **`redact`** - the `redact` argument (default `True`) replaces tokens, account IDs, and other sensitive values (`token`, `password`, `email`, `accountId`, `authKey`, `uuid`, `cid`, `authorizeCode`) with `##_REDACTED_##` so logs can be shared safely. It only has an effect while debug logging is enabled.
|
|
454
|
+
- **`LibraryLogger.verbose`** - set this class attribute to `True` to additionally print the full request & response body of every API call.
|
|
443
455
|
|
|
444
|
-
|
|
456
|
+
### Debug to the console
|
|
457
|
+
|
|
458
|
+
Setting the `pyvesync` logger to `DEBUG` sends debug output to wherever your logging is configured (the console by default). `redact` stays on so tokens are not exposed:
|
|
445
459
|
|
|
446
460
|
```python
|
|
447
461
|
import logging
|
|
448
462
|
import asyncio
|
|
449
|
-
import
|
|
450
|
-
from pyvesync.
|
|
463
|
+
from pyvesync import VeSync
|
|
464
|
+
from pyvesync.utils.logs import LibraryLogger
|
|
451
465
|
|
|
452
466
|
logger = logging.getLogger("pyvesync")
|
|
453
467
|
logger.setLevel(logging.DEBUG)
|
|
468
|
+
logging.basicConfig() # Send log records to the console
|
|
454
469
|
|
|
455
470
|
|
|
456
471
|
async def main():
|
|
457
472
|
async with VeSync("user", "password") as manager:
|
|
458
|
-
manager.debug = True
|
|
459
473
|
manager.redact = True # True by default
|
|
474
|
+
LibraryLogger.verbose = True # Optional - print full request/response content
|
|
460
475
|
await manager.login()
|
|
461
476
|
await manager.update()
|
|
462
477
|
|
|
463
|
-
outlet = manager.outlets[0]
|
|
478
|
+
outlet = manager.devices.outlets[0]
|
|
464
479
|
await outlet.update()
|
|
465
480
|
await outlet.turn_off()
|
|
466
481
|
outlet.display()
|
|
@@ -470,40 +485,58 @@ if __name__ == "__main__":
|
|
|
470
485
|
asyncio.run(main())
|
|
471
486
|
```
|
|
472
487
|
|
|
473
|
-
###
|
|
488
|
+
### Debug to a file (recommended for sharing with issues)
|
|
474
489
|
|
|
475
|
-
|
|
490
|
+
The `log_to_file()` method is the simplest way to capture a full debug log. It sets the `pyvesync` logger to `DEBUG` **and** attaches a file handler in one call, so you do not need to configure logging yourself. It also prints to the console by default; pass `stdout=False` to log only to the file. Keep `redact=True` (the default) so the log is safe to attach to a GitHub issue:
|
|
476
491
|
|
|
477
492
|
```python
|
|
478
|
-
|
|
479
493
|
import asyncio
|
|
480
494
|
from pyvesync import VeSync
|
|
495
|
+
from pyvesync.utils.logs import LibraryLogger
|
|
496
|
+
|
|
481
497
|
|
|
482
498
|
async def main():
|
|
483
|
-
|
|
484
|
-
|
|
499
|
+
# redact=True is the default - sensitive values are scrubbed from the log
|
|
500
|
+
async with VeSync("user", "password", redact=True) as manager:
|
|
501
|
+
# Enables DEBUG logging and writes it to pyvesync.log
|
|
502
|
+
# stdout=True (the default) also prints the log to the console
|
|
503
|
+
manager.log_to_file("pyvesync.log", stdout=True)
|
|
504
|
+
|
|
505
|
+
# Optional - include the full request & response body of every API call
|
|
506
|
+
LibraryLogger.verbose = True
|
|
507
|
+
|
|
485
508
|
await manager.login()
|
|
486
509
|
await manager.update()
|
|
487
510
|
|
|
488
|
-
outlet = manager.outlets[0]
|
|
511
|
+
outlet = manager.devices.outlets[0]
|
|
489
512
|
await outlet.update()
|
|
490
513
|
await outlet.turn_off()
|
|
491
514
|
outlet.display()
|
|
492
515
|
|
|
516
|
+
|
|
493
517
|
if __name__ == "__main__":
|
|
494
518
|
asyncio.run(main())
|
|
495
|
-
|
|
496
519
|
```
|
|
497
520
|
|
|
521
|
+
## Contribution Expectations
|
|
522
|
+
|
|
523
|
+
Contributions — from humans and AI coding assistants alike — must follow three rules. These keep the library correct and maintainable. See the [contributing guide](https://webdjoe.github.io/pyvesync/latest/development/contributing) for full detail.
|
|
524
|
+
|
|
525
|
+
1. **Follow the intended architecture.** New devices are added through the three-tier hierarchy (base class → concrete device + API mixin → device map), with state in the device's `DeviceState` subclass, models in `models/`, and constants in `pyvesync.const`. API request/response shapes must come from a real packet capture — never invented.
|
|
526
|
+
2. **Verify on real hardware before merge.** New device support and any change to device behavior must be tested on the physical device, and the pull request must state the model and firmware version tested. Testing only through Home Assistant does not count.
|
|
527
|
+
3. **One change per pull request.** Each PR must be a single feature, fix, or edit. Large or mixed PRs that bundle unrelated changes will be asked to be broken up before review.
|
|
528
|
+
|
|
529
|
+
> **Using an AI assistant?** Agent instructions live in [`AGENTS.md`](AGENTS.md), which Codex and most tools read directly; [`CLAUDE.md`](CLAUDE.md) imports it for Claude Code. Point your assistant at it and the [contributing guide](https://webdjoe.github.io/pyvesync/latest/development/contributing). You are responsible for the result: it must follow the architecture, must not fabricate API fields, must not be submitted without real-device testing, and must be a single focused change.
|
|
530
|
+
|
|
498
531
|
## Feature Requests
|
|
499
532
|
|
|
500
|
-
Before filing an issue to request a new feature or device, please ensure that you will take the time to test the feature
|
|
533
|
+
Before filing an issue to request a new feature or device, please ensure that you will take the time to test the feature thoroughly. 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.
|
|
501
534
|
|
|
502
535
|
```bash
|
|
503
536
|
mkdir python_test && cd python_test
|
|
504
537
|
|
|
505
538
|
# Check Python version is 3.11 or higher
|
|
506
|
-
python3 --version # or python --version
|
|
539
|
+
python3 --version # or python --version
|
|
507
540
|
# Create a new venv
|
|
508
541
|
python3 -m venv pyvesync-venv
|
|
509
542
|
# Activate the venv on linux
|
|
@@ -521,14 +554,11 @@ pip install git+https://github.com/webdjoe/pyvesync.git@refs/pull/PR_NUMBER/head
|
|
|
521
554
|
|
|
522
555
|
Test functionality with a script, please adjust methods and logging statements to the device you are testing.
|
|
523
556
|
|
|
524
|
-
See
|
|
557
|
+
See [testing_scripts](./testing_scripts/README.md) for a fully functioning test script for all devices.
|
|
525
558
|
|
|
526
559
|
```python
|
|
527
560
|
import asyncio
|
|
528
|
-
import sys
|
|
529
561
|
import logging
|
|
530
|
-
import json
|
|
531
|
-
from functool import chain
|
|
532
562
|
from pyvesync import VeSync
|
|
533
563
|
|
|
534
564
|
vs_logger = logging.getLogger("pyvesync")
|
|
@@ -540,65 +570,47 @@ logger.setLevel(logging.DEBUG)
|
|
|
540
570
|
USERNAME = "YOUR USERNAME"
|
|
541
571
|
PASSWORD = "YOUR PASSWORD"
|
|
542
572
|
|
|
543
|
-
DEVICE_NAME = "Device"
|
|
573
|
+
DEVICE_NAME = "Device" # Device to test
|
|
544
574
|
|
|
545
575
|
async def test_device():
|
|
546
576
|
# Instantiate VeSync class and login
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
logger.debug("Device is on %s", dev.is_on)
|
|
581
|
-
|
|
582
|
-
logger.debug("Sleep mode")
|
|
583
|
-
fan.sleep_mode()
|
|
584
|
-
logger.debug("Current mode - %s", dev.details['mode'])
|
|
585
|
-
|
|
586
|
-
fan.auto_mode()
|
|
587
|
-
|
|
588
|
-
logger.debug("Set Fan Speed - %s", dev.set_fan_speed)
|
|
589
|
-
logger.debug("Current Fan Level - %s", dev.fan_level)
|
|
590
|
-
logger.debug("Current mode - %s", dev.mode)
|
|
591
|
-
|
|
592
|
-
# Display all device info
|
|
593
|
-
logger.debug(dev.display(state=True))
|
|
594
|
-
logger.debug(dev.to_json(state=True, indent=True))
|
|
595
|
-
dev_dict = dev.to_dict(state=True)
|
|
577
|
+
async with VeSync(USERNAME, PASSWORD, redact=True) as manager:
|
|
578
|
+
await manager.login()
|
|
579
|
+
|
|
580
|
+
# Pull and update devices
|
|
581
|
+
await manager.update()
|
|
582
|
+
|
|
583
|
+
for dev in manager.devices:
|
|
584
|
+
# Print all device info
|
|
585
|
+
logger.debug(dev.device_name)
|
|
586
|
+
dev.display()
|
|
587
|
+
|
|
588
|
+
# Find correct device
|
|
589
|
+
if dev.device_name.lower() != DEVICE_NAME.lower():
|
|
590
|
+
logger.debug("%s is not %s, continuing", dev.device_name, DEVICE_NAME)
|
|
591
|
+
continue
|
|
592
|
+
|
|
593
|
+
logger.debug('-------------- %s -----------------', dev.device_name)
|
|
594
|
+
dev.display()
|
|
595
|
+
|
|
596
|
+
# Test device methods
|
|
597
|
+
logger.debug("Device is on: %s", dev.is_on)
|
|
598
|
+
|
|
599
|
+
logger.debug("Turning on")
|
|
600
|
+
await dev.turn_on()
|
|
601
|
+
logger.debug("Device is on: %s", dev.is_on)
|
|
602
|
+
|
|
603
|
+
logger.debug("Turning off")
|
|
604
|
+
await dev.turn_off()
|
|
605
|
+
logger.debug("Device is on: %s", dev.is_on)
|
|
606
|
+
|
|
607
|
+
# Display all device info as JSON
|
|
608
|
+
logger.debug(dev.to_json(state=True, indent=True))
|
|
609
|
+
dev_dict = dev.to_dict(state=True)
|
|
596
610
|
|
|
597
611
|
if __name__ == "__main__":
|
|
598
612
|
logger.debug("Testing device")
|
|
599
613
|
asyncio.run(test_device())
|
|
600
|
-
...
|
|
601
|
-
|
|
602
614
|
```
|
|
603
615
|
|
|
604
616
|
## Device Requests
|