Unitree G1では,メーカーが準備したAPI・SDKを使って既存の機能を呼び出す形で行う高レベル制御と,rt/lowcmdにLowCmd_をpublishして全ての関節を自前で制御する低レベル制御があります。じつは,これに加えて,高レベル制御で動作中にSwitchToUserCtrl()でユーザ制御に切り替え,rt/user_lowcmdにLowCmd_相当の制御を行う3つめの動作モードがあるのでした。
こんにちは,S.T.です。Unitree G1でアプリケーションを作っていると,高レベル制御で動かしたい処理と,低レベル制御に近い形で関節を動かしたい処理をどう共存させるか,という場面があります。今回はその手段として,user_lowcmdを使う制御方法を紹介します。
この記事では,記事執筆時点(2026/7)のunitree_sdk2を前提にしています。実機を動かす話なので,実行時は必ずUnitreeのドキュメント,利用している機体,ファームウェア,周囲の安全を確認してください。
1.Unitree G1の高レベル制御と低レベル制御
Unitree G1をSDKから制御する方法は,大きく分けると高レベル制御と低レベル制御があります。
高レベル制御は,LocoClientなどを使って,G1の内部にある運動制御サービスに命令を出す方法です。例えば,速度を指定して移動させたり,立ち上がらせたり,停止させたりします。アプリケーション側から見ると,「移動する」「止まる」「立つ」といった粒度で命令を出せるため,G1の基本的な移動や姿勢制御を扱う場合はこちらを使うことになります。
一方,低レベル制御は,LowCmd_とLowState_を使って,各関節に対して目標位置,目標速度,ゲイン,フィードフォワードトルクなどを指定する方法です。G1の低レベル制御のサンプルでは,rt/lowcmdにLowCmd_をpublishし,rt/lowstateから状態をsubscribeしています。
static const std::string HG_CMD_TOPIC = "rt/lowcmd"; static const std::string HG_STATE_TOPIC = "rt/lowstate";
かなり単純化すると,高レベル制御はG1に「この速度で歩いて」と言う方法で,低レベル制御は各関節に「この角度にこのゲインで動いて」と言う方法です。どちらが良いという話ではなく,作りたいアプリケーションによって使う層が変わります。
2.高レベル制御と低レベル制御を共存させたい場面
低レベル制御は自由度が高いです。ただし,アプリケーション側が持つ責任も大きくなります。関節を直接動かすので,姿勢,ゲイン,コマンド周期,安全停止,復帰処理などを考える必要があります。
また,通常の低レベル制御ではrt/lowcmdを使います。この方法は,G1の内部運動制御とは別のレイヤで関節コマンドを送るため,高レベル制御で動くアプリケーションと単純には混ぜづらいです。
例えば,普段は公式の制御を使って移動し,必要な区間だけユーザ側で関節を制御し,終わったらまた公式の制御に戻したい,という構成を考えます。この場合,問題になるのは制御権の受け渡しです。高レベル制御と低レベル制御を同時に無秩序に動かすのではなく,明示的に切り替えられる仕組みがあると扱いやすくなります。
ここで使えるのが,今回扱うuser_lowcmdです。
3.user_lowcmdを使う制御モード
unitree_sdk2には,LocoClient経由でユーザ制御に切り替えるAPIがあります。具体的には,SwitchToUserCtrl()でユーザ制御に入り,SwitchToInternalCtrl()で内部制御に戻します。
unitree::robot::g1::LocoClient client; client.Init(); client.SwitchToUserCtrl(); // rt/user_lowcmd に LowCmd_ を publish する client.SwitchToInternalCtrl(unitree::robot::g1::InternalFsmMode::LAST);
このモードでは,ユーザアプリケーションはrt/user_lowcmdにLowCmd_をpublishします。通常の低レベル制御でrt/lowcmdに送っていたものと同じような考え方で,q,dq,kp,kd,tauを指定できます。
static const std::string kTopicUserCtrl = "rt/user_lowcmd"; static const std::string kTopicState = "rt/lowstate";
ここで重要なのは,rt/user_lowcmdはrt/lowcmdを直接使う通常の低レベル制御とは別の経路だという点です。LocoClientを使って公式の運動制御からユーザ制御に切り替え,ユーザ制御中はrt/user_lowcmdに関節コマンドを送り,終わったらLocoClientで公式の運動制御に戻します。
つまり,高レベル制御で動くアプリケーションと,低レベル制御に近い関節制御を行うアプリケーションを,制御モードの切り替えとして扱えるということです。アプリケーションを作る側から見ると,入口と出口がはっきりするので扱いやすくなります。
4.rt/user_lowcmdの使い方を見てみる
使い方の流れを見ていきます。全体を俯瞰した図が以下になります。>
LocoClientを初期化し,現在の状態を確認します。SDKのサンプルでは,GetFsmId()で現在のFSM IDを確認し,期待する状態でなければ終了するようになっています。
unitree::robot::g1::LocoClient client;
client.Init();
client.SetTimeout(5.f);
int current_fsm_id;
client.GetFsmId(current_fsm_id);
if (current_fsm_id != 1) {
std::cout << "Current fsm is not PASSIVE" << std::endl;
return -1;
}
実機を扱う場合,この確認はかなり大事です。想定していない姿勢や制御状態から関節コマンドを送り始めると,危険な動作になる可能性があります。特にuser_lowcmdは関節コマンドを送る仕組みなので,開始時の状態は明示的に確認した方がよいです。
次に,rt/user_lowcmdのpublisherと,rt/lowstateのsubscriberを用意します。状態取得は通常の低レベル制御と同じくLowState_を使うため,現在の関節角を読んで,そこから目標姿勢へ補間していくような実装にできます。
unitree::robot::ChannelPublisherPtr<unitree_hg::msg::dds_::LowCmd_> user_ctrl_publisher;
unitree_hg::msg::dds_::LowCmd_ msg;
user_ctrl_publisher.reset(
new unitree::robot::ChannelPublisher<unitree_hg::msg::dds_::LowCmd_>(
"rt/user_lowcmd"
)
);
user_ctrl_publisher->InitChannel();
ユーザ制御へ入る直前には,いきなり大きな目標角を入れるのではなく,現在角から初期姿勢へゆっくり遷移させるのが安全です。SDKのサンプルでも,現在角を読んだあと,数秒かけて初期姿勢へ近づける実装になっています。
client.SwitchToUserCtrl();
for (int i = 0; i < init_time_steps; ++i) {
float phase = 1.0f * i / init_time_steps;
for (int j = 0; j < num_joints; ++j) {
msg.motor_cmd().at(j).q(init_pos[j] * phase + current_pos[j] * (1 - phase));
msg.motor_cmd().at(j).dq(0.0f);
msg.motor_cmd().at(j).kp(kp);
msg.motor_cmd().at(j).kd(kd);
msg.motor_cmd().at(j).tau(0.0f);
}
user_ctrl_publisher->Write(msg);
std::this_thread::sleep_for(sleep_time);
}
ユーザ制御で実行したい動作が終わったら,内部制御に戻します。戻り先にはLAST,PASSIVE,WALKRUNがあります。LASTは,ユーザ制御に入る前の公式運動制御状態に戻す指定です。
client.SwitchToInternalCtrl(unitree::robot::g1::InternalFsmMode::LAST);
このようにしておくと,アプリケーション全体としてはLocoClientで公式の運動制御を扱いつつ,一部の動作だけrt/user_lowcmdで低レベル制御に近い関節制御を行う,という構成にできます。
もちろん,user_lowcmdを使えば何でも安全になる,という話ではありません。関節コマンドを送っている以上,ゲイン,目標角,速度制限,姿勢異常時の停止などはアプリケーション側で考える必要があります。また,他のプロセスから競合する制御コマンドが送られていないことも確認する必要があります。
ただ,公式の運動制御とユーザ定義の関節制御を,切り替え可能な形で扱えるのは便利です。rt/lowcmdを直接使う構成よりも,高レベル制御へ戻す流れを作りやすくなります。
5.まとめ
今回はUnitree G1でuser_lowcmdを使う制御方法を紹介しました。
G1には,高レベル制御としてLocoClientを使う方法と,低レベル制御としてrt/lowcmdにLowCmd_を投げる方法があります。これに加えて,SwitchToUserCtrl()でユーザ制御に切り替え,rt/user_lowcmdにLowCmd_をpublishする方法があります。
この方法を使うと,普段は公式の運動制御を使い,必要な区間だけユーザ定義の関節制御を行い,終わったら公式の運動制御に戻す,という構成を作れます。高レベル制御で動くアプリケーションと低レベル制御で動くアプリケーションを共存させるための手段として,使いやすい選択肢だと思います。
制御権の切り替えをSDKのAPIとして扱えるのは良い機能ですね。
次世代システム研究室では,グループ全体のインテグレーションを支援してくれるアーキテクトを募集しています。アプリケーション開発者の方,次世代システム研究室にご興味を持って頂ける方がいらっしゃいましたら,ぜひ募集職種一覧からご応募をお願いします。