Binding for Apple-TV

Pyatv is under development for TVOS, there are many missing things. You can check GitHub for progress and issues.

this doesn’t work. While integrating pyatv I need to make a lot of changes to fit into the java environment. The binding is not just running a command line, because this took 3-5s per key. It directly integrates the Python code (using jpy). You could check the repo and compare the initial checkin with the latest code.

Hi all,

First of all thank you for your work and efforts.
I really like to use this binding and tried all sorts of ideas in this thread.
But I can’t get it to work.
Installing to the stage of scanning and registering with my atv4 is done. I can put commands to my atv via atvremote, that’s fine.
But if I drop the jar 2.5.0 into my addons folder, oh crashes. After i restart oh, all other bindings are working well, but appletv binding posts errors every 10s.
I suspect it’s the polling.
I think it’s a conflict of the python versions installed on my rpi4. The readme tells 3.5 is needed, I have 3.7 running. I can’t get the install of pyatv to work with 3.5.
My oh is 2.5.3 stable, server is raspberry pi 4, running openhabian.
I would greatly appreciate any help to get this working. I don’t want to go the path of doing a minimal control via executecommands for atvremote via cli.
Thanks in advance,
Thomas

any update with this working with tvOS 13?

No, the situation

  • the binding is embedding pyatv 0.3, which was fine until Apple made those changes, which leads into
  • ATV4 with iOS 13 doesn’t work
  • ATV3 still works fine, but the bindings shows the exception every 10s (which doesn’t impact overall functionality, at least in my own installation)
  • I worked a lot on the embedded PyATV version to build a bridge between the binding and pyatv. This is based on Python, which I have only “trial&error” knowledge. In fact I tried to wrap the CLI version directly into the binding and have some kind of events (bacllbacks to the binding) - sounds ugly, is ugly, but was the only way to get something working
  • This would have to be done with PyATV4 again and I know that’s a good bunch of work
  • I’m pretty sure the current approach will never pass the review process

In fact I’m lacking the required Python knowledge. I know that OH embeds the same Java/Python bridge to enable Jython based rules. The PyATV didn’t show much interest to help on this.

Fianally it’s a matter of time. I’m already spending significant time on developing the bindings for Shelly, MagentaTV, GREE, Carnet, Rachio so the ATV binding got less and less priority. And last, but not least: I don’t have an ATV4, which means a lot of back and forth for testing.

1 Like

If any one else is missing Markus’ great AppleTV binding because of tvOS upgrade, there is a workaround!
I was frustrated losing the binding but in my research for a solution I found this:
HABApp

Using HABApp, I can use the pyatv library directly. It is not as integrated as a binding and I haven’t succeeded in getting all “push” functionality in pyatv to work, but my main use is to be able to use my single remote for “everything” and for that it works great!

I hope this helps someone!

@magnuslsjoberg this can be a great solution! Can you explain the path you followed to integrate pyatv in HABApp?

@Rickytr

HABApp

  1. Follow habapp’s installation instructions and install habapp in a virtual python environment.
  2. Implement a habapp test rule and verify installation.
  3. Set up habapp to start at boot.

pyatv

  1. Activate the habapp virtual environment.
  2. Install pyatv in that environment following the instructions for pyatv.
  3. Run “atvremote” to verify the setup.

HABApp / pyatv

Please note that this is a only simple way to get it to work! I used the ‘atvremote’ code as
inspiration but didn’t understand all of it. I am certain that someone with greater python skills
could integrate this in a better way. The code below is trimmed down but should work.

NOTE! The indentation got messed up when I paste the code here (don’t know what I’m doing wrong)

import HABApp
from HABApp.openhab.events import ItemCommandEvent
import logging
import asyncio
import pyatv

class AppleTV(HABApp.Rule):

    def __init__(self):
        super().__init__()

    # Get the event loop for asyncio
    self.loop = HABApp.core.const.loop

    # No Apple TV connected yet
    self.atv = None

    # Listen to control commands from openHAB (from proxy String AppleTV_Control)
    self.listen_event( 'AppleTV_Control', self.control, ItemCommandEvent )

    # Scan and connect
    self.run_soon(self.connect)

    # Poll status every 5s
    self.run_every(None,5,self.status)

async def connect(self):
    log.info("Discovering devices on network...")
    atvs = await pyatv.scan(self.loop, timeout=20)
    if atvs :
        # Only got one AppleTV
        log.info("Connecting to {0}".format(atvs[0].address))
        self.atv = await pyatv.connect(atvs[0],self.loop)
        log.info(self.atv.device_info)
    else :
        log.error("No device found")

def control(self,event):
    assert isinstance(event, ItemCommandEvent), type(event)
    if self.atv :
        # Let the async function 'handle_command' handle this later
        self.run_soon(self.handle_command, str(event.value))    

async def handle_command(self,command) :
    # In 'atvremote' this is much more elegant!
    if command == 'select' :
        await self.atv.remote_control.select()
    # elif ...
    #... list all commands handled
    elif command == 'home_hold' :
        await self.atv.remote_control.home(action=pyatv.const.InputAction.Hold)

async def status(self) :
    if self.atv :
        status = await self.atv.metadata.playing()
        log.info(f'Playing:\n{status}')
        
        app = self.atv.metadata.app
        log.info(f'App: {app}') 


# Rule initialization      
log = logging.getLogger('HABApp')
AppleTV()
2 Likes

Great guide @magnuslsjoberg! Thanks a lot.

@Rickytr You are welcome!

I find both HABApp and pyatv very robust and both projects have excellent documentation.

If you find a way to handle the commands better (instead of listing all of them) or if you
find out how to get push updates from pyatv rather than polling, please report here!

Hi markus7017 and thank you very much for all your work!
Is there any way to get this working with OH3?
Thank you.

1 Like

Hi everyon,
+1

@magnuslsjoberg I tried to follow your instructions and I can run HABApp from commandline and it is loading the rules files, but I get some errors and have no idea where to start:

[2021-02-23 10:45:17,687] [             HABApp.Rules]    ERROR | Error "name 'self' is not defined" in load:
[2021-02-23 10:45:17,688] [             HABApp.Rules]    ERROR | Could not load /etc/openhab/habapp/rules/test.py!
[2021-02-23 10:45:17,688] [             HABApp.Rules]    ERROR | File "/opt/habapp/lib/python3.7/site-packages/HABApp/rule_manager/rule_file.py", line 79, in load
[2021-02-23 10:45:17,688] [             HABApp.Rules]    ERROR |     self.create_rules(created_rules)
[2021-02-23 10:45:17,688] [             HABApp.Rules]    ERROR | File "/opt/habapp/lib/python3.7/site-packages/HABApp/rule_manager/rule_file.py", line 68, in create_rules
[2021-02-23 10:45:17,689] [             HABApp.Rules]    ERROR |     '__HABAPP__RULES': created_rules,
[2021-02-23 10:45:17,689] [             HABApp.Rules]    ERROR | File "/usr/lib/python3.7/runpy.py", line 263, in run_path
[2021-02-23 10:45:17,689] [             HABApp.Rules]    ERROR |     pkg_name=pkg_name, script_name=fname)
[2021-02-23 10:45:17,689] [             HABApp.Rules]    ERROR | File "/usr/lib/python3.7/runpy.py", line 96, in _run_module_code
[2021-02-23 10:45:17,690] [             HABApp.Rules]    ERROR |     mod_name, mod_spec, pkg_name, script_name)
[2021-02-23 10:45:17,690] [             HABApp.Rules]    ERROR | File "/usr/lib/python3.7/runpy.py", line 85, in _run_code
[2021-02-23 10:45:17,690] [             HABApp.Rules]    ERROR |     exec(code, run_globals)
[2021-02-23 10:45:17,690] [             HABApp.Rules]    ERROR | File "/etc/openhab/habapp/rules/test.py", line 7, in test.py
[2021-02-23 10:45:17,691] [             HABApp.Rules]    ERROR |     3    import logging
[2021-02-23 10:45:17,691] [             HABApp.Rules]    ERROR |     4    import asyncio
[2021-02-23 10:45:17,691] [             HABApp.Rules]    ERROR |     5    import pyatv
[2021-02-23 10:45:17,691] [             HABApp.Rules]    ERROR |     6    
[2021-02-23 10:45:17,692] [             HABApp.Rules]    ERROR | --> 7    class AppleTV(HABApp.Rule):
[2021-02-23 10:45:17,692] [             HABApp.Rules]    ERROR |     8    
[2021-02-23 10:45:17,692] [             HABApp.Rules]    ERROR |     ..................................................
[2021-02-23 10:45:17,692] [             HABApp.Rules]    ERROR |      logging = <module 'logging' from '/usr/lib/python3.7/logging/__init__.py'>
[2021-02-23 10:45:17,693] [             HABApp.Rules]    ERROR |      asyncio = <module 'asyncio' from '/usr/lib/python3.7/asyncio/__init__.py'>
[2021-02-23 10:45:17,694] [             HABApp.Rules]    ERROR |      pyatv = <module 'pyatv' from '/opt/habapp/lib/python3.7/site-packages/pyatv/__init__.py'>
[2021-02-23 10:45:17,694] [             HABApp.Rules]    ERROR |      HABApp.Rule = <class 'HABApp.rule.rule.Rule'>
[2021-02-23 10:45:17,694] [             HABApp.Rules]    ERROR |     ..................................................
[2021-02-23 10:45:17,694] [             HABApp.Rules]    ERROR | 
[2021-02-23 10:45:17,695] [             HABApp.Rules]    ERROR | File "/etc/openhab/habapp/rules/test.py", line 13, in AppleTV
[2021-02-23 10:45:17,695] [             HABApp.Rules]    ERROR |     9    def __init__(self):
[2021-02-23 10:45:17,695] [             HABApp.Rules]    ERROR |     10       super().__init__()
[2021-02-23 10:45:17,695] [             HABApp.Rules]    ERROR |     11   
[2021-02-23 10:45:17,696] [             HABApp.Rules]    ERROR |     12   # Get the event loop for asyncio
[2021-02-23 10:45:17,696] [             HABApp.Rules]    ERROR | --> 13   self.loop = HABApp.core.const.loop
[2021-02-23 10:45:17,696] [             HABApp.Rules]    ERROR |     14   
[2021-02-23 10:45:17,697] [             HABApp.Rules]    ERROR |     ..................................................
[2021-02-23 10:45:17,697] [             HABApp.Rules]    ERROR |      __init__ = <function 'AppleTV.__init__' test.py:9>
[2021-02-23 10:45:17,697] [             HABApp.Rules]    ERROR |      HABApp.core.const.loop = <_UnixSelectorEventLoop running=True closed=False debug=True>
[2021-02-23 10:45:17,697] [             HABApp.Rules]    ERROR |     ..................................................
[2021-02-23 10:45:17,697] [             HABApp.Rules]    ERROR | 
[2021-02-23 10:45:17,698] [             HABApp.Rules]    ERROR | NameError: name 'self' is not defined

When I try to set HABApp to run as a service, the log file does not show anything when I start it but that is a separate topic I guess.

Hi Ronny!

As I wrote in my post:

NOTE! The indentation got messed up when I paste the code here (don’t know what I’m doing wrong)

The init should be:

    def __init__(self):
        super().__init__()

        # Get the event loop for asyncio
        self.loop = HABApp.core.const.loop

        # No Apple TV connected yet
        self.atv = None

        # Listen to control commands from openHAB (from proxy String AppleTV_Control)
        self.listen_event( 'AppleTV_Control', self.control, ItemCommandEvent )

        # Scan and connect
        self.run_soon(self.connect)

        # Poll status every 5s
        self.run_every(None,5,self.status)

Please try that!

@markus7017
+1 for oh3!

1 Like

Would be nice to see this binding built for Openhab 3!

2 Likes

Would be really nice to control the ATV through openhab!

Hi all-

I’ve spent a little time looking at what needs to be done to get this binding working on OH3 and with newer ATV devices. I spent quite a bit of time trying to get jpy working and didn’t have much success. I think the limitations placed on us by OSGI and jpy make the problem unlikely to be easily solved.

However, pyatv includes a utility called atvscript, which I think can be used to avoid java-python inter-operation.

As a first step, I managed to get discovery working. I think pairing without running atvremote on the command line is going to be hard to do, but with good documentation that might not be a huge problem. The atvscript tool can also push updates to us in real-time, so there’s actually an advantage versus polling as the current binding does.

Hopefully most of the binding code can simply be reused, which is always a plus. Not sure how much time I’ll have to work on this, but if anyone is interested in testing, let me know!

Somehow the OH runtime includes jpy, but I never got to the point to get access to this instance. In general that shouldn’t be unsolvable, but you are right I also spent a lot of time on this. The reason for polling was just a lack of Python knowledge. The bindings brings a it’s own and modified copy of the older pyatv. The way to include external components in OH3 has also changed. The binding did it’s job for OH2, but I think a rework if the lower layer is required.

Using the command line tools and redirecting the output should be feasible. It did this once for my WhatsApp binding running youwsapp cli and this worked pretty well (I could re-open the repo if you are interested in the code). Let me know if you have questions in the existing code.

And yes, I could support testing, because I can’t control my ATV from OH anymore :wink:

Hi-

Thanks for your feedback! I appreciate that you’ve responded so quickly. I had a look through your code for the binding and it seems pretty reasonable. Generally speaking, I give my work freely to whoever wants to use it, so regardless of whether I succeed, the changes I try will be free for anyone to take.

I do like the idea of jpy, and (oddly enough) I’ve got experience with java/native interaction. Unfortunately, I think that the restrictions OH3 have put in place mean that it’s maybe not worth trying. I am not a python expert but I think if (as the ultimate goal) the binding were to read the output of a python script that runs commands without having to start it each time, we can have all of the benefits of embedded python without the hassle of getting it to work. There’s support in pyatv for doing that with events, so I think any changes needed should be possible to send back to the pyatv folks.

Long story short, I have an idea of how this might be done, but I would be glad to look at your code for inspiration! I can keep you posted and once I have something that seems like it works, I’ll push it somewhere you can have a look.

One other thing, it seems like pyatv can control other devices like HomePod minis and such… was wondering if maybe there was a better name for the binding but am not sure I have a good idea. Something maybe to think about!