pyvesync 3.0.0b0__py3-none-any.whl

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 (49) hide show
  1. pyvesync/__init__.py +11 -0
  2. pyvesync/base_devices/__init__.py +35 -0
  3. pyvesync/base_devices/bulb_base.py +348 -0
  4. pyvesync/base_devices/fan_base.py +398 -0
  5. pyvesync/base_devices/fryer_base.py +69 -0
  6. pyvesync/base_devices/humidifier_base.py +460 -0
  7. pyvesync/base_devices/outlet_base.py +315 -0
  8. pyvesync/base_devices/purifier_base.py +518 -0
  9. pyvesync/base_devices/switch_base.py +270 -0
  10. pyvesync/base_devices/thermostat_base.py +277 -0
  11. pyvesync/base_devices/vesyncbasedevice.py +580 -0
  12. pyvesync/const.py +749 -0
  13. pyvesync/device_container.py +360 -0
  14. pyvesync/device_map.py +1090 -0
  15. pyvesync/devices/__init__.py +1 -0
  16. pyvesync/devices/vesyncbulb.py +1254 -0
  17. pyvesync/devices/vesyncfan.py +238 -0
  18. pyvesync/devices/vesynchumidifier.py +785 -0
  19. pyvesync/devices/vesynckitchen.py +664 -0
  20. pyvesync/devices/vesyncoutlet.py +962 -0
  21. pyvesync/devices/vesyncpurifier.py +951 -0
  22. pyvesync/devices/vesyncswitch.py +449 -0
  23. pyvesync/devices/vesyncthermostat.py +175 -0
  24. pyvesync/models/__init__.py +11 -0
  25. pyvesync/models/base_models.py +117 -0
  26. pyvesync/models/bulb_models.py +173 -0
  27. pyvesync/models/bypass_models.py +264 -0
  28. pyvesync/models/fan_models.py +46 -0
  29. pyvesync/models/fryer_models.py +28 -0
  30. pyvesync/models/home_models.py +158 -0
  31. pyvesync/models/humidifier_models.py +187 -0
  32. pyvesync/models/outlet_models.py +194 -0
  33. pyvesync/models/purifier_models.py +204 -0
  34. pyvesync/models/switch_models.py +89 -0
  35. pyvesync/models/thermostat_models.py +122 -0
  36. pyvesync/models/vesync_models.py +252 -0
  37. pyvesync/utils/__init__.py +1 -0
  38. pyvesync/utils/colors.py +248 -0
  39. pyvesync/utils/device_mixins.py +366 -0
  40. pyvesync/utils/enum_utils.py +25 -0
  41. pyvesync/utils/errors.py +859 -0
  42. pyvesync/utils/helpers.py +696 -0
  43. pyvesync/utils/logs.py +500 -0
  44. pyvesync/vesync.py +445 -0
  45. pyvesync-3.0.0b0.dist-info/METADATA +527 -0
  46. pyvesync-3.0.0b0.dist-info/RECORD +49 -0
  47. pyvesync-3.0.0b0.dist-info/WHEEL +5 -0
  48. pyvesync-3.0.0b0.dist-info/licenses/LICENSE +21 -0
  49. pyvesync-3.0.0b0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,785 @@
1
+ """VeSync Humidifier Devices."""
2
+ from __future__ import annotations
3
+ import logging
4
+ from typing import TYPE_CHECKING
5
+ from typing_extensions import deprecated
6
+ import orjson
7
+
8
+ from pyvesync.base_devices import VeSyncHumidifier
9
+ from pyvesync.utils.helpers import Helpers, Validators, Timer
10
+ from pyvesync.utils.device_mixins import BypassV2Mixin, process_bypassv2_result
11
+ from pyvesync.const import DeviceStatus, IntFlag, ConnectionStatus
12
+ from pyvesync.models.bypass_models import ResultV2GetTimer, ResultV2SetTimer
13
+ from pyvesync.models.humidifier_models import (
14
+ ClassicLVHumidResult,
15
+ Levoit1000SResult,
16
+ InnerHumidifierBaseResult,
17
+ Superior6000SResult,
18
+ )
19
+
20
+ if TYPE_CHECKING:
21
+ from pyvesync import VeSync
22
+ from pyvesync.models.vesync_models import ResponseDeviceDetailsModel
23
+ from pyvesync.device_map import (
24
+ HumidifierMap
25
+ )
26
+
27
+
28
+ logger = logging.getLogger(__name__)
29
+
30
+
31
+ class VeSyncHumid200300S(BypassV2Mixin, VeSyncHumidifier):
32
+ """300S Humidifier Class.
33
+
34
+ Primary class for VeSync humidifier devices.
35
+
36
+ Args:
37
+ details (ResponseDeviceDetailsModel): The device details.
38
+ manager (VeSync): The manager object for API calls.
39
+ feature_map (HumidifierMap): The feature map for the device.
40
+
41
+ Attributes:
42
+ state (HumidifierState): The state of the humidifier.
43
+ last_response (ResponseInfo): Last response info from API call.
44
+ manager (VeSync): Manager object for API calls.
45
+ device_name (str): Name of device.
46
+ device_image (str): URL for device image.
47
+ cid (str): Device ID.
48
+ connection_type (str): Connection type of device.
49
+ device_type (str): Type of device.
50
+ type (str): Type of device.
51
+ uuid (str): UUID of device, not always present.
52
+ config_module (str): Configuration module of device.
53
+ mac_id (str): MAC ID of device.
54
+ current_firm_version (str): Current firmware version of device.
55
+ device_region (str): Region of device. (US, EU, etc.)
56
+ pid (str): Product ID of device, pulled by some devices on update.
57
+ sub_device_no (int): Sub-device number of device.
58
+ product_type (str): Product type of device.
59
+ features (dict): Features of device.
60
+ mist_levels (list): List of mist levels.
61
+ mist_modes (list): List of mist modes.
62
+ target_minmax (tuple): Tuple of target min and max values.
63
+ warm_mist_levels (list): List of warm mist levels.
64
+ """
65
+
66
+ __slots__ = ()
67
+
68
+ def __init__(self, details: ResponseDeviceDetailsModel,
69
+ manager: VeSync, feature_map: HumidifierMap) -> None:
70
+ """Initialize 200S/300S Humidifier class."""
71
+ super().__init__(details, manager, feature_map)
72
+
73
+ def _set_state(self, resp_model: ClassicLVHumidResult) -> None:
74
+ """Set state from get_details API model."""
75
+ self.state.connection_status = ConnectionStatus.ONLINE
76
+ self.state.device_status = DeviceStatus.from_bool(resp_model.enabled)
77
+ self.state.mode = resp_model.mode
78
+ self.state.humidity = resp_model.humidity or IntFlag.NOT_SUPPORTED
79
+ self.state.mist_virtual_level = resp_model.mist_virtual_level or 0
80
+ self.state.mist_level = resp_model.mist_level or 0
81
+ self.state.water_lacks = resp_model.water_lacks
82
+ self.state.humidity_high = resp_model.humidity_high
83
+ self.state.water_tank_lifted = resp_model.water_tank_lifted
84
+ self.state.auto_stop_target_reached = resp_model.automatic_stop_reach_target
85
+ if (
86
+ self.supports_nightlight
87
+ and resp_model.night_light_brightness != IntFlag.NOT_SUPPORTED
88
+ ):
89
+ self.state.nightlight_brightness = resp_model.night_light_brightness
90
+ self.state.nightlight_status = (
91
+ DeviceStatus.ON
92
+ if resp_model.night_light_brightness > 0
93
+ else DeviceStatus.OFF
94
+ )
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:
97
+ self.state.warm_mist_level = resp_model.warm_level
98
+ self.state.warm_mist_enabled = resp_model.warm_enabled
99
+ config = resp_model.configuration
100
+ if config is not None:
101
+ self.state.auto_target_humidity = config.auto_target_humidity
102
+ self.state.automatic_stop_config = config.automatic_stop
103
+ self.state.display_set_status = DeviceStatus.from_bool(config.display)
104
+
105
+ async def get_details(self) -> None:
106
+ r_dict = await self.call_bypassv2_api("getHumidifierStatus")
107
+ r_model = process_bypassv2_result(
108
+ self, logger, 'get_details', r_dict, ClassicLVHumidResult
109
+ )
110
+ if r_model is None:
111
+ return
112
+ self._set_state(r_model)
113
+
114
+ async def toggle_switch(self, toggle: bool | None = None) -> bool:
115
+ if toggle is None:
116
+ toggle = self.state.device_status == DeviceStatus.ON
117
+
118
+ payload_data = {'enabled': toggle, 'id': 0}
119
+ r_dict = await self.call_bypassv2_api("setSwitch", payload_data)
120
+ r = Helpers.process_dev_response(logger, "toggle_switch", self, r_dict)
121
+ if r is None:
122
+ return False
123
+
124
+ self.state.device_status = DeviceStatus.from_bool(toggle)
125
+ self.state.connection_status = ConnectionStatus.ONLINE
126
+ return True
127
+
128
+ async def get_timer(self) -> Timer | None:
129
+ r_dict = await self.call_bypassv2_api("getTimer")
130
+ result_model = process_bypassv2_result(
131
+ self, logger, "get_timer", r_dict, ResultV2GetTimer
132
+ )
133
+ if result_model is None:
134
+ return None
135
+ if not result_model.timers:
136
+ logger.debug("No timers found")
137
+ return None
138
+ timer = result_model.timers[0]
139
+ self.state.timer = Timer(
140
+ timer_duration=timer.total,
141
+ action=timer.action,
142
+ id=timer.id,
143
+ )
144
+ return self.state.timer
145
+
146
+ async def clear_timer(self) -> bool:
147
+ if self.state.timer is None:
148
+ logger.debug("No timer to clear, run get_timer() first.")
149
+ return False
150
+ payload = {
151
+ "id": self.state.timer.id,
152
+ }
153
+ r_dict = await self.call_bypassv2_api("delTimer", payload)
154
+ r = Helpers.process_dev_response(logger, "clear_timer", self, r_dict)
155
+ if r is None:
156
+ return False
157
+ self.state.timer = None
158
+ return True
159
+
160
+ async def set_timer(self, duration: int, action: str | None = None) -> bool:
161
+ if action is None:
162
+ action = (
163
+ DeviceStatus.OFF
164
+ if self.state.device_status == DeviceStatus.ON
165
+ else DeviceStatus.ON
166
+ )
167
+ payload_data = {
168
+ "action": str(action),
169
+ "total": duration,
170
+ }
171
+ r_dict = await self.call_bypassv2_api("addTimer", payload_data)
172
+ r = process_bypassv2_result(self, logger, "set_timer", r_dict, ResultV2SetTimer)
173
+ if r is None:
174
+ return False
175
+
176
+ self.state.timer = Timer(
177
+ timer_duration=duration, action=action, id=r.id, remaining=0
178
+ )
179
+ return True
180
+
181
+ @deprecated("Use turn_on_automatic_stop() instead.")
182
+ async def automatic_stop_on(self) -> bool:
183
+ """Turn 200S/300S Humidifier automatic stop on."""
184
+ return await self.toggle_automatic_stop(True)
185
+
186
+ @deprecated("Use turn_off_automatic_stop() instead.")
187
+ async def automatic_stop_off(self) -> bool:
188
+ """Turn 200S/300S Humidifier automatic stop on."""
189
+ return await self.toggle_automatic_stop(False)
190
+
191
+ @deprecated("Use toggle_automatic_stop(toggle: bool) instead.")
192
+ async def set_automatic_stop(self, mode: bool) -> bool:
193
+ """Set 200S/300S Humidifier to automatic stop."""
194
+ return await self.toggle_automatic_stop(mode)
195
+
196
+ async def toggle_automatic_stop(self, toggle: bool | None = None) -> bool:
197
+ if toggle is None:
198
+ toggle = self.state.automatic_stop_config != DeviceStatus.ON
199
+
200
+ payload_data = {'enabled': toggle}
201
+ r_dict = await self.call_bypassv2_api("setAutomaticStop", payload_data)
202
+ r = Helpers.process_dev_response(logger, "set_automatic_stop", self, r_dict)
203
+ if r is None:
204
+ return False
205
+ self.state.automatic_stop_config = toggle
206
+ self.state.connection_status = ConnectionStatus.ONLINE
207
+ return True
208
+
209
+ @deprecated("Use toggle_display(toggle: bool) instead.")
210
+ async def set_display(self, toggle: bool) -> bool:
211
+ """Deprecated method to toggle display on/off.
212
+
213
+ Use toggle_display(toggle: bool) instead.
214
+ """
215
+ return await self.toggle_display(toggle)
216
+
217
+ async def toggle_display(self, toggle: bool) -> bool:
218
+ payload_data = {'state': toggle}
219
+ r_dict = await self.call_bypassv2_api("setDisplay", payload_data)
220
+ r = Helpers.process_dev_response(logger, "set_display", self, r_dict)
221
+ if r is None:
222
+ return False
223
+ self.state.display_set_status = DeviceStatus.from_bool(toggle)
224
+ self.state.connection_status = ConnectionStatus.ONLINE
225
+ return True
226
+
227
+ async def set_humidity(self, humidity: int) -> bool:
228
+ if not Validators.validate_range(humidity, *self.target_minmax):
229
+ logger.debug(
230
+ "Invalid humidity, must be between %s and %s", *self.target_minmax
231
+ )
232
+ return False
233
+
234
+ payload_data = {"target_humidity": humidity}
235
+ r_dict = await self.call_bypassv2_api("setTargetHumidity", payload_data)
236
+ r = Helpers.process_dev_response(logger, "set_humidity", self, r_dict)
237
+ if r is None:
238
+ return False
239
+ self.state.auto_target_humidity = humidity
240
+ self.state.connection_status = ConnectionStatus.ONLINE
241
+ return True
242
+
243
+ async def set_nightlight_brightness(self, brightness: int) -> bool:
244
+ if not self.supports_nightlight:
245
+ logger.debug('%s is a %s does not have a nightlight',
246
+ self.device_name, self.device_type)
247
+ return False
248
+
249
+ if Validators.validate_zero_to_hundred(brightness):
250
+ logger.debug("Brightness value must be set between 0 and 100")
251
+ return False
252
+
253
+ payload_data = {"night_light_brightness": brightness}
254
+ r_dict = await self.call_bypassv2_api("setNightLightBrightness", payload_data)
255
+ r = Helpers.process_dev_response(
256
+ logger, "set_night_light_brightness", self, r_dict
257
+ )
258
+ if r is None:
259
+ return False
260
+ self.state.nightlight_brightness = brightness
261
+ self.state.nightlight_status = (
262
+ DeviceStatus.ON if brightness > 0 else DeviceStatus.OFF
263
+ )
264
+ return True
265
+
266
+ @deprecated("Use set_mode(mode: str) instead.")
267
+ async def set_humidity_mode(self, mode: str) -> bool:
268
+ """Deprecated method to set humidifier mode.
269
+
270
+ Use `set_mode(mode: str)` instead.
271
+ """
272
+ return await self.set_mode(mode)
273
+
274
+ async def set_mode(self, mode: str) -> bool:
275
+ if mode.lower() not in self.mist_modes:
276
+ logger.debug("Invalid humidity mode used - %s", mode)
277
+ logger.debug(
278
+ "Proper modes for this device are - %s",
279
+ orjson.dumps(
280
+ self.mist_modes, option=orjson.OPT_INDENT_2 | orjson.OPT_NON_STR_KEYS
281
+ ),
282
+ )
283
+ return False
284
+
285
+ payload_data = {"mode": self.mist_modes[mode.lower()]}
286
+ r_dict = await self.call_bypassv2_api("setHumidityMode", payload_data)
287
+ r = Helpers.process_dev_response(logger, "set_humidity_mode", self, r_dict)
288
+ if r is None:
289
+ return False
290
+
291
+ self.state.mode = mode
292
+ self.state.connection_status = ConnectionStatus.ONLINE
293
+ return True
294
+
295
+ async def set_warm_level(self, warm_level: int) -> bool:
296
+ if not self.supports_warm_mist:
297
+ logger.debug('%s is a %s does not have a mist warmer',
298
+ self.device_name, self.device_type)
299
+ return False
300
+
301
+ if warm_level not in self.warm_mist_levels:
302
+ logger.debug("warm_level value must be - %s",
303
+ str(self.warm_mist_levels))
304
+ return False
305
+
306
+ payload_data = {"type": "warm", "level": warm_level, "id": 0}
307
+ r_dict = await self.call_bypassv2_api("setVirtualLevel", payload_data)
308
+ r = Helpers.process_dev_response(logger, "set_warm_level", self, r_dict)
309
+ if r is None:
310
+ return False
311
+
312
+ self.state.warm_mist_level = warm_level
313
+ self.state.warm_mist_enabled = True
314
+ self.state.connection_status = ConnectionStatus.ONLINE
315
+ return True
316
+
317
+ async def set_mist_level(self, level: int) -> bool:
318
+ if level not in self.mist_levels:
319
+ logger.debug(
320
+ "Humidifier mist level must be between %s and %s",
321
+ self.mist_levels[0],
322
+ self.mist_levels[-1],
323
+ )
324
+ return False
325
+
326
+ payload_data = {"id": 0, "level": level, "type": "mist"}
327
+ r_dict = await self.call_bypassv2_api("setVirtualLevel", payload_data)
328
+ r = Helpers.process_dev_response(logger, "set_mist_level", self, r_dict)
329
+ if r is None:
330
+ return False
331
+
332
+ self.state.mist_virtual_level = level
333
+ self.state.mist_level = level
334
+ self.state.connection_status = ConnectionStatus.ONLINE
335
+ return True
336
+
337
+
338
+ class VeSyncHumid200S(VeSyncHumid200300S):
339
+ """Levoit Classic 200S Specific class.
340
+
341
+ Overrides the `toggle_display(toggle: bool)` method of the
342
+ VeSyncHumid200300S class.
343
+
344
+ Args:
345
+ details (ResponseDeviceDetailsModel): The device details.
346
+ manager (VeSync): The manager object for API calls.
347
+ feature_map (HumidifierMap): The feature map for the device.
348
+
349
+ Attributes:
350
+ state (HumidifierState): The state of the humidifier.
351
+ last_response (ResponseInfo): Last response from API call.
352
+ manager (VeSync): Manager object for API calls.
353
+ device_name (str): Name of device.
354
+ device_image (str): URL for device image.
355
+ cid (str): Device ID.
356
+ connection_type (str): Connection type of device.
357
+ device_type (str): Type of device.
358
+ type (str): Type of device.
359
+ uuid (str): UUID of device, not always present.
360
+ config_module (str): Configuration module of device.
361
+ mac_id (str): MAC ID of device.
362
+ current_firm_version (str): Current firmware version of device.
363
+ device_region (str): Region of device. (US, EU, etc.)
364
+ pid (str): Product ID of device, pulled by some devices on update.
365
+ sub_device_no (int): Sub-device number of device.
366
+ product_type (str): Product type of device.
367
+ features (dict): Features of device.
368
+ mist_levels (list): List of mist levels.
369
+ mist_modes (list): List of mist modes.
370
+ target_minmax (tuple): Tuple of target min and max values.
371
+ warm_mist_levels (list): List of warm mist levels.
372
+ """
373
+
374
+ def __init__(self, details: ResponseDeviceDetailsModel,
375
+ manager: VeSync, feature_map: HumidifierMap) -> None:
376
+ """Initialize levoit 200S device class.
377
+
378
+ This overrides the `toggle_display(toggle: bool)` method of the
379
+ VeSyncHumid200300S class.
380
+ """
381
+ super().__init__(details, manager, feature_map)
382
+
383
+ async def toggle_display(self, toggle: bool) -> bool:
384
+ payload_data = {"enabled": toggle, "id": 0}
385
+ r_dict = await self.call_bypassv2_api("setIndicatorLightSwitch", payload_data)
386
+ r = Helpers.process_dev_response(logger, "toggle_display", self, r_dict)
387
+ if r is None:
388
+ return False
389
+
390
+ self.state.display_set_status = DeviceStatus.from_bool(toggle)
391
+ self.state.connection_status = ConnectionStatus.ONLINE
392
+ return True
393
+
394
+
395
+ class VeSyncSuperior6000S(BypassV2Mixin, VeSyncHumidifier):
396
+ """Superior 6000S Humidifier.
397
+
398
+ Args:
399
+ details (ResponseDeviceDetailsModel): The device details.
400
+ manager (VeSync): The manager object for API calls.
401
+ feature_map (HumidifierMap): The feature map for the device.
402
+
403
+ Attributes:
404
+ state (HumidifierState): The state of the humidifier.
405
+ last_response (ResponseInfo): Last response from API call.
406
+ manager (VeSync): Manager object for API calls.
407
+ device_name (str): Name of device.
408
+ device_image (str): URL for device image.
409
+ cid (str): Device ID.
410
+ connection_type (str): Connection type of device.
411
+ device_type (str): Type of device.
412
+ type (str): Type of device.
413
+ uuid (str): UUID of device, not always present.
414
+ config_module (str): Configuration module of device.
415
+ mac_id (str): MAC ID of device.
416
+ current_firm_version (str): Current firmware version of device.
417
+ device_region (str): Region of device. (US, EU, etc.)
418
+ pid (str): Product ID of device, pulled by some devices on update.
419
+ sub_device_no (int): Sub-device number of device.
420
+ product_type (str): Product type of device.
421
+ features (dict): Features of device.
422
+ mist_levels (list): List of mist levels.
423
+ mist_modes (list): List of mist modes.
424
+ target_minmax (tuple): Tuple of target min and max values.
425
+ warm_mist_levels (list): List of warm mist levels.
426
+ """
427
+
428
+ def __init__(self, details: ResponseDeviceDetailsModel,
429
+ manager: VeSync, feature_map: HumidifierMap) -> None:
430
+ """Initialize Superior 6000S Humidifier class."""
431
+ super().__init__(details, manager, feature_map)
432
+
433
+ def _set_state(self, resp_model: Superior6000SResult) -> None:
434
+ """Set state from Superior 6000S API result model."""
435
+ self.state.device_status = DeviceStatus.from_int(resp_model.powerSwitch)
436
+ self.state.connection_status = ConnectionStatus.ONLINE
437
+ self.state.mode = resp_model.workMode
438
+ self.state.auto_target_humidity = resp_model.targetHumidity
439
+ self.state.mist_level = resp_model.mistLevel
440
+ self.state.mist_virtual_level = resp_model.virtualLevel
441
+ self.state.water_lacks = bool(resp_model.waterLacksState)
442
+ self.state.water_tank_lifted = bool(resp_model.waterTankLifted)
443
+ self.state.automatic_stop_config = bool(resp_model.autoStopSwitch)
444
+ self.state.auto_stop_target_reached = bool(resp_model.autoStopState)
445
+ self.state.display_set_status = DeviceStatus.from_int(resp_model.screenSwitch)
446
+ self.state.display_status = DeviceStatus.from_int(resp_model.screenState)
447
+ self.state.auto_preference = resp_model.autoPreference
448
+ self.state.filter_life_percent = resp_model.filterLifePercent
449
+ self.state.temperature = resp_model.temperature # Unknown units
450
+
451
+ drying_mode = resp_model.dryingMode
452
+ if drying_mode is not None:
453
+ self.state.drying_mode_status = DeviceStatus.from_int(drying_mode.dryingState)
454
+ self.state.drying_mode_level = drying_mode.dryingLevel
455
+ self.state.drying_mode_auto_switch = DeviceStatus.from_int(
456
+ drying_mode.autoDryingSwitch
457
+ )
458
+
459
+ if resp_model.timerRemain > 0:
460
+ self.state.timer = Timer(
461
+ resp_model.timerRemain,
462
+ DeviceStatus.from_bool(self.state.device_status != DeviceStatus.ON)
463
+ )
464
+
465
+ async def get_details(self) -> None:
466
+ r_dict = await self.call_bypassv2_api("getHumidifierStatus")
467
+ r_model = process_bypassv2_result(
468
+ self, logger, "get_details", r_dict, Superior6000SResult
469
+ )
470
+ if r_model is None:
471
+ return
472
+
473
+ self._set_state(r_model)
474
+
475
+ async def toggle_switch(self, toggle: bool | None = None) -> bool:
476
+ if toggle is None:
477
+ toggle = self.state.device_status != DeviceStatus.ON
478
+
479
+ payload_data = {"powerSwitch": int(toggle), "switchIdx": 0}
480
+ r_dict = await self.call_bypassv2_api("setSwitch", payload_data)
481
+ r = Helpers.process_dev_response(logger, "toggle_switch", self, r_dict)
482
+ if r is None:
483
+ return False
484
+
485
+ self.state.device_status = DeviceStatus.from_bool(toggle)
486
+ self.state.connection_status = ConnectionStatus.ONLINE
487
+ return True
488
+
489
+ async def toggle_automatic_stop(self, toggle: bool | None = None) -> bool:
490
+ if toggle is None:
491
+ toggle = self.state.automatic_stop_config is not True
492
+
493
+ payload_data = {"autoStopSwitch": int(toggle)}
494
+ r_dict = await self.call_bypassv2_api("setAutoStopSwitch", payload_data)
495
+ r = Helpers.process_dev_response(logger, "toggle_automatic_stop", self, r_dict)
496
+ if r is None:
497
+ return False
498
+
499
+ self.state.device_status = DeviceStatus.from_bool(toggle)
500
+ self.state.connection_status = ConnectionStatus.ONLINE
501
+ return True
502
+
503
+ @deprecated("Use toggle_drying_mode() instead.")
504
+ async def set_drying_mode_enabled(self, mode: bool) -> bool:
505
+ """Set drying mode on/off."""
506
+ return await self.toggle_drying_mode(mode)
507
+
508
+ async def toggle_drying_mode(self, toggle: bool | None = None) -> bool:
509
+ if toggle is None:
510
+ toggle = self.state.drying_mode_status != DeviceStatus.ON
511
+
512
+ payload_data = {"autoDryingSwitch": int(toggle)}
513
+ r_dict = await self.call_bypassv2_api("setDryingMode", payload_data)
514
+ r = Helpers.process_dev_response(logger, "set_drying_mode_enabled", self, r_dict)
515
+ if r is None:
516
+ return False
517
+
518
+ self.state.connection_status = ConnectionStatus.ONLINE
519
+ self.state.drying_mode_auto_switch = DeviceStatus.from_bool(toggle)
520
+ return True
521
+
522
+ @deprecated("Use toggle_display() instead.")
523
+ async def set_display_enabled(self, mode: bool) -> bool:
524
+ """Set display on/off.
525
+
526
+ Deprecated method, please use toggle_display() instead.
527
+ """
528
+ return await self.toggle_display(mode)
529
+
530
+ async def toggle_display(self, toggle: bool | None = None) -> bool:
531
+ if toggle is None:
532
+ toggle = self.state.display_set_status != DeviceStatus.ON
533
+
534
+ payload_data = {"screenSwitch": int(toggle)}
535
+ r_dict = await self.call_bypassv2_api("setDisplay", payload_data)
536
+ r = Helpers.process_dev_response(logger, "set_display", self, r_dict)
537
+ if r is None:
538
+ return False
539
+
540
+ self.state.display_set_status = DeviceStatus.from_bool(toggle)
541
+ self.state.connection_status = ConnectionStatus.ONLINE
542
+ return True
543
+
544
+ async def set_humidity(self, humidity: int) -> bool:
545
+ if Validators.validate_range(humidity, *self.target_minmax):
546
+ logger.debug("Humidity value must be set between 30 and 80")
547
+ return False
548
+
549
+ payload_data = {"targetHumidity": humidity}
550
+ r_dict = await self.call_bypassv2_api("setTargetHumidity", payload_data)
551
+ r = Helpers.process_dev_response(logger, "set_humidity", self, r_dict)
552
+ if r is None:
553
+ return False
554
+
555
+ self.state.auto_target_humidity = humidity
556
+ self.state.connection_status = ConnectionStatus.ONLINE
557
+ return True
558
+
559
+ @deprecated("Use set_mode(mode: str) instead.")
560
+ async def set_humidity_mode(self, mode: str) -> bool:
561
+ """Set humidifier mode."""
562
+ return await self.set_mode(mode)
563
+
564
+ async def set_mode(self, mode: str) -> bool:
565
+ if mode.lower() not in self.mist_modes:
566
+ logger.debug('Invalid humidity mode used - %s',
567
+ mode)
568
+ logger.debug(
569
+ "Proper modes for this device are - %s",
570
+ orjson.dumps(
571
+ self.mist_modes, option=orjson.OPT_INDENT_2 | orjson.OPT_NON_STR_KEYS
572
+ ),
573
+ )
574
+ return False
575
+
576
+ payload_data = {"workMode": self.mist_modes[mode.lower()]}
577
+ r_dict = await self.call_bypassv2_api("setHumidityMode", payload_data)
578
+
579
+ r = Helpers.process_dev_response(logger, "set_humidity_mode", self, r_dict)
580
+ if r is None:
581
+ return False
582
+ self.state.mode = mode
583
+ self.state.connection_status = ConnectionStatus.ONLINE
584
+ return True
585
+
586
+ async def set_mist_level(self, level: int) -> bool:
587
+ if level not in self.mist_levels:
588
+ logger.debug("Humidifier mist level must be between 0 and 9")
589
+ return False
590
+
591
+ payload_data = {"levelIdx": 0, "virtualLevel": level, "levelType": "mist"}
592
+ r_dict = await self.call_bypassv2_api("setVirtualLevel", payload_data)
593
+ r = Helpers.process_dev_response(logger, "set_mist_level", self, r_dict)
594
+ if r is None:
595
+ return False
596
+
597
+ self.state.mist_level = level
598
+ self.state.mist_virtual_level = level
599
+ self.state.connection_status = ConnectionStatus.ONLINE
600
+ return True
601
+
602
+
603
+ class VeSyncHumid1000S(VeSyncHumid200300S):
604
+ """Levoit OasisMist 1000S Specific class.
605
+
606
+ Args:
607
+ details (ResponseDeviceDetailsModel): The device details.
608
+ manager (VeSync): The manager object for API calls.
609
+ feature_map (HumidifierMap): The feature map for the device.
610
+
611
+ Attributes:
612
+ state (HumidifierState): The state of the humidifier.
613
+ last_response (ResponseInfo): Last response from API call.
614
+ manager (VeSync): Manager object for API calls.
615
+ device_name (str): Name of device.
616
+ device_image (str): URL for device image.
617
+ cid (str): Device ID.
618
+ connection_type (str): Connection type of device.
619
+ device_type (str): Type of device.
620
+ type (str): Type of device.
621
+ uuid (str): UUID of device, not always present.
622
+ config_module (str): Configuration module of device.
623
+ mac_id (str): MAC ID of device.
624
+ current_firm_version (str): Current firmware version of device.
625
+ device_region (str): Region of device. (US, EU, etc.)
626
+ pid (str): Product ID of device, pulled by some devices on update.
627
+ sub_device_no (int): Sub-device number of device.
628
+ product_type (str): Product type of device.
629
+ features (dict): Features of device.
630
+ mist_levels (list): List of mist levels.
631
+ mist_modes (list): List of mist modes.
632
+ target_minmax (tuple): Tuple of target min and max values.
633
+ warm_mist_levels (list): List of warm mist levels.
634
+ """
635
+
636
+ def __init__(self, details: ResponseDeviceDetailsModel,
637
+ manager: VeSync, feature_map: HumidifierMap) -> None:
638
+ """Initialize levoit 1000S device class."""
639
+ super().__init__(details, manager, feature_map)
640
+
641
+ def _set_state(self, resp_model: InnerHumidifierBaseResult) -> None:
642
+ """Set state of Levoit 1000S from API result model."""
643
+ if not isinstance(resp_model, Levoit1000SResult):
644
+ return
645
+ self.state.device_status = DeviceStatus.from_int(resp_model.powerSwitch)
646
+ self.state.connection_status = ConnectionStatus.ONLINE
647
+ self.state.mode = resp_model.workMode
648
+ self.state.auto_target_humidity = resp_model.targetHumidity
649
+ self.state.mist_level = resp_model.mistLevel
650
+ self.state.mist_virtual_level = resp_model.virtualLevel
651
+ self.state.water_lacks = bool(resp_model.waterLacksState)
652
+ self.state.water_tank_lifted = bool(resp_model.waterTankLifted)
653
+ self.state.automatic_stop_config = bool(resp_model.autoStopSwitch)
654
+ self.state.auto_stop_target_reached = bool(resp_model.autoStopState)
655
+ self.state.display_set_status = DeviceStatus.from_int(resp_model.screenSwitch)
656
+ self.state.display_status = DeviceStatus.from_int(resp_model.screenState)
657
+
658
+ async def get_details(self) -> None:
659
+ r_dict = await self.call_bypassv2_api("getHumidifierStatus")
660
+ r_model = process_bypassv2_result(
661
+ self, logger, 'get_details', r_dict, Levoit1000SResult
662
+ )
663
+ if r_model is None:
664
+ return
665
+
666
+ self._set_state(r_model)
667
+
668
+ @deprecated("Use toggle_switch() instead.")
669
+ async def set_display(self, toggle: bool) -> bool:
670
+ """Toggle display on/off.
671
+
672
+ This is a deprecated method, please use toggle_display() instead.
673
+ """
674
+ return await self.toggle_switch(toggle)
675
+
676
+ async def toggle_display(self, toggle: bool) -> bool:
677
+ payload_data = {"screenSwitch": int(toggle)}
678
+ body = self._build_request("setDisplay", payload_data)
679
+ headers = Helpers.req_header_bypass()
680
+
681
+ r_dict, _ = await self.manager.async_call_api(
682
+ '/cloud/v2/deviceManaged/bypassV2',
683
+ method='post',
684
+ headers=headers,
685
+ json_object=body.to_dict(),
686
+ )
687
+ r = Helpers.process_dev_response(logger, "set_display", self, r_dict)
688
+ if r is None:
689
+ return False
690
+ self.state.display_set_status = DeviceStatus.from_bool(toggle)
691
+ self.state.connection_status = ConnectionStatus.ONLINE
692
+ return True
693
+
694
+ @deprecated("Use set_mode() instead.")
695
+ async def set_humidity_mode(self, mode: str) -> bool:
696
+ """Set humidifier mode - sleep, auto or manual.
697
+
698
+ Deprecated, please use set_mode() instead.
699
+ """
700
+ return await self.set_mode(mode)
701
+
702
+ async def set_mode(self, mode: str) -> bool:
703
+ if mode.lower() not in self.mist_modes:
704
+ logger.debug("Invalid humidity mode used - %s", mode)
705
+ logger.debug(
706
+ "Proper modes for this device are - %s",
707
+ orjson.dumps(
708
+ self.mist_modes, option=orjson.OPT_INDENT_2 | orjson.OPT_NON_STR_KEYS
709
+ ),
710
+ )
711
+ return False
712
+
713
+ payload_data = {"workMode": mode.lower()}
714
+ r_dict = await self.call_bypassv2_api("setHumidityMode", payload_data)
715
+ r = Helpers.process_dev_response(logger, "set_mode", self, r_dict)
716
+ if r is None:
717
+ return False
718
+
719
+ self.state.mode = mode
720
+ self.state.connection_status = ConnectionStatus.ONLINE
721
+ return True
722
+
723
+ async def set_mist_level(self, level: int) -> bool:
724
+ if level not in self.mist_levels:
725
+ logger.debug('Humidifier mist level out of range')
726
+ return False
727
+
728
+ payload_data = {"levelIdx": 0, "virtualLevel": level, "levelType": "mist"}
729
+ r_dict = await self.call_bypassv2_api('virtualLevel', payload_data)
730
+ r = Helpers.process_dev_response(logger, "set_mist_level", self, r_dict)
731
+ if r is None:
732
+ return False
733
+
734
+ self.state.mist_level = level
735
+ self.state.mist_virtual_level = level
736
+ self.state.connection_status = ConnectionStatus.ONLINE
737
+ return True
738
+
739
+ async def toggle_switch(self, toggle: bool | None = None) -> bool:
740
+ if toggle is None:
741
+ toggle = self.state.device_status != DeviceStatus.ON
742
+
743
+ payload_data = {"powerSwitch": int(toggle), "switchIdx": 0}
744
+ r_dict = await self.call_bypassv2_api("setSwitch", payload_data)
745
+ r = Helpers.process_dev_response(logger, "toggle_switch", self, r_dict)
746
+ if r is None:
747
+ return False
748
+
749
+ self.state.device_status = DeviceStatus.from_bool(toggle)
750
+ self.state.connection_status = ConnectionStatus.ONLINE
751
+ return True
752
+
753
+ async def set_humidity(self, humidity: int) -> bool:
754
+ if Validators.validate_range(humidity, *self.target_minmax):
755
+ logger.debug("Humidity value must be set between %s and %s",
756
+ self.target_minmax[0], self.target_minmax[1])
757
+ return False
758
+
759
+ payload_data = {"targetHumidity": humidity}
760
+ r_dict = await self.call_bypassv2_api("setTargetHumidity", payload_data)
761
+ r = Helpers.process_dev_response(logger, "set_humidity", self, r_dict)
762
+ if r is None:
763
+ return False
764
+
765
+ self.state.auto_target_humidity = humidity
766
+ self.state.connection_status = ConnectionStatus.ONLINE
767
+ return True
768
+
769
+ @deprecated("Use toggle_automatic_stop() instead.")
770
+ async def set_automatic_stop(self, mode: bool) -> bool:
771
+ return await self.toggle_automatic_stop(mode)
772
+
773
+ async def toggle_automatic_stop(self, toggle: bool | None = None) -> bool:
774
+ if toggle is None:
775
+ toggle = self.state.automatic_stop_config != DeviceStatus.ON
776
+
777
+ payload_data = {"autoStopSwitch": int(toggle)}
778
+ r_dict = await self.call_bypassv2_api("setAutoStopSwitch", payload_data)
779
+ r = Helpers.process_dev_response(logger, "set_automatic_stop", self, r_dict)
780
+
781
+ if r is None:
782
+ return False
783
+ self.state.automatic_stop_config = toggle
784
+ self.state.connection_status = ConnectionStatus.ONLINE
785
+ return True