Add multi-plug toggle, debounce via unique topic subscriptions

Button (Door Button) now toggles Squiggle, Sideboard Lamp, and Bamboo Lights
via three parallel mqtt-out publishes.

Key fix: avoid subscribing to a wildcard '#' topic alongside a specific topic
in the same Node-RED instance. The mqtt-broker re-adds its handler as an
EventEmitter listener on each subscribe call (file:
  /usr/src/node-red/node_modules/@node-red/nodes/core/network/10-mqtt.js
  _clientOn('message', subscription.handler) is unconditional), so after
each deploy/restart the same message arrives N times to internal listeners.
Each mqtt-in node now uses a unique topic (zigbee2mqtt/<device>).

Removed zigbee-in # wildcard and plug-state ui-text widgets (state now
displayed via per-plug ui-text widgets).

Added function node support to flow2json.py for msg filtering logic.
Added ui-text format/width/height defaults support.
Added dashboard 2.0 config node defaults matching reference flow:
  ui-base, ui-theme, ui-page, ui-group with full property set.
This commit is contained in:
2026-06-30 22:12:22 -07:00
parent ca1c7eda26
commit 517f3dbdae
4 changed files with 458 additions and 199 deletions
+86 -18
View File
@@ -5,44 +5,112 @@ brokers:
host: mosquitto
port: 1883
plugs:
- Squiggle
- Sideboard Lamp
- Bamboo Lights
dashboards:
- tab: Home
icon: dashboard
groups:
- name: Zigbee
width: 6
- base: Dashboard
pages:
- page: Home
icon: home
path: /home
groups:
- name: Plugs
width: 6
flows:
- tab: Zigbee Monitor
description: Monitor and control zigbee2mqtt coordinator
nodes:
- id: zigbee-in
type: mqtt-in
- id: zigbee-state-squiggle
type: mqtt in
broker: mosquitto
topic: zigbee2mqtt/#
qos: 2
topic: zigbee2mqtt/Squiggle
qos: 0
- id: debug
type: debug
- id: zigbee-state-sideboard
type: mqtt in
broker: mosquitto
topic: zigbee2mqtt/Sideboard Lamp
qos: 0
- id: last-msg
- id: zigbee-state-bamboo
type: mqtt in
broker: mosquitto
topic: zigbee2mqtt/Bamboo Lights
qos: 0
- id: button-in
type: mqtt in
broker: mosquitto
topic: zigbee2mqtt/Door Button
qos: 0
- id: btn-filter
type: function
name: Single press only
code: |
try {
const p = JSON.parse(msg.payload);
if (p.action === 'single') {
msg.payload = { state: 'TOGGLE' };
return msg;
}
} catch (e) {}
return null;
- id: plug-out-squiggle
type: mqtt out
broker: mosquitto
topic: zigbee2mqtt/Squiggle/set
- id: plug-out-sideboard
type: mqtt out
broker: mosquitto
topic: zigbee2mqtt/Sideboard Lamp/set
- id: plug-out-bamboo
type: mqtt out
broker: mosquitto
topic: zigbee2mqtt/Bamboo Lights/set
- id: text-squiggle
type: ui-text
group: Zigbee
format: "{{msg.payload|json}}"
group: Plugs
label: Squiggle
format: "{{msg.payload.state}}"
- id: text-sideboard
type: ui-text
group: Plugs
label: Sideboard Lamp
format: "{{msg.payload.state}}"
- id: text-bamboo
type: ui-text
group: Plugs
label: Bamboo Lights
format: "{{msg.payload.state}}"
- id: permit-btn
type: ui-button
group: Zigbee
label: "Permit Join (60s)"
group: Plugs
label: Permit Join (60s)
topic: zigbee2mqtt/bridge/request/permit_join
payload:
value: true
time: 60
- id: mqtt-out
type: mqtt-out
type: mqtt out
broker: mosquitto
wires:
zigbee-in: [debug, last-msg]
zigbee-state-squiggle: [text-squiggle]
zigbee-state-sideboard: [text-sideboard]
zigbee-state-bamboo: [text-bamboo]
button-in: [btn-filter]
btn-filter: [plug-out-squiggle, plug-out-sideboard, plug-out-bamboo]
permit-btn: [mqtt-out]