ATV4 - Apple TV Binding

Hallo

I would like to control my Apple TV 4 on OH2. I have read in several post’s that it shoud be possible with the Apple Home Kit Binding. Nevertheless in my opinion I have the following comprehension. The Home Kit with the appropriate binding is useful for control these different smart devices, however not for the ATV4 itself.

How can I control the ATV4 itself on OH2?

Thanks.

Regards

Raphael

1 Like

The only way I do know is to steer the ATV via a Harmony hub (Logitech). But this means additional hardware…

One possibility is pyatv and exec binding

I use pyatv, works very well.

Hi,

Would this work also with older Apple TV Versions?

Could you post a few examples how you trigger the signals?

Thank you very much in advance

Marcel

1 Like

Here are all info’s … install py script and in a rule executeCommandLine

Hi @ei_Gelb_Geek and @r27,

how do you listen to a change of play_state?
If you do not use it yourself, do you have any idea to setup?

Thanks in advance!
HFM

Hi,

I’d be interested in setting it up as well and controll the appleTV with openhab. Would you please be able to share some insights (for a linux/python newbee) on how to setup pyatv?

Kurt

Hi Kurt,

what do you want to do exactly?
On what system is your openHAB? RPI 3?
Which version do you use? 2.2?

HFM

Hi,
I’m running Openhab 2.2 on an RPI3 (openhabian). As I’m controlling my TV via openhab already (LIRC), I’d like to do the same with my apple TV. I’ve never used python, so I just followed the steps, shown on githup for installing pyatv, however something seems to not work:

pip install pyatv
Traceback (most recent call last):
  File "/usr/bin/pip", line 9, in <module>
    load_entry_point('pip==1.5.6', 'console_scripts', 'pip')()
  File "/usr/lib/python2.7/dist-packages/pkg_resources.py", line 356, in load_entry_point
    return get_distribution(dist).load_entry_point(group, name)
  File "/usr/lib/python2.7/dist-packages/pkg_resources.py", line 2476, in load_entry_point
    return ep.load()
  File "/usr/lib/python2.7/dist-packages/pkg_resources.py", line 2190, in load
    ['__name__'])
  File "/usr/lib/python2.7/dist-packages/pip/__init__.py", line 74, in <module>
    from pip.vcs import git, mercurial, subversion, bazaar  # noqa
  File "/usr/lib/python2.7/dist-packages/pip/vcs/mercurial.py", line 9, in <module>
    from pip.download import path_to_url
  File "/usr/lib/python2.7/dist-packages/pip/download.py", line 25, in <module>
    from requests.compat import IncompleteRead
ImportError: cannot import name IncompleteRead

:confounded:

Kurt

Hi Kurt,

you need Python 3.5.2 and above. So you have to install it manually because unfortunately there is no package in repository. So try this.

wget https://www.python.org/ftp/python/3.6.4/Python-3.6.4.tgz
tar xvf Python-3.6.4.tgz
cd Python-3.6.4
sudo ./configure --enable-optimizations
sudo make -j8
sudo make altinstall

Then you should have the option to

pip3.6 install pyatv (maybe with sudo)

After that it is easy to scan atv

atvremote scan

and to send some commands like

atvremote -a (or use loginid plus address) play

But I have no clue how to build a useful listener without increasing load too much.

HFM

1 Like

Hi,

thanks a million for your guidance! It works :star_struck:

I’ll play around with it a while. The first thing I noticed though is a quite extrem lag: Between issuing the command on the RPi and actually seeing it on the ATV, it takes arr. 7sec. :thinking:

Kurt

Hi Kurt,

I think this is because there is a session send everytime.
Another aspect is the load of RPI. If too many processes are running in parallel maybe it is too much for it.

You can send multiple commands after another like

atvremote -a (or use loginid plus address) play stop top_menu

I prefer to send multiple commands sending it to the background like

atvremote -a (or use loginid plus address) play &
atvremote -a (or use loginid plus address) stop &
atvremote -a (or use loginid plus address) top_menu &

I would advise to try it and test what is better for you.

HFM

PS: If you use loginid and address it is 3 seconds faster. I believe. :slight_smile:

1 Like

I see! Thanks for the hint again!

Kurt

Python 3.5.3 is only needed when running pyatv from the master branch (when cloning directly from GitHub). Running the latest release on pypi (0.3.9) only requires python 3.4.2, so the upgrade isn’t really necessary right now but will be in the future.

I can help out building a small script that listens for push updates and sends the update somewhere if you like? It’s quite easy with the “push updates” API:

https://pyatv.readthedocs.io/en/master/metadata.html#push-updates

Question is what you want to do with the data once you have it?

1 Like

You will see lag when using autodiscover, but it should be quite fast if you specify address and login_id manually when running a command. pyatv is not that heavy, so it shouldn’t put much load on the system.

1 Like

Hi Postlund,

the goal is to use the data provided to set the values via REST API (https://docs.openhab.org/configuration/restdocs.html and http://demo.openhab.org:8080/doc/index.html) to items.

The push updates API sends the following data:

  • Media type
  • Play state
  • Title
  • Position
  • Repeat
  • Shuffle

An example:
If my wife starts playing a movie (or if possible pushes the button of the remote to activate apple tv) in the evening the state could be sent via API and openHAB could react on this change. Then it decides due to the luminance to switch on dimmed lights (for tv; a small lamp is already on due to motion). If she pauses the movie the lights are dimmed up to brightness. If play state is set to “Idle” and nothing happens for more than X minutes, then everything is switched off.

Another example:
My wife likes shows like “The good wife”. Sometimes there are several weeks between watching sessions. So she (and the apple tv) forgets about what she saw the last time. If the title and position could be sent to openhab to other fields, openHAB can react saving that. And she can look in her app what she watched the last time.

So what would be great if you could develop it?

  • Listener who reacts on a change of each of the listed (above) values
  • Reaction is sending an API request to openHAB
  • Some examples for GET, POST, PUT, DELETE with Python

Being flashed from your propose and sending thanks in advance!
HFM

Hi,
super! works smoothly!
thanks for the help!

Kurt

1 Like

The REST-part is not really an expertise of mine, so I can provide some conceptual code for that but you will have to adjust it to include the correct payload and select a proper endpoint. I’m only used to Home Assistant (that’s what I developed pyatv for) and have never used OpenHAB.

A very basic example could look like this:

"""Simple push update listener example."""

import sys
import asyncio
import aiohttp
from pyatv import helpers


class PushListener:

    def __init__(self, session):
        self.session = session

    async def _post(self, playstatus):
        # This should correspond to the json data expected by the API
        payload = {
            'title': playstatus.title
        }

        # Change address to some API endpoint
        async with self.session.post(
            'http://somehost/api/test', json=payload) as resp:
            # This just prints the response - do something useful here
            print('Response: ', await resp.text())

    def playstatus_update(self, _, playstatus):
        asyncio.ensure_future(self._post(playstatus))

    def playstatus_error(self, updater, exception):
        print('An error occurred (restarting): {0}'.format(exception))
        updater.start(initial_delay=1)


async def _listen_for_push_updates(atv):
    print('Starting to listen for updates')
    try:
        with aiohttp.ClientSession() as session:
            atv.push_updater.listener = PushListener(session)
            await atv.push_updater.start()
    except Exception as ex:
        print('Got an error: ', ex)
    finally:
        await atv.logout()

async def _no_device_found():
    print('No Apple TV found', file=sys.stderr)


if __name__ == '__main__':
    helpers.auto_connect(_listen_for_push_updates,
                         not_found=_no_device_found)

You will have to adjust the code in _post to do what you want. Also, if you want to POST to multiple endpoints, you can quite easily refactor to accomplish that. Multiple

Hi postlund,

thanks for your work!

I tried 2 days to get it working but I failed. I am too unexperienced with Python. I think I have no clue from the basic concepts.

What I tried

  • I tried to get a manual connect (instead autoconnect from helpers), that was a success, but then I put _listen_for_push_updates as second param and got lots of errors
  • Because I did not find out how to send a body and not json, I tried to include another library (python-openhab; https://pypi.python.org/pypi/python-openhab/2.2) to simplify the communication with openHAB; I got a first result but nothing happened on openHAB

Maybe you could assist a little bit?

Bye
HFM