コンテンツにスキップ

StampFly Simulator

Note: English version follows after the Japanese section. / 日本語の後に英語版があります。

StampFly用のPythonベースフライトシミュレータ。物理エンジン、センサモデル、制御システム、HILSテスト機能を備えた教育・研究プラットフォーム。

シミュレータの選択

シミュレータ 特徴 用途
VPython版vpython/ 軽量、ブラウザ3D表示、センサモデル充実 制御学習、SILS/HILS
Genesis版genesis/ 高精度物理エンジン、2000Hz物理演算、物理量ベース制御 高精度シミュレーション
# VPythonシミュレータを起動
sf sim run vpython

# Genesisシミュレータを起動
sf sim run genesis

1. 概要

このシミュレータについて(VPython版)

StampFly Simulatorは、マイクロドローン「StampFly」の動力学をPythonでシミュレートするツールです。実機ファームウェア(firmware/vehicle/)と互換性のある制御アルゴリズムを実装しており、以下の用途に使用できます:

  • 制御アルゴリズムの開発・検証: 安全な環境でPIDゲインの調整やフライトモードの実装
  • センサフュージョンの研究: リアルなノイズモデルを持つセンサシミュレーション
  • 教育: ドローン動力学と制御理論の可視化学習
  • HILSテスト: 実機ファームウェアとシミュレータの連携テスト

機体構成

               Front
          FL (M4)   FR (M1)
             ╲   ▲   ╱
              ╲  │  ╱       CW: M2, M4
               ╲ │ ╱       CCW: M1, M3
                ╲│╱
                 ╳         ← Center of Mass
                ╱│╲
               ╱ │ ╲
              ╱  │  ╲
             ╱   │   ╲
          RL (M3)    RR (M2)
                Rear

機体パラメータ

パラメータ 単位
質量 35 g
慣性モーメント Ixx 9.16e-6 kg·m²
慣性モーメント Iyy 13.3e-6 kg·m²
慣性モーメント Izz 20.4e-6 kg·m²
バッテリー電圧 3.7 V

2. セットアップ

必要環境

  • Python 3.10〜3.12(推奨3.12)
  • pip(パッケージマネージャ)

インストール

# リポジトリのルートディレクトリで実行
cd stampfly_ecosystem/simulator

# 依存パッケージのインストール
pip install -r requirements.txt

依存パッケージ

パッケージ 用途 必須
numpy 数値計算
matplotlib グラフ描画
vpython 3D可視化
numpy-stl STLモデル読み込み
Pillow 画像処理
opencv-python 画像処理(オプティカルフロー)
hid ジョイスティック入力
pyserial HILS通信

注意: hidパッケージはシステムレベルのHIDライブラリが必要です。

# macOS
brew install hidapi

# Ubuntu/Debian
sudo apt-get install libhidapi-hidraw0

3. ディレクトリ構成

simulator/
├── README.md              # 本ドキュメント
├── requirements.txt       # 依存パッケージ
├── vpython/               # VPython版シミュレータ
│   ├── core/              # 物理エンジン
│   │   ├── physics.py     # 剛体力学(RigidBody)
│   │   ├── dynamics.py    # マルチコプタ動力学
│   │   └── motors.py      # モーター・プロペラモデル
│   ├── sensors/           # センサモデル
│   │   ├── imu.py         # BMI270 6軸IMU
│   │   ├── barometer.py   # BMP280 気圧センサ
│   │   └── ...            # 他センサ
│   ├── control/           # 制御システム
│   │   ├── pid.py         # 不完全微分フィルタ付きPID
│   │   └── ...            # 制御器
│   ├── interfaces/        # 外部インターフェース
│   ├── visualization/     # VPython 3Dレンダラ
│   └── scripts/           # 実行スクリプト
│       └── run_sim.py     # メインシミュレータ
├── genesis/               # Genesis版シミュレータ
│   ├── scripts/           # 実行スクリプト
│   │   └── run_genesis_sim.py
│   ├── motor_model.py     # モーターモデル
│   └── control_allocation.py  # 制御配分
├── shared/                # 共有リソース
│   ├── assets/            # 3Dモデル・テクスチャ
│   ├── configs/           # 設定ファイル
│   └── scenarios/         # シナリオ定義
├── sandbox/               # 実験用ツール
├── tools/                 # ユーティリティ
├── tests/                 # テスト
└── output/                # 出力ファイル

4. 基本的な使い方

クイックスタート(初めての方へ)

コントローラがあれば、すぐにドローン操縦を体験できます!

Step 1: コントローラの準備

  1. AtomS3 + Atom JoyStickにファームウェアを書き込む(controller/README.md参照)
  2. コントローラの電源を入れる
  3. 画面(ボタン)を押してメニューを開き、右スティックで「Comm: ESP-NOW」の行を選ぶ → 右ボタンを 2 回押して「Comm: USB HID」にする(自動で再起動)
  4. PCにUSB接続(ゲームパッドとして認識される)

Step 2: シミュレータの起動

# sf CLIを使用(推奨)
sf sim run vpython

# または直接起動
cd stampfly_ecosystem/simulator/vpython/scripts
python run_sim.py

ブラウザが開き、3Dビューが表示されます。スティックを操作してドローンを飛ばしましょう!

sf CLIコマンド

# VPythonシミュレータを起動(デフォルト)
sf sim run

# Genesisシミュレータを起動
sf sim run genesis

# 利用可能なシミュレータを一覧
sf sim list

# ヘッドレスモード(テスト用)
sf sim headless -d 30

直接起動オプション

# 基本起動(ボクセルワールド、ランダム地形)
python vpython/scripts/run_sim.py

# リングワールド(円形コース)で起動
python vpython/scripts/run_sim.py --world ringworld

# シード値を指定して同じ地形を再現
python vpython/scripts/run_sim.py --seed 12345
オプション 短縮形 説明 デフォルト
--world -w ワールドタイプ(voxel/ringworld) voxel
--seed -s 地形生成シード値(省略時ランダム) なし

ワールドタイプ

タイプ 説明
voxel ボクセルベースのランダム地形。毎回異なる地形で練習できます
ringworld 円形のレースコース。周回飛行の練習に最適

ジョイスティック操作

AtomS3 + Atom JoyStickをUSB HIDモードで使用:

入力 機能 Mode 2 Mode 3
スロットル 上昇/下降 左Y 右Y
ロール 左右移動 右X 左X
ピッチ 前後移動 右Y 左Y
ヨー 旋回 左X 右X

ヒント: スティックがドリフトする場合は、コントローラのメニューから「Calibration」を実行してください。デッドバンドは「Deadband」から0-5%で調整できます。

コード例:基本的な物理シミュレーション

import numpy as np
from simulator.core import multicopter

# 機体の作成
mass = 0.037  # 37g
inertia = [
    [9.16e-6, 0.0, 0.0],
    [0.0, 13.3e-6, 0.0],
    [0.0, 0.0, 20.4e-6]
]
drone = multicopter(mass=mass, inersia=inertia)

# 初期状態の設定
drone.set_pqr([[0.0], [0.0], [0.0]])  # 角速度 [rad/s]
drone.set_uvw([[0.0], [0.0], [0.0]])  # 速度 [m/s]

# ホバリング電圧の計算
weight = mass * 9.81
hover_voltage = drone.motor_prop[0].equilibrium_voltage(weight / 4)

# シミュレーションループ
dt = 0.001  # 1ms
for _ in range(1000):
    voltage = [hover_voltage] * 4
    drone.step(voltage, dt)

    # 状態の取得
    position = drone.body.position
    euler = drone.body.euler
    print(f"Position: {position.T}, Euler: {euler.T}")

コード例:センサシミュレーション

import numpy as np
from simulator.sensors import IMU, Barometer, Magnetometer

# センサの作成
imu = IMU(sample_rate=400)  # 400Hz
baro = Barometer(sample_rate=50)  # 50Hz
mag = Magnetometer(sample_rate=100)  # 100Hz

# 真値からセンサ出力を生成
true_accel = np.array([0.0, 0.0, -9.81])  # m/s²
true_gyro = np.array([0.0, 0.0, 0.0])     # rad/s
true_altitude = 1.5  # m

# IMU出力(ノイズ付き)
gyro_meas, accel_meas = imu.update(true_gyro, true_accel, dt=0.0025)

# 気圧計出力
pressure, temperature = baro.update(true_altitude, dt=0.02)

# 地磁気センサ出力
mag_field = mag.update(attitude=np.array([0, 0, 0]), dt=0.01)

コード例:制御システム

import numpy as np
from simulator.control import RateController, MotorMixer

# 制御器の作成(ファームウェア互換ゲイン)
rate_ctrl = RateController()
mixer = MotorMixer()

# スティック入力からモーター出力を計算
gyro = np.array([0.1, -0.05, 0.02])  # 現在の角速度
throttle = 0.5

roll_out, pitch_out, yaw_out = rate_ctrl.update_from_stick(
    stick_roll=0.3,
    stick_pitch=-0.2,
    stick_yaw=0.1,
    gyro=gyro,
    dt=0.0025
)

motors = mixer.mix(throttle, roll_out, pitch_out, yaw_out)
print(f"Motor outputs: {motors}")  # [0.0, 1.0] の範囲

5. 制御モード

Rate(Acro)モード

角速度を直接制御するモード。スティック入力が目標角速度にマッピングされます。

from simulator.control import RateController

ctrl = RateController()

# スティック入力 [-1, 1] → 目標レート
# max_rate: Roll=220°/s, Pitch=220°/s, Yaw=200°/s
roll_out, pitch_out, yaw_out = ctrl.update_from_stick(
    stick_roll=0.5,    # 110°/s 目標
    stick_pitch=0.0,
    stick_yaw=0.0,
    gyro=current_gyro,
    dt=0.0025
)

Angle(Stabilize)モード

姿勢角を制御するモード。セルフレベリング機能付き。

from simulator.control import AttitudeController

ctrl = AttitudeController()

# スティック入力 [-1, 1] → 目標角度
# max_angle: Roll=30°, Pitch=30°
roll_out, pitch_out, yaw_out = ctrl.update(
    stick_roll=0.5,    # 15° 目標
    stick_pitch=0.0,
    stick_yaw=0.0,
    attitude=current_attitude,  # [roll, pitch, yaw]
    gyro=current_gyro,
    dt=0.0025
)

Altitude Holdモード

高度を維持するモード。

from simulator.control import AltitudeController

ctrl = AltitudeController(hover_throttle=0.5)

# 目標高度の設定
ctrl.set_altitude(1.5)  # 1.5m

# 更新
throttle = ctrl.update(
    current_altitude=1.2,
    vertical_velocity=0.1,
    stick_throttle=0.5,
    dt=0.02
)

6. SILS/HILSテスト

SILS(Software-in-the-Loop)

Pythonで実装した制御器をシミュレータ内で実行:

from simulator.interfaces import SILSInterface, SensorData

sils = SILSInterface()

# 制御器の更新
sensor_data = SensorData(
    gyro=np.array([0.0, 0.0, 0.0]),
    accel=np.array([0.0, 0.0, -9.81]),
    mag=np.array([20.0, 0.0, 45.0]),
    pressure=101325.0,
    altitude=0.0,
)

commands = sils.update(sensor_data, dt=0.0025)
motor_outputs = commands.motors

HILS(Hardware In the Loop Simulation)

実機ファームウェアとシリアル通信でセンサデータを注入:

注(2026-08-20): 以下はPython側インターフェースの実装例。firmware/vehiclefirmware/controller にはHILSの受信処理が実装されておらず、呼び出し元も無いため、実機とは現状接続できない。詳細は docs/plans/archive/hils-firmware.md を参照。

from simulator.interfaces import HILSInterface, HILSSimulationRunner

# HILSインターフェースの作成
hils = HILSInterface()
hils.connect(port="/dev/tty.usbserial-XXX", baudrate=921600)

# シミュレータからセンサデータを注入
hils.inject_imu(
    timestamp_us=1000,
    gyro=np.array([0.0, 0.0, 0.0]),
    accel=np.array([0.0, 0.0, -9.81])
)

# ファームウェアからモーター出力を取得
motor_output = hils.get_motor_output(timeout=0.01)
if motor_output:
    print(f"Motors: {motor_output.motors}")

7. プロトコル

シミュレータは protocol/spec/messages.yaml で定義されたメッセージ形式をサポート:

メッセージ サイズ 方向 用途
ControlPacket 14 bytes Controller→Vehicle スティック入力
TelemetryPacket 22 bytes Vehicle→Controller ESP-NOWテレメトリ
TelemetryWSPacket 108 bytes Vehicle→GCS WebSocketテレメトリ

バイナリログ

from simulator.interfaces import BinaryLogger, BinaryLogReader

# ログの書き込み
logger = BinaryLogger("flight_log.bin")
logger.write_control(control_packet)
logger.write_telemetry(telemetry_packet)
logger.close()

# ログの読み込み
reader = BinaryLogReader("flight_log.bin")
for packet_type, packet in reader:
    print(f"{packet_type}: {packet}")

8. テストの実行

cd stampfly_ecosystem/simulator/scripts

# センサモデルのテスト
python test_sensors.py

# プロトコルのテスト
python test_protocol.py

# 制御システムのテスト
python test_control.py

# センサ特性の可視化
python visualize_sensors.py

9. トラブルシューティング

VPythonが起動しない

# Jupyter環境では以下を試す
jupyter nbextension enable --py vpython

# または、ブラウザで直接開く
python -c "from vpython import *; sphere()"

ジョイスティックが認識されない

  1. HIDAPIライブラリがインストールされているか確認
  2. ジョイスティックがUSB接続されているか確認
  3. 他のアプリケーションがデバイスを占有していないか確認
import hid
for device in hid.enumerate():
    print(f"VID: {device['vendor_id']:04x}, PID: {device['product_id']:04x}")

HILS通信エラー

注(2026-08-20): ファームウェア側にHILSの受信処理が未実装のため、以下の手順を試しても接続はできない。実装状況は 6. SILS/HILSテスト を参照。

  1. シリアルポートが正しいか確認
  2. ボーレート(921600)が一致しているか確認
  3. ファームウェアがHILSモードで起動しているか確認

10. 参考資料

リソース 説明
MIGRATION_PLAN.md 移植計画(開発者向け)
stampfly_sim オリジナルシミュレータ
firmware/vehicle/ 実機ファームウェア
protocol/spec/ プロトコル定義

Simulator Selection

Simulator Features Use Cases
VPython version (this directory) Lightweight, browser 3D, rich sensor models Control learning, SILS/HILS
Genesis version (sandbox/genesis_sim/) High-precision physics, 2000Hz, physical units High-fidelity simulation

1. Overview

About This Simulator (VPython Version)

StampFly Simulator is a Python-based tool for simulating the dynamics of the "StampFly" micro drone. It implements control algorithms compatible with the actual firmware (firmware/vehicle/) and can be used for:

  • Control Algorithm Development: Safely tune PID gains and implement flight modes
  • Sensor Fusion Research: Sensor simulation with realistic noise models
  • Education: Visual learning of drone dynamics and control theory
  • HILS Testing: Integration testing between firmware and simulator

Vehicle Configuration

               Front
          FL (M4)   FR (M1)
             ╲   ▲   ╱
              ╲  │  ╱       CW: M2, M4
               ╲ │ ╱       CCW: M1, M3
                ╲│╱
                 ╳         ← Center of Mass
                ╱│╲
               ╱ │ ╲
              ╱  │  ╲
             ╱   │   ╲
          RL (M3)    RR (M2)
                Rear

Vehicle Parameters

Parameter Value Unit
Mass 35 g
Inertia Ixx 9.16e-6 kg·m²
Inertia Iyy 13.3e-6 kg·m²
Inertia Izz 20.4e-6 kg·m²
Battery Voltage 3.7 V

2. Setup

Requirements

  • Python 3.10-3.12 (3.12 recommended)
  • pip (package manager)

Installation

# Run from the repository root
cd stampfly_ecosystem/simulator

# Install dependencies
pip install -r requirements.txt

Dependencies

Package Purpose Required
numpy Numerical computation
matplotlib Plotting
vpython 3D visualization
numpy-stl STL model loading
Pillow Image processing
opencv-python Image processing (optical flow)
hid Joystick input
pyserial HILS communication

Note: The hid package requires system-level HID libraries.

# macOS
brew install hidapi

# Ubuntu/Debian
sudo apt-get install libhidapi-hidraw0

3. Directory Structure

simulator/
├── README.md              # This document
├── MIGRATION_PLAN.md      # Migration plan (for developers)
├── requirements.txt       # Dependencies
├── __init__.py
├── core/                  # Physics engine
│   ├── physics.py         # Rigid body dynamics
│   ├── dynamics.py        # Multicopter dynamics
│   ├── aerodynamics.py    # Aerodynamics
│   ├── motors.py          # Motor/propeller model
│   └── battery.py         # Battery model
├── sensors/               # Sensor models
│   ├── imu.py             # BMI270 6-axis IMU
│   ├── magnetometer.py    # BMM150 magnetometer
│   ├── barometer.py       # BMP280 barometer
│   ├── tof.py             # VL53L3CX ToF sensor
│   ├── opticalflow.py     # PMW3901 optical flow
│   ├── power_monitor.py   # INA3221 power monitor
│   └── noise_models.py    # Allan variance-based noise
├── control/               # Control system
│   ├── pid.py             # PID with incomplete derivative filter
│   ├── rate_controller.py # Rate (angular velocity) control
│   ├── attitude_controller.py  # Attitude/altitude control
│   └── motor_mixer.py     # X-quad motor mixer
├── interfaces/            # External interfaces
│   ├── joystick.py        # HID joystick
│   ├── messages.py        # Protocol messages
│   ├── protocol_bridge.py # Simulator state ↔ protocol conversion
│   ├── sils_interface.py   # SILS interface
│   └── hils_interface.py  # HILS interface (Python side only; firmware not implemented)
├── visualization/         # Visualization
│   └── vpython_backend.py # VPython 3D renderer
├── assets/                # Resources
│   ├── meshes/            # 3D models (STL)
│   ├── textures/          # Texture images
│   └── loaders/           # File loaders
├── scripts/               # Execution scripts
│   ├── run_sim.py         # Main simulator
│   ├── sandbox.py         # Development testing
│   ├── test_sensors.py    # Sensor model tests
│   ├── test_protocol.py   # Protocol tests
│   ├── test_control.py    # Control system tests
│   └── visualize_sensors.py  # Sensor visualization
└── output/                # Output files (.gitignore'd)

4. Basic Usage

Quick Start (For Beginners)

If you have a controller, you can experience drone piloting right away!

Step 1: Prepare the Controller

  1. Flash firmware to AtomS3 + Atom JoyStick (see controller/README.md)
  2. Power on the controller
  3. Press the screen (button) to open the menu, select the "Comm: ESP-NOW" row with the right stick → press the right button twice to reach "Comm: USB HID" (restarts automatically)
  4. Connect to PC via USB (recognized as a gamepad)

Step 2: Launch the Simulator

cd stampfly_ecosystem/simulator/scripts
python run_sim.py

A browser opens showing the 3D view. Use the sticks to fly the drone!

Command Line Options

# Basic launch (voxel world, random terrain)
python run_sim.py

# Launch with ring world (circular course)
python run_sim.py --world ringworld

# Specify seed for reproducible terrain
python run_sim.py --seed 12345

# Ring world with specific seed
python run_sim.py --world ringworld --seed 42
Option Short Description Default
--world -w World type (voxel/ringworld) voxel
--seed -s Terrain generation seed (random if omitted) None

World Types

Type Description
voxel Voxel-based random terrain. Practice with different terrain each time
ringworld Circular race course. Great for circuit flying practice

Joystick Controls

Using AtomS3 + Atom JoyStick in USB HID mode:

Input Function Mode 2 Mode 3
Throttle Ascend/Descend Left Y Right Y
Roll Move left/right Right X Left X
Pitch Move forward/back Right Y Left Y
Yaw Rotate Left X Right X

Tip: If sticks are drifting, run "Calibration" from the controller menu. Deadband can be adjusted from 0-5% in the "Deadband" menu.

Code Example: Basic Physics Simulation

import numpy as np
from simulator.core import multicopter

# Create vehicle
mass = 0.037  # 37g
inertia = [
    [9.16e-6, 0.0, 0.0],
    [0.0, 13.3e-6, 0.0],
    [0.0, 0.0, 20.4e-6]
]
drone = multicopter(mass=mass, inersia=inertia)

# Set initial state
drone.set_pqr([[0.0], [0.0], [0.0]])  # Angular velocity [rad/s]
drone.set_uvw([[0.0], [0.0], [0.0]])  # Velocity [m/s]

# Calculate hover voltage
weight = mass * 9.81
hover_voltage = drone.motor_prop[0].equilibrium_voltage(weight / 4)

# Simulation loop
dt = 0.001  # 1ms
for _ in range(1000):
    voltage = [hover_voltage] * 4
    drone.step(voltage, dt)

    # Get state
    position = drone.body.position
    euler = drone.body.euler
    print(f"Position: {position.T}, Euler: {euler.T}")

Code Example: Sensor Simulation

import numpy as np
from simulator.sensors import IMU, Barometer, Magnetometer

# Create sensors
imu = IMU(sample_rate=400)  # 400Hz
baro = Barometer(sample_rate=50)  # 50Hz
mag = Magnetometer(sample_rate=100)  # 100Hz

# Generate sensor output from true values
true_accel = np.array([0.0, 0.0, -9.81])  # m/s²
true_gyro = np.array([0.0, 0.0, 0.0])     # rad/s
true_altitude = 1.5  # m

# IMU output (with noise)
gyro_meas, accel_meas = imu.update(true_gyro, true_accel, dt=0.0025)

# Barometer output
pressure, temperature = baro.update(true_altitude, dt=0.02)

# Magnetometer output
mag_field = mag.update(attitude=np.array([0, 0, 0]), dt=0.01)

Code Example: Control System

import numpy as np
from simulator.control import RateController, MotorMixer

# Create controllers (firmware-compatible gains)
rate_ctrl = RateController()
mixer = MotorMixer()

# Calculate motor output from stick input
gyro = np.array([0.1, -0.05, 0.02])  # Current angular velocity
throttle = 0.5

roll_out, pitch_out, yaw_out = rate_ctrl.update_from_stick(
    stick_roll=0.3,
    stick_pitch=-0.2,
    stick_yaw=0.1,
    gyro=gyro,
    dt=0.0025
)

motors = mixer.mix(throttle, roll_out, pitch_out, yaw_out)
print(f"Motor outputs: {motors}")  # Range [0.0, 1.0]

5. Control Modes

Rate (Acro) Mode

Direct angular velocity control. Stick input maps to target angular rates.

from simulator.control import RateController

ctrl = RateController()

# Stick input [-1, 1] → Target rate
# max_rate: Roll=220°/s, Pitch=220°/s, Yaw=200°/s
roll_out, pitch_out, yaw_out = ctrl.update_from_stick(
    stick_roll=0.5,    # 110°/s target
    stick_pitch=0.0,
    stick_yaw=0.0,
    gyro=current_gyro,
    dt=0.0025
)

Angle (Stabilize) Mode

Attitude angle control with self-leveling.

from simulator.control import AttitudeController

ctrl = AttitudeController()

# Stick input [-1, 1] → Target angle
# max_angle: Roll=30°, Pitch=30°
roll_out, pitch_out, yaw_out = ctrl.update(
    stick_roll=0.5,    # 15° target
    stick_pitch=0.0,
    stick_yaw=0.0,
    attitude=current_attitude,  # [roll, pitch, yaw]
    gyro=current_gyro,
    dt=0.0025
)

Altitude Hold Mode

Maintains altitude.

from simulator.control import AltitudeController

ctrl = AltitudeController(hover_throttle=0.5)

# Set target altitude
ctrl.set_altitude(1.5)  # 1.5m

# Update
throttle = ctrl.update(
    current_altitude=1.2,
    vertical_velocity=0.1,
    stick_throttle=0.5,
    dt=0.02
)

6. SILS/HILS Testing

SILS (Software-in-the-Loop)

Run Python-implemented controllers within the simulator:

from simulator.interfaces import SILSInterface, SensorData

sils = SILSInterface()

# Update controller
sensor_data = SensorData(
    gyro=np.array([0.0, 0.0, 0.0]),
    accel=np.array([0.0, 0.0, -9.81]),
    mag=np.array([20.0, 0.0, 45.0]),
    pressure=101325.0,
    altitude=0.0,
)

commands = sils.update(sensor_data, dt=0.0025)
motor_outputs = commands.motors

HILS (Hardware In the Loop Simulation)

Inject sensor data to real firmware via serial communication:

Note (2026-08-20): The example below shows the Python-side interface only. Neither firmware/vehicle nor firmware/controller implements the HILS receiver, and nothing currently calls this interface, so it cannot connect to real hardware yet. See docs/plans/archive/hils-firmware.md for details.

from simulator.interfaces import HILSInterface, HILSSimulationRunner

# Create HILS interface
hils = HILSInterface()
hils.connect(port="/dev/tty.usbserial-XXX", baudrate=921600)

# Inject sensor data from simulator
hils.inject_imu(
    timestamp_us=1000,
    gyro=np.array([0.0, 0.0, 0.0]),
    accel=np.array([0.0, 0.0, -9.81])
)

# Get motor output from firmware
motor_output = hils.get_motor_output(timeout=0.01)
if motor_output:
    print(f"Motors: {motor_output.motors}")

7. Protocol

The simulator supports message formats defined in protocol/spec/messages.yaml:

Message Size Direction Purpose
ControlPacket 14 bytes Controller→Vehicle Stick input
TelemetryPacket 22 bytes Vehicle→Controller ESP-NOW telemetry
TelemetryWSPacket 108 bytes Vehicle→GCS WebSocket telemetry

Binary Logging

from simulator.interfaces import BinaryLogger, BinaryLogReader

# Write log
logger = BinaryLogger("flight_log.bin")
logger.write_control(control_packet)
logger.write_telemetry(telemetry_packet)
logger.close()

# Read log
reader = BinaryLogReader("flight_log.bin")
for packet_type, packet in reader:
    print(f"{packet_type}: {packet}")

8. Running Tests

cd stampfly_ecosystem/simulator/scripts

# Sensor model tests
python test_sensors.py

# Protocol tests
python test_protocol.py

# Control system tests
python test_control.py

# Sensor visualization
python visualize_sensors.py

9. Troubleshooting

VPython Won't Start

# In Jupyter environment, try:
jupyter nbextension enable --py vpython

# Or open directly in browser:
python -c "from vpython import *; sphere()"

Joystick Not Recognized

  1. Check if HIDAPI library is installed
  2. Check if joystick is connected via USB
  3. Check if another application is using the device
import hid
for device in hid.enumerate():
    print(f"VID: {device['vendor_id']:04x}, PID: {device['product_id']:04x}")

HILS Communication Errors

Note (2026-08-20): The firmware does not implement a HILS receiver yet, so the steps below will not achieve a connection. See 6. SILS/HILS Testing for implementation status.

  1. Verify the serial port is correct
  2. Verify baud rate (921600) matches
  3. Verify firmware is running in HILS mode

10. References

Resource Description
MIGRATION_PLAN.md Migration plan (for developers)
stampfly_sim Original simulator
firmware/vehicle/ Actual firmware
protocol/spec/ Protocol definitions