Skip to content

Fix OTA firmware subscribe topic accordingly to server implementation#268

Open
neomilium wants to merge 1 commit intothingsboard:masterfrom
opus-codium:fix-ota-subscribe-topic
Open

Fix OTA firmware subscribe topic accordingly to server implementation#268
neomilium wants to merge 1 commit intothingsboard:masterfrom
opus-codium:fix-ota-subscribe-topic

Conversation

@neomilium
Copy link

@neomilium neomilium commented Feb 27, 2026

On every MQTT connection, the device reports a firmware error to ThingsBoard:

fw_state: FAILED
fw_error: "Subscribing the given topic (v2/fw/response/+) failed"

OTA checking and downloading still work — the subscribe failure does not prevent
firmware updates. However, the device's firmware state on the ThingsBoard
dashboard permanently shows FAILED, which is misleading and prevents
reliable monitoring of actual OTA status.

Bug

FIRMWARE_RESPONSE_SUBSCRIBE_TOPIC is set to "v2/fw/response/+", but the
ThingsBoard server only accepts subscriptions to "v2/fw/response/+/chunk/+".

// src/OTA_Firmware_Update.h (current)
char constexpr FIRMWARE_RESPONSE_TOPIC[]           = "v2/fw/response/%u/chunk/";   // ← has /chunk/
char constexpr FIRMWARE_RESPONSE_SUBSCRIBE_TOPIC[] = "v2/fw/response/+";           // ← missing /chunk/+
char constexpr FIRMWARE_REQUEST_TOPIC[]            = "v2/fw/request/%u/chunk/%u";   // ← has /chunk/

The subscribe topic is inconsistent with the two other topic constants defined
right next to it, both of which include the /chunk/ segment.

The ThingsBoard MQTT transport handler (MqttTransportHandler.java) validates
subscribe topics with an exact-match switch statement. The only accepted
firmware response topic is DEVICE_FIRMWARE_RESPONSES_TOPIC, which resolves to
"v2/fw/response/+/chunk/+":

// MqttTopics.java
public static final String DEVICE_FIRMWARE_RESPONSES_TOPIC =
    BASE_DEVICE_API_TOPIC_V2 + FIRMWARE + RESPONSE + "/" + SUB_TOPIC + CHUNK + SUB_TOPIC;
//  "v2"                     + "/fw"    + "/response" + "/" + "+"     + "/chunk/" + "+"
//  → "v2/fw/response/+/chunk/+"

Any topic that doesn't match a case falls through to default, which returns
TOPIC_FILTER_INVALID in the SUBACK:

// MqttTransportHandler.java, processSubscribe()
case MqttTopics.DEVICE_FIRMWARE_RESPONSES_TOPIC:   // "v2/fw/response/+/chunk/+"
    registerSubQoS(topic, grantedQoSList, reqQoS);
    break;
default:
    log.warn("[{}][{}] Failed to subscribe because topic is not supported [{}][{}]", ...);
    grantedQoSList.add(ReturnCodeResolver.getSubscriptionReturnCode(
        deviceSessionCtx.getMqttVersion(), MqttReasonCodes.SubAck.TOPIC_FILTER_INVALID));
    break;

When Firmware_OTA_Subscribe() detects the failure, it sends
Firmware_Send_State(FW_STATE_FAILED, message) to the server — polluting the
device's firmware state.

Additional note: MQTT wildcard mismatch

Even if the server accepted v2/fw/response/+, the subscription would never
match firmware chunks. The server publishes to topics like
v2/fw/response/42/chunk/0 (5 levels). The MQTT + wildcard matches exactly
one topic level (MQTT spec §4.7.1), so v2/fw/response/+ (4 levels) cannot
match.

Why OTA still works despite the error

Resubscribe_Topics() calls Firmware_OTA_Subscribe() on every MQTT connect.
This is the code path that triggers the spurious FAILED state. It runs
unconditionally, even when no firmware update is in progress. The return value
is discarded: (void)api->Resubscribe_Topic().

The actual OTA check and download paths are unaffected:

  • OTA check uses shared attributes over v1/devices/me/attributes/response/+,
    a completely separate MQTT path that works correctly.
  • OTA download, when triggered from Firmware_Shared_Attribute_Received(),
    calls Firmware_OTA_Subscribe() again independently. Whether this second call
    succeeds or fails depends on the MQTT client implementation — some clients
    tolerate SUBACK errors and still deliver matching messages.

The only visible impact is the false FAILED state on the server dashboard,
which is reported on every connect regardless of whether an update is in progress.

FIRMWARE_RESPONSE_SUBSCRIBE_TOPIC was "v2/fw/response/+" but the
ThingsBoard server only accepts "v2/fw/response/+/chunk/+". The wrong
topic caused a SUBACK rejection on every MQTT connect, which in turn
made the SDK send fw_state=FAILED / fw_error="Subscribing the given
topic (v2/fw/response/+) failed" as telemetry — polluting the device
firmware state on the dashboard.

The subscribe topic was also inconsistent with its two sibling constants
defined on adjacent lines (FIRMWARE_RESPONSE_TOPIC and
FIRMWARE_REQUEST_TOPIC), both of which already include the /chunk/
segment.

Server reference: MqttTopics.DEVICE_FIRMWARE_RESPONSES_TOPIC resolves
to "v2/fw/response/+/chunk/+" and MqttTransportHandler.processSubscribe()
rejects anything else with TOPIC_FILTER_INVALID.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant