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,147 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2021-2025
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 entity.
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.model import device as hmd
38
+ from aiohomematic.model.data_point import CallParameterCollector
39
+ from aiohomematic.model.generic.data_point import GenericDataPoint
40
+ from aiohomematic.model.support import DataPointNameData
41
+
42
+
43
+ class DpDummy(GenericDataPoint[Any, Any]):
44
+ """
45
+ Backend-detached `GenericDataPoint` using only default values.
46
+
47
+ All backend-touching operations are overridden to be no-ops.
48
+ """
49
+
50
+ __slots__ = ()
51
+
52
+ is_hmtype = False
53
+
54
+ def __init__(self, *, channel: hmd.Channel, param_field: str | Field) -> None:
55
+ """
56
+ Initialize the dummy data point.
57
+
58
+ We still call `super().__init__` to get a valid object layout, but all
59
+ runtime behavior that would contact the backend is disabled via
60
+ overrides below.
61
+ """
62
+
63
+ super().__init__(
64
+ channel=channel,
65
+ paramset_key=ParamsetKey.DUMMY,
66
+ parameter=f"DUMMY-{str(param_field)}",
67
+ parameter_data=ParameterData(
68
+ DEFAULT=None,
69
+ FLAGS=0,
70
+ ID="0",
71
+ MAX=None,
72
+ MIN=None,
73
+ OPERATIONS=0,
74
+ SPECIAL={},
75
+ TYPE=ParameterType.DUMMY,
76
+ UNIT="",
77
+ VALUE_LIST=(),
78
+ ),
79
+ )
80
+
81
+ # --- Metadata and identity -------------------------------------------
82
+ @property
83
+ def usage(self) -> DataPointUsage:
84
+ """Never create/ expose this data point as a real entity."""
85
+ return DataPointUsage.NO_CREATE
86
+
87
+ @property
88
+ def requires_polling(self) -> bool:
89
+ """Never poll from this data point."""
90
+ return False
91
+
92
+ @property
93
+ def state_uncertain(self) -> bool:
94
+ """Never report state uncertainty for this data point."""
95
+ return True
96
+
97
+ @property
98
+ def modified_at(self) -> datetime:
99
+ """Never report modification timestamp for this data point."""
100
+ return INIT_DATETIME
101
+
102
+ @property
103
+ def refreshed_at(self) -> datetime:
104
+ """Never report refresh timestamp for this data point."""
105
+ return INIT_DATETIME
106
+
107
+ @property
108
+ def dpk(self) -> DataPointKey:
109
+ """Return a stable placeholder data point key."""
110
+ # Return a stable placeholder key so equality/set operations are safe.
111
+ return cast(DataPointKey, ("", "", ""))
112
+
113
+ @property
114
+ def value(self) -> Any:
115
+ """Return the value of the data_point."""
116
+ return None
117
+
118
+ # Optional: provide a stable, recognizable name to aid debugging
119
+ def _get_data_point_name(self) -> DataPointNameData:
120
+ """Return a stable, recognizable name to aid debugging."""
121
+ name = super()._get_data_point_name()
122
+ # Replace parameter part with a dummy marker without touching address
123
+ return DataPointNameData(
124
+ device_name=f"DUMMY_{name.name}",
125
+ channel_name=f"DUMMY_{name.full_name}",
126
+ parameter_name=f"DUMMY_{name.parameter_name}",
127
+ )
128
+
129
+ # --- Backend interactions (all no-ops) --------------------------------
130
+ async def event(self, *, value: Any, received_at: datetime) -> None:
131
+ """Ignore backend events entirely."""
132
+ return
133
+
134
+ async def load_data_point_value(self, *, call_source: CallSource, direct_call: bool = False) -> None:
135
+ """Do not read from backend; keep defaults as-is."""
136
+ return
137
+
138
+ async def send_value(
139
+ self,
140
+ *,
141
+ value: Any,
142
+ collector: CallParameterCollector | None = None,
143
+ collector_order: int = 50,
144
+ do_validate: bool = True,
145
+ ) -> set[DP_KEY_VALUE]:
146
+ """Do not write to backend; accept but perform no operation."""
147
+ return set()
@@ -0,0 +1,76 @@
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
+ from typing import cast
8
+
9
+ from aiohomematic.const import DataPointCategory
10
+ from aiohomematic.exceptions import ValidationException
11
+ from aiohomematic.model.generic.data_point import GenericDataPoint
12
+ from aiohomematic.property_decorators import state_property
13
+
14
+
15
+ class BaseDpNumber[NumberParameterT: int | float | None](GenericDataPoint[NumberParameterT, int | float | str]):
16
+ """
17
+ Implementation of a number.
18
+
19
+ This is a default data point that gets automatically generated.
20
+ """
21
+
22
+ __slots__ = ()
23
+
24
+ _category = DataPointCategory.NUMBER
25
+
26
+ def _prepare_number_for_sending(
27
+ self, *, value: int | float | str, type_converter: type, do_validate: bool = True
28
+ ) -> NumberParameterT:
29
+ """Prepare value before sending."""
30
+ if not do_validate or (
31
+ value is not None and isinstance(value, int | float) and self._min <= type_converter(value) <= self._max
32
+ ):
33
+ return cast(NumberParameterT, type_converter(value))
34
+ if self._special and isinstance(value, str) and value in self._special:
35
+ return cast(NumberParameterT, type_converter(self._special[value]))
36
+ raise ValidationException(
37
+ f"NUMBER failed: Invalid value: {value} (min: {self._min}, max: {self._max}, special:{self._special})"
38
+ )
39
+
40
+
41
+ class DpFloat(BaseDpNumber[float | None]):
42
+ """
43
+ Implementation of a Float.
44
+
45
+ This is a default data point that gets automatically generated.
46
+ """
47
+
48
+ __slots__ = ()
49
+
50
+ def _prepare_value_for_sending(self, *, value: int | float | str, do_validate: bool = True) -> float | None:
51
+ """Prepare value before sending."""
52
+ return self._prepare_number_for_sending(value=value, type_converter=float, do_validate=do_validate)
53
+
54
+ @state_property
55
+ def value(self) -> float | None:
56
+ """Return the value of the data_point."""
57
+ return cast(float | None, self._value)
58
+
59
+
60
+ class DpInteger(BaseDpNumber[int | None]):
61
+ """
62
+ Implementation of an Integer.
63
+
64
+ This is a default data point that gets automatically generated.
65
+ """
66
+
67
+ __slots__ = ()
68
+
69
+ def _prepare_value_for_sending(self, *, value: int | float | str, do_validate: bool = True) -> int | None:
70
+ """Prepare value before sending."""
71
+ return self._prepare_number_for_sending(value=value, type_converter=int, do_validate=do_validate)
72
+
73
+ @state_property
74
+ def value(self) -> int | None:
75
+ """Return the value of the data_point."""
76
+ return cast(int | None, self._value)
@@ -0,0 +1,39 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2021-2025
3
+ """Module for data points implemented using the select category."""
4
+
5
+ from __future__ import annotations
6
+
7
+ from aiohomematic.const import DataPointCategory
8
+ from aiohomematic.exceptions import ValidationException
9
+ from aiohomematic.model.generic.data_point import GenericDataPoint
10
+ from aiohomematic.model.support import get_value_from_value_list
11
+ from aiohomematic.property_decorators import state_property
12
+
13
+
14
+ class DpSelect(GenericDataPoint[int | str, int | float | str]):
15
+ """
16
+ Implementation of a select data_point.
17
+
18
+ This is a default data point that gets automatically generated.
19
+ """
20
+
21
+ __slots__ = ()
22
+
23
+ _category = DataPointCategory.SELECT
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 str(self._default)
31
+
32
+ def _prepare_value_for_sending(self, *, value: int | float | str, do_validate: bool = True) -> int:
33
+ """Prepare value before sending."""
34
+ # We allow setting the value via index as well, just in case.
35
+ if isinstance(value, int | float) and self._values and 0 <= value < len(self._values):
36
+ return int(value)
37
+ if self._values and value in self._values:
38
+ return self._values.index(value)
39
+ raise ValidationException(f"Value not in value_list for {self.name}/{self.unique_id}")
@@ -0,0 +1,74 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2021-2025
3
+ """Module for data points implemented using the sensor category."""
4
+
5
+ from __future__ import annotations
6
+
7
+ from collections.abc import Mapping
8
+ import logging
9
+ from typing import Any, Final, cast
10
+
11
+ from aiohomematic.const import DataPointCategory, Parameter, ParameterType
12
+ from aiohomematic.model.generic.data_point import GenericDataPoint
13
+ from aiohomematic.model.support import check_length_and_log, get_value_from_value_list
14
+ from aiohomematic.property_decorators import state_property
15
+
16
+ _LOGGER: Final = logging.getLogger(__name__)
17
+
18
+
19
+ class DpSensor[SensorT: float | int | str | None](GenericDataPoint[SensorT, None]):
20
+ """
21
+ Implementation of a sensor.
22
+
23
+ This is a default data point that gets automatically generated.
24
+ """
25
+
26
+ __slots__ = ()
27
+
28
+ _category = DataPointCategory.SENSOR
29
+
30
+ @state_property
31
+ def value(self) -> SensorT:
32
+ """Return the value."""
33
+ if (value := get_value_from_value_list(value=self._value, value_list=self.values)) is not None:
34
+ return cast(SensorT, value)
35
+ if convert_func := self._get_converter_func():
36
+ return cast(SensorT, convert_func(value=self._value))
37
+ return cast(
38
+ SensorT,
39
+ check_length_and_log(name=self.name, value=self._value)
40
+ if self._type == ParameterType.STRING
41
+ else self._value,
42
+ )
43
+
44
+ def _get_converter_func(self) -> Any:
45
+ """Return a converter based on sensor."""
46
+ if convert_func := _VALUE_CONVERTERS_BY_PARAM.get(self.parameter):
47
+ return convert_func
48
+ return None
49
+
50
+
51
+ def _fix_rssi(*, value: Any) -> int | None:
52
+ """
53
+ Fix rssi value.
54
+
55
+ See https://github.com/sukramj/aiohomematic/blob/devel/docs/rssi_fix.md.
56
+ """
57
+ if value is None:
58
+ return None
59
+ if isinstance(value, int):
60
+ if -127 < value < 0:
61
+ return value
62
+ if 1 < value < 127:
63
+ return value * -1
64
+ if -256 < value < -129:
65
+ return (value * -1) - 256
66
+ if 129 < value < 256:
67
+ return value - 256
68
+ return None
69
+
70
+
71
+ _VALUE_CONVERTERS_BY_PARAM: Mapping[str, Any] = {
72
+ Parameter.RSSI_PEER: _fix_rssi,
73
+ Parameter.RSSI_DEVICE: _fix_rssi,
74
+ }
@@ -0,0 +1,54 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2021-2025
3
+ """Module for data points implemented using the switch category."""
4
+
5
+ from __future__ import annotations
6
+
7
+ from typing import cast
8
+
9
+ from aiohomematic.const import DataPointCategory, Parameter, ParameterType
10
+ from aiohomematic.decorators import inspector
11
+ from aiohomematic.model.data_point import CallParameterCollector
12
+ from aiohomematic.model.generic.data_point import GenericDataPoint
13
+ from aiohomematic.property_decorators import state_property
14
+
15
+
16
+ class DpSwitch(GenericDataPoint[bool | None, bool]):
17
+ """
18
+ Implementation of a switch.
19
+
20
+ This is a default data point that gets automatically generated.
21
+ """
22
+
23
+ __slots__ = ()
24
+
25
+ _category = DataPointCategory.SWITCH
26
+
27
+ @state_property
28
+ def value(self) -> bool | None:
29
+ """Get the value of the data_point."""
30
+ if self._type == ParameterType.ACTION:
31
+ return False
32
+ return cast(bool | None, self._value)
33
+
34
+ @inspector
35
+ async def turn_on(self, *, on_time: float | None = None, collector: CallParameterCollector | None = None) -> None:
36
+ """Turn the switch on."""
37
+ if on_time is not None:
38
+ await self.set_on_time(on_time=on_time)
39
+ await self.send_value(value=True, collector=collector)
40
+
41
+ @inspector
42
+ async def turn_off(self, *, collector: CallParameterCollector | None = None) -> None:
43
+ """Turn the switch off."""
44
+ await self.send_value(value=False, collector=collector)
45
+
46
+ @inspector
47
+ async def set_on_time(self, *, on_time: float) -> None:
48
+ """Set the on time value in seconds."""
49
+ await self._client.set_value(
50
+ channel_address=self._channel.address,
51
+ paramset_key=self._paramset_key,
52
+ parameter=Parameter.ON_TIME,
53
+ value=float(on_time),
54
+ )
@@ -0,0 +1,29 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2021-2025
3
+ """Module for 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.generic.data_point import GenericDataPoint
11
+ from aiohomematic.model.support import check_length_and_log
12
+ from aiohomematic.property_decorators import state_property
13
+
14
+
15
+ class DpText(GenericDataPoint[str, str]):
16
+ """
17
+ Implementation of a text.
18
+
19
+ This is a default data point that gets automatically generated.
20
+ """
21
+
22
+ __slots__ = ()
23
+
24
+ _category = DataPointCategory.TEXT
25
+
26
+ @state_property
27
+ def value(self) -> str | None:
28
+ """Get the value of the data_point."""
29
+ return cast(str | None, check_length_and_log(name=self.name, value=self._value))