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.
- pyvesync/__init__.py +5 -0
- pyvesync/base_devices/__init__.py +35 -0
- pyvesync/base_devices/bulb_base.py +330 -0
- pyvesync/base_devices/fan_base.py +392 -0
- pyvesync/base_devices/fryer_base.py +68 -0
- pyvesync/base_devices/humidifier_base.py +462 -0
- pyvesync/base_devices/outlet_base.py +327 -0
- pyvesync/base_devices/purifier_base.py +525 -0
- pyvesync/base_devices/switch_base.py +271 -0
- pyvesync/base_devices/thermostat_base.py +283 -0
- pyvesync/base_devices/vesyncbasedevice.py +572 -0
- pyvesync/const.py +825 -0
- pyvesync/device_container.py +373 -0
- pyvesync/device_map.py +1172 -0
- pyvesync/devices/__init__.py +1 -0
- pyvesync/devices/vesyncbulb.py +1126 -0
- pyvesync/devices/vesyncfan.py +233 -0
- pyvesync/devices/vesynchumidifier.py +808 -0
- pyvesync/devices/vesynckitchen.py +651 -0
- pyvesync/devices/vesyncoutlet.py +954 -0
- pyvesync/devices/vesyncpurifier.py +990 -0
- pyvesync/devices/vesyncswitch.py +415 -0
- pyvesync/devices/vesyncthermostat.py +171 -0
- pyvesync/models/__init__.py +11 -0
- pyvesync/models/base_models.py +129 -0
- pyvesync/models/bulb_models.py +197 -0
- pyvesync/models/bypass_models.py +289 -0
- pyvesync/models/fan_models.py +47 -0
- pyvesync/models/fryer_models.py +34 -0
- pyvesync/models/home_models.py +169 -0
- pyvesync/models/humidifier_models.py +202 -0
- pyvesync/models/outlet_models.py +220 -0
- pyvesync/models/purifier_models.py +261 -0
- pyvesync/models/switch_models.py +101 -0
- pyvesync/models/thermostat_models.py +134 -0
- pyvesync/models/vesync_models.py +439 -0
- pyvesync/utils/__init__.py +1 -0
- pyvesync/utils/colors.py +250 -0
- pyvesync/utils/device_mixins.py +362 -0
- pyvesync/utils/enum_utils.py +26 -0
- pyvesync/utils/errors.py +867 -0
- pyvesync/utils/helpers.py +700 -0
- pyvesync/utils/logs.py +525 -0
- pyvesync/vesync.py +638 -0
- pyvesync/vesynchome.py +152 -0
- pyvesync-3.0.0.dist-info/METADATA +567 -0
- pyvesync-3.0.0.dist-info/RECORD +50 -0
- pyvesync-3.0.0.dist-info/WHEEL +5 -0
- pyvesync-3.0.0.dist-info/licenses/LICENSE +21 -0
- pyvesync-3.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
"""Module for VeSync Fans (Not Purifiers or Humidifiers)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from typing import TYPE_CHECKING
|
|
7
|
+
|
|
8
|
+
from pyvesync.base_devices import VeSyncFanBase
|
|
9
|
+
from pyvesync.const import ConnectionStatus, DeviceStatus
|
|
10
|
+
from pyvesync.models.bypass_models import TimerModels
|
|
11
|
+
from pyvesync.models.fan_models import TowerFanResult
|
|
12
|
+
from pyvesync.utils.device_mixins import BypassV2Mixin, process_bypassv2_result
|
|
13
|
+
from pyvesync.utils.helpers import Helpers, Timer
|
|
14
|
+
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
from pyvesync import VeSync
|
|
17
|
+
from pyvesync.device_map import FanMap
|
|
18
|
+
from pyvesync.models.vesync_models import ResponseDeviceDetailsModel
|
|
19
|
+
|
|
20
|
+
logger = logging.getLogger(__name__)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class VeSyncTowerFan(BypassV2Mixin, VeSyncFanBase):
|
|
24
|
+
"""Levoit Tower Fan Device Class."""
|
|
25
|
+
|
|
26
|
+
__slots__ = ()
|
|
27
|
+
|
|
28
|
+
def __init__(
|
|
29
|
+
self,
|
|
30
|
+
details: ResponseDeviceDetailsModel,
|
|
31
|
+
manager: VeSync,
|
|
32
|
+
feature_map: FanMap,
|
|
33
|
+
) -> None:
|
|
34
|
+
"""Initialize the VeSync Base API V2 Fan Class."""
|
|
35
|
+
super().__init__(details, manager, feature_map)
|
|
36
|
+
|
|
37
|
+
def _set_fan_state(self, res: TowerFanResult) -> None:
|
|
38
|
+
"""Set fan state attributes from API response."""
|
|
39
|
+
self.state.connection_status = ConnectionStatus.ONLINE
|
|
40
|
+
self.state.device_status = DeviceStatus.from_int(res.powerSwitch)
|
|
41
|
+
self.state.mode = res.workMode
|
|
42
|
+
self.state.fan_level = res.fanSpeedLevel
|
|
43
|
+
self.state.fan_set_level = res.manualSpeedLevel
|
|
44
|
+
self.state.temperature = res.temperature
|
|
45
|
+
|
|
46
|
+
self.state.mute_status = DeviceStatus.from_int(res.muteState)
|
|
47
|
+
self.state.mute_set_status = DeviceStatus.from_int(res.muteSwitch)
|
|
48
|
+
self.state.oscillation_status = DeviceStatus.from_int(res.oscillationState)
|
|
49
|
+
self.state.oscillation_set_status = DeviceStatus.from_int(res.oscillationSwitch)
|
|
50
|
+
self.state.display_status = DeviceStatus.from_int(res.screenState)
|
|
51
|
+
self.state.display_set_status = DeviceStatus.from_int(res.screenSwitch)
|
|
52
|
+
self.state.displaying_type = DeviceStatus.from_int(res.displayingType)
|
|
53
|
+
|
|
54
|
+
if res.timerRemain is not None and res.timerRemain > 0:
|
|
55
|
+
if self.state.device_status == DeviceStatus.ON:
|
|
56
|
+
self.state.timer = Timer(res.timerRemain, action='off')
|
|
57
|
+
else:
|
|
58
|
+
self.state.timer = Timer(res.timerRemain, action='on')
|
|
59
|
+
if res.sleepPreference is None:
|
|
60
|
+
return
|
|
61
|
+
self.state.sleep_preference_type = res.sleepPreference.sleepPreferenceType
|
|
62
|
+
self.state.sleep_fallasleep_remain = DeviceStatus.from_int(
|
|
63
|
+
res.sleepPreference.fallAsleepRemain
|
|
64
|
+
)
|
|
65
|
+
self.state.sleep_oscillation_switch = DeviceStatus.from_int(
|
|
66
|
+
res.sleepPreference.oscillationSwitch
|
|
67
|
+
)
|
|
68
|
+
self.state.sleep_change_fan_level = DeviceStatus.from_int(
|
|
69
|
+
res.sleepPreference.autoChangeFanLevelSwitch
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
async def get_details(self) -> None:
|
|
73
|
+
r_dict = await self.call_bypassv2_api('getTowerFanStatus')
|
|
74
|
+
result = process_bypassv2_result(
|
|
75
|
+
self, logger, 'get_details', r_dict, TowerFanResult
|
|
76
|
+
)
|
|
77
|
+
if result is None:
|
|
78
|
+
return
|
|
79
|
+
self._set_fan_state(result)
|
|
80
|
+
|
|
81
|
+
async def set_fan_speed(self, speed: int | None = None) -> bool:
|
|
82
|
+
if speed is None:
|
|
83
|
+
new_speed = Helpers.bump_level(self.state.fan_level, self.fan_levels)
|
|
84
|
+
else:
|
|
85
|
+
new_speed = speed
|
|
86
|
+
|
|
87
|
+
if new_speed not in self.fan_levels:
|
|
88
|
+
logger.debug('Invalid fan speed level used - %s', speed)
|
|
89
|
+
return False
|
|
90
|
+
|
|
91
|
+
payload_data = {'manualSpeedLevel': speed, 'levelType': 'wind', 'levelIdx': 0}
|
|
92
|
+
r_dict = await self.call_bypassv2_api('setLevel', payload_data)
|
|
93
|
+
r = Helpers.process_dev_response(logger, 'set_fan_speed', self, r_dict)
|
|
94
|
+
if r is None:
|
|
95
|
+
return False
|
|
96
|
+
|
|
97
|
+
self.state.fan_level = speed
|
|
98
|
+
self.state.connection_status = ConnectionStatus.ONLINE
|
|
99
|
+
return True
|
|
100
|
+
|
|
101
|
+
async def set_mode(self, mode: str) -> bool:
|
|
102
|
+
if mode.lower() == DeviceStatus.OFF:
|
|
103
|
+
logger.warning('Deprecated - Use `turn_off` method to turn off device')
|
|
104
|
+
return await self.turn_off()
|
|
105
|
+
|
|
106
|
+
if mode.lower() not in self.modes:
|
|
107
|
+
logger.debug('Invalid purifier mode used - %s', mode)
|
|
108
|
+
return False
|
|
109
|
+
|
|
110
|
+
payload_data = {'workMode': mode}
|
|
111
|
+
r_dict = await self.call_bypassv2_api('setTowerFanMode', payload_data)
|
|
112
|
+
r = Helpers.process_dev_response(logger, 'set_mode', self, r_dict)
|
|
113
|
+
if r is None:
|
|
114
|
+
return False
|
|
115
|
+
|
|
116
|
+
self.state.mode = mode
|
|
117
|
+
self.state.connection_status = ConnectionStatus.ONLINE
|
|
118
|
+
return True
|
|
119
|
+
|
|
120
|
+
async def toggle_switch(self, toggle: bool | None = None) -> bool:
|
|
121
|
+
if toggle is None:
|
|
122
|
+
toggle = self.state.device_status == DeviceStatus.OFF
|
|
123
|
+
payload_data = {
|
|
124
|
+
'powerSwitch': int(toggle),
|
|
125
|
+
'switchIdx': 0,
|
|
126
|
+
}
|
|
127
|
+
r_dict = await self.call_bypassv2_api('setSwitch', payload_data)
|
|
128
|
+
r = Helpers.process_dev_response(logger, 'toggle_switch', self, r_dict)
|
|
129
|
+
if r is None:
|
|
130
|
+
return False
|
|
131
|
+
|
|
132
|
+
self.state.device_status = DeviceStatus.from_bool(toggle)
|
|
133
|
+
self.state.connection_status = ConnectionStatus.ONLINE
|
|
134
|
+
return True
|
|
135
|
+
|
|
136
|
+
async def toggle_oscillation(self, toggle: bool) -> bool:
|
|
137
|
+
payload_data = {'oscillationSwitch': int(toggle)}
|
|
138
|
+
r_dict = await self.call_bypassv2_api('setOscillationSwitch', payload_data)
|
|
139
|
+
r = Helpers.process_dev_response(logger, 'toggle_oscillation', self, r_dict)
|
|
140
|
+
if r is None:
|
|
141
|
+
return False
|
|
142
|
+
|
|
143
|
+
self.state.oscillation_status = DeviceStatus.from_bool(toggle)
|
|
144
|
+
self.state.connection_status = ConnectionStatus.ONLINE
|
|
145
|
+
return True
|
|
146
|
+
|
|
147
|
+
async def toggle_mute(self, toggle: bool) -> bool:
|
|
148
|
+
payload_data = {'muteSwitch': int(toggle)}
|
|
149
|
+
r_dict = await self.call_bypassv2_api('setMuteSwitch', payload_data)
|
|
150
|
+
r = Helpers.process_dev_response(logger, 'toggle_mute', self, r_dict)
|
|
151
|
+
if r is None:
|
|
152
|
+
return False
|
|
153
|
+
|
|
154
|
+
self.state.mute_status = DeviceStatus.from_bool(toggle)
|
|
155
|
+
self.state.connection_status = ConnectionStatus.ONLINE
|
|
156
|
+
return True
|
|
157
|
+
|
|
158
|
+
async def toggle_display(self, toggle: bool) -> bool:
|
|
159
|
+
payload_data = {'screenSwitch': int(toggle)}
|
|
160
|
+
r_dict = await self.call_bypassv2_api('setDisplay', payload_data)
|
|
161
|
+
r = Helpers.process_dev_response(logger, 'toggle_display', self, r_dict)
|
|
162
|
+
if r is None:
|
|
163
|
+
return False
|
|
164
|
+
|
|
165
|
+
self.state.display_status = DeviceStatus.from_bool(toggle)
|
|
166
|
+
self.state.connection_status = ConnectionStatus.ONLINE
|
|
167
|
+
return True
|
|
168
|
+
|
|
169
|
+
async def toggle_displaying_type(self, toggle: bool) -> bool:
|
|
170
|
+
"""Set displaying type on/off - Unknown functionality."""
|
|
171
|
+
payload_data = {'displayingType': int(toggle)}
|
|
172
|
+
r_dict = await self.call_bypassv2_api('setDisplayingType', payload_data)
|
|
173
|
+
r = Helpers.process_dev_response(logger, 'toggle_displaying_type', self, r_dict)
|
|
174
|
+
if r is None:
|
|
175
|
+
return False
|
|
176
|
+
|
|
177
|
+
self.state.displaying_type = DeviceStatus.from_bool(toggle)
|
|
178
|
+
self.state.connection_status = ConnectionStatus.ONLINE
|
|
179
|
+
return True
|
|
180
|
+
|
|
181
|
+
async def get_timer(self) -> None:
|
|
182
|
+
r_dict = await self.call_bypassv2_api('getTimer', {})
|
|
183
|
+
r_model = process_bypassv2_result(
|
|
184
|
+
self, logger, 'get_timer', r_dict, TimerModels.ResultV2GetTimer
|
|
185
|
+
)
|
|
186
|
+
if not r_model:
|
|
187
|
+
logger.debug('No timer found - run get_timer()')
|
|
188
|
+
return
|
|
189
|
+
if r_model.timers is None or len(r_model.timers) == 0:
|
|
190
|
+
logger.debug('No timer found - run get_timer()')
|
|
191
|
+
return
|
|
192
|
+
timer = r_model.timers[0]
|
|
193
|
+
if not isinstance(timer, TimerModels.TimerItemV2):
|
|
194
|
+
logger.debug('Invalid timer found for %s', self.device_name)
|
|
195
|
+
return
|
|
196
|
+
self.state.timer = Timer(timer.remain, timer.action, timer.id)
|
|
197
|
+
|
|
198
|
+
async def set_timer(self, duration: int, action: str | None = None) -> bool:
|
|
199
|
+
if action is None:
|
|
200
|
+
action = (
|
|
201
|
+
DeviceStatus.OFF
|
|
202
|
+
if self.state.device_status == DeviceStatus.ON
|
|
203
|
+
else DeviceStatus.ON
|
|
204
|
+
)
|
|
205
|
+
if action not in [DeviceStatus.ON, DeviceStatus.OFF]:
|
|
206
|
+
logger.debug('Invalid action used - %s', action)
|
|
207
|
+
return False
|
|
208
|
+
payload_data = {
|
|
209
|
+
'action': action,
|
|
210
|
+
'total': duration,
|
|
211
|
+
}
|
|
212
|
+
r_dict = await self.call_bypassv2_api('setTimer', payload_data)
|
|
213
|
+
r_model = process_bypassv2_result(
|
|
214
|
+
self, logger, 'set_timer', r_dict, TimerModels.ResultV2SetTimer
|
|
215
|
+
)
|
|
216
|
+
if r_model is None:
|
|
217
|
+
return False
|
|
218
|
+
self.state.timer = Timer(duration, action, r_model.id)
|
|
219
|
+
return True
|
|
220
|
+
|
|
221
|
+
async def clear_timer(self) -> bool:
|
|
222
|
+
if self.state.timer is None:
|
|
223
|
+
logger.debug('No timer found for - run get_timer()')
|
|
224
|
+
return False
|
|
225
|
+
payload_data = {
|
|
226
|
+
'id': self.state.timer.id,
|
|
227
|
+
}
|
|
228
|
+
r_dict = await self.call_bypassv2_api('clearTimer', payload_data)
|
|
229
|
+
result = Helpers.process_dev_response(logger, 'clear_timer', self, r_dict)
|
|
230
|
+
if result is None:
|
|
231
|
+
return False
|
|
232
|
+
self.state.timer = None
|
|
233
|
+
return True
|