-
Notifications
You must be signed in to change notification settings - Fork 33
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
10 changed files
with
949 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -102,3 +102,6 @@ venv.bak/ | |
|
||
# mypy | ||
.mypy_cache/ | ||
|
||
# Other | ||
*~ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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()) |
Oops, something went wrong.