From f5e289f863e1074240debbcf0cddaba12022cc60 Mon Sep 17 00:00:00 2001 From: Chance Newkirk <40219357+cnewkirk@users.noreply.github.com> Date: Sat, 4 Jul 2026 09:43:17 -0700 Subject: [PATCH] =?UTF-8?q?docs:=20external-reader=20pass=20=E2=80=94=20me?= =?UTF-8?q?rmaid=20diagrams,=20real=20onboarding=20flow,=20security=20mode?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit README: repo/CLI naming note, architecture + day-timeline mermaid diagrams (GitHub renders these natively), commands gain inventory + security, setup flow now copies the example and uses inventory to map YOUR lights instead of applying the fictional example, hardware-requirements section, module map folded into a contributors details block, MIT license section, test count unstale. Example config header teaches the copy-and-edit flow. Runbook loses the last homelab phrasings (the Mac, apartment). Co-Authored-By: Claude Fable 5 --- README.md | 217 ++++++++++++++++++++++--------- deploy/synology/README-docker.md | 18 +-- examples/home.yaml | 19 ++- 3 files changed, 175 insertions(+), 79 deletions(-) diff --git a/README.md b/README.md index a438d17..7a20b1b 100644 --- a/README.md +++ b/README.md @@ -1,26 +1,46 @@ -# hue-iac +# hueman Declarative, Terraform-style management of Philips Hue lighting — a sun-anchored -circadian day cycle, TV-aware bias lighting, night motion guidance, and honoured -manual overrides. +circadian day cycle, TV-aware bias lighting, night motion guidance, a panic +mode, and honoured manual overrides. + +> The CLI and Python package are currently named `hue-iac`; they will be renamed +> to `hueman` before a public release. Everywhere you see `hue-iac` below, that's +> this project. You describe the desired state of your lights in a YAML file; `hue-iac` diffs it against the bridge (`plan`), converges it (`apply`), and — for the behaviours a bridge can't run natively — ships a small resident **circadian daemon** that -drives a zone smoothly along the solar curve and holds a TV-bias look while you -watch. Built for the **Hue Bridge Pro** and its local **CLIP API v2**. +drives a zone smoothly along the solar curve, holds a TV-bias look while you +watch, and steps aside the moment you touch a dimmer. Built for the local +**CLIP API v2**; the night-motion features use **Hue Bridge Pro MotionAware**. -``` -hue-iac validate # parse + validate the config (no bridge needed) -hue-iac preview # print the circadian colour curve for a day -hue-iac plan # show what apply would change (read-only) -hue-iac apply # converge the bridge's declarative state -hue-iac circadian run # run the circadian + TV-bias daemon (foreground) -hue-iac circadian resume # clear a manual-override suspension out-of-band -hue-iac watch # legacy live motion controller (see Limitations) -``` +## How it fits together + +```mermaid +flowchart LR + yaml["hue.yaml
(desired state)"] + + subgraph cli["hue-iac"] + plan["plan / apply
(diff + converge)"] + daemon["circadian daemon
(circadian run)"] + end -## The three layers + subgraph bridge["Hue Bridge (CLIP v2, local)"] + native["native state
rooms · zones · smart scenes
night-red MotionAware automation"] + lights(["your lights"]) + end + + tv["TV state
probe · control file · scene"] + + yaml --> plan + yaml --> daemon + plan -->|provision, idempotent| native + native --> lights + daemon -->|"curve tick every 60 s, 75 s fades"| lights + tv -.-> daemon + bridge -.->|SSE events| daemon +``` 1. **`apply` provisions native bridge state** — rooms/zones, sensor sensitivity, a generated sun-anchored `smart_scene` day cycle (`circadian_scene:`), @@ -30,75 +50,95 @@ hue-iac watch # legacy live motion controller (see Limitations) 2. **The circadian daemon** (`circadian run`) is the live runtime for what the bridge cannot express: it re-samples the solar-elevation curve every minute and drives one zone through a continuous colour/brightness drift (75 s - cross-fades over 60 s ticks — no visible steps), detects manual overrides by - settle-and-compare and steps back until you resume, and holds a **TV bias** - look on the viewing lights while the TV is on (detected by a TCP probe of the - TV, an SSE scene trigger, or a control file — OR-combined, debounced), snapping - them back to the room's curve seconds after it turns off. + cross-fades over 60 s ticks — no visible steps). If you dim or switch the + zone yourself it notices (settle-and-compare) and backs off until you resume. + While the TV is on it holds a **TV bias** look on your viewing lights + (detected by a TCP probe of the TV, a bridge scene trigger, or a control + file — OR-combined, debounced), snapping them back to the room's curve + seconds after it turns off. An optional **security mode** turns the whole + home into an escalating panic show (legible red alert, then hard-cut colour + chaos with a photosensitivity-safe brightness cap) until disarmed. 3. **`watch`** is the older per-sensor motion runtime (`motion_policies:`). It predates the daemon and MotionAware; see Limitations before using it. -## Architecture +### A day under hueman -The decision logic is isolated from all I/O so it is deterministic and fully -unit-tested; the network surface is thin and replaceable. +```mermaid +timeline + title A day under hueman + sunrise : daemon wakes the zone : warm, ~2,650 K + morning : ramps brighter and cooler + solar noon : coolest point of the day : ~4,300 K at full brightness + evening : descends into golden hour + hand-off : zone fades off at the time you set : daemon goes idle + overnight : MotionAware night-red guidance : dim red on motion, auto-off +``` -| Module | Responsibility | -| --- | --- | -| `config.py` | Parse + validate the YAML into typed, frozen dataclasses | -| `sun.py` | NOAA sunrise/sunset + solar elevation for a location (`SolarCalculator`) | -| `circadian.py` | Solar elevation → colour-temperature/brightness curve (`CircadianCurve`) | -| `circadian_scene.py` | Samples the curve at ≤6 sun knee-times into the generated smart-scene steps | -| `circadian_control.py` | Pure daemon state machine: drive window, settle-and-compare override detection (`CircadianController`) | -| `bias_control.py` | Pure TV-bias core: per-light hold/drive/off decisions + debounced trigger aggregation | -| `engine.py` | Pure motion/timing state machine for `watch` (`PolicyEngine`) | -| `payload.py` | Decision output → CLIP request bodies, incl. sRGB → CIE xy | -| `nightmotion.py` | Pure night-motion/scene helpers: scene bodies, automation transform, tolerant scene-look diff | -| `reconcile.py` | Terraform-style plan/apply: `Planner` + area, sensitivity, smart-scene, circadian-scene, and night-motion reconcilers | -| `state.py` | Index the live bridge (incl. MotionAware areas); resolve names → ids | -| `client.py` | CLIP API v2 client | -| `pin.py` | Trust-on-first-use TLS certificate pinning | -| `circadian_daemon.py` | The resident daemon: curve ticks, SSE events, TV-bias triggers/probe | -| `watch.py` | Legacy live runtime: bridge event stream → `PolicyEngine` → commands | -| `cli.py` | The `hue-iac` command-line interface | +## The commands + +``` +hue-iac -c my-home.yaml validate # parse + validate the config (no bridge needed) +hue-iac -c my-home.yaml preview # print the circadian colour curve for a day +hue-iac -c my-home.yaml inventory # list everything on your bridge, by name +hue-iac -c my-home.yaml plan # show what apply would change (read-only) +hue-iac -c my-home.yaml apply # converge the bridge to the declared state +hue-iac -c my-home.yaml circadian run # run the circadian + TV-bias daemon (foreground) +hue-iac -c my-home.yaml circadian resume # clear a manual-override suspension out-of-band +hue-iac -c my-home.yaml security on|off # arm / disarm the panic show +hue-iac -c my-home.yaml watch # legacy live motion controller (see Limitations) +``` + +`-c` defaults to `./hue.yaml` if you omit it. ## Install ```bash -cd hue-iac -python3 -m pip install -e . # installs the `hue-iac` console script -# or just: python3 -m pip install requests pyyaml +git clone https://github.com/cnewkirk/hueman && cd hueman +python3 -m pip install -e . # installs the `hue-iac` console script +# contributors: python3 -m pip install -e ".[dev]" && python3 -m pytest ``` -Requires Python 3.10+. Run the tests with `python3 -m pytest`. +Requires Python 3.10+. ## First-time setup -1. **Find your bridge IP** (Hue app → Settings → My Hue System → your bridge), - and put it in the config `bridge.host` or `export HUE_BRIDGE_HOST=...`. -2. **Pair** — press the bridge's physical link button, then: +1. **Copy the example config** and make it yours: ```bash - hue-iac auth + cp examples/home.yaml my-home.yaml ``` - It prints an application key. Store it as an environment variable so it never - lands in the config file: +2. **Point it at your bridge** — find the bridge IP in the Hue app + (Settings → My Hue System → your bridge) and set `bridge.host` in + `my-home.yaml` (or `export HUE_BRIDGE_HOST=...`). +3. **Pair** — press the bridge's physical link button, then: + ```bash + hue-iac -c my-home.yaml auth + ``` + It prints an application key. Keep it in an environment variable so it never + lands in a file: ```bash export HUE_APPLICATION_KEY= ``` -3. **Validate and preview** (no bridge calls): +4. **See what you have** — list your real lights, rooms, zones, and motion + areas by name, then edit `my-home.yaml`'s `areas:` (and light names + throughout) to match your home: + ```bash + hue-iac -c my-home.yaml inventory + ``` +5. **Validate and preview** (no bridge writes): ```bash - hue-iac -c examples/home.yaml validate - hue-iac -c examples/home.yaml preview + hue-iac -c my-home.yaml validate + hue-iac -c my-home.yaml preview # prints your circadian curve for today ``` -4. **Plan and apply**: +6. **Plan, then apply** — `plan` is read-only and shows exactly what `apply` + would change; nothing touches the bridge until you apply: ```bash - hue-iac -c examples/home.yaml plan - hue-iac -c examples/home.yaml apply + hue-iac -c my-home.yaml plan + hue-iac -c my-home.yaml apply ``` -5. **Run the daemon** (foreground; use Docker/systemd to keep it up — a Synology +7. **Run the daemon** (foreground; use Docker/systemd to keep it up — a Synology Docker runbook lives in `deploy/synology/README-docker.md`): ```bash - hue-iac -c examples/home.yaml circadian run + hue-iac -c my-home.yaml circadian run ``` ## TLS @@ -162,14 +202,25 @@ Key sections: (`mode: night_only` when the daemon owns the day; three timeslots are emitted — night start, a 00:00 clone for the small hours, and an actionless `day_start` slot that keeps the wrapped night slot from governing dark evenings). +- `security` — the daemon-native panic mode: alert/chaos tuning, a hard + photosensitivity floor on luminance flashing, arm/disarm triggers, and a + decoupled sound-cue contract for an external audio adapter. - `motion_policies` — per-sensor policies for the legacy `watch` runtime. +## What needs which hardware + +- `plan`/`apply` areas, smart scenes, the circadian daemon, TV bias, and + security mode work on any **CLIP v2** bridge (square Hue bridge or Bridge Pro). +- `night_motion` and motion-area inventory need a **Hue Bridge Pro** running + **MotionAware** (motion sensed by the bulb grid — no PIR sensors required). +- The legacy `watch` runtime needs old-style PIR **Hue motion sensors**. + ## Limitations and notes -- The pure decision layer (engine, circadian, daemon controller, bias, sun, - config, reconcile, nightmotion) is covered by ~240 unit tests. `client`, - `pin`, and the daemon's I/O shell talk to a real bridge and should be - exercised on your network. +- The pure decision layer (config, sun, circadian, daemon controller, bias, + security, engine, reconcile, nightmotion) is covered by 300+ unit tests. + `client`, `pin`, and the daemon's I/O shell talk to a real bridge and should + be exercised on your network. - **`watch` is legacy and known-inadequate against current bridge firmware**: its echo-buffer override detection predates the bridge's periodic re-emission of settled `grouped_light` values, which it can misread as manual overrides, @@ -179,3 +230,43 @@ Key sections: - The daemon runs in the foreground; pair it with Docker (`--restart always`), `systemd`, or similar. On a manual override it suspends until a power-cycle of the zone, `hue-iac circadian resume`, or the daily safety resume. +- Security mode's chaos runs over the REST API, which rate-limits under load — + the show degrades gracefully (the daemon logs drop stats). True per-frame + control would need the Entertainment streaming API. + +## Architecture (for contributors) + +The decision logic is isolated from all I/O so it is deterministic and fully +unit-tested; the network surface is thin and replaceable. + +
+Module map + +| Module | Responsibility | +| --- | --- | +| `config.py` | Parse + validate the YAML into typed, frozen dataclasses | +| `sun.py` | NOAA sunrise/sunset + solar elevation for a location (`SolarCalculator`) | +| `circadian.py` | Solar elevation → colour-temperature/brightness curve (`CircadianCurve`) | +| `circadian_scene.py` | Samples the curve at ≤6 sun knee-times into the generated smart-scene steps | +| `circadian_control.py` | Pure daemon state machine: drive window, settle-and-compare override detection (`CircadianController`) | +| `bias_control.py` | Pure TV-bias core: per-light hold/drive/off decisions + debounced trigger aggregation | +| `security_control.py` | Pure security-mode core: alert breathe → luminance-capped chaos frames | +| `engine.py` | Pure motion/timing state machine for `watch` (`PolicyEngine`) | +| `payload.py` | Decision output → CLIP request bodies, incl. sRGB → CIE xy | +| `nightmotion.py` | Pure night-motion/scene helpers: scene bodies, automation transform, tolerant scene-look diff | +| `reconcile.py` | Terraform-style plan/apply: `Planner` + area, sensitivity, smart-scene, circadian-scene, and night-motion reconcilers | +| `state.py` | Index the live bridge (incl. MotionAware areas); resolve names → ids | +| `client.py` | CLIP API v2 client | +| `pin.py` | Trust-on-first-use TLS certificate pinning | +| `circadian_daemon.py` | The resident daemon: curve ticks, SSE events, TV-bias triggers, security show | +| `watch.py` | Legacy live runtime: bridge event stream → `PolicyEngine` → commands | +| `cli.py` | The `hue-iac` command-line interface | + +
+ +Design docs for every major feature live in +[`docs/superpowers/specs/`](docs/superpowers/specs/). + +## License + +[MIT](LICENSE). diff --git a/deploy/synology/README-docker.md b/deploy/synology/README-docker.md index 100db25..b76300c 100644 --- a/deploy/synology/README-docker.md +++ b/deploy/synology/README-docker.md @@ -22,12 +22,12 @@ daemon.** The cron job is retired once this container is confirmed running. > **`hue.yaml` MUST contain a `circadian_daemon:` block.** `hue-iac circadian run` > raises `HueIacError("no 'circadian_daemon' block in config")` and the container exits > immediately (crash-loops under `--restart always`) if it is missing. At minimum the -> block needs `zone:` (the apartment zone the daemon drives). The daemon tunables also +> block needs `zone:` (the zone the daemon drives). The daemon tunables also > live here — notably `manual_override.control_file` (the resume-signal file, default > `.hue-circadian-resume`) and `log.path` (default `logs/circadian.log`, which lands > under the mounted `/data/logs`). See `hue_iac/config.py` (`CircadianDaemonSpec`) for the -> full set of keys and their defaults. The live `hue.yaml` gets this block added during -> deploy; if you are starting from scratch, add it before building. +> full set of keys and their defaults. If yours doesn't have the block yet, add +> it before building. > > **TV bias hold (`circadian_daemon.bias`):** if you enable the **control-file** > trigger source (`bias.triggers.control_file`), its `on_file`/`off_file` must land @@ -45,11 +45,11 @@ Throughout this document, substitute `` with the actual share name The NAS keeps a plain **build context** at `$HUE_IAC_HOME/build/` (Dockerfile, `hue_iac/`, `pyproject.toml`, `README.md`) — **not a git clone** (the live deploy -has no `src/` checkout; source is copied in from the dev machine). From the Mac, +has no `src/` checkout; source is copied in from the dev machine). From your dev machine, sync the current `main` into it, then build over SSH: ```sh -# On the Mac, from a clean checkout of origin/main: +# On your dev machine, from a checkout of this repo at origin/main: rsync -r --delete hue_iac/ @:/volume1//hue-iac/build/hue_iac/ rsync pyproject.toml Dockerfile @:/volume1//hue-iac/build/ # (scp also works but needs -O — Synology has SFTP disabled.) @@ -59,7 +59,7 @@ sudo /usr/local/bin/docker build -t hue-iac:latest /volume1//hue-iac/buil ``` Alternatively, import a pre-built tarball via Container Manager UI: -Container Manager → Image → Import → select the `.tar` file exported from the Mac. +Container Manager → Image → Import → select the `.tar` file exported from your dev machine. --- @@ -150,7 +150,7 @@ docker exec hue-circadian hue-iac -c /data/hue.yaml circadian resume ## Step 5 — Update the image -Same as Step 1: re-sync the source from the Mac into `build/`, rebuild, then +Same as Step 1: re-sync the source from your dev machine into `build/`, rebuild, then recreate the container: ```sh @@ -169,8 +169,8 @@ sudo /usr/local/bin/docker rm -f hue-circadian |---------|--------------|-----| | Container exits immediately | Bad config / missing file mount | `docker logs hue-circadian` for the error | | SSE stream drops / reconnects | Bridge rebooted or LAN blip | Daemon auto-reconnects; monitor logs | -| TLS pin mismatch | Bridge cert rotated | Refresh `.hue-pin.json` from the Mac, restart container | -| `BLOCKED` on scene apply | Zone or scene not found | Run `hue-iac plan` on the Mac to diagnose | +| TLS pin mismatch | Bridge cert rotated | Refresh `.hue-pin.json` from your dev machine, restart container | +| `BLOCKED` on scene apply | Zone or scene not found | Run `hue-iac plan` from your dev machine to diagnose | --- diff --git a/examples/home.yaml b/examples/home.yaml index e20fbcd..326c045 100644 --- a/examples/home.yaml +++ b/examples/home.yaml @@ -1,13 +1,18 @@ # --------------------------------------------------------------------------- # hue-iac example configuration # -# Declarative desired state for a Hue Bridge Pro. Run: -# hue-iac -c examples/home.yaml validate # check syntax -# hue-iac -c examples/home.yaml preview # see the circadian curve -# hue-iac -c examples/home.yaml plan # diff against the bridge -# hue-iac -c examples/home.yaml apply # provision areas/scenes/night-red -# hue-iac -c examples/home.yaml circadian run # run the circadian + TV-bias daemon -# hue-iac -c examples/home.yaml watch # legacy motion controller +# Copy this file (`cp examples/home.yaml my-home.yaml`), point bridge.host at +# your bridge, and replace the light/room/zone names with yours — run +# `hue-iac -c my-home.yaml inventory` to see what your bridge actually has. +# +# Declarative desired state for a Hue bridge (CLIP v2; night_motion needs a +# Bridge Pro running MotionAware). Then: +# hue-iac -c my-home.yaml validate # check syntax (no bridge needed) +# hue-iac -c my-home.yaml preview # see the circadian curve +# hue-iac -c my-home.yaml plan # diff against the bridge (read-only) +# hue-iac -c my-home.yaml apply # provision areas/scenes/night-red +# hue-iac -c my-home.yaml circadian run # run the circadian + TV-bias daemon +# hue-iac -c my-home.yaml watch # legacy motion controller # --------------------------------------------------------------------------- bridge: