-
Notifications
You must be signed in to change notification settings - Fork 81
Added sound mode support #43
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
bf52811
50b891d
0c26783
302cb9f
8349bda
ca63f87
7d5cf51
3cfd304
11c81f0
489ebfd
57f779d
c561221
d9ba123
387d9d6
ed4fba1
0b88140
0498541
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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 | ||
|
|
@@ -37,6 +37,15 @@ | |
| "Media Server": "SERVER", "Spotify": "SPOTIFY", | ||
| "Flickr": "FLICKR", "Favorites": "FAVORITES"} | ||
|
|
||
| SOUND_MODE_MAPPING = OrderedDict([('MUSIC', ['PLII MUSIC']), | ||
| ('MOVIE', ['PLII MOVIE']), | ||
| ('GAME', ['PLII GAME']), | ||
| ('PURE DIRECT', ['DIRECT']), | ||
| ('AUTO', ['None']), | ||
| ('DOLBY DIGITAL', ['DOLBY DIGITAL']), | ||
| ('MCH STEREO', ['MULTI CH STEREO']), | ||
| ('STEREO', ['STEREO'])]) | ||
|
|
||
| PLAYING_SOURCES = ("Online Music", "Media Server", "iPod/USB", "Bluetooth", | ||
| "Internet Radio", "Favorites", "SpotifyConnect", "Flickr", | ||
| "TUNER", "NET/USB", "HDRADIO", "Music Server", "NETWORK", | ||
|
|
@@ -70,6 +79,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" | ||
|
|
@@ -105,7 +115,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, | ||
|
|
@@ -123,6 +133,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, | ||
|
|
@@ -141,6 +152,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, | ||
|
|
@@ -159,6 +171,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" | ||
|
|
@@ -217,6 +230,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 = None | ||
| self._sound_mode_raw = None | ||
| self._sound_mode_dict = SOUND_MODE_MAPPING | ||
| self._netaudio_func_list = [] | ||
| self._playing_func_list = [] | ||
| self._favorite_func_list = [] | ||
|
|
@@ -370,7 +386,7 @@ 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: | ||
|
|
@@ -1032,6 +1048,14 @@ 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"): | ||
| sound_mode_raw = child[0].text.rstrip() | ||
| self._sound_mode = self.match_sound_mode(sound_mode_raw) | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you move the
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. just did it. |
||
| self._sound_mode_raw = sound_mode_raw | ||
| if "selectSurround" in relevant_tags.keys(): | ||
| relevant_tags.pop("selectSurround", None) | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you pop both keys from the list, when the first one was found?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am poping both keys when the first one is found.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Therefore I think this part of the code is correct, and a second HTTP request is not possible unless none of the two keys ("selectSurround" and "SurrMode") are found.
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ah ok, I was confused by the if statement. |
||
| if "SurrMode" in relevant_tags.keys(): | ||
| relevant_tags.pop("SurrMode", None) | ||
|
|
||
| return relevant_tags | ||
|
|
||
|
|
@@ -1107,6 +1131,26 @@ 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.""" | ||
| return self._sound_mode | ||
|
|
||
| @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 list of available sound modes as string.""" | ||
| return self._sound_mode_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.""" | ||
|
|
@@ -1212,6 +1256,69 @@ 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 = sound_mode | ||
| 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): | ||
| Error_msg = "Syntax of sound mode dictionary not valid, use: OrderedDict([('COMMAND', ['VALUE1','VALUE2'])])" | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. line too long (117 > 79 characters) |
||
| if type(sound_mode_dict) == OrderedDict: | ||
| mode_list = list(sound_mode_dict.values()) | ||
| for sublist in mode_list: | ||
| if type(sublist) == list: | ||
| for element in sublist: | ||
| if type(element) != str: | ||
| _LOGGER.error(Error_msg) | ||
| return False | ||
| else: | ||
| _LOGGER.error(Error_msg) | ||
| return False | ||
| else: | ||
| _LOGGER.error(Error_msg) | ||
| return False | ||
|
|
||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. blank line contains whitespace |
||
| self._sound_mode_dict = sound_mode_dict | ||
| return True | ||
|
|
||
| def match_sound_mode(self, sound_mode_raw): | ||
| try: | ||
| mode_list = list(self._sound_mode_dict.values()) | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When I look at these lines, I get the impression you created the
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In principle you would be right, and it would be nicer to do it that way if each sound_mode_raw would correspond to exactly 1 matched sound mode. I did think about this before I set it up, and in my opinion it is more elegant to do it like I did it because now I have a single matched sound_mode as key, and then all corresponding raw_sound_modes as list in the value corresponding to that key. In this way I do not need to repeat the matched sound_mode as the value for several raw_sound_mode keys (if you would do it the other way around). Therefore my method seemed more compact and in my opinion more elegant for this situation.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In principle I chose for keeping the self._sound_mode_dict as compact and most eligant as I could make it, and having a bit more complicated code in the match_sound_mode function. I did this because users of this library and Home Assistant will probably want to tweak the self._sound_mode_dict to have the exact mapping values for their model of receiver, if it is not yet incorperated in the default mapping dict.
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It does not look too efficient to replace a dictionary key access by a nested loop across all values of a dictionary during run time. Maintaining own mapping rules is indeed easier using your way of modelling. Thus, I would use a dictionary with key:
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @scarface-4711 smart, it is indead more efficient to use a simple dict key acces during runtime, I have implemented this change in the code. |
||
| for sublist in mode_list: | ||
| if sound_mode_raw.upper() in sublist: | ||
| mode_index = mode_list.index(sublist) | ||
| sound_mode = list(self._sound_mode_dict.keys())[mode_index] | ||
| return sound_mode | ||
| except ValueError: | ||
| pass | ||
| _LOGGER.warning("Not able to match sound mode, returning raw sound mode.") | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. line too long (82 > 79 characters) |
||
| return sound_mode_raw | ||
|
|
||
| def toggle_play_pause(self): | ||
| """Toggle play pause media player.""" | ||
| # Use Play/Pause button only for sources which support NETAUDIO | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
line too long (88 > 79 characters)