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:
starkillerOG 2020-06-16 14:46:39 +02:00 committed by GitHub
parent 25607c7129
commit 6db5ff98ed
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 1272 additions and 138 deletions

View file

@ -160,6 +160,7 @@ omit =
homeassistant/components/deluge/switch.py
homeassistant/components/denon/media_player.py
homeassistant/components/denonavr/media_player.py
homeassistant/components/denonavr/receiver.py
homeassistant/components/deutsche_bahn/sensor.py
homeassistant/components/devolo_home_control/__init__.py
homeassistant/components/devolo_home_control/binary_sensor.py

View file

@ -1,15 +1,33 @@
"""The denonavr component."""
import logging
import voluptuous as vol
from homeassistant.const import ATTR_ENTITY_ID
import homeassistant.helpers.config_validation as cv
from homeassistant import config_entries, core
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
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"
ATTR_COMMAND = "command"
_LOGGER = logging.getLogger(__name__)
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})
@ -19,7 +37,7 @@ SERVICE_TO_METHOD = {
}
def setup(hass, config):
def setup(hass: core.HomeAssistant, config: dict):
"""Set up the denonavr platform."""
def service_handler(service):
@ -33,3 +51,72 @@ def setup(hass, config):
hass.services.register(DOMAIN, service, service_handler, schema=schema)
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)

View 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

View file

@ -1,7 +1,46 @@
{
"domain": "denonavr",
"name": "Denon AVR Network Receivers",
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/denonavr",
"requirements": ["denonavr==0.8.1"],
"codeowners": ["@scarface-4711", "@starkillerOG"]
"requirements": ["denonavr==0.9.3", "getmac==0.8.2"],
"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"
}
]
}

View file

@ -1,12 +1,8 @@
"""Support for Denon AVR receivers using their HTTP interface."""
from collections import namedtuple
import logging
import denonavr
import voluptuous as vol
from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerEntity
from homeassistant.components.media_player import MediaPlayerEntity
from homeassistant.components.media_player.const import (
MEDIA_TYPE_CHANNEL,
MEDIA_TYPE_MUSIC,
@ -25,10 +21,7 @@ from homeassistant.components.media_player.const import (
)
from homeassistant.const import (
ATTR_ENTITY_ID,
CONF_HOST,
CONF_NAME,
CONF_TIMEOUT,
CONF_ZONE,
CONF_MAC,
ENTITY_MATCH_ALL,
ENTITY_MATCH_NONE,
STATE_OFF,
@ -36,25 +29,22 @@ from homeassistant.const import (
STATE_PAUSED,
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 . import DOMAIN
from . import CONF_RECEIVER
from .config_flow import (
CONF_MANUFACTURER,
CONF_MODEL,
CONF_SERIAL_NUMBER,
CONF_TYPE,
DOMAIN,
)
_LOGGER = logging.getLogger(__name__)
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_VOLUME_STEP
| SUPPORT_VOLUME_MUTE
@ -73,99 +63,32 @@ SUPPORT_MEDIA_MODES = (
| 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(
{
vol.Optional(CONF_HOST): cv.string,
vol.Optional(CONF_NAME): cv.string,
vol.Optional(CONF_SHOW_ALL_SOURCES, default=DEFAULT_SHOW_SOURCES): cv.boolean,
vol.Optional(CONF_ZONES): vol.All(cv.ensure_list, [DENON_ZONE_SCHEMA]),
vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int,
}
)
NewHost = namedtuple("NewHost", ["host", "name"])
def setup_platform(hass, config, add_entities, discovery_info=None):
"""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)
async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up the DenonAVR receiver from a config entry."""
entities = []
receiver = hass.data[DOMAIN][config_entry.entry_id][CONF_RECEIVER]
for receiver_zone in receiver.zones.values():
if config_entry.data[CONF_SERIAL_NUMBER] is not None:
unique_id = f"{config_entry.unique_id}-{receiver_zone.zone}"
else:
unique_id = None
entities.append(DenonDevice(receiver_zone, unique_id, config_entry))
_LOGGER.debug(
"%s receiver at host %s initialized", receiver.manufacturer, receiver.host
)
async_add_entities(entities)
class DenonDevice(MediaPlayerEntity):
"""Representation of a Denon Media Player Device."""
def __init__(self, receiver):
def __init__(self, receiver, unique_id, config_entry):
"""Initialize the device."""
self._receiver = receiver
self._name = self._receiver.name
self._unique_id = unique_id
self._config_entry = config_entry
self._muted = self._receiver.muted
self._volume = self._receiver.volume
self._current_source = self._receiver.input_func
@ -237,6 +160,30 @@ class DenonDevice(MediaPlayerEntity):
self._sound_mode = self._receiver.sound_mode
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
def name(self):
"""Return the name of the device."""

View 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

View 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"
}
}
}
}
}

View 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"
}
}
}
}
}

View file

@ -66,7 +66,6 @@ SERVICE_HANDLERS = {
SERVICE_YEELIGHT: ("yeelight", None),
"yamaha": ("media_player", "yamaha"),
"logitech_mediaserver": ("media_player", "squeezebox"),
"denonavr": ("media_player", "denonavr"),
"frontier_silicon": ("media_player", "frontier_silicon"),
"openhome": ("media_player", "openhome"),
"bose_soundtouch": ("media_player", "soundtouch"),
@ -82,6 +81,7 @@ OPTIONAL_SERVICE_HANDLERS = {SERVICE_DLNA_DMR: ("media_player", "dlna_dmr")}
MIGRATED_SERVICE_HANDLERS = [
"axis",
"deconz",
"denonavr",
"esphome",
"google_cast",
SERVICE_HEOS,

View file

@ -31,6 +31,7 @@ FLOWS = [
"coronavirus",
"daikin",
"deconz",
"denonavr",
"devolo_home_control",
"dialogflow",
"directv",

View file

@ -17,6 +17,44 @@ SSDP = {
"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": [
{
"deviceType": "urn:schemas-upnp-org:device:MediaServer:1",

View file

@ -472,7 +472,7 @@ defusedxml==0.6.0
deluge-client==1.7.1
# homeassistant.components.denonavr
denonavr==0.8.1
denonavr==0.9.3
# homeassistant.components.devolo_home_control
devolo-home-control-api==0.11.0
@ -634,6 +634,7 @@ georss_ign_sismologia_client==0.2
# homeassistant.components.qld_bushfire
georss_qld_bushfire_alert_client==0.3
# homeassistant.components.denonavr
# homeassistant.components.huawei_lte
# homeassistant.components.kef
# homeassistant.components.minecraft_server

View file

@ -215,7 +215,7 @@ datapoint==0.9.5
defusedxml==0.6.0
# homeassistant.components.denonavr
denonavr==0.8.1
denonavr==0.9.3
# homeassistant.components.devolo_home_control
devolo-home-control-api==0.11.0
@ -281,6 +281,7 @@ georss_ign_sismologia_client==0.2
# homeassistant.components.qld_bushfire
georss_qld_bushfire_alert_client==0.3
# homeassistant.components.denonavr
# homeassistant.components.huawei_lte
# homeassistant.components.kef
# homeassistant.components.minecraft_server

View 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"

View file

@ -1,57 +1,92 @@
"""The tests for the denonavr media player platform."""
from unittest.mock import patch
import pytest
from homeassistant.components import media_player
from homeassistant.components.denonavr import ATTR_COMMAND, DOMAIN, SERVICE_GET_COMMAND
from homeassistant.const import ATTR_ENTITY_ID, CONF_HOST, CONF_NAME, CONF_PLATFORM
from homeassistant.setup import async_setup_component
from homeassistant.components.denonavr import ATTR_COMMAND, SERVICE_GET_COMMAND
from homeassistant.components.denonavr.config_flow import (
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"
ENTITY_ID = f"{media_player.DOMAIN}.{NAME}"
TEST_HOST = "1.2.3.4"
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")
def client_fixture():
"""Patch of client library for tests."""
with patch(
"homeassistant.components.denonavr.media_player.denonavr.DenonAVR",
autospec=True,
"homeassistant.components.denonavr.receiver.denonavr.DenonAVR", autospec=True,
) 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}
yield mock_client_class.return_value
async def setup_denonavr(hass):
"""Initialize webostv and media_player for tests."""
assert await async_setup_component(
hass,
media_player.DOMAIN,
{
media_player.DOMAIN: {
CONF_PLATFORM: "denonavr",
CONF_HOST: "fake",
CONF_NAME: NAME,
}
},
"""Initialize media_player for tests."""
entry_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,
}
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()
state = hass.states.get(ENTITY_ID)
assert state
assert state.name == TEST_NAME
async def test_get_command(hass, client):
"""Test generic command functionality."""
await setup_denonavr(hass)
data = {
ATTR_ENTITY_ID: ENTITY_ID,
ATTR_COMMAND: "test",
ATTR_COMMAND: "test_command",
}
await hass.services.async_call(DOMAIN, SERVICE_GET_COMMAND, data)
await hass.async_block_till_done()
client.send_get_command.assert_called_with("test")
client.send_get_command.assert_called_with("test_command")