DenonAVR Config Flow (#35255)
Co-authored-by: J. Nick Koston <nick@koston.org> Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
This commit is contained in:
parent
25607c7129
commit
6db5ff98ed
15 changed files with 1272 additions and 138 deletions
|
@ -160,6 +160,7 @@ omit =
|
||||||
homeassistant/components/deluge/switch.py
|
homeassistant/components/deluge/switch.py
|
||||||
homeassistant/components/denon/media_player.py
|
homeassistant/components/denon/media_player.py
|
||||||
homeassistant/components/denonavr/media_player.py
|
homeassistant/components/denonavr/media_player.py
|
||||||
|
homeassistant/components/denonavr/receiver.py
|
||||||
homeassistant/components/deutsche_bahn/sensor.py
|
homeassistant/components/deutsche_bahn/sensor.py
|
||||||
homeassistant/components/devolo_home_control/__init__.py
|
homeassistant/components/devolo_home_control/__init__.py
|
||||||
homeassistant/components/devolo_home_control/binary_sensor.py
|
homeassistant/components/devolo_home_control/binary_sensor.py
|
||||||
|
|
|
@ -1,15 +1,33 @@
|
||||||
"""The denonavr component."""
|
"""The denonavr component."""
|
||||||
|
import logging
|
||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.const import ATTR_ENTITY_ID
|
from homeassistant import config_entries, core
|
||||||
import homeassistant.helpers.config_validation as cv
|
from homeassistant.const import ATTR_ENTITY_ID, CONF_HOST
|
||||||
|
from homeassistant.exceptions import ConfigEntryNotReady
|
||||||
|
from homeassistant.helpers import config_validation as cv, entity_registry as er
|
||||||
from homeassistant.helpers.dispatcher import dispatcher_send
|
from homeassistant.helpers.dispatcher import dispatcher_send
|
||||||
|
|
||||||
DOMAIN = "denonavr"
|
from .config_flow import (
|
||||||
|
CONF_SHOW_ALL_SOURCES,
|
||||||
|
CONF_ZONE2,
|
||||||
|
CONF_ZONE3,
|
||||||
|
DEFAULT_SHOW_SOURCES,
|
||||||
|
DEFAULT_TIMEOUT,
|
||||||
|
DEFAULT_ZONE2,
|
||||||
|
DEFAULT_ZONE3,
|
||||||
|
DOMAIN,
|
||||||
|
)
|
||||||
|
from .receiver import ConnectDenonAVR
|
||||||
|
|
||||||
|
CONF_RECEIVER = "receiver"
|
||||||
|
UNDO_UPDATE_LISTENER = "undo_update_listener"
|
||||||
SERVICE_GET_COMMAND = "get_command"
|
SERVICE_GET_COMMAND = "get_command"
|
||||||
ATTR_COMMAND = "command"
|
ATTR_COMMAND = "command"
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
CALL_SCHEMA = vol.Schema({vol.Required(ATTR_ENTITY_ID): cv.comp_entity_ids})
|
CALL_SCHEMA = vol.Schema({vol.Required(ATTR_ENTITY_ID): cv.comp_entity_ids})
|
||||||
|
|
||||||
GET_COMMAND_SCHEMA = CALL_SCHEMA.extend({vol.Required(ATTR_COMMAND): cv.string})
|
GET_COMMAND_SCHEMA = CALL_SCHEMA.extend({vol.Required(ATTR_COMMAND): cv.string})
|
||||||
|
@ -19,7 +37,7 @@ SERVICE_TO_METHOD = {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def setup(hass, config):
|
def setup(hass: core.HomeAssistant, config: dict):
|
||||||
"""Set up the denonavr platform."""
|
"""Set up the denonavr platform."""
|
||||||
|
|
||||||
def service_handler(service):
|
def service_handler(service):
|
||||||
|
@ -33,3 +51,72 @@ def setup(hass, config):
|
||||||
hass.services.register(DOMAIN, service, service_handler, schema=schema)
|
hass.services.register(DOMAIN, service, service_handler, schema=schema)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(
|
||||||
|
hass: core.HomeAssistant, entry: config_entries.ConfigEntry
|
||||||
|
):
|
||||||
|
"""Set up the denonavr components from a config entry."""
|
||||||
|
hass.data.setdefault(DOMAIN, {})
|
||||||
|
|
||||||
|
# Connect to receiver
|
||||||
|
connect_denonavr = ConnectDenonAVR(
|
||||||
|
hass,
|
||||||
|
entry.data[CONF_HOST],
|
||||||
|
DEFAULT_TIMEOUT,
|
||||||
|
entry.options.get(CONF_SHOW_ALL_SOURCES, DEFAULT_SHOW_SOURCES),
|
||||||
|
entry.options.get(CONF_ZONE2, DEFAULT_ZONE2),
|
||||||
|
entry.options.get(CONF_ZONE3, DEFAULT_ZONE3),
|
||||||
|
)
|
||||||
|
if not await connect_denonavr.async_connect_receiver():
|
||||||
|
raise ConfigEntryNotReady
|
||||||
|
receiver = connect_denonavr.receiver
|
||||||
|
|
||||||
|
undo_listener = entry.add_update_listener(update_listener)
|
||||||
|
|
||||||
|
hass.data[DOMAIN][entry.entry_id] = {
|
||||||
|
CONF_RECEIVER: receiver,
|
||||||
|
UNDO_UPDATE_LISTENER: undo_listener,
|
||||||
|
}
|
||||||
|
|
||||||
|
hass.async_create_task(
|
||||||
|
hass.config_entries.async_forward_entry_setup(entry, "media_player")
|
||||||
|
)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
async def async_unload_entry(
|
||||||
|
hass: core.HomeAssistant, config_entry: config_entries.ConfigEntry
|
||||||
|
):
|
||||||
|
"""Unload a config entry."""
|
||||||
|
unload_ok = await hass.config_entries.async_forward_entry_unload(
|
||||||
|
config_entry, "media_player"
|
||||||
|
)
|
||||||
|
|
||||||
|
hass.data[DOMAIN][config_entry.entry_id][UNDO_UPDATE_LISTENER]()
|
||||||
|
|
||||||
|
# Remove zone2 and zone3 entities if needed
|
||||||
|
entity_registry = await er.async_get_registry(hass)
|
||||||
|
entries = er.async_entries_for_config_entry(entity_registry, config_entry.entry_id)
|
||||||
|
zone2_id = f"{config_entry.unique_id}-Zone2"
|
||||||
|
zone3_id = f"{config_entry.unique_id}-Zone3"
|
||||||
|
for entry in entries:
|
||||||
|
if entry.unique_id == zone2_id and not config_entry.options.get(CONF_ZONE2):
|
||||||
|
entity_registry.async_remove(entry.entity_id)
|
||||||
|
_LOGGER.debug("Removing zone2 from DenonAvr")
|
||||||
|
if entry.unique_id == zone3_id and not config_entry.options.get(CONF_ZONE3):
|
||||||
|
entity_registry.async_remove(entry.entity_id)
|
||||||
|
_LOGGER.debug("Removing zone3 from DenonAvr")
|
||||||
|
|
||||||
|
if unload_ok:
|
||||||
|
hass.data[DOMAIN].pop(config_entry.entry_id)
|
||||||
|
|
||||||
|
return unload_ok
|
||||||
|
|
||||||
|
|
||||||
|
async def update_listener(
|
||||||
|
hass: core.HomeAssistant, config_entry: config_entries.ConfigEntry
|
||||||
|
):
|
||||||
|
"""Handle options update."""
|
||||||
|
await hass.config_entries.async_reload(config_entry.entry_id)
|
||||||
|
|
256
homeassistant/components/denonavr/config_flow.py
Normal file
256
homeassistant/components/denonavr/config_flow.py
Normal file
|
@ -0,0 +1,256 @@
|
||||||
|
"""Config flow to configure Denon AVR receivers using their HTTP interface."""
|
||||||
|
from functools import partial
|
||||||
|
import logging
|
||||||
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
|
import denonavr
|
||||||
|
from getmac import get_mac_address
|
||||||
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from homeassistant import config_entries
|
||||||
|
from homeassistant.components import ssdp
|
||||||
|
from homeassistant.const import CONF_HOST, CONF_MAC
|
||||||
|
from homeassistant.core import callback
|
||||||
|
from homeassistant.helpers.device_registry import format_mac
|
||||||
|
|
||||||
|
from .receiver import ConnectDenonAVR
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
DOMAIN = "denonavr"
|
||||||
|
|
||||||
|
SUPPORTED_MANUFACTURERS = ["Denon", "DENON", "Marantz"]
|
||||||
|
|
||||||
|
CONF_SHOW_ALL_SOURCES = "show_all_sources"
|
||||||
|
CONF_ZONE2 = "zone2"
|
||||||
|
CONF_ZONE3 = "zone3"
|
||||||
|
CONF_TYPE = "type"
|
||||||
|
CONF_MODEL = "model"
|
||||||
|
CONF_MANUFACTURER = "manufacturer"
|
||||||
|
CONF_SERIAL_NUMBER = "serial_number"
|
||||||
|
|
||||||
|
DEFAULT_SHOW_SOURCES = False
|
||||||
|
DEFAULT_TIMEOUT = 5
|
||||||
|
DEFAULT_ZONE2 = False
|
||||||
|
DEFAULT_ZONE3 = False
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = vol.Schema({vol.Optional(CONF_HOST): str})
|
||||||
|
|
||||||
|
|
||||||
|
class OptionsFlowHandler(config_entries.OptionsFlow):
|
||||||
|
"""Options for the component."""
|
||||||
|
|
||||||
|
def __init__(self, config_entry: config_entries.ConfigEntry):
|
||||||
|
"""Init object."""
|
||||||
|
self.config_entry = config_entry
|
||||||
|
|
||||||
|
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=user_input)
|
||||||
|
|
||||||
|
settings_schema = vol.Schema(
|
||||||
|
{
|
||||||
|
vol.Optional(
|
||||||
|
CONF_SHOW_ALL_SOURCES,
|
||||||
|
default=self.config_entry.options.get(
|
||||||
|
CONF_SHOW_ALL_SOURCES, DEFAULT_SHOW_SOURCES
|
||||||
|
),
|
||||||
|
): bool,
|
||||||
|
vol.Optional(
|
||||||
|
CONF_ZONE2,
|
||||||
|
default=self.config_entry.options.get(CONF_ZONE2, DEFAULT_ZONE2),
|
||||||
|
): bool,
|
||||||
|
vol.Optional(
|
||||||
|
CONF_ZONE3,
|
||||||
|
default=self.config_entry.options.get(CONF_ZONE3, DEFAULT_ZONE3),
|
||||||
|
): bool,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return self.async_show_form(step_id="init", data_schema=settings_schema)
|
||||||
|
|
||||||
|
|
||||||
|
class DenonAvrFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
||||||
|
"""Handle a Denon AVR config flow."""
|
||||||
|
|
||||||
|
VERSION = 1
|
||||||
|
CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_POLL
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
"""Initialize the Denon AVR flow."""
|
||||||
|
self.host = None
|
||||||
|
self.serial_number = None
|
||||||
|
self.model_name = None
|
||||||
|
self.timeout = DEFAULT_TIMEOUT
|
||||||
|
self.show_all_sources = DEFAULT_SHOW_SOURCES
|
||||||
|
self.zone2 = DEFAULT_ZONE2
|
||||||
|
self.zone3 = DEFAULT_ZONE3
|
||||||
|
self.d_receivers = []
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
@callback
|
||||||
|
def async_get_options_flow(config_entry) -> OptionsFlowHandler:
|
||||||
|
"""Get the options flow."""
|
||||||
|
return OptionsFlowHandler(config_entry)
|
||||||
|
|
||||||
|
async def async_step_user(self, user_input=None):
|
||||||
|
"""Handle a flow initialized by the user."""
|
||||||
|
errors = {}
|
||||||
|
if user_input is not None:
|
||||||
|
# check if IP address is set manually
|
||||||
|
host = user_input.get(CONF_HOST)
|
||||||
|
if host:
|
||||||
|
self.host = host
|
||||||
|
return await self.async_step_connect()
|
||||||
|
|
||||||
|
# discovery using denonavr library
|
||||||
|
self.d_receivers = await self.hass.async_add_executor_job(denonavr.discover)
|
||||||
|
# More than one receiver could be discovered by that method
|
||||||
|
if len(self.d_receivers) == 1:
|
||||||
|
self.host = self.d_receivers[0]["host"]
|
||||||
|
return await self.async_step_connect()
|
||||||
|
if len(self.d_receivers) > 1:
|
||||||
|
# show selection form
|
||||||
|
return await self.async_step_select()
|
||||||
|
|
||||||
|
errors["base"] = "discovery_error"
|
||||||
|
|
||||||
|
return self.async_show_form(
|
||||||
|
step_id="user", data_schema=CONFIG_SCHEMA, errors=errors
|
||||||
|
)
|
||||||
|
|
||||||
|
async def async_step_select(self, user_input=None):
|
||||||
|
"""Handle multiple receivers found."""
|
||||||
|
errors = {}
|
||||||
|
if user_input is not None:
|
||||||
|
self.host = user_input["select_host"]
|
||||||
|
return await self.async_step_connect()
|
||||||
|
|
||||||
|
select_scheme = vol.Schema(
|
||||||
|
{
|
||||||
|
vol.Required("select_host"): vol.In(
|
||||||
|
[d_receiver["host"] for d_receiver in self.d_receivers]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return self.async_show_form(
|
||||||
|
step_id="select", data_schema=select_scheme, errors=errors
|
||||||
|
)
|
||||||
|
|
||||||
|
async def async_step_confirm(self, user_input=None):
|
||||||
|
"""Allow the user to confirm adding the device."""
|
||||||
|
if user_input is not None:
|
||||||
|
return await self.async_step_connect()
|
||||||
|
|
||||||
|
return self.async_show_form(step_id="confirm")
|
||||||
|
|
||||||
|
async def async_step_connect(self, user_input=None):
|
||||||
|
"""Connect to the receiver."""
|
||||||
|
connect_denonavr = ConnectDenonAVR(
|
||||||
|
self.hass,
|
||||||
|
self.host,
|
||||||
|
self.timeout,
|
||||||
|
self.show_all_sources,
|
||||||
|
self.zone2,
|
||||||
|
self.zone3,
|
||||||
|
)
|
||||||
|
if not await connect_denonavr.async_connect_receiver():
|
||||||
|
return self.async_abort(reason="connection_error")
|
||||||
|
receiver = connect_denonavr.receiver
|
||||||
|
|
||||||
|
mac_address = await self.async_get_mac(self.host)
|
||||||
|
|
||||||
|
if not self.serial_number:
|
||||||
|
self.serial_number = receiver.serial_number
|
||||||
|
if not self.model_name:
|
||||||
|
self.model_name = (receiver.model_name).replace("*", "")
|
||||||
|
|
||||||
|
if self.serial_number is not None:
|
||||||
|
unique_id = self.construct_unique_id(self.model_name, self.serial_number)
|
||||||
|
await self.async_set_unique_id(unique_id)
|
||||||
|
self._abort_if_unique_id_configured()
|
||||||
|
else:
|
||||||
|
_LOGGER.error(
|
||||||
|
"Could not get serial number of host %s, "
|
||||||
|
"unique_id's will not be available",
|
||||||
|
self.host,
|
||||||
|
)
|
||||||
|
for entry in self._async_current_entries():
|
||||||
|
if entry.data[CONF_HOST] == self.host:
|
||||||
|
return self.async_abort(reason="already_configured")
|
||||||
|
|
||||||
|
return self.async_create_entry(
|
||||||
|
title=receiver.name,
|
||||||
|
data={
|
||||||
|
CONF_HOST: self.host,
|
||||||
|
CONF_MAC: mac_address,
|
||||||
|
CONF_TYPE: receiver.receiver_type,
|
||||||
|
CONF_MODEL: self.model_name,
|
||||||
|
CONF_MANUFACTURER: receiver.manufacturer,
|
||||||
|
CONF_SERIAL_NUMBER: self.serial_number,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
async def async_step_ssdp(self, discovery_info):
|
||||||
|
"""Handle a discovered Denon AVR.
|
||||||
|
|
||||||
|
This flow is triggered by the SSDP component. It will check if the
|
||||||
|
host is already configured and delegate to the import step if not.
|
||||||
|
"""
|
||||||
|
# Filter out non-Denon AVRs#1
|
||||||
|
if (
|
||||||
|
discovery_info.get(ssdp.ATTR_UPNP_MANUFACTURER)
|
||||||
|
not in SUPPORTED_MANUFACTURERS
|
||||||
|
):
|
||||||
|
return self.async_abort(reason="not_denonavr_manufacturer")
|
||||||
|
|
||||||
|
# Check if required information is present to set the unique_id
|
||||||
|
if (
|
||||||
|
ssdp.ATTR_UPNP_MODEL_NAME not in discovery_info
|
||||||
|
or ssdp.ATTR_UPNP_SERIAL not in discovery_info
|
||||||
|
):
|
||||||
|
return self.async_abort(reason="not_denonavr_missing")
|
||||||
|
|
||||||
|
self.model_name = discovery_info[ssdp.ATTR_UPNP_MODEL_NAME].replace("*", "")
|
||||||
|
self.serial_number = discovery_info[ssdp.ATTR_UPNP_SERIAL]
|
||||||
|
self.host = urlparse(discovery_info[ssdp.ATTR_SSDP_LOCATION]).hostname
|
||||||
|
|
||||||
|
unique_id = self.construct_unique_id(self.model_name, self.serial_number)
|
||||||
|
await self.async_set_unique_id(unique_id)
|
||||||
|
self._abort_if_unique_id_configured({CONF_HOST: self.host})
|
||||||
|
|
||||||
|
# pylint: disable=no-member # https://github.com/PyCQA/pylint/issues/3167
|
||||||
|
self.context.update(
|
||||||
|
{
|
||||||
|
"title_placeholders": {
|
||||||
|
"name": discovery_info.get(ssdp.ATTR_UPNP_FRIENDLY_NAME, self.host)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return await self.async_step_confirm()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def construct_unique_id(model_name, serial_number):
|
||||||
|
"""Construct the unique id from the ssdp discovery or user_step."""
|
||||||
|
return f"{model_name}-{serial_number}"
|
||||||
|
|
||||||
|
async def async_get_mac(self, host):
|
||||||
|
"""Get the mac address of the DenonAVR receiver."""
|
||||||
|
try:
|
||||||
|
mac_address = await self.hass.async_add_executor_job(
|
||||||
|
partial(get_mac_address, **{"ip": host})
|
||||||
|
)
|
||||||
|
if not mac_address:
|
||||||
|
mac_address = await self.hass.async_add_executor_job(
|
||||||
|
partial(get_mac_address, **{"hostname": host})
|
||||||
|
)
|
||||||
|
except Exception as err: # pylint: disable=broad-except
|
||||||
|
_LOGGER.error("Unable to get mac address: %s", err)
|
||||||
|
mac_address = None
|
||||||
|
|
||||||
|
if mac_address is not None:
|
||||||
|
mac_address = format_mac(mac_address)
|
||||||
|
return mac_address
|
|
@ -1,7 +1,46 @@
|
||||||
{
|
{
|
||||||
"domain": "denonavr",
|
"domain": "denonavr",
|
||||||
"name": "Denon AVR Network Receivers",
|
"name": "Denon AVR Network Receivers",
|
||||||
|
"config_flow": true,
|
||||||
"documentation": "https://www.home-assistant.io/integrations/denonavr",
|
"documentation": "https://www.home-assistant.io/integrations/denonavr",
|
||||||
"requirements": ["denonavr==0.8.1"],
|
"requirements": ["denonavr==0.9.3", "getmac==0.8.2"],
|
||||||
"codeowners": ["@scarface-4711", "@starkillerOG"]
|
"codeowners": ["@scarface-4711", "@starkillerOG"],
|
||||||
|
"ssdp": [
|
||||||
|
{
|
||||||
|
"manufacturer": "Denon",
|
||||||
|
"deviceType": "urn:schemas-upnp-org:device:MediaRenderer:1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"manufacturer": "DENON",
|
||||||
|
"deviceType": "urn:schemas-upnp-org:device:MediaRenderer:1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"manufacturer": "Marantz",
|
||||||
|
"deviceType": "urn:schemas-upnp-org:device:MediaRenderer:1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"manufacturer": "Denon",
|
||||||
|
"deviceType": "urn:schemas-upnp-org:device:MediaServer:1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"manufacturer": "DENON",
|
||||||
|
"deviceType": "urn:schemas-upnp-org:device:MediaServer:1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"manufacturer": "Marantz",
|
||||||
|
"deviceType": "urn:schemas-upnp-org:device:MediaServer:1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"manufacturer": "Denon",
|
||||||
|
"deviceType": "urn:schemas-denon-com:device:AiosDevice:1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"manufacturer": "DENON",
|
||||||
|
"deviceType": "urn:schemas-denon-com:device:AiosDevice:1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"manufacturer": "Marantz",
|
||||||
|
"deviceType": "urn:schemas-denon-com:device:AiosDevice:1"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,8 @@
|
||||||
"""Support for Denon AVR receivers using their HTTP interface."""
|
"""Support for Denon AVR receivers using their HTTP interface."""
|
||||||
|
|
||||||
from collections import namedtuple
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
import denonavr
|
from homeassistant.components.media_player import MediaPlayerEntity
|
||||||
import voluptuous as vol
|
|
||||||
|
|
||||||
from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerEntity
|
|
||||||
from homeassistant.components.media_player.const import (
|
from homeassistant.components.media_player.const import (
|
||||||
MEDIA_TYPE_CHANNEL,
|
MEDIA_TYPE_CHANNEL,
|
||||||
MEDIA_TYPE_MUSIC,
|
MEDIA_TYPE_MUSIC,
|
||||||
|
@ -25,10 +21,7 @@ from homeassistant.components.media_player.const import (
|
||||||
)
|
)
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_ENTITY_ID,
|
ATTR_ENTITY_ID,
|
||||||
CONF_HOST,
|
CONF_MAC,
|
||||||
CONF_NAME,
|
|
||||||
CONF_TIMEOUT,
|
|
||||||
CONF_ZONE,
|
|
||||||
ENTITY_MATCH_ALL,
|
ENTITY_MATCH_ALL,
|
||||||
ENTITY_MATCH_NONE,
|
ENTITY_MATCH_NONE,
|
||||||
STATE_OFF,
|
STATE_OFF,
|
||||||
|
@ -36,25 +29,22 @@ from homeassistant.const import (
|
||||||
STATE_PAUSED,
|
STATE_PAUSED,
|
||||||
STATE_PLAYING,
|
STATE_PLAYING,
|
||||||
)
|
)
|
||||||
import homeassistant.helpers.config_validation as cv
|
from homeassistant.helpers import device_registry as dr
|
||||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||||
|
|
||||||
from . import DOMAIN
|
from . import CONF_RECEIVER
|
||||||
|
from .config_flow import (
|
||||||
|
CONF_MANUFACTURER,
|
||||||
|
CONF_MODEL,
|
||||||
|
CONF_SERIAL_NUMBER,
|
||||||
|
CONF_TYPE,
|
||||||
|
DOMAIN,
|
||||||
|
)
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
ATTR_SOUND_MODE_RAW = "sound_mode_raw"
|
ATTR_SOUND_MODE_RAW = "sound_mode_raw"
|
||||||
|
|
||||||
CONF_INVALID_ZONES_ERR = "Invalid Zone (expected Zone2 or Zone3)"
|
|
||||||
CONF_SHOW_ALL_SOURCES = "show_all_sources"
|
|
||||||
CONF_VALID_ZONES = ["Zone2", "Zone3"]
|
|
||||||
CONF_ZONES = "zones"
|
|
||||||
|
|
||||||
DEFAULT_SHOW_SOURCES = False
|
|
||||||
DEFAULT_TIMEOUT = 2
|
|
||||||
|
|
||||||
KEY_DENON_CACHE = "denonavr_hosts"
|
|
||||||
|
|
||||||
SUPPORT_DENON = (
|
SUPPORT_DENON = (
|
||||||
SUPPORT_VOLUME_STEP
|
SUPPORT_VOLUME_STEP
|
||||||
| SUPPORT_VOLUME_MUTE
|
| SUPPORT_VOLUME_MUTE
|
||||||
|
@ -73,99 +63,32 @@ SUPPORT_MEDIA_MODES = (
|
||||||
| SUPPORT_PLAY
|
| SUPPORT_PLAY
|
||||||
)
|
)
|
||||||
|
|
||||||
DENON_ZONE_SCHEMA = vol.Schema(
|
|
||||||
{
|
|
||||||
vol.Required(CONF_ZONE): vol.In(CONF_VALID_ZONES, CONF_INVALID_ZONES_ERR),
|
|
||||||
vol.Optional(CONF_NAME): cv.string,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||||
{
|
"""Set up the DenonAVR receiver from a config entry."""
|
||||||
vol.Optional(CONF_HOST): cv.string,
|
entities = []
|
||||||
vol.Optional(CONF_NAME): cv.string,
|
receiver = hass.data[DOMAIN][config_entry.entry_id][CONF_RECEIVER]
|
||||||
vol.Optional(CONF_SHOW_ALL_SOURCES, default=DEFAULT_SHOW_SOURCES): cv.boolean,
|
for receiver_zone in receiver.zones.values():
|
||||||
vol.Optional(CONF_ZONES): vol.All(cv.ensure_list, [DENON_ZONE_SCHEMA]),
|
if config_entry.data[CONF_SERIAL_NUMBER] is not None:
|
||||||
vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int,
|
unique_id = f"{config_entry.unique_id}-{receiver_zone.zone}"
|
||||||
}
|
else:
|
||||||
)
|
unique_id = None
|
||||||
|
entities.append(DenonDevice(receiver_zone, unique_id, config_entry))
|
||||||
NewHost = namedtuple("NewHost", ["host", "name"])
|
_LOGGER.debug(
|
||||||
|
"%s receiver at host %s initialized", receiver.manufacturer, receiver.host
|
||||||
|
)
|
||||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
async_add_entities(entities)
|
||||||
"""Set up the Denon platform."""
|
|
||||||
# Initialize list with receivers to be started
|
|
||||||
receivers = []
|
|
||||||
|
|
||||||
cache = hass.data.get(KEY_DENON_CACHE)
|
|
||||||
if cache is None:
|
|
||||||
cache = hass.data[KEY_DENON_CACHE] = set()
|
|
||||||
|
|
||||||
# Get config option for show_all_sources and timeout
|
|
||||||
show_all_sources = config[CONF_SHOW_ALL_SOURCES]
|
|
||||||
timeout = config[CONF_TIMEOUT]
|
|
||||||
|
|
||||||
# Get config option for additional zones
|
|
||||||
zones = config.get(CONF_ZONES)
|
|
||||||
if zones is not None:
|
|
||||||
add_zones = {}
|
|
||||||
for entry in zones:
|
|
||||||
add_zones[entry[CONF_ZONE]] = entry.get(CONF_NAME)
|
|
||||||
else:
|
|
||||||
add_zones = None
|
|
||||||
|
|
||||||
# Start assignment of host and name
|
|
||||||
new_hosts = []
|
|
||||||
# 1. option: manual setting
|
|
||||||
if config.get(CONF_HOST) is not None:
|
|
||||||
host = config.get(CONF_HOST)
|
|
||||||
name = config.get(CONF_NAME)
|
|
||||||
new_hosts.append(NewHost(host=host, name=name))
|
|
||||||
|
|
||||||
# 2. option: discovery using netdisco
|
|
||||||
if discovery_info is not None:
|
|
||||||
host = discovery_info.get("host")
|
|
||||||
name = discovery_info.get("name")
|
|
||||||
new_hosts.append(NewHost(host=host, name=name))
|
|
||||||
|
|
||||||
# 3. option: discovery using denonavr library
|
|
||||||
if config.get(CONF_HOST) is None and discovery_info is None:
|
|
||||||
d_receivers = denonavr.discover()
|
|
||||||
# More than one receiver could be discovered by that method
|
|
||||||
for d_receiver in d_receivers:
|
|
||||||
host = d_receiver["host"]
|
|
||||||
name = d_receiver["friendlyName"]
|
|
||||||
new_hosts.append(NewHost(host=host, name=name))
|
|
||||||
|
|
||||||
for entry in new_hosts:
|
|
||||||
# Check if host not in cache, append it and save for later
|
|
||||||
# starting
|
|
||||||
if entry.host not in cache:
|
|
||||||
new_device = denonavr.DenonAVR(
|
|
||||||
host=entry.host,
|
|
||||||
name=entry.name,
|
|
||||||
show_all_inputs=show_all_sources,
|
|
||||||
timeout=timeout,
|
|
||||||
add_zones=add_zones,
|
|
||||||
)
|
|
||||||
for new_zone in new_device.zones.values():
|
|
||||||
receivers.append(DenonDevice(new_zone))
|
|
||||||
cache.add(host)
|
|
||||||
_LOGGER.info("Denon receiver at host %s initialized", host)
|
|
||||||
|
|
||||||
# Add all freshly discovered receivers
|
|
||||||
if receivers:
|
|
||||||
add_entities(receivers)
|
|
||||||
|
|
||||||
|
|
||||||
class DenonDevice(MediaPlayerEntity):
|
class DenonDevice(MediaPlayerEntity):
|
||||||
"""Representation of a Denon Media Player Device."""
|
"""Representation of a Denon Media Player Device."""
|
||||||
|
|
||||||
def __init__(self, receiver):
|
def __init__(self, receiver, unique_id, config_entry):
|
||||||
"""Initialize the device."""
|
"""Initialize the device."""
|
||||||
self._receiver = receiver
|
self._receiver = receiver
|
||||||
self._name = self._receiver.name
|
self._name = self._receiver.name
|
||||||
|
self._unique_id = unique_id
|
||||||
|
self._config_entry = config_entry
|
||||||
self._muted = self._receiver.muted
|
self._muted = self._receiver.muted
|
||||||
self._volume = self._receiver.volume
|
self._volume = self._receiver.volume
|
||||||
self._current_source = self._receiver.input_func
|
self._current_source = self._receiver.input_func
|
||||||
|
@ -237,6 +160,30 @@ class DenonDevice(MediaPlayerEntity):
|
||||||
self._sound_mode = self._receiver.sound_mode
|
self._sound_mode = self._receiver.sound_mode
|
||||||
self._sound_mode_raw = self._receiver.sound_mode_raw
|
self._sound_mode_raw = self._receiver.sound_mode_raw
|
||||||
|
|
||||||
|
@property
|
||||||
|
def unique_id(self):
|
||||||
|
"""Return the unique id of the zone."""
|
||||||
|
return self._unique_id
|
||||||
|
|
||||||
|
@property
|
||||||
|
def device_info(self):
|
||||||
|
"""Return the device info of the receiver."""
|
||||||
|
if self._config_entry.data[CONF_SERIAL_NUMBER] is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
device_info = {
|
||||||
|
"identifiers": {(DOMAIN, self._config_entry.unique_id)},
|
||||||
|
"manufacturer": self._config_entry.data[CONF_MANUFACTURER],
|
||||||
|
"name": self._config_entry.title,
|
||||||
|
"model": f"{self._config_entry.data[CONF_MODEL]}-{self._config_entry.data[CONF_TYPE]}",
|
||||||
|
}
|
||||||
|
if self._config_entry.data[CONF_MAC] is not None:
|
||||||
|
device_info["connections"] = {
|
||||||
|
(dr.CONNECTION_NETWORK_MAC, self._config_entry.data[CONF_MAC])
|
||||||
|
}
|
||||||
|
|
||||||
|
return device_info
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self):
|
||||||
"""Return the name of the device."""
|
"""Return the name of the device."""
|
||||||
|
|
71
homeassistant/components/denonavr/receiver.py
Normal file
71
homeassistant/components/denonavr/receiver.py
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
"""Code to handle a DenonAVR receiver."""
|
||||||
|
import logging
|
||||||
|
|
||||||
|
import denonavr
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class ConnectDenonAVR:
|
||||||
|
"""Class to async connect to a DenonAVR receiver."""
|
||||||
|
|
||||||
|
def __init__(self, hass, host, timeout, show_all_inputs, zone2, zone3):
|
||||||
|
"""Initialize the class."""
|
||||||
|
self._hass = hass
|
||||||
|
self._receiver = None
|
||||||
|
self._host = host
|
||||||
|
self._show_all_inputs = show_all_inputs
|
||||||
|
self._timeout = timeout
|
||||||
|
|
||||||
|
self._zones = {}
|
||||||
|
if zone2:
|
||||||
|
self._zones["Zone2"] = None
|
||||||
|
if zone3:
|
||||||
|
self._zones["Zone3"] = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def receiver(self):
|
||||||
|
"""Return the class containing all connections to the receiver."""
|
||||||
|
return self._receiver
|
||||||
|
|
||||||
|
async def async_connect_receiver(self):
|
||||||
|
"""Connect to the DenonAVR receiver."""
|
||||||
|
if not await self._hass.async_add_executor_job(self.init_receiver_class):
|
||||||
|
return False
|
||||||
|
|
||||||
|
if (
|
||||||
|
self._receiver.manufacturer is None
|
||||||
|
or self._receiver.name is None
|
||||||
|
or self._receiver.model_name is None
|
||||||
|
or self._receiver.receiver_type is None
|
||||||
|
):
|
||||||
|
return False
|
||||||
|
|
||||||
|
_LOGGER.debug(
|
||||||
|
"%s receiver %s at host %s connected, model %s, serial %s, type %s",
|
||||||
|
self._receiver.manufacturer,
|
||||||
|
self._receiver.name,
|
||||||
|
self._receiver.host,
|
||||||
|
self._receiver.model_name,
|
||||||
|
self._receiver.serial_number,
|
||||||
|
self._receiver.receiver_type,
|
||||||
|
)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def init_receiver_class(self):
|
||||||
|
"""Initialize the DenonAVR class in a way that can called by async_add_executor_job."""
|
||||||
|
try:
|
||||||
|
self._receiver = denonavr.DenonAVR(
|
||||||
|
host=self._host,
|
||||||
|
show_all_inputs=self._show_all_inputs,
|
||||||
|
timeout=self._timeout,
|
||||||
|
add_zones=self._zones,
|
||||||
|
)
|
||||||
|
except ConnectionError:
|
||||||
|
_LOGGER.error(
|
||||||
|
"ConnectionError during setup of denonavr with host %s", self._host
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
48
homeassistant/components/denonavr/strings.json
Normal file
48
homeassistant/components/denonavr/strings.json
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"flow_title": "Denon AVR Network Receiver: {name}",
|
||||||
|
"step": {
|
||||||
|
"user": {
|
||||||
|
"title": "Denon AVR Network Receivers",
|
||||||
|
"description": "Connect to your receiver, if the IP address is not set, auto-discovery is used",
|
||||||
|
"data": {
|
||||||
|
"host": "IP address"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"confirm": {
|
||||||
|
"title": "Denon AVR Network Receivers",
|
||||||
|
"description": "Please confirm adding the receiver"
|
||||||
|
},
|
||||||
|
"select": {
|
||||||
|
"title": "Select the receiver that you wish to connect",
|
||||||
|
"description": "Run the setup again if you want to connect additional receivers",
|
||||||
|
"data": {
|
||||||
|
"select_host": "Receiver IP"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"discovery_error": "Failed to discover a Denon AVR Network Receiver"
|
||||||
|
},
|
||||||
|
"abort": {
|
||||||
|
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
|
||||||
|
"already_in_progress": "Config flow for this Denon AVR is already in progress",
|
||||||
|
"connection_error": "Failed to connect, please try again",
|
||||||
|
"not_denonavr_manufacturer": "Not a Denon AVR Network Receiver, discovered manafucturer did not match",
|
||||||
|
"not_denonavr_missing": "Not a Denon AVR Network Receiver, discovery information not complete"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"step": {
|
||||||
|
"init": {
|
||||||
|
"title": "Denon AVR Network Receivers",
|
||||||
|
"description": "Specify optional settings",
|
||||||
|
"data": {
|
||||||
|
"show_all_sources": "Show all sources",
|
||||||
|
"zone2": "Set up Zone 2",
|
||||||
|
"zone3": "Set up Zone 3"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
48
homeassistant/components/denonavr/translations/en.json
Normal file
48
homeassistant/components/denonavr/translations/en.json
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"flow_title": "Denon AVR Network Receiver: {name}",
|
||||||
|
"step": {
|
||||||
|
"user": {
|
||||||
|
"title": "Denon AVR Network Receivers",
|
||||||
|
"description": "Connect to your receiver, if the IP address is not set, auto-discovery is used",
|
||||||
|
"data": {
|
||||||
|
"host": "IP address"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"confirm": {
|
||||||
|
"title": "Denon AVR Network Receivers",
|
||||||
|
"description": "Please confirm adding the receiver"
|
||||||
|
},
|
||||||
|
"select": {
|
||||||
|
"title": "Select the receiver that you wish to connect",
|
||||||
|
"description": "Run the setup again if you want to connect additional receivers",
|
||||||
|
"data": {
|
||||||
|
"select_host": "Receiver IP"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"discovery_error": "Failed to discover a Denon AVR Network Receiver"
|
||||||
|
},
|
||||||
|
"abort": {
|
||||||
|
"already_configured": "Device is already configured",
|
||||||
|
"already_in_progress": "Config flow for this Denon AVR is already in progress",
|
||||||
|
"connection_error": "Failed to connect, please try again",
|
||||||
|
"not_denonavr_manufacturer": "Not a Denon AVR Network Receiver, discovered manafucturer did not match",
|
||||||
|
"not_denonavr_missing": "Not a Denon AVR Network Receiver, discovery information not complete"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"step": {
|
||||||
|
"init": {
|
||||||
|
"title": "Denon AVR Network Receivers",
|
||||||
|
"description": "Specify optional settings",
|
||||||
|
"data": {
|
||||||
|
"show_all_sources": "Show all sources",
|
||||||
|
"zone2": "Set up Zone 2",
|
||||||
|
"zone3": "Set up Zone 3"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -66,7 +66,6 @@ SERVICE_HANDLERS = {
|
||||||
SERVICE_YEELIGHT: ("yeelight", None),
|
SERVICE_YEELIGHT: ("yeelight", None),
|
||||||
"yamaha": ("media_player", "yamaha"),
|
"yamaha": ("media_player", "yamaha"),
|
||||||
"logitech_mediaserver": ("media_player", "squeezebox"),
|
"logitech_mediaserver": ("media_player", "squeezebox"),
|
||||||
"denonavr": ("media_player", "denonavr"),
|
|
||||||
"frontier_silicon": ("media_player", "frontier_silicon"),
|
"frontier_silicon": ("media_player", "frontier_silicon"),
|
||||||
"openhome": ("media_player", "openhome"),
|
"openhome": ("media_player", "openhome"),
|
||||||
"bose_soundtouch": ("media_player", "soundtouch"),
|
"bose_soundtouch": ("media_player", "soundtouch"),
|
||||||
|
@ -82,6 +81,7 @@ OPTIONAL_SERVICE_HANDLERS = {SERVICE_DLNA_DMR: ("media_player", "dlna_dmr")}
|
||||||
MIGRATED_SERVICE_HANDLERS = [
|
MIGRATED_SERVICE_HANDLERS = [
|
||||||
"axis",
|
"axis",
|
||||||
"deconz",
|
"deconz",
|
||||||
|
"denonavr",
|
||||||
"esphome",
|
"esphome",
|
||||||
"google_cast",
|
"google_cast",
|
||||||
SERVICE_HEOS,
|
SERVICE_HEOS,
|
||||||
|
|
|
@ -31,6 +31,7 @@ FLOWS = [
|
||||||
"coronavirus",
|
"coronavirus",
|
||||||
"daikin",
|
"daikin",
|
||||||
"deconz",
|
"deconz",
|
||||||
|
"denonavr",
|
||||||
"devolo_home_control",
|
"devolo_home_control",
|
||||||
"dialogflow",
|
"dialogflow",
|
||||||
"directv",
|
"directv",
|
||||||
|
|
|
@ -17,6 +17,44 @@ SSDP = {
|
||||||
"manufacturer": "Royal Philips Electronics"
|
"manufacturer": "Royal Philips Electronics"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"denonavr": [
|
||||||
|
{
|
||||||
|
"deviceType": "urn:schemas-upnp-org:device:MediaRenderer:1",
|
||||||
|
"manufacturer": "Denon"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"deviceType": "urn:schemas-upnp-org:device:MediaRenderer:1",
|
||||||
|
"manufacturer": "DENON"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"deviceType": "urn:schemas-upnp-org:device:MediaRenderer:1",
|
||||||
|
"manufacturer": "Marantz"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"deviceType": "urn:schemas-upnp-org:device:MediaServer:1",
|
||||||
|
"manufacturer": "Denon"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"deviceType": "urn:schemas-upnp-org:device:MediaServer:1",
|
||||||
|
"manufacturer": "DENON"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"deviceType": "urn:schemas-upnp-org:device:MediaServer:1",
|
||||||
|
"manufacturer": "Marantz"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"deviceType": "urn:schemas-denon-com:device:AiosDevice:1",
|
||||||
|
"manufacturer": "Denon"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"deviceType": "urn:schemas-denon-com:device:AiosDevice:1",
|
||||||
|
"manufacturer": "DENON"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"deviceType": "urn:schemas-denon-com:device:AiosDevice:1",
|
||||||
|
"manufacturer": "Marantz"
|
||||||
|
}
|
||||||
|
],
|
||||||
"directv": [
|
"directv": [
|
||||||
{
|
{
|
||||||
"deviceType": "urn:schemas-upnp-org:device:MediaServer:1",
|
"deviceType": "urn:schemas-upnp-org:device:MediaServer:1",
|
||||||
|
|
|
@ -472,7 +472,7 @@ defusedxml==0.6.0
|
||||||
deluge-client==1.7.1
|
deluge-client==1.7.1
|
||||||
|
|
||||||
# homeassistant.components.denonavr
|
# homeassistant.components.denonavr
|
||||||
denonavr==0.8.1
|
denonavr==0.9.3
|
||||||
|
|
||||||
# homeassistant.components.devolo_home_control
|
# homeassistant.components.devolo_home_control
|
||||||
devolo-home-control-api==0.11.0
|
devolo-home-control-api==0.11.0
|
||||||
|
@ -634,6 +634,7 @@ georss_ign_sismologia_client==0.2
|
||||||
# homeassistant.components.qld_bushfire
|
# homeassistant.components.qld_bushfire
|
||||||
georss_qld_bushfire_alert_client==0.3
|
georss_qld_bushfire_alert_client==0.3
|
||||||
|
|
||||||
|
# homeassistant.components.denonavr
|
||||||
# homeassistant.components.huawei_lte
|
# homeassistant.components.huawei_lte
|
||||||
# homeassistant.components.kef
|
# homeassistant.components.kef
|
||||||
# homeassistant.components.minecraft_server
|
# homeassistant.components.minecraft_server
|
||||||
|
|
|
@ -215,7 +215,7 @@ datapoint==0.9.5
|
||||||
defusedxml==0.6.0
|
defusedxml==0.6.0
|
||||||
|
|
||||||
# homeassistant.components.denonavr
|
# homeassistant.components.denonavr
|
||||||
denonavr==0.8.1
|
denonavr==0.9.3
|
||||||
|
|
||||||
# homeassistant.components.devolo_home_control
|
# homeassistant.components.devolo_home_control
|
||||||
devolo-home-control-api==0.11.0
|
devolo-home-control-api==0.11.0
|
||||||
|
@ -281,6 +281,7 @@ georss_ign_sismologia_client==0.2
|
||||||
# homeassistant.components.qld_bushfire
|
# homeassistant.components.qld_bushfire
|
||||||
georss_qld_bushfire_alert_client==0.3
|
georss_qld_bushfire_alert_client==0.3
|
||||||
|
|
||||||
|
# homeassistant.components.denonavr
|
||||||
# homeassistant.components.huawei_lte
|
# homeassistant.components.huawei_lte
|
||||||
# homeassistant.components.kef
|
# homeassistant.components.kef
|
||||||
# homeassistant.components.minecraft_server
|
# homeassistant.components.minecraft_server
|
||||||
|
|
561
tests/components/denonavr/test_config_flow.py
Normal file
561
tests/components/denonavr/test_config_flow.py
Normal file
|
@ -0,0 +1,561 @@
|
||||||
|
"""Test the DenonAVR config flow."""
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from homeassistant import config_entries, data_entry_flow
|
||||||
|
from homeassistant.components import ssdp
|
||||||
|
from homeassistant.components.denonavr.config_flow import (
|
||||||
|
CONF_MANUFACTURER,
|
||||||
|
CONF_MODEL,
|
||||||
|
CONF_SERIAL_NUMBER,
|
||||||
|
CONF_SHOW_ALL_SOURCES,
|
||||||
|
CONF_TYPE,
|
||||||
|
CONF_ZONE2,
|
||||||
|
CONF_ZONE3,
|
||||||
|
DOMAIN,
|
||||||
|
)
|
||||||
|
from homeassistant.const import CONF_HOST, CONF_MAC
|
||||||
|
|
||||||
|
from tests.async_mock import patch
|
||||||
|
from tests.common import MockConfigEntry
|
||||||
|
|
||||||
|
TEST_HOST = "1.2.3.4"
|
||||||
|
TEST_MAC = "ab:cd:ef:gh"
|
||||||
|
TEST_HOST2 = "5.6.7.8"
|
||||||
|
TEST_NAME = "Test_Receiver"
|
||||||
|
TEST_MODEL = "model5"
|
||||||
|
TEST_RECEIVER_TYPE = "avr-x"
|
||||||
|
TEST_SERIALNUMBER = "123456789"
|
||||||
|
TEST_MANUFACTURER = "Denon"
|
||||||
|
TEST_SSDP_LOCATION = f"http://{TEST_HOST}/"
|
||||||
|
TEST_UNIQUE_ID = f"{TEST_MODEL}-{TEST_SERIALNUMBER}"
|
||||||
|
TEST_DISCOVER_1_RECEIVER = [{CONF_HOST: TEST_HOST}]
|
||||||
|
TEST_DISCOVER_2_RECEIVER = [{CONF_HOST: TEST_HOST}, {CONF_HOST: TEST_HOST2}]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(name="denonavr_connect", autouse=True)
|
||||||
|
def denonavr_connect_fixture():
|
||||||
|
"""Mock denonavr connection and entry setup."""
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.denonavr.receiver.denonavr.DenonAVR._update_input_func_list",
|
||||||
|
return_value=True,
|
||||||
|
), patch(
|
||||||
|
"homeassistant.components.denonavr.receiver.denonavr.DenonAVR._get_receiver_name",
|
||||||
|
return_value=TEST_NAME,
|
||||||
|
), patch(
|
||||||
|
"homeassistant.components.denonavr.receiver.denonavr.DenonAVR._get_support_sound_mode",
|
||||||
|
return_value=True,
|
||||||
|
), patch(
|
||||||
|
"homeassistant.components.denonavr.receiver.denonavr.DenonAVR._update_avr_2016",
|
||||||
|
return_value=True,
|
||||||
|
), patch(
|
||||||
|
"homeassistant.components.denonavr.receiver.denonavr.DenonAVR._update_avr",
|
||||||
|
return_value=True,
|
||||||
|
), patch(
|
||||||
|
"homeassistant.components.denonavr.receiver.denonavr.DenonAVR.get_device_info",
|
||||||
|
return_value=True,
|
||||||
|
), patch(
|
||||||
|
"homeassistant.components.denonavr.receiver.denonavr.DenonAVR.name", TEST_NAME,
|
||||||
|
), patch(
|
||||||
|
"homeassistant.components.denonavr.receiver.denonavr.DenonAVR.model_name",
|
||||||
|
TEST_MODEL,
|
||||||
|
), patch(
|
||||||
|
"homeassistant.components.denonavr.receiver.denonavr.DenonAVR.serial_number",
|
||||||
|
TEST_SERIALNUMBER,
|
||||||
|
), patch(
|
||||||
|
"homeassistant.components.denonavr.receiver.denonavr.DenonAVR.manufacturer",
|
||||||
|
TEST_MANUFACTURER,
|
||||||
|
), patch(
|
||||||
|
"homeassistant.components.denonavr.receiver.denonavr.DenonAVR.receiver_type",
|
||||||
|
TEST_RECEIVER_TYPE,
|
||||||
|
), patch(
|
||||||
|
"homeassistant.components.denonavr.config_flow.get_mac_address",
|
||||||
|
return_value=TEST_MAC,
|
||||||
|
), patch(
|
||||||
|
"homeassistant.components.denonavr.async_setup_entry", return_value=True
|
||||||
|
):
|
||||||
|
yield
|
||||||
|
|
||||||
|
|
||||||
|
async def test_config_flow_manual_host_success(hass):
|
||||||
|
"""
|
||||||
|
Successful flow manually initialized by the user.
|
||||||
|
|
||||||
|
Host specified.
|
||||||
|
"""
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == "form"
|
||||||
|
assert result["step_id"] == "user"
|
||||||
|
assert result["errors"] == {}
|
||||||
|
|
||||||
|
result = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"], {CONF_HOST: TEST_HOST},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == "create_entry"
|
||||||
|
assert result["title"] == TEST_NAME
|
||||||
|
assert result["data"] == {
|
||||||
|
CONF_HOST: TEST_HOST,
|
||||||
|
CONF_MAC: TEST_MAC,
|
||||||
|
CONF_MODEL: TEST_MODEL,
|
||||||
|
CONF_TYPE: TEST_RECEIVER_TYPE,
|
||||||
|
CONF_MANUFACTURER: TEST_MANUFACTURER,
|
||||||
|
CONF_SERIAL_NUMBER: TEST_SERIALNUMBER,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_config_flow_manual_discover_1_success(hass):
|
||||||
|
"""
|
||||||
|
Successful flow manually initialized by the user.
|
||||||
|
|
||||||
|
Without the host specified and 1 receiver discovered.
|
||||||
|
"""
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == "form"
|
||||||
|
assert result["step_id"] == "user"
|
||||||
|
assert result["errors"] == {}
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.denonavr.config_flow.denonavr.ssdp.identify_denonavr_receivers",
|
||||||
|
return_value=TEST_DISCOVER_1_RECEIVER,
|
||||||
|
):
|
||||||
|
result = await hass.config_entries.flow.async_configure(result["flow_id"], {},)
|
||||||
|
|
||||||
|
assert result["type"] == "create_entry"
|
||||||
|
assert result["title"] == TEST_NAME
|
||||||
|
assert result["data"] == {
|
||||||
|
CONF_HOST: TEST_HOST,
|
||||||
|
CONF_MAC: TEST_MAC,
|
||||||
|
CONF_MODEL: TEST_MODEL,
|
||||||
|
CONF_TYPE: TEST_RECEIVER_TYPE,
|
||||||
|
CONF_MANUFACTURER: TEST_MANUFACTURER,
|
||||||
|
CONF_SERIAL_NUMBER: TEST_SERIALNUMBER,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_config_flow_manual_discover_2_success(hass):
|
||||||
|
"""
|
||||||
|
Successful flow manually initialized by the user.
|
||||||
|
|
||||||
|
Without the host specified and 2 receiver discovered.
|
||||||
|
"""
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == "form"
|
||||||
|
assert result["step_id"] == "user"
|
||||||
|
assert result["errors"] == {}
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.denonavr.config_flow.denonavr.ssdp.identify_denonavr_receivers",
|
||||||
|
return_value=TEST_DISCOVER_2_RECEIVER,
|
||||||
|
):
|
||||||
|
result = await hass.config_entries.flow.async_configure(result["flow_id"], {},)
|
||||||
|
|
||||||
|
assert result["type"] == "form"
|
||||||
|
assert result["step_id"] == "select"
|
||||||
|
assert result["errors"] == {}
|
||||||
|
|
||||||
|
result = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"], {"select_host": TEST_HOST2},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == "create_entry"
|
||||||
|
assert result["title"] == TEST_NAME
|
||||||
|
assert result["data"] == {
|
||||||
|
CONF_HOST: TEST_HOST2,
|
||||||
|
CONF_MAC: TEST_MAC,
|
||||||
|
CONF_MODEL: TEST_MODEL,
|
||||||
|
CONF_TYPE: TEST_RECEIVER_TYPE,
|
||||||
|
CONF_MANUFACTURER: TEST_MANUFACTURER,
|
||||||
|
CONF_SERIAL_NUMBER: TEST_SERIALNUMBER,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_config_flow_manual_discover_error(hass):
|
||||||
|
"""
|
||||||
|
Failed flow manually initialized by the user.
|
||||||
|
|
||||||
|
Without the host specified and no receiver discovered.
|
||||||
|
"""
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == "form"
|
||||||
|
assert result["step_id"] == "user"
|
||||||
|
assert result["errors"] == {}
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.denonavr.config_flow.denonavr.ssdp.identify_denonavr_receivers",
|
||||||
|
return_value=[],
|
||||||
|
):
|
||||||
|
result = await hass.config_entries.flow.async_configure(result["flow_id"], {},)
|
||||||
|
|
||||||
|
assert result["type"] == "form"
|
||||||
|
assert result["step_id"] == "user"
|
||||||
|
assert result["errors"] == {"base": "discovery_error"}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_config_flow_manual_host_no_serial(hass):
|
||||||
|
"""
|
||||||
|
Successful flow manually initialized by the user.
|
||||||
|
|
||||||
|
Host specified and an error getting the serial number.
|
||||||
|
"""
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == "form"
|
||||||
|
assert result["step_id"] == "user"
|
||||||
|
assert result["errors"] == {}
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.denonavr.receiver.denonavr.DenonAVR.serial_number",
|
||||||
|
None,
|
||||||
|
):
|
||||||
|
result = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"], {CONF_HOST: TEST_HOST},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == "create_entry"
|
||||||
|
assert result["title"] == TEST_NAME
|
||||||
|
assert result["data"] == {
|
||||||
|
CONF_HOST: TEST_HOST,
|
||||||
|
CONF_MAC: TEST_MAC,
|
||||||
|
CONF_MODEL: TEST_MODEL,
|
||||||
|
CONF_TYPE: TEST_RECEIVER_TYPE,
|
||||||
|
CONF_MANUFACTURER: TEST_MANUFACTURER,
|
||||||
|
CONF_SERIAL_NUMBER: None,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_config_flow_manual_host_no_mac(hass):
|
||||||
|
"""
|
||||||
|
Successful flow manually initialized by the user.
|
||||||
|
|
||||||
|
Host specified and an error getting the mac address.
|
||||||
|
"""
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == "form"
|
||||||
|
assert result["step_id"] == "user"
|
||||||
|
assert result["errors"] == {}
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.denonavr.config_flow.get_mac_address",
|
||||||
|
return_value=None,
|
||||||
|
):
|
||||||
|
result = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"], {CONF_HOST: TEST_HOST},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == "create_entry"
|
||||||
|
assert result["title"] == TEST_NAME
|
||||||
|
assert result["data"] == {
|
||||||
|
CONF_HOST: TEST_HOST,
|
||||||
|
CONF_MAC: None,
|
||||||
|
CONF_MODEL: TEST_MODEL,
|
||||||
|
CONF_TYPE: TEST_RECEIVER_TYPE,
|
||||||
|
CONF_MANUFACTURER: TEST_MANUFACTURER,
|
||||||
|
CONF_SERIAL_NUMBER: TEST_SERIALNUMBER,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_config_flow_manual_host_no_serial_no_mac(hass):
|
||||||
|
"""
|
||||||
|
Successful flow manually initialized by the user.
|
||||||
|
|
||||||
|
Host specified and an error getting the serial number and mac address.
|
||||||
|
"""
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == "form"
|
||||||
|
assert result["step_id"] == "user"
|
||||||
|
assert result["errors"] == {}
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.denonavr.receiver.denonavr.DenonAVR.serial_number",
|
||||||
|
None,
|
||||||
|
), patch(
|
||||||
|
"homeassistant.components.denonavr.config_flow.get_mac_address",
|
||||||
|
return_value=None,
|
||||||
|
):
|
||||||
|
result = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"], {CONF_HOST: TEST_HOST},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == "create_entry"
|
||||||
|
assert result["title"] == TEST_NAME
|
||||||
|
assert result["data"] == {
|
||||||
|
CONF_HOST: TEST_HOST,
|
||||||
|
CONF_MAC: None,
|
||||||
|
CONF_MODEL: TEST_MODEL,
|
||||||
|
CONF_TYPE: TEST_RECEIVER_TYPE,
|
||||||
|
CONF_MANUFACTURER: TEST_MANUFACTURER,
|
||||||
|
CONF_SERIAL_NUMBER: None,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_config_flow_manual_host_no_serial_no_mac_exception(hass):
|
||||||
|
"""
|
||||||
|
Successful flow manually initialized by the user.
|
||||||
|
|
||||||
|
Host specified and an error getting the serial number and exception getting mac address.
|
||||||
|
"""
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == "form"
|
||||||
|
assert result["step_id"] == "user"
|
||||||
|
assert result["errors"] == {}
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.denonavr.receiver.denonavr.DenonAVR.serial_number",
|
||||||
|
None,
|
||||||
|
), patch(
|
||||||
|
"homeassistant.components.denonavr.config_flow.get_mac_address",
|
||||||
|
side_effect=OSError,
|
||||||
|
):
|
||||||
|
result = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"], {CONF_HOST: TEST_HOST},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == "create_entry"
|
||||||
|
assert result["title"] == TEST_NAME
|
||||||
|
assert result["data"] == {
|
||||||
|
CONF_HOST: TEST_HOST,
|
||||||
|
CONF_MAC: None,
|
||||||
|
CONF_MODEL: TEST_MODEL,
|
||||||
|
CONF_TYPE: TEST_RECEIVER_TYPE,
|
||||||
|
CONF_MANUFACTURER: TEST_MANUFACTURER,
|
||||||
|
CONF_SERIAL_NUMBER: None,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_config_flow_manual_host_connection_error(hass):
|
||||||
|
"""
|
||||||
|
Failed flow manually initialized by the user.
|
||||||
|
|
||||||
|
Host specified and a connection error.
|
||||||
|
"""
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == "form"
|
||||||
|
assert result["step_id"] == "user"
|
||||||
|
assert result["errors"] == {}
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.denonavr.receiver.denonavr.DenonAVR.get_device_info",
|
||||||
|
side_effect=ConnectionError,
|
||||||
|
), patch(
|
||||||
|
"homeassistant.components.denonavr.receiver.denonavr.DenonAVR.receiver_type",
|
||||||
|
None,
|
||||||
|
):
|
||||||
|
result = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"], {CONF_HOST: TEST_HOST},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == "abort"
|
||||||
|
assert result["reason"] == "connection_error"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_config_flow_manual_host_no_device_info(hass):
|
||||||
|
"""
|
||||||
|
Failed flow manually initialized by the user.
|
||||||
|
|
||||||
|
Host specified and no device info (due to receiver power off).
|
||||||
|
"""
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == "form"
|
||||||
|
assert result["step_id"] == "user"
|
||||||
|
assert result["errors"] == {}
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.denonavr.receiver.denonavr.DenonAVR.receiver_type",
|
||||||
|
None,
|
||||||
|
):
|
||||||
|
result = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"], {CONF_HOST: TEST_HOST},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == "abort"
|
||||||
|
assert result["reason"] == "connection_error"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_config_flow_ssdp(hass):
|
||||||
|
"""Successful flow initialized by ssdp discovery."""
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN,
|
||||||
|
context={"source": config_entries.SOURCE_SSDP},
|
||||||
|
data={
|
||||||
|
ssdp.ATTR_UPNP_MANUFACTURER: TEST_MANUFACTURER,
|
||||||
|
ssdp.ATTR_UPNP_MODEL_NAME: TEST_MODEL,
|
||||||
|
ssdp.ATTR_UPNP_SERIAL: TEST_SERIALNUMBER,
|
||||||
|
ssdp.ATTR_SSDP_LOCATION: TEST_SSDP_LOCATION,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == "form"
|
||||||
|
assert result["step_id"] == "confirm"
|
||||||
|
|
||||||
|
result = await hass.config_entries.flow.async_configure(result["flow_id"], {},)
|
||||||
|
|
||||||
|
assert result["type"] == "create_entry"
|
||||||
|
assert result["title"] == TEST_NAME
|
||||||
|
assert result["data"] == {
|
||||||
|
CONF_HOST: TEST_HOST,
|
||||||
|
CONF_MAC: TEST_MAC,
|
||||||
|
CONF_MODEL: TEST_MODEL,
|
||||||
|
CONF_TYPE: TEST_RECEIVER_TYPE,
|
||||||
|
CONF_MANUFACTURER: TEST_MANUFACTURER,
|
||||||
|
CONF_SERIAL_NUMBER: TEST_SERIALNUMBER,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_config_flow_ssdp_not_denon(hass):
|
||||||
|
"""
|
||||||
|
Failed flow initialized by ssdp discovery.
|
||||||
|
|
||||||
|
Not supported manufacturer.
|
||||||
|
"""
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN,
|
||||||
|
context={"source": config_entries.SOURCE_SSDP},
|
||||||
|
data={
|
||||||
|
ssdp.ATTR_UPNP_MANUFACTURER: "NotSupported",
|
||||||
|
ssdp.ATTR_UPNP_MODEL_NAME: TEST_MODEL,
|
||||||
|
ssdp.ATTR_UPNP_SERIAL: TEST_SERIALNUMBER,
|
||||||
|
ssdp.ATTR_SSDP_LOCATION: TEST_SSDP_LOCATION,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == "abort"
|
||||||
|
assert result["reason"] == "not_denonavr_manufacturer"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_config_flow_ssdp_missing_info(hass):
|
||||||
|
"""
|
||||||
|
Failed flow initialized by ssdp discovery.
|
||||||
|
|
||||||
|
Missing information.
|
||||||
|
"""
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN,
|
||||||
|
context={"source": config_entries.SOURCE_SSDP},
|
||||||
|
data={
|
||||||
|
ssdp.ATTR_UPNP_MANUFACTURER: TEST_MANUFACTURER,
|
||||||
|
ssdp.ATTR_SSDP_LOCATION: TEST_SSDP_LOCATION,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == "abort"
|
||||||
|
assert result["reason"] == "not_denonavr_missing"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_options_flow(hass):
|
||||||
|
"""Test specifying non default settings using options flow."""
|
||||||
|
config_entry = MockConfigEntry(
|
||||||
|
domain=DOMAIN,
|
||||||
|
unique_id=TEST_UNIQUE_ID,
|
||||||
|
data={
|
||||||
|
CONF_HOST: TEST_HOST,
|
||||||
|
CONF_MAC: TEST_MAC,
|
||||||
|
CONF_MODEL: TEST_MODEL,
|
||||||
|
CONF_TYPE: TEST_RECEIVER_TYPE,
|
||||||
|
CONF_MANUFACTURER: TEST_MANUFACTURER,
|
||||||
|
CONF_SERIAL_NUMBER: TEST_SERIALNUMBER,
|
||||||
|
},
|
||||||
|
title=TEST_NAME,
|
||||||
|
)
|
||||||
|
config_entry.add_to_hass(hass)
|
||||||
|
|
||||||
|
assert await hass.config_entries.async_setup(config_entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
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_SHOW_ALL_SOURCES: True, CONF_ZONE2: True, CONF_ZONE3: True},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
|
||||||
|
assert config_entry.options == {
|
||||||
|
CONF_SHOW_ALL_SOURCES: True,
|
||||||
|
CONF_ZONE2: True,
|
||||||
|
CONF_ZONE3: True,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_config_flow_manual_host_no_serial_double_config(hass):
|
||||||
|
"""
|
||||||
|
Failed flow manually initialized by the user twice.
|
||||||
|
|
||||||
|
Host specified and an error getting the serial number.
|
||||||
|
"""
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == "form"
|
||||||
|
assert result["step_id"] == "user"
|
||||||
|
assert result["errors"] == {}
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.denonavr.receiver.denonavr.DenonAVR.serial_number",
|
||||||
|
None,
|
||||||
|
):
|
||||||
|
result = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"], {CONF_HOST: TEST_HOST},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == "create_entry"
|
||||||
|
assert result["title"] == TEST_NAME
|
||||||
|
assert result["data"] == {
|
||||||
|
CONF_HOST: TEST_HOST,
|
||||||
|
CONF_MAC: TEST_MAC,
|
||||||
|
CONF_MODEL: TEST_MODEL,
|
||||||
|
CONF_TYPE: TEST_RECEIVER_TYPE,
|
||||||
|
CONF_MANUFACTURER: TEST_MANUFACTURER,
|
||||||
|
CONF_SERIAL_NUMBER: None,
|
||||||
|
}
|
||||||
|
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == "form"
|
||||||
|
assert result["step_id"] == "user"
|
||||||
|
assert result["errors"] == {}
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.denonavr.receiver.denonavr.DenonAVR.serial_number",
|
||||||
|
None,
|
||||||
|
):
|
||||||
|
result = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"], {CONF_HOST: TEST_HOST},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == "abort"
|
||||||
|
assert result["reason"] == "already_configured"
|
|
@ -1,57 +1,92 @@
|
||||||
"""The tests for the denonavr media player platform."""
|
"""The tests for the denonavr media player platform."""
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from homeassistant.components import media_player
|
from homeassistant.components import media_player
|
||||||
from homeassistant.components.denonavr import ATTR_COMMAND, DOMAIN, SERVICE_GET_COMMAND
|
from homeassistant.components.denonavr import ATTR_COMMAND, SERVICE_GET_COMMAND
|
||||||
from homeassistant.const import ATTR_ENTITY_ID, CONF_HOST, CONF_NAME, CONF_PLATFORM
|
from homeassistant.components.denonavr.config_flow import (
|
||||||
from homeassistant.setup import async_setup_component
|
CONF_MANUFACTURER,
|
||||||
|
CONF_MODEL,
|
||||||
|
CONF_SERIAL_NUMBER,
|
||||||
|
CONF_TYPE,
|
||||||
|
DOMAIN,
|
||||||
|
)
|
||||||
|
from homeassistant.const import ATTR_ENTITY_ID, CONF_HOST, CONF_MAC
|
||||||
|
|
||||||
from tests.async_mock import patch
|
from tests.common import MockConfigEntry
|
||||||
|
|
||||||
NAME = "fake"
|
TEST_HOST = "1.2.3.4"
|
||||||
ENTITY_ID = f"{media_player.DOMAIN}.{NAME}"
|
TEST_MAC = "ab:cd:ef:gh"
|
||||||
|
TEST_NAME = "Test_Receiver"
|
||||||
|
TEST_MODEL = "model5"
|
||||||
|
TEST_SERIALNUMBER = "123456789"
|
||||||
|
TEST_MANUFACTURER = "Denon"
|
||||||
|
TEST_RECEIVER_TYPE = "avr-x"
|
||||||
|
TEST_ZONE = "Main"
|
||||||
|
TEST_UNIQUE_ID = f"{TEST_MODEL}-{TEST_SERIALNUMBER}"
|
||||||
|
TEST_TIMEOUT = 2
|
||||||
|
TEST_SHOW_ALL_SOURCES = False
|
||||||
|
TEST_ZONE2 = False
|
||||||
|
TEST_ZONE3 = False
|
||||||
|
ENTITY_ID = f"{media_player.DOMAIN}.{TEST_NAME}"
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(name="client")
|
@pytest.fixture(name="client")
|
||||||
def client_fixture():
|
def client_fixture():
|
||||||
"""Patch of client library for tests."""
|
"""Patch of client library for tests."""
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.components.denonavr.media_player.denonavr.DenonAVR",
|
"homeassistant.components.denonavr.receiver.denonavr.DenonAVR", autospec=True,
|
||||||
autospec=True,
|
|
||||||
) as mock_client_class, patch(
|
) as mock_client_class, patch(
|
||||||
"homeassistant.components.denonavr.media_player.denonavr.discover"
|
"homeassistant.components.denonavr.receiver.denonavr.discover"
|
||||||
):
|
):
|
||||||
mock_client_class.return_value.name = NAME
|
mock_client_class.return_value.name = TEST_NAME
|
||||||
|
mock_client_class.return_value.model_name = TEST_MODEL
|
||||||
|
mock_client_class.return_value.serial_number = TEST_SERIALNUMBER
|
||||||
|
mock_client_class.return_value.manufacturer = TEST_MANUFACTURER
|
||||||
|
mock_client_class.return_value.receiver_type = TEST_RECEIVER_TYPE
|
||||||
|
mock_client_class.return_value.zone = TEST_ZONE
|
||||||
|
mock_client_class.return_value.input_func_list = []
|
||||||
|
mock_client_class.return_value.sound_mode_list = []
|
||||||
mock_client_class.return_value.zones = {"Main": mock_client_class.return_value}
|
mock_client_class.return_value.zones = {"Main": mock_client_class.return_value}
|
||||||
yield mock_client_class.return_value
|
yield mock_client_class.return_value
|
||||||
|
|
||||||
|
|
||||||
async def setup_denonavr(hass):
|
async def setup_denonavr(hass):
|
||||||
"""Initialize webostv and media_player for tests."""
|
"""Initialize media_player for tests."""
|
||||||
assert await async_setup_component(
|
entry_data = {
|
||||||
hass,
|
CONF_HOST: TEST_HOST,
|
||||||
media_player.DOMAIN,
|
CONF_MAC: TEST_MAC,
|
||||||
{
|
CONF_MODEL: TEST_MODEL,
|
||||||
media_player.DOMAIN: {
|
CONF_TYPE: TEST_RECEIVER_TYPE,
|
||||||
CONF_PLATFORM: "denonavr",
|
CONF_MANUFACTURER: TEST_MANUFACTURER,
|
||||||
CONF_HOST: "fake",
|
CONF_SERIAL_NUMBER: TEST_SERIALNUMBER,
|
||||||
CONF_NAME: NAME,
|
}
|
||||||
}
|
|
||||||
},
|
mock_entry = MockConfigEntry(
|
||||||
|
domain=DOMAIN, unique_id=TEST_UNIQUE_ID, data=entry_data,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
mock_entry.add_to_hass(hass)
|
||||||
|
|
||||||
|
await hass.config_entries.async_setup(mock_entry.entry_id)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
state = hass.states.get(ENTITY_ID)
|
||||||
|
|
||||||
|
assert state
|
||||||
|
assert state.name == TEST_NAME
|
||||||
|
|
||||||
|
|
||||||
async def test_get_command(hass, client):
|
async def test_get_command(hass, client):
|
||||||
"""Test generic command functionality."""
|
"""Test generic command functionality."""
|
||||||
|
|
||||||
await setup_denonavr(hass)
|
await setup_denonavr(hass)
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
ATTR_ENTITY_ID: ENTITY_ID,
|
ATTR_ENTITY_ID: ENTITY_ID,
|
||||||
ATTR_COMMAND: "test",
|
ATTR_COMMAND: "test_command",
|
||||||
}
|
}
|
||||||
await hass.services.async_call(DOMAIN, SERVICE_GET_COMMAND, data)
|
await hass.services.async_call(DOMAIN, SERVICE_GET_COMMAND, data)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
client.send_get_command.assert_called_with("test")
|
client.send_get_command.assert_called_with("test_command")
|
||||||
|
|
Loading…
Add table
Reference in a new issue