Added sound mode support#43
Conversation
new functions to use: - denonavr.sound_mode (gives the current matched sound mode (after an denonavr.update)) - denonavr.sound_mode_raw (gives the current sound mode as received from the AVR (after an denonavr.update)) - denonavr.sound_mode_list (gives a list of supported sound mode commands) - denonavr.sound_mode_dict (gives the full dictionarry containing all supported commands including their matching values) - denonavr.set_sound_mode(VALUE) (change the sound mode of the AVR using one of the VALUE's of the denonavr.sound_mode_list) - denonavr.set_sound_mode_dict (change the matching sound mode dict, this will throw an error if the wrong syntax is used with an example of a correct syntax)
| return sound_mode | ||
| except ValueError: | ||
| pass | ||
| _LOGGER.warning("Not able to match sound mode, returning raw sound mode.") |
| else: | ||
| _LOGGER.error(Error_msg) | ||
| return False | ||
|
|
| 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.
line too long (117 > 79 characters)
| # Set all tags to be evaluated | ||
| relevant_tags = {"Power": None, "InputFuncSelect": None, "Mute": None, | ||
| "MasterVolume": None} | ||
| "MasterVolume": None, "selectSurround": None, "SurrMode": None} |
fixed houndci-bot typo's
|
|
||
| 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.
IndentationError: unexpected indent
unexpected indentation
|
|
||
| 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.
continuation line under-indented for visual indent
|
@scarface-4711 could you please merge this PR? |
|
@scarface-4711 could you please merge this PR? |
|
@starkillerOG , was on vacation and did have lots of work before. I was not able to test your changes until now. Setting sound modes using the values from sound_mode_list is working fine. The modes for setting seem to be the same over several receiver models. The mapping of sound modes does not work for my receiver. Every time I run the update method there is a Not able to match sound mode, returning raw sound mode. warning, because the mapping of my receiver is totally different than yours. When you look at other XML files from the tests/xml subdirectory of this repository, many other models will have the same issue. |
| self._sound_mode = self.match_sound_mode(sound_mode_raw) | ||
| self._sound_mode_raw = sound_mode_raw | ||
| if "selectSurround" in relevant_tags.keys(): | ||
| relevant_tags.pop("selectSurround", None) |
There was a problem hiding this comment.
Could you pop both keys from the list, when the first one was found?
They seem to cover the same information. At the moment there might be a second HTTP request, which is not needed, because the STATUS_URL is called first and there is a second call to MAINZONE_URL if some tags are not found yet.
There was a problem hiding this comment.
I am poping both keys when the first one is found.
when one of the keys is found elif (child.tag == "selectSurround" or child.tag == "SurrMode"): , both keys are poped using relevant_tags.pop("selectSurround", None) and relevant_tags.pop("SurrMode", None).
The if "selectSurround" in relevant_tags.keys(): and if "SurrMode" in relevant_tags.keys(): will in principle always return true, however I put those in to prevent the occasion that later when the code is changed there is no problem with trying to remove a key that does not exist. In the case that if "selectSurround" in relevant_tags.keys(): or if "SurrMode" in relevant_tags.keys(): returns false, the key is not in the list anyway so their is no need to pop it.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
ah ok, I was confused by the if statement.
Btw. you don't need the if statements here. If you pop with an default value, there won't be an exception even if the key is not in the list
|
@scarface-4711 The mapping is indeed quite difficult. I am happy to include more mapping values and make the mapping structure bigger (just fill in the list), This will make this list pretty big indeed. The problem is that I do not know the values that are returnd. For my receiver I just asked for all the sound modes I use and then put the returned values in the mapping list. However if I compare my retrun values with list such as: Note however that I implemented the option to change the mapping list: |
|
@scarface-4711 The raw values can always be obtained using: |
Moved the warning if the sound mode can not be matched from the update function to when you call the matched sound mode.
Added all the sound mode values that were present in denonavr/tests/xml/
| "Flickr": "FLICKR", "Favorites": "FAVORITES"} | ||
|
|
||
| SOUND_MODE_MAPPING = OrderedDict([('MUSIC', ['PLII MUSIC']), | ||
| ('MOVIE', ['PLII MOVIE','PLII CINEMA', |
|
@scarface-4711 I added all the sound mode values that were present in denonavr/tests/xml/. I suggest we merge this PR, and over time add more mapping values to the list when people owning a diffrent model open an issue for missing mapping values. In the mean time those people can customly define their mapping values with denonavr.set_sound_mode_dict(DICT_TO_USE), or when using HomeAssistant define it in the options of HomeAssistant. |
ol-iver
left a comment
There was a problem hiding this comment.
Please check my remarks. I'll check additional sound mode of my receiver.
| 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) |
There was a problem hiding this comment.
Could you move the match_sound_mode to the sound_mode property?
You won't need the self. _sound_mode_match_warning attribute then.
| self._sound_mode = self.match_sound_mode(sound_mode_raw) | ||
| self._sound_mode_raw = sound_mode_raw | ||
| if "selectSurround" in relevant_tags.keys(): | ||
| relevant_tags.pop("selectSurround", None) |
There was a problem hiding this comment.
ah ok, I was confused by the if statement.
Btw. you don't need the if statements here. If you pop with an default value, there won't be an exception even if the key is not in the list
|
|
||
| def match_sound_mode(self, sound_mode_raw): | ||
| try: | ||
| mode_list = list(self._sound_mode_dict.values()) |
There was a problem hiding this comment.
When I look at these lines, I get the impression you created the SOUND_MODE_MAPPING and self._sound_mode_dict the wrong way and mixed key and values.
You are using it to get the sound modes from the raw values. For me this is a normal dictionary with each sound_mode_raw as key and its sound_mode as value. That would simplify this part here a lot.
sound_mode_list would be a set set(self._sound_mode_dict.values()) then.
There was a problem hiding this comment.
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.
Unfortunately this is not the case, and multiple sound_mode_raw's can be matched to the same 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.
There was a problem hiding this comment.
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.
However those users will in principle never have to deal with the code of the match_sound_mode function, so it is no problem if that gets a bit more complicated.
There was a problem hiding this comment.
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: sound_mode_raw and value: sound_mode during run time as I suggested.
This dictionary could be created by the set_sound_mode_dict method you already created from a dictionary with the structure you suggested (key: sound_mode, value: list of sound_mode_raw)
There was a problem hiding this comment.
@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.
moved match_sound_mode to sound_mode property. Therefore I could get rid of self._sound_mode and self. _sound_mode_match_warning.
|
@scarface-4711 shall I remove the |
|
The sound modes are different at my receiver There are other sound modes which are specific to one category: |
|
@starkillerOG : you can remove the |
Added aditional sound mode mapping values removed the if tag in list checks for the pop function.
| "Media Server": "SERVER", "Spotify": "SPOTIFY", | ||
| "Flickr": "FLICKR", "Favorites": "FAVORITES"} | ||
|
|
||
| SOUND_MODE_MAPPING = OrderedDict \ |
instead of looping over the original nice syntax self._sound_mode_dict, a second dict is created based on self._sound_mode_dict, this new dict is called self._SM_match_dict. In this bigger dictionary the key value structure is reversed (but it contains duplicate values). This self._SM_match_dict is used to match the raw sound mode with a simple dict value request based on key.
| def set_sound_mode_dict(self, sound_mode_dict): | ||
| Error_msg = ("Syntax of sound mode dictionary not valid, " | ||
| "use: OrderedDict([('COMMAND', ['VALUE1','VALUE2'])])") | ||
| if (type(sound_mode_dict) == OrderedDict or type(sound_mode_dict) == dict): |
|
|
||
| @property | ||
| def sound_mode_match_dict(self): | ||
| """Return a dictionary that is used to map each sound_mode_raw to it's matched sound_mode.""" |
There was a problem hiding this comment.
line too long (101 > 79 characters)
|
|
||
| @property | ||
| def sound_mode_dict(self): | ||
| """Return a dictionary of available sound modes with their mapping values.""" |
| Error_msg = ("Syntax of sound mode dictionary not valid, " | ||
| "use: OrderedDict([('COMMAND', ['VALUE1','VALUE2'])])") | ||
| if (type(sound_mode_dict) == OrderedDict or\ | ||
| type(sound_mode_dict) == dict): |
There was a problem hiding this comment.
visually indented line with same indent as next logical line
| def set_sound_mode_dict(self, sound_mode_dict): | ||
| Error_msg = ("Syntax of sound mode dictionary not valid, " | ||
| "use: OrderedDict([('COMMAND', ['VALUE1','VALUE2'])])") | ||
| if (type(sound_mode_dict) == OrderedDict or\ |
There was a problem hiding this comment.
the backslash is redundant between brackets
| @property | ||
| def SM_match_dict(self): | ||
| """ | ||
| Return a dictionary that is used to |
|
@scarface-4711 I implemented all your change requests. Thanks for the feedback. Your receiver works the same as mine, I can also define "stereo" from each of the menu's (MUSIC, MOVIE, GAME etc. However this is actually the same sound mode regardless from wich menu you selected it. You can see this if you look at the phone application or at the web-interface of the receiver. Their the sound mode menu's are organized in a diffrent way and their there is only 1 "stereo" option. |
|
@scarface-4711 could you have a final look at the code and give any aditional comments or merge it? |
|
@starkillerOG changes are looking good. There are some small comments and warnings from pylint and pydocstyle. Could you please fix them? I'll push a new version for you tomorrow evening then. |
| # sent command | ||
| try: | ||
| if self.send_get_command(command_url): | ||
| self.sound_mode() = sound_mode |
There was a problem hiding this comment.
SyntaxError: can't assign to function call
| "Flickr": "FLICKR", "Favorites": "FAVORITES"} | ||
|
|
||
| SOUND_MODE_MAPPING = OrderedDict\ | ||
| ([('MUSIC', ['PLII MUSIC', 'DTS NEO:6 MUSIC', 'DOLBY D +NEO:X M', |
There was a problem hiding this comment.
continuation line missing indentation or outdented
| "Media Server": "SERVER", "Spotify": "SPOTIFY", | ||
| "Flickr": "FLICKR", "Favorites": "FAVORITES"} | ||
|
|
||
| SOUND_MODE_MAPPING = OrderedDict(\ |
There was a problem hiding this comment.
the backslash is redundant between brackets
|
@scarface-4711 I think I fixed all small comments and warnings. |
|
@starkillerOG version 0.7.0 is now available on pypi including your sound mode support. |
|
@scarface-4711 Thank you! |
new functions to use: