#!/usr/bin/env python3 """POS Briefing MQTT bridge for Home Assistant. Runs on steel141 as user `claude` via systemd. Subscribes to a command topic; on PRESS, runs pos_briefing.py as a subprocess and prints the daily briefing. Registers an HA button via MQTT discovery and reports status/availability. Topics: homeassistant/button/pos_briefing/config -- discovery (retained, on connect) posbriefing/availability -- online / offline (LWT) posbriefing/button/press -- receives: PRESS posbriefing/status -- idle / printing / ok / error: """ import os import json import logging import subprocess import threading import paho.mqtt.client as mqtt MQTT_HOST = os.environ.get("MQTT_HOST", "192.168.0.154") MQTT_PORT = int(os.environ.get("MQTT_PORT", "1883")) MQTT_USER = os.environ.get("MQTT_USER", "posbriefing") MQTT_PASS = os.environ.get("MQTT_PASSWORD", "") BRIEFING = "/home/claude/bin/POS-Automation/pos_briefing.py" PRINT_TIMEOUT = 180 DISCOVERY_TOPIC = "homeassistant/button/pos_briefing/config" AVAIL_TOPIC = "posbriefing/availability" COMMAND_TOPIC = "posbriefing/button/press" STATUS_TOPIC = "posbriefing/status" logging.basicConfig(level=logging.INFO, format="%(asctime)s [pos-mqtt] %(levelname)s %(message)s") log = logging.getLogger(__name__) _busy = threading.Lock() def publish_discovery(client): payload = { "name": "Print Daily Briefing", "unique_id": "pos_briefing_button", "command_topic": COMMAND_TOPIC, "payload_press": "PRESS", "availability_topic": AVAIL_TOPIC, "payload_available": "online", "payload_not_available": "offline", "device": { "identifiers": ["pos_briefing"], "name": "POS Briefing", "manufacturer": "Seth", "model": "TM-m30 briefing", }, "icon": "mdi:printer-pos", } client.publish(DISCOVERY_TOPIC, json.dumps(payload), retain=True) client.publish(AVAIL_TOPIC, "online", retain=True) client.publish(STATUS_TOPIC, "idle", retain=True) log.info("Published MQTT discovery payload") def run_briefing(client): if not _busy.acquire(blocking=False): log.warning("Briefing already running; ignoring press") return try: client.publish(STATUS_TOPIC, "printing", retain=True) log.info("Running briefing...") r = subprocess.run(["/usr/bin/python3", BRIEFING], capture_output=True, text=True, timeout=PRINT_TIMEOUT) if r.returncode == 0 and "Receipt sent" in r.stdout: client.publish(STATUS_TOPIC, "ok", retain=True) log.info("Briefing printed OK") else: tail = (r.stdout + r.stderr).strip().splitlines()[-1:] or ["nonzero"] client.publish(STATUS_TOPIC, f"error:{tail[0][:80]}", retain=True) log.error(f"Briefing failed rc={r.returncode}: {tail}") except subprocess.TimeoutExpired: client.publish(STATUS_TOPIC, "error:timeout", retain=True) log.error("Briefing timed out") finally: _busy.release() def on_connect(client, userdata, flags, rc): if rc == 0: log.info("Connected to MQTT broker") publish_discovery(client) client.subscribe(COMMAND_TOPIC) log.info(f"Subscribed to {COMMAND_TOPIC}") else: log.error(f"MQTT connect failed: rc={rc}") def on_message(client, userdata, msg): cmd = msg.payload.decode().strip().upper() log.info(f"Received: {cmd}") if cmd == "PRESS": threading.Thread(target=run_briefing, args=(client,), daemon=True).start() else: log.warning(f"Unknown command: {cmd}") def main(): # Explicit VERSION1 callbacks (paho 2.x defaults to this but warns); our # on_connect/on_message use the (client, userdata, flags, rc) signature. client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION1, client_id="pos-briefing-bridge") client.username_pw_set(MQTT_USER, MQTT_PASS) client.on_connect = on_connect client.on_message = on_message client.will_set(AVAIL_TOPIC, "offline", retain=True) log.info(f"Connecting to {MQTT_HOST}:{MQTT_PORT}...") client.connect(MQTT_HOST, MQTT_PORT, keepalive=60) try: client.loop_forever() finally: client.publish(AVAIL_TOPIC, "offline", retain=True) client.disconnect() if __name__ == "__main__": main()