Update homekit to improve representation of activity based remotes (#47261)
parent
3f2ca16ad7
commit
fd310e1f41
|
@ -50,6 +50,7 @@ from . import ( # noqa: F401
|
|||
type_lights,
|
||||
type_locks,
|
||||
type_media_players,
|
||||
type_remotes,
|
||||
type_security_systems,
|
||||
type_sensors,
|
||||
type_switches,
|
||||
|
|
|
@ -12,6 +12,7 @@ from homeassistant.components.cover import (
|
|||
DEVICE_CLASS_WINDOW,
|
||||
)
|
||||
from homeassistant.components.media_player import DEVICE_CLASS_TV
|
||||
from homeassistant.components.remote import SUPPORT_ACTIVITY
|
||||
from homeassistant.const import (
|
||||
ATTR_BATTERY_CHARGING,
|
||||
ATTR_BATTERY_LEVEL,
|
||||
|
@ -103,6 +104,7 @@ def get_accessory(hass, driver, state, aid, config):
|
|||
|
||||
a_type = None
|
||||
name = config.get(CONF_NAME, state.name)
|
||||
features = state.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
|
||||
|
||||
if state.domain == "alarm_control_panel":
|
||||
a_type = "SecuritySystem"
|
||||
|
@ -115,7 +117,6 @@ def get_accessory(hass, driver, state, aid, config):
|
|||
|
||||
elif state.domain == "cover":
|
||||
device_class = state.attributes.get(ATTR_DEVICE_CLASS)
|
||||
features = state.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
|
||||
|
||||
if device_class in (DEVICE_CLASS_GARAGE, DEVICE_CLASS_GATE) and features & (
|
||||
cover.SUPPORT_OPEN | cover.SUPPORT_CLOSE
|
||||
|
@ -179,6 +180,9 @@ def get_accessory(hass, driver, state, aid, config):
|
|||
elif state.domain == "vacuum":
|
||||
a_type = "Vacuum"
|
||||
|
||||
elif state.domain == "remote" and features & SUPPORT_ACTIVITY:
|
||||
a_type = "ActivityRemote"
|
||||
|
||||
elif state.domain in ("automation", "input_boolean", "remote", "scene", "script"):
|
||||
a_type = "Switch"
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ import voluptuous as vol
|
|||
from homeassistant import config_entries
|
||||
from homeassistant.components.camera import DOMAIN as CAMERA_DOMAIN
|
||||
from homeassistant.components.media_player import DOMAIN as MEDIA_PLAYER_DOMAIN
|
||||
from homeassistant.components.remote import DOMAIN as REMOTE_DOMAIN
|
||||
from homeassistant.config_entries import SOURCE_IMPORT
|
||||
from homeassistant.const import (
|
||||
ATTR_FRIENDLY_NAME,
|
||||
|
@ -53,7 +54,7 @@ MODE_EXCLUDE = "exclude"
|
|||
|
||||
INCLUDE_EXCLUDE_MODES = [MODE_EXCLUDE, MODE_INCLUDE]
|
||||
|
||||
DOMAINS_NEED_ACCESSORY_MODE = [CAMERA_DOMAIN, MEDIA_PLAYER_DOMAIN]
|
||||
DOMAINS_NEED_ACCESSORY_MODE = [CAMERA_DOMAIN, MEDIA_PLAYER_DOMAIN, REMOTE_DOMAIN]
|
||||
NEVER_BRIDGED_DOMAINS = [CAMERA_DOMAIN]
|
||||
|
||||
CAMERA_ENTITY_PREFIX = f"{CAMERA_DOMAIN}."
|
||||
|
@ -74,7 +75,7 @@ SUPPORTED_DOMAINS = [
|
|||
"lock",
|
||||
MEDIA_PLAYER_DOMAIN,
|
||||
"person",
|
||||
"remote",
|
||||
REMOTE_DOMAIN,
|
||||
"scene",
|
||||
"script",
|
||||
"sensor",
|
||||
|
@ -93,6 +94,7 @@ DEFAULT_DOMAINS = [
|
|||
"light",
|
||||
"lock",
|
||||
MEDIA_PLAYER_DOMAIN,
|
||||
REMOTE_DOMAIN,
|
||||
"switch",
|
||||
"vacuum",
|
||||
"water_heater",
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
"""Class to hold all media player accessories."""
|
||||
import logging
|
||||
|
||||
from pyhap.const import CATEGORY_SWITCH, CATEGORY_TELEVISION
|
||||
from pyhap.const import CATEGORY_SWITCH
|
||||
|
||||
from homeassistant.components.media_player import (
|
||||
ATTR_INPUT_SOURCE,
|
||||
|
@ -42,17 +42,9 @@ from .accessories import TYPES, HomeAccessory
|
|||
from .const import (
|
||||
ATTR_KEY_NAME,
|
||||
CHAR_ACTIVE,
|
||||
CHAR_ACTIVE_IDENTIFIER,
|
||||
CHAR_CONFIGURED_NAME,
|
||||
CHAR_CURRENT_VISIBILITY_STATE,
|
||||
CHAR_IDENTIFIER,
|
||||
CHAR_INPUT_SOURCE_TYPE,
|
||||
CHAR_IS_CONFIGURED,
|
||||
CHAR_MUTE,
|
||||
CHAR_NAME,
|
||||
CHAR_ON,
|
||||
CHAR_REMOTE_KEY,
|
||||
CHAR_SLEEP_DISCOVER_MODE,
|
||||
CHAR_VOLUME,
|
||||
CHAR_VOLUME_CONTROL_TYPE,
|
||||
CHAR_VOLUME_SELECTOR,
|
||||
|
@ -62,43 +54,15 @@ from .const import (
|
|||
FEATURE_PLAY_PAUSE,
|
||||
FEATURE_PLAY_STOP,
|
||||
FEATURE_TOGGLE_MUTE,
|
||||
KEY_ARROW_DOWN,
|
||||
KEY_ARROW_LEFT,
|
||||
KEY_ARROW_RIGHT,
|
||||
KEY_ARROW_UP,
|
||||
KEY_BACK,
|
||||
KEY_EXIT,
|
||||
KEY_FAST_FORWARD,
|
||||
KEY_INFORMATION,
|
||||
KEY_NEXT_TRACK,
|
||||
KEY_PLAY_PAUSE,
|
||||
KEY_PREVIOUS_TRACK,
|
||||
KEY_REWIND,
|
||||
KEY_SELECT,
|
||||
SERV_INPUT_SOURCE,
|
||||
SERV_SWITCH,
|
||||
SERV_TELEVISION,
|
||||
SERV_TELEVISION_SPEAKER,
|
||||
)
|
||||
from .type_remotes import REMOTE_KEYS, RemoteInputSelectAccessory
|
||||
from .util import get_media_player_features
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
MEDIA_PLAYER_KEYS = {
|
||||
0: KEY_REWIND,
|
||||
1: KEY_FAST_FORWARD,
|
||||
2: KEY_NEXT_TRACK,
|
||||
3: KEY_PREVIOUS_TRACK,
|
||||
4: KEY_ARROW_UP,
|
||||
5: KEY_ARROW_DOWN,
|
||||
6: KEY_ARROW_LEFT,
|
||||
7: KEY_ARROW_RIGHT,
|
||||
8: KEY_SELECT,
|
||||
9: KEY_BACK,
|
||||
10: KEY_EXIT,
|
||||
11: KEY_PLAY_PAUSE,
|
||||
15: KEY_INFORMATION,
|
||||
}
|
||||
|
||||
# Names may not contain special characters
|
||||
# or emjoi (/ is a special character for Apple)
|
||||
|
@ -250,22 +214,22 @@ class MediaPlayer(HomeAccessory):
|
|||
|
||||
|
||||
@TYPES.register("TelevisionMediaPlayer")
|
||||
class TelevisionMediaPlayer(HomeAccessory):
|
||||
class TelevisionMediaPlayer(RemoteInputSelectAccessory):
|
||||
"""Generate a Television Media Player accessory."""
|
||||
|
||||
def __init__(self, *args):
|
||||
"""Initialize a Switch accessory object."""
|
||||
super().__init__(*args, category=CATEGORY_TELEVISION)
|
||||
"""Initialize a Television Media Player accessory object."""
|
||||
super().__init__(
|
||||
SUPPORT_SELECT_SOURCE,
|
||||
ATTR_INPUT_SOURCE,
|
||||
ATTR_INPUT_SOURCE_LIST,
|
||||
*args,
|
||||
)
|
||||
state = self.hass.states.get(self.entity_id)
|
||||
|
||||
self.support_select_source = False
|
||||
|
||||
self.sources = []
|
||||
|
||||
self.chars_tv = [CHAR_REMOTE_KEY]
|
||||
self.chars_speaker = []
|
||||
features = state.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
|
||||
|
||||
self.chars_speaker = []
|
||||
|
||||
self._supports_play_pause = features & (SUPPORT_PLAY | SUPPORT_PAUSE)
|
||||
if features & SUPPORT_VOLUME_MUTE or features & SUPPORT_VOLUME_STEP:
|
||||
self.chars_speaker.extend(
|
||||
|
@ -274,27 +238,11 @@ class TelevisionMediaPlayer(HomeAccessory):
|
|||
if features & SUPPORT_VOLUME_SET:
|
||||
self.chars_speaker.append(CHAR_VOLUME)
|
||||
|
||||
source_list = state.attributes.get(ATTR_INPUT_SOURCE_LIST, [])
|
||||
if source_list and features & SUPPORT_SELECT_SOURCE:
|
||||
self.support_select_source = True
|
||||
|
||||
serv_tv = self.add_preload_service(SERV_TELEVISION, self.chars_tv)
|
||||
self.set_primary_service(serv_tv)
|
||||
serv_tv.configure_char(CHAR_CONFIGURED_NAME, value=self.display_name)
|
||||
serv_tv.configure_char(CHAR_SLEEP_DISCOVER_MODE, value=True)
|
||||
self.char_active = serv_tv.configure_char(
|
||||
CHAR_ACTIVE, setter_callback=self.set_on_off
|
||||
)
|
||||
|
||||
self.char_remote_key = serv_tv.configure_char(
|
||||
CHAR_REMOTE_KEY, setter_callback=self.set_remote_key
|
||||
)
|
||||
|
||||
if CHAR_VOLUME_SELECTOR in self.chars_speaker:
|
||||
serv_speaker = self.add_preload_service(
|
||||
SERV_TELEVISION_SPEAKER, self.chars_speaker
|
||||
)
|
||||
serv_tv.add_linked_service(serv_speaker)
|
||||
self.serv_tv.add_linked_service(serv_speaker)
|
||||
|
||||
name = f"{self.display_name} Volume"
|
||||
serv_speaker.configure_char(CHAR_NAME, value=name)
|
||||
|
@ -318,25 +266,6 @@ class TelevisionMediaPlayer(HomeAccessory):
|
|||
CHAR_VOLUME, setter_callback=self.set_volume
|
||||
)
|
||||
|
||||
if self.support_select_source:
|
||||
self.sources = source_list
|
||||
self.char_input_source = serv_tv.configure_char(
|
||||
CHAR_ACTIVE_IDENTIFIER, setter_callback=self.set_input_source
|
||||
)
|
||||
for index, source in enumerate(self.sources):
|
||||
serv_input = self.add_preload_service(
|
||||
SERV_INPUT_SOURCE, [CHAR_IDENTIFIER, CHAR_NAME]
|
||||
)
|
||||
serv_tv.add_linked_service(serv_input)
|
||||
serv_input.configure_char(CHAR_CONFIGURED_NAME, value=source)
|
||||
serv_input.configure_char(CHAR_NAME, value=source)
|
||||
serv_input.configure_char(CHAR_IDENTIFIER, value=index)
|
||||
serv_input.configure_char(CHAR_IS_CONFIGURED, value=True)
|
||||
input_type = 3 if "hdmi" in source.lower() else 0
|
||||
serv_input.configure_char(CHAR_INPUT_SOURCE_TYPE, value=input_type)
|
||||
serv_input.configure_char(CHAR_CURRENT_VISIBILITY_STATE, value=False)
|
||||
_LOGGER.debug("%s: Added source %s", self.entity_id, source)
|
||||
|
||||
self.async_update_state(state)
|
||||
|
||||
def set_on_off(self, value):
|
||||
|
@ -377,7 +306,7 @@ class TelevisionMediaPlayer(HomeAccessory):
|
|||
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)
|
||||
key_name = MEDIA_PLAYER_KEYS.get(value)
|
||||
key_name = REMOTE_KEYS.get(value)
|
||||
if key_name is None:
|
||||
_LOGGER.warning("%s: Unhandled key press for %s", self.entity_id, value)
|
||||
return
|
||||
|
@ -393,12 +322,13 @@ class TelevisionMediaPlayer(HomeAccessory):
|
|||
service = SERVICE_MEDIA_PLAY_PAUSE
|
||||
params = {ATTR_ENTITY_ID: self.entity_id}
|
||||
self.async_call_service(DOMAIN, service, params)
|
||||
else:
|
||||
# Unhandled keys can be handled by listening to the event bus
|
||||
self.hass.bus.fire(
|
||||
EVENT_HOMEKIT_TV_REMOTE_KEY_PRESSED,
|
||||
{ATTR_KEY_NAME: key_name, ATTR_ENTITY_ID: self.entity_id},
|
||||
)
|
||||
return
|
||||
|
||||
# Unhandled keys can be handled by listening to the event bus
|
||||
self.hass.bus.async_fire(
|
||||
EVENT_HOMEKIT_TV_REMOTE_KEY_PRESSED,
|
||||
{ATTR_KEY_NAME: key_name, ATTR_ENTITY_ID: self.entity_id},
|
||||
)
|
||||
|
||||
@callback
|
||||
def async_update_state(self, new_state):
|
||||
|
@ -424,18 +354,4 @@ class TelevisionMediaPlayer(HomeAccessory):
|
|||
if self.char_mute.value != current_mute_state:
|
||||
self.char_mute.set_value(current_mute_state)
|
||||
|
||||
# Set active input
|
||||
if self.support_select_source and self.sources:
|
||||
source_name = new_state.attributes.get(ATTR_INPUT_SOURCE)
|
||||
_LOGGER.debug("%s: Set current input to %s", self.entity_id, source_name)
|
||||
if source_name in self.sources:
|
||||
index = self.sources.index(source_name)
|
||||
if self.char_input_source.value != index:
|
||||
self.char_input_source.set_value(index)
|
||||
elif hk_state:
|
||||
_LOGGER.warning(
|
||||
"%s: Sources out of sync. Restart Home Assistant",
|
||||
self.entity_id,
|
||||
)
|
||||
if self.char_input_source.value != 0:
|
||||
self.char_input_source.set_value(0)
|
||||
self._async_update_input_state(hk_state, new_state)
|
||||
|
|
|
@ -0,0 +1,214 @@
|
|||
"""Class to hold remote accessories."""
|
||||
from abc import abstractmethod
|
||||
import logging
|
||||
|
||||
from pyhap.const import CATEGORY_TELEVISION
|
||||
|
||||
from homeassistant.components.remote import (
|
||||
ATTR_ACTIVITY,
|
||||
ATTR_ACTIVITY_LIST,
|
||||
ATTR_CURRENT_ACTIVITY,
|
||||
DOMAIN as REMOTE_DOMAIN,
|
||||
SUPPORT_ACTIVITY,
|
||||
)
|
||||
from homeassistant.const import (
|
||||
ATTR_ENTITY_ID,
|
||||
ATTR_SUPPORTED_FEATURES,
|
||||
SERVICE_TURN_OFF,
|
||||
SERVICE_TURN_ON,
|
||||
STATE_ON,
|
||||
)
|
||||
from homeassistant.core import callback
|
||||
|
||||
from .accessories import TYPES, HomeAccessory
|
||||
from .const import (
|
||||
ATTR_KEY_NAME,
|
||||
CHAR_ACTIVE,
|
||||
CHAR_ACTIVE_IDENTIFIER,
|
||||
CHAR_CONFIGURED_NAME,
|
||||
CHAR_CURRENT_VISIBILITY_STATE,
|
||||
CHAR_IDENTIFIER,
|
||||
CHAR_INPUT_SOURCE_TYPE,
|
||||
CHAR_IS_CONFIGURED,
|
||||
CHAR_NAME,
|
||||
CHAR_REMOTE_KEY,
|
||||
CHAR_SLEEP_DISCOVER_MODE,
|
||||
EVENT_HOMEKIT_TV_REMOTE_KEY_PRESSED,
|
||||
KEY_ARROW_DOWN,
|
||||
KEY_ARROW_LEFT,
|
||||
KEY_ARROW_RIGHT,
|
||||
KEY_ARROW_UP,
|
||||
KEY_BACK,
|
||||
KEY_EXIT,
|
||||
KEY_FAST_FORWARD,
|
||||
KEY_INFORMATION,
|
||||
KEY_NEXT_TRACK,
|
||||
KEY_PLAY_PAUSE,
|
||||
KEY_PREVIOUS_TRACK,
|
||||
KEY_REWIND,
|
||||
KEY_SELECT,
|
||||
SERV_INPUT_SOURCE,
|
||||
SERV_TELEVISION,
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
REMOTE_KEYS = {
|
||||
0: KEY_REWIND,
|
||||
1: KEY_FAST_FORWARD,
|
||||
2: KEY_NEXT_TRACK,
|
||||
3: KEY_PREVIOUS_TRACK,
|
||||
4: KEY_ARROW_UP,
|
||||
5: KEY_ARROW_DOWN,
|
||||
6: KEY_ARROW_LEFT,
|
||||
7: KEY_ARROW_RIGHT,
|
||||
8: KEY_SELECT,
|
||||
9: KEY_BACK,
|
||||
10: KEY_EXIT,
|
||||
11: KEY_PLAY_PAUSE,
|
||||
15: KEY_INFORMATION,
|
||||
}
|
||||
|
||||
|
||||
class RemoteInputSelectAccessory(HomeAccessory):
|
||||
"""Generate a InputSelect accessory."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
required_feature,
|
||||
source_key,
|
||||
source_list_key,
|
||||
*args,
|
||||
**kwargs,
|
||||
):
|
||||
"""Initialize a InputSelect accessory object."""
|
||||
super().__init__(*args, category=CATEGORY_TELEVISION, **kwargs)
|
||||
state = self.hass.states.get(self.entity_id)
|
||||
features = state.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
|
||||
|
||||
self.source_key = source_key
|
||||
self.sources = []
|
||||
self.support_select_source = False
|
||||
if features & required_feature:
|
||||
self.sources = state.attributes.get(source_list_key, [])
|
||||
if self.sources:
|
||||
self.support_select_source = True
|
||||
|
||||
self.chars_tv = [CHAR_REMOTE_KEY]
|
||||
serv_tv = self.serv_tv = self.add_preload_service(
|
||||
SERV_TELEVISION, self.chars_tv
|
||||
)
|
||||
self.char_remote_key = self.serv_tv.configure_char(
|
||||
CHAR_REMOTE_KEY, setter_callback=self.set_remote_key
|
||||
)
|
||||
self.set_primary_service(serv_tv)
|
||||
serv_tv.configure_char(CHAR_CONFIGURED_NAME, value=self.display_name)
|
||||
serv_tv.configure_char(CHAR_SLEEP_DISCOVER_MODE, value=True)
|
||||
self.char_active = serv_tv.configure_char(
|
||||
CHAR_ACTIVE, setter_callback=self.set_on_off
|
||||
)
|
||||
|
||||
if not self.support_select_source:
|
||||
return
|
||||
|
||||
self.char_input_source = serv_tv.configure_char(
|
||||
CHAR_ACTIVE_IDENTIFIER, setter_callback=self.set_input_source
|
||||
)
|
||||
for index, source in enumerate(self.sources):
|
||||
serv_input = self.add_preload_service(
|
||||
SERV_INPUT_SOURCE, [CHAR_IDENTIFIER, CHAR_NAME]
|
||||
)
|
||||
serv_tv.add_linked_service(serv_input)
|
||||
serv_input.configure_char(CHAR_CONFIGURED_NAME, value=source)
|
||||
serv_input.configure_char(CHAR_NAME, value=source)
|
||||
serv_input.configure_char(CHAR_IDENTIFIER, value=index)
|
||||
serv_input.configure_char(CHAR_IS_CONFIGURED, value=True)
|
||||
input_type = 3 if "hdmi" in source.lower() else 0
|
||||
serv_input.configure_char(CHAR_INPUT_SOURCE_TYPE, value=input_type)
|
||||
serv_input.configure_char(CHAR_CURRENT_VISIBILITY_STATE, value=False)
|
||||
_LOGGER.debug("%s: Added source %s", self.entity_id, source)
|
||||
|
||||
@abstractmethod
|
||||
def set_on_off(self, value):
|
||||
"""Move switch state to value if call came from HomeKit."""
|
||||
|
||||
@abstractmethod
|
||||
def set_input_source(self, value):
|
||||
"""Send input set value if call came from HomeKit."""
|
||||
|
||||
@abstractmethod
|
||||
def set_remote_key(self, value):
|
||||
"""Send remote key value if call came from HomeKit."""
|
||||
|
||||
@callback
|
||||
def _async_update_input_state(self, hk_state, new_state):
|
||||
"""Update input state after state changed."""
|
||||
# Set active input
|
||||
if not self.support_select_source or not self.sources:
|
||||
return
|
||||
source_name = new_state.attributes.get(self.source_key)
|
||||
_LOGGER.debug("%s: Set current input to %s", self.entity_id, source_name)
|
||||
if source_name in self.sources:
|
||||
index = self.sources.index(source_name)
|
||||
if self.char_input_source.value != index:
|
||||
self.char_input_source.set_value(index)
|
||||
elif hk_state:
|
||||
_LOGGER.warning(
|
||||
"%s: Sources out of sync. Restart Home Assistant",
|
||||
self.entity_id,
|
||||
)
|
||||
if self.char_input_source.value != 0:
|
||||
self.char_input_source.set_value(0)
|
||||
|
||||
|
||||
@TYPES.register("ActivityRemote")
|
||||
class ActivityRemote(RemoteInputSelectAccessory):
|
||||
"""Generate a Activity Remote accessory."""
|
||||
|
||||
def __init__(self, *args):
|
||||
"""Initialize a Activity Remote accessory object."""
|
||||
super().__init__(
|
||||
SUPPORT_ACTIVITY,
|
||||
ATTR_CURRENT_ACTIVITY,
|
||||
ATTR_ACTIVITY_LIST,
|
||||
*args,
|
||||
)
|
||||
self.async_update_state(self.hass.states.get(self.entity_id))
|
||||
|
||||
def set_on_off(self, value):
|
||||
"""Move switch state to value if call came from HomeKit."""
|
||||
_LOGGER.debug('%s: Set switch state for "on_off" to %s', self.entity_id, value)
|
||||
service = SERVICE_TURN_ON if value else SERVICE_TURN_OFF
|
||||
params = {ATTR_ENTITY_ID: self.entity_id}
|
||||
self.async_call_service(REMOTE_DOMAIN, service, params)
|
||||
|
||||
def set_input_source(self, value):
|
||||
"""Send input set value if call came from HomeKit."""
|
||||
_LOGGER.debug("%s: Set current input to %s", self.entity_id, value)
|
||||
source = self.sources[value]
|
||||
params = {ATTR_ENTITY_ID: self.entity_id, ATTR_ACTIVITY: source}
|
||||
self.async_call_service(REMOTE_DOMAIN, SERVICE_TURN_ON, params)
|
||||
|
||||
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)
|
||||
key_name = REMOTE_KEYS.get(value)
|
||||
if key_name is None:
|
||||
_LOGGER.warning("%s: Unhandled key press for %s", self.entity_id, value)
|
||||
return
|
||||
self.hass.bus.async_fire(
|
||||
EVENT_HOMEKIT_TV_REMOTE_KEY_PRESSED,
|
||||
{ATTR_KEY_NAME: key_name, ATTR_ENTITY_ID: self.entity_id},
|
||||
)
|
||||
|
||||
@callback
|
||||
def async_update_state(self, new_state):
|
||||
"""Update Television remote state after state changed."""
|
||||
current_state = new_state.state
|
||||
# Power state remote
|
||||
hk_state = 1 if current_state == STATE_ON else 0
|
||||
_LOGGER.debug("%s: Set current active state to %s", self.entity_id, hk_state)
|
||||
if self.char_active.value != hk_state:
|
||||
self.char_active.set_value(hk_state)
|
||||
|
||||
self._async_update_input_state(hk_state, new_state)
|
|
@ -16,6 +16,7 @@ from homeassistant.components.media_player import (
|
|||
DEVICE_CLASS_TV,
|
||||
DOMAIN as MEDIA_PLAYER_DOMAIN,
|
||||
)
|
||||
from homeassistant.components.remote import DOMAIN as REMOTE_DOMAIN, SUPPORT_ACTIVITY
|
||||
from homeassistant.const import (
|
||||
ATTR_CODE,
|
||||
ATTR_DEVICE_CLASS,
|
||||
|
@ -503,4 +504,6 @@ def state_needs_accessory_mode(state):
|
|||
return (
|
||||
state.domain == MEDIA_PLAYER_DOMAIN
|
||||
and state.attributes.get(ATTR_DEVICE_CLASS) == DEVICE_CLASS_TV
|
||||
or state.domain == REMOTE_DOMAIN
|
||||
and state.attributes.get(ATTR_SUPPORTED_FEATURES) & SUPPORT_ACTIVITY
|
||||
)
|
||||
|
|
|
@ -107,6 +107,7 @@ ALLOWED_USED_COMPONENTS = {
|
|||
"onboarding",
|
||||
"persistent_notification",
|
||||
"person",
|
||||
"remote",
|
||||
"script",
|
||||
"shopping_list",
|
||||
"sun",
|
||||
|
|
|
@ -0,0 +1,148 @@
|
|||
"""Test different accessory types: Remotes."""
|
||||
|
||||
from homeassistant.components.homekit.const import (
|
||||
ATTR_KEY_NAME,
|
||||
ATTR_VALUE,
|
||||
EVENT_HOMEKIT_TV_REMOTE_KEY_PRESSED,
|
||||
KEY_ARROW_RIGHT,
|
||||
)
|
||||
from homeassistant.components.homekit.type_remotes import ActivityRemote
|
||||
from homeassistant.components.remote import (
|
||||
ATTR_ACTIVITY,
|
||||
ATTR_ACTIVITY_LIST,
|
||||
ATTR_CURRENT_ACTIVITY,
|
||||
DOMAIN,
|
||||
SUPPORT_ACTIVITY,
|
||||
)
|
||||
from homeassistant.const import (
|
||||
ATTR_ENTITY_ID,
|
||||
ATTR_SUPPORTED_FEATURES,
|
||||
STATE_OFF,
|
||||
STATE_ON,
|
||||
STATE_STANDBY,
|
||||
)
|
||||
|
||||
from tests.common import async_mock_service
|
||||
|
||||
|
||||
async def test_activity_remote(hass, hk_driver, events, caplog):
|
||||
"""Test if remote accessory and HA are updated accordingly."""
|
||||
entity_id = "remote.harmony"
|
||||
hass.states.async_set(
|
||||
entity_id,
|
||||
None,
|
||||
{
|
||||
ATTR_SUPPORTED_FEATURES: SUPPORT_ACTIVITY,
|
||||
ATTR_CURRENT_ACTIVITY: "Apple TV",
|
||||
ATTR_ACTIVITY_LIST: ["TV", "Apple TV"],
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
acc = ActivityRemote(hass, hk_driver, "ActivityRemote", entity_id, 2, None)
|
||||
await acc.run()
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert acc.aid == 2
|
||||
assert acc.category == 31 # Television
|
||||
|
||||
assert acc.char_active.value == 0
|
||||
assert acc.char_remote_key.value == 0
|
||||
assert acc.char_input_source.value == 1
|
||||
|
||||
hass.states.async_set(
|
||||
entity_id,
|
||||
STATE_ON,
|
||||
{
|
||||
ATTR_SUPPORTED_FEATURES: SUPPORT_ACTIVITY,
|
||||
ATTR_CURRENT_ACTIVITY: "Apple TV",
|
||||
ATTR_ACTIVITY_LIST: ["TV", "Apple TV"],
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert acc.char_active.value == 1
|
||||
|
||||
hass.states.async_set(entity_id, STATE_OFF)
|
||||
await hass.async_block_till_done()
|
||||
assert acc.char_active.value == 0
|
||||
|
||||
hass.states.async_set(entity_id, STATE_ON)
|
||||
await hass.async_block_till_done()
|
||||
assert acc.char_active.value == 1
|
||||
|
||||
hass.states.async_set(entity_id, STATE_STANDBY)
|
||||
await hass.async_block_till_done()
|
||||
assert acc.char_active.value == 0
|
||||
|
||||
hass.states.async_set(
|
||||
entity_id,
|
||||
STATE_ON,
|
||||
{
|
||||
ATTR_SUPPORTED_FEATURES: SUPPORT_ACTIVITY,
|
||||
ATTR_CURRENT_ACTIVITY: "TV",
|
||||
ATTR_ACTIVITY_LIST: ["TV", "Apple TV"],
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert acc.char_input_source.value == 0
|
||||
|
||||
hass.states.async_set(
|
||||
entity_id,
|
||||
STATE_ON,
|
||||
{
|
||||
ATTR_SUPPORTED_FEATURES: SUPPORT_ACTIVITY,
|
||||
ATTR_CURRENT_ACTIVITY: "Apple TV",
|
||||
ATTR_ACTIVITY_LIST: ["TV", "Apple TV"],
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert acc.char_input_source.value == 1
|
||||
|
||||
# Set from HomeKit
|
||||
call_turn_on = async_mock_service(hass, DOMAIN, "turn_on")
|
||||
call_turn_off = async_mock_service(hass, DOMAIN, "turn_off")
|
||||
|
||||
acc.char_active.client_update_value(1)
|
||||
await hass.async_block_till_done()
|
||||
assert call_turn_on
|
||||
assert call_turn_on[0].data[ATTR_ENTITY_ID] == entity_id
|
||||
assert len(events) == 1
|
||||
assert events[-1].data[ATTR_VALUE] is None
|
||||
|
||||
acc.char_active.client_update_value(0)
|
||||
await hass.async_block_till_done()
|
||||
assert call_turn_off
|
||||
assert call_turn_off[0].data[ATTR_ENTITY_ID] == entity_id
|
||||
assert len(events) == 2
|
||||
assert events[-1].data[ATTR_VALUE] is None
|
||||
|
||||
acc.char_input_source.client_update_value(1)
|
||||
await hass.async_block_till_done()
|
||||
assert call_turn_on
|
||||
assert call_turn_on[1].data[ATTR_ENTITY_ID] == entity_id
|
||||
assert call_turn_on[1].data[ATTR_ACTIVITY] == "Apple TV"
|
||||
assert len(events) == 3
|
||||
assert events[-1].data[ATTR_VALUE] is None
|
||||
|
||||
acc.char_input_source.client_update_value(0)
|
||||
await hass.async_block_till_done()
|
||||
assert call_turn_on
|
||||
assert call_turn_on[2].data[ATTR_ENTITY_ID] == entity_id
|
||||
assert call_turn_on[2].data[ATTR_ACTIVITY] == "TV"
|
||||
assert len(events) == 4
|
||||
assert events[-1].data[ATTR_VALUE] is None
|
||||
|
||||
events = []
|
||||
|
||||
def listener(event):
|
||||
events.append(event)
|
||||
|
||||
hass.bus.async_listen(EVENT_HOMEKIT_TV_REMOTE_KEY_PRESSED, listener)
|
||||
|
||||
acc.char_remote_key.client_update_value(20)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
acc.char_remote_key.client_update_value(7)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert len(events) == 1
|
||||
assert events[0].data[ATTR_KEY_NAME] == KEY_ARROW_RIGHT
|
Loading…
Reference in New Issue