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