Files
david 60ac42e711 Split button: single→3 plugs, hold-release→DJ Booth only
zigbee-monitor.yaml:
- Single click (action=single) toggles Squiggle, Sideboard Lamp,
  Bamboo Lights
- Press-hold-release (action=release after hold) toggles DJ Booth
  only
- Two filter functions: "Single click" and "Hold release", button-in
  fan-out wires to both
- DJ Booth added to zigbee-state-in and ui-text widgets for visibility
- "plugs:" list replaced with "single_plugs:" and "hold_plugs:" for
  declarative clarity (generator doesn't use them, but documents intent)

infra/zigbee2mqtt/configuration.yaml:
- Removed DJ Booth from "all_plugs" group (now Squiggle, Sideboard
  Lamp, Bamboo Lights only)
- Pushed same change to live /root/zigbee2mqtt/configuration.yaml and
  restarted zigbee2mqtt container

Diagnostics in justfile + scripts/:

justfile:
- Constants: RPI_HOST, MQTT_HOST, MQTT_PORT, RPI_DIR, NR_PORT
- rpi, mqtt-pub, mqtt-sub, mqtt-watch, mqtt-button
- nr-show, nr-diff, nr-logs, nr-editor, nr-dashboard
- z2m-devices, z2m-logs, z2m-state
- mqtt-logs, restart, restart-all

scripts/mqtt.py:
- MQTT publish/subscribe/watch helpers using paho-mqtt v2.x
- Run via uv run --with paho-mqtt (no global install needed)

scripts/diag.py:
- nr-show: live flow summary with mqtt-out topic list
- nr-diff: structural diff deployed vs repo, normalizes random uuids
- z2m-devices: paired devices + group membership
- mqtt-button: publish action and watch /set topics (E2E test)

Verified end-to-end via `just mqtt-button`:
  single  → 3 messages on .../set (Squiggle, Sideboard Lamp, Bamboo Lights)
  release → 1 message on .../set (DJ Booth)
2026-06-30 23:28:13 -07:00

90 lines
2.5 KiB
Python

#!/usr/bin/env python3
"""MQTT diagnostic helpers — invoked by justfile mqtt-* recipes."""
import argparse
import sys
import time
import paho.mqtt.client as mqtt
def pub(host, port, topic, payload):
c = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2)
c.connect(host, port, 60)
c.loop_start()
info = c.publish(topic, payload)
info.wait_for_publish(timeout=5)
c.loop_stop()
c.disconnect()
def sub(host, port, topic, seconds):
got = []
def on_msg(client, userdata, msg):
got.append((msg.topic, msg.payload.decode()))
c = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2)
c.on_message = on_msg
c.connect(host, port, 60)
c.loop_start()
c.subscribe(topic)
end = time.monotonic() + seconds
while time.monotonic() < end:
time.sleep(0.1)
c.loop_stop()
c.disconnect()
for t, p in got:
print(t, p)
def watch(host, port, topic):
def on_msg(client, userdata, msg):
print(msg.topic, msg.payload.decode())
c = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2)
c.on_message = on_msg
c.connect(host, port, 60)
c.loop_start()
c.subscribe(topic)
try:
while True:
time.sleep(0.5)
except KeyboardInterrupt:
pass
c.loop_stop()
c.disconnect()
def main():
ap = argparse.ArgumentParser()
sub_p = ap.add_subparsers(dest="cmd", required=True)
ppub = sub_p.add_parser("pub", help="Publish a single message")
ppub.add_argument("--host", default="192.168.81.147")
ppub.add_argument("--port", type=int, default=1883)
ppub.add_argument("--topic", required=True)
ppub.add_argument("--payload", required=True)
psub = sub_p.add_parser("sub", help="Subscribe for N seconds and print messages")
psub.add_argument("--host", default="192.168.81.147")
psub.add_argument("--port", type=int, default=1883)
psub.add_argument("--topic", required=True)
psub.add_argument("--seconds", type=float, default=5.0)
pwatch = sub_p.add_parser("watch", help="Subscribe forever and print messages")
pwatch.add_argument("--host", default="192.168.81.147")
pwatch.add_argument("--port", type=int, default=1883)
pwatch.add_argument("--topic", default="zigbee2mqtt/#")
args = ap.parse_args()
if args.cmd == "pub":
pub(args.host, args.port, args.topic, args.payload)
elif args.cmd == "sub":
sub(args.host, args.port, args.topic, args.seconds)
elif args.cmd == "watch":
watch(args.host, args.port, args.topic)
if __name__ == "__main__":
main()