Overview

This tutorial demonstrates how Mongoose Library can be used to implement an MQTT client. We'll create an MQTT client that:

  1. Connects to the public HiveMQ MQTT server mqtt://broker.hivemq.com:1883
  2. When connected, subscribes to the topic mg/+/test
  3. Publishes a hello message to the mg/clnt/test topic
  4. Receives that message back from the subscribed topic and closes the connection
  5. Creates a timer that checks if a connection is closed, and reconnects to repeat the whole process

The full source code for this tutorial is at https://github.com/cesanta/mongoose/tree/master/examples/mqtt-client

API

The MQTT API is rich, here we'll only use three functions:

struct mg_connection *mg_mqtt_connect(struct mg_mgr *mgr, const char *url,
                                      struct mg_mqtt_opts *opts,
                                      mg_event_handler_t fn, void *fn_data);
void mg_mqtt_pub(struct mg_connection *c, struct mg_str topic,
                 struct mg_str data, int qos, bool retain);
void mg_mqtt_sub(struct mg_connection *c, struct mg_str topic, int qos);

The function mg_mqtt_connect() tries to connect to the MQTT broker whose URL is specified in parameter url, returning a connection handler or NULL on failure. The function specified in parameter fn will be our callback function, and parameter fn_data will be its argument. We can indicate session and last will options in a structure pointed to by the opts parameter.

The function mg_mqtt_pub() performs the action of publishing the message data to the topic topic.

The function mg_mqtt_sub() performs the action of subscribing to the topic topic.

For detailed information, see the documentation.

Global variables

First we declare global variables to make it easy to change tunable parameters, like MQTT server address, topic names, and QoS level.

Also, we make an MQTT connection pointer, s_conn, globally visible. A reconnection timer function is going to check that pointer for NULL, and if it is NULL, a timer function will create a new connection. Then, a connection event handler sets this pointer to NULL when a connection closes.

Main function

The main() function is simple: we initialise an event manager and start the event loop, as usual. Also we create a timer - note that a timer is created on the stack but since it is declared in main(), it lives as long as the application is running.

Note that the timer is initialised after the event manager. This is important because here the timer is created with the MG_TIMER_RUN_NOW flag, which executes the timer function immediately. Our timer function will use the event manager, so it must be initialised beforehand.

The timer function is called with an interval of 3 seconds; and our event manager poll timeout is 1 second.

For more information on timers, check the timers tutorial.

The event loop executes until a termination signal is received. After that, the mg_mgr_free() and mg_timer_free() functions are called to perform cleanup.

Timer function

The timer function implements the reconnection logic and is trivial. It creates a client connection s_conn if it is NULL. This pattern should be used for any type of client connection that must be kept alive.

Here we connect with a clean session and request the broker to publish a "goodbye" message on our behalf, when detecting our disconnection.

Event handler

The event handler function checks which event ev has arrived and acts accordingly. The MG_EV_OPEN event is the very first event that is sent to every connection when it is just created and added to the event manager. In our case, we simply log that fact:

Connection failures will trigger an MG_EV_ERROR event, an error reason is passed as a char * pointer in parameter ev_data; check the error handling tutorial for more information.

Here, on any error - for example, DNS lookup failure, we log that error:

When the TCP connection to the MQTT server is established, we initialise TLS if the MQTT server URL is mqtts://:

Note that, for that to work, the application must be built with TLS enabled. By default, the HiveMQ URL is not TLS, so building with TLS is not required; more information on how to build can be found below.

Then we catch the MG_EV_MQTT_OPEN event which is sent when the MQTT server accepted us as a client. There, we subscribe to the desired topic, and immediately publish to it. Note that these functions do not immediately send data to the MQTT server. As any other Mongoose output function, they just append data to the output buffer. Data gets sent when we exit the event handler and Mongoose performs the event manager poll, mg_mgr_poll().

When we receive a message - and we should, we print it and signal Mongoose to terminate the connection:

And when we are getting closed, we set s_conn to NULL. The timer function will kick in the reconnection later:

Build and run

  • If you've not already done so, clone the Mongoose Library repo
    $ git clone https://github.com/cesanta/mongoose
    
  • Build the example, this will also start the MQTT client:
    $ cd mongoose/examples/mqtt-client
    $ make clean all
    
  • Observe the log
    1814f03947d 2 main.c:30:fn              CREATED
    1814f03968b 2 main.c:45:fn              CONNECTED to mqtt://broker.hivemq.com:1883
    1814f03968b 2 main.c:47:fn              SUBSCRIBED to mg/+/test
    1814f03968b 2 main.c:51:fn              PUBLISHED hello -> mg/clnt/test
    1814f03978a 2 main.c:56:fn              RECEIVED hello <- mg/clnt/test
    1814f03978a 2 main.c:59:fn              CLOSED
    1814f03a344 2 main.c:30:fn              CREATED
    1814f03a58b 2 main.c:45:fn              CONNECTED to mqtt://broker.hivemq.com:1883
    1814f03a58b 2 main.c:47:fn              SUBSCRIBED to mg/+/test
    1814f03a58b 2 main.c:51:fn              PUBLISHED hello -> mg/clnt/test
    1814f03a681 2 main.c:56:fn              RECEIVED hello <- mg/clnt/test
    1814f03a681 2 main.c:59:fn              CLOSED
    

Build with TLS support

The makefile will take care of this, just pass it the proper argument.

  • For openSSL:

    $ make clean all SSL=OPENSSL
    
  • For mbedTLS:

    $ make clean all SSL=MBEDTLS
    
  • If you need to provide extra information, like for example includes and library paths, do it with the EXTRA_CFLAGS variable, like:

    $ make clean all SSL=MBEDTLS EXTRA_CFLAGS="-I/path/to/mbedtls/include -L/path/to/mbedtls/lib"
    

For more information on building TLS clients, check the TLS tutorial