Construire un Simulateur Informatique Spatial avec Claude Code
Je voulais comprendre comment fonctionnent les ordinateurs des engins spatiaux. La planification en temps réel, le durcissement aux radiations, le réseau tolérant aux délais qui maintient les rovers martiens en communication avec la Terre. Avec Artemis II à l’horizon (la première mission lunaire habitée en plus de 50 ans), le moment semblait venu de creuser le sujet. J’ai donc construit un simulateur. De zéro. Avec Claude Code comme pair programmeur.
Aucun matériel physique. Tout tourne dans QEMU et Docker sur un ordinateur portable. Code sur GitHub.
flowchart LR
QEMU["QEMU (Cortex-M3)"] -->|UART socket| Bridge[uart_bridge.py]
Bridge -->|bpsendfile| SC[Spacecraft Node]
SC -->|LTP + 5s delay| GS[Ground Station]
subgraph Docker
SC
GS
end
flowchart TB
QEMU["QEMU (Cortex-M3)"] -->|UART socket| Bridge[uart_bridge.py]
Bridge -->|bpsendfile| SC["Spacecraft Node (Docker)"]
SC -->|LTP + 5s delay| GS["Ground Station (Docker)"]
Pourquoi Ce Projet
Je voulais apprendre quelque chose de vraiment difficile. Quelque chose où les concepts sont inconnus et les outils impitoyables. Des compilateurs C ciblant ARM. Des scripts d’éditeur de liens. Des E/S mappées en mémoire. Des tables de vecteurs d’interruption.
Claude Code a rendu cela possible. Non pas parce qu’il a écrit tout le code, mais parce qu’il expliquait ce que fait un script d’éditeur de liens pendant que nous en écrivions un, de sorte que l’explication était ancrée dans le problème réel que je résolvais. Idem pour volatile, la préemption de priorité FreeRTOS et les minuteries de retransmission LTP.
La Feuille de Route
J’ai divisé le projet en quatre phases, chacune s’appuyant sur la précédente :
| Phase | Quoi | Concepts Clés |
|---|---|---|
| Bare Metal | Cross-compilation ARM, sortie UART, gestionnaires d’interruption | E/S mappées en mémoire, minuterie SysTick, assembleur de démarrage |
| FreeRTOS | Tâches, files d’attente, watchdogs, inversion de priorité | Ordonnancement déterministe, protocoles mutex, préemption |
| DTN | Réseau à deux nœuds, liens dégradés, CFDP, routage par graphe de contact | Bundle Protocol, store-and-forward, fiabilité LTP |
| Intégration | Pipeline de télémétrie complet avec délais à distance de Mars | Pont UART, tc netem, ION OWLT synchronisé |
Chaque phase comporte des tests automatisés. Chaque jalon est une PR avec CI validé.
Phase 1 : Bare Metal
Le premier défi était de faire tourner quoi que ce soit. Cross-compilation ARM ciblant un Cortex-M3 (MPS2-AN385) dans QEMU. Pas de système d’exploitation, pas de bibliothèque standard, pas de printf.
Claude m’a aidé à comprendre la séquence de démarrage : la table des vecteurs, le gestionnaire de reset, la copie de .data de la flash vers la RAM, la mise à zéro de .bss. Des choses qui se produisent avant même l’exécution de main().
La première victoire fut un seul caractère apparaissant sur une console UART :
#define UART0_DR (*(volatile uint32_t *)0x40004000)
void uart_putc(char c) {
UART0_DR = c;
}
Deux lignes de C. Aucune bibliothèque. Juste écrire un octet à une adresse mémoire qui se trouve être câblée à un port série. C’était comme parler directement à la machine.
De là : interruptions du minuterie SysTick, gestionnaires d’interruption, sortie structurée. La démo SysTick configure un minuterie matériel pour se déclencher à 2 Hz et compte 10 ticks :
SysTick interrupt demo
======================
Ticking at 2 Hz for 5 seconds (10 ticks)...
tick 1
tick 2
tick 3
tick 4
tick 5
tick 6
tick 7
tick 8
tick 9
tick 10
Done — 5 seconds counted by interrupt.
Aucun tick manqué. Aucun tick supplémentaire. Le CPU dort entre les interruptions et le matériel le réveille exactement au bon moment. Exécution déterministe depuis le bare metal.
Phase 2 : FreeRTOS
Avec le bare metal fonctionnel, j’ai ajouté FreeRTOS, un système d’exploitation temps réel qui tourne sur tout, des dispositifs médicaux aux satellites.
Les exercices ont été construits progressivement :
- Deux tâches à des cadences différentes. Multitâche de base.
- Communication par files d’attente. Partage sécurisé de données entre tâches.
- Minuterie watchdog. Détecter les tâches bloquées et s’en remettre.
- Pipeline de capteurs. Quatre capteurs à des cadences différentes alimentant une chaîne de traitement.
- Inversion de priorité. Déclencher et résoudre le bug classique des RTOS.
Voici le pipeline de capteurs qui démarre. Notez le gyroscope à 10 Hz dominant le flux, avec la lecture de température à 1 Hz insérée au tick 1001 :
FreeRTOS Sensor Pipeline Demo
=============================
[GYRO] Sensor online (10 Hz, priority 4)
[PROC] Processor online (priority 3)
[TELEM] Telemetry online (priority 2)
[TEMP] Sensor online (1 Hz, priority 1)
[TELEM] #000 GYRO: 300 at tick 100
[TELEM] #001 GYRO: 300 at tick 200
...
[TELEM] #009 GYRO: 300 at tick 1000
[TELEM] #010 TEMP: 1991 at tick 1001
[TELEM] #011 GYRO: 300 at tick 1100
C’est la préemption basée sur les priorités en action. Le gyroscope tourne à la priorité 4, le capteur de température à la priorité 1. La lecture de température ne passe que lorsque le gyroscope n’occupe pas le CPU.
L’exercice sur l’inversion de priorité était le plus instructif. J’ai créé trois tâches où une tâche à basse priorité détient un mutex dont une tâche à haute priorité a besoin, et une tâche à priorité moyenne affame les deux. La solution : l’héritage de priorité, où FreeRTOS booste temporairement la tâche à basse priorité pour qu’elle puisse libérer le mutex plus rapidement.
C’est le bug qui a failli tuer la mission Mars Pathfinder en 1997. Le construire moi-même a fait « cliquer » l’explication du manuel.
Phase 3 : Réseau Tolérant aux Délais
TCP/IP ne fonctionne pas dans l’espace. Les temps aller-retour vers Mars vont de 6 à 44 minutes. Les liens tombent pendant des heures quand les planètes occultent le signal. L’hypothèse de TCP d’une connexion continue à faible latence s’effondre complètement.
La réponse de NASA JPL est DTN, ou Delay-Tolerant Networking. Le Bundle Protocol stocke les données localement et les transmet de nœud en nœud quand les liens deviennent disponibles. Il est conçu précisément pour les conditions qui brisent internet.
J’ai construit l’implémentation ION de la NASA dans Docker et effectué des tests progressivement plus difficiles :
- Connectivité de base. Deux nœuds échangeant des bundles.
- Liens dégradés.
tc netemajoutant 500ms de latence, 25% de perte de paquets, des pannes complètes. - Transfert de fichiers CFDP. Livraison fiable de fichiers avec vérifications d’intégrité.
- Routage par graphe de contact. Bundles mis en file d’attente pendant les interruptions de lien, livrés quand les fenêtres s’ouvrent.
Le test de lien intermittent raconte toute l’histoire DTN en quelques lignes de sortie :
Test: intermittent link (send during outage, deliver on recovery)...
qdisc: qdisc netem root refcnt 2 limit 1000 loss 100%
confirmed: bundle queued (link is down)
restoring link...
PASS: bundle held during outage
PASS: bundle delivered after link recovery
C’est le store-and-forward en action. Le bundle est envoyé pendant que le lien est complètement mort, 100% de perte de paquets. Il attend dans la file d’attente du nœud local. Au moment où nous effaçons la règle netem et restaurons la connectivité, LTP retransmet et le bundle arrive à l’autre bout. TCP aurait abandonné depuis longtemps.
Phase 4 : Intégration
La dernière phase connecte tout. Le firmware FreeRTOS génère de la télémétrie dans QEMU. Un script pont Python lit la sortie UART et l’injecte sous forme de bundles DTN. Les bundles traversent un réseau retardé pour atteindre une station au sol.
Le firmware démarre et commence immédiatement à transmettre :
# Spacecraft Telemetry Firmware v1.0
# ===================================
# GYRO sensor online (10 Hz, priority 4)
# Processor online (priority 3)
# Telemetry online (priority 2)
# TEMP sensor online (1 Hz, priority 1)
# BATT sensor online (0.5 Hz, priority 1)
# SUN sensor online (2 Hz, priority 1)
$TELEM,0000,GYRO,300,100
$TELEM,0001,GYRO,300,200
...
$TELEM,0005,SUN,801,501
...
$TELEM,0011,TEMP,1991,1001
$TELEM,0012,SUN,801,1001
Quatre capteurs à quatre cadences différentes. On peut voir le gyroscope à 10 Hz produisant la plupart des lectures, avec le capteur solaire à 2 Hz, la température à 1 Hz et la batterie à 0,5 Hz intercalés. Le pont regroupe ceux-ci en bundles DTN toutes les 2 secondes.
Le test de délai de Mars prouve que l’ensemble du pipeline fonctionne dans des conditions réalistes :
Running Mars-delay end-to-end tests...
PASS: tc netem delay 5s applied on spacecraft
PASS: bridge exited cleanly
PASS: bridge read telemetry lines
PASS: bridge sent >= 1 bundle (got 12)
PASS: ground station received files (got 8)
PASS: received telemetry lines (got 224)
PASS: telemetry CSV format valid (5 fields)
INFO: delay observable (8 delivered at bridge exit < 12 sent)
7 passed, 0 failed
12 bundles envoyés, mais seulement 8 livrés au moment où le pont s’est arrêté. Le reste était encore en transit dans le délai de 5 secondes. C’est le délai qui est réel, pas simulé dans le logiciel.
La simulation du délai de Mars utilise deux mécanismes synchronisés :
- tc netem ajoute 5 secondes de latence réseau réelle aux deux conteneurs
- Les tables de portée ION définissent le même temps de lumière unidirectionnel de 5 secondes pour que les minuteries de retransmission LTP soient correctes
Les deux doivent concorder. Si le délai réel est de 5 secondes mais qu’ION pense qu’il est d'1 seconde, LTP retransmet agressivement et inonde le lien. Mettre cela au point m’a plus appris sur la conception de protocoles que n’importe quel chapitre de manuel sur la fiabilité.
Travailler avec Claude Code
Ce projet m’aurait pris des mois sans Claude. Non pas parce que le code est complexe (la plupart des fichiers font moins de 300 lignes) mais parce que la courbe d’apprentissage pour chaque domaine est abrupte.
Ce qui a bien fonctionné :
- Connaissance inter-domaines. Le projet couvre C, Python, assembleur ARM, Docker, ION DTN, FreeRTOS, QEMU, tc netem et LTP. Personne ne connaît bien tous ces domaines. Claude pouvait passer de l’un à l’autre.
- Jalons pilotés par les tests. Chaque phase a des tests automatisés. Claude a aidé à écrire des assertions qui vérifient le comportement réel, pas seulement « ça compile ».
- Workflow basé sur les PR. Chaque jalon est une branche et PR séparée avec CI. Le support worktree de Claude Code a maintenu cela propre. Branches isolées, aucune contamination croisée accidentelle.
Ce qui a nécessité de la prudence :
- Le C embarqué est impitoyable. Les erreurs off-by-one dans les scripts d’éditeur de liens ne donnent pas de stack trace. Ils donnent un hard fault ou une corruption silencieuse. Les suggestions de Claude nécessitaient une vérification soigneuse par rapport à la documentation matérielle.
- La documentation ION DTN est clairsemée. Les données d’entraînement de Claude ne couvrent pas les mécanismes internes profonds d’ION. Je me suis appuyé sur le code source d’ION et les exemples de configuration plutôt que sur les explications de Claude sur le comportement spécifique à ION.
Les Chiffres
| Métrique | Valeur |
|---|---|
| Tests totaux | 50+ assertions dans 7 suites de tests |
| Langages | C, Python, Bash |
| Matériel | Aucun (QEMU + Docker) |
| Lignes de C | ~1 200 (firmware + bare metal) |
| Lignes de Python | ~1 500 (tests + pont + scripts DTN) |
| Config ION DTN | ~350 lignes dans 6 fichiers .rc |
Essayez
Tout est open source et tourne sur n’importe quelle machine Linux avec QEMU et Docker :
sudo apt install gcc-arm-none-eabi qemu-system-arm build-essential
git clone https://github.com/granda/spacecraft-computing-sim
cd spacecraft-computing-sim
make -C bare-metal run # bare metal UART output
make -C freertos run # FreeRTOS sensor pipeline
make -C dtn test # two-node DTN network
make -C integration test-bridge # full telemetry pipeline
make -C integration test-mars-delay # Mars-distance delays
Aucun matériel physique. Aucun service cloud. Juste un ordinateur portable et de la curiosité sur le fonctionnement des ordinateurs des engins spatiaux.