Options flow for Monoprice sources (#33156)

* Options flow for monoprice sources

* Fix lint errors
This commit is contained in:
On Freund 2020-03-24 02:32:21 +02:00 committed by GitHub
parent 2d002f3ef6
commit b50281a917
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 169 additions and 28 deletions

View File

@ -33,6 +33,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
_LOGGER.error("Error connecting to Monoprice controller at %s", port)
raise ConfigEntryNotReady
entry.add_update_listener(_update_listener)
for component in PLATFORMS:
hass.async_create_task(
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
async def _update_listener(hass: HomeAssistant, entry: ConfigEntry):
"""Handle options update."""
await hass.config_entries.async_reload(entry.entry_id)

View File

@ -21,17 +21,31 @@ from .const import DOMAIN # pylint:disable=unused-import
_LOGGER = logging.getLogger(__name__)
DATA_SCHEMA = vol.Schema(
{
vol.Required(CONF_PORT): str,
vol.Optional(CONF_SOURCE_1): str,
vol.Optional(CONF_SOURCE_2): str,
vol.Optional(CONF_SOURCE_3): str,
vol.Optional(CONF_SOURCE_4): str,
vol.Optional(CONF_SOURCE_5): str,
vol.Optional(CONF_SOURCE_6): str,
SOURCES = [
CONF_SOURCE_1,
CONF_SOURCE_2,
CONF_SOURCE_3,
CONF_SOURCE_4,
CONF_SOURCE_5,
CONF_SOURCE_6,
]
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):
@ -45,19 +59,8 @@ async def validate_input(hass: core.HomeAssistant, data):
_LOGGER.error("Error connecting to Monoprice controller")
raise CannotConnect
sources_config = {
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() != "")
}
sources = _sources_from_config(data)
# Return info that you want to store in the config entry.
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
)
@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):
"""Error to indicate we cannot connect."""

View File

@ -1,6 +1,7 @@
"""Support for interfacing with Monoprice 6 zone home audio controller."""
import logging
from homeassistant import core
from homeassistant.components.media_player import MediaPlayerDevice
from homeassistant.components.media_player.const import (
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_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]
@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):
"""Set up the Monoprice 6-zone amplifier platform."""
port = config_entry.data[CONF_PORT]
monoprice = hass.data[DOMAIN][config_entry.entry_id]
sources = _get_sources(config_entry.data.get(CONF_SOURCES))
sources = _get_sources(config_entry)
entities = []
for i in range(1, 4):

View File

@ -22,5 +22,20 @@
"abort": {
"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"
}
}
}
}
}

View File

@ -2,7 +2,7 @@
from asynctest import patch
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 (
CONF_SOURCE_1,
CONF_SOURCE_4,
@ -12,6 +12,8 @@ from homeassistant.components.monoprice.const import (
)
from homeassistant.const import CONF_PORT
from tests.common import MockConfigEntry
CONFIG = {
CONF_PORT: "/test/port",
CONF_SOURCE_1: "one",
@ -45,7 +47,7 @@ async def test_form(hass):
assert result2["title"] == CONFIG[CONF_PORT]
assert result2["data"] == {
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()
assert len(mock_setup.mock_calls) == 1
@ -86,3 +88,32 @@ async def test_generic_exception(hass):
assert result2["type"] == "form"
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"}

View File

@ -37,6 +37,7 @@ from homeassistant.helpers.entity_component import async_update_entity
from tests.common import MockConfigEntry
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_2_ID = "media_player.zone_12"
@ -117,6 +118,20 @@ async def _setup_monoprice(hass, monoprice):
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):
await hass.services.async_call(
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):
"""Test updating values from monoprice."""
"""Test snapshot save/restore service calls."""
monoprice = MockMonoprice()
await _setup_monoprice(hass, monoprice)
@ -305,6 +319,15 @@ async def test_source_list(hass):
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):
"""Test source selection methods."""
monoprice = MockMonoprice()