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 , CoverConf
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+ )
3247
3348
3449async def async_setup_entry (
3550 hass : HomeAssistant ,
3651 config_entry : config_entries .ConfigEntry ,
3752 async_add_entities : AddConfigEntryEntitiesCallback ,
3853) -> None :
39- """Set up cover(s) for KNX platform."""
54+ """Set up the KNX cover platform."""
4055 knx_module = hass .data [KNX_MODULE_KEY ]
41- config : list [ConfigType ] = knx_module .config_yaml [Platform .COVER ]
56+ platform = async_get_current_platform ()
57+ knx_module .config_store .add_platform (
58+ platform = Platform .COVER ,
59+ controller = KnxUiEntityPlatformController (
60+ knx_module = knx_module ,
61+ entity_platform = platform ,
62+ entity_class = KnxUiCover ,
63+ ),
64+ )
4265
43- async_add_entities (KNXCover (knx_module , entity_config ) for entity_config in config )
66+ entities : list [KnxYamlEntity | KnxUiEntity ] = []
67+ if yaml_platform_config := knx_module .config_yaml .get (Platform .COVER ):
68+ entities .extend (
69+ KnxYamlCover (knx_module , entity_config )
70+ for entity_config in yaml_platform_config
71+ )
72+ if ui_config := knx_module .config_store .data ["entities" ].get (Platform .COVER ):
73+ entities .extend (
74+ KnxUiCover (knx_module , unique_id , config )
75+ for unique_id , config in ui_config .items ()
76+ )
77+ if entities :
78+ async_add_entities (entities )
4479
4580
46- class KNXCover ( KnxYamlEntity , CoverEntity ):
81+ class _KnxCover ( CoverEntity ):
4782 """Representation of a KNX cover."""
4883
4984 _device : XknxCover
5085
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 )
86+ def init_base (self ) -> None :
87+ """Initialize common attributes - may be based on xknx device instance."""
7988 _supports_tilt = False
8089 self ._attr_supported_features = (
81- CoverEntityFeature .CLOSE
82- | CoverEntityFeature .OPEN
83- | CoverEntityFeature .SET_POSITION
90+ CoverEntityFeature .CLOSE | CoverEntityFeature .OPEN
8491 )
92+ if self ._device .supports_position or self ._device .supports_stop :
93+ # when stop is supported, xknx travelcalculator can set position
94+ self ._attr_supported_features |= CoverEntityFeature .SET_POSITION
8595 if self ._device .step .writable :
8696 _supports_tilt = True
8797 self ._attr_supported_features |= (
@@ -97,13 +107,7 @@ def __init__(self, knx_module: KNXModule, config: ConfigType) -> None:
97107 if _supports_tilt :
98108 self ._attr_supported_features |= CoverEntityFeature .STOP_TILT
99109
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- )
110+ self ._attr_device_class = CoverDeviceClass .BLIND if _supports_tilt else None
107111
108112 @property
109113 def current_cover_position (self ) -> int | None :
@@ -180,3 +184,102 @@ async def async_close_cover_tilt(self, **kwargs: Any) -> None:
180184 async def async_stop_cover_tilt (self , ** kwargs : Any ) -> None :
181185 """Stop the cover tilt."""
182186 await self ._device .stop ()
187+
188+
189+ class KnxYamlCover (_KnxCover , KnxYamlEntity ):
190+ """Representation of a KNX cover configured from YAML."""
191+
192+ _device : XknxCover
193+
194+ def __init__ (self , knx_module : KNXModule , config : ConfigType ) -> None :
195+ """Initialize the cover."""
196+ super ().__init__ (
197+ knx_module = knx_module ,
198+ device = XknxCover (
199+ xknx = knx_module .xknx ,
200+ name = config [CONF_NAME ],
201+ group_address_long = config .get (CoverSchema .CONF_MOVE_LONG_ADDRESS ),
202+ group_address_short = config .get (CoverSchema .CONF_MOVE_SHORT_ADDRESS ),
203+ group_address_stop = config .get (CoverSchema .CONF_STOP_ADDRESS ),
204+ group_address_position_state = config .get (
205+ CoverSchema .CONF_POSITION_STATE_ADDRESS
206+ ),
207+ group_address_angle = config .get (CoverSchema .CONF_ANGLE_ADDRESS ),
208+ group_address_angle_state = config .get (
209+ CoverSchema .CONF_ANGLE_STATE_ADDRESS
210+ ),
211+ group_address_position = config .get (CoverSchema .CONF_POSITION_ADDRESS ),
212+ travel_time_down = config [CoverConf .TRAVELLING_TIME_DOWN ],
213+ travel_time_up = config [CoverConf .TRAVELLING_TIME_UP ],
214+ invert_updown = config [CoverConf .INVERT_UPDOWN ],
215+ invert_position = config [CoverConf .INVERT_POSITION ],
216+ invert_angle = config [CoverConf .INVERT_ANGLE ],
217+ ),
218+ )
219+ self .init_base ()
220+
221+ self ._attr_entity_category = config .get (CONF_ENTITY_CATEGORY )
222+ self ._attr_unique_id = (
223+ f"{ self ._device .updown .group_address } _"
224+ f"{ self ._device .position_target .group_address } "
225+ )
226+ if custom_device_class := config .get (CONF_DEVICE_CLASS ):
227+ self ._attr_device_class = custom_device_class
228+
229+
230+ def _create_ui_cover (xknx : XKNX , knx_config : ConfigType , name : str ) -> XknxCover :
231+ """Return a KNX Light device to be used within XKNX."""
232+
233+ def get_address (
234+ key : str , address_type : Literal ["write" , "state" ] = CONF_GA_WRITE
235+ ) -> str | None :
236+ """Get a single group address for given key."""
237+ return knx_config [key ][address_type ] if key in knx_config else None
238+
239+ def get_addresses (
240+ key : str , address_type : Literal ["write" , "state" ] = CONF_GA_STATE
241+ ) -> list [Any ] | None :
242+ """Get group address including passive addresses as list."""
243+ return (
244+ [knx_config [key ][address_type ], * knx_config [key ][CONF_GA_PASSIVE ]]
245+ if key in knx_config
246+ else None
247+ )
248+
249+ return XknxCover (
250+ xknx = xknx ,
251+ name = name ,
252+ group_address_long = get_addresses (CONF_GA_UP_DOWN , CONF_GA_WRITE ),
253+ group_address_short = get_addresses (CONF_GA_STEP , CONF_GA_WRITE ),
254+ group_address_stop = get_addresses (CONF_GA_STOP , CONF_GA_WRITE ),
255+ group_address_position = get_addresses (CONF_GA_POSITION_SET , CONF_GA_WRITE ),
256+ group_address_position_state = get_addresses (CONF_GA_POSITION_STATE ),
257+ group_address_angle = get_address (CONF_GA_ANGLE ),
258+ group_address_angle_state = get_addresses (CONF_GA_ANGLE ),
259+ travel_time_down = knx_config [CoverConf .TRAVELLING_TIME_DOWN ],
260+ travel_time_up = knx_config [CoverConf .TRAVELLING_TIME_UP ],
261+ invert_updown = knx_config .get (CoverConf .INVERT_UPDOWN , False ),
262+ invert_position = knx_config .get (CoverConf .INVERT_POSITION , False ),
263+ invert_angle = knx_config .get (CoverConf .INVERT_ANGLE , False ),
264+ sync_state = knx_config [CONF_SYNC_STATE ],
265+ )
266+
267+
268+ class KnxUiCover (_KnxCover , KnxUiEntity ):
269+ """Representation of a KNX cover configured from the UI."""
270+
271+ _device : XknxCover
272+
273+ def __init__ (
274+ self , knx_module : KNXModule , unique_id : str , config : dict [str , Any ]
275+ ) -> None :
276+ """Initialize KNX cover."""
277+ super ().__init__ (
278+ knx_module = knx_module ,
279+ unique_id = unique_id ,
280+ entity_config = config [CONF_ENTITY ],
281+ )
282+ self ._device = _create_ui_cover (
283+ knx_module .xknx , config [DOMAIN ], config [CONF_ENTITY ][CONF_NAME ]
284+ )
285+ self .init_base ()
0 commit comments