Add support for receivers to HomeKit (#100717)
This commit is contained in:
parent
c1b9400833
commit
4c255677c3
8 changed files with 83 additions and 18 deletions
|
@ -183,7 +183,9 @@ def get_accessory( # noqa: C901
|
|||
device_class = state.attributes.get(ATTR_DEVICE_CLASS)
|
||||
feature_list = config.get(CONF_FEATURE_LIST, [])
|
||||
|
||||
if device_class == MediaPlayerDeviceClass.TV:
|
||||
if device_class == MediaPlayerDeviceClass.RECEIVER:
|
||||
a_type = "ReceiverMediaPlayer"
|
||||
elif device_class == MediaPlayerDeviceClass.TV:
|
||||
a_type = "TelevisionMediaPlayer"
|
||||
elif validate_media_player_features(state, feature_list):
|
||||
a_type = "MediaPlayer"
|
||||
|
@ -274,7 +276,7 @@ class HomeAccessory(Accessory): # type: ignore[misc]
|
|||
aid: int,
|
||||
config: dict,
|
||||
*args: Any,
|
||||
category: str = CATEGORY_OTHER,
|
||||
category: int = CATEGORY_OTHER,
|
||||
device_id: str | None = None,
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
|
|
|
@ -115,6 +115,9 @@ TYPE_SPRINKLER = "sprinkler"
|
|||
TYPE_SWITCH = "switch"
|
||||
TYPE_VALVE = "valve"
|
||||
|
||||
# #### Categories ####
|
||||
CATEGORY_RECEIVER = 34
|
||||
|
||||
# #### Services ####
|
||||
SERV_ACCESSORY_INFO = "AccessoryInformation"
|
||||
SERV_AIR_QUALITY_SENSOR = "AirQualitySensor"
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
"include_exclude_mode": "Inclusion Mode",
|
||||
"domains": "[%key:component::homekit::config::step::user::data::include_domains%]"
|
||||
},
|
||||
"description": "HomeKit can be configured expose a bridge or a single accessory. In accessory mode, only a single entity can be used. Accessory mode is required for media players with the TV device class to function properly. Entities in the \u201cDomains to include\u201d will be included to HomeKit. You will be able to select which entities to include or exclude from this list on the next screen.",
|
||||
"description": "HomeKit can be configured expose a bridge or a single accessory. In accessory mode, only a single entity can be used. Accessory mode is required for media players with the TV or RECEIVER device class to function properly. Entities in the \u201cDomains to include\u201d will be included to HomeKit. You will be able to select which entities to include or exclude from this list on the next screen.",
|
||||
"title": "Select mode and domains."
|
||||
},
|
||||
"accessory": {
|
||||
|
@ -57,7 +57,7 @@
|
|||
"data": {
|
||||
"include_domains": "Domains to include"
|
||||
},
|
||||
"description": "Choose the domains to be included. All supported entities in the domain will be included except for categorized entities. A separate HomeKit instance in accessory mode will be created for each tv media player, activity based remote, lock, and camera.",
|
||||
"description": "Choose the domains to be included. All supported entities in the domain will be included except for categorized entities. A separate HomeKit instance in accessory mode will be created for each tv/receiver media player, activity based remote, lock, and camera.",
|
||||
"title": "Select domains to be included"
|
||||
},
|
||||
"pairing": {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
"""Class to hold all media player accessories."""
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
from pyhap.const import CATEGORY_SWITCH
|
||||
|
||||
|
@ -36,6 +37,7 @@ from homeassistant.core import callback
|
|||
from .accessories import TYPES, HomeAccessory
|
||||
from .const import (
|
||||
ATTR_KEY_NAME,
|
||||
CATEGORY_RECEIVER,
|
||||
CHAR_ACTIVE,
|
||||
CHAR_MUTE,
|
||||
CHAR_NAME,
|
||||
|
@ -218,18 +220,20 @@ class MediaPlayer(HomeAccessory):
|
|||
class TelevisionMediaPlayer(RemoteInputSelectAccessory):
|
||||
"""Generate a Television Media Player accessory."""
|
||||
|
||||
def __init__(self, *args):
|
||||
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
||||
"""Initialize a Television Media Player accessory object."""
|
||||
super().__init__(
|
||||
MediaPlayerEntityFeature.SELECT_SOURCE,
|
||||
ATTR_INPUT_SOURCE,
|
||||
ATTR_INPUT_SOURCE_LIST,
|
||||
*args,
|
||||
**kwargs,
|
||||
)
|
||||
state = self.hass.states.get(self.entity_id)
|
||||
assert state
|
||||
features = state.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
|
||||
|
||||
self.chars_speaker = []
|
||||
self.chars_speaker: list[str] = []
|
||||
|
||||
self._supports_play_pause = features & (
|
||||
MediaPlayerEntityFeature.PLAY | MediaPlayerEntityFeature.PAUSE
|
||||
|
@ -358,3 +362,17 @@ class TelevisionMediaPlayer(RemoteInputSelectAccessory):
|
|||
self.char_mute.set_value(current_mute_state)
|
||||
|
||||
self._async_update_input_state(hk_state, new_state)
|
||||
|
||||
|
||||
@TYPES.register("ReceiverMediaPlayer")
|
||||
class ReceiverMediaPlayer(TelevisionMediaPlayer):
|
||||
"""Generate a Receiver Media Player accessory.
|
||||
|
||||
For HomeKit, a Receiver Media Player is exactly the same as a
|
||||
Television Media Player except it has a different category
|
||||
which will tell HomeKit how to render the device.
|
||||
"""
|
||||
|
||||
def __init__(self, *args: Any) -> None:
|
||||
"""Initialize a Receiver Media Player accessory object."""
|
||||
super().__init__(*args, category=CATEGORY_RECEIVER)
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
"""Class to hold remote accessories."""
|
||||
from abc import ABC, abstractmethod
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
from pyhap.const import CATEGORY_TELEVISION
|
||||
|
||||
|
@ -80,19 +81,21 @@ class RemoteInputSelectAccessory(HomeAccessory, ABC):
|
|||
|
||||
def __init__(
|
||||
self,
|
||||
required_feature,
|
||||
source_key,
|
||||
source_list_key,
|
||||
*args,
|
||||
**kwargs,
|
||||
):
|
||||
required_feature: int,
|
||||
source_key: str,
|
||||
source_list_key: str,
|
||||
*args: Any,
|
||||
category: int = CATEGORY_TELEVISION,
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
"""Initialize a InputSelect accessory object."""
|
||||
super().__init__(*args, category=CATEGORY_TELEVISION, **kwargs)
|
||||
super().__init__(*args, category=category, **kwargs)
|
||||
state = self.hass.states.get(self.entity_id)
|
||||
assert state
|
||||
features = state.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
|
||||
|
||||
self._mapped_sources_list = []
|
||||
self._mapped_sources = {}
|
||||
self._mapped_sources_list: list[str] = []
|
||||
self._mapped_sources: dict[str, str] = {}
|
||||
self.source_key = source_key
|
||||
self.source_list_key = source_list_key
|
||||
self.sources = []
|
||||
|
|
|
@ -614,7 +614,8 @@ def state_needs_accessory_mode(state: State) -> bool:
|
|||
|
||||
return (
|
||||
state.domain == MEDIA_PLAYER_DOMAIN
|
||||
and state.attributes.get(ATTR_DEVICE_CLASS) == MediaPlayerDeviceClass.TV
|
||||
and state.attributes.get(ATTR_DEVICE_CLASS)
|
||||
in (MediaPlayerDeviceClass.TV, MediaPlayerDeviceClass.RECEIVER)
|
||||
or state.domain == REMOTE_DOMAIN
|
||||
and state.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
|
||||
& RemoteEntityFeature.ACTIVITY
|
||||
|
|
|
@ -17,7 +17,10 @@ from homeassistant.components.homekit.const import (
|
|||
TYPE_SWITCH,
|
||||
TYPE_VALVE,
|
||||
)
|
||||
from homeassistant.components.media_player import MediaPlayerEntityFeature
|
||||
from homeassistant.components.media_player import (
|
||||
MediaPlayerDeviceClass,
|
||||
MediaPlayerEntityFeature,
|
||||
)
|
||||
from homeassistant.components.sensor import SensorDeviceClass
|
||||
from homeassistant.components.vacuum import VacuumEntityFeature
|
||||
from homeassistant.const import (
|
||||
|
@ -202,7 +205,14 @@ def test_type_covers(type_name, entity_id, state, attrs) -> None:
|
|||
"TelevisionMediaPlayer",
|
||||
"media_player.tv",
|
||||
"on",
|
||||
{ATTR_DEVICE_CLASS: "tv"},
|
||||
{ATTR_DEVICE_CLASS: MediaPlayerDeviceClass.TV},
|
||||
{},
|
||||
),
|
||||
(
|
||||
"ReceiverMediaPlayer",
|
||||
"media_player.receiver",
|
||||
"on",
|
||||
{ATTR_DEVICE_CLASS: MediaPlayerDeviceClass.RECEIVER},
|
||||
{},
|
||||
),
|
||||
],
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
"""Test different accessory types: Media Players."""
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.homekit.accessories import HomeDriver
|
||||
from homeassistant.components.homekit.const import (
|
||||
ATTR_KEY_NAME,
|
||||
ATTR_VALUE,
|
||||
|
@ -15,6 +16,7 @@ from homeassistant.components.homekit.const import (
|
|||
)
|
||||
from homeassistant.components.homekit.type_media_players import (
|
||||
MediaPlayer,
|
||||
ReceiverMediaPlayer,
|
||||
TelevisionMediaPlayer,
|
||||
)
|
||||
from homeassistant.components.media_player import (
|
||||
|
@ -629,3 +631,29 @@ async def test_media_player_television_unsafe_chars(
|
|||
assert events[-1].data[ATTR_VALUE] is None
|
||||
|
||||
assert acc.char_input_source.value == 4
|
||||
|
||||
|
||||
async def test_media_player_receiver(
|
||||
hass: HomeAssistant, hk_driver: HomeDriver, caplog: pytest.LogCaptureFixture
|
||||
) -> None:
|
||||
"""Test if television accessory with unsafe characters."""
|
||||
entity_id = "media_player.receiver"
|
||||
sources = ["MUSIC", "HDMI 3/ARC", "SCREEN MIRRORING", "HDMI 2/MHL", "HDMI", "MUSIC"]
|
||||
hass.states.async_set(
|
||||
entity_id,
|
||||
None,
|
||||
{
|
||||
ATTR_DEVICE_CLASS: MediaPlayerDeviceClass.TV,
|
||||
ATTR_SUPPORTED_FEATURES: 3469,
|
||||
ATTR_MEDIA_VOLUME_MUTED: False,
|
||||
ATTR_INPUT_SOURCE: "HDMI 2/MHL",
|
||||
ATTR_INPUT_SOURCE_LIST: sources,
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
acc = ReceiverMediaPlayer(hass, hk_driver, "MediaPlayer", entity_id, 2, None)
|
||||
await acc.run()
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert acc.aid == 2
|
||||
assert acc.category == 34 # Receiver
|
||||
|
|
Loading…
Add table
Reference in a new issue