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