Affichage des articles dont le libellé est coap. Afficher tous les articles
Affichage des articles dont le libellé est coap. Afficher tous les articles


Créer un serveur CoAP

J’évoquai dans un précédent article comment créer un client CoAP directement à partir de la spécification, sans avoir à s’imposer une librairie.

Nous allons donc maintenant voir comment mettre en place facilement un serveur CoAP à partir des différentes solutions déjà existantes sur le marché.

Actinium

Intégré dans la suite logicielle "Californium" mise en avant par la fondation Eclipse dans le cadre de son grand projet "IOT", cet outil est basé entièrement sur des services REST : de l’administration au déploiement de nouveaux services, tout peut être fait par l’appel à des URL. Il est également très simple de créer de nouvelles application puisque tout est codé en Javascript! Il utilise en effet le moteur open source Rhino de la fondation Mozilla. Petit exemple d’application (issu de la documentation) :


app.root.onget = fnc;
app.root.onpost = fnc;
app.root.onput = fnc;
app.root.ondelete = fnc;

fnc = function(request) {
    request.respond(2.05, "Hello World"); 
}

L’ensemble du code est très léger, le serveur se démarre à partir d’un simple Jar :

java -jar actinium-*.jar

Cependant, Rhino est désormais connu pour être un peu vieillissant et ses performances ne sont plus comparables aux moteurs Javascript plus récents, comme le montre ce document. Depuis Java 8, il a d’ailleurs été remplacé par Nashorn.

Node.JS

Puisque nous sommes en train de parler de moteurs JS côté serveur, qu’en est-il de Node.JS? Il existe en effet un module "node-coap" développé par Matteo Collina. Il profite alors des très bonnes performances du moteur “V8” de Google lui permettant de s’insérer dans des applications "temps réel".

Egalement très simple d’utilisation, il permet de tester rapidement les fonctionnalités essentielles du protocole. Par exemple :


var coap = require("coap");

var server = coap.createServer();

server.on('request', function(req, res) {
 switch(req.url) {
  case ('/test') :
   if (req.method == 'GET') {
    res.end("NodeJS CoAP server OK!!!");
    console.log("[test] " + req.method + " to " + req.url);
   }
  break;
  case ('/.well-known/core') :
   if (req.method == 'GET') {
    res.code = '2.05';
    res.setOption("Content-Format", "application/link-format");
res.end(";title=Test server,;title=Hello!");
   }
  break;
  default:
   res.code = '4.04';
   res.setOption("Content-Format", 'text/html');
   res.end('<html><head><title>404 - Not found</title></head><body><h1>Not found.</h1></body></html>');
   console.log("[404] " + req.method + " to " + req.url);
  break;
 }
});

On voit ici qu’il est très facile de mettre en place un serveur répondant à différentes URL et requêtes HTTP.

Au passage, vous pouvez lire dans cet exemple l’implémentation de la découverte de services (discovery) utilisée par CoAP et constituée d’une requête GET sur l’URL "/.well-known/core" renvoyant du contenu au format "CoRE Link Format" (RFC 6690).

Ponte

Pour aller plus loin, cet autre module Node.JS propose une solution complète mêlant HTTP, CoAP et MQTT (protocole basé sur du publish/subscribe), en plus notamment de quelques connecteurs vers des bases de données NoSQL :

Je vous laisse le soin de découvrir ses fonctionnalités.

Amusez-vous bien!


Fichier(s) joint(s) :



Un client CoAP simple en C

CoAP est un protocole de communication dédié aux objets connectés : basé sur UDP et REST, il permet de diminuer au maximum le contenu des messages pour économiser les ressources des objets lors des transferts de données.

Nous allons voir ici comment implémenter votre propre client, à l'aide de la spécification du protocole. Le but est d'être capable d'envoyer, de la manière la plus simple possible, des données à un serveur CoAP exposant des services. Niveau matériel, un Arduino Mega avec un shield Ethernet communique avec un serveur Java.

Commençons par la structure des messages. Ce chapitre de la spécification nous explique comment les structurer :

8 bits 8 bits 8 bits 8 bits
Ver T TKL Code Message ID
Token
Options
Terminator Payload

Je vous laisse vous référer à la spécification pour la signification des champs. Un peu plus en détails, les Options suivent un format propre :

8 bits 8 bits 8 bits 8 bits
Ver T TKL Code Message ID
Token
Delta Length Value
Terminator Payload

Ici nous allons construire un message contenant :

  • version : 1
  • type : Request
  • TKL : 0 (pas de Token)
  • Code : POST
  • vers le service : "/sensors"
  • contenu : "test"

Voici ce que cela donne :

8 bits 8 bits 8 bits 8 bits
1 Request 0 POST Message ID
 
Uri-Path 7 URI segment "sensors"
Terminator "test"

Chaque Option possède un code "Option Number" propre. Par exemple, "Uri-Path" qui permet d'indiquer un segment de l'adresse du service, a pour code 11. Le Delta se calcule alors en additionnant le code de l'option avec celles des précédentes :

8 bits 8 bits 8 bits 8 bits
1 Request 0 POST Message ID
 
0+11 7 "sensors"
Terminator "test"

Si l'on veut spécifier plusieurs options du même type, il n'est pas nécessaire d'additionner le code. Par exemple, pour pointer sur le service "/sensors/temp" :

8 bits 8 bits 8 bits 8 bits
1 Request 0 POST Message ID
 
0+11 7 "sensors"
0+11+0 4 "temp"
Terminator "test"

En revanche, pour une autre Option, par exemple "Max-Age" qui a le code 14 :

8 bits 8 bits 8 bits 8 bits
1 Request 0 POST Message ID
 
0+11 7 "sensors"
0+11+14 2 12
Terminator "test"

Revenons à notre exemple et transformons tout en valeurs selon la spec et notre cas :

8 bits 8 bits 8 bits 8 bits
1 1 0 0.02 52942
 
11 7 "sensors"
Terminator "test"

Ce qui nous donne, en binaire en respectant la longueur des champs (pour les champs les plus simples) :

8 bits 8 bits 8 bits 8 bits
01 01 00000 00000010 1100111011001110
 
1011 0111 "sensors"
11 11 1111 "test"

Enfin, pour se faciliter l'envoi via le contrôleur Ethernet, dont la librairie est prévue pour traiter des "char" sur 8 bits, il suffit de transformer le résultat en concaténant les bits par 8 :

8 bits 8 bits 8 bits 8 bits
0x50 0x02 0xCE 0xCE
 
0xB7 "sensors"
0xFF "test"

En pratique, un petit exemple de code Arduino pour envoyer nos données :


#include <SPI.h>
#include <Ethernet.h>
#include <EthernetUdp.h>

char coapacket[18];
EthernetUDP udp;

void loop() {
  coapacket[0] = 0x50;
  coapacket[1] = 0x02;
  coapacket[2] = 0xce;
  coapacket[3] = 0xce;
  coapacket[4] = 0xB7;
  coapacket[5] = 's';
  coapacket[6] = 'e';
  coapacket[7] = 'n';
  coapacket[8] = 's';
  coapacket[9] = 'o';
  coapacket[10] = 'r';
  coapacket[11] = 's';
  coapacket[12] = 0xff;
  coapacket[13] = 't';
  coapacket[14] = 'e';
  coapacket[15] = 's';
  coapacket[16] = 't';
  coapacket[17] = '\0';
 
  udp.beginPacket(serverIP, 5683);
  udp.write(coapacket);
  udp.endPacket();
}

Voilà tout!


Fichier(s) joint(s) :