
Unitree G1のDex3ハンドをIsaac Sim上で動かす
はじめに
前回の記事ではIsaac Lab + Unitree G1のシミュレーション環境を構築し、シーン上にG1ロボットを表示するところまで確認しました。本記事では、DDSを用いてG1のDex3ハンド(3本指ハンド)をシミュレーション上で動かします。
Dex3の各関節のマッピングや、左右の手で回転方向が異なるという仕様上の注意点についても解説します。
前回の記事
DDSとは
DDS(Data Distribution Service)は、リアルタイムのデータ配信に特化した通信プロトコルです。unitree_sim_isaaclabはUnitree実機と同じDDSを採用しています。これにより、シミュレーション上で動かしたコードを最小限の変更で実機にも適用できるというメリットがあります。
Unitree SDKでは内部的にCycloneDDSを使用しています。
前提
- 前回の記事の環境構築が完了していること
DDS通信の設定ポイント
Isaac Sim側のDDSの設定を確認しておきます。dds/dds_master.pyを見ると、以下のように初期化されています。
ChannelFactoryInitialize(1) # ドメインID=1、インターフェース自動選択
外部スクリプトからコマンドを送る際は、同じドメインID(1)を指定する必要があります。ここを間違えるとコマンドが届きません。
最初にテストした際、以下のように書いてしまい通信できないという問題に遭遇しました。
# NG: ドメインIDが0、インターフェースがloopback
ChannelFactoryInitialize(0, 'lo')
# OK: Isaac Sim側と同じドメインID
ChannelFactoryInitialize(1)
Dex3ハンドの制御トピックは以下の通りです。
- 右手:
rt/dex3/right/cmd - 左手:
rt/dex3/left/cmd
ハンドコマンドの構造
Unitree SDK2 PythonのDex3制御に使うデータ型はHandCmd_とMotorCmd_です。(後のセクションのデモで出てきます)
HandCmd_の初期化には引数が必要で、デフォルトコンストラクタは使えません。
from unitree_sdk2py.idl.unitree_hg.msg.dds_ import HandCmd_, MotorCmd_
# NG: 引数なしではエラーになる
cmd = HandCmd_()
# OK: motor_cmdとreserveを明示的に渡す
motors = [
MotorCmd_(mode=1, q=0.5, dq=0.0, tau=0.0, kp=10.0, kd=1.0, reserve=0)
for _ in range(7)
]
cmd = HandCmd_(motor_cmd=motors, reserve=[0, 0, 0, 0])
MotorCmd_の各パラメータの意味は以下の通りです。
| パラメータ | 型 | 説明 |
|---|---|---|
mode |
uint8 | 制御モード(1=位置制御) |
q |
float32 | 目標関節角度 |
dq |
float32 | 目標関節角速度 |
tau |
float32 | トルク指令 |
kp |
float32 | 位置制御ゲイン(P制御) |
kd |
float32 | 速度制御ゲイン(D制御) |
reserve |
uint32 | 予約領域 |
Dex3の関節マッピング
Dex3は片手あたり7自由度で、motor_cmdの7要素が以下の関節に対応しています。
| インデックス | 関節名 | 指 |
|---|---|---|
| 0 | thumb_0_joint | 親指の根元 |
| 1 | thumb_1_joint | 親指の中間 |
| 2 | thumb_2_joint | 親指の先端 |
| 3 | middle_0_joint | 中指の根元 |
| 4 | middle_1_joint | 中指の先端 |
| 5 | index_0_joint | 人差し指の根元 |
| 6 | index_1_joint | 人差し指の先端 |
このマッピングはaction_provider/action_provider_dds.pyのleft_hand_joint_mappingおよびright_hand_joint_mappingで定義されています。
左右の手で閉じる方向が異なる
実際にさまざまな値を送って動作を確認したところ、左手と右手の両方で、関節ごとに閉じる方向(符号)が異なることがわかりました。どちらの手も一律に同じ符号を送るだけでは正しく閉じません。
| 関節 | 右手(閉じる) | 左手(閉じる) |
|---|---|---|
| thumb_0 (親指の根元) | +1.0 | +1.0 |
| thumb_1 (親指の中間) | +1.0 | -1.0 |
| thumb_2 (親指の先端) | -1.0 | +1.0 |
| middle_0 (中指の根元) | +1.0 | -1.0 |
| middle_1 (中指の先端) | +1.0 | -1.0 |
| index_0 (人差し指の根元) | +1.0 | -1.0 |
| index_1 (人差し指の先端) | +1.0 | -1.0 |
つまり、両手を同時に閉じるには以下のように関節ごとに符号を指定する必要があります。
# 右手を閉じる: thumb_2だけ-、他は+
right_cmd = make_cmd([1.0, 1.0, -1.0, 1.0, 1.0, 1.0, 1.0])
# 左手を閉じる: thumb_0とthumb_2だけ+、他は-
left_cmd = make_cmd([1.0, -1.0, 1.0, -1.0, -1.0, -1.0, -1.0])
G1のDex3ハンドは左右で鏡像配置になっており、関節ごとに回転軸の構造が異なります。特にthumb_2(親指の先端)は左右で符号が逆転しています。対応表を参照して関節ごとに個別に符号を設定する必要があります。
シミュレーションの起動
まずIsaac Simでシミュレーションを起動します。
conda activate unitree_sim_env
cd ~/unitree_sim_isaaclab
python sim_main.py --device cpu --enable_cameras --task Isaac-PickPlace-RedBlock-G129-Dex3-Joint --enable_dex3_dds --robot_type g129
タスクにはRedBlockを使用しています。前回の記事で使用したCylinderタスクではオブジェクトがG1の手元に近い位置に配置されており、手の動きを確認する際に干渉するためです。RedBlockタスクではオブジェクトがロボットから離れた位置に配置されるため、ハンドの動作確認に適しています。
起動時のデフォルトカメラアングルだと手元が全く見えません。
ビューポート(ロボットやシーンが写っているエリア)の上部にカメラアイコンがあるので、そこを選択して「Cameras > front_cam」とすると、ロボットの目線で手元を見ることができます。
カメラ設定箇所

front_cam

もしくは「Perspective」を選択して、手動で見やすいアングルに設定することもできます。

マウス右クリックで視点の回転、ホイールクリックで移動、ホイール回転でズームができます。
補足: シーンの照明をカスタムする
デフォルトのRedBlockシーンはライト設定がコメントアウトされており、倉庫モデル自体の照明のみで暗めです。
ハンドの色が黒いため、影の影響で指の曲がり具合などが視認しづらく、左右の手が同じ状態になっているかを判別するのが困難です。


手元が見にくい場合は、シーン設定ファイルにライトを追加すると確認しやすくなります。

tasks/common_scene/base_scene_pickplace_redblock.py内のコメントアウトされたライト設定を以下のように書き換えます。
# 4. light configuration
light = AssetBaseCfg(
prim_path="/World/light",
spawn=sim_utils.DomeLightCfg(color=(0.9, 0.9, 0.9), intensity=4000.0),
)
light_top = AssetBaseCfg(
prim_path="/World/light_top",
spawn=sim_utils.DistantLightCfg(color=(1.0, 1.0, 1.0), intensity=2000.0),
)
DomeLightCfgはシーン全体を均一に照らす環境光、DistantLightCfgは太陽光のような平行光源です。intensityの値を上げると明るくなります。変更後はIsaac Simの再起動が必要です。
と、この記事を書いていて気がついたのですが、「Camera Light」を選択すれば、全体的に良い感じの明るさになりますね。
ただ質感的なところは結構マットな印象なので、この辺りは見え方の好みで、上のカスタムライトの使用なども検討いただければと思います。

開閉テスト
シーンが表示されたら、別ターミナルを開き、以下のコマンドでスクリプトをファイルに保存します。
cat > ~/unitree_sim_isaaclab/test_dex3.py << 'EOF'
import time
from unitree_sdk2py.core.channel import ChannelPublisher, ChannelFactoryInitialize
from unitree_sdk2py.idl.unitree_hg.msg.dds_ import HandCmd_, MotorCmd_
ChannelFactoryInitialize(1)
pub_right = ChannelPublisher('rt/dex3/right/cmd', HandCmd_)
pub_right.Init()
pub_left = ChannelPublisher('rt/dex3/left/cmd', HandCmd_)
pub_left.Init()
def make_cmd(q_values):
motors = [
MotorCmd_(mode=1, q=q_values[i], dq=0.0, tau=0.0, kp=10.0, kd=1.0, reserve=0)
for i in range(7)
]
return HandCmd_(motor_cmd=motors, reserve=[0, 0, 0, 0])
CLOSE = 1.0
# 右手: thumb_2だけ-、他は+
RIGHT_CLOSE = [CLOSE, CLOSE, -CLOSE, CLOSE, CLOSE, CLOSE, CLOSE]
# 左手: thumb_0とthumb_2だけ+、他は-
LEFT_CLOSE = [CLOSE, -CLOSE, CLOSE, -CLOSE, -CLOSE, -CLOSE, -CLOSE]
print('両手を閉じます...')
for i in range(300):
pub_right.Write(make_cmd(RIGHT_CLOSE)) # 右手: 関節ごとに符号が異なる
pub_left.Write(make_cmd(LEFT_CLOSE)) # 左手: 関節ごとに符号が異なる
time.sleep(0.02)
print('両手を開きます...')
for i in range(300):
pub_right.Write(make_cmd([0.0] * 7))
pub_left.Write(make_cmd([0.0] * 7))
time.sleep(0.02)
print('完了')
EOF
保存したスクリプトを実行します。
cd ~/unitree_sim_isaaclab
conda activate unitree_sim_env
python test_dex3.py
Isaac SimのビューポートでG1の手元を見ると、両手が閉じてから開く動きが確認できます。
インタラクティブコントローラーの作成
テスト用の1回だけ実行するスクリプトだけでなく、対話的にハンドを操作できるコントローラーも作成しました。
ポイントは以下の通りです。
- バックグラウンドスレッドで20ms間隔(50Hz)で常にコマンドを送信し続ける
- メインスレッドでユーザーのコマンド入力を受け付ける
smooth_move関数で目標値を段階的に変化させ、なめらかな動作を実現する- 左右の手で回転方向が逆であることを吸収し、
close/openコマンドで直感的に操作できるようにする
cat > ~/unitree_sim_isaaclab/dex_controller.py << 'EOF'
import time
import threading
from unitree_sdk2py.core.channel import ChannelPublisher, ChannelFactoryInitialize
from unitree_sdk2py.idl.unitree_hg.msg.dds_ import HandCmd_, MotorCmd_
ChannelFactoryInitialize(1)
pub_right = ChannelPublisher('rt/dex3/right/cmd', HandCmd_)
pub_right.Init()
pub_left = ChannelPublisher('rt/dex3/left/cmd', HandCmd_)
pub_left.Init()
# 閉じる方向は関節ごとに異なる
left_q = [0.0] * 7
right_q = [0.0] * 7
kp = 10.0
kd = 1.0
running = True
CLOSE_VAL = 1.0
# 右手: thumb_2だけ-、他は+
RIGHT_CLOSE = [CLOSE_VAL, CLOSE_VAL, -CLOSE_VAL, CLOSE_VAL, CLOSE_VAL, CLOSE_VAL, CLOSE_VAL]
# 左手: thumb_0とthumb_2だけ+、他は-
LEFT_CLOSE = [CLOSE_VAL, -CLOSE_VAL, CLOSE_VAL, -CLOSE_VAL, -CLOSE_VAL, -CLOSE_VAL, -CLOSE_VAL]
def make_cmd(q_values):
motors = [
MotorCmd_(mode=1, q=q_values[i], dq=0.0, tau=0.0, kp=kp, kd=kd, reserve=0)
for i in range(7)
]
return HandCmd_(motor_cmd=motors, reserve=[0, 0, 0, 0])
def publish_loop():
while running:
pub_left.Write(make_cmd(left_q))
pub_right.Write(make_cmd(right_q))
time.sleep(0.02)
t = threading.Thread(target=publish_loop, daemon=True)
t.start()
def smooth_move(target_left, target_right, steps=50):
global left_q, right_q
start_left = left_q[:]
start_right = right_q[:]
for s in range(steps):
ratio = (s + 1) / steps
for i in range(7):
left_q[i] = start_left[i] + (target_left[i] - start_left[i]) * ratio
right_q[i] = start_right[i] + (target_right[i] - start_right[i]) * ratio
time.sleep(0.02)
def show_help():
print("""
===== Dex3 ハンドコントローラー =====
コマンド:
close 両手を閉じる
open 両手を開く
close left 左手だけ閉じる
close right 右手だけ閉じる
open left 左手だけ開く
open right 右手だけ開く
status 現在の値を表示
help このヘルプを表示
quit 終了
====================================
""")
print("Dex3コントローラー起動中...")
show_help()
while True:
try:
cmd = input("> ").strip().lower()
except (EOFError, KeyboardInterrupt):
break
if cmd in ('quit', 'q'):
break
elif cmd in ('help', 'h'):
show_help()
elif cmd == 'close':
smooth_move(LEFT_CLOSE, RIGHT_CLOSE)
print("両手を閉じました")
elif cmd == 'open':
smooth_move([0.0]*7, [0.0]*7)
print("両手を開きました")
elif cmd == 'close left':
smooth_move(LEFT_CLOSE, right_q[:])
print("左手を閉じました")
elif cmd == 'close right':
smooth_move(left_q[:], RIGHT_CLOSE)
print("右手を閉じました")
elif cmd == 'open left':
smooth_move([0.0]*7, right_q[:])
print("左手を開きました")
elif cmd == 'open right':
smooth_move(left_q[:], [0.0]*7)
print("右手を開きました")
elif cmd == 'status':
print(f"左手: {[f'{q:.2f}' for q in left_q]}")
print(f"右手: {[f'{q:.2f}' for q in right_q]}")
elif cmd == '':
continue
else:
print(f"不明なコマンド: {cmd} (helpで一覧表示)")
running = False
print("終了します")
EOF
保存したら実行します。
conda activate unitree_sim_env
python dex_controller.py
> close # 両手を閉じる
> open # 両手を開く
> close left # 左手だけ閉じる
> close right # 右手だけ閉じる
> status # 現在の各モーターの値を表示
> quit # 終了
まとめ
Isaac Sim上のUnitree G1に対して、DDSを用いてDex3ハンドの制御を実現しました。
今後は実機のG1への展開、Inspire製の5本指ハンドを使ったより複雑な制御の記事を出します。
G1の全身を使った実験や、Isaac Simの基本的な操作方法、強化学習や模倣学習の基礎等もバンバン記事にしていきます。








