0
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?

# Windows(WSL2)上にMuJoCo + Unitree G1のシミュレーション環境を構築する

0
Posted at

はじめに

Windows 11 + WSL2(Ubuntu 26.04 LTS)上に、MuJoCoを使ったUnitree G1ヒューマノイドロボットのシミュレーション環境を構築した記録です。

  • 学習済みRLポリシーで歩かせる「お手軽ルート」
  • 実機と同じSDK(DDS通信)経由で自作の制御プログラムを動かす「開発向けルート」
  • さらにキーボードで対話的に歩行方向を指示する

という3段階で構築しました。素直には進まず、いくつもの落とし穴にハマったので、解決策込みでまとめます。

環境

  • OS: Windows 11 Home
  • WSL2ディストリビューション: Ubuntu 26.04 LTS(システムPythonは3.14)
  • GPU: 特になし(CPU推論のみ)

全体構成

ルート 使うリポジトリ できること
ルートA unitree_rl_gym 学習済みポリシーで固定速度の歩行デモを再生
ルートB unitree_mujoco + unitree_sdk2_python 実機と同じDDS経由でMuJoCo上のG1を制御

事前準備:Python 3.14問題への対応

Ubuntu 26.04 LTSはシステムPythonが3.14と新しく、cyclonedds(DDS通信ライブラリ)など一部パッケージが未対応でした。system Pythonを直接使わず、condaでPython 3.11の仮想環境を切っておくのが安全です。

mkdir -p ~/miniconda3
wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh -O ~/miniconda3/miniconda.sh
bash ~/miniconda3/miniconda.sh -b -u -p ~/miniconda3
~/miniconda3/bin/conda init bash
source ~/.bashrc

初回、Anacondaのデフォルトチャンネルの利用規約同意を求められることがあります。

conda tos accept --override-channels --channel https://repo.anaconda.com/pkgs/main
conda tos accept --override-channels --channel https://repo.anaconda.com/pkgs/r

環境を作成します。

conda create -n unitree-sim python=3.11 -y
conda activate unitree-sim

ハマりポイント①:conda activate を忘れて (base)(Python 3.14)のまま作業してしまい、原因不明のエラーに悩まされることが何度もありました。プロンプト先頭が (unitree-sim) になっているか、毎回確認するクセをつけるのがおすすめです。


ルートA:学習済みポリシーで歩行デモ

git clone https://github.com/unitreerobotics/unitree_rl_gym.git
cd unitree_rl_gym

setup.py が isaacgym(PyPIに存在しない、NVIDIAから個別入手が必要なパッケージ)を必須依存にしているため、素直に pip install -e . すると失敗します。依存解決をスキップして個別インストールします。

pip install -e . --no-deps
pip install torch mujoco pyyaml matplotlib numpy

ハマりポイント②:READMEどおり mujoco==3.2.3 と厳密にバージョン指定すると、環境によってはプリビルドwheelが無くソースビルドに失敗します(過去にWSLで経験したCMake/GLFWのビルドエラーと同種の問題)。バージョン指定を外し、使っているPythonに対応した最新版を入れるだけで解決しました。

インストール後、以下のような警告が出ますが無視してOKです(isaacgym等は学習専用で、再生には不要なため)。

ERROR: pip's dependency resolver does not currently take into account all the packages that are installed...
unitree-rl-gym 1.0.0 requires isaacgym, which is not installed.

実行:

python deploy/deploy_mujoco/deploy_mujoco.py g1.yaml

これでMuJoCoのウィンドウが開き、G1が学習済みポリシーで歩行するデモを確認できました。

Adobe Express - レコーディング 2026-09-17 235831歩いた.gif
歩いた

※このデモはキーボード操作等の対話機能は無く、YAML内の cmd_init で固定された速度で直進するだけの再生専用スクリプトです。


ルートB:SDK経由でG1を制御する

実機と同じDDS通信インターフェースでG1を動かす、開発向けのルートです。

CycloneDDSライブラリの導入

unitree_sdk2_python は cyclonedds のPythonバインディングに依存していますが、これは裏でC言語ライブラリを必要とします。まず先にビルド・インストールします。

sudo apt update
sudo apt install -y cmake build-essential

cd ~
git clone https://github.com/eclipse-cyclonedds/cyclonedds -b releases/0.10.x
cd cyclonedds && mkdir build install && cd build
cmake .. -DCMAKE_INSTALL_PREFIX=../install
cmake --build . --target install

ハマりポイント③:この手順を踏まずに pip3 install -e . すると Could not locate cyclonedds. Try to set CYCLONEDDS_HOME or CMAKE_PREFIX_PATH というエラーになります。

unitree_sdk2_pythonのインストール

conda activate unitree-sim
cd ~
git clone https://github.com/unitreerobotics/unitree_sdk2_python.git
cd unitree_sdk2_python
export CYCLONEDDS_HOME=~/cyclonedds/install
pip3 install -e .
pip3 install mujoco pygame

CYCLONEDDS_HOME はシェルセッションごとに消えるので、毎回設定するのが面倒であれば ~/.bashrc に追記しておくと良いです。

echo 'export CYCLONEDDS_HOME=~/cyclonedds/install' >> ~/.bashrc

unitree_mujocoのセットアップ

cd ~
git clone https://github.com/unitreerobotics/unitree_mujoco.git

unitree_mujoco/simulate_python/config.py を編集します。

ROBOT = "g1"
DOMAIN_ID = 0        # 実機側サンプルスクリプトがdomain 0固定のため合わせる
INTERFACE = "lo"      # ループバック
USE_JOYSTICK = 0       # ★重要。詳細は後述

ハマりポイント④(最重要):DOMAIN_ID はデフォルトで 1(実機との混線防止用)ですが、SDK付属の多くのサンプルスクリプトは ChannelFactoryInitialize(0, ...) のように domain 0固定 で書かれています。ここが食い違うと、シミュレータとクライアントが別のDDSドメインに存在することになり、エラーも出ずに何も反応しなくなります。実機を混在させないなら DOMAIN_ID = 0 に揃えるのが簡単です。

シミュレータ起動:

cd ~/unitree_mujoco/simulate_python
python3 ./unitree_mujoco.py

低レベル制御サンプルを動かす

unitree_sdk2_python/example/g1/ には high_level/(歩く・手を振る等の高レベルコマンド)と low_level/(関節PD制御)のサンプルがあります。

high_levelは動かない

high_level/g1_loco_client_example.py でコマンドを送ると、以下のようなエラーになります。

[ClientStub] send request error. id: 5493541235335

ハマりポイント⑤:unitree_mujoco のsimulate_pythonは低レベル制御のみ対応(README にも明記)です。高レベルコマンド(歩行制御・WaveHand等)は実機の内蔵ファームウェアが処理しているもので、単純な物理シミュレータには実装されていません。high_level系のサンプルはこの環境では使えないと割り切って、low_levelを使います。

low_levelサンプルの実行時エラー

cd ~/unitree_sdk2_python/example/g1/low_level
python3 g1_low_level_example.py lo

これも最初は以下でクラッシュしました。

TypeError: 'NoneType' object is not subscriptable

Init() 内の MotionSwitcherClient(実機で高レベル制御を止めるための処理)が、シミュレータには存在しないサービスに問い合わせて失敗するのが原因でした。該当箇所をコメントアウトして回避します。

    def Init(self):
        # self.msc = MotionSwitcherClient()
        # self.msc.SetTimeout(5.0)
        # self.msc.Init()

        # status, result = self.msc.CheckMode()
        # while result['name']:
        #     self.msc.ReleaseMode()
        #     status, result = self.msc.CheckMode()
        #     time.sleep(1)

        # create publisher #

ロボットが動かない → USE_JOYSTICKが原因

修正後に実行してもロボットが動かず、IMUの値も [0.0, 0.0, 0.0] のまま変化しませんでした。

ハマりポイント⑥:config.py の USE_JOYSTICK = 1(デフォルト)が原因でした。物理コントローラーが接続されていないと、内部で sys.exit() が呼ばれ、物理演算スレッドが起動直後に停止します。他のDDS配信スレッドは動き続けるため、「エラーは出ないがデータも関節も一切動かない」という分かりにくい症状になります。USE_JOYSTICK = 0 に変更し、シミュレータを再起動したところ解決しました。

立たせると倒れる → 仕様通り

USE_JOYSTICK を直すと今度は正しく物理演算が動き出し、G1が脱力して倒れました。

これは異常ではなく仕様です。g1_low_level_example.py はバランス制御を持たない「関節を直接動かすだけの検証用スクリプト」のため、重力に対して自立できません。倒れずに動きを見たい場合は config.py の ENABLE_ELASTIC_BAND = True にすると、胴体が仮想バネで宙に吊られた状態になり、関節の動きだけを確認できます(MuJoCoウィンドウ内で 9 キーがON/OFF、7/8 キーで吊り高さ調整)。


キーボードでの対話操作(歩行方向の指示)

物理ゲームパッドをWSL2で使おうとした際にもう一つ大きな壁がありました。

USB接続コントローラーの壁

usbipd-win でWindows→WSL2へUSBデバイスを転送すること自体は成功しましたが(lsusb にコントローラーが表示される)、/dev/input/ ディレクトリごと存在せず、pygame からは検出できませんでした。

ハマりポイント⑦:WSL2の標準カーネルには、USB HID/ジョイスティック入力に必要なドライバ(CONFIG_HID, CONFIG_INPUT_JOYDEV 等)が組み込まれていません。対応するにはWSL2カーネルを自前でビルドし直す必要があり、手間が大きいため今回は見送りました。

キーボード入力への切り替え

代わりに、実機用の歩行制御スクリプト unitree_rl_gym/deploy/deploy_real/deploy_real.py(ジョイスティック入力でRLポリシーに速度指令を送る仕組みを持つ)を改造し、コントローラー入力部分をキーボード入力に置き換えました。

unitree_rl_gym/deploy/deploy_real/deploy_keyboard.py として新規作成:

from legged_gym import LEGGED_GYM_ROOT_DIR
from typing import Union
import numpy as np
import time
import torch
import sys
import termios
import tty
import select
import threading

from unitree_sdk2py.core.channel import ChannelPublisher, ChannelFactoryInitialize
from unitree_sdk2py.core.channel import ChannelSubscriber, ChannelFactoryInitialize
from unitree_sdk2py.idl.default import unitree_hg_msg_dds__LowCmd_, unitree_hg_msg_dds__LowState_
from unitree_sdk2py.idl.default import unitree_go_msg_dds__LowCmd_, unitree_go_msg_dds__LowState_
from unitree_sdk2py.idl.unitree_hg.msg.dds_ import LowCmd_ as LowCmdHG
from unitree_sdk2py.idl.unitree_go.msg.dds_ import LowCmd_ as LowCmdGo
from unitree_sdk2py.idl.unitree_hg.msg.dds_ import LowState_ as LowStateHG
from unitree_sdk2py.idl.unitree_go.msg.dds_ import LowState_ as LowStateGo
from unitree_sdk2py.utils.crc import CRC

from common.command_helper import create_damping_cmd, create_zero_cmd, init_cmd_hg, init_cmd_go, MotorMode
from common.rotation_helper import get_gravity_orientation, transform_imu_data
from common.remote_controller import KeyMap
from config import Config


class KeyboardController:
    """RemoteController互換のキーボード入力版。
    w/s: 前進/後退, a/d: 左右移動, q/e: 左右旋回, space: 停止
    g: start相当(脱力解除), f: A相当(制御開始), x: select相当(終了)
    """

    def __init__(self):
        self.lx = 0.0
        self.ly = 0.0
        self.rx = 0.0
        self.ry = 0.0
        self.button = [0] * 16
        self._running = True
        self._fd = sys.stdin.fileno()
        self._old_settings = termios.tcgetattr(self._fd)
        tty.setcbreak(self._fd)
        self._thread = threading.Thread(target=self._loop, daemon=True)
        self._thread.start()
        print("[Keyboard] w/s:前後  a/d:左右  q/e:旋回  space:停止  g:start  f:A  x:select(終了)")

    def _loop(self):
        while self._running:
            if select.select([sys.stdin], [], [], 0.05)[0]:
                ch = sys.stdin.read(1)
                if ch == "w":
                    self.ly = 1.0
                elif ch == "s":
                    self.ly = -1.0
                elif ch == "a":
                    self.lx = 1.0
                elif ch == "d":
                    self.lx = -1.0
                elif ch == "q":
                    self.rx = 1.0
                elif ch == "e":
                    self.rx = -1.0
                elif ch == " ":
                    self.lx = self.ly = self.rx = 0.0
                elif ch == "g":
                    self.button[KeyMap.start] = 1
                elif ch == "f":
                    self.button[KeyMap.A] = 1
                elif ch == "x":
                    self.button[KeyMap.select] = 1

    def restore(self):
        self._running = False
        termios.tcsetattr(self._fd, termios.TCSADRAIN, self._old_settings)


class Controller:
    def __init__(self, config: Config) -> None:
        self.config = config
        self.remote_controller = KeyboardController()

        self.policy = torch.jit.load(config.policy_path)
        self.qj = np.zeros(config.num_actions, dtype=np.float32)
        self.dqj = np.zeros(config.num_actions, dtype=np.float32)
        self.action = np.zeros(config.num_actions, dtype=np.float32)
        self.target_dof_pos = config.default_angles.copy()
        self.obs = np.zeros(config.num_obs, dtype=np.float32)
        self.cmd = np.array([0.0, 0, 0])
        self.counter = 0
        self._got_state = False

        if config.msg_type == "hg":
            self.low_cmd = unitree_hg_msg_dds__LowCmd_()
            self.low_state = unitree_hg_msg_dds__LowState_()
            self.mode_pr_ = MotorMode.PR
            self.mode_machine_ = 0

            self.lowcmd_publisher_ = ChannelPublisher(config.lowcmd_topic, LowCmdHG)
            self.lowcmd_publisher_.Init()

            self.lowstate_subscriber = ChannelSubscriber(config.lowstate_topic, LowStateHG)
            self.lowstate_subscriber.Init(self.LowStateHgHandler, 10)

        elif config.msg_type == "go":
            self.low_cmd = unitree_go_msg_dds__LowCmd_()
            self.low_state = unitree_go_msg_dds__LowState_()

            self.lowcmd_publisher_ = ChannelPublisher(config.lowcmd_topic, LowCmdGo)
            self.lowcmd_publisher_.Init()

            self.lowstate_subscriber = ChannelSubscriber(config.lowstate_topic, LowStateGo)
            self.lowstate_subscriber.Init(self.LowStateGoHandler, 10)

        else:
            raise ValueError("Invalid msg_type")

        self.wait_for_low_state()

        if config.msg_type == "hg":
            init_cmd_hg(self.low_cmd, self.mode_machine_, self.mode_pr_)
        elif config.msg_type == "go":
            init_cmd_go(self.low_cmd, weak_motor=self.config.weak_motor)

    def LowStateHgHandler(self, msg: LowStateHG):
        self.low_state = msg
        self.mode_machine_ = self.low_state.mode_machine
        self._got_state = True

    def LowStateGoHandler(self, msg: LowStateGo):
        self.low_state = msg
        self._got_state = True

    def send_cmd(self, cmd: Union[LowCmdGo, LowCmdHG]):
        cmd.crc = CRC().Crc(cmd)
        self.lowcmd_publisher_.Write(cmd)

    def wait_for_low_state(self):
        while not self._got_state:
            time.sleep(self.config.control_dt)
        print("Successfully connected to the simulator.")

    def zero_torque_state(self):
        print("Enter zero torque state.")
        print("Press 'g' to continue...")
        while self.remote_controller.button[KeyMap.start] != 1:
            create_zero_cmd(self.low_cmd)
            self.send_cmd(self.low_cmd)
            time.sleep(self.config.control_dt)

    def move_to_default_pos(self):
        print("Moving to default pos.")
        total_time = 2
        num_step = int(total_time / self.config.control_dt)

        dof_idx = self.config.leg_joint2motor_idx + self.config.arm_waist_joint2motor_idx
        kps = self.config.kps + self.config.arm_waist_kps
        kds = self.config.kds + self.config.arm_waist_kds
        default_pos = np.concatenate((self.config.default_angles, self.config.arm_waist_target), axis=0)
        dof_size = len(dof_idx)

        init_dof_pos = np.zeros(dof_size, dtype=np.float32)
        for i in range(dof_size):
            init_dof_pos[i] = self.low_state.motor_state[dof_idx[i]].q

        for i in range(num_step):
            alpha = i / num_step
            for j in range(dof_size):
                motor_idx = dof_idx[j]
                target_pos = default_pos[j]
                self.low_cmd.motor_cmd[motor_idx].q = init_dof_pos[j] * (1 - alpha) + target_pos * alpha
                self.low_cmd.motor_cmd[motor_idx].qd = 0
                self.low_cmd.motor_cmd[motor_idx].kp = kps[j]
                self.low_cmd.motor_cmd[motor_idx].kd = kds[j]
                self.low_cmd.motor_cmd[motor_idx].tau = 0
            self.send_cmd(self.low_cmd)
            time.sleep(self.config.control_dt)

    def default_pos_state(self):
        print("Enter default pos state.")
        print("Press 'f' to start walking...")
        while self.remote_controller.button[KeyMap.A] != 1:
            for i in range(len(self.config.leg_joint2motor_idx)):
                motor_idx = self.config.leg_joint2motor_idx[i]
                self.low_cmd.motor_cmd[motor_idx].q = self.config.default_angles[i]
                self.low_cmd.motor_cmd[motor_idx].qd = 0
                self.low_cmd.motor_cmd[motor_idx].kp = self.config.kps[i]
                self.low_cmd.motor_cmd[motor_idx].kd = self.config.kds[i]
                self.low_cmd.motor_cmd[motor_idx].tau = 0
            for i in range(len(self.config.arm_waist_joint2motor_idx)):
                motor_idx = self.config.arm_waist_joint2motor_idx[i]
                self.low_cmd.motor_cmd[motor_idx].q = self.config.arm_waist_target[i]
                self.low_cmd.motor_cmd[motor_idx].qd = 0
                self.low_cmd.motor_cmd[motor_idx].kp = self.config.arm_waist_kps[i]
                self.low_cmd.motor_cmd[motor_idx].kd = self.config.arm_waist_kds[i]
                self.low_cmd.motor_cmd[motor_idx].tau = 0
            self.send_cmd(self.low_cmd)
            time.sleep(self.config.control_dt)

    def run(self):
        self.counter += 1
        for i in range(len(self.config.leg_joint2motor_idx)):
            self.qj[i] = self.low_state.motor_state[self.config.leg_joint2motor_idx[i]].q
            self.dqj[i] = self.low_state.motor_state[self.config.leg_joint2motor_idx[i]].dq

        quat = self.low_state.imu_state.quaternion
        ang_vel = np.array([self.low_state.imu_state.gyroscope], dtype=np.float32)

        if self.config.imu_type == "torso":
            waist_yaw = self.low_state.motor_state[self.config.arm_waist_joint2motor_idx[0]].q
            waist_yaw_omega = self.low_state.motor_state[self.config.arm_waist_joint2motor_idx[0]].dq
            quat, ang_vel = transform_imu_data(waist_yaw=waist_yaw, waist_yaw_omega=waist_yaw_omega, imu_quat=quat, imu_omega=ang_vel)

        gravity_orientation = get_gravity_orientation(quat)
        qj_obs = self.qj.copy()
        dqj_obs = self.dqj.copy()
        qj_obs = (qj_obs - self.config.default_angles) * self.config.dof_pos_scale
        dqj_obs = dqj_obs * self.config.dof_vel_scale
        ang_vel = ang_vel * self.config.ang_vel_scale
        period = 0.8
        count = self.counter * self.config.control_dt
        phase = count % period / period
        sin_phase = np.sin(2 * np.pi * phase)
        cos_phase = np.cos(2 * np.pi * phase)

        self.cmd[0] = self.remote_controller.ly
        self.cmd[1] = self.remote_controller.lx * -1
        self.cmd[2] = self.remote_controller.rx * -1

        num_actions = self.config.num_actions
        self.obs[:3] = ang_vel
        self.obs[3:6] = gravity_orientation
        self.obs[6:9] = self.cmd * self.config.cmd_scale * self.config.max_cmd
        self.obs[9 : 9 + num_actions] = qj_obs
        self.obs[9 + num_actions : 9 + num_actions * 2] = dqj_obs
        self.obs[9 + num_actions * 2 : 9 + num_actions * 3] = self.action
        self.obs[9 + num_actions * 3] = sin_phase
        self.obs[9 + num_actions * 3 + 1] = cos_phase

        obs_tensor = torch.from_numpy(self.obs).unsqueeze(0)
        self.action = self.policy(obs_tensor).detach().numpy().squeeze()

        target_dof_pos = self.config.default_angles + self.action * self.config.action_scale

        for i in range(len(self.config.leg_joint2motor_idx)):
            motor_idx = self.config.leg_joint2motor_idx[i]
            self.low_cmd.motor_cmd[motor_idx].q = target_dof_pos[i]
            self.low_cmd.motor_cmd[motor_idx].qd = 0
            self.low_cmd.motor_cmd[motor_idx].kp = self.config.kps[i]
            self.low_cmd.motor_cmd[motor_idx].kd = self.config.kds[i]
            self.low_cmd.motor_cmd[motor_idx].tau = 0

        for i in range(len(self.config.arm_waist_joint2motor_idx)):
            motor_idx = self.config.arm_waist_joint2motor_idx[i]
            self.low_cmd.motor_cmd[motor_idx].q = self.config.arm_waist_target[i]
            self.low_cmd.motor_cmd[motor_idx].qd = 0
            self.low_cmd.motor_cmd[motor_idx].kp = self.config.arm_waist_kps[i]
            self.low_cmd.motor_cmd[motor_idx].kd = self.config.arm_waist_kds[i]
            self.low_cmd.motor_cmd[motor_idx].tau = 0

        self.send_cmd(self.low_cmd)
        time.sleep(self.config.control_dt)


if __name__ == "__main__":
    import argparse

    parser = argparse.ArgumentParser()
    parser.add_argument("net", type=str, help="network interface")
    parser.add_argument("config", type=str, help="config file name in the configs folder", default="g1.yaml")
    args = parser.parse_args()

    config_path = f"{LEGGED_GYM_ROOT_DIR}/deploy/deploy_real/configs/{args.config}"
    config = Config(config_path)

    ChannelFactoryInitialize(0, args.net)

    controller = Controller(config)

    try:
        controller.zero_torque_state()
        controller.move_to_default_pos()
        controller.default_pos_state()

        while True:
            controller.run()
            if controller.remote_controller.button[KeyMap.select] == 1:
                break
    except KeyboardInterrupt:
        pass
    finally:
        create_damping_cmd(controller.low_cmd)
        controller.send_cmd(controller.low_cmd)
        controller.remote_controller.restore()
        print("Exit")

このファイルは、既存の unitree_rl_gym の deploy/deploy_real/ フォルダ内(common/, config.py, configs/g1.yaml が既にある場所)に置く必要があります。

依存関係の追加インストール

unitree_rl_gym は最初 (base) 環境(Route A用)に入れていたため、unitree-sim 環境には legged_gym パッケージや torch 等が入っておらず、以下を個別に追加しました。

conda activate unitree-sim
cd (unitree_rl_gymのクローン先)
pip install -e . --no-deps
pip install torch pyyaml scipy

tickフィールドのバグへの対応

最後にもう一つ、deploy_real.py オリジナルの wait_for_low_state() は実機のLowStateメッセージの tick(カウンタ)フィールドを見て接続確認していますが、unitree_mujoco 側のシミュレータブリッジはこの tick フィールドを一切設定していません。そのため永遠に接続待ちのまま止まってしまいます。上記コードのように、独自の _got_state フラグを使って「メッセージを1回でも受信したか」で判定するよう書き換えることで解決しました。

実行

conda activate unitree-sim
cd (unitree_rl_gymのクローン先)/deploy/deploy_real
python3 deploy_keyboard.py lo g1.yaml

レコーディング 2026-09-17 234754荒ぶるロボ.gif

荒ぶるロボット

接続は問題なく、完了しキーボード操作にも反応しているようでしたが
ただひたすらロボットを荒ぶる状態を見せられただけでした。
今回はここで限界。。次回はもうちょっと意図した動きをさせたいと思います。


まとめ:ハマりポイント一覧

# 症状 原因 対処
① conda環境を作ったつもりが反映されない conda activate 忘れ プロンプトの (env名) を都度確認
② mujoco==3.2.3 のwheelビルドエラー Python 3.14向けwheel未提供 バージョン固定を外す
③ Could not locate cyclonedds CycloneDDS Cライブラリ未導入 ソースからビルドし CYCLONEDDS_HOME を設定
④ コマンドを送っても無反応 DOMAIN_ID の不一致(sim:1 / sample:0) どちらかに統一(今回は0に統一)
⑤ [ClientStub] send request error high_level制御はシミュレータ非対応 low_levelサンプルを使う
⑥ TypeError: 'NoneType' object is not subscriptable MotionSwitcherClient がsimに存在しないサービスを呼ぶ 該当処理をコメントアウト
⑦ 何も動かない・IMUが変化しない USE_JOYSTICK=1 でコントローラー未接続時に物理演算スレッドが停止 USE_JOYSTICK=0 に変更
⑧ 起立させると倒れる low_levelサンプルにバランス制御が無い(仕様) ENABLE_ELASTIC_BAND=True で宙吊り検証 or RLポリシー(ルートA)を使う
⑨ USBゲームパッドが pygame から見えない WSL2標準カーネルにHID/ジョイスティックドライバが無い カスタムカーネルビルド(今回は見送り、キーボード代替)
⑩ Successfully connected が出ない シミュレータ側が LowState.tick を更新しない 独自の受信フラグに置き換え

所感

実機用に書かれたSDK・サンプル群を「純粋な物理シミュレータ」に接続する際は、実機側のファームウェアが暗黙に担っていた処理(高レベル制御、コントローラー安全装置との連携、状態カウンタの更新等)が抜け落ちていることに起因するハマりどころが多い、という印象でした。エラーメッセージが出ない「無反応系」のトラブルは、DDSの通信経路(ドメインID/トピック/受信判定ロジック)を疑うのが近道でした。

0
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
0
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?