1 """Support to select an option from a list."""
3 from __future__
import annotations
6 from typing
import Any, Self, cast
8 import voluptuous
as vol
17 SERVICE_SELECT_OPTION,
18 SERVICE_SELECT_PREVIOUS,
38 _LOGGER = logging.getLogger(__name__)
40 DOMAIN =
"input_select"
42 CONF_INITIAL =
"initial"
43 CONF_OPTIONS =
"options"
45 SERVICE_SET_OPTIONS =
"set_options"
48 STORAGE_VERSION_MINOR = 2
53 return vol.Unique()(options)
54 except vol.Invalid
as exc:
58 STORAGE_FIELDS: VolDictType = {
59 vol.Required(CONF_NAME): vol.All(str, vol.Length(min=1)),
60 vol.Required(CONF_OPTIONS): vol.All(
61 cv.ensure_list, vol.Length(min=1), _unique, [cv.string]
63 vol.Optional(CONF_INITIAL): cv.string,
64 vol.Optional(CONF_ICON): cv.icon,
69 """Remove duplicated options."""
70 unique_options =
list(dict.fromkeys(options))
73 if len(unique_options) != len(options):
76 "Input select '%s' with options %s had duplicated options, the"
77 " duplicates have been removed"
86 """Configure validation helper for input select (voluptuous)."""
87 options = cfg[CONF_OPTIONS]
88 initial = cfg.get(CONF_INITIAL)
89 if initial
is not None and initial
not in options:
91 f
"initial state {initial} is not part of the options: {','.join(options)}"
97 CONFIG_SCHEMA = vol.Schema(
99 DOMAIN: cv.schema_with_slug_keys(
102 vol.Optional(CONF_NAME): cv.string,
103 vol.Required(CONF_OPTIONS): vol.All(
104 cv.ensure_list, vol.Length(min=1), [cv.string]
106 vol.Optional(CONF_INITIAL): cv.string,
107 vol.Optional(CONF_ICON): cv.icon,
113 extra=vol.ALLOW_EXTRA,
115 RELOAD_SERVICE_SCHEMA = vol.Schema({})
119 """Store entity registry data."""
122 self, old_major_version: int, old_minor_version: int, old_data: dict[str, Any]
124 """Migrate to the new version."""
125 if old_major_version == 1:
126 if old_minor_version < 2:
127 for item
in old_data[
"items"]:
128 options = item[ATTR_OPTIONS]
130 options, item.get(CONF_NAME)
135 async
def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
136 """Set up an input select."""
137 component = EntityComponent[InputSelect](_LOGGER, DOMAIN, hass)
139 id_manager = collection.IDManager()
141 yaml_collection = collection.YamlCollection(
142 logging.getLogger(f
"{__name__}.yaml_collection"), id_manager
144 collection.sync_entity_lifecycle(
145 hass, DOMAIN, DOMAIN, component, yaml_collection, InputSelect
150 hass, STORAGE_VERSION, STORAGE_KEY, minor_version=STORAGE_VERSION_MINOR
154 collection.sync_entity_lifecycle(
155 hass, DOMAIN, DOMAIN, component, storage_collection, InputSelect
158 await yaml_collection.async_load(
159 [{CONF_ID: id_, **cfg}
for id_, cfg
in config.get(DOMAIN, {}).items()]
161 await storage_collection.async_load()
163 collection.DictStorageCollectionWebsocket(
164 storage_collection, DOMAIN, DOMAIN, STORAGE_FIELDS, STORAGE_FIELDS
167 async
def reload_service_handler(service_call: ServiceCall) ->
None:
168 """Reload yaml entities."""
169 conf = await component.async_prepare_reload(skip_reset=
True)
172 await yaml_collection.async_load(
173 [{CONF_ID: id_, **cfg}
for id_, cfg
in conf.get(DOMAIN, {}).items()]
180 reload_service_handler,
181 schema=RELOAD_SERVICE_SCHEMA,
184 component.async_register_entity_service(
185 SERVICE_SELECT_FIRST,
187 InputSelect.async_first.__name__,
190 component.async_register_entity_service(
193 InputSelect.async_last.__name__,
196 component.async_register_entity_service(
198 {vol.Optional(ATTR_CYCLE, default=
True): bool},
199 InputSelect.async_next.__name__,
202 component.async_register_entity_service(
203 SERVICE_SELECT_OPTION,
204 {vol.Required(ATTR_OPTION): cv.string},
205 InputSelect.async_select_option.__name__,
208 component.async_register_entity_service(
209 SERVICE_SELECT_PREVIOUS,
210 {vol.Optional(ATTR_CYCLE, default=
True): bool},
211 InputSelect.async_previous.__name__,
214 component.async_register_entity_service(
217 vol.Required(ATTR_OPTIONS): vol.All(
218 cv.ensure_list, vol.Length(min=1), [cv.string]
228 """Input storage based collection."""
230 CREATE_UPDATE_SCHEMA = vol.Schema(vol.All(STORAGE_FIELDS, _cv_input_select))
233 """Validate the config is valid."""
238 """Suggest an ID based on the config."""
239 return cast(str, info[CONF_NAME])
242 self, item: dict[str, Any], update_data: dict[str, Any]
244 """Return a new updated data object."""
246 return {CONF_ID: item[CONF_ID]} | update_data
251 """Representation of a select input."""
253 _entity_component_unrecorded_attributes = (
254 SelectEntity._entity_component_unrecorded_attributes - {ATTR_OPTIONS}
256 _unrecorded_attributes = frozenset({ATTR_EDITABLE})
258 _attr_should_poll =
False
262 """Initialize a select input."""
271 """Return entity instance initialized from storage."""
272 input_select = cls(config)
273 input_select.editable =
True
278 """Return entity instance initialized from yaml."""
279 input_select = cls(config)
280 input_select.entity_id = f
"{DOMAIN}.{config[CONF_ID]}"
281 input_select.editable =
False
285 """Run when entity about to be added."""
291 if not state
or state.state
not in self.
optionsoptions:
298 """Return the state attributes."""
299 return {ATTR_EDITABLE: self.editable}
302 """Select new option."""
303 if option
not in self.
optionsoptions:
305 f
"Invalid option: {option} (possible options: {', '.join(self.options)})"
312 unique_options =
list(dict.fromkeys(options))
313 if len(unique_options) != len(options):
320 "Current option: %s no longer valid (possible options: %s)",
322 ", ".join(self.
optionsoptions),
329 """Handle when the config is updated."""
330 self.
_attr_icon_attr_icon = config.get(CONF_ICON)
331 self.
_attr_name_attr_name = config.get(CONF_NAME)
str|None current_option(self)
None async_write_ha_state(self)
State|None async_get_last_state(self)
None async_register_admin_service(HomeAssistant hass, str domain, str service, Callable[[ServiceCall], Awaitable[None]|None] service_func, VolSchemaType schema=vol.Schema({}, extra=vol.PREVENT_EXTRA))