Un troisième temporisateur de surveillance du ESP8266, version finale
2018-06-24
Mise à jour: 2018-06-27
Récupérer automatiquement des redémarrages en boucle du ESP8266

Le billet précédent présentait un mécanisme de récupération des démarrages en catastrophe du ESP8266 trop prompt à tenter un téléversement sans fil d'un nouveau micrologiciel. Il se peut que le ESP redémarre à cause d'une exception elle même causé par un problème passager du réseau électrique. Il est inutile de recharger le micrologiciel du ESP, il n'est pas fautif.

Ci-dessous une méthode pour garder la trace de la raison des démarrages du ESP8266 est présentée. En gros, il s'agit de conserver la raison du dernier démarrage ainsi que le nombre de démarrages consécutifs pour cette raison dans un enregistrement dans la mémoire de l'horloge en temps réel (Real Time Clock ou RTC en anglais).

Table des matières

  1. Mémoire RTC
  2. Alignement des variables
  3. Compter le nombre de démarrages successifs pour une même raison
  4. Compteur de démarrages et chien de garde loop
  5. Mise à jour du 27 juin 2018
  6. Conclusion
  7. Téléchargements

  1. Mémoire RTC
  2. Les informations au sujet des redémarrages sont enregistrées dans la mémoire non volatile de l'horloge temps réel de l'ESP8266. La capacité de cette mémoire est relativement limitée; elle ne compte que 768 octets dont les 256 premiers octets sont réservés pour le système. Il ne reste que 512 octets accessibles à l'utilisateur. Or cette ressource est précieuse, car son contenu n'est pas effacé quand le ESP8266 est placé en mode de veille profonde alors que les données stockées dans la mémoire vive sont perdues.

    Il n'y a pas d'accès direct à la mémoire RTC. Celle-ci est divisée en 192 tranches de 4 octets. Pour lire le contenu du 6e octet, il faut lire la deuxième tranche de 4 octets puis récupérer les 8 bits désirés dans ces 32 bits.

    Les 64 premières tranches sont réservées pour le système. Donc l'utilisateur est libre d'utiliser les tranches dont l'adresse est entre 64 et 191. Il est plus pratique de les numéroter de 0 à 127. C'est ce que font les fonctions rtcUserMemoryRead() et rtcUserMemoryWrite() de la classe EspClass. De façon semblable, la fonction

    bool setRestartRtcAddress(uint8_t addr = DEFAULT_ADDRESS);

    déclarée dans mdEspRestart.h est utilisée pour spécifier le numéro de tranche de 0 à 127 où l'information au sujet du dernier démarrage sera stockée. Si l'on spécifie la valeur par défaut DEFAULT_ADDRESS ou si la fonction setRestartRtcAddress n'est pas invoquée, l'information est conservée à la toute fin de la mémoire RTC.

    L'information est dans une structure nommée restart. Le préprocesseur « calcule » l'adresse par défaut qui est affectée à la variable globale rtcAddress. RESTART_BUCKET_SIZE est le plus petit multiple de 4 plus grand ou égal à la taille de restart. Si l'on préfère, RESTART_BUCKET_SIZE/4 est le nombre minimum de tranches pouvant contenir la structure restart.

    #define RESTART_BLOCK_SIZE ((sizeof(restart) + 3) & (~3)) #define LAST_VALID_ADDRESS 64 + 128 - (RESTART_BLOCK_SIZE / 4) uint8 rtcAddress = LAST_VALID_ADDRESS;

    Comme on peut voir, la fonction vérifie que l'adresse spécifiée ne dépasse pas la dernière tranche possible étant donnée la taille de restart. Autrement la fonction renvoie la valeur logique faux (false).

    bool setRestartRtcAddress(uint8_t addr) { bool result = true; if (addr == DEFAULT_ADDRESS) { rtcAddress = LAST_VALID_ADDRESS; } else { addr = addr + 64; result = (addr <= LAST_VALID_ADDRESS); if (result) rtcAddress = addr; } return result; }

    Puisque la validation de l'adresse RTC est faite avec setRestartRtcAddress, je n'utiliserai pas les fonctions de lecture et d'écriture de la classe EspClass qui refont cette validation à chaque étape. D'ailleurs, il y a une erreur dans cette étape. Voici l'une des fonctions.

    bool EspClass::rtcUserMemoryRead(uint32_t offset, uint32_t *data, size_t size) { if (size + offset > 512) { return false; } else { return system_rtc_mem_read(64 + offset, data, size); } }

    Le paramètre offset spécifie la tranche dans l'espace utilisateur et elle doit donc avoir une valeur entre 0 et 127. Le pointeur data est l'adresse de la variable devant accueillir les données lues et size est le nombre d'octets à lire. Il est donc possible de lire plus de 4 octets à la fois. Clairement, le test devrait être

      (size + (64 + offset)*4 > 768) ou (size + offset*4 > 512)
    
    L'erreur a été soulignée et devrait être corrigée bientôt.

  3. Alignement des variables
  4. Voulant diminuer le plus possible la taille de l'enregistrement conservé dans la mémoire RTC, j'ai créé une structure de 4 octets.

    typedef uint8_t restartReason_t; typedef uint8_t restartCount_t; typedef uint16_t restartData_t; struct { restartReason_t reason; restartCount_t count; restartData_t data; } restart;

    Un octet est utilisé pour sauvegarder la raison du démarrage (reason) et le compte du nombre de redémarrages consécutifs pour cette même raison est conservé dans deux octets (count). L'octet entre (data) contiendra de l'information supplémentaire dont il sera question plus tard.

    L'ordre des éléments d'un struct peut être important. Initialement l'ordre était différent.

    struct { restartReason_t reason; restartData_t data; restartCount_t count; } restart;

    Cette structure occupe 5 octets.

    adressevariabletaille
    24reason1
    25remplissage1
    26data2
    28count1

    La raison est que le compilateur rajoute un octet de remplissage après reason pour que la variable de deux octets data ait une adresse qui est un multiple de deux ce qui permet un accès plus rapide.

    On peut éliminer cette action du compilateur avec un attribut.

    struct __attribute__((packed)) { restartReason_t reason; restartData_t data; restartCount_t count; } restart;

    Alors la structure occupera 4 octets.

    adressevariabletaille
    24reason1
    25data2
    27count1

    En revanche, l'accès à ses membres sera plus lent. Il est préférable de s'en tenir à la structure proposée ci-dessus.

    Le SDK de Espressif contient deux fonctions pour accéder à la mémoire RTC dont on a déjà vu une en action ci-dessus. Voici leur déclaration.

    bool system_rtc_mem_read(uint8 src_addr, void *des_addr, uint16 load_size); bool system_rtc_mem_write(uint8 des_addr, const void *src_addr, uint16 save_size);

    Il s'avère que l'alignement de la destination (des_addr) et de la source src_addr) est important. Il faut que ces variables soient alignées sur une limite de 32 bits, en d'autres mots, leur adresse doit être un multiple de 4. Sinon, les fonctions ne font rien, sauf retourner la valeur faux (false). La documentation de Espressif est très claire à ce sujet,

    Espressif, (2018/05) ESP8266 Non-OS SDK API Reference, Version 2.2.1, "3.3.23 system_rtc_mem_write" p. 19.
    https://www.espressif.com/sites/default/files/documentation/2c-esp8266_non_os_sdk_api_reference_en.pdf

    Malheureusement, je n'avais pas lu la documentation et c'est presque par hasard que j'ai enfin compris que le problème provenait de l'alignement de restart. Le compte des démarrages consécutifs pouvait être bon puis après quelques ajouts mineurs au croquis, le compte restait fixé à 1 qu'importe le nombre de fois que je redémarrais l'ESP d'une façon donnée. La raison était l'alignement de restart sur une limite de 16 bits, car le plus gros membre de la structure est de 16 bits seulement. Selon la taille des autres variables du croquis, l'adresse de restart, toujours un multiple de 2, pouvait aussi être un multiple de 4 et alors tout fonctionnait ou ne pas l'être et alors les données sauvegardées en mémoire RTC n'étaient copiées dans restart par system_rtc_mem_read. Heureusement, on peut obtenir l'alignement désiré avec l'attribut aligned. La déclaration de la structure restart dans le fichier mdEspStart.cpp garantit qu'elle pourra être lue de la mémoire RTC.

    struct __attribute__((packed)) { restartReason_t reason; restartCount_t count; restartData_t data; } restart __attribute__((aligned(4)));

    Mentionnons un autre piège associé aux fonctions system_rtc_mem_write et system_rtc_mem_read. Les paramètres load_size et save_size spécifient le nombre d'octets à lire ou à écrire sur la mémoire RTC mais en fait, le nombre réel d'octets lus ou écrits est arrondi au plus petit multiple de 4 plus grand ou égal à la taille spécifiée. J'insiste sur ce fait, si restart faisait 5 octets alors system_rtc_mem_read(64, &restart, 5) lirait deux tranches de mémoire RTC, les numéros 64 et 65, et les trois derniers octets des huit lus empiéteraient sur les variables qui suivent restart.

    Les fonctions rtcUserMemoryRead() et rtcUserMemoryWrite() de la classe EspClass sont aussi assujetties à ces deux contraintes évidemment.

  5. Compter le nombre de démarrages successifs pour une même raison
  6. Voici comment tenir compte des causes des redémarrages de l'ESP au début du croquis dans la fonction setup(). On commence en récupérant de la mémoire RTC l'information au sujet du démarrage précédent. Puis on compare la cause de ce dernier avec la cause du démarrage en cours. Si c'est la même, la valeur du compteur de démarrages consécutifs count est augmentée, sinon on remet le compteur à 1. Puis on sauvegarde l'information du démarrage en cours dans la mémoire RTC.

    getRestart(reason, data, count); currentReason = ESP.getResetInfoPtr()->reason; isSameReason = (currentReason == reason); if (isSameReason) { count++; } else { count = 1; } setRestart(currentReason, data, count); switch (currentReason) { case REASON_DEFAULT_RST: ... break; case REASON_WDT_RST: ... break; ...

    Après on examine la cause du démarrage courant pour décider de l'action à prendre. Comme dans le billet précédent, on pourrait remplacer le micrologiciel en place avec une version antérieure, mais en plus on peut faire ce geste un peu drastique seulement si le compteur de démarrages consécutifs a atteint une valeur critique. J'utilise 3 comme valeur critique pour les redémarrages en boucle causés par les chiens de garde et les exceptions.

    Voilà pour les grandes lignes. Comme toujours, il y a des complications. Toutes les exceptions sont traitées comme si elles étaient une même cause de démarrage. C'est peut-être la bonne stratégie, mais j'ai décidé de traiter différentes exceptions comme étant différentes causes de démarrage. Plus précisément, la cause reste une exception, mais le compteur est remis à un si une deuxième exception est différente de l'exception qui aurait causé le démarrage précédent.

    getRestart(reason, data, count); currentReason = ESP.getResetInfoPtr()->reason; isSameReason = (currentReason == reason); if (currentReason == REASON_EXCEPTION_RST) { exceptionCause = ESP.getResetInfoPtr()->exccause; isSameReason = (isSameReason & (data == exceptionCause)); data = exceptionCause; } if (isSameReason) { count++; } else { count = 1; } setRestart(currentReason, data, count);

    Comme on peut voir, cela est fait avec un test un peu plus complexe pour décider si la cause du démarrage actuel est la même que celle du démarrage antérieur. Pour vérifier si une même exception s'est produite deux fois de suite, on sauvegarde l'identité de l'exception dans data qui est un membre de la structure restart, .

    On peut se demander comment le processus commence; d'où vient l'information au sujet d'un démarrage précédent qui ne peut pas exister lors du tout premier démarrage ? J'ai décidé d'inclure un identificateur dans l'enregistrement sauvegarder dans la mémoire RTC. Si cet identificateur n'est pas présent dans les données provenant de la mémoire RTC, les valeurs de la structure restart sont remises à zéro en quelque sorte.

    Puisque restart est déjà de la taille d'une tranche complète de mémoire RTC, ajouter un membre pour agir comme identificateur doublerait la taille qu'il occupe dans la mémoire RTC. Or il n'y a que 7 causes de redémarrage alors il n'est pas nécessaire de consacrer un octet en entier pour sauvegarder cette information. Donc l'identificateur sera une valeur particulière 0b1011 = 0xB stockée dans les 4 bits supérieurs du membre reason de restart.

    #define RESTART_MARKER 0b10110000 // 0xB0 = 176 #define MARKER_MASK 0b11110000 // 0xF0 = 240 #define REASON_MASK 0b00001111 // 0x0F = 15

    Avec ces définitions il est facile de récupérer soit l'identificateur, soit la raison ou enregistrer une raison pour un démarrage.

    restart.reason & REASON_MASK la cause du démarrage
    restart.reason & MARKER_MASK l'identificateur; si égal à RESTART_MARKER il s'agit d'un enregistrement valide.
    RESET_MARKER | (aReason & REASON_MASK) valeur à mettre dans restart.reason pour enregistrer la cause aReason et inclure l'identificateur d'un enregistrement.

    Maintenant que la structure restart est correctement définie et que l'adresse de la tranche de mémoire RTC à utiliser est fixée, il est très facile de lire et d'écrire l'enregistrement restart. Voici les fonctions de mdEspRestart.cpp qui prennent en charge ces opérations.

    void loadRestart(void) { system_rtc_mem_read(rtcAddress, &restart, sizeof(restart)); if ( ( (restart.reason & MARKER_MASK) != RESTART_MARKER ) || ( (restart.reason & REASON_MASK) > REASON_EXT_SYS_RST) ) { initRestart(); } } void saveRestart(void) { system_rtc_mem_write(rtcAddress, &restart, sizeof(restart)); }

    On peut voir que la vérification de la validité de l'enregistrement lu de la mémoire RTC est un peu plus poussée que ce qui a été décrit ci-dessus. En effet en plus de confirmer la présence de l'identificateur, il faut que la raison de démarrage soit valide aussi.

    Comme d'habitude, je rends disponible le code source du croquis ESP8266 Arduino dont des éléments apparaissent ci-dessus. Notez qu'il s'agit d'une étape vers la version finale qui sera présentée dans la section suivante. Conséquemment, mdEspRestart.cpp est truffé de Serial.print qui m'aidaient à développer le programme. En outre, la fonction, dumpInfo() vérifie la taille et l'alignement de la structure restart. Voici un exemple de ce qu'affiche le programme sur le moniteur série quand on choisi l'option 'C' deux fois de suite pour redémarrer l'ESP8266.

    Redémarrage avec déclenchement du chien de garde matériel (attendre 6 secondes) ets Jan 8 2013,rst cause:4, boot mode:(3,7) wdt reset load 0x4010f000, len 1384, room 16 tail 8 chksum 0x2d csum 0x2d v614f7c32 ~ld loadRestart(): system_rtc_mem_read success loadRestart(): restart.reason: 0xb6, restart.data: 2, restart.count: 1. loadRestart(): MARKER VALID: true, reason: 6, reason valid: true setRestartReason(1) setRestartReason: restart.reason: 0xb1, restart.data: 2, restart.count: 1. setRestartReason: MARKER VALID: true, reason: 1, reason valid: false saveRestart(): restart.reason: 0xb1, restart.data: 3, restart.count: 2. saveRestart(): MARKER VALID: true, reason: 1, reason valid: true saveRestart(): system_rtc_mem_write success saveRestart(): checking save by reloading loadRestart(): system_rtc_mem_read success loadRestart(): restart.reason: 0xb1, restart.data: 3, restart.count: 2. loadRestart(): MARKER VALID: true, reason: 1, reason valid: true >>> Cause du démarrage: déclenchement du chien de garde matériel. Compte: 1. <<< Pour choisir comment redémarrer le ESP, appuyer sur A - avec ESP.restart() B - avec ESP.reset() C - avec déclenchement du chien de garde matériel D - avec déclenchement du chien de garde logiciel E - avec l'exception 0 - division avec 0 F - avec l'exception 3 - LoadStoreErrorCause ou - appuyer le bouton de réinitialisation.

  7. Compteur de démarrages et chien de garde loop
  8. On peut combiner le chien de garde loop qui a été l'objet du premier billet de cette série (Un troisième temporisateur de surveillance du ESP8266, version simplifiée) avec le mécanisme de sauvegarde de l'information au sujet de la cause du redémarrage du ESP8266 pour obtenir une bibliothèque qui est encore plus simple d'utilisation.

    Voici le fichier en-tête de la bibliothèque amputé de ses commentaires.

    #ifndef _MDESPRESTART_H_ #define _MDESPRESTART_H_ #define LWD_TIMEOUT 12000 #define REASON_USER_RESET 7 #define REASON_USER_RESTART 8 #define REASON_LWD_RST 9 #define REASON_LWD_LOOP_RST 10 #define REASON_LWD_OVW_RST 11 #define LOOP_END 0xFFFFFFFF #define DEFAULT_RESTART_ADDRESS 0xFF typedef uint8_t restartReason_t; typedef uint16_t restartCount_t; typedef uint32_t restartData_t; void lwdtStamp(restartData_t data = LOOP_END); void lwdtFeed(void); void lwdtInit(unsigned long timeout = LWD_TIMEOUT); restartReason_t getRestartReason(restartCount_t &count, restartData_t &data); void userReset(restartData_t data = 0); void userRestart(restartData_t data = 0); bool setRestartRtcAddress(uint8_t addr = DEFAULT_RESTART_ADDRESS); bool isUartBoot(void); #endif

    Le fonctionnement du chien de garde loop est presque entièrement opaque, caché dans le fichier mdEspRestart.cpp. Il faut quand même initialiser le chien de garde ce qu'on fait avec la fonction lwdtInit() vers la fin de la fonction setup() du croquis. Cette fonction à un paramètre optionnel, le temps d'attente avant que le chien de garde morde s'il n'est pas nourri. La valeur par défaut est LWD_TIMEOUT fixée à 12 secondes qui est le double du chien de garde matériel du ESP.

    Le chien de garde doit être nourri au début de la fonction loop() avec la fonction lwdtFeed(). C'est le seul endroit où devrait apparaître cette fonction. La fonction lwdtStamp() (ou lwdtStamp(LOOP_END)) doit être la dernière instruction de la fonction loop().

    À chaque étape importante, le progrès est marqué avec la fonction lwdtStamp(id)id est une valeur numérique unique entre 0 et 2^32-2 = 4 294 967 294. J'ai appelé cette valeur l'identificateur de module parce que d'habitude dans mes croquis chaque tâche accomplie dans loop() est faite par une fonction que j'appelle un module. Si le chien de garde est la métaphore pour le temporisateur de surveillance, alors la fonction lwdtStamp est comme le pointage à chaque station que doit faire le gardien de nuit pendant ses rondes de surveillance.

    La fonction getRestartReason() est utilisée vers le début de la fonction setup() pour gérer le cycle de démarrage. Elle renvoie la cause du démarrage actuelle et le nombre de fois consécutives que le démarrage a été fait pour cette même raison dans la variable count. En plus des sept raisons déjà définies dans le SDK de Espressif, la bibliothèque en rajoute cinq.

    RaisonValeurDescription
    REASON_DEFAULT_RST 0 démarrage normal à la mise sous tension
    REASON_WDT_RST 1 redémarrage du chien de garde matériel
    REASON_EXCEPTION_RST 2 redémarrage à cause d'une exception (1)
    REASON_SOFT_WDT_RST 3 redémarrage du chien de garde logiciel
    REASON_SOFT_RESTART 4 redémarrage par programmation (ESP.reset() ou ESP.restart())
    REASON_DEEP_SLEEP_AWAKE 5 réveil après un sommeil profond
    REASON_EXT_SYS_RST 6 réinitialisation externe du système
    REASON_USER_RESET 7 redémarrage avec userReset() (3)
    REASON_USER_RESTART 8 redémarrage avec userRestart() (3)
    REASON_LWD_RST 9 redémarrage du chien de garde loop
    REASON_LWD_LOOP_RST 10 redémarrage du chien de garde loop, la fonction loop() n'a pas été complétée (2)
    REASON_LWD_OVW_RST 11 redémarrage du chien de garde loop, ses variables ont été écrasées (2)

    Notes

    (1)La variable data contient le numéro de l'exception.
    (2)La variable data contient l'identificateur du dernier module marqué avec lwdtStamp().
    (3)La variable data contient la valeur du paramètre de userReset() ou userRestart().

    Il y a trois nouvelles raisons de démarrage associées au chien de garde loop. Ces raisons ont déjà été examinée (voir Le chien de garde loop dans Un troisième temporisateur de surveillance du ESP8266, version simplifiée.

    Il y a aussi les fonctions userReset() et userRestart() qui peuvent être utilisé à la place de ESP.reset() et ESP.restart() respectivement si c'est utile de pouvoir distinguer leur utilisation au démarrage du croquis. On peut aussi utiliser ces fonctions pour sauvegarder une valeur de 32 bits (un int par exemple) en mémoire RTC pour y avoir accès après le démarrage.

    Comment avant, la cause du démarrage précédent est conservée dans une structure restart enregistrée dans la mémoire RTC. Cependant, j'ai opté pour une structure qui prend 8 octets de mémoire.

    struct __attribute__((packed)) { uint8_t flag; restartReason_t reason; restartCount_t count; restartData_t data; } restart __attribute__((aligned(4)));

    Je préférais que le membre data soit assez grand pour contenir un entier (le type int occupe 4 octets, car le microprocesseur du ESP8266 est un Tensilica L106 Diamond est de 32 bits). Il faut aussi un membre booléen de plus à la structure restart pour gérer les démarrages causés par le temporisateur de surveillance supplémentaire ou par l'utilisateur. Le champ flag peut à la fois identifier la validité de l'enregistrement et servir d'identificateur de démarrage pour raison supplémentaire en lui affectant l'une de deux valeurs.

    #define RESTART_MARKER 0xA5 // 1010 0101 #define LWD_USR_MARKER 0xA1 // 1010 0001 #define MARKER_MASK 0xFB // 1111 1011

    RESTART_MARKER dénote un redémarrage pour l'une des sept raisons habituelles du ESP8266 alors que LWD_USR_MARKER dénote l'une des cinq nouvelles raisons pour un redémarrage du ESP8266. Toute autre valeur est une indication que la structure est corrompue. Comme on peut voir, ces deux valeurs ne diffèrent que d'un bit, c'est le champ booléen supplémentaire dont il était question ci-dessus. Le masque MARKER_MASK sert à vérifier que restart.flag contient l'une des deux valeurs RESTART_MARKER et LWD_USR_MARKER.

    Comme pour le chien de garde, la détermination de la cause du redémarrage et le calcul du nombre de démarrages consécutifs pour la même raison sont fait de façon opaque dans mdEspRestart.cpp. La fonction getRestarReason() renvoi la raison, le compte et le contenu de data. Avant d'examiner le code, voici sa logique.

    Les étapes de la démarche plus simple d'avant sont indiquées par le fond beige. Si le redémarrage est fait pour une des raisons standard, il n'y a pas de grand changement. En revanche si le redémarrage est fait automatiquement par le chien de garde loop ou par l'utilisateur en invoquant les fonctions userRestart() ou userReset() alors il y a des étapes supplémentaire. Premièrement ces fonctions doivent modifier le contenu de restart pour 1) identifier la raison du démarrage qui s'en vient, et pour 2) ajouter un indicateur de démarrage pour raison supplémentaire. On sait déjà que cela est fait en changeant la valeur de restart.flag à LWD_USR_MARKER. Deuxièmement, il faut les fonctions doivent sauvegarder le contenu modifié de restart dans la mémoire RTC. Enfin, elle redémarre le ESP avec la fonction ESP.restart().

    C'est en vérifiant la valeur de restart.flag que getRestartReason() peut distinguer un démarrage pour l'une des raisons supplémentaires d'un démarrage cause par ESP.restart() ou ESP.reset(). A peine deux ou trois lignes de code additionnelles sont nécessaire dans getRestartReason pour gérer le démarrage pour raisons supplémentaires. Elles sont en gras ci-dessous

    loadRestart(); restartReason_t reason = ESP.getResetInfoPtr()->reason; restartReason_t restartreason = restart.reason; if ( (restart.flag == LWD_USR_MARKER) && (reason == REASON_SOFT_RESTART) ) { reason = restartreason; } boolean isSameReason = (reason == restartreason); if (reason == REASON_EXCEPTION_RST) { restartData_t exceptionCause = ESP.getResetInfoPtr()->exccause; isSameReason = isSameReason && (exceptionCause == restart.data); restart.data = exceptionCause; } if (isSameReason) { restart.count++; } else { restart.count = 1; } restart.flag = RESTART_MARKER; restart.reason = reason; saveRestart();

    Préparer restart avant un redémarrage pour raison supplémentaire n'est pas complexe non plus.

    void setLwdtRestartReason(restartReason_t reason) { if (restart.reason != reason) { restart.count = 0; // it will be incremented by getRestartReason } restart.reason = reason; restart.flag = LWD_USR_MARKER; saveRestart(); }

    Avec cette bibliothèque, on peut mettre en œuvre la technique de récupération en cas de redémarrages inopportuns décrite dans le billet précédent Récupérer automatiquement des redémarrages en boucle du ESP8266 en tenant compte de la raison et du nombre consécutif de démarrages pour cette raison.

  9. Mise à jour du 27 juin 2018
  10. De petites modifications ont été faites à la bibliothèque depuis la première version publique.

  11. Conclusion
  12. Par rapport à la première version du troisième chien de garde que j'ai publiée il y a presque 10 mois, cette nouvelle version est une amélioration. Premièrement, la présentation sous forme de bibliothèque simplifie considérablement l'utilisation. Et deuxièmement, le code est plus clair, du moins je l'espère.

    L'ancienne version permettait d'utiliser la mémoire EEPROM plutôt que la mémoire RTC. J'ai opté pour l'élimination de cette possibilité pour deux raisons. Avec la création d'une bibliothèque, il fallait ajouter un paramètre à la fonction setRestartRtcAddress pour spécifier quelle mémoire non volatile utiliser. Cela voulait dire que la bibliothèque EEPROM était toujours importée et je voulais éviter cette dépendance. En plus, avec la prise en charge du compte de démarrage successif, le contenu de restart est copié sur la mémoire non volatile au moins une fois à chaque démarrage et deux fois quand celui-ci est à cause d'une raison supplémentaire. L'usure de la mémoire flash est à éviter.

    Il est impossible de créer un programme qui agisse comme chien de garde sans faille. Supposons que le croquis s'emballe et systématiquement efface le contenu de la mémoire RTC. Alors le chien de garde loop ne fonctionnera plus, car la fonction loadRestart() réinitialisera la structure restart à chaque démarrage.

    Un chien de garde matériel, une puce spécialisée, est préférable pour plus de certitude. Cependant, qu'est qui garantie que la puce fonctionne correctement ? Il faudrait un autre puce comme chien de garde du chien de garde. Et une autre après. Impossible de se sortir de cette régression sans fin à moins d'accepter la possibilité d'un bris non anticipé.

    Ceci étant dit, j'utiliserai la bibliothèque mdEspRestart quand j'élaborerai des croquis avec l'espoir qu'elle m'aidera à identifier des problèmes de programmation.

    Auparavant j'avais mentionné l'idée de créer une classe C++ pour encapsuler cette fonctionnalité. Maintenant que je suis « expert » en la matière avec deux classes C++ à mon actif, je pense que je pourrais le faire. Cependant, je doute de l'intérêt de la chose. L'objet serait nécessairement un « singleton » (un des patrons de conception de la Gang of Four). Or je trouve aberrant de créer une classe dont la raison d'être est le polymorphisme et la possibilité de créer plusieurs instances pour après s'assurer qu'il n'y aura qu'une seule instance de l'objet créée et qu'aucune autre classe héritera du singleton. Avec Borland Pascal, Delphi et maintenant Free Pascal, je préfère créer une unité séparée qui n'affiche que les fonctions et attributs publics dans la partie interface et qui cache les détails dans la partie implémentation de l'unité. De point de vue syntaxique, le résultat est presque identique à la création du singleton. Alors j'ai choisi de faire la même chose en C++. Malheureusement, mes connaissances encore très limitées du C/C++, malgré mon expertise auto proclamée ci-dessus, ne me permettent pas de juger du bien-fondé de cette décision. Commentaires ? Conseils ? Il y a un lien vers mon courriel au bas de la page.

  13. Téléchargements
  14. On peut télécharger la bibliothèque mdEspRestart.zip qui s'intègre à l'EDI Arduino avec le gestionnaire de bibliothèque (menu: Croquis/Inclure une bibliothèque/Ajouter bibliothèque .ZIP...)

    Si l'on préfère tester celle-ci avant de l'installer dans l'EDI, télécharger l'exemple esp_boot_lwdt.zip.

    Pour les situations où la mémoire RTC est presque toute utilisée à d'autres fins, il y a une version allégée de la bibliothèque, mdEspRestartSF.zip, qui n'accapare que 4 octets. L'exemple esp_boot_lwdt_sf.zip permet de test avant d'installer dans l'EDI. Pour réduire la taille de l'enregistrement, le type restartCount_t est d'un seul octet et le type restartData_t occupe deux octets seulement. Enfin, seulement trois bits de restart.reason servent à confirmer que restart est valide, le quatrième est utilisé comme indicateur de raison de démarrage supplémentaire.

    Les chances que getRestartReason() interprète les données lues de la mémoire RTC comme valide alors qu'elles ne le sont pas quand restart occupe 8 octets sont très faible, un peu moins de quatre centièmes de 1% ((2/256)×(12/256)). Dans mdEspRestartSF ce type d'erreur est bien plus probable, presque 9,4% ((1/8)×(12/16)). Il y a une façon de diminuer cette probabilité à un peu moins de 6% ((1/16)×(15/16)) mais en perdant l'habileté de distinguer userReset() et userRestart(). Pour ce faire, on garde 4 bits de restart.reason pour vérifier la validité des données. On change les codes des raisons de démarrage.
    RaisonValeurDescription
    REASON_DEFAULT_RST 0 démarrage normal à la mise sous tension
    REASON_WDT_RST 1 redémarrage du chien de garde matériel
    REASON_EXCEPTION_RST 2 redémarrage à cause d'une exception (1)
    REASON_SOFT_WDT_RST 3 redémarrage du chien de garde logiciel
    REASON_SOFT_RESTART 4 redémarrage par programmation (ESP.reset() ou ESP.restart())
    REASON_DEEP_SLEEP_AWAKE 5 réveil après un sommeil profond
    REASON_EXT_SYS_RST 6 réinitialisation externe du système
    REASON_USER_RST 7 redémarrage avec userReset() ou userRestart()
    REASON_LWD_RST 8 redémarrage du chien de garde loop
    REASON_LWD_LOOP_RST 9 redémarrage du chien de garde loop, la fonction loop() n'a pas été complétée (2)
    REASON_LWD_OVW_RST 10 redémarrage du chien de garde loop, ses variables ont été écrasées (2)
    REASON_USER_X 11 marqueur pour REASON_USER_RST
    REASON_LWD_X 12 marqueur pour REASON_LWD_RST
    REASON_LWD_LOOP_X 13 marqueur pour REASON_LWD_RST
    REASON_LWD_OVW_X 14 marqueur pour REASON_LWD_OVW_RST

    Au lieu de vérifier s'il y a un indicateur de démarrage pour une des raisons additionnelles, getRestartReason() vérifie si restart.reason contient l'un des marqueurs (..._X) et, le cas échéant, choisit comme raison de démarrage la raison supplémentaire correspondante. Je n'ai pas encore assez d'expérience pour savoir si cette complication vaut la peine, car pour l'instant j'utilise la version 8 octets.
Récupérer automatiquement des redémarrages en boucle du ESP8266

L'attribut packed n'est pas nécessaire ici, mais après avoir gaspillé tellement de temps sur ces questions d'alignement des variables, j'estime que bretelles et ceinture sont de rigueur.