aiohomematic 2026.1.29__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (188) hide show
  1. aiohomematic/__init__.py +110 -0
  2. aiohomematic/_log_context_protocol.py +29 -0
  3. aiohomematic/api.py +410 -0
  4. aiohomematic/async_support.py +250 -0
  5. aiohomematic/backend_detection.py +462 -0
  6. aiohomematic/central/__init__.py +103 -0
  7. aiohomematic/central/async_rpc_server.py +760 -0
  8. aiohomematic/central/central_unit.py +1152 -0
  9. aiohomematic/central/config.py +463 -0
  10. aiohomematic/central/config_builder.py +772 -0
  11. aiohomematic/central/connection_state.py +160 -0
  12. aiohomematic/central/coordinators/__init__.py +38 -0
  13. aiohomematic/central/coordinators/cache.py +414 -0
  14. aiohomematic/central/coordinators/client.py +480 -0
  15. aiohomematic/central/coordinators/connection_recovery.py +1141 -0
  16. aiohomematic/central/coordinators/device.py +1166 -0
  17. aiohomematic/central/coordinators/event.py +514 -0
  18. aiohomematic/central/coordinators/hub.py +532 -0
  19. aiohomematic/central/decorators.py +184 -0
  20. aiohomematic/central/device_registry.py +229 -0
  21. aiohomematic/central/events/__init__.py +104 -0
  22. aiohomematic/central/events/bus.py +1392 -0
  23. aiohomematic/central/events/integration.py +424 -0
  24. aiohomematic/central/events/types.py +194 -0
  25. aiohomematic/central/health.py +762 -0
  26. aiohomematic/central/rpc_server.py +353 -0
  27. aiohomematic/central/scheduler.py +794 -0
  28. aiohomematic/central/state_machine.py +391 -0
  29. aiohomematic/client/__init__.py +203 -0
  30. aiohomematic/client/_rpc_errors.py +187 -0
  31. aiohomematic/client/backends/__init__.py +48 -0
  32. aiohomematic/client/backends/base.py +335 -0
  33. aiohomematic/client/backends/capabilities.py +138 -0
  34. aiohomematic/client/backends/ccu.py +487 -0
  35. aiohomematic/client/backends/factory.py +116 -0
  36. aiohomematic/client/backends/homegear.py +294 -0
  37. aiohomematic/client/backends/json_ccu.py +252 -0
  38. aiohomematic/client/backends/protocol.py +316 -0
  39. aiohomematic/client/ccu.py +1857 -0
  40. aiohomematic/client/circuit_breaker.py +459 -0
  41. aiohomematic/client/config.py +64 -0
  42. aiohomematic/client/handlers/__init__.py +40 -0
  43. aiohomematic/client/handlers/backup.py +157 -0
  44. aiohomematic/client/handlers/base.py +79 -0
  45. aiohomematic/client/handlers/device_ops.py +1085 -0
  46. aiohomematic/client/handlers/firmware.py +144 -0
  47. aiohomematic/client/handlers/link_mgmt.py +199 -0
  48. aiohomematic/client/handlers/metadata.py +436 -0
  49. aiohomematic/client/handlers/programs.py +144 -0
  50. aiohomematic/client/handlers/sysvars.py +100 -0
  51. aiohomematic/client/interface_client.py +1304 -0
  52. aiohomematic/client/json_rpc.py +2068 -0
  53. aiohomematic/client/request_coalescer.py +282 -0
  54. aiohomematic/client/rpc_proxy.py +629 -0
  55. aiohomematic/client/state_machine.py +324 -0
  56. aiohomematic/const.py +2207 -0
  57. aiohomematic/context.py +275 -0
  58. aiohomematic/converter.py +270 -0
  59. aiohomematic/decorators.py +390 -0
  60. aiohomematic/exceptions.py +185 -0
  61. aiohomematic/hmcli.py +997 -0
  62. aiohomematic/i18n.py +193 -0
  63. aiohomematic/interfaces/__init__.py +407 -0
  64. aiohomematic/interfaces/central.py +1067 -0
  65. aiohomematic/interfaces/client.py +1096 -0
  66. aiohomematic/interfaces/coordinators.py +63 -0
  67. aiohomematic/interfaces/model.py +1921 -0
  68. aiohomematic/interfaces/operations.py +217 -0
  69. aiohomematic/logging_context.py +134 -0
  70. aiohomematic/metrics/__init__.py +125 -0
  71. aiohomematic/metrics/_protocols.py +140 -0
  72. aiohomematic/metrics/aggregator.py +534 -0
  73. aiohomematic/metrics/dataclasses.py +489 -0
  74. aiohomematic/metrics/emitter.py +292 -0
  75. aiohomematic/metrics/events.py +183 -0
  76. aiohomematic/metrics/keys.py +300 -0
  77. aiohomematic/metrics/observer.py +563 -0
  78. aiohomematic/metrics/stats.py +172 -0
  79. aiohomematic/model/__init__.py +189 -0
  80. aiohomematic/model/availability.py +65 -0
  81. aiohomematic/model/calculated/__init__.py +89 -0
  82. aiohomematic/model/calculated/climate.py +276 -0
  83. aiohomematic/model/calculated/data_point.py +315 -0
  84. aiohomematic/model/calculated/field.py +147 -0
  85. aiohomematic/model/calculated/operating_voltage_level.py +286 -0
  86. aiohomematic/model/calculated/support.py +232 -0
  87. aiohomematic/model/custom/__init__.py +214 -0
  88. aiohomematic/model/custom/capabilities/__init__.py +67 -0
  89. aiohomematic/model/custom/capabilities/climate.py +41 -0
  90. aiohomematic/model/custom/capabilities/light.py +87 -0
  91. aiohomematic/model/custom/capabilities/lock.py +44 -0
  92. aiohomematic/model/custom/capabilities/siren.py +63 -0
  93. aiohomematic/model/custom/climate.py +1130 -0
  94. aiohomematic/model/custom/cover.py +722 -0
  95. aiohomematic/model/custom/data_point.py +360 -0
  96. aiohomematic/model/custom/definition.py +300 -0
  97. aiohomematic/model/custom/field.py +89 -0
  98. aiohomematic/model/custom/light.py +1174 -0
  99. aiohomematic/model/custom/lock.py +322 -0
  100. aiohomematic/model/custom/mixins.py +445 -0
  101. aiohomematic/model/custom/profile.py +945 -0
  102. aiohomematic/model/custom/registry.py +251 -0
  103. aiohomematic/model/custom/siren.py +462 -0
  104. aiohomematic/model/custom/switch.py +195 -0
  105. aiohomematic/model/custom/text_display.py +289 -0
  106. aiohomematic/model/custom/valve.py +78 -0
  107. aiohomematic/model/data_point.py +1416 -0
  108. aiohomematic/model/device.py +1840 -0
  109. aiohomematic/model/event.py +216 -0
  110. aiohomematic/model/generic/__init__.py +327 -0
  111. aiohomematic/model/generic/action.py +40 -0
  112. aiohomematic/model/generic/action_select.py +62 -0
  113. aiohomematic/model/generic/binary_sensor.py +30 -0
  114. aiohomematic/model/generic/button.py +31 -0
  115. aiohomematic/model/generic/data_point.py +177 -0
  116. aiohomematic/model/generic/dummy.py +150 -0
  117. aiohomematic/model/generic/number.py +76 -0
  118. aiohomematic/model/generic/select.py +56 -0
  119. aiohomematic/model/generic/sensor.py +76 -0
  120. aiohomematic/model/generic/switch.py +54 -0
  121. aiohomematic/model/generic/text.py +33 -0
  122. aiohomematic/model/hub/__init__.py +100 -0
  123. aiohomematic/model/hub/binary_sensor.py +24 -0
  124. aiohomematic/model/hub/button.py +28 -0
  125. aiohomematic/model/hub/connectivity.py +190 -0
  126. aiohomematic/model/hub/data_point.py +342 -0
  127. aiohomematic/model/hub/hub.py +864 -0
  128. aiohomematic/model/hub/inbox.py +135 -0
  129. aiohomematic/model/hub/install_mode.py +393 -0
  130. aiohomematic/model/hub/metrics.py +208 -0
  131. aiohomematic/model/hub/number.py +42 -0
  132. aiohomematic/model/hub/select.py +52 -0
  133. aiohomematic/model/hub/sensor.py +37 -0
  134. aiohomematic/model/hub/switch.py +43 -0
  135. aiohomematic/model/hub/text.py +30 -0
  136. aiohomematic/model/hub/update.py +221 -0
  137. aiohomematic/model/support.py +592 -0
  138. aiohomematic/model/update.py +140 -0
  139. aiohomematic/model/week_profile.py +1827 -0
  140. aiohomematic/property_decorators.py +719 -0
  141. aiohomematic/py.typed +0 -0
  142. aiohomematic/rega_scripts/accept_device_in_inbox.fn +51 -0
  143. aiohomematic/rega_scripts/create_backup_start.fn +28 -0
  144. aiohomematic/rega_scripts/create_backup_status.fn +89 -0
  145. aiohomematic/rega_scripts/fetch_all_device_data.fn +97 -0
  146. aiohomematic/rega_scripts/get_backend_info.fn +25 -0
  147. aiohomematic/rega_scripts/get_inbox_devices.fn +61 -0
  148. aiohomematic/rega_scripts/get_program_descriptions.fn +31 -0
  149. aiohomematic/rega_scripts/get_serial.fn +44 -0
  150. aiohomematic/rega_scripts/get_service_messages.fn +83 -0
  151. aiohomematic/rega_scripts/get_system_update_info.fn +39 -0
  152. aiohomematic/rega_scripts/get_system_variable_descriptions.fn +31 -0
  153. aiohomematic/rega_scripts/set_program_state.fn +17 -0
  154. aiohomematic/rega_scripts/set_system_variable.fn +19 -0
  155. aiohomematic/rega_scripts/trigger_firmware_update.fn +67 -0
  156. aiohomematic/schemas.py +256 -0
  157. aiohomematic/store/__init__.py +55 -0
  158. aiohomematic/store/dynamic/__init__.py +43 -0
  159. aiohomematic/store/dynamic/command.py +250 -0
  160. aiohomematic/store/dynamic/data.py +175 -0
  161. aiohomematic/store/dynamic/details.py +187 -0
  162. aiohomematic/store/dynamic/ping_pong.py +416 -0
  163. aiohomematic/store/persistent/__init__.py +71 -0
  164. aiohomematic/store/persistent/base.py +285 -0
  165. aiohomematic/store/persistent/device.py +233 -0
  166. aiohomematic/store/persistent/incident.py +380 -0
  167. aiohomematic/store/persistent/paramset.py +241 -0
  168. aiohomematic/store/persistent/session.py +556 -0
  169. aiohomematic/store/serialization.py +150 -0
  170. aiohomematic/store/storage.py +689 -0
  171. aiohomematic/store/types.py +526 -0
  172. aiohomematic/store/visibility/__init__.py +40 -0
  173. aiohomematic/store/visibility/parser.py +141 -0
  174. aiohomematic/store/visibility/registry.py +722 -0
  175. aiohomematic/store/visibility/rules.py +307 -0
  176. aiohomematic/strings.json +237 -0
  177. aiohomematic/support.py +706 -0
  178. aiohomematic/tracing.py +236 -0
  179. aiohomematic/translations/de.json +237 -0
  180. aiohomematic/translations/en.json +237 -0
  181. aiohomematic/type_aliases.py +51 -0
  182. aiohomematic/validator.py +128 -0
  183. aiohomematic-2026.1.29.dist-info/METADATA +296 -0
  184. aiohomematic-2026.1.29.dist-info/RECORD +188 -0
  185. aiohomematic-2026.1.29.dist-info/WHEEL +5 -0
  186. aiohomematic-2026.1.29.dist-info/entry_points.txt +2 -0
  187. aiohomematic-2026.1.29.dist-info/licenses/LICENSE +21 -0
  188. aiohomematic-2026.1.29.dist-info/top_level.txt +1 -0
@@ -0,0 +1,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