1 """Validation helpers for KNX config schemas."""
3 from collections.abc
import Callable
8 import voluptuous
as vol
9 from xknx.dpt
import DPTBase, DPTNumeric, DPTString
10 from xknx.exceptions
import CouldNotParseAddress
11 from xknx.telegram.address
import IndividualAddress, parse_device_group_address
17 """Validate that value is parsable as given sensor type."""
19 def dpt_value_validator(value: Any) -> str | int:
20 """Validate that value is parsable as sensor type."""
22 isinstance(value, (str, int))
23 and dpt_base_class.parse_transcoder(value)
is not None
27 f
"type '{value}' is not a valid DPT identifier for"
28 f
" {dpt_base_class.__name__}."
31 return dpt_value_validator
37 sensor_type_validator = vol.Any(numeric_type_validator, string_type_validator)
41 """Validate that value is parsable as GroupAddress or InternalGroupAddress."""
42 if not isinstance(value, (str, int)):
44 f
"'{value}' is not a valid KNX group address: Invalid type '{type(value).__name__}'"
47 parse_device_group_address(value)
48 except CouldNotParseAddress
as exc:
50 f
"'{value}' is not a valid KNX group address: {exc.message}"
56 """Validate a group address or None."""
59 return ga_validator(value)
if value
is not None else None
62 ga_list_validator = vol.All(
65 vol.IsTrue(
"value must be a group address or a list containing group addresses"),
68 ga_list_validator_optional = vol.Maybe(
72 vol.Any(vol.IsTrue(), vol.SetTo(
None)),
76 ia_validator = vol.Any(
77 vol.All(str, str.strip, cv.matches_regex(IndividualAddress.ADDRESS_RE.pattern)),
78 vol.All(vol.Coerce(int), vol.Range(min=1, max=65535)),
80 "value does not match pattern for KNX individual address"
81 " '<area>.<line>.<device>' (eg.'1.1.100')"
87 """Validate that value is parsable as IPv4 address.
89 Optionally check if address is in a reserved multicast block or is explicitly not.
92 address = ipaddress.IPv4Address(value)
93 except ipaddress.AddressValueError
as ex:
94 raise vol.Invalid(f
"value '{value}' is not a valid IPv4 address: {ex}")
from ex
95 if multicast
is not None and address.is_multicast != multicast:
97 f
"value '{value}' is not a valid IPv4"
98 f
" {'multicast' if multicast else 'unicast'} address"
103 sync_state_validator = vol.Any(
104 vol.All(vol.Coerce(int), vol.Range(min=2, max=1440)),
106 cv.matches_regex(
r"^(init|expire|every)( \d*)?$"),
111 """Transform a string to an enum member.
113 Backwards compatible with member names of xknx 2.x climate DPT Enums
114 due to unintentional breaking change in HA 2024.8.
117 def _string_transform(value: Any) -> str:
118 """Upper and slugify string and substitute old member names.
120 Previously this was checked against Enum values instead of names. These
121 looked like `FAN_ONLY = "Fan only"`, therefore the upper & replace part.
123 if not isinstance(value, str):
124 raise vol.Invalid(
"value should be a string")
125 name = value.upper().replace(
" ",
"_")
129 case
"FROST_PROTECTION":
130 return "BUILDING_PROTECTION"
132 return "DEHUMIDIFICATION"
138 vol.In(enumClass.__members__),
139 enumClass.__getitem__,
Callable[[Any], str|int] dpt_subclass_validator(type[DPTBase] dpt_base_class)
str|int|None maybe_ga_validator(Any value)
str|int ga_validator(Any value)
vol.All backwards_compatible_xknx_climate_enum_member(type[Enum] enumClass)
str ip_v4_validator(Any value, bool|None multicast=None)