Rejouer la suite de vérification¶
Cette page documente comment relancer, de façon reproductible, les scripts qui vérifient les scénarios de requirements.md (section « Vérification ») — et comment en ajouter un nouveau. Pour le pourquoi de chaque scénario (quelle décision d'architecture il vérifie), voir requirements.md ; cette page se concentre sur le comment les rejouer.
Prérequis¶
omc(OpenModelica Compiler) et le toolchain MinGW d'une installation OpenModelica sur lePATH— voirCLAUDE.md.OPENMODELICAHOMEpositionné sur la racine de l'installation (ex.D:/Programmes/OpenModelica1.27.1-64bit).
Piège rencontré en session : sur un poste où OpenModelica est installé mais où omc n'a pas été ajouté au PATH d'un shell fraîchement ouvert (omc: command not found), le chemin d'installation réel peut se retrouver dans un artefact de build laissé par une session précédente (ex. un *.makefile généré par une simulation, qui référence l'installation via CPPFLAGS/LDFLAGS) — plus rapide que de chercher sur tout le disque. Une fois le chemin connu, préfixer la commande :
export PATH="/d/Programmes/OpenModelica1.27.1-64bit/bin:$PATH" # adapter le chemin à l'installation réelle
Lancer la suite¶
Toute la suite, en parallèle (recommandé)¶
Depuis MicroPythonMCU/Resources/Verification/, dans un shell bash (Git Bash, ou le msys fourni avec OpenModelica) :
./run_tests.sh # toute la suite, 4 exécutions simultanées
./run_tests.sh -j 2 # autre degré de parallélisme
./run_tests.sh verify_08_pwm.mos ... # seulement certains scripts
./run_tests.sh -k # garder artefacts et journaux (débogage)
./run_tests.sh --copy # avant un tag : sur une copie des fichiers suivis (voir ci-dessous)
PASS/FAIL/ERREUR avec la durée de chaque .mos, puis la fin du journal de chaque script en échec ; il se termine avec le code 1 si un script n'a pas passé. Si omc n'est pas sur le PATH mais que OPENMODELICAHOME est positionné, il complète le PATH lui-même. Sauf -k, il supprime ensuite tous les artefacts générés, qui portent tous le nom du fileNamePrefix de leur script.
Les scripts qui partagent des fichiers sont exécutés à la suite l'un de l'autre, jamais en même temps : même fileNamePrefix (verify_01/verify_05, tous deux BasicBlink), ou manipulation des copies de système de fichiers (verify_25/verify_27, qui effacent tous les mcu_datalogger_* et écrivent fs_copies.txt). verify_36 nomme ses copies mcu1_/mcu2_datalogger_* et ses fichiers d'après son fileNamePrefix : il n'entre en conflit avec personne. Ce regroupement est automatique : un nouveau script en conflit est pris en compte sans modifier run_tests.sh.
Durées mesurées (i5-13420H, 4 cœurs performants + 4 basse consommation, 16 Go, dépôt dans OneDrive) : ~265 s en séquentiel, ~125 s avec run_tests.sh. Le gain plafonne à ~2× quel que soit -j (3, 4 ou 6 donnent le même temps) : chaque omc occupe déjà plusieurs cœurs, notamment en compilant en parallèle les fichiers C générés, et le processeur est saturé. Hors OneDrive, la suite est ~10–15 % plus rapide.
Constaté le 2026-10-01 : pendant une session où OneDrive synchronisait beaucoup de fichiers modifiés, la suite lancée dans le dépôt a pris 21 min (KitchenScale seul : 712 s) et verify_25 a échoué sur Permission denied / Device or resource busy (fichiers générés verrouillés). Les mêmes 36 scénarios, sur une copie hors OneDrive, passaient en 3 min 34 (KitchenScale : 76 s, compilation comprise). Un temps mesuré dans le dossier synchronisé n'est donc pas un coût du modèle ; en cas de doute, rejouer hors OneDrive. --copy le fait, mais ne copie que les fichiers suivis par git : de nouveaux fichiers pas encore ajoutés en sont absents.
Pendant le travail ou avant un tag : dépôt ou copie¶
| Quand | Commande | Ce qui est testé |
|---|---|---|
| Pendant le travail (itération) | ./run_tests.sh, ou quelques scripts ciblés |
le dépôt, tel qu'il est sur le disque |
| Avant de poser un tag | ./run_tests.sh --copy |
ce que livrera le tag : les seuls fichiers de MicroPythonMCU/ suivis par git (modifications non commitées comprises), copiés dans un dossier temporaire hors du dépôt |
--copy attrape le défaut propre à une livraison par tag : un fichier nécessaire (script, source C, image de flash) présent sur le disque mais jamais ajouté à git. Il manquerait aux utilisateurs ; ici, la suite échoue. Les fichiers non suivis sont listés avant la suite. La copie tourne hors du dépôt, donc hors de OneDrive : aucun artefact n'est écrit dans le dossier synchronisé. Elle prend une demi-seconde (443 fichiers, 25 Mo).
Sous une autre version d'OpenModelica¶
Les utilisateurs n'ont pas tous la version de développement : les postes du lycée avaient la 1.26.8, où la bibliothèque échouait sans erreur visible à cause d'un changement de taille des booléens (cf. requirements.md, décision « Tableaux de booléens et fonctions externes »). Pour rejouer la suite sous une autre version installée à côté, positionner OPENMODELICAHOME et le PATH sur elle le temps de la commande :
( export OPENMODELICAHOME="d:/programmes/OpenModelica1.24.4-64bit"
export PATH="/d/programmes/OpenModelica1.24.4-64bit/bin:/d/programmes/OpenModelica1.24.4-64bit/tools/msys/ucrt64/bin:$PATH"
./run_tests.sh --copy )
modelica_boolean fait un octet. Un changement de l'interface C (Internal/*_sync.mo, fonctions externes) se vérifie sous les deux.
--copy se combine avec les autres options (-j, -k, liste de scripts), transmises à la copie de run_tests.sh. Sous le récapitulatif, il rappelle le commit testé et la mention « + modifications non commitées » s'il y a lieu : un résultat doit dire sur quoi il a tourné. Avec -k, la copie est conservée et son chemin affiché.
Un script à la fois¶
Depuis MicroPythonMCU/Resources/Verification/ :
omc verify_01_basic_blink.mos
omc verify_02_sleep_compression.mos
omc verify_03_input_reactivity.mos
omc verify_04_script_error.mos
omc verify_05_reset.mos
omc verify_06_pin_echo.mos
omc verify_07_adc_read.mos
omc verify_08_pwm.mos
omc verify_09_import.mos
omc verify_10_pin_irq.mos
omc verify_11_timer.mos
omc verify_12_display.mos
omc verify_13_uart_loopback.mos
omc verify_14_uart_echo.mos
omc verify_15_uart_sensor.mos
omc verify_16_uart_gps.mos
omc verify_17_uart_lcd.mos
omc verify_18_uart_regulation.mos
omc verify_19_uart_state_machine.mos
omc verify_20_uart_echo_table.mos
omc verify_21_i2c_echo.mos
omc verify_22_i2c_multi.mos
omc verify_23_i2c_nopullup.mos
omc verify_24_i2c_grove_lcd.mos
omc verify_25_filesystem.mos
omc verify_26_adc_sleep.mos
omc verify_27_filesystem_script.mos
omc verify_28_gpio_timing.mos
omc verify_29_python_dll.mos
omc verify_30_stdlib_import.mos
omc verify_31_hx711.mos
omc verify_32_kitchen_scale.mos
omc verify_33_multi_independent.mos
omc verify_34_multi_handshake.mos
omc verify_35_multi_uart.mos
omc verify_36_multi_filesystem.mos
omc verify_37_multi_i2c_mem.mos
omc verify_38_multi_i2c_irq.mos
omc verify_39_gpio_pull.mos
Modelica, charge ../../package.mo, simule, vérifie) et affiche PASS: verify_0X_... ou FAIL: verify_0X_... sur sa propre ligne — reproductible en ligne de commande, sans session OMEdit interactive.
Ce que vérifie chaque script¶
| Script | Modèle exercé | Scénario de requirements.md |
Résultat attendu |
|---|---|---|---|
verify_01_basic_blink.mos |
Examples.BasicBlink |
Clignotement de base (shim machine/time, boucle de synchro) |
GP0 alterne ≈3 V / 0 V à la bonne période |
verify_02_sleep_compression.mos |
Examples.Program.SleepCompression |
Compression du sleep (cœur de la valeur du projet) |
Bascules aux instants attendus, simulation rapide (pas de temps réel proportionnel au temps simulé) |
verify_03_input_reactivity.mos |
Examples.Gpio.InputReactivity |
Réactivité en entrée pendant un sleep |
Réaction peu après la transition, pas à l'échéance du sleep |
verify_04_script_error.mos |
Examples.Program.Error |
Exception non gérée dans le script | La simulation s'arrête proprement en erreur : l'exécutable, relancé par son .bat, sort avec le code -1 (et non -1073741819, le plantage que contourne Library = "-lwinpthread"), et son journal contient ZeroDivisionError et le message du runtime |
verify_05_reset.mos |
Examples.BasicBlink (relancé deux fois) |
Cycle de vie de l'External Object | Deux relances produisent des résultats strictement identiques |
verify_06_pin_echo.mos |
Examples.Gpio.PinEcho |
Bouclage entre deux broches du même MCU |
GP3 suit GP1 (relu via GP2) à chaque phase, sans lecture périmée |
verify_07_adc_read.mos |
Examples.Adc.Read |
Entrée analogique (machine.ADC) |
GP1 reflète le pont diviseur (~2,2 V), GP0 (LED) allumée à t=0,3 s, après la 2e lecture (la 1re, à t=0, voit encore 0 V) |
verify_08_pwm.mos |
Examples.Pwm.Led |
Sortie PWM (machine.PWM) |
GP0 suit le créneau attendu (haut/bas conformes à la période/rapport cyclique), y compris bien après la fin du script |
verify_09_import.mos |
Examples.Program.Imports |
Import de modules auxiliaires (addScriptDirToPath/libraryPath) |
GP0/GP1 (LED) s'allument, confirmant que les deux imports (dossier du script, bibliothèque partagée) ont réussi |
verify_10_pin_irq.mos |
Examples.Irq.Pin |
Interruption sur broche (machine.Pin.irq) |
GP0 bascule au front montant, reste inchangée au front descendant (filtrage par sens de front) |
verify_11_timer.mos |
Examples.Irq.Timer |
Minuteur logiciel (machine.Timer) |
GP0 bascule toutes les 500 ms pendant un sleep long, sans l'écourter (« pitstop ») |
verify_12_display.mos |
Examples.Display.Demo |
Périphérique d'affichage pédagogique (machine.Display) |
seq 0→1→2, charCode conformes au texte, défilement 20×2 (ancien message en ligne 2) |
verify_13_uart_loopback.mos |
Examples.Uart.Loopback |
Liaison série électrique réelle bouclée (machine.UART) |
Trame 8N1 correcte sur GP0 (start bas, données poids faible en tête, stop/repos hauts), deux trames enchaînées sans trou, témoin GP3 allumé (octets relus intacts) |
verify_14_uart_echo.mos |
Examples.Uart.EchoPy |
Dialogue avec un périphérique série externe, en mode Script | Les deux lignes au repos haut, trame 'H' correcte à l'entrée du périphérique, périphérique muet avant la fin de la ligne puis en émission, témoin GP7 à l'état haut |
verify_15_uart_sensor.mos |
Examples.Uart.Sensor |
Requête/réponse dans les deux sens ({vN} et {oN}) |
Deux mesures différentes lues sur une rampe, et valueOut[1] passe à 42,5 après SET 42.5 |
verify_16_uart_gps.mos |
Examples.Uart.GpsPy |
Émission périodique spontanée, en mode Script | Rien avant la première échéance, émissions aux échéances, au moins 4 phrases NMEA à somme de contrôle correcte en 500 ms, compteur du script publié sur valueOut |
verify_17_uart_lcd.mos |
Examples.Uart.Lcd |
Afficheur 20x2 sur une vraie trame série | Codes ASCII conformes au texte décodé, défilement ligne 1 → ligne 2 (valide aussi l'icône factorisée Internal.TwoLineTextIcon) |
verify_18_uart_regulation.mos |
Examples.Uart.Regulation |
Boucle de régulation fermée par la liaison série | La température part de 20 °C et rejoint la consigne de 40 °C, avec une commande cohérente avec le gain statique du procédé — seule la sortie réelle de l'appareil peut produire ce résultat |
verify_19_uart_state_machine.mos |
Examples.Uart.StateMachinePy |
Appareil décrit par un script Python à machine d'état | État 0 → 1 → 0 et exactement 2 lectures servies : l'état persiste, et aucune transition n'est rejouée |
verify_20_uart_echo_table.mos |
Examples.Uart.Echo |
Même montage que Uart.EchoPy, en mode Table |
Écho octet par octet : le périphérique émet déjà à t=17 ms, avant la fin de la ligne — complément exact de verify_14 |
verify_21_i2c_echo.mos |
Examples.I2c.Echo |
Bus I2C électrique, un maître et un esclave | Bus au repos haut, START conforme, ACK de l'adresse tenu par l'esclave, trame de 9 octets écrite puis relue, registre lu derrière un START répété, témoin GP7 allumé |
verify_22_i2c_multi.mos |
Examples.I2c.MultiDevice |
Trois esclaves sur le même bus, deux paires de tirages en parallèle, 400 kHz | scan() exact, chaque écho ne reçoit que sa trame (pas de diaphonie), EIO sur une adresse absente, témoin GP7 allumé |
verify_23_i2c_nopullup.mos |
Examples.I2c.NoPullUp |
Même bus sans résistance de tirage externe : seuls restent les tirages internes que I2C() active |
Lignes au repos à plus de 3 V, mais ETIMEDOUT (SCL trop lente à remonter à 400 kHz), scan() vide, aucun esclave sollicité, témoin GP7 allumé |
verify_24_i2c_grove_lcd.mos |
Examples.I2c.GroveLcd |
Écran Grove LCD RGB piloté par un driver du commerce, sans modification | Écran éteint et noir avant l'initialisation, puis « hello World » en ligne 1 dès la colonne 2, rétroéclairage rouge, vert, bleu |
verify_25_filesystem.mos |
Examples.FileSystem.Boot |
Système de fichiers, démarrage boot.py/main.py, déterminisme |
Deux simulations : GP1 allumée (auto-contrôle de main.py), deux copies horodatées distinctes aux measurements.csv identiques, .. bloqué à la racine de la flash, image source intacte. Le script supprime lui-même ses copies en fin de scénario |
verify_27_filesystem_script.mos |
Examples.FileSystem.Script |
boot.py de la flash, puis un script à la place de main.py |
GP1 allumée, la copie contient data/notes.txt et pas data/measurements.csv (main.py n'a pas tourné) |
verify_26_adc_sleep.mos |
Examples.Adc.Sleep |
Entrée ADC traversant le seuil logique (correctif « l'ADC coupe l'entrée numérique ») | Cinq sleep(0.2) de 200 000 µs exactement (ticks_us()) malgré 10 franchissements du seuil par seconde, sleep_us(250) mesuré à 250 µs, aucune IRQ, témoin GP1 allumé à la fin seulement |
verify_28_gpio_timing.mos |
Examples.Gpio.Timing |
Coût temporel des accès GPIO (gpioOpTime = 5 µs) : bit-banging, attente active, disable_irq/enable_irq, idle() |
Impulsion on(); off() de 5 µs mesurée côté Modelica, 11 impulsions, rafale de 100 µs mesurée par ticks_us(), attente active sortie 0,5 µs après le front, callback du front masqué exécuté à enable_irq() (500 ms), idle() sur une milliseconde ronde |
verify_29_python_dll.mos |
PythonDll, défini dans le script (Examples.BasicBlink avec Verification/python_dll_origin.py) |
Distribution Python embarquée : python312.dll chargée par son chemin absolu, pas par le PATH |
GP0 allumée : la DLL chargée (sys.dllhandle) est dans sys.prefix, soit Resources/PythonRuntime, même avec un Python système dans le PATH ; sinon exception, simulation en erreur |
verify_30_stdlib_import.mos |
StdlibImport, défini dans le script (Examples.BasicBlink avec Verification/stdlib_import.py) |
Cloisonnement de os limité au code du microcontrôleur, y compris pour les modules de python312.zip |
GP0 allumée : ctypes, random, tempfile importés et utilisables (vrai os), alors que l'import os du script reste celui de MicroPython (refus sans système de fichiers) |
verify_31_hx711.mos |
Examples.Weighing.Hx711Read |
HX711 lu par le driver de robert-hh sans modification, chaîne force → corps d'épreuve → pont → convertisseur | Codes exacts (429 497 à gain 128, 214 748 à gain 64 pour 1 kg) côté HX711 et relus par le driver sur l'afficheur ; 25 puis 27 impulsions ; veille pendant power_down(), gain 128 au réveil |
verify_32_kitchen_scale.mos |
Examples.Weighing.KitchenScale |
Balance de cuisine complète : écran I2C, HX711 bruité, bouton TARE sur interruption, deux drivers du commerce | Écran : « 0 g » après la tare du plateau, « 350 g » avec le bol, « Tare... » puis « 0 g » après l'appui, « 250 g » après la farine (à 1 g près). Le plus long de la suite (≈ 50 s de simulation, cf. peripheriques-pesee.md) |
verify_33_multi_independent.mos |
Examples.MultiMcu.Independent |
Deux MCU, même programme et même module importé (sous-interpréteurs) | Chaque carte compte ses 5 tops et allume GP1 ; journal préfixé [Independent.mcu1]/[Independent.mcu2] |
verify_34_multi_handshake.mos |
Examples.MultiMcu.Handshake |
Poignée de main GPIO entre deux MCU câblés dans les deux sens (coupure discrète de la boucle) | ACK suit REQ à moins de 0,5 ms dans les deux sens, trois tours, GP2 de A allumée |
verify_35_multi_uart.mos |
Examples.MultiMcu.Uart |
Liaison série croisée entre deux MCU, 9600 bauds | Repos haut, start bit à t ≈ 20 ms, trois PONG reçus, GP7 de A allumée |
verify_36_multi_filesystem.mos |
Examples.MultiMcu.FileSystem |
Deux systèmes de fichiers à partir de la même image | Une copie mcu1_datalogger_* et une mcu2_datalogger_*, auto-contrôles passés, mesures distinctes |
verify_37_multi_i2c_mem.mos |
Examples.MultiMcu.I2c |
machine.I2CTarget en mode mémoire, maître sur un autre MCU |
scan() = [0x42], LED de B allumée par writeto_mem, 2,000 V relus, GP7 de A allumée |
verify_38_multi_i2c_irq.mos |
Examples.MultiMcu.I2cIrq |
machine.I2CTarget à gestionnaire IRQ (END_WRITE, READ_REQ) |
ID → b'MCU-B', CNT → 2 puis 3, GP7 de A allumée |
verify_39_gpio_pull.mos |
Examples.Gpio.Pull |
Tirages internes (Pin.PULL_UP, Pin.PULL_DOWN) et vraie haute impédance |
Boutons sans résistance externe : GP0 à 3,3 V relâché / 0 V appuyé, GP3 l'inverse, recopiés sur GP6/GP7 ; GP4 (1 MΩ vers 3,3 V) à 3,3 V sans tirage, 0,16 V avec le tirage bas, 3,3 V avec le tirage haut |
Scénarios sans .mos (vérification visuelle ou démonstrateurs)¶
Certains éléments de Examples/ ne correspondent volontairement à aucun script .mos :
- Lisibilité visuelle de l'icône/du diagramme (scénario dédié de
requirements.md) : vérifiée via les outils MCP-OpenModelica (iconDiagram/classDiagram) et une relecture visuelle directe de l'image obtenue — pas de critère numérique automatisable. Examples.Gpio.LedChaser: démonstrateur (chenillard visuel sur les 8 GPIO), pas un scénario de vérification derequirements.md— sert à donner à voir l'animation des icônesPeripherals.LEDen conditions de clignotement rapide, pas à être rejoué automatiquement.
Un nouvel exemple a besoin d'un .mos dédié seulement s'il vérifie un comportement numérique précis (une décision d'architecture, un correctif). Un exemple purement illustratif (comme Gpio.LedChaser) n'en a pas besoin.
Écrire un nouveau verify_0N_....mos¶
Reprendre la structure des scripts existants (voir verify_01_basic_blink.mos pour le cas simple, verify_04_script_error.mos pour le cas « échec attendu ») :
loadModel(Modelica); getErrorString();
loadFile("../../package.mo"); getErrorString();
simulate(MicroPythonMCU.Examples.MonScenario, stopTime = ..., numberOfIntervals = ..., fileNamePrefix = "MonScenario");
b := getErrorString();
vX := val(mcu.GPx.v, <instant>, "MonScenario_res.mat");
if <condition de succès> then
print("PASS: verify_0N_mon_scenario (...)\n");
else
print("FAIL: verify_0N_mon_scenario (...)\n" + b);
end if;
simulate() n'est pas lisible depuis un .mos (r.resultFile introuvable), et getErrorString() peut être vide alors que la simulation a échoué. Relancer l'exécutable par son .bat avec system(".\\MonScenario.bat", "MonScenario_run.txt") (code de sortie non nul), lire le journal par readFile, et y chercher un texte précis avec regex(texte, motif, 1), qui rend le nombre de correspondances (cf. verify_04_script_error.mos). Modelica.Utilities.Strings.find/System.stringFind restent indisponibles.
Piège rencontré en session : getErrorString() n'est pas un critère, ni de succès ni d'échec. Il a longtemps contenu l'avertissement « The initial conditions are not fully specified », présent dans tous les scénarios, y compris ceux qui réussissent parfaitement : ce texte a fait échouer à tort un critère b == "", et réussir à tort l'ancien critère b <> "" de verify_04. Cet avertissement a disparu (valeurs de départ explicites, cf. requirements.md), mais n'importe quel autre avertissement peut prendre sa place. Un if b == "" and ... then PASS, comme tenté une première fois pour verify_09_import.mos, échoue donc à tort. Se fier uniquement aux valeurs numériques attendues (val(...)) pour le critère de succès ; b reste utile seulement pour le diagnostic affiché dans le message FAIL.
Nettoyage¶
run_tests.sh les supprime lui-même à la fin (sauf -k). Lancés à la main, les artefacts de compilation générés par ces exécutions (.exe, .o, .c générés, *_res.mat, *.makefile, etc.) sont couverts par .gitignore — vérifier git status après une session de vérification pour confirmer qu'aucun artefact non couvert (ex. un nouveau motif de nom de fichier) ne traîne. Les copies de système de fichiers (mcu_datalogger_*) créées par verify_25 et verify_27 sont supprimées par les scripts eux-mêmes, qui désactivent aussi l'ouverture de l'Explorateur (simflags = "-override=mcu.fsOpenExplorer=false") ; s'il est interrompu, elles restent dans ce dossier (et sont effacées au lancement suivant).