pyvesync 3.1.0__tar.gz → 3.1.2__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.0/src/pyvesync.egg-info → pyvesync-3.1.2}/PKG-INFO +1 -1
- {pyvesync-3.1.0 → pyvesync-3.1.2}/pyproject.toml +1 -1
- {pyvesync-3.1.0 → pyvesync-3.1.2}/src/pyvesync/auth.py +9 -0
- {pyvesync-3.1.0 → pyvesync-3.1.2}/src/pyvesync/const.py +1 -0
- {pyvesync-3.1.0 → pyvesync-3.1.2}/src/pyvesync/device_map.py +4 -4
- {pyvesync-3.1.0 → pyvesync-3.1.2}/src/pyvesync/devices/vesynchumidifier.py +1 -0
- {pyvesync-3.1.0 → pyvesync-3.1.2}/src/pyvesync/devices/vesyncpurifier.py +1 -0
- {pyvesync-3.1.0 → pyvesync-3.1.2}/src/pyvesync/utils/errors.py +4 -4
- {pyvesync-3.1.0 → pyvesync-3.1.2}/src/pyvesync/vesync.py +35 -1
- {pyvesync-3.1.0 → pyvesync-3.1.2/src/pyvesync.egg-info}/PKG-INFO +1 -1
- {pyvesync-3.1.0 → pyvesync-3.1.2}/LICENSE +0 -0
- {pyvesync-3.1.0 → pyvesync-3.1.2}/MANIFEST.in +0 -0
- {pyvesync-3.1.0 → pyvesync-3.1.2}/README.md +0 -0
- {pyvesync-3.1.0 → pyvesync-3.1.2}/requirements.txt +0 -0
- {pyvesync-3.1.0 → pyvesync-3.1.2}/setup.cfg +0 -0
- {pyvesync-3.1.0 → pyvesync-3.1.2}/setup.py +0 -0
- {pyvesync-3.1.0 → pyvesync-3.1.2}/src/pyvesync/__init__.py +0 -0
- {pyvesync-3.1.0 → pyvesync-3.1.2}/src/pyvesync/base_devices/__init__.py +0 -0
- {pyvesync-3.1.0 → pyvesync-3.1.2}/src/pyvesync/base_devices/bulb_base.py +0 -0
- {pyvesync-3.1.0 → pyvesync-3.1.2}/src/pyvesync/base_devices/fan_base.py +0 -0
- {pyvesync-3.1.0 → pyvesync-3.1.2}/src/pyvesync/base_devices/fryer_base.py +0 -0
- {pyvesync-3.1.0 → pyvesync-3.1.2}/src/pyvesync/base_devices/humidifier_base.py +0 -0
- {pyvesync-3.1.0 → pyvesync-3.1.2}/src/pyvesync/base_devices/outlet_base.py +0 -0
- {pyvesync-3.1.0 → pyvesync-3.1.2}/src/pyvesync/base_devices/purifier_base.py +0 -0
- {pyvesync-3.1.0 → pyvesync-3.1.2}/src/pyvesync/base_devices/switch_base.py +0 -0
- {pyvesync-3.1.0 → pyvesync-3.1.2}/src/pyvesync/base_devices/thermostat_base.py +0 -0
- {pyvesync-3.1.0 → pyvesync-3.1.2}/src/pyvesync/base_devices/vesyncbasedevice.py +0 -0
- {pyvesync-3.1.0 → pyvesync-3.1.2}/src/pyvesync/device_container.py +0 -0
- {pyvesync-3.1.0 → pyvesync-3.1.2}/src/pyvesync/devices/__init__.py +0 -0
- {pyvesync-3.1.0 → pyvesync-3.1.2}/src/pyvesync/devices/vesyncbulb.py +0 -0
- {pyvesync-3.1.0 → pyvesync-3.1.2}/src/pyvesync/devices/vesyncfan.py +0 -0
- {pyvesync-3.1.0 → pyvesync-3.1.2}/src/pyvesync/devices/vesynckitchen.py +0 -0
- {pyvesync-3.1.0 → pyvesync-3.1.2}/src/pyvesync/devices/vesyncoutlet.py +0 -0
- {pyvesync-3.1.0 → pyvesync-3.1.2}/src/pyvesync/devices/vesyncswitch.py +0 -0
- {pyvesync-3.1.0 → pyvesync-3.1.2}/src/pyvesync/devices/vesyncthermostat.py +0 -0
- {pyvesync-3.1.0 → pyvesync-3.1.2}/src/pyvesync/models/__init__.py +0 -0
- {pyvesync-3.1.0 → pyvesync-3.1.2}/src/pyvesync/models/base_models.py +0 -0
- {pyvesync-3.1.0 → pyvesync-3.1.2}/src/pyvesync/models/bulb_models.py +0 -0
- {pyvesync-3.1.0 → pyvesync-3.1.2}/src/pyvesync/models/bypass_models.py +0 -0
- {pyvesync-3.1.0 → pyvesync-3.1.2}/src/pyvesync/models/fan_models.py +0 -0
- {pyvesync-3.1.0 → pyvesync-3.1.2}/src/pyvesync/models/fryer_models.py +0 -0
- {pyvesync-3.1.0 → pyvesync-3.1.2}/src/pyvesync/models/home_models.py +0 -0
- {pyvesync-3.1.0 → pyvesync-3.1.2}/src/pyvesync/models/humidifier_models.py +0 -0
- {pyvesync-3.1.0 → pyvesync-3.1.2}/src/pyvesync/models/outlet_models.py +0 -0
- {pyvesync-3.1.0 → pyvesync-3.1.2}/src/pyvesync/models/purifier_models.py +0 -0
- {pyvesync-3.1.0 → pyvesync-3.1.2}/src/pyvesync/models/switch_models.py +0 -0
- {pyvesync-3.1.0 → pyvesync-3.1.2}/src/pyvesync/models/thermostat_models.py +0 -0
- {pyvesync-3.1.0 → pyvesync-3.1.2}/src/pyvesync/models/vesync_models.py +0 -0
- {pyvesync-3.1.0 → pyvesync-3.1.2}/src/pyvesync/utils/__init__.py +0 -0
- {pyvesync-3.1.0 → pyvesync-3.1.2}/src/pyvesync/utils/colors.py +0 -0
- {pyvesync-3.1.0 → pyvesync-3.1.2}/src/pyvesync/utils/device_mixins.py +0 -0
- {pyvesync-3.1.0 → pyvesync-3.1.2}/src/pyvesync/utils/enum_utils.py +0 -0
- {pyvesync-3.1.0 → pyvesync-3.1.2}/src/pyvesync/utils/helpers.py +0 -0
- {pyvesync-3.1.0 → pyvesync-3.1.2}/src/pyvesync/utils/logs.py +0 -0
- {pyvesync-3.1.0 → pyvesync-3.1.2}/src/pyvesync/vesynchome.py +0 -0
- {pyvesync-3.1.0 → pyvesync-3.1.2}/src/pyvesync.egg-info/SOURCES.txt +0 -0
- {pyvesync-3.1.0 → pyvesync-3.1.2}/src/pyvesync.egg-info/dependency_links.txt +0 -0
- {pyvesync-3.1.0 → pyvesync-3.1.2}/src/pyvesync.egg-info/requires.txt +0 -0
- {pyvesync-3.1.0 → pyvesync-3.1.2}/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.2
|
|
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>
|
|
@@ -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.2"
|
|
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"
|
|
@@ -145,6 +145,15 @@ class VeSyncAuth:
|
|
|
145
145
|
self._country_code = country_code.upper()
|
|
146
146
|
self._current_region = region
|
|
147
147
|
|
|
148
|
+
async def reauthenticate(self) -> bool:
|
|
149
|
+
"""Re-authenticate using stored username and password.
|
|
150
|
+
|
|
151
|
+
Returns:
|
|
152
|
+
True if re-authentication successful, False otherwise
|
|
153
|
+
"""
|
|
154
|
+
self.clear_credentials()
|
|
155
|
+
return await self.login()
|
|
156
|
+
|
|
148
157
|
async def load_credentials_from_file(
|
|
149
158
|
self, file_path: str | Path | None = None
|
|
150
159
|
) -> bool:
|
|
@@ -34,6 +34,7 @@ from uuid import uuid4
|
|
|
34
34
|
|
|
35
35
|
from pyvesync.utils.enum_utils import IntEnumMixin
|
|
36
36
|
|
|
37
|
+
MAX_API_REAUTH_RETRIES = 3
|
|
37
38
|
DEFAULT_LANGUAGE = 'en'
|
|
38
39
|
API_BASE_URL = None # Global URL (non-EU regions): "https://smartapi.vesync.com"
|
|
39
40
|
# If device is out of reach, the cloud api sends a timeout response after 7 seconds,
|
|
@@ -750,7 +750,7 @@ purifier_modules: list[PurifierMap] = [
|
|
|
750
750
|
PurifierAutoPreference.QUIET,
|
|
751
751
|
],
|
|
752
752
|
features=[PurifierFeatures.AIR_QUALITY],
|
|
753
|
-
fan_levels=list(range(1,
|
|
753
|
+
fan_levels=list(range(1, 4)),
|
|
754
754
|
device_alias='Core 300S',
|
|
755
755
|
model_display='Core 300S',
|
|
756
756
|
model_name='Core 300S',
|
|
@@ -790,7 +790,7 @@ purifier_modules: list[PurifierMap] = [
|
|
|
790
790
|
),
|
|
791
791
|
PurifierMap(
|
|
792
792
|
class_name='VeSyncAir131',
|
|
793
|
-
dev_types=['LV-PUR131S', 'LV-RH131S'],
|
|
793
|
+
dev_types=['LV-PUR131S', 'LV-RH131S', 'LV-RH131S-WM'],
|
|
794
794
|
modes=[PurifierModes.SLEEP, PurifierModes.MANUAL, PurifierModes.AUTO],
|
|
795
795
|
features=[PurifierFeatures.AIR_QUALITY],
|
|
796
796
|
fan_levels=list(range(1, 4)),
|
|
@@ -888,7 +888,7 @@ purifier_modules: list[PurifierMap] = [
|
|
|
888
888
|
setup_entry='EL551S',
|
|
889
889
|
),
|
|
890
890
|
PurifierMap(
|
|
891
|
-
class_name='
|
|
891
|
+
class_name='VeSyncAirSprout',
|
|
892
892
|
dev_types=[
|
|
893
893
|
'LAP-B851S-WEU',
|
|
894
894
|
'LAP-B851S-WNA',
|
|
@@ -925,7 +925,7 @@ objects for purifier devices."""
|
|
|
925
925
|
fan_modules: list[FanMap] = [
|
|
926
926
|
FanMap(
|
|
927
927
|
class_name='VeSyncTowerFan',
|
|
928
|
-
dev_types=['LTF-F422S-KEU', 'LTF-F422S-WUSR', 'LTF-
|
|
928
|
+
dev_types=['LTF-F422S-KEU', 'LTF-F422S-WUSR', 'LTF-F422S-WJP', 'LTF-F422S-WUS'],
|
|
929
929
|
modes=[
|
|
930
930
|
FanModes.NORMAL,
|
|
931
931
|
FanModes.TURBO,
|
|
@@ -665,6 +665,7 @@ class VeSyncHumid1000S(VeSyncHumid200300S):
|
|
|
665
665
|
self.state.device_status = DeviceStatus.from_int(resp_model.powerSwitch)
|
|
666
666
|
self.state.connection_status = ConnectionStatus.ONLINE
|
|
667
667
|
self.state.mode = resp_model.workMode
|
|
668
|
+
self.state.humidity = resp_model.humidity
|
|
668
669
|
self.state.auto_target_humidity = resp_model.targetHumidity
|
|
669
670
|
self.state.mist_level = resp_model.mistLevel
|
|
670
671
|
self.state.mist_virtual_level = resp_model.virtualLevel
|
|
@@ -123,6 +123,7 @@ class VeSyncAirBypass(BypassV2Mixin, VeSyncPurifier):
|
|
|
123
123
|
result (InnerPurifierResult): Data model for inner result in purifier
|
|
124
124
|
details response.
|
|
125
125
|
"""
|
|
126
|
+
self.state.connection_status = ConnectionStatus.ONLINE
|
|
126
127
|
self.state.device_status = DeviceStatus.ON if result.enabled else DeviceStatus.OFF
|
|
127
128
|
self.state.filter_life = result.filter_life or 0
|
|
128
129
|
self.state.mode = result.mode
|
|
@@ -799,9 +799,11 @@ class VeSyncLoginError(VeSyncError):
|
|
|
799
799
|
class VeSyncTokenError(VeSyncError):
|
|
800
800
|
"""Exception raised for VeSync API authentication errors."""
|
|
801
801
|
|
|
802
|
-
def __init__(self) -> None:
|
|
802
|
+
def __init__(self, msg: str | None = None) -> None:
|
|
803
803
|
"""Initialize the exception with a message."""
|
|
804
|
-
super().__init__(
|
|
804
|
+
super().__init__(
|
|
805
|
+
f'Token expired or invalid - {msg if msg else "Re-authentication required"}'
|
|
806
|
+
)
|
|
805
807
|
|
|
806
808
|
|
|
807
809
|
class VeSyncServerError(VeSyncError):
|
|
@@ -857,8 +859,6 @@ def raise_api_errors(error_info: ResponseInfo) -> None:
|
|
|
857
859
|
raise VeSyncRateLimitError
|
|
858
860
|
case ErrorTypes.AUTHENTICATION:
|
|
859
861
|
raise VeSyncLoginError(error_info.message)
|
|
860
|
-
case ErrorTypes.TOKEN_ERROR:
|
|
861
|
-
raise VeSyncTokenError
|
|
862
862
|
case ErrorTypes.SERVER_ERROR:
|
|
863
863
|
msg = (
|
|
864
864
|
f'{error_info.message} - '
|
|
@@ -16,6 +16,7 @@ from pyvesync.auth import VeSyncAuth
|
|
|
16
16
|
from pyvesync.const import (
|
|
17
17
|
DEFAULT_REGION,
|
|
18
18
|
DEFAULT_TZ,
|
|
19
|
+
MAX_API_REAUTH_RETRIES,
|
|
19
20
|
REGION_API_MAP,
|
|
20
21
|
STATUS_OK,
|
|
21
22
|
)
|
|
@@ -34,6 +35,7 @@ from pyvesync.utils.errors import (
|
|
|
34
35
|
VeSyncAPIStatusCodeError,
|
|
35
36
|
VeSyncError,
|
|
36
37
|
VeSyncServerError,
|
|
38
|
+
VeSyncTokenError,
|
|
37
39
|
raise_api_errors,
|
|
38
40
|
)
|
|
39
41
|
from pyvesync.utils.helpers import Helpers
|
|
@@ -47,6 +49,7 @@ class VeSync: # pylint: disable=function-redefined
|
|
|
47
49
|
|
|
48
50
|
__slots__ = (
|
|
49
51
|
'__weakref__',
|
|
52
|
+
'_api_attempts',
|
|
50
53
|
'_auth',
|
|
51
54
|
'_close_session',
|
|
52
55
|
'_debug',
|
|
@@ -130,6 +133,7 @@ class VeSync: # pylint: disable=function-redefined
|
|
|
130
133
|
Object to store device state information
|
|
131
134
|
"""
|
|
132
135
|
self.session = session
|
|
136
|
+
self._api_attempts = 0
|
|
133
137
|
self._close_session = False
|
|
134
138
|
self._debug = debug
|
|
135
139
|
self.redact = redact
|
|
@@ -381,6 +385,7 @@ class VeSync: # pylint: disable=function-redefined
|
|
|
381
385
|
VeSyncAPIResponseError: If API response is invalid.
|
|
382
386
|
VeSyncServerError: If server returns an error.
|
|
383
387
|
"""
|
|
388
|
+
self.enabled = False
|
|
384
389
|
success = await self._auth.login()
|
|
385
390
|
if success:
|
|
386
391
|
self.enabled = True
|
|
@@ -403,6 +408,9 @@ class VeSync: # pylint: disable=function-redefined
|
|
|
403
408
|
async def update_all_devices(self) -> None:
|
|
404
409
|
"""Run `get_details()` for each device and update state."""
|
|
405
410
|
logger.debug('Start updating the device details one by one')
|
|
411
|
+
if len(self._device_container) == 0:
|
|
412
|
+
logger.error('No devices to update')
|
|
413
|
+
return
|
|
406
414
|
update_tasks: list[asyncio.Task] = [
|
|
407
415
|
asyncio.create_task(device.update()) for device in self._device_container
|
|
408
416
|
]
|
|
@@ -410,7 +418,7 @@ class VeSync: # pylint: disable=function-redefined
|
|
|
410
418
|
for task in done:
|
|
411
419
|
exc = task.exception()
|
|
412
420
|
if exc is not None and isinstance(exc, VeSyncError):
|
|
413
|
-
logger.
|
|
421
|
+
logger.error('Error updating device: %s', exc)
|
|
414
422
|
|
|
415
423
|
async def __aenter__(self) -> Self:
|
|
416
424
|
"""Asynchronous context manager enter."""
|
|
@@ -424,6 +432,24 @@ class VeSync: # pylint: disable=function-redefined
|
|
|
424
432
|
return
|
|
425
433
|
logger.debug('Session not closed, exiting context manager')
|
|
426
434
|
|
|
435
|
+
async def _reauthenticate(self) -> bool:
|
|
436
|
+
"""Re-authenticate using stored username and password.
|
|
437
|
+
|
|
438
|
+
Returns:
|
|
439
|
+
True if re-authentication successful, False otherwise
|
|
440
|
+
"""
|
|
441
|
+
self.enabled = False
|
|
442
|
+
self._api_attempts += 1
|
|
443
|
+
if self._api_attempts >= MAX_API_REAUTH_RETRIES:
|
|
444
|
+
logger.error('Max API re-authentication attempts reached')
|
|
445
|
+
raise VeSyncTokenError
|
|
446
|
+
success = await self.auth.reauthenticate()
|
|
447
|
+
if success:
|
|
448
|
+
self.enabled = True
|
|
449
|
+
self._api_attempts = 0
|
|
450
|
+
return True
|
|
451
|
+
return await self.auth.reauthenticate()
|
|
452
|
+
|
|
427
453
|
async def async_call_api(
|
|
428
454
|
self,
|
|
429
455
|
api: str,
|
|
@@ -460,6 +486,7 @@ class VeSync: # pylint: disable=function-redefined
|
|
|
460
486
|
if self.session is None:
|
|
461
487
|
self.session = ClientSession()
|
|
462
488
|
self._close_session = True
|
|
489
|
+
self._api_attempts += 1
|
|
463
490
|
response = None
|
|
464
491
|
status_code = None
|
|
465
492
|
if isinstance(json_object, DataClassORJSONMixin):
|
|
@@ -491,6 +518,13 @@ class VeSync: # pylint: disable=function-redefined
|
|
|
491
518
|
resp_dict = Helpers.try_json_loads(resp_bytes)
|
|
492
519
|
if isinstance(resp_dict, dict):
|
|
493
520
|
error_info = ErrorCodes.get_error_info(resp_dict.get('code'))
|
|
521
|
+
if error_info.error_type == ErrorTypes.TOKEN_ERROR:
|
|
522
|
+
if await self._reauthenticate():
|
|
523
|
+
self.enabled = True
|
|
524
|
+
return await self.async_call_api(
|
|
525
|
+
api, method, json_object, headers
|
|
526
|
+
)
|
|
527
|
+
raise VeSyncTokenError
|
|
494
528
|
if resp_dict.get('msg') is not None:
|
|
495
529
|
error_info.message = f'{error_info.message} ({resp_dict["msg"]})'
|
|
496
530
|
raise_api_errors(error_info)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pyvesync
|
|
3
|
-
Version: 3.1.
|
|
3
|
+
Version: 3.1.2
|
|
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>
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|