Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
postlund committed Nov 25, 2019
1 parent 3e1d751 commit eaef4e8
Show file tree
Hide file tree
Showing 10 changed files with 949 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -102,3 +102,6 @@ venv.bak/

# mypy
.mypy_cache/

# Other
*~
68 changes: 68 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# Apple TV Beta Component

In tvOS 13, Apple dropped their old legacy protocols inherited from iTunes
(DAAP/DACP/DMAP) and now rely fully on their new protocol, Media Remote Protocol.
Or MRP for short. This broke the Apple TV component in Home Assistant as
`pyatv`, the underlying protocol, only supports the old legacy protocol stack.

I have put a lot of work into completing suport for MRP in `pyatv` and it is
starting to reach a point where it is usable. But there is still some work
left until it can be released and all changes integrated back into Home Assistant.
However, I want to give everyone a chance to try out the new component and come
with feedback, so I publish what will become the official component (that is
bundled with Home Assistant by default) here as a custom component so you can
try it out.

Issues and trouble reports should be reported in the `pyatv` repository:

[**>> Report issues here <<**](https://github.com/postlund/pyatv)

## Features and limitations

Currently Home Assistant implements all features that `pyatv` suports for DMAP.
The intention is to implement the same features for MRP first and add additional
features after that. The feature list is available
[here](https://postlund.github.io/pyatv/).

Other limitations as follows:

* As Home Assistant does not support zeroconf for custom components, auto-discovery
will not work with this component. You will have to manually add your device
via the Integrations page.

* This component will completely override the builtin component!

* The component will not handle re-connects properly. So if the connection is
lost, e.g. due to reboots, it will probably not recover until you restart
Home Assistant. Fixing this is next on the list.

* Using YAML is not support now, but will be added in the future.

* Already stated in the feature list, but artwork does not work and appears to be
a bit tricky to fix. We'll see when that happens.

* When pairing with an Apple TV running tvOS, the initial screen with a PIN code
will not disappear after pairing (so it appears that you get three PIN input
screens). This is a known issue and is fixed on the `master` branch in `pyatv`.
It will be integrated into this component soon. For now, you can just ignore
that screen (e.g. press menu on the remote).

## Installing

I recommend that you install [HACS](https://hacs.xyz/) and add this repository
to it. That way you get updates automatically. But you can just copy and add
files the old fashined way as well.

## Setting up

Head over to that Integrations page and add an Apple TV from there. You have to
provide either the name of a device, its IP-address or a unique identifier
(that you got via `atvremote scan`). If you are unsure about what to enter, just
type something, press submit and suggestions will be presented for you.

## Finally...

Remember, this is beta software. Features are not fully developed yet, things
will not work, etc. If you try it out, I would be *very* grateful if you reported
any issues you encounter. It helps me iron out bugs and making the integration
stable before submitting it to Home Assistant. It's a win-win in the end, really.
51 changes: 51 additions & 0 deletions custom_components/apple_tv/.translations/en.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
{
"config": {
"title": "Apple TV",
"flow_title": "Apple TV: {name}",
"step": {
"user": {
"title": "Connect to the device",
"description": "Enter a device name, IP-address or unique identifier.{devices}",
"data": {
"device_id": "Device ID"
}
},
"pair_with_pin": {
"title": "Pair with device",
"description": "Pairing is required for protocol `{protocol}`. Please input PIN code displayed on screen.",
"data": {
"pin": "PIN Code"
}
},
"pair_no_pin": {
"title": "Pair with device",
"description": "Pairing is required for protocol `{protocol}`. Please enter PIN {pin} on device to continue."
},
"service_problem": {
"description": "A problem occurred while pairing protocol `{protocol}`. It will be ignored.",
"title": "Failed to add protocol"
},
"confirm": {
"description": "Do you want to setup the Apple TV named `{name}` in Home Assistant?",
"title": "Discovered Apple TV"
},
"zeroconf": {
"lookup_id_failed": "Failed to look up device id for device."
}
},
"error": {
"device_not_found": "Device could not be found on network.",
"no_usable_service": "A device was found but could not identify any way to establish a connection.",
"unknown": "Unexpected error",
"auth": "Authentication error (invalid PIN?)"
},
"abort": {
"already_configured": "Device is already configured",
"no_credentials": "No credentials available for device",
"unrecoverable_error": "An unrecoverable occurred",
"device_did_not_pair": "No attempt to finish pairing process was made from the device.",
"backoff": "Device does not accept pairing reqests at this stage, try again later.",
"timeout": "Timed out while waiting for device"
}
}
}
115 changes: 115 additions & 0 deletions custom_components/apple_tv/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
"""The Apple TV integration."""
import logging

import voluptuous as vol

from homeassistant.core import callback
from homeassistant.helpers import discovery
from homeassistant.const import (
CONF_NAME,
EVENT_HOMEASSISTANT_STOP,
)

from .const import (
DOMAIN, CONF_IDENTIFIER, CONF_PROTOCOL, CONF_CREDENTIALS,
APPLE_TV_DEVICE_TYPES, KEY_API, KEY_POWER
)

_LOGGER = logging.getLogger(__name__)

CONFIG_SCHEMA = vol.Schema({}, extra=vol.ALLOW_EXTRA)


async def async_setup(hass, config):
"""Set up the Apple TV integration."""
return True


async def async_setup_entry(hass, entry):
"""Set up a config entry for Apple TV."""
import pyatv

identifier = entry.data[CONF_IDENTIFIER]
protocol = entry.data[CONF_PROTOCOL]
credentials = entry.data[CONF_CREDENTIALS]
atvs = await pyatv.scan(hass.loop, identifier=identifier, protocol=protocol)
if not atvs:
_LOGGER.error("Failed to find device with identifier " + identifier)
return False

conf = atvs[0]
for protocol, credentials in credentials.items():
conf.set_credentials(int(protocol), credentials)

atv = await pyatv.connect(conf, hass.loop)
power = AppleTVPowerManager(hass, atv, False)

@callback
def on_hass_stop(event):
"""Stop push updates when hass stops."""
atv.push_updater.stop()

hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, on_hass_stop)

hass.data.setdefault(KEY_API, {})[identifier] = atv
hass.data.setdefault(KEY_POWER, {})[identifier] = power

dev_reg = await hass.helpers.device_registry.async_get_registry()
dev_reg.async_get_or_create(
config_entry_id=entry.entry_id,
connections=set(),
identifiers={(DOMAIN, entry.data[CONF_IDENTIFIER])},
manufacturer="Apple",
name="Apple TV",
# model='',
# sw_version=''
)

hass.async_create_task(
hass.config_entries.async_forward_entry_setup(entry, "media_player")
)

hass.async_create_task(
discovery.async_load_platform(hass, "remote", DOMAIN, entry.data, entry.data)
)

return True


class AppleTVPowerManager:
"""Manager for global power management of an Apple TV.
An instance is used per device to share the same power state between
several platforms.
"""

def __init__(self, hass, atv, is_off):
"""Initialize power manager."""
self.hass = hass
self.atv = atv
self.listeners = []
self._is_on = not is_off

async def init(self):
"""Initialize power management."""
if self._is_on:
self.atv.push_updater.start()
await self.atv.connect()

@property
def turned_on(self):
"""Return true if device is on or off."""
return self._is_on

async def set_power_on(self, value):
"""Change if a device is on or off."""
if value != self._is_on:
self._is_on = value
if not self._is_on:
self.atv.push_updater.stop()
else:
self.atv.push_updater.start()
await self.atv.connect()

for listener in self.listeners:
self.hass.async_create_task(listener.async_update_ha_state())
Loading

0 comments on commit eaef4e8

Please sign in to comment.