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.
- aiohomematic/async_support.py +7 -7
- aiohomematic/caches/dynamic.py +31 -26
- aiohomematic/caches/persistent.py +34 -32
- aiohomematic/caches/visibility.py +19 -7
- aiohomematic/central/__init__.py +87 -74
- aiohomematic/central/decorators.py +2 -2
- aiohomematic/central/xml_rpc_server.py +27 -24
- aiohomematic/client/__init__.py +72 -56
- aiohomematic/client/_rpc_errors.py +3 -3
- aiohomematic/client/json_rpc.py +33 -25
- aiohomematic/client/xml_rpc.py +14 -9
- aiohomematic/const.py +2 -1
- aiohomematic/converter.py +19 -19
- aiohomematic/exceptions.py +2 -1
- aiohomematic/model/__init__.py +4 -3
- aiohomematic/model/calculated/__init__.py +1 -1
- aiohomematic/model/calculated/climate.py +9 -9
- aiohomematic/model/calculated/data_point.py +13 -7
- aiohomematic/model/calculated/operating_voltage_level.py +2 -2
- aiohomematic/model/calculated/support.py +7 -7
- aiohomematic/model/custom/__init__.py +3 -3
- aiohomematic/model/custom/climate.py +57 -34
- aiohomematic/model/custom/cover.py +32 -18
- aiohomematic/model/custom/data_point.py +9 -7
- aiohomematic/model/custom/definition.py +23 -17
- aiohomematic/model/custom/light.py +52 -23
- aiohomematic/model/custom/lock.py +16 -12
- aiohomematic/model/custom/siren.py +6 -3
- aiohomematic/model/custom/switch.py +3 -2
- aiohomematic/model/custom/valve.py +3 -2
- aiohomematic/model/data_point.py +62 -49
- aiohomematic/model/device.py +48 -42
- aiohomematic/model/event.py +6 -5
- aiohomematic/model/generic/__init__.py +6 -4
- aiohomematic/model/generic/action.py +1 -1
- aiohomematic/model/generic/data_point.py +7 -5
- aiohomematic/model/generic/number.py +3 -3
- aiohomematic/model/generic/select.py +1 -1
- aiohomematic/model/generic/sensor.py +2 -2
- aiohomematic/model/generic/switch.py +3 -3
- aiohomematic/model/hub/__init__.py +17 -16
- aiohomematic/model/hub/data_point.py +12 -7
- aiohomematic/model/hub/number.py +3 -3
- aiohomematic/model/hub/select.py +3 -3
- aiohomematic/model/hub/text.py +2 -2
- aiohomematic/model/support.py +8 -7
- aiohomematic/model/update.py +6 -6
- aiohomematic/support.py +44 -38
- aiohomematic/validator.py +6 -6
- {aiohomematic-2025.10.1.dist-info → aiohomematic-2025.10.2.dist-info}/METADATA +1 -1
- aiohomematic-2025.10.2.dist-info/RECORD +78 -0
- aiohomematic_support/client_local.py +19 -12
- aiohomematic-2025.10.1.dist-info/RECORD +0 -78
- {aiohomematic-2025.10.1.dist-info → aiohomematic-2025.10.2.dist-info}/WHEEL +0 -0
- {aiohomematic-2025.10.1.dist-info → aiohomematic-2025.10.2.dist-info}/licenses/LICENSE +0 -0
- {aiohomematic-2025.10.1.dist-info → aiohomematic-2025.10.2.dist-info}/top_level.txt +0 -0
aiohomematic/async_support.py
CHANGED
|
@@ -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
|
|
aiohomematic/caches/dynamic.py
CHANGED
|
@@ -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,
|
|
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,
|
|
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:
|