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