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,174 @@
1
+ """
2
+ A number of functions used to calculate values based on existing data.
3
+
4
+ Climate related formula are based on:
5
+ - thermal comfort (https://github.com/dolezsa/thermal_comfort) ground works.
6
+ - https://gist.github.com/E3V3A/8f9f0aa18380d4ab2546cd50b725a219
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import logging
12
+ import math
13
+ from typing import Final
14
+
15
+ from aiohomematic.support import extract_exc_args
16
+
17
+ _LOGGER: Final = logging.getLogger(__name__)
18
+
19
+
20
+ def _calculate_heat_index(temperature: float, humidity: int) -> float:
21
+ """
22
+ Calculate the Heat Index (feels like temperature) based on the NOAA equation.
23
+
24
+ References:
25
+ [1] https://en.wikipedia.org/wiki/Heat_index
26
+ [2] http://www.wpc.ncep.noaa.gov/html/heatindex_equation.shtml
27
+ [3] https://github.com/geanders/weathermetrics/blob/master/R/heat_index.R
28
+ [4] https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3801457/
29
+
30
+ """
31
+
32
+ # SI units (Celsius)
33
+ c1 = -8.78469475556
34
+ c2 = 1.61139411
35
+ c3 = 2.33854883889
36
+ c4 = -0.14611605
37
+ c5 = -0.012308094
38
+ c6 = -0.0164248277778
39
+ c7 = 0.002211732
40
+ c8 = 0.00072546
41
+ c9 = -0.000003582
42
+
43
+ temperature_fahrenheit = (temperature * 9 / 5) + 32
44
+ heat_index_fahrenheit = 0.5 * (
45
+ temperature_fahrenheit + 61.0 + (temperature_fahrenheit - 68.0) * 1.2 + humidity * 0.094
46
+ )
47
+
48
+ if ((heat_index_fahrenheit + temperature_fahrenheit) / 2) >= 80: # [°F]
49
+ # temperature > 27C and humidity > 40 %
50
+ heat_index_celsius = math.fsum(
51
+ [
52
+ c1,
53
+ c2 * temperature,
54
+ c3 * humidity,
55
+ c4 * temperature * humidity,
56
+ c5 * temperature**2,
57
+ c6 * humidity**2,
58
+ c7 * temperature**2 * humidity,
59
+ c8 * temperature * humidity**2,
60
+ c9 * temperature**2 * humidity**2,
61
+ ]
62
+ )
63
+ else:
64
+ heat_index_celsius = (heat_index_fahrenheit - 32) * 5 / 9
65
+
66
+ return heat_index_celsius
67
+
68
+
69
+ def _calculate_wind_chill(temperature: float, wind_speed: float) -> float | None:
70
+ """
71
+ Calculate the Wind Chill (feels like temperature) based on NOAA.
72
+
73
+ References:
74
+ [1] https://en.wikipedia.org/wiki/Wind_chill
75
+ [2] https://www.wpc.ncep.noaa.gov/html/windchill.shtml
76
+
77
+ """
78
+ # Wind Chill Temperature is only defined for temperatures at or below 10°C and wind speeds above 4.8 Km/h.
79
+ if temperature > 10 or wind_speed <= 4.8: # if temperature > 50 or wind_speed <= 3: # (°F, Mph)
80
+ return None
81
+
82
+ return float(13.12 + (0.6215 * temperature) - 11.37 * wind_speed**0.16 + 0.3965 * temperature * wind_speed**0.16)
83
+
84
+
85
+ def calculate_vapor_concentration(temperature: float, humidity: int) -> float | None:
86
+ """Calculate the vapor concentration."""
87
+ try:
88
+ abs_temperature = temperature + 273.15
89
+ vapor_concentration = 6.112
90
+ vapor_concentration *= math.exp((17.67 * temperature) / (243.5 + temperature))
91
+ vapor_concentration *= humidity
92
+ vapor_concentration *= 2.1674
93
+ vapor_concentration /= abs_temperature
94
+
95
+ return round(vapor_concentration, 2)
96
+ except ValueError as verr:
97
+ _LOGGER.debug(
98
+ "Unable to calculate 'vapor concentration' with temperature: %s, humidity: %s (%s)",
99
+ temperature,
100
+ humidity,
101
+ extract_exc_args(exc=verr),
102
+ )
103
+ return None
104
+
105
+
106
+ def calculate_apparent_temperature(temperature: float, humidity: int, wind_speed: float) -> float | None:
107
+ """Calculate the apparent temperature based on NOAA."""
108
+ try:
109
+ if temperature <= 10 and wind_speed > 4.8:
110
+ # Wind Chill for low temp cases (and wind)
111
+ apparent_temperature = _calculate_wind_chill(temperature=temperature, wind_speed=wind_speed)
112
+ elif temperature >= 26.7:
113
+ # Heat Index for High temp cases
114
+ apparent_temperature = _calculate_heat_index(temperature=temperature, humidity=humidity)
115
+ else:
116
+ apparent_temperature = temperature
117
+
118
+ return round(apparent_temperature, 1) # type: ignore[arg-type]
119
+ except ValueError as verr:
120
+ if temperature == 0.0 and humidity == 0:
121
+ return 0.0
122
+ _LOGGER.debug(
123
+ "Unable to calculate 'apparent temperature' with temperature: %s, humidity: %s (%s)",
124
+ temperature,
125
+ humidity,
126
+ extract_exc_args(exc=verr),
127
+ )
128
+ return None
129
+
130
+
131
+ def calculate_dew_point(temperature: float, humidity: int) -> float | None:
132
+ """Calculate the dew point."""
133
+ try:
134
+ a0 = 373.15 / (273.15 + temperature)
135
+ s = -7.90298 * (a0 - 1)
136
+ s += 5.02808 * math.log(a0, 10)
137
+ s += -1.3816e-7 * (pow(10, (11.344 * (1 - 1 / a0))) - 1)
138
+ s += 8.1328e-3 * (pow(10, (-3.49149 * (a0 - 1))) - 1)
139
+ s += math.log(1013.246, 10)
140
+ vp = pow(10, s - 3) * humidity
141
+ td = math.log(vp / 0.61078)
142
+
143
+ return round((241.88 * td) / (17.558 - td), 1)
144
+ except ValueError as verr:
145
+ if temperature == 0.0 and humidity == 0:
146
+ return 0.0
147
+ _LOGGER.debug(
148
+ "Unable to calculate 'dew point' with temperature: %s, humidity: %s (%s)",
149
+ temperature,
150
+ humidity,
151
+ extract_exc_args(exc=verr),
152
+ )
153
+ return None
154
+
155
+
156
+ def calculate_frost_point(temperature: float, humidity: int) -> float | None:
157
+ """Calculate the frost point."""
158
+ try:
159
+ if (dewpoint := calculate_dew_point(temperature=temperature, humidity=humidity)) is None:
160
+ return None
161
+ t = temperature + 273.15
162
+ td = dewpoint + 273.15
163
+
164
+ return round((td + (2671.02 / ((2954.61 / t) + 2.193665 * math.log(t) - 13.3448)) - t) - 273.15, 1)
165
+ except ValueError as verr:
166
+ if temperature == 0.0 and humidity == 0:
167
+ return 0.0
168
+ _LOGGER.debug(
169
+ "Unable to calculate 'frost point' with temperature: %s, humidity: %s (%s)",
170
+ temperature,
171
+ humidity,
172
+ extract_exc_args(exc=verr),
173
+ )
174
+ return None
@@ -0,0 +1,175 @@
1
+ """
2
+ Custom data points for AioHomematic.
3
+
4
+ This subpackage provides higher-level, device-specific data points that combine
5
+ multiple backend parameters into single, meaningful entities (for example: a
6
+ thermostat, a blind with tilt, a fixed-color light, a lock, a siren, a switch,
7
+ or an irrigation valve). It also contains discovery helpers and a schema-based
8
+ validation for model-specific configurations.
9
+
10
+ What this package does
11
+ - Discovery: create_custom_data_points() inspects a device model and, if a
12
+ matching custom definition exists and the device is not ignored for customs,
13
+ creates the appropriate custom data point(s) and attaches them to the device.
14
+ - Definitions: The definition module holds the catalog of supported models and
15
+ the rules that describe which parameters form each custom entity. It exposes
16
+ helpers to query availability, enumerate required parameters, and validate the
17
+ definition schema.
18
+ - Specializations: Rich custom data point classes for climate, light, cover,
19
+ lock, siren, switch, and irrigation valve provide tailored behavior and an API
20
+ focused on user intent (e.g., set_temperature, open_tilt, set_profile,
21
+ turn_on with effect, lock/open, vent, etc.).
22
+
23
+ How it relates to the generic layer
24
+ Custom data points build on top of generic data points. While the generic layer
25
+ maps one backend parameter to one data point, this package groups multiple
26
+ parameters across channels (where needed) into a single higher-level entity. The
27
+ result is a simpler interface for automations and UIs, while still allowing the
28
+ underlying generic data points to be created when desired.
29
+
30
+ Public API entry points commonly used by integrators
31
+ - create_custom_data_points(device): Run discovery and attach custom data points.
32
+ - data_point_definition_exists(model): Check if a custom definition is available.
33
+ - get_custom_configs(model, category=None): Retrieve the CustomConfig entries
34
+ used to create custom data points for a model (optionally filtered by
35
+ category).
36
+ - get_required_parameters(): Return all parameters that must be fetched to allow
37
+ custom data points to function properly.
38
+ - validate_custom_data_point_definition(): Validate the internal definition
39
+ schema; useful in tests and development.
40
+ """
41
+
42
+ from __future__ import annotations
43
+
44
+ import logging
45
+ from typing import Final
46
+
47
+ from aiohomematic.decorators import inspector
48
+ from aiohomematic.model import device as hmd
49
+ from aiohomematic.model.custom.climate import (
50
+ PROFILE_DICT,
51
+ PROFILE_PREFIX,
52
+ SIMPLE_PROFILE_DICT,
53
+ SIMPLE_WEEKDAY_LIST,
54
+ WEEKDAY_DICT,
55
+ BaseCustomDpClimate,
56
+ ClimateActivity,
57
+ ClimateMode,
58
+ ClimateProfile,
59
+ CustomDpIpThermostat,
60
+ CustomDpRfThermostat,
61
+ CustomDpSimpleRfThermostat,
62
+ ScheduleProfile,
63
+ ScheduleWeekday,
64
+ )
65
+ from aiohomematic.model.custom.cover import (
66
+ CustomDpBlind,
67
+ CustomDpCover,
68
+ CustomDpGarage,
69
+ CustomDpIpBlind,
70
+ CustomDpWindowDrive,
71
+ )
72
+ from aiohomematic.model.custom.data_point import CustomDataPoint
73
+ from aiohomematic.model.custom.definition import (
74
+ data_point_definition_exists,
75
+ get_custom_configs,
76
+ get_required_parameters,
77
+ validate_custom_data_point_definition,
78
+ )
79
+ from aiohomematic.model.custom.light import (
80
+ CustomDpColorDimmer,
81
+ CustomDpColorDimmerEffect,
82
+ CustomDpColorTempDimmer,
83
+ CustomDpDimmer,
84
+ CustomDpIpDrgDaliLight,
85
+ CustomDpIpFixedColorLight,
86
+ CustomDpIpRGBWLight,
87
+ LightOffArgs,
88
+ LightOnArgs,
89
+ )
90
+ from aiohomematic.model.custom.lock import (
91
+ BaseCustomDpLock,
92
+ CustomDpButtonLock,
93
+ CustomDpIpLock,
94
+ CustomDpRfLock,
95
+ LockState,
96
+ )
97
+ from aiohomematic.model.custom.siren import BaseCustomDpSiren, CustomDpIpSiren, CustomDpIpSirenSmoke, SirenOnArgs
98
+ from aiohomematic.model.custom.switch import CustomDpSwitch
99
+ from aiohomematic.model.custom.valve import CustomDpIpIrrigationValve
100
+
101
+ __all__ = [
102
+ "BaseCustomDpClimate",
103
+ "BaseCustomDpLock",
104
+ "BaseCustomDpSiren",
105
+ "ClimateActivity",
106
+ "ClimateMode",
107
+ "ClimateProfile",
108
+ "CustomDataPoint",
109
+ "CustomDpBlind",
110
+ "CustomDpButtonLock",
111
+ "CustomDpColorDimmer",
112
+ "CustomDpColorDimmerEffect",
113
+ "CustomDpColorTempDimmer",
114
+ "CustomDpCover",
115
+ "CustomDpDimmer",
116
+ "CustomDpGarage",
117
+ "CustomDpIpBlind",
118
+ "CustomDpIpDrgDaliLight",
119
+ "CustomDpIpFixedColorLight",
120
+ "CustomDpIpIrrigationValve",
121
+ "CustomDpIpLock",
122
+ "CustomDpIpRGBWLight",
123
+ "CustomDpIpSiren",
124
+ "CustomDpIpSirenSmoke",
125
+ "CustomDpIpThermostat",
126
+ "CustomDpRfLock",
127
+ "CustomDpRfThermostat",
128
+ "CustomDpSimpleRfThermostat",
129
+ "CustomDpSwitch",
130
+ "CustomDpWindowDrive",
131
+ "LightOffArgs",
132
+ "LightOnArgs",
133
+ "LockState",
134
+ "PROFILE_DICT",
135
+ "PROFILE_PREFIX",
136
+ "SIMPLE_PROFILE_DICT",
137
+ "SIMPLE_WEEKDAY_LIST",
138
+ "ScheduleProfile",
139
+ "ScheduleWeekday",
140
+ "SirenOnArgs",
141
+ "WEEKDAY_DICT",
142
+ "create_custom_data_points",
143
+ "data_point_definition_exists",
144
+ "get_custom_configs",
145
+ "get_required_parameters",
146
+ "validate_custom_data_point_definition",
147
+ ]
148
+
149
+ _LOGGER: Final = logging.getLogger(__name__)
150
+
151
+
152
+ @inspector()
153
+ def create_custom_data_points(device: hmd.Device) -> None:
154
+ """Decides which data point category should be used, and creates the required data points."""
155
+
156
+ if device.ignore_for_custom_data_point:
157
+ _LOGGER.debug(
158
+ "CREATE_CUSTOM_DATA_POINTS: Ignoring for custom data point: %s, %s, %s due to ignored",
159
+ device.interface_id,
160
+ device,
161
+ device.model,
162
+ )
163
+ return
164
+ if data_point_definition_exists(device.model):
165
+ _LOGGER.debug(
166
+ "CREATE_CUSTOM_DATA_POINTS: Handling custom data point integration: %s, %s, %s",
167
+ device.interface_id,
168
+ device,
169
+ device.model,
170
+ )
171
+
172
+ # Call the custom creation function.
173
+ for custom_config in get_custom_configs(model=device.model):
174
+ for channel in device.channels.values():
175
+ custom_config.make_ce_func(channel, custom_config)