1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Pythonで2機のドローンを同時制御してみた(MuJoCo × 箱庭 × WSL2/Docker)

1
Posted at

この記事では、前回の
Windows 11 / WSL2 / Docker + ゲームパッドで MuJoCo 版 箱庭ドローンシミュレータを起動する
を土台にして、MuJoCo 版の箱庭ドローンシミュレータを Python API から 2 機同時に動かす手順をまとめます。

今回のゴールは次の 2 つです。

  • MuJoCo viewer 上で 2 機の機体を確認できる
  • Python external_rpc から 2 機へ同時に SetReady / TakeOff / GoTo / Land を送れる

スクリーンショット 2026-04-23 8.16.27.png
(実行時の様子)

動画で見たい方は、こちらのデモもどうぞ。

前回は「Windows 側ゲームパッドで 1 機を動かす」話でしたが、今回は
Python から複数機をプログラム制御する
ところまで進めます。

※本手順は v3.7.1 時点の構成を前提にしています。

v3.7.1 全体の変更点は、こちらのリリース記事も参照してください。

前回記事をまだ読んでいない方向けに、今回必要な前提だけ先に書いておくと、

  • MuJoCo viewer が起動できる
  • Docker コンテナに入って hako-cmd start が使える
  • hakoniwa-drone-core の repo が手元にある

の 3 つが揃っていれば、今回の記事の内容は追えます。
つまり、「まず Python API から 2 機動かしてみたい」という方は、このまま読んで大丈夫です。


この記事でやること

今回は次の流れで進めます。

  1. Docker 上で MuJoCo 2 機サンプルを 3 ステップで起動する
  2. その後で、裏側で使っている runtime 生成の仕組みを確認する

最終的には次の Python サンプルが動く状態にします。

python3 drone_api/external_rpc/samples/mujoco_two_drone_demo.py

このサンプルは、Drone-1Drone-2 に対して次を行います。

  1. SetReady
  2. TakeOff
  3. GoTo
  4. GetState
  5. Land

前提

この記事では、前回の記事で整えた次の前提を使います。

  • hakoniwa-drone-core の repo が手元にある
  • Python 実行環境がある
  • MuJoCo 関連の実行環境が整っている

WSL2 / Docker の環境構築は、前回の記事で扱いました。
今回の記事では、WSL2 / Docker の環境構築はできている前提で進めます。

Dockerコンテナを起動するには、

  • bash docker/run.bash

を叩きます。

別端末から同じコンテナに入るには、

  • bash docker/attach.bash

を叩きます。これが前回との違いです。

まだ前回の構成を試していない場合は、先にこちらを参照してください。

今回の記事では、この Docker コンテナ運用を前提にして MuJoCo 複数機 + Python API を扱います。


1. まず Docker コンテナを起動して、MuJoCo viewer を立ち上げる

まずは runtime 生成の話はいったん後回しにして、2 機サンプルを先に動かします。

bash docker/run.bash

コンテナに入ったら、repo ルートで次を実行します。

cd /root/workspace
linux-main_hako_drone_service \
  config/drone/fleets/api-mujoco-instance-2.json \
  config/pdudef/drone-pdudef-current.json \
  --real-sleep-msec 1 \
  --mujoco-viewer

このコマンドを実行すると、MuJoCo viewer が立ち上がります。
この時点では viewer が立ち上がるだけで、シミュレーション自体はまだ開始していません。

viewer では次のキーが使えます。

  • c
    • follow camera / free camera の切り替え
  • v
    • camera orientation のリセット
  • 19
    • follow 対象の機体を切り替え

今回の 2 機サンプルでは、実際に使うのは 12 です。


2. 別端末からコンテナへ入り、シミュレーションを開始する

別ターミナルを開いて、もう一度 WSL2 側からコンテナへ入ります。

bash docker/attach.bash

その中で、次を実行します。

hako-cmd start

これでシミュレーションが開始し、viewer 上の 2 機が動かせる状態になります。


3. さらに同じ端末で、Python サンプルを実行する

次のサンプルを実行します。

cd /root/workspace
python3 drone_api/external_rpc/samples/mujoco_two_drone_demo.py

このサンプルでは、2 機に対して基本的な API 呼び出しを順に行います。

  • set_ready_async()
  • takeoff_async()
  • goto_async()
  • get_state()
  • land_async()

重要なのは、今回のサンプルが
FleetRpcController(..., use_async_shared=True)
を使っていることです。


4. 起動確認

成功の確認ポイントは次の 4 つです。

1. linux-main_hako_drone_service 起動後に viewer が立ち上がる

まず、MuJoCo viewer が立ち上がることを確認します。

2. hako-cmd start 後に 2 機が見えている

api-mujoco-instance-2.json を読み込んで、

  • Drone-1
  • Drone-2

の 2 機が起動していることを確認します。

3. viewer で追跡対象を切り替えられる

MuJoCo viewer で、

  • 1
  • 2

を押したときに follow 対象が切り替われば OK です。

4. Python サンプルのログが流れる

たとえば次のようなログが出れば成功です。

INFO: set_ready wait_start futures=2
INFO: set_ready[0] ok=True message=completed
INFO: set_ready[1] ok=True message=completed
INFO: takeoff wait_start futures=2
INFO: goto wait_start futures=2

5. 今回生成するファイル

ここまでで、2 機サンプル自体は動かせます。
ここから先は、その裏側で使っている runtime 生成の仕組みを見ていきます。

今回の 2 機 MuJoCo runtime では、次の 4 種類のファイルを使います。

ファイル 役割
config/drone/fleets/api-current.json 機体名・初期位置・MuJoCo instance override
config/drone/fleets/services/api-current-service.json Python external_rpc が接続する service 定義
config/pdudef/drone-pdudef-current.json Hakoniwa PDU 定義
config/drone/mujoco-current/drone.xml MuJoCo scene と 2 機分の body 定義

大事なのは、今回はもう
手書きで drone.xmldrone_config_i.json を増やさない
ことです。

generator で runtime を作ってから、それを Python サンプルで使います。


6. fleet / service / pdudef を生成する

まず、2 機の MuJoCo API 用 config を current 系へ生成します。

python3 tools/gen_fleet_scale_config.py \
  -n 2 \
  --layout packed-rings \
  --ring-spacing-meter 1 \
  --type-name api-mujoco \
  --type-config-path config/drone/fleets/types/api-mujoco.json \
  --fleet-path config/drone/fleets/api-current.json \
  --pdudef-path config/pdudef/drone-pdudef-current.json \
  --service-config-path config/drone/fleets/services/api-current-service.json \
  --service-out-path config/drone/fleets/services/api-current-service.json \
  --enable-mujoco-overrides

この generator がやっていることは次です。

  • Drone-1, Drone-2 を持つ fleet config を作る
  • Python API が接続する service config を作る
  • Hakoniwa PDU 定義を作る
  • 各機体の mujoco.modelName / mujoco.propNames を自動で埋める

たとえば api-current.json には、各機体ごとに次のような MuJoCo override が入ります。

  • Drone-1
    • modelName = d1_b_drone_base
    • propNames = d1_b_prop1 ... d1_b_prop4
  • Drone-2
    • modelName = d2_b_drone_base
    • propNames = d2_b_prop1 ... d2_b_prop4

つまり、fleet 側の機体名と MuJoCo 内の body 名の対応付けも、ここで揃います。


7. MuJoCo XML を生成する

次に、scene template と 1 機分の body template から、2 機構成の drone.xml を生成します。

python3 tools/gen_mujoco_multidrone_xml.py \
  -n 2 \
  --scene-template-path config/drone/fleets/types/mujoco-scene.xml.template \
  --drone-template-path config/drone/fleets/types/mujoco-drone.xml.template \
  --output-path config/drone/mujoco-current/drone.xml

ここで使っている template の役割は次のとおりです。

ファイル 役割
mujoco-scene.xml.template scene 全体。ground / landmark / building / obstacle / light など
mujoco-drone.xml.template 1 機ぶんの body 構造

generator は、

  • scene template の __DRONE_BODIES__
  • drone template 内の d1_*, d2_* という命名パターン

を展開して、最終的な drone.xml を作ります。


8. scene template は何をするのか

今回の構成では、scene 全体はユーザ側でテンプレートとして管理できます。

つまり、

  • 地面
  • landmark
  • 建物
  • 障害物
  • light

mujoco-scene.xml.template 側で自由に書けます。

一方で、複数機の drone body は generator が差し込みます。

イメージはこうです。

<mujoco>
  ...
  <worldbody>
    <light .../>
    <geom name="ground" .../>
    <body name="building_1" ...>...</body>
    <body name="obstacle_1" ...>...</body>

    __DRONE_BODIES__
  </worldbody>
</mujoco>

この設計にしたことで、

  • 機体数は generator で増やす
  • world の見た目や障害物は template で編集する

という責務分離ができています。


9. use_async_shared=True を使う理由

FleetRpcController のデフォルトは use_async_shared=False です。
ただし、これは後方互換のための既定値です。

False の場合は、

  • 同期 RPC client を
  • Python の thread pool で並列実行する

という互換経路になります。

一方で今回やりたいのは、

  • call を連続発行する
  • 複数機の応答をまとめて待つ
  • 1 process / 1 thread に近い軽量な経路で処理したい

ことです。

そのため、複数機の新規利用では use_async_shared=True を使います。


10. 実際のサンプルの流れ

今回の mujoco_two_drone_demo.py では、2 機分の command を先に投げてから、まとめて待っています。

このサンプルで使っているのは、external_rpcfleet API です。

ここで良いのは、

  • 機体名を渡すだけでよい
  • set_ready_async()takeoff_async() のように API 名が素直
  • wait_for_all() で複数機の応答をまとめて待てる

という点です。

複数機シミュレータの API は、実際には

  • 機体ごとの接続管理
  • 同時実行
  • 応答待ち
  • タイムアウト処理

が煩雑になりがちです。

その点、今回の fleet API はかなり直感的です。
実際のサンプルも、かなりそのまま読めます。

たとえば今回の流れを抜き出すと、ほぼ次のようなコードです。

with FleetRpcController(
    drone_names=["Drone-1", "Drone-2"],
    service_config_path=SERVICE_CONFIG_PATH.resolve(),
    use_async_shared=True,
) as fleet:
    ready_futures = [
        fleet.set_ready_async("Drone-1"),
        fleet.set_ready_async("Drone-2"),
    ]
    fleet.wait_for_all(ready_futures)

    takeoff_futures = [
        fleet.takeoff_async("Drone-1", 1.5),
        fleet.takeoff_async("Drone-2", 1.5),
    ]
    fleet.wait_for_all(takeoff_futures)

    goto_futures = [
        fleet.goto_async("Drone-1", 0.0, 1.0, 1.5),
        fleet.goto_async("Drone-2", 1.0, 1.0, 1.5),
    ]
    fleet.wait_for_all(goto_futures)

    state1 = fleet.get_state("Drone-1")
    state2 = fleet.get_state("Drone-2")

    land_futures = [
        fleet.land_async("Drone-1", timeout_sec=5.0),
        fleet.land_async("Drone-2", timeout_sec=5.0),
    ]
    fleet.wait_for_all(land_futures, return_exceptions=True)

つまり、

  • 1 機ずつ順番に終わるまで待つ
    ではなく
  • 2 機へ先に投げて、あとでまとめて待つ

という形になっています。

見てのとおり、

  • 機体名
  • コマンド
  • 目標値

がそのままコードに出てくるので、かなり読みやすいです。

しかも今回は use_async_shared=True を使っているので、

  • 互換用の同期 wrapper ではなく
  • shared runtime ベースの複数機経路

をそのまま使えます。

個人的には、「複数機 API なのに、単機 API をそのまま横に並べたような感覚で書ける」
のがこのサンプルの強みだと思っています。


11. よくあるハマりどころ

mujoco_two_drone_demo.py 実行前に MuJoCo drone service が起動していない

先にコンテナ内で linux-main_hako_drone_service ... を起動しておく必要があります。

mujoco_two_drone_demo.py 実行前に hako-cmd start をしていない

viewer が立ち上がっていても、hako-cmd start を実行していなければシミュレーションは始まりません。

api-current.jsonapi-current-service.json を生成し直していない

generator を回した後に、古い current を見ていると噛み合いません。

drone.xmlconfig/drone/mujoco-current/drone.xml に出していない

api-mujoco.json 側の modelPathconfig/drone/mujoco-current/drone.xml を見ます。

use_async_shared=True を付け忘れる

複数機サンプルとして試すなら、ここは明示しておくのが安全です。


12. 今回の位置づけ

前回の記事では、MuJoCo 版箱庭ドローンを
Windows 側ゲームパッドで動かす
ところまでを扱いました。

今回の記事では、その次の段階として、
MuJoCo 複数機 runtime を生成し、Python API から 2 機を同時に動かす
ところまでを扱いました。

これで、

  • RC 操作でまず試す
  • 次に Python API で自動制御へ進む

という流れが一通り揃ったことになります。

次にやるなら、たとえば次の方向が自然です。

  • scene template に建物や障害物を追加する
  • mujoco_two_drone_demo.py をベースに独自 mission を書く
  • 2 機ではなく N 機へ拡張する

補足

より詳しい設計や手順は、repo 内の次の文書にもまとまっています。

  • docs/fleets/mujoco-external-rpc-quickstart.md
  • docs/fleets/mujoco-runtime-generation.md
  • docs/fleets/external-rpc-tutorial.md
  • drone_api/external_rpc/README.md

Qiita ではまず「何ができるか」と「最短でどう動かすか」に絞りました。
細かい設計や拡張ポイントを見たい場合は、これらの文書も合わせて参照してください。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?