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,208 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
# Copyright (c) 2021-2026
|
|
3
|
+
"""
|
|
4
|
+
Hub sensors for system metrics.
|
|
5
|
+
|
|
6
|
+
This module provides hub sensor data points for exposing key system metrics
|
|
7
|
+
to Home Assistant. These sensors allow monitoring of system health,
|
|
8
|
+
connection latency, and event timing without needing separate diagnostic entities.
|
|
9
|
+
|
|
10
|
+
Public API
|
|
11
|
+
----------
|
|
12
|
+
- HmSystemHealthSensor: Overall system health score (0-100%)
|
|
13
|
+
- HmConnectionLatencySensor: Average RPC connection latency in milliseconds
|
|
14
|
+
- HmLastEventAgeSensor: Seconds since last backend event
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
from datetime import datetime
|
|
20
|
+
import logging
|
|
21
|
+
from typing import TYPE_CHECKING, Final
|
|
22
|
+
|
|
23
|
+
from slugify import slugify
|
|
24
|
+
|
|
25
|
+
from aiohomematic.const import (
|
|
26
|
+
HUB_ADDRESS,
|
|
27
|
+
METRICS_SENSOR_CONNECTION_LATENCY_NAME,
|
|
28
|
+
METRICS_SENSOR_LAST_EVENT_AGE_NAME,
|
|
29
|
+
METRICS_SENSOR_SYSTEM_HEALTH_NAME,
|
|
30
|
+
DataPointCategory,
|
|
31
|
+
HubValueType,
|
|
32
|
+
)
|
|
33
|
+
from aiohomematic.interfaces import (
|
|
34
|
+
CentralInfoProtocol,
|
|
35
|
+
ChannelProtocol,
|
|
36
|
+
ConfigProviderProtocol,
|
|
37
|
+
EventBusProviderProtocol,
|
|
38
|
+
EventPublisherProtocol,
|
|
39
|
+
HubSensorDataPointProtocol,
|
|
40
|
+
ParameterVisibilityProviderProtocol,
|
|
41
|
+
ParamsetDescriptionProviderProtocol,
|
|
42
|
+
TaskSchedulerProtocol,
|
|
43
|
+
)
|
|
44
|
+
from aiohomematic.model.data_point import CallbackDataPoint
|
|
45
|
+
from aiohomematic.model.support import HubPathData, PathData, generate_unique_id, get_hub_data_point_name_data
|
|
46
|
+
from aiohomematic.property_decorators import DelegatedProperty, Kind, state_property
|
|
47
|
+
from aiohomematic.support import PayloadMixin
|
|
48
|
+
|
|
49
|
+
if TYPE_CHECKING:
|
|
50
|
+
from aiohomematic.metrics import MetricsObserver
|
|
51
|
+
|
|
52
|
+
_LOGGER: Final = logging.getLogger(__name__)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class _BaseMetricsSensor(CallbackDataPoint, HubSensorDataPointProtocol, PayloadMixin):
|
|
56
|
+
"""Base class for metrics hub sensors."""
|
|
57
|
+
|
|
58
|
+
__slots__ = (
|
|
59
|
+
"_cached_value",
|
|
60
|
+
"_metrics_observer",
|
|
61
|
+
"_name_data",
|
|
62
|
+
"_state_uncertain",
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
_category = DataPointCategory.HUB_SENSOR
|
|
66
|
+
_enabled_default = True
|
|
67
|
+
_sensor_name: str
|
|
68
|
+
_unit: str
|
|
69
|
+
|
|
70
|
+
def __init__(
|
|
71
|
+
self,
|
|
72
|
+
*,
|
|
73
|
+
metrics_observer: MetricsObserver,
|
|
74
|
+
config_provider: ConfigProviderProtocol,
|
|
75
|
+
central_info: CentralInfoProtocol,
|
|
76
|
+
event_bus_provider: EventBusProviderProtocol,
|
|
77
|
+
event_publisher: EventPublisherProtocol,
|
|
78
|
+
task_scheduler: TaskSchedulerProtocol,
|
|
79
|
+
paramset_description_provider: ParamsetDescriptionProviderProtocol,
|
|
80
|
+
parameter_visibility_provider: ParameterVisibilityProviderProtocol,
|
|
81
|
+
) -> None:
|
|
82
|
+
"""Initialize the metrics sensor."""
|
|
83
|
+
PayloadMixin.__init__(self)
|
|
84
|
+
self._metrics_observer: Final = metrics_observer
|
|
85
|
+
unique_id: Final = generate_unique_id(
|
|
86
|
+
config_provider=config_provider,
|
|
87
|
+
address=HUB_ADDRESS,
|
|
88
|
+
parameter=slugify(self._sensor_name),
|
|
89
|
+
)
|
|
90
|
+
self._name_data: Final = get_hub_data_point_name_data(
|
|
91
|
+
channel=None, legacy_name=self._sensor_name, central_name=central_info.name
|
|
92
|
+
)
|
|
93
|
+
super().__init__(
|
|
94
|
+
unique_id=unique_id,
|
|
95
|
+
central_info=central_info,
|
|
96
|
+
event_bus_provider=event_bus_provider,
|
|
97
|
+
event_publisher=event_publisher,
|
|
98
|
+
task_scheduler=task_scheduler,
|
|
99
|
+
paramset_description_provider=paramset_description_provider,
|
|
100
|
+
parameter_visibility_provider=parameter_visibility_provider,
|
|
101
|
+
)
|
|
102
|
+
self._state_uncertain: bool = True
|
|
103
|
+
self._cached_value: float = 0.0
|
|
104
|
+
|
|
105
|
+
available: Final = DelegatedProperty[bool](path="_central_info.available", kind=Kind.STATE)
|
|
106
|
+
enabled_default: Final = DelegatedProperty[bool](path="_enabled_default")
|
|
107
|
+
full_name: Final = DelegatedProperty[str](path="_name_data.full_name")
|
|
108
|
+
name: Final = DelegatedProperty[str](path="_name_data.name", kind=Kind.CONFIG)
|
|
109
|
+
state_uncertain: Final = DelegatedProperty[bool](path="_state_uncertain")
|
|
110
|
+
unit: Final = DelegatedProperty[str](path="_unit", kind=Kind.CONFIG)
|
|
111
|
+
|
|
112
|
+
@property
|
|
113
|
+
def channel(self) -> ChannelProtocol | None:
|
|
114
|
+
"""Return the identified channel."""
|
|
115
|
+
return None
|
|
116
|
+
|
|
117
|
+
@property
|
|
118
|
+
def data_type(self) -> HubValueType | None:
|
|
119
|
+
"""Return the data type of the sensor."""
|
|
120
|
+
return HubValueType.FLOAT
|
|
121
|
+
|
|
122
|
+
@property
|
|
123
|
+
def description(self) -> str | None:
|
|
124
|
+
"""Return data point description."""
|
|
125
|
+
return None
|
|
126
|
+
|
|
127
|
+
@property
|
|
128
|
+
def legacy_name(self) -> str | None:
|
|
129
|
+
"""Return the original name."""
|
|
130
|
+
return None
|
|
131
|
+
|
|
132
|
+
@state_property
|
|
133
|
+
def value(self) -> float:
|
|
134
|
+
"""Return the system health score as percentage (0-100)."""
|
|
135
|
+
return self._get_current_value()
|
|
136
|
+
|
|
137
|
+
def refresh(self, *, write_at: datetime) -> None:
|
|
138
|
+
"""Refresh the sensor value from metrics observer."""
|
|
139
|
+
current_value = self._get_current_value()
|
|
140
|
+
if self._cached_value != current_value:
|
|
141
|
+
self._cached_value = current_value
|
|
142
|
+
self._set_modified_at(modified_at=write_at)
|
|
143
|
+
else:
|
|
144
|
+
self._set_refreshed_at(refreshed_at=write_at)
|
|
145
|
+
self._state_uncertain = False
|
|
146
|
+
self.publish_data_point_updated_event()
|
|
147
|
+
|
|
148
|
+
def _get_current_value(self) -> float:
|
|
149
|
+
"""Return the current metric value. Override in subclasses."""
|
|
150
|
+
raise NotImplementedError
|
|
151
|
+
|
|
152
|
+
def _get_path_data(self) -> PathData:
|
|
153
|
+
"""Return the path data of the data_point."""
|
|
154
|
+
return HubPathData(name=slugify(self._sensor_name))
|
|
155
|
+
|
|
156
|
+
def _get_signature(self) -> str:
|
|
157
|
+
"""Return the signature of the data_point."""
|
|
158
|
+
return f"{self._category}/{self.name}"
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
class HmSystemHealthSensor(_BaseMetricsSensor):
|
|
162
|
+
"""
|
|
163
|
+
Hub sensor for system health score.
|
|
164
|
+
|
|
165
|
+
Exposes the overall system health as a percentage (0-100%).
|
|
166
|
+
The health score is derived from client connection states.
|
|
167
|
+
"""
|
|
168
|
+
|
|
169
|
+
__slots__ = ()
|
|
170
|
+
_sensor_name = METRICS_SENSOR_SYSTEM_HEALTH_NAME
|
|
171
|
+
_unit = "%"
|
|
172
|
+
|
|
173
|
+
def _get_current_value(self) -> float:
|
|
174
|
+
"""Return the current health score as percentage."""
|
|
175
|
+
return round(self._metrics_observer.get_overall_health_score() * 100, 1)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
class HmConnectionLatencySensor(_BaseMetricsSensor):
|
|
179
|
+
"""
|
|
180
|
+
Hub sensor for connection latency.
|
|
181
|
+
|
|
182
|
+
Exposes the average RPC connection latency in milliseconds.
|
|
183
|
+
"""
|
|
184
|
+
|
|
185
|
+
__slots__ = ()
|
|
186
|
+
_sensor_name = METRICS_SENSOR_CONNECTION_LATENCY_NAME
|
|
187
|
+
_unit = "ms"
|
|
188
|
+
|
|
189
|
+
def _get_current_value(self) -> float:
|
|
190
|
+
"""Return the current average latency from ping/pong metrics."""
|
|
191
|
+
return round(self._metrics_observer.get_aggregated_latency(pattern="ping_pong").avg_ms, 1)
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
class HmLastEventAgeSensor(_BaseMetricsSensor):
|
|
195
|
+
"""
|
|
196
|
+
Hub sensor for last event age.
|
|
197
|
+
|
|
198
|
+
Exposes the time in seconds since the last backend event was received.
|
|
199
|
+
A value of -1 indicates no events have been received yet.
|
|
200
|
+
"""
|
|
201
|
+
|
|
202
|
+
__slots__ = ()
|
|
203
|
+
_sensor_name = METRICS_SENSOR_LAST_EVENT_AGE_NAME
|
|
204
|
+
_unit = "s"
|
|
205
|
+
|
|
206
|
+
def _get_current_value(self) -> float:
|
|
207
|
+
"""Return the current last event age in seconds."""
|
|
208
|
+
return round(self._metrics_observer.get_last_event_age_seconds(), 1)
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
# Copyright (c) 2021-2026
|
|
3
|
+
"""Module for data points implemented using the number category."""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import logging
|
|
8
|
+
from typing import Final
|
|
9
|
+
|
|
10
|
+
from aiohomematic import i18n
|
|
11
|
+
from aiohomematic.const import DataPointCategory
|
|
12
|
+
from aiohomematic.decorators import inspector
|
|
13
|
+
from aiohomematic.model.hub.data_point import GenericSysvarDataPoint
|
|
14
|
+
|
|
15
|
+
_LOGGER: Final = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class SysvarDpNumber(GenericSysvarDataPoint):
|
|
19
|
+
"""Implementation of a sysvar number."""
|
|
20
|
+
|
|
21
|
+
__slots__ = ()
|
|
22
|
+
|
|
23
|
+
_category = DataPointCategory.HUB_NUMBER
|
|
24
|
+
_is_extended = True
|
|
25
|
+
|
|
26
|
+
@inspector
|
|
27
|
+
async def send_variable(self, *, value: float) -> None:
|
|
28
|
+
"""Set the value of the data_point."""
|
|
29
|
+
if value is not None and self.max is not None and self.min is not None:
|
|
30
|
+
if self.min <= float(value) <= self.max:
|
|
31
|
+
await super().send_variable(value=value)
|
|
32
|
+
else:
|
|
33
|
+
_LOGGER.error(
|
|
34
|
+
i18n.tr(
|
|
35
|
+
key="exception.model.hub.number.invalid_value",
|
|
36
|
+
value=value,
|
|
37
|
+
min=self.min,
|
|
38
|
+
max=self.max,
|
|
39
|
+
)
|
|
40
|
+
)
|
|
41
|
+
return
|
|
42
|
+
await super().send_variable(value=value)
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
# Copyright (c) 2021-2026
|
|
3
|
+
"""Module for hub data points implemented using the select category."""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import logging
|
|
8
|
+
from typing import Final
|
|
9
|
+
|
|
10
|
+
from aiohomematic import i18n
|
|
11
|
+
from aiohomematic.const import DataPointCategory
|
|
12
|
+
from aiohomematic.decorators import inspector
|
|
13
|
+
from aiohomematic.model.hub.data_point import GenericSysvarDataPoint
|
|
14
|
+
from aiohomematic.model.support import get_value_from_value_list
|
|
15
|
+
from aiohomematic.property_decorators import state_property
|
|
16
|
+
|
|
17
|
+
_LOGGER: Final = logging.getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class SysvarDpSelect(GenericSysvarDataPoint):
|
|
21
|
+
"""Implementation of a sysvar select data_point."""
|
|
22
|
+
|
|
23
|
+
__slots__ = ()
|
|
24
|
+
|
|
25
|
+
_category = DataPointCategory.HUB_SELECT
|
|
26
|
+
_is_extended = True
|
|
27
|
+
|
|
28
|
+
@state_property
|
|
29
|
+
def value(self) -> str | None:
|
|
30
|
+
"""Get the value of the data_point."""
|
|
31
|
+
if (value := get_value_from_value_list(value=self._value, value_list=self.values)) is not None:
|
|
32
|
+
return value
|
|
33
|
+
return None
|
|
34
|
+
|
|
35
|
+
@inspector
|
|
36
|
+
async def send_variable(self, *, value: int | str) -> None:
|
|
37
|
+
"""Set the value of the data_point."""
|
|
38
|
+
# We allow setting the value via index as well, just in case.
|
|
39
|
+
if isinstance(value, int) and self._values:
|
|
40
|
+
if 0 <= value < len(self._values):
|
|
41
|
+
await super().send_variable(value=value)
|
|
42
|
+
elif self._values:
|
|
43
|
+
if value in self._values:
|
|
44
|
+
await super().send_variable(value=self._values.index(value))
|
|
45
|
+
else:
|
|
46
|
+
_LOGGER.error(
|
|
47
|
+
i18n.tr(
|
|
48
|
+
key="exception.model.select.value_not_in_value_list",
|
|
49
|
+
name=self.name,
|
|
50
|
+
unique_id=self.unique_id,
|
|
51
|
+
)
|
|
52
|
+
)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
# Copyright (c) 2021-2026
|
|
3
|
+
"""Module for hub data points implemented using the sensor category."""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import logging
|
|
8
|
+
from typing import Any, Final
|
|
9
|
+
|
|
10
|
+
from aiohomematic.const import DataPointCategory, HubValueType
|
|
11
|
+
from aiohomematic.model.hub.data_point import GenericSysvarDataPoint
|
|
12
|
+
from aiohomematic.model.support import check_length_and_log, get_value_from_value_list
|
|
13
|
+
from aiohomematic.property_decorators import state_property
|
|
14
|
+
|
|
15
|
+
_LOGGER: Final = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class SysvarDpSensor(GenericSysvarDataPoint):
|
|
19
|
+
"""Implementation of a sysvar sensor."""
|
|
20
|
+
|
|
21
|
+
__slots__ = ()
|
|
22
|
+
|
|
23
|
+
_category = DataPointCategory.HUB_SENSOR
|
|
24
|
+
|
|
25
|
+
@state_property
|
|
26
|
+
def value(self) -> Any | None:
|
|
27
|
+
"""Return the value."""
|
|
28
|
+
if (
|
|
29
|
+
self._data_type == HubValueType.LIST
|
|
30
|
+
and (value := get_value_from_value_list(value=self._value, value_list=self.values)) is not None
|
|
31
|
+
):
|
|
32
|
+
return value
|
|
33
|
+
return (
|
|
34
|
+
check_length_and_log(name=self._legacy_name, value=self._value)
|
|
35
|
+
if self._data_type == HubValueType.STRING
|
|
36
|
+
else self._value
|
|
37
|
+
)
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
# Copyright (c) 2021-2026
|
|
3
|
+
"""Module for hub data points implemented using the switch category."""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from typing import Final
|
|
8
|
+
|
|
9
|
+
from aiohomematic.const import DataPointCategory
|
|
10
|
+
from aiohomematic.decorators import inspector
|
|
11
|
+
from aiohomematic.model.hub.data_point import GenericProgramDataPoint, GenericSysvarDataPoint
|
|
12
|
+
from aiohomematic.property_decorators import DelegatedProperty, Kind
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class SysvarDpSwitch(GenericSysvarDataPoint):
|
|
16
|
+
"""Implementation of a sysvar switch data_point."""
|
|
17
|
+
|
|
18
|
+
__slots__ = ()
|
|
19
|
+
|
|
20
|
+
_category = DataPointCategory.HUB_SWITCH
|
|
21
|
+
_is_extended = True
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class ProgramDpSwitch(GenericProgramDataPoint):
|
|
25
|
+
"""Implementation of a program switch data_point."""
|
|
26
|
+
|
|
27
|
+
__slots__ = ()
|
|
28
|
+
|
|
29
|
+
_category = DataPointCategory.HUB_SWITCH
|
|
30
|
+
|
|
31
|
+
value: Final = DelegatedProperty[bool | None](path="_is_active", kind=Kind.STATE)
|
|
32
|
+
|
|
33
|
+
@inspector
|
|
34
|
+
async def turn_off(self) -> None:
|
|
35
|
+
"""Turn the program off."""
|
|
36
|
+
await self._hub_data_fetcher.set_program_state(pid=self._pid, state=False)
|
|
37
|
+
await self._hub_data_fetcher.fetch_program_data(scheduled=False)
|
|
38
|
+
|
|
39
|
+
@inspector
|
|
40
|
+
async def turn_on(self) -> None:
|
|
41
|
+
"""Turn the program on."""
|
|
42
|
+
await self._hub_data_fetcher.set_program_state(pid=self._pid, state=True)
|
|
43
|
+
await self._hub_data_fetcher.fetch_program_data(scheduled=False)
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
# Copyright (c) 2021-2026
|
|
3
|
+
"""Module for hub data points implemented using the text category."""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from typing import cast
|
|
8
|
+
|
|
9
|
+
from aiohomematic.const import DataPointCategory
|
|
10
|
+
from aiohomematic.model.hub.data_point import GenericSysvarDataPoint
|
|
11
|
+
from aiohomematic.model.support import check_length_and_log
|
|
12
|
+
from aiohomematic.property_decorators import state_property
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class SysvarDpText(GenericSysvarDataPoint):
|
|
16
|
+
"""Implementation of a sysvar text data_point."""
|
|
17
|
+
|
|
18
|
+
__slots__ = ()
|
|
19
|
+
|
|
20
|
+
_category = DataPointCategory.HUB_TEXT
|
|
21
|
+
_is_extended = True
|
|
22
|
+
|
|
23
|
+
@state_property
|
|
24
|
+
def value(self) -> str | None:
|
|
25
|
+
"""Get the value of the data_point."""
|
|
26
|
+
return cast(str | None, check_length_and_log(name=self._legacy_name, value=self._value))
|
|
27
|
+
|
|
28
|
+
async def send_variable(self, *, value: str | None) -> None:
|
|
29
|
+
"""Set the value of the data_point."""
|
|
30
|
+
await super().send_variable(value=value)
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
# Copyright (c) 2021-2026
|
|
3
|
+
"""Module for hub update data point."""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import asyncio
|
|
8
|
+
from datetime import datetime
|
|
9
|
+
import logging
|
|
10
|
+
from typing import Final
|
|
11
|
+
|
|
12
|
+
from slugify import slugify
|
|
13
|
+
|
|
14
|
+
from aiohomematic import i18n
|
|
15
|
+
from aiohomematic.const import HUB_ADDRESS, DataPointCategory, SystemUpdateData
|
|
16
|
+
from aiohomematic.decorators import inspector
|
|
17
|
+
from aiohomematic.interfaces import (
|
|
18
|
+
CentralInfoProtocol,
|
|
19
|
+
ChannelProtocol,
|
|
20
|
+
ConfigProviderProtocol,
|
|
21
|
+
EventBusProviderProtocol,
|
|
22
|
+
EventPublisherProtocol,
|
|
23
|
+
GenericHubDataPointProtocol,
|
|
24
|
+
ParameterVisibilityProviderProtocol,
|
|
25
|
+
ParamsetDescriptionProviderProtocol,
|
|
26
|
+
PrimaryClientProviderProtocol,
|
|
27
|
+
TaskSchedulerProtocol,
|
|
28
|
+
)
|
|
29
|
+
from aiohomematic.model.data_point import CallbackDataPoint
|
|
30
|
+
from aiohomematic.model.support import HubPathData, PathData, generate_unique_id, get_hub_data_point_name_data
|
|
31
|
+
from aiohomematic.property_decorators import DelegatedProperty, Kind
|
|
32
|
+
from aiohomematic.support import PayloadMixin
|
|
33
|
+
|
|
34
|
+
_LOGGER: Final = logging.getLogger(__name__)
|
|
35
|
+
|
|
36
|
+
_UPDATE_NAME: Final = "System Update"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class HmUpdate(CallbackDataPoint, GenericHubDataPointProtocol, PayloadMixin):
|
|
40
|
+
"""Class for a Homematic system update data point."""
|
|
41
|
+
|
|
42
|
+
__slots__ = (
|
|
43
|
+
"_available_firmware",
|
|
44
|
+
"_config_provider",
|
|
45
|
+
"_current_firmware",
|
|
46
|
+
"_name_data",
|
|
47
|
+
"_primary_client_provider",
|
|
48
|
+
"_state_uncertain",
|
|
49
|
+
"_update_available",
|
|
50
|
+
"_update_in_progress",
|
|
51
|
+
"_version_before_update",
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
_category = DataPointCategory.HUB_UPDATE
|
|
55
|
+
_enabled_default = True
|
|
56
|
+
|
|
57
|
+
def __init__(
|
|
58
|
+
self,
|
|
59
|
+
*,
|
|
60
|
+
config_provider: ConfigProviderProtocol,
|
|
61
|
+
central_info: CentralInfoProtocol,
|
|
62
|
+
event_bus_provider: EventBusProviderProtocol,
|
|
63
|
+
event_publisher: EventPublisherProtocol,
|
|
64
|
+
task_scheduler: TaskSchedulerProtocol,
|
|
65
|
+
paramset_description_provider: ParamsetDescriptionProviderProtocol,
|
|
66
|
+
parameter_visibility_provider: ParameterVisibilityProviderProtocol,
|
|
67
|
+
primary_client_provider: PrimaryClientProviderProtocol,
|
|
68
|
+
) -> None:
|
|
69
|
+
"""Initialize the data_point."""
|
|
70
|
+
PayloadMixin.__init__(self)
|
|
71
|
+
unique_id: Final = generate_unique_id(
|
|
72
|
+
config_provider=config_provider,
|
|
73
|
+
address=HUB_ADDRESS,
|
|
74
|
+
parameter=slugify(_UPDATE_NAME),
|
|
75
|
+
)
|
|
76
|
+
self._name_data: Final = get_hub_data_point_name_data(
|
|
77
|
+
channel=None, legacy_name=_UPDATE_NAME, central_name=central_info.name
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
super().__init__(
|
|
81
|
+
unique_id=unique_id,
|
|
82
|
+
central_info=central_info,
|
|
83
|
+
event_bus_provider=event_bus_provider,
|
|
84
|
+
event_publisher=event_publisher,
|
|
85
|
+
task_scheduler=task_scheduler,
|
|
86
|
+
paramset_description_provider=paramset_description_provider,
|
|
87
|
+
parameter_visibility_provider=parameter_visibility_provider,
|
|
88
|
+
)
|
|
89
|
+
self._config_provider: Final = config_provider
|
|
90
|
+
self._primary_client_provider: Final = primary_client_provider
|
|
91
|
+
self._state_uncertain: bool = True
|
|
92
|
+
self._current_firmware: str = ""
|
|
93
|
+
self._available_firmware: str = ""
|
|
94
|
+
self._update_available: bool = False
|
|
95
|
+
self._update_in_progress: bool = False
|
|
96
|
+
self._version_before_update: str | None = None
|
|
97
|
+
|
|
98
|
+
available: Final = DelegatedProperty[bool](path="_central_info.available", kind=Kind.STATE)
|
|
99
|
+
available_firmware: Final = DelegatedProperty[str](path="_available_firmware", kind=Kind.STATE)
|
|
100
|
+
current_firmware: Final = DelegatedProperty[str](path="_current_firmware", kind=Kind.STATE)
|
|
101
|
+
enabled_default: Final = DelegatedProperty[bool](path="_enabled_default")
|
|
102
|
+
full_name: Final = DelegatedProperty[str](path="_name_data.full_name")
|
|
103
|
+
in_progress: Final = DelegatedProperty[bool](path="_update_in_progress", kind=Kind.STATE)
|
|
104
|
+
name: Final = DelegatedProperty[str](path="_name_data.name", kind=Kind.CONFIG)
|
|
105
|
+
state_uncertain: Final = DelegatedProperty[bool](path="_state_uncertain")
|
|
106
|
+
update_available: Final = DelegatedProperty[bool](path="_update_available", kind=Kind.STATE)
|
|
107
|
+
|
|
108
|
+
@property
|
|
109
|
+
def channel(self) -> ChannelProtocol | None:
|
|
110
|
+
"""Return the identified channel."""
|
|
111
|
+
return None
|
|
112
|
+
|
|
113
|
+
@property
|
|
114
|
+
def description(self) -> str | None:
|
|
115
|
+
"""Return data point description."""
|
|
116
|
+
return None
|
|
117
|
+
|
|
118
|
+
@property
|
|
119
|
+
def legacy_name(self) -> str | None:
|
|
120
|
+
"""Return the original name."""
|
|
121
|
+
return None
|
|
122
|
+
|
|
123
|
+
@inspector
|
|
124
|
+
async def install(self) -> bool:
|
|
125
|
+
"""Trigger the firmware update process with progress monitoring."""
|
|
126
|
+
if client := self._primary_client_provider.primary_client:
|
|
127
|
+
# Store current version for progress detection
|
|
128
|
+
self._version_before_update = self._current_firmware
|
|
129
|
+
if result := await client.trigger_firmware_update():
|
|
130
|
+
self._update_in_progress = True
|
|
131
|
+
self.publish_data_point_updated_event()
|
|
132
|
+
|
|
133
|
+
# Start progress monitoring task
|
|
134
|
+
self._task_scheduler.create_task(
|
|
135
|
+
target=self._monitor_update_progress(),
|
|
136
|
+
name="hub_update_progress_monitor",
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
return result
|
|
140
|
+
return False
|
|
141
|
+
|
|
142
|
+
def update_data(self, *, data: SystemUpdateData, write_at: datetime) -> None:
|
|
143
|
+
"""Update the data point with new system update data."""
|
|
144
|
+
do_update: bool = False
|
|
145
|
+
if self._current_firmware != data.current_firmware:
|
|
146
|
+
self._current_firmware = data.current_firmware
|
|
147
|
+
do_update = True
|
|
148
|
+
if self._available_firmware != data.available_firmware:
|
|
149
|
+
self._available_firmware = data.available_firmware
|
|
150
|
+
do_update = True
|
|
151
|
+
if self._update_available != data.update_available:
|
|
152
|
+
self._update_available = data.update_available
|
|
153
|
+
do_update = True
|
|
154
|
+
|
|
155
|
+
if do_update:
|
|
156
|
+
self._set_modified_at(modified_at=write_at)
|
|
157
|
+
else:
|
|
158
|
+
self._set_refreshed_at(refreshed_at=write_at)
|
|
159
|
+
self._state_uncertain = False
|
|
160
|
+
self.publish_data_point_updated_event()
|
|
161
|
+
|
|
162
|
+
def _get_path_data(self) -> PathData:
|
|
163
|
+
"""Return the path data of the data_point."""
|
|
164
|
+
return HubPathData(name=slugify(_UPDATE_NAME))
|
|
165
|
+
|
|
166
|
+
def _get_signature(self) -> str:
|
|
167
|
+
"""Return the signature of the data_point."""
|
|
168
|
+
return f"{self._category}/{self.name}"
|
|
169
|
+
|
|
170
|
+
async def _monitor_update_progress(self) -> None:
|
|
171
|
+
"""Monitor update progress by polling system information."""
|
|
172
|
+
start_time = datetime.now()
|
|
173
|
+
|
|
174
|
+
try:
|
|
175
|
+
while (
|
|
176
|
+
datetime.now() - start_time
|
|
177
|
+
).total_seconds() < self._config_provider.config.schedule_timer_config.system_update_progress_timeout:
|
|
178
|
+
await asyncio.sleep(
|
|
179
|
+
self._config_provider.config.schedule_timer_config.system_update_progress_check_interval
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
if client := self._primary_client_provider.primary_client:
|
|
183
|
+
try:
|
|
184
|
+
update_info = await client.get_system_update_info()
|
|
185
|
+
|
|
186
|
+
if update_info and update_info.current_firmware != self._version_before_update:
|
|
187
|
+
_LOGGER.info(
|
|
188
|
+
i18n.tr(
|
|
189
|
+
key="log.model.hub.update.progress_completed",
|
|
190
|
+
old_version=self._version_before_update,
|
|
191
|
+
new_version=update_info.current_firmware,
|
|
192
|
+
)
|
|
193
|
+
)
|
|
194
|
+
# Update data with new firmware info
|
|
195
|
+
self._current_firmware = update_info.current_firmware
|
|
196
|
+
self._available_firmware = update_info.available_firmware
|
|
197
|
+
self._update_available = update_info.update_available
|
|
198
|
+
# Reset circuit breakers after successful update
|
|
199
|
+
# to allow immediate data refresh
|
|
200
|
+
client.reset_circuit_breakers()
|
|
201
|
+
break
|
|
202
|
+
except Exception as err:
|
|
203
|
+
# CCU may be offline during reboot - continue polling
|
|
204
|
+
_LOGGER.debug(
|
|
205
|
+
i18n.tr(
|
|
206
|
+
key="log.model.hub.update.progress_poll_error",
|
|
207
|
+
error=str(err),
|
|
208
|
+
)
|
|
209
|
+
)
|
|
210
|
+
else:
|
|
211
|
+
_LOGGER.warning(
|
|
212
|
+
i18n.tr(
|
|
213
|
+
key="log.model.hub.update.progress_timeout",
|
|
214
|
+
timeout=self._config_provider.config.schedule_timer_config.system_update_progress_timeout,
|
|
215
|
+
)
|
|
216
|
+
)
|
|
217
|
+
finally:
|
|
218
|
+
self._update_in_progress = False
|
|
219
|
+
self._version_before_update = None
|
|
220
|
+
self._set_modified_at(modified_at=datetime.now())
|
|
221
|
+
self.publish_data_point_updated_event()
|