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,599 @@
1
+ """Support for data points used within aiohomematic."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from abc import abstractmethod
6
+ from collections.abc import Mapping
7
+ from enum import StrEnum
8
+ import logging
9
+ from typing import Any, Final
10
+
11
+ from aiohomematic import central as hmcu
12
+ from aiohomematic.const import (
13
+ ADDRESS_SEPARATOR,
14
+ PROGRAM_ADDRESS,
15
+ PROGRAM_SET_PATH_ROOT,
16
+ PROGRAM_STATE_PATH_ROOT,
17
+ SET_PATH_ROOT,
18
+ STATE_PATH_ROOT,
19
+ SYSVAR_ADDRESS,
20
+ SYSVAR_SET_PATH_ROOT,
21
+ SYSVAR_STATE_PATH_ROOT,
22
+ SYSVAR_TYPE,
23
+ VIRTDEV_SET_PATH_ROOT,
24
+ VIRTDEV_STATE_PATH_ROOT,
25
+ VIRTUAL_REMOTE_ADDRESSES,
26
+ DataPointUsage,
27
+ Interface,
28
+ ParameterData,
29
+ ParameterType,
30
+ )
31
+ from aiohomematic.model import device as hmd
32
+ from aiohomematic.model.custom import definition as hmed
33
+ from aiohomematic.model.decorators import (
34
+ get_public_attributes_for_config_property,
35
+ get_public_attributes_for_info_property,
36
+ get_public_attributes_for_state_property,
37
+ )
38
+ from aiohomematic.support import to_bool
39
+
40
+ __all__ = [
41
+ "ChannelNameData",
42
+ "DataPointNameData",
43
+ "GenericParameterType",
44
+ "PayloadMixin",
45
+ "check_channel_is_the_only_primary_channel",
46
+ "convert_value",
47
+ "generate_channel_unique_id",
48
+ "generate_unique_id",
49
+ "get_channel_name_data",
50
+ "get_custom_data_point_name",
51
+ "get_device_name",
52
+ "get_data_point_name_data",
53
+ "get_event_name",
54
+ "get_index_of_value_from_value_list",
55
+ "get_value_from_value_list",
56
+ "is_binary_sensor",
57
+ ]
58
+ _LOGGER: Final = logging.getLogger(__name__)
59
+
60
+ type GenericParameterType = bool | int | float | str | None
61
+
62
+ # dict with binary_sensor relevant value lists and the corresponding TRUE value
63
+ _BINARY_SENSOR_TRUE_VALUE_DICT_FOR_VALUE_LIST: Final[Mapping[tuple[str, ...], str]] = {
64
+ ("CLOSED", "OPEN"): "OPEN",
65
+ ("DRY", "RAIN"): "RAIN",
66
+ ("STABLE", "NOT_STABLE"): "NOT_STABLE",
67
+ }
68
+
69
+
70
+ class PayloadMixin:
71
+ """Mixin to add payload methods to class."""
72
+
73
+ __slots__ = ()
74
+
75
+ @property
76
+ def config_payload(self) -> Mapping[str, Any]:
77
+ """Return the config payload."""
78
+ return {
79
+ key: value
80
+ for key, value in get_public_attributes_for_config_property(data_object=self).items()
81
+ if value is not None
82
+ }
83
+
84
+ @property
85
+ def info_payload(self) -> Mapping[str, Any]:
86
+ """Return the info payload."""
87
+ return {
88
+ key: value
89
+ for key, value in get_public_attributes_for_info_property(data_object=self).items()
90
+ if value is not None
91
+ }
92
+
93
+ @property
94
+ def state_payload(self) -> Mapping[str, Any]:
95
+ """Return the state payload."""
96
+ return {
97
+ key: value
98
+ for key, value in get_public_attributes_for_state_property(data_object=self).items()
99
+ if value is not None
100
+ }
101
+
102
+
103
+ class ChannelNameData:
104
+ """Dataclass for channel name parts."""
105
+
106
+ __slots__ = (
107
+ "channel_name",
108
+ "device_name",
109
+ "full_name",
110
+ "sub_device_name",
111
+ )
112
+
113
+ def __init__(self, device_name: str, channel_name: str) -> None:
114
+ """Init the DataPointNameData class."""
115
+ self.device_name: Final = device_name
116
+ self.channel_name: Final = self._get_channel_name(device_name=device_name, channel_name=channel_name)
117
+ self.full_name = f"{device_name} {self.channel_name}".strip() if self.channel_name else device_name
118
+ self.sub_device_name = channel_name if channel_name else device_name
119
+
120
+ @staticmethod
121
+ def empty() -> ChannelNameData:
122
+ """Return an empty DataPointNameData."""
123
+ return ChannelNameData(device_name="", channel_name="")
124
+
125
+ @staticmethod
126
+ def _get_channel_name(device_name: str, channel_name: str) -> str:
127
+ """Return the channel_name of the data_point only name."""
128
+ if device_name and channel_name and channel_name.startswith(device_name):
129
+ c_name = channel_name.replace(device_name, "").strip()
130
+ if c_name.startswith(ADDRESS_SEPARATOR):
131
+ c_name = c_name[1:]
132
+ return c_name
133
+ return channel_name.strip()
134
+
135
+
136
+ class DataPointNameData(ChannelNameData):
137
+ """Dataclass for data_point name parts."""
138
+
139
+ __slots__ = (
140
+ "name",
141
+ "parameter_name",
142
+ )
143
+
144
+ def __init__(self, device_name: str, channel_name: str, parameter_name: str | None = None) -> None:
145
+ """Init the DataPointNameData class."""
146
+ super().__init__(device_name=device_name, channel_name=channel_name)
147
+
148
+ self.name: Final = self._get_data_point_name(
149
+ device_name=device_name, channel_name=channel_name, parameter_name=parameter_name
150
+ )
151
+ self.full_name = f"{device_name} {self.name}".strip() if self.name else device_name
152
+ self.parameter_name = parameter_name
153
+
154
+ @staticmethod
155
+ def empty() -> DataPointNameData:
156
+ """Return an empty DataPointNameData."""
157
+ return DataPointNameData(device_name="", channel_name="")
158
+
159
+ @staticmethod
160
+ def _get_channel_parameter_name(channel_name: str, parameter_name: str | None) -> str:
161
+ """Return the channel parameter name of the data_point."""
162
+ if channel_name and parameter_name:
163
+ return f"{channel_name} {parameter_name}".strip()
164
+ return channel_name.strip()
165
+
166
+ def _get_data_point_name(self, device_name: str, channel_name: str, parameter_name: str | None) -> str:
167
+ """Return the name of the data_point only name."""
168
+ channel_parameter_name = self._get_channel_parameter_name(
169
+ channel_name=channel_name, parameter_name=parameter_name
170
+ )
171
+ if device_name and channel_parameter_name and channel_parameter_name.startswith(device_name):
172
+ return channel_parameter_name[len(device_name) :].lstrip()
173
+ return channel_parameter_name
174
+
175
+
176
+ class HubNameData:
177
+ """Class for hub data_point name parts."""
178
+
179
+ __slots__ = (
180
+ "full_name",
181
+ "name",
182
+ )
183
+
184
+ def __init__(self, name: str, central_name: str | None = None, channel_name: str | None = None) -> None:
185
+ """Init the DataPointNameData class."""
186
+ self.name: Final = name
187
+ self.full_name = (
188
+ f"{channel_name} {self.name}".strip() if channel_name else f"{central_name} {self.name}".strip()
189
+ )
190
+
191
+ @staticmethod
192
+ def empty() -> HubNameData:
193
+ """Return an empty HubNameData."""
194
+ return HubNameData(name="")
195
+
196
+
197
+ def check_length_and_log(name: str | None, value: Any) -> Any:
198
+ """Check the length of a datapoint and log if too long."""
199
+ if isinstance(value, str) and len(value) > 255:
200
+ _LOGGER.debug(
201
+ "Value of datapoint %s exceedes maximum allowed length of 255 chars. Value will be limited to 255 chars",
202
+ name,
203
+ )
204
+ return value[0:255:1]
205
+ return value
206
+
207
+
208
+ def get_device_name(central: hmcu.CentralUnit, device_address: str, model: str) -> str:
209
+ """Return the cached name for a device, or an auto-generated."""
210
+ if name := central.device_details.get_name(address=device_address):
211
+ return name
212
+
213
+ _LOGGER.debug(
214
+ "GET_DEVICE_NAME: Using auto-generated name for %s %s",
215
+ model,
216
+ device_address,
217
+ )
218
+ return _get_generic_name(address=device_address, model=model)
219
+
220
+
221
+ def _get_generic_name(address: str, model: str) -> str:
222
+ """Return auto-generated device/channel name."""
223
+ return f"{model}_{address}"
224
+
225
+
226
+ def get_channel_name_data(channel: hmd.Channel) -> ChannelNameData:
227
+ """Get name for data_point."""
228
+ if channel_base_name := _get_base_name_from_channel_or_device(channel=channel):
229
+ return ChannelNameData(
230
+ device_name=channel.device.name,
231
+ channel_name=channel_base_name,
232
+ )
233
+
234
+ _LOGGER.debug(
235
+ "GET_CHANNEL_NAME_DATA: Using unique_id for %s %s",
236
+ channel.device.model,
237
+ channel.address,
238
+ )
239
+ return ChannelNameData.empty()
240
+
241
+
242
+ class PathData:
243
+ """The data point path data."""
244
+
245
+ @property
246
+ @abstractmethod
247
+ def set_path(self) -> str:
248
+ """Return the base set path of the data_point."""
249
+
250
+ @property
251
+ @abstractmethod
252
+ def state_path(self) -> str:
253
+ """Return the base state path of the data_point."""
254
+
255
+
256
+ class DataPointPathData(PathData):
257
+ """The data point path data."""
258
+
259
+ __slots__ = (
260
+ "_set_path",
261
+ "_state_path",
262
+ )
263
+
264
+ def __init__(
265
+ self,
266
+ interface: Interface | None,
267
+ address: str,
268
+ channel_no: int | None,
269
+ kind: str,
270
+ name: str | None = None,
271
+ ):
272
+ """Init the path data."""
273
+ path_item: Final = f"{address.upper()}/{channel_no}/{kind.upper()}"
274
+ self._set_path: Final = (
275
+ f"{VIRTDEV_SET_PATH_ROOT if interface == Interface.CCU_JACK else SET_PATH_ROOT}/{path_item}"
276
+ )
277
+ self._state_path: Final = (
278
+ f"{VIRTDEV_STATE_PATH_ROOT if interface == Interface.CCU_JACK else STATE_PATH_ROOT}/{path_item}"
279
+ )
280
+
281
+ @property
282
+ def set_path(self) -> str:
283
+ """Return the base set path of the data_point."""
284
+ return self._set_path
285
+
286
+ @property
287
+ def state_path(self) -> str:
288
+ """Return the base state path of the data_point."""
289
+ return self._state_path
290
+
291
+
292
+ class ProgramPathData(PathData):
293
+ """The program path data."""
294
+
295
+ __slots__ = (
296
+ "_set_path",
297
+ "_state_path",
298
+ )
299
+
300
+ def __init__(self, pid: str):
301
+ """Init the path data."""
302
+ self._set_path: Final = f"{PROGRAM_SET_PATH_ROOT}/{pid}"
303
+ self._state_path: Final = f"{PROGRAM_STATE_PATH_ROOT}/{pid}"
304
+
305
+ @property
306
+ def set_path(self) -> str:
307
+ """Return the base set path of the program."""
308
+ return self._set_path
309
+
310
+ @property
311
+ def state_path(self) -> str:
312
+ """Return the base state path of the program."""
313
+ return self._state_path
314
+
315
+
316
+ class SysvarPathData(PathData):
317
+ """The sysvar path data."""
318
+
319
+ __slots__ = (
320
+ "_set_path",
321
+ "_state_path",
322
+ )
323
+
324
+ def __init__(self, vid: str):
325
+ """Init the path data."""
326
+ self._set_path: Final = f"{SYSVAR_SET_PATH_ROOT}/{vid}"
327
+ self._state_path: Final = f"{SYSVAR_STATE_PATH_ROOT}/{vid}"
328
+
329
+ @property
330
+ def set_path(self) -> str:
331
+ """Return the base set path of the sysvar."""
332
+ return self._set_path
333
+
334
+ @property
335
+ def state_path(self) -> str:
336
+ """Return the base state path of the sysvar."""
337
+ return self._state_path
338
+
339
+
340
+ def get_data_point_name_data(
341
+ channel: hmd.Channel,
342
+ parameter: str,
343
+ ) -> DataPointNameData:
344
+ """Get name for data_point."""
345
+ if channel_name := _get_base_name_from_channel_or_device(channel=channel):
346
+ p_name = parameter.title().replace("_", " ")
347
+
348
+ if _check_channel_name_with_channel_no(name=channel_name):
349
+ c_name = channel_name.split(ADDRESS_SEPARATOR)[0]
350
+ c_postfix = ""
351
+ if channel.central.paramset_descriptions.is_in_multiple_channels(
352
+ channel_address=channel.address, parameter=parameter
353
+ ):
354
+ c_postfix = "" if channel.no in (0, None) else f" ch{channel.no}"
355
+ data_point_name = DataPointNameData(
356
+ device_name=channel.device.name,
357
+ channel_name=c_name,
358
+ parameter_name=f"{p_name}{c_postfix}",
359
+ )
360
+ else:
361
+ data_point_name = DataPointNameData(
362
+ device_name=channel.device.name,
363
+ channel_name=channel_name,
364
+ parameter_name=p_name,
365
+ )
366
+ return data_point_name
367
+
368
+ _LOGGER.debug(
369
+ "GET_DATA_POINT_NAME: Using unique_id for %s %s %s",
370
+ channel.device.model,
371
+ channel.address,
372
+ parameter,
373
+ )
374
+ return DataPointNameData.empty()
375
+
376
+
377
+ def get_hub_data_point_name_data(
378
+ channel: hmd.Channel | None,
379
+ legacy_name: str,
380
+ central_name: str,
381
+ ) -> HubNameData:
382
+ """Get name for hub data_point."""
383
+ if not channel:
384
+ return HubNameData(
385
+ central_name=central_name,
386
+ name=legacy_name,
387
+ )
388
+ if channel_name := _get_base_name_from_channel_or_device(channel=channel):
389
+ p_name = (
390
+ legacy_name.replace("_", " ")
391
+ .replace(channel.address, "")
392
+ .replace(channel.id, "")
393
+ .replace(channel.device.id, "")
394
+ .strip()
395
+ )
396
+
397
+ if _check_channel_name_with_channel_no(name=channel_name):
398
+ channel_name = channel_name.split(":")[0]
399
+
400
+ return HubNameData(channel_name=channel_name, name=p_name)
401
+
402
+ _LOGGER.debug(
403
+ "GET_DATA_POINT_NAME: Using unique_id for %s %s %s",
404
+ channel.device.model,
405
+ channel.address,
406
+ legacy_name,
407
+ )
408
+ return HubNameData.empty()
409
+
410
+
411
+ def get_event_name(
412
+ channel: hmd.Channel,
413
+ parameter: str,
414
+ ) -> DataPointNameData:
415
+ """Get name for event."""
416
+ if channel_name := _get_base_name_from_channel_or_device(channel=channel):
417
+ p_name = parameter.title().replace("_", " ")
418
+ if _check_channel_name_with_channel_no(name=channel_name):
419
+ c_name = "" if channel.no in (0, None) else f" ch{channel.no}"
420
+ event_name = DataPointNameData(
421
+ device_name=channel.device.name,
422
+ channel_name=c_name,
423
+ parameter_name=p_name,
424
+ )
425
+ else:
426
+ event_name = DataPointNameData(
427
+ device_name=channel.device.name,
428
+ channel_name=channel_name,
429
+ parameter_name=p_name,
430
+ )
431
+ return event_name
432
+
433
+ _LOGGER.debug(
434
+ "GET_EVENT_NAME: Using unique_id for %s %s %s",
435
+ channel.device.model,
436
+ channel.address,
437
+ parameter,
438
+ )
439
+ return DataPointNameData.empty()
440
+
441
+
442
+ def get_custom_data_point_name(
443
+ channel: hmd.Channel,
444
+ is_only_primary_channel: bool,
445
+ ignore_multiple_channels_for_name: bool,
446
+ usage: DataPointUsage,
447
+ postfix: str = "",
448
+ ) -> DataPointNameData:
449
+ """Get name for custom data_point."""
450
+ if channel_name := _get_base_name_from_channel_or_device(channel=channel):
451
+ if (is_only_primary_channel or ignore_multiple_channels_for_name) and _check_channel_name_with_channel_no(
452
+ name=channel_name
453
+ ):
454
+ return DataPointNameData(
455
+ device_name=channel.device.name,
456
+ channel_name=channel_name.split(ADDRESS_SEPARATOR)[0],
457
+ parameter_name=postfix,
458
+ )
459
+ if _check_channel_name_with_channel_no(name=channel_name):
460
+ c_name = channel_name.split(ADDRESS_SEPARATOR)[0]
461
+ p_name = channel_name.split(ADDRESS_SEPARATOR)[1]
462
+ marker = "ch" if usage == DataPointUsage.CDP_PRIMARY else "vch"
463
+ p_name = f"{marker}{p_name}"
464
+ return DataPointNameData(device_name=channel.device.name, channel_name=c_name, parameter_name=p_name)
465
+ return DataPointNameData(device_name=channel.device.name, channel_name=channel_name)
466
+
467
+ _LOGGER.debug(
468
+ "GET_CUSTOM_DATA_POINT_NAME: Using unique_id for %s %s %s",
469
+ channel.device.model,
470
+ channel.address,
471
+ channel.no,
472
+ )
473
+ return DataPointNameData.empty()
474
+
475
+
476
+ def generate_unique_id(
477
+ central: hmcu.CentralUnit,
478
+ address: str,
479
+ parameter: str | None = None,
480
+ prefix: str | None = None,
481
+ ) -> str:
482
+ """
483
+ Build unique identifier from address and parameter.
484
+
485
+ Central id is additionally used for heating groups.
486
+ Prefix is used for events and buttons.
487
+ """
488
+ unique_id = address.replace(ADDRESS_SEPARATOR, "_").replace("-", "_")
489
+ if parameter:
490
+ unique_id = f"{unique_id}_{parameter}"
491
+
492
+ if prefix:
493
+ unique_id = f"{prefix}_{unique_id}"
494
+ if (
495
+ address in (PROGRAM_ADDRESS, SYSVAR_ADDRESS)
496
+ or address.startswith("INT000")
497
+ or address.split(ADDRESS_SEPARATOR)[0] in VIRTUAL_REMOTE_ADDRESSES
498
+ ):
499
+ return f"{central.config.central_id}_{unique_id}".lower()
500
+ return f"{unique_id}".lower()
501
+
502
+
503
+ def generate_channel_unique_id(
504
+ central: hmcu.CentralUnit,
505
+ address: str,
506
+ ) -> str:
507
+ """Build unique identifier for a channel from address."""
508
+ unique_id = address.replace(ADDRESS_SEPARATOR, "_").replace("-", "_")
509
+ if address.split(ADDRESS_SEPARATOR)[0] in VIRTUAL_REMOTE_ADDRESSES:
510
+ return f"{central.config.central_id}_{unique_id}".lower()
511
+ return unique_id.lower()
512
+
513
+
514
+ def _get_base_name_from_channel_or_device(channel: hmd.Channel) -> str | None:
515
+ """Get the name from channel if it's not default, otherwise from device."""
516
+ default_channel_name = f"{channel.device.model} {channel.address}"
517
+ name = channel.central.device_details.get_name(address=channel.address)
518
+ if name is None or name == default_channel_name:
519
+ return channel.device.name if channel.no is None else f"{channel.device.name}:{channel.no}"
520
+ return name
521
+
522
+
523
+ def _check_channel_name_with_channel_no(name: str) -> bool:
524
+ """Check if name contains channel and this is an int."""
525
+ if name.count(ADDRESS_SEPARATOR) == 1:
526
+ channel_part = name.split(ADDRESS_SEPARATOR)[1]
527
+ try:
528
+ int(channel_part)
529
+ except ValueError:
530
+ return False
531
+ return True
532
+ return False
533
+
534
+
535
+ def convert_value(value: Any, target_type: ParameterType, value_list: tuple[str, ...] | None) -> Any:
536
+ """Convert a value to target_type."""
537
+ if value is None:
538
+ return None
539
+ if target_type == ParameterType.BOOL:
540
+ if value_list:
541
+ # relevant for ENUMs retyped to a BOOL
542
+ return _get_binary_sensor_value(value=value, value_list=value_list)
543
+ if isinstance(value, str):
544
+ return to_bool(value)
545
+ return bool(value)
546
+ if target_type == ParameterType.FLOAT:
547
+ return float(value)
548
+ if target_type == ParameterType.INTEGER:
549
+ return int(float(value))
550
+ if target_type == ParameterType.STRING:
551
+ return str(value)
552
+ return value
553
+
554
+
555
+ def is_binary_sensor(parameter_data: ParameterData) -> bool:
556
+ """Check, if the sensor is a binary_sensor."""
557
+ if parameter_data["TYPE"] == ParameterType.BOOL:
558
+ return True
559
+ if value_list := parameter_data.get("VALUE_LIST"):
560
+ return tuple(value_list) in _BINARY_SENSOR_TRUE_VALUE_DICT_FOR_VALUE_LIST
561
+ return False
562
+
563
+
564
+ def _get_binary_sensor_value(value: int, value_list: tuple[str, ...]) -> bool:
565
+ """Return, the value of a binary_sensor."""
566
+ try:
567
+ str_value = value_list[value]
568
+ if true_value := _BINARY_SENSOR_TRUE_VALUE_DICT_FOR_VALUE_LIST.get(value_list):
569
+ return str_value == true_value
570
+ except IndexError:
571
+ pass
572
+ return False
573
+
574
+
575
+ def check_channel_is_the_only_primary_channel(
576
+ current_channel_no: int | None,
577
+ device_def: Mapping[str, Any],
578
+ device_has_multiple_channels: bool,
579
+ ) -> bool:
580
+ """Check if this channel is the only primary channel."""
581
+ primary_channel: int = device_def[hmed.CDPD.PRIMARY_CHANNEL]
582
+ return bool(primary_channel == current_channel_no and device_has_multiple_channels is False)
583
+
584
+
585
+ def get_value_from_value_list(value: SYSVAR_TYPE, value_list: tuple[str, ...] | list[str] | None) -> str | None:
586
+ """Check if value is in value list."""
587
+ if value is not None and isinstance(value, int) and value_list is not None and value < len(value_list):
588
+ return value_list[int(value)]
589
+ return None
590
+
591
+
592
+ def get_index_of_value_from_value_list(
593
+ value: SYSVAR_TYPE, value_list: tuple[str, ...] | list[str] | None
594
+ ) -> int | None:
595
+ """Check if value is in value list."""
596
+ if value is not None and isinstance(value, (str, StrEnum)) and value_list is not None and value in value_list:
597
+ return value_list.index(value)
598
+
599
+ return None