When you integrate a Zigbee network with Node-RED (most often via MQTT and a bridge like Zigbee2MQTT or deCONZ), you may run into a nasty problem:
- A seemingly simple automation starts
running over and over
- Lights keep toggling, scenes re-trigger,
notifications spam your phone
- The flow never settles until you manually
disable it
In many cases, the
root cause is this combination:
- Zigbee broadcasting duplicate messages (by design or due to mesh retries),
and
- A Node-RED flow that feeds its own output
back into its input without
any protection against duplicates.
This article
explains why Zigbee can legitimately produce duplicate events, how
Node-RED reacts to them, and what specifically causes endless loops so you can
design flows that are robust and loop‑free.
1. How Node-RED
Actually Executes Automations
Node-RED is event-driven:
- A message (msg) arrives at an input node
(e.g., mqtt in, http in, zigbee in).
- That message passes through a series of
nodes (function, switch, change, etc.).
- One or more output nodes emit new messages
(e.g., mqtt out, zigbee out, http response).
Important
characteristics:
- Node-RED does not inherently
remember “what it did last time” unless you explicitly use context (flow/global
context, persistent storage, RBE node, etc.).
- Every incoming message is treated as significant by
default, even if it’s identical to the previous one.
- If you connect an output back—directly or
indirectly—to an input that listens on the same event stream,
you’ve built a feedback loop.
Now mix this with
Zigbee, which is allowed to send duplicate broadcasts for
reliability. Without safeguards, each duplicate can re-enter the same flow and
cause an infinite loop.
2. Why Zigbee
Broadcasts Duplicate Messages
Zigbee is a low‑power mesh protocol.
Redundancy is part of its design. Duplicate or repeated messages can appear for
several reasons:
2.1 Mesh Routing
and Retries
- Zigbee devices often route messages through
multiple routers to reach the coordinator.
- When acknowledgements are missed, devices
may re-transmit the same frame.
- Some stacks (or poorly behaving devices)
can end up sending the same state report two or three times within
a short window.
From a Zigbee
perspective, this is acceptable; the protocol is “at least once”,
not “exactly once.”
2.2 Attribute
Reports and State Refreshes
Sensors and switches
may emit:
- Regular attribute reports (e.g.,
temperature every X minutes)
- State refreshes even when nothing changed (some
firmware behaves this way)
- Both a button press event and
a new attribute value for the same physical action
Your bridge (e.g.,
Zigbee2MQTT) usually converts those into MQTT messages on
topics like:
- zigbee2mqtt/living_room_switch
- zigbee2mqtt/living_room_switch/action
- zigbee2mqtt/living_room_switch/state
Depending on
configuration, multiple MQTT messages may describe what is
effectively one user action.
2.3 Group Commands
and Broadcast Behaviour
When Zigbee devices
use group addressing:
- A single command is broadcast to a group
address.
- Multiple routers may rebroadcast or
repeat the command for coverage.
- The bridge/gateway might report
group-level changes as multiple similar events on MQTT.
Bottom line: Duplicate
messages are normal in Zigbee environments. It is Node-RED’s job (your
flows) to handle them gracefully.
3. How Duplicate
Zigbee Messages Turn Into Node-RED Infinite Loops
An infinite loop
usually appears when three conditions combine:
- Node-RED listens to Zigbee events (typically
via mqtt in on zigbee2mqtt/...).
- The flow’s output sends commands
back to the same device or topic.
- Zigbee or the bridge repeats or
re-broadcasts those messages, feeding them back into Node-RED.
3.1 Classic Loop
Pattern: MQTT In → Logic → MQTT Out → MQTT In
A very common
problematic pattern looks like this:
- mqtt in subscribes to zigbee2mqtt/switch_1.
- Node-RED flow processes the message.
- mqtt out publishes a command or
modified state back to the same topic zigbee2mqtt/switch_1 (or
another topic that the bridge mirrors back).
- The Zigbee bridge receives that command
and updates its own state, then publishes a new
message on zigbee2mqtt/switch_1.
- Node-RED sees that as a fresh
incoming event and repeats the entire process.
Now add duplicate
Zigbee broadcasts:
- If each command or state change results
in two or three almost-identical MQTT events, each one will
retrigger the flow.
- With toggle-style logic (e.g., “flip the
current state”), those duplicates cause the device to keep changing state
back and forth.
3.2 Toggle Logic +
Duplicates = Runaway Behaviour
Flows like this are
particularly vulnerable:
- Use a change or function node
to set msg.payload = !msg.payload or “if ON then OFF, else ON.”
- Then send the resulting state back to the
same device or topic.
If the source
sends two “ON” events quickly:
- First ON: flow toggles → sends OFF →
device goes OFF.
- Second ON (duplicate): flow toggles again
→ sends ON → device goes ON.
- The device changing state may emit new
reports (ON/OFF), which are seen as fresh triggers.
- The cycle continues, sometimes spiralling
into a continuous loop until something is disconnected.
Because Node-RED
doesn’t know that the second ON was a duplicate, it repeats the
logic blindly.
4. Specific Root
Causes Inside Node-RED Flows
Here are the most
common design patterns that make endless loops very likely when Zigbee
duplicates messages.
4.1 Subscribing and
Publishing to the Same Topic
If you have:
- mqtt in on zigbee2mqtt/device_X
- mqtt out also publishing to zigbee2mqtt/device_X
then every outgoing
message has a high chance of:
- Coming back as a state update
- Being seen as a new input
- Triggering the flow once again
This is safe only if:
- Outgoing commands are strictly idempotent (e.g.,
always “set brightness to 40%”), and
- You filter duplicates or
drop messages where state hasn’t truly changed.
Without that, each
Zigbee duplicate amplifies the feedback.
4.2 Using Device
State as Both Trigger and Target
Example pattern:
- “When sensor A goes ON, toggle light B.”
- The gateway publishes both:
- A state update for sensor A
- A state update for light B
- Node-RED listens to the entire zigbee2mqtt/# tree.
If your flow logic
does not narrow down what it listens to, the state change of
light B might be interpreted as another trigger, which toggles
something else, and so on. Duplicates multiply this effect.
4.3 Multiple
Bridges or Cross-Linked Brokers
In more advanced
setups:
- You may have Zigbee2MQTT → Broker A →
Node-RED → Broker B → Another bridge back to Broker A.
- Or, multiple bridges syncing topics with
each other.
Each duplicated Zigbee
event bounces around the network, and Node-RED flows that publish on mirrored
topics can unwittingly participate in a topic echo chamber.
5. How to Confirm
That Zigbee Duplicates Are Causing the Loop
Before changing
everything, verify the actual behaviour.
5.1 Use Node-RED
Debug Nodes
- Attach a debug node near
the start of the flow (after mqtt in).
- Set it to show “complete msg
object”.
- Trigger the problematic device once.
Inspect:
- msg.topic: Is it what you expect? Do you
see messages from both state and command topics
entering the same path?
- msg.payload: Are multiple identical
payloads arriving within milliseconds?
- _msgid and timestamps: Do you see
near-simultaneous messages, indicating duplicates?
5.2 Watch the MQTT
Traffic
Use an MQTT client
(MQTT Explorer, mosquitto_sub, etc.):
Bash
mosquitto_sub -h
your_broker -t 'zigbee2mqtt/#' -v
Press a button or
change state once and watch:
- Do you see two or more identical
lines for the same topic and payload?
- Are there extra state reports after
Node-RED sends its commands?
If yes, you know that:
- Zigbee / the bridge is producing
duplicates, and
- Node-RED is likely re-triggering flows
each time.
6. How to Prevent
Endless Loops in Node-RED with Zigbee
You generally need to
address both sides:
- The flow logic (avoid
circular paths and toggle-on-duplicate designs).
- How you filter or deduplicate Zigbee
events.
6.1 Break Circular
Message Paths
Design so that:
- mqtt in and mqtt out do not use
the same topic for both reading and writing.
- Prefer a clear split:
- State topics: published by Zigbee bridge, read by
Node-RED (e.g., zigbee2mqtt/device_X)
- Command topics: written by Node-RED to control devices
(e.g., zigbee2mqtt/device_X/set)
With Zigbee2MQTT, for
example, use:
- State: zigbee2mqtt/my_light
- Command: zigbee2mqtt/my_light/set
Node-RED should be
wired such that only state topics trigger flows, and only
command topics are written as outputs.
6.2 Make Actions
Idempotent Instead of Relative
Avoid “toggle” logic
that depends on the current reported state, particularly when that
state might be duplicated.
Instead of:
- “If it’s ON, send OFF; if OFF, send ON”
prefer:
- “Always set brightness to 30% when motion
is detected”
- “Always turn the light ON when this switch
is pressed”
This way, if two
identical messages arrive:
- Both produce the same command (ON
to 30%)
- The second one doesn’t reverse or conflict
with the first, it just reaffirms the same state
6.3 Filter
Duplicate Messages in Node-RED
Node-RED provides
multiple tools to suppress duplicates before they trigger loops.
6.3.1 RBE (Report
By Exception) Node
- Place an rbe node after mqtt
in.
- Configure it to block repeated
values for the same topic.
Result:
- Only messages where msg.payload actually changes are
passed through.
- If Zigbee sends the same “ON” twice, the
second one is dropped.
6.3.2 Delay Node
with “Rate Limit” and Drop
- Use a delay node set to rate
limit messages (e.g., “1 msg / 1 second, drop intermediate”).
- This can prevent very fast duplicate
bursts from hammering the flow.
Useful when a device
or bridge tends to send multiple updates in a short window.
6.3.3 Custom
De-Dupe in a Function Node
For fine-grained
control, you can store the last payload per topic in context:
JavaScript
let last =
context.get('last') || {};
let key = msg.topic;
let current =
JSON.stringify(msg.payload);
if (last[key] ===
current) {
// Duplicate, drop message
return null;
}
last[key] = current;
context.set('last',
last);
return msg;
Place this early in
the flow. Now only changed payloads move forward.
6.4 Use
Bridge-Level Options (Where Available)
Gateways like
Zigbee2MQTT offer configuration to reduce noisy or duplicate events:
- Options like debounce, occupancy_timeout,
specific reporting intervals, etc.
- For some devices, you can configure reporting
thresholds (e.g., “only report temperature if changed by 0.5°C”).
Check your bridge’s
documentation for:
- How to limit excessive reporting
- How to avoid sending both “action” and “state” events
if you only need one
Less noise at the
source means fewer opportunities for loops in Node-RED.
7. Summary
Node-RED automations
can loop endlessly when Zigbee broadcasts duplicate messages because:
- Zigbee is designed for at-least-once
delivery, so duplicate broadcasts and repeated state
reports are normal.
- Node-RED treats every incoming message as
a fresh event, and flows often:
- Listen to Zigbee state topics,
and
- Publish back to the same or
mirrored topics, forming feedback loops.
- Toggle-based or “relative” logic combined
with duplicates causes devices to oscillate or re-trigger
endlessly.
To prevent this:
- Separate state and command topics and avoid subscribing and publishing
to the same topic.
- Design flows to be idempotent:
send absolute states (“turn ON”, “set 40%”) instead of toggles when
possible.
- Use RBE, delay,
or custom de-duplication nodes to drop duplicates before
they re-enter the logic.
- Tune your Zigbee bridge to reduce
unnecessary or repeated reports.
With these patterns in
place, your Node-RED + Zigbee setup will handle duplicates gracefully, without
falling into infinite loops.
