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,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)