mirror of
https://github.com/home-assistant/core.git
synced 2025-04-25 09:47:52 +00:00
Options flow for Monoprice sources (#33156)
* Options flow for monoprice sources * Fix lint errors
This commit is contained in:
parent
2d002f3ef6
commit
b50281a917
@ -33,6 +33,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
|
|||||||
_LOGGER.error("Error connecting to Monoprice controller at %s", port)
|
_LOGGER.error("Error connecting to Monoprice controller at %s", port)
|
||||||
raise ConfigEntryNotReady
|
raise ConfigEntryNotReady
|
||||||
|
|
||||||
|
entry.add_update_listener(_update_listener)
|
||||||
|
|
||||||
for component in PLATFORMS:
|
for component in PLATFORMS:
|
||||||
hass.async_create_task(
|
hass.async_create_task(
|
||||||
hass.config_entries.async_forward_entry_setup(entry, component)
|
hass.config_entries.async_forward_entry_setup(entry, component)
|
||||||
@ -53,3 +55,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry):
|
|||||||
)
|
)
|
||||||
|
|
||||||
return unload_ok
|
return unload_ok
|
||||||
|
|
||||||
|
|
||||||
|
async def _update_listener(hass: HomeAssistant, entry: ConfigEntry):
|
||||||
|
"""Handle options update."""
|
||||||
|
await hass.config_entries.async_reload(entry.entry_id)
|
||||||
|
@ -21,17 +21,31 @@ from .const import DOMAIN # pylint:disable=unused-import
|
|||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
DATA_SCHEMA = vol.Schema(
|
SOURCES = [
|
||||||
{
|
CONF_SOURCE_1,
|
||||||
vol.Required(CONF_PORT): str,
|
CONF_SOURCE_2,
|
||||||
vol.Optional(CONF_SOURCE_1): str,
|
CONF_SOURCE_3,
|
||||||
vol.Optional(CONF_SOURCE_2): str,
|
CONF_SOURCE_4,
|
||||||
vol.Optional(CONF_SOURCE_3): str,
|
CONF_SOURCE_5,
|
||||||
vol.Optional(CONF_SOURCE_4): str,
|
CONF_SOURCE_6,
|
||||||
vol.Optional(CONF_SOURCE_5): str,
|
]
|
||||||
vol.Optional(CONF_SOURCE_6): str,
|
|
||||||
|
OPTIONS_FOR_DATA = {vol.Optional(source): str for source in SOURCES}
|
||||||
|
|
||||||
|
DATA_SCHEMA = vol.Schema({vol.Required(CONF_PORT): str, **OPTIONS_FOR_DATA})
|
||||||
|
|
||||||
|
|
||||||
|
@core.callback
|
||||||
|
def _sources_from_config(data):
|
||||||
|
sources_config = {
|
||||||
|
str(idx + 1): data.get(source) for idx, source in enumerate(SOURCES)
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
index: name.strip()
|
||||||
|
for index, name in sources_config.items()
|
||||||
|
if (name is not None and name.strip() != "")
|
||||||
}
|
}
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
async def validate_input(hass: core.HomeAssistant, data):
|
async def validate_input(hass: core.HomeAssistant, data):
|
||||||
@ -45,19 +59,8 @@ async def validate_input(hass: core.HomeAssistant, data):
|
|||||||
_LOGGER.error("Error connecting to Monoprice controller")
|
_LOGGER.error("Error connecting to Monoprice controller")
|
||||||
raise CannotConnect
|
raise CannotConnect
|
||||||
|
|
||||||
sources_config = {
|
sources = _sources_from_config(data)
|
||||||
1: data.get(CONF_SOURCE_1),
|
|
||||||
2: data.get(CONF_SOURCE_2),
|
|
||||||
3: data.get(CONF_SOURCE_3),
|
|
||||||
4: data.get(CONF_SOURCE_4),
|
|
||||||
5: data.get(CONF_SOURCE_5),
|
|
||||||
6: data.get(CONF_SOURCE_6),
|
|
||||||
}
|
|
||||||
sources = {
|
|
||||||
index: name.strip()
|
|
||||||
for index, name in sources_config.items()
|
|
||||||
if (name is not None and name.strip() != "")
|
|
||||||
}
|
|
||||||
# Return info that you want to store in the config entry.
|
# Return info that you want to store in the config entry.
|
||||||
return {CONF_PORT: data[CONF_PORT], CONF_SOURCES: sources}
|
return {CONF_PORT: data[CONF_PORT], CONF_SOURCES: sources}
|
||||||
|
|
||||||
@ -86,6 +89,55 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
step_id="user", data_schema=DATA_SCHEMA, errors=errors
|
step_id="user", data_schema=DATA_SCHEMA, errors=errors
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
@core.callback
|
||||||
|
def async_get_options_flow(config_entry):
|
||||||
|
"""Define the config flow to handle options."""
|
||||||
|
return MonopriceOptionsFlowHandler(config_entry)
|
||||||
|
|
||||||
|
|
||||||
|
@core.callback
|
||||||
|
def _key_for_source(index, source, previous_sources):
|
||||||
|
if str(index) in previous_sources:
|
||||||
|
key = vol.Optional(source, default=previous_sources[str(index)])
|
||||||
|
else:
|
||||||
|
key = vol.Optional(source)
|
||||||
|
|
||||||
|
return key
|
||||||
|
|
||||||
|
|
||||||
|
class MonopriceOptionsFlowHandler(config_entries.OptionsFlow):
|
||||||
|
"""Handle a Monoprice options flow."""
|
||||||
|
|
||||||
|
def __init__(self, config_entry):
|
||||||
|
"""Initialize."""
|
||||||
|
self.config_entry = config_entry
|
||||||
|
|
||||||
|
@core.callback
|
||||||
|
def _previous_sources(self):
|
||||||
|
if CONF_SOURCES in self.config_entry.options:
|
||||||
|
previous = self.config_entry.options[CONF_SOURCES]
|
||||||
|
else:
|
||||||
|
previous = self.config_entry.data[CONF_SOURCES]
|
||||||
|
|
||||||
|
return previous
|
||||||
|
|
||||||
|
async def async_step_init(self, user_input=None):
|
||||||
|
"""Manage the options."""
|
||||||
|
if user_input is not None:
|
||||||
|
return self.async_create_entry(
|
||||||
|
title="", data={CONF_SOURCES: _sources_from_config(user_input)}
|
||||||
|
)
|
||||||
|
|
||||||
|
previous_sources = self._previous_sources()
|
||||||
|
|
||||||
|
options = {
|
||||||
|
_key_for_source(idx + 1, source, previous_sources): str
|
||||||
|
for idx, source in enumerate(SOURCES)
|
||||||
|
}
|
||||||
|
|
||||||
|
return self.async_show_form(step_id="init", data_schema=vol.Schema(options),)
|
||||||
|
|
||||||
|
|
||||||
class CannotConnect(exceptions.HomeAssistantError):
|
class CannotConnect(exceptions.HomeAssistantError):
|
||||||
"""Error to indicate we cannot connect."""
|
"""Error to indicate we cannot connect."""
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
"""Support for interfacing with Monoprice 6 zone home audio controller."""
|
"""Support for interfacing with Monoprice 6 zone home audio controller."""
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
from homeassistant import core
|
||||||
from homeassistant.components.media_player import MediaPlayerDevice
|
from homeassistant.components.media_player import MediaPlayerDevice
|
||||||
from homeassistant.components.media_player.const import (
|
from homeassistant.components.media_player.const import (
|
||||||
SUPPORT_SELECT_SOURCE,
|
SUPPORT_SELECT_SOURCE,
|
||||||
@ -27,7 +28,10 @@ SUPPORT_MONOPRICE = (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _get_sources(sources_config):
|
@core.callback
|
||||||
|
def _get_sources_from_dict(data):
|
||||||
|
sources_config = data[CONF_SOURCES]
|
||||||
|
|
||||||
source_id_name = {int(index): name for index, name in sources_config.items()}
|
source_id_name = {int(index): name for index, name in sources_config.items()}
|
||||||
|
|
||||||
source_name_id = {v: k for k, v in source_id_name.items()}
|
source_name_id = {v: k for k, v in source_id_name.items()}
|
||||||
@ -37,13 +41,22 @@ def _get_sources(sources_config):
|
|||||||
return [source_id_name, source_name_id, source_names]
|
return [source_id_name, source_name_id, source_names]
|
||||||
|
|
||||||
|
|
||||||
|
@core.callback
|
||||||
|
def _get_sources(config_entry):
|
||||||
|
if CONF_SOURCES in config_entry.options:
|
||||||
|
data = config_entry.options
|
||||||
|
else:
|
||||||
|
data = config_entry.data
|
||||||
|
return _get_sources_from_dict(data)
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||||
"""Set up the Monoprice 6-zone amplifier platform."""
|
"""Set up the Monoprice 6-zone amplifier platform."""
|
||||||
port = config_entry.data[CONF_PORT]
|
port = config_entry.data[CONF_PORT]
|
||||||
|
|
||||||
monoprice = hass.data[DOMAIN][config_entry.entry_id]
|
monoprice = hass.data[DOMAIN][config_entry.entry_id]
|
||||||
|
|
||||||
sources = _get_sources(config_entry.data.get(CONF_SOURCES))
|
sources = _get_sources(config_entry)
|
||||||
|
|
||||||
entities = []
|
entities = []
|
||||||
for i in range(1, 4):
|
for i in range(1, 4):
|
||||||
|
@ -22,5 +22,20 @@
|
|||||||
"abort": {
|
"abort": {
|
||||||
"already_configured": "Device is already configured"
|
"already_configured": "Device is already configured"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"step": {
|
||||||
|
"init": {
|
||||||
|
"title": "Configure sources",
|
||||||
|
"data": {
|
||||||
|
"source_1": "Name of source #1",
|
||||||
|
"source_2": "Name of source #2",
|
||||||
|
"source_3": "Name of source #3",
|
||||||
|
"source_4": "Name of source #4",
|
||||||
|
"source_5": "Name of source #5",
|
||||||
|
"source_6": "Name of source #6"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -2,7 +2,7 @@
|
|||||||
from asynctest import patch
|
from asynctest import patch
|
||||||
from serial import SerialException
|
from serial import SerialException
|
||||||
|
|
||||||
from homeassistant import config_entries, setup
|
from homeassistant import config_entries, data_entry_flow, setup
|
||||||
from homeassistant.components.monoprice.const import (
|
from homeassistant.components.monoprice.const import (
|
||||||
CONF_SOURCE_1,
|
CONF_SOURCE_1,
|
||||||
CONF_SOURCE_4,
|
CONF_SOURCE_4,
|
||||||
@ -12,6 +12,8 @@ from homeassistant.components.monoprice.const import (
|
|||||||
)
|
)
|
||||||
from homeassistant.const import CONF_PORT
|
from homeassistant.const import CONF_PORT
|
||||||
|
|
||||||
|
from tests.common import MockConfigEntry
|
||||||
|
|
||||||
CONFIG = {
|
CONFIG = {
|
||||||
CONF_PORT: "/test/port",
|
CONF_PORT: "/test/port",
|
||||||
CONF_SOURCE_1: "one",
|
CONF_SOURCE_1: "one",
|
||||||
@ -45,7 +47,7 @@ async def test_form(hass):
|
|||||||
assert result2["title"] == CONFIG[CONF_PORT]
|
assert result2["title"] == CONFIG[CONF_PORT]
|
||||||
assert result2["data"] == {
|
assert result2["data"] == {
|
||||||
CONF_PORT: CONFIG[CONF_PORT],
|
CONF_PORT: CONFIG[CONF_PORT],
|
||||||
CONF_SOURCES: {1: CONFIG[CONF_SOURCE_1], 4: CONFIG[CONF_SOURCE_4]},
|
CONF_SOURCES: {"1": CONFIG[CONF_SOURCE_1], "4": CONFIG[CONF_SOURCE_4]},
|
||||||
}
|
}
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert len(mock_setup.mock_calls) == 1
|
assert len(mock_setup.mock_calls) == 1
|
||||||
@ -86,3 +88,32 @@ async def test_generic_exception(hass):
|
|||||||
|
|
||||||
assert result2["type"] == "form"
|
assert result2["type"] == "form"
|
||||||
assert result2["errors"] == {"base": "unknown"}
|
assert result2["errors"] == {"base": "unknown"}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_options_flow(hass):
|
||||||
|
"""Test config flow options."""
|
||||||
|
conf = {CONF_PORT: "/test/port", CONF_SOURCES: {"4": "four"}}
|
||||||
|
|
||||||
|
config_entry = MockConfigEntry(
|
||||||
|
domain=DOMAIN,
|
||||||
|
# unique_id="abcde12345",
|
||||||
|
data=conf,
|
||||||
|
# options={CONF_SHOW_ON_MAP: True},
|
||||||
|
)
|
||||||
|
config_entry.add_to_hass(hass)
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.monoprice.async_setup_entry", return_value=True
|
||||||
|
):
|
||||||
|
result = await hass.config_entries.options.async_init(config_entry.entry_id)
|
||||||
|
|
||||||
|
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||||
|
assert result["step_id"] == "init"
|
||||||
|
|
||||||
|
result = await hass.config_entries.options.async_configure(
|
||||||
|
result["flow_id"],
|
||||||
|
user_input={CONF_SOURCE_1: "one", CONF_SOURCE_4: "", CONF_SOURCE_5: "five"},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
|
||||||
|
assert config_entry.options[CONF_SOURCES] == {"1": "one", "5": "five"}
|
||||||
|
@ -37,6 +37,7 @@ from homeassistant.helpers.entity_component import async_update_entity
|
|||||||
from tests.common import MockConfigEntry
|
from tests.common import MockConfigEntry
|
||||||
|
|
||||||
MOCK_CONFIG = {CONF_PORT: "fake port", CONF_SOURCES: {"1": "one", "3": "three"}}
|
MOCK_CONFIG = {CONF_PORT: "fake port", CONF_SOURCES: {"1": "one", "3": "three"}}
|
||||||
|
MOCK_OPTIONS = {CONF_SOURCES: {"2": "two", "4": "four"}}
|
||||||
|
|
||||||
ZONE_1_ID = "media_player.zone_11"
|
ZONE_1_ID = "media_player.zone_11"
|
||||||
ZONE_2_ID = "media_player.zone_12"
|
ZONE_2_ID = "media_player.zone_12"
|
||||||
@ -117,6 +118,20 @@ async def _setup_monoprice(hass, monoprice):
|
|||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
|
||||||
|
async def _setup_monoprice_with_options(hass, monoprice):
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.monoprice.get_monoprice", new=lambda *a: monoprice,
|
||||||
|
):
|
||||||
|
config_entry = MockConfigEntry(
|
||||||
|
domain=DOMAIN, data=MOCK_CONFIG, options=MOCK_OPTIONS
|
||||||
|
)
|
||||||
|
config_entry.add_to_hass(hass)
|
||||||
|
await hass.config_entries.async_setup(config_entry.entry_id)
|
||||||
|
# setup_component(self.hass, DOMAIN, MOCK_CONFIG)
|
||||||
|
# self.hass.async_block_till_done()
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
|
||||||
async def _call_media_player_service(hass, name, data):
|
async def _call_media_player_service(hass, name, data):
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
MEDIA_PLAYER_DOMAIN, name, service_data=data, blocking=True
|
MEDIA_PLAYER_DOMAIN, name, service_data=data, blocking=True
|
||||||
@ -256,7 +271,6 @@ async def test_restore_without_snapshort(hass):
|
|||||||
|
|
||||||
async def test_update(hass):
|
async def test_update(hass):
|
||||||
"""Test updating values from monoprice."""
|
"""Test updating values from monoprice."""
|
||||||
"""Test snapshot save/restore service calls."""
|
|
||||||
monoprice = MockMonoprice()
|
monoprice = MockMonoprice()
|
||||||
await _setup_monoprice(hass, monoprice)
|
await _setup_monoprice(hass, monoprice)
|
||||||
|
|
||||||
@ -305,6 +319,15 @@ async def test_source_list(hass):
|
|||||||
assert ["one", "three"] == state.attributes[ATTR_INPUT_SOURCE_LIST]
|
assert ["one", "three"] == state.attributes[ATTR_INPUT_SOURCE_LIST]
|
||||||
|
|
||||||
|
|
||||||
|
async def test_source_list_with_options(hass):
|
||||||
|
"""Test source list property."""
|
||||||
|
await _setup_monoprice_with_options(hass, MockMonoprice())
|
||||||
|
|
||||||
|
state = hass.states.get(ZONE_1_ID)
|
||||||
|
# Note, the list is sorted!
|
||||||
|
assert ["two", "four"] == state.attributes[ATTR_INPUT_SOURCE_LIST]
|
||||||
|
|
||||||
|
|
||||||
async def test_select_source(hass):
|
async def test_select_source(hass):
|
||||||
"""Test source selection methods."""
|
"""Test source selection methods."""
|
||||||
monoprice = MockMonoprice()
|
monoprice = MockMonoprice()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user