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