22
33from __future__ import annotations
44
5- from collections .abc import Callable
6- from typing import Any
5+ from typing import Any , Literal
76
7+ from xknx import XKNX
88from xknx .devices import Cover as XknxCover
99
1010from homeassistant import config_entries
2222 Platform ,
2323)
2424from homeassistant .core import HomeAssistant
25- from homeassistant .helpers .entity_platform import AddConfigEntryEntitiesCallback
25+ from homeassistant .helpers .entity_platform import (
26+ AddConfigEntryEntitiesCallback ,
27+ async_get_current_platform ,
28+ )
2629from homeassistant .helpers .typing import ConfigType
2730
2831from . import KNXModule
29- from .const import KNX_MODULE_KEY
30- from .entity import KnxYamlEntity
32+ from .const import CONF_SYNC_STATE , DOMAIN , KNX_MODULE_KEY
33+ from .entity import KnxUiEntity , KnxUiEntityPlatformController , KnxYamlEntity
3134from .schema import CoverSchema
35+ from .storage .const import (
36+ CONF_ENTITY ,
37+ CONF_GA_ANGLE ,
38+ CONF_GA_PASSIVE ,
39+ CONF_GA_POSITION_SET ,
40+ CONF_GA_POSITION_STATE ,
41+ CONF_GA_STATE ,
42+ CONF_GA_STEP ,
43+ CONF_GA_STOP ,
44+ CONF_GA_UP_DOWN ,
45+ CONF_GA_WRITE ,
46+ CONF_INVERT_ANGLE ,
47+ CONF_INVERT_POSITION ,
48+ CONF_INVERT_UPDOWN ,
49+ CONF_TRAVELLING_TIME_DOWN ,
50+ CONF_TRAVELLING_TIME_UP ,
51+ )
3252
3353
3454async def async_setup_entry (
3555 hass : HomeAssistant ,
3656 config_entry : config_entries .ConfigEntry ,
3757 async_add_entities : AddConfigEntryEntitiesCallback ,
3858) -> None :
39- """Set up cover(s) for KNX platform."""
59+ """Set up the KNX cover platform."""
4060 knx_module = hass .data [KNX_MODULE_KEY ]
41- config : list [ConfigType ] = knx_module .config_yaml [Platform .COVER ]
61+ platform = async_get_current_platform ()
62+ knx_module .config_store .add_platform (
63+ platform = Platform .COVER ,
64+ controller = KnxUiEntityPlatformController (
65+ knx_module = knx_module ,
66+ entity_platform = platform ,
67+ entity_class = KnxUiCover ,
68+ ),
69+ )
4270
43- async_add_entities (KNXCover (knx_module , entity_config ) for entity_config in config )
71+ entities : list [KnxYamlEntity | KnxUiEntity ] = []
72+ if yaml_platform_config := knx_module .config_yaml .get (Platform .COVER ):
73+ entities .extend (
74+ KnxYamlCover (knx_module , entity_config )
75+ for entity_config in yaml_platform_config
76+ )
77+ if ui_config := knx_module .config_store .data ["entities" ].get (Platform .COVER ):
78+ entities .extend (
79+ KnxUiCover (knx_module , unique_id , config )
80+ for unique_id , config in ui_config .items ()
81+ )
82+ if entities :
83+ async_add_entities (entities )
4484
4585
46- class KNXCover ( KnxYamlEntity , CoverEntity ):
86+ class _KnxCover ( CoverEntity ):
4787 """Representation of a KNX cover."""
4888
4989 _device : XknxCover
5090
51- def __init__ (self , knx_module : KNXModule , config : ConfigType ) -> None :
52- """Initialize the cover."""
53- super ().__init__ (
54- knx_module = knx_module ,
55- device = XknxCover (
56- xknx = knx_module .xknx ,
57- name = config [CONF_NAME ],
58- group_address_long = config .get (CoverSchema .CONF_MOVE_LONG_ADDRESS ),
59- group_address_short = config .get (CoverSchema .CONF_MOVE_SHORT_ADDRESS ),
60- group_address_stop = config .get (CoverSchema .CONF_STOP_ADDRESS ),
61- group_address_position_state = config .get (
62- CoverSchema .CONF_POSITION_STATE_ADDRESS
63- ),
64- group_address_angle = config .get (CoverSchema .CONF_ANGLE_ADDRESS ),
65- group_address_angle_state = config .get (
66- CoverSchema .CONF_ANGLE_STATE_ADDRESS
67- ),
68- group_address_position = config .get (CoverSchema .CONF_POSITION_ADDRESS ),
69- travel_time_down = config [CoverSchema .CONF_TRAVELLING_TIME_DOWN ],
70- travel_time_up = config [CoverSchema .CONF_TRAVELLING_TIME_UP ],
71- invert_updown = config [CoverSchema .CONF_INVERT_UPDOWN ],
72- invert_position = config [CoverSchema .CONF_INVERT_POSITION ],
73- invert_angle = config [CoverSchema .CONF_INVERT_ANGLE ],
74- ),
75- )
76- self ._unsubscribe_auto_updater : Callable [[], None ] | None = None
77-
78- self ._attr_entity_category = config .get (CONF_ENTITY_CATEGORY )
91+ def init_base (self ) -> None :
92+ """Initialize common attributes - may be based on xknx device instance."""
7993 _supports_tilt = False
8094 self ._attr_supported_features = (
81- CoverEntityFeature .CLOSE
82- | CoverEntityFeature .OPEN
83- | CoverEntityFeature .SET_POSITION
95+ CoverEntityFeature .CLOSE | CoverEntityFeature .OPEN
8496 )
97+ if self ._device .supports_position or self ._device .supports_stop :
98+ # when stop is supported, xknx travelcalculator can set position
99+ self ._attr_supported_features |= CoverEntityFeature .SET_POSITION
85100 if self ._device .step .writable :
86101 _supports_tilt = True
87102 self ._attr_supported_features |= (
@@ -97,13 +112,7 @@ def __init__(self, knx_module: KNXModule, config: ConfigType) -> None:
97112 if _supports_tilt :
98113 self ._attr_supported_features |= CoverEntityFeature .STOP_TILT
99114
100- self ._attr_device_class = config .get (CONF_DEVICE_CLASS ) or (
101- CoverDeviceClass .BLIND if _supports_tilt else None
102- )
103- self ._attr_unique_id = (
104- f"{ self ._device .updown .group_address } _"
105- f"{ self ._device .position_target .group_address } "
106- )
115+ self ._attr_device_class = CoverDeviceClass .BLIND if _supports_tilt else None
107116
108117 @property
109118 def current_cover_position (self ) -> int | None :
@@ -180,3 +189,102 @@ async def async_close_cover_tilt(self, **kwargs: Any) -> None:
180189 async def async_stop_cover_tilt (self , ** kwargs : Any ) -> None :
181190 """Stop the cover tilt."""
182191 await self ._device .stop ()
192+
193+
194+ class KnxYamlCover (_KnxCover , KnxYamlEntity ):
195+ """Representation of a KNX cover configured from YAML."""
196+
197+ _device : XknxCover
198+
199+ def __init__ (self , knx_module : KNXModule , config : ConfigType ) -> None :
200+ """Initialize the cover."""
201+ super ().__init__ (
202+ knx_module = knx_module ,
203+ device = XknxCover (
204+ xknx = knx_module .xknx ,
205+ name = config [CONF_NAME ],
206+ group_address_long = config .get (CoverSchema .CONF_MOVE_LONG_ADDRESS ),
207+ group_address_short = config .get (CoverSchema .CONF_MOVE_SHORT_ADDRESS ),
208+ group_address_stop = config .get (CoverSchema .CONF_STOP_ADDRESS ),
209+ group_address_position_state = config .get (
210+ CoverSchema .CONF_POSITION_STATE_ADDRESS
211+ ),
212+ group_address_angle = config .get (CoverSchema .CONF_ANGLE_ADDRESS ),
213+ group_address_angle_state = config .get (
214+ CoverSchema .CONF_ANGLE_STATE_ADDRESS
215+ ),
216+ group_address_position = config .get (CoverSchema .CONF_POSITION_ADDRESS ),
217+ travel_time_down = config [CoverSchema .CONF_TRAVELLING_TIME_DOWN ],
218+ travel_time_up = config [CoverSchema .CONF_TRAVELLING_TIME_UP ],
219+ invert_updown = config [CoverSchema .CONF_INVERT_UPDOWN ],
220+ invert_position = config [CoverSchema .CONF_INVERT_POSITION ],
221+ invert_angle = config [CoverSchema .CONF_INVERT_ANGLE ],
222+ ),
223+ )
224+ self .init_base ()
225+
226+ self ._attr_entity_category = config .get (CONF_ENTITY_CATEGORY )
227+ self ._attr_unique_id = (
228+ f"{ self ._device .updown .group_address } _"
229+ f"{ self ._device .position_target .group_address } "
230+ )
231+ if custom_device_class := config .get (CONF_DEVICE_CLASS ):
232+ self ._attr_device_class = custom_device_class
233+
234+
235+ def _create_ui_cover (xknx : XKNX , knx_config : ConfigType , name : str ) -> XknxCover :
236+ """Return a KNX Light device to be used within XKNX."""
237+
238+ def get_address (
239+ key : str , address_type : Literal ["write" , "state" ] = CONF_GA_WRITE
240+ ) -> str | None :
241+ """Get a single group address for given key."""
242+ return knx_config [key ][address_type ] if key in knx_config else None
243+
244+ def get_addresses (
245+ key : str , address_type : Literal ["write" , "state" ] = CONF_GA_STATE
246+ ) -> list [Any ] | None :
247+ """Get group address including passive addresses as list."""
248+ return (
249+ [knx_config [key ][address_type ], * knx_config [key ][CONF_GA_PASSIVE ]]
250+ if key in knx_config
251+ else None
252+ )
253+
254+ return XknxCover (
255+ xknx = xknx ,
256+ name = name ,
257+ group_address_long = get_addresses (CONF_GA_UP_DOWN , CONF_GA_WRITE ),
258+ group_address_short = get_addresses (CONF_GA_STEP , CONF_GA_WRITE ),
259+ group_address_stop = get_addresses (CONF_GA_STOP , CONF_GA_WRITE ),
260+ group_address_position = get_addresses (CONF_GA_POSITION_SET , CONF_GA_WRITE ),
261+ group_address_position_state = get_addresses (CONF_GA_POSITION_STATE ),
262+ group_address_angle = get_address (CONF_GA_ANGLE ),
263+ group_address_angle_state = get_addresses (CONF_GA_ANGLE ),
264+ travel_time_down = knx_config [CONF_TRAVELLING_TIME_DOWN ],
265+ travel_time_up = knx_config [CONF_TRAVELLING_TIME_UP ],
266+ invert_updown = knx_config .get (CONF_INVERT_UPDOWN , False ),
267+ invert_position = knx_config .get (CONF_INVERT_POSITION , False ),
268+ invert_angle = knx_config .get (CONF_INVERT_ANGLE , False ),
269+ sync_state = knx_config [CONF_SYNC_STATE ],
270+ )
271+
272+
273+ class KnxUiCover (_KnxCover , KnxUiEntity ):
274+ """Representation of a KNX cover configured from the UI."""
275+
276+ _device : XknxCover
277+
278+ def __init__ (
279+ self , knx_module : KNXModule , unique_id : str , config : dict [str , Any ]
280+ ) -> None :
281+ """Initialize KNX cover."""
282+ super ().__init__ (
283+ knx_module = knx_module ,
284+ unique_id = unique_id ,
285+ entity_config = config [CONF_ENTITY ],
286+ )
287+ self ._device = _create_ui_cover (
288+ knx_module .xknx , config [DOMAIN ], config [CONF_ENTITY ][CONF_NAME ]
289+ )
290+ self .init_base ()
0 commit comments