Skip to content

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/tcp of the official Ai-Thinker SDK (Ai-Thinker-Open/Ai-Thinker-WB2, version release_bl_iot_sdk_1.6.40); the code can be found directly in the local SDK.

🎯Page GoalConnect to a Broker and publish/subscribe to topics via the axk_mqtt component, mastering MQTT client initialization, event callbacks and the send/receive flow.
🧰Prerequisites① Ai-WB2 development board (Type-C data cable) ② A 2.4GHz router (with public internet access) ③ A computer (with an MQTT debugging tool, e.g. MQTTX) ④ Environment set up per [SDK Installation](../../sdk/sdk_intro) and completed [Connect Wi-Fi](./wifi_connect).
🔗RelatedEncrypted MQTT: [MQTTS](./mqtts); HTTP reporting: [HTTP POST](./http_post).

Enter the Example Project

Open the terminal and enter the official mqtt/tcp example project directory:

cd ~/Ai-Thinker-WB2/applications/protocols/mqtt/tcp

Note: cd is the “change directory” command, entering the official example project; all subsequent make commands must run in this directory.

Modify the Router Parameters and Broker Address

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.io is 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 is mqtt://host:port (default port 1883).

Write the Code

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 the Project

Build in the project directory:

make -j8

Note: make is the “build” command, turning code into firmware (the program flashed into the board) the board can run; -j8 builds with 8 parallel cores, faster.

On success a firmware build_out/tcp.bin is generated.

Flash the Firmware

Keep the board connected via USB, confirm the serial device, and flash:

make flash p=/dev/ttyUSB0 b=921600

Note: make flash is the “flash” command, writing the compiled firmware into the board. After p= comes the serial device (often /dev/ttyUSB0 on Linux, COM3-like on Windows — use your computer’s actual one), b=921600 is 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.

Run and Verify

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_t structure 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 by axk_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 handle
  • topic: topic string
  • data: message data pointer
  • len: message length
  • qos: QoS level (0/1/2)
  • retain: whether to retain the message (1 retain / 0 not)

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 handle
  • topic: topic string
  • qos: 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 handle
  • topic: 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 the MQTT_EVENT_* macros)
  • topic / topic_len: topic and its length
  • data / data_len: message data and its length
  • msg_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_id with these macros to tell the event type

Return: none (macro definitions)

📌 Event callback fields: on MQTT_EVENT_DATA, use event->topic (length topic_len) and event->data (length data_len); match subscribe/publish acks against the call return values via event->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
c
#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.

Released under the MIT License. Build Time 2026-09-11 14:52:23