fwt_software/docs/mqtt-api.md

5.3 KiB
Raw Blame History

MQTT API

The program is both an MQTT subscriber (remote control) and publisher (status + capture events). The MQTT channel is the MqttControlChannel implementation of IControlChannel (src/mqtt/MqttControlChannel.cpp, Eclipse Paho C++). It is used only when MQTT is enabled; otherwise a NullControlChannel runs (publishes dropped, auto-sweep mode). All topics are namespaced by tower name:

GGS/FWT/<tower_name>/...

<tower_name> comes from config.ini (General.tower_name). All messages use QoS 1. Published messages are retained.

Connection

  • Broker URI = Network.zkms_server_ip from config.ini; client ID = the tower name.
  • Auth: mqtt_user / mqtt_pw (preferring $FGC_MQTT_USER/$FGC_MQTT_PW); clean_session = true, keep-alive 20 s, set_automatic_reconnect(true).
  • Connect timeout 5 s. On connection loss Paho auto-reconnects; the channel re-subscribes on reconnect.
  • The program no longer exits if MQTT is unavailable — it logs a warning and continues in degraded mode. Use --no-mqtt to disable MQTT entirely (null channel).

Subscribed topics (inbound — remote control)

Subscribed on (re)connect; handled in MqttControlChannel::message_arrived() (src/mqtt/MqttControlChannel.cpp).

Topic Payload Parsed as Effect
GGS/FWT/<tower>/target_HDG integer heading as string, e.g. "137" validated with stoi, stored as string Sets the yaw target used in ControlCode 1 (converted to encoder counts via [Motor])
GGS/FWT/<tower>/ControlCode integer as string, "0" or "1" stoi → int Selects the capture mode (see below)

Invalid (non-integer) payloads are caught and logged; the previous value is kept.

The main loop consumes these via get_sub_data(), which returns a snapshot and clears the "available" flags so each update is acted on once (MqttControlChannel::poll()).

ControlCode semantics

ControlCode Behaviour (CaptureScheduler)
0 Automatic sweep. Walk the scan grid (ping-pong): MOVE to the next (yaw,pitch) waypoint, wait for standstill, trigger cameras.
1 Directed. MOVE yaw to target_HDG (pitch held), wait for standstill, trigger.

When a ControlCode message arrives, the program echoes the current code back on the StatusCode topic.

Published topics (outbound)

Topic When Payload QoS / Retain
GGS/FWT/<tower>/StatusCode At startup ("0"); whenever a ControlCode message is received (echoes the code) integer as string 1 / retained
GGS/FWT/<tower>/CamEvent After each image is saved JSON object (below) 1 / retained
GGS/FWT/<tower>/Env Once per fresh ambient sensor sample (see [Env] period_ms) JSON object (below) 1 / retained

CamEvent payload

Built by MqttControlChannel::publishCamEvent from a CamEvent:

{ "fwt":"ExampleTower", "cam":"RGB", "hdg":1373, "pit":300, "time":1719312345678, "degraded":false }
Field Type Meaning
fwt string Tower name (config.ini tower_name)
cam string Camera label: RGB, ACR, or NIR (by camera index)
hdg int Gimbal yaw heading × 10 (one decimal place encoded as integer) at capture time
pit int Gimbal pitch elevation × 10 at capture time (derived from the pitch encoder)
time int Capture timestamp, Unix epoch milliseconds (matches the .jxl filename)
degraded bool true when the quality gate ran out of attempts and this is its best effort rather than a frame that passed — see [Capture] in configuration.md. Consumers should weight or skip these rather than treating them as good

The time value is the same Unix-ms timestamp used as the image filename, so a consumer can locate the file for a given event: <RGB|ACR|NIR>/<time>.jxl.

Env payload

Built by MqttControlChannel::publishEnv from an EnvEvent, sourced from whichever IEnvSensor backend is active (SHT41 on the LattePanda's own I2C bus by default; see configuration.md):

{ "fwt":"ExampleTower", "temp_c":22.4, "humid_pct":46.1, "time":1719312345678 }
Field Type Meaning
fwt string Tower name (config.ini tower_name)
temp_c float Ambient temperature, °C
humid_pct float Ambient relative humidity, %
time int Sample timestamp, Unix epoch milliseconds

Topic summary

subscribe:  GGS/FWT/<tower>/target_HDG      (int heading)
            GGS/FWT/<tower>/ControlCode     (0 = auto sweep, 1 = directed)

publish:    GGS/FWT/<tower>/StatusCode      (echoed control code)
            GGS/FWT/<tower>/CamEvent        (JSON: fwt, cam, hdg×10, pit×10, time-ms)
            GGS/FWT/<tower>/Env             (JSON: fwt, temp_c, humid_pct, time-ms)

Local testing

Point zkms_server_ip at a local broker and exercise the topics with Mosquitto:

mosquitto_sub -t 'GGS/FWT/ExampleTower/#' -v          # watch everything for the tower
mosquitto_pub -t 'GGS/FWT/ExampleTower/ControlCode' -m '0'
mosquitto_pub -t 'GGS/FWT/ExampleTower/target_HDG'  -m '180'