I. Avant propos

La rédaction de cet article assume que vous avez connaissance des matériels utilisés et des mécanismes détaillés dans la première partie. Il serait donc préférable que vous l’ayez lue avant d’entamer la lecture de cette seconde partie, car je serai amené à y faire référence à plusieurs reprises.

Comme nous avons abordé le HTML dans la première partie, nous allons ici aborder le CSS, ce qui va nous permettre à la fois de disposer de deux fichiers (HTML et CSS) pour détailler le mécanisme que nous allons mettre en place et d’effleurer les principes du CSS dont l’utilisation vous permettra d’améliorer la présentation de votre télécommande, encore qu’à ce sujet, je ne fasse que vous ouvrir le couvercle de la boîte à outils, tant mes compétences en matière d’esthétique sont limitées.

II. CSS (Cascading Style Sheets)

II-A. Introduction

À l’origine, le HTML n’était pas conçu pour permettre des présentations sophistiquées. Le but était simplement de donner accès, par l’intermédiaire d’un réseau, à des pages web (contenant de l’information sous forme de texte) stockées sur un serveur, en utilisant des liens hypertextes. Il s’agissait, en quelque sorte, d’un traitement de texte basique, augmenté de la capacité à gérer des hyperliens. Les balises, attributs et propriétés, peu nombreux, étaient à peu près cantonnés à la gestion de l’hypertexte, des titres, des paragraphes et des listes.

Cette situation n’a duré que relativement peu de temps, et, rapidement, ce langage s’est enrichi, offrant des fonctionnalités considérablement plus évoluées, comme on peut le constater en se promenant sur la toile. Ceci n’a, bien entendu, pas pu se faire sans entraîner la complexification de la structure du fichier HTML, rendant son élaboration et sa maintenance délicates. La solution à ce problème a été l’introduction du CSS ( Cascading Style Sheets ou Feuilles de Style en Cascade) qui a permis de séparer le contenu (l’information) de la mise en forme (la présentation).

II-B. Principe de base

« Le CSS, comment ça marche ? » dirait… l’autre, ou plutôt : à quoi ça sert ? Nous avons vu dans la première partie, lors de notre « approche » du HTML, l’utilisation de l’attribut « style » pour modifier, notamment, la couleur et la taille de la fonte. Cet attribut, mais il n’est pas le seul, sert à changer le comportement par défaut de la balise dans laquelle il est utilisé, avec pour objectif, dans la plupart des cas, d’améliorer la présentation. Cette utilisation « inline » a été la première possibilité de mise en forme des pages HTML. Malheureusement, la forme a, sinon pris le pas sur le contenu (encore que…), du moins pris une importance considérable dans la communication, rendant cette technique lourde et difficile à maintenir. C’est là que le CSS intervient.

Imaginons, par exemple, que vous vouliez fixer l’indentation (retrait de la première ligne) de tous les paragraphes de votre page à 30 pixels. Comme il s’agit d’un paragraphe, la balise impactée sera la balise <p>. L’attribut contenant la propriété que vous devez paramétrer, à savoir  text-indent , s’appelle  style . Vous devrez alors incorporer  style="text‑indent: 30px"  à chaque occurrence de la balise <p> de la manière suivante :

 
Sélectionnez
<p style="text-indent: 30px">Texte du paragraphe</p>

avec :

  • <p> : la balise (tag) dont vous voulez modifier le comportement par défaut ;
  •  style  : l’un des attributs supportés par la balise <p>, mais le seul concerné par le CSS ;
  •  text-indent  : la propriété que l’on souhaite modifier ici ;
  •  30px  : la nouvelle valeur de cette propriété.

Déjà, ça, en informatique on n’aime pas. Écrire de nombreuses fois la même ligne n’est pas très productif. Les « copier/coller », on sait faire, mais bon ! De plus, si d’aventure vous estimez que ce retrait est trop important ou pas assez, voire inutile, vous devrez faire autant de modifications qu’il y a de paragraphes. Ce n’est vraiment pas satisfaisant. Le CSS vous permet, avec la ligne suivante :

 
Sélectionnez
p {text-indent: 30px}

d’imposer à toutes les balises <p> ce formatage, et ce en une seule opération. Cette ligne pourra être placée :

  • soit dans la partie « en-tête » du fichier HTML lui-même (c’est-à-dire avant la balise <body>), entre les balises <style> et </style> ;
  • soit dans un fichier séparé ayant l’extension .css : dans ce cas, en supposant que votre fichier s’appelle « monfichier.css » et qu’il se trouve dans le même répertoire que votre fichier HTML, vous devrez l’invoquer dans l’en-tête du fichier HTML de la manière suivante :
 
Sélectionnez
<link rel="stylesheet" type="text/css" href="monfichier.css">

type="text/css" peut être omis dans la mesure où c’est la valeur par défaut quand rel="stylesheet". rel est obligatoire, ainsi que href évidemment.

Vous pouvez considérer la balise <link /> un peu comme une clause # include<>, car elle sert à lier un fichier extérieur à votre fichier HTML, la différence étant que son absence n’empêchera pas votre fichier HTML d’être fonctionnel, mais sans mise en forme.

Voici ce à quoi pourrait ressembler votre fichier HTML dans le premier cas :

 
Sélectionnez
<!doctype html>

<html lang="fr">

<meta charset="UTF-8" />
<title>Titre de la page</title>
<style>
p {text-indent: 30px}
</style>

<body>
  <p>Paragraphe 1</p>
  <p>Paragraphe 2</p>
</body>

</html>

Attention à ne pas confondre la balise <style> avec l’attribut  style . Bien que les deux soient utilisés dans le même but, leur manipulation est différente.

et dans le deuxième cas :

 
Sélectionnez
<!doctype html>

<html lang="fr">

<meta charset="UTF-8" />
<title>Titre de la page</title>
<link rel="stylesheet" type="text/css" href="monfichier.css">

<body>
  <p>Paragraphe 1</p>
  <p>Paragraphe 2</p>
</body>

</html>

pour le fichier HTML, accompagné du fichier CSS « monfichier.css » contenant :

monfichier.css
Sélectionnez
p
{
  text-indent: 30px;
}

Le « ; » (point-virgule) situé en fin de ligne n’est nécessaire que pour séparer, quand il y en a plusieurs, les différentes propriétés que vous paramétrez. Vous pouvez l’omettre dans le cas où une seule propriété est paramétrée, comme c’est le cas ici, ou pour la dernière propriété de la liste, ce qui est également le cas ici.

En ce qui concerne le codage, ces deux approches sont identiques. Le code contenu dans le fichier CSS est rigoureusement le même que le code écrit entre les balises <style> et </style>de votre fichier HTML.

Si votre application comporte un seul fichier HTML, ce qui en l’occurrence est notre cas, la première approche peut être privilégiée, surtout quand, comme ici, la mise en forme est très basique. Je vous propose malgré tout d’utiliser la seconde approche qui, d’une part, se révèle assez rapidement plus judicieuse que la première, en séparant définitivement la forme du fond, mais va surtout, d’autre part, nous donner l’opportunité de créer un fichier CSS que le fichier HTML va devoir appeler, ce qui va nous permettre de détailler les mécanismes utilisés.

Vous pouvez même aller plus loin dans la modularité en dispatchant, selon des critères que vous jugeriez importants, votre code CSS entre plusieurs fichiers. Dans ce cas, vous devez appeler chacun de ces fichiers avec une balise <link /> spécifique.

II-C. Mise en œuvre

Appliquons ce que nous venons de voir au fichier HTML décrivant notre télécommande, vu dans la première partie :

 
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>

Le code tel que je vous le livre ici ne respecte pas la « bonne pratique » en ce qui concerne la clarté de son écriture, à la fois trop compacte et dénuée d’indentation. La raison de ce choix réside en ce que ce code est, pour l’instant, destiné à être inclus au sketch et que, dès lors, son impact sur la mémoire de l’Arduino n’est pas négligeable, tant s’en faut. Or, il faut savoir que chaque saut de ligne ajoute un caractère à l’ensemble, ainsi que chaque « cran » d’indentation.

J’aurais pu, pour aller au bout du raisonnement, supprimer un certain nombre de lignes qui ne sont pas nécessaires à son fonctionnement et mettre le tout sur une même ligne, mais je voulais malgré tout fournir un code syntaxiquement correct et un minimum lisible.

En utilisant la technique intégrant le CSS au fichier HTML, voici ce que nous obtenons :

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

La technique utilisant un fichier CSS externe nous donne :

  • pour le fichier HTML :
 
Sélectionnez
<!DOCTYPE html>
<html lang="fr">
<meta charset="utf-8" />
<title>Télécommande</title>
<link rel="stylesheet" type="text/css" href="monfichier.css">
<body>
<form  method="get">
<p class="titre">Cliquez sur un bouton</p>
<input type="submit" value="ON" name="on" />
<input type="submit" value="OFF" name="off" />
</form>
<p class="retour">LED éteinte</p>
</body>
</html>
  • et pour le fichier CSS :
 
Sélectionnez
input{font-size: 10em}
.titre{font-size: 5em}
.retour{color: rgb(125,0,0); font-size: 5em}

Le fonctionnement est rigoureusement le même pour le CSS intégré au fichier HTML et pour le CSS placé dans un fichier externe. Comme nous utiliserons le fichier CSS externe, je ne décrirai que le code correspondant à cette technique.

Essayons d’analyser ce code.

Le CSS ne s’occupe que du style. Prenons, par exemple, le cas de la première balise <input>. La ligne HTML originale correspondant à cette balise est :

 
Sélectionnez
<input style="font-size: 10em" type="submit" value="ON" name="on" />

Seule la partie style="font-size: 10em" sera concernée par le fichier CSS. Comment écrire cela en CSS ? C’est très simple :

  • le nom de la balise à paramétrer, ici <input>, apparaîtra en début de ligne (pour faire simple, mais ce n’est pas obligatoire) ;
  • les propriétés à paramétrer (ici, il n’y en a qu’une) seront placées après, les unes à la suite des autres, sur une même ligne ou sur des lignes successives, séparées par des points-virgules ( ; ), encadrées par des accolades ( {} ).

Nous obtenons la ligne suivante dans le fichier CSS :

 
Sélectionnez
input{font-size: 10em}

ou, comme je préfère l’écrire (mais c’est personnel) :

 
Sélectionnez
input
{
  font-size: 10em;
}

Comme dit plus haut, seul l’attribut style est concerné : les autres attributs, type, value et name, resteront inline, car ils ne sont pas gérés par le CSS. La ligne originale deviendra :

 
Sélectionnez
<input type="submit" value="ON" name="on" />

La seconde utilisation de la balise <input> suivra le même processus, mis à part le paramétrage de sa propriété font-size qui, du coup, est déjà fait. On commence à voir là l’intérêt du système.

 
Sélectionnez
<input type="submit" value="OFF" name="off" />

Voyons maintenant le cas de la balise <p>. Le code inline de la première occurrence de cette balise est le suivant :

 
Sélectionnez
<p style="font-size: 5em">Cliquez sur un bouton</p>

En utilisant le même mécanisme que ci-dessus, nous obtenons :

 
Sélectionnez
<p>Cliquez sur un bouton</p>

pour la partie HTML, et :

 
Sélectionnez
P
{
  font-size: 5em;
}

pour la partie CSS. Jusque-là, tout va bien. Par contre, en abordant le second paragraphe, ça se gâte. En effet, si la partie HTML ne pose pas de problème, notre code HTML inline :

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

devenant :

 
Sélectionnez
<p>LED éteinte</p>

le code CSS devient quant à lui :

 
Sélectionnez
P
{
  color: rgb(125,0,0);
  font-size: 5em;
}

ce qui fait que, dans notre fichier CSS, nous aurons deux paramétrages différents pour un même élément, à savoir :

 
Sélectionnez
P
{
  font-size: 5em;
}

P
{
  color: rgb(125,0,0);
  font-size: 5em;
}

Ce qui fait que, comme c’est généralement (toujours ?) le cas en programmation, seul le dernier paramétrage sera pris en compte, et donc que l’affichage de ces deux paragraphes sera identique, du moins au niveau de « la forme ». Le texte, à savoir « le fond », restera quant à lui conforme à ce qui a été codé.

Bien évidemment, vous vous en doutez, une solution existe.

On pourrait envisager de réutiliser la technique inline pour caractériser les nouveaux paragraphes, mais, outre le fait qu’on se retrouverait en butte aux problèmes évoqués plus haut liés à cette technique, on mélangerait deux méthodes de mise en forme ce qui n’est pas souhaitable.

Le CSS offre deux solutions :

  • l’attribut classqui permet de définir un ensemble de caractéristiques et de les appliquer à un nombre quelconque de balises ;
  • l’attribut id qui permet également de définir un ensemble de caractéristiques, mais ne permet de les appliquer qu’à une seule balise.

Le fonctionnement de ces deux attributs est identique. Nous pourrions ici utiliser indifféremment l’un ou l’autre, le résultat serait le même, mais c’est à éviter absolument. L’attribut id ne doit être utilisé que dans le cas d’un traitement particulier effectué sur une seule balise. Il sert à identifier de façon univoque cette balise. Nous aurons l’occasion de revenir plus tard sur cet attribut.

L’attribut class va nous permettre de sélectionner un groupe de balises, identiques ou non, et de leur affecter un paramétrage commun.

Ce paramétrage doit avoir un nom permettant de l’invoquer depuis les balises auxquelles il est destiné. On réalise cela dans le fichier CSS, et ce peut être comparé à la déclaration d’une fonction. Le mot réservé est… « . » (le point) et il doit être suivi immédiatement du nom que vous souhaitez lui donner. Ce nom doit impérativement commencer par une lettre (majuscule ou minuscule) et ne peut comporter que des lettres, des chiffres, ainsi que les caractères « - » et « _ ». Dans notre cas, nous avons deux paramétrages distincts à créer et je vous propose de le faire en saisissant ce qui suit dans votre fichier CSS :

 
Sélectionnez
.titre
{
  font-size: 5em;
}

.retour
{
  color: rgb(125,0,0);
  font-size: 5em;
}

Il ne doit pas y avoir d’espace entre la valeur et l’unité dans laquelle elle est exprimée. Le code suivant est correct :

 
Sélectionnez
font-size: 5em;
text-indent: 30px;

alors que celui-là ne l’est pas :

 
Sélectionnez
font-size: 5 em;
text-indent: 30 px;

Nous venons donc de déclarer les « classes » .titre et .retour : elles reprennent simplement les paramétrages effectués plus haut. Je mets « classes » entre guillemets, car il ne faut pas confondre avec les classes utilisées en programmation objet. Cette appellation est pratique, car elle établit un lien euphonique avec l’attribut class correspondant.

Mais ce n’est pas fini. Il faut maintenant indiquer à chacune des deux balises <p> la « classe » qu’elle doit utiliser. Pour ce faire, nous utiliserons l’attribut class dans le code HTML de la façon suivante, pour utiliser ces deux « classes » comme il se doit :

 
Sélectionnez
<p class="titre">Cliquez sur un bouton</p>

et

 
Sélectionnez
<p class="retour">LED éteinte</p>

Voilà pour le principe « général » d’utilisation des « classes ». Je dis « général » car on est loin d’en avoir fait le tour, mais pour l’utilisation que l’on va en faire, c’est suffisant. Il ne s’agit pas ici d’un tutoriel sur le CSS et je ne décris que ce qui nous est utile.

Pour ceux qui souhaitent aller plus loin en ce qui concerne le CSS, deux adresses :

Nous venons donc de voir, dans les grandes lignes, la façon de passer du code de mise en forme inline au fichier CSS équivalent. Comment allons-nous utiliser ce fichier avec notre « serveur » Arduino ?

III. Utilisation des fichiers sur notre serveur

Dans la première partie, tout se trouvait inclus dans le sketch Arduino, à savoir le logiciel serveur, le fichier HTML avec sa mise en forme inline, l’équivalent PHP pour traiter les commandes envoyées par le client, le traitement des données issues du capteur… Bref ! Une usine à gaz (enfin, au moins un petit briquet).

Dans tout ce fatras, le « fichier » HTML avec tout ce qu’il contient fait tache. Il ne fait pas partie du programme exécuté par l’Arduino : ce n’est techniquement que du texte qui va être envoyé tel quel au client (le navigateur) qui, lui, va se charger de l’interpréter pour afficher la télécommande à l’écran. De plus, ce code HTML va, à terme, devoir intégrer du CSS et du JavaScript. Ce texte prend de la place, que ce soit en RAM ou en mémoire flash, ce qui va rapidement limiter les possibilités théoriques du dispositif. La simplicité de notre exemple d’application autorise bien sûr cette pratique, la première partie le démontre, mais, du moins je le souhaite, vos ambitions seront plus vastes que celles de ce tutoriel, et il serait dommage de les brider si on peut faire autrement. Cela dit, il ne faut pas rêver : il y aura quand même des limites.

Le système Arduino est riche en possibilités et, entre autres choses, il permet, muni du dispositif ad hoc, de lire et d’écrire des fichiers en utilisant une mémoire de masse « universelle » : la carte SD (ou microSD).

Comble de chance, le shield Ethernet que nous avons utilisé pour réaliser notre serveur possède un lecteur de cartes microSD. Quel heureux hasard ! Mais est-ce bien un hasard ? Assurément non. Un serveur digne de ce nom, fût-il à base d’Arduino, nécessite une mémoire de masse, à défaut de quoi il devra se contenter d’un succès d’estime.

Nous allons donc, avant de poursuivre, jeter un coup d’œil sur l’utilisation de ce lecteur de carte.

III-A. Lecture d’une carte microSD

Sur le plan matériel, tout le monde sait ce qu’est une carte microSD : il est donc inutile de s’étendre là-dessus. Je pense également que vous n’aurez pas de problème pour trouver l’endroit qui lui est réservé sur le shield Ethernet.

La carte microSD devra être formatée en Fat16 ou en Fat32 (formatage généralement déjà réalisé sur les cartes du commerce), à défaut de quoi elle ne sera pas reconnue par le lecteur, ou plutôt par la bibliothèque dont on va se servir. Il est impératif également que votre système de développement (PC, portable ou autre) dispose d’un lecteur de carte microSD, soit intégré, soit sous forme d’un adaptateur externe, car vous aurez à écrire des fichiers sur cette carte.

Pour utiliser le lecteur intégré à votre shield Ethernet, l’environnement Arduino vous offre, en standard la bibliothèque SD. Il s’agit d’une version édulcorée de la bibliothèque SdFatLib développée par Bill Greiman, que vous pouvez télécharger iciPage de téléchargement. Elle fonctionnera dans la plupart des cas, mais je vous conseillerais malgré tout d’utiliser la bibliothèque originale : elle est plus rapide, offre plus de fonctions, permet l’ouverture de plusieurs fichiers simultanément, et, surtout, elle autorise les noms de fichier longs. Si, un jour, vous deviez utiliser une bibliothèque de fichiers fournie par un tiers, il y a peu de chance pour que ceux-ci respectent l’ancien standard 8.3 de Windows®.

Je vous propose, dans un premier temps, de vous montrer comment lire un fichier présent sur une carte microSD, pour en afficher le contenu sur le moniteur série. Il est donc nécessaire, avant tout, d’écrire un fichier sur cette carte. N’importe quel fichier texte ferait l’affaire, mais autant prendre immédiatement un fichier en relation avec notre tutoriel.

Reprenons l’exemple vu plus haut :

lecture-sd.html
TéléchargerSé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.
<!DOCTYPE html>

<html lang="fr">

  <head> <!-- La balise <head> peut être omise en HTML5 -->
    <meta charset="utf-8" />
    <title>Télécommande</title>
    <style>
      input{font-size: 10em}
      .titre{font-size: 5em}
      .retour{color: rgb(125,0,0); font-size: 5em}
    </style>
  </head>
  
  <body>
    <form  method="get">
      <p class="titre">Cliquez sur un bouton</p>
      <input type="submit" value="ON" name="on" />
      <input  type="submit" value="OFF" name="off" />
    </form>
    <p class="retour">LED éteinte</p>
  </body>
    
</html>

Comme vous pouvez le constater, j’ai légèrement remanié ce code pour le rendre plus lisible, notamment en ajoutant les indentations. Je peux à présent le faire sans soucis, car ce code ne sera plus intégré au sketch Arduino, et donc, la place qu’il occupe ne sera plus un problème.

Pour rester cohérent avec ce qui a été dit à ce sujet, nous ne prendrons pas le code utilisant le CSS inline. Toutefois, comme il est préférable d’aborder le problème avec un seul fichier, nous utiliserons pour l’instant le code HTML avec le CSS intégré dans l’entête.

Téléchargez ce fichier et copiez-le sur votre carte microSD. Insérez à présent cette carte dans le logement adéquat se trouvant sur votre shield Ethernet : vous êtes « matériellement » prêt pour la lecture de la carte microSD.

Matériellement seulement, car il vous faut le sketch qui va avec. Voici donc le sketch en question :

lecture-sd.ino
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.
/* 
   CONDITIONS MATÉRIELLES:
   Arduino UNO rev3 / Shield Ethernet 2
   SPI SD: SS = 4
   Carte microSD HC 4Gio
   IDE 1.8.5

   OBJECTIF:
   Lire le contenu du fichier "lecture_sd.html" depuis la carte micro SD pour l'afficher
   dans le terminal série
*/

#include <SPI.h>     // L'inclusion explicite de la bibliothèque SPI n'est pas nécessaire 
#include <SdFat.h>   // Inclusion de la bibliothèque SdFat
SdFat SD;            // Création d’une instance de SdFat

//CONSTANTE
const int carteSD = 4; // Broche de sélection par défaut pour la communication SPI avec la carte SD

//INITIALISATIONS
void setup()
{
  Serial.begin(115200);   // Penser à paramétrer le terminal série avec la même valeur
  Serial.println("SETUP en cours ...");
  File fichier;           // On instancie un objet "File" fourni par la bibliothèque SdFat
  /* Initialisation de la carte SD */
  Serial.println("Initialisation de la carte SD");
  if (SD.begin(carteSD))  // Si la fonction d'initialisation renvoi TRUE,
  {
    Serial.println("Initialisation OK!");
  }
  else                    // sinon, message d'erreur et on quitte le SETUP
  {
    Serial.println("Initialisation impossible:\n\tAbandon du setup!");
    return;  // Sort du setup
  }
  Serial.println("============================================");
  if (SD.exists("lecture-sd.html"))                            // Si le fichier "lecture_sd.html"
  {                                                            // existe, on l'ouvre en lecture puis
    fichier = SD.open("lecture-sd.html", FILE_READ);           // on lit et on envoi son contenu, 
    Serial.println("Contenu du fichier 'lecture-sd.html'\n");  // caractère par caractère, vers le
    while (fichier.available())                                // moniteur série pour l'afficher.
    {
      Serial.write(fichier.read());
    }
    fichier.close();
  }
  else  //Sinon message d'erreur et on quitte le SETUP
  {
    Serial.println("Acces impossible au fichier 'lecture-sd.html':\n\tAbandon du setup!");
    return;  //Sort du setup
  }
}

//PROGRAMME
void loop()
{
  
}

Le fonctionnement global du sketch est indiqué dans les commentaires. Je vais malgré tout préciser quelques lignes :

  • #include <SPI.h>(ligne15) : l’inclusion de cette bibliothèque n’est pas nécessaire dans la mesure où la bibliothèque SdFat s’en charge déjà ;
  • deux tests sont effectués :

    • le premier, if (SD.begin(carteSD))(ligne 28) permet à la fois d’initialiser le lecteur de carte en utilisant la fonction begin(), de s’assurer que l’inscription du lecteur de carte microSD sur le bus SPI s’est bien passée et qu’une carte microSD valide est bien présente dans le slot ad hoc,
    • le second, if (SD.exists("lecture-sd.html")(ligne 38) vérifie l’existence du fichier désiré avant d’effectuer une quelconque opération dessus ;
  • fichier = SD.open("lecture-sd.html", FILE_READ);(ligne 40) : ouverture du fichier en lecture. Comme il s’agit du mode d’ouverture par défaut,FILE_READpeut être omis, contrairement àFILE_WRITE, par exemple, si l’on voulait l’ouvrir en écriture ;
  • Serial.write(fichier.read())(ligne 44) : pour plus de clarté, cette ligne pourrait être décomposée comme suit :
 
Sélectionnez
44.
45.
char car = fichier.read();
Serial.write(car);

Cette technique de transfert de fichier caractère par caractère est simple d’emploi et fiable. Son inconvénient principal dans notre contexte est sa relative lenteur. Si, par la suite, vous comptez utiliser des frameworks CSS ou JavaScript, ou des images, pesant plusieurs centaines de kilooctets, sachez que c’est possible, mais que le temps de chargement ne sera pas négligeable.

Téléversez ce sketch puis ouvrez le moniteur série. Vous devriez obtenir ceci :

Image non disponible

J’espère que vous n’espériez pas obtenir l’affichage de la page HTML, auquel cas vous devez être déçu. Le moniteur série n’est pas un navigateur et ne peut malheureusement afficher que du texte brut. C’est d’ailleurs tout ce qu’on lui demande.

Nous avons donc notre fichier HTML apparemment accessible : il est temps de voir de quelle manière le serveur va pouvoir le mettre à la disposition du client pour permettre à celui-ci d’afficher la page correspondante.

III-B. Arduino serveur de fichier HTML

Reprenons le sketch de base vu dans la première partie, destiné à établir une connexion fonctionnelle entre le serveur et le client :

 
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[] = {0x90, 0xA2, 0xDA, 0x10, 0x4F, 0x25};
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();
    }
  }
}

Nous allons le modifier comme suit :

tuto.ino
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 <SdFat.h>
#include <Ethernet.h>

SdFat SD;

const int carteSD = 4;

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

void setup()
{
  SD.begin(carteSD);
  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();
        if (SD.exists("lecture_sd_1.html"))
        {
          File fichier = SD.open("lecture_sd.html", FILE_READ);
          while (fichier.available())
          {
            client.write(fichier.read());
          }
          fichier.close();
        }
        else
        {
          client.println(F("Fichier 'lecture_sd.html' introuvable..."));
        }
      }
      client.stop();
    }
  }
}

Quelles sont les modifications effectuées ? Comme il s’agit essentiellement de la fusion du sketch serveur de base avec le sketch de lecture d’une carte microSD, lesquels ont été détaillés précédemment, je ne préciserai que deux points :

  • #include <Ethernet.h>(ligne 2) : comme je l’ai mentionné dans la première partie, et à l’attention de ceux qui auraient fait l’impasse dessus, la bibliothèque Ethernet officielle a été mise à jour et gère, depuis sa version 2.0, les puces WIZnet® W5100, 5200 et 5500. La bibliothèque Ethernet2, écrite dans le but de gérer la puce W5500 qui équipe le shield Ethernet version 2 n’est donc plus nécessaire et le projet ne sera plus maintenu. D’ailleurs, le shield Ethernet version 1 n’est plus fabriqué par Arduino. Si ce n’est pas déjà fait, vous pouvez mettre à jour votre bibliothèque officielle en version 2.0 en passant par le menu « Outils » de votre EDI Arduino puis « Inclure une bibliothèque → Gérer les bibliothèques » ;
  • à partir de la ligne 47 jusqu’à la ligne 59, j’utilise exactement le même code que celui permettant de lire le fichier « lecture_sd.html » présent sur la carte microSD et d’envoyer son contenu sur le moniteur série de l’EDI Arduino, à un petit détail prêt : la ligne Serial.write(fichier.read())devient client.write(fichier.read())pour acheminer le contenu du fichier non plus sur le terminal série, mais vers le client Ethernet, de même que les lignes :

     
    Sélectionnez
    Serial.println("Acces impossible au fichier 'lecture-sd.html':\n\tAbandon du setup!");
    return;  //Sort du setup
  • deviennent :
tuto.ino
Sélectionnez
client.println(F("Fichier 'lecture_sd.html' introuvable..."));

pour rediriger de la même manière le message d’erreur vers le client. Comme vous pouvez le constater, le mécanisme est rigoureusement identique et les écritures tout à fait cohérentes.

Téléversez ce sketch et connectez-vous au serveur : l’interface de votre télécommande doit s’afficher dans la fenêtre de votre navigateur.

Nous savons donc maintenant demander, depuis le client, une page web stockée sur la carte microSD, ce qui nous permet de nous affranchir totalement de la limite imposée par la faible capacité mémoire de l’Arduino UNO, du moins en ce qui concerne la taille du code HTML. Mais cela n’est pas gratuit. L’utilisation de la bibliothèque SdFat nous coûte à peu près 8,4 Kio de mémoire flash (emplacement du programme) et 840 octets de RAM (emplacement des données), ce qui veut dire que, tant que le volume des données stockées sur la carte microSD sera inférieur à approximativement 10 Kio, ce choix peut être discutable.

Pour info, j’ai déterminé ces valeurs avec le sketch minimum suivant :

Image non disponible

Bon ! on s’est débarrassé de la partie HTML qui « encombrait » notre sketch, lequel est devenu, du coup, nettement plus lisible. Par contre, on a toujours du code CSS qui « encombre » notre code HTML.

Question : comment va-t-on faire pour utiliser du code CSS issu d’un autre fichier que celui contenant le code HTML ?

III-C. Arduino serveur de fichier CSS

Réponse : en utilisant exactement le même mécanisme !

Comme pour le chapitre précédent, si vous voulez « servir » un fichier CSS depuis la carte microSD, il est impératif que ce fichier se trouve sur cette carte. Que va contenir ce fichier : tout simplement le code CSS qui se trouve entre les balises <style> et </style>, à savoir :

tuto.css
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
input
{
  font-size: 10em;
}

.titre
{
  font-size: 5em;
}

.retour
{
  color: rgb(125,0,0);
  font-size: 5em;
}

Bien que j’ai fait remarquer plus haut que le point-virgule ( ; ) n’est nécessaire que quand il sépare le paramétrage de deux propriétés, je conseille de le mettre systématiquement. Cela limite d’une part le risque de l’oublier lors de la saisie elle-même et d’autre part le risque d’oublier de l’ajouter à l’ancienne dernière ligne, lors du rajout d’une ou de plusieurs lignes.

Je vous propose de copier le code ci-dessus dans un fichier appelé tuto.css, ou tout autre nom, si vous êtes sûr de ne pas vous mélanger les crayons, mais en conservant le suffixe .css naturellement (par principe, car ce n’est pas nécessaire).

Je suppose que vous avez deviné qu’il va vous falloir écrire ce fichier sur la carte microSD, mais ne vous précipitez pas. Pour utiliser ce fichier, il va falloir également modifier le fichier HTML. Celui-ci va, en effet, devoir appeler le fichier CSS pour pouvoir le charger, et la mise en forme intégrée à l’entête va pouvoir être supprimée, ce qui est le but.

Voici ce que ça va donner :

tuto.html
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
<!DOCTYPE html>

<html lang="fr">

  <head> <!-- La balise <head> peut être omise en HTML5 -->
    <meta charset="utf-8" />
    <link rel="stylesheet" type="text/css" href="tuto.css">
    <title>Télécommande</title>
  </head>
  
  <body>
    <form  method="get">
      <p class="titre">Cliquez sur un bouton</p>
      <input type="submit" value="ON" name="on" />
      <input  type="submit" value="OFF" name="off" />
    </form>
    <p class="retour">LED éteinte</p>
  </body>
    
</html>

Copiez ce code dans un fichier que vous appellerez tuto.html pour rester cohérent avec tuto.css, et à présent, vous pouvez écrire les deux fichiers sur votre carte microSD. Conservez pour le moment le fichier lecture-sd.html.

Si vous êtes curieux, créez un dossier vide sur votre PC, placez-y le fichier tuto.html et ouvrez-le avec votre navigateur. Ajoutez dans le même dossier le fichier tuto.css et ouvrez à nouveau tuto.html. Comparez les affichages.

Vous noterez également que l’absence du fichier tuto.css ne déclenche pas d’erreur. La page web est simplement affichée sans mise en forme.

Insérez à présent la carte microSD dans le slot qui lui est réservé sur votre prototype, puis connectez-vous au serveur. Pas de chance ! votre navigateur n’affiche pas du tout ce que vous espériez, mais quelque chose comme ceci :

Image non disponible

Ce message d’erreur révèle deux problèmes :

  1. Le premier problème est d’ordre « mécanique ». En effet, comme vous n’avez pas modifié votre sketch, par ma faute puisque je ne vous l’ai pas demandé (ce qui engendre d’ailleurs le problème n°2), vous essayez toujours de vous connecter à la page correspondant au fichier lecture-sd.html. Comme je vous ai demandé, juste au-dessus, de conserver ce fichier, pourquoi est-il introuvable ? La raison en est simple. En enlevant la carte microSD pour ajouter les fichiers tuto.html et tuto.css depuis votre PC, vous avez déconnecté le lecteur de carte du bus SPI. Malheureusement, la bibliothèque SdFat ne gère pas la réinscription automatique du lecteur sur le bus SPI lors de la réinsertion de la carte microSD. Pour que celle-ci soit à nouveau reconnue, vous devez réinitialiser le lecteur en exécutant un SD.begin(carteSD);. Cette commande n’est accessible que depuis le setup(). Comme celui-ci n’est exécuté qu’une fois, au démarrage du sketch, vous devez soit « éteindre » votre carte Arduino puis la « rallumer », soit téléverser à nouveau le sketch, soit effectuer un reset de la carte à l’aide du bouton ad hoc. Je préconise bien sûr la troisième méthode ;
  2. Le second problème, comme on vient de le voir, est qu’il faut modifier le sketch pour qu’il prenne en compte le nouveau fichier HTML. La modification est triviale, il vous suffit à priori de remplacer les occurrences de lecture-sd.htmlpar tuto.html.

Voici le fragment de code concerné par la modification :

tuto.ino
Sélectionnez
if (SD.exists("tuto.html"))
{
  File fichier = SD.open("tuto.html", FILE_READ);
  while (fichier.available())
  {
    client.write(fichier.read());
  }
  fichier.close();
}
else
{
  client.println(F("Fichier 'tuto.html' introuvable..."));
}

Téléversez ce code et connectez-vous. Hélas, rien ne fonctionne aujourd’hui. Voilà ce que l’on obtient :

Image non disponible

Bon ! La télécommande est bien là, c’est déjà ça, mais où est passée la mise en forme ? Je vous propose, pour le découvrir, d’aller voir du côté du moniteur série de votre EDI Arduino. Ouvrez donc ce moniteur et connectez-vous à nouveau. Vous devriez obtenir quelque chose qui ressemble à ça :

Image non disponible

Décortiquons :

  • la première ligne nous est familière. Il s’agit de la requête standard envoyée au serveur par le navigateur pour demander l’affichage de notre télécommande. Pour plus de détails à ce sujet, voir le chapitre IV-B-2. Connexion fonctionnelle dans la première partie ;
  • vient ensuite la ligne GET /tuto.css HTTP/1.1 : que signifie cette ligne ? Lors du chargement de la page par le navigateur, celui-ci interprète le code HTML ligne par ligne. Il arrive à un moment sur la ligne contenant l’instruction <link rel="stylesheet" type="text/css" href="tuto.css"> . Cette instruction peut être comparée à l’instruction #include <lib.h> que vous utilisez dans vos sketches pour utiliser du code contenu dans un fichier externe (une bibliothèque par exemple). Elle demande au navigateur de charger le fichier tuto.css dont le contenu va, en quelque sorte, être ajouté au code HTML à l’endroit où se trouve cette instruction pour être utilisé comme s’il se trouvait entre les balises <style> et </style>. Le navigateur génère alors cette requête, dont vous pouvez constater la conformité de la structure avec ce que nous avons vu dans la première partie, et l’envoie au serveur. Comme nous l’avons vu dans la première partie, la requête doit être traitée par le serveur, lequel se charge également d’envoyer un accusé de réception, à défaut de quoi le client répète sa requête neuf fois (donc dix requêtes sont envoyées). Si aucune réponse ne lui parvient et s’il n’a pas autre chose à faire, le serveur ferme la connexion. Ce traitement n’a pas encore été réalisé, mais ça ne va pas tarder.
  • Enfin, la ligne GET /favicon.ico HTTP/1.1 : cette requête dépend du navigateur et elle est envoyée automatiquement, ou pas. Son but est de charger une icône se trouvant à la racine du site web pour l’affecter en général à l’onglet représentant la page dans laquelle s’affiche le site. Cette requête n’offrant pas de réel intérêt pour ce tutoriel, j’avais dans un premier temps décidé de l’ignorer. Pourtant, ces dix lignes qui s’affichent dans le moniteur m’agacent et dénotent un manque de finition. Je vais donc traiter ce problème un peu plus bas, dès que nous aurons écrit deux petites fonctions bien pratiques.

Donc, comme promis ci-dessus, nous allons traiter la requête GET /tuto.css HTTP/1.1. Nous savons, dans les grandes lignes, comment traiter ce type de requêtes : nous l’avons déjà fait pour la demande d’envoi du fichier tuto.html et le principe est rigoureusement le même.

Voici notre nouveau sketch :

Test-serveur.ino
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.
81.
82.
83.
84.
#include <SdFat.h>
#include <Ethernet.h>

SdFat SD;

const int carteSD = 4;

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

void setup()
{
  SD.begin(carteSD);
  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();
        if (SD.exists("tuto.html"))
        {
          File fichier = SD.open("tuto.html", FILE_READ);
          while (fichier.available())
          {
            client.write(fichier.read());
          }
          fichier.close();
        }
        else
        {
          client.println(F("Fichier 'tuto.html' introuvable..."));
        }
      }
      else if (reception.startsWith("GET /tuto.css HTTP/1.1"))
      {
        client.println(F("HTTP/1.1 200 OK"));
        client.println(F("Content-Type: text/css"));
        client.println(F("Connection: close"));
        client.println();
        if (SD.exists("tuto.css"))
        {
          File fichier = SD.open("tuto.css", FILE_READ);
          while (fichier.available())
          {
            client.write(fichier.read());
          }
          fichier.close();
        }
        else
        {
          client.println(F("Fichier 'tuto.css' introuvable..."));
        }
      }
      client.stop();
    }
  }
}

Quelles sont les nouveautés ? Le code que nous avons ajouté, bien sûr. Tout se passe entre la ligne 61 et la ligne 80, ces deux lignes étant incluses. Mais quelles sont les réelles nouveautés dans ce code ? Comparez le fragment situé entre les lignes 41 et 60 avec celui situé entre les lignes 61 et 80 : c’est pratiquement le même. Les modifications portent sur :

  • le test pour isoler la requête à laquelle on va répondre, ligne 61. GET /tuto.css HTTP/1.1 remplace GET / HTTP/1.1 ;
  • l’indication, dans l’accusé de réception destiné au client, du type de données que celui-ci va recevoir, à savoir text/css en remplacement de text/html utilisé précédemment. Cette précision est essentielle, car en cas d’omission, le navigateur essayera par défaut d’interpréter le texte comme étant du code HTML. Puisque ce n’en est pas, il le considérera comme du simple texte et l’affichera comme tel si c’est possible ;
  • le passage du fichier tuto.css comme paramètre dans les lignes 67, 69 et 78, en remplacement du fichier tuto.html utilisé précédemment.

Les changements ne portent que sur les paramètres. Techniquement, c’est le même code. Il serait donc peut-être judicieux de mettre tout ça dans une fonction, d’autant que nous userons par la suite du même mécanisme pour charger un fichier JavaScript.

Mais ne nous emballons pas…

Nous allons en fait écrire deux fonctions : l’une pour créer l’accusé de réception, l’autre pour l’envoi du fichier proprement dit.

Le rôle de la première fonction va être de générer l’accusé de réception. En voici le code :

Fonction arHtml()
Sélectionnez
void arHtml(EthernetClient nomClient, char type)
{
  nomClient.println(F("HTTP/1.1 200 OK"));
  nomClient.print(F("Content-Type: "));
  nomClient.println(type);
  nomClient.println(F("Connection: close"));
  nomClient.println();
}

Cette fonction, nommée arHtml (pour accusé de réception Html), ne retourne aucune valeur : pour cette raison elle n’est pas typée et sa déclaration doit donc commencer par le mot clé void (vide). Elle demande deux paramètres :

  • nomClient de type EthernetClient : ce paramètre recevra à l’utilisation l’instance EthernetClient créée lors de la connexion et dont on a besoin pour connaître le destinataire de l’accusé de réception afin de construire les instructions nomClient.println() ;
  • type de type String : ce paramètre, quant à lui, va recevoir le renseignement relatif au type des données qui vont être envoyées au client, renseignement nécessaire à une interprétation correcte des dites données par le navigateur.

Le rôle de la deuxième fonction va être d’envoyer le fichier demandé au client. Voici son code :

Fonction envoiFichier()
Sélectionnez
void envoiFichier(EthernetClient nomClient, String fichierEnCours)
{
  char tableau[fichierEnCours.length()+1];
  fichierEnCours.toCharArray(tableau, fichierEnCours.length()+1);
  if (SD.exists(tableau))
  {
    File fichier = SD.open(tableau, FILE_READ);
    while (fichier.available())
    {
      nomClient.write(fichier.read());
    }
    fichier.close();
  }
  else
  {
    nomClient.println("Fichier '" + fichierEnCours + "' introuvable...");
  }

La construction de cette fonction suit la même démarche que celle utilisée pour la fonction précédente : je ne la détaillerai donc pas. Je vais juste dire un mot sur les deux premières lignes. La fonction SD.exists() n’accepte pas d’argument de type String. Or, la variable fichierEnCours, qui devrait lui être passée en argument est justement un type String. Ces deux lignes ont simplement pour fonction de « transtyper » cette variable en une autre variable appelée tableau qui elle, est de type tableau de caractères, et comme telle, est acceptée par la fonction SD.exists(). Elles utilisent la méthode toCharArray()Référence Arduino fournie par l’objet String dont c’est précisément l’usage.

III-D. Récapitulation

Nous venons de détailler les processus permettant au serveur de fournir les fichiers demandés par le client, à condition bien sûr que ces fichiers soient stockés sur la carte microSD insérée dans le slot présent sur le shield Ethernet. Cette technique vous permet d’héberger un site web complet, avec son fichier HTML, son (ses) fichier(s) de mise en forme CSS et son (ses) éventuel(s) fichier(s) JavaScript, à condition qu’ils ne dépassent pas la capacité de la carte que vous utilisez. La bibliothèque SdFat gérant les cartes SD et SDHC, votre marge de manœuvre est confortable puisque vous pouvez y stocker jusqu’à 32 Gio de données, textes, images, etc.

La technique utilisée dans la première partie ne vous offrait pas cette possibilité, en raison de la capacité de stockage limitée de la mémoire offerte par votre Arduino UNO, encore que, eu égard à la simplicité de la réalisation proposée dans cet article, on aurait pu le faire.

Un autre problème se serait également posé : l’utilisation d’une image. Comme promis plus haut, nous allons rajouter une petite icône à notre télécommande. Je vous propose celle-ci : Image non disponible, mais si vous en préférez une autre, n’hésitez pas. Après tout, c’est votre télécommande. Sinon, téléchargez le fichier icone.pngTélécharger l'icône et copiez-le sur la carte microSD. Modifiez le fichier tuto-1.html comme suit (ajout de la ligne 8), soit directement sur la carte microSD, soit la version qui se trouve sur votre PC. Dans ce cas, n’oubliez pas de mettre à jour le fichier se trouvant sur la carte microSD. Remettez celle-ci dans son slot sur le shield Ethernet.

Le nouveau fichier tuto-1.html :

tuto-1.html
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
<!DOCTYPE html>

<html lang="fr">

  <head> <!-- La balise <head> peut être omise en HTML5 -->
    <meta charset="utf-8" />
    <link rel="stylesheet" type="text/css" href="tuto-1.css">
    <link rel="icon" type="image/png" href="icone.png">
    <title>Télécommande</title>
  </head>
  
  <body>
    <form  method="get">
      <p class="titre">Cliquez sur un bouton</p>
      <input type="submit" value="ON" name="on" />
      <input  type="submit" value="OFF" name="off" />
    </form>
    <p class="retour">LED éteinte</p>
  </body>
    
</html>

et le nouveau sketch serveur dans son intégralité :

test-serveur.ino
Cacher/Afficher le codeSé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.
81.
82.
83.
84.
85.
86.
87.
88.
89.
#include <SdFat.h>
#include <Ethernet.h>

SdFat SD;

const int carteSD = 4;

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

void setup()
{
  SD.begin(carteSD);
  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"))
      {
        arHtml(client, "text/html");
        envoiFichier(client, "tuto-1.html");
      }
      else if (reception.startsWith("GET /tuto-1.css HTTP/1.1"))
      {
        arHtml(client, "text/css");
        envoiFichier(client, "tuto-1.css");
      }
      else if (reception.startsWith("GET /icone.png HTTP/1.1"))
      {
        arHtml(client, "image/png");
        envoiFichier(client, "icône.png");
      }
      client.stop();
    }
  }
}

// FONCTIONS

void arHtml(EthernetClient nomClient, char type)
{
  nomClient.println(F("HTTP/1.1 200 OK"));
  nomClient.print(F("Content-Type: "));
  nomClient.println(type);
  nomClient.println(F("Connection: close"));
  nomClient.println();
}

void envoiFichier(EthernetClient nomClient, String fichierEnCours)
{
  char tableau[fichierEnCours.length()+1];
  fichierEnCours.toCharArray(tableau, fichierEnCours.length()+1);
  if (SD.exists(tableau))
  {
    File fichier = SD.open(tableau, FILE_READ);
    while (fichier.available())
    {
      nomClient.write(fichier.read());
    }
    fichier.close();
  }
  else
  {
    nomClient.println("Fichier '" + fichierEnCours + "' introuvable...");         
  }  
}

Téléversez ce sketch et connectez-vous. Voici ce que vous obtenez :

Image non disponible

Il s’agit, sans surprise, de la page que vous connaissez déjà, avec le petit plus dû à l’icône apparaissant dans l’onglet.

Image non disponible

L’affichage sur le moniteur est enfin convenable. Juste pour le fun, nous sommes là pour expérimenter, reconnectez-vous : vous pouvez constater l’absence de la requête concernant le téléchargement de l’icône. Comment expliquer cela, d’autant que l’icône est toujours à sa place dans l’onglet ? Tout simplement, le navigateur dispose d’un mécanisme permettant, entre autres choses, d’augmenter la vitesse d’affichage et d’économiser le trafic sur le réseau : la mise en cache. Grâce à ce dispositif, l’icône a été mémorisée sur le PC, ce qui fait qu’il est inutile de la recharger tant qu’on ne la modifie pas. Il n’y a donc pas de requête.

Ce sketch est parfaitement fonctionnel, mais il faut bien dire qu’à part envoyer la page HTML que vous aviez réalisée dans la première partie, il ne fait pas grand-chose. Pourtant, dans les coulisses, il y a eu pas mal de changements.

Vous avez peut-être remarqué que si vous cliquez sur un des boutons, pour voir, non seulement la LED ne change pas d’état, mais en plus votre navigateur vous informe de la réinitialisation de votre page et n’affiche donc plus votre télécommande. Ceci est normal. En effet, le code HTML que nous avons utilisé, et qui vient de la première partie, est lui aussi fonctionnel, ce qui fait que le clic sur un des boutons envoie bien une requête au serveur. Or, nous n’avons rien prévu pour traiter cette requête dans notre sketch, et donc le serveur ne renvoie rien, pas même l’accusé de réception, ce qui entraîne la réinitialisation de la connexion, comme nous l’avons vu dans le tutoriel précédent.

Je vous laisse, à titre d’exercice, le soin de compléter ce sketch pour résoudre ce problème. Vous avez tous les éléments pour le faire. Comme je suis plutôt d’un naturel serviable, je vais quand même vous donner un lien pour télécharger le sketch completTéléchargement des fichiers INO, HTML, CSS et PNG. Je vous encourage malgré tout à essayer de l’écrire vous-même. Au niveau formation, il n’y a rien de mieux.

Par contre, et là vous allez probablement être déçu, en l’état actuel des choses, seule la partie télécommande est opérationnelle : les fonctionnalités concernant la télémétrie ne vont pas pouvoir être implémentées. Comme le code HTML est à présent contenu dans un fichier localisé sur la carte microSD, il n’est plus accessible directement par le sketch. Son contenu ne pourra donc pas être modifié avant son envoi et votre page s’affichera toujours identique à elle-même.

IV. Conclusion

Les techniques que nous venons de voir sont-elles donc inutilisables dans notre contexte et avons-nous fait ce travail pour rien ? Non bien sûr, sans quoi l’intérêt de ce tutoriel aurait été très limité.

D’abord, j’ai dit que le code HTML n’était plus « accessible directement par le sketch ». Cela sous-entend qu’il l’est de manière indirecte. La bibliothèque SdFat nous permet en effet aussi bien d’écrire que de lire sur la carte microSD. Il est donc possible de générer un fichier HTML tenant compte des mises à jour que l’on souhaite afficher chez le client et d’envoyer ensuite ce fichier en utilisant les méthodes que nous venons de voir.

Cependant, cette manière d’opérer va compliquer le code de façon non négligeable, d’une part, et d’autre part l’impact de ce code sur la mémoire va limiter considérablement notre marge de manœuvre concernant l’évolution du système. En conséquence, cette pratique n’offre plus vraiment d’intérêt comparée à la manière dont nous avons traité le problème dans la première partie.

Ensuite, et heureusement, il y a une manière bien plus élégante et efficace pour surmonter cette difficulté : je vous propose de découvrir, dans la troisième et dernière partie de ce tutoriel, comment utiliser JavaScript et JSON pour mettre en œuvre un système de requêtes AJAX qui va permettre de ne transmettre qu’une fois les fichiers, lors de la connexion, en transférant au client la charge de générer les mises à jour de la page, ce qui contribuera à alléger la charge de notre serveur pour lui permettre de faire son travail de microcontrôleur.

V. Remerciements

Je remercie f-leb pour sa relecture technique et ses suggestions.

Je remercie également escartefigue pour sa relecture orthographique.