Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial support for HomeKit enabled televisions #32404

Merged
merged 2 commits into from
Mar 5, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions homeassistant/components/homekit_controller/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,5 @@
"fanv2": "fan",
"air-quality": "air_quality",
"occupancy": "binary_sensor",
"television": "media_player",
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"name": "HomeKit Controller",
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/homekit_controller",
"requirements": ["aiohomekit[IP]==0.2.15"],
"requirements": ["aiohomekit[IP]==0.2.17"],
"dependencies": [],
"zeroconf": ["_hap._tcp.local."],
"codeowners": ["@Jc2k"]
Expand Down
169 changes: 169 additions & 0 deletions homeassistant/components/homekit_controller/media_player.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
"""Support for HomeKit Controller Televisions."""
import logging

from aiohomekit.model.characteristics import (
CharacteristicsTypes,
CurrentMediaStateValues,
RemoteKeyValues,
TargetMediaStateValues,
)
from aiohomekit.utils import clamp_enum_to_char

from homeassistant.components.media_player import DEVICE_CLASS_TV, MediaPlayerDevice
from homeassistant.components.media_player.const import (
SUPPORT_PAUSE,
SUPPORT_PLAY,
SUPPORT_STOP,
)
from homeassistant.const import STATE_IDLE, STATE_PAUSED, STATE_PLAYING
from homeassistant.core import callback

from . import KNOWN_DEVICES, HomeKitEntity

_LOGGER = logging.getLogger(__name__)


HK_TO_HA_STATE = {
CurrentMediaStateValues.PLAYING: STATE_PLAYING,
CurrentMediaStateValues.PAUSED: STATE_PAUSED,
CurrentMediaStateValues.STOPPED: STATE_IDLE,
}


async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up Homekit television."""
hkid = config_entry.data["AccessoryPairingID"]
conn = hass.data[KNOWN_DEVICES][hkid]

@callback
def async_add_service(aid, service):
if service["stype"] != "television":
return False
info = {"aid": aid, "iid": service["iid"]}
async_add_entities([HomeKitTelevision(conn, info)], True)
return True

conn.add_listener(async_add_service)


class HomeKitTelevision(HomeKitEntity, MediaPlayerDevice):
"""Representation of a HomeKit Controller Television."""

def __init__(self, accessory, discovery_info):
"""Initialise the TV."""
self._state = None
self._features = 0
self._supported_target_media_state = set()
self._supported_remote_key = set()
super().__init__(accessory, discovery_info)

def get_characteristic_types(self):
"""Define the homekit characteristics the entity cares about."""
return [
CharacteristicsTypes.CURRENT_MEDIA_STATE,
CharacteristicsTypes.TARGET_MEDIA_STATE,
CharacteristicsTypes.REMOTE_KEY,
]

def _setup_target_media_state(self, char):
self._supported_target_media_state = clamp_enum_to_char(
TargetMediaStateValues, char
)

if TargetMediaStateValues.PAUSE in self._supported_target_media_state:
self._features |= SUPPORT_PAUSE

if TargetMediaStateValues.PLAY in self._supported_target_media_state:
self._features |= SUPPORT_PLAY

if TargetMediaStateValues.STOP in self._supported_target_media_state:
self._features |= SUPPORT_STOP

def _setup_remote_key(self, char):
self._supported_remote_key = clamp_enum_to_char(RemoteKeyValues, char)
if RemoteKeyValues.PLAY_PAUSE in self._supported_remote_key:
self._features |= SUPPORT_PAUSE | SUPPORT_PLAY

@property
def device_class(self):
"""Define the device class for a HomeKit enabled TV."""
return DEVICE_CLASS_TV

@property
def supported_features(self):
"""Flag media player features that are supported."""
return self._features

@property
def state(self):
"""State of the tv."""
homekit_state = self.get_hk_char_value(CharacteristicsTypes.CURRENT_MEDIA_STATE)
if homekit_state is None:
return None
return HK_TO_HA_STATE[homekit_state]

async def async_media_play(self):
"""Send play command."""
if self.state == STATE_PLAYING:
_LOGGER.debug("Cannot play while already playing")
return

if TargetMediaStateValues.PLAY in self._supported_target_media_state:
characteristics = [
{
"aid": self._aid,
"iid": self._chars["target-media-state"],
"value": TargetMediaStateValues.PLAY,
}
]
await self._accessory.put_characteristics(characteristics)
elif RemoteKeyValues.PLAY_PAUSE in self._supported_remote_key:
characteristics = [
{
"aid": self._aid,
"iid": self._chars["remote-key"],
"value": RemoteKeyValues.PLAY_PAUSE,
}
]
await self._accessory.put_characteristics(characteristics)

async def async_media_pause(self):
"""Send pause command."""
if self.state == STATE_PAUSED:
_LOGGER.debug("Cannot pause while already paused")
return

if TargetMediaStateValues.PAUSE in self._supported_target_media_state:
characteristics = [
{
"aid": self._aid,
"iid": self._chars["target-media-state"],
"value": TargetMediaStateValues.PAUSE,
}
]
await self._accessory.put_characteristics(characteristics)
elif RemoteKeyValues.PLAY_PAUSE in self._supported_remote_key:
characteristics = [
{
"aid": self._aid,
"iid": self._chars["remote-key"],
"value": RemoteKeyValues.PLAY_PAUSE,
}
]
await self._accessory.put_characteristics(characteristics)

async def async_media_stop(self):
"""Send stop command."""
if self.state == STATE_IDLE:
_LOGGER.debug("Cannot stop when already idle")
return

if TargetMediaStateValues.STOP in self._supported_target_media_state:
characteristics = [
{
"aid": self._aid,
"iid": self._chars["target-media-state"],
"value": TargetMediaStateValues.STOP,
}
]
await self._accessory.put_characteristics(characteristics)
2 changes: 1 addition & 1 deletion requirements_all.txt
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ aioftp==0.12.0
aioharmony==0.1.13

# homeassistant.components.homekit_controller
aiohomekit[IP]==0.2.15
aiohomekit[IP]==0.2.17

# homeassistant.components.emulated_hue
# homeassistant.components.http
Expand Down
2 changes: 1 addition & 1 deletion requirements_test_all.txt
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ aiobotocore==0.11.1
aioesphomeapi==2.6.1

# homeassistant.components.homekit_controller
aiohomekit[IP]==0.2.15
aiohomekit[IP]==0.2.17

# homeassistant.components.emulated_hue
# homeassistant.components.http
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
"""Make sure that handling real world LG HomeKit characteristics isn't broken."""


from homeassistant.components.media_player.const import SUPPORT_PAUSE, SUPPORT_PLAY

from tests.components.homekit_controller.common import (
Helper,
setup_accessories_from_file,
setup_test_accessories,
)


async def test_lg_tv(hass):
"""Test that a Koogeek LS1 can be correctly setup in HA."""
accessories = await setup_accessories_from_file(hass, "lg_tv.json")
config_entry, pairing = await setup_test_accessories(hass, accessories)

entity_registry = await hass.helpers.entity_registry.async_get_registry()

# Assert that the entity is correctly added to the entity registry
entry = entity_registry.async_get("media_player.lg_webos_tv_af80")
assert entry.unique_id == "homekit-999AAAAAA999-48"

helper = Helper(
hass, "media_player.lg_webos_tv_af80", pairing, accessories[0], config_entry
)
state = await helper.poll_and_get_state()

# Assert that the friendly name is detected correctly
assert state.attributes["friendly_name"] == "LG webOS TV AF80"

# Assert that all optional features the LS1 supports are detected
assert state.attributes["supported_features"] == (SUPPORT_PAUSE | SUPPORT_PLAY)

device_registry = await hass.helpers.device_registry.async_get_registry()

device = device_registry.async_get(entry.device_id)
assert device.manufacturer == "LG Electronics"
assert device.name == "LG webOS TV AF80"
assert device.model == "OLED55B9PUA"
assert device.sw_version == "04.71.04"
assert device.via_device_id is None
Loading