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.
- pyvesync-3.0.0/PKG-INFO +567 -0
- pyvesync-3.0.0/README.md +521 -0
- pyvesync-3.0.0/pyproject.toml +372 -0
- pyvesync-3.0.0/requirements.txt +3 -0
- pyvesync-3.0.0/setup.cfg +12 -0
- pyvesync-3.0.0/setup.py +5 -0
- pyvesync-3.0.0/src/pyvesync/__init__.py +5 -0
- pyvesync-3.0.0/src/pyvesync/base_devices/__init__.py +35 -0
- pyvesync-3.0.0/src/pyvesync/base_devices/bulb_base.py +330 -0
- pyvesync-3.0.0/src/pyvesync/base_devices/fan_base.py +392 -0
- pyvesync-3.0.0/src/pyvesync/base_devices/fryer_base.py +68 -0
- pyvesync-3.0.0/src/pyvesync/base_devices/humidifier_base.py +462 -0
- pyvesync-3.0.0/src/pyvesync/base_devices/outlet_base.py +327 -0
- pyvesync-3.0.0/src/pyvesync/base_devices/purifier_base.py +525 -0
- pyvesync-3.0.0/src/pyvesync/base_devices/switch_base.py +271 -0
- pyvesync-3.0.0/src/pyvesync/base_devices/thermostat_base.py +283 -0
- pyvesync-3.0.0/src/pyvesync/base_devices/vesyncbasedevice.py +572 -0
- pyvesync-3.0.0/src/pyvesync/const.py +825 -0
- pyvesync-3.0.0/src/pyvesync/device_container.py +373 -0
- pyvesync-3.0.0/src/pyvesync/device_map.py +1172 -0
- pyvesync-3.0.0/src/pyvesync/devices/__init__.py +1 -0
- pyvesync-3.0.0/src/pyvesync/devices/vesyncbulb.py +1126 -0
- pyvesync-3.0.0/src/pyvesync/devices/vesyncfan.py +233 -0
- pyvesync-3.0.0/src/pyvesync/devices/vesynchumidifier.py +808 -0
- pyvesync-3.0.0/src/pyvesync/devices/vesynckitchen.py +651 -0
- pyvesync-3.0.0/src/pyvesync/devices/vesyncoutlet.py +954 -0
- pyvesync-3.0.0/src/pyvesync/devices/vesyncpurifier.py +990 -0
- pyvesync-3.0.0/src/pyvesync/devices/vesyncswitch.py +415 -0
- pyvesync-3.0.0/src/pyvesync/devices/vesyncthermostat.py +171 -0
- pyvesync-3.0.0/src/pyvesync/models/__init__.py +11 -0
- pyvesync-3.0.0/src/pyvesync/models/base_models.py +129 -0
- pyvesync-3.0.0/src/pyvesync/models/bulb_models.py +197 -0
- pyvesync-3.0.0/src/pyvesync/models/bypass_models.py +289 -0
- pyvesync-3.0.0/src/pyvesync/models/fan_models.py +47 -0
- pyvesync-3.0.0/src/pyvesync/models/fryer_models.py +34 -0
- pyvesync-3.0.0/src/pyvesync/models/home_models.py +169 -0
- pyvesync-3.0.0/src/pyvesync/models/humidifier_models.py +202 -0
- pyvesync-3.0.0/src/pyvesync/models/outlet_models.py +220 -0
- pyvesync-3.0.0/src/pyvesync/models/purifier_models.py +261 -0
- pyvesync-3.0.0/src/pyvesync/models/switch_models.py +101 -0
- pyvesync-3.0.0/src/pyvesync/models/thermostat_models.py +134 -0
- pyvesync-3.0.0/src/pyvesync/models/vesync_models.py +439 -0
- pyvesync-3.0.0/src/pyvesync/utils/__init__.py +1 -0
- pyvesync-3.0.0/src/pyvesync/utils/colors.py +250 -0
- pyvesync-3.0.0/src/pyvesync/utils/device_mixins.py +362 -0
- pyvesync-3.0.0/src/pyvesync/utils/enum_utils.py +26 -0
- pyvesync-3.0.0/src/pyvesync/utils/errors.py +867 -0
- pyvesync-3.0.0/src/pyvesync/utils/helpers.py +700 -0
- pyvesync-3.0.0/src/pyvesync/utils/logs.py +525 -0
- pyvesync-3.0.0/src/pyvesync/vesync.py +638 -0
- pyvesync-3.0.0/src/pyvesync/vesynchome.py +152 -0
- pyvesync-3.0.0/src/pyvesync.egg-info/PKG-INFO +567 -0
- pyvesync-3.0.0/src/pyvesync.egg-info/SOURCES.txt +57 -0
- pyvesync-3.0.0/src/pyvesync.egg-info/requires.txt +29 -0
- pyvesync-2.1.18/PKG-INFO +0 -1109
- pyvesync-2.1.18/README.md +0 -1070
- pyvesync-2.1.18/requirements.txt +0 -1
- pyvesync-2.1.18/setup.cfg +0 -26
- pyvesync-2.1.18/setup.py +0 -41
- pyvesync-2.1.18/src/pyvesync/__init__.py +0 -11
- pyvesync-2.1.18/src/pyvesync/helpers.py +0 -854
- pyvesync-2.1.18/src/pyvesync/vesync.py +0 -450
- pyvesync-2.1.18/src/pyvesync/vesyncbasedevice.py +0 -202
- pyvesync-2.1.18/src/pyvesync/vesyncbulb.py +0 -1416
- pyvesync-2.1.18/src/pyvesync/vesyncfan.py +0 -3218
- pyvesync-2.1.18/src/pyvesync/vesynckitchen.py +0 -655
- pyvesync-2.1.18/src/pyvesync/vesyncoutlet.py +0 -806
- pyvesync-2.1.18/src/pyvesync/vesyncswitch.py +0 -412
- pyvesync-2.1.18/src/pyvesync.egg-info/PKG-INFO +0 -1109
- pyvesync-2.1.18/src/pyvesync.egg-info/SOURCES.txt +0 -20
- pyvesync-2.1.18/src/pyvesync.egg-info/requires.txt +0 -7
- {pyvesync-2.1.18 → pyvesync-3.0.0}/LICENSE +0 -0
- {pyvesync-2.1.18 → pyvesync-3.0.0}/MANIFEST.in +0 -0
- {pyvesync-2.1.18 → pyvesync-3.0.0}/src/pyvesync.egg-info/dependency_links.txt +0 -0
- {pyvesync-2.1.18 → pyvesync-3.0.0}/src/pyvesync.egg-info/top_level.txt +0 -0
pyvesync-3.0.0/PKG-INFO
ADDED
|
@@ -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 [](https://pypi.python.org/pypi/pyvesync) [](https://dev.azure.com/webdjoe/pyvesync/_build/latest?definitionId=4&branchName=master) [](https://github.com/Naereen/badges/) [](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).
|