L'internet des Objets ou bien Internet of Things (IOT) est défini comme une infrastructure qui permet d'interconnecter des objets physiques ou virtuels en utilisant les technologies de l'information et de la communication.
Cette technologie fait l'objet de recommandations définis pat l'ITU-T Y.2060 (06/2012)
Les objets sont connectés en utilisant les technologies Sigfox ou LoraWan. Ces réseaux sont disponibles sur tout le territoire et nécessitent un abonnement auprès d'un opérateur. Toutefois la technologie LoraWan peut être déployée indépendamment d'un opérateur car il existe une version libre.
Les antennes sont associées à une station qui assure la gestion des connexions radios et l'interfaçage avec le réseau local (LoraWan) ou internet (Sigfox ou LoraWan).
Les données sont transmises, via un protocole réseau standard, à un serveur associé à une base de données pour le stockage des données des objets connectés.
Les données sont ainsi accessibles par l'intermédiaire d'un serveur web.
Les données des capteurs sont envoyées au "broker", via une connexion TCP/IP, en utilisant le protocole MQTT. On parle de publication, ces données sont accessibles au serveur pour le stockage en utilisant une méthode d'abonnement.
Le broker ne stocke généralement pas les données reçues, ce qui signifie que le serveur doit être abonné avant que les données ne soient transmises.
Dans cette configuration, le broker, le serveur ainsi que la base de données peuvent être implémentés sur le même système.
Les données sont également accessibles par l'intermédiaire d'un serveur web.
Les capteurs envoient leurs données à l'aide des méthodes GET ou POST du web. Ces données sont traitées à l'aide d'un script CGI ou fastCGI.
Dans le cas de la méthode "POST", les données peuvent être transmises en utilisant le format JSON qui est très utilisé dans les échanges de données sur le web.
Ce format est une suite de paires clé : valeur séparées par des virgules l'ensemble encadré par des accolades :
{ "cle" : valeur, "liste" : [ "cle1" : valeur1 , "cle2" : valeur2 , ... ] }
Les types de données des valeurs peuvent être des nombres, chaînes de caractères, booléens ou encore des listes encadrées par des crochets, pour ce dernier cas, les éléments de la liste sont des paires cle : valeur.
Exemple pour les données d'un capteur de température et humidité :
{ "temperature" : 22.2 , "humidite" : 60 }
Ce protocole fonctionne par système de publication (publish) et abonnement (suscribe) géré par un serveur : le courtier (broker). Chaque objet, ordinateur, serveur peut s'abonner à un ou plusieurs messages, ces messages peuvent être envoyés par ces mêmes appareils.
Les messages sont hiérarchisés sous la forme d'un arborescence où chaque niveau est séparé du suivant par le caractère "/", comme par exemple :
/maison/piece1/lumiere1
Cela correspond à une source lumineuse lumiere1 de la pièce piece1 de la maison.
Il est possible d'utiliser le caractère joker "+" pour accéder à tous les éléments d'une partie de l'arborescence.
/maison/+/lumiere1
Cela permet d'accéder aux lumiere1 de toutes les pièces.
Une base de données relationnelle est un système de stockage de données organisé en tables. Chaque table est composée de rangées avec chaque rangée composée de colonnes. Chaque colonne est identifiée par un nom (champ). Une rangée est une liste de données.
Dans ce type d'architecture, il est possible d'effectuer des accès par rangée et/ou par colonne à l'aide de filtres. Ces derniers sont définis à partir de règles.
Une structure de donnée complète est structurée en tables reliées à l'aide des valeurs qu'elles contiennent que sont les clés primaires et étrangères. Les clés primaires sont uniques, c'est à dire que deux rangées ne peuvent avoir la même clé. Cette unicité permet d'accéder à une rangée sans ambiguïté.
Il existe plusieurs bases de données, nous citerons, ici, les logiciels libres les plus utilisés
On propose de créer une base obletsconnectes qui permet de stocker les mesures horodatées de données de capteurs de température, humidité, pression, ...
Ce diagramme a été créé avec MySQL Workbench.
La base est composée de plusieurs tables :
Les tables constructeurs et fournisseurs sont utiles pour la maintenance du système afin de pouvoir accéder aux documents du capteur en cas de panne ou de modification du système.
Cet exemple est basique et ne contient sans doute pas toutes les informations nécessaires et utiles, mais il permet de montrer ce que peut être une base de donnée pour l'internet des objets.
Le langage SQL (Structured_Query_Language) est le langage qui permet d'accéder aux données de cette base. Il permet de gérer les bases ainsi que les tables de chaque base, les données de chaque table (ajout, suppression, modification), ... On peut également visiter la partie SQL du site w3schools ou encore la documentation SQLite
Le langage sql n'est pas sensible à la casse (majuscule ou minuscule).
Les types de données disponibles peuvent varier en fonction de la base de donnée utilisée. on présente ici un échantillon de ces types :
INT(taille)
: entier défini sur 32 bits, le paramètre taille, optionnelle sur certaines bases, correspond à la taille d'affichage des entiers.FLOAT(p)
: réel simple précision si p, optionnel sur certaines bases, est inférieur ou égal à 24, double précision, s'il reste inférieur à 53.VARCHAR(taille)
: chaîne de caractère avec taille, optionnel sur certaines bases, qui correspond à la taille maximale réservée.DATE
: qui correspond à la date au format AAAA-MM-DDTIME
: qui correspond à l'heure au format hh:mm:ssDATETIME
: qui correspond à la date et l'heure au format AAAA-MM-DD hh:mm:ssLes valeurs par défaut des paramètres dépendent des bases de données.
Cela n'est pas utilisé avec SQLite, car dans ce cas on utilise un fichier par base.
CREATE database nombase;
: créer une nouvelle base de nom nombaseUSE nombase;
: utiliser la base nombase avec les commandes qui suivent.DROP database nombase;
: supprime définitivement la base.SHOW databases;
: affiche la liste des bases présentes.CREATE TABLE nomtable ( liste colonnes );
: créer une table en précisant la liste des colonnes séparées par des virgules, la syntaxe de chaque colonne est :
nom type options
: nom est le nom de la colonne, type est le type de données, les options peuvent être :
NOT NULL
pour obliger a affecter une valeur qui peut être nulle.PRIMARY KEY
pour une clé primaire avec le type INTEGER
pour certaines bases, cette option peut être suivie de AUTOINCREMENT
pour une gestion automatique de la clé primaireFOREIGN KEY (nom) REFERENCES nomtable (cle primaire dans nomtable)
pour spécifier que la colonne nom est une clé étrangère reliée à la clé primaire de la table nomtableSELECT liste colonnes FROM nomtable;
: lit les valeurs des colonnes (séparées par une virgule) de la table nomtable, pour lire toutes les colonnes, on peut utiliser le caractère jocker *SELECT liste colonnes FROM nomtable WHERE condition;
: même lecture mais en ajoutant un filtre pour la sélection des rangées, la condition peut être :
nomcolonne operateur valeur
pour afficher les rangées en respectant cette condition, il peut y avoir plusieurs conditions relièées par des opérateurs logiques.limit debut,nombre
avec debut qui le numéro de la première rangée à lire et nombre le nombre de rangées.SELECT liste colonnes FROM nomtable GROUP by nomcolonne;
: même lecture mais en n'affichant une seule rangée par valeur de la colonne nomcolonneINSERT INTO nomtable (liste des noms de colonnes) VALUES (liste des valeurs);
ajoute en fin de table les valeurs spécifiés.UPDATE nomtable SET nomcolonne=valeur WHERE condition;
: modifie une valeur d'une colonne dans une rangée spécifiée par condition.DELETE FROM nomtable WHERE condition;
: supprime une ou plusieurs rangées qui respectent la condition, s'il n'y a pas de condition, tout le contenu de la table est suppriméDROP TABLE nomtable;
: supprime la table et tout ce qu'elle contient.CREATE TABLE mesures ( idmesure INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, date datetime NOT NULL, valeur FLOAT NOT NULL, idobjet INT(11) NOT NULL , FOREIGN KEY (idobjet) REFERENCES objets (idobjet) );
La table mesure comprend une clé primaire idmesure qui est incrementée à chaque ajout de rangée
valeur correspond à la valeur fournie par le capteur représenté par idobjet à la date enregistrée dans date
idobjet est une clé étrangère vers la clé primaire de la table objets
On utilise la librairie sqlite, il faut inclure le fichier de définitions sqlite3.h et ajouter sqlite3 à l'édition de lien.
L'accès à la base en appliquant les étapes suivantes :
sqlite3_open(fichierbase,fd_sql)
avec fichierbase qui est le fichier représentant la base de données, et fd_sql qui est l'adresse d'un pointeur vers une variable de type sqlite3
, la valeur de retour est un code d'erreur qui vaut SQLITE_OK
si tout s'est bien passésqlite3_exec(sql_fd, requete_sql, traitementresultat, 0, &erreursMSG)
avec sql_fd qui est un pointeur vers la structure sqlite3
, requete_sql qui est une chaîne de caractères qui contient la requête sql, traitementresultat qui pointe vers une fonction qui traite les résultats de la requête si cela est nécessaire, et erreurMSG qui contient le message d'erreur si celui-ci existe; la valeur de retour est un code d'erreur qui vaut SQLITE_OK
si tout s'est bien passésqlite3_close(sql_fd)
avec sql_fd qui est un pointeur vers la structure sqlite3
Le programme permet uniquement d'ajouter des mesures à la table mesures et de lire son contenu.
Fichier basecapteurs.h
#ifndef __BASECAPTEURS_H #define __BASECAPTEURS_H #include <sqlite3.h> typedef struct sMesure { char dateheure[30] ; float valeur; int idobjet; } t_mesure; typedef t_mesure * ptr_mesure; char *datemaintenant(char *); int ajouteDonnees(sqlite3 *,t_mesure ); int lireDonnees(sqlite3 *,int (*)(void*,int,char**,char**)); #endif
La fonction datemaintenant retourne la date courante sous la forme d'une chaîne de caractères compatible avec le format datetime de la base de données.
La fonction ajouteDonnees ajoute une mesure à la table mesures en utilisant la requête sql insert.
La fonction LireDonnees effectue la lecture complète de la table mesure, le traitement du résultat est confié à la fonction transmise en paramètres.
Fichier basecapteurs.c
#include <time.h> #include <stdlib.h> #include <stdio.h> #include "basecapteurs.h" char *datemaintenant(char *dateheure) { time_t temps ; struct tm *sdateheure; temps= time(NULL); sdateheure = localtime(&temps); strftime(dateheure,30,"%Y-%m-%d %H:%M:%S",sdateheure); return dateheure; } int ajouteDonnees(sqlite3 *sql_fd,t_mesure mesure) { char sql[1024]; char *erreursMSG; sprintf(sql,"insert into mesures (date,valeur,idobjet) values ( \"%s\" , %.2f , %d );",mesure.dateheure,mesure.valeur,mesure.idobjet); int erreur = sqlite3_exec(sql_fd, sql, NULL, 0, &erreursMSG); if (erreur != SQLITE_OK) { fprintf(stderr,"Erreur %d %s\n",erreur,erreursMSG); sqlite3_free(erreursMSG); } return erreur; } int lireDonnees(sqlite3 *sql_fd,int (*traiteresultat)(void*,int,char**,char**)) { char *erreursMSG; int erreur = sqlite3_exec(sql_fd, "select * from mesures", traiteresultat, 0, &erreursMSG); if (erreur != SQLITE_OK) { fprintf(stderr,"Erreur %d %s\n",erreur,erreursMSG); sqlite3_free(erreursMSG); } return erreur; }
Fichier testcapteurs.c
#include <stdlib.h> #include <stdio.h> #include <sqlite3.h> #include "basecapteurs.h" static int traitement(void *NonUtilise, int nbcols, char **colonne, char **NomCol) { for(int i = 0; i<nbcols; i++) { printf("%s = %s\n", NomCol[i], colonne[i] ? colonne[i] : "NULL"); } printf("\n"); return 0; } int main(int argc, char **argv) { sqlite3 *sql_fd; t_mesure mesure; int erreur ; erreur = sqlite3_open("objetsconnectes.db",&sql_fd); if( erreur ) { fprintf(stderr,"Erreur %d\n",erreur); return EXIT_FAILURE; } if (argc != 6) { erreur = lireDonnees(sql_fd,traitement); if (erreur != SQLITE_OK) { return EXIT_FAILURE; } } else { printf("ajout de mesures\n"); datemaintenant(mesure.dateheure); for(int i=0;i<5;i+=1) { mesure.valeur = strtof(argv[i+1],NULL); mesure.idobjet = i+1; printf("ajoute mesure %f pour l'objet %d\n",mesure.valeur,mesure.idobjet); erreur = ajouteDonnees(sql_fd,mesure); if (erreur != SQLITE_OK) { return EXIT_FAILURE; } } } sqlite3_close(sql_fd); return EXIT_SUCCESS; }
Le programme de test affiche le contenu de la table mesures ou bien ajoute les valeurs des 5 valeurs des capteurs qui sont : la température,l'humidité, la pression, la luminosité,la valeur des UVs.
Ces 5 valeurs correspondent aux 5 objets de la table objets.
On utilise la librairie sqlite3
L'accès à la base en appliquant les étapes suivantes :
sqlite3.connect(fichierbase)
qui retourne un objet objetconnexion
qui sera utilisé par les autres fonctionsobjetconnexion.cursor()
qui retourne un objet cursor
qui permet d'effectuer les requêtes sqlobjetcurseur.execute(sql)
qui exécute la requête sql, et retourne un objet dont le type dépend du résultat attendu.objetcurseur.close()
libère l'objet cursor
objetconnexion.commit()
qui termine les requêtes en attente, il n'est pas nécessaire si l'option autocommit est activée.objetconnexion.close()
libère l'objet connection
Le programme permet uniquement d'ajouter des mesures à la table mesures et de lire son contenu.
Fichier basecapteurs.py
import sqlite3 from datetime import datetime def afficheresultats(resultats): for rangee in resultats: ligne = "" for colonne in rangee: ligne = ligne + "\t" + str(colonne) print(ligne) def main(args): connexiondb = sqlite3.connect("objetsconnectes.db") curseur = connexiondb.cursor() nbargs = len(args) if nbargs != 6 : res=curseur.execute("select * from mesures") resultats = res.fetchall() afficheresultats(resultats) else: date = datetime.now() chdate = date.strftime("%Y-%m-%d %H:%M:%S") for i in range(5): valeur=args[i+1] idobjet = i + 1 sql = f"insert into mesures ( date, valeur , idobjet ) values ( \"{chdate}\" , {valeur} , {idobjet} )" curseur.execute(sql) curseur.close() connexiondb.commit() connexiondb.close() return 0 if __name__ == '__main__': import sys sys.exit(main(sys.argv))
Comme pour le programme C si on transmets 5 paramètres on ajoute ces valeurs dans la table mesures, sinon on lite le contenu de la table mesures.
Lors de la lecture l'objet retourné est une liste de liste. On construit une chaîne de caractères par ligne, que l'on affiche ensuite.
Dédié à la domotique, Domoticz est un serveur HTTP, une base de données, un client MQTT, le tout administré à distance à l'aide d'une interface web.Il est multiplate-forme (linux,windows, raspberry pi).
La connexion distante se fait avec l'URL http://ip:port/, où l'ip est l'adresse IP de la machine sur laquelle est installée la machine et le numéro de port est 8080 pour le protocole http. Il est également possible d'utiliser le protocole HTTPS avec le numéro de port 443 à condition qu'il n'y ait pas d'autre serveur web sur la machine qui utilise déjà ce port. De plus le port 443 n'est utilisable qu'en mode root. Il est également possible de modifier le numéro de port HTTPS.
${ROOTDOMOTICZ}/domoticz -sslwww nouveauport -sslcert ${ROOTDOMOTICZ}/server_cert.pem
Le premier accès se fait en mode administrateur avec le login et mot de passe par défaut, fournis sur le site de domoticz.
La première étape consiste à choisir la langue et saisir les coordonnées GPS. Ensuite il faut créer un utilisateur qui sera utilisé par les objets distants pour transmettre les données, mais également pour accéder aux données des capteurs.
Ensuite il faut ajouter un utilisateur qui sera utilisé par les objets qui transmettent les données, et pour accéder aux données sans utiliser le mode administrateur. Cela se fait avec le menu configuration->users : saisir un nom, un mot de passe, les droits utilisateurs puis ajouter.
Maintenant le système est prêt pour l'enregistrement des capteurs et actionneurs. On peut, par exemple, choisir une interface matérielle de type Dummy puis lui ajouter des capteurs virtuels (température, humidité, ...).
La transmission des données se fait avec une URL HTTP qui, pour un capteur, est de la forme :
https://ip:port/json.htm?type=command¶m=udevice&idx=numero&nvalue=nombre&svalue=chaine
La réponse se fait au format JSON (présenté en début de cette page), quand tout s'est bien passe elle est
{ "status": "OK", "title": "Update Device" }
Ce logiciel permet d'utiliser MQTT en souscrivant, par exemple, un abonnement auprès du serveur MQTT. Pour cela il faut ajouter un matériel MQTT Client Gateway with LAN interface, l'arborescence par défaut est domoticz/in pour les données entrantes (capteurs). Il faut également préciser l'adresse disante qui peut être localhost si les serveurs MQTT et Domoticz sont situés sur la même machine.