Skip to main content

atv-homekit-remote

Pure-Python HomeKit Target Controller + HomeKit Data Stream (HDS) library for Apple TV remote buttons and Siri voice input.

This ports the core behavior of marcusadolfsson/appletv-siri-voice from Node/HAP-NodeJS to Python. It uses HAP-python for HomeKit pairing/encrypted HAP transport and implements the missing Target Control and HDS protocol layers in this package.

What it supports

  • HomeKit Target Controller accessory profile (category 32)
  • Apple TV target registration and persistent Target Control configuration
  • Active-target selection and HomeKit button events
  • HomeKit Data Stream setup and encrypted TCP transport
  • targetControl/whoami target-to-HDS association
  • Siri dataSend/open, Opus audio packets, acknowledgements and close handling
  • 16 kHz / mono / signed PCM16 -> 20 ms Opus frames
  • Async Python API
  • Optional HTTP control API
  • Persistent HAP pairing and target state

Requirements

  • Python 3.11+
  • Apple TV / tvOS with HomeKit Target Control support (tvOS 12+)
  • Host on the same LAN as the Apple TV
  • mDNS/Bonjour traffic must reach the Apple TV

opuslib-next-bundled is used so supported CPython platforms do not need a separately installed libopus package.

Install

python -m pip install -e .

Or directly from Git:

python -m pip install "git+https://github.com/jstnjx/atv-homekit-remote.git"

Fastest way to run it

atv-homekit-remote
Setting Default Environment variable
HomeKit name Apple TV HomeKit Remote HAP_NAME
HomeKit username generated and persisted HAP_USERNAME
Pair code generated and persisted HAP_PINCODE / PINCODE
HAP port 47129 HAP_PORT
Control API bind 127.0.0.1 CTRL_BIND
Control API port 8477 CTRL_PORT
Persistent state directory .atv-homekit-remote HAP_STORAGE

Then open Apple Home -> Add Accessory -> More options, select Apple TV HomeKit Remote, and enter the displayed setup code.

Keep the state directory. It contains the HomeKit pairing keys and registered Target Control configuration.

Python library API

Start the accessory

import asyncio

from atv_homekit_remote import AppleTVHomeKitRemote, RemoteConfig


async def main():
    remote = AppleTVHomeKitRemote(
        RemoteConfig(
            name="Apple TV HomeKit Remote",
            state_dir="./atv-homekit-remote-state",
        )
    )

    async with remote:
        print(remote.state)
        await asyncio.Event().wait()


asyncio.run(main())

Constructing the object does not bind sockets. await remote.start() / async with remote builds the HAP accessory on the currently running asyncio event loop.

Buttons

After the Apple TV has registered as a target and activated the Target Control service:

from atv_homekit_remote import Button

remote.set_active_identifier(207551296)
await remote.press(Button.MENU)
await remote.press("PLAY_PAUSE")
await remote.press(Button.ARROW_RIGHT, hold_ms=500)

SIRI is intentionally not exposed as an ordinary button press because the Siri button is coupled to an HDS audio session. Use the Siri API below.

Send a complete PCM buffer to Siri

Input format is exactly:

  • signed PCM16 little-endian
  • 16,000 Hz
  • mono
pcm = open("utterance.pcm", "rb").read()
await remote.send_pcm(pcm, target=207551296)

A byte buffer is paced as real-time 20 ms audio by default. Pass realtime=False only when you explicitly want to push frames as fast as the HDS connection accepts them.

Stream live microphone/TTS audio

session = await remote.start_siri(target=207551296)
try:
    while True:
        chunk = await next_pcm_chunk()  # any chunk size is fine
        if chunk is None:
            break
        await session.write(chunk)
    await session.finish()
except Exception:
    await session.cancel()
    raise

The session buffers arbitrary chunk sizes into 640-byte / 20 ms PCM frames, encodes them with Opus, groups up to five Opus frames per HDS dataSend/data event, and sends the final endOfStream marker before waiting for the Apple TV acknowledgement.

You can also pass an async iterator directly:

await remote.send_pcm(microphone_chunks(), target=207551296)

Async iterators are assumed to be live and are not artificially paced.

Runtime state

print(remote.state)

Example shape:

{
  "name": "Apple TV HomeKit Remote",
  "active_identifier": 207551296,
  "active": true,
  "configured_targets": [
    {
      "target_identifier": 207551296,
      "target_name": "Living Room",
      "target_category": 24,
      "buttons": {}
    }
  ],
  "hds_targets": [207551296],
  "siri_ready": true
}

siri_ready requires all of the following: a selected target, the Apple TV having set Target Control Active, and a live HDS connection identified with targetControl/whoami.

HTTP control API

The atv-homekit-remote command starts a local control API on 127.0.0.1:8477 unless --no-http is used.

Endpoint Purpose
GET /state Current targets / active target / HDS readiness
POST /active/<id> Select target
POST /press/<BUTTON>?target=<id> Press and release a remote button
POST /siri/stream?target=<id> Stream raw PCM16 16 kHz mono request body to Siri
POST /siri/file?file=/path/test.wav&target=<id> Send a 16 kHz mono PCM16 WAV file
POST /recover Toggle the Target Control Siri hardware capability and bump HAP config version

Example:

curl -X POST --data-binary @utterance.pcm \
  "http://127.0.0.1:8477/siri/stream?target=207551296"

The control API deliberately defaults to loopback. If you expose it with CTRL_BIND=0.0.0.0, protect it at the host firewall or reverse proxy; the API does not add its own authentication unless CTRL_TOKEN is configured.

HDS implementation notes

HDS session keys are derived from the same Pair Verify shared secret as the originating HAP connection:

  • accessory -> controller: HKDF-SHA512 info HDS-Read-Encryption-Key
  • controller -> accessory: HKDF-SHA512 info HDS-Write-Encryption-Key
  • salt: controllerKeySalt || accessoryKeySalt
  • transport: ChaCha20-Poly1305
  • frame nonce: 64-bit little-endian counter, left-padded to the 96-bit nonce required by modern crypto APIs

The package includes an HDS serializer/deserializer supporting the scalar, string/data, list, dictionary and compression forms used by Apple TV Target Control / Siri sessions.

Recovery behavior

The original Node bridge can republish a buttons-only accessory and then republish Siri services to encourage tvOS to reopen a stale HDS connection. recover_hds() implements the same capability-transition idea without destroying the HAP-python process: it toggles the Target Control hardwareImplemented capability and increments the HAP configuration version on both transitions.

await remote.recover_hds()

If an Apple TV remains stuck after that, restart the process while preserving the state directory. Pairing and target configuration are retained.

Tests

python -m pip install -e ".[test]"
pytest

The automated tests cover TLV8, HDS serialization, framing crypto primitives, target/audio capability encoding, and Opus frame generation. A real Apple TV is still required for an end-to-end HomeKit pairing/HDS/Siri validation.

License / attribution

Apache-2.0. See NOTICE for attribution to the original appletv-siri-voice project and HAP-NodeJS, whose documented/open-source Target Control and HDS behavior was used as the interoperability reference for this Python implementation.

Metadata

Release files for atv-homekit-remote 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for atv-homekit-remote 0.1.0
File Size Uploaded
atv_homekit_remote-0.1.0.tar.gz 37.4 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for atv-homekit-remote 0.1.0
File Interpreter ABI Platform
atv_homekit_remote-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 77.5 kB

Release files / atv_homekit_remote-0.1.0.tar.gz

Download URL atv_homekit_remote-0.1.0.tar.gz
Size 37.4 kB
Tags Source
SHA-256 checksum
How to use checksums
ca57c812d7b489fb356f49fb5f0cd51475da95e98da4d2e0f7254e6a65d23473
BLAKE2b-256 checksum
How to use checksums
292da84cbe2519163429ac0d9fe8efa5b81ac522e5ecc2756ee3098b1fe5d7d6
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 2, 2026.

Transparency log

Release files / atv_homekit_remote-0.1.0-py3-none-any.whl

Download URL atv_homekit_remote-0.1.0-py3-none-any.whl
Size 40.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
70d5ea863e15c01d3f546a321fd4625331cceb18d54e9f5f4a15d3bd5377f1a9
BLAKE2b-256 checksum
How to use checksums
e2c8caa3c6d52438eb0655fdf0f0f23d8505ec272c201a6c8e500efdf90bccb5
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 2, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page