MQTT-SN Client tutorial

Table of contents Table of contents

This is a simple tutorial showing how to use the embeNET MQTT-SN Client API.

Overview and intended usage

The MQTT-SN Client service relies on the UDP networking being available. It is thus advised to start this service once the underlying networking is operational. In embeNET-enabled node, the networking starts once the node joins the network, which is signalled through the 'onJoined' callback. In this tutorial this callback will start the MQTT-SN Client service.

Once the client is started it will try to connect to a MQTT-SN gateway, given its IPv6 address and port. In this tutorial we will assume, that the gateway runs in the border router on a default port no 1885. Once connected the client will be able to subscribe to MQTT topics, register new topics as well as publish and receive data on these topics.

The software architecture of this client is event driven, meaning that all API calls are non-blocking, and operations requiring longer time are started and their end is signalled through an application defined callback.

MQTT-SN Architecture

MQTT-SN is a UDP-based version of the popular MQTT protocol, used extensively in IoT networks and beyond. In MQTT-SN the clients connect to the MQTT-SN Gateway or MQTT-SN Forwarder (which in turn connects to the MQTT-SN Gateway). The Gateway connects to the standard MQTT broker (or has an integrated broker). The main function of the Gateway is protocol translation between MQTT-SN and MQTT.

Functionality of the MQTT-SN Client

This client supports the following functions:

  • connecting and disconnecting to the gateway
  • registering topics
  • subscribing to topics and receiving messages on that topic
  • publishing messages on topics

Basic usage

MQTT-SN client API

All the functions of this MQTT-SN client are available in the mqttsn_client.h header file:

#include "mqttsn_client.h"
MQTT-SN client API declaration.

MQTT-SN client descriptor

This MQTT-SN Client follows an object-oriented approach in C, in which there is a single structure representing the client's state. The structure is MQTTSNClient and it needs to instantiated by the user code:

// Structure describing the MQTT-SN client
MQTTSNClient client;
Structure describing the MQTT-SN Client.
Definition: mqttsn_client.h:219

Initialization

Before any usage, the MQTT-SN client needs to be initialized by a call to MQTTSN_CLIENT_Init

// Initialize the client and open local UDP port 1885 for communication with the gateway
MQTTSN_CLIENT_Init(&client, 1885, "Client0", NULL);
MQTTSNClientResult MQTTSN_CLIENT_Init(MQTTSNClient *client, uint16_t port, const char *clientId, const MQTTSNClientEventHandlers *eventHandlers)
Initializes the MQTT-SN client.

In the above example, the MQTT-SN client will use socket on local port 1885 to receive messages from the gateway, and this socket will be used later to communicate with the gateway. The client will introduce itself as "Client0". Be aware that this client identification string must be unique. One way to achieve this is to use the node's UID in client Id like this:

// Initialize the client and open local UDP port 1885 for communication with the gateway
// Use the UID of the node as part of the client ID
EMBENET_EUI64 uid = EMBENET_NODE_GetUID();
char clientId[32];
sprintf(clientId, "Client:%016lX", uid);
MQTTSN_CLIENT_Init(&client, 1885, clientId, NULL);

Event handling through application callbacks

In order to react to MQTT-SN client events, the application can register callbacks during client initialization. This provides the intended way of working with this client. The callbacks are gathered in the MQTTSNClientEventHandlers structure. All callbacks are optional, meaning that setting each to NULL will disable the callback.

The following example shows how to set up the callbacks:

static void onClientConnected(struct MQTTSNClient* client) {
// The client has connected to the gateway
}
static void onClientDisconnected(struct MQTTSNClient* client) {
// The client has disconnected from the gateway
}
static void onTopicRegisteredByGateway(struct MQTTSNClient* client, MQTTSNTopicId topicId, const char* topicName) {
// The gateway has registered the topic
}
static MQTTSNClientEventHandlers eventHandlers = {onClientConnected, onClientDisconnected, onTopicRegisteredByGateway};
void mqttInitWithCallbacks(void) {
// Initialize the client
// Use the UID of the node as part of the client ID
EMBENET_EUI64 uid = EMBENET_NODE_GetUID();
char clientId[32];
sprintf(clientId, "Client:%016lX", uid);
MQTTSN_CLIENT_Init(&client, 1885, clientId, &eventHandlers);
}
uint16_t MQTTSNTopicId
Type describing topic id.
Definition: mqttsn_client.h:118
Structure grouping event callbacks for MQTT-SN client.
Definition: mqttsn_client.h:183

Starting the service and connecting to the gateway

As mentioned in the overview, we will typically start the MQTT-SN client service once we have connected to the network. First we need to connect to the gateway. We can do that through a call to MQTTSN_CLIENT_Connect. Below is a minimal example of the 'onJoined' embeNET Node callback implementation:

void onJoined(EMBENET_PANID panId, const EMBENET_NODE_QuickJoinCredentials* quickJoinCredentials) {
// Get the address of the border router and treat it as a MQTT-SN gateway address
EMBENET_IPV6 gatewayAddress;
EMBENET_NODE_GetBorderRouterAddress(&gatewayAddress);
// Try to connect to the gateway
MQTTSN_CLIENT_Connect(&client, &gatewayAddress, 1885, 120, 60, NULL, NULL);
}
MQTTSNClientResult MQTTSN_CLIENT_Connect(MQTTSNClient *client, EMBENET_IPV6 const *gatewayAddress, uint16_t gatewayPort, uint16_t keepAliveTime, uint16_t pingPeriod, const char *willTopic, const uint8_t *willMsg)
Makes the client try to connect to a gateway.

Once connected to the gateway, the client will call the MQTTSNClientEventHandlers::onConnected callback, where further interaction with the gateway can take place.

Registering topics by the client

In MQTT-SN the topics need to be registered in the gateway, before they can be used. The topic registration allows the gateway to assign a topic ID (MQTTSNTopicId) so that the clients can use it instead of string topic names. This makes the UDP payloads much smaller.

In order to register a topic, the application needs to call MQTTSN_CLIENT_RegisterTopic function:

// Register the topic in the gateway
MQTTSN_CLIENT_RegisterTopic(&client, "myTopic", onTopicRegisteredByClient);
MQTTSNClientResult MQTTSN_CLIENT_RegisterTopic(MQTTSNClient *client, const char *topic, MQTTSNOnTopicRegisteredByClient onTopicRegisteredCallback)
Registers a topic in the gateway.

If successfully registered, the onTopicRegisteredByClient will be called as callback:

static void onTopicRegisteredByClient(const struct MQTTSNClient* client, MQTTSNTopicId topicId, const char* topicName) {
// The client has successfully registered a topic
}

Topics registered by the gateway

The topics can also be registered by the gateway. The gateway may have a set of predefined topics, that it wishes to register in each connecting client. For each such topic, the MQTTSNClientEventHandlers::onTopicRegisteredByGateway callback will be called.

This may also happen when the client gets disconnected from the gateway and connects back again. Is such scenario the gateway will update the client with topics it previously registered at the gateway. Again, for each such topic, the MQTTSNClientEventHandlers::onTopicRegisteredByGateway callback will be called. To avoid this particular scenario, the client can connect using the MQTTSN_CLIENT_CleanConnect instead of MQTTSN_CLIENT_Connect to force the gateway 'forget' all previous topics associated with that client.

Topic IDs

Wether the topic was registered by the client or by the gateway, the client application can always check the ID of the topic through a call to MQTTSN_CLIENT_GetTopicId

Publishing on a topic

Once the topic is registered, the client can publish on the given topic using either a full topic name and the MQTTSN_CLIENT_PublishMessage function:

// Publish a message to the topic using topic name
MQTTSN_CLIENT_PublishMessage(&client, "myTopic", "Hello world!", 12);
MQTTSNClientResult MQTTSN_CLIENT_PublishMessage(MQTTSNClient *client, const char *topic, const char *message, size_t messageLen)
Publishes a message on a topic given the topic string.

or topic ID and the MQTTSN_CLIENT_PublishMessageById function:

// Publish a message to the topic using topic ID
MQTTSNTopicId topicId = MQTTSN_CLIENT_GetTopicId(&client, "myTopic");
MQTTSN_CLIENT_PublishMessageById(&client, topicId, "Hello world!", 12);
MQTTSNTopicId MQTTSN_CLIENT_GetTopicId(MQTTSNClient *client, const char *topic)
Gets the id of the registered topic.
MQTTSNClientResult MQTTSN_CLIENT_PublishMessageById(MQTTSNClient *client, MQTTSNTopicId topicId, const char *message, size_t messageLen)
Publishes a message on a topic given the topic id.

Subscribing to a topic

The client can subscribe to a topic and receive notifications once any data is published on that topic by any other client. To do that the application code needs to call MQTTSN_CLIENT_Subscribe providing the topic name and the callback function that will be called when the data on the given topic is published:

// Subscribe to the topic using topic name
MQTTSN_CLIENT_Subscribe(&client, "myTopic", onPublishReceived);
MQTTSNClientResult MQTTSN_CLIENT_Subscribe(MQTTSNClient *client, const char *topic, MQTTSNOnPublishReceived onPublishReceivedCallback)
Subscribes to the topic.

MQTT-SN Client API library

For more information see MQTT-SN Client