Claude Codeで宇宙船コンピューティングシミュレーターを構築する

宇宙船のコンピューターがどのように動作するかを理解したかった。リアルタイムスケジューリング、放射線耐性、火星のローバーが地球と通信し続けるための遅延耐性ネットワーク。アルテミスII(50年以上ぶりの有人月面ミッション)が間近に迫る中、今こそ深く掘り下げるべき時だと感じた。そこでシミュレーターを構築した。ゼロから。Claude Codeをペアプログラマーとして。

物理ハードウェアは一切不要。すべてがラップトップ上のQEMUとDockerで動作する。コードは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)"]

なぜこのプロジェクトか

本当に難しいことを学びたかった。概念が馴染みなく、ツールが容赦ないもの。ARMをターゲットにするCコンパイラ。リンカースクリプト。メモリマップドI/O。割り込みベクターテーブル。

Claude Codeがこれを可能にした。すべてのコードを書いてくれたからではなく、実際にリンカースクリプトを書きながらそれが何をするかを説明してくれたから。説明は私が解決しようとしている実際の問題に根ざしていた。volatile、FreeRTOSの優先度プリエンプション、LTP再送タイマーについても同様だった。

ロードマップ

プロジェクトを4つのフェーズに分け、それぞれ前のフェーズの上に構築した:

フェーズ内容主要概念
ベアメタルARMクロスコンパイル、UART出力、割り込みハンドラーメモリマップドI/O、SysTick タイマー、起動アセンブリ
FreeRTOSタスク、キュー、ウォッチドッグ、優先度逆転決定論的スケジューリング、mutexプロトコル、プリエンプション
DTN2ノードネットワーク、劣化リンク、CFDP、コンタクトグラフルーティングBundle Protocol、ストアアンドフォワード、LTP信頼性
統合火星距離遅延を伴う完全テレメトリーパイプラインUARTブリッジ、tc netem、同期されたION OWLT

各フェーズには自動テストがある。すべてのマイルストーンはCIが通過したPRである。

フェーズ1:ベアメタル

最初の課題は何かを動かすことだった。QEMUでCortex-M3(MPS2-AN385)をターゲットにしたARMクロスコンパイル。OS無し、標準ライブラリ無し、printf無し。

Claudeは起動シーケンスを理解するのを助けてくれた:ベクターテーブル、リセットハンドラー、.dataをフラッシュからRAMにコピーすること、.bssをゼロ化すること。main()が実行される前に起こることだ。

最初の成果はUARTコンソールに表示された単一の文字だった:

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

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

2行のC。ライブラリ無し。シリアルポートに配線されたメモリアドレスにバイトを書き込むだけ。マシンと直接話しているような感覚だった。

そこから:SysTickタイマー割り込み、割り込みハンドラー、構造化出力。SysTick デモはハードウェアタイマーを2Hzで発火するように設定し、10ティックを数える:

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.

ティックの欠落無し。余分なティック無し。CPUは割り込みの間に眠り、ハードウェアがちょうど正しい瞬間に起こす。ベアメタルからの決定論的実行。

フェーズ2:FreeRTOS

ベアメタルが動作したところで、FreeRTOSを追加した。医療機器から衛星まであらゆるもので動作するリアルタイムオペレーティングシステムだ。

演習は段階的に構築された:

  1. 異なるレートの2つのタスク。 基本的なマルチタスキング。
  2. キューベースの通信。 タスク間でのデータの安全な共有。
  3. ウォッチドッグタイマー。 ハングしたタスクの検出と回復。
  4. センサーパイプライン。 異なるレートの4つのセンサーが処理チェーンに供給。
  5. 優先度逆転。 古典的なRTOSバグの引き起こしと解決。

センサーパイプラインの起動時の様子。10Hzジャイロがストリームを支配し、1Hz温度読み取りがティック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

これが優先度ベースのプリエンプションの動作だ。ジャイロは優先度4で動作し、温度センサーは優先度1で動作する。温度読み取りはジャイロがCPUを占有していない時のみ通過する。

優先度逆転の演習が最も教育的だった。低優先度タスクが高優先度タスクに必要なmutexを保持し、中優先度タスクが両方を飢餓状態にする3つのタスクを作成した。解決策:優先度継承。FreeRTOSが低優先度タスクを一時的に昇格させてmutexをより速く解放できるようにする。

これは1997年の火星パスファインダーミッションをほぼ終わらせたバグだ。自分でそれを構築することで、教科書の説明がクリックした。

フェーズ3:遅延耐性ネットワーク

TCP/IPは宇宙では機能しない。火星への往復時間は6〜44分に及ぶ。惑星が信号を遮蔽すると、リンクは何時間も切断される。TCPの継続的で低遅延の接続という前提は完全に崩れる。

NASA JPLの答えはDTN(遅延耐性ネットワーク)だ。Bundle Protocolはデータをローカルに保存し、リンクが利用可能になるとホップごとに転送する。インターネットを壊す条件のためにまさに設計されている。

NASAのION実装をDockerで構築し、段階的に難しいテストを実行した:

断続的なリンクテストがDTNの全体像を数行の出力で語る:

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

これがストアアンドフォワードの動作だ。バンドルはリンクが完全に死んでいる間(100%パケットロス)に送信される。ローカルノードのキューで待機する。netemルールをクリアして接続を回復した瞬間、LTPが再送信してバンドルが相手側に届く。TCPとっくに諦めていただろう。

フェーズ4:統合

最終フェーズはすべてを繋げる。FreeRTOSファームウェアがQEMUでテレメトリーを生成する。PythonブリッジスクリプトがUART出力を読み取り、DTNバンドルとして注入する。バンドルは遅延ネットワークを通過して地上局に到達する。

ファームウェアは起動直後にストリーミングを開始する:

# 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

4つの異なるレートの4つのセンサー。10Hzジャイロがほとんどの読み取りを生成し、2Hz太陽センサー、1Hz温度、0.5Hzバッテリーが混在しているのが見える。ブリッジは2秒ごとにこれらをDTNバンドルにまとめる。

火星遅延テストはリアルな条件下でパイプライン全体が動作することを証明する:

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バンドル送信、しかしブリッジが終了した時点で8つのみ配信。残りは5秒の遅延を通過中だった。これはソフトウェアでシミュレートされた遅延ではなく、リアルな遅延だ。

火星遅延シミュレーションは2つの同期されたメカニズムを使用する:

  1. tc netemが両方のコンテナに5秒の実際のネットワーク遅延を追加する
  2. ION範囲テーブルがLTP再送タイマーが正確になるよう同じ5秒の片道光伝播時間を設定する

両方が一致しなければならない。実際の遅延が5秒でIONが1秒だと思っていたら、LTPは積極的に再送信してリンクを溢れさせる。これを正しく設定することで、信頼性に関するどの教科書の章よりもプロトコル設計について多くを学んだ。

Claude Codeとの作業

このプロジェクトはClaudeなしでは数ヶ月かかっていただろう。コードが複雑だからではなく(ほとんどのファイルは300行以下)、各ドメインの学習曲線が急だからだ。

うまくいったこと:

注意が必要だったこと:

数字

メトリック
テスト総数7つのテストスイートで50以上のアサーション
言語C、Python、Bash
ハードウェアなし(QEMU + Docker)
Cのコード行数約1,200行(ファームウェア + ベアメタル)
Pythonのコード行数約1,500行(テスト + ブリッジ + DTNスクリプト)
ION DTN設定6つの.rcファイルで約350行

試してみる

すべてはオープンソースで、QEMUとDockerを持つ任意のLinuxマシンで動作する:

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

物理ハードウェア不要。クラウドサービス不要。ただのラップトップと宇宙船のコンピューターがどう動くかへの好奇心だけ。