Hey Siri — Fixer Upper

Had some fun playing with the Channels API. I came up with a simple HomeKit scene for my wife: say "Fixer Upper" and it:

  1. Uses ADB to turn on the TV with a Fire TV Stick,
  2. Reads the upnext list from the DVR, to find the ID of the next unwatched episode of her show,
  3. Plays that recording

It's powered by the fixerupper Python3 script, below, and I use a homebridge plugin called cmdswitch2 for the HomeKit/Siri tie-in to a command. You need to turn on ADB Debugging on the FireStick, and generate an adb "key" (which I did with a python module called adb_shell and its keygen command ).

The only hitches: it takes some time for the TV to be powered up over ADB (~15sec or so), and by then the playback has started, but the Fire TV pauses it. So I monitor for a pause for 25 second, and use the API to resume if one is encountered.

Also, sometimes the Channels API goes down and is unreachable (Cannot connect to host 192.168.0.XX:57000). Restarting Channels on the Fire Stick manually resolves this. Possibly it was getting overwhelmed with too many requests during testing...

The code:

#!/usr/bin/env python3
# fixerupper: Power up FireTV, queue up and play
# "Up Next" episode of SHOW='Fixer Upper'
# Copyright 2020, JD Smith
import androidtv
import aiohttp
import asyncio
import time
import os

# Filepath
path=os.path.dirname(os.path.realpath(__file__))

#Configure to your liking
SHOW='Fixer Upper'
FIRESTICK_IP='192.168.0.XX'
STATUS=f"http://{FIRESTICK_IP}:57000/api/status"
ADBKEY=os.path.join(path,"adbkey")
DVR_UPNEXT='http://dvr.local:8089/dvr/recordings/upnext'

async def upnext(session):
    async with session.get(DVR_UPNEXT) as resp:
        id=[d['ID'] for d in await resp.json() if SHOW in d['Path']]
        if not id:
            print(SHOW, ' not found in upnext')
            exit()
        return id[0]    

async def channels_command(session,command):
    url=f"http://{FIRESTICK_IP}:57000/api/{command}"
    await session.post(url)

async def status(session):
    async with session.get(STATUS) as resp:
        return await resp.json()

async def main():
    async with aiohttp.ClientSession() as session:
        id=await upnext(session)
        try:
            await channels_command(session,f"play/recording/{id}")
        except:
            print("Error: Failed to playback recording")
            exit
        for cnt in range(25):
            s=await status(session)
            if s["status"]=="paused":
                await channels_command(session,"resume")
                return
            else:
                time.sleep(1)
        
# Press the On Button (synchronously)
def turn_on():
    ftv=androidtv.firetv.FireTV(FIRESTICK_IP,auth_timeout_s=2,adbkey=ADBKEY)
    ftv.turn_on()

turn_on()
asyncio.run(main())

If your TV uses HDMI CEC, then you should be able to issue the /play api directly and it will turn the TV on for you.

I had hoped it would work that way. It would be preferable because using ADB is quite slow and was a pain to setup. The TV does have CEC, but the api play commands don't turn it on. It's an older Vizio with a Fire Stick 4K. Hitting Home Key on Fire Stick remote does turn it on, which I presume uses CEC. In fact all androidtv does is connect, authorize, hit the "POWER" key, and hit the "HOME" key.

Any thing to try?

There's a hidden API POST /wakeup you can try. But the /api/play does the same thing too.

/wakeup returns ok after a few seconds, but doesn't turn TV on. CEC is enabled on both TV and Fire Stick. Not clear why Fire Stick Home would be able to wake it up, while Channels API can't...