La version 0.10 de MOGWAI NANO est disponible. Elle apporte des nouveautés bien visibles : de quoi manipuler la date et l’heure, de nouvelles possibilités pour les écrans TFT, un analyseur de programmes entre 5 et 10 fois plus rapide. Mais le changement le plus important de cette version est moins spectaculaire : j’ai repris de fond en comble la façon dont le moteur exécute les événements, les timers et les blocs post { ... }. Si vous écrivez des programmes qui réagissent à des événements, c’est la partie à lire attentivement.
La date et l’heure arrivent sur le NANO
C’était un manque évident pour un runtime qui tourne sur des microcontrôleurs : la 0.10 introduit une première série de primitives pour travailler avec la date et l’heure courantes.
Le principe est simple : un instant est représenté par un nombre de ticks. Comme c’est un nombre ordinaire, vous pouvez le stocker, le comparer ou le soustraire à un autre exactement comme n’importe quelle valeur.
nowpousse la date et l’heure courantes, sous forme de ticks.->datetransforme un nombre de ticks en un enregistrement avec l’année, le mois, le jour, l’heure, les minutes, les secondes, le jour de la semaine et le jour de l’année.date->fait l’opération inverse.->durationtransforme un nombre de ticks, par exemple la différence entre deux dates, en jours, heures, minutes, secondes et millisecondes.->durationsdonne plutôt le total exprimé dans chaque unité, etduration->revient aux ticks.setDateTimerègle l’horloge de l’appareil à partir d’un enregistrement, en UTC comme tout le reste. L’année, le mois et le jour sont obligatoires ; l’heure, les minutes et les secondes sont facultatives et valent 0 si elles sont absentes. C’est une primitive propre au NANO : elle n’existe pas dans le moteur MOGWAI pour le bureau.
Bonne nouvelle pour la cohérence de l’écosystème : now, ->date, date->, ->duration, ->durations et duration-> existent aussi dans le moteur MOGWAI pour le bureau et s’y comportent de la même façon. Un script qui manipule des dates n’a donc pas à être réécrit selon qu’il tourne sur votre PC ou sur votre ESP32.
Un détail technique qui a son importance : un nombre de ticks demande plus de précision que ce qu’un float peut offrir. Depuis la 0.10, le type .number est un double, comme dans le moteur MOGWAI pour le bureau, et ne porte plus qu’une seule valeur. Les dates se comportent ainsi de la même façon sur votre PC et sur l’appareil, et on peut ajouter ou retrancher à un instant avec les simples + et -.
Les écrans TFT : défilement, texte lissé et police LCD
Trois ajouts pour les primitives tft.*, réservées à l’ESP32.
Le défilement. tft.scroll déplace le contenu d’un rectangle du canevas, d’un certain nombre de pixels en horizontal et en vertical, et remplit la bande libérée avec la couleur de votre choix. Avec un décalage vertical de -8, le contenu monte d’une ligne de texte 8×8 : c’est exactement ce qu’il faut pour construire une console qui défile ligne par ligne. Le défilement s’opère sur le canevas en mémoire ; c’est le tft.refresh suivant qui envoie le rectangle à l’écran, environ 80 ms pour un écran 240×320 complet.
Le texte lissé. true tft.smooth active l’anti-crénelage pour le texte dessiné en taille 2 ou plus. Seules les marches d’escalier des bords en diagonale sont adoucies ; les bords droits et les angles restent nets. Le lissage se mélange avec ce qui se trouve déjà sur le canevas, donc il fonctionne aussi avec un fond transparent. Le mélange est calculé en lumière linéaire (avec correction gamma) et arrondi à la valeur la plus proche plutôt que tronqué, ce qui donne des bords plus naturels qu’un simple mélange des valeurs de couleur stockées. C’est désactivé par défaut.
La police LCD. Une seconde police, dessinée avec sept segments comme sur une horloge numérique, fait son apparition à côté de la police 8×8 intégrée. 1 tft.setFont la sélectionne, 0 tft.setFont revient à la police 8×8. Elle contient les chiffres, quelques symboles (-, _, =, °, : et .) et les lettres qu’un afficheur à sept segments sait représenter. Elle s’agrandit avec l’argument de taille. Pour obtenir l’aspect d’un vrai afficheur LCD, tft.setGhost dessine les segments éteints dans la couleur de votre choix, derrière les segments allumés ; -1 tft.setGhost les supprime. Ces deux réglages reviennent à leurs valeurs par défaut à chaque tft.init.

L’ensemble a été essayé sur la Waveshare ESP32-S3-Touch-LCD-2, en rotation 0.
Événements, timers et post { ... } : une refonte en profondeur
C’est le gros morceau de cette version, et il mérite qu’on prenne le temps de l’expliquer.
Le problème
Avant chaque instruction du code en cours, le moteur vérifie s’il y a des événements, des timers ou des blocs postés en attente. Jusqu’ici, il les démarrait immédiatement, même quand un autre gestionnaire était déjà en cours d’exécution. Les conséquences étaient désagréables :
- un gestionnaire pouvait être interrompu par un autre événement, un timer, un bloc
post { ... }, et même par un nouveau tick de son propre timer ; - quand plusieurs gestionnaires attendaient, le dernier arrivé s’exécutait en premier : trois événements mis en file dans l’ordre A, B, C s’exécutaient dans l’ordre C, B, A ;
- l’imbrication grandissait avec la longueur de la file. Sur un appareil qui a peu de RAM, une rafale d’événements pouvait épuiser la pile.
La nouvelle règle
Les événements, les timers et les blocs post { ... } s’exécutent maintenant un par un, dans l’ordre d’arrivée. Un gestionnaire va au bout de son exécution avant que le suivant ne démarre. Le programme principal, lui, continue d’avancer : il donne la main aux gestionnaires en attente à raison d’un par instruction.
Autour de cette règle, plusieurs garde-fous :
- Un timer déjà en attente n’est pas remis en file. Chaque timer garde au plus un tick en attente. Si un gestionnaire est plus lent que la période de son propre timer, la file ne grossit plus indéfiniment et le programme principal ne meurt plus de faim. Le tick arrivé pendant l’exécution est conservé et s’exécute juste après : le timer ne perd pas son rythme, il ne s’accumule simplement plus. Un tick déjà en file au moment d’un
timer.stops’exécute encore une fois. - Un événement garde au plus 8 occurrences en attente. Quand la file d’un événement est pleine, la nouvelle occurrence est abandonnée et un message est écrit dans la sortie de debug (pour la première, puis toutes les 50 occurrences). Contrairement aux ticks de timer, les occurrences d’un événement portent des données (
eventData) et ne peuvent pas être fusionnées : une borne est la seule protection sûre contre un producteur plus rapide que son gestionnaire. Les occurrences d’événements différents ne se comptent pas entre elles. breakdevient local au code qui l’a déclenché. Auparavant, unbreakexécuté dans un gestionnaire, hors de toute boucle, laissait son drapeau levé à la fin du gestionnaire, et la boucle du programme principal s’arrêtait à son prochain contrôle. Le drapeau est maintenant sauvegardé avant le démarrage d’un gestionnaire et restauré à sa fin : lebreakd’un gestionnaire n’affecte que les boucles de ce gestionnaire.mogwai.haltetmogwai.exitrestent globaux, puisqu’ils arrêtent tout le programme.- Aucun gestionnaire ne démarre quand une session de debug est en pause. Le code tapé dans la console pendant la pause,
waitet le pas-à-pas ne déclenchent plus d’événements, de timers ni de posts : quand le programme est en pause, tout est en pause. Les gestionnaires en attente s’exécutent à la reprise. waitne tourne plus à plein régime pour rien. Il dort 1 ms par tour quand aucun gestionnaire n’attend ou quand il est lui-même appelé depuis un gestionnaire, ce qui réduit la charge CPU d’un programme qui attend beaucoup.
Ce qui change pour vous
Changement de comportement. L’ordre dans lequel les effets de bord de plusieurs gestionnaires en attente deviennent visibles a changé : ils apparaissent désormais dans l’ordre d’arrivée. Si l’un de vos programmes dépendait, sans le dire, de l’ancien ordre (le dernier arrivé en premier), il se comportera différemment.
Deux précisions importantes :
- Un gestionnaire qui appelle
waitlaisse le programme principal avancer, mais ne laisse pas les autres gestionnaires démarrer entre-temps. - Une erreur levée dans un gestionnaire et non traitée sur place (avec
trapouguard) arrête toujours le programme, comme avant.
En contrepartie, le comportement est enfin prévisible : plus d’interruption surprise au milieu d’un gestionnaire, plus d’ordre inversé, plus de pile qui explose sur une rafale. Pour des programmes qui tournent des semaines sur un microcontrôleur, c’est une fondation bien plus saine.
Un analyseur 5 à 10 fois plus rapide
L’analyse d’un programme (parsing) est désormais entre 5 et 10 fois plus rapide qu’avant. Rien ne change dans la manière d’écrire les programmes ni dans leur comportement. Le gain se sent surtout sur les gros programmes et en mode debug (nano.debug et nano.debugall), là où l’analyse était la plus longue.
L’ESP32-S3 avec PSRAM, seule cible ESP32 supportée
Le classique ESP32 (ESP32_REV3, environ 40 Ko de RAM libre) n’est plus une cible. Les mesures ont montré une fragmentation de la mémoire sous charge soutenue, et pratiquement aucune marge pour les tâches. C’est un changement de documentation : rien ne change dans le code. Le README (tableau des plateformes, démarrage rapide, considérations mémoire) l’indique désormais clairement, et les instructions de flashage pour ESP32_REV3 ont disparu.
Côté Raspberry Pi Pico 2 W, le build MogwaiNanoPicoW est maintenant décrit pour ce qu’il est : une version minimale du runtime, sans les fonctions propres à l’ESP32 (esp32.setPinFunction, la LED embarquée, le BLE périphérique, le TFT et le tactile). Son nom ne change pas.
Corrections
Quelques bugs corrigés au passage, tous liés à la même zone du moteur :
mogwai.exitn’arrêtait pas une boucleforever do: elle continuait de tourner sans rien exécuter au lieu de terminer le programme. C’est corrigé.breakpouvait arrêter la boucle englobante par erreur, dansforeach(sur une liste, unMOGDataet une chaîne) comme dansrepeat,for,forstepetduring. Le drapeau est maintenant remis à zéro juste après le corps de la boucle.whilepouvait échouer avec une erreur de type après unmogwai.exitou unbreaksurvenu pendant l’évaluation de la condition. Il sort désormais proprement de la boucle.- Un gestionnaire en échec (exception dans un événement, un timer ou un bloc posté) laissait sa pile derrière lui, et le code suivant s’exécutait sur la mauvaise pile. La pile est maintenant toujours retirée à la fin du gestionnaire, quelle que soit l’issue.
after <ms> do { ... }(la forme sucrée deLATER) utilisait toujours le même nom de timer : le premier appel fonctionnait, mais le deuxième échouait parce qu’un timer de ce nom existait déjà, et un appel pouvait même démarrer le timer d’un appel précédent. Chaque appel crée maintenant son propre timer, avec un nom unique.
Pour finir
La 0.10 est une version à deux visages : des nouveautés concrètes (la date et l’heure, les écrans TFT) et un chantier de fond sur l’exécution des gestionnaires, qui rend le comportement du moteur prévisible. Si vos programmes utilisent des événements, des timers ou des blocs post { ... }, relisez la section qui leur est consacrée et testez-les avant de déployer.
Vous trouverez le code, le CHANGELOG complet et la release sur GitHub :
- Dépôt : https://github.com/Sydney680928/MogwaiNano
- Release 0.10.0 : https://github.com/Sydney680928/MogwaiNano/releases/tag/v0.10.0
Si vous rencontrez un souci ou si vous avez une idée, le suivi des tickets est ici : https://github.com/Sydney680928/MogwaiNano/issues