Skip to content

Commit 9823621

Browse files
authored
Allow to configure KNX time, date & datetime entities via UI (home-assistant#157603)
1 parent 90dc3a8 commit 9823621

File tree

14 files changed

+708
-94
lines changed

14 files changed

+708
-94
lines changed

homeassistant/components/knx/const.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,8 +162,11 @@ class FanZeroMode(StrEnum):
162162
Platform.BINARY_SENSOR,
163163
Platform.CLIMATE,
164164
Platform.COVER,
165+
Platform.DATE,
166+
Platform.DATETIME,
165167
Platform.LIGHT,
166168
Platform.SWITCH,
169+
Platform.TIME,
167170
}
168171

169172
# Map KNX controller modes to HA modes. This list might not be complete.

homeassistant/components/knx/date.py

Lines changed: 80 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
from __future__ import annotations
44

55
from datetime import date as dt_date
6+
from typing import Any
67

7-
from xknx import XKNX
88
from xknx.devices import DateDevice as XknxDateDevice
99
from xknx.dpt.dpt_11 import KNXDate as XKNXDate
1010

@@ -18,19 +18,25 @@
1818
Platform,
1919
)
2020
from homeassistant.core import HomeAssistant
21-
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
21+
from homeassistant.helpers.entity_platform import (
22+
AddConfigEntryEntitiesCallback,
23+
async_get_current_platform,
24+
)
2225
from homeassistant.helpers.restore_state import RestoreEntity
2326
from homeassistant.helpers.typing import ConfigType
2427

2528
from .const import (
2629
CONF_RESPOND_TO_READ,
2730
CONF_STATE_ADDRESS,
2831
CONF_SYNC_STATE,
32+
DOMAIN,
2933
KNX_ADDRESS,
3034
KNX_MODULE_KEY,
3135
)
32-
from .entity import KnxYamlEntity
36+
from .entity import KnxUiEntity, KnxUiEntityPlatformController, KnxYamlEntity
3337
from .knx_module import KNXModule
38+
from .storage.const import CONF_ENTITY, CONF_GA_DATE
39+
from .storage.util import ConfigExtractor
3440

3541

3642
async def async_setup_entry(
@@ -40,40 +46,36 @@ async def async_setup_entry(
4046
) -> None:
4147
"""Set up entities for KNX platform."""
4248
knx_module = hass.data[KNX_MODULE_KEY]
43-
config: list[ConfigType] = knx_module.config_yaml[Platform.DATE]
44-
45-
async_add_entities(
46-
KNXDateEntity(knx_module, entity_config) for entity_config in config
49+
platform = async_get_current_platform()
50+
knx_module.config_store.add_platform(
51+
platform=Platform.DATE,
52+
controller=KnxUiEntityPlatformController(
53+
knx_module=knx_module,
54+
entity_platform=platform,
55+
entity_class=KnxUiDate,
56+
),
4757
)
4858

49-
50-
def _create_xknx_device(xknx: XKNX, config: ConfigType) -> XknxDateDevice:
51-
"""Return a XKNX DateTime object to be used within XKNX."""
52-
return XknxDateDevice(
53-
xknx,
54-
name=config[CONF_NAME],
55-
localtime=False,
56-
group_address=config[KNX_ADDRESS],
57-
group_address_state=config.get(CONF_STATE_ADDRESS),
58-
respond_to_read=config[CONF_RESPOND_TO_READ],
59-
sync_state=config[CONF_SYNC_STATE],
60-
)
59+
entities: list[KnxYamlEntity | KnxUiEntity] = []
60+
if yaml_platform_config := knx_module.config_yaml.get(Platform.DATE):
61+
entities.extend(
62+
KnxYamlDate(knx_module, entity_config)
63+
for entity_config in yaml_platform_config
64+
)
65+
if ui_config := knx_module.config_store.data["entities"].get(Platform.DATE):
66+
entities.extend(
67+
KnxUiDate(knx_module, unique_id, config)
68+
for unique_id, config in ui_config.items()
69+
)
70+
if entities:
71+
async_add_entities(entities)
6172

6273

63-
class KNXDateEntity(KnxYamlEntity, DateEntity, RestoreEntity):
74+
class _KNXDate(DateEntity, RestoreEntity):
6475
"""Representation of a KNX date."""
6576

6677
_device: XknxDateDevice
6778

68-
def __init__(self, knx_module: KNXModule, config: ConfigType) -> None:
69-
"""Initialize a KNX time."""
70-
super().__init__(
71-
knx_module=knx_module,
72-
device=_create_xknx_device(knx_module.xknx, config),
73-
)
74-
self._attr_entity_category = config.get(CONF_ENTITY_CATEGORY)
75-
self._attr_unique_id = str(self._device.remote_value.group_address)
76-
7779
async def async_added_to_hass(self) -> None:
7880
"""Restore last state."""
7981
await super().async_added_to_hass()
@@ -94,3 +96,52 @@ def native_value(self) -> dt_date | None:
9496
async def async_set_value(self, value: dt_date) -> None:
9597
"""Change the value."""
9698
await self._device.set(value)
99+
100+
101+
class KnxYamlDate(_KNXDate, KnxYamlEntity):
102+
"""Representation of a KNX date configured from YAML."""
103+
104+
_device: XknxDateDevice
105+
106+
def __init__(self, knx_module: KNXModule, config: ConfigType) -> None:
107+
"""Initialize a KNX date."""
108+
super().__init__(
109+
knx_module=knx_module,
110+
device=XknxDateDevice(
111+
knx_module.xknx,
112+
name=config[CONF_NAME],
113+
localtime=False,
114+
group_address=config[KNX_ADDRESS],
115+
group_address_state=config.get(CONF_STATE_ADDRESS),
116+
respond_to_read=config[CONF_RESPOND_TO_READ],
117+
sync_state=config[CONF_SYNC_STATE],
118+
),
119+
)
120+
self._attr_entity_category = config.get(CONF_ENTITY_CATEGORY)
121+
self._attr_unique_id = str(self._device.remote_value.group_address)
122+
123+
124+
class KnxUiDate(_KNXDate, KnxUiEntity):
125+
"""Representation of a KNX date configured from the UI."""
126+
127+
_device: XknxDateDevice
128+
129+
def __init__(
130+
self, knx_module: KNXModule, unique_id: str, config: dict[str, Any]
131+
) -> None:
132+
"""Initialize KNX date."""
133+
super().__init__(
134+
knx_module=knx_module,
135+
unique_id=unique_id,
136+
entity_config=config[CONF_ENTITY],
137+
)
138+
knx_conf = ConfigExtractor(config[DOMAIN])
139+
self._device = XknxDateDevice(
140+
knx_module.xknx,
141+
name=config[CONF_ENTITY][CONF_NAME],
142+
localtime=False,
143+
group_address=knx_conf.get_write(CONF_GA_DATE),
144+
group_address_state=knx_conf.get_state_and_passive(CONF_GA_DATE),
145+
respond_to_read=knx_conf.get(CONF_RESPOND_TO_READ),
146+
sync_state=knx_conf.get(CONF_SYNC_STATE),
147+
)

homeassistant/components/knx/datetime.py

Lines changed: 80 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
from __future__ import annotations
44

55
from datetime import datetime
6+
from typing import Any
67

7-
from xknx import XKNX
88
from xknx.devices import DateTimeDevice as XknxDateTimeDevice
99
from xknx.dpt.dpt_19 import KNXDateTime as XKNXDateTime
1010

@@ -18,7 +18,10 @@
1818
Platform,
1919
)
2020
from homeassistant.core import HomeAssistant
21-
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
21+
from homeassistant.helpers.entity_platform import (
22+
AddConfigEntryEntitiesCallback,
23+
async_get_current_platform,
24+
)
2225
from homeassistant.helpers.restore_state import RestoreEntity
2326
from homeassistant.helpers.typing import ConfigType
2427
from homeassistant.util import dt as dt_util
@@ -27,11 +30,14 @@
2730
CONF_RESPOND_TO_READ,
2831
CONF_STATE_ADDRESS,
2932
CONF_SYNC_STATE,
33+
DOMAIN,
3034
KNX_ADDRESS,
3135
KNX_MODULE_KEY,
3236
)
33-
from .entity import KnxYamlEntity
37+
from .entity import KnxUiEntity, KnxUiEntityPlatformController, KnxYamlEntity
3438
from .knx_module import KNXModule
39+
from .storage.const import CONF_ENTITY, CONF_GA_DATETIME
40+
from .storage.util import ConfigExtractor
3541

3642

3743
async def async_setup_entry(
@@ -41,40 +47,36 @@ async def async_setup_entry(
4147
) -> None:
4248
"""Set up entities for KNX platform."""
4349
knx_module = hass.data[KNX_MODULE_KEY]
44-
config: list[ConfigType] = knx_module.config_yaml[Platform.DATETIME]
45-
46-
async_add_entities(
47-
KNXDateTimeEntity(knx_module, entity_config) for entity_config in config
50+
platform = async_get_current_platform()
51+
knx_module.config_store.add_platform(
52+
platform=Platform.DATETIME,
53+
controller=KnxUiEntityPlatformController(
54+
knx_module=knx_module,
55+
entity_platform=platform,
56+
entity_class=KnxUiDateTime,
57+
),
4858
)
4959

50-
51-
def _create_xknx_device(xknx: XKNX, config: ConfigType) -> XknxDateTimeDevice:
52-
"""Return a XKNX DateTime object to be used within XKNX."""
53-
return XknxDateTimeDevice(
54-
xknx,
55-
name=config[CONF_NAME],
56-
localtime=False,
57-
group_address=config[KNX_ADDRESS],
58-
group_address_state=config.get(CONF_STATE_ADDRESS),
59-
respond_to_read=config[CONF_RESPOND_TO_READ],
60-
sync_state=config[CONF_SYNC_STATE],
61-
)
60+
entities: list[KnxYamlEntity | KnxUiEntity] = []
61+
if yaml_platform_config := knx_module.config_yaml.get(Platform.DATETIME):
62+
entities.extend(
63+
KnxYamlDateTime(knx_module, entity_config)
64+
for entity_config in yaml_platform_config
65+
)
66+
if ui_config := knx_module.config_store.data["entities"].get(Platform.DATETIME):
67+
entities.extend(
68+
KnxUiDateTime(knx_module, unique_id, config)
69+
for unique_id, config in ui_config.items()
70+
)
71+
if entities:
72+
async_add_entities(entities)
6273

6374

64-
class KNXDateTimeEntity(KnxYamlEntity, DateTimeEntity, RestoreEntity):
75+
class _KNXDateTime(DateTimeEntity, RestoreEntity):
6576
"""Representation of a KNX datetime."""
6677

6778
_device: XknxDateTimeDevice
6879

69-
def __init__(self, knx_module: KNXModule, config: ConfigType) -> None:
70-
"""Initialize a KNX time."""
71-
super().__init__(
72-
knx_module=knx_module,
73-
device=_create_xknx_device(knx_module.xknx, config),
74-
)
75-
self._attr_entity_category = config.get(CONF_ENTITY_CATEGORY)
76-
self._attr_unique_id = str(self._device.remote_value.group_address)
77-
7880
async def async_added_to_hass(self) -> None:
7981
"""Restore last state."""
8082
await super().async_added_to_hass()
@@ -99,3 +101,52 @@ def native_value(self) -> datetime | None:
99101
async def async_set_value(self, value: datetime) -> None:
100102
"""Change the value."""
101103
await self._device.set(value.astimezone(dt_util.get_default_time_zone()))
104+
105+
106+
class KnxYamlDateTime(_KNXDateTime, KnxYamlEntity):
107+
"""Representation of a KNX datetime configured from YAML."""
108+
109+
_device: XknxDateTimeDevice
110+
111+
def __init__(self, knx_module: KNXModule, config: ConfigType) -> None:
112+
"""Initialize a KNX datetime."""
113+
super().__init__(
114+
knx_module=knx_module,
115+
device=XknxDateTimeDevice(
116+
knx_module.xknx,
117+
name=config[CONF_NAME],
118+
localtime=False,
119+
group_address=config[KNX_ADDRESS],
120+
group_address_state=config.get(CONF_STATE_ADDRESS),
121+
respond_to_read=config[CONF_RESPOND_TO_READ],
122+
sync_state=config[CONF_SYNC_STATE],
123+
),
124+
)
125+
self._attr_entity_category = config.get(CONF_ENTITY_CATEGORY)
126+
self._attr_unique_id = str(self._device.remote_value.group_address)
127+
128+
129+
class KnxUiDateTime(_KNXDateTime, KnxUiEntity):
130+
"""Representation of a KNX datetime configured from the UI."""
131+
132+
_device: XknxDateTimeDevice
133+
134+
def __init__(
135+
self, knx_module: KNXModule, unique_id: str, config: dict[str, Any]
136+
) -> None:
137+
"""Initialize KNX datetime."""
138+
super().__init__(
139+
knx_module=knx_module,
140+
unique_id=unique_id,
141+
entity_config=config[CONF_ENTITY],
142+
)
143+
knx_conf = ConfigExtractor(config[DOMAIN])
144+
self._device = XknxDateTimeDevice(
145+
knx_module.xknx,
146+
name=config[CONF_ENTITY][CONF_NAME],
147+
localtime=False,
148+
group_address=knx_conf.get_write(CONF_GA_DATETIME),
149+
group_address_state=knx_conf.get_state_and_passive(CONF_GA_DATETIME),
150+
respond_to_read=knx_conf.get(CONF_RESPOND_TO_READ),
151+
sync_state=knx_conf.get(CONF_SYNC_STATE),
152+
)

homeassistant/components/knx/storage/const.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@
1313

1414
CONF_GA_SENSOR: Final = "ga_sensor"
1515
CONF_GA_SWITCH: Final = "ga_switch"
16+
CONF_GA_DATE: Final = "ga_date"
17+
CONF_GA_DATETIME: Final = "ga_datetime"
18+
CONF_GA_TIME: Final = "ga_time"
1619

1720
# Climate
1821
CONF_GA_TEMPERATURE_CURRENT: Final = "ga_temperature_current"

homeassistant/components/knx/storage/entity_store_schema.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@
4646
CONF_GA_COLOR_TEMP,
4747
CONF_GA_CONTROLLER_MODE,
4848
CONF_GA_CONTROLLER_STATUS,
49+
CONF_GA_DATE,
50+
CONF_GA_DATETIME,
4951
CONF_GA_FAN_SPEED,
5052
CONF_GA_FAN_SWING,
5153
CONF_GA_FAN_SWING_HORIZONTAL,
@@ -72,6 +74,7 @@
7274
CONF_GA_SWITCH,
7375
CONF_GA_TEMPERATURE_CURRENT,
7476
CONF_GA_TEMPERATURE_TARGET,
77+
CONF_GA_TIME,
7578
CONF_GA_UP_DOWN,
7679
CONF_GA_VALVE,
7780
CONF_GA_WHITE_BRIGHTNESS,
@@ -199,6 +202,24 @@
199202
),
200203
)
201204

205+
DATE_KNX_SCHEMA = vol.Schema(
206+
{
207+
vol.Required(CONF_GA_DATE): GASelector(write_required=True, valid_dpt="11.001"),
208+
vol.Optional(CONF_RESPOND_TO_READ, default=False): selector.BooleanSelector(),
209+
vol.Optional(CONF_SYNC_STATE, default=True): SyncStateSelector(),
210+
}
211+
)
212+
213+
DATETIME_KNX_SCHEMA = vol.Schema(
214+
{
215+
vol.Required(CONF_GA_DATETIME): GASelector(
216+
write_required=True, valid_dpt="19.001"
217+
),
218+
vol.Optional(CONF_RESPOND_TO_READ, default=False): selector.BooleanSelector(),
219+
vol.Optional(CONF_SYNC_STATE, default=True): SyncStateSelector(),
220+
}
221+
)
222+
202223

203224
@unique
204225
class LightColorMode(StrEnum):
@@ -336,6 +357,14 @@ class LightColorMode(StrEnum):
336357
},
337358
)
338359

360+
TIME_KNX_SCHEMA = vol.Schema(
361+
{
362+
vol.Required(CONF_GA_TIME): GASelector(write_required=True, valid_dpt="10.001"),
363+
vol.Optional(CONF_RESPOND_TO_READ, default=False): selector.BooleanSelector(),
364+
vol.Optional(CONF_SYNC_STATE, default=True): SyncStateSelector(),
365+
}
366+
)
367+
339368

340369
@unique
341370
class ConfSetpointShiftMode(StrEnum):
@@ -482,8 +511,11 @@ class ConfClimateFanSpeedMode(StrEnum):
482511
Platform.BINARY_SENSOR: BINARY_SENSOR_KNX_SCHEMA,
483512
Platform.CLIMATE: CLIMATE_KNX_SCHEMA,
484513
Platform.COVER: COVER_KNX_SCHEMA,
514+
Platform.DATE: DATE_KNX_SCHEMA,
515+
Platform.DATETIME: DATETIME_KNX_SCHEMA,
485516
Platform.LIGHT: LIGHT_KNX_SCHEMA,
486517
Platform.SWITCH: SWITCH_KNX_SCHEMA,
518+
Platform.TIME: TIME_KNX_SCHEMA,
487519
}
488520

489521
ENTITY_STORE_DATA_SCHEMA: VolSchemaType = vol.All(

0 commit comments

Comments
 (0)