pyvesync 3.1.2__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.
Files changed (59) hide show
  1. {pyvesync-3.1.2/src/pyvesync.egg-info → pyvesync-3.1.4}/PKG-INFO +47 -41
  2. {pyvesync-3.1.2 → pyvesync-3.1.4}/README.md +46 -40
  3. {pyvesync-3.1.2 → pyvesync-3.1.4}/pyproject.toml +1 -1
  4. {pyvesync-3.1.2 → pyvesync-3.1.4}/src/pyvesync/auth.py +16 -9
  5. {pyvesync-3.1.2 → pyvesync-3.1.4}/src/pyvesync/base_devices/fan_base.py +10 -10
  6. {pyvesync-3.1.2 → pyvesync-3.1.4}/src/pyvesync/base_devices/humidifier_base.py +0 -22
  7. {pyvesync-3.1.2 → pyvesync-3.1.4}/src/pyvesync/base_devices/outlet_base.py +31 -16
  8. {pyvesync-3.1.2 → pyvesync-3.1.4}/src/pyvesync/base_devices/purifier_base.py +10 -4
  9. {pyvesync-3.1.2 → pyvesync-3.1.4}/src/pyvesync/const.py +36 -2
  10. {pyvesync-3.1.2 → pyvesync-3.1.4}/src/pyvesync/device_container.py +5 -0
  11. {pyvesync-3.1.2 → pyvesync-3.1.4}/src/pyvesync/device_map.py +46 -13
  12. {pyvesync-3.1.2 → pyvesync-3.1.4}/src/pyvesync/devices/vesyncbulb.py +16 -14
  13. {pyvesync-3.1.2 → pyvesync-3.1.4}/src/pyvesync/devices/vesyncfan.py +5 -4
  14. {pyvesync-3.1.2 → pyvesync-3.1.4}/src/pyvesync/devices/vesynchumidifier.py +22 -16
  15. {pyvesync-3.1.2 → pyvesync-3.1.4}/src/pyvesync/devices/vesynckitchen.py +2 -3
  16. {pyvesync-3.1.2 → pyvesync-3.1.4}/src/pyvesync/devices/vesyncoutlet.py +258 -101
  17. {pyvesync-3.1.2 → pyvesync-3.1.4}/src/pyvesync/devices/vesyncpurifier.py +12 -21
  18. {pyvesync-3.1.2 → pyvesync-3.1.4}/src/pyvesync/models/home_models.py +1 -1
  19. {pyvesync-3.1.2 → pyvesync-3.1.4}/src/pyvesync/models/humidifier_models.py +9 -0
  20. {pyvesync-3.1.2 → pyvesync-3.1.4}/src/pyvesync/models/outlet_models.py +63 -11
  21. {pyvesync-3.1.2 → pyvesync-3.1.4}/src/pyvesync/models/purifier_models.py +1 -1
  22. {pyvesync-3.1.2 → pyvesync-3.1.4}/src/pyvesync/utils/device_mixins.py +9 -76
  23. {pyvesync-3.1.2 → pyvesync-3.1.4}/src/pyvesync/utils/errors.py +17 -8
  24. {pyvesync-3.1.2 → pyvesync-3.1.4}/src/pyvesync/utils/helpers.py +31 -52
  25. {pyvesync-3.1.2 → pyvesync-3.1.4}/src/pyvesync/utils/logs.py +232 -139
  26. {pyvesync-3.1.2 → pyvesync-3.1.4}/src/pyvesync/vesync.py +77 -80
  27. {pyvesync-3.1.2 → pyvesync-3.1.4/src/pyvesync.egg-info}/PKG-INFO +47 -41
  28. {pyvesync-3.1.2 → pyvesync-3.1.4}/LICENSE +0 -0
  29. {pyvesync-3.1.2 → pyvesync-3.1.4}/MANIFEST.in +0 -0
  30. {pyvesync-3.1.2 → pyvesync-3.1.4}/requirements.txt +0 -0
  31. {pyvesync-3.1.2 → pyvesync-3.1.4}/setup.cfg +0 -0
  32. {pyvesync-3.1.2 → pyvesync-3.1.4}/setup.py +0 -0
  33. {pyvesync-3.1.2 → pyvesync-3.1.4}/src/pyvesync/__init__.py +0 -0
  34. {pyvesync-3.1.2 → pyvesync-3.1.4}/src/pyvesync/base_devices/__init__.py +0 -0
  35. {pyvesync-3.1.2 → pyvesync-3.1.4}/src/pyvesync/base_devices/bulb_base.py +0 -0
  36. {pyvesync-3.1.2 → pyvesync-3.1.4}/src/pyvesync/base_devices/fryer_base.py +0 -0
  37. {pyvesync-3.1.2 → pyvesync-3.1.4}/src/pyvesync/base_devices/switch_base.py +0 -0
  38. {pyvesync-3.1.2 → pyvesync-3.1.4}/src/pyvesync/base_devices/thermostat_base.py +0 -0
  39. {pyvesync-3.1.2 → pyvesync-3.1.4}/src/pyvesync/base_devices/vesyncbasedevice.py +0 -0
  40. {pyvesync-3.1.2 → pyvesync-3.1.4}/src/pyvesync/devices/__init__.py +0 -0
  41. {pyvesync-3.1.2 → pyvesync-3.1.4}/src/pyvesync/devices/vesyncswitch.py +0 -0
  42. {pyvesync-3.1.2 → pyvesync-3.1.4}/src/pyvesync/devices/vesyncthermostat.py +0 -0
  43. {pyvesync-3.1.2 → pyvesync-3.1.4}/src/pyvesync/models/__init__.py +0 -0
  44. {pyvesync-3.1.2 → pyvesync-3.1.4}/src/pyvesync/models/base_models.py +0 -0
  45. {pyvesync-3.1.2 → pyvesync-3.1.4}/src/pyvesync/models/bulb_models.py +0 -0
  46. {pyvesync-3.1.2 → pyvesync-3.1.4}/src/pyvesync/models/bypass_models.py +0 -0
  47. {pyvesync-3.1.2 → pyvesync-3.1.4}/src/pyvesync/models/fan_models.py +0 -0
  48. {pyvesync-3.1.2 → pyvesync-3.1.4}/src/pyvesync/models/fryer_models.py +0 -0
  49. {pyvesync-3.1.2 → pyvesync-3.1.4}/src/pyvesync/models/switch_models.py +0 -0
  50. {pyvesync-3.1.2 → pyvesync-3.1.4}/src/pyvesync/models/thermostat_models.py +0 -0
  51. {pyvesync-3.1.2 → pyvesync-3.1.4}/src/pyvesync/models/vesync_models.py +0 -0
  52. {pyvesync-3.1.2 → pyvesync-3.1.4}/src/pyvesync/utils/__init__.py +0 -0
  53. {pyvesync-3.1.2 → pyvesync-3.1.4}/src/pyvesync/utils/colors.py +0 -0
  54. {pyvesync-3.1.2 → pyvesync-3.1.4}/src/pyvesync/utils/enum_utils.py +0 -0
  55. {pyvesync-3.1.2 → pyvesync-3.1.4}/src/pyvesync/vesynchome.py +0 -0
  56. {pyvesync-3.1.2 → pyvesync-3.1.4}/src/pyvesync.egg-info/SOURCES.txt +0 -0
  57. {pyvesync-3.1.2 → pyvesync-3.1.4}/src/pyvesync.egg-info/dependency_links.txt +0 -0
  58. {pyvesync-3.1.2 → pyvesync-3.1.4}/src/pyvesync.egg-info/requires.txt +0 -0
  59. {pyvesync-3.1.2 → 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.2
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, debug=False, redact=True, session=None)
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, debug=False, redact=True, session=None)
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 respective lists in the instantiated `VeSync` class:
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 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.
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.py`
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
- logger = logging.getLogger(__name__)
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, debug=True, redact=True) as manager:
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
- 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.
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, debug=False, redact=True, session=None)
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, debug=False, redact=True, session=None)
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 respective lists in the instantiated `VeSync` class:
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 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.
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.py`
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
- logger = logging.getLogger(__name__)
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, debug=True, redact=True) as manager:
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
- 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.
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.2"
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.
@@ -174,7 +172,7 @@ class VeSyncAuth:
174
172
  else:
175
173
  file_path_object = Path(file_path)
176
174
  if not file_path_object or not file_path_object.exists():
177
- logger.debug('No token file found to load credentials')
175
+ logger.debug('Credentials file not found: %s', file_path_object)
178
176
  return False
179
177
  try:
180
178
  data = await asyncio.to_thread(
@@ -184,7 +182,7 @@ class VeSyncAuth:
184
182
  self._token = data['token']
185
183
  self._account_id = data['account_id']
186
184
  self._country_code = data['country_code'].upper()
187
- self._current_region = data['current_region']
185
+ self._current_region = data['current_region'].upper()
188
186
  logger.debug('Credentials loaded from file: %s', file_path)
189
187
  except orjson.JSONDecodeError as exc:
190
188
  logger.warning('Failed to load credentials from file: %s', exc)
@@ -196,17 +194,26 @@ class VeSyncAuth:
196
194
  self.manager.enabled = True
197
195
  return True
198
196
 
199
- def output_credentials(self) -> str | None:
200
- """Output current credentials as JSON string."""
197
+ def output_credentials_dict(self) -> dict[str, str] | None:
198
+ """Output current credentials as a dictionary."""
201
199
  if not self.is_authenticated:
202
200
  logger.debug('No credentials to output, not authenticated')
203
201
  return None
204
- credentials = {
205
- 'token': self._token,
206
- 'account_id': self._account_id,
202
+ return {
203
+ 'token': self._token or '',
204
+ 'account_id': self._account_id or '',
207
205
  'country_code': self._country_code,
208
206
  'current_region': self._current_region,
209
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
210
217
  try:
211
218
  return orjson.dumps(credentials).decode('utf-8')
212
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 Purifiers.
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 Purifier State.
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 Purifier Mode.
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 Purifier Fan Speed.
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 Purifier to Auto Mode.
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 Purifier to Advanced Sleep Mode.
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 Purifier to Sleep Mode.
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 Purifier to Manual Mode - Normal Mode.
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 Purifier to Normal Mode.
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 Purifier to Turbo Mode.
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.