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,333 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2021-2025
3
+ """
4
+ Hub (backend) data points for AioHomematic.
5
+
6
+ Overview
7
+ - This module reflects the state and capabilities of the backend
8
+ at the hub level. It exposes backend programs and system variables as data
9
+ points that can be observed and acted upon by higher layers (e.g.,
10
+ integrations).
11
+
12
+ Responsibilities
13
+ - Fetch current lists of programs and system variables from the central unit.
14
+ - Create and maintain concrete hub data point instances for those items.
15
+ - Keep hub data points in sync with the backend (update values, add/remove).
16
+ - Notify the system about newly created hub data points via backend events.
17
+
18
+ Public API (selected)
19
+ - Hub: Orchestrates scanning and synchronization of hub-level data points.
20
+ - ProgramDpButton / ProgramDpSwitch: Represent a backend program as an
21
+ invocable button or a switch-like control, respectively.
22
+ - Sysvar data points: Map system variables to appropriate types:
23
+ - SysvarDpSensor, SysvarDpBinarySensor, SysvarDpSelect, SysvarDpNumber,
24
+ SysvarDpSwitch, SysvarDpText.
25
+ - __all__: Exposes the classes and types intended for external consumption.
26
+
27
+ Lifecycle and Flow
28
+ 1. fetch_program_data / fetch_sysvar_data (async) are scheduled or triggered
29
+ manually depending on configuration and availability of the central unit.
30
+ 2. On fetch:
31
+ - The module retrieves program/sysvar lists from the primary client.
32
+ - It identifies removed items and cleans up corresponding data points.
33
+ - It updates existing data points or creates new ones as needed.
34
+ 3. For newly created hub data points, a BackendSystemEvent.HUB_REFRESHED event
35
+ is emitted with a categorized mapping of the new points for consumers.
36
+
37
+ Type Mapping for System Variables
38
+ - Based on SysvarType and the extended_sysvar flag, system variables are
39
+ represented by the most suitable hub data point class. For example:
40
+ - ALARM/LOGIC → binary_sensor or switch (if extended)
41
+ - LIST (extended) → select
42
+ - FLOAT/INTEGER (extended) → number
43
+ - STRING (extended) → text
44
+ - Any other case → generic sensor
45
+
46
+ Concurrency and Reliability
47
+ - Fetch operations are protected by semaphores to avoid concurrent updates of
48
+ the same kind (programs or sysvars).
49
+ - The inspector decorator helps ensure exceptions do not propagate unexpectedly
50
+ when fetching; errors are logged and the system continues operating.
51
+
52
+ Backend Specifics and Cleanup
53
+ - For CCU backends, certain internal variables (e.g., legacy "OldVal*",
54
+ "pcCCUID") are filtered out to avoid exposing irrelevant state.
55
+
56
+ Categories and New Data Point Discovery
57
+ - Newly created hub data points are grouped into HUB_CATEGORIES and returned as
58
+ a mapping, so subscribers can register and present them appropriately.
59
+
60
+ Related Modules
61
+ - aiohomematic.model.hub.data_point: Base types for hub-level data points.
62
+ - aiohomematic.central: Central unit coordination and backend communication.
63
+ - aiohomematic.const: Shared constants, enums, and data structures.
64
+
65
+ Example:
66
+ - Typical usage occurs inside the central unit scheduling:
67
+ hub = Hub(central)
68
+ await hub.fetch_program_data(scheduled=True)
69
+ await hub.fetch_sysvar_data(scheduled=True)
70
+
71
+ This module complements device/channel data points by reflecting control center
72
+ state and enabling automations at the backend level.
73
+
74
+ """
75
+
76
+ from __future__ import annotations
77
+
78
+ import asyncio
79
+ from collections.abc import Collection, Mapping, Set as AbstractSet
80
+ from datetime import datetime
81
+ import logging
82
+ from typing import Final, NamedTuple
83
+
84
+ from aiohomematic import central as hmcu
85
+ from aiohomematic.const import (
86
+ HUB_CATEGORIES,
87
+ Backend,
88
+ BackendSystemEvent,
89
+ DataPointCategory,
90
+ ProgramData,
91
+ SystemVariableData,
92
+ SysvarType,
93
+ )
94
+ from aiohomematic.decorators import inspector
95
+ from aiohomematic.model.hub.binary_sensor import SysvarDpBinarySensor
96
+ from aiohomematic.model.hub.button import ProgramDpButton
97
+ from aiohomematic.model.hub.data_point import GenericHubDataPoint, GenericProgramDataPoint, GenericSysvarDataPoint
98
+ from aiohomematic.model.hub.number import SysvarDpNumber
99
+ from aiohomematic.model.hub.select import SysvarDpSelect
100
+ from aiohomematic.model.hub.sensor import SysvarDpSensor
101
+ from aiohomematic.model.hub.switch import ProgramDpSwitch, SysvarDpSwitch
102
+ from aiohomematic.model.hub.text import SysvarDpText
103
+
104
+ __all__ = [
105
+ "GenericProgramDataPoint",
106
+ "GenericSysvarDataPoint",
107
+ "Hub",
108
+ "ProgramDpButton",
109
+ "ProgramDpSwitch",
110
+ "ProgramDpType",
111
+ "SysvarDpBinarySensor",
112
+ "SysvarDpNumber",
113
+ "SysvarDpSelect",
114
+ "SysvarDpSensor",
115
+ "SysvarDpSwitch",
116
+ "SysvarDpText",
117
+ ]
118
+
119
+ _LOGGER: Final = logging.getLogger(__name__)
120
+
121
+ _EXCLUDED: Final = [
122
+ "OldVal",
123
+ "pcCCUID",
124
+ ]
125
+
126
+
127
+ class ProgramDpType(NamedTuple):
128
+ """Key for data points."""
129
+
130
+ pid: str
131
+ button: ProgramDpButton
132
+ switch: ProgramDpSwitch
133
+
134
+
135
+ class Hub:
136
+ """The Homematic hub."""
137
+
138
+ __slots__ = (
139
+ "_sema_fetch_sysvars",
140
+ "_sema_fetch_programs",
141
+ "_central",
142
+ "_config",
143
+ )
144
+
145
+ def __init__(self, *, central: hmcu.CentralUnit) -> None:
146
+ """Initialize Homematic hub."""
147
+ self._sema_fetch_sysvars: Final = asyncio.Semaphore()
148
+ self._sema_fetch_programs: Final = asyncio.Semaphore()
149
+ self._central: Final = central
150
+ self._config: Final = central.config
151
+
152
+ @inspector(re_raise=False)
153
+ async def fetch_sysvar_data(self, *, scheduled: bool) -> None:
154
+ """Fetch sysvar data for the hub."""
155
+ if self._config.enable_sysvar_scan:
156
+ _LOGGER.debug(
157
+ "FETCH_SYSVAR_DATA: %s fetching of system variables for %s",
158
+ "Scheduled" if scheduled else "Manual",
159
+ self._central.name,
160
+ )
161
+ async with self._sema_fetch_sysvars:
162
+ if self._central.available:
163
+ await self._update_sysvar_data_points()
164
+
165
+ @inspector(re_raise=False)
166
+ async def fetch_program_data(self, *, scheduled: bool) -> None:
167
+ """Fetch program data for the hub."""
168
+ if self._config.enable_program_scan:
169
+ _LOGGER.debug(
170
+ "FETCH_PROGRAM_DATA: %s fetching of programs for %s",
171
+ "Scheduled" if scheduled else "Manual",
172
+ self._central.name,
173
+ )
174
+ async with self._sema_fetch_programs:
175
+ if self._central.available:
176
+ await self._update_program_data_points()
177
+
178
+ async def _update_program_data_points(self) -> None:
179
+ """Retrieve all program data and update program values."""
180
+ if not (client := self._central.primary_client):
181
+ return
182
+ if (programs := await client.get_all_programs(markers=self._config.program_markers)) is None:
183
+ _LOGGER.debug("UPDATE_PROGRAM_DATA_POINTS: Unable to retrieve programs for %s", self._central.name)
184
+ return
185
+
186
+ _LOGGER.debug(
187
+ "UPDATE_PROGRAM_DATA_POINTS: %i programs received for %s",
188
+ len(programs),
189
+ self._central.name,
190
+ )
191
+
192
+ if missing_program_ids := self._identify_missing_program_ids(programs=programs):
193
+ self._remove_program_data_point(ids=missing_program_ids)
194
+
195
+ new_programs: list[GenericProgramDataPoint] = []
196
+
197
+ for program_data in programs:
198
+ if program_dp := self._central.get_program_data_point(pid=program_data.pid):
199
+ program_dp.button.update_data(data=program_data)
200
+ program_dp.switch.update_data(data=program_data)
201
+ else:
202
+ program_dp = self._create_program_dp(data=program_data)
203
+ new_programs.append(program_dp.button)
204
+ new_programs.append(program_dp.switch)
205
+
206
+ if new_programs:
207
+ self._central.emit_backend_system_callback(
208
+ system_event=BackendSystemEvent.HUB_REFRESHED,
209
+ new_data_points=_get_new_hub_data_points(data_points=new_programs),
210
+ )
211
+
212
+ async def _update_sysvar_data_points(self) -> None:
213
+ """Retrieve all variable data and update hmvariable values."""
214
+ if not (client := self._central.primary_client):
215
+ return
216
+ if (variables := await client.get_all_system_variables(markers=self._config.sysvar_markers)) is None:
217
+ _LOGGER.debug("UPDATE_SYSVAR_DATA_POINTS: Unable to retrieve sysvars for %s", self._central.name)
218
+ return
219
+
220
+ _LOGGER.debug(
221
+ "UPDATE_SYSVAR_DATA_POINTS: %i sysvars received for %s",
222
+ len(variables),
223
+ self._central.name,
224
+ )
225
+
226
+ # remove some variables in case of CCU backend
227
+ # - OldValue(s) are for internal calculations
228
+ if self._central.model is Backend.CCU:
229
+ variables = _clean_variables(variables=variables)
230
+
231
+ if missing_variable_ids := self._identify_missing_variable_ids(variables=variables):
232
+ self._remove_sysvar_data_point(del_data_point_ids=missing_variable_ids)
233
+
234
+ new_sysvars: list[GenericSysvarDataPoint] = []
235
+
236
+ for sysvar in variables:
237
+ if dp := self._central.get_sysvar_data_point(vid=sysvar.vid):
238
+ dp.write_value(value=sysvar.value, write_at=datetime.now())
239
+ else:
240
+ new_sysvars.append(self._create_system_variable(data=sysvar))
241
+
242
+ if new_sysvars:
243
+ self._central.emit_backend_system_callback(
244
+ system_event=BackendSystemEvent.HUB_REFRESHED,
245
+ new_data_points=_get_new_hub_data_points(data_points=new_sysvars),
246
+ )
247
+
248
+ def _create_program_dp(self, *, data: ProgramData) -> ProgramDpType:
249
+ """Create program as data_point."""
250
+ program_dp = ProgramDpType(
251
+ pid=data.pid,
252
+ button=ProgramDpButton(central=self._central, data=data),
253
+ switch=ProgramDpSwitch(central=self._central, data=data),
254
+ )
255
+ self._central.add_program_data_point(program_dp=program_dp)
256
+ return program_dp
257
+
258
+ def _create_system_variable(self, *, data: SystemVariableData) -> GenericSysvarDataPoint:
259
+ """Create system variable as data_point."""
260
+ sysvar_dp = self._create_sysvar_data_point(data=data)
261
+ self._central.add_sysvar_data_point(sysvar_data_point=sysvar_dp)
262
+ return sysvar_dp
263
+
264
+ def _create_sysvar_data_point(self, *, data: SystemVariableData) -> GenericSysvarDataPoint:
265
+ """Create sysvar data_point."""
266
+ data_type = data.data_type
267
+ extended_sysvar = data.extended_sysvar
268
+ if data_type:
269
+ if data_type in (SysvarType.ALARM, SysvarType.LOGIC):
270
+ if extended_sysvar:
271
+ return SysvarDpSwitch(central=self._central, data=data)
272
+ return SysvarDpBinarySensor(central=self._central, data=data)
273
+ if data_type == SysvarType.LIST and extended_sysvar:
274
+ return SysvarDpSelect(central=self._central, data=data)
275
+ if data_type in (SysvarType.FLOAT, SysvarType.INTEGER) and extended_sysvar:
276
+ return SysvarDpNumber(central=self._central, data=data)
277
+ if data_type == SysvarType.STRING and extended_sysvar:
278
+ return SysvarDpText(central=self._central, data=data)
279
+
280
+ return SysvarDpSensor(central=self._central, data=data)
281
+
282
+ def _remove_program_data_point(self, *, ids: set[str]) -> None:
283
+ """Remove sysvar data_point from hub."""
284
+ for pid in ids:
285
+ self._central.remove_program_button(pid=pid)
286
+
287
+ def _remove_sysvar_data_point(self, *, del_data_point_ids: set[str]) -> None:
288
+ """Remove sysvar data_point from hub."""
289
+ for vid in del_data_point_ids:
290
+ self._central.remove_sysvar_data_point(vid=vid)
291
+
292
+ def _identify_missing_program_ids(self, *, programs: tuple[ProgramData, ...]) -> set[str]:
293
+ """Identify missing programs."""
294
+ return {dp.pid for dp in self._central.program_data_points if dp.pid not in [x.pid for x in programs]}
295
+
296
+ def _identify_missing_variable_ids(self, *, variables: tuple[SystemVariableData, ...]) -> set[str]:
297
+ """Identify missing variables."""
298
+ variable_ids: dict[str, bool] = {x.vid: x.extended_sysvar for x in variables}
299
+ missing_variable_ids: list[str] = []
300
+ for dp in self._central.sysvar_data_points:
301
+ if dp.data_type == SysvarType.STRING:
302
+ continue
303
+ if (vid := dp.vid) is not None and (
304
+ vid not in variable_ids or (dp.is_extended is not variable_ids.get(vid))
305
+ ):
306
+ missing_variable_ids.append(vid)
307
+ return set(missing_variable_ids)
308
+
309
+
310
+ def _is_excluded(*, variable: str, excludes: list[str]) -> bool:
311
+ """Check if variable is excluded by exclude_list."""
312
+ return any(marker in variable for marker in excludes)
313
+
314
+
315
+ def _clean_variables(*, variables: tuple[SystemVariableData, ...]) -> tuple[SystemVariableData, ...]:
316
+ """Clean variables by removing excluded."""
317
+ return tuple(sv for sv in variables if not _is_excluded(variable=sv.legacy_name, excludes=_EXCLUDED))
318
+
319
+
320
+ def _get_new_hub_data_points(
321
+ *,
322
+ data_points: Collection[GenericHubDataPoint],
323
+ ) -> Mapping[DataPointCategory, AbstractSet[GenericHubDataPoint]]:
324
+ """Return data points as category dict."""
325
+ hub_data_points: dict[DataPointCategory, set[GenericHubDataPoint]] = {}
326
+ for hub_category in HUB_CATEGORIES:
327
+ hub_data_points[hub_category] = set()
328
+
329
+ for dp in data_points:
330
+ if dp.is_registered is False:
331
+ hub_data_points[dp.category].add(dp)
332
+
333
+ return hub_data_points
@@ -0,0 +1,24 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2021-2025
3
+ """Module for hub data points implemented using the binary_sensor category."""
4
+
5
+ from __future__ import annotations
6
+
7
+ from aiohomematic.const import DataPointCategory
8
+ from aiohomematic.model.hub.data_point import GenericSysvarDataPoint
9
+ from aiohomematic.property_decorators import state_property
10
+
11
+
12
+ class SysvarDpBinarySensor(GenericSysvarDataPoint):
13
+ """Implementation of a sysvar binary_sensor."""
14
+
15
+ __slots__ = ()
16
+
17
+ _category = DataPointCategory.HUB_BINARY_SENSOR
18
+
19
+ @state_property
20
+ def value(self) -> bool | None:
21
+ """Return the value of the data_point."""
22
+ if self._value is not None:
23
+ return bool(self._value)
24
+ return None
@@ -0,0 +1,28 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2021-2025
3
+ """Module for hub data points implemented using the button 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
10
+ from aiohomematic.property_decorators import state_property
11
+
12
+
13
+ class ProgramDpButton(GenericProgramDataPoint):
14
+ """Class for a Homematic program button."""
15
+
16
+ __slots__ = ()
17
+
18
+ _category = DataPointCategory.HUB_BUTTON
19
+
20
+ @state_property
21
+ def available(self) -> bool:
22
+ """Return the availability of the device."""
23
+ return self._is_active and self._central.available
24
+
25
+ @inspector
26
+ async def press(self) -> None:
27
+ """Handle the button press."""
28
+ await self.central.execute_program(pid=self.pid)