New methods for input_select component (#42968)

This adds a `cycle` attribute to select_previous/next, and
select_first and select_last services.

This is quite useful for streamlining using input_select via
automations, such as when they represent a list of states to step
through; if the first option is the dimmest and the last the brightest,
one may not want to accidentally cycle from the first to the last, for
example.

Similarly, being able to directly select the first or last removes
adjustment in related automations.
This commit is contained in:
Lars Marowsky-Brée 2021-01-27 11:17:59 +01:00 committed by GitHub
parent 122a4e03f8
commit 34194da1b6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 124 additions and 7 deletions

View File

@ -29,10 +29,13 @@ CONF_OPTIONS = "options"
ATTR_OPTION = "option"
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
@ -141,14 +144,26 @@ async def async_setup(hass: HomeAssistantType, config: ConfigType) -> bool:
component.async_register_entity_service(
SERVICE_SELECT_NEXT,
{},
callback(lambda entity, call: entity.async_offset_index(1)),
{vol.Optional(ATTR_CYCLE, default=True): bool},
"async_next",
)
component.async_register_entity_service(
SERVICE_SELECT_PREVIOUS,
{vol.Optional(ATTR_CYCLE, default=True): bool},
"async_previous",
)
component.async_register_entity_service(
SERVICE_SELECT_FIRST,
{},
callback(lambda entity, call: entity.async_offset_index(-1)),
callback(lambda entity, call: entity.async_select_index(0)),
)
component.async_register_entity_service(
SERVICE_SELECT_LAST,
{},
callback(lambda entity, call: entity.async_select_index(-1)),
)
component.async_register_entity_service(
@ -263,13 +278,37 @@ class InputSelect(RestoreEntity):
self.async_write_ha_state()
@callback
def async_offset_index(self, offset):
"""Offset current index."""
current_index = self._options.index(self._current_option)
new_index = (current_index + offset) % len(self._options)
def async_select_index(self, idx):
"""Select new option by index."""
new_index = idx % len(self._options)
self._current_option = self._options[new_index]
self.async_write_ha_state()
@callback
def async_offset_index(self, offset, cycle):
"""Offset current index."""
current_index = self._options.index(self._current_option)
new_index = current_index + offset
if cycle:
new_index = new_index % len(self._options)
else:
if new_index < 0:
new_index = 0
elif new_index >= len(self._options):
new_index = len(self._options) - 1
self._current_option = self._options[new_index]
self.async_write_ha_state()
@callback
def async_next(self, cycle):
"""Select next option."""
self.async_offset_index(1, cycle)
@callback
def async_previous(self, cycle):
"""Select previous option."""
self.async_offset_index(-1, cycle)
@callback
def async_set_options(self, options):
"""Set options."""

View File

@ -4,6 +4,9 @@ select_next:
entity_id:
description: Entity id of the input select to select the next value for.
example: input_select.my_select
cycle:
description: If the option should cycle from the last to the first (defaults to true).
example: true
select_option:
description: Select an option of an input select entity.
fields:
@ -19,6 +22,21 @@ select_previous:
entity_id:
description: Entity id of the input select to select the previous value for.
example: input_select.my_select
cycle:
description: If the option should cycle from the first to the last (defaults to true).
example: true
select_first:
description: Select the first option of an input select entity.
fields:
entity_id:
description: Entity id of the input select to select the first value for.
example: input_select.my_select
select_last:
description: Select the last option of an input select entity.
fields:
entity_id:
description: Entity id of the input select to select the last value for.
example: input_select.my_select
set_options:
description: Set the options of an input select entity.
fields:

View File

@ -9,6 +9,8 @@ from homeassistant.components.input_select import (
ATTR_OPTIONS,
CONF_INITIAL,
DOMAIN,
SERVICE_SELECT_FIRST,
SERVICE_SELECT_LAST,
SERVICE_SELECT_NEXT,
SERVICE_SELECT_OPTION,
SERVICE_SELECT_PREVIOUS,
@ -104,6 +106,32 @@ def select_previous(hass, entity_id):
)
@bind_hass
def select_first(hass, entity_id):
"""Set first value of input_select.
This is a legacy helper method. Do not use it for new tests.
"""
hass.async_create_task(
hass.services.async_call(
DOMAIN, SERVICE_SELECT_FIRST, {ATTR_ENTITY_ID: entity_id}
)
)
@bind_hass
def select_last(hass, entity_id):
"""Set last value of input_select.
This is a legacy helper method. Do not use it for new tests.
"""
hass.async_create_task(
hass.services.async_call(
DOMAIN, SERVICE_SELECT_LAST, {ATTR_ENTITY_ID: entity_id}
)
)
async def test_config(hass):
"""Test config."""
invalid_configs = [
@ -207,6 +235,38 @@ async def test_select_previous(hass):
assert "last option" == state.state
async def test_select_first_last(hass):
"""Test select_first and _last methods."""
assert await async_setup_component(
hass,
DOMAIN,
{
DOMAIN: {
"test_1": {
"options": ["first option", "middle option", "last option"],
"initial": "middle option",
}
}
},
)
entity_id = "input_select.test_1"
state = hass.states.get(entity_id)
assert "middle option" == state.state
select_first(hass, entity_id)
await hass.async_block_till_done()
state = hass.states.get(entity_id)
assert "first option" == state.state
select_last(hass, entity_id)
await hass.async_block_till_done()
state = hass.states.get(entity_id)
assert "last option" == state.state
async def test_config_options(hass):
"""Test configuration options."""
count_start = len(hass.states.async_entity_ids())