Flashage OTA et système de fichiers sur l’ESP-01

Hervé Troadec

L’ESP-01 est, avec l’ESP-02 et l’ESP-05, une des plus petites implémentations commercialisées du microcontrôleur ESP8266EX de la firme Espressif. En tant que telle, il est normal qu’il n’exploite pas toutes les possibilités de ce microcontrôleur comme le ferait par exemple un ESP-12E, ni qu’il offre toutes les facilités de prototypage d’un NodeMCU-1.0.

À l’évidence, son manque de capacité GPIO ne le destine pas spécifiquement à la gestion d’ensembles électromécaniques. Pour l’exploiter au mieux, il faut l’utiliser dans son domaine d’expertise, la WiFi, et pour ce faire, lui fournir un écosystème ad hoc et des applications adaptées.

C’est ce que nous allons faire ici en exploitant, dans un premier temps, le principe de la mise à jour OTA, qui facilitera grandement les opérations de flashage, et en mettant en place, dans un deuxième temps, un système de fichiers opérationnel comparable, bien que de faible capacité, à celui offert par un lecteur de carte microSD.

Nous mettrons en œuvre ces deux techniques pour réaliser, à titre d’exemple, ce qui pourrait être le point de départ d’une petite station météo.

Commentez

- Titre : Flashage OTA et système de fichiers sur l’ESP-01

- Auteur : Hervé Troadec

- Parution : 27 août 2020

- Public visé :

- Licence : Le contenu de cet article est rédigé par Hervé Troadec et est mis à disposition selon les termes de la Licence Creative Commons Attribution - Pas d'Utilisation Commerciale - Partage dans les Mêmes Conditions 3.0 non transposé. Les logos Developpez.com, en-tête, pied de page, css, et look & feel de l'article sont Copyright ® 2013 Developpez.com.

Sommaire

- I. Avant-propos

- II. Introduction

- III. Prérequis

- III-A. Matériel

- III-B. Logiciel

- IV. Programmation de l’ESP-01 avec l’adaptateur

- V. OTA (Over The Air)

- V-A. Organisation de la mémoire

- V-B. Mise en place de l’OTA

- V-C. Mise à jour OTA

- V-D. Utilisation standard de la WiFi

- V-E. Quelques précisions

- VI. Le système de fichiers

- VI-A. Le système de fichiers SPIFFS

- VI-B. Le système de fichiers LittleFS

- VI-B-1. Installation de ESP8266LittleFS

- VI-B-2. Téléversement des fichiers

- VII. Application exemple

- VII-A. Préparation

- VII-B. Téléversements

- VII-C. Connexion

- VIII. Conclusion

- IX. Remerciements

I - Avant-propos

Quel choix avons-nous pour expérimenter avec le microcontrôleur ESP8266EX ?

Dans la famille ESP8266, il n’y a, en pratique, que trois cartes qui se prêtent à l’expérimentation, à savoir qui disposent de broches enfichables : ESP-01, ESP-05 et ESP-201.

- L’ESP-01, qui dispose de quatre broches GPIO dont deux réservées à la liaison série. Il n’est pas enfichable sur une plaque de prototypage.

- L’ESP-05, qui ne dispose que de deux broches GPIO, réservées à la liaison série, et est plutôt destiné à fournir la fonctionnalité WiFi, par exemple à une carte Arduino, au travers de cette liaison.

- L’ESP-201, qui correspond à un ESP-12E équipé de broches, et qui fournit l’accès à toutes les fonctionnalités de l’ESP8266EX. Toutefois, la disposition de ses broches rend malaisée son utilisation sur les supports de prototypage standards. Il offre la double fonctionnalité de l’antenne intégrée et de l’antenne externe.

Ces cartes nécessitent l’utilisation d’un adaptateur UART/USB pour être programmées.

Au-delà de cette famille, il existe des modules comme NodeMcu ou Wemos, implémentant l’ESP-12E, offrant une interface UART/USB et essentiellement destinés au développement. Ils n’ont pas vocation à être embarqués dans le produit final, du moins dans le domaine industriel, lequel hébergera plutôt le module ESP-12E. Ces modules correspondent en gros à un ESP-201 équipé d’un adaptateur UART/USB et d’un régulateur de tension, et dont la disposition des broches est compatible avec les supports de prototypage.

Pourquoi avoir choisi l’ESP-01 comme support matériel pour ce tutoriel. Ce n’est en effet pas la carte la plus polyvalente parmi celles présentées ci-dessus, et les applications que ce manque de polyvalence permettra seront limitées. Voici, pêle-mêle, quelques éléments de réponse à cette question, dans un ordre qui n’est pas forcément représentatif de leur pertinence respective :

- le coût : il s’agit, à priori, de la carte la moins chère de la famille ESP8266EX, et concernant l’expérimentation, c’est un plus indéniable. Par dix, on les trouve à environ un euro pièce, c’est dire qu’en cas de crash, il n’y a pas de drame ;

- sans avoir la polyvalence de ses grandes sœurs, elle n’en dispose pas moins de la WiFi, de deux broches GPIO et de l’implémentation possible d’un bus I2C ou d’un bus 1-wire ce qui est loin d’être négligeable ;

- étant donné que ce tutoriel est réalisé sur la carte la moins polyvalente de la série, il sera valable pour toutes les autres cartes sans aucune restriction ;

- l’absence d’interface UART intégrée et le peu de composants implantés ont un impact bénéfique sur la consommation, bien que ce bénéfice soit relativement négligeable en cas d’utilisation de la WiFi, cette dernière étant grosse consommatrice d’énergie ;

- l’encombrement minimal de cette carte la rend intégrable dans pratiquement n’importe quel projet, pour peu que ses capacités suffisent à ce projet ;

- enfin, cette minicarte dispose en quelque sorte d’un miniécosystème sous la forme de petits « shields » offrant quelques fonctionnalités intéressantes comme l’interfaçage avec un relais ou un capteur de température.

Ce module relais tient sans problème dans la boite d’encastrement standard d’un interrupteur mural et permet de transformer celui-ci en interrupteur télécommandé par un smartphone en utilisant la fonctionnalité WiFi.
Il faut bien entendu lui fournir une alimentation, ce qui est réalisé avec le module alimentation présenté ci-dessous, qui se logera également sans difficulté dans le boîtier encastré.
Le prix de ce module est d’environ 2,50 € livré depuis la Chine.

Comme on peut le voir sur cette capture, il s’agit ici d’un module basé sur le capteur de température/humidité DHT22. Accompagné de l’alimentation présentée ci-dessous, et installé dans un boîtier ventilé, il pourra être mis en place partout où vous disposez du 220 V.
L’affichage de la température se fera sur une tablette ou sur un smartphone.
Le prix de ce module est d’environ 3,00 € livré depuis la Chine.

Module alimentation 220V-5V 700 mA. Idéal pour alimenter les modules ci-dessus qui nécessitent 5V.
Le prix de ce module est d’environ 1,70 € livré depuis la Chine.

L’utilisation du 220 V est potentiellement dangereuse. Les modules ci-dessus ne bénéficiant d’aucune isolation, les précautions à prendre lors de leur manipulation et le respect des normes concernant leur intégration dans un quelconque dispositif relève de votre responsabilité.

Soyez prudent !

Comme on peut le constater, cette petite carte a donc bien des atouts et mérite qu’on y prête attention. Nous allons voir, dans ce tutoriel, comment « booster » ses possibilités.

II - Introduction

Deux des limitations les plus gênantes de l’ESP-01 sont, à mes yeux, son flashage acrobatique et son manque cruel de sorties GPIO. On peut (dans une certaine mesure, ne rêvons pas) contourner ces insuffisances grâce à deux techniques plus simples d’emploi qu’il n’y paraît, et au choix judicieux de l’application à implémenter.

Nous allons, dans un premier temps, mettre en place une technique de mise à jour sans fil qui va utiliser la liaison Wifi en lieu et place de la liaison série : l’OTA (Over The Air). Cette technique va à la fois nous permettre de nous passer de la manipulation contraignante de l’adaptateur USB-UART et nous faire gagner une entrée/sortie GPIO, celle anciennement utilisée en réception (Rx) par la liaison série. Malheureusement, l’entrée/sortie correspondant à la transmission (Tx) de la liaison série n’est pas opérationnelle, peut-être à cause de la LED intégrée qui y est connectée. Nous passons donc de deux broches GPIO opérationnelles à trois (ce n’est pas Byzance, mais c’est mieux que rien), et d’un protocole de téléversement pas très souple à un protocole standard ne nécessitant aucune manipulation spécifique, et surtout aucune liaison physique.

Dans un deuxième temps, nous allons voir comment créer un système de fichiers dans la partie disponible de la mémoire flash et comment l’utiliser. La première étape consistera à installer l’outil de téléversement du système de fichiers dans l’EDI Arduino et à apprendre comment l’utiliser (simplissime), la seconde se réduisant à l’utilisation de la bibliothèque LittleFS.h, équivalent de la bibliothèque SdFat.h dédiée à la gestion des cartes microSD. Comme l’utilisation d’un lecteur de carte microSD nécessite l’utilisation d’un bus SPI, soit quatre broches GPIO, cela revient à ajouter virtuellement ces quatre broches GPIO à notre ESP-01. Pour que ce soit intéressant, il faut naturellement que notre application en ait besoin et c’est pourquoi je parlais plus haut du choix judicieux de l’application.

On pourra m’opposer à juste titre la très grande différence de capacité entre un ESP-01, disons 400 kio et une carte microSD standard, disons 4 Gio, soit un facteur de 1 pour 10 000. Pour être honnête, je ne considère pas ce critère vraiment pertinent dans la mesure où le traitement de 4 Gio de données n’est clairement pas à la portée d’un Arduino UNO, carte à laquelle je compare, à tort ou à raison, l’ESP-01.

Le simple transfert, depuis un shield Ethernet 2, d’un fichier JavaScript d’une centaine de kilooctets dans une application client-serveur prend plusieurs dizaines de secondes : c’est dissuasif. Ce genre d’architecture ne permet raisonnablement que de traiter des fichiers d’une dizaine de kilooctets, et ce n’est déjà pas si mal.

Nous verrons enfin comment implémenter une petite application graphique destinée à afficher sur un navigateur la température et l’hygrométrie fournies par un capteur DHT22. Ce capteur ne nécessitant qu’une broche GPIO en plus de son alimentation pour fonctionner, il restera deux broches GPIO inutilisées. Tout bien considéré, leur nombre est donc « largement » suffisant puisqu’on pourrait même connecter, en I2C, un capteur barométrique comme le BMP280 pour en faire une vraie petite station météo.

III - Prérequis

III-A - Matériel

Pour réaliser cette manipulation, nous n’aurons pas besoin de casser la tirelire, seuls les trois modules suivants étant indispensables :

Matériel

Description

ESP-01 :

- microcontrôleur ESP8266EX ;

- fréquence CPU : 80 MHz ;

- fréquence flash : 40 MHz ;

- mémoire flash externe : 958 448 ko ;

- mémoire dynamique : 81 920 ko ;

- alimentation : 3,3 V ;

- GPIO en mode UART : 2 ;

- GPIO en mode OTA : 3.

Brochage de l’ESP-01

- VCC : 3,3 V ;

- GND : 0 V (masse) ;

- GPIO 1 : GPIO + U0TXD ⟶ Fonction transmission UART pendant le flashage ;

- GPIO 3 : GPIO + U0RXD ⟶ Fonction réception UART pendant le flashage ;

- EN : CHIP-EN ⟶ Activation du chip :

- état haut = chip actif,

- état bas = chip inactif,

- la broche doit être connectée ;

- RST : EXT_RSTB ⟶ Réinitialisation :

- état normal niveau haut (pull up),

- niveau bas pour réinitialiser ;

- GPIO 0 : GPIO + doit être maintenue à l’état bas pendant le flashage ;

- GPIO 2 : GPIO.

Note :
le document technique recommande d’utiliser EN plutôt que RST pour la réinitialisation.

Adaptateur USB↔UART pour ESP-01 :

- chip CH340 ;

- switch mode programmation / mode standard ;

- alimentation : 5 V (USB) ;

- niveau logique : 3,3 V.

DHT22 :

- alimentation : 3,3 V à 6,0 V ;

- sortie numérique sur bus 1 conducteur ;

- échelle de mesure :

- température : -40 à +80 °C,

- humidité relative : 0 à 100 % ;

- précision :

- température : ± 0,5 °C,

- humidité relative : ± 5 % ;

- résolution :

- température : 0,1 °C,

- humidité relative : 0,1 %.

Une fois l’OTA mise en place, vous devrez bien sûr ajouter les éléments propres à votre projet, tout comme vous l’auriez fait sans cette technique.

Pour information, voici les trois versions de l’ESP-01 actuellement disponibles sur le marché :

Module

Couleur du PCB

Mémoire flash

LED témoin d’alimentation

LED_BUILTIN

ESP-01

Bleu

512 Kio

Rouge

Bleue sur TX

ESP-01

Noir

1 Mio

Rouge

Bleue sur TX

ESP-01S

Noir

1 Mio

Absente

Bleue sur GPIO 2

L’ESP-01 « bleu » (512 Kio) n’a plus vraiment d’intérêt dans la mesure où on le trouve au même prix que ceux disposant de 1 Mio de mémoire flash. De plus, il est contre-indiqué pour être utilisé conjointement avec l’OTA (voir remarque plus bas).

Pour les autres versions, je destinerais plutôt l’ESP-01 standard au prototypage, quand il est parfois utile (voire prudent) d’être en mesure de vérifier d’un coup d’œil si le module est alimenté, et la version « S » à la « production » pour éviter les consommations inutiles, surtout en cas d’utilisation d’alimentation autonome.

III-B - Logiciel

Pour suivre ce tutoriel, il vous faudra :

- l’EDI Arduino version 1.6.8 ou plus récente (version actuelle : 1.8.12) ;

- le gestionnaire de carte ESP8266 installé : si ce n’est pas encore le cas, le tutoriel Débuter avec le module WiFi ESP8266 vous indiquera la marche à suivre (version actuelle : 2.6.3) ;

- l’interpréteur Python 3 installé sur votre système (version actuelle : 3.8.2) : sous Linux, Python est installé d’origine. Si vous êtes sous Mac OSX® ou Windows® et que python 3 n’est pas installé, rendez-vous sur la page de téléchargement concernée, respectivement ici et là.

Vous verrez souvent indiqué sur Internet que la version 2.7 de Python est requise. Cela vient du fait que le portage sous Python 3 est très récent. Vous ne devriez cependant pas avoir de problème, quelle que soit la version que vous utilisez.

Je vous conseillerais malgré tout, si ce n’est déjà fait, d’installer la version 3, car il est probable que dans un avenir plus ou moins proche, seul le portage sous Python 3 sera maintenu.

Il vous faut également expliquer à l’EDI que vous allez travailler avec un ESP8266EX :

Comme il n’y a pas d’item spécifique pour le module ESP-01, vous devrez opter pour la configuration générique ESP8266 (attention à ne pas confondre avec l’ESP8285).

IV - Programmation de l’ESP-01 avec l’adaptateur

Avant tout, il vous faut un sketch. Plutôt que le « blink » habituel, je vous propose le sketch suivant :

info-esp.ino

1. ADC_MODE(ADC_VCC);

3. void setup()

4. {

5. Serial.begin(115200);

6. }

8. void loop()

9. {

10. /* ESP8266EX */

11. Serial.println("INFO ESP8266EX");

12. Serial.printf("Numéro de série de l'ESP8266EX .....: %u\n", ESP.getChipId());

13. float frequenceCpu = float(ESP.getCpuFreqMHz());

14. Serial.printf("Fréquence du CPU ...................: %4.1f MHz\n", frequenceCpu);

15. float vcc = float(ESP.getVcc()) / 1000;

16. Serial.printf("Alimentation .......................: %4.2f V\n", vcc);

17. String chaine = ESP.getCoreVersion();

18. chaine.replace('_', '.');

19. Serial.print("Version du gestionnaire de carte...: ");

20. Serial.println(chaine);

21. Serial.printf("Version du SDK .....................: %s\n\n", ESP.getSdkVersion());

22. /* Mémoire flash */

23. Serial.println("INFO FLASH");

24. Serial.printf("Numéro de série du chip ............: %u\n", ESP.getFlashChipId());

25. float frequenceFlash = float(ESP.getFlashChipSpeed()) /1000000;

26. Serial.printf("Fréquence ..........................: %4.1f MHz\n", frequenceFlash);

27. Serial.printf("capacité effective .................: %u octets\n", ESP.getFlashChipRealSize());

28. Serial.printf("capacité paramétrée dans l'EDI .....: %u octets\n", ESP.getFlashChipSize());

29. Serial.printf("Taille du sketch ...................: %u octets\n", ESP.getSketchSize());

30. Serial.printf("Mémoire disponible .................: %u octets\n\n\n", ESP.getFreeSketchSpace());

31. delay(10000);

32. }

qui va avoir l’avantage de nous fournir quelques renseignements de base sur notre ESP-01 et sur la mémoire flash dont il est pourvu, ainsi que sur la configuration concernant celle-ci dans l’EDI.

Ensuite, vous devez connecter le module ESP-01 à l’adaptateur. Voici ce que ça donne en image :

Effectuez à présent les manipulations suivantes :

- positionner le commutateur sur « PROG » (sérigraphie sur la carte) ;

- brancher l’adaptateur sur une prise USB du PC ;

- attendre une seconde environ puis basculer le commutateur sur « UART » ;

- sélectionner le port série correspondant à votre matériel ;

- téléverser le sketch comme d’habitude ;

- débrancher l’adaptateur de la prise USB.

À partir de maintenant, soit l’utilisation du sketch nécessite la liaison série : dans ce cas vous devrez rebrancher l’adaptateur sur le PC ; soit la liaison série est inutile : vous devrez alors déconnecter l'ESP-01 de l’adaptateur pour l’utiliser dans le prototypage de votre projet, car le type de connexion utilisé ne laisse pas l’accès aux broches. C’est là que vous allez apprécier l’OTA. N’oubliez pas de raccorder la broche EN au VCC (+ 3,3 V) pour activer le microcontrôleur.

Pour ce qui nous concerne dans l’immédiat, comme le sketch que nous venons de téléverser est destiné à envoyer des données sur le moniteur série de l’EDI, vous allez ouvrir celui-ci et reconnecter l’adaptateur au PC.

Vous devriez obtenir ce genre de chose :

V - OTA (Over The Air)

L’OTA est une technique de mise à jour devenue courante depuis l’avènement des smartphones et des tablettes. L’appareil télécharge depuis Internet, via le réseau Wifi routé par la box, la nouvelle version d’un logiciel puis, après vérification du bon déroulement de l’opération, met à jour ce logiciel. C’est ce que nous allons faire, avec notre petit ESP-01 dans le rôle du smartphone.

Outre la différence de technologie entre les deux méthodes de téléversement, Série ou Wifi, le déroulement des opérations n’est pas le même. Lors du téléversement Série, on écrase purement et simplement l’ancienne version en la remplaçant par la nouvelle. Dans le cas du téléversement OTA, on commence par compresser le fichier binaire issu de la compilation du nouveau sketch, ensuite on téléverse cette nouvelle version dans une zone disponible de la mémoire flash réservée au programme, puis on vérifie l’intégrité du fichier téléchargé à l’aide d’une somme de contrôle MD5. Si cette vérification est concluante, on décompresse la nouvelle version à l’emplacement de l’ancienne, ce qui a pour effet d’écraser celle-ci.

Pour cette raison, la taille du sketch compilé (le fichier binaire), ne doit pas excéder la moitié de la mémoire flash allouée au programme, car celle-ci contient deux versions du sketch : la version en cours et la version compressée. En théorie, on pourrait dire que la somme de la taille du binaire et de la taille du binaire compressé ne devrait pas excéder la taille disponible pour le programme, ce qui conduirait à la possibilité d’écrire des sketchs occupant plus de la moitié de la mémoire programme. Il y a cependant deux inconvénients :

- le premier, et le plus important, est que la documentation ne précise pas si la version compressée est copiée à la fin de la mémoire programme ou à partir du milieu. Dans le premier cas, le sketch décompressé pourrait occuper plus de la moitié de la mémoire, mais pas dans le second ;

- le deuxième inconvénient est que pour pouvoir faire la somme des deux fichiers (le binaire standard et le binaire compressé) il faut connaître leur taille, et que celle du binaire compressé n’est connue que lors du téléversement (le renseignement est affiché dans la console), donc trop tard.

Réserver deux fois la taille du sketch est donc plus simple et plus sûr, d’autant que la place ne manque pas.

V-A - Organisation de la mémoire

Le microcontrôleur ESP8266EX qui équipe le module ESP-01 ne possède pas de mémoire flash interne. En cela, d’ailleurs, l’appellation « microcontrôleur » n’est pas justifiée, car un microcontrôleur incorpore, en théorie, une mémoire programme statique sous forme de PROM, EEPROM ou, plus récemment, de mémoire flash. On va malgré tout continuer à l’appeler comme ça. Ce microcontrôleur n’a pas été conçu, à l’origine, pour être autonome, mais plutôt pour apporter la fonctionnalité Wifi à d’autres systèmes. Le voici, par exemple, implémenté par Arduino sur la carte UNO WiFi :

La carte citée ci-dessus n’existe plus et je n’en parle que pour illustrer mon propos. Elle est remplacée par la carte « UNO WIFI rev.2 ».

Cette carte est architecturée autour du nouveau contrôleur 8 bits ATmega4809 de Microchip, qui remplace le contrôleur ATMega328P de chez Atmel, et embarque la puce WiFi NINA-W102 de chez u-blox en remplacement de la puce ESP8266EX de chez Espressif.

Pour acquérir cette autonomie, ce qui se justifie au regard de ses possibilités intrinsèques, il faut lui adjoindre de la mémoire non volatile pour stocker un programme. On utilise pour cela un chip mémoire flash série, comme le M25P10, avec lequel il communiquera via un bus SPI, ce protocole étant totalement transparent pour l’utilisateur final.

L’ESP8266EX peut gérer théoriquement jusqu’à 16 Mio de mémoire flash. Cela ne dépend que de la capacité du chip mémoire auquel il est connecté. En ce qui concerne l’ESP-01, comme vu plus haut, trois versions sont disponibles : une avec 512 Kio et deux avec 1 Mio ; dont seules les versions implémentant 1 Mio seront utilisables ici.

J’utilise personnellement la version 1 Mio qui dispose d’un voyant d’alimentation pour les raisons vues plus haut, mais c’est affaire de choix.

Ce tutoriel peut être adapté à toutes les versions commercialisées de l’ESP-XX, même à la version ESP-05 qui ne dispose à la base d’aucune broche GPIO, grâce à la libération de la broche Rx (GPIO3) par l’OTA.

Tout est logiciel, il n’y a rien de figé dans l’organisation de la mémoire flash et l’on peut donc imaginer nombre de structures différentes : cela demande toutefois du savoir-faire et du travail. Heureusement, des développeurs compétents nous ont concocté des bibliothèques qui s’occupent de tout ça. Ils nous laissent même la possibilité de configurer, dans une certaine mesure, la répartition de cette mémoire pour l’adapter au mieux à nos projets en jouant sur la quantité de mémoire allouée au sketch et au système de fichiers.

Nous disposons théoriquement de 1 Mio de mémoire flash. En réalité, un espace est réservé à partir de l’adresse 0xFB000, ce qui rend les vingt derniers kilooctets inexploitables par l’utilisateur final (que nous sommes). Ils sont en fait utilisés par des fichiers de configuration et des programmes « système » destinés entre autres à gérer la Wifi. Il nous reste donc environ 1004 Kio à répartir entre le sketch et un système de fichiers.

Comme nous l’avons vu, en ce qui concerne le sketch, il faut réserver au moins deux fois sa taille, car il y en aura toujours deux versions dans la mémoire flash. Quant au système de fichiers, tout dépend de l’utilisation que l’on veut en faire. Cela peut aller de zéro octet si on n’en a pas besoin, et dans ce cas le sketch peut occuper jusqu’à 502 Kio (1004 / 2), jusqu’à 512 Kio, et dans ce cas le sketch ne devra pas excéder 246 Kio ((1004 – 512) / 2).

Cette dernière configuration, qui est proposée dans le paramétrage, ne semble pas viable dans l’environnement Arduino. En effet, le sketch minimal Arduino, avec donc un setup() vide et une loop() vide, occupe déjà 255 656 octets, soit à peu près 250 kio, ce qui dépasse la limite théoriquement acceptable : l’ESP8266EX a en effet besoin d’un certain nombre de bibliothèques pour fonctionner dans l’environnement Arduino, et ces bibliothèques prennent de la place.

De plus, dans la documentation technique de l’ESP8266EX, Espressif stipule que l’OTA ne peut pas être utilisé sur les versions pourvues de seulement 512 kio de mémoire flash, soit la configuration dans laquelle on se retrouve si l’on réserve 512 kio sur 1 Mio pour le système de fichiers.

Bien que nous n’utilisions pas, dans l’immédiat, le système de fichiers, nous garderons la configuration par défaut : elle privilégie la place accordée au sketch, mais réserve suffisamment de place pour notre utilisation future de ce système de fichiers. Cela dit, à part la configuration vue ci-dessus, toutes les autres sont utilisables sans risque particulier. Il faut bien remarquer que même la configuration qui réserve 256 Kio pour le système de fichiers laisse malgré tout environ 124 Kio pour le sketch, déduction faite de la place occupée par les bibliothèques ESP8266EX, qu’il faut comparer aux 32 Kio de l’Arduino UNO. Il y a donc de quoi travailler sans trop se poser de question.

La capture suivante montre le paramétrage proposé par défaut dans l’EDI, à savoir 64 Kio de mémoire réservée au système de fichiers, et ~470 Kio de mémoire programme utilisable, soit ~940 Kio (470 × 2). Comme on l’a vu, cette quantité de mémoire réservée au sketch actif peut ne pas être une limite absolue étant donné que le sketch téléchargé est compressé.

Vous avez peut-être remarqué que j’ai augmenté la vitesse de téléversement par rapport à sa valeur par défaut. Comme cela, je gagne un peu de temps, mais dans la mesure où les téléversements suivants sont censés être effectués en OTA, ce n’est pas vraiment nécessaire.

Voici, par exemple, à quoi peut ressembler la mémoire de L’ESP-01 après téléversement de notre sketch :

Pour des raisons de lisibilité des textes, l’échelle n’est pas respectée, mais le principe est là :

- la compilation de sketch.ino engendre le fichier sketch.bin ;

- sketch.bin est compressé pour donner sketch.bin.gz ;

- sketch.bin.gz est téléversé dans la deuxième moitié de la mémoire programme puis vérifié à l’aide d’une somme de contrôle ;

- si la vérification est positive, sketch.bin.gz est décompressé à l’adresse 0x0000 et le programme démarre.

V-B - Mise en place de l’OTA

La gestion de l’OTA est purement logicielle. Voici donc le sketch minimal permettant d’initialiser cette gestion :

esp-ota.ino

1. #include <ArduinoOTA.h>

3. const char* ssid = "identité_du_routeur";  // Généralement, "routeur" = "box"

4. const char* password = "mot_de_passe_du_routeur";

6. void setup()

7. {

8. WiFi.mode(WIFI_STA);

9. WiFi.begin(ssid, password);

10. while (WiFi.waitForConnectResult() != WL_CONNECTED)

11. {

12. delay(1000);

13. ESP.restart();

14. }

15. ArduinoOTA.begin();

16. }

18. void loop()

19. {

20. ArduinoOTA.handle();

21. }

C’est tout. Je ne sais pas ce que vous en pensez, mais il est difficile de faire plus simple. La bibliothèque ArduinoOTA s’occupe de tout, notamment de charger les bibliothèques dont on a besoin. Les étapes sont les suivantes :

- importation de la bibliothèque ArduinoOTA ;

- fourniture des « identifiant et mot de passe » pour la connexion Wifi au routeur (box), comme pour connecter une tablette ;

- lancement de la procédure de connexion : cette procédure est répétée toutes les secondes jusqu’à ce que la connexion soit établie ;

- démarrage du service OTA ;

- utilisation des routines OTA dans la boucle principale (loop()).

Si l’on fait abstraction du code concernant la connexion Wifi proprement dite, la mise en place de l’OTA ne demande que trois lignes.

Téléversez ce code et attendez… Enfin non, n’attendez pas trop quand même. Il ne va rien se passer.

Regardez la console en bas de l’EDI :

On vous demande de faire un « reset » physique « via RTS pin » (RST pin peut-être) pour que les changements soient pris en compte. Comme il n’y a pas de bouton reset implémenté sur l’ESP-01, on fait ça à la hussarde : on débranche et on rebranche.

Maintenant, vous pouvez patienter quelques secondes sans perdre inutilement votre temps : si tout s’est bien passé, vous constaterez, en déroulant le menu « Outils ⇨ Port », l’apparition d’une nouvelle catégorie de ports intitulée « Ports réseau », et un nouvel item dans cette catégorie : il s’agit, vous vous en doutez, du port que vous allez utiliser pour faire vos mises à jour OTA. Son nom est constitué du numéro d’identification de votre ESP8266, que vous connaissez déjà grâce au sketch info-esp.ino, et de l’adresse IP que lui a attribuée le routeur sur le réseau.

L’appellation « Ports réseau » est discutable et est à mon avis utilisée pour faire un parallèle avec « Ports série ». Il s’agit en fait d’une URL.

Si vous ne pouvez pas changer l’adresse IP, qui est laissée à la seule initiative du routeur, vous pouvez par contre personnaliser la partie identification, de manière à retrouver plus facilement l’ESP concerné par le sketch en cours dans l’EDI, dans le cas où vous en auriez plusieurs de connectés.

Il vous suffira de saisir la ligne suivante avant la ligne ArduinoOTA.begin(); :

ArduinoOTA.setHostname("Bureau");

« Bureau » étant bien entendu un exemple. Dans ce cas, l’intitulé du port devient Bureau at 192.168.1.37.

V-C - Mise à jour OTA

Comme nous n’avons pas encore téléversé de sketch, à part bien sûr celui concernant la mise en place de l’OTA, ce que nous allons faire maintenant n’est pas, stricto sensu, une mise à jour. Cependant, en pratique, il n’y a aucune différence.

Le code vu dans le chapitre précédent fera à présent toujours partie de votre sketch. La mise en place doit impérativement être renouvelée à chaque écriture, car le code en cours va être écrasé par le nouveau, avec toutes ses fonctionnalités, y compris l’OTA.

Ce code a été une première fois envoyé via la liaison série, c’était la seule disponible, et il va à partir de maintenant être acheminé via l’OTA puisque celle-ci est opérationnelle. Si vous l’oubliez, votre mise à jour fonctionnera, mais vous ne bénéficierez plus de l’OTA. Ce n’est pas dramatique dans la mesure où on peut recommencer la manipulation en utilisant le port série, mais si ça vous impose un démontage de l’ESP, c’est ennuyeux. Ce n’est pas le but de la manœuvre.

Pour que l’on parle bien de la même chose, maintenant que vous avez le « squelette » OTA en place (que vous pouvez d’ailleurs sauvegarder dans un fichier appelé par exemple template‑ota.ino), le mot sketch ne concernera plus que ce que vous auriez écrit si vous n’utilisiez pas l’OTA. Et en l’occurrence, vous écrirez vos sketchs exactement comme d’habitude.

Pour faire simple et pour revenir à la tradition, nous allons faire clignoter une LED. Réalisez le montage suivant, ou du moins quelque chose d’analogue :

et téléversez le code que voilà :

maj-ota.ino

1. /***** Include et  déclarations OTA ***********************/

2. #include <ArduinoOTA.h>

3. const char* ssid = "identité_du_routeur";

4. const char* password = "mot_de_passe_du_routeur";

5. /**********************************************************/

7. // Déclarations du sketch

8. int led = 2;

9. unsigned long reference = 0;

11. void setup()

12. {

13. // Setup du sketch

14. pinMode(led, OUTPUT);

18. /***** Setup OTA ****************************************/

19. WiFi.mode(WIFI_STA);

20. WiFi.begin(ssid, password);

21. while (WiFi.waitForConnectResult() != WL_CONNECTED)

22. {

23. delay(1000);

24. ESP.restart();

25. }

26. ArduinoOTA.begin();

27. /********************************************************/

28. }

31. void loop()

32. {

33. /***** Procédure OTA ************************************/

34. ArduinoOTA.handle();

35. /********************************************************/

37. // Sketch

38. unsigned long instant = millis();

39. if (instant - reference >= 500)

40. {

41. reference = instant;

42. digitalWrite(led,  ! digitalRead(led));

43. }

44. }

Si votre LED est bien élevée, elle devrait clignoter.

Peut-être vous demandez-vous pour quelle raison je n’utilise pas la procédure delay() pour réaliser le clignotement, comme elle est utilisée dans le sketch « blink.ino ». Ce n’est, bien entendu, pas pour le plaisir de compliquer le code.

La fonction ArduinoOTA.handle()a pour but de répondre à une requête OTA, émise généralement lors d’une demande de mise à jour. Pour cette raison, elle est placée dans la boucle principale qui l’appelle en permanence, car la requête peut arriver n’importe quand.

Lors de l’utilisation de la procédure delay(), le déroulement du programme est bloqué pendant le temps paramétré, ce qui fait que, si la requête arrive pendant ce temps-là, elle sera ignorée, et la mise à jour ne pourra pas s’effectuer.

Donc, à retenir : il ne faut pas utiliser de procédure bloquante quand on utilise l’OTA.

Ne soyez pas étonné si le résultat n’est pas immédiat. Le code que vous venez de téléverser doit d’abord être vérifié puis, seulement si le résultat est conforme, il va être décompressé à la place de l’ancienne version. Ces opérations prennent du temps. Prévoyez environ vingt-cinq secondes.

Dans la capture suivante, les zones grisées représentent le code auquel vous ne devez pas toucher, du moins si vous désirez conserver les fonctionnalités OTA. Vous ne vous en occupez plus : il fait partie des meubles. Pour le reste, vous faites ce que vous voulez.

V-D - Utilisation standard de la WiFi

On peut s’interroger quant à la possibilité d’utiliser la WiFi dans nos sketchs, alors qu’on sait qu’elle est utilisée par l’OTA : c’est évidemment possible, heureusement. L’OTA ne vous limite en rien, et mis à part les quelques lignes nécessaires à son fonctionnement, vous n’avez rien à changer à vos habitudes de travail.

Voici par exemple un sketch permettant de télécommander l’allumage d’une LED depuis un navigateur, adapté librement, pour l’OTA, du sketch « WiFiManualWebServer.ino » que vous trouverez dans le menu Fichier ⇨ Exemples ⇨ ESP8266WiFi. Ce sketch utilise le même montage que le sketch précédent.

serveur-wifi-ota.ino

1. /***** Include et  déclarations OTA ***********************/

2. #include <ArduinoOTA.h>

3. const char* ssid = "identité_du_routeur";

4. const char* password = "mot_de_passe_du_routeur";

5. /**********************************************************/

7. // Déclarations du sketch

9. WiFiServer serveur(80);

11. const int led = 2;

12. const int ledTx = 1;

15. void setup()

16. {

17. // Setup du sketch

18. pinMode(ledTx, OUTPUT);

19. digitalWrite(ledTx, HIGH);

21. /***** Setup OTA ****************************************/

22. WiFi.mode(WIFI_STA);

23. WiFi.begin(ssid, password);

24. while (WiFi.waitForConnectResult() != WL_CONNECTED)

25. {

26. delay(1000);

27. ESP.restart();

28. }

29. ArduinoOTA.begin();

30. /********************************************************/

32. serveur.begin();

34. // La led émet 5 flashes pour avertir de la fin de la mise à jour

35. for (int i = 0; i < 5; i++)

36. {

37. digitalWrite(ledTx, LOW);

38. delay(100);

39. digitalWrite(ledTx, HIGH);

40. delay(300);

41. }

42. }

45. void loop()

46. {

47. /***** Procédure OTA ************************************/

48. ArduinoOTA.handle();

49. /********************************************************/

51. // Sketch

52. WiFiClient client = serveur.available();

53. if (!client) return;

54. client.setTimeout(1000);

55. String reception = client.readStringUntil('\r'); // On ne lit que la première ligne

56. while (client.available()) client.read(); // puis on vide le buffer

57. // On traite la requête

58. if (reception.indexOf(F("?ON")) != -1) digitalWrite(led, HIGH);

59. else digitalWrite(led, LOW);

60. // On envoie la réponse

61. client.println(F("HTTP/1.1 200 OK"));

62. client.println(F("Content-Type: text/html"));

63. client.println("");

64. client.println(F("<!DOCTYPE HTML>"));

65. client.println(F("<html>\n"));

66. client.println(F("<meta charset='utf-8'/>\n"));

67. client.println(F("<body>"));

68. client.print(F("\t<p>La LED est "));

69. if (digitalRead(led) == HIGH) client.print(F("allumée !"));

70. else client.print(F("éteinte !"));

71. client.println("</p>");

72. client.print(F("\t<p>Cliquez <a href='?ON'>ici</a> pour allumer la LED, ou <a href='?OFF'>ici</a> pour l'éteindre.</p>"));

73. client.println(F("</body>\n"));

74. client.print(F("</html>"));

75. }

Lors de la mise au point d’un sketch, on est amené à faire des téléversements fréquents. Utiliser la procédure OTA ne change rien à cet état de fait. Or, comme vu plus haut, une fois le téléversement OTA effectué, ce dont l’EDI nous fait part, le sketch n’est pas immédiatement opérationnel. C’est pour cette raison que j’ajoute les lignes suivantes :

for (int i = 0; i < 5; i++)
{
digitalWrite(ledTx, LOW);
delay(100);
digitalWrite(ledTx, HIGH);
delay(300);
}

destinées à faire clignoter la LED bleue à la fin du setup().

De cette manière, je suis averti du moment où je peux commencer à utiliser mon dispositif. Cela peut sembler superflu au premier abord, mais je vous assure qu’au deuxième « rabord », c’est bien pratique, et ça permet en plus de donner une utilité à cette LED.

V-E - Quelques précisions

- La durée d’une mise à jour peut facilement dépasser les trente secondes. Elle commence avec la compilation, puis continue avec la compression, le téléversement, la vérification, la décompression et le redémarrage. Soyez patient.

- Quand vous effectuez une mise à jour OTA, votre module peut très bien, et c’est l’intérêt, être en fonction en dehors de votre environnement immédiat : vous ne savez donc pas forcément ce qu’il est en train de faire. Or, l’exécution du programme en cours peut être interrompue pendant quelques dizaines de secondes, en fonction de la taille du code. Si le traitement de l’action en cours dépend de cette exécution, il peut être judicieux de veiller soit à l’achèvement préalable de cette action si elle est critique, soit à la reprise de son traitement par la nouvelle version au lancement de celle-ci.

- Utilisation de la broche GPIO 1 :

- son usage standard est la connexion Tx de l’UART ;

- la LED intégrée est reliée à cette sortie, mais ne peut pas être invoquée en tant que LED_BUILTIN. On y accède avec son numéro GPIO (1). À noter qu’elle est allumée à l’état bas ;

- la broche en elle-même ne semble pas utilisable en tant que broche GPIO.

- Utilisation du reset matériel (mise à l’état bas de la broche RST) :

- déconseillé par Espressif au profit de l’utilisation de la broche EN ;

- à proscrire en cas d’utilisation de l’OTA : le chip ne redémarre pas.

- Broche EN : pour un fonctionnement standard du module, la broche EN doit être maintenue à l’état haut (3,3 V). En positionnant cette broche à l’état bas, on met le microcontrôleur à l’état de veille, c’est-à-dire en état de consommation minimale d’énergie. Le déroulement du programme est arrêté. Cette broche ne doit pas rester « en l’air », et sera donc généralement connectée au VCC par l’intermédiaire d’une résistance de tirage (typiquement 10 kΩ), ce qui permettra la mise à la masse à l’aide d’un bouton en cas de besoin (reset).

VI - Le système de fichiers

Comme vous avez pu le constater précédemment, la quantité de mémoire flash allouée au microprogramme utilisateur de l’ESP-01 peut être modulée en fonction des besoins. Typiquement, sur le mégaoctet de mémoire disponible, environ 750 kio sont utilisables ce qui dépasse largement la taille moyenne d’un sketch, surtout quand on est habitué à tout faire tenir dans les 32 kio du contrôleur ATmega328 qui équipe un Arduino UNO. Bien que la donne soit différente si vous utilisez l’OTA, et quand vous aurez commencé, vous ne pourrez plus vous en passer, c’est du moins mon cas, vous aurez malgré tout de 400 à 500 kio de mémoire inemployée, en fonction de la taille de votre sketch.

Vous pouvez exploiter cette mémoire en y créant un système de fichiers dans lequel vous pourrez :

- copier des fichiers depuis votre PC à l’aide d’un utilitaire installé dans l’EDI Arduino ;

- accéder à ces fichiers en lecture ou en écriture depuis votre sketch ;

- créer et manipuler des fichiers depuis votre sketch ;

- en bref, faire comme si vous aviez un lecteur de carte microSD et la bibliothèque SdFat, en tenant compte toutefois de la différence de capacités, lesquelles ne sont pas comparables.

VI-A - Le système de fichiers SPIFFS

Je cite le système de fichiers SPIFFS (SPI Flash File System) pour la forme, car nous ne l’utiliserons pas. Il fonctionne très bien et est parfaitement adapté aux espaces mémoire et aux fichiers de taille réduite en raison de la petite taille de ses clusters (256 octets). De plus, c’est le système de fichiers « historique » et c’est celui que j’utilise (ou du moins que j’utilisais jusqu’à présent). Malheureusement, son évolution est en stand by et il va probablement être supprimé dans les prochaines versions du gestionnaire de cartes ESP8266, au profit du système LittleFS plus performant et activement maintenu. Il est donc préférable, pour des raisons de pérennité, de passer au petit nouveau.

Pour info, le système de fichiers SPIFFS est toujours présent dans la version 2.7.4 qui est actuellement la dernière version.

VI-B - Le système de fichiers LittleFS

Réputé pour être plus rapide que le SPIFFS dans la plupart des cas, il implémente en outre une véritable arborescence avec gestion des répertoires. Son inconvénient majeur pour l’utilisation dans une configuration modeste, comme celle de l’ESP-01, est la taille de ses clusters : 4 kio. À part ça, son utilisation est pratiquement identique à celle du SPIFFS et le passage de l’un à l’autre ne pose aucun problème.

VI-B-1 - Installation de ESP8266LittleFS

Il va falloir, dans un premier temps, « installer » l’outil de téléversement du système de fichiers dans l’EDI Arduino. Je mets « installer » entre guillemets, car il s’agit en réalité d’une simple copie de fichier dans un répertoire spécifique.

Commencez dans un premier temps par télécharger sur Github la dernière version du fichier ESP8266LittleFS-x.y.z, actuellement ESP8266LittleFS-2.6.0, puis décompressez-la dans le répertoire /tools qui se trouve dans la racine du répertoire d’installation de l’EDI Arduino que vous utilisez. Si ce répertoire (/tools) n’existe pas, ce qui peut être le cas si vous n’avez pas encore installé d’outil externe, créez-le.

N’hésitez pas à vérifier ici que la procédure d’installation n’a pas changé, on ne sait jamais.

Vous devriez obtenir une arborescence de ce genre :

/home/user/../arduino-1.8.12/tools/ESP8266LittleFS/tool/esp8266littlefs.jar

en admettant que le répertoire de votre version courante d’Arduino s’appelle /arduino‑1.8.12. Lors de futures mises à jour, il vous suffira de remplacer le fichier esp8266littlefs.jar par la nouvelle version.

Ouvrez l’EDI Arduino ou relancez-le s’il était déjà ouvert puis déroulez le menu « Outils ». L’entrée ESP8266 LittleFS Data Upload doit s’y trouver :

Vous pouvez remarquer, juste en dessous, l’entrée ESP8266 Sketch Data Upload qui correspond au même outil, mais pour le système de fichiers SPIFFS.

VI-B-2 - Téléversement des fichiers

Votre outil de téléversement est prêt à être utilisé, voici son mode d’emploi :

- chargez un sketch dans l’EDI : n’importe quel sketch conviendrait, mais il est plus logique de charger celui qui utilisera le système de fichiers que vous allez mettre en place ;

- dans le dossier de ce sketch, créez le dossier /data ;

- dans le dossier /data, copiez les fichiers que vous voulez transférer sur votre ESP-01, en utilisant l’arborescence qui vous convient, le nom proprement dit de chaque fichier ou dossier, indépendamment de son chemin, étant limité à 31 caractères, suffixe inclus .

Vous devez considérer le dossier /data comme étant la racine du système de fichiers que vous allez créer dans la mémoire de votre ESP-01.

Depuis votre sketch, vous pourrez invoquer les fichiers se trouvant à la racine avec ou sans le préfixe « / », c’est-à-dire que le fichier /data/fichier sera invoqué indifféremment sous la forme fichier ou /fichier ; par contre, les fichiers se trouvant dans des sous-répertoires, comme /data/chemin/fichier, seront invoqués classiquement, c’est-à-dire sous la forme /chemin/fichier.

Le nombre de fichiers et de répertoires n’est limité que par la quantité de mémoire attribuée au système de fichiers dans votre paramétrage.

À partir de maintenant, de deux choses l’une :

- soit vous utilisez le téléversement série, auquel cas vous devez vous assurer que le bon port série est sélectionné et si besoin, en fonction de votre version de l’EDI, vérifier que le moniteur série est fermé. Vous devrez de plus respecter le protocole de téléversement que l’on a vu au Chapitre III. Programmation de l’ESP-01 avec l’adaptateur ;

- soit vous utilisez le téléversement OTA, auquel cas vous sélectionnez simplement le port réseau concerné.

N’oubliez pas également de vérifier que la bonne carte est sélectionnée : c’est arrivé à des gens très biens de téléverser sur la mauvaise carte.

Il ne vous reste plus à présent qu’à cliquer sur Outils ⇨ ESP8266 LittleFS Data Upload et le téléversement du système de fichiers commence. Une autre différence notable entre les deux modes de téléversement réside dans la différence sensible des durées de transfert, à l’avantage indéniable de l’OTA.

À ce stade, il faut bien prendre en compte que vous téléversez l’image du système de fichiers contenu dans votre dossier data, et que cette image va écraser le système fichiers, s’il existe, déjà présent dans la mémoire flash. Il n’est pas possible avec cette méthode d’ajouter individuellement, depuis un PC, un fichier (ou dossier) à un système de fichiers existant.

Par contre, bien entendu, le sketch n’est quant à lui pas affecté par cette opération. Les deux emplacements mémoire sont distincts l’un de l’autre.

VII - Application exemple

Maintenant qu’on en sait un peu plus sur ces deux techniques, on va les mettre en œuvre dans une petite application qui pourra éventuellement servir de squelette pour des applications plus sophistiquées.

VII-A - Préparation

Commencez par réaliser ce montage (ou un montage équivalent) :

À l’emplacement qui vous conviendra le mieux, créez le dossier /meteo. Dans ce dossier, créez un fichier texte vide, nommez-le meteo.ino et copiez dedans le sketch suivant :

meteo.ino

1. /***** Include et  déclarations OTA ***********************/

2. #include <ArduinoOTA.h>

3. const char* ssid = "Identification du routeur (box)";

4. const char* password = "Mot de passe du routeur (box)";

5. /**********************************************************/

8. // includes et déclarations du sketch

10. #include "LittleFS.h"

11. #include "DHT.h"

13. #define DHTTYPE DHT22

15. unsigned long reference = 0;

16. const byte ledBuiltIn = 1;

17. const byte brocheDHT = 2;

18. float hygrometrie = 0.0;

19. float temperature = 0.0;

21. DHT capteurDHT(brocheDHT, DHTTYPE);

22. WiFiServer serveur(80);

24. void setup()

25. {

26. // Setup du sketch

27. pinMode(ledBuiltIn, OUTPUT);

28. digitalWrite(ledBuiltIn, HIGH);

29. LittleFS.begin();

30. capteurDHT.begin();

32. /***** Setup OTA ****************************************/

33. WiFi.mode(WIFI_STA);

34. WiFi.begin(ssid, password);

35. while (WiFi.waitForConnectResult() != WL_CONNECTED)

36. {

37. delay(1000);

38. ESP.restart();

39. }

40. ArduinoOTA.begin();

41. /********************************************************/

43. serveur.begin();

44. // La led clignote 5 fois pour avertir de la fin de la mise à jour

45. for (int i = 0; i < 5; i++)

46. {

47. digitalWrite(ledBuiltIn, LOW);

48. delay(200);

49. digitalWrite(ledBuiltIn, HIGH);

50. delay(200);

51. }

52. }

55. void loop()

56. {

57. /***** Procédure OTA ************************************/

58. ArduinoOTA.handle();

59. /********************************************************/

61. // Sketch

62. /* Récupération des données issues du capteur DHT22 */

64. unsigned long instant = millis();

65. if (instant - reference >= 2500) // Pause entre deux mesures (ce capteur est très lent)

66. {

67. hygrometrie = capteurDHT.readHumidity();

68. temperature = capteurDHT.readTemperature(); // °C par défaut : (true) pour Fahrenheit

69. if (isnan(hygrometrie) || isnan(temperature)) // à tout hasard

70. {

71. hygrometrie = 0.0;

72. temperature = 0.0;

73. }

74. reference = instant;

75. }

77. /* Implémentation du serveur */

79. WiFiClient client = serveur.available();

80. if (!client) return;

81. client.setTimeout(1000);

83. /* Récupération de la requête */

85. String reception = client.readStringUntil('\r'); // On ne lit que la première ligne

86. while (client.available()) client.read(); // puis on vide le tampon

88. /* Traitement de la requête */

90. if (reception.indexOf("ajax?") != -1) // Si requête ajax

91. {

92. if (reception.indexOf("maj?") == -1) // Si pas maj

93. {

94. reception.replace("GET /ajax?", "");

95. reception.replace("HTTP/1.1", "");

96. reception.trim();

97. }

98. arHtml(client, "application/json");

99. maj(client, hygrometrie, temperature);

100. }

101. else

102. {

103. if (reception.startsWith("GET / HTTP/1.1"))

104. {

105. arHtml(client, "text/html");

106. envoiFichier(client, "/meteo.html");

107. }

108. else if (reception.indexOf("tCadran.js") != -1)

109. {

110. arHtml(client, "application/javascript");

111. envoiFichier(client, "/tCadran.js");

112. }

113. else if (reception.indexOf("ajax.js") != -1)

114. {

115. arHtml(client, "application/javascript");

116. envoiFichier(client, "/ajax.js");

117. }

118. else if (reception.indexOf("thermo.js") != -1)

119. {

120. arHtml(client, "application/javascript");

121. envoiFichier(client, "/thermo.js");

122. }

123. else if (reception.indexOf("hygro.js") != -1)

124. {

125. arHtml(client, "application/javascript");

126. envoiFichier(client, "/hygro.js");

127. }

128. delay(1);

129. }

130. client.stop();

131. }

133. // FONCTIONS

135. void arHtml(WiFiClient& nomClient, String type)

136. {

137. nomClient.println(F("HTTP/1.1 200 OK"));

138. nomClient.println("Content-Type: " + type +"");

139. nomClient.println(F("Connection: close"));

140. nomClient.println(F(""));

141. }

143. void envoiFichier(WiFiClient& nomClient, String fichierEnCours)

144. {

146. if (LittleFS.exists(fichierEnCours))

147. {

148. File fichier = LittleFS.open(fichierEnCours, "r");

149. while (fichier.available())

150. {

151. nomClient.write(fichier.read());

152. }

153. fichier.close();

154. }

155. else

156. {

157. nomClient.println("Page '" + fichierEnCours + "' introuvable...");

158. }

159. }

161. void maj(WiFiClient& nomClient, float h, float t)

162. {

163. String reponseJSON = "{";

164. reponseJSON.concat("\"hygrometrie\":");

165. reponseJSON.concat(h);

166. reponseJSON.concat(",\"temperature\":");

167. reponseJSON.concat(t);

168. reponseJSON.concat("}");

169. nomClient.println(reponseJSON);

170. }

Pour la suite, créer le fichier toto.ino signifiera créer un fichier texte vide et le nommer toto.ino.

Toujours dans le dossier /meteo, créez le dossier /data et ouvrez-le. Dans ce dossier :

- créez le fichier meteo.htmlet copiez dedans le code suivant :

meteo.html

1. <!DOCTYPE html>

3. <html lang="fr">

5. <head>

6. <meta charset="utf-8" />

7. <style>

8. .cadran {padding: 15px; }

9. table {border-collapse: collapse; background-color: rgb(100,100,100);}

10. td {border: 1px solid rgb(80,80,80); }

11. </style>

12. <script src="tCadran.js"></script>

13. <script src="ajax.js"></script>

14. <title>Météo</title>

15. </head>

17. <body onload="maj()">

18. <table style="padding: 15px">

19. <tr>

20. <td class="cadran"><canvas id="thermo"></canvas></td>

21. <td class="cadran"><canvas id="hygro"></canvas></td>

22. </tr>

23. </table>

24. <script src="thermo.js"></script>

25. <script src="hygro.js"></script>

26. </body>

28. </html>

- créez le fichier tCadran.js et copiez dedans le code suivant :

tCadran.js

1. /*

2. Dessin d'un cadran gradué circulaire

3. */

5. TCadran = function (params)

6. {

7. const DEMI_PI           = Math.PI / 2;

8. const DEUX_PI           = Math.PI * 2;

9. const DEB_RAD           = DEMI_PI + Math.PI / 6;

10. const FIN_RAD           = DEUX_PI + DEMI_PI - Math.PI / 6;

11. const LARGEUR_SECTEUR   = 16;

13. var decalage = 20;

14. if (this.encadrement == false) decalage = 2;

16. this.idCanvas         = params.idCanvas;

17. this.dimCanvas        = params.dimCanvas;

18. this.mesure           = params.mesure;

20. this.rayon            = this.dimCanvas / 2;

21. this.centreX          = this.dimCanvas / 2;

22. this.centreY          = this.dimCanvas / 2;

24. this.clFondCadran     = params.clFondCadran;

26. this.encadrement      = params.encadrement;

27. this.clBordure        = params.clBordure;

28. this.clBiseauExt      = params.clBiseauExt;

29. this.clBiseauInt      = params.clBiseauInt;

31. this.bandeau          = params.bandeau;

32. this.valeurMini       = params.valeurMini;

33. this.valeurMaxi       = params.valeurMaxi;

34. this.secteurs         = params.secteurs;

35. this.clGraduations    = params.clGraduations;

36. this.nbPetiteGrad     = params.nbPetiteGrad;

37. this.nbMoyenneGrad    = params.nbMoyenneGrad;

38. this.nbGrandeGrad     = params.nbGrandeGrad;

40. this.policeLegendes   = params.policeLegendes;

41. this.ajustLegendX     = params.ajustLegendX;

42. this.ajustLegendY     = params.ajustLegendY;

44. this.titre            = params.titre;

45. this.policeTitre      = params.policeTitre;

46. this.posTitre         = params.posTitre;

47. this.clTitre          = params.clTitre;

49. this.sousTitre        = params.sousTitre;

50. this.policeSousTitre  = params.policeSousTitre;

51. this.posSousTitre     = params.posSousTitre;

52. this.clSousTitre      = params.clSousTitre;

54. this.largeurAiguille  = params.largeurAiguille;

55. this.clAiguille       = params.clAiguille;

56. this.largeurCentre    = params.largeurCentre;

57. this.clCentre         = params.clCentre;

64. this.dessineFondCadran = function(context)

65. {

66. context.arc(this.centreX ,this.centreY ,this.rayon, 0, DEUX_PI);

67. context.fillStyle = this.clFondCadran;

68. context.fill();

69. };

72. this.dessineEncadrement = function(context)

73. {

74. context.lineWidth = 2;

75. context.beginPath();

76. context.arc(this.centreX, this.centreY, this.rayon - 1, 0, DEUX_PI);

77. context.strokeStyle = this.clBordure;

78. context.stroke();

80. context.lineWidth = 8;

81. context.beginPath();

82. context.arc(this.centreX ,this.centreY ,this.rayon - 6 , 0, DEUX_PI);

83. context.strokeStyle = this.clBiseauExt;

84. context.stroke();

86. context.beginPath();

87. context.arc(this.centreX ,this.centreY ,this.rayon - 14 , 0, DEUX_PI);

88. context.strokeStyle = this.clBiseauInt;

89. context.stroke();

90. };

93. this.dessineSecteurs = function(context)

94. {

95. var nbDiv = this.valeurMaxi - this.valeurMini;

96. var divRad = (FIN_RAD - DEB_RAD) / nbDiv;

97. for (var i = 0; i < this.secteurs.length; i++)

98. {

99. context.lineWidth = LARGEUR_SECTEUR;

100. context.beginPath();

101. context.arc(this.centreX, this.centreY, this.rayon - decalage - LARGEUR_SECTEUR / 2 , DEB_RAD + (this.secteurs[i][0] - this.valeurMini) * divRad, DEB_RAD + (this.secteurs[i][1] - this.valeurMini) * divRad);

102. context.strokeStyle = this.secteurs[i][2];

103. context.stroke();

104. }

105. };

108. this.graduations = function(context, epGrad, nbGrad, lgGrad)

109. {

110. context.lineWidth = epGrad;

111. for (var i = 0; i <= nbGrad; ++i)

112. {

113. context.beginPath();

114. context.strokeStyle = this.clGraduations;

115. var angle = (((FIN_RAD - DEB_RAD) / nbGrad) * i) + DEB_RAD;

116. context.arc(this.centreX, this.centreY, this.rayon - decalage, angle, angle);

117. context.arc(this.centreX, this.centreY, this.rayon - decalage - lgGrad, angle, angle);

118. context.closePath();

119. context.stroke();

120. }

121. };

124. this.dessineGraduations = function (context)

125. {

126. this.graduations(context, 1, this.nbPetiteGrad, 7);

127. this.graduations(context, 3, this.nbMoyenneGrad, 10);

128. this.graduations(context, 3, this.nbGrandeGrad, 15);

129. };

132. this.dessineLegendes = function (context)

133. {

134. context.font = this.policeLegendes;

135. context.fillStyle = this.clGraduations; // Même couleur que pour les graduations

136. for (var i = 0; i <= this.nbGrandeGrad; ++i)

137. {

138. var angle = (((FIN_RAD - DEB_RAD) / this.nbGrandeGrad) * i) + DEB_RAD;

139. var y = this.centreY + Math.sin(angle) * (this.rayon + this.ajustLegendY - decalage - LARGEUR_SECTEUR - 15);

140. var x = this.centreX + Math.cos(angle) * (this.rayon + this.ajustLegendX - decalage - LARGEUR_SECTEUR - 15);

142. context.textAlign = "center";

143. var val = 10 * i + this.valeurMini;

144. context.fillText(val.toString(), x, y + 5);

145. }

146. };

149. this.dessineTitre = function(context)

150. {

151. context.font = this.policeTitre;

152. context.fillStyle = this.clTitre;

153. context.textBaseline = "middle";

154. context.fillText(this.titre, this.centreX, this.posTitre);

155. };

158. this.dessineSousTitre = function(context)

159. {

160. context.font = this.policeSousTitre;

161. context.fillStyle = this.clSousTitre;

162. context.textBaseline = "middle";

163. context.fillText(this.sousTitre, this.centreX, this.posSousTitre);

164. };

167. this.dessineAiguille = function (context)

168. {

169. var valeur = this.mesure - this.valeurMini;

170. context.translate(this.centreX, this.centreY);

171. context.rotate((((FIN_RAD - DEB_RAD) / (this.valeurMaxi - this.valeurMini)) * valeur) + DEB_RAD);

172. context.beginPath();

173. context.arc(0,  0,  this.largeurAiguille, DEMI_PI,  DEMI_PI * 3);

174. context.lineTo(this.rayon - decalage - LARGEUR_SECTEUR / 2, 0);

175. context.closePath();

176. context.fillStyle = this.clAiguille;

177. context.fill();

178. context.beginPath();

179. context.arc(0,  0,  this.largeurCentre, 0,  DEUX_PI);

180. context.fillStyle = this.clCentre;

181. context.fill();

182. };

185. this.dessineCadran = function()

186. {

187. canvas = document.getElementById(this.idCanvas);

188. canvas.width = this.dimCanvas;

189. canvas.height = this.dimCanvas;

191. ctxA = canvas.getContext("2d");

192. ctxB = canvas.getContext("2d");

194. this.dessineFondCadran(ctxA);

195. if (this.encadrement)

196. {

197. this.dessineEncadrement(ctxA);

198. }

199. if (this.bandeau)

200. {

201. this.dessineSecteurs(ctxA);

202. }

203. this.dessineGraduations(ctxA);

204. this.dessineLegendes(ctxA);

205. this.dessineTitre(ctxA);

206. this.dessineSousTitre(ctxA);

207. this.dessineAiguille(ctxB);

208. };

209. };

- créez le fichier ajax.js et copiez dedans le code suivant :

ajax.js

1. const typeReq = "ajax?";

2. const reqMaj  = "maj?";

3. var requete   = "";

5. function maj()

6. {

7. requete = typeReq + reqMaj;

8. setInterval(requeteAjax, 1000);

9. }

11. function requeteAjax()

12. {

13. var xhr = new XMLHttpRequest();

14. xhr.open("GET", requete , true);

15. xhr.send(null);

16. xhr.onreadystatechange = function()

17. {

18. if (xhr.readyState == 4)

19. {

20. actualisation(xhr.responseText);

21. }

22. }

23. }

25. function actualisation(reponse)

26. {

27. var retour = JSON.parse(reponse);

29. cadranThermo.mesure = retour.temperature;

30. cadranThermo.dessineCadran();

32. cadranHygro.mesure = retour.hygrometrie;

33. cadranHygro.dessineCadran();

35. requete = typeReq + reqMaj;

36. }

- créez le fichier thermo.js et copiez dedans le code suivant :

thermo.js

1. cadranThermo = new TCadran(

2. {

3. idCanvas        : "thermo",

4. mesure          : 22.5,

5. dimCanvas       : 300,

7. clFondCadran    : "#404040",

9. encadrement     : true,

10. clBordure       : "#808080",

11. clBiseauExt     : "#EEEEEE",

12. clBiseauInt     : "#BBBBBB",

14. bandeau         : false,

15. valeurMini      : -10,

16. valeurMaxi      : 40,

17. secteurs        : [[-10, 15, "#0000FF"],[15, 30, "#00DD00"],[30, 40, "#FF5000"]],

18. clGraduations   : "#FFFFFF",

19. nbPetiteGrad    : 50,

20. nbMoyenneGrad   : 10,

21. nbGrandeGrad    : 5,

23. policeLegendes  : "17px Arial",

24. ajustLegendX    : 0,

25. ajustLegendY    : 0,

27. titre           : "Thermomètre",

28. policeTitre     : "italic 20px Arial",

29. posTitre        : 100,

30. clTitre         : "#FFFFFF",

32. sousTitre       : "°C",

33. policeSousTitre : "bold 20px Arial",

34. posSousTitre    : 200,

35. clSousTitre     : "#FFFFFF",

37. largeurAiguille : 15,

38. clAiguille      : "#DDDDFF",

39. largeurCentre   : 25,

40. clCentre        : "#BB0000"

41. });

- créez le fichier hygro.js et copiez dedans le code suivant :

hygro.js

1. cadranHygro = new TCadran(

2. {

3. idCanvas        : "hygro",

4. mesure          : 60,

5. dimCanvas       : 300,

7. clFondCadran    : "#404040",

9. encadrement     : true,

10. clBordure       : "#808080",

11. clBiseauExt     : "#EEEEEE",

12. clBiseauInt     : "#BBBBBB",

14. bandeau         : true,

15. valeurMini      : 0,

16. valeurMaxi      : 100,

17. secteurs        : [[0, 30, "#FFFF00"],[30, 70, "#00DD00"],[70, 100, "#0000FF"]],

18. clGraduations   : "#FFFFFF",

19. nbPetiteGrad    : 100,

20. nbMoyenneGrad   : 20,

21. nbGrandeGrad    : 10,

23. policeLegendes  : "17px Arial",

24. ajustLegendX    : 0,

25. ajustLegendY    : 0,

27. titre           : "Hygromètre",

28. policeTitre     : "italic 20px Arial",

29. posTitre        : 100,

30. clTitre         : "#FFFFFF",

32. sousTitre       : "%",

33. policeSousTitre : "bold 20px Arial",

34. posSousTitre    : 200,

35. clSousTitre     : "#FFFFFF",

37. largeurAiguille : 15,

38. clAiguille      : "#DDDDFF",

39. largeurCentre   : 25,

40. clCentre        : "#BB0000"

41. });

Cette application est fournie à titre d’exemple pour illustrer ce que l’on a vu dans cet article. Je ne vais donc pas la détailler, car ce n’est pas le sujet de ce tutoriel. Vous trouverez l’explication des mécanismes liés au fonctionnement du serveur et du client dans les tutoriels :

- Le navigateur aux commandes de l’arduino – Partie 1 ;

- Le navigateur aux commandes de l’arduino – Partie 2 ;

- Le navigateur aux commandes de l’arduino – Partie 3.

VII-B - Téléversements

Vous avez deux téléversements à effectuer : le sketch et les fichiers contenus dans le répertoire /data. Vous pouvez bien entendu effectuer ces deux téléversements dans l’ordre qui vous convient, mais comme vous ne disposez pas encore de la fonctionnalité OTA, il est préférable de téléverser d’abord le sketch, en utilisant le port série, pour mettre en même temps cette fonctionnalité en place. Comme cela, dans un deuxième temps, vous pourrez téléverser le système de fichiers en utilisant l’OTA.

La marche à suivre est simple :

- vous téléversez le sketch meteo.ino en suivant la procédure décrite au chapitre IV. Programmation de l’ESP-01 avec l'adaptateur ;

- vous téléversez le système de fichiers selon la procédure expliquée au chapitre VI.B.2 Téléversement des fichiers en optant bien entendu pour le téléversement OTA. Remarquez que la majeure partie de cette procédure vient d’être réalisée dans la section VII.A Préparation du présent chapitre.

C’est tout. Toutes vos prochaines mises à jour, que ce soit pour le sketch ou pour le système de fichiers se feront via l’OTA.

VII-C - Connexion

Ouvrez votre navigateur préféré et connectez-vous à l’adresse que vous a attribuée votre routeur DHCP et que vous avez utilisée pour le téléversement OTA. Vous devriez obtenir quelque chose qui ressemble à ceci :

Chacun de ces deux cadrans représente une instance de l’objet TCadran (Type Cadran) implémenté dans le fichier JavaScript tCadran.js. La meilleure place pour l’appel de ce fichier est l’entête du fichier HTML, mais ce n’est pas obligatoire. Il suffit qu’il soit appelé avant la (ou les) demande(s) de création d’une instance de cet objet.

Pour cette réalisation, le code de création de ces deux instances se trouve respectivement dans les fichiers thermo.js et hygro.js. L’appel de ces deux fichiers se fait dans la section <body></body> du fichier HTML, à l’emplacement où l’on souhaite dessiner chaque cadran. La personnalisation des cadrans se fait dans ces fichiers en modifiant les paramètres. N’hésitez pas à le faire.

Pour tester les modifications, il faut pouvoir afficher facilement les cadrans. Il est hors de question de téléverser les fichiers à chaque fois : cela deviendrait rapidement pénible.

Dans votre dossier /data, modifiez la dernière ligne de ces deux fichiers comme suit :

});

devient :

}).dessineCadran();

En faisant cela, vous lancerez la procédure dessineCadran() de l’objet instancié en début de fichier, dont la fonction est de dessiner le cadran en suivant le paramétrage contenu dans le fichier, à chaque fois que ce fichier sera invoqué, c’est-à-dire à chaque fois que vous ouvrirez (ou actualiserez) le fichier HTML dans votre navigateur. Bien entendu, dans cette configuration, il n’y aura pas de mise à jour de l’affichage, et la valeur indiquée correspondra à la valeur saisie à la ligne :

1. mesure          : 22.5,

valeur que vous pouvez également modifier pour vos essais.

Une fois le résultat désiré obtenu, restaurez la dernière ligne à sa valeur originale et téléversez votre système de fichiers.

VIII - Conclusion

Merci d’avoir prêté intérêt à ce tutoriel. J’espère qu’il aura pu vous être utile. Vous trouverez le dossier /meteo et tout ce qu’il contient prêts à l’emploi dans cette archive.

Je n’ai, à l’évidence, pas traité toutes les fonctionnalités offertes par la bibliothèque ArduinoOTA, notamment celles liées à la sécurité. Il en est de même pour la bibliothèque LittleFS, et ses nombreuses méthodes de manipulation des fichiers.

Pour aller plus loin, je vous engage à consulter les documentations suivantes :

- LittleFS ;

- OTA.

IX - Remerciements

Mes remerciements vont à f-leb pour ses observations et conseils techniques, ainsi qu’à Claude Leloup pour sa relecture orthotypographique.