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 (
CONF_SHOW_ALL_SOURCES,
CONF_UPDATE_AUDYSSEY,
CONF_ZONE2,
CONF_ZONE3,
DEFAULT_SHOW_SOURCES,
DEFAULT_TIMEOUT,
DEFAULT_UPDATE_AUDYSSEY,
DEFAULT_ZONE2,
DEFAULT_ZONE3,
DOMAIN,
@ -53,6 +55,9 @@ async def async_setup_entry(
hass.data[DOMAIN][entry.entry_id] = {
CONF_RECEIVER: receiver,
CONF_UPDATE_AUDYSSEY: entry.options.get(
CONF_UPDATE_AUDYSSEY, DEFAULT_UPDATE_AUDYSSEY
),
UNDO_UPDATE_LISTENER: undo_listener,
}

View file

@ -30,11 +30,13 @@ CONF_ZONE3 = "zone3"
CONF_MODEL = "model"
CONF_MANUFACTURER = "manufacturer"
CONF_SERIAL_NUMBER = "serial_number"
CONF_UPDATE_AUDYSSEY = "update_audyssey"
DEFAULT_SHOW_SOURCES = False
DEFAULT_TIMEOUT = 5
DEFAULT_ZONE2 = False
DEFAULT_ZONE3 = False
DEFAULT_UPDATE_AUDYSSEY = False
CONFIG_SCHEMA = vol.Schema({vol.Optional(CONF_HOST): str})
@ -67,6 +69,12 @@ class OptionsFlowHandler(config_entries.OptionsFlow):
CONF_ZONE3,
default=self.config_entry.options.get(CONF_ZONE3, DEFAULT_ZONE3),
): 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_SERIAL_NUMBER,
CONF_TYPE,
CONF_UPDATE_AUDYSSEY,
DEFAULT_UPDATE_AUDYSSEY,
DOMAIN,
)
_LOGGER = logging.getLogger(__name__)
ATTR_SOUND_MODE_RAW = "sound_mode_raw"
ATTR_DYNAMIC_EQ = "dynamic_eq"
SUPPORT_DENON = (
SUPPORT_VOLUME_STEP
@ -75,6 +78,8 @@ PARALLEL_UPDATES = 1
# Services
SERVICE_GET_COMMAND = "get_command"
SERVICE_SET_DYNAMIC_EQ = "set_dynamic_eq"
SERVICE_UPDATE_AUDYSSEY = "update_audyssey"
async def async_setup_entry(
@ -84,14 +89,23 @@ async def async_setup_entry(
):
"""Set up the DenonAVR receiver from a config entry."""
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():
if config_entry.data[CONF_SERIAL_NUMBER] is not None:
unique_id = f"{config_entry.unique_id}-{receiver_zone.zone}"
else:
unique_id = f"{config_entry.entry_id}-{receiver_zone.zone}"
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(
"%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},
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)
@ -115,11 +139,13 @@ class DenonDevice(MediaPlayerEntity):
receiver: DenonAVR,
unique_id: str,
config_entry: config_entries.ConfigEntry,
update_audyssey: bool,
):
"""Initialize the device."""
self._receiver = receiver
self._unique_id = unique_id
self._config_entry = config_entry
self._update_audyssey = update_audyssey
self._supported_features_base = SUPPORT_DENON
self._supported_features_base |= (
@ -194,6 +220,8 @@ class DenonDevice(MediaPlayerEntity):
async def async_update(self) -> None:
"""Get the latest status information from device."""
await self._receiver.async_update()
if self._update_audyssey:
await self._receiver.async_update_audyssey()
@property
def available(self):
@ -350,13 +378,22 @@ class DenonDevice(MediaPlayerEntity):
@property
def extra_state_attributes(self):
"""Return device specific state attributes."""
if self._receiver.power != POWER_ON:
return {}
state_attributes = {}
if (
self._receiver.sound_mode_raw is not None
and self._receiver.support_sound_mode
and self._receiver.power == POWER_ON
):
return {ATTR_SOUND_MODE_RAW: self._receiver.sound_mode_raw}
return {}
state_attributes[ATTR_SOUND_MODE_RAW] = self._receiver.sound_mode_raw
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 def async_media_play_pause(self):
@ -436,6 +473,23 @@ class DenonDevice(MediaPlayerEntity):
"""Send generic 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
async_log_errors = staticmethod( # pylint: disable=no-staticmethod-decorator
async_log_errors

View file

@ -9,3 +9,22 @@ get_command:
command:
description: Endpoint of the command, including associated parameters.
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": {
"show_all_sources": "Show all sources",
"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": {
"show_all_sources": "Show all sources",
"zone2": "Set up Zone 2",
"zone3": "Set up Zone 3"
"zone3": "Set up Zone 3",
"update_audyssey": "Update Audyssey settings"
},
"description": "Specify optional settings",
"title": "Denon AVR Network Receivers"

View file

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