aiohomematic 2025.11.3__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.

Potentially problematic release.


This version of aiohomematic might be problematic. Click here for more details.

Files changed (77) hide show
  1. aiohomematic/__init__.py +61 -0
  2. aiohomematic/async_support.py +212 -0
  3. aiohomematic/central/__init__.py +2309 -0
  4. aiohomematic/central/decorators.py +155 -0
  5. aiohomematic/central/rpc_server.py +295 -0
  6. aiohomematic/client/__init__.py +1848 -0
  7. aiohomematic/client/_rpc_errors.py +81 -0
  8. aiohomematic/client/json_rpc.py +1326 -0
  9. aiohomematic/client/rpc_proxy.py +311 -0
  10. aiohomematic/const.py +1127 -0
  11. aiohomematic/context.py +18 -0
  12. aiohomematic/converter.py +108 -0
  13. aiohomematic/decorators.py +302 -0
  14. aiohomematic/exceptions.py +164 -0
  15. aiohomematic/hmcli.py +186 -0
  16. aiohomematic/model/__init__.py +140 -0
  17. aiohomematic/model/calculated/__init__.py +84 -0
  18. aiohomematic/model/calculated/climate.py +290 -0
  19. aiohomematic/model/calculated/data_point.py +327 -0
  20. aiohomematic/model/calculated/operating_voltage_level.py +299 -0
  21. aiohomematic/model/calculated/support.py +234 -0
  22. aiohomematic/model/custom/__init__.py +177 -0
  23. aiohomematic/model/custom/climate.py +1532 -0
  24. aiohomematic/model/custom/cover.py +792 -0
  25. aiohomematic/model/custom/data_point.py +334 -0
  26. aiohomematic/model/custom/definition.py +871 -0
  27. aiohomematic/model/custom/light.py +1128 -0
  28. aiohomematic/model/custom/lock.py +394 -0
  29. aiohomematic/model/custom/siren.py +275 -0
  30. aiohomematic/model/custom/support.py +41 -0
  31. aiohomematic/model/custom/switch.py +175 -0
  32. aiohomematic/model/custom/valve.py +114 -0
  33. aiohomematic/model/data_point.py +1123 -0
  34. aiohomematic/model/device.py +1445 -0
  35. aiohomematic/model/event.py +208 -0
  36. aiohomematic/model/generic/__init__.py +217 -0
  37. aiohomematic/model/generic/action.py +34 -0
  38. aiohomematic/model/generic/binary_sensor.py +30 -0
  39. aiohomematic/model/generic/button.py +27 -0
  40. aiohomematic/model/generic/data_point.py +171 -0
  41. aiohomematic/model/generic/dummy.py +147 -0
  42. aiohomematic/model/generic/number.py +76 -0
  43. aiohomematic/model/generic/select.py +39 -0
  44. aiohomematic/model/generic/sensor.py +74 -0
  45. aiohomematic/model/generic/switch.py +54 -0
  46. aiohomematic/model/generic/text.py +29 -0
  47. aiohomematic/model/hub/__init__.py +333 -0
  48. aiohomematic/model/hub/binary_sensor.py +24 -0
  49. aiohomematic/model/hub/button.py +28 -0
  50. aiohomematic/model/hub/data_point.py +340 -0
  51. aiohomematic/model/hub/number.py +39 -0
  52. aiohomematic/model/hub/select.py +49 -0
  53. aiohomematic/model/hub/sensor.py +37 -0
  54. aiohomematic/model/hub/switch.py +44 -0
  55. aiohomematic/model/hub/text.py +30 -0
  56. aiohomematic/model/support.py +586 -0
  57. aiohomematic/model/update.py +143 -0
  58. aiohomematic/property_decorators.py +496 -0
  59. aiohomematic/py.typed +0 -0
  60. aiohomematic/rega_scripts/fetch_all_device_data.fn +92 -0
  61. aiohomematic/rega_scripts/get_program_descriptions.fn +30 -0
  62. aiohomematic/rega_scripts/get_serial.fn +44 -0
  63. aiohomematic/rega_scripts/get_system_variable_descriptions.fn +30 -0
  64. aiohomematic/rega_scripts/set_program_state.fn +12 -0
  65. aiohomematic/rega_scripts/set_system_variable.fn +15 -0
  66. aiohomematic/store/__init__.py +34 -0
  67. aiohomematic/store/dynamic.py +551 -0
  68. aiohomematic/store/persistent.py +988 -0
  69. aiohomematic/store/visibility.py +812 -0
  70. aiohomematic/support.py +664 -0
  71. aiohomematic/validator.py +112 -0
  72. aiohomematic-2025.11.3.dist-info/METADATA +144 -0
  73. aiohomematic-2025.11.3.dist-info/RECORD +77 -0
  74. aiohomematic-2025.11.3.dist-info/WHEEL +5 -0
  75. aiohomematic-2025.11.3.dist-info/entry_points.txt +2 -0
  76. aiohomematic-2025.11.3.dist-info/licenses/LICENSE +21 -0
  77. aiohomematic-2025.11.3.dist-info/top_level.txt +1 -0
@@ -0,0 +1,1128 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2021-2025
3
+ """Module for data points implemented using the light category."""
4
+
5
+ from __future__ import annotations
6
+
7
+ from collections.abc import Mapping
8
+ from enum import IntEnum, StrEnum
9
+ import math
10
+ from typing import Any, Final, TypedDict, Unpack
11
+
12
+ from aiohomematic.const import DataPointCategory, DataPointUsage, DeviceProfile, Field, Parameter
13
+ from aiohomematic.model import device as hmd
14
+ from aiohomematic.model.custom import definition as hmed
15
+ from aiohomematic.model.custom.data_point import CustomDataPoint
16
+ from aiohomematic.model.custom.support import CustomConfig, ExtendedConfig
17
+ from aiohomematic.model.data_point import CallParameterCollector, bind_collector
18
+ from aiohomematic.model.generic import DpAction, DpFloat, DpInteger, DpSelect, DpSensor, GenericDataPoint
19
+ from aiohomematic.property_decorators import state_property
20
+
21
+ _DIMMER_OFF: Final = 0.0
22
+ _EFFECT_OFF: Final = "Off"
23
+ _LEVEL_TO_BRIGHTNESS_MULTIPLIER: Final = 100
24
+ _MAX_BRIGHTNESS: Final = 255.0
25
+ _MAX_KELVIN: Final = 1000000
26
+ _MAX_MIREDS: Final = 500
27
+ _MAX_SATURATION: Final = 100.0
28
+ _MIN_BRIGHTNESS: Final = 0.0
29
+ _MIN_HUE: Final = 0.0
30
+ _MIN_MIREDS: Final = 153
31
+ _MIN_SATURATION: Final = 0.0
32
+ _NOT_USED: Final = 111600
33
+ _OLD_LEVEL: Final = 1.005
34
+ _SATURATION_MULTIPLIER: Final = 100
35
+
36
+
37
+ class _DeviceOperationMode(StrEnum):
38
+ """Enum with device operation modes."""
39
+
40
+ PWM = "4_PWM"
41
+ RGB = "RGB"
42
+ RGBW = "RGBW"
43
+ TUNABLE_WHITE = "2_TUNABLE_WHITE"
44
+
45
+
46
+ class _ColorBehaviour(StrEnum):
47
+ """Enum with color behaviours."""
48
+
49
+ DO_NOT_CARE = "DO_NOT_CARE"
50
+ OFF = "OFF"
51
+ OLD_VALUE = "OLD_VALUE"
52
+ ON = "ON"
53
+
54
+
55
+ class _FixedColor(StrEnum):
56
+ """Enum with colors."""
57
+
58
+ BLACK = "BLACK"
59
+ BLUE = "BLUE"
60
+ DO_NOT_CARE = "DO_NOT_CARE"
61
+ GREEN = "GREEN"
62
+ OLD_VALUE = "OLD_VALUE"
63
+ PURPLE = "PURPLE"
64
+ RED = "RED"
65
+ TURQUOISE = "TURQUOISE"
66
+ WHITE = "WHITE"
67
+ YELLOW = "YELLOW"
68
+
69
+
70
+ class _StateChangeArg(StrEnum):
71
+ """Enum with light state change arguments."""
72
+
73
+ BRIGHTNESS = "brightness"
74
+ COLOR_TEMP_KELVIN = "color_temp_kelvin"
75
+ EFFECT = "effect"
76
+ HS_COLOR = "hs_color"
77
+ OFF = "off"
78
+ ON = "on"
79
+ ON_TIME = "on_time"
80
+ RAMP_TIME = "ramp_time"
81
+
82
+
83
+ class _TimeUnit(IntEnum):
84
+ """Enum with time units."""
85
+
86
+ SECONDS = 0
87
+ MINUTES = 1
88
+ HOURS = 2
89
+
90
+
91
+ _NO_COLOR: Final = (
92
+ _FixedColor.BLACK,
93
+ _FixedColor.DO_NOT_CARE,
94
+ _FixedColor.OLD_VALUE,
95
+ )
96
+
97
+ _EXCLUDE_FROM_COLOR_BEHAVIOUR: Final = (
98
+ _ColorBehaviour.DO_NOT_CARE,
99
+ _ColorBehaviour.OFF,
100
+ _ColorBehaviour.OLD_VALUE,
101
+ )
102
+
103
+ _OFF_COLOR_BEHAVIOUR: Final = (
104
+ _ColorBehaviour.DO_NOT_CARE,
105
+ _ColorBehaviour.OFF,
106
+ _ColorBehaviour.OLD_VALUE,
107
+ )
108
+
109
+ _FIXED_COLOR_SWITCHER: Mapping[str, tuple[float, float]] = {
110
+ _FixedColor.WHITE: (_MIN_HUE, _MIN_SATURATION),
111
+ _FixedColor.RED: (_MIN_HUE, _MAX_SATURATION),
112
+ _FixedColor.YELLOW: (60.0, _MAX_SATURATION),
113
+ _FixedColor.GREEN: (120.0, _MAX_SATURATION),
114
+ _FixedColor.TURQUOISE: (180.0, _MAX_SATURATION),
115
+ _FixedColor.BLUE: (240.0, _MAX_SATURATION),
116
+ _FixedColor.PURPLE: (300.0, _MAX_SATURATION),
117
+ }
118
+
119
+
120
+ class LightOnArgs(TypedDict, total=False):
121
+ """Matcher for the light turn on arguments."""
122
+
123
+ brightness: int
124
+ color_temp_kelvin: int
125
+ effect: str
126
+ hs_color: tuple[float, float]
127
+ on_time: float
128
+ ramp_time: float
129
+
130
+
131
+ class LightOffArgs(TypedDict, total=False):
132
+ """Matcher for the light turn off arguments."""
133
+
134
+ on_time: float
135
+ ramp_time: float
136
+
137
+
138
+ class CustomDpDimmer(CustomDataPoint):
139
+ """Base class for Homematic light data point."""
140
+
141
+ __slots__ = (
142
+ "_dp_group_level",
143
+ "_dp_level",
144
+ "_dp_on_time_value",
145
+ "_dp_ramp_time_value",
146
+ )
147
+ _category = DataPointCategory.LIGHT
148
+
149
+ def _init_data_point_fields(self) -> None:
150
+ """Init the data_point fields."""
151
+ super()._init_data_point_fields()
152
+ self._dp_level: DpFloat = self._get_data_point(field=Field.LEVEL, data_point_type=DpFloat)
153
+ self._dp_group_level: DpSensor[float | None] = self._get_data_point(
154
+ field=Field.GROUP_LEVEL, data_point_type=DpSensor[float | None]
155
+ )
156
+ self._dp_on_time_value: DpAction = self._get_data_point(field=Field.ON_TIME_VALUE, data_point_type=DpAction)
157
+ self._dp_ramp_time_value: DpAction = self._get_data_point(field=Field.RAMP_TIME_VALUE, data_point_type=DpAction)
158
+
159
+ @state_property
160
+ def is_on(self) -> bool | None:
161
+ """Return true if dimmer is on."""
162
+ return self._dp_level.value is not None and self._dp_level.value > _DIMMER_OFF
163
+
164
+ @state_property
165
+ def brightness(self) -> int | None:
166
+ """Return the brightness of this light between min/max brightness."""
167
+ return int((self._dp_level.value or _MIN_BRIGHTNESS) * _MAX_BRIGHTNESS)
168
+
169
+ @property
170
+ def brightness_pct(self) -> int | None:
171
+ """Return the brightness in percent of this light."""
172
+ return int((self._dp_level.value or _MIN_BRIGHTNESS) * _LEVEL_TO_BRIGHTNESS_MULTIPLIER)
173
+
174
+ @property
175
+ def group_brightness(self) -> int | None:
176
+ """Return the group brightness of this light between min/max brightness."""
177
+ if self._dp_group_level.value is not None:
178
+ return int(self._dp_group_level.value * _MAX_BRIGHTNESS)
179
+ return None
180
+
181
+ @property
182
+ def group_brightness_pct(self) -> int | None:
183
+ """Return the group brightness in percent of this light."""
184
+ if self._dp_group_level.value is not None:
185
+ return int(self._dp_group_level.value * _LEVEL_TO_BRIGHTNESS_MULTIPLIER)
186
+ return None
187
+
188
+ @state_property
189
+ def color_temp_kelvin(self) -> int | None:
190
+ """Return the color temperature in kelvin."""
191
+ return None
192
+
193
+ @state_property
194
+ def hs_color(self) -> tuple[float, float] | None:
195
+ """Return the hue and saturation color value [float, float]."""
196
+ return None
197
+
198
+ @property
199
+ def supports_brightness(self) -> bool:
200
+ """Flag if light supports brightness."""
201
+ return isinstance(self._dp_level, DpFloat)
202
+
203
+ @property
204
+ def supports_color_temperature(self) -> bool:
205
+ """Flag if light supports color temperature."""
206
+ return self.color_temp_kelvin is not None
207
+
208
+ @property
209
+ def supports_effects(self) -> bool:
210
+ """Flag if light supports effects."""
211
+ return self.effects is not None and len(self.effects) > 0
212
+
213
+ @property
214
+ def supports_hs_color(self) -> bool:
215
+ """Flag if light supports color."""
216
+ return self.hs_color is not None
217
+
218
+ @property
219
+ def supports_transition(self) -> bool:
220
+ """Flag if light supports transition."""
221
+ return isinstance(self._dp_ramp_time_value, DpAction)
222
+
223
+ @state_property
224
+ def effect(self) -> str | None:
225
+ """Return the current effect."""
226
+ return None
227
+
228
+ @state_property
229
+ def effects(self) -> tuple[str, ...] | None:
230
+ """Return the supported effects."""
231
+ return None
232
+
233
+ @bind_collector()
234
+ async def turn_on(self, *, collector: CallParameterCollector | None = None, **kwargs: Unpack[LightOnArgs]) -> None:
235
+ """Turn the light on."""
236
+ if (on_time := kwargs.get("on_time")) is not None:
237
+ self.set_timer_on_time(on_time=on_time)
238
+ if not self.is_state_change(on=True, **kwargs):
239
+ return
240
+
241
+ if (timer := self.get_and_start_timer()) is not None:
242
+ await self._set_on_time_value(on_time=timer, collector=collector)
243
+ if ramp_time := kwargs.get("ramp_time"):
244
+ await self._set_ramp_time_on_value(ramp_time=ramp_time, collector=collector)
245
+ if not (brightness := kwargs.get("brightness", self.brightness)):
246
+ brightness = int(_MAX_BRIGHTNESS)
247
+ level = brightness / _MAX_BRIGHTNESS
248
+ await self._dp_level.send_value(value=level, collector=collector)
249
+
250
+ @bind_collector()
251
+ async def turn_off(
252
+ self, *, collector: CallParameterCollector | None = None, **kwargs: Unpack[LightOffArgs]
253
+ ) -> None:
254
+ """Turn the light off."""
255
+ self.reset_timer_on_time()
256
+ if not self.is_state_change(off=True, **kwargs):
257
+ return
258
+ if ramp_time := kwargs.get("ramp_time"):
259
+ await self._set_ramp_time_off_value(ramp_time=ramp_time, collector=collector)
260
+ await self._dp_level.send_value(value=_DIMMER_OFF, collector=collector)
261
+
262
+ @bind_collector()
263
+ async def _set_on_time_value(self, *, on_time: float, collector: CallParameterCollector | None = None) -> None:
264
+ """Set the on time value in seconds."""
265
+ await self._dp_on_time_value.send_value(value=on_time, collector=collector, do_validate=False)
266
+
267
+ async def _set_ramp_time_on_value(
268
+ self, *, ramp_time: float, collector: CallParameterCollector | None = None
269
+ ) -> None:
270
+ """Set the ramp time value in seconds."""
271
+ await self._dp_ramp_time_value.send_value(value=ramp_time, collector=collector)
272
+
273
+ async def _set_ramp_time_off_value(
274
+ self, *, ramp_time: float, collector: CallParameterCollector | None = None
275
+ ) -> None:
276
+ """Set the ramp time value in seconds."""
277
+ await self._set_ramp_time_on_value(ramp_time=ramp_time, collector=collector)
278
+
279
+ def is_state_change(self, **kwargs: Any) -> bool:
280
+ """Check if the state changes due to kwargs."""
281
+ if (on_time_running := self.timer_on_time_running) is not None and on_time_running is True:
282
+ return True
283
+ if self.timer_on_time is not None:
284
+ return True
285
+ if kwargs.get(_StateChangeArg.ON_TIME) is not None:
286
+ return True
287
+ if kwargs.get(_StateChangeArg.RAMP_TIME) is not None:
288
+ return True
289
+ if kwargs.get(_StateChangeArg.ON) is not None and self.is_on is not True and len(kwargs) == 1:
290
+ return True
291
+ if kwargs.get(_StateChangeArg.OFF) is not None and self.is_on is not False and len(kwargs) == 1:
292
+ return True
293
+ if (brightness := kwargs.get(_StateChangeArg.BRIGHTNESS)) is not None and brightness != self.brightness:
294
+ return True
295
+ if (hs_color := kwargs.get(_StateChangeArg.HS_COLOR)) is not None and hs_color != self.hs_color:
296
+ return True
297
+ if (
298
+ color_temp_kelvin := kwargs.get(_StateChangeArg.COLOR_TEMP_KELVIN)
299
+ ) is not None and color_temp_kelvin != self.color_temp_kelvin:
300
+ return True
301
+ if (effect := kwargs.get(_StateChangeArg.EFFECT)) is not None and effect != self.effect:
302
+ return True
303
+ return super().is_state_change(**kwargs)
304
+
305
+
306
+ class CustomDpColorDimmer(CustomDpDimmer):
307
+ """Class for Homematic dimmer with color data point."""
308
+
309
+ __slots__ = ("_dp_color",)
310
+
311
+ def _init_data_point_fields(self) -> None:
312
+ """Init the data_point fields."""
313
+ super()._init_data_point_fields()
314
+ self._dp_color: DpInteger = self._get_data_point(field=Field.COLOR, data_point_type=DpInteger)
315
+
316
+ @state_property
317
+ def hs_color(self) -> tuple[float, float] | None:
318
+ """Return the hue and saturation color value [float, float]."""
319
+ if (color := self._dp_color.value) is not None:
320
+ if color >= 200:
321
+ # 200 is a special case (white), so we have a saturation of 0.
322
+ # Larger values are undefined.
323
+ # For the sake of robustness we return "white" anyway.
324
+ return _MIN_HUE, _MIN_SATURATION
325
+
326
+ # For all other colors we assume saturation of 1
327
+ return color / 200 * 360, _MAX_SATURATION
328
+ return _MIN_HUE, _MIN_SATURATION
329
+
330
+ @bind_collector()
331
+ async def turn_on(self, *, collector: CallParameterCollector | None = None, **kwargs: Unpack[LightOnArgs]) -> None:
332
+ """Turn the light on."""
333
+ if not self.is_state_change(on=True, **kwargs):
334
+ return
335
+ if (hs_color := kwargs.get("hs_color")) is not None:
336
+ khue, ksaturation = hs_color
337
+ hue = khue / 360
338
+ saturation = ksaturation / _SATURATION_MULTIPLIER
339
+ color = 200 if saturation < 0.1 else int(round(max(min(hue, 1), 0) * 199))
340
+ await self._dp_color.send_value(value=color, collector=collector)
341
+ await super().turn_on(collector=collector, **kwargs)
342
+
343
+
344
+ class CustomDpColorDimmerEffect(CustomDpColorDimmer):
345
+ """Class for Homematic dimmer with color data point."""
346
+
347
+ __slots__ = ("_dp_effect",)
348
+
349
+ _effects: tuple[str, ...] = (
350
+ _EFFECT_OFF,
351
+ "Slow color change",
352
+ "Medium color change",
353
+ "Fast color change",
354
+ "Campemit",
355
+ "Waterfall",
356
+ "TV simulation",
357
+ )
358
+
359
+ def _init_data_point_fields(self) -> None:
360
+ """Init the data_point fields."""
361
+ super()._init_data_point_fields()
362
+ self._dp_effect: DpInteger = self._get_data_point(field=Field.PROGRAM, data_point_type=DpInteger)
363
+
364
+ @state_property
365
+ def effect(self) -> str | None:
366
+ """Return the current effect."""
367
+ if self._dp_effect.value is not None:
368
+ return self._effects[int(self._dp_effect.value)]
369
+ return None
370
+
371
+ @state_property
372
+ def effects(self) -> tuple[str, ...] | None:
373
+ """Return the supported effects."""
374
+ return self._effects
375
+
376
+ @bind_collector()
377
+ async def turn_on(self, *, collector: CallParameterCollector | None = None, **kwargs: Unpack[LightOnArgs]) -> None:
378
+ """Turn the light on."""
379
+ if not self.is_state_change(on=True, **kwargs):
380
+ return
381
+
382
+ if "effect" not in kwargs and self.supports_effects and self.effect != _EFFECT_OFF:
383
+ await self._dp_effect.send_value(value=0, collector=collector, collector_order=5)
384
+
385
+ if (
386
+ self.supports_effects
387
+ and (effect := kwargs.get("effect")) is not None
388
+ and (effect_idx := self._effects.index(effect)) is not None
389
+ ):
390
+ await self._dp_effect.send_value(value=effect_idx, collector=collector, collector_order=95)
391
+
392
+ await super().turn_on(collector=collector, **kwargs)
393
+
394
+
395
+ class CustomDpColorTempDimmer(CustomDpDimmer):
396
+ """Class for Homematic dimmer with color temperature."""
397
+
398
+ __slots__ = ("_dp_color_level",)
399
+
400
+ def _init_data_point_fields(self) -> None:
401
+ """Init the data_point fields."""
402
+ super()._init_data_point_fields()
403
+ self._dp_color_level: DpFloat = self._get_data_point(field=Field.COLOR_LEVEL, data_point_type=DpFloat)
404
+
405
+ @state_property
406
+ def color_temp_kelvin(self) -> int | None:
407
+ """Return the color temperature in kelvin."""
408
+ return math.floor(
409
+ _MAX_KELVIN / int(_MAX_MIREDS - (_MAX_MIREDS - _MIN_MIREDS) * (self._dp_color_level.value or _DIMMER_OFF))
410
+ )
411
+
412
+ @bind_collector()
413
+ async def turn_on(self, *, collector: CallParameterCollector | None = None, **kwargs: Unpack[LightOnArgs]) -> None:
414
+ """Turn the light on."""
415
+ if not self.is_state_change(on=True, **kwargs):
416
+ return
417
+ if (color_temp_kelvin := kwargs.get("color_temp_kelvin")) is not None:
418
+ color_level = (_MAX_MIREDS - math.floor(_MAX_KELVIN / color_temp_kelvin)) / (_MAX_MIREDS - _MIN_MIREDS)
419
+ await self._dp_color_level.send_value(value=color_level, collector=collector)
420
+
421
+ await super().turn_on(collector=collector, **kwargs)
422
+
423
+
424
+ class CustomDpIpRGBWLight(CustomDpDimmer):
425
+ """Class for HomematicIP HmIP-RGBW light data point."""
426
+
427
+ __slots__ = (
428
+ "_dp_activity_state",
429
+ "_dp_color_temperature_kelvin",
430
+ "_dp_device_operation_mode",
431
+ "_dp_effect",
432
+ "_dp_hue",
433
+ "_dp_on_time_unit",
434
+ "_dp_ramp_time_to_off_unit",
435
+ "_dp_ramp_time_to_off_value",
436
+ "_dp_ramp_time_unit",
437
+ "_dp_saturation",
438
+ )
439
+
440
+ def _init_data_point_fields(self) -> None:
441
+ """Init the data_point fields."""
442
+ super()._init_data_point_fields()
443
+ self._dp_activity_state: DpSensor[str | None] = self._get_data_point(
444
+ field=Field.DIRECTION, data_point_type=DpSensor[str | None]
445
+ )
446
+ self._dp_color_temperature_kelvin: DpInteger = self._get_data_point(
447
+ field=Field.COLOR_TEMPERATURE, data_point_type=DpInteger
448
+ )
449
+ self._dp_device_operation_mode: DpSelect = self._get_data_point(
450
+ field=Field.DEVICE_OPERATION_MODE, data_point_type=DpSelect
451
+ )
452
+ self._dp_on_time_unit: DpAction = self._get_data_point(field=Field.ON_TIME_UNIT, data_point_type=DpAction)
453
+ self._dp_effect: DpAction = self._get_data_point(field=Field.EFFECT, data_point_type=DpAction)
454
+ self._dp_hue: DpInteger = self._get_data_point(field=Field.HUE, data_point_type=DpInteger)
455
+ self._dp_ramp_time_to_off_unit: DpAction = self._get_data_point(
456
+ field=Field.RAMP_TIME_TO_OFF_UNIT, data_point_type=DpAction
457
+ )
458
+ self._dp_ramp_time_to_off_value: DpAction = self._get_data_point(
459
+ field=Field.RAMP_TIME_TO_OFF_VALUE, data_point_type=DpAction
460
+ )
461
+ self._dp_ramp_time_unit: DpAction = self._get_data_point(field=Field.RAMP_TIME_UNIT, data_point_type=DpAction)
462
+ self._dp_saturation: DpFloat = self._get_data_point(field=Field.SATURATION, data_point_type=DpFloat)
463
+
464
+ @state_property
465
+ def color_temp_kelvin(self) -> int | None:
466
+ """Return the color temperature in kelvin."""
467
+ if not self._dp_color_temperature_kelvin.value:
468
+ return None
469
+ return self._dp_color_temperature_kelvin.value
470
+
471
+ @state_property
472
+ def hs_color(self) -> tuple[float, float] | None:
473
+ """Return the hue and saturation color value [float, float]."""
474
+ if self._dp_hue.value is not None and self._dp_saturation.value is not None:
475
+ return self._dp_hue.value, self._dp_saturation.value * _SATURATION_MULTIPLIER
476
+ return None
477
+
478
+ @property
479
+ def _relevant_data_points(self) -> tuple[GenericDataPoint, ...]:
480
+ """Returns the list of relevant data points. To be overridden by subclasses."""
481
+ if self._device_operation_mode == _DeviceOperationMode.RGBW:
482
+ return (
483
+ self._dp_hue,
484
+ self._dp_level,
485
+ self._dp_saturation,
486
+ self._dp_color_temperature_kelvin,
487
+ )
488
+ if self._device_operation_mode == _DeviceOperationMode.RGB:
489
+ return self._dp_hue, self._dp_level, self._dp_saturation
490
+ if self._device_operation_mode == _DeviceOperationMode.TUNABLE_WHITE:
491
+ return self._dp_level, self._dp_color_temperature_kelvin
492
+ return (self._dp_level,)
493
+
494
+ @property
495
+ def supports_color_temperature(self) -> bool:
496
+ """Flag if light supports color temperature."""
497
+ return self._device_operation_mode == _DeviceOperationMode.TUNABLE_WHITE
498
+
499
+ @property
500
+ def supports_effects(self) -> bool:
501
+ """Flag if light supports effects."""
502
+ return (
503
+ self._device_operation_mode != _DeviceOperationMode.PWM
504
+ and self.effects is not None
505
+ and len(self.effects) > 0
506
+ )
507
+
508
+ @property
509
+ def supports_hs_color(self) -> bool:
510
+ """Flag if light supports color."""
511
+ return self._device_operation_mode in (
512
+ _DeviceOperationMode.RGBW,
513
+ _DeviceOperationMode.RGB,
514
+ )
515
+
516
+ @property
517
+ def usage(self) -> DataPointUsage:
518
+ """
519
+ Return the data_point usage.
520
+
521
+ Avoid creating data points that are not usable in selected device operation mode.
522
+ """
523
+ if (
524
+ self._device_operation_mode in (_DeviceOperationMode.RGB, _DeviceOperationMode.RGBW)
525
+ and self._channel.no in (2, 3, 4)
526
+ ) or (self._device_operation_mode == _DeviceOperationMode.TUNABLE_WHITE and self._channel.no in (3, 4)):
527
+ return DataPointUsage.NO_CREATE
528
+ return self._get_data_point_usage()
529
+
530
+ @state_property
531
+ def effects(self) -> tuple[str, ...] | None:
532
+ """Return the supported effects."""
533
+ return self._dp_effect.values or ()
534
+
535
+ @bind_collector()
536
+ async def turn_on(self, *, collector: CallParameterCollector | None = None, **kwargs: Unpack[LightOnArgs]) -> None:
537
+ """Turn the light on."""
538
+ if on_time := (kwargs.get("on_time") or self.get_and_start_timer()):
539
+ kwargs["on_time"] = on_time
540
+ if not self.is_state_change(on=True, **kwargs):
541
+ return
542
+ if (hs_color := kwargs.get("hs_color")) is not None:
543
+ hue, ksaturation = hs_color
544
+ saturation = ksaturation / _SATURATION_MULTIPLIER
545
+ await self._dp_hue.send_value(value=int(hue), collector=collector)
546
+ await self._dp_saturation.send_value(value=saturation, collector=collector)
547
+ if color_temp_kelvin := kwargs.get("color_temp_kelvin"):
548
+ await self._dp_color_temperature_kelvin.send_value(value=color_temp_kelvin, collector=collector)
549
+ if on_time is None and kwargs.get("ramp_time"):
550
+ await self._set_on_time_value(on_time=_NOT_USED, collector=collector)
551
+ if self.supports_effects and (effect := kwargs.get("effect")) is not None:
552
+ await self._dp_effect.send_value(value=effect, collector=collector)
553
+
554
+ await super().turn_on(collector=collector, **kwargs)
555
+
556
+ @bind_collector()
557
+ async def turn_off(
558
+ self, *, collector: CallParameterCollector | None = None, **kwargs: Unpack[LightOffArgs]
559
+ ) -> None:
560
+ """Turn the light off."""
561
+ if kwargs.get("on_time") is None and kwargs.get("ramp_time"):
562
+ await self._set_on_time_value(on_time=_NOT_USED, collector=collector)
563
+ await super().turn_off(collector=collector, **kwargs)
564
+
565
+ @property
566
+ def _device_operation_mode(self) -> _DeviceOperationMode:
567
+ """Return the device operation mode."""
568
+ if (mode := self._dp_device_operation_mode.value) is None:
569
+ return _DeviceOperationMode.RGBW
570
+ return _DeviceOperationMode(mode)
571
+
572
+ @bind_collector()
573
+ async def _set_on_time_value(self, *, on_time: float, collector: CallParameterCollector | None = None) -> None:
574
+ """Set the on time value in seconds."""
575
+ on_time, on_time_unit = _recalc_unit_timer(time=on_time)
576
+ if on_time_unit is not None:
577
+ await self._dp_on_time_unit.send_value(value=on_time_unit, collector=collector)
578
+ await self._dp_on_time_value.send_value(value=float(on_time), collector=collector)
579
+
580
+ async def _set_ramp_time_on_value(
581
+ self, *, ramp_time: float, collector: CallParameterCollector | None = None
582
+ ) -> None:
583
+ """Set the ramp time value in seconds."""
584
+ ramp_time, ramp_time_unit = _recalc_unit_timer(time=ramp_time)
585
+ if ramp_time_unit is not None:
586
+ await self._dp_ramp_time_unit.send_value(value=ramp_time_unit, collector=collector)
587
+ await self._dp_ramp_time_value.send_value(value=float(ramp_time), collector=collector)
588
+
589
+ async def _set_ramp_time_off_value(
590
+ self, *, ramp_time: float, collector: CallParameterCollector | None = None
591
+ ) -> None:
592
+ """Set the ramp time value in seconds."""
593
+ ramp_time, ramp_time_unit = _recalc_unit_timer(time=ramp_time)
594
+ if ramp_time_unit is not None:
595
+ await self._dp_ramp_time_unit.send_value(value=ramp_time_unit, collector=collector)
596
+ await self._dp_ramp_time_value.send_value(value=float(ramp_time), collector=collector)
597
+
598
+
599
+ class CustomDpIpDrgDaliLight(CustomDpDimmer):
600
+ """Class for HomematicIP HmIP-DRG-DALI light data point."""
601
+
602
+ __slots__ = (
603
+ "_dp_color_temperature_kelvin",
604
+ "_dp_effect",
605
+ "_dp_hue",
606
+ "_dp_on_time_unit",
607
+ "_dp_ramp_time_to_off_unit",
608
+ "_dp_ramp_time_to_off_value",
609
+ "_dp_ramp_time_unit",
610
+ "_dp_saturation",
611
+ )
612
+
613
+ def _init_data_point_fields(self) -> None:
614
+ """Init the data_point fields."""
615
+ super()._init_data_point_fields()
616
+ self._dp_color_temperature_kelvin: DpInteger = self._get_data_point(
617
+ field=Field.COLOR_TEMPERATURE, data_point_type=DpInteger
618
+ )
619
+ self._dp_on_time_unit: DpAction = self._get_data_point(field=Field.ON_TIME_UNIT, data_point_type=DpAction)
620
+ self._dp_effect: DpAction = self._get_data_point(field=Field.EFFECT, data_point_type=DpAction)
621
+ self._dp_hue: DpInteger = self._get_data_point(field=Field.HUE, data_point_type=DpInteger)
622
+ self._dp_ramp_time_to_off_unit: DpAction = self._get_data_point(
623
+ field=Field.RAMP_TIME_TO_OFF_UNIT, data_point_type=DpAction
624
+ )
625
+ self._dp_ramp_time_to_off_value: DpAction = self._get_data_point(
626
+ field=Field.RAMP_TIME_TO_OFF_VALUE, data_point_type=DpAction
627
+ )
628
+ self._dp_ramp_time_unit: DpAction = self._get_data_point(field=Field.RAMP_TIME_UNIT, data_point_type=DpAction)
629
+ self._dp_saturation: DpFloat = self._get_data_point(field=Field.SATURATION, data_point_type=DpFloat)
630
+
631
+ @state_property
632
+ def color_temp_kelvin(self) -> int | None:
633
+ """Return the color temperature in kelvin."""
634
+ if not self._dp_color_temperature_kelvin.value:
635
+ return None
636
+ return self._dp_color_temperature_kelvin.value
637
+
638
+ @state_property
639
+ def hs_color(self) -> tuple[float, float] | None:
640
+ """Return the hue and saturation color value [float, float]."""
641
+ if self._dp_hue.value is not None and self._dp_saturation.value is not None:
642
+ return self._dp_hue.value, self._dp_saturation.value * _SATURATION_MULTIPLIER
643
+ return None
644
+
645
+ @property
646
+ def _relevant_data_points(self) -> tuple[GenericDataPoint, ...]:
647
+ """Returns the list of relevant data points. To be overridden by subclasses."""
648
+ return (self._dp_level,)
649
+
650
+ @state_property
651
+ def effects(self) -> tuple[str, ...] | None:
652
+ """Return the supported effects."""
653
+ return self._dp_effect.values or ()
654
+
655
+ @bind_collector()
656
+ async def turn_on(self, *, collector: CallParameterCollector | None = None, **kwargs: Unpack[LightOnArgs]) -> None:
657
+ """Turn the light on."""
658
+ if not self.is_state_change(on=True, **kwargs):
659
+ return
660
+ if (hs_color := kwargs.get("hs_color")) is not None:
661
+ hue, ksaturation = hs_color
662
+ saturation = ksaturation / _SATURATION_MULTIPLIER
663
+ await self._dp_hue.send_value(value=int(hue), collector=collector)
664
+ await self._dp_saturation.send_value(value=saturation, collector=collector)
665
+ if color_temp_kelvin := kwargs.get("color_temp_kelvin"):
666
+ await self._dp_color_temperature_kelvin.send_value(value=color_temp_kelvin, collector=collector)
667
+ if kwargs.get("on_time") is None and kwargs.get("ramp_time"):
668
+ await self._set_on_time_value(on_time=_NOT_USED, collector=collector)
669
+ if self.supports_effects and (effect := kwargs.get("effect")) is not None:
670
+ await self._dp_effect.send_value(value=effect, collector=collector)
671
+
672
+ await super().turn_on(collector=collector, **kwargs)
673
+
674
+ @bind_collector()
675
+ async def _set_on_time_value(self, *, on_time: float, collector: CallParameterCollector | None = None) -> None:
676
+ """Set the on time value in seconds."""
677
+ on_time, on_time_unit = _recalc_unit_timer(time=on_time)
678
+ if on_time_unit:
679
+ await self._dp_on_time_unit.send_value(value=on_time_unit, collector=collector)
680
+ await self._dp_on_time_value.send_value(value=float(on_time), collector=collector)
681
+
682
+ async def _set_ramp_time_on_value(
683
+ self, *, ramp_time: float, collector: CallParameterCollector | None = None
684
+ ) -> None:
685
+ """Set the ramp time value in seconds."""
686
+ ramp_time, ramp_time_unit = _recalc_unit_timer(time=ramp_time)
687
+ if ramp_time_unit:
688
+ await self._dp_ramp_time_unit.send_value(value=ramp_time_unit, collector=collector)
689
+ await self._dp_ramp_time_value.send_value(value=float(ramp_time), collector=collector)
690
+
691
+ async def _set_ramp_time_off_value(
692
+ self, *, ramp_time: float, collector: CallParameterCollector | None = None
693
+ ) -> None:
694
+ """Set the ramp time value in seconds."""
695
+ ramp_time, ramp_time_unit = _recalc_unit_timer(time=ramp_time)
696
+ if ramp_time_unit:
697
+ await self._dp_ramp_time_unit.send_value(value=ramp_time_unit, collector=collector)
698
+ await self._dp_ramp_time_value.send_value(value=float(ramp_time), collector=collector)
699
+
700
+
701
+ class CustomDpIpFixedColorLight(CustomDpDimmer):
702
+ """Class for HomematicIP HmIP-BSL light data point."""
703
+
704
+ __slots__ = (
705
+ "_dp_channel_color",
706
+ "_dp_color",
707
+ "_dp_effect",
708
+ "_dp_on_time_unit",
709
+ "_dp_ramp_time_unit",
710
+ "_effect_list",
711
+ )
712
+
713
+ @state_property
714
+ def color_name(self) -> str | None:
715
+ """Return the name of the color."""
716
+ return self._dp_color.value
717
+
718
+ @property
719
+ def channel_color_name(self) -> str | None:
720
+ """Return the name of the channel color."""
721
+ return self._dp_channel_color.value
722
+
723
+ def _init_data_point_fields(self) -> None:
724
+ """Init the data_point fields."""
725
+ super()._init_data_point_fields()
726
+ self._dp_color: DpSelect = self._get_data_point(field=Field.COLOR, data_point_type=DpSelect)
727
+ self._dp_channel_color: DpSensor[str | None] = self._get_data_point(
728
+ field=Field.CHANNEL_COLOR, data_point_type=DpSensor[str | None]
729
+ )
730
+ self._dp_on_time_unit: DpAction = self._get_data_point(field=Field.ON_TIME_UNIT, data_point_type=DpAction)
731
+ self._dp_ramp_time_unit: DpAction = self._get_data_point(field=Field.RAMP_TIME_UNIT, data_point_type=DpAction)
732
+ self._dp_effect: DpSelect = self._get_data_point(field=Field.COLOR_BEHAVIOUR, data_point_type=DpSelect)
733
+ self._effect_list = (
734
+ tuple(str(item) for item in self._dp_effect.values if item not in _EXCLUDE_FROM_COLOR_BEHAVIOUR)
735
+ if (self._dp_effect and self._dp_effect.values)
736
+ else ()
737
+ )
738
+
739
+ @state_property
740
+ def effect(self) -> str | None:
741
+ """Return the current effect."""
742
+ if (effect := self._dp_effect.value) is not None and effect in self._effect_list:
743
+ return effect
744
+ return None
745
+
746
+ @state_property
747
+ def effects(self) -> tuple[str, ...] | None:
748
+ """Return the supported effects."""
749
+ return self._effect_list
750
+
751
+ @state_property
752
+ def hs_color(self) -> tuple[float, float] | None:
753
+ """Return the hue and saturation color value [float, float]."""
754
+ if (
755
+ self._dp_color.value is not None
756
+ and (hs_color := _FIXED_COLOR_SWITCHER.get(self._dp_color.value)) is not None
757
+ ):
758
+ return hs_color
759
+ return _MIN_HUE, _MIN_SATURATION
760
+
761
+ @property
762
+ def channel_hs_color(self) -> tuple[float, float] | None:
763
+ """Return the channel hue and saturation color value [float, float]."""
764
+ if self._dp_channel_color.value is not None:
765
+ return _FIXED_COLOR_SWITCHER.get(self._dp_channel_color.value, (_MIN_HUE, _MIN_SATURATION))
766
+ return None
767
+
768
+ @bind_collector()
769
+ async def turn_on(self, *, collector: CallParameterCollector | None = None, **kwargs: Unpack[LightOnArgs]) -> None:
770
+ """Turn the light on."""
771
+ if not self.is_state_change(on=True, **kwargs):
772
+ return
773
+ if (hs_color := kwargs.get("hs_color")) is not None:
774
+ simple_rgb_color = _convert_color(color=hs_color)
775
+ await self._dp_color.send_value(value=simple_rgb_color, collector=collector)
776
+ elif self.color_name in _NO_COLOR:
777
+ await self._dp_color.send_value(value=_FixedColor.WHITE, collector=collector)
778
+ if (effect := kwargs.get("effect")) is not None and effect in self._effect_list:
779
+ await self._dp_effect.send_value(value=effect, collector=collector)
780
+ elif self._dp_effect.value not in self._effect_list:
781
+ await self._dp_effect.send_value(value=_ColorBehaviour.ON, collector=collector)
782
+ elif (color_behaviour := self._dp_effect.value) is not None:
783
+ await self._dp_effect.send_value(value=color_behaviour, collector=collector)
784
+
785
+ await super().turn_on(collector=collector, **kwargs)
786
+
787
+ @bind_collector()
788
+ async def _set_on_time_value(self, *, on_time: float, collector: CallParameterCollector | None = None) -> None:
789
+ """Set the on time value in seconds."""
790
+ on_time, on_time_unit = _recalc_unit_timer(time=on_time)
791
+ if on_time_unit:
792
+ await self._dp_on_time_unit.send_value(value=on_time_unit, collector=collector)
793
+ await self._dp_on_time_value.send_value(value=float(on_time), collector=collector)
794
+
795
+ async def _set_ramp_time_on_value(
796
+ self, *, ramp_time: float, collector: CallParameterCollector | None = None
797
+ ) -> None:
798
+ """Set the ramp time value in seconds."""
799
+ ramp_time, ramp_time_unit = _recalc_unit_timer(time=ramp_time)
800
+ if ramp_time_unit:
801
+ await self._dp_ramp_time_unit.send_value(value=ramp_time_unit, collector=collector)
802
+ await self._dp_ramp_time_value.send_value(value=float(ramp_time), collector=collector)
803
+
804
+
805
+ def _recalc_unit_timer(*, time: float) -> tuple[float, int | None]:
806
+ """Recalculate unit and value of timer."""
807
+ ramp_time_unit = _TimeUnit.SECONDS
808
+ if time == _NOT_USED:
809
+ return time, None
810
+ if time > 16343:
811
+ time /= 60
812
+ ramp_time_unit = _TimeUnit.MINUTES
813
+ if time > 16343:
814
+ time /= 60
815
+ ramp_time_unit = _TimeUnit.HOURS
816
+ return time, ramp_time_unit
817
+
818
+
819
+ def _convert_color(*, color: tuple[float, float]) -> str:
820
+ """
821
+ Convert the given color to the reduced color of the device.
822
+
823
+ Device contains only 8 colors including white and black,
824
+ so a conversion is required.
825
+ """
826
+ hue: int = int(color[0])
827
+ if int(color[1]) < 5:
828
+ return _FixedColor.WHITE
829
+ if 30 < hue <= 90:
830
+ return _FixedColor.YELLOW
831
+ if 90 < hue <= 150:
832
+ return _FixedColor.GREEN
833
+ if 150 < hue <= 210:
834
+ return _FixedColor.TURQUOISE
835
+ if 210 < hue <= 270:
836
+ return _FixedColor.BLUE
837
+ if 270 < hue <= 330:
838
+ return _FixedColor.PURPLE
839
+ return _FixedColor.RED
840
+
841
+
842
+ def make_ip_dimmer(
843
+ *,
844
+ channel: hmd.Channel,
845
+ custom_config: CustomConfig,
846
+ ) -> None:
847
+ """Create HomematicIP dimmer data point."""
848
+ hmed.make_custom_data_point(
849
+ channel=channel,
850
+ data_point_class=CustomDpDimmer,
851
+ device_profile=DeviceProfile.IP_DIMMER,
852
+ custom_config=custom_config,
853
+ )
854
+
855
+
856
+ def make_rf_dimmer(
857
+ *,
858
+ channel: hmd.Channel,
859
+ custom_config: CustomConfig,
860
+ ) -> None:
861
+ """Create Homematic classic dimmer data point."""
862
+ hmed.make_custom_data_point(
863
+ channel=channel,
864
+ data_point_class=CustomDpDimmer,
865
+ device_profile=DeviceProfile.RF_DIMMER,
866
+ custom_config=custom_config,
867
+ )
868
+
869
+
870
+ def make_rf_dimmer_color(
871
+ *,
872
+ channel: hmd.Channel,
873
+ custom_config: CustomConfig,
874
+ ) -> None:
875
+ """Create Homematic classic dimmer with color data point."""
876
+ hmed.make_custom_data_point(
877
+ channel=channel,
878
+ data_point_class=CustomDpColorDimmer,
879
+ device_profile=DeviceProfile.RF_DIMMER_COLOR,
880
+ custom_config=custom_config,
881
+ )
882
+
883
+
884
+ def make_rf_dimmer_color_fixed(
885
+ *,
886
+ channel: hmd.Channel,
887
+ custom_config: CustomConfig,
888
+ ) -> None:
889
+ """Create Homematic classic dimmer with fixed color data point."""
890
+ hmed.make_custom_data_point(
891
+ channel=channel,
892
+ data_point_class=CustomDpColorDimmer,
893
+ device_profile=DeviceProfile.RF_DIMMER_COLOR_FIXED,
894
+ custom_config=custom_config,
895
+ )
896
+
897
+
898
+ def make_rf_dimmer_color_effect(
899
+ *,
900
+ channel: hmd.Channel,
901
+ custom_config: CustomConfig,
902
+ ) -> None:
903
+ """Create Homematic classic dimmer and effect with color data point."""
904
+ hmed.make_custom_data_point(
905
+ channel=channel,
906
+ data_point_class=CustomDpColorDimmerEffect,
907
+ device_profile=DeviceProfile.RF_DIMMER_COLOR,
908
+ custom_config=custom_config,
909
+ )
910
+
911
+
912
+ def make_rf_dimmer_color_temp(
913
+ *,
914
+ channel: hmd.Channel,
915
+ custom_config: CustomConfig,
916
+ ) -> None:
917
+ """Create Homematic classic dimmer with color temperature data point."""
918
+ hmed.make_custom_data_point(
919
+ channel=channel,
920
+ data_point_class=CustomDpColorTempDimmer,
921
+ device_profile=DeviceProfile.RF_DIMMER_COLOR_TEMP,
922
+ custom_config=custom_config,
923
+ )
924
+
925
+
926
+ def make_rf_dimmer_with_virt_channel(
927
+ *,
928
+ channel: hmd.Channel,
929
+ custom_config: CustomConfig,
930
+ ) -> None:
931
+ """Create Homematic classic dimmer data point."""
932
+ hmed.make_custom_data_point(
933
+ channel=channel,
934
+ data_point_class=CustomDpDimmer,
935
+ device_profile=DeviceProfile.RF_DIMMER_WITH_VIRT_CHANNEL,
936
+ custom_config=custom_config,
937
+ )
938
+
939
+
940
+ def make_ip_fixed_color_light(
941
+ *,
942
+ channel: hmd.Channel,
943
+ custom_config: CustomConfig,
944
+ ) -> None:
945
+ """Create fixed color light data points like HmIP-BSL."""
946
+ hmed.make_custom_data_point(
947
+ channel=channel,
948
+ data_point_class=CustomDpIpFixedColorLight,
949
+ device_profile=DeviceProfile.IP_FIXED_COLOR_LIGHT,
950
+ custom_config=custom_config,
951
+ )
952
+
953
+
954
+ def make_ip_simple_fixed_color_light_wired(
955
+ *,
956
+ channel: hmd.Channel,
957
+ custom_config: CustomConfig,
958
+ ) -> None:
959
+ """Create simple fixed color light data points like HmIPW-WRC6."""
960
+ hmed.make_custom_data_point(
961
+ channel=channel,
962
+ data_point_class=CustomDpIpFixedColorLight,
963
+ device_profile=DeviceProfile.IP_SIMPLE_FIXED_COLOR_LIGHT_WIRED,
964
+ custom_config=custom_config,
965
+ )
966
+
967
+
968
+ def make_ip_rgbw_light(
969
+ *,
970
+ channel: hmd.Channel,
971
+ custom_config: CustomConfig,
972
+ ) -> None:
973
+ """Create simple fixed color light data points like HmIP-RGBW."""
974
+ hmed.make_custom_data_point(
975
+ channel=channel,
976
+ data_point_class=CustomDpIpRGBWLight,
977
+ device_profile=DeviceProfile.IP_RGBW_LIGHT,
978
+ custom_config=custom_config,
979
+ )
980
+
981
+
982
+ def make_ip_drg_dali_light(
983
+ *,
984
+ channel: hmd.Channel,
985
+ custom_config: CustomConfig,
986
+ ) -> None:
987
+ """Create color light data points like HmIP-DRG-DALI."""
988
+ hmed.make_custom_data_point(
989
+ channel=channel,
990
+ data_point_class=CustomDpIpDrgDaliLight,
991
+ device_profile=DeviceProfile.IP_DRG_DALI,
992
+ custom_config=custom_config,
993
+ )
994
+
995
+
996
+ # Case for device model is not relevant.
997
+ # HomeBrew (HB-) devices are always listed as HM-.
998
+ DEVICES: Mapping[str, CustomConfig | tuple[CustomConfig, ...]] = {
999
+ "263 132": CustomConfig(make_ce_func=make_rf_dimmer),
1000
+ "263 133": CustomConfig(make_ce_func=make_rf_dimmer_with_virt_channel),
1001
+ "263 134": CustomConfig(make_ce_func=make_rf_dimmer),
1002
+ "HBW-LC4-IN4-DR": CustomConfig(
1003
+ make_ce_func=make_rf_dimmer,
1004
+ channels=(
1005
+ 5,
1006
+ 6,
1007
+ 7,
1008
+ 8,
1009
+ ),
1010
+ extended=ExtendedConfig(
1011
+ additional_data_points={
1012
+ 1: (
1013
+ Parameter.PRESS_LONG,
1014
+ Parameter.PRESS_SHORT,
1015
+ Parameter.SENSOR,
1016
+ ),
1017
+ 2: (
1018
+ Parameter.PRESS_LONG,
1019
+ Parameter.PRESS_SHORT,
1020
+ Parameter.SENSOR,
1021
+ ),
1022
+ 3: (
1023
+ Parameter.PRESS_LONG,
1024
+ Parameter.PRESS_SHORT,
1025
+ Parameter.SENSOR,
1026
+ ),
1027
+ 4: (
1028
+ Parameter.PRESS_LONG,
1029
+ Parameter.PRESS_SHORT,
1030
+ Parameter.SENSOR,
1031
+ ),
1032
+ }
1033
+ ),
1034
+ ),
1035
+ "HBW-LC-RGBWW-IN6-DR": (
1036
+ CustomConfig(
1037
+ make_ce_func=make_rf_dimmer,
1038
+ channels=(7, 8, 9, 10, 11, 12),
1039
+ extended=ExtendedConfig(
1040
+ additional_data_points={
1041
+ (
1042
+ 1,
1043
+ 2,
1044
+ 3,
1045
+ 4,
1046
+ 5,
1047
+ 6,
1048
+ ): (
1049
+ Parameter.PRESS_LONG,
1050
+ Parameter.PRESS_SHORT,
1051
+ Parameter.SENSOR,
1052
+ )
1053
+ },
1054
+ ),
1055
+ ),
1056
+ CustomConfig(
1057
+ make_ce_func=make_rf_dimmer_color_fixed,
1058
+ channels=(13,),
1059
+ extended=ExtendedConfig(fixed_channels={15: {Field.COLOR: Parameter.COLOR}}),
1060
+ ),
1061
+ CustomConfig(
1062
+ make_ce_func=make_rf_dimmer_color_fixed,
1063
+ channels=(14,),
1064
+ extended=ExtendedConfig(fixed_channels={16: {Field.COLOR: Parameter.COLOR}}),
1065
+ ),
1066
+ ),
1067
+ "HM-DW-WM": CustomConfig(make_ce_func=make_rf_dimmer, channels=(1, 2, 3, 4)),
1068
+ "HM-LC-AO-SM": CustomConfig(make_ce_func=make_rf_dimmer_with_virt_channel),
1069
+ "HM-LC-DW-WM": CustomConfig(make_ce_func=make_rf_dimmer_color_temp, channels=(1, 3, 5)),
1070
+ "HM-LC-Dim1L-CV": CustomConfig(make_ce_func=make_rf_dimmer_with_virt_channel),
1071
+ "HM-LC-Dim1L-CV-2": CustomConfig(make_ce_func=make_rf_dimmer_with_virt_channel),
1072
+ "HM-LC-Dim1L-Pl": CustomConfig(make_ce_func=make_rf_dimmer_with_virt_channel),
1073
+ "HM-LC-Dim1L-Pl-2": CustomConfig(make_ce_func=make_rf_dimmer),
1074
+ "HM-LC-Dim1L-Pl-3": CustomConfig(make_ce_func=make_rf_dimmer_with_virt_channel),
1075
+ "HM-LC-Dim1PWM-CV": CustomConfig(make_ce_func=make_rf_dimmer_with_virt_channel),
1076
+ "HM-LC-Dim1PWM-CV-2": CustomConfig(make_ce_func=make_rf_dimmer_with_virt_channel),
1077
+ "HM-LC-Dim1T-CV": CustomConfig(make_ce_func=make_rf_dimmer_with_virt_channel),
1078
+ "HM-LC-Dim1T-CV-2": CustomConfig(make_ce_func=make_rf_dimmer_with_virt_channel),
1079
+ "HM-LC-Dim1T-DR": CustomConfig(make_ce_func=make_rf_dimmer, channels=(1, 2, 3)),
1080
+ "HM-LC-Dim1T-FM": CustomConfig(make_ce_func=make_rf_dimmer_with_virt_channel),
1081
+ "HM-LC-Dim1T-FM-2": CustomConfig(make_ce_func=make_rf_dimmer_with_virt_channel),
1082
+ "HM-LC-Dim1T-FM-LF": CustomConfig(make_ce_func=make_rf_dimmer),
1083
+ "HM-LC-Dim1T-Pl": CustomConfig(make_ce_func=make_rf_dimmer_with_virt_channel),
1084
+ "HM-LC-Dim1T-Pl-2": CustomConfig(make_ce_func=make_rf_dimmer),
1085
+ "HM-LC-Dim1T-Pl-3": CustomConfig(make_ce_func=make_rf_dimmer_with_virt_channel),
1086
+ "HM-LC-Dim1TPBU-FM": CustomConfig(make_ce_func=make_rf_dimmer_with_virt_channel),
1087
+ "HM-LC-Dim1TPBU-FM-2": CustomConfig(make_ce_func=make_rf_dimmer_with_virt_channel),
1088
+ "HM-LC-Dim2L-CV": CustomConfig(make_ce_func=make_rf_dimmer, channels=(1, 2)),
1089
+ "HM-LC-Dim2L-SM": CustomConfig(make_ce_func=make_rf_dimmer, channels=(1, 2)),
1090
+ "HM-LC-Dim2L-SM-2": CustomConfig(make_ce_func=make_rf_dimmer, channels=(1, 2, 3, 4, 5, 6)),
1091
+ "HM-LC-Dim2T-SM": CustomConfig(make_ce_func=make_rf_dimmer, channels=(1, 2)),
1092
+ "HM-LC-Dim2T-SM-2": CustomConfig(make_ce_func=make_rf_dimmer, channels=(1, 2, 3, 4, 5, 6)),
1093
+ "HM-LC-RGBW-WM": CustomConfig(make_ce_func=make_rf_dimmer_color_effect),
1094
+ "HMW-LC-Dim1L-DR": CustomConfig(make_ce_func=make_rf_dimmer, channels=(3,)),
1095
+ "HSS-DX": CustomConfig(make_ce_func=make_rf_dimmer),
1096
+ "HmIP-DRG-DALI": CustomConfig(make_ce_func=make_ip_drg_dali_light, channels=tuple(range(1, 49))),
1097
+ "HmIP-BDT": CustomConfig(make_ce_func=make_ip_dimmer, channels=(4,)),
1098
+ "HmIP-BSL": CustomConfig(make_ce_func=make_ip_fixed_color_light, channels=(8, 12)),
1099
+ "HmIP-DRDI3": CustomConfig(
1100
+ make_ce_func=make_ip_dimmer,
1101
+ channels=(5, 9, 13),
1102
+ ),
1103
+ "HmIP-FDT": CustomConfig(make_ce_func=make_ip_dimmer, channels=(2,)),
1104
+ "HmIP-PDT": CustomConfig(make_ce_func=make_ip_dimmer, channels=(3,)),
1105
+ "HmIP-RGBW": CustomConfig(make_ce_func=make_ip_rgbw_light),
1106
+ "HmIP-LSC": CustomConfig(make_ce_func=make_ip_rgbw_light),
1107
+ "HmIP-SCTH230": CustomConfig(
1108
+ make_ce_func=make_ip_dimmer,
1109
+ channels=(12,),
1110
+ extended=ExtendedConfig(
1111
+ additional_data_points={
1112
+ 1: (Parameter.CONCENTRATION,),
1113
+ 4: (
1114
+ Parameter.HUMIDITY,
1115
+ Parameter.ACTUAL_TEMPERATURE,
1116
+ ),
1117
+ }
1118
+ ),
1119
+ ),
1120
+ "HmIP-WGT": CustomConfig(make_ce_func=make_ip_dimmer, channels=(2,)),
1121
+ "HmIPW-DRD3": CustomConfig(
1122
+ make_ce_func=make_ip_dimmer,
1123
+ channels=(2, 6, 10),
1124
+ ),
1125
+ "HmIPW-WRC6": CustomConfig(make_ce_func=make_ip_simple_fixed_color_light_wired, channels=(7, 8, 9, 10, 11, 12, 13)),
1126
+ "OLIGO.smart.iq.HM": CustomConfig(make_ce_func=make_rf_dimmer, channels=(1, 2, 3, 4, 5, 6)),
1127
+ }
1128
+ hmed.ALL_DEVICES[DataPointCategory.LIGHT] = DEVICES