Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 25 additions & 70 deletions homeassistant/components/input_select/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,19 @@

import voluptuous as vol

from homeassistant.components.select import SelectEntity
from homeassistant.components.select import (
ATTR_CYCLE,
ATTR_OPTION,
ATTR_OPTIONS,
SERVICE_SELECT_FIRST,
SERVICE_SELECT_LAST,
SERVICE_SELECT_NEXT,
SERVICE_SELECT_OPTION,
SERVICE_SELECT_PREVIOUS,
SelectEntity,
)
from homeassistant.const import (
ATTR_EDITABLE,
ATTR_OPTION,
CONF_ICON,
CONF_ID,
CONF_NAME,
Expand All @@ -35,14 +44,6 @@
CONF_INITIAL = "initial"
CONF_OPTIONS = "options"

ATTR_OPTIONS = "options"
ATTR_CYCLE = "cycle"

SERVICE_SELECT_OPTION = "select_option"
SERVICE_SELECT_NEXT = "select_next"
SERVICE_SELECT_PREVIOUS = "select_previous"
SERVICE_SELECT_FIRST = "select_first"
SERVICE_SELECT_LAST = "select_last"
SERVICE_SET_OPTIONS = "set_options"
STORAGE_KEY = DOMAIN
STORAGE_VERSION = 1
Expand Down Expand Up @@ -188,33 +189,33 @@ async def reload_service_handler(service_call: ServiceCall) -> None:
)

component.async_register_entity_service(
SERVICE_SELECT_OPTION,
{vol.Required(ATTR_OPTION): cv.string},
"async_select_option",
SERVICE_SELECT_FIRST,
{},
InputSelect.async_first.__name__,
)

component.async_register_entity_service(
SERVICE_SELECT_NEXT,
{vol.Optional(ATTR_CYCLE, default=True): bool},
"async_next",
SERVICE_SELECT_LAST,
{},
InputSelect.async_last.__name__,
)

component.async_register_entity_service(
SERVICE_SELECT_PREVIOUS,
SERVICE_SELECT_NEXT,
{vol.Optional(ATTR_CYCLE, default=True): bool},
"async_previous",
InputSelect.async_next.__name__,
)

component.async_register_entity_service(
SERVICE_SELECT_FIRST,
{},
callback(lambda entity, call: entity.async_select_index(0)),
SERVICE_SELECT_OPTION,
{vol.Required(ATTR_OPTION): cv.string},
InputSelect.async_select_option.__name__,
)

component.async_register_entity_service(
SERVICE_SELECT_LAST,
{},
callback(lambda entity, call: entity.async_select_index(-1)),
SERVICE_SELECT_PREVIOUS,
{vol.Optional(ATTR_CYCLE, default=True): bool},
InputSelect.async_previous.__name__,
)

component.async_register_entity_service(
Expand Down Expand Up @@ -310,52 +311,6 @@ async def async_select_option(self, option: str) -> None:
self._attr_current_option = option
self.async_write_ha_state()

@callback
def async_select_index(self, idx: int) -> None:
"""Select new option by index."""
new_index = idx % len(self.options)
self._attr_current_option = self.options[new_index]
self.async_write_ha_state()

@callback
def async_offset_index(self, offset: int, cycle: bool) -> None:
"""Offset current index."""

current_index = (
self.options.index(self.current_option)
if self.current_option is not None
else 0
)

new_index = current_index + offset
if cycle:
new_index = new_index % len(self.options)
elif new_index < 0:
new_index = 0
elif new_index >= len(self.options):
new_index = len(self.options) - 1

self._attr_current_option = self.options[new_index]
self.async_write_ha_state()

@callback
def async_next(self, cycle: bool) -> None:
"""Select next option."""
# If there is no current option, first item is the next
if self.current_option is None:
self.async_select_index(0)
return
self.async_offset_index(1, cycle)

@callback
def async_previous(self, cycle: bool) -> None:
"""Select previous option."""
# If there is no current option, last item is the previous
if self.current_option is None:
self.async_select_index(-1)
return
self.async_offset_index(-1, cycle)

async def async_set_options(self, options: list[str]) -> None:
"""Set options."""
unique_options = list(dict.fromkeys(options))
Expand Down
109 changes: 107 additions & 2 deletions homeassistant/components/select/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,25 @@
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant, ServiceCall
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.config_validation import ( # noqa: F401
from homeassistant.helpers.config_validation import (
PLATFORM_SCHEMA,
PLATFORM_SCHEMA_BASE,
)
from homeassistant.helpers.entity import Entity, EntityDescription
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.typing import ConfigType

from .const import ATTR_OPTION, ATTR_OPTIONS, DOMAIN, SERVICE_SELECT_OPTION
from .const import (
ATTR_CYCLE,
ATTR_OPTION,
ATTR_OPTIONS,
DOMAIN,
SERVICE_SELECT_FIRST,
SERVICE_SELECT_LAST,
SERVICE_SELECT_NEXT,
SERVICE_SELECT_OPTION,
SERVICE_SELECT_PREVIOUS,
)

SCAN_INTERVAL = timedelta(seconds=30)

Expand All @@ -29,6 +39,22 @@

_LOGGER = logging.getLogger(__name__)

__all__ = [
"ATTR_CYCLE",
"ATTR_OPTION",
"ATTR_OPTIONS",
"DOMAIN",
"PLATFORM_SCHEMA_BASE",
"PLATFORM_SCHEMA",
"SelectEntity",
"SelectEntityDescription",
"SERVICE_SELECT_FIRST",
"SERVICE_SELECT_LAST",
"SERVICE_SELECT_NEXT",
"SERVICE_SELECT_OPTION",
"SERVICE_SELECT_PREVIOUS",
]

# mypy: disallow-any-generics


Expand All @@ -39,12 +65,36 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
)
await component.async_setup(config)

component.async_register_entity_service(
SERVICE_SELECT_FIRST,
{},
SelectEntity.async_first.__name__,
)

component.async_register_entity_service(
SERVICE_SELECT_LAST,
{},
SelectEntity.async_last.__name__,
)

component.async_register_entity_service(
SERVICE_SELECT_NEXT,
{vol.Optional(ATTR_CYCLE, default=True): bool},
SelectEntity.async_next.__name__,
)

component.async_register_entity_service(
SERVICE_SELECT_OPTION,
{vol.Required(ATTR_OPTION): cv.string},
async_select_option,
)

component.async_register_entity_service(
SERVICE_SELECT_PREVIOUS,
{vol.Optional(ATTR_CYCLE, default=True): bool},
SelectEntity.async_previous.__name__,
)

return True


Expand Down Expand Up @@ -122,3 +172,58 @@ def select_option(self, option: str) -> None:
async def async_select_option(self, option: str) -> None:
"""Change the selected option."""
await self.hass.async_add_executor_job(self.select_option, option)

@final
async def async_first(self) -> None:
"""Select first option."""
await self._async_select_index(0)

@final
async def async_last(self) -> None:
"""Select last option."""
await self._async_select_index(-1)

@final
async def async_next(self, cycle: bool) -> None:
"""Select next option.

If there is no current option, first item is the next.
"""
if self.current_option is None:
await self.async_first()
return
await self._async_offset_index(1, cycle)

@final
async def async_previous(self, cycle: bool) -> None:
"""Select previous option.

If there is no current option, last item is the previous.
"""
if self.current_option is None:
await self.async_last()
return
await self._async_offset_index(-1, cycle)

@final
async def _async_offset_index(self, offset: int, cycle: bool) -> None:
"""Offset current index."""
current_index = 0
if self.current_option is not None and self.current_option in self.options:
current_index = self.options.index(self.current_option)

new_index = current_index + offset
if cycle:
new_index = new_index % len(self.options)
elif new_index < 0:
new_index = 0
elif new_index >= len(self.options):
new_index = len(self.options) - 1

await self.async_select_option(self.options[new_index])

@final
async def _async_select_index(self, idx: int) -> None:
"""Select new option by index."""
new_index = idx % len(self.options)
await self.async_select_option(self.options[new_index])
6 changes: 6 additions & 0 deletions homeassistant/components/select/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,15 @@

DOMAIN = "select"

ATTR_CYCLE = "cycle"
ATTR_OPTIONS = "options"
ATTR_OPTION = "option"

CONF_CYCLE = "cycle"
CONF_OPTION = "option"

SERVICE_SELECT_FIRST = "select_first"
SERVICE_SELECT_LAST = "select_last"
SERVICE_SELECT_NEXT = "select_next"
SERVICE_SELECT_OPTION = "select_option"
SERVICE_SELECT_PREVIOUS = "select_previous"
Loading