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,327 @@
|
|
|
1
|
+
"""Base Devices for Outlets."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from typing import TYPE_CHECKING
|
|
7
|
+
|
|
8
|
+
from pyvesync.base_devices.vesyncbasedevice import DeviceState, VeSyncBaseToggleDevice
|
|
9
|
+
from pyvesync.const import NightlightModes, OutletFeatures
|
|
10
|
+
from pyvesync.models.base_models import DefaultValues
|
|
11
|
+
from pyvesync.models.outlet_models import RequestEnergyHistory, ResponseEnergyHistory
|
|
12
|
+
from pyvesync.utils.helpers import Helpers
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from pyvesync import VeSync
|
|
16
|
+
from pyvesync.device_map import OutletMap
|
|
17
|
+
from pyvesync.models.outlet_models import ResponseEnergyResult
|
|
18
|
+
from pyvesync.models.vesync_models import ResponseDeviceDetailsModel
|
|
19
|
+
|
|
20
|
+
logger = logging.getLogger(__name__)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class OutletState(DeviceState):
|
|
24
|
+
"""Base state class for Outlets.
|
|
25
|
+
|
|
26
|
+
This class holds all of the state information for the outlet devices. The state
|
|
27
|
+
instance is stored in the `state` attribute of the outlet device. This is only
|
|
28
|
+
for holding state information and does not contain any methods for controlling
|
|
29
|
+
the device or retrieving information from the API.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
device (VeSyncOutlet): The device object.
|
|
33
|
+
details (ResponseDeviceDetailsModel): The device details.
|
|
34
|
+
feature_map (OutletMap): The feature map for the device.
|
|
35
|
+
|
|
36
|
+
Attributes:
|
|
37
|
+
active_time (int): Active time of device, defaults to None.
|
|
38
|
+
connection_status (str): Connection status of device.
|
|
39
|
+
device (VeSyncOutlet): Device object.
|
|
40
|
+
device_status (str): Device status.
|
|
41
|
+
features (dict): Features of device.
|
|
42
|
+
last_update_ts (int): Last update timestamp of device, defaults to None.
|
|
43
|
+
energy (float): Energy usage in kWh.
|
|
44
|
+
monthly_history (ResponseEnergyResult): Monthly energy history.
|
|
45
|
+
nightlight_automode (str): Nightlight automode status.
|
|
46
|
+
nightlight_brightness (int): Nightlight brightness level.
|
|
47
|
+
nightlight_status (str): Nightlight status.
|
|
48
|
+
power (float): Power usage in Watts.
|
|
49
|
+
voltage (float): Voltage in Volts.
|
|
50
|
+
weekly_history (ResponseEnergyResult): Weekly energy history.
|
|
51
|
+
yearly_history (ResponseEnergyResult): Yearly energy history.
|
|
52
|
+
|
|
53
|
+
Methods:
|
|
54
|
+
update_ts: Update last update timestamp.
|
|
55
|
+
to_dict: Dump state to JSON.
|
|
56
|
+
to_json: Dump state to JSON string.
|
|
57
|
+
to_jsonb: Dump state to JSON bytes.
|
|
58
|
+
as_tuple: Convert state to tuple of (name, value) tuples.
|
|
59
|
+
|
|
60
|
+
Note:
|
|
61
|
+
Not all attributes are available on all devices. Some attributes may be None or
|
|
62
|
+
not supported depending on the device type and features. The attributes are set
|
|
63
|
+
based on the device features and the API response.
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
__slots__ = (
|
|
67
|
+
'energy',
|
|
68
|
+
'monthly_history',
|
|
69
|
+
'nightlight_automode',
|
|
70
|
+
'nightlight_brightness',
|
|
71
|
+
'nightlight_status',
|
|
72
|
+
'power',
|
|
73
|
+
'voltage',
|
|
74
|
+
'weekly_history',
|
|
75
|
+
'yearly_history',
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
def __init__(
|
|
79
|
+
self,
|
|
80
|
+
device: VeSyncOutlet,
|
|
81
|
+
details: ResponseDeviceDetailsModel,
|
|
82
|
+
feature_map: OutletMap,
|
|
83
|
+
) -> None:
|
|
84
|
+
"""Initialize VeSync Switch State."""
|
|
85
|
+
super().__init__(device, details, feature_map)
|
|
86
|
+
self._exclude_serialization = [
|
|
87
|
+
'weakly_history',
|
|
88
|
+
'monthly_history',
|
|
89
|
+
'yearly_history',
|
|
90
|
+
]
|
|
91
|
+
self.device: VeSyncOutlet = device
|
|
92
|
+
self.features: list[str] = feature_map.features
|
|
93
|
+
self.active_time: int | None = 0
|
|
94
|
+
self.power: float | None = None
|
|
95
|
+
self.energy: float | None = None
|
|
96
|
+
self.voltage: float | None = None
|
|
97
|
+
self.nightlight_status: str | None = None
|
|
98
|
+
self.nightlight_brightness: int | None = None
|
|
99
|
+
self.nightlight_automode: str | None = None
|
|
100
|
+
self.weekly_history: ResponseEnergyResult | None = None
|
|
101
|
+
self.monthly_history: ResponseEnergyResult | None = None
|
|
102
|
+
self.yearly_history: ResponseEnergyResult | None = None
|
|
103
|
+
|
|
104
|
+
def annual_history_to_json(self) -> None | str:
|
|
105
|
+
"""Dump annual history."""
|
|
106
|
+
if not self.device.supports_energy:
|
|
107
|
+
logger.info('Device does not support energy monitoring.')
|
|
108
|
+
return None
|
|
109
|
+
if self.yearly_history is None:
|
|
110
|
+
logger.info('No yearly history available, run device.get_yearly_history().')
|
|
111
|
+
return None
|
|
112
|
+
return self.yearly_history.to_json()
|
|
113
|
+
|
|
114
|
+
def monthly_history_to_json(self) -> None | str:
|
|
115
|
+
"""Dump monthly history."""
|
|
116
|
+
if not self.device.supports_energy:
|
|
117
|
+
logger.info('Device does not support energy monitoring.')
|
|
118
|
+
return None
|
|
119
|
+
if self.monthly_history is None:
|
|
120
|
+
logger.info('No monthly history available, run device.get_monthly_history().')
|
|
121
|
+
return None
|
|
122
|
+
return self.monthly_history.to_json()
|
|
123
|
+
|
|
124
|
+
def weekly_history_to_json(self) -> None | str:
|
|
125
|
+
"""Dump weekly history."""
|
|
126
|
+
if not self.device.supports_energy:
|
|
127
|
+
logger.info('Device does not support energy monitoring.')
|
|
128
|
+
return None
|
|
129
|
+
if self.weekly_history is None:
|
|
130
|
+
logger.info('No weekly history available, run device.get_weekly_history().')
|
|
131
|
+
return None
|
|
132
|
+
return self.weekly_history.to_json()
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
class VeSyncOutlet(VeSyncBaseToggleDevice):
|
|
136
|
+
"""Base class for Etekcity Outlets.
|
|
137
|
+
|
|
138
|
+
State is stored in the `state` attribute of the device.
|
|
139
|
+
This is only for holding state information and does not
|
|
140
|
+
contain any methods for controlling the device or retrieving
|
|
141
|
+
information from the API.
|
|
142
|
+
|
|
143
|
+
Args:
|
|
144
|
+
details (ResponseDeviceDetailsModel): The device details.
|
|
145
|
+
manager (VeSync): The VeSync manager.
|
|
146
|
+
feature_map (OutletMap): The feature map for the device.
|
|
147
|
+
|
|
148
|
+
Attributes:
|
|
149
|
+
state (OutletState): The state of the outlet.
|
|
150
|
+
last_response (ResponseInfo): Last response from API call.
|
|
151
|
+
manager (VeSync): Manager object for API calls.
|
|
152
|
+
device_name (str): Name of device.
|
|
153
|
+
device_image (str): URL for device image.
|
|
154
|
+
cid (str): Device ID.
|
|
155
|
+
connection_type (str): Connection type of device.
|
|
156
|
+
device_type (str): Type of device.
|
|
157
|
+
type (str): Type of device.
|
|
158
|
+
uuid (str): UUID of device, not always present.
|
|
159
|
+
config_module (str): Configuration module of device.
|
|
160
|
+
mac_id (str): MAC ID of device.
|
|
161
|
+
current_firm_version (str): Current firmware version of device.
|
|
162
|
+
device_region (str): Region of device. (US, EU, etc.)
|
|
163
|
+
pid (str): Product ID of device, pulled by some devices on update.
|
|
164
|
+
sub_device_no (int): Sub-device number of device.
|
|
165
|
+
product_type (str): Product type of device.
|
|
166
|
+
features (dict): Features of device.
|
|
167
|
+
"""
|
|
168
|
+
|
|
169
|
+
def __init__(
|
|
170
|
+
self, details: ResponseDeviceDetailsModel, manager: VeSync, feature_map: OutletMap
|
|
171
|
+
) -> None:
|
|
172
|
+
"""Initialize VeSync Outlet base class."""
|
|
173
|
+
super().__init__(details, manager, feature_map)
|
|
174
|
+
self.state: OutletState = OutletState(self, details, feature_map)
|
|
175
|
+
self.nightlight_modes = feature_map.nightlight_modes
|
|
176
|
+
|
|
177
|
+
def _build_energy_request(self, method: str) -> RequestEnergyHistory:
|
|
178
|
+
"""Build energy request post."""
|
|
179
|
+
request_keys = [
|
|
180
|
+
'acceptLanguage',
|
|
181
|
+
'accountID',
|
|
182
|
+
'appVersion',
|
|
183
|
+
'phoneBrand',
|
|
184
|
+
'phoneOS',
|
|
185
|
+
'timeZone',
|
|
186
|
+
'token',
|
|
187
|
+
'traceId',
|
|
188
|
+
'userCountryCode',
|
|
189
|
+
'debugMode',
|
|
190
|
+
'homeTimeZone',
|
|
191
|
+
'uuid',
|
|
192
|
+
]
|
|
193
|
+
body = Helpers.get_class_attributes(DefaultValues, request_keys)
|
|
194
|
+
body.update(Helpers.get_class_attributes(self.manager, request_keys))
|
|
195
|
+
body.update(Helpers.get_class_attributes(self, request_keys))
|
|
196
|
+
body['method'] = method
|
|
197
|
+
return RequestEnergyHistory.from_dict(body)
|
|
198
|
+
|
|
199
|
+
async def _get_energy_history(self, history_interval: str) -> None:
|
|
200
|
+
"""Pull energy history from API.
|
|
201
|
+
|
|
202
|
+
Args:
|
|
203
|
+
history_interval (str): The interval for the energy history,
|
|
204
|
+
options are 'getLastWeekEnergy', 'getLastMonthEnergy', 'getLastYearEnergy'
|
|
205
|
+
|
|
206
|
+
Note:
|
|
207
|
+
Builds the state.<history_interval>_history attribute.
|
|
208
|
+
"""
|
|
209
|
+
if not self.supports_energy:
|
|
210
|
+
logger.debug('Device does not support energy monitoring.')
|
|
211
|
+
return
|
|
212
|
+
history_intervals = [
|
|
213
|
+
'getLastWeekEnergy',
|
|
214
|
+
'getLastMonthEnergy',
|
|
215
|
+
'getLastYearEnergy',
|
|
216
|
+
]
|
|
217
|
+
if history_interval not in history_intervals:
|
|
218
|
+
logger.debug('Invalid history interval: %s', history_interval)
|
|
219
|
+
return
|
|
220
|
+
body = self._build_energy_request(history_interval)
|
|
221
|
+
headers = Helpers.req_header_bypass()
|
|
222
|
+
r_bytes, _ = await self.manager.async_call_api(
|
|
223
|
+
f'/cloud/v1/device/{history_interval}',
|
|
224
|
+
'post',
|
|
225
|
+
headers=headers,
|
|
226
|
+
json_object=body.to_dict(),
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
r = Helpers.process_dev_response(logger, history_interval, self, r_bytes)
|
|
230
|
+
if r is None:
|
|
231
|
+
return
|
|
232
|
+
response = ResponseEnergyHistory.from_dict(r)
|
|
233
|
+
match history_interval:
|
|
234
|
+
case 'getLastWeekEnergy':
|
|
235
|
+
self.state.weekly_history = response.result
|
|
236
|
+
case 'getLastMonthEnergy':
|
|
237
|
+
self.state.monthly_history = response.result
|
|
238
|
+
case 'getLastYearEnergy':
|
|
239
|
+
self.state.yearly_history = response.result
|
|
240
|
+
|
|
241
|
+
@property
|
|
242
|
+
def supports_nightlight(self) -> bool:
|
|
243
|
+
"""Return True if device supports nightlight.
|
|
244
|
+
|
|
245
|
+
Returns:
|
|
246
|
+
bool: True if device supports nightlight, False otherwise.
|
|
247
|
+
"""
|
|
248
|
+
return OutletFeatures.NIGHTLIGHT in self.features
|
|
249
|
+
|
|
250
|
+
@property
|
|
251
|
+
def supports_energy(self) -> bool:
|
|
252
|
+
"""Return True if device supports energy.
|
|
253
|
+
|
|
254
|
+
Returns:
|
|
255
|
+
bool: True if device supports energy, False otherwise.
|
|
256
|
+
"""
|
|
257
|
+
return OutletFeatures.ENERGY_MONITOR in self.features
|
|
258
|
+
|
|
259
|
+
async def get_weekly_energy(self) -> None:
|
|
260
|
+
"""Build weekly energy history dictionary.
|
|
261
|
+
|
|
262
|
+
The data is stored in the `device.state.weekly_history` attribute
|
|
263
|
+
as a `ResponseEnergyResult` object.
|
|
264
|
+
"""
|
|
265
|
+
await self._get_energy_history('getLastWeekEnergy')
|
|
266
|
+
|
|
267
|
+
async def get_monthly_energy(self) -> None:
|
|
268
|
+
"""Build Monthly Energy History Dictionary.
|
|
269
|
+
|
|
270
|
+
The data is stored in the `device.state.monthly_history` attribute
|
|
271
|
+
as a `ResponseEnergyResult` object.
|
|
272
|
+
"""
|
|
273
|
+
await self._get_energy_history('getLastMonthEnergy')
|
|
274
|
+
|
|
275
|
+
async def get_yearly_energy(self) -> None:
|
|
276
|
+
"""Build Yearly Energy Dictionary.
|
|
277
|
+
|
|
278
|
+
The data is stored in the `device.state.yearly_history` attribute
|
|
279
|
+
as a `ResponseEnergyResult` object.
|
|
280
|
+
"""
|
|
281
|
+
await self._get_energy_history('getLastYearEnergy')
|
|
282
|
+
|
|
283
|
+
async def update_energy(self) -> None:
|
|
284
|
+
"""Build weekly, monthly and yearly dictionaries."""
|
|
285
|
+
if self.supports_energy:
|
|
286
|
+
await self.get_weekly_energy()
|
|
287
|
+
await self.get_monthly_energy()
|
|
288
|
+
await self.get_yearly_energy()
|
|
289
|
+
|
|
290
|
+
async def set_nightlight_state(self, mode: str) -> bool:
|
|
291
|
+
"""Set nightlight mode.
|
|
292
|
+
|
|
293
|
+
Available nightlight states are found in the `device.nightlight_modes` attribute.
|
|
294
|
+
|
|
295
|
+
Args:
|
|
296
|
+
mode (str): Nightlight mode to set.
|
|
297
|
+
|
|
298
|
+
Returns:
|
|
299
|
+
bool: True if nightlight mode set successfully, False otherwise.
|
|
300
|
+
"""
|
|
301
|
+
del mode # unused
|
|
302
|
+
if not self.supports_nightlight:
|
|
303
|
+
logger.debug('Device does not support nightlight.')
|
|
304
|
+
else:
|
|
305
|
+
logger.debug('Nightlight mode not configured for %s', self.device_name)
|
|
306
|
+
return False
|
|
307
|
+
|
|
308
|
+
async def turn_on_nightlight(self) -> bool:
|
|
309
|
+
"""Turn on nightlight if supported."""
|
|
310
|
+
if not self.supports_nightlight:
|
|
311
|
+
logger.debug('Device does not support nightlight.')
|
|
312
|
+
return False
|
|
313
|
+
return await self.set_nightlight_state(NightlightModes.ON)
|
|
314
|
+
|
|
315
|
+
async def turn_off_nightlight(self) -> bool:
|
|
316
|
+
"""Turn off nightlight if supported."""
|
|
317
|
+
if not self.supports_nightlight:
|
|
318
|
+
logger.debug('Device does not support nightlight.')
|
|
319
|
+
return False
|
|
320
|
+
return await self.set_nightlight_state(NightlightModes.OFF)
|
|
321
|
+
|
|
322
|
+
async def set_nightlight_auto(self) -> bool:
|
|
323
|
+
"""Set nightlight to auto mode."""
|
|
324
|
+
if not self.supports_nightlight:
|
|
325
|
+
logger.debug('Device does not support nightlight.')
|
|
326
|
+
return False
|
|
327
|
+
return await self.set_nightlight_state(NightlightModes.AUTO)
|