diff --git a/homeassistant/components/knx/const.py b/homeassistant/components/knx/const.py index 41dc9e10b1487..8a751ebfe0c98 100644 --- a/homeassistant/components/knx/const.py +++ b/homeassistant/components/knx/const.py @@ -162,8 +162,11 @@ class FanZeroMode(StrEnum): Platform.BINARY_SENSOR, Platform.CLIMATE, Platform.COVER, + Platform.DATE, + Platform.DATETIME, Platform.LIGHT, Platform.SWITCH, + Platform.TIME, } # Map KNX controller modes to HA modes. This list might not be complete. diff --git a/homeassistant/components/knx/date.py b/homeassistant/components/knx/date.py index a4fc8d276bce9..5ac84ae6543e9 100644 --- a/homeassistant/components/knx/date.py +++ b/homeassistant/components/knx/date.py @@ -3,8 +3,8 @@ from __future__ import annotations from datetime import date as dt_date +from typing import Any -from xknx import XKNX from xknx.devices import DateDevice as XknxDateDevice from xknx.dpt.dpt_11 import KNXDate as XKNXDate @@ -18,7 +18,10 @@ Platform, ) from homeassistant.core import HomeAssistant -from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback +from homeassistant.helpers.entity_platform import ( + AddConfigEntryEntitiesCallback, + async_get_current_platform, +) from homeassistant.helpers.restore_state import RestoreEntity from homeassistant.helpers.typing import ConfigType @@ -26,11 +29,14 @@ CONF_RESPOND_TO_READ, CONF_STATE_ADDRESS, CONF_SYNC_STATE, + DOMAIN, KNX_ADDRESS, KNX_MODULE_KEY, ) -from .entity import KnxYamlEntity +from .entity import KnxUiEntity, KnxUiEntityPlatformController, KnxYamlEntity from .knx_module import KNXModule +from .storage.const import CONF_ENTITY, CONF_GA_DATE +from .storage.util import ConfigExtractor async def async_setup_entry( @@ -40,40 +46,36 @@ async def async_setup_entry( ) -> None: """Set up entities for KNX platform.""" knx_module = hass.data[KNX_MODULE_KEY] - config: list[ConfigType] = knx_module.config_yaml[Platform.DATE] - - async_add_entities( - KNXDateEntity(knx_module, entity_config) for entity_config in config + platform = async_get_current_platform() + knx_module.config_store.add_platform( + platform=Platform.DATE, + controller=KnxUiEntityPlatformController( + knx_module=knx_module, + entity_platform=platform, + entity_class=KnxUiDate, + ), ) - -def _create_xknx_device(xknx: XKNX, config: ConfigType) -> XknxDateDevice: - """Return a XKNX DateTime object to be used within XKNX.""" - return XknxDateDevice( - xknx, - name=config[CONF_NAME], - localtime=False, - group_address=config[KNX_ADDRESS], - group_address_state=config.get(CONF_STATE_ADDRESS), - respond_to_read=config[CONF_RESPOND_TO_READ], - sync_state=config[CONF_SYNC_STATE], - ) + entities: list[KnxYamlEntity | KnxUiEntity] = [] + if yaml_platform_config := knx_module.config_yaml.get(Platform.DATE): + entities.extend( + KnxYamlDate(knx_module, entity_config) + for entity_config in yaml_platform_config + ) + if ui_config := knx_module.config_store.data["entities"].get(Platform.DATE): + entities.extend( + KnxUiDate(knx_module, unique_id, config) + for unique_id, config in ui_config.items() + ) + if entities: + async_add_entities(entities) -class KNXDateEntity(KnxYamlEntity, DateEntity, RestoreEntity): +class _KNXDate(DateEntity, RestoreEntity): """Representation of a KNX date.""" _device: XknxDateDevice - def __init__(self, knx_module: KNXModule, config: ConfigType) -> None: - """Initialize a KNX time.""" - super().__init__( - knx_module=knx_module, - device=_create_xknx_device(knx_module.xknx, config), - ) - self._attr_entity_category = config.get(CONF_ENTITY_CATEGORY) - self._attr_unique_id = str(self._device.remote_value.group_address) - async def async_added_to_hass(self) -> None: """Restore last state.""" await super().async_added_to_hass() @@ -94,3 +96,52 @@ def native_value(self) -> dt_date | None: async def async_set_value(self, value: dt_date) -> None: """Change the value.""" await self._device.set(value) + + +class KnxYamlDate(_KNXDate, KnxYamlEntity): + """Representation of a KNX date configured from YAML.""" + + _device: XknxDateDevice + + def __init__(self, knx_module: KNXModule, config: ConfigType) -> None: + """Initialize a KNX date.""" + super().__init__( + knx_module=knx_module, + device=XknxDateDevice( + knx_module.xknx, + name=config[CONF_NAME], + localtime=False, + group_address=config[KNX_ADDRESS], + group_address_state=config.get(CONF_STATE_ADDRESS), + respond_to_read=config[CONF_RESPOND_TO_READ], + sync_state=config[CONF_SYNC_STATE], + ), + ) + self._attr_entity_category = config.get(CONF_ENTITY_CATEGORY) + self._attr_unique_id = str(self._device.remote_value.group_address) + + +class KnxUiDate(_KNXDate, KnxUiEntity): + """Representation of a KNX date configured from the UI.""" + + _device: XknxDateDevice + + def __init__( + self, knx_module: KNXModule, unique_id: str, config: dict[str, Any] + ) -> None: + """Initialize KNX date.""" + super().__init__( + knx_module=knx_module, + unique_id=unique_id, + entity_config=config[CONF_ENTITY], + ) + knx_conf = ConfigExtractor(config[DOMAIN]) + self._device = XknxDateDevice( + knx_module.xknx, + name=config[CONF_ENTITY][CONF_NAME], + localtime=False, + group_address=knx_conf.get_write(CONF_GA_DATE), + group_address_state=knx_conf.get_state_and_passive(CONF_GA_DATE), + respond_to_read=knx_conf.get(CONF_RESPOND_TO_READ), + sync_state=knx_conf.get(CONF_SYNC_STATE), + ) diff --git a/homeassistant/components/knx/datetime.py b/homeassistant/components/knx/datetime.py index 04d04527241bb..4d7e72c097a43 100644 --- a/homeassistant/components/knx/datetime.py +++ b/homeassistant/components/knx/datetime.py @@ -3,8 +3,8 @@ from __future__ import annotations from datetime import datetime +from typing import Any -from xknx import XKNX from xknx.devices import DateTimeDevice as XknxDateTimeDevice from xknx.dpt.dpt_19 import KNXDateTime as XKNXDateTime @@ -18,7 +18,10 @@ Platform, ) from homeassistant.core import HomeAssistant -from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback +from homeassistant.helpers.entity_platform import ( + AddConfigEntryEntitiesCallback, + async_get_current_platform, +) from homeassistant.helpers.restore_state import RestoreEntity from homeassistant.helpers.typing import ConfigType from homeassistant.util import dt as dt_util @@ -27,11 +30,14 @@ CONF_RESPOND_TO_READ, CONF_STATE_ADDRESS, CONF_SYNC_STATE, + DOMAIN, KNX_ADDRESS, KNX_MODULE_KEY, ) -from .entity import KnxYamlEntity +from .entity import KnxUiEntity, KnxUiEntityPlatformController, KnxYamlEntity from .knx_module import KNXModule +from .storage.const import CONF_ENTITY, CONF_GA_DATETIME +from .storage.util import ConfigExtractor async def async_setup_entry( @@ -41,40 +47,36 @@ async def async_setup_entry( ) -> None: """Set up entities for KNX platform.""" knx_module = hass.data[KNX_MODULE_KEY] - config: list[ConfigType] = knx_module.config_yaml[Platform.DATETIME] - - async_add_entities( - KNXDateTimeEntity(knx_module, entity_config) for entity_config in config + platform = async_get_current_platform() + knx_module.config_store.add_platform( + platform=Platform.DATETIME, + controller=KnxUiEntityPlatformController( + knx_module=knx_module, + entity_platform=platform, + entity_class=KnxUiDateTime, + ), ) - -def _create_xknx_device(xknx: XKNX, config: ConfigType) -> XknxDateTimeDevice: - """Return a XKNX DateTime object to be used within XKNX.""" - return XknxDateTimeDevice( - xknx, - name=config[CONF_NAME], - localtime=False, - group_address=config[KNX_ADDRESS], - group_address_state=config.get(CONF_STATE_ADDRESS), - respond_to_read=config[CONF_RESPOND_TO_READ], - sync_state=config[CONF_SYNC_STATE], - ) + entities: list[KnxYamlEntity | KnxUiEntity] = [] + if yaml_platform_config := knx_module.config_yaml.get(Platform.DATETIME): + entities.extend( + KnxYamlDateTime(knx_module, entity_config) + for entity_config in yaml_platform_config + ) + if ui_config := knx_module.config_store.data["entities"].get(Platform.DATETIME): + entities.extend( + KnxUiDateTime(knx_module, unique_id, config) + for unique_id, config in ui_config.items() + ) + if entities: + async_add_entities(entities) -class KNXDateTimeEntity(KnxYamlEntity, DateTimeEntity, RestoreEntity): +class _KNXDateTime(DateTimeEntity, RestoreEntity): """Representation of a KNX datetime.""" _device: XknxDateTimeDevice - def __init__(self, knx_module: KNXModule, config: ConfigType) -> None: - """Initialize a KNX time.""" - super().__init__( - knx_module=knx_module, - device=_create_xknx_device(knx_module.xknx, config), - ) - self._attr_entity_category = config.get(CONF_ENTITY_CATEGORY) - self._attr_unique_id = str(self._device.remote_value.group_address) - async def async_added_to_hass(self) -> None: """Restore last state.""" await super().async_added_to_hass() @@ -99,3 +101,52 @@ def native_value(self) -> datetime | None: async def async_set_value(self, value: datetime) -> None: """Change the value.""" await self._device.set(value.astimezone(dt_util.get_default_time_zone())) + + +class KnxYamlDateTime(_KNXDateTime, KnxYamlEntity): + """Representation of a KNX datetime configured from YAML.""" + + _device: XknxDateTimeDevice + + def __init__(self, knx_module: KNXModule, config: ConfigType) -> None: + """Initialize a KNX datetime.""" + super().__init__( + knx_module=knx_module, + device=XknxDateTimeDevice( + knx_module.xknx, + name=config[CONF_NAME], + localtime=False, + group_address=config[KNX_ADDRESS], + group_address_state=config.get(CONF_STATE_ADDRESS), + respond_to_read=config[CONF_RESPOND_TO_READ], + sync_state=config[CONF_SYNC_STATE], + ), + ) + self._attr_entity_category = config.get(CONF_ENTITY_CATEGORY) + self._attr_unique_id = str(self._device.remote_value.group_address) + + +class KnxUiDateTime(_KNXDateTime, KnxUiEntity): + """Representation of a KNX datetime configured from the UI.""" + + _device: XknxDateTimeDevice + + def __init__( + self, knx_module: KNXModule, unique_id: str, config: dict[str, Any] + ) -> None: + """Initialize KNX datetime.""" + super().__init__( + knx_module=knx_module, + unique_id=unique_id, + entity_config=config[CONF_ENTITY], + ) + knx_conf = ConfigExtractor(config[DOMAIN]) + self._device = XknxDateTimeDevice( + knx_module.xknx, + name=config[CONF_ENTITY][CONF_NAME], + localtime=False, + group_address=knx_conf.get_write(CONF_GA_DATETIME), + group_address_state=knx_conf.get_state_and_passive(CONF_GA_DATETIME), + respond_to_read=knx_conf.get(CONF_RESPOND_TO_READ), + sync_state=knx_conf.get(CONF_SYNC_STATE), + ) diff --git a/homeassistant/components/knx/storage/const.py b/homeassistant/components/knx/storage/const.py index 5b092e00d2e62..eac1dde1f10b4 100644 --- a/homeassistant/components/knx/storage/const.py +++ b/homeassistant/components/knx/storage/const.py @@ -13,6 +13,9 @@ CONF_GA_SENSOR: Final = "ga_sensor" CONF_GA_SWITCH: Final = "ga_switch" +CONF_GA_DATE: Final = "ga_date" +CONF_GA_DATETIME: Final = "ga_datetime" +CONF_GA_TIME: Final = "ga_time" # Climate CONF_GA_TEMPERATURE_CURRENT: Final = "ga_temperature_current" diff --git a/homeassistant/components/knx/storage/entity_store_schema.py b/homeassistant/components/knx/storage/entity_store_schema.py index fbb5deb1b790b..7b742b63b598a 100644 --- a/homeassistant/components/knx/storage/entity_store_schema.py +++ b/homeassistant/components/knx/storage/entity_store_schema.py @@ -46,6 +46,8 @@ CONF_GA_COLOR_TEMP, CONF_GA_CONTROLLER_MODE, CONF_GA_CONTROLLER_STATUS, + CONF_GA_DATE, + CONF_GA_DATETIME, CONF_GA_FAN_SPEED, CONF_GA_FAN_SWING, CONF_GA_FAN_SWING_HORIZONTAL, @@ -72,6 +74,7 @@ CONF_GA_SWITCH, CONF_GA_TEMPERATURE_CURRENT, CONF_GA_TEMPERATURE_TARGET, + CONF_GA_TIME, CONF_GA_UP_DOWN, CONF_GA_VALVE, CONF_GA_WHITE_BRIGHTNESS, @@ -199,6 +202,24 @@ ), ) +DATE_KNX_SCHEMA = vol.Schema( + { + vol.Required(CONF_GA_DATE): GASelector(write_required=True, valid_dpt="11.001"), + vol.Optional(CONF_RESPOND_TO_READ, default=False): selector.BooleanSelector(), + vol.Optional(CONF_SYNC_STATE, default=True): SyncStateSelector(), + } +) + +DATETIME_KNX_SCHEMA = vol.Schema( + { + vol.Required(CONF_GA_DATETIME): GASelector( + write_required=True, valid_dpt="19.001" + ), + vol.Optional(CONF_RESPOND_TO_READ, default=False): selector.BooleanSelector(), + vol.Optional(CONF_SYNC_STATE, default=True): SyncStateSelector(), + } +) + @unique class LightColorMode(StrEnum): @@ -336,6 +357,14 @@ class LightColorMode(StrEnum): }, ) +TIME_KNX_SCHEMA = vol.Schema( + { + vol.Required(CONF_GA_TIME): GASelector(write_required=True, valid_dpt="10.001"), + vol.Optional(CONF_RESPOND_TO_READ, default=False): selector.BooleanSelector(), + vol.Optional(CONF_SYNC_STATE, default=True): SyncStateSelector(), + } +) + @unique class ConfSetpointShiftMode(StrEnum): @@ -482,8 +511,11 @@ class ConfClimateFanSpeedMode(StrEnum): Platform.BINARY_SENSOR: BINARY_SENSOR_KNX_SCHEMA, Platform.CLIMATE: CLIMATE_KNX_SCHEMA, Platform.COVER: COVER_KNX_SCHEMA, + Platform.DATE: DATE_KNX_SCHEMA, + Platform.DATETIME: DATETIME_KNX_SCHEMA, Platform.LIGHT: LIGHT_KNX_SCHEMA, Platform.SWITCH: SWITCH_KNX_SCHEMA, + Platform.TIME: TIME_KNX_SCHEMA, } ENTITY_STORE_DATA_SCHEMA: VolSchemaType = vol.All( diff --git a/homeassistant/components/knx/strings.json b/homeassistant/components/knx/strings.json index fc6c04b318b9c..6c61a737570fe 100644 --- a/homeassistant/components/knx/strings.json +++ b/homeassistant/components/knx/strings.json @@ -176,6 +176,10 @@ "state_address": "State address", "valid_dpts": "Valid DPTs" }, + "respond_to_read": { + "description": "Respond to GroupValueRead telegrams received to the configured send address.", + "label": "Respond to read" + }, "sync_state": { "description": "Actively request state updates from KNX bus for state addresses.", "options": { @@ -438,6 +442,24 @@ } } }, + "date": { + "description": "The KNX date platform is used as an interface to date objects.", + "knx": { + "ga_date": { + "description": "The group address of the date object.", + "label": "Date" + } + } + }, + "datetime": { + "description": "The KNX datetime platform is used as an interface to date and time objects.", + "knx": { + "ga_datetime": { + "description": "The group address of the date and time object.", + "label": "Date and time" + } + } + }, "header": "Create new entity", "light": { "description": "The KNX light platform is used as an interface to dimming actuators, LED controllers, DALI gateways and similar.", @@ -546,10 +568,15 @@ "invert": { "description": "Invert payloads before processing or sending.", "label": "Invert" - }, - "respond_to_read": { - "description": "Respond to GroupValueRead telegrams received to the configured send address.", - "label": "Respond to read" + } + } + }, + "time": { + "description": "The KNX time platform is used as an interface to time objects.", + "knx": { + "ga_time": { + "description": "The group address of the time object.", + "label": "Time" } } }, diff --git a/homeassistant/components/knx/time.py b/homeassistant/components/knx/time.py index 3bc171cae3186..65078f2af3021 100644 --- a/homeassistant/components/knx/time.py +++ b/homeassistant/components/knx/time.py @@ -3,8 +3,8 @@ from __future__ import annotations from datetime import time as dt_time +from typing import Any -from xknx import XKNX from xknx.devices import TimeDevice as XknxTimeDevice from xknx.dpt.dpt_10 import KNXTime as XknxTime @@ -18,7 +18,10 @@ Platform, ) from homeassistant.core import HomeAssistant -from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback +from homeassistant.helpers.entity_platform import ( + AddConfigEntryEntitiesCallback, + async_get_current_platform, +) from homeassistant.helpers.restore_state import RestoreEntity from homeassistant.helpers.typing import ConfigType @@ -26,11 +29,14 @@ CONF_RESPOND_TO_READ, CONF_STATE_ADDRESS, CONF_SYNC_STATE, + DOMAIN, KNX_ADDRESS, KNX_MODULE_KEY, ) -from .entity import KnxYamlEntity +from .entity import KnxUiEntity, KnxUiEntityPlatformController, KnxYamlEntity from .knx_module import KNXModule +from .storage.const import CONF_ENTITY, CONF_GA_TIME +from .storage.util import ConfigExtractor async def async_setup_entry( @@ -40,40 +46,36 @@ async def async_setup_entry( ) -> None: """Set up entities for KNX platform.""" knx_module = hass.data[KNX_MODULE_KEY] - config: list[ConfigType] = knx_module.config_yaml[Platform.TIME] - - async_add_entities( - KNXTimeEntity(knx_module, entity_config) for entity_config in config + platform = async_get_current_platform() + knx_module.config_store.add_platform( + platform=Platform.TIME, + controller=KnxUiEntityPlatformController( + knx_module=knx_module, + entity_platform=platform, + entity_class=KnxUiTime, + ), ) - -def _create_xknx_device(xknx: XKNX, config: ConfigType) -> XknxTimeDevice: - """Return a XKNX DateTime object to be used within XKNX.""" - return XknxTimeDevice( - xknx, - name=config[CONF_NAME], - localtime=False, - group_address=config[KNX_ADDRESS], - group_address_state=config.get(CONF_STATE_ADDRESS), - respond_to_read=config[CONF_RESPOND_TO_READ], - sync_state=config[CONF_SYNC_STATE], - ) + entities: list[KnxYamlEntity | KnxUiEntity] = [] + if yaml_platform_config := knx_module.config_yaml.get(Platform.TIME): + entities.extend( + KnxYamlTime(knx_module, entity_config) + for entity_config in yaml_platform_config + ) + if ui_config := knx_module.config_store.data["entities"].get(Platform.TIME): + entities.extend( + KnxUiTime(knx_module, unique_id, config) + for unique_id, config in ui_config.items() + ) + if entities: + async_add_entities(entities) -class KNXTimeEntity(KnxYamlEntity, TimeEntity, RestoreEntity): +class _KNXTime(TimeEntity, RestoreEntity): """Representation of a KNX time.""" _device: XknxTimeDevice - def __init__(self, knx_module: KNXModule, config: ConfigType) -> None: - """Initialize a KNX time.""" - super().__init__( - knx_module=knx_module, - device=_create_xknx_device(knx_module.xknx, config), - ) - self._attr_entity_category = config.get(CONF_ENTITY_CATEGORY) - self._attr_unique_id = str(self._device.remote_value.group_address) - async def async_added_to_hass(self) -> None: """Restore last state.""" await super().async_added_to_hass() @@ -94,3 +96,52 @@ def native_value(self) -> dt_time | None: async def async_set_value(self, value: dt_time) -> None: """Change the value.""" await self._device.set(value) + + +class KnxYamlTime(_KNXTime, KnxYamlEntity): + """Representation of a KNX time configured from YAML.""" + + _device: XknxTimeDevice + + def __init__(self, knx_module: KNXModule, config: ConfigType) -> None: + """Initialize a KNX time.""" + super().__init__( + knx_module=knx_module, + device=XknxTimeDevice( + knx_module.xknx, + name=config[CONF_NAME], + localtime=False, + group_address=config[KNX_ADDRESS], + group_address_state=config.get(CONF_STATE_ADDRESS), + respond_to_read=config[CONF_RESPOND_TO_READ], + sync_state=config[CONF_SYNC_STATE], + ), + ) + self._attr_entity_category = config.get(CONF_ENTITY_CATEGORY) + self._attr_unique_id = str(self._device.remote_value.group_address) + + +class KnxUiTime(_KNXTime, KnxUiEntity): + """Representation of a KNX time configured from the UI.""" + + _device: XknxTimeDevice + + def __init__( + self, knx_module: KNXModule, unique_id: str, config: dict[str, Any] + ) -> None: + """Initialize KNX time.""" + super().__init__( + knx_module=knx_module, + unique_id=unique_id, + entity_config=config[CONF_ENTITY], + ) + knx_conf = ConfigExtractor(config[DOMAIN]) + self._device = XknxTimeDevice( + knx_module.xknx, + name=config[CONF_ENTITY][CONF_NAME], + localtime=False, + group_address=knx_conf.get_write(CONF_GA_TIME), + group_address_state=knx_conf.get_state_and_passive(CONF_GA_TIME), + respond_to_read=knx_conf.get(CONF_RESPOND_TO_READ), + sync_state=knx_conf.get(CONF_SYNC_STATE), + ) diff --git a/tests/components/knx/fixtures/config_store_date.json b/tests/components/knx/fixtures/config_store_date.json new file mode 100644 index 0000000000000..69c1549aa84d6 --- /dev/null +++ b/tests/components/knx/fixtures/config_store_date.json @@ -0,0 +1,43 @@ +{ + "version": 2, + "minor_version": 2, + "key": "knx/config_store.json", + "data": { + "entities": { + "date": { + "knx_es_01K76NGZRMJA74CBRQF9KXNPE8": { + "entity": { + "name": "date_with_state_address", + "device_info": null, + "entity_category": null + }, + "knx": { + "ga_date": { + "write": "0/0/1", + "state": "0/0/2", + "passive": [] + }, + "respond_to_read": true, + "sync_state": "init" + } + }, + "knx_es_01K76NGZRMJA74CBRQF9KXNPE9": { + "entity": { + "name": "date_without_state_address", + "device_info": null, + "entity_category": null + }, + "knx": { + "ga_date": { + "write": "0/0/1", + "state": null, + "passive": [] + }, + "respond_to_read": false, + "sync_state": false + } + } + } + } + } +} diff --git a/tests/components/knx/fixtures/config_store_datetime.json b/tests/components/knx/fixtures/config_store_datetime.json new file mode 100644 index 0000000000000..b18446732a34a --- /dev/null +++ b/tests/components/knx/fixtures/config_store_datetime.json @@ -0,0 +1,43 @@ +{ + "version": 2, + "minor_version": 2, + "key": "knx/config_store.json", + "data": { + "entities": { + "datetime": { + "knx_es_01K76NGZRMJA74CBRQF9KXNPE8": { + "entity": { + "name": "datetime_with_state_address", + "device_info": null, + "entity_category": null + }, + "knx": { + "ga_datetime": { + "write": "0/0/1", + "state": "0/0/2", + "passive": [] + }, + "respond_to_read": true, + "sync_state": "init" + } + }, + "knx_es_01K76NGZRMJA74CBRQF9KXNPE9": { + "entity": { + "name": "datetime_without_state_address", + "device_info": null, + "entity_category": null + }, + "knx": { + "ga_datetime": { + "write": "0/0/1", + "state": null, + "passive": [] + }, + "respond_to_read": false, + "sync_state": false + } + } + } + } + } +} diff --git a/tests/components/knx/fixtures/config_store_time.json b/tests/components/knx/fixtures/config_store_time.json new file mode 100644 index 0000000000000..c92f1fedd1620 --- /dev/null +++ b/tests/components/knx/fixtures/config_store_time.json @@ -0,0 +1,43 @@ +{ + "version": 2, + "minor_version": 2, + "key": "knx/config_store.json", + "data": { + "entities": { + "time": { + "knx_es_01K76NGZRMJA74CBRQF9KXNPE8": { + "entity": { + "name": "time_with_state_address", + "device_info": null, + "entity_category": null + }, + "knx": { + "ga_time": { + "write": "0/0/1", + "state": "0/0/2", + "passive": [] + }, + "respond_to_read": true, + "sync_state": "init" + } + }, + "knx_es_01K76NGZRMJA74CBRQF9KXNPE9": { + "entity": { + "name": "time_without_state_address", + "device_info": null, + "entity_category": null + }, + "knx": { + "ga_time": { + "write": "0/0/1", + "state": null, + "passive": [] + }, + "respond_to_read": false, + "sync_state": false + } + } + } + } + } +} diff --git a/tests/components/knx/snapshots/test_websocket.ambr b/tests/components/knx/snapshots/test_websocket.ambr index e532a76c0246d..1896c958877d1 100644 --- a/tests/components/knx/snapshots/test_websocket.ambr +++ b/tests/components/knx/snapshots/test_websocket.ambr @@ -937,6 +937,102 @@ 'type': 'result', }) # --- +# name: test_knx_get_schema[date] + dict({ + 'id': 1, + 'result': list([ + dict({ + 'name': 'ga_date', + 'options': dict({ + 'passive': True, + 'state': dict({ + 'required': False, + }), + 'validDPTs': list([ + dict({ + 'main': 11, + 'sub': 1, + }), + ]), + 'write': dict({ + 'required': True, + }), + }), + 'required': True, + 'type': 'knx_group_address', + }), + dict({ + 'default': False, + 'name': 'respond_to_read', + 'optional': True, + 'required': False, + 'selector': dict({ + 'boolean': dict({ + }), + }), + 'type': 'ha_selector', + }), + dict({ + 'allow_false': False, + 'default': True, + 'name': 'sync_state', + 'optional': True, + 'required': False, + 'type': 'knx_sync_state', + }), + ]), + 'success': True, + 'type': 'result', + }) +# --- +# name: test_knx_get_schema[datetime] + dict({ + 'id': 1, + 'result': list([ + dict({ + 'name': 'ga_datetime', + 'options': dict({ + 'passive': True, + 'state': dict({ + 'required': False, + }), + 'validDPTs': list([ + dict({ + 'main': 19, + 'sub': 1, + }), + ]), + 'write': dict({ + 'required': True, + }), + }), + 'required': True, + 'type': 'knx_group_address', + }), + dict({ + 'default': False, + 'name': 'respond_to_read', + 'optional': True, + 'required': False, + 'selector': dict({ + 'boolean': dict({ + }), + }), + 'type': 'ha_selector', + }), + dict({ + 'allow_false': False, + 'default': True, + 'name': 'sync_state', + 'optional': True, + 'required': False, + 'type': 'knx_sync_state', + }), + ]), + 'success': True, + 'type': 'result', + }) +# --- # name: test_knx_get_schema[light] dict({ 'id': 1, @@ -1405,6 +1501,54 @@ 'type': 'result', }) # --- +# name: test_knx_get_schema[time] + dict({ + 'id': 1, + 'result': list([ + dict({ + 'name': 'ga_time', + 'options': dict({ + 'passive': True, + 'state': dict({ + 'required': False, + }), + 'validDPTs': list([ + dict({ + 'main': 10, + 'sub': 1, + }), + ]), + 'write': dict({ + 'required': True, + }), + }), + 'required': True, + 'type': 'knx_group_address', + }), + dict({ + 'default': False, + 'name': 'respond_to_read', + 'optional': True, + 'required': False, + 'selector': dict({ + 'boolean': dict({ + }), + }), + 'type': 'ha_selector', + }), + dict({ + 'allow_false': False, + 'default': True, + 'name': 'sync_state', + 'optional': True, + 'required': False, + 'type': 'knx_sync_state', + }), + ]), + 'success': True, + 'type': 'result', + }) +# --- # name: test_knx_get_schema[tts] dict({ 'error': dict({ diff --git a/tests/components/knx/test_date.py b/tests/components/knx/test_date.py index 1e6e5102bcf8d..98e35d16db0f0 100644 --- a/tests/components/knx/test_date.py +++ b/tests/components/knx/test_date.py @@ -7,9 +7,10 @@ ) from homeassistant.components.knx.const import CONF_RESPOND_TO_READ, KNX_ADDRESS from homeassistant.components.knx.schema import DateSchema -from homeassistant.const import CONF_NAME +from homeassistant.const import CONF_NAME, Platform from homeassistant.core import HomeAssistant, State +from . import KnxEntityGenerator from .conftest import KNXTestKit from tests.common import mock_restore_cache @@ -89,3 +90,41 @@ async def test_date_restore_and_respond(hass: HomeAssistant, knx: KNXTestKit) -> ) state = hass.states.get("date.test") assert state.state == "2024-02-24" + + +async def test_date_ui_create( + hass: HomeAssistant, + knx: KNXTestKit, + create_ui_entity: KnxEntityGenerator, +) -> None: + """Test creating a date entity.""" + await knx.setup_integration() + await create_ui_entity( + platform=Platform.DATE, + entity_data={"name": "test"}, + knx_data={ + "ga_date": {"write": "0/0/1", "state": "0/0/2"}, + "respond_to_read": True, + "sync_state": True, + }, + ) + # created entity sends a read-request to the read address + await knx.assert_read("0/0/2", response=(0x18, 0x02, 0x18)) + knx.assert_state("date.test", "2024-02-24") + + +async def test_date_ui_load(knx: KNXTestKit) -> None: + """Test loading date entities from storage.""" + await knx.setup_integration(config_store_fixture="config_store_date.json") + + # date_with_state_address + await knx.assert_read("0/0/2", response=(0x18, 0x02, 0x18), ignore_order=True) + + knx.assert_state( + "date.date_with_state_address", + "2024-02-24", + ) + knx.assert_state( + "date.date_without_state_address", + "unknown", + ) diff --git a/tests/components/knx/test_datetime.py b/tests/components/knx/test_datetime.py index 025145ad1a395..b79e8abe8a631 100644 --- a/tests/components/knx/test_datetime.py +++ b/tests/components/knx/test_datetime.py @@ -7,9 +7,10 @@ ) from homeassistant.components.knx.const import CONF_RESPOND_TO_READ, KNX_ADDRESS from homeassistant.components.knx.schema import DateTimeSchema -from homeassistant.const import CONF_NAME +from homeassistant.const import CONF_NAME, Platform from homeassistant.core import HomeAssistant, State +from . import KnxEntityGenerator from .conftest import KNXTestKit from tests.common import mock_restore_cache @@ -93,3 +94,47 @@ async def test_date_restore_and_respond(hass: HomeAssistant, knx: KNXTestKit) -> ) state = hass.states.get("datetime.test") assert state.state == "2020-01-01T18:04:05+00:00" + + +async def test_datetime_ui_create( + hass: HomeAssistant, + knx: KNXTestKit, + create_ui_entity: KnxEntityGenerator, +) -> None: + """Test creating a datetime entity.""" + await knx.setup_integration() + await create_ui_entity( + platform=Platform.DATETIME, + entity_data={"name": "test"}, + knx_data={ + "ga_datetime": {"write": "0/0/1", "state": "0/0/2"}, + "respond_to_read": True, + "sync_state": True, + }, + ) + # created entity sends a read-request to the read address + await knx.assert_read( + "0/0/2", response=(0x7B, 0x07, 0x19, 0x49, 0x28, 0x08, 0x00, 0x00) + ) + knx.assert_state("datetime.test", "2023-07-25T16:40:08+00:00") + + +async def test_datetime_ui_load(knx: KNXTestKit) -> None: + """Test loading datetime entities from storage.""" + await knx.setup_integration(config_store_fixture="config_store_datetime.json") + + # datetime_with_state_address + await knx.assert_read( + "0/0/2", + response=(0x7B, 0x07, 0x19, 0x49, 0x28, 0x08, 0x00, 0x00), + ignore_order=True, + ) + + knx.assert_state( + "datetime.datetime_with_state_address", + "2023-07-25T16:40:08+00:00", + ) + knx.assert_state( + "datetime.datetime_without_state_address", + "unknown", + ) diff --git a/tests/components/knx/test_time.py b/tests/components/knx/test_time.py index 05f84339742f6..08a4edff70f46 100644 --- a/tests/components/knx/test_time.py +++ b/tests/components/knx/test_time.py @@ -7,9 +7,10 @@ DOMAIN as TIME_DOMAIN, SERVICE_SET_VALUE, ) -from homeassistant.const import CONF_NAME +from homeassistant.const import CONF_NAME, Platform from homeassistant.core import HomeAssistant, State +from . import KnxEntityGenerator from .conftest import KNXTestKit from tests.common import mock_restore_cache @@ -89,3 +90,41 @@ async def test_time_restore_and_respond(hass: HomeAssistant, knx: KNXTestKit) -> ) state = hass.states.get("time.test") assert state.state == "12:00:00" + + +async def test_time_ui_create( + hass: HomeAssistant, + knx: KNXTestKit, + create_ui_entity: KnxEntityGenerator, +) -> None: + """Test creating a time entity.""" + await knx.setup_integration() + await create_ui_entity( + platform=Platform.TIME, + entity_data={"name": "test"}, + knx_data={ + "ga_time": {"write": "0/0/1", "state": "0/0/2"}, + "respond_to_read": True, + "sync_state": True, + }, + ) + # created entity sends a read-request to the read address + await knx.assert_read("0/0/2", response=(0x01, 0x02, 0x03)) + knx.assert_state("time.test", "01:02:03") + + +async def test_time_ui_load(knx: KNXTestKit) -> None: + """Test loading time entities from storage.""" + await knx.setup_integration(config_store_fixture="config_store_time.json") + + # time_with_state_address + await knx.assert_read("0/0/2", response=(0x01, 0x02, 0x03), ignore_order=True) + + knx.assert_state( + "time.time_with_state_address", + "01:02:03", + ) + knx.assert_state( + "time.time_without_state_address", + "unknown", + )