Sommaire
Découvrons dans ce tutoriel comment piloter en temps réel une matrice de LED bicolore depuis une page Web en quelque ligne.
Pour cela nous allons utiliser une matrice de 8×8 LEDs pilotable en I²C que vous pouvez trouver chez Adafruit pour environ 15€. Pour piloter cette matrice nous allons utiliser un ESP8266, ici un D1 Mini Pro (environ 6€).
Prérequis
Pour réaliser ce tutoriel, vous aurez besoin :
-
Un serveur Constellation
-
Un ESP8266
-
Une matrice de LED I²C
Si c’est la première fois que vous utilisez un ESP8266 connecté à Constellation, je vous recommande de suivre ce guide d’introduction avant de commencer !
Etape 1 : connecter la matrice de LED
Nous avons ici utilisé un D1 Mini Pro, un ESP8266 intégrant nativement une interface de programmation USB, très pratique pour créer des prototypes rapides.
La matrice de LED se connecte à l’ESP8266 en I²C. Nous avons utilisé les fils Noir et Blanc pour alimenter la matrice depuis les pins “5V” et “GND” du D1 Mini ainsi que les fils Bleu et Vert pour connecter les ports D1 et D2 du D1 Mini aux ports SCL et SDA de la matrice de LED :
Et voilà le montage est déjà fini ! Vous pouvez maintenant connecter votre D1 Mini en USB à votre PC pour le programmer.
Etape 2 : programmer l’ESP8266
Pour démarrer vous devez dans Constellation déclarer une sentinelle associée à une clé d’accès et un package virtuel. Ici notre ESP8266 est représenté par la sentinelle nommée « ESP8266 » et le package virtuel se nomme « MatrixController ».
Dans l’Arduino IDE, nous créons un nouveau projet à partir du Starter Kit Constellation pour ESP8266 dans lequel nous allons définir le nom de notre réseau Wifi (SSID) ainsi que sa clé d’accès puis nous configurerons le client Constellation en spécifiant l’identité de notre package virtuel, sa clé d’accès et l’adresse/port de notre serveur Constellation :
1 2 3 4 5 6 7 |
// ESP8266 Wifi #include <ESP8266WiFi.h> char ssid[] = "MON SSID"; char password[] = "macléWifi!!!!"; // Constellation client Constellation<WiFiClient> constellation("X.X.X.X", 8088, "ESP8266", "MatrixController", "xxxxxxxxxxxxxxxxx"); |
Encore une fois si tout cela est nouveau pour vous, je vous recommande de suivre ce guide d’introduction à l’API Constellation pour Arduino/ESP8266.
Maintenant que la coquille de notre package virtuel sur ESP8266 est prête, ajoutons les librairies d’Adafruit pour piloter la matrice de LED bicolore.
Dans le gestionnaire de bibliothèque (menu Croquis > Inclure une bibliothèque), installez les librairies suivantes :
- Adafruit GFX
- Adafruit LED Backpack
Puis dans votre code, ajoutez ces librairies et déclarez une variable de type « Adafruit_BicolorMatrix » que nous nommerons « matrix » :
1 2 3 4 |
#include "Adafruit_LEDBackpack.h" #include "Adafruit_GFX.h" #include <Wire.h> Adafruit_BicolorMatrix matrix = Adafruit_BicolorMatrix(); |
Au démarrage, dans la méthode « setup() », initialisez la matrice de LED. Pour rappel nous avons branché la matrice I²C sur les ports D1 et D2 :
1 2 3 |
// Init matrix led Wire.begin(D1, D2); matrix.begin(0x70); |
Toujours dans la méthode d’initialisation, enregistrons un premier MessageCallback que nous allons nommer « SetPixel ». Ce MessageCallback permettra d’allumer ou éteindre un pixel de la matrice.
Nous le déclarons avec 4 paramètres d’entrée :
- x et y pour définir la position du pixel à piloter
- state : un boolean indiquant si le pixel doit être allumé ou éteint
- clear: un paramètre optionnel de type boolean pour indiquer si il faut effacer la matrice avant (par défaut « true »)
Comme vous le constatez ci-dessous, le code de ce MessageCallback commence par effacer la matrice si le dernière paramètre (clear) n’est pas défini (car optionnel) ou si il est à « true » en invoquant la méthode « matrix.clear()« .
Ensuite on appelle la méthode « drawPixel » en passant les arguments de notre MC, à savoir le « x », le « y » et le « state ».
Si le paramètre « state » est vrai, on allume le pixel en vert (LED_GREEN) sinon on l’éteint (LED_OFF).
Le code est donc :
1 2 3 4 5 6 7 8 9 10 |
// Register SetPixel constellation.registerMessageCallback("SetPixel", MessageCallbackDescriptor().setDescription("Set Pixel On or Off").addParameter<int>("x").addParameter<int>("y").addParameter<bool>("state", "ON or OFF").addOptionalParameter<bool>("clear", true, "Clear matrix before drawn"), [](JsonObject& json) { if(json["Data"].size() < 4 || json["Data"][3].as<bool>()) { matrix.clear(); } matrix.drawPixel(json["Data"][0].as<int>(), json["Data"][1].as<int>(), json["Data"][2].as<bool>() ? LED_GREEN : LED_OFF); matrix.writeDisplay(); }); |
Comme il s’agit d’une matrice bicolore, on peut allumer chaque pixel en vert ou en rouge, ou bien en vert ET en rouge ce qui donne du orange.
Nous allons donc ajouter un deuxième MessageCallback que nous nommerons « SetPixelColor » sensiblement identique au premier, sauf que le paramètre « state » est maintenant de type « int » et se nomme « color ».
D’après les constantes de la librairie d’Adafruit, LED_GREEN = 2, LED_OFF = 0, le rouge (LED_RED) = 1 et le Orange/Jaune = 2. Le code est donc :
1 2 3 4 5 6 7 8 9 10 |
// Register SetPixelColor constellation.registerMessageCallback("SetPixelColor", MessageCallbackDescriptor().setDescription("Set Pixel On or Off").addParameter<int>("x").addParameter<int>("y").addParameter<int>("color", "0 = Off, 1 = red, 2 = orange, 3 = green").addOptionalParameter<bool>("clear", true, "Clear matrix before drawn"), [](JsonObject& json) { if(json["Data"].size() < 4 || json["Data"][3].as<bool>()) { matrix.clear(); } matrix.drawPixel(json["Data"][0].as<int>(), json["Data"][1].as<int>(), json["Data"][2].as<int>()); matrix.writeDisplay(); }); |
Pour finir, ajoutons un troisième et dernier MessageCallback pour effacer l’écran :
1 2 3 4 5 6 7 |
// Register Clear constellation.registerMessageCallback("Clear", MessageCallbackDescriptor().setDescription("Clear matrix"), [](JsonObject& json) { matrix.clear(); matrix.writeDisplay(); }); |
Vous pouvez téléverser le programme puis en vous connectant sur le MessageCallbacks Explorer de la Console Constellation vous constaterez que les trois MessageCallbacks sont bien référencés dans votre Constellation.
Votre ESP8266 est prêt ! Vous pouvez tester les MC directement depuis la Console pour vous assurer que tout fonctionne correctement :
Par exemple en invoquant le MessageCallback « SetPixel » avec x=1, y=2 et state=true, le résultat est instantanément visible sur la matrice :
Etape 3 : créer une page Web
Maintenant que notre matrice connectée est prête, créons une page Web pour pouvoir la piloter.
Pour cela nous allons créer une grille de 8×8 représentant la matrice sur laquelle nous pourrions dessiner à la sourie ou au droit (touch) avec la possibilité de choisir la couleur entre le verte, le roue et le orange. Nous allons utiliser le Canvas HTML5 pour cela.
Inutile d’utiliser le framework AngularJS pour cela, nous utiliserons la librairie Constellation pour Javascript.
Commençons donc par créer une page HTML classique dans laquelle ajoutons dans l’entête <head> les scripts nécessaires pour connecter la page à Constellation :
1 2 3 4 |
<meta name="viewport" content="width=device-width" /> <script type="text/javascript" src="https://code.jquery.com/jquery-2.2.4.min.js"></script> <script type="text/javascript" src="https://ajax.aspnetcdn.com/ajax/signalr/jquery.signalr-2.2.1.min.js"></script> <script type="text/javascript" src="https://cdn.myconstellation.io/js/Constellation-1.8.1.min.js"></script> |
Nous avons également ajouté la meta « viewport » pour adapter proprement notre page sur un mobile.
Dans le corps <body> de notre page ajoutons un canvas pour la grille, la liste des couleurs possibles et un bouton pour effacer l’écran :
1 2 3 4 5 6 7 8 9 10 |
<canvas id="canvas" width="400" height="400"></canvas> <ul id="colors"> <li><a href="#" data-colorId="0" data-color="white">Eraser</a></li> <li><a href="#" data-colorId="1" data-color="red" class="selected">Rouge</a></li> <li><a href="#" data-colorId="2" data-color="orange">Orange</a></li> <li><a href="#" data-colorId="3" data-color="green">Vert</a></li> </ul> <button id="clear">Clear</button> |
Vous remarquerez que nous stockons dans des attributs « data- » l’ID de la couleur de la matrice de LED (0 à 3) et le nom de la couleur HTML équivalente (ex. red = 1). La couleur sélectionnée est repérée par la classe CSS « selected ».
Pour la mise en page justement, ajoutez le code CSS dans une balise <style> dans la page :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
body { text-align:center; background:#e9e9e9 } #canvas { margin:0 auto 20px auto; display:block; background:#fff; cursor:crosshair } #colors { list-style:none; margin:0 0 20px 0; padding:0 } #colors li { display:inline-block } #colors a { display:inline-block; width:50px; height:50px; margin-right:10px; text-indent:-4000px; overflow:hidden; border-radius:50% } #colors a.selected { border:2px solid #000; width:45px; height:45px } button { border-radius: 8px; padding: 10px 20px 10px 20px; color: #ffffff; font-size: 20px; background: #808080; } |
Enfin, passons au code Javascript de notre page. Ouvrez une balise <script> pour ajouter le code ci-dessous.
On commence tout d’abord par déclarer la taille de notre matrice (8×8) ainsi que le client « consumer » de Constellation en spécifiant l’adresse/port de votre serveur Constellation ainsi qu’une clé d’accès pour pouvoir s’y connecter.
Ensuite on déclare les variables globales de notre script.
1 2 3 4 5 6 7 8 9 10 |
// Config var matrixWidth = 8, matrixHeight = 8; var constellation = $.signalR.createConstellationConsumer("http://x.x.x.x:8088/", "xxxxxxxxxxxxxx", "WebMatrixController"); // Global variables var pixels = []; var selectedColor = {}; var mouseDown = false; var canvas, context; var boxWidth, boxHeight; |
On ajoute une fonction « initBoard » pour dessiner notre grille dans le canvas HTML et initialiser le tableau « pixels » qui contiendra l’état de la matrice :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
function initBoard() { // Init the pixels array pixels = []; for (var x = 0; x <= matrixWidth; x++) { pixels[x] = []; for (var y = 0; y <= matrixHeight; y++) { pixels[x][y] = 0; } } // Create the vertical lines for (var x = 0; x <= matrixWidth; x++) { context.moveTo(1 + x * boxWidth, 0); context.lineTo(1 + x * boxWidth, canvas.height()); } // Create the horizontal lines for (var x = 0; x <= matrixHeight; x++) { context.moveTo(0, 1 + x * boxHeight); context.lineTo(canvas.width(), 1 + x * boxHeight); } // Draw lines context.lineWidth=1; context.strokeStyle = "black"; context.stroke(); } |
On déclare ensuite une fonction « setPixel » qui se chargera à la fois de dessiner dans le canvas la couleur sélectionnée du pixel mais également d’envoyer un message à notre ESP8266 pour invoquer le MessageCallback « SetPixelColor » en spécifiant la position du pixel et la couleur sélectionnée. Le tableau « pixel » permet de n’envoyer le message que si la couleur a changé pour éviter d’envoyer plusieurs fois le même ordre.
1 2 3 4 5 6 7 8 9 10 11 |
function setPixel(x, y) { if(pixels[x][y] != selectedColor.color) { // Save pixel pixels[x][y] = selectedColor.color; // Draw on canvas context.fillStyle=selectedColor.color; context.fillRect(2 + boxWidth * x, 2 + boxHeight * y, boxWidth - 2, boxHeight - 2); // SetPixelColor on ESP8266 constellation.server.sendMessage({ Scope: 'Package', Args: ['MatrixDesign'] }, 'SetPixelColor', [ x, y, selectedColor.id ]); } } |
Pour l’interaction nous ajoutons trois méthodes :
- moveStart : lorsque la sourie/le doigt (si touch) est posé sur la grille
- move : lorsque la sourie/le doigt (si touch) se déplace sur la grille
- moveEnd : lorsque la sourie/le doigt (si touch) « quitte » la grille
Dans les deux premières méthodes l’idée est de déduire la position du pixel en fonction des arguments de l’événement levé afin d’invoquer la méthode « setPixel » que nous avons déclarée ci-dessus qui se chargera à son tour de dessiner la bonne couleur dans le canvas tout envoyant le message à notre ESP8266.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
function moveStart(e, mobile, obj) { mouseDown = true; // Determine the pixel coordinates var event = mobile ? e.originalEvent : e; var x = Math.trunc((event.pageX - obj.offsetLeft) / boxWidth); var y = Math.trunc((event.pageY - obj.offsetTop) / boxHeight); // Block mobile event if (mobile) { e.preventDefault(); } // Set pixel setPixel(x, y); } function move(e, mobile, obj) { if (mouseDown) { // Determine the pixel coordinates var pageX = mobile ? e.originalEvent.targetTouches[0].pageX : e.pageX; var pageY = mobile ? e.originalEvent.targetTouches[0].pageY : e.pageY; var x = Math.trunc((pageX - obj.offsetLeft) / boxWidth); var y = Math.trunc((pageY - obj.offsetTop) / boxHeight); // Block mobile event if (mobile) { e.preventDefault(); } // Set pixel setPixel(x, y); } } function moveEnd() { mouseDown = false; } |
Il ne reste plus qu’à écrire le code de démarrage de notre page.
On commence par initialiser nos variables globales (instance et taille du canvas, contexte de rendu 2D du canvas). On initialise ensuite la grille par notre méthode « iniBoard » et on se connecte à notre Constellation (constellation.connection.start()).
Pour chaque couleur de notre liste, on affecte la couleur du background définie par son attribut data-color et on ajoute un handler au click de façon à récupéré dans l’objet « selectedColor » la couleur sélectionnée par l’utilisateur.
On affecte également un handler au click du bouton « Clear » pour effacer le Canvas et invoquer le MessageCallback « clear » sur notre ESP8266 afin de vider la matrice.
Et pour finir on attache des handlers aux événements de la souris (mouseDown, mouseMove et mouseUp) et du tactile (touchStart, touchMove et touchEnd) sur nos tris méthodes ci-dessus pour gérer les interactions avec notre grille.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
// On page load $(document).ready(function() { // Variables canvas = $("#canvas"); context = canvas[0].getContext('2d'); boxWidth = (canvas.width() - 2) / matrixWidth; boxHeight = (canvas.height() - 2) / matrixHeight; // Init board initBoard(); // Constellation connection constellation.connection.stateChanged(function (change) { if (change.newState === $.signalR.connectionState.connected) { console.log("Je suis connecté !"); } }); constellation.connection.start(); // For each color buttons $("#colors a").each(function() { // Set background color $(this).css("background", $(this).attr("data-color")); // Attach handler on click $(this).click(function() { // Load the selected color selectedColor.id = $(this).attr("data-colorId"); selectedColor.color = $(this).attr("data-color"); // Set the selected class $("#colors a").removeAttr("class", ""); $(this).attr("class", "selected"); return false; }); // Load the selected color if( $(this).attr("class") == "selected") { selectedColor.id = $(this).attr("data-colorId"); selectedColor.color = $(this).attr("data-color"); } }); // Clear button $("#clear").click(function() { // Clear the matrix constellation.server.sendMessage({ Scope: 'Package', Args: ['MatrixDesign'] }, 'Clear', {}); // Clear the canvas var s = document.getElementById ("canvas"); var w = s.width; s.width = 10; s.width = w; // Redraw the board initBoard(); }); // Attach Touch events canvas.bind('touchstart', function(e) { moveStart(e, true, this); }); canvas.bind('touchmove', function(e) { move(e, true, this); }); $(this).bind('touchend', function() { moveEnd(); }); // Attach Mouse events canvas.mousedown(function(e) { moveStart(e, false, this); }); canvas.mousemove(function(e) { move(e, false, this); }); $(this).mouseup(function() { moveEnd(); }); }); |
Et voilà notre page HTML est prête ! Lancez-la dans notre navigateur et profitez du résultat.
Demos
Quelques démonstrations animées ….
Démarrez la discussion sur le forum Constellation