Overview
MQTT (Message Queuing Telemetry Transport) is the most mainstream publish/subscribe lightweight messaging protocol in IoT: devices act as clients connecting to a Broker (the message relay), publishing (publish) and subscribing (subscribe) to messages by topic — the two sides never need to know each other exists, and one-to-many, many-to-one communication comes for free. Compared with HTTP, MQTT messages have small overhead, support QoS levels and offline messages, and suit weak-network, low-power devices. This tutorial demonstrates: connecting to a public Broker, publishing/subscribing to /topic/qos0 and /topic/qos1, and printing the received data.
In plain words: MQTT is like following a WeChat subscription account — a device "follows" (subscribes to) a topic; when the account posts an article (publishes a message), every follower receives it, and the author doesn't need to know who's reading. The "platform" in between is the Broker (message relay), and the topic is the "channel name". After the board connects to the Broker, both publishing and subscribing happen through topics.
This tutorial is written based on the official example
applications/protocols/mqtt/tcpof the official Ai-Thinker SDK (Ai-Thinker-Open/Ai-Thinker-WB2, versionrelease_bl_iot_sdk_1.6.40); the code can be found directly in the local SDK.
Open the terminal and enter the official mqtt/tcp example project directory:
cd ~/Ai-Thinker-WB2/applications/protocols/mqtt/tcp
Note:
cdis the “change directory” command, entering the official example project; all subsequentmakecommands must run in this directory.
Open tcp/main.c and change the SSID/password at the top (same as Connect Wi-Fi).
Open tcp/demo.c and change the Broker address:
axk_mqtt_client_config_t mqtt_cfg = {
.uri = "mqtt://mqtt.eclipseprojects.io",
.event_handle = event_cb,
};
⚠️ The official default
mqtt.eclipseprojects.iois a public test server that may have stopped service. Use a working public Broker instead (e.g.mqtt://broker.emqx.io,mqtt://test.mosquitto.org) or a self-hosted Broker; the URI format ismqtt://host:port(default port 1883).
Open tcp/demo.c. The full code for this step has been moved to the end of this page:
📜 Full Code — in the “Full Code” section below, collapsed by default — click to expand, identical to the official example (
applications/protocols/mqtt/tcp/tcp/demo.c).
Code highlights:
| Code | Purpose |
|---|---|
axk_mqtt_client_config_t { .uri, .event_handle } |
Fill in the Broker address and callback; get it wrong and no server can be reached |
axk_mqtt_client_init(&mqtt_cfg) |
Creates the client from the config; if it fails nothing else can be done |
axk_mqtt_client_start(client) |
Starts the client and asynchronously connects to the Broker; connection happens automatically |
MQTT_EVENT_CONNECTED |
The “connected” notification — publishing/subscribing must wait until it arrives |
axk_mqtt_client_publish(client, topic, data, len, qos, retain) |
Sends a message to a topic; every device subscribed to that topic receives it |
axk_mqtt_client_subscribe(client, topic, qos) |
Subscribes to a topic; without subscribing you won’t receive its messages |
axk_mqtt_client_unsubscribe(client, topic) |
Unsubscribes; you no longer receive messages on that topic |
MQTT_EVENT_DATA |
The “message received” notification — use event->topic/data to get the content |
MQTT_EVENT_ERROR |
The “error” notification — the error code tells whether it’s a network or TLS problem |
💡 MQTT key concepts: topics are level-separated with
/(e.g./topic/qos0) and support wildcards+(single level),#(multi level); QoS 0 = at most once (no ack), 1 = at least once (may duplicate), 2 = exactly once (most overhead); with retain=1 the Broker keeps the last message for future subscribers. Official example flow: after connecting, publish qos1 → subscribe two topics → unsubscribe qos1 → after the subscribe ack, publish qos0.
Build in the project directory:
make -j8
Note:
makeis the “build” command, turning code into firmware (the program flashed into the board) the board can run;-j8builds with 8 parallel cores, faster.
On success a firmware build_out/tcp.bin is generated.
Keep the board connected via USB, confirm the serial device, and flash:
make flash p=/dev/ttyUSB0 b=921600
Note:
make flashis the “flash” command, writing the compiled firmware into the board. Afterp=comes the serial device (often/dev/ttyUSB0on Linux,COM3-like on Windows — use your computer’s actual one),b=921600is the flash baud rate (serial transfer speed), keep the default.
⏳ During flashing, press and hold the EN button on the board when prompted to enter download mode; wait for the progress bar to complete — that means the flash succeeded.
First open an MQTT debugging tool on the computer (e.g. MQTTX), connect to the same Broker (e.g. broker.emqx.io:1883), and subscribe to /topic/qos0 and /topic/qos1.
After flashing, the board automatically restarts and runs. The serial (baud rate 921600, the “speech rate” of serial transfer — both sides must agree) prints:
[APP] [EVT] GOT IP 5594
MQTT_EVENT_CONNECTED
sent publish successful, msg_id=1
sent subscribe successful, msg_id=2
sent subscribe successful, msg_id=3
sent unsubscribe successful, msg_id=4
MQTT_EVENT_SUBSCRIBED, msg_id=3
sent publish successful, msg_id=5
The computer MQTT tool will receive two messages: data_3 on /topic/qos1, data on /topic/qos0 — the whole publish/subscribe chain is open.
Reverse verification: publish any message (e.g. hello wb2) to /topic/qos0 in the computer tool, and the board serial prints:
MQTT_EVENT_DATA
TOPIC=/topic/qos0
DATA=hello wb2
Two-way communication verified. Seeing MQTT_EVENT_CONNECTED and the computer tool receiving data_3 and data means success; if MQTT_EVENT_CONNECTED never appears, first confirm the board got GOT IP (online) and the computer MQTT tool can connect to the same Broker (target server reachable), otherwise see the FAQ at the end.
💡 The same topic can be subscribed by multiple devices at once — publish a message on another device/another tool session, and all subscribers (including the board) receive it: that’s MQTT one-to-many communication.
API Summary for This Tutorial
axk_mqtt_client_init(&config)
Creates an MQTT client (configuring the uri/event callback), returns a handle.
Parameters:
config:axk_mqtt_client_config_tstructure pointer;.uri(mqtt://host:port) and.event_handle(event callback) are required
Return: client handle (axk_mqtt_client_handle_t) on success; NULL on failure
axk_mqtt_client_start(client)
Starts the client and asynchronously connects to the Broker.
Parameters:
client: the client handle returned byaxk_mqtt_client_init
Return: 0 on success; negative error code on failure
axk_mqtt_client_publish(client, topic, data, len, qos, retain)
Publishes a message to a topic.
Parameters:
client: the client handletopic: topic stringdata: message data pointerlen: message lengthqos: QoS level (0/1/2)retain: whether to retain the message (1retain /0not)
Return: message ID (msg_id) on success; negative on failure
axk_mqtt_client_subscribe(client, topic, qos)
Subscribes to a topic.
Parameters:
client: the client handletopic: topic stringqos: QoS level (0/1/2)
Return: message ID (msg_id) on success; negative on failure
axk_mqtt_client_unsubscribe(client, topic)
Unsubscribes from a topic.
Parameters:
client: the client handletopic: topic string
Return: 0 on success; negative error code on failure
axk_mqtt_client_config_t
The client configuration structure.
Parameters:
.uri: Broker address, e.g."mqtt://host:1883".event_handle: the event callback function
Return: none (a structure, not a function)
axk_mqtt_event_handle_t
The event handle structure (callback argument).
Parameters:
event_id: event type (compared against theMQTT_EVENT_*macros)topic/topic_len: topic and its lengthdata/data_len: message data and its lengthmsg_id: message ID (corresponds to the subscribe/publish return value)error_handle: error info
Return: none (a structure, not a function)
MQTT_EVENT_*
Event macros: MQTT_EVENT_CONNECTED / DISCONNECTED / SUBSCRIBED / UNSUBSCRIBED / PUBLISHED / DATA / ERROR.
Parameters:
- in the callback, compare
event->event_idwith these macros to tell the event type
Return: none (macro definitions)
📌 Event callback fields: on
MQTT_EVENT_DATA, useevent->topic(lengthtopic_len) andevent->data(lengthdata_len); match subscribe/publish acks against the call return values viaevent->msg_id. Don't do time-consuming work in the callback (printing, processing, etc. should be handed to a task queue).
Full Code
Below is the complete tcp/demo.c source, identical to the official example (applications/protocols/mqtt/tcp/tcp/demo.c):
📜 Click to expand the full tcp/demo.c code
#include <stdio.h>
#include <FreeRTOS.h>
#include <task.h>
#include <mqtt_client.h>
#include "blog.h"
static void log_error_if_nonzero(const char *message, int error_code)
{
if (error_code != 0) {
blog_error("Last error %s: 0x%x", message, error_code);
}
}
static axk_err_t event_cb(axk_mqtt_event_handle_t event)
{
int32_t event_id;
axk_mqtt_client_handle_t client = event->client;
event_id = event->event_id;
blog_debug("Event dispatched, event_id=%d", event_id);
int msg_id;
switch ((axk_mqtt_event_id_t)event_id) {
case MQTT_EVENT_CONNECTED:
blog_info("MQTT_EVENT_CONNECTED");
msg_id = axk_mqtt_client_publish(client, "/topic/qos1", "data_3", 0, 1, 0);
blog_info("sent publish successful, msg_id=%d", msg_id);
msg_id = axk_mqtt_client_subscribe(client, "/topic/qos0", 0);
blog_info("sent subscribe successful, msg_id=%d", msg_id);
msg_id = axk_mqtt_client_subscribe(client, "/topic/qos1", 1);
blog_info("sent subscribe successful, msg_id=%d", msg_id);
msg_id = axk_mqtt_client_unsubscribe(client, "/topic/qos1");
blog_info("sent unsubscribe successful, msg_id=%d", msg_id);
break;
case MQTT_EVENT_DISCONNECTED:
blog_info("MQTT_EVENT_DISCONNECTED");
break;
case MQTT_EVENT_SUBSCRIBED:
blog_info("MQTT_EVENT_SUBSCRIBED, msg_id=%d", event->msg_id);
msg_id = axk_mqtt_client_publish(client, "/topic/qos0", "data", 0, 0, 0);
blog_info("sent publish successful, msg_id=%d", msg_id);
break;
case MQTT_EVENT_UNSUBSCRIBED:
blog_info("MQTT_EVENT_UNSUBSCRIBED, msg_id=%d", event->msg_id);
break;
case MQTT_EVENT_PUBLISHED:
blog_info("MQTT_EVENT_PUBLISHED, msg_id=%d", event->msg_id);
break;
case MQTT_EVENT_DATA:
blog_info("MQTT_EVENT_DATA");
printf("TOPIC=%.*s\r\n", event->topic_len, event->topic);
printf("DATA=%.*s\r\n", event->data_len, event->data);
break;
case MQTT_EVENT_ERROR:
blog_info("MQTT_EVENT_ERROR");
if (event->error_handle->error_type == MQTT_ERROR_TYPE_TCP_TRANSPORT) {
log_error_if_nonzero("reported from axk-tls", event->error_handle->axk_tls_last_axk_err);
log_error_if_nonzero("reported from tls stack", event->error_handle->axk_tls_stack_err);
log_error_if_nonzero("captured as transport's socket errno", event->error_handle->axk_transport_sock_errno);
blog_info("Last errno string (%s)", strerror(event->error_handle->axk_transport_sock_errno));
}
break;
default:
blog_info("Other event id:%d", event->event_id);
break;
}
return AXK_OK;
}
void mqtt_start(void)
{
axk_mqtt_client_config_t mqtt_cfg = {
.uri = "mqtt://mqtt.eclipseprojects.io",
.event_handle = event_cb,
};
axk_mqtt_client_handle_t client = axk_mqtt_client_init(&mqtt_cfg);
axk_mqtt_client_start(client);
}FAQ & Troubleshooting
⚠️ Never receive MQTT_EVENT_CONNECTED
Cause: Broker unreachable, DNS resolution failed, or the public Broker stopped service (the official default mqtt.eclipseprojects.io may be down)
Fix: switch to mqtt://broker.emqx.io or mqtt://test.mosquitto.org; first test with the computer MQTT tool whether the same Broker is reachable
⚠️ MQTT_EVENT_ERROR keeps appearing
Cause: TCP connection refused/timed out, the Broker requires authentication, or the network blocks port 1883
Fix: look at axk_transport_sock_errno and the errno string in the error callback to locate it; confirm the router can reach the public port 1883; for Brokers needing account/password use the .username/.password config
⚠️ The computer tool connects to the Broker but the board doesn't
Cause: DNS cache/network not ready on the board side, or wrong start timing
Fix: confirm mqtt_start() is only called after GOT_IP (the official example calls it in the GOT_IP callback); wait a few seconds and retry
⚠️ Publish succeeds but the computer doesn't receive it
Cause: topic mismatch (including case), a QoS setting issue, or the computer subscribed later than a retain=0 publish
Fix: verify the topic string is exactly identical (/topic/qos0 starts with /); subscribe on the computer first, then flash the board; use retain=1 during debugging for easier review
⚠️ Heavy processing in the event callback causes message loss
Cause: a blocked callback affects packet reception
Fix: only copy data in the callback; hand it to the business thread via a queue/task (the official callback only prints)
⚠️ Keeps printing Connecting, never GOT IP (can't connect to the router)
Cause: wrong SSID/password, the router is on 5GHz, or the signal is too weak
Fix: verify the SSID/password in tcp/main.c; make sure the router broadcasts 2.4GHz; move the board closer to the router; you can run Connect Wi-Fi alone to verify networking first
⚠️ Serial device not found / can't open
Cause: USB-to-serial driver not installed, insufficient permission, or the cable only charges and can't transfer data
Fix: on Linux check the device with lsusb/dmesg; if permission denied run sudo chmod 666 /dev/ttyUSB0; on Windows install the driver and check the COM port in Device Manager; try a data-capable cable
⚠️ Flashing keeps waiting / fails
Cause: download mode wasn't entered, wrong baud rate, or a wrong serial number
Fix: press and hold EN during flashing to enter download mode as prompted; change p=/dev/ttyUSB0 to your actual serial port; try another USB port or cable
Self-Check
The serial prints MQTT_EVENT_CONNECTED and the computer tool receives both data_3 and data; the board receives the tool's topic messages — MQTT is verified.

