> 技术文档 > 使用ROS-Unity实现宇树人型机器人电机数据实时获取_ros2 unity

使用ROS-Unity实现宇树人型机器人电机数据实时获取_ros2 unity


引言

        在机器人开发领域,ROS(Robot Operating System)作为行业标准框架,与Unity游戏引擎的结合正在开创全新的可能性。这种融合不仅让机器人仿真更加逼真,还为实时监控和控制提供了强大工具。本文将详细介绍如何配置ROS 2与Unity之间的通信系统,实现对宇树G1人型机器人电机位置和速度的实时获取。

一、系统架构概览

        系统配置:ubuntu24.04(本次项目使用ROS2与Unity进行通信,ROS也同样支持)。ROS版本为Jazzy。人型机器人模型为宇树开源模型G1(自己的模型也同样可以)。

二、环境配置

        本项目对于ROS2的安装,NIVIDA显卡驱动等不做详细叙述,详情请参考鱼香肉丝帖子进行ROS2的安装。

        Unity:对于Unity的安装以及使用和训练,参考青龙人型机器人的开源项目,链接:https://github.com/loongOpen/Unity-RL-Playgroundhttps://github.com/loongOpen/Unity-RL-Playground

该github项目详细叙述了Unity的安装以及训练方法,涵盖了多种人型机器人模型的训练以及urdf的文件描述。同时支持导入本地机器人进行强化训练开发。该项目相较于issacgym等,训练参数调整较少,训练时长明细降低。我训练400000step只花了8分钟时间,且行走效果较好。具体可参考青龙github开源项目,readme.md中有具体开发过程。

三、ROS-Unity通信配置

        ROS与Unity之间的通信采用的是TCP通讯,其项目链接:https://github.com/Unity-Technologies/Unity-Robotics-Hub/tree/main/tutorials/ros_unity_integrationhttps://github.com/Unity-Technologies/Unity-Robotics-Hub/tree/main/tutorials/ros_unity_integration可以根据项目完成ROS与Unity之间通信,这里主要来详细去介绍怎么解决运行过程中的报错,首先文件夹的存放路径需要注意,在主目录下一共存在ros2_ws(自己创建的工作空间包含ROS-TCP-Endpoint-main-ros2、unity_robotics_demo_msgs、unity_robotics_demo)、ROS_unity(包含ROS-TCP-Connector,用来在unity中导入ROS-TCP-Connector软件包)、Unity-Robotics-Hub-main(包含ros2功能包用来获取UNITY消息unity_robotics_demo_msgs、unity_robotics_demo)。

报错总结:UNITY:1.没有播放按钮,无法进行训练以及演示。 解决:显卡驱动安装失败,无法调用显卡,重新安装显卡驱动(建议使用自带的软件与更新去下载附加。版本需要选open-kernel)。

2.可以进行播放,但是演示结果机器人并没有进行走路。解决:首先查看是否导入了训练后的ONNX,如果导入之后还是这样的话,估计会显示报错:DilNotFoundException: libdl.so assembly: type: member:(null)
Assimp.Unmanaged.UnmanagedLibrary+UnmanagedLinuxLibraryImplementation.NativeLoadLibrary。解决办法:

安装必需系统库​

# Ubuntu/Debiansudo apt-get updatesudo apt-get install libc6-dev# CentOS/RHELsudo yum install glibc-devel

修复符号链接​

# 查找现有库ls -l /usr/lib/x86_64-linux-gnu/libdl.so*# 创建必要链接sudo ln -sf /usr/lib/x86_64-linux-gnu/libdl.so.2 /usr/lib/libdl.so

完成以上步骤即可解决。

3.解决报错ImportError: libpython3.13.so.1.0: cannot open shared object file: No such ......

参考博客安装Issacgym后测试examples报错:ImportError: libpython3.8.so.1.0: cannot open shared object file: No such fi-CSDN博客

解决办法为:终端命令行查找lib位置

find / -name libpython3.8.so.1.0 2>/dev/null

终端输出如下:

/home/user_name/miniconda3/envs/unitree-rl/lib/libpython3.8.so.1.0/home/user_name/miniconda3/pkgs/python-3.8.20-he870216_0/lib/libpython3.8.so.1.

根据查找到的位置,终端输入命令(替换 你的库文件路径 为实际找到的libpython3.8.so.1.0 文件的目录)

export LD_LIBRARY_PATH=/home/user_name/miniconda3/envs/unitree-rl/lib/:$LD_LIBRARY_PATH
source ~/.bashrc # 或者对应的 shell 配置文件

  我按照以上之后发现,每次打开都需进行替换,所以我们可以将其加入到bashrc文件中。

# 设置unity环境库路径export LD_LIBRARY_PATH=\"/home/user_name/miniconda3/envs/unitree-rl/lib/:$LD_LIBRARY_PATH\"

4.可以完成tcp连接,但是ros2 topic查看话题时发生报错。

raise UnsupportedTypeSupport(pkg_name)rosidl_generator_py.import_type_support_impl.UnsupportedTypeSupport: Could not import \'rosidl_typesupport_c\' for package \'unity_robotics_demo_msgs\'sgs\'

大概率是编译出现问题,解决办法:

cd ~/ros2_ws# 完全清理rm -rf build install log# 使用 Python 3.13 重新编译colcon build \\ --symlink-install \\ --merge-install \\ --cmake-args \\ -DPYTHON_EXECUTABLE=/usr/bin/python3.13 \\ -DPYTHON_LIBRARY=/usr/lib/x86_64-linux-gnu/libpython3.13.so.1.0 \\ -DPYTHON_INCLUDE_DIR=/usr/include/python3.13# 更新 .bashrcecho \"export PYTHONPATH=/usr/lib/python3.13/site-packages:\\$PYTHONPATH\" >> ~/.bashrcsource ~/.bashrc

5.报错显示:

 Usage of dash-separated \'script-dir\' will not be supported in future versions. Please use the underscore name \'script_dir\' instead. This deprecation is overdue, please update your project and remove deprecated calls to avoid build errors in the future. See https://setuptools.pypa.io/en/latest/userguide/declarative_config.html for details.

 解决办法:

# 进入包源码目录(路径可能不同)cd ~/ros2_ws/src/unity_robotics_Hub

找到unity_robotics_demoros_tcp_endpoint包中的 setup.pysetup.cfg文件。

将文件中的短横线改成下横线。

# 不推荐(即将废弃)setup(install-scripts=\'bin\', script-dir=\'bin\')# 推荐写法setup(install_scripts=\'bin\', script_dir=\'bin\')

五、电机速度、位姿脚本编写

和ROS-Unity项目中的创建脚本一样,脚本内容如下所示。

using UnityEngine;using Unity.Robotics.ROSTCPConnector;using RosMessageTypes.Sensor;using RosMessageTypes.Std; // 用于 HeaderMsgusing RosMessageTypes.BuiltinInterfaces; // 用于 TimeMsgusing System.Collections.Generic;using System.Linq;public class G1JointStatePublisher : MonoBehaviour{ [Header(\"ROS 连接设置\")] public string topicName = \"/g1/joint_states\"; [Range(10, 100)] public float publishFrequency = 50f; [Header(\"机器人配置\")] public GameObject robotRoot; public List manualJoints = new List(); private ROSConnection ros; private List joints = new List(); private Dictionary jointNameMap = new Dictionary(); private float timeElapsed; private float publishInterval; private static readonly Dictionary G1JointNameMapping = new Dictionary { {\"LF_HIP\", \"front_left_hip_joint\"}, {\"LF_THIGH\", \"front_left_thigh_joint\"}, {\"LF_CALF\", \"front_left_calf_joint\"}, {\"RF_HIP\", \"front_right_hip_joint\"}, {\"RF_THIGH\", \"front_right_thigh_joint\"}, {\"RF_CALF\", \"front_right_calf_joint\"}, {\"LH_HIP\", \"rear_left_hip_joint\"}, {\"LH_THIGH\", \"rear_left_thigh_joint\"}, {\"LH_CALF\", \"rear_left_calf_joint\"}, {\"RH_HIP\", \"rear_right_hip_joint\"}, {\"RH_THIGH\", \"rear_right_thigh_joint\"}, {\"RH_CALF\", \"rear_right_calf_joint\"} }; void Start() { publishInterval = 1f / publishFrequency; ros = ROSConnection.GetOrCreateInstance(); ros.RegisterPublisher(topicName); ConfigureJoints(); if (joints.Count == 0) { Debug.LogError(\"未配置关节! 请手动分配关节或检查机器人模型\"); } else { Debug.Log($\"已配置 {joints.Count} 个关节\"); } } void FixedUpdate() { timeElapsed += Time.fixedDeltaTime; if (timeElapsed >= publishInterval) { PublishJointStates(); timeElapsed = 0; } } private void ConfigureJoints() { joints.Clear(); jointNameMap.Clear(); if (manualJoints.Count > 0) { foreach (var joint in manualJoints) { if (joint != null) {  joints.Add(joint);  jointNameMap[joint] = FormatJointName(joint.gameObject.name); } } return; } if (robotRoot == null) { Debug.LogWarning(\"未指定机器人根对象,尝试自动查找...\"); robotRoot = GameObject.Find(\"G1\") ?? GameObject.Find(\"UnitreeG1\"); } if (robotRoot != null) { var allJoints = robotRoot.GetComponentsInChildren() .Where(j => j.jointType != ArticulationJointType.FixedJoint) .ToList(); foreach (var joint in allJoints) { joints.Add(joint); if (TryGetStandardJointName(joint.gameObject.name, out string standardName)) {  jointNameMap[joint] = standardName; } else {  jointNameMap[joint] = FormatJointName(joint.gameObject.name); } } } } private bool TryGetStandardJointName(string gameObjectName, out string jointName) { foreach (var kvp in G1JointNameMapping) { if (gameObjectName.Contains(kvp.Key)) { jointName = kvp.Value; return true; } } jointName = null; return false; } private string FormatJointName(string originalName) { return originalName .Replace(\"(Clone)\", \"\") .Replace(\" \", \"_\") .ToLower(); } private void PublishJointStates() { var jointStates = new JointStateMsg { header = new HeaderMsg { // 使用 BuiltinInterfaces.TimeMsg stamp = new TimeMsg {  sec = (int)Time.timeAsDouble,  nanosec = (uint)((Time.timeAsDouble - (int)Time.timeAsDouble) * 1e9) }, frame_id = \"g1_base_link\" } }; int count = joints.Count; jointStates.name = new string[count]; jointStates.position = new double[count]; jointStates.velocity = new double[count]; jointStates.effort = new double[count]; for (int i = 0; i  0) { manualJoints = new List(joints); Debug.Log($\"找到 {joints.Count} 个关节并添加到手动列表\"); } }}

创建完脚本后,将其拖拽到G1当中,并且在inspector对G1的关节进行添加。

在右边的manual joints中选者机器人的关节,可以先在左边点击具体关节,再确定测试的关节后将关节导入进脚本。并进行运行。重新打开终端输入:

建立通信

ros2 run ros_tcp_endpoint default_server_endpoint --ros-args -p ROS_IP:=198.168.122.1 -p ROS_TCP_PORT:=10000

发布话题后接受话题消息

ros2 topic echo /g1/joint_states