aiohomematic 2025.8.6__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 (77) hide show
  1. aiohomematic/__init__.py +47 -0
  2. aiohomematic/async_support.py +146 -0
  3. aiohomematic/caches/__init__.py +10 -0
  4. aiohomematic/caches/dynamic.py +554 -0
  5. aiohomematic/caches/persistent.py +459 -0
  6. aiohomematic/caches/visibility.py +774 -0
  7. aiohomematic/central/__init__.py +2034 -0
  8. aiohomematic/central/decorators.py +110 -0
  9. aiohomematic/central/xml_rpc_server.py +267 -0
  10. aiohomematic/client/__init__.py +1746 -0
  11. aiohomematic/client/json_rpc.py +1193 -0
  12. aiohomematic/client/xml_rpc.py +222 -0
  13. aiohomematic/const.py +795 -0
  14. aiohomematic/context.py +8 -0
  15. aiohomematic/converter.py +82 -0
  16. aiohomematic/decorators.py +188 -0
  17. aiohomematic/exceptions.py +145 -0
  18. aiohomematic/hmcli.py +159 -0
  19. aiohomematic/model/__init__.py +137 -0
  20. aiohomematic/model/calculated/__init__.py +65 -0
  21. aiohomematic/model/calculated/climate.py +230 -0
  22. aiohomematic/model/calculated/data_point.py +319 -0
  23. aiohomematic/model/calculated/operating_voltage_level.py +311 -0
  24. aiohomematic/model/calculated/support.py +174 -0
  25. aiohomematic/model/custom/__init__.py +175 -0
  26. aiohomematic/model/custom/climate.py +1334 -0
  27. aiohomematic/model/custom/const.py +146 -0
  28. aiohomematic/model/custom/cover.py +741 -0
  29. aiohomematic/model/custom/data_point.py +318 -0
  30. aiohomematic/model/custom/definition.py +861 -0
  31. aiohomematic/model/custom/light.py +1092 -0
  32. aiohomematic/model/custom/lock.py +389 -0
  33. aiohomematic/model/custom/siren.py +268 -0
  34. aiohomematic/model/custom/support.py +40 -0
  35. aiohomematic/model/custom/switch.py +172 -0
  36. aiohomematic/model/custom/valve.py +112 -0
  37. aiohomematic/model/data_point.py +1109 -0
  38. aiohomematic/model/decorators.py +173 -0
  39. aiohomematic/model/device.py +1347 -0
  40. aiohomematic/model/event.py +210 -0
  41. aiohomematic/model/generic/__init__.py +211 -0
  42. aiohomematic/model/generic/action.py +32 -0
  43. aiohomematic/model/generic/binary_sensor.py +28 -0
  44. aiohomematic/model/generic/button.py +25 -0
  45. aiohomematic/model/generic/data_point.py +162 -0
  46. aiohomematic/model/generic/number.py +73 -0
  47. aiohomematic/model/generic/select.py +36 -0
  48. aiohomematic/model/generic/sensor.py +72 -0
  49. aiohomematic/model/generic/switch.py +52 -0
  50. aiohomematic/model/generic/text.py +27 -0
  51. aiohomematic/model/hub/__init__.py +334 -0
  52. aiohomematic/model/hub/binary_sensor.py +22 -0
  53. aiohomematic/model/hub/button.py +26 -0
  54. aiohomematic/model/hub/data_point.py +332 -0
  55. aiohomematic/model/hub/number.py +37 -0
  56. aiohomematic/model/hub/select.py +47 -0
  57. aiohomematic/model/hub/sensor.py +35 -0
  58. aiohomematic/model/hub/switch.py +42 -0
  59. aiohomematic/model/hub/text.py +28 -0
  60. aiohomematic/model/support.py +599 -0
  61. aiohomematic/model/update.py +136 -0
  62. aiohomematic/py.typed +0 -0
  63. aiohomematic/rega_scripts/fetch_all_device_data.fn +75 -0
  64. aiohomematic/rega_scripts/get_program_descriptions.fn +30 -0
  65. aiohomematic/rega_scripts/get_serial.fn +44 -0
  66. aiohomematic/rega_scripts/get_system_variable_descriptions.fn +30 -0
  67. aiohomematic/rega_scripts/set_program_state.fn +12 -0
  68. aiohomematic/rega_scripts/set_system_variable.fn +15 -0
  69. aiohomematic/support.py +482 -0
  70. aiohomematic/validator.py +65 -0
  71. aiohomematic-2025.8.6.dist-info/METADATA +69 -0
  72. aiohomematic-2025.8.6.dist-info/RECORD +77 -0
  73. aiohomematic-2025.8.6.dist-info/WHEEL +5 -0
  74. aiohomematic-2025.8.6.dist-info/licenses/LICENSE +21 -0
  75. aiohomematic-2025.8.6.dist-info/top_level.txt +2 -0
  76. aiohomematic_support/__init__.py +1 -0
  77. aiohomematic_support/client_local.py +349 -0
@@ -0,0 +1,861 @@
1
+ """The module contains device descriptions for custom data points."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Mapping
6
+ from copy import deepcopy
7
+ import logging
8
+ from typing import Any, Final, cast
9
+
10
+ import voluptuous as vol
11
+
12
+ from aiohomematic import support as hms, validator as val
13
+ from aiohomematic.const import DataPointCategory, Parameter
14
+ from aiohomematic.exceptions import AioHomematicException
15
+ from aiohomematic.model import device as hmd
16
+ from aiohomematic.model.custom.const import CDPD, DeviceProfile, Field
17
+ from aiohomematic.model.custom.support import CustomConfig
18
+ from aiohomematic.model.support import generate_unique_id
19
+ from aiohomematic.support import extract_exc_args
20
+
21
+ _LOGGER: Final = logging.getLogger(__name__)
22
+
23
+ DEFAULT_INCLUDE_DEFAULT_DPS: Final = True
24
+
25
+ ALL_DEVICES: dict[DataPointCategory, Mapping[str, CustomConfig | tuple[CustomConfig, ...]]] = {}
26
+ ALL_BLACKLISTED_DEVICES: list[tuple[str, ...]] = []
27
+
28
+ _SCHEMA_ADDITIONAL_DPS = vol.Schema(
29
+ {vol.Required(vol.Any(int, tuple[int, ...])): vol.Schema((vol.Optional(Parameter),))}
30
+ )
31
+
32
+ _SCHEMA_FIELD_DETAILS = vol.Schema({vol.Required(Field): Parameter})
33
+
34
+ _SCHEMA_FIELD = vol.Schema({vol.Required(vol.Any(int, None)): _SCHEMA_FIELD_DETAILS})
35
+
36
+ _SCHEMA_DEVICE_GROUP = vol.Schema(
37
+ {
38
+ vol.Required(CDPD.PRIMARY_CHANNEL.value, default=0): vol.Any(val.positive_int, None),
39
+ vol.Required(CDPD.ALLOW_UNDEFINED_GENERIC_DPS.value, default=False): bool,
40
+ vol.Optional(CDPD.STATE_CHANNEL.value): vol.Any(int, None),
41
+ vol.Optional(CDPD.SECONDARY_CHANNELS.value): (val.positive_int,),
42
+ vol.Optional(CDPD.REPEATABLE_FIELDS.value): _SCHEMA_FIELD_DETAILS,
43
+ vol.Optional(CDPD.VISIBLE_REPEATABLE_FIELDS.value): _SCHEMA_FIELD_DETAILS,
44
+ vol.Optional(CDPD.FIELDS.value): _SCHEMA_FIELD,
45
+ vol.Optional(CDPD.VISIBLE_FIELDS.value): _SCHEMA_FIELD,
46
+ }
47
+ )
48
+
49
+ _SCHEMA_DEVICE_GROUPS = vol.Schema(
50
+ {
51
+ vol.Required(CDPD.DEVICE_GROUP.value): _SCHEMA_DEVICE_GROUP,
52
+ vol.Optional(CDPD.ADDITIONAL_DPS.value): _SCHEMA_ADDITIONAL_DPS,
53
+ vol.Optional(CDPD.INCLUDE_DEFAULT_DPS.value, default=DEFAULT_INCLUDE_DEFAULT_DPS): bool,
54
+ }
55
+ )
56
+
57
+ _SCHEMA_DEVICE_DESCRIPTION = vol.Schema(
58
+ {
59
+ vol.Required(CDPD.DEFAULT_DPS.value): _SCHEMA_ADDITIONAL_DPS,
60
+ vol.Required(CDPD.DEVICE_DEFINITIONS.value): vol.Schema(
61
+ {
62
+ vol.Required(DeviceProfile): _SCHEMA_DEVICE_GROUPS,
63
+ }
64
+ ),
65
+ }
66
+ )
67
+
68
+ _CUSTOM_DATA_POINT_DEFINITION: Mapping[CDPD, Mapping[int | DeviceProfile, Any]] = {
69
+ CDPD.DEFAULT_DPS: {
70
+ 0: (
71
+ Parameter.ACTUAL_TEMPERATURE,
72
+ Parameter.DUTY_CYCLE,
73
+ Parameter.DUTYCYCLE,
74
+ Parameter.LOW_BAT,
75
+ Parameter.LOWBAT,
76
+ Parameter.OPERATING_VOLTAGE,
77
+ Parameter.RSSI_DEVICE,
78
+ Parameter.RSSI_PEER,
79
+ Parameter.SABOTAGE,
80
+ Parameter.TIME_OF_OPERATION,
81
+ ),
82
+ 2: (Parameter.BATTERY_STATE,),
83
+ 4: (Parameter.BATTERY_STATE,),
84
+ },
85
+ CDPD.DEVICE_DEFINITIONS: {
86
+ DeviceProfile.IP_BUTTON_LOCK: {
87
+ CDPD.DEVICE_GROUP: {
88
+ CDPD.ALLOW_UNDEFINED_GENERIC_DPS: True,
89
+ CDPD.REPEATABLE_FIELDS: {
90
+ Field.BUTTON_LOCK: Parameter.GLOBAL_BUTTON_LOCK,
91
+ },
92
+ },
93
+ },
94
+ DeviceProfile.IP_COVER: {
95
+ CDPD.DEVICE_GROUP: {
96
+ CDPD.SECONDARY_CHANNELS: (1, 2),
97
+ CDPD.STATE_CHANNEL: -1,
98
+ CDPD.REPEATABLE_FIELDS: {
99
+ Field.COMBINED_PARAMETER: Parameter.COMBINED_PARAMETER,
100
+ Field.LEVEL: Parameter.LEVEL,
101
+ Field.LEVEL_2: Parameter.LEVEL_2,
102
+ Field.STOP: Parameter.STOP,
103
+ },
104
+ CDPD.FIELDS: {
105
+ -1: {
106
+ Field.DIRECTION: Parameter.ACTIVITY_STATE,
107
+ Field.OPERATION_MODE: Parameter.CHANNEL_OPERATION_MODE,
108
+ },
109
+ },
110
+ CDPD.VISIBLE_FIELDS: {
111
+ -1: {
112
+ Field.GROUP_LEVEL: Parameter.LEVEL,
113
+ Field.GROUP_LEVEL_2: Parameter.LEVEL_2,
114
+ },
115
+ },
116
+ },
117
+ },
118
+ DeviceProfile.IP_DIMMER: {
119
+ CDPD.DEVICE_GROUP: {
120
+ CDPD.SECONDARY_CHANNELS: (1, 2),
121
+ CDPD.STATE_CHANNEL: -1,
122
+ CDPD.REPEATABLE_FIELDS: {
123
+ Field.LEVEL: Parameter.LEVEL,
124
+ Field.ON_TIME_VALUE: Parameter.ON_TIME,
125
+ Field.RAMP_TIME_VALUE: Parameter.RAMP_TIME,
126
+ },
127
+ CDPD.VISIBLE_FIELDS: {
128
+ -1: {
129
+ Field.GROUP_LEVEL: Parameter.LEVEL,
130
+ },
131
+ },
132
+ },
133
+ },
134
+ DeviceProfile.IP_GARAGE: {
135
+ CDPD.DEVICE_GROUP: {
136
+ CDPD.REPEATABLE_FIELDS: {
137
+ Field.DOOR_COMMAND: Parameter.DOOR_COMMAND,
138
+ Field.SECTION: Parameter.SECTION,
139
+ },
140
+ CDPD.VISIBLE_REPEATABLE_FIELDS: {
141
+ Field.DOOR_STATE: Parameter.DOOR_STATE,
142
+ },
143
+ },
144
+ CDPD.ADDITIONAL_DPS: {
145
+ 1: (Parameter.STATE,),
146
+ },
147
+ },
148
+ DeviceProfile.IP_HDM: {
149
+ CDPD.DEVICE_GROUP: {
150
+ CDPD.FIELDS: {
151
+ 0: {
152
+ Field.DIRECTION: Parameter.ACTIVITY_STATE,
153
+ Field.LEVEL: Parameter.LEVEL,
154
+ Field.LEVEL_2: Parameter.LEVEL_2,
155
+ Field.STOP: Parameter.STOP,
156
+ },
157
+ },
158
+ },
159
+ },
160
+ DeviceProfile.IP_FIXED_COLOR_LIGHT: {
161
+ CDPD.DEVICE_GROUP: {
162
+ CDPD.SECONDARY_CHANNELS: (1, 2),
163
+ CDPD.STATE_CHANNEL: -1,
164
+ CDPD.REPEATABLE_FIELDS: {
165
+ Field.COLOR: Parameter.COLOR,
166
+ Field.COLOR_BEHAVIOUR: Parameter.COLOR_BEHAVIOUR,
167
+ Field.LEVEL: Parameter.LEVEL,
168
+ Field.ON_TIME_UNIT: Parameter.DURATION_UNIT,
169
+ Field.ON_TIME_VALUE: Parameter.DURATION_VALUE,
170
+ Field.RAMP_TIME_UNIT: Parameter.RAMP_TIME_UNIT,
171
+ Field.RAMP_TIME_VALUE: Parameter.RAMP_TIME_VALUE,
172
+ },
173
+ CDPD.VISIBLE_FIELDS: {
174
+ -1: {
175
+ Field.CHANNEL_COLOR: Parameter.COLOR,
176
+ Field.GROUP_LEVEL: Parameter.LEVEL,
177
+ },
178
+ },
179
+ },
180
+ },
181
+ DeviceProfile.IP_SIMPLE_FIXED_COLOR_LIGHT_WIRED: {
182
+ CDPD.DEVICE_GROUP: {
183
+ CDPD.REPEATABLE_FIELDS: {
184
+ Field.COLOR: Parameter.COLOR,
185
+ Field.COLOR_BEHAVIOUR: Parameter.COLOR_BEHAVIOUR,
186
+ Field.LEVEL: Parameter.LEVEL,
187
+ Field.ON_TIME_UNIT: Parameter.DURATION_UNIT,
188
+ Field.ON_TIME_VALUE: Parameter.DURATION_VALUE,
189
+ Field.RAMP_TIME_UNIT: Parameter.RAMP_TIME_UNIT,
190
+ Field.RAMP_TIME_VALUE: Parameter.RAMP_TIME_VALUE,
191
+ },
192
+ },
193
+ },
194
+ DeviceProfile.IP_SIMPLE_FIXED_COLOR_LIGHT: {
195
+ CDPD.DEVICE_GROUP: {
196
+ CDPD.REPEATABLE_FIELDS: {
197
+ Field.COLOR: Parameter.COLOR,
198
+ Field.LEVEL: Parameter.LEVEL,
199
+ Field.ON_TIME_UNIT: Parameter.DURATION_UNIT,
200
+ Field.ON_TIME_VALUE: Parameter.DURATION_VALUE,
201
+ Field.RAMP_TIME_UNIT: Parameter.RAMP_TIME_UNIT,
202
+ Field.RAMP_TIME_VALUE: Parameter.RAMP_TIME_VALUE,
203
+ },
204
+ },
205
+ },
206
+ DeviceProfile.IP_RGBW_LIGHT: {
207
+ CDPD.DEVICE_GROUP: {
208
+ CDPD.SECONDARY_CHANNELS: (1, 2, 3),
209
+ CDPD.REPEATABLE_FIELDS: {
210
+ Field.COLOR_TEMPERATURE: Parameter.COLOR_TEMPERATURE,
211
+ Field.DIRECTION: Parameter.ACTIVITY_STATE,
212
+ Field.ON_TIME_VALUE: Parameter.DURATION_VALUE,
213
+ Field.ON_TIME_UNIT: Parameter.DURATION_UNIT,
214
+ Field.EFFECT: Parameter.EFFECT,
215
+ Field.HUE: Parameter.HUE,
216
+ Field.LEVEL: Parameter.LEVEL,
217
+ Field.RAMP_TIME_TO_OFF_UNIT: Parameter.RAMP_TIME_TO_OFF_UNIT,
218
+ Field.RAMP_TIME_TO_OFF_VALUE: Parameter.RAMP_TIME_TO_OFF_VALUE,
219
+ Field.RAMP_TIME_UNIT: Parameter.RAMP_TIME_UNIT,
220
+ Field.RAMP_TIME_VALUE: Parameter.RAMP_TIME_VALUE,
221
+ Field.SATURATION: Parameter.SATURATION,
222
+ },
223
+ CDPD.FIELDS: {
224
+ -1: {
225
+ Field.DEVICE_OPERATION_MODE: Parameter.DEVICE_OPERATION_MODE,
226
+ },
227
+ },
228
+ },
229
+ },
230
+ DeviceProfile.IP_DRG_DALI: {
231
+ CDPD.DEVICE_GROUP: {
232
+ CDPD.REPEATABLE_FIELDS: {
233
+ Field.COLOR_TEMPERATURE: Parameter.COLOR_TEMPERATURE,
234
+ Field.ON_TIME_VALUE: Parameter.DURATION_VALUE,
235
+ Field.ON_TIME_UNIT: Parameter.DURATION_UNIT,
236
+ Field.EFFECT: Parameter.EFFECT,
237
+ Field.HUE: Parameter.HUE,
238
+ Field.LEVEL: Parameter.LEVEL,
239
+ Field.RAMP_TIME_TO_OFF_UNIT: Parameter.RAMP_TIME_TO_OFF_UNIT,
240
+ Field.RAMP_TIME_TO_OFF_VALUE: Parameter.RAMP_TIME_TO_OFF_VALUE,
241
+ Field.RAMP_TIME_UNIT: Parameter.RAMP_TIME_UNIT,
242
+ Field.RAMP_TIME_VALUE: Parameter.RAMP_TIME_VALUE,
243
+ Field.SATURATION: Parameter.SATURATION,
244
+ },
245
+ },
246
+ },
247
+ DeviceProfile.IP_IRRIGATION_VALVE: {
248
+ CDPD.DEVICE_GROUP: {
249
+ CDPD.SECONDARY_CHANNELS: (1, 2),
250
+ CDPD.REPEATABLE_FIELDS: {
251
+ Field.STATE: Parameter.STATE,
252
+ Field.ON_TIME_VALUE: Parameter.ON_TIME,
253
+ },
254
+ CDPD.VISIBLE_FIELDS: {
255
+ -1: {
256
+ Field.GROUP_STATE: Parameter.STATE,
257
+ },
258
+ },
259
+ },
260
+ CDPD.ADDITIONAL_DPS: {
261
+ -2: (
262
+ Parameter.WATER_FLOW,
263
+ Parameter.WATER_VOLUME,
264
+ Parameter.WATER_VOLUME_SINCE_OPEN,
265
+ ),
266
+ },
267
+ },
268
+ DeviceProfile.IP_SWITCH: {
269
+ CDPD.DEVICE_GROUP: {
270
+ CDPD.SECONDARY_CHANNELS: (1, 2),
271
+ CDPD.STATE_CHANNEL: -1,
272
+ CDPD.REPEATABLE_FIELDS: {
273
+ Field.STATE: Parameter.STATE,
274
+ Field.ON_TIME_VALUE: Parameter.ON_TIME,
275
+ },
276
+ CDPD.VISIBLE_FIELDS: {
277
+ -1: {
278
+ Field.GROUP_STATE: Parameter.STATE,
279
+ },
280
+ },
281
+ },
282
+ CDPD.ADDITIONAL_DPS: {
283
+ 3: (
284
+ Parameter.CURRENT,
285
+ Parameter.ENERGY_COUNTER,
286
+ Parameter.FREQUENCY,
287
+ Parameter.POWER,
288
+ Parameter.ACTUAL_TEMPERATURE,
289
+ Parameter.VOLTAGE,
290
+ ),
291
+ },
292
+ },
293
+ DeviceProfile.IP_LOCK: {
294
+ CDPD.DEVICE_GROUP: {
295
+ CDPD.REPEATABLE_FIELDS: {
296
+ Field.DIRECTION: Parameter.ACTIVITY_STATE,
297
+ Field.LOCK_STATE: Parameter.LOCK_STATE,
298
+ Field.LOCK_TARGET_LEVEL: Parameter.LOCK_TARGET_LEVEL,
299
+ },
300
+ CDPD.FIELDS: {
301
+ -1: {
302
+ Field.ERROR: Parameter.ERROR_JAMMED,
303
+ },
304
+ },
305
+ },
306
+ },
307
+ DeviceProfile.IP_SIREN: {
308
+ CDPD.DEVICE_GROUP: {
309
+ CDPD.REPEATABLE_FIELDS: {
310
+ Field.ACOUSTIC_ALARM_ACTIVE: Parameter.ACOUSTIC_ALARM_ACTIVE,
311
+ Field.OPTICAL_ALARM_ACTIVE: Parameter.OPTICAL_ALARM_ACTIVE,
312
+ Field.ACOUSTIC_ALARM_SELECTION: Parameter.ACOUSTIC_ALARM_SELECTION,
313
+ Field.OPTICAL_ALARM_SELECTION: Parameter.OPTICAL_ALARM_SELECTION,
314
+ Field.DURATION: Parameter.DURATION_VALUE,
315
+ Field.DURATION_UNIT: Parameter.DURATION_UNIT,
316
+ },
317
+ },
318
+ },
319
+ DeviceProfile.IP_SIREN_SMOKE: {
320
+ CDPD.DEVICE_GROUP: {
321
+ CDPD.REPEATABLE_FIELDS: {
322
+ Field.SMOKE_DETECTOR_COMMAND: Parameter.SMOKE_DETECTOR_COMMAND,
323
+ },
324
+ CDPD.VISIBLE_REPEATABLE_FIELDS: {
325
+ Field.SMOKE_DETECTOR_ALARM_STATUS: Parameter.SMOKE_DETECTOR_ALARM_STATUS,
326
+ },
327
+ },
328
+ },
329
+ DeviceProfile.IP_THERMOSTAT: {
330
+ CDPD.DEVICE_GROUP: {
331
+ CDPD.REPEATABLE_FIELDS: {
332
+ Field.ACTIVE_PROFILE: Parameter.ACTIVE_PROFILE,
333
+ Field.BOOST_MODE: Parameter.BOOST_MODE,
334
+ Field.CONTROL_MODE: Parameter.CONTROL_MODE,
335
+ Field.MIN_MAX_VALUE_NOT_RELEVANT_FOR_MANU_MODE: Parameter.MIN_MAX_VALUE_NOT_RELEVANT_FOR_MANU_MODE,
336
+ Field.OPTIMUM_START_STOP: Parameter.OPTIMUM_START_STOP,
337
+ Field.PARTY_MODE: Parameter.PARTY_MODE,
338
+ Field.SETPOINT: Parameter.SET_POINT_TEMPERATURE,
339
+ Field.SET_POINT_MODE: Parameter.SET_POINT_MODE,
340
+ Field.TEMPERATURE_MAXIMUM: Parameter.TEMPERATURE_MAXIMUM,
341
+ Field.TEMPERATURE_MINIMUM: Parameter.TEMPERATURE_MINIMUM,
342
+ Field.TEMPERATURE_OFFSET: Parameter.TEMPERATURE_OFFSET,
343
+ },
344
+ CDPD.VISIBLE_REPEATABLE_FIELDS: {
345
+ Field.HEATING_COOLING: Parameter.HEATING_COOLING,
346
+ Field.HUMIDITY: Parameter.HUMIDITY,
347
+ Field.TEMPERATURE: Parameter.ACTUAL_TEMPERATURE,
348
+ },
349
+ CDPD.VISIBLE_FIELDS: {
350
+ 0: {
351
+ Field.LEVEL: Parameter.LEVEL,
352
+ Field.CONCENTRATION: Parameter.CONCENTRATION,
353
+ },
354
+ 8: {
355
+ Field.STATE: Parameter.STATE,
356
+ },
357
+ },
358
+ CDPD.FIELDS: {
359
+ 7: {
360
+ Field.HEATING_VALVE_TYPE: Parameter.HEATING_VALVE_TYPE,
361
+ },
362
+ },
363
+ },
364
+ },
365
+ DeviceProfile.IP_THERMOSTAT_GROUP: {
366
+ CDPD.DEVICE_GROUP: {
367
+ CDPD.REPEATABLE_FIELDS: {
368
+ Field.ACTIVE_PROFILE: Parameter.ACTIVE_PROFILE,
369
+ Field.BOOST_MODE: Parameter.BOOST_MODE,
370
+ Field.CONTROL_MODE: Parameter.CONTROL_MODE,
371
+ Field.HEATING_VALVE_TYPE: Parameter.HEATING_VALVE_TYPE,
372
+ Field.MIN_MAX_VALUE_NOT_RELEVANT_FOR_MANU_MODE: Parameter.MIN_MAX_VALUE_NOT_RELEVANT_FOR_MANU_MODE,
373
+ Field.OPTIMUM_START_STOP: Parameter.OPTIMUM_START_STOP,
374
+ Field.PARTY_MODE: Parameter.PARTY_MODE,
375
+ Field.SETPOINT: Parameter.SET_POINT_TEMPERATURE,
376
+ Field.SET_POINT_MODE: Parameter.SET_POINT_MODE,
377
+ Field.TEMPERATURE_MAXIMUM: Parameter.TEMPERATURE_MAXIMUM,
378
+ Field.TEMPERATURE_MINIMUM: Parameter.TEMPERATURE_MINIMUM,
379
+ Field.TEMPERATURE_OFFSET: Parameter.TEMPERATURE_OFFSET,
380
+ },
381
+ CDPD.VISIBLE_REPEATABLE_FIELDS: {
382
+ Field.HEATING_COOLING: Parameter.HEATING_COOLING,
383
+ Field.HUMIDITY: Parameter.HUMIDITY,
384
+ Field.TEMPERATURE: Parameter.ACTUAL_TEMPERATURE,
385
+ },
386
+ CDPD.FIELDS: {
387
+ 0: {
388
+ Field.LEVEL: Parameter.LEVEL,
389
+ },
390
+ 3: {
391
+ Field.STATE: Parameter.STATE,
392
+ },
393
+ },
394
+ },
395
+ CDPD.INCLUDE_DEFAULT_DPS: False,
396
+ },
397
+ DeviceProfile.RF_BUTTON_LOCK: {
398
+ CDPD.DEVICE_GROUP: {
399
+ CDPD.PRIMARY_CHANNEL: None,
400
+ CDPD.ALLOW_UNDEFINED_GENERIC_DPS: True,
401
+ CDPD.REPEATABLE_FIELDS: {
402
+ Field.BUTTON_LOCK: Parameter.GLOBAL_BUTTON_LOCK,
403
+ },
404
+ },
405
+ },
406
+ DeviceProfile.RF_COVER: {
407
+ CDPD.DEVICE_GROUP: {
408
+ CDPD.REPEATABLE_FIELDS: {
409
+ Field.DIRECTION: Parameter.DIRECTION,
410
+ Field.LEVEL: Parameter.LEVEL,
411
+ Field.LEVEL_2: Parameter.LEVEL_SLATS,
412
+ Field.LEVEL_COMBINED: Parameter.LEVEL_COMBINED,
413
+ Field.STOP: Parameter.STOP,
414
+ },
415
+ },
416
+ },
417
+ DeviceProfile.RF_DIMMER: {
418
+ CDPD.DEVICE_GROUP: {
419
+ CDPD.REPEATABLE_FIELDS: {
420
+ Field.LEVEL: Parameter.LEVEL,
421
+ Field.ON_TIME_VALUE: Parameter.ON_TIME,
422
+ Field.RAMP_TIME_VALUE: Parameter.RAMP_TIME,
423
+ },
424
+ },
425
+ },
426
+ DeviceProfile.RF_DIMMER_COLOR: {
427
+ CDPD.DEVICE_GROUP: {
428
+ CDPD.REPEATABLE_FIELDS: {
429
+ Field.LEVEL: Parameter.LEVEL,
430
+ Field.ON_TIME_VALUE: Parameter.ON_TIME,
431
+ Field.RAMP_TIME_VALUE: Parameter.RAMP_TIME,
432
+ },
433
+ CDPD.FIELDS: {
434
+ 1: {
435
+ Field.COLOR: Parameter.COLOR,
436
+ },
437
+ 2: {
438
+ Field.PROGRAM: Parameter.PROGRAM,
439
+ },
440
+ },
441
+ },
442
+ },
443
+ DeviceProfile.RF_DIMMER_COLOR_FIXED: {
444
+ CDPD.DEVICE_GROUP: {
445
+ CDPD.REPEATABLE_FIELDS: {
446
+ Field.LEVEL: Parameter.LEVEL,
447
+ Field.ON_TIME_VALUE: Parameter.ON_TIME,
448
+ Field.RAMP_TIME_VALUE: Parameter.RAMP_TIME,
449
+ },
450
+ },
451
+ },
452
+ DeviceProfile.RF_DIMMER_COLOR_TEMP: {
453
+ CDPD.DEVICE_GROUP: {
454
+ CDPD.REPEATABLE_FIELDS: {
455
+ Field.LEVEL: Parameter.LEVEL,
456
+ Field.ON_TIME_VALUE: Parameter.ON_TIME,
457
+ Field.RAMP_TIME_VALUE: Parameter.RAMP_TIME,
458
+ },
459
+ CDPD.FIELDS: {
460
+ 1: {
461
+ Field.COLOR_LEVEL: Parameter.LEVEL,
462
+ },
463
+ },
464
+ },
465
+ },
466
+ DeviceProfile.RF_DIMMER_WITH_VIRT_CHANNEL: {
467
+ CDPD.DEVICE_GROUP: {
468
+ CDPD.SECONDARY_CHANNELS: (1, 2),
469
+ CDPD.REPEATABLE_FIELDS: {
470
+ Field.LEVEL: Parameter.LEVEL,
471
+ Field.ON_TIME_VALUE: Parameter.ON_TIME,
472
+ Field.RAMP_TIME_VALUE: Parameter.RAMP_TIME,
473
+ },
474
+ },
475
+ },
476
+ DeviceProfile.RF_LOCK: {
477
+ CDPD.DEVICE_GROUP: {
478
+ CDPD.REPEATABLE_FIELDS: {
479
+ Field.DIRECTION: Parameter.DIRECTION,
480
+ Field.OPEN: Parameter.OPEN,
481
+ Field.STATE: Parameter.STATE,
482
+ Field.ERROR: Parameter.ERROR,
483
+ },
484
+ },
485
+ },
486
+ DeviceProfile.RF_SWITCH: {
487
+ CDPD.DEVICE_GROUP: {
488
+ CDPD.REPEATABLE_FIELDS: {
489
+ Field.STATE: Parameter.STATE,
490
+ Field.ON_TIME_VALUE: Parameter.ON_TIME,
491
+ },
492
+ },
493
+ CDPD.ADDITIONAL_DPS: {
494
+ 1: (
495
+ Parameter.CURRENT,
496
+ Parameter.ENERGY_COUNTER,
497
+ Parameter.FREQUENCY,
498
+ Parameter.POWER,
499
+ Parameter.VOLTAGE,
500
+ ),
501
+ },
502
+ },
503
+ DeviceProfile.RF_THERMOSTAT: {
504
+ CDPD.DEVICE_GROUP: {
505
+ CDPD.REPEATABLE_FIELDS: {
506
+ Field.AUTO_MODE: Parameter.AUTO_MODE,
507
+ Field.BOOST_MODE: Parameter.BOOST_MODE,
508
+ Field.COMFORT_MODE: Parameter.COMFORT_MODE,
509
+ Field.CONTROL_MODE: Parameter.CONTROL_MODE,
510
+ Field.LOWERING_MODE: Parameter.LOWERING_MODE,
511
+ Field.MANU_MODE: Parameter.MANU_MODE,
512
+ Field.SETPOINT: Parameter.SET_TEMPERATURE,
513
+ },
514
+ CDPD.FIELDS: {
515
+ None: {
516
+ Field.MIN_MAX_VALUE_NOT_RELEVANT_FOR_MANU_MODE: Parameter.MIN_MAX_VALUE_NOT_RELEVANT_FOR_MANU_MODE,
517
+ Field.TEMPERATURE_MAXIMUM: Parameter.TEMPERATURE_MAXIMUM,
518
+ Field.TEMPERATURE_MINIMUM: Parameter.TEMPERATURE_MINIMUM,
519
+ Field.TEMPERATURE_OFFSET: Parameter.TEMPERATURE_OFFSET,
520
+ Field.WEEK_PROGRAM_POINTER: Parameter.WEEK_PROGRAM_POINTER,
521
+ }
522
+ },
523
+ CDPD.VISIBLE_REPEATABLE_FIELDS: {
524
+ Field.HUMIDITY: Parameter.ACTUAL_HUMIDITY,
525
+ Field.TEMPERATURE: Parameter.ACTUAL_TEMPERATURE,
526
+ },
527
+ CDPD.VISIBLE_FIELDS: {
528
+ 0: {
529
+ Field.VALVE_STATE: Parameter.VALVE_STATE,
530
+ },
531
+ },
532
+ },
533
+ },
534
+ DeviceProfile.RF_THERMOSTAT_GROUP: {
535
+ CDPD.DEVICE_GROUP: {
536
+ CDPD.REPEATABLE_FIELDS: {
537
+ Field.AUTO_MODE: Parameter.AUTO_MODE,
538
+ Field.BOOST_MODE: Parameter.BOOST_MODE,
539
+ Field.COMFORT_MODE: Parameter.COMFORT_MODE,
540
+ Field.CONTROL_MODE: Parameter.CONTROL_MODE,
541
+ Field.LOWERING_MODE: Parameter.LOWERING_MODE,
542
+ Field.MANU_MODE: Parameter.MANU_MODE,
543
+ Field.SETPOINT: Parameter.SET_TEMPERATURE,
544
+ },
545
+ CDPD.FIELDS: {
546
+ None: {
547
+ Field.MIN_MAX_VALUE_NOT_RELEVANT_FOR_MANU_MODE: Parameter.MIN_MAX_VALUE_NOT_RELEVANT_FOR_MANU_MODE,
548
+ Field.TEMPERATURE_MAXIMUM: Parameter.TEMPERATURE_MAXIMUM,
549
+ Field.TEMPERATURE_MINIMUM: Parameter.TEMPERATURE_MINIMUM,
550
+ Field.TEMPERATURE_OFFSET: Parameter.TEMPERATURE_OFFSET,
551
+ Field.WEEK_PROGRAM_POINTER: Parameter.WEEK_PROGRAM_POINTER,
552
+ }
553
+ },
554
+ CDPD.VISIBLE_REPEATABLE_FIELDS: {
555
+ Field.HUMIDITY: Parameter.ACTUAL_HUMIDITY,
556
+ Field.TEMPERATURE: Parameter.ACTUAL_TEMPERATURE,
557
+ },
558
+ CDPD.VISIBLE_FIELDS: {
559
+ 0: {
560
+ Field.VALVE_STATE: Parameter.VALVE_STATE,
561
+ },
562
+ },
563
+ },
564
+ CDPD.INCLUDE_DEFAULT_DPS: False,
565
+ },
566
+ DeviceProfile.SIMPLE_RF_THERMOSTAT: {
567
+ CDPD.DEVICE_GROUP: {
568
+ CDPD.VISIBLE_REPEATABLE_FIELDS: {
569
+ Field.HUMIDITY: Parameter.HUMIDITY,
570
+ Field.TEMPERATURE: Parameter.TEMPERATURE,
571
+ },
572
+ CDPD.FIELDS: {
573
+ 1: {
574
+ Field.SETPOINT: Parameter.SETPOINT,
575
+ },
576
+ },
577
+ },
578
+ },
579
+ },
580
+ }
581
+
582
+ VALID_CUSTOM_DATA_POINT_DEFINITION = _SCHEMA_DEVICE_DESCRIPTION(_CUSTOM_DATA_POINT_DEFINITION)
583
+
584
+
585
+ def validate_custom_data_point_definition() -> Any:
586
+ """Validate the custom data point definition."""
587
+ try:
588
+ return _SCHEMA_DEVICE_DESCRIPTION(_CUSTOM_DATA_POINT_DEFINITION)
589
+ except vol.Invalid as err: # pragma: no cover
590
+ _LOGGER.error("The custom data point definition could not be validated. %s, %s", err.path, err.msg)
591
+ return None
592
+
593
+
594
+ def make_custom_data_point(
595
+ channel: hmd.Channel,
596
+ data_point_class: type,
597
+ device_profile: DeviceProfile,
598
+ custom_config: CustomConfig,
599
+ ) -> None:
600
+ """
601
+ Create custom data point.
602
+
603
+ We use a helper-function to avoid raising exceptions during object-init.
604
+ """
605
+ add_channel_groups_to_device(device=channel.device, device_profile=device_profile, custom_config=custom_config)
606
+ group_no = get_channel_group_no(device=channel.device, channel_no=channel.no)
607
+ channels = _relevant_channels(device_profile=device_profile, custom_config=custom_config)
608
+ if channel.no in set(channels):
609
+ _create_custom_data_point(
610
+ channel=channel,
611
+ custom_data_point_class=data_point_class,
612
+ device_profile=device_profile,
613
+ device_def=_get_device_group(device_profile=device_profile, group_no=group_no),
614
+ custom_data_point_def=_get_device_data_points(device_profile=device_profile, group_no=group_no),
615
+ group_no=group_no,
616
+ custom_config=_rebase_pri_channels(device_profile=device_profile, custom_config=custom_config),
617
+ )
618
+
619
+
620
+ def _create_custom_data_point(
621
+ channel: hmd.Channel,
622
+ custom_data_point_class: type,
623
+ device_profile: DeviceProfile,
624
+ device_def: Mapping[CDPD, Any],
625
+ custom_data_point_def: Mapping[int, tuple[Parameter, ...]],
626
+ group_no: int | None,
627
+ custom_config: CustomConfig,
628
+ ) -> None:
629
+ """Create custom data point."""
630
+ unique_id = generate_unique_id(central=channel.central, address=channel.address)
631
+
632
+ try:
633
+ if (
634
+ dp := custom_data_point_class(
635
+ channel=channel,
636
+ unique_id=unique_id,
637
+ device_profile=device_profile,
638
+ device_def=device_def,
639
+ custom_data_point_def=custom_data_point_def,
640
+ group_no=group_no,
641
+ custom_config=custom_config,
642
+ )
643
+ ) and dp.has_data_points:
644
+ channel.add_data_point(data_point=dp)
645
+ except Exception as exc:
646
+ raise AioHomematicException(
647
+ f"_CREATE_CUSTOM_DATA_POINT: unable to create custom data point: {extract_exc_args(exc=exc)}"
648
+ ) from exc
649
+
650
+
651
+ def _rebase_pri_channels(device_profile: DeviceProfile, custom_config: CustomConfig) -> CustomConfig:
652
+ """Re base primary channel of custom config."""
653
+ device_def = _get_device_group(device_profile, 0)
654
+ if (pri_def := device_def[CDPD.PRIMARY_CHANNEL]) is None:
655
+ return custom_config
656
+ pri_channels = [cu + pri_def for cu in custom_config.channels]
657
+ return CustomConfig(
658
+ make_ce_func=custom_config.make_ce_func,
659
+ channels=tuple(pri_channels),
660
+ extended=custom_config.extended,
661
+ )
662
+
663
+
664
+ def _relevant_channels(device_profile: DeviceProfile, custom_config: CustomConfig) -> tuple[int | None, ...]:
665
+ """Return the relevant channels."""
666
+ device_def = _get_device_group(device_profile, 0)
667
+ def_channels = [device_def[CDPD.PRIMARY_CHANNEL]]
668
+ if sec_channels := device_def.get(CDPD.SECONDARY_CHANNELS):
669
+ def_channels.extend(sec_channels)
670
+
671
+ channels: set[int | None] = set()
672
+ for def_ch in def_channels:
673
+ for conf_ch in custom_config.channels:
674
+ if def_ch is not None and conf_ch is not None:
675
+ channels.add(def_ch + conf_ch)
676
+ else:
677
+ channels.add(None)
678
+ return tuple(channels)
679
+
680
+
681
+ def add_channel_groups_to_device(
682
+ device: hmd.Device, device_profile: DeviceProfile, custom_config: CustomConfig
683
+ ) -> None:
684
+ """Return the relevant channels."""
685
+ device_def = _get_device_group(device_profile, 0)
686
+ if (pri_channel := device_def[CDPD.PRIMARY_CHANNEL]) is None:
687
+ return
688
+ for conf_channel in custom_config.channels:
689
+ if conf_channel is None:
690
+ continue
691
+ group_no = conf_channel + pri_channel
692
+ device.add_channel_to_group(channel_no=group_no, group_no=group_no)
693
+ if state_channel := device_def.get(CDPD.STATE_CHANNEL):
694
+ device.add_channel_to_group(channel_no=conf_channel + state_channel, group_no=group_no)
695
+ if sec_channels := device_def.get(CDPD.SECONDARY_CHANNELS):
696
+ for sec_channel in sec_channels:
697
+ device.add_channel_to_group(channel_no=conf_channel + sec_channel, group_no=group_no)
698
+
699
+
700
+ def get_channel_group_no(device: hmd.Device, channel_no: int | None) -> int | None:
701
+ """Get channel group of sub_device."""
702
+ return device.get_channel_group_no(channel_no=channel_no)
703
+
704
+
705
+ def get_default_data_points() -> Mapping[int | tuple[int, ...], tuple[Parameter, ...]]:
706
+ """Return the default data point."""
707
+ return cast(
708
+ Mapping[int | tuple[int, ...], tuple[Parameter, ...]], VALID_CUSTOM_DATA_POINT_DEFINITION[CDPD.DEFAULT_DPS]
709
+ )
710
+
711
+
712
+ def get_include_default_data_points(device_profile: DeviceProfile) -> bool:
713
+ """Return if default data points should be included."""
714
+ device = _get_device_definition(device_profile)
715
+ return device.get(CDPD.INCLUDE_DEFAULT_DPS, DEFAULT_INCLUDE_DEFAULT_DPS)
716
+
717
+
718
+ def _get_device_definition(device_profile: DeviceProfile) -> Mapping[CDPD, Any]:
719
+ """Return device from data_point definitions."""
720
+ return cast(
721
+ Mapping[CDPD, Any],
722
+ VALID_CUSTOM_DATA_POINT_DEFINITION[CDPD.DEVICE_DEFINITIONS][device_profile],
723
+ )
724
+
725
+
726
+ def _get_device_group(device_profile: DeviceProfile, group_no: int | None) -> Mapping[CDPD, Any]:
727
+ """Return the device group."""
728
+ device = _get_device_definition(device_profile)
729
+ group = cast(dict[CDPD, Any], device[CDPD.DEVICE_GROUP])
730
+ # Create a deep copy of the group due to channel rebase
731
+ group = deepcopy(group)
732
+ if not group_no:
733
+ return group
734
+ # Add group_no to the primary_channel to get the real primary_channel number
735
+ if (primary_channel := group[CDPD.PRIMARY_CHANNEL]) is not None:
736
+ group[CDPD.PRIMARY_CHANNEL] = primary_channel + group_no
737
+
738
+ # Add group_no to the secondary_channels
739
+ # to get the real secondary_channel numbers
740
+ if secondary_channel := group.get(CDPD.SECONDARY_CHANNELS):
741
+ group[CDPD.SECONDARY_CHANNELS] = [x + group_no for x in secondary_channel]
742
+
743
+ group[CDPD.VISIBLE_FIELDS] = _rebase_data_point_dict(
744
+ data_point_dict=CDPD.VISIBLE_FIELDS, group=group, group_no=group_no
745
+ )
746
+ group[CDPD.FIELDS] = _rebase_data_point_dict(data_point_dict=CDPD.FIELDS, group=group, group_no=group_no)
747
+ return group
748
+
749
+
750
+ def _rebase_data_point_dict(
751
+ data_point_dict: CDPD, group: Mapping[CDPD, Any], group_no: int
752
+ ) -> Mapping[int | None, Any]:
753
+ """Rebase data_point_dict with group_no."""
754
+ new_fields: dict[int | None, Any] = {}
755
+ if fields := group.get(data_point_dict):
756
+ for channel_no, field in fields.items():
757
+ if channel_no is None:
758
+ new_fields[channel_no] = field
759
+ else:
760
+ new_fields[channel_no + group_no] = field
761
+ return new_fields
762
+
763
+
764
+ def _get_device_data_points(device_profile: DeviceProfile, group_no: int | None) -> Mapping[int, tuple[Parameter, ...]]:
765
+ """Return the device data points."""
766
+ if (
767
+ additional_dps := VALID_CUSTOM_DATA_POINT_DEFINITION[CDPD.DEVICE_DEFINITIONS]
768
+ .get(device_profile, {})
769
+ .get(CDPD.ADDITIONAL_DPS, {})
770
+ ) and not group_no:
771
+ return cast(Mapping[int, tuple[Parameter, ...]], additional_dps)
772
+ new_dps: dict[int, tuple[Parameter, ...]] = {}
773
+ if additional_dps:
774
+ for channel_no, field in additional_dps.items():
775
+ new_dps[channel_no + group_no] = field
776
+ return new_dps
777
+
778
+
779
+ def get_custom_configs(
780
+ model: str,
781
+ category: DataPointCategory | None = None,
782
+ ) -> tuple[CustomConfig, ...]:
783
+ """Return the data_point configs to create custom data points."""
784
+ model = model.lower().replace("hb-", "hm-")
785
+ custom_configs: list[CustomConfig] = []
786
+ for category_blacklisted_devices in ALL_BLACKLISTED_DEVICES:
787
+ if hms.element_matches_key(
788
+ search_elements=category_blacklisted_devices,
789
+ compare_with=model,
790
+ ):
791
+ return ()
792
+
793
+ for pf, category_devices in ALL_DEVICES.items():
794
+ if category is not None and pf != category:
795
+ continue
796
+ if func := _get_data_point_config_by_category(
797
+ category_devices=category_devices,
798
+ model=model,
799
+ ):
800
+ if isinstance(func, tuple):
801
+ custom_configs.extend(func) # noqa:PERF401
802
+ else:
803
+ custom_configs.append(func)
804
+ return tuple(custom_configs)
805
+
806
+
807
+ def _get_data_point_config_by_category(
808
+ category_devices: Mapping[str, CustomConfig | tuple[CustomConfig, ...]],
809
+ model: str,
810
+ ) -> CustomConfig | tuple[CustomConfig, ...] | None:
811
+ """Return the data_point configs to create custom data points."""
812
+ for d_type, custom_configs in category_devices.items():
813
+ if model.lower() == d_type.lower():
814
+ return custom_configs
815
+
816
+ for d_type, custom_configs in category_devices.items():
817
+ if model.lower().startswith(d_type.lower()):
818
+ return custom_configs
819
+
820
+ return None
821
+
822
+
823
+ def is_multi_channel_device(model: str, category: DataPointCategory) -> bool:
824
+ """Return true, if device has multiple channels."""
825
+ channels: list[int | None] = []
826
+ for custom_config in get_custom_configs(model=model, category=category):
827
+ channels.extend(custom_config.channels)
828
+ return len(channels) > 1
829
+
830
+
831
+ def data_point_definition_exists(model: str) -> bool:
832
+ """Check if device desc exits."""
833
+ return len(get_custom_configs(model)) > 0
834
+
835
+
836
+ def get_required_parameters() -> tuple[Parameter, ...]:
837
+ """Return all required parameters for custom data points."""
838
+ required_parameters: list[Parameter] = []
839
+ for channel in VALID_CUSTOM_DATA_POINT_DEFINITION[CDPD.DEFAULT_DPS]:
840
+ required_parameters.extend(VALID_CUSTOM_DATA_POINT_DEFINITION[CDPD.DEFAULT_DPS][channel])
841
+ for device in VALID_CUSTOM_DATA_POINT_DEFINITION[CDPD.DEVICE_DEFINITIONS]:
842
+ device_def = VALID_CUSTOM_DATA_POINT_DEFINITION[CDPD.DEVICE_DEFINITIONS][device][CDPD.DEVICE_GROUP]
843
+ required_parameters.extend(list(device_def.get(CDPD.REPEATABLE_FIELDS, {}).values()))
844
+ required_parameters.extend(list(device_def.get(CDPD.VISIBLE_REPEATABLE_FIELDS, {}).values()))
845
+ required_parameters.extend(list(device_def.get(CDPD.REPEATABLE_FIELDS, {}).values()))
846
+ for additional_data_points in list(
847
+ VALID_CUSTOM_DATA_POINT_DEFINITION[CDPD.DEVICE_DEFINITIONS][device].get(CDPD.ADDITIONAL_DPS, {}).values()
848
+ ):
849
+ required_parameters.extend(additional_data_points)
850
+
851
+ for category_spec in ALL_DEVICES.values():
852
+ for custom_configs in category_spec.values():
853
+ if isinstance(custom_configs, CustomConfig):
854
+ if extended := custom_configs.extended:
855
+ required_parameters.extend(extended.required_parameters)
856
+ else:
857
+ for custom_config in custom_configs:
858
+ if extended := custom_config.extended:
859
+ required_parameters.extend(extended.required_parameters)
860
+
861
+ return tuple(sorted(set(required_parameters)))