pyvesync 3.0.0b4__tar.gz → 3.0.0b6__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.0b4/src/pyvesync.egg-info → pyvesync-3.0.0b6}/PKG-INFO +7 -7
  2. {pyvesync-3.0.0b4 → pyvesync-3.0.0b6}/README.md +7 -7
  3. {pyvesync-3.0.0b4 → pyvesync-3.0.0b6}/pyproject.toml +1 -1
  4. {pyvesync-3.0.0b4 → pyvesync-3.0.0b6}/src/pyvesync/base_devices/fan_base.py +9 -9
  5. {pyvesync-3.0.0b4 → pyvesync-3.0.0b6}/src/pyvesync/base_devices/humidifier_base.py +0 -2
  6. {pyvesync-3.0.0b4 → pyvesync-3.0.0b6}/src/pyvesync/base_devices/purifier_base.py +5 -5
  7. {pyvesync-3.0.0b4 → pyvesync-3.0.0b6}/src/pyvesync/base_devices/vesyncbasedevice.py +1 -3
  8. {pyvesync-3.0.0b4 → pyvesync-3.0.0b6}/src/pyvesync/const.py +2 -2
  9. {pyvesync-3.0.0b4 → pyvesync-3.0.0b6}/src/pyvesync/device_map.py +0 -2
  10. {pyvesync-3.0.0b4 → pyvesync-3.0.0b6}/src/pyvesync/devices/vesyncfan.py +6 -4
  11. {pyvesync-3.0.0b4 → pyvesync-3.0.0b6}/src/pyvesync/devices/vesynchumidifier.py +1 -0
  12. {pyvesync-3.0.0b4 → pyvesync-3.0.0b6}/src/pyvesync/devices/vesyncpurifier.py +10 -46
  13. {pyvesync-3.0.0b4 → pyvesync-3.0.0b6}/src/pyvesync/models/fan_models.py +1 -2
  14. {pyvesync-3.0.0b4 → pyvesync-3.0.0b6}/src/pyvesync/models/vesync_models.py +2 -1
  15. {pyvesync-3.0.0b4 → pyvesync-3.0.0b6}/src/pyvesync/utils/errors.py +2 -2
  16. {pyvesync-3.0.0b4 → pyvesync-3.0.0b6}/src/pyvesync/vesync.py +63 -56
  17. {pyvesync-3.0.0b4 → pyvesync-3.0.0b6/src/pyvesync.egg-info}/PKG-INFO +7 -7
  18. {pyvesync-3.0.0b4 → pyvesync-3.0.0b6}/LICENSE +0 -0
  19. {pyvesync-3.0.0b4 → pyvesync-3.0.0b6}/MANIFEST.in +0 -0
  20. {pyvesync-3.0.0b4 → pyvesync-3.0.0b6}/requirements.txt +0 -0
  21. {pyvesync-3.0.0b4 → pyvesync-3.0.0b6}/setup.cfg +0 -0
  22. {pyvesync-3.0.0b4 → pyvesync-3.0.0b6}/setup.py +0 -0
  23. {pyvesync-3.0.0b4 → pyvesync-3.0.0b6}/src/pyvesync/__init__.py +0 -0
  24. {pyvesync-3.0.0b4 → pyvesync-3.0.0b6}/src/pyvesync/base_devices/__init__.py +0 -0
  25. {pyvesync-3.0.0b4 → pyvesync-3.0.0b6}/src/pyvesync/base_devices/bulb_base.py +0 -0
  26. {pyvesync-3.0.0b4 → pyvesync-3.0.0b6}/src/pyvesync/base_devices/fryer_base.py +0 -0
  27. {pyvesync-3.0.0b4 → pyvesync-3.0.0b6}/src/pyvesync/base_devices/outlet_base.py +0 -0
  28. {pyvesync-3.0.0b4 → pyvesync-3.0.0b6}/src/pyvesync/base_devices/switch_base.py +0 -0
  29. {pyvesync-3.0.0b4 → pyvesync-3.0.0b6}/src/pyvesync/base_devices/thermostat_base.py +0 -0
  30. {pyvesync-3.0.0b4 → pyvesync-3.0.0b6}/src/pyvesync/device_container.py +0 -0
  31. {pyvesync-3.0.0b4 → pyvesync-3.0.0b6}/src/pyvesync/devices/__init__.py +0 -0
  32. {pyvesync-3.0.0b4 → pyvesync-3.0.0b6}/src/pyvesync/devices/vesyncbulb.py +0 -0
  33. {pyvesync-3.0.0b4 → pyvesync-3.0.0b6}/src/pyvesync/devices/vesynckitchen.py +0 -0
  34. {pyvesync-3.0.0b4 → pyvesync-3.0.0b6}/src/pyvesync/devices/vesyncoutlet.py +0 -0
  35. {pyvesync-3.0.0b4 → pyvesync-3.0.0b6}/src/pyvesync/devices/vesyncswitch.py +0 -0
  36. {pyvesync-3.0.0b4 → pyvesync-3.0.0b6}/src/pyvesync/devices/vesyncthermostat.py +0 -0
  37. {pyvesync-3.0.0b4 → pyvesync-3.0.0b6}/src/pyvesync/models/__init__.py +0 -0
  38. {pyvesync-3.0.0b4 → pyvesync-3.0.0b6}/src/pyvesync/models/base_models.py +0 -0
  39. {pyvesync-3.0.0b4 → pyvesync-3.0.0b6}/src/pyvesync/models/bulb_models.py +0 -0
  40. {pyvesync-3.0.0b4 → pyvesync-3.0.0b6}/src/pyvesync/models/bypass_models.py +0 -0
  41. {pyvesync-3.0.0b4 → pyvesync-3.0.0b6}/src/pyvesync/models/fryer_models.py +0 -0
  42. {pyvesync-3.0.0b4 → pyvesync-3.0.0b6}/src/pyvesync/models/home_models.py +0 -0
  43. {pyvesync-3.0.0b4 → pyvesync-3.0.0b6}/src/pyvesync/models/humidifier_models.py +0 -0
  44. {pyvesync-3.0.0b4 → pyvesync-3.0.0b6}/src/pyvesync/models/outlet_models.py +0 -0
  45. {pyvesync-3.0.0b4 → pyvesync-3.0.0b6}/src/pyvesync/models/purifier_models.py +0 -0
  46. {pyvesync-3.0.0b4 → pyvesync-3.0.0b6}/src/pyvesync/models/switch_models.py +0 -0
  47. {pyvesync-3.0.0b4 → pyvesync-3.0.0b6}/src/pyvesync/models/thermostat_models.py +0 -0
  48. {pyvesync-3.0.0b4 → pyvesync-3.0.0b6}/src/pyvesync/utils/__init__.py +0 -0
  49. {pyvesync-3.0.0b4 → pyvesync-3.0.0b6}/src/pyvesync/utils/colors.py +0 -0
  50. {pyvesync-3.0.0b4 → pyvesync-3.0.0b6}/src/pyvesync/utils/device_mixins.py +0 -0
  51. {pyvesync-3.0.0b4 → pyvesync-3.0.0b6}/src/pyvesync/utils/enum_utils.py +0 -0
  52. {pyvesync-3.0.0b4 → pyvesync-3.0.0b6}/src/pyvesync/utils/helpers.py +0 -0
  53. {pyvesync-3.0.0b4 → pyvesync-3.0.0b6}/src/pyvesync/utils/logs.py +0 -0
  54. {pyvesync-3.0.0b4 → pyvesync-3.0.0b6}/src/pyvesync/vesynchome.py +0 -0
  55. {pyvesync-3.0.0b4 → pyvesync-3.0.0b6}/src/pyvesync.egg-info/SOURCES.txt +0 -0
  56. {pyvesync-3.0.0b4 → pyvesync-3.0.0b6}/src/pyvesync.egg-info/dependency_links.txt +0 -0
  57. {pyvesync-3.0.0b4 → pyvesync-3.0.0b6}/src/pyvesync.egg-info/requires.txt +0 -0
  58. {pyvesync-3.0.0b4 → pyvesync-3.0.0b6}/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.0b4
3
+ Version: 3.0.0b6
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>
@@ -94,11 +94,11 @@ async def main():
94
94
  # To print logs to file
95
95
  manager.log_to_file("pyvesync.log")
96
96
 
97
- await manager.login() # Still returns true
97
+ await manager.login()
98
98
  if not manager.enabled:
99
99
  print("Not logged in.")
100
100
  return
101
- await manager.get_devices() # Instantiates supported devices in device list
101
+ await manager.get_devices() # Instantiates supported devices in device list, automatically called by login, only needed if you would like updates
102
102
  await manager.update() # Updates the state of all devices
103
103
 
104
104
  # manager.devices is a DeviceContainer object
@@ -218,7 +218,7 @@ The [raise_api_errors()](https://webdjoe.github.io/pyvesync/latest/development/u
218
218
  - `VeSyncRateLimitError` - The API's rate limit has been exceeded.
219
219
  - `VeSyncAPIStatusCodeError` - The API returned a non-200 status code.
220
220
  - `VeSyncTokenError` - The API returned a token error and requires `login()` to be called again.
221
- - `VeSyncLoginError` - The user name or password is incorrect.
221
+ - `VeSyncLoginError` - The username or password is incorrect.
222
222
 
223
223
  ## Installation
224
224
 
@@ -305,7 +305,7 @@ from pyvesync.logs import VeSyncLoginError
305
305
 
306
306
  async def main():
307
307
  async with VeSync("user", "password") as manager:
308
- await manager.login() # Still returns true
308
+ await manager.login()
309
309
  await manager.update()
310
310
 
311
311
  # Acts as a set of device instances
@@ -376,7 +376,7 @@ async def main():
376
376
  async with VeSync("user", "password") as manager:
377
377
  manager.debug = True
378
378
  manager.redact = True # True by default
379
- await manager.login() # Still returns true
379
+ await manager.login()
380
380
  await manager.update()
381
381
 
382
382
  outlet = manager.outlets[0]
@@ -436,7 +436,7 @@ DEVICE_NAME = "Device" # Device to test
436
436
  async def test_device():
437
437
  # Instantiate VeSync class and login
438
438
  async with VeSync(USERNAME, PASSWORD, debug=True, redact=True) as manager:
439
- login = await manager.login()
439
+ await manager.login()
440
440
 
441
441
  # Pull and update devices
442
442
  await manager.update()
@@ -63,11 +63,11 @@ async def main():
63
63
  # To print logs to file
64
64
  manager.log_to_file("pyvesync.log")
65
65
 
66
- await manager.login() # Still returns true
66
+ await manager.login()
67
67
  if not manager.enabled:
68
68
  print("Not logged in.")
69
69
  return
70
- await manager.get_devices() # Instantiates supported devices in device list
70
+ await manager.get_devices() # Instantiates supported devices in device list, automatically called by login, only needed if you would like updates
71
71
  await manager.update() # Updates the state of all devices
72
72
 
73
73
  # manager.devices is a DeviceContainer object
@@ -187,7 +187,7 @@ The [raise_api_errors()](https://webdjoe.github.io/pyvesync/latest/development/u
187
187
  - `VeSyncRateLimitError` - The API's rate limit has been exceeded.
188
188
  - `VeSyncAPIStatusCodeError` - The API returned a non-200 status code.
189
189
  - `VeSyncTokenError` - The API returned a token error and requires `login()` to be called again.
190
- - `VeSyncLoginError` - The user name or password is incorrect.
190
+ - `VeSyncLoginError` - The username or password is incorrect.
191
191
 
192
192
  ## Installation
193
193
 
@@ -274,7 +274,7 @@ from pyvesync.logs import VeSyncLoginError
274
274
 
275
275
  async def main():
276
276
  async with VeSync("user", "password") as manager:
277
- await manager.login() # Still returns true
277
+ await manager.login()
278
278
  await manager.update()
279
279
 
280
280
  # Acts as a set of device instances
@@ -345,7 +345,7 @@ async def main():
345
345
  async with VeSync("user", "password") as manager:
346
346
  manager.debug = True
347
347
  manager.redact = True # True by default
348
- await manager.login() # Still returns true
348
+ await manager.login()
349
349
  await manager.update()
350
350
 
351
351
  outlet = manager.outlets[0]
@@ -405,7 +405,7 @@ DEVICE_NAME = "Device" # Device to test
405
405
  async def test_device():
406
406
  # Instantiate VeSync class and login
407
407
  async with VeSync(USERNAME, PASSWORD, debug=True, redact=True) as manager:
408
- login = await manager.login()
408
+ await manager.login()
409
409
 
410
410
  # Pull and update devices
411
411
  await manager.update()
@@ -510,4 +510,4 @@ And to all of those that contributed to the project:
510
510
  <img src="https://contrib.rocks/image?repo=webdjoe/pyvesync" />
511
511
  </a>
512
512
 
513
- Made with [contrib.rocks](https://contrib.rocks).
513
+ Made with [contrib.rocks](https://contrib.rocks).
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "pyvesync"
7
- version = "3.0.0b4"
7
+ version = "3.0.0b6"
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"
@@ -24,16 +24,16 @@ class FanState(DeviceState):
24
24
 
25
25
 
26
26
  Attributes:
27
- display_set_state (str): Display set state.
27
+ display_set_status (str): Display set status.
28
28
  display_status (str): Display status.
29
29
  displaying_type (str): Displaying type.
30
30
  fan_level (int): Fan level.
31
31
  fan_set_level (int): Fan set level.
32
32
  humidity (int): Humidity level.
33
33
  mode (str): Mode of device.
34
- mute_set_state (str): Mute set state.
34
+ mute_set_status (str): Mute set status.
35
35
  mute_status (str): Mute status.
36
- oscillation_set_state (str): Oscillation set state.
36
+ oscillation_set_status (str): Oscillation set status.
37
37
  oscillation_status (str): Oscillation status.
38
38
  sleep_change_fan_level (str): Sleep change fan level.
39
39
  sleep_fallasleep_remain (str): Sleep fall asleep remain.
@@ -45,16 +45,16 @@ class FanState(DeviceState):
45
45
  """
46
46
 
47
47
  __slots__ = (
48
- "display_set_state",
48
+ "display_set_status",
49
49
  "display_status",
50
50
  "displaying_type",
51
51
  "fan_level",
52
52
  "fan_set_level",
53
53
  "humidity",
54
54
  "mode",
55
- "mute_set_state",
55
+ "mute_set_status",
56
56
  "mute_status",
57
- "oscillation_set_state",
57
+ "oscillation_set_status",
58
58
  "oscillation_status",
59
59
  "sleep_change_fan_level",
60
60
  "sleep_fallasleep_remain",
@@ -89,11 +89,11 @@ class FanState(DeviceState):
89
89
  self.sleep_oscillation_switch: str | None = None
90
90
  self.sleep_change_fan_level: str | None = None
91
91
  self.mute_status: str | None = None
92
- self.mute_set_state: str | None = None
92
+ self.mute_set_status: str | None = None
93
93
  self.oscillation_status: str | None = None
94
- self.oscillation_set_state: str | None = None
94
+ self.oscillation_set_status: str | None = None
95
95
  self.display_status: str | None = None
96
- self.display_set_state: str | None = None
96
+ self.display_set_status: str | None = None
97
97
  self.displaying_type: str | None = None
98
98
 
99
99
 
@@ -32,7 +32,6 @@ class HumidifierState(DeviceState):
32
32
  auto_stop_target_reached (bool): Automatic stop target reached.
33
33
  auto_target_humidity (int): Target humidity level.
34
34
  automatic_stop_config (bool): Automatic stop configuration.
35
- child_lock_status (bool): Child lock status.
36
35
  display_set_status (str): Display set status.
37
36
  display_status (str): Display status.
38
37
  drying_mode_auto_switch (str): Drying mode auto switch status.
@@ -59,7 +58,6 @@ class HumidifierState(DeviceState):
59
58
  "auto_stop_target_reached",
60
59
  "auto_target_humidity",
61
60
  "automatic_stop_config",
62
- "child_lock_status",
63
61
  "display_set_status",
64
62
  "display_status",
65
63
  "drying_mode_auto_switch",
@@ -43,7 +43,7 @@ class PurifierState(DeviceState):
43
43
  air_quality_level (AirQualityLevel): Air quality level of the purifier.
44
44
  child_lock (bool): Child lock status of the purifier.
45
45
  display_status (str): Display status of the purifier.
46
- display_set_state (str): Display set state of the purifier.
46
+ display_set_status (str): Display set status of the purifier.
47
47
  display_forever (bool): Display forever status of the purifier.
48
48
  timer (Timer): Timer object for the purifier.
49
49
  pm25 (int): PM2.5 value of the purifier.
@@ -66,7 +66,7 @@ class PurifierState(DeviceState):
66
66
  "auto_room_size",
67
67
  "child_lock",
68
68
  "display_forever",
69
- "display_set_state",
69
+ "display_set_status",
70
70
  "display_status",
71
71
  "fan_level",
72
72
  "fan_rotate_angle",
@@ -100,7 +100,7 @@ class PurifierState(DeviceState):
100
100
  self.child_lock: bool = False
101
101
  self.filter_open_state: bool = False
102
102
  self.display_status: str | None = None
103
- self.display_set_state: str | None = None
103
+ self.display_set_status: str | None = None
104
104
  self.display_forever: bool = False
105
105
  # Attributes not supported by all purifiers
106
106
  self.pm25: int | None = None
@@ -177,10 +177,10 @@ class PurifierState(DeviceState):
177
177
  return self.display_status
178
178
 
179
179
  @property
180
- @deprecated("Use display_set_state instead.")
180
+ @deprecated("Use display_set_status instead.")
181
181
  def display_switch(self) -> str | None:
182
182
  """Return display switch status."""
183
- return self.display_set_state
183
+ return self.display_set_status
184
184
 
185
185
 
186
186
  class VeSyncPurifier(VeSyncBaseToggleDevice):
@@ -148,9 +148,7 @@ class VeSyncBaseDevice(ABC, Generic[VS_STATE_T]):
148
148
 
149
149
  def __hash__(self) -> int:
150
150
  """Use CID and sub-device number to make device hash."""
151
- if isinstance(self.sub_device_no, int) and self.sub_device_no > 0:
152
- return hash(self.cid + str(self.sub_device_no))
153
- return hash(self.cid)
151
+ return hash(self.cid + str(self.sub_device_no))
154
152
 
155
153
  def __str__(self) -> str:
156
154
  """Use device info for string representation of class."""
@@ -202,7 +202,7 @@ class DeviceStatus(StrEnum):
202
202
  return -1
203
203
 
204
204
  @classmethod
205
- def from_int(cls, value: int) -> str:
205
+ def from_int(cls, value: int | None) -> str:
206
206
  """Convert integer value to corresponding string.
207
207
 
208
208
  If value is 1, return ON and if 0, return OFF.
@@ -219,7 +219,7 @@ class DeviceStatus(StrEnum):
219
219
  @classmethod
220
220
  def from_bool(cls, value: bool) -> 'DeviceStatus':
221
221
  """Convert boolean value to corresponding string."""
222
- return cls.ON if value else cls.OFF
222
+ return cls.ON if value is True else cls.OFF
223
223
 
224
224
 
225
225
  class ConnectionStatus(StrEnum):
@@ -853,9 +853,7 @@ fan_modules: list[FanMap] = [
853
853
  class_name="VeSyncTowerFan",
854
854
  dev_types=["LTF-F422S-KEU", "LTF-F422S-WUSR", "LTF-F422_WJP", "LTF-F422S-WUS"],
855
855
  modes=[
856
- FanModes.SLEEP, # Alias for Advanced sleep mode
857
856
  FanModes.NORMAL,
858
- FanModes.MANUAL,
859
857
  FanModes.TURBO,
860
858
  FanModes.AUTO,
861
859
  FanModes.ADVANCED_SLEEP,
@@ -43,11 +43,11 @@ class VeSyncTowerFan(BypassV2Mixin, VeSyncFanBase):
43
43
  self.state.thermal_comfort = res.thermalComfort
44
44
 
45
45
  self.state.mute_status = DeviceStatus.from_int(res.muteState)
46
- self.state.mute_set_state = DeviceStatus.from_int(res.muteSwitch)
46
+ self.state.mute_set_status = DeviceStatus.from_int(res.muteSwitch)
47
47
  self.state.oscillation_status = DeviceStatus.from_int(res.oscillationState)
48
- self.state.oscillation_set_state = DeviceStatus.from_int(res.oscillationSwitch)
48
+ self.state.oscillation_set_status = DeviceStatus.from_int(res.oscillationSwitch)
49
49
  self.state.display_status = DeviceStatus.from_int(res.screenState)
50
- self.state.display_set_state = DeviceStatus.from_int(res.screenSwitch)
50
+ self.state.display_set_status = DeviceStatus.from_int(res.screenSwitch)
51
51
  self.state.displaying_type = DeviceStatus.from_int(res.displayingType)
52
52
 
53
53
  if res.timerRemain is not None and res.timerRemain > 0:
@@ -116,7 +116,7 @@ class VeSyncTowerFan(BypassV2Mixin, VeSyncFanBase):
116
116
  payload_data = {
117
117
  "workMode": mode
118
118
  }
119
- r_dict = await self.call_bypassv2_api("setTowerFanStatus", payload_data)
119
+ r_dict = await self.call_bypassv2_api("setTowerFanMode", payload_data)
120
120
  r = Helpers.process_dev_response(logger, "set_mode", self, r_dict)
121
121
  if r is None:
122
122
  return False
@@ -126,6 +126,8 @@ class VeSyncTowerFan(BypassV2Mixin, VeSyncFanBase):
126
126
  return True
127
127
 
128
128
  async def toggle_switch(self, toggle: bool | None = None) -> bool:
129
+ if toggle is None:
130
+ toggle = self.state.device_status == DeviceStatus.OFF
129
131
  payload_data = {
130
132
  'powerSwitch': int(toggle),
131
133
  'switchIdx': 0,
@@ -221,6 +221,7 @@ class VeSyncHumid200300S(BypassV2Mixin, VeSyncHumidifier):
221
221
  if r is None:
222
222
  return False
223
223
  self.state.display_set_status = DeviceStatus.from_bool(toggle)
224
+ self.state.display_status = DeviceStatus.from_bool(toggle)
224
225
  self.state.connection_status = ConnectionStatus.ONLINE
225
226
  return True
226
227
 
@@ -12,7 +12,6 @@ from pyvesync.utils.device_mixins import (
12
12
  process_bypassv2_result
13
13
  )
14
14
  from pyvesync.utils.helpers import Helpers, Timer
15
- # from pyvesync.models.base_models import DefaultValues
16
15
  from pyvesync.const import (
17
16
  PurifierAutoPreference,
18
17
  DeviceStatus,
@@ -124,7 +123,7 @@ class VeSyncAirBypass(BypassV2Mixin, VeSyncPurifier):
124
123
  self.state.child_lock = result.child_lock or False
125
124
  config = result.configuration
126
125
  if config is not None:
127
- self.state.display_set_state = DeviceStatus.ON if config.display \
126
+ self.state.display_set_status = DeviceStatus.ON if config.display \
128
127
  else DeviceStatus.OFF
129
128
  self.state.display_forever = config.display_forever
130
129
  if config.auto_preference is not None:
@@ -134,8 +133,7 @@ class VeSyncAirBypass(BypassV2Mixin, VeSyncPurifier):
134
133
  self.state.pm25 = result.air_quality_value
135
134
  self.state.set_air_quality_level(result.air_quality)
136
135
  if result.night_light is not None:
137
- self.state.nightlight_status = DeviceStatus.ON if result.night_light \
138
- else DeviceStatus.OFF
136
+ self.state.nightlight_status = DeviceStatus(result.night_light)
139
137
 
140
138
  async def get_details(self) -> None:
141
139
  r_dict = await self.call_bypassv2_api('getPurifierStatus')
@@ -251,6 +249,7 @@ class VeSyncAirBypass(BypassV2Mixin, VeSyncPurifier):
251
249
  return False
252
250
 
253
251
  self.state.fan_level = new_speed
252
+ self.state.fan_set_level = new_speed
254
253
  self.state.mode = PurifierModes.MANUAL # Set mode to manual to set fan speed
255
254
  self.state.device_status = DeviceStatus.ON
256
255
  self.state.connection_status = ConnectionStatus.ONLINE
@@ -351,7 +350,7 @@ class VeSyncAirBypass(BypassV2Mixin, VeSyncPurifier):
351
350
  if r is None:
352
351
  return False
353
352
  self.state.connection_status = ConnectionStatus.ONLINE
354
- self.state.display_set_state = DeviceStatus.from_bool(mode)
353
+ self.state.display_set_status = DeviceStatus.from_bool(mode)
355
354
  return True
356
355
 
357
356
  async def set_nightlight_mode(self, mode: str) -> bool:
@@ -477,7 +476,7 @@ class VeSyncAirBaseV2(VeSyncAirBypass):
477
476
  self.state.light_detection_status = DeviceStatus.from_int(
478
477
  details.environmentLightState
479
478
  )
480
- self.state.display_set_state = DeviceStatus.from_int(details.screenSwitch)
479
+ self.state.display_set_status = DeviceStatus.from_int(details.screenSwitch)
481
480
  self.state.display_status = DeviceStatus.from_int(details.screenState)
482
481
  auto_pref = details.autoPreference
483
482
  if auto_pref is not None:
@@ -588,7 +587,7 @@ class VeSyncAirBaseV2(VeSyncAirBypass):
588
587
  return True
589
588
 
590
589
  async def toggle_display(self, mode: bool) -> bool:
591
- if bool(self.state.display_set_state) == mode:
590
+ if bool(self.state.display_set_status) == mode:
592
591
  _LOGGER.debug('Display is already %s', mode)
593
592
  return True
594
593
 
@@ -601,7 +600,7 @@ class VeSyncAirBaseV2(VeSyncAirBypass):
601
600
  if r is None:
602
601
  return False
603
602
 
604
- self.state.display_set_state = DeviceStatus.from_bool(mode)
603
+ self.state.display_set_status = DeviceStatus.from_bool(mode)
605
604
  self.state.connection_status = ConnectionStatus.ONLINE
606
605
  return True
607
606
 
@@ -707,6 +706,7 @@ class VeSyncAirBaseV2(VeSyncAirBypass):
707
706
  return False
708
707
 
709
708
  self.state.fan_set_level = new_speed
709
+ self.state.fan_level = new_speed
710
710
  self.state.mode = PurifierModes.MANUAL
711
711
  self.state.device_status = DeviceStatus.ON
712
712
  self.state.connection_status = ConnectionStatus.ONLINE
@@ -806,7 +806,7 @@ class VeSyncAir131(BypassV1Mixin, VeSyncPurifier):
806
806
  self.state.active_time = details.activeTime
807
807
  self.state.filter_life = details.filterLife.percent
808
808
  self.state.display_status = details.screenStatus
809
- self.state.display_set_state = details.screenStatus
809
+ self.state.display_set_status = details.screenStatus
810
810
  self.state.child_lock = bool(DeviceStatus(details.childLock))
811
811
  self.state.mode = details.mode
812
812
  self.state.fan_level = details.level or 0
@@ -814,15 +814,6 @@ class VeSyncAir131(BypassV1Mixin, VeSyncPurifier):
814
814
  self.state.set_air_quality_level(details.airQuality)
815
815
 
816
816
  async def get_details(self) -> None:
817
- # body = self._build_request('deviceDetail')
818
- # headers = Helpers.req_header_bypass()
819
-
820
- # r_dict, _ = await self.manager.async_call_api(
821
- # '/cloud/v1/deviceManaged/deviceDetail',
822
- # method='post',
823
- # headers=headers,
824
- # json_object=body.to_dict(),
825
- # )
826
817
  r_dict = await self.call_bypassv1_api(
827
818
  RequestPurifier131, method="deviceDetail", endpoint="deviceDetail"
828
819
  )
@@ -837,13 +828,6 @@ class VeSyncAir131(BypassV1Mixin, VeSyncPurifier):
837
828
  update_dict = {
838
829
  "status": "on" if mode else "off"
839
830
  }
840
- # body = self._build_request('airPurifierScreenCtl', update_dict=update_dict)
841
- # headers = Helpers.req_header_bypass()
842
-
843
- # r_dict, _ = await self.manager.async_call_api(
844
- # '/cloud/v1/deviceManaged/airPurifierScreenCtl', 'post',
845
- # json_object=body.to_dict(), headers=headers
846
- # )
847
831
  r_dict = await self.call_bypassv1_api(
848
832
  RequestPurifier131, method="airPurifierScreenCtl",
849
833
  endpoint="airPurifierScreenCtl", update_dict=update_dict
@@ -852,7 +836,7 @@ class VeSyncAir131(BypassV1Mixin, VeSyncPurifier):
852
836
  if r is None:
853
837
  return False
854
838
 
855
- self.state.display_set_state = DeviceStatus.from_bool(mode)
839
+ self.state.display_set_status = DeviceStatus.from_bool(mode)
856
840
  self.state.display_status = DeviceStatus.from_bool(mode)
857
841
  self.state.connection_status = ConnectionStatus.ONLINE
858
842
  return True
@@ -864,12 +848,6 @@ class VeSyncAir131(BypassV1Mixin, VeSyncPurifier):
864
848
  update_dict = {
865
849
  "status": DeviceStatus.from_bool(toggle).value
866
850
  }
867
- # body = self._build_request('airPurifierPowerSwitchCtl', update_dict=update_dict)
868
- # headers = Helpers.req_header_bypass()
869
- # r_dict, _ = await self.manager.async_call_api(
870
- # '/cloud/v1/deviceManaged/airPurifierPowerSwitchCtl', 'post',
871
- # json_object=body.to_dict(), headers=headers
872
- # )
873
851
  r_dict = await self.call_bypassv1_api(
874
852
  RequestPurifier131, method="airPurifierPowerSwitchCtl",
875
853
  endpoint="airPurifierPowerSwitchCtl", update_dict=update_dict
@@ -900,13 +878,6 @@ class VeSyncAir131(BypassV1Mixin, VeSyncPurifier):
900
878
  update_dict = {
901
879
  "level": new_speed
902
880
  }
903
- # body = self._build_request('airPurifierSpeedCtl', update_dict=update_dict)
904
- # headers = Helpers.req_header_bypass()
905
-
906
- # r_dict, _ = await self.manager.async_call_api(
907
- # '/cloud/v1/deviceManaged/airPurifierSpeedCtl', 'post',
908
- # json_object=body, headers=headers
909
- # )
910
881
  r_dict = await self.call_bypassv1_api(
911
882
  RequestPurifier131Level, method="airPurifierSpeedCtl",
912
883
  endpoint="airPurifierSpeedCtl", update_dict=update_dict
@@ -935,13 +906,6 @@ class VeSyncAir131(BypassV1Mixin, VeSyncPurifier):
935
906
  update_dict = {
936
907
  "mode": mode
937
908
  }
938
- # body = self._build_request('airPurifierModeCtl', update_dict=update_dict)
939
- # headers = Helpers.req_header_bypass()
940
-
941
- # r_dict, _ = await self.manager.async_call_api(
942
- # '/cloud/v1/deviceManaged/airPurifierRunModeCtl', 'post',
943
- # json_object=body.to_dict(), headers=headers
944
- # )
945
909
  r_dict = await self.call_bypassv1_api(
946
910
  RequestPurifier131Mode, method="airPurifierRunModeCtl",
947
911
  endpoint="airPurifierRunModeCtl", update_dict=update_dict
@@ -15,9 +15,8 @@ from pyvesync.models.bypass_models import (
15
15
 
16
16
  @dataclass
17
17
  class TowerFanResult(BypassV2InnerResult):
18
- """Vital 100S/200S and Everest Fan Result Model."""
18
+ """Tower Fan Result Model."""
19
19
  powerSwitch: int
20
- filterLifePercent: int
21
20
  workMode: str
22
21
  manualSpeedLevel: int
23
22
  fanSpeedLevel: int
@@ -247,7 +247,8 @@ class ResponseDeviceDetailsModel(ResponseBaseModel):
247
247
  """Perform device_list pre-deserialization processes.
248
248
 
249
249
  This performs the following:
250
- - Flattens the `deviceProp` field into `deviceStatus`, `connectionStatus` and `macID`
250
+ - Flattens the `deviceProp` field into `deviceStatus`, `connectionStatus`
251
+ and `macID`
251
252
  - Sets `cid` to `uuid` or `macID` if null
252
253
  """
253
254
  super().__pre_deserialize__(d)
@@ -787,7 +787,7 @@ class VeSyncError(Exception):
787
787
  """
788
788
 
789
789
 
790
- class VesyncLoginError(VeSyncError):
790
+ class VeSyncLoginError(VeSyncError):
791
791
  """Exception raised for login authentication errors."""
792
792
 
793
793
  def __init__(self, msg: str) -> None:
@@ -855,7 +855,7 @@ def raise_api_errors(error_info: ResponseInfo) -> None:
855
855
  case ErrorTypes.RATE_LIMIT:
856
856
  raise VeSyncRateLimitError
857
857
  case ErrorTypes.AUTHENTICATION:
858
- raise VesyncLoginError(error_info.message)
858
+ raise VeSyncLoginError(error_info.message)
859
859
  case ErrorTypes.TOKEN_ERROR:
860
860
  raise VeSyncTokenError
861
861
  case ErrorTypes.SERVER_ERROR:
@@ -39,7 +39,7 @@ from pyvesync.utils.errors import (
39
39
  VeSyncAPIStatusCodeError,
40
40
  VeSyncError,
41
41
  VeSyncServerError,
42
- VesyncLoginError,
42
+ VeSyncLoginError,
43
43
  raise_api_errors,
44
44
  )
45
45
 
@@ -74,7 +74,7 @@ class VeSync: # pylint: disable=function-redefined
74
74
  'username'
75
75
  )
76
76
 
77
- def __init__(self,
77
+ def __init__(self, # noqa: PLR0913
78
78
  username: str,
79
79
  password: str,
80
80
  country_code: str = DEFAULT_REGION,
@@ -106,6 +106,8 @@ class VeSync: # pylint: disable=function-redefined
106
106
  time_zone (str): Time zone for device from IANA database, by default
107
107
  DEFAULT_TZ. This is automatically set to the time zone of the
108
108
  VeSync account during login.
109
+ debug (bool): Enable debug logging, by default False.
110
+ redact (bool): Enable redaction of sensitive information, by default True.
109
111
 
110
112
  Attributes:
111
113
  session (ClientSession): Client session for API calls
@@ -310,17 +312,16 @@ class VeSync: # pylint: disable=function-redefined
310
312
 
311
313
  Username and password are provided when class is instantiated.
312
314
 
313
- Returns:
314
- True if login successful, False if not.
315
-
316
315
  Raises:
317
- VeSyncLoginError: If login fails due to invalid username or password.
316
+ VeSyncLoginError: If login fails, for example due to invalid username
317
+ or password.
318
318
  VeSyncAPIResponseError: If API response is invalid.
319
319
  VeSyncServerError: If server returns an error.
320
320
  """
321
321
  if not isinstance(self.username, str) or len(self.username) == 0 \
322
322
  or not isinstance(self.password, str) or len(self.password) == 0:
323
- raise VesyncLoginError('Username and password must be specified')
323
+ raise VeSyncLoginError('Username and password must be specified')
324
+
324
325
  request_auth = RequestAuthModel(
325
326
  email=self.username,
326
327
  method='authByPWDOrOTM',
@@ -332,27 +333,32 @@ class VeSync: # pylint: disable=function-redefined
332
333
  )
333
334
  if resp_dict is None:
334
335
  raise VeSyncAPIResponseError('Error receiving response to auth request')
335
- if resp_dict.get('code') == 0:
336
- try:
337
- response_model = ResponseLoginModel.from_dict(resp_dict)
338
- except Exception as exc:
339
- logger.debug('Error parsing auth response: %s', exc)
340
- raise VeSyncAPIResponseError(
341
- 'Error receiving response to auth request'
342
- ) from exc
343
- result = response_model.result
344
- if isinstance(result, IntRespAuthResultModel):
345
- # If the result is an IntRespAuthResultModel,
346
- # we need to log in with the auth code
347
- return await self._login_token(auth_code=result.authorizeCode)
348
-
349
- error_info = ErrorCodes.get_error_info(resp_dict.get("code"))
350
- resp_message = resp_dict.get('msg')
351
- if resp_message is not None:
352
- error_info.message = f'{error_info.message} ({resp_message})'
353
- raise VeSyncAPIResponseError(
354
- f"Error receiving response to auth request - {error_info.message}"
355
- )
336
+
337
+ if resp_dict.get('code') != 0:
338
+ error_info = ErrorCodes.get_error_info(resp_dict.get("code"))
339
+ resp_message = resp_dict.get('msg')
340
+ if resp_message is not None:
341
+ error_info.message = f'{error_info.message} ({resp_message})'
342
+ raise VeSyncAPIResponseError(
343
+ f"Error receiving response to auth request - {error_info.message}"
344
+ )
345
+
346
+ try:
347
+ response_model = ResponseLoginModel.from_dict(resp_dict)
348
+ except Exception as exc:
349
+ logger.debug('Error parsing auth response: %s', exc)
350
+ raise VeSyncAPIResponseError(
351
+ 'Error receiving response to auth request'
352
+ ) from exc
353
+
354
+ result = response_model.result
355
+ if not isinstance(result, IntRespAuthResultModel):
356
+ raise VeSyncAPIResponseError(
357
+ "Error receiving response to login request -"
358
+ " result is not IntRespAuthResultModel"
359
+ )
360
+
361
+ return await self._login_token(auth_code=result.authorizeCode)
356
362
 
357
363
  async def _login_token(
358
364
  self,
@@ -370,11 +376,9 @@ class VeSync: # pylint: disable=function-redefined
370
376
  region_change_token (str): "bizToken" to use when calling this endpoint
371
377
  for a second time with a different region.
372
378
 
373
- Returns:
374
- True if login successful, False if not.
375
-
376
379
  Raises:
377
- VeSyncLoginError: If login fails due to invalid username or password.
380
+ VeSyncLoginError: If login fails, for example due to invalid username
381
+ or password.
378
382
  VeSyncAPIResponseError: If API response is invalid.
379
383
  VeSyncServerError: If server returns an error.
380
384
  """
@@ -398,35 +402,38 @@ class VeSync: # pylint: disable=function-redefined
398
402
  "Error receiving response to login request -"
399
403
  "result is not IntRespLoginResultModel"
400
404
  )
401
- if response_model.code == 0:
402
- result = response_model.result
403
- if not isinstance(result, IntRespLoginResultModel):
404
- raise VeSyncAPIResponseError(
405
- "Error receiving response to login request -"
406
- " result is not IntRespLoginResultModel"
407
- )
408
- self._token = result.token
409
- self._account_id = result.accountID
410
- self.country_code = result.countryCode
411
- self.enabled = True
412
- logger.debug('Login successful')
413
- return True
414
- error_info = ErrorCodes.get_error_info(resp_dict.get("code"))
415
- if error_info.error_type == ErrorTypes.CROSS_REGION: # "cross region error."
416
- result = response_model.result
417
- self.country_code = result.countryCode
418
- return await self._login_token(region_change_token=result.bizToken)
419
- resp_message = resp_dict.get('msg')
420
- if resp_message is not None:
421
- error_info.message = f'{error_info.message} ({resp_message})'
422
- raise VesyncLoginError(
423
- f"Error receiving response to login request - {error_info.message}"
424
- )
405
+ if response_model.code != 0:
406
+ error_info = ErrorCodes.get_error_info(resp_dict.get("code"))
407
+ if error_info.error_type == ErrorTypes.CROSS_REGION: # cross region error
408
+ result = response_model.result
409
+ self.country_code = result.countryCode
410
+ await self._login_token(region_change_token=result.bizToken)
411
+ resp_message = resp_dict.get('msg')
412
+ if resp_message is not None:
413
+ error_info.message = f'{error_info.message} ({resp_message})'
414
+ raise VeSyncLoginError(
415
+ f"Error receiving response to login request - {error_info.message}"
416
+ )
417
+
418
+ result = response_model.result
419
+ if not isinstance(result, IntRespLoginResultModel):
420
+ raise VeSyncAPIResponseError(
421
+ "Error receiving response to login request -"
422
+ " result is not IntRespLoginResultModel"
423
+ )
424
+
425
+ self._token = result.token
426
+ self._account_id = result.accountID
427
+ self.country_code = result.countryCode
428
+ self.enabled = True
429
+ logger.debug('Login successful')
430
+
425
431
  except (MissingField, UnserializableDataError) as exc:
426
432
  logger.debug('Error parsing login response: %s', exc)
427
433
  raise VeSyncAPIResponseError(
428
434
  'Error receiving response to login request'
429
435
  ) from exc
436
+ return True
430
437
 
431
438
  async def update(self) -> None:
432
439
  """Fetch updated information about devices and new device list.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyvesync
3
- Version: 3.0.0b4
3
+ Version: 3.0.0b6
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>
@@ -94,11 +94,11 @@ async def main():
94
94
  # To print logs to file
95
95
  manager.log_to_file("pyvesync.log")
96
96
 
97
- await manager.login() # Still returns true
97
+ await manager.login()
98
98
  if not manager.enabled:
99
99
  print("Not logged in.")
100
100
  return
101
- await manager.get_devices() # Instantiates supported devices in device list
101
+ await manager.get_devices() # Instantiates supported devices in device list, automatically called by login, only needed if you would like updates
102
102
  await manager.update() # Updates the state of all devices
103
103
 
104
104
  # manager.devices is a DeviceContainer object
@@ -218,7 +218,7 @@ The [raise_api_errors()](https://webdjoe.github.io/pyvesync/latest/development/u
218
218
  - `VeSyncRateLimitError` - The API's rate limit has been exceeded.
219
219
  - `VeSyncAPIStatusCodeError` - The API returned a non-200 status code.
220
220
  - `VeSyncTokenError` - The API returned a token error and requires `login()` to be called again.
221
- - `VeSyncLoginError` - The user name or password is incorrect.
221
+ - `VeSyncLoginError` - The username or password is incorrect.
222
222
 
223
223
  ## Installation
224
224
 
@@ -305,7 +305,7 @@ from pyvesync.logs import VeSyncLoginError
305
305
 
306
306
  async def main():
307
307
  async with VeSync("user", "password") as manager:
308
- await manager.login() # Still returns true
308
+ await manager.login()
309
309
  await manager.update()
310
310
 
311
311
  # Acts as a set of device instances
@@ -376,7 +376,7 @@ async def main():
376
376
  async with VeSync("user", "password") as manager:
377
377
  manager.debug = True
378
378
  manager.redact = True # True by default
379
- await manager.login() # Still returns true
379
+ await manager.login()
380
380
  await manager.update()
381
381
 
382
382
  outlet = manager.outlets[0]
@@ -436,7 +436,7 @@ DEVICE_NAME = "Device" # Device to test
436
436
  async def test_device():
437
437
  # Instantiate VeSync class and login
438
438
  async with VeSync(USERNAME, PASSWORD, debug=True, redact=True) as manager:
439
- login = await manager.login()
439
+ await manager.login()
440
440
 
441
441
  # Pull and update devices
442
442
  await manager.update()
File without changes
File without changes
File without changes
File without changes
File without changes