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,172 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2021-2026
3
+ """
4
+ Mutable statistics classes for metrics tracking.
5
+
6
+ This module provides mutable dataclasses for tracking runtime statistics.
7
+ These are used by components to record metrics which are then aggregated.
8
+
9
+ Public API
10
+ ----------
11
+ - CacheStats: Cache hit/miss/size statistics
12
+ - LatencyStats: Request latency statistics (count, min, max, avg)
13
+ - ServiceStats: Service method execution statistics (call count, errors, timing)
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ from dataclasses import dataclass
19
+ import math
20
+
21
+
22
+ @dataclass(slots=True)
23
+ class SizeOnlyStats:
24
+ """
25
+ Size-only statistics for registries and trackers.
26
+
27
+ Used for components that are not true caches (no hit/miss semantics):
28
+ - DeviceDescriptionRegistry (authoritative store)
29
+ - ParamsetDescriptionRegistry (authoritative store)
30
+ - ParameterVisibilityRegistry (rule engine with memoization)
31
+ - PingPongTracker (connection health tracker)
32
+ - CommandTracker (sent command tracker)
33
+ """
34
+
35
+ size: int = 0
36
+ """Current number of entries."""
37
+
38
+ evictions: int = 0
39
+ """Number of entries removed (for memory management, not cache semantics)."""
40
+
41
+
42
+ @dataclass(slots=True)
43
+ class CacheStats:
44
+ """
45
+ Statistics for cache performance monitoring.
46
+
47
+ Used for true caches with hit/miss semantics:
48
+ - CentralDataCache
49
+ """
50
+
51
+ hits: int = 0
52
+ """Number of successful cache lookups."""
53
+
54
+ misses: int = 0
55
+ """Number of cache misses."""
56
+
57
+ size: int = 0
58
+ """Current number of entries in the cache."""
59
+
60
+ evictions: int = 0
61
+ """Number of entries evicted from the cache."""
62
+
63
+ @property
64
+ def hit_rate(self) -> float:
65
+ """Return hit rate as percentage."""
66
+ if (total := self.hits + self.misses) == 0:
67
+ return 100.0
68
+ return (self.hits / total) * 100
69
+
70
+ def record_hit(self) -> None:
71
+ """Record a cache hit."""
72
+ self.hits += 1
73
+
74
+ def record_miss(self) -> None:
75
+ """Record a cache miss."""
76
+ self.misses += 1
77
+
78
+ def reset(self) -> None:
79
+ """Reset cache statistics."""
80
+ self.hits = 0
81
+ self.misses = 0
82
+ self.size = 0
83
+ self.evictions = 0
84
+
85
+
86
+ @dataclass(slots=True)
87
+ class LatencyStats:
88
+ """Statistics for request latency tracking."""
89
+
90
+ count: int = 0
91
+ """Number of latency samples."""
92
+
93
+ total_ms: float = 0.0
94
+ """Total latency in milliseconds."""
95
+
96
+ min_ms: float = math.inf
97
+ """Minimum latency in milliseconds."""
98
+
99
+ max_ms: float = 0.0
100
+ """Maximum latency in milliseconds."""
101
+
102
+ @property
103
+ def avg_ms(self) -> float:
104
+ """Return average latency in milliseconds."""
105
+ if self.count == 0:
106
+ return 0.0
107
+ return self.total_ms / self.count
108
+
109
+ def record(self, *, duration_ms: float) -> None:
110
+ """Record a latency sample."""
111
+ self.count += 1
112
+ self.total_ms += duration_ms
113
+ self.min_ms = min(self.min_ms, duration_ms)
114
+ self.max_ms = max(self.max_ms, duration_ms)
115
+
116
+ def reset(self) -> None:
117
+ """Reset latency statistics."""
118
+ self.count = 0
119
+ self.total_ms = 0.0
120
+ self.min_ms = math.inf
121
+ self.max_ms = 0.0
122
+
123
+
124
+ @dataclass(slots=True)
125
+ class ServiceStats:
126
+ """
127
+ Statistics for service method execution tracking.
128
+
129
+ This class tracks call counts, errors, and timing for service methods
130
+ decorated with @inspector(measure_performance=True).
131
+ """
132
+
133
+ call_count: int = 0
134
+ """Total number of calls to this method."""
135
+
136
+ error_count: int = 0
137
+ """Number of calls that raised exceptions."""
138
+
139
+ total_duration_ms: float = 0.0
140
+ """Total execution time in milliseconds."""
141
+
142
+ max_duration_ms: float = 0.0
143
+ """Maximum execution time in milliseconds."""
144
+
145
+ @property
146
+ def avg_duration_ms(self) -> float:
147
+ """Return average execution time in milliseconds."""
148
+ if self.call_count == 0:
149
+ return 0.0
150
+ return self.total_duration_ms / self.call_count
151
+
152
+ @property
153
+ def error_rate(self) -> float:
154
+ """Return error rate as percentage."""
155
+ if self.call_count == 0:
156
+ return 0.0
157
+ return (self.error_count / self.call_count) * 100
158
+
159
+ def record(self, *, duration_ms: float, had_error: bool) -> None:
160
+ """Record a service call."""
161
+ self.call_count += 1
162
+ self.total_duration_ms += duration_ms
163
+ self.max_duration_ms = max(self.max_duration_ms, duration_ms)
164
+ if had_error:
165
+ self.error_count += 1
166
+
167
+ def reset(self) -> None:
168
+ """Reset all statistics."""
169
+ self.call_count = 0
170
+ self.error_count = 0
171
+ self.total_duration_ms = 0.0
172
+ self.max_duration_ms = 0.0
@@ -0,0 +1,189 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2021-2026
3
+ """
4
+ Data point and event model for AioHomematic.
5
+
6
+ Overview
7
+ --------
8
+ This package provides the runtime model layer that transforms device and channel
9
+ parameter descriptions from Homematic backends into typed data point objects and
10
+ events. It orchestrates the creation of data point hierarchies (devices, channels,
11
+ data points) and manages their lifecycle.
12
+
13
+ The model layer is purely domain-focused with no I/O operations. All backend
14
+ communication is delegated to the client layer through protocol interfaces.
15
+
16
+ Subpackages
17
+ -----------
18
+ The model is organized into specialized data point types:
19
+
20
+ - **generic**: Default data point implementations (switch, number, sensor, select,
21
+ binary_sensor, button, action, text) for standard parameter types.
22
+ - **custom**: Device-specific implementations providing higher-level abstractions
23
+ (climate, cover, light, lock, siren, valve) for complex multi-parameter devices.
24
+ - **calculated**: Derived data points computing values from other data points
25
+ (e.g., dew point, apparent temperature, battery level percentage).
26
+ - **hub**: Backend system data points including programs and system variables exposed
27
+ by the CCU/Homegear hub.
28
+
29
+ Public API
30
+ ----------
31
+ - `create_data_points_and_events`: Main factory function for populating device
32
+ channels with data points and events based on paramset descriptions.
33
+
34
+ Workflow
35
+ --------
36
+ During device initialization, `create_data_points_and_events` is invoked for each
37
+ device. It performs the following steps:
38
+
39
+ 1. Iterates through all device channels and their paramset descriptions.
40
+ 2. Applies visibility rules to filter relevant parameters.
41
+ 3. Creates event objects for parameters supporting EVENT operations.
42
+ 4. Creates appropriate data point instances (generic or custom).
43
+ 5. Instantiates calculated data points based on available source data points.
44
+
45
+ The resulting data point objects are registered with their parent channels and
46
+ become accessible through the central unit's query API.
47
+
48
+ Notes
49
+ -----
50
+ The entrypoint function is decorated with `@inspector` for automatic exception
51
+ handling and logging. All data point creation follows the factory pattern with
52
+ type selection based on parameter metadata and device profiles.
53
+
54
+ """
55
+
56
+ from __future__ import annotations
57
+
58
+ from collections.abc import Mapping
59
+ import logging
60
+ from typing import Final
61
+
62
+ from aiohomematic.const import (
63
+ CLICK_EVENTS,
64
+ DEVICE_ERROR_EVENTS,
65
+ IMPULSE_EVENTS,
66
+ Field,
67
+ Flag,
68
+ Operations,
69
+ Parameter,
70
+ ParameterData,
71
+ ParamsetKey,
72
+ ServiceScope,
73
+ )
74
+ from aiohomematic.decorators import inspector
75
+ from aiohomematic.interfaces.model import ChannelProtocol, DeviceProtocol
76
+ from aiohomematic.model.availability import AvailabilityInfo
77
+ from aiohomematic.model.calculated import create_calculated_data_points
78
+ from aiohomematic.model.event import create_event_and_append_to_channel
79
+ from aiohomematic.model.generic import create_data_point_and_append_to_channel
80
+
81
+ __all__ = [
82
+ # Data classes
83
+ "AvailabilityInfo",
84
+ # Factory
85
+ "create_data_points_and_events",
86
+ ]
87
+
88
+ # Some parameters are marked as INTERNAL in the paramset and not considered by default,
89
+ # but some are required and should be added here.
90
+ _ALLOWED_INTERNAL_PARAMETERS: Final[Mapping[Field, Parameter]] = {
91
+ Field.DIRECTION: Parameter.DIRECTION,
92
+ Field.ON_TIME_LIST: Parameter.ON_TIME_LIST_1,
93
+ Field.REPETITIONS: Parameter.REPETITIONS,
94
+ }
95
+ _LOGGER: Final = logging.getLogger(__name__)
96
+
97
+
98
+ @inspector(scope=ServiceScope.INTERNAL)
99
+ def create_data_points_and_events(*, device: DeviceProtocol) -> None:
100
+ """Create the data points associated to this device."""
101
+ for channel in device.channels.values():
102
+ for paramset_key, paramsset_key_descriptions in channel.paramset_descriptions.items():
103
+ if not device.parameter_visibility_provider.is_relevant_paramset(
104
+ channel=channel,
105
+ paramset_key=paramset_key,
106
+ ):
107
+ continue
108
+ for (
109
+ parameter,
110
+ parameter_data,
111
+ ) in paramsset_key_descriptions.items():
112
+ parameter_is_un_ignored = channel.device.parameter_visibility_provider.parameter_is_un_ignored(
113
+ channel=channel,
114
+ paramset_key=paramset_key,
115
+ parameter=parameter,
116
+ )
117
+ if channel.device.parameter_visibility_provider.should_skip_parameter(
118
+ channel=channel,
119
+ paramset_key=paramset_key,
120
+ parameter=parameter,
121
+ parameter_is_un_ignored=parameter_is_un_ignored,
122
+ ):
123
+ continue
124
+ _process_parameter(
125
+ channel=channel,
126
+ paramset_key=paramset_key,
127
+ parameter=parameter,
128
+ parameter_data=parameter_data,
129
+ parameter_is_un_ignored=parameter_is_un_ignored,
130
+ )
131
+
132
+ create_calculated_data_points(channel=channel)
133
+
134
+
135
+ def _process_parameter(
136
+ *,
137
+ channel: ChannelProtocol,
138
+ paramset_key: ParamsetKey,
139
+ parameter: str,
140
+ parameter_data: ParameterData,
141
+ parameter_is_un_ignored: bool,
142
+ ) -> None:
143
+ """Process individual parameter to create data points and events."""
144
+ if paramset_key == ParamsetKey.MASTER and parameter_data["OPERATIONS"] == 0:
145
+ # required to fix hm master paramset operation values
146
+ parameter_data["OPERATIONS"] = 3
147
+
148
+ if _should_create_event(parameter_data=parameter_data, parameter=parameter):
149
+ create_event_and_append_to_channel(
150
+ channel=channel,
151
+ parameter=parameter,
152
+ parameter_data=parameter_data,
153
+ )
154
+ if _should_skip_data_point(
155
+ parameter_data=parameter_data, parameter=parameter, parameter_is_un_ignored=parameter_is_un_ignored
156
+ ):
157
+ _LOGGER.debug(
158
+ "CREATE_DATA_POINTS: Skipping %s (no event or internal)",
159
+ parameter,
160
+ )
161
+ return
162
+ # CLICK_EVENTS are allowed for Buttons
163
+ if parameter not in IMPULSE_EVENTS and (not parameter.startswith(DEVICE_ERROR_EVENTS) or parameter_is_un_ignored):
164
+ create_data_point_and_append_to_channel(
165
+ channel=channel,
166
+ paramset_key=paramset_key,
167
+ parameter=parameter,
168
+ parameter_data=parameter_data,
169
+ )
170
+
171
+
172
+ def _should_create_event(*, parameter_data: ParameterData, parameter: str) -> bool:
173
+ """Determine if an event should be created for the parameter."""
174
+ return bool(
175
+ parameter_data["OPERATIONS"] & Operations.EVENT
176
+ and (parameter in CLICK_EVENTS or parameter.startswith(DEVICE_ERROR_EVENTS) or parameter in IMPULSE_EVENTS)
177
+ )
178
+
179
+
180
+ def _should_skip_data_point(*, parameter_data: ParameterData, parameter: str, parameter_is_un_ignored: bool) -> bool:
181
+ """Determine if a data point should be skipped."""
182
+ return bool(
183
+ (not parameter_data["OPERATIONS"] & Operations.EVENT and not parameter_data["OPERATIONS"] & Operations.WRITE)
184
+ or (
185
+ parameter_data["FLAGS"] & Flag.INTERNAL
186
+ and parameter not in _ALLOWED_INTERNAL_PARAMETERS.values()
187
+ and not parameter_is_un_ignored
188
+ )
189
+ )
@@ -0,0 +1,65 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2021-2026
3
+ """
4
+ Availability information for Homematic devices.
5
+
6
+ This module provides the AvailabilityInfo dataclass that bundles
7
+ device reachability, battery state, and signal strength information
8
+ into a single unified view for external consumers.
9
+
10
+ Public API of this module is defined by __all__.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ from dataclasses import dataclass
16
+ from datetime import datetime
17
+ from typing import Final
18
+
19
+ __all__: Final = ["AvailabilityInfo"]
20
+
21
+
22
+ @dataclass(frozen=True, slots=True)
23
+ class AvailabilityInfo:
24
+ """
25
+ Bundled availability information for a Homematic device.
26
+
27
+ Provides a unified view of device connectivity and health status,
28
+ combining reachability, battery state, and signal strength.
29
+
30
+ This dataclass is immutable (frozen) to ensure thread-safety when
31
+ passed between components.
32
+
33
+ Example:
34
+ >>> info = device.availability
35
+ >>> if not info.is_reachable:
36
+ ... print(f"Device unreachable since {info.last_updated}")
37
+ >>> if info.low_battery:
38
+ ... print(f"Battery low: {info.battery_level}%")
39
+
40
+ """
41
+
42
+ is_reachable: bool
43
+ """Device is reachable (inverse of UNREACH parameter)."""
44
+
45
+ last_updated: datetime | None
46
+ """Most recent data point modification time across all channels."""
47
+
48
+ battery_level: int | None = None
49
+ """Battery level percentage (0-100), from OperatingVoltageLevel or BATTERY_STATE."""
50
+
51
+ low_battery: bool | None = None
52
+ """Low battery indicator from LOW_BAT parameter."""
53
+
54
+ signal_strength: int | None = None
55
+ """Signal strength in dBm from RSSI_DEVICE (negative values, e.g., -65)."""
56
+
57
+ @property
58
+ def has_battery(self) -> bool:
59
+ """Return True if any battery information is available."""
60
+ return self.battery_level is not None or self.low_battery is not None
61
+
62
+ @property
63
+ def has_signal_info(self) -> bool:
64
+ """Return True if signal strength information is available."""
65
+ return self.signal_strength is not None
@@ -0,0 +1,89 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2021-2026
3
+ """
4
+ Calculated (derived) data points for AioHomematic.
5
+
6
+ This subpackage provides data points whose values are computed from one or more
7
+ underlying device data points. Typical examples include climate-related metrics
8
+ (such as dew point, apparent temperature, frost point, vapor concentration) and
9
+ battery/voltage related assessments (such as operating voltage level).
10
+
11
+ How it works:
12
+ - Each calculated data point is a lightweight model that subscribes to one or
13
+ more generic data points of a channel and recomputes its value when any of
14
+ the source data points change.
15
+ - Relevance is determined per channel. A calculated data point class exposes an
16
+ "is_relevant_for_model" method that decides if the channel provides the
17
+ necessary inputs.
18
+ - Creation is handled centrally via the factory function below.
19
+
20
+ Factory:
21
+ - create_calculated_data_points(channel): Iterates over the known calculated
22
+ implementations, checks their relevance against the given channel, and, if
23
+ applicable, creates and attaches instances to the channel so they behave like
24
+ normal read-only data points.
25
+
26
+ Modules/classes:
27
+ - ApparentTemperature, DewPoint, DewPointSpread, Enthalphy, FrostPoint, VaporConcentration: Climate-related
28
+ sensors implemented in climate.py using well-known formulas (see
29
+ aiohomematic.model.calculated.support for details and references).
30
+ - OperatingVoltageLevel: Interprets battery/voltage values and exposes a human
31
+ readable operating level classification.
32
+
33
+ These calculated data points complement generic and custom data points by
34
+ exposing useful metrics not directly provided by the device/firmware.
35
+ """
36
+
37
+ from __future__ import annotations
38
+
39
+ import logging
40
+ from typing import Final
41
+
42
+ from aiohomematic.const import ServiceScope
43
+ from aiohomematic.decorators import inspector
44
+ from aiohomematic.interfaces.model import ChannelProtocol
45
+ from aiohomematic.model.calculated.climate import (
46
+ ApparentTemperature,
47
+ DewPoint,
48
+ DewPointSpread,
49
+ Enthalpy,
50
+ FrostPoint,
51
+ VaporConcentration,
52
+ )
53
+ from aiohomematic.model.calculated.data_point import CalculatedDataPoint
54
+ from aiohomematic.model.calculated.operating_voltage_level import OperatingVoltageLevel
55
+
56
+ __all__ = [
57
+ # Base
58
+ "CalculatedDataPoint",
59
+ # Climate
60
+ "ApparentTemperature",
61
+ "DewPoint",
62
+ "DewPointSpread",
63
+ "Enthalpy",
64
+ "FrostPoint",
65
+ "VaporConcentration",
66
+ # Factory
67
+ "create_calculated_data_points",
68
+ # Voltage
69
+ "OperatingVoltageLevel",
70
+ ]
71
+
72
+ _CALCULATED_DATA_POINTS: Final = (
73
+ ApparentTemperature,
74
+ DewPoint,
75
+ DewPointSpread,
76
+ Enthalpy,
77
+ FrostPoint,
78
+ OperatingVoltageLevel,
79
+ VaporConcentration,
80
+ )
81
+ _LOGGER: Final = logging.getLogger(__name__)
82
+
83
+
84
+ @inspector(scope=ServiceScope.INTERNAL)
85
+ def create_calculated_data_points(*, channel: ChannelProtocol) -> None:
86
+ """Decides which data point category should be used, and creates the required data points."""
87
+ for dp in _CALCULATED_DATA_POINTS:
88
+ if dp.is_relevant_for_model(channel=channel):
89
+ channel.add_data_point(data_point=dp(channel=channel))