この記事では、前回の
Windows 11 / WSL2 / Docker + ゲームパッドで MuJoCo 版 箱庭ドローンシミュレータを起動する
を土台にして、MuJoCo 版の箱庭ドローンシミュレータを Python API から 2 機同時に動かす手順をまとめます。
今回のゴールは次の 2 つです。
- MuJoCo viewer 上で 2 機の機体を確認できる
- Python
external_rpcから 2 機へ同時にSetReady / TakeOff / GoTo / Landを送れる
動画で見たい方は、こちらのデモもどうぞ。
前回は「Windows 側ゲームパッドで 1 機を動かす」話でしたが、今回は
Python から複数機をプログラム制御する
ところまで進めます。
※本手順は v3.7.1 時点の構成を前提にしています。
v3.7.1 全体の変更点は、こちらのリリース記事も参照してください。
前回記事をまだ読んでいない方向けに、今回必要な前提だけ先に書いておくと、
- MuJoCo viewer が起動できる
- Docker コンテナに入って
hako-cmd startが使える -
hakoniwa-drone-coreの repo が手元にある
の 3 つが揃っていれば、今回の記事の内容は追えます。
つまり、「まず Python API から 2 機動かしてみたい」という方は、このまま読んで大丈夫です。
この記事でやること
今回は次の流れで進めます。
- Docker 上で MuJoCo 2 機サンプルを 3 ステップで起動する
- その後で、裏側で使っている runtime 生成の仕組みを確認する
最終的には次の Python サンプルが動く状態にします。
python3 drone_api/external_rpc/samples/mujoco_two_drone_demo.py
このサンプルは、Drone-1 と Drone-2 に対して次を行います。
SetReadyTakeOffGoToGetStateLand
前提
この記事では、前回の記事で整えた次の前提を使います。
-
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 のリセット
-
1〜9- follow 対象の機体を切り替え
今回の 2 機サンプルでは、実際に使うのは 1 と 2 です。
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-1Drone-2
の 2 機が起動していることを確認します。
3. viewer で追跡対象を切り替えられる
MuJoCo viewer で、
12
を押したときに 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.xml や drone_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-1modelName = d1_b_drone_basepropNames = d1_b_prop1 ... d1_b_prop4
-
Drone-2modelName = d2_b_drone_basepropNames = 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_rpc の fleet 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.json と api-current-service.json を生成し直していない
generator を回した後に、古い current を見ていると噛み合いません。
drone.xml を config/drone/mujoco-current/drone.xml に出していない
api-mujoco.json 側の modelPath は config/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.mddocs/fleets/mujoco-runtime-generation.mddocs/fleets/external-rpc-tutorial.mddrone_api/external_rpc/README.md
Qiita ではまず「何ができるか」と「最短でどう動かすか」に絞りました。
細かい設計や拡張ポイントを見たい場合は、これらの文書も合わせて参照してください。
