aiohomematic 2026.1.29__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.
- aiohomematic/__init__.py +110 -0
- aiohomematic/_log_context_protocol.py +29 -0
- aiohomematic/api.py +410 -0
- aiohomematic/async_support.py +250 -0
- aiohomematic/backend_detection.py +462 -0
- aiohomematic/central/__init__.py +103 -0
- aiohomematic/central/async_rpc_server.py +760 -0
- aiohomematic/central/central_unit.py +1152 -0
- aiohomematic/central/config.py +463 -0
- aiohomematic/central/config_builder.py +772 -0
- aiohomematic/central/connection_state.py +160 -0
- aiohomematic/central/coordinators/__init__.py +38 -0
- aiohomematic/central/coordinators/cache.py +414 -0
- aiohomematic/central/coordinators/client.py +480 -0
- aiohomematic/central/coordinators/connection_recovery.py +1141 -0
- aiohomematic/central/coordinators/device.py +1166 -0
- aiohomematic/central/coordinators/event.py +514 -0
- aiohomematic/central/coordinators/hub.py +532 -0
- aiohomematic/central/decorators.py +184 -0
- aiohomematic/central/device_registry.py +229 -0
- aiohomematic/central/events/__init__.py +104 -0
- aiohomematic/central/events/bus.py +1392 -0
- aiohomematic/central/events/integration.py +424 -0
- aiohomematic/central/events/types.py +194 -0
- aiohomematic/central/health.py +762 -0
- aiohomematic/central/rpc_server.py +353 -0
- aiohomematic/central/scheduler.py +794 -0
- aiohomematic/central/state_machine.py +391 -0
- aiohomematic/client/__init__.py +203 -0
- aiohomematic/client/_rpc_errors.py +187 -0
- aiohomematic/client/backends/__init__.py +48 -0
- aiohomematic/client/backends/base.py +335 -0
- aiohomematic/client/backends/capabilities.py +138 -0
- aiohomematic/client/backends/ccu.py +487 -0
- aiohomematic/client/backends/factory.py +116 -0
- aiohomematic/client/backends/homegear.py +294 -0
- aiohomematic/client/backends/json_ccu.py +252 -0
- aiohomematic/client/backends/protocol.py +316 -0
- aiohomematic/client/ccu.py +1857 -0
- aiohomematic/client/circuit_breaker.py +459 -0
- aiohomematic/client/config.py +64 -0
- aiohomematic/client/handlers/__init__.py +40 -0
- aiohomematic/client/handlers/backup.py +157 -0
- aiohomematic/client/handlers/base.py +79 -0
- aiohomematic/client/handlers/device_ops.py +1085 -0
- aiohomematic/client/handlers/firmware.py +144 -0
- aiohomematic/client/handlers/link_mgmt.py +199 -0
- aiohomematic/client/handlers/metadata.py +436 -0
- aiohomematic/client/handlers/programs.py +144 -0
- aiohomematic/client/handlers/sysvars.py +100 -0
- aiohomematic/client/interface_client.py +1304 -0
- aiohomematic/client/json_rpc.py +2068 -0
- aiohomematic/client/request_coalescer.py +282 -0
- aiohomematic/client/rpc_proxy.py +629 -0
- aiohomematic/client/state_machine.py +324 -0
- aiohomematic/const.py +2207 -0
- aiohomematic/context.py +275 -0
- aiohomematic/converter.py +270 -0
- aiohomematic/decorators.py +390 -0
- aiohomematic/exceptions.py +185 -0
- aiohomematic/hmcli.py +997 -0
- aiohomematic/i18n.py +193 -0
- aiohomematic/interfaces/__init__.py +407 -0
- aiohomematic/interfaces/central.py +1067 -0
- aiohomematic/interfaces/client.py +1096 -0
- aiohomematic/interfaces/coordinators.py +63 -0
- aiohomematic/interfaces/model.py +1921 -0
- aiohomematic/interfaces/operations.py +217 -0
- aiohomematic/logging_context.py +134 -0
- aiohomematic/metrics/__init__.py +125 -0
- aiohomematic/metrics/_protocols.py +140 -0
- aiohomematic/metrics/aggregator.py +534 -0
- aiohomematic/metrics/dataclasses.py +489 -0
- aiohomematic/metrics/emitter.py +292 -0
- aiohomematic/metrics/events.py +183 -0
- aiohomematic/metrics/keys.py +300 -0
- aiohomematic/metrics/observer.py +563 -0
- aiohomematic/metrics/stats.py +172 -0
- aiohomematic/model/__init__.py +189 -0
- aiohomematic/model/availability.py +65 -0
- aiohomematic/model/calculated/__init__.py +89 -0
- aiohomematic/model/calculated/climate.py +276 -0
- aiohomematic/model/calculated/data_point.py +315 -0
- aiohomematic/model/calculated/field.py +147 -0
- aiohomematic/model/calculated/operating_voltage_level.py +286 -0
- aiohomematic/model/calculated/support.py +232 -0
- aiohomematic/model/custom/__init__.py +214 -0
- aiohomematic/model/custom/capabilities/__init__.py +67 -0
- aiohomematic/model/custom/capabilities/climate.py +41 -0
- aiohomematic/model/custom/capabilities/light.py +87 -0
- aiohomematic/model/custom/capabilities/lock.py +44 -0
- aiohomematic/model/custom/capabilities/siren.py +63 -0
- aiohomematic/model/custom/climate.py +1130 -0
- aiohomematic/model/custom/cover.py +722 -0
- aiohomematic/model/custom/data_point.py +360 -0
- aiohomematic/model/custom/definition.py +300 -0
- aiohomematic/model/custom/field.py +89 -0
- aiohomematic/model/custom/light.py +1174 -0
- aiohomematic/model/custom/lock.py +322 -0
- aiohomematic/model/custom/mixins.py +445 -0
- aiohomematic/model/custom/profile.py +945 -0
- aiohomematic/model/custom/registry.py +251 -0
- aiohomematic/model/custom/siren.py +462 -0
- aiohomematic/model/custom/switch.py +195 -0
- aiohomematic/model/custom/text_display.py +289 -0
- aiohomematic/model/custom/valve.py +78 -0
- aiohomematic/model/data_point.py +1416 -0
- aiohomematic/model/device.py +1840 -0
- aiohomematic/model/event.py +216 -0
- aiohomematic/model/generic/__init__.py +327 -0
- aiohomematic/model/generic/action.py +40 -0
- aiohomematic/model/generic/action_select.py +62 -0
- aiohomematic/model/generic/binary_sensor.py +30 -0
- aiohomematic/model/generic/button.py +31 -0
- aiohomematic/model/generic/data_point.py +177 -0
- aiohomematic/model/generic/dummy.py +150 -0
- aiohomematic/model/generic/number.py +76 -0
- aiohomematic/model/generic/select.py +56 -0
- aiohomematic/model/generic/sensor.py +76 -0
- aiohomematic/model/generic/switch.py +54 -0
- aiohomematic/model/generic/text.py +33 -0
- aiohomematic/model/hub/__init__.py +100 -0
- aiohomematic/model/hub/binary_sensor.py +24 -0
- aiohomematic/model/hub/button.py +28 -0
- aiohomematic/model/hub/connectivity.py +190 -0
- aiohomematic/model/hub/data_point.py +342 -0
- aiohomematic/model/hub/hub.py +864 -0
- aiohomematic/model/hub/inbox.py +135 -0
- aiohomematic/model/hub/install_mode.py +393 -0
- aiohomematic/model/hub/metrics.py +208 -0
- aiohomematic/model/hub/number.py +42 -0
- aiohomematic/model/hub/select.py +52 -0
- aiohomematic/model/hub/sensor.py +37 -0
- aiohomematic/model/hub/switch.py +43 -0
- aiohomematic/model/hub/text.py +30 -0
- aiohomematic/model/hub/update.py +221 -0
- aiohomematic/model/support.py +592 -0
- aiohomematic/model/update.py +140 -0
- aiohomematic/model/week_profile.py +1827 -0
- aiohomematic/property_decorators.py +719 -0
- aiohomematic/py.typed +0 -0
- aiohomematic/rega_scripts/accept_device_in_inbox.fn +51 -0
- aiohomematic/rega_scripts/create_backup_start.fn +28 -0
- aiohomematic/rega_scripts/create_backup_status.fn +89 -0
- aiohomematic/rega_scripts/fetch_all_device_data.fn +97 -0
- aiohomematic/rega_scripts/get_backend_info.fn +25 -0
- aiohomematic/rega_scripts/get_inbox_devices.fn +61 -0
- aiohomematic/rega_scripts/get_program_descriptions.fn +31 -0
- aiohomematic/rega_scripts/get_serial.fn +44 -0
- aiohomematic/rega_scripts/get_service_messages.fn +83 -0
- aiohomematic/rega_scripts/get_system_update_info.fn +39 -0
- aiohomematic/rega_scripts/get_system_variable_descriptions.fn +31 -0
- aiohomematic/rega_scripts/set_program_state.fn +17 -0
- aiohomematic/rega_scripts/set_system_variable.fn +19 -0
- aiohomematic/rega_scripts/trigger_firmware_update.fn +67 -0
- aiohomematic/schemas.py +256 -0
- aiohomematic/store/__init__.py +55 -0
- aiohomematic/store/dynamic/__init__.py +43 -0
- aiohomematic/store/dynamic/command.py +250 -0
- aiohomematic/store/dynamic/data.py +175 -0
- aiohomematic/store/dynamic/details.py +187 -0
- aiohomematic/store/dynamic/ping_pong.py +416 -0
- aiohomematic/store/persistent/__init__.py +71 -0
- aiohomematic/store/persistent/base.py +285 -0
- aiohomematic/store/persistent/device.py +233 -0
- aiohomematic/store/persistent/incident.py +380 -0
- aiohomematic/store/persistent/paramset.py +241 -0
- aiohomematic/store/persistent/session.py +556 -0
- aiohomematic/store/serialization.py +150 -0
- aiohomematic/store/storage.py +689 -0
- aiohomematic/store/types.py +526 -0
- aiohomematic/store/visibility/__init__.py +40 -0
- aiohomematic/store/visibility/parser.py +141 -0
- aiohomematic/store/visibility/registry.py +722 -0
- aiohomematic/store/visibility/rules.py +307 -0
- aiohomematic/strings.json +237 -0
- aiohomematic/support.py +706 -0
- aiohomematic/tracing.py +236 -0
- aiohomematic/translations/de.json +237 -0
- aiohomematic/translations/en.json +237 -0
- aiohomematic/type_aliases.py +51 -0
- aiohomematic/validator.py +128 -0
- aiohomematic-2026.1.29.dist-info/METADATA +296 -0
- aiohomematic-2026.1.29.dist-info/RECORD +188 -0
- aiohomematic-2026.1.29.dist-info/WHEEL +5 -0
- aiohomematic-2026.1.29.dist-info/entry_points.txt +2 -0
- aiohomematic-2026.1.29.dist-info/licenses/LICENSE +21 -0
- aiohomematic-2026.1.29.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,1174 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
# Copyright (c) 2021-2026
|
|
3
|
+
"""
|
|
4
|
+
Custom light data points for dimmers and colored lighting.
|
|
5
|
+
|
|
6
|
+
Public API of this module is defined by __all__.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from collections.abc import Mapping
|
|
12
|
+
from enum import StrEnum
|
|
13
|
+
import math
|
|
14
|
+
from typing import Final, TypedDict, Unpack
|
|
15
|
+
|
|
16
|
+
from aiohomematic.const import DataPointCategory, DataPointUsage, DeviceProfile, Field, Parameter
|
|
17
|
+
from aiohomematic.model.custom.capabilities.light import LightCapabilities
|
|
18
|
+
from aiohomematic.model.custom.data_point import CustomDataPoint
|
|
19
|
+
from aiohomematic.model.custom.field import DataPointField
|
|
20
|
+
from aiohomematic.model.custom.mixins import BrightnessMixin, StateChangeArgs, StateChangeTimerMixin, TimerUnitMixin
|
|
21
|
+
from aiohomematic.model.custom.registry import DeviceConfig, DeviceProfileRegistry, ExtendedDeviceConfig
|
|
22
|
+
from aiohomematic.model.data_point import CallParameterCollector, bind_collector
|
|
23
|
+
from aiohomematic.model.generic import (
|
|
24
|
+
DpAction,
|
|
25
|
+
DpActionSelect,
|
|
26
|
+
DpFloat,
|
|
27
|
+
DpInteger,
|
|
28
|
+
DpSelect,
|
|
29
|
+
DpSensor,
|
|
30
|
+
GenericDataPointAny,
|
|
31
|
+
)
|
|
32
|
+
from aiohomematic.property_decorators import DelegatedProperty, Kind, state_property
|
|
33
|
+
|
|
34
|
+
# Activity states indicating LED is active
|
|
35
|
+
_ACTIVITY_STATES_ACTIVE: Final[frozenset[str]] = frozenset({"UP", "DOWN"})
|
|
36
|
+
_DIMMER_OFF: Final = 0.0
|
|
37
|
+
_EFFECT_OFF: Final = "Off"
|
|
38
|
+
_LEVEL_TO_BRIGHTNESS_MULTIPLIER: Final = 100
|
|
39
|
+
_MAX_BRIGHTNESS: Final = 255.0
|
|
40
|
+
_MAX_KELVIN: Final = 1000000
|
|
41
|
+
_MAX_MIREDS: Final = 500
|
|
42
|
+
_MAX_SATURATION: Final = 100.0
|
|
43
|
+
_MIN_BRIGHTNESS: Final = 0.0
|
|
44
|
+
_MIN_HUE: Final = 0.0
|
|
45
|
+
_MIN_MIREDS: Final = 153
|
|
46
|
+
_MIN_SATURATION: Final = 0.0
|
|
47
|
+
_NOT_USED: Final = 111600
|
|
48
|
+
_SATURATION_MULTIPLIER: Final = 100
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class _DeviceOperationMode(StrEnum):
|
|
52
|
+
"""Enum with device operation modes."""
|
|
53
|
+
|
|
54
|
+
PWM = "4_PWM"
|
|
55
|
+
RGB = "RGB"
|
|
56
|
+
RGBW = "RGBW"
|
|
57
|
+
TUNABLE_WHITE = "2_TUNABLE_WHITE"
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class _ColorBehaviour(StrEnum):
|
|
61
|
+
"""Enum with color behaviours."""
|
|
62
|
+
|
|
63
|
+
DO_NOT_CARE = "DO_NOT_CARE"
|
|
64
|
+
OFF = "OFF"
|
|
65
|
+
OLD_VALUE = "OLD_VALUE"
|
|
66
|
+
ON = "ON"
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class FixedColor(StrEnum):
|
|
70
|
+
"""Enum with colors."""
|
|
71
|
+
|
|
72
|
+
BLACK = "BLACK"
|
|
73
|
+
BLUE = "BLUE"
|
|
74
|
+
DO_NOT_CARE = "DO_NOT_CARE"
|
|
75
|
+
GREEN = "GREEN"
|
|
76
|
+
OLD_VALUE = "OLD_VALUE"
|
|
77
|
+
PURPLE = "PURPLE"
|
|
78
|
+
RED = "RED"
|
|
79
|
+
TURQUOISE = "TURQUOISE"
|
|
80
|
+
WHITE = "WHITE"
|
|
81
|
+
YELLOW = "YELLOW"
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class _StateChangeArg(StrEnum):
|
|
85
|
+
"""Enum with light state change arguments."""
|
|
86
|
+
|
|
87
|
+
BRIGHTNESS = "brightness"
|
|
88
|
+
COLOR_TEMP_KELVIN = "color_temp_kelvin"
|
|
89
|
+
EFFECT = "effect"
|
|
90
|
+
HS_COLOR = "hs_color"
|
|
91
|
+
OFF = "off"
|
|
92
|
+
ON = "on"
|
|
93
|
+
ON_TIME = "on_time"
|
|
94
|
+
RAMP_TIME = "ramp_time"
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
_NO_COLOR: Final = (
|
|
98
|
+
FixedColor.BLACK,
|
|
99
|
+
FixedColor.DO_NOT_CARE,
|
|
100
|
+
FixedColor.OLD_VALUE,
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
_EXCLUDE_FROM_COLOR_BEHAVIOUR: Final = (
|
|
104
|
+
_ColorBehaviour.DO_NOT_CARE,
|
|
105
|
+
_ColorBehaviour.OFF,
|
|
106
|
+
_ColorBehaviour.OLD_VALUE,
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
_OFF_COLOR_BEHAVIOUR: Final = (
|
|
110
|
+
_ColorBehaviour.DO_NOT_CARE,
|
|
111
|
+
_ColorBehaviour.OFF,
|
|
112
|
+
_ColorBehaviour.OLD_VALUE,
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
FIXED_COLOR_TO_HS_CONVERTER: Mapping[str, tuple[float, float]] = {
|
|
116
|
+
FixedColor.WHITE: (_MIN_HUE, _MIN_SATURATION),
|
|
117
|
+
FixedColor.RED: (_MIN_HUE, _MAX_SATURATION),
|
|
118
|
+
FixedColor.YELLOW: (60.0, _MAX_SATURATION),
|
|
119
|
+
FixedColor.GREEN: (120.0, _MAX_SATURATION),
|
|
120
|
+
FixedColor.TURQUOISE: (180.0, _MAX_SATURATION),
|
|
121
|
+
FixedColor.BLUE: (240.0, _MAX_SATURATION),
|
|
122
|
+
FixedColor.PURPLE: (300.0, _MAX_SATURATION),
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
# ON_TIME_LIST values mapping: (ms_value, enum_string)
|
|
126
|
+
# Used for flash timing in LED sequences
|
|
127
|
+
_ON_TIME_LIST_VALUES: Final[tuple[tuple[int, str], ...]] = (
|
|
128
|
+
(100, "100MS"),
|
|
129
|
+
(200, "200MS"),
|
|
130
|
+
(300, "300MS"),
|
|
131
|
+
(400, "400MS"),
|
|
132
|
+
(500, "500MS"),
|
|
133
|
+
(600, "600MS"),
|
|
134
|
+
(700, "700MS"),
|
|
135
|
+
(800, "800MS"),
|
|
136
|
+
(900, "900MS"),
|
|
137
|
+
(1000, "1S"),
|
|
138
|
+
(2000, "2S"),
|
|
139
|
+
(3000, "3S"),
|
|
140
|
+
(4000, "4S"),
|
|
141
|
+
(5000, "5S"),
|
|
142
|
+
)
|
|
143
|
+
_PERMANENTLY_ON: Final = "PERMANENTLY_ON"
|
|
144
|
+
|
|
145
|
+
# Repetitions constants
|
|
146
|
+
_NO_REPETITION: Final = "NO_REPETITION"
|
|
147
|
+
_INFINITE_REPETITIONS: Final = "INFINITE_REPETITIONS"
|
|
148
|
+
_MAX_REPETITIONS: Final = 18
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def _convert_repetitions(*, repetitions: int | None) -> str:
|
|
152
|
+
"""
|
|
153
|
+
Convert repetitions count to REPETITIONS VALUE_LIST value.
|
|
154
|
+
|
|
155
|
+
Args:
|
|
156
|
+
repetitions: Number of repetitions (0=none, 1-18=count, -1=infinite, None=none).
|
|
157
|
+
|
|
158
|
+
Returns:
|
|
159
|
+
VALUE_LIST string (NO_REPETITION, REPETITIONS_001-018, or INFINITE_REPETITIONS).
|
|
160
|
+
|
|
161
|
+
Raises:
|
|
162
|
+
ValueError: If repetitions is outside valid range (-1 to 18).
|
|
163
|
+
|
|
164
|
+
"""
|
|
165
|
+
if repetitions is None or repetitions == 0:
|
|
166
|
+
return _NO_REPETITION
|
|
167
|
+
|
|
168
|
+
if repetitions == -1:
|
|
169
|
+
return _INFINITE_REPETITIONS
|
|
170
|
+
|
|
171
|
+
if repetitions < -1 or repetitions > _MAX_REPETITIONS:
|
|
172
|
+
msg = f"Repetitions must be -1 (infinite), 0 (none), or 1-{_MAX_REPETITIONS}, got {repetitions}"
|
|
173
|
+
raise ValueError(msg)
|
|
174
|
+
|
|
175
|
+
return f"REPETITIONS_{repetitions:03d}"
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def _convert_flash_time_to_on_time_list(*, flash_time_ms: int | None) -> str:
|
|
179
|
+
"""Convert flash time in milliseconds to nearest ON_TIME_LIST value."""
|
|
180
|
+
if flash_time_ms is None or flash_time_ms <= 0:
|
|
181
|
+
return _PERMANENTLY_ON
|
|
182
|
+
|
|
183
|
+
# If flash_time is larger than 5000ms, use PERMANENTLY_ON
|
|
184
|
+
if flash_time_ms > 5000:
|
|
185
|
+
return _PERMANENTLY_ON
|
|
186
|
+
|
|
187
|
+
# Find the closest match
|
|
188
|
+
best_match = _PERMANENTLY_ON
|
|
189
|
+
best_diff = math.inf
|
|
190
|
+
|
|
191
|
+
for ms_value, enum_str in _ON_TIME_LIST_VALUES:
|
|
192
|
+
if (diff := abs(ms_value - flash_time_ms)) < best_diff:
|
|
193
|
+
best_diff = diff
|
|
194
|
+
best_match = enum_str
|
|
195
|
+
|
|
196
|
+
return best_match
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
class LightOnArgs(TypedDict, total=False):
|
|
200
|
+
"""Matcher for the light turn on arguments."""
|
|
201
|
+
|
|
202
|
+
brightness: int
|
|
203
|
+
color_temp_kelvin: int
|
|
204
|
+
effect: str
|
|
205
|
+
hs_color: tuple[float, float]
|
|
206
|
+
on_time: float
|
|
207
|
+
ramp_time: float
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
class LightOffArgs(TypedDict, total=False):
|
|
211
|
+
"""Matcher for the light turn off arguments."""
|
|
212
|
+
|
|
213
|
+
on_time: float
|
|
214
|
+
ramp_time: float
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
class SoundPlayerLedOnArgs(LightOnArgs, total=False):
|
|
218
|
+
"""Arguments for CustomDpSoundPlayerLed turn_on method (extends LightOnArgs)."""
|
|
219
|
+
|
|
220
|
+
repetitions: int # 0=none, 1-18=count, -1=infinite (converted to VALUE_LIST entry)
|
|
221
|
+
flash_time: int # Flash duration in milliseconds (converted to nearest VALUE_LIST entry)
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
class CustomDpDimmer(StateChangeTimerMixin, BrightnessMixin, CustomDataPoint):
|
|
225
|
+
"""Base class for Homematic light data point."""
|
|
226
|
+
|
|
227
|
+
__slots__ = ("_capabilities",)
|
|
228
|
+
|
|
229
|
+
_category = DataPointCategory.LIGHT
|
|
230
|
+
|
|
231
|
+
# Declarative data point field definitions
|
|
232
|
+
_dp_group_level: Final = DataPointField(field=Field.GROUP_LEVEL, dpt=DpSensor[float | None])
|
|
233
|
+
_dp_level: Final = DataPointField(field=Field.LEVEL, dpt=DpFloat)
|
|
234
|
+
_dp_on_time_value = DataPointField(field=Field.ON_TIME_VALUE, dpt=DpAction)
|
|
235
|
+
_dp_ramp_time_value = DataPointField(field=Field.RAMP_TIME_VALUE, dpt=DpAction)
|
|
236
|
+
|
|
237
|
+
@property
|
|
238
|
+
def brightness_pct(self) -> int | None:
|
|
239
|
+
"""Return the brightness in percent of this light."""
|
|
240
|
+
return self.level_to_brightness_pct(self._dp_level.value or _MIN_BRIGHTNESS)
|
|
241
|
+
|
|
242
|
+
@property
|
|
243
|
+
def capabilities(self) -> LightCapabilities:
|
|
244
|
+
"""Return the light capabilities."""
|
|
245
|
+
if (caps := getattr(self, "_capabilities", None)) is None:
|
|
246
|
+
caps = self._compute_capabilities()
|
|
247
|
+
object.__setattr__(self, "_capabilities", caps)
|
|
248
|
+
return caps
|
|
249
|
+
|
|
250
|
+
@property
|
|
251
|
+
def group_brightness(self) -> int | None:
|
|
252
|
+
"""Return the group brightness of this light between min/max brightness."""
|
|
253
|
+
if self._dp_group_level.value is not None:
|
|
254
|
+
return self.level_to_brightness(self._dp_group_level.value)
|
|
255
|
+
return None
|
|
256
|
+
|
|
257
|
+
@property
|
|
258
|
+
def group_brightness_pct(self) -> int | None:
|
|
259
|
+
"""Return the group brightness in percent of this light."""
|
|
260
|
+
if self._dp_group_level.value is not None:
|
|
261
|
+
return self.level_to_brightness_pct(self._dp_group_level.value)
|
|
262
|
+
return None
|
|
263
|
+
|
|
264
|
+
@property
|
|
265
|
+
def has_color_temperature(self) -> bool:
|
|
266
|
+
"""Return True if light currently has color temperature."""
|
|
267
|
+
return self.color_temp_kelvin is not None
|
|
268
|
+
|
|
269
|
+
@property
|
|
270
|
+
def has_effects(self) -> bool:
|
|
271
|
+
"""Return True if light currently has effects."""
|
|
272
|
+
return self.effects is not None and len(self.effects) > 0
|
|
273
|
+
|
|
274
|
+
@property
|
|
275
|
+
def has_hs_color(self) -> bool:
|
|
276
|
+
"""Return True if light currently has hs color."""
|
|
277
|
+
return self.hs_color is not None
|
|
278
|
+
|
|
279
|
+
@state_property
|
|
280
|
+
def brightness(self) -> int | None:
|
|
281
|
+
"""Return the brightness of this light between min/max brightness."""
|
|
282
|
+
return self.level_to_brightness(self._dp_level.value or _MIN_BRIGHTNESS)
|
|
283
|
+
|
|
284
|
+
@state_property
|
|
285
|
+
def color_temp_kelvin(self) -> int | None:
|
|
286
|
+
"""Return the color temperature in kelvin."""
|
|
287
|
+
return None
|
|
288
|
+
|
|
289
|
+
@state_property
|
|
290
|
+
def effect(self) -> str | None:
|
|
291
|
+
"""Return the current effect."""
|
|
292
|
+
return None
|
|
293
|
+
|
|
294
|
+
@state_property
|
|
295
|
+
def effects(self) -> tuple[str, ...] | None:
|
|
296
|
+
"""Return the supported effects."""
|
|
297
|
+
return None
|
|
298
|
+
|
|
299
|
+
@state_property
|
|
300
|
+
def hs_color(self) -> tuple[float, float] | None:
|
|
301
|
+
"""Return the hue and saturation color value [float, float]."""
|
|
302
|
+
return None
|
|
303
|
+
|
|
304
|
+
@state_property
|
|
305
|
+
def is_on(self) -> bool | None:
|
|
306
|
+
"""Return true if dimmer is on."""
|
|
307
|
+
return self._dp_level.value is not None and self._dp_level.value > _DIMMER_OFF
|
|
308
|
+
|
|
309
|
+
def is_state_change(self, **kwargs: Unpack[StateChangeArgs]) -> bool:
|
|
310
|
+
"""Check if the state changes due to kwargs."""
|
|
311
|
+
if self.is_timer_state_change():
|
|
312
|
+
return True
|
|
313
|
+
if kwargs.get(_StateChangeArg.ON_TIME) is not None:
|
|
314
|
+
return True
|
|
315
|
+
if kwargs.get(_StateChangeArg.RAMP_TIME) is not None:
|
|
316
|
+
return True
|
|
317
|
+
if kwargs.get(_StateChangeArg.ON) is not None and self.is_on is not True and len(kwargs) == 1:
|
|
318
|
+
return True
|
|
319
|
+
if kwargs.get(_StateChangeArg.OFF) is not None and self.is_on is not False and len(kwargs) == 1:
|
|
320
|
+
return True
|
|
321
|
+
if (brightness := kwargs.get(_StateChangeArg.BRIGHTNESS)) is not None and brightness != self.brightness:
|
|
322
|
+
return True
|
|
323
|
+
if (hs_color := kwargs.get(_StateChangeArg.HS_COLOR)) is not None and hs_color != self.hs_color:
|
|
324
|
+
return True
|
|
325
|
+
if (
|
|
326
|
+
color_temp_kelvin := kwargs.get(_StateChangeArg.COLOR_TEMP_KELVIN)
|
|
327
|
+
) is not None and color_temp_kelvin != self.color_temp_kelvin:
|
|
328
|
+
return True
|
|
329
|
+
if (effect := kwargs.get(_StateChangeArg.EFFECT)) is not None and effect != self.effect:
|
|
330
|
+
return True
|
|
331
|
+
return super().is_state_change(**kwargs)
|
|
332
|
+
|
|
333
|
+
@bind_collector
|
|
334
|
+
async def turn_off(
|
|
335
|
+
self, *, collector: CallParameterCollector | None = None, **kwargs: Unpack[LightOffArgs]
|
|
336
|
+
) -> None:
|
|
337
|
+
"""Turn the light off."""
|
|
338
|
+
self.reset_timer_on_time()
|
|
339
|
+
if not self.is_state_change(off=True, **kwargs):
|
|
340
|
+
return
|
|
341
|
+
if ramp_time := kwargs.get("ramp_time"):
|
|
342
|
+
await self._set_ramp_time_off_value(ramp_time=ramp_time, collector=collector)
|
|
343
|
+
await self._dp_level.send_value(value=_DIMMER_OFF, collector=collector)
|
|
344
|
+
|
|
345
|
+
@bind_collector
|
|
346
|
+
async def turn_on(self, *, collector: CallParameterCollector | None = None, **kwargs: Unpack[LightOnArgs]) -> None:
|
|
347
|
+
"""Turn the light on."""
|
|
348
|
+
if (on_time := kwargs.get("on_time")) is not None:
|
|
349
|
+
self.set_timer_on_time(on_time=on_time)
|
|
350
|
+
if not self.is_state_change(on=True, **kwargs):
|
|
351
|
+
return
|
|
352
|
+
|
|
353
|
+
if (timer := self.get_and_start_timer()) is not None:
|
|
354
|
+
await self._set_on_time_value(on_time=timer, collector=collector)
|
|
355
|
+
if ramp_time := kwargs.get("ramp_time"):
|
|
356
|
+
await self._set_ramp_time_on_value(ramp_time=ramp_time, collector=collector)
|
|
357
|
+
if not (brightness := kwargs.get("brightness", self.brightness)):
|
|
358
|
+
brightness = int(_MAX_BRIGHTNESS)
|
|
359
|
+
level = self.brightness_to_level(brightness)
|
|
360
|
+
await self._dp_level.send_value(value=level, collector=collector)
|
|
361
|
+
|
|
362
|
+
def _compute_capabilities(self) -> LightCapabilities:
|
|
363
|
+
"""Compute static capabilities based on DataPoint types."""
|
|
364
|
+
return LightCapabilities(
|
|
365
|
+
brightness=isinstance(self._dp_level, DpFloat),
|
|
366
|
+
transition=isinstance(getattr(self, "_dp_ramp_time_value", None), DpAction),
|
|
367
|
+
)
|
|
368
|
+
|
|
369
|
+
@bind_collector
|
|
370
|
+
async def _set_on_time_value(self, *, on_time: float, collector: CallParameterCollector | None = None) -> None:
|
|
371
|
+
"""Set the on time value in seconds."""
|
|
372
|
+
await self._dp_on_time_value.send_value(value=on_time, collector=collector, do_validate=False)
|
|
373
|
+
|
|
374
|
+
async def _set_ramp_time_off_value(
|
|
375
|
+
self, *, ramp_time: float, collector: CallParameterCollector | None = None
|
|
376
|
+
) -> None:
|
|
377
|
+
"""Set the ramp time value in seconds."""
|
|
378
|
+
await self._set_ramp_time_on_value(ramp_time=ramp_time, collector=collector)
|
|
379
|
+
|
|
380
|
+
async def _set_ramp_time_on_value(
|
|
381
|
+
self, *, ramp_time: float, collector: CallParameterCollector | None = None
|
|
382
|
+
) -> None:
|
|
383
|
+
"""Set the ramp time value in seconds."""
|
|
384
|
+
await self._dp_ramp_time_value.send_value(value=ramp_time, collector=collector)
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
class CustomDpColorDimmer(CustomDpDimmer):
|
|
388
|
+
"""Class for Homematic dimmer with color data point."""
|
|
389
|
+
|
|
390
|
+
__slots__ = () # Required to prevent __dict__ creation (descriptors are class-level)
|
|
391
|
+
|
|
392
|
+
# Declarative data point field definitions
|
|
393
|
+
_dp_color: Final = DataPointField(field=Field.COLOR, dpt=DpInteger)
|
|
394
|
+
|
|
395
|
+
@state_property
|
|
396
|
+
def hs_color(self) -> tuple[float, float] | None:
|
|
397
|
+
"""Return the hue and saturation color value [float, float]."""
|
|
398
|
+
if (color := self._dp_color.value) is not None:
|
|
399
|
+
if color >= 200:
|
|
400
|
+
# 200 is a special case (white), so we have a saturation of 0.
|
|
401
|
+
# Larger values are undefined.
|
|
402
|
+
# For the sake of robustness we return "white" anyway.
|
|
403
|
+
return _MIN_HUE, _MIN_SATURATION
|
|
404
|
+
|
|
405
|
+
# For all other colors we assume saturation of 1
|
|
406
|
+
return color / 200 * 360, _MAX_SATURATION
|
|
407
|
+
return _MIN_HUE, _MIN_SATURATION
|
|
408
|
+
|
|
409
|
+
@bind_collector
|
|
410
|
+
async def turn_on(self, *, collector: CallParameterCollector | None = None, **kwargs: Unpack[LightOnArgs]) -> None:
|
|
411
|
+
"""Turn the light on."""
|
|
412
|
+
if not self.is_state_change(on=True, **kwargs):
|
|
413
|
+
return
|
|
414
|
+
if (hs_color := kwargs.get("hs_color")) is not None:
|
|
415
|
+
khue, ksaturation = hs_color
|
|
416
|
+
hue = khue / 360
|
|
417
|
+
saturation = ksaturation / _SATURATION_MULTIPLIER
|
|
418
|
+
color = 200 if saturation < 0.1 else int(round(max(min(hue, 1), 0) * 199))
|
|
419
|
+
await self._dp_color.send_value(value=color, collector=collector)
|
|
420
|
+
await super().turn_on(collector=collector, **kwargs)
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
class CustomDpColorDimmerEffect(CustomDpColorDimmer):
|
|
424
|
+
"""Class for Homematic dimmer with color data point."""
|
|
425
|
+
|
|
426
|
+
__slots__ = () # Required to prevent __dict__ creation (descriptors are class-level)
|
|
427
|
+
|
|
428
|
+
_effects: tuple[str, ...] = (
|
|
429
|
+
_EFFECT_OFF,
|
|
430
|
+
"Slow color change",
|
|
431
|
+
"Medium color change",
|
|
432
|
+
"Fast color change",
|
|
433
|
+
"Campemit",
|
|
434
|
+
"Waterfall",
|
|
435
|
+
"TV simulation",
|
|
436
|
+
)
|
|
437
|
+
|
|
438
|
+
# Declarative data point field definitions
|
|
439
|
+
_dp_effect: Final = DataPointField(field=Field.PROGRAM, dpt=DpInteger)
|
|
440
|
+
|
|
441
|
+
effects: Final = DelegatedProperty[tuple[str, ...] | None](path="_effects", kind=Kind.STATE)
|
|
442
|
+
|
|
443
|
+
@state_property
|
|
444
|
+
def effect(self) -> str | None:
|
|
445
|
+
"""Return the current effect."""
|
|
446
|
+
if self._dp_effect.value is not None:
|
|
447
|
+
return self._effects[int(self._dp_effect.value)]
|
|
448
|
+
return None
|
|
449
|
+
|
|
450
|
+
@bind_collector
|
|
451
|
+
async def turn_on(self, *, collector: CallParameterCollector | None = None, **kwargs: Unpack[LightOnArgs]) -> None:
|
|
452
|
+
"""Turn the light on."""
|
|
453
|
+
if not self.is_state_change(on=True, **kwargs):
|
|
454
|
+
return
|
|
455
|
+
|
|
456
|
+
if "effect" not in kwargs and self.has_effects and self.effect != _EFFECT_OFF:
|
|
457
|
+
await self._dp_effect.send_value(value=0, collector=collector, collector_order=5)
|
|
458
|
+
|
|
459
|
+
if (
|
|
460
|
+
self.has_effects
|
|
461
|
+
and (effect := kwargs.get("effect")) is not None
|
|
462
|
+
and (effect_idx := self._effects.index(effect)) is not None
|
|
463
|
+
):
|
|
464
|
+
await self._dp_effect.send_value(value=effect_idx, collector=collector, collector_order=95)
|
|
465
|
+
|
|
466
|
+
await super().turn_on(collector=collector, **kwargs)
|
|
467
|
+
|
|
468
|
+
|
|
469
|
+
class CustomDpColorTempDimmer(CustomDpDimmer):
|
|
470
|
+
"""Class for Homematic dimmer with color temperature."""
|
|
471
|
+
|
|
472
|
+
__slots__ = () # Required to prevent __dict__ creation (descriptors are class-level)
|
|
473
|
+
|
|
474
|
+
# Declarative data point field definitions
|
|
475
|
+
_dp_color_level: Final = DataPointField(field=Field.COLOR_LEVEL, dpt=DpFloat)
|
|
476
|
+
|
|
477
|
+
@state_property
|
|
478
|
+
def color_temp_kelvin(self) -> int | None:
|
|
479
|
+
"""Return the color temperature in kelvin."""
|
|
480
|
+
return math.floor(
|
|
481
|
+
_MAX_KELVIN / int(_MAX_MIREDS - (_MAX_MIREDS - _MIN_MIREDS) * (self._dp_color_level.value or _DIMMER_OFF))
|
|
482
|
+
)
|
|
483
|
+
|
|
484
|
+
@bind_collector
|
|
485
|
+
async def turn_on(self, *, collector: CallParameterCollector | None = None, **kwargs: Unpack[LightOnArgs]) -> None:
|
|
486
|
+
"""Turn the light on."""
|
|
487
|
+
if not self.is_state_change(on=True, **kwargs):
|
|
488
|
+
return
|
|
489
|
+
if (color_temp_kelvin := kwargs.get("color_temp_kelvin")) is not None:
|
|
490
|
+
color_level = (_MAX_MIREDS - math.floor(_MAX_KELVIN / color_temp_kelvin)) / (_MAX_MIREDS - _MIN_MIREDS)
|
|
491
|
+
await self._dp_color_level.send_value(value=color_level, collector=collector)
|
|
492
|
+
|
|
493
|
+
await super().turn_on(collector=collector, **kwargs)
|
|
494
|
+
|
|
495
|
+
|
|
496
|
+
class CustomDpIpRGBWLight(TimerUnitMixin, CustomDpDimmer):
|
|
497
|
+
"""Class for HomematicIP HmIP-RGBW light data point."""
|
|
498
|
+
|
|
499
|
+
__slots__ = () # Required to prevent __dict__ creation (descriptors are class-level)
|
|
500
|
+
|
|
501
|
+
# Declarative data point field definitions
|
|
502
|
+
_dp_direction: Final = DataPointField(field=Field.DIRECTION, dpt=DpSensor[str | None])
|
|
503
|
+
_dp_color_temperature_kelvin: Final = DataPointField(field=Field.COLOR_TEMPERATURE, dpt=DpInteger)
|
|
504
|
+
_dp_device_operation_mode: Final = DataPointField(field=Field.DEVICE_OPERATION_MODE, dpt=DpSelect)
|
|
505
|
+
_dp_effect: Final = DataPointField(field=Field.EFFECT, dpt=DpActionSelect)
|
|
506
|
+
|
|
507
|
+
_dp_hue: Final = DataPointField(field=Field.HUE, dpt=DpInteger)
|
|
508
|
+
_dp_on_time_unit = DataPointField(field=Field.ON_TIME_UNIT, dpt=DpActionSelect)
|
|
509
|
+
_dp_ramp_time_to_off_unit: Final = DataPointField(field=Field.RAMP_TIME_TO_OFF_UNIT, dpt=DpActionSelect)
|
|
510
|
+
_dp_ramp_time_to_off_value: Final = DataPointField(field=Field.RAMP_TIME_TO_OFF_VALUE, dpt=DpAction)
|
|
511
|
+
_dp_ramp_time_unit = DataPointField(field=Field.RAMP_TIME_UNIT, dpt=DpActionSelect)
|
|
512
|
+
_dp_saturation: Final = DataPointField(field=Field.SATURATION, dpt=DpFloat)
|
|
513
|
+
|
|
514
|
+
@property
|
|
515
|
+
def _device_operation_mode(self) -> _DeviceOperationMode:
|
|
516
|
+
"""Return the device operation mode."""
|
|
517
|
+
try:
|
|
518
|
+
return _DeviceOperationMode(str(self._dp_device_operation_mode.value))
|
|
519
|
+
except Exception:
|
|
520
|
+
# Fallback to a sensible default if the value is not set or unexpected
|
|
521
|
+
return _DeviceOperationMode.RGBW
|
|
522
|
+
|
|
523
|
+
@property
|
|
524
|
+
def _relevant_data_points(self) -> tuple[GenericDataPointAny, ...]:
|
|
525
|
+
"""Returns the list of relevant data points. To be overridden by subclasses."""
|
|
526
|
+
if self._device_operation_mode == _DeviceOperationMode.RGBW:
|
|
527
|
+
return (
|
|
528
|
+
self._dp_hue,
|
|
529
|
+
self._dp_level,
|
|
530
|
+
self._dp_saturation,
|
|
531
|
+
self._dp_color_temperature_kelvin,
|
|
532
|
+
)
|
|
533
|
+
if self._device_operation_mode == _DeviceOperationMode.RGB:
|
|
534
|
+
return self._dp_hue, self._dp_level, self._dp_saturation
|
|
535
|
+
if self._device_operation_mode == _DeviceOperationMode.TUNABLE_WHITE:
|
|
536
|
+
return self._dp_level, self._dp_color_temperature_kelvin
|
|
537
|
+
return (self._dp_level,)
|
|
538
|
+
|
|
539
|
+
@property
|
|
540
|
+
def has_color_temperature(self) -> bool:
|
|
541
|
+
"""Return True if light currently has color temperature (mode-dependent)."""
|
|
542
|
+
return self._device_operation_mode == _DeviceOperationMode.TUNABLE_WHITE
|
|
543
|
+
|
|
544
|
+
@property
|
|
545
|
+
def has_effects(self) -> bool:
|
|
546
|
+
"""Return True if light currently has effects (mode-dependent)."""
|
|
547
|
+
return (
|
|
548
|
+
self._device_operation_mode != _DeviceOperationMode.PWM
|
|
549
|
+
and self.effects is not None
|
|
550
|
+
and len(self.effects) > 0
|
|
551
|
+
)
|
|
552
|
+
|
|
553
|
+
@property
|
|
554
|
+
def has_hs_color(self) -> bool:
|
|
555
|
+
"""Return True if light currently has hs color (mode-dependent)."""
|
|
556
|
+
return self._device_operation_mode in (
|
|
557
|
+
_DeviceOperationMode.RGBW,
|
|
558
|
+
_DeviceOperationMode.RGB,
|
|
559
|
+
)
|
|
560
|
+
|
|
561
|
+
@property
|
|
562
|
+
def usage(self) -> DataPointUsage:
|
|
563
|
+
"""
|
|
564
|
+
Return the data_point usage.
|
|
565
|
+
|
|
566
|
+
Avoid creating data points that are not usable in selected device operation mode.
|
|
567
|
+
"""
|
|
568
|
+
if (
|
|
569
|
+
self._device_operation_mode in (_DeviceOperationMode.RGB, _DeviceOperationMode.RGBW)
|
|
570
|
+
and self._channel.no in (2, 3, 4)
|
|
571
|
+
) or (self._device_operation_mode == _DeviceOperationMode.TUNABLE_WHITE and self._channel.no in (3, 4)):
|
|
572
|
+
return DataPointUsage.NO_CREATE
|
|
573
|
+
return self._get_data_point_usage()
|
|
574
|
+
|
|
575
|
+
@state_property
|
|
576
|
+
def color_temp_kelvin(self) -> int | None:
|
|
577
|
+
"""Return the color temperature in kelvin."""
|
|
578
|
+
if not self._dp_color_temperature_kelvin.value:
|
|
579
|
+
return None
|
|
580
|
+
return self._dp_color_temperature_kelvin.value
|
|
581
|
+
|
|
582
|
+
@state_property
|
|
583
|
+
def effects(self) -> tuple[str, ...] | None:
|
|
584
|
+
"""Return the supported effects."""
|
|
585
|
+
return self._dp_effect.values or ()
|
|
586
|
+
|
|
587
|
+
@state_property
|
|
588
|
+
def hs_color(self) -> tuple[float, float] | None:
|
|
589
|
+
"""Return the hue and saturation color value [float, float]."""
|
|
590
|
+
if self._dp_hue.value is not None and self._dp_saturation.value is not None:
|
|
591
|
+
return self._dp_hue.value, self._dp_saturation.value * _SATURATION_MULTIPLIER
|
|
592
|
+
return None
|
|
593
|
+
|
|
594
|
+
@bind_collector
|
|
595
|
+
async def turn_off(
|
|
596
|
+
self, *, collector: CallParameterCollector | None = None, **kwargs: Unpack[LightOffArgs]
|
|
597
|
+
) -> None:
|
|
598
|
+
"""Turn the light off."""
|
|
599
|
+
if kwargs.get("on_time") is None and kwargs.get("ramp_time"):
|
|
600
|
+
await self._set_on_time_value(on_time=_NOT_USED, collector=collector)
|
|
601
|
+
await super().turn_off(collector=collector, **kwargs)
|
|
602
|
+
|
|
603
|
+
@bind_collector
|
|
604
|
+
async def turn_on(self, *, collector: CallParameterCollector | None = None, **kwargs: Unpack[LightOnArgs]) -> None:
|
|
605
|
+
"""Turn the light on."""
|
|
606
|
+
if on_time := (kwargs.get("on_time") or self.get_and_start_timer()):
|
|
607
|
+
kwargs["on_time"] = on_time
|
|
608
|
+
if not self.is_state_change(on=True, **kwargs):
|
|
609
|
+
return
|
|
610
|
+
if (hs_color := kwargs.get("hs_color")) is not None:
|
|
611
|
+
hue, ksaturation = hs_color
|
|
612
|
+
saturation = ksaturation / _SATURATION_MULTIPLIER
|
|
613
|
+
await self._dp_hue.send_value(value=int(hue), collector=collector)
|
|
614
|
+
await self._dp_saturation.send_value(value=saturation, collector=collector)
|
|
615
|
+
if color_temp_kelvin := kwargs.get("color_temp_kelvin"):
|
|
616
|
+
await self._dp_color_temperature_kelvin.send_value(value=color_temp_kelvin, collector=collector)
|
|
617
|
+
if on_time is None and kwargs.get("ramp_time"):
|
|
618
|
+
await self._set_on_time_value(on_time=_NOT_USED, collector=collector)
|
|
619
|
+
if self.has_effects and (effect := kwargs.get("effect")) is not None:
|
|
620
|
+
await self._dp_effect.send_value(value=effect, collector=collector)
|
|
621
|
+
|
|
622
|
+
await super().turn_on(collector=collector, **kwargs)
|
|
623
|
+
|
|
624
|
+
@bind_collector
|
|
625
|
+
async def _set_on_time_value(self, *, on_time: float, collector: CallParameterCollector | None = None) -> None:
|
|
626
|
+
"""Set the on time value with automatic unit conversion."""
|
|
627
|
+
on_time, on_time_unit = self._recalc_unit_timer(time=on_time)
|
|
628
|
+
await self._dp_on_time_unit.send_value(value=on_time_unit, collector=collector)
|
|
629
|
+
await self._dp_on_time_value.send_value(value=float(on_time), collector=collector)
|
|
630
|
+
|
|
631
|
+
async def _set_ramp_time_off_value(
|
|
632
|
+
self, *, ramp_time: float, collector: CallParameterCollector | None = None
|
|
633
|
+
) -> None:
|
|
634
|
+
"""Set the ramp time off value with automatic unit conversion."""
|
|
635
|
+
ramp_time, ramp_time_unit = self._recalc_unit_timer(time=ramp_time)
|
|
636
|
+
await self._dp_ramp_time_unit.send_value(value=ramp_time_unit, collector=collector)
|
|
637
|
+
await self._dp_ramp_time_value.send_value(value=float(ramp_time), collector=collector)
|
|
638
|
+
|
|
639
|
+
async def _set_ramp_time_on_value(
|
|
640
|
+
self, *, ramp_time: float, collector: CallParameterCollector | None = None
|
|
641
|
+
) -> None:
|
|
642
|
+
"""Set the ramp time on value with automatic unit conversion."""
|
|
643
|
+
ramp_time, ramp_time_unit = self._recalc_unit_timer(time=ramp_time)
|
|
644
|
+
await self._dp_ramp_time_unit.send_value(value=ramp_time_unit, collector=collector)
|
|
645
|
+
await self._dp_ramp_time_value.send_value(value=float(ramp_time), collector=collector)
|
|
646
|
+
|
|
647
|
+
|
|
648
|
+
class CustomDpIpDrgDaliLight(TimerUnitMixin, CustomDpDimmer):
|
|
649
|
+
"""Class for HomematicIP HmIP-DRG-DALI light data point."""
|
|
650
|
+
|
|
651
|
+
__slots__ = () # Required to prevent __dict__ creation (descriptors are class-level)
|
|
652
|
+
|
|
653
|
+
# Declarative data point field definitions
|
|
654
|
+
_dp_color_temperature_kelvin: Final = DataPointField(field=Field.COLOR_TEMPERATURE, dpt=DpInteger)
|
|
655
|
+
_dp_effect: Final = DataPointField(field=Field.EFFECT, dpt=DpActionSelect)
|
|
656
|
+
_dp_hue: Final = DataPointField(field=Field.HUE, dpt=DpInteger)
|
|
657
|
+
_dp_on_time_unit = DataPointField(field=Field.ON_TIME_UNIT, dpt=DpActionSelect)
|
|
658
|
+
_dp_ramp_time_to_off_unit: Final = DataPointField(field=Field.RAMP_TIME_TO_OFF_UNIT, dpt=DpActionSelect)
|
|
659
|
+
_dp_ramp_time_to_off_value: Final = DataPointField(field=Field.RAMP_TIME_TO_OFF_VALUE, dpt=DpAction)
|
|
660
|
+
_dp_ramp_time_unit = DataPointField(field=Field.RAMP_TIME_UNIT, dpt=DpActionSelect)
|
|
661
|
+
_dp_saturation: Final = DataPointField(field=Field.SATURATION, dpt=DpFloat)
|
|
662
|
+
|
|
663
|
+
@property
|
|
664
|
+
def _relevant_data_points(self) -> tuple[GenericDataPointAny, ...]:
|
|
665
|
+
"""Returns the list of relevant data points. To be overridden by subclasses."""
|
|
666
|
+
return (self._dp_level,)
|
|
667
|
+
|
|
668
|
+
@state_property
|
|
669
|
+
def color_temp_kelvin(self) -> int | None:
|
|
670
|
+
"""Return the color temperature in kelvin."""
|
|
671
|
+
if not self._dp_color_temperature_kelvin.value:
|
|
672
|
+
return None
|
|
673
|
+
return self._dp_color_temperature_kelvin.value
|
|
674
|
+
|
|
675
|
+
@state_property
|
|
676
|
+
def effects(self) -> tuple[str, ...] | None:
|
|
677
|
+
"""Return the supported effects."""
|
|
678
|
+
return self._dp_effect.values or ()
|
|
679
|
+
|
|
680
|
+
@state_property
|
|
681
|
+
def hs_color(self) -> tuple[float, float] | None:
|
|
682
|
+
"""Return the hue and saturation color value [float, float]."""
|
|
683
|
+
if self._dp_hue.value is not None and self._dp_saturation.value is not None:
|
|
684
|
+
return self._dp_hue.value, self._dp_saturation.value * _SATURATION_MULTIPLIER
|
|
685
|
+
return None
|
|
686
|
+
|
|
687
|
+
@bind_collector
|
|
688
|
+
async def turn_on(self, *, collector: CallParameterCollector | None = None, **kwargs: Unpack[LightOnArgs]) -> None:
|
|
689
|
+
"""Turn the light on."""
|
|
690
|
+
if not self.is_state_change(on=True, **kwargs):
|
|
691
|
+
return
|
|
692
|
+
if (hs_color := kwargs.get("hs_color")) is not None:
|
|
693
|
+
hue, ksaturation = hs_color
|
|
694
|
+
saturation = ksaturation / _SATURATION_MULTIPLIER
|
|
695
|
+
await self._dp_hue.send_value(value=int(hue), collector=collector)
|
|
696
|
+
await self._dp_saturation.send_value(value=saturation, collector=collector)
|
|
697
|
+
if color_temp_kelvin := kwargs.get("color_temp_kelvin"):
|
|
698
|
+
await self._dp_color_temperature_kelvin.send_value(value=color_temp_kelvin, collector=collector)
|
|
699
|
+
if kwargs.get("on_time") is None and kwargs.get("ramp_time"):
|
|
700
|
+
await self._set_on_time_value(on_time=_NOT_USED, collector=collector)
|
|
701
|
+
if self.has_effects and (effect := kwargs.get("effect")) is not None:
|
|
702
|
+
await self._dp_effect.send_value(value=effect, collector=collector)
|
|
703
|
+
|
|
704
|
+
await super().turn_on(collector=collector, **kwargs)
|
|
705
|
+
|
|
706
|
+
|
|
707
|
+
class CustomDpIpFixedColorLight(TimerUnitMixin, CustomDpDimmer):
|
|
708
|
+
"""Class for HomematicIP HmIP-BSL light data point."""
|
|
709
|
+
|
|
710
|
+
__slots__ = ("_effect_list",) # Keep instance variable, descriptors are class-level
|
|
711
|
+
|
|
712
|
+
# Declarative data point field definitions
|
|
713
|
+
_dp_channel_color: Final = DataPointField(field=Field.CHANNEL_COLOR, dpt=DpSensor[str | None])
|
|
714
|
+
_dp_color: Final = DataPointField(field=Field.COLOR, dpt=DpSelect)
|
|
715
|
+
_dp_effect: Final = DataPointField(field=Field.COLOR_BEHAVIOUR, dpt=DpSelect)
|
|
716
|
+
_dp_on_time_unit = DataPointField(field=Field.ON_TIME_UNIT, dpt=DpActionSelect)
|
|
717
|
+
_dp_ramp_time_unit = DataPointField(field=Field.RAMP_TIME_UNIT, dpt=DpActionSelect)
|
|
718
|
+
|
|
719
|
+
_effect_list: tuple[str, ...]
|
|
720
|
+
|
|
721
|
+
channel_color_name: Final = DelegatedProperty[str | None](path="_dp_channel_color.value")
|
|
722
|
+
effects: Final = DelegatedProperty[tuple[str, ...] | None](path="_effect_list", kind=Kind.STATE)
|
|
723
|
+
|
|
724
|
+
@property
|
|
725
|
+
def channel_hs_color(self) -> tuple[float, float] | None:
|
|
726
|
+
"""Return the channel hue and saturation color value [float, float]."""
|
|
727
|
+
if self._dp_channel_color.value is not None:
|
|
728
|
+
return FIXED_COLOR_TO_HS_CONVERTER.get(self._dp_channel_color.value, (_MIN_HUE, _MIN_SATURATION))
|
|
729
|
+
return None
|
|
730
|
+
|
|
731
|
+
@state_property
|
|
732
|
+
def color_name(self) -> str | None:
|
|
733
|
+
"""Return the name of the color."""
|
|
734
|
+
val = self._dp_color.value
|
|
735
|
+
return val if isinstance(val, str) else None
|
|
736
|
+
|
|
737
|
+
@state_property
|
|
738
|
+
def effect(self) -> str | None:
|
|
739
|
+
"""Return the current effect."""
|
|
740
|
+
if (effect := self._dp_effect.value) is not None and effect in self._effect_list:
|
|
741
|
+
return effect if isinstance(effect, str) else None
|
|
742
|
+
return None
|
|
743
|
+
|
|
744
|
+
@state_property
|
|
745
|
+
def hs_color(self) -> tuple[float, float] | None:
|
|
746
|
+
"""Return the hue and saturation color value [float, float]."""
|
|
747
|
+
if (
|
|
748
|
+
self._dp_color.value is not None
|
|
749
|
+
and isinstance(self._dp_color.value, str)
|
|
750
|
+
and (hs_color := FIXED_COLOR_TO_HS_CONVERTER.get(self._dp_color.value)) is not None
|
|
751
|
+
):
|
|
752
|
+
return hs_color
|
|
753
|
+
return _MIN_HUE, _MIN_SATURATION
|
|
754
|
+
|
|
755
|
+
@bind_collector
|
|
756
|
+
async def turn_on(self, *, collector: CallParameterCollector | None = None, **kwargs: Unpack[LightOnArgs]) -> None:
|
|
757
|
+
"""Turn the light on."""
|
|
758
|
+
if not self.is_state_change(on=True, **kwargs):
|
|
759
|
+
return
|
|
760
|
+
if (hs_color := kwargs.get("hs_color")) is not None:
|
|
761
|
+
simple_rgb_color = hs_color_to_fixed_converter(color=hs_color)
|
|
762
|
+
await self._dp_color.send_value(value=simple_rgb_color, collector=collector)
|
|
763
|
+
elif self.color_name in _NO_COLOR:
|
|
764
|
+
await self._dp_color.send_value(value=FixedColor.WHITE, collector=collector)
|
|
765
|
+
if (effect := kwargs.get("effect")) is not None and effect in self._effect_list:
|
|
766
|
+
await self._dp_effect.send_value(value=effect, collector=collector)
|
|
767
|
+
elif self._dp_effect.value not in self._effect_list:
|
|
768
|
+
await self._dp_effect.send_value(value=_ColorBehaviour.ON, collector=collector)
|
|
769
|
+
elif (color_behaviour := self._dp_effect.value) is not None:
|
|
770
|
+
await self._dp_effect.send_value(value=color_behaviour, collector=collector)
|
|
771
|
+
|
|
772
|
+
await super().turn_on(collector=collector, **kwargs)
|
|
773
|
+
|
|
774
|
+
def _post_init(self) -> None:
|
|
775
|
+
"""Post action after initialisation of the data point fields."""
|
|
776
|
+
super()._post_init()
|
|
777
|
+
|
|
778
|
+
self._effect_list = (
|
|
779
|
+
tuple(str(item) for item in self._dp_effect.values if item not in _EXCLUDE_FROM_COLOR_BEHAVIOUR)
|
|
780
|
+
if (self._dp_effect and self._dp_effect.values)
|
|
781
|
+
else ()
|
|
782
|
+
)
|
|
783
|
+
|
|
784
|
+
|
|
785
|
+
def hs_color_to_fixed_converter(*, color: tuple[float, float]) -> str:
|
|
786
|
+
"""
|
|
787
|
+
Convert the given color to the reduced color of the device.
|
|
788
|
+
|
|
789
|
+
Device contains only 8 colors including white and black,
|
|
790
|
+
so a conversion is required.
|
|
791
|
+
"""
|
|
792
|
+
hue: int = int(color[0])
|
|
793
|
+
if int(color[1]) < 5:
|
|
794
|
+
return FixedColor.WHITE
|
|
795
|
+
if 30 < hue <= 90:
|
|
796
|
+
return FixedColor.YELLOW
|
|
797
|
+
if 90 < hue <= 150:
|
|
798
|
+
return FixedColor.GREEN
|
|
799
|
+
if 150 < hue <= 210:
|
|
800
|
+
return FixedColor.TURQUOISE
|
|
801
|
+
if 210 < hue <= 270:
|
|
802
|
+
return FixedColor.BLUE
|
|
803
|
+
if 270 < hue <= 330:
|
|
804
|
+
return FixedColor.PURPLE
|
|
805
|
+
return FixedColor.RED
|
|
806
|
+
|
|
807
|
+
|
|
808
|
+
class CustomDpSoundPlayerLed(TimerUnitMixin, CustomDpDimmer):
|
|
809
|
+
"""Class for HomematicIP sound player LED data point (HmIP-MP3P channel 6)."""
|
|
810
|
+
|
|
811
|
+
__slots__ = () # Required to prevent __dict__ creation (descriptors are class-level)
|
|
812
|
+
|
|
813
|
+
# Additional declarative data point field definitions for LED channel
|
|
814
|
+
# Note: _dp_level and _dp_ramp_time_value are inherited from CustomDpDimmer
|
|
815
|
+
# Map on_time to DURATION_VALUE/UNIT for TimerUnitMixin compatibility (override parent)
|
|
816
|
+
_dp_on_time_value = DataPointField(field=Field.DURATION_VALUE, dpt=DpAction)
|
|
817
|
+
_dp_on_time_unit = DataPointField(field=Field.DURATION_UNIT, dpt=DpActionSelect)
|
|
818
|
+
_dp_ramp_time_unit = DataPointField(field=Field.RAMP_TIME_UNIT, dpt=DpActionSelect)
|
|
819
|
+
_dp_color: Final = DataPointField(field=Field.COLOR, dpt=DpSelect)
|
|
820
|
+
_dp_on_time_list: Final = DataPointField(field=Field.ON_TIME_LIST, dpt=DpActionSelect)
|
|
821
|
+
_dp_repetitions: Final = DataPointField(field=Field.REPETITIONS, dpt=DpActionSelect)
|
|
822
|
+
_dp_direction: Final = DataPointField(field=Field.DIRECTION, dpt=DpSensor[str | None])
|
|
823
|
+
|
|
824
|
+
# Expose available options via DelegatedProperty (from VALUE_LISTs)
|
|
825
|
+
available_colors: Final = DelegatedProperty[tuple[str, ...] | None](path="_dp_color.values", kind=Kind.STATE)
|
|
826
|
+
|
|
827
|
+
@state_property
|
|
828
|
+
def color_name(self) -> str | None:
|
|
829
|
+
"""Return the name of the color."""
|
|
830
|
+
val = self._dp_color.value
|
|
831
|
+
return val if isinstance(val, str) else None
|
|
832
|
+
|
|
833
|
+
@state_property
|
|
834
|
+
def hs_color(self) -> tuple[float, float] | None:
|
|
835
|
+
"""Return the hue and saturation color value [float, float]."""
|
|
836
|
+
if (
|
|
837
|
+
self._dp_color.value is not None
|
|
838
|
+
and isinstance(self._dp_color.value, str)
|
|
839
|
+
and (hs_color := FIXED_COLOR_TO_HS_CONVERTER.get(self._dp_color.value)) is not None
|
|
840
|
+
):
|
|
841
|
+
return hs_color
|
|
842
|
+
return _MIN_HUE, _MIN_SATURATION
|
|
843
|
+
|
|
844
|
+
@bind_collector
|
|
845
|
+
async def turn_off(
|
|
846
|
+
self,
|
|
847
|
+
*,
|
|
848
|
+
collector: CallParameterCollector | None = None,
|
|
849
|
+
**kwargs: Unpack[LightOffArgs],
|
|
850
|
+
) -> None:
|
|
851
|
+
"""Turn off the LED."""
|
|
852
|
+
self.reset_timer_on_time()
|
|
853
|
+
await self._dp_level.send_value(value=0.0, collector=collector)
|
|
854
|
+
await self._dp_color.send_value(value=FixedColor.BLACK, collector=collector)
|
|
855
|
+
await self._dp_on_time_value.send_value(value=0, collector=collector)
|
|
856
|
+
|
|
857
|
+
@bind_collector
|
|
858
|
+
async def turn_on(
|
|
859
|
+
self,
|
|
860
|
+
*,
|
|
861
|
+
collector: CallParameterCollector | None = None,
|
|
862
|
+
**kwargs: Unpack[SoundPlayerLedOnArgs],
|
|
863
|
+
) -> None:
|
|
864
|
+
"""
|
|
865
|
+
Turn on the LED with optional color and brightness settings.
|
|
866
|
+
|
|
867
|
+
API is comparable to CustomDpIpFixedColorLight.
|
|
868
|
+
|
|
869
|
+
Args:
|
|
870
|
+
collector: Optional call parameter collector.
|
|
871
|
+
**kwargs: LED parameters from SoundPlayerLedOnArgs (extends LightOnArgs):
|
|
872
|
+
brightness: Brightness 0-255 (converted to 0.0-1.0 for device).
|
|
873
|
+
hs_color: Hue/saturation tuple for color selection.
|
|
874
|
+
on_time: Duration in seconds (auto-converted to value+unit via TimerUnitMixin).
|
|
875
|
+
ramp_time: Ramp time in seconds (auto-converted to value+unit via TimerUnitMixin).
|
|
876
|
+
repetitions: 0=none, 1-18=count, -1=infinite (converted to VALUE_LIST).
|
|
877
|
+
flash_time: Flash duration in ms (converted to nearest ON_TIME_LIST value).
|
|
878
|
+
|
|
879
|
+
"""
|
|
880
|
+
# Handle timer like CustomDpDimmer: store if passed, then retrieve via get_and_start_timer
|
|
881
|
+
if (on_time_arg := kwargs.get("on_time")) is not None:
|
|
882
|
+
self.set_timer_on_time(on_time=on_time_arg)
|
|
883
|
+
|
|
884
|
+
# Convert brightness from 0-255 to 0.0-1.0
|
|
885
|
+
brightness_int = kwargs.get("brightness")
|
|
886
|
+
brightness = self.brightness_to_level(brightness_int) if brightness_int is not None else 1.0
|
|
887
|
+
|
|
888
|
+
# Use pre-set timer (from set_timer_on_time) or fall back to kwargs/default
|
|
889
|
+
on_time = self.get_and_start_timer() or kwargs.get("on_time", 0.0)
|
|
890
|
+
ramp_time = kwargs.get("ramp_time", 0.0)
|
|
891
|
+
repetitions_value = _convert_repetitions(repetitions=kwargs.get("repetitions"))
|
|
892
|
+
flash_time_value = _convert_flash_time_to_on_time_list(flash_time_ms=kwargs.get("flash_time"))
|
|
893
|
+
|
|
894
|
+
# Handle color: convert hs_color or default to WHITE (like CustomDpIpFixedColorLight)
|
|
895
|
+
if (hs_color := kwargs.get("hs_color")) is not None:
|
|
896
|
+
color = hs_color_to_fixed_converter(color=hs_color)
|
|
897
|
+
elif self.color_name in _NO_COLOR:
|
|
898
|
+
color = FixedColor.WHITE
|
|
899
|
+
else:
|
|
900
|
+
color = self.color_name or FixedColor.WHITE
|
|
901
|
+
|
|
902
|
+
# Send parameters - order matters for batching
|
|
903
|
+
await self._dp_level.send_value(value=brightness, collector=collector)
|
|
904
|
+
await self._dp_color.send_value(value=color, collector=collector)
|
|
905
|
+
await self._dp_on_time_list.send_value(value=flash_time_value, collector=collector)
|
|
906
|
+
await self._dp_repetitions.send_value(value=repetitions_value, collector=collector)
|
|
907
|
+
# Use mixin methods for automatic unit conversion
|
|
908
|
+
await self._set_ramp_time_on_value(ramp_time=ramp_time, collector=collector)
|
|
909
|
+
await self._set_on_time_value(on_time=on_time, collector=collector)
|
|
910
|
+
|
|
911
|
+
|
|
912
|
+
# =============================================================================
|
|
913
|
+
# DeviceProfileRegistry Registration
|
|
914
|
+
# =============================================================================
|
|
915
|
+
|
|
916
|
+
# RF Dimmer (simple)
|
|
917
|
+
DeviceProfileRegistry.register(
|
|
918
|
+
category=DataPointCategory.LIGHT,
|
|
919
|
+
models=("263 132", "263 134", "HSS-DX"),
|
|
920
|
+
data_point_class=CustomDpDimmer,
|
|
921
|
+
profile_type=DeviceProfile.RF_DIMMER,
|
|
922
|
+
)
|
|
923
|
+
|
|
924
|
+
# RF Dimmer with virtual channels
|
|
925
|
+
DeviceProfileRegistry.register(
|
|
926
|
+
category=DataPointCategory.LIGHT,
|
|
927
|
+
models=(
|
|
928
|
+
"263 133",
|
|
929
|
+
"HM-LC-AO-SM",
|
|
930
|
+
"HM-LC-Dim1L-CV",
|
|
931
|
+
"HM-LC-Dim1L-CV-2",
|
|
932
|
+
"HM-LC-Dim1L-Pl",
|
|
933
|
+
"HM-LC-Dim1L-Pl-3",
|
|
934
|
+
"HM-LC-Dim1PWM-CV",
|
|
935
|
+
"HM-LC-Dim1PWM-CV-2",
|
|
936
|
+
"HM-LC-Dim1T-CV",
|
|
937
|
+
"HM-LC-Dim1T-CV-2",
|
|
938
|
+
"HM-LC-Dim1T-FM",
|
|
939
|
+
"HM-LC-Dim1T-FM-2",
|
|
940
|
+
"HM-LC-Dim1T-Pl",
|
|
941
|
+
"HM-LC-Dim1T-Pl-3",
|
|
942
|
+
"HM-LC-Dim1TPBU-FM",
|
|
943
|
+
"HM-LC-Dim1TPBU-FM-2",
|
|
944
|
+
),
|
|
945
|
+
data_point_class=CustomDpDimmer,
|
|
946
|
+
profile_type=DeviceProfile.RF_DIMMER_WITH_VIRT_CHANNEL,
|
|
947
|
+
)
|
|
948
|
+
|
|
949
|
+
# RF Dimmer (specific channels)
|
|
950
|
+
DeviceProfileRegistry.register(
|
|
951
|
+
category=DataPointCategory.LIGHT,
|
|
952
|
+
models="HM-DW-WM",
|
|
953
|
+
data_point_class=CustomDpDimmer,
|
|
954
|
+
profile_type=DeviceProfile.RF_DIMMER,
|
|
955
|
+
channels=(1, 2, 3, 4),
|
|
956
|
+
)
|
|
957
|
+
DeviceProfileRegistry.register(
|
|
958
|
+
category=DataPointCategory.LIGHT,
|
|
959
|
+
models="HM-LC-Dim1T-DR",
|
|
960
|
+
data_point_class=CustomDpDimmer,
|
|
961
|
+
profile_type=DeviceProfile.RF_DIMMER,
|
|
962
|
+
channels=(1, 2, 3),
|
|
963
|
+
)
|
|
964
|
+
DeviceProfileRegistry.register(
|
|
965
|
+
category=DataPointCategory.LIGHT,
|
|
966
|
+
models="HM-LC-Dim1T-FM-LF",
|
|
967
|
+
data_point_class=CustomDpDimmer,
|
|
968
|
+
profile_type=DeviceProfile.RF_DIMMER,
|
|
969
|
+
)
|
|
970
|
+
DeviceProfileRegistry.register(
|
|
971
|
+
category=DataPointCategory.LIGHT,
|
|
972
|
+
models=("HM-LC-Dim1L-Pl-2", "HM-LC-Dim1T-Pl-2"),
|
|
973
|
+
data_point_class=CustomDpDimmer,
|
|
974
|
+
profile_type=DeviceProfile.RF_DIMMER,
|
|
975
|
+
)
|
|
976
|
+
DeviceProfileRegistry.register(
|
|
977
|
+
category=DataPointCategory.LIGHT,
|
|
978
|
+
models=("HM-LC-Dim2L-CV", "HM-LC-Dim2L-SM", "HM-LC-Dim2T-SM"),
|
|
979
|
+
data_point_class=CustomDpDimmer,
|
|
980
|
+
profile_type=DeviceProfile.RF_DIMMER,
|
|
981
|
+
channels=(1, 2),
|
|
982
|
+
)
|
|
983
|
+
DeviceProfileRegistry.register(
|
|
984
|
+
category=DataPointCategory.LIGHT,
|
|
985
|
+
models=("HM-LC-Dim2L-SM-2", "HM-LC-Dim2T-SM-2"),
|
|
986
|
+
data_point_class=CustomDpDimmer,
|
|
987
|
+
profile_type=DeviceProfile.RF_DIMMER,
|
|
988
|
+
channels=(1, 2, 3, 4, 5, 6),
|
|
989
|
+
)
|
|
990
|
+
DeviceProfileRegistry.register(
|
|
991
|
+
category=DataPointCategory.LIGHT,
|
|
992
|
+
models="HMW-LC-Dim1L-DR",
|
|
993
|
+
data_point_class=CustomDpDimmer,
|
|
994
|
+
profile_type=DeviceProfile.RF_DIMMER,
|
|
995
|
+
channels=(3,),
|
|
996
|
+
)
|
|
997
|
+
DeviceProfileRegistry.register(
|
|
998
|
+
category=DataPointCategory.LIGHT,
|
|
999
|
+
models="OLIGO.smart.iq.HM",
|
|
1000
|
+
data_point_class=CustomDpDimmer,
|
|
1001
|
+
profile_type=DeviceProfile.RF_DIMMER,
|
|
1002
|
+
channels=(1, 2, 3, 4, 5, 6),
|
|
1003
|
+
)
|
|
1004
|
+
|
|
1005
|
+
# RF Dimmer with color temperature
|
|
1006
|
+
DeviceProfileRegistry.register(
|
|
1007
|
+
category=DataPointCategory.LIGHT,
|
|
1008
|
+
models="HM-LC-DW-WM",
|
|
1009
|
+
data_point_class=CustomDpColorTempDimmer,
|
|
1010
|
+
profile_type=DeviceProfile.RF_DIMMER_COLOR_TEMP,
|
|
1011
|
+
channels=(1, 3, 5),
|
|
1012
|
+
)
|
|
1013
|
+
|
|
1014
|
+
# RF Dimmer with color and effect
|
|
1015
|
+
DeviceProfileRegistry.register(
|
|
1016
|
+
category=DataPointCategory.LIGHT,
|
|
1017
|
+
models="HM-LC-RGBW-WM",
|
|
1018
|
+
data_point_class=CustomDpColorDimmerEffect,
|
|
1019
|
+
profile_type=DeviceProfile.RF_DIMMER_COLOR,
|
|
1020
|
+
)
|
|
1021
|
+
|
|
1022
|
+
# IP Dimmer
|
|
1023
|
+
DeviceProfileRegistry.register(
|
|
1024
|
+
category=DataPointCategory.LIGHT,
|
|
1025
|
+
models="HmIP-BDT",
|
|
1026
|
+
data_point_class=CustomDpDimmer,
|
|
1027
|
+
profile_type=DeviceProfile.IP_DIMMER,
|
|
1028
|
+
channels=(4,),
|
|
1029
|
+
)
|
|
1030
|
+
DeviceProfileRegistry.register(
|
|
1031
|
+
category=DataPointCategory.LIGHT,
|
|
1032
|
+
models="HmIP-DRDI3",
|
|
1033
|
+
data_point_class=CustomDpDimmer,
|
|
1034
|
+
profile_type=DeviceProfile.IP_DIMMER,
|
|
1035
|
+
channels=(5, 9, 13),
|
|
1036
|
+
)
|
|
1037
|
+
DeviceProfileRegistry.register(
|
|
1038
|
+
category=DataPointCategory.LIGHT,
|
|
1039
|
+
models="HmIP-FDT",
|
|
1040
|
+
data_point_class=CustomDpDimmer,
|
|
1041
|
+
profile_type=DeviceProfile.IP_DIMMER,
|
|
1042
|
+
channels=(2,),
|
|
1043
|
+
)
|
|
1044
|
+
DeviceProfileRegistry.register(
|
|
1045
|
+
category=DataPointCategory.LIGHT,
|
|
1046
|
+
models="HmIP-PDT",
|
|
1047
|
+
data_point_class=CustomDpDimmer,
|
|
1048
|
+
profile_type=DeviceProfile.IP_DIMMER,
|
|
1049
|
+
channels=(3,),
|
|
1050
|
+
)
|
|
1051
|
+
DeviceProfileRegistry.register(
|
|
1052
|
+
category=DataPointCategory.LIGHT,
|
|
1053
|
+
models="HmIP-WGT",
|
|
1054
|
+
data_point_class=CustomDpDimmer,
|
|
1055
|
+
profile_type=DeviceProfile.IP_DIMMER,
|
|
1056
|
+
channels=(2,),
|
|
1057
|
+
)
|
|
1058
|
+
DeviceProfileRegistry.register(
|
|
1059
|
+
category=DataPointCategory.LIGHT,
|
|
1060
|
+
models="HmIPW-DRD3",
|
|
1061
|
+
data_point_class=CustomDpDimmer,
|
|
1062
|
+
profile_type=DeviceProfile.IP_DIMMER,
|
|
1063
|
+
channels=(2, 6, 10),
|
|
1064
|
+
)
|
|
1065
|
+
|
|
1066
|
+
# IP Fixed Color Light
|
|
1067
|
+
DeviceProfileRegistry.register(
|
|
1068
|
+
category=DataPointCategory.LIGHT,
|
|
1069
|
+
models="HmIP-BSL",
|
|
1070
|
+
data_point_class=CustomDpIpFixedColorLight,
|
|
1071
|
+
profile_type=DeviceProfile.IP_FIXED_COLOR_LIGHT,
|
|
1072
|
+
channels=(8, 12),
|
|
1073
|
+
)
|
|
1074
|
+
|
|
1075
|
+
# IP Simple Fixed Color Light (Wired)
|
|
1076
|
+
DeviceProfileRegistry.register(
|
|
1077
|
+
category=DataPointCategory.LIGHT,
|
|
1078
|
+
models="HmIPW-WRC6",
|
|
1079
|
+
data_point_class=CustomDpIpFixedColorLight,
|
|
1080
|
+
profile_type=DeviceProfile.IP_SIMPLE_FIXED_COLOR_LIGHT_WIRED,
|
|
1081
|
+
channels=(7, 8, 9, 10, 11, 12, 13),
|
|
1082
|
+
)
|
|
1083
|
+
|
|
1084
|
+
# IP RGBW Light
|
|
1085
|
+
DeviceProfileRegistry.register(
|
|
1086
|
+
category=DataPointCategory.LIGHT,
|
|
1087
|
+
models=("HmIP-RGBW", "HmIP-LSC"),
|
|
1088
|
+
data_point_class=CustomDpIpRGBWLight,
|
|
1089
|
+
profile_type=DeviceProfile.IP_RGBW_LIGHT,
|
|
1090
|
+
)
|
|
1091
|
+
|
|
1092
|
+
# IP DRG DALI Light
|
|
1093
|
+
DeviceProfileRegistry.register(
|
|
1094
|
+
category=DataPointCategory.LIGHT,
|
|
1095
|
+
models="HmIP-DRG-DALI",
|
|
1096
|
+
data_point_class=CustomDpIpDrgDaliLight,
|
|
1097
|
+
profile_type=DeviceProfile.IP_DRG_DALI,
|
|
1098
|
+
channels=tuple(range(1, 49)),
|
|
1099
|
+
)
|
|
1100
|
+
|
|
1101
|
+
# HmIP-SCTH230 (Dimmer with additional sensors)
|
|
1102
|
+
DeviceProfileRegistry.register(
|
|
1103
|
+
category=DataPointCategory.LIGHT,
|
|
1104
|
+
models="HmIP-SCTH230",
|
|
1105
|
+
data_point_class=CustomDpDimmer,
|
|
1106
|
+
profile_type=DeviceProfile.IP_DIMMER,
|
|
1107
|
+
channels=(12,),
|
|
1108
|
+
extended=ExtendedDeviceConfig(
|
|
1109
|
+
additional_data_points={
|
|
1110
|
+
1: (Parameter.CONCENTRATION,),
|
|
1111
|
+
4: (Parameter.HUMIDITY, Parameter.ACTUAL_TEMPERATURE),
|
|
1112
|
+
}
|
|
1113
|
+
),
|
|
1114
|
+
)
|
|
1115
|
+
|
|
1116
|
+
# HBW-LC4-IN4-DR (Dimmer with additional inputs)
|
|
1117
|
+
DeviceProfileRegistry.register(
|
|
1118
|
+
category=DataPointCategory.LIGHT,
|
|
1119
|
+
models="HBW-LC4-IN4-DR",
|
|
1120
|
+
data_point_class=CustomDpDimmer,
|
|
1121
|
+
profile_type=DeviceProfile.RF_DIMMER,
|
|
1122
|
+
channels=(5, 6, 7, 8),
|
|
1123
|
+
extended=ExtendedDeviceConfig(
|
|
1124
|
+
additional_data_points={
|
|
1125
|
+
1: (Parameter.PRESS_LONG, Parameter.PRESS_SHORT, Parameter.SENSOR),
|
|
1126
|
+
2: (Parameter.PRESS_LONG, Parameter.PRESS_SHORT, Parameter.SENSOR),
|
|
1127
|
+
3: (Parameter.PRESS_LONG, Parameter.PRESS_SHORT, Parameter.SENSOR),
|
|
1128
|
+
4: (Parameter.PRESS_LONG, Parameter.PRESS_SHORT, Parameter.SENSOR),
|
|
1129
|
+
}
|
|
1130
|
+
),
|
|
1131
|
+
)
|
|
1132
|
+
|
|
1133
|
+
# HBW-LC-RGBWW-IN6-DR (Complex device with multiple configs)
|
|
1134
|
+
DeviceProfileRegistry.register_multiple(
|
|
1135
|
+
category=DataPointCategory.LIGHT,
|
|
1136
|
+
models="HBW-LC-RGBWW-IN6-DR",
|
|
1137
|
+
configs=(
|
|
1138
|
+
DeviceConfig(
|
|
1139
|
+
data_point_class=CustomDpDimmer,
|
|
1140
|
+
profile_type=DeviceProfile.RF_DIMMER,
|
|
1141
|
+
channels=(7, 8, 9, 10, 11, 12),
|
|
1142
|
+
extended=ExtendedDeviceConfig(
|
|
1143
|
+
additional_data_points={
|
|
1144
|
+
(1, 2, 3, 4, 5, 6): (
|
|
1145
|
+
Parameter.PRESS_LONG,
|
|
1146
|
+
Parameter.PRESS_SHORT,
|
|
1147
|
+
Parameter.SENSOR,
|
|
1148
|
+
)
|
|
1149
|
+
},
|
|
1150
|
+
),
|
|
1151
|
+
),
|
|
1152
|
+
DeviceConfig(
|
|
1153
|
+
data_point_class=CustomDpColorDimmer,
|
|
1154
|
+
profile_type=DeviceProfile.RF_DIMMER_COLOR_FIXED,
|
|
1155
|
+
channels=(13,),
|
|
1156
|
+
extended=ExtendedDeviceConfig(fixed_channel_fields={15: {Field.COLOR: Parameter.COLOR}}),
|
|
1157
|
+
),
|
|
1158
|
+
DeviceConfig(
|
|
1159
|
+
data_point_class=CustomDpColorDimmer,
|
|
1160
|
+
profile_type=DeviceProfile.RF_DIMMER_COLOR_FIXED,
|
|
1161
|
+
channels=(14,),
|
|
1162
|
+
extended=ExtendedDeviceConfig(fixed_channel_fields={16: {Field.COLOR: Parameter.COLOR}}),
|
|
1163
|
+
),
|
|
1164
|
+
),
|
|
1165
|
+
)
|
|
1166
|
+
|
|
1167
|
+
# HmIP-MP3P LED Control (channel 6)
|
|
1168
|
+
DeviceProfileRegistry.register(
|
|
1169
|
+
category=DataPointCategory.LIGHT,
|
|
1170
|
+
models="HmIP-MP3P",
|
|
1171
|
+
data_point_class=CustomDpSoundPlayerLed,
|
|
1172
|
+
profile_type=DeviceProfile.IP_SOUND_PLAYER_LED,
|
|
1173
|
+
channels=(6,),
|
|
1174
|
+
)
|