mirror of
https://github.com/home-assistant/core.git
synced 2025-07-19 11:17:21 +00:00
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:
parent
b156ae7812
commit
04aa4e898a
@ -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))
|
||||||
|
Loading…
x
Reference in New Issue
Block a user