From 75a049808096422f758362afe24138cffee813e0 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 11 Mar 2014 22:45:05 -0700 Subject: [PATCH] Upgraded to use new version of pychromecast --- home-assistant.conf.default | 1 - homeassistant/bootstrap.py | 5 +- homeassistant/components/__init__.py | 7 + homeassistant/components/chromecast.py | 251 ++++++++++++++++++++----- homeassistant/components/keyboard.py | 49 +++-- homeassistant/external/pychromecast | 1 - 6 files changed, 246 insertions(+), 68 deletions(-) delete mode 160000 homeassistant/external/pychromecast diff --git a/home-assistant.conf.default b/home-assistant.conf.default index 5213038873a..867cea6ce17 100644 --- a/home-assistant.conf.default +++ b/home-assistant.conf.default @@ -20,7 +20,6 @@ username=admin password=PASSWORD [chromecast] -host=192.168.1.3 [downloader] download_dir=downloads diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index 3343caa4370..72dbddd5ac7 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -119,11 +119,10 @@ def from_config_file(config_path): sun = None # Chromecast - if has_opt("chromecast", "host"): + if has_section("chromecast"): chromecast = load_module('chromecast') - chromecast_started = chromecast.setup(bus, statemachine, - get_opt("chromecast", "host")) + chromecast_started = chromecast.setup(bus, statemachine) add_status("Chromecast", chromecast_started) else: diff --git a/homeassistant/components/__init__.py b/homeassistant/components/__init__.py index 653ac55360d..5fc9862c696 100644 --- a/homeassistant/components/__init__.py +++ b/homeassistant/components/__init__.py @@ -30,6 +30,13 @@ STATE_HOME = 'home' SERVICE_TURN_ON = 'turn_on' SERVICE_TURN_OFF = 'turn_off' +SERVICE_VOLUME_UP = "volume_up" +SERVICE_VOLUME_DOWN = "volume_down" +SERVICE_VOLUME_MUTE = "volume_mute" +SERVICE_MEDIA_PLAY_PAUSE = "media_play_pause" +SERVICE_MEDIA_NEXT_TRACK = "media_next_track" +SERVICE_MEDIA_PREV_TRACK = "media_prev_track" + _LOADED_MOD = {} diff --git a/homeassistant/components/chromecast.py b/homeassistant/components/chromecast.py index 404b3567623..3413de42f6a 100644 --- a/homeassistant/components/chromecast.py +++ b/homeassistant/components/chromecast.py @@ -21,6 +21,18 @@ ATTR_FRIENDLY_NAME = 'friendly_name' ATTR_HOST = 'host' ATTR_STATE = 'state' ATTR_OPTIONS = 'options' +ATTR_MEDIA_STATE = 'media_state' +ATTR_MEDIA_CONTENT_ID = 'media_content_id' +ATTR_MEDIA_TITLE = 'media_title' +ATTR_MEDIA_ARTIST = 'media_artist' +ATTR_MEDIA_ALBUM = 'media_album' +ATTR_MEDIA_IMAGE_URL = 'media_image_url' +ATTR_MEDIA_VOLUME = 'media_volume' +ATTR_MEDIA_DURATION = 'media_duration' + +MEDIA_STATE_UNKNOWN = 'unknown' +MEDIA_STATE_PLAYING = 'playing' +MEDIA_STATE_STOPPED = 'stopped' def is_on(statemachine, entity_id=None): @@ -34,83 +46,220 @@ def is_on(statemachine, entity_id=None): for entity_id in entity_ids) -def setup(bus, statemachine, host): +def volume_up(bus, entity_id=None): + """ Send the chromecast the command for volume up. """ + data = {components.ATTR_ENTITY_ID: entity_id} if entity_id else {} + + bus.call_service(DOMAIN, components.SERVICE_VOLUME_UP, data) + + +def volume_down(bus, entity_id=None): + """ Send the chromecast the command for volume down. """ + data = {components.ATTR_ENTITY_ID: entity_id} if entity_id else {} + + bus.call_service(DOMAIN, components.SERVICE_VOLUME_DOWN, data) + + +def media_play_pause(bus, entity_id=None): + """ Send the chromecast the command for play/pause. """ + data = {components.ATTR_ENTITY_ID: entity_id} if entity_id else {} + + bus.call_service(DOMAIN, components.SERVICE_MEDIA_PLAY_PAUSE, data) + + +def media_next_track(bus, entity_id=None): + """ Send the chromecast the command for next track. """ + data = {components.ATTR_ENTITY_ID: entity_id} if entity_id else {} + + bus.call_service(DOMAIN, components.SERVICE_MEDIA_NEXT_TRACK, data) + + +def media_prev_track(bus, entity_id=None): + """ Send the chromecast the command for prev track. """ + data = {components.ATTR_ENTITY_ID: entity_id} if entity_id else {} + + bus.call_service(DOMAIN, components.SERVICE_MEDIA_PREV_TRACK, data) + + +# pylint: disable=too-many-locals, too-many-branches +def setup(bus, statemachine): """ Listen for chromecast events. """ logger = logging.getLogger(__name__) try: - from homeassistant.external import pychromecast + import pychromecast except ImportError: logger.exception(("Failed to import pychromecast. " - "Did you maybe not cloned the git submodules?")) + "Did you maybe not install the 'pychromecast' " + "dependency?")) return False - logger.info("Getting device status") - device = pychromecast.get_device_status(host) + logger.info("Scanning for Chromecasts") + hosts = pychromecast.discover_chromecasts() - if not device: - logger.error("Could not find Chromecast") + casts = {} + + for host in hosts: + try: + cast = pychromecast.PyChromecast(host) + + entity_id = ENTITY_ID_FORMAT.format( + util.slugify(cast.device.friendly_name)) + + casts[entity_id] = cast + + except pychromecast.ConnectionError: + pass + + if not casts: + logger.error("Could not find Chromecasts") return False - entity = ENTITY_ID_FORMAT.format(util.slugify(device.friendly_name)) + def update_chromecast_state(entity_id, chromecast): + """ Retrieve state of Chromecast and update statemachine. """ + chromecast.refresh() - if not bus.has_service(DOMAIN, components.SERVICE_TURN_OFF): - def turn_off_service(service): - """ Service to exit any running app on the specified ChromeCast and - shows idle screen. Will quit all ChromeCasts if nothing specified. - """ - entity_id = service.data.get(components.ATTR_ENTITY_ID) + status = chromecast.app - entity_ids = [entity_id] if entity_id \ - else util.filter_entity_ids(statemachine.entity_ids, DOMAIN) + state_attr = {ATTR_HOST: chromecast.host, + ATTR_FRIENDLY_NAME: chromecast.device.friendly_name} - for entity_id in entity_ids: - state = statemachine.get_state(entity_id) + if status and status.app_id != pychromecast.APP_ID['HOME']: + state = status.app_id - try: - pychromecast.quit_app(state.attributes[ATTR_HOST]) - except (AttributeError, KeyError): - # AttributeError: state returned None - # KeyError: ATTR_HOST did not exist - pass + ramp = chromecast.get_protocol(pychromecast.PROTOCOL_RAMP) - bus.register_service(DOMAIN, components.SERVICE_TURN_OFF, - turn_off_service) + if ramp and ramp.state != pychromecast.RAMP_STATE_UNKNOWN: + + if ramp.state == pychromecast.RAMP_STATE_PLAYING: + state_attr[ATTR_MEDIA_STATE] = MEDIA_STATE_PLAYING + else: + state_attr[ATTR_MEDIA_STATE] = MEDIA_STATE_STOPPED + + if ramp.content_id: + state_attr[ATTR_MEDIA_CONTENT_ID] = ramp.content_id + + if ramp.title: + state_attr[ATTR_MEDIA_TITLE] = ramp.title + + if ramp.artist: + state_attr[ATTR_MEDIA_ARTIST] = ramp.artist + + if ramp.album: + state_attr[ATTR_MEDIA_ALBUM] = ramp.album + + if ramp.image_url: + state_attr[ATTR_MEDIA_IMAGE_URL] = ramp.image_url + + if ramp.duration: + state_attr[ATTR_MEDIA_DURATION] = ramp.duration + + state_attr[ATTR_MEDIA_VOLUME] = ramp.volume + else: + state = STATE_NO_APP + + statemachine.set_state(entity_id, state, state_attr) + + def update_chromecast_states(time): # pylint: disable=unused-argument + """ Updates all chromecast states. """ + logger.info("Updating Chromecast status") + + for entity_id, cast in casts.items(): + update_chromecast_state(entity_id, cast) + + def _service_to_entities(service): + """ Helper method to get entities from service. """ + entity_id = service.data.get(components.ATTR_ENTITY_ID) + + if entity_id: + cast = casts.get(entity_id) + + if cast: + yield entity_id, cast + + else: + for item in casts.items(): + yield item + + def turn_off_service(service): + """ Service to exit any running app on the specified ChromeCast and + shows idle screen. Will quit all ChromeCasts if nothing specified. + """ + for entity_id, cast in _service_to_entities(service): + cast.quit_app() + update_chromecast_state(entity_id, cast) + + def volume_up_service(service): + """ Service to send the chromecast the command for volume up. """ + for _, cast in _service_to_entities(service): + ramp = cast.get_protocol(pychromecast.PROTOCOL_RAMP) + + if ramp: + ramp.volume_up() + + def volume_down_service(service): + """ Service to send the chromecast the command for volume down. """ + for _, cast in _service_to_entities(service): + ramp = cast.get_protocol(pychromecast.PROTOCOL_RAMP) + + if ramp: + ramp.volume_down() + + def media_play_pause_service(service): + """ Service to send the chromecast the command for play/pause. """ + for _, cast in _service_to_entities(service): + ramp = cast.get_protocol(pychromecast.PROTOCOL_RAMP) + + if ramp: + ramp.playpause() + + def media_next_track_service(service): + """ Service to send the chromecast the command for next track. """ + for entity_id, cast in _service_to_entities(service): + ramp = cast.get_protocol(pychromecast.PROTOCOL_RAMP) + + if ramp: + ramp.next() + update_chromecast_state(entity_id, cast) + + def play_youtube_video_service(service, video_id): + """ Plays specified video_id on the Chromecast's YouTube channel. """ + if video_id: # if service.data.get('video') returned None + for entity_id, cast in _service_to_entities(service): + pychromecast.play_youtube_video(video_id, cast.host) + update_chromecast_state(entity_id, cast) + + ha.track_time_change(bus, update_chromecast_states) + + bus.register_service(DOMAIN, components.SERVICE_TURN_OFF, + turn_off_service) + + bus.register_service(DOMAIN, components.SERVICE_VOLUME_UP, + volume_up_service) + + bus.register_service(DOMAIN, components.SERVICE_VOLUME_DOWN, + volume_down_service) + + bus.register_service(DOMAIN, components.SERVICE_MEDIA_PLAY_PAUSE, + media_play_pause_service) + + bus.register_service(DOMAIN, components.SERVICE_MEDIA_NEXT_TRACK, + media_next_track_service) bus.register_service(DOMAIN, "start_fireplace", lambda service: - pychromecast.play_youtube_video(host, "eyU3bRy2x44")) + play_youtube_video_service(service, "eyU3bRy2x44")) bus.register_service(DOMAIN, "start_epic_sax", lambda service: - pychromecast.play_youtube_video(host, "kxopViU98Xo")) + play_youtube_video_service(service, "kxopViU98Xo")) bus.register_service(DOMAIN, SERVICE_YOUTUBE_VIDEO, lambda service: - pychromecast.play_youtube_video( - host, service.data['video'])) + play_youtube_video_service(service, + service.data.get('video'))) - def update_chromecast_state(time): # pylint: disable=unused-argument - """ Retrieve state of Chromecast and update statemachine. """ - logger.info("Updating app status") - status = pychromecast.get_app_status(host) - - if status: - state = STATE_NO_APP if status.name == pychromecast.APP_ID_HOME \ - else status.name - statemachine.set_state(entity, state, - {ATTR_FRIENDLY_NAME: - pychromecast.get_friendly_name( - status.name), - ATTR_HOST: host, - ATTR_STATE: status.state, - ATTR_OPTIONS: status.options}) - else: - statemachine.set_state(entity, STATE_NO_APP, {ATTR_HOST: host}) - - ha.track_time_change(bus, update_chromecast_state) - - update_chromecast_state(None) + update_chromecast_states(None) return True diff --git a/homeassistant/components/keyboard.py b/homeassistant/components/keyboard.py index 746aed6564d..095f39efe71 100644 --- a/homeassistant/components/keyboard.py +++ b/homeassistant/components/keyboard.py @@ -6,14 +6,39 @@ Provides functionality to emulate keyboard presses on host machine. """ import logging +import homeassistant.components as components + DOMAIN = "keyboard" -SERVICE_KEYBOARD_VOLUME_UP = "volume_up" -SERVICE_KEYBOARD_VOLUME_DOWN = "volume_down" -SERVICE_KEYBOARD_VOLUME_MUTE = "volume_mute" -SERVICE_KEYBOARD_MEDIA_PLAY_PAUSE = "media_play_pause" -SERVICE_KEYBOARD_MEDIA_NEXT_TRACK = "media_next_track" -SERVICE_KEYBOARD_MEDIA_PREV_TRACK = "media_prev_track" + +def volume_up(bus): + """ Press the keyboard button for volume up. """ + bus.call_service(DOMAIN, components.SERVICE_VOLUME_UP) + + +def volume_down(bus): + """ Press the keyboard button for volume down. """ + bus.call_service(DOMAIN, components.SERVICE_VOLUME_DOWN) + + +def volume_mute(bus): + """ Press the keyboard button for muting volume. """ + bus.call_service(DOMAIN, components.SERVICE_VOLUME_MUTE) + + +def media_play_pause(bus): + """ Press the keyboard button for play/pause. """ + bus.call_service(DOMAIN, components.SERVICE_MEDIA_PLAY_PAUSE) + + +def media_next_track(bus): + """ Press the keyboard button for next track. """ + bus.call_service(DOMAIN, components.SERVICE_MEDIA_NEXT_TRACK) + + +def media_prev_track(bus): + """ Press the keyboard button for prev track. """ + bus.call_service(DOMAIN, components.SERVICE_MEDIA_PREV_TRACK) def setup(bus): @@ -29,27 +54,27 @@ def setup(bus): keyboard = pykeyboard.PyKeyboard() keyboard.special_key_assignment() - bus.register_service(DOMAIN, SERVICE_KEYBOARD_VOLUME_UP, + bus.register_service(DOMAIN, components.SERVICE_VOLUME_UP, lambda service: keyboard.tap_key(keyboard.volume_up_key)) - bus.register_service(DOMAIN, SERVICE_KEYBOARD_VOLUME_DOWN, + bus.register_service(DOMAIN, components.SERVICE_VOLUME_DOWN, lambda service: keyboard.tap_key(keyboard.volume_down_key)) - bus.register_service(DOMAIN, SERVICE_KEYBOARD_VOLUME_MUTE, + bus.register_service(DOMAIN, components.SERVICE_VOLUME_MUTE, lambda service: keyboard.tap_key(keyboard.volume_mute_key)) - bus.register_service(DOMAIN, SERVICE_KEYBOARD_MEDIA_PLAY_PAUSE, + bus.register_service(DOMAIN, components.SERVICE_MEDIA_PLAY_PAUSE, lambda service: keyboard.tap_key(keyboard.media_play_pause_key)) - bus.register_service(DOMAIN, SERVICE_KEYBOARD_MEDIA_NEXT_TRACK, + bus.register_service(DOMAIN, components.SERVICE_MEDIA_NEXT_TRACK, lambda service: keyboard.tap_key(keyboard.media_next_track_key)) - bus.register_service(DOMAIN, SERVICE_KEYBOARD_MEDIA_PREV_TRACK, + bus.register_service(DOMAIN, components.SERVICE_MEDIA_PREV_TRACK, lambda service: keyboard.tap_key(keyboard.media_prev_track_key)) diff --git a/homeassistant/external/pychromecast b/homeassistant/external/pychromecast deleted file mode 160000 index 3d697b56a4a..00000000000 --- a/homeassistant/external/pychromecast +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 3d697b56a4a9178b9970e0596340a5e507ffd67c