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, PLATFORM_SCHEMA, SUPPORT_NEXT_TRACK, SUPPORT_SELECT_SOURCE,
SUPPORT_PAUSE, SUPPORT_PREVIOUS_TRACK, SUPPORT_TURN_OFF, SUPPORT_PAUSE, SUPPORT_PREVIOUS_TRACK, SUPPORT_TURN_OFF,
SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET,
MediaPlayerDevice) SUPPORT_STOP, MediaPlayerDevice)
from homeassistant.const import ( from homeassistant.const import (
CONF_HOST, CONF_NAME, STATE_OFF, STATE_ON, STATE_UNKNOWN) CONF_HOST, CONF_NAME, STATE_OFF, STATE_ON, STATE_UNKNOWN)
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
@ -22,16 +22,32 @@ _LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = 'Music station' DEFAULT_NAME = 'Music station'
SUPPORT_DENON = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | \ SUPPORT_DENON = SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \
SUPPORT_VOLUME_MUTE | SUPPORT_PREVIOUS_TRACK | \ SUPPORT_TURN_ON | SUPPORT_TURN_OFF | SUPPORT_SELECT_SOURCE \
SUPPORT_SELECT_SOURCE | SUPPORT_NEXT_TRACK | \
SUPPORT_TURN_ON | SUPPORT_TURN_OFF SUPPORT_MEDIA_MODES = SUPPORT_PAUSE | SUPPORT_STOP | \
SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_HOST): cv.string, vol.Required(CONF_HOST): cv.string,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): 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): def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the Denon platform.""" """Setup the Denon platform."""
@ -53,14 +69,39 @@ class DenonDevice(MediaPlayerDevice):
self._host = host self._host = host
self._pwstate = 'PWSTANDBY' self._pwstate = 'PWSTANDBY'
self._volume = 0 self._volume = 0
self._source_list = {'TV': 'SITV', 'Tuner': 'SITUNER', # Initial value 60dB, changed if we get a MVMAX
'Internet Radio': 'SIIRP', 'Favorites': 'SIFVP'} self._volume_max = 60
self._source_list = NORMAL_INPUTS.copy()
self._source_list.update(MEDIA_MODES)
self._muted = False self._muted = False
self._mediasource = '' 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 @classmethod
def telnet_request(cls, telnet, command): def telnet_request(cls, telnet, command, all_lines=False):
"""Execute `command` and return the response.""" """Execute `command` and return the response."""
_LOGGER.debug('Sending: "%s"', command)
telnet.write(command.encode('ASCII') + b'\r') telnet.write(command.encode('ASCII') + b'\r')
lines = [] lines = []
while True: while True:
@ -68,12 +109,16 @@ class DenonDevice(MediaPlayerDevice):
if not line: if not line:
break break
lines.append(line.decode('ASCII').strip()) lines.append(line.decode('ASCII').strip())
_LOGGER.debug('Recived: "%s"', line)
if all_lines:
return lines
return lines[0] return lines[0]
def telnet_command(self, command): def telnet_command(self, command):
"""Establish a telnet connection and sends `command`.""" """Establish a telnet connection and sends `command`."""
telnet = telnetlib.Telnet(self._host) telnet = telnetlib.Telnet(self._host)
_LOGGER.debug('Sending: "%s"', command)
telnet.write(command.encode('ASCII') + b'\r') telnet.write(command.encode('ASCII') + b'\r')
telnet.read_very_eager() # skip response telnet.read_very_eager() # skip response
telnet.close() telnet.close()
@ -85,12 +130,30 @@ class DenonDevice(MediaPlayerDevice):
except OSError: except OSError:
return False return False
if self._should_setup_sources:
self._setup_sources(telnet)
self._should_setup_sources = False
self._pwstate = self.telnet_request(telnet, 'PW?') self._pwstate = self.telnet_request(telnet, 'PW?')
volume_str = self.telnet_request(telnet, 'MV?')[len('MV'):] for line in self.telnet_request(telnet, 'MV?', all_lines=True):
self._volume = int(volume_str) / 60 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._muted = (self.telnet_request(telnet, 'MU?') == 'MUON')
self._mediasource = self.telnet_request(telnet, 'SI?')[len('SI'):] 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() telnet.close()
return True return True
@ -112,7 +175,7 @@ class DenonDevice(MediaPlayerDevice):
@property @property
def volume_level(self): def volume_level(self):
"""Volume level of the media player (0..1).""" """Volume level of the media player (0..1)."""
return self._volume return self._volume / self._volume_max
@property @property
def is_volume_muted(self): def is_volume_muted(self):
@ -122,17 +185,27 @@ class DenonDevice(MediaPlayerDevice):
@property @property
def source_list(self): def source_list(self):
"""List of available input sources.""" """List of available input sources."""
return list(self._source_list.keys()) return sorted(list(self._source_list.keys()))
@property @property
def media_title(self): def media_title(self):
"""Current media source.""" """Current media info."""
return self._mediasource return self._mediainfo
@property @property
def supported_media_commands(self): def supported_media_commands(self):
"""Flag of media commands that are supported.""" """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): def turn_off(self):
"""Turn off media player.""" """Turn off media player."""
@ -148,8 +221,8 @@ class DenonDevice(MediaPlayerDevice):
def set_volume_level(self, volume): def set_volume_level(self, volume):
"""Set volume level, range 0..1.""" """Set volume level, range 0..1."""
# 60dB max self.telnet_command('MV' +
self.telnet_command('MV' + str(round(volume * 60)).zfill(2)) str(round(volume * self._volume_max)).zfill(2))
def mute_volume(self, mute): def mute_volume(self, mute):
"""Mute (true) or unmute (false) media player.""" """Mute (true) or unmute (false) media player."""
@ -163,6 +236,10 @@ class DenonDevice(MediaPlayerDevice):
"""Pause media player.""" """Pause media player."""
self.telnet_command('NS9B') self.telnet_command('NS9B')
def media_stop(self):
"""Pause media player."""
self.telnet_command('NS9C')
def media_next_track(self): def media_next_track(self):
"""Send the next track command.""" """Send the next track command."""
self.telnet_command('NS9D') self.telnet_command('NS9D')
@ -177,4 +254,4 @@ class DenonDevice(MediaPlayerDevice):
def select_source(self, source): def select_source(self, source):
"""Select input source.""" """Select input source."""
self.telnet_command(self._source_list.get(source)) self.telnet_command('SI' + self._source_list.get(source))