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

6.8 KiB
Raw Permalink Blame History

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 = printingok / 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).