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,292 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
# Copyright (c) 2021-2026
|
|
3
|
+
"""
|
|
4
|
+
Metric emission utilities for event-driven metrics.
|
|
5
|
+
|
|
6
|
+
This module provides utilities for emitting metric events without
|
|
7
|
+
coupling productive code to the metrics system.
|
|
8
|
+
|
|
9
|
+
Public API
|
|
10
|
+
----------
|
|
11
|
+
- emit_latency: Emit a latency metric event
|
|
12
|
+
- emit_counter: Emit a counter metric event
|
|
13
|
+
- emit_gauge: Emit a gauge metric event
|
|
14
|
+
- emit_health: Emit a health metric event
|
|
15
|
+
- LatencyContext: Context manager for automatic latency tracking
|
|
16
|
+
- MetricEmitterMixin: Mixin class for components that emit metrics
|
|
17
|
+
|
|
18
|
+
Usage
|
|
19
|
+
-----
|
|
20
|
+
from aiohomematic.metrics import MetricKeys, emit_latency
|
|
21
|
+
|
|
22
|
+
# Emit with type-safe key
|
|
23
|
+
emit_latency(
|
|
24
|
+
event_bus=bus,
|
|
25
|
+
key=MetricKeys.ping_pong_rtt(interface_id="hmip_rf"),
|
|
26
|
+
duration_ms=42.5,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
# Emit with string key (for dynamic keys)
|
|
30
|
+
emit_counter(
|
|
31
|
+
event_bus=bus,
|
|
32
|
+
key="custom.metric.key",
|
|
33
|
+
delta=1,
|
|
34
|
+
)
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
from __future__ import annotations
|
|
38
|
+
|
|
39
|
+
from datetime import datetime
|
|
40
|
+
import time
|
|
41
|
+
from typing import TYPE_CHECKING, Protocol, Self, runtime_checkable
|
|
42
|
+
|
|
43
|
+
from aiohomematic.metrics.events import CounterMetricEvent, GaugeMetricEvent, HealthMetricEvent, LatencyMetricEvent
|
|
44
|
+
from aiohomematic.metrics.keys import MetricKey
|
|
45
|
+
|
|
46
|
+
if TYPE_CHECKING:
|
|
47
|
+
from aiohomematic.central.events import EventBus
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@runtime_checkable
|
|
51
|
+
class EventBusProviderProtocol(Protocol):
|
|
52
|
+
"""Protocol for objects that provide an EventBus."""
|
|
53
|
+
|
|
54
|
+
@property
|
|
55
|
+
def event_bus(self) -> EventBus:
|
|
56
|
+
"""Return the event bus."""
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
# =============================================================================
|
|
60
|
+
# Standalone Emission Functions
|
|
61
|
+
# =============================================================================
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def emit_latency(
|
|
65
|
+
*,
|
|
66
|
+
event_bus: EventBus,
|
|
67
|
+
key: MetricKey | str,
|
|
68
|
+
duration_ms: float,
|
|
69
|
+
) -> None:
|
|
70
|
+
"""
|
|
71
|
+
Emit a latency metric event.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
event_bus: EventBus to publish to.
|
|
75
|
+
key: Metric key (MetricKey instance or string).
|
|
76
|
+
duration_ms: Duration in milliseconds.
|
|
77
|
+
|
|
78
|
+
"""
|
|
79
|
+
event_bus.publish_sync(
|
|
80
|
+
event=LatencyMetricEvent(
|
|
81
|
+
timestamp=datetime.now(),
|
|
82
|
+
metric_key=str(key),
|
|
83
|
+
duration_ms=duration_ms,
|
|
84
|
+
)
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def emit_counter(
|
|
89
|
+
*,
|
|
90
|
+
event_bus: EventBus,
|
|
91
|
+
key: MetricKey | str,
|
|
92
|
+
delta: int = 1,
|
|
93
|
+
) -> None:
|
|
94
|
+
"""
|
|
95
|
+
Emit a counter metric event.
|
|
96
|
+
|
|
97
|
+
Args:
|
|
98
|
+
event_bus: EventBus to publish to.
|
|
99
|
+
key: Metric key (MetricKey instance or string).
|
|
100
|
+
delta: Amount to change the counter by (default: 1).
|
|
101
|
+
|
|
102
|
+
"""
|
|
103
|
+
event_bus.publish_sync(
|
|
104
|
+
event=CounterMetricEvent(
|
|
105
|
+
timestamp=datetime.now(),
|
|
106
|
+
metric_key=str(key),
|
|
107
|
+
delta=delta,
|
|
108
|
+
)
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def emit_gauge(
|
|
113
|
+
*,
|
|
114
|
+
event_bus: EventBus,
|
|
115
|
+
key: MetricKey | str,
|
|
116
|
+
value: float,
|
|
117
|
+
) -> None:
|
|
118
|
+
"""
|
|
119
|
+
Emit a gauge metric event.
|
|
120
|
+
|
|
121
|
+
Args:
|
|
122
|
+
event_bus: EventBus to publish to.
|
|
123
|
+
key: Metric key (MetricKey instance or string).
|
|
124
|
+
value: Current gauge value.
|
|
125
|
+
|
|
126
|
+
"""
|
|
127
|
+
event_bus.publish_sync(
|
|
128
|
+
event=GaugeMetricEvent(
|
|
129
|
+
timestamp=datetime.now(),
|
|
130
|
+
metric_key=str(key),
|
|
131
|
+
value=value,
|
|
132
|
+
)
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def emit_health(
|
|
137
|
+
*,
|
|
138
|
+
event_bus: EventBus,
|
|
139
|
+
key: MetricKey | str,
|
|
140
|
+
healthy: bool,
|
|
141
|
+
reason: str | None = None,
|
|
142
|
+
) -> None:
|
|
143
|
+
"""
|
|
144
|
+
Emit a health metric event.
|
|
145
|
+
|
|
146
|
+
Args:
|
|
147
|
+
event_bus: EventBus to publish to.
|
|
148
|
+
key: Metric key (MetricKey instance or string).
|
|
149
|
+
healthy: Whether the component is healthy.
|
|
150
|
+
reason: Optional reason for the state.
|
|
151
|
+
|
|
152
|
+
"""
|
|
153
|
+
event_bus.publish_sync(
|
|
154
|
+
event=HealthMetricEvent(
|
|
155
|
+
timestamp=datetime.now(),
|
|
156
|
+
metric_key=str(key),
|
|
157
|
+
healthy=healthy,
|
|
158
|
+
reason=reason,
|
|
159
|
+
)
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
# =============================================================================
|
|
164
|
+
# Context Manager for Latency Tracking
|
|
165
|
+
# =============================================================================
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
class LatencyContext:
|
|
169
|
+
"""
|
|
170
|
+
Context manager for automatic latency tracking.
|
|
171
|
+
|
|
172
|
+
Usage:
|
|
173
|
+
with LatencyContext(
|
|
174
|
+
event_bus=bus,
|
|
175
|
+
key=MetricKeys.handler_execution(event_type="MyEvent"),
|
|
176
|
+
):
|
|
177
|
+
# ... do work ...
|
|
178
|
+
# Latency event emitted automatically on exit
|
|
179
|
+
"""
|
|
180
|
+
|
|
181
|
+
__slots__ = ("_event_bus", "_key", "_start_time")
|
|
182
|
+
|
|
183
|
+
def __init__(
|
|
184
|
+
self,
|
|
185
|
+
*,
|
|
186
|
+
event_bus: EventBus | None,
|
|
187
|
+
key: MetricKey | str,
|
|
188
|
+
) -> None:
|
|
189
|
+
"""Initialize the context."""
|
|
190
|
+
self._event_bus = event_bus
|
|
191
|
+
self._key = key
|
|
192
|
+
self._start_time: float = 0.0
|
|
193
|
+
|
|
194
|
+
def __enter__(self) -> Self:
|
|
195
|
+
"""Start timing."""
|
|
196
|
+
self._start_time = time.monotonic()
|
|
197
|
+
return self
|
|
198
|
+
|
|
199
|
+
def __exit__( # kwonly: disable
|
|
200
|
+
self, exc_type: type[BaseException] | None, exc_val: BaseException | None, exc_tb: object
|
|
201
|
+
) -> None:
|
|
202
|
+
"""Emit latency event."""
|
|
203
|
+
if self._event_bus is None:
|
|
204
|
+
return
|
|
205
|
+
duration_ms = (time.monotonic() - self._start_time) * 1000
|
|
206
|
+
emit_latency(
|
|
207
|
+
event_bus=self._event_bus,
|
|
208
|
+
key=self._key,
|
|
209
|
+
duration_ms=duration_ms,
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
# =============================================================================
|
|
214
|
+
# Mixin Class
|
|
215
|
+
# =============================================================================
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
class MetricEmitterMixin:
|
|
219
|
+
"""
|
|
220
|
+
Mixin class for components that emit metrics.
|
|
221
|
+
|
|
222
|
+
Components using this mixin must have:
|
|
223
|
+
- _event_bus_provider: EventBusProviderProtocol (or _event_bus: EventBus)
|
|
224
|
+
|
|
225
|
+
The mixin provides protected methods for emitting each metric type.
|
|
226
|
+
These methods are no-ops if no EventBus is available, making metrics
|
|
227
|
+
collection truly optional.
|
|
228
|
+
"""
|
|
229
|
+
|
|
230
|
+
# This will be provided by the implementing class
|
|
231
|
+
_event_bus_provider: EventBusProviderProtocol | None
|
|
232
|
+
|
|
233
|
+
def _emit_counter(self, *, key: MetricKey | str, delta: int = 1) -> None:
|
|
234
|
+
"""
|
|
235
|
+
Emit a counter metric event.
|
|
236
|
+
|
|
237
|
+
Args:
|
|
238
|
+
key: Metric key (MetricKey instance or string).
|
|
239
|
+
delta: Amount to change the counter by.
|
|
240
|
+
|
|
241
|
+
"""
|
|
242
|
+
if (bus := self._get_event_bus()) is None:
|
|
243
|
+
return
|
|
244
|
+
emit_counter(event_bus=bus, key=key, delta=delta)
|
|
245
|
+
|
|
246
|
+
def _emit_gauge(self, *, key: MetricKey | str, value: float) -> None:
|
|
247
|
+
"""
|
|
248
|
+
Emit a gauge metric event.
|
|
249
|
+
|
|
250
|
+
Args:
|
|
251
|
+
key: Metric key (MetricKey instance or string).
|
|
252
|
+
value: Current gauge value.
|
|
253
|
+
|
|
254
|
+
"""
|
|
255
|
+
if (bus := self._get_event_bus()) is None:
|
|
256
|
+
return
|
|
257
|
+
emit_gauge(event_bus=bus, key=key, value=value)
|
|
258
|
+
|
|
259
|
+
def _emit_health(self, *, key: MetricKey | str, healthy: bool, reason: str | None = None) -> None:
|
|
260
|
+
"""
|
|
261
|
+
Emit a health metric event.
|
|
262
|
+
|
|
263
|
+
Args:
|
|
264
|
+
key: Metric key (MetricKey instance or string).
|
|
265
|
+
healthy: Whether the component is healthy.
|
|
266
|
+
reason: Optional reason for the state.
|
|
267
|
+
|
|
268
|
+
"""
|
|
269
|
+
if (bus := self._get_event_bus()) is None:
|
|
270
|
+
return
|
|
271
|
+
emit_health(event_bus=bus, key=key, healthy=healthy, reason=reason)
|
|
272
|
+
|
|
273
|
+
def _emit_latency(self, *, key: MetricKey | str, duration_ms: float) -> None:
|
|
274
|
+
"""
|
|
275
|
+
Emit a latency metric event.
|
|
276
|
+
|
|
277
|
+
Args:
|
|
278
|
+
key: Metric key (MetricKey instance or string).
|
|
279
|
+
duration_ms: Duration in milliseconds.
|
|
280
|
+
|
|
281
|
+
"""
|
|
282
|
+
if (bus := self._get_event_bus()) is None:
|
|
283
|
+
return
|
|
284
|
+
emit_latency(event_bus=bus, key=key, duration_ms=duration_ms)
|
|
285
|
+
|
|
286
|
+
def _get_event_bus(self) -> EventBus | None:
|
|
287
|
+
"""Get the EventBus if available."""
|
|
288
|
+
if hasattr(self, "_event_bus_provider") and self._event_bus_provider is not None:
|
|
289
|
+
return self._event_bus_provider.event_bus
|
|
290
|
+
if hasattr(self, "_event_bus"):
|
|
291
|
+
return getattr(self, "_event_bus", None)
|
|
292
|
+
return None
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
# Copyright (c) 2021-2026
|
|
3
|
+
"""
|
|
4
|
+
Metric event types for event-driven metrics collection.
|
|
5
|
+
|
|
6
|
+
This module defines the event hierarchy for metrics emission. Components emit
|
|
7
|
+
these events to the EventBus, where MetricsObserver aggregates them.
|
|
8
|
+
|
|
9
|
+
Event Types
|
|
10
|
+
-----------
|
|
11
|
+
- LatencyMetricEvent: For timing measurements (RPC calls, ping/pong, handlers)
|
|
12
|
+
- CounterMetricEvent: For countable events (cache hits, failures, successes)
|
|
13
|
+
- GaugeMetricEvent: For current-value metrics (queue depth, connection count)
|
|
14
|
+
- HealthMetricEvent: For health state changes (client health, circuit breaker)
|
|
15
|
+
|
|
16
|
+
Usage
|
|
17
|
+
-----
|
|
18
|
+
from aiohomematic.metrics import MetricKeys, emit_latency
|
|
19
|
+
|
|
20
|
+
# Emit latency metric with type-safe key
|
|
21
|
+
emit_latency(
|
|
22
|
+
event_bus=event_bus,
|
|
23
|
+
key=MetricKeys.ping_pong_rtt(interface_id="hmip_rf"),
|
|
24
|
+
duration_ms=45.2,
|
|
25
|
+
)
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
from __future__ import annotations
|
|
29
|
+
|
|
30
|
+
from dataclasses import dataclass
|
|
31
|
+
from enum import Enum
|
|
32
|
+
from typing import Final
|
|
33
|
+
|
|
34
|
+
from aiohomematic.central.events.types import Event
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class MetricType(Enum):
|
|
38
|
+
"""Type of metric for categorization."""
|
|
39
|
+
|
|
40
|
+
LATENCY = "latency"
|
|
41
|
+
COUNTER = "counter"
|
|
42
|
+
GAUGE = "gauge"
|
|
43
|
+
HEALTH = "health"
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@dataclass(frozen=True, slots=True)
|
|
47
|
+
class MetricEvent(Event):
|
|
48
|
+
"""
|
|
49
|
+
Base class for all metric events.
|
|
50
|
+
|
|
51
|
+
All metric events share a common metric_key field that identifies
|
|
52
|
+
the metric. The key follows the pattern: {component}.{metric}.{identifier}
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
metric_key: str
|
|
56
|
+
"""
|
|
57
|
+
Full metric key identifying this metric.
|
|
58
|
+
|
|
59
|
+
Pattern: {component}.{metric}.{identifier}
|
|
60
|
+
Examples: "ping_pong.rtt.hmip_rf", "cache.data.hit", "handler.execution.DataPointValueReceivedEvent"
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
@property
|
|
64
|
+
def key(self) -> str:
|
|
65
|
+
"""Return the event key for EventBus routing."""
|
|
66
|
+
return self.metric_key
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@dataclass(frozen=True, slots=True)
|
|
70
|
+
class LatencyMetricEvent(MetricEvent):
|
|
71
|
+
"""
|
|
72
|
+
Event for timing measurements.
|
|
73
|
+
|
|
74
|
+
Emitted when an operation completes to record its duration.
|
|
75
|
+
Used for RPC calls, ping/pong round-trips, handler execution, etc.
|
|
76
|
+
"""
|
|
77
|
+
|
|
78
|
+
duration_ms: float = 0.0
|
|
79
|
+
"""Duration of the operation in milliseconds."""
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@dataclass(frozen=True, slots=True)
|
|
83
|
+
class CounterMetricEvent(MetricEvent):
|
|
84
|
+
"""
|
|
85
|
+
Event for countable occurrences.
|
|
86
|
+
|
|
87
|
+
Emitted when a countable event occurs (cache hit, RPC success, failure, etc.).
|
|
88
|
+
"""
|
|
89
|
+
|
|
90
|
+
delta: int = 1
|
|
91
|
+
"""Amount to change the counter by (default: 1)."""
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
@dataclass(frozen=True, slots=True)
|
|
95
|
+
class GaugeMetricEvent(MetricEvent):
|
|
96
|
+
"""
|
|
97
|
+
Event for current-value metrics.
|
|
98
|
+
|
|
99
|
+
Emitted when a gauge value changes (queue depth, connection count, etc.).
|
|
100
|
+
Unlike counters, gauges represent the current state, not a delta.
|
|
101
|
+
"""
|
|
102
|
+
|
|
103
|
+
value: float = 0.0
|
|
104
|
+
"""Current value of the gauge."""
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
@dataclass(frozen=True, slots=True)
|
|
108
|
+
class HealthMetricEvent(MetricEvent):
|
|
109
|
+
"""
|
|
110
|
+
Event for health state changes.
|
|
111
|
+
|
|
112
|
+
Emitted when a component's health state changes.
|
|
113
|
+
"""
|
|
114
|
+
|
|
115
|
+
healthy: bool = True
|
|
116
|
+
"""Whether the component is healthy."""
|
|
117
|
+
|
|
118
|
+
reason: str | None = None
|
|
119
|
+
"""Optional reason for the health state (especially for unhealthy)."""
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
# =============================================================================
|
|
123
|
+
# Self-Healing Events
|
|
124
|
+
# =============================================================================
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
@dataclass(frozen=True, slots=True)
|
|
128
|
+
class SelfHealingTriggeredEvent(Event):
|
|
129
|
+
"""
|
|
130
|
+
Emitted when self-healing coordinator reacts to a circuit breaker event.
|
|
131
|
+
|
|
132
|
+
Key is interface_id.
|
|
133
|
+
|
|
134
|
+
This event allows external observers (like MetricsAggregator) to track
|
|
135
|
+
self-healing activity without the coordinator maintaining internal stats.
|
|
136
|
+
"""
|
|
137
|
+
|
|
138
|
+
interface_id: str
|
|
139
|
+
action: str # "trip_logged", "recovery_initiated", "data_refresh_scheduled"
|
|
140
|
+
details: str | None
|
|
141
|
+
|
|
142
|
+
@property
|
|
143
|
+
def key(self) -> str:
|
|
144
|
+
"""Return interface_id as key."""
|
|
145
|
+
return self.interface_id
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
@dataclass(frozen=True, slots=True)
|
|
149
|
+
class SelfHealingDataRefreshEvent(Event):
|
|
150
|
+
"""
|
|
151
|
+
Emitted after self-healing data refresh completes.
|
|
152
|
+
|
|
153
|
+
Key is interface_id.
|
|
154
|
+
|
|
155
|
+
Allows subscribers to track data refresh success/failure.
|
|
156
|
+
"""
|
|
157
|
+
|
|
158
|
+
interface_id: str
|
|
159
|
+
success: bool
|
|
160
|
+
error_message: str | None
|
|
161
|
+
|
|
162
|
+
@property
|
|
163
|
+
def key(self) -> str:
|
|
164
|
+
"""Return interface_id as key."""
|
|
165
|
+
return self.interface_id
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
# Type alias for any metric event
|
|
169
|
+
AnyMetricEvent = LatencyMetricEvent | CounterMetricEvent | GaugeMetricEvent | HealthMetricEvent
|
|
170
|
+
|
|
171
|
+
# All metric event types for subscription
|
|
172
|
+
METRIC_EVENT_TYPES: Final = (
|
|
173
|
+
LatencyMetricEvent,
|
|
174
|
+
CounterMetricEvent,
|
|
175
|
+
GaugeMetricEvent,
|
|
176
|
+
HealthMetricEvent,
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
# Self-healing event types for subscription
|
|
180
|
+
SELF_HEALING_EVENT_TYPES: Final = (
|
|
181
|
+
SelfHealingTriggeredEvent,
|
|
182
|
+
SelfHealingDataRefreshEvent,
|
|
183
|
+
)
|