How to Use MQTT with Zig on ESP32: A Complete Guide to ESP-IDF Integration
Use the idf.mqtt module from the kassane/zig-esp-idf-sample repository to wrap the ESP-IDF C API, configure a ClientConfig, initialize with mqtt.init(), and manage connections via mqtt.start() and mqtt.stop().
The kassane/zig-esp-idf-sample project provides a lightweight Zig wrapper around the ESP-IDF MQTT client, allowing you to use MQTT with Zig on ESP32 devices without writing C bindings manually. This wrapper translates C error codes into Zig errors and exposes a type-safe API for configuring brokers, publishing messages, and handling events. All MQTT functionality resides in imports/mqtt.zig and is re-exported through imports/idf.zig as idf.mqtt.
Setting Up MQTT in Your Zig ESP32 Project
Importing the MQTT Module
To begin using MQTT with Zig on ESP32, import the module from the ESP-IDF namespace. The idf.zig file re-exports the MQTT wrapper, making it accessible as idf.mqtt:
const idf = @import("esp_idf");
const mqtt = idf.mqtt;
const log = @import("log");
This import gives you access to all core types including mqtt.Handle, mqtt.ClientConfig, and error sets defined in imports/mqtt.zig.
Configuring the Client
Before connecting, populate a mqtt.ClientConfig struct. This struct mirrors the ESP-IDF esp_mqtt_client_config_t and is defined at line 12 of imports/mqtt.zig. At minimum, you must specify the broker URI:
var config = mqtt.ClientConfig{
.uri = "mqtt://test.mosquitto.org",
.client_id = "zig_esp32_client",
.keepalive = 60,
.disable_auto_reconnect = false,
.disable_clean_session = false,
.username = null,
.password = null,
.event_handle = null,
};
Key configuration fields include:
.uri: The broker address (e.g.,mqtt://broker.hivemq.comormqtts://secure.broker:8883)..keepalive: Keep-alive interval in seconds..disable_auto_reconnect: Set totrueto disable automatic reconnection logic.
Initializing and Starting the MQTT Client
Once configured, initialize the client using mqtt.init() defined at lines 58-60 in imports/mqtt.zig. This function returns an optional Handle (opaque pointer to the underlying C client) or null on allocation failure:
const client = mqtt.init(&config) orelse {
log.err("Failed to allocate MQTT client", .{});
return;
};
After initialization, start the connection process with mqtt.start() (lines 77-79 in imports/mqtt.zig). This function initiates the underlying network connection and MQTT handshake, returning a Zig error if the ESP-IDF call fails:
mqtt.start(client) catch |e| {
log.err("MQTT start failed: {}", .{e});
return;
};
To gracefully disconnect, call mqtt.stop(client) followed by mqtt.destroy(client) to free resources.
Publishing and Subscribing to Topics
Publishing Messages
Use mqtt.publish() (lines 97-112 in imports/mqtt.zig) to send messages immediately. The function signature accepts the client handle, topic string, payload bytes, and a configuration struct for QoS and retain flags:
const topic = "zig/esp32/sensor";
const payload = "temperature=23.5";
const msg_id = mqtt.publish(
client,
topic,
payload,
.{
.qos = 1, // At least once delivery
.retain = false,
}
) catch |e| {
log.err("Publish failed: {}", .{e});
return 0;
};
log.info("Published message with ID: {}", .{msg_id});
The function returns a u32 message ID on success, which corresponds to the packet identifier assigned by the ESP-IDF client.
Subscribing to Topics
Subscribe using mqtt.subscribe() (lines 38-45 in imports/mqtt.zig). This function takes the client handle, topic filter, and QoS level:
mqtt.subscribe(client, "zig/esp32/commands", 1) catch |e| {
log.err("Subscribe failed: {}", .{e});
};
For multiple subscriptions in a single call, use mqtt.subscribeMultiple() (lines 46-50 in imports/mqtt.zig), which accepts a slice of topic/QoS pairs.
Handling Offline Messages with Enqueue
When the client is disconnected, mqtt.enqueue() (lines 14-31 in imports/mqtt.zig) stores messages in the ESP-IDF outbox for later transmission. This is useful for maintaining telemetry during network interruptions:
mqtt.enqueue(
client,
"zig/esp32/offline",
"cached_data",
.{
.qos = 1,
.retain = false,
.store = true, // Store in outbox when offline
}
) catch |e| {
log.err("Enqueue failed: {}", .{e});
};
Handling MQTT Events in Zig
The ESP-IDF MQTT client operates asynchronously and communicates via an event loop. Register callbacks using mqtt.registerEvent() (lines 60-66 in imports/mqtt.zig) to handle connection status, incoming messages, and errors:
fn mqttEventHandler(event: *mqtt.Event, arg: ?*anyopaque) callconv(.C) void {
_ = arg; // Unused user argument
switch (event.event_id) {
.MQTT_EVENT_CONNECTED => {
log.info("Connected to broker", .{});
},
.MQTT_EVENT_DISCONNECTED => {
log.warn("Disconnected from broker", .{});
},
.MQTT_EVENT_DATA => {
const topic = event.topic[0..event.topic_len];
const data = event.data[0..event.data_len];
log.info("Received on {s}: {s}", .{ topic, data });
},
.MQTT_EVENT_ERROR => {
log.err("MQTT error occurred", .{});
},
else => {},
}
}
// In app_main:
mqtt.registerEvent(client, .MQTT_EVENT_ANY, mqttEventHandler, null) catch |e| {
log.err("Failed to register event handler: {}", .{e});
};
The event handler uses C calling convention (callconv(.C)) because it interfaces directly with the ESP-IDF C layer. The mqtt.Event struct provides fields for topic, data, message ID, and error codes.
Advanced MQTT Features
MQTT 5 Support
The wrapper includes experimental support for MQTT protocol version 5. Access these features through the mqtt.v5 submodule, which provides functions like setConnectProperty() and setUserProperty() (lines 186-236 in imports/mqtt.zig). These allow setting properties such as session expiry intervals and user-defined key-value pairs in CONNECT packets.
Custom Events and Configuration Options
For complex applications, use mqtt.dispatchCustomEvent() to inject user-defined events into the MQTT event loop, enabling coordination between your application logic and the MQTT client thread. Additionally, the ClientConfig struct supports TLS configuration via the .cert_pem, .client_cert_pem, and .client_key_pem fields for secure broker connections.
Summary
- Import path: Access MQTT functionality via
const mqtt = @import("esp_idf").mqtt;fromimports/idf.zig. - Configuration: Define broker settings using
mqtt.ClientConfiginimports/mqtt.zig(line 12), ensuring you set at least the.urifield. - Lifecycle: Initialize with
mqtt.init()(line 58), connect withmqtt.start()(line 77), and clean up withmqtt.stop()andmqtt.destroy(). - Messaging: Publish immediately with
mqtt.publish()(line 97), subscribe withmqtt.subscribe()(line 38), and queue offline messages viamqtt.enqueue()(line 14). - Events: Register asynchronous handlers using
mqtt.registerEvent()(line 60) to process incoming data and connection state changes.
Frequently Asked Questions
How do I configure the MQTT broker URI in Zig?
Set the .uri field of mqtt.ClientConfig to a valid MQTT broker address before calling mqtt.init(). For example, use mqtt://broker.hivemq.com for unencrypted connections or mqtts://secure.broker:8883 for TLS-encrypted brokers. This configuration is defined at line 12 of imports/mqtt.zig.
What is the difference between mqtt.publish and mqtt.enqueue?
mqtt.publish() (lines 97-112 in imports/mqtt.zig) sends messages immediately and requires an active connection, returning a message ID or an error if the client is disconnected. In contrast, mqtt.enqueue() (lines 14-31) stores messages in the ESP-IDF outbox when the client is offline, transmitting them automatically once connectivity is restored.
How do I handle incoming MQTT messages in Zig?
Register an event handler using mqtt.registerEvent() (line 60 in imports/mqtt.zig), specifying .MQTT_EVENT_DATA or .MQTT_EVENT_ANY as the event type. Your handler function must use callconv(.C) and accept *mqtt.Event and ?*anyopaque parameters. Inside the handler, check event.event_id for .MQTT_EVENT_DATA and access event.topic and event.data to process incoming payloads.
Does this Zig wrapper support MQTT 5?
Yes, the wrapper includes experimental MQTT 5 support accessible through the mqtt.v5 submodule defined in imports/mqtt.zig (lines 186-236). This submodule provides functions like setConnectProperty() and setUserProperty() to configure MQTT 5 specific features such as session expiry intervals and user-defined properties in CONNECT packets.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →