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,532 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
# Copyright (c) 2021-2026
|
|
3
|
+
"""
|
|
4
|
+
Hub coordinator for managing programs and system variables.
|
|
5
|
+
|
|
6
|
+
This module provides centralized management of system-level data points like
|
|
7
|
+
programs and system variables that are exposed through the Hub.
|
|
8
|
+
|
|
9
|
+
The HubCoordinator provides:
|
|
10
|
+
- Program data point management
|
|
11
|
+
- System variable data point management
|
|
12
|
+
- Hub data refresh coordination
|
|
13
|
+
- Program execution and state management
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
from collections.abc import Mapping
|
|
19
|
+
from datetime import datetime
|
|
20
|
+
import logging
|
|
21
|
+
from typing import Any, Final
|
|
22
|
+
|
|
23
|
+
from aiohomematic import i18n
|
|
24
|
+
from aiohomematic.central.events import ProgramExecutedEvent, SysvarStateChangedEvent
|
|
25
|
+
from aiohomematic.const import DataPointCategory, Interface, ProgramTrigger
|
|
26
|
+
from aiohomematic.decorators import inspector
|
|
27
|
+
from aiohomematic.interfaces import (
|
|
28
|
+
CentralInfoProtocol,
|
|
29
|
+
ChannelLookupProtocol,
|
|
30
|
+
ClientProviderProtocol,
|
|
31
|
+
ConfigProviderProtocol,
|
|
32
|
+
EventBusProviderProtocol,
|
|
33
|
+
EventPublisherProtocol,
|
|
34
|
+
GenericProgramDataPointProtocol,
|
|
35
|
+
GenericSysvarDataPointProtocol,
|
|
36
|
+
HealthTrackerProtocol,
|
|
37
|
+
HubDataFetcherProtocol,
|
|
38
|
+
HubDataPointManagerProtocol,
|
|
39
|
+
MetricsProviderProtocol,
|
|
40
|
+
ParameterVisibilityProviderProtocol,
|
|
41
|
+
ParamsetDescriptionProviderProtocol,
|
|
42
|
+
PrimaryClientProviderProtocol,
|
|
43
|
+
TaskSchedulerProtocol,
|
|
44
|
+
)
|
|
45
|
+
from aiohomematic.model.hub import ConnectivityDpType, Hub, InstallModeDpType, MetricsDpType, ProgramDpType
|
|
46
|
+
from aiohomematic.property_decorators import DelegatedProperty
|
|
47
|
+
from aiohomematic.type_aliases import UnsubscribeCallback
|
|
48
|
+
|
|
49
|
+
_LOGGER: Final = logging.getLogger(__name__)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class HubCoordinator(HubDataFetcherProtocol, HubDataPointManagerProtocol):
|
|
53
|
+
"""Coordinator for hub-level data points (programs and system variables)."""
|
|
54
|
+
|
|
55
|
+
__slots__ = (
|
|
56
|
+
"_central_info",
|
|
57
|
+
"_event_bus_provider",
|
|
58
|
+
"_hub",
|
|
59
|
+
"_primary_client_provider",
|
|
60
|
+
"_program_data_points",
|
|
61
|
+
"_state_path_to_name",
|
|
62
|
+
"_sysvar_data_points",
|
|
63
|
+
"_sysvar_unsubscribes",
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
def __init__(
|
|
67
|
+
self,
|
|
68
|
+
*,
|
|
69
|
+
central_info: CentralInfoProtocol,
|
|
70
|
+
channel_lookup: ChannelLookupProtocol,
|
|
71
|
+
client_provider: ClientProviderProtocol,
|
|
72
|
+
config_provider: ConfigProviderProtocol,
|
|
73
|
+
event_bus_provider: EventBusProviderProtocol,
|
|
74
|
+
event_publisher: EventPublisherProtocol,
|
|
75
|
+
health_tracker: HealthTrackerProtocol,
|
|
76
|
+
metrics_provider: MetricsProviderProtocol,
|
|
77
|
+
parameter_visibility_provider: ParameterVisibilityProviderProtocol,
|
|
78
|
+
paramset_description_provider: ParamsetDescriptionProviderProtocol,
|
|
79
|
+
primary_client_provider: PrimaryClientProviderProtocol,
|
|
80
|
+
task_scheduler: TaskSchedulerProtocol,
|
|
81
|
+
) -> None:
|
|
82
|
+
"""
|
|
83
|
+
Initialize the hub coordinator.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
----
|
|
87
|
+
central_info: Provider for central system information
|
|
88
|
+
channel_lookup: Provider for channel lookup operations
|
|
89
|
+
client_provider: Provider for client access by interface
|
|
90
|
+
config_provider: Provider for configuration access
|
|
91
|
+
event_bus_provider: Provider for event bus access
|
|
92
|
+
event_publisher: Provider for event emission
|
|
93
|
+
health_tracker: Provider for connection health tracking
|
|
94
|
+
metrics_provider: Provider for metrics aggregator access
|
|
95
|
+
parameter_visibility_provider: Provider for parameter visibility rules
|
|
96
|
+
paramset_description_provider: Provider for paramset descriptions
|
|
97
|
+
primary_client_provider: Provider for primary client access
|
|
98
|
+
task_scheduler: Scheduler for async tasks
|
|
99
|
+
|
|
100
|
+
"""
|
|
101
|
+
self._central_info: Final = central_info
|
|
102
|
+
self._event_bus_provider: Final = event_bus_provider
|
|
103
|
+
self._primary_client_provider: Final = primary_client_provider
|
|
104
|
+
|
|
105
|
+
# {sysvar_name, sysvar_data_point}
|
|
106
|
+
self._sysvar_data_points: Final[dict[str, GenericSysvarDataPointProtocol]] = {}
|
|
107
|
+
# {program_name, program_button}
|
|
108
|
+
self._program_data_points: Final[dict[str, ProgramDpType]] = {}
|
|
109
|
+
self._state_path_to_name: Final[dict[str, str]] = {}
|
|
110
|
+
# Unsubscribe callbacks for sysvar event subscriptions
|
|
111
|
+
self._sysvar_unsubscribes: Final[list[UnsubscribeCallback]] = []
|
|
112
|
+
|
|
113
|
+
# Create Hub with protocol interfaces
|
|
114
|
+
self._hub: Final = Hub(
|
|
115
|
+
central_info=central_info,
|
|
116
|
+
channel_lookup=channel_lookup,
|
|
117
|
+
client_provider=client_provider,
|
|
118
|
+
config_provider=config_provider,
|
|
119
|
+
event_bus_provider=event_bus_provider,
|
|
120
|
+
event_publisher=event_publisher,
|
|
121
|
+
health_tracker=health_tracker,
|
|
122
|
+
hub_data_fetcher=self,
|
|
123
|
+
hub_data_point_manager=self,
|
|
124
|
+
metrics_provider=metrics_provider,
|
|
125
|
+
parameter_visibility_provider=parameter_visibility_provider,
|
|
126
|
+
paramset_description_provider=paramset_description_provider,
|
|
127
|
+
primary_client_provider=primary_client_provider,
|
|
128
|
+
task_scheduler=task_scheduler,
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
connectivity_dps: Final = DelegatedProperty[Mapping[str, ConnectivityDpType]](path="_hub.connectivity_dps")
|
|
132
|
+
install_mode_dps: Final = DelegatedProperty[Mapping[Interface, InstallModeDpType]](path="_hub.install_mode_dps")
|
|
133
|
+
metrics_dps: Final = DelegatedProperty[MetricsDpType | None](path="_hub.metrics_dps")
|
|
134
|
+
|
|
135
|
+
@property
|
|
136
|
+
def data_point_paths(self) -> tuple[str, ...]:
|
|
137
|
+
"""Return the data point paths."""
|
|
138
|
+
return tuple(self._state_path_to_name.keys())
|
|
139
|
+
|
|
140
|
+
@property
|
|
141
|
+
def program_data_points(self) -> tuple[GenericProgramDataPointProtocol, ...]:
|
|
142
|
+
"""Return the program data points (both buttons and switches)."""
|
|
143
|
+
return tuple(
|
|
144
|
+
[x.button for x in self._program_data_points.values()]
|
|
145
|
+
+ [x.switch for x in self._program_data_points.values()]
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
@property
|
|
149
|
+
def sysvar_data_points(self) -> tuple[GenericSysvarDataPointProtocol, ...]:
|
|
150
|
+
"""Return the sysvar data points."""
|
|
151
|
+
return tuple(self._sysvar_data_points.values())
|
|
152
|
+
|
|
153
|
+
def add_program_data_point(self, *, program_dp: ProgramDpType) -> None:
|
|
154
|
+
"""
|
|
155
|
+
Add new program data point.
|
|
156
|
+
|
|
157
|
+
Args:
|
|
158
|
+
----
|
|
159
|
+
program_dp: Program data point to add
|
|
160
|
+
|
|
161
|
+
"""
|
|
162
|
+
self._program_data_points[program_dp.pid] = program_dp
|
|
163
|
+
self._state_path_to_name[program_dp.button.state_path] = program_dp.pid
|
|
164
|
+
_LOGGER.debug(
|
|
165
|
+
"ADD_PROGRAM_DATA_POINT: Added program %s to %s",
|
|
166
|
+
program_dp.pid,
|
|
167
|
+
self._central_info.name,
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
def add_sysvar_data_point(self, *, sysvar_data_point: GenericSysvarDataPointProtocol) -> None:
|
|
171
|
+
"""
|
|
172
|
+
Add new system variable data point.
|
|
173
|
+
|
|
174
|
+
Args:
|
|
175
|
+
----
|
|
176
|
+
sysvar_data_point: System variable data point to add
|
|
177
|
+
|
|
178
|
+
"""
|
|
179
|
+
if (vid := sysvar_data_point.vid) is not None:
|
|
180
|
+
self._sysvar_data_points[vid] = sysvar_data_point
|
|
181
|
+
_LOGGER.debug(
|
|
182
|
+
"ADD_SYSVAR_DATA_POINT: Added sysvar %s to %s",
|
|
183
|
+
vid,
|
|
184
|
+
self._central_info.name,
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
self._state_path_to_name[sysvar_data_point.state_path] = sysvar_data_point.vid
|
|
188
|
+
|
|
189
|
+
# Add event subscription for this sysvar via EventBus with filtering
|
|
190
|
+
async def event_handler(*, event: SysvarStateChangedEvent) -> None:
|
|
191
|
+
"""Filter and handle sysvar events."""
|
|
192
|
+
if event.state_path == sysvar_data_point.state_path:
|
|
193
|
+
await sysvar_data_point.event(value=event.value, received_at=event.received_at)
|
|
194
|
+
|
|
195
|
+
self._sysvar_unsubscribes.append(
|
|
196
|
+
self._event_bus_provider.event_bus.subscribe(
|
|
197
|
+
event_type=SysvarStateChangedEvent, event_key=sysvar_data_point.state_path, handler=event_handler
|
|
198
|
+
)
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
def clear(self) -> None:
|
|
202
|
+
"""
|
|
203
|
+
Clear all sysvar event subscriptions.
|
|
204
|
+
|
|
205
|
+
Call this method when stopping the central unit to prevent leaked subscriptions.
|
|
206
|
+
"""
|
|
207
|
+
for unsubscribe in self._sysvar_unsubscribes:
|
|
208
|
+
unsubscribe()
|
|
209
|
+
self._sysvar_unsubscribes.clear()
|
|
210
|
+
_LOGGER.debug("CLEAR: Cleared %s sysvar event subscriptions", self._central_info.name)
|
|
211
|
+
|
|
212
|
+
def create_install_mode_dps(self) -> Mapping[Interface, InstallModeDpType]:
|
|
213
|
+
"""
|
|
214
|
+
Create install mode data points for all supported interfaces.
|
|
215
|
+
|
|
216
|
+
Returns a dict of InstallModeDpType by Interface.
|
|
217
|
+
"""
|
|
218
|
+
return self._hub.create_install_mode_dps()
|
|
219
|
+
|
|
220
|
+
async def execute_program(self, *, pid: str) -> bool:
|
|
221
|
+
"""
|
|
222
|
+
Execute a program on the backend.
|
|
223
|
+
|
|
224
|
+
Args:
|
|
225
|
+
----
|
|
226
|
+
pid: Program identifier
|
|
227
|
+
|
|
228
|
+
Returns:
|
|
229
|
+
-------
|
|
230
|
+
True if execution succeeded, False otherwise
|
|
231
|
+
|
|
232
|
+
"""
|
|
233
|
+
if client := self._primary_client_provider.primary_client:
|
|
234
|
+
success = await client.execute_program(pid=pid)
|
|
235
|
+
|
|
236
|
+
# Emit program executed event
|
|
237
|
+
program_name = pid # Default to pid if name not found
|
|
238
|
+
if program_dp := self._program_data_points.get(pid):
|
|
239
|
+
program_name = program_dp.button.name
|
|
240
|
+
|
|
241
|
+
await self._event_bus_provider.event_bus.publish(
|
|
242
|
+
event=ProgramExecutedEvent(
|
|
243
|
+
timestamp=datetime.now(),
|
|
244
|
+
program_id=pid,
|
|
245
|
+
program_name=program_name,
|
|
246
|
+
triggered_by=ProgramTrigger.API,
|
|
247
|
+
success=success,
|
|
248
|
+
)
|
|
249
|
+
)
|
|
250
|
+
return success
|
|
251
|
+
return False
|
|
252
|
+
|
|
253
|
+
def fetch_connectivity_data(self, *, scheduled: bool) -> None:
|
|
254
|
+
"""
|
|
255
|
+
Refresh connectivity binary sensors with current values.
|
|
256
|
+
|
|
257
|
+
Args:
|
|
258
|
+
----
|
|
259
|
+
scheduled: Whether this is a scheduled refresh
|
|
260
|
+
|
|
261
|
+
"""
|
|
262
|
+
self._hub.fetch_connectivity_data(scheduled=scheduled)
|
|
263
|
+
|
|
264
|
+
@inspector(re_raise=False)
|
|
265
|
+
async def fetch_inbox_data(self, *, scheduled: bool) -> None:
|
|
266
|
+
"""
|
|
267
|
+
Fetch inbox data from the backend.
|
|
268
|
+
|
|
269
|
+
Args:
|
|
270
|
+
----
|
|
271
|
+
scheduled: Whether this is a scheduled refresh
|
|
272
|
+
|
|
273
|
+
"""
|
|
274
|
+
await self._hub.fetch_inbox_data(scheduled=scheduled)
|
|
275
|
+
|
|
276
|
+
@inspector(re_raise=False)
|
|
277
|
+
async def fetch_install_mode_data(self, *, scheduled: bool) -> None:
|
|
278
|
+
"""
|
|
279
|
+
Fetch install mode data from the backend.
|
|
280
|
+
|
|
281
|
+
Args:
|
|
282
|
+
----
|
|
283
|
+
scheduled: Whether this is a scheduled refresh
|
|
284
|
+
|
|
285
|
+
"""
|
|
286
|
+
await self._hub.fetch_install_mode_data(scheduled=scheduled)
|
|
287
|
+
|
|
288
|
+
def fetch_metrics_data(self, *, scheduled: bool) -> None:
|
|
289
|
+
"""
|
|
290
|
+
Refresh metrics hub sensors with current values.
|
|
291
|
+
|
|
292
|
+
Args:
|
|
293
|
+
----
|
|
294
|
+
scheduled: Whether this is a scheduled refresh
|
|
295
|
+
|
|
296
|
+
"""
|
|
297
|
+
self._hub.fetch_metrics_data(scheduled=scheduled)
|
|
298
|
+
|
|
299
|
+
@inspector(re_raise=False)
|
|
300
|
+
async def fetch_program_data(self, *, scheduled: bool) -> None:
|
|
301
|
+
"""
|
|
302
|
+
Fetch program data from the backend.
|
|
303
|
+
|
|
304
|
+
Args:
|
|
305
|
+
----
|
|
306
|
+
scheduled: Whether this is a scheduled refresh
|
|
307
|
+
|
|
308
|
+
"""
|
|
309
|
+
await self._hub.fetch_program_data(scheduled=scheduled)
|
|
310
|
+
|
|
311
|
+
@inspector(re_raise=False)
|
|
312
|
+
async def fetch_system_update_data(self, *, scheduled: bool) -> None:
|
|
313
|
+
"""
|
|
314
|
+
Fetch system update data from the backend.
|
|
315
|
+
|
|
316
|
+
Args:
|
|
317
|
+
----
|
|
318
|
+
scheduled: Whether this is a scheduled refresh
|
|
319
|
+
|
|
320
|
+
"""
|
|
321
|
+
await self._hub.fetch_system_update_data(scheduled=scheduled)
|
|
322
|
+
|
|
323
|
+
@inspector(re_raise=False)
|
|
324
|
+
async def fetch_sysvar_data(self, *, scheduled: bool) -> None:
|
|
325
|
+
"""
|
|
326
|
+
Fetch system variable data from the backend.
|
|
327
|
+
|
|
328
|
+
Args:
|
|
329
|
+
----
|
|
330
|
+
scheduled: Whether this is a scheduled refresh
|
|
331
|
+
|
|
332
|
+
"""
|
|
333
|
+
await self._hub.fetch_sysvar_data(scheduled=scheduled)
|
|
334
|
+
|
|
335
|
+
def get_hub_data_points(
|
|
336
|
+
self, *, category: DataPointCategory | None = None, registered: bool | None = None
|
|
337
|
+
) -> tuple[GenericProgramDataPointProtocol | GenericSysvarDataPointProtocol, ...]:
|
|
338
|
+
"""
|
|
339
|
+
Return the hub data points (programs and sysvars) filtered by category and registration.
|
|
340
|
+
|
|
341
|
+
Args:
|
|
342
|
+
----
|
|
343
|
+
category: Optional category to filter by
|
|
344
|
+
registered: Optional registration status to filter by
|
|
345
|
+
|
|
346
|
+
Returns:
|
|
347
|
+
-------
|
|
348
|
+
Tuple of matching hub data points
|
|
349
|
+
|
|
350
|
+
"""
|
|
351
|
+
return tuple(
|
|
352
|
+
he
|
|
353
|
+
for he in (self.program_data_points + self.sysvar_data_points)
|
|
354
|
+
if (category is None or he.category == category) and (registered is None or he.is_registered == registered)
|
|
355
|
+
)
|
|
356
|
+
|
|
357
|
+
def get_program_data_point(
|
|
358
|
+
self, *, pid: str | None = None, legacy_name: str | None = None, state_path: str | None = None
|
|
359
|
+
) -> ProgramDpType | None:
|
|
360
|
+
"""
|
|
361
|
+
Return a program data point by ID or legacy name.
|
|
362
|
+
|
|
363
|
+
Args:
|
|
364
|
+
----
|
|
365
|
+
pid: Program identifier
|
|
366
|
+
legacy_name: Legacy name of the program
|
|
367
|
+
state_path: State path of the program
|
|
368
|
+
|
|
369
|
+
Returns:
|
|
370
|
+
-------
|
|
371
|
+
Program data point or None if not found
|
|
372
|
+
|
|
373
|
+
"""
|
|
374
|
+
if state_path and (pid := self._state_path_to_name.get(state_path)):
|
|
375
|
+
return self.get_program_data_point(pid=pid)
|
|
376
|
+
|
|
377
|
+
if pid and (program := self._program_data_points.get(pid)):
|
|
378
|
+
return program
|
|
379
|
+
if legacy_name:
|
|
380
|
+
for program in self._program_data_points.values():
|
|
381
|
+
if legacy_name in (program.button.legacy_name, program.switch.legacy_name):
|
|
382
|
+
return program
|
|
383
|
+
return None
|
|
384
|
+
|
|
385
|
+
async def get_system_variable(self, *, legacy_name: str) -> Any | None:
|
|
386
|
+
"""
|
|
387
|
+
Get system variable value from the backend.
|
|
388
|
+
|
|
389
|
+
Args:
|
|
390
|
+
----
|
|
391
|
+
legacy_name: Legacy name of the system variable
|
|
392
|
+
|
|
393
|
+
Returns:
|
|
394
|
+
-------
|
|
395
|
+
Current value of the system variable or None
|
|
396
|
+
|
|
397
|
+
"""
|
|
398
|
+
if client := self._primary_client_provider.primary_client:
|
|
399
|
+
return await client.get_system_variable(name=legacy_name)
|
|
400
|
+
return None
|
|
401
|
+
|
|
402
|
+
def get_sysvar_data_point(
|
|
403
|
+
self, *, vid: str | None = None, legacy_name: str | None = None, state_path: str | None = None
|
|
404
|
+
) -> GenericSysvarDataPointProtocol | None:
|
|
405
|
+
"""
|
|
406
|
+
Return a system variable data point by ID or legacy name.
|
|
407
|
+
|
|
408
|
+
Args:
|
|
409
|
+
----
|
|
410
|
+
vid: System variable identifier
|
|
411
|
+
legacy_name: Legacy name of the system variable
|
|
412
|
+
state_path: State path of the system variable
|
|
413
|
+
|
|
414
|
+
Returns:
|
|
415
|
+
-------
|
|
416
|
+
System variable data point or None if not found
|
|
417
|
+
|
|
418
|
+
"""
|
|
419
|
+
if state_path and (vid := self._state_path_to_name.get(state_path)):
|
|
420
|
+
return self.get_sysvar_data_point(vid=vid)
|
|
421
|
+
|
|
422
|
+
if vid and (sysvar := self._sysvar_data_points.get(vid)):
|
|
423
|
+
return sysvar
|
|
424
|
+
if legacy_name:
|
|
425
|
+
for sysvar in self._sysvar_data_points.values():
|
|
426
|
+
if sysvar.legacy_name == legacy_name:
|
|
427
|
+
return sysvar
|
|
428
|
+
return None
|
|
429
|
+
|
|
430
|
+
async def init_hub(self) -> None:
|
|
431
|
+
"""Initialize the hub by fetching program, sysvar, inbox, install mode, metrics, and connectivity data."""
|
|
432
|
+
_LOGGER.debug("INIT_HUB: Initializing hub for %s", self._central_info.name)
|
|
433
|
+
await self._hub.fetch_program_data(scheduled=True)
|
|
434
|
+
await self._hub.fetch_sysvar_data(scheduled=True)
|
|
435
|
+
await self._hub.fetch_inbox_data(scheduled=False)
|
|
436
|
+
await self._hub.init_install_mode()
|
|
437
|
+
self._hub.init_metrics()
|
|
438
|
+
self._hub.init_connectivity()
|
|
439
|
+
|
|
440
|
+
async def init_install_mode(self) -> Mapping[Interface, InstallModeDpType]:
|
|
441
|
+
"""
|
|
442
|
+
Initialize install mode data points for all supported interfaces.
|
|
443
|
+
|
|
444
|
+
Creates data points, fetches initial state from backend, and publishes refresh event.
|
|
445
|
+
Returns a dict of InstallModeDpType by Interface.
|
|
446
|
+
"""
|
|
447
|
+
return await self._hub.init_install_mode()
|
|
448
|
+
|
|
449
|
+
def publish_install_mode_refreshed(self) -> None:
|
|
450
|
+
"""Publish HUB_REFRESHED event for install mode data points."""
|
|
451
|
+
self._hub.publish_install_mode_refreshed()
|
|
452
|
+
|
|
453
|
+
def remove_program_button(self, *, pid: str) -> None:
|
|
454
|
+
"""
|
|
455
|
+
Remove a program button.
|
|
456
|
+
|
|
457
|
+
Args:
|
|
458
|
+
----
|
|
459
|
+
pid: Program identifier
|
|
460
|
+
|
|
461
|
+
"""
|
|
462
|
+
if (program_dp := self.get_program_data_point(pid=pid)) is not None:
|
|
463
|
+
program_dp.button.publish_device_removed_event()
|
|
464
|
+
program_dp.switch.publish_device_removed_event()
|
|
465
|
+
self._program_data_points.pop(pid, None)
|
|
466
|
+
self._state_path_to_name.pop(program_dp.button.state_path, None)
|
|
467
|
+
_LOGGER.debug(
|
|
468
|
+
"REMOVE_PROGRAM_BUTTON: Removed program %s from %s",
|
|
469
|
+
pid,
|
|
470
|
+
self._central_info.name,
|
|
471
|
+
)
|
|
472
|
+
|
|
473
|
+
def remove_sysvar_data_point(self, *, vid: str) -> None:
|
|
474
|
+
"""
|
|
475
|
+
Remove a system variable data point.
|
|
476
|
+
|
|
477
|
+
Args:
|
|
478
|
+
----
|
|
479
|
+
vid: System variable identifier
|
|
480
|
+
|
|
481
|
+
"""
|
|
482
|
+
if (sysvar_dp := self.get_sysvar_data_point(vid=vid)) is not None:
|
|
483
|
+
sysvar_dp.publish_device_removed_event()
|
|
484
|
+
self._sysvar_data_points.pop(vid, None)
|
|
485
|
+
self._state_path_to_name.pop(sysvar_dp.state_path, None)
|
|
486
|
+
|
|
487
|
+
# Note: Event subscriptions are cleaned up via clear() when central stops
|
|
488
|
+
|
|
489
|
+
_LOGGER.debug(
|
|
490
|
+
"REMOVE_SYSVAR_DATA_POINT: Removed sysvar %s from %s",
|
|
491
|
+
vid,
|
|
492
|
+
self._central_info.name,
|
|
493
|
+
)
|
|
494
|
+
|
|
495
|
+
async def set_program_state(self, *, pid: str, state: bool) -> bool:
|
|
496
|
+
"""
|
|
497
|
+
Set program state on the backend.
|
|
498
|
+
|
|
499
|
+
Args:
|
|
500
|
+
----
|
|
501
|
+
pid: Program identifier
|
|
502
|
+
state: New program state
|
|
503
|
+
|
|
504
|
+
Returns:
|
|
505
|
+
-------
|
|
506
|
+
True if setting succeeded, False otherwise
|
|
507
|
+
|
|
508
|
+
"""
|
|
509
|
+
if client := self._primary_client_provider.primary_client:
|
|
510
|
+
return await client.set_program_state(pid=pid, state=state)
|
|
511
|
+
return False
|
|
512
|
+
|
|
513
|
+
async def set_system_variable(self, *, legacy_name: str, value: Any) -> None:
|
|
514
|
+
"""
|
|
515
|
+
Set system variable value on the backend.
|
|
516
|
+
|
|
517
|
+
Args:
|
|
518
|
+
----
|
|
519
|
+
legacy_name: Legacy name of the system variable
|
|
520
|
+
value: New value
|
|
521
|
+
|
|
522
|
+
"""
|
|
523
|
+
if dp := self.get_sysvar_data_point(legacy_name=legacy_name):
|
|
524
|
+
await dp.send_variable(value=value)
|
|
525
|
+
else:
|
|
526
|
+
_LOGGER.error(
|
|
527
|
+
i18n.tr(
|
|
528
|
+
key="log.central.set_system_variable.not_found",
|
|
529
|
+
legacy_name=legacy_name,
|
|
530
|
+
name=self._central_info.name,
|
|
531
|
+
)
|
|
532
|
+
)
|