aiohomematic 2025.10.7__py3-none-any.whl → 2025.10.9__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 (73) hide show
  1. aiohomematic/__init__.py +3 -3
  2. aiohomematic/async_support.py +1 -1
  3. aiohomematic/central/__init__.py +59 -31
  4. aiohomematic/central/decorators.py +1 -1
  5. aiohomematic/central/rpc_server.py +1 -1
  6. aiohomematic/client/__init__.py +19 -13
  7. aiohomematic/client/_rpc_errors.py +1 -1
  8. aiohomematic/client/json_rpc.py +29 -3
  9. aiohomematic/client/rpc_proxy.py +20 -2
  10. aiohomematic/const.py +25 -6
  11. aiohomematic/context.py +1 -1
  12. aiohomematic/converter.py +1 -1
  13. aiohomematic/decorators.py +1 -1
  14. aiohomematic/exceptions.py +1 -1
  15. aiohomematic/hmcli.py +1 -1
  16. aiohomematic/model/__init__.py +1 -1
  17. aiohomematic/model/calculated/__init__.py +21 -4
  18. aiohomematic/model/calculated/climate.py +59 -1
  19. aiohomematic/model/calculated/data_point.py +1 -1
  20. aiohomematic/model/calculated/operating_voltage_level.py +1 -1
  21. aiohomematic/model/calculated/support.py +41 -3
  22. aiohomematic/model/custom/__init__.py +1 -1
  23. aiohomematic/model/custom/climate.py +7 -4
  24. aiohomematic/model/custom/const.py +1 -1
  25. aiohomematic/model/custom/cover.py +1 -1
  26. aiohomematic/model/custom/data_point.py +1 -1
  27. aiohomematic/model/custom/definition.py +1 -1
  28. aiohomematic/model/custom/light.py +1 -1
  29. aiohomematic/model/custom/lock.py +1 -1
  30. aiohomematic/model/custom/siren.py +1 -1
  31. aiohomematic/model/custom/support.py +1 -1
  32. aiohomematic/model/custom/switch.py +1 -1
  33. aiohomematic/model/custom/valve.py +1 -1
  34. aiohomematic/model/data_point.py +3 -2
  35. aiohomematic/model/device.py +10 -13
  36. aiohomematic/model/event.py +1 -1
  37. aiohomematic/model/generic/__init__.py +1 -1
  38. aiohomematic/model/generic/action.py +1 -1
  39. aiohomematic/model/generic/binary_sensor.py +1 -1
  40. aiohomematic/model/generic/button.py +1 -1
  41. aiohomematic/model/generic/data_point.py +1 -1
  42. aiohomematic/model/generic/number.py +1 -1
  43. aiohomematic/model/generic/select.py +1 -1
  44. aiohomematic/model/generic/sensor.py +1 -1
  45. aiohomematic/model/generic/switch.py +1 -1
  46. aiohomematic/model/generic/text.py +1 -1
  47. aiohomematic/model/hub/__init__.py +1 -1
  48. aiohomematic/model/hub/binary_sensor.py +1 -1
  49. aiohomematic/model/hub/button.py +1 -1
  50. aiohomematic/model/hub/data_point.py +1 -1
  51. aiohomematic/model/hub/number.py +1 -1
  52. aiohomematic/model/hub/select.py +1 -1
  53. aiohomematic/model/hub/sensor.py +1 -1
  54. aiohomematic/model/hub/switch.py +1 -1
  55. aiohomematic/model/hub/text.py +1 -1
  56. aiohomematic/model/support.py +1 -1
  57. aiohomematic/model/update.py +1 -1
  58. aiohomematic/property_decorators.py +2 -2
  59. aiohomematic/store/__init__.py +34 -0
  60. aiohomematic/{caches → store}/dynamic.py +4 -4
  61. aiohomematic/store/persistent.py +933 -0
  62. aiohomematic/{caches → store}/visibility.py +4 -4
  63. aiohomematic/support.py +20 -17
  64. aiohomematic/validator.py +1 -1
  65. {aiohomematic-2025.10.7.dist-info → aiohomematic-2025.10.9.dist-info}/METADATA +1 -1
  66. aiohomematic-2025.10.9.dist-info/RECORD +78 -0
  67. aiohomematic_support/client_local.py +2 -2
  68. aiohomematic/caches/__init__.py +0 -12
  69. aiohomematic/caches/persistent.py +0 -478
  70. aiohomematic-2025.10.7.dist-info/RECORD +0 -78
  71. {aiohomematic-2025.10.7.dist-info → aiohomematic-2025.10.9.dist-info}/WHEEL +0 -0
  72. {aiohomematic-2025.10.7.dist-info → aiohomematic-2025.10.9.dist-info}/licenses/LICENSE +0 -0
  73. {aiohomematic-2025.10.7.dist-info → aiohomematic-2025.10.9.dist-info}/top_level.txt +0 -0
@@ -1,478 +0,0 @@
1
- # SPDX-License-Identifier: MIT
2
- # Copyright (c) 2021-2025 Daniel Perna, SukramJ
3
- """
4
- Persistent caches used to persist Homematic metadata between runs.
5
-
6
- This module provides on-disk caches that complement the short‑lived, in‑memory
7
- caches from aiohomematic.caches.dynamic. The goal is to minimize expensive data
8
- retrieval from the backend by storing stable metadata such as device and
9
- paramset descriptions in JSON files inside a dedicated cache directory.
10
-
11
- Overview
12
- - BasePersistentCache: Abstract base for file‑backed caches. It encapsulates
13
- file path resolution, change detection via hashing, and thread‑safe save/load
14
- operations delegated to the CentralUnit looper.
15
- - DeviceDescriptionCache: Persists device descriptions per interface, including
16
- the mapping of device/channels and model metadata.
17
- - ParamsetDescriptionCache: Persists paramset descriptions per interface and
18
- channel, and offers helpers to query parameters, paramset keys and related
19
- channel addresses.
20
-
21
- Key behaviors
22
- - Saves only if caches are enabled (CentralConfig.use_caches) and content has
23
- changed (hash comparison), keeping I/O minimal and predictable.
24
- - Uses orjson for fast binary writes and json for reads with a custom
25
- object_hook to rebuild nested defaultdict structures.
26
- - Save/load/clear operations are synchronized via a semaphore and executed via
27
- the CentralUnit looper to avoid blocking the event loop.
28
-
29
- Helper functions are provided to build cache paths and filenames and to
30
- optionally clean up stale cache directories.
31
- """
32
-
33
- from __future__ import annotations
34
-
35
- from abc import ABC
36
- import asyncio
37
- from collections import defaultdict
38
- from collections.abc import Mapping
39
- from datetime import datetime
40
- import json
41
- import logging
42
- import os
43
- from typing import Any, Final
44
-
45
- import orjson
46
- from slugify import slugify
47
-
48
- from aiohomematic import central as hmcu
49
- from aiohomematic.const import (
50
- ADDRESS_SEPARATOR,
51
- CACHE_PATH,
52
- FILE_DEVICES,
53
- FILE_PARAMSETS,
54
- INIT_DATETIME,
55
- UTF_8,
56
- DataOperationResult,
57
- DeviceDescription,
58
- ParameterData,
59
- ParamsetKey,
60
- )
61
- from aiohomematic.model.device import Device
62
- from aiohomematic.support import (
63
- check_or_create_directory,
64
- delete_file,
65
- get_device_address,
66
- get_split_channel_address,
67
- hash_sha256,
68
- regular_to_default_dict_hook,
69
- )
70
-
71
- _LOGGER: Final = logging.getLogger(__name__)
72
-
73
-
74
- class BasePersistentCache(ABC):
75
- """Cache for files."""
76
-
77
- __slots__ = (
78
- "_cache_dir",
79
- "_central",
80
- "_file_postfix",
81
- "_filename",
82
- "_persistent_cache",
83
- "_save_load_semaphore",
84
- "last_hash_saved",
85
- "last_save_triggered",
86
- )
87
-
88
- _file_postfix: str
89
-
90
- def __init__(
91
- self,
92
- *,
93
- central: hmcu.CentralUnit,
94
- persistent_cache: dict[str, Any],
95
- ) -> None:
96
- """Initialize the base class of the persistent cache."""
97
- self._save_load_semaphore: Final = asyncio.Semaphore()
98
- self._central: Final = central
99
- self._cache_dir: Final = _get_cache_path(storage_folder=central.config.storage_folder)
100
- self._filename: Final = _get_filename(central_name=central.name, file_name=self._file_postfix)
101
- self._persistent_cache: Final = persistent_cache
102
- self.last_save_triggered: datetime = INIT_DATETIME
103
- self.last_hash_saved = hash_sha256(value=persistent_cache)
104
-
105
- @property
106
- def cache_hash(self) -> str:
107
- """Return the hash of the cache."""
108
- return hash_sha256(value=self._persistent_cache)
109
-
110
- @property
111
- def data_changed(self) -> bool:
112
- """Return if the data has changed."""
113
- return self.cache_hash != self.last_hash_saved
114
-
115
- @property
116
- def _file_path(self) -> str:
117
- """Return the full file path."""
118
- return os.path.join(self._cache_dir, self._filename)
119
-
120
- async def save(self) -> DataOperationResult:
121
- """Save current data to disk."""
122
- if not self._should_save:
123
- return DataOperationResult.NO_SAVE
124
-
125
- def _perform_save() -> DataOperationResult:
126
- try:
127
- with open(file=self._file_path, mode="wb") as file_pointer:
128
- file_pointer.write(
129
- orjson.dumps(
130
- self._persistent_cache,
131
- option=orjson.OPT_NON_STR_KEYS,
132
- )
133
- )
134
- self.last_hash_saved = self.cache_hash
135
- except json.JSONDecodeError:
136
- return DataOperationResult.SAVE_FAIL
137
- return DataOperationResult.SAVE_SUCCESS
138
-
139
- async with self._save_load_semaphore:
140
- return await self._central.looper.async_add_executor_job(
141
- _perform_save, name=f"save-persistent-cache-{self._filename}"
142
- )
143
-
144
- @property
145
- def _should_save(self) -> bool:
146
- """Determine if save operation should proceed."""
147
- self.last_save_triggered = datetime.now()
148
- return (
149
- check_or_create_directory(directory=self._cache_dir)
150
- and self._central.config.use_caches
151
- and self.cache_hash != self.last_hash_saved
152
- )
153
-
154
- async def load(self) -> DataOperationResult:
155
- """Load data from disk into the dictionary."""
156
- if not check_or_create_directory(directory=self._cache_dir) or not os.path.exists(self._file_path):
157
- return DataOperationResult.NO_LOAD
158
-
159
- def _perform_load() -> DataOperationResult:
160
- with open(file=self._file_path, encoding=UTF_8) as file_pointer:
161
- try:
162
- data = json.loads(file_pointer.read(), object_hook=regular_to_default_dict_hook)
163
- if (converted_hash := hash_sha256(value=data)) == self.last_hash_saved:
164
- return DataOperationResult.NO_LOAD
165
- self._persistent_cache.clear()
166
- self._persistent_cache.update(data)
167
- self.last_hash_saved = converted_hash
168
- except json.JSONDecodeError:
169
- return DataOperationResult.LOAD_FAIL
170
- return DataOperationResult.LOAD_SUCCESS
171
-
172
- async with self._save_load_semaphore:
173
- return await self._central.looper.async_add_executor_job(
174
- _perform_load, name=f"load-persistent-cache-{self._filename}"
175
- )
176
-
177
- async def clear(self) -> None:
178
- """Remove stored file from disk."""
179
-
180
- def _perform_clear() -> None:
181
- delete_file(folder=self._cache_dir, file_name=self._filename)
182
- self._persistent_cache.clear()
183
-
184
- async with self._save_load_semaphore:
185
- await self._central.looper.async_add_executor_job(_perform_clear, name="clear-persistent-cache")
186
-
187
-
188
- class DeviceDescriptionCache(BasePersistentCache):
189
- """Cache for device/channel names."""
190
-
191
- __slots__ = (
192
- "_addresses",
193
- "_device_descriptions",
194
- "_raw_device_descriptions",
195
- )
196
-
197
- _file_postfix = FILE_DEVICES
198
-
199
- def __init__(self, *, central: hmcu.CentralUnit) -> None:
200
- """Initialize the device description cache."""
201
- # {interface_id, [device_descriptions]}
202
- self._raw_device_descriptions: Final[dict[str, list[DeviceDescription]]] = defaultdict(list)
203
- super().__init__(
204
- central=central,
205
- persistent_cache=self._raw_device_descriptions,
206
- )
207
- # {interface_id, {device_address, [channel_address]}}
208
- self._addresses: Final[dict[str, dict[str, set[str]]]] = defaultdict(lambda: defaultdict(set))
209
- # {interface_id, {address, device_descriptions}}
210
- self._device_descriptions: Final[dict[str, dict[str, DeviceDescription]]] = defaultdict(dict)
211
-
212
- def add_device(self, *, interface_id: str, device_description: DeviceDescription) -> None:
213
- """Add a device to the cache."""
214
- # Fast-path: If the address is not yet known, skip costly removal operations.
215
- if (address := device_description["ADDRESS"]) not in self._device_descriptions[interface_id]:
216
- self._raw_device_descriptions[interface_id].append(device_description)
217
- self._process_device_description(interface_id=interface_id, device_description=device_description)
218
- return
219
- # Address exists: remove old entries before adding the new description.
220
- self._remove_device(
221
- interface_id=interface_id,
222
- addresses_to_remove=[address],
223
- )
224
- self._raw_device_descriptions[interface_id].append(device_description)
225
- self._process_device_description(interface_id=interface_id, device_description=device_description)
226
-
227
- def get_raw_device_descriptions(self, *, interface_id: str) -> list[DeviceDescription]:
228
- """Retrieve raw device descriptions from the cache."""
229
- return self._raw_device_descriptions[interface_id]
230
-
231
- def remove_device(self, *, device: Device) -> None:
232
- """Remove device from cache."""
233
- self._remove_device(
234
- interface_id=device.interface_id,
235
- addresses_to_remove=[device.address, *device.channels.keys()],
236
- )
237
-
238
- def _remove_device(self, *, interface_id: str, addresses_to_remove: list[str]) -> None:
239
- """Remove a device from the cache."""
240
- # Use a set for faster membership checks
241
- addresses_set = set(addresses_to_remove)
242
- self._raw_device_descriptions[interface_id] = [
243
- device for device in self._raw_device_descriptions[interface_id] if device["ADDRESS"] not in addresses_set
244
- ]
245
- addr_map = self._addresses[interface_id]
246
- desc_map = self._device_descriptions[interface_id]
247
- for address in addresses_set:
248
- # Pop with default to avoid KeyError and try/except overhead
249
- if ADDRESS_SEPARATOR not in address:
250
- addr_map.pop(address, None)
251
- desc_map.pop(address, None)
252
-
253
- def get_addresses(self, *, interface_id: str | None = None) -> frozenset[str]:
254
- """Return the addresses by interface as a set."""
255
- if interface_id:
256
- return frozenset(self._addresses[interface_id])
257
- return frozenset(addr for interface_id in self.get_interface_ids() for addr in self._addresses[interface_id])
258
-
259
- def get_device_descriptions(self, *, interface_id: str) -> Mapping[str, DeviceDescription]:
260
- """Return the devices by interface."""
261
- return self._device_descriptions[interface_id]
262
-
263
- def get_interface_ids(self) -> tuple[str, ...]:
264
- """Return the interface ids."""
265
- return tuple(self._raw_device_descriptions.keys())
266
-
267
- def has_device_descriptions(self, *, interface_id: str) -> bool:
268
- """Return the devices by interface."""
269
- return interface_id in self._device_descriptions
270
-
271
- def find_device_description(self, *, interface_id: str, device_address: str) -> DeviceDescription | None:
272
- """Return the device description by interface and device_address."""
273
- return self._device_descriptions[interface_id].get(device_address)
274
-
275
- def get_device_description(self, *, interface_id: str, address: str) -> DeviceDescription:
276
- """Return the device description by interface and device_address."""
277
- return self._device_descriptions[interface_id][address]
278
-
279
- def get_device_with_channels(self, *, interface_id: str, device_address: str) -> Mapping[str, DeviceDescription]:
280
- """Return the device dict by interface and device_address."""
281
- device_descriptions: dict[str, DeviceDescription] = {
282
- device_address: self.get_device_description(interface_id=interface_id, address=device_address)
283
- }
284
- children = device_descriptions[device_address]["CHILDREN"]
285
- for channel_address in children:
286
- device_descriptions[channel_address] = self.get_device_description(
287
- interface_id=interface_id, address=channel_address
288
- )
289
- return device_descriptions
290
-
291
- def get_model(self, *, device_address: str) -> str | None:
292
- """Return the device type."""
293
- for data in self._device_descriptions.values():
294
- if items := data.get(device_address):
295
- return items["TYPE"]
296
- return None
297
-
298
- def _convert_device_descriptions(self, *, interface_id: str, device_descriptions: list[DeviceDescription]) -> None:
299
- """Convert provided list of device descriptions."""
300
- for device_description in device_descriptions:
301
- self._process_device_description(interface_id=interface_id, device_description=device_description)
302
-
303
- def _process_device_description(self, *, interface_id: str, device_description: DeviceDescription) -> None:
304
- """Convert provided dict of device descriptions."""
305
- address = device_description["ADDRESS"]
306
- device_address = get_device_address(address=address)
307
- self._device_descriptions[interface_id][address] = device_description
308
-
309
- # Avoid redundant membership checks; set.add is idempotent and cheaper than check+add
310
- addr_set = self._addresses[interface_id][device_address]
311
- addr_set.add(device_address)
312
- addr_set.add(address)
313
-
314
- async def load(self) -> DataOperationResult:
315
- """Load device data from disk into _device_description_cache."""
316
- if not self._central.config.use_caches:
317
- _LOGGER.debug("load: not caching paramset descriptions for %s", self._central.name)
318
- return DataOperationResult.NO_LOAD
319
- if (result := await super().load()) == DataOperationResult.LOAD_SUCCESS:
320
- for (
321
- interface_id,
322
- device_descriptions,
323
- ) in self._raw_device_descriptions.items():
324
- self._convert_device_descriptions(interface_id=interface_id, device_descriptions=device_descriptions)
325
- return result
326
-
327
-
328
- class ParamsetDescriptionCache(BasePersistentCache):
329
- """Cache for paramset descriptions."""
330
-
331
- __slots__ = (
332
- "_address_parameter_cache",
333
- "_raw_paramset_descriptions",
334
- )
335
-
336
- _file_postfix = FILE_PARAMSETS
337
-
338
- def __init__(self, *, central: hmcu.CentralUnit) -> None:
339
- """Init the paramset description cache."""
340
- # {interface_id, {channel_address, paramsets}}
341
- self._raw_paramset_descriptions: Final[dict[str, dict[str, dict[ParamsetKey, dict[str, ParameterData]]]]] = (
342
- defaultdict(lambda: defaultdict(lambda: defaultdict(dict)))
343
- )
344
- super().__init__(
345
- central=central,
346
- persistent_cache=self._raw_paramset_descriptions,
347
- )
348
-
349
- # {(device_address, parameter), [channel_no]}
350
- self._address_parameter_cache: Final[dict[tuple[str, str], set[int | None]]] = {}
351
-
352
- @property
353
- def raw_paramset_descriptions(
354
- self,
355
- ) -> Mapping[str, Mapping[str, Mapping[ParamsetKey, Mapping[str, ParameterData]]]]:
356
- """Return the paramset descriptions."""
357
- return self._raw_paramset_descriptions
358
-
359
- def add(
360
- self,
361
- *,
362
- interface_id: str,
363
- channel_address: str,
364
- paramset_key: ParamsetKey,
365
- paramset_description: dict[str, ParameterData],
366
- ) -> None:
367
- """Add paramset description to cache."""
368
- self._raw_paramset_descriptions[interface_id][channel_address][paramset_key] = paramset_description
369
- self._add_address_parameter(channel_address=channel_address, paramsets=[paramset_description])
370
-
371
- def remove_device(self, *, device: Device) -> None:
372
- """Remove device paramset descriptions from cache."""
373
- if interface := self._raw_paramset_descriptions.get(device.interface_id):
374
- for channel_address in device.channels:
375
- if channel_address in interface:
376
- del self._raw_paramset_descriptions[device.interface_id][channel_address]
377
-
378
- def has_interface_id(self, *, interface_id: str) -> bool:
379
- """Return if interface is in paramset_descriptions cache."""
380
- return interface_id in self._raw_paramset_descriptions
381
-
382
- def get_paramset_keys(self, *, interface_id: str, channel_address: str) -> tuple[ParamsetKey, ...]:
383
- """Get paramset_keys from paramset descriptions cache."""
384
- return tuple(self._raw_paramset_descriptions[interface_id][channel_address])
385
-
386
- def get_channel_paramset_descriptions(
387
- self, *, interface_id: str, channel_address: str
388
- ) -> Mapping[ParamsetKey, Mapping[str, ParameterData]]:
389
- """Get paramset descriptions for a channelfrom cache."""
390
- return self._raw_paramset_descriptions[interface_id].get(channel_address, {})
391
-
392
- def get_paramset_descriptions(
393
- self, *, interface_id: str, channel_address: str, paramset_key: ParamsetKey
394
- ) -> Mapping[str, ParameterData]:
395
- """Get paramset descriptions from cache."""
396
- return self._raw_paramset_descriptions[interface_id][channel_address][paramset_key]
397
-
398
- def get_parameter_data(
399
- self, *, interface_id: str, channel_address: str, paramset_key: ParamsetKey, parameter: str
400
- ) -> ParameterData | None:
401
- """Get parameter_data from cache."""
402
- return self._raw_paramset_descriptions[interface_id][channel_address][paramset_key].get(parameter)
403
-
404
- def is_in_multiple_channels(self, *, channel_address: str, parameter: str) -> bool:
405
- """Check if parameter is in multiple channels per device."""
406
- if ADDRESS_SEPARATOR not in channel_address:
407
- return False
408
- if channels := self._address_parameter_cache.get((get_device_address(address=channel_address), parameter)):
409
- return len(channels) > 1
410
- return False
411
-
412
- def get_channel_addresses_by_paramset_key(
413
- self, *, interface_id: str, device_address: str
414
- ) -> Mapping[ParamsetKey, list[str]]:
415
- """Get device channel addresses."""
416
- channel_addresses: dict[ParamsetKey, list[str]] = {}
417
- interface_paramset_descriptions = self._raw_paramset_descriptions[interface_id]
418
- for (
419
- channel_address,
420
- paramset_descriptions,
421
- ) in interface_paramset_descriptions.items():
422
- if channel_address.startswith(device_address):
423
- for p_key in paramset_descriptions:
424
- if (paramset_key := ParamsetKey(p_key)) not in channel_addresses:
425
- channel_addresses[paramset_key] = []
426
- channel_addresses[paramset_key].append(channel_address)
427
-
428
- return channel_addresses
429
-
430
- def _init_address_parameter_list(self) -> None:
431
- """
432
- Initialize a device_address/parameter list.
433
-
434
- Used to identify, if a parameter name exists is in multiple channels.
435
- """
436
- for channel_paramsets in self._raw_paramset_descriptions.values():
437
- for channel_address, paramsets in channel_paramsets.items():
438
- self._add_address_parameter(channel_address=channel_address, paramsets=list(paramsets.values()))
439
-
440
- def _add_address_parameter(self, *, channel_address: str, paramsets: list[dict[str, Any]]) -> None:
441
- """Add address parameter to cache."""
442
- device_address, channel_no = get_split_channel_address(channel_address=channel_address)
443
- cache = self._address_parameter_cache
444
- for paramset in paramsets:
445
- if not paramset:
446
- continue
447
- for parameter in paramset:
448
- cache.setdefault((device_address, parameter), set()).add(channel_no)
449
-
450
- async def load(self) -> DataOperationResult:
451
- """Load paramset descriptions from disk into paramset cache."""
452
- if not self._central.config.use_caches:
453
- _LOGGER.debug("load: not caching device descriptions for %s", self._central.name)
454
- return DataOperationResult.NO_LOAD
455
- if (result := await super().load()) == DataOperationResult.LOAD_SUCCESS:
456
- self._init_address_parameter_list()
457
- return result
458
-
459
- async def save(self) -> DataOperationResult:
460
- """Save current paramset descriptions to disk."""
461
- return await super().save()
462
-
463
-
464
- def _get_cache_path(*, storage_folder: str) -> str:
465
- """Return the cache path."""
466
- return f"{storage_folder}/{CACHE_PATH}"
467
-
468
-
469
- def _get_filename(*, central_name: str, file_name: str) -> str:
470
- """Return the cache filename."""
471
- return f"{slugify(central_name)}_{file_name}"
472
-
473
-
474
- def cleanup_cache_dirs(*, central_name: str, storage_folder: str) -> None:
475
- """Clean up the used cached directories."""
476
- cache_dir = _get_cache_path(storage_folder=storage_folder)
477
- for file_to_delete in (FILE_DEVICES, FILE_PARAMSETS):
478
- delete_file(folder=cache_dir, file_name=_get_filename(central_name=central_name, file_name=file_to_delete))
@@ -1,78 +0,0 @@
1
- aiohomematic/__init__.py,sha256=ngULK_anZQwwUUCVcberBdVjguYfboiuG9VoueKy9fA,2283
2
- aiohomematic/async_support.py,sha256=BeNKaDrFsRA5-_uAFzmyyKPqlImfSs58C22Nqd5dZAg,7887
3
- aiohomematic/const.py,sha256=0akDDWSDmPgfp-ynHluHHIKL3_1RamHINEec2jE9qC0,26643
4
- aiohomematic/context.py,sha256=M7gkA7KFT0dp35gzGz2dzKVXu1PP0sAnepgLlmjyRS4,451
5
- aiohomematic/converter.py,sha256=gaNHe-WEiBStZMuuRz9iGn3Mo_CGz1bjgLtlYBJJAko,3624
6
- aiohomematic/decorators.py,sha256=M4n_VSyqmsUgQQQv_-3JWQxYPbS6KEkhCS8OzAfaVKo,11060
7
- aiohomematic/exceptions.py,sha256=8Uu3rADawhYlAz6y4J52aJ-wKok8Z7YbUYUwWeGMKhs,5028
8
- aiohomematic/hmcli.py,sha256=qNstNDX6q8t3mJFCGlXlmRVobGabntrPtFi3kchf1Eg,4933
9
- aiohomematic/property_decorators.py,sha256=56lHGATgRtaFkIK_IXcR2tBW9mIVITcCwH5KOw575GA,17162
10
- aiohomematic/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
- aiohomematic/support.py,sha256=7FTIDvRZvGFMfN3i_zBnHtJQd-vDqTMTq2i1G5GmW3Y,22834
12
- aiohomematic/validator.py,sha256=HUikmo-SFksehFBAdZmBv4ajy0XkjgvXvcCfbexnzZo,3563
13
- aiohomematic/caches/__init__.py,sha256=_gI30tbsWgPRaHvP6cRxOQr6n9bYZzU-jp1WbHhWg-A,470
14
- aiohomematic/caches/dynamic.py,sha256=0hOu-WoYUc9_3fofMeg_OjlYS-quD4uTyDI6zd5W4Do,22553
15
- aiohomematic/caches/persistent.py,sha256=xUMjvu5Vthz9W0LLllSbcqTADZvVV025b4VnPzrPnis,20604
16
- aiohomematic/caches/visibility.py,sha256=8lTO-jfAUzd90atUOK8rKMrzRa__m083RAoEovg0Q0o,31676
17
- aiohomematic/central/__init__.py,sha256=_ft-2HXfn0pF_LTrNyV_mZ7cHkHuRgeprBJZx5MlK0I,92659
18
- aiohomematic/central/decorators.py,sha256=NUMSsQ_Or6gno4LzagrNMXeBtmbBbYyoIlMI0TFp1_E,6908
19
- aiohomematic/central/rpc_server.py,sha256=wf2KG-cj_wIdgfRHY3GIFFzOenJbz8MfUGLdF1drd3k,10971
20
- aiohomematic/client/__init__.py,sha256=w7ns0JZNroKNy9Yw1YM1ssxhPwXUoVNpPo5RLAbgK7E,73857
21
- aiohomematic/client/_rpc_errors.py,sha256=-NPtGvkQPJ4V2clDxv1tKy09M9JZm61pUCeki9DDh6s,2984
22
- aiohomematic/client/json_rpc.py,sha256=7p8j6uhS0y2LuJVtobQqwtpOA_AsC5HqEdGB0T8ZSu4,50177
23
- aiohomematic/client/rpc_proxy.py,sha256=v0YyhfQ_qylQpqGvGtylJtG3_tIk9PN6tWMHkki4D48,10705
24
- aiohomematic/model/__init__.py,sha256=KO7gas_eEzm67tODKqWTs0617CSGeKKjOWOlDbhRo_Q,5458
25
- aiohomematic/model/data_point.py,sha256=Ml8AOQ1RcRezTYWiGBlIXwcTLolQMX5Cyb-O7GtNDm4,41586
26
- aiohomematic/model/device.py,sha256=15z5G2X3jSJaj-yz7jX_tnirzipRIGBJPymObY3Dmjk,52942
27
- aiohomematic/model/event.py,sha256=82H8M_QNMCCC29mP3R16alJyKWS3Hb3aqY_aFMSqCvo,6874
28
- aiohomematic/model/support.py,sha256=l5E9Oon20nkGWOSEmbYtqQbpbh6-H4rIk8xtEtk5Fcg,19657
29
- aiohomematic/model/update.py,sha256=5F39xNz9B2GKJ8TvJHPMC-Wu97HfkiiMawjnHEYMnoA,5156
30
- aiohomematic/model/calculated/__init__.py,sha256=UGLePgKDH8JpLqjhPBgvBzjggI34omcaCPsc6tcM8Xs,2811
31
- aiohomematic/model/calculated/climate.py,sha256=GXBsC5tnrC_BvnFBkJ9KUqE7uVcGD1KTU_6-OleF5H4,8545
32
- aiohomematic/model/calculated/data_point.py,sha256=oTN8y3B9weh7CX3ZFiDyZFgvX77iUwge-acg49pd1sI,11609
33
- aiohomematic/model/calculated/operating_voltage_level.py,sha256=ZrOPdNoWQ5QLr4yzMRsoPG3UuJKRkBUHfchIrpKZU4o,13527
34
- aiohomematic/model/calculated/support.py,sha256=vOxTvWe8SBCwJpLzcVA8ibtfw4eP8yTUZj4Jt9hWt9k,6695
35
- aiohomematic/model/custom/__init__.py,sha256=UzczqjsUqWvS9ZaqKeb6elbjb2y5W3cgFPB0YQUHaeM,6095
36
- aiohomematic/model/custom/climate.py,sha256=zSLQUY_tU7tDlbM-vW15BGuyWRjcR_DyqOwSg1_Vmfw,57217
37
- aiohomematic/model/custom/const.py,sha256=Kh1pnab6nmwbaY43CfXQy3yrWpPwsrQdl1Ea2aZ6aw0,4961
38
- aiohomematic/model/custom/cover.py,sha256=hlIeQD0cZpq7X222J7ygm6kD4AE6h5IN-wcqzvZCLFA,29057
39
- aiohomematic/model/custom/data_point.py,sha256=l2pTz7Fu5jGCstXHK1cWCFfBWIJeKmtt37qdGLmrQhA,14155
40
- aiohomematic/model/custom/definition.py,sha256=9kSdqVOHQs65Q2Op5QknNQv5lLmZkZlGCUUCRGicOaw,35662
41
- aiohomematic/model/custom/light.py,sha256=2UxQOoupwTpQ-5iwY51gL_B815sgDXNW-HG-QhAFb9E,44448
42
- aiohomematic/model/custom/lock.py,sha256=ndzZ0hp7FBohw7T_qR0jPobwlcwxus9M1DuDu_7vfPw,11996
43
- aiohomematic/model/custom/siren.py,sha256=DT8RoOCl7FqstgRSBK-RWRcY4T29LuEdnlhaWCB6ATk,9785
44
- aiohomematic/model/custom/support.py,sha256=UvencsvCwgpm4iqRNRt5KRs560tyw1NhYP5ZaqmCT2k,1453
45
- aiohomematic/model/custom/switch.py,sha256=tIAd501_yqQB9dd1pcTTmF7tEhFqqj3gfcSgBYN_2_8,6963
46
- aiohomematic/model/custom/valve.py,sha256=u9RYzeJ8FNmpFO6amlLElXTQdAeqac5yo7NbZYS6Z9U,4242
47
- aiohomematic/model/generic/__init__.py,sha256=-ho8m9gFlORBGNPn2i8c9i5-GVLLFvTlf5FFpaTJbFw,7675
48
- aiohomematic/model/generic/action.py,sha256=niJPvTs43b9GiKomdBaBKwjOwtmNxR_YRhj5Fpje9NU,997
49
- aiohomematic/model/generic/binary_sensor.py,sha256=U5GvfRYbhwe0jRmaedD4LVZ_24SyyPbVr74HEfZXoxE,887
50
- aiohomematic/model/generic/button.py,sha256=6jZ49woI9gYJEx__PjguDNbc5vdE1P-YcLMZZFkYRCg,740
51
- aiohomematic/model/generic/data_point.py,sha256=2NvdU802JUo4NZh0v6oMI-pVtlNluSFse7ISMGqo70g,6084
52
- aiohomematic/model/generic/number.py,sha256=nJgOkMZwNfPtzBrX2o5RAjBt-o8KrKuqtDa9LBj0Jw0,2678
53
- aiohomematic/model/generic/select.py,sha256=vWfLUdQBjZLG-q-WZMxHk9Klawg_iNOEeSoVHrvG35I,1538
54
- aiohomematic/model/generic/sensor.py,sha256=wCnQ8IoC8uPTN29R250pfJa4x6y9sh4c3vxQ4Km8Clg,2262
55
- aiohomematic/model/generic/switch.py,sha256=VIMwIVok9kSRoSb-s5saYRHeiZcNWH4J5FyMSxUAbpw,1842
56
- aiohomematic/model/generic/text.py,sha256=vtNV7YxZuxF6LzNRKRAeOtSQtPQxPaEd560OFaVR13U,854
57
- aiohomematic/model/hub/__init__.py,sha256=g2m-5rba6SNCfGrlxwqYa8mlP5-N2obFAvyHJV8i4FY,13525
58
- aiohomematic/model/hub/binary_sensor.py,sha256=Z4o-zghHSc83ZHUUCtHqWEGueD9K1Fe0JEt_xJNdx_Y,752
59
- aiohomematic/model/hub/button.py,sha256=XMnoImnz5vDybxfrP4GWDp2M5gEMG71d8ba1YzVYAnE,890
60
- aiohomematic/model/hub/data_point.py,sha256=E6qn1gVhZ4meti-Tpd03f-YyfKnkUh-FpLbwBaa-d1c,10653
61
- aiohomematic/model/hub/number.py,sha256=12BK6mBOJn4aP7DWuWvMYfiaLmDX7ejd5tDCoHa2bUk,1237
62
- aiohomematic/model/hub/select.py,sha256=C9ke_8U_pzI0fctIeOZHwq-fvWj5s64jW6-DcZT-SrU,1674
63
- aiohomematic/model/hub/sensor.py,sha256=cIr-7bsbOLBxnH33EHQ6D42vc61abO4QYLWLzkm1T10,1192
64
- aiohomematic/model/hub/switch.py,sha256=510Vrlyak5TRsMvURrGYMkad-CbGUKWh20jjHQGOios,1390
65
- aiohomematic/model/hub/text.py,sha256=JOy-hfDSu95NbxP5JeGgga9NGrR8JLNmz_h4k8J1GXU,999
66
- aiohomematic/rega_scripts/fetch_all_device_data.fn,sha256=7uxhHoelAOsH6yYr1n1M1XwIRgDmItiHnWIMhDYEimk,4373
67
- aiohomematic/rega_scripts/get_program_descriptions.fn,sha256=pGmj377MkqbZi6j-UBKQAsXTphwh1kDwDKqXij8zUBE,835
68
- aiohomematic/rega_scripts/get_serial.fn,sha256=t1oeo-sB_EuVeiY24PLcxFSkdQVgEWGXzpemJQZFybY,1079
69
- aiohomematic/rega_scripts/get_system_variable_descriptions.fn,sha256=UKXvC0_5lSApdQ2atJc0E5Stj5Zt3lqh0EcliokYu2c,849
70
- aiohomematic/rega_scripts/set_program_state.fn,sha256=0bnv7lUj8FMjDZBz325tDVP61m04cHjVj4kIOnUUgpY,279
71
- aiohomematic/rega_scripts/set_system_variable.fn,sha256=sTmr7vkPTPnPkor5cnLKlDvfsYRbGO1iq2z_2pMXq5E,383
72
- aiohomematic-2025.10.7.dist-info/licenses/LICENSE,sha256=q-B0xpREuZuvKsmk3_iyVZqvZ-vJcWmzMZpeAd0RqtQ,1083
73
- aiohomematic_support/__init__.py,sha256=_0YtF4lTdC_k6-zrM2IefI0u0LMr_WA61gXAyeGLgbY,66
74
- aiohomematic_support/client_local.py,sha256=nFeYkoX_EXXIwbrpL_5peYQG-934D0ASN6kflYp0_4I,12819
75
- aiohomematic-2025.10.7.dist-info/METADATA,sha256=EP3Y37kiLdfx6DP4M988pY_gDoL3lKF_9LE3miXFxro,7603
76
- aiohomematic-2025.10.7.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
77
- aiohomematic-2025.10.7.dist-info/top_level.txt,sha256=5TDRlUWQPThIUwQjOj--aUo4UA-ow4m0sNhnoCBi5n8,34
78
- aiohomematic-2025.10.7.dist-info/RECORD,,