Improve denon media_player (#4836)

* Add debug level logging of messages in denon

* Added media stop for Denon AVR Media Player

* Sort source list

* Rework input selection for Denon AVR

This reworks the input selection, adding more modes and making it so
that the media controls are only announced in modes where they actually
makes sense.

* Added real media info for Denon AVR Media modes

* Read more configuration from denon devices

This reads network name, and overrides the local name with that.

This also reads the source names and reconfigures the input list to
those names, and also reads the source deleted list and removes the
inputs that are set to deleted in the device.

* Discover and handle max volume in Denon media player

* Rework source discovery in Denon media player

This uses SSFUN as authorative source for which sources that we should
present.
This commit is contained in:
Anton Lundin 2016-12-12 06:04:36 +01:00 committed by Paulus Schoutsen
parent b156ae7812
commit 04aa4e898a

View File

@ -13,7 +13,7 @@ from homeassistant.components.media_player import (
PLATFORM_SCHEMA, SUPPORT_NEXT_TRACK, SUPPORT_SELECT_SOURCE,
SUPPORT_PAUSE, SUPPORT_PREVIOUS_TRACK, SUPPORT_TURN_OFF,
SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET,
MediaPlayerDevice)
SUPPORT_STOP, MediaPlayerDevice)
from homeassistant.const import (
CONF_HOST, CONF_NAME, STATE_OFF, STATE_ON, STATE_UNKNOWN)
import homeassistant.helpers.config_validation as cv
@ -22,16 +22,32 @@ _LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = 'Music station'
SUPPORT_DENON = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | \
SUPPORT_VOLUME_MUTE | SUPPORT_PREVIOUS_TRACK | \
SUPPORT_SELECT_SOURCE | SUPPORT_NEXT_TRACK | \
SUPPORT_TURN_ON | SUPPORT_TURN_OFF
SUPPORT_DENON = SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \
SUPPORT_TURN_ON | SUPPORT_TURN_OFF | SUPPORT_SELECT_SOURCE \
SUPPORT_MEDIA_MODES = SUPPORT_PAUSE | SUPPORT_STOP | \
SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_HOST): cv.string,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
})
NORMAL_INPUTS = {'Cd': 'CD', 'Dvd': 'DVD', 'Blue ray': 'BD', 'TV': 'TV',
'Satelite / Cable': 'SAT/CBL', 'Game': 'GAME',
'Game2': 'GAME2', 'Video Aux': 'V.AUX', 'Dock': 'DOCK'}
MEDIA_MODES = {'Tuner': 'TUNER', 'Media server': 'SERVER',
'Ipod dock': 'IPOD', 'Net/USB': 'NET/USB',
'Rapsody': 'RHAPSODY', 'Napster': 'NAPSTER',
'Pandora': 'PANDORA', 'LastFM': 'LASTFM',
'Flickr': 'FLICKR', 'Favorites': 'FAVORITES',
'Internet Radio': 'IRADIO', 'USB/IPOD': 'USB/IPOD'}
# Sub-modes of 'NET/USB'
# {'USB': 'USB', 'iPod Direct': 'IPD', 'Internet Radio': 'IRP',
# 'Favorites': 'FVP'}
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the Denon platform."""
@ -53,14 +69,39 @@ class DenonDevice(MediaPlayerDevice):
self._host = host
self._pwstate = 'PWSTANDBY'
self._volume = 0
self._source_list = {'TV': 'SITV', 'Tuner': 'SITUNER',
'Internet Radio': 'SIIRP', 'Favorites': 'SIFVP'}
# Initial value 60dB, changed if we get a MVMAX
self._volume_max = 60
self._source_list = NORMAL_INPUTS.copy()
self._source_list.update(MEDIA_MODES)
self._muted = False
self._mediasource = ''
self._mediainfo = ''
self._should_setup_sources = True
def _setup_sources(self, telnet):
# NSFRN - Network name
self._name = self.telnet_request(telnet, 'NSFRN ?')[len('NSFRN '):]
# SSFUN - Configured sources with names
self._source_list = {}
for line in self.telnet_request(telnet, 'SSFUN ?', all_lines=True):
source, configured_name = line[len('SSFUN'):].split(" ", 1)
self._source_list[configured_name] = source
# SSSOD - Deleted sources
for line in self.telnet_request(telnet, 'SSSOD ?', all_lines=True):
source, status = line[len('SSSOD'):].split(" ", 1)
if status == 'DEL':
for pretty_name, name in self._source_list.items():
if source == name:
del self._source_list[pretty_name]
break
@classmethod
def telnet_request(cls, telnet, command):
def telnet_request(cls, telnet, command, all_lines=False):
"""Execute `command` and return the response."""
_LOGGER.debug('Sending: "%s"', command)
telnet.write(command.encode('ASCII') + b'\r')
lines = []
while True:
@ -68,12 +109,16 @@ class DenonDevice(MediaPlayerDevice):
if not line:
break
lines.append(line.decode('ASCII').strip())
_LOGGER.debug('Recived: "%s"', line)
if all_lines:
return lines
return lines[0]
def telnet_command(self, command):
"""Establish a telnet connection and sends `command`."""
telnet = telnetlib.Telnet(self._host)
_LOGGER.debug('Sending: "%s"', command)
telnet.write(command.encode('ASCII') + b'\r')
telnet.read_very_eager() # skip response
telnet.close()
@ -85,12 +130,30 @@ class DenonDevice(MediaPlayerDevice):
except OSError:
return False
if self._should_setup_sources:
self._setup_sources(telnet)
self._should_setup_sources = False
self._pwstate = self.telnet_request(telnet, 'PW?')
volume_str = self.telnet_request(telnet, 'MV?')[len('MV'):]
self._volume = int(volume_str) / 60
for line in self.telnet_request(telnet, 'MV?', all_lines=True):
if line.startswith('MVMAX '):
# only grab two digit max, don't care about any half digit
self._volume_max = int(line[len('MVMAX '):len('MVMAX XX')])
continue
if line.startswith('MV'):
self._volume = int(line[len('MV'):])
self._muted = (self.telnet_request(telnet, 'MU?') == 'MUON')
self._mediasource = self.telnet_request(telnet, 'SI?')[len('SI'):]
if self._mediasource in MEDIA_MODES.values():
self._mediainfo = ""
answer_codes = ["NSE0", "NSE1X", "NSE2X", "NSE3X", "NSE4", "NSE5",
"NSE6", "NSE7", "NSE8"]
for line in self.telnet_request(telnet, 'NSE', all_lines=True):
self._mediainfo += line[len(answer_codes.pop()):] + '\n'
else:
self._mediainfo = self.source
telnet.close()
return True
@ -112,7 +175,7 @@ class DenonDevice(MediaPlayerDevice):
@property
def volume_level(self):
"""Volume level of the media player (0..1)."""
return self._volume
return self._volume / self._volume_max
@property
def is_volume_muted(self):
@ -122,17 +185,27 @@ class DenonDevice(MediaPlayerDevice):
@property
def source_list(self):
"""List of available input sources."""
return list(self._source_list.keys())
return sorted(list(self._source_list.keys()))
@property
def media_title(self):
"""Current media source."""
return self._mediasource
"""Current media info."""
return self._mediainfo
@property
def supported_media_commands(self):
"""Flag of media commands that are supported."""
return SUPPORT_DENON
if self._mediasource in MEDIA_MODES.values():
return SUPPORT_DENON | SUPPORT_MEDIA_MODES
else:
return SUPPORT_DENON
@property
def source(self):
"""Return the current input source."""
for pretty_name, name in self._source_list.items():
if self._mediasource == name:
return pretty_name
def turn_off(self):
"""Turn off media player."""
@ -148,8 +221,8 @@ class DenonDevice(MediaPlayerDevice):
def set_volume_level(self, volume):
"""Set volume level, range 0..1."""
# 60dB max
self.telnet_command('MV' + str(round(volume * 60)).zfill(2))
self.telnet_command('MV' +
str(round(volume * self._volume_max)).zfill(2))
def mute_volume(self, mute):
"""Mute (true) or unmute (false) media player."""
@ -163,6 +236,10 @@ class DenonDevice(MediaPlayerDevice):
"""Pause media player."""
self.telnet_command('NS9B')
def media_stop(self):
"""Pause media player."""
self.telnet_command('NS9C')
def media_next_track(self):
"""Send the next track command."""
self.telnet_command('NS9D')
@ -177,4 +254,4 @@ class DenonDevice(MediaPlayerDevice):
def select_source(self, source):
"""Select input source."""
self.telnet_command(self._source_list.get(source))
self.telnet_command('SI' + self._source_list.get(source))