Add denonavr DynamicEQ and Audyssey service (#48694)

* denonavr: Add DynamicEQ and Audyssey service

* Remove debug print

* Syntax sugar

* Apply suggestions from code review

Co-authored-by: J. Nick Koston <nick@koston.org>

* Update homeassistant/components/denonavr/services.yaml

Co-authored-by: J. Nick Koston <nick@koston.org>

* Remove trailing whitespaces

Co-authored-by: J. Nick Koston <nick@koston.org>
This commit is contained in:
MarBra 2021-04-22 03:55:30 +02:00 committed by GitHub
parent 6a4f414236
commit 9003dbfdf3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 140 additions and 7 deletions

View File

@ -11,10 +11,12 @@ from homeassistant.helpers.httpx_client import get_async_client
from .config_flow import ( from .config_flow import (
CONF_SHOW_ALL_SOURCES, CONF_SHOW_ALL_SOURCES,
CONF_UPDATE_AUDYSSEY,
CONF_ZONE2, CONF_ZONE2,
CONF_ZONE3, CONF_ZONE3,
DEFAULT_SHOW_SOURCES, DEFAULT_SHOW_SOURCES,
DEFAULT_TIMEOUT, DEFAULT_TIMEOUT,
DEFAULT_UPDATE_AUDYSSEY,
DEFAULT_ZONE2, DEFAULT_ZONE2,
DEFAULT_ZONE3, DEFAULT_ZONE3,
DOMAIN, DOMAIN,
@ -53,6 +55,9 @@ async def async_setup_entry(
hass.data[DOMAIN][entry.entry_id] = { hass.data[DOMAIN][entry.entry_id] = {
CONF_RECEIVER: receiver, CONF_RECEIVER: receiver,
CONF_UPDATE_AUDYSSEY: entry.options.get(
CONF_UPDATE_AUDYSSEY, DEFAULT_UPDATE_AUDYSSEY
),
UNDO_UPDATE_LISTENER: undo_listener, UNDO_UPDATE_LISTENER: undo_listener,
} }

View File

@ -30,11 +30,13 @@ CONF_ZONE3 = "zone3"
CONF_MODEL = "model" CONF_MODEL = "model"
CONF_MANUFACTURER = "manufacturer" CONF_MANUFACTURER = "manufacturer"
CONF_SERIAL_NUMBER = "serial_number" CONF_SERIAL_NUMBER = "serial_number"
CONF_UPDATE_AUDYSSEY = "update_audyssey"
DEFAULT_SHOW_SOURCES = False DEFAULT_SHOW_SOURCES = False
DEFAULT_TIMEOUT = 5 DEFAULT_TIMEOUT = 5
DEFAULT_ZONE2 = False DEFAULT_ZONE2 = False
DEFAULT_ZONE3 = False DEFAULT_ZONE3 = False
DEFAULT_UPDATE_AUDYSSEY = False
CONFIG_SCHEMA = vol.Schema({vol.Optional(CONF_HOST): str}) CONFIG_SCHEMA = vol.Schema({vol.Optional(CONF_HOST): str})
@ -67,6 +69,12 @@ class OptionsFlowHandler(config_entries.OptionsFlow):
CONF_ZONE3, CONF_ZONE3,
default=self.config_entry.options.get(CONF_ZONE3, DEFAULT_ZONE3), default=self.config_entry.options.get(CONF_ZONE3, DEFAULT_ZONE3),
): bool, ): bool,
vol.Optional(
CONF_UPDATE_AUDYSSEY,
default=self.config_entry.options.get(
CONF_UPDATE_AUDYSSEY, DEFAULT_UPDATE_AUDYSSEY
),
): bool,
} }
) )

View File

@ -45,12 +45,15 @@ from .config_flow import (
CONF_MODEL, CONF_MODEL,
CONF_SERIAL_NUMBER, CONF_SERIAL_NUMBER,
CONF_TYPE, CONF_TYPE,
CONF_UPDATE_AUDYSSEY,
DEFAULT_UPDATE_AUDYSSEY,
DOMAIN, DOMAIN,
) )
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
ATTR_SOUND_MODE_RAW = "sound_mode_raw" ATTR_SOUND_MODE_RAW = "sound_mode_raw"
ATTR_DYNAMIC_EQ = "dynamic_eq"
SUPPORT_DENON = ( SUPPORT_DENON = (
SUPPORT_VOLUME_STEP SUPPORT_VOLUME_STEP
@ -75,6 +78,8 @@ PARALLEL_UPDATES = 1
# Services # Services
SERVICE_GET_COMMAND = "get_command" SERVICE_GET_COMMAND = "get_command"
SERVICE_SET_DYNAMIC_EQ = "set_dynamic_eq"
SERVICE_UPDATE_AUDYSSEY = "update_audyssey"
async def async_setup_entry( async def async_setup_entry(
@ -84,14 +89,23 @@ async def async_setup_entry(
): ):
"""Set up the DenonAVR receiver from a config entry.""" """Set up the DenonAVR receiver from a config entry."""
entities = [] entities = []
receiver = hass.data[DOMAIN][config_entry.entry_id][CONF_RECEIVER] data = hass.data[DOMAIN][config_entry.entry_id]
receiver = data[CONF_RECEIVER]
update_audyssey = data.get(CONF_UPDATE_AUDYSSEY, DEFAULT_UPDATE_AUDYSSEY)
for receiver_zone in receiver.zones.values(): for receiver_zone in receiver.zones.values():
if config_entry.data[CONF_SERIAL_NUMBER] is not None: if config_entry.data[CONF_SERIAL_NUMBER] is not None:
unique_id = f"{config_entry.unique_id}-{receiver_zone.zone}" unique_id = f"{config_entry.unique_id}-{receiver_zone.zone}"
else: else:
unique_id = f"{config_entry.entry_id}-{receiver_zone.zone}" unique_id = f"{config_entry.entry_id}-{receiver_zone.zone}"
await receiver_zone.async_setup() await receiver_zone.async_setup()
entities.append(DenonDevice(receiver_zone, unique_id, config_entry)) entities.append(
DenonDevice(
receiver_zone,
unique_id,
config_entry,
update_audyssey,
)
)
_LOGGER.debug( _LOGGER.debug(
"%s receiver at host %s initialized", receiver.manufacturer, receiver.host "%s receiver at host %s initialized", receiver.manufacturer, receiver.host
) )
@ -103,6 +117,16 @@ async def async_setup_entry(
{vol.Required(ATTR_COMMAND): cv.string}, {vol.Required(ATTR_COMMAND): cv.string},
f"async_{SERVICE_GET_COMMAND}", f"async_{SERVICE_GET_COMMAND}",
) )
platform.async_register_entity_service(
SERVICE_SET_DYNAMIC_EQ,
{vol.Required(ATTR_DYNAMIC_EQ): cv.boolean},
f"async_{SERVICE_SET_DYNAMIC_EQ}",
)
platform.async_register_entity_service(
SERVICE_UPDATE_AUDYSSEY,
{},
f"async_{SERVICE_UPDATE_AUDYSSEY}",
)
async_add_entities(entities, update_before_add=True) async_add_entities(entities, update_before_add=True)
@ -115,11 +139,13 @@ class DenonDevice(MediaPlayerEntity):
receiver: DenonAVR, receiver: DenonAVR,
unique_id: str, unique_id: str,
config_entry: config_entries.ConfigEntry, config_entry: config_entries.ConfigEntry,
update_audyssey: bool,
): ):
"""Initialize the device.""" """Initialize the device."""
self._receiver = receiver self._receiver = receiver
self._unique_id = unique_id self._unique_id = unique_id
self._config_entry = config_entry self._config_entry = config_entry
self._update_audyssey = update_audyssey
self._supported_features_base = SUPPORT_DENON self._supported_features_base = SUPPORT_DENON
self._supported_features_base |= ( self._supported_features_base |= (
@ -194,6 +220,8 @@ class DenonDevice(MediaPlayerEntity):
async def async_update(self) -> None: async def async_update(self) -> None:
"""Get the latest status information from device.""" """Get the latest status information from device."""
await self._receiver.async_update() await self._receiver.async_update()
if self._update_audyssey:
await self._receiver.async_update_audyssey()
@property @property
def available(self): def available(self):
@ -350,13 +378,22 @@ class DenonDevice(MediaPlayerEntity):
@property @property
def extra_state_attributes(self): def extra_state_attributes(self):
"""Return device specific state attributes.""" """Return device specific state attributes."""
if self._receiver.power != POWER_ON:
return {}
state_attributes = {}
if ( if (
self._receiver.sound_mode_raw is not None self._receiver.sound_mode_raw is not None
and self._receiver.support_sound_mode and self._receiver.support_sound_mode
and self._receiver.power == POWER_ON
): ):
return {ATTR_SOUND_MODE_RAW: self._receiver.sound_mode_raw} state_attributes[ATTR_SOUND_MODE_RAW] = self._receiver.sound_mode_raw
return {} if self._receiver.dynamic_eq is not None:
state_attributes[ATTR_DYNAMIC_EQ] = self._receiver.dynamic_eq
return state_attributes
@property
def dynamic_eq(self):
"""Status of DynamicEQ."""
return self._receiver.dynamic_eq
@async_log_errors @async_log_errors
async def async_media_play_pause(self): async def async_media_play_pause(self):
@ -436,6 +473,23 @@ class DenonDevice(MediaPlayerEntity):
"""Send generic command.""" """Send generic command."""
return await self._receiver.async_get_command(command) return await self._receiver.async_get_command(command)
@async_log_errors
async def async_update_audyssey(self):
"""Get the latest audyssey information from device."""
await self._receiver.async_update_audyssey()
@async_log_errors
async def async_set_dynamic_eq(self, dynamic_eq: bool):
"""Turn DynamicEQ on or off."""
if dynamic_eq:
result = await self._receiver.async_dynamic_eq_on()
else:
result = await self._receiver.async_dynamic_eq_off()
if self._update_audyssey:
await self._receiver.async_update_audyssey()
return result
# Decorator defined before is a staticmethod # Decorator defined before is a staticmethod
async_log_errors = staticmethod( # pylint: disable=no-staticmethod-decorator async_log_errors = staticmethod( # pylint: disable=no-staticmethod-decorator
async_log_errors async_log_errors

View File

@ -9,3 +9,22 @@ get_command:
command: command:
description: Endpoint of the command, including associated parameters. description: Endpoint of the command, including associated parameters.
example: "/goform/formiPhoneAppDirect.xml?RCKSK0410370" example: "/goform/formiPhoneAppDirect.xml?RCKSK0410370"
set_dynamic_eq:
description: "Enable or disable DynamicEQ."
target:
entity:
integration: denonavr
domain: media_player
fields:
dynamic_eq:
description: "True/false for enable/disable."
default: true
example: true
selector:
boolean:
update_audyssey:
description: "Update Audyssey settings."
target:
entity:
integration: denonavr
domain: media_player

View File

@ -40,7 +40,8 @@
"data": { "data": {
"show_all_sources": "Show all sources", "show_all_sources": "Show all sources",
"zone2": "Set up Zone 2", "zone2": "Set up Zone 2",
"zone3": "Set up Zone 3" "zone3": "Set up Zone 3",
"update_audyssey": "Update Audyssey settings"
} }
} }
} }

View File

@ -38,7 +38,8 @@
"data": { "data": {
"show_all_sources": "Show all sources", "show_all_sources": "Show all sources",
"zone2": "Set up Zone 2", "zone2": "Set up Zone 2",
"zone3": "Set up Zone 3" "zone3": "Set up Zone 3",
"update_audyssey": "Update Audyssey settings"
}, },
"description": "Specify optional settings", "description": "Specify optional settings",
"title": "Denon AVR Network Receivers" "title": "Denon AVR Network Receivers"

View File

@ -11,6 +11,7 @@ from homeassistant.components.denonavr.config_flow import (
CONF_SERIAL_NUMBER, CONF_SERIAL_NUMBER,
CONF_SHOW_ALL_SOURCES, CONF_SHOW_ALL_SOURCES,
CONF_TYPE, CONF_TYPE,
CONF_UPDATE_AUDYSSEY,
CONF_ZONE2, CONF_ZONE2,
CONF_ZONE3, CONF_ZONE3,
DOMAIN, DOMAIN,
@ -28,6 +29,7 @@ TEST_IGNORED_MODEL = "HEOS 7"
TEST_RECEIVER_TYPE = "avr-x" TEST_RECEIVER_TYPE = "avr-x"
TEST_SERIALNUMBER = "123456789" TEST_SERIALNUMBER = "123456789"
TEST_MANUFACTURER = "Denon" TEST_MANUFACTURER = "Denon"
TEST_UPDATE_AUDYSSEY = False
TEST_SSDP_LOCATION = f"http://{TEST_HOST}/" TEST_SSDP_LOCATION = f"http://{TEST_HOST}/"
TEST_UNIQUE_ID = f"{TEST_MODEL}-{TEST_SERIALNUMBER}" TEST_UNIQUE_ID = f"{TEST_MODEL}-{TEST_SERIALNUMBER}"
TEST_DISCOVER_1_RECEIVER = [{CONF_HOST: TEST_HOST}] TEST_DISCOVER_1_RECEIVER = [{CONF_HOST: TEST_HOST}]
@ -397,6 +399,7 @@ async def test_options_flow(hass):
CONF_TYPE: TEST_RECEIVER_TYPE, CONF_TYPE: TEST_RECEIVER_TYPE,
CONF_MANUFACTURER: TEST_MANUFACTURER, CONF_MANUFACTURER: TEST_MANUFACTURER,
CONF_SERIAL_NUMBER: TEST_SERIALNUMBER, CONF_SERIAL_NUMBER: TEST_SERIALNUMBER,
CONF_UPDATE_AUDYSSEY: TEST_UPDATE_AUDYSSEY,
}, },
title=TEST_NAME, title=TEST_NAME,
) )
@ -420,6 +423,7 @@ async def test_options_flow(hass):
CONF_SHOW_ALL_SOURCES: True, CONF_SHOW_ALL_SOURCES: True,
CONF_ZONE2: True, CONF_ZONE2: True,
CONF_ZONE3: True, CONF_ZONE3: True,
CONF_UPDATE_AUDYSSEY: False,
} }

View File

@ -13,7 +13,10 @@ from homeassistant.components.denonavr.config_flow import (
) )
from homeassistant.components.denonavr.media_player import ( from homeassistant.components.denonavr.media_player import (
ATTR_COMMAND, ATTR_COMMAND,
ATTR_DYNAMIC_EQ,
SERVICE_GET_COMMAND, SERVICE_GET_COMMAND,
SERVICE_SET_DYNAMIC_EQ,
SERVICE_UPDATE_AUDYSSEY,
) )
from homeassistant.const import ATTR_ENTITY_ID, CONF_HOST from homeassistant.const import ATTR_ENTITY_ID, CONF_HOST
@ -94,3 +97,41 @@ async def test_get_command(hass, client):
await hass.async_block_till_done() await hass.async_block_till_done()
client.async_get_command.assert_awaited_with("test_command") client.async_get_command.assert_awaited_with("test_command")
async def test_dynamic_eq(hass, client):
"""Test that dynamic eq method works."""
await setup_denonavr(hass)
data = {
ATTR_ENTITY_ID: ENTITY_ID,
ATTR_DYNAMIC_EQ: True,
}
# Verify on call
await hass.services.async_call(DOMAIN, SERVICE_SET_DYNAMIC_EQ, data)
await hass.async_block_till_done()
# Verify off call
data[ATTR_DYNAMIC_EQ] = False
await hass.services.async_call(DOMAIN, SERVICE_SET_DYNAMIC_EQ, data)
await hass.async_block_till_done()
client.async_dynamic_eq_on.assert_called_once()
client.async_dynamic_eq_off.assert_called_once()
async def test_update_audyssey(hass, client):
"""Test that dynamic eq method works."""
await setup_denonavr(hass)
# Verify call
await hass.services.async_call(
DOMAIN,
SERVICE_UPDATE_AUDYSSEY,
{
ATTR_ENTITY_ID: ENTITY_ID,
},
)
await hass.async_block_till_done()
client.async_update_audyssey.assert_called_once()