はじめに
前回、VRゴーグルでUnitree G1をテレオペしてみた(シミュレーション編) という記事で、シミュレーション上でのテレオペをやりました
今回はその続編として、ついに実機でテレオペをやってみました!
今回のテレオペは有線接続でやりました(G1から伸びているケーブルがサーバーとつながっている)。無線でもできるっぽのですが、その場合G1を一部分解してソフトを焼き直すという面倒な工程が入ってしまうので、やるなら有線でやってからという感じですね。
シミュレーション上でテレオペができていれば、特にコードを大きく変えるようなことはないので、simでやっていたことをそのままrealに持って来るという感じです。
この記事を読めば、実機でのテレオペのおおよそのイメージが掴めると思います!!!
システム構成
シム編とほぼ一緒ですね。
違うところは、MuJoCoが実機のG1に置き換わることと、そのG1がサーバーに有線で直結されることです。
| レイヤー | 中身 |
|---|---|
| VR側 | PICO 4 ヘッドセット + コントローラ2 + モーショントラッカー3(足首2・腰1)+ XRoboToolkit |
| サーバー側 | NVIDIA製GPU 搭載の Ubuntu マシン(シム編と同じ) |
| ロボット | Unitree G1(29DoF)+ 安全クレーン/ガントリー |
| 接続 | PICO ↔ サーバー は WiFi(Tailscale) / サーバー ↔ G1 は 有線 192.168.123.x
|
| ソフト |
gear_sonic_deploy:C++ deployバイナリ + PICO streamer(MuJoCoは無し) |
「どこまでワイヤレスなの?」問題
公式構成でワイヤレスなのは 人間側(PICO ↔ PC)だけです。PC ↔ G1 は有線が前提で、deploy.sh ... real は 192.168.123.x を持つNICを自動検出して動きます。
つまりG1にはLANケーブルが刺さっている状態がベースライン。完全ワイヤレス化はG1内蔵のJetsonで推論を回す必要があり、それは別の話になります(後述)。
データの流れは以下。
環境
| 項目 | 内容 |
|---|---|
| GPU | NVIDIA RTX 3090(24GB) |
| OS | Ubuntu 22.04 |
| CUDA / TensorRT | CUDA 12.8 / TensorRT 10.13(x86_64はバージョン厳守) |
| ロボット | Unitree G1(29DoF) |
| VR | PICO 4 + モーショントラッカー×3 |
| G1との接続 |
サーバーに有線で直結(192.168.123.x / 20mのLANケーブル) |
| 安全装備 | 安全クレーン / ガントリー |
TensorRTのバージョンは公式が「Danger」扱いしています。
異なるバージョンを使うと推論結果が誤ることが知られており、plannerが誤ったモーションを出力し、危険なロボット挙動を招く
シムのときも「バージョン厳守」ではありましたが、実機だと意味の重さが違いますね。x86_64 は 10.13、Jetson は 10.7 です。
シムとの差分は微小
起動コマンドはこれだけです。
# sim側が起動していたら落とす
pkill -9 -f run_sim_loop
cd gear_sonic_deploy
source scripts/setup_env.sh
./deploy.sh --input-type zmq_manager real # ← シム編では最後が sim だった
MuJoCoを起動する窓が消えるので、立ち上げるプロセスも5個から3個に減ります。
❌ 実機で CYCLONEDDS_URI を設定してはいけません。
シム編で作った ~/cyclonedds_localhost.xml は「lo だけ使え・相手はlocalhostだ」という指示です。実機のG1は別マシンにいるので、これを読ませると永久に発見できず Lost LowState になります。
起動スクリプトの先頭で unset CYCLONEDDS_URI しておくのが安全です。シム編の設定をそのまま引きずるとハマります。
まああんまりここで詰まる人はいないと思いますが、、、
でまあこれだけやればいけるか楽勝じゃんとか思ってたんですけど、しょうもないことでつまづいてしまいました(泣)
① 起動した瞬間、G1が勝手に立ち上がって転倒した
ポリシーは'A+B+X+Y'で追従モードにするまで起動しないんだから、とりあえずサーバー側からちゃんとG1が見えてるかを確認しようと思い、G1を床に座らせた状態で deploy.sh ... real を起動しました。
そうしたら、G1が自分で立ち上がろうとして(正確には、立ち姿勢になろうとして足を伸ばした)、後ろ向きに倒れてしまいました。
いやーーー焦りましたね。幸い何も壊れたりはしませんでしたが、、
ソースを読んで確定させた
何が起きたんだろうと思い、コードを探してみたところ、こんな記述が見つかりました。 g1_deploy_onnx_ref.cpp を読みました。犯人はこれです↓
// INIT state handler: ramp the robot from its current pose to the
// default standing angles over `duration_` seconds (linear interpolation).
bool InitControl() {
if (!ls) { return false; } // LowState未着 →「LowState is not available」を出して待つだけ
for (int i = 0; i < G1_NUM_MOTOR; ++i) {
q_target[i] = default_angles[i]; // 目標=起立姿勢
kp[i] = kps[i]; // 位置制御ゲインON
kd[i] = kds[i];
}
if (time_ < duration_) { // duration_ = 3.0秒
double ratio = time_ / duration_;
q_target[i] = current_pos*(1.0-ratio) + default_angles[i]*ratio; // 線形補間
} else {
program_state_ = WAIT_FOR_CONTROL;
std::cout << "Init Done"; // 動作が終わってから表示される
}
}
program_state_ の初期値は INIT です。つまり deploy.sh を起動するとこの状態で始まるということ。
整理するとこういうことです。
-
LowState(=ロボットの状態)が届いた瞬間に、無条件で起立姿勢へのランプが始まる。 LANを挿してG1の電源が入っていれば、それだけで発火します。人間の操作は要りません -
A+B+X+Yは無関係。 あれはWAIT_FOR_CONTROL → CONTROLの遷移条件で、このランプの後にあります。「ポリシーは動かない」は事実ですが、ポリシーの手前に無条件の起立動作がある - このランプにバランス制御は入っていない。 関節角を機械的に動かすだけの開ループです。なんも考えずにとりあえず足を伸ばしてるというわけですね
- 現在姿勢が起立姿勢から遠いほどランプは激しくなります。座位からだと大きく動くし、すでに立っていたらあんまり動きません。
用語ミニ解説
- ランプ(ramp):目標値までなめらかに変化させること。ここでは「今の関節角 → 起立姿勢の関節角」を3秒かけて線形に動かしています
- 開ループ:結果を見ずに指令だけ出す制御。転びそうかどうかを見ていないのがポイントです
公式ドキュメントにはこう書いてあります。
Make robot stand loose but standing — Put the G1 somehow slack on gantry
僕はこれはテレオペを実際にやる時に吊っておけばいいのかと解釈してたんですけど、これはdeployするときはもう吊るしとけのような意味だったわけですね(英語力の無さが表れていて悲しい)。
ネットワークの実測(PICO ↔ サーバー)
公式の基準は Good < 10ms / 許容 10–30ms / Poor > 30ms(30ms超はつまずきリスク増)です。実ストリーム中(ヘッドセット装着+send ON・30秒)に測りました。
| 指標 | min | p50 | p90 | p99 |
|---|---|---|---|---|
rcv_rtt(実データ経路のRTT推定) |
6.0 | 8.9 | 10.4 | 12.6 ms |
スループット 593 KB/s。p50 8.9ms = 公式 Good 判定でした。
サーバーが弱かったり、サーバー直挿しじゃなくてルーターにG1からのLANを挿したりするとこの辺の数値は結構怪しくなってくるかも知れません。
起動スクリプト(抜粋)
シム編と同じく、毎回3つ手で立ち上げるのは面倒なのでスクリプト化しました。
# 1) XRoboToolkit PC Service
gnome-terminal --title="1-service" -- bash -c "/opt/apps/roboticsservice/runService.sh"
sleep 3
# 3) C++ deploy(実機)
gnome-terminal --title="3-deploy" -- bash -c \
"cd gear_sonic_deploy && source scripts/setup_env.sh && unset CYCLONEDDS_URI && \
./deploy.sh --input-type zmq_manager real; exec bash"
sleep 7
# 4) PICO streamer
gnome-terminal --title="4-pico" -- bash -c \
"source .venv_teleop/bin/activate && python gear_sonic/scripts/pico_manager_thread_server.py --manager"
実際に起動する際は、安全確認のスクリプトなど混ぜて万全の状態で行うことを強く推奨します。
シム編にあった socat の窓は削除しました。PICOは RoboticsService の TCP 63901 に直接繋がっていて(全インターフェースでLISTENするのでTailscale経由で届く)、中継していた 60061 は pico_manager 用のローカルポートでした。最初から不要だったわけですね…。
テレオペ実行
ここまで来たら、あとは順番通りにやるだけです。
1. G1を【浮かせた】状態で ./launch_teleop_real.sh
2. 安全確認に y → deploy の窓で Y
3. 空中で3秒のランプ → 立ち姿勢になる → "Init Done"
★ここで自動停止(剛性を保って立ち姿勢を維持)
4. ロープをたるませてG1を接地する
(この時点でG1に制御はかかっていないので、たるませすぎると倒れます)
5. PICO接続 → Status: WORKING
6. キャリブ姿勢(直立・両足揃え・腕は真下)で静止
7. A+B+X+Y
主なコントローラ操作(シム編と同じです)
| 動作 | ボタン |
|---|---|
| ポリシー起動 / 緊急停止 | A+B+X+Y(初回は全身キャリブも実行) |
| POSE ⇔ PLANNER | A+X |
| VR_3PT(上半身のみ追従) | 左スティック押し込み |
| 緊急停止(キーボード) | deployの窓で O
|
結果
実機での全身テレオペ、達成できました!!!
VRゴーグルを被って腕を上げればG1も腕を上げ、体を捻れば追従します。やっぱり実機は感動が大きいですね。
終わりに
次回は onboard化(完全ワイヤレス) に挑戦しようと思っています。G1の背面を開けて一部ソフトを焼き直す必要があるっぽいのですが、技術的にめちゃめちゃ難しいことはなさそうなので、意外とすんなり行けてしまったりするのかなーと思っていたり、、。
最後まで読んでいただきありがとうございました!!!!
参考リンク
- VRゴーグルでUnitree G1をテレオペしてみた(シミュレーション編) — 前回の記事
- PICO VR Whole-body Teleop — 「Step-by-Step: Teleop on Real Robot」が実機編の本体
- Whole-body Teleoperation Guide — 安全・服装・WiFi遅延・キャリブ・トラブルシュート
- Installation (Deployment) — TensorRTのバージョン表

