Skip to content

MQTT input stops or faults

An MQTT-backed datasource was streaming, and something breaks:

  • Inference goes from running to idle/faulted on its own — no one pressed Stop.
  • Predictions come back as NaN, or the dashboard shows nothing plausible.
  • The MQTT output sink reports write failures, with a reason like transport: not connected.
  • A tag mapping is rejected at save time with a tag_outside_subscription_* message.
  • Enable, or Test Connection, fails outright before anything ever streamed.

MQTT is a push protocol — inference pulls one sample per tick from a cache the adapter fills as messages arrive. Every cause below is really the same question from a different angle: did the cache get a usable value recently enough?

Five distinct failure modes share this symptom set. Grep the backend log to tell them apart — all examples below show only error codes/reasons, never payload contents or credentials.

Terminal window
docker compose -f docker-compose.release.yml logs backend | grep -iE "MqttAdapter|ChannelReadException|channel .* read failed|tag stale|disconnected|not connected"
  1. Stale topic (slow or dead publisher). A warning fires before the fault:

    MqttAdapter tag stale: tag=<topic> age=<age> staleAfter=<window>
    Channel <index> (<tag>) read failed — stopping inference
    Inference fault: <reason>

    The cache expiry window is clamp(50 / SamplingHz, 5s, 60s)5 seconds at the default 10 Hz. A topic publishing slower than that window ages out and raises ChannelReadException, which the runtime treats the same as a dead sensor: it stops inference rather than feed the model a frozen value.

  2. NaN / Infinity sentinel. The MQTT payload parser only accepts finite numbers — NaN, Infinity, -Infinity, and an overflowing literal like 1e400 are all rejected before they ever reach the cache. A device that publishes a “no measurement” sentinel this way never refreshes its cached value, so it ages out and fails exactly the same way as cause 1 above — same log lines, same ChannelReadException. There is no separate “NaN reached the model” failure mode by design; that would silently poison every downstream prediction instead.

  3. Client-id eviction. Look for:

    MqttAdapter disconnected (SessionTakenOver); reconnecting

    or, on the output side, a write failing with:

    transport: not connected

    MQTT brokers evict the incumbent connection when a second client connects with the same client id. On a current build this now happens only when: two datasources of the same role (two inputs, or two outputs) share one explicit Client ID, or an external MQTT client reuses a datasource’s configured id.

  4. Tag outside the subscribed Base Topic. Saving a tag mapping (or enabling the datasource) fails immediately with:

    tag_outside_subscription_<tagRef>

    The input adapter subscribes only to its mapped tags (or the configured Base Topic wildcard) — a topic path outside that scope is rejected at save time instead of silently never producing a reading.

  5. Socket-level connect failure. Test Connection or Enable fails before any streaming starts, with one of:

    connect_failed unreachable / refused / DNS failure   timeout the connect attempt was cancelled before finishing

    Backend log corroborates with the elapsed time and exception type:

    MqttAdapter connect failed: broker=<broker> elapsedMs=<ms> errorType=<type>
  1. Stale topic / NaN sentinel (causes 1–2). Fix the publisher’s cadence or stop it from publishing a non-finite sentinel, then re-enable the datasource — a fault tears the runtime down to idle, so recovery is not automatic:

    • Confirm the mapped topic’s real publish interval and compare it against the stale window at the datasource’s configured Sampling Hz.
    • If the topic legitimately reports “no measurement”, switch the device/gateway to simply not publish during that period rather than sending NaN/Infinity — both are treated identically (a quiet topic), so there’s no advantage to sending the sentinel.
    • Re-enable the datasource (reloads the model, reconnects the adapter), then press Start.
  2. Client-id eviction (cause 3). Give each same-role datasource (two inputs, or two outputs) sharing an explicit Client ID a distinct value, or clear the field so MQTTnet auto-generates a unique id per side. If an external client is reusing your datasource’s id, change one of them. This does not apply to an input/output pair built from the same config — the v1.28.0 role suffix (-in/-out) already keeps those from colliding, with no edit needed.

  3. Tag outside subscription (cause 4). Either widen the datasource’s Base Topic to cover the tag’s topic path, or fix the tag’s topic path to fall inside the current Base Topic. Re-save the mapping.

  4. Socket failure (cause 5). Verify host, port, and network reachability from inside the backend container — not from your desktop. Swap localhost for the broker’s container/service name (or host.docker.internal from a container to the Docker host), then retry Test Connection.

  • Check every mapped topic’s publish interval against the stale window before enabling a new MQTT input — lower Sampling Hz to widen tolerance (1 Hz → 50s) if a topic legitimately publishes slowly.
  • Never reuse one explicit Client ID across two datasources of the same role (two inputs, or two outputs), and don’t reuse a datasource’s Client ID for an external MQTT client. An input/output pair on the same config is already safe — v1.28.0 auto-suffixes each side (-in/-out).
  • Scope the Base Topic tightly to the tags you actually map, and map tags whose topic paths already fall inside it.
  • Don’t encode “no data” as NaN/Infinity on the wire. A device that goes quiet when it has nothing to report behaves identically and is easier to reason about than a sentinel value.
  • Test Connection from the box’s perspective, not your workstation’s — a broker reachable from your desktop over localhost may not be reachable from the backend container at all.