At 2am the hallway started clicking. Not once. Forever. Two wall switches were flipping each other on and off in a tight loop. A soft relay machine gun in the dark. I finally went and watched the logs. The shape of it was unmistakable:
.155 RUL: POWER1#STATE... -> Publish cmnd/.166/Power 0 .166 MQT: cmnd POWER 0 received -> POWER OFF -> RUL: POWER1#STATE... -> Publish cmnd/.155/Power 0 .155 MQT: cmnd POWER 0 received -> POWER OFF -> RUL: POWER1#STATE... -> Publish cmnd/.166/Power 0
Each device applied the incoming change and then re-announced it. That made the other device apply it and re-announce too. Forever. If you have ever watched a replication storm, a webhook retry loop, or two event handlers feeding each other, you know this shape. It just doesn’t usually live in your hallway wall.
The mechanism is general. A state-mirroring protocol where the receiver applies the effect but never updates the dedup key it uses to suppress its own echo. Every incoming message looks new. So the system amplifies instead of settling.
I built this into two light switches with Tasmota and MQTT while getting a new house’s smart-home stack ready. The switches are Martin Jerry units talking to my own broker, mqtt_broker_esp. The goal was a virtual 3-way: flip either switch and the other follows. It worked great for months. Then a power blip hit. Both rebooted and started screaming at each other.
This is how the bug works, how I found it live instead of guessing, and the one-line fix that killed it.
The setup
Two switches, mirrored over MQTT. The naive version is obvious: when switch A changes, publish A’s new state to switch B, and vice versa.
Rule1 ON Power1#State!=%var1% DO Backlog VAR1 %value%; Publish cmnd/<peer>/Power %value% ENDON
The !=%var1% part was my echo guard. The theory was simple. Store the last value I acted on in VAR1 . Only publish when the state actually changes. The command coming back from the peer would then match VAR1 and get suppressed. One hop each direction, then quiet.
... continue reading