LOCODUINO

Aide
Forum de discussion
Dépôt GIT Locoduino
Flux RSS

mardi 19 mars 2024

Visiteurs connectés : 38

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

.
Par : bobyAndCo

DIFFICULTÉ :

Dans les articles précédents Piloter son Arduino avec son navigateur web et Node.js (1) et Piloter son Arduino avec son navigateur web et Node.js (2), nous avons vu que nous pouvions commander un Arduino avec des boutons ou des potentiomètres inclus dans une page HTML.

Dans ce nouvel article, nous allons piloter des servomoteurs en simulant le pilotages d’aiguillages.

<

Tous les fichiers utilisés dans cet article sont téléchargeables ici :

Archive3

Ces dossiers et fichiers sont à placer dans le même répertoire que pour les articles précédents.

PNG - 100.5 kio

PILOTER UN SERVOMOTEUR

JPEG - 34.9 kio
Les servomoteurs que nous connaissons généralement sont, malgré leur faible coût, des appareils sophistiqués. Ils peuvent tourner dans un sens comme dans l’autre mais ne peuvent en général réaliser que des rotations inférieures à 360°.

Les servomoteurs fonctionnent à une fréquence fixe de 50 Hz. Cela veut dire que le cycle état haut (HIGH) / état bas (LOW) se répète toutes les 20 millisecondes.

Mais dans ce cycle, la durée de l’état haut n’est pas forcément la même que celle de l’état bas. L’état haut peut être de 1 ms alors que l’état bas est de 19 ms. Et c’est cette différence de durée de l’état haut qui va déterminer l’angle de rotation à appliquer au servomoteur. Une durée du HIGH de 2 ms appliquée toutes les 20 ms (fréquence 50 Hz) commande au moteur d’appliquer un angle de 180°, 1,5 ms, un angle de 90°, 1 ms un angle de 0°.

Pour trouver la durée de l’état haut pour un angle quelconque, il faut donc diviser 2ms par 360 puis multiplier par la valeur de l’angle voulue puis ajouter 1 ms.

Ainsi, si je veux un angle de 175°, je dois calculer : (2 / 360 * 175) + 1 = 1,972 ms.

Mais le but de cet article n’est pas d’expliquer le fonctionnement des servomoteurs. Comme d’habitude, il existe un très bon article sur Locoduino qui traite des servomoteurs. Vous pouvez vous y reporter pour plus d’informations.

Et comme souvent avec l’Arduino, il existe aussi une bibliothèque pour nous aider à programmer des cas complexes. Ici, la bibliothèque s’appelle tout simplement Servo... et il existe un article de Locoduino qui traite spécifiquement de cette bibliothèque servo.

 

Mais voyons tout d’abord le montage :

PNG - 18 kio

Attention, l’alimentation du moteur en 5V est prise directement sur la carte ce qui est possible avec un seul servomoteur. Si vous deviez en piloter plusieurs comme dans le second exemple à suivre, il serait alors nécessaire d’utiliser une alimentation externe. Sur un servomoteur, le fil rouge est à relier au 5V, le noir au GND. Le dernier, parfois blanc, jaune ou autre est à relier à la pin de l’Arduino qui envoie les commandes ; ici la pin 9.

 

Fichier Servo1.ino

/*
  Ce sketch reprend en partie les éléments de l'exemple Knob de Arduino

  Controlling a servo position using a potentiometer (variable resistor)
  by Michal Rinott <http://people.interaction-ivrea.it/m.rinott>

  modified on 8 Nov 2013
  by Scott Fitzgerald
  http://www.arduino.cc/en/Tutorial/Knob
*/

#include <Servo.h>
Servo myservo;  // create servo object to control a servo
int attachPin = 9; // Le servo est attaché à la pin 9

char msgString[10]; // Tableau qui va recevoir les caractères envoyés
int cur = 1500;     // Position initiale du servo (µs)
int val = 1500;     // Valeur à atteindre pour le servo (µs)
int del = 0;        // Temporisation (ms)

void receipt() {
  // Lecture du port série
  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
      parse(msgString);
    }
    else {                        // Caractères du message
      sprintf(msgString, "%s%c", msgString, c);
    }
  }
}

void parse(char *msgString) {
  sscanf(msgString, "%d%d", &val, &del); // On copie les valeurs reçues dans les varables val et del
}

void process() {
  if (val > cur) {
    for (cur; val >= cur; cur++) {
      myservo.writeMicroseconds(cur); // (µs)
      delay(del);                     // (ms)
    }
  }
  else if (val < cur) {
    for (cur; val <= cur; cur--) {
      myservo.writeMicroseconds(cur); // (µs)
      delay(del);                     // (ms)
    }
  }
}

void setup() {
  Serial.begin(115200);
  myservo.attach(attachPin);   // attaches the servo on pin 9 to the servo object
  myservo.writeMicroseconds(cur); // On place le servo à sa position de départ
  delay(200);
}

void loop() {
  receipt();
  process();
}

Petite précision, la bibliothèque Servo et incluse avec l’IDE. Vous n’avez rien d’autre à faire que de la charger avec : #include <Servo.h> (ligne 1)

Si vous avez suivi les précédents articles, une bonne partie du code vous est maintenant familière. La réception des messages a été "sortie" de la loop() pour devenir la fonction receipt(). Fonction receipt() qui est appelée dans la loop().

Quand le caractère délimiteur de fin de message est rencontré (ligne 29) le message est envoyé à une nouvelle fonction : parse(msgString);

Dans ce premier exemple, le message envoyé par la page web est très simple. Outre les caractères délimiteurs de début ’<’ et de fin ’>’, il contient en premier, l’angle à appliquer en microsecondes puis la tempo en millisecondes, les deux séparés par un espace.

Exemple : <2000 10> qui signifie que l’angle demandé est 2000 µs et l’attente entre chaque pas (degré) est de 10 ms.

Les commandes à envoyer au servomoteur sont traitées par la fonction process() :

void process() {
  if (val > cur) {
    for (cur; val >= cur; cur++) {
      myservo.writeMicroseconds(cur); // (µs)
      delay(del);                     // (ms)
    }
  }
  else if (val < cur) {
    for (cur; val <= cur; cur--) {
      myservo.writeMicroseconds(cur); // (µs)
      delay(del);                     // (ms)
    }
  }
}

Si la valeur à atteindre (val) est supérieure à la position courante (cur), avec la boucle for (cur; val >= cur; cur++), on incrémente de 1 en 1 la valeur courante envoyée au servo : myservo.writeMicroseconds(cur);

A l’inverse, si la valeur à atteindre (val) est inférieure à la position courante (cur), avec la boucle for (cur; val <= cur; cur--), on décrémente la valeur cur envoyée au servo : myservo.writeMicroseconds(cur);

La vitesse de rotation est régulée par la fonction delay(del)del est la seconde valeur envoyée dans le message <2000 10>. Ici 10 ms. Notez bien que plus del est important, plus la vitesse sera lente.

 

La page HTML Servo1

PNG - 33.9 kio

Dans notre page HTML, nous allons placer des champs « input » de type=number pour déterminer l’angle mini et l’angle maxi souhaités, ici respectivement 1000 et 2000 sur l’image. Ces champs possèdent plusieurs propriétés et option intéressantes.

Tout d’abord, on peut incrémenter ou décrémenter les valeurs avec des flèches hautes et basses. Mais on peut aussi entrer la valeur directement au clavier. Et si la valeur saisie n’est pas numérique, alors elle est refusée.

Pour les options, on trouve en particulier min et max qui interdisent des saisies en dehors de ces limites (bien utile dans notre cas pour éviter des saisies incohérentes ou risquées pour notre servomoteur). On trouve aussi l’option step s’il l’on souhaite incrémenter de 2 en 2, 5 en 5, 10 en 10.

Dans notre projet, le servomoteur est sensé piloter un aiguillage. Les options min et max sont donc très importantes pour limiter le débattement de l’aiguille et éviter sa détérioration éventuelle. Mais nous allons ajouter une possibilité appréciée en modélisme ferroviaire qui est la vitesse de déplacement que l’on souhaite en général assez lente pour reproduire ce que l’on rencontre dans la réalité.

Comme on peut le voir, cela fonctionne dans un sens puis dans l’autre.

Code de la page Servo1.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:50px;
        }
        table {
            width:100%;
        }
        td {
            padding:10px;
            text-align:center;
        }
        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 des écouteurs
            var goDown = document.getElementById('goDown');
            goDown.addEventListener('click', function() {
                    set(this.id); // Appel de la fonction set() avec en paramètre l'ID du bouton
            });
            var goUp = document.getElementById('goUp');
            goUp.addEventListener('click', function() {
                    set(this.id); // Appel de la fonction set() avec en paramètre l'ID du bouton
            });
            var speed = document.getElementById('speed');
            speed.addEventListener('input', function() {
                    $('#log').html(speed.value); // Affichage de la valeur dans la span id="log"
            });
            
            // Fonction set avec pour paramètre l'ID du bouton cliqué
            var set = function (id) { 
                switch (id) {
                    case "goDown":
                        var msgString = '<'+document.getElementById('min').value+" "+document.getElementById('speed').value+'>';
                        break;
                    case "goUp":
                        var msgString = '<'+document.getElementById('max').value+" "+document.getElementById('speed').value+'>';
                        break;
                    default:
                    return alert("Aucune valeur valide !");
                }				
                socket.emit('message', msgString);
            }
            
            // Initialisation à 1500 µs et delais 0 ms
            socket.emit('message', '<1500 0>');
        });
    </script>
  </head>
  <body>
    <table>
      <tr>
        <td width="30%"><input id="min" type="number" min="0" max="1500" value="1000" /></td>
        <td width="40%"></td>
        <td width="30%"><input id="max" type="number" min="1500" max="3000" value="2000" /></td>
      </tr>
      <tr>
        <td>Mini</td>
        <td></td>
        <td>Maxi</td>
      </tr>
      <tr>
        <td></td>
        <td><input id="speed" type="range" min="0" max="100" value="10" /></td>
        <td></td>
      </tr>
      <tr>
        <td></td>
        <td>Tempo : <span id="log">10</span> ms</td>
        <td></td>
      </tr>
      <tr>
        <td><input id="goDown" type="button" value="<-" /></td>
        <td></td>
        <td><input id="goUp" type="button" value="->" /></td>
      </tr>
    </table>
  </body>
</html>

Rien dans ce code que vous n’ayez déjà vu hormis (this.id) lignes 51 et 55 en paramètre de la fonction set(). En Programmation Orientée Objet, this est un pointeur sur l’objet courant. Ici, this.id renvoie donc l’id du bouton qui est à l’origine de l’appel de la fonction.

Voilà donc un exemple simple pour paramétrer un servo comme moteur d’aiguillage en choisissant l’angle mini et maxi et la vitesse de rotation.

Oui mais ! me direz-vous, dans la vraie vie, du moins sur un vrai circuit, ça ne fonctionne pas comme cela. Il n’y a pas de circuits avec un seul aiguillage. Et avec la fonction delay(), l’exécution du programme est arrêtée à intervalles réguliers. Impossible d’attendre qu’une aiguille ait fini sa manœuvre pour en actionner une autre.

Oui, vous avez raison, cet exemple est très théorique mais permet de comprendre assez facilement comment on peut faire.

Maintenant, voici la version « en vrai grandeur ». C’est un peu plus complexe je reconnais.

PILOTER PLUSIEURS SERVOS SIMULTANÉMENT ET SANS ARRÊT DU PROGRAMME

La librairie Servo supporte jusqu’à 12 servomoteurs sur la plupart des cartes Arduino et 48 sur l’Arduino Mega. Voila qui ouvre de larges perspectives.

Sur mon propre réseau, je pilote au total 21 aiguilles, 14 au niveau n0 et 7 en gare cachée n-1.

Ce TCO est visible ici. L’image des aiguilles change en fonction de la direction quand on clique dessus.

 

Fichier Servo2.ino

#include <Servo.h>

#define NB_AIGUILLAGES          21   // nombre d'aiguillages à piloter (mettez vos propres valeurs)
#define START_PIN               22   // Première des pins affectées aux servos 

char msgString[12];  // Tableau qui va recevoir les caractères envoyés
const int initPos = 1500;  // Position initiale des servos (en microsecondes)

typedef struct { // Déclaration d'une structure "Aiguillage"
  int val;   // Angle (en microsecondes)
  char sens; // Sens de rotation
  long del;  // Tempo (en millisecondes)
  int cur;   // Position courante
  bool act;  // En cours de changement
  unsigned long prev; // Dernière valeur de temps stockée
} Aiguillage ;

Aiguillage aig[NB_AIGUILLAGES]; // Création d'un tableau d'aiguillages (aig[ ]) à partir de la structure "Aiguillage"
Servo myServo[NB_AIGUILLAGES];  // Création d'un tableau d'objets servos à partir du constructeur Servo

void receipt () {
  // Lecture du port série
  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
      parse(msgString);           // Appel de la fonction parse()
    }
    else {                        // Caractères du message
      sprintf(msgString, "%s%c", msgString, c);
    }
  }
}

void parse(char *msgString) {
  int n;  // Numéro de l'aiguillage
  int v;  // Valeur à atteindre pour le servo
  int d;  // Vitesse de rotation

  sscanf(msgString, "%d%d%d", &n, &v, &d);

  aig[n].val = v; // Affectation des différentes valeurs pour l'aiguillage n
  aig[n].del = d;
  aig[n].act = true;
  aig[n].prev = 0;
  if (aig[n].val > aig[n].cur) aig[n].sens = '+';      // Le sens est positif
  else if (aig[n].val < aig[n].cur) aig[n].sens = '-'; // Le sens est négatif
  else aig[n].act = false; // La position demandée est la même que la position courante, on ne fait rien
}

void process () {
  // Application des modifications
  for (int i = 0; i < NB_AIGUILLAGES; i++) {
    if (aig[i].act == true) {                           // Une action est en cours sur cet aiguillage
      unsigned long currentMillis = millis();           // On affecte à la variable "currentMillis" l'heure courante
      if (currentMillis - aig[i].prev >= aig[i].del) {  // Si le délais d'attente pour cet aiguillage est dépassé
        if (aig[i].sens == '+') aig[i].cur ++;          // On incrémente la position courante de cet aiguillage de 1
        else if (aig[i].sens == '-') aig[i].cur --;     // On décrémente la position courante de cet aiguillage de 1
        myServo[i].writeMicroseconds(aig[i].cur);       // On envoie au servo la valeur à appliquer
        aig[i].prev = currentMillis;                    // Le prev pour cet aiguillage prend la valeur currentMillis
      }
      if (aig[i].cur == aig[i].val) {                   // Le servo a atteint son angle final
        aig[i].act = false;                             // La valeur de act (action) est modifiée à false
      }
    }
  }
}


void setup() {

  /////--- COMMUNICATION SERIE---////
  Serial.begin(115200);

  ///////////--- SERVOS---///////////
  for (int i = 0; i < NB_AIGUILLAGES; i++) {            // Initialisation de chaque élément des tableaux myServo et aig
    myServo[i].attach(START_PIN + i);
    myServo[i].writeMicroseconds(initPos);
    delay(200);
    aig[i].val = initPos;
    aig[i].del = 0;
    aig[i].cur = initPos;
    aig[i].act = false;
    aig[i].prev = 0;
    delay(200);
  }
  Serial.println("Init. ok");
}

void loop() {
  receipt();
  process();
}

 

Qu’y a t’il de vraiment différent dans ce nouveau fichier servo2.ino ?

Tout d’abord, de la ligne 9 à la ligne 16 la déclaration d’une structure qui a pour nom : Aiguillage.

 

Qu’est-ce qu’une structure et en quoi cela peut-il être utile ?

D’après Wikipedia en anglais, la page en français étant nettement insuffisante :

"A struct in the C programming language (and many derivatives) is a composite data type declaration that defines a physically grouped list of variables to be placed under one name in a block of memory, allowing the different variables to be accessed via a single pointer, or the struct declared name which returns the same address. The struct can contain many other complex and simple data types in an association."

Ce qui traduit par Google et après quelques corrections peut vouloir dire :

"Une structure dans le langage de programmation C (et de nombreuses dérivées) est une déclaration de type de données composite qui définit une liste de variables physiquement groupées à placer sous un seul nom dans un bloc de mémoire permettant d’accéder aux différentes variables via un seul pointeur, ou le nom déclaré de la structure qui renvoie la même adresse. La structure peut contenir de nombreux types de données complexes et simples dans une association."

Contrairement aux tableaux qui vous obligent à utiliser le même type dans tout le tableau, vous pouvez créer une structure comportant des variables de types long, char, int et double à la fois.

Dans notre cas précis, nous rassemblons donc toutes les variables propres à un aiguillage à l’intérieur d’un même ensemble de la ligne 9 à la ligne 16 : val -> l’angle à atteindre, sens -> le sens de rotation, del -> le délais entre deux changement d’angles pour déterminer la vitesse de rotation, cur -> la current position (position actuelle) etc… Notez bien qu’à ce stade, les éléments de la structure n’ont aucune valeur, ils ne sont pas initialisés.

C’est que avant tout, nous allons déclarer un tableau de structure à partir de la structure initiale Aiguillage. Nous voulons en effet répéter cette structure autant de fois que nous avons d’aiguillages pour que chaque aiguillage puisse contenir ses propres valeurs pour les différentes variables. Pour ce faire, nous allons écrire ligne 18 : Aiguillage aig[NB_AIGUILLAGES];

Cela revient à dire : Créer un tableau d’une dimension 21 (nombre d’aiguillages) dont chaque membre du tableau à la même « structure » que la structure initiale Aiguillage.

JPEG - 94 kio

Pour plus d’information sur les structures, vous pouvez vous reporter à l’article de Jean-Luc.

Je ne saurais que trop le répéter, les tableaux en particulier (mais aussi les structures) sont des outils très puissants de programmation qui, au final, quand vous les maîtriserez, vous deviendront indispensables. Lisez, prenez du temps, faites des exercices car le jour où vous aurez compris, vous crierez « eurêka ».

Je pense qu’avec la suite qui va venir, vous percevrez immédiatement les avantages.

Premier avantage, l’accès aux membres de la structure se fait à la manière d’un objet. Dans notre cas, si l’on veut atteindre la valeur du sens de notre premier aiguillage, il suffira d’écrire valRecherchee = aig[0].sens ; aig est le nom du tableau, [0] désigne l’index du premier élément d’un tableau, .sens désigne le membre. L’accès à un membre d’une structure se réalise à l’aide de la variable de type structure et de l’opérateur "." suivi du nom du champ visé. C’est vraiment très facile !

Le deuxième gros avantage des tableaux, et à fortiori des tableaux de structures, c’est l’énorme productivité du code, la simplification d’écriture et donc la réduction des sources d’erreurs.

L’illustration nous en est donnée dans le setup() entre les lignes 73 à 91. Avec cette simple boucle for (int i = 0; i < NB_AIGUILLAGES; i++) nous allons attacher les pins aux 21 servos (à partir de la pin 22 sur mon MEGA), écrire la position initiale de chaque servo et initialiser d’un bloc les valeurs de 21 aiguillages. Sans cela, il aurait fallu écrire pas moins de 147 lignes de code au lieu de 7 ici avec le côté fastidieux et les risques d’erreurs inévitables. Et inutile de préciser que avec 140 lignes de code supplémentaires, il vous est impossible d’avoir une vue d’ensemble de votre code.

Le loop() se limite à l’exécution de deux fonction : receipt() et process().

Pour la fonction receipt(), il y a un ajout par rapport à toute à l’heure puisque la première variable du message reçu est le numéro de l’aiguillage. Par exemple <0 2000 10> pour appliquer au premier aiguillage (index 0 du tableau) un angle de 2000 µs avec un délai de 10 ms.

Quand tout le message est reçu, ligne 30, la fonction parse() est appelée avec le message en paramètre (msgString). La fonction parse() a donc elle aussi une nouvelle variable qui est le numéro de l’aiguillage (n). Comme avant, les variables sont initialisées avec la fonction sscanf(msgString, "%d%d%d", &n, &v, &d); ligne 43 et on peut donc affecter à l’aiguillage concerné les valeur reçue dans le message :

aig[n].val = v;
aig[n].del = d;

lignes 45 et 46 qui ne devraient pas vous poser de problème. Par contre, on rencontre ligne 47 un nouveau champ, aig[n].act (pour action). Nous allons voir dans la fonction process() à quoi nous sert cette valeur booléenne ici initialisée à true.

Ligne 48, nous rencontrons aussi un nouveau champ aig[n].prev pour preview auquel nous affectons la valeur 0.

Enfin, lignes 49 et 50, nous avons encore un nouveau champ aig[n].sens qui, selon que l’angle à atteindre est supérieur ou inférieur à la position courante, sera positif ou négatif (horaire ou anti horaire).

lignes 51, la position demandée est la même que la position actuelle, on ne fait rien en affectant à aig[n].act la valeur false.

Nous voyons bien que la structure nous permet de stocker un nombre plus conséquent de valeurs mais pour lesquels la compréhension est totale.

Voyons maintenant la fonction process() qui est elle radicalement différente.

Tout d’abord, ligne 56 une boucle for (int i = 0; i < NB_AIGUILLAGES; i++) exactement identique à celle du setup() et qui nous permet de parcourir l’ensemble des éléments du tableau d’aiguillages.

Ligne 57, voilà à quoi il servait d’initialiser aig[n].act = true à la ligne 47. En effet, rien ne sert d’ exécuter du code pour rien. if (aig[i].act == true) nous permet de savoir qu’il y a une action en cours sur cet aiguillage et qu’il faut donc poursuivre le code inclus. La valeur de aig[n].act est remise à false quand la position courante à atteint la position demandée : lignes 65 et 66

if (aig[i].cur == aig[i].val) {
        aig[i].act = false;
 }

Analysons les lignes 58 et suivantes maintenant. Vous le savez, on vous le répète régulièrement sur Locoduino, l’usage de la fonction delay() peut être préjudiciable car elle bloque purement et simplement le programme pour la valeur en millisecondes passée en paramètre. Cela veut dire que rien d’autre ne peut être exécuté pendent ce temps. C’est le premier exemple ci-dessus. Mais dans ce second exemple, on ne peut pas se permettre d’attendre qu’un aiguillage ait fini sa manœuvre pour en lancer un autre au besoin. On a donc recours à la fonction millis() ligne 58 : unsigned long currentMillis = millis();.

Je ne vais pas m’attarder sur cette fonction qui a elle aussi fait l’objet d’un (très bon) article sur Locoduino Comment gérer le temps dans un programme ?

Souvenez-vous, ligne 48 aig[n].prev = 0; que nous avions initialisé à 0. Eh bien, ce champ nous permet de stocker pour chaque aiguillage la valeur currentMillis de la ligne 58 et de vérifier ligne 59 si la condition pour cet aiguillage est vraie ou pas if (currentMillis - aig[i].prev >= aig[i].del) avec une vitesse aig[i].del qui est elle aussi individualisée et peut être différente pour chaque aiguillage.

Voilà, encore une fois, vous avez dans ce second exemple un code certainement complexe mais particulièrement efficient. Je vous invite vraiment à le comprendre ce qui vous permettra d’aller plus loin en programmation Arduino.

Pour le code HTML de ce second exemple (Servo2.html), je n’ai pas fait dans la complication. J’ai repris le même que pour l’exemple précédent en ajoutant simplement un menu déroulant pour pouvoir sélectionner l’un des aiguillages.

PNG - 131.5 kio

Code de la page Servo2.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: 50px;
      }
      table {
          width: 100%;
      }
      td {
          padding: 10px;
          text-align: center;
      }
      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 des écouteurs
          var goDown = document.getElementById('goDown');
          goDown.addEventListener('click', function() {
                  set(this.id);
          });
          var goUp = document.getElementById('goUp');
          goUp.addEventListener('click', function() {
                  set(this.id);
          });
          var speed = document.getElementById('speed');
          speed.addEventListener('input', function() {
                  $('#log').html(speed.value);
          });
          
          // Fonction set appelée quand on clique la flèche gauche ou la flèche droite
          var set = function (id) { 
              switch (id) {
                  case "goDown":
                      var msgString = '<'+numAig+" "+document.getElementById('min').value+" "+document.getElementById('speed').value+'>';
                      break;
                  case "goUp":
                      var msgString = '<'+numAig+" "+document.getElementById('max').value+" "+document.getElementById('speed').value+'>';
                      break;
                  default:
                  return alert("Aucune valeur valide !");
              }				
              socket.emit('message', msgString);
          }
		  
	  var nbAig = 21; // Nombre d'aiguillages
          
          // Initialisation à 1500 µs et delais 0 ms
	  for(var i = 0; i < nbAig; i++) {
              socket.emit('message', '<'+i+' 1500 0>');
          }
          
          // Création du menu déroulant
          var menuAiguille = function () {
            // Pour chaque aiguillage, création d'une ligne de menu
            for(var i = 0; i < nbAig; i++) {
              $('#menuAiguille').append('<option value="'+ i +'">'+ 'Aiguillage '+ i +'</option>');
            }
          }

          // Appel de la fonction menuAiguille()
          menuAiguille();
          
          // Déclaration d'une variable globale pour les n° d'aiguilles
          var numAig = null;

          // Installation d'un écouteur sur le menu déroulant
          $("select[id='menuAiguille']").change( function() {
              // numAig prend la valeur selectionnée
              numAig = $("select[id='menuAiguille'] > option:selected").val();
          });
      });
    </script>
  </head>
  <body>
    <table>
      <tr>
        <td width="30%"></td>
        <td width="40%">
          <select id="menuAiguille">
            <option value="">-- Sélectionnez un aiguillage --</option>
          </select>
        </td>
        <td width="30%"></td>
      </tr>
      <tr>
        <td><input id="min" type="number" min="0" max="1700" value="1500" /></td>
        <td></td>
        <td><input id="max" type="number" min="1400" max="3000" value="1500" /></td>
      </tr>
      <tr>
        <td>Mini</td>
        <td></td>
        <td>Maxi</td>
      </tr>
      <tr>
        <td></td>
        <td><input id="speed" type="range" min="0" max="100" value="10" /></td>
        <td></td>
      </tr>
      <tr>
        <td></td>
        <td>Tempo : <span id="log">10</span> ms</td>
        <td></td>
      </tr>
      <tr>
        <td><input id="goDown" type="button" value="<-" /></td>
        <td></td>
        <td><input id="goUp" type="button" value="->" /></td>
      </tr>
    </table>
  </body>
</html>

Et comme je suis un peu fainéant, j’ai délégué au programme le soin de créer chacune des 21 lignes du menu.

Dans le body lignes 109, 110 et 111 nous avons les « bases » du menu :

<select id="menuAiguille">
    <option value="">-- Sélectionnez un aiguillage --</option>
</select>

Dans le <script>, j’ai ajouté le code suivant à partir de la ligne 82 :

// Création du menu déroulant
var menuAiguille = function () {
  // Pour chaque aiguillage, création d'une ligne de menu
  for(var i = 0; i < nbAig; i++) {
    $('#menuAiguille').append('<option value="'+ i +'">'+ 'Aiguillage '+ i +'</option>');
  }
}

Ligne 75, j’ai placé dans la variable nbAig le nombre de lignes souhaité et le programme me les a créées dans la boucle for à suivre.

Bien sûr, ligne 91, je fais un appel explicite à la fonction pour l’activer.

Mais j’ai un autre problème à régler puisqu’il faut que je récupère le numéro de l’aiguillage qui est sélectionné et que je le stocke dans une variable. C’est ce qui va être fait entre les lignes 94 à 100 où je déclare une variable numAig ligne 94 à laquelle j’affecte une valeur avec l’écouteur installé juste dessous lignes 97 à 100.

Je peux maintenant piloter individuellement chacun de mes 21 servomoteurs.

Bien sûr, cette page HTML ne va pas créer un TCO mais elle peut par exemple permettre la recherche des valeurs et les réglages à appliquer aux aiguillages d’un réseau.

Ici ce que donne le mouvement d’un servomoteur dont la temporisation a été réglée sur 25 ms. Les angles sont de 1500 et 1610 µs

Nous voilà à la fin d’un troisième article encore dense avec des notions complexes mais qui relèvent plus du code Arduino que du HTML ou du JavaScript.

Une fois encore, j’essayerai de répondre au mieux à toutes vos questions et je vous invite à lire le quatrième et dernier article de cette série où nous présenterons une télécommande de locomotives fonctionnant avec DCC++ installé sur un UNO ou un MEGA ; via le port série bien évidemment et pour un budget de 20 à 30 €.

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

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 »

Le monde des objets (1)

Le monde des objets (2)

Le monde des objets (3)

Le monde des objets (4)

Les pointeurs (1)

Les pointeurs (2)

Les Timers (I)

Les Timers (II)

Les Timers (III)

Les Timers (IV)

Les Timers (V)

Bien utiliser l’IDE d’Arduino (1)

Bien utiliser l’IDE d’Arduino (2)

Comment gérer le temps dans un programme ?

La programmation, qu’est ce que c’est

Types, constantes et variables

Installation de l’IDE Arduino

Répéter des instructions : les boucles

Les interruptions (1)

Instructions conditionnelles : le if ... else

Instructions conditionnelles : le switch ... case

Comment concevoir rationnellement votre système

Comment gérer l’aléatoire ?

Calculer avec l’Arduino (1)

Calculer avec l’Arduino (2)

Les structures

Systèmes de numération

Les fonctions

Trois façons de déclarer des constantes

Transcription d’un programme simple en programmation objet

Ces tableaux qui peuvent nous simplifier le développement Arduino

Les chaînes de caractères

Trucs, astuces et choses à ne pas faire !

Processing pour nos trains

Arduino : toute première fois !

Démarrer en Processing (1)

TCOs en Processing (1)

TCOs en Processing (2)

L’assembleur (1)

L’assembleur (2)

L’assembleur (3)

L’assembleur (4)

L’assembleur (5)

L’assembleur (6)

L’assembleur (7)

L’assembleur (8)

L’assembleur (9)

Les derniers articles

L’assembleur (9)


Christian

L’assembleur (8)


Christian

L’assembleur (7)


Christian

L’assembleur (6)


Christian

L’assembleur (5)


Christian

L’assembleur (4)


Christian

L’assembleur (3)


Christian

L’assembleur (2)


Christian

L’assembleur (1)


Christian

TCOs en Processing (2)


Pierre59

Les articles les plus lus

Les Timers (I)

Les interruptions (1)

Instructions conditionnelles : le if ... else

Bien utiliser l’IDE d’Arduino (1)

Ces tableaux qui peuvent nous simplifier le développement Arduino

Comment gérer le temps dans un programme ?

Les structures

Les Timers (III)

Les Timers (II)

Instructions conditionnelles : le switch ... case