MOGWAI NANO 0.10 : la date et l’heure, des écrans TFT plus soignés, et des événements enfin bien rangés

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.

  • now pousse la date et l’heure courantes, sous forme de ticks.
  • ->date transforme 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.
  • ->duration transforme un nombre de ticks, par exemple la différence entre deux dates, en jours, heures, minutes, secondes et millisecondes. ->durations donne plutôt le total exprimé dans chaque unité, et duration-> revient aux ticks.
  • setDateTime rè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.

Un chronomètre numérique affichant 00:00:19.20 avec des boutons "reset" et "stop".
La police LCD

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.stop s’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.
  • break devient local au code qui l’a déclenché. Auparavant, un break exé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 : le break d’un gestionnaire n’affecte que les boucles de ce gestionnaire. mogwai.halt et mogwai.exit restent 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, wait et 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.
  • wait ne 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 wait laisse 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 trap ou guard) 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.exit n’arrêtait pas une boucle forever do : elle continuait de tourner sans rien exécuter au lieu de terminer le programme. C’est corrigé.
  • break pouvait arrêter la boucle englobante par erreur, dans foreach (sur une liste, un MOGData et une chaîne) comme dans repeat, for, forstep et during. Le drapeau est maintenant remis à zéro juste après le corps de la boucle.
  • while pouvait échouer avec une erreur de type après un mogwai.exit ou un break survenu 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 de LATER) 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 :

Si vous rencontrez un souci ou si vous avez une idée, le suivi des tickets est ici : https://github.com/Sydney680928/MogwaiNano/issues

Laisser un commentaire

En savoir plus sur CODING 4 PHONE

Abonnez-vous pour poursuivre la lecture et avoir accès à l’ensemble des archives.

Poursuivre la lecture