Guillaume Rivière 2016 – 2024

Le logo de la CCI Bayonne Pays Basque

Programmation Orientée Objet en Java

TP – Cas d'étude : Internet of Things

BUTS PÉDAGOGIQUES

  • Revoir l'écriture de classes et l'implémentation d'interfaces en Java
  • Revoir les notions d'agrégation, d'héritage et de polymorphisme
  • Lecture et écriture dans des fichiers textes
  • Connexion au SGBD MySQL avec JDBC
  • Communication réseau avec une socket
  • Apprendre à s'appuyer sur une API et à utiliser sa documentation

Le contexte du cas d'étude traité dans ce TP est l'internet des objets. Aujourd'hui, le Web et les services Internet sont alimentés par les utilisateurs. Demain, ils seront alimentés par les objets connectés. Montres, chaussures… réfrigérateurs, plantes vertes… entreprises, entrepôts, production… la croissance des années 2010 atteint plusieurs dizaines de milliards d'objets connectés (soit beaucoup plus que les quelques milliards de smartphones…).

Le nombre d'objets connectés devrait passer de quelques milliards en 2010 à plusieurs dizaines de milliards en 2020, alors que les ordinateurs et les smartphones resteront à quelques milliards.
Figure 0.1 : Graphique de l'évolution du nombre d'objet connecté.

Par exemple, le ICEdot Crash Sensor, qui est déjà commercialisé, se fixe sur un casque de vélo. En cas de choc, détecté par son gyroscope et son accéléromètre, ICEdot Crash Sensor envoi un signal au smartphone du cycliste. Un compte à rebours est lancé… si le cycliste ne désactive pas le compte à rebours, alors les secours sont prévenus :

Le petit boîtier jaune et rond de ICEdot Crash Sensor est attaché à l'arrière d'un casque de vélo. Un smarphone affiche un compte à rebours de 30 secondes et un bouton glissière pour pouvoir arrêter le décompte.
Figure 0.2 : Le produit ICEdot Crash Sensor.

Les objets connectés ne fonctionnent pas seuls. Leurs capacités de stockage et de calcul restent très limitées et toute une infrastructure distante vient en appui. Au sein de cette infrastructure, les plateformes centrales reçoivent les données des objets connectés et mettent ces données à disposition des services qu'elles hébergent. Les données massives produites par les objets connectés sont traitées et utilisées par des logiciels spécifiques (p. ex. Big Data, Machine Learning, Business Intelligence). Dans ce TP, nous allons nous focaliser sur la plateforme centrale de l'Internet des objets :

Sur ce schéma, les objets connectés communiquent localement entre eux et avec une Gateway, par Bluetooth, Bluetooth Low Energy, Infrarouge, WiFi ou radiofréquence. Cette Gateway peut être un Smartphone ou une HomeBox. Ensuite, la Gateway communique avec une plateforme centrale IoT par 3G/4G/WiFi ou par fibre ethernet. Les objets connectés peuvent éventuellement communiquer directement avec la plateforme centrale IoT par 2G, Sigfox ou LoRa. La plateforme centrale IoT stocke les données dans une base de données (données structurées) et dans des fichiers journalisés (données semi-structurées). Ces données brutes peuvent être exploitées par des modules de Business Intelligence, de Big Data et de Machine Learning.
Figure 0.3 : Contexte de l'internet des objets.

Votre travail, dans ce TP, est de développer la plateforme centrale avec le langage Java et son API. La plateforme recevra des datagrammes de données (censés provenir d'objets connectés) et les empilera dans des fichiers de journalisation (données semi-structurées). Ce travail a été décomposé en cinq étapes de développement, ce qui correspond aux cinq versions successives du programme qui suivent. Ci-dessous une illustration du programme que vous allez obtenir :

La classe principale Run est appelée depuis un terminal avec avec la machine virtuelle Java. Le programme affiche un message de bienvenue, puis des messages d'attente et des message qui avertissent des écrires dans les fichiers de journalisation.
Figure 0.4 : Illustration de l'exécution du programme de plateforme IoT.

Exercice 1 • Développer les classes principales (Version 1)

L'objectif est de créer les classes principales de la plateforme IoT pour tester une première version simple (qui sera complétée dans les versions suivantes). Les datagrammes de données seront saisis depuis le clavier par l'utilisateur et écrits dans des fichiers par la plateforme.

Le macro-diagramme de classe (sans les méthodes, ni les attributs), des cinq classes et de l'interface, de la Version 1, est illustré ci-après :

Les cinq classes et l'interface de la version 1, reliées par quatre agrégations, un lien et une implémentation.
Figure 1.1 : Macro-diagramme de classe de la Version 1.

Pour mieux comprendre le programme qui va être écrit, ci-dessous le macro-diagramme de séquence d'un scénario nominal avec deux Thing, deux Services et l'envoi de deux datagrammes. Cliquez pour afficher le diagramme de séquence correspondant.

Chronologie des pincipaux envois de messages, entre sept objets de la version 1 et l'utilisateur.
Figure 1.2-a : Macro-diagramme de séquence de la Version 1.
Question 1.1 : La classe Thing

Pour écrire la classe Thing, nous allons utiliser deux structures de données (très utiles) de l'API Java : les HashMap et les ArrayList.

L'utilisation des ArrayList est très proche des tableaux que vous connaissez déjà (mais qui eux sont de taille fixe). Il s'agit de stocker des données et d'y accéder par des indices entiers. En revanche, les ArrayList implémentent de nombreuses interfaces de l'API Java (Iterable<E>, Collection<E>, List<E>, RandomAccess) et offrent de nombreuses méthodes pour les manipuler. Vous allez utiliser cette structure de données pour référencer les services souscrits par la classe Thing.

La structure d'un tableau qui stocke consécutivement les références de quatre objets de la classe String.
Figure 1.1.1 : Illustration des array.
La structure de quatre cellules d'un ArrayList qui stockent chacune un objet de la classe String. Puis, la deuxième cellule et sont objet sont supprimées par un appel à remove(2)
Figure 1.1.2 : Illustration des ArrayList.

Les HashMap servent toujours à stocker des données, mais l'indexation est différente. Il est, par exemple, possible d'indexer les données avec des chaînes de caractères (clés). Alors, pour accéder à un élément, il suffit de donner la clé pour directement obtenir la valeur recherchée. Ce genre d'association clé ⇒ valeur permet de retrouver rapidement une valeur et évite les nombreux if qu'il aurait fallu écrire en utilisant des tableaux. De plus, cette indexation par clé est indifférente au changement de position de la valeur, alors que dans un ArrayList la valeur de l'index peut par exemple changer suite à une insertion ou une suppression. Vous allez utiliser cette structure de données pour associer, dans la classe Thing, la clé d'une donnée (qui sera une chaîne de caractères de trois lettres) et sa valeur (qui sera une chaîne de caractères).

Cliquez pour afficher les détails de cette structure de données.
Illustration d'un HashMap où quatre objets de classe String sont indexés par quatre clés. Puis, le deuxième objet est supprimé par un appel à remove() en spécifiant sa clé.
Figure 1.1.3-a : Illustration des HashMap.

Le diagramme UML de la classe Thing est le suivant :

Les 4 attributs et les 11 méthodes de la classe Thing.
Figure 1.1.4 : Diagramme de la classe Thing.

Créez la classe Thing, ses attributs et ses méthodes. Quelques remarques d'implantation :

Question 1.2 : La classe Run
La méthode de la classe Run.
Figure 1.2.1 : Diagramme de la classe Run.

Écrivez la classe Run :

  1. Instanciez un objet t1 de classe Thing, avec comme adresse MAC "f0:de:f1:39:7f:17" et comme id utilisateur "1".
  2. Pour plus de convivialité, affichez le message "Welcome on IoT central platform." au début du programme et "bye." à la fin du programme.
  3. Appelez la méthode setFromDatagram() de t1 avec ce datagramme :
    "geo 43.433331 -1.58333;pul 128;bat 90.0"
  4. Affichez l'objet t1 grâce à sa méthode toString() et un appel à System.out.println().

Bien entendu, compilez et testez l'exécution. (Si besoin, créez une classe Service vide. Elle sera complétée à la question suivante.)

Question 1.3 : La classe Service
Les 2 attributs et les 3 méthodes de la classe Service.
Figure 1.3.1 : Diagramme de la classe Service.

Rappel de notation : le symbole croisillon « # » indique le niveau de visibilité protected.

Écrivez la classe Service. Elle possède un attribut de la classe PrintWriter qui permet d'écrire dans des fichiers. Quelques remarques d'implantation :

  1. Parmi les choses à faire dans le constructeur, il faut notamment initialiser l'attribut pw qui permettra d'écrire dans le fichier de log associé au service (c.-à-d. un fichier journal ou encore fichier de trace). Pour ouvrir un fichier, il faut procéder en plusieurs étapes :
    1. Créer un FileWriter en donnant un nom de fichier. Par contre, attention, le nom du fichier dans lequel le service écrira sera composé de "log_", suivit du nom du service, puis termine avec l'extension ".txt".
        FileWriter fw = new FileWriter ("nom_du_fichier_a_ecrire.txt", true); // true pour ecrire a la fin si fichier existe deja
    2. Ensuite, créer un BufferedWriter à partir du FileWriter :
        BufferedWriter bw = new BufferedWriter (fw);
    3. Enfin, créer le PrintWriter à partir du BufferedWriter :
        this.pw = new PrintWriter (bw);
  2. La méthode writeData() ajoute une ligne dans le fichier (qui aura été ouvert par le constructeur). Pour écrire dans le fichier, il suffit d'appeler la méthode println() de l'attribut pw (qui aura été initialisé par le constructeur).
    Chaque ligne du fichier commencera avec la date, suivit d'un ";" puis la description de l'objet thing obtenue avec sa méthode toString(). Pour formater la date sous forme d'une chaîne de caractères, faîtes comme suit :
        Date now = new Date () ;
        SimpleDateFormat formater = new SimpleDateFormat ("yyyy-MM-dd H:m:s");
        String d = formater.format (now);
    Une fois la ligne écrite, il faut synchroniser les entrées-sorties (qui sont temporisées par un tampon, ou buffer) en appelant la méthode flush() de la classe PrintWriter sur l'attribut pw.

    Littéralement, le mot flush veut dire « tirer la chasse d'eau ». En jargon informatique, le verbe flusher veut dire demander de « nettoyer » ou « vider » une ressource intermédiaire (tampon, cache…).

  3. Enfin, la méthode close() ferme le fichier. Pour ce faire, elle flushe le fichier, puis fait appel à la méthode close() de l'attribut pw.

Une fois la classe terminée, revenez dans la classe Run :

  1. Instanciez un objet s1 de classe Service en l'appelant « mon_service »
  2. Demandez à cet objet s1 d'écrire les données de t1 avec sa méthode writeData().
  3. N'oubliez pas d'appeler la méthode close() de s1 avant de terminer le programme.

Bien entendu, compilez et testez l'exécution !

Question 1.4 : La méthode update() de la classe Thing

Nous allons ajouter une nouvelle méthode à la classe Thing : void update (). Son rôle est d'appeler la méthode writeData() de chaque service souscrit pour que les données de l'objet soit transmises à chaque service. Aide :

Vous avez deux principales manières de procéder pour parcourir le ArrayList :

1. Soit en parcourant les indices :
    /* Parcours ArrayList avec les indices */
    for (int i=0 ; i < this.arrServices.size() ; i++) {
        Service service = this.arrServices.get(i) ;
    }

2/ Soit en utilisant un itérateur :
    /* Parcours ArrayList avec iterateur */
    Iterator<Service> it = this.arrServices.iterator();
    while (it.hasNext()) {
        Service service = it.next() ;
    }
  • Lors de son initialisation l'itérateur est positionné au début du HashMap.
  • L'appel it.hasNext() retourne vrai si des éléments peuvent encore être lus.
  • L'appel it.next() retourne le prochain élément qui peut être lu.

Choisissez la manière que vous comprenez le mieux.

Une fois la classe terminée, revenez dans la classe Run :

  1. Créez quatre nouveaux services s2, s3, s4 et s5.
  2. Abonnez l'objet t1 à ces nouveaux services en utilisant sa méthode subscribe(). Appelez la méthode update() de t1.

Bien entendu, compilez et testez l'exécution !!!

Question 1.5 : L'interface DataReceiver et la classe KeyboardInput

Écrivez le code de l'interface DataReceiver :

Les 4 méthodes de l'interface DataReceiver.
Figure 1.5.1 : Diagramme de l'interface DataReceiver.

La classe KeyboardInput implémente l'interface DataReceiver. Le comportement de sa méthode readDatagram() sera de lire des datagrammes depuis le clavier.

L'attribut et les 5 méthodes de la classe KeyboardInput.
Figure 1.5.2 : Diagramme de la classe KeyboardInput.

Écrivez la classe KeyboardInput. Quelques remarques d'implantation :

Une fois l'écriture de la classe terminée, revenez dans la classe Run :

  1. Instanciez un objet k de classe KeyboardInput.
  2. Invoquez sa méthode open().
  3. Puis lisez un datagramme avec cet objet en utilisant sa méthode readDatagram(), et affichez le datagramme lu.
  4. Enfin, n'oubliez pas ensuite d'appeler sa méthode close().

Bien entendu, compilez et testez l'exécution.

Figure 1.5.7 : Exemple d'exécution complète.
Question 1.6 : La classe Platform
Les 2 attributs et les 5 méthodes de la classe Platform.
Figure 1.6.1 : Diagramme de la classe Platform.

Écrivez la classe Platform. Quelques remarques d'implantation :

Une fois la classe terminée, revenez dans la classe Run.

  1. Retirez l'appel à readDatagram() de l'exercice précédent.
  2. Instanciez une plateforme p. Ajoutez-lui les services s1 à s5 et l'objet connecté t1 (de la classe Thing) que vous aviez déjà créés auparavant.
  3. Invoquez l'exécution de la plateforme p avec sa méthode run(), méthode à laquelle vous donnerez en paramètre l'objet k de classe KeyboardInput précédemment créé.

Bien entendu, compilez et testez l'exécution. Par exemple, saisissez plusieurs datagrammes (par copier-coller) pour l'objet connecté d'adresse MAC "f0:de:f1:39:7f:17" (c.-à-d. t1) signifiant la position géographique (geo), le pouls (pul) et le niveau de batterie (bat) mesurés par l'objet connecté :

Enfin, saisissez « quit » pour arrêter la saisie.

Regardez sur le disque si les fichiers journaux ont bien été créés.

Maintenant que la Version 1 est en place, nous allons la faire évoluer et la transformer. Grâce à la puissance de l'Orienté Objet, cela ne va demander que peu d'efforts.

Exercice 2 • Lire depuis un fichier (Version 2)

Dans cette nouvelle version, les données seront envoyées à partir de la lecture du fichier simu.txt. Le but est de faciliter les développements en aidant à automatiser les phases de test (plutôt que de copier-coller les datagrammes manuellement…). Grâce à la puissance de l'Orienté Objet, nous pouvons faire cela très simplement : créer une nouvelle classe FileReader qui implémente la classe DataReceiver et la substituer au KeyboardInput de la Version 1.

Le macro-diagramme de classe, avec la nouvelle classe FileReader, de la Version 2 est illustré ci-dessous :

Les cinq classes et l'interface de la version 1, plus la nouvelle classe de la version 2 qui implémente aussi l'interface
Figure 2.1 : Macro-diagramme de classe de la Version 2.

Ci-dessous le macro-diagramme de séquence d'un scénario nominal (avec deux Thing, deux Services et l'envoi de deux datagrammes) qui montre l'utilisation de la nouvelle classe FileReader. Remarquez que rien n'a changé par rapport à la Version 1, sauf au niveau de la lecture des datagrammes. Cliquez pour afficher le diagramme de séquence correspondant.

Chronologie des pincipaux envois de messages, entre sept objets de la version 2.
Figure 2.2-a : Macro-diagramme de séquence de la Version 2.
Question 2.1 : La classe FileReader

Téléchargez le fichier de datagrammes simu.txt sur votre disque dans le répertoire de votre projet Eclipse, à côté des fichiers de configuration « .classpath » et « .project » (c.-à-d. au-dessus des répertoires bin/ et src/). Ouvrez-le avec Notepad++ et observez la composition des lignes. Nous allons écrire la classe FileReader de sorte qu'elle lise une nouvelle ligne dans un fichier de datagrammes à chaque appel de la méthode readDatagram().

Les 3 attributs et les 5 méthodes de la classe FileReader.
Figure 2.1.1 : Diagramme de la classe FileReader.

Écrivez la classe FileReader. Quelques remarques d'implantation :

Une fois la classe terminée, revenez dans la classe Run. Créez un nouvel objet f de classe FileReader et utilisez-le pour remplacer le KeyboardInput utilisé précédemment lors de l'appel à la méthode run() de la plateforme p.

Compilez et testez l'exécution.

Nous avons donc complètement changé l'utilisation du programme et ce avec un minimum de modifications. Surtout, il suffira juste de « rebrancher » le KeyboardInput k sur la méthode run() pour revenir au comportement précédent (mais, ne le faîtes pas pour le moment).

Exercice 3 • Spécialiser la plateforme (Version 3)

L'objectif est de spécialiser les classes Service et Thing pour que notre plateforme IoT puisse proposer des fonctionnalités différentes selon le type de service ou le type d'objet connecté. Nous allons illustrer avec deux services qui commencent à être de plus en plus répandus et créer les deux classes SmartHome (pour la domotique) et QuantifiedSelf (pour la mesure de soi). Ces services eux-mêmes pourraient être encore spécialisés, ou encore d'autres services pourraient être dérivés de la classe Service. Les fonctionnalités de ces classes dérivées pourraient également être complétées (envoi d'e-mail, de sms, calcul, alertes…). Les objets connectés aussi pourraient être spécialisés par de nombreuses manières, mais nous allons simplement créer une classe ThingTempo dérivée de la classe Thing.

Le macro-diagramme de classe de la Version 3 avec les trois nouvelles classes spécialisées est illustré ci-dessous :

Les six classes et l'interface des versions 1 et 2, plus trois nouvelles classes filles et trois lien d'héritage
Figure 3.1 : Macro-diagramme de classe de la Version 2.
Question 3.1 : La classe SmartHome

Nous considérons que pour pouvoir être enregistrées dans les journaux, les données de domotiques doivent définir l'état (allumé ou éteint) de l'objet connecté. La clé de trois lettres associée à ces données sera "sta" (pour state).

Pour ce faire, la classe SmartHome étend la classe Service en complétant le code de la méthode writeData(). Le nouveau comportement sera d'écrire dans le fichier journal seulement si l'objet thing comporte une donnée de clé "sta".

Les 2 méthodes de la classe SmartHome.
Figure 3.1.1 : Diagramme de la classe SmartHome.

Écrivez la classe SmartHome.

Une fois la classe terminée, revenez dans la classe Run :

  1. Créez un nouveau service sh de classe SmartHome portant le nom « myKWHome ».
  2. Ajoutez-le à la plateforme p.
  3. Utilisez le FileReader f comme DataReceiver de la méthode run() de la plateforme.

Compilez et testez l'exécution.

Question 3.2 : La classe QuantifiedSelf

Nous considérons que pour pouvoir être enregistrées dans les journaux, les données de mesure de soi doivent aussi renseigner la position géographique de l'objet connecté. La clé de trois lettres associée à ces données sera "geo". De plus, les données des services QuantifiedSelf ne seront pas journalisées avec la date, mais avec une mesure de temps en millisecondes : le nombre de millisecondes écoulées depuis le début de l'ère Unix (c.-à-d. le nombre de secondes écoulées depuis le 1er janvier 1970 à 00h00.00 UTC).

En parallèle, un axe calendaire démarrant au 1/1/1970 et un axe temporel en millisecondes démmarant à 0. La date du 15-10-2015 à 16h30.00 UTC+2 est affichée sur le premier axe et la valeur correspondante 1508077920000 est présentée sur le deuxième axe.
Figure 3.2.1 : Axe du temps en millisecondes écoulées depuis le début de l'ère Unix
(exemple depuis la France en heure d'été, avec le fuseau horaire HAEC (UTC+2)).

Pour ce faire, la classe QuantifiedSelf étend la classe Service en substituant le code de la méthode writeData(). Le nouveau comportement sera d'écrire, lorsque l'objet thing comporte une donnée de clé "geo", dans le fichier journal avec l'attribut hérité pw et en récupérant le temps en millisecondes grâce à la méthode getTime() de la classe Date comme ceci :
    Date now = new Date() ;
    long time = now.getTime() ;

Les 2 méthodes de la classe QuantifiedSelf.
Figure 3.2.2 : Diagramme de la classe QuantifiedSelf.

Écrivez la classe QuantifiedSelf.

Une fois la classe terminée, revenez dans la classe Run :

  1. Créez un nouveau service qs de classe QuantifiedSelf portant le nom « RUNstats ».
  2. Ajoutez-le à la plateforme p.
  3. Utilisez le FileReader f comme DataReceiver de la méthode run() de la plateforme.

Compilez et testez l'exécution.

Question 3.3 : La classe ThingTempo

Certains objets connectés envoient plus de données que souhaité. Nous voulons temporiser avec la classe ThingTempo en instaurant un délai qui doit être écoulé avant d'envoyer de nouvelles données aux services souscrits.

Pour ce faire, la classe ThingTempo étends la classe Thing et complète le code de la méthode update(), en appelant le code de la classe mère seulement si le délai est écoulé depuis le dernier update() :

Axe temporel en millisecondes, lorsque le délai n'est pas écoulé et que : now ≤ lastUpdate + delay. Axe temporel en millisecondes, lorsque le délai est écoulé  et que : now > lastUpdate + delay.
Figure 3.3.1 : Schéma de l'axe du temps.

Ce délai (en secondes) est donné en paramètre du constructeur de la classe ThingTempo (en plus du nom du service et de l'id utilisateur).

Les 2 attributs et les 2 méthodes de la classe ThingTempo.
Figure 3.3.2 : Diagramme de la classe ThingTempo.

Écrivez la classe ThingTempo.

Une fois la classe terminée, revenez dans la classe Run :

  1. Instanciez un objet t2 de classe ThingTempo avec comme adresse MAC "f0:de:f1:39:7f:18", 1 comme id utilisateur et 60 comme délai en secondes.
  2. Ajoutez-le à la plateforme p.
  3. Abonnez-le à au moins deux services de s1 à s5 (hors QuantifiedSelf et SmartHome).
  4. Pour que la saisie se fasse au clavier par l'utilisateur, remettez en place le KeyboardInput k comme DataReceiver de la méthode run() de la plateforme p.

Compilez et testez l'exécution :

Ouvrez, avec Notepad++, le fichier journal d'un des deux services auxquels vous avez abonné l'objet. À la fin du journal, vérifiez la présence des deux lignes consécutives avec 128 et 148 pulsations et surtout notez l'absence de la ligne avec 133 pulsations.

Exercice 4 • Paramétrer la plateforme depuis une base de données (Version 4)

L'objectif de cette nouvelle version est de décrire, dans une base de données MySQL, les utilisateurs, les objets connectés qu'ils possèdent et les services qu'ils utilisent.

Pour le moment, la liste des services et des objets connectés reste figée dans le code source que vous avez écrit. Pour pouvoir ajouter des nouveaux services ou de nouveaux objets connectés, il faut donc recompiler le logiciel à chaque fois (et disposer du code source et des compétences nécessaires). Cette façon de faire est plutôt irréaliste. Pour y remédier, nous allons décrire la liste des services et des objets connectés dans une base de données. De nouveaux services pourront alors être ajoutés ou supprimés par simple requête sur la base. Ci-dessous le schéma relationnel de cette base de données :

Cliquer pour voir une autre façon de dessiner
Les quatre tables de la base de données sont représentées avec les noms des champs alignés horizontalement. Trois relations de clés étrangères sont dessinées.
Figure 4.1-a : Schéma relationnel de la base de données.

Notation : les clés primaires sont soulignées, les clés étrangères sont précédées du symbole croisillon « # »

Question 4.1 : La base de données

Le fichier platform_iot.sql contient les requêtes SQL pour créer et peupler la base de données plaform_iot. (Rappel : vous aviez déjà créé et manipulé cette base de donnée en TP de SGBD en 1A)

  1. Téléchargez le fichier platform_iot.sql sur votre disque dur.
  2. Démarrez le service WAMP.
  3. Depuis phpMyAdmin, chargez la base en important le fichier plaform_iot.sql que vous venez de télécharger.
  4. Vérifiez que la base est en place. Consultez les lignes des tables :
    • Combien de services sont présents sur la plateforme ? De quels types sont-ils ?
    • Combien d'objets connectés sont présents sur plateforme ?
    • Combien d'objets connectés possède chaque utilisateur ?
    • À quels services est abonné chaque utilisateur ?
Question 4.2 : Modifier la classe Platform

Nous allons ajouter une méthode à la classe Platform. Cette nouvelle méthode loadFromDatabase() initialise la plateforme en consultant la base de données plaform_iot :

  1. La méthode loadFromDatabase() instancie de nouveaux services à partir de leur nom et de leur type, puis les ajoute à la liste des services de la plateforme avec la méthode addService(). La requête pour récupérer les ids, les noms et les types de tous les services sera :
    SELECT * FROM table_service ;
    • Si le type est la chaîne "smarthome", alors la méthode loadFromDatabase() construira un service de la classe SmartHome. Si le type est la chaîne "quantifiedself", alors elle construira un service de la classe QuantifiedSelf. Sinon, elle construira un service de la classe Service.
    • La méthode loadFromDatabase() ajoute aussi chaque service, avec son id comme clé, dans un objet mapIds (de classe HashMap<String, Service>), que vous aurez déclaré et instancié au début de la méthode. (Rappel : c'est la méthode put(K, V) qui permet d'insérer des éléments dans un HashMap)
  2. La méthode loadFromDatabase() récupère les adresses MAC, id utilisateur et types de tous les objets connectés avec la requête :
    SELECT * FROM table_thing ;
    • Elle instancie de nouveaux objets connectés à partir des adresses MAC et des types, puis les ajoute à la liste des objets connectés de la plateforme avec la méthode addThing().
      • Si le type est la chaîne "thingtempo", alors elle construit un objet connecté de la classe ThingTempo. Sinon, elle construit un objet connecté de la classe Thing.
      • Dans le cas de la classe ThingTempo, il faudra aussi récupérer le champ param pour indiquer la valeur du délai. Pour obtenir un long depuis une chaîne de caractère, utiliser la méthode statique parseLong() de la classe Long.
    • Elle fait souscrire chaque objet connecté, aux services auxquels son utilisateur est abonné, grâce à leur méthode subscribe().
      • Pour récupérer la liste des services, utiliser la requête suivante (en remplaçant l'adresse MAC par l'adresse recherchée) :
        SELECT id_service FROM table_subscribe WHERE id_user IN (SELECT id_user FROM table_thing WHERE mac='xx:xx:xx:xx:xx:xx') ;
        Attention, ne pas réutiliser le même Statement que précédemment pour faire cette requête, mais créez un nouvel objet Statement.
      • Pour retrouver l'objet de classe Service correspondant, utilisez le HashMap mapIds et sa méthode get().

Écrivez la nouvelle méthode loadFromDatabase(). Pour se connecter et requêter le SGBD MySQL depuis votre code Java, reprenez le TP sur JDBC.
Rappel : Pensez à importer le driver JDBC mysql-connector-java-5.1.40-bin.jar dans le dossier bin\ (qu'il faut d'abord créer avec New > Folder) et à l'associer aux bibliothèques (libraries) de la configuration de votre projet.

Revenez dans la classe Run :

  1. Remettez en place le FileReader f comme DataReceiver de la méthode run() de la plateforme.
  2. Mettez en commentaire toutes les lignes de déclaration d'objets connectés ou de services, les lignes d'ajout à la plateforme p et de souscription a des services.
  3. À la place, faîtes simplement un appel à la méthode loadFromDatabase() sur la plateforme p.

Supprimez tous les fichiers journaux présents sur le disque. Compilez et testez l'exécution. Vérifiez que trois fichiers journaux ont été créés et consultez leurs contenus.

Exercice 5 • Communiquer au travers du réseau (Version 5)

L'objectif est d'instaurer une communication réseau pour recevoir les données. Les données seront émises sur le réseau par un deuxième programme simulateur de passerelle (Gateway : Smartphone, Homebox…).

Les sockets permettent de faire communiquer deux programmes au travers du réseau. Nous allons utiliser ce mécanisme pour recevoir des octets provenant d'un programme distant. Pour développer cela, nous allons, dans la suite, faire deux choses :

  1. Créer une nouvelle classe SocketServer qui implémente l'interface DataReceiver. Elle remplacera les classes KeyboardInput et FileReader précédentes et permettra de recevoir des octets depuis le réseau (et non plus depuis le clavier ou un fichier).
  2. Écrire un deuxième programme de test, qui enverra des datagrammes sur le réseau, afin de pouvoir tester que tout fonctionne. Ce programme qui simulera la passerelle (Gateway) lira des datagrammes depuis un fichier (et réutilisera notre classe FileReader à cet effet) et les enverra sur le réseau grâce à la classe SocketClient.

Le macro-diagramme de classe, avec les trois nouvelles classes et la nouvelle interface à écrire, pour cette Version 5, est illustré ci-dessous :

Pour la plateforme centrale, les neuf classes et l'interface déjà existantes, plus une nouvelle classe qui implémente l'interface. Pour la passerelle, deux nouvelles classes et une nouvelle interface.
Figure 5.1 : Macro-diagramme de classe de la Version 1.

Ci-dessous le macro-diagramme de séquence d'un scénario nominal avec deux Thing, deux Services et l'envoi de deux datagrammes par le simulateur de passerelle. Cliquez pour afficher le diagramme de séquence correspondant.

Chronologie des pincipaux envois de messages, entre sept objets de la plateforme centrale IoT et trois objets de la passerelle.
Figure 5.2-a : Macro-diagramme de séquence de la Version 5.
Question 5.1 : La classe SocketServer

La classe SocketServer permet d'ouvrir une communication et d'écouter les octets arrivant sur un port réseau de la machine.

Les 4 attributs et les 5 méthodes de la classe SocketServer.
Figure 5.1.1 : Diagramme de la classe SocketServer.

Créez cette nouvelle classe dans votre projet Eclipse et copiez son code : SocketServer.java

Modifiez la classe Run et instanciez un objet s de classe SocketServer qui écoute sur le port 51291 et qui sera le DataReceiver utilisé par la méthode run() de la plateforme p.

Compilez le programme depuis le menu : Project > Build Project. Pour tester son exécution :

  1. Avec Notepad++, créer dans votre projet (à côté de « simu.txt ») un nouveau fichier Batch que vous appelerez server.bat et recopiez la ligne suivante en adaptant les chemins :
    java -classpath "D:\\chemin\\de\\votre\\projet\\lib\\mysql-connector-java-5.1.40-bin.jar;D:\\chemin\\de\\votre\\projet\\bin\\" Run
  2. Ouvrez une invite de commande,
  3. descendez dans le répertoire de votre projet Eclipse,
  4. puis exécutez votre programme serveur en invoquant : server.bat
  5. Le programme reste en attente d'une connexion client.
  6. Ne faîtes rien et laissez-le en attente.
Question 5.2 : L'interface DataSender et la classe SocketClient

L'interface DataSender décrit le comportement d'une entité capable d'écrire des datagrammes.

Les 4 méthodes de l'interface DataSender.
Figure 5.2.1 : Diagramme de l'interface DataSender.

Créez cette nouvelle interface dans votre projet Eclipse et copiez son code : DataSender.java

La classe SocketClient permet d'ouvrir une socket pour communiquer avec un programme serveur. Pour cela, elle doit connaître l'adresse IP de la machine sur laquelle le serveur s'exécute et le numéro de port sur lequel il écoute. La classe Socket permet de se connecter au serveur et de lui envoyer des datagrammes.

Les 4 attributs et les 5 méthodes de la classe SocketClient.
Figure 5.2.2 : Diagramme de la classe SocketClient.

Créez cette nouvelle classe dans votre projet Eclipse et copiez son code : SocketClient.java

Question 5.3 : La classe GatewaySimulator

La classe GatewaySimulator est la classe principale du programme de test.

La méthode de la classe GatewaySimulator.
Figure 5.3.1 : Diagramme de la classe GatewaySimulator.

Écrivez la classe GatewaySimulator et :

  1. Instanciez un objet f de classe FileReader qui lira depuis le fichier "simu.txt".
  2. Instanciez un objet s de classe SocketClient qui se connectera à la machine d'adresse IP "127.0.0.1" et écrira sur le port 51291.
  3. Appelez la méthode open() de ces deux objets.
  4. Tant que le FileReader f est prêt à lire :
    • Lire un datagramme depuis le fichier avec le FileReader f
    • Si le datagramme n'est pas null
    • alors envoyer le datagramme sur le réseau grâce au SocketClient s
  5. Appelez la méthode close() de ces deux objets.

Compilez le programme depuis le menu : Project > Build Project. Si la compilation a réussi :

Pour être sûr que tout fonctionne bien, vérifiez les dates des dernières écritures dans les fichiers journaux.

Vu que le programme serveur ne s'arrête jamais, faîtes « Ctr » + « C » pour le stopper.

Bilan : La plateforme IoT est maintenant complète. Elle charge les services et les objets depuis une base de données. Elle écoute les datagrammes provenant du réseau. Elle écrit les données reçues dans des fichiers journaux. De nombreuses autres fonctionnalités pourraient être ajoutées. Cependant, les présentes fonctionnalités vous ont déjà démontré la puissance du paradigme orienté objet, la souplesse et l'agilité offertes. Une fois l'architecture de la version 1 en place, développer le reste est très facile et rapide. Écrire la même chose avec un langage procédural serait vraiment laborieux.

Question subsidiaire : Quel langage est le plus présent parmi les offres d'emplois ? Indices : le développement des Systèmes d'Information et de Gestion des entreprises (que nous étudierons au prochain semestre) exige de nombreux programmeurs et nombreux sont les applicatifs serveurs qui utilisent le langage Java.

Pour approfondir le sujet traité, vous pouvez consulter le dossier « Cap sur les plateformes IoT » à partir duquel le présent énoncé a été construit :
  • Partie 1 : BlogFichier (Octo, septembre 2015)
  • Partie 2 : BlogFichier (Octo, octobre 2015)
  • Partie 3 : BlogFichier (Octo, février 2016)