RIOT, le système d'exploitation libre pour l'Internet des objets

Open Silicium n° 018 | avril 2016 | Alexandre Abadie - Francisco Acosta Padilla - Cédric Adjih - Emmanuel Baccelli
  • Actuellement 0 sur 5 étoiles
0
Merci d'avoir participé !
Vous avez déjà noté cette page, vous ne pouvez la noter qu'une fois !
Votre note a été changée, merci de votre participation !
La variété des plateformes matérielles et logicielles pour l'Internet des objets (en anglais : Internet of Things, ou IoT) rend difficile le choix d'une bibliothèque de développement à utiliser. RIOT se présente comme le système d'exploitation « friendly » pour l'IoT en proposant une API uniforme pour une grande diversité de cartes. RIOT est économe en mémoire et en énergie et supporte les protocoles standards de communication pour l'IoT. Voici un tour d'horizon de RIOT et de ses possibles applications.

Introduction

RIOT est un système d'exploitation construit autour d'un micro-noyau temps-réel, développé pour les besoins de l'Internet des objets et des réseaux sans fil. Initialement lancé en Europe par INRIA, Freie Universität Berlin, et l'Université de Hambourg en 2013, RIOT est actuellement développé par une large communauté du logiciel libre rassemblant des développeurs du monde entier. Le code source de RIOT est disponible en ligne [1], gratuitement, sous licence LGPLv2.1.

L'objectif socio-économique de RIOT est de fournir à l'IoT une plateforme logicielle libre, gratuite et résolument communautaire (au sens de Linux par exemple) afin d'accélérer l'innovation en réduisant les coûts du développement de logiciel IoT, de favoriser transparence et protection de la vie privée pour les utilisateurs de l'IoT, et une diminution des déchets électroniques en supprimant le verrouillage logiciel des objets connectés.

L'objectif technique de RIOT est de faciliter la vie des programmeurs, en fournissant un système d'exploitation embarqué qui puisse s'adapter à tout type de cartes et d'objets connectés de l'IoT et ses diverses applications (domotique, robotique, réseaux de capteurs, etc.), en offrant la capacité d'interagir avec une grande variété de périphériques tout en consommant le moins d'énergie possible.

Pour les besoins de l'Internet des objets, RIOT implémente également des protocoles réseaux standards dont nous verrons un exemple d'application à la fin de cet article. Une bonne illustration de ces capacités de communication est donnée par le projet water.li [2], qui présente une application typique d'objets connectés utilisant RIOT pour « l'Internet des plantes », à l'aide des protocoles standards tels que :

- 6LoWPAN : la version basse consommation d'IPv6 [3] ;

- RPL : du routage multi-saut sans-fil basse énergie pour IPv6 [4] ;

- CoAP : la version basse consommation de HTTP [5].

Côté développeur, RIOT facilite l'utilisation des chaînes de compilation nécessaires pour les différentes cartes supportées et des outils pour flasher un microcontrôleur (MCU). Son architecture modulaire permet également de n'utiliser que ce qui est réellement nécessaire au programme pour fonctionner. De cette manière, un programme fonctionnant avec RIOT peut ne consommer que quelques kilooctets de RAM (~1.5kB au minimum), ce qui le rend utilisable avec des configurations matérielles aux ressources très limitées.

1. Architecture et détails techniques

On l'a déjà dit précédemment : RIOT est un micro-noyau temps-réel dont le cœur est construit autour de threads et de leurs interactions. Pour suivre au mieux la description architecturale qui suit, le lecteur est invité à consulter le code source de RIOT sur GitHub [1]. À la racine du code source, le dossier core contient l'implémentation des concepts fondamentaux de ce type d'architecture (Figure 1) :

- Un scheduler (ordonnanceur) gérant le basculement entre les contextes d'exécutions (threads) en fonction de leurs priorités. Plus la valeur de priorité est haute, moins le contexte d'exécution est prioritaire. Une interruption préempte tout contexte en cours d'exécution.

- Le multi-threading et la synchronisation entre thread : chaque thread est créé avec une taille de pile associée, ce qui permet de contrôler plus finement l'espace mémoire nécessaire à son exécution. Les mutex  permettent également de gérer la synchronisation entre ces threads. Chaque thread est initialisé avec une priorité puis est « passé » à l'ordonnanceur. À noter que le programme principal d'une application s'exécute dans le thread appelé « main » qui possède par défaut une priorité intermédiaire.

- Les mécanismes de communication entre threads (IPC inter-processus) se traduisent par des échanges de messages qui peuvent être synchrones (bloquants) ou asynchrones (non bloquants), avec de nombreuses autres options. L'utilisation d'une file de messages dans le cas des communications asynchrones permet de limiter la perte de messages si une interruption se produit avant réception.

Fig 1. Arborescence du code de RIOT.

Se trouvent également associés à ce noyau :

- les dossiers boards et cpu qui contiennent les définitions et configurations spécifiques pour chaque carte et CPU supportés (numéros de broches, vitesse d'horloge, drivers, adresses des registres) ;

- le dossier drivers contient les pilotes de périphériques : capteurs, modules réseau, etc. C'est plus particulièrement dans drivers/include/periph qu'est définie l'API générique de l'interface avec les périphériques internes des MCU : UART, I2C, SPI et autres GPIO ;

- des bibliothèques systèmes dans sys pour se faciliter la vie sans avoir à réinventer la roue : un shell, des timers génériques, des fonctions de hachage, une implémentation de sémaphore, un mapping de l'API Arduino vers RIOT, etc. ;

- enfin, le dossier sys/net contient les implémentations des protocoles réseaux comme Ethernet, IPv4, IPv6, UDP et bien sûr les protocoles orientés IoT : IEEE802.15.4, 6LowPAN, RPL, SLIP. Tous ces protocoles sont utilisables à travers une API réseau modulaire et générique nommée GNRC (Generic Network Stack) comme le montre la figure 2.

Fig 2. Pile réseau générique (GNRC) de RIOT.

Terminons cette présentation succincte de l'architecture de RIOT par quelques mots sur les autres dossiers utiles aux développeurs :

- dist contient des outils d'aide au développement et au prototypage rapide : pyterm affiche la sortie de l'interface UART de débogage, tapsetup configure des interfaces tap sur un système hôte, openocd contient un script shell écrit autour d'OpenOCD pour flasher et déboguer un programme.

- examples et tests contiennent de nombreux exemples dont on pourra s'inspirer afin de débuter rapidement et efficacement le développement d'applications basées sur RIOT.

Pour plus d'information, le lecteur est invité à consulter la documentation sur le wiki [6] de RIOT ainsi que la description de son API [7].

2. Cartes et périphériques externes supportés

Le support d'une carte disponible sur le marché commence par le support de son microcontrôleur. Pour RIOT, le choix de ces microcontrôleurs (ou plus généralement d'une famille de microcontrôleurs) s'est fait souvent en priorité en fonction de leur adéquation à l'Internet des objets. Tout d'abord, l'aspect économique est essentiel puisque les objets connectés ont vocation à être répandus largement. D'un côté il existe des MCU qui sont donc trop chers et d'un autre, certains modèles à bas coût ne disposent pas de suffisamment de mémoire flash ou de RAM pour y faire tourner une application RIOT avec les piles protocolaires. L'autre contrainte directement liée à l'IoT (et aux problèmes environnementaux !) est la consommation énergétique des MCU.

Cependant, l'offre allant croissant, il est finalement très facile de trouver des MCU – et les cartes associées – ayant des caractéristiques compatibles avec RIOT. 4 familles de CPU sont aujourd'hui supportées par RIOT :

- ARM : dans cette (grande) famille, RIOT fonctionne par exemple sur les microcontrôleurs de type ARM Cortex-M0 et présents entre autres sur les cartes STM32F0discovery chez ST ou SAMR21-xpro chez Atmel (Figure 3). Citons également les CPU de type ARM Cortex-M3 sur les cartes Arduino-due (Atmel), mbed LPC1768 (NXP) ou encore la Fox chez HIKOB. Enfin, RIOT supporte des cartes à base de Cortex M4 et d'ARMv7 (voir la carte MSBA2 en Figure 4).

Fig 3. La carte Atmel SAMR21 Xplained Pro qui a été utilisée pour les exemples d'applications de cet article.

Fig 4. La carte MSBA2 fabriquée par l'Université Libre de Berlin dispose d'un ARMv7.

- MSP430 : fabriqué par Texas Instrument, cette famille de microcontrôleurs permet une consommation énergétique très faible et intègre une large gamme de périphériques. On trouve ces MCU par exemple sur les cartes Z1 chez Zolertia (Figure 5).

Fig 5. La carte Z1 de Zolertia.

- AVR : parmi les microcontrôleurs Atmel, seul l'ATMega2560 est supporté (partiellement) et donc la carte Arduino Mega2560 (Figure 6) peut être utilisée avec RIOT.

Fig 6. La carte Arduino Mega2560. Cette carte possède 50 broches E/S numériques et 16 broches analogiques, mais elles ne sont pas toutes mappées dans l'API RIOT. Avant de se lancer dans un montage avec cette carte, il faudra donc se référer à son implémentation dans RIOT pour savoir quelles broches sont disponibles.

- x86 : il n'est pas encore supporté en tant que tel. Néanmoins, une fonctionnalité importante est la possibilité de lancer une application RIOT sur un PC, en tant que processus natif d'un autre OS de type Linux ou Mac OS. Il est même possible de faire tourner plusieurs instances de RIOT sur un PC (ou un Mac), et de les faire communiquer en réseau via un émulateur de lien Ethernet. Ces fonctionnalités sont notamment utilisées pour le test et le débogage du code embarqué avec RIOT, et permettent l'utilisation d'outils standards pour le développement tels que gdb, valgrind, wireshark etc. Depuis cette cible dite native, naturellement, la partie du code proche du matériel, tels les drivers, n'est pas utilisée. C'est le cas pour SPI, I2C ou les GPIO.

Nous ne détaillerons pas dans cet article la liste exhaustive des cartes du marché supportées par RIOT [8].

Note

Pour certaines cartes le support n'est pas complet : par exemple, toutes les broches ne sont pas mappées ou l'implémentation des périphériques internes du MCU est partielle. 2 options s'offrent à vous dans ce cas :

- choisir une autre carte pour laquelle la fonctionnalité que vous recherchez est disponible dans RIOT,

- vous rapprocher des membres de la communauté RIOT et pourquoi pas, leur proposer une contribution officielle (« Pull Request ») ajoutant le support pour le driver dont vous avez besoin [9].

C'est la même chose concernant les pilotes pour périphériques externes.

Le support de périphériques externes n'est pas en reste dans RIOT puisqu'y sont implémentés des pilotes pour la plupart des capteurs utilisés dans le monde de l'IoT :

- capteurs de mesure de température, pression, luminosité : DHT, SHT, ISL29020, MPL3115A2, etc. ;

- capteurs de distance comme le SRF02 ;

- accéléromètres, gyroscopes (MPU9150).

La liste est assez longue.

Comme RIOT est conçu pour l'IoT, on trouve bien sûr des pilotes pour modules réseau : XBee, nRF24L01, adaptateur Ethernet, etc.

La liste complète des périphériques utilisables peut être consultée sur la documentation en ligne de RIOT [10].

3. Premiers pas avec RIOT

Maintenant que nous avons présenté ce système dans ses grandes lignes et donné un aperçu des plateformes matérielles et des périphériques utilisables, il est temps de passer à la partie pratique. Il s'agit ici de donner quelques éléments pour bien débuter puis de montrer comment écrire une application avec RIOT.

Le premier programme présenté dans cette section (« Hello World », paragraphe 3.2) pourra être testé sur la carte d'évaluation Atmel SAMR21 Xplained Pro, déjà mentionnée plus haut, mais également sur toutes les cartes supportées par RIOT puisqu'il ne nécessite aucun pilote particulier. Par contre, pour des raisons de simplicité (et de place dans cet article), le second programme ne pourra être testé directement qu'avec une SAMR21 Xplained Pro et son extension Io1 Xplained Pro, car il dépend directement de la configuration matérielle. En effet, il nécessite la présence du capteur de température présent sur le bus I2C de la carte d'extension. Avec quelques modifications, il est toutefois facile d'adapter le code pour utiliser un autre périphérique externe. Par exemple, on pourrait utiliser un capteur de type DHT, dont le driver est présent dans RIOT et s'inspirer du programme de test associé : RIOT/tests/driver_dht/main.c.

Enfin, le système hôte utilisé ici pour la compilation et le téléversement des programmes est Ubuntu 15.10 : c'est une version stable actuelle (mais non LTS) et certains manques dans les versions antérieures d'OpenOCD y sont corrigés.

Pour se connecter au port série/USB et communiquer avec les interfaces séries des cartes, l'utilisateur sur le système hôte doit faire partie du groupe dialout (cas d'Ubuntu) :

host% sudo adduser <login> dialout

3.1 Récupération du code et installation des outils de compilation

Avant de commencer à programmer une application, nous nous plaçons dans un dossier de travail et y clonons le code source de RIOT depuis GitHub :

host% mkdir ~/riot-os && cd ~/riot-os

host% git clone https://github.com/RIOT-OS/RIOT.git

Il faudra aussi installer les outils nécessaires à la compilation et au débogage puisqu'ils dépendent de la famille de microcontrôleurs cible : MSP430, AVR ou ARM.

Cela commence par l'installation de quelques paquetages communs :

host% sudo apt-get install build-essential g++-multilib gtkterm openocd

Le paquetage g++-multilib est uniquement nécessaire si votre système hôte est 64bits. Notez également qu'il s'agit du paquetage pour le compilateur C++ : nous ne l'avons pas dit jusqu'à présent, mais RIOT est compatible C++ !

Installons maintenant les compilateurs croisés ainsi que leurs outils associés pour les différentes familles de microcontrôleurs :

- ARM :

host% sudo apt install gcc-arm-none-eabi gdb-arm-none-eabi binutils-arm-linux-gnueabi

- AVR :

host% sudo apt install gcc-avr gdb-avr avr-libc binutils-avr avrdude

- MSP430 :

host% sudo apt install gcc-msp430 mspdebug msp430-libc binutils-msp430

Nous sommes prêts pour compiler et flasher un premier programme !

3.2 Compilation et flash d'un premier programme

Ici nous allons décrire l'exemple « Hello World » présent dans le dossier examples du code source de RIOT. Cela nous permet de donner quelques explications sur le système de compilation fourni par RIOT.

Dans cet exemple se trouvent 2 fichiers :

- Makefile contient des variables de configuration pour la chaîne de compilation de l'application ;

- main.c contient le code C du programme principal à compiler.

Toutes les applications RIOT sont  créées à partir de ces 2 fichiers.

Voici ce que contient le Makefile :

# donnons un nom à notre application

APPLICATION = hello-world

# par défaut, on compile pour la cible native, c'est-à-dire le PC hôte.

BOARD ?= native

# variable définissant le chemin où se trouve la racine du code de RIOT

RIOTBASE ?= $(CURDIR)/../..

# activation du mode développement

CFLAGS += -DDEVELHELP

# on rend la sortie de la compilation moins bavarde

QUIET ?= 1

# autres directives de compilation nécessaires depuis le dossier racine de RIOT

include $(RIOTBASE)/Makefile.include

Dans Makefile, 2 variables sont essentielles :

- RIOTBASE permet de développer nos applications en dehors de la base de code de RIOT. Cette option est donc très utile pour éviter de polluer cette dernière avec les fichiers générés par la compilation.

- BOARD permet de spécifier la carte cible pour la compilation. Ce nom peut être pris dans la liste des noms de dossiers présents dans boards.

Le fichier main.c quant à lui est très basique : il ne fait qu'afficher « Hello World » puis des informations sur la carte et le microcontrôleur, ensuite il retourne. Voici son contenu :

#include <stdio.h>

int main(void)

{

    puts("Hello World!");

    printf("You are running RIOT on a(n) %s board.\n", RIOT_BOARD);

    printf("This board features a(n) %s MCU.\n", RIOT_MCU);

    return 0;

}

On commence donc par copier le dossier de l'exemple « hello world » vers le dossier de travail, puis on lance la compilation depuis cet emplacement en spécifiant le chemin vers le dossier racine de RIOT avec la variable RIOTBASE (vous pouvez aussi la configurer une fois pour toutes dans le Makefile).

host% cp -R ~/riot-os/RIOT/examples/hello-world ~/riot-os/hello-world

host% cd ~/riot-os/hello-world

host% make RIOTBASE=~/riot-os/RIOT

Building application "hello-world" for "native" with MCU "native".

"make" -C /home/login/riot-os/RIOT/boards/native

"make" -C /home/login/riot-os/RIOT/boards/native/drivers

"make" -C /home/login/riot-os/RIOT/core

"make" -C /home/login/riot-os/RIOT/cpu/native

"make" -C /home/login/riot-os/RIOT/cpu/native/periph

"make" -C /home/login/riot-os/RIOT/drivers

"make" -C /home/login/riot-os/RIOT/sys

"make" -C /home/login/riot-os/RIOT/sys/auto_init

   text    data     bss     dec     hex filename

  23792     392   47652   71836   1189c /home/login/riot-os/hello-world/bin/native/hello-world.elf

On vient de générer un fichier binaire elf natif compatible avec le système hôte. Le résultat de la compilation se trouve dans le sous-dossier bin/native et l'exécutable bin/native/hello-world.elf doit normalement fonctionner sur l'hôte de compilation :

host% bin/native/hello-world.elf

RIOT native interrupts/signals initialized.

LED_GREEN_OFF

LED_RED_ON

RIOT native board initialized.

RIOT native hardware initialization complete.

main(): This is RIOT! (Version: 2016.03-devel-xxx-xxx-xxx)

Hello World!

You are running RIOT on a(n) native board.

This board features a(n) native MCU.

Compilons maintenant notre programme pour générer du code binaire compatible avec l'ARM Cortex-M0 de la carte SAMR21. Pour cela, on affecte à la variable BOARD le nom de la carte cible, ici samr21-xpro :

host% make RIOTBASE=~/riot-os/RIOT BOARD=samr21-xpro

[…]

   text    data     bss     dec     hex filename

   8252     128    2752   11132    2b7c /home/login/riot-os/hello-world/bin/samr21-xpro/hello-world.elf

Il s'agit désormais de programmer la carte SAMR21 avec le binaire que nous venons de générer.

Pour cela, branchons d'abord la carte sur un des ports USB de l'ordinateur hôte. Si vous ne savez pas sur quel port est connectée votre carte, RIOT fournit un script bien pratique pour le trouver : RIOT/dist/tools/usb-serial/list-ttys.sh.

host% ~/riot-os/RIOT/dist/tools/usb-serial/list-ttys.sh

/sys/bus/usb/devices/2-1.2.1: Atmel Corp. EDBG CMSIS-DAP serial: 'ATML2127031800004653', tty(s): ttyACM0

Ce script est surtout utile si plusieurs cartes sont branchées en même temps sur l'hôte de compilation comme on le verra plus tard. Il nous donne 2 informations importantes :

- serial : ATML2127031800004653 désigne le numéro de série de la carte. On peut choisir explicitement sur quelle carte flasher un programme en affectant cette valeur à la variable SERIAL.

- tty(s): ttyACM0 : le terminal sur lequel la carte est visible. Ici ce sera /dev/ttyACM0. On peut forcer cette valeur avec la variable PORT. Cette variable est utilisée par la cible term pour savoir sur quel terminal ouvrir une connexion. RIOT embarque son propre terminal pyterm.

Cependant, nous n'avons pour l'instant qu'une seule carte branchée et elle est connectée sur le terminal par défaut donc on peut se dispenser de ces deux variables et flasher le microcontrôleur avec la cible flash, qui lance le téléversement sur le microcontrôleur avec l'outil adapté (avrdude pour un AVR ou OpenOCD pour les autres) via l'interface UART de la carte branchée sur le port série/USB.

host% make RIOTBASE=~/riot-os/RIOT BOARD=samr21-xpro flash

[…]

### Flashing Target ###

Open On-Chip Debugger 0.9.0 (2015-09-02-10:42)

[…]

    TargetName         Type       Endian TapName            State       

--  ------------------ ---------- ------ ------------------ ------------

 0* at91samr21g18.cpu  cortex_m   little at91samr21g18.cpu  running

target state: halted

target halted due to debug-request, current mode: Thread

xPSR: 0x01000000 pc: 0x00000438 msp: 0x20000b60

auto erase enabled

Info : SAMD MCU: SAMR21G18A (256KB Flash, 32KB RAM)

wrote 16384 bytes from file /home/login/riot-os/hello-world/bin/samr21-xpro/hello-world.hex in 3.129823s (5.112 KiB/s)

target state: halted

target halted due to debug-request, current mode: Thread

xPSR: 0x01000000 pc: 0x000003dc msp: 0x20000b40

verified 8380 bytes in 0.721287s (11.346 KiB/s)

shutdown command invoked

Done flashing

Enfin, il s'agit d'ouvrir un terminal communicant avec la carte que nous venons de programmer. Ceci s'effectue avec la cible term.

Vous devriez alors voir le fameux message s'afficher dans la console de débogage, qui est donc le résultat du programme s'exécutant sur la carte SAMR21, communicant avec votre PC via le port série/USB :

host% make BOARD=samr21-xpro  RIOTBASE=~/riot-os/RIOT term

2016-02-23 18:04:07,688 - INFO # Connect to serial port /dev/ttyACM0

Welcome to pyterm!

Type '/exit' to exit.

2016-02-23 18:04:11,462 - INFO # main(): This is RIOT! (Version: 2016.03-devel-xxx-xxx-xx)

2016-02-23 18:04:11,463 - INFO # Hello World!

2016-02-23 18:04:11,467 - INFO # You are running RIOT on a(n) samr21-xpro board.

2016-02-23 18:04:11,470 - INFO # This board features a(n) samd21 MCU.

/exit

2016-02-23 18:05:02,999 - INFO # Exiting Pyterm

Remarquons que la compilation, le téléversement et l'ouverture du terminal peuvent se faire en une seule commande :

host% make RIOTBASE=~/riot-os/RIOT BOARD=samr21-xpro flash term

Note

Attention !

Sur certaines cartes, le programme s'est déjà terminé au moment où le terminal se lance et donc aucun message n'est affiché. Il faut dans ce cas appuyer sur le bouton « reset » (cas de l'Atmel SAMR21 ou de l'Arduino Due), s'il y en a un, pour le voir s'afficher. Par ailleurs pour l'Arduino MEGA 2560 (mais non le Arduino MEGA ADK), il faudra rajouter à la commande make les options supplémentaires :  PROGRAMMER='wiring' PROGRAMMER_FLAGS='-P $(PORT) -b 115200 -D'.

3.3. Lecture d'un capteur de température sur I2C

Nous venons donc de voir comment se servir du système de compilation, passons maintenant à un exemple plus réaliste : la lecture d'un capteur de température sur un bus I2C. Cette valeur sera lue et affichée sur la console de débogage toutes les 5 secondes.

Rappelons que cet exemple, tel qu'expliqué ici, ne peut fonctionner qu'avec la carte Atmel SAMR21 et son extension I/O1 Xplained Pro (figure 7). Son code source est disponible sur GitHub [11].

Fig 7. La carte Atmel  SAMR21 Xplained Pro et son extension I/O1 Xplained Pro utilisée pour lire le capteur de température sur le bus I2C (remarquez le cavalier « UART » sur cette carte d'extension ; il faut l'enlever pour désactiver la boucle de retour UART).

Pour utiliser l'interface I2C de RIOT et ajouter une temporisation de 5 secondes entre chaque lecture/affichage, il faut ajouter 2 nouvelles instructions au Makefile : USEMODULE et FEATURES_REQUIRED.

USEMODULE += xtimer

FEATURES_REQUIRED += periph_i2c

Le Makefile est prêt, passons maintenant au fichier main.c. Le principe est plutôt simple : la fonction i2c_init_master() (voir la documentation en ligne [12]) initialise notre nœud comme maître sur le bus I2C puis le programme entre dans une boucle infinie où il va lire par intervalle de 5 secondes la valeur sur le périphérique avec la fonction read_temperature(). La lecture de la mesure de température sur le bus I2C s'effectue avec i2c_read_bytes().

D'après le datasheet de l'extension [13], l'adresse où lire le registre (2 octets) contenant la mesure de température est 0b1001 A2 A1 A0 : les broches Ai se trouvent à l'arrière de la carte et ont la valeur 1 par défaut, mais elles passent à 0 quand elles sont soudées. Dans notre cas, on ne va pas sortir le fer et laisser ces broches à 1. L'adresse est donc 0b1001111 et on peut la décomposer avec cette notation hexadécimale : (0x48 | 0x07).

Autre information importante pour lire notre capteur : le numéro de l'interface I2C à utiliser côté RIOT. La SAMR21 n'ayant qu'une interface I2C, il s'agira donc de 0.

Le programme main.ccommence donc de manière classique, par les directives d'inclusions des fichiers d'entête nécessaires. En particulier periph_conf.h qui se trouve dans les dossiers de support des cartes (par exemple RIOT/boards/samr21-xpro/include pour la samr21-xpro). Il contient les définitions des variables génériques pour RIOT, mais affectées des valeurs spécifiques à chaque carte. Le fichier periph/i2c.h contient les définitions de l'API générique de communication sur un bus I2C dont l'implémentation (le .c) se trouve dans les dossiers de chaque CPU (par exemple, pour notre carte Atmel et son Samd21, il s'agit de RIOT/cpu/samd21/periph/i2c.c).

#include <stdio.h>

#include <stdlib.h>

#include "xtimer.h"

#include "periph_conf.h"

#include "periph/i2c.h"

/* Numéro de l'interface I2C */

#define I2C_INTERFACE 0

/* Adresse du capteur sur le bus I2C */

#define SENSOR_ADDR  (0x48 | 0x07)

/* Intervalle de 5 secondes */

#define INTERVAL (5000000U)

puis on définit la fonction statique read_temperature() :

static int read_temperature(void)

{

    uint16_t temperature;

    char buffer[2] = { 0 };

    

    /* lecture de la température sur le bus I2C */

    i2c_read_bytes(I2C_INTERFACE, SENSOR_ADDR, buffer, 2);

    uint16_t data = (buffer[0] << 8) | buffer[1];

    int8_t sign = 1;

    

    /* Traitement du bit de signe */

    if (data & (1 << 15)) {

 sign *= -1;

 data &= ~(1 << 15);

    }

    

    /* Conversion de la température en Celsius */

    data = (data >> 5);

    temperature = data * sign * 0.125;

    

    return (int)temperature;

}

La temporisation de 5 secondes entre les lectures consécutives se fait avec les fonctions xtimer_now() et xtimer_usleep_until() fournies par le module xtimer. La fonction printf() affiche alors les mesures sur la console de débogage. L'interface UART de débogage possède toujours le numéro 0 dans l'API RIOT et est aussi toujours disponible, même sur les cartes partiellement supportées. C'est sur l'UART 0 que les fonctions printf() et puts() écrivent les chaînes de caractères.

Cela nous donne donc la fonction main :

int main(void)

{

    /* Interface I2C initialisée comme maître */

    i2c_init_master(I2C_INTERFACE, I2C_SPEED_NORMAL);

   

    uint32_t last_wakeup = xtimer_now();

    for (;;) {

        printf("Temperature: %i°C\n", read_temperature());

        xtimer_usleep_until(&last_wakeup, INTERVAL);

    }

    return 0;

}

Il ne reste plus qu'à vérifier tout ça :

host% make RIOTBASE=~/riot-os/RIOT BOARD=samr21-xpro flash term

Building application "i2c_temperature" for "samr21-xpro" with MCU "samd21".

[…]

   text    data     bss     dec     hex filename

  14512     128    2784   17424    4410 /home/login/riot-os/i2c_temperature/bin/samr21-xpro/i2c_temperature.elf

[…]

Info : SAMD MCU: SAMR21G18A (256KB Flash, 32KB RAM)

wrote 16384 bytes from file /home/login/riot-os/i2c_temperature/bin/samr21-xpro/i2c_temperature.hex in 3.177128s (5.036 KiB/s)

target state: halted

target halted due to debug-request, current mode: Thread

xPSR: 0x01000000 pc: 0x00000438 msp: 0x20000b60

verified 14636 bytes in 1.229100s (11.629 KiB/s)

shutdown command invoked

Done flashing

/home/login/riot-os/RIOT/dist/tools/pyterm/pyterm -p "/dev/ttyACM0" -b "115200"

Twisted not available, please install it if you want to use pyterm's JSON capabilities

2016-03-02 16:06:13,070 - INFO # Connect to serial port /dev/ttyACM0

Welcome to pyterm!

Type '/exit' to exit.

2016-03-02 16:06:18,020 - INFO # Temperature: 24°C

2016-03-02 16:06:23,040 - INFO # Temperature: 24°C

On remarque donc que l'API de RIOT est très pratique et nous simplifie énormément le travail : tout le nécessaire se fait en seulement quelques lignes de C ANSI, avec du code lisible et fonctionnel.

4. Communication entre nœuds

Arrivés à ce point, nous savons lire un capteur, compiler un programme et le flasher sur une carte, mais pour vraiment mesurer les possibilités qu'offre RIOT pour l'IoT, il nous en faut un peu plus, en l'occurrence quelques explications sur l'utilisation des threads, la communication inter-processus (IPC) et l'API de communication réseau. L'intégralité des codes sources présentés dans cette section est disponible sur GitHub [14].

4.1 Principe de fonctionnement

On se propose ici de créer un mini réseau IoT sans fil constitué d'un nœud (même s'il peut y en avoir plusieurs) avec capteur envoyant des informations par radio (nœud client) et d'un autre nœud en écoute des paquets radio qui lui sont envoyés (nœud serveur) comme décrit dans la figure 8.

Fig 8. Principe de fonctionnement de la communication entre les nœuds.

La configuration matérielle reste encore la même que précédemment puisque nous allons une fois de plus profiter des possibilités de la carte Atmel SAMR21 (l'utilisation d'autres cartes pourvues d'une interface radio supportée par RIOT est également possible). Une antenne IEEE 802.15.4 [15] est intégrée à cette carte pour permettre la communication sans-fil et RIOT la reconnaît automatiquement lorsqu'on charge les modules GNRC dans son Makefile. Pour mettre en pratique cet exemple, il vous faudra donc 1 carte SAMR21 seule (qui servira de nœud serveur) d'une part, et d'autre part une (ou plusieurs) carte SAMR21 avec son extension I/O1 Xplained, pour jouer le rôle de nœud client.

Les paquets sont envoyés sur un réseau 6LoWPAN ayant une topologie en étoile. Chaque nœud du réseau possède une adresse IPv6 qui lui est propre et déterminée à partir de son adresse physique. Pour des raisons de simplicité, tous les nœuds clients de notre mini réseau doivent connaître a priori l'adresse du serveur pour lui envoyer leurs messages. On verra dans un premier temps comment obtenir cette adresse avec RIOT.

Le programme qui va fonctionner sur le nœud client reprend celui déjà présenté dans la section 3.4 auquel on ajoute seulement l'envoi de paquets UDP vers le serveur. Côté nœud serveur, il y aura un peu plus de nouveautés, car on va utiliser un thread dédié à l'écoute sur un port UDP. Ce thread transfère les messages reçus au thread principal qui les écrit ensuite sur l'UART 0 du nœud serveur.

4.2 Quelques manipulations préalables

Avant de commencer, nous allons déterminer quelques informations utiles sur les nœuds. Tout d'abord, on doit connaître les numéros de série et ports tty sur lesquels sont branchées les cartes. Ces informations nous assurent que l'on flashe le bon programme sur la bonne carte (nœud). Branchons donc la carte qui nous servira de nœud serveur puis lançons le fameux script list-ttys.sh vu plus haut :

host% ~/riot-os/RIOT/dist/tools/usb-serial/list-ttys.sh

/sys/bus/usb/devices/2-1.2.1: Atmel Corp. EDBG CMSIS-DAP serial: 'ATML2127031800004658', tty(s): ttyACM0

Ensuite, il reste à brancher la carte qui servira de nœud client et à relancer le script :

host% ~/riot-os/RIOT/dist/tools/usb-serial/list-ttys.sh

/sys/bus/usb/devices/2-1.2.1: Atmel Corp. EDBG CMSIS-DAP serial: 'ATML2127031800004658', tty(s): ttyACM0

/sys/bus/usb/devices/2-1.2.2: Atmel Corp. EDBG CMSIS-DAP serial: 'ATML2127031800004653', tty(s): ttyACM1

Dans ce cas, pour flasher et ouvrir un terminal sur les nœuds, il faudra donc ajouter à la commande make :

- SERIAL=ATML2127031800004658 PORT=/dev/ttyACM0 pour le nœud serveur ;

- SERIAL=ATML2127031800004653 PORT=/dev/ttyACM1 pour le nœud client.

La deuxième chose à faire est de déterminer l'adresse IPv6 du nœud serveur. En effet, cette adresse sera écrite en dur dans chaque nœud client de notre réseau pour qu'il sache à qui envoyer ses paquets UDP. L'exemple gnrc_networking présent dans RIOT/examples/gnrc_networking (n'hésitez pas aller regarder le code) va ici nous servir puisqu'il utilise le shell RIOT et propose plusieurs commandes de configuration réseau et de gestion du nœud. On commence donc par le copier dans notre dossier de travail, puis on le flashe sur la carte qui servira de nœud serveur :

host% cp -R ~/riot-os/RIOT/examples/gnrc_networking ~/riot-os/.

host% cd ~/riot-os/gnrc_networking

host% make RIOTBASE=~/riot-os/RIOT BOARD=samr21-xpro SERIAL=ATML2127031800004658 PORT=/dev/ttyACM0 flash term

Building application "gnrc_networking" for "samr21-xpro" with MCU "samd21".

[…]

   text    data     bss     dec     hex filename

  78940     220   22984  102144   18f00 /home/login/riot-os/gnrc_networking/bin/samr21-xpro/gnrc_networking.elf

[…]

Remarquez que l'ajout de la pile réseau nous fait compiler beaucoup plus de modules et que le programme généré est aussi plus gros (~100Ko). Mais le plus intéressant, c'est de voir que pour ce prix, RIOT s'exécutant sur la carte SAMR21 nous répond maintenant via le shell (comme dans la ligne de commandes sous Linux). La commande help affiche par exemple la liste des commandes disponibles :

2016-03-02 18:57:14,963 - INFO # Connect to serial port /dev/ttyACM0

Welcome to pyterm!

Type '/exit' to exit.

help

2016-03-02 19:01:46,074 - INFO # help

2016-03-02 19:01:46,076 - INFO # Command              Description

2016-03-02 19:01:46,080 - INFO # ---------------------------------------

2016-03-02 19:01:46,085 - INFO # udp                  send data over UDP and listen on UDP ports

2016-03-02 19:01:46,088 - INFO # reboot               Reboot the node

2016-03-02 19:01:46,094 - INFO # ps                   Prints information about running threads.

2016-03-02 19:01:46,097 - INFO # ping6                Ping via ICMPv6

2016-03-02 19:01:46,101 - INFO # random_init          initializes the PRNG

2016-03-02 19:01:46,106 - INFO # random_get           returns 32 bit of pseudo randomness

2016-03-02 19:01:46,110 - INFO # ifconfig             Configure network interfaces

2016-03-02 19:01:46,113 - INFO # txtsnd               send raw data

2016-03-02 19:01:46,119 - INFO # fibroute             Manipulate the FIB (info: 'fibroute [add|del]')

2016-03-02 19:01:46,123 - INFO # ncache               manage neighbor cache by hand

2016-03-02 19:01:46,127 - INFO # routers              IPv6 default router list

2016-03-02 19:01:46,134 - INFO # rpl                  rpl configuration tool [help|init|rm|root|show]

L'adresse IP de la carte, que RIOT configure automatiquement via les protocoles IPv6 standards en la matière, s'obtient avec la commande ifconfig (toujours comme sous Linux) :

2016-03-02 19:03:02,194 - INFO # > ifconfig

2016-03-02 19:03:02,199 - INFO # Iface  7   HWaddr: 12:9a  Channel: 26  Page: 0  NID: 0x23

2016-03-02 19:03:02,203 - INFO #            Long HWaddr: 5a:47:3c:7c:49:50:12:9a

2016-03-02 19:03:02,210 - INFO #            TX-Power: 0dBm  State: IDLE  max. Retrans.: 3  CSMA Retries: 4

2016-03-02 19:03:02,215 - INFO #            AUTOACK  CSMA  MTU:1280  HL:64  6LO  RTR  IPHC  

2016-03-02 19:03:02,218 - INFO #            Source address length: 8

2016-03-02 19:03:02,221 - INFO #            Link type: wireless

2016-03-02 19:03:02,227 - INFO #            inet6 addr: ff02::1/128  scope: local [multicast]

2016-03-02 19:03:02,232 - INFO #            inet6 addr: fe80::5847:3c7c:4950:129a/64  scope: local

2016-03-02 19:03:02,238 - INFO #            inet6 addr: ff02::1:ff50:129a/128  scope: local [multicast]

L'avant-dernière ligne nous donne cette information. Sur la carte utilisée ici, l'adresse IPv6 est fe80::5847:3c7c:4950:129a.

Nous avons enfin toutes les informations nécessaires et nous pouvons passer à la programmation des nœuds client et serveur de notre exemple.

4.3 Ajout de la pile réseau dans le programme du nœud client

Le code de notre nœud client est sensiblement le même que celui de la section 3.4, il suffit de lui ajouter la pile réseau. Dans un premier temps, nous rajoutons les modules nécessaires au Makefile :

USEMODULE += gnrc_netif_default

USEMODULE += auto_init_gnrc_netif

Ces deux modules vont compiler le code de chargement et d'initialisation automatique de la couche réseau qui sert à détecter l'antenne radio présente sur la carte et à attribuer une adresse à cette interface. Il nous faut ensuite charger les modules réseaux de RIOT relatifs à IPv6 et UDP :

USEMODULE += gnrc_ipv6_default

USEMODULE += gnrc_conn_udp

Dans le fichier main.c, il faut inclure les entêtes réseau :

#include "net/af.h"

#include "net/gnrc/ipv6.h"

#include "net/conn/udp.h"

Puis définir l'adresse et le port du nœud serveur sur le réseau :

const char * SERVER_IP = "fe80::5847:3c7c:4950:129a";

const uint16_t SERVER_PORT = 8000;

Pour envoyer les messages, 2 fonctions seulement vont nous servir :

- ipv6_addr_from_str() pour convertir l'adresse IPv6 SERVER_IP initialisée sous forme de chaîne en un type compréhensible par l'API RIOT ;

-  conn_udp_sendto() pour envoyer le message au serveur par UDP.

Pour terminer, le printf() du programme précédent est remplacé par un appel à cette fonction dans la boucle infinie de la fonction main :

int main(void)

{

    /* interface i2c initialisée comme maître */

    i2c_init_master(I2C_INTERFACE, I2C_SPEED_NORMAL);

    

    /* format destination address from string */

    ipv6_addr_t dst;

    ipv6_addr_from_str(&dst, SERVER_IP);

    uint32_t last_wakeup = xtimer_now();

    char message[MAX_MESSAGE_LENGTH];

    for(;;) {

        snprintf(message, sizeof(message), "Temperature: %i°C", read_temperature());

        /* envoi du message au serveur */

        conn_udp_sendto(message, strlen(message), NULL, 0, (struct sockaddr *)&dst,

                        sizeof(dst), AF_INET6, (uint16_t)0, SERVER_PORT);

        /* temporisation de 5 secondes */

        xtimer_usleep_until(&last_wakeup, INTERVAL);

    }

    return 0;

}

Nous conseillons bien sûr de consulter le code complet sur GitHub [16]. À ce stade, on peut flasher ce programme sur le nœud client :

host% make RIOTBASE=~/riot-os/RIOT BOARD=samr21-xpro SERIAL=ATML2127031800004653 PORT=/dev/ttyACM1 flash

4.4 Programmation du nœud serveur

Le programme du nœud serveur, quant à lui, utilise 2 nouveaux concepts qui n'ont jusqu'à présent pas été évoqués, mais qui représentent un avantage de RIOT vis-à-vis de ses concurrents : les threads et l'IPC. Le principe de ce programme est de démarrer un thread dédié à l'écoute sur le port UDP 8000 qui, pour chaque paquet reçu, envoie de façon synchrone un message au thread principal.

De la même manière que précédemment, les modules réseaux sont ajoutés dans le Makefile :

USEMODULE += gnrc_netif_default

USEMODULE += auto_init_gnrc_netif

USEMODULE += gnrc_ipv6_default

USEMODULE += gnrc_conn_udp

Ensuite dans main.c (nous en donnons ici les grandes lignes), il faut inclure les entêtes RIOT nécessaires à l'utilisation des threads, de l'IPC et de la pile réseau :

#include "thread.h"

#include "msg.h"

#include "net/af.h"

#include "net/conn/udp.h"

#include "net/gnrc/ipv6.h"

Vient ensuite une série de définitions et d'initialisations de variables statiques dans l'exécution de ce programme, en particulier :

- le numéro du port UDP sur lequel le thread serveur écoute :

static uint16_t SERVER_PORT=8000;

- la pile associée au thread serveur. La valeur par défaut est 8192.

static char server_stack[THREAD_STACKSIZE_DEFAULT];

- le pid du thread principal qui est renseigné au début de la fonction main.

static kernel_pid_t main_thread_pid;

Le thread serveur est créé par un appel dans main à la fonction thread_create(), qui prend en paramètres la pile allouée statiquement pour ce thread, sa priorité et le pointeur vers la fonction server_thread() qui sera exécutée par ce thread.

La fonction server_thread() commence par initialiser la file de messages du thread serveur avec msg_init_queue() pour la communication entre threads. Ensuite, la connexion UDP est créée avec la fonction conn_udp_create(), puis, dans une boucle infinie, le thread se met en attente avec conn_udp_recvfrom() qui bloque son exécution jusqu'à la réception d'un paquet UDP. Pour chaque paquet reçu, un message (sous forme de tableau de caractères) est construit à partir de son contenu et de son adresse source, obtenue avec ipv6_addr_to_str(). Enfin, le message est envoyé au thread principal, identifié par son pid, avec msg_send_receive(). Cette fonction bloque l'exécution jusqu'à réception d'une réponse du thread principal.

Voici le détail de cette fonction :

static void *server_thread(void *args) {

    msg_init_queue(server_msg_queue, SERVER_MSG_QUEUE_SIZE);

    /* [… variables locales …] */

    /* Ouverture de la connexion UDP */

    ipv6_addr_t server_addr = IPV6_ADDR_UNSPECIFIED;

    conn_udp_create(&server_conn, &server_addr, sizeof(server_addr), AF_INET6, SERVER_PORT);

    /* Boucle infinie de traitement des paquets entrants */

    for(;;) {

 /* remise à zéro du buffer où sont stockés les paquets */

 memset(server_buffer, 0, sizeof(server_buffer));

 /* le thread reste ici en attente de paquets entrants */

        conn_udp_recvfrom(&server_conn, server_buffer, sizeof(server_buffer), &addr_src,

     &addr_len, &SERVER_PORT);

 /* construction du message à partir du contenu du paquet et de son adresse source */

 ipv6_addr_to_str(addr_src_str, &addr_src, IPV6_ADDR_MAX_STR_LEN);

 snprintf(message, sizeof(message), "%s from: %s\n", server_buffer, addr_src_str);

 /* le message est transféré au thread principal */

 msg_t msg;

 msg.content.value = (uint32_t)&message;

 msg_send_receive(&msg, NULL, main_thread_pid);

    }

    

    return NULL;

}

Remarquez aussi que le message envoyé contient l'adresse en mémoire du tableau de caractères et non son contenu. En effet, le contenu d'un message RIOT (content.value) est de type uint32_t et ne peut donc pas contenir notre chaîne de caractères. Pour des raisons de simplicité de l'exemple, nous protégeons cette adresse avec une communication synchrone entre les threads (e.g. msg_send_receiveau lieu de msg_send).

Dans la fonction main qui est exécutée par le thread principal, après avoir identifié le pid du thread principal avec thread_getpid()et initialisé le thread serveur avec thread_create(), le programme entre dans une boucle infinie, où msg_receive() le met en attente des messages venant du thread serveur. Une fois le message écrit sur l'interface de débogage de l'ordinateur hôte, une réponse est envoyée au thread serveur avec msg_reply() pour le débloquer. De cette manière, on s'assure que le message est correctement traité avant de continuer.

Voici le détail de la fonction main :

int main(void)

{

    /* initialisation de la file de messages du thread principal */

    msg_init_queue(main_msg_queue, MAIN_MSG_QUEUE_SIZE);

    

    /* obtention du pid du thread principal */

    main_thread_pid = thread_getpid();

    /* création du thread « serveur » */

    thread_create(server_stack, sizeof(server_stack), THREAD_PRIORITY_MAIN - 1,

                  THREAD_CREATE_STACKTEST, server_thread, NULL, "IP server");

    msg_t msg;

    char * message = NULL;

    for (;;) {

 /* l'exécution est bloquée jusqu'à la réception d'un message */

 msg_receive(&msg);

 message = (char*)msg.content.value;

 

 /* le message est écrit sur le terminal */

 printf("%s", message);

 

 /* une réponse est envoyée au thread « serveur » pour le débloquer */

 msg_reply(&msg, NULL);

    }

}

Le code complet de cet exemple est une nouvelle fois disponible sur GitHub [17].

Après compilation et flash de ce programme sur le nœud serveur, si votre nœud client est toujours en fonctionnement, vous devriez voir les premiers messages arriver :

host% make RIOTBASE=~/riot-os/RIOT BOARD=samr21-xpro SERIAL=ATML2127031800004658 PORT=/dev/ttyACM0 flash term

2016-03-03 11:26:57,185 - INFO # Connect to serial port /dev/ttyACM0

Welcome to pyterm!

Type '/exit' to exit.

2016-03-03 11:26:59,851 - INFO # Temperature: 24°C from: fe80::5841:3c49:c5f0:23d2

2016-03-03 11:27:04,863 - INFO # Temperature: 24°C from: fe80::5846:1378:e3c1:d5ae

Nous venons donc de construire un mini réseau de capteurs utilisant les protocoles basse consommation pour la communication sans-fil, destinés à l'IoT : IEEE 802.15.4 et 6LoWPAN.

4.5 Ajoutons un peu de Bluetooth dans notre réseau

Comme nous l'avons dit précédemment, les fonctions printf()/puts() écrivent directement sur l'interface UART par défaut de RIOT (n°0) qui correspond au port série/USB côté PC hôte. La carte Atmel SAMR21 possède cependant une deuxième interface UART (RX sur la broche PA23 et TX sur la broche PA22), il est donc tout à fait envisageable de faire transférer par le nœud serveur les mesures de température de notre réseau 6LowPAN sur un smartphone ou une tablette via une connexion Bluetooth (BLE ou versions antérieures). Notre nœud serveur UDP se transforme alors en routeur 6LowPAN vers Bluetooth.

Il existe sur le marché des modules clés en main [18] (Bluetooth 2.0) et [19] (Bluetooth Low Energy, BLE) avec une interface UART. On va se servir de cette possibilité. Le code complet de cet exemple est disponible sur GitHub [20].

Avant de commencer, il faut apparier le module Bluetooth avec votre smartphone/tablette sur lequel il faut installer une application de type terminal Bluetooth (ce n'est pas le choix qui manque) pour se connecter au module. Nous partons du principe que vous savez comment configurer votre module Bluetooth en lui envoyant des commandes sur le port série : en particulier le mode d'authentification, le nom et le plus important, la vitesse du bus.

Les seules modifications à apporter au programme serveur présenté à la section 4.3 sont :

- d'ajouter le module periph_uart au Makefile :

USEMODULE += periph_uart

- d'inclure l'entête de définitions de l'interface UART (comme pour l'I2C) :

#include "periph/uart.h"

- de définir le numéro de l'interface UART et sa vitesse (qui doit correspondre à celle que vous avez configurée sur votre module Bluetooth) :

#define UART_INTERFACE (1)

#define UART_BAUDRATE  (115200)

- d'initialiser l'interface UART pour communiquer avec votre module Bluetooth au début de la fonction main :

uart_init(UART_INTERFACE, UART_BAUDRATE, NULL, (void *)NULL);

- et enfin de remplacer l'appel à printf() par la fonction uart_write() à la réception des messages venant du thread serveur :

uart_write(UART_INTERFACE, (uint8_t*)message, strlen(message));

Fig 10. Le montage complet avec les cartes Atmel, un module Sparkfun Bluetooth Mate Silver [18] et les messages affichés sur un Nexus 5 utilisant l'application Bluetooth Terminal.

4.6 Fonctionnalités réseaux supplémentaires

En plus des fonctionnalités réseaux que vous avez expérimentées jusqu'ici dans les exemples ci-dessus, d'autres fonctionnalités standards sont disponibles dans RIOT, permettant par exemple :

- la configuration automatique du routage multi-saut sans-fil à basse énergie grâce à un module implémentant le protocole RPL [4] ;

- du transfert web pour systèmes très contraints grâce à un autre module externe implémentant CoAP [5] ;

- un routeur multi-interfaces conforme à la fois aux standards IPv6 filaire et sans-fil 6LoWPAN.

Cette dernière fonctionnalité permet par exemple de concevoir, efficacement et simplement, un routeur basse énergie utilisant RIOT pour assurer l'interconnexion avec l'Internet pour un réseau IoT (border router), ou un routeur basse consommation utilisant RIOT pour interconnecter directement deux réseaux IoT utilisant des radios différentes.

En composant ces différents modules de base, il devient donc facile de construire une solution adaptée à une très grande variété de scénarios IoT, dont la quasi-totalité de ceux envisagés actuellement par l'industrie dans ce domaine.

Conclusion

RIOT est un OS pour les objets connectés qui ne peuvent pas exécuter Linux faute de mémoire suffisante ou de CPU adaptés. Sur de tels objets, RIOT offre un équivalent de Linux tant du point de vue fonctionnalités techniques, que du point de vue approche sociale (logiciel libre et communautaire).

Comme on l'a vu dans cet article, RIOT offre une API simple et performante, permettant à tout un chacun de développer des applications IoT pour une large gamme d'objets connectés (32-bit, 16-bit et 8-bit), en s'appuyant sur des outils connus (gcc, gdb, valgrind, etc.) et du code concis et lisible, en langage standard (C ANSI et C++).

Si le support de certains matériels et de certains protocoles de communication est encore partiel ou à fournir, RIOT propose une base robuste, et toute contribution est la bienvenue, suivant un process simple, accessible et clair. De cette manière, le code de RIOT est constamment développé, vérifié et amélioré par une communauté dynamique et auto-organisée, au sein de laquelle une centaine de développeurs venant d'horizons divers coopèrent actuellement. Il ne tient qu'à vous d'y participer vous-même !

Bibliographie

[1] Projet RIOT sur GitHub, https://github.com/RIOT-OS/RIOT

[2] Raisons du choix de RIOT dans le projet Watr.li, http://watr.li/riot.html

[3] Protocole 6LowPAN, https://fr.wikipedia.org/wiki/6LoWPAN

[4] Protocole RPL, https://tools.ietf.org/html/rfc6550

[5] Protocole COAP, https://tools.ietf.org/html/rfc7252

[6] Wiki RIOT, https://github.com/RIOT-OS/RIOT/wiki

[7] Documentation de l'API RIOT, http://doc.riot-os.org

[8] Liste de cartes supportées par RIOT, https://github.com/RIOT-OS/RIOT/wiki/RIOT-Platforms

[9] Contribuer à RIOT, https://github.com/RIOT-OS/RIOT/wiki/Contributing-to-RIOT

[10] Liste des périphériques externes supportés, http://riot-os.org/api/group__drivers.html

[11] Code source de la section 3.4, https://github.com/aabadie/riot-apps/tree/master/i2c_temperature

[12] API I2C dans RIOT, http://riot-os.org/api/group__drivers__periph__i2c.html

[13] Doc de carte d'extension Atmel I/O1 Xplained pro, http://www.atmel.com/images/atmel-42078-io1-xplained-pro_user-guide.pdf

[14] Code source des exemples de la partie 4, https://github.com/aabadie/riot-apps/tree/master/point_to_point_network

[15] IEEE 802.15.4, https://fr.wikipedia.org/wiki/IEEE_802.15.4

[16] Code source de la section 4.2, https://github.com/aabadie/riot-apps/tree/master/point_to_point_network/client_with_sensor

[17] Code source de la section 4.3, https://github.com/aabadie/riot-apps/tree/master/point_to_point_network/server_to_terminal

[18] SparkFun Bluetooth Modem - BlueSMiRF Silver, https://www.sparkfun.com/products/12577

[19] Adafruit Bluefruit LE UART Friend, https://www.adafruit.com/product/2479

[20] Code source de la section 4.4, https://github.com/aabadie/riot-apps/tree/master/point_to_point_network/server_to_bluetooth

Tags : IoT, noyau, temps réel