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,424 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
# Copyright (c) 2021-2026
|
|
3
|
+
"""
|
|
4
|
+
Integration-level events for Home Assistant and other consumers.
|
|
5
|
+
|
|
6
|
+
Overview
|
|
7
|
+
--------
|
|
8
|
+
This module provides focused integration events that aggregate multiple internal
|
|
9
|
+
events into consumer-friendly structures. These events are designed for:
|
|
10
|
+
|
|
11
|
+
- Home Assistant integration (control_unit.py, generic_entity.py)
|
|
12
|
+
- Third-party integrations
|
|
13
|
+
- Monitoring and debugging tools
|
|
14
|
+
|
|
15
|
+
Event Hierarchy
|
|
16
|
+
---------------
|
|
17
|
+
Integration Events (this module):
|
|
18
|
+
- SystemStatusChangedEvent: Infrastructure + lifecycle changes
|
|
19
|
+
- DeviceLifecycleEvent: Device creation, removal, availability
|
|
20
|
+
- DataPointsCreatedEvent: data point discovery
|
|
21
|
+
- DeviceTriggerEvent: Device triggers (button press, etc.)
|
|
22
|
+
|
|
23
|
+
Data Point-Level Events (event_bus.py):
|
|
24
|
+
- DataPointUpdatedEvent: High-frequency value updates (per-data point subscription)
|
|
25
|
+
|
|
26
|
+
Design Philosophy
|
|
27
|
+
-----------------
|
|
28
|
+
- Each event has a clear, single responsibility
|
|
29
|
+
- Immutable dataclasses (frozen=True, slots=True) for safety and performance
|
|
30
|
+
- Minimal payload - only relevant fields are populated
|
|
31
|
+
- Targeted registration - subscribe only to what you need
|
|
32
|
+
|
|
33
|
+
Example Usage
|
|
34
|
+
-------------
|
|
35
|
+
from aiohomematic.central.events import (
|
|
36
|
+
SystemStatusChangedEvent,
|
|
37
|
+
DeviceLifecycleEvent,
|
|
38
|
+
DataPointsCreatedEvent,
|
|
39
|
+
DeviceTriggerEvent,
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
# Subscribe to system status changes
|
|
43
|
+
central.event_bus.subscribe(
|
|
44
|
+
event_type=SystemStatusChangedEvent,
|
|
45
|
+
event_key=None,
|
|
46
|
+
handler=on_system_status,
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
# Subscribe to device lifecycle events
|
|
50
|
+
central.event_bus.subscribe(
|
|
51
|
+
event_type=DeviceLifecycleEvent,
|
|
52
|
+
event_key=None,
|
|
53
|
+
handler=on_device_lifecycle,
|
|
54
|
+
)
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
from __future__ import annotations
|
|
58
|
+
|
|
59
|
+
from collections.abc import Mapping
|
|
60
|
+
from dataclasses import dataclass
|
|
61
|
+
from enum import StrEnum
|
|
62
|
+
from typing import TYPE_CHECKING, Any
|
|
63
|
+
|
|
64
|
+
from aiohomematic.central.events.types import Event
|
|
65
|
+
from aiohomematic.const import (
|
|
66
|
+
CentralState,
|
|
67
|
+
ClientState,
|
|
68
|
+
DataPointCategory,
|
|
69
|
+
DeviceTriggerEventType,
|
|
70
|
+
FailureReason,
|
|
71
|
+
IntegrationIssueSeverity,
|
|
72
|
+
IntegrationIssueType,
|
|
73
|
+
PingPongMismatchType,
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
if TYPE_CHECKING:
|
|
77
|
+
from aiohomematic.interfaces import CallbackDataPointProtocol
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
__all__ = [
|
|
81
|
+
"DataPointsCreatedEvent",
|
|
82
|
+
"DeviceLifecycleEvent",
|
|
83
|
+
"DeviceLifecycleEventType",
|
|
84
|
+
"DeviceTriggerEvent",
|
|
85
|
+
"IntegrationIssue",
|
|
86
|
+
"SystemStatusChangedEvent",
|
|
87
|
+
]
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
@dataclass(frozen=True, slots=True)
|
|
91
|
+
class IntegrationIssue:
|
|
92
|
+
"""
|
|
93
|
+
Issue that should be presented to user via integration's UI.
|
|
94
|
+
|
|
95
|
+
Used to communicate problems that require user attention
|
|
96
|
+
(e.g., connection failures, configuration errors).
|
|
97
|
+
|
|
98
|
+
Example:
|
|
99
|
+
```python
|
|
100
|
+
# Creating a ping-pong mismatch issue
|
|
101
|
+
issue = IntegrationIssue(
|
|
102
|
+
issue_type=IntegrationIssueType.PING_PONG_MISMATCH,
|
|
103
|
+
severity=IntegrationIssueSeverity.WARNING,
|
|
104
|
+
interface_id="ccu-HmIP-RF",
|
|
105
|
+
mismatch_type=PingPongMismatchType.PENDING,
|
|
106
|
+
mismatch_count=5,
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
# Creating a fetch data failed issue
|
|
110
|
+
issue = IntegrationIssue(
|
|
111
|
+
issue_type=IntegrationIssueType.FETCH_DATA_FAILED,
|
|
112
|
+
severity=IntegrationIssueSeverity.WARNING,
|
|
113
|
+
interface_id="ccu-BidCos-RF",
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
# Accessing computed properties for HA integration
|
|
117
|
+
issue.issue_id # "ping_pong_mismatch_ccu-HmIP-RF"
|
|
118
|
+
issue.translation_key # "ping_pong_mismatch"
|
|
119
|
+
issue.translation_placeholders # {"interface_id": "ccu-HmIP-RF", ...}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
"""
|
|
123
|
+
|
|
124
|
+
issue_type: IntegrationIssueType
|
|
125
|
+
"""Type of issue - determines translation_key and issue_id prefix."""
|
|
126
|
+
|
|
127
|
+
severity: IntegrationIssueSeverity
|
|
128
|
+
"""Issue severity level."""
|
|
129
|
+
|
|
130
|
+
interface_id: str
|
|
131
|
+
"""Interface ID where the issue occurred."""
|
|
132
|
+
|
|
133
|
+
mismatch_type: PingPongMismatchType | None = None
|
|
134
|
+
"""Mismatch type (only for PING_PONG_MISMATCH issues)."""
|
|
135
|
+
|
|
136
|
+
mismatch_count: int | None = None
|
|
137
|
+
"""Mismatch count (only for PING_PONG_MISMATCH issues)."""
|
|
138
|
+
|
|
139
|
+
@property
|
|
140
|
+
def issue_id(self) -> str:
|
|
141
|
+
"""Generate unique issue ID from type and interface."""
|
|
142
|
+
return f"{self.issue_type.value}_{self.interface_id}"
|
|
143
|
+
|
|
144
|
+
@property
|
|
145
|
+
def translation_key(self) -> str:
|
|
146
|
+
"""Return translation key (same as issue_type value)."""
|
|
147
|
+
return self.issue_type.value
|
|
148
|
+
|
|
149
|
+
@property
|
|
150
|
+
def translation_placeholders(self) -> dict[str, str]:
|
|
151
|
+
"""Return all placeholders for translation interpolation."""
|
|
152
|
+
result: dict[str, str] = {"interface_id": self.interface_id}
|
|
153
|
+
if self.mismatch_type is not None:
|
|
154
|
+
result["mismatch_type"] = self.mismatch_type.value
|
|
155
|
+
if self.mismatch_count is not None:
|
|
156
|
+
result["mismatch_count"] = str(self.mismatch_count)
|
|
157
|
+
return result
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
@dataclass(frozen=True, slots=True)
|
|
161
|
+
class SystemStatusChangedEvent(Event):
|
|
162
|
+
"""
|
|
163
|
+
System infrastructure and lifecycle status event.
|
|
164
|
+
|
|
165
|
+
Aggregates: CentralStateChangedEvent, ConnectionStateChangedEvent,
|
|
166
|
+
ClientStateChangedEvent, CallbackStateChangedEvent, FetchDataFailedEvent,
|
|
167
|
+
PingPongMismatchEvent.
|
|
168
|
+
|
|
169
|
+
**HA Registration Point**: `control_unit.py`
|
|
170
|
+
|
|
171
|
+
Example:
|
|
172
|
+
```python
|
|
173
|
+
async def on_system_status(*, event: SystemStatusChangedEvent) -> None:
|
|
174
|
+
if event.central_state == CentralState.FAILED:
|
|
175
|
+
if event.failure_reason == FailureReason.AUTH:
|
|
176
|
+
async_create_issue(..., translation_key="auth_failed")
|
|
177
|
+
elif event.failure_reason == FailureReason.NETWORK:
|
|
178
|
+
async_create_issue(..., translation_key="connection_failed")
|
|
179
|
+
|
|
180
|
+
elif event.central_state == CentralState.DEGRADED:
|
|
181
|
+
# Show status per degraded interface
|
|
182
|
+
for iface_id, reason in (event.degraded_interfaces or {}).items():
|
|
183
|
+
_LOGGER.warning("Interface %s degraded: %s", iface_id, reason)
|
|
184
|
+
|
|
185
|
+
for issue in event.issues:
|
|
186
|
+
async_create_issue(...)
|
|
187
|
+
|
|
188
|
+
central.event_bus.subscribe(
|
|
189
|
+
event_type=SystemStatusChangedEvent,
|
|
190
|
+
event_key=None,
|
|
191
|
+
handler=on_system_status,
|
|
192
|
+
)
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
"""
|
|
196
|
+
|
|
197
|
+
# Lifecycle
|
|
198
|
+
central_state: CentralState | None = None
|
|
199
|
+
"""Central unit state change (STARTING, INITIALIZING, RUNNING, DEGRADED, RECOVERING, FAILED, STOPPED)."""
|
|
200
|
+
|
|
201
|
+
# Failure details (populated when central_state is FAILED)
|
|
202
|
+
failure_reason: FailureReason | None = None
|
|
203
|
+
"""
|
|
204
|
+
Categorized reason for the failure.
|
|
205
|
+
|
|
206
|
+
Only set when central_state is FAILED. Enables integrations to distinguish
|
|
207
|
+
between different error types (AUTH, NETWORK, INTERNAL, etc.) and show
|
|
208
|
+
appropriate user-facing messages.
|
|
209
|
+
"""
|
|
210
|
+
|
|
211
|
+
failure_interface_id: str | None = None
|
|
212
|
+
"""Interface ID that caused the failure, if applicable."""
|
|
213
|
+
|
|
214
|
+
# Degraded details (populated when central_state is DEGRADED)
|
|
215
|
+
degraded_interfaces: Mapping[str, FailureReason] | None = None
|
|
216
|
+
"""
|
|
217
|
+
Interfaces that are degraded with their failure reasons.
|
|
218
|
+
|
|
219
|
+
Only set when central_state is DEGRADED. Maps interface_id to its
|
|
220
|
+
FailureReason, enabling integrations to show detailed status per interface.
|
|
221
|
+
Example: {"ccu-HmIP-RF": FailureReason.NETWORK, "ccu-BidCos-RF": FailureReason.AUTH}
|
|
222
|
+
"""
|
|
223
|
+
|
|
224
|
+
# Infrastructure
|
|
225
|
+
connection_state: tuple[str, bool] | None = None
|
|
226
|
+
"""Connection state change: (interface_id, connected)."""
|
|
227
|
+
|
|
228
|
+
client_state: tuple[str, ClientState, ClientState] | None = None
|
|
229
|
+
"""Client state change: (interface_id, old_state, new_state)."""
|
|
230
|
+
|
|
231
|
+
callback_state: tuple[str, bool] | None = None
|
|
232
|
+
"""Callback server state change: (interface_id, alive)."""
|
|
233
|
+
|
|
234
|
+
# Issues
|
|
235
|
+
issues: tuple[IntegrationIssue, ...] = ()
|
|
236
|
+
"""Issues that should be presented to user (errors, warnings)."""
|
|
237
|
+
|
|
238
|
+
@property
|
|
239
|
+
def key(self) -> Any:
|
|
240
|
+
"""Key identifier for this event."""
|
|
241
|
+
return None
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
class DeviceLifecycleEventType(StrEnum):
|
|
245
|
+
"""Type of device lifecycle event."""
|
|
246
|
+
|
|
247
|
+
CREATED = "created"
|
|
248
|
+
DELAYED = "delayed"
|
|
249
|
+
UPDATED = "updated"
|
|
250
|
+
REMOVED = "removed"
|
|
251
|
+
AVAILABILITY_CHANGED = "availability_changed"
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
@dataclass(frozen=True, slots=True)
|
|
255
|
+
class DeviceLifecycleEvent(Event):
|
|
256
|
+
"""
|
|
257
|
+
Device lifecycle and availability event.
|
|
258
|
+
|
|
259
|
+
Aggregates: SystemEventTypeData (DEVICES_CREATED, DEVICES_DELAYED, DEVICE_REMOVED),
|
|
260
|
+
DeviceAvailabilityChangedEvent.
|
|
261
|
+
|
|
262
|
+
**HA Registration Points**:
|
|
263
|
+
- `control_unit.py`: Device registry updates, virtual remotes, repair issues
|
|
264
|
+
- `generic_entity.py`: Data point availability updates (optional)
|
|
265
|
+
|
|
266
|
+
Example:
|
|
267
|
+
```python
|
|
268
|
+
async def on_device_lifecycle(*, event: DeviceLifecycleEvent) -> None:
|
|
269
|
+
if event.event_type == DeviceLifecycleEventType.CREATED:
|
|
270
|
+
# Add to device registry
|
|
271
|
+
for device_address in event.device_addresses:
|
|
272
|
+
device_registry.async_get_or_create(...)
|
|
273
|
+
|
|
274
|
+
elif event.event_type == DeviceLifecycleEventType.DELAYED:
|
|
275
|
+
# Create repair issue for delayed device
|
|
276
|
+
for address in event.device_addresses:
|
|
277
|
+
async_create_issue(
|
|
278
|
+
issue_id=f"devices_delayed|{event.interface_id}|{address}",
|
|
279
|
+
...
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
elif event.event_type == DeviceLifecycleEventType.AVAILABILITY_CHANGED:
|
|
283
|
+
# Update data point availability
|
|
284
|
+
for address, available in event.availability_changes:
|
|
285
|
+
...
|
|
286
|
+
|
|
287
|
+
central.event_bus.subscribe(
|
|
288
|
+
event_type=DeviceLifecycleEvent,
|
|
289
|
+
event_key=None,
|
|
290
|
+
handler=on_device_lifecycle,
|
|
291
|
+
)
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
"""
|
|
295
|
+
|
|
296
|
+
event_type: DeviceLifecycleEventType
|
|
297
|
+
"""Type of device lifecycle event."""
|
|
298
|
+
|
|
299
|
+
# For CREATED/UPDATED/REMOVED/DELAYED
|
|
300
|
+
device_addresses: tuple[str, ...] = ()
|
|
301
|
+
"""Affected device addresses."""
|
|
302
|
+
|
|
303
|
+
# For AVAILABILITY_CHANGED
|
|
304
|
+
availability_changes: tuple[tuple[str, bool], ...] = ()
|
|
305
|
+
"""Availability changes: tuple of (device_address, is_available)."""
|
|
306
|
+
|
|
307
|
+
# For CREATED - includes virtual remotes flag
|
|
308
|
+
includes_virtual_remotes: bool = False
|
|
309
|
+
"""Whether virtual remotes are included in this creation event."""
|
|
310
|
+
|
|
311
|
+
# For DELAYED - interface where devices were delayed
|
|
312
|
+
interface_id: str | None = None
|
|
313
|
+
"""Interface ID for delayed device creation (used for repair issues)."""
|
|
314
|
+
|
|
315
|
+
@property
|
|
316
|
+
def key(self) -> Any:
|
|
317
|
+
"""Key identifier for this event."""
|
|
318
|
+
return None
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
@dataclass(frozen=True, slots=True)
|
|
322
|
+
class DataPointsCreatedEvent(Event):
|
|
323
|
+
"""
|
|
324
|
+
New data points created event.
|
|
325
|
+
|
|
326
|
+
Emitted when new data points are created (device addition, config reload).
|
|
327
|
+
Data points are grouped by category for platform-specific handling.
|
|
328
|
+
|
|
329
|
+
**HA Registration Point**: `control_unit.py`
|
|
330
|
+
(Dispatches to platform async_add_entities functions)
|
|
331
|
+
|
|
332
|
+
Example:
|
|
333
|
+
```python
|
|
334
|
+
async def on_data_points_created(*, event: DataPointsCreatedEvent) -> None:
|
|
335
|
+
for category, data_points in event.new_data_points:
|
|
336
|
+
async_dispatcher_send(
|
|
337
|
+
hass,
|
|
338
|
+
signal_new_data_point(entry_id=entry_id, platform=category),
|
|
339
|
+
data_points,
|
|
340
|
+
)
|
|
341
|
+
|
|
342
|
+
central.event_bus.subscribe(
|
|
343
|
+
event_type=DataPointsCreatedEvent,
|
|
344
|
+
event_key=None,
|
|
345
|
+
handler=on_data_points_created,
|
|
346
|
+
)
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
"""
|
|
350
|
+
|
|
351
|
+
new_data_points: Mapping[DataPointCategory, tuple[CallbackDataPointProtocol, ...]]
|
|
352
|
+
"""
|
|
353
|
+
New data points grouped by category.
|
|
354
|
+
|
|
355
|
+
Tuple of (category, data_points) pairs.
|
|
356
|
+
Only includes data points that should be exposed to integrations.
|
|
357
|
+
"""
|
|
358
|
+
|
|
359
|
+
@property
|
|
360
|
+
def key(self) -> Any:
|
|
361
|
+
"""Key identifier for this event."""
|
|
362
|
+
return None
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
@dataclass(frozen=True, slots=True)
|
|
366
|
+
class DeviceTriggerEvent(Event):
|
|
367
|
+
"""
|
|
368
|
+
Device trigger event (button press, sensor trigger, etc.).
|
|
369
|
+
|
|
370
|
+
Forwarded to Home Assistant's event bus for automations.
|
|
371
|
+
|
|
372
|
+
**HA Registration Point**: `control_unit.py`
|
|
373
|
+
(Fires HA event on homematicip_local.event)
|
|
374
|
+
|
|
375
|
+
Example:
|
|
376
|
+
```python
|
|
377
|
+
async def on_device_trigger(*, event: DeviceTriggerEvent) -> None:
|
|
378
|
+
hass.bus.async_fire(
|
|
379
|
+
event_type=f"{DOMAIN}.event",
|
|
380
|
+
event_data={
|
|
381
|
+
"trigger_type": event.trigger_type,
|
|
382
|
+
"model": event.model,
|
|
383
|
+
"interface_id": event.interface_id,
|
|
384
|
+
"device_address": event.device_address,
|
|
385
|
+
"channel_no": event.channel_no,
|
|
386
|
+
"parameter": event.parameter,
|
|
387
|
+
"value": event.value,
|
|
388
|
+
},
|
|
389
|
+
)
|
|
390
|
+
|
|
391
|
+
central.event_bus.subscribe(
|
|
392
|
+
event_type=DeviceTriggerEvent,
|
|
393
|
+
event_key=None,
|
|
394
|
+
handler=on_device_trigger,
|
|
395
|
+
)
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
"""
|
|
399
|
+
|
|
400
|
+
trigger_type: DeviceTriggerEventType
|
|
401
|
+
"""Type of device trigger event."""
|
|
402
|
+
|
|
403
|
+
model: str
|
|
404
|
+
"""Model of the device."""
|
|
405
|
+
|
|
406
|
+
interface_id: str
|
|
407
|
+
"""Interface ID where event occurred."""
|
|
408
|
+
|
|
409
|
+
device_address: str
|
|
410
|
+
"""Device address of the device."""
|
|
411
|
+
|
|
412
|
+
channel_no: int | None
|
|
413
|
+
"""Channel number of the device."""
|
|
414
|
+
|
|
415
|
+
parameter: str
|
|
416
|
+
"""Parameter name (e.g., PRESS_SHORT, MOTION)."""
|
|
417
|
+
|
|
418
|
+
value: str | int | float | bool
|
|
419
|
+
"""Event value."""
|
|
420
|
+
|
|
421
|
+
@property
|
|
422
|
+
def key(self) -> Any:
|
|
423
|
+
"""Key identifier for this event."""
|
|
424
|
+
return None
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
# Copyright (c) 2021-2026
|
|
3
|
+
"""
|
|
4
|
+
Event type definitions for the aiohomematic event system.
|
|
5
|
+
|
|
6
|
+
This module contains event dataclasses that are used across the codebase.
|
|
7
|
+
These events are defined separately from the EventBus to avoid circular
|
|
8
|
+
import dependencies.
|
|
9
|
+
|
|
10
|
+
All event types in this module:
|
|
11
|
+
- Are immutable dataclasses (frozen=True, slots=True)
|
|
12
|
+
- Inherit from the Event base class
|
|
13
|
+
- Have a `key` property for event routing
|
|
14
|
+
|
|
15
|
+
These events are re-exported from `central.events` for backward compatibility.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
from abc import ABC, abstractmethod
|
|
21
|
+
from dataclasses import dataclass
|
|
22
|
+
from datetime import datetime
|
|
23
|
+
from enum import IntEnum
|
|
24
|
+
from typing import Any
|
|
25
|
+
|
|
26
|
+
from aiohomematic.const import CentralState, CircuitState, ClientState
|
|
27
|
+
|
|
28
|
+
__all__ = [
|
|
29
|
+
"CentralStateChangedEvent",
|
|
30
|
+
"CircuitBreakerStateChangedEvent",
|
|
31
|
+
"CircuitBreakerTrippedEvent",
|
|
32
|
+
"ClientStateChangedEvent",
|
|
33
|
+
"Event",
|
|
34
|
+
"EventPriority",
|
|
35
|
+
"HealthRecordedEvent",
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class EventPriority(IntEnum):
|
|
40
|
+
"""
|
|
41
|
+
Priority levels for event handlers.
|
|
42
|
+
|
|
43
|
+
Higher priority handlers are called before lower priority handlers.
|
|
44
|
+
Handlers with the same priority are called in subscription order.
|
|
45
|
+
|
|
46
|
+
Use priorities sparingly - most handlers should use NORMAL priority.
|
|
47
|
+
Reserve CRITICAL for handlers that must run before all others (e.g., logging, metrics).
|
|
48
|
+
Reserve LOW for handlers that should run after all others (e.g., cleanup, notifications).
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
LOW = 0
|
|
52
|
+
"""Lowest priority - runs after all other handlers."""
|
|
53
|
+
|
|
54
|
+
NORMAL = 50
|
|
55
|
+
"""Default priority for most handlers."""
|
|
56
|
+
|
|
57
|
+
HIGH = 100
|
|
58
|
+
"""Higher priority - runs before NORMAL handlers."""
|
|
59
|
+
|
|
60
|
+
CRITICAL = 200
|
|
61
|
+
"""Highest priority - runs before all other handlers (e.g., logging, metrics)."""
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@dataclass(frozen=True, slots=True)
|
|
65
|
+
class Event(ABC):
|
|
66
|
+
"""
|
|
67
|
+
Base class for all events in the EventBus.
|
|
68
|
+
|
|
69
|
+
All events are immutable dataclasses with slots for memory efficiency.
|
|
70
|
+
The timestamp field is included in all events for debugging and auditing.
|
|
71
|
+
A key must be provided to uniquely identify the event.
|
|
72
|
+
"""
|
|
73
|
+
|
|
74
|
+
timestamp: datetime
|
|
75
|
+
|
|
76
|
+
@property
|
|
77
|
+
@abstractmethod
|
|
78
|
+
def key(self) -> Any:
|
|
79
|
+
"""Key identifier for this event."""
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@dataclass(frozen=True, slots=True)
|
|
83
|
+
class CircuitBreakerStateChangedEvent(Event):
|
|
84
|
+
"""
|
|
85
|
+
Circuit breaker state transition.
|
|
86
|
+
|
|
87
|
+
Key is interface_id.
|
|
88
|
+
|
|
89
|
+
Emitted when a circuit breaker transitions between states
|
|
90
|
+
(CLOSED, OPEN, HALF_OPEN).
|
|
91
|
+
"""
|
|
92
|
+
|
|
93
|
+
timestamp: datetime
|
|
94
|
+
interface_id: str
|
|
95
|
+
old_state: CircuitState
|
|
96
|
+
new_state: CircuitState
|
|
97
|
+
failure_count: int
|
|
98
|
+
success_count: int
|
|
99
|
+
last_failure_time: datetime | None
|
|
100
|
+
|
|
101
|
+
@property
|
|
102
|
+
def key(self) -> Any:
|
|
103
|
+
"""Key identifier for this event."""
|
|
104
|
+
return self.interface_id
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
@dataclass(frozen=True, slots=True)
|
|
108
|
+
class CircuitBreakerTrippedEvent(Event):
|
|
109
|
+
"""
|
|
110
|
+
Circuit breaker tripped (opened due to failures).
|
|
111
|
+
|
|
112
|
+
Key is interface_id.
|
|
113
|
+
|
|
114
|
+
Emitted when a circuit breaker transitions to OPEN state,
|
|
115
|
+
indicating repeated failures.
|
|
116
|
+
"""
|
|
117
|
+
|
|
118
|
+
timestamp: datetime
|
|
119
|
+
interface_id: str
|
|
120
|
+
failure_count: int
|
|
121
|
+
last_failure_reason: str | None
|
|
122
|
+
cooldown_seconds: float
|
|
123
|
+
|
|
124
|
+
@property
|
|
125
|
+
def key(self) -> Any:
|
|
126
|
+
"""Key identifier for this event."""
|
|
127
|
+
return self.interface_id
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
@dataclass(frozen=True, slots=True)
|
|
131
|
+
class ClientStateChangedEvent(Event):
|
|
132
|
+
"""
|
|
133
|
+
Client state machine transition.
|
|
134
|
+
|
|
135
|
+
Key is interface_id.
|
|
136
|
+
|
|
137
|
+
Emitted when a client transitions between states
|
|
138
|
+
(INIT, CONNECTED, DISCONNECTED, etc.).
|
|
139
|
+
"""
|
|
140
|
+
|
|
141
|
+
timestamp: datetime
|
|
142
|
+
interface_id: str
|
|
143
|
+
old_state: ClientState
|
|
144
|
+
new_state: ClientState
|
|
145
|
+
trigger: str | None
|
|
146
|
+
|
|
147
|
+
@property
|
|
148
|
+
def key(self) -> Any:
|
|
149
|
+
"""Key identifier for this event."""
|
|
150
|
+
return self.interface_id
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
@dataclass(frozen=True, slots=True)
|
|
154
|
+
class CentralStateChangedEvent(Event):
|
|
155
|
+
"""
|
|
156
|
+
Central unit state machine transition.
|
|
157
|
+
|
|
158
|
+
Key is central_name.
|
|
159
|
+
|
|
160
|
+
Emitted when the central unit transitions between states
|
|
161
|
+
(STARTING, RUNNING, DEGRADED, etc.).
|
|
162
|
+
"""
|
|
163
|
+
|
|
164
|
+
timestamp: datetime
|
|
165
|
+
central_name: str
|
|
166
|
+
old_state: CentralState
|
|
167
|
+
new_state: CentralState
|
|
168
|
+
trigger: str | None
|
|
169
|
+
|
|
170
|
+
@property
|
|
171
|
+
def key(self) -> Any:
|
|
172
|
+
"""Key identifier for this event."""
|
|
173
|
+
return self.central_name
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
@dataclass(frozen=True, slots=True)
|
|
177
|
+
class HealthRecordedEvent(Event):
|
|
178
|
+
"""
|
|
179
|
+
Health status recorded for an interface.
|
|
180
|
+
|
|
181
|
+
Key is interface_id.
|
|
182
|
+
|
|
183
|
+
Emitted by CircuitBreaker when a request succeeds or fails,
|
|
184
|
+
enabling health tracking without direct callback coupling.
|
|
185
|
+
"""
|
|
186
|
+
|
|
187
|
+
timestamp: datetime
|
|
188
|
+
interface_id: str
|
|
189
|
+
success: bool
|
|
190
|
+
|
|
191
|
+
@property
|
|
192
|
+
def key(self) -> Any:
|
|
193
|
+
"""Key identifier for this event."""
|
|
194
|
+
return self.interface_id
|