"""Support for Yamaha MusicCast Receivers.""" import logging import voluptuous as vol from homeassistant.components.media_player import ( MediaPlayerDevice, PLATFORM_SCHEMA) from homeassistant.components.media_player.const import ( MEDIA_TYPE_MUSIC, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY, SUPPORT_PREVIOUS_TRACK, SUPPORT_SELECT_SOURCE, SUPPORT_STOP, SUPPORT_TURN_OFF, SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET) from homeassistant.const import ( CONF_HOST, CONF_PORT, STATE_IDLE, STATE_ON, STATE_PAUSED, STATE_PLAYING, STATE_UNKNOWN) import homeassistant.helpers.config_validation as cv import homeassistant.util.dt as dt_util _LOGGER = logging.getLogger(__name__) SUPPORTED_FEATURES = ( SUPPORT_PLAY | SUPPORT_PAUSE | SUPPORT_STOP | SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK | SUPPORT_TURN_ON | SUPPORT_TURN_OFF | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | SUPPORT_SELECT_SOURCE ) KNOWN_HOSTS_KEY = 'data_yamaha_musiccast' INTERVAL_SECONDS = 'interval_seconds' DEFAULT_PORT = 5005 DEFAULT_INTERVAL = 480 PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Required(CONF_HOST): cv.string, vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, vol.Optional(INTERVAL_SECONDS, default=DEFAULT_INTERVAL): cv.positive_int, }) def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the Yamaha MusicCast platform.""" import socket import pymusiccast known_hosts = hass.data.get(KNOWN_HOSTS_KEY) if known_hosts is None: known_hosts = hass.data[KNOWN_HOSTS_KEY] = [] _LOGGER.debug("known_hosts: %s", known_hosts) host = config.get(CONF_HOST) port = config.get(CONF_PORT) interval = config.get(INTERVAL_SECONDS) # Get IP of host to prevent duplicates try: ipaddr = socket.gethostbyname(host) except (OSError) as error: _LOGGER.error( "Could not communicate with %s:%d: %s", host, port, error) return if [item for item in known_hosts if item[0] == ipaddr]: _LOGGER.warning("Host %s:%d already registered", host, port) return if [item for item in known_hosts if item[1] == port]: _LOGGER.warning("Port %s:%d already registered", host, port) return reg_host = (ipaddr, port) known_hosts.append(reg_host) try: receiver = pymusiccast.McDevice( ipaddr, udp_port=port, mc_interval=interval) except pymusiccast.exceptions.YMCInitError as err: _LOGGER.error(err) receiver = None if receiver: for zone in receiver.zones: _LOGGER.debug( "Receiver: %s / Port: %d / Zone: %s", receiver, port, zone) add_entities( [YamahaDevice(receiver, receiver.zones[zone])], True) else: known_hosts.remove(reg_host) class YamahaDevice(MediaPlayerDevice): """Representation of a Yamaha MusicCast device.""" def __init__(self, recv, zone): """Initialize the Yamaha MusicCast device.""" self._recv = recv self._name = recv.name self._source = None self._source_list = [] self._zone = zone self.mute = False self.media_status = None self.media_status_received = None self.power = STATE_UNKNOWN self.status = STATE_UNKNOWN self.volume = 0 self.volume_max = 0 self._recv.set_yamaha_device(self) self._zone.set_yamaha_device(self) @property def name(self): """Return the name of the device.""" return "{} ({})".format(self._name, self._zone.zone_id) @property def state(self): """Return the state of the device.""" if self.power == STATE_ON and self.status is not STATE_UNKNOWN: return self.status return self.power @property def should_poll(self): """Push an update after each command.""" return True @property def is_volume_muted(self): """Boolean if volume is currently muted.""" return self.mute @property def volume_level(self): """Volume level of the media player (0..1).""" return self.volume @property def supported_features(self): """Flag of features that are supported.""" return SUPPORTED_FEATURES @property def source(self): """Return the current input source.""" return self._source @property def source_list(self): """List of available input sources.""" return self._source_list @source_list.setter def source_list(self, value): """Set source_list attribute.""" self._source_list = value @property def media_content_type(self): """Return the media content type.""" return MEDIA_TYPE_MUSIC @property def media_duration(self): """Duration of current playing media in seconds.""" return self.media_status.media_duration \ if self.media_status else None @property def media_image_url(self): """Image url of current playing media.""" return self.media_status.media_image_url \ if self.media_status else None @property def media_artist(self): """Artist of current playing media, music track only.""" return self.media_status.media_artist if self.media_status else None @property def media_album(self): """Album of current playing media, music track only.""" return self.media_status.media_album if self.media_status else None @property def media_track(self): """Track number of current playing media, music track only.""" return self.media_status.media_track if self.media_status else None @property def media_title(self): """Title of current playing media.""" return self.media_status.media_title if self.media_status else None @property def media_position(self): """Position of current playing media in seconds.""" if self.media_status and self.state in \ [STATE_PLAYING, STATE_PAUSED, STATE_IDLE]: return self.media_status.media_position @property def media_position_updated_at(self): """When was the position of the current playing media valid. Returns value from homeassistant.util.dt.utcnow(). """ return self.media_status_received if self.media_status else None def update(self): """Get the latest details from the device.""" _LOGGER.debug("update: %s", self.entity_id) self._recv.update_status() self._zone.update_status() def update_hass(self): """Push updates to HASS.""" if self.entity_id: _LOGGER.debug("update_hass: pushing updates") self.schedule_update_ha_state() return True def turn_on(self): """Turn on specified media player or all.""" _LOGGER.debug("Turn device: on") self._zone.set_power(True) def turn_off(self): """Turn off specified media player or all.""" _LOGGER.debug("Turn device: off") self._zone.set_power(False) def media_play(self): """Send the media player the command for play/pause.""" _LOGGER.debug("Play") self._recv.set_playback("play") def media_pause(self): """Send the media player the command for pause.""" _LOGGER.debug("Pause") self._recv.set_playback("pause") def media_stop(self): """Send the media player the stop command.""" _LOGGER.debug("Stop") self._recv.set_playback("stop") def media_previous_track(self): """Send the media player the command for prev track.""" _LOGGER.debug("Previous") self._recv.set_playback("previous") def media_next_track(self): """Send the media player the command for next track.""" _LOGGER.debug("Next") self._recv.set_playback("next") def mute_volume(self, mute): """Send mute command.""" _LOGGER.debug("Mute volume: %s", mute) self._zone.set_mute(mute) def set_volume_level(self, volume): """Set volume level, range 0..1.""" _LOGGER.debug("Volume level: %.2f / %d", volume, volume * self.volume_max) self._zone.set_volume(volume * self.volume_max) def select_source(self, source): """Send the media player the command to select input source.""" _LOGGER.debug("select_source: %s", source) self.status = STATE_UNKNOWN self._zone.set_input(source) def new_media_status(self, status): """Handle updates of the media status.""" _LOGGER.debug("new media_status arrived") self.media_status = status self.media_status_received = dt_util.utcnow()