aiohomematic 2025.10.1__py3-none-any.whl → 2025.10.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 (57) hide show
  1. aiohomematic/async_support.py +8 -8
  2. aiohomematic/caches/dynamic.py +31 -26
  3. aiohomematic/caches/persistent.py +34 -32
  4. aiohomematic/caches/visibility.py +19 -7
  5. aiohomematic/central/__init__.py +88 -75
  6. aiohomematic/central/decorators.py +2 -2
  7. aiohomematic/central/xml_rpc_server.py +33 -25
  8. aiohomematic/client/__init__.py +72 -56
  9. aiohomematic/client/_rpc_errors.py +3 -3
  10. aiohomematic/client/json_rpc.py +33 -25
  11. aiohomematic/client/xml_rpc.py +14 -9
  12. aiohomematic/const.py +2 -1
  13. aiohomematic/converter.py +19 -19
  14. aiohomematic/exceptions.py +2 -1
  15. aiohomematic/model/__init__.py +4 -3
  16. aiohomematic/model/calculated/__init__.py +1 -1
  17. aiohomematic/model/calculated/climate.py +9 -9
  18. aiohomematic/model/calculated/data_point.py +13 -7
  19. aiohomematic/model/calculated/operating_voltage_level.py +2 -2
  20. aiohomematic/model/calculated/support.py +7 -7
  21. aiohomematic/model/custom/__init__.py +3 -3
  22. aiohomematic/model/custom/climate.py +57 -34
  23. aiohomematic/model/custom/cover.py +32 -18
  24. aiohomematic/model/custom/data_point.py +9 -7
  25. aiohomematic/model/custom/definition.py +23 -17
  26. aiohomematic/model/custom/light.py +52 -23
  27. aiohomematic/model/custom/lock.py +16 -12
  28. aiohomematic/model/custom/siren.py +8 -3
  29. aiohomematic/model/custom/switch.py +3 -2
  30. aiohomematic/model/custom/valve.py +3 -2
  31. aiohomematic/model/data_point.py +63 -49
  32. aiohomematic/model/device.py +48 -42
  33. aiohomematic/model/event.py +6 -5
  34. aiohomematic/model/generic/__init__.py +6 -4
  35. aiohomematic/model/generic/action.py +1 -1
  36. aiohomematic/model/generic/data_point.py +8 -6
  37. aiohomematic/model/generic/number.py +3 -3
  38. aiohomematic/model/generic/select.py +1 -1
  39. aiohomematic/model/generic/sensor.py +2 -2
  40. aiohomematic/model/generic/switch.py +3 -3
  41. aiohomematic/model/hub/__init__.py +17 -16
  42. aiohomematic/model/hub/data_point.py +12 -7
  43. aiohomematic/model/hub/number.py +3 -3
  44. aiohomematic/model/hub/select.py +3 -3
  45. aiohomematic/model/hub/text.py +2 -2
  46. aiohomematic/model/support.py +10 -9
  47. aiohomematic/model/update.py +6 -6
  48. aiohomematic/property_decorators.py +2 -0
  49. aiohomematic/support.py +44 -38
  50. aiohomematic/validator.py +6 -6
  51. {aiohomematic-2025.10.1.dist-info → aiohomematic-2025.10.3.dist-info}/METADATA +1 -1
  52. aiohomematic-2025.10.3.dist-info/RECORD +78 -0
  53. aiohomematic_support/client_local.py +26 -14
  54. aiohomematic-2025.10.1.dist-info/RECORD +0 -78
  55. {aiohomematic-2025.10.1.dist-info → aiohomematic-2025.10.3.dist-info}/WHEEL +0 -0
  56. {aiohomematic-2025.10.1.dist-info → aiohomematic-2025.10.3.dist-info}/licenses/LICENSE +0 -0
  57. {aiohomematic-2025.10.1.dist-info → aiohomematic-2025.10.3.dist-info}/top_level.txt +0 -0
@@ -142,7 +142,7 @@ class Hub:
142
142
  "_config",
143
143
  )
144
144
 
145
- def __init__(self, central: hmcu.CentralUnit) -> None:
145
+ def __init__(self, *, central: hmcu.CentralUnit) -> None:
146
146
  """Initialize Homematic hub."""
147
147
  self._sema_fetch_sysvars: Final = asyncio.Semaphore()
148
148
  self._sema_fetch_programs: Final = asyncio.Semaphore()
@@ -150,7 +150,7 @@ class Hub:
150
150
  self._config: Final = central.config
151
151
 
152
152
  @inspector(re_raise=False)
153
- async def fetch_sysvar_data(self, scheduled: bool) -> None:
153
+ async def fetch_sysvar_data(self, *, scheduled: bool) -> None:
154
154
  """Fetch sysvar data for the hub."""
155
155
  if self._config.enable_sysvar_scan:
156
156
  _LOGGER.debug(
@@ -163,7 +163,7 @@ class Hub:
163
163
  await self._update_sysvar_data_points()
164
164
 
165
165
  @inspector(re_raise=False)
166
- async def fetch_program_data(self, scheduled: bool) -> None:
166
+ async def fetch_program_data(self, *, scheduled: bool) -> None:
167
167
  """Fetch program data for the hub."""
168
168
  if self._config.enable_program_scan:
169
169
  _LOGGER.debug(
@@ -206,7 +206,7 @@ class Hub:
206
206
  if new_programs:
207
207
  self._central.fire_backend_system_callback(
208
208
  system_event=BackendSystemEvent.HUB_REFRESHED,
209
- new_hub_data_points=_get_new_hub_data_points(data_points=new_programs),
209
+ new_data_points=_get_new_hub_data_points(data_points=new_programs),
210
210
  )
211
211
 
212
212
  async def _update_sysvar_data_points(self) -> None:
@@ -226,7 +226,7 @@ class Hub:
226
226
  # remove some variables in case of CCU backend
227
227
  # - OldValue(s) are for internal calculations
228
228
  if self._central.model is Backend.CCU:
229
- variables = _clean_variables(variables)
229
+ variables = _clean_variables(variables=variables)
230
230
 
231
231
  if missing_variable_ids := self._identify_missing_variable_ids(variables=variables):
232
232
  self._remove_sysvar_data_point(del_data_point_ids=missing_variable_ids)
@@ -242,10 +242,10 @@ class Hub:
242
242
  if new_sysvars:
243
243
  self._central.fire_backend_system_callback(
244
244
  system_event=BackendSystemEvent.HUB_REFRESHED,
245
- new_hub_data_points=_get_new_hub_data_points(data_points=new_sysvars),
245
+ new_data_points=_get_new_hub_data_points(data_points=new_sysvars),
246
246
  )
247
247
 
248
- def _create_program_dp(self, data: ProgramData) -> ProgramDpType:
248
+ def _create_program_dp(self, *, data: ProgramData) -> ProgramDpType:
249
249
  """Create program as data_point."""
250
250
  program_dp = ProgramDpType(
251
251
  pid=data.pid,
@@ -255,13 +255,13 @@ class Hub:
255
255
  self._central.add_program_data_point(program_dp=program_dp)
256
256
  return program_dp
257
257
 
258
- def _create_system_variable(self, data: SystemVariableData) -> GenericSysvarDataPoint:
258
+ def _create_system_variable(self, *, data: SystemVariableData) -> GenericSysvarDataPoint:
259
259
  """Create system variable as data_point."""
260
260
  sysvar_dp = self._create_sysvar_data_point(data=data)
261
261
  self._central.add_sysvar_data_point(sysvar_data_point=sysvar_dp)
262
262
  return sysvar_dp
263
263
 
264
- def _create_sysvar_data_point(self, data: SystemVariableData) -> GenericSysvarDataPoint:
264
+ def _create_sysvar_data_point(self, *, data: SystemVariableData) -> GenericSysvarDataPoint:
265
265
  """Create sysvar data_point."""
266
266
  data_type = data.data_type
267
267
  extended_sysvar = data.extended_sysvar
@@ -279,21 +279,21 @@ class Hub:
279
279
 
280
280
  return SysvarDpSensor(central=self._central, data=data)
281
281
 
282
- def _remove_program_data_point(self, ids: set[str]) -> None:
282
+ def _remove_program_data_point(self, *, ids: set[str]) -> None:
283
283
  """Remove sysvar data_point from hub."""
284
284
  for pid in ids:
285
285
  self._central.remove_program_button(pid=pid)
286
286
 
287
- def _remove_sysvar_data_point(self, del_data_point_ids: set[str]) -> None:
287
+ def _remove_sysvar_data_point(self, *, del_data_point_ids: set[str]) -> None:
288
288
  """Remove sysvar data_point from hub."""
289
289
  for vid in del_data_point_ids:
290
290
  self._central.remove_sysvar_data_point(vid=vid)
291
291
 
292
- def _identify_missing_program_ids(self, programs: tuple[ProgramData, ...]) -> set[str]:
292
+ def _identify_missing_program_ids(self, *, programs: tuple[ProgramData, ...]) -> set[str]:
293
293
  """Identify missing programs."""
294
294
  return {dp.pid for dp in self._central.program_data_points if dp.pid not in [x.pid for x in programs]}
295
295
 
296
- def _identify_missing_variable_ids(self, variables: tuple[SystemVariableData, ...]) -> set[str]:
296
+ def _identify_missing_variable_ids(self, *, variables: tuple[SystemVariableData, ...]) -> set[str]:
297
297
  """Identify missing variables."""
298
298
  variable_ids: dict[str, bool] = {x.vid: x.extended_sysvar for x in variables}
299
299
  missing_variable_ids: list[str] = []
@@ -307,17 +307,18 @@ class Hub:
307
307
  return set(missing_variable_ids)
308
308
 
309
309
 
310
- def _is_excluded(variable: str, excludes: list[str]) -> bool:
310
+ def _is_excluded(*, variable: str, excludes: list[str]) -> bool:
311
311
  """Check if variable is excluded by exclude_list."""
312
312
  return any(marker in variable for marker in excludes)
313
313
 
314
314
 
315
- def _clean_variables(variables: tuple[SystemVariableData, ...]) -> tuple[SystemVariableData, ...]:
315
+ def _clean_variables(*, variables: tuple[SystemVariableData, ...]) -> tuple[SystemVariableData, ...]:
316
316
  """Clean variables by removing excluded."""
317
- return tuple(sv for sv in variables if not _is_excluded(sv.legacy_name, _EXCLUDED))
317
+ return tuple(sv for sv in variables if not _is_excluded(variable=sv.legacy_name, excludes=_EXCLUDED))
318
318
 
319
319
 
320
320
  def _get_new_hub_data_points(
321
+ *,
321
322
  data_points: Collection[GenericHubDataPoint],
322
323
  ) -> Mapping[DataPointCategory, AbstractSet[GenericHubDataPoint]]:
323
324
  """Return data points as category dict."""
@@ -47,6 +47,7 @@ class GenericHubDataPoint(CallbackDataPoint, PayloadMixin):
47
47
 
48
48
  def __init__(
49
49
  self,
50
+ *,
50
51
  central: hmcu.CentralUnit,
51
52
  address: str,
52
53
  data: HubData,
@@ -132,6 +133,7 @@ class GenericSysvarDataPoint(GenericHubDataPoint):
132
133
 
133
134
  def __init__(
134
135
  self,
136
+ *,
135
137
  central: hmcu.CentralUnit,
136
138
  data: SystemVariableData,
137
139
  ) -> None:
@@ -206,7 +208,7 @@ class GenericSysvarDataPoint(GenericHubDataPoint):
206
208
  """Return the path data of the data_point."""
207
209
  return SysvarPathData(vid=self._vid)
208
210
 
209
- async def event(self, value: Any, received_at: datetime) -> None:
211
+ async def event(self, *, value: Any, received_at: datetime) -> None:
210
212
  """Handle event for which this data_point has subscribed."""
211
213
  self.write_value(value=value, write_at=received_at)
212
214
 
@@ -215,7 +217,7 @@ class GenericSysvarDataPoint(GenericHubDataPoint):
215
217
  self._temporary_value = None
216
218
  self._reset_temporary_timestamps()
217
219
 
218
- def write_value(self, value: Any, write_at: datetime) -> None:
220
+ def write_value(self, *, value: Any, write_at: datetime) -> None:
219
221
  """Set variable value on the backend."""
220
222
  self._reset_temporary_value()
221
223
 
@@ -230,7 +232,7 @@ class GenericSysvarDataPoint(GenericHubDataPoint):
230
232
  self._state_uncertain = False
231
233
  self.fire_data_point_updated_callback()
232
234
 
233
- def _write_temporary_value(self, value: Any, write_at: datetime) -> None:
235
+ def _write_temporary_value(self, *, value: Any, write_at: datetime) -> None:
234
236
  """Update the temporary value of the data_point."""
235
237
  self._reset_temporary_value()
236
238
 
@@ -243,7 +245,7 @@ class GenericSysvarDataPoint(GenericHubDataPoint):
243
245
  self._state_uncertain = True
244
246
  self.fire_data_point_updated_callback()
245
247
 
246
- def _convert_value(self, old_value: Any, new_value: Any) -> Any:
248
+ def _convert_value(self, *, old_value: Any, new_value: Any) -> Any:
247
249
  """Convert to value to SYSVAR_TYPE."""
248
250
  if new_value is None:
249
251
  return None
@@ -261,10 +263,12 @@ class GenericSysvarDataPoint(GenericHubDataPoint):
261
263
  return value
262
264
 
263
265
  @inspector
264
- async def send_variable(self, value: Any) -> None:
266
+ async def send_variable(self, *, value: Any) -> None:
265
267
  """Set variable value on the backend."""
266
268
  if client := self.central.primary_client:
267
- await client.set_system_variable(legacy_name=self._legacy_name, value=parse_sys_var(self._data_type, value))
269
+ await client.set_system_variable(
270
+ legacy_name=self._legacy_name, value=parse_sys_var(data_type=self._data_type, raw_value=value)
271
+ )
268
272
  self._write_temporary_value(value=value, write_at=datetime.now())
269
273
 
270
274
 
@@ -280,6 +284,7 @@ class GenericProgramDataPoint(GenericHubDataPoint):
280
284
 
281
285
  def __init__(
282
286
  self,
287
+ *,
283
288
  central: hmcu.CentralUnit,
284
289
  data: ProgramData,
285
290
  ) -> None:
@@ -315,7 +320,7 @@ class GenericProgramDataPoint(GenericHubDataPoint):
315
320
  """Return the program id."""
316
321
  return self._pid
317
322
 
318
- def update_data(self, data: ProgramData) -> None:
323
+ def update_data(self, *, data: ProgramData) -> None:
319
324
  """Set variable value on the backend."""
320
325
  do_update: bool = False
321
326
  if self._is_active != data.is_active:
@@ -23,11 +23,11 @@ class SysvarDpNumber(GenericSysvarDataPoint):
23
23
  _is_extended = True
24
24
 
25
25
  @inspector
26
- async def send_variable(self, value: float) -> None:
26
+ async def send_variable(self, *, value: float) -> None:
27
27
  """Set the value of the data_point."""
28
28
  if value is not None and self.max is not None and self.min is not None:
29
29
  if self.min <= float(value) <= self.max:
30
- await super().send_variable(value)
30
+ await super().send_variable(value=value)
31
31
  else:
32
32
  _LOGGER.warning(
33
33
  "SYSVAR.NUMBER failed: Invalid value: %s (min: %s, max: %s)",
@@ -36,4 +36,4 @@ class SysvarDpNumber(GenericSysvarDataPoint):
36
36
  self.max,
37
37
  )
38
38
  return
39
- await super().send_variable(value)
39
+ await super().send_variable(value=value)
@@ -32,15 +32,15 @@ class SysvarDpSelect(GenericSysvarDataPoint):
32
32
  return None
33
33
 
34
34
  @inspector
35
- async def send_variable(self, value: int | str) -> None:
35
+ async def send_variable(self, *, value: int | str) -> None:
36
36
  """Set the value of the data_point."""
37
37
  # We allow setting the value via index as well, just in case.
38
38
  if isinstance(value, int) and self._values:
39
39
  if 0 <= value < len(self._values):
40
- await super().send_variable(value)
40
+ await super().send_variable(value=value)
41
41
  elif self._values:
42
42
  if value in self._values:
43
- await super().send_variable(self._values.index(value))
43
+ await super().send_variable(value=self._values.index(value))
44
44
  else:
45
45
  _LOGGER.warning(
46
46
  "Value not in value_list for %s/%s",
@@ -25,6 +25,6 @@ class SysvarDpText(GenericSysvarDataPoint):
25
25
  """Get the value of the data_point."""
26
26
  return cast(str | None, check_length_and_log(name=self._legacy_name, value=self._value))
27
27
 
28
- async def send_variable(self, value: str | None) -> None:
28
+ async def send_variable(self, *, value: str | None) -> None:
29
29
  """Set the value of the data_point."""
30
- await super().send_variable(value)
30
+ await super().send_variable(value=value)
@@ -74,7 +74,7 @@ class ChannelNameData:
74
74
  "sub_device_name",
75
75
  )
76
76
 
77
- def __init__(self, device_name: str, channel_name: str) -> None:
77
+ def __init__(self, *, device_name: str, channel_name: str) -> None:
78
78
  """Init the DataPointNameData class."""
79
79
  self.device_name: Final = device_name
80
80
  self.channel_name: Final = self._get_channel_name(device_name=device_name, channel_name=channel_name)
@@ -87,7 +87,7 @@ class ChannelNameData:
87
87
  return ChannelNameData(device_name="", channel_name="")
88
88
 
89
89
  @staticmethod
90
- def _get_channel_name(device_name: str, channel_name: str) -> str:
90
+ def _get_channel_name(*, device_name: str, channel_name: str) -> str:
91
91
  """Return the channel_name of the data_point only name."""
92
92
  if device_name and channel_name and channel_name.startswith(device_name):
93
93
  c_name = channel_name.replace(device_name, "").strip()
@@ -105,7 +105,7 @@ class DataPointNameData(ChannelNameData):
105
105
  "parameter_name",
106
106
  )
107
107
 
108
- def __init__(self, device_name: str, channel_name: str, parameter_name: str | None = None) -> None:
108
+ def __init__(self, *, device_name: str, channel_name: str, parameter_name: str | None = None) -> None:
109
109
  """Init the DataPointNameData class."""
110
110
  super().__init__(device_name=device_name, channel_name=channel_name)
111
111
 
@@ -121,13 +121,13 @@ class DataPointNameData(ChannelNameData):
121
121
  return DataPointNameData(device_name="", channel_name="")
122
122
 
123
123
  @staticmethod
124
- def _get_channel_parameter_name(channel_name: str, parameter_name: str | None) -> str:
124
+ def _get_channel_parameter_name(*, channel_name: str, parameter_name: str | None) -> str:
125
125
  """Return the channel parameter name of the data_point."""
126
126
  if channel_name and parameter_name:
127
127
  return f"{channel_name} {parameter_name}".strip()
128
128
  return channel_name.strip()
129
129
 
130
- def _get_data_point_name(self, device_name: str, channel_name: str, parameter_name: str | None) -> str:
130
+ def _get_data_point_name(self, *, device_name: str, channel_name: str, parameter_name: str | None) -> str:
131
131
  """Return the name of the data_point only name."""
132
132
  channel_parameter_name = self._get_channel_parameter_name(
133
133
  channel_name=channel_name, parameter_name=parameter_name
@@ -145,7 +145,7 @@ class HubNameData:
145
145
  "name",
146
146
  )
147
147
 
148
- def __init__(self, name: str, central_name: str | None = None, channel_name: str | None = None) -> None:
148
+ def __init__(self, *, name: str, central_name: str | None = None, channel_name: str | None = None) -> None:
149
149
  """Init the DataPointNameData class."""
150
150
  self.name: Final = name
151
151
  self.full_name = (
@@ -227,6 +227,7 @@ class DataPointPathData(PathData):
227
227
 
228
228
  def __init__(
229
229
  self,
230
+ *,
230
231
  interface: Interface | None,
231
232
  address: str,
232
233
  channel_no: int | None,
@@ -261,7 +262,7 @@ class ProgramPathData(PathData):
261
262
  "_state_path",
262
263
  )
263
264
 
264
- def __init__(self, pid: str):
265
+ def __init__(self, *, pid: str):
265
266
  """Init the path data."""
266
267
  self._set_path: Final = f"{PROGRAM_SET_PATH_ROOT}/{pid}"
267
268
  self._state_path: Final = f"{PROGRAM_STATE_PATH_ROOT}/{pid}"
@@ -285,7 +286,7 @@ class SysvarPathData(PathData):
285
286
  "_state_path",
286
287
  )
287
288
 
288
- def __init__(self, vid: str):
289
+ def __init__(self, *, vid: str):
289
290
  """Init the path data."""
290
291
  self._set_path: Final = f"{SYSVAR_SET_PATH_ROOT}/{vid}"
291
292
  self._state_path: Final = f"{SYSVAR_STATE_PATH_ROOT}/{vid}"
@@ -527,7 +528,7 @@ def _convert_value_noncached(value: Any, target_type: ParameterType, value_list:
527
528
  # relevant for ENUMs retyped to a BOOL
528
529
  return _get_binary_sensor_value(value=value, value_list=value_list)
529
530
  if isinstance(value, str):
530
- return to_bool(value)
531
+ return to_bool(value=value)
531
532
  return bool(value)
532
533
  if target_type == ParameterType.FLOAT:
533
534
  return float(value)
@@ -39,7 +39,7 @@ class DpUpdate(CallbackDataPoint, PayloadMixin):
39
39
 
40
40
  _category = DataPointCategory.UPDATE
41
41
 
42
- def __init__(self, device: hmd.Device) -> None:
42
+ def __init__(self, *, device: hmd.Device) -> None:
43
43
  """Init the callback data_point."""
44
44
  PayloadMixin.__init__(self)
45
45
  self._device: Final = device
@@ -112,7 +112,7 @@ class DpUpdate(CallbackDataPoint, PayloadMixin):
112
112
  kind=DataPointCategory.UPDATE,
113
113
  )
114
114
 
115
- def register_data_point_updated_callback(self, cb: Callable, custom_id: str) -> CALLBACK_TYPE:
115
+ def register_data_point_updated_callback(self, *, cb: Callable, custom_id: str) -> CALLBACK_TYPE:
116
116
  """Register update callback."""
117
117
  if custom_id != DEFAULT_CUSTOM_ID:
118
118
  if self._custom_id is not None:
@@ -121,18 +121,18 @@ class DpUpdate(CallbackDataPoint, PayloadMixin):
121
121
  )
122
122
  self._custom_id = custom_id
123
123
 
124
- if self._device.register_firmware_update_callback(cb) is not None:
124
+ if self._device.register_firmware_update_callback(cb=cb) is not None:
125
125
  return partial(self._unregister_data_point_updated_callback, cb=cb, custom_id=custom_id)
126
126
  return None
127
127
 
128
- def _unregister_data_point_updated_callback(self, cb: Callable, custom_id: str) -> None:
128
+ def _unregister_data_point_updated_callback(self, *, cb: Callable, custom_id: str) -> None:
129
129
  """Unregister update callback."""
130
130
  if custom_id is not None:
131
131
  self._custom_id = None
132
- self._device.unregister_firmware_update_callback(cb)
132
+ self._device.unregister_firmware_update_callback(cb=cb)
133
133
 
134
134
  @inspector
135
- async def update_firmware(self, refresh_after_update_intervals: tuple[int, ...]) -> bool:
135
+ async def update_firmware(self, *, refresh_after_update_intervals: tuple[int, ...]) -> bool:
136
136
  """Turn the update on."""
137
137
  return await self._device.update_firmware(refresh_after_update_intervals=refresh_after_update_intervals)
138
138
 
@@ -65,6 +65,8 @@ class _GenericProperty[GETTER, SETTER](property):
65
65
 
66
66
  """
67
67
 
68
+ __kwonly_check__ = False
69
+
68
70
  fget: Callable[[Any], GETTER] | None
69
71
  fset: Callable[[Any, SETTER], None] | None
70
72
  fdel: Callable[[Any], None] | None