Construyendo un Simulador de Computación Espacial con Claude Code

Quería entender cómo funcionan las computadoras de las naves espaciales. La planificación en tiempo real, el endurecimiento contra radiación, las redes tolerantes a demoras que mantienen a los rovers de Marte en comunicación con la Tierra. Con Artemis II en el horizonte (la primera misión lunar tripulada en más de 50 años) parecía el momento adecuado para profundizar. Así que construí un simulador. Desde cero. Con Claude Code como mi programador par.

Sin hardware físico. Todo corre en QEMU y Docker en una laptop. Código en 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)"]

Por Qué Este Proyecto

Quería aprender algo genuinamente difícil. Algo donde los conceptos son desconocidos y las herramientas son implacables. Compiladores de C que apuntan a ARM. Scripts de enlazador. E/S mapeada en memoria. Tablas de vectores de interrupción.

Claude Code hizo esto posible. No porque escribiera todo el código, sino porque explicó qué hace un script de enlazador mientras estábamos escribiendo uno, de modo que la explicación estaba anclada en el problema real que estaba resolviendo. Lo mismo para volatile, la apropiación preventiva por prioridades de FreeRTOS y los temporizadores de retransmisión de LTP.

La Hoja de Ruta

Dividí el proyecto en cuatro fases, cada una construyendo sobre la anterior:

FaseQuéConceptos Clave
Bare MetalCompilación cruzada ARM, salida UART, manejadores de interrupciónE/S mapeada en memoria, temporizador SysTick, ensamblador de inicio
FreeRTOSTareas, colas, watchdogs, inversión de prioridadesPlanificación determinista, protocolos mutex, apropiación preventiva
DTNRed de dos nodos, enlaces degradados, CFDP, enrutamiento por grafo de contactoBundle Protocol, almacenar y reenviar, confiabilidad LTP
IntegraciónPipeline de telemetría completo con retrasos a distancia de MartePuente UART, tc netem, ION OWLT sincronizado

Cada fase tiene pruebas automatizadas. Cada hito es un PR con CI aprobado.

Fase 1: Bare Metal

El primer desafío fue conseguir que algo funcionara. Compilación cruzada ARM apuntando a un Cortex-M3 (MPS2-AN385) en QEMU. Sin SO, sin biblioteca estándar, sin printf.

Claude me ayudó a entender la secuencia de inicio: la tabla de vectores, el manejador de reset, copiar .data de flash a RAM, poner a cero .bss. Cosas que suceden antes de que main() siquiera se ejecute.

La primera victoria fue un solo carácter apareciendo en una consola UART:

#define UART0_DR  (*(volatile uint32_t *)0x40004000)

void uart_putc(char c) {
    UART0_DR = c;
}

Dos líneas de C. Sin bibliotecas. Solo escribir un byte en una dirección de memoria que resulta estar conectada a un puerto serie. Se sentía como hablar directamente con la máquina.

Desde allí: interrupciones del temporizador SysTick, manejadores de interrupción, salida estructurada. La demo de SysTick configura un temporizador de hardware para disparar a 2 Hz y cuenta 10 tics:

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.

Sin tics perdidos. Sin tics extra. La CPU duerme entre interrupciones y el hardware la despierta en exactamente el momento correcto. Ejecución determinista desde bare metal.

Fase 2: FreeRTOS

Con bare metal funcionando, agregué FreeRTOS, un sistema operativo en tiempo real que corre en todo, desde dispositivos médicos hasta satélites.

Los ejercicios se construyeron progresivamente:

  1. Dos tareas a diferentes ritmos. Multitarea básica.
  2. Comunicación basada en colas. Compartir datos entre tareas de forma segura.
  3. Temporizador watchdog. Detectar y recuperarse de tareas bloqueadas.
  4. Pipeline de sensores. Cuatro sensores a diferentes ritmos alimentando una cadena de procesamiento.
  5. Inversión de prioridades. Desencadenar y resolver el bug clásico de RTOS.

Aquí está el pipeline de sensores arrancando. Nótese el giroscopio a 10 Hz dominando el flujo, con la lectura de temperatura a 1 Hz apareciendo en el tic 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

Eso es la apropiación preventiva basada en prioridades en acción. El giroscopio corre a prioridad 4, el sensor de temperatura a prioridad 1. La lectura de temperatura solo pasa cuando el giroscopio no está ocupando la CPU.

El ejercicio de inversión de prioridades fue el más instructivo. Creé tres tareas donde una tarea de baja prioridad sostiene un mutex que una tarea de alta prioridad necesita, y una tarea de prioridad media muere de inanición junto a ambas. La solución: herencia de prioridades, donde FreeRTOS temporalmente eleva la tarea de baja prioridad para que pueda liberar el mutex más rápido.

Este es el bug que casi mató la misión Mars Pathfinder en 1997. Construirlo yo mismo hizo que la explicación del libro de texto encajara.

Fase 3: Redes Tolerantes a Demoras

TCP/IP no funciona en el espacio. Los tiempos de ida y vuelta a Marte van de 6 a 44 minutos. Los enlaces caen durante horas cuando los planetas ocluyen la señal. La suposición de TCP de una conexión continua y de baja latencia se rompe completamente.

La respuesta de NASA JPL es DTN, o Redes Tolerantes a Demoras. El Bundle Protocol almacena datos localmente y los reenvía salto a salto cuando los enlaces están disponibles. Está diseñado exactamente para las condiciones que rompen internet.

Construí la implementación ION de NASA en Docker y ejecuté pruebas progresivamente más difíciles:

La prueba de enlace intermitente cuenta toda la historia de DTN en pocas líneas de salida:

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

Eso es almacenar y reenviar en acción. El bundle se envía mientras el enlace está completamente muerto, 100% de pérdida de paquetes. Se queda en la cola del nodo local. En el momento en que limpiamos la regla netem y restauramos la conectividad, LTP retransmite y el bundle llega al otro extremo. TCP se habría rendido hace mucho.

Fase 4: Integración

La fase final conecta todo. El firmware FreeRTOS genera telemetría en QEMU. Un script puente en Python lee la salida UART e inyecta como bundles DTN. Los bundles atraviesan una red con demoras para llegar a una estación terrestre.

El firmware arranca e inmediatamente comienza a transmitir:

# 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

Cuatro sensores a cuatro ritmos diferentes. Se puede ver el giroscopio a 10 Hz produciendo la mayoría de las lecturas, con el sensor solar a 2 Hz, temperatura a 1 Hz y batería a 0.5 Hz intercalados. El puente agrupa estos en bundles DTN cada 2 segundos.

La prueba de retraso de Marte prueba que todo el pipeline funciona bajo condiciones realistas:

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 enviados, pero solo 8 entregados para cuando el puente salió. El resto todavía estaba en tránsito a través del retraso de 5 segundos. Ese es el retraso siendo real, no simulado en software.

La simulación de retraso de Marte usa dos mecanismos sincronizados:

  1. tc netem agrega 5 segundos de latencia de red real a ambos contenedores
  2. Tablas de rango ION establecen el mismo tiempo de luz unidireccional de 5 segundos para que los temporizadores de retransmisión LTP sean correctos

Ambos deben coincidir. Si el retraso real es de 5 segundos pero ION cree que es de 1 segundo, LTP retransmite agresivamente e inunda el enlace. Conseguir esto correcto me enseñó más sobre diseño de protocolos que cualquier capítulo de libro de texto sobre confiabilidad.

Trabajando con Claude Code

Este proyecto me habría tomado meses sin Claude. No porque el código sea complejo (la mayoría de los archivos tienen menos de 300 líneas) sino porque la curva de aprendizaje para cada dominio es empinada.

Lo que funcionó bien:

Lo que requirió cuidado:

Los Números

MétricaValor
Total de pruebas50+ aserciones en 7 suites de prueba
LenguajesC, Python, Bash
HardwareNinguno (QEMU + Docker)
Líneas de C~1,200 (firmware + bare metal)
Líneas de Python~1,500 (pruebas + puente + scripts DTN)
Config ION DTN~350 líneas en 6 archivos .rc

Pruébalo

Todo es código abierto y corre en cualquier máquina Linux con QEMU y 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

Sin hardware físico. Sin servicios en la nube. Solo una laptop y curiosidad sobre cómo funcionan las computadoras de las naves espaciales.