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,208 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2021-2026
3
+ """
4
+ Hub sensors for system metrics.
5
+
6
+ This module provides hub sensor data points for exposing key system metrics
7
+ to Home Assistant. These sensors allow monitoring of system health,
8
+ connection latency, and event timing without needing separate diagnostic entities.
9
+
10
+ Public API
11
+ ----------
12
+ - HmSystemHealthSensor: Overall system health score (0-100%)
13
+ - HmConnectionLatencySensor: Average RPC connection latency in milliseconds
14
+ - HmLastEventAgeSensor: Seconds since last backend event
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ from datetime import datetime
20
+ import logging
21
+ from typing import TYPE_CHECKING, Final
22
+
23
+ from slugify import slugify
24
+
25
+ from aiohomematic.const import (
26
+ HUB_ADDRESS,
27
+ METRICS_SENSOR_CONNECTION_LATENCY_NAME,
28
+ METRICS_SENSOR_LAST_EVENT_AGE_NAME,
29
+ METRICS_SENSOR_SYSTEM_HEALTH_NAME,
30
+ DataPointCategory,
31
+ HubValueType,
32
+ )
33
+ from aiohomematic.interfaces import (
34
+ CentralInfoProtocol,
35
+ ChannelProtocol,
36
+ ConfigProviderProtocol,
37
+ EventBusProviderProtocol,
38
+ EventPublisherProtocol,
39
+ HubSensorDataPointProtocol,
40
+ ParameterVisibilityProviderProtocol,
41
+ ParamsetDescriptionProviderProtocol,
42
+ TaskSchedulerProtocol,
43
+ )
44
+ from aiohomematic.model.data_point import CallbackDataPoint
45
+ from aiohomematic.model.support import HubPathData, PathData, generate_unique_id, get_hub_data_point_name_data
46
+ from aiohomematic.property_decorators import DelegatedProperty, Kind, state_property
47
+ from aiohomematic.support import PayloadMixin
48
+
49
+ if TYPE_CHECKING:
50
+ from aiohomematic.metrics import MetricsObserver
51
+
52
+ _LOGGER: Final = logging.getLogger(__name__)
53
+
54
+
55
+ class _BaseMetricsSensor(CallbackDataPoint, HubSensorDataPointProtocol, PayloadMixin):
56
+ """Base class for metrics hub sensors."""
57
+
58
+ __slots__ = (
59
+ "_cached_value",
60
+ "_metrics_observer",
61
+ "_name_data",
62
+ "_state_uncertain",
63
+ )
64
+
65
+ _category = DataPointCategory.HUB_SENSOR
66
+ _enabled_default = True
67
+ _sensor_name: str
68
+ _unit: str
69
+
70
+ def __init__(
71
+ self,
72
+ *,
73
+ metrics_observer: MetricsObserver,
74
+ config_provider: ConfigProviderProtocol,
75
+ central_info: CentralInfoProtocol,
76
+ event_bus_provider: EventBusProviderProtocol,
77
+ event_publisher: EventPublisherProtocol,
78
+ task_scheduler: TaskSchedulerProtocol,
79
+ paramset_description_provider: ParamsetDescriptionProviderProtocol,
80
+ parameter_visibility_provider: ParameterVisibilityProviderProtocol,
81
+ ) -> None:
82
+ """Initialize the metrics sensor."""
83
+ PayloadMixin.__init__(self)
84
+ self._metrics_observer: Final = metrics_observer
85
+ unique_id: Final = generate_unique_id(
86
+ config_provider=config_provider,
87
+ address=HUB_ADDRESS,
88
+ parameter=slugify(self._sensor_name),
89
+ )
90
+ self._name_data: Final = get_hub_data_point_name_data(
91
+ channel=None, legacy_name=self._sensor_name, central_name=central_info.name
92
+ )
93
+ super().__init__(
94
+ unique_id=unique_id,
95
+ central_info=central_info,
96
+ event_bus_provider=event_bus_provider,
97
+ event_publisher=event_publisher,
98
+ task_scheduler=task_scheduler,
99
+ paramset_description_provider=paramset_description_provider,
100
+ parameter_visibility_provider=parameter_visibility_provider,
101
+ )
102
+ self._state_uncertain: bool = True
103
+ self._cached_value: float = 0.0
104
+
105
+ available: Final = DelegatedProperty[bool](path="_central_info.available", kind=Kind.STATE)
106
+ enabled_default: Final = DelegatedProperty[bool](path="_enabled_default")
107
+ full_name: Final = DelegatedProperty[str](path="_name_data.full_name")
108
+ name: Final = DelegatedProperty[str](path="_name_data.name", kind=Kind.CONFIG)
109
+ state_uncertain: Final = DelegatedProperty[bool](path="_state_uncertain")
110
+ unit: Final = DelegatedProperty[str](path="_unit", kind=Kind.CONFIG)
111
+
112
+ @property
113
+ def channel(self) -> ChannelProtocol | None:
114
+ """Return the identified channel."""
115
+ return None
116
+
117
+ @property
118
+ def data_type(self) -> HubValueType | None:
119
+ """Return the data type of the sensor."""
120
+ return HubValueType.FLOAT
121
+
122
+ @property
123
+ def description(self) -> str | None:
124
+ """Return data point description."""
125
+ return None
126
+
127
+ @property
128
+ def legacy_name(self) -> str | None:
129
+ """Return the original name."""
130
+ return None
131
+
132
+ @state_property
133
+ def value(self) -> float:
134
+ """Return the system health score as percentage (0-100)."""
135
+ return self._get_current_value()
136
+
137
+ def refresh(self, *, write_at: datetime) -> None:
138
+ """Refresh the sensor value from metrics observer."""
139
+ current_value = self._get_current_value()
140
+ if self._cached_value != current_value:
141
+ self._cached_value = current_value
142
+ self._set_modified_at(modified_at=write_at)
143
+ else:
144
+ self._set_refreshed_at(refreshed_at=write_at)
145
+ self._state_uncertain = False
146
+ self.publish_data_point_updated_event()
147
+
148
+ def _get_current_value(self) -> float:
149
+ """Return the current metric value. Override in subclasses."""
150
+ raise NotImplementedError
151
+
152
+ def _get_path_data(self) -> PathData:
153
+ """Return the path data of the data_point."""
154
+ return HubPathData(name=slugify(self._sensor_name))
155
+
156
+ def _get_signature(self) -> str:
157
+ """Return the signature of the data_point."""
158
+ return f"{self._category}/{self.name}"
159
+
160
+
161
+ class HmSystemHealthSensor(_BaseMetricsSensor):
162
+ """
163
+ Hub sensor for system health score.
164
+
165
+ Exposes the overall system health as a percentage (0-100%).
166
+ The health score is derived from client connection states.
167
+ """
168
+
169
+ __slots__ = ()
170
+ _sensor_name = METRICS_SENSOR_SYSTEM_HEALTH_NAME
171
+ _unit = "%"
172
+
173
+ def _get_current_value(self) -> float:
174
+ """Return the current health score as percentage."""
175
+ return round(self._metrics_observer.get_overall_health_score() * 100, 1)
176
+
177
+
178
+ class HmConnectionLatencySensor(_BaseMetricsSensor):
179
+ """
180
+ Hub sensor for connection latency.
181
+
182
+ Exposes the average RPC connection latency in milliseconds.
183
+ """
184
+
185
+ __slots__ = ()
186
+ _sensor_name = METRICS_SENSOR_CONNECTION_LATENCY_NAME
187
+ _unit = "ms"
188
+
189
+ def _get_current_value(self) -> float:
190
+ """Return the current average latency from ping/pong metrics."""
191
+ return round(self._metrics_observer.get_aggregated_latency(pattern="ping_pong").avg_ms, 1)
192
+
193
+
194
+ class HmLastEventAgeSensor(_BaseMetricsSensor):
195
+ """
196
+ Hub sensor for last event age.
197
+
198
+ Exposes the time in seconds since the last backend event was received.
199
+ A value of -1 indicates no events have been received yet.
200
+ """
201
+
202
+ __slots__ = ()
203
+ _sensor_name = METRICS_SENSOR_LAST_EVENT_AGE_NAME
204
+ _unit = "s"
205
+
206
+ def _get_current_value(self) -> float:
207
+ """Return the current last event age in seconds."""
208
+ return round(self._metrics_observer.get_last_event_age_seconds(), 1)
@@ -0,0 +1,42 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2021-2026
3
+ """Module for data points implemented using the number category."""
4
+
5
+ from __future__ import annotations
6
+
7
+ import logging
8
+ from typing import Final
9
+
10
+ from aiohomematic import i18n
11
+ from aiohomematic.const import DataPointCategory
12
+ from aiohomematic.decorators import inspector
13
+ from aiohomematic.model.hub.data_point import GenericSysvarDataPoint
14
+
15
+ _LOGGER: Final = logging.getLogger(__name__)
16
+
17
+
18
+ class SysvarDpNumber(GenericSysvarDataPoint):
19
+ """Implementation of a sysvar number."""
20
+
21
+ __slots__ = ()
22
+
23
+ _category = DataPointCategory.HUB_NUMBER
24
+ _is_extended = True
25
+
26
+ @inspector
27
+ async def send_variable(self, *, value: float) -> None:
28
+ """Set the value of the data_point."""
29
+ if value is not None and self.max is not None and self.min is not None:
30
+ if self.min <= float(value) <= self.max:
31
+ await super().send_variable(value=value)
32
+ else:
33
+ _LOGGER.error(
34
+ i18n.tr(
35
+ key="exception.model.hub.number.invalid_value",
36
+ value=value,
37
+ min=self.min,
38
+ max=self.max,
39
+ )
40
+ )
41
+ return
42
+ await super().send_variable(value=value)
@@ -0,0 +1,52 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2021-2026
3
+ """Module for hub data points implemented using the select category."""
4
+
5
+ from __future__ import annotations
6
+
7
+ import logging
8
+ from typing import Final
9
+
10
+ from aiohomematic import i18n
11
+ from aiohomematic.const import DataPointCategory
12
+ from aiohomematic.decorators import inspector
13
+ from aiohomematic.model.hub.data_point import GenericSysvarDataPoint
14
+ from aiohomematic.model.support import get_value_from_value_list
15
+ from aiohomematic.property_decorators import state_property
16
+
17
+ _LOGGER: Final = logging.getLogger(__name__)
18
+
19
+
20
+ class SysvarDpSelect(GenericSysvarDataPoint):
21
+ """Implementation of a sysvar select data_point."""
22
+
23
+ __slots__ = ()
24
+
25
+ _category = DataPointCategory.HUB_SELECT
26
+ _is_extended = True
27
+
28
+ @state_property
29
+ def value(self) -> str | None:
30
+ """Get the value of the data_point."""
31
+ if (value := get_value_from_value_list(value=self._value, value_list=self.values)) is not None:
32
+ return value
33
+ return None
34
+
35
+ @inspector
36
+ async def send_variable(self, *, value: int | str) -> None:
37
+ """Set the value of the data_point."""
38
+ # We allow setting the value via index as well, just in case.
39
+ if isinstance(value, int) and self._values:
40
+ if 0 <= value < len(self._values):
41
+ await super().send_variable(value=value)
42
+ elif self._values:
43
+ if value in self._values:
44
+ await super().send_variable(value=self._values.index(value))
45
+ else:
46
+ _LOGGER.error(
47
+ i18n.tr(
48
+ key="exception.model.select.value_not_in_value_list",
49
+ name=self.name,
50
+ unique_id=self.unique_id,
51
+ )
52
+ )
@@ -0,0 +1,37 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2021-2026
3
+ """Module for hub data points implemented using the sensor category."""
4
+
5
+ from __future__ import annotations
6
+
7
+ import logging
8
+ from typing import Any, Final
9
+
10
+ from aiohomematic.const import DataPointCategory, HubValueType
11
+ from aiohomematic.model.hub.data_point import GenericSysvarDataPoint
12
+ from aiohomematic.model.support import check_length_and_log, get_value_from_value_list
13
+ from aiohomematic.property_decorators import state_property
14
+
15
+ _LOGGER: Final = logging.getLogger(__name__)
16
+
17
+
18
+ class SysvarDpSensor(GenericSysvarDataPoint):
19
+ """Implementation of a sysvar sensor."""
20
+
21
+ __slots__ = ()
22
+
23
+ _category = DataPointCategory.HUB_SENSOR
24
+
25
+ @state_property
26
+ def value(self) -> Any | None:
27
+ """Return the value."""
28
+ if (
29
+ self._data_type == HubValueType.LIST
30
+ and (value := get_value_from_value_list(value=self._value, value_list=self.values)) is not None
31
+ ):
32
+ return value
33
+ return (
34
+ check_length_and_log(name=self._legacy_name, value=self._value)
35
+ if self._data_type == HubValueType.STRING
36
+ else self._value
37
+ )
@@ -0,0 +1,43 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2021-2026
3
+ """Module for hub data points implemented using the switch category."""
4
+
5
+ from __future__ import annotations
6
+
7
+ from typing import Final
8
+
9
+ from aiohomematic.const import DataPointCategory
10
+ from aiohomematic.decorators import inspector
11
+ from aiohomematic.model.hub.data_point import GenericProgramDataPoint, GenericSysvarDataPoint
12
+ from aiohomematic.property_decorators import DelegatedProperty, Kind
13
+
14
+
15
+ class SysvarDpSwitch(GenericSysvarDataPoint):
16
+ """Implementation of a sysvar switch data_point."""
17
+
18
+ __slots__ = ()
19
+
20
+ _category = DataPointCategory.HUB_SWITCH
21
+ _is_extended = True
22
+
23
+
24
+ class ProgramDpSwitch(GenericProgramDataPoint):
25
+ """Implementation of a program switch data_point."""
26
+
27
+ __slots__ = ()
28
+
29
+ _category = DataPointCategory.HUB_SWITCH
30
+
31
+ value: Final = DelegatedProperty[bool | None](path="_is_active", kind=Kind.STATE)
32
+
33
+ @inspector
34
+ async def turn_off(self) -> None:
35
+ """Turn the program off."""
36
+ await self._hub_data_fetcher.set_program_state(pid=self._pid, state=False)
37
+ await self._hub_data_fetcher.fetch_program_data(scheduled=False)
38
+
39
+ @inspector
40
+ async def turn_on(self) -> None:
41
+ """Turn the program on."""
42
+ await self._hub_data_fetcher.set_program_state(pid=self._pid, state=True)
43
+ await self._hub_data_fetcher.fetch_program_data(scheduled=False)
@@ -0,0 +1,30 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2021-2026
3
+ """Module for hub data points implemented using the text category."""
4
+
5
+ from __future__ import annotations
6
+
7
+ from typing import cast
8
+
9
+ from aiohomematic.const import DataPointCategory
10
+ from aiohomematic.model.hub.data_point import GenericSysvarDataPoint
11
+ from aiohomematic.model.support import check_length_and_log
12
+ from aiohomematic.property_decorators import state_property
13
+
14
+
15
+ class SysvarDpText(GenericSysvarDataPoint):
16
+ """Implementation of a sysvar text data_point."""
17
+
18
+ __slots__ = ()
19
+
20
+ _category = DataPointCategory.HUB_TEXT
21
+ _is_extended = True
22
+
23
+ @state_property
24
+ def value(self) -> str | None:
25
+ """Get the value of the data_point."""
26
+ return cast(str | None, check_length_and_log(name=self._legacy_name, value=self._value))
27
+
28
+ async def send_variable(self, *, value: str | None) -> None:
29
+ """Set the value of the data_point."""
30
+ await super().send_variable(value=value)
@@ -0,0 +1,221 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2021-2026
3
+ """Module for hub update data point."""
4
+
5
+ from __future__ import annotations
6
+
7
+ import asyncio
8
+ from datetime import datetime
9
+ import logging
10
+ from typing import Final
11
+
12
+ from slugify import slugify
13
+
14
+ from aiohomematic import i18n
15
+ from aiohomematic.const import HUB_ADDRESS, DataPointCategory, SystemUpdateData
16
+ from aiohomematic.decorators import inspector
17
+ from aiohomematic.interfaces import (
18
+ CentralInfoProtocol,
19
+ ChannelProtocol,
20
+ ConfigProviderProtocol,
21
+ EventBusProviderProtocol,
22
+ EventPublisherProtocol,
23
+ GenericHubDataPointProtocol,
24
+ ParameterVisibilityProviderProtocol,
25
+ ParamsetDescriptionProviderProtocol,
26
+ PrimaryClientProviderProtocol,
27
+ TaskSchedulerProtocol,
28
+ )
29
+ from aiohomematic.model.data_point import CallbackDataPoint
30
+ from aiohomematic.model.support import HubPathData, PathData, generate_unique_id, get_hub_data_point_name_data
31
+ from aiohomematic.property_decorators import DelegatedProperty, Kind
32
+ from aiohomematic.support import PayloadMixin
33
+
34
+ _LOGGER: Final = logging.getLogger(__name__)
35
+
36
+ _UPDATE_NAME: Final = "System Update"
37
+
38
+
39
+ class HmUpdate(CallbackDataPoint, GenericHubDataPointProtocol, PayloadMixin):
40
+ """Class for a Homematic system update data point."""
41
+
42
+ __slots__ = (
43
+ "_available_firmware",
44
+ "_config_provider",
45
+ "_current_firmware",
46
+ "_name_data",
47
+ "_primary_client_provider",
48
+ "_state_uncertain",
49
+ "_update_available",
50
+ "_update_in_progress",
51
+ "_version_before_update",
52
+ )
53
+
54
+ _category = DataPointCategory.HUB_UPDATE
55
+ _enabled_default = True
56
+
57
+ def __init__(
58
+ self,
59
+ *,
60
+ config_provider: ConfigProviderProtocol,
61
+ central_info: CentralInfoProtocol,
62
+ event_bus_provider: EventBusProviderProtocol,
63
+ event_publisher: EventPublisherProtocol,
64
+ task_scheduler: TaskSchedulerProtocol,
65
+ paramset_description_provider: ParamsetDescriptionProviderProtocol,
66
+ parameter_visibility_provider: ParameterVisibilityProviderProtocol,
67
+ primary_client_provider: PrimaryClientProviderProtocol,
68
+ ) -> None:
69
+ """Initialize the data_point."""
70
+ PayloadMixin.__init__(self)
71
+ unique_id: Final = generate_unique_id(
72
+ config_provider=config_provider,
73
+ address=HUB_ADDRESS,
74
+ parameter=slugify(_UPDATE_NAME),
75
+ )
76
+ self._name_data: Final = get_hub_data_point_name_data(
77
+ channel=None, legacy_name=_UPDATE_NAME, central_name=central_info.name
78
+ )
79
+
80
+ super().__init__(
81
+ unique_id=unique_id,
82
+ central_info=central_info,
83
+ event_bus_provider=event_bus_provider,
84
+ event_publisher=event_publisher,
85
+ task_scheduler=task_scheduler,
86
+ paramset_description_provider=paramset_description_provider,
87
+ parameter_visibility_provider=parameter_visibility_provider,
88
+ )
89
+ self._config_provider: Final = config_provider
90
+ self._primary_client_provider: Final = primary_client_provider
91
+ self._state_uncertain: bool = True
92
+ self._current_firmware: str = ""
93
+ self._available_firmware: str = ""
94
+ self._update_available: bool = False
95
+ self._update_in_progress: bool = False
96
+ self._version_before_update: str | None = None
97
+
98
+ available: Final = DelegatedProperty[bool](path="_central_info.available", kind=Kind.STATE)
99
+ available_firmware: Final = DelegatedProperty[str](path="_available_firmware", kind=Kind.STATE)
100
+ current_firmware: Final = DelegatedProperty[str](path="_current_firmware", kind=Kind.STATE)
101
+ enabled_default: Final = DelegatedProperty[bool](path="_enabled_default")
102
+ full_name: Final = DelegatedProperty[str](path="_name_data.full_name")
103
+ in_progress: Final = DelegatedProperty[bool](path="_update_in_progress", kind=Kind.STATE)
104
+ name: Final = DelegatedProperty[str](path="_name_data.name", kind=Kind.CONFIG)
105
+ state_uncertain: Final = DelegatedProperty[bool](path="_state_uncertain")
106
+ update_available: Final = DelegatedProperty[bool](path="_update_available", kind=Kind.STATE)
107
+
108
+ @property
109
+ def channel(self) -> ChannelProtocol | None:
110
+ """Return the identified channel."""
111
+ return None
112
+
113
+ @property
114
+ def description(self) -> str | None:
115
+ """Return data point description."""
116
+ return None
117
+
118
+ @property
119
+ def legacy_name(self) -> str | None:
120
+ """Return the original name."""
121
+ return None
122
+
123
+ @inspector
124
+ async def install(self) -> bool:
125
+ """Trigger the firmware update process with progress monitoring."""
126
+ if client := self._primary_client_provider.primary_client:
127
+ # Store current version for progress detection
128
+ self._version_before_update = self._current_firmware
129
+ if result := await client.trigger_firmware_update():
130
+ self._update_in_progress = True
131
+ self.publish_data_point_updated_event()
132
+
133
+ # Start progress monitoring task
134
+ self._task_scheduler.create_task(
135
+ target=self._monitor_update_progress(),
136
+ name="hub_update_progress_monitor",
137
+ )
138
+
139
+ return result
140
+ return False
141
+
142
+ def update_data(self, *, data: SystemUpdateData, write_at: datetime) -> None:
143
+ """Update the data point with new system update data."""
144
+ do_update: bool = False
145
+ if self._current_firmware != data.current_firmware:
146
+ self._current_firmware = data.current_firmware
147
+ do_update = True
148
+ if self._available_firmware != data.available_firmware:
149
+ self._available_firmware = data.available_firmware
150
+ do_update = True
151
+ if self._update_available != data.update_available:
152
+ self._update_available = data.update_available
153
+ do_update = True
154
+
155
+ if do_update:
156
+ self._set_modified_at(modified_at=write_at)
157
+ else:
158
+ self._set_refreshed_at(refreshed_at=write_at)
159
+ self._state_uncertain = False
160
+ self.publish_data_point_updated_event()
161
+
162
+ def _get_path_data(self) -> PathData:
163
+ """Return the path data of the data_point."""
164
+ return HubPathData(name=slugify(_UPDATE_NAME))
165
+
166
+ def _get_signature(self) -> str:
167
+ """Return the signature of the data_point."""
168
+ return f"{self._category}/{self.name}"
169
+
170
+ async def _monitor_update_progress(self) -> None:
171
+ """Monitor update progress by polling system information."""
172
+ start_time = datetime.now()
173
+
174
+ try:
175
+ while (
176
+ datetime.now() - start_time
177
+ ).total_seconds() < self._config_provider.config.schedule_timer_config.system_update_progress_timeout:
178
+ await asyncio.sleep(
179
+ self._config_provider.config.schedule_timer_config.system_update_progress_check_interval
180
+ )
181
+
182
+ if client := self._primary_client_provider.primary_client:
183
+ try:
184
+ update_info = await client.get_system_update_info()
185
+
186
+ if update_info and update_info.current_firmware != self._version_before_update:
187
+ _LOGGER.info(
188
+ i18n.tr(
189
+ key="log.model.hub.update.progress_completed",
190
+ old_version=self._version_before_update,
191
+ new_version=update_info.current_firmware,
192
+ )
193
+ )
194
+ # Update data with new firmware info
195
+ self._current_firmware = update_info.current_firmware
196
+ self._available_firmware = update_info.available_firmware
197
+ self._update_available = update_info.update_available
198
+ # Reset circuit breakers after successful update
199
+ # to allow immediate data refresh
200
+ client.reset_circuit_breakers()
201
+ break
202
+ except Exception as err:
203
+ # CCU may be offline during reboot - continue polling
204
+ _LOGGER.debug(
205
+ i18n.tr(
206
+ key="log.model.hub.update.progress_poll_error",
207
+ error=str(err),
208
+ )
209
+ )
210
+ else:
211
+ _LOGGER.warning(
212
+ i18n.tr(
213
+ key="log.model.hub.update.progress_timeout",
214
+ timeout=self._config_provider.config.schedule_timer_config.system_update_progress_timeout,
215
+ )
216
+ )
217
+ finally:
218
+ self._update_in_progress = False
219
+ self._version_before_update = None
220
+ self._set_modified_at(modified_at=datetime.now())
221
+ self.publish_data_point_updated_event()