Files
nr-flow-validator/justfile
T
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

157 lines
5.2 KiB
Makefile

# nr-flow-validator — Declarative Node-RED flows with conftest validation
set export
# Default: generate a flow, validate, and show results
default: validate
# ---- Flow generation ----
# Generate Node-RED flow JSON from a YAML spec
generate FLOW="zigbee-monitor":
@echo "=== Generating {{FLOW}} ==="
python3 scripts/flow2json.py flows/{{FLOW}}.yaml > output/{{FLOW}}.json
@echo "Wrote output/{{FLOW}}.json"
# Generate all flows
generate-all:
@for f in flows/*.yaml; do \
name=$(basename "$f" .yaml); \
just generate "$name"; \
done
# ---- Validation ----
# Validate a generated flow against Rego policies
validate FLOW="zigbee-monitor": generate
@echo "=== Validating {{FLOW}} ==="
conftest test output/{{FLOW}}.json -p policy/ --namespace nodered --no-color
# Validate all flows
validate-all:
@for f in output/*.json; do \
echo "=== Validating $(basename "$f") ==="; \
conftest test "$$f" -p policy/ --namespace nodered --no-color; \
done
# ---- Deployment ----
# Deploy flow to Node-RED instance
deploy FLOW="zigbee-monitor" HOST="http://192.168.81.147:1880": generate validate
@echo "=== Deploying {{FLOW}} to {{HOST}} ==="
curl -s -o /dev/null -w "HTTP %{http_code}" \
-X POST {{HOST}}/flows \
-H "Content-Type: application/json" \
-H "Node-RED-Deployment-Type: full" \
-d @output/{{FLOW}}.json
@echo ""
# Quick deploy (skip validation)
deploy-quick FLOW="zigbee-monitor" HOST="http://192.168.81.147:1880": generate
@echo "=== Quick deploy {{FLOW}} to {{HOST}} ==="
curl -s -o /dev/null -w "HTTP %{http_code}" \
-X POST {{HOST}}/flows \
-H "Content-Type: application/json" \
-H "Node-RED-Deployment-Type: full" \
-d @output/{{FLOW}}.json
@echo ""
# ---- Viewing ----
# Fetch and display current flows from Node-RED
pull HOST="http://192.168.81.147:1880":
curl -s {{HOST}}/flows | python3 -m json.tool
# Open dashboard in browser
dashboard:
xdg-open http://192.168.81.147:1880/dashboard 2>/dev/null || open http://192.168.81.147:1880/dashboard 2>/dev/null || echo "Open: http://192.168.81.147:1880/dashboard"
# ---- Development ----
# Watch for changes and re-validate
watch FLOW="zigbee-monitor":
@while true; do \
inotifywait -q -e modify flows/{{FLOW}}.yaml policy/*.rego; \
clear; just validate {{FLOW}}; \
done
# Create a new flow from template
new NAME:
@cp flows/zigbee-monitor.yaml flows/{{NAME}}.yaml
@echo "Created flows/{{NAME}}.yaml — edit and run 'just validate {{NAME}}'"
# ---- Diagnostics ----
# All commands target the live RPi at $RPI_HOST (default root@192.168.81.147).
# Override with: just RPI_HOST=user@host mqtt-pub topic msg
RPI_HOST := "root@192.168.81.147"
MQTT_HOST := "192.168.81.147"
MQTT_PORT := "1883"
RPI_DIR := "/root"
NR_PORT := "1880"
# Run a command on the RPi over SSH
rpi CMD *ARGS:
ssh -o ConnectTimeout=5 {{RPI_HOST}} '{{CMD}} {{ARGS}}'
# Publish to MQTT broker on the RPi
mqtt-pub TOPIC PAYLOAD:
uv run --with paho-mqtt python3 scripts/mqtt.py pub --topic '{{TOPIC}}' --payload '{{PAYLOAD}}'
# Subscribe to MQTT broker on the RPi for N seconds
mqtt-sub TOPIC SECONDS="5":
uv run --with paho-mqtt python3 scripts/mqtt.py sub --topic '{{TOPIC}}' --seconds {{SECONDS}}
# Publish a button action and watch what /set topics fire (E2E test)
mqtt-button ACTION="single":
uv run --with paho-mqtt python3 scripts/diag.py mqtt-button --action {{ACTION}}
# Show live Node-RED flow summary (node count, function names, button wires, mqtt-out topics)
nr-show:
python3 scripts/diag.py show
# Diff deployed Node-RED flow against repo (excluding random uuid ids)
nr-diff FLOW="zigbee-monitor": generate
python3 scripts/diag.py diff output/{{FLOW}}.json
# Tail Node-RED container logs
nr-logs:
ssh {{RPI_HOST}} 'docker logs --tail 50 -f nodered'
# Tail zigbee2mqtt container logs
z2m-logs:
ssh {{RPI_HOST}} 'docker logs --tail 50 -f zigbee2mqtt'
# Tail mosquitto container logs
mqtt-logs:
ssh {{RPI_HOST}} 'docker logs --tail 50 -f mosquitto'
# Restart a service container
restart SERVICE:
ssh {{RPI_HOST}} 'docker restart {{SERVICE}}'
# Restart the full stack
restart-all:
ssh {{RPI_HOST}} 'docker restart mosquitto zigbee2mqtt nodered'
# Show paired zigbee devices and their friendly names
z2m-devices:
ssh {{RPI_HOST}} 'cat {{RPI_DIR}}/zigbee2mqtt/configuration.yaml' > /tmp/z2m.yaml
python3 scripts/diag.py z2m-devices /tmp/z2m.yaml
# Show per-device persisted state from z2m
z2m-state:
ssh {{RPI_HOST}} 'cat {{RPI_DIR}}/zigbee2mqtt/state.json' | python3 -m json.tool | head -60
# Open the Node-RED editor
nr-editor:
xdg-open http://{{MQTT_HOST}}:{{NR_PORT}} 2>/dev/null || open http://{{MQTT_HOST}}:{{NR_PORT}} 2>/dev/null || echo "Open: http://{{MQTT_HOST}}:{{NR_PORT}}"
# Open the Dashboard 2.0 SPA
nr-dashboard:
xdg-open http://{{MQTT_HOST}}:{{NR_PORT}}/dashboard 2>/dev/null || open http://{{MQTT_HOST}}:{{NR_PORT}}/dashboard 2>/dev/null || echo "Open: http://{{MQTT_HOST}}:{{NR_PORT}}/dashboard"
# Watch live MQTT traffic (matches the z2m base_topic by default)
mqtt-watch TOPIC="zigbee2mqtt/#":
uv run --with paho-mqtt python3 scripts/mqtt.py watch --topic '{{TOPIC}}'