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,31 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
# Copyright (c) 2021-2026
|
|
3
|
+
"""
|
|
4
|
+
Generic button data points for momentary press actions.
|
|
5
|
+
|
|
6
|
+
Public API of this module is defined by __all__.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from aiohomematic.const import DataPointCategory
|
|
12
|
+
from aiohomematic.decorators import inspector
|
|
13
|
+
from aiohomematic.model.generic.data_point import GenericDataPoint
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class DpButton(GenericDataPoint[None, bool]):
|
|
17
|
+
"""
|
|
18
|
+
Implementation of a button.
|
|
19
|
+
|
|
20
|
+
This is a default data point that gets automatically generated.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
__slots__ = ()
|
|
24
|
+
|
|
25
|
+
_category = DataPointCategory.BUTTON
|
|
26
|
+
_validate_state_change = False
|
|
27
|
+
|
|
28
|
+
@inspector
|
|
29
|
+
async def press(self) -> None:
|
|
30
|
+
"""Handle the button press."""
|
|
31
|
+
await self.send_value(value=True)
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
# Copyright (c) 2021-2026
|
|
3
|
+
"""
|
|
4
|
+
Base implementation for generic data points.
|
|
5
|
+
|
|
6
|
+
Public API of this module is defined by __all__.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from datetime import datetime
|
|
12
|
+
import logging
|
|
13
|
+
from typing import Any, Final, TypeAlias
|
|
14
|
+
|
|
15
|
+
from aiohomematic import i18n
|
|
16
|
+
from aiohomematic.central.events import DeviceLifecycleEvent, DeviceLifecycleEventType
|
|
17
|
+
from aiohomematic.const import DP_KEY_VALUE, DataPointUsage, Parameter, ParameterData, ParamsetKey
|
|
18
|
+
from aiohomematic.decorators import inspector
|
|
19
|
+
from aiohomematic.exceptions import ValidationException
|
|
20
|
+
from aiohomematic.interfaces import ChannelProtocol, GenericDataPointProtocol
|
|
21
|
+
from aiohomematic.model import data_point as hme
|
|
22
|
+
from aiohomematic.model.support import DataPointNameData, get_data_point_name_data
|
|
23
|
+
from aiohomematic.property_decorators import hm_property
|
|
24
|
+
from aiohomematic.type_aliases import ParamType
|
|
25
|
+
|
|
26
|
+
_LOGGER: Final = logging.getLogger(__name__)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class GenericDataPoint[ParameterT: ParamType, InputParameterT: ParamType](
|
|
30
|
+
hme.BaseParameterDataPoint[ParameterT, InputParameterT],
|
|
31
|
+
GenericDataPointProtocol[ParameterT | None],
|
|
32
|
+
):
|
|
33
|
+
"""Base class for generic data point."""
|
|
34
|
+
|
|
35
|
+
__slots__ = ("_cached_usage",)
|
|
36
|
+
|
|
37
|
+
_validate_state_change: bool = True
|
|
38
|
+
is_hmtype: bool = True
|
|
39
|
+
|
|
40
|
+
def __init__(
|
|
41
|
+
self,
|
|
42
|
+
*,
|
|
43
|
+
channel: ChannelProtocol,
|
|
44
|
+
paramset_key: ParamsetKey,
|
|
45
|
+
parameter: str,
|
|
46
|
+
parameter_data: ParameterData,
|
|
47
|
+
) -> None:
|
|
48
|
+
"""Initialize the generic data_point."""
|
|
49
|
+
super().__init__(
|
|
50
|
+
channel=channel,
|
|
51
|
+
paramset_key=paramset_key,
|
|
52
|
+
parameter=parameter,
|
|
53
|
+
parameter_data=parameter_data,
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
@hm_property(cached=True)
|
|
57
|
+
def usage(self) -> DataPointUsage:
|
|
58
|
+
"""Return the data_point usage."""
|
|
59
|
+
if self._is_forced_sensor or self._is_un_ignored:
|
|
60
|
+
return DataPointUsage.DATA_POINT
|
|
61
|
+
if (force_enabled := self._enabled_by_channel_operation_mode) is None:
|
|
62
|
+
return self._get_data_point_usage()
|
|
63
|
+
return DataPointUsage.DATA_POINT if force_enabled else DataPointUsage.NO_CREATE # pylint: disable=using-constant-test
|
|
64
|
+
|
|
65
|
+
async def event(self, *, value: Any, received_at: datetime) -> None:
|
|
66
|
+
"""Handle event for which this data_point has subscribed."""
|
|
67
|
+
self._device.client.last_value_send_tracker.remove_last_value_send(
|
|
68
|
+
dpk=self.dpk,
|
|
69
|
+
value=value,
|
|
70
|
+
)
|
|
71
|
+
old_value, new_value = self.write_value(value=value, write_at=received_at)
|
|
72
|
+
if old_value == new_value:
|
|
73
|
+
return
|
|
74
|
+
|
|
75
|
+
if self._parameter == Parameter.CONFIG_PENDING and new_value is False and old_value is True:
|
|
76
|
+
# do what is needed on device config change.
|
|
77
|
+
await self._device.on_config_changed()
|
|
78
|
+
|
|
79
|
+
# send device availability events
|
|
80
|
+
if self._parameter in (
|
|
81
|
+
Parameter.UN_REACH,
|
|
82
|
+
Parameter.STICKY_UN_REACH,
|
|
83
|
+
):
|
|
84
|
+
# notify_data_points=True ensures entities on all channels refresh their availability
|
|
85
|
+
self._device.publish_device_updated_event(notify_data_points=True)
|
|
86
|
+
await self._event_bus_provider.event_bus.publish(
|
|
87
|
+
event=DeviceLifecycleEvent(
|
|
88
|
+
timestamp=datetime.now(),
|
|
89
|
+
event_type=DeviceLifecycleEventType.AVAILABILITY_CHANGED,
|
|
90
|
+
availability_changes=((self._device.address, new_value is False),),
|
|
91
|
+
)
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
def is_state_change(self, *, value: ParameterT | None) -> bool:
|
|
95
|
+
"""
|
|
96
|
+
Check if the state/value changes.
|
|
97
|
+
|
|
98
|
+
If the state is uncertain, the state should also marked as changed.
|
|
99
|
+
"""
|
|
100
|
+
if value != self._value:
|
|
101
|
+
return True
|
|
102
|
+
if self.state_uncertain:
|
|
103
|
+
return True
|
|
104
|
+
_LOGGER.debug("NO_STATE_CHANGE: %s", self.name)
|
|
105
|
+
return False
|
|
106
|
+
|
|
107
|
+
@inspector
|
|
108
|
+
async def send_value(
|
|
109
|
+
self,
|
|
110
|
+
*,
|
|
111
|
+
value: InputParameterT,
|
|
112
|
+
collector: hme.CallParameterCollector | None = None,
|
|
113
|
+
collector_order: int = 50,
|
|
114
|
+
do_validate: bool = True,
|
|
115
|
+
) -> set[DP_KEY_VALUE]:
|
|
116
|
+
"""Send value to ccu, or use collector if set."""
|
|
117
|
+
if not self.is_writable:
|
|
118
|
+
_LOGGER.error(
|
|
119
|
+
i18n.tr(
|
|
120
|
+
key="log.model.generic_data_point.send_value.not_writable",
|
|
121
|
+
full_name=self.full_name,
|
|
122
|
+
)
|
|
123
|
+
)
|
|
124
|
+
return set()
|
|
125
|
+
try:
|
|
126
|
+
prepared_value = self._prepare_value_for_sending(value=value, do_validate=do_validate)
|
|
127
|
+
except (ValueError, ValidationException) as verr:
|
|
128
|
+
_LOGGER.warning(verr)
|
|
129
|
+
return set()
|
|
130
|
+
|
|
131
|
+
converted_value = self._convert_value(value=prepared_value)
|
|
132
|
+
# if collector is set, then add value to collector
|
|
133
|
+
if collector:
|
|
134
|
+
collector.add_data_point(data_point=self, value=converted_value, collector_order=collector_order)
|
|
135
|
+
return set()
|
|
136
|
+
|
|
137
|
+
# if collector is not set, then send value directly
|
|
138
|
+
if self._validate_state_change and not self.is_state_change(value=converted_value):
|
|
139
|
+
return set()
|
|
140
|
+
|
|
141
|
+
return await self._client.set_value(
|
|
142
|
+
channel_address=self._channel.address,
|
|
143
|
+
paramset_key=self._paramset_key,
|
|
144
|
+
parameter=self._parameter,
|
|
145
|
+
value=converted_value,
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
def _get_data_point_name(self) -> DataPointNameData:
|
|
149
|
+
"""Create the name for the data_point."""
|
|
150
|
+
return get_data_point_name_data(
|
|
151
|
+
channel=self._channel,
|
|
152
|
+
parameter=self._parameter,
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
def _get_data_point_usage(self) -> DataPointUsage:
|
|
156
|
+
"""Generate the usage for the data_point."""
|
|
157
|
+
if self._forced_usage:
|
|
158
|
+
return self._forced_usage
|
|
159
|
+
if self._parameter_visibility_provider.parameter_is_hidden(
|
|
160
|
+
channel=self._channel,
|
|
161
|
+
paramset_key=self._paramset_key,
|
|
162
|
+
parameter=self._parameter,
|
|
163
|
+
):
|
|
164
|
+
return DataPointUsage.NO_CREATE
|
|
165
|
+
|
|
166
|
+
return (
|
|
167
|
+
DataPointUsage.NO_CREATE
|
|
168
|
+
if (self._device.has_custom_data_point_definition and not self._device.allow_undefined_generic_data_points)
|
|
169
|
+
else DataPointUsage.DATA_POINT
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
def _prepare_value_for_sending(self, *, value: InputParameterT, do_validate: bool = True) -> ParameterT:
|
|
173
|
+
"""Prepare value, if required, before send."""
|
|
174
|
+
return value # type: ignore[return-value]
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
GenericDataPointAny: TypeAlias = GenericDataPoint[Any, Any]
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
# Copyright (c) 2021-2026
|
|
3
|
+
"""
|
|
4
|
+
Dummy generic data point (backend-detached placeholder).
|
|
5
|
+
|
|
6
|
+
This class derives from `GenericDataPoint` but overrides all methods that would
|
|
7
|
+
normally interact with the backend so it behaves like an inert data point that
|
|
8
|
+
uses safe default values only.
|
|
9
|
+
The DpDummy class is intended to be used as a placeholder for custom data
|
|
10
|
+
points that are not implemented in the backend.
|
|
11
|
+
|
|
12
|
+
Key properties:
|
|
13
|
+
- It never triggers backend I/O (no reads, no writes, no subscriptions).
|
|
14
|
+
- It always reports `usage = DataPointUsage.NO_CREATE` so it is not created as a
|
|
15
|
+
real data point.
|
|
16
|
+
- It is not readable or writable and does not require polling nor support
|
|
17
|
+
events.
|
|
18
|
+
- It exposes safe, static defaults for metadata and state.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
from datetime import datetime
|
|
24
|
+
from typing import Any, cast
|
|
25
|
+
|
|
26
|
+
from aiohomematic.const import (
|
|
27
|
+
DP_KEY_VALUE,
|
|
28
|
+
INIT_DATETIME,
|
|
29
|
+
CallSource,
|
|
30
|
+
DataPointKey,
|
|
31
|
+
DataPointUsage,
|
|
32
|
+
Field,
|
|
33
|
+
ParameterData,
|
|
34
|
+
ParameterType,
|
|
35
|
+
ParamsetKey,
|
|
36
|
+
)
|
|
37
|
+
from aiohomematic.decorators import inspector
|
|
38
|
+
from aiohomematic.interfaces import ChannelProtocol
|
|
39
|
+
from aiohomematic.model.data_point import CallParameterCollector
|
|
40
|
+
from aiohomematic.model.generic.data_point import GenericDataPointAny
|
|
41
|
+
from aiohomematic.model.support import DataPointNameData
|
|
42
|
+
from aiohomematic.property_decorators import Kind, _GenericProperty
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class DpDummy(GenericDataPointAny):
|
|
46
|
+
"""
|
|
47
|
+
Backend-detached `GenericDataPoint` using only default values.
|
|
48
|
+
|
|
49
|
+
All backend-touching operations are overridden to be no-ops.
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
__slots__ = ()
|
|
53
|
+
|
|
54
|
+
is_hmtype = False
|
|
55
|
+
|
|
56
|
+
def __init__(self, *, channel: ChannelProtocol, param_field: str | Field) -> None:
|
|
57
|
+
"""
|
|
58
|
+
Initialize the dummy data point.
|
|
59
|
+
|
|
60
|
+
We still call `super().__init__` to get a valid object layout, but all
|
|
61
|
+
runtime behavior that would contact the backend is disabled via
|
|
62
|
+
overrides below.
|
|
63
|
+
"""
|
|
64
|
+
super().__init__(
|
|
65
|
+
channel=channel,
|
|
66
|
+
paramset_key=ParamsetKey.DUMMY,
|
|
67
|
+
parameter=f"DUMMY-{str(param_field)}",
|
|
68
|
+
parameter_data=ParameterData(
|
|
69
|
+
DEFAULT=None,
|
|
70
|
+
FLAGS=0,
|
|
71
|
+
ID="0",
|
|
72
|
+
MAX=None,
|
|
73
|
+
MIN=None,
|
|
74
|
+
OPERATIONS=0,
|
|
75
|
+
SPECIAL={},
|
|
76
|
+
TYPE=ParameterType.DUMMY,
|
|
77
|
+
UNIT="",
|
|
78
|
+
VALUE_LIST=(),
|
|
79
|
+
),
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
@property
|
|
83
|
+
def dpk(self) -> DataPointKey:
|
|
84
|
+
"""Return a stable placeholder data point key."""
|
|
85
|
+
# Return a stable placeholder key so equality/set operations are safe.
|
|
86
|
+
return cast(DataPointKey, ("", "", ""))
|
|
87
|
+
|
|
88
|
+
@property
|
|
89
|
+
def modified_at(self) -> datetime:
|
|
90
|
+
"""Never report modification timestamp for this data point."""
|
|
91
|
+
return INIT_DATETIME
|
|
92
|
+
|
|
93
|
+
@property
|
|
94
|
+
def refreshed_at(self) -> datetime:
|
|
95
|
+
"""Never report refresh timestamp for this data point."""
|
|
96
|
+
return INIT_DATETIME
|
|
97
|
+
|
|
98
|
+
@property
|
|
99
|
+
def requires_polling(self) -> bool:
|
|
100
|
+
"""Never poll from this data point."""
|
|
101
|
+
return False
|
|
102
|
+
|
|
103
|
+
@property
|
|
104
|
+
def state_uncertain(self) -> bool:
|
|
105
|
+
"""Never report state uncertainty for this data point."""
|
|
106
|
+
return True
|
|
107
|
+
|
|
108
|
+
@property
|
|
109
|
+
def usage(self) -> DataPointUsage:
|
|
110
|
+
"""Never create/ expose this data point as a real data point."""
|
|
111
|
+
return DataPointUsage.NO_CREATE
|
|
112
|
+
|
|
113
|
+
async def event(self, *, value: Any, received_at: datetime) -> None:
|
|
114
|
+
"""Ignore backend events entirely."""
|
|
115
|
+
return
|
|
116
|
+
|
|
117
|
+
@inspector(re_raise=False)
|
|
118
|
+
async def load_data_point_value(self, *, call_source: CallSource, direct_call: bool = False) -> None:
|
|
119
|
+
"""Do not read from backend; keep defaults as-is."""
|
|
120
|
+
return
|
|
121
|
+
|
|
122
|
+
async def send_value(
|
|
123
|
+
self,
|
|
124
|
+
*,
|
|
125
|
+
value: Any,
|
|
126
|
+
collector: CallParameterCollector | None = None,
|
|
127
|
+
collector_order: int = 50,
|
|
128
|
+
do_validate: bool = True,
|
|
129
|
+
) -> set[DP_KEY_VALUE]:
|
|
130
|
+
"""Do not write to backend; accept but perform no operation."""
|
|
131
|
+
return set()
|
|
132
|
+
|
|
133
|
+
def _get_data_point_name(self) -> DataPointNameData:
|
|
134
|
+
"""Return a stable, recognizable name to aid debugging."""
|
|
135
|
+
name = super()._get_data_point_name()
|
|
136
|
+
# Replace parameter part with a dummy marker without touching address
|
|
137
|
+
return DataPointNameData(
|
|
138
|
+
device_name=f"DUMMY_{name.name}",
|
|
139
|
+
channel_name=f"DUMMY_{name.full_name}",
|
|
140
|
+
parameter_name=f"DUMMY_{name.parameter_name}",
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
def _get_value(self) -> Any:
|
|
144
|
+
"""Return the value of the data_point."""
|
|
145
|
+
return None
|
|
146
|
+
|
|
147
|
+
def _set_value(self, value: Any) -> None: # kwonly: disable
|
|
148
|
+
"""Ignore setting value for dummy data point."""
|
|
149
|
+
|
|
150
|
+
value: _GenericProperty[Any, Any] = _GenericProperty(fget=_get_value, fset=_set_value, kind=Kind.STATE)
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
# Copyright (c) 2021-2026
|
|
3
|
+
"""
|
|
4
|
+
Generic number data points for numeric input values.
|
|
5
|
+
|
|
6
|
+
Public API of this module is defined by __all__.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from typing import cast
|
|
12
|
+
|
|
13
|
+
from aiohomematic import i18n
|
|
14
|
+
from aiohomematic.const import DataPointCategory
|
|
15
|
+
from aiohomematic.exceptions import ValidationException
|
|
16
|
+
from aiohomematic.model.generic.data_point import GenericDataPoint
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class BaseDpNumber[NumberParameterT: int | float | None](GenericDataPoint[NumberParameterT, int | float | str]):
|
|
20
|
+
"""
|
|
21
|
+
Implementation of a number.
|
|
22
|
+
|
|
23
|
+
This is a default data point that gets automatically generated.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
__slots__ = ()
|
|
27
|
+
|
|
28
|
+
_category = DataPointCategory.NUMBER
|
|
29
|
+
|
|
30
|
+
def _prepare_number_for_sending(
|
|
31
|
+
self, *, value: int | float | str, type_converter: type, do_validate: bool = True
|
|
32
|
+
) -> NumberParameterT:
|
|
33
|
+
"""Prepare value before sending."""
|
|
34
|
+
if not do_validate or (
|
|
35
|
+
value is not None and isinstance(value, int | float) and self._min <= type_converter(value) <= self._max
|
|
36
|
+
):
|
|
37
|
+
return cast(NumberParameterT, type_converter(value))
|
|
38
|
+
if self._special and isinstance(value, str) and value in self._special:
|
|
39
|
+
return cast(NumberParameterT, type_converter(self._special[value]))
|
|
40
|
+
raise ValidationException(
|
|
41
|
+
i18n.tr(
|
|
42
|
+
key="exception.model.number.invalid_value",
|
|
43
|
+
value=value,
|
|
44
|
+
min=self._min,
|
|
45
|
+
max=self._max,
|
|
46
|
+
special=self._special,
|
|
47
|
+
)
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class DpFloat(BaseDpNumber[float | None]):
|
|
52
|
+
"""
|
|
53
|
+
Implementation of a Float.
|
|
54
|
+
|
|
55
|
+
This is a default data point that gets automatically generated.
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
__slots__ = ()
|
|
59
|
+
|
|
60
|
+
def _prepare_value_for_sending(self, *, value: int | float | str, do_validate: bool = True) -> float | None:
|
|
61
|
+
"""Prepare value before sending."""
|
|
62
|
+
return self._prepare_number_for_sending(value=value, type_converter=float, do_validate=do_validate)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class DpInteger(BaseDpNumber[int | None]):
|
|
66
|
+
"""
|
|
67
|
+
Implementation of an Integer.
|
|
68
|
+
|
|
69
|
+
This is a default data point that gets automatically generated.
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
__slots__ = ()
|
|
73
|
+
|
|
74
|
+
def _prepare_value_for_sending(self, *, value: int | float | str, do_validate: bool = True) -> int | None:
|
|
75
|
+
"""Prepare value before sending."""
|
|
76
|
+
return self._prepare_number_for_sending(value=value, type_converter=int, do_validate=do_validate)
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
# Copyright (c) 2021-2026
|
|
3
|
+
"""
|
|
4
|
+
Generic select data points for dropdown selection values.
|
|
5
|
+
|
|
6
|
+
Public API of this module is defined by __all__.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from aiohomematic import i18n
|
|
12
|
+
from aiohomematic.const import DataPointCategory
|
|
13
|
+
from aiohomematic.exceptions import ValidationException
|
|
14
|
+
from aiohomematic.model.generic.data_point import GenericDataPoint
|
|
15
|
+
from aiohomematic.model.support import get_value_from_value_list
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class DpSelect(GenericDataPoint[int | str, int | float | str]):
|
|
19
|
+
"""
|
|
20
|
+
Implementation of a select data_point.
|
|
21
|
+
|
|
22
|
+
This is a default data point that gets automatically generated.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
__slots__ = ()
|
|
26
|
+
|
|
27
|
+
_category = DataPointCategory.SELECT
|
|
28
|
+
|
|
29
|
+
def _get_value(self) -> int | str:
|
|
30
|
+
"""Return the value for readings."""
|
|
31
|
+
# For index-based ENUMs (HM), convert integer index to string value.
|
|
32
|
+
if (value := get_value_from_value_list(value=self._value, value_list=self.values)) is not None:
|
|
33
|
+
return value
|
|
34
|
+
# For string-based ENUMs (HmIP), return the string value directly if valid.
|
|
35
|
+
if isinstance(self._value, str) and self._values is not None and self._value in self._values:
|
|
36
|
+
return self._value
|
|
37
|
+
return self._default
|
|
38
|
+
|
|
39
|
+
def _prepare_value_for_sending(self, *, value: int | float | str, do_validate: bool = True) -> int | str:
|
|
40
|
+
"""Prepare value before sending."""
|
|
41
|
+
# We allow setting the value via index as well, just in case.
|
|
42
|
+
if isinstance(value, int | float) and self._values and 0 <= value < len(self._values):
|
|
43
|
+
return int(value)
|
|
44
|
+
if self._values and value in self._values:
|
|
45
|
+
# For string-based ENUMs (HmIP), send the string value directly.
|
|
46
|
+
# For index-based ENUMs (HM), convert string to index.
|
|
47
|
+
if self._enum_value_is_index:
|
|
48
|
+
return self._values.index(value)
|
|
49
|
+
return str(value)
|
|
50
|
+
raise ValidationException(
|
|
51
|
+
i18n.tr(
|
|
52
|
+
key="exception.model.select.value_not_in_value_list",
|
|
53
|
+
name=self.name,
|
|
54
|
+
unique_id=self.unique_id,
|
|
55
|
+
)
|
|
56
|
+
)
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
# Copyright (c) 2021-2026
|
|
3
|
+
"""
|
|
4
|
+
Generic sensor data points for numeric and text values.
|
|
5
|
+
|
|
6
|
+
Public API of this module is defined by __all__.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from collections.abc import Mapping
|
|
12
|
+
import logging
|
|
13
|
+
from typing import Any, Final, cast
|
|
14
|
+
|
|
15
|
+
from aiohomematic.const import DataPointCategory, Parameter, ParameterType
|
|
16
|
+
from aiohomematic.model.generic.data_point import GenericDataPoint
|
|
17
|
+
from aiohomematic.model.support import check_length_and_log, get_value_from_value_list
|
|
18
|
+
|
|
19
|
+
_LOGGER: Final = logging.getLogger(__name__)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class DpSensor[SensorT: float | int | str | None](GenericDataPoint[SensorT, None]):
|
|
23
|
+
"""
|
|
24
|
+
Implementation of a sensor.
|
|
25
|
+
|
|
26
|
+
This is a default data point that gets automatically generated.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
__slots__ = ()
|
|
30
|
+
|
|
31
|
+
_category = DataPointCategory.SENSOR
|
|
32
|
+
|
|
33
|
+
def _get_converter_func(self) -> Any:
|
|
34
|
+
"""Return a converter based on sensor."""
|
|
35
|
+
if convert_func := _VALUE_CONVERTERS_BY_PARAM.get(self.parameter):
|
|
36
|
+
return convert_func
|
|
37
|
+
return None
|
|
38
|
+
|
|
39
|
+
def _get_value(self) -> SensorT:
|
|
40
|
+
"""Return the value for readings."""
|
|
41
|
+
if (value := get_value_from_value_list(value=self._value, value_list=self.values)) is not None:
|
|
42
|
+
return cast(SensorT, value)
|
|
43
|
+
if convert_func := self._get_converter_func():
|
|
44
|
+
return cast(SensorT, convert_func(value=self._value))
|
|
45
|
+
return cast(
|
|
46
|
+
SensorT,
|
|
47
|
+
check_length_and_log(name=self.name, value=self._value)
|
|
48
|
+
if self._type == ParameterType.STRING
|
|
49
|
+
else self._value,
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _fix_rssi(*, value: Any) -> int | None:
|
|
54
|
+
"""
|
|
55
|
+
Fix rssi value.
|
|
56
|
+
|
|
57
|
+
See https://github.com/sukramj/aiohomematic/blob/devel/docs/rssi_fix.md.
|
|
58
|
+
"""
|
|
59
|
+
if value is None:
|
|
60
|
+
return None
|
|
61
|
+
if isinstance(value, int):
|
|
62
|
+
if -127 < value < 0:
|
|
63
|
+
return value
|
|
64
|
+
if 1 < value < 127:
|
|
65
|
+
return value * -1
|
|
66
|
+
if -256 < value < -129:
|
|
67
|
+
return (value * -1) - 256
|
|
68
|
+
if 129 < value < 256:
|
|
69
|
+
return value - 256
|
|
70
|
+
return None
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
_VALUE_CONVERTERS_BY_PARAM: Mapping[str, Any] = {
|
|
74
|
+
Parameter.RSSI_PEER: _fix_rssi,
|
|
75
|
+
Parameter.RSSI_DEVICE: _fix_rssi,
|
|
76
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
# Copyright (c) 2021-2026
|
|
3
|
+
"""
|
|
4
|
+
Generic switch data points for toggle operations.
|
|
5
|
+
|
|
6
|
+
Public API of this module is defined by __all__.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from aiohomematic.const import DataPointCategory, Parameter, ParameterType
|
|
12
|
+
from aiohomematic.decorators import inspector
|
|
13
|
+
from aiohomematic.model.data_point import CallParameterCollector
|
|
14
|
+
from aiohomematic.model.generic.data_point import GenericDataPoint
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class DpSwitch(GenericDataPoint[bool | None, bool]):
|
|
18
|
+
"""
|
|
19
|
+
Implementation of a switch.
|
|
20
|
+
|
|
21
|
+
This is a default data point that gets automatically generated.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
__slots__ = ()
|
|
25
|
+
|
|
26
|
+
_category = DataPointCategory.SWITCH
|
|
27
|
+
|
|
28
|
+
@inspector
|
|
29
|
+
async def set_on_time(self, *, on_time: float) -> None:
|
|
30
|
+
"""Set the on time value in seconds."""
|
|
31
|
+
await self._client.set_value(
|
|
32
|
+
channel_address=self._channel.address,
|
|
33
|
+
paramset_key=self._paramset_key,
|
|
34
|
+
parameter=Parameter.ON_TIME,
|
|
35
|
+
value=float(on_time),
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
@inspector
|
|
39
|
+
async def turn_off(self, *, collector: CallParameterCollector | None = None) -> None:
|
|
40
|
+
"""Turn the switch off."""
|
|
41
|
+
await self.send_value(value=False, collector=collector)
|
|
42
|
+
|
|
43
|
+
@inspector
|
|
44
|
+
async def turn_on(self, *, on_time: float | None = None, collector: CallParameterCollector | None = None) -> None:
|
|
45
|
+
"""Turn the switch on."""
|
|
46
|
+
if on_time is not None:
|
|
47
|
+
await self.set_on_time(on_time=on_time)
|
|
48
|
+
await self.send_value(value=True, collector=collector)
|
|
49
|
+
|
|
50
|
+
def _get_value(self) -> bool | None:
|
|
51
|
+
"""Return the value for readings."""
|
|
52
|
+
if self._type == ParameterType.ACTION:
|
|
53
|
+
return False
|
|
54
|
+
return self._value
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
# Copyright (c) 2021-2026
|
|
3
|
+
"""
|
|
4
|
+
Generic text data points for string input values.
|
|
5
|
+
|
|
6
|
+
Public API of this module is defined by __all__.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from typing import cast
|
|
12
|
+
|
|
13
|
+
from aiohomematic.const import DataPointCategory
|
|
14
|
+
from aiohomematic.model.generic.data_point import GenericDataPoint
|
|
15
|
+
from aiohomematic.model.support import check_length_and_log
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class DpText(GenericDataPoint[str, str]):
|
|
19
|
+
"""
|
|
20
|
+
Implementation of a text.
|
|
21
|
+
|
|
22
|
+
This is a default data point that gets automatically generated.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
__slots__ = ()
|
|
26
|
+
|
|
27
|
+
_category = DataPointCategory.TEXT
|
|
28
|
+
|
|
29
|
+
def _get_value(self) -> str:
|
|
30
|
+
"""Return the value for readings."""
|
|
31
|
+
if (val := check_length_and_log(name=self.name, value=self._value)) is not None:
|
|
32
|
+
return cast(str, val)
|
|
33
|
+
return self._default
|