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,204 @@
1
+ """Data models for VeSync Purifiers.
2
+
3
+ These models inherit from `ResponseBaseModel` and `RequestBaseModel` from the
4
+ `base_models` module.
5
+
6
+ The `InnerPurifierBaseResult` class is used as a base class for the inner purifier
7
+ result models for all models and the mashumaro discriminator determines the correct
8
+ subclass when deserializing.
9
+ """
10
+ from __future__ import annotations
11
+ from dataclasses import dataclass
12
+ # import orjson
13
+ from mashumaro.config import BaseConfig
14
+ from mashumaro.types import Discriminator
15
+ from pyvesync.const import IntFlag, StrFlag
16
+ from pyvesync.models.bypass_models import (
17
+ RequestBypassV1,
18
+ BypassV2InnerResult,
19
+ BypassV1Result,
20
+ )
21
+ from pyvesync.models.base_models import (
22
+ ResponseBaseModel,
23
+ RequestBaseModel,
24
+ )
25
+
26
+
27
+ @dataclass
28
+ class InnerPurifierBaseResult(BypassV2InnerResult):
29
+ """Base class for inner purifier results model."""
30
+
31
+ class Config(BaseConfig): # type: ignore[override]
32
+ """Configure the results model to use subclass discriminator."""
33
+ discriminator = Discriminator(include_subtypes=True)
34
+
35
+
36
+ @dataclass
37
+ class PurifierV2DetailsResult(InnerPurifierBaseResult):
38
+ """Vital 100S/200S and Everest Purifier Result Model."""
39
+ powerSwitch: int
40
+ filterLifePercent: int
41
+ workMode: str
42
+ manualSpeedLevel: int
43
+ fanSpeedLevel: int
44
+ AQLevel: int
45
+ PM25: int
46
+ screenState: int
47
+ childLockSwitch: int
48
+ screenSwitch: int
49
+ lightDetectionSwitch: int
50
+ environmentLightState: int
51
+ scheduleCount: int
52
+ timerRemain: int
53
+ efficientModeTimeRemain: int
54
+ errorCode: int
55
+ autoPreference: VitalAutoPreferences | None = None
56
+ fanRotateAngle: int | None = IntFlag.NOT_SUPPORTED
57
+ filterOpenState: int | None = IntFlag.NOT_SUPPORTED
58
+ PM1: int | None = IntFlag.NOT_SUPPORTED
59
+ PM10: int | None = IntFlag.NOT_SUPPORTED
60
+ AQPercent: int | None = IntFlag.NOT_SUPPORTED
61
+
62
+
63
+ @dataclass
64
+ class VitalAutoPreferences:
65
+ """Vital 100S/200S Auto Preferences."""
66
+ autoPreferenceType: str
67
+ roomSize: int
68
+
69
+
70
+ @dataclass
71
+ class PurifierCoreDetailsResult(InnerPurifierBaseResult):
72
+ """Purifier inner Result Dict."""
73
+ enabled: bool
74
+ filter_life: int
75
+ mode: str
76
+ level: int
77
+ device_error_code: int
78
+ air_quality: int | None = None
79
+ display: bool | None = None
80
+ child_lock: bool | None = None
81
+ configuration: PurifierCoreDetailsConfig | None = None
82
+ extension: dict | None = None
83
+ air_quality_value: int | None = None
84
+ night_light: str | None = StrFlag.NOT_SUPPORTED
85
+ fan_rotate: str | None = StrFlag.NOT_SUPPORTED
86
+
87
+
88
+ # Purifier Timer Models
89
+
90
+
91
+ @dataclass
92
+ class PurifierModifyTimerResult(InnerPurifierBaseResult):
93
+ """Purifier inner Add Timer Result Dict."""
94
+ id: int
95
+
96
+
97
+ @dataclass
98
+ class PurifierGetTimerResult(InnerPurifierBaseResult):
99
+ """Purifier inner Timer Result Dict."""
100
+ timers: list[ResponsePurifierTimerItems] | None
101
+
102
+
103
+ @dataclass
104
+ class ResponsePurifierTimerItems(ResponseBaseModel):
105
+ """Purifier Timer Items Response Dict."""
106
+ id: int
107
+ remain: int
108
+ total: int
109
+ action: str
110
+
111
+
112
+ @dataclass
113
+ class PurifierV2TimerPayloadData(RequestBaseModel):
114
+ """Purifier Timer Payload Data Request Dict."""
115
+ enabled: bool
116
+ startAct: list[PurifierV2TimerActionItems]
117
+ tmgEvt: PurifierV2EventTiming | None = None
118
+ type: int = 0
119
+ subDeviceNo: int = 0
120
+ repeat: int = 0
121
+
122
+
123
+ @dataclass
124
+ class PurifierV2TimerActionItems(RequestBaseModel):
125
+ """Purifier Timer Action Items Request Dict."""
126
+ type: str
127
+ act: int
128
+ num: int = 0
129
+
130
+
131
+ @dataclass
132
+ class PurifierV2EventTiming(RequestBaseModel):
133
+ """Purifier Event Timing Request Dict."""
134
+ clkSec: int
135
+
136
+
137
+ # Internal Purifier Details Models
138
+
139
+
140
+ @dataclass
141
+ class PurifierCoreDetailsConfig(ResponseBaseModel):
142
+ """Config dict in Core purifier details response."""
143
+ display: bool
144
+ display_forever: bool
145
+ auto_preference: None | PurifierCoreAutoConfig
146
+
147
+
148
+ @dataclass
149
+ class PurifierCoreAutoConfig(ResponseBaseModel):
150
+ """Auto configuration Core dict in purifier details response."""
151
+ type: str
152
+ room_size: int
153
+
154
+
155
+ @dataclass
156
+ class PurifierDetailsExtension(ResponseBaseModel):
157
+ """Extension dict in purifier details response for Core 200/300/400."""
158
+ schedule_count: int
159
+ timer_remain: int
160
+
161
+
162
+ # LV - PUR131S Purifier Models
163
+
164
+
165
+ @dataclass
166
+ class RequestPurifier131(RequestBypassV1):
167
+ """Purifier 131 Request Dict."""
168
+ status: str | None = None
169
+
170
+
171
+ @dataclass
172
+ class RequestPurifier131Mode(RequestBypassV1):
173
+ """Purifier 131 Request Dict."""
174
+ mode: str
175
+
176
+
177
+ @dataclass
178
+ class RequestPurifier131Level(RequestBypassV1):
179
+ """Purifier 131 Request Dict."""
180
+ level: int
181
+
182
+
183
+ @dataclass
184
+ class Purifier131Result(BypassV1Result):
185
+ """Purifier 131 Details Response Dict."""
186
+ screenStatus: str
187
+ filterLife: Purifier131Filter
188
+ activeTime: int
189
+ levelNew: int
190
+ level: int | None
191
+ mode: str
192
+ airQuality: str
193
+ deviceName: str
194
+ childLock: str
195
+ deviceStatus: str
196
+ connectionStatus: str
197
+
198
+
199
+ @dataclass
200
+ class Purifier131Filter(ResponseBaseModel):
201
+ """Filter details model for LV PUR131."""
202
+ change: bool
203
+ useHour: int
204
+ percent: int
@@ -0,0 +1,89 @@
1
+ """Data models for VeSync switches.
2
+
3
+ These models inherit from `ResponseBaseModel` and `RequestBaseModel` from the
4
+ `base_models` module.
5
+ """
6
+ from __future__ import annotations
7
+ from typing import Any
8
+ from dataclasses import dataclass
9
+ import orjson
10
+ from mashumaro.config import BaseConfig
11
+ from pyvesync.models.bypass_models import RequestBypassV1
12
+ from pyvesync.models.base_models import ResponseCodeModel, ResponseBaseModel
13
+
14
+
15
+ @dataclass
16
+ class ResponseSwitchDetails(ResponseCodeModel):
17
+ """Dimmer and Wall Switch Details Response Dict."""
18
+ result: InternalDimmerDetailsResult | InternalSwitchResult | None = None
19
+
20
+
21
+ @dataclass
22
+ class InternalSwitchResult(ResponseBaseModel):
23
+ """Dimmer Status Response Dict."""
24
+ deviceStatus: str
25
+ connectionStatus: str
26
+ activeTime: int
27
+
28
+
29
+ @dataclass
30
+ class InternalDimmerDetailsResult(ResponseBaseModel):
31
+ """Dimmer Details Result Dict."""
32
+ devicename: str
33
+ brightness: int
34
+ indicatorlightStatus: str
35
+ rgbStatus: str
36
+ rgbValue: DimmerRGB
37
+ deviceStatus: str
38
+ connectionStatus: str
39
+ activeTime: int = 0
40
+ timer: Any | None = None
41
+ schedule: Any | None = None
42
+ deviceImg: str | None = None
43
+
44
+
45
+ @dataclass
46
+ class DimmerRGB:
47
+ """Dimmer RGB Color Dict."""
48
+ red: int
49
+ green: int
50
+ blue: int
51
+
52
+
53
+ @dataclass
54
+ class RequestSwitchBase(RequestBypassV1):
55
+ """Base Dimmer Request Dict.
56
+
57
+ Inherits from RequestBypassV1 to include the common fields for all requests.
58
+ """
59
+
60
+
61
+ @dataclass
62
+ class RequestDimmerBrightness(RequestBypassV1):
63
+ """Dimmer Status Request Dict."""
64
+ brightness: str
65
+
66
+
67
+ @dataclass
68
+ class RequestDimmerDetails(RequestBypassV1):
69
+ """Dimmer Details Request Dict."""
70
+
71
+
72
+ @dataclass
73
+ class RequestSwitchStatus(RequestBypassV1):
74
+ """Dimmer Details Request Dict."""
75
+ status: str
76
+ switchNo: int
77
+
78
+
79
+ @dataclass
80
+ class RequestDimmerStatus(RequestBypassV1):
81
+ """Dimmer Status Request Dict."""
82
+ status: str
83
+ rgbValue: dict | None = None
84
+
85
+ class Config(BaseConfig): # type: ignore[override]
86
+ """Dimmer Indicator Control Config Dict."""
87
+ omit_none = True
88
+ omit_default = True
89
+ orjson_options = orjson.OPT_NON_STR_KEYS
@@ -0,0 +1,122 @@
1
+ """Data models for VeSync thermostats."""
2
+
3
+ from __future__ import annotations
4
+ from dataclasses import dataclass, field
5
+ from mashumaro.mixins.orjson import DataClassORJSONMixin
6
+ from mashumaro import field_options
7
+
8
+ from pyvesync.const import ThermostatConst
9
+ from pyvesync.models.bypass_models import BypassV2InnerResult
10
+
11
+
12
+ @dataclass
13
+ class ResultThermostatDetails(BypassV2InnerResult):
14
+ """Result model for thermostat details."""
15
+ supportMode: list[int]
16
+ workMode: int
17
+ workStatus: int
18
+ fanMode: int
19
+ fanStatus: int
20
+ tempUnit: str
21
+ temperature: float
22
+ humidity: int
23
+ heatToTemp: int
24
+ coolToTemp: int
25
+ lockStatus: bool
26
+ scheduleOrHold: int
27
+ holdEndTime: int
28
+ holdOption: int
29
+ deadband: int
30
+ ecoType: int
31
+ alertStatus: int
32
+ routines: list[ThermostatSimpleRoutine]
33
+ routineRunningId: int | None = None
34
+
35
+
36
+ @dataclass
37
+ class ThermostatSimpleRoutine(DataClassORJSONMixin):
38
+ """Thermostat routine model."""
39
+ name: str
40
+ routineId: int
41
+
42
+
43
+ @dataclass
44
+ class ThermostatRoutine(DataClassORJSONMixin):
45
+ """Model for full thermostat routine."""
46
+ name: str
47
+ routineId: int
48
+ type: int
49
+ heatToTemp: int
50
+ coolToTemp: int
51
+ heatFanMode: int
52
+ coolFanMode: int
53
+ usuallyMask: int
54
+ sensorIds: list[str]
55
+
56
+
57
+ @dataclass
58
+ class ThermostatMinorDetails(DataClassORJSONMixin):
59
+ """Model for thermostat minor details."""
60
+ mcu_version: str = field(metadata=field_options(alias="mcuVersion"))
61
+ hvac_capacity: int = field(metadata=field_options(alias="hvacCapcity"))
62
+ timestamp: int = field(metadata=field_options(alias="timeStamp"))
63
+ time_zone: int = field(metadata=field_options(alias="timeZone"))
64
+ offset_in_sec: int = field(metadata=field_options(alias="offsetInSec"))
65
+ time_fmt: int = field(metadata=field_options(alias="timeFmt"))
66
+ date_fmt: int = field(metadata=field_options(alias="dateFmt"))
67
+ fan_delay_time: int = field(metadata=field_options(alias="fanDelayTime"))
68
+ fan_circulation_time: int = field(metadata=field_options(alias="fanCirTime"))
69
+ hvac_protect_time: int = field(metadata=field_options(alias="hvacProtecTime"))
70
+ hvac_min_on_time: int = field(metadata=field_options(alias="hvacMinOnTime"))
71
+ aux_min_on_time: int = field(metadata=field_options(alias="auxMinOnTime"))
72
+ screen_brightness: int = field(metadata=field_options(alias="screenBrightness"))
73
+ standby_timeout: int = field(metadata=field_options(alias="standbyTimeOut"))
74
+ aux_low_temperature: int = field(metadata=field_options(alias="auxLowBalanceTemp"))
75
+ aux_high_temperature: int = field(metadata=field_options(alias="auxHighBalanceTemp"))
76
+ keytone: bool = field(metadata=field_options(alias="keyTone"))
77
+ smart_schedule_enabled: bool = field(
78
+ metadata=field_options(alias="smartScheduleEnabled")
79
+ )
80
+ time_to_temp_enabled: bool = field(metadata=field_options(alias="timeToTempEnabled"))
81
+ early_on_enabled: bool = field(metadata=field_options(alias="earlyOnEnabled"))
82
+ reminder_list: list[ThermostatReminder] = field(
83
+ metadata=field_options(alias="reminderList")
84
+ )
85
+ alarm_list: list[ThermostatAlarm] = field(metadata=field_options(alias="alarmList"))
86
+
87
+
88
+ @dataclass
89
+ class ThermostatReminder(DataClassORJSONMixin):
90
+ """Model for thermostat reminder."""
91
+ code: int
92
+ enabled: bool
93
+ frequency: int
94
+ code_name: str | None = None
95
+ last_maintenance_time: int | None = field(
96
+ default=None, metadata=field_options(alias="lastMaintenTime")
97
+ )
98
+
99
+ @classmethod
100
+ def __post_deserialize__(cls, obj: ThermostatReminder) -> ThermostatReminder: # type: ignore[override]
101
+ """Post-deserialization processing."""
102
+ if isinstance(obj.code, int):
103
+ obj.code_name = ThermostatConst.ReminderCode(obj.code).name
104
+ return obj
105
+
106
+
107
+ @dataclass
108
+ class ThermostatAlarm(DataClassORJSONMixin):
109
+ """Model for thermostat alarm."""
110
+ code: int
111
+ enabled: bool
112
+ code_name: str | None = None
113
+ aux_runtime_limit: int | None = field(
114
+ default=None, metadata=field_options(alias="auxRunTimeLimit")
115
+ )
116
+
117
+ @classmethod
118
+ def __post_deserialize__(cls, obj: ThermostatAlarm) -> ThermostatAlarm: # type: ignore[override]
119
+ """Post-deserialization processing."""
120
+ if obj.code is not None:
121
+ obj.code_name = ThermostatConst.AlarmCode(obj.code).name
122
+ return obj
@@ -0,0 +1,252 @@
1
+ """Models for general VeSync API requests and responses.
2
+
3
+ Dataclasses should follow the naming convention of Request/Response + <API Name> + Model.
4
+ Internal models should be named starting with IntResp/IntReq<API Name>Model.
5
+
6
+ Note:
7
+ All models should inherit `ResponseBaseModel` or `RequestBaseModel`. Use
8
+ `pyvesync.models.base_models.DefaultValues` to set default values. There
9
+ should be no repeating keys set in the child models.
10
+
11
+ """
12
+ from __future__ import annotations
13
+ from dataclasses import dataclass, field
14
+ import hashlib
15
+ from typing import Any
16
+
17
+ from pyvesync.models.base_models import (
18
+ ResponseCodeModel,
19
+ ResponseBaseModel,
20
+ RequestBaseModel,
21
+ DefaultValues,
22
+ )
23
+
24
+
25
+ @dataclass
26
+ class RequestLoginModel(RequestBaseModel):
27
+ """Request model for login."""
28
+ # Arguments to set
29
+ email: str
30
+ method: str
31
+ password: str
32
+ # default values
33
+ acceptLanguage: str = DefaultValues.acceptLanguage
34
+ appVersion: str = DefaultValues.appVersion
35
+ timeZone: str = DefaultValues.timeZone
36
+ phoneBrand: str = DefaultValues.phoneBrand
37
+ phoneOS: str = DefaultValues.phoneOS
38
+ traceId: str = field(default_factory=DefaultValues.traceId)
39
+ # Non-default constants
40
+ userType: str = '1'
41
+ devToken: str = ''
42
+
43
+ def __post_init__(self) -> None:
44
+ """Set the method field."""
45
+ self.password = self.hash_password(self.password)
46
+
47
+ @staticmethod
48
+ def hash_password(string: str) -> str:
49
+ """Encode password."""
50
+ return hashlib.md5(string.encode('utf-8')).hexdigest() # noqa: S324
51
+
52
+
53
+ @dataclass
54
+ class IntRespLoginResultModel(ResponseBaseModel):
55
+ """Model for the 'result' field in login response containing token and account ID.
56
+
57
+ This class is inherited by the `ResponseLoginModel` class.
58
+ """
59
+ accountID: str
60
+ acceptLanguage: str
61
+ countryCode: str
62
+ token: str
63
+
64
+
65
+ @dataclass
66
+ class ResponseLoginModel(ResponseCodeModel):
67
+ """Model for the login response.
68
+
69
+ Inherits from `BaseResultModel`. The `BaseResultModel` class provides the
70
+ defaults "code" and "msg" fields for the response.
71
+
72
+ Attributes:
73
+ result: ResponseLoginResultModel
74
+ The inner model for the 'result' field in the login response.
75
+
76
+ Examples:
77
+ ```python
78
+ a = {
79
+ "code": 0,
80
+ "msg": "success",
81
+ "stacktrace": null,
82
+ "module": null,
83
+ "traceId": "123456",
84
+ "result": {
85
+ "accountID": "123456",
86
+ "acceptLanguage": "en",
87
+ "countryCode": "US",
88
+ }
89
+ }
90
+ b = ResponseLoginModel.from_dict(a)
91
+ account_id = b.result.accountId
92
+ token = b.result.token
93
+ ```
94
+ """
95
+ result: IntRespLoginResultModel
96
+ stacktrace: str | None
97
+ module: str | None
98
+
99
+
100
+ @dataclass
101
+ class RequestDeviceListModel(RequestBaseModel):
102
+ """Model for the device list request."""
103
+ token: str
104
+ accountID: str
105
+ timeZone: str = DefaultValues.timeZone
106
+ method: str = 'devices'
107
+ pageNo: int = 1
108
+ pageSize: int = 100
109
+ appVersion: str = DefaultValues.appVersion
110
+ phoneBrand: str = DefaultValues.phoneBrand
111
+ phoneOS: str = DefaultValues.phoneOS
112
+ acceptLanguage: str = DefaultValues.acceptLanguage
113
+ traceId: str = str(DefaultValues.traceId())
114
+
115
+
116
+ def _flatten_device_prop(d: dict[str, Any]) -> dict[str, Any]:
117
+ """Flatten deviceProp field in device list response."""
118
+ if isinstance(d.get('deviceProp'), dict):
119
+ device_prop = d['deviceProp']
120
+ if device_prop.get('powerSwitch') is not None:
121
+ d['deviceStatus'] = "on" if device_prop['powerSwitch'] == 1 else "off"
122
+ if device_prop.get('connectionStatus') is not None:
123
+ d['connectionStatus'] = device_prop['connectionStatus']
124
+ if device_prop.get('wifiMac') is not None:
125
+ d['macID'] = device_prop['wifiMac']
126
+ del d['deviceProp']
127
+ return d
128
+
129
+
130
+ @dataclass
131
+ class ResponseDeviceDetailsModel(ResponseBaseModel):
132
+ """Internal response model for each device in device list response.
133
+
134
+ Populates the 'list' field in the `InternalDeviceListResult`.
135
+
136
+ Certain Devices have device status information in the `deviceProp` or `extension`
137
+ fields. This model flattens those fields into the `deviceStatus` and
138
+ `connectionStatus` fields before deserialization.
139
+ """
140
+ deviceRegion: str
141
+ isOwner: bool
142
+ deviceName: str
143
+ deviceImg: str
144
+ cid: str
145
+ connectionType: str
146
+ deviceType: str
147
+ type: str
148
+ uuid: str | None
149
+ configModule: str
150
+ macID: str
151
+ mode: str
152
+ speed: str | None
153
+ currentFirmVersion: str
154
+ subDeviceType: str | None
155
+ subDeviceList: None | str
156
+ extension: None | InternalDeviceListExtension
157
+ subDeviceNo: int = 0
158
+ deviceStatus: str = "off"
159
+ connectionStatus: str = "offline"
160
+ productType: str | None = None
161
+ # deviceProp: InitVar[None | InternalDevicePropModel] = None
162
+
163
+ # def __post_init__(self, deviceProp: None | IntRespDevicePropModel) -> None:
164
+ # """Set productType based on deviceType."""
165
+ # if deviceProp is not None:
166
+ # if deviceProp.powerSwitch == 1:
167
+ # self.deviceStatus = "on"
168
+ # elif deviceProp.powerSwitch == 0:
169
+ # self.deviceStatus = "off"
170
+ # if isinstance(deviceProp.connectionStatus, str) and \
171
+ # self.connectionStatus != deviceProp.connectionStatus:
172
+ # self.connectionStatus = deviceProp.connectionStatus
173
+
174
+ @classmethod
175
+ def __pre_deserialize__(cls, d: dict[Any, Any]) -> dict[Any, Any]:
176
+ """Perform device_list pre-deserialization processes.
177
+
178
+ This performs the following:
179
+ - Flattens the deviceProp field into deviceStatus and connectionStatus fields
180
+ - Sets subDeviceNo to 0 if not present
181
+ - Sets cid to uuid or macID if null
182
+ """
183
+ super().__pre_deserialize__(d)
184
+ d = _flatten_device_prop(d)
185
+ if d.get('subDeviceNo') is None:
186
+ d['subDeviceNo'] = 0
187
+ if d.get('cid') is None:
188
+ d['cid'] = d.get('uuid') if d.get('uuid') is not None else d.get("macID")
189
+ return d
190
+
191
+
192
+ @dataclass
193
+ class InternalDeviceListResult(ResponseBaseModel):
194
+ """Internal model for the 'result' field in device list response.
195
+
196
+ Notes:
197
+ Used by the `ResponseDeviceListModel` class to populate result field.
198
+ """
199
+ total: int
200
+ pageSize: int
201
+ pageNo: int
202
+ list: list[ResponseDeviceDetailsModel]
203
+
204
+
205
+ @dataclass
206
+ class ResponseDeviceListModel(ResponseCodeModel):
207
+ """Device list response model.
208
+
209
+ Inherits from `BaseResultModel`. The `BaseResultModel` class provides the
210
+ defaults "code" and "msg" fields for the response.
211
+
212
+ Attributes:
213
+ result: InternalDeviceListResult
214
+ The inner model for the 'result' field in the device list response.
215
+ module: str | None
216
+ stacktrace: str | None
217
+
218
+ Notes:
219
+ See the `DeviceListResultModel` and `DeviceListDeviceModel` classes for
220
+ the inner model of the 'result' field.
221
+ """
222
+ module: str | None
223
+ stacktrace: str | None
224
+ result: InternalDeviceListResult
225
+
226
+
227
+ @dataclass
228
+ class InternalDeviceListExtension(ResponseBaseModel):
229
+ """Internal Optional 'extension' field in device list response.
230
+
231
+ Used by the `InnerRespDeviceListDevModel` class to populate
232
+ the extension field in the device list response.
233
+ """
234
+ airQuality: None | int
235
+ airQualityLevel: None | int
236
+ mode: None | str
237
+ fanSpeedLevel: None | str
238
+
239
+
240
+ @dataclass
241
+ class RequestPID(RequestBaseModel):
242
+ """Model for the PID request."""
243
+ method: str
244
+ appVersion: str
245
+ phoneBrand: str
246
+ phoneOS: str
247
+ traceId: str
248
+ token: str
249
+ accountID: str
250
+ mobileID: str
251
+ configModule: str
252
+ region: str
@@ -0,0 +1 @@
1
+ """Helper utilities and classes for the VeSync API."""