aiohomematic 2025.8.6__tar.gz → 2025.8.8__tar.gz

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 (106) hide show
  1. {aiohomematic-2025.8.6 → aiohomematic-2025.8.8}/LICENSE +1 -1
  2. {aiohomematic-2025.8.6 → aiohomematic-2025.8.8}/PKG-INFO +3 -3
  3. {aiohomematic-2025.8.6 → aiohomematic-2025.8.8}/README.md +2 -2
  4. {aiohomematic-2025.8.6 → aiohomematic-2025.8.8}/aiohomematic/caches/dynamic.py +8 -12
  5. {aiohomematic-2025.8.6 → aiohomematic-2025.8.8}/aiohomematic/const.py +1 -1
  6. {aiohomematic-2025.8.6 → aiohomematic-2025.8.8}/aiohomematic/model/calculated/__init__.py +3 -3
  7. {aiohomematic-2025.8.6 → aiohomematic-2025.8.8}/aiohomematic/model/data_point.py +1 -3
  8. {aiohomematic-2025.8.6 → aiohomematic-2025.8.8}/aiohomematic/model/device.py +34 -59
  9. {aiohomematic-2025.8.6 → aiohomematic-2025.8.8}/aiohomematic/model/hub/__init__.py +5 -9
  10. aiohomematic-2025.8.8/aiohomematic/rega_scripts/fetch_all_device_data.fn +92 -0
  11. {aiohomematic-2025.8.6 → aiohomematic-2025.8.8}/aiohomematic.egg-info/PKG-INFO +3 -3
  12. {aiohomematic-2025.8.6 → aiohomematic-2025.8.8}/aiohomematic.egg-info/top_level.txt +0 -1
  13. aiohomematic-2025.8.6/aiohomematic/rega_scripts/fetch_all_device_data.fn +0 -75
  14. {aiohomematic-2025.8.6 → aiohomematic-2025.8.8}/aiohomematic/__init__.py +0 -0
  15. {aiohomematic-2025.8.6 → aiohomematic-2025.8.8}/aiohomematic/async_support.py +0 -0
  16. {aiohomematic-2025.8.6 → aiohomematic-2025.8.8}/aiohomematic/caches/__init__.py +0 -0
  17. {aiohomematic-2025.8.6 → aiohomematic-2025.8.8}/aiohomematic/caches/persistent.py +0 -0
  18. {aiohomematic-2025.8.6 → aiohomematic-2025.8.8}/aiohomematic/caches/visibility.py +0 -0
  19. {aiohomematic-2025.8.6 → aiohomematic-2025.8.8}/aiohomematic/central/__init__.py +0 -0
  20. {aiohomematic-2025.8.6 → aiohomematic-2025.8.8}/aiohomematic/central/decorators.py +0 -0
  21. {aiohomematic-2025.8.6 → aiohomematic-2025.8.8}/aiohomematic/central/xml_rpc_server.py +0 -0
  22. {aiohomematic-2025.8.6 → aiohomematic-2025.8.8}/aiohomematic/client/__init__.py +0 -0
  23. {aiohomematic-2025.8.6 → aiohomematic-2025.8.8}/aiohomematic/client/json_rpc.py +0 -0
  24. {aiohomematic-2025.8.6 → aiohomematic-2025.8.8}/aiohomematic/client/xml_rpc.py +0 -0
  25. {aiohomematic-2025.8.6 → aiohomematic-2025.8.8}/aiohomematic/context.py +0 -0
  26. {aiohomematic-2025.8.6 → aiohomematic-2025.8.8}/aiohomematic/converter.py +0 -0
  27. {aiohomematic-2025.8.6 → aiohomematic-2025.8.8}/aiohomematic/decorators.py +0 -0
  28. {aiohomematic-2025.8.6 → aiohomematic-2025.8.8}/aiohomematic/exceptions.py +0 -0
  29. {aiohomematic-2025.8.6 → aiohomematic-2025.8.8}/aiohomematic/hmcli.py +0 -0
  30. {aiohomematic-2025.8.6 → aiohomematic-2025.8.8}/aiohomematic/model/__init__.py +0 -0
  31. {aiohomematic-2025.8.6 → aiohomematic-2025.8.8}/aiohomematic/model/calculated/climate.py +0 -0
  32. {aiohomematic-2025.8.6 → aiohomematic-2025.8.8}/aiohomematic/model/calculated/data_point.py +0 -0
  33. {aiohomematic-2025.8.6 → aiohomematic-2025.8.8}/aiohomematic/model/calculated/operating_voltage_level.py +0 -0
  34. {aiohomematic-2025.8.6 → aiohomematic-2025.8.8}/aiohomematic/model/calculated/support.py +0 -0
  35. {aiohomematic-2025.8.6 → aiohomematic-2025.8.8}/aiohomematic/model/custom/__init__.py +0 -0
  36. {aiohomematic-2025.8.6 → aiohomematic-2025.8.8}/aiohomematic/model/custom/climate.py +0 -0
  37. {aiohomematic-2025.8.6 → aiohomematic-2025.8.8}/aiohomematic/model/custom/const.py +0 -0
  38. {aiohomematic-2025.8.6 → aiohomematic-2025.8.8}/aiohomematic/model/custom/cover.py +0 -0
  39. {aiohomematic-2025.8.6 → aiohomematic-2025.8.8}/aiohomematic/model/custom/data_point.py +0 -0
  40. {aiohomematic-2025.8.6 → aiohomematic-2025.8.8}/aiohomematic/model/custom/definition.py +0 -0
  41. {aiohomematic-2025.8.6 → aiohomematic-2025.8.8}/aiohomematic/model/custom/light.py +0 -0
  42. {aiohomematic-2025.8.6 → aiohomematic-2025.8.8}/aiohomematic/model/custom/lock.py +0 -0
  43. {aiohomematic-2025.8.6 → aiohomematic-2025.8.8}/aiohomematic/model/custom/siren.py +0 -0
  44. {aiohomematic-2025.8.6 → aiohomematic-2025.8.8}/aiohomematic/model/custom/support.py +0 -0
  45. {aiohomematic-2025.8.6 → aiohomematic-2025.8.8}/aiohomematic/model/custom/switch.py +0 -0
  46. {aiohomematic-2025.8.6 → aiohomematic-2025.8.8}/aiohomematic/model/custom/valve.py +0 -0
  47. {aiohomematic-2025.8.6 → aiohomematic-2025.8.8}/aiohomematic/model/decorators.py +0 -0
  48. {aiohomematic-2025.8.6 → aiohomematic-2025.8.8}/aiohomematic/model/event.py +0 -0
  49. {aiohomematic-2025.8.6 → aiohomematic-2025.8.8}/aiohomematic/model/generic/__init__.py +0 -0
  50. {aiohomematic-2025.8.6 → aiohomematic-2025.8.8}/aiohomematic/model/generic/action.py +0 -0
  51. {aiohomematic-2025.8.6 → aiohomematic-2025.8.8}/aiohomematic/model/generic/binary_sensor.py +0 -0
  52. {aiohomematic-2025.8.6 → aiohomematic-2025.8.8}/aiohomematic/model/generic/button.py +0 -0
  53. {aiohomematic-2025.8.6 → aiohomematic-2025.8.8}/aiohomematic/model/generic/data_point.py +0 -0
  54. {aiohomematic-2025.8.6 → aiohomematic-2025.8.8}/aiohomematic/model/generic/number.py +0 -0
  55. {aiohomematic-2025.8.6 → aiohomematic-2025.8.8}/aiohomematic/model/generic/select.py +0 -0
  56. {aiohomematic-2025.8.6 → aiohomematic-2025.8.8}/aiohomematic/model/generic/sensor.py +0 -0
  57. {aiohomematic-2025.8.6 → aiohomematic-2025.8.8}/aiohomematic/model/generic/switch.py +0 -0
  58. {aiohomematic-2025.8.6 → aiohomematic-2025.8.8}/aiohomematic/model/generic/text.py +0 -0
  59. {aiohomematic-2025.8.6 → aiohomematic-2025.8.8}/aiohomematic/model/hub/binary_sensor.py +0 -0
  60. {aiohomematic-2025.8.6 → aiohomematic-2025.8.8}/aiohomematic/model/hub/button.py +0 -0
  61. {aiohomematic-2025.8.6 → aiohomematic-2025.8.8}/aiohomematic/model/hub/data_point.py +0 -0
  62. {aiohomematic-2025.8.6 → aiohomematic-2025.8.8}/aiohomematic/model/hub/number.py +0 -0
  63. {aiohomematic-2025.8.6 → aiohomematic-2025.8.8}/aiohomematic/model/hub/select.py +0 -0
  64. {aiohomematic-2025.8.6 → aiohomematic-2025.8.8}/aiohomematic/model/hub/sensor.py +0 -0
  65. {aiohomematic-2025.8.6 → aiohomematic-2025.8.8}/aiohomematic/model/hub/switch.py +0 -0
  66. {aiohomematic-2025.8.6 → aiohomematic-2025.8.8}/aiohomematic/model/hub/text.py +0 -0
  67. {aiohomematic-2025.8.6 → aiohomematic-2025.8.8}/aiohomematic/model/support.py +0 -0
  68. {aiohomematic-2025.8.6 → aiohomematic-2025.8.8}/aiohomematic/model/update.py +0 -0
  69. {aiohomematic-2025.8.6 → aiohomematic-2025.8.8}/aiohomematic/py.typed +0 -0
  70. {aiohomematic-2025.8.6 → aiohomematic-2025.8.8}/aiohomematic/rega_scripts/get_program_descriptions.fn +0 -0
  71. {aiohomematic-2025.8.6 → aiohomematic-2025.8.8}/aiohomematic/rega_scripts/get_serial.fn +0 -0
  72. {aiohomematic-2025.8.6 → aiohomematic-2025.8.8}/aiohomematic/rega_scripts/get_system_variable_descriptions.fn +0 -0
  73. {aiohomematic-2025.8.6 → aiohomematic-2025.8.8}/aiohomematic/rega_scripts/set_program_state.fn +0 -0
  74. {aiohomematic-2025.8.6 → aiohomematic-2025.8.8}/aiohomematic/rega_scripts/set_system_variable.fn +0 -0
  75. {aiohomematic-2025.8.6 → aiohomematic-2025.8.8}/aiohomematic/support.py +0 -0
  76. {aiohomematic-2025.8.6 → aiohomematic-2025.8.8}/aiohomematic/validator.py +0 -0
  77. {aiohomematic-2025.8.6 → aiohomematic-2025.8.8}/aiohomematic.egg-info/SOURCES.txt +0 -0
  78. {aiohomematic-2025.8.6 → aiohomematic-2025.8.8}/aiohomematic.egg-info/dependency_links.txt +0 -0
  79. {aiohomematic-2025.8.6 → aiohomematic-2025.8.8}/aiohomematic.egg-info/requires.txt +0 -0
  80. {aiohomematic-2025.8.6 → aiohomematic-2025.8.8}/aiohomematic_support/__init__.py +0 -0
  81. {aiohomematic-2025.8.6 → aiohomematic-2025.8.8}/aiohomematic_support/client_local.py +0 -0
  82. {aiohomematic-2025.8.6 → aiohomematic-2025.8.8}/pyproject.toml +0 -0
  83. {aiohomematic-2025.8.6 → aiohomematic-2025.8.8}/setup.cfg +0 -0
  84. {aiohomematic-2025.8.6 → aiohomematic-2025.8.8}/tests/test_action.py +0 -0
  85. {aiohomematic-2025.8.6 → aiohomematic-2025.8.8}/tests/test_binary_sensor.py +0 -0
  86. {aiohomematic-2025.8.6 → aiohomematic-2025.8.8}/tests/test_button.py +0 -0
  87. {aiohomematic-2025.8.6 → aiohomematic-2025.8.8}/tests/test_calculated_support.py +0 -0
  88. {aiohomematic-2025.8.6 → aiohomematic-2025.8.8}/tests/test_central.py +0 -0
  89. {aiohomematic-2025.8.6 → aiohomematic-2025.8.8}/tests/test_central_pydevccu.py +0 -0
  90. {aiohomematic-2025.8.6 → aiohomematic-2025.8.8}/tests/test_climate.py +0 -0
  91. {aiohomematic-2025.8.6 → aiohomematic-2025.8.8}/tests/test_cover.py +0 -0
  92. {aiohomematic-2025.8.6 → aiohomematic-2025.8.8}/tests/test_decorator.py +0 -0
  93. {aiohomematic-2025.8.6 → aiohomematic-2025.8.8}/tests/test_device.py +0 -0
  94. {aiohomematic-2025.8.6 → aiohomematic-2025.8.8}/tests/test_entity.py +0 -0
  95. {aiohomematic-2025.8.6 → aiohomematic-2025.8.8}/tests/test_event.py +0 -0
  96. {aiohomematic-2025.8.6 → aiohomematic-2025.8.8}/tests/test_json_rpc.py +0 -0
  97. {aiohomematic-2025.8.6 → aiohomematic-2025.8.8}/tests/test_light.py +0 -0
  98. {aiohomematic-2025.8.6 → aiohomematic-2025.8.8}/tests/test_lock.py +0 -0
  99. {aiohomematic-2025.8.6 → aiohomematic-2025.8.8}/tests/test_number.py +0 -0
  100. {aiohomematic-2025.8.6 → aiohomematic-2025.8.8}/tests/test_select.py +0 -0
  101. {aiohomematic-2025.8.6 → aiohomematic-2025.8.8}/tests/test_sensor.py +0 -0
  102. {aiohomematic-2025.8.6 → aiohomematic-2025.8.8}/tests/test_siren.py +0 -0
  103. {aiohomematic-2025.8.6 → aiohomematic-2025.8.8}/tests/test_support.py +0 -0
  104. {aiohomematic-2025.8.6 → aiohomematic-2025.8.8}/tests/test_switch.py +0 -0
  105. {aiohomematic-2025.8.6 → aiohomematic-2025.8.8}/tests/test_text.py +0 -0
  106. {aiohomematic-2025.8.6 → aiohomematic-2025.8.8}/tests/test_valve.py +0 -0
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2021 Daniel Perna
3
+ Copyright (c) 2021 Daniel Perna, SukramJ
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aiohomematic
3
- Version: 2025.8.6
3
+ Version: 2025.8.8
4
4
  Summary: Homematic interface for Home Assistant running on Python 3.
5
5
  Home-page: https://github.com/sukramj/aiohomematic
6
6
  Author-email: SukramJ <sukramj@icloud.com>, Daniel Perna <danielperna84@gmail.com>
@@ -49,9 +49,9 @@ Unlike pyhomematic, which required manual device mappings, aiohomematic automati
49
49
 
50
50
  ## Installation (with Home Assistant)
51
51
 
52
- Install via the custom component: [custom_homematic](https://github.com/sukramj/custom_homematic).
52
+ Install via the custom component: [Homematic(IP) Local](https://github.com/sukramj/homematicip_local).
53
53
 
54
- Follow the installation guide: https://github.com/sukramj/custom_homematic/wiki/Installation
54
+ Follow the installation guide: https://github.com/sukramj/homematicip_local/wiki/Installation
55
55
 
56
56
  ## Requirements
57
57
 
@@ -21,9 +21,9 @@ Unlike pyhomematic, which required manual device mappings, aiohomematic automati
21
21
 
22
22
  ## Installation (with Home Assistant)
23
23
 
24
- Install via the custom component: [custom_homematic](https://github.com/sukramj/custom_homematic).
24
+ Install via the custom component: [Homematic(IP) Local](https://github.com/sukramj/homematicip_local).
25
25
 
26
- Follow the installation guide: https://github.com/sukramj/custom_homematic/wiki/Installation
26
+ Follow the installation guide: https://github.com/sukramj/homematicip_local/wiki/Installation
27
27
 
28
28
  ## Requirements
29
29
 
@@ -25,11 +25,13 @@ from collections.abc import Mapping
25
25
  from datetime import datetime
26
26
  import logging
27
27
  from typing import Any, Final, cast
28
+ from urllib.parse import unquote
28
29
 
29
30
  from aiohomematic import central as hmcu
30
31
  from aiohomematic.const import (
31
32
  DP_KEY_VALUE,
32
33
  INIT_DATETIME,
34
+ ISO_8859_1,
33
35
  LAST_COMMAND_SEND_STORE_TIMEOUT,
34
36
  MAX_CACHE_AGE,
35
37
  NO_CACHE_ENTRY,
@@ -272,7 +274,6 @@ class CentralDataCache:
272
274
 
273
275
  __slots__ = (
274
276
  "_central",
275
- "_escaped_channel_cache",
276
277
  "_refreshed_at",
277
278
  "_value_cache",
278
279
  )
@@ -283,7 +284,6 @@ class CentralDataCache:
283
284
  # { key, value}
284
285
  self._value_cache: Final[dict[Interface, Mapping[str, Any]]] = {}
285
286
  self._refreshed_at: Final[dict[Interface, datetime]] = {}
286
- self._escaped_channel_cache: Final[dict[str, str]] = {}
287
287
 
288
288
  async def load(self, direct_call: bool = False, interface: Interface | None = None) -> None:
289
289
  """Fetch data from backend."""
@@ -310,7 +310,10 @@ class CentralDataCache:
310
310
 
311
311
  def add_data(self, interface: Interface, all_device_data: Mapping[str, Any]) -> None:
312
312
  """Add data to cache."""
313
- self._value_cache[interface] = all_device_data
313
+ self._value_cache[interface] = {
314
+ unquote(string=k, encoding=ISO_8859_1): unquote(string=v, encoding=ISO_8859_1) if isinstance(v, str) else v
315
+ for k, v in all_device_data.items()
316
+ }
314
317
  self._refreshed_at[interface] = datetime.now()
315
318
 
316
319
  def get_data(
@@ -320,14 +323,8 @@ class CentralDataCache:
320
323
  parameter: str,
321
324
  ) -> Any:
322
325
  """Get data from cache."""
323
- if not self._is_empty(interface=interface):
324
- # Escape channel address only once per unique address
325
- if (escaped := self._escaped_channel_cache.get(channel_address)) is None:
326
- escaped = channel_address.replace(":", "%3A") if ":" in channel_address else channel_address
327
- self._escaped_channel_cache[channel_address] = escaped
328
- key = f"{interface}.{escaped}.{parameter}"
329
- if (iface_cache := self._value_cache.get(interface)) is not None:
330
- return iface_cache.get(key, NO_CACHE_ENTRY)
326
+ if not self._is_empty(interface=interface) and (iface_cache := self._value_cache.get(interface)) is not None:
327
+ return iface_cache.get(f"{interface}.{channel_address}.{parameter}", NO_CACHE_ENTRY)
331
328
  return NO_CACHE_ENTRY
332
329
 
333
330
  def clear(self, interface: Interface | None = None) -> None:
@@ -335,7 +332,6 @@ class CentralDataCache:
335
332
  if interface:
336
333
  self._value_cache[interface] = {}
337
334
  self._refreshed_at[interface] = INIT_DATETIME
338
- self._escaped_channel_cache.clear()
339
335
  else:
340
336
  for _interface in self._central.interfaces:
341
337
  self.clear(interface=_interface)
@@ -11,7 +11,7 @@ import re
11
11
  import sys
12
12
  from typing import Any, Final, NamedTuple, Required, TypedDict
13
13
 
14
- VERSION: Final = "2025.8.6"
14
+ VERSION: Final = "2025.8.8"
15
15
 
16
16
  # Detect test speedup mode via environment
17
17
  _TEST_SPEEDUP: Final = (
@@ -60,6 +60,6 @@ _LOGGER: Final = logging.getLogger(__name__)
60
60
  @inspector()
61
61
  def create_calculated_data_points(channel: hmd.Channel) -> None:
62
62
  """Decides which data point category should be used, and creates the required data points."""
63
- for cdp in _CALCULATED_DATA_POINTS:
64
- if cdp.is_relevant_for_model(channel=channel):
65
- channel.add_data_point(data_point=cdp(channel=channel))
63
+ for dp in _CALCULATED_DATA_POINTS:
64
+ if dp.is_relevant_for_model(channel=channel):
65
+ channel.add_data_point(data_point=dp(channel=channel))
@@ -861,9 +861,7 @@ class BaseParameterDataPoint[
861
861
 
862
862
  self.write_value(
863
863
  value=await self._device.value_cache.get_value(
864
- channel_address=self._channel.address,
865
- paramset_key=self._paramset_key,
866
- parameter=self._parameter,
864
+ dpk=self.dpk,
867
865
  call_source=call_source,
868
866
  direct_call=direct_call,
869
867
  ),
@@ -371,6 +371,8 @@ class Device(PayloadMixin):
371
371
  """Return the room of the device, if only one assigned in CCU."""
372
372
  if self._rooms and len(self._rooms) == 1:
373
373
  return list(self._rooms)[0]
374
+ if (maintenance_channel := self.get_channel(channel_address=f"{self._address}:0")) is not None:
375
+ return maintenance_channel.room
374
376
  return None
375
377
 
376
378
  @property
@@ -857,6 +859,10 @@ class Channel(PayloadMixin):
857
859
  """Return the room of the device, if only one assigned in CCU."""
858
860
  if self._rooms and len(self._rooms) == 1:
859
861
  return list(self._rooms)[0]
862
+ if self.is_group_master:
863
+ return None
864
+ if (master_channel := self.group_master) is not None:
865
+ return master_channel.room
860
866
  return None
861
867
 
862
868
  @property
@@ -1140,121 +1146,90 @@ class _ValueCache:
1140
1146
 
1141
1147
  async def get_value(
1142
1148
  self,
1143
- channel_address: str,
1144
- paramset_key: ParamsetKey,
1145
- parameter: str,
1149
+ dpk: DataPointKey,
1146
1150
  call_source: CallSource,
1147
1151
  direct_call: bool = False,
1148
1152
  ) -> Any:
1149
1153
  """Load data."""
1154
+
1150
1155
  async with self._sema_get_or_load_value:
1151
- if (
1152
- direct_call is False
1153
- and (
1154
- cached_value := self._get_value_from_cache(
1155
- channel_address=channel_address,
1156
- paramset_key=paramset_key,
1157
- parameter=parameter,
1158
- )
1159
- )
1160
- != NO_CACHE_ENTRY
1161
- ):
1156
+ if direct_call is False and (cached_value := self._get_value_from_cache(dpk=dpk)) != NO_CACHE_ENTRY:
1162
1157
  return NO_CACHE_ENTRY if cached_value == self._NO_VALUE_CACHE_ENTRY else cached_value
1163
1158
 
1164
- value_dict: dict[str, Any] = {parameter: self._NO_VALUE_CACHE_ENTRY}
1159
+ value_dict: dict[str, Any] = {dpk.parameter: self._NO_VALUE_CACHE_ENTRY}
1165
1160
  try:
1166
- value_dict = await self._get_values_for_cache(
1167
- channel_address=channel_address,
1168
- paramset_key=paramset_key,
1169
- parameter=parameter,
1170
- )
1161
+ value_dict = await self._get_values_for_cache(dpk=dpk)
1171
1162
  except BaseHomematicException as bhexc:
1172
1163
  _LOGGER.debug(
1173
1164
  "GET_OR_LOAD_VALUE: Failed to get data for %s, %s, %s, %s: %s",
1174
1165
  self._device.model,
1175
- channel_address,
1176
- parameter,
1166
+ dpk.channel_address,
1167
+ dpk.parameter,
1177
1168
  call_source,
1178
1169
  extract_exc_args(exc=bhexc),
1179
1170
  )
1180
1171
  for d_parameter, d_value in value_dict.items():
1181
1172
  self._add_entry_to_device_cache(
1182
- channel_address=channel_address,
1183
- paramset_key=paramset_key,
1184
- parameter=d_parameter,
1173
+ dpk=DataPointKey(
1174
+ interface_id=dpk.interface_id,
1175
+ channel_address=dpk.channel_address,
1176
+ paramset_key=dpk.paramset_key,
1177
+ parameter=d_parameter,
1178
+ ),
1185
1179
  value=d_value,
1186
1180
  )
1187
1181
  return (
1188
1182
  NO_CACHE_ENTRY
1189
- if (value := value_dict.get(parameter)) and value == self._NO_VALUE_CACHE_ENTRY
1183
+ if (value := value_dict.get(dpk.parameter)) and value == self._NO_VALUE_CACHE_ENTRY
1190
1184
  else value
1191
1185
  )
1192
1186
 
1193
- async def _get_values_for_cache(
1194
- self, channel_address: str, paramset_key: ParamsetKey, parameter: str
1195
- ) -> dict[str, Any]:
1187
+ async def _get_values_for_cache(self, dpk: DataPointKey) -> dict[str, Any]:
1196
1188
  """Return a value from CCU to store in cache."""
1197
1189
  if not self._device.available:
1198
1190
  _LOGGER.debug(
1199
1191
  "GET_VALUES_FOR_CACHE failed: device %s (%s) is not available", self._device.name, self._device.address
1200
1192
  )
1201
1193
  return {}
1202
- if paramset_key == ParamsetKey.VALUES:
1194
+ if dpk.paramset_key == ParamsetKey.VALUES:
1203
1195
  return {
1204
- parameter: await self._device.client.get_value(
1205
- channel_address=channel_address,
1206
- paramset_key=paramset_key,
1207
- parameter=parameter,
1196
+ dpk.parameter: await self._device.client.get_value(
1197
+ channel_address=dpk.channel_address,
1198
+ paramset_key=dpk.paramset_key,
1199
+ parameter=dpk.parameter,
1208
1200
  call_source=CallSource.HM_INIT,
1209
1201
  )
1210
1202
  }
1211
1203
  return await self._device.client.get_paramset(
1212
- address=channel_address, paramset_key=paramset_key, call_source=CallSource.HM_INIT
1204
+ address=dpk.channel_address, paramset_key=dpk.paramset_key, call_source=CallSource.HM_INIT
1213
1205
  )
1214
1206
 
1215
- def _add_entry_to_device_cache(
1216
- self, channel_address: str, paramset_key: ParamsetKey, parameter: str, value: Any
1217
- ) -> None:
1207
+ def _add_entry_to_device_cache(self, dpk: DataPointKey, value: Any) -> None:
1218
1208
  """Add value to cache."""
1219
- key = DataPointKey(
1220
- interface_id=self._device.interface_id,
1221
- channel_address=channel_address,
1222
- paramset_key=paramset_key,
1223
- parameter=parameter,
1224
- )
1225
1209
  # write value to cache even if an exception has occurred
1226
1210
  # to avoid repetitive calls to CCU within max_age
1227
- self._device_cache[key] = CacheEntry(value=value, refresh_at=datetime.now())
1211
+ self._device_cache[dpk] = CacheEntry(value=value, refresh_at=datetime.now())
1228
1212
 
1229
1213
  def _get_value_from_cache(
1230
1214
  self,
1231
- channel_address: str,
1232
- paramset_key: ParamsetKey,
1233
- parameter: str,
1215
+ dpk: DataPointKey,
1234
1216
  ) -> Any:
1235
1217
  """Load data from caches."""
1236
1218
  # Try to get data from central cache
1237
1219
  if (
1238
- paramset_key == ParamsetKey.VALUES
1220
+ dpk.paramset_key == ParamsetKey.VALUES
1239
1221
  and (
1240
1222
  global_value := self._device.central.data_cache.get_data(
1241
1223
  interface=self._device.interface,
1242
- channel_address=channel_address,
1243
- parameter=parameter,
1224
+ channel_address=dpk.channel_address,
1225
+ parameter=dpk.parameter,
1244
1226
  )
1245
1227
  )
1246
1228
  != NO_CACHE_ENTRY
1247
1229
  ):
1248
1230
  return global_value
1249
1231
 
1250
- # Try to get data from device cache
1251
- key = DataPointKey(
1252
- interface_id=self._device.interface_id,
1253
- channel_address=channel_address,
1254
- paramset_key=paramset_key,
1255
- parameter=parameter,
1256
- )
1257
- if (cache_entry := self._device_cache.get(key, CacheEntry.empty())) and cache_entry.is_valid:
1232
+ if (cache_entry := self._device_cache.get(dpk, CacheEntry.empty())) and cache_entry.is_valid:
1258
1233
  return cache_entry.value
1259
1234
  return NO_CACHE_ENTRY
1260
1235
 
@@ -289,21 +289,17 @@ class Hub:
289
289
 
290
290
  def _identify_missing_program_ids(self, programs: tuple[ProgramData, ...]) -> set[str]:
291
291
  """Identify missing programs."""
292
- return {
293
- program_dp.pid
294
- for program_dp in self._central.program_data_points
295
- if program_dp.pid not in [x.pid for x in programs]
296
- }
292
+ return {dp.pid for dp in self._central.program_data_points if dp.pid not in [x.pid for x in programs]}
297
293
 
298
294
  def _identify_missing_variable_ids(self, variables: tuple[SystemVariableData, ...]) -> set[str]:
299
295
  """Identify missing variables."""
300
296
  variable_ids: dict[str, bool] = {x.vid: x.extended_sysvar for x in variables}
301
297
  missing_variable_ids: list[str] = []
302
- for svdp in self._central.sysvar_data_points:
303
- if svdp.data_type == SysvarType.STRING:
298
+ for dp in self._central.sysvar_data_points:
299
+ if dp.data_type == SysvarType.STRING:
304
300
  continue
305
- if (vid := svdp.vid) is not None and (
306
- vid not in variable_ids or (svdp.is_extended is not variable_ids.get(vid))
301
+ if (vid := dp.vid) is not None and (
302
+ vid not in variable_ids or (dp.is_extended is not variable_ids.get(vid))
307
303
  ):
308
304
  missing_variable_ids.append(vid)
309
305
  return set(missing_variable_ids)
@@ -0,0 +1,92 @@
1
+ !# fetch_all_device_data.fn v2.2
2
+ !# This script fetches all device data required to initialize the entities without affecting the duty cycle.
3
+ !#
4
+ !# Original script: https://github.com/ioBroker/ioBroker.hm-rega/blob/master/regascripts/datapoints.fn
5
+ !# datapoints.fn 1.9
6
+ !# 3'2013-9'2014 hobbyquaker https://github.com/hobbyquaker
7
+ !#
8
+ !# Dieses Homematic-Script gibt eine Liste aller Datenpunkte, die zur Laufzeit einen validen Zeitstempel haben, als JSON String aus.
9
+ !#
10
+ !# modified by: SukramJ https://github.com/SukramJ && Baxxy13 https://github.com/Baxxy13
11
+ !# v2.2 - 09/2023
12
+ !#
13
+ !# Das Interface wird durch die Integration an 'sUse_Interface' übergeben.
14
+ !# Nutzbare Interfaces: BidCos-RF, BidCos-Wired, HmIP-RF, VirtualDevices
15
+ !# Zum Testen direkt auf der Homematic-Zentrale muss das Interface wie folgt eingetragen werden: sUse_Interface = "HmIP-RF";
16
+
17
+ string sUse_Interface = "##interface##";
18
+ string sDevId;
19
+ string sChnId;
20
+ string sDPId;
21
+ var vDPValue;
22
+ boolean bDPFirst = true;
23
+ object oInterface = interfaces.Get(sUse_Interface);
24
+
25
+ Write('{');
26
+ if (oInterface) {
27
+ integer iInterface_ID = interfaces.Get(sUse_Interface).ID();
28
+ string sAllDevices = dom.GetObject(ID_DEVICES).EnumUsedIDs();
29
+ foreach (sDevId, sAllDevices) {
30
+ object oDevice = dom.GetObject(sDevId);
31
+ if ((oDevice) && (oDevice.ReadyConfig()) && (oDevice.Interface() == iInterface_ID)) {
32
+ foreach (sChnId, oDevice.Channels()) {
33
+ object oChannel = dom.GetObject(sChnId);
34
+ if (oChannel) {
35
+ var oDPs = oChannel.DPs();
36
+ if (oDPs) {
37
+ foreach(sDPId, oDPs.EnumUsedIDs()) {
38
+ object oDP = dom.GetObject(sDPId);
39
+ if (oDP && oDP.Timestamp()) {
40
+ if (oDP.TypeName() != "VARDP") {
41
+ integer sValueType = oDP.ValueType();
42
+ boolean bHasValue = false;
43
+ string sValue;
44
+ string sID = oDP.Name().UriEncode();
45
+ if (sValueType == 20) {
46
+ sValue = oDP.Value().UriEncode();
47
+ bHasValue = true;
48
+ } else {
49
+ vDPValue = oDP.Value();
50
+ if (sValueType == 2) {
51
+ if (vDPValue) {
52
+ sValue = "true";
53
+ } else {
54
+ sValue = "false";
55
+ }
56
+ bHasValue = true;
57
+ } else {
58
+ if (vDPValue == "") {
59
+ sValue = "0";
60
+ } else {
61
+ sValue = vDPValue;
62
+ }
63
+ bHasValue = true;
64
+ }
65
+ }
66
+ if (bHasValue) {
67
+ if (bDPFirst) {
68
+ bDPFirst = false;
69
+ } else {
70
+ WriteLine(',');
71
+ }
72
+ Write('"');
73
+ Write(sID);
74
+ Write('":');
75
+ if (sValueType == 20) {
76
+ Write('"');
77
+ Write(sValue);
78
+ Write('"');
79
+ } else {
80
+ Write(sValue);
81
+ }
82
+ }
83
+ }
84
+ }
85
+ }
86
+ }
87
+ }
88
+ }
89
+ }
90
+ }
91
+ }
92
+ Write('}');
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aiohomematic
3
- Version: 2025.8.6
3
+ Version: 2025.8.8
4
4
  Summary: Homematic interface for Home Assistant running on Python 3.
5
5
  Home-page: https://github.com/sukramj/aiohomematic
6
6
  Author-email: SukramJ <sukramj@icloud.com>, Daniel Perna <danielperna84@gmail.com>
@@ -49,9 +49,9 @@ Unlike pyhomematic, which required manual device mappings, aiohomematic automati
49
49
 
50
50
  ## Installation (with Home Assistant)
51
51
 
52
- Install via the custom component: [custom_homematic](https://github.com/sukramj/custom_homematic).
52
+ Install via the custom component: [Homematic(IP) Local](https://github.com/sukramj/homematicip_local).
53
53
 
54
- Follow the installation guide: https://github.com/sukramj/custom_homematic/wiki/Installation
54
+ Follow the installation guide: https://github.com/sukramj/homematicip_local/wiki/Installation
55
55
 
56
56
  ## Requirements
57
57
 
@@ -1,3 +1,2 @@
1
1
  aiohomematic
2
- aiohomematic_storage
3
2
  aiohomematic_support
@@ -1,75 +0,0 @@
1
- !# fetch_all_device_data.fn v2.2
2
- !# This script fetches all device data required to initialize the entities without affecting the duty cycle.
3
- !#
4
- !# Original script: https://github.com/ioBroker/ioBroker.hm-rega/blob/master/regascripts/datapoints.fn
5
- !# datapoints.fn 1.9
6
- !# 3'2013-9'2014 hobbyquaker https://github.com/hobbyquaker
7
- !#
8
- !# Dieses Homematic-Script gibt eine Liste aller Datenpunkte, die zur Laufzeit einen validen Zeitstempel haben, als JSON String aus.
9
- !#
10
- !# modified by: SukramJ https://github.com/SukramJ && Baxxy13 https://github.com/Baxxy13
11
- !# v2.2 - 09/2023
12
- !#
13
- !# Das Interface wird durch die Integration an 'sUse_Interface' übergeben.
14
- !# Nutzbare Interfaces: BidCos-RF, BidCos-Wired, HmIP-RF, VirtualDevices
15
- !# Zum Testen direkt auf der Homematic-Zentrale muss das Interface wie folgt eingetragen werden: sUse_Interface = "HmIP-RF";
16
-
17
- string sUse_Interface = "##interface##";
18
- string sDevId;
19
- string sChnId;
20
- string sDPId;
21
- string sDPId;
22
- var vDPValue;
23
- boolean bDPFirst = true;
24
- object oInterface = interfaces.Get(sUse_Interface);
25
-
26
- Write('{');
27
- if (oInterface) {
28
- integer iInterface_ID = interfaces.Get(sUse_Interface).ID();
29
- string sAllDevices = dom.GetObject(ID_DEVICES).EnumUsedIDs();
30
- foreach (sDevId, sAllDevices) {
31
- object oDevice = dom.GetObject(sDevId);
32
- if ((oDevice) && (oDevice.ReadyConfig()) && (oDevice.Interface() == iInterface_ID)) {
33
- foreach (sChnId, oDevice.Channels()) {
34
- object oChannel = dom.GetObject(sChnId);
35
- foreach(sDPId, oChannel.DPs().EnumUsedIDs()) {
36
- object oDP = dom.GetObject(sDPId);
37
- if (oDP && oDP.Timestamp()) {
38
- if (oDP.TypeName() != "VARDP") {
39
- if (bDPFirst) {
40
- bDPFirst = false;
41
- } else {
42
- WriteLine(',');
43
- }
44
- integer sValueType = oDP.ValueType();
45
- Write('"');
46
- WriteURL(oDP.Name());
47
- Write('":');
48
- if (sValueType == 20) {
49
- Write('"');
50
- WriteURL(oDP.Value());
51
- Write('"');
52
- } else {
53
- vDPValue = oDP.Value();
54
- if (sValueType == 2) {
55
- if (vDPValue) {
56
- Write("true");
57
- } else {
58
- Write("false");
59
- }
60
- } else {
61
- if (vDPValue == "") {
62
- Write("0");
63
- } else {
64
- Write(vDPValue);
65
- }
66
- }
67
- }
68
- }
69
- }
70
- }
71
- }
72
- }
73
- }
74
- }
75
- Write('}');