aiohomematic 2025.11.3__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.

Potentially problematic release.


This version of aiohomematic might be problematic. Click here for more details.

Files changed (77) hide show
  1. aiohomematic/__init__.py +61 -0
  2. aiohomematic/async_support.py +212 -0
  3. aiohomematic/central/__init__.py +2309 -0
  4. aiohomematic/central/decorators.py +155 -0
  5. aiohomematic/central/rpc_server.py +295 -0
  6. aiohomematic/client/__init__.py +1848 -0
  7. aiohomematic/client/_rpc_errors.py +81 -0
  8. aiohomematic/client/json_rpc.py +1326 -0
  9. aiohomematic/client/rpc_proxy.py +311 -0
  10. aiohomematic/const.py +1127 -0
  11. aiohomematic/context.py +18 -0
  12. aiohomematic/converter.py +108 -0
  13. aiohomematic/decorators.py +302 -0
  14. aiohomematic/exceptions.py +164 -0
  15. aiohomematic/hmcli.py +186 -0
  16. aiohomematic/model/__init__.py +140 -0
  17. aiohomematic/model/calculated/__init__.py +84 -0
  18. aiohomematic/model/calculated/climate.py +290 -0
  19. aiohomematic/model/calculated/data_point.py +327 -0
  20. aiohomematic/model/calculated/operating_voltage_level.py +299 -0
  21. aiohomematic/model/calculated/support.py +234 -0
  22. aiohomematic/model/custom/__init__.py +177 -0
  23. aiohomematic/model/custom/climate.py +1532 -0
  24. aiohomematic/model/custom/cover.py +792 -0
  25. aiohomematic/model/custom/data_point.py +334 -0
  26. aiohomematic/model/custom/definition.py +871 -0
  27. aiohomematic/model/custom/light.py +1128 -0
  28. aiohomematic/model/custom/lock.py +394 -0
  29. aiohomematic/model/custom/siren.py +275 -0
  30. aiohomematic/model/custom/support.py +41 -0
  31. aiohomematic/model/custom/switch.py +175 -0
  32. aiohomematic/model/custom/valve.py +114 -0
  33. aiohomematic/model/data_point.py +1123 -0
  34. aiohomematic/model/device.py +1445 -0
  35. aiohomematic/model/event.py +208 -0
  36. aiohomematic/model/generic/__init__.py +217 -0
  37. aiohomematic/model/generic/action.py +34 -0
  38. aiohomematic/model/generic/binary_sensor.py +30 -0
  39. aiohomematic/model/generic/button.py +27 -0
  40. aiohomematic/model/generic/data_point.py +171 -0
  41. aiohomematic/model/generic/dummy.py +147 -0
  42. aiohomematic/model/generic/number.py +76 -0
  43. aiohomematic/model/generic/select.py +39 -0
  44. aiohomematic/model/generic/sensor.py +74 -0
  45. aiohomematic/model/generic/switch.py +54 -0
  46. aiohomematic/model/generic/text.py +29 -0
  47. aiohomematic/model/hub/__init__.py +333 -0
  48. aiohomematic/model/hub/binary_sensor.py +24 -0
  49. aiohomematic/model/hub/button.py +28 -0
  50. aiohomematic/model/hub/data_point.py +340 -0
  51. aiohomematic/model/hub/number.py +39 -0
  52. aiohomematic/model/hub/select.py +49 -0
  53. aiohomematic/model/hub/sensor.py +37 -0
  54. aiohomematic/model/hub/switch.py +44 -0
  55. aiohomematic/model/hub/text.py +30 -0
  56. aiohomematic/model/support.py +586 -0
  57. aiohomematic/model/update.py +143 -0
  58. aiohomematic/property_decorators.py +496 -0
  59. aiohomematic/py.typed +0 -0
  60. aiohomematic/rega_scripts/fetch_all_device_data.fn +92 -0
  61. aiohomematic/rega_scripts/get_program_descriptions.fn +30 -0
  62. aiohomematic/rega_scripts/get_serial.fn +44 -0
  63. aiohomematic/rega_scripts/get_system_variable_descriptions.fn +30 -0
  64. aiohomematic/rega_scripts/set_program_state.fn +12 -0
  65. aiohomematic/rega_scripts/set_system_variable.fn +15 -0
  66. aiohomematic/store/__init__.py +34 -0
  67. aiohomematic/store/dynamic.py +551 -0
  68. aiohomematic/store/persistent.py +988 -0
  69. aiohomematic/store/visibility.py +812 -0
  70. aiohomematic/support.py +664 -0
  71. aiohomematic/validator.py +112 -0
  72. aiohomematic-2025.11.3.dist-info/METADATA +144 -0
  73. aiohomematic-2025.11.3.dist-info/RECORD +77 -0
  74. aiohomematic-2025.11.3.dist-info/WHEEL +5 -0
  75. aiohomematic-2025.11.3.dist-info/entry_points.txt +2 -0
  76. aiohomematic-2025.11.3.dist-info/licenses/LICENSE +21 -0
  77. aiohomematic-2025.11.3.dist-info/top_level.txt +1 -0
@@ -0,0 +1,340 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2021-2025
3
+ """Module for AioHomematic hub data points."""
4
+
5
+ from __future__ import annotations
6
+
7
+ from datetime import datetime
8
+ from typing import Any, Final
9
+
10
+ from slugify import slugify
11
+
12
+ from aiohomematic import central as hmcu
13
+ from aiohomematic.const import (
14
+ PROGRAM_ADDRESS,
15
+ SYSVAR_ADDRESS,
16
+ SYSVAR_TYPE,
17
+ HubData,
18
+ ProgramData,
19
+ SystemVariableData,
20
+ SysvarType,
21
+ )
22
+ from aiohomematic.decorators import inspector
23
+ from aiohomematic.model.data_point import CallbackDataPoint
24
+ from aiohomematic.model.device import Channel
25
+ from aiohomematic.model.support import (
26
+ PathData,
27
+ ProgramPathData,
28
+ SysvarPathData,
29
+ generate_unique_id,
30
+ get_hub_data_point_name_data,
31
+ )
32
+ from aiohomematic.property_decorators import config_property, state_property
33
+ from aiohomematic.support import PayloadMixin, parse_sys_var
34
+
35
+
36
+ class GenericHubDataPoint(CallbackDataPoint, PayloadMixin):
37
+ """Class for a Homematic system variable."""
38
+
39
+ __slots__ = (
40
+ "_channel",
41
+ "_description",
42
+ "_enabled_default",
43
+ "_legacy_name",
44
+ "_name_data",
45
+ "_state_uncertain",
46
+ )
47
+
48
+ def __init__(
49
+ self,
50
+ *,
51
+ central: hmcu.CentralUnit,
52
+ address: str,
53
+ data: HubData,
54
+ ) -> None:
55
+ """Initialize the data_point."""
56
+ PayloadMixin.__init__(self)
57
+ unique_id: Final = generate_unique_id(
58
+ central=central,
59
+ address=address,
60
+ parameter=slugify(data.legacy_name),
61
+ )
62
+ self._legacy_name = data.legacy_name
63
+ self._channel = central.identify_channel(text=data.legacy_name)
64
+ self._name_data: Final = get_hub_data_point_name_data(
65
+ channel=self._channel, legacy_name=data.legacy_name, central_name=central.name
66
+ )
67
+ self._description = data.description
68
+ super().__init__(central=central, unique_id=unique_id)
69
+ self._enabled_default: Final = data.enabled_default
70
+ self._state_uncertain: bool = True
71
+
72
+ @state_property
73
+ def available(self) -> bool:
74
+ """Return the availability of the device."""
75
+ return self.central.available
76
+
77
+ @property
78
+ def legacy_name(self) -> str | None:
79
+ """Return the original sysvar name."""
80
+ return self._legacy_name
81
+
82
+ @property
83
+ def channel(self) -> Channel | None:
84
+ """Return the identified channel."""
85
+ return self._channel
86
+
87
+ @config_property
88
+ def description(self) -> str | None:
89
+ """Return sysvar description."""
90
+ return self._description
91
+
92
+ @property
93
+ def full_name(self) -> str:
94
+ """Return the fullname of the data_point."""
95
+ return self._name_data.full_name
96
+
97
+ @property
98
+ def enabled_default(self) -> bool:
99
+ """Return if the data_point should be enabled."""
100
+ return self._enabled_default
101
+
102
+ @config_property
103
+ def name(self) -> str:
104
+ """Return the name of the data_point."""
105
+ return self._name_data.name
106
+
107
+ @property
108
+ def state_uncertain(self) -> bool:
109
+ """Return, if the state is uncertain."""
110
+ return self._state_uncertain
111
+
112
+ def _get_signature(self) -> str:
113
+ """Return the signature of the data_point."""
114
+ return f"{self._category}/{self.name}"
115
+
116
+
117
+ class GenericSysvarDataPoint(GenericHubDataPoint):
118
+ """Class for a Homematic system variable."""
119
+
120
+ __slots__ = (
121
+ "_current_value",
122
+ "_data_type",
123
+ "_max",
124
+ "_min",
125
+ "_previous_value",
126
+ "_temporary_value",
127
+ "_unit",
128
+ "_values",
129
+ "_vid",
130
+ )
131
+
132
+ _is_extended = False
133
+
134
+ def __init__(
135
+ self,
136
+ *,
137
+ central: hmcu.CentralUnit,
138
+ data: SystemVariableData,
139
+ ) -> None:
140
+ """Initialize the data_point."""
141
+ self._vid: Final = data.vid
142
+ super().__init__(central=central, address=SYSVAR_ADDRESS, data=data)
143
+ self._data_type = data.data_type
144
+ self._values: Final[tuple[str, ...] | None] = tuple(data.values) if data.values else None
145
+ self._max: Final = data.max_value
146
+ self._min: Final = data.min_value
147
+ self._unit: Final = data.unit
148
+ self._current_value: SYSVAR_TYPE = data.value
149
+ self._previous_value: SYSVAR_TYPE = None
150
+ self._temporary_value: SYSVAR_TYPE = None
151
+
152
+ @property
153
+ def data_type(self) -> SysvarType | None:
154
+ """Return the availability of the device."""
155
+ return self._data_type
156
+
157
+ @data_type.setter
158
+ def data_type(self, data_type: SysvarType) -> None:
159
+ """Write data_type."""
160
+ self._data_type = data_type
161
+
162
+ @config_property
163
+ def vid(self) -> str:
164
+ """Return sysvar id."""
165
+ return self._vid
166
+
167
+ @property
168
+ def previous_value(self) -> SYSVAR_TYPE:
169
+ """Return the previous value."""
170
+ return self._previous_value
171
+
172
+ @property
173
+ def _value(self) -> Any | None:
174
+ """Return the value."""
175
+ return self._temporary_value if self._temporary_refreshed_at > self._refreshed_at else self._current_value
176
+
177
+ @state_property
178
+ def value(self) -> Any | None:
179
+ """Return the value."""
180
+ return self._value
181
+
182
+ @state_property
183
+ def values(self) -> tuple[str, ...] | None:
184
+ """Return the value_list."""
185
+ return self._values
186
+
187
+ @config_property
188
+ def max(self) -> float | int | None:
189
+ """Return the max value."""
190
+ return self._max
191
+
192
+ @config_property
193
+ def min(self) -> float | int | None:
194
+ """Return the min value."""
195
+ return self._min
196
+
197
+ @config_property
198
+ def unit(self) -> str | None:
199
+ """Return the unit of the data_point."""
200
+ return self._unit
201
+
202
+ @property
203
+ def is_extended(self) -> bool:
204
+ """Return if the data_point is an extended type."""
205
+ return self._is_extended
206
+
207
+ def _get_path_data(self) -> PathData:
208
+ """Return the path data of the data_point."""
209
+ return SysvarPathData(vid=self._vid)
210
+
211
+ async def event(self, *, value: Any, received_at: datetime) -> None:
212
+ """Handle event for which this data_point has subscribed."""
213
+ self.write_value(value=value, write_at=received_at)
214
+
215
+ def _reset_temporary_value(self) -> None:
216
+ """Reset the temp storage."""
217
+ self._temporary_value = None
218
+ self._reset_temporary_timestamps()
219
+
220
+ def write_value(self, *, value: Any, write_at: datetime) -> None:
221
+ """Set variable value on the backend."""
222
+ self._reset_temporary_value()
223
+
224
+ old_value = self._current_value
225
+ new_value = self._convert_value(old_value=old_value, new_value=value)
226
+ if old_value == new_value:
227
+ self._set_refreshed_at(refreshed_at=write_at)
228
+ else:
229
+ self._set_modified_at(modified_at=write_at)
230
+ self._previous_value = old_value
231
+ self._current_value = new_value
232
+ self._state_uncertain = False
233
+ self.emit_data_point_updated_event()
234
+
235
+ def _write_temporary_value(self, *, value: Any, write_at: datetime) -> None:
236
+ """Update the temporary value of the data_point."""
237
+ self._reset_temporary_value()
238
+
239
+ temp_value = self._convert_value(old_value=self._current_value, new_value=value)
240
+ if self._value == temp_value:
241
+ self._set_temporary_refreshed_at(refreshed_at=write_at)
242
+ else:
243
+ self._set_temporary_modified_at(modified_at=write_at)
244
+ self._temporary_value = temp_value
245
+ self._state_uncertain = True
246
+ self.emit_data_point_updated_event()
247
+
248
+ def _convert_value(self, *, old_value: Any, new_value: Any) -> Any:
249
+ """Convert to value to SYSVAR_TYPE."""
250
+ if new_value is None:
251
+ return None
252
+ value = new_value
253
+ if self._data_type:
254
+ value = parse_sys_var(data_type=self._data_type, raw_value=new_value)
255
+ elif isinstance(old_value, bool):
256
+ value = bool(new_value)
257
+ elif isinstance(old_value, int):
258
+ value = int(new_value)
259
+ elif isinstance(old_value, str):
260
+ value = str(new_value)
261
+ elif isinstance(old_value, float):
262
+ value = float(new_value)
263
+ return value
264
+
265
+ @inspector
266
+ async def send_variable(self, *, value: Any) -> None:
267
+ """Set variable value on the backend."""
268
+ if client := self.central.primary_client:
269
+ await client.set_system_variable(
270
+ legacy_name=self._legacy_name, value=parse_sys_var(data_type=self._data_type, raw_value=value)
271
+ )
272
+ self._write_temporary_value(value=value, write_at=datetime.now())
273
+
274
+
275
+ class GenericProgramDataPoint(GenericHubDataPoint):
276
+ """Class for a generic Homematic progran data point."""
277
+
278
+ __slots__ = (
279
+ "_pid",
280
+ "_is_active",
281
+ "_is_internal",
282
+ "_last_execute_time",
283
+ )
284
+
285
+ def __init__(
286
+ self,
287
+ *,
288
+ central: hmcu.CentralUnit,
289
+ data: ProgramData,
290
+ ) -> None:
291
+ """Initialize the data_point."""
292
+ self._pid: Final = data.pid
293
+ super().__init__(
294
+ central=central,
295
+ address=PROGRAM_ADDRESS,
296
+ data=data,
297
+ )
298
+ self._is_active: bool = data.is_active
299
+ self._is_internal: bool = data.is_internal
300
+ self._last_execute_time: str = data.last_execute_time
301
+ self._state_uncertain: bool = True
302
+
303
+ @state_property
304
+ def is_active(self) -> bool:
305
+ """Return the program is active."""
306
+ return self._is_active
307
+
308
+ @config_property
309
+ def is_internal(self) -> bool:
310
+ """Return the program is internal."""
311
+ return self._is_internal
312
+
313
+ @state_property
314
+ def last_execute_time(self) -> str:
315
+ """Return the last execute time."""
316
+ return self._last_execute_time
317
+
318
+ @config_property
319
+ def pid(self) -> str:
320
+ """Return the program id."""
321
+ return self._pid
322
+
323
+ def update_data(self, *, data: ProgramData) -> None:
324
+ """Set variable value on the backend."""
325
+ do_update: bool = False
326
+ if self._is_active != data.is_active:
327
+ self._is_active = data.is_active
328
+ do_update = True
329
+ if self._is_internal != data.is_internal:
330
+ self._is_internal = data.is_internal
331
+ do_update = True
332
+ if self._last_execute_time != data.last_execute_time:
333
+ self._last_execute_time = data.last_execute_time
334
+ do_update = True
335
+ if do_update:
336
+ self.emit_data_point_updated_event()
337
+
338
+ def _get_path_data(self) -> PathData:
339
+ """Return the path data of the data_point."""
340
+ return ProgramPathData(pid=self.pid)
@@ -0,0 +1,39 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2021-2025
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.const import DataPointCategory
11
+ from aiohomematic.decorators import inspector
12
+ from aiohomematic.model.hub.data_point import GenericSysvarDataPoint
13
+
14
+ _LOGGER: Final = logging.getLogger(__name__)
15
+
16
+
17
+ class SysvarDpNumber(GenericSysvarDataPoint):
18
+ """Implementation of a sysvar number."""
19
+
20
+ __slots__ = ()
21
+
22
+ _category = DataPointCategory.HUB_NUMBER
23
+ _is_extended = True
24
+
25
+ @inspector
26
+ async def send_variable(self, *, value: float) -> None:
27
+ """Set the value of the data_point."""
28
+ if value is not None and self.max is not None and self.min is not None:
29
+ if self.min <= float(value) <= self.max:
30
+ await super().send_variable(value=value)
31
+ else:
32
+ _LOGGER.warning(
33
+ "SYSVAR.NUMBER failed: Invalid value: %s (min: %s, max: %s)",
34
+ value,
35
+ self.min,
36
+ self.max,
37
+ )
38
+ return
39
+ await super().send_variable(value=value)
@@ -0,0 +1,49 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2021-2025
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.const import DataPointCategory
11
+ from aiohomematic.decorators import inspector
12
+ from aiohomematic.model.hub.data_point import GenericSysvarDataPoint
13
+ from aiohomematic.model.support import get_value_from_value_list
14
+ from aiohomematic.property_decorators import state_property
15
+
16
+ _LOGGER: Final = logging.getLogger(__name__)
17
+
18
+
19
+ class SysvarDpSelect(GenericSysvarDataPoint):
20
+ """Implementation of a sysvar select data_point."""
21
+
22
+ __slots__ = ()
23
+
24
+ _category = DataPointCategory.HUB_SELECT
25
+ _is_extended = True
26
+
27
+ @state_property
28
+ def value(self) -> str | None:
29
+ """Get the value of the data_point."""
30
+ if (value := get_value_from_value_list(value=self._value, value_list=self.values)) is not None:
31
+ return value
32
+ return None
33
+
34
+ @inspector
35
+ async def send_variable(self, *, value: int | str) -> None:
36
+ """Set the value of the data_point."""
37
+ # We allow setting the value via index as well, just in case.
38
+ if isinstance(value, int) and self._values:
39
+ if 0 <= value < len(self._values):
40
+ await super().send_variable(value=value)
41
+ elif self._values:
42
+ if value in self._values:
43
+ await super().send_variable(value=self._values.index(value))
44
+ else:
45
+ _LOGGER.warning(
46
+ "Value not in value_list for %s/%s",
47
+ self.name,
48
+ self.unique_id,
49
+ )
@@ -0,0 +1,37 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2021-2025
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, SysvarType
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 == SysvarType.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 == SysvarType.STRING
36
+ else self._value
37
+ )
@@ -0,0 +1,44 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2021-2025
3
+ """Module for hub data points implemented using the switch category."""
4
+
5
+ from __future__ import annotations
6
+
7
+ from aiohomematic.const import DataPointCategory
8
+ from aiohomematic.decorators import inspector
9
+ from aiohomematic.model.hub.data_point import GenericProgramDataPoint, GenericSysvarDataPoint
10
+ from aiohomematic.property_decorators import state_property
11
+
12
+
13
+ class SysvarDpSwitch(GenericSysvarDataPoint):
14
+ """Implementation of a sysvar switch data_point."""
15
+
16
+ __slots__ = ()
17
+
18
+ _category = DataPointCategory.HUB_SWITCH
19
+ _is_extended = True
20
+
21
+
22
+ class ProgramDpSwitch(GenericProgramDataPoint):
23
+ """Implementation of a program switch data_point."""
24
+
25
+ __slots__ = ()
26
+
27
+ _category = DataPointCategory.HUB_SWITCH
28
+
29
+ @state_property
30
+ def value(self) -> bool | None:
31
+ """Get the value of the data_point."""
32
+ return self._is_active
33
+
34
+ @inspector
35
+ async def turn_on(self) -> None:
36
+ """Turn the program on."""
37
+ await self.central.set_program_state(pid=self._pid, state=True)
38
+ await self._central.fetch_program_data(scheduled=False)
39
+
40
+ @inspector
41
+ async def turn_off(self) -> None:
42
+ """Turn the program off."""
43
+ await self.central.set_program_state(pid=self._pid, state=False)
44
+ await self._central.fetch_program_data(scheduled=False)
@@ -0,0 +1,30 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2021-2025
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)