david c2de5ba85b zigbee2mqtt: patch Bridge TypeError on BLZ coordinator
The Pine64 BLZ fork's zigbee2mqtt image crashes on start because
Bridge.start() calls firstCoordinatorEndpoint().deviceIeeeAddress
before the onMQTTMessage listener is registered. The BLZ coordinator
does not register its endpoint (the coordinator is the dongle itself,
no ZDO loopback), so firstCoordinatorEndpoint() returns undefined.

The crash happens in publishInfo() BEFORE onMQTTMessage is wired up,
which means every bridge MQTT request (permit_join, device rename,
group ops, etc.) silently no-ops — even though the message is
received.

Add infra/zigbee2mqtt/scripts/blz-bridge-patch.js that applies two
surgical fixes (idempotent):

  1. Optional chain on the .deviceIeeeAddress access so the missing
     endpoint doesn't throw.
  2. Move onMQTTMessage registration BEFORE publishInfo/devices/
     groups/definitions, and wrap each publish in try/catch so
     Bridge.start() completes even if a single publish fails.

Add 'just z2m-patch-bridge' to apply the patch on the live container
after fresh image pulls.
2026-07-01 01:22:56 -07:00

nr-flow-validator

Declarative Node-RED flows with conftest/Rego validation. Generates flows.json from a YAML spec, validates against a Rego policy pack, deploys via the Node-RED HTTP API.

Layout

infra/                           # host stack config (compose, z2m, mosquitto, nodered settings)
flows/<name>.yaml                # declarative spec
scripts/flow2json.py             # YAML → JSON generator
policy/flow.rego                 # Rego policy pack (conftest)
output/<name>.json               # generated flows.json

Quick start

just new my-flow           # copy template
$EDITOR flows/my-flow.yaml
just validate              # generate + validate
just deploy                # push to Node-RED at 192.168.81.147:1880

See infra/README.md for the full Docker stack and rebuild procedure.

Diagnostics

All diagnostic commands live in the justfile. Run just --list to see them.

  • just nr-show — live flow summary (node count, functions, button wiring, mqtt-out topics)
  • just nr-diff — diff deployed /flows against output/<name>.json (ignores random uuid ids)
  • just mqtt-button [action] — E2E test: publish a button action and watch which /set topics fire
  • just z2m-devices — paired zigbee devices + group membership
  • just mqtt-pub TOPIC PAYLOAD / just mqtt-sub TOPIC / just mqtt-watch — raw MQTT
  • just nr-logs / just z2m-logs / just mqtt-logs / just doorbell-logs — container log tails
  • just restart SERVICE / just restart-all — docker restart
  • just doorbell-publish "Title" "Body" — publish a test alert to ntfy
  • just rpi CMD — run any command on the RPi over SSH

scripts/mqtt.py and scripts/diag.py are the underlying helpers. Override the target host with just RPI_HOST=user@host mqtt-button single.

Generation invariants

These are enforced by policy/flow.rego and burned in from real bugs found in deployment. The generator follows them by construction; the policy is the second line of defense.

  1. Flat structure only. Node-RED 5.0 silently breaks MQTT broker connection when a tab has an internal nodes: [...] array. Always emit all nodes at top level with a z field referencing the tab id. Use policy/flow.rego::deny_wrapped_tab to reject wrapped structure.
  2. Every node needs wires: []. Missing or non-array wires throws TypeError in Flow.js::rewireNode. deny_missing_wires enforces this.
  3. MQTT brokers must have autoConnect: true. Without it the broker never reconnects after the first user disconnects. deny_no_autoconnect.
  4. protocolVersion must be "3", "4", or "5". deny_invalid_protocol.
  5. ui-button must have icon: "". Dashboard 2.0 throws TypeError on prependIcon otherwise. Generator sets this explicitly.
  6. Dashboard widgets need a z field referencing their tab id, otherwise the SPA renders the group but the widgets never appear. Generator sets this.
  7. Wire references must resolve to known node ids. deny_wire_ref.

Validation rules summary

Rule Catches
deny_missing_type node without type
deny_missing_field required field missing for type
deny_broker_ref mqtt in/out references unknown broker
deny_group_ref ui-* references unknown group
deny_page_ref ui-group references unknown page
deny_wire_ref wire targets unknown node
deny_missing_flow wire references a node not in z of a tab
deny_isolated_node node with no wires in/out (config nodes exempted)
deny_broker_port mqtt-broker port outside 1-65535
deny_wrapped_tab tab with internal nodes array (nodered 5.0 bug)
deny_missing_wires node without wires array
deny_no_autoconnect mqtt-broker without autoConnect: true
deny_invalid_protocol mqtt-broker protocolVersion not in 3/4/5
deny_unused_broker broker defined but never referenced

Deployment target

Node-RED on RPi 3B+ DietPi at 192.168.81.147:1880, persisted via Docker bind mount to /root/nodered. The deploy target's flows.json is the source of truth in production; this generator is for declarative editing.

Dashboard 2.0 SPA quirk

Dashboard 2.0's dist/index.html ships with relative asset paths that break at /dashboard/home. /data/start.sh on the host patches in <base href="/dashboard/"> on container start. Without that patch the SPA loads but assets 404 and nothing renders.

S
Description
Declarative Node-RED flow generator with conftest/Rego validation
Readme 188 KiB
Languages
JavaScript 29.7%
Python 26.6%
C++ 20.1%
Just 7.9%
Open Policy Agent 6.3%
Other 9.4%