Metadata-Version: 2.4
Name: hue-entertainment
Version: 0.1.2
Summary: Async Python client for the Philips Hue Entertainment streaming API (DTLS-PSK + HueStream).
Author-email: The Music Assistant Authors <marcelveldt@users.noreply.github.com>
License: Apache-2.0
Project-URL: Bug Reports, https://github.com/music-assistant/hue-entertainment/issues
Project-URL: Source Code, https://github.com/music-assistant/hue-entertainment
Platform: any
Classifier: Environment :: Console
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: aiohttp>=3.9
Requires-Dist: cryptography>=42.0
Requires-Dist: zeroconf>=0.130
Provides-Extra: test
Requires-Dist: codespell==2.4.2; extra == "test"
Requires-Dist: mypy==2.1.0; extra == "test"
Requires-Dist: pre-commit==4.6.0; extra == "test"
Requires-Dist: pre-commit-hooks==6.0.0; extra == "test"
Requires-Dist: pytest==9.0.3; extra == "test"
Requires-Dist: pytest-aiohttp==1.1.1; extra == "test"
Requires-Dist: pytest-asyncio==1.4.0; extra == "test"
Requires-Dist: pytest-cov==7.1.0; extra == "test"
Requires-Dist: ruff==0.15.16; extra == "test"
Dynamic: license-file

# hue-entertainment

Async Python client for the **Philips Hue Entertainment** streaming API.

It pairs with a Hue bridge, discovers entertainment areas, and streams colour frames to the
lights over **DTLS 1.2 PSK** using the `HueStream` protocol with minimal latency. It works
with both the Hue **V2** ("square") bridge and the Hue **Pro** bridge.

The DTLS 1.2 PSK handshake, record layer and HueStream encoder are pure Python on top of
[`cryptography`](https://cryptography.io/) — no `openssl` subprocess, no C bindings, no
other DTLS dependency. Blocking socket I/O is confined to a dedicated sender thread, so it
never blocks your asyncio event loop.

It powers the Hue Entertainment plugin (the Sendspin bridge) in
[Music Assistant](https://github.com/music-assistant/server) and the
[ambilight-hue-pro-bridge](https://github.com/marcelveldt/ambilight-hue-pro-bridge).

## What it provides

- **Bridge discovery** — find Hue bridges on the LAN via mDNS (`discover_bridges`).
- **Pairing** — create an application key and the DTLS client key (`HueEntertainmentAPI.pair`).
- **Areas** — list a bridge's entertainment configurations with their channels and positions.
- **Streaming** — start/stop an entertainment stream and push per-channel colours at up to ~50 Hz.
- **`EntertainmentSession`** — a high-level async facade that opens the stream on demand, runs
  the blocking DTLS work in an executor, enforces the bridge's single-active-stream constraint,
  and tears the stream down after inactivity.
- Works with the V2 and Pro bridges; dependencies are `aiohttp`, `cryptography` and `zeroconf`.

## What you can build with it

Anything that drives Hue lights from a fast colour source — screen / Ambilight sync, music
visualisers, games, rich notifications, or your own virtual bridge — without the Hue Sync
hardware.

## Planned extensions

- HueStream **v1** framing and the **CIE xy** colour space (currently HueStream v2 / RGB).

## Install

```bash
pip install hue-entertainment
```

Requires Python 3.11+. Runtime dependencies: `aiohttp`, `cryptography`, `zeroconf`.

## Usage

```python
import asyncio

from hue_entertainment import EntertainmentSession, HueEntertainmentAPI, LightColorCommand


async def main() -> None:
    host = "192.168.1.50"

    # One-time pairing (press the bridge link button first):
    api = HueEntertainmentAPI(host)
    creds = await api.pair()  # {"username": ..., "clientkey": ...}
    await api.close()

    # Stream to an entertainment area:
    session = EntertainmentSession(host, creds["username"], creds["clientkey"])
    areas = await session.get_entertainment_areas()
    area = areas[0]

    await session.start(area.id)  # activates the area + opens the DTLS stream
    try:
        # Drive frames at your own rate (~25-50 Hz). One command per channel.
        for _ in range(250):
            session.send(
                [LightColorCommand(channel_id=ch.channel_id, red=65535) for ch in area.channels]
            )
            await asyncio.sleep(1 / 50)
    finally:
        await session.aclose()


asyncio.run(main())
```

## API

- **`HueEntertainmentAPI(host, app_key=None)`** — low-level CLIP v2 wrapper:
  `pair()`, `get_entertainment_areas()`, `start_entertainment(area_id)`,
  `stop_entertainment(area_id)`, `get_bridge_id()`, `close()`.
- **`HueDtlsStreamer()`** — low-level DTLS/HueStream streamer:
  `connect(host, username, clientkey, area_id)` (blocking — run in an executor),
  `send_colors([...])`, `disconnect()`, `is_connected`.
- **`EntertainmentSession(host, app_key, client_key, *, idle_timeout=10.0)`** — the
  recommended high-level facade: on-demand `start(area_id)`, non-blocking `send(...)`,
  `stop()` / `aclose()`, automatic idle teardown, and enforcement of the bridge's
  single-active-stream constraint. The blocking DTLS work runs in an executor for you.
- **Models** — `EntertainmentArea`, `LightChannel`, `LightColorCommand`.

`LightColorCommand` carries 16-bit RGB; the encoder emits the duplicated-byte HueStream v2
format and lets the bridge gamut-map per light.

## Development

```bash
scripts/setup.sh
pre-commit run --all-files
pytest
```

## License

[Apache 2.0](LICENSE)
