Multi-plug toggle wiring with single button, avoid Node-RED mqtt-broker duplicate-delivery bug #1

Closed
opened 2026-07-01 05:12:46 +00:00 by david · 1 comment
Owner

Track the wiring that maps a single ThirdReality button (Door Button) to toggling three ThirdReality smart plugs (Squiggle, Sideboard Lamp, Bamboo Lights) via zigbee2mqtt.

Findings

  • Node-RED 5.0 + Dashboard 2.0 (v1.30.2) mqtt-broker has a duplicate-delivery bug when a single mqtt-in subscriber exists alongside a wildcard # subscriber on the same base topic. Source: /usr/src/node-red/node_modules/@node-red/nodes/core/network/10-mqtt.js line ~1091: node._clientOn('message', subscription.handler) is unconditional and re-adds the handler as an EventEmitter listener on every subscribe call.
  • Workaround: each mqtt-in must subscribe to a unique topic. Use specific topics (zigbee2mqtt/<device>) instead of wildcards (zigbee2mqtt/#).
  • zigbee2mqtt groups do not support TOGGLE — only ON/OFF. Use individual mqtt out nodes per plug.

Implemented

  • Three parallel mqtt out nodes, each publishes TOGGLE to its plug on button press
  • Three separate mqtt in nodes, one per plug, drive the corresponding ui-text widget
  • btn-filter function node drops non-action:single button events (battery reports, double press, etc.)

Reboot survival

All configs are bind-mounted to host paths:

  • /root/nodered/flows.json → nodered container /data/flows.json (volume)
  • /root/zigbee2mqtt/configuration.yaml → zigbee2mqtt container /app/data/configuration.yaml (volume)
  • /root/nodered/node_modules/@flowfuse/node-red-dashboard/dist/index.html (modified with <base href="/dashboard/"> to fix Vue Router 4 replace() error)

The <base href> patch is fragile: any npm install in the nodered container would overwrite it. Needs a proper upstream PR to FlowFuse/node-red-dashboard.

Next steps

  • Submit upstream PR for the <base href> issue (or document as manual fix)
  • Add Rego policy to catch wildcard-alongside-specific topic collisions
  • Add ui-template support for advanced widget rendering
Track the wiring that maps a single ThirdReality button (Door Button) to toggling three ThirdReality smart plugs (Squiggle, Sideboard Lamp, Bamboo Lights) via zigbee2mqtt. ## Findings - Node-RED 5.0 + Dashboard 2.0 (v1.30.2) mqtt-broker has a duplicate-delivery bug when a single mqtt-in subscriber exists alongside a wildcard `#` subscriber on the same base topic. Source: `/usr/src/node-red/node_modules/@node-red/nodes/core/network/10-mqtt.js` line ~1091: `node._clientOn('message', subscription.handler)` is unconditional and re-adds the handler as an EventEmitter listener on every subscribe call. - Workaround: each mqtt-in must subscribe to a unique topic. Use specific topics (`zigbee2mqtt/<device>`) instead of wildcards (`zigbee2mqtt/#`). - zigbee2mqtt groups do not support `TOGGLE` — only `ON`/`OFF`. Use individual `mqtt out` nodes per plug. ## Implemented - Three parallel `mqtt out` nodes, each publishes TOGGLE to its plug on button press - Three separate `mqtt in` nodes, one per plug, drive the corresponding `ui-text` widget - `btn-filter` function node drops non-`action:single` button events (battery reports, double press, etc.) ## Reboot survival All configs are bind-mounted to host paths: - `/root/nodered/flows.json` → nodered container `/data/flows.json` (volume) - `/root/zigbee2mqtt/configuration.yaml` → zigbee2mqtt container `/app/data/configuration.yaml` (volume) - `/root/nodered/node_modules/@flowfuse/node-red-dashboard/dist/index.html` (modified with `<base href="/dashboard/">` to fix Vue Router 4 `replace()` error) The `<base href>` patch is fragile: any `npm install` in the nodered container would overwrite it. Needs a proper upstream PR to FlowFuse/node-red-dashboard. ## Next steps - Submit upstream PR for the `<base href>` issue (or document as manual fix) - Add Rego policy to catch wildcard-alongside-specific topic collisions - Add ui-template support for advanced widget rendering
Author
Owner

Update: the duplicate-delivery theory in this ticket was a red herring. The real bug in nodered 5.0 + mqtt-broker is that tabs containing an internal nodes: [...] array silently break the MQTT broker connection — no error, no log, just no Connected to broker line. Switching to a flat structure (all nodes at top level with z field referencing the tab id) restores the broker. Verified by bisection from a minimal flow (broker connects) to a wrapped-tab flow (broker never connects).

The wildcard-alongside-specific topic pattern also works fine once the structure is flat, so the subscribe-handler stacking theory from 10-mqtt.js:1091 is not the blocker. We still use specific topics for cleanliness.

Added deny_wrapped_tab to the Rego policy to catch this on validation. Implementing in the next commit (closes this issue).

Update: the duplicate-delivery theory in this ticket was a red herring. The real bug in nodered 5.0 + mqtt-broker is that **tabs containing an internal `nodes: [...]` array silently break the MQTT broker connection** — no error, no log, just no `Connected to broker` line. Switching to a flat structure (all nodes at top level with `z` field referencing the tab id) restores the broker. Verified by bisection from a minimal flow (broker connects) to a wrapped-tab flow (broker never connects). The wildcard-alongside-specific topic pattern also works fine once the structure is flat, so the subscribe-handler stacking theory from `10-mqtt.js:1091` is not the blocker. We still use specific topics for cleanliness. Added `deny_wrapped_tab` to the Rego policy to catch this on validation. Implementing in the next commit (closes this issue).
david closed this issue 2026-07-01 06:16:21 +00:00
Sign in to join this conversation.
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: david/nr-flow-validator#1