Add support for MS Azure MQTT

This commit is contained in:
cpq
2026-10-05 12:41:56 +01:00
parent da82df2041
commit cf9d56be79
10 changed files with 381 additions and 145 deletions
+21 -16
View File
@@ -1,27 +1,32 @@
PROG ?= example # Program we are building
DELETE = rm -rf # Command to remove files
OUT ?= -o $(PROG) # Compiler argument for output file
SOURCES = main.c mongoose.c mongoose_mqtt.c packed_fs.c # Source code files, packed_fs.c contains ca.pem, which contains CA certs for TLS
CFLAGS = -W -Wall -Wextra -g -I. # Build options
REPO_ROOT ?= ../../..
PROG ?= example
DELETE = rm -rf
OUT ?= -o $(PROG)
CFLAGS = -W -Wall -Wextra -g -I. -I$(REPO_ROOT)
SOURCES = main.c mongoose_mqtt.c $(REPO_ROOT)/mongoose.c
# Mongoose build options. See https://mongoose.ws/docs/getting-started/build-options/
CFLAGS_EXTRA ?= -DMG_TLS=MG_TLS_BUILTIN
CFLAGS_MONGOOSE += -DMG_TLS=MG_TLS_BUILTIN
CFLAGS_MONGOOSE += -DMG_ENABLE_LINES=1
ifeq ($(OS),Windows_NT) # Windows settings. Assume MinGW compiler. To use VC: make CC=cl CFLAGS=/MD OUT=/Feprog.exe
PROG ?= example.exe # Use .exe suffix for the binary
CC = gcc # Use MinGW gcc compiler
CFLAGS += -lws2_32 # Link against Winsock library
DELETE = cmd /C del /Q /F /S # Command prompt command to delete files
OUT ?= -o $(PROG) # Build output
MAKE += WINDOWS=1 CC=$(CC)
# This is for Azure - it just cannot Chacha20
CFLAGS_MONGOOSE += -DMG_ENABLE_CHACHA20=0
# Windows settings. Assume MinGW compiler.
# To use VS compiler, run: make CC=cl CFLAGS=/MD OUT=/Feprog.exe
ifeq ($(OS),Windows_NT)
PROG ?= example.exe
CC = gcc
CFLAGS += -lws2_32
DELETE = cmd /C del /Q /F /S
OUT ?= -o $(PROG)
endif
all: $(PROG) # Default target. Build and run program
all: $(PROG)
$(RUN) ./$(PROG) $(ARGS)
$(PROG): $(SOURCES) # Build program from sources
$(PROG): $(SOURCES)
$(CC) $(SOURCES) $(CFLAGS) $(CFLAGS_MONGOOSE) $(CFLAGS_EXTRA) $(OUT)
clean: # Cleanup. Delete built program and all build artifacts
clean:
$(DELETE) $(PROG) *.o *.obj *.exe *.dSYM mbedtls
+95 -1
View File
@@ -1 +1,95 @@
See detailed tutorial at https://mongoose.ws/tutorials/mqtt/mqtt-client/
# Mongoose MQTT client
This MQTT client implementation implements the following:
- Connects to the MQTT server specified by MQTT_SERVER_URL
- When connected, subscribes to the topic MQTT_SUBSCRIBE_TOPIC
- When it receives a message, echoes it back to MQTT_PUBLISH_TOPIC
- Timer-based reconnection logic revives the connection when it is down
- Ping server periodically. When disconnected, a last will is published
- Implements "ota.update" for OTA updates, see https://mongoose.ws/mqtt/
By default, it uses HiveMQ public broker, and can be tested with
the [HiveMQ Websocket Client](https://www.hivemq.com/demos/websocket-client/):
- Subscribe to `mg/123/#`
- Send a message to `mg/123/rx`
## Integrating into an embedded project
1. Copy `mongoose_mqtt.c` to your embedded project and add it to the build
2. Add `mg_mqtt_init(&mgr)` after `mg_mgr_init()`
3. Add `mg_mqtt_poll(&mgr)` after `mg_mgr_poll()`
## Microsoft Azure IoT Hub
1. Create IoT Hub
2. Generate device self-signed certificates:
```sh
openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:prime256v1 -pkeyopt ec_param_enc:named_curve -nodes -keyout device.key -out device.crt -days 3650 -subj "/CN=MYDEVICE"
```
3. Show certificate thumbprint:
```sh
openssl x509 -in device.crt -noout -fingerprint -sha256 | tr -d ':' | cut -d= -f2
```
4. Register a device with the thumbprint from above, and set the following:
```c
#define AZURE_HUB_NAME "HUB_NAME" // Change this
#define AZURE_DEVICE_ID "DEVICE_ID" // Change this
// Do not change this
#define MQTT_SERVER_URL "mqtts://" AZURE_HUB_NAME ".device.azure-devices.net"
#define MQTT_CLIENT_ID AZURE_DEVICE_ID
#define MQTT_USER AZURE_HUB_NAME ".azure-devices.net/" AZURE_DEVICE_ID "/?api-version=2021-04-12"
#define MQTT_PUBLISH_TOPIC "devices/" AZURE_DEVICE_ID "/messages/events/"
#define MQTT_SUBSCRIBE_TOPIC "devices/" AZURE_DEVICE_ID "/messages/devicebound/#"
```
5. Set `TLS_CA`. Visit https://mongoose.ws/tls/, enter "HUB_NAME.device.azure-devices.nett:8883" into the CA field and click on the "Get CA Certificate" button. Enable the "Show as C/C++ constant", copy-paste to your code.
6. Set `TLS_KEY` and `TLS_CRT` with the output of this commands:
```sh
sed 's/\r$//; s/._/ "&\\n"/; $!s/$/ \\/' device.key
sed 's/\r$//; s/._/ "&\\n"/; $!s/$/ \\/' device.crt
```
## Microsoft Azure Event Grid
1. Create Azure Event Grid (EG) instance
2. Generate device self-signed certificates:
```sh
openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:prime256v1 -pkeyopt ec_param_enc:named_curve -nodes -keyout device.key -out device.crt -days 3650 -subj "/CN=MYDEVICE"
```
3. Show certificate thumbprint:
```sh
openssl x509 -in device.crt -noout -fingerprint -sha256 | tr -d ':' | cut -d= -f2
```
4. Register a client on EG: choose a name, and use the thumbprint from above
5. Set URL, username and client ID:
```c
#define MQTT_SERVER_URL "mqtts://INSTANCE.REGION.ts.eventgrid.azure.net:8883"
#define MQTT_CLIENT_ID "CLIENT_NAME"
#define MQTT_USER "CLIENT_NAME"
```
6. Set `TLS_CA`. Visit https://mongoose.ws/tls/, enter "INSTANCE.REGION.ts.eventgrid.azure.net:8883" into the CA field and click on the "Get CA Certificate" button. Enable the "Show as C/C++ constant", copy-paste to your code.
7. Set `TLS_KEY` and `TLS_CRT` with the output of this commands:
```sh
sed 's/\r$//; s/._/ "&\\n"/; $!s/$/ \\/' device.key
sed 's/\r$//; s/._/ "&\\n"/; $!s/$/ \\/' device.crt
```
8. In Azure EG, go to Instance / MQTT Broker / Topic spaces, add "space1" with `mg/#` pattern
9. In Azure EG, go to Instance / MQTT Broker / Permissions bindings, add binding1 and binding2:
- $all , space1 , Publisher
- $all , space1 , Subscriber
-1
View File
@@ -1 +0,0 @@
../../../mongoose.c
-1
View File
@@ -1 +0,0 @@
../../../mongoose.h
+49 -30
View File
@@ -1,24 +1,22 @@
// Copyright (c) 2026 Cesanta Software Limited
// All rights reserved
//
// Example MQTT client. It performs the following steps:
// 1. Connects to the MQTT server specified by MQTT_SERVER_URL
// 2. When connected, subscribes to the topic MQTT_SUBSCRIBE_TOPIC
// 3. When it receives a message, echoes it back to MQTT_PUBLISH_TOPIC
// 4. Timer-based reconnection logic revives the connection when it is down
// 5. Ping server periodically. When disconnected, a last will is published
// 6. Implements "ota.update" for OTA updates, see https://mongoose.ws/mqtt/
#include "mongoose.h"
#define MQTT_SERVER_URL "mqtt://broker.hivemq.com:1883"
#define MQTT_PUBLISH_TOPIC "mg/123/tx"
#define MQTT_SUBSCRIBE_TOPIC "mg/123/rx"
#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"
#define MQTT_QOS 1
#define RECONNECT_PERIOD_MS 3000
#define MQTT_RECONNECT_PERIOD_MS 3000
#define TLS_CA ""
#define TLS_KEY ""
#define TLS_CRT ""
static struct mg_connection *s_mqtt_conn; // Client connection
static struct mg_rpc *s_rpc = NULL; // List of registered RPC methods
static void subscribe(struct mg_connection *c, struct mg_str topic) {
struct mg_mqtt_opts opts = {};
@@ -69,7 +67,9 @@ static void mqtt_ev_handler(struct mg_connection *c, int ev, void *ev_data) {
// c->is_hexdumping = 1;
} else if (ev == MG_EV_CONNECT) {
if (c->is_tls) {
struct mg_tls_opts opts = {.ca = mg_unpacked("/certs/ca.pem"),
struct mg_tls_opts opts = {.ca = mg_str(TLS_CA),
.cert = mg_str(TLS_CRT),
.key = mg_str(TLS_KEY),
.name = mg_url_host(MQTT_SERVER_URL)};
mg_tls_init(c, &opts);
}
@@ -77,16 +77,31 @@ static void mqtt_ev_handler(struct mg_connection *c, int ev, void *ev_data) {
// On error, log error message
MG_ERROR(("%lu ERROR %s", c->id, (char *) ev_data));
} else if (ev == MG_EV_MQTT_OPEN) {
// MQTT connect is successful
MG_DEBUG(("%lu CONNECTED to %s", c->id, MQTT_SERVER_URL));
subscribe(c, mg_str(MQTT_SUBSCRIBE_TOPIC));
int status = *(int *) ev_data;
MG_DEBUG(("%lu CONNECT status: %d", c->id, status));
if (status == 0) {
subscribe(c, mg_str(MQTT_SUBSCRIBE_TOPIC));
if (mg_match(mg_str(MQTT_SERVER_URL), mg_str("#azure-devices.net"), 0)) {
// This is Azure IoT Hub. Subscribe for DPS messages
subscribe(c, mg_str("$iothub/methods/POST/#"));
}
}
} else if (ev == MG_EV_MQTT_MSG) {
// When we get echo response, print it
char response[100];
struct mg_mqtt_message *mm = (struct mg_mqtt_message *) ev_data;
mg_snprintf(response, sizeof(response), "Got %.*s -> %.*s", mm->topic.len,
mm->topic.buf, mm->data.len, mm->data.buf);
publish(c, mg_str(MQTT_PUBLISH_TOPIC), mg_str(response));
struct mg_str caps[5]; // caps[0] = method name, caps[2] = request id
if (mg_match(mm->topic, mg_str("$iothub/methods/POST/*/?$rid=*"), caps)) {
// Azure direct method call. Construct a stub response, "{}"
char topic[128];
mg_snprintf(topic, sizeof(topic), "$iothub/methods/res/%d/?$rid=%.*s",
200, (int) caps[2].len, caps[2].buf);
publish(c, mg_str(topic), mg_str("{}"));
} else {
char response[100];
mg_snprintf(response, sizeof(response), "Got %.*s -> %.*s", mm->topic.len,
mm->topic.buf, mm->data.len, mm->data.buf);
publish(c, mg_str(MQTT_PUBLISH_TOPIC), mg_str(response));
}
} else if (ev == MG_EV_MQTT_CMD) {
struct mg_mqtt_message *mm = (struct mg_mqtt_message *) ev_data;
if (mm->cmd == MQTT_CMD_PINGREQ) mg_mqtt_pong(c);
@@ -97,9 +112,7 @@ static void mqtt_ev_handler(struct mg_connection *c, int ev, void *ev_data) {
}
void mg_mqtt_init(struct mg_mgr *mgr) {
(void) mgr;
if (!s_rpc) mg_rpc_add(&s_rpc, mg_str("ota.update"), rpc_ota_update, NULL);
mg_mem_files = mg_packed_files; // Use generated packed filesystem
mg_rpc_add(&mgr->rpcs, mg_str("ota.update"), rpc_ota_update, NULL);
}
void mg_mqtt_poll(struct mg_mgr *mgr) {
@@ -107,18 +120,24 @@ void mg_mqtt_poll(struct mg_mgr *mgr) {
// Reconnect if connection is closed, and send MQTT PINGs to keep
// the connection alive or to detect connection loss
if (mg_timer_expired(&timer, RECONNECT_PERIOD_MS, mg_now())) {
if (mg_timer_expired(&timer, MQTT_RECONNECT_PERIOD_MS, mg_now())) {
if (s_mqtt_conn == NULL) {
struct mg_mqtt_opts opts = {.clean = true,
.qos = MQTT_QOS,
.topic = mg_str(MQTT_PUBLISH_TOPIC),
.keepalive = 5,
.version = 4,
.message = mg_str("bye")};
struct mg_mqtt_opts opts = {
.clean = true,
// .client_id = mg_str(MQTT_CLIENT_ID),
.user = mg_str(MQTT_USER),
.pass = mg_str(MQTT_PASS),
.qos = MQTT_QOS,
.keepalive = 5,
.version = 4, // MQTT 3.1.1
.topic = mg_str(MQTT_PUBLISH_TOPIC),
.message = mg_str("bye"),
};
s_mqtt_conn =
mg_mqtt_connect(mgr, MQTT_SERVER_URL, &opts, mqtt_ev_handler, NULL);
} else {
mg_mqtt_ping(s_mqtt_conn);
// publish(s_mqtt_conn, mg_str(MQTT_PUBLISH_TOPIC), mg_str("hi"));
}
}
}
-1
View File
@@ -1 +0,0 @@
../../http/http-client/packed_fs.c