# 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:`. - 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:` | ### 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 (30–50 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:`; 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).