Node-RED MQTT Heartbeats: Keep Each Sensor on Its Own Trigger Timer
A gateway receives MQTT measurements from sensors A and B. A continues publishing, B goes silent, and the expected silence alarm never appears. If both streams feed one extending Trigger timer, A may be continually postponing the deadline that was supposed to detect B’s absence.
This walkthrough uses the Node-RED 5.0.8 Trigger help and its tagged implementation. The example is a proposed industrial telemetry fixture, not an executed flow or a hard real-time safety watchdog.
Define what one heartbeat represents
Start with an explicit, bounded set of accepted topics, such as plant/press/A/heartbeat and plant/press/B/heartbeat. Filter that set before Trigger. A broad MQTT subscription can deliver messages for observation without granting every topic permission to create another monitored stream.
Choose a stable stream identity. For these dedicated heartbeat topics, msg.topic is sufficient after validation. If several measurement topics belong to one device heartbeat contract, map them to a deliberate device key first. Do not silently switch between “sensor,” “topic” and “machine” as if they describe the same monitored entity.
Also specify which received messages count as fresh observations. A retained snapshot or replayed old message must not automatically renew liveness just because it arrived at the gateway. For this fixture, only messages accepted by the application’s freshness policy enter the watchdog. That policy must be established upstream; the Trigger timer does not authenticate acquisition time.
Configure a silence timer for each accepted stream
The help documents a watchdog configuration: send nothing initially, wait for the configured interval, and extend that interval whenever another message arrives. Configure the second output to identify a silence event, preserving the validated stream identity for the consumer.
Then enable separate streams using the selected message property. This is the setting that changes the failure mechanism. In the runtime, bytopic === "all" forces every message into the internal key _none. Otherwise, the selected property supplies the key, and the extension branch replaces the timeout for that key.
An extending timer shared by all inputs answers “has any accepted message arrived recently?” A keyed timer answers that question independently for each stream that has started a timer. Both configurations can be useful, but only the second matches this per-sensor requirement.

Walk the busy-A, silent-B counterexample
Use an illustrative silence interval of ten seconds. At time zero, A and B each provide an accepted heartbeat. A then reports at three, six and nine seconds; B sends nothing further.
With one shared extending timer, the accepted A message at nine seconds moves the next deadline to nineteen seconds. No B-specific timeout exists at ten seconds. Continuing A traffic can keep that shared timer active indefinitely, even though B remains silent.
With keyed timers, A’s messages replace only A’s deadline. B’s deadline remains ten seconds after its own last accepted input. The proposed expected result is a B silence event when that timer is serviced. Actual event-loop scheduling may delay delivery; ten seconds is a configured interval, not a measured maximum alarm latency.
Record the stream identity on the emitted event. A generic “sensor timeout” payload without its key leaves the downstream alarm unable to identify which independent timer expired.
Reject missing identities instead of grouping them
The runtime obtains the configured property and falls back to _none for a missing or falsy value. Two malformed messages can therefore share a timer even when separate-stream mode is enabled. Checking the editor setting alone does not prove isolation.
Validate a nonempty key from the bounded allowed set before Trigger. Route rejected identities to a diagnostic path without renewing a real sensor’s timer. Include a proposed test in which both malformed messages lack the key: the flow should reject them rather than reporting a successful keyed-watchdog configuration.
Reset handling is keyed too. A message containing msg.reset clears the selected stream’s timer; the implementation checks presence of the property, so setting it to false is not equivalent to removing it. Construct reset messages deliberately and preserve the intended key. Do not leave a reset property on ordinary heartbeat messages.
Account for startup and flow replacement
A sensor C that never sends an input has not created a Trigger timer. The node cannot infer C from an equipment inventory it never received. Define a separate startup policy for expected devices, such as an explicit awaiting-first-observation state with its own deadline, rather than claiming keyed Trigger alone covers never-seen sensors.
The implementation clears its in-memory timers when the node closes. A deployment or restart therefore requires reestablishing the monitoring state under that startup policy. Persistent application context does not automatically restore these runtime timeout objects.
For acceptance, exercise continued A traffic, silent B, missing keys, targeted reset and never-seen C as separate cases. Save the exact settings and accepted input sequence with each expected identity. No broker or Node-RED runtime was operated here. The deliverable is a per-stream silence contract that other healthy traffic cannot accidentally renew.