Skip to content

Automatic Publication of Status and Config

The RTK Base firmware automatically publishes its live status and full device configuration as a single JSON document to the MQTT topic base/{SN}/info. The publication happens on connect, periodically, on important hardware events, and on demand. This is the primary mechanism by which a fleet manager, cloud portal, or external integration learns what the base is, what it is configured to do, and what it is currently doing.

The same JSON document is also returned by the HTTP GET /get-info endpoint; both surfaces are byte-identical at the field level. See REST API » Get System Information for the HTTP counterpart.


Transport

Property Value Source
Topic base/<SN>/info components/mqtt_manager/mqtt_api.c:314
QoS 0 (best effort) components/mqtt_manager/mqtt_api.c:315 (literal 0)
Retain 0 (not retained) components/mqtt_manager/mqtt_api.c:325
Content-Type raw UTF-8 JSON (MQTT has no content type)
Payload upper bound MQTT_PAYLOAD_MAX_SIZE = 131072 bytes (128 KiB) components/mqtt_manager/mqtt_api.c:25
ESP-MQTT outbox limit 64 * 1024 bytes components/mqtt_manager/mqtt_manager.c:44
Transport esp_mqtt_client_publish() via mqtt_publish_data() components/mqtt_manager/mqtt_manager.c:277
Client type literal "base" (so the topic prefix is always base/...) components/rtk_base/app_base_manager.c:1304
<SN> source NVS key BASE_CONFIG_SERIAL_NUM components/mqtt_manager/mqtt_manager.c:99

The publish primitive logs a single ESP_LOGE ("Impossible to publish data, mqtt isn't ready.") the first time the link is not up, and silently returns ESP_ERR_INVALID_STATE on every subsequent call until the link is restored.


Publication Triggers

There are four code paths that schedule an info publication. All four go through the same work-queue entry mqtt-publish-info, executed by mqtt_api_publish_info_work() at components/mqtt_manager/mqtt_api.c:321, which calls mqtt_api_publish_info(~0, 0).

# Trigger Condition Source
1 MQTT connect MQTT_EVENT_CONNECTED is received components/mqtt_manager/mqtt_manager.c:336 (mqtt_api_schedule_initial_info())
2 Periodic 6 h uptime_sec() - last_info > 6 * 3600 components/mqtt_manager/mqtt_api.c:336-340, polled from components/rtk_base/periodic_task.c:190
3 ZED UBX MON-VER received First time the ZED module reports its MON-VER UBX message (class 0x27, id 0x03) carrying the unique ID components/rtk_base/app_base_manager.c:1037-1053 (zed_ubx_recv_callback)
4 On-demand request Any client publishes to base/{SN}/info/get components/mqtt_manager/mqtt_api.c:398-399

Throttle semantics

  • The 6-hour counter (last_info) is updated only on a successful publish (components/mqtt_manager/mqtt_api.c:326-327).
  • The periodic task wakes every TASK_WAKE_UP_PERIOD = 100 ms (components/rtk_base/periodic_task.c:29) and re-evaluates the throttle each tick.
  • On a fresh MQTT connect, Trigger 1 fires first and resets the 6-hour timer.
  • If MQTT has been offline for an extended period, the moment the link comes back up Trigger 1 publishes and resets the timer — there is no "catch-up burst" of missed publications.

Async dispatch

Triggers 1, 2, and 3 all call mqtt_api_schedule_initial_info() which uses the brlib work queue:

add_work("mqtt-publish-info", mqtt_api_publish_info_work, &priv, 0);

If the work queue is full, add_work() returns ESP_FAIL and the publish is silently dropped (components/mqtt_manager/mqtt_api.c does not check the return value).


On-Demand Request

A consumer can request a fresh info publication at any time by publishing an empty message to the trigger topic:

mosquitto_pub -h mqtt.yamabikorobots.net \
    -p 8883 \
    --cert client.crt --key client.key \
    -t "base/BRRB000021/info/get" \
    -m ""

The base responds by publishing the same full JSON document to base/{SN}/info (Trigger 4 above). The request/response pair is handled in components/mqtt_manager/mqtt_api.c:393-401:

if (!strcmp(mytopic, "info/get")) {
    mqtt_api_publish_info(~0, 0);
}

This is the same pattern used for the other REST-like operations over MQTT (see MQTT API).


JSON Payload Schema

The payload is built by rtkbase_json_get_info() in components/http_manager/http_server.c:165 (approximately 750 lines). It takes a bitmask argument that controls which fields are emitted. The MQTT publisher always passes ~0 (all bits set), so what the broker sees is the maximum-detail document.

Bitmask flags

Defined in components/mqtt_manager/include/mqtt_api.h:20-24:

Flag Value Meaning
INF_RUNTIME 0x001 Live values: state, uptime, memory, antenna, RTCM, position, IP, etc.
INF_STATIC 0x002 Values that do not change at runtime: firmware version, sha256, idf, build date, hardware, modem SIM, etc.
INF_CONFIG 0x004 User-configurable settings: NVS-stored parameters.
INF_BASIC 0x100 Granularity level: emit only the core top-level fields.
INF_ALL 0x200 Granularity level: emit all sub-fields.
ANY ~0 (used internally) All bits set — full document.

The combination is evaluated by the helper macro at components/http_manager/http_server.c:167:

#define TEST(TYP, LVL) ((bitmask & (TYP)) && (bitmask & (LVL)))

Schema

The full per-field schema (every top-level key, every nested object, every conditional field, and a complete worked example) lives in REST API » Get System Information. The HTTP GET /get-info endpoint and the MQTT base/{SN}/info publication are byte-identical at the JSON level, so the schema is authoritative for both surfaces.

The HTTP path also lets the caller request a reduced document (for example, ?0x06 to get config + runtime only). No current MQTT caller exercises that — the broker always sees the full document.

Top-level keys

For quick orientation, the document groups configuration and runtime state under these keys (the modem block is only present on hardware with a 4G modem attached):

Key Description
esp ESP32 identity, uptime, memory
gnss ZED module, FSM state, position, reference, RTCM
base Application-level state, LEDs, firmware-update progress
eth Ethernet link state and IP configuration
wap WiFi access point state and connected stations
sta WiFi station uplink state (when STA mode is enabled)
udp UDP RTCM output configuration
mqtt MQTT broker configuration and link state
router Teltonika RUT2xx router
modem Quectel 4G modem and SIM (only on equipped hardware)

Online Status & Last Will

The firmware publishes a single-byte state to base/{SN}/online to let a remote consumer (fleet manager, dashboard, monitoring agent) detect whether each base is currently reachable on the broker. The topic uses the MQTT Last Will and Testament (LWT) mechanism so that even an ungraceful crash is observable.

Payload Meaning
y Online — fresh MQTT session established. The base just published this on MQTT_EVENT_CONNECTED.
r Restarting — the base is briefly reconnecting (cert renewal, broker URL change, error recovery). Reappears within ~5 s as y.
q Quitting — graceful stop (reboot, OTA, WiFi STA lost, console mqtt stop). May come back later as y.
L Lost — broker detected an ungraceful disconnect (no DISCONNECT packet before the 120 s keepalive expired).

Lowercase letters are published by the base, uppercase L by the broker — a passive observer can tell which side emitted the message.

Transport: topic base/{SN}/online, QoS 0, retain 1, 1-byte payload. The LWT is registered on every MQTT_EVENT_BEFORE_CONNECT (components/mqtt_manager/mqtt_manager.c:325-330); the explicit y is published on MQTT_EVENT_CONNECTED (components/mqtt_manager/mqtt_manager.c:339); r is published by mqtt_schedule_restart() (:654); q is published by mqtt_stop() (:682); L is the LWT message ("L", :33-38).

Consumer pattern

Subscribe once to base/+/online and react to the single retained byte. A fresh subscriber receives the last-known state immediately on subscribe (the broker delivers the retained message as a normal PUBLISH).

mosquitto_sub -h mqtt.yamabikorobots.net -p 8883 \
    --cert client.crt --key client.key \
    -t "base/+/online" -v

For guaranteed delivery of every transition, subscribe with QoS ≥ 1: the publisher uses QoS 0, but MQTT downgrades to the subscriber's maximum, so a QoS 1 subscriber gets an acknowledged delivery of each state change.

Edge cases

  • A clean MQTT_EVENT_DISCONNECTED (transient link drop) does not fire the LWT — the base sends a proper DISCONNECT first. Consumers do not see a spurious L during a normal reconnect.
  • A transient r or q published with QoS 0 may be lost under network stress, but the next retained publication (y on reconnect, or the broker's L on keepalive expiry) always converges the consumer's view to the last-known state.

Errors and Failure Modes

MQTT offline

If the link is not up when mqtt_publish_data() is called, it returns ESP_ERR_INVALID_STATE, logs a single ESP_LOGE the first time, and suppresses further warnings via a static link_warned flag (components/mqtt_manager/mqtt_manager.c:281-303).

For Trigger 2 (the 6-hour periodic), last_info is only updated on success (components/mqtt_manager/mqtt_api.c:326-327), so the next periodic-task tick will see now - last_info > 6 * 3600 and re-schedule the work entry. The publication will then succeed at the first opportunity.

For Trigger 1 (MQTT connect), the publication is performed inside the MQTT_EVENT_CONNECTED handler, so the link is by definition up. For Trigger 3 (ZED UBX), if the link is down at the moment the work entry runs, the publish is dropped silently and the next trigger will repeat the attempt.

JSON build failure

mqtt_api_publish_info() at components/mqtt_manager/mqtt_api.c:300-318 does not check whether mqtt_api_get_info_fn returned NULL. In practice, the function pointer is always assigned to rtkbase_json_get_info (set in app_base_manager.c:1306) and that function always returns a valid cJSON * because its first line is cJSON_CreateObject(). The defensive fallback at mqtt_api.c:310-312 only kicks in if the function pointer itself is NULL (impossible in production):

} else {
    resp_str = strdup("{}");
}

Work-queue overflow

add_work() from components/brlib/longwork.c:31-42 returns ESP_FAIL when the FreeRTOS queue is full. mqtt_api_schedule_initial_info() does not check this return; the publication is silently dropped. This is considered acceptable because the next periodic tick or the next connect/reconnect will retry.

No retries at the publish layer

mqtt_api_publish_info() has no internal retry loop. The only retry mechanism is the periodic trigger (#2) combined with the connect trigger (#1): if a publish fails, the next 6-hour tick (or the next connect) will schedule another one. Triggers #3 and #4 have no natural retry — they depend on the ZED module reporting MON-VER again, or a consumer re-issuing the info/get request.


See Also