LOCODUINO

Piloter son Arduino avec son navigateur web et Node.js

Piloter son Arduino avec son navigateur web et Node.js (2)

.
Par : bobyAndCo

DIFFICULTÉ :

Dans l’article précédent Piloter son Arduino avec son navigateur web et Node.js (1), nous avons vu comment installer Node.js sur notre ordinateur et tester une première application permettant d’allumer ou d’éteindre la LED de l’Arduino avec un bouton poussoir inclus dans une page web.

Dans ce second article, il va être question de potentiomètres. Nous allons également travailler avec des messages plus complexes que de simples 0 ou 1 ce qui nous permettra d’envisager le pilotage d’applications importantes.

Tous les fichiers que nous allons utiliser pour ce deuxième article sont téléchargeables ici :

Archive2

Comme pour l’article précédent, vous devez les déposer dans votre répertoire "user".

REGLER L’INTENSITE D’UNE LED AVEC UN POTENTIOMETRE

Dans le premier projet de ce second article, nous allons utiliser un potentiomètre dans notre page web et régler ainsi la luminosité d’une LED reliée à une sortie PWM de notre Arduino. Ici la sortie 11. Le montage est on ne peut plus simple.

On a besoin d’une LED (peut importe la couleur) et d’une résistance de 220 Ohms minimum (jusqu’à 1K voire 1K,5 avec les LED basse consommation).

Et il faut téléverser dans l’Arduino le sketch fadeLed.ino.

PNG - 123.8 kio

Par l’intermédiaire de l’instruction analogWrite(), la broche génère une onde carrée avec un « duty cycle » dont la valeur oscille entre 0 (0% HAUT donc toujours au niveau BAS) et 255 (100% HAUT donc toujours au niveau HAUT).

PNG - 20.5 kio

La page web http://localhost:8080/fadeLed.html contient un simple potentiomètre qui permet de faire varier la valeur lue de 0 à 100%.

PNG - 25.2 kio

Voici ce que l’on souhaite obtenir.

Pour bien comprendre la PWM, je vous invite à lire les articles de Locoduino, La PWM : Qu’est-ce que c’est ?

Voyons le code de la page web fadeLed.html :

<html>
	<head>
		<meta charset='UTF-8'>
		<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
		<script src="./socket.io/socket.io.js"></script>
		<link rel="stylesheet" href="http://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
		<style>
			html {
				margin:20px;
			}
			table {
				width:450px;
			}
			td {
				padding:10px;
			}
			input[type="range"] {
				position: relative;
				margin-left: 1em;
			}
			input[type="range"]:after,
			input[type="range"]:before {
				position: absolute;
				top: 1em;
			}
			input[type="range"]:before {
				left:0em;
				content: attr(min);
			}
			input[type="range"]:after {
				right: 0em;
				content: attr(max);
			}
			#lightSlider:hover {
				cursor:pointer;
			}
			#log {
				text-align: center;
				color:red;
			}
		</style>

		<script>
			$(function() {	
				var socket = io.connect('http://localhost:8080');

				// Installation d'un écouteur sur le slider
				var slider = document.getElementById('lightSlider');
				slider.addEventListener('input', function() {
						setFade(slider.value);
				});

				var setFade = function (fadeValue) { 
						// La valeur lue dans le slider est convertie en caractère
						var fadeAscii = String.fromCharCode(fadeValue);
						// ... affichée sous le curseur
						$('#log').html(fadeValue+" %");
						// ... puis envoyée au serveur Node
						socket.emit('message', fadeAscii);
				}
				// Initialisation
				setFade(0);
			});
		</script>
	</head>
	<body>
		<table>
			<tr>
				<td><input id="lightSlider" type="range" min="0" max="100" value="0" /></td>
			</tr>
			<tr>
				<td id="log">0 %</td>
			</tr>
			<tr>
				<td>Déplacez le curseur pour faire varier la luminosité de la LED.</td>
			</tr>
		</table>
	</body>
</html>

Tout d’abord le potentiomètre qui est inclus dans le body ligne 69.

<input id="lightSlider" type="range" min="0" max="100" value="0" />

C’est une balise input de type="range". Les valeurs mini et maxi que renvoie le potentiomètre sont définies dans min="0" max="100". Toutes autres valeurs mini ou maxi peuvent être choisies. Quant à value="0", cela permet de positionner le curseur à 0 au chargement de la page. J’ai appelé l’identifiant unique de l’objet « lightSlider » (id="lightSlider") pour pouvoir m’en servir plus tard dans ma programmation.

L’objet graphique input range dispose d’autres options. Ainsi, la valeur du pas qui est 1 par défaut « step="1" » peut être réglée sur toute autre valeur (2 en 2, 5 en 5, 10 en 10…). N’hésitez pas à chercher des exemples sur internet.

De même, pour le CSS où vous voyez entre les balises <style> que je n’ai pas hésité à utiliser des options disponibles.

Mais attardons nous sur un élément nouveau et important du code JavaScript (lignes 48 à 51) :

// Installation d'un écouteur sur le slider
var slider = document.getElementById('lightSlider');
	slider.addEventListener('input', function() {
	setFade(slider.value);
});

Comme le commentaire l’indique, j’ai installé un écouteur sur l’objet qui a pour id « lightSlider ». Cet écouteur n’est actif que sur les événements de type « input », c’est à dire quand on déplace le curseur. Cela appellera alors la fonction setFade avec comme paramètre (slider.value).

Il est possible d’installer plusieurs écouteurs sur un même objet pour des événements différents comme le double click ou le passage de la souris. Ces nouveaux écouteurs peuvent appeler d’autres fonctions.

Comme dans le dernier exemple du premier article (power3.html), la fonction setFade reprend la conversion du caractère en code ASCII : var fadeAscii = String.fromCharCode(fadeValue); et envoie ce code ASCII à l’Arduino via la fonction socket.emit('message', fadeAscii);

$('#log').html(fadeValue+" %"); sert à afficher en temps réel la valeur renvoyée par le potentiomètre. Vous avez bien noté que la valeur envoyée est en pourcentage.

Voyons maintenant les choses côté Arduino avec le sketch fadeLed.ino

/*
  Ce sketche est inspiré de : https://www.arduino.cc/en/tutorial/fade

  This example shows how to fade an LED on pin 11
  using the analogWrite() function.

  The analogWrite() function uses PWM, so if
  you want to change the pin you're using, be
  sure to use another PWM capable pin. On most
  Arduino, the PWM pins are identified with
  a "~" sign, like ~3, ~5, ~6, ~9, ~10 and ~11.
*/

int led = 11;           // the PWM pin
float brightness = 0;   // how bright the LED is

void process() {
  brightness *= 2.55;
  analogWrite(led, brightness);
  Serial.println(brightness);
}

// the setup routine runs once when you press reset:
void setup() {
  // declare pin 11 to be an output:
  pinMode(led, OUTPUT);
  Serial.begin(115200);
}

void loop() {
  while (Serial.available()) {
    brightness = (int)Serial.read();
    if (brightness >= 0 || brightness <= 100) {
      process();
    }
  }
}

Dans le loop, la valeur reçue du navigateur est stockée dans la variable brightness qui est de type float. En effet, si les valeurs reçues sont de type entier comprises entre 0 et 100 (on s’exprime ici en % de luminosité), cette valeur devra être multipliée par le coefficient 2.55 pour être envoyée sur la sortie avec la fonction analogWrite() dont la plage de valeurs va de 0 à 255

Vous constaterez que là aussi, le code est concis.

COMMANDER UNE LED RVB

Pour cet exemple, nous allons continuer à nous servir de potentiomètres, trois cette fois, pour pouvoir agir sur chacune des trois couleurs primaires d’une LED RVB.

Faire fonctionner une LED RVB n’est pas beaucoup plus compliqué qu’une LED simple. Il faut tout d’abord repérer les broches par rapport à la broche la plus longue qui peut être soit une cathode commune, soit une anode commune. Ici nous avons une cathode commune qui sera reliée au GND de l’Arduino.

PNG - 11.3 kio

Voici le schéma du montage. Les résistances sont des 270 ohms 1/4 W.

Et il faut téléverser dans l’Arduino le sketch colorMixingLamp.ino.

JPEG - 141.8 kio

Côté HTML, nous allons avoir une page led_rvb.html (http://localhost:8080/led_rvb.html) très similaire à la précédente avec trois potentiomètres au lieu d’un auxquels nous allons donner des ID R_Slider pour le rouge, V_Slider pour le vert et B_Slider pour le bleu. Facile, non ?

<html>
	<head>
		<meta charset='UTF-8'>
		<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
		<script src="./socket.io/socket.io.js"></script>
		<link rel="stylesheet" href="http://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
		<style>
			html {
				margin:50px;
			}
			table {
				width:100%;
			}
			td {
				padding:10px;
			}
			input[type="range"] {
				position: relative;
				margin-left: 1em;
			}
			input[type="range"]:after,
			input[type="range"]:before {
				position: absolute;
				top: 1em;
			}
			input[type="range"]:before {
				left:0em;
				content: attr(min);
			}
			input[type="range"]:after {
				right: 0em;
				content: attr(max);
			}
			#lightSlider:hover {
				cursor:pointer;
			}
			#log {
				text-align: center;
				color:red;
			}
		</style>

		<script>
			$(function() {	
				var socket = io.connect('http://localhost:8080');
				
				var fullMsg = ""; // Initialisation d'une variable globale pour la réception des messages
				
				// En cas de réception d'un message du serveur
				socket.on('message', function(message) {
					fullMsg += message;  // On ajoute le message reçu à fullMsg
					if(message.charAt(message.length-1) == '\n') { // Si délimiteur de fin de message
						$('#log').html(fullMsg); // Affichage du message
						fullMsg = ""; // On efface le contenu de fullMsg
					}
				});

				// Installation d'écouteurs sur les 3 sliders
				/*
				var r = document.getElementById('R_Slider');
				r.addEventListener('input', function() {
						set();
				});
				var v = document.getElementById('V_Slider');
				v.addEventListener('input', function() {
						set();
				});
				var b = document.getElementById('B_Slider');
				b.addEventListener('input', function() {
						set();
				});
				*/
				
				$("[type=range]").on("input", function () {
					set();
				});

				var set = function () { 
						var msgString = '<'+R_Slider.value+" "+V_Slider.value+" "+B_Slider.value+'>';
						socket.emit('message', msgString);
				}
				
				// Initialisation
				set();
			});
		</script>
	</head>
	<body>
		<table>
			<tr>
				<td>Rouge :</td><td><input id="R_Slider" type="range" min="0" max="255" value="0" /></td>
			</tr>
            <tr>
				<td>Vert :</td><td><input id="V_Slider" type="range" min="0" max="255" value="0" /></td>
			</tr>
            <tr>
				<td>Bleu :</td><td><input id="B_Slider" type="range" min="0" max="255" value="0" /></td>
			</tr>
            <tr>
				<td>Logs : </td><td id="log"></td>
			</tr>
			
		</table>
	</body>
</html>

Le principal intérêt de ce troisième exemple concerne essentiellement le message que nous allons envoyer à l’Arduino. En effet, jusqu’ici, les messages envoyés étaient simples et ne concernaient qu’un seul paramètre et avec des valeurs inférieures à 128 (7 bits). Nous pouvions donc nous servir des codes ASCII de caractères.

Mais ici, nous devons envoyer au moins deux informations à l’Arduino. La couleur concernée et la valeur souhaitée. Sachant qu’il s’agit de sorties en PWM, ces valeurs peuvent varier de 0 à 255. Nous allons donc être obligé d’envoyer des chaines de caractères. Strings en Anglais.

Les messages envoyés à l’Arduino pourraient être de type R 123 si l’on voulait régler l’intensité du rouge sur 123 par exemple ou B 255 si l’on voulait un bleu au maximum.

Mais ici nous allons retenir une autre structuration de message qui consistera à envoyer les trois valeurs RVB dans un seul message et dans un ordre précis, le R en premier, le V en deuxième et le B en troisième. Le message sera donc de type 123 255 75 où, vous l’avez compris, la valeur du rouge est 123, du vert 255 et du bleu 75.

Dans le JavasScript, la structuration du message et son envoi sont programmés ainsi dans la fonction set() :

var set = function () {
msgString = '<'+R_Slider.value+" "+V_Slider.value+" "+B_Slider.value+'>';
		socket.emit('message', msgString);
}

msgString est la variable qui contient le message avec la valeur de R_Slider puis un espace puis la valeur de V_Slider puis un espace et enfin la valeur de B_Slider. Remarquez que le message commence par le caractère ’<’ et se termine par ’>’, ceci est important, nous en reparlerons quand nous examinerons le croquis de l’Arduino.

Cette fonction set() réalise donc la structuration du message et son envoi : socket.emit('message', msgString);

Il y a aussi une autre chose très intéressante dans ce code JavaScript. Alors que dans les exemples précédents, nous avions placé un script ou un écouteur sur chaque élément actif, nous aurions également dû placer un écouteur sur chacun des trois potentiomètres ce qui aurait donné ceci :

// Installation d'écouteurs sur les 3 sliders
var r = document.getElementById('R_Slider');
     r.addEventListener('input', function() {
     set();
});
var v = document.getElementById('V_Slider');
     v.addEventListener('input', function() {
     set();
});
var b = document.getElementById('B_Slider');
     b.addEventListener('input', function() {
     set();
});

En fait, nous allons utiliser le code suivant qui va faire exactement la même chose :

$("[type=range]").on("input", function () {
     set();
});

$("[type=range]") nous permet de ne plus à avoir à cibler chaque potentiomètre individuellement mais cible tous les éléments de la page qui ont pour type="range".

Ce code à été laissé volontairement sur la page mais mis en commentaire de la ligne 58 à 72, vous pouvez tout à fait le supprimer.

Au final, quand vous observez le JavaScript de cette page, vous vous apercevez qu’il est extrêmement compact, ce qui est une très bonne chose en programmation.

Voyons maintenant le code coté Arduino qui est inclus dans le croquis colorMixingLamp.ino. Pour ce programme, je suis parti d’un croquis du même nom déjà existant dans les exemples de l’Arduino dans 10.StarterKit et je l’ai adapté pour les besoins.

/*
  Ce sketch reprend en partie les éléments de :

  Arduino Starter Kit example
  Project 4  - Color Mixing Lamp

  This sketch is written to accompany Project 3 in the
  Arduino Starter Kit

  Parts required:
  1 RGB LED
  3 220 ohm resistors

  Created 13 September 2012
  Modified 14 November 2012
  by Scott Fitzgerald
  Thanks to Federico Vanzati for improvements

  http://www.arduino.cc/starterKit

  This example code is part of the public domain

  Modifiée par Christophe BOBILLE - juin 2017
*/

const int redLEDPin = 9;      // LED connected to digital pin 9
const int greenLEDPin = 10;   // LED connected to digital pin 10
const int blueLEDPin = 11;    // LED connected to digital pin 11

int redValue; // value to write to the red LED
int greenValue; // value to write to the green LED
int blueValue; // value to write to the blue LED

char msgString[12]; // Tableau qui va recevoir les caractères envoyés
char response[24];  // Tableau qui va recevoir les caractères de la reponse
//

void process (char *msgString) {
  int r, v, b;
  sscanf(msgString, "%d%d%d", &r, &v, &b);

  analogWrite(redLEDPin, r);
  analogWrite(greenLEDPin, v);
  analogWrite(blueLEDPin, b);

  snprintf(response, sizeof(response), "%d - %d - %d\n", r, v, b);
  Serial.print(response);
}

void setup() {
  Serial.begin(115200);
  // set the digital pins as outputs
  pinMode(greenLEDPin, OUTPUT);
  pinMode(redLEDPin, OUTPUT);
  pinMode(blueLEDPin, OUTPUT);
}

void loop() {
  char c;
  while (Serial.available()) {
    c = Serial.read();
    if (c == '<') {               // Caractère délimiteur de début de message
      sprintf(msgString, "");
    }
    else if (c == '>')  {         // Caractère délimiteur de fin de message
      process(msgString);
    }
    else {                        // Caractères du message
      sprintf(msgString, "%s%c", msgString, c);
    }
  }
}

Le premier élément nouveau et important est à la ligne 34 : char msgString[12];

Juste un petit rappel concernant les chaines de caractères. Une chaîne de caractères (string) n’est rien d’autre qu’un tableau de caractères et se « manipule comme tout autre tableau ». Nous avons donc ici déclaré un tableau de caractères (char) ayant pour nom msgString et dont la taille est fixée ici à 12 (c’est la taille maximale du message pouvant être envoyé dans notre projet + un caractère de retour à la ligne ’\n’, inutile de gaspiller la mémoire avec un tableau plus grand). Pour accéder par exemple au premier caractère de la chaîne, il faut donc faire x = msgString[0]. Si vous n’êtes pas familier avec les tableaux d’une manière générale, je ne peux que vous inviter à creuser cette partie de la programmation qui est extrêmement puissante et utile.

Dans la loop, nous avons while (Serial.available()) et Serial.read() déjà rencontrés. Par contre sprintf est une fonction nouvelle que j’ai empruntée au langage C car elle aussi est très puissante.

On rencontre cette fonction une première fois ligne 62 :

if (c == '<') {               // Caractère délimiteur de début de message
      sprintf(msgString, "");
}

Souvenez-vous dans le JavaScript : msgString = '<'+R_Slider.value+" "+V_Slider.value+" "+B_Slider.value+'>';

Nous avions ajouté ’<’ en début de message et ’>’ en fin de message. Ces caractères vont nous servir à délimiter le début et la fin du message. Ligne 62 du code Arduino, si le caractère rencontré est ’<’, alors notre tableau msgString est vidé : sprintf(msgString, "");

Au dessous, ligne 65 :

else if (c == '>')  {         // Caractère délimiteur de fin de message
      process(msgString);
}

Nous avons rencontré le caractère de fin de message, nous allons maintenant le traiter : process(msgString);

Mais en attendant, on exécute le code ligne 68 :

else {                        // Caractères à l'intérieur du message
      sprintf(msgString, "%s%c", msgString, c);
}

Nous allons ajouter (concaténer) les caractères du message dans notre tableau msgString et en même temps en faire la conversion au format string grâce là encore à la fonction sprintf(). Souvenez-vous précédemment que un « 1 » reçu était converti en « 49 » son code ASCII. Ici, un « 1 » reçu est bien converti en chaîne « 1 ».

Nous avons vu ligne 65 que quand tout le message avait été reçu, on appelait la fonction process avec notre tableau msgString en paramètre.

Cette fonction process est elle aussi complexe à appréhender mais vous allez le voir, elle est très puissante également. Dans un premier temps, ligne 39, nous allons déclarer trois variables de type int que nous allons nommer r, v et b.

Puis la fonction sscanf va convertir chacune des trois valeurs contenues dans msgString (et qui sont reconnues comme telles car séparées par des espaces) en décimal et placer ces valeurs décimales dans les trois variables r, v et b. C’est assez magique non ?

Vous avez sans doute remarqué que je suis obligé d’utiliser les pointeurs (*) et des références (&) mais si vous n’êtes pas familiers avec ces concepts, vous pouvez vous contenter de recopier ce code. Mais si vous voulez approfondir sur ces questions de pointeurs, je vous renvoie aux articles de Thierry sur ce sujet (articles Les pointeurs (1) et Les pointeurs (2)).

Vous comprenez bien que si vous aviez envoyé cinq valeurs par exemple (1234 12 12345 1 1234567), vous auriez eu à déclarer cinq variable, par exemple : int a, b, c, d, e ; et vous auriez récupéré la valeur de ces variable en décimal de la façon suivante : sscanf(msgString, "%d%d%d%d%d ", &a, &b, &c, &d, &e) ;

De la même façon, si nous avions envoyé non plus les trois valeurs RVB à la suite mais choisi de mettre une lettre puis la valeur, R 123 ou V 255, nous aurions décodé ce message de la façon suivante :

Char color ;
Int value ;
sscanf(msgString, "%s%d ", &color, &value);

et la suite du code :

if(color == 'R') {
  analogWrite(redLEDPin, value);
}
else if(color == 'V') {
  analogWrite(greenLEDPin, value);
}
else if(color == 'B') {
  analogWrite(blueLEDPin, value);
}

Mais la première méthode est tout de même plus simple et moins « verbeuse ».

Pour la réponse aussi, j’ai eu recours à des fonctions empruntées au langage C lignes 46 et 47.

  snprintf(response, sizeof(response), "%d - %d - %d\n", r, v, b);
  Serial.print(response);

La fonction Serial.printf() n’est pas reconnue par Arduino. Il faut donc contourner.

Ligne 35, on créé un tableau de CHAR pour la réponse : char response[24]; // Tableau qui va recevoir les caractères de la réponse

Puis on va utiliser ligne 46 la fonction snprintf() pour formater notre réponse en y incluant les variables pour enfin envoyer la variable response avec un Serial.print() bien classique et bien connu.

Notez que sans cela le code Arduino aurait été plus long :

  Serial.print(r);
  Serial.print(" - ");
  Serial.print(v);
  Serial.print(" - ");
  Serial.print(b);
  Serial.print("\n");

Voilà donc au travers de cet exemple de la LED RVB comment on peut manipuler des chaines de caractère en langage C dans l’Arduino. C’est complexe mais en même temps si puissant que ça mérite à mon avis que l’on s’y intéresse.

Si vous souhaitez aller plus loin sur les questions de chaines de caractères, vous pouvez vous reporter à l’article de Thierry Les chaînes de caractères.

Nous voila arrivé à la fin de ce deuxième article où nous avons découvert les potentiomètres, accessoires très utiles dans nos montages Arduino. Mais nous avons également vu comment lire (facilement) des messages longs dans l’Arduino ce qui va nous permettre d’envisager l’envoi de commandes complexes avec de nombreux paramètres à l’intérieur.

Cela sera le cas dans l’article 3 où nous allons piloter un servomoteur et surtout dans l’article 4 où nous traiterons de l’envoi de commandes à DCC++ pour le pilotage de locomotives.

Comme d’habitude, n’hésitez pas à poser toutes vos questions pour autant quelles concernent le modélisme ferroviaire.

5 Messages

Réagissez à « Piloter son Arduino avec son navigateur web et Node.js (2) »

Qui êtes-vous ?
Votre message

Pour créer des paragraphes, laissez simplement des lignes vides.

Lien hypertexte

(Si votre message se réfère à un article publié sur le Web, ou à une page fournissant plus d’informations, vous pouvez indiquer ci-après le titre de la page et son adresse.)

Rubrique « Programmation »

Les derniers articles

Les articles les plus lus