core/homeassistant/components/media_player/denonavr.py

339 lines
11 KiB
Python
Raw Normal View History

"""
Support for Denon AVR receivers using their HTTP interface.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/media_player.denon/
"""
import logging
from collections import namedtuple
import voluptuous as vol
from homeassistant.components.media_player import (
SUPPORT_PAUSE, SUPPORT_NEXT_TRACK, SUPPORT_PREVIOUS_TRACK,
SUPPORT_TURN_OFF, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_STEP,
SUPPORT_SELECT_SOURCE, SUPPORT_PLAY_MEDIA, MEDIA_TYPE_CHANNEL,
MediaPlayerDevice, PLATFORM_SCHEMA, SUPPORT_TURN_ON,
MEDIA_TYPE_MUSIC, SUPPORT_VOLUME_SET, SUPPORT_PLAY)
from homeassistant.const import (
CONF_HOST, STATE_OFF, STATE_PLAYING, STATE_PAUSED,
CONF_NAME, STATE_ON, CONF_ZONE)
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['denonavr==0.5.0']
_LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = None
DEFAULT_SHOW_SOURCES = False
CONF_SHOW_ALL_SOURCES = 'show_all_sources'
CONF_ZONES = 'zones'
CONF_VALID_ZONES = ['Zone2', 'Zone3']
CONF_INVALID_ZONES_ERR = 'Invalid Zone (expected Zone2 or Zone3)'
KEY_DENON_CACHE = 'denonavr_hosts'
SUPPORT_DENON = SUPPORT_VOLUME_STEP | SUPPORT_VOLUME_MUTE | \
SUPPORT_TURN_ON | SUPPORT_TURN_OFF | \
SUPPORT_SELECT_SOURCE | SUPPORT_VOLUME_SET
SUPPORT_MEDIA_MODES = SUPPORT_PLAY_MEDIA | \
SUPPORT_PAUSE | SUPPORT_PREVIOUS_TRACK | \
SUPPORT_NEXT_TRACK | SUPPORT_VOLUME_SET | SUPPORT_PLAY
DENON_ZONE_SCHEMA = vol.Schema({
vol.Required(CONF_ZONE): vol.In(CONF_VALID_ZONES, CONF_INVALID_ZONES_ERR),
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
})
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_HOST): cv.string,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_SHOW_ALL_SOURCES, default=DEFAULT_SHOW_SOURCES):
cv.boolean,
vol.Optional(CONF_ZONES):
vol.All(cv.ensure_list, [DENON_ZONE_SCHEMA])
})
NewHost = namedtuple('NewHost', ['host', 'name'])
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the Denon platform."""
# pylint: disable=import-error
import denonavr
# Initialize list with receivers to be started
receivers = []
cache = hass.data.get(KEY_DENON_CACHE)
if cache is None:
cache = hass.data[KEY_DENON_CACHE] = set()
# Get config option for show_all_sources
show_all_sources = config.get(CONF_SHOW_ALL_SOURCES)
# Get config option for additional zones
zones = config.get(CONF_ZONES)
if zones is not None:
add_zones = {}
for entry in zones:
add_zones[entry[CONF_ZONE]] = entry[CONF_NAME]
else:
add_zones = None
# Start assignment of host and name
new_hosts = []
# 1. option: manual setting
if config.get(CONF_HOST) is not None:
host = config.get(CONF_HOST)
name = config.get(CONF_NAME)
new_hosts.append(NewHost(host=host, name=name))
# 2. option: discovery using netdisco
if discovery_info is not None:
host = discovery_info.get('host')
name = discovery_info.get('name')
new_hosts.append(NewHost(host=host, name=name))
# 3. option: discovery using denonavr library
if config.get(CONF_HOST) is None and discovery_info is None:
d_receivers = denonavr.discover()
# More than one receiver could be discovered by that method
if d_receivers is not None:
for d_receiver in d_receivers:
host = d_receiver["host"]
name = d_receiver["friendlyName"]
new_hosts.append(NewHost(host=host, name=name))
for entry in new_hosts:
# Check if host not in cache, append it and save for later
# starting
if entry.host not in cache:
new_device = denonavr.DenonAVR(
entry.host, entry.name, show_all_sources, add_zones)
for new_zone in new_device.zones.values():
receivers.append(DenonDevice(new_zone))
cache.add(host)
_LOGGER.info("Denon receiver at host %s initialized", host)
# Add all freshly discovered receivers
if receivers:
add_devices(receivers)
class DenonDevice(MediaPlayerDevice):
"""Representation of a Denon Media Player Device."""
def __init__(self, receiver):
"""Initialize the device."""
self._receiver = receiver
self._name = self._receiver.name
self._muted = self._receiver.muted
self._volume = self._receiver.volume
self._current_source = self._receiver.input_func
self._source_list = self._receiver.input_func_list
self._state = self._receiver.state
self._power = self._receiver.power
self._media_image_url = self._receiver.image_url
self._title = self._receiver.title
self._artist = self._receiver.artist
self._album = self._receiver.album
self._band = self._receiver.band
self._frequency = self._receiver.frequency
self._station = self._receiver.station
def update(self):
"""Get the latest status information from device."""
self._receiver.update()
self._name = self._receiver.name
self._muted = self._receiver.muted
self._volume = self._receiver.volume
self._current_source = self._receiver.input_func
self._source_list = self._receiver.input_func_list
self._state = self._receiver.state
self._power = self._receiver.power
self._media_image_url = self._receiver.image_url
self._title = self._receiver.title
self._artist = self._receiver.artist
self._album = self._receiver.album
self._band = self._receiver.band
self._frequency = self._receiver.frequency
self._station = self._receiver.station
@property
def name(self):
"""Return the name of the device."""
return self._name
@property
def state(self):
"""Return the state of the device."""
return self._state
@property
def is_volume_muted(self):
"""Return boolean if volume is currently muted."""
return self._muted
@property
def volume_level(self):
"""Volume level of the media player (0..1)."""
# Volume is sent in a format like -50.0. Minimum is -80.0,
# maximum is 18.0
return (float(self._volume) + 80) / 100
@property
def source(self):
"""Return the current input source."""
return self._current_source
@property
def source_list(self):
"""Return a list of available input sources."""
return self._source_list
@property
def supported_features(self):
"""Flag media player features that are supported."""
if self._current_source in self._receiver.netaudio_func_list:
return SUPPORT_DENON | SUPPORT_MEDIA_MODES
else:
return SUPPORT_DENON
@property
def media_content_id(self):
"""Content ID of current playing media."""
return None
@property
def media_content_type(self):
"""Content type of current playing media."""
if self._state == STATE_PLAYING or self._state == STATE_PAUSED:
return MEDIA_TYPE_MUSIC
else:
return MEDIA_TYPE_CHANNEL
@property
def media_duration(self):
"""Duration of current playing media in seconds."""
return None
@property
def media_image_url(self):
"""Image url of current playing media."""
if self._current_source in self._receiver.playing_func_list:
return self._media_image_url
else:
return None
@property
def media_title(self):
"""Title of current playing media."""
if self._current_source not in self._receiver.playing_func_list:
return self._current_source
elif self._title is not None:
return self._title
else:
return self._frequency
@property
def media_artist(self):
"""Artist of current playing media, music track only."""
if self._artist is not None:
return self._artist
else:
return self._band
@property
def media_album_name(self):
"""Album name of current playing media, music track only."""
if self._album is not None:
return self._album
else:
return self._station
@property
def media_album_artist(self):
"""Album artist of current playing media, music track only."""
return None
@property
def media_track(self):
"""Track number of current playing media, music track only."""
return None
@property
def media_series_title(self):
"""Title of series of current playing media, TV show only."""
return None
@property
def media_season(self):
"""Season of current playing media, TV show only."""
return None
@property
def media_episode(self):
"""Episode of current playing media, TV show only."""
return None
def media_play_pause(self):
"""Simulate play pause media player."""
return self._receiver.toggle_play_pause()
def media_previous_track(self):
"""Send previous track command."""
return self._receiver.previous_track()
def media_next_track(self):
"""Send next track command."""
return self._receiver.next_track()
def select_source(self, source):
"""Select input source."""
return self._receiver.set_input_func(source)
def turn_on(self):
"""Turn on media player."""
if self._receiver.power_on():
self._state = STATE_ON
return True
else:
return False
def turn_off(self):
"""Turn off media player."""
if self._receiver.power_off():
self._state = STATE_OFF
return True
else:
return False
def volume_up(self):
"""Volume up the media player."""
return self._receiver.volume_up()
def volume_down(self):
"""Volume down media player."""
return self._receiver.volume_down()
def set_volume_level(self, volume):
"""Set volume level, range 0..1."""
# Volume has to be sent in a format like -50.0. Minimum is -80.0,
# maximum is 18.0
volume_denon = float((volume * 100) - 80)
if volume_denon > 18:
volume_denon = float(18)
try:
if self._receiver.set_volume(volume_denon):
self._volume = volume_denon
return True
else:
return False
except ValueError:
return False
def mute_volume(self, mute):
"""Send mute command."""
return self._receiver.mute(mute)