Interface Web Interface homme machine Dans cette partie de l’activité, vous allez concevoir l’interface web de notre application. 🖮 Travail n° 1 : IHM Dans ce travail, on vous demande de concevoir une Interface Homme Machine (→ IHM) c’est-à-dire l’interface depuis laquelle un utilisateur humain pourra interagir avec l’application de supervision de la lyre d’éclairage. Rappel Dans notre cas, cette IHM est une page web servie par un serveur web intégré dans une application NodeJS via le module Express. Télécharger l’archive fournie dans le répertoire de votre application (→ ~/Documents/njs-lyreduino/). Cette archive contient l’ensemble des fichiers de l’application NodeJS à partir de laquelle vous allez pouvoir vous inspirer pour concevoir votre IHM. Exemple pi@rpi-defrance:~/Documents/njs-lyreduino $ wget https://www.lycee-benoit.tech/BTS/_defrance/dev/web101/_attachments/travail-01-initial.zip (1) 1 Copier-coller le lien de l’archive à la suite de la commande wget On peut aussi télécharger l’archive depuis son poste Windows et la transférer sur la Raspberry Pi avec le logiciel WinSCP ou l’exécutable pscp.exe de PuTTY. La désarchiver sur la Raspberry Pi avec la commande unzip travail-01-initial.zip Exécuter l’application sur la Raspberry Pi avec la commande node app.js Se rendre sur la page web servie par l’application à l’aide du navigateur internet du PC (→ https://<adresse ip rpi>:3000 ou https://<nom rpi>:3000) Étudier le code source de l’application NodeJS (→ app.js) ainsi que celui de la page web (fichiers public/index.html et public/css/style.css) Stopper l’application avec Ctrl+C. Dupliquer l’application dans un nouveau répertoire (→ exemple : my-njs-lyreduino) qui constituera le “bac à sable” dans lequel vous pourrez développer votre propre application, en dehors de ce qui vous sera proposé comme corrigé Exemple pi@rpi-defrance:~/Documents $ cp -R njs-lyreduino/ my-njs-lyreduino/ (1) 1 copie récursive de l’ensemble des fichiers et sous-répertoires du répertoire njs-lyreduino/ dans my-njs-lyreduino/ Dans ce nouveau répertoire, modifier les fichiers public/index.html et éventuellement public/style.css pour proposer une évolution de la page web selon vos connaissances en HTML/CSS de façon à disposer : d'1 slider pour la commande d’inclinaison de la lyre d’éclairage d'1 slider ou équivalent pour contrôler l’intensité lumineuse de la lyre d’éclairage Lancer votre propre application depuis le répertoire ~/Documents/my-njs-lyreduino pour apprécier le rendu de votre page web. Pour ceux qui connaissent déjà HTML/CSS/Javascript, il est possible de combiner les commandes pan & tilt de la lyre avec un contrôle tierce du genre de celui présenté sur Touch-enabled Virtual JoyStick Controller In JavaScript ou sur JoyStick ou bien encore sur nippleJS Corrigé Archive protégée par mot de passe : travail-01-corrige 🖮 Travail n° 2 : Interception évènements Dans ce travail, on vous demande de faire évoluer votre interface web de façon à pouvoir à intercepter par programme les actions de l’utilisateur sur cette dernière (→ déplacement des sliders) Désarchiver la proposition de corrigé au travail n°1 dans ~/Documents/njs-lyreduino Ajouter la ligne suivante dans l’entête du fichier index.html index.html <head> <!-- [...] --> (1) <script src="./js/client.js" defer></script> (2) </head> 1 Commentaire qui représente le contenu actuel de votre entête HTML (→ balise head) 2 Ligne à ajouter à l’entête actuel Créer un fichier client.js dans le répertoire public/js/ de l’application et y ajouter le contenu suivant : client.js // IIFE (Immediately Invoqued Function Expression) => crée un scope local // inaccessible "globalement". (function() { var panSlider = document.getElementById('pan'); panSlider.addEventListener("input", function(e) { console.log("evt : " + e.type + " -> elt type : " + e.target.type + " / elt id : " + e.target.id ); (1) console.log("id : " + panSlider.id + " / value : " + panSlider.value); (2) }, false ); }()); 1 Affiche des informations sur le type de l’évènement (→ input) ainsi que le type (→ range) et l’id (→ pan) de l’élément de la page web sur lequel l’évènement s’est produit 2 Affiche l’id et la valeur courante du slider Actualiser la page web dans votre navigateur (→ touche F5) Ouvrir les outils de développement web et constater dans la console ce qui se passe lorsque vous déplacez le slider associé à la commande de rotation de la lyre Appliquer les mêmes modifications dans votre propre application (→ fichiers du répertoire my-njs-lyreduino/) et modifier le fichier public/js/client.js pour permettre d’afficher les valeurs des 2 autres sliders. Corrigé Archive protégée par mot de passe : travail-02-corrige Communication des évènements de l’IHM à l’application NodeJS Dans cette partie de l’activité, vous allez faire en sorte de notifier les actions de l’utilisateur réalisées au niveau de l’IHM (→ page web affichée sur le navigateur du PC) à l’application NodeJS (→ programme s’éxécutant sur la Raspberry Pi) de façon à ce que cette dernière puisse élaborer les ordres à envoyer à la carte Arduino Uno R3 pour piloter les servomoteurs et le shield “Neopixel” . Ceci est réalisable à l’aide de webSockets. webSockets Les webSockets sont des connexions entre clients et serveurs web. Les connexions webSocket sont des flux de données, dans lesquels le premier octet envoyé dans le flux à une extrémité est le premier octet lu à l’autre extrémité. Les flux de données sont des structures de programmation courantes et vous les verrez utilisés à de nombreux endroits. Ils relient les programmes aux fichiers de votre ordinateur, les programmes clients aux programmes serveurs, les programmes de bureau aux ports réseau… Dans NodeJS plusieurs modules existent pour mettre en œuvre les webSockets. Celui que vous allez utiliser se nomme socket.io ws est un autre module populaire pour utiliser les webSockets sous NodeJS. 🖮 Travail n° 3 Mise en œuvre de socket.io Désarchiver la proposition de corrigé au travail n°2 dans ~/Documents/njs-lyreduino Installer le module socket.io Exemple pi@rpi-defrance:~/Documents/njs-lyreduino $ npm install socket.io --save Ajouter à la fin du fichier app.js le code suivant : app.js const io = require('socket.io')(server); (1) io.on('connection', function(socket) { (2) socket.on('join', function(data) { (3) console.log(">> " + data + " <<" ); }); socket.on('pantilt', function(data) { (3) console.log("Slider " + data.id.toString().toUpperCase() + " -> " + data.val ); }); }); 1 importation du module socket.io 2 Bloc de code exécuté lors de la connexion à l’application depuis une page web via une webSocket 3 Mise en place des gestionnaires d’évènements propres à l’application (Ex. : pantilt→ nom de l’évènement envoyé par la page web lorsqu’un des sliders pilotant les servomoteurs est modifié) Modifier le fichier public/index.html pour y include la référence au script socket.io qui gère le coté client des webSockets public/index.html <head> <script src="/socket.io/socket.io.js" defer></script> (1) <script src="./js/client.js" defer></script> </head> 1 partie cliente du module socket.io. À insérer obligatoirement avant le script client.js Modifier le fichier public/js/client.js pour y ajouter les blocs numérotés dans le listing ci-dessous public/js/client.js // [...] var socket = window.io.connect(window.location.hostname + ':' + 3000); (1) // [...] for(let elt of servoSliders) { elt.addEventListener("input", function(e) { console.log("servo id : " + e.target.id + " / servo value : " + e.target.value); socket.emit('pantilt', { (2) id: e.target.id, val: e.target.value }); }, false ); }; socket.on('connect', function() { (3) socket.emit('join', 'Client connecté !'); }); 1 Déclaration et initialisation d’une webSocket vers l’application NodeJS 2 Envoi de l’évènement identifié “pantilt” sur modification des sliders liés à la commande des servomoteurs. Le contenu de cet évènement est un objet Javascript contenant d’une part l’identifiant HTML du slider modifié et d’autre part la valeur de ce slider 3 Gestionnaire d’évènement appelé automatiquement dès que la liaison par webSocket avec l’application NodeJS est établie. Celui-ci se contente ici d’envoyer un évènement identifié “join” dont le contenu est une chaîne de caractères (→ Client connecté !) Exécuter l’application pour vérifier que les actions sur les sliders sont bien notifiées à l’application NodeJS. Illustration du résultat attendu pi@rpi-defrance:~/Documents/njs-lyreduino $ node app.js Server listening on https://localhost:3000 >> Client connecté ! << Slider PAN -> 187 Slider TILT -> 87 Modifier l’application NodeJS pour afficher 2 messages différents selon le slider actionné Exemple de résultat attendu pi@rpi-defrance:~/Documents/njs-lyreduino $ node app.js Server listening on https://localhost:3000 >> Client connecté ! << Rotation requise -> 79 (1) Inclinaison requise -> 197 (2) 1 Message pour slider “pan” actionné 2 Message pour slider “tilt” actionné Sur la base du code que vous venez de mettre œuvre, faire en sorte d’afficher dans l’application NodeJS un message indiquant la valeur du slider de contrôle de la luminosité lorsque celui-ci est actionné. Résultat attendu pi@rpi-defrance:~/Documents/njs-lyreduino $ node app.js Server listening on https://localhost:3000 >> Client connecté ! << Rotation requise -> 235 Inclinaison requise -> 190 Slider BRIGHTNESS -> 55 (1) 1 Message affiché sur 🖮 Travail n° 4 Clients multiples Puisque plusieurs clients peuvent se connecter à l’application NodeJS, on aimerait bien que toute modification de slider depuis un client soit propagée à l’ensemble des clients connectés. Ceci peut être réalisé facilement avec socket.io en utilisant la méthode broadcast() Modifier le gestionnaire de l’évènement “pantilt” de l’application NodeJS pour y ajouter la ligne numérotée dans le listing ci-dessous app.js socket.on('pantilt', function(data) { console.log("Slider " + data.id.toString().toUpperCase() + " -> " + data.val ); socket.broadcast.emit('pantilt', data); (1) }); 1 Diffusion (→ broadcast en anglais) de l’évènement reçu à l’ensemble des clients connectés Ajouter la portion de code suivant dans le fichier public/js/client.js public/js/client.js socket.on('pantilt', function(data) { (1) document.getElementById(data.id).value = data.val; }); 1 Interception de l’évènement “pantilt” envoyé par l’application NodeJS Relancer l’application et s’y connecter depuis plusieurs clients (2 instances de navigateur web depuis1 ou plusieurs PCs). Vérifier que toute action sur le slider “pan” ou “tilt” de la page web d’un client est répercutée sur l’ensemble des autres clients. Trop bien, non ? 😉 Modifier l’application pour également propager à l’ensemble des clients toute action sur le slider de contrôle de la luminosité. Corrigé des travaux n°3 et n°4 Archive protégée par mot de passe : travail-03-04-corrige Communication Raspberry Pi ↔ Arduino Uno R3 Dans cette partie de l’activité, vous allez modifier l’application NodeJS de façon à mettre en œuvre la communication entre la Raspberry Pi et la Arduino Uno R3 via la liaison série. Le module serialport est un de ceux qui permettent de communiquer par liaison série depuis une application NodeJS. Pour d’informations sur le module serialport, consulter le site Node SerialPort Le processus d’utilisation du module serialport est le même à chaque fois : importer et initialiser le module serialport ouvrir le port série configurer les fonctions de callback — c’est-à-dire les fonctions appelée automatiquement lorsque surviennent les différents évènements possibles sur une liaison série : réception de données, ouverture effective du port série, fermeture du port série … — et les laisser faire le reste Ci-dessous figure le code source de base pour mettre en œuvre le module serialport Exemple d’utilisation du module serialport simple-app-serialport.js // Importer le module `serialport` const SerialPort = require('serialport') // Définir les callbacks function showPortOpen() { console.log('port open. Data rate: ' + myPort.baudRate); } function readSerialData(data) { console.log(data); } function showPortClose() { console.log('port closed.'); } function showError(error) { console.log('Serial port error: ' + error); } // Ouvrir le port série de Raspberry Pi let myPort = new SerialPort('/dev/ttyAMA0', 9600); // Mettre en place l'interpréteur de données reçues let Readline = SerialPort.parsers.Readline; let parser = new Readline(); // Appliquer l'interpréteur au flux des données reçues // sur le port série myPort.pipe(parser); // Mettre en place les callbacks myPort.on('open', showPortOpen); parser.on('data', readSerialData); myPort.on('close', showPortClose); myPort.on('error', showError); // Ecrire sur le port série myPort.write("Hello world !\n"); 🖮 Travail n° 5 1ère mise en œuvre de serialport Désarchiver la proposition de corrigé aux travaux n°3 & n°4 dans ~/Documents/njs-lyreduino Installer le module serialport Exemple pi@rpi-defrance:~/Documents/njs-lyreduino $ npm install serialport --save Créer dans le répertoire njs-lyreduino/ un fichier simple-app-serialport.js et y copier le code source d’exemple donné plus haut Sur la Raspberry Pi relier avec énormément de précaution les broches GPIO14 et GPIO15 comme illustré ci dessous Ce câblage va permettre de reboucler localement et temporairement la liaison série : tout ce qui sera envoyé sur la liaison série de la Raspberry Pi (→ broche GPIO14/UART0_TxD) sera également reçu par la Raspberry Pi (→ broche GPIO15/UART0_RxD) au lieu d’être envoyé sur l'Arduino Uno R3. On va ainsi pouvoir vérifier facilement que les communications par voie série se déroulent correctement. Ouvrir une nouvelle session PuTTY sur la Raspberry Pi (ou dupliquer la connexion existante) sur la nouvelle session taper la commande pi@rpi-defrance:~ $ cat < /dev/ttyAMA0 (1) 1 se met en attente de caractères recus sur la liaison série et les affiche lorsqu’ils arrivent. Lancer l’application simple-app-serialport.js sur la 1èresession de PuTTY (→ node simple-app-serialport.js) Vérifier que le message “Hello World !” s’affiche sur la 2ème session de PuTTY. Interrompre la commande cat avec la séquence de touche Ctrl+C et saisir à présent la commande pi@rpi-defrance:~ $ ls | sudo tee /dev/ttyAMA0 (1) 1 Exécute la commande ls (→ liste le contenu du répertoire courant) et redirige — via un pipe (→ caractère ‘|’) — l’affichage à la fois sur l’écran et vers la liaison série (→ sudo tee /dev/ttyAMA0) Vérifier cette fois-ci que c’est l’application simple-app-serialport qui a reçu des données Débrancher l’une des extrémité du câble de rebouclage (GPIO14 ou GPIO15) et refaire l’étape précédente pour constater que cette fois-ci aucun caractère n’est reçu sur l’application NodeJS étant donné que la liaison est interrompue physiquement. 🖮 Travail n° 6 🔥 Intégration à l’application de pilotage de la lyre On vous demande ici d’intégrer les portions de code présentes dans l’application simple-app-serialport.js dans l’application app.js de façon à ce qu’elle envoie sur la liaison série les ordres qui correspondent aux actions sur les sliders de la page web. Les ordres reconnus par la lyre sont les suivant : Commande de rotation/inclinaison de la lyre format : ‘$’ ‘S’ <angle-pan> <angle-tilt> ‘\r’ L’angle (“pan” ou “tilt”) est défini — en degrés — dans l’intervalle [0, 160] S’il vaut -1, l’angle correspondant n’est pas modifié Ex. : “$S45,60\r” pour commander un angle de rotation de 45° et un angle d’inclinaison de 60° Ex. : “$S-1,30\r” pour demander un angle d’inclinaison de 30° sans toucher à l’angle de rotation Commande de sélection de gobo format : ‘$’ ‘G’ <id-gobo> ‘\r’ 3 gobos sont possibles. Le gobo est défini par son identifiant : id=0 id=1 id=2 Ex. : “$G2\r” pour commander l’affichage d’un gobo qui se présente sous la forme d’un damier avec des couleurs aléatoires Commande de luminosité de la lyre format : ‘$’ ‘L’ <%-luminosité> ‘\r’ La luminosité sera exprimée en % (0% → min., 100% → max.) Ex. : “$L50\r” pour commander une luminosité de 50% 🖮 Travail n° 7 Évolution de l’application Modifier l’application (interface HTML, script Javascript, application NodeJS) de façon à pouvoir sélectionner — depuis l’interface web — le gobo désiré parmi les 3 possibles. Vous pouvez utiliser un radio button pour sélectionner le gobo. Voir <input type="radio"> pour la documentation sur ce type d’élement de formulaire HTML ✎ Travail n° 8 Analyse de trame série On vous demande ici d’analyser une trame série à partie d’une capture réalisée sur un analyseur logique Analog Discovery 2 . Cet analyseur est piloté par le logiciel Waveforms Installer si nécessaire le logiciel Waveforms (installateur disponible sur le NAS) Exécuter Waveforms et ouvrir le fichier `serial.dwf3work (→ menu File Open Project]) Répondre aux questions suivantes en vous appuyant sur ce document et le manuel de référence du logiciel Waveforms : Combien de caractères contient la capture ? Comment et sur combien de bits de données sont codés les caractères ? Combien de bits de stop ont été configurés pour chaque trame ? Un bit de parité est-il présent ? Si oui, de quel type (paire ou impaire) ? Justifier. Dans une trame série les caractères sont-ils codés bit de poids faible en 1er ou bit de poids fort en 1er ? Justifier la réponse en se basant sur la trame du 1ier caractère de la capture (→ ‘y’) Si je dois transmettre le message “d3”, quel est le 1er caractère qui apparaitra dans la capture faite par l’analyseur : ‘d’ ou ‘3’' ? Mesurer à l’aide d’un curseur la durée d'1 bit. Y a-t-il une relation avec la vitesse de transmission ? Si oui, laquelle ? 🞄 🞄 🞄 Installation SN1/2IR/EC - NodeRED