Another step through the IoT field: programming things using RIOT-OS
In the last article, we saw how to build a simple cloud-based infrastructure where a virtual weather station publishes values, retrieved from some API forecast service to AWS IoT Core platform, to display them to the final user by a web application. This implementation comes with the assumption that the virtual station is a simple application running on a NodeJS runtime: however, it is a huge limitation and we want to overcome it.
The next step is to move the virtual station implementation to an operating system that can be deployed on a real microcontroller. One of them is RIOT-OS, based on a microkernel architecture where standard development and debugging tools such as gcc can be used to develop applications written in C/C++ programming languages. However, we have to reconsider the protocol which is used to send data to the AWS IoT Core platform: since MQTT can't be used in a sensor network, we need to have a light-weighted version of it, called MQTT-SN.
So the previous architecture can be updated by considering three main entities:
- Microcontroller (at least one): an MQTT-SN client running on RIOT-OS which sends data from its sensor through an MQTT-SN connection within a specific topic identifier.
- MQTT-SN Broker: a server implementation of the protocol which listens for MQTT-SN traffic on a particular port.
- Transparent Bridge: a module that subscribes to the available MQTT-SN topics to translate the data published on it and sends it to the MQTT broker located on AWS.
Through the article, we will show how to set up these three components to make them work to the previous cloud infrastructure located on AWS. This solution can be found on this repository.
MQTT-SN Broker with MOSQUITTO.RSMB
As a first step, we need to start the MQTT-SN broker. Ideally, it runs on another node of the network, while in this article has been released on the same instance of RIOT-OS, by the usage of a tap interface that needs to be configured with a proper IPv6 address in the following way.
./<yourRIOTDIR>/dist/tools/tapsetup/tapsetup sudo ip a a <youripv6address>/<yoursubnet> dev tapbr0
This broker has been implemented by a server instance of RSMB (Real Simple Message Broker). After cloning the repository from GitHub, it can be launched from the rsmb/src folder by the command
./broker_mqtts config.conf
where config.conf is the following configuration file
# add some debug output trace_output protocol listener 1885 INADDR_ANY mqtts ipv6 true listener 1886 INADDR_ANY ipv6 true
When the server is running, it will listen for MQTT-SN traffic on UDP port 1885, while MQTT connections will be managed through TCP port 1886. The MQTT connection will be used by the transparent bridge to forward at our cloud platform the messages published to the server instance.
MQTT-SN Client with RIOT-OS
After the server is up, we can develop a Riot application capable of running in any board supported by the OS. This task relies on the usage emCute, an MQTT-SN implementation through UDP sockets.
So we have to declare it in the Makefile, along with the main networking modules, before proceeding.
# Specify the mandatory networking modules for IPv6 and UDP USEMODULE += gnrc_sock_udp USEMODULE += gnrc_ipv6_default # Include MQTT-SN USEMODULE += emcute # For testing we also include the ping6 command and some stats USEMODULE += gnrc_icmpv6_echo
Now we can define the main function of the program, which will expose one single command: run, which accepts three parameters:
- the identifier of the board which will be used by the AWS cloud platform
- the IPv6 address of the MQTT-SN broker
- the port where the MQTT-SN broker is listening
static const shell_command_t shell_commands[] = {
{"run", "run the measure process", run},
{ NULL, NULL, NULL }
};
The corresponding run method, after launching the emCute thread with a given identifier
thread_create(stack, sizeof(stack), EMCUTE_PRIO, 0,
emcute_thread, id, "emcute");
tries to establish the connection by the usage of the following instruction
is_connected = con(ip, port);
If the application is connected, then it will retrieve periodically the needed data by calling the function get_measure_msg, which returns a string formatted in the following way:
<temperature>|<humidity>|<wind_dir>|<wind_int>|<rain>|<id>
This string will be sent to the default topic "riot/weather" by the usage of the method pub: it will populate the struct emcute_topic_t with the topic provided by the run method and then send the message with QoS (Quality of Service) level up to 0 by checking if any error has been raised by the emCute thread.
if (emcute_pub(&t, msg, strlen(msg), flags) != EMCUTE_OK) {
printf("error: unable to publish data to topic '%s [%i]'\n",
t.name, (int)t.id);
return 1;
}
That's it. Our data is properly sent through the MQTT-SN connection. Unfortunately, since development has been made on the native instance of RIOT-OS, measures are random values within a particular range. The next step is to add drivers for some well-known sensors and then try to deploy this client on real hardware. Also, since the emCute relies on the usage of a global-site IPv6 address, we need to execute before the run command the following one:
ifconfig 5 add <your ipv6 addres>
where your ipv6 address is on the same subnet of the one defined for the MQTT-SN broker.
Node.js implementation of a transparent MQTT-SN/MQTT bridge
The last step is to make a bridge between the MQTT-SN broker and the MQTT broker provided by AWS. There are many available options, but my choice was to adapt the simple library for MQTT connection implemented in the last article, written in Javascript for the NodeJS runtime. The MQTT connection was delegated to the MQTTClient class, which initialize an MQTT connection during the construction and offers two basic methods, publish and subscribe. This class relies on the aws-iot-device-sdk, which can't be used to establish a connection to our local broker. However, the AWS module relies on the more general mqtt package, which can be used to have a connection inside our node. So, to make the MQTTClient usable in this particular scenario, we simply need to change the constructor adding two possible options for the broker that we want to use.
class MqttClient{
constructor(broker, config){
if(broker == 'AWS'){
this.device = awsIot.device({
keyPath: config.keyPath,
certPath: config.certPath,
caPath: config.caPath,
clientId: config.clientId
host: config.host
});
}
else if(broker == 'MOSQUITTO'){
this.device = mqtt.connect('mqtt://127.0.0.1', {port: 1886});
}
this.device
.on('connect', (() =>{
console.log('connected to %s broker.', broker);
}).bind(this));
}
...
}
So we would like to use two instances of this class to:
- make an MQTT connection to AWS Broker. This can be done by following the same steps of the previous tutorial, i.e. create a configuration file that has various entries containing the path to different certificates required by AWS.
- make an MQTT connection to the local RSMB Broker to make a subscription to the topic where the Riot application is publishing its data.
This logic has been implemented in the main file gateway.js. After loading the initial configuration, it will initialize the two clients as explained before and then it will make a subscription to the MQTT-SN topic "riot/weather" with the following instruction:
if (require.main === module) {
...
this.localClient = new MqttClient('MOSQUITTO', null);
this.awsClient = new MqttClient('AWS', this.config);
this.localClient.subscribe(DEFAULT_TOPIC, callback.bind(this));
}
The callback function attached to the subscribe method, in particular, is a function that sends a VirtualStation object with the publish method of the AWS client. That is done after the method setSensorsFromRiot hast converted the message format defined in the Riot application to a JSON format which is supported by the web application of the previous article.
function callback(topic, msg){
let station = null;
try{
const values = msg.split(SEPARATOR);
station = new VirtualStation(values[5], "");
station.setSensorsFromRiot(values[0], values[1], values[2],
values[3], values[4]);
}
catch(error){
console.log("Invalid message format");
}
station && this.awsClient.publish(this.config.topic,station);
}
With this final step, our new client that will be deployed to real-world microprocessors supporting RIOT-OS can reach the AWS Cloud Platform.
Final Considerations
A video demonstration of this article can be found in the video below.
As the previous article, this article is made to address the topics of the second assignment provided by the IoT course held at Sapienza University - Rome (IT). The goal of this article is being able to set up a cloud platform in order to receive, store and view data from an environmental station.
I hope that this reading will be useful for all my colleagues and for others to understand how the development of an MQTT-SN architecture works.
Giovanni