aiohomematic 2025.8.7__py3-none-any.whl → 2025.8.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.

@@ -298,14 +298,14 @@ class CentralUnit(PayloadMixin):
298
298
  )
299
299
 
300
300
  @property
301
- def interface_ids(self) -> tuple[str, ...]:
301
+ def interface_ids(self) -> frozenset[str]:
302
302
  """Return all associated interface ids."""
303
- return tuple(self._clients)
303
+ return frozenset(self._clients)
304
304
 
305
305
  @property
306
- def interfaces(self) -> tuple[Interface, ...]:
306
+ def interfaces(self) -> frozenset[Interface]:
307
307
  """Return all associated interfaces."""
308
- return tuple(client.interface for client in self._clients.values())
308
+ return frozenset(client.interface for client in self._clients.values())
309
309
 
310
310
  @property
311
311
  def is_alive(self) -> bool:
@@ -1009,19 +1009,19 @@ class CentralUnit(PayloadMixin):
1009
1009
  return
1010
1010
 
1011
1011
  async with self._device_add_semaphore:
1012
- # We need this to avoid adding duplicates.
1013
- known_addresses = tuple(
1014
- dev_desc["ADDRESS"]
1015
- for dev_desc in self._device_descriptions.get_raw_device_descriptions(interface_id=interface_id)
1016
- )
1012
+ # Use mapping membership to avoid rebuilding known addresses and allow O(1) checks.
1013
+ existing_map = self._device_descriptions.get_device_descriptions(interface_id=interface_id)
1017
1014
  client = self._clients[interface_id]
1018
1015
  save_paramset_descriptions = False
1019
1016
  save_device_descriptions = False
1020
1017
  for dev_desc in device_descriptions:
1021
1018
  try:
1019
+ address = dev_desc["ADDRESS"]
1020
+ # Check existence before mutating cache to ensure we detect truly new addresses.
1021
+ is_new_address = address not in existing_map
1022
1022
  self._device_descriptions.add_device(interface_id=interface_id, device_description=dev_desc)
1023
1023
  save_device_descriptions = True
1024
- if dev_desc["ADDRESS"] not in known_addresses:
1024
+ if is_new_address:
1025
1025
  await client.fetch_paramset_descriptions(device_description=dev_desc)
1026
1026
  save_paramset_descriptions = True
1027
1027
  except Exception as exc: # pragma: no cover
@@ -1043,7 +1043,7 @@ class CentralUnit(PayloadMixin):
1043
1043
  await self._create_devices(new_device_addresses=new_device_addresses)
1044
1044
 
1045
1045
  def _check_for_new_device_addresses(self) -> Mapping[str, set[str]]:
1046
- """Check if there are new devices, that needs to be created."""
1046
+ """Check if there are new devices that need to be created."""
1047
1047
  new_device_addresses: dict[str, set[str]] = {}
1048
1048
  for interface_id in self.interface_ids:
1049
1049
  if not self._paramset_descriptions.has_interface_id(interface_id=interface_id):
@@ -1053,21 +1053,16 @@ class CentralUnit(PayloadMixin):
1053
1053
  )
1054
1054
  continue
1055
1055
 
1056
- if interface_id not in new_device_addresses:
1057
- new_device_addresses[interface_id] = set()
1058
-
1056
+ # Build the set locally and assign only if non-empty to avoid add-then-delete pattern
1057
+ new_set: set[str] = set()
1059
1058
  for device_address in self._device_descriptions.get_addresses(interface_id=interface_id):
1060
1059
  if device_address not in self._devices:
1061
- new_device_addresses[interface_id].add(device_address)
1062
-
1063
- if not new_device_addresses[interface_id]:
1064
- del new_device_addresses[interface_id]
1060
+ new_set.add(device_address)
1061
+ if new_set:
1062
+ new_device_addresses[interface_id] = new_set
1065
1063
 
1066
1064
  if _LOGGER.isEnabledFor(level=DEBUG):
1067
- count: int = 0
1068
- for item in new_device_addresses.values():
1069
- count += len(item)
1070
-
1065
+ count = sum(len(item) for item in new_device_addresses.values())
1071
1066
  _LOGGER.debug(
1072
1067
  "CHECK_FOR_NEW_DEVICE_ADDRESSES: %s: %i.",
1073
1068
  "Found new device addresses" if new_device_addresses else "Did not find any new device addresses",
@@ -1298,7 +1293,7 @@ class CentralUnit(PayloadMixin):
1298
1293
  full_format: bool = False,
1299
1294
  un_ignore_candidates_only: bool = False,
1300
1295
  use_channel_wildcard: bool = False,
1301
- ) -> list[str]:
1296
+ ) -> tuple[str, ...]:
1302
1297
  """
1303
1298
  Return all parameters from VALUES paramset.
1304
1299
 
@@ -1368,7 +1363,7 @@ class CentralUnit(PayloadMixin):
1368
1363
  else:
1369
1364
  parameters.add(f"{parameter}:{paramset_key}@{model}:{channel_repr}")
1370
1365
 
1371
- return list(parameters)
1366
+ return tuple(parameters)
1372
1367
 
1373
1368
  def _get_virtual_remote(self, device_address: str) -> Device | None:
1374
1369
  """Get the virtual remote for the Client."""
@@ -1811,8 +1806,8 @@ class CentralConfig:
1811
1806
  enable_program_scan: bool = DEFAULT_ENABLE_PROGRAM_SCAN,
1812
1807
  enable_sysvar_scan: bool = DEFAULT_ENABLE_SYSVAR_SCAN,
1813
1808
  hm_master_poll_after_send_intervals: tuple[int, ...] = DEFAULT_HM_MASTER_POLL_AFTER_SEND_INTERVALS,
1814
- ignore_custom_device_definition_models: tuple[str, ...] = DEFAULT_IGNORE_CUSTOM_DEVICE_DEFINITION_MODELS,
1815
- interfaces_requiring_periodic_refresh: tuple[Interface, ...] = INTERFACES_REQUIRING_PERIODIC_REFRESH,
1809
+ ignore_custom_device_definition_models: frozenset[str] = DEFAULT_IGNORE_CUSTOM_DEVICE_DEFINITION_MODELS,
1810
+ interfaces_requiring_periodic_refresh: frozenset[Interface] = INTERFACES_REQUIRING_PERIODIC_REFRESH,
1816
1811
  json_port: int | None = None,
1817
1812
  listen_ip_addr: str | None = None,
1818
1813
  listen_port: int | None = None,
@@ -1823,7 +1818,7 @@ class CentralConfig:
1823
1818
  sys_scan_interval: int = DEFAULT_SYS_SCAN_INTERVAL,
1824
1819
  sysvar_markers: tuple[DescriptionMarker | str, ...] = DEFAULT_SYSVAR_MARKERS,
1825
1820
  tls: bool = DEFAULT_TLS,
1826
- un_ignore_list: tuple[str, ...] = DEFAULT_UN_IGNORES,
1821
+ un_ignore_list: frozenset[str] = DEFAULT_UN_IGNORES,
1827
1822
  verify_tls: bool = DEFAULT_VERIFY_TLS,
1828
1823
  ) -> None:
1829
1824
  """Init the client config."""
@@ -1838,8 +1833,8 @@ class CentralConfig:
1838
1833
  self.enable_sysvar_scan: Final = enable_sysvar_scan
1839
1834
  self.hm_master_poll_after_send_intervals: Final = hm_master_poll_after_send_intervals
1840
1835
  self.host: Final = host
1841
- self.ignore_custom_device_definition_models: Final = ignore_custom_device_definition_models
1842
- self.interfaces_requiring_periodic_refresh: Final = interfaces_requiring_periodic_refresh
1836
+ self.ignore_custom_device_definition_models: Final = frozenset(ignore_custom_device_definition_models or ())
1837
+ self.interfaces_requiring_periodic_refresh: Final = frozenset(interfaces_requiring_periodic_refresh or ())
1843
1838
  self.json_port: Final = json_port
1844
1839
  self.listen_ip_addr: Final = listen_ip_addr
1845
1840
  self.listen_port: Final = listen_port
@@ -1877,9 +1872,9 @@ class CentralConfig:
1877
1872
  return 443 if self.tls else 80
1878
1873
 
1879
1874
  @property
1880
- def enabled_interface_configs(self) -> tuple[hmcl.InterfaceConfig, ...]:
1875
+ def enabled_interface_configs(self) -> frozenset[hmcl.InterfaceConfig]:
1881
1876
  """Return the interface configs."""
1882
- return tuple(ic for ic in self._interface_configs if ic.enabled is True)
1877
+ return frozenset(ic for ic in self._interface_configs if ic.enabled is True)
1883
1878
 
1884
1879
  @property
1885
1880
  def use_caches(self) -> bool:
aiohomematic/const.py CHANGED
@@ -9,9 +9,10 @@ from enum import Enum, IntEnum, StrEnum
9
9
  import os
10
10
  import re
11
11
  import sys
12
- from typing import Any, Final, NamedTuple, Required, TypedDict
12
+ from types import MappingProxyType
13
+ from typing import Any, Final, NamedTuple, Required, TypeAlias, TypedDict
13
14
 
14
- VERSION: Final = "2025.8.7"
15
+ VERSION: Final = "2025.8.9"
15
16
 
16
17
  # Detect test speedup mode via environment
17
18
  _TEST_SPEEDUP: Final = (
@@ -24,7 +25,7 @@ DEFAULT_ENABLE_DEVICE_FIRMWARE_CHECK: Final = False
24
25
  DEFAULT_ENABLE_PROGRAM_SCAN: Final = True
25
26
  DEFAULT_ENABLE_SYSVAR_SCAN: Final = True
26
27
  DEFAULT_HM_MASTER_POLL_AFTER_SEND_INTERVALS: Final = (5,)
27
- DEFAULT_IGNORE_CUSTOM_DEVICE_DEFINITION_MODELS: Final[tuple[str, ...]] = ()
28
+ DEFAULT_IGNORE_CUSTOM_DEVICE_DEFINITION_MODELS: Final[frozenset[str]] = frozenset()
28
29
  DEFAULT_INCLUDE_INTERNAL_PROGRAMS: Final = False
29
30
  DEFAULT_INCLUDE_INTERNAL_SYSVARS: Final = True
30
31
  DEFAULT_MAX_READ_WORKERS: Final = 1
@@ -35,7 +36,7 @@ DEFAULT_PROGRAM_MARKERS: Final[tuple[DescriptionMarker | str, ...]] = ()
35
36
  DEFAULT_SYSVAR_MARKERS: Final[tuple[DescriptionMarker | str, ...]] = ()
36
37
  DEFAULT_SYS_SCAN_INTERVAL: Final = 30
37
38
  DEFAULT_TLS: Final = False
38
- DEFAULT_UN_IGNORES: Final[tuple[str, ...]] = ()
39
+ DEFAULT_UN_IGNORES: Final[frozenset[str]] = frozenset()
39
40
  DEFAULT_VERIFY_TLS: Final = False
40
41
 
41
42
  # Default encoding for json service calls, persistent cache
@@ -52,22 +53,24 @@ CHANNEL_ADDRESS_PATTERN: Final = re.compile(r"^[0-9a-zA-Z-]{5,20}:[0-9]{1,3}$")
52
53
  DEVICE_ADDRESS_PATTERN: Final = re.compile(r"^[0-9a-zA-Z-]{5,20}$")
53
54
  ALLOWED_HOSTNAME_PATTERN: Final = re.compile(r"(?!-)[a-z0-9-]{1,63}(?<!-)$", re.IGNORECASE)
54
55
  HTMLTAG_PATTERN: Final = re.compile(r"<.*?>|&([a-z0-9]+|#[0-9]{1,6}|#x[0-9a-f]{1,6});")
55
- SCHEDULER_PROFILE_PATTERN = re.compile(
56
+ SCHEDULER_PROFILE_PATTERN: Final = re.compile(
56
57
  r"^P[1-6]_(ENDTIME|TEMPERATURE)_(MONDAY|TUESDAY|WEDNESDAY|THURSDAY|FRIDAY|SATURDAY|SUNDAY)_([1-9]|1[0-3])$"
57
58
  )
58
- SCHEDULER_TIME_PATTERN = re.compile(r"^(([0-1]{0,1}[0-9])|(2[0-4])):[0-5][0-9]")
59
-
60
- ALWAYS_ENABLE_SYSVARS_BY_ID: Final = "40", "41"
61
- RENAME_SYSVAR_BY_NAME: Final = {
62
- "${sysVarAlarmMessages}": "ALARM_MESSAGES",
63
- "${sysVarPresence}": "PRESENCE",
64
- "${sysVarServiceMessages}": "SERVICE_MESSAGES",
65
- }
59
+ SCHEDULER_TIME_PATTERN: Final = re.compile(r"^(([0-1]{0,1}[0-9])|(2[0-4])):[0-5][0-9]")
60
+
61
+ ALWAYS_ENABLE_SYSVARS_BY_ID: Final[frozenset[str]] = frozenset({"40", "41"})
62
+ RENAME_SYSVAR_BY_NAME: Final[Mapping[str, str]] = MappingProxyType(
63
+ {
64
+ "${sysVarAlarmMessages}": "ALARM_MESSAGES",
65
+ "${sysVarPresence}": "PRESENCE",
66
+ "${sysVarServiceMessages}": "SERVICE_MESSAGES",
67
+ }
68
+ )
66
69
 
67
- SYSVAR_ENABLE_DEFAULT: Final = "40", "41"
70
+ SYSVAR_ENABLE_DEFAULT: Final[frozenset[str]] = frozenset({"40", "41"})
68
71
 
69
72
  ADDRESS_SEPARATOR: Final = ":"
70
- BLOCK_LOG_TIMEOUT = 60
73
+ BLOCK_LOG_TIMEOUT: Final = 60
71
74
  CACHE_PATH: Final = "cache"
72
75
  CONF_PASSWORD: Final = "password"
73
76
  CONF_USERNAME: Final = "username"
@@ -79,7 +82,7 @@ DEVICE_DESCRIPTIONS_DIR: Final = "export_device_descriptions"
79
82
  DEVICE_FIRMWARE_CHECK_INTERVAL: Final = 21600 # 6h
80
83
  DEVICE_FIRMWARE_DELIVERING_CHECK_INTERVAL: Final = 3600 # 1h
81
84
  DEVICE_FIRMWARE_UPDATING_CHECK_INTERVAL: Final = 300 # 5m
82
- DUMMY_SERIAL = "SN0815"
85
+ DUMMY_SERIAL: Final = "SN0815"
83
86
  FILE_DEVICES: Final = "homematic_devices.json"
84
87
  FILE_PARAMSETS: Final = "homematic_paramsets.json"
85
88
  HUB_PATH: Final = "hub"
@@ -87,7 +90,7 @@ IDENTIFIER_SEPARATOR: Final = "@"
87
90
  INIT_DATETIME: Final = datetime.strptime("01.01.1970 00:00:00", DATETIME_FORMAT)
88
91
  IP_ANY_V4: Final = "0.0.0.0"
89
92
  JSON_SESSION_AGE: Final = 90
90
- KWARGS_ARG_DATA_POINT = "data_point"
93
+ KWARGS_ARG_DATA_POINT: Final = "data_point"
91
94
  LAST_COMMAND_SEND_STORE_TIMEOUT: Final = 60
92
95
  LOCAL_HOST: Final = "127.0.0.1"
93
96
  MAX_CACHE_AGE: Final = 10
@@ -113,7 +116,7 @@ WAIT_FOR_CALLBACK: Final[int | None] = None
113
116
  SCHEDULER_NOT_STARTED_SLEEP: Final = 0.05 if _TEST_SPEEDUP else 10
114
117
  SCHEDULER_LOOP_SLEEP: Final = 0.05 if _TEST_SPEEDUP else 5
115
118
 
116
- CALLBACK_WARN_INTERVAL = CONNECTION_CHECKER_INTERVAL * 40
119
+ CALLBACK_WARN_INTERVAL: Final = CONNECTION_CHECKER_INTERVAL * 40
117
120
 
118
121
  # Path
119
122
  PROGRAM_SET_PATH_ROOT: Final = "program/set"
@@ -125,7 +128,7 @@ SYSVAR_STATE_PATH_ROOT: Final = "sysvar/status"
125
128
  VIRTDEV_SET_PATH_ROOT: Final = "virtdev/set"
126
129
  VIRTDEV_STATE_PATH_ROOT: Final = "virtdev/status"
127
130
 
128
- CALLBACK_TYPE = Callable[[], None] | None
131
+ CALLBACK_TYPE: TypeAlias = Callable[[], None] | None
129
132
 
130
133
 
131
134
  class Backend(StrEnum):
@@ -536,22 +539,26 @@ class ParameterType(StrEnum):
536
539
  EMPTY = ""
537
540
 
538
541
 
539
- CLICK_EVENTS: Final[tuple[Parameter, ...]] = (
540
- Parameter.PRESS,
541
- Parameter.PRESS_CONT,
542
- Parameter.PRESS_LOCK,
543
- Parameter.PRESS_LONG,
544
- Parameter.PRESS_LONG_RELEASE,
545
- Parameter.PRESS_LONG_START,
546
- Parameter.PRESS_SHORT,
547
- Parameter.PRESS_UNLOCK,
542
+ CLICK_EVENTS: Final[frozenset[Parameter]] = frozenset(
543
+ {
544
+ Parameter.PRESS,
545
+ Parameter.PRESS_CONT,
546
+ Parameter.PRESS_LOCK,
547
+ Parameter.PRESS_LONG,
548
+ Parameter.PRESS_LONG_RELEASE,
549
+ Parameter.PRESS_LONG_START,
550
+ Parameter.PRESS_SHORT,
551
+ Parameter.PRESS_UNLOCK,
552
+ }
548
553
  )
549
554
 
550
555
  DEVICE_ERROR_EVENTS: Final[tuple[Parameter, ...]] = (Parameter.ERROR, Parameter.SENSOR_ERROR)
551
556
 
552
- DATA_POINT_EVENTS: Final[tuple[EventType, ...]] = (
553
- EventType.IMPULSE,
554
- EventType.KEYPRESS,
557
+ DATA_POINT_EVENTS: Final[frozenset[EventType]] = frozenset(
558
+ {
559
+ EventType.IMPULSE,
560
+ EventType.KEYPRESS,
561
+ }
555
562
  )
556
563
 
557
564
 
@@ -567,26 +574,32 @@ class DataPointKey(NamedTuple):
567
574
  type DP_KEY_VALUE = tuple[DataPointKey, Any]
568
575
  type SYSVAR_TYPE = bool | float | int | str | None
569
576
 
570
- HMIP_FIRMWARE_UPDATE_IN_PROGRESS_STATES: Final[tuple[DeviceFirmwareState, ...]] = (
571
- DeviceFirmwareState.DO_UPDATE_PENDING,
572
- DeviceFirmwareState.PERFORMING_UPDATE,
577
+ HMIP_FIRMWARE_UPDATE_IN_PROGRESS_STATES: Final[frozenset[DeviceFirmwareState]] = frozenset(
578
+ {
579
+ DeviceFirmwareState.DO_UPDATE_PENDING,
580
+ DeviceFirmwareState.PERFORMING_UPDATE,
581
+ }
573
582
  )
574
583
 
575
- HMIP_FIRMWARE_UPDATE_READY_STATES: Final[tuple[DeviceFirmwareState, ...]] = (
576
- DeviceFirmwareState.READY_FOR_UPDATE,
577
- DeviceFirmwareState.DO_UPDATE_PENDING,
578
- DeviceFirmwareState.PERFORMING_UPDATE,
584
+ HMIP_FIRMWARE_UPDATE_READY_STATES: Final[frozenset[DeviceFirmwareState]] = frozenset(
585
+ {
586
+ DeviceFirmwareState.READY_FOR_UPDATE,
587
+ DeviceFirmwareState.DO_UPDATE_PENDING,
588
+ DeviceFirmwareState.PERFORMING_UPDATE,
589
+ }
579
590
  )
580
591
 
581
- IMPULSE_EVENTS: Final[tuple[Parameter, ...]] = (Parameter.SEQUENCE_OK,)
592
+ IMPULSE_EVENTS: Final[frozenset[Parameter]] = frozenset({Parameter.SEQUENCE_OK})
582
593
 
583
- KEY_CHANNEL_OPERATION_MODE_VISIBILITY: Final[Mapping[str, tuple[str, ...]]] = {
584
- Parameter.STATE: ("BINARY_BEHAVIOR",),
585
- Parameter.PRESS_LONG: ("KEY_BEHAVIOR", "SWITCH_BEHAVIOR"),
586
- Parameter.PRESS_LONG_RELEASE: ("KEY_BEHAVIOR", "SWITCH_BEHAVIOR"),
587
- Parameter.PRESS_LONG_START: ("KEY_BEHAVIOR", "SWITCH_BEHAVIOR"),
588
- Parameter.PRESS_SHORT: ("KEY_BEHAVIOR", "SWITCH_BEHAVIOR"),
589
- }
594
+ KEY_CHANNEL_OPERATION_MODE_VISIBILITY: Final[Mapping[str, frozenset[str]]] = MappingProxyType(
595
+ {
596
+ Parameter.STATE: frozenset({"BINARY_BEHAVIOR"}),
597
+ Parameter.PRESS_LONG: frozenset({"KEY_BEHAVIOR", "SWITCH_BEHAVIOR"}),
598
+ Parameter.PRESS_LONG_RELEASE: frozenset({"KEY_BEHAVIOR", "SWITCH_BEHAVIOR"}),
599
+ Parameter.PRESS_LONG_START: frozenset({"KEY_BEHAVIOR", "SWITCH_BEHAVIOR"}),
600
+ Parameter.PRESS_SHORT: frozenset({"KEY_BEHAVIOR", "SWITCH_BEHAVIOR"}),
601
+ }
602
+ )
590
603
 
591
604
  HUB_CATEGORIES: Final[tuple[DataPointCategory, ...]] = (
592
605
  DataPointCategory.HUB_BINARY_SENSOR,
@@ -616,42 +629,54 @@ CATEGORIES: Final[tuple[DataPointCategory, ...]] = (
616
629
  DataPointCategory.VALVE,
617
630
  )
618
631
 
619
- PRIMARY_CLIENT_CANDIDATE_INTERFACES: Final = (
620
- Interface.HMIP_RF,
621
- Interface.BIDCOS_RF,
622
- Interface.BIDCOS_WIRED,
632
+ PRIMARY_CLIENT_CANDIDATE_INTERFACES: Final[frozenset[Interface]] = frozenset(
633
+ {
634
+ Interface.HMIP_RF,
635
+ Interface.BIDCOS_RF,
636
+ Interface.BIDCOS_WIRED,
637
+ }
623
638
  )
624
639
 
625
- RELEVANT_INIT_PARAMETERS: Final[tuple[Parameter, ...]] = (
626
- Parameter.CONFIG_PENDING,
627
- Parameter.STICKY_UN_REACH,
628
- Parameter.UN_REACH,
640
+ RELEVANT_INIT_PARAMETERS: Final[frozenset[Parameter]] = frozenset(
641
+ {
642
+ Parameter.CONFIG_PENDING,
643
+ Parameter.STICKY_UN_REACH,
644
+ Parameter.UN_REACH,
645
+ }
629
646
  )
630
647
 
631
- INTERFACES_SUPPORTING_FIRMWARE_UPDATES: Final[tuple[Interface, ...]] = (
632
- Interface.BIDCOS_RF,
633
- Interface.BIDCOS_WIRED,
634
- Interface.HMIP_RF,
648
+ INTERFACES_SUPPORTING_FIRMWARE_UPDATES: Final[frozenset[Interface]] = frozenset(
649
+ {
650
+ Interface.BIDCOS_RF,
651
+ Interface.BIDCOS_WIRED,
652
+ Interface.HMIP_RF,
653
+ }
635
654
  )
636
655
 
637
- INTERFACES_SUPPORTING_XML_RPC: Final[tuple[Interface, ...]] = (
638
- Interface.BIDCOS_RF,
639
- Interface.BIDCOS_WIRED,
640
- Interface.HMIP_RF,
641
- Interface.VIRTUAL_DEVICES,
656
+ INTERFACES_SUPPORTING_XML_RPC: Final[frozenset[Interface]] = frozenset(
657
+ {
658
+ Interface.BIDCOS_RF,
659
+ Interface.BIDCOS_WIRED,
660
+ Interface.HMIP_RF,
661
+ Interface.VIRTUAL_DEVICES,
662
+ }
642
663
  )
643
664
 
644
- INTERFACES_REQUIRING_PERIODIC_REFRESH: Final[tuple[Interface, ...]] = (
645
- Interface.CCU_JACK,
646
- Interface.CUXD,
665
+ INTERFACES_REQUIRING_PERIODIC_REFRESH: Final[frozenset[Interface]] = frozenset(
666
+ {
667
+ Interface.CCU_JACK,
668
+ Interface.CUXD,
669
+ }
647
670
  )
648
671
 
649
672
  DEFAULT_USE_PERIODIC_SCAN_FOR_INTERFACES: Final = True
650
673
 
651
- IGNORE_FOR_UN_IGNORE_PARAMETERS: Final[tuple[Parameter, ...]] = (
652
- Parameter.CONFIG_PENDING,
653
- Parameter.STICKY_UN_REACH,
654
- Parameter.UN_REACH,
674
+ IGNORE_FOR_UN_IGNORE_PARAMETERS: Final[frozenset[Parameter]] = frozenset(
675
+ {
676
+ Parameter.CONFIG_PENDING,
677
+ Parameter.STICKY_UN_REACH,
678
+ Parameter.UN_REACH,
679
+ }
655
680
  )
656
681
 
657
682
 
@@ -659,12 +684,14 @@ IGNORE_FOR_UN_IGNORE_PARAMETERS: Final[tuple[Parameter, ...]] = (
659
684
  _IGNORE_ON_INITIAL_LOAD_PARAMETERS_END_RE: Final = re.compile(r".*(_ERROR)$")
660
685
  # Ignore Parameter on initial load that start with
661
686
  _IGNORE_ON_INITIAL_LOAD_PARAMETERS_START_RE: Final = re.compile(r"^(ERROR_|RSSI_)")
662
- _IGNORE_ON_INITIAL_LOAD_PARAMETERS: Final = (
663
- Parameter.DUTY_CYCLE,
664
- Parameter.DUTYCYCLE,
665
- Parameter.LOW_BAT,
666
- Parameter.LOWBAT,
667
- Parameter.OPERATING_VOLTAGE,
687
+ _IGNORE_ON_INITIAL_LOAD_PARAMETERS: Final[frozenset[Parameter]] = frozenset(
688
+ {
689
+ Parameter.DUTY_CYCLE,
690
+ Parameter.DUTYCYCLE,
691
+ Parameter.LOW_BAT,
692
+ Parameter.LOWBAT,
693
+ Parameter.OPERATING_VOLTAGE,
694
+ }
668
695
  )
669
696
 
670
697
 
@@ -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))
@@ -280,6 +280,10 @@ class CalculatedDataPoint[ParameterT: GenericParameterType](BaseDataPoint):
280
280
  """Generate the usage for the data point."""
281
281
  return DataPointUsage.DATA_POINT
282
282
 
283
+ def _get_signature(self) -> str:
284
+ """Return the signature of the data_point."""
285
+ return f"{self._category}/{self._channel.device.model}/{self._calculated_parameter}"
286
+
283
287
  async def load_data_point_value(self, call_source: CallSource, direct_call: bool = False) -> None:
284
288
  """Init the data point values."""
285
289
  for dp in self._readable_data_points:
@@ -300,7 +304,7 @@ class CalculatedDataPoint[ParameterT: GenericParameterType](BaseDataPoint):
300
304
  @property
301
305
  def _should_fire_data_point_updated_callback(self) -> bool:
302
306
  """Check if a data point has been updated or refreshed."""
303
- if self.fired_recently: # pylint: disable=using-constant-test
307
+ if self.fired_recently:
304
308
  return False
305
309
 
306
310
  if (relevant_values_data_point := self._relevant_values_data_points) is not None and len(
@@ -177,6 +177,10 @@ class CustomDataPoint(BaseDataPoint):
177
177
  return DataPointUsage.CDP_PRIMARY
178
178
  return DataPointUsage.CDP_SECONDARY
179
179
 
180
+ def _get_signature(self) -> str:
181
+ """Return the signature of the data_point."""
182
+ return f"{self._category}/{self._channel.device.model}/{self.data_point_name_postfix}"
183
+
180
184
  async def load_data_point_value(self, call_source: CallSource, direct_call: bool = False) -> None:
181
185
  """Init the data point values."""
182
186
  for dp in self._readable_data_points:
@@ -146,6 +146,7 @@ class CallbackDataPoint(ABC):
146
146
  "_modified_at",
147
147
  "_path_data",
148
148
  "_refreshed_at",
149
+ "_signature",
149
150
  "_temporary_modified_at",
150
151
  "_temporary_refreshed_at",
151
152
  "_unique_id",
@@ -164,6 +165,7 @@ class CallbackDataPoint(ABC):
164
165
  self._fired_at: datetime = INIT_DATETIME
165
166
  self._modified_at: datetime = INIT_DATETIME
166
167
  self._refreshed_at: datetime = INIT_DATETIME
168
+ self._signature: Final = self._get_signature()
167
169
  self._temporary_modified_at: datetime = INIT_DATETIME
168
170
  self._temporary_refreshed_at: datetime = INIT_DATETIME
169
171
 
@@ -252,6 +254,11 @@ class CallbackDataPoint(ABC):
252
254
  def name(self) -> str:
253
255
  """Return the name of the data_point."""
254
256
 
257
+ @property
258
+ def signature(self) -> str:
259
+ """Return the data_point signature."""
260
+ return self._signature
261
+
255
262
  @config_property
256
263
  def unique_id(self) -> str:
257
264
  """Return the unique_id."""
@@ -325,6 +332,10 @@ class CallbackDataPoint(ABC):
325
332
  def _get_path_data(self) -> PathData:
326
333
  """Return the path data."""
327
334
 
335
+ @abstractmethod
336
+ def _get_signature(self) -> str:
337
+ """Return the signature of the data_point."""
338
+
328
339
  def _unregister_data_point_updated_callback(self, cb: Callable, custom_id: str) -> None:
329
340
  """Unregister data_point updated callback."""
330
341
  if cb in self._data_point_updated_callbacks:
@@ -840,6 +851,10 @@ class BaseParameterDataPoint[
840
851
  return multiplier
841
852
  return DEFAULT_MULTIPLIER
842
853
 
854
+ def _get_signature(self) -> str:
855
+ """Return the signature of the data_point."""
856
+ return f"{self._category}/{self._channel.device.model}/{self._parameter}"
857
+
843
858
  @abstractmethod
844
859
  async def event(self, value: Any, received_at: datetime | None = None) -> None:
845
860
  """Handle event for which this handler has subscribed."""
@@ -861,9 +876,7 @@ class BaseParameterDataPoint[
861
876
 
862
877
  self.write_value(
863
878
  value=await self._device.value_cache.get_value(
864
- channel_address=self._channel.address,
865
- paramset_key=self._paramset_key,
866
- parameter=self._parameter,
879
+ dpk=self.dpk,
867
880
  call_source=call_source,
868
881
  direct_call=direct_call,
869
882
  ),