33from __future__ import annotations
44
55import logging
6- from typing import Any
6+ from typing import TYPE_CHECKING , Any
77
88import voluptuous as vol
99
3232
3333from . import TriggerUpdateCoordinator
3434from .const import DOMAIN
35+ from .entity import AbstractTemplateEntity
3536from .template_entity import TemplateEntity , make_template_entity_common_modern_schema
3637from .trigger_entity import TriggerEntity
3738
4546
4647SELECT_SCHEMA = vol .Schema (
4748 {
48- vol .Required (CONF_STATE ): cv .template ,
49+ vol .Optional (CONF_STATE ): cv .template ,
4950 vol .Required (CONF_SELECT_OPTION ): cv .SCRIPT_SCHEMA ,
5051 vol .Required (ATTR_OPTIONS ): cv .template ,
5152 vol .Optional (CONF_OPTIMISTIC , default = DEFAULT_OPTIMISTIC ): cv .boolean ,
@@ -116,7 +117,37 @@ async def async_setup_entry(
116117 async_add_entities ([TemplateSelect (hass , validated_config , config_entry .entry_id )])
117118
118119
119- class TemplateSelect (TemplateEntity , SelectEntity ):
120+ class AbstractTemplateSelect (AbstractTemplateEntity , SelectEntity ):
121+ """Representation of a template select features."""
122+
123+ # The super init is not called because TemplateEntity and TriggerEntity will call AbstractTemplateEntity.__init__.
124+ # This ensures that the __init__ on AbstractTemplateEntity is not called twice.
125+ def __init__ (self , config : dict [str , Any ]) -> None : # pylint: disable=super-init-not-called
126+ """Initialize the features."""
127+ self ._template = config .get (CONF_STATE )
128+
129+ self ._options_template = config [ATTR_OPTIONS ]
130+
131+ self ._attr_assumed_state = self ._optimistic = (
132+ self ._template is None or config .get (CONF_OPTIMISTIC , DEFAULT_OPTIMISTIC )
133+ )
134+ self ._attr_options = []
135+ self ._attr_current_option = None
136+
137+ async def async_select_option (self , option : str ) -> None :
138+ """Change the selected option."""
139+ if self ._optimistic :
140+ self ._attr_current_option = option
141+ self .async_write_ha_state ()
142+ if select_option := self ._action_scripts .get (CONF_SELECT_OPTION ):
143+ await self .async_run_script (
144+ select_option ,
145+ run_variables = {ATTR_OPTION : option },
146+ context = self ._context ,
147+ )
148+
149+
150+ class TemplateSelect (TemplateEntity , AbstractTemplateSelect ):
120151 """Representation of a template select."""
121152
122153 _attr_should_poll = False
@@ -128,16 +159,16 @@ def __init__(
128159 unique_id : str | None ,
129160 ) -> None :
130161 """Initialize the select."""
131- super ().__init__ (hass , config = config , unique_id = unique_id )
132- assert self ._attr_name is not None
133- self ._value_template = config [CONF_STATE ]
134- # Scripts can be an empty list, therefore we need to check for None
162+ TemplateEntity .__init__ (self , hass , config = config , unique_id = unique_id )
163+ AbstractTemplateSelect .__init__ (self , config )
164+
165+ name = self ._attr_name
166+ if TYPE_CHECKING :
167+ assert name is not None
168+
135169 if (select_option := config .get (CONF_SELECT_OPTION )) is not None :
136- self .add_script (CONF_SELECT_OPTION , select_option , self ._attr_name , DOMAIN )
137- self ._options_template = config [ATTR_OPTIONS ]
138- self ._attr_assumed_state = self ._optimistic = config .get (CONF_OPTIMISTIC , False )
139- self ._attr_options = []
140- self ._attr_current_option = None
170+ self .add_script (CONF_SELECT_OPTION , select_option , name , DOMAIN )
171+
141172 self ._attr_device_info = async_device_info_to_link_from_device_id (
142173 hass ,
143174 config .get (CONF_DEVICE_ID ),
@@ -146,12 +177,13 @@ def __init__(
146177 @callback
147178 def _async_setup_templates (self ) -> None :
148179 """Set up templates."""
149- self .add_template_attribute (
150- "_attr_current_option" ,
151- self ._value_template ,
152- validator = cv .string ,
153- none_on_template_error = True ,
154- )
180+ if self ._template is not None :
181+ self .add_template_attribute (
182+ "_attr_current_option" ,
183+ self ._template ,
184+ validator = cv .string ,
185+ none_on_template_error = True ,
186+ )
155187 self .add_template_attribute (
156188 "_attr_options" ,
157189 self ._options_template ,
@@ -160,24 +192,11 @@ def _async_setup_templates(self) -> None:
160192 )
161193 super ()._async_setup_templates ()
162194
163- async def async_select_option (self , option : str ) -> None :
164- """Change the selected option."""
165- if self ._optimistic :
166- self ._attr_current_option = option
167- self .async_write_ha_state ()
168- if select_option := self ._action_scripts .get (CONF_SELECT_OPTION ):
169- await self .async_run_script (
170- select_option ,
171- run_variables = {ATTR_OPTION : option },
172- context = self ._context ,
173- )
174-
175195
176- class TriggerSelectEntity (TriggerEntity , SelectEntity ):
196+ class TriggerSelectEntity (TriggerEntity , AbstractTemplateSelect ):
177197 """Select entity based on trigger data."""
178198
179199 domain = SELECT_DOMAIN
180- extra_template_keys = (CONF_STATE ,)
181200 extra_template_keys_complex = (ATTR_OPTIONS ,)
182201
183202 def __init__ (
@@ -187,7 +206,12 @@ def __init__(
187206 config : dict ,
188207 ) -> None :
189208 """Initialize the entity."""
190- super ().__init__ (hass , coordinator , config )
209+ TriggerEntity .__init__ (self , hass , coordinator , config )
210+ AbstractTemplateSelect .__init__ (self , config )
211+
212+ if CONF_STATE in config :
213+ self ._to_render_simple .append (CONF_STATE )
214+
191215 # Scripts can be an empty list, therefore we need to check for None
192216 if (select_option := config .get (CONF_SELECT_OPTION )) is not None :
193217 self .add_script (
@@ -197,24 +221,26 @@ def __init__(
197221 DOMAIN ,
198222 )
199223
200- @property
201- def current_option (self ) -> str | None :
202- """Return the currently selected option."""
203- return self ._rendered .get (CONF_STATE )
224+ def _handle_coordinator_update (self ):
225+ """Handle updated data from the coordinator."""
226+ self ._process_data ()
204227
205- @property
206- def options (self ) -> list [str ]:
207- """Return the list of available options."""
208- return self ._rendered .get (ATTR_OPTIONS , [])
228+ if not self .available :
229+ self .async_write_ha_state ()
230+ return
209231
210- async def async_select_option (self , option : str ) -> None :
211- """Change the selected option."""
212- if self ._config [CONF_OPTIMISTIC ]:
213- self ._attr_current_option = option
232+ write_ha_state = False
233+ if (options := self ._rendered .get (ATTR_OPTIONS )) is not None :
234+ self ._attr_options = vol .All (cv .ensure_list , [cv .string ])(options )
235+ write_ha_state = True
236+
237+ if (state := self ._rendered .get (CONF_STATE )) is not None :
238+ self ._attr_current_option = cv .string (state )
239+ write_ha_state = True
240+
241+ if len (self ._rendered ) > 0 :
242+ # In case any non optimistic template
243+ write_ha_state = True
244+
245+ if write_ha_state :
214246 self .async_write_ha_state ()
215- if select_option := self ._action_scripts .get (CONF_SELECT_OPTION ):
216- await self .async_run_script (
217- select_option ,
218- run_variables = {ATTR_OPTION : option },
219- context = self ._context ,
220- )
0 commit comments