How to connect STM32 to EMQX
We take an STM32 Nucleo-H723ZG, add an MQTT client to it, and connect it to an EMQX Cloud broker over TLS with username/password authentication and per-device topic access. No RTOS, no lwIP, no mbedTLS. Mongoose does the TCP/IP, TLS and MQTT parts.
The project we start from is nucleo-h723zg/minimal, a plain Makefile build with CMSIS headers. The steps are the same for the nucleo-h723zg/cubemx project, and for any other board Mongoose supports.
The setup
The board has two cables going to my workstation. An Ethernet cable goes into a USB-Ethernet dongle, and a USB cable goes to the on-board ST-Link, which I use for flashing and for the serial console.
On the workstation I turned on internet sharing from Wi-Fi to that dongle. That makes the workstation run a DHCP server on the dongle interface, so the board gets an IP address when it boots and can reach the internet through my Wi-Fi. A plain cable to your office router works just as well, of course.
Step 1: Build and flash the minimal project
Prepare your build environment, then clone the repo and flash the minimal tutorial:
git clone https://github.com/cesanta/mongoose
cd mongoose/tutorials/stm32/nucleo-h723zg/minimal
make flash
Start your serial monitor before running make flash, so you catch the boot
log. The build fetches the CMSIS headers, compiles the firmware and flashes it
with STM32CubeProgrammer CLI. In the log you should see Mongoose start and get
an IP address over DHCP. Open that address in a browser and you get a greeting
page. The LED blinks too.
main.c is short. It sets up the hardware, creates a Mongoose event manager,
starts an HTTP server, and then runs two things in a loop: the network and the
blinky:
struct mg_mgr mgr;
mg_mgr_init(&mgr);
mg_http_listen(&mgr, "http://0.0.0.0", http_ev_handler, NULL);
for (;;) {
mg_mgr_poll(&mgr, 0);
blink_task();
}
Once Mongoose is in the firmware you get the rest of it for free: OTA, HTTP, MQTT, SMTP, Modbus TCP and so on. Adding any of these is mostly copying code from an existing tutorial. We need MQTT, so that's what we copy.
Step 2: Add the MQTT client
The MQTT client tutorial
keeps all its logic in a single file, mongoose_mqtt.c. Its README says you
need to copy the file and add two calls. So:
- Copy
tutorials/mqtt/mqtt-client/mongoose_mqtt.cinto the project directory - Add it to the
Makefile:
SOURCES = main.c hal.c mongoose_mqtt.c
- In
main.c, callmg_mqtt_init()aftermg_mgr_init(), andmg_mqtt_poll()in the main loop:
struct mg_mgr mgr;
mg_mgr_init(&mgr);
mg_mqtt_init(&mgr);
mg_http_listen(&mgr, "http://0.0.0.0", http_ev_handler, NULL);
for (;;) {
mg_mgr_poll(&mgr, 0);
mg_mqtt_poll(&mgr);
blink_task();
}
Step 3: Check it against HiveMQ
What the client does is simple. It connects to the broker, subscribes to one topic, and echoes every message it gets there back to another topic. Out of the box it points at the public HiveMQ broker:
#define MQTT_SERVER_URL "mqtt://broker.hivemq.com:1883"
#define MQTT_CLIENT_ID "d3"
#define MQTT_USER MQTT_CLIENT_ID
#define MQTT_PASS ""
#define MQTT_PUBLISH_TOPIC "mg/" MQTT_CLIENT_ID "/tx"
#define MQTT_SUBSCRIBE_TOPIC "mg/" MQTT_CLIENT_ID "/rx"
The client ID d3 works as a device ID here, and it's part of both topic
names. Run make flash and watch the log. The board connects to HiveMQ and
subscribes to mg/d3/rx.
Open the HiveMQ WebSocket client,
click Connect and subscribe to mg/# so you see everything. Publish hello
to mg/d3/rx. The board receives it and sends the response to mg/d3/tx.
Good. MQTT works, so from here on anything that breaks is the EMQX config, not the client.
Step 4: Create an EMQX deployment
Go to EMQX Cloud and sign in. I used my GitHub account. You'll see a few deployment types. The default is Dedicated Flex, which runs on AWS, Google Cloud, Azure or Oracle. For a demo the Serverless plan is enough, and it has a free tier. Pick it and click Deploy.
Open the deployment. The overview page has the MQTT connection info, including the broker address. Copy it.
Step 5: Point the firmware at EMQX
In mongoose_mqtt.c, change the server URL and client ID:
#define MQTT_SERVER_URL "mqtts://YOUR_DEPLOYMENT_ADDRESS:8883"
#define MQTT_CLIENT_ID "d1"
Note mqtts:// and port 8883. That's MQTT over TLS, and the scheme is what
tells Mongoose to do the TLS handshake. MQTT_USER is defined as
MQTT_CLIENT_ID, so the username becomes d1 automatically. Leave the topics
as they are, they now turn into mg/d1/rx and mg/d1/tx.
The CA certificate
The client needs the CA certificate to verify the server, and it expects it
as a C string in TLS_CA. EMQX lets you download it as a PEM file from the
deployment page. You can convert that by hand, but there's an easier way: the
Mongoose TLS helper. Enter your deployment address
with port 8883, click "Get CA Certificate", and it fetches the CA from the
server. I compared it with the downloaded file and it was the same
certificate. Tick "Show as C/C++ constant" and it does all the quoting for
you. Paste the result into TLS_CA.
Step 6: Access control in EMQX
EMQX splits access control into two parts: authentication and authorization.
Authentication is the MQTT username and password. That's part of the MQTT
protocol itself, every broker has it. In the EMQX console, add a user with
username d1 and password d1. Then set the password in mongoose_mqtt.c:
#define MQTT_PASS "d1"
Authorization is not part of MQTT. Each cloud vendor does it their own
way, but the idea is always the same: you attach rules to a client ID that
say which topics it may publish and subscribe to. In EMQX, add an
authorization rule for client ID d1, topic mg/d1/#, action publish and
subscribe, permission allow.
Now d1 can only touch topics under mg/d1/. Use one ID per device and every
device gets its own little topic tree.
Step 7: Test it
Run make flash again. The log should show a successful connection to EMQX
and a subscription to mg/d1/rx.
To send something to the board, use the MQTT client built into the EMQX
console. In my deployment it was under Diagnostics. Connect it using the
credentials you just added, subscribe to mg/#, and publish
anything to mg/d1/rx.
The board gets the message and publishes the response, and the console
client shows both: the message we sent and the echo coming back on
mg/d1/tx. Done, the STM32 is talking to EMQX.
What's in the firmware
Notice what's not in this project. No Cube framework, no RTOS, no separate TCP/IP stack like lwIP, no TLS library. It's CMSIS headers, a small HAL for clocks and pins, and Mongoose. The firmware is bare metal, and the same code runs unchanged under an RTOS like FreeRTOS or ThreadX.
On the EMQX side there's a lot more you can do with the data once it arrives. For example, you can create a time series table and add a rule that stores everything the device publishes into it, then read that data back through EMQX APIs, including plain HTTP. That's a topic for another article.
Links:
- MQTT client tutorial
- STM32 tutorials
- Mongoose TLS helper
- How to connect STM32 to Azure IoT Hub, the same board and client talking to Azure with mutual TLS
- MQTT on a microcontroller, for background on MQTT itself