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 :

PhaseQuoiConcepts Clés
Bare MetalCross-compilation ARM, sortie UART, gestionnaires d’interruptionE/S mappées en mémoire, minuterie SysTick, assembleur de démarrage
FreeRTOSTâches, files d’attente, watchdogs, inversion de prioritéOrdonnancement déterministe, protocoles mutex, préemption
DTNRéseau à deux nœuds, liens dégradés, CFDP, routage par graphe de contactBundle Protocol, store-and-forward, fiabilité LTP
IntégrationPipeline de télémétrie complet avec délais à distance de MarsPont 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 :

  1. Deux tâches à des cadences différentes. Multitâche de base.
  2. Communication par files d’attente. Partage sécurisé de données entre tâches.
  3. Minuterie watchdog. Détecter les tâches bloquées et s’en remettre.
  4. Pipeline de capteurs. Quatre capteurs à des cadences différentes alimentant une chaîne de traitement.
  5. 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 :

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 :

  1. tc netem ajoute 5 secondes de latence réseau réelle aux deux conteneurs
  2. 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é :

Ce qui a nécessité de la prudence :

Les Chiffres

MétriqueValeur
Tests totaux50+ assertions dans 7 suites de tests
LangagesC, Python, Bash
MatérielAucun (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.