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.
- pyvesync/__init__.py +11 -0
- pyvesync/base_devices/__init__.py +35 -0
- pyvesync/base_devices/bulb_base.py +348 -0
- pyvesync/base_devices/fan_base.py +398 -0
- pyvesync/base_devices/fryer_base.py +69 -0
- pyvesync/base_devices/humidifier_base.py +460 -0
- pyvesync/base_devices/outlet_base.py +315 -0
- pyvesync/base_devices/purifier_base.py +518 -0
- pyvesync/base_devices/switch_base.py +270 -0
- pyvesync/base_devices/thermostat_base.py +277 -0
- pyvesync/base_devices/vesyncbasedevice.py +580 -0
- pyvesync/const.py +749 -0
- pyvesync/device_container.py +360 -0
- pyvesync/device_map.py +1090 -0
- pyvesync/devices/__init__.py +1 -0
- pyvesync/devices/vesyncbulb.py +1254 -0
- pyvesync/devices/vesyncfan.py +238 -0
- pyvesync/devices/vesynchumidifier.py +785 -0
- pyvesync/devices/vesynckitchen.py +664 -0
- pyvesync/devices/vesyncoutlet.py +962 -0
- pyvesync/devices/vesyncpurifier.py +951 -0
- pyvesync/devices/vesyncswitch.py +449 -0
- pyvesync/devices/vesyncthermostat.py +175 -0
- pyvesync/models/__init__.py +11 -0
- pyvesync/models/base_models.py +117 -0
- pyvesync/models/bulb_models.py +173 -0
- pyvesync/models/bypass_models.py +264 -0
- pyvesync/models/fan_models.py +46 -0
- pyvesync/models/fryer_models.py +28 -0
- pyvesync/models/home_models.py +158 -0
- pyvesync/models/humidifier_models.py +187 -0
- pyvesync/models/outlet_models.py +194 -0
- pyvesync/models/purifier_models.py +204 -0
- pyvesync/models/switch_models.py +89 -0
- pyvesync/models/thermostat_models.py +122 -0
- pyvesync/models/vesync_models.py +252 -0
- pyvesync/utils/__init__.py +1 -0
- pyvesync/utils/colors.py +248 -0
- pyvesync/utils/device_mixins.py +366 -0
- pyvesync/utils/enum_utils.py +25 -0
- pyvesync/utils/errors.py +859 -0
- pyvesync/utils/helpers.py +696 -0
- pyvesync/utils/logs.py +500 -0
- pyvesync/vesync.py +445 -0
- pyvesync-3.0.0b0.dist-info/METADATA +527 -0
- pyvesync-3.0.0b0.dist-info/RECORD +49 -0
- pyvesync-3.0.0b0.dist-info/WHEEL +5 -0
- pyvesync-3.0.0b0.dist-info/licenses/LICENSE +21 -0
- 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."""
|