Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
137 changes: 134 additions & 3 deletions denonavr/denonavr.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
# pylint: disable=too-many-lines
# pylint: disable=no-else-return

from collections import namedtuple
from collections import (namedtuple, OrderedDict)
from io import BytesIO
import logging
import time
Expand Down Expand Up @@ -37,6 +37,19 @@
"Media Server": "SERVER", "Spotify": "SPOTIFY",
"Flickr": "FLICKR", "Favorites": "FAVORITES"}

SOUND_MODE_MAPPING = OrderedDict(
[('MUSIC', ['PLII MUSIC', 'DTS NEO:6 MUSIC', 'DOLBY D +NEO:X M',
'ROCK ARENA', 'JAZZ CLUB', 'MATRIX']),
('MOVIE', ['PLII MOVIE', 'PLII CINEMA', 'DTS NEO:X CINEMA',
'DTS NEO:6 CINEMA', 'DOLBY D +NEO:X C', 'MONO MOVIE']),
('GAME', ['PLII GAME', 'DOLBY D +NEO:X G', 'VIDEO GAME']),
('AUTO', ['None']),
('VIRTUAL', ['VIRTUAL']),
('PURE DIRECT', ['DIRECT']),
('DOLBY DIGITAL', ['DOLBY DIGITAL', 'DOLBY D + DOLBY SURROUND']),
('MCH STEREO', ['MULTI CH STEREO', 'MULTI CH IN']),
('STEREO', ['STEREO'])])

PLAYING_SOURCES = ("Online Music", "Media Server", "iPod/USB", "Bluetooth",
"Internet Radio", "Favorites", "SpotifyConnect", "Flickr",
"TUNER", "NET/USB", "HDRADIO", "Music Server", "NETWORK",
Expand Down Expand Up @@ -70,6 +83,7 @@
COMMAND_SET_VOLUME_URL = "/goform/formiPhoneAppVolume.xml?1+%.1f"
COMMAND_MUTE_ON_URL = "/goform/formiPhoneAppMute.xml?1+MuteOn"
COMMAND_MUTE_OFF_URL = "/goform/formiPhoneAppMute.xml?1+MuteOff"
COMMAND_SEL_SM_URL = "/goform/formiPhoneAppDirect.xml?MS"

# Zone 2 URLs
STATUS_Z2_URL = "/goform/formZone2_Zone2XmlStatus.xml"
Expand Down Expand Up @@ -105,7 +119,7 @@
"command_power_standby", "command_volume_up",
"command_volume_down", "command_set_volume",
"command_mute_on", "command_mute_off",
"command_netaudio_post"])
"command_sel_sound_mode", "command_netaudio_post"])

DENONAVR_URLS = ReceiverURLs(appcommand=APPCOMMAND_URL,
status=STATUS_URL,
Expand All @@ -123,6 +137,7 @@
command_set_volume=COMMAND_SET_VOLUME_URL,
command_mute_on=COMMAND_MUTE_ON_URL,
command_mute_off=COMMAND_MUTE_OFF_URL,
command_sel_sound_mode=COMMAND_SEL_SM_URL,
command_netaudio_post=COMMAND_NETAUDIO_POST_URL)

ZONE2_URLS = ReceiverURLs(appcommand=APPCOMMAND_URL,
Expand All @@ -141,6 +156,7 @@
command_set_volume=COMMAND_SET_VOLUME_Z2_URL,
command_mute_on=COMMAND_MUTE_ON_Z2_URL,
command_mute_off=COMMAND_MUTE_OFF_Z2_URL,
command_sel_sound_mode=COMMAND_SEL_SM_URL,
command_netaudio_post=COMMAND_NETAUDIO_POST_URL)

ZONE3_URLS = ReceiverURLs(appcommand=APPCOMMAND_URL,
Expand All @@ -159,6 +175,7 @@
command_set_volume=COMMAND_SET_VOLUME_Z3_URL,
command_mute_on=COMMAND_MUTE_ON_Z3_URL,
command_mute_off=COMMAND_MUTE_OFF_Z3_URL,
command_sel_sound_mode=COMMAND_SEL_SM_URL,
command_netaudio_post=COMMAND_NETAUDIO_POST_URL)

POWER_ON = "ON"
Expand Down Expand Up @@ -217,6 +234,9 @@ def __init__(self, host, name=None, show_all_inputs=False, timeout=2.0,
self._input_func = None
self._input_func_list = {}
self._input_func_list_rev = {}
self._sound_mode_raw = None
self._sound_mode_dict = SOUND_MODE_MAPPING
self._sm_match_dict = self.construct_sm_match_dict()
self._netaudio_func_list = []
self._playing_func_list = []
self._favorite_func_list = []
Expand Down Expand Up @@ -370,7 +390,8 @@ def _update_avr(self):
# pylint: disable=too-many-branches,too-many-statements
# Set all tags to be evaluated
relevant_tags = {"Power": None, "InputFuncSelect": None, "Mute": None,
"MasterVolume": None}
"MasterVolume": None, "selectSurround": None,
"SurrMode": None}

# Get status XML from Denon receiver via HTTP
try:
Expand Down Expand Up @@ -1032,6 +1053,10 @@ def _get_status_from_xml_tags(self, root, relevant_tags):
elif child.tag == "FriendlyName" and self._name is None:
self._name = child[0].text
relevant_tags.pop(child.tag, None)
elif child.tag == "selectSurround" or child.tag == "SurrMode":
self._sound_mode_raw = child[0].text.rstrip()
relevant_tags.pop("selectSurround", None)
relevant_tags.pop("SurrMode", None)

return relevant_tags

Expand Down Expand Up @@ -1107,6 +1132,32 @@ def input_func_list(self):
"""Return a list of available input sources as string."""
return sorted(self._input_func_list.keys())

@property
def sound_mode(self):
"""Return the matched current sound mode as a string."""
sound_mode_matched = self.match_sound_mode(self._sound_mode_raw)
return sound_mode_matched

@property
def sound_mode_list(self):
"""Return a list of available sound modes as string."""
return self._sound_mode_dict.keys()

@property
def sound_mode_dict(self):
"""Return a dict of available sound modes with their mapping values."""
return self._sound_mode_dict

@property
def sm_match_dict(self):
"""Dict to map each sound_mode_raw to it's matched sound_mode."""
return self._sm_match_dict

@property
def sound_mode_raw(self):
"""Return the current sound mode as string as received from the AVR."""
return self._sound_mode_raw

@property
def image_url(self):
"""Return image URL of current playing media when powered on."""
Expand Down Expand Up @@ -1212,6 +1263,86 @@ def set_input_func(self, input_func):
input_func)
return False

@sound_mode.setter
def sound_mode(self, sound_mode):
"""Setter function for sound_mode to switch sound_mode of device."""
self.set_sound_mode(sound_mode)

def set_sound_mode(self, sound_mode):
"""
Set sound_mode of device.

Valid values depend on the device and should be taken from
"sound_mode_list".
Return "True" on success and "False" on fail.
"""
# For selection of sound mode other names then at receiving sound modes
# have to be used
# Therefore source mapping is needed to get sound_mode
# Create command URL and send command via HTTP GET
command_url = self._urls.command_sel_sound_mode + sound_mode
# sent command
try:
if self.send_get_command(command_url):
self._sound_mode_raw = self._sound_mode_dict[sound_mode][0]
return True
else:
return False
except requests.exceptions.RequestException:
_LOGGER.error("Connection error: sound mode function %s not set.",
sound_mode)
return False

def set_sound_mode_dict(self, sound_mode_dict):
"""set the matching dictionary used to match the raw sound mode."""
error_msg = ("Syntax of sound mode dictionary not valid, "
"use: OrderedDict([('COMMAND', ['VALUE1','VALUE2'])])")
if (isinstance(sound_mode_dict, OrderedDict) or
isinstance(sound_mode_dict, dict)):
mode_list = list(sound_mode_dict.values())
for sublist in mode_list:
if isinstance(sublist, list):
for element in sublist:
if not isinstance(element, str):
_LOGGER.error(error_msg)
return False
else:
_LOGGER.error(error_msg)
return False
else:
_LOGGER.error(error_msg)
return False
self._sound_mode_dict = sound_mode_dict
self._sm_match_dict = self.construct_sm_match_dict()
return True

def construct_sm_match_dict(self):
"""
Internal method to construct the sm_match_dict.

Reverse the key value structure. The sm_match_dict is bigger,
but allows for direct matching using a dictionary key access.
The sound_mode_dict is uses externally to set this dictionary
because that has a nicer syntax
"""
mode_dict = list(self._sound_mode_dict.items())
match_mode_dict = {}
for matched_mode, sublist in mode_dict:
for raw_mode in sublist:
match_mode_dict[raw_mode] = matched_mode
return match_mode_dict

def match_sound_mode(self, sound_mode_raw):
"""Match the raw_sound_mode to its corresponding sound_mode."""
try:
sound_mode = self._sm_match_dict[sound_mode_raw.upper()]
return sound_mode
except KeyError:
pass
_LOGGER.warning("Not able to match sound mode, "
"returning raw sound mode.")
return sound_mode_raw

def toggle_play_pause(self):
"""Toggle play pause media player."""
# Use Play/Pause button only for sources which support NETAUDIO
Expand Down