ドローンデモ

Lambda C は ドローンを始めとする自律ロボット制御 を主要なターゲットとして設計されました。このページでは、リポジトリに同梱されている demos/raylib_drone/ を題材に、組み込み制御を Lambda C でどう構造化するかを示します。

Lambda C で制御されるドローン配送シミュレータ

なぜドローンが Lambda C の最初のターゲットなのか

自律ドローンのソフトウェアは古典的に2層に分かれます。

これは Lambda C のコア設計と一致します:

スクリプトは「やりたいこと」を表明し、ホストの FFI が「物理的に可能か」を判定する。これは航空宇宙・自動車制御で標準的な階層分離そのものです。Lambda C はこの境界を 約 100 ns のロード時リンク済 FFI で一直線に通す目的で作られています。

同梱デモが行うこと

地点 A で荷物を受け取り、B に配達し、A に戻る (ENABLE_POINT_C = 1 で A → B → C → A の三角ルートに拡張)。

 IDLE → TAKEOFF → FLY_TO_B → HOVER_B → DESCEND_B → DROP


                              TAKEOFF_B → (FLY_TO_C → HOVER_C → DESCEND_C → WAIT_C → TAKEOFF_C)?


                                                FLY_TO_A → HOVER_A → DESCEND_A → LANDED

実装されているもの:

画面左上にはテレメトリ HUD (高度・位置・スロットル・姿勢・ARMED 状態・ミッション状態・荷物状態・選択中ルート) が、右上にはスクリプト状態と FPS が表示されます。

操作キー:

キー動作
Rスクリプトを切替 — 単純往復 delivery.c (A→B→A) ⇔ 巡回 patrol.c (A→B→C→A)
1 / 2 / 3巡航高度を 3m / 5m / 10m に変更
Spaceドローン位置をリセット
Tトレースモードのトグル
ESC終了

R を押すと、ホストが SDK のスクリプトパスを差し替えて再ロードし、まったく別の挙動スクリプトに切り替わります (main_sdk.cswap_script())。2 本とも荷物を A で積み B で降ろす配送ミッションで、違いは経路だけ — 直行 (A→B→A) か C 地点を経由する三角ルート (A→B→C→A) か。スクリプトをエディタで直接編集して保存すれば自動でリロードされ、1/2/3 は巡航高度をその場で書き換える便利キーです (script_patcher.h 参照)。

スクリプト層: ミッションの「意図」を書く

delivery.c物理を知らないdrone_get_altitude()drone_set_throttle() といった FFI を通じて意図を表明するだけです。

/* FFI 宣言: ホストが何を提供してくれるか */
double drone_get_time();
double drone_get_altitude();
double drone_get_x();
double drone_get_z();
void   drone_set_arm(int armed);
void   drone_set_throttle(double throttle);
void   drone_set_pitch(double pitch);
void   drone_set_roll(double roll);
void   drone_set_led(int r, int g, int b);

/* ミッションステートマシン */
switch (g_mission_state) {
case 1: /* TAKEOFF — 巡航高度まで上昇 */
    drone_set_throttle(altitude_hold(CRUISE_ALT));
    if (at_altitude(CRUISE_ALT)) g_mission_state = 2;
    break;

case 2: /* FLY_TO_B — 配達地点へ */
    drone_set_throttle(altitude_hold(CRUISE_ALT));
    fly_toward(POINT_B_X, POINT_B_Z);
    if (at_position(POINT_B_X, POINT_B_Z)) {
        g_hover_start_time = drone_get_time();
        g_mission_state = 3;
    }
    break;
/* ... */
}

このスクリプトは:

ホスト層: 物理と「実行可能性」を所有する

ホスト側 (main_sdk.cdrone_api.c) が物理シミュレーションと FFI 実装を持ちます。実機では同じ位置にモータドライバ、IMU、armed 検証ロジックが入る想定です。

/* SDK が要求する 2 つのコールバックだけ */

static void on_init(LcvmState *vm, void *user) {
    /* フレーム watchdog: 60fps ≒ 16ms */
    lcvm_set_watchdog_frame(16000);

    /* FFI を一括登録 (drone_ffi.h から自動生成) */
    lcvm_register_ffi_all();
}

static void on_frame(LcvmState *vm, float dt, void *user) {
    UserState *state = (UserState *)user;
    state->sim_time += dt;
    drone_set_time(state->sim_time);

    /* スクリプトが書き込んだ throttle/pitch/roll を物理に反映 */
    drone_physics_update(dt);
}

ホスト側 FFI 実装の中で実行可能性を判定します。例:

スクリプトが「スロットル 2.0」と要求しても、HAL が拒否ないし飽和させる。スクリプトに無制限の権限はない — これが Lambda C の安全境界です。

FFI: 意図と実行を繋ぐ薄い境界

ドローンデモは わずか 12 個の FFI で完結します:

/* drone_ffi.h - 12 関数だけ */
double drone_get_time();
double drone_get_altitude();
double drone_get_x();
double drone_get_z();
void   drone_set_arm(int armed);
void   drone_set_mission_state(int state);
void   drone_set_has_package(int has);
void   drone_set_throttle(double throttle);
void   drone_set_pitch(double pitch);
void   drone_set_roll(double roll);
void   drone_set_yaw(double yaw);
void   drone_set_led(int r, int g, int b);

これだけで配達ミッション全体が記述できる、というのが要点です。lcvmc --gen-ffi drone_ffi.h で登録関数 lcvm_register_ffi_all() が自動生成されるため、手書きの FFI ボイラープレートはほぼ不要です。

実行時は ロード時に名前→ID へ解決済み であり、毎フレーム数百回呼ばれても約 100 ns/呼び出しの定数時間です。

ホットリロード: 飛行中のドローンを止めずにミッションを書き換える

デモを実行中に scripts/delivery.c を開き、巡航高度などの値を編集して保存します:

double CRUISE_ALT = 5.0;   /* 8.0 に変えて保存してみる */

保存すると SDK がファイル変更を検知し、lcvmc が自動的に走って新しい .lcbc がロードされ、飛行中のドローンが次のフレームから新しい高度で動く。VM 状態 (現在の位置・速度・armed フラグ) はそのままです。さらに R キーを押せば、delivery.c (単純往復) と patrol.c (巡回) の別スクリプトへ丸ごと切り替わります

これがどう実機に効くのか:

Simulator SDK: 同じ設計を自分のドメインへ

ドローンデモは Lambda C Simulator SDK (lib/lcvm_sim.h) のリファレンス実装です。SDK は次を提供します:

顧客が実装するのは2つのコールバックだけ:

lcvm_sim_config_t cfg = {
    .script_path       = "mission.c",
    .enable_hot_reload = 1,
    .on_init           = on_init,    /* FFI 登録 */
    .on_frame          = on_frame,   /* 物理 + 描画 */
    .user_data         = &state,
};
lcvm_sim_ctx_t *sim = lcvm_sim_create(&cfg);

while (running) {
    lcvm_sim_update(sim, dt);
}

ドローンは一例にすぎず、同じパターンは次のような領域にそのまま展開できます:

各ドメインのコールバック例は サンプル を参照してください。

試してみる

cd demos/raylib_drone
make
./drone_emu_sdk          # Windows なら drone_emu_sdk.exe
# またはリポジトリルートから:  .\run_delivery.ps1

走らせたら:

scripts/delivery.c / scripts/patrol.c をベースに自分のミッションを書いてみるのが、Lambda C を理解する最短ルートです。

関連