mirror of
https://github.com/home-assistant/core.git
synced 2025-04-29 11:47:50 +00:00

* knx entity CRUD - initial commit - switch * platform dependent schema * coerce empty GA-lists to None * read entity configuration from WS * use entity_id instead of unique_id for lookup * Add device support * Rename KNXEntityStore to KNXConfigStore * fix test after rename * Send schema options for creating / editing entities * Return entity_id after entity creation * remove device_class config in favour of more-info-dialog settings * refactor group address schema for custom selector * Rename GA keys and remove invalid keys from schema * fix rebase * Fix deleting devices and their entities * Validate entity schema in extra step - return validation infos * Use exception to signal validation error; return validated data * Forward validation result when editing entities * Get proper validation error message for optional GAs * Add entity validation only WS command * use ulid instead of uuid * Fix error handling for edit unknown entity * Remove unused optional group address sets from validated schema * Add optional dpt field for ga_schema * Move knx config things to sub-key * Add light platform * async_forward_entry_setups only once * Test crate and remove devices * Test removing entities of a removed device * Test entity creation and storage * Test deleting entities * Test unsuccessful entity creation * Test updating entity data * Test get entity config * Test validate entity * Update entity data by entity_id instead of unique_id * Remove unnecessary uid unique check * remove schema_options * test fixture for entity creation * clean up group address schema class can be used to add custom serializer later * Revert: Add light platfrom * remove unused optional_ga_schema * Test GASelector * lint tests * Review * group entities before adding * fix / ignore mypy * always has_entity_name * Entity name: check for empty string when no device * use constants instead of strings in schema * Fix mypy errors for voluptuous schemas --------- Co-authored-by: Marc Mueller <30130371+cdce8p@users.noreply.github.com>
106 lines
3.4 KiB
Python
106 lines
3.4 KiB
Python
"""Validation helpers for KNX config schemas."""
|
|
|
|
from collections.abc import Callable
|
|
import ipaddress
|
|
from typing import Any
|
|
|
|
import voluptuous as vol
|
|
from xknx.dpt import DPTBase, DPTNumeric, DPTString
|
|
from xknx.exceptions import CouldNotParseAddress
|
|
from xknx.telegram.address import IndividualAddress, parse_device_group_address
|
|
|
|
import homeassistant.helpers.config_validation as cv
|
|
|
|
|
|
def dpt_subclass_validator(dpt_base_class: type[DPTBase]) -> Callable[[Any], str | int]:
|
|
"""Validate that value is parsable as given sensor type."""
|
|
|
|
def dpt_value_validator(value: Any) -> str | int:
|
|
"""Validate that value is parsable as sensor type."""
|
|
if (
|
|
isinstance(value, (str, int))
|
|
and dpt_base_class.parse_transcoder(value) is not None
|
|
):
|
|
return value
|
|
raise vol.Invalid(
|
|
f"type '{value}' is not a valid DPT identifier for"
|
|
f" {dpt_base_class.__name__}."
|
|
)
|
|
|
|
return dpt_value_validator
|
|
|
|
|
|
numeric_type_validator = dpt_subclass_validator(DPTNumeric) # type: ignore[type-abstract]
|
|
sensor_type_validator = dpt_subclass_validator(DPTBase) # type: ignore[type-abstract]
|
|
string_type_validator = dpt_subclass_validator(DPTString)
|
|
|
|
|
|
def ga_validator(value: Any) -> str | int:
|
|
"""Validate that value is parsable as GroupAddress or InternalGroupAddress."""
|
|
if not isinstance(value, (str, int)):
|
|
raise vol.Invalid(
|
|
f"'{value}' is not a valid KNX group address: Invalid type '{type(value).__name__}'"
|
|
)
|
|
try:
|
|
parse_device_group_address(value)
|
|
except CouldNotParseAddress as exc:
|
|
raise vol.Invalid(
|
|
f"'{value}' is not a valid KNX group address: {exc.message}"
|
|
) from exc
|
|
return value
|
|
|
|
|
|
def maybe_ga_validator(value: Any) -> str | int | None:
|
|
"""Validate a group address or None."""
|
|
# this is a version of vol.Maybe(ga_validator) that delivers the
|
|
# error message of ga_validator if validation fails.
|
|
return ga_validator(value) if value is not None else None
|
|
|
|
|
|
ga_list_validator = vol.All(
|
|
cv.ensure_list,
|
|
[ga_validator],
|
|
vol.IsTrue("value must be a group address or a list containing group addresses"),
|
|
)
|
|
|
|
ga_list_validator_optional = vol.Maybe(
|
|
vol.All(
|
|
cv.ensure_list,
|
|
[ga_validator],
|
|
vol.Any(vol.IsTrue(), vol.SetTo(None)), # avoid empty lists -> None
|
|
)
|
|
)
|
|
|
|
ia_validator = vol.Any(
|
|
vol.All(str, str.strip, cv.matches_regex(IndividualAddress.ADDRESS_RE.pattern)),
|
|
vol.All(vol.Coerce(int), vol.Range(min=1, max=65535)),
|
|
msg=(
|
|
"value does not match pattern for KNX individual address"
|
|
" '<area>.<line>.<device>' (eg.'1.1.100')"
|
|
),
|
|
)
|
|
|
|
|
|
def ip_v4_validator(value: Any, multicast: bool | None = None) -> str:
|
|
"""Validate that value is parsable as IPv4 address.
|
|
|
|
Optionally check if address is in a reserved multicast block or is explicitly not.
|
|
"""
|
|
try:
|
|
address = ipaddress.IPv4Address(value)
|
|
except ipaddress.AddressValueError as ex:
|
|
raise vol.Invalid(f"value '{value}' is not a valid IPv4 address: {ex}") from ex
|
|
if multicast is not None and address.is_multicast != multicast:
|
|
raise vol.Invalid(
|
|
f"value '{value}' is not a valid IPv4"
|
|
f" {'multicast' if multicast else 'unicast'} address"
|
|
)
|
|
return str(address)
|
|
|
|
|
|
sync_state_validator = vol.Any(
|
|
vol.All(vol.Coerce(int), vol.Range(min=2, max=1440)),
|
|
cv.boolean,
|
|
cv.matches_regex(r"^(init|expire|every)( \d*)?$"),
|
|
)
|