pyvesync 3.0.0b1__tar.gz → 3.0.0b3__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 (58) hide show
  1. {pyvesync-3.0.0b1/src/pyvesync.egg-info → pyvesync-3.0.0b3}/PKG-INFO +1 -1
  2. {pyvesync-3.0.0b1 → pyvesync-3.0.0b3}/pyproject.toml +1 -1
  3. {pyvesync-3.0.0b1 → pyvesync-3.0.0b3}/src/pyvesync/base_devices/fan_base.py +13 -22
  4. {pyvesync-3.0.0b1 → pyvesync-3.0.0b3}/src/pyvesync/base_devices/humidifier_base.py +19 -22
  5. {pyvesync-3.0.0b1 → pyvesync-3.0.0b3}/src/pyvesync/base_devices/outlet_base.py +7 -7
  6. {pyvesync-3.0.0b1 → pyvesync-3.0.0b3}/src/pyvesync/base_devices/purifier_base.py +17 -22
  7. {pyvesync-3.0.0b1 → pyvesync-3.0.0b3}/src/pyvesync/base_devices/switch_base.py +5 -5
  8. {pyvesync-3.0.0b1 → pyvesync-3.0.0b3}/src/pyvesync/base_devices/vesyncbasedevice.py +5 -4
  9. {pyvesync-3.0.0b1 → pyvesync-3.0.0b3}/src/pyvesync/const.py +18 -9
  10. {pyvesync-3.0.0b1 → pyvesync-3.0.0b3}/src/pyvesync/devices/vesynchumidifier.py +4 -4
  11. {pyvesync-3.0.0b1 → pyvesync-3.0.0b3}/src/pyvesync/devices/vesyncpurifier.py +2 -4
  12. {pyvesync-3.0.0b1 → pyvesync-3.0.0b3}/src/pyvesync/models/base_models.py +4 -0
  13. {pyvesync-3.0.0b1 → pyvesync-3.0.0b3}/src/pyvesync/models/fan_models.py +1 -1
  14. {pyvesync-3.0.0b1 → pyvesync-3.0.0b3}/src/pyvesync/models/home_models.py +8 -8
  15. {pyvesync-3.0.0b1 → pyvesync-3.0.0b3}/src/pyvesync/models/humidifier_models.py +2 -2
  16. {pyvesync-3.0.0b1 → pyvesync-3.0.0b3}/src/pyvesync/models/purifier_models.py +7 -8
  17. {pyvesync-3.0.0b1 → pyvesync-3.0.0b3}/src/pyvesync/models/vesync_models.py +146 -12
  18. {pyvesync-3.0.0b1 → pyvesync-3.0.0b3}/src/pyvesync/utils/errors.py +6 -0
  19. {pyvesync-3.0.0b1 → pyvesync-3.0.0b3}/src/pyvesync/utils/logs.py +23 -9
  20. {pyvesync-3.0.0b1 → pyvesync-3.0.0b3}/src/pyvesync/vesync.py +201 -24
  21. pyvesync-3.0.0b3/src/pyvesync/vesynchome.py +162 -0
  22. {pyvesync-3.0.0b1 → pyvesync-3.0.0b3/src/pyvesync.egg-info}/PKG-INFO +1 -1
  23. {pyvesync-3.0.0b1 → pyvesync-3.0.0b3}/src/pyvesync.egg-info/SOURCES.txt +1 -0
  24. {pyvesync-3.0.0b1 → pyvesync-3.0.0b3}/LICENSE +0 -0
  25. {pyvesync-3.0.0b1 → pyvesync-3.0.0b3}/MANIFEST.in +0 -0
  26. {pyvesync-3.0.0b1 → pyvesync-3.0.0b3}/README.md +0 -0
  27. {pyvesync-3.0.0b1 → pyvesync-3.0.0b3}/requirements.txt +0 -0
  28. {pyvesync-3.0.0b1 → pyvesync-3.0.0b3}/setup.cfg +0 -0
  29. {pyvesync-3.0.0b1 → pyvesync-3.0.0b3}/setup.py +0 -0
  30. {pyvesync-3.0.0b1 → pyvesync-3.0.0b3}/src/pyvesync/__init__.py +0 -0
  31. {pyvesync-3.0.0b1 → pyvesync-3.0.0b3}/src/pyvesync/base_devices/__init__.py +0 -0
  32. {pyvesync-3.0.0b1 → pyvesync-3.0.0b3}/src/pyvesync/base_devices/bulb_base.py +0 -0
  33. {pyvesync-3.0.0b1 → pyvesync-3.0.0b3}/src/pyvesync/base_devices/fryer_base.py +0 -0
  34. {pyvesync-3.0.0b1 → pyvesync-3.0.0b3}/src/pyvesync/base_devices/thermostat_base.py +0 -0
  35. {pyvesync-3.0.0b1 → pyvesync-3.0.0b3}/src/pyvesync/device_container.py +0 -0
  36. {pyvesync-3.0.0b1 → pyvesync-3.0.0b3}/src/pyvesync/device_map.py +0 -0
  37. {pyvesync-3.0.0b1 → pyvesync-3.0.0b3}/src/pyvesync/devices/__init__.py +0 -0
  38. {pyvesync-3.0.0b1 → pyvesync-3.0.0b3}/src/pyvesync/devices/vesyncbulb.py +0 -0
  39. {pyvesync-3.0.0b1 → pyvesync-3.0.0b3}/src/pyvesync/devices/vesyncfan.py +0 -0
  40. {pyvesync-3.0.0b1 → pyvesync-3.0.0b3}/src/pyvesync/devices/vesynckitchen.py +0 -0
  41. {pyvesync-3.0.0b1 → pyvesync-3.0.0b3}/src/pyvesync/devices/vesyncoutlet.py +0 -0
  42. {pyvesync-3.0.0b1 → pyvesync-3.0.0b3}/src/pyvesync/devices/vesyncswitch.py +0 -0
  43. {pyvesync-3.0.0b1 → pyvesync-3.0.0b3}/src/pyvesync/devices/vesyncthermostat.py +0 -0
  44. {pyvesync-3.0.0b1 → pyvesync-3.0.0b3}/src/pyvesync/models/__init__.py +0 -0
  45. {pyvesync-3.0.0b1 → pyvesync-3.0.0b3}/src/pyvesync/models/bulb_models.py +0 -0
  46. {pyvesync-3.0.0b1 → pyvesync-3.0.0b3}/src/pyvesync/models/bypass_models.py +0 -0
  47. {pyvesync-3.0.0b1 → pyvesync-3.0.0b3}/src/pyvesync/models/fryer_models.py +0 -0
  48. {pyvesync-3.0.0b1 → pyvesync-3.0.0b3}/src/pyvesync/models/outlet_models.py +0 -0
  49. {pyvesync-3.0.0b1 → pyvesync-3.0.0b3}/src/pyvesync/models/switch_models.py +0 -0
  50. {pyvesync-3.0.0b1 → pyvesync-3.0.0b3}/src/pyvesync/models/thermostat_models.py +0 -0
  51. {pyvesync-3.0.0b1 → pyvesync-3.0.0b3}/src/pyvesync/utils/__init__.py +0 -0
  52. {pyvesync-3.0.0b1 → pyvesync-3.0.0b3}/src/pyvesync/utils/colors.py +0 -0
  53. {pyvesync-3.0.0b1 → pyvesync-3.0.0b3}/src/pyvesync/utils/device_mixins.py +0 -0
  54. {pyvesync-3.0.0b1 → pyvesync-3.0.0b3}/src/pyvesync/utils/enum_utils.py +0 -0
  55. {pyvesync-3.0.0b1 → pyvesync-3.0.0b3}/src/pyvesync/utils/helpers.py +0 -0
  56. {pyvesync-3.0.0b1 → pyvesync-3.0.0b3}/src/pyvesync.egg-info/dependency_links.txt +0 -0
  57. {pyvesync-3.0.0b1 → pyvesync-3.0.0b3}/src/pyvesync.egg-info/requires.txt +0 -0
  58. {pyvesync-3.0.0b1 → pyvesync-3.0.0b3}/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.0.0b1
3
+ Version: 3.0.0b3
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.0.0b1"
7
+ version = "3.0.0b3"
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"
@@ -6,13 +6,7 @@ from abc import abstractmethod
6
6
  from typing_extensions import deprecated
7
7
 
8
8
  from pyvesync.base_devices.vesyncbasedevice import VeSyncBaseToggleDevice, DeviceState
9
- from pyvesync.const import (
10
- FanModes,
11
- FanFeatures,
12
- DeviceStatus,
13
- FanSleepPreference,
14
- StrFlag,
15
- )
9
+ from pyvesync.const import FanModes, FanFeatures
16
10
 
17
11
  if TYPE_CHECKING:
18
12
  from pyvesync import VeSync
@@ -26,10 +20,7 @@ logger = logging.getLogger(__name__)
26
20
  class FanState(DeviceState):
27
21
  """Base state class for Purifiers.
28
22
 
29
- Not all attributes are supported by all devices. Attributes are set to
30
- UNKNOWN by default and attributes that are not supported by all devices are
31
- set to [StrFlag.NOT_SUPPORTED][pyvesync.const.StrFlag] or
32
- [IntFlag.NOT_SUPPORTED][pyvesync.const.IntFlag].
23
+ Not all attributes are supported by all devices.
33
24
 
34
25
 
35
26
  Attributes:
@@ -93,17 +84,17 @@ class FanState(DeviceState):
93
84
  self.humidity: int | None = None
94
85
  self.temperature: int | None = None
95
86
  self.thermal_comfort: int | None = None
96
- self.sleep_preference_type: str | None = FanSleepPreference.UNKNOWN
97
- self.sleep_fallasleep_remain: str | None = DeviceStatus.UNKNOWN
98
- self.sleep_oscillation_switch: str | None = DeviceStatus.UNKNOWN
99
- self.sleep_change_fan_level: str | None = DeviceStatus.UNKNOWN
100
- self.mute_status: str = DeviceStatus.UNKNOWN
101
- self.mute_set_state: str = DeviceStatus.UNKNOWN
102
- self.oscillation_status: str = DeviceStatus.UNKNOWN
103
- self.oscillation_set_state: str = DeviceStatus.UNKNOWN
104
- self.display_status: str = DeviceStatus.UNKNOWN
105
- self.display_set_state: str = DeviceStatus.UNKNOWN
106
- self.displaying_type: str = StrFlag.NOT_SUPPORTED
87
+ self.sleep_preference_type: str | None = None
88
+ self.sleep_fallasleep_remain: str | None = None
89
+ self.sleep_oscillation_switch: str | None = None
90
+ self.sleep_change_fan_level: str | None = None
91
+ self.mute_status: str | None = None
92
+ self.mute_set_state: str | None = None
93
+ self.oscillation_status: str | None = None
94
+ self.oscillation_set_state: str | None = None
95
+ self.display_status: str | None = None
96
+ self.display_set_state: str | None = None
97
+ self.displaying_type: str | None = None
107
98
 
108
99
 
109
100
  class VeSyncFanBase(VeSyncBaseToggleDevice):
@@ -9,9 +9,7 @@ from pyvesync.base_devices.vesyncbasedevice import VeSyncBaseToggleDevice, Devic
9
9
  from pyvesync.const import (
10
10
  HumidifierFeatures,
11
11
  HumidifierModes,
12
- DeviceStatus,
13
- IntFlag,
14
- StrFlag
12
+ DeviceStatus
15
13
  )
16
14
 
17
15
  if TYPE_CHECKING:
@@ -27,8 +25,7 @@ class HumidifierState(DeviceState):
27
25
  """State Class for VeSync Humidifiers.
28
26
 
29
27
  This is the state class for all vesync humidifiers. If there are
30
- new features or state attributes, they should be added directly to
31
- this class and be set to the default `NOT_SUPPORTED` flag.
28
+ new features or state attributes.
32
29
 
33
30
  Attributes:
34
31
  auto_preference (int): Auto preference level.
@@ -97,29 +94,29 @@ class HumidifierState(DeviceState):
97
94
  """
98
95
  super().__init__(device, details, feature_map)
99
96
  self.auto_stop_target_reached: bool = False
100
- self.auto_target_humidity: int = IntFlag.NOT_SUPPORTED
97
+ self.auto_target_humidity: int | None = None
101
98
  self.automatic_stop_config: bool = False
102
99
  self.display_set_status: str = DeviceStatus.UNKNOWN
103
100
  self.display_status: str = DeviceStatus.UNKNOWN
104
- self.humidity: int = IntFlag.NOT_SUPPORTED
101
+ self.humidity: int | None = None
105
102
  self.humidity_high: bool = False
106
103
  self.mist_level: int | None = None
107
104
  self.mist_virtual_level: int | None = None
108
- self.mode: str = HumidifierModes.UNKNOWN
109
- self.nightlight_brightness: int = IntFlag.NOT_SUPPORTED
110
- self.nightlight_status: str = StrFlag.NOT_SUPPORTED
105
+ self.mode: str | None = None
106
+ self.nightlight_brightness: int | None = None
107
+ self.nightlight_status: str | None = None
111
108
  self.warm_mist_enabled: bool | None = None
112
- self.warm_mist_level: int = IntFlag.NOT_SUPPORTED
109
+ self.warm_mist_level: int | None = None
113
110
  self.water_lacks: bool = False
114
111
  self.water_tank_lifted: bool = False
115
- self.temperature: int = IntFlag.NOT_SUPPORTED
112
+ self.temperature: int | None = None
116
113
  # Superior 6000S States
117
- self.auto_preference: int = IntFlag.NOT_SUPPORTED
118
- self.filter_life_percent: int = IntFlag.NOT_SUPPORTED
119
- self.drying_mode_level: int = IntFlag.NOT_SUPPORTED
120
- self.drying_mode_auto_switch: str = StrFlag.NOT_SUPPORTED
121
- self.drying_mode_status: str = StrFlag.NOT_SUPPORTED
122
- self.drying_mode_time_remain: int = IntFlag.NOT_SUPPORTED
114
+ self.auto_preference: int | None = None
115
+ self.filter_life_percent: int | None = None
116
+ self.drying_mode_level: int | None = None
117
+ self.drying_mode_auto_switch: str | None = None
118
+ self.drying_mode_status: str | None = None
119
+ self.drying_mode_time_remain: int | None = None
123
120
 
124
121
  @property
125
122
  def automatic_stop(self) -> bool:
@@ -141,7 +138,7 @@ class HumidifierState(DeviceState):
141
138
  return self.auto_stop_target_reached
142
139
 
143
140
  @property
144
- def target_humidity(self) -> int:
141
+ def target_humidity(self) -> int | None:
145
142
  """Return the target humidity level.
146
143
 
147
144
  Returns:
@@ -150,7 +147,7 @@ class HumidifierState(DeviceState):
150
147
  return self.auto_target_humidity
151
148
 
152
149
  @property
153
- def auto_humidity(self) -> int:
150
+ def auto_humidity(self) -> int | None:
154
151
  """Return the auto humidity level.
155
152
 
156
153
  Returns:
@@ -174,7 +171,7 @@ class HumidifierState(DeviceState):
174
171
  return self.humidity
175
172
 
176
173
  @property
177
- def drying_mode_state(self) -> str:
174
+ def drying_mode_state(self) -> str | None:
178
175
  """Return the drying mode state.
179
176
 
180
177
  Returns:
@@ -183,7 +180,7 @@ class HumidifierState(DeviceState):
183
180
  return self.drying_mode_status
184
181
 
185
182
  @property
186
- def drying_mode_seconds_remaining(self) -> int:
183
+ def drying_mode_seconds_remaining(self) -> int | None:
187
184
  """Return the drying mode seconds remaining.
188
185
 
189
186
  Return:
@@ -3,7 +3,7 @@ from __future__ import annotations
3
3
  import logging
4
4
  from typing import TYPE_CHECKING
5
5
 
6
- from pyvesync.const import OutletFeatures, IntFlag, StrFlag, NightlightModes
6
+ from pyvesync.const import OutletFeatures, NightlightModes
7
7
  from pyvesync.utils.helpers import Helpers
8
8
  from pyvesync.base_devices.vesyncbasedevice import VeSyncBaseToggleDevice, DeviceState
9
9
  from pyvesync.models.outlet_models import RequestEnergyHistory, ResponseEnergyHistory
@@ -89,12 +89,12 @@ class OutletState(DeviceState):
89
89
  self.device: VeSyncOutlet = device
90
90
  self.features: list[str] = feature_map.features
91
91
  self.active_time: int | None = 0
92
- self.power: float = IntFlag.NOT_SUPPORTED
93
- self.energy: float = IntFlag.NOT_SUPPORTED
94
- self.voltage: float = IntFlag.NOT_SUPPORTED
95
- self.nightlight_status: str | None = StrFlag.NOT_SUPPORTED
96
- self.nightlight_brightness: int | None = IntFlag.NOT_SUPPORTED
97
- self.nightlight_automode: str | None = StrFlag.NOT_SUPPORTED
92
+ self.power: float | None = None
93
+ self.energy: float | None = None
94
+ self.voltage: float | None = None
95
+ self.nightlight_status: str | None = None
96
+ self.nightlight_brightness: int | None = None
97
+ self.nightlight_automode: str | None = None
98
98
  self.weekly_history: ResponseEnergyResult | None = None
99
99
  self.monthly_history: ResponseEnergyResult | None = None
100
100
  self.yearly_history: ResponseEnergyResult | None = None
@@ -12,10 +12,7 @@ from pyvesync.const import (
12
12
  PurifierFeatures,
13
13
  PurifierModes,
14
14
  DeviceStatus,
15
- NightlightModes,
16
- PurifierAutoPreference,
17
- IntFlag,
18
- StrFlag,
15
+ NightlightModes
19
16
  )
20
17
 
21
18
  if TYPE_CHECKING:
@@ -97,25 +94,23 @@ class PurifierState(DeviceState):
97
94
  self.fan_level: int | None = None
98
95
  self.fan_set_level: int | None = None
99
96
  self.filter_life: int | None = None
100
- self.auto_preference_type: str | None = PurifierAutoPreference.UNKNOWN
97
+ self.auto_preference_type: str | None = None
101
98
  self.auto_room_size: int | None = None
102
- self._air_quality_level: AirQualityLevel = AirQualityLevel.UNKNOWN
99
+ self._air_quality_level: AirQualityLevel | None = None
103
100
  self.child_lock: bool = False
104
101
  self.filter_open_state: bool = False
105
- self.display_status: str = DeviceStatus.UNKNOWN
106
- self.display_set_state: str = DeviceStatus.UNKNOWN
102
+ self.display_status: str | None = None
103
+ self.display_set_state: str | None = None
107
104
  self.display_forever: bool = False
108
105
  # Attributes not supported by all purifiers
109
- # When update is first called, devices that support these attributes will
110
- # update them from NOT_SUPPORTED, whether to None or another value.
111
- self.pm25: int | None = IntFlag.NOT_SUPPORTED
112
- self.pm1: int | None = IntFlag.NOT_SUPPORTED
113
- self.pm10: int | None = IntFlag.NOT_SUPPORTED
114
- self.aq_percent: int | None = IntFlag.NOT_SUPPORTED
115
- self.light_detection_switch: str = StrFlag.NOT_SUPPORTED
116
- self.light_detection_status: str = StrFlag.NOT_SUPPORTED
117
- self.nightlight_status: str = StrFlag.NOT_SUPPORTED
118
- self.fan_rotate_angle: int | None = IntFlag.NOT_SUPPORTED
106
+ self.pm25: int | None = None
107
+ self.pm1: int | None = None
108
+ self.pm10: int | None = None
109
+ self.aq_percent: int | None = None
110
+ self.light_detection_switch: str | None = None
111
+ self.light_detection_status: str | None = None
112
+ self.nightlight_status: str | None = None
113
+ self.fan_rotate_angle: int | None = None
119
114
 
120
115
  @property
121
116
  def air_quality_level(self) -> int:
@@ -171,19 +166,19 @@ class PurifierState(DeviceState):
171
166
 
172
167
  @property
173
168
  @deprecated("Use PurifierState.nightlight_status instead.")
174
- def night_light(self) -> str:
169
+ def night_light(self) -> str | None:
175
170
  """Return night light status."""
176
171
  return self.nightlight_status
177
172
 
178
173
  @property
179
174
  @deprecated("Use display_status instead.")
180
- def display_state(self) -> str:
175
+ def display_state(self) -> str | None:
181
176
  """Return display status."""
182
177
  return self.display_status
183
178
 
184
179
  @property
185
180
  @deprecated("Use display_set_state instead.")
186
- def display_switch(self) -> str:
181
+ def display_switch(self) -> str | None:
187
182
  """Return display switch status."""
188
183
  return self.display_set_state
189
184
 
@@ -499,7 +494,7 @@ class VeSyncPurifier(VeSyncBaseToggleDevice):
499
494
 
500
495
  @property
501
496
  @deprecated("Use self.state.nightlight_status instead.")
502
- def night_light(self) -> str:
497
+ def night_light(self) -> str | None:
503
498
  """Get night light state.
504
499
 
505
500
  Returns:
@@ -7,7 +7,7 @@ from typing_extensions import deprecated
7
7
  from pyvesync.base_devices.vesyncbasedevice import VeSyncBaseToggleDevice, DeviceState
8
8
 
9
9
  from pyvesync.utils.colors import Color
10
- from pyvesync.const import SwitchFeatures, StrFlag, IntFlag
10
+ from pyvesync.const import SwitchFeatures
11
11
 
12
12
  if TYPE_CHECKING:
13
13
  from pyvesync import VeSync
@@ -57,11 +57,11 @@ class SwitchState(DeviceState):
57
57
  """Initialize VeSync Switch State."""
58
58
  super().__init__(device, details, feature_map)
59
59
  self.device: VeSyncSwitch = device
60
- self._backlight_color: Color | str | None = StrFlag.NOT_SUPPORTED
61
- self.brightness: int = IntFlag.NOT_SUPPORTED
60
+ self._backlight_color: Color | None = None
61
+ self.brightness: int | None = None
62
62
  self.active_time: int | None = 0
63
- self.backlight_status: str = StrFlag.NOT_SUPPORTED
64
- self.indicator_status: str = StrFlag.NOT_SUPPORTED
63
+ self.backlight_status: str | None = None
64
+ self.indicator_status: str | None = None
65
65
 
66
66
  @property
67
67
  def backlight_rgb(self) -> RGB | None:
@@ -10,7 +10,7 @@ from typing_extensions import deprecated
10
10
  # import warnings
11
11
  import orjson
12
12
 
13
- from pyvesync.const import DeviceStatus, ConnectionStatus, IntFlag, StrFlag
13
+ from pyvesync.const import DeviceStatus, ConnectionStatus
14
14
 
15
15
  logger = logging.getLogger(__name__)
16
16
 
@@ -53,6 +53,7 @@ class VeSyncBaseDevice(ABC, Generic[VS_STATE_T]):
53
53
  config_module (str): Configuration module of device.
54
54
  mac_id (str): MAC ID of device.
55
55
  current_firm_version (str): Current firmware version of device.
56
+ latest_firm_version (str | None): Latest firmware version of device.
56
57
  device_region (str): Region of device. (US, EU, etc.)
57
58
  pid (str): Product ID of device, pulled by some devices on update.
58
59
  sub_device_no (int): Sub-device number of device.
@@ -96,6 +97,7 @@ class VeSyncBaseDevice(ABC, Generic[VS_STATE_T]):
96
97
  "enabled",
97
98
  "features",
98
99
  "last_response",
100
+ "latest_firm_version",
99
101
  "mac_id",
100
102
  "manager",
101
103
  "pid",
@@ -129,6 +131,7 @@ class VeSyncBaseDevice(ABC, Generic[VS_STATE_T]):
129
131
  self.config_module: str = details.configModule
130
132
  self.mac_id: str | None = details.macID
131
133
  self.current_firm_version = details.currentFirmVersion
134
+ self.latest_firm_version: str | None = None
132
135
  self.device_region: str | None = details.deviceRegion
133
136
  self.pid: str | None = None
134
137
  self.sub_device_no: int | None = details.subDeviceNo
@@ -192,7 +195,7 @@ class VeSyncBaseDevice(ABC, Generic[VS_STATE_T]):
192
195
 
193
196
  This is going to be updated.
194
197
  """
195
- return False
198
+ return self.latest_firm_version != self.current_firm_version
196
199
 
197
200
  async def set_timer(self, duration: int, action: str | None = None) -> bool:
198
201
  """Set timer for device.
@@ -542,8 +545,6 @@ class DeviceState:
542
545
  or attr_name in [*self.__base_exclusions, *self._exclude_serialization]
543
546
  ):
544
547
  continue
545
- if attr_value in [IntFlag.NOT_SUPPORTED, StrFlag.NOT_SUPPORTED]:
546
- continue
547
548
  state_dict[attr_name] = attr_value
548
549
  return state_dict
549
550
 
@@ -4,7 +4,8 @@ All device states and information are defined by Enums in this module.
4
4
 
5
5
  Attributes:
6
6
  DEFAULT_LANGUAGE (str): Default language for the VeSync app.
7
- API_BASE_URL (str): Base URL for the VeSync API.
7
+ API_BASE_URL (str): Base URL for the VeSync API. If not specified,
8
+ a region-specific API URL is automatically selected.
8
9
  API_TIMEOUT (int): Timeout for API requests.
9
10
  USER_AGENT (str): User agent for API requests.
10
11
  DEFAULT_TZ (str): Default timezone for VeSync devices, updated by API after
@@ -12,6 +13,8 @@ Attributes:
12
13
  DEFAULT_REGION (str): Default region for VeSync devices,
13
14
  updated by API when retrieving devices.
14
15
  APP_VERSION (str): Version of the VeSync app.
16
+ APP_ID (str): ID of the app. VeSync uses a random 8-letter string, but any non-empty
17
+ string works.
15
18
  PHONE_BRAND (str): Brand of the phone used to login to the VeSync app.
16
19
  PHONE_OS (str): Operating system of the phone used to login to the VeSync app.
17
20
  MOBILE_ID (str): Unique identifier for the phone used to login to the VeSync app.
@@ -20,31 +23,37 @@ Attributes:
20
23
  BYPASS_HEADER_UA (str): Bypass header user agent
21
24
  TERMINAL_ID (str): Unique identifier for new API calls
22
25
  """
23
- from random import randint
24
- from uuid import uuid1
26
+ from random import randint, choices
27
+ from uuid import uuid4
28
+ import string
25
29
  from enum import StrEnum, IntEnum, Enum
26
30
  from types import MappingProxyType
27
31
  from pyvesync.utils.enum_utils import IntEnumMixin
28
32
 
29
33
  DEFAULT_LANGUAGE = "en"
30
- API_BASE_URL = "https://smartapi.vesync.com"
34
+ API_BASE_URL = None # Global URL (non-EU regions): "https://smartapi.vesync.com"
31
35
  # If device is out of reach, the cloud api sends a timeout response after 7 seconds,
32
36
  # using 8 here so there is time enough to catch that message
37
+ API_BASE_URL_US = "https://smartapi.vesync.com"
38
+ API_BASE_URL_EU = "https://smartapi.vesync.eu"
39
+ NON_EU_REGIONS = ['US', 'CA', 'MX', 'JP']
33
40
  API_TIMEOUT = 8
34
41
  USER_AGENT = (
35
42
  "VeSync/3.2.39 (com.etekcity.vesyncPlatform; build:5; iOS 15.5.0) Alamofire/5.2.1"
36
43
  )
37
44
  DEFAULT_TZ = "America/New_York"
38
45
  DEFAULT_REGION = "US"
39
- APP_VERSION = "2.8.6"
46
+ APP_VERSION = "5.6.60"
47
+ APP_ID = ''.join(choices(string.ascii_lowercase + string.digits, k=8)) # noqa: S311
40
48
  PHONE_BRAND = "SM N9005"
41
49
  PHONE_OS = "Android"
42
50
  # MOBILE_ID = "1234567890123456"
43
51
  MOBILE_ID = str(randint(1000000000000000, 9999999999999999)) # noqa: S311
44
52
  USER_TYPE = "1"
45
- BYPASS_APP_V = "VeSync 3.0.51"
53
+ BYPASS_APP_V = f"VeSync {APP_VERSION}"
46
54
  BYPASS_HEADER_UA = "okhttp/3.12.1"
47
- TERMINAL_ID = '2' + uuid1().hex
55
+ TERMINAL_ID = '2' + str(uuid4()).replace('-', '')
56
+ CLIENT_TYPE = "vesyncApp"
48
57
 
49
58
 
50
59
  # Generic Constants
@@ -64,7 +73,7 @@ class ProductTypes(StrEnum):
64
73
 
65
74
 
66
75
  class IntFlag(IntEnum):
67
- """Integer flag to indicate if a device is not supported.
76
+ """DEPRECATED. Integer flag to indicate if a device is not supported.
68
77
 
69
78
  This is used by data models as a default value for feature attributes
70
79
  that are not supported by all devices.
@@ -82,7 +91,7 @@ class IntFlag(IntEnum):
82
91
 
83
92
 
84
93
  class StrFlag(StrEnum):
85
- """String flag to indicate if a device is not supported.
94
+ """DEPRECATED. String flag to indicate if a device is not supported.
86
95
 
87
96
  This is used by data models as a default value for feature attributes
88
97
  that are not supported by all devices.
@@ -8,7 +8,7 @@ import orjson
8
8
  from pyvesync.base_devices import VeSyncHumidifier
9
9
  from pyvesync.utils.helpers import Helpers, Validators, Timer
10
10
  from pyvesync.utils.device_mixins import BypassV2Mixin, process_bypassv2_result
11
- from pyvesync.const import DeviceStatus, IntFlag, ConnectionStatus
11
+ from pyvesync.const import DeviceStatus, ConnectionStatus
12
12
  from pyvesync.models.bypass_models import ResultV2GetTimer, ResultV2SetTimer
13
13
  from pyvesync.models.humidifier_models import (
14
14
  ClassicLVHumidResult,
@@ -75,7 +75,7 @@ class VeSyncHumid200300S(BypassV2Mixin, VeSyncHumidifier):
75
75
  self.state.connection_status = ConnectionStatus.ONLINE
76
76
  self.state.device_status = DeviceStatus.from_bool(resp_model.enabled)
77
77
  self.state.mode = resp_model.mode
78
- self.state.humidity = resp_model.humidity or IntFlag.NOT_SUPPORTED
78
+ self.state.humidity = resp_model.humidity
79
79
  self.state.mist_virtual_level = resp_model.mist_virtual_level or 0
80
80
  self.state.mist_level = resp_model.mist_level or 0
81
81
  self.state.water_lacks = resp_model.water_lacks
@@ -84,7 +84,7 @@ class VeSyncHumid200300S(BypassV2Mixin, VeSyncHumidifier):
84
84
  self.state.auto_stop_target_reached = resp_model.automatic_stop_reach_target
85
85
  if (
86
86
  self.supports_nightlight
87
- and resp_model.night_light_brightness != IntFlag.NOT_SUPPORTED
87
+ and resp_model.night_light_brightness is not None
88
88
  ):
89
89
  self.state.nightlight_brightness = resp_model.night_light_brightness
90
90
  self.state.nightlight_status = (
@@ -93,7 +93,7 @@ class VeSyncHumid200300S(BypassV2Mixin, VeSyncHumidifier):
93
93
  else DeviceStatus.OFF
94
94
  )
95
95
  self.state.display_status = DeviceStatus.from_bool(resp_model.display)
96
- if self.supports_warm_mist and resp_model.warm_level != IntFlag.NOT_SUPPORTED:
96
+ if self.supports_warm_mist and resp_model.warm_level is not None:
97
97
  self.state.warm_mist_level = resp_model.warm_level
98
98
  self.state.warm_mist_enabled = resp_model.warm_enabled
99
99
  config = resp_model.configuration
@@ -14,8 +14,6 @@ from pyvesync.utils.device_mixins import (
14
14
  from pyvesync.utils.helpers import Helpers, Timer
15
15
  # from pyvesync.models.base_models import DefaultValues
16
16
  from pyvesync.const import (
17
- IntFlag,
18
- StrFlag,
19
17
  PurifierAutoPreference,
20
18
  DeviceStatus,
21
19
  ConnectionStatus,
@@ -135,7 +133,7 @@ class VeSyncAirBypass(BypassV2Mixin, VeSyncPurifier):
135
133
  if self.supports_air_quality is True:
136
134
  self.state.pm25 = result.air_quality_value
137
135
  self.state.set_air_quality_level(result.air_quality)
138
- if result.night_light != StrFlag.NOT_SUPPORTED:
136
+ if result.night_light is not None:
139
137
  self.state.nightlight_status = DeviceStatus.ON if result.night_light \
140
138
  else DeviceStatus.OFF
141
139
 
@@ -490,7 +488,7 @@ class VeSyncAirBaseV2(VeSyncAirBypass):
490
488
  self.state.pm10 = details.PM10
491
489
  self.state.aq_percent = details.AQPercent
492
490
  self.state.fan_rotate_angle = details.fanRotateAngle
493
- if details.filterOpenState != IntFlag.NOT_SUPPORTED:
491
+ if details.filterOpenState is not None:
494
492
  self.state.filter_open_state = bool(details.filterOpenState)
495
493
  if details.timerRemain > 0:
496
494
  self.state.timer = Timer(details.timerRemain, 'off')
@@ -27,6 +27,7 @@ from mashumaro.config import BaseConfig
27
27
  from pyvesync.const import (
28
28
  USER_TYPE,
29
29
  APP_VERSION,
30
+ APP_ID,
30
31
  PHONE_BRAND,
31
32
  PHONE_OS,
32
33
  MOBILE_ID,
@@ -35,6 +36,7 @@ from pyvesync.const import (
35
36
  DEFAULT_TZ,
36
37
  BYPASS_HEADER_UA,
37
38
  TERMINAL_ID,
39
+ CLIENT_TYPE
38
40
  )
39
41
 
40
42
 
@@ -86,6 +88,8 @@ class DefaultValues:
86
88
  _call_number: ClassVar[int] = 0
87
89
  userType: str = USER_TYPE
88
90
  appVersion: str = APP_VERSION
91
+ clientType: str = CLIENT_TYPE
92
+ appId: str = APP_ID
89
93
  phoneBrand: str = PHONE_BRAND
90
94
  phoneOS: str = PHONE_OS
91
95
  mobileId: str = MOBILE_ID
@@ -33,7 +33,7 @@ class TowerFanResult(BypassV2InnerResult):
33
33
  thermalComfort: int
34
34
  errorCode: int
35
35
  scheduleCount: int
36
- displayingType: int = IntFlag.NOT_SUPPORTED
36
+ displayingType: int | None = None
37
37
  sleepPreference: FanSleepPreferences | None = None
38
38
 
39
39
 
@@ -20,7 +20,6 @@ from dataclasses import dataclass, field
20
20
 
21
21
  from pyvesync.models.base_models import (
22
22
  ResponseCodeModel,
23
- ResponseBaseModel,
24
23
  RequestBaseModel,
25
24
  DefaultValues,
26
25
  )
@@ -36,9 +35,9 @@ class RequestHomeModel(RequestBaseModel):
36
35
  """
37
36
  # Arguments set by Manager Instance by passing kw_argument manager
38
37
  # these fields should be set to init=False
39
- accountID: str = field(init=False)
40
- token: str = field(init=False)
41
- userCountryCode: str = field(init=False)
38
+ accountID: str
39
+ token: str
40
+ userCountryCode: str
42
41
  # Non-default constants
43
42
  method: str = 'getHomeList'
44
43
  # default values
@@ -52,17 +51,17 @@ class RequestHomeModel(RequestBaseModel):
52
51
 
53
52
 
54
53
  @dataclass
55
- class ResponseHomeModel(ResponseBaseModel):
54
+ class ResponseHomeModel(ResponseCodeModel):
56
55
  """Model for the home data response.
57
56
 
58
- Inherits from `ResponseBaseModel`. The `ResponseBaseModel` class provides the
57
+ Inherits from `ResponseCodeModel`. The `ResponseCodeModel` class provides the
59
58
  `code` and `msg` fields. The `ResponseHomeModel` class provides the `result`
60
59
  field containing the home data.
61
60
 
62
61
  Attributes:
63
62
  result: dict: The home data.
64
63
  """
65
- result: IntResponseHomeResultModel
64
+ result: IntResponseHomeResultModel | IntResponseHomeInfoResultModel | None
66
65
 
67
66
 
68
67
  @dataclass
@@ -154,5 +153,6 @@ class IntResponseHomeResultModel:
154
153
  @dataclass
155
154
  class IntResponseHomeListModel:
156
155
  """Internal model for the 'homeList' field in home response result."""
157
- homeID: str
156
+ homeId: int
158
157
  homeName: str
158
+ nickname: str | None = None
@@ -73,8 +73,8 @@ class ClassicLVHumidResult(InnerHumidifierBaseResult):
73
73
  automatic_stop_reach_target: bool = False
74
74
  water_tank_lifted: bool = False
75
75
  warm_enabled: bool = False
76
- warm_level: int = IntFlag.NOT_SUPPORTED
77
- night_light_brightness: int = IntFlag.NOT_SUPPORTED
76
+ warm_level: int | None = None
77
+ night_light_brightness: int | None = None
78
78
  configuration: ClassicConfig | None = None
79
79
 
80
80
 
@@ -12,7 +12,6 @@ from dataclasses import dataclass
12
12
  # import orjson
13
13
  from mashumaro.config import BaseConfig
14
14
  from mashumaro.types import Discriminator
15
- from pyvesync.const import IntFlag, StrFlag
16
15
  from pyvesync.models.bypass_models import (
17
16
  RequestBypassV1,
18
17
  BypassV2InnerResult,
@@ -53,11 +52,11 @@ class PurifierV2DetailsResult(InnerPurifierBaseResult):
53
52
  efficientModeTimeRemain: int
54
53
  errorCode: int
55
54
  autoPreference: VitalAutoPreferences | None = None
56
- fanRotateAngle: int | None = IntFlag.NOT_SUPPORTED
57
- filterOpenState: int | None = IntFlag.NOT_SUPPORTED
58
- PM1: int | None = IntFlag.NOT_SUPPORTED
59
- PM10: int | None = IntFlag.NOT_SUPPORTED
60
- AQPercent: int | None = IntFlag.NOT_SUPPORTED
55
+ fanRotateAngle: int | None = None
56
+ filterOpenState: int | None = None
57
+ PM1: int | None = None
58
+ PM10: int | None = None
59
+ AQPercent: int | None = None
61
60
 
62
61
 
63
62
  @dataclass
@@ -81,8 +80,8 @@ class PurifierCoreDetailsResult(InnerPurifierBaseResult):
81
80
  configuration: PurifierCoreDetailsConfig | None = None
82
81
  extension: dict | None = None
83
82
  air_quality_value: int | None = None
84
- night_light: str | None = StrFlag.NOT_SUPPORTED
85
- fan_rotate: str | None = StrFlag.NOT_SUPPORTED
83
+ night_light: str | None = None
84
+ fan_rotate: str | None = None
86
85
 
87
86
 
88
87
  # Purifier Timer Models