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>
This commit is contained in:
Mortdecai
2026-06-07 13:01:15 -04:00
parent 5061c21b94
commit cb7ad8b0ff
2 changed files with 169 additions and 0 deletions
@@ -0,0 +1,120 @@
# 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).