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.

@@ -30,11 +30,11 @@ available on the CentralUnit and model descriptors.
30
30
  from __future__ import annotations
31
31
 
32
32
  from collections import defaultdict
33
- from collections.abc import Mapping
33
+ from collections.abc import Iterable, Mapping
34
34
  from functools import cache
35
35
  import logging
36
36
  import re
37
- from typing import Final
37
+ from typing import Final, TypeAlias
38
38
 
39
39
  from aiohomematic import central as hmcu, support as hms
40
40
  from aiohomematic.const import ADDRESS_SEPARATOR, CLICK_EVENTS, UN_IGNORE_WILDCARD, Parameter, ParamsetKey
@@ -43,7 +43,12 @@ from aiohomematic.model.custom import get_required_parameters
43
43
  from aiohomematic.support import element_matches_key
44
44
 
45
45
  _LOGGER: Final = logging.getLogger(__name__)
46
- _CACHE_KEY_TYPE = tuple[str, int, ParamsetKey, str]
46
+
47
+ # Readability type aliases for internal cache structures
48
+ TModelName: TypeAlias = str
49
+ TChannelNo: TypeAlias = int | None
50
+ TUnIgnoreChannelNo: TypeAlias = TChannelNo | str
51
+ TParameterName: TypeAlias = str
47
52
 
48
53
  # Define which additional parameters from MASTER paramset should be created as data_point.
49
54
  # By default these are also on the _HIDDEN_PARAMETERS, which prevents these data points
@@ -51,172 +56,174 @@ _CACHE_KEY_TYPE = tuple[str, int, ParamsetKey, str]
51
56
  # and not for general display.
52
57
  # {model: (channel_no, parameter)}
53
58
 
54
- _RELEVANT_MASTER_PARAMSETS_BY_CHANNEL: Final[Mapping[int | str | None, tuple[Parameter, ...]]] = {
55
- None: (Parameter.GLOBAL_BUTTON_LOCK, Parameter.LOW_BAT_LIMIT),
56
- 0: (Parameter.GLOBAL_BUTTON_LOCK, Parameter.LOW_BAT_LIMIT),
59
+ _RELEVANT_MASTER_PARAMSETS_BY_CHANNEL: Final[Mapping[TChannelNo, frozenset[Parameter]]] = {
60
+ None: frozenset({Parameter.GLOBAL_BUTTON_LOCK, Parameter.LOW_BAT_LIMIT}),
61
+ 0: frozenset({Parameter.GLOBAL_BUTTON_LOCK, Parameter.LOW_BAT_LIMIT}),
57
62
  }
58
63
 
59
- _CLIMATE_MASTER_PARAMETERS: Final[tuple[Parameter, ...]] = (
60
- Parameter.HEATING_VALVE_TYPE,
61
- Parameter.MIN_MAX_VALUE_NOT_RELEVANT_FOR_MANU_MODE,
62
- Parameter.OPTIMUM_START_STOP,
63
- Parameter.TEMPERATURE_MAXIMUM,
64
- Parameter.TEMPERATURE_MINIMUM,
65
- Parameter.TEMPERATURE_OFFSET,
66
- Parameter.WEEK_PROGRAM_POINTER,
64
+ _CLIMATE_MASTER_PARAMETERS: Final[frozenset[Parameter]] = frozenset(
65
+ {
66
+ Parameter.HEATING_VALVE_TYPE,
67
+ Parameter.MIN_MAX_VALUE_NOT_RELEVANT_FOR_MANU_MODE,
68
+ Parameter.OPTIMUM_START_STOP,
69
+ Parameter.TEMPERATURE_MAXIMUM,
70
+ Parameter.TEMPERATURE_MINIMUM,
71
+ Parameter.TEMPERATURE_OFFSET,
72
+ Parameter.WEEK_PROGRAM_POINTER,
73
+ }
67
74
  )
68
75
 
69
- _RELEVANT_MASTER_PARAMSETS_BY_DEVICE: Final[Mapping[str, tuple[tuple[int | None, ...], tuple[Parameter, ...]]]] = {
70
- "ALPHA-IP-RBG": ((1,), _CLIMATE_MASTER_PARAMETERS),
71
- "ELV-SH-TACO": ((2,), (Parameter.CHANNEL_OPERATION_MODE,)),
76
+ _RELEVANT_MASTER_PARAMSETS_BY_DEVICE: Final[Mapping[TModelName, tuple[frozenset[TChannelNo], frozenset[Parameter]]]] = {
77
+ "ALPHA-IP-RBG": (frozenset({1}), _CLIMATE_MASTER_PARAMETERS),
78
+ "ELV-SH-TACO": (frozenset({2}), frozenset({Parameter.CHANNEL_OPERATION_MODE})),
72
79
  "HM-CC-RT-DN": (
73
- (None,),
80
+ frozenset({None}),
74
81
  _CLIMATE_MASTER_PARAMETERS,
75
82
  ),
76
83
  "HM-CC-VG-1": (
77
- (None,),
84
+ frozenset({None}),
78
85
  _CLIMATE_MASTER_PARAMETERS,
79
86
  ),
80
87
  "HM-TC-IT-WM-W-EU": (
81
- (None,),
88
+ frozenset({None}),
82
89
  _CLIMATE_MASTER_PARAMETERS,
83
90
  ),
84
- "HmIP-BWTH": ((1, 8), _CLIMATE_MASTER_PARAMETERS),
91
+ "HmIP-BWTH": (frozenset({1, 8}), _CLIMATE_MASTER_PARAMETERS),
85
92
  "HmIP-DRBLI4": (
86
- (1, 2, 3, 4, 5, 6, 7, 8, 9, 13, 17, 21),
87
- (Parameter.CHANNEL_OPERATION_MODE,),
93
+ frozenset({1, 2, 3, 4, 5, 6, 7, 8, 9, 13, 17, 21}),
94
+ frozenset({Parameter.CHANNEL_OPERATION_MODE}),
88
95
  ),
89
- "HmIP-DRDI3": ((1, 2, 3), (Parameter.CHANNEL_OPERATION_MODE,)),
90
- "HmIP-DRSI1": ((1,), (Parameter.CHANNEL_OPERATION_MODE,)),
91
- "HmIP-DRSI4": ((1, 2, 3, 4), (Parameter.CHANNEL_OPERATION_MODE,)),
92
- "HmIP-DSD-PCB": ((1,), (Parameter.CHANNEL_OPERATION_MODE,)),
93
- "HmIP-FCI1": ((1,), (Parameter.CHANNEL_OPERATION_MODE,)),
94
- "HmIP-FCI6": (tuple(range(1, 7)), (Parameter.CHANNEL_OPERATION_MODE,)),
95
- "HmIP-FSI16": ((1,), (Parameter.CHANNEL_OPERATION_MODE,)),
96
- "HmIP-HEATING": ((1,), _CLIMATE_MASTER_PARAMETERS),
97
- "HmIP-MIO16-PCB": ((13, 14, 15, 16), (Parameter.CHANNEL_OPERATION_MODE,)),
98
- "HmIP-MOD-RC8": (tuple(range(1, 9)), (Parameter.CHANNEL_OPERATION_MODE,)),
99
- "HmIP-RGBW": ((0,), (Parameter.DEVICE_OPERATION_MODE,)),
100
- "HmIP-STH": ((1,), _CLIMATE_MASTER_PARAMETERS),
101
- "HmIP-WGT": ((8, 14), _CLIMATE_MASTER_PARAMETERS),
102
- "HmIP-WTH": ((1,), _CLIMATE_MASTER_PARAMETERS),
103
- "HmIP-eTRV": ((1,), _CLIMATE_MASTER_PARAMETERS),
104
- "HmIPW-DRBL4": ((1, 5, 9, 13), (Parameter.CHANNEL_OPERATION_MODE,)),
105
- "HmIPW-DRI16": (tuple(range(1, 17)), (Parameter.CHANNEL_OPERATION_MODE,)),
106
- "HmIPW-DRI32": (tuple(range(1, 33)), (Parameter.CHANNEL_OPERATION_MODE,)),
107
- "HmIPW-FIO6": (tuple(range(1, 7)), (Parameter.CHANNEL_OPERATION_MODE,)),
108
- "HmIPW-STH": ((1,), _CLIMATE_MASTER_PARAMETERS),
96
+ "HmIP-DRDI3": (frozenset({1, 2, 3}), frozenset({Parameter.CHANNEL_OPERATION_MODE})),
97
+ "HmIP-DRSI1": (frozenset({1}), frozenset({Parameter.CHANNEL_OPERATION_MODE})),
98
+ "HmIP-DRSI4": (frozenset({1, 2, 3, 4}), frozenset({Parameter.CHANNEL_OPERATION_MODE})),
99
+ "HmIP-DSD-PCB": (frozenset({1}), frozenset({Parameter.CHANNEL_OPERATION_MODE})),
100
+ "HmIP-FCI1": (frozenset({1}), frozenset({Parameter.CHANNEL_OPERATION_MODE})),
101
+ "HmIP-FCI6": (frozenset(range(1, 7)), frozenset({Parameter.CHANNEL_OPERATION_MODE})),
102
+ "HmIP-FSI16": (frozenset({1}), frozenset({Parameter.CHANNEL_OPERATION_MODE})),
103
+ "HmIP-HEATING": (frozenset({1}), _CLIMATE_MASTER_PARAMETERS),
104
+ "HmIP-MIO16-PCB": (frozenset({13, 14, 15, 16}), frozenset({Parameter.CHANNEL_OPERATION_MODE})),
105
+ "HmIP-MOD-RC8": (frozenset(range(1, 9)), frozenset({Parameter.CHANNEL_OPERATION_MODE})),
106
+ "HmIP-RGBW": (frozenset({0}), frozenset({Parameter.DEVICE_OPERATION_MODE})),
107
+ "HmIP-STH": (frozenset({1}), _CLIMATE_MASTER_PARAMETERS),
108
+ "HmIP-WGT": (frozenset({8, 14}), _CLIMATE_MASTER_PARAMETERS),
109
+ "HmIP-WTH": (frozenset({1}), _CLIMATE_MASTER_PARAMETERS),
110
+ "HmIP-eTRV": (frozenset({1}), _CLIMATE_MASTER_PARAMETERS),
111
+ "HmIPW-DRBL4": (frozenset({1, 5, 9, 13}), frozenset({Parameter.CHANNEL_OPERATION_MODE})),
112
+ "HmIPW-DRI16": (frozenset(range(1, 17)), frozenset({Parameter.CHANNEL_OPERATION_MODE})),
113
+ "HmIPW-DRI32": (frozenset(range(1, 33)), frozenset({Parameter.CHANNEL_OPERATION_MODE})),
114
+ "HmIPW-FIO6": (frozenset(range(1, 7)), frozenset({Parameter.CHANNEL_OPERATION_MODE})),
115
+ "HmIPW-STH": (frozenset({1}), _CLIMATE_MASTER_PARAMETERS),
109
116
  }
110
117
 
111
118
  # Ignore events for some devices
112
- _IGNORE_DEVICES_FOR_DATA_POINT_EVENTS: Final[Mapping[str, tuple[Parameter, ...]]] = {
119
+ _IGNORE_DEVICES_FOR_DATA_POINT_EVENTS: Final[Mapping[TModelName, frozenset[Parameter]]] = {
113
120
  "HmIP-PS": CLICK_EVENTS,
114
121
  }
115
122
 
116
- _IGNORE_DEVICES_FOR_DATA_POINT_EVENTS_LOWER: Final[dict[str, tuple[str, ...]]] = {
117
- model.lower(): tuple(event for event in events) for model, events in _IGNORE_DEVICES_FOR_DATA_POINT_EVENTS.items()
123
+ _IGNORE_DEVICES_FOR_DATA_POINT_EVENTS_LOWER: Final[Mapping[TModelName, frozenset[Parameter]]] = {
124
+ model.lower(): frozenset(events) for model, events in _IGNORE_DEVICES_FOR_DATA_POINT_EVENTS.items()
118
125
  }
119
126
 
120
127
  # data points that will be created, but should be hidden.
121
- _HIDDEN_PARAMETERS: Final[tuple[Parameter, ...]] = (
122
- Parameter.ACTIVITY_STATE,
123
- Parameter.CHANNEL_OPERATION_MODE,
124
- Parameter.CONFIG_PENDING,
125
- Parameter.DIRECTION,
126
- Parameter.ERROR,
127
- Parameter.HEATING_VALVE_TYPE,
128
- Parameter.LOW_BAT_LIMIT,
129
- Parameter.MIN_MAX_VALUE_NOT_RELEVANT_FOR_MANU_MODE,
130
- Parameter.OPTIMUM_START_STOP,
131
- Parameter.SECTION,
132
- Parameter.STICKY_UN_REACH,
133
- Parameter.TEMPERATURE_MAXIMUM,
134
- Parameter.TEMPERATURE_MINIMUM,
135
- Parameter.TEMPERATURE_OFFSET,
136
- Parameter.UN_REACH,
137
- Parameter.UPDATE_PENDING,
138
- Parameter.WORKING,
128
+ _HIDDEN_PARAMETERS: Final[frozenset[Parameter]] = frozenset(
129
+ {
130
+ Parameter.ACTIVITY_STATE,
131
+ Parameter.CHANNEL_OPERATION_MODE,
132
+ Parameter.CONFIG_PENDING,
133
+ Parameter.DIRECTION,
134
+ Parameter.ERROR,
135
+ Parameter.HEATING_VALVE_TYPE,
136
+ Parameter.LOW_BAT_LIMIT,
137
+ Parameter.MIN_MAX_VALUE_NOT_RELEVANT_FOR_MANU_MODE,
138
+ Parameter.OPTIMUM_START_STOP,
139
+ Parameter.SECTION,
140
+ Parameter.STICKY_UN_REACH,
141
+ Parameter.TEMPERATURE_MAXIMUM,
142
+ Parameter.TEMPERATURE_MINIMUM,
143
+ Parameter.TEMPERATURE_OFFSET,
144
+ Parameter.UN_REACH,
145
+ Parameter.UPDATE_PENDING,
146
+ Parameter.WORKING,
147
+ }
139
148
  )
140
- # Fast membership for hidden parameters
141
- _HIDDEN_PARAMETERS_SET: Final[set[Parameter]] = set(_HIDDEN_PARAMETERS)
142
149
 
143
150
  # Parameters within the VALUES paramset for which we don't create data points.
144
- _IGNORED_PARAMETERS: Final[tuple[str, ...]] = (
145
- "ACCESS_AUTHORIZATION",
146
- "ACOUSTIC_NOTIFICATION_SELECTION",
147
- "ADAPTION_DRIVE",
148
- "AES_KEY",
149
- "ALARM_COUNT",
150
- "ALL_LEDS",
151
- "ARROW_DOWN",
152
- "ARROW_UP",
153
- "BACKLIGHT",
154
- "BEEP",
155
- "BELL",
156
- "BLIND",
157
- "BOOST_STATE",
158
- "BOOST_TIME",
159
- "BOOT",
160
- "BULB",
161
- "CLEAR_ERROR",
162
- "CLEAR_WINDOW_OPEN_SYMBOL",
163
- "CLOCK",
164
- "CONTROL_DIFFERENTIAL_TEMPERATURE",
165
- "DATE_TIME_UNKNOWN",
166
- "DECISION_VALUE",
167
- "DEVICE_IN_BOOTLOADER",
168
- "DISPLAY_DATA_ALIGNMENT",
169
- "DISPLAY_DATA_BACKGROUND_COLOR",
170
- "DISPLAY_DATA_COMMIT",
171
- "DISPLAY_DATA_ICON",
172
- "DISPLAY_DATA_ID",
173
- "DISPLAY_DATA_STRING",
174
- "DISPLAY_DATA_TEXT_COLOR",
175
- "DOOR",
176
- "EXTERNAL_CLOCK",
177
- "FROST_PROTECTION",
178
- "HUMIDITY_LIMITER",
179
- "IDENTIFICATION_MODE_KEY_VISUAL",
180
- "IDENTIFICATION_MODE_LCD_BACKLIGHT",
181
- "INCLUSION_UNSUPPORTED_DEVICE",
182
- "INHIBIT",
183
- "INSTALL_MODE",
184
- "INTERVAL",
185
- "LEVEL_REAL",
186
- "OLD_LEVEL",
187
- "OVERFLOW",
188
- "OVERRUN",
189
- "PARTY_SET_POINT_TEMPERATURE",
190
- "PARTY_TEMPERATURE",
191
- "PARTY_TIME_END",
192
- "PARTY_TIME_START",
193
- "PHONE",
194
- "PROCESS",
195
- "QUICK_VETO_TIME",
196
- "RAMP_STOP",
197
- "RELOCK_DELAY",
198
- "SCENE",
199
- "SELF_CALIBRATION",
200
- "SERVICE_COUNT",
201
- "SET_SYMBOL_FOR_HEATING_PHASE",
202
- "SHADING_SPEED",
203
- "SHEV_POS",
204
- "SPEED",
205
- "STATE_UNCERTAIN",
206
- "SUBMIT",
207
- "SWITCH_POINT_OCCURED",
208
- "TEMPERATURE_LIMITER",
209
- "TEMPERATURE_OUT_OF_RANGE",
210
- "TEXT",
211
- "USER_COLOR",
212
- "USER_PROGRAM",
213
- "VALVE_ADAPTION",
214
- "WINDOW",
215
- "WIN_RELEASE",
216
- "WIN_RELEASE_ACT",
151
+ _IGNORED_PARAMETERS: Final[frozenset[TParameterName]] = frozenset(
152
+ {
153
+ "ACCESS_AUTHORIZATION",
154
+ "ACOUSTIC_NOTIFICATION_SELECTION",
155
+ "ADAPTION_DRIVE",
156
+ "AES_KEY",
157
+ "ALARM_COUNT",
158
+ "ALL_LEDS",
159
+ "ARROW_DOWN",
160
+ "ARROW_UP",
161
+ "BACKLIGHT",
162
+ "BEEP",
163
+ "BELL",
164
+ "BLIND",
165
+ "BOOST_STATE",
166
+ "BOOST_TIME",
167
+ "BOOT",
168
+ "BULB",
169
+ "CLEAR_ERROR",
170
+ "CLEAR_WINDOW_OPEN_SYMBOL",
171
+ "CLOCK",
172
+ "CONTROL_DIFFERENTIAL_TEMPERATURE",
173
+ "DATE_TIME_UNKNOWN",
174
+ "DECISION_VALUE",
175
+ "DEVICE_IN_BOOTLOADER",
176
+ "DISPLAY_DATA_ALIGNMENT",
177
+ "DISPLAY_DATA_BACKGROUND_COLOR",
178
+ "DISPLAY_DATA_COMMIT",
179
+ "DISPLAY_DATA_ICON",
180
+ "DISPLAY_DATA_ID",
181
+ "DISPLAY_DATA_STRING",
182
+ "DISPLAY_DATA_TEXT_COLOR",
183
+ "DOOR",
184
+ "EXTERNAL_CLOCK",
185
+ "FROST_PROTECTION",
186
+ "HUMIDITY_LIMITER",
187
+ "IDENTIFICATION_MODE_KEY_VISUAL",
188
+ "IDENTIFICATION_MODE_LCD_BACKLIGHT",
189
+ "INCLUSION_UNSUPPORTED_DEVICE",
190
+ "INHIBIT",
191
+ "INSTALL_MODE",
192
+ "INTERVAL",
193
+ "LEVEL_REAL",
194
+ "OLD_LEVEL",
195
+ "OVERFLOW",
196
+ "OVERRUN",
197
+ "PARTY_SET_POINT_TEMPERATURE",
198
+ "PARTY_TEMPERATURE",
199
+ "PARTY_TIME_END",
200
+ "PARTY_TIME_START",
201
+ "PHONE",
202
+ "PROCESS",
203
+ "QUICK_VETO_TIME",
204
+ "RAMP_STOP",
205
+ "RELOCK_DELAY",
206
+ "SCENE",
207
+ "SELF_CALIBRATION",
208
+ "SERVICE_COUNT",
209
+ "SET_SYMBOL_FOR_HEATING_PHASE",
210
+ "SHADING_SPEED",
211
+ "SHEV_POS",
212
+ "SPEED",
213
+ "STATE_UNCERTAIN",
214
+ "SUBMIT",
215
+ "SWITCH_POINT_OCCURED",
216
+ "TEMPERATURE_LIMITER",
217
+ "TEMPERATURE_OUT_OF_RANGE",
218
+ "TEXT",
219
+ "USER_COLOR",
220
+ "USER_PROGRAM",
221
+ "VALVE_ADAPTION",
222
+ "WINDOW",
223
+ "WIN_RELEASE",
224
+ "WIN_RELEASE_ACT",
225
+ }
217
226
  )
218
- # Fast membership for ignored VALUE parameters
219
- _IGNORED_PARAMETERS_SET: Final[set[str]] = set(_IGNORED_PARAMETERS)
220
227
 
221
228
 
222
229
  # Precompile Regex patterns for wildcard checks
@@ -228,31 +235,28 @@ _IGNORED_PARAMETERS_START_RE: Final = re.compile(
228
235
  )
229
236
 
230
237
 
231
- def _parameter_is_wildcard_ignored(parameter: str) -> bool:
238
+ def _parameter_is_wildcard_ignored(parameter: TParameterName) -> bool:
232
239
  """Check if a parameter matches common wildcard patterns."""
233
240
  return bool(_IGNORED_PARAMETERS_END_RE.match(parameter) or _IGNORED_PARAMETERS_START_RE.match(parameter))
234
241
 
235
242
 
236
243
  # Parameters within the paramsets for which we create data points.
237
- _UN_IGNORE_PARAMETERS_BY_DEVICE: Final[Mapping[str, tuple[Parameter, ...]]] = {
238
- "HmIP-DLD": (Parameter.ERROR_JAMMED,),
239
- "HmIP-SWSD": (Parameter.SMOKE_DETECTOR_ALARM_STATUS,),
240
- "HM-OU-LED16": (Parameter.LED_STATUS,),
241
- "HM-Sec-Win": (Parameter.DIRECTION, Parameter.WORKING, Parameter.ERROR, Parameter.STATUS),
242
- "HM-Sec-Key": (Parameter.DIRECTION, Parameter.ERROR),
243
- "HmIP-PCBS-BAT": (
244
- Parameter.OPERATING_VOLTAGE,
245
- Parameter.LOW_BAT,
246
- ), # To override ignore for HmIP-PCBS
244
+ _UN_IGNORE_PARAMETERS_BY_DEVICE: Final[Mapping[TModelName, frozenset[Parameter]]] = {
245
+ "HmIP-DLD": frozenset({Parameter.ERROR_JAMMED}),
246
+ "HmIP-SWSD": frozenset({Parameter.SMOKE_DETECTOR_ALARM_STATUS}),
247
+ "HM-OU-LED16": frozenset({Parameter.LED_STATUS}),
248
+ "HM-Sec-Win": frozenset({Parameter.DIRECTION, Parameter.WORKING, Parameter.ERROR, Parameter.STATUS}),
249
+ "HM-Sec-Key": frozenset({Parameter.DIRECTION, Parameter.ERROR}),
250
+ "HmIP-PCBS-BAT": frozenset({Parameter.OPERATING_VOLTAGE, Parameter.LOW_BAT}), # To override ignore for HmIP-PCBS
247
251
  }
248
252
 
249
- _UN_IGNORE_PARAMETERS_BY_MODEL_LOWER: Final[dict[str, tuple[str, ...]]] = {
250
- model.lower(): parameters for model, parameters in _UN_IGNORE_PARAMETERS_BY_DEVICE.items()
253
+ _UN_IGNORE_PARAMETERS_BY_MODEL_LOWER: Final[dict[TModelName, frozenset[Parameter]]] = {
254
+ model.lower(): frozenset(parameters) for model, parameters in _UN_IGNORE_PARAMETERS_BY_DEVICE.items()
251
255
  }
252
256
 
253
257
 
254
258
  @cache
255
- def _get_parameters_for_model_prefix(model_prefix: str | None) -> tuple[str, ...] | None:
259
+ def _get_parameters_for_model_prefix(model_prefix: str | None) -> frozenset[Parameter] | None:
256
260
  """Return the dict value by wildcard type."""
257
261
  if model_prefix is None:
258
262
  return None
@@ -264,59 +268,74 @@ def _get_parameters_for_model_prefix(model_prefix: str | None) -> tuple[str, ...
264
268
 
265
269
 
266
270
  # Parameters by device within the VALUES paramset for which we don't create data points.
267
- _IGNORE_PARAMETERS_BY_DEVICE: Final[Mapping[Parameter, tuple[str, ...]]] = {
268
- Parameter.CURRENT_ILLUMINATION: (
269
- "HmIP-SMI",
270
- "HmIP-SMO",
271
- "HmIP-SPI",
271
+ _IGNORE_PARAMETERS_BY_DEVICE: Final[Mapping[Parameter, frozenset[TModelName]]] = {
272
+ Parameter.CURRENT_ILLUMINATION: frozenset(
273
+ {
274
+ "HmIP-SMI",
275
+ "HmIP-SMO",
276
+ "HmIP-SPI",
277
+ }
272
278
  ),
273
- Parameter.LOWBAT: (
274
- "HM-LC-Sw1-DR",
275
- "HM-LC-Sw1-FM",
276
- "HM-LC-Sw1-PCB",
277
- "HM-LC-Sw1-Pl",
278
- "HM-LC-Sw1-Pl-DN-R1",
279
- "HM-LC-Sw1PBU-FM",
280
- "HM-LC-Sw2-FM",
281
- "HM-LC-Sw4-DR",
282
- "HM-SwI-3-FM",
279
+ Parameter.LOWBAT: frozenset(
280
+ {
281
+ "HM-LC-Sw1-DR",
282
+ "HM-LC-Sw1-FM",
283
+ "HM-LC-Sw1-PCB",
284
+ "HM-LC-Sw1-Pl",
285
+ "HM-LC-Sw1-Pl-DN-R1",
286
+ "HM-LC-Sw1PBU-FM",
287
+ "HM-LC-Sw2-FM",
288
+ "HM-LC-Sw4-DR",
289
+ "HM-SwI-3-FM",
290
+ }
283
291
  ),
284
- Parameter.LOW_BAT: ("HmIP-BWTH", "HmIP-PCBS"),
285
- Parameter.OPERATING_VOLTAGE: (
286
- "ELV-SH-BS2",
287
- "HmIP-BDT",
288
- "HmIP-BROLL",
289
- "HmIP-BS2",
290
- "HmIP-BSL",
291
- "HmIP-BSM",
292
- "HmIP-BWTH",
293
- "HmIP-DR",
294
- "HmIP-FDT",
295
- "HmIP-FROLL",
296
- "HmIP-FSM",
297
- "HmIP-MOD-OC8",
298
- "HmIP-PCBS",
299
- "HmIP-PDT",
300
- "HmIP-PMFS",
301
- "HmIP-PS",
302
- "HmIP-SFD",
303
- "HmIP-SMO230",
304
- "HmIP-WGT",
292
+ Parameter.LOW_BAT: frozenset({"HmIP-BWTH", "HmIP-PCBS"}),
293
+ Parameter.OPERATING_VOLTAGE: frozenset(
294
+ {
295
+ "ELV-SH-BS2",
296
+ "HmIP-BDT",
297
+ "HmIP-BROLL",
298
+ "HmIP-BS2",
299
+ "HmIP-BSL",
300
+ "HmIP-BSM",
301
+ "HmIP-BWTH",
302
+ "HmIP-DR",
303
+ "HmIP-FDT",
304
+ "HmIP-FROLL",
305
+ "HmIP-FSM",
306
+ "HmIP-MOD-OC8",
307
+ "HmIP-PCBS",
308
+ "HmIP-PDT",
309
+ "HmIP-PMFS",
310
+ "HmIP-PS",
311
+ "HmIP-SFD",
312
+ "HmIP-SMO230",
313
+ "HmIP-WGT",
314
+ }
305
315
  ),
306
- Parameter.VALVE_STATE: ("HmIPW-FALMOT-C12", "HmIP-FALMOT-C12"),
316
+ Parameter.VALVE_STATE: frozenset({"HmIPW-FALMOT-C12", "HmIP-FALMOT-C12"}),
307
317
  }
308
318
 
309
- _IGNORE_PARAMETERS_BY_DEVICE_LOWER: Final[dict[str, tuple[str, ...]]] = {
310
- parameter: tuple(model.lower() for model in s) for parameter, s in _IGNORE_PARAMETERS_BY_DEVICE.items()
319
+ _IGNORE_PARAMETERS_BY_DEVICE_LOWER: Final[dict[TParameterName, frozenset[TModelName]]] = {
320
+ parameter: frozenset(model.lower() for model in s) for parameter, s in _IGNORE_PARAMETERS_BY_DEVICE.items()
311
321
  }
312
322
 
313
323
  # Some devices have parameters on multiple channels,
314
324
  # but we want to use it only from a certain channel.
315
- _ACCEPT_PARAMETER_ONLY_ON_CHANNEL: Final[Mapping[str, int]] = {Parameter.LOWBAT: 0}
325
+ _ACCEPT_PARAMETER_ONLY_ON_CHANNEL: Final[Mapping[TParameterName, int]] = {Parameter.LOWBAT: 0}
316
326
 
317
327
 
318
328
  class ParameterVisibilityCache:
319
- """Cache for parameter visibility."""
329
+ """
330
+ Cache for parameter visibility.
331
+
332
+ The cache centralizes rules that determine whether a data point parameter is
333
+ created, ignored, un-ignored, or merely hidden for UI purposes. It combines
334
+ static rules (per-model/per-channel) with dynamic user-provided overrides and
335
+ memoizes decisions per (model/channel/paramset/parameter) to avoid repeated
336
+ computations during runtime. Behavior is unchanged; this class only clarifies
337
+ intent and structure through documentation and small naming improvements.
338
+ """
320
339
 
321
340
  __slots__ = (
322
341
  "_central",
@@ -342,36 +361,36 @@ class ParameterVisibilityCache:
342
361
  self._central = central
343
362
  self._storage_folder: Final = central.config.storage_folder
344
363
  self._required_parameters: Final = get_required_parameters()
345
- self._raw_un_ignores: Final[tuple[str, ...]] = central.config.un_ignore_list or ()
364
+ self._raw_un_ignores: Final[frozenset[str]] = central.config.un_ignore_list or frozenset()
346
365
 
347
366
  # un_ignore from custom un_ignore files
348
367
  # parameter
349
- self._custom_un_ignore_values_parameters: Final[set[str]] = set()
368
+ self._custom_un_ignore_values_parameters: Final[set[TParameterName]] = set()
350
369
 
351
- # model, channel_no, paramset_key, parameter
352
- self._custom_un_ignore_complex: Final[dict[str, dict[int | str | None, dict[str, set[str]]]]] = defaultdict(
353
- lambda: defaultdict(lambda: defaultdict(set))
354
- )
355
- self._ignore_custom_device_definition_models: Final[tuple[str, ...]] = (
370
+ # model -> channel_no -> paramset_key -> set(parameter)
371
+ self._custom_un_ignore_complex: Final[
372
+ dict[TModelName, dict[TUnIgnoreChannelNo, dict[ParamsetKey, set[TParameterName]]]]
373
+ ] = defaultdict(lambda: defaultdict(lambda: defaultdict(set)))
374
+ self._ignore_custom_device_definition_models: Final[frozenset[TModelName]] = (
356
375
  central.config.ignore_custom_device_definition_models
357
376
  )
358
377
 
359
378
  # model, channel_no, paramset_key, set[parameter]
360
379
  self._un_ignore_parameters_by_device_paramset_key: Final[
361
- dict[str, dict[int | None, dict[ParamsetKey, set[str]]]]
380
+ dict[TModelName, dict[TChannelNo, dict[ParamsetKey, set[TParameterName]]]]
362
381
  ] = defaultdict(lambda: defaultdict(lambda: defaultdict(set)))
363
382
 
364
383
  # model, channel_no
365
- self._relevant_master_paramsets_by_device: Final[dict[str, set[int | None]]] = defaultdict(set)
384
+ self._relevant_master_paramsets_by_device: Final[dict[TModelName, set[TChannelNo]]] = defaultdict(set)
366
385
  # Cache for resolving matching prefix key in _un_ignore_parameters_by_device_paramset_key
367
- self._un_ignore_prefix_cache: dict[str, str | None] = {}
386
+ self._un_ignore_prefix_cache: dict[TModelName, str | None] = {}
368
387
  # Cache for resolving matching prefix key in _relevant_master_paramsets_by_device
369
- self._relevant_prefix_cache: dict[str, str | None] = {}
388
+ self._relevant_prefix_cache: dict[TModelName, str | None] = {}
370
389
  # Per-instance memoization for repeated queries.
371
390
  # Key: (model_l, channel_no, paramset_key, parameter)
372
- self._param_ignored_cache: dict[tuple[str, int, ParamsetKey, str], bool] = {}
391
+ self._param_ignored_cache: dict[tuple[TModelName, TChannelNo, ParamsetKey, TParameterName], bool] = {}
373
392
  # Key: (model_l, channel_no, paramset_key, parameter, custom_only)
374
- self._param_un_ignored_cache: dict[tuple[str, int, ParamsetKey, str, bool], bool] = {}
393
+ self._param_un_ignored_cache: dict[tuple[TModelName, TChannelNo, ParamsetKey, TParameterName, bool], bool] = {}
375
394
  self._init()
376
395
 
377
396
  def _init(self) -> None:
@@ -383,7 +402,7 @@ class ParameterVisibilityCache:
383
402
  model_l = model.lower()
384
403
  channel_nos, parameters = channels_parameter
385
404
 
386
- def _add_channel(dt_l: str, params: tuple[Parameter, ...], ch_no: int | None) -> None:
405
+ def _add_channel(dt_l: str, params: frozenset[Parameter], ch_no: TChannelNo) -> None:
387
406
  self._relevant_master_paramsets_by_device[dt_l].add(ch_no)
388
407
  self._un_ignore_parameters_by_device_paramset_key[dt_l][ch_no][ParamsetKey.MASTER].update(params)
389
408
 
@@ -395,7 +414,17 @@ class ParameterVisibilityCache:
395
414
 
396
415
  self._process_un_ignore_entries(lines=self._raw_un_ignores)
397
416
 
398
- def model_is_ignored(self, model: str) -> bool:
417
+ def _resolve_prefix_key(
418
+ self, model_l: TModelName, mapping: Mapping[str, object], cache_dict: dict[TModelName, str | None]
419
+ ) -> str | None:
420
+ """Resolve and memoize the first key in mapping that prefixes model_l."""
421
+ dt_short_key = cache_dict.get(model_l)
422
+ if dt_short_key is None and model_l not in cache_dict:
423
+ dt_short_key = next((k for k in mapping if model_l.startswith(k)), None)
424
+ cache_dict[model_l] = dt_short_key
425
+ return dt_short_key
426
+
427
+ def model_is_ignored(self, model: TModelName) -> bool:
399
428
  """Check if a model should be ignored for custom data points."""
400
429
  return element_matches_key(
401
430
  search_elements=self._ignore_custom_device_definition_models,
@@ -406,13 +435,12 @@ class ParameterVisibilityCache:
406
435
  self,
407
436
  channel: hmd.Channel,
408
437
  paramset_key: ParamsetKey,
409
- parameter: str,
438
+ parameter: TParameterName,
410
439
  ) -> bool:
411
440
  """Check if parameter can be ignored."""
412
441
  model_l = channel.device.model.lower()
413
442
  # Fast path via per-instance memoization
414
- ch_no_key = channel.no if isinstance(channel.no, int) else (-1 if channel.no is None else channel.no)
415
- if (cache_key := (model_l, ch_no_key, paramset_key, parameter)) in self._param_ignored_cache:
443
+ if (cache_key := (model_l, channel.no, paramset_key, parameter)) in self._param_ignored_cache:
416
444
  return self._param_ignored_cache[cache_key]
417
445
 
418
446
  if paramset_key == ParamsetKey.VALUES:
@@ -425,7 +453,7 @@ class ParameterVisibilityCache:
425
453
 
426
454
  if (
427
455
  (
428
- (parameter in _IGNORED_PARAMETERS_SET or _parameter_is_wildcard_ignored(parameter=parameter))
456
+ (parameter in _IGNORED_PARAMETERS or _parameter_is_wildcard_ignored(parameter=parameter))
429
457
  and parameter not in self._required_parameters
430
458
  )
431
459
  or hms.element_matches_key(
@@ -454,14 +482,11 @@ class ParameterVisibilityCache:
454
482
  if parameter in self._custom_un_ignore_complex[model_l][channel.no][ParamsetKey.MASTER]:
455
483
  return False # pragma: no cover
456
484
 
457
- # Resolve matching device type prefix once and cache per model
458
- dt_short_key = self._un_ignore_prefix_cache.get(model_l)
459
- if dt_short_key is None and model_l not in self._un_ignore_prefix_cache:
460
- # Find first key that is a prefix of model_l
461
- dt_short_key = next(
462
- (k for k in self._un_ignore_parameters_by_device_paramset_key if model_l.startswith(k)), None
463
- )
464
- self._un_ignore_prefix_cache[model_l] = dt_short_key
485
+ dt_short_key = self._resolve_prefix_key(
486
+ model_l=model_l,
487
+ mapping=self._un_ignore_parameters_by_device_paramset_key,
488
+ cache_dict=self._un_ignore_prefix_cache,
489
+ )
465
490
  if (
466
491
  dt_short_key is not None
467
492
  and parameter
@@ -479,7 +504,7 @@ class ParameterVisibilityCache:
479
504
  self,
480
505
  channel: hmd.Channel,
481
506
  paramset_key: ParamsetKey,
482
- parameter: str,
507
+ parameter: TParameterName,
483
508
  custom_only: bool = False,
484
509
  ) -> bool:
485
510
  """
@@ -496,8 +521,7 @@ class ParameterVisibilityCache:
496
521
  # check if parameter is in custom_un_ignore with paramset_key
497
522
  model_l = channel.device.model.lower()
498
523
  # Fast path via per-instance memoization
499
- ch_no_key = channel.no if isinstance(channel.no, int) else (-1 if channel.no is None else channel.no)
500
- if (cache_key := (model_l, ch_no_key, paramset_key, parameter, custom_only)) in self._param_un_ignored_cache:
524
+ if (cache_key := (model_l, channel.no, paramset_key, parameter, custom_only)) in self._param_un_ignored_cache:
501
525
  return self._param_un_ignored_cache[cache_key]
502
526
 
503
527
  search_matrix = (
@@ -529,7 +553,7 @@ class ParameterVisibilityCache:
529
553
  self,
530
554
  channel: hmd.Channel,
531
555
  paramset_key: ParamsetKey,
532
- parameter: str,
556
+ parameter: TParameterName,
533
557
  custom_only: bool = False,
534
558
  ) -> bool:
535
559
  """
@@ -540,13 +564,11 @@ class ParameterVisibilityCache:
540
564
  """
541
565
  if not custom_only:
542
566
  model_l = channel.device.model.lower()
543
- # Resolve matching device type prefix once and cache per model
544
- dt_short_key = self._un_ignore_prefix_cache.get(model_l)
545
- if dt_short_key is None and model_l not in self._un_ignore_prefix_cache:
546
- dt_short_key = next(
547
- (k for k in self._un_ignore_parameters_by_device_paramset_key if model_l.startswith(k)), None
548
- )
549
- self._un_ignore_prefix_cache[model_l] = dt_short_key
567
+ dt_short_key = self._resolve_prefix_key(
568
+ model_l=model_l,
569
+ mapping=self._un_ignore_parameters_by_device_paramset_key,
570
+ cache_dict=self._un_ignore_prefix_cache,
571
+ )
550
572
 
551
573
  # check if parameter is in _RELEVANT_MASTER_PARAMSETS_BY_DEVICE
552
574
  if (
@@ -564,7 +586,7 @@ class ParameterVisibilityCache:
564
586
  )
565
587
 
566
588
  def should_skip_parameter(
567
- self, channel: hmd.Channel, paramset_key: ParamsetKey, parameter: str, parameter_is_un_ignored: bool
589
+ self, channel: hmd.Channel, paramset_key: ParamsetKey, parameter: TParameterName, parameter_is_un_ignored: bool
568
590
  ) -> bool:
569
591
  """Determine if a parameter should be skipped."""
570
592
  if self.parameter_is_ignored(
@@ -587,7 +609,7 @@ class ParameterVisibilityCache:
587
609
 
588
610
  return paramset_key == ParamsetKey.MASTER and not parameter_is_un_ignored
589
611
 
590
- def _process_un_ignore_entries(self, lines: tuple[str, ...]) -> None:
612
+ def _process_un_ignore_entries(self, lines: Iterable[str]) -> None:
591
613
  """Batch process un_ignore entries into cache."""
592
614
  for line in lines:
593
615
  # ignore empty line
@@ -610,17 +632,19 @@ class ParameterVisibilityCache:
610
632
  line,
611
633
  )
612
634
 
613
- def _get_un_ignore_line_details(self, line: str) -> tuple[str, int | str | None, str, ParamsetKey] | str | None:
635
+ def _get_un_ignore_line_details(
636
+ self, line: str
637
+ ) -> tuple[TModelName, TUnIgnoreChannelNo, TParameterName, ParamsetKey] | str | None:
614
638
  """
615
639
  Check the format of the line for un_ignore file.
616
640
 
617
641
  model, channel_no, paramset_key, parameter
618
642
  """
619
643
 
620
- model: str | None = None
621
- channel_no: int | str | None = None
644
+ model: TModelName | None = None
645
+ channel_no: TUnIgnoreChannelNo = None
622
646
  paramset_key: ParamsetKey | None = None
623
- parameter: str | None = None
647
+ parameter: TParameterName | None = None
624
648
 
625
649
  if "@" in line:
626
650
  data = line.split("@")
@@ -687,10 +711,10 @@ class ParameterVisibilityCache:
687
711
 
688
712
  def _add_complex_un_ignore_entry(
689
713
  self,
690
- model: str,
691
- channel_no: int | str | None,
714
+ model: TModelName,
715
+ channel_no: TUnIgnoreChannelNo,
692
716
  paramset_key: ParamsetKey,
693
- parameter: str,
717
+ parameter: TParameterName,
694
718
  ) -> None:
695
719
  """Add line to un ignore cache."""
696
720
  if paramset_key == ParamsetKey.MASTER:
@@ -713,7 +737,7 @@ class ParameterVisibilityCache:
713
737
  self,
714
738
  channel: hmd.Channel,
715
739
  paramset_key: ParamsetKey,
716
- parameter: str,
740
+ parameter: TParameterName,
717
741
  ) -> bool:
718
742
  """
719
743
  Return if parameter should be hidden.
@@ -721,7 +745,7 @@ class ParameterVisibilityCache:
721
745
  This is required to determine the data_point usage.
722
746
  Return only hidden parameters, that are no defined in the un_ignore file.
723
747
  """
724
- return parameter in _HIDDEN_PARAMETERS_SET and not self._parameter_is_un_ignored(
748
+ return parameter in _HIDDEN_PARAMETERS and not self._parameter_is_un_ignored(
725
749
  channel=channel,
726
750
  paramset_key=paramset_key,
727
751
  parameter=parameter,
@@ -744,12 +768,11 @@ class ParameterVisibilityCache:
744
768
  return True
745
769
  model_l = channel.device.model.lower()
746
770
  # Resolve matching device type prefix once and cache per model
747
- dt_short_key = self._relevant_prefix_cache.get(model_l)
748
- if dt_short_key is None and model_l not in self._relevant_prefix_cache:
749
- dt_short_key = next(
750
- (k for k in self._relevant_master_paramsets_by_device if model_l.startswith(k)), None
751
- )
752
- self._relevant_prefix_cache[model_l] = dt_short_key
771
+ dt_short_key = self._resolve_prefix_key(
772
+ model_l=model_l,
773
+ mapping=self._relevant_master_paramsets_by_device,
774
+ cache_dict=self._relevant_prefix_cache,
775
+ )
753
776
  if dt_short_key is not None and channel.no in self._relevant_master_paramsets_by_device[dt_short_key]:
754
777
  return True
755
778
  return False
@@ -766,7 +789,7 @@ def check_ignore_parameters_is_clean() -> bool:
766
789
  [
767
790
  parameter
768
791
  for parameter in get_required_parameters()
769
- if (parameter in _IGNORED_PARAMETERS_SET or _parameter_is_wildcard_ignored(parameter=parameter))
792
+ if (parameter in _IGNORED_PARAMETERS or _parameter_is_wildcard_ignored(parameter=parameter))
770
793
  and parameter not in un_ignore_parameters_by_device
771
794
  ]
772
795
  )