From 062db8a37db37d9d815c1feca85b89a1526de2e8 Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Thu, 5 Apr 2018 21:14:36 +0200 Subject: [PATCH 1/9] Add sound mode support add sound mode support using new denonavr library functions --- .../components/media_player/denonavr.py | 71 ++++++++++++++++--- 1 file changed, 61 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/media_player/denonavr.py b/homeassistant/components/media_player/denonavr.py index fe8fc46c24b26..9d8d00ccee623 100644 --- a/homeassistant/components/media_player/denonavr.py +++ b/homeassistant/components/media_player/denonavr.py @@ -6,29 +6,32 @@ """ import logging -from collections import namedtuple +from collections import (namedtuple, OrderedDict) import voluptuous as vol from homeassistant.components.media_player import ( SUPPORT_PAUSE, SUPPORT_NEXT_TRACK, SUPPORT_PREVIOUS_TRACK, SUPPORT_TURN_OFF, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_STEP, - SUPPORT_SELECT_SOURCE, SUPPORT_PLAY_MEDIA, MEDIA_TYPE_CHANNEL, - MediaPlayerDevice, PLATFORM_SCHEMA, SUPPORT_TURN_ON, - MEDIA_TYPE_MUSIC, SUPPORT_VOLUME_SET, SUPPORT_PLAY) + SUPPORT_SELECT_SOURCE, SUPPORT_SELECT_SOUND_MODE, + SUPPORT_PLAY_MEDIA, MEDIA_TYPE_CHANNEL, MediaPlayerDevice, + PLATFORM_SCHEMA, SUPPORT_TURN_ON, MEDIA_TYPE_MUSIC, + SUPPORT_VOLUME_SET, SUPPORT_PLAY) from homeassistant.const import ( CONF_HOST, STATE_OFF, STATE_PLAYING, STATE_PAUSED, CONF_NAME, STATE_ON, CONF_ZONE, CONF_TIMEOUT) import homeassistant.helpers.config_validation as cv -REQUIREMENTS = ['denonavr==0.6.1'] +REQUIREMENTS = ['denonavr==0.7.0'] _LOGGER = logging.getLogger(__name__) -DEFAULT_NAME = None DEFAULT_SHOW_SOURCES = False DEFAULT_TIMEOUT = 2 +DEFAULT_SOUND_MODE = True CONF_SHOW_ALL_SOURCES = 'show_all_sources' CONF_ZONES = 'zones' +CONF_SOUND_MODE = 'sound_mode' +CONF_SOUND_MODE_DICT = 'sound_mode_dict' CONF_VALID_ZONES = ['Zone2', 'Zone3'] CONF_INVALID_ZONES_ERR = 'Invalid Zone (expected Zone2 or Zone3)' KEY_DENON_CACHE = 'denonavr_hosts' @@ -49,6 +52,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Optional(CONF_HOST): cv.string, vol.Optional(CONF_NAME): cv.string, + vol.Optional(CONF_SOUND_MODE, default=DEFAULT_SOUND_MODE): cv.boolean, + vol.Optional(CONF_SOUND_MODE_DICT): vol.Schema({str: list}), vol.Optional(CONF_SHOW_ALL_SOURCES, default=DEFAULT_SHOW_SOURCES): cv.boolean, vol.Optional(CONF_ZONES): @@ -84,6 +89,10 @@ def setup_platform(hass, config, add_devices, discovery_info=None): else: add_zones = None + # Get config option for sound mode + sound_mode_support = config.get(CONF_SOUND_MODE) + sound_mode_dict = config.get(CONF_SOUND_MODE_DICT) + # Start assignment of host and name new_hosts = [] # 1. option: manual setting @@ -117,7 +126,9 @@ def setup_platform(hass, config, add_devices, discovery_info=None): 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)) + receivers.append(DenonDevice(new_zone, + sound_mode_support, + sound_mode_dict)) cache.add(host) _LOGGER.info("Denon receiver at host %s initialized", host) @@ -129,7 +140,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): class DenonDevice(MediaPlayerDevice): """Representation of a Denon Media Player Device.""" - def __init__(self, receiver): + def __init__(self, receiver, sound_mode_support, sound_mode_dict): """Initialize the device.""" self._receiver = receiver self._name = self._receiver.name @@ -147,6 +158,24 @@ def __init__(self, receiver): self._frequency = self._receiver.frequency self._station = self._receiver.station + self._sound_mode_support = sound_mode_support + if sound_mode_support: + self._sound_mode = self._receiver.sound_mode + self._sound_mode_raw = self._receiver.sound_mode_raw + if sound_mode_dict is None: + self._sound_mode_list = self._receiver.sound_mode_list + else: + self._receiver.set_sound_mode_dict(sound_mode_dict) + self._sound_mode_list = list(sound_mode_dict) + else: + self._sound_mode = None + self._sound_mode_raw = None + self._sound_mode_list = None + + self._supported_features_base = SUPPORT_DENON + self._supported_features_base |= (sound_mode_support and + SUPPORT_SELECT_SOUND_MODE) + def update(self): """Get the latest status information from device.""" self._receiver.update() @@ -164,6 +193,9 @@ def update(self): self._band = self._receiver.band self._frequency = self._receiver.frequency self._station = self._receiver.station + if self._sound_mode_support: + self._sound_mode = self._receiver.sound_mode + self._sound_mode_raw = self._receiver.sound_mode_raw @property def name(self): @@ -197,12 +229,27 @@ def source_list(self): """Return a list of available input sources.""" return self._source_list + @property + def sound_mode(self): + """Return the current matched sound mode.""" + return self._sound_mode + + @property + def sound_mode_raw(self): + """Return the current raw sound mode.""" + return self._sound_mode_raw + + @property + def sound_mode_list(self): + """Return a list of available sound modes.""" + return self._sound_mode_list + @property def supported_features(self): """Flag media player features that are supported.""" if self._current_source in self._receiver.netaudio_func_list: - return SUPPORT_DENON | SUPPORT_MEDIA_MODES - return SUPPORT_DENON + return self._supported_features_base | SUPPORT_MEDIA_MODES + return self._supported_features_base @property def media_content_id(self): @@ -292,6 +339,10 @@ def select_source(self, source): """Select input source.""" return self._receiver.set_input_func(source) + def select_sound_mode(self, sound_mode): + """Select sound mode.""" + return self._receiver.set_sound_mode(sound_mode) + def turn_on(self): """Turn on media player.""" if self._receiver.power_on(): From 69440e25302952d4e8289da68cda7a514ebcdc55 Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Thu, 5 Apr 2018 21:17:25 +0200 Subject: [PATCH 2/9] Denonavr to v.0.7.0 --- requirements_all.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_all.txt b/requirements_all.txt index 9a0613211da91..6c2b9f1878c0d 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -239,7 +239,7 @@ defusedxml==0.5.0 deluge-client==1.0.5 # homeassistant.components.media_player.denonavr -denonavr==0.6.1 +denonavr==0.7.0 # homeassistant.components.media_player.directv directpy==0.2 From 8dab888dbe2ce351216db25a33c9c08b0438b4b7 Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Thu, 5 Apr 2018 21:27:25 +0200 Subject: [PATCH 3/9] Add sound mode support --- .../components/media_player/__init__.py | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/homeassistant/components/media_player/__init__.py b/homeassistant/components/media_player/__init__.py index 615c758cd1a73..50cb9535877fe 100644 --- a/homeassistant/components/media_player/__init__.py +++ b/homeassistant/components/media_player/__init__.py @@ -54,6 +54,7 @@ SERVICE_PLAY_MEDIA = 'play_media' SERVICE_SELECT_SOURCE = 'select_source' +SERVICE_SELECT_SOUND_MODE = 'select_sound_mode' SERVICE_CLEAR_PLAYLIST = 'clear_playlist' ATTR_MEDIA_VOLUME_LEVEL = 'volume_level' @@ -78,6 +79,8 @@ ATTR_APP_NAME = 'app_name' ATTR_INPUT_SOURCE = 'source' ATTR_INPUT_SOURCE_LIST = 'source_list' +ATTR_SOUND_MODE = 'sound_mode' +ATTR_SOUND_MODE_LIST = 'sound_mode_list' ATTR_MEDIA_ENQUEUE = 'enqueue' ATTR_MEDIA_SHUFFLE = 'shuffle' @@ -106,6 +109,7 @@ SUPPORT_CLEAR_PLAYLIST = 8192 SUPPORT_PLAY = 16384 SUPPORT_SHUFFLE_SET = 32768 +SUPPORT_SELECT_SOUND_MODE = 65536 # Service call validation schemas MEDIA_PLAYER_SCHEMA = vol.Schema({ @@ -129,6 +133,10 @@ vol.Required(ATTR_INPUT_SOURCE): cv.string, }) +MEDIA_PLAYER_SELECT_SOUND_MODE_SCHEMA = MEDIA_PLAYER_SCHEMA.extend({ + vol.Required(ATTR_SOUND_MODE): cv.string, +}) + MEDIA_PLAYER_PLAY_MEDIA_SCHEMA = MEDIA_PLAYER_SCHEMA.extend({ vol.Required(ATTR_MEDIA_CONTENT_TYPE): cv.string, vol.Required(ATTR_MEDIA_CONTENT_ID): cv.string, @@ -164,6 +172,9 @@ SERVICE_SELECT_SOURCE: { 'method': 'async_select_source', 'schema': MEDIA_PLAYER_SELECT_SOURCE_SCHEMA}, + SERVICE_SELECT_SOUND_MODE: { + 'method': 'async_select_sound_mode', + 'schema': MEDIA_PLAYER_SELECT_SOUND_MODE_SCHEMA}, SERVICE_PLAY_MEDIA: { 'method': 'async_play_media', 'schema': MEDIA_PLAYER_PLAY_MEDIA_SCHEMA}, @@ -194,6 +205,8 @@ ATTR_APP_NAME, ATTR_INPUT_SOURCE, ATTR_INPUT_SOURCE_LIST, + ATTR_SOUND_MODE, + ATTR_SOUND_MODE_LIST, ATTR_MEDIA_SHUFFLE, ] @@ -343,6 +356,17 @@ def select_source(hass, source, entity_id=None): hass.services.call(DOMAIN, SERVICE_SELECT_SOURCE, data) +@bind_hass +def select_sound_mode(hass, sound_mode, entity_id=None): + """Send the media player the command to select sound mode.""" + data = {ATTR_SOUND_MODE: sound_mode} + + if entity_id: + data[ATTR_ENTITY_ID] = entity_id + + hass.services.call(DOMAIN, SERVICE_SELECT_SOUND_MODE, data) + + @bind_hass def clear_playlist(hass, entity_id=None): """Send the media player the command for clear playlist.""" @@ -387,6 +411,8 @@ def async_service_handler(service): params['position'] = service.data.get(ATTR_MEDIA_SEEK_POSITION) elif service.service == SERVICE_SELECT_SOURCE: params['source'] = service.data.get(ATTR_INPUT_SOURCE) + elif service.service == SERVICE_SELECT_SOUND_MODE: + params['sound_mode'] = service.data.get(ATTR_SOUND_MODE) elif service.service == SERVICE_PLAY_MEDIA: params['media_type'] = \ service.data.get(ATTR_MEDIA_CONTENT_TYPE) @@ -569,6 +595,16 @@ def source_list(self): """List of available input sources.""" return None + @property + def sound_mode(self): + """Name of the current sound mode.""" + return None + + @property + def sound_mode_list(self): + """List of available sound modes.""" + return None + @property def shuffle(self): """Boolean if shuffle is enabled.""" @@ -712,6 +748,17 @@ def async_select_source(self, source): """ return self.hass.async_add_job(self.select_source, source) + def select_sound_mode(self, sound_mode): + """Select sound mode.""" + raise NotImplementedError() + + def async_select_sound_mode(self, sound_mode): + """Select sound mode. + + This method must be run in the event loop and returns a coroutine. + """ + return self.hass.async_add_job(self.select_sound_mode, sound_mode) + def clear_playlist(self): """Clear players playlist.""" raise NotImplementedError() @@ -785,6 +832,11 @@ def support_select_source(self): """Boolean if select source command supported.""" return bool(self.supported_features & SUPPORT_SELECT_SOURCE) + @property + def support_select_sound_mode(self): + """Boolean if select sound mode command supported.""" + return bool(self.supported_features & SUPPORT_SELECT_SOUND_MODE) + @property def support_clear_playlist(self): """Boolean if clear playlist command supported.""" From 142a71a77c4d44acc61dacb652d03581492fe881 Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Thu, 5 Apr 2018 21:34:58 +0200 Subject: [PATCH 4/9] Add sound mode support --- homeassistant/components/media_player/services.yaml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/homeassistant/components/media_player/services.yaml b/homeassistant/components/media_player/services.yaml index 95072f0270c22..3739a9a35c82b 100644 --- a/homeassistant/components/media_player/services.yaml +++ b/homeassistant/components/media_player/services.yaml @@ -144,6 +144,16 @@ select_source: description: Name of the source to switch to. Platform dependent. example: 'video1' +select_sound_mode: + description: Send the media player the command to change sound mode. + fields: + entity_id: + description: Name(s) of entities to change sound mode on. + example: 'media_player.marantz' + sound_mode: + description: Name of the sound mode to switch to. + example: 'Music' + clear_playlist: description: Send the media player the command to clear players playlist. fields: From c7df1bb998f0a9d01f1139298f9930c65329bb40 Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Thu, 5 Apr 2018 21:44:21 +0200 Subject: [PATCH 5/9] Add sound mode support --- homeassistant/components/media_player/denonavr.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/media_player/denonavr.py b/homeassistant/components/media_player/denonavr.py index 9d8d00ccee623..3bce1e215fdd1 100644 --- a/homeassistant/components/media_player/denonavr.py +++ b/homeassistant/components/media_player/denonavr.py @@ -6,7 +6,7 @@ """ import logging -from collections import (namedtuple, OrderedDict) +from collections import namedtuple import voluptuous as vol from homeassistant.components.media_player import ( From a5ae281af6f6d8f9b0b8b0f7454ef3eaaab9a9da Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Thu, 3 May 2018 11:17:43 +0200 Subject: [PATCH 6/9] Added sound_mode_raw as a state attribute This is usefull because the sound_mode_raw shows more information than only the sound_mode. --- homeassistant/components/media_player/denonavr.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/homeassistant/components/media_player/denonavr.py b/homeassistant/components/media_player/denonavr.py index 3bce1e215fdd1..646194008428b 100644 --- a/homeassistant/components/media_player/denonavr.py +++ b/homeassistant/components/media_player/denonavr.py @@ -36,6 +36,8 @@ CONF_INVALID_ZONES_ERR = 'Invalid Zone (expected Zone2 or Zone3)' KEY_DENON_CACHE = 'denonavr_hosts' +ATTR_SOUND_MODE_RAW = 'sound_mode_raw' + SUPPORT_DENON = SUPPORT_VOLUME_STEP | SUPPORT_VOLUME_MUTE | \ SUPPORT_TURN_ON | SUPPORT_TURN_OFF | \ SUPPORT_SELECT_SOURCE | SUPPORT_VOLUME_SET @@ -323,6 +325,14 @@ def media_episode(self): """Episode of current playing media, TV show only.""" return None + @property + def device_state_attributes(self): + """Return device specific state attributes.""" + attributes = {} + if self._sound_mode_raw is not None and self._sound_mode_support: + attributes[ATTR_SOUND_MODE_RAW] = self._sound_mode_raw + return attributes + def media_play_pause(self): """Simulate play pause media player.""" return self._receiver.toggle_play_pause() From 90924ace02ee82cd943a035bbc900c40f17426b6 Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Thu, 3 May 2018 17:45:47 +0200 Subject: [PATCH 7/9] update sound mode raw property --- homeassistant/components/media_player/denonavr.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/media_player/denonavr.py b/homeassistant/components/media_player/denonavr.py index 646194008428b..0d3c43e85d833 100644 --- a/homeassistant/components/media_player/denonavr.py +++ b/homeassistant/components/media_player/denonavr.py @@ -329,7 +329,8 @@ def media_episode(self): def device_state_attributes(self): """Return device specific state attributes.""" attributes = {} - if self._sound_mode_raw is not None and self._sound_mode_support: + if self._sound_mode_raw is not None and self._sound_mode_support\ + and self._power == 'ON': attributes[ATTR_SOUND_MODE_RAW] = self._sound_mode_raw return attributes From e4da0d456033fa9361f0eb45fd74e773be3a414b Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Thu, 3 May 2018 17:47:18 +0200 Subject: [PATCH 8/9] spacing --- homeassistant/components/media_player/denonavr.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/media_player/denonavr.py b/homeassistant/components/media_player/denonavr.py index 0d3c43e85d833..c3bdce263e732 100644 --- a/homeassistant/components/media_player/denonavr.py +++ b/homeassistant/components/media_player/denonavr.py @@ -330,7 +330,7 @@ def device_state_attributes(self): """Return device specific state attributes.""" attributes = {} if self._sound_mode_raw is not None and self._sound_mode_support\ - and self._power == 'ON': + and self._power == 'ON': attributes[ATTR_SOUND_MODE_RAW] = self._sound_mode_raw return attributes From 29659cb67a3536a6d937eb4162710bd198629435 Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Thu, 3 May 2018 17:48:13 +0200 Subject: [PATCH 9/9] spacing --- homeassistant/components/media_player/denonavr.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/media_player/denonavr.py b/homeassistant/components/media_player/denonavr.py index c3bdce263e732..c253c5cc1a3e2 100644 --- a/homeassistant/components/media_player/denonavr.py +++ b/homeassistant/components/media_player/denonavr.py @@ -330,7 +330,7 @@ def device_state_attributes(self): """Return device specific state attributes.""" attributes = {} if self._sound_mode_raw is not None and self._sound_mode_support\ - and self._power == 'ON': + and self._power == 'ON': attributes[ATTR_SOUND_MODE_RAW] = self._sound_mode_raw return attributes