aiohomematic 2025.10.1__py3-none-any.whl → 2025.10.2__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 (56) hide show
  1. aiohomematic/async_support.py +7 -7
  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 +87 -74
  6. aiohomematic/central/decorators.py +2 -2
  7. aiohomematic/central/xml_rpc_server.py +27 -24
  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 +6 -3
  29. aiohomematic/model/custom/switch.py +3 -2
  30. aiohomematic/model/custom/valve.py +3 -2
  31. aiohomematic/model/data_point.py +62 -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 +7 -5
  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 +8 -7
  47. aiohomematic/model/update.py +6 -6
  48. aiohomematic/support.py +44 -38
  49. aiohomematic/validator.py +6 -6
  50. {aiohomematic-2025.10.1.dist-info → aiohomematic-2025.10.2.dist-info}/METADATA +1 -1
  51. aiohomematic-2025.10.2.dist-info/RECORD +78 -0
  52. aiohomematic_support/client_local.py +19 -12
  53. aiohomematic-2025.10.1.dist-info/RECORD +0 -78
  54. {aiohomematic-2025.10.1.dist-info → aiohomematic-2025.10.2.dist-info}/WHEEL +0 -0
  55. {aiohomematic-2025.10.1.dist-info → aiohomematic-2025.10.2.dist-info}/licenses/LICENSE +0 -0
  56. {aiohomematic-2025.10.1.dist-info → aiohomematic-2025.10.2.dist-info}/top_level.txt +0 -0
@@ -193,7 +193,7 @@ INTERFACE_EVENT_SCHEMA = vol.Schema(
193
193
  class CentralUnit(LogContextMixin, PayloadMixin):
194
194
  """Central unit that collects everything to handle communication from/to the backend."""
195
195
 
196
- def __init__(self, central_config: CentralConfig) -> None:
196
+ def __init__(self, *, central_config: CentralConfig) -> None:
197
197
  """Init the central unit."""
198
198
  self._state: CentralUnitState = CentralUnitState.NEW
199
199
  self._clients_started: bool = False
@@ -219,7 +219,7 @@ class CentralUnit(LogContextMixin, PayloadMixin):
219
219
  # {interface_id, client}
220
220
  self._clients: Final[dict[str, hmcl.Client]] = {}
221
221
  self._data_point_key_event_subscriptions: Final[
222
- dict[DataPointKey, list[Callable[[Any, datetime], Coroutine[Any, Any, None]]]]
222
+ dict[DataPointKey, list[Callable[..., Coroutine[Any, Any, None]]]]
223
223
  ] = {}
224
224
  self._data_point_path_event_subscriptions: Final[dict[str, DataPointKey]] = {}
225
225
  self._sysvar_data_point_event_subscriptions: Final[dict[str, Callable]] = {}
@@ -229,7 +229,7 @@ class CentralUnit(LogContextMixin, PayloadMixin):
229
229
  self._sysvar_data_points: Final[dict[str, GenericSysvarDataPoint]] = {}
230
230
  # {sysvar_name, program_button}
231
231
  self._program_data_points: Final[dict[str, ProgramDpType]] = {}
232
- # Signature: (name, *args)
232
+ # Signature: (system_event, new_data_points, new_channel_events, **kwargs)
233
233
  # e.g. DEVICES_CREATED, HUB_REFRESHED
234
234
  self._backend_system_callbacks: Final[set[Callable]] = set()
235
235
  # Signature: (interface_id, channel_address, parameter, value)
@@ -421,14 +421,14 @@ class CentralUnit(LogContextMixin, PayloadMixin):
421
421
  self._version = max(versions) if versions else None
422
422
  return self._version
423
423
 
424
- def add_sysvar_data_point(self, sysvar_data_point: GenericSysvarDataPoint) -> None:
424
+ def add_sysvar_data_point(self, *, sysvar_data_point: GenericSysvarDataPoint) -> None:
425
425
  """Add new program button."""
426
426
  if (vid := sysvar_data_point.vid) is not None:
427
427
  self._sysvar_data_points[vid] = sysvar_data_point
428
428
  if sysvar_data_point.state_path not in self._sysvar_data_point_event_subscriptions:
429
429
  self._sysvar_data_point_event_subscriptions[sysvar_data_point.state_path] = sysvar_data_point.event
430
430
 
431
- def remove_sysvar_data_point(self, vid: str) -> None:
431
+ def remove_sysvar_data_point(self, *, vid: str) -> None:
432
432
  """Remove a sysvar data_point."""
433
433
  if (sysvar_dp := self.get_sysvar_data_point(vid=vid)) is not None:
434
434
  sysvar_dp.fire_device_removed_callback()
@@ -436,18 +436,18 @@ class CentralUnit(LogContextMixin, PayloadMixin):
436
436
  if sysvar_dp.state_path in self._sysvar_data_point_event_subscriptions:
437
437
  del self._sysvar_data_point_event_subscriptions[sysvar_dp.state_path]
438
438
 
439
- def add_program_data_point(self, program_dp: ProgramDpType) -> None:
439
+ def add_program_data_point(self, *, program_dp: ProgramDpType) -> None:
440
440
  """Add new program button."""
441
441
  self._program_data_points[program_dp.pid] = program_dp
442
442
 
443
- def remove_program_button(self, pid: str) -> None:
443
+ def remove_program_button(self, *, pid: str) -> None:
444
444
  """Remove a program button."""
445
445
  if (program_dp := self.get_program_data_point(pid=pid)) is not None:
446
446
  program_dp.button.fire_device_removed_callback()
447
447
  program_dp.switch.fire_device_removed_callback()
448
448
  del self._program_data_points[pid]
449
449
 
450
- def identify_channel(self, text: str) -> Channel | None:
450
+ def identify_channel(self, *, text: str) -> Channel | None:
451
451
  """Identify channel within a text."""
452
452
  for device in self._devices.values():
453
453
  if channel := device.identify_channel(text=text):
@@ -455,7 +455,7 @@ class CentralUnit(LogContextMixin, PayloadMixin):
455
455
  return None
456
456
 
457
457
  async def save_caches(
458
- self, save_device_descriptions: bool = False, save_paramset_descriptions: bool = False
458
+ self, *, save_device_descriptions: bool = False, save_paramset_descriptions: bool = False
459
459
  ) -> None:
460
460
  """Save persistent caches."""
461
461
  if save_device_descriptions:
@@ -496,7 +496,7 @@ class CentralUnit(LogContextMixin, PayloadMixin):
496
496
  ):
497
497
  self._xml_rpc_server = xml_rpc_server
498
498
  self._listen_port = xml_rpc_server.listen_port
499
- self._xml_rpc_server.add_central(self)
499
+ self._xml_rpc_server.add_central(central=self)
500
500
  except OSError as oserr:
501
501
  self._state = CentralUnitState.STOPPED_BY_ERROR
502
502
  raise AioHomematicException(
@@ -573,7 +573,7 @@ class CentralUnit(LogContextMixin, PayloadMixin):
573
573
  _LOGGER.info("RESTART_CLIENTS: Central %s restarted clients", self.name)
574
574
 
575
575
  @inspector(re_raise=False)
576
- async def refresh_firmware_data(self, device_address: str | None = None) -> None:
576
+ async def refresh_firmware_data(self, *, device_address: str | None = None) -> None:
577
577
  """Refresh device firmware data."""
578
578
  if device_address and (device := self.get_device(address=device_address)) is not None and device.is_updatable:
579
579
  await self._refresh_device_descriptions(client=device.client, device_address=device_address)
@@ -586,7 +586,7 @@ class CentralUnit(LogContextMixin, PayloadMixin):
586
586
  device.refresh_firmware_data()
587
587
 
588
588
  @inspector(re_raise=False)
589
- async def refresh_firmware_data_by_state(self, device_firmware_states: tuple[DeviceFirmwareState, ...]) -> None:
589
+ async def refresh_firmware_data_by_state(self, *, device_firmware_states: tuple[DeviceFirmwareState, ...]) -> None:
590
590
  """Refresh device firmware data for processing devices."""
591
591
  for device in [
592
592
  device_in_state
@@ -595,7 +595,7 @@ class CentralUnit(LogContextMixin, PayloadMixin):
595
595
  ]:
596
596
  await self.refresh_firmware_data(device_address=device.address)
597
597
 
598
- async def _refresh_device_descriptions(self, client: hmcl.Client, device_address: str | None = None) -> None:
598
+ async def _refresh_device_descriptions(self, *, client: hmcl.Client, device_address: str | None = None) -> None:
599
599
  """Refresh device descriptions."""
600
600
  device_descriptions: tuple[DeviceDescription, ...] | None = None
601
601
  if (
@@ -688,7 +688,7 @@ class CentralUnit(LogContextMixin, PayloadMixin):
688
688
  _LOGGER.debug("CREATE_CLIENTS successful for %s", self.name)
689
689
  return True
690
690
 
691
- async def _create_client(self, interface_config: hmcl.InterfaceConfig) -> bool:
691
+ async def _create_client(self, *, interface_config: hmcl.InterfaceConfig) -> bool:
692
692
  """Create a client."""
693
693
  try:
694
694
  if client := await hmcl.create_client(
@@ -744,6 +744,7 @@ class CentralUnit(LogContextMixin, PayloadMixin):
744
744
  @loop_check
745
745
  def fire_interface_event(
746
746
  self,
747
+ *,
747
748
  interface_id: str,
748
749
  interface_event_type: InterfaceEventType,
749
750
  data: dict[str, Any],
@@ -761,7 +762,7 @@ class CentralUnit(LogContextMixin, PayloadMixin):
761
762
  event_data=cast(dict[EventKey, Any], INTERFACE_EVENT_SCHEMA(event_data)),
762
763
  )
763
764
 
764
- async def _identify_ip_addr(self, port: int) -> str:
765
+ async def _identify_ip_addr(self, *, port: int) -> str:
765
766
  ip_addr: str | None = None
766
767
  while ip_addr is None:
767
768
  try:
@@ -811,24 +812,24 @@ class CentralUnit(LogContextMixin, PayloadMixin):
811
812
  system_information = client.system_information
812
813
  return system_information
813
814
 
814
- def get_client(self, interface_id: str) -> hmcl.Client:
815
+ def get_client(self, *, interface_id: str) -> hmcl.Client:
815
816
  """Return a client by interface_id."""
816
817
  if not self.has_client(interface_id=interface_id):
817
818
  raise AioHomematicException(f"get_client: interface_id {interface_id} does not exist on {self.name}")
818
819
  return self._clients[interface_id]
819
820
 
820
- def get_channel(self, channel_address: str) -> Channel | None:
821
+ def get_channel(self, *, channel_address: str) -> Channel | None:
821
822
  """Return Homematic channel."""
822
823
  if device := self.get_device(address=channel_address):
823
824
  return device.get_channel(channel_address=channel_address)
824
825
  return None
825
826
 
826
- def get_device(self, address: str) -> Device | None:
827
+ def get_device(self, *, address: str) -> Device | None:
827
828
  """Return Homematic device."""
828
829
  d_address = get_device_address(address=address)
829
830
  return self._devices.get(d_address)
830
831
 
831
- def get_data_point_by_custom_id(self, custom_id: str) -> CallbackDataPoint | None:
832
+ def get_data_point_by_custom_id(self, *, custom_id: str) -> CallbackDataPoint | None:
832
833
  """Return Homematic data_point by custom_id."""
833
834
  for dp in self.get_data_points(registered=True):
834
835
  if dp.custom_id == custom_id:
@@ -837,6 +838,7 @@ class CentralUnit(LogContextMixin, PayloadMixin):
837
838
 
838
839
  def get_data_points(
839
840
  self,
841
+ *,
840
842
  category: DataPointCategory | None = None,
841
843
  interface: Interface | None = None,
842
844
  exclude_no_create: bool = True,
@@ -853,7 +855,7 @@ class CentralUnit(LogContextMixin, PayloadMixin):
853
855
  return tuple(all_data_points)
854
856
 
855
857
  def get_readable_generic_data_points(
856
- self, paramset_key: ParamsetKey | None = None, interface: Interface | None = None
858
+ self, *, paramset_key: ParamsetKey | None = None, interface: Interface | None = None
857
859
  ) -> tuple[GenericDataPoint, ...]:
858
860
  """Return the readable generic data points."""
859
861
  return tuple(
@@ -875,7 +877,7 @@ class CentralUnit(LogContextMixin, PayloadMixin):
875
877
  return client
876
878
 
877
879
  def get_hub_data_points(
878
- self, category: DataPointCategory | None = None, registered: bool | None = None
880
+ self, *, category: DataPointCategory | None = None, registered: bool | None = None
879
881
  ) -> tuple[GenericHubDataPoint, ...]:
880
882
  """Return the program data points."""
881
883
  return tuple(
@@ -884,7 +886,9 @@ class CentralUnit(LogContextMixin, PayloadMixin):
884
886
  if (category is None or he.category == category) and (registered is None or he.is_registered == registered)
885
887
  )
886
888
 
887
- def get_events(self, event_type: EventType, registered: bool | None = None) -> tuple[tuple[GenericEvent, ...], ...]:
889
+ def get_events(
890
+ self, *, event_type: EventType, registered: bool | None = None
891
+ ) -> tuple[tuple[GenericEvent, ...], ...]:
888
892
  """Return all channel event data points."""
889
893
  hm_channel_events: list[tuple[GenericEvent, ...]] = []
890
894
  for device in self.devices:
@@ -902,7 +906,7 @@ class CentralUnit(LogContextMixin, PayloadMixin):
902
906
  if cl.get_virtual_remote() is not None
903
907
  )
904
908
 
905
- def has_client(self, interface_id: str) -> bool:
909
+ def has_client(self, *, interface_id: str) -> bool:
906
910
  """Check if client exists in central."""
907
911
  return interface_id in self._clients
908
912
 
@@ -930,7 +934,7 @@ class CentralUnit(LogContextMixin, PayloadMixin):
930
934
  await self._data_cache.load()
931
935
  return True
932
936
 
933
- async def _create_devices(self, new_device_addresses: Mapping[str, set[str]]) -> None:
937
+ async def _create_devices(self, *, new_device_addresses: Mapping[str, set[str]]) -> None:
934
938
  """Trigger creation of the objects that expose the functionality."""
935
939
  if not self._clients:
936
940
  raise AioHomematicException(
@@ -986,7 +990,7 @@ class CentralUnit(LogContextMixin, PayloadMixin):
986
990
  new_channel_events=new_channel_events,
987
991
  )
988
992
 
989
- async def delete_device(self, interface_id: str, device_address: str) -> None:
993
+ async def delete_device(self, *, interface_id: str, device_address: str) -> None:
990
994
  """Delete devices from central."""
991
995
  _LOGGER.debug(
992
996
  "DELETE_DEVICE: interface_id = %s, device_address = %s",
@@ -1000,7 +1004,7 @@ class CentralUnit(LogContextMixin, PayloadMixin):
1000
1004
  await self.delete_devices(interface_id=interface_id, addresses=[device_address, *list(device.channels.keys())])
1001
1005
 
1002
1006
  @callback_backend_system(system_event=BackendSystemEvent.DELETE_DEVICES)
1003
- async def delete_devices(self, interface_id: str, addresses: tuple[str, ...]) -> None:
1007
+ async def delete_devices(self, *, interface_id: str, addresses: tuple[str, ...]) -> None:
1004
1008
  """Delete devices from central."""
1005
1009
  _LOGGER.debug(
1006
1010
  "DELETE_DEVICES: interface_id = %s, addresses = %s",
@@ -1013,12 +1017,12 @@ class CentralUnit(LogContextMixin, PayloadMixin):
1013
1017
  await self.save_caches()
1014
1018
 
1015
1019
  @callback_backend_system(system_event=BackendSystemEvent.NEW_DEVICES)
1016
- async def add_new_devices(self, interface_id: str, device_descriptions: tuple[DeviceDescription, ...]) -> None:
1020
+ async def add_new_devices(self, *, interface_id: str, device_descriptions: tuple[DeviceDescription, ...]) -> None:
1017
1021
  """Add new devices to central unit."""
1018
1022
  await self._add_new_devices(interface_id=interface_id, device_descriptions=device_descriptions)
1019
1023
 
1020
1024
  @inspector(measure_performance=True)
1021
- async def _add_new_devices(self, interface_id: str, device_descriptions: tuple[DeviceDescription, ...]) -> None:
1025
+ async def _add_new_devices(self, *, interface_id: str, device_descriptions: tuple[DeviceDescription, ...]) -> None:
1022
1026
  """Add new devices to central unit."""
1023
1027
  if not device_descriptions:
1024
1028
  _LOGGER.debug(
@@ -1104,7 +1108,7 @@ class CentralUnit(LogContextMixin, PayloadMixin):
1104
1108
  return new_device_addresses
1105
1109
 
1106
1110
  @callback_event
1107
- async def data_point_event(self, interface_id: str, channel_address: str, parameter: str, value: Any) -> None:
1111
+ async def data_point_event(self, *, interface_id: str, channel_address: str, parameter: str, value: Any) -> None:
1108
1112
  """If a device emits some sort event, we will handle it here."""
1109
1113
  _LOGGER_EVENT.debug(
1110
1114
  "EVENT: interface_id = %s, channel_address = %s, parameter = %s, value = %s",
@@ -1143,7 +1147,7 @@ class CentralUnit(LogContextMixin, PayloadMixin):
1143
1147
  received_at = datetime.now()
1144
1148
  for callback_handler in self._data_point_key_event_subscriptions[dpk]:
1145
1149
  if callable(callback_handler):
1146
- await callback_handler(value, received_at)
1150
+ await callback_handler(value=value, received_at=received_at)
1147
1151
  except RuntimeError as rterr: # pragma: no cover
1148
1152
  _LOGGER_EVENT.debug(
1149
1153
  "EVENT: RuntimeError [%s]. Failed to call callback for: %s, %s, %s",
@@ -1161,7 +1165,7 @@ class CentralUnit(LogContextMixin, PayloadMixin):
1161
1165
  extract_exc_args(exc=exc),
1162
1166
  )
1163
1167
 
1164
- def data_point_path_event(self, state_path: str, value: str) -> None:
1168
+ def data_point_path_event(self, *, state_path: str, value: str) -> None:
1165
1169
  """If a device emits some sort event, we will handle it here."""
1166
1170
  _LOGGER_EVENT.debug(
1167
1171
  "DATA_POINT_PATH_EVENT: topic = %s, payload = %s",
@@ -1171,7 +1175,7 @@ class CentralUnit(LogContextMixin, PayloadMixin):
1171
1175
 
1172
1176
  if (dpk := self._data_point_path_event_subscriptions.get(state_path)) is not None:
1173
1177
  self._looper.create_task(
1174
- self.data_point_event(
1178
+ target=self.data_point_event(
1175
1179
  interface_id=dpk.interface_id,
1176
1180
  channel_address=dpk.channel_address,
1177
1181
  parameter=dpk.parameter,
@@ -1180,7 +1184,7 @@ class CentralUnit(LogContextMixin, PayloadMixin):
1180
1184
  name=f"device-data-point-event-{dpk.interface_id}-{dpk.channel_address}-{dpk.parameter}",
1181
1185
  )
1182
1186
 
1183
- def sysvar_data_point_path_event(self, state_path: str, value: str) -> None:
1187
+ def sysvar_data_point_path_event(self, *, state_path: str, value: str) -> None:
1184
1188
  """If a device emits some sort event, we will handle it here."""
1185
1189
  _LOGGER_EVENT.debug(
1186
1190
  "SYSVAR_DATA_POINT_PATH_EVENT: topic = %s, payload = %s",
@@ -1194,7 +1198,8 @@ class CentralUnit(LogContextMixin, PayloadMixin):
1194
1198
  if callable(callback_handler):
1195
1199
  received_at = datetime.now()
1196
1200
  self._looper.create_task(
1197
- callback_handler(value, received_at), name=f"sysvar-data-point-event-{state_path}"
1201
+ target=callback_handler(value=value, received_at=received_at),
1202
+ name=f"sysvar-data-point-event-{state_path}",
1198
1203
  )
1199
1204
  except RuntimeError as rterr: # pragma: no cover
1200
1205
  _LOGGER_EVENT.debug(
@@ -1210,13 +1215,13 @@ class CentralUnit(LogContextMixin, PayloadMixin):
1210
1215
  )
1211
1216
 
1212
1217
  @callback_backend_system(system_event=BackendSystemEvent.LIST_DEVICES)
1213
- def list_devices(self, interface_id: str) -> list[DeviceDescription]:
1218
+ def list_devices(self, *, interface_id: str) -> list[DeviceDescription]:
1214
1219
  """Return already existing devices to the backend."""
1215
1220
  result = self._device_descriptions.get_raw_device_descriptions(interface_id=interface_id)
1216
1221
  _LOGGER.debug("LIST_DEVICES: interface_id = %s, channel_count = %i", interface_id, len(result))
1217
1222
  return result
1218
1223
 
1219
- def add_event_subscription(self, data_point: BaseParameterDataPoint) -> None:
1224
+ def add_event_subscription(self, *, data_point: BaseParameterDataPoint) -> None:
1220
1225
  """Add data_point to central event subscription."""
1221
1226
  if isinstance(data_point, GenericDataPoint | GenericEvent) and (
1222
1227
  data_point.is_readable or data_point.supports_events
@@ -1242,7 +1247,7 @@ class CentralUnit(LogContextMixin, PayloadMixin):
1242
1247
  for device in self.devices:
1243
1248
  await device.remove_central_links()
1244
1249
 
1245
- def remove_device(self, device: Device) -> None:
1250
+ def remove_device(self, *, device: Device) -> None:
1246
1251
  """Remove device to central collections."""
1247
1252
  if device.address not in self._devices:
1248
1253
  _LOGGER.debug(
@@ -1257,7 +1262,7 @@ class CentralUnit(LogContextMixin, PayloadMixin):
1257
1262
  self._device_details.remove_device(device=device)
1258
1263
  del self._devices[device.address]
1259
1264
 
1260
- def remove_event_subscription(self, data_point: BaseParameterDataPoint) -> None:
1265
+ def remove_event_subscription(self, *, data_point: BaseParameterDataPoint) -> None:
1261
1266
  """Remove event subscription from central collections."""
1262
1267
  if isinstance(data_point, GenericDataPoint | GenericEvent) and data_point.supports_events:
1263
1268
  if data_point.dpk in self._data_point_key_event_subscriptions:
@@ -1265,39 +1270,40 @@ class CentralUnit(LogContextMixin, PayloadMixin):
1265
1270
  if data_point.state_path in self._data_point_path_event_subscriptions:
1266
1271
  del self._data_point_path_event_subscriptions[data_point.state_path]
1267
1272
 
1268
- def get_last_event_seen_for_interface(self, interface_id: str) -> datetime | None:
1273
+ def get_last_event_seen_for_interface(self, *, interface_id: str) -> datetime | None:
1269
1274
  """Return the last event seen for an interface."""
1270
1275
  return self._last_event_seen_for_interface.get(interface_id)
1271
1276
 
1272
- def set_last_event_seen_for_interface(self, interface_id: str) -> None:
1277
+ def set_last_event_seen_for_interface(self, *, interface_id: str) -> None:
1273
1278
  """Set the last event seen for an interface."""
1274
1279
  self._last_event_seen_for_interface[interface_id] = datetime.now()
1275
1280
 
1276
- async def execute_program(self, pid: str) -> bool:
1281
+ async def execute_program(self, *, pid: str) -> bool:
1277
1282
  """Execute a program on the backend."""
1278
1283
  if client := self.primary_client:
1279
1284
  return await client.execute_program(pid=pid)
1280
1285
  return False
1281
1286
 
1282
- async def set_program_state(self, pid: str, state: bool) -> bool:
1287
+ async def set_program_state(self, *, pid: str, state: bool) -> bool:
1283
1288
  """Execute a program on the backend."""
1284
1289
  if client := self.primary_client:
1285
1290
  return await client.set_program_state(pid=pid, state=state)
1286
1291
  return False
1287
1292
 
1288
1293
  @inspector(re_raise=False)
1289
- async def fetch_sysvar_data(self, scheduled: bool) -> None:
1294
+ async def fetch_sysvar_data(self, *, scheduled: bool) -> None:
1290
1295
  """Fetch sysvar data for the hub."""
1291
1296
  await self._hub.fetch_sysvar_data(scheduled=scheduled)
1292
1297
 
1293
1298
  @inspector(re_raise=False)
1294
- async def fetch_program_data(self, scheduled: bool) -> None:
1299
+ async def fetch_program_data(self, *, scheduled: bool) -> None:
1295
1300
  """Fetch program data for the hub."""
1296
1301
  await self._hub.fetch_program_data(scheduled=scheduled)
1297
1302
 
1298
1303
  @inspector(measure_performance=True)
1299
1304
  async def load_and_refresh_data_point_data(
1300
1305
  self,
1306
+ *,
1301
1307
  interface: Interface,
1302
1308
  paramset_key: ParamsetKey | None = None,
1303
1309
  direct_call: bool = False,
@@ -1309,13 +1315,13 @@ class CentralUnit(LogContextMixin, PayloadMixin):
1309
1315
  paramset_key=paramset_key, interface=interface, direct_call=direct_call
1310
1316
  )
1311
1317
 
1312
- async def get_system_variable(self, legacy_name: str) -> Any | None:
1318
+ async def get_system_variable(self, *, legacy_name: str) -> Any | None:
1313
1319
  """Get system variable from the backend."""
1314
1320
  if client := self.primary_client:
1315
- return await client.get_system_variable(legacy_name)
1321
+ return await client.get_system_variable(name=legacy_name)
1316
1322
  return None
1317
1323
 
1318
- async def set_system_variable(self, legacy_name: str, value: Any) -> None:
1324
+ async def set_system_variable(self, *, legacy_name: str, value: Any) -> None:
1319
1325
  """Set variable value on the backend."""
1320
1326
  if dp := self.get_sysvar_data_point(legacy_name=legacy_name):
1321
1327
  await dp.send_variable(value=value)
@@ -1324,6 +1330,7 @@ class CentralUnit(LogContextMixin, PayloadMixin):
1324
1330
 
1325
1331
  def get_parameters(
1326
1332
  self,
1333
+ *,
1327
1334
  paramset_key: ParamsetKey,
1328
1335
  operations: tuple[Operations, ...],
1329
1336
  full_format: bool = False,
@@ -1401,7 +1408,7 @@ class CentralUnit(LogContextMixin, PayloadMixin):
1401
1408
 
1402
1409
  return tuple(parameters)
1403
1410
 
1404
- def _get_virtual_remote(self, device_address: str) -> Device | None:
1411
+ def _get_virtual_remote(self, *, device_address: str) -> Device | None:
1405
1412
  """Get the virtual remote for the Client."""
1406
1413
  for client in self._clients.values():
1407
1414
  virtual_remote = client.get_virtual_remote()
@@ -1410,7 +1417,7 @@ class CentralUnit(LogContextMixin, PayloadMixin):
1410
1417
  return None
1411
1418
 
1412
1419
  def get_generic_data_point(
1413
- self, channel_address: str, parameter: str, paramset_key: ParamsetKey | None = None
1420
+ self, *, channel_address: str, parameter: str, paramset_key: ParamsetKey | None = None
1414
1421
  ) -> GenericDataPoint | None:
1415
1422
  """Get data_point by channel_address and parameter."""
1416
1423
  if device := self.get_device(address=channel_address):
@@ -1419,20 +1426,20 @@ class CentralUnit(LogContextMixin, PayloadMixin):
1419
1426
  )
1420
1427
  return None
1421
1428
 
1422
- def get_event(self, channel_address: str, parameter: str) -> GenericEvent | None:
1429
+ def get_event(self, *, channel_address: str, parameter: str) -> GenericEvent | None:
1423
1430
  """Return the hm event."""
1424
1431
  if device := self.get_device(address=channel_address):
1425
1432
  return device.get_generic_event(channel_address=channel_address, parameter=parameter)
1426
1433
  return None
1427
1434
 
1428
- def get_custom_data_point(self, address: str, channel_no: int) -> CustomDataPoint | None:
1435
+ def get_custom_data_point(self, *, address: str, channel_no: int) -> CustomDataPoint | None:
1429
1436
  """Return the hm custom_data_point."""
1430
1437
  if device := self.get_device(address=address):
1431
1438
  return device.get_custom_data_point(channel_no=channel_no)
1432
1439
  return None
1433
1440
 
1434
1441
  def get_sysvar_data_point(
1435
- self, vid: str | None = None, legacy_name: str | None = None
1442
+ self, *, vid: str | None = None, legacy_name: str | None = None
1436
1443
  ) -> GenericSysvarDataPoint | None:
1437
1444
  """Return the sysvar data_point."""
1438
1445
  if vid and (sysvar := self._sysvar_data_points.get(vid)):
@@ -1443,7 +1450,7 @@ class CentralUnit(LogContextMixin, PayloadMixin):
1443
1450
  return sysvar
1444
1451
  return None
1445
1452
 
1446
- def get_program_data_point(self, pid: str | None = None, legacy_name: str | None = None) -> ProgramDpType | None:
1453
+ def get_program_data_point(self, *, pid: str | None = None, legacy_name: str | None = None) -> ProgramDpType | None:
1447
1454
  """Return the program data points."""
1448
1455
  if pid and (program := self._program_data_points.get(pid)):
1449
1456
  return program
@@ -1461,7 +1468,7 @@ class CentralUnit(LogContextMixin, PayloadMixin):
1461
1468
  """Return the registered sysvar state path."""
1462
1469
  return tuple(self._sysvar_data_point_event_subscriptions)
1463
1470
 
1464
- def get_un_ignore_candidates(self, include_master: bool = False) -> list[str]:
1471
+ def get_un_ignore_candidates(self, *, include_master: bool = False) -> list[str]:
1465
1472
  """Return the candidates for un_ignore."""
1466
1473
  candidates = sorted(
1467
1474
  # 1. request simple parameter list for values parameters
@@ -1505,20 +1512,20 @@ class CentralUnit(LogContextMixin, PayloadMixin):
1505
1512
  self._device_details.clear()
1506
1513
  self._data_cache.clear()
1507
1514
 
1508
- def register_homematic_callback(self, cb: Callable) -> CALLBACK_TYPE:
1515
+ def register_homematic_callback(self, *, cb: Callable) -> CALLBACK_TYPE:
1509
1516
  """Register ha_event callback in central."""
1510
1517
  if callable(cb) and cb not in self._homematic_callbacks:
1511
1518
  self._homematic_callbacks.add(cb)
1512
1519
  return partial(self._unregister_homematic_callback, cb=cb)
1513
1520
  return None
1514
1521
 
1515
- def _unregister_homematic_callback(self, cb: Callable) -> None:
1522
+ def _unregister_homematic_callback(self, *, cb: Callable) -> None:
1516
1523
  """RUn register ha_event callback in central."""
1517
1524
  if cb in self._homematic_callbacks:
1518
1525
  self._homematic_callbacks.remove(cb)
1519
1526
 
1520
1527
  @loop_check
1521
- def fire_homematic_callback(self, event_type: EventType, event_data: dict[EventKey, str]) -> None:
1528
+ def fire_homematic_callback(self, *, event_type: EventType, event_data: dict[EventKey, str]) -> None:
1522
1529
  """
1523
1530
  Fire homematic_callback in central.
1524
1531
 
@@ -1526,28 +1533,28 @@ class CentralUnit(LogContextMixin, PayloadMixin):
1526
1533
  """
1527
1534
  for callback_handler in self._homematic_callbacks:
1528
1535
  try:
1529
- callback_handler(event_type, event_data)
1536
+ callback_handler(event_type=event_type, event_data=event_data)
1530
1537
  except Exception as exc:
1531
1538
  _LOGGER.error(
1532
1539
  "FIRE_HOMEMATIC_CALLBACK: Unable to call handler: %s",
1533
1540
  extract_exc_args(exc=exc),
1534
1541
  )
1535
1542
 
1536
- def register_backend_parameter_callback(self, cb: Callable) -> CALLBACK_TYPE:
1543
+ def register_backend_parameter_callback(self, *, cb: Callable) -> CALLBACK_TYPE:
1537
1544
  """Register backend_parameter callback in central."""
1538
1545
  if callable(cb) and cb not in self._backend_parameter_callbacks:
1539
1546
  self._backend_parameter_callbacks.add(cb)
1540
1547
  return partial(self._unregister_backend_parameter_callback, cb=cb)
1541
1548
  return None
1542
1549
 
1543
- def _unregister_backend_parameter_callback(self, cb: Callable) -> None:
1550
+ def _unregister_backend_parameter_callback(self, *, cb: Callable) -> None:
1544
1551
  """Un register backend_parameter callback in central."""
1545
1552
  if cb in self._backend_parameter_callbacks:
1546
1553
  self._backend_parameter_callbacks.remove(cb)
1547
1554
 
1548
1555
  @loop_check
1549
1556
  def fire_backend_parameter_callback(
1550
- self, interface_id: str, channel_address: str, parameter: str, value: Any
1557
+ self, *, interface_id: str, channel_address: str, parameter: str, value: Any
1551
1558
  ) -> None:
1552
1559
  """
1553
1560
  Fire backend_parameter callback in central.
@@ -1556,27 +1563,29 @@ class CentralUnit(LogContextMixin, PayloadMixin):
1556
1563
  """
1557
1564
  for callback_handler in self._backend_parameter_callbacks:
1558
1565
  try:
1559
- callback_handler(interface_id, channel_address, parameter, value)
1566
+ callback_handler(
1567
+ interface_id=interface_id, channel_address=channel_address, parameter=parameter, value=value
1568
+ )
1560
1569
  except Exception as exc:
1561
1570
  _LOGGER.error(
1562
1571
  "FIRE_BACKEND_PARAMETER_CALLBACK: Unable to call handler: %s",
1563
1572
  extract_exc_args(exc=exc),
1564
1573
  )
1565
1574
 
1566
- def register_backend_system_callback(self, cb: Callable) -> CALLBACK_TYPE:
1575
+ def register_backend_system_callback(self, *, cb: Callable) -> CALLBACK_TYPE:
1567
1576
  """Register system_event callback in central."""
1568
1577
  if callable(cb) and cb not in self._backend_parameter_callbacks:
1569
1578
  self._backend_system_callbacks.add(cb)
1570
1579
  return partial(self._unregister_backend_system_callback, cb=cb)
1571
1580
  return None
1572
1581
 
1573
- def _unregister_backend_system_callback(self, cb: Callable) -> None:
1582
+ def _unregister_backend_system_callback(self, *, cb: Callable) -> None:
1574
1583
  """Un register system_event callback in central."""
1575
1584
  if cb in self._backend_system_callbacks:
1576
1585
  self._backend_system_callbacks.remove(cb)
1577
1586
 
1578
1587
  @loop_check
1579
- def fire_backend_system_callback(self, system_event: BackendSystemEvent, **kwargs: Any) -> None:
1588
+ def fire_backend_system_callback(self, *, system_event: BackendSystemEvent, **kwargs: Any) -> None:
1580
1589
  """
1581
1590
  Fire system_event callback in central.
1582
1591
 
@@ -1584,7 +1593,7 @@ class CentralUnit(LogContextMixin, PayloadMixin):
1584
1593
  """
1585
1594
  for callback_handler in self._backend_system_callbacks:
1586
1595
  try:
1587
- callback_handler(system_event, **kwargs)
1596
+ callback_handler(system_event=system_event, **kwargs)
1588
1597
  except Exception as exc:
1589
1598
  _LOGGER.error(
1590
1599
  "FIRE_BACKEND_SYSTEM_CALLBACK: Unable to call handler: %s",
@@ -1599,7 +1608,7 @@ class CentralUnit(LogContextMixin, PayloadMixin):
1599
1608
  class _Scheduler(threading.Thread):
1600
1609
  """Periodically check connection to the backend, and load data when required."""
1601
1610
 
1602
- def __init__(self, central: CentralUnit) -> None:
1611
+ def __init__(self, *, central: CentralUnit) -> None:
1603
1612
  """Init the connection checker."""
1604
1613
  threading.Thread.__init__(self, name=f"ConnectionChecker for {central.name}")
1605
1614
  self._central: Final = central
@@ -1644,7 +1653,7 @@ class _Scheduler(threading.Thread):
1644
1653
  )
1645
1654
 
1646
1655
  self._central.looper.create_task(
1647
- self._run_scheduler_tasks(),
1656
+ target=self._run_scheduler_tasks(),
1648
1657
  name="run_scheduler_tasks",
1649
1658
  )
1650
1659
 
@@ -1799,6 +1808,7 @@ class _SchedulerJob:
1799
1808
 
1800
1809
  def __init__(
1801
1810
  self,
1811
+ *,
1802
1812
  task: Callable,
1803
1813
  run_interval: int,
1804
1814
  next_run: datetime | None = None,
@@ -1827,6 +1837,7 @@ class CentralConfig:
1827
1837
 
1828
1838
  def __init__(
1829
1839
  self,
1840
+ *,
1830
1841
  central_id: str,
1831
1842
  host: str,
1832
1843
  interface_configs: AbstractSet[hmcl.InterfaceConfig],
@@ -1939,7 +1950,7 @@ class CentralConfig:
1939
1950
  """Create the central. Throws BaseHomematicException on validation failure."""
1940
1951
  try:
1941
1952
  self.check_config()
1942
- return CentralUnit(self)
1953
+ return CentralUnit(central_config=self)
1943
1954
  except BaseHomematicException as bhexc:
1944
1955
  raise AioHomematicException(
1945
1956
  f"CREATE_CENTRAL: Not able to create a central: : {extract_exc_args(exc=bhexc)}"
@@ -1953,7 +1964,7 @@ class CentralConfig:
1953
1964
  url = f"{url}:{self.json_port}"
1954
1965
  return f"{url}"
1955
1966
 
1956
- def create_json_rpc_client(self, central: CentralUnit) -> JsonRpcAioHttpClient:
1967
+ def create_json_rpc_client(self, *, central: CentralUnit) -> JsonRpcAioHttpClient:
1957
1968
  """Create a json rpc client."""
1958
1969
  return JsonRpcAioHttpClient(
1959
1970
  username=self.username,
@@ -1974,7 +1985,7 @@ class CentralConnectionState:
1974
1985
  self._json_issues: Final[list[str]] = []
1975
1986
  self._xml_proxy_issues: Final[list[str]] = []
1976
1987
 
1977
- def add_issue(self, issuer: ConnectionProblemIssuer, iid: str) -> bool:
1988
+ def add_issue(self, *, issuer: ConnectionProblemIssuer, iid: str) -> bool:
1978
1989
  """Add issue to collection."""
1979
1990
  if isinstance(issuer, JsonRpcAioHttpClient) and iid not in self._json_issues:
1980
1991
  self._json_issues.append(iid)
@@ -1986,7 +1997,7 @@ class CentralConnectionState:
1986
1997
  return True
1987
1998
  return False
1988
1999
 
1989
- def remove_issue(self, issuer: ConnectionProblemIssuer, iid: str) -> bool:
2000
+ def remove_issue(self, *, issuer: ConnectionProblemIssuer, iid: str) -> bool:
1990
2001
  """Add issue to collection."""
1991
2002
  if isinstance(issuer, JsonRpcAioHttpClient) and iid in self._json_issues:
1992
2003
  self._json_issues.remove(iid)
@@ -1998,7 +2009,7 @@ class CentralConnectionState:
1998
2009
  return True
1999
2010
  return False
2000
2011
 
2001
- def has_issue(self, issuer: ConnectionProblemIssuer, iid: str) -> bool:
2012
+ def has_issue(self, *, issuer: ConnectionProblemIssuer, iid: str) -> bool:
2002
2013
  """Add issue to collection."""
2003
2014
  if isinstance(issuer, JsonRpcAioHttpClient):
2004
2015
  return iid in self._json_issues
@@ -2007,6 +2018,7 @@ class CentralConnectionState:
2007
2018
 
2008
2019
  def handle_exception_log(
2009
2020
  self,
2021
+ *,
2010
2022
  issuer: ConnectionProblemIssuer,
2011
2023
  iid: str,
2012
2024
  exception: Exception,
@@ -2038,6 +2050,7 @@ class CentralConnectionState:
2038
2050
 
2039
2051
 
2040
2052
  def _get_new_data_points(
2053
+ *,
2041
2054
  new_devices: set[Device],
2042
2055
  ) -> Mapping[DataPointCategory, AbstractSet[CallbackDataPoint]]:
2043
2056
  """Return new data points by category."""
@@ -2053,7 +2066,7 @@ def _get_new_data_points(
2053
2066
  return data_points_by_category
2054
2067
 
2055
2068
 
2056
- def _get_new_channel_events(new_devices: set[Device]) -> tuple[tuple[GenericEvent, ...], ...]:
2069
+ def _get_new_channel_events(*, new_devices: set[Device]) -> tuple[tuple[GenericEvent, ...], ...]:
2057
2070
  """Return new channel events by category."""
2058
2071
  channel_events: list[tuple[GenericEvent, ...]] = []
2059
2072