# E2E テレコマ動作確認システム 実機フライトソフト / SILS の振る舞いを、**テレコマンドバス (MQTT) 経由で外部から E2E 検証**する仕組みです。 「`RESET` を送ると本当にソフトウェアリセットがかかるか」「`CONTROL,OFF` で制御が止まるか」「`CAPTURE` で画像が降りてくるか」といったシナリオを、コマンド送信と受信テレメトリの観測だけで自動判定します。 実機と SILS は同じ MQTT トピック (`sat/command` / `sat/telemetry` / `sat/image`) と共通テレメトリスキーマ (`shared/shared/telemetry_schema.json`) を喋るため、**同一のシナリオコードを `--target=sils|hardware` で両対象に当てられます**。 ## 構成 ```mermaid flowchart LR subgraph Test[pytest E2E ハーネス ground/e2e/] TC[TelecommandClient
MQTT ラッパ] SC[シナリオテスト群] SC --> TC end TC -- publish sat/command --> B[(MQTT ブローカー
mosquitto)] B -- sat/telemetry / sat/image --> TC B <-- TCP80/81 --> BR[bridge.py] BR <-- WiFi --> HW[実機 Pico W] B <-- 直結 --> SILS[SILS] style HW fill:#fdd style SILS fill:#ddf ``` テストは MQTT ブローカーにだけ依存します。`--target=hardware` では別途 `bridge.py` + 実機が、`--target=sils` では SILS が、同じブローカーにぶら下がっている前提です。 実機 / SILS の差 (リセット時の挙動・所要時間・期待 `reset_reason`) は `e2e/targets.py` の `TargetProfile` に閉じ込め、シナリオ本体は共通化しています。 ## ファイル構成 | ファイル | 役割 | |---------|------| | `e2e/telecommand.py` | `TelecommandClient` — コマンド送信、テレメトリ・画像の subscribe と待機ヘルパ | | `e2e/targets.py` | `TargetProfile` — sils / hardware ごとのタイムアウト・期待値 | | `conftest.py` | `--target` / `--broker-host` オプション、fixtures、マーカー skip | | `tests/test_reset.py` | ソフトウェアリセット試験 (フラッグシップ) | | `tests/test_control.py` | コマンド → テレメトリ反映 (parametrize でケース追加可) | | `tests/test_target_capture.py` | 撮影・画像ダウンリンク | | `tests/test_liveness.py` | テレメトリ疎通・レート・コマンドカウンタ | ## RESET 試験の判定基準 テレメトリの `uptime` / `command_count` / `reset_reason` の変化から、再起動の成立を判定します。 | 指標 | RESET 前 | RESET 後 | 意味 | |------|---------|---------|------| | `uptime` [ms] | 大きい値 | 大幅に低下 | 起動からの経過時間がリセット = 再起動した | | `command_count` | > 0 | ほぼ 0 | コマンドカウンタがリセット | | `reset_reason` | 直前要因 | SOFTWARE/WATCHDOG | リセット要因 (補助判定) | - **主基準**は複合 (`uptime` 低下 + `command_count` リセット)。SILS は接続維持のまま値が即時リセットされ、実機は瞬断後に `bridge.py` が再接続して低 `uptime` で復帰します。どちらも同じ条件式で検出できます。 - `reset_reason` は補助判定です。実機の値は `vreg_and_chip_reset_hw->chip_reset` (`main.cpp`) の生値で、ブリングアップ時に実測して `targets.py` の `HARDWARE.expected_reset_reason` を校正します。未校正でも再起動自体の検証は独立した別テストで担保されます。 ## 実行方法 ### SILS (CI 相当・ローカル) ```bash uv sync --all-packages --dev # 一度だけ全パッケージ同期 cd ground && docker compose up -d mosquitto # MQTT ブローカー uv run --no-sync python -m sils # 別シェルで SILS 起動 uv run --no-sync pytest ground/e2e/tests --target=sils -v ``` `--no-sync` は `uv run` の自動同期がワークスペースの editable (`sils` / `simulator`) を prune して SILS が起動できなくなるのを防ぐため (`uv sync --all-packages --dev` 実施済みが前提)。 ### 実機 (HILS) ```bash # mosquitto + 実機 Pico W 稼働 + bridge.py 起動 の状態で uv run pytest ground/e2e/tests --target=hardware -v ``` ## テストケースの追加と pytest 引数 素の pytest に乗っているため、標準の引数・機能がそのまま使えます。 - **単純なケース** (コマンド → テレメトリ反映) は `tests/test_control.py` の `CASES` 表に 1 行追加するだけで増やせます。 - **複雑なシナリオ**は専用 `test_*.py` に「1 関数 = 1 シナリオ」で追加します。 - **マーカー**で対象を絞れます: `hardware` (実機限定 / sils では自動 skip)、`slow` (RESET 等)、`image` (撮影系)。 - 主な引数: `-k <式>` (名前選択)、`-m <マーカー>` (種別選択)、`-x` / `--maxfail` (失敗で停止)、`-v` / `-s` (出力)、`--target` / `--broker-host` (独自)。 ```bash # 例: 制御系のみ、低速試験を除外して実行 uv run pytest ground/e2e/tests -m "not slow" -k control --target=sils -v ``` ## 既知の課題 / TODO ### 姿勢制御が目標近傍で整定せず自動撮影が発火しない `test_target_triggers_auto_capture` (TARGET → ミッション成功時の自動撮影) は現在 **xfail** です。 - **症状**: 目標角に対し太陽方向は許容誤差 (±10°) 内まで寄るものの、制御がバンバン制御 (固定量 `pulse_delta_us` のキック) のため整定せず、角速度が ±18°/s 規模で振動する**限界サイクル**に入る。`STABLE` 状態かつ `|error| < 許容` かつ `角速度 < 0.5°/s` が同時に成立して `is_target_in_angle` が立つ瞬間が、1Hz のテレメトリ/撮影判定境界と安定して一致しない。結果、`is_control_finished()` を契機とする自動撮影が発火しない。 - **重要**: これは **SILS で再現する不具合**であり、同一の制御ロジックを使う**実機でも発火しない**。したがって hardware 限定にして隠すのではなく、xfail として可視化する。 - **影響箇所 (移植関係・同期修正が必要)**: - `satelite/firmware/control.cpp` の `STABLE` 状態 (実機 FSW) - `satelite/sils/sils/controller.py` の `STABLE` 状態 (SILS 移植) - **修正方針 (いずれか / 組み合わせ)**: 1. `error` に比例してパルス変化量を縮小する比例制御化 (目標近傍でキックを弱める) 2. デッドバンド導入 — 小誤差では `LOTATE` に遷移させない 3. 整定保持 — `|error| < 許容` を一定時間継続したら `is_target_in_angle` を確定する - **完了条件**: 上記修正後、`is_control_finished()` が安定して立つことを確認し、`test_target_triggers_auto_capture` の `@pytest.mark.xfail` を外す。