From 50b23e19695c5369a9d990452d85e3cd5dfece69 Mon Sep 17 00:00:00 2001 From: Jon Maddox Date: Mon, 14 Sep 2015 17:27:00 -0400 Subject: [PATCH] adds airplay speakers as media_players --- .../components/media_player/itunes.py | 168 +++++++++++++++++- 1 file changed, 164 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/media_player/itunes.py b/homeassistant/components/media_player/itunes.py index c1a22ec78f9..7bb58e23a51 100644 --- a/homeassistant/components/media_player/itunes.py +++ b/homeassistant/components/media_player/itunes.py @@ -30,9 +30,10 @@ import logging from homeassistant.components.media_player import ( MediaPlayerDevice, MEDIA_TYPE_MUSIC, SUPPORT_PAUSE, SUPPORT_SEEK, SUPPORT_VOLUME_SET, SUPPORT_VOLUME_MUTE, SUPPORT_PREVIOUS_TRACK, - SUPPORT_NEXT_TRACK) + SUPPORT_NEXT_TRACK, SUPPORT_TURN_ON, SUPPORT_TURN_OFF, + ATTR_ENTITY_PICTURE, ATTR_SUPPORTED_MEDIA_COMMANDS) from homeassistant.const import ( - STATE_IDLE, STATE_PLAYING, STATE_PAUSED) + STATE_IDLE, STATE_PLAYING, STATE_PAUSED, STATE_OFF, STATE_ON) import requests @@ -41,6 +42,10 @@ _LOGGER = logging.getLogger(__name__) SUPPORT_ITUNES = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \ SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK | SUPPORT_SEEK +SUPPORT_AIRPLAY = SUPPORT_VOLUME_SET | SUPPORT_TURN_ON | SUPPORT_TURN_OFF + +DOMAIN = 'itunes' + class Itunes(object): """ itunes-api client. """ @@ -110,6 +115,24 @@ class Itunes(object): """ Returns a URL of the current track's album art. """ return self._base_url + '/artwork' + def airplay_devices(self): + """ Returns a list of AirPlay devices. """ + return self._request('GET', '/airplay_devices') + + def airplay_device(self, id): + """ Returns an AirPlay device. """ + return self._request('GET', '/airplay_devices/' + id) + + def toggle_airplay_device(self, id, toggle): + """ Toggles airplay device on or off, id, toggle True or False. """ + command = 'on' if toggle else 'off' + return self._request('PUT', '/airplay_devices/' + id + '/' + command) + + def set_volume_airplay_device(self, id, level): + """ Sets volume, returns current state of device, id,level 0-100. """ + path = '/airplay_devices/' + id + '/volume' + return self._request('PUT', path, {'level': level}) + # pylint: disable=unused-argument # pylint: disable=abstract-method # pylint: disable=too-many-instance-attributes @@ -122,7 +145,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None): ItunesDevice( config.get('name', 'iTunes'), config.get('host'), - config.get('port') + config.get('port'), + add_devices ) ]) @@ -132,10 +156,11 @@ class ItunesDevice(MediaPlayerDevice): # pylint: disable=too-many-public-methods - def __init__(self, name, host, port): + def __init__(self, name, host, port, add_devices): self._name = name self._host = host self._port = port + self._add_devices = add_devices self.client = Itunes(self._host, self._port) @@ -149,6 +174,8 @@ class ItunesDevice(MediaPlayerDevice): self.player_state = None + self.airplay_devices = {} + self.update() def update_state(self, state_hash): @@ -191,6 +218,28 @@ class ItunesDevice(MediaPlayerDevice): now_playing = self.client.now_playing() self.update_state(now_playing) + found_devices = self.client.airplay_devices() + found_devices = found_devices.get('airplay_devices', []) + + new_devices = [] + + for device_data in found_devices: + device_id = device_data.get('id') + + if self.airplay_devices.get(device_id): + # update it + airplay_device = self.airplay_devices.get(device_id) + airplay_device.update_state(device_data) + else: + # add it + airplay_device = AirPlayDevice(device_id, self.client) + airplay_device.update_state(device_data) + self.airplay_devices[device_id] = airplay_device + new_devices.append(airplay_device) + + if new_devices: + self._add_devices(new_devices) + @property def is_volume_muted(self): """ Boolean if volume is currently muted. """ @@ -271,3 +320,114 @@ class ItunesDevice(MediaPlayerDevice): """ media_previous media player. """ response = self.client.previous() self.update_state(response) + + +class AirPlayDevice(MediaPlayerDevice): + """ Represents an AirPlay device via an iTunes-API instance. """ + + # pylint: disable=too-many-public-methods + + def __init__(self, id, client): + self._id = id + + self.client = client + + self.device_name = "AirPlay" + self.kind = None + self.active = False + self.selected = False + self.volume = 0 + self.supports_audio = False + self.supports_video = False + + def update_state(self, state_hash): + if 'player_state' in state_hash: + self.player_state = state_hash.get('player_state', None) + + if 'name' in state_hash: + self.device_name = state_hash.get('name', 'AirPlay') + + if 'kind' in state_hash: + self.kind = state_hash.get('kind', None) + + if 'active' in state_hash: + self.active = state_hash.get('active', None) + + if 'selected' in state_hash: + self.selected = state_hash.get('selected', None) + + if 'sound_volume' in state_hash: + self.volume = state_hash.get('sound_volume', 0) + + if 'supports_audio' in state_hash: + self.supports_audio = state_hash.get('supports_audio', None) + + if 'supports_video' in state_hash: + self.supports_video = state_hash.get('supports_video', None) + + @property + def name(self): + """ Returns the name of the device. """ + return self.device_name + + @property + def state(self): + """ Returns the state of the device. """ + + if self.selected is True: + return STATE_ON + else: + return STATE_OFF + + def update(self): + """ Retrieve latest state. """ + + @property + def volume_level(self): + return float(self.volume)/100.0 + + @property + def media_content_type(self): + return MEDIA_TYPE_MUSIC + + @property + def supported_media_commands(self): + """ Flags of media commands that are supported. """ + return SUPPORT_AIRPLAY + + @property + def device_state_attributes(self): + """ Return the state attributes. """ + state_attr = {} + state_attr[ATTR_SUPPORTED_MEDIA_COMMANDS] = SUPPORT_AIRPLAY + + if self.state == STATE_OFF: + state_attr[ATTR_ENTITY_PICTURE] = \ + ('https://cloud.githubusercontent.com/assets/260/9833073' + '/6eb5c906-5958-11e5-9b4a-472cdf36be16.png') + else: + state_attr[ATTR_ENTITY_PICTURE] = \ + ('https://cloud.githubusercontent.com/assets/260/9833072' + '/6eb13cce-5958-11e5-996f-e2aaefbc9a24.png') + + return state_attr + + def set_volume_level(self, volume): + """ set volume level, range 0..1. """ + volume = int(volume * 100) + response = self.client.set_volume_airplay_device(self._id, volume) + self.update_state(response) + + def turn_on(self): + """ Select AirPlay. """ + self.update_state({"selected": True}) + self.update_ha_state() + response = self.client.toggle_airplay_device(self._id, True) + self.update_state(response) + + def turn_off(self): + """ Deselect AirPlay. """ + self.update_state({"selected": False}) + self.update_ha_state() + response = self.client.toggle_airplay_device(self._id, False) + self.update_state(response)