pyvesync 3.2.1__tar.gz → 3.3.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.2.1/src/pyvesync.egg-info → pyvesync-3.3.2}/PKG-INFO +1 -1
- {pyvesync-3.2.1 → pyvesync-3.3.2}/pyproject.toml +1 -1
- {pyvesync-3.2.1 → pyvesync-3.3.2}/src/pyvesync/base_devices/humidifier_base.py +32 -0
- {pyvesync-3.2.1 → pyvesync-3.3.2}/src/pyvesync/base_devices/purifier_base.py +5 -29
- {pyvesync-3.2.1 → pyvesync-3.3.2}/src/pyvesync/device_map.py +14 -3
- {pyvesync-3.2.1 → pyvesync-3.3.2}/src/pyvesync/devices/vesynchumidifier.py +65 -18
- {pyvesync-3.2.1 → pyvesync-3.3.2}/src/pyvesync/devices/vesyncoutlet.py +3 -2
- {pyvesync-3.2.1 → pyvesync-3.3.2}/src/pyvesync/devices/vesyncpurifier.py +149 -0
- {pyvesync-3.2.1 → pyvesync-3.3.2}/src/pyvesync/models/humidifier_models.py +2 -2
- {pyvesync-3.2.1 → pyvesync-3.3.2}/src/pyvesync/models/purifier_models.py +24 -0
- {pyvesync-3.2.1 → pyvesync-3.3.2}/src/pyvesync/utils/errors.py +9 -9
- {pyvesync-3.2.1 → pyvesync-3.3.2/src/pyvesync.egg-info}/PKG-INFO +1 -1
- {pyvesync-3.2.1 → pyvesync-3.3.2}/LICENSE +0 -0
- {pyvesync-3.2.1 → pyvesync-3.3.2}/MANIFEST.in +0 -0
- {pyvesync-3.2.1 → pyvesync-3.3.2}/README.md +0 -0
- {pyvesync-3.2.1 → pyvesync-3.3.2}/requirements.txt +0 -0
- {pyvesync-3.2.1 → pyvesync-3.3.2}/setup.cfg +0 -0
- {pyvesync-3.2.1 → pyvesync-3.3.2}/setup.py +0 -0
- {pyvesync-3.2.1 → pyvesync-3.3.2}/src/pyvesync/__init__.py +0 -0
- {pyvesync-3.2.1 → pyvesync-3.3.2}/src/pyvesync/auth.py +0 -0
- {pyvesync-3.2.1 → pyvesync-3.3.2}/src/pyvesync/base_devices/__init__.py +0 -0
- {pyvesync-3.2.1 → pyvesync-3.3.2}/src/pyvesync/base_devices/bulb_base.py +0 -0
- {pyvesync-3.2.1 → pyvesync-3.3.2}/src/pyvesync/base_devices/fan_base.py +0 -0
- {pyvesync-3.2.1 → pyvesync-3.3.2}/src/pyvesync/base_devices/fryer_base.py +0 -0
- {pyvesync-3.2.1 → pyvesync-3.3.2}/src/pyvesync/base_devices/outlet_base.py +0 -0
- {pyvesync-3.2.1 → pyvesync-3.3.2}/src/pyvesync/base_devices/switch_base.py +0 -0
- {pyvesync-3.2.1 → pyvesync-3.3.2}/src/pyvesync/base_devices/thermostat_base.py +0 -0
- {pyvesync-3.2.1 → pyvesync-3.3.2}/src/pyvesync/base_devices/vesyncbasedevice.py +0 -0
- {pyvesync-3.2.1 → pyvesync-3.3.2}/src/pyvesync/const.py +0 -0
- {pyvesync-3.2.1 → pyvesync-3.3.2}/src/pyvesync/device_container.py +0 -0
- {pyvesync-3.2.1 → pyvesync-3.3.2}/src/pyvesync/devices/__init__.py +0 -0
- {pyvesync-3.2.1 → pyvesync-3.3.2}/src/pyvesync/devices/vesyncbulb.py +0 -0
- {pyvesync-3.2.1 → pyvesync-3.3.2}/src/pyvesync/devices/vesyncfan.py +0 -0
- {pyvesync-3.2.1 → pyvesync-3.3.2}/src/pyvesync/devices/vesynckitchen.py +0 -0
- {pyvesync-3.2.1 → pyvesync-3.3.2}/src/pyvesync/devices/vesyncswitch.py +0 -0
- {pyvesync-3.2.1 → pyvesync-3.3.2}/src/pyvesync/devices/vesyncthermostat.py +0 -0
- {pyvesync-3.2.1 → pyvesync-3.3.2}/src/pyvesync/models/__init__.py +0 -0
- {pyvesync-3.2.1 → pyvesync-3.3.2}/src/pyvesync/models/base_models.py +0 -0
- {pyvesync-3.2.1 → pyvesync-3.3.2}/src/pyvesync/models/bulb_models.py +0 -0
- {pyvesync-3.2.1 → pyvesync-3.3.2}/src/pyvesync/models/bypass_models.py +0 -0
- {pyvesync-3.2.1 → pyvesync-3.3.2}/src/pyvesync/models/fan_models.py +0 -0
- {pyvesync-3.2.1 → pyvesync-3.3.2}/src/pyvesync/models/fryer_models.py +0 -0
- {pyvesync-3.2.1 → pyvesync-3.3.2}/src/pyvesync/models/home_models.py +0 -0
- {pyvesync-3.2.1 → pyvesync-3.3.2}/src/pyvesync/models/outlet_models.py +0 -0
- {pyvesync-3.2.1 → pyvesync-3.3.2}/src/pyvesync/models/switch_models.py +0 -0
- {pyvesync-3.2.1 → pyvesync-3.3.2}/src/pyvesync/models/thermostat_models.py +0 -0
- {pyvesync-3.2.1 → pyvesync-3.3.2}/src/pyvesync/models/vesync_models.py +0 -0
- {pyvesync-3.2.1 → pyvesync-3.3.2}/src/pyvesync/utils/__init__.py +0 -0
- {pyvesync-3.2.1 → pyvesync-3.3.2}/src/pyvesync/utils/colors.py +0 -0
- {pyvesync-3.2.1 → pyvesync-3.3.2}/src/pyvesync/utils/device_mixins.py +0 -0
- {pyvesync-3.2.1 → pyvesync-3.3.2}/src/pyvesync/utils/enum_utils.py +0 -0
- {pyvesync-3.2.1 → pyvesync-3.3.2}/src/pyvesync/utils/helpers.py +0 -0
- {pyvesync-3.2.1 → pyvesync-3.3.2}/src/pyvesync/utils/logs.py +0 -0
- {pyvesync-3.2.1 → pyvesync-3.3.2}/src/pyvesync/vesync.py +0 -0
- {pyvesync-3.2.1 → pyvesync-3.3.2}/src/pyvesync/vesynchome.py +0 -0
- {pyvesync-3.2.1 → pyvesync-3.3.2}/src/pyvesync.egg-info/SOURCES.txt +0 -0
- {pyvesync-3.2.1 → pyvesync-3.3.2}/src/pyvesync.egg-info/dependency_links.txt +0 -0
- {pyvesync-3.2.1 → pyvesync-3.3.2}/src/pyvesync.egg-info/requires.txt +0 -0
- {pyvesync-3.2.1 → pyvesync-3.3.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.2
|
|
3
|
+
Version: 3.3.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.2
|
|
7
|
+
version = "3.3.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"
|
|
@@ -416,6 +416,22 @@ class VeSyncHumidifier(VeSyncBaseToggleDevice):
|
|
|
416
416
|
logger.error('Nightlight brightness has not been configured.')
|
|
417
417
|
return False
|
|
418
418
|
|
|
419
|
+
async def toggle_nightlight(self, toggle: bool | None = None) -> bool:
|
|
420
|
+
"""Toggle night light on/off.
|
|
421
|
+
|
|
422
|
+
Args:
|
|
423
|
+
toggle (bool): True to turn on night light, False to turn off.
|
|
424
|
+
|
|
425
|
+
Returns:
|
|
426
|
+
bool: Success of request.
|
|
427
|
+
"""
|
|
428
|
+
del toggle
|
|
429
|
+
if not self.supports_nightlight:
|
|
430
|
+
logger.error('Nightlight is not supported for this device.')
|
|
431
|
+
return False
|
|
432
|
+
logger.error('Nightlight has not been configured.')
|
|
433
|
+
return False
|
|
434
|
+
|
|
419
435
|
async def set_warm_level(self, warm_level: int) -> bool:
|
|
420
436
|
"""Set Humidifier Warm Level.
|
|
421
437
|
|
|
@@ -432,6 +448,22 @@ class VeSyncHumidifier(VeSyncBaseToggleDevice):
|
|
|
432
448
|
logger.error('Warm level is not supported for this device.')
|
|
433
449
|
return False
|
|
434
450
|
|
|
451
|
+
async def turn_on_nightlight(self) -> bool:
|
|
452
|
+
"""Turn on night light.
|
|
453
|
+
|
|
454
|
+
Returns:
|
|
455
|
+
bool: Success of request.
|
|
456
|
+
"""
|
|
457
|
+
return await self.toggle_nightlight(True)
|
|
458
|
+
|
|
459
|
+
async def turn_off_nightlight(self) -> bool:
|
|
460
|
+
"""Turn off night light.
|
|
461
|
+
|
|
462
|
+
Returns:
|
|
463
|
+
bool: Success of request.
|
|
464
|
+
"""
|
|
465
|
+
return await self.toggle_nightlight(False)
|
|
466
|
+
|
|
435
467
|
async def toggle_drying_mode(self, toggle: bool | None = None) -> bool:
|
|
436
468
|
"""enable/disable drying filters after turning off."""
|
|
437
469
|
del toggle
|
|
@@ -109,7 +109,7 @@ class PurifierState(DeviceState):
|
|
|
109
109
|
self.auto_preference_type: str | None = None
|
|
110
110
|
self.auto_room_size: int | None = None
|
|
111
111
|
self._air_quality_level: AirQualityLevel | None = None
|
|
112
|
-
self.child_lock: bool =
|
|
112
|
+
self.child_lock: bool | None = None
|
|
113
113
|
self.filter_open_state: bool = False
|
|
114
114
|
self.display_status: str | None = None
|
|
115
115
|
self.display_set_status: str | None = None
|
|
@@ -301,6 +301,10 @@ class VeSyncPurifier(VeSyncBaseToggleDevice):
|
|
|
301
301
|
bool: True if successful, False otherwise.
|
|
302
302
|
"""
|
|
303
303
|
del mode
|
|
304
|
+
if not self.supports_nightlight:
|
|
305
|
+
logger.error('Nightlight not supported for this device.')
|
|
306
|
+
else:
|
|
307
|
+
logger.error('Nightlight not configured for this device.')
|
|
304
308
|
return False
|
|
305
309
|
|
|
306
310
|
async def set_nightlight_dim(self) -> bool:
|
|
@@ -492,34 +496,6 @@ class VeSyncPurifier(VeSyncBaseToggleDevice):
|
|
|
492
496
|
"""
|
|
493
497
|
return await self.toggle_child_lock(True)
|
|
494
498
|
|
|
495
|
-
@deprecated('Use `turn_off_child_lock()` instead.')
|
|
496
|
-
async def child_lock_off(self) -> bool:
|
|
497
|
-
"""Turn off child lock (display lock).
|
|
498
|
-
|
|
499
|
-
This has been deprecated, use `turn_off_child_lock()` instead.
|
|
500
|
-
"""
|
|
501
|
-
return await self.toggle_child_lock(False)
|
|
502
|
-
|
|
503
|
-
@property
|
|
504
|
-
@deprecated('Use self.state.child_lock instead.')
|
|
505
|
-
def child_lock(self) -> bool:
|
|
506
|
-
"""Get child lock state.
|
|
507
|
-
|
|
508
|
-
Returns:
|
|
509
|
-
bool : True if child lock is enabled, False if not.
|
|
510
|
-
"""
|
|
511
|
-
return self.state.child_lock
|
|
512
|
-
|
|
513
|
-
@property
|
|
514
|
-
@deprecated('Use self.state.nightlight_status instead.')
|
|
515
|
-
def night_light(self) -> str | None:
|
|
516
|
-
"""Get night light state.
|
|
517
|
-
|
|
518
|
-
Returns:
|
|
519
|
-
str : Night light state (on, dim, off)
|
|
520
|
-
"""
|
|
521
|
-
return self.state.nightlight_status
|
|
522
|
-
|
|
523
499
|
@deprecated('Use turn_on_light_detection() instead.')
|
|
524
500
|
async def set_light_detection_on(self) -> bool:
|
|
525
501
|
"""Turn on light detection feature."""
|
|
@@ -697,7 +697,7 @@ humidifier_modules = [
|
|
|
697
697
|
),
|
|
698
698
|
HumidifierMap(
|
|
699
699
|
class_name='VeSyncHumid1000S',
|
|
700
|
-
dev_types=['LUH-M101S-WUS', 'LUH-M101S-
|
|
700
|
+
dev_types=['LUH-M101S-WUS', 'LUH-M101S-WUSR'],
|
|
701
701
|
features=[],
|
|
702
702
|
mist_modes={
|
|
703
703
|
HumidifierModes.AUTO: 'auto',
|
|
@@ -823,15 +823,26 @@ purifier_modules: list[PurifierMap] = [
|
|
|
823
823
|
),
|
|
824
824
|
PurifierMap(
|
|
825
825
|
class_name='VeSyncAir131',
|
|
826
|
-
dev_types=['LV-PUR131S'
|
|
826
|
+
dev_types=['LV-PUR131S'],
|
|
827
827
|
modes=[PurifierModes.SLEEP, PurifierModes.MANUAL, PurifierModes.AUTO],
|
|
828
828
|
features=[PurifierFeatures.AIR_QUALITY],
|
|
829
829
|
fan_levels=list(range(1, 4)),
|
|
830
830
|
device_alias='LV-PUR131S',
|
|
831
|
-
model_display='LV-PUR131S
|
|
831
|
+
model_display='LV-PUR131S Series',
|
|
832
832
|
model_name='LV131S',
|
|
833
833
|
setup_entry='LV-PUR131S',
|
|
834
834
|
),
|
|
835
|
+
PurifierMap(
|
|
836
|
+
class_name='VeSyncAirRH131',
|
|
837
|
+
dev_types=['LV-RH131S-WM', 'LV-RH131S'],
|
|
838
|
+
modes=[PurifierModes.SLEEP, PurifierModes.MANUAL, PurifierModes.AUTO],
|
|
839
|
+
features=[PurifierFeatures.AIR_QUALITY],
|
|
840
|
+
fan_levels=list(range(1, 4)),
|
|
841
|
+
device_alias='LV-RH131S',
|
|
842
|
+
model_display='LV-RH131S Series',
|
|
843
|
+
model_name='LV131S',
|
|
844
|
+
setup_entry='LV-RH131S',
|
|
845
|
+
),
|
|
835
846
|
PurifierMap(
|
|
836
847
|
class_name='VeSyncAirBaseV2',
|
|
837
848
|
dev_types=[
|
|
@@ -269,13 +269,19 @@ class VeSyncHumid200300S(BypassV2Mixin, VeSyncHumidifier):
|
|
|
269
269
|
)
|
|
270
270
|
return True
|
|
271
271
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
272
|
+
async def toggle_nightlight(self, toggle: bool | None = None) -> bool:
|
|
273
|
+
if not self.supports_nightlight:
|
|
274
|
+
logger.warning(
|
|
275
|
+
'%s is a %s does not have a nightlight or it is not supported.',
|
|
276
|
+
self.device_name,
|
|
277
|
+
self.device_type,
|
|
278
|
+
)
|
|
279
|
+
return False
|
|
275
280
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
281
|
+
if toggle is None:
|
|
282
|
+
toggle = self.state.nightlight_status != DeviceStatus.ON
|
|
283
|
+
brightness = 100 if toggle else 0
|
|
284
|
+
return await self.set_nightlight_brightness(brightness)
|
|
279
285
|
|
|
280
286
|
async def set_mode(self, mode: str) -> bool:
|
|
281
287
|
if mode.lower() not in self.mist_modes:
|
|
@@ -711,14 +717,6 @@ class VeSyncHumid1000S(VeSyncHumid200300S):
|
|
|
711
717
|
self.state.connection_status = ConnectionStatus.ONLINE
|
|
712
718
|
return True
|
|
713
719
|
|
|
714
|
-
@deprecated('Use set_mode() instead.')
|
|
715
|
-
async def set_humidity_mode(self, mode: str) -> bool:
|
|
716
|
-
"""Set humidifier mode - sleep, auto or manual.
|
|
717
|
-
|
|
718
|
-
Deprecated, please use set_mode() instead.
|
|
719
|
-
"""
|
|
720
|
-
return await self.set_mode(mode)
|
|
721
|
-
|
|
722
720
|
async def set_mode(self, mode: str) -> bool:
|
|
723
721
|
if mode.lower() not in self.mist_modes:
|
|
724
722
|
logger.warning('Invalid humidity mode used - %s', mode)
|
|
@@ -792,10 +790,6 @@ class VeSyncHumid1000S(VeSyncHumid200300S):
|
|
|
792
790
|
self.state.connection_status = ConnectionStatus.ONLINE
|
|
793
791
|
return True
|
|
794
792
|
|
|
795
|
-
@deprecated('Use toggle_automatic_stop() instead.')
|
|
796
|
-
async def set_automatic_stop(self, mode: bool) -> bool:
|
|
797
|
-
return await self.toggle_automatic_stop(mode)
|
|
798
|
-
|
|
799
793
|
async def toggle_automatic_stop(self, toggle: bool | None = None) -> bool:
|
|
800
794
|
if toggle is None:
|
|
801
795
|
toggle = self.state.automatic_stop_config != DeviceStatus.ON
|
|
@@ -809,3 +803,56 @@ class VeSyncHumid1000S(VeSyncHumid200300S):
|
|
|
809
803
|
self.state.automatic_stop_config = toggle
|
|
810
804
|
self.state.connection_status = ConnectionStatus.ONLINE
|
|
811
805
|
return True
|
|
806
|
+
|
|
807
|
+
async def toggle_nightlight(self, toggle: bool | None = None) -> bool:
|
|
808
|
+
if not self.supports_nightlight:
|
|
809
|
+
logger.warning(
|
|
810
|
+
'%s is a %s does not have a nightlight or it is not supported.',
|
|
811
|
+
self.device_name,
|
|
812
|
+
self.device_type,
|
|
813
|
+
)
|
|
814
|
+
return False
|
|
815
|
+
|
|
816
|
+
if toggle is None:
|
|
817
|
+
toggle = self.state.nightlight_status != DeviceStatus.ON
|
|
818
|
+
|
|
819
|
+
payload_data = {'nightLightSwitch': int(toggle)}
|
|
820
|
+
r_dict = await self.call_bypassv2_api('setNightLightStatus', payload_data)
|
|
821
|
+
r = Helpers.process_dev_response(logger, 'toggle_nightlight', self, r_dict)
|
|
822
|
+
if r is None:
|
|
823
|
+
return False
|
|
824
|
+
|
|
825
|
+
self.state.nightlight_status = DeviceStatus.from_bool(toggle)
|
|
826
|
+
self.state.connection_status = ConnectionStatus.ONLINE
|
|
827
|
+
return True
|
|
828
|
+
|
|
829
|
+
async def set_nightlight_brightness(self, brightness: int) -> bool:
|
|
830
|
+
if not self.supports_nightlight:
|
|
831
|
+
logger.warning(
|
|
832
|
+
'%s is a %s does not have a nightlight or it is not supported.',
|
|
833
|
+
self.device_name,
|
|
834
|
+
self.device_type,
|
|
835
|
+
)
|
|
836
|
+
return False
|
|
837
|
+
|
|
838
|
+
if not Validators.validate_zero_to_hundred(brightness):
|
|
839
|
+
logger.warning('Brightness value must be set between 0 and 100')
|
|
840
|
+
return False
|
|
841
|
+
|
|
842
|
+
payload_data = {
|
|
843
|
+
'brightness': brightness,
|
|
844
|
+
'nightLightSwitch': 1 if brightness > 0 else 0,
|
|
845
|
+
}
|
|
846
|
+
r_dict = await self.call_bypassv2_api('setLightStatus', payload_data)
|
|
847
|
+
r = Helpers.process_dev_response(
|
|
848
|
+
logger, 'set_night_light_brightness', self, r_dict
|
|
849
|
+
)
|
|
850
|
+
if r is None:
|
|
851
|
+
return False
|
|
852
|
+
|
|
853
|
+
self.state.nightlight_brightness = brightness
|
|
854
|
+
self.state.nightlight_status = (
|
|
855
|
+
DeviceStatus.ON if brightness > 0 else DeviceStatus.OFF
|
|
856
|
+
)
|
|
857
|
+
self.state.connection_status = ConnectionStatus.ONLINE
|
|
858
|
+
return True
|
|
@@ -94,6 +94,7 @@ class VeSyncOutlet7A(VeSyncOutlet):
|
|
|
94
94
|
self, details: ResponseDeviceDetailsModel, manager: VeSync, feature_map: OutletMap
|
|
95
95
|
) -> None:
|
|
96
96
|
"""Initialize Etekcity 7A round outlet class."""
|
|
97
|
+
self.request_keys: list[str] = []
|
|
97
98
|
super().__init__(details, manager, feature_map)
|
|
98
99
|
|
|
99
100
|
def _build_headers(self) -> dict:
|
|
@@ -169,7 +170,7 @@ class VeSyncOutlet7A(VeSyncOutlet):
|
|
|
169
170
|
return False
|
|
170
171
|
|
|
171
172
|
if isinstance(r_dict, dict) and 'error' in r_dict:
|
|
172
|
-
_ = Helpers.process_dev_response(logger, '
|
|
173
|
+
_ = Helpers.process_dev_response(logger, 'toggle_switch', self, r_dict)
|
|
173
174
|
return False
|
|
174
175
|
|
|
175
176
|
self.state.update_ts()
|
|
@@ -803,7 +804,7 @@ class VeSyncOutletWHOGPlug(BypassV2Mixin, VeSyncOutlet):
|
|
|
803
804
|
async def toggle_switch(self, toggle: bool | None = None) -> bool:
|
|
804
805
|
if toggle is None:
|
|
805
806
|
toggle = self.state.device_status != DeviceStatus.ON
|
|
806
|
-
toggle_int =
|
|
807
|
+
toggle_int = int(toggle)
|
|
807
808
|
r_dict = await self.call_bypassv2_api(
|
|
808
809
|
'setProperty',
|
|
809
810
|
data={'powerSwitch_1': toggle_int},
|
|
@@ -23,6 +23,7 @@ from pyvesync.models.purifier_models import (
|
|
|
23
23
|
InnerPurifierBaseResult,
|
|
24
24
|
Purifier131Result,
|
|
25
25
|
PurifierCoreDetailsResult,
|
|
26
|
+
PurifierRH131Result,
|
|
26
27
|
PurifierSproutResult,
|
|
27
28
|
PurifierV2EventTiming,
|
|
28
29
|
PurifierV2TimerActionItems,
|
|
@@ -980,3 +981,151 @@ class VeSyncAir131(BypassV1Mixin, VeSyncPurifier):
|
|
|
980
981
|
self.state.mode = mode
|
|
981
982
|
self.state.connection_status = ConnectionStatus.ONLINE
|
|
982
983
|
return True
|
|
984
|
+
|
|
985
|
+
|
|
986
|
+
class VeSyncAirRH131(BypassV2Mixin, VeSyncPurifier):
|
|
987
|
+
"""Levoit Air Purifier Class.
|
|
988
|
+
|
|
989
|
+
Class for LV-PUR131S, using BypassV1 API.
|
|
990
|
+
|
|
991
|
+
Attributes:
|
|
992
|
+
state (PurifierState): State of the device.
|
|
993
|
+
last_response (ResponseInfo): Last response from API call.
|
|
994
|
+
manager (VeSync): Manager object for API calls.
|
|
995
|
+
device_name (str): Name of device.
|
|
996
|
+
device_image (str): URL for device image.
|
|
997
|
+
cid (str): Device ID.
|
|
998
|
+
connection_type (str): Connection type of device.
|
|
999
|
+
device_type (str): Type of device.
|
|
1000
|
+
type (str): Type of device.
|
|
1001
|
+
uuid (str): UUID of device, not always present.
|
|
1002
|
+
config_module (str): Configuration module of device.
|
|
1003
|
+
mac_id (str): MAC ID of device.
|
|
1004
|
+
current_firm_version (str): Current firmware version of device.
|
|
1005
|
+
device_region (str): Region of device. (US, EU, etc.)
|
|
1006
|
+
pid (str): Product ID of device, pulled by some devices on update.
|
|
1007
|
+
sub_device_no (int): Sub-device number of device.
|
|
1008
|
+
product_type (str): Product type of device.
|
|
1009
|
+
features (dict): Features of device.
|
|
1010
|
+
modes (list[str]): List of modes supported by the device.
|
|
1011
|
+
fan_levels (list[int]): List of fan levels supported by the device.
|
|
1012
|
+
nightlight_modes (list[str]): List of nightlight modes supported by the device.
|
|
1013
|
+
auto_preferences (list[str]): List of auto preferences supported by the device.
|
|
1014
|
+
"""
|
|
1015
|
+
|
|
1016
|
+
__slots__ = ()
|
|
1017
|
+
|
|
1018
|
+
def __init__(
|
|
1019
|
+
self,
|
|
1020
|
+
details: ResponseDeviceDetailsModel,
|
|
1021
|
+
manager: VeSync,
|
|
1022
|
+
feature_map: PurifierMap,
|
|
1023
|
+
) -> None:
|
|
1024
|
+
"""Initialize air purifier class."""
|
|
1025
|
+
super().__init__(details, manager, feature_map)
|
|
1026
|
+
|
|
1027
|
+
def _set_state(self, details: PurifierRH131Result) -> None:
|
|
1028
|
+
"""Set state from purifier API get_details() response."""
|
|
1029
|
+
self.state.device_status = DeviceStatus.from_bool(details.enabled)
|
|
1030
|
+
self.state.connection_status = ConnectionStatus.ONLINE
|
|
1031
|
+
if details.filter_life is not None:
|
|
1032
|
+
self.state.filter_life = details.filter_life
|
|
1033
|
+
self.state.display_status = DeviceStatus.from_bool(details.display)
|
|
1034
|
+
self.state.display_set_status = DeviceStatus.from_bool(details.display)
|
|
1035
|
+
self.state.mode = details.mode
|
|
1036
|
+
self.state.fan_level = details.level or 0
|
|
1037
|
+
self.state.fan_set_level = details.level or 0
|
|
1038
|
+
self.state.set_air_quality_level(details.air_quality or 0)
|
|
1039
|
+
self.state.child_lock = details.child_lock
|
|
1040
|
+
|
|
1041
|
+
async def get_details(self) -> None:
|
|
1042
|
+
r_dict = await self.call_bypassv2_api('getPurifierStatus')
|
|
1043
|
+
r_model = process_bypassv2_result(
|
|
1044
|
+
self, _LOGGER, 'get_details', r_dict, PurifierRH131Result
|
|
1045
|
+
)
|
|
1046
|
+
if r_model is None:
|
|
1047
|
+
return
|
|
1048
|
+
|
|
1049
|
+
self._set_state(r_model)
|
|
1050
|
+
|
|
1051
|
+
async def toggle_switch(self, toggle: bool | None = None) -> bool:
|
|
1052
|
+
if toggle is None:
|
|
1053
|
+
toggle = self.state.device_status != DeviceStatus.ON
|
|
1054
|
+
|
|
1055
|
+
payload_data = {'enabled': toggle, 'id': 0}
|
|
1056
|
+
r_dict = await self.call_bypassv2_api('setSwitch', payload_data)
|
|
1057
|
+
r = Helpers.process_dev_response(_LOGGER, 'toggle_switch', self, r_dict)
|
|
1058
|
+
if r is None:
|
|
1059
|
+
return False
|
|
1060
|
+
|
|
1061
|
+
self.state.device_status = DeviceStatus.from_bool(toggle)
|
|
1062
|
+
self.state.connection_status = ConnectionStatus.ONLINE
|
|
1063
|
+
return True
|
|
1064
|
+
|
|
1065
|
+
async def toggle_display(self, mode: bool) -> bool:
|
|
1066
|
+
update_dict = {'state': mode}
|
|
1067
|
+
r_dict = await self.call_bypassv2_api('setDisplay', update_dict)
|
|
1068
|
+
r = Helpers.process_dev_response(_LOGGER, 'toggle_display', self, r_dict)
|
|
1069
|
+
if r is None:
|
|
1070
|
+
return False
|
|
1071
|
+
|
|
1072
|
+
self.state.display_set_status = DeviceStatus.from_bool(mode)
|
|
1073
|
+
self.state.display_status = DeviceStatus.from_bool(mode)
|
|
1074
|
+
self.state.connection_status = ConnectionStatus.ONLINE
|
|
1075
|
+
return True
|
|
1076
|
+
|
|
1077
|
+
async def set_mode(self, mode: str) -> bool:
|
|
1078
|
+
if mode not in self.modes:
|
|
1079
|
+
_LOGGER.warning('Invalid purifier mode used - %s', mode)
|
|
1080
|
+
return False
|
|
1081
|
+
|
|
1082
|
+
payload_data = {'mode': mode}
|
|
1083
|
+
r_dict = await self.call_bypassv2_api('setPurifierMode', payload_data)
|
|
1084
|
+
r = Helpers.process_dev_response(_LOGGER, 'set_mode', self, r_dict)
|
|
1085
|
+
if r is None:
|
|
1086
|
+
return False
|
|
1087
|
+
|
|
1088
|
+
self.state.mode = mode
|
|
1089
|
+
self.state.connection_status = ConnectionStatus.ONLINE
|
|
1090
|
+
return True
|
|
1091
|
+
|
|
1092
|
+
async def set_fan_speed(self, speed: int | None = None) -> bool:
|
|
1093
|
+
if speed is not None:
|
|
1094
|
+
if speed not in self.fan_levels:
|
|
1095
|
+
_LOGGER.warning(
|
|
1096
|
+
'%s is invalid speed - valid speeds are %s',
|
|
1097
|
+
speed,
|
|
1098
|
+
str(self.fan_levels),
|
|
1099
|
+
)
|
|
1100
|
+
return False
|
|
1101
|
+
new_speed = speed
|
|
1102
|
+
else:
|
|
1103
|
+
new_speed = Helpers.bump_level(self.state.fan_level or 0, self.fan_levels)
|
|
1104
|
+
|
|
1105
|
+
payload_data = {'level': new_speed, 'id': 0, 'type': 'wind'}
|
|
1106
|
+
|
|
1107
|
+
r_dict = await self.call_bypassv2_api('setLevel', payload_data)
|
|
1108
|
+
|
|
1109
|
+
r = Helpers.process_dev_response(_LOGGER, 'set_fan_speed', self, r_dict)
|
|
1110
|
+
if r is None:
|
|
1111
|
+
return False
|
|
1112
|
+
|
|
1113
|
+
self.state.fan_level = new_speed
|
|
1114
|
+
self.state.fan_set_level = new_speed
|
|
1115
|
+
self.state.connection_status = ConnectionStatus.ONLINE
|
|
1116
|
+
self.state.mode = PurifierModes.MANUAL
|
|
1117
|
+
return True
|
|
1118
|
+
|
|
1119
|
+
async def toggle_child_lock(self, toggle: bool | None = None) -> bool:
|
|
1120
|
+
if toggle is None:
|
|
1121
|
+
toggle = not bool(self.state.child_lock)
|
|
1122
|
+
payload_data = {'child_lock': toggle}
|
|
1123
|
+
r_dict = await self.call_bypassv2_api('setChildLock', payload_data)
|
|
1124
|
+
|
|
1125
|
+
r = Helpers.process_dev_response(_LOGGER, 'toggle_child_lock', self, r_dict)
|
|
1126
|
+
if r is None:
|
|
1127
|
+
return False
|
|
1128
|
+
|
|
1129
|
+
self.state.child_lock = toggle
|
|
1130
|
+
self.state.connection_status = ConnectionStatus.ONLINE
|
|
1131
|
+
return True
|
|
@@ -72,7 +72,7 @@ class ClassicLVHumidResult(InnerHumidifierBaseResult):
|
|
|
72
72
|
mist_virtual_level: int
|
|
73
73
|
mist_level: int
|
|
74
74
|
mode: str
|
|
75
|
-
display: Annotated[bool, Alias('
|
|
75
|
+
display: Annotated[bool, Alias('indicator_light_switch')]
|
|
76
76
|
water_lacks: bool
|
|
77
77
|
humidity: int | None = None
|
|
78
78
|
humidity_high: bool = False
|
|
@@ -89,7 +89,7 @@ class ClassicConfig(ResponseBaseModel):
|
|
|
89
89
|
"""Classic 200S Humidifier Configuration Model."""
|
|
90
90
|
|
|
91
91
|
auto_target_humidity: int = 0
|
|
92
|
-
display: Annotated[bool, Alias('
|
|
92
|
+
display: Annotated[bool, Alias('indicator_light_switch')] = False
|
|
93
93
|
automatic_stop: bool = False
|
|
94
94
|
|
|
95
95
|
class Config(BaseConfig): # type: ignore[override]
|
|
@@ -258,3 +258,27 @@ class Purifier131Filter(ResponseBaseModel):
|
|
|
258
258
|
change: bool
|
|
259
259
|
useHour: int
|
|
260
260
|
percent: int
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
@dataclass
|
|
264
|
+
class PurifierRH131Result(BypassV2InnerResult):
|
|
265
|
+
"""Purifier RH131 Details Response Dict."""
|
|
266
|
+
|
|
267
|
+
enabled: bool
|
|
268
|
+
filter_life: int
|
|
269
|
+
mode: str
|
|
270
|
+
level: int
|
|
271
|
+
air_quality: int
|
|
272
|
+
display: bool
|
|
273
|
+
child_lock: bool
|
|
274
|
+
buzzer: bool
|
|
275
|
+
configuration: dict
|
|
276
|
+
extension: PurifierRH131DetailsExtension | None = None
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
@dataclass
|
|
280
|
+
class PurifierRH131DetailsExtension(ResponseBaseModel):
|
|
281
|
+
"""Extension dict in RH131 purifier details response."""
|
|
282
|
+
|
|
283
|
+
schedule_count: int
|
|
284
|
+
timer_remain: int
|
|
@@ -695,7 +695,7 @@ class ErrorCodes:
|
|
|
695
695
|
'TOKEN_EXPIRED', ErrorTypes.TOKEN_ERROR, 'Invalid token'
|
|
696
696
|
),
|
|
697
697
|
'-999999999': ResponseInfo(
|
|
698
|
-
'UNKNOWN', ErrorTypes.
|
|
698
|
+
'UNKNOWN', ErrorTypes.UNKNOWN_ERROR, 'Unknown error'
|
|
699
699
|
),
|
|
700
700
|
'-11307000': ResponseInfo(
|
|
701
701
|
'UUID_NOT_EXIST',
|
|
@@ -757,23 +757,23 @@ class ErrorCodes:
|
|
|
757
757
|
"""
|
|
758
758
|
try:
|
|
759
759
|
if error_code is None:
|
|
760
|
-
|
|
760
|
+
error_code = '-999999999'
|
|
761
761
|
error_str = str(error_code)
|
|
762
762
|
error_int = int(error_code)
|
|
763
763
|
if error_str == '0':
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
if error_str in cls.errors:
|
|
764
|
+
error_info = ResponseInfo('SUCCESS', ErrorTypes.SUCCESS, 'Success')
|
|
765
|
+
elif error_str in cls.errors:
|
|
767
766
|
error_info = cls.errors[error_str]
|
|
768
767
|
else:
|
|
769
768
|
error_code = int(error_int / 1000) * 1000
|
|
770
|
-
|
|
771
|
-
error_info = replace(error_object)
|
|
772
|
-
|
|
769
|
+
error_info = cls.errors[str(error_code)]
|
|
773
770
|
if msg:
|
|
771
|
+
error_info = replace(error_info)
|
|
774
772
|
error_info.message = f'{error_info.message} - {msg}'
|
|
775
773
|
except (ValueError, TypeError, KeyError):
|
|
776
|
-
|
|
774
|
+
error_info = ResponseInfo(
|
|
775
|
+
'UNKNOWN', ErrorTypes.UNKNOWN_ERROR, 'Unknown error'
|
|
776
|
+
)
|
|
777
777
|
return error_info
|
|
778
778
|
|
|
779
779
|
@classmethod
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pyvesync
|
|
3
|
-
Version: 3.2
|
|
3
|
+
Version: 3.3.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
|