pyvesync 3.1.1__tar.gz → 3.1.4__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.1.1/src/pyvesync.egg-info → pyvesync-3.1.4}/PKG-INFO +47 -41
- {pyvesync-3.1.1 → pyvesync-3.1.4}/README.md +46 -40
- {pyvesync-3.1.1 → pyvesync-3.1.4}/pyproject.toml +1 -1
- {pyvesync-3.1.1 → pyvesync-3.1.4}/src/pyvesync/auth.py +25 -9
- {pyvesync-3.1.1 → pyvesync-3.1.4}/src/pyvesync/base_devices/fan_base.py +10 -10
- {pyvesync-3.1.1 → pyvesync-3.1.4}/src/pyvesync/base_devices/humidifier_base.py +0 -22
- {pyvesync-3.1.1 → pyvesync-3.1.4}/src/pyvesync/base_devices/outlet_base.py +31 -16
- {pyvesync-3.1.1 → pyvesync-3.1.4}/src/pyvesync/base_devices/purifier_base.py +10 -4
- {pyvesync-3.1.1 → pyvesync-3.1.4}/src/pyvesync/const.py +37 -2
- {pyvesync-3.1.1 → pyvesync-3.1.4}/src/pyvesync/device_container.py +5 -0
- {pyvesync-3.1.1 → pyvesync-3.1.4}/src/pyvesync/device_map.py +46 -13
- {pyvesync-3.1.1 → pyvesync-3.1.4}/src/pyvesync/devices/vesyncbulb.py +16 -14
- {pyvesync-3.1.1 → pyvesync-3.1.4}/src/pyvesync/devices/vesyncfan.py +5 -4
- {pyvesync-3.1.1 → pyvesync-3.1.4}/src/pyvesync/devices/vesynchumidifier.py +22 -16
- {pyvesync-3.1.1 → pyvesync-3.1.4}/src/pyvesync/devices/vesynckitchen.py +2 -3
- {pyvesync-3.1.1 → pyvesync-3.1.4}/src/pyvesync/devices/vesyncoutlet.py +258 -101
- {pyvesync-3.1.1 → pyvesync-3.1.4}/src/pyvesync/devices/vesyncpurifier.py +13 -22
- {pyvesync-3.1.1 → pyvesync-3.1.4}/src/pyvesync/models/home_models.py +1 -1
- {pyvesync-3.1.1 → pyvesync-3.1.4}/src/pyvesync/models/humidifier_models.py +9 -0
- {pyvesync-3.1.1 → pyvesync-3.1.4}/src/pyvesync/models/outlet_models.py +63 -11
- {pyvesync-3.1.1 → pyvesync-3.1.4}/src/pyvesync/models/purifier_models.py +2 -2
- {pyvesync-3.1.1 → pyvesync-3.1.4}/src/pyvesync/utils/device_mixins.py +9 -76
- {pyvesync-3.1.1 → pyvesync-3.1.4}/src/pyvesync/utils/errors.py +18 -9
- {pyvesync-3.1.1 → pyvesync-3.1.4}/src/pyvesync/utils/helpers.py +31 -52
- {pyvesync-3.1.1 → pyvesync-3.1.4}/src/pyvesync/utils/logs.py +232 -139
- {pyvesync-3.1.1 → pyvesync-3.1.4}/src/pyvesync/vesync.py +99 -71
- {pyvesync-3.1.1 → pyvesync-3.1.4/src/pyvesync.egg-info}/PKG-INFO +47 -41
- {pyvesync-3.1.1 → pyvesync-3.1.4}/LICENSE +0 -0
- {pyvesync-3.1.1 → pyvesync-3.1.4}/MANIFEST.in +0 -0
- {pyvesync-3.1.1 → pyvesync-3.1.4}/requirements.txt +0 -0
- {pyvesync-3.1.1 → pyvesync-3.1.4}/setup.cfg +0 -0
- {pyvesync-3.1.1 → pyvesync-3.1.4}/setup.py +0 -0
- {pyvesync-3.1.1 → pyvesync-3.1.4}/src/pyvesync/__init__.py +0 -0
- {pyvesync-3.1.1 → pyvesync-3.1.4}/src/pyvesync/base_devices/__init__.py +0 -0
- {pyvesync-3.1.1 → pyvesync-3.1.4}/src/pyvesync/base_devices/bulb_base.py +0 -0
- {pyvesync-3.1.1 → pyvesync-3.1.4}/src/pyvesync/base_devices/fryer_base.py +0 -0
- {pyvesync-3.1.1 → pyvesync-3.1.4}/src/pyvesync/base_devices/switch_base.py +0 -0
- {pyvesync-3.1.1 → pyvesync-3.1.4}/src/pyvesync/base_devices/thermostat_base.py +0 -0
- {pyvesync-3.1.1 → pyvesync-3.1.4}/src/pyvesync/base_devices/vesyncbasedevice.py +0 -0
- {pyvesync-3.1.1 → pyvesync-3.1.4}/src/pyvesync/devices/__init__.py +0 -0
- {pyvesync-3.1.1 → pyvesync-3.1.4}/src/pyvesync/devices/vesyncswitch.py +0 -0
- {pyvesync-3.1.1 → pyvesync-3.1.4}/src/pyvesync/devices/vesyncthermostat.py +0 -0
- {pyvesync-3.1.1 → pyvesync-3.1.4}/src/pyvesync/models/__init__.py +0 -0
- {pyvesync-3.1.1 → pyvesync-3.1.4}/src/pyvesync/models/base_models.py +0 -0
- {pyvesync-3.1.1 → pyvesync-3.1.4}/src/pyvesync/models/bulb_models.py +0 -0
- {pyvesync-3.1.1 → pyvesync-3.1.4}/src/pyvesync/models/bypass_models.py +0 -0
- {pyvesync-3.1.1 → pyvesync-3.1.4}/src/pyvesync/models/fan_models.py +0 -0
- {pyvesync-3.1.1 → pyvesync-3.1.4}/src/pyvesync/models/fryer_models.py +0 -0
- {pyvesync-3.1.1 → pyvesync-3.1.4}/src/pyvesync/models/switch_models.py +0 -0
- {pyvesync-3.1.1 → pyvesync-3.1.4}/src/pyvesync/models/thermostat_models.py +0 -0
- {pyvesync-3.1.1 → pyvesync-3.1.4}/src/pyvesync/models/vesync_models.py +0 -0
- {pyvesync-3.1.1 → pyvesync-3.1.4}/src/pyvesync/utils/__init__.py +0 -0
- {pyvesync-3.1.1 → pyvesync-3.1.4}/src/pyvesync/utils/colors.py +0 -0
- {pyvesync-3.1.1 → pyvesync-3.1.4}/src/pyvesync/utils/enum_utils.py +0 -0
- {pyvesync-3.1.1 → pyvesync-3.1.4}/src/pyvesync/vesynchome.py +0 -0
- {pyvesync-3.1.1 → pyvesync-3.1.4}/src/pyvesync.egg-info/SOURCES.txt +0 -0
- {pyvesync-3.1.1 → pyvesync-3.1.4}/src/pyvesync.egg-info/dependency_links.txt +0 -0
- {pyvesync-3.1.1 → pyvesync-3.1.4}/src/pyvesync.egg-info/requires.txt +0 -0
- {pyvesync-3.1.1 → pyvesync-3.1.4}/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.1.
|
|
3
|
+
Version: 3.1.4
|
|
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>
|
|
@@ -101,13 +101,9 @@ async def main():
|
|
|
101
101
|
country_code="US", # Optional - country Code to select correct server
|
|
102
102
|
session=session, # Optional - aiohttp.ClientSession
|
|
103
103
|
time_zone="America/New_York", # Optional - Timezone, defaults to America/New_York
|
|
104
|
-
debug=False, # Optional - Debug output
|
|
105
104
|
redact=True # Optional - Redact sensitive information from logs
|
|
106
105
|
) as manager:
|
|
107
106
|
|
|
108
|
-
# To enable debug mode - prints request and response content for
|
|
109
|
-
# api calls that return an error code
|
|
110
|
-
manager.debug = True
|
|
111
107
|
# Redact mode is enabled by default, set to False to disable
|
|
112
108
|
manager.redact = False
|
|
113
109
|
|
|
@@ -194,8 +190,7 @@ VeSync(
|
|
|
194
190
|
password: str,
|
|
195
191
|
country_code: str = DEFAULT_COUNTRY_CODE, # US
|
|
196
192
|
session: ClientSession | None = None,
|
|
197
|
-
time_zone: str = DEFAULT_TZ # America/New_York
|
|
198
|
-
debug: bool = False,
|
|
193
|
+
time_zone: str = DEFAULT_TZ # America/New_York,
|
|
199
194
|
redact: bool = True,
|
|
200
195
|
)
|
|
201
196
|
```
|
|
@@ -325,7 +320,7 @@ from pyvesync import VeSync
|
|
|
325
320
|
from pyvesync.logs import VeSyncLoginError
|
|
326
321
|
|
|
327
322
|
# VeSync is an asynchronous context manager
|
|
328
|
-
# VeSync(username, password,
|
|
323
|
+
# VeSync(username, password, redact=True, session=None)
|
|
329
324
|
|
|
330
325
|
async def main():
|
|
331
326
|
async with VeSync("user", "password") as manager:
|
|
@@ -350,7 +345,7 @@ if __name__ == "__main__":
|
|
|
350
345
|
asyncio.run(main())
|
|
351
346
|
```
|
|
352
347
|
|
|
353
|
-
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.
|
|
348
|
+
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.md) for more details.
|
|
354
349
|
|
|
355
350
|
```python
|
|
356
351
|
import asyncio
|
|
@@ -358,7 +353,7 @@ from pyvesync import VeSync
|
|
|
358
353
|
from pyvesync.logs import VeSyncLoginError
|
|
359
354
|
|
|
360
355
|
# VeSync is an asynchronous context manager
|
|
361
|
-
# VeSync(username, password,
|
|
356
|
+
# VeSync(username, password, redact=True, session=None)
|
|
362
357
|
|
|
363
358
|
async def main():
|
|
364
359
|
async with VeSync("user", "password") as manager:
|
|
@@ -403,7 +398,7 @@ if __name__ == "__main__":
|
|
|
403
398
|
asyncio.run(main())
|
|
404
399
|
```
|
|
405
400
|
|
|
406
|
-
Devices are stored in the
|
|
401
|
+
Devices are stored in the `VeSync.devices` attribute, which is a `DeviceContainer` instance that acts as a mutable set. The `DeviceContainer` has properties for each product type that return lists of device instances:
|
|
407
402
|
|
|
408
403
|
```python
|
|
409
404
|
await manager.login() # Asynchronous
|
|
@@ -440,15 +435,22 @@ See the [device documentation](https://webdjoe.github.io/pyvesync/latest/devices
|
|
|
440
435
|
|
|
441
436
|
## Debug mode and redact
|
|
442
437
|
|
|
443
|
-
To
|
|
438
|
+
To set debug, use the `logger` object in the `VeSync` class. Setting the logger level to `DEBUG` will print debug information to the console. The `log_to_file()` method can be used to log to a file.
|
|
444
439
|
|
|
445
440
|
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`.
|
|
446
441
|
|
|
442
|
+
This is an example of debug mode with redact enabled:
|
|
443
|
+
|
|
447
444
|
```python
|
|
445
|
+
import logging
|
|
448
446
|
import asyncio
|
|
449
447
|
import aiohttp
|
|
450
448
|
from pyvesync.vesync import VeSync
|
|
451
449
|
|
|
450
|
+
logger = logging.getLogger("pyvesync")
|
|
451
|
+
logger.setLevel(logging.DEBUG)
|
|
452
|
+
|
|
453
|
+
|
|
452
454
|
async def main():
|
|
453
455
|
async with VeSync("user", "password") as manager:
|
|
454
456
|
manager.debug = True
|
|
@@ -466,6 +468,31 @@ if __name__ == "__main__":
|
|
|
466
468
|
asyncio.run(main())
|
|
467
469
|
```
|
|
468
470
|
|
|
471
|
+
### Logging to File
|
|
472
|
+
|
|
473
|
+
To log to a file, use the `log_to_file()` method of the `VeSync` class. Pass the file path as an argument.
|
|
474
|
+
|
|
475
|
+
```python
|
|
476
|
+
|
|
477
|
+
import asyncio
|
|
478
|
+
from pyvesync import VeSync
|
|
479
|
+
|
|
480
|
+
async def main():
|
|
481
|
+
async with VeSync("user", "password") as manager:
|
|
482
|
+
manager.log_to_file("pyvesync.log", level=logging.DEBUG, stdout=True) # stdout argument prints log to console as well
|
|
483
|
+
await manager.login()
|
|
484
|
+
await manager.update()
|
|
485
|
+
|
|
486
|
+
outlet = manager.outlets[0]
|
|
487
|
+
await outlet.update()
|
|
488
|
+
await outlet.turn_off()
|
|
489
|
+
outlet.display()
|
|
490
|
+
|
|
491
|
+
if __name__ == "__main__":
|
|
492
|
+
asyncio.run(main())
|
|
493
|
+
|
|
494
|
+
```
|
|
495
|
+
|
|
469
496
|
## Feature Requests
|
|
470
497
|
|
|
471
498
|
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.
|
|
@@ -492,7 +519,7 @@ pip install git+https://github.com/webdjoe/pyvesync.git@refs/pull/PR_NUMBER/head
|
|
|
492
519
|
|
|
493
520
|
Test functionality with a script, please adjust methods and logging statements to the device you are testing.
|
|
494
521
|
|
|
495
|
-
`test.
|
|
522
|
+
See `[testing_scripts](./testing_scripts/README.md)` for a fully functioning test script for all devices.
|
|
496
523
|
|
|
497
524
|
```python
|
|
498
525
|
import asyncio
|
|
@@ -502,7 +529,10 @@ import json
|
|
|
502
529
|
from functool import chain
|
|
503
530
|
from pyvesync import VeSync
|
|
504
531
|
|
|
505
|
-
|
|
532
|
+
vs_logger = logging.getLogger("pyvesync")
|
|
533
|
+
vs_logger.setLevel(logging.DEBUG)
|
|
534
|
+
|
|
535
|
+
logger = logging.getLogger('pyvesync_test')
|
|
506
536
|
logger.setLevel(logging.DEBUG)
|
|
507
537
|
|
|
508
538
|
USERNAME = "YOUR USERNAME"
|
|
@@ -512,7 +542,7 @@ DEVICE_NAME = "Device" # Device to test
|
|
|
512
542
|
|
|
513
543
|
async def test_device():
|
|
514
544
|
# Instantiate VeSync class and login
|
|
515
|
-
async with VeSync(USERNAME, PASSWORD,
|
|
545
|
+
async with VeSync(USERNAME, PASSWORD, redact=True) as manager:
|
|
516
546
|
await manager.login()
|
|
517
547
|
|
|
518
548
|
# Pull and update devices
|
|
@@ -571,35 +601,11 @@ if __name__ == "__main__":
|
|
|
571
601
|
|
|
572
602
|
## Device Requests
|
|
573
603
|
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
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.
|
|
577
|
-
|
|
578
|
-
For example:
|
|
579
|
-
|
|
580
|
-
Before:
|
|
581
|
-
|
|
582
|
-
```json
|
|
583
|
-
{
|
|
584
|
-
"tk": "abc123abc123==3rf",
|
|
585
|
-
"accountId": "123456789",
|
|
586
|
-
"cid": "abcdef12-3gh-ij"
|
|
587
|
-
}
|
|
588
|
-
```
|
|
589
|
-
|
|
590
|
-
After:
|
|
591
|
-
|
|
592
|
-
```json
|
|
593
|
-
{
|
|
594
|
-
"tk": "AAA111AAA111==1AA",
|
|
595
|
-
"accountId": "111111111",
|
|
596
|
-
"cid": "AAAAAA11-1AA-AA"
|
|
597
|
-
}
|
|
598
|
-
```
|
|
604
|
+
If you would like to request a new device to be added to the library, please open an issue on GitHub. Be sure to include the device model number and a link to the product page. If you are able to provide packet captures or are willing to share the device temporarily, please indicate that in the issue. See the [Packet Capturing for New Device Support](https://webdjoe.github.io/pyvesync/latest/development/capturing.md) document for more details.
|
|
599
605
|
|
|
600
606
|
## Contributing
|
|
601
607
|
|
|
602
|
-
All [contributions](CONTRIBUTING.md) are welcome.
|
|
608
|
+
All [contributions](https://webdjoe.github.io/pyvesync/latest/development/CONTRIBUTING.md) are welcome.
|
|
603
609
|
|
|
604
610
|
This project is licensed under [MIT](LICENSE).
|
|
605
611
|
|
|
@@ -55,13 +55,9 @@ async def main():
|
|
|
55
55
|
country_code="US", # Optional - country Code to select correct server
|
|
56
56
|
session=session, # Optional - aiohttp.ClientSession
|
|
57
57
|
time_zone="America/New_York", # Optional - Timezone, defaults to America/New_York
|
|
58
|
-
debug=False, # Optional - Debug output
|
|
59
58
|
redact=True # Optional - Redact sensitive information from logs
|
|
60
59
|
) as manager:
|
|
61
60
|
|
|
62
|
-
# To enable debug mode - prints request and response content for
|
|
63
|
-
# api calls that return an error code
|
|
64
|
-
manager.debug = True
|
|
65
61
|
# Redact mode is enabled by default, set to False to disable
|
|
66
62
|
manager.redact = False
|
|
67
63
|
|
|
@@ -148,8 +144,7 @@ VeSync(
|
|
|
148
144
|
password: str,
|
|
149
145
|
country_code: str = DEFAULT_COUNTRY_CODE, # US
|
|
150
146
|
session: ClientSession | None = None,
|
|
151
|
-
time_zone: str = DEFAULT_TZ # America/New_York
|
|
152
|
-
debug: bool = False,
|
|
147
|
+
time_zone: str = DEFAULT_TZ # America/New_York,
|
|
153
148
|
redact: bool = True,
|
|
154
149
|
)
|
|
155
150
|
```
|
|
@@ -279,7 +274,7 @@ from pyvesync import VeSync
|
|
|
279
274
|
from pyvesync.logs import VeSyncLoginError
|
|
280
275
|
|
|
281
276
|
# VeSync is an asynchronous context manager
|
|
282
|
-
# VeSync(username, password,
|
|
277
|
+
# VeSync(username, password, redact=True, session=None)
|
|
283
278
|
|
|
284
279
|
async def main():
|
|
285
280
|
async with VeSync("user", "password") as manager:
|
|
@@ -304,7 +299,7 @@ if __name__ == "__main__":
|
|
|
304
299
|
asyncio.run(main())
|
|
305
300
|
```
|
|
306
301
|
|
|
307
|
-
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.
|
|
302
|
+
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.md) for more details.
|
|
308
303
|
|
|
309
304
|
```python
|
|
310
305
|
import asyncio
|
|
@@ -312,7 +307,7 @@ from pyvesync import VeSync
|
|
|
312
307
|
from pyvesync.logs import VeSyncLoginError
|
|
313
308
|
|
|
314
309
|
# VeSync is an asynchronous context manager
|
|
315
|
-
# VeSync(username, password,
|
|
310
|
+
# VeSync(username, password, redact=True, session=None)
|
|
316
311
|
|
|
317
312
|
async def main():
|
|
318
313
|
async with VeSync("user", "password") as manager:
|
|
@@ -357,7 +352,7 @@ if __name__ == "__main__":
|
|
|
357
352
|
asyncio.run(main())
|
|
358
353
|
```
|
|
359
354
|
|
|
360
|
-
Devices are stored in the
|
|
355
|
+
Devices are stored in the `VeSync.devices` attribute, which is a `DeviceContainer` instance that acts as a mutable set. The `DeviceContainer` has properties for each product type that return lists of device instances:
|
|
361
356
|
|
|
362
357
|
```python
|
|
363
358
|
await manager.login() # Asynchronous
|
|
@@ -394,15 +389,22 @@ See the [device documentation](https://webdjoe.github.io/pyvesync/latest/devices
|
|
|
394
389
|
|
|
395
390
|
## Debug mode and redact
|
|
396
391
|
|
|
397
|
-
To
|
|
392
|
+
To set debug, use the `logger` object in the `VeSync` class. Setting the logger level to `DEBUG` will print debug information to the console. The `log_to_file()` method can be used to log to a file.
|
|
398
393
|
|
|
399
394
|
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`.
|
|
400
395
|
|
|
396
|
+
This is an example of debug mode with redact enabled:
|
|
397
|
+
|
|
401
398
|
```python
|
|
399
|
+
import logging
|
|
402
400
|
import asyncio
|
|
403
401
|
import aiohttp
|
|
404
402
|
from pyvesync.vesync import VeSync
|
|
405
403
|
|
|
404
|
+
logger = logging.getLogger("pyvesync")
|
|
405
|
+
logger.setLevel(logging.DEBUG)
|
|
406
|
+
|
|
407
|
+
|
|
406
408
|
async def main():
|
|
407
409
|
async with VeSync("user", "password") as manager:
|
|
408
410
|
manager.debug = True
|
|
@@ -420,6 +422,31 @@ if __name__ == "__main__":
|
|
|
420
422
|
asyncio.run(main())
|
|
421
423
|
```
|
|
422
424
|
|
|
425
|
+
### Logging to File
|
|
426
|
+
|
|
427
|
+
To log to a file, use the `log_to_file()` method of the `VeSync` class. Pass the file path as an argument.
|
|
428
|
+
|
|
429
|
+
```python
|
|
430
|
+
|
|
431
|
+
import asyncio
|
|
432
|
+
from pyvesync import VeSync
|
|
433
|
+
|
|
434
|
+
async def main():
|
|
435
|
+
async with VeSync("user", "password") as manager:
|
|
436
|
+
manager.log_to_file("pyvesync.log", level=logging.DEBUG, stdout=True) # stdout argument prints log to console as well
|
|
437
|
+
await manager.login()
|
|
438
|
+
await manager.update()
|
|
439
|
+
|
|
440
|
+
outlet = manager.outlets[0]
|
|
441
|
+
await outlet.update()
|
|
442
|
+
await outlet.turn_off()
|
|
443
|
+
outlet.display()
|
|
444
|
+
|
|
445
|
+
if __name__ == "__main__":
|
|
446
|
+
asyncio.run(main())
|
|
447
|
+
|
|
448
|
+
```
|
|
449
|
+
|
|
423
450
|
## Feature Requests
|
|
424
451
|
|
|
425
452
|
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.
|
|
@@ -446,7 +473,7 @@ pip install git+https://github.com/webdjoe/pyvesync.git@refs/pull/PR_NUMBER/head
|
|
|
446
473
|
|
|
447
474
|
Test functionality with a script, please adjust methods and logging statements to the device you are testing.
|
|
448
475
|
|
|
449
|
-
`test.
|
|
476
|
+
See `[testing_scripts](./testing_scripts/README.md)` for a fully functioning test script for all devices.
|
|
450
477
|
|
|
451
478
|
```python
|
|
452
479
|
import asyncio
|
|
@@ -456,7 +483,10 @@ import json
|
|
|
456
483
|
from functool import chain
|
|
457
484
|
from pyvesync import VeSync
|
|
458
485
|
|
|
459
|
-
|
|
486
|
+
vs_logger = logging.getLogger("pyvesync")
|
|
487
|
+
vs_logger.setLevel(logging.DEBUG)
|
|
488
|
+
|
|
489
|
+
logger = logging.getLogger('pyvesync_test')
|
|
460
490
|
logger.setLevel(logging.DEBUG)
|
|
461
491
|
|
|
462
492
|
USERNAME = "YOUR USERNAME"
|
|
@@ -466,7 +496,7 @@ DEVICE_NAME = "Device" # Device to test
|
|
|
466
496
|
|
|
467
497
|
async def test_device():
|
|
468
498
|
# Instantiate VeSync class and login
|
|
469
|
-
async with VeSync(USERNAME, PASSWORD,
|
|
499
|
+
async with VeSync(USERNAME, PASSWORD, redact=True) as manager:
|
|
470
500
|
await manager.login()
|
|
471
501
|
|
|
472
502
|
# Pull and update devices
|
|
@@ -525,35 +555,11 @@ if __name__ == "__main__":
|
|
|
525
555
|
|
|
526
556
|
## Device Requests
|
|
527
557
|
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
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.
|
|
531
|
-
|
|
532
|
-
For example:
|
|
533
|
-
|
|
534
|
-
Before:
|
|
535
|
-
|
|
536
|
-
```json
|
|
537
|
-
{
|
|
538
|
-
"tk": "abc123abc123==3rf",
|
|
539
|
-
"accountId": "123456789",
|
|
540
|
-
"cid": "abcdef12-3gh-ij"
|
|
541
|
-
}
|
|
542
|
-
```
|
|
543
|
-
|
|
544
|
-
After:
|
|
545
|
-
|
|
546
|
-
```json
|
|
547
|
-
{
|
|
548
|
-
"tk": "AAA111AAA111==1AA",
|
|
549
|
-
"accountId": "111111111",
|
|
550
|
-
"cid": "AAAAAA11-1AA-AA"
|
|
551
|
-
}
|
|
552
|
-
```
|
|
558
|
+
If you would like to request a new device to be added to the library, please open an issue on GitHub. Be sure to include the device model number and a link to the product page. If you are able to provide packet captures or are willing to share the device temporarily, please indicate that in the issue. See the [Packet Capturing for New Device Support](https://webdjoe.github.io/pyvesync/latest/development/capturing.md) document for more details.
|
|
553
559
|
|
|
554
560
|
## Contributing
|
|
555
561
|
|
|
556
|
-
All [contributions](CONTRIBUTING.md) are welcome.
|
|
562
|
+
All [contributions](https://webdjoe.github.io/pyvesync/latest/development/CONTRIBUTING.md) are welcome.
|
|
557
563
|
|
|
558
564
|
This project is licensed under [MIT](LICENSE).
|
|
559
565
|
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "pyvesync"
|
|
7
|
-
version = "3.1.
|
|
7
|
+
version = "3.1.4"
|
|
8
8
|
description = "pyvesync is a library to manage Etekcity Devices, Cosori Air Fryers, and Levoit Air Purifiers run on the VeSync app."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11"
|
|
@@ -64,8 +64,6 @@ class VeSyncAuth:
|
|
|
64
64
|
username: VeSync account username (email)
|
|
65
65
|
password: VeSync account password
|
|
66
66
|
country_code: Country code in ISO 3166 Alpha-2 format
|
|
67
|
-
current_region: Current region code - determines API base URL
|
|
68
|
-
token_file_path: Path to store/load authentication token
|
|
69
67
|
|
|
70
68
|
Note:
|
|
71
69
|
Either username/password or token/account_id must be provided.
|
|
@@ -145,6 +143,15 @@ class VeSyncAuth:
|
|
|
145
143
|
self._country_code = country_code.upper()
|
|
146
144
|
self._current_region = region
|
|
147
145
|
|
|
146
|
+
async def reauthenticate(self) -> bool:
|
|
147
|
+
"""Re-authenticate using stored username and password.
|
|
148
|
+
|
|
149
|
+
Returns:
|
|
150
|
+
True if re-authentication successful, False otherwise
|
|
151
|
+
"""
|
|
152
|
+
self.clear_credentials()
|
|
153
|
+
return await self.login()
|
|
154
|
+
|
|
148
155
|
async def load_credentials_from_file(
|
|
149
156
|
self, file_path: str | Path | None = None
|
|
150
157
|
) -> bool:
|
|
@@ -165,7 +172,7 @@ class VeSyncAuth:
|
|
|
165
172
|
else:
|
|
166
173
|
file_path_object = Path(file_path)
|
|
167
174
|
if not file_path_object or not file_path_object.exists():
|
|
168
|
-
logger.debug('
|
|
175
|
+
logger.debug('Credentials file not found: %s', file_path_object)
|
|
169
176
|
return False
|
|
170
177
|
try:
|
|
171
178
|
data = await asyncio.to_thread(
|
|
@@ -175,7 +182,7 @@ class VeSyncAuth:
|
|
|
175
182
|
self._token = data['token']
|
|
176
183
|
self._account_id = data['account_id']
|
|
177
184
|
self._country_code = data['country_code'].upper()
|
|
178
|
-
self._current_region = data['current_region']
|
|
185
|
+
self._current_region = data['current_region'].upper()
|
|
179
186
|
logger.debug('Credentials loaded from file: %s', file_path)
|
|
180
187
|
except orjson.JSONDecodeError as exc:
|
|
181
188
|
logger.warning('Failed to load credentials from file: %s', exc)
|
|
@@ -187,17 +194,26 @@ class VeSyncAuth:
|
|
|
187
194
|
self.manager.enabled = True
|
|
188
195
|
return True
|
|
189
196
|
|
|
190
|
-
def
|
|
191
|
-
"""Output current credentials as
|
|
197
|
+
def output_credentials_dict(self) -> dict[str, str] | None:
|
|
198
|
+
"""Output current credentials as a dictionary."""
|
|
192
199
|
if not self.is_authenticated:
|
|
193
200
|
logger.debug('No credentials to output, not authenticated')
|
|
194
201
|
return None
|
|
195
|
-
|
|
196
|
-
'token': self._token,
|
|
197
|
-
'account_id': self._account_id,
|
|
202
|
+
return {
|
|
203
|
+
'token': self._token or '',
|
|
204
|
+
'account_id': self._account_id or '',
|
|
198
205
|
'country_code': self._country_code,
|
|
199
206
|
'current_region': self._current_region,
|
|
200
207
|
}
|
|
208
|
+
|
|
209
|
+
def output_credentials_json(self) -> str | None:
|
|
210
|
+
"""Output current authentication credentials as a JSON string."""
|
|
211
|
+
if not self.is_authenticated:
|
|
212
|
+
logger.debug('No credentials to output, not authenticated')
|
|
213
|
+
return None
|
|
214
|
+
credentials = self.output_credentials_dict()
|
|
215
|
+
if credentials is None:
|
|
216
|
+
return None
|
|
201
217
|
try:
|
|
202
218
|
return orjson.dumps(credentials).decode('utf-8')
|
|
203
219
|
except orjson.JSONEncodeError as exc:
|
|
@@ -21,7 +21,7 @@ logger = logging.getLogger(__name__)
|
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
class FanState(DeviceState):
|
|
24
|
-
"""Base state class for
|
|
24
|
+
"""Base state class for Fans.
|
|
25
25
|
|
|
26
26
|
Not all attributes are supported by all devices.
|
|
27
27
|
|
|
@@ -73,7 +73,7 @@ class FanState(DeviceState):
|
|
|
73
73
|
details: ResponseDeviceDetailsModel,
|
|
74
74
|
feature_map: FanMap,
|
|
75
75
|
) -> None:
|
|
76
|
-
"""Initialize
|
|
76
|
+
"""Initialize Fan State.
|
|
77
77
|
|
|
78
78
|
Args:
|
|
79
79
|
device (VeSyncFanBase): Device object.
|
|
@@ -187,7 +187,7 @@ class VeSyncFanBase(VeSyncBaseToggleDevice):
|
|
|
187
187
|
|
|
188
188
|
@abstractmethod
|
|
189
189
|
async def set_mode(self, mode: str) -> bool:
|
|
190
|
-
"""Set
|
|
190
|
+
"""Set Fan Mode.
|
|
191
191
|
|
|
192
192
|
Args:
|
|
193
193
|
mode (str): Mode to set, varies by device type.
|
|
@@ -198,7 +198,7 @@ class VeSyncFanBase(VeSyncBaseToggleDevice):
|
|
|
198
198
|
|
|
199
199
|
@abstractmethod
|
|
200
200
|
async def set_fan_speed(self, speed: int | None = None) -> bool:
|
|
201
|
-
"""Set
|
|
201
|
+
"""Set Fan Fan Speed.
|
|
202
202
|
|
|
203
203
|
Args:
|
|
204
204
|
speed (int, optional): Fan speed level according to device specs.
|
|
@@ -208,7 +208,7 @@ class VeSyncFanBase(VeSyncBaseToggleDevice):
|
|
|
208
208
|
"""
|
|
209
209
|
|
|
210
210
|
async def set_auto_mode(self) -> bool:
|
|
211
|
-
"""Set
|
|
211
|
+
"""Set Fan to Auto Mode.
|
|
212
212
|
|
|
213
213
|
Returns:
|
|
214
214
|
bool: Success of request.
|
|
@@ -223,7 +223,7 @@ class VeSyncFanBase(VeSyncBaseToggleDevice):
|
|
|
223
223
|
return False
|
|
224
224
|
|
|
225
225
|
async def set_advanced_sleep_mode(self) -> bool:
|
|
226
|
-
"""Set
|
|
226
|
+
"""Set Fan to Advanced Sleep Mode.
|
|
227
227
|
|
|
228
228
|
Returns:
|
|
229
229
|
bool: Success of request.
|
|
@@ -238,7 +238,7 @@ class VeSyncFanBase(VeSyncBaseToggleDevice):
|
|
|
238
238
|
return False
|
|
239
239
|
|
|
240
240
|
async def set_sleep_mode(self) -> bool:
|
|
241
|
-
"""Set
|
|
241
|
+
"""Set Fan to Sleep Mode.
|
|
242
242
|
|
|
243
243
|
This is also referred to as Advanced Sleep Mode on some devices.
|
|
244
244
|
|
|
@@ -255,7 +255,7 @@ class VeSyncFanBase(VeSyncBaseToggleDevice):
|
|
|
255
255
|
return False
|
|
256
256
|
|
|
257
257
|
async def set_manual_mode(self) -> bool:
|
|
258
|
-
"""Set
|
|
258
|
+
"""Set Fan to Manual Mode - Normal Mode.
|
|
259
259
|
|
|
260
260
|
Returns:
|
|
261
261
|
bool: Success of request.
|
|
@@ -270,7 +270,7 @@ class VeSyncFanBase(VeSyncBaseToggleDevice):
|
|
|
270
270
|
return False
|
|
271
271
|
|
|
272
272
|
async def set_normal_mode(self) -> bool:
|
|
273
|
-
"""Set
|
|
273
|
+
"""Set Fan to Normal Mode.
|
|
274
274
|
|
|
275
275
|
Returns:
|
|
276
276
|
bool: Success of request.
|
|
@@ -285,7 +285,7 @@ class VeSyncFanBase(VeSyncBaseToggleDevice):
|
|
|
285
285
|
return False
|
|
286
286
|
|
|
287
287
|
async def set_turbo_mode(self) -> bool:
|
|
288
|
-
"""Set
|
|
288
|
+
"""Set Fan to Turbo Mode.
|
|
289
289
|
|
|
290
290
|
Returns:
|
|
291
291
|
bool: Success of request.
|
|
@@ -6,8 +6,6 @@ import logging
|
|
|
6
6
|
from abc import abstractmethod
|
|
7
7
|
from typing import TYPE_CHECKING
|
|
8
8
|
|
|
9
|
-
from typing_extensions import deprecated
|
|
10
|
-
|
|
11
9
|
from pyvesync.base_devices.vesyncbasedevice import DeviceState, VeSyncBaseToggleDevice
|
|
12
10
|
from pyvesync.const import DeviceStatus, HumidifierFeatures, HumidifierModes
|
|
13
11
|
|
|
@@ -128,16 +126,6 @@ class HumidifierState(DeviceState):
|
|
|
128
126
|
"""
|
|
129
127
|
return self.automatic_stop_config
|
|
130
128
|
|
|
131
|
-
@property
|
|
132
|
-
@deprecated('Use auto_stop_target_reached instead.')
|
|
133
|
-
def automatic_stop_target_reached(self) -> bool:
|
|
134
|
-
"""Deprecated function.
|
|
135
|
-
|
|
136
|
-
Returns:
|
|
137
|
-
bool: True if automatic stop target is reached, False otherwise.
|
|
138
|
-
"""
|
|
139
|
-
return self.auto_stop_target_reached
|
|
140
|
-
|
|
141
129
|
@property
|
|
142
130
|
def target_humidity(self) -> int | None:
|
|
143
131
|
"""Return the target humidity level.
|
|
@@ -161,16 +149,6 @@ class HumidifierState(DeviceState):
|
|
|
161
149
|
"""Return True if auto mode is enabled."""
|
|
162
150
|
return self.mode in [HumidifierModes.AUTO, self.mode, HumidifierModes.HUMIDITY]
|
|
163
151
|
|
|
164
|
-
@property
|
|
165
|
-
@deprecated('Use humidity property instead.')
|
|
166
|
-
def humidity_level(self) -> int | None:
|
|
167
|
-
"""Deprecated function.
|
|
168
|
-
|
|
169
|
-
Returns:
|
|
170
|
-
int | None: Humidity level.
|
|
171
|
-
"""
|
|
172
|
-
return self.humidity
|
|
173
|
-
|
|
174
152
|
@property
|
|
175
153
|
def drying_mode_state(self) -> str | None:
|
|
176
154
|
"""Return the drying mode state.
|