mirror of
https://github.com/home-assistant/core.git
synced 2025-04-23 16:57:53 +00:00
Add sound mode support to Onkyo (#133531)
This commit is contained in:
parent
2bba185e4c
commit
38cc26485a
@ -1,6 +1,7 @@
|
||||
"""The onkyo component."""
|
||||
|
||||
from dataclasses import dataclass
|
||||
import logging
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_HOST, Platform
|
||||
@ -9,10 +10,18 @@ from homeassistant.exceptions import ConfigEntryNotReady
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
|
||||
from .const import DOMAIN, OPTION_INPUT_SOURCES, InputSource
|
||||
from .const import (
|
||||
DOMAIN,
|
||||
OPTION_INPUT_SOURCES,
|
||||
OPTION_LISTENING_MODES,
|
||||
InputSource,
|
||||
ListeningMode,
|
||||
)
|
||||
from .receiver import Receiver, async_interview
|
||||
from .services import DATA_MP_ENTITIES, async_register_services
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
PLATFORMS = [Platform.MEDIA_PLAYER]
|
||||
|
||||
CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN)
|
||||
@ -24,6 +33,7 @@ class OnkyoData:
|
||||
|
||||
receiver: Receiver
|
||||
sources: dict[InputSource, str]
|
||||
sound_modes: dict[ListeningMode, str]
|
||||
|
||||
|
||||
type OnkyoConfigEntry = ConfigEntry[OnkyoData]
|
||||
@ -50,7 +60,10 @@ async def async_setup_entry(hass: HomeAssistant, entry: OnkyoConfigEntry) -> boo
|
||||
sources_store: dict[str, str] = entry.options[OPTION_INPUT_SOURCES]
|
||||
sources = {InputSource(k): v for k, v in sources_store.items()}
|
||||
|
||||
entry.runtime_data = OnkyoData(receiver, sources)
|
||||
sound_modes_store: dict[str, str] = entry.options.get(OPTION_LISTENING_MODES, {})
|
||||
sound_modes = {ListeningMode(k): v for k, v in sound_modes_store.items()}
|
||||
|
||||
entry.runtime_data = OnkyoData(receiver, sources, sound_modes)
|
||||
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
"""Config flow for Onkyo."""
|
||||
|
||||
from collections.abc import Mapping
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
@ -33,12 +34,14 @@ from .const import (
|
||||
CONF_SOURCES,
|
||||
DOMAIN,
|
||||
OPTION_INPUT_SOURCES,
|
||||
OPTION_LISTENING_MODES,
|
||||
OPTION_MAX_VOLUME,
|
||||
OPTION_MAX_VOLUME_DEFAULT,
|
||||
OPTION_VOLUME_RESOLUTION,
|
||||
OPTION_VOLUME_RESOLUTION_DEFAULT,
|
||||
VOLUME_RESOLUTION_ALLOWED,
|
||||
InputSource,
|
||||
ListeningMode,
|
||||
)
|
||||
from .receiver import ReceiverInfo, async_discover, async_interview
|
||||
|
||||
@ -46,9 +49,14 @@ _LOGGER = logging.getLogger(__name__)
|
||||
|
||||
CONF_DEVICE = "device"
|
||||
|
||||
INPUT_SOURCES_ALL_MEANINGS = [
|
||||
input_source.value_meaning for input_source in InputSource
|
||||
]
|
||||
INPUT_SOURCES_DEFAULT: dict[str, str] = {}
|
||||
LISTENING_MODES_DEFAULT: dict[str, str] = {}
|
||||
INPUT_SOURCES_ALL_MEANINGS = {
|
||||
input_source.value_meaning: input_source for input_source in InputSource
|
||||
}
|
||||
LISTENING_MODES_ALL_MEANINGS = {
|
||||
listening_mode.value_meaning: listening_mode for listening_mode in ListeningMode
|
||||
}
|
||||
STEP_MANUAL_SCHEMA = vol.Schema({vol.Required(CONF_HOST): str})
|
||||
STEP_RECONFIGURE_SCHEMA = vol.Schema(
|
||||
{
|
||||
@ -59,7 +67,14 @@ STEP_CONFIGURE_SCHEMA = STEP_RECONFIGURE_SCHEMA.extend(
|
||||
{
|
||||
vol.Required(OPTION_INPUT_SOURCES): SelectSelector(
|
||||
SelectSelectorConfig(
|
||||
options=INPUT_SOURCES_ALL_MEANINGS,
|
||||
options=list(INPUT_SOURCES_ALL_MEANINGS),
|
||||
multiple=True,
|
||||
mode=SelectSelectorMode.DROPDOWN,
|
||||
)
|
||||
),
|
||||
vol.Required(OPTION_LISTENING_MODES): SelectSelector(
|
||||
SelectSelectorConfig(
|
||||
options=list(LISTENING_MODES_ALL_MEANINGS),
|
||||
multiple=True,
|
||||
mode=SelectSelectorMode.DROPDOWN,
|
||||
)
|
||||
@ -238,9 +253,8 @@ class OnkyoConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
CONF_HOST: self._receiver_info.host,
|
||||
},
|
||||
options={
|
||||
**entry_options,
|
||||
OPTION_VOLUME_RESOLUTION: volume_resolution,
|
||||
OPTION_MAX_VOLUME: entry_options[OPTION_MAX_VOLUME],
|
||||
OPTION_INPUT_SOURCES: entry_options[OPTION_INPUT_SOURCES],
|
||||
},
|
||||
)
|
||||
|
||||
@ -250,12 +264,24 @@ class OnkyoConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
input_source_meanings: list[str] = user_input[OPTION_INPUT_SOURCES]
|
||||
if not input_source_meanings:
|
||||
errors[OPTION_INPUT_SOURCES] = "empty_input_source_list"
|
||||
else:
|
||||
|
||||
listening_modes: list[str] = user_input[OPTION_LISTENING_MODES]
|
||||
if not listening_modes:
|
||||
errors[OPTION_LISTENING_MODES] = "empty_listening_mode_list"
|
||||
|
||||
if not errors:
|
||||
input_sources_store: dict[str, str] = {}
|
||||
for input_source_meaning in input_source_meanings:
|
||||
input_source = InputSource.from_meaning(input_source_meaning)
|
||||
input_source = INPUT_SOURCES_ALL_MEANINGS[input_source_meaning]
|
||||
input_sources_store[input_source.value] = input_source_meaning
|
||||
|
||||
listening_modes_store: dict[str, str] = {}
|
||||
for listening_mode_meaning in listening_modes:
|
||||
listening_mode = LISTENING_MODES_ALL_MEANINGS[
|
||||
listening_mode_meaning
|
||||
]
|
||||
listening_modes_store[listening_mode.value] = listening_mode_meaning
|
||||
|
||||
result = self.async_create_entry(
|
||||
title=self._receiver_info.model_name,
|
||||
data={
|
||||
@ -265,6 +291,7 @@ class OnkyoConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
OPTION_VOLUME_RESOLUTION: volume_resolution,
|
||||
OPTION_MAX_VOLUME: OPTION_MAX_VOLUME_DEFAULT,
|
||||
OPTION_INPUT_SOURCES: input_sources_store,
|
||||
OPTION_LISTENING_MODES: listening_modes_store,
|
||||
},
|
||||
)
|
||||
|
||||
@ -278,16 +305,13 @@ class OnkyoConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
if reconfigure_entry is None:
|
||||
suggested_values = {
|
||||
OPTION_VOLUME_RESOLUTION: OPTION_VOLUME_RESOLUTION_DEFAULT,
|
||||
OPTION_INPUT_SOURCES: [],
|
||||
OPTION_INPUT_SOURCES: INPUT_SOURCES_DEFAULT,
|
||||
OPTION_LISTENING_MODES: LISTENING_MODES_DEFAULT,
|
||||
}
|
||||
else:
|
||||
entry_options = reconfigure_entry.options
|
||||
suggested_values = {
|
||||
OPTION_VOLUME_RESOLUTION: entry_options[OPTION_VOLUME_RESOLUTION],
|
||||
OPTION_INPUT_SOURCES: [
|
||||
InputSource(input_source).value_meaning
|
||||
for input_source in entry_options[OPTION_INPUT_SOURCES]
|
||||
],
|
||||
}
|
||||
|
||||
return self.async_show_form(
|
||||
@ -356,6 +380,7 @@ class OnkyoConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
OPTION_VOLUME_RESOLUTION: volume_resolution,
|
||||
OPTION_MAX_VOLUME: max_volume,
|
||||
OPTION_INPUT_SOURCES: sources_store,
|
||||
OPTION_LISTENING_MODES: LISTENING_MODES_DEFAULT,
|
||||
},
|
||||
)
|
||||
|
||||
@ -373,7 +398,14 @@ OPTIONS_STEP_INIT_SCHEMA = vol.Schema(
|
||||
),
|
||||
vol.Required(OPTION_INPUT_SOURCES): SelectSelector(
|
||||
SelectSelectorConfig(
|
||||
options=INPUT_SOURCES_ALL_MEANINGS,
|
||||
options=list(INPUT_SOURCES_ALL_MEANINGS),
|
||||
multiple=True,
|
||||
mode=SelectSelectorMode.DROPDOWN,
|
||||
)
|
||||
),
|
||||
vol.Required(OPTION_LISTENING_MODES): SelectSelector(
|
||||
SelectSelectorConfig(
|
||||
options=list(LISTENING_MODES_ALL_MEANINGS),
|
||||
multiple=True,
|
||||
mode=SelectSelectorMode.DROPDOWN,
|
||||
)
|
||||
@ -387,6 +419,7 @@ class OnkyoOptionsFlowHandler(OptionsFlow):
|
||||
|
||||
_data: dict[str, Any]
|
||||
_input_sources: dict[InputSource, str]
|
||||
_listening_modes: dict[ListeningMode, str]
|
||||
|
||||
async def async_step_init(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
@ -394,20 +427,40 @@ class OnkyoOptionsFlowHandler(OptionsFlow):
|
||||
"""Manage the options."""
|
||||
errors = {}
|
||||
|
||||
entry_options = self.config_entry.options
|
||||
entry_options: Mapping[str, Any] = self.config_entry.options
|
||||
entry_options = {
|
||||
OPTION_LISTENING_MODES: LISTENING_MODES_DEFAULT,
|
||||
**entry_options,
|
||||
}
|
||||
|
||||
if user_input is not None:
|
||||
self._input_sources = {}
|
||||
for input_source_meaning in user_input[OPTION_INPUT_SOURCES]:
|
||||
input_source = InputSource.from_meaning(input_source_meaning)
|
||||
input_source_name = entry_options[OPTION_INPUT_SOURCES].get(
|
||||
input_source.value, input_source_meaning
|
||||
)
|
||||
self._input_sources[input_source] = input_source_name
|
||||
|
||||
if not self._input_sources:
|
||||
input_source_meanings: list[str] = user_input[OPTION_INPUT_SOURCES]
|
||||
if not input_source_meanings:
|
||||
errors[OPTION_INPUT_SOURCES] = "empty_input_source_list"
|
||||
else:
|
||||
|
||||
listening_mode_meanings: list[str] = user_input[OPTION_LISTENING_MODES]
|
||||
if not listening_mode_meanings:
|
||||
errors[OPTION_LISTENING_MODES] = "empty_listening_mode_list"
|
||||
|
||||
if not errors:
|
||||
self._input_sources = {}
|
||||
for input_source_meaning in input_source_meanings:
|
||||
input_source = INPUT_SOURCES_ALL_MEANINGS[input_source_meaning]
|
||||
input_source_name = entry_options[OPTION_INPUT_SOURCES].get(
|
||||
input_source.value, input_source_meaning
|
||||
)
|
||||
self._input_sources[input_source] = input_source_name
|
||||
|
||||
self._listening_modes = {}
|
||||
for listening_mode_meaning in listening_mode_meanings:
|
||||
listening_mode = LISTENING_MODES_ALL_MEANINGS[
|
||||
listening_mode_meaning
|
||||
]
|
||||
listening_mode_name = entry_options[OPTION_LISTENING_MODES].get(
|
||||
listening_mode.value, listening_mode_meaning
|
||||
)
|
||||
self._listening_modes[listening_mode] = listening_mode_name
|
||||
|
||||
self._data = {
|
||||
OPTION_VOLUME_RESOLUTION: entry_options[OPTION_VOLUME_RESOLUTION],
|
||||
OPTION_MAX_VOLUME: user_input[OPTION_MAX_VOLUME],
|
||||
@ -423,6 +476,10 @@ class OnkyoOptionsFlowHandler(OptionsFlow):
|
||||
InputSource(input_source).value_meaning
|
||||
for input_source in entry_options[OPTION_INPUT_SOURCES]
|
||||
],
|
||||
OPTION_LISTENING_MODES: [
|
||||
ListeningMode(listening_mode).value_meaning
|
||||
for listening_mode in entry_options[OPTION_LISTENING_MODES]
|
||||
],
|
||||
}
|
||||
|
||||
return self.async_show_form(
|
||||
@ -440,28 +497,48 @@ class OnkyoOptionsFlowHandler(OptionsFlow):
|
||||
if user_input is not None:
|
||||
input_sources_store: dict[str, str] = {}
|
||||
for input_source_meaning, input_source_name in user_input[
|
||||
"input_sources"
|
||||
OPTION_INPUT_SOURCES
|
||||
].items():
|
||||
input_source = InputSource.from_meaning(input_source_meaning)
|
||||
input_source = INPUT_SOURCES_ALL_MEANINGS[input_source_meaning]
|
||||
input_sources_store[input_source.value] = input_source_name
|
||||
|
||||
listening_modes_store: dict[str, str] = {}
|
||||
for listening_mode_meaning, listening_mode_name in user_input[
|
||||
OPTION_LISTENING_MODES
|
||||
].items():
|
||||
listening_mode = LISTENING_MODES_ALL_MEANINGS[listening_mode_meaning]
|
||||
listening_modes_store[listening_mode.value] = listening_mode_name
|
||||
|
||||
return self.async_create_entry(
|
||||
data={
|
||||
**self._data,
|
||||
OPTION_INPUT_SOURCES: input_sources_store,
|
||||
OPTION_LISTENING_MODES: listening_modes_store,
|
||||
}
|
||||
)
|
||||
|
||||
schema_dict: dict[Any, Selector] = {}
|
||||
|
||||
input_sources_schema_dict: dict[Any, Selector] = {}
|
||||
for input_source, input_source_name in self._input_sources.items():
|
||||
schema_dict[
|
||||
input_sources_schema_dict[
|
||||
vol.Required(input_source.value_meaning, default=input_source_name)
|
||||
] = TextSelector()
|
||||
|
||||
listening_modes_schema_dict: dict[Any, Selector] = {}
|
||||
for listening_mode, listening_mode_name in self._listening_modes.items():
|
||||
listening_modes_schema_dict[
|
||||
vol.Required(listening_mode.value_meaning, default=listening_mode_name)
|
||||
] = TextSelector()
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="names",
|
||||
data_schema=vol.Schema(
|
||||
{vol.Required("input_sources"): section(vol.Schema(schema_dict))}
|
||||
{
|
||||
vol.Required(OPTION_INPUT_SOURCES): section(
|
||||
vol.Schema(input_sources_schema_dict)
|
||||
),
|
||||
vol.Required(OPTION_LISTENING_MODES): section(
|
||||
vol.Schema(listening_modes_schema_dict)
|
||||
),
|
||||
}
|
||||
),
|
||||
)
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
from enum import Enum
|
||||
import typing
|
||||
from typing import ClassVar, Literal, Self
|
||||
from typing import Literal, Self
|
||||
|
||||
import pyeiscp
|
||||
|
||||
@ -24,7 +24,27 @@ VOLUME_RESOLUTION_ALLOWED: tuple[VolumeResolution, ...] = typing.get_args(
|
||||
OPTION_MAX_VOLUME = "max_volume"
|
||||
OPTION_MAX_VOLUME_DEFAULT = 100.0
|
||||
|
||||
|
||||
class EnumWithMeaning(Enum):
|
||||
"""Enum with meaning."""
|
||||
|
||||
value_meaning: str
|
||||
|
||||
def __new__(cls, value: str) -> Self:
|
||||
"""Create enum."""
|
||||
obj = object.__new__(cls)
|
||||
obj._value_ = value
|
||||
obj.value_meaning = cls._get_meanings()[value]
|
||||
|
||||
return obj
|
||||
|
||||
@staticmethod
|
||||
def _get_meanings() -> dict[str, str]:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
OPTION_INPUT_SOURCES = "input_sources"
|
||||
OPTION_LISTENING_MODES = "listening_modes"
|
||||
|
||||
_INPUT_SOURCE_MEANINGS = {
|
||||
"00": "VIDEO1 ··· VCR/DVR ··· STB/DVR",
|
||||
@ -71,7 +91,7 @@ _INPUT_SOURCE_MEANINGS = {
|
||||
}
|
||||
|
||||
|
||||
class InputSource(Enum):
|
||||
class InputSource(EnumWithMeaning):
|
||||
"""Receiver input source."""
|
||||
|
||||
DVR = "00"
|
||||
@ -116,24 +136,100 @@ class InputSource(Enum):
|
||||
HDMI_7 = "57"
|
||||
MAIN_SOURCE = "80"
|
||||
|
||||
__meaning_mapping: ClassVar[dict[str, Self]] = {} # type: ignore[misc]
|
||||
@staticmethod
|
||||
def _get_meanings() -> dict[str, str]:
|
||||
return _INPUT_SOURCE_MEANINGS
|
||||
|
||||
value_meaning: str
|
||||
|
||||
def __new__(cls, value: str) -> Self:
|
||||
"""Create InputSource enum."""
|
||||
obj = object.__new__(cls)
|
||||
obj._value_ = value
|
||||
obj.value_meaning = _INPUT_SOURCE_MEANINGS[value]
|
||||
_LISTENING_MODE_MEANINGS = {
|
||||
"00": "STEREO",
|
||||
"01": "DIRECT",
|
||||
"02": "SURROUND",
|
||||
"03": "FILM ··· GAME RPG ··· ADVANCED GAME",
|
||||
"04": "THX",
|
||||
"05": "ACTION ··· GAME ACTION",
|
||||
"06": "MUSICAL ··· GAME ROCK ··· ROCK/POP",
|
||||
"07": "MONO MOVIE",
|
||||
"08": "ORCHESTRA ··· CLASSICAL",
|
||||
"09": "UNPLUGGED",
|
||||
"0A": "STUDIO MIX ··· ENTERTAINMENT SHOW",
|
||||
"0B": "TV LOGIC ··· DRAMA",
|
||||
"0C": "ALL CH STEREO ··· EXTENDED STEREO",
|
||||
"0D": "THEATER DIMENSIONAL ··· FRONT STAGE SURROUND",
|
||||
"0E": "ENHANCED 7/ENHANCE ··· GAME SPORTS ··· SPORTS",
|
||||
"0F": "MONO",
|
||||
"11": "PURE AUDIO ··· PURE DIRECT",
|
||||
"12": "MULTIPLEX",
|
||||
"13": "FULL MONO ··· MONO MUSIC",
|
||||
"14": "DOLBY VIRTUAL/SURROUND ENHANCER",
|
||||
"15": "DTS SURROUND SENSATION",
|
||||
"16": "AUDYSSEY DSX",
|
||||
"17": "DTS VIRTUAL:X",
|
||||
"1F": "WHOLE HOUSE MODE ··· MULTI ZONE MUSIC",
|
||||
"23": "STAGE (JAPAN GENRE CONTROL)",
|
||||
"25": "ACTION (JAPAN GENRE CONTROL)",
|
||||
"26": "MUSIC (JAPAN GENRE CONTROL)",
|
||||
"2E": "SPORTS (JAPAN GENRE CONTROL)",
|
||||
"40": "STRAIGHT DECODE ··· 5.1 CH SURROUND",
|
||||
"41": "DOLBY EX/DTS ES",
|
||||
"42": "THX CINEMA",
|
||||
"43": "THX SURROUND EX",
|
||||
"44": "THX MUSIC",
|
||||
"45": "THX GAMES",
|
||||
"50": "THX U(2)/S(2)/I/S CINEMA",
|
||||
"51": "THX U(2)/S(2)/I/S MUSIC",
|
||||
"52": "THX U(2)/S(2)/I/S GAMES",
|
||||
"80": "DOLBY ATMOS/DOLBY SURROUND ··· PLII/PLIIx MOVIE",
|
||||
"81": "PLII/PLIIx MUSIC",
|
||||
"82": "DTS:X/NEURAL:X ··· NEO:6/NEO:X CINEMA",
|
||||
"83": "NEO:6/NEO:X MUSIC",
|
||||
"84": "DOLBY SURROUND THX CINEMA ··· PLII/PLIIx THX CINEMA",
|
||||
"85": "DTS NEURAL:X THX CINEMA ··· NEO:6/NEO:X THX CINEMA",
|
||||
"86": "PLII/PLIIx GAME",
|
||||
"87": "NEURAL SURR",
|
||||
"88": "NEURAL THX/NEURAL SURROUND",
|
||||
"89": "DOLBY SURROUND THX GAMES ··· PLII/PLIIx THX GAMES",
|
||||
"8A": "DTS NEURAL:X THX GAMES ··· NEO:6/NEO:X THX GAMES",
|
||||
"8B": "DOLBY SURROUND THX MUSIC ··· PLII/PLIIx THX MUSIC",
|
||||
"8C": "DTS NEURAL:X THX MUSIC ··· NEO:6/NEO:X THX MUSIC",
|
||||
"8D": "NEURAL THX CINEMA",
|
||||
"8E": "NEURAL THX MUSIC",
|
||||
"8F": "NEURAL THX GAMES",
|
||||
"90": "PLIIz HEIGHT",
|
||||
"91": "NEO:6 CINEMA DTS SURROUND SENSATION",
|
||||
"92": "NEO:6 MUSIC DTS SURROUND SENSATION",
|
||||
"93": "NEURAL DIGITAL MUSIC",
|
||||
"94": "PLIIz HEIGHT + THX CINEMA",
|
||||
"95": "PLIIz HEIGHT + THX MUSIC",
|
||||
"96": "PLIIz HEIGHT + THX GAMES",
|
||||
"97": "PLIIz HEIGHT + THX U2/S2 CINEMA",
|
||||
"98": "PLIIz HEIGHT + THX U2/S2 MUSIC",
|
||||
"99": "PLIIz HEIGHT + THX U2/S2 GAMES",
|
||||
"9A": "NEO:X GAME",
|
||||
"A0": "PLIIx/PLII Movie + AUDYSSEY DSX",
|
||||
"A1": "PLIIx/PLII MUSIC + AUDYSSEY DSX",
|
||||
"A2": "PLIIx/PLII GAME + AUDYSSEY DSX",
|
||||
"A3": "NEO:6 CINEMA + AUDYSSEY DSX",
|
||||
"A4": "NEO:6 MUSIC + AUDYSSEY DSX",
|
||||
"A5": "NEURAL SURROUND + AUDYSSEY DSX",
|
||||
"A6": "NEURAL DIGITAL MUSIC + AUDYSSEY DSX",
|
||||
"A7": "DOLBY EX + AUDYSSEY DSX",
|
||||
"FF": "AUTO SURROUND",
|
||||
}
|
||||
|
||||
cls.__meaning_mapping[obj.value_meaning] = obj
|
||||
|
||||
return obj
|
||||
class ListeningMode(EnumWithMeaning):
|
||||
"""Receiver listening mode."""
|
||||
|
||||
@classmethod
|
||||
def from_meaning(cls, meaning: str) -> Self:
|
||||
"""Get InputSource enum from its meaning."""
|
||||
return cls.__meaning_mapping[meaning]
|
||||
_ignore_ = "ListeningMode _k _v _meaning"
|
||||
|
||||
ListeningMode = vars()
|
||||
for _k in _LISTENING_MODE_MEANINGS:
|
||||
ListeningMode["I" + _k] = _k
|
||||
|
||||
@staticmethod
|
||||
def _get_meanings() -> dict[str, str]:
|
||||
return _LISTENING_MODE_MEANINGS
|
||||
|
||||
|
||||
ZONES = {"main": "Main", "zone2": "Zone 2", "zone3": "Zone 3", "zone4": "Zone 4"}
|
||||
|
@ -3,6 +3,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from enum import Enum
|
||||
from functools import cache
|
||||
import logging
|
||||
from typing import Any, Literal
|
||||
@ -39,6 +40,7 @@ from .const import (
|
||||
PYEISCP_COMMANDS,
|
||||
ZONES,
|
||||
InputSource,
|
||||
ListeningMode,
|
||||
VolumeResolution,
|
||||
)
|
||||
from .receiver import Receiver, async_discover
|
||||
@ -63,6 +65,8 @@ CONF_SOURCES_DEFAULT = {
|
||||
"fm": "Radio",
|
||||
}
|
||||
|
||||
ISSUE_URL_PLACEHOLDER = "/config/integrations/dashboard/add?domain=onkyo"
|
||||
|
||||
PLATFORM_SCHEMA = MEDIA_PLAYER_PLATFORM_SCHEMA.extend(
|
||||
{
|
||||
vol.Optional(CONF_HOST): cv.string,
|
||||
@ -79,23 +83,23 @@ PLATFORM_SCHEMA = MEDIA_PLAYER_PLATFORM_SCHEMA.extend(
|
||||
}
|
||||
)
|
||||
|
||||
SUPPORT_ONKYO_WO_VOLUME = (
|
||||
|
||||
SUPPORTED_FEATURES_BASE = (
|
||||
MediaPlayerEntityFeature.TURN_ON
|
||||
| MediaPlayerEntityFeature.TURN_OFF
|
||||
| MediaPlayerEntityFeature.SELECT_SOURCE
|
||||
| MediaPlayerEntityFeature.PLAY_MEDIA
|
||||
)
|
||||
SUPPORT_ONKYO = (
|
||||
SUPPORT_ONKYO_WO_VOLUME
|
||||
| MediaPlayerEntityFeature.VOLUME_SET
|
||||
SUPPORTED_FEATURES_VOLUME = (
|
||||
MediaPlayerEntityFeature.VOLUME_SET
|
||||
| MediaPlayerEntityFeature.VOLUME_MUTE
|
||||
| MediaPlayerEntityFeature.VOLUME_STEP
|
||||
)
|
||||
|
||||
DEFAULT_PLAYABLE_SOURCES = (
|
||||
InputSource.from_meaning("FM"),
|
||||
InputSource.from_meaning("AM"),
|
||||
InputSource.from_meaning("DAB"),
|
||||
PLAYABLE_SOURCES = (
|
||||
InputSource.FM,
|
||||
InputSource.AM,
|
||||
InputSource.DAB,
|
||||
)
|
||||
|
||||
ATTR_PRESET = "preset"
|
||||
@ -118,7 +122,6 @@ AUDIO_INFORMATION_MAPPING = [
|
||||
"auto_phase_control_phase",
|
||||
"upmix_mode",
|
||||
]
|
||||
|
||||
VIDEO_INFORMATION_MAPPING = [
|
||||
"video_input_port",
|
||||
"input_resolution",
|
||||
@ -131,7 +134,6 @@ VIDEO_INFORMATION_MAPPING = [
|
||||
"picture_mode",
|
||||
"input_hdr",
|
||||
]
|
||||
ISSUE_URL_PLACEHOLDER = "/config/integrations/dashboard/add?domain=onkyo"
|
||||
|
||||
type LibValue = str | tuple[str, ...]
|
||||
|
||||
@ -139,7 +141,19 @@ type LibValue = str | tuple[str, ...]
|
||||
def _get_single_lib_value(value: LibValue) -> str:
|
||||
if isinstance(value, str):
|
||||
return value
|
||||
return value[0]
|
||||
return value[-1]
|
||||
|
||||
|
||||
def _get_lib_mapping[T: Enum](cmds: Any, cls: type[T]) -> dict[T, LibValue]:
|
||||
result: dict[T, LibValue] = {}
|
||||
for k, v in cmds["values"].items():
|
||||
try:
|
||||
key = cls(k)
|
||||
except ValueError:
|
||||
continue
|
||||
result[key] = v["name"]
|
||||
|
||||
return result
|
||||
|
||||
|
||||
@cache
|
||||
@ -154,15 +168,7 @@ def _input_source_lib_mappings(zone: str) -> dict[InputSource, LibValue]:
|
||||
case "zone4":
|
||||
cmds = PYEISCP_COMMANDS["zone4"]["SL4"]
|
||||
|
||||
result: dict[InputSource, LibValue] = {}
|
||||
for k, v in cmds["values"].items():
|
||||
try:
|
||||
source = InputSource(k)
|
||||
except ValueError:
|
||||
continue
|
||||
result[source] = v["name"]
|
||||
|
||||
return result
|
||||
return _get_lib_mapping(cmds, InputSource)
|
||||
|
||||
|
||||
@cache
|
||||
@ -170,6 +176,24 @@ def _rev_input_source_lib_mappings(zone: str) -> dict[LibValue, InputSource]:
|
||||
return {value: key for key, value in _input_source_lib_mappings(zone).items()}
|
||||
|
||||
|
||||
@cache
|
||||
def _listening_mode_lib_mappings(zone: str) -> dict[ListeningMode, LibValue]:
|
||||
match zone:
|
||||
case "main":
|
||||
cmds = PYEISCP_COMMANDS["main"]["LMD"]
|
||||
case "zone2":
|
||||
cmds = PYEISCP_COMMANDS["zone2"]["LMZ"]
|
||||
case _:
|
||||
return {}
|
||||
|
||||
return _get_lib_mapping(cmds, ListeningMode)
|
||||
|
||||
|
||||
@cache
|
||||
def _rev_listening_mode_lib_mappings(zone: str) -> dict[LibValue, ListeningMode]:
|
||||
return {value: key for key, value in _listening_mode_lib_mappings(zone).items()}
|
||||
|
||||
|
||||
async def async_setup_platform(
|
||||
hass: HomeAssistant,
|
||||
config: ConfigType,
|
||||
@ -303,6 +327,7 @@ async def async_setup_entry(
|
||||
volume_resolution: VolumeResolution = entry.options[OPTION_VOLUME_RESOLUTION]
|
||||
max_volume: float = entry.options[OPTION_MAX_VOLUME]
|
||||
sources = data.sources
|
||||
sound_modes = data.sound_modes
|
||||
|
||||
def connect_callback(receiver: Receiver) -> None:
|
||||
if not receiver.first_connect:
|
||||
@ -331,6 +356,7 @@ async def async_setup_entry(
|
||||
volume_resolution=volume_resolution,
|
||||
max_volume=max_volume,
|
||||
sources=sources,
|
||||
sound_modes=sound_modes,
|
||||
)
|
||||
entities[zone] = zone_entity
|
||||
async_add_entities([zone_entity])
|
||||
@ -345,6 +371,7 @@ class OnkyoMediaPlayer(MediaPlayerEntity):
|
||||
_attr_should_poll = False
|
||||
|
||||
_supports_volume: bool = False
|
||||
_supports_sound_mode: bool = False
|
||||
_supports_audio_info: bool = False
|
||||
_supports_video_info: bool = False
|
||||
_query_timer: asyncio.TimerHandle | None = None
|
||||
@ -357,6 +384,7 @@ class OnkyoMediaPlayer(MediaPlayerEntity):
|
||||
volume_resolution: VolumeResolution,
|
||||
max_volume: float,
|
||||
sources: dict[InputSource, str],
|
||||
sound_modes: dict[ListeningMode, str],
|
||||
) -> None:
|
||||
"""Initialize the Onkyo Receiver."""
|
||||
self._receiver = receiver
|
||||
@ -381,7 +409,27 @@ class OnkyoMediaPlayer(MediaPlayerEntity):
|
||||
value: key for key, value in self._source_mapping.items()
|
||||
}
|
||||
|
||||
self._sound_mode_lib_mapping = _listening_mode_lib_mappings(zone)
|
||||
self._rev_sound_mode_lib_mapping = _rev_listening_mode_lib_mappings(zone)
|
||||
self._sound_mode_mapping = {
|
||||
key: value
|
||||
for key, value in sound_modes.items()
|
||||
if key in self._sound_mode_lib_mapping
|
||||
}
|
||||
self._rev_sound_mode_mapping = {
|
||||
value: key for key, value in self._sound_mode_mapping.items()
|
||||
}
|
||||
|
||||
self._attr_source_list = list(self._rev_source_mapping)
|
||||
self._attr_sound_mode_list = list(self._rev_sound_mode_mapping)
|
||||
|
||||
self._attr_supported_features = SUPPORTED_FEATURES_BASE
|
||||
if zone == "main":
|
||||
self._attr_supported_features |= SUPPORTED_FEATURES_VOLUME
|
||||
self._supports_volume = True
|
||||
self._attr_supported_features |= MediaPlayerEntityFeature.SELECT_SOUND_MODE
|
||||
self._supports_sound_mode = True
|
||||
|
||||
self._attr_extra_state_attributes = {}
|
||||
|
||||
async def async_added_to_hass(self) -> None:
|
||||
@ -394,13 +442,6 @@ class OnkyoMediaPlayer(MediaPlayerEntity):
|
||||
self._query_timer.cancel()
|
||||
self._query_timer = None
|
||||
|
||||
@property
|
||||
def supported_features(self) -> MediaPlayerEntityFeature:
|
||||
"""Return media player features that are supported."""
|
||||
if self._supports_volume:
|
||||
return SUPPORT_ONKYO
|
||||
return SUPPORT_ONKYO_WO_VOLUME
|
||||
|
||||
@callback
|
||||
def _update_receiver(self, propname: str, value: Any) -> None:
|
||||
"""Update a property in the receiver."""
|
||||
@ -466,6 +507,24 @@ class OnkyoMediaPlayer(MediaPlayerEntity):
|
||||
"input-selector" if self._zone == "main" else "selector", source_lib_single
|
||||
)
|
||||
|
||||
async def async_select_sound_mode(self, sound_mode: str) -> None:
|
||||
"""Select listening sound mode."""
|
||||
if not self.sound_mode_list or sound_mode not in self.sound_mode_list:
|
||||
raise ServiceValidationError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="invalid_sound_mode",
|
||||
translation_placeholders={
|
||||
"invalid_sound_mode": sound_mode,
|
||||
"entity_id": self.entity_id,
|
||||
},
|
||||
)
|
||||
|
||||
sound_mode_lib = self._sound_mode_lib_mapping[
|
||||
self._rev_sound_mode_mapping[sound_mode]
|
||||
]
|
||||
sound_mode_lib_single = _get_single_lib_value(sound_mode_lib)
|
||||
self._update_receiver("listening-mode", sound_mode_lib_single)
|
||||
|
||||
async def async_select_output(self, hdmi_output: str) -> None:
|
||||
"""Set hdmi-out."""
|
||||
self._update_receiver("hdmi-output-selector", hdmi_output)
|
||||
@ -476,7 +535,7 @@ class OnkyoMediaPlayer(MediaPlayerEntity):
|
||||
"""Play radio station by preset number."""
|
||||
if self.source is not None:
|
||||
source = self._rev_source_mapping[self.source]
|
||||
if media_type.lower() == "radio" and source in DEFAULT_PLAYABLE_SOURCES:
|
||||
if media_type.lower() == "radio" and source in PLAYABLE_SOURCES:
|
||||
self._update_receiver("preset", media_id)
|
||||
|
||||
@callback
|
||||
@ -517,7 +576,9 @@ class OnkyoMediaPlayer(MediaPlayerEntity):
|
||||
self._attr_extra_state_attributes.pop(ATTR_PRESET, None)
|
||||
self._attr_extra_state_attributes.pop(ATTR_VIDEO_OUT, None)
|
||||
elif command in ["volume", "master-volume"] and value != "N/A":
|
||||
self._supports_volume = True
|
||||
if not self._supports_volume:
|
||||
self._attr_supported_features |= SUPPORTED_FEATURES_VOLUME
|
||||
self._supports_volume = True
|
||||
# AMP_VOL / (VOL_RESOLUTION * (MAX_VOL / 100))
|
||||
volume_level: float = value / (
|
||||
self._volume_resolution * self._max_volume / 100
|
||||
@ -535,6 +596,14 @@ class OnkyoMediaPlayer(MediaPlayerEntity):
|
||||
self._attr_extra_state_attributes[ATTR_PRESET] = value
|
||||
elif ATTR_PRESET in self._attr_extra_state_attributes:
|
||||
del self._attr_extra_state_attributes[ATTR_PRESET]
|
||||
elif command == "listening-mode" and value != "N/A":
|
||||
if not self._supports_sound_mode:
|
||||
self._attr_supported_features |= (
|
||||
MediaPlayerEntityFeature.SELECT_SOUND_MODE
|
||||
)
|
||||
self._supports_sound_mode = True
|
||||
self._parse_sound_mode(value)
|
||||
self._query_av_info_delayed()
|
||||
elif command == "audio-information":
|
||||
self._supports_audio_info = True
|
||||
self._parse_audio_information(value)
|
||||
@ -561,6 +630,21 @@ class OnkyoMediaPlayer(MediaPlayerEntity):
|
||||
)
|
||||
self._attr_source = source_meaning
|
||||
|
||||
@callback
|
||||
def _parse_sound_mode(self, mode_lib: LibValue) -> None:
|
||||
sound_mode = self._rev_sound_mode_lib_mapping[mode_lib]
|
||||
if sound_mode in self._sound_mode_mapping:
|
||||
self._attr_sound_mode = self._sound_mode_mapping[sound_mode]
|
||||
return
|
||||
|
||||
sound_mode_meaning = sound_mode.value_meaning
|
||||
_LOGGER.error(
|
||||
'Listening mode "%s" is invalid for entity: %s',
|
||||
sound_mode_meaning,
|
||||
self.entity_id,
|
||||
)
|
||||
self._attr_sound_mode = sound_mode_meaning
|
||||
|
||||
@callback
|
||||
def _parse_audio_information(
|
||||
self, audio_information: tuple[str] | Literal["N/A"]
|
||||
|
@ -27,17 +27,20 @@
|
||||
"description": "Configure {name}",
|
||||
"data": {
|
||||
"volume_resolution": "Volume resolution",
|
||||
"input_sources": "[%key:component::onkyo::options::step::init::data::input_sources%]"
|
||||
"input_sources": "[%key:component::onkyo::options::step::init::data::input_sources%]",
|
||||
"listening_modes": "[%key:component::onkyo::options::step::init::data::listening_modes%]"
|
||||
},
|
||||
"data_description": {
|
||||
"volume_resolution": "Number of steps it takes for the receiver to go from the lowest to the highest possible volume.",
|
||||
"input_sources": "[%key:component::onkyo::options::step::init::data_description::input_sources%]"
|
||||
"input_sources": "[%key:component::onkyo::options::step::init::data_description::input_sources%]",
|
||||
"listening_modes": "[%key:component::onkyo::options::step::init::data_description::listening_modes%]"
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
|
||||
"empty_input_source_list": "[%key:component::onkyo::options::error::empty_input_source_list%]",
|
||||
"empty_listening_mode_list": "[%key:component::onkyo::options::error::empty_listening_mode_list%]",
|
||||
"unknown": "[%key:common::config_flow::error::unknown%]"
|
||||
},
|
||||
"abort": {
|
||||
@ -53,11 +56,13 @@
|
||||
"init": {
|
||||
"data": {
|
||||
"max_volume": "Maximum volume limit (%)",
|
||||
"input_sources": "Input sources"
|
||||
"input_sources": "Input sources",
|
||||
"listening_modes": "Listening modes"
|
||||
},
|
||||
"data_description": {
|
||||
"max_volume": "Maximum volume limit as a percentage. This will associate Home Assistant's maximum volume to this value on the receiver, i.e., if you set this to 50%, then setting the volume to 100% in Home Assistant will cause the volume on the receiver to be set to 50% of its maximum value.",
|
||||
"input_sources": "List of input sources supported by the receiver."
|
||||
"input_sources": "List of input sources supported by the receiver.",
|
||||
"listening_modes": "List of listening modes supported by the receiver."
|
||||
}
|
||||
},
|
||||
"names": {
|
||||
@ -65,12 +70,17 @@
|
||||
"input_sources": {
|
||||
"name": "Input source names",
|
||||
"description": "Mappings of receiver's input sources to their names."
|
||||
},
|
||||
"listening_modes": {
|
||||
"name": "Listening mode names",
|
||||
"description": "Mappings of receiver's listening modes to their names."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"empty_input_source_list": "Input source list cannot be empty"
|
||||
"empty_input_source_list": "Input source list cannot be empty",
|
||||
"empty_listening_mode_list": "Listening mode list cannot be empty"
|
||||
}
|
||||
},
|
||||
"issues": {
|
||||
@ -84,6 +94,9 @@
|
||||
}
|
||||
},
|
||||
"exceptions": {
|
||||
"invalid_sound_mode": {
|
||||
"message": "Cannot select sound mode \"{invalid_sound_mode}\" for entity: {entity_id}."
|
||||
},
|
||||
"invalid_source": {
|
||||
"message": "Cannot select input source \"{invalid_source}\" for entity: {entity_id}."
|
||||
}
|
||||
|
@ -34,8 +34,9 @@ def create_config_entry_from_info(info: ReceiverInfo) -> MockConfigEntry:
|
||||
data = {CONF_HOST: info.host}
|
||||
options = {
|
||||
"volume_resolution": 80,
|
||||
"input_sources": {"12": "tv"},
|
||||
"max_volume": 100,
|
||||
"input_sources": {"12": "tv"},
|
||||
"listening_modes": {"00": "stereo"},
|
||||
}
|
||||
|
||||
return MockConfigEntry(
|
||||
@ -52,8 +53,9 @@ def create_empty_config_entry() -> MockConfigEntry:
|
||||
data = {CONF_HOST: ""}
|
||||
options = {
|
||||
"volume_resolution": 80,
|
||||
"input_sources": {"12": "tv"},
|
||||
"max_volume": 100,
|
||||
"input_sources": {"12": "tv"},
|
||||
"listening_modes": {"00": "stereo"},
|
||||
}
|
||||
|
||||
return MockConfigEntry(
|
||||
|
@ -11,7 +11,9 @@ from homeassistant.components.onkyo.config_flow import OnkyoConfigFlow
|
||||
from homeassistant.components.onkyo.const import (
|
||||
DOMAIN,
|
||||
OPTION_INPUT_SOURCES,
|
||||
OPTION_LISTENING_MODES,
|
||||
OPTION_MAX_VOLUME,
|
||||
OPTION_MAX_VOLUME_DEFAULT,
|
||||
OPTION_VOLUME_RESOLUTION,
|
||||
)
|
||||
from homeassistant.config_entries import SOURCE_USER
|
||||
@ -226,7 +228,11 @@ async def test_ssdp_discovery_success(
|
||||
|
||||
select_result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
user_input={"volume_resolution": 200, "input_sources": ["TV"]},
|
||||
user_input={
|
||||
"volume_resolution": 200,
|
||||
"input_sources": ["TV"],
|
||||
"listening_modes": ["THX"],
|
||||
},
|
||||
)
|
||||
|
||||
assert select_result["type"] is FlowResultType.CREATE_ENTRY
|
||||
@ -349,34 +355,6 @@ async def test_ssdp_discovery_no_host(
|
||||
assert result["reason"] == "unknown"
|
||||
|
||||
|
||||
async def test_configure_empty_source_list(
|
||||
hass: HomeAssistant, default_mock_discovery
|
||||
) -> None:
|
||||
"""Test receiver configuration with no sources set."""
|
||||
|
||||
init_result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": SOURCE_USER},
|
||||
)
|
||||
|
||||
form_result = await hass.config_entries.flow.async_configure(
|
||||
init_result["flow_id"],
|
||||
{"next_step_id": "manual"},
|
||||
)
|
||||
|
||||
select_result = await hass.config_entries.flow.async_configure(
|
||||
form_result["flow_id"],
|
||||
user_input={CONF_HOST: "sample-host-name"},
|
||||
)
|
||||
|
||||
configure_result = await hass.config_entries.flow.async_configure(
|
||||
select_result["flow_id"],
|
||||
user_input={"volume_resolution": 200, "input_sources": []},
|
||||
)
|
||||
|
||||
assert configure_result["errors"] == {"input_sources": "empty_input_source_list"}
|
||||
|
||||
|
||||
async def test_configure_no_resolution(
|
||||
hass: HomeAssistant, default_mock_discovery
|
||||
) -> None:
|
||||
@ -404,33 +382,61 @@ async def test_configure_no_resolution(
|
||||
)
|
||||
|
||||
|
||||
async def test_configure_resolution_set(
|
||||
hass: HomeAssistant, default_mock_discovery
|
||||
) -> None:
|
||||
"""Test receiver configure with specified resolution."""
|
||||
async def test_configure(hass: HomeAssistant, default_mock_discovery) -> None:
|
||||
"""Test receiver configure."""
|
||||
|
||||
init_result = await hass.config_entries.flow.async_init(
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": SOURCE_USER},
|
||||
)
|
||||
|
||||
form_result = await hass.config_entries.flow.async_configure(
|
||||
init_result["flow_id"],
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{"next_step_id": "manual"},
|
||||
)
|
||||
|
||||
select_result = await hass.config_entries.flow.async_configure(
|
||||
form_result["flow_id"],
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
user_input={CONF_HOST: "sample-host-name"},
|
||||
)
|
||||
|
||||
configure_result = await hass.config_entries.flow.async_configure(
|
||||
select_result["flow_id"],
|
||||
user_input={"volume_resolution": 200, "input_sources": ["TV"]},
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
user_input={
|
||||
OPTION_VOLUME_RESOLUTION: 200,
|
||||
OPTION_INPUT_SOURCES: [],
|
||||
OPTION_LISTENING_MODES: ["THX"],
|
||||
},
|
||||
)
|
||||
assert result["step_id"] == "configure_receiver"
|
||||
assert result["errors"] == {OPTION_INPUT_SOURCES: "empty_input_source_list"}
|
||||
|
||||
assert configure_result["type"] is FlowResultType.CREATE_ENTRY
|
||||
assert configure_result["options"]["volume_resolution"] == 200
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
user_input={
|
||||
OPTION_VOLUME_RESOLUTION: 200,
|
||||
OPTION_INPUT_SOURCES: ["TV"],
|
||||
OPTION_LISTENING_MODES: [],
|
||||
},
|
||||
)
|
||||
assert result["step_id"] == "configure_receiver"
|
||||
assert result["errors"] == {OPTION_LISTENING_MODES: "empty_listening_mode_list"}
|
||||
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
user_input={
|
||||
OPTION_VOLUME_RESOLUTION: 200,
|
||||
OPTION_INPUT_SOURCES: ["TV"],
|
||||
OPTION_LISTENING_MODES: ["THX"],
|
||||
},
|
||||
)
|
||||
assert result["type"] is FlowResultType.CREATE_ENTRY
|
||||
assert result["options"] == {
|
||||
OPTION_VOLUME_RESOLUTION: 200,
|
||||
OPTION_MAX_VOLUME: OPTION_MAX_VOLUME_DEFAULT,
|
||||
OPTION_INPUT_SOURCES: {"12": "TV"},
|
||||
OPTION_LISTENING_MODES: {"04": "THX"},
|
||||
}
|
||||
|
||||
|
||||
async def test_configure_invalid_resolution_set(
|
||||
@ -601,21 +607,26 @@ async def test_import_success(
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert import_result["type"] is FlowResultType.CREATE_ENTRY
|
||||
assert import_result["data"]["host"] == "host 1"
|
||||
assert import_result["options"]["volume_resolution"] == 80
|
||||
assert import_result["options"]["max_volume"] == 100
|
||||
assert import_result["options"]["input_sources"] == {
|
||||
"00": "Auxiliary",
|
||||
"01": "Video",
|
||||
assert import_result["data"] == {"host": "host 1"}
|
||||
assert import_result["options"] == {
|
||||
"volume_resolution": 80,
|
||||
"max_volume": 100,
|
||||
"input_sources": {
|
||||
"00": "Auxiliary",
|
||||
"01": "Video",
|
||||
},
|
||||
"listening_modes": {},
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"ignore_translations",
|
||||
[
|
||||
[ # The schema is dynamically created from input sources
|
||||
[ # The schema is dynamically created from input sources and listening modes
|
||||
"component.onkyo.options.step.names.sections.input_sources.data.TV",
|
||||
"component.onkyo.options.step.names.sections.input_sources.data_description.TV",
|
||||
"component.onkyo.options.step.names.sections.listening_modes.data.STEREO",
|
||||
"component.onkyo.options.step.names.sections.listening_modes.data_description.STEREO",
|
||||
]
|
||||
],
|
||||
)
|
||||
@ -635,6 +646,7 @@ async def test_options_flow(hass: HomeAssistant, config_entry: MockConfigEntry)
|
||||
user_input={
|
||||
OPTION_MAX_VOLUME: 42,
|
||||
OPTION_INPUT_SOURCES: [],
|
||||
OPTION_LISTENING_MODES: ["STEREO"],
|
||||
},
|
||||
)
|
||||
|
||||
@ -647,6 +659,20 @@ async def test_options_flow(hass: HomeAssistant, config_entry: MockConfigEntry)
|
||||
user_input={
|
||||
OPTION_MAX_VOLUME: 42,
|
||||
OPTION_INPUT_SOURCES: ["TV"],
|
||||
OPTION_LISTENING_MODES: [],
|
||||
},
|
||||
)
|
||||
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["step_id"] == "init"
|
||||
assert result["errors"] == {OPTION_LISTENING_MODES: "empty_listening_mode_list"}
|
||||
|
||||
result = await hass.config_entries.options.async_configure(
|
||||
result["flow_id"],
|
||||
user_input={
|
||||
OPTION_MAX_VOLUME: 42,
|
||||
OPTION_INPUT_SOURCES: ["TV"],
|
||||
OPTION_LISTENING_MODES: ["STEREO"],
|
||||
},
|
||||
)
|
||||
|
||||
@ -657,6 +683,7 @@ async def test_options_flow(hass: HomeAssistant, config_entry: MockConfigEntry)
|
||||
result["flow_id"],
|
||||
user_input={
|
||||
OPTION_INPUT_SOURCES: {"TV": "television"},
|
||||
OPTION_LISTENING_MODES: {"STEREO": "Duophonia"},
|
||||
},
|
||||
)
|
||||
|
||||
@ -665,4 +692,5 @@ async def test_options_flow(hass: HomeAssistant, config_entry: MockConfigEntry)
|
||||
OPTION_VOLUME_RESOLUTION: old_volume_resolution,
|
||||
OPTION_MAX_VOLUME: 42.0,
|
||||
OPTION_INPUT_SOURCES: {"12": "television"},
|
||||
OPTION_LISTENING_MODES: {"00": "Duophonia"},
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user