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,251 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
# Copyright (c) 2021-2026
|
|
3
|
+
"""
|
|
4
|
+
Device profile registry for custom data point configurations.
|
|
5
|
+
|
|
6
|
+
This module provides a centralized registry for mapping device models to their
|
|
7
|
+
custom data point configurations, replacing the distributed ALL_DEVICES pattern.
|
|
8
|
+
|
|
9
|
+
Key types:
|
|
10
|
+
- DeviceConfig: Configuration for a specific device model
|
|
11
|
+
- ExtendedDeviceConfig: Extended configuration with additional fields
|
|
12
|
+
- DeviceProfileRegistry: Central registry class for device profile configurations
|
|
13
|
+
|
|
14
|
+
Example usage:
|
|
15
|
+
from aiohomematic.model.custom import (
|
|
16
|
+
DeviceProfileRegistry,
|
|
17
|
+
DeviceConfig,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
# Register a device
|
|
21
|
+
DeviceProfileRegistry.register(
|
|
22
|
+
category=DataPointCategory.CLIMATE,
|
|
23
|
+
models=("HmIP-BWTH", "HmIP-STH"),
|
|
24
|
+
data_point_class=CustomDpIpThermostat,
|
|
25
|
+
profile_type=DeviceProfile.IP_THERMOSTAT,
|
|
26
|
+
schedule_channel_no=1,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
# Get configurations for a model
|
|
30
|
+
configs = DeviceProfileRegistry.get_configs(model="HmIP-BWTH")
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
from __future__ import annotations
|
|
34
|
+
|
|
35
|
+
from collections.abc import Mapping
|
|
36
|
+
from dataclasses import dataclass
|
|
37
|
+
from typing import TYPE_CHECKING, ClassVar
|
|
38
|
+
|
|
39
|
+
from aiohomematic.const import DataPointCategory, DeviceProfile, Field, Parameter
|
|
40
|
+
|
|
41
|
+
if TYPE_CHECKING:
|
|
42
|
+
from aiohomematic.model.custom.data_point import CustomDataPoint
|
|
43
|
+
|
|
44
|
+
__all__ = [
|
|
45
|
+
"DeviceConfig",
|
|
46
|
+
"DeviceProfileRegistry",
|
|
47
|
+
"ExtendedDeviceConfig",
|
|
48
|
+
]
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@dataclass(frozen=True, kw_only=True, slots=True)
|
|
52
|
+
class ExtendedDeviceConfig:
|
|
53
|
+
"""Extended configuration for custom data point creation."""
|
|
54
|
+
|
|
55
|
+
fixed_channel_fields: Mapping[int, Mapping[Field, Parameter]] | None = None
|
|
56
|
+
additional_data_points: Mapping[int | tuple[int, ...], tuple[Parameter, ...]] | None = None
|
|
57
|
+
|
|
58
|
+
@property
|
|
59
|
+
def required_parameters(self) -> tuple[Parameter, ...]:
|
|
60
|
+
"""Return required parameters from extended config."""
|
|
61
|
+
required_parameters: list[Parameter] = []
|
|
62
|
+
if fixed_channels := self.fixed_channel_fields:
|
|
63
|
+
for mapping in fixed_channels.values():
|
|
64
|
+
required_parameters.extend(mapping.values())
|
|
65
|
+
|
|
66
|
+
if additional_dps := self.additional_data_points:
|
|
67
|
+
for parameters in additional_dps.values():
|
|
68
|
+
required_parameters.extend(parameters)
|
|
69
|
+
|
|
70
|
+
return tuple(required_parameters)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
@dataclass(frozen=True, kw_only=True, slots=True)
|
|
74
|
+
class DeviceConfig:
|
|
75
|
+
"""Configuration for mapping a device model to its custom data point implementation."""
|
|
76
|
+
|
|
77
|
+
data_point_class: type[CustomDataPoint]
|
|
78
|
+
profile_type: DeviceProfile
|
|
79
|
+
channels: tuple[int | None, ...] = (1,)
|
|
80
|
+
extended: ExtendedDeviceConfig | None = None
|
|
81
|
+
schedule_channel_no: int | None = None
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class DeviceProfileRegistry:
|
|
85
|
+
"""Central registry for device profile configurations."""
|
|
86
|
+
|
|
87
|
+
_configs: ClassVar[dict[DataPointCategory, dict[str, DeviceConfig | tuple[DeviceConfig, ...]]]] = {}
|
|
88
|
+
_blacklist: ClassVar[set[str]] = set()
|
|
89
|
+
|
|
90
|
+
@classmethod
|
|
91
|
+
def blacklist(cls, *models: str) -> None:
|
|
92
|
+
"""Blacklist device models."""
|
|
93
|
+
cls._blacklist.update(m.lower().replace("hb-", "hm-") for m in models)
|
|
94
|
+
|
|
95
|
+
@classmethod
|
|
96
|
+
def clear(cls) -> None:
|
|
97
|
+
"""Clear all registrations. Primarily for testing."""
|
|
98
|
+
cls._configs.clear()
|
|
99
|
+
cls._blacklist.clear()
|
|
100
|
+
|
|
101
|
+
@classmethod
|
|
102
|
+
def get_all_configs(
|
|
103
|
+
cls,
|
|
104
|
+
*,
|
|
105
|
+
category: DataPointCategory,
|
|
106
|
+
) -> Mapping[str, DeviceConfig | tuple[DeviceConfig, ...]]:
|
|
107
|
+
"""Return all configurations for a category."""
|
|
108
|
+
return cls._configs.get(category, {})
|
|
109
|
+
|
|
110
|
+
@classmethod
|
|
111
|
+
def get_all_extended_configs(cls) -> tuple[ExtendedDeviceConfig, ...]:
|
|
112
|
+
"""Return all extended configurations from all categories."""
|
|
113
|
+
extended_configs: list[ExtendedDeviceConfig] = []
|
|
114
|
+
for category_configs in cls._configs.values():
|
|
115
|
+
for device_config in category_configs.values():
|
|
116
|
+
if isinstance(device_config, tuple):
|
|
117
|
+
extended_configs.extend(cfg.extended for cfg in device_config if cfg.extended)
|
|
118
|
+
elif device_config.extended:
|
|
119
|
+
extended_configs.append(device_config.extended)
|
|
120
|
+
return tuple(extended_configs)
|
|
121
|
+
|
|
122
|
+
@classmethod
|
|
123
|
+
def get_blacklist(cls) -> tuple[str, ...]:
|
|
124
|
+
"""Return current blacklist entries."""
|
|
125
|
+
return tuple(sorted(cls._blacklist))
|
|
126
|
+
|
|
127
|
+
@classmethod
|
|
128
|
+
def get_configs(
|
|
129
|
+
cls,
|
|
130
|
+
*,
|
|
131
|
+
model: str,
|
|
132
|
+
category: DataPointCategory | None = None,
|
|
133
|
+
) -> tuple[DeviceConfig, ...]:
|
|
134
|
+
"""
|
|
135
|
+
Return device configurations for a model.
|
|
136
|
+
|
|
137
|
+
Model matching algorithm (hierarchical, first-match wins):
|
|
138
|
+
1. Normalize model name (lowercase, replace "hb-" with "hm-")
|
|
139
|
+
2. Check blacklist - return empty if blacklisted
|
|
140
|
+
3. For each category, try matching in order:
|
|
141
|
+
a. Exact match: model == registered_key
|
|
142
|
+
b. Prefix match: model.startswith(registered_key)
|
|
143
|
+
|
|
144
|
+
Why prefix matching?
|
|
145
|
+
Homematic devices often have variants (e.g., "HmIP-BWTH-1" and "HmIP-BWTH-2")
|
|
146
|
+
that share the same profile. Prefix matching allows registering "hmip-bwth"
|
|
147
|
+
once to cover all variants, reducing duplication.
|
|
148
|
+
|
|
149
|
+
Model normalization:
|
|
150
|
+
- Lowercase: Makes matching case-insensitive
|
|
151
|
+
- "hb-" → "hm-": HomeBrew devices use "HB-" prefix but behave like "HM-" devices
|
|
152
|
+
|
|
153
|
+
Result aggregation:
|
|
154
|
+
A model can match multiple categories (e.g., a thermostat might have both
|
|
155
|
+
CLIMATE and SENSOR data points). Results from all matching categories are
|
|
156
|
+
combined into a single tuple.
|
|
157
|
+
|
|
158
|
+
Storage format:
|
|
159
|
+
Registry entries can be either:
|
|
160
|
+
- Single DeviceConfig: For simple devices
|
|
161
|
+
- Tuple of DeviceConfigs: For devices with multiple data point types
|
|
162
|
+
(e.g., lock + button_lock on same device)
|
|
163
|
+
"""
|
|
164
|
+
# Normalize model name for consistent matching
|
|
165
|
+
normalized = model.lower().replace("hb-", "hm-")
|
|
166
|
+
|
|
167
|
+
# Check blacklist first (fast path for excluded devices)
|
|
168
|
+
if cls.is_blacklisted(model=model):
|
|
169
|
+
return ()
|
|
170
|
+
|
|
171
|
+
configs: list[DeviceConfig] = []
|
|
172
|
+
|
|
173
|
+
# Search specified category or all categories
|
|
174
|
+
categories = [category] if category else list(cls._configs.keys())
|
|
175
|
+
|
|
176
|
+
for cat in categories:
|
|
177
|
+
if cat not in cls._configs:
|
|
178
|
+
continue
|
|
179
|
+
|
|
180
|
+
# Priority 1: Exact match (most specific)
|
|
181
|
+
if result := cls._configs[cat].get(normalized):
|
|
182
|
+
if isinstance(result, tuple):
|
|
183
|
+
configs.extend(result)
|
|
184
|
+
else:
|
|
185
|
+
configs.append(result)
|
|
186
|
+
continue # Found exact match, skip prefix matching for this category
|
|
187
|
+
|
|
188
|
+
# Priority 2: Prefix match (for device variants)
|
|
189
|
+
for model_key, result in cls._configs[cat].items():
|
|
190
|
+
if normalized.startswith(model_key):
|
|
191
|
+
if isinstance(result, tuple):
|
|
192
|
+
configs.extend(result)
|
|
193
|
+
else:
|
|
194
|
+
configs.append(result)
|
|
195
|
+
break # First prefix match wins, stop searching this category
|
|
196
|
+
|
|
197
|
+
return tuple(configs)
|
|
198
|
+
|
|
199
|
+
@classmethod
|
|
200
|
+
def is_blacklisted(cls, *, model: str) -> bool:
|
|
201
|
+
"""Check if a model is blacklisted."""
|
|
202
|
+
normalized = model.lower().replace("hb-", "hm-")
|
|
203
|
+
return any(normalized.startswith(bl) for bl in cls._blacklist)
|
|
204
|
+
|
|
205
|
+
@classmethod
|
|
206
|
+
def register(
|
|
207
|
+
cls,
|
|
208
|
+
*,
|
|
209
|
+
category: DataPointCategory,
|
|
210
|
+
models: str | tuple[str, ...],
|
|
211
|
+
data_point_class: type[CustomDataPoint],
|
|
212
|
+
profile_type: DeviceProfile,
|
|
213
|
+
channels: tuple[int | None, ...] = (1,),
|
|
214
|
+
extended: ExtendedDeviceConfig | None = None,
|
|
215
|
+
schedule_channel_no: int | None = None,
|
|
216
|
+
) -> None:
|
|
217
|
+
"""Register a device configuration."""
|
|
218
|
+
config = DeviceConfig(
|
|
219
|
+
data_point_class=data_point_class,
|
|
220
|
+
profile_type=profile_type,
|
|
221
|
+
channels=channels,
|
|
222
|
+
extended=extended,
|
|
223
|
+
schedule_channel_no=schedule_channel_no,
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
models_tuple = (models,) if isinstance(models, str) else models
|
|
227
|
+
|
|
228
|
+
if category not in cls._configs:
|
|
229
|
+
cls._configs[category] = {}
|
|
230
|
+
|
|
231
|
+
for model in models_tuple:
|
|
232
|
+
normalized = model.lower().replace("hb-", "hm-")
|
|
233
|
+
cls._configs[category][normalized] = config
|
|
234
|
+
|
|
235
|
+
@classmethod
|
|
236
|
+
def register_multiple(
|
|
237
|
+
cls,
|
|
238
|
+
*,
|
|
239
|
+
category: DataPointCategory,
|
|
240
|
+
models: str | tuple[str, ...],
|
|
241
|
+
configs: tuple[DeviceConfig, ...],
|
|
242
|
+
) -> None:
|
|
243
|
+
"""Register multiple configurations for the same model(s)."""
|
|
244
|
+
models_tuple = (models,) if isinstance(models, str) else models
|
|
245
|
+
|
|
246
|
+
if category not in cls._configs:
|
|
247
|
+
cls._configs[category] = {}
|
|
248
|
+
|
|
249
|
+
for model in models_tuple:
|
|
250
|
+
normalized = model.lower().replace("hb-", "hm-")
|
|
251
|
+
cls._configs[category][normalized] = configs
|