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