L'objectif est de gérer la situation quand le ESP8266 est pris dans une boucle de redémarrage. Voici comment cela se présente. Un nouveau micrologiciel est téléversé et le ESP8266 est mis en fonction. Après un certain temps, un bogue se manifeste. Une exception est soulevée et le ESP8266 redémarre. La source de l'exception est toujours présente et elle est de nouveau soulevée, d'où un nouveau redémarrage. Le cycle d'exceptions et démarrages se répète sans fin jusqu'à ce que le ESP8266 soit retiré de sa position pour téléverser une version corrigée du micrologiciel avec une connexion série. Malheureusement, cela est souvent difficile, le dispositif étant dans un endroit peu accessible.
On peut voir la cause du démarrage ou redémarrage d'un ESP8266 dans le moniteur série de l'EDI Arduino tant que le ESP8266 est branché par une connexion série. Il est bien plus utile de récupérer cette information dans le croquis Arduino pour gérer les problèmes récurrents associés à des exceptions de logiciels ou des morsures de chiens de garde examinés dans le billet précédent. Avec cette information on peut lancer un téléversement sans fil du micrologiciel pour revenir à une version qui fonctionnait. Voilà ce qui est proposé ci-dessous. Au préalable, le comportement du ESP8266 au démarrage est examiné.
Table des matières
- Démarrage du ESP8266
- Récupération de la raison du démarrage du ESP8266
- Quelques questions
- Gérer le bogue du mode UART
- Gérer les cycles de démarrage
- Consigner les erreurs au système de journalisation
- Mise en garde importante
- Téléchargements
Démarrage du ESP8266
Selon un document de Espressif ESP8266 Reset Causes and Common Fatal Exception Causes, « À chaque redémarrage de l'ESP8266, le code ROM affichera un nombre correspondant à la cause de la réinitialisation ». Il imprime également quelque chose appelé le mode de démarrage (boot mode) qui indique d'où le code a été pris. Cela est visible sur les captures d'écran de la fenêtre du moniteur série de l'EDI Arduino.
ets Jan 8 2013, rst cause:2, boot mode: (1,6)
ets Jan 8 2013, rst cause:4, boot mode: (3,6)
Le 2
à la droite de rst cause
, dénote que
le chien de garde logiciel a réinitialisé le dispositif alors que le
4
dans le second exemple dénote que le chien de garde matériel
est la cause de la réinitialisation. Quand le premier chiffre du mode de
démarrage est égal à 1 cela veut dire que le code actuellement exécuté vient
d'être téléchargé sur l'ESP8266. Un 3, comme dans le second exemple, indique
que le code provient de la mémoire flash SPI.
L'explication du mode de démarrage provient d'un document de Max Filippov
intitulé Boot Process. Selon ce texte, le nombre affiché à droite
de rst cause
est la valeur de trois broches du circuit intégré
qui contrôlent le comportement de celui-ci à son démarrage. On sait que l'on
doit fixer correctement les valeurs logiques des broches GPIO0, GPIO2 et
GPIO15 au démarrage du ESP8266 pour le mettre dans le mode désiré. Bien qu'il
y ait huit combinaisons possibles, seulement trois sont définies dans le
document et seulement deux sont utilisées en pratique.
GPIO15 | GPIO0 | GPIO2 | Dec. | Mode | Description |
---|---|---|---|---|---|
0 | 0 | 1 | 1 | UART | Télécharger le code du UART (programmation) |
0 | 1 | 1 | 3 | Flash | Code de la mémoire SPI Flash (usage normal) |
1 | x | x | 4-7 | SDIO | pas utilisé (voir encadré ci-dessous) |
En situation normale, on désire que le ESP8266 exécute le code déjà contenu dans la mémoire flash lorsqu'il est mis sous tension. Pour cela il faut que la broche GPIO0 soit tirée vers Vcc (3,3 volts). En revanche, quand on veut téléverser un nouveau micrologiciel vers le ESP8266, la broche GPIO0 doit être mise à terre quand la puce est placée sous tension. Une fois le téléversement complété, le code téléversé sera immédiatement exécuté. La broches GPIO15 doit toujours être mise à terre et GPIO2 doit être tirée vers Vcc. Les petits systèmes de développement WeMos D1 mini, pro etc. et NodeMCU gèrent les valeurs de ces trois broches automatiquement et une intervention physique n'est pas nécessaire pour téléverser un micrologiciel vers leur ESP8266. Ce n'est pas le cas pour le commutateur sans fil Sonoff; on doit appuyer sur son bouton-poussoir en appliquant la tension pour téléverser un nouveau micrologiciel. On s'en doute, ce bouton-poussoir raccorde GPIO0 à la terre lorsque le contact est fermé.
Il est utile pour le programmeur de voir cette information dans le moniteur série de l'EDI Arduino lorsqu'il élabore une nouvelle version de micrologiciel du ESP8266. Cependant, l'information doit être récupérée par programme si le croquis doit l'utiliser.
Récupération de la raison du démarrage du ESP8266
La classe EspClass
contient une méthode qui retourne la
cause de la réinitialisation du ESP8266 en tant que chaîne:
getResetReason()
. Il y a une autre fonction, nommée
getResetInfoPtr()
, qui renvoie un pointeur vers une structure
avec des informations au sujet de la réinitialisation du système, y compris
la raison de celle-ci. Cette dernière est plus utile pour ce qui suit.
J'ai écrit un croquis Arduino pour redémarrer le ESP8266 de différentes façons et après afficher l'information recueillie sur la cause du redémarrage. Voici le premier écran affiché dans le moniteur série de l'EDI Arduino après le téléversement du croquis.
On note l'avertissement qu'il faut appuyer sur le bouton de réinitialisation du dispositif après avoir téléversé ce nouveau micrologiciel vers le ESP8266. Si on ne le fait pas, le ESP ne redémarra pas. S'il n'y a pas de bouton de réinitialisation, on peut simplement couper puis rétablir l'alimentation.
Si l'on repart le ESP8266 avec la fonction restart()
ou reset()
, la valeur dans ESP.getResetInfoPtr()->reason
est REASON_SOFT_RESTART(=4)
:
Si l'on provoque la morsure des chiens de garde materiel puis
logiciel du ESP8266, la raison du redémarrage sont REASON_WDT_RST(=1)
et REASON_SOFT_WDT_RST(=3)
respectivement.
Si le dispositif est réinitialisé en activant la broche RESET
,
en appuyant sur le bouton-poussoir nommé RESET
de système
de développement WeMos D1 mini d'un NodeMCU par exemple, alors la raison est
REASON_EXT_SYS_RST(=6)
.
Si une exception est soulevée pendant l'exécution du micrologiciel, le
ESP8266 est reparti et la raison donnée est REASON_EXCEPTION_RST(=2)
.
On peut également récupérer l'identité de l'exception dans la structure
d'information de redémarrage: getResetInfoPtr()->exccause
.
REASON_DEEP_SLEEP_AWAKE(=5)
. Il y a aussi
REASON_DEFAULT_RST(=0)
qui correspond à la mise sous tension du
ESP8266.
Quelques questions
La démarche précédente soulève des questions, dont les suivantes.
- Pourquoi faut-il appuyer sur le bouton
RESET
après avoir téléversé le micrologiciel ? -
Ivan Grokhotkov donne une description détaillée de ce bogue. En gros, quand le
ESP8266 est en mode UART il reste dans ce mode même après avoir été
redémarré avec
ESP.restart()
ou une exception etc. Il ne vérifie pas l'état des 3 broches GPIO15, GPIO2 et GPIO0. Donc, au redémarrage, il ne fait que vérifier l'entrée série en attente d'un téléversement d'un nouveau micrologiciel. C'est ce qui nous porte à croire que le dispositif est en panne. - Y a-t-il une façon de redémarrer le ESP8266 pour contourner ce bogue ?
- Non. Seule une réinitialisation manuelle de ESP8266 arrive à lui faire quitter le mode UART après un téléversement. Une approche un peu radicale à ce problème fait l'objet de la section suivante.
- Pourquoi la raison POWER_ON n'est pas affichée par le WeMos D1 mini ou NodeMCU alors qu'on l'observe quand on met un Sonoff en marche ?
- Il
y a une discussion sur ce sujet sur le Wiki de ESP8266-Arduino.
La conclusion qui semble s'en dégager est que cette différence est causée par
la conception des circuits entourant le ESP8266. J'ai comparé les
schématiques du WeMos D1 et du Sonoff en examinant de près les connexions à la broche 32
(RST) de la puce. Chez le Sonoff, la broche est directement reliée à Vcc
(3,3V) en passant par une résistance de 10K ohms probablement pour limiter le
courant. Donc dès que l'appareil est mis sous tension, RST a une valeur
logique de 1 (HIGH) et est inactif. Pour ce qui est du D1 mini, la broche est connectée à
Vcc à travers une résistance de 12K ohms et aussi à la terre à travers un
condensateur de 100 nF. Il s'agit d'un circuit R-C classique dont une
utilisation courante est de ralentir l'augmentation de la tension. Il faut
un certain temps pour que le condensateur n'atteigne sa pleine charge. Selon
une calculatrice il faudra 1,2 ms avant que la tension entre les
bornes du condensateur atteigne 2,08V. C'est comme si l'on appuyait sur le
bouton-poussoir
RESET
quand on met sous tension ce dispositif pour le relâcher plus tard. Ce qui expliquerait pourquoi la raison donnée pour le démarrage estREASON_EXT_SYS_RST(=6)
.Logiquement, on devrait observer deux démarrages quand le WeMos D1 mini est mis sous tension:
REASON_DEFAULT_RST
suivi très rapidement deREASON_EXT_SYS_RST
. Il me semble qu'il serait possible de tester cela, mais je suis trop paresseux pour essayer et je préfère me contenter de mon explication ci-dessus, qui pourrait être complètement fausse.En passant, on peut définir la directive
MULTI_ANNOUNCEMENT
dans le croquis pour faire afficher la cause initiale du démarrage du ESP8266. Malheureusement, ce petit truc ne permet pas de vérifier s'il y a deux démarrages en succession rapide. - Quelle est la différence entre
ESP.restart()
etESP.reset()
? -
Elle n'est pas claire en ce qui me concerne. Selon
Links2004,
ESP.reset()
correspond à activer la broche RST du ESP8266 alors queESP.restart()
effectuerait un redémarrage plus propre. J'utilise toujours cette dernière instruction depuis que j'ai lu ce commentaire au tout début de mes recherches sur la puce. Je n'ai donc aucune expérience avecESP.reset()
ce qui m'enlève toute possibilité d'en faire la comparaison.
Gérer le bogue du mode UART
Le bogue du mode UART, si je peux utiliser ce terme, a été mentionné à quelques reprises ici et dans le billet précédent. Il importe de souligner que ce bogue est pratiquement sans importance. Il ne m'est jamais arrivé de téléverser un nouveau micrologiciel par connexion série vers un ESP8266 et d'avoir utilisé ce dernier sans le débrancher.
En fait, ce bogue est nuisible aux néophytes qui en sont à leurs premiers essaies et qui sont facilement découragés quand ils ont l'impression que le micrologiciel fraîchement téléversé ne fonctionne plus pour une raison insaisissable alors qu'il fonctionnait il y avait à peine quelque secondes.
Ceci étant dit, il m'arrivait de perdre du temps à cause de lui parce que j'avais oublié que je venais de téléverser un croquis. Cela arrive le plus souvent dans le feu de l'action après une longue séquence de téléversements pour essayer de comprendre ou corriger une erreur qui n'est nullement liée au bogue. Depuis assez longtemps, j'ai opté pour une approche un peu radicale. Mes croquis sont toujours en panne après un téléversement du micrologiciel. Donc impossible de tester ce dernier avant de repartir manuellement le ESP8266.
Pour autant que je sache, il n'y a pas de méthode dans la classe
EspClass
pour récupérer le mode d'opération. Mais un
contributeur au forum de la
communauté ESP8266, dont le sobriquet est Off, a élaboré une routine
en assembleur pour le faire. Voici comment je l'utilise au début de
la fonction setup()
de mes croquis Arduino pour ESP8266.
Gérer les cycles de démarrage
Il y a trois causes de cycles de démarrage du ESP8266.
Valeur | Raison | Source |
1 | REASON_WDT_RST | Chien de garde matériel |
3 | REASON_SOFT_WDT_RST | Chien de garde logiciel |
2 | REASON_EXCEPTION_RST | Exception |
Si le ESP8266 est démarré pour l'une de ces trois raisons, on peut tenter
un téléversement sans fil (OTA - over the air) du micrologiciel pour en
installer une version correcte. Heureusement, tout le travail pour mettre en
oeuvre cette opération est déjà codé dans la bibliothèque
ESP8266httpUpdate
. Celle-ci est automatiquement installée dans
l'EDI Arduino avec les autres bibliothèque du ESP8266. J'ai modifié quelque
peu le croquis le plus simple des deux exemples fournis avec la bibliothèque.
Voir le menu Fichier/Exemples/ESP8266httpUpdate
de l'EDI
Arduino.
La seule complication qu'on retrouve dans cet exemple découle du fait que
le réseau Wi-Fi où se trouve le serveur HTTP pour la mise à jour sans fil
pourrait être que celui utilisé par le ESP8266. Normalement, le ESP se
rebranche automatiquement au dernier réseau Wi-Fi utilisé. Si l'on veut que
la connexion au réseau de travail se fasse après une mise à jour du
micrologiciel sans fil, il faut rétablir la connexion originale avant de
repartir le ESP. Voilà pourquoi on retrouve l'instruction
ESPhttpUpdate.rebootOnUpdate(false);
au début de la fonction
performOtaUpdate()
. Sans cela, cette dernière ne retourne pas la
valeur true
quand la mise à jour fonctionne parce qu'elle
redémarre l'ESP avec la fonction ESP.restart()
.
Consigner les erreurs au système de journalisation
On voudrait des objets qui soient infaillibles. Puisqu'il s'agit d'un objectif inatteignable, on se contente d'objets qui peuvent récupérer d'une erreur imprévue. Or si le mécanisme de récupération décrit ci-dessus fonctionne, comment s'en rendre compte ? Plusieurs messages seront affichés dans le moniteur série de l'EDI Arduino s'il faut retourner à une version antérieure du micrologiciel du ESP8266. En pratique, le dispositif ne sera pas branché à un ordinateur de bureau et ces messages ne seront pas vus.
Une façon de procéder est de transmettre les messages pertinents vers un
système de journalisation. J'ai déjà un tel système centralisé sur un Raspberry Pi avec
lequel communiquent les dispositifs ayant Tasmota comme micrologiciel et
quelques script Python pour lancer une alerte de température excessive. Dans
la même veine, j'ai modifié le micrologiciel présenté à la section précédente
pour que les messages qui étaient destinés à la sortie série soient envoyés
au système de journalisation. J'ai opté pour la bibliothèque Syslog de Martin
Sloup parce qu'elle ressemble au module syslog
pour Python que j'utilise déjà. Voir l'extrait de code de chaeplin
si l'on ne veut pas
quelque chose d'aussi élaboré. D'ailleurs, l'approche de Theo Arends dans
Tasmota est semblable à celle de ce dernier.
Les Serial.print()
du code original ont été remplacés par
des appels à la fonction log()
définie ci-dessous.
Un message (variable msg
) est acheminé vers la fonction
syslog.log()
qui le transmettra au serveur de journalisation si
sa priorité (level
) est supérieure ou égale au niveau fixé selon la
variable logThreshold
. Typiquement ce seuil de transmission des
messages vers le serveur de journalisation est fixé avec la fonction
setLogThreshold()
dans la partie d'initialisation
(setup()
) du croquis, mais il peut être modifié en tout temps.
La transmission du message vers le moniteur série dépend de la valeur de la
variable verbose
.
Valeur | Description |
---|---|
VERBOSE_NONE |
Le message n'est jamais transmis |
VERBOSE_ECHO |
Le message est transmis si sa priorité (level ) est
supérieure ou égale au niveau fixé selon la variable logThrehold .
En d'autres mots, un message est acheminé vers la sortie sérielle seulement
s'il est aussi acheminé vers le système de journalisation. |
VERBOSE_ALL |
Le message est toujours transmis qu'importe la valeur de
level . |
On peut voir les messages transmis sur la sortie UART du ESP8266 si le dispositif est connecté à un ordinateur avec un câble USB-UART. On peut voir les messages envoyés au système de journalisation avec la commande suivante
Attention, il n'y a aucune indication si le système de journalisation ne peut pas être atteint. C'est d'ailleurs le propre du protocole de transport UDP emprunté pour la journalisation.
Mise en garde importante
Je ne propose pas que l'on adopte la stratégie présentée ci-dessus. C'est une ébauche présentée à des fins « pédagogiques ». Il y a deux complications qu'il faut souligner.
Premièrement, la mise à jour sans fil automatique, qui dans les faits est un retour vers une version antérieure du micrologiciel, se fait trop promptement. Une bonne proportion des problèmes rencontrés avec notre système de domotique est liée à des coupures de tension ou peut-être des surtensions transitoires quand le courant est rétabli. Il n'y a pas de faute de programmation; la plupart du temps, le dispositif fonctionne correctement après un simple redémarrage.
Deuxièmement, le mécanisme proposé ci-dessus ne fonctionne pas avec
le chien de garde loop décrit dans le billet précédent. On se
souviendra que la morsure de celui-ci se termine par un redémarrage du ESP
avec l'instruction ESP.restart()
. Si l'on ajoutait
REASON_SOFT_RESTART
comme raison pour faire une mise à jour
sans fil, il s'en suivrait un cycle sans fin de mise à jour automatique puisque
cette opération se termine elle aussi avec un ESP.restart()
.
Il y a des façons de contourner en partie ces problèmes. On pourrait
mettre en place le fichier binaire contenant la version « sure » du
micrologiciel sur le serveur HTTP qu'à des moments stratégiques. Il est
probable que les problèmes récurrents se manifesteront après l'installation
d'un nouveau micrologiciel par exemple. La morsure du chien de garde
loop pourrait se terminer avec while (1) {}
qui
déclencherait une morsure du chien de garde logiciel.
Ces solutions de contournement ne me satisfont pas. Je préfère compter le nombre de fois qu'une même raison est la cause de redémarrages consécutifs du ESP et procéder à un téléchargement d'une version « sure » du micrologiciel seulement quand ce compte atteint une valeur critique. Ceci exige que l'on conserve en mémoire la cause du précédent redémarrage. Du coup, il devient possible de distinguer la morsure du chien de garde loop des autres raisons de redémarrage.
Voilà ce qui sera abordé dans le prochain billet. Tout cela est déjà
disponible depuis quelques mois, mais je vais réécrire le code pour qu'il
soit plus clair que le mécanisme que j'avais proposé est indépendant du
troisième chien de garde. Il sera tout aussi utile pour contourner le premier
problème soulevé ci-dessus dans un croquis qui n'utilise pas ce chien de
garde. simple de s'en servir.
Téléchargements
- esp_boot_reason.zip
- Contient le croquis
esp_boot_reason.ino
qui signale la cause d'un redémarrage du ESP et qui permet à l'utilisateur de sélectionner la raison du démarrage suivante. - esp_restart_ota.zip
- Contient le croquis
esp_restart_ota.ino
montrant comment récupérer automatiquement quand le ESP s'engage dans une boucle de redémarrages. - esp_restart_log_ota.zip
- Contient le croquis
esp_restart_log_ota.ino
qui montre comment récupérer automatiquement quand le ESP s'engage dans une boucle de redémarrages et consigne les erreurs et les résultats de la démarche de mise à jour dans le jsystème de ournalisation.
Chacun de ces croquis est accompagné de deux fichiers d'en-tête contenant
toutes les chaînes du programme en anglais et en français. Un seul de ces
fichiers doit être inclue dans le croquis. On est loin du mécanisme GNU
gettext
.