diff --git a/homeassistant/components/homekit/const.py b/homeassistant/components/homekit/const.py index f0224ce71f40ae..8dc08142d5beb2 100644 --- a/homeassistant/components/homekit/const.py +++ b/homeassistant/components/homekit/const.py @@ -19,6 +19,9 @@ CONF_ENTITY_CONFIG = "entity_config" CONF_FEATURE = "feature" CONF_FEATURE_LIST = "feature_list" +CONF_REMOTE = "remote" +CONF_REMOTE_ID = "remote_id" +CONF_KEY_MAP = "key_map" CONF_FILTER = "filter" CONF_LINKED_BATTERY_SENSOR = "linked_battery_sensor" CONF_LINKED_BATTERY_CHARGING_SENSOR = "linked_battery_charging_sensor" @@ -39,6 +42,21 @@ FEATURE_PLAY_STOP = "play_stop" FEATURE_TOGGLE_MUTE = "toggle_mute" +# #### Keys #### +KEY_REWIND = "rewind" +KEY_FAST_FORWARD = "fast_forward" +KEY_NEXT_TRACK = "next_track" +KEY_PREVIOUS_TRACK = "previous_track" +KEY_UP = "up" +KEY_DOWN = "down" +KEY_LEFT = "left" +KEY_RIGHT = "right" +KEY_SELECT = "select" +KEY_BACK = "back" +KEY_EXIT = "exit" +KEY_PLAY_PAUSE = "play_pause" +KEY_INFO = "info" + # #### HomeKit Component Event #### EVENT_HOMEKIT_CHANGED = "homekit_state_change" diff --git a/homeassistant/components/homekit/manifest.json b/homeassistant/components/homekit/manifest.json index 482cef57ca756b..691f9faa0e77f9 100644 --- a/homeassistant/components/homekit/manifest.json +++ b/homeassistant/components/homekit/manifest.json @@ -3,7 +3,7 @@ "name": "HomeKit", "documentation": "https://www.home-assistant.io/integrations/homekit", "requirements": ["HAP-python==2.8.2","fnvhash==0.1.0","PyQRCode==1.2.1","base36==0.1.1"], - "dependencies": ["http"], + "dependencies": ["http", "remote"], "after_dependencies": ["logbook"], "codeowners": ["@bdraco"] } diff --git a/homeassistant/components/homekit/type_media_players.py b/homeassistant/components/homekit/type_media_players.py index 78c11fc41f9ea4..7ad3aa341b1bbb 100644 --- a/homeassistant/components/homekit/type_media_players.py +++ b/homeassistant/components/homekit/type_media_players.py @@ -1,4 +1,5 @@ """Class to hold all media player accessories.""" +from copy import copy import logging from pyhap.const import CATEGORY_SWITCH, CATEGORY_TELEVISION @@ -17,6 +18,11 @@ SUPPORT_VOLUME_SET, SUPPORT_VOLUME_STEP, ) +from homeassistant.components.remote import ( + ATTR_COMMAND, + DOMAIN as REMOTE_DOMAIN, + SERVICE_SEND_COMMAND, +) from homeassistant.const import ( ATTR_ENTITY_ID, ATTR_SUPPORTED_FEATURES, @@ -36,6 +42,7 @@ STATE_STANDBY, STATE_UNKNOWN, ) +from homeassistant.helpers.script import Script from . import TYPES from .accessories import HomeAccessory @@ -56,10 +63,26 @@ CHAR_VOLUME_CONTROL_TYPE, CHAR_VOLUME_SELECTOR, CONF_FEATURE_LIST, + CONF_KEY_MAP, + CONF_REMOTE, + CONF_REMOTE_ID, FEATURE_ON_OFF, FEATURE_PLAY_PAUSE, FEATURE_PLAY_STOP, FEATURE_TOGGLE_MUTE, + KEY_BACK, + KEY_DOWN, + KEY_EXIT, + KEY_FAST_FORWARD, + KEY_INFO, + KEY_LEFT, + KEY_NEXT_TRACK, + KEY_PLAY_PAUSE, + KEY_PREVIOUS_TRACK, + KEY_REWIND, + KEY_RIGHT, + KEY_SELECT, + KEY_UP, SERV_INPUT_SOURCE, SERV_SWITCH, SERV_TELEVISION, @@ -68,22 +91,39 @@ _LOGGER = logging.getLogger(__name__) -MEDIA_PLAYER_KEYS = { - # 0: "Rewind", - # 1: "FastForward", - # 2: "NextTrack", - # 3: "PreviousTrack", - # 4: "ArrowUp", - # 5: "ArrowDown", - # 6: "ArrowLeft", - # 7: "ArrowRight", - # 8: "Select", - # 9: "Back", - # 10: "Exit", +REMOTE_KEYS = { + 0: "rewind", + 1: "fast_forward", + 2: "next_track", + 3: "previous_track", + 4: "up", + 5: "down", + 6: "left", + 7: "right", + 8: "select", + 9: "back", + 10: "exit", 11: SERVICE_MEDIA_PLAY_PAUSE, - # 15: "Information", + 15: "info", +} + +REMOTE_KEY_TO_NUMBER = { + KEY_REWIND: 0, + KEY_FAST_FORWARD: 1, + KEY_NEXT_TRACK: 2, + KEY_PREVIOUS_TRACK: 3, + KEY_UP: 4, + KEY_DOWN: 5, + KEY_LEFT: 6, + KEY_RIGHT: 7, + KEY_SELECT: 8, + KEY_BACK: 9, + KEY_EXIT: 10, + KEY_PLAY_PAUSE: 11, + KEY_INFO: 15, } + MODE_FRIENDLY_NAME = { FEATURE_ON_OFF: "Power", FEATURE_PLAY_PAUSE: "Play/Pause", @@ -269,6 +309,23 @@ def __init__(self, *args): CHAR_REMOTE_KEY, setter_callback=self.set_remote_key ) + self._remote = None + if CONF_REMOTE in self.config: + self._remote = ( + self.config[CONF_REMOTE] + if isinstance(self.config[CONF_REMOTE], str) + else self.config[CONF_REMOTE][CONF_REMOTE_ID] + ) + + self._keys = copy(REMOTE_KEYS) + if CONF_KEY_MAP in self.config[CONF_REMOTE]: + for key, value in self.config[CONF_REMOTE][CONF_KEY_MAP].items(): + self._keys[REMOTE_KEY_TO_NUMBER[key]] = ( + value + if isinstance(value, str) + else Script(self.hass, value) + ) + if CHAR_VOLUME_SELECTOR in self.chars_speaker: serv_speaker = self.add_preload_service( SERV_TELEVISION_SPEAKER, self.chars_speaker @@ -358,10 +415,11 @@ def set_input_source(self, value): def set_remote_key(self, value): """Send remote key value if call came from HomeKit.""" _LOGGER.debug("%s: Set remote key to %s", self.entity_id, value) - service = MEDIA_PLAYER_KEYS.get(value) - if service: - # Handle Play Pause - if service == SERVICE_MEDIA_PLAY_PAUSE: + key = REMOTE_KEYS.get(value) + + if key: + # Handle play/pause + if key == SERVICE_MEDIA_PLAY_PAUSE: state = self.hass.states.get(self.entity_id).state if state in (STATE_PLAYING, STATE_PAUSED): service = ( @@ -369,8 +427,22 @@ def set_remote_key(self, value): if state == STATE_PAUSED else SERVICE_MEDIA_PAUSE ) - params = {ATTR_ENTITY_ID: self.entity_id} - self.call_service(DOMAIN, service, params) + + params = {ATTR_ENTITY_ID: self.entity_id} + self.call_service(DOMAIN, service, params) + return + if isinstance(key, str): + if self._remote is not None: + params = {ATTR_ENTITY_ID: self._remote, ATTR_COMMAND: key} + self.call_service(REMOTE_DOMAIN, SERVICE_SEND_COMMAND, params) + return + else: + key.run() + return + + _LOGGER.debug( + "Remote entity undefined or script not specified for this key" + ) def update_state(self, new_state): """Update Television state after state changed.""" diff --git a/homeassistant/components/homekit/util.py b/homeassistant/components/homekit/util.py index 0295440df499d6..c0118cf11a2f4f 100644 --- a/homeassistant/components/homekit/util.py +++ b/homeassistant/components/homekit/util.py @@ -7,7 +7,7 @@ import pyqrcode import voluptuous as vol -from homeassistant.components import fan, media_player, sensor +from homeassistant.components import fan, media_player, remote, sensor from homeassistant.const import ( ATTR_CODE, ATTR_SUPPORTED_FEATURES, @@ -22,8 +22,11 @@ from .const import ( CONF_FEATURE, CONF_FEATURE_LIST, + CONF_KEY_MAP, CONF_LINKED_BATTERY_SENSOR, CONF_LOW_BATTERY_THRESHOLD, + CONF_REMOTE, + CONF_REMOTE_ID, DEFAULT_LOW_BATTERY_THRESHOLD, FEATURE_ON_OFF, FEATURE_PLAY_PAUSE, @@ -32,6 +35,19 @@ HOMEKIT_NOTIFY_ID, HOMEKIT_PAIRING_QR, HOMEKIT_PAIRING_QR_SECRET, + KEY_BACK, + KEY_DOWN, + KEY_EXIT, + KEY_FAST_FORWARD, + KEY_INFO, + KEY_LEFT, + KEY_NEXT_TRACK, + KEY_PLAY_PAUSE, + KEY_PREVIOUS_TRACK, + KEY_REWIND, + KEY_RIGHT, + KEY_SELECT, + KEY_UP, TYPE_FAUCET, TYPE_OUTLET, TYPE_SHOWER, @@ -53,15 +69,11 @@ } ) -FEATURE_SCHEMA = BASIC_INFO_SCHEMA.extend( - {vol.Optional(CONF_FEATURE_LIST, default=None): cv.ensure_list} -) - CODE_SCHEMA = BASIC_INFO_SCHEMA.extend( {vol.Optional(ATTR_CODE, default=None): vol.Any(None, cv.string)} ) -MEDIA_PLAYER_SCHEMA = vol.Schema( +FEATURE_SCHEMA = vol.Schema( { vol.Required(CONF_FEATURE): vol.All( cv.string, @@ -77,6 +89,42 @@ } ) +KEY_MAP_SCHEMA = vol.All( + { + vol.Optional(KEY_REWIND): vol.Any(cv.SCRIPT_SCHEMA, cv.string), + vol.Optional(KEY_FAST_FORWARD): vol.Any(cv.SCRIPT_SCHEMA, cv.string), + vol.Optional(KEY_NEXT_TRACK): vol.Any(cv.SCRIPT_SCHEMA, cv.string), + vol.Optional(KEY_PREVIOUS_TRACK): vol.Any(cv.SCRIPT_SCHEMA, cv.string), + vol.Optional(KEY_UP): vol.Any(cv.SCRIPT_SCHEMA, cv.string), + vol.Optional(KEY_DOWN): vol.Any(cv.SCRIPT_SCHEMA, cv.string), + vol.Optional(KEY_LEFT): vol.Any(cv.SCRIPT_SCHEMA, cv.string), + vol.Optional(KEY_RIGHT): vol.Any(cv.SCRIPT_SCHEMA, cv.string), + vol.Optional(KEY_SELECT): vol.Any(cv.SCRIPT_SCHEMA, cv.string), + vol.Optional(KEY_BACK): vol.Any(cv.SCRIPT_SCHEMA, cv.string), + vol.Optional(KEY_EXIT): vol.Any(cv.SCRIPT_SCHEMA, cv.string), + vol.Optional(KEY_PLAY_PAUSE): vol.Any(cv.SCRIPT_SCHEMA, cv.string), + vol.Optional(KEY_INFO): vol.Any(cv.SCRIPT_SCHEMA, cv.string), + } +) + +REMOTE_SCHEMA = vol.All( + { + vol.Optional(CONF_REMOTE_ID, default=None): vol.Any( + None, cv.entity_domain(remote.DOMAIN) + ), + vol.Optional(CONF_KEY_MAP): KEY_MAP_SCHEMA, + } +) + +MEDIA_PLAYER_SCHEMA = BASIC_INFO_SCHEMA.extend( + { + vol.Optional(CONF_FEATURE_LIST, default=None): cv.ensure_list, + vol.Optional(CONF_REMOTE, default=None): vol.Any( + cv.entity_domain(remote.DOMAIN), REMOTE_SCHEMA + ), + } +) + SWITCH_TYPE_SCHEMA = BASIC_INFO_SCHEMA.extend( { vol.Optional(CONF_TYPE, default=TYPE_SWITCH): vol.All(