aiohomematic 2026.1.29__py3-none-any.whl

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