aiohomematic 2025.10.0__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 +90 -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 +3 -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 +44 -20
  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.0.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.0.dist-info/RECORD +0 -78
  54. {aiohomematic-2025.10.0.dist-info → aiohomematic-2025.10.2.dist-info}/WHEEL +0 -0
  55. {aiohomematic-2025.10.0.dist-info → aiohomematic-2025.10.2.dist-info}/licenses/LICENSE +0 -0
  56. {aiohomematic-2025.10.0.dist-info → aiohomematic-2025.10.2.dist-info}/top_level.txt +0 -0
@@ -184,6 +184,7 @@ class JsonRpcAioHttpClient(LogContextMixin):
184
184
 
185
185
  def __init__(
186
186
  self,
187
+ *,
187
188
  username: str,
188
189
  password: str,
189
190
  device_url: str,
@@ -204,7 +205,7 @@ class JsonRpcAioHttpClient(LogContextMixin):
204
205
  self._password: Final = password
205
206
  self._looper = Looper()
206
207
  self._tls: Final = tls
207
- self._tls_context: Final[SSLContext | bool] = get_tls_context(verify_tls) if tls else False
208
+ self._tls_context: Final[SSLContext | bool] = get_tls_context(verify_tls=verify_tls) if tls else False
208
209
  self._url: Final = f"{device_url}{PATH_JSON_RPC}"
209
210
  self._script_cache: Final[dict[str, str]] = {}
210
211
  self._last_session_id_refresh: datetime | None = None
@@ -234,10 +235,10 @@ class JsonRpcAioHttpClient(LogContextMixin):
234
235
  self._last_session_id_refresh = datetime.now()
235
236
  return self._session_id is not None
236
237
  if self._session_id:
237
- self._session_id = await self._do_renew_login(self._session_id)
238
+ self._session_id = await self._do_renew_login(session_id=self._session_id)
238
239
  return self._session_id is not None
239
240
 
240
- async def _do_renew_login(self, session_id: str) -> str | None:
241
+ async def _do_renew_login(self, *, session_id: str) -> str | None:
241
242
  """Renew JSON-RPC session or perform login."""
242
243
  if self._has_session_recently_refreshed:
243
244
  return session_id
@@ -291,6 +292,7 @@ class JsonRpcAioHttpClient(LogContextMixin):
291
292
 
292
293
  async def _post(
293
294
  self,
295
+ *,
294
296
  method: _JsonRpcMethod,
295
297
  extra_params: dict[_JsonKey, Any] | None = None,
296
298
  use_default_params: bool = True,
@@ -328,6 +330,7 @@ class JsonRpcAioHttpClient(LogContextMixin):
328
330
 
329
331
  async def _post_script(
330
332
  self,
333
+ *,
331
334
  script_name: str,
332
335
  extra_params: dict[_JsonKey, Any] | None = None,
333
336
  keep_session: bool = True,
@@ -370,7 +373,7 @@ class JsonRpcAioHttpClient(LogContextMixin):
370
373
 
371
374
  return response
372
375
 
373
- async def _get_script(self, script_name: str) -> str | None:
376
+ async def _get_script(self, *, script_name: str) -> str | None:
374
377
  """Return a script from the script cache. Load if required."""
375
378
  if script_name in self._script_cache:
376
379
  return self._script_cache[script_name]
@@ -387,6 +390,7 @@ class JsonRpcAioHttpClient(LogContextMixin):
387
390
 
388
391
  async def _do_post(
389
392
  self,
393
+ *,
390
394
  session_id: bool | str,
391
395
  method: _JsonRpcMethod,
392
396
  extra_params: dict[_JsonKey, Any] | None = None,
@@ -527,7 +531,7 @@ class JsonRpcAioHttpClient(LogContextMixin):
527
531
  )
528
532
  raise ClientException(exc) from exc
529
533
 
530
- async def _get_json_reponse(self, response: ClientResponse) -> dict[str, Any] | Any:
534
+ async def _get_json_reponse(self, *, response: ClientResponse) -> dict[str, Any] | Any:
531
535
  """Return the json object from response."""
532
536
  try:
533
537
  return await response.json(encoding=UTF_8)
@@ -543,7 +547,7 @@ class JsonRpcAioHttpClient(LogContextMixin):
543
547
  """Logout of the backend."""
544
548
  try:
545
549
  await self._looper.block_till_done()
546
- await self._do_logout(self._session_id)
550
+ await self._do_logout(session_id=self._session_id)
547
551
  except BaseHomematicException:
548
552
  _LOGGER.debug("LOGOUT: logout failed")
549
553
 
@@ -552,7 +556,7 @@ class JsonRpcAioHttpClient(LogContextMixin):
552
556
  if self._is_internal_session:
553
557
  await self._client_session.close()
554
558
 
555
- async def _do_logout(self, session_id: str | None) -> None:
559
+ async def _do_logout(self, *, session_id: str | None) -> None:
556
560
  """Logout of the backend."""
557
561
  if not session_id:
558
562
  _LOGGER.debug("DO_LOGOUT: Not logged in. Not logging out.")
@@ -579,7 +583,7 @@ class JsonRpcAioHttpClient(LogContextMixin):
579
583
  """Clear the current session."""
580
584
  self._session_id = None
581
585
 
582
- async def execute_program(self, pid: str) -> bool:
586
+ async def execute_program(self, *, pid: str) -> bool:
583
587
  """Execute a program on the backend."""
584
588
  params = {
585
589
  _JsonKey.ID: pid,
@@ -596,7 +600,7 @@ class JsonRpcAioHttpClient(LogContextMixin):
596
600
 
597
601
  return True
598
602
 
599
- async def set_program_state(self, pid: str, state: bool) -> bool:
603
+ async def set_program_state(self, *, pid: str, state: bool) -> bool:
600
604
  """Set the program state on the backend."""
601
605
  params = {
602
606
  _JsonKey.ID: pid,
@@ -613,7 +617,7 @@ class JsonRpcAioHttpClient(LogContextMixin):
613
617
 
614
618
  return True
615
619
 
616
- async def set_system_variable(self, legacy_name: str, value: Any) -> bool:
620
+ async def set_system_variable(self, *, legacy_name: str, value: Any) -> bool:
617
621
  """Set a system variable on the backend."""
618
622
  params = {_JsonKey.NAME: legacy_name, _JsonKey.VALUE: value}
619
623
  if isinstance(value, bool):
@@ -639,7 +643,7 @@ class JsonRpcAioHttpClient(LogContextMixin):
639
643
 
640
644
  return True
641
645
 
642
- async def delete_system_variable(self, name: str) -> bool:
646
+ async def delete_system_variable(self, *, name: str) -> bool:
643
647
  """Delete a system variable from the backend."""
644
648
  params = {_JsonKey.NAME: name}
645
649
  response = await self._post(
@@ -654,7 +658,7 @@ class JsonRpcAioHttpClient(LogContextMixin):
654
658
 
655
659
  return True
656
660
 
657
- async def get_system_variable(self, name: str) -> Any:
661
+ async def get_system_variable(self, *, name: str) -> Any:
658
662
  """Get single system variable from the backend."""
659
663
  params = {_JsonKey.NAME: name}
660
664
  response = await self._post(
@@ -666,7 +670,7 @@ class JsonRpcAioHttpClient(LogContextMixin):
666
670
  return response[_JsonKey.RESULT]
667
671
 
668
672
  async def get_all_system_variables(
669
- self, markers: tuple[DescriptionMarker | str, ...]
673
+ self, *, markers: tuple[DescriptionMarker | str, ...]
670
674
  ) -> tuple[SystemVariableData, ...]:
671
675
  """Get all system variables from the backend."""
672
676
  variables: list[SystemVariableData] = []
@@ -843,7 +847,7 @@ class JsonRpcAioHttpClient(LogContextMixin):
843
847
 
844
848
  return channel_ids_function
845
849
 
846
- async def get_device_description(self, interface: Interface, address: str) -> DeviceDescription | None:
850
+ async def get_device_description(self, *, interface: Interface, address: str) -> DeviceDescription | None:
847
851
  """Get device descriptions from the backend."""
848
852
  device_description: DeviceDescription | None = None
849
853
  params = {
@@ -860,7 +864,7 @@ class JsonRpcAioHttpClient(LogContextMixin):
860
864
  return device_description
861
865
 
862
866
  @staticmethod
863
- def _convert_device_description(json_data: dict[str, Any]) -> DeviceDescription:
867
+ def _convert_device_description(*, json_data: dict[str, Any]) -> DeviceDescription:
864
868
  """Convert json data dor device description."""
865
869
  device_description = DeviceDescription(
866
870
  TYPE=json_data["type"],
@@ -904,7 +908,7 @@ class JsonRpcAioHttpClient(LogContextMixin):
904
908
  return device_details
905
909
 
906
910
  async def get_paramset(
907
- self, interface: Interface, address: str, paramset_key: ParamsetKey | str
911
+ self, *, interface: Interface, address: str, paramset_key: ParamsetKey | str
908
912
  ) -> dict[str, Any] | None:
909
913
  """Get paramset from the backend."""
910
914
  paramset: dict[str, Any] = {}
@@ -927,6 +931,7 @@ class JsonRpcAioHttpClient(LogContextMixin):
927
931
 
928
932
  async def put_paramset(
929
933
  self,
934
+ *,
930
935
  interface: Interface,
931
936
  address: str,
932
937
  paramset_key: ParamsetKey | str,
@@ -952,7 +957,7 @@ class JsonRpcAioHttpClient(LogContextMixin):
952
957
  str(json_result),
953
958
  )
954
959
 
955
- async def get_value(self, interface: Interface, address: str, paramset_key: ParamsetKey, parameter: str) -> Any:
960
+ async def get_value(self, *, interface: Interface, address: str, paramset_key: ParamsetKey, parameter: str) -> Any:
956
961
  """Get value from the backend."""
957
962
  value: Any = None
958
963
  params = {
@@ -973,7 +978,9 @@ class JsonRpcAioHttpClient(LogContextMixin):
973
978
 
974
979
  return value
975
980
 
976
- async def set_value(self, interface: Interface, address: str, parameter: str, value_type: str, value: Any) -> None:
981
+ async def set_value(
982
+ self, *, interface: Interface, address: str, parameter: str, value_type: str, value: Any
983
+ ) -> None:
977
984
  """Set value to the backend."""
978
985
  params = {
979
986
  _JsonKey.INTERFACE: interface,
@@ -996,7 +1003,7 @@ class JsonRpcAioHttpClient(LogContextMixin):
996
1003
  )
997
1004
 
998
1005
  async def get_paramset_description(
999
- self, interface: Interface, address: str, paramset_key: ParamsetKey
1006
+ self, *, interface: Interface, address: str, paramset_key: ParamsetKey
1000
1007
  ) -> Mapping[str, ParameterData] | None:
1001
1008
  """Get paramset description from the backend."""
1002
1009
  paramset_description: dict[str, ParameterData] = {}
@@ -1015,7 +1022,7 @@ class JsonRpcAioHttpClient(LogContextMixin):
1015
1022
  return paramset_description
1016
1023
 
1017
1024
  @staticmethod
1018
- def _convert_parameter_data(json_data: dict[str, Any]) -> ParameterData:
1025
+ def _convert_parameter_data(*, json_data: dict[str, Any]) -> ParameterData:
1019
1026
  """Convert json data to parameter data."""
1020
1027
 
1021
1028
  _type = json_data["TYPE"]
@@ -1039,7 +1046,7 @@ class JsonRpcAioHttpClient(LogContextMixin):
1039
1046
 
1040
1047
  return parameter_data
1041
1048
 
1042
- async def get_all_device_data(self, interface: Interface) -> Mapping[str, Any]:
1049
+ async def get_all_device_data(self, *, interface: Interface) -> Mapping[str, Any]:
1043
1050
  """Get the all device data of the backend."""
1044
1051
  all_device_data: dict[str, Any] = {}
1045
1052
  params = {
@@ -1064,7 +1071,7 @@ class JsonRpcAioHttpClient(LogContextMixin):
1064
1071
 
1065
1072
  return all_device_data
1066
1073
 
1067
- async def get_all_programs(self, markers: tuple[DescriptionMarker | str, ...]) -> tuple[ProgramData, ...]:
1074
+ async def get_all_programs(self, *, markers: tuple[DescriptionMarker | str, ...]) -> tuple[ProgramData, ...]:
1068
1075
  """Get the all programs of the backend."""
1069
1076
  all_programs: list[ProgramData] = []
1070
1077
 
@@ -1118,7 +1125,7 @@ class JsonRpcAioHttpClient(LogContextMixin):
1118
1125
 
1119
1126
  return tuple(all_programs)
1120
1127
 
1121
- async def is_present(self, interface: Interface) -> bool:
1128
+ async def is_present(self, *, interface: Interface) -> bool:
1122
1129
  """Get value from the backend."""
1123
1130
  value: bool = False
1124
1131
  params = {_JsonKey.INTERFACE: interface}
@@ -1131,7 +1138,7 @@ class JsonRpcAioHttpClient(LogContextMixin):
1131
1138
 
1132
1139
  return value
1133
1140
 
1134
- async def has_program_ids(self, channel_hmid: str) -> bool:
1141
+ async def has_program_ids(self, *, channel_hmid: str) -> bool:
1135
1142
  """Return if a channel has program ids."""
1136
1143
  params = {_JsonKey.ID: channel_hmid}
1137
1144
  response = await self._post(
@@ -1206,7 +1213,7 @@ class JsonRpcAioHttpClient(LogContextMixin):
1206
1213
 
1207
1214
  return True
1208
1215
 
1209
- async def list_devices(self, interface: Interface) -> tuple[DeviceDescription, ...]:
1216
+ async def list_devices(self, *, interface: Interface) -> tuple[DeviceDescription, ...]:
1210
1217
  """List devices from the backend."""
1211
1218
  devices: tuple[DeviceDescription, ...] = ()
1212
1219
  _LOGGER.debug("LIST_DEVICES: Getting all available interfaces")
@@ -1262,6 +1269,7 @@ class JsonRpcAioHttpClient(LogContextMixin):
1262
1269
 
1263
1270
 
1264
1271
  def _get_params(
1272
+ *,
1265
1273
  session_id: bool | str,
1266
1274
  extra_params: dict[_JsonKey, Any] | None,
1267
1275
  use_default_params: bool,
@@ -88,11 +88,14 @@ class XmlRpcProxy(xmlrpc.client.ServerProxy):
88
88
 
89
89
  def __init__(
90
90
  self,
91
+ *,
91
92
  max_workers: int,
92
93
  interface_id: str,
93
94
  connection_state: hmcu.CentralConnectionState,
94
- *args: Any,
95
- **kwargs: Any,
95
+ uri: str,
96
+ headers: list[tuple[str, str]],
97
+ tls: bool = False,
98
+ verify_tls: bool = False,
96
99
  ) -> None:
97
100
  """Initialize new proxy for server and get local ip."""
98
101
  self._interface_id: Final = interface_id
@@ -101,17 +104,19 @@ class XmlRpcProxy(xmlrpc.client.ServerProxy):
101
104
  self._proxy_executor: Final = (
102
105
  ThreadPoolExecutor(max_workers=max_workers, thread_name_prefix=interface_id) if max_workers > 0 else None
103
106
  )
104
- self._tls: Final[bool] = kwargs.pop(_TLS, False)
105
- self._verify_tls: Final[bool] = kwargs.pop(_VERIFY_TLS, True)
107
+ self._tls: Final[bool] = tls
108
+ self._verify_tls: Final[bool] = verify_tls
106
109
  self._supported_methods: tuple[str, ...] = ()
110
+ kwargs: dict[str, Any] = {}
107
111
  if self._tls:
108
- kwargs[_CONTEXT] = get_tls_context(self._verify_tls)
112
+ kwargs[_CONTEXT] = get_tls_context(verify_tls=self._verify_tls)
109
113
  # Due to magic method the log_context must be defined manually.
110
114
  self.log_context: Final[Mapping[str, Any]] = {"interface_id": self._interface_id, "tls": self._tls}
111
- xmlrpc.client.ServerProxy.__init__( # type: ignore[misc]
115
+ xmlrpc.client.ServerProxy.__init__(
112
116
  self,
117
+ uri=uri,
113
118
  encoding=ISO_8859_1,
114
- *args, # noqa: B026
119
+ headers=headers,
115
120
  **kwargs,
116
121
  )
117
122
 
@@ -234,7 +239,7 @@ def _cleanup_args(*args: Any) -> Any:
234
239
  return args
235
240
 
236
241
 
237
- def _cleanup_item(item: Any) -> Any:
242
+ def _cleanup_item(*, item: Any) -> Any:
238
243
  """Cleanup a single item."""
239
244
  if isinstance(item, StrEnum):
240
245
  return str(item)
@@ -245,7 +250,7 @@ def _cleanup_item(item: Any) -> Any:
245
250
  return item
246
251
 
247
252
 
248
- def _cleanup_paramset(paramset: Mapping[str, Any]) -> Mapping[str, Any]:
253
+ def _cleanup_paramset(*, paramset: Mapping[str, Any]) -> Mapping[str, Any]:
249
254
  """Cleanup a paramset."""
250
255
  new_paramset: dict[str, Any] = {}
251
256
  for name, value in paramset.items():
aiohomematic/const.py CHANGED
@@ -19,7 +19,7 @@ import sys
19
19
  from types import MappingProxyType
20
20
  from typing import Any, Final, NamedTuple, Required, TypeAlias, TypedDict
21
21
 
22
- VERSION: Final = "2025.10.0"
22
+ VERSION: Final = "2025.10.2"
23
23
 
24
24
  # Detect test speedup mode via environment
25
25
  _TEST_SPEEDUP: Final = (
@@ -45,6 +45,7 @@ DEFAULT_SYSVAR_MARKERS: Final[tuple[DescriptionMarker | str, ...]] = ()
45
45
  DEFAULT_SYS_SCAN_INTERVAL: Final = 30
46
46
  DEFAULT_TLS: Final = False
47
47
  DEFAULT_UN_IGNORES: Final[frozenset[str]] = frozenset()
48
+ DEFAULT_USE_GROUP_CHANNEL_FOR_COVER_STATE: Final = True
48
49
  DEFAULT_VERIFY_TLS: Final = False
49
50
 
50
51
  # Default encoding for json service calls, persistent cache
@@ -99,6 +100,7 @@ IDENTIFIER_SEPARATOR: Final = "@"
99
100
  INIT_DATETIME: Final = datetime.strptime("01.01.1970 00:00:00", DATETIME_FORMAT)
100
101
  IP_ANY_V4: Final = "0.0.0.0"
101
102
  JSON_SESSION_AGE: Final = 90
103
+ KWARGS_ARG_CUSTOM_ID: Final = "custom_id"
102
104
  KWARGS_ARG_DATA_POINT: Final = "data_point"
103
105
  LAST_COMMAND_SEND_STORE_TIMEOUT: Final = 60
104
106
  LOCAL_HOST: Final = "127.0.0.1"
aiohomematic/converter.py CHANGED
@@ -21,23 +21,23 @@ _LOGGER = logging.getLogger(__name__)
21
21
 
22
22
 
23
23
  @lru_cache(maxsize=1024)
24
- def _convert_cpv_to_hm_level(cpv: Any) -> Any:
24
+ def _convert_cpv_to_hm_level(*, value: Any) -> Any:
25
25
  """Convert combined parameter value for hm level."""
26
- if isinstance(cpv, str) and cpv.startswith("0x"):
27
- return ast.literal_eval(cpv) / 100 / 2
28
- return cpv
26
+ if isinstance(value, str) and value.startswith("0x"):
27
+ return ast.literal_eval(value) / 100 / 2
28
+ return value
29
29
 
30
30
 
31
31
  @lru_cache(maxsize=1024)
32
- def _convert_cpv_to_hmip_level(cpv: Any) -> Any:
32
+ def _convert_cpv_to_hmip_level(*, value: Any) -> Any:
33
33
  """Convert combined parameter value for hmip level."""
34
- return int(cpv) / 100
34
+ return int(value) / 100
35
35
 
36
36
 
37
37
  @lru_cache(maxsize=1024)
38
- def convert_hm_level_to_cpv(hm_level: Any) -> Any:
38
+ def convert_hm_level_to_cpv(*, value: Any) -> Any:
39
39
  """Convert hm level to combined parameter value."""
40
- return format(int(hm_level * 100 * 2), "#04x")
40
+ return format(int(value * 100 * 2), "#04x")
41
41
 
42
42
 
43
43
  CONVERTABLE_PARAMETERS: Final = (Parameter.COMBINED_PARAMETER, Parameter.LEVEL_COMBINED)
@@ -52,28 +52,28 @@ _COMBINED_PARAMETER_NAMES: Final = {"L": Parameter.LEVEL, "L2": Parameter.LEVEL_
52
52
 
53
53
 
54
54
  @lru_cache(maxsize=1024)
55
- def _convert_combined_parameter_to_paramset(cpv: str) -> dict[str, Any]:
55
+ def _convert_combined_parameter_to_paramset(*, value: str) -> dict[str, Any]:
56
56
  """Convert combined parameter to paramset."""
57
57
  paramset: dict[str, Any] = {}
58
- for cp_param_value in cpv.split(","):
58
+ for cp_param_value in value.split(","):
59
59
  cp_param, value = cp_param_value.split("=")
60
60
  if parameter := _COMBINED_PARAMETER_NAMES.get(cp_param):
61
61
  if converter := _COMBINED_PARAMETER_TO_HM_CONVERTER.get(parameter):
62
- paramset[parameter] = converter(value)
62
+ paramset[parameter] = converter(value=value)
63
63
  else:
64
64
  paramset[parameter] = value
65
65
  return paramset
66
66
 
67
67
 
68
68
  @lru_cache(maxsize=1024)
69
- def _convert_level_combined_to_paramset(lcv: str) -> dict[str, Any]:
69
+ def _convert_level_combined_to_paramset(*, value: str) -> dict[str, Any]:
70
70
  """Convert combined parameter to paramset."""
71
- if "," in lcv:
72
- l1_value, l2_value = lcv.split(",")
71
+ if "," in value:
72
+ l1_value, l2_value = value.split(",")
73
73
  if converter := _COMBINED_PARAMETER_TO_HM_CONVERTER.get(Parameter.LEVEL_COMBINED):
74
74
  return {
75
- Parameter.LEVEL: converter(l1_value),
76
- Parameter.LEVEL_SLATS: converter(l2_value),
75
+ Parameter.LEVEL: converter(value=l1_value),
76
+ Parameter.LEVEL_SLATS: converter(value=l2_value),
77
77
  }
78
78
  return {}
79
79
 
@@ -85,12 +85,12 @@ _COMBINED_PARAMETER_TO_PARAMSET_CONVERTER: Final = {
85
85
 
86
86
 
87
87
  @lru_cache(maxsize=1024)
88
- def convert_combined_parameter_to_paramset(parameter: str, cpv: str) -> dict[str, Any]:
88
+ def convert_combined_parameter_to_paramset(*, parameter: str, value: str) -> dict[str, Any]:
89
89
  """Convert combined parameter to paramset."""
90
90
  try:
91
91
  if converter := _COMBINED_PARAMETER_TO_PARAMSET_CONVERTER.get(parameter): # type: ignore[call-overload]
92
- return cast(dict[str, Any], converter(cpv))
93
- _LOGGER.debug("CONVERT_COMBINED_PARAMETER_TO_PARAMSET: No converter found for %s: %s", parameter, cpv)
92
+ return cast(dict[str, Any], converter(value=value))
93
+ _LOGGER.debug("CONVERT_COMBINED_PARAMETER_TO_PARAMSET: No converter found for %s: %s", parameter, value)
94
94
  except Exception as exc:
95
95
  _LOGGER.debug("CONVERT_COMBINED_PARAMETER_TO_PARAMSET: Convert failed %s", extract_exc_args(exc=exc))
96
96
  return {}
@@ -102,12 +102,13 @@ class InternalBackendException(BaseHomematicException):
102
102
  super().__init__("InternalBackendException", *args)
103
103
 
104
104
 
105
- def _reduce_args(args: tuple[Any, ...]) -> tuple[Any, ...] | Any:
105
+ def _reduce_args(*, args: tuple[Any, ...]) -> tuple[Any, ...] | Any:
106
106
  """Return the first arg, if there is only one arg."""
107
107
  return args[0] if len(args) == 1 else args
108
108
 
109
109
 
110
110
  def log_exception[**P, R](
111
+ *,
111
112
  exc_type: type[BaseException],
112
113
  logger: logging.Logger = _LOGGER,
113
114
  level: int = logging.ERROR,
@@ -46,7 +46,7 @@ _LOGGER: Final = logging.getLogger(__name__)
46
46
 
47
47
 
48
48
  @inspector
49
- def create_data_points_and_events(device: hmd.Device) -> None:
49
+ def create_data_points_and_events(*, device: hmd.Device) -> None:
50
50
  """Create the data points associated to this device."""
51
51
  for channel in device.channels.values():
52
52
  for paramset_key, paramsset_key_descriptions in channel.paramset_descriptions.items():
@@ -83,6 +83,7 @@ def create_data_points_and_events(device: hmd.Device) -> None:
83
83
 
84
84
 
85
85
  def _process_parameter(
86
+ *,
86
87
  channel: hmd.Channel,
87
88
  paramset_key: ParamsetKey,
88
89
  parameter: str,
@@ -119,7 +120,7 @@ def _process_parameter(
119
120
  )
120
121
 
121
122
 
122
- def _should_create_event(parameter_data: ParameterData, parameter: str) -> bool:
123
+ def _should_create_event(*, parameter_data: ParameterData, parameter: str) -> bool:
123
124
  """Determine if an event should be created for the parameter."""
124
125
  return bool(
125
126
  parameter_data["OPERATIONS"] & Operations.EVENT
@@ -127,7 +128,7 @@ def _should_create_event(parameter_data: ParameterData, parameter: str) -> bool:
127
128
  )
128
129
 
129
130
 
130
- def _should_skip_data_point(parameter_data: ParameterData, parameter: str, parameter_is_un_ignored: bool) -> bool:
131
+ def _should_skip_data_point(*, parameter_data: ParameterData, parameter: str, parameter_is_un_ignored: bool) -> bool:
131
132
  """Determine if a data point should be skipped."""
132
133
  return bool(
133
134
  (not parameter_data["OPERATIONS"] & Operations.EVENT and not parameter_data["OPERATIONS"] & Operations.WRITE)
@@ -60,7 +60,7 @@ _LOGGER: Final = logging.getLogger(__name__)
60
60
 
61
61
 
62
62
  @inspector
63
- def create_calculated_data_points(channel: hmd.Channel) -> None:
63
+ def create_calculated_data_points(*, channel: hmd.Channel) -> None:
64
64
  """Decides which data point category should be used, and creates the required data points."""
65
65
  for dp in _CALCULATED_DATA_POINTS:
66
66
  if dp.is_relevant_for_model(channel=channel):
@@ -34,7 +34,7 @@ class BaseClimateSensor[SensorT: float | None](CalculatedDataPoint[SensorT]):
34
34
 
35
35
  _category = DataPointCategory.SENSOR
36
36
 
37
- def __init__(self, channel: hmd.Channel) -> None:
37
+ def __init__(self, *, channel: hmd.Channel) -> None:
38
38
  """Initialize the data point."""
39
39
 
40
40
  super().__init__(channel=channel)
@@ -70,7 +70,7 @@ class ApparentTemperature(BaseClimateSensor):
70
70
 
71
71
  _calculated_parameter = CalulatedParameter.APPARENT_TEMPERATURE
72
72
 
73
- def __init__(self, channel: hmd.Channel) -> None:
73
+ def __init__(self, *, channel: hmd.Channel) -> None:
74
74
  """Initialize the data point."""
75
75
  super().__init__(channel=channel)
76
76
  self._unit = "°C"
@@ -83,7 +83,7 @@ class ApparentTemperature(BaseClimateSensor):
83
83
  )
84
84
 
85
85
  @staticmethod
86
- def is_relevant_for_model(channel: hmd.Channel) -> bool:
86
+ def is_relevant_for_model(*, channel: hmd.Channel) -> bool:
87
87
  """Return if this calculated data point is relevant for the model."""
88
88
  return (
89
89
  element_matches_key(
@@ -120,13 +120,13 @@ class DewPoint(BaseClimateSensor):
120
120
 
121
121
  _calculated_parameter = CalulatedParameter.DEW_POINT
122
122
 
123
- def __init__(self, channel: hmd.Channel) -> None:
123
+ def __init__(self, *, channel: hmd.Channel) -> None:
124
124
  """Initialize the data point."""
125
125
  super().__init__(channel=channel)
126
126
  self._unit = "°C"
127
127
 
128
128
  @staticmethod
129
- def is_relevant_for_model(channel: hmd.Channel) -> bool:
129
+ def is_relevant_for_model(*, channel: hmd.Channel) -> bool:
130
130
  """Return if this calculated data point is relevant for the model."""
131
131
  return _is_relevant_for_model_temperature_and_humidity(channel=channel)
132
132
 
@@ -148,13 +148,13 @@ class FrostPoint(BaseClimateSensor):
148
148
 
149
149
  _calculated_parameter = CalulatedParameter.FROST_POINT
150
150
 
151
- def __init__(self, channel: hmd.Channel) -> None:
151
+ def __init__(self, *, channel: hmd.Channel) -> None:
152
152
  """Initialize the data point."""
153
153
  super().__init__(channel=channel)
154
154
  self._unit = "°C"
155
155
 
156
156
  @staticmethod
157
- def is_relevant_for_model(channel: hmd.Channel) -> bool:
157
+ def is_relevant_for_model(*, channel: hmd.Channel) -> bool:
158
158
  """Return if this calculated data point is relevant for the model."""
159
159
  return _is_relevant_for_model_temperature_and_humidity(
160
160
  channel=channel, relevant_models=_RELEVANT_MODELS_FROST_POINT
@@ -178,13 +178,13 @@ class VaporConcentration(BaseClimateSensor):
178
178
 
179
179
  _calculated_parameter = CalulatedParameter.VAPOR_CONCENTRATION
180
180
 
181
- def __init__(self, channel: hmd.Channel) -> None:
181
+ def __init__(self, *, channel: hmd.Channel) -> None:
182
182
  """Initialize the data point."""
183
183
  super().__init__(channel=channel)
184
184
  self._unit = "g/m³"
185
185
 
186
186
  @staticmethod
187
- def is_relevant_for_model(channel: hmd.Channel) -> bool:
187
+ def is_relevant_for_model(*, channel: hmd.Channel) -> bool:
188
188
  """Return if this calculated data point is relevant for the model."""
189
189
  return _is_relevant_for_model_temperature_and_humidity(channel=channel)
190
190
 
@@ -59,6 +59,7 @@ class CalculatedDataPoint[ParameterT: GenericParameterType](BaseDataPoint):
59
59
 
60
60
  def __init__(
61
61
  self,
62
+ *,
62
63
  channel: hmd.Channel,
63
64
  ) -> None:
64
65
  """Initialize the data point."""
@@ -92,7 +93,7 @@ class CalculatedDataPoint[ParameterT: GenericParameterType](BaseDataPoint):
92
93
  )
93
94
 
94
95
  def _add_data_point[DataPointT: hmge.GenericDataPoint](
95
- self, parameter: str, paramset_key: ParamsetKey | None, data_point_type: type[DataPointT]
96
+ self, *, parameter: str, paramset_key: ParamsetKey | None, data_point_type: type[DataPointT]
96
97
  ) -> DataPointT:
97
98
  """Add a new data point."""
98
99
  if generic_data_point := self._channel.get_generic_data_point(parameter=parameter, paramset_key=paramset_key):
@@ -109,7 +110,12 @@ class CalculatedDataPoint[ParameterT: GenericParameterType](BaseDataPoint):
109
110
  )
110
111
 
111
112
  def _add_device_data_point[DataPointT: hmge.GenericDataPoint](
112
- self, channel_address: str, parameter: str, paramset_key: ParamsetKey | None, data_point_type: type[DataPointT]
113
+ self,
114
+ *,
115
+ channel_address: str,
116
+ parameter: str,
117
+ paramset_key: ParamsetKey | None,
118
+ data_point_type: type[DataPointT],
113
119
  ) -> DataPointT:
114
120
  """Add a new data point."""
115
121
  if generic_data_point := self._channel.device.get_generic_data_point(
@@ -133,7 +139,7 @@ class CalculatedDataPoint[ParameterT: GenericParameterType](BaseDataPoint):
133
139
  return bool(self._operations & Operations.READ)
134
140
 
135
141
  @staticmethod
136
- def is_relevant_for_model(channel: hmd.Channel) -> bool:
142
+ def is_relevant_for_model(*, channel: hmd.Channel) -> bool:
137
143
  """Return if this calculated data point is relevant for the channel."""
138
144
  return False
139
145
 
@@ -286,7 +292,7 @@ class CalculatedDataPoint[ParameterT: GenericParameterType](BaseDataPoint):
286
292
  """Return the signature of the data_point."""
287
293
  return f"{self._category}/{self._channel.device.model}/{self._calculated_parameter}"
288
294
 
289
- async def load_data_point_value(self, call_source: CallSource, direct_call: bool = False) -> None:
295
+ async def load_data_point_value(self, *, call_source: CallSource, direct_call: bool = False) -> None:
290
296
  """Init the data point values."""
291
297
  for dp in self._readable_data_points:
292
298
  await dp.load_data_point_value(call_source=call_source, direct_call=direct_call)
@@ -306,7 +312,7 @@ class CalculatedDataPoint[ParameterT: GenericParameterType](BaseDataPoint):
306
312
  @property
307
313
  def _should_fire_data_point_updated_callback(self) -> bool:
308
314
  """Check if a data point has been updated or refreshed."""
309
- if self.fired_recently: # pylint: disable=using-constant-test
315
+ if self.fired_event_recently: # pylint: disable=using-constant-test
310
316
  return False
311
317
 
312
318
  if (relevant_values_data_point := self._relevant_values_data_points) is not None and len(
@@ -314,9 +320,9 @@ class CalculatedDataPoint[ParameterT: GenericParameterType](BaseDataPoint):
314
320
  ) <= 1:
315
321
  return True
316
322
 
317
- return all(dp.fired_recently for dp in relevant_values_data_point)
323
+ return all(dp.fired_event_recently for dp in relevant_values_data_point)
318
324
 
319
- def _unregister_data_point_updated_callback(self, cb: Callable, custom_id: str) -> None:
325
+ def _unregister_data_point_updated_callback(self, *, cb: Callable, custom_id: str) -> None:
320
326
  """Unregister update callback."""
321
327
  for unregister in self._unregister_callbacks:
322
328
  if unregister is not None:
@@ -41,7 +41,7 @@ class OperatingVoltageLevel[SensorT: float | None](CalculatedDataPoint[SensorT])
41
41
  _calculated_parameter = CalulatedParameter.OPERATING_VOLTAGE_LEVEL
42
42
  _category = DataPointCategory.SENSOR
43
43
 
44
- def __init__(self, channel: hmd.Channel) -> None:
44
+ def __init__(self, *, channel: hmd.Channel) -> None:
45
45
  """Initialize the data point."""
46
46
  super().__init__(channel=channel)
47
47
  self._type = ParameterType.FLOAT
@@ -89,7 +89,7 @@ class OperatingVoltageLevel[SensorT: float | None](CalculatedDataPoint[SensorT])
89
89
  )
90
90
 
91
91
  @staticmethod
92
- def is_relevant_for_model(channel: hmd.Channel) -> bool:
92
+ def is_relevant_for_model(*, channel: hmd.Channel) -> bool:
93
93
  """Return if this calculated data point is relevant for the model."""
94
94
  if element_matches_key(
95
95
  search_elements=_IGNORE_OPERATING_VOLTAGE_LEVEL_MODELS, compare_with=channel.device.model