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
pyvesync/device_map.py ADDED
@@ -0,0 +1,1090 @@
1
+ """Device and module mappings for VeSync devices.
2
+
3
+ This module contains mappings for VeSync devices to their respective classes.
4
+ The mappings are used to create instances of the appropriate device class
5
+ based on the device type and define features and modes. The device type is taken
6
+ from the `deviceType` field in the device list API.
7
+
8
+ The AirFryerMap, OutletMap, SwitchMap, BulbMap, FanMap, HumidifierMap, PurifierMap
9
+ and ThermostatMap dataclasses are used to define the mappings for each product type
10
+ with the assocated module, class, features and other device specific configuration. The
11
+ [`get_device_config`][pyvesync.device_map.get_device_config] function is used
12
+ to get the device map object from the device type to instantiate the appropriate class.
13
+ The individual `get_<product-type>` functions are used to get the device details
14
+ for the specific to the device type. Both functions return the same model,
15
+ the individual product type functions are used to satisfy type checking in
16
+ the individual devices.
17
+
18
+ Attributes:
19
+ outlet_modules: list[OutletMap]: List of Outlet device mappings.
20
+ switch_modules: list[SwitchMap]: List of Switch device mappings.
21
+ bulb_modules: list[BulbMap]: List of Bulb device mappings.
22
+ fan_modules: list[FanMap]: List of Fan device mappings.
23
+ purifier_modules: list[PurifierMap]: List of Purifier device mappings.
24
+ humidifier_modules: list[HumidifierMap]: List of Humidifier device mappings.
25
+ air_fryer_modules: list[AirFryerMap]: List of Air Fryer device mappings.
26
+ thermostat_modules: list[ThermostatMap]: List of Thermostat device mappings.
27
+
28
+ Classes:
29
+ ProductTypes: Enum: General device types enum.
30
+ DeviceMapTemplate: Template for DeviceModules mapping.
31
+ OutletMap: Template for Outlet device mapping.
32
+ SwitchMap: Template for Switch device mapping.
33
+ BulbMap: dataclass: Template for Bulb device mapping.
34
+ FanMap: dataclass: Template for Fan device mapping.
35
+ HumidifierMap: dataclass: Template for Humidifier device mapping.
36
+ PurifierMap: dataclass: Template for Purifier device mapping.
37
+ AirFryerMap: dataclass: Template for Air Fryer device mapping.
38
+ ThermostatMap: dataclass: Template for Thermostat device mapping.
39
+
40
+ Functions:
41
+ get_device_config: Get the device map object from the device type.
42
+ get_outlet: Get outlet device config, returning OutletMap object.
43
+ get_switch: Get switch device config, returning SwitchMap object.
44
+ get_bulb: Get the bulb device config, returning BulbMap object.
45
+ get_fan: Get the fan device config, returning the FanMap object.
46
+ get_humidifier: Get the humidfier config, returning the HumidifierMap object.
47
+ get_purifier: Get the purifier config, returning the PurifierMap object.
48
+ get_air_fryer: Get the Air Fryer config, returning the AirFryerMap object.
49
+ get_thermostat: Get the thermostat config, returning the ThermostatMap object.
50
+
51
+ Note:
52
+ To add devices, add the device mapping to the appropriate `<product-type>_modules`
53
+ list, ensuring all required fields are present based on the `<product-type>Map`
54
+ fields.
55
+ """
56
+ from itertools import chain
57
+ # from types import ModuleType
58
+ from types import ModuleType
59
+ from typing import Union
60
+ from dataclasses import dataclass, field
61
+ from pyvesync.devices import vesyncbulb
62
+ from pyvesync.devices import vesyncoutlet
63
+ from pyvesync.devices import vesyncswitch
64
+ from pyvesync.devices import vesyncfan, vesynchumidifier, vesyncpurifier
65
+ from pyvesync.devices import vesynckitchen
66
+ from pyvesync.devices import vesyncthermostat
67
+ from pyvesync.const import (
68
+ ColorMode,
69
+ PurifierModes,
70
+ HumidifierModes,
71
+ BulbFeatures,
72
+ OutletFeatures,
73
+ ProductTypes,
74
+ SwitchFeatures,
75
+ HumidifierFeatures,
76
+ PurifierFeatures,
77
+ NightlightModes,
78
+ PurifierAutoPreference,
79
+ FanSleepPreference,
80
+ FanModes,
81
+ FanFeatures,
82
+ ThermostatEcoTypes,
83
+ ThermostatFanModes,
84
+ ThermostatWorkModes,
85
+ ThermostatHoldOptions,
86
+ ThermostatRoutineTypes,
87
+ )
88
+
89
+ T_MAPS = Union[
90
+ list["OutletMap"],
91
+ list["SwitchMap"],
92
+ list["BulbMap"],
93
+ list["FanMap"],
94
+ list["HumidifierMap"],
95
+ list["PurifierMap"],
96
+ list["AirFryerMap"],
97
+ list["ThermostatMap"]
98
+ ]
99
+
100
+
101
+ @dataclass(kw_only=True)
102
+ class DeviceMapTemplate:
103
+ """Template for DeviceModules mapping.
104
+
105
+ Attributes:
106
+ dev_types (list[str]): List of device types to match from API.
107
+ class_name (str): Class name of the device.
108
+ product_type (str): Product type of the device.
109
+ module (ModuleType): Module for the device.
110
+ setup_entry (str): Setup entry for the device.
111
+ model_display (str): Display name of the model.
112
+ model_name (str): Name of the model.
113
+ device_alias (str | None): Alias for the device, if any.
114
+ features (list[str]): List of features for the device.
115
+ """
116
+
117
+ dev_types: list[str]
118
+ class_name: str
119
+ product_type: str
120
+ module: ModuleType
121
+ setup_entry: str
122
+ model_display: str
123
+ model_name: str
124
+ device_alias: str | None = None
125
+ features: list[str] = field(default_factory=list)
126
+
127
+
128
+ @dataclass(kw_only=True)
129
+ class OutletMap(DeviceMapTemplate):
130
+ """Template for DeviceModules mapping.
131
+
132
+ Attributes:
133
+ dev_types (list[str]): List of device types to match from API.
134
+ class_name (str): Class name of the device.
135
+ product_type (str): Product type of the device - ProductTypes.OUTLET
136
+ module (ModuleType): Module for the device.
137
+ setup_entry (str): Setup entry for the device.
138
+ model_display (str): Display name of the model.
139
+ model_name (str): Name of the model.
140
+ device_alias (str | None): Alias for the device, if any.
141
+ features (list[str]): List of features for the device.
142
+ product_type (str): Product type of the device.
143
+ module (ModuleType): Module for the device.
144
+ nightlight_modes (list[str]): List of nightlight modes for the device.
145
+ """
146
+ product_type: str = ProductTypes.OUTLET
147
+ module: ModuleType = vesyncoutlet
148
+ nightlight_modes: list[NightlightModes] = field(default_factory=list)
149
+
150
+
151
+ @dataclass(kw_only=True)
152
+ class SwitchMap(DeviceMapTemplate):
153
+ """Template for DeviceModules mapping.
154
+
155
+ Attributes:
156
+ dev_types (list[str]): List of device types to match from API.
157
+ class_name (str): Class name of the device.
158
+ product_type (str): Product type of the device - ProductTypes.SWITCH
159
+ module (ModuleType): Module for the device.
160
+ setup_entry (str): Setup entry for the device.
161
+ model_display (str): Display name of the model.
162
+ model_name (str): Name of the model.
163
+ device_alias (str | None): Alias for the device, if any.
164
+ features (list[str]): List of features for the device.
165
+ product_type (str): Product type of the device.
166
+ module (ModuleType): Module for the device.
167
+ """
168
+ product_type: str = ProductTypes.SWITCH
169
+ module: ModuleType = vesyncswitch
170
+
171
+
172
+ @dataclass(kw_only=True)
173
+ class BulbMap(DeviceMapTemplate):
174
+ """Template for DeviceModules mapping.
175
+
176
+ Attributes:
177
+ dev_types (list[str]): List of device types to match from API.
178
+ class_name (str): Class name of the device.
179
+ product_type (str): Product type of the device - ProductTypes.BULB
180
+ module (ModuleType): Module for the device.
181
+ setup_entry (str): Setup entry for the device.
182
+ model_display (str): Display name of the model.
183
+ model_name (str): Name of the model.
184
+ device_alias (str | None): Alias for the device, if any.
185
+ features (list[str]): List of features for the device.
186
+ color_model (str | None): Color model for the device.
187
+ color_modes (list[str]): List of color modes for the device.
188
+ """
189
+ color_model: str | None = None
190
+ product_type: str = ProductTypes.BULB
191
+ module: ModuleType = vesyncbulb
192
+ color_modes: list[str] = field(default_factory=list)
193
+
194
+
195
+ @dataclass(kw_only=True)
196
+ class FanMap(DeviceMapTemplate):
197
+ """Template for DeviceModules mapping.
198
+
199
+ Attributes:
200
+ dev_types (list[str]): List of device types to match from API.
201
+ class_name (str): Class name of the device.
202
+ product_type (str): Product type of the device - ProductTypes.FAN
203
+ module (ModuleType): Module for the device.
204
+ setup_entry (str): Setup entry for the device.
205
+ model_display (str): Display name of the model.
206
+ model_name (str): Name of the model.
207
+ device_alias (str | None): Alias for the device, if any.
208
+ features (list[str]): List of features for the device.
209
+ fan_levels (list[int]): List of fan levels for the device.
210
+ modes (list[str]): List of modes for the device.
211
+ sleep_preferences (list[str]): List of sleep preferences for the device.
212
+ set_mode_method (str): Method to set the mode for the device.
213
+ """
214
+ product_type: str = ProductTypes.FAN
215
+ module: ModuleType = vesyncfan
216
+ fan_levels: list[int] = field(default_factory=list)
217
+ modes: list[str] = field(default_factory=list)
218
+ sleep_preferences: list[str] = field(default_factory=list)
219
+ set_mode_method: str = ""
220
+
221
+
222
+ @dataclass(kw_only=True)
223
+ class HumidifierMap(DeviceMapTemplate):
224
+ """Template for DeviceModules mapping.
225
+
226
+ Attributes:
227
+ dev_types (list[str]): List of device types to match from API.
228
+ class_name (str): Class name of the device.
229
+ product_type (str): Product type of the device - ProductTypes.HUMIDIFIER
230
+ module (ModuleType): Module for the device.
231
+ setup_entry (str): Setup entry for the device.
232
+ model_display (str): Display name of the model.
233
+ model_name (str): Name of the model.
234
+ device_alias (str | None): Alias for the device, if any.
235
+ features (list[str]): List of features for the device.
236
+ mist_modes (dict[str, str]): Dictionary of mist modes for the device.
237
+ mist_levels (list[int | str]): List of mist levels for the device.
238
+ target_minmax (tuple[int, int]): Minimum and maximum target humidity levels.
239
+ warm_mist_levels (list[int | str]): List of warm mist levels for the device.
240
+ """
241
+
242
+ mist_modes: dict[str, str] = field(default_factory=dict)
243
+ mist_levels: list[int | str] = field(default_factory=list)
244
+ product_type: str = ProductTypes.HUMIDIFIER
245
+ module: ModuleType = vesynchumidifier
246
+ target_minmax: tuple[int, int] = (30, 80)
247
+ warm_mist_levels: list[int | str] = field(default_factory=list)
248
+
249
+
250
+ @dataclass(kw_only=True)
251
+ class PurifierMap(DeviceMapTemplate):
252
+ """Template for DeviceModules mapping.
253
+
254
+ Attributes:
255
+ dev_types (list[str]): List of device types to match from API.
256
+ class_name (str): Class name of the device.
257
+ product_type (str): Product type of the device - ProductTypes.PURIFIER
258
+
259
+ module (ModuleType): Module for the device.
260
+ setup_entry (str): Setup entry for the device.
261
+ model_display (str): Display name of the model.
262
+ model_name (str): Name of the model.
263
+ device_alias (str | None): Alias for the device, if any.
264
+ features (list[str]): List of features for the device.
265
+ fan_levels (list[int]): List of fan levels for the device.
266
+ modes (list[str]): List of modes for the device.
267
+ nightlight_modes (list[str]): List of nightlight modes for the device.
268
+ auto_preferences (list[str]): List of auto preferences for the device.
269
+ """
270
+
271
+ product_type: str = ProductTypes.PURIFIER
272
+ module: ModuleType = vesyncpurifier
273
+ fan_levels: list[int] = field(default_factory=list)
274
+ modes: list[str] = field(default_factory=list)
275
+ nightlight_modes: list[str] = field(default_factory=list)
276
+ auto_preferences: list[str] = field(default_factory=list)
277
+
278
+
279
+ @dataclass(kw_only=True)
280
+ class AirFryerMap(DeviceMapTemplate):
281
+ """Template for DeviceModules mapping.
282
+
283
+ Attributes:
284
+ dev_types (list[str]): List of device types to match from API.
285
+ class_name (str): Class name of the device.
286
+ product_type (str): Product type of the device - ProductTypes.AIR_FRYER
287
+ module (ModuleType): Module for the device.
288
+ setup_entry (str): Setup entry for the device.
289
+ model_display (str): Display name of the model.
290
+ model_name (str): Name of the model.
291
+ device_alias (str | None): Alias for the device, if any.
292
+ features (list[str]): List of features for the device.
293
+ product_type (str): Product type of the device.
294
+ module (ModuleType): Module for the device.
295
+ """
296
+
297
+ product_type: str = ProductTypes.AIR_FRYER
298
+ module: ModuleType = vesynckitchen
299
+
300
+
301
+ @dataclass(kw_only=True)
302
+ class ThermostatMap(DeviceMapTemplate):
303
+ """Template for Thermostat device mapping.
304
+
305
+ Attributes:
306
+ dev_types (list[str]): List of device types to match from API.
307
+ class_name (str): Class name of the device.
308
+ product_type (str): Product type of the device.
309
+ module (ModuleType): Module for the device.
310
+ setup_entry (str): Setup entry for the device.
311
+ model_display (str): Display name of the model.
312
+ model_name (str): Name of the model.
313
+ device_alias (str | None): Alias for the device, if any.
314
+ features (list[str]): List of features for the device.
315
+ modes (list[int]): List of modes for the device.
316
+ fan_modes (list[int]): List of fan modes for the device.
317
+ eco_types (list[int]): List of eco types for the device.
318
+ hold_options (list[int]): List of hold options for the device.
319
+ routine_types (list[int]): List of routine types for the device.
320
+ """
321
+ product_type: str = ProductTypes.THERMOSTAT
322
+ module: ModuleType = vesyncthermostat
323
+ modes: list[int] = field(default_factory=list)
324
+ fan_modes: list[int] = field(default_factory=list)
325
+ eco_types: list[int] = field(default_factory=list)
326
+ hold_options: list[int] = field(default_factory=list)
327
+ routine_types: list[int] = field(default_factory=list)
328
+
329
+
330
+ thermostat_modules = [
331
+ ThermostatMap(
332
+ dev_types=['LTM-A401S-WUS'],
333
+ class_name='VeSyncAuraThermostat',
334
+ fan_modes=[
335
+ ThermostatFanModes.AUTO,
336
+ ThermostatFanModes.CIRCULATE,
337
+ ThermostatFanModes.ON,
338
+ ],
339
+ modes=[
340
+ ThermostatWorkModes.HEAT,
341
+ ThermostatWorkModes.COOL,
342
+ ThermostatWorkModes.AUTO,
343
+ ThermostatWorkModes.OFF,
344
+ ],
345
+ eco_types=[
346
+ ThermostatEcoTypes.BALANCE,
347
+ ThermostatEcoTypes.COMFORT_FIRST,
348
+ ThermostatEcoTypes.COMFORT_SECOND,
349
+ ThermostatEcoTypes.ECO_FIRST,
350
+ ThermostatEcoTypes.ECO_SECOND,
351
+ ],
352
+ hold_options=[
353
+ ThermostatHoldOptions.PERMANENTLY,
354
+ ThermostatHoldOptions.FOUR_HOURS,
355
+ ThermostatHoldOptions.TWO_HOURS,
356
+ ThermostatHoldOptions.UNTIL_NEXT_SCHEDULED_ITEM,
357
+ ],
358
+ routine_types=[
359
+ ThermostatRoutineTypes.AWAY,
360
+ ThermostatRoutineTypes.CUSTOM,
361
+ ThermostatRoutineTypes.HOME,
362
+ ThermostatRoutineTypes.SLEEP,
363
+ ],
364
+ setup_entry='LTM-A401S-WUS',
365
+ model_display='LTM-A401S Series',
366
+ model_name='Aura Thermostat',
367
+ )
368
+ ]
369
+
370
+
371
+ outlet_modules = [
372
+ OutletMap(
373
+ dev_types=["wifi-switch-1.3"],
374
+ class_name="VeSyncOutlet7A",
375
+ features=[OutletFeatures.ENERGY_MONITOR],
376
+ model_name="WiFi Outlet US/CA",
377
+ model_display="ESW01-USA Series",
378
+ setup_entry="wifi-switch-1.3",
379
+ ),
380
+ OutletMap(
381
+ dev_types=["ESW10-USA"],
382
+ class_name="VeSyncESW10USA",
383
+ features=[],
384
+ model_name="10A WiFi Outlet USA",
385
+ model_display="ESW10-USA Series",
386
+ setup_entry="ESW03-USA",
387
+ ),
388
+ OutletMap(
389
+ dev_types=["ESW01-EU"],
390
+ class_name="VeSyncOutlet10A",
391
+ features=[OutletFeatures.ENERGY_MONITOR],
392
+ model_name="10A WiFi Outlet Europe",
393
+ model_display="ESW01-EU",
394
+ setup_entry="ESW01-EU",
395
+ ),
396
+ OutletMap(
397
+ dev_types=["ESW15-USA"],
398
+ class_name="VeSyncOutlet15A",
399
+ features=[OutletFeatures.ENERGY_MONITOR, OutletFeatures.NIGHTLIGHT],
400
+ nightlight_modes=[NightlightModes.ON, NightlightModes.OFF, NightlightModes.AUTO],
401
+ model_name="15A WiFi Outlet US/CA",
402
+ model_display="ESW15-USA Series",
403
+ setup_entry="ESW15-USA",
404
+ ),
405
+ OutletMap(
406
+ dev_types=["ESO15-TB"],
407
+ class_name="VeSyncOutdoorPlug",
408
+ features=[OutletFeatures.ENERGY_MONITOR],
409
+ model_name="Outdoor Plug",
410
+ model_display="ESO15-TB Series",
411
+ setup_entry="ESO15-TB",
412
+ ),
413
+ OutletMap(
414
+ dev_types=["BSDOG01"],
415
+ class_name="VeSyncOutletBSDGO1",
416
+ features=[OutletFeatures.ONOFF],
417
+ model_name="Smart Plug",
418
+ model_display="Smart Plug Series",
419
+ setup_entry="BSDOG01",
420
+ device_alias="Greensun Smart Plug",
421
+ ),
422
+ ]
423
+ """List of ['OutletMap'][pyvesync.device_map.OutletMap] configuration
424
+ objects for outlet devices."""
425
+
426
+ switch_modules = [
427
+ SwitchMap(
428
+ dev_types=["ESWL01"],
429
+ class_name="VeSyncWallSwitch",
430
+ device_alias="Wall Switch",
431
+ features=[SwitchFeatures.ONOFF],
432
+ model_name="Light Switch",
433
+ model_display="ESWL01 Series",
434
+ setup_entry="ESWL01",
435
+ ),
436
+ SwitchMap(
437
+ dev_types=["ESWD16"],
438
+ class_name="VeSyncDimmerSwitch",
439
+ features=[
440
+ SwitchFeatures.DIMMABLE,
441
+ SwitchFeatures.INDICATOR_LIGHT,
442
+ SwitchFeatures.BACKLIGHT_RGB,
443
+ ],
444
+ device_alias="Dimmer Switch",
445
+ model_name="Dimmer Switch",
446
+ model_display="ESWD16 Series",
447
+ setup_entry="ESWD16",
448
+ ),
449
+ SwitchMap(
450
+ dev_types=["ESWL03"],
451
+ class_name="VeSyncWallSwitch",
452
+ device_alias="Three-Way Wall Switch",
453
+ features=[SwitchFeatures.ONOFF],
454
+ model_name="Light Switch 3 way",
455
+ model_display="ESWL03 Series",
456
+ setup_entry="ESWL03",
457
+ ),
458
+ ]
459
+ """List of ['SwitchMap'][pyvesync.device_map.SwitchMap] configuration
460
+ objects for switch devices."""
461
+
462
+
463
+ # Bulb Device Definition
464
+ # Format: BulbMap(
465
+ # dev_types=["<device_type>"],
466
+ # class_name="<class_name>",
467
+ # features=["<feature>"],
468
+ # color_model="<color_model>",
469
+ # device_alias="<device_alias>")
470
+ bulb_modules = [
471
+ BulbMap(
472
+ dev_types=["ESL100"],
473
+ class_name="VeSyncBulbESL100",
474
+ features=[SwitchFeatures.DIMMABLE],
475
+ color_model=None,
476
+ device_alias="Dimmable Bright White Bulb",
477
+ color_modes=[ColorMode.WHITE],
478
+ model_display="ESL100 Series",
479
+ model_name="Soft white light bulb",
480
+ setup_entry="ESL100",
481
+ ),
482
+ BulbMap(
483
+ dev_types=["ESL100CW"],
484
+ class_name="VeSyncBulbESL100CW",
485
+ features=[BulbFeatures.DIMMABLE, BulbFeatures.COLOR_TEMP],
486
+ color_model=None,
487
+ device_alias="Dimmable Tunable White Bulb",
488
+ color_modes=[ColorMode.WHITE],
489
+ model_display="ESL100CW Series",
490
+ model_name="Cool-to-Warm White Light Bulb",
491
+ setup_entry="ESL100CW",
492
+ ),
493
+ BulbMap(
494
+ dev_types=["XYD0001"],
495
+ class_name="VeSyncBulbValcenoA19MC",
496
+ features=[
497
+ BulbFeatures.DIMMABLE,
498
+ BulbFeatures.MULTICOLOR,
499
+ BulbFeatures.COLOR_TEMP,
500
+ ],
501
+ color_model=ColorMode.HSV,
502
+ device_alias="Valceno Dimmable RGB Bulb",
503
+ color_modes=[ColorMode.WHITE, ColorMode.COLOR],
504
+ model_display="XYD0001",
505
+ model_name="Valceno WiFi Bulb",
506
+ setup_entry="XYD0001",
507
+ ),
508
+ BulbMap(
509
+ dev_types=["ESL100MC"],
510
+ class_name="VeSyncBulbESL100MC",
511
+ features=[
512
+ BulbFeatures.COLOR_TEMP,
513
+ BulbFeatures.MULTICOLOR,
514
+ BulbFeatures.DIMMABLE,
515
+ ],
516
+ color_model=ColorMode.RGB,
517
+ device_alias="Etekcity Dimmable RGB Bulb",
518
+ color_modes=[ColorMode.WHITE, ColorMode.COLOR],
519
+ model_name="Multicolour Bulb",
520
+ model_display="ESL100MC",
521
+ setup_entry="ESL100MC",
522
+ ),
523
+ ]
524
+ """List of ['BulbMap'][pyvesync.device_map.BulbMap] configuration
525
+ objects for bulb devices."""
526
+
527
+
528
+ humidifier_modules = [
529
+ HumidifierMap(
530
+ class_name="VeSyncHumid200300S",
531
+ dev_types=[
532
+ "Classic300S",
533
+ "LUH-A601S-WUSB",
534
+ "LUH-A601S-AUSW",
535
+ ],
536
+ features=[
537
+ HumidifierFeatures.NIGHTLIGHT,
538
+ HumidifierFeatures.NIGHTLIGHT_BRIGHTNESS,
539
+ ],
540
+ mist_modes={
541
+ HumidifierModes.AUTO: "auto",
542
+ HumidifierModes.SLEEP: "sleep",
543
+ HumidifierModes.MANUAL: "manual",
544
+ },
545
+ mist_levels=list(range(1, 10)),
546
+ device_alias="Classic 300S",
547
+ model_display="LUH-A601S Series",
548
+ model_name="Classic 300S",
549
+ setup_entry="Classic300S",
550
+ ),
551
+ HumidifierMap(
552
+ class_name="VeSyncHumid200S",
553
+ dev_types=["Classic200S"],
554
+ features=[],
555
+ mist_modes={
556
+ HumidifierModes.AUTO: "auto",
557
+ HumidifierModes.MANUAL: "manual",
558
+ },
559
+ mist_levels=list(range(1, 10)),
560
+ device_alias="Classic 200S",
561
+ model_display="Classic 200S Series",
562
+ model_name="Classic 200S",
563
+ setup_entry="Classic200S",
564
+ ),
565
+ HumidifierMap(
566
+ class_name="VeSyncHumid200300S",
567
+ dev_types=["Dual200S", "LUH-D301S-WUSR", "LUH-D301S-WJP", "LUH-D301S-WEU"],
568
+ features=[],
569
+ mist_modes={
570
+ HumidifierModes.AUTO: "auto",
571
+ HumidifierModes.MANUAL: "manual",
572
+ },
573
+ mist_levels=list(range(1, 3)),
574
+ device_alias="Dual 200S",
575
+ model_display="LUH-D301S Series",
576
+ model_name="Dual 200S",
577
+ setup_entry="Dual200S",
578
+ ),
579
+ HumidifierMap(
580
+ class_name="VeSyncHumid200300S",
581
+ dev_types=[
582
+ "LUH-A602S-WUSR",
583
+ "LUH-A602S-WUS",
584
+ "LUH-A602S-WEUR",
585
+ "LUH-A602S-WEU",
586
+ "LUH-A602S-WJP",
587
+ "LUH-A602S-WUSC",
588
+ ],
589
+ features=[HumidifierFeatures.WARM_MIST],
590
+ mist_modes={
591
+ HumidifierModes.AUTO: "auto",
592
+ HumidifierModes.SLEEP: "sleep",
593
+ HumidifierModes.MANUAL: "manual",
594
+ },
595
+ mist_levels=list(range(1, 10)),
596
+ warm_mist_levels=[0, 1, 2, 3],
597
+ device_alias="LV600S",
598
+ model_display="LUH-A602S Series",
599
+ model_name="LV600S",
600
+ setup_entry="LUH-A602S-WUS",
601
+ ),
602
+ HumidifierMap(
603
+ class_name="VeSyncHumid200300S",
604
+ dev_types=["LUH-O451S-WEU"],
605
+ features=[HumidifierFeatures.WARM_MIST],
606
+ mist_modes={
607
+ HumidifierModes.AUTO: "auto",
608
+ HumidifierModes.SLEEP: "sleep",
609
+ HumidifierModes.MANUAL: "manual",
610
+ },
611
+ mist_levels=list(range(1, 10)),
612
+ warm_mist_levels=list(range(4)),
613
+ device_alias="OasisMist 450S EU",
614
+ model_name="OasisMist 4.5L",
615
+ model_display="LUH-O451S Series",
616
+ setup_entry="LUH-O451S-WUS",
617
+ ),
618
+ HumidifierMap(
619
+ class_name="VeSyncHumid200300S",
620
+ dev_types=["LUH-O451S-WUS", "LUH-O451S-WUSR", "LUH-O601S-WUS", "LUH-O601S-KUS"],
621
+ features=[HumidifierFeatures.WARM_MIST],
622
+ mist_modes={
623
+ HumidifierModes.AUTO: "auto",
624
+ HumidifierModes.SLEEP: "sleep",
625
+ HumidifierModes.MANUAL: "manual",
626
+ HumidifierModes.HUMIDITY: "humidity",
627
+ },
628
+ mist_levels=list(range(1, 10)),
629
+ warm_mist_levels=list(range(4)),
630
+ device_alias="OasisMist 450S",
631
+ model_display="OasisMist 4.5L Series",
632
+ model_name="OasisMist 4.5L",
633
+ setup_entry="LUH-O451S-WUS",
634
+ ),
635
+ HumidifierMap(
636
+ class_name="VeSyncHumid1000S",
637
+ dev_types=["LUH-M101S-WUS", "LUH-M101S-WEUR", "LUH-M101S-WUSR"],
638
+ features=[],
639
+ mist_modes={
640
+ HumidifierModes.AUTO: "auto",
641
+ HumidifierModes.SLEEP: "sleep",
642
+ HumidifierModes.MANUAL: "manual",
643
+ },
644
+ mist_levels=list(range(1, 10)),
645
+ device_alias="Oasismist 1000S",
646
+ model_display="Oasismist Series",
647
+ model_name="Oasismist 1000S",
648
+ setup_entry="LUH-M101S",
649
+ ),
650
+ HumidifierMap(
651
+ class_name="VeSyncSuperior6000S",
652
+ dev_types=["LEH-S601S-WUS", "LEH-S601S-WUSR", "LEH-S601S-WEUR"],
653
+ features=[HumidifierFeatures.DRYING_MODE],
654
+ mist_modes={
655
+ HumidifierModes.AUTO: "autoPro",
656
+ HumidifierModes.SLEEP: "sleep",
657
+ HumidifierModes.HUMIDITY: "humidity",
658
+ HumidifierModes.MANUAL: "manual",
659
+ HumidifierModes.AUTOPRO: "autoPro",
660
+ },
661
+ mist_levels=list(range(1, 10)),
662
+ device_alias="Superior 6000S",
663
+ model_display="LEH-S601S Series",
664
+ model_name="Superior 6000S",
665
+ setup_entry="LEH-S601S",
666
+ ),
667
+ ]
668
+ """List of ['HumidifierMap'][pyvesync.device_map.HumidifierMap] configuration
669
+ objects for humidifier devices."""
670
+
671
+
672
+ purifier_modules: list[PurifierMap] = [
673
+ PurifierMap(
674
+ class_name="VeSyncAirBypass",
675
+ dev_types=["Core200S", "LAP-C201S-AUSR", "LAP-C202S-WUSR"],
676
+ modes=[PurifierModes.SLEEP, PurifierModes.MANUAL],
677
+ features=[PurifierFeatures.RESET_FILTER, PurifierFeatures.NIGHTLIGHT],
678
+ auto_preferences=[
679
+ PurifierAutoPreference.DEFAULT,
680
+ PurifierAutoPreference.EFFICIENT,
681
+ PurifierAutoPreference.QUIET
682
+ ],
683
+ fan_levels=list(range(1, 4)),
684
+ nightlight_modes=[NightlightModes.ON, NightlightModes.OFF, NightlightModes.DIM],
685
+ device_alias="Core 200S",
686
+ model_display="Core 200S",
687
+ model_name="Core 200S",
688
+ setup_entry="Core200S",
689
+ ),
690
+ PurifierMap(
691
+ class_name="VeSyncAirBypass",
692
+ dev_types=[
693
+ "Core300S",
694
+ "LAP-C301S-WJP",
695
+ "LAP-C302S-WUSB",
696
+ "LAP-C301S-WAAA",
697
+ "LAP-C302S-WGC",
698
+ ],
699
+ modes=[PurifierModes.SLEEP, PurifierModes.MANUAL, PurifierModes.AUTO],
700
+ auto_preferences=[
701
+ PurifierAutoPreference.DEFAULT,
702
+ PurifierAutoPreference.EFFICIENT,
703
+ PurifierAutoPreference.QUIET
704
+ ],
705
+ features=[PurifierFeatures.AIR_QUALITY],
706
+ fan_levels=list(range(1, 5)),
707
+ device_alias="Core 300S",
708
+ model_display="Core 300S",
709
+ model_name="Core 300S",
710
+ setup_entry="Core300S",
711
+ ),
712
+ PurifierMap(
713
+ class_name="VeSyncAirBypass",
714
+ dev_types=["Core400S", "LAP-C401S-WJP", "LAP-C401S-WUSR", "LAP-C401S-WAAA"],
715
+ modes=[PurifierModes.SLEEP, PurifierModes.MANUAL, PurifierModes.AUTO],
716
+ features=[PurifierFeatures.AIR_QUALITY],
717
+ fan_levels=list(range(1, 5)),
718
+ device_alias="Core 400S",
719
+ auto_preferences=[
720
+ PurifierAutoPreference.DEFAULT,
721
+ PurifierAutoPreference.EFFICIENT,
722
+ PurifierAutoPreference.QUIET
723
+ ],
724
+ model_display="Core 400S",
725
+ model_name="Core 400S",
726
+ setup_entry="Core400S",
727
+ ),
728
+ PurifierMap(
729
+ class_name="VeSyncAirBypass",
730
+ dev_types=["Core600S", "LAP-C601S-WUS", "LAP-C601S-WUSR", "LAP-C601S-WEU"],
731
+ modes=[PurifierModes.SLEEP, PurifierModes.MANUAL, PurifierModes.AUTO],
732
+ features=[PurifierFeatures.AIR_QUALITY],
733
+ auto_preferences=[
734
+ PurifierAutoPreference.DEFAULT,
735
+ PurifierAutoPreference.EFFICIENT,
736
+ PurifierAutoPreference.QUIET
737
+ ],
738
+ fan_levels=list(range(1, 5)),
739
+ device_alias="Core 600S",
740
+ model_display="Core 600S",
741
+ model_name="Core 600S",
742
+ setup_entry="Core600S",
743
+ ),
744
+ PurifierMap(
745
+ class_name="VeSyncAir131",
746
+ dev_types=["LV-PUR131S", "LV-RH131S"],
747
+ modes=[PurifierModes.SLEEP, PurifierModes.MANUAL, PurifierModes.AUTO],
748
+ features=[PurifierFeatures.AIR_QUALITY],
749
+ fan_levels=list(range(1, 3)),
750
+ device_alias="LV-PUR131S",
751
+ model_display="LV-PUR131S/RH131S Series",
752
+ model_name="LV131S",
753
+ setup_entry="LV-PUR131S",
754
+ ),
755
+ PurifierMap(
756
+ class_name="VeSyncAirBaseV2",
757
+ dev_types=[
758
+ "LAP-V102S-AASR",
759
+ "LAP-V102S-WUS",
760
+ "LAP-V102S-WEU",
761
+ "LAP-V102S-AUSR",
762
+ "LAP-V102S-WJP",
763
+ "LAP-V102S-AJPR",
764
+ "LAP-V102S-AEUR",
765
+ ],
766
+ modes=[
767
+ PurifierModes.SLEEP,
768
+ PurifierModes.MANUAL,
769
+ PurifierModes.AUTO,
770
+ PurifierModes.PET,
771
+ ],
772
+ features=[PurifierFeatures.AIR_QUALITY],
773
+ fan_levels=list(range(1, 5)),
774
+ device_alias="Vital 100S",
775
+ auto_preferences=[
776
+ PurifierAutoPreference.DEFAULT,
777
+ PurifierAutoPreference.EFFICIENT,
778
+ PurifierAutoPreference.QUIET
779
+ ],
780
+ model_display="LAP-V102S Series",
781
+ model_name="Vital 100S",
782
+ setup_entry="LAP-V102S",
783
+ ),
784
+ PurifierMap(
785
+ class_name="VeSyncAirBaseV2",
786
+ dev_types=[
787
+ "LAP-V201S-AASR",
788
+ "LAP-V201S-WJP",
789
+ "LAP-V201S-WEU",
790
+ "LAP-V201S-WUS",
791
+ "LAP-V201-AUSR",
792
+ "LAP-V201S-AUSR",
793
+ "LAP-V201S-AEUR",
794
+ ],
795
+ modes=[
796
+ PurifierModes.SLEEP,
797
+ PurifierModes.MANUAL,
798
+ PurifierModes.AUTO,
799
+ PurifierModes.PET,
800
+ ],
801
+ features=[
802
+ PurifierFeatures.AIR_QUALITY,
803
+ PurifierFeatures.LIGHT_DETECT
804
+ ],
805
+ fan_levels=list(range(1, 5)),
806
+ device_alias="Vital 200S",
807
+ auto_preferences=[
808
+ PurifierAutoPreference.DEFAULT,
809
+ PurifierAutoPreference.EFFICIENT,
810
+ PurifierAutoPreference.QUIET
811
+ ],
812
+ model_display="LAP-V201S Series",
813
+ model_name="Vital 200S",
814
+ setup_entry="LAP-V201S",
815
+ ),
816
+ PurifierMap(
817
+ class_name="VeSyncAirBaseV2",
818
+ dev_types=[
819
+ "LAP-EL551S-AUS",
820
+ "LAP-EL551S-AEUR",
821
+ "LAP-EL551S-WEU",
822
+ "LAP-EL551S-WUS",
823
+ ],
824
+ modes=[
825
+ PurifierModes.SLEEP,
826
+ PurifierModes.MANUAL,
827
+ PurifierModes.AUTO,
828
+ PurifierModes.TURBO,
829
+ ],
830
+ features=[
831
+ PurifierFeatures.AIR_QUALITY,
832
+ PurifierFeatures.VENT_ANGLE,
833
+ PurifierFeatures.LIGHT_DETECT
834
+ ],
835
+ fan_levels=list(range(1, 4)),
836
+ device_alias="Everest Air",
837
+ auto_preferences=[
838
+ PurifierAutoPreference.DEFAULT,
839
+ PurifierAutoPreference.EFFICIENT,
840
+ PurifierAutoPreference.QUIET
841
+ ],
842
+ model_display="LAP-EL551S Series",
843
+ model_name="Everest Air",
844
+ setup_entry="EL551S",
845
+ ),
846
+ ]
847
+ """List of ['PurifierMap'][pyvesync.device_map.PurifierMap] configuration
848
+ objects for purifier devices."""
849
+
850
+
851
+ fan_modules: list[FanMap] = [
852
+ FanMap(
853
+ class_name="VeSyncTowerFan",
854
+ dev_types=["LTF-F422S-KEU", "LTF-F422S-WUSR", "LTF-F422_WJP", "LTF-F422S-WUS"],
855
+ modes=[
856
+ FanModes.SLEEP, # Alias for Advanced sleep mode
857
+ FanModes.NORMAL,
858
+ FanModes.MANUAL,
859
+ FanModes.TURBO,
860
+ FanModes.AUTO,
861
+ FanModes.ADVANCED_SLEEP,
862
+ ],
863
+ set_mode_method="setTowerFanMode",
864
+ features=[
865
+ FanFeatures.OSCILLATION,
866
+ FanFeatures.DISPLAYING_TYPE,
867
+ FanFeatures.SOUND,
868
+ ],
869
+ fan_levels=list(range(1, 13)),
870
+ device_alias="Tower Fan",
871
+ sleep_preferences=[
872
+ FanSleepPreference.DEFAULT,
873
+ FanSleepPreference.ADVANCED,
874
+ FanSleepPreference.TURBO,
875
+ FanSleepPreference.EFFICIENT,
876
+ FanSleepPreference.QUIET,
877
+ ], # Unknown sleep preferences, need to be verified
878
+ model_display="LTF-F422S Series",
879
+ model_name="Classic 42-Inch Tower Fan",
880
+ setup_entry="LTF-F422S",
881
+ ),
882
+ ]
883
+ """List of ['FanMap'][pyvesync.device_map.FanMap] configuration
884
+ objects for fan devices."""
885
+
886
+
887
+ air_fryer_modules: list[AirFryerMap] = [
888
+ AirFryerMap(
889
+ class_name="VeSyncAirFryer158",
890
+ module=vesynckitchen,
891
+ dev_types=['CS137-AF/CS158-AF', 'CS158-AF', 'CS137-AF', 'CS358-AF'],
892
+ device_alias="Air Fryer",
893
+ model_display="CS158/159/168/169-AF Series",
894
+ model_name="Smart/Pro/Pro Gen 2 5.8 Qt. Air Fryer",
895
+ setup_entry="CS137-AF/CS158-AF",
896
+ )
897
+ ]
898
+ """List of ['AirFryerMap'][pyvesync.device_map.AirFryerMap] configuration
899
+ for air fryer devices."""
900
+
901
+
902
+ def get_device_config(device_type: str) -> DeviceMapTemplate | None:
903
+ """Get general device details from device type to create instance.
904
+
905
+ Args:
906
+ device_type (str): Device type to match from device list API call.
907
+
908
+ Returns:
909
+ DeviceMapTemplate | None: DeviceMapTemplate object or None if not found.
910
+ """
911
+ all_modules: list[T_MAPS] = [
912
+ outlet_modules,
913
+ switch_modules,
914
+ bulb_modules,
915
+ fan_modules,
916
+ purifier_modules,
917
+ humidifier_modules,
918
+ air_fryer_modules,
919
+ thermostat_modules,
920
+ ]
921
+ for module in chain(*all_modules):
922
+ if device_type in module.dev_types:
923
+ return module
924
+ if device_type.count("-") > 1:
925
+ device_type = "-".join(device_type.split("-")[:-1])
926
+ for module in chain(*all_modules):
927
+ if any(device_type.lower() in dev.lower() for dev in module.dev_types):
928
+ return module
929
+ return None
930
+
931
+
932
+ def get_fan(device_type: str) -> FanMap | None:
933
+ """Get fan device details from device type.
934
+
935
+ Args:
936
+ device_type (str): Device type to match from device list API call.
937
+
938
+ Returns:
939
+ FanMap | None: FanMap object or None if not found.
940
+ """
941
+ for module in fan_modules:
942
+ if device_type in module.dev_types:
943
+ return module
944
+ # Try to match with a more generic device type
945
+ if device_type.count("-") > 1:
946
+ device_type = "-".join(device_type.split("-")[:-1])
947
+ for module in fan_modules:
948
+ if any(device_type.lower() in dev.lower() for dev in module.dev_types):
949
+ return module
950
+ return None
951
+
952
+
953
+ def get_purifier(device_type: str) -> PurifierMap | None:
954
+ """Get purifier device details from device type.
955
+
956
+ Args:
957
+ device_type (str): Device type to match from device list API call.
958
+
959
+ Returns:
960
+ PurifierMap | None: PurifierMap object or None if not found.
961
+ """
962
+ for module in purifier_modules:
963
+ if device_type in module.dev_types:
964
+ return module
965
+ if device_type.count("-") > 1:
966
+ device_type = "-".join(device_type.split("-")[:-1])
967
+ for module in purifier_modules:
968
+ if any(device_type.lower() in dev.lower() for dev in module.dev_types):
969
+ return module
970
+ return None
971
+
972
+
973
+ def get_humidifier(device_type: str) -> HumidifierMap | None:
974
+ """Get humidifier device details from device type.
975
+
976
+ Args:
977
+ device_type (str): Device type to match from device list API call.
978
+
979
+ Returns:
980
+ HumidifierMap | None: HumidifierMap object or None if not found.
981
+ """
982
+ for module in humidifier_modules:
983
+ if device_type in module.dev_types:
984
+ return module
985
+ if device_type.count("-") > 1:
986
+ device_type = "-".join(device_type.split("-")[:-1])
987
+ for module in humidifier_modules:
988
+ if any(device_type.lower() in dev.lower() for dev in module.dev_types):
989
+ return module
990
+ return None
991
+
992
+
993
+ def get_outlet(device_type: str) -> OutletMap | None:
994
+ """Get outlet device details from device type.
995
+
996
+ Args:
997
+ device_type (str): Device type to match from device list API call.
998
+
999
+ Returns:
1000
+ OutletMap | None: OutletMap object or None if not found.
1001
+ """
1002
+ for module in outlet_modules:
1003
+ if device_type in module.dev_types:
1004
+ return module
1005
+ if device_type.count("-") > 1:
1006
+ device_type = "-".join(device_type.split("-")[:-1])
1007
+ for module in outlet_modules:
1008
+ if any(device_type.lower() in dev.lower() for dev in module.dev_types):
1009
+ return module
1010
+ return None
1011
+
1012
+
1013
+ def get_switch(device_type: str) -> SwitchMap | None:
1014
+ """Get switch device details from device type.
1015
+
1016
+ Args:
1017
+ device_type (str): Device type to match from device list API call.
1018
+
1019
+ Returns:
1020
+ SwitchMap | None: SwitchMap object or None if not found.
1021
+ """
1022
+ for module in switch_modules:
1023
+ if device_type in module.dev_types:
1024
+ return module
1025
+ if device_type.count("-") > 1:
1026
+ device_type = "-".join(device_type.split("-")[:-1])
1027
+ for module in switch_modules:
1028
+ if any(device_type.lower() in dev.lower() for dev in module.dev_types):
1029
+ return module
1030
+ return None
1031
+
1032
+
1033
+ def get_bulb(device_type: str) -> BulbMap | None:
1034
+ """Get bulb device details from device type.
1035
+
1036
+ Args:
1037
+ device_type (str): Device type to match from device list API call.
1038
+
1039
+ Returns:
1040
+ BulbMap | None: BulbMap object or None if not found.
1041
+ """
1042
+ for module in bulb_modules:
1043
+ if device_type in module.dev_types:
1044
+ return module
1045
+ if device_type.count("-") > 1:
1046
+ device_type = "-".join(device_type.split("-")[:-1])
1047
+ for module in bulb_modules:
1048
+ if any(device_type.lower() in dev.lower() for dev in module.dev_types):
1049
+ return module
1050
+ return None
1051
+
1052
+
1053
+ def get_air_fryer(device_type: str) -> AirFryerMap | None:
1054
+ """Get air fryer device details from device type.
1055
+
1056
+ Args:
1057
+ device_type (str): Device type to match from device list API call.
1058
+
1059
+ Returns:
1060
+ AirFryerMap | None: AirFryerMap object or None if not found.
1061
+ """
1062
+ for module in air_fryer_modules:
1063
+ if device_type in module.dev_types:
1064
+ return module
1065
+ if device_type.count("-") > 1:
1066
+ device_type = "-".join(device_type.split("-")[:-1])
1067
+ for module in air_fryer_modules:
1068
+ if any(device_type.lower() in dev.lower() for dev in module.dev_types):
1069
+ return module
1070
+ return None
1071
+
1072
+
1073
+ def get_thermostat(device_type: str) -> ThermostatMap | None:
1074
+ """Get the device map for a thermostat.
1075
+
1076
+ Args:
1077
+ device_type (str): The device type to match.
1078
+
1079
+ Returns:
1080
+ ThermostatMap | None: The matching thermostat map or None if not found.
1081
+ """
1082
+ for module in thermostat_modules:
1083
+ if device_type in module.dev_types:
1084
+ return module
1085
+ if device_type.count("-") > 1:
1086
+ device_type = "-".join(device_type.split("-")[:-1])
1087
+ for module in thermostat_modules:
1088
+ if any(device_type.lower() in dev.lower() for dev in module.dev_types):
1089
+ return module
1090
+ return None