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.
Files changed (188) hide show
  1. aiohomematic/__init__.py +110 -0
  2. aiohomematic/_log_context_protocol.py +29 -0
  3. aiohomematic/api.py +410 -0
  4. aiohomematic/async_support.py +250 -0
  5. aiohomematic/backend_detection.py +462 -0
  6. aiohomematic/central/__init__.py +103 -0
  7. aiohomematic/central/async_rpc_server.py +760 -0
  8. aiohomematic/central/central_unit.py +1152 -0
  9. aiohomematic/central/config.py +463 -0
  10. aiohomematic/central/config_builder.py +772 -0
  11. aiohomematic/central/connection_state.py +160 -0
  12. aiohomematic/central/coordinators/__init__.py +38 -0
  13. aiohomematic/central/coordinators/cache.py +414 -0
  14. aiohomematic/central/coordinators/client.py +480 -0
  15. aiohomematic/central/coordinators/connection_recovery.py +1141 -0
  16. aiohomematic/central/coordinators/device.py +1166 -0
  17. aiohomematic/central/coordinators/event.py +514 -0
  18. aiohomematic/central/coordinators/hub.py +532 -0
  19. aiohomematic/central/decorators.py +184 -0
  20. aiohomematic/central/device_registry.py +229 -0
  21. aiohomematic/central/events/__init__.py +104 -0
  22. aiohomematic/central/events/bus.py +1392 -0
  23. aiohomematic/central/events/integration.py +424 -0
  24. aiohomematic/central/events/types.py +194 -0
  25. aiohomematic/central/health.py +762 -0
  26. aiohomematic/central/rpc_server.py +353 -0
  27. aiohomematic/central/scheduler.py +794 -0
  28. aiohomematic/central/state_machine.py +391 -0
  29. aiohomematic/client/__init__.py +203 -0
  30. aiohomematic/client/_rpc_errors.py +187 -0
  31. aiohomematic/client/backends/__init__.py +48 -0
  32. aiohomematic/client/backends/base.py +335 -0
  33. aiohomematic/client/backends/capabilities.py +138 -0
  34. aiohomematic/client/backends/ccu.py +487 -0
  35. aiohomematic/client/backends/factory.py +116 -0
  36. aiohomematic/client/backends/homegear.py +294 -0
  37. aiohomematic/client/backends/json_ccu.py +252 -0
  38. aiohomematic/client/backends/protocol.py +316 -0
  39. aiohomematic/client/ccu.py +1857 -0
  40. aiohomematic/client/circuit_breaker.py +459 -0
  41. aiohomematic/client/config.py +64 -0
  42. aiohomematic/client/handlers/__init__.py +40 -0
  43. aiohomematic/client/handlers/backup.py +157 -0
  44. aiohomematic/client/handlers/base.py +79 -0
  45. aiohomematic/client/handlers/device_ops.py +1085 -0
  46. aiohomematic/client/handlers/firmware.py +144 -0
  47. aiohomematic/client/handlers/link_mgmt.py +199 -0
  48. aiohomematic/client/handlers/metadata.py +436 -0
  49. aiohomematic/client/handlers/programs.py +144 -0
  50. aiohomematic/client/handlers/sysvars.py +100 -0
  51. aiohomematic/client/interface_client.py +1304 -0
  52. aiohomematic/client/json_rpc.py +2068 -0
  53. aiohomematic/client/request_coalescer.py +282 -0
  54. aiohomematic/client/rpc_proxy.py +629 -0
  55. aiohomematic/client/state_machine.py +324 -0
  56. aiohomematic/const.py +2207 -0
  57. aiohomematic/context.py +275 -0
  58. aiohomematic/converter.py +270 -0
  59. aiohomematic/decorators.py +390 -0
  60. aiohomematic/exceptions.py +185 -0
  61. aiohomematic/hmcli.py +997 -0
  62. aiohomematic/i18n.py +193 -0
  63. aiohomematic/interfaces/__init__.py +407 -0
  64. aiohomematic/interfaces/central.py +1067 -0
  65. aiohomematic/interfaces/client.py +1096 -0
  66. aiohomematic/interfaces/coordinators.py +63 -0
  67. aiohomematic/interfaces/model.py +1921 -0
  68. aiohomematic/interfaces/operations.py +217 -0
  69. aiohomematic/logging_context.py +134 -0
  70. aiohomematic/metrics/__init__.py +125 -0
  71. aiohomematic/metrics/_protocols.py +140 -0
  72. aiohomematic/metrics/aggregator.py +534 -0
  73. aiohomematic/metrics/dataclasses.py +489 -0
  74. aiohomematic/metrics/emitter.py +292 -0
  75. aiohomematic/metrics/events.py +183 -0
  76. aiohomematic/metrics/keys.py +300 -0
  77. aiohomematic/metrics/observer.py +563 -0
  78. aiohomematic/metrics/stats.py +172 -0
  79. aiohomematic/model/__init__.py +189 -0
  80. aiohomematic/model/availability.py +65 -0
  81. aiohomematic/model/calculated/__init__.py +89 -0
  82. aiohomematic/model/calculated/climate.py +276 -0
  83. aiohomematic/model/calculated/data_point.py +315 -0
  84. aiohomematic/model/calculated/field.py +147 -0
  85. aiohomematic/model/calculated/operating_voltage_level.py +286 -0
  86. aiohomematic/model/calculated/support.py +232 -0
  87. aiohomematic/model/custom/__init__.py +214 -0
  88. aiohomematic/model/custom/capabilities/__init__.py +67 -0
  89. aiohomematic/model/custom/capabilities/climate.py +41 -0
  90. aiohomematic/model/custom/capabilities/light.py +87 -0
  91. aiohomematic/model/custom/capabilities/lock.py +44 -0
  92. aiohomematic/model/custom/capabilities/siren.py +63 -0
  93. aiohomematic/model/custom/climate.py +1130 -0
  94. aiohomematic/model/custom/cover.py +722 -0
  95. aiohomematic/model/custom/data_point.py +360 -0
  96. aiohomematic/model/custom/definition.py +300 -0
  97. aiohomematic/model/custom/field.py +89 -0
  98. aiohomematic/model/custom/light.py +1174 -0
  99. aiohomematic/model/custom/lock.py +322 -0
  100. aiohomematic/model/custom/mixins.py +445 -0
  101. aiohomematic/model/custom/profile.py +945 -0
  102. aiohomematic/model/custom/registry.py +251 -0
  103. aiohomematic/model/custom/siren.py +462 -0
  104. aiohomematic/model/custom/switch.py +195 -0
  105. aiohomematic/model/custom/text_display.py +289 -0
  106. aiohomematic/model/custom/valve.py +78 -0
  107. aiohomematic/model/data_point.py +1416 -0
  108. aiohomematic/model/device.py +1840 -0
  109. aiohomematic/model/event.py +216 -0
  110. aiohomematic/model/generic/__init__.py +327 -0
  111. aiohomematic/model/generic/action.py +40 -0
  112. aiohomematic/model/generic/action_select.py +62 -0
  113. aiohomematic/model/generic/binary_sensor.py +30 -0
  114. aiohomematic/model/generic/button.py +31 -0
  115. aiohomematic/model/generic/data_point.py +177 -0
  116. aiohomematic/model/generic/dummy.py +150 -0
  117. aiohomematic/model/generic/number.py +76 -0
  118. aiohomematic/model/generic/select.py +56 -0
  119. aiohomematic/model/generic/sensor.py +76 -0
  120. aiohomematic/model/generic/switch.py +54 -0
  121. aiohomematic/model/generic/text.py +33 -0
  122. aiohomematic/model/hub/__init__.py +100 -0
  123. aiohomematic/model/hub/binary_sensor.py +24 -0
  124. aiohomematic/model/hub/button.py +28 -0
  125. aiohomematic/model/hub/connectivity.py +190 -0
  126. aiohomematic/model/hub/data_point.py +342 -0
  127. aiohomematic/model/hub/hub.py +864 -0
  128. aiohomematic/model/hub/inbox.py +135 -0
  129. aiohomematic/model/hub/install_mode.py +393 -0
  130. aiohomematic/model/hub/metrics.py +208 -0
  131. aiohomematic/model/hub/number.py +42 -0
  132. aiohomematic/model/hub/select.py +52 -0
  133. aiohomematic/model/hub/sensor.py +37 -0
  134. aiohomematic/model/hub/switch.py +43 -0
  135. aiohomematic/model/hub/text.py +30 -0
  136. aiohomematic/model/hub/update.py +221 -0
  137. aiohomematic/model/support.py +592 -0
  138. aiohomematic/model/update.py +140 -0
  139. aiohomematic/model/week_profile.py +1827 -0
  140. aiohomematic/property_decorators.py +719 -0
  141. aiohomematic/py.typed +0 -0
  142. aiohomematic/rega_scripts/accept_device_in_inbox.fn +51 -0
  143. aiohomematic/rega_scripts/create_backup_start.fn +28 -0
  144. aiohomematic/rega_scripts/create_backup_status.fn +89 -0
  145. aiohomematic/rega_scripts/fetch_all_device_data.fn +97 -0
  146. aiohomematic/rega_scripts/get_backend_info.fn +25 -0
  147. aiohomematic/rega_scripts/get_inbox_devices.fn +61 -0
  148. aiohomematic/rega_scripts/get_program_descriptions.fn +31 -0
  149. aiohomematic/rega_scripts/get_serial.fn +44 -0
  150. aiohomematic/rega_scripts/get_service_messages.fn +83 -0
  151. aiohomematic/rega_scripts/get_system_update_info.fn +39 -0
  152. aiohomematic/rega_scripts/get_system_variable_descriptions.fn +31 -0
  153. aiohomematic/rega_scripts/set_program_state.fn +17 -0
  154. aiohomematic/rega_scripts/set_system_variable.fn +19 -0
  155. aiohomematic/rega_scripts/trigger_firmware_update.fn +67 -0
  156. aiohomematic/schemas.py +256 -0
  157. aiohomematic/store/__init__.py +55 -0
  158. aiohomematic/store/dynamic/__init__.py +43 -0
  159. aiohomematic/store/dynamic/command.py +250 -0
  160. aiohomematic/store/dynamic/data.py +175 -0
  161. aiohomematic/store/dynamic/details.py +187 -0
  162. aiohomematic/store/dynamic/ping_pong.py +416 -0
  163. aiohomematic/store/persistent/__init__.py +71 -0
  164. aiohomematic/store/persistent/base.py +285 -0
  165. aiohomematic/store/persistent/device.py +233 -0
  166. aiohomematic/store/persistent/incident.py +380 -0
  167. aiohomematic/store/persistent/paramset.py +241 -0
  168. aiohomematic/store/persistent/session.py +556 -0
  169. aiohomematic/store/serialization.py +150 -0
  170. aiohomematic/store/storage.py +689 -0
  171. aiohomematic/store/types.py +526 -0
  172. aiohomematic/store/visibility/__init__.py +40 -0
  173. aiohomematic/store/visibility/parser.py +141 -0
  174. aiohomematic/store/visibility/registry.py +722 -0
  175. aiohomematic/store/visibility/rules.py +307 -0
  176. aiohomematic/strings.json +237 -0
  177. aiohomematic/support.py +706 -0
  178. aiohomematic/tracing.py +236 -0
  179. aiohomematic/translations/de.json +237 -0
  180. aiohomematic/translations/en.json +237 -0
  181. aiohomematic/type_aliases.py +51 -0
  182. aiohomematic/validator.py +128 -0
  183. aiohomematic-2026.1.29.dist-info/METADATA +296 -0
  184. aiohomematic-2026.1.29.dist-info/RECORD +188 -0
  185. aiohomematic-2026.1.29.dist-info/WHEEL +5 -0
  186. aiohomematic-2026.1.29.dist-info/entry_points.txt +2 -0
  187. aiohomematic-2026.1.29.dist-info/licenses/LICENSE +21 -0
  188. 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
+ )