Files
POS-Automation/docs/superpowers/specs/2026-06-07-ha-google-home-trigger-design.md
Mortdecai cb7ad8b0ff docs: design for HA/Google Home on-demand POS briefing trigger
Replaces the never-installed cron approach with an MQTT bridge mirroring the
lgtv pattern: Google Home button -> HA script/scene -> MQTT -> steel141 prints.
Adds MAC-based printer discovery (printer IP is DHCP-unstable) and a gemma4
summarizer swap. Includes scripts/dryrun.py (non-printing pipeline harness).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 13:01:15 -04:00

121 lines
6.8 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# POS Briefing — Home Assistant / Google Home Trigger (Design)
Date: 2026-06-07
Status: Proposed — awaiting Seth's review
## Goal
Replace the (never-installed) cron schedule with an **on-demand** trigger: tap a
**button in Google Home** → the daily briefing prints on the Epson TM-m30. No cron.
## Background / findings (this session)
- "Not firing" had two causes, both now moot under this design: (1) no cron entry
was ever installed on steel141 — the "schedule 4:58am/3pm" commit only edited
SESSION.md; (2) `yfinance` was missing from steel141's system python3 →
**installed** (`pip --user --break-system-packages`, matching lgtv's PEP-668 approach).
- The pipeline itself **works end-to-end on steel141** (verified via non-printing
dry run, `scripts/dryrun.py`): FreshRSS 100 items, weather (the uncommitted
hourly rewrite), finance (yfinance), and summarization all produce a ~2.4 KB
ESC/POS receipt. `zfs`/`reddit`/`grafana` return `None` on steel141 (non-fatal;
out of scope here — see Out of Scope).
- Printer **changes IP on every restart** (router DHCP, no reservation). Currently
`192.168.0.184:9100`, MAC `50:57:9c:eb:0f:07` (Epson). Always on.
## Architecture
Mirrors the proven `lgtv` chain (steel141 systemd MQTT bridge + HA MQTT discovery +
free manual `google_assistant` integration).
```
[Google Home tap]
→ google_assistant integration (already wired; acct slingshooter08; exposes LG TV switch today)
→ HA script "Print Daily Briefing" (exposed to Google as a Scene → momentary button)
→ MQTT publish to broker CT 301 (192.168.0.154:1883)
→ pos-briefing-mqtt.service on steel141 (systemd, User=claude)
→ pos_briefing.py → printer discovery (by MAC) → ESC/POS over TCP :9100
→ status/availability published back to MQTT (HA shows success/fail)
```
## Components
### 1. MQTT bridge — `pos_briefing_mqtt.py` + `pos-briefing-mqtt.service` (new, steel141)
- systemd `Type=simple`, `User=claude`, `EnvironmentFile=/etc/pos-briefing/pos-briefing.env`
(holds `MQTT_PASSWORD`, root-owned 0600, never committed). `Restart=on-failure`.
- Connects to broker `192.168.0.154:1883`, user `posbriefing` (or reuse an existing
broker user — TBD at install; lgtv uses a per-device user).
- On connect: publishes **MQTT discovery** for an HA **button** entity
(`homeassistant/button/pos_briefing/config`) + availability `online`; LWT → `offline`.
- Subscribes command topic `posbriefing/button/press` (payload `PRESS`).
- On press: runs the briefing **as a subprocess**
(`/usr/bin/python3 /home/claude/bin/POS-Automation/pos_briefing.py`) so a slow/long
print never blocks the MQTT loop and a script crash can't kill the bridge.
Publishes `posbriefing/status` = `printing``ok` / `error:<msg>`.
- Debounce: ignore a new press while one is already in flight (prevents double prints).
Topics:
| Topic | Dir | Payload |
|-------|-----|---------|
| `homeassistant/button/pos_briefing/config` | pub (retain) | discovery JSON |
| `posbriefing/availability` | pub (retain) | `online` / `offline` (LWT) |
| `posbriefing/button/press` | sub | `PRESS` |
| `posbriefing/status` | pub (retain) | `idle` / `printing` / `ok` / `error:<msg>` |
### 2. Printer discovery (new, in `pos_briefing.py`) — kills the DHCP-IP problem
- Resolve current printer IP by **MAC `50:57:9c:eb:0f:07`** instead of trusting a
hardcoded `printer_ip`.
- Algorithm (lgtv "cached IP, sweep on miss"):
1. Read cached IP from `~/.cache/pos-briefing/last_ip` (seed = config `printer_ip`).
2. `ping -c1` it, check `ip neigh` lladdr == target MAC, and `:9100` open → use it.
3. On miss: ARP-prime sweep the /24 (`ping -c1 -W1` fan-out), then scan `ip neigh`
for the MAC → its IP. Re-cache.
4. Still not found → raise a clear error (bridge reports `error:printer-not-found`).
- Config gains `printer_mac`; `printer_ip` becomes a seed/fallback only.
- **Belt-and-suspenders (Seth's action, router-side):** add a DHCP reservation for
the printer MAC so the IP stops moving. Discovery makes this optional for correctness.
### 3. HA script + Google Home exposure (Seth's HA side; I provide exact YAML/steps)
- MQTT discovery auto-creates `button.pos_briefing` (no hand YAML for the entity).
- Google's `google_assistant` integration doesn't support HA `button` entities → wrap:
an HA `script.print_daily_briefing` that presses the button. Scripts map to Google's
**Scene** trait → appear as a tappable **button** in the Google Home app (momentary).
- Add the script to the same `google_assistant` expose list that already exposes the
TV switch; re-sync. (Per lgtv DECISIONS 2026-06-06: manual integration, scopes must
include email+name, "HTTP basic auth header" OFF, Test activated by slingshooter08.)
### 4. gemma4 summarizer swap (in `pos_briefing.py`)
- Point summarization at a **gemma4 model on steel141 Ollama** (candidate
`gemma4:26b` or `gemma4:31b-it-q4_K_M`) via the existing direct Ollama HTTP call —
change `ollama_model` in config + the prompt string.
- **Prompt convergence:** done by Claude *now*, interactively, via the `ask_gemma4`
MCP tool against real FreshRSS article samples, iterating until summaries are tight
(3050 words, factual, no preamble). The finalized prompt string is then baked into
`pos_briefing.py` (which calls Ollama directly — it cannot call the MCP at runtime).
### 5. Deps / install
- `yfinance` — installed (done). `paho-mqtt` — present for `claude`. Confirm both
pinned/persisted. `install.sh` mirrors lgtv: copies bridge to `/usr/local/bin`,
unit to `/etc/systemd/system`, env to `/etc/pos-briefing/`, `daemon-reload`, enable.
## Error handling
- Printer not found / unreachable → `error:printer-not-found`; no partial print.
- Briefing subprocess nonzero exit or timeout (cap ~180s) → `error:<rc>`; bridge stays up.
- MQTT disconnect → paho auto-reconnect; LWT marks `offline` so HA/Google show it down.
- Data-source failures inside the briefing remain non-fatal (dashboard prints what it has).
## Testing
- `scripts/dryrun.py` — full pipeline, print suppressed (already used to verify).
- Bridge unit: publish `PRESS` to the command topic by hand (`mosquitto_pub`) → observe
`status` transitions and a real receipt; confirm `button.pos_briefing` appears in HA.
- Discovery: unit-test MAC→IP resolution with cache hit, cache miss+sweep, and not-found.
- End-to-end: tap the button in Google Home → receipt prints.
## Out of scope (noted, not fixed here)
- `zfs` (steel141 isn't the tank host — `cannot open 'tank'`), `reddit`, `grafana`
returning `None` on steel141. Track separately.
- Committing the in-tree weather rewrite is folded into this work (it's verified).
## Open items for Seth
- Broker MQTT user for the bridge: new `posbriefing` user vs reuse existing? (install-time)
- gemma4 model choice: `26b` vs `31b-it-q4_K_M` (I'll recommend after convergence).