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,289 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
# Copyright (c) 2021-2026
|
|
3
|
+
"""
|
|
4
|
+
Custom text display data points for devices with LCD displays.
|
|
5
|
+
|
|
6
|
+
This module provides support for HmIP devices with text display capabilities,
|
|
7
|
+
such as the HmIP-WRCD (Wall-mount Remote Control with Display).
|
|
8
|
+
|
|
9
|
+
Public API of this module is defined by __all__.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import logging
|
|
15
|
+
from typing import Final, TypedDict, Unpack
|
|
16
|
+
|
|
17
|
+
from aiohomematic import i18n
|
|
18
|
+
from aiohomematic.const import DataPointCategory, DeviceProfile, Field
|
|
19
|
+
from aiohomematic.exceptions import ValidationException
|
|
20
|
+
from aiohomematic.model.custom.data_point import CustomDataPoint
|
|
21
|
+
from aiohomematic.model.custom.field import DataPointField
|
|
22
|
+
from aiohomematic.model.custom.registry import DeviceProfileRegistry
|
|
23
|
+
from aiohomematic.model.data_point import CallParameterCollector, bind_collector
|
|
24
|
+
from aiohomematic.model.generic import DpAction, DpActionSelect, DpBinarySensor
|
|
25
|
+
from aiohomematic.property_decorators import DelegatedProperty, Kind, state_property
|
|
26
|
+
|
|
27
|
+
__all__ = ["CustomDpTextDisplay", "TextDisplayArgs"]
|
|
28
|
+
|
|
29
|
+
_LOGGER: Final = logging.getLogger(__name__)
|
|
30
|
+
|
|
31
|
+
# Default values for send_text parameters
|
|
32
|
+
_DEFAULT_BACKGROUND_COLOR: Final = "WHITE"
|
|
33
|
+
_DEFAULT_TEXT_COLOR: Final = "BLACK"
|
|
34
|
+
_DEFAULT_ALIGNMENT: Final = "CENTER"
|
|
35
|
+
_DEFAULT_DISPLAY_ID: Final = 1
|
|
36
|
+
_DEFAULT_REPEAT: Final = 1
|
|
37
|
+
_DEFAULT_INTERVAL: Final = 1
|
|
38
|
+
|
|
39
|
+
# Validation ranges
|
|
40
|
+
_MIN_DISPLAY_ID: Final = 1
|
|
41
|
+
_MAX_DISPLAY_ID: Final = 5
|
|
42
|
+
_MIN_INTERVAL: Final = 1
|
|
43
|
+
_MAX_INTERVAL: Final = 15
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class TextDisplayArgs(TypedDict, total=False):
|
|
47
|
+
"""Arguments for send_text method."""
|
|
48
|
+
|
|
49
|
+
text: str
|
|
50
|
+
icon: str
|
|
51
|
+
background_color: str
|
|
52
|
+
text_color: str
|
|
53
|
+
alignment: str
|
|
54
|
+
display_id: int
|
|
55
|
+
sound: str
|
|
56
|
+
repeat: int
|
|
57
|
+
interval: int
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class CustomDpTextDisplay(CustomDataPoint):
|
|
61
|
+
"""Class for HomematicIP text display data point."""
|
|
62
|
+
|
|
63
|
+
__slots__ = () # Required to prevent __dict__ creation (descriptors are class-level)
|
|
64
|
+
|
|
65
|
+
_category = DataPointCategory.TEXT_DISPLAY
|
|
66
|
+
|
|
67
|
+
# Declarative data point field definitions
|
|
68
|
+
_dp_acoustic_notification_selection: Final = DataPointField(
|
|
69
|
+
field=Field.ACOUSTIC_NOTIFICATION_SELECTION, dpt=DpActionSelect
|
|
70
|
+
)
|
|
71
|
+
_dp_display_data_alignment: Final = DataPointField(field=Field.DISPLAY_DATA_ALIGNMENT, dpt=DpActionSelect)
|
|
72
|
+
_dp_display_data_background_color: Final = DataPointField(
|
|
73
|
+
field=Field.DISPLAY_DATA_BACKGROUND_COLOR, dpt=DpActionSelect
|
|
74
|
+
)
|
|
75
|
+
_dp_display_data_commit: Final = DataPointField(field=Field.DISPLAY_DATA_COMMIT, dpt=DpAction)
|
|
76
|
+
_dp_display_data_icon: Final = DataPointField(field=Field.DISPLAY_DATA_ICON, dpt=DpActionSelect)
|
|
77
|
+
_dp_display_data_id: Final = DataPointField(field=Field.DISPLAY_DATA_ID, dpt=DpActionSelect)
|
|
78
|
+
_dp_display_data_string: Final = DataPointField(field=Field.DISPLAY_DATA_STRING, dpt=DpAction)
|
|
79
|
+
_dp_display_data_text_color: Final = DataPointField(field=Field.DISPLAY_DATA_TEXT_COLOR, dpt=DpActionSelect)
|
|
80
|
+
_dp_interval: Final = DataPointField(field=Field.INTERVAL, dpt=DpActionSelect)
|
|
81
|
+
_dp_repetitions: Final = DataPointField(field=Field.REPETITIONS, dpt=DpActionSelect)
|
|
82
|
+
_dp_burst_limit_warning: Final = DataPointField(field=Field.BURST_LIMIT_WARNING, dpt=DpBinarySensor)
|
|
83
|
+
|
|
84
|
+
# Expose available options via DelegatedProperty
|
|
85
|
+
@staticmethod
|
|
86
|
+
def _get_index_from_value_list(*, value: str | None, value_list: tuple[str, ...] | None) -> int | None:
|
|
87
|
+
"""Get the index of a value in a value list."""
|
|
88
|
+
if value is None or value_list is None:
|
|
89
|
+
return None
|
|
90
|
+
if value in value_list:
|
|
91
|
+
return value_list.index(value)
|
|
92
|
+
return None
|
|
93
|
+
|
|
94
|
+
@staticmethod
|
|
95
|
+
def _get_repetition_string(*, repeat: int) -> str:
|
|
96
|
+
"""Convert repetition int to device string format (REPETITIONS_XXX)."""
|
|
97
|
+
if repeat == 0:
|
|
98
|
+
return "NO_REPETITION"
|
|
99
|
+
if repeat == -1:
|
|
100
|
+
return "INFINITE_REPETITIONS"
|
|
101
|
+
return f"REPETITIONS_{repeat:03d}"
|
|
102
|
+
|
|
103
|
+
_available_repetitions: Final = DelegatedProperty[tuple[str, ...] | None](path="_dp_repetitions.values")
|
|
104
|
+
available_alignments: Final = DelegatedProperty[tuple[str, ...] | None](
|
|
105
|
+
path="_dp_display_data_alignment.values", kind=Kind.STATE
|
|
106
|
+
)
|
|
107
|
+
available_background_colors: Final = DelegatedProperty[tuple[str, ...] | None](
|
|
108
|
+
path="_dp_display_data_background_color.values", kind=Kind.STATE
|
|
109
|
+
)
|
|
110
|
+
available_icons: Final = DelegatedProperty[tuple[str, ...] | None](
|
|
111
|
+
path="_dp_display_data_icon.values", kind=Kind.STATE
|
|
112
|
+
)
|
|
113
|
+
available_sounds: Final = DelegatedProperty[tuple[str, ...] | None](
|
|
114
|
+
path="_dp_acoustic_notification_selection.values", kind=Kind.STATE
|
|
115
|
+
)
|
|
116
|
+
available_text_colors: Final = DelegatedProperty[tuple[str, ...] | None](
|
|
117
|
+
path="_dp_display_data_text_color.values", kind=Kind.STATE
|
|
118
|
+
)
|
|
119
|
+
burst_limit_warning: Final = DelegatedProperty[bool](path="_dp_burst_limit_warning.value")
|
|
120
|
+
|
|
121
|
+
@state_property
|
|
122
|
+
def has_icons(self) -> bool:
|
|
123
|
+
"""Return true if display has icons."""
|
|
124
|
+
return self.available_icons is not None
|
|
125
|
+
|
|
126
|
+
@state_property
|
|
127
|
+
def has_sounds(self) -> bool:
|
|
128
|
+
"""Return true if display has sounds."""
|
|
129
|
+
return self.available_sounds is not None
|
|
130
|
+
|
|
131
|
+
@bind_collector
|
|
132
|
+
async def send_text(
|
|
133
|
+
self,
|
|
134
|
+
*,
|
|
135
|
+
collector: CallParameterCollector | None = None,
|
|
136
|
+
**kwargs: Unpack[TextDisplayArgs],
|
|
137
|
+
) -> None:
|
|
138
|
+
"""
|
|
139
|
+
Send text to the display.
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
collector: Optional call parameter collector.
|
|
143
|
+
**kwargs: Display parameters from TextDisplayArgs:
|
|
144
|
+
text: The text to display (required).
|
|
145
|
+
icon: Icon name from available_icons (optional).
|
|
146
|
+
background_color: Background color (optional, default: WHITE).
|
|
147
|
+
text_color: Text color (optional, default: BLACK).
|
|
148
|
+
alignment: Text alignment (optional, default: CENTER).
|
|
149
|
+
display_id: Display slot 1-5 (optional, default: 1).
|
|
150
|
+
sound: Sound name from available_sounds (optional).
|
|
151
|
+
repeat: Sound repetitions 0-15 (optional, default: 1).
|
|
152
|
+
interval: Interval between sound tones 1-15 (optional, default: 1).
|
|
153
|
+
|
|
154
|
+
"""
|
|
155
|
+
# Warn if burst limit is active
|
|
156
|
+
if self.burst_limit_warning:
|
|
157
|
+
_LOGGER.warning(
|
|
158
|
+
i18n.tr(
|
|
159
|
+
key="log.model.custom.text_display.send_text.burst_limit_warning",
|
|
160
|
+
full_name=self.full_name,
|
|
161
|
+
)
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
text = kwargs.get("text", "")
|
|
165
|
+
icon = kwargs.get("icon")
|
|
166
|
+
background_color = kwargs.get("background_color", _DEFAULT_BACKGROUND_COLOR)
|
|
167
|
+
text_color = kwargs.get("text_color", _DEFAULT_TEXT_COLOR)
|
|
168
|
+
alignment = kwargs.get("alignment", _DEFAULT_ALIGNMENT)
|
|
169
|
+
display_id = kwargs.get("display_id", _DEFAULT_DISPLAY_ID)
|
|
170
|
+
sound = kwargs.get("sound")
|
|
171
|
+
repeat = kwargs.get("repeat", _DEFAULT_REPEAT)
|
|
172
|
+
interval = kwargs.get("interval", _DEFAULT_INTERVAL)
|
|
173
|
+
|
|
174
|
+
# Validate icon if provided
|
|
175
|
+
if icon is not None and self.available_icons and icon not in self.available_icons:
|
|
176
|
+
raise ValidationException(
|
|
177
|
+
i18n.tr(
|
|
178
|
+
key="exception.model.custom.text_display.invalid_icon",
|
|
179
|
+
full_name=self.full_name,
|
|
180
|
+
value=icon,
|
|
181
|
+
)
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
# Validate background color
|
|
185
|
+
if self.available_background_colors and background_color not in self.available_background_colors:
|
|
186
|
+
raise ValidationException(
|
|
187
|
+
i18n.tr(
|
|
188
|
+
key="exception.model.custom.text_display.invalid_background_color",
|
|
189
|
+
full_name=self.full_name,
|
|
190
|
+
value=background_color,
|
|
191
|
+
)
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
# Validate text color
|
|
195
|
+
if self.available_text_colors and text_color not in self.available_text_colors:
|
|
196
|
+
raise ValidationException(
|
|
197
|
+
i18n.tr(
|
|
198
|
+
key="exception.model.custom.text_display.invalid_text_color",
|
|
199
|
+
full_name=self.full_name,
|
|
200
|
+
value=text_color,
|
|
201
|
+
)
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
# Validate alignment
|
|
205
|
+
if self.available_alignments and alignment not in self.available_alignments:
|
|
206
|
+
raise ValidationException(
|
|
207
|
+
i18n.tr(
|
|
208
|
+
key="exception.model.custom.text_display.invalid_alignment",
|
|
209
|
+
full_name=self.full_name,
|
|
210
|
+
value=alignment,
|
|
211
|
+
)
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
# Validate display_id
|
|
215
|
+
if not _MIN_DISPLAY_ID <= display_id <= _MAX_DISPLAY_ID:
|
|
216
|
+
raise ValidationException(
|
|
217
|
+
i18n.tr(
|
|
218
|
+
key="exception.model.custom.text_display.invalid_display_id",
|
|
219
|
+
full_name=self.full_name,
|
|
220
|
+
value=display_id,
|
|
221
|
+
)
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
# Validate sound if provided
|
|
225
|
+
if sound is not None and self.available_sounds and sound not in self.available_sounds:
|
|
226
|
+
raise ValidationException(
|
|
227
|
+
i18n.tr(
|
|
228
|
+
key="exception.model.custom.text_display.invalid_sound",
|
|
229
|
+
full_name=self.full_name,
|
|
230
|
+
value=sound,
|
|
231
|
+
)
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
# Validate repeat - convert to string format for validation
|
|
235
|
+
if (repetition_value := self._get_repetition_string(repeat=repeat)) not in (self._available_repetitions or ()):
|
|
236
|
+
raise ValidationException(
|
|
237
|
+
i18n.tr(
|
|
238
|
+
key="exception.model.custom.text_display.invalid_repeat",
|
|
239
|
+
full_name=self.full_name,
|
|
240
|
+
value=repeat,
|
|
241
|
+
)
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
# Validate interval
|
|
245
|
+
if not _MIN_INTERVAL <= interval <= _MAX_INTERVAL:
|
|
246
|
+
raise ValidationException(
|
|
247
|
+
i18n.tr(
|
|
248
|
+
key="exception.model.custom.text_display.invalid_interval",
|
|
249
|
+
full_name=self.full_name,
|
|
250
|
+
value=interval,
|
|
251
|
+
)
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
# Get icon value (use first icon = NO_ICON if not specified)
|
|
255
|
+
icon_value = self.available_icons[0] if self.available_icons else "NO_ICON"
|
|
256
|
+
if icon is not None:
|
|
257
|
+
icon_value = icon
|
|
258
|
+
|
|
259
|
+
# Send display parameters using individual data points
|
|
260
|
+
# The collector batches these into a single put_paramset call
|
|
261
|
+
await self._dp_display_data_background_color.send_value(value=background_color, collector=collector)
|
|
262
|
+
await self._dp_display_data_text_color.send_value(value=text_color, collector=collector)
|
|
263
|
+
await self._dp_display_data_icon.send_value(value=icon_value, collector=collector)
|
|
264
|
+
await self._dp_display_data_alignment.send_value(value=alignment, collector=collector)
|
|
265
|
+
await self._dp_display_data_string.send_value(value=text, collector=collector)
|
|
266
|
+
await self._dp_display_data_id.send_value(value=display_id, collector=collector)
|
|
267
|
+
|
|
268
|
+
# Send sound parameters if sound is specified
|
|
269
|
+
if sound is not None:
|
|
270
|
+
await self._dp_acoustic_notification_selection.send_value(value=sound, collector=collector)
|
|
271
|
+
await self._dp_repetitions.send_value(value=repetition_value, collector=collector)
|
|
272
|
+
await self._dp_interval.send_value(value=interval, collector=collector)
|
|
273
|
+
|
|
274
|
+
# DISPLAY_DATA_COMMIT triggers the display update - must be last
|
|
275
|
+
await self._dp_display_data_commit.send_value(value=True, collector=collector)
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
# =============================================================================
|
|
279
|
+
# DeviceProfileRegistry Registration
|
|
280
|
+
# =============================================================================
|
|
281
|
+
|
|
282
|
+
# IP Text Display (HmIP-WRCD)
|
|
283
|
+
DeviceProfileRegistry.register(
|
|
284
|
+
category=DataPointCategory.TEXT_DISPLAY,
|
|
285
|
+
models="HmIP-WRCD",
|
|
286
|
+
data_point_class=CustomDpTextDisplay,
|
|
287
|
+
profile_type=DeviceProfile.IP_TEXT_DISPLAY,
|
|
288
|
+
channels=(3,),
|
|
289
|
+
)
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
# Copyright (c) 2021-2026
|
|
3
|
+
"""
|
|
4
|
+
Custom valve data points for heating valve controls.
|
|
5
|
+
|
|
6
|
+
Public API of this module is defined by __all__.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import logging
|
|
12
|
+
from typing import Final, Unpack
|
|
13
|
+
|
|
14
|
+
from aiohomematic.const import DataPointCategory, DeviceProfile, Field
|
|
15
|
+
from aiohomematic.model.custom.data_point import CustomDataPoint
|
|
16
|
+
from aiohomematic.model.custom.field import DataPointField
|
|
17
|
+
from aiohomematic.model.custom.mixins import GroupStateMixin, StateChangeArgs, StateChangeTimerMixin
|
|
18
|
+
from aiohomematic.model.custom.registry import DeviceProfileRegistry
|
|
19
|
+
from aiohomematic.model.data_point import CallParameterCollector, bind_collector
|
|
20
|
+
from aiohomematic.model.generic import DpAction, DpBinarySensor, DpSwitch
|
|
21
|
+
from aiohomematic.property_decorators import DelegatedProperty, Kind
|
|
22
|
+
|
|
23
|
+
_LOGGER: Final = logging.getLogger(__name__)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class CustomDpIpIrrigationValve(StateChangeTimerMixin, GroupStateMixin, CustomDataPoint):
|
|
27
|
+
"""Class for Homematic irrigation valve data point."""
|
|
28
|
+
|
|
29
|
+
__slots__ = () # Required to prevent __dict__ creation (descriptors are class-level)
|
|
30
|
+
|
|
31
|
+
_category = DataPointCategory.VALVE
|
|
32
|
+
|
|
33
|
+
# Declarative data point field definitions
|
|
34
|
+
_dp_group_state = DataPointField(field=Field.GROUP_STATE, dpt=DpBinarySensor)
|
|
35
|
+
_dp_on_time_value = DataPointField(field=Field.ON_TIME_VALUE, dpt=DpAction)
|
|
36
|
+
_dp_state: Final = DataPointField(field=Field.STATE, dpt=DpSwitch)
|
|
37
|
+
|
|
38
|
+
value: Final = DelegatedProperty[bool | None](path="_dp_state.value", kind=Kind.STATE)
|
|
39
|
+
|
|
40
|
+
@bind_collector
|
|
41
|
+
async def close(self, *, collector: CallParameterCollector | None = None) -> None:
|
|
42
|
+
"""Turn the valve off."""
|
|
43
|
+
self.reset_timer_on_time()
|
|
44
|
+
if not self.is_state_change(off=True):
|
|
45
|
+
return
|
|
46
|
+
await self._dp_state.turn_off(collector=collector)
|
|
47
|
+
|
|
48
|
+
def is_state_change(self, **kwargs: Unpack[StateChangeArgs]) -> bool:
|
|
49
|
+
"""Check if the state changes due to kwargs."""
|
|
50
|
+
if self.is_state_change_for_on_off(**kwargs):
|
|
51
|
+
return True
|
|
52
|
+
return super().is_state_change(**kwargs)
|
|
53
|
+
|
|
54
|
+
@bind_collector
|
|
55
|
+
async def open(self, *, on_time: float | None = None, collector: CallParameterCollector | None = None) -> None:
|
|
56
|
+
"""Turn the valve on."""
|
|
57
|
+
if on_time is not None:
|
|
58
|
+
self.set_timer_on_time(on_time=on_time)
|
|
59
|
+
if not self.is_state_change(on=True):
|
|
60
|
+
return
|
|
61
|
+
|
|
62
|
+
if (timer := self.get_and_start_timer()) is not None:
|
|
63
|
+
await self._dp_on_time_value.send_value(value=timer, collector=collector, do_validate=False)
|
|
64
|
+
await self._dp_state.turn_on(collector=collector)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
# =============================================================================
|
|
68
|
+
# DeviceProfileRegistry Registration
|
|
69
|
+
# =============================================================================
|
|
70
|
+
|
|
71
|
+
# IP Irrigation Valve
|
|
72
|
+
DeviceProfileRegistry.register(
|
|
73
|
+
category=DataPointCategory.VALVE,
|
|
74
|
+
models=("ELV-SH-WSM", "HmIP-WSM"),
|
|
75
|
+
data_point_class=CustomDpIpIrrigationValve,
|
|
76
|
+
profile_type=DeviceProfile.IP_IRRIGATION_VALVE,
|
|
77
|
+
channels=(4,),
|
|
78
|
+
)
|