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
@@ -28,7 +28,7 @@ class Looper:
28
28
  self._tasks: Final[set[asyncio.Future[Any]]] = set()
29
29
  self._loop = asyncio.get_event_loop()
30
30
 
31
- async def block_till_done(self, wait_time: float | None = None) -> None:
31
+ async def block_till_done(self, *, wait_time: float | None = None) -> None:
32
32
  """
33
33
  Block until all pending work is done.
34
34
 
@@ -39,14 +39,14 @@ class Looper:
39
39
  start_time: float | None = None
40
40
  deadline: float | None = (monotonic() + wait_time) if wait_time is not None else None
41
41
  current_task = asyncio.current_task()
42
- while tasks := [task for task in self._tasks if task is not current_task and not cancelling(task)]:
42
+ while tasks := [task for task in self._tasks if task is not current_task and not cancelling(task=task)]:
43
43
  # If we have a deadline and have exceeded it, log remaining tasks and break
44
44
  if deadline is not None and monotonic() >= deadline:
45
45
  for task in tasks:
46
46
  _LOGGER.warning("Shutdown timeout reached; task still pending: %s", task)
47
47
  break
48
48
 
49
- await self._await_and_log_pending(tasks)
49
+ await self._await_and_log_pending(pending=tasks)
50
50
 
51
51
  if start_time is None:
52
52
  # Avoid calling monotonic() until we know
@@ -63,7 +63,7 @@ class Looper:
63
63
  for task in tasks:
64
64
  _LOGGER.debug("Waiting for task: %s", task)
65
65
 
66
- async def _await_and_log_pending(self, pending: Collection[asyncio.Future[Any]]) -> None:
66
+ async def _await_and_log_pending(self, *, pending: Collection[asyncio.Future[Any]]) -> None:
67
67
  """Await and log tasks that take a long time."""
68
68
  wait_time = 0
69
69
  while pending:
@@ -74,7 +74,7 @@ class Looper:
74
74
  for task in pending:
75
75
  _LOGGER.debug("Waited %s seconds for task: %s", wait_time, task)
76
76
 
77
- def create_task(self, target: Coroutine[Any, Any, Any], name: str) -> None:
77
+ def create_task(self, *, target: Coroutine[Any, Any, Any], name: str) -> None:
78
78
  """Add task to the executor pool."""
79
79
  try:
80
80
  self._loop.call_soon_threadsafe(self._async_create_task, target, name)
@@ -92,7 +92,7 @@ class Looper:
92
92
  task.add_done_callback(self._tasks.remove)
93
93
  return task
94
94
 
95
- def run_coroutine(self, coro: Coroutine, name: str) -> Any:
95
+ def run_coroutine(self, *, coro: Coroutine, name: str) -> Any:
96
96
  """Call coroutine from sync."""
97
97
  try:
98
98
  return asyncio.run_coroutine_threadsafe(coro, self._loop).result()
@@ -128,7 +128,7 @@ class Looper:
128
128
  task.cancel()
129
129
 
130
130
 
131
- def cancelling(task: asyncio.Future[Any]) -> bool:
131
+ def cancelling(*, task: asyncio.Future[Any]) -> bool:
132
132
  """Return True if task is cancelling."""
133
133
  return bool((cancelling_ := getattr(task, "cancelling", None)) and cancelling_())
134
134
 
@@ -60,7 +60,7 @@ class CommandCache:
60
60
  "_last_send_command",
61
61
  )
62
62
 
63
- def __init__(self, interface_id: str) -> None:
63
+ def __init__(self, *, interface_id: str) -> None:
64
64
  """Init command cache."""
65
65
  self._interface_id: Final = interface_id
66
66
  # (paramset_key, device_address, channel_no, parameter)
@@ -68,6 +68,7 @@ class CommandCache:
68
68
 
69
69
  def add_set_value(
70
70
  self,
71
+ *,
71
72
  channel_address: str,
72
73
  parameter: str,
73
74
  value: Any,
@@ -89,7 +90,7 @@ class CommandCache:
89
90
  return {(dpk, value)}
90
91
 
91
92
  def add_put_paramset(
92
- self, channel_address: str, paramset_key: ParamsetKey, values: dict[str, Any]
93
+ self, *, channel_address: str, paramset_key: ParamsetKey, values: dict[str, Any]
93
94
  ) -> set[DP_KEY_VALUE]:
94
95
  """Add data from put paramset command."""
95
96
  dpk_values: set[DP_KEY_VALUE] = set()
@@ -106,10 +107,10 @@ class CommandCache:
106
107
  return dpk_values
107
108
 
108
109
  def add_combined_parameter(
109
- self, parameter: str, channel_address: str, combined_parameter: str
110
+ self, *, parameter: str, channel_address: str, combined_parameter: str
110
111
  ) -> set[DP_KEY_VALUE]:
111
112
  """Add data from combined parameter."""
112
- if values := convert_combined_parameter_to_paramset(parameter=parameter, cpv=combined_parameter):
113
+ if values := convert_combined_parameter_to_paramset(parameter=parameter, value=combined_parameter):
113
114
  return self.add_put_paramset(
114
115
  channel_address=channel_address,
115
116
  paramset_key=ParamsetKey.VALUES,
@@ -117,7 +118,7 @@ class CommandCache:
117
118
  )
118
119
  return set()
119
120
 
120
- def get_last_value_send(self, dpk: DataPointKey, max_age: int = LAST_COMMAND_SEND_STORE_TIMEOUT) -> Any:
121
+ def get_last_value_send(self, *, dpk: DataPointKey, max_age: int = LAST_COMMAND_SEND_STORE_TIMEOUT) -> Any:
121
122
  """Return the last send values."""
122
123
  if result := self._last_send_command.get(dpk):
123
124
  value, last_send_dt = result
@@ -131,6 +132,7 @@ class CommandCache:
131
132
 
132
133
  def remove_last_value_send(
133
134
  self,
135
+ *,
134
136
  dpk: DataPointKey,
135
137
  value: Any = None,
136
138
  max_age: int = LAST_COMMAND_SEND_STORE_TIMEOUT,
@@ -158,7 +160,7 @@ class DeviceDetailsCache:
158
160
  "_refreshed_at",
159
161
  )
160
162
 
161
- def __init__(self, central: hmcu.CentralUnit) -> None:
163
+ def __init__(self, *, central: hmcu.CentralUnit) -> None:
162
164
  """Init the device details cache."""
163
165
  self._central: Final = central
164
166
  self._channel_rooms: Final[dict[str, set[str]]] = defaultdict(set)
@@ -169,7 +171,7 @@ class DeviceDetailsCache:
169
171
  self._names_cache: Final[dict[str, str]] = {}
170
172
  self._refreshed_at = INIT_DATETIME
171
173
 
172
- async def load(self, direct_call: bool = False) -> None:
174
+ async def load(self, *, direct_call: bool = False) -> None:
173
175
  """Fetch names from the backend."""
174
176
  if direct_call is False and changed_within_seconds(
175
177
  last_change=self._refreshed_at, max_age=int(MAX_CACHE_AGE / 3)
@@ -194,27 +196,27 @@ class DeviceDetailsCache:
194
196
  """Return device channel ids."""
195
197
  return self._device_channel_ids
196
198
 
197
- def add_name(self, address: str, name: str) -> None:
199
+ def add_name(self, *, address: str, name: str) -> None:
198
200
  """Add name to cache."""
199
201
  self._names_cache[address] = name
200
202
 
201
- def get_name(self, address: str) -> str | None:
203
+ def get_name(self, *, address: str) -> str | None:
202
204
  """Get name from cache."""
203
205
  return self._names_cache.get(address)
204
206
 
205
- def add_interface(self, address: str, interface: Interface) -> None:
207
+ def add_interface(self, *, address: str, interface: Interface) -> None:
206
208
  """Add interface to cache."""
207
209
  self._interface_cache[address] = interface
208
210
 
209
- def get_interface(self, address: str) -> Interface:
211
+ def get_interface(self, *, address: str) -> Interface:
210
212
  """Get interface from cache."""
211
213
  return self._interface_cache.get(address) or Interface.BIDCOS_RF
212
214
 
213
- def add_address_id(self, address: str, hmid: str) -> None:
215
+ def add_address_id(self, *, address: str, hmid: str) -> None:
214
216
  """Add channel id for a channel."""
215
217
  self._device_channel_ids[address] = hmid
216
218
 
217
- def get_address_id(self, address: str) -> str:
219
+ def get_address_id(self, *, address: str) -> str:
218
220
  """Get id for address."""
219
221
  return self._device_channel_ids.get(address) or "0"
220
222
 
@@ -232,11 +234,11 @@ class DeviceDetailsCache:
232
234
  _device_rooms[get_device_address(address=channel_address)].update(rooms)
233
235
  return _device_rooms
234
236
 
235
- def get_device_rooms(self, device_address: str) -> set[str]:
237
+ def get_device_rooms(self, *, device_address: str) -> set[str]:
236
238
  """Return all rooms by device_address."""
237
239
  return set(self._device_rooms.get(device_address, ()))
238
240
 
239
- def get_channel_rooms(self, channel_address: str) -> set[str]:
241
+ def get_channel_rooms(self, *, channel_address: str) -> set[str]:
240
242
  """Return rooms by channel_address."""
241
243
  return self._channel_rooms[channel_address]
242
244
 
@@ -246,13 +248,13 @@ class DeviceDetailsCache:
246
248
  return await client.get_all_functions()
247
249
  return {}
248
250
 
249
- def get_function_text(self, address: str) -> str | None:
251
+ def get_function_text(self, *, address: str) -> str | None:
250
252
  """Return function by address."""
251
253
  if functions := self._functions.get(address):
252
254
  return ",".join(functions)
253
255
  return None
254
256
 
255
- def remove_device(self, device: Device) -> None:
257
+ def remove_device(self, *, device: Device) -> None:
256
258
  """Remove name from cache."""
257
259
  if device.address in self._names_cache:
258
260
  del self._names_cache[device.address]
@@ -278,14 +280,14 @@ class CentralDataCache:
278
280
  "_value_cache",
279
281
  )
280
282
 
281
- def __init__(self, central: hmcu.CentralUnit) -> None:
283
+ def __init__(self, *, central: hmcu.CentralUnit) -> None:
282
284
  """Init the central data cache."""
283
285
  self._central: Final = central
284
286
  # { key, value}
285
287
  self._value_cache: Final[dict[Interface, Mapping[str, Any]]] = {}
286
288
  self._refreshed_at: Final[dict[Interface, datetime]] = {}
287
289
 
288
- async def load(self, direct_call: bool = False, interface: Interface | None = None) -> None:
290
+ async def load(self, *, direct_call: bool = False, interface: Interface | None = None) -> None:
289
291
  """Fetch data from the backend."""
290
292
  _LOGGER.debug("load: Loading device data for %s", self._central.name)
291
293
  for client in self._central.clients:
@@ -300,6 +302,7 @@ class CentralDataCache:
300
302
 
301
303
  async def refresh_data_point_data(
302
304
  self,
305
+ *,
303
306
  paramset_key: ParamsetKey | None = None,
304
307
  interface: Interface | None = None,
305
308
  direct_call: bool = False,
@@ -308,13 +311,14 @@ class CentralDataCache:
308
311
  for dp in self._central.get_readable_generic_data_points(paramset_key=paramset_key, interface=interface):
309
312
  await dp.load_data_point_value(call_source=CallSource.HM_INIT, direct_call=direct_call)
310
313
 
311
- def add_data(self, interface: Interface, all_device_data: Mapping[str, Any]) -> None:
314
+ def add_data(self, *, interface: Interface, all_device_data: Mapping[str, Any]) -> None:
312
315
  """Add data to cache."""
313
316
  self._value_cache[interface] = all_device_data
314
317
  self._refreshed_at[interface] = datetime.now()
315
318
 
316
319
  def get_data(
317
320
  self,
321
+ *,
318
322
  interface: Interface,
319
323
  channel_address: str,
320
324
  parameter: str,
@@ -324,7 +328,7 @@ class CentralDataCache:
324
328
  return iface_cache.get(f"{interface}.{channel_address}.{parameter}", NO_CACHE_ENTRY)
325
329
  return NO_CACHE_ENTRY
326
330
 
327
- def clear(self, interface: Interface | None = None) -> None:
331
+ def clear(self, *, interface: Interface | None = None) -> None:
328
332
  """Clear the cache."""
329
333
  if interface:
330
334
  self._value_cache[interface] = {}
@@ -333,11 +337,11 @@ class CentralDataCache:
333
337
  for _interface in self._central.interfaces:
334
338
  self.clear(interface=_interface)
335
339
 
336
- def _get_refreshed_at(self, interface: Interface) -> datetime:
340
+ def _get_refreshed_at(self, *, interface: Interface) -> datetime:
337
341
  """Return when cache has been refreshed."""
338
342
  return self._refreshed_at.get(interface, INIT_DATETIME)
339
343
 
340
- def _is_empty(self, interface: Interface) -> bool:
344
+ def _is_empty(self, *, interface: Interface) -> bool:
341
345
  """Return if cache is empty for the given interface."""
342
346
  # If there is no data stored for the requested interface, treat as empty.
343
347
  if not self._value_cache.get(interface):
@@ -365,6 +369,7 @@ class PingPongCache:
365
369
 
366
370
  def __init__(
367
371
  self,
372
+ *,
368
373
  central: hmcu.CentralUnit,
369
374
  interface_id: str,
370
375
  allowed_delta: int = PING_PONG_MISMATCH_COUNT,
@@ -422,7 +427,7 @@ class PingPongCache:
422
427
  self._pending_pong_logged = False
423
428
  self._unknown_pong_logged = False
424
429
 
425
- def handle_send_ping(self, ping_ts: datetime) -> None:
430
+ def handle_send_ping(self, *, ping_ts: datetime) -> None:
426
431
  """Handle send ping timestamp."""
427
432
  self._pending_pongs.add(ping_ts)
428
433
  self._check_and_fire_pong_event(
@@ -436,7 +441,7 @@ class PingPongCache:
436
441
  ping_ts,
437
442
  )
438
443
 
439
- def handle_received_pong(self, pong_ts: datetime) -> None:
444
+ def handle_received_pong(self, *, pong_ts: datetime) -> None:
440
445
  """Handle received pong timestamp."""
441
446
  if pong_ts in self._pending_pongs:
442
447
  self._pending_pongs.remove(pong_ts)
@@ -492,7 +497,7 @@ class PingPongCache:
492
497
  pong_ts,
493
498
  )
494
499
 
495
- def _check_and_fire_pong_event(self, event_type: InterfaceEventType, pong_mismatch_count: int) -> None:
500
+ def _check_and_fire_pong_event(self, *, event_type: InterfaceEventType, pong_mismatch_count: int) -> None:
496
501
  """Fire an event about the pong status."""
497
502
 
498
503
  def _fire_event(mismatch_count: int) -> None:
@@ -89,6 +89,7 @@ class BasePersistentCache(ABC):
89
89
 
90
90
  def __init__(
91
91
  self,
92
+ *,
92
93
  central: hmcu.CentralUnit,
93
94
  persistent_cache: dict[str, Any],
94
95
  ) -> None:
@@ -145,14 +146,14 @@ class BasePersistentCache(ABC):
145
146
  """Determine if save operation should proceed."""
146
147
  self.last_save_triggered = datetime.now()
147
148
  return (
148
- check_or_create_directory(self._cache_dir)
149
+ check_or_create_directory(directory=self._cache_dir)
149
150
  and self._central.config.use_caches
150
151
  and self.cache_hash != self.last_hash_saved
151
152
  )
152
153
 
153
154
  async def load(self) -> DataOperationResult:
154
155
  """Load data from disk into the dictionary."""
155
- if not check_or_create_directory(self._cache_dir) or not os.path.exists(self._file_path):
156
+ if not check_or_create_directory(directory=self._cache_dir) or not os.path.exists(self._file_path):
156
157
  return DataOperationResult.NO_LOAD
157
158
 
158
159
  def _perform_load() -> DataOperationResult:
@@ -195,7 +196,7 @@ class DeviceDescriptionCache(BasePersistentCache):
195
196
 
196
197
  _file_postfix = FILE_DEVICES
197
198
 
198
- def __init__(self, central: hmcu.CentralUnit) -> None:
199
+ def __init__(self, *, central: hmcu.CentralUnit) -> None:
199
200
  """Initialize the device description cache."""
200
201
  # {interface_id, [device_descriptions]}
201
202
  self._raw_device_descriptions: Final[dict[str, list[DeviceDescription]]] = defaultdict(list)
@@ -208,7 +209,7 @@ class DeviceDescriptionCache(BasePersistentCache):
208
209
  # {interface_id, {address, device_descriptions}}
209
210
  self._device_descriptions: Final[dict[str, dict[str, DeviceDescription]]] = defaultdict(dict)
210
211
 
211
- def add_device(self, interface_id: str, device_description: DeviceDescription) -> None:
212
+ def add_device(self, *, interface_id: str, device_description: DeviceDescription) -> None:
212
213
  """Add a device to the cache."""
213
214
  # Fast-path: If the address is not yet known, skip costly removal operations.
214
215
  if (address := device_description["ADDRESS"]) not in self._device_descriptions[interface_id]:
@@ -223,18 +224,18 @@ class DeviceDescriptionCache(BasePersistentCache):
223
224
  self._raw_device_descriptions[interface_id].append(device_description)
224
225
  self._process_device_description(interface_id=interface_id, device_description=device_description)
225
226
 
226
- def get_raw_device_descriptions(self, interface_id: str) -> list[DeviceDescription]:
227
+ def get_raw_device_descriptions(self, *, interface_id: str) -> list[DeviceDescription]:
227
228
  """Retrieve raw device descriptions from the cache."""
228
229
  return self._raw_device_descriptions[interface_id]
229
230
 
230
- def remove_device(self, device: Device) -> None:
231
+ def remove_device(self, *, device: Device) -> None:
231
232
  """Remove device from cache."""
232
233
  self._remove_device(
233
234
  interface_id=device.interface_id,
234
235
  addresses_to_remove=[device.address, *device.channels.keys()],
235
236
  )
236
237
 
237
- def _remove_device(self, interface_id: str, addresses_to_remove: list[str]) -> None:
238
+ def _remove_device(self, *, interface_id: str, addresses_to_remove: list[str]) -> None:
238
239
  """Remove a device from the cache."""
239
240
  # Use a set for faster membership checks
240
241
  addresses_set = set(addresses_to_remove)
@@ -249,23 +250,23 @@ class DeviceDescriptionCache(BasePersistentCache):
249
250
  addr_map.pop(address, None)
250
251
  desc_map.pop(address, None)
251
252
 
252
- def get_addresses(self, interface_id: str) -> frozenset[str]:
253
+ def get_addresses(self, *, interface_id: str) -> frozenset[str]:
253
254
  """Return the addresses by interface as a set."""
254
255
  return frozenset(self._addresses[interface_id])
255
256
 
256
- def get_device_descriptions(self, interface_id: str) -> Mapping[str, DeviceDescription]:
257
+ def get_device_descriptions(self, *, interface_id: str) -> Mapping[str, DeviceDescription]:
257
258
  """Return the devices by interface."""
258
259
  return self._device_descriptions[interface_id]
259
260
 
260
- def find_device_description(self, interface_id: str, device_address: str) -> DeviceDescription | None:
261
+ def find_device_description(self, *, interface_id: str, device_address: str) -> DeviceDescription | None:
261
262
  """Return the device description by interface and device_address."""
262
263
  return self._device_descriptions[interface_id].get(device_address)
263
264
 
264
- def get_device_description(self, interface_id: str, address: str) -> DeviceDescription:
265
+ def get_device_description(self, *, interface_id: str, address: str) -> DeviceDescription:
265
266
  """Return the device description by interface and device_address."""
266
267
  return self._device_descriptions[interface_id][address]
267
268
 
268
- def get_device_with_channels(self, interface_id: str, device_address: str) -> Mapping[str, DeviceDescription]:
269
+ def get_device_with_channels(self, *, interface_id: str, device_address: str) -> Mapping[str, DeviceDescription]:
269
270
  """Return the device dict by interface and device_address."""
270
271
  device_descriptions: dict[str, DeviceDescription] = {
271
272
  device_address: self.get_device_description(interface_id=interface_id, address=device_address)
@@ -277,22 +278,22 @@ class DeviceDescriptionCache(BasePersistentCache):
277
278
  )
278
279
  return device_descriptions
279
280
 
280
- def get_model(self, device_address: str) -> str | None:
281
+ def get_model(self, *, device_address: str) -> str | None:
281
282
  """Return the device type."""
282
283
  for data in self._device_descriptions.values():
283
284
  if items := data.get(device_address):
284
285
  return items["TYPE"]
285
286
  return None
286
287
 
287
- def _convert_device_descriptions(self, interface_id: str, device_descriptions: list[DeviceDescription]) -> None:
288
+ def _convert_device_descriptions(self, *, interface_id: str, device_descriptions: list[DeviceDescription]) -> None:
288
289
  """Convert provided list of device descriptions."""
289
290
  for device_description in device_descriptions:
290
291
  self._process_device_description(interface_id=interface_id, device_description=device_description)
291
292
 
292
- def _process_device_description(self, interface_id: str, device_description: DeviceDescription) -> None:
293
+ def _process_device_description(self, *, interface_id: str, device_description: DeviceDescription) -> None:
293
294
  """Convert provided dict of device descriptions."""
294
295
  address = device_description["ADDRESS"]
295
- device_address = get_device_address(address)
296
+ device_address = get_device_address(address=address)
296
297
  self._device_descriptions[interface_id][address] = device_description
297
298
 
298
299
  # Avoid redundant membership checks; set.add is idempotent and cheaper than check+add
@@ -310,7 +311,7 @@ class DeviceDescriptionCache(BasePersistentCache):
310
311
  interface_id,
311
312
  device_descriptions,
312
313
  ) in self._raw_device_descriptions.items():
313
- self._convert_device_descriptions(interface_id, device_descriptions)
314
+ self._convert_device_descriptions(interface_id=interface_id, device_descriptions=device_descriptions)
314
315
  return result
315
316
 
316
317
 
@@ -324,7 +325,7 @@ class ParamsetDescriptionCache(BasePersistentCache):
324
325
 
325
326
  _file_postfix = FILE_PARAMSETS
326
327
 
327
- def __init__(self, central: hmcu.CentralUnit) -> None:
328
+ def __init__(self, *, central: hmcu.CentralUnit) -> None:
328
329
  """Init the paramset description cache."""
329
330
  # {interface_id, {channel_address, paramsets}}
330
331
  self._raw_paramset_descriptions: Final[dict[str, dict[str, dict[ParamsetKey, dict[str, ParameterData]]]]] = (
@@ -347,6 +348,7 @@ class ParamsetDescriptionCache(BasePersistentCache):
347
348
 
348
349
  def add(
349
350
  self,
351
+ *,
350
352
  interface_id: str,
351
353
  channel_address: str,
352
354
  paramset_key: ParamsetKey,
@@ -356,49 +358,49 @@ class ParamsetDescriptionCache(BasePersistentCache):
356
358
  self._raw_paramset_descriptions[interface_id][channel_address][paramset_key] = paramset_description
357
359
  self._add_address_parameter(channel_address=channel_address, paramsets=[paramset_description])
358
360
 
359
- def remove_device(self, device: Device) -> None:
361
+ def remove_device(self, *, device: Device) -> None:
360
362
  """Remove device paramset descriptions from cache."""
361
363
  if interface := self._raw_paramset_descriptions.get(device.interface_id):
362
364
  for channel_address in device.channels:
363
365
  if channel_address in interface:
364
366
  del self._raw_paramset_descriptions[device.interface_id][channel_address]
365
367
 
366
- def has_interface_id(self, interface_id: str) -> bool:
368
+ def has_interface_id(self, *, interface_id: str) -> bool:
367
369
  """Return if interface is in paramset_descriptions cache."""
368
370
  return interface_id in self._raw_paramset_descriptions
369
371
 
370
- def get_paramset_keys(self, interface_id: str, channel_address: str) -> tuple[ParamsetKey, ...]:
372
+ def get_paramset_keys(self, *, interface_id: str, channel_address: str) -> tuple[ParamsetKey, ...]:
371
373
  """Get paramset_keys from paramset descriptions cache."""
372
374
  return tuple(self._raw_paramset_descriptions[interface_id][channel_address])
373
375
 
374
376
  def get_channel_paramset_descriptions(
375
- self, interface_id: str, channel_address: str
377
+ self, *, interface_id: str, channel_address: str
376
378
  ) -> Mapping[ParamsetKey, Mapping[str, ParameterData]]:
377
379
  """Get paramset descriptions for a channelfrom cache."""
378
380
  return self._raw_paramset_descriptions[interface_id].get(channel_address, {})
379
381
 
380
382
  def get_paramset_descriptions(
381
- self, interface_id: str, channel_address: str, paramset_key: ParamsetKey
383
+ self, *, interface_id: str, channel_address: str, paramset_key: ParamsetKey
382
384
  ) -> Mapping[str, ParameterData]:
383
385
  """Get paramset descriptions from cache."""
384
386
  return self._raw_paramset_descriptions[interface_id][channel_address][paramset_key]
385
387
 
386
388
  def get_parameter_data(
387
- self, interface_id: str, channel_address: str, paramset_key: ParamsetKey, parameter: str
389
+ self, *, interface_id: str, channel_address: str, paramset_key: ParamsetKey, parameter: str
388
390
  ) -> ParameterData | None:
389
391
  """Get parameter_data from cache."""
390
392
  return self._raw_paramset_descriptions[interface_id][channel_address][paramset_key].get(parameter)
391
393
 
392
- def is_in_multiple_channels(self, channel_address: str, parameter: str) -> bool:
394
+ def is_in_multiple_channels(self, *, channel_address: str, parameter: str) -> bool:
393
395
  """Check if parameter is in multiple channels per device."""
394
396
  if ADDRESS_SEPARATOR not in channel_address:
395
397
  return False
396
- if channels := self._address_parameter_cache.get((get_device_address(channel_address), parameter)):
398
+ if channels := self._address_parameter_cache.get((get_device_address(address=channel_address), parameter)):
397
399
  return len(channels) > 1
398
400
  return False
399
401
 
400
402
  def get_channel_addresses_by_paramset_key(
401
- self, interface_id: str, device_address: str
403
+ self, *, interface_id: str, device_address: str
402
404
  ) -> Mapping[ParamsetKey, list[str]]:
403
405
  """Get device channel addresses."""
404
406
  channel_addresses: dict[ParamsetKey, list[str]] = {}
@@ -425,9 +427,9 @@ class ParamsetDescriptionCache(BasePersistentCache):
425
427
  for channel_address, paramsets in channel_paramsets.items():
426
428
  self._add_address_parameter(channel_address=channel_address, paramsets=list(paramsets.values()))
427
429
 
428
- def _add_address_parameter(self, channel_address: str, paramsets: list[dict[str, Any]]) -> None:
430
+ def _add_address_parameter(self, *, channel_address: str, paramsets: list[dict[str, Any]]) -> None:
429
431
  """Add address parameter to cache."""
430
- device_address, channel_no = get_split_channel_address(channel_address)
432
+ device_address, channel_no = get_split_channel_address(channel_address=channel_address)
431
433
  cache = self._address_parameter_cache
432
434
  for paramset in paramsets:
433
435
  if not paramset:
@@ -449,17 +451,17 @@ class ParamsetDescriptionCache(BasePersistentCache):
449
451
  return await super().save()
450
452
 
451
453
 
452
- def _get_cache_path(storage_folder: str) -> str:
454
+ def _get_cache_path(*, storage_folder: str) -> str:
453
455
  """Return the cache path."""
454
456
  return f"{storage_folder}/{CACHE_PATH}"
455
457
 
456
458
 
457
- def _get_filename(central_name: str, file_name: str) -> str:
459
+ def _get_filename(*, central_name: str, file_name: str) -> str:
458
460
  """Return the cache filename."""
459
461
  return f"{slugify(central_name)}_{file_name}"
460
462
 
461
463
 
462
- def cleanup_cache_dirs(central_name: str, storage_folder: str) -> None:
464
+ def cleanup_cache_dirs(*, central_name: str, storage_folder: str) -> None:
463
465
  """Clean up the used cached directories."""
464
466
  cache_dir = _get_cache_path(storage_folder=storage_folder)
465
467
  for file_to_delete in (FILE_DEVICES, FILE_PARAMSETS):
@@ -239,7 +239,7 @@ _IGNORED_PARAMETERS_START_RE: Final = re.compile(
239
239
  )
240
240
 
241
241
 
242
- def _parameter_is_wildcard_ignored(parameter: TParameterName) -> bool:
242
+ def _parameter_is_wildcard_ignored(*, parameter: TParameterName) -> bool:
243
243
  """Check if a parameter matches common wildcard patterns."""
244
244
  return bool(_IGNORED_PARAMETERS_END_RE.match(parameter) or _IGNORED_PARAMETERS_START_RE.match(parameter))
245
245
 
@@ -260,7 +260,7 @@ _UN_IGNORE_PARAMETERS_BY_MODEL_LOWER: Final[dict[TModelName, frozenset[Parameter
260
260
 
261
261
 
262
262
  @cache
263
- def _get_parameters_for_model_prefix(model_prefix: str | None) -> frozenset[Parameter] | None:
263
+ def _get_parameters_for_model_prefix(*, model_prefix: str | None) -> frozenset[Parameter] | None:
264
264
  """Return the dict value by wildcard type."""
265
265
  if model_prefix is None:
266
266
  return None
@@ -359,6 +359,7 @@ class ParameterVisibilityCache:
359
359
 
360
360
  def __init__(
361
361
  self,
362
+ *,
362
363
  central: hmcu.CentralUnit,
363
364
  ) -> None:
364
365
  """Init the parameter visibility cache."""
@@ -419,7 +420,7 @@ class ParameterVisibilityCache:
419
420
  self._process_un_ignore_entries(lines=self._raw_un_ignores)
420
421
 
421
422
  def _resolve_prefix_key(
422
- self, model_l: TModelName, mapping: Mapping[str, object], cache_dict: dict[TModelName, str | None]
423
+ self, *, model_l: TModelName, mapping: Mapping[str, object], cache_dict: dict[TModelName, str | None]
423
424
  ) -> str | None:
424
425
  """Resolve and memoize the first key in mapping that prefixes model_l."""
425
426
  dt_short_key = cache_dict.get(model_l)
@@ -428,7 +429,7 @@ class ParameterVisibilityCache:
428
429
  cache_dict[model_l] = dt_short_key
429
430
  return dt_short_key
430
431
 
431
- def model_is_ignored(self, model: TModelName) -> bool:
432
+ def model_is_ignored(self, *, model: TModelName) -> bool:
432
433
  """Check if a model should be ignored for custom data points."""
433
434
  return element_matches_key(
434
435
  search_elements=self._ignore_custom_device_definition_models,
@@ -437,6 +438,7 @@ class ParameterVisibilityCache:
437
438
 
438
439
  def parameter_is_ignored(
439
440
  self,
441
+ *,
440
442
  channel: hmd.Channel,
441
443
  paramset_key: ParamsetKey,
442
444
  parameter: TParameterName,
@@ -506,6 +508,7 @@ class ParameterVisibilityCache:
506
508
 
507
509
  def _parameter_is_un_ignored(
508
510
  self,
511
+ *,
509
512
  channel: hmd.Channel,
510
513
  paramset_key: ParamsetKey,
511
514
  parameter: TParameterName,
@@ -555,6 +558,7 @@ class ParameterVisibilityCache:
555
558
 
556
559
  def parameter_is_un_ignored(
557
560
  self,
561
+ *,
558
562
  channel: hmd.Channel,
559
563
  paramset_key: ParamsetKey,
560
564
  parameter: TParameterName,
@@ -590,7 +594,12 @@ class ParameterVisibilityCache:
590
594
  )
591
595
 
592
596
  def should_skip_parameter(
593
- self, channel: hmd.Channel, paramset_key: ParamsetKey, parameter: TParameterName, parameter_is_un_ignored: bool
597
+ self,
598
+ *,
599
+ channel: hmd.Channel,
600
+ paramset_key: ParamsetKey,
601
+ parameter: TParameterName,
602
+ parameter_is_un_ignored: bool,
594
603
  ) -> bool:
595
604
  """Determine if a parameter should be skipped."""
596
605
  if self.parameter_is_ignored(
@@ -613,7 +622,7 @@ class ParameterVisibilityCache:
613
622
 
614
623
  return paramset_key == ParamsetKey.MASTER and not parameter_is_un_ignored
615
624
 
616
- def _process_un_ignore_entries(self, lines: Iterable[str]) -> None:
625
+ def _process_un_ignore_entries(self, *, lines: Iterable[str]) -> None:
617
626
  """Batch process un_ignore entries into cache."""
618
627
  for line in lines:
619
628
  # ignore empty line
@@ -637,7 +646,7 @@ class ParameterVisibilityCache:
637
646
  )
638
647
 
639
648
  def _get_un_ignore_line_details(
640
- self, line: str
649
+ self, *, line: str
641
650
  ) -> tuple[TModelName, TUnIgnoreChannelNo, TParameterName, ParamsetKey] | str | None:
642
651
  """
643
652
  Check the format of the line for un_ignore file.
@@ -715,6 +724,7 @@ class ParameterVisibilityCache:
715
724
 
716
725
  def _add_complex_un_ignore_entry(
717
726
  self,
727
+ *,
718
728
  model: TModelName,
719
729
  channel_no: TUnIgnoreChannelNo,
720
730
  paramset_key: ParamsetKey,
@@ -739,6 +749,7 @@ class ParameterVisibilityCache:
739
749
 
740
750
  def parameter_is_hidden(
741
751
  self,
752
+ *,
742
753
  channel: hmd.Channel,
743
754
  paramset_key: ParamsetKey,
744
755
  parameter: TParameterName,
@@ -757,6 +768,7 @@ class ParameterVisibilityCache:
757
768
 
758
769
  def is_relevant_paramset(
759
770
  self,
771
+ *,
760
772
  channel: hmd.Channel,
761
773
  paramset_key: ParamsetKey,
762
774
  ) -> bool: