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.
Files changed (59) hide show
  1. {pyvesync-3.2.1/src/pyvesync.egg-info → pyvesync-3.3.2}/PKG-INFO +1 -1
  2. {pyvesync-3.2.1 → pyvesync-3.3.2}/pyproject.toml +1 -1
  3. {pyvesync-3.2.1 → pyvesync-3.3.2}/src/pyvesync/base_devices/humidifier_base.py +32 -0
  4. {pyvesync-3.2.1 → pyvesync-3.3.2}/src/pyvesync/base_devices/purifier_base.py +5 -29
  5. {pyvesync-3.2.1 → pyvesync-3.3.2}/src/pyvesync/device_map.py +14 -3
  6. {pyvesync-3.2.1 → pyvesync-3.3.2}/src/pyvesync/devices/vesynchumidifier.py +65 -18
  7. {pyvesync-3.2.1 → pyvesync-3.3.2}/src/pyvesync/devices/vesyncoutlet.py +3 -2
  8. {pyvesync-3.2.1 → pyvesync-3.3.2}/src/pyvesync/devices/vesyncpurifier.py +149 -0
  9. {pyvesync-3.2.1 → pyvesync-3.3.2}/src/pyvesync/models/humidifier_models.py +2 -2
  10. {pyvesync-3.2.1 → pyvesync-3.3.2}/src/pyvesync/models/purifier_models.py +24 -0
  11. {pyvesync-3.2.1 → pyvesync-3.3.2}/src/pyvesync/utils/errors.py +9 -9
  12. {pyvesync-3.2.1 → pyvesync-3.3.2/src/pyvesync.egg-info}/PKG-INFO +1 -1
  13. {pyvesync-3.2.1 → pyvesync-3.3.2}/LICENSE +0 -0
  14. {pyvesync-3.2.1 → pyvesync-3.3.2}/MANIFEST.in +0 -0
  15. {pyvesync-3.2.1 → pyvesync-3.3.2}/README.md +0 -0
  16. {pyvesync-3.2.1 → pyvesync-3.3.2}/requirements.txt +0 -0
  17. {pyvesync-3.2.1 → pyvesync-3.3.2}/setup.cfg +0 -0
  18. {pyvesync-3.2.1 → pyvesync-3.3.2}/setup.py +0 -0
  19. {pyvesync-3.2.1 → pyvesync-3.3.2}/src/pyvesync/__init__.py +0 -0
  20. {pyvesync-3.2.1 → pyvesync-3.3.2}/src/pyvesync/auth.py +0 -0
  21. {pyvesync-3.2.1 → pyvesync-3.3.2}/src/pyvesync/base_devices/__init__.py +0 -0
  22. {pyvesync-3.2.1 → pyvesync-3.3.2}/src/pyvesync/base_devices/bulb_base.py +0 -0
  23. {pyvesync-3.2.1 → pyvesync-3.3.2}/src/pyvesync/base_devices/fan_base.py +0 -0
  24. {pyvesync-3.2.1 → pyvesync-3.3.2}/src/pyvesync/base_devices/fryer_base.py +0 -0
  25. {pyvesync-3.2.1 → pyvesync-3.3.2}/src/pyvesync/base_devices/outlet_base.py +0 -0
  26. {pyvesync-3.2.1 → pyvesync-3.3.2}/src/pyvesync/base_devices/switch_base.py +0 -0
  27. {pyvesync-3.2.1 → pyvesync-3.3.2}/src/pyvesync/base_devices/thermostat_base.py +0 -0
  28. {pyvesync-3.2.1 → pyvesync-3.3.2}/src/pyvesync/base_devices/vesyncbasedevice.py +0 -0
  29. {pyvesync-3.2.1 → pyvesync-3.3.2}/src/pyvesync/const.py +0 -0
  30. {pyvesync-3.2.1 → pyvesync-3.3.2}/src/pyvesync/device_container.py +0 -0
  31. {pyvesync-3.2.1 → pyvesync-3.3.2}/src/pyvesync/devices/__init__.py +0 -0
  32. {pyvesync-3.2.1 → pyvesync-3.3.2}/src/pyvesync/devices/vesyncbulb.py +0 -0
  33. {pyvesync-3.2.1 → pyvesync-3.3.2}/src/pyvesync/devices/vesyncfan.py +0 -0
  34. {pyvesync-3.2.1 → pyvesync-3.3.2}/src/pyvesync/devices/vesynckitchen.py +0 -0
  35. {pyvesync-3.2.1 → pyvesync-3.3.2}/src/pyvesync/devices/vesyncswitch.py +0 -0
  36. {pyvesync-3.2.1 → pyvesync-3.3.2}/src/pyvesync/devices/vesyncthermostat.py +0 -0
  37. {pyvesync-3.2.1 → pyvesync-3.3.2}/src/pyvesync/models/__init__.py +0 -0
  38. {pyvesync-3.2.1 → pyvesync-3.3.2}/src/pyvesync/models/base_models.py +0 -0
  39. {pyvesync-3.2.1 → pyvesync-3.3.2}/src/pyvesync/models/bulb_models.py +0 -0
  40. {pyvesync-3.2.1 → pyvesync-3.3.2}/src/pyvesync/models/bypass_models.py +0 -0
  41. {pyvesync-3.2.1 → pyvesync-3.3.2}/src/pyvesync/models/fan_models.py +0 -0
  42. {pyvesync-3.2.1 → pyvesync-3.3.2}/src/pyvesync/models/fryer_models.py +0 -0
  43. {pyvesync-3.2.1 → pyvesync-3.3.2}/src/pyvesync/models/home_models.py +0 -0
  44. {pyvesync-3.2.1 → pyvesync-3.3.2}/src/pyvesync/models/outlet_models.py +0 -0
  45. {pyvesync-3.2.1 → pyvesync-3.3.2}/src/pyvesync/models/switch_models.py +0 -0
  46. {pyvesync-3.2.1 → pyvesync-3.3.2}/src/pyvesync/models/thermostat_models.py +0 -0
  47. {pyvesync-3.2.1 → pyvesync-3.3.2}/src/pyvesync/models/vesync_models.py +0 -0
  48. {pyvesync-3.2.1 → pyvesync-3.3.2}/src/pyvesync/utils/__init__.py +0 -0
  49. {pyvesync-3.2.1 → pyvesync-3.3.2}/src/pyvesync/utils/colors.py +0 -0
  50. {pyvesync-3.2.1 → pyvesync-3.3.2}/src/pyvesync/utils/device_mixins.py +0 -0
  51. {pyvesync-3.2.1 → pyvesync-3.3.2}/src/pyvesync/utils/enum_utils.py +0 -0
  52. {pyvesync-3.2.1 → pyvesync-3.3.2}/src/pyvesync/utils/helpers.py +0 -0
  53. {pyvesync-3.2.1 → pyvesync-3.3.2}/src/pyvesync/utils/logs.py +0 -0
  54. {pyvesync-3.2.1 → pyvesync-3.3.2}/src/pyvesync/vesync.py +0 -0
  55. {pyvesync-3.2.1 → pyvesync-3.3.2}/src/pyvesync/vesynchome.py +0 -0
  56. {pyvesync-3.2.1 → pyvesync-3.3.2}/src/pyvesync.egg-info/SOURCES.txt +0 -0
  57. {pyvesync-3.2.1 → pyvesync-3.3.2}/src/pyvesync.egg-info/dependency_links.txt +0 -0
  58. {pyvesync-3.2.1 → pyvesync-3.3.2}/src/pyvesync.egg-info/requires.txt +0 -0
  59. {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.1
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.1"
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 = False
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-WEUR', 'LUH-M101S-WUSR'],
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', 'LV-RH131S', 'LV-RH131S-WM'],
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/RH131S Series',
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
- @deprecated('Use set_mode(mode: str) instead.')
273
- async def set_humidity_mode(self, mode: str) -> bool:
274
- """Deprecated - set humidifier mode.
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
- Use `set_mode(mode: str)` instead.
277
- """
278
- return await self.set_mode(mode)
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, 'get_details', self, r_dict)
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 = bool(toggle)
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('indicator_light_status')]
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('indicator_light_status')] = False
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.SERVER_ERROR, 'Unknown error'
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
- return ResponseInfo('UNKNOWN', ErrorTypes.UNKNOWN_ERROR, 'Unknown error')
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
- return ResponseInfo('SUCCESS', ErrorTypes.SUCCESS, 'Success')
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
- error_object = cls.errors[str(error_code)]
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
- return ResponseInfo('UNKNOWN', ErrorTypes.UNKNOWN_ERROR, 'Unknown error')
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.1
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