postlund / pyatv

A client library for Apple TV and AirPlay devices
https://pyatv.dev
MIT License
882 stars 96 forks source link

"pyatv.exceptions.NotSupportedError: {control} is not supported" when interacting with Apple TV on tvOS 18.0 #2510

Closed tcannie closed 6 days ago

tcannie commented 6 days ago

Describe the bug

Not sure if I am missing something but when running a simple test.py script or the example tutorial.py script located at https://github.com/postlund/pyatv/blob/master/examples/tutorial.py I am encountering a pyatv.exceptions.NotSupportedError. It seems to be any control that I send (up,down,top_menu) atvremote is working properly but when calling atv.remote_control.{command}() it fails.

How to reproduce the bug?

Connect to and attempt to send a remote_control command to an apple tv 4k running tvOS 18.0. All seem to reach that exception for some reason.

What is expected behavior?

The library sends the requested command to the apple tv and it executes the command.

Operating System

Linux

Python

3.11

pyatv

0.15.1

Device

Apple TV 4K OS 18.0

Additional context

Here is the output when running a test script that simply connects to the IP address and sends an atv.remote_control.menu() command:

Discovering devices on network... Connecting to 100.101.202.87 Traceback (most recent call last): File "/home/pcratv/Documents/sgatv/sgatvflask/test.py", line 32, in LOOP.run_until_complete(send_control(LOOP)) File "/usr/lib/python3.11/asyncio/base_events.py", line 653, in run_until_complete return future.result() ^^^^^^^^^^^^^^^ File "/home/pcratv/Documents/sgatv/sgatvflask/test.py", line 24, in send_control await atv.remote_control.menu() File "/home/pcratv/Documents/sgatv/sgatvflask/venv/lib/python3.11/site-packages/pyatv/core/facade.py", line 107, in menu return await self.relay("menu")(action=action) ^^^^^^^^^^^^^^^^^^ File "/home/pcratv/Documents/sgatv/sgatvflask/venv/lib/python3.11/site-packages/pyatv/core/relayer.py", line 91, in relay instance = self._find_instance( ^^^^^^^^^^^^^^^^^^^^ File "/home/pcratv/Documents/sgatv/sgatvflask/venv/lib/python3.11/site-packages/pyatv/core/relayer.py", line 115, in _find_instance raise exceptions.NotSupportedError(f"{target} is not supported") pyatv.exceptions.NotSupportedError: menu is not supported

When the tutorial.py script described above is run and the remote_control/{id}/{command} endpoint is hit, this is the response: Remote control command failed: up is not supported

postlund commented 6 days ago

That means you don't have any protocol paired supporting that particular function. Try pairing AirPlay and companion, then try again. You can use atvremote if you like, the credentials are persistently stored.

tcannie commented 6 days ago

Hmm I have paired both airplay and companion via atvremote and am still encountering that same message. I can send the controls via atvremote but not through script. Is there some way I can provide more helpful info to assist with finding the cause?

postlund commented 6 days ago

Ah, right, the example does not in fact tap in to the default storage. You should pass storage=FileStorage.default_storage(loop) to the connect and scan calls.

from pyatv.storage.file_storage import FileStorage
tcannie commented 6 days ago

Still no luck unfortunately. I am running just a modified version of your currently playing function example. Here is the test that I am running for reference:

import asyncio
import sys

import pyatv
from pyatv.const import Protocol
from pyatv.storage.file_storage import FileStorage

LOOP = asyncio.get_event_loop()

# Method that is dispatched by the asyncio event loop
async def send_control(loop):
    print("Discovering devices on network...")
    atvs = await pyatv.scan(loop, hosts=["100.101.202.87"],storage=FileStorage.default_storage(loop),timeout=5)

    if not atvs:
        print("No device found", file=sys.stderr)
        return

    print(f"Connecting to {atvs[0].address}")
    atv = await pyatv.connect(atvs[0],loop,storage=FileStorage.default_storage(loop))

    try:
        await atv.remote_control.up()
    finally:
        # Do not forget to close
        atv.close()

if __name__ == "__main__":
    # Setup event loop and connect
    LOOP.run_until_complete(send_control(LOOP))

I appreciate all the help so far, sorry if I am missing something silly

tcannie commented 6 days ago

I figured it out! Looked through the docs and also found the await storage.load() function that had to be called. If anyone else runs into this these lines did it:

storage=FileStorage.default_storage(LOOP)
await storage.load()

Then I passed storage=storage into both the scan and connect calls.

Thanks again so much for the help @postlund!