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プロトコル、プリエンプション |
| DTN | 2ノードネットワーク、劣化リンク、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を追加した。医療機器から衛星まであらゆるもので動作するリアルタイムオペレーティングシステムだ。
演習は段階的に構築された:
- 異なるレートの2つのタスク。 基本的なマルチタスキング。
- キューベースの通信。 タスク間でのデータの安全な共有。
- ウォッチドッグタイマー。 ハングしたタスクの検出と回復。
- センサーパイプライン。 異なるレートの4つのセンサーが処理チェーンに供給。
- 優先度逆転。 古典的な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で構築し、段階的に難しいテストを実行した:
- 基本的な接続性。 2つのノードがバンドルを交換。
- 劣化リンク。
tc netemが500msの遅延、25%のパケットロス、完全な停止を追加。 - CFDPファイル転送。 整合性チェック付きの信頼できるファイル配信。
- コンタクトグラフルーティング。 リンクギャップ中にキューに入れられたバンドルが、ウィンドウが開くと配信される。
断続的なリンクテストが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つの同期されたメカニズムを使用する:
- tc netemが両方のコンテナに5秒の実際のネットワーク遅延を追加する
- ION範囲テーブルがLTP再送タイマーが正確になるよう同じ5秒の片道光伝播時間を設定する
両方が一致しなければならない。実際の遅延が5秒でIONが1秒だと思っていたら、LTPは積極的に再送信してリンクを溢れさせる。これを正しく設定することで、信頼性に関するどの教科書の章よりもプロトコル設計について多くを学んだ。
Claude Codeとの作業
このプロジェクトはClaudeなしでは数ヶ月かかっていただろう。コードが複雑だからではなく(ほとんどのファイルは300行以下)、各ドメインの学習曲線が急だからだ。
うまくいったこと:
- クロスドメイン知識。 プロジェクトはC、Python、ARMアセンブリ、Docker、ION DTN、FreeRTOS、QEMU、tc netem、LTPにまたがる。これらすべてをよく知る人はいない。Claudeはそれらの間を切り替えることができた。
- テスト駆動のマイルストーン。 各フェーズには自動テストがある。Claudeは「コンパイルが通る」だけでなく、実際の動作を検証するアサーションを書くのを助けた。
- PRベースのワークフロー。 各マイルストーンは別々のブランチとCIを持つPR。Claude Codeのworktreeサポートがこれをクリーンに保った。孤立したブランチ、偶発的な相互汚染なし。
注意が必要だったこと:
- 組み込みCは容赦ない。 リンカースクリプトのオフバイワンエラーはスタックトレースを与えない。ハードフォルトやサイレントな破損を引き起こす。Claudeの提案はハードウェアドキュメントと照らし合わせて慎重に検証する必要があった。
- ION DTNのドキュメントは乏しい。 Claudeのトレーニングデータには深いION内部は含まれていない。ION固有の動作についてはClaudeの説明よりもIONソースコードと設定例に頼った。
数字
| メトリック | 値 |
|---|---|
| テスト総数 | 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
物理ハードウェア不要。クラウドサービス不要。ただのラップトップと宇宙船のコンピューターがどう動くかへの好奇心だけ。