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,217 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
# Copyright (c) 2021-2026
|
|
3
|
+
"""
|
|
4
|
+
Operation protocol interfaces.
|
|
5
|
+
|
|
6
|
+
This module defines protocol interfaces for operational tasks like
|
|
7
|
+
scheduling, caching, and parameter visibility management.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from abc import abstractmethod
|
|
13
|
+
import asyncio
|
|
14
|
+
from collections.abc import Callable, Mapping
|
|
15
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
16
|
+
from typing import TYPE_CHECKING, Any, Protocol, runtime_checkable
|
|
17
|
+
|
|
18
|
+
from aiohomematic.const import DeviceDescription, Interface, ParameterData, ParamsetKey
|
|
19
|
+
from aiohomematic.type_aliases import AsyncTaskFactoryAny, CoroutineAny
|
|
20
|
+
|
|
21
|
+
if TYPE_CHECKING:
|
|
22
|
+
from aiohomematic.interfaces import ChannelProtocol
|
|
23
|
+
from aiohomematic.store import CacheName, CacheStatistics
|
|
24
|
+
from aiohomematic.store.types import IncidentSeverity, IncidentSnapshot, IncidentType, PingPongJournal
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@runtime_checkable
|
|
28
|
+
class TaskSchedulerProtocol(Protocol):
|
|
29
|
+
"""
|
|
30
|
+
Protocol for scheduling async tasks.
|
|
31
|
+
|
|
32
|
+
Implemented by Looper.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
@abstractmethod
|
|
36
|
+
def async_add_executor_job[T](
|
|
37
|
+
self, target: Callable[..., T], *args: Any, name: str, executor: ThreadPoolExecutor | None = None
|
|
38
|
+
) -> asyncio.Future[T]:
|
|
39
|
+
"""Add an executor job from within the event_loop."""
|
|
40
|
+
|
|
41
|
+
@abstractmethod
|
|
42
|
+
async def block_till_done(self, *, wait_time: float | None = None) -> None:
|
|
43
|
+
"""Block until all pending work is done."""
|
|
44
|
+
|
|
45
|
+
@abstractmethod
|
|
46
|
+
def cancel_tasks(self) -> None:
|
|
47
|
+
"""Cancel running tasks."""
|
|
48
|
+
|
|
49
|
+
@abstractmethod
|
|
50
|
+
def create_task(self, *, target: CoroutineAny | AsyncTaskFactoryAny, name: str) -> None:
|
|
51
|
+
"""Create and schedule an async task."""
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@runtime_checkable
|
|
55
|
+
class ParameterVisibilityProviderProtocol(Protocol):
|
|
56
|
+
"""
|
|
57
|
+
Protocol for accessing parameter visibility information.
|
|
58
|
+
|
|
59
|
+
Implemented by ParameterVisibilityRegistry.
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
@abstractmethod
|
|
63
|
+
def is_relevant_paramset(self, *, channel: ChannelProtocol, paramset_key: ParamsetKey) -> bool:
|
|
64
|
+
"""
|
|
65
|
+
Return if a paramset is relevant.
|
|
66
|
+
|
|
67
|
+
Required to load MASTER paramsets, which are not initialized by default.
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
@abstractmethod
|
|
71
|
+
def model_is_ignored(self, *, model: str) -> bool:
|
|
72
|
+
"""Check if a model should be ignored for custom data points."""
|
|
73
|
+
|
|
74
|
+
@abstractmethod
|
|
75
|
+
def parameter_is_hidden(self, *, channel: ChannelProtocol, paramset_key: ParamsetKey, parameter: str) -> bool:
|
|
76
|
+
"""Check if a parameter is hidden."""
|
|
77
|
+
|
|
78
|
+
@abstractmethod
|
|
79
|
+
def parameter_is_un_ignored(
|
|
80
|
+
self, *, channel: ChannelProtocol, paramset_key: ParamsetKey, parameter: str, custom_only: bool = False
|
|
81
|
+
) -> bool:
|
|
82
|
+
"""Check if a parameter is un-ignored (visible)."""
|
|
83
|
+
|
|
84
|
+
@abstractmethod
|
|
85
|
+
def should_skip_parameter(
|
|
86
|
+
self, *, channel: ChannelProtocol, paramset_key: ParamsetKey, parameter: str, parameter_is_un_ignored: bool
|
|
87
|
+
) -> bool:
|
|
88
|
+
"""Determine if a parameter should be skipped."""
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
@runtime_checkable
|
|
92
|
+
class DeviceDetailsProviderProtocol(Protocol):
|
|
93
|
+
"""
|
|
94
|
+
Protocol for accessing device details.
|
|
95
|
+
|
|
96
|
+
Implemented by DeviceDescriptionRegistry.
|
|
97
|
+
"""
|
|
98
|
+
|
|
99
|
+
@abstractmethod
|
|
100
|
+
def get_address_id(self, *, address: str) -> int:
|
|
101
|
+
"""Get an ID for an address."""
|
|
102
|
+
|
|
103
|
+
@abstractmethod
|
|
104
|
+
def get_channel_rooms(self, *, channel_address: str) -> set[str]:
|
|
105
|
+
"""Get rooms for a channel."""
|
|
106
|
+
|
|
107
|
+
@abstractmethod
|
|
108
|
+
def get_device_rooms(self, *, device_address: str) -> set[str]:
|
|
109
|
+
"""Get rooms for a device."""
|
|
110
|
+
|
|
111
|
+
@abstractmethod
|
|
112
|
+
def get_function_text(self, *, address: str) -> str | None:
|
|
113
|
+
"""Get function text for an address."""
|
|
114
|
+
|
|
115
|
+
@abstractmethod
|
|
116
|
+
def get_interface(self, *, address: str) -> Interface:
|
|
117
|
+
"""Get interface for an address."""
|
|
118
|
+
|
|
119
|
+
@abstractmethod
|
|
120
|
+
def get_name(self, *, address: str) -> str | None:
|
|
121
|
+
"""Get name for an address."""
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
@runtime_checkable
|
|
125
|
+
class DeviceDescriptionProviderProtocol(Protocol):
|
|
126
|
+
"""
|
|
127
|
+
Protocol for accessing device descriptions.
|
|
128
|
+
|
|
129
|
+
Implemented by DeviceDescriptionRegistry.
|
|
130
|
+
"""
|
|
131
|
+
|
|
132
|
+
@abstractmethod
|
|
133
|
+
def get_device_description(self, *, interface_id: str, address: str) -> DeviceDescription:
|
|
134
|
+
"""Get device description."""
|
|
135
|
+
|
|
136
|
+
@abstractmethod
|
|
137
|
+
def get_device_with_channels(self, *, interface_id: str, device_address: str) -> Mapping[str, DeviceDescription]:
|
|
138
|
+
"""Get device with all channel descriptions."""
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
@runtime_checkable
|
|
142
|
+
class ParamsetDescriptionProviderProtocol(Protocol):
|
|
143
|
+
"""
|
|
144
|
+
Protocol for accessing paramset descriptions.
|
|
145
|
+
|
|
146
|
+
Implemented by ParamsetDescriptionRegistry.
|
|
147
|
+
"""
|
|
148
|
+
|
|
149
|
+
@abstractmethod
|
|
150
|
+
def get_channel_paramset_descriptions(
|
|
151
|
+
self, *, interface_id: str, channel_address: str
|
|
152
|
+
) -> Mapping[ParamsetKey, Mapping[str, ParameterData]]:
|
|
153
|
+
"""Get all paramset descriptions for a channel."""
|
|
154
|
+
|
|
155
|
+
@abstractmethod
|
|
156
|
+
def get_parameter_data(
|
|
157
|
+
self, *, interface_id: str, channel_address: str, paramset_key: ParamsetKey, parameter: str
|
|
158
|
+
) -> ParameterData | None:
|
|
159
|
+
"""Get parameter data from paramset description."""
|
|
160
|
+
|
|
161
|
+
@abstractmethod
|
|
162
|
+
def has_parameter(
|
|
163
|
+
self, *, interface_id: str, channel_address: str, paramset_key: ParamsetKey, parameter: str
|
|
164
|
+
) -> bool:
|
|
165
|
+
"""Check if a parameter exists in the paramset description."""
|
|
166
|
+
|
|
167
|
+
@abstractmethod
|
|
168
|
+
def is_in_multiple_channels(self, *, channel_address: str, parameter: str) -> bool:
|
|
169
|
+
"""Check if parameter is in multiple channels per device."""
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
@runtime_checkable
|
|
173
|
+
class CacheWithStatisticsProtocol(Protocol):
|
|
174
|
+
"""
|
|
175
|
+
Protocol for caches that provide statistics.
|
|
176
|
+
|
|
177
|
+
Caches implementing this protocol provide local counters for hits, misses,
|
|
178
|
+
and evictions that can be read directly by MetricsAggregator without
|
|
179
|
+
requiring EventBus events.
|
|
180
|
+
"""
|
|
181
|
+
|
|
182
|
+
@property
|
|
183
|
+
@abstractmethod
|
|
184
|
+
def name(self) -> CacheName:
|
|
185
|
+
"""Return the cache name for identification."""
|
|
186
|
+
|
|
187
|
+
@property
|
|
188
|
+
@abstractmethod
|
|
189
|
+
def size(self) -> int:
|
|
190
|
+
"""Return current number of entries in the cache."""
|
|
191
|
+
|
|
192
|
+
@property
|
|
193
|
+
@abstractmethod
|
|
194
|
+
def statistics(self) -> CacheStatistics:
|
|
195
|
+
"""Return the cache statistics container."""
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
@runtime_checkable
|
|
199
|
+
class IncidentRecorderProtocol(Protocol):
|
|
200
|
+
"""
|
|
201
|
+
Protocol for recording diagnostic incidents.
|
|
202
|
+
|
|
203
|
+
Implemented by IncidentStore.
|
|
204
|
+
"""
|
|
205
|
+
|
|
206
|
+
@abstractmethod
|
|
207
|
+
async def record_incident(
|
|
208
|
+
self,
|
|
209
|
+
*,
|
|
210
|
+
incident_type: IncidentType,
|
|
211
|
+
severity: IncidentSeverity,
|
|
212
|
+
message: str,
|
|
213
|
+
interface_id: str | None = None,
|
|
214
|
+
context: dict[str, Any] | None = None,
|
|
215
|
+
journal: PingPongJournal | None = None,
|
|
216
|
+
) -> IncidentSnapshot:
|
|
217
|
+
"""Record a new incident and persist it."""
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
# Copyright (c) 2021-2026
|
|
3
|
+
"""
|
|
4
|
+
Logging integration with request context.
|
|
5
|
+
|
|
6
|
+
This module provides logging utilities that automatically include request context
|
|
7
|
+
information in log messages, enabling correlation of logs across async call chains.
|
|
8
|
+
|
|
9
|
+
Key features:
|
|
10
|
+
- ContextualLoggerAdapter: Logger adapter that prefixes messages with request ID
|
|
11
|
+
- RequestContextFilter: Logging filter that adds context fields to log records
|
|
12
|
+
- get_contextual_logger: Factory function for contextual loggers
|
|
13
|
+
|
|
14
|
+
Example:
|
|
15
|
+
_LOGGER = get_contextual_logger(name=__name__)
|
|
16
|
+
_LOGGER.info("Processing device") # → "[abc12345:set_value] Processing device"
|
|
17
|
+
|
|
18
|
+
Public API of this module is defined by __all__.
|
|
19
|
+
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
from collections.abc import MutableMapping
|
|
25
|
+
import logging
|
|
26
|
+
from typing import Any
|
|
27
|
+
|
|
28
|
+
from aiohomematic.context import get_request_context
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class ContextualLoggerAdapter(logging.LoggerAdapter[logging.Logger]):
|
|
32
|
+
"""
|
|
33
|
+
Logger adapter that includes request context in log messages.
|
|
34
|
+
|
|
35
|
+
Automatically prefixes log messages with request_id and operation
|
|
36
|
+
from the current RequestContext.
|
|
37
|
+
|
|
38
|
+
Example:
|
|
39
|
+
_LOGGER = ContextualLoggerAdapter(logging.getLogger(__name__), {})
|
|
40
|
+
# With request context set:
|
|
41
|
+
_LOGGER.info("Processing") # → "[abc12345:set_value] Processing"
|
|
42
|
+
# Without request context:
|
|
43
|
+
_LOGGER.info("Processing") # → "Processing"
|
|
44
|
+
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
def process( # kwonly: disable
|
|
48
|
+
self,
|
|
49
|
+
msg: str,
|
|
50
|
+
kwargs: MutableMapping[str, Any],
|
|
51
|
+
) -> tuple[str, MutableMapping[str, Any]]:
|
|
52
|
+
"""
|
|
53
|
+
Process log message to include request context.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
msg: The log message.
|
|
57
|
+
kwargs: Additional logging keyword arguments.
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
Tuple of (processed message, kwargs).
|
|
61
|
+
|
|
62
|
+
"""
|
|
63
|
+
if ctx := get_request_context():
|
|
64
|
+
prefix = f"[{ctx.request_id}]"
|
|
65
|
+
if ctx.operation:
|
|
66
|
+
prefix = f"[{ctx.request_id}:{ctx.operation}]"
|
|
67
|
+
msg = f"{prefix} {msg}"
|
|
68
|
+
|
|
69
|
+
return msg, kwargs
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class RequestContextFilter(logging.Filter):
|
|
73
|
+
"""
|
|
74
|
+
Logging filter that adds context fields to log records.
|
|
75
|
+
|
|
76
|
+
Use with JSON formatters or structured logging to include request context
|
|
77
|
+
as separate fields in log records.
|
|
78
|
+
|
|
79
|
+
Example:
|
|
80
|
+
handler = logging.StreamHandler()
|
|
81
|
+
handler.addFilter(RequestContextFilter())
|
|
82
|
+
# Log records will have: request_id, operation, device_address, elapsed_ms
|
|
83
|
+
|
|
84
|
+
"""
|
|
85
|
+
|
|
86
|
+
def filter(self, record: logging.LogRecord) -> bool: # kwonly: disable
|
|
87
|
+
"""
|
|
88
|
+
Add request context fields to log record.
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
record: The log record to augment.
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
Always True (never filters out records).
|
|
95
|
+
|
|
96
|
+
"""
|
|
97
|
+
ctx = get_request_context()
|
|
98
|
+
|
|
99
|
+
record.request_id = ctx.request_id if ctx else "none"
|
|
100
|
+
record.operation = ctx.operation if ctx else "none"
|
|
101
|
+
record.device_address = ctx.device_address if ctx else "none"
|
|
102
|
+
record.elapsed_ms = ctx.elapsed_ms if ctx else 0.0
|
|
103
|
+
|
|
104
|
+
return True
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def get_contextual_logger(*, name: str) -> ContextualLoggerAdapter:
|
|
108
|
+
"""
|
|
109
|
+
Get a logger that includes request context in messages.
|
|
110
|
+
|
|
111
|
+
Factory function that creates a ContextualLoggerAdapter wrapping a
|
|
112
|
+
standard logger. Use this instead of logging.getLogger() to get
|
|
113
|
+
automatic request context prefixing.
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
name: Logger name (typically __name__).
|
|
117
|
+
|
|
118
|
+
Returns:
|
|
119
|
+
ContextualLoggerAdapter that prefixes messages with request context.
|
|
120
|
+
|
|
121
|
+
Example:
|
|
122
|
+
_LOGGER = get_contextual_logger(name=__name__)
|
|
123
|
+
_LOGGER.info("Processing device") # → "[abc12345:set_value] Processing device"
|
|
124
|
+
|
|
125
|
+
"""
|
|
126
|
+
return ContextualLoggerAdapter(logging.getLogger(name), {})
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
# Define public API for this module
|
|
130
|
+
__all__ = [
|
|
131
|
+
"ContextualLoggerAdapter",
|
|
132
|
+
"RequestContextFilter",
|
|
133
|
+
"get_contextual_logger",
|
|
134
|
+
]
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
# Copyright (c) 2021-2026
|
|
3
|
+
"""
|
|
4
|
+
Metrics collection, aggregation, and observability.
|
|
5
|
+
|
|
6
|
+
This module provides both event-driven and polling-based metrics collection.
|
|
7
|
+
|
|
8
|
+
Event-Driven Metrics (MetricsObserver)
|
|
9
|
+
--------------------------------------
|
|
10
|
+
Components emit metric events to the EventBus, and MetricsObserver aggregates them.
|
|
11
|
+
Use emit_* functions to emit metrics and MetricsObserver to query them.
|
|
12
|
+
|
|
13
|
+
Polling-Based Metrics (MetricsAggregator)
|
|
14
|
+
-----------------------------------------
|
|
15
|
+
MetricsAggregator queries system components directly to collect metrics.
|
|
16
|
+
Use for comprehensive system snapshots.
|
|
17
|
+
|
|
18
|
+
Public API
|
|
19
|
+
----------
|
|
20
|
+
Event-driven:
|
|
21
|
+
- MetricEvent, LatencyMetricEvent, CounterMetricEvent, GaugeMetricEvent, HealthMetricEvent
|
|
22
|
+
- MetricsObserver, ObserverSnapshot, LatencyTracker, HealthState
|
|
23
|
+
- emit_latency, emit_counter, emit_gauge, emit_health
|
|
24
|
+
- MetricEmitterMixin, LatencyContext
|
|
25
|
+
|
|
26
|
+
Polling-based:
|
|
27
|
+
- MetricsAggregator, MetricsSnapshot
|
|
28
|
+
- RpcMetrics, RpcServerMetrics, EventMetrics, CacheMetrics, HealthMetrics
|
|
29
|
+
- RecoveryMetrics, ModelMetrics, ServiceMetrics
|
|
30
|
+
|
|
31
|
+
Note: Protocol dependencies for MetricsAggregator are in aiohomematic.interfaces:
|
|
32
|
+
- ClientProviderForMetricsProtocol
|
|
33
|
+
- DeviceProviderForMetricsProtocol
|
|
34
|
+
- HubDataPointManagerForMetricsProtocol
|
|
35
|
+
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
from __future__ import annotations
|
|
39
|
+
|
|
40
|
+
from aiohomematic.metrics.aggregator import MetricsAggregator
|
|
41
|
+
from aiohomematic.metrics.dataclasses import (
|
|
42
|
+
CacheMetrics,
|
|
43
|
+
EventMetrics,
|
|
44
|
+
HealthMetrics,
|
|
45
|
+
MetricsSnapshot,
|
|
46
|
+
ModelMetrics,
|
|
47
|
+
RecoveryMetrics,
|
|
48
|
+
RpcMetrics,
|
|
49
|
+
RpcServerMetrics,
|
|
50
|
+
ServiceMetrics,
|
|
51
|
+
)
|
|
52
|
+
from aiohomematic.metrics.emitter import (
|
|
53
|
+
EventBusProviderProtocol,
|
|
54
|
+
LatencyContext,
|
|
55
|
+
MetricEmitterMixin,
|
|
56
|
+
emit_counter,
|
|
57
|
+
emit_gauge,
|
|
58
|
+
emit_health,
|
|
59
|
+
emit_latency,
|
|
60
|
+
)
|
|
61
|
+
from aiohomematic.metrics.events import (
|
|
62
|
+
METRIC_EVENT_TYPES,
|
|
63
|
+
AnyMetricEvent,
|
|
64
|
+
CounterMetricEvent,
|
|
65
|
+
GaugeMetricEvent,
|
|
66
|
+
HealthMetricEvent,
|
|
67
|
+
LatencyMetricEvent,
|
|
68
|
+
MetricEvent,
|
|
69
|
+
MetricType,
|
|
70
|
+
)
|
|
71
|
+
from aiohomematic.metrics.keys import MetricKey, MetricKeys
|
|
72
|
+
from aiohomematic.metrics.observer import (
|
|
73
|
+
MAX_METRIC_KEYS,
|
|
74
|
+
HealthState,
|
|
75
|
+
LatencyTracker,
|
|
76
|
+
MetricsObserver,
|
|
77
|
+
ObserverSnapshot,
|
|
78
|
+
)
|
|
79
|
+
from aiohomematic.metrics.stats import CacheStats, LatencyStats, ServiceStats, SizeOnlyStats
|
|
80
|
+
|
|
81
|
+
__all__ = [
|
|
82
|
+
# Aggregator
|
|
83
|
+
"MetricsAggregator",
|
|
84
|
+
# Dataclasses
|
|
85
|
+
"CacheMetrics",
|
|
86
|
+
"EventMetrics",
|
|
87
|
+
"HealthMetrics",
|
|
88
|
+
"MetricsSnapshot",
|
|
89
|
+
"ModelMetrics",
|
|
90
|
+
"RecoveryMetrics",
|
|
91
|
+
"RpcMetrics",
|
|
92
|
+
"RpcServerMetrics",
|
|
93
|
+
"ServiceMetrics",
|
|
94
|
+
# Emitter
|
|
95
|
+
"EventBusProviderProtocol",
|
|
96
|
+
"LatencyContext",
|
|
97
|
+
"MetricEmitterMixin",
|
|
98
|
+
"emit_counter",
|
|
99
|
+
"emit_gauge",
|
|
100
|
+
"emit_health",
|
|
101
|
+
"emit_latency",
|
|
102
|
+
# Events
|
|
103
|
+
"AnyMetricEvent",
|
|
104
|
+
"CounterMetricEvent",
|
|
105
|
+
"GaugeMetricEvent",
|
|
106
|
+
"HealthMetricEvent",
|
|
107
|
+
"LatencyMetricEvent",
|
|
108
|
+
"METRIC_EVENT_TYPES",
|
|
109
|
+
"MetricEvent",
|
|
110
|
+
"MetricType",
|
|
111
|
+
# Keys
|
|
112
|
+
"MetricKey",
|
|
113
|
+
"MetricKeys",
|
|
114
|
+
# Observer
|
|
115
|
+
"HealthState",
|
|
116
|
+
"LatencyTracker",
|
|
117
|
+
"MAX_METRIC_KEYS",
|
|
118
|
+
"MetricsObserver",
|
|
119
|
+
"ObserverSnapshot",
|
|
120
|
+
# Stats
|
|
121
|
+
"CacheStats",
|
|
122
|
+
"LatencyStats",
|
|
123
|
+
"ServiceStats",
|
|
124
|
+
"SizeOnlyStats",
|
|
125
|
+
]
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
# Copyright (c) 2021-2026
|
|
3
|
+
"""
|
|
4
|
+
Minimal protocol interfaces for metrics aggregation.
|
|
5
|
+
|
|
6
|
+
This private module defines minimal protocol interfaces used by MetricsAggregator.
|
|
7
|
+
Located in metrics/ (not interfaces/) to avoid circular imports during initialization.
|
|
8
|
+
|
|
9
|
+
Import path: aiohomematic.interfaces (public) or aiohomematic.metrics._protocols (internal)
|
|
10
|
+
|
|
11
|
+
These protocols are:
|
|
12
|
+
- Re-exported via interfaces/__init__.py for public use
|
|
13
|
+
- Extended by full protocols (DeviceProviderProtocol, ClientProviderProtocol,
|
|
14
|
+
HubDataPointManagerProtocol) in interfaces/central.py and interfaces/client.py
|
|
15
|
+
|
|
16
|
+
IMPORTANT: This module must NOT import from interfaces/ to avoid circular imports.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
from abc import abstractmethod
|
|
22
|
+
from typing import TYPE_CHECKING, Any, Protocol, runtime_checkable
|
|
23
|
+
|
|
24
|
+
if TYPE_CHECKING:
|
|
25
|
+
from aiohomematic.store.types import CacheStatistics
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@runtime_checkable
|
|
29
|
+
class DeviceProviderForMetricsProtocol(Protocol):
|
|
30
|
+
"""
|
|
31
|
+
Minimal protocol for device access in metrics context.
|
|
32
|
+
|
|
33
|
+
Provides only the `devices` property needed by MetricsAggregator
|
|
34
|
+
to collect model statistics without requiring the full DeviceProviderProtocol.
|
|
35
|
+
|
|
36
|
+
Implemented by CentralUnit.
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
@property
|
|
40
|
+
@abstractmethod
|
|
41
|
+
def devices(self) -> tuple[Any, ...]:
|
|
42
|
+
"""Return all registered devices."""
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@runtime_checkable
|
|
46
|
+
class ClientProviderForMetricsProtocol(Protocol):
|
|
47
|
+
"""
|
|
48
|
+
Minimal protocol for client access in metrics context.
|
|
49
|
+
|
|
50
|
+
Provides only the `clients` property needed by MetricsAggregator
|
|
51
|
+
to collect RPC metrics without requiring the full ClientProviderProtocol.
|
|
52
|
+
|
|
53
|
+
Implemented by CentralUnit.
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
@property
|
|
57
|
+
@abstractmethod
|
|
58
|
+
def clients(self) -> tuple[Any, ...]:
|
|
59
|
+
"""Return all connected clients."""
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@runtime_checkable
|
|
63
|
+
class HubDataPointManagerForMetricsProtocol(Protocol):
|
|
64
|
+
"""
|
|
65
|
+
Minimal protocol for hub data point access in metrics context.
|
|
66
|
+
|
|
67
|
+
Provides only the properties needed by MetricsAggregator to collect
|
|
68
|
+
program/sysvar counts without requiring the full HubDataPointManagerProtocol.
|
|
69
|
+
|
|
70
|
+
Implemented by HubCoordinator.
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
@property
|
|
74
|
+
@abstractmethod
|
|
75
|
+
def program_data_points(self) -> tuple[Any, ...]:
|
|
76
|
+
"""Return all program data points."""
|
|
77
|
+
|
|
78
|
+
@property
|
|
79
|
+
@abstractmethod
|
|
80
|
+
def sysvar_data_points(self) -> tuple[Any, ...]:
|
|
81
|
+
"""Return all system variable data points."""
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
@runtime_checkable
|
|
85
|
+
class CacheProviderForMetricsProtocol(Protocol):
|
|
86
|
+
"""
|
|
87
|
+
Minimal protocol for cache access in metrics context.
|
|
88
|
+
|
|
89
|
+
Provides cache sizes and statistics needed by MetricsAggregator to collect
|
|
90
|
+
cache metrics without requiring the full CacheCoordinator.
|
|
91
|
+
|
|
92
|
+
Implemented by CacheCoordinator.
|
|
93
|
+
"""
|
|
94
|
+
|
|
95
|
+
@property
|
|
96
|
+
@abstractmethod
|
|
97
|
+
def data_cache_size(self) -> int:
|
|
98
|
+
"""Return data cache size."""
|
|
99
|
+
|
|
100
|
+
@property
|
|
101
|
+
@abstractmethod
|
|
102
|
+
def data_cache_statistics(self) -> CacheStatistics:
|
|
103
|
+
"""Return data cache statistics."""
|
|
104
|
+
|
|
105
|
+
@property
|
|
106
|
+
@abstractmethod
|
|
107
|
+
def device_descriptions_size(self) -> int:
|
|
108
|
+
"""Return device descriptions cache size."""
|
|
109
|
+
|
|
110
|
+
@property
|
|
111
|
+
@abstractmethod
|
|
112
|
+
def paramset_descriptions_size(self) -> int:
|
|
113
|
+
"""Return paramset descriptions cache size."""
|
|
114
|
+
|
|
115
|
+
@property
|
|
116
|
+
@abstractmethod
|
|
117
|
+
def visibility_cache_size(self) -> int:
|
|
118
|
+
"""Return visibility cache size."""
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
@runtime_checkable
|
|
122
|
+
class RecoveryProviderForMetricsProtocol(Protocol):
|
|
123
|
+
"""
|
|
124
|
+
Minimal protocol for recovery status access in metrics context.
|
|
125
|
+
|
|
126
|
+
Provides recovery statistics needed by MetricsAggregator to collect
|
|
127
|
+
recovery metrics without requiring the full ConnectionRecoveryCoordinator.
|
|
128
|
+
|
|
129
|
+
Implemented by ConnectionRecoveryCoordinator.
|
|
130
|
+
"""
|
|
131
|
+
|
|
132
|
+
@property
|
|
133
|
+
@abstractmethod
|
|
134
|
+
def in_recovery(self) -> bool:
|
|
135
|
+
"""Return True if any recovery is in progress."""
|
|
136
|
+
|
|
137
|
+
@property
|
|
138
|
+
@abstractmethod
|
|
139
|
+
def recovery_states(self) -> dict[str, Any]:
|
|
140
|
+
"""Return recovery states for all tracked interfaces."""
|