I. Introduction

La télécommande et la télémétrie sont deux concepts de plus en plus liés entre eux. On est conscient depuis longtemps que, dans de nombreux cas, pour optimiser les prises de décision, la télécommande efficace d’un système peut nécessiter un retour d’informations. Il ne s’agit pas ici d’asservissement, car la réponse à l’information retournée n’est pas automatique : elle dépend de la décision qu’un opérateur va prendre, ou pas, en fonction de cette information.

L’asservissement, s’il a l’avantage d’être plus rapide, et en général plus précis, en ce qui concerne la réponse à une information, quelle qu’elle soit, n’est pas toujours utilisable. Il est en effet impossible d’envisager tous les cas et l’intelligence artificielle, si intelligence il y a, n’en est, quoi qu’on en dise, qu’à ses balbutiements : il est à craindre (ou à espérer, je ne suis pas encore fixé là-dessus), que son évolution ne lui permette pas d’égaler les fonctionnalités d’un cerveau biologique, serait-ce celui d’une mouche.

Pendant longtemps, la mise en œuvre de ces concepts, pour des réalisations personnelles, n’était que peu envisageable, compte tenu du manque de moyens mis à la portée du grand public, tant en ce qui concerne l’aspect financier que l’aspect technique. La donne a bien évolué, grâce notamment au concept de l’Open Source, qu’il soit appliqué aux logiciels ou aux matériels, lesquels sont devenus abordables dans tous les sens du terme.

Nous allons donc en profiter sans vergogne.

II. Cahier des charges

Le but de cette manipulation sera d’utiliser le navigateur pour :

  • transmettre une commande Marche/Arrêt vers un matériel à l’aide d’un bouton (widget) ;
  • récupérer une information rendant compte du résultat de cette opération ;
  • traiter cette information ;
  • récupérer une information issue d’un capteur ;
  • afficher cette information.

Cahier des charges modeste, certes, mais cahier des charges malgré tout.

Imaginons un petit scénario (il s’agit bien d’un scénario puisque pour le réaliser, nous allons écrire des sketchs ;-) pour nous fixer les idées : vous disposez d’une serre pour faire pousser vos tomates en été. Cette serre est munie d’un vasistas motorisé que vous souhaitez pouvoir actionner à distance en fonction de la valeur de la température ambiante fournie par un capteur. La gestion de l’ouverture du vasistas et du capteur de température sera dévolue à une carte Arduino UNO reliée à la box par un shield Ethernet. La télécommande se fera par l’intermédiaire du navigateur de votre mobile/tablette/PC relié à la box par le réseau Ethernet ou WiFi. De même, le navigateur affichera les données télémétriques, réduites ici à la température.

Ce scénario manque, certes, d’un peu d’envergure, mais c’est une base de travail suffisante. Sa réalisation est relativement peu coûteuse et facile à mettre en œuvre, d’autant que, pour le tutoriel, la commande du mécanisme d’ouverture sera seulement simulée par l’allumage ou l’extinction d’une LED, commandée par un bouton affiché dans votre navigateur.

Le système pourra facilement être étendu à la commande d’éclairage, d’électrovannes alimentant un arrosage ou une brumisation, en fonction de données issues de capteurs d’humidité du sol et de l’air… Enfin bref : on fait ce qu’on veut, et, bien sûr, on n’est pas limité à la culture des tomates.

Dans le cas présent, un asservissement classique aurait aussi bien fait l’affaire, mais il n’aurait pas pu servir de base à ce tutoriel.

À noter que la brumisation des tomates dans une serre est une très mauvaise idée. Cela crée les conditions idéales (chaleur et humidité) pour le développement des spores du Phytophtora infestans (mildiou pour les intimes).

III. Matériel utilisé

Pour rester conforme au scénario proposé, je vais continuer à faire référence à la serre, mais inutile d’en construire une : pour le tutoriel, on peut s’en passer.

III-A. Côté serre : le serveur

  • Le « cerveau » sera une carte Arduino UNO, donc, du très classique, facile à se procurer.

    Image non disponible
  • L’interfaçage réseau sera confié à un shield Ethernet2, construit autour de la puce Wiznet W5500, dont les caractéristiques techniques sont supérieures à celles du shield Ethernet, basé sur le chip W5100. Cela dit, si vous disposez déjà d’un shield Ethernet, il fera l’affaire. À partir de sa version 2.0, la bibliothèque officielle Arduino gère les puces W5100, W5200 et W5500. Le plus simple, donc, pour ne pas vous poser de question quant à la bibliothèque à utiliser, c'est de mettre à jour cette bibliothèque dans l'EDI Arduino, si ce n'est pas déjà fait.

    Image non disponible
  • Le capteur de température sera un LM35 en boîtier TO92. Son prix attractif et sa simplicité de mise en œuvre sont les raisons de mon choix.

    Image non disponible
  • Une LED standard accompagnée de sa résistance de polarisation.

  • Une plaque d’essai et des connecteurs en quantité suffisante.

La serre devra disposer de l’électricité et du réseau Ethernet (CPL).

Pour ceux qui n’ont pas d’a priori envers le matériel non officiel (mais légal, le projet est open source), il y a une possibilité particulièrement intéressante en matière de coût, d’encombrement et de simplicité de mise en œuvre, à l’empilage « Arduino UNO-shield Ethernet2 », et que vous pourrez trouver ici.

Image non disponible

Cette carte, d’après mes tests, est totalement compatible avec le matériel officiel. Elle est acceptée comme une carte Arduino-Genuino UNO par le gestionnaire de carte de l’EDI Arduino, et le sketch est parfaitement exécuté, sans modification d’aucune sorte. Elle peut donc remplacer, sans travail supplémentaire, le montage « officiel » de ce tutoriel, ainsi qu’être utilisée dans toute autre application mettant en œuvre l’Arduino UNO et le shield Ethernet2.

Quand je parle de « mes tests », je veux dire que tout ce que j’ai fait avec cette carte a fonctionné de la même manière qu’avec les cartes officielles. Je n’ai pas mené une batterie de tests exhaustive visant à « homologuer » le produit.

Enfin, une troisième possibilité encore meilleur marché, mais demandant un peu de travail d’adaptation (câblage), est la solution suivante :

Image non disponible

Le clone Arduino nano v3 est reconnu en tant qu’Arduino nano par le gestionnaire de cartes de l’IDE Arduino. Le module Ethernet communique avec la carte Arduino nano par l’intermédiaire du bus SPI de la même manière que le shield officiel avec la carte Arduino UNO.

Contrairement au nano v3 officiel, certains clones peuvent utiliser la puce CH340G ou CP2102 au lieu de la puce ATmega16U2 comme adaptateur USB↔UART, ce qui est le cas ici. Normalement, les OS récents et à jour disposent des pilotes associés. Si votre matériel ne fonctionne pas, c’est probablement que ce pilote n’est pas installé chez vous. Cliquez sur GNU-Linux, MacOS ou Windows pour télécharger le driver CH340G correspondant à votre OS, ou sur la page de téléchargement Silicon Labs pour le pilote CP2102, puis installez-le.

Quelle que soit la solution matérielle que vous aurez retenue, la partie logicielle sera la même : la télécommande que nous allons réaliser sera pleinement fonctionnelle dans tous les cas. Nous verrons cependant comment étendre les possibilités pour les deux premières, en utilisant les fonctionnalités offertes par la présence du lecteur de carte microSD.

En ce qui concerne la troisième solution, l’idée de départ était de lui adjoindre un lecteur de carte microSD « low cost » pour proposer trois configurations matérielles fonctionnellement équivalentes, mais dans trois gammes de prix distinctes.

Image non disponible

Las, si les modules microSD et Ethernet fonctionnent parfaitement séparément, ils entrent en conflit sur le bus SPI, et là, c’est le module microSD qui gagne. Je ne sais pas lequel des deux modules est responsable, mais il semblerait que ce soit le module microSD.

Adjoindre un lecteur « officiel » fait remonter le coût de la troisième configuration suffisamment près de celui de la seconde pour que ça ne vaille plus la peine. Donc, si on veut rester dans du « low cost », par exemple dans le cas de nombreux serveurs à installer, on se passera de module microSD. Cette configuration est adaptée à ce tutoriel, mais sera insuffisante pour la seconde partie.

Pour information, les coûts respectifs de ces trois configurations sont de l’ordre de 46 €, 22 € et 6 €, à la date de réalisation de ce tutoriel.

III-B. Côté télécommande : le client

  • Pour le développement et la mise au point, un PC de bureau ou un portable, reliés à une box par le réseau Ethernet ou WiFi.
  • Pour l’utilisation, une tablette ou un smartphone relié en WiFi à la box.
  • Au niveau logiciel, n’importe quel navigateur moderne, à jour, et implémentant JavaScript et CSS devrait faire le travail. À moins que vous ne soyez un inconditionnel du mode texte, évitez Lynx. Il est clair que vous pouvez l’utiliser (du moins sans le CSS), mais, dans ce cas, je vous suppose suffisamment compétent pour faire vous-même les modifications qui s’imposent.

III-C. Côté réseau : entre le serveur et le client

À titre indicatif, voici un exemple très classique de réseau domestique tel qu’il existe très probablement chez vous, à quelques détails près du genre nombre et type des appareils connectés :

Image non disponible

Vous pouvez, comme d’habitude, agrandir l’image, pour avoir une meilleure lecture des légendes. Tout ce qui se trouve au-dessous de la ligne pourpre horizontale ne concerne qu’internet et n’est présent dans ce schéma que pour bien distinguer la partie Ethernet de la partie Internet.

Le réseau que vous allez utiliser dans ce tutoriel est le réseau Ethernet, qui part de la box et distribue vos locaux, soit par l’intermédiaire du câble ou du réseau électrique avec la technologie CPL (courant porteur en ligne), soit grâce à la technologie WiFi, qui permet de vous passer de toute infrastructure matérielle.

Je ne connais pas votre installation, évidemment, mais je suis à peu près sûr d’une chose : vous êtes devant votre ordinateur et le matériel Arduino est à proximité. Dans le cas contraire, sur le plan ergonomique, vous aurez un problème. Par contre, votre box Internet n’est pas forcément accessible, et même si votre ordinateur peut se connecter en Wifi, le shield Ethernet 2 nécessite quant à lui une connexion RJ45.

Donc, pour essayer de clarifier la situation, je vous propose de réaliser, en fonction de votre installation et de votre matériel, un réseau équivalent au réseau minimum suivant.

Image non disponible

IV. Le serveur

IV-A. Définition

La notion de serveur informatique fait intervenir de nombreux concepts et je vous invite à consulter Wikipédia si vous voulez en avoir une idée générale. C’est un sujet trop vaste pour que je me risque, en quelques lignes, à en donner une définition. Je vais me contenter de le définir au travers de notre réalisation.

Le serveur est essentiellement constitué de deux parties distinctes :

  • une partie matérielle :

    • l’unité de traitement : ici la carte Arduino UNO,
    • une interface réseau, ici le shield Ethernet2 ;
  • une partie logicielle :

    • le sketch Arduino,
    • la bibliothèque Ethernet.

La partie matérielle ayant été traitée plus haut, ce chapitre ne va donc traiter que la partie logicielle, but de ce tutoriel.

À partir de maintenant, vous devez bien différencier les deux entités situées de part et d’autre du réseau :

  • le client : il sera, dans la suite de ce tutoriel, appelé client, navigateur ou télécommande, ce qui voudra dire la même chose.
  • Le serveur : concerne le côté « Arduino » et n’aura pas d’autre dénomination.

IV-B. Connexion au réseau

Les bus sont globalement définis par le protocole qu’ils utilisent, tels les bus SPI, IIC, CAN, etc. et l’infrastructure matérielle qui leur est propre.

On peut, en simplifiant, considérer un réseau comme étant l’appellation générique de différents bus ayant en commun une même architecture matérielle et utilisant divers protocoles. Ces protocoles sont hiérarchisés en plusieurs couches, depuis la couche physique jusqu’à la couche session, un peu comme un système d’exploitation.

Il n’est bien entendu pas question de détailler tout le processus. Nous ne nous intéresserons qu’à la dernière couche. Dans cette couche, vous avez plusieurs protocoles possibles, les plus connus du grand public étant :

  • FTP : protocole dédié au transfert de fichiers ;
  • SMTP et POP3 : protocoles dédiés à l’envoi et à la réception de courriels ;
  • HTTP : protocole dédié à la communication client-serveur.

Liste non exhaustive.

C’est le protocole HTTP (HyperText Transfert Protocol) qui va nous intéresser. Pour plus d’informations sur ce protocole, vous pouvez consulter la page Hypertext transfert protocol sur Wikipédia.

IV-B-1. Connexion minimale

Voici le code minimal pour vous connecter au réseau Ethernet :

 
Sélectionnez
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.
#include <Ethernet.h>

byte macSerre[] = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0};
IPAddress IPSerre(192,168,1,200);
EthernetServer serveurHTTP(80);

void setup()
{
  Ethernet.begin(macSerre, IPSerre);
  serveurHTTP.begin();
  Serial.begin(115200);
}

void loop()
{
  EthernetClient client = serveurHTTP.available();
  if (client)
  {
    if (client.connected())
    {
      while (client.available())
      {
        Serial.write(client.read());
      }
      client.stop();
    }
  }
}

Que fait ce code ? Rien en apparence, mais en réalité, tout est là ou presque, et, comme vous pouvez le constater, tout tient en quelques lignes. Analyse rapide :

 
Sélectionnez
1.
#include <Ethernet.h>

Chargement de la bibliothèque Ethernet.

La puce Ethernet communique avec le microcontrôleur par l’intermédiaire du bus SPI. Il n’est cependant pas nécessaire de charger explicitement la bibliothèque gérant ce bus, car celle-ci est déjà chargée par la bibliothèque Ethernet.

 
Sélectionnez
3.
4.
5.
byte macSerre[] = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0};
IPAddress IPSerre(192,168,1,200);
EthernetServer serveurHTTP(80);

Déclaration des constantes destinées à paramétrer votre connexion Ethernet :

  • macSerre[] : identifiant mac de votre serveur. Cet identifiant, bien que nécessaire au paramétrage, n’a pas de valeur imposée. Vous devez donc remplir ce tableau avec six octets quelconques, le seul impératif étant de vous assurer qu’aucun autre matériel, sur le réseau, n’a la même séquence ;
  • IPSerre() : adresse IP que vous souhaitez attribuer à votre serveur sur le réseau. Cette attribution est optionnelle. Si vous avez un routeur DHCP, ce qui est le cas la plupart du temps (box Internet), vous pouvez le laisser attribuer automatiquement une adresse dans la plage DHCP. Je conseille malgré tout de laisser la plage DHCP disponible pour les connexions « nomades », mais cela n’engage que moi. Le matériel que vous allez piloter étant « sédentaire », il est préférable que vous fixiez vous-même une adresse à l’extérieur de la plage DHCP. Connectez-vous au gestionnaire de votre box pour connaître les limites des plages. En ce qui me concerne, et pour vous donner un exemple concret, le serveur DHCP de ma Livebox réserve les adresses comprises entre 192.168.1.10 et 192.168.1.150, d’une part, et la Livebox elle-même occupe l’adresse 192.168.1.1 d’autre part. Les autres adresses sont donc disponibles (sauf celles que j’ai déjà attribuées, bien sûr), et j’ai choisi l’adresse 192.168.1.200 pour ce tutoriel.
  • ServeurHTTP() : port (porte logicielle) par lequel le matériel utilisant le protocole HTTP aura accès au système d’exploitation. Le port « 80 » est classiquement réservé à ce protocole, et nous l’utiliserons donc, mais ce n’est pas obligatoire.
 
Sélectionnez
9.
10.
11.
Ethernet.begin(macSerre, IPSerre);
serveurHTTP.begin();
Serial.begin(115200);

Dans la section « setup() », on trouve :

  • Ethernet.begin() : procédure d’initialisation de la connexion Ethernet avec les paramètres définis précédemment. Si vous avez opté pour une attribution automatique (DHCP) de l’adresse du serveur, vous ne devez fournir que l’adresse matérielle (macSerre) à cette procédure ;
  • serveurHTTP.begin() : démarrage du serveur ;
  • Serial.begin(115200) : initialisation de la liaison série pour visualiser les données relatives à la connexion en cours dans le moniteur série de l’IDE. Cette ligne est inutile pour la connexion proprement dite.
 
Sélectionnez
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
EthernetClient client = serveurHTTP.available();
if (client)
{
  if (client.connected())
  {
    while (client.available())
    {
      Serial.write(client.read());
    }
    client.stop();
  }
}

Dans la boucle principale, nous avons :

  • EthernetClient client = serveurHTTP.available() : teste à chaque itération une éventuelle tentative de connexion, et, dans l’affirmative, crée une instance EthernetClient pour ce nouveau client.
  • if (client) : si le client existe ;
  • if (client.connected()) : si le client est connecté ;
  • while (client.available()) : tant qu’il reste un caractère dans le tampon, on le lit avec client.Read() et on l’écrit sur le moniteur série avec Serial.write() ;
  • enfin, on ferme la connexion.

Téléversez ce sketch sur votre serveur puis lancez le moniteur série fourni par l’EDI. Réglez la vitesse de transmission à la valeur que vous avez choisie, ici 115 200 bd. À présent, saisissez, dans la zone ad hoc de votre navigateur, l’adresse IP que vous avez choisie pour ce tutoriel, ici 192.168.1.200 et connectez-vous.

À partir de maintenant, et sauf avis contraire, quand je vous demanderai de vous connecter, cela voudra dire :

  • Saisissez, dans la barre d’adresse de votre navigateur, l’adresse IP de votre serveur, que ce soit l’IP fixe que vous avez choisie ou l’IP attribuée par le serveur DHCP de votre routeur (box) ;
  • Appuyez sur la touche « ENTRÉE » de votre clavier, ou cliquez sur le bouton (ou la flèche) équivalent, en fonction de vos habitudes.

Vous devriez obtenir, mises à part les différences dues au navigateur utilisé, les affichages suivants :

Image non disponible

sur le navigateur, et :

Image non disponible

sur le moniteur série de l’EDI.

Comment analyser ceci ?

Déjà, il s’est passé quelque chose. C’est satisfaisant en soi. Le navigateur nous indique que la connexion a été réinitialisée, nous verrons pourquoi un peu plus loin, quant au moniteur, il affiche successivement dix fois le message suivant :

 
Sélectionnez
GET / HTTP/1.1 
Host: 192.168.1.200 
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:60.0) Gecko/20100101 Firefox/60.0 
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 
Accept-Language: fr-FR,en-US;q=0.7,en;q=0.3 
Accept-Encoding: gzip, deflate 
DNT: 1
Connection: keep-alive
Upgrade-Insecure-Requests: 1

Ce message est envoyé par le navigateur au serveur à chaque tentative de connexion de la part du client. Il peut différer de celui qui s’affiche sur votre moniteur, car votre contexte peut être différent du mien, notamment en ce qui concerne le navigateur, mais il aura vraiment un air de famille. Par contre, la première ligne, GET / HTTP/1.1, sera exactement la même. Du moins je vous le souhaite, car dans le cas contraire, nous avons un problème.

Pourquoi ce message s’affiche-t-il dix fois de suite. Eh bien ! votre navigateur sait qu’il peut y avoir de la « friture sur la ligne », si je puis employer cette vieille expression qui nous vient de l’époque de la TSF (Télégraphie Sans Fil). Il tente donc plusieurs fois de se connecter, jusqu’à obtenir un accusé de réception du serveur. Or, il se trouve que dans le sketch, nous n’avons rien prévu de ce genre. Alors, après dix tentatives infructueuses, le navigateur abandonne la partie, considérant que la ligne est en dérangement. Et c’est le résultat de cette impossibilité de se connecter qu’il affiche en disant que la connexion a été réinitialisée.

IV-B-2. Connexion fonctionnelle

En ce qui nous concerne, cette analyse est mauvaise. Les dix demandes de connexion envoyées par le client sont bien parvenues au serveur : la preuve en est donnée par les dix affichages dans le moniteur série. Vous allez donc simplement modifier votre sketch afin qu’il fasse parvenir au client un accusé de réception de sa demande. De plus, vous allez vous assurer qu’il s’agit bien d’une requête HTTP demandant l’envoi d’une page HTML en vérifiant que la première ligne est bien de la forme GET / HTTP/1.1.

Voici votre nouveau sketch :

 
Sélectionnez
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.
#include <Ethernet.h>

byte macSerre[] = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0};
IPAddress IPSerre(192,168,1,200);
EthernetServer serveurHTTP(80);

void setup()
{
  Ethernet.begin(macSerre, IPSerre);
  serveurHTTP.begin();
  Serial.begin(115200);
}

void loop()
{
  EthernetClient client = serveurHTTP.available();
  if (client)
  {
    if (client.connected())
    {
      String reception;
      while (client.available())
      {
        char carLu = client.read();
        if (carLu != 10)
        {
          reception += carLu;
        }
        else
        {
          break;
        }
      }
      Serial.println(reception);
      if (reception.startsWith("GET / HTTP/1.1"))
      {
        client.println(F("HTTP/1.1 200 OK"));
        client.println(F("Content-Type: text/html"));
        client.println(F("Connection: close"));
        client.println();
      }
      client.stop();
    }
  }
}

Détail des modifications :

 
Sélectionnez
21.
String reception;

Déclaration de la variable locale reception de type String (chaîne de caractères).

 
Sélectionnez
24.
char carLu = client.read();

Transfert du caractère disponible en sortie de tampon vers la variable locale carLu : il s’agit bien d’un transfert, à savoir que ce caractère va être copié depuis le tampon, collé dans la variable, puis supprimé du tampon, ce qui permet au caractère suivant de devenir disponible pour la lecture suivante. On comprend que ce mécanisme permet d’accéder successivement à tous les caractères contenus dans le tampon, tout en vidant celui-ci.

 
Sélectionnez
25.
26.
27.
28.
29.
30.
31.
32.
if (carLu != 10)
{
  reception += carLu;
}
else
{
  break;
}

Pourquoi ce test, alors que la ligne reception += carlu; suffit pour récupérer la chaîne reçue ? En fait, comme nous le verrons ultérieurement, seule la première ligne envoyée par le navigateur du client nous intéresse. Chaque ligne se termine par le caractère de code ASCII 10, appelé caractère de fin de ligne (LF pour Line Feed en anglais). Ce test, qui nous permet de détecter la fin de la première ligne, va donc également nous permettre d’empêcher la récupération des caractères inutiles contenus dans le tampon. Cela permet, d’une part, de gagner du temps, et, d’autre part, de ne stocker dans la variable reception que les renseignements utiles et donc d’éviter d’encombrer pour rien la mémoire vive dont la capacité n’est pas phénoménale. On utilise ensuite break; pour sortir de la boucle while, sinon tous les caractères, excepté ceux de fin de ligne, seront lus et stockés.

 
Sélectionnez
35.
36.
37.
38.
39.
40.
41.
if (reception.startsWith("GET / HTTP/1.1"))
{
  client.println(F("HTTP/1.1 200 OK"));
  client.println(F("Content-Type: text/html"));
  client.println(F("Connection: close"));
  client.println();
}

La modification principale est dans ce fragment de code :

  • le test sur la chaîne GET / HTTP/1.1 envoyée par le client, qu’on appellera « requête » à partir de maintenant, renseigne le serveur sur la nature de cette requête et sur la version du protocole utilisé. Elle fait partie du protocole HTTP et indique au serveur qu’on attend de lui l’envoi d’une ressource, quelle qu’elle soit, dans la limite de sa compatibilité avec le protocole en question, ici HTTP/1.1. Vous ne pourrez pas envoyer des carottes… encore qu’avec l’évolution des techniques, allez savoir !
    Cette requête utilise la méthode GET et serait généralement suivie, lors d’une navigation standard, de l’adresse d’une page WEB (URL). Notre serveur étant un peu atypique, ce n’est pas exactement le cas ici, où seule l’adresse du serveur est utile ;
  • ce renseignement va être exploité par le serveur pour construire l’accusé de réception qu’attend le client. Pour notre application, cet accusé de réception ne nécessite que quatre lignes, mais il y en aura souvent plus dans le cas général :

    • le serveur envoie d’abord la chaîne HTTP/1.1 200 OK indiquant au client que la demande lui est bien parvenue, qu’elle a été comprise et qu’elle va être exécutée. Quand vous demandez l’affichage d’une page WEB, c’est le logiciel serveur, par exemple « Apache », qui se charge de cette réponse. Si la ressource n’est pas disponible, le serveur remplacera le nombre 200 par le nombre 404, que vous avez certainement déjà eu l’occasion de voir affiché par votre navigateur. Il ne s’agit pas, dans ce cas, d’un problème de connexion, mais, dans la plupart des cas, d’un lien obsolète,
    • le serveur va ensuite indiquer la nature de ce qu’il va envoyer, en respectant bien entendu le format adéquat. Comme nous allons, dans un premier temps, envoyer une page HTML, la nature de l’envoi sera text/html, text parce qu’il s’agit de texte et html pour indiquer que ce texte représente du code HTML, que le navigateur devra interpréter comme tel,
    • la troisième ligne indique que la connexion sera fermée en fin de transaction,
    • quant à la quatrième ligne, vide, elle est obligatoire, car elle sert de délimiteur pour indiquer au client la fin de l’accusé de réception et le début de l’envoi de l’objet de la requête.

Téléversez ce sketch et connectez-vous au serveur. Vous devriez obtenir ceci :

Image non disponible
Image non disponible

Le navigateur n’indique plus de problème de connexion, et le moniteur n’affiche plus qu’une seule fois le message, celui-ci réduit à la première ligne qui, seule, nous intéresse.

Bon ! c’est pas tout : ça fonctionne peut-être bien, mais ça n’affiche pas grand-chose. Effectivement, vous n’avez rien envoyé après l’accusé de réception : c’est votre faute. Non, je plaisante. Ne vous inquiétez pas, on va arranger ça.

IV-B-3. Un peu de HTML

Que ceux qui ne connaissent pas le HTML se rassurent : l’usage que nous allons faire de ce langage, dans le cadre de ce tutoriel, sera très limité. Cela dit, la présentation en souffrira, mais ce n’est pas l’objet ici.

Pour faire très court, le HTML (HyperText Markup Langage) est, à l’origine, un langage de description de page basé sur l’utilisation de balises, en quelque sorte des « instructions », pour faire un parallèle avec un langage de programmation, et qui implémente le protocole HTTP pour permettre la navigation d’une zone de texte à une autre, qu’elles soient situées dans le même document ou dans des documents différents, et que ces documents se trouvent sur un même PC ou sur des PC reliés par un réseau.

Un fichier HTML est un simple fichier texte, dont l’extension est « .html », ce qui fait que, pour coder du HTML, un éditeur de texte suffit. L’utilisation d’un traitement de texte est à proscrire dans la mesure où le fichier généré par celui-ci n’est pas un fichier texte. Considérant ce que vous allez coder, n’importe quel éditeur fera l’affaire, mais, si vous disposez d’un éditeur offrant la coloration syntaxique, ce n’en sera que mieux.

D’abord, et je ne devrais pas le dire, HTML est très permissif, ou du moins, les moteurs de rendu des navigateurs ne sont pas à cheval sur la syntaxe. Ce sont des interpréteurs et ils interprètent au sens premier du terme. Ce qu’ils comprennent, ils le traduisent et l’utilisent, le reste, ils l’ignorent.

Je vous propose de saisir (copier) le code HTML suivant dans un éditeur de texte :

 
Sélectionnez
<!DOCTYPE html>

<html lang="fr">

  <meta charset="utf-8" />
  <title>Ma première page</title>

  <body>
    <div>
      <h3>Informations sur la page</h3>
      <p>Cette page HTML ne contient qu’un peu de texte, avec un minimum de mise en forme pour l’exemple. Elle fait appel aux fonctionnalités de base d’un navigateur, à savoir afficher du texte et établir un hyperlien. Sa syntaxe satisfait aux exigences du W3C.</p>
      <p>Le <a href="https://fr.wikipedia.org/wiki/World_Wide_Web_Consortium">W3C</a> (<i>World Wide Web Consortium</i>), est l’organisme ayant en charge la promotion et la standardisation du Web.</p>
    </div>
  </body>

</html>

Pour l’anecdote, le code suivant, bien que ne respectant pas la syntaxe HTML5, fera le même travail :

 
Sélectionnez
<meta charset="utf-8" />
<title>Ma première page</title>
<h3>Informations sur la page</h3>
<p>Cette page HTML ne contient qu’un peu de texte, avec un minimum mise en forme pour l’exemple. Elle fait appel aux fonctionnalités de base d’un navigateur, à savoir afficher du texte et établir un hyperlien. Sa syntaxe satisfait aux exigences du W3C.</p>
<p>Le <a href="https://fr.wikipedia.org/wiki/World_Wide_Web_Consortium">W3C</a> (<i>World Wide Web Consortium</i>), est l’organisme ayant en charge la promotion et la standardisation du Web.</p>

Je vous conseille toutefois de rester prudent. Cela fonctionne ici, car nous faisons appel à des fonctionnalités de base, mais si vous voulez aller plus loin avec ce langage, vous aurez des problèmes un jour ou l’autre. Autant adopter tout de suite de bonnes pratiques.

Il n’en demeure pas moins que dans le cas d’un serveur réalisé sur une base Arduino UNO, étant donné le peu d’espace mémoire dont on dispose, il peut être nécessaire de gagner de la place en prenant quelques libertés avec ce principe, mais ça doit vraiment rester une exception faite dans un but bien précis et non pas par laxisme. En ce qui concerne ce tutoriel, nous pouvons rester rigoureux.

Comme les codes que nous allons écrire sont très courts, vous pouvez, si vous le désirez, les vérifier en utilisant le validateur HTML en ligne proposé par le W3C.

Voici quelques explications :

La structure de base d’un fichier HTML5 est la suivante :

 
Sélectionnez
<!DOCTYPE html>

<html>

  <title></title>

  <body>

  </body>

</html>
  • la ligne <!DOCTYPE html> indique au navigateur que le langage utilisé ici est le HTML5 (voir ici) ;
  • l’ensemble du code se trouve entre les balises <html> et </html> (voir ici) ;
  • le texte situé entre les balises <title> et </title> représente le libellé de l’onglet de votre navigateur qui concerne cette page (voir ici). Il peut également apparaître dans la barre de titre ;
  • l’intégralité de la description de la page se trouve entre les balises <body> et </body> (voir ici).

En ce qui concerne notre page :

  • la ligne <meta charset="utf-8" /> indique au navigateur que les caractères sont encodés en UTF-8. Si l’éditeur que vous utilisez encode dans un autre format, vous devrez spécifier ce format, à défaut de quoi les diacritiques ne seront pas traités correctement (voir ici et ) ;
  • les balises <div> et </div> délimitent un bloc cohérent au niveau de la présentation de la page (voir ici) ;
  • les balises <h3> et </h3> déterminent un niveau de titre et formatent le texte inséré entre elles en conséquence (voir ici) ;
  • les balises <p> et </p> délimitent un paragraphe (voir ici) ;
  • les balises <i> et </i> servent à mettre en italique le texte qu’elles encadrent ;
  • les balises <a> et </a> servent à définir un hyperlien en utilisant l’« attribut » href (voir ici et ).

L’impact des balises sur le texte qu’elles concernent peut être modifié globalement ou individuellement en utilisant des « attributs », mais je n’irai pas plus loin sur ce chapitre. Soyez persuadé que les possibilités sont vastes, et je vous encourage à creuser le sujet.

Sauvegardez ce texte dans un fichier, par exemple « test.html », et ouvrez-le dans votre navigateur pour voir ce que ça donne.

Maintenant que vous avez votre page HTML, comment allez-vous y accéder depuis le client ? Parce que c’est quand même ça le but de la manœuvre.

Eh bien ! vous allez procéder de la même façon que pour récupérer l’accusé de réception, en envoyant le texte du fichier HTML au client en utilisant l’instruction client.println().

Une petite précision en passant :

peut-être avez-vous remarqué que l’argument fourni à l’instruction client.println() était de la forme (F("texte")). Pour ceux qui ne le savent pas, cette technique permet de forcer la mise en mémoire flash des chaînes de caractères, d’où le F, c’est-à-dire en mémoire programme, plutôt qu’en RAM.

C’est indispensable dans notre cas, car la RAM ne faisant que 2 048 octets, elle serait très rapidement saturée. Il est évident que cela se fait au détriment de la mémoire programme, et qu’on peut déboucher sur l’impossibilité de mener à bien une réalisation. Tout système a ses limites. Il conviendra dans ce cas, soit de revoir ses prétentions à la baisse, soit d’utiliser un matériel de plus grande capacité.

Modifiez le sketch précédent en complétant l’accusé de réception comme suit :

 
Sélectionnez
client.println(F("HTTP/1.1 200 OK"));
client.println(F("Content-Type: text/html"));
client.println(F("Connection: close"));
client.println();
client.println(F( "<!DOCTYPE html>"
                  "<html lang=\"fr\">"
                  "<meta charset=\"utf-8\" />"
                  "<title>Ma première page</title>"
                  "<body>"
                  "<div>"
                  "<h3>Informations sur la page</h3>"
                  "<p>Cette page HTML ne contient qu’un peu de texte, avec un minimum de mise en forme pour l’exemple. Elle fait appel aux fonctionnalités de base d'un navigateur, à savoir afficher du texte et établir un hyperlien. Sa syntaxe satisfait aux exigences du W3C.</p>"
                  "<p>Le <a href=\"https://fr.wikipedia.org/wiki/World_Wide_Web_Consortium\">W3C</a> (<i>World Wide Web Consortium</i>), est l’organisme ayant en charge la promotion et la standardisation du Web.</p>"
                  "</div>"
                  "</body>"
                  "</html>"));

J’ai disposé la chaîne constituant le fichier HTML de telle manière qu’elle soit facile à lire dans l’éditeur, ce qui facilite l’écriture quand on construit la page directement dans le sketch, mais ce n’est pas obligatoire.

J’attire votre attention sur la ligne :

<a href=\"https://fr.wikipedia.org/wiki/World_Wide_Web_Consortium\">W3C</a>

Cette ligne est de la forme :

<balise attribut="valeur">texte</balise>

Comme elle doit être envoyée sous la forme d’une chaîne, vous êtes obligé de la mettre entre guillemets. Or, elle contient elle-même des guillemets que le compilateur va interpréter non pas comme des caractères à envoyer, mais comme des délimiteurs de chaîne, ce qui va poser problème.

La solution consiste à faire précéder les guillemets représentant des caractères à envoyer par le caractère d’échappement « \ » (antislash), qui force le compilateur à interpréter le caractère qui suit comme un caractère ordinaire.

Téléversez le nouveau sketch et connectez-vous comme d’habitude. Votre navigateur devrait afficher ceci :

Image non disponible

Vérifiez le libellé de l’onglet et n’hésitez pas à cliquer sur le lien. Il vous enverra sur la page Wikipédia consacrée au W3C. Par curiosité, faites un clic droit sur la page et sélectionnez « Code source de la page » ou ce qui y ressemble dans votre navigateur. Vous constaterez que tout le code est sur une seule ligne. La façon dont il est présenté dans l’EDI n’a donc rien changé véritablement.

Il est possible, voire probable, que vous soyez actuellement devant votre PC ou votre portable. Il serait cependant bon de vérifier que ça fonctionne avec un smartphone ou une tablette : c’est le moment.

Maintenant que vous savez envoyer une page HTML, c’est-à-dire du texte (ou plutôt des octets, puisque c’est tout ce que sait acheminer le réseau), depuis le serveur vers le client, il va être intéressant d’envisager la possibilité de faire l’opération inverse, à savoir envoyer du texte depuis le client vers le serveur.

IV-B-4. Dialogue entre le client et le serveur

Vous savez déjà que le client peut envoyer du texte au serveur. Souvenez-vous des messages affichés dans le moniteur du serveur (le moniteur série de l’EDI) : il s’agissait bien de texte envoyé par le navigateur, donc par le client, lors de sa demande de connexion. Souvenez-vous également que j’avais précisé que seule la première ligne allait nous intéresser, et que nous avions fait en sorte de ne recevoir qu’elle.

Je vous propose une petite expérience : connectez-vous à votre serveur, mais en ajoutant /toto à votre adresse. Pour moi, ça donnera 192.168.1.200/toto. Vous devriez obtenir quelque chose comme :

Image non disponible

La connexion a été réinitialisée. C’est pourtant la bonne adresse, et jusque-là, ça ne fonctionnait pas si mal.

Mais voici l’explication :

Image non disponible

Il y a effectivement eu dix tentatives de connexion, à l’issue desquelles le navigateur, ne recevant pas l’accusé de réception, rend compte de l’impossibilité de se connecter.

Rien de plus normal ! Comme vous pouvez le constater, notre « toto » (qu’est-ce qu’on peut le faire travailler, ce brave toto) est parvenu au serveur, incorporé à la première ligne du message envoyé par le client lors de sa demande de connexion. Cela nous montre comment on va pouvoir entamer le dialogue, seulement la conséquence est que notre test :

 
Sélectionnez
if (reception.startsWith("GET / HTTP/1.1")

n’est plus valable. Mais, de la même manière que « toute casserole trouve son couvercle », tout problème a sa solution (du moins quand ça veut rigoler).

Qu’est-ce qui nous intéresse vraiment dans ce début de message ?

  • HTTP/1.1 ? Non ! C’est simplement votre navigateur qui informe votre serveur qu’il utilise ce protocole pour causer avec lui. Comme, dans le cadre de votre application, ce renseignement ne sera l’objet d’aucun traitement, vous n’avez pas besoin de tester sa présence.
  • GET ? Ben non, pas plus en fait, dans la mesure où, toujours dans le cadre de votre application, vous n’utiliserez que cette méthode. Le fonctionnement de cette méthode, comme vous venez de le voir, consiste simplement à ajouter la requête à l’URL. Une URL pouvant contenir jusqu’à 3000 caractères environ, vous avez de la marge.

En résumé, techniquement parlant, ce test est inutile. Nous l’utiliserons quand même. Vous allez probablement vous poser la question : « Qu’est-ce qu’il nous fait là, pépère ? ». Je me la pose moi-même assez souvent.

La prudence nous impose d’effectuer un test pour nous assurer de la bonne réception de la requête (sans oublier toutefois que le test en question n’offre pas une sécurité absolue). Le problème est que nous aurons affaire à deux types de requêtes :

  • lors de la connexion, la seule chose que l’on demande au serveur est l’envoi de la page. Le message reçu sera GET / HTTP/1.1 ;
  • les requêtes suivantes seront des commandes spécifiques, et le message reçu sera de la forme GET /?toto HTTP/1.1.

Nous modifierons donc notre test comme suit :

 
Sélectionnez
reception.trim();
if ((reception.startsWith("GET /") && (reception.endsWith(" HTTP/1.1"))

La première ligne supprime les espaces et les caractères non imprimables, s’il y en a, situés au début et à la fin de la chaîne reception. En l’occurrence, il y a un caractère non imprimable en fin de chaîne, et sans cette précaution, le test ne passe pas.

La seconde ligne parle d’elle-même. Elle vérifie que la chaîne reçue commence par GET / et se termine par HTTP/1.1, ce qui fait que, qu’il y ait ou non un message inséré entre ces deux portions de chaîne, mais à la condition que cette chaîne soit présente, entière ou encadrant le message, le test sera passé.

Maintenant que les principes de fonctionnement sont posés, nous pouvons passer à une première application pratique.

V. Télécommande

Pour permettre à ceux, s’il y en a, qui ne disposent pas d’une serre équipée, nous allons restreindre nos prétentions à la commande d’une LED. Donc, avant tout, et pour se fixer les idées, un petit montage me semble un bon compromis :

Image non disponible

L’anode de la LED est reliée à la broche 2, quant à la cathode, elle est reliée à la masse au travers d’une résistance de 220 Ω. L’allumage nécessitera donc l’application d’un état haut sur la broche 2.

Si vous préférez, vous pouvez, pour cet exercice, utiliser la LED intégrée à la carte Arduino UNO, en remplaçant la broche 2 par la broche 13.

V-A. L’interface de commande

Il s’agit de la page HTML qui va vous permettre de transmettre la commande d’allumage ou d’extinction de la LED.

Saisissez (copiez) le code suivant dans un éditeur :

 
Sélectionnez
<!DOCTYPE html>
<html lang="fr">
<meta charset="utf-8" />
<title>Télécommande</title>
<body>
<form  method="get">
<p style="font-size: 5em">Cliquez sur un bouton</p>
<input style="font-size: 10em" type="submit" value="ON" name="on" />
<input style="font-size: 10em" type="submit" value="OFF" name="off" />
</form>
<p style="color: rgb(125,0,0); font-size: 5em">LED éteinte</p>
</body>
</html>

Sauvegardez-le dans un fichier, « interface .html » par exemple, et ouvrez-le dans votre navigateur. Vous allez obtenir quelque chose qui ressemble à ceci :

Image non disponible

La première constatation qui s’impose, c’est qu’au niveau de l’affichage, on n’a pas fait dans la dentelle. Pourquoi un affichage aussi monstrueux ? « J’vais vous l’dire, moi, j’vais vous l’dire » ;-). Si, au lieu de vous connecter avec votre PC ou votre portable, vous vous connectez avec votre smartphone, vous allez tout de suite comprendre. Et, vu que la finalité est quand même d’utiliser un smartphone ou une tablette comme télécommande, autant adapter tout de suite l’affichage à ce format. Il y a d’autres façons de procéder, un peu plus subtiles, mais pour l’instant, nous nous contenterons de ça. Cela dit, avant de vous connecter, il faudra écrire le sketch.

Explication sur les nouveaux éléments HTML utilisés :

Comme je l’ai dit plus haut, le comportement des balises peut être modifié grâce à ce que l’on nomme des « attributs ». Ici, nous modifions par exemple le comportement de la balise <p> avec l’attribut style="" (voir ici). Cet attribut permet d’insérer du code de « mise en forme » directement au niveau de la balise dans le code HTML. C’est à la fois riche en possibilités et déconseillé depuis l’avènement du CSS qui facilite grandement le développement et la maintenance. Comme, pour l’instant, on ne peut pas techniquement utiliser le CSS, on gardera la technique du style en ligne (inline).

L’attribut style peut contenir énormément de paramètres, séparés par un « ; » (point virgule). Ici, par exemple, style="font-size : 5em" permet de modifier la taille de la fonte du texte affiché par la première balise <p> (voir ici). Pour la seconde, on modifie également la couleur du texte affiché (voir ici).

Voyons à présent les deux nouvelles balises :

  • <form></form> : cette balise est un conteneur dans lequel vous pouvez placer d’autres balises offrant la possibilité à l’utilisateur d’interagir avec le serveur. Son attribut method permet de définir la méthode d’envoi utilisée, en l’occurrence method="get", que l’on retrouve dans la chaîne GET / HTTP/1.1. La méthode get étant la méthode par défaut, on peut omettre ce paramètre pour gagner de la place (voir ici) ;
  • <input /> : balise multiusage fournissant une entrée utilisateur. Selon le type choisi, cela peut être des boutons pour déclencher des actions, des listes déroulantes pour effectuer des choix, des zones de texte à remplir, etc. (voir ici et ). Le type submit choisi ici fournit un bouton dont la fonction est de soumettre (envoyer) au serveur l’ensemble de la fiche (form) dans laquelle il est déclaré, avec la méthode indiquée. Son usage est ici détourné. Lorsqu’on clique sur le bouton, il va envoyer son nom (name) et sa valeur (value), sous la forme ?name=value. Comme on a envoyé toto précédemment. C’est suffisant pour nous.
    Les boutons sont automatiquement adaptés à la taille du texte qu’ils contiennent, et donc, en modifiant la taille de la fonte, on modifie la taille des boutons.

Voici la nouvelle page remplaçant celle du sketch précédent :

 
Sélectionnez
client.println(F("HTTP/1.1 200 OK"));
client.println(F("Content-Type: text/html"));
client.println(F("Connection: close"));
client.println();
client.println(F( "<!DOCTYPE html>"
                  "<html lang=\"fr\">"
                  "<meta charset=\"utf-8\" />"
                  "<title>Télécommande</title>"
                  "<body>"
                  "<form  method=\"get\">"
                  "<p style=\"font-size: 5em\">Cliquez sur un bouton</p>"
                  "<input style=\"font-size: 10em\" type=\"submit\" value=\"ON\" name=\"on\">"
                  "<input style=\"font-size: 10em\" type=\"submit\" value=\"OFF\" name=\"off\">"
                   "<p style=\"color: rgb(125,0,0); font-size: 5em\">LED éteinte</p>"
                   "</form>"
                   "</body>"
                   "</html>"));

Téléversez ce sketch et connectez-vous. Regardez les modifications apportées à l’URL quand vous cliquez sur les boutons et comparez-les aux textes envoyés au serveur en utilisant le moniteur série :

  • 192.168.1.200/?on=ON : envoi GET /?on=ON HTTP/1.1 au serveur ;
  • 192.168.1.200/?off=OFF : envoi GET /?off=OFF HTTP/1.1 au serveur.

Donc, si vous avez bien suivi, il va suffire de regarder, après le test concernant la validité du message, bien sûr, si ce message contient on=ON ou off=OFF, pour savoir si vous devez allumer ou éteindre la LED.

C’est ce que vont faire les deux lignes suivantes :

 
Sélectionnez
if (reception.indexOf("on=ON") != -1) digitalWrite(LED, HIGH);
else if (reception.indexOf("off=OFF") != -1) digitalWrite(LED, LOW);

à placer juste avant l’envoi de la page HTML.

Il ne faudra pas oublier de déclarer la LED sur la broche 2 et de la configurer en sortie :

 
Sélectionnez
const int LED = 2;

void setup()
{
  //
  //
  pinMode(LED, OUTPUT);
  //
}

Téléversez ce sketch et connectez-vous. Les boutons sont opérationnels. Faites également les tests avec un smartphone ou une tablette. Ça commence à prendre forme, non ?

V-B. Retour d’information

C’est pas tout ça : vous savez que ça fonctionne parce que la LED est sous vos yeux, mais si vous commandez à distance l’ouverture du vasistas de votre serre, vous n’êtes pas forcément en mesure d’apprécier le résultat de l’opération. C’est pourtant un renseignement qu’il serait intéressant de pouvoir récupérer.

Dans la réalité, vous avez probablement utilisé des commutateurs de fin de course (microswitch) pour stopper le moteur quand les positions extrêmes du vasistas, ouvert ou fermé, sont atteintes. Il vous suffit de raccorder chacune des sorties de ces commutateurs à une broche différente de l’Arduino UNO et de tester l’état de ces broches, puis envoyer au client le résultat de ces tests.

Dans cette simulation, on se contentera de renvoyer l’état de la LED, mais on utilisera rigoureusement le même procédé.

Dans notre code, la ligne responsable de l’affichage « LED éteinte » est la ligne :

 
Sélectionnez
"<p style=\"color: rgb(125,0,0); font-size: 5em\">LED éteinte</p>"

Deux remarques :

  1. Tant qu’on ne change pas cette ligne, l’affichage n’a aucune raison de changer, quel que soit l’état de la LED ;
  2. Tout le reste n’ayant aucune raison de changer, seule cette ligne est à modifier.

Nous allons donc envoyer la page en trois fragments :

  1. Premier fragment, le début de la page :

     
    Sélectionnez
    client.println(F("HTTP/1.1 200 OK"));
    client.println(F("Content-Type: text/html"));
    client.println(F("Connection: close"));
    client.println();
    client.println(F( "<!DOCTYPE html>"
                      "<html lang=\"fr\">"
                      "<meta charset=\"utf-8\" />"
                      "<title>Télécommande</title>"
                      "<body>"
                      "<form  method=\"get\">"
                      "<p style=\"font-size: 5em\">Cliquez sur un bouton</p>"
                      "<input style=\"font-size: 10em\" type=\"submit\" value=\"ON\" name=\"on\">"
                      "<input style=\"font-size: 10em\" type=\"submit\" value=\"OFF\" name=\"off\">"));
  2. Deuxième fragment, la ligne isolée : le but de la manœuvre est d’afficher « LED éteinte » ou « LED allumée » en fonction de l’état de la LED. Il nous faut donc tester l’état de la LED, construire la chaîne codant l’affichage correspondant et envoyer cette chaîne. C’est ce que réalise cette portion de code :

     
    Sélectionnez
    if (digitalRead(LED) == HIGH) client.println("<p style=\"color: rgb(255,0,0); font-size: 5em\">LED allumée</p>");
    else if (digitalRead(LED) == LOW) client.println("<p style=\"color: rgb(125,0,0); font-size: 5em\">LED éteinte</p>");
  3. Enfin troisième fragment, la fin de la page :

     
    Sélectionnez
    client.println(F( "</form>"
                      "</body>"
                      "</html>"));

V-C. Résumé

Voici, pour résumer le chapitre 5, l’intégralité du sketch.

 
Sélectionnez
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.
58.
59.
60.
61.
62.
63.
64.
#include <Ethernet.h>

byte macSerre[] = {0x90, 0xA2, 0xDA, 0x10, 0x4F, 0x25};
IPAddress IPSerre(192,168,1,200);
EthernetServer serveurHTTP(80);

const int LED = 2;

void setup()
{
  Ethernet.begin(macSerre, IPSerre);
  serveurHTTP.begin();
  pinMode(LED, OUTPUT);
  Serial.begin(115200);
}

void loop()
{
  EthernetClient client = serveurHTTP.available();
  if (client)
  {
    if (client.connected())
    {
      String reception;
      while (client.available())
      {
        char carLu = client.read();
        if (carLu != 10)        {
          reception += carLu;
        }
        else
        {
          break;
        }
      }
      Serial.println(reception);
      reception.trim();
      if ((reception.startsWith("GET /")) && (reception.endsWith(" HTTP/1.1")))
      {
        if (reception.indexOf("on=ON") != -1) digitalWrite(LED, HIGH);
        else if (reception.indexOf("off=OFF") != -1) digitalWrite(LED, LOW);     
        client.println(F("HTTP/1.1 200 OK"));
        client.println(F("Content-Type: text/html"));
        client.println(F("Connection: close"));
        client.println();
        client.println(F( "<!DOCTYPE html>"
                          "<html lang=\"fr\">"
                          "<meta charset=\"utf-8\" />"
                          "<title>Télécommande</title>"
                          "<body>"
                          "<form  method=\"get\">"
                          "<p style=\"font-size: 5em\">Cliquez sur un bouton</p>"
                          "<input style=\"font-size: 10em\" type=\"submit\" value=\"ON\" name=\"on\">"
                          "<input style=\"font-size: 10em\" type=\"submit\" value=\"OFF\" name=\"off\">"));
        if (digitalRead(LED) == HIGH) client.println("<p style=\"color: rgb(255,0,0); font-size: 5em\">LED allumée</p>");
        else if (digitalRead(LED) == LOW) client.println("<p style=\"color: rgb(125,0,0); font-size: 5em\">LED éteinte</p>");
        client.println(F( "</form>"
                          "</body>"
                          "</html>"));
      }
      client.stop();
    }
  }
}

IMPORTANT :

ce code, écrit en langage Arduino, tournant sur notre « petit serveur » et destiné à construire la page HTML demandée par le client, est strictement l’équivalent d’un programme écrit en PHP et tournant sur un serveur pour grandes personnes comme Apache. Il va même plus loin dans la mesure où, grâce aux spécificités de ce même « petit serveur, il permet de piloter du matériel.

Pas mal pour quelques euros, même en tenant compte d’une légère différence d’échelle.

La partie télécommande est terminée. Nous allons pouvoir aborder la partie télémétrie.

VI. Télémétrie

Le titre de ce chapitre est peut-être un peu prétentieux, eu égard à la modestie des moyens mis en œuvre, mais il s’agit bien de télémétrie, au sens informatique du terme, à savoir la récupération des données physiques relatives à un système déporté, à fins de traitement ou d’affichage.

Ici, la donnée en question sera la température ambiante régnant dans le voisinage du serveur, donc dans la serre, récupérée par celui-ci à l’aide d’un capteur et envoyée au client pour affichage et prise de décision quant à l’ouverture du vasistas. Scénario modeste, certes, comme stipulé précédemment, mais une fois compris et atteint, il peut être étendu à une multitude de mesures, dans une multitude de situations, la limitation étant liée à la capacité du système en lui-même et à l’imagination du gestionnaire.

Nous allons donc, dans ce tutoriel, utiliser un capteur de température LM35 relié à une carte Arduino UNO par l’intermédiaire d’une plaque d’essais. Jusque-là, rien de bien exceptionnel. Ce capteur est banal et sa mise en œuvre d’une extrême simplicité. Vous le trouverez, entre autres, conditionné en boîtier TO92 pour environ 0,50 € en Chine, port compris, et pour environ 2,00 € en France, port en sus, la grande différence étant le temps d’acheminement.

Ce qui va changer par rapport à l’utilisation classique de ce genre de capteurs, c’est qu’ici, vous n’allez pas utiliser les afficheurs habituels dévolus à ce genre d’application, LED 7-segments ou afficheur LCD, pour afficher la température qu’il va mesurer. Vous allez afficher cette température dans la fenêtre de votre navigateur internet préféré, en faisant transiter l’information par le réseau Ethernet local, raccordé à votre box internet.

Mais n’anticipons pas, commençons déjà par nous familiariser avec le capteur LM35.

VI-A. Mise en œuvre du capteur LM35

VI-A-1. Carte d’identité biométrique du bonhomme

Voici une présentation rapide du composant :

  • boîtier TO92 (3 pins) :

    Image non disponible
  • Disponible également dans les boîtiers TO-CAN (3), TO-220 (3) et SOIC (8) ;

  • brochage du boîtier utilisé :

    Image non disponible
  • J’attire votre attention sur le fait qu’il s’agit d’une vue du dessous. Lors de mes premiers essais avec ce composant, il y a quelques années, j’étais passé à côté de ce détail et je m’étais gratté la tête un moment avant de comprendre. Le composant ne m’en a pas voulu : il est plutôt robuste ;

  • application typique :

    Image non disponible
  • Utilisé tel quel, la plage de température couverte s’étend de 2 °C à 150 °C ;

  • gabarit du boîtier utilisé :

    Image non disponible
  • Les mesures sont en millimètre (mm).

Ce composant est un circuit intégré dont la fonction est de mesurer la température ambiante et de générer, sur la broche de sortie (OUTPUT), une tension proportionnelle à cette température. La tension en question est généralement destinée à alimenter, après traitement, un dispositif d’affichage ou de régulation.

Voilà ! Vous savez tous de ce petit composant. Enfin quand je dis « tout », je suis loin du compte, mais c’est tout ce dont nous avons besoin pour ce tutoriel. Je vous conseille la lecture de son datasheet : vous verrez que l’adjonction de quelques composants permet d’aller plus loin.

VI-A-2. Le convertisseur analogique-numérique ou CAN

Comme on vient de le voir, le LM35 fournit, en sortie, une tension proportionnelle à la température mesurée. Le problème est que la tension est une grandeur analogique et qu’elle n’est pas manipulable en tant que telle par un système informatique qui ne connaît que le binaire. Il faut donc trouver un moyen d’exprimer cette grandeur dans cette base. Par chance, le circuit ATmega328P, microcontrôleur sur lequel est basé l’Arduino UNO, implémente un dispositif dont c’est justement la fonction : le convertisseur analogique-numérique (CAN, ou ADC pour Analog to Digital Converter). Cette fonctionnalité n’est accessible que sur les broches A0 à A5. Les broches GPIO 0 à 13 ne font pas partie du club.

La première des choses à faire va donc être de câbler le LM35 en conséquence. Je vous propose pour cela de réaliser la maquette suivante :

Image non disponible

Difficile de faire plus simple : outre les broches + et que vous allez raccorder à l’alimentation, la broche OUTPUT doit être raccordée à l’une des entrées analogique de l’Arduino UNO. Je vous propose arbitrairement la broche A0.

Le convertisseur analogique-numérique (CAN) de l’Arduino UNO a une résolution de 10 bits, c’est-à-dire qu’il va retourner une valeur, comprise entre 0 et 1023 (210-1), représentant la proportion, en 1024ième, entre la tension présente sur son entrée et une tension de référence.

Soit N la valeur renvoyée par le convertisseur analogique-numérique, VLM la tension fournie par le capteur LM35 et VREF la tension de référence, on a la relation :

Image non disponible (1)

Nous déduisons naturellement de (1) la relation suivante :

Image non disponible (2)

On sait que la tension de sortie augmente de 10 mV, soit 0,01 V quand la température augmente de 1 °C et qu’elle est de 0 V à 0 °C (c’est du moins ce que l’on va considérer).
Si on appelle T la température, on aura donc la relation :

Image non disponible

ou encore

Image non disponible (3)

On tire de (2) et (3) la relation :

Image non disponible (4)

Par défaut, à moins d’appliquer une tension de référence différente sur sa broche AREF, la tension de référence utilisée par l’Arduino UNO est le 5 V de son alimentation. La relation (4) devient :

Image non disponible

formule que nous allons utiliser pour calculer notre température en fonction de N, valeur renvoyée par la ligne de code :

 
Sélectionnez
N = analogRead(numBroche);

numBroche étant le numéro la broche analogique affectée à la sortie du capteur LM35, c’est-à-dire 0 en ce qui nous concerne, puisqu’on a opté pour la broche A0.

Petite remarque en ce qui concerne le numéro avec lequel je référence ici les broches A0 à A5, à savoir 0 à 5. On pourrait imaginer un conflit avec les broches GPIO 0 à 5. En fait, il n’y a pas d’ambiguïté. Les broches GPIO ne supportent pas la fonction analogRead(), ce qui fait qu’analogRead(0), par exemple, ne peut concerner que la broche analogique A0. On peut bien sûr référencer ces broches de manière plus classique en utilisant les constantes prédéfinies A0 à A5 : analogRead(0) est équivalent à analogRead(A0) etc.

Par contre, si on veut utiliser ces broches en mode GPIO, il faudra les référencer avec les numéros 14 à 19.

VI-A-3. Limitations liées à cette conversion

La première limitation est liée à la résolution proprement dite du convertisseur. Celui de l’Arduino UNO effectue une conversion sur 10 bits, c’est-à -dire que, pour faire simple, il va découper une tension de référence en 210 « tranches » égales et regarder à quelle tranche correspond la tension à convertir, à la suite de quoi il retournera le rang de la tranche en question. Cette limitation étant intrinsèque au matériel, il n’est pas possible de la contourner.

La seconde limitation est liée à l’(in)adéquation de la plage de mesure avec la tension de référence. Cette limitation sera minimale si la plage de mesure est égale à la tension de référence et maximale si la plage de mesure est inférieure ou égale à la valeur de la « tranche » vue plus haut. Elle prendra, naturellement, des valeurs intermédiaires à l’intérieur de ces limites.

Dans les conditions de notre réalisation, la tension de référence utilisée est la tension par défaut et correspond au 5 V de l’alimentation de la carte. Chaque « tranche » est donc égale à 5 / (210-1) ≃ 0,00489 V soit 4,89 mV. La plage de mesure du LM35 tel que câblé ici s’étend de 2°C à 150°C, soit 148°C, ce qui correspond à 1,48 V. Cette valeur, qui représente moins d’un tiers des 5 V de référence, laisse à penser que la conversion ne sera pas réalisée au mieux des possibilités du dispositif.

Deux solutions s’offrent à vous pour optimiser cette conversion :

  1. Amplifier le signal pour que son maximum approche, sans les dépasser, les 5 V de référence. Ceci demande de faire appel à un dispositif électronique externe ;
  2. Changer la référence. Pour réaliser cela, vous devrez utiliser l’une des trois commandes suivantes (pour l’Arduino UNO) :
 
Sélectionnez
analogReference(DEFAULT); // Référence interne de 5 V
analogReference(INTERNAL); // Référence interne de 1,1 V
analogReference(EXTERNAL); // Utilisation externe de la broche AREF

La première ligne peut être omise. Il s’agit de la référence par défaut appliquée à la broche AREF (pour Analog REFerence). La seconde ligne impose la tension de 1,1 V, générée en interne, à la broche AREF. La troisième ligne vous offre la possibilité d’imposer une tension externe de votre choix à la broche AREF.

ATTENTION : dans ce cas, la tension doit impérativement être comprise entre 0 et 5 V, et le protocole doit être suivi correctement pour ne pas risquer d’endommager le contrôleur.

Dans tous les cas, la justesse du résultat sera liée à la stabilité et à la valeur de la tension de référence. Que vous utilisiez la tension par défaut, à savoir le 5 V tiré de l’alimentation, la tension de référence interne 1,1 V ou une tension externe choisie par vous, vous devrez, pour obtenir les meilleurs résultats, mesurer cette tension « en production », car, non seulement elle peut être, même au repos, différente des valeurs théoriques, mais de plus, elle peut être affectée par la consommation de la carte en fonction du travail que vous lui demandez. Cette tension mesurée devra remplacer dans votre sketch final la tension standard sur laquelle vous vous êtes basé pour vos essais.

Il est cependant probable que, dans la plupart des cas, cette manipulation sera superflue, la qualité des résultats obtenus étant généralement suffisante.

Je n’irai pas plus loin sur ce sujet, car cela sort du cadre de cet article. Je vous engage à consulter la page analogReference sur le site officiel Arduino.

VI-A-4. Principe d’utilisation

Je vous propose ce premier sketch pour vous montrer ce que ça peut donner :

 
Sélectionnez
const byte LM35 = 0;

void setup()
{
  Serial.begin(115200);
}

void loop()
{
  byte lecture = analogRead(LM35);
  float temperature = float(lecture) * 500 / 1024;
  Serial.print("Température : ");
  Serial.print(temperature, 1);
  Serial.println(" °C");
  delay(2500);
}

Quelques explications concernant ce sketch :

  • byte lecture = analogRead(LM35); : effectue une numérisation de la tension présente sur la broche dont le numéro est la valeur de la constante LM35, ici 0, et stocke le résultat dans la variable lecture de type byte ;
  • float temperature = float(lecture) * 500 / 1024; : effectue le calcul expliqué plus haut et stocke le résultat dans la variable temperature de type float. Le transtypage float(lecture) est nécessaire pour ne pas perdre les décimales ;
  • Serial.print(temperature, 1); : affiche la température dans le moniteur série, avec une décimale ;
  • puis on introduit un délai de 2,5 s pour ne pas monopoliser la liaison série et calmer l’affichage.

Votre moniteur série va afficher quelque chose qui peut ressembler à ça :

Image non disponible

On ne peut pas dire que ce résultat soit enthousiasmant. Sur neuf mesures effectuées, on constate des fluctuations entraînant un ∆T de près de 5 °C, ce qui est énorme. De plus, indépendamment de la fiabilité du capteur ou de la conversion, la numérisation sur 10 bits ne permet, avec une tension de référence de 5 V, qu’un affichage à 0,5 °C près environ (500 / 1024 ⋍ 0,5).

Ce n’est pas très satisfaisant. Nous allons donc traiter ce problème avant d’aller plus loin.

Sur un autre plan, la temporisation à l’aide de la procédure bloquante delay() sera pénalisante quand il s’agira d’incorporer la mesure de la température à un programme plus général, dont elle ne sera qu’une des fonctionnalités.

Il faudra donc traiter le problème différemment dans ce cas.

VI-A-5. Pondération de la température

Je vais faire l’hypothèse que les données issues du capteur ne sont pas fausses en elles-mêmes, mais plutôt réparties autour de la valeur exacte suivant une courbe de type « gaussien ».

Dans ce cas, pour avoir un résultat plus conforme à la réalité, il suffit d’effectuer une série de mesures successives et d’en faire la moyenne. Voici le sketch qui va réaliser cela :

 
Sélectionnez
const byte LM35 = 0;
const word iteration = 100;

void setup()
{
  Serial.begin(115200);
}

void loop()
{
  long lecture = 0;
  for (word i = 0; i < iteration; i++)
  {
    lecture += analogRead(LM35);
  }
  float temperature = float(lecture) *500 /1024 /iteration;
  Serial.println(temperature, 1);
  delay(1000);
}

Détails du sketch :

 
Sélectionnez
long lecture = 0;
for (word i = 0; i < iteration; i++)
{
  lecture += analogRead(LM35);
}

On numérise iteration fois la température (ici, 100 fois) et on ajoute au fur et à mesure le résultat à la variable lecture, qui a été déclarée avant le début de la boucle.

 
Sélectionnez
float temperature = float(lecture) *500 /1024 /iteration;

On calcule la température résultante comme dans le sketch précédent, en n’oubliant pas de diviser par le nombre d’itérations.

Ceci nous a permis de descendre le ∆T à 0,5 °C environ, ce qui est plus acceptable. Je vous propose de regrouper le code utile dans une fonction, ce qui facilitera son utilisation ultérieurement.

 
Sélectionnez
const byte LM35 = 0;
const word iteration = 100;

void setup()
{
  Serial.begin(115200);
}

void loop()
{
  Serial.println(temperature(LM35, 100), 1);
  delay(1000);
}

float temperature(byte broche, word iteration)
{
  long lecture = 0;
  for (word i = 0; i < iteration; i++)
  {
    lecture += analogRead(broche);
  }   
  return(float(lecture) *500 /1024 /iteration);
}

VI-B. Affichage de la température dans le navigateur client

Nous allons simplement reprendre ici le sketch fourni en résumé au chapitre 5, en le modifiant, bien sûr, pour l’adapter à sa nouvelle fonction. Je considère (j’espère) comme comprises les techniques déjà utilisées et ne les réexpliquerai donc pas. Voilà le nouveau sketch :

 
Sélectionnez
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.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
#include <Ethernet.h>

byte macSerre[] = {0x90, 0xA2, 0xDA, 0x10, 0x4F, 0x25};
IPAddress IPSerre(192,168,1,200);
EthernetServer serveurHTTP(80);

const byte LM35 = 0;

void setup()
{
  Ethernet.begin(macSerre, IPSerre);
  serveurHTTP.begin();
  pinMode(LED, OUTPUT);
  Serial.begin(115200);
}

void loop()
{
  EthernetClient client = serveurHTTP.available();
  if (client)
  {
    if (client.connected())
    {
      String reception;
      while (client.available())
      {
        char carLu = client.read();        
        if (carLu != 10)
        {
          reception += carLu;
        }
        else
        {
          break;
        }
      }
      Serial.println(reception);
      reception.trim();
      if ((reception.startsWith("GET /")) && (reception.endsWith(" HTTP/1.1")))
      {    
        client.println(F("HTTP/1.1 200 OK"));
        client.println(F("Content-Type: text/html"));
        client.println(F("Connection: close"));
        client.println();
        client.println(F( "<!DOCTYPE html>"
                          "<html lang=\"fr\">"
                          "<meta charset=\"utf-8\" />"
                          "<meta http-equiv=\"refresh\" content=\"1\" />"
                          "<title>Télécommande</title>"
                          "<body>"));
        client.print(F(   "<p>La température est "));
        client.print(temperature(LM35, 100));
        client.println(F( " °C</p>"));
        client.println(F( "</body>"
                          "</html>"));
      }
      client.stop();
    }
  }
}

float temperature(byte broche, word iteration)
{
  long lecture = 0;
  for (word i = 0; i < iteration; i++)
  {
    lecture += analogRead(broche);
  }   
  return(float(lecture) *500 /1024 /iteration);    
}

Quelles sont les modifications apportées :

  • ajout de la déclaration de la constante LM35 qui représente l’entrée analogique utilisée par le capteur LM35, à savoir A0 ;
  • suppression de la déclaration de la LED sur la broche 2 ;
  • suppression de la partie de code se trouvant entre les balises <form> et </form>, balises comprises, concernant l’affichage des boutons, ainsi que la partie concernant la gestion des boutons, inutiles ici. Il ne faut pas oublier de terminer le fragment de chaîne qui précède la portion enlevée par ));, sous peine de déclencher une erreur lors de la compilation ;
  • ajout de la ligne "<meta http-equiv=\"refresh\" content=\"1\" />" qui va insérer le code HTML suivant :

     
    Sélectionnez
    <meta http-equiv="refresh" content="1" />
  • Cette ligne a pour objet de demander au navigateur de recharger la page (refresh) à la période indiquée par la valeur, exprimée en secondes, attribuée au paramètre content. De cette manière, l’affichage de la température sera mis à jour automatiquement. Dans notre exemple, la page sera rechargée toutes les secondes, ce qui va permettre de constater rapidement les variations. Dans l’application réelle, cette fréquence sera inutilement élevée, et un rafraîchissement toutes les minutes, par exemple, sera largement suffisant ;

  • remplacement de la partie qui concernait les boutons par le code suivant :

     
    Sélectionnez
    client.print(F("<p>La température est "));
    client.print(temperature(LM35, 100));
    client.println(F(" °C</p>"));
  • Ces instructions vont insérer la ligne suivante dans le code HTML envoyé ;

     
    Sélectionnez
    <p>La température est [valeur] °C</p>
  • où [valeur] représente le retour de la fonction temperature(LM35, 100) ;

  • enfin, ajout en dehors de la boucle loop() (avant ou après, mais pas dedans) de la fonction élaborée au chapitre précédent :

     
    Sélectionnez
    float temperature(byte broche, word iteration)
    {
      long lecture = 0;
      for (word i = 0; i < iteration; i++)
      {
        lecture += analogRead(broche);
      }   
      return(float(lecture) *500 /1024 /iteration);
    }

Voici ce que ça donne chez moi :

Image non disponible

Oups ! Va falloir que je baisse le chauffage ! Ou alors que j’arrête de réfléchir.

Le principal, c’est que ça fonctionne. Nous avons presque fini : la télécommande est opérationnelle et la télémétrie également. Il va falloir à présent les regrouper dans une même application.

VII. L’union fait la force

Maintenant que nous avons réalisé séparément les fonctionnalités qui nous intéressaient, il nous reste à assembler les morceaux pour en faire quelque chose de cohérent avec notre cahier des charges. C’est ce qu’effectue le sketch suivant :

 
Sélectionnez
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.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
#include <Ethernet.h>

byte macSerre[] = {0x90, 0xA2, 0xDA, 0x10, 0x4F, 0x25};
IPAddress IPSerre(192,168,1,200);
EthernetServer serveurHTTP(80);

const int LED = 2;
const byte LM35 = 0;

void setup()
{
  Ethernet.begin(macSerre, IPSerre);
  serveurHTTP.begin();
  pinMode(LED, OUTPUT);
  Serial.begin(115200);
}

void loop()
{
  EthernetClient client = serveurHTTP.available();
  if (client)
  {
    if (client.connected())
    {
      String reception;
      while (client.available())
      {
        char carLu = client.read();
        if (carLu != 10)
        {
          reception += carLu;
        }
        else
        {
          break;
        }
      }
      Serial.println(reception);
      reception.trim();
      if ((reception.startsWith("GET /")) && (reception.endsWith(" HTTP/1.1")))
      {
        if (reception.indexOf("on=ON") != -1) digitalWrite(LED, HIGH);
        else if (reception.indexOf("off=OFF") != -1) digitalWrite(LED, LOW);
        client.println(F("HTTP/1.1 200 OK"));
        client.println(F("Content-Type: text/html"));
        client.println(F("Connection: close"));
        client.println();
        client.println(F( "<!DOCTYPE html>"
                          "<html lang=\"fr\">"
                          "<meta charset=\"utf-8\" />"
                          "<meta http-equiv=\"refresh\"content=\"5\" />"
                          "<title>Télécommande</title>"
                          "<body>"));
        client.print(F(   "<p style=\"font-size: 4em\">La température est "));
        client.print(temperature(LM35, 100));
        client.println(F( " °C</p>"));
        client.println(F( "<form  method=\"get\">"
                          "<p style=\"font-size: 4em\">Cliquez sur un bouton</p>"
                          "<input style=\"font-size: 10em\" type=\"submit\" value=\"ON\" name=\"on\">"
                          "<input style=\"font-size: 10em\" type=\"submit\" value=\"OFF\" name=\"off\">"));
        if (digitalRead(LED) == HIGH) client.println("<p style=\"color: rgb(255,0,0); font-size: 4em\">LED allumée</p>");
        else if (digitalRead(LED) == LOW) client.println("<p style=\"color: rgb(125,0,0); font-size: 4em\">LED éteinte</p>");
        client.println(F( "</form>"
                          "</body>"
                          "</html>"));
      }
      client.stop();
    }
  }
}

float temperature(byte broche, word iteration)
{
  long lecture = 0;
  for (word i = 0; i < iteration; i++)
  {
    lecture += analogRead(broche);
  }   
  return(float(lecture) *500 /1024 /iteration);
}

Si on fait abstraction d’une petite modification de la taille de la fonte (4em à la place de 5em) et de la période de rafraîchissement (5 s au lieu de 1 s), ce sketch ne fait que regrouper les deux sketchs précédents et ne nécessite donc aucune explication particulière.

VIII. Petit bonus

Le sketch que je vous propose ici fait exactement le même travail que le précédent, avec toutefois une présentation un peu différente. Au lieu d’utiliser deux boutons distincts pour l’ouverture et la fermeture du vasistas (toujours dans notre scénario), un seul sera utilisé comme bascule. Son libellé indique alors sa fonction :

  • quand le vasistas est fermé, le bouton affiche « Ouvrir » ;
  • quand le vasistas est ouvert, le bouton affiche « Fermer ».

Vous savez donc ce qui va se passer si vous cliquez dessus et, par voie de conséquence, quelle est la position du vasistas.

 
Sélectionnez
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.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
#include <Ethernet.h>

byte macSerre[] = {0x90, 0xA2, 0xDA, 0x10, 0x4F, 0x25};
IPAddress IPSerre(192,168,1,200);
EthernetServer serveurHTTP(80);

const int LED = 2;
const byte LM35 = 0;

void setup()
{
  Ethernet.begin(macSerre, IPSerre);
  serveurHTTP.begin();
  pinMode(LED, OUTPUT);
  Serial.begin(115200);
}

void loop()
{
  EthernetClient client = serveurHTTP.available();
  if (client)
  {
    if (client.connected())
    {
      String reception;
      while (client.available())
      {
        char carLu = client.read();
        if (carLu != 10)
        {
          reception += carLu;
        }
        else
        {
          break;
        }
      }
      Serial.println(reception);
      reception.trim();
      if ((reception.startsWith("GET /")) && (reception.endsWith(" HTTP/1.1")))
      {
        if (reception.indexOf("vasistas=Ouvrir") != -1) digitalWrite(LED, HIGH);
        else if (reception.indexOf("vasistas=Fermer") != -1) digitalWrite(LED, LOW);     
        client.println(F("HTTP/1.1 200 OK"));
        client.println(F("Content-Type: text/html"));
        client.println(F("Connection: close"));
        client.println();
        client.println(F( "<!DOCTYPE html>\n"
                          "<html lang=\"fr\">\n"
                          "<meta charset=\"utf-8\" />\n"
                          "<meta http-equiv=\"refresh\" content=\"5\" />\n"
                          "<title>Télécommande</title>\n"
                          "<body>\n"
                          "<div style=\"font-size: 4em\">"));
        client.print(F(   "<p>Température :</p>\n"
                          "<p style=\"font-size: 1.7em; color: rgb(0,0,255)\">"));
        client.print(temperature(LM35, 100));
        client.println(F( " °C</p>"));
        client.println(F( "<form  method=\"get\">"));
        if (digitalRead(LED) == HIGH) client.println(F("<input style=\"font-size: 2.5em; color: rgba(0,200,0)\" type=\"submit\" value=\"Fermer\" name=\"vasistas\" />"));
        else if (digitalRead(LED) == LOW) client.println(F("<input style=\"font-size: 2.5em; color: rgba(0,100,0)\" type=\"submit\" value=\"Ouvrir\" name=\"vasistas\" />"));
        client.println(F( "</form>\n"
                          "</div>\n"
                          "</body>\n"
                          "</html>"));
      }
      client.stop();
    }
  }
}

float temperature(byte broche, word iteration)
{
  long lecture = 0;
  for (word i = 0; i < iteration; i++)
  {
    lecture += analogRead(broche);
  }   
  return(float(lecture) *500 /1024 /iteration);
}

Si vous regardez ce sketch avec attention, vous allez peut-être remarquer quelque chose de troublant concernant la gestion de la taille de la fonte avec « em ».

En effet, vous allez trouver un :

 
Sélectionnez
<div style="font-size: 4em">

qui va agrandir la fonte utilisée pour afficher le titre « Température : », puis, un peu plus loin, vous trouvez :

 
Sélectionnez
<p style="font-size: 1.7em">

pour afficher la valeur de cette température. Or, la valeur de la température s’affiche avec une fonte plus grande que le titre, alors qu’elle est paramétrée à 1.7em au lieu de 4em, ce qui est contre-intuitif.

Ceci est dû au fait que les balises (mères) transmettent leurs paramètres en héritage aux balises qu’elles contiennent (filles), sous réserve, bien sûr, que ces paramètres puissent être utilisés par les balises « filles » (sinon ils sont ignorés).

Dans notre structure, nous avons :

 
Sélectionnez
<div style="font-size: 4em">
  <p style="font-size: 1.7em">
  </p>
</div>

La balise <p></p> reçoit en héritage les « 4em » de sa balise mère <div></div>. Donc, pour elle, la taille de référence de la fonte est « 4em ». Elle applique donc son propre paramètre à cette taille de référence, ce qui fait que la taille résultante est (4 x 1.7)em, soit 6.8em, ce qui est cohérent avec ce que l’on observe.

Je pense que le reste ne devrait pas poser de problème de compréhension.

IX. Conclusion

Vous voici arrivé au terme de ce tutoriel : merci de l’avoir suivi.

Vous savez maintenant comment envoyer une commande à votre carte Arduino en utilisant une interface affichée dans la fenêtre de votre navigateur, quelle que soit la machine sur laquelle il est installé. Vous savez également comment récupérer une information sur la bonne exécution de cette commande et la traiter. Vous avez vu aussi comment utiliser un capteur pour effectuer une mesure et la transmettre sous forme de donnée à afficher dans votre navigateur.

Il ne reste plus qu’à adapter ce que vous venez de voir à vos besoins réels ou du moins à ce que votre imagination vous donnera envie de réaliser, même, et surtout, si c’est inutile.

X. Remerciements

Je remercie vivement LittleWhite, f-leb, Vincent Petit, nlbmoi et Winjerome pour leur relecture technique, ainsi que escartefigue pour sa relecture orthographique.