| 版本 | 修订日期 | 修改内容 | 备注 |
|---|---|---|---|
| V1.0 | 2024-10-15 | 整合明确信息,正式对外 | |
| V1.1 | 2024-12-11 | WebSocket上层应用开发接口 | |
| V1.2 | 2024-12-23 | 模型训练启用pointfoot_flat地形 | |
| V1.3 | 2024-12-24 | 增加RL 训练环境 docker容器场景下启动步骤 | |
| V1.4 | 2025-3-13 | 1、上层应用接口新增里程计接口 2、上层应用接口新增调整身高接口 | robot-r-3.0.4 及以上版本 |
| V1.5 | 2025-4-17 | 1、上层应用接口新增摔倒恢复接口 2、上层应用接口notify_robot_info 新增摔倒状态 | robot-r-3.0.18 及以上版本 |
| V1.6 | 2025-7-18 | Pointfoot 改名 tron1 | github仓库名原为pointfoot-legged-gym,现已更新至tron1-rl-isaacgym,本质是同一个项目,用户无需关心其区别,参照文档操作即可 |
1. 点足SDK 概述
提示:
- limxsdk-lowlevel:在开发者模式下,用户可以使用此接口开发自己的运动控制算法,完成算法的仿真和实机无缝部署。
- 上层应用开发接口:用户可以在机器人预先安装的运动控制算法基础上,利用上层应用开发接口开发自己的软件业务功能,例如建图、导航和机器人管理等。
1.1 通讯架构图
下图呈现了开发者的电脑与机器人本体的系统组成及交互关系。开发电脑部分涵盖运控算法节点和软件业务逻辑实现模块,通过上层应用开发接口和lowlevel的数据通讯控制机器人本体的运动。机器人本体由数据交换机、主控电脑及各类硬件组件构成,主控电脑负责协调各组件运行。
2. 查看/设置机器人型号
在编译和运行RL训练、控制算法及仿真器程序时,选择正确的机器人型号至关重要。您可以通过查看机器人型号并将其设置到环境变量 ROBOT_TYPE 中,确保在不同任务中准确识别并应用相应的机器人模型。以下是查看和配置机器人型号的步骤。
-
请选择并连接您机器人的 Wi-Fi 热点,密码为:
12345678
-
在浏览器中输入
http://10.192.1.2:8080可以进入“机器人信息页”,并查看机器人信息。如下图所示,页面中显示的SN (序列号) 为PF_TRON1A_075,其中PF_TRON1A是机器人型号类型。
-
设置机器人型号:打开 Bash 终端,输入以下 Shell 命令来设置机器人型号。这样在编译和运行 RL 训练、控制算法以及仿真器程序时,您将能获取到正确的机器人型号信息。
echo 'export ROBOT_TYPE=PF_TRON1A' >> ~/.bashrc && source ~/.bashrc
3. 仿真与真机调试
提示:
这部分主要介绍利用我们的SDK中的example进行 仿真和真机调试,开发者可以参考example以及后面的接口介绍开发自己的运控程序替换我们的example来进行仿真和真机调试。建议开发好的运控程序先在gazebo中运行,效果符合预期后,再在真机上进行测试。
3.1 搭建开发环境
在算法开发者自己的电脑中,我们推荐在 **Ubuntu 20.04 **操作系统上建立基于 ROS Noetic 的算法开发环境。ROS提供了一系列工具和库,如核心库、通信库和仿真工具(如Gazebo),极大地便利了机器人算法的开发、测试和部署。这些资源为用户提供了一个丰富而完整的算法开发环境。
当然,即使没有ROS,您也可以选择在其他环境中开发自己的运动控制算法。我们提供的运动控制开发接口,是一个基于标准C++11和Python的无依赖SDK。它支持跨操作系统和平台调用开发,为开发者提供了更灵活的选择。
- ROS Noetic 安装请参考文档:https://wiki.ros.org/noetic/Installation/Ubuntu,选择“ros-noetic-desktop-full”进行安装。
- ROS Noetic 安装完成后,Bash终端输入以下Shell命令,安装开发环境所依赖的库:
```sudo apt-get update
sudo apt install ros-noetic-urdf \
ros-noetic-kdl-parser \
ros-noetic-urdf-parser-plugin \
ros-noetic-hardware-interface \
ros-noetic-controller-manager \
ros-noetic-controller-interface \
ros-noetic-controller-manager-msgs \
ros-noetic-control-msgs \
ros-noetic-ros-control \
ros-noetic-robot-state-* \
ros-noetic-joint-state-* \
ros-noetic-gazebo-* \
ros-noetic-rqt-gui \
ros-noetic-rqt-controller-manager \
ros-noetic-plotjuggler* \
ros-noetic-joy-teleop ros-noetic-joy \
cmake build-essential libpcl-dev libeigen3-dev libopencv-dev libmatio-dev \
python3-pip libboost-all-dev libtbb-dev liburdfdom-dev liborocos-kdl-dev -y
3.2 创建工作空间
可以按照以下步骤,创建一个算法开发工作空间:
- 打开一个Bash终端。
- 创建一个新目录来存放工作空间。例如,可以在用户的主目录下创建一个名为“limx_ws”的目录:
mkdir -p ~/limx_ws/src
- 下载运动控制开发接口:
cd ~/limx_ws/src
git clone https://github.com/limxdynamics/limxsdk-lowlevel.git
- 下载Gazebo仿真器:
cd ~/limx_ws/src
git clone https://github.com/limxdynamics/tron1-gazebo-ros.git
- 下载机器人模型描述文件
cd ~/limx_ws/src
git clone https://github.com/limxdynamics/robot-description.git
- 下载可视化调试工具
cd ~/limx_ws/src
git clone https://github.com/limxdynamics/robot-visualization.git
- 编译工程:
# 如您安装了Conda,请临时禁用 Conda 环境
# 因为 Conda 会干扰 ROS 的运行环境设置
conda deactivate
cd ~/limx_ws
catkin_make install
3.3 仿真调试
- 设置机器人型号:请参考“查看/设置机器人型号”章节,查看您的机器人型号。如果尚未设置,请按照以下步骤进行设置。
- 通过 Shell 命令
tree -L 1 src/robot-description/pointfoot列出可用的机器人类型:
- 通过 Shell 命令
limx@limx:~$ tree -L 1 src/robot-description/pointfoot
src/robot-description/pointfoot
├── PF_P441A
├── PF_P441B
├── PF_P441C
├── PF_P441C2
├── PF_TRON1A
├── SF_TRON1A
└── WF_TRON1A
- 以 PF_TRON1A(请根据实际机器人类型进行替换)为例,设置机器人型号类型:
echo 'export ROBOT_TYPE=PF_TRON1A' >> ~/.bashrc && source ~/.bashrc
- 运行仿真器:
# 如您安装了Conda,请临时禁用 Conda 环境
# 因为 Conda 会干扰 ROS 的运行环境设置
conda deactivate
cd ~/limx_ws
source install/setup.bash
roslaunch pointfoot_gazebo empty_world.launch
- 运行控制例程,确保仿真器中机器人有运动,说明仿真环境搭建完成:
# 如您安装了Conda,请临时禁用 Conda 环境
# 因为 Conda 会干扰 ROS 的运行环境设置
conda deactivate
cd ~/limx_ws
source install/setup.bash
rosrun limxsdk_lowlevel pf_groupJoints_move
- 注:为nanogedit
3.4 真机调试
- 打开开发者模式:机器人开机后,同时按下遥控器按键
R1 + Left,这时本体主机将会自动重启并切换机器人到开发者模式。在此模式下,用户可以开发自己的运动控制算法。模式设置掉电后不会失效,重新开机后仍然是开发者模式。遥控器切换工作模式的按键列表如下:
| 按键 | 模式 | 说明 |
|---|---|---|
| R1+Left | 开发者模式(需授权) | 用户使用运动控制开发接口开发自己的运动控制算法。 |
| R1+Right | 遥控模式 | 运行预安装的控制算法,实现复杂地形的平稳行走,如:上下台阶、过坎等。 |
- 修改开发者电脑IP:确保您的开发电脑与机器人本体通过外置网口连接。
- 设置您的电脑IP地址为:10.192.1.200,并通过Shell命令ping 10.192.1.2 能够正常ping通。如下图所示对您的开发电脑进行IP设置:
- 进行校零动作:机器人开机启动后,执行运控程序之前,请进行校零,使机器人各个关节回到初始位置。校零对应的遥控器按键为L1+R1。
- 设置机器人型号:请参考“查看/设置机器人型号”章节,查看您的机器人型号。如果尚未设置,请按照以下步骤进行设置。
- 通过 Shell 命令
tree -L 1 src/robot-description/pointfoot列出可用的机器人类型:
- 通过 Shell 命令
limx@limx:~$ tree -L 1 src/robot-description/pointfoot
src/robot-description/pointfoot
├── PF_P441A
├── PF_P441B
├── PF_P441C
├── PF_P441C2
├── PF_TRON1A
├── SF_TRON1A
└── WF_TRON1A
- 以 PF_TRON1A(请根据实际机器人类型进行替换)为例,设置机器人型号类型:
echo 'export ROBOT_TYPE=PF_TRON1A' >> ~/.bashrc && source ~/.bashrc
- 实机部署运行。按以下方式指定机器人IP地址运行例程,实现实机部署(在进行实机部署运行时,确保机器人吊装非常重要):
# 如您安装了Conda,请临时禁用 Conda 环境
# 因为 Conda 会干扰 ROS 的运行环境设置
conda deactivate
source install/setup.bash
rosrun limxsdk_lowlevel pf_groupJoints_move 10.192.1.2
3.5 可视化工具
我们提供了一套可视化调试工具,可用于仿真和实际机器部署。这些工具包括使用RViz和Plotjuggler来直观展示数据。通过我们的GitHub链接:
https://github.com/limxdynamics/robot-visualization
您可以轻松获取并使用这些工具。这些工具的使用简单直观,有助于提高开发效率和代码调试的便利性。
按照以下步骤,创建一个可视化工具工作空间:
- 打开一个Bash终端。
- 创建一个新目录来存放工作空间。例如,可以在用户的主目录下创建一个名为“limx_ws”的目录:
mkdir -p ~/limx_ws/src
- 下载机器人模型描述文件
cd ~/limx_ws/src
git clone https://github.com/limxdynamics/robot-description.git
- 下载可视化工具
cd ~/limx_ws/src
git clone https://github.com/limxdynamics/robot-visualization.git
- 编译可视化工具
# 如您安装了Conda,请临时禁用 Conda 环境
# 因为 Conda 会干扰 ROS 的运行环境设置
conda deactivate
cd ~/limx_ws
catkin_make install
3.5.1 Plotjuggler的使用
PlotJuggler 是一个功能强大的数据可视化工具,为用户提供直观的界面,方便加载、显示和分析各种类型的数据。用户可以通过图表、曲线和图形来展示数据,从而更好地理解数据之间的关系和趋势。PlotJuggler 不仅支持实时数据的可视化,还可以加载和处理大量的历史数据。官网地址:https://plotjuggler.io
- 仿真运行:运行Plotjugler的方法如下
# 如您安装了Conda,请临时禁用 Conda 环境
# 因为 Conda 会干扰 ROS 的运行环境设置
conda deactivate
source install/setup.bash
roslaunch robot_visualization pointfoot_plot_sim.launch
- 真机运行:运行Plotjugler的方法如下
- 打开pointfoot_plot_hw.launch文件,修改机器人的 IP 地址为实际机器人地址:

- 然后重新编译工具,执行以下命令:
# 如您安装了Conda,请临时禁用 Conda 环境
# 因为 Conda 会干扰 ROS 的运行环境设置
conda deactivate
cd ~/limx_ws
catkin_make install
- 编译完成后,可以通过以下 Shell 命令运行 PlotJuggler 工具:
- # 如您安装了Conda,请临时禁用 Conda 环境
# 因为 Conda 会干扰 ROS 的运行环境设置
conda deactivate
cd ~/limx_ws
source install/setup.bash
roslaunch robot_visualization pointfoot_plot_hw.launch
- 如下所示为Plotjugler的运行效果
| 数据主题 | 说明 |
|---|---|
| /ImuData | IMU 实时数据 |
| /RobotCmdPointFoot | 机器人控制指令实时数据 |
| /RobotStatePointFoot | 机器人状态实时数据 |

3.5.2 RViz的使用
在实际机器部署中,除了使用 PlotJuggler 进行数据可视化外,还可以利用 RViz 工具来实时查看机器人的运行情况。RViz 是一款强大的3D可视化工具,可用于呈现机器人的传感器数据、运动状态和环境感知。通过 RViz,用户可以实时观察 机器人的运动轨迹、传感器数据的变化情况,帮助调试和监控机器人的运行状态。在部署过程中,可以将 RViz 配置为订阅机器人的传感器数据话题,并显示在3D场景中,以便实时监测机器人的行为并进行必要的调整和优化。按以下步骤运行RViz:
- 打开pointfoot_rviz_hw.launch文件,修改机器人的 IP 地址为实际机器人地址:

- 然后重新编译工具,执行以下命令:
# 如您安装了Conda,请临时禁用 Conda 环境
# 因为 Conda 会干扰 ROS 的运行环境设置
conda deactivate
catkin_make install
- 编译完成后,可以通过以下 Shell 命令运行 RViz:
# 如您安装了Conda,请临时禁用 Conda 环境
# 因为 Conda 会干扰 ROS 的运行环境设置
conda deactivate
source install/setup.bash
roslaunch robot_visualization pointfoot_rviz_hw.launch

3.6 包/日志/埋点数据
机器人系统会自动录制:机器人的IMU数据(ImuData)、机器人状态数据(RobotStatePointFoot)以及机器人控制数据(RobotCmdPointFoot)等重要数据。这些数据对于机器人的运动控制分析至关重要。此外,机器人还会记录运行时的日志数据以及诊断埋点的结构化数据,以便在需要时进行故障排查和性能优化。
当电脑与机器人WiFi热点连接时,可以通过浏览器输入 http://10.192.1.2:8090进行访问,并从中下载这些数据。这一过程为机器人的监控、维护和调试提供了便利。
3.6.1 数据包可视化分析方法
- **数据包下载:**当您把
.bag文件下载后,您可以使用PlotJuggler可视化工具加载这些包数据进行分析。特别需要注意的是,如果您下载的是.bag.active文件,您需要使用如下Shell命令把它重新索引.bag.active文件,生成一个新的.bag文件,以便PlotJuggler加载。
rosbag reindex your_file.bag.active
mv your_file.bag.active your_file.bag
- **可视化查看:**通过Shell命令
rosrun plotjuggler plotjuggler -n启动PlotJuggler可视化工具。如下图所示加载数据包并分析数据。
3.6.2 日志及诊断埋点数据
如下图所示分别为日志和埋点结构化数据。在需要时可以用于故障排查和性能优化。

3.7 MuJoCo 仿真
MuJoCo是一款轻量级且高性能的物理仿真器,专为多关节机器人和机械系统设计。它具备高效的物理引擎,能够精确处理接触和摩擦,且无需依赖 ROS,可独立运行。凭借其高速计算能力,MuJoCo 被广泛应用于机器人仿真和强化学习,尤其适合对仿真效率要求较高的场景。以下是使用 MuJoCo 进行仿真的步骤:
3.7.1 运行MuJoCo 仿真
- 打开一个 Bash 终端。
- 下载 MuJoCo 仿真器代码:
git clone --recurse https://github.com/limxdynamics/tron1-mujoco-sim.git
- 安装运动控制开发库:
- Linux x86_64 环境
pip install tron1-mujoco-sim/limxsdk-lowlevel/python3/amd64/limxsdk-*-py3-none-any.whl
- Linux aarch64 环境
pip install tron1-mujoco-sim/limxsdk-lowlevel/python3/aarch64/limxsdk-*-py3-none-any.whl
- 设置机器人型号:请参考“查看/设置机器人型号”章节,查看您的机器人型号。如果尚未设置,请按照以下步骤进行设置。
- 通过 Shell 命令
tree -L 1 tron1-mujoco-sim/robot-description/pointfoot列出可用的机器人类型:
- 通过 Shell 命令
limx@limx:~$ tree -L 1 tron1-mujoco-sim/robot-description/pointfoot
tron1-mujoco-sim/robot-description/pointfoot
├── PF_P441A
├── PF_P441B
├── PF_P441C
├── PF_P441C2
├── PF_TRON1A
├── SF_TRON1A
└── WF_TRON1A
- 以 PF_TRON1A(请根据实际机器人类型进行替换)为例,设置机器人型号类型:
echo 'export ROBOT_TYPE=PF_TRON1A' >> ~/.bashrc && source ~/.bashrc
- 运行 MuJoCo 仿真器:
python3 tron1-mujoco-sim/simulator.py
3.7.2 运行控制器程序
- 打开一个 Bash 终端。
- 安装编译所需环境
sudo apt update
sudo apt install -y cmake build-essential
- 编译控制器SDK示例:
cd tron1-mujoco-sim/limxsdk-lowlevel
mkdir -p build
cd build
cmake ..
make
- 设置机器人型号:请参考“查看/设置机器人型号”章节,查看您的机器人型号。如果尚未设置,请按照以下步骤进行设置。
- 通过 Shell 命令
tree -L 1 tron1-mujoco-sim/robot-description/pointfoot列出可用的机器人类型:
- 通过 Shell 命令
limx@limx:~$ tree -L 1 tron1-mujoco-sim/robot-description/pointfoot
tron1-mujoco-sim/robot-description/pointfoot
├── PF_P441A
├── PF_P441B
├── PF_P441C
├── PF_P441C2
├── PF_TRON1A
├── SF_TRON1A
└── WF_TRON1A
- 以 PF_TRON1A(请根据实际机器人类型进行替换)为例,设置机器人型号类型:
echo 'export ROBOT_TYPE=PF_TRON1A' >> ~/.bashrc && source ~/.bashrc
- 运行控制器SDK示例:
./examples/pf_groupJoints_move
3.7.3 运行效果展示
3.8 仿真遥控模式功能
- 请从“下载中心”下载主控软件包并安装,下载地址:https://support.limxdynamics.com/down-center
以您下载的文件robot-tron1-r-3.4.9.20250708095546.tar.gz为例,按下面方法解压并安装到指定目录:(注意下方软件包名称,请替换为实际的软件包名称)
sudo mkdir -p /opt/limx \
&& tar -xzvf robot-tron1-r-3.4.9.20250708095546.tar.gz \
&& cd robot-tron1-r-3.4.9.20250708095546 \
&& sudo tar -xzvf robot-tron1-r-3.4.9.20250708095546.tar.gz -C /opt/limx \
&& cd .. && rm -rf robot-tron1-r-3.4.9.20250708095546 \
&& echo 'source /opt/limx/install/setup.bash' >> ~/.bashrc \
&& source ~/.bashrc
- 以 WF_TRON1A(请根据实际机器人类型进行替换)为例,设置机器人型号类型:
echo 'export ROBOT_TYPE=WF_TRON1A' >> ~/.bashrc && source ~/.bashrc
- 打开一个终端,运行仿真器:您可以根据需要选择MuJoco或Gazebo仿真器,这里我们以Gazebo仿真器为例启动仿真器。启动Gazebo仿真器之前,请按前面章节正确搭建开发环境并编译好Gazebo仿真器。
# 如您安装了Conda,请临时禁用 Conda 环境
# 因为 Conda 会干扰 ROS 的运行环境设置
conda deactivate
cd ~/limx_ws
source install/setup.bash
roslaunch pointfoot_gazebo empty_world.launch
- 打开一个终端,运行机器人算法软件。如机器人摔倒状态,通过虚拟遥控器组合按键
L2 + Y恢复。请根据机器人类型选择对应的启动命令:- WF_TRON1A 启动命令:
# 为仿真需要,设置一个假SN
setRobotSN WF_TRON1A_001
# 启动算法软件
mroslaunch ${MROS_ETC_PATH}/tron1_controllers/tron1a_wheelfoot_controllers_sim.launch
- PF_TRON1A 启动命令:
# 为仿真需要,设置一个假SN
setRobotSN PF_TRON1A_001
# 启动算法软件
mroslaunch ${MROS_ETC_PATH}/tron1_controllers/tron1a_pointfoot_controllers_sim.launch
- SF_TRON1A 启动命令:
# 为仿真需要,设置一个假SN
setRobotSN SF_TRON1A_001
# 启动算法软件
mroslaunch ${MROS_ETC_PATH}/tron1_controllers/tron1a_solefoot_controllers_sim.launch
- 打开一个终端,下载并运行虚拟遥控器
# 下载虚拟遥控器
git clone https://github.com/limxdynamics/robot-joystick.git
# 运行虚拟遥控器
./robot-joystick/robot-joystick
此时,您可以按下表虚拟遥控器按键功能操作机器人:
| 按键 | 功能 |
|---|---|
| L1 + B | 进入蹲起状态,对应遥控器“L1 + O” 按键 |
| L1 + Y | 进入行走状态,对应遥控器“L1 + △” 按键 |
| L1 + A | 进入蹲下状态,对应遥控器“L1 + X” 按键 |
| L2 + Y | 摔倒爬起,对应遥控器“L2 + △” 按键 |
| 左右摇杆 | 控制机器人行走 |
4. 底层运动控制开发接口
跨平台底层运动控制开发接口库提供统一的C++/Python API,兼容ROS1、ROS2及非ROS系统,实现运动控制算法的快速移植与部署。通过硬件抽象层和标准化通信协议,开发者可无缝切换仿真与真实硬件环境,显著降低多平台适配成本。
4.1 C++ 运动控制开发接口
4.1.1 getInstance 接口介绍
| 函数名 | getInstance |
|---|---|
| 函数原型 | static PointFoot* getInstance(); |
| 功能概述 | 获取 PointFoot 机器人类单例实例的指针 |
| 参数 | 无 |
| 返回值 | PointFoot*,指向 PointFoot 实例的指针 |
| 备注 | 使用了单例模式,确保 PointFoot 类只有一个实例存在于程序中 |
| 代码示例 |
#include <thread>
// 包含 limxsdk::PointFoot 头文件,用于引入 PointFoot 类
#include "limxsdk/pointfoot.h"
// 使用 limxsdk 命名空间,简化对 PointFoot 类的引用
using namespace limxsdk;
int main(int argc, char *argv[]){
// 获取 PointFoot 类的单例实例
PointFoot* pf = PointFoot::getInstance();
// 无限循环以保持程序运行
while (true)
{
// 休眠 1000 毫秒
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
}
return 0;
}
4.1.2 init接口介绍
| 函数名 | init |
|---|---|
| 函数原型 | bool init(const std::string& robot_ip_address = "127.0.0.1"); |
| 功能概述 | 初始化运动控制算法程序的通信运行环境,通常在主函数中调用其它接口之前调用,完成初始化工作。 |
| 参数 | robot_ip_address:机器人的 IP 地址。对于仿真,通常设置为 "127.0.0.1",而对于真实机器人,可能设置为 "10.192.1.2"。 |
| 返回值 | 如果初始化成功,则返回 true;否则返回 false。 |
| 备注 | 无 |
| 代码示例 |
#include <thread>
// 包含 limxsdk::PointFoot 头文件,用于引入 PointFoot 类
#include "limxsdk/pointfoot.h"
// 使用 limxsdk 命名空间,简化对 PointFoot 类的引用
using namespace limxsdk;
int main(int argc, char *argv[]){
// 获取 PointFoot 类的单例实例
PointFoot* pf = PointFoot::getInstance();
// 默认机器人 IP 地址
std::string robot_ip = "127.0.0.1";
if (argc > 1)
{
// 如果提供了命令行参数,则使用命令行参数作为机器人 IP 地址
robot_ip = argv[1];
}
// 初始化运动控制算法程序的通信运行环境
if (!pf->init(robot_ip))
{
// 如果初始化失败,则退出程序
exit(1);
}
// 无限循环以保持程序运行
while (true)
{
// 休眠 1000 毫秒
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
}
return 0;
}
4.1.3 getMotorNumber接口介绍
| 函数名 | getMotorNumber |
|---|---|
| 函数原型 | uint32_t getMotorNumber(); |
| 功能概述 | 获取机器人中的电机数量。 |
| 参数 | 无 |
| 返回值 | 返回一个无符号整数,表示机器人中的总电机数量。 |
| 备注 | 通常情况下,点足机器人的电机数量为6个 |
| 代码示例 |
#include <thread>
// 包含 limxsdk::PointFoot 头文件,用于引入 PointFoot 类
#include "limxsdk/pointfoot.h"
// 使用 limxsdk 命名空间,简化对 PointFoot 类的引用
using namespace limxsdk;
int main(int argc, char *argv[]){
// 获取 PointFoot 类的单例实例
PointFoot* pf = PointFoot::getInstance();
// 默认机器人 IP 地址
std::string robot_ip = "127.0.0.1";
if (argc > 1)
{
// 如果提供了命令行参数,则使用命令行参数作为机器人 IP 地址
robot_ip = argv[1];
}
// 初始化运动控制算法程序的通信运行环境
if (!pf->init(robot_ip))
{
// 如果初始化失败,则退出程序
exit(1);
}
// 获取机器人中的电机数量
uint32_t motor_num = pf->getMotorNumber();
// 无限循环以保持程序运行
while (true)
{
// 休眠 1000 毫秒
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
}
return 0;
}
4.1.4 subscribeImuData 接口介绍
| 函数名 | subscribeImuData |
|---|---|
| 函数原型 | void subscribeImuData(std::function<void(const ImuDataConstPtr&)> cb); |
| 功能概述 | 订阅机器人的 IMU数据,并在接收到新的 IMU 数据时调用指定的回调函数。 |
| 参数 | cb: 用于处理新 IMU 数据的回调函数。 |
| 返回值 | 无 |
备注:ImuData 数据结构原型如下:
/**
* @struct ImuData
*
* @brief 表示基于传感器反馈的机器人 IMU 数据的结构体。
*
* 此结构体封装了 IMU 数据,包括加速度计、陀螺仪和四元数。
*/
struct ImuData {
uint64_t stamp; // 时间戳,以纳秒为单位,通常表示记录或生成此数据时的时间。
float acc[3]; // 用于存储 IMU 加速度计数据,以跟踪沿三个轴(X、Y、Z)的线性加速度。
float gyro[3]; // 用于存储 IMU 陀螺仪数据,以跟踪沿三个轴(X、Y、Z)的角速度或旋转速度。
float quat[4]; // 用于存储 IMU 四元数数据,表示在三维空间中的方向(w、x、y、z)。
};
// 智能指针类型别名
typedef std::shared_ptr<ImuData> ImuDataPtr;
typedef std::shared_ptr<ImuData const> ImuDataConstPtr;
代码示例
#include <thread>
// 包含 limxsdk::PointFoot 头文件,用于引入 PointFoot 类
#include "limxsdk/pointfoot.h"
// 使用 limxsdk 命名空间,简化对 PointFoot 类的引用
using namespace limxsdk;
int main(int argc, char *argv[]){
// 获取 PointFoot 类的单例实例
PointFoot* pf = PointFoot::getInstance();
// 默认机器人 IP 地址
std::string robot_ip = "127.0.0.1";
if (argc > 1)
{
// 如果提供了命令行参数,则使用命令行参数作为机器人 IP 地址
robot_ip = argv[1];
}
// 初始化运动控制算法程序的通信运行环境
if (!pf->init(robot_ip))
{
// 如果初始化失败,则退出程序
exit(1);
}
// 订阅机器人状态更新,并指定回调函数
pf->subscribeImuData([&](const ImuDataConstPtr& msg) {
// 在这里处理接收到的 ImuData 数据
// 注意:回调函数会在收到ImuData时被调用
});
// 无限循环以保持程序运行
while (true)
{
// 休眠 1000 毫秒
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
}
return 0;
}
4.1.5 subscribeRobotState接口介绍
| 函数名 | subscribeRobotState |
|---|---|
| 函数原型 | void subscribeRobotState(std::function<void(const RobotStateConstPtr&)> cb); |
| 功能概述 | 订阅接收关于机器人状态的更新。 |
| 参数 | cb:回调函数,当接收到机器人状态更新时将被调用。回调函数参数指向 RobotState 对象的常量指针。 |
| 返回值 | 无 |
备注:RobotState 数据结构原型如下:
/**
* @struct RobotState
*
* @brief 代表基于传感器反馈的机器人状态的结构体。
*
* 此结构封装了各种数据点,可用于监控和控制机器人,包括 IMU 数据(加速度计、陀螺仪、四元数)、输出扭矩、当前角度和速度等。
*/
struct RobotState {
// 默认构造函数
RobotState() { }
// 带参数的构造函数,用于初始化向量大小为 motor_num 的 tau、q、dq 向量,初始值均为 0.0
RobotState(int motor_num)
: tau(motor_num, 0.0)
, q(motor_num, 0.0)
, dq(motor_num, 0.0) { }
uint64_t stamp; // 时间戳,通常表示记录或生成这些数据的时间,以纳秒为单位
std::vector<float> tau; // 用于存储当前估计的输出扭矩(以牛顿米为单位)的向量
std::vector<float> q; // 用于存储当前角度(以弧度为单位)的向量
std::vector<float> dq; // 用于存储当前速度(以弧度每秒为单位)的向量
};
// 智能指针类型别名
typedef std::shared_ptr<RobotState> RobotStatePtr;
typedef std::shared_ptr<RobotState const> RobotStateConstPtr;
机器人状态数据tau、q、dq数组对应的电机顺序如下:
点足
0: abad_L_Joint, 1: hip_L_Joint, 2: knee_L_Joint
3: abad_R_Joint, 4: hip_R_Joint, 5: knee_R_Joint
点轮足
0: abad_L_Joint, 1: hip_L_Joint, 2: knee_L_Joint, 3: wheel_L_Joint
4: abad_R_Joint, 5: hip_R_Joint, 6: knee_R_Joint, 6: wheel_R_Joint
点双足
0: abad_L_Joint, 1: hip_L_Joint, 2: knee_L_Joint, 3: ankle_L_Joint
4: abad_R_Joint, 5: hip_R_Joint, 6: knee_R_Joint, 6: ankle_R_Joint
代码示例
#include <thread>
// 包含 limxsdk::PointFoot 头文件,用于引入 PointFoot 类
#include "limxsdk/pointfoot.h"
// 使用 limxsdk 命名空间,简化对 PointFoot 类的引用
using namespace limxsdk;
int main(int argc, char *argv[]){
// 获取 PointFoot 类的单例实例
PointFoot* pf = PointFoot::getInstance();
// 默认机器人 IP 地址
std::string robot_ip = "127.0.0.1";
if (argc > 1)
{
// 如果提供了命令行参数,则使用命令行参数作为机器人 IP 地址
robot_ip = argv[1];
}
// 初始化运动控制算法程序的通信运行环境
if (!pf->init(robot_ip))
{
// 如果初始化失败,则退出程序
exit(1);
}
// 订阅机器人状态更新,并指定回调函数
pf->subscribeRobotState([&](const RobotStateConstPtr& msg) {
// 在这里处理接收到的 RobotState 数据
// 注意:回调函数会在收到机器人状态更新时被调用
});
// 无限循环以保持程序运行
while (true)
{
// 休眠 1000 毫秒
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
}
return 0;
}
4.1.6 publishRobotCmd接口介绍
| 函数名 | publishRobotCmd |
|---|---|
| 函数原型 | bool publishRobotCmd(const RobotCmd& cmd); |
| 功能概述 | 发布一个命令来 控制机器人的动作。 |
| 参数 | cmd:表示所需机器人命令的 RobotCmd 对象参数如下。![]() |
| 返回值 | 无 |
备注
cmd:表示所需机器人命令的 RobotCmd 对象。
命令数据数组对应的电机顺序如下:
- 点足 0: abad_L_Joint, 1: hip_L_Joint, 2: knee_L_Joint 3: abad_R_Joint, 4: hip_R_Joint, 5: knee_R_Joint
- 点轮足 0: abad_L_Joint, 1: hip_L_Joint, 2: knee_L_Joint, 3: wheel_L_Joint 4: abad_R_Joint, 5: hip_R_Joint, 6: knee_R_Joint, 6: wheel_R_Joint
- 点双足 0: abad_L_Joint, 1: hip_L_Joint, 2: knee_L_Joint, 3: ankle_L_Joint 4: abad_R_Joint, 5: hip_R_Joint, 6: knee_R_Joint, 6: ankle_R_Joint
- RobotCmd 数据结构原型如下:
/**
* @struct RobotCmd
*
* @brief 代表控制机器人的命令的结构体。
*
* 这个结构体包含了可以用于控制机器人的各种命令,包括期望的工作模式、期望的角度、期望的速度、期望的输出扭矩、期望的位置刚度和期望的速度刚度。
*/
struct RobotCmd {
RobotCmd() { }
RobotCmd(int motor_num)
: mode(motor_num, 0)
, q(motor_num, 0.0)
, dq(motor_num, 0.0)
, tau(motor_num, 0.0)
, Kp(motor_num, 0.0)
, Kd(motor_num, 0.0) { }
uint64_t stamp; // 时间戳(以纳秒为单位),通常表示记录或生成数据时的时间。
std::vector<uint8_t> mode; // 0: 力矩模式控制;1:速度模式控制;2:位置模式控制,默认设置为:0
std::vector<float> q; // 存储期望角度的向量(以弧度为单位)。
std::vector<float> dq; // 存储期望速度的向量(以弧度每秒为单位)。
std::vector<float> tau; // 存储期望输出扭矩的向量(以牛顿米为单位)。
std::vector<float> Kp; // 存储期望位置刚度的向量(以牛顿米每弧度为单位)。
std::vector<float> Kd; // 存储期望速度刚度的向量(以牛顿米每弧度每秒为单位)。
};
// 智能指针类型别名
typedef std::shared_ptr<RobotCmd> RobotCmdPtr;
typedef std::shared_ptr<RobotCmd const> RobotCmdConstPtr;
代码示例
#include <thread>
// 包含 limxsdk::PointFoot 头文件,用于引入 PointFoot 类
#include "limxsdk/pointfoot.h"
// 使用 limxsdk 命名空间,简化对 PointFoot 类的引用
using namespace limxsdk;
int main(int argc, char *argv[]){
// 获取 PointFoot 类的单例实例
PointFoot* pf = PointFoot::getInstance();
// 默认机器人 IP 地址
std::string robot_ip = "127.0.0.1";
if (argc > 1)
{
// 如果提供了命令行参数,则使用命令行参数作为机器人 IP 地址
robot_ip = argv[1];
}
// 初始化运动控制算法程序的通信运行环境
if (!pf->init(robot_ip))
{
// 如果初始化失败,则退出程序
exit(1);
}
// 获取机器人中的电机数量
uint32_t motor_num = pf->getMotorNumber();
// 创建一个包含机器人电机数量的 RobotCmd 对象
RobotCmd cmd(motor_num);
// 发布控制指令
pf->publishRobotCmd(cmd);
// 无限循环以保持程序运行
while (true)
{
// 休眠 1000 毫秒
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
}
return 0;
}
4.1.7 subscribeSensorJoy 接口介绍
| 函数名 | subscribeSensorJoy |
|---|---|
| 函数原型 | void subscribeSensorJoy(std::function<void(const SensorJoyConstPtr&)> cb); |
| 功能概述 | 在真机部署中,该方法用于订阅来自机器人遥控器的数据。当机器人接收到遥控器的数据时,将会调用指定的回调函数,并传递包含遥控器数据的 SensorJoy 结构体常量指针给回调函数进行处理。 |
| 参数 | cb: 表示回调函数,用于接收机器人遥控器的数据。回调函数的参数类型为 SensorJoyConstPtr,即指向 SensorJoy 结构体常量的共享指针。 |
| 返回值 | 无 |
备注
- SensorJoy 数据结构原型如下:
/**
* @struct SensorJoy
*
* @brief 机器人遥控器数据的结构体。
*
* 该结构体包含了与遥控器相关的时间戳信息,以及摇杆和按钮值。
*/
struct SensorJoy {
uint64_t stamp; // 与传感器输入相关的时间戳,单位为纳秒。
std::vector<float> axes; // 表示操纵摇杆的值。
std::vector<int32_t> buttons; // 表示操纵按钮状态的值。
};
// SensorJoy 智能指针类型的定义
typedef std::shared_ptr<SensorJoy> SensorJoyPtr;
typedef std::shared_ptr<const SensorJoy> SensorJoyConstPtr;
- 遥控器摇杆映射
| 摇杆名 | Axes Index |
|----------------|-------------|
| left_horizon | 0 |
|----------------|-------------|
| left_vertival | 1 |
|----------------|-------------|
| right_horizon | 2 |
|----------------|-------------|
| right_vertival | 3 |
|----------------|-------------|
- 遥控器按键映射
| 按键名 | Buttons Index |
|----------------|----------------|
| X | 0 |
|----------------|----------------|
| 〇 | 1 |
|----------------|----------------|
| 口 | 2 |
|----------------|----------------|
| △ | 3 |
|----------------|----------------|
| L1 | 4 |
|----------------|----------------|
| L2 | 6 |
|----------------|----------------|
| R1 | 7 |
|----------------|----------------|
| R2 | 5 |
|----------------|----------------|
| SELECT | 8 |
|----------------|----------------|
| START | 9 |
|----------------|----------------|
| UP | 12 |
|----------------|----------------|
| DOWN | 13 |
|----------------|----------------|
| LEFT | 14 |
|----------------|----------------|
| RIGHT | 15 |
|----------------|----------------|
| MENU | 16 |
|----------------|----------------|
| BACK | 17 |
|----------------|----------------|
- 适配您自己的按键逻辑时,建议不要和系统预留按键功能冲突了。开发者模式下系统预留按键功能:
| 按键名 | 功能说明
|----------------|-------------------------------
| L1 + R1 | 机器人校零
|----------------|-------------------------------
| R1 + Left | 打开开发者模式
|----------------|-------------------------------
| R1 + Right | 打开运动控制模式
|----------------|-------------------------------
| 左 + 右摇杆按键 | 机器人紧急停止
|----------------|-------------------------------
| 右摇杆按键 | 解除机器人紧急停止
|----------------|-------------------------------
代码示例
#include <thread>
// 包含 limxsdk::PointFoot 头文件,用于引入 PointFoot 类
#include "limxsdk/pointfoot.h"
// 使用 limxsdk 命名空间,简化对 PointFoot 类的引用
using namespace limxsdk;
int main(int argc, char *argv[]){
// 获取 PointFoot 类的单例实例
PointFoot* pf = PointFoot::getInstance();
// 默认机器人 IP 地址
std::string robot_ip = "127.0.0.1";
if (argc > 1)
{
// 如果提供了命令行参数,则使用命令行参数作为机器人 IP 地址
robot_ip = argv[1];
}
// 初始化运动控制算法程序的通信运行环境
if (!pf->init(robot_ip))
{
// 如果初始化失败,则退出程序
exit(1);
}
// 订阅机器人遥控器数据
pf->subscribeSensorJoy([&](const limxsdk::SensorJoyConstPtr &joy) {
// L1 & R1 按下
if (joy->buttons[4] == 1 && joy->buttons[7] == 1)
{
// 在这里执行相关操作
}
// 处理摇杆数据
double axes_left_horizontal = joy->axes[0];
double axes_left_vertical = joy->axes[1];
double axes_right_horizontal = joy->axes[2];
double axes_right_vertical = joy->axes[3];
});
// 无限循环以保持程序运行
while (true)
{
// 休眠 1000 毫秒
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
}
return 0;
}
4.1.8 subscribeDiagnosticValue 接口介绍
| 函数名 | subscribeDiagnosticValue |
|---|---|
| 函数原型 | void subscribeDiagnosticValue(std::function<void(const DiagnosticValueConstPtr&)> cb); |
| 功能概述 | 在真机部署中,该方法用于订阅机器人的诊断值和状态信息。当机器人发出诊断值时,系统会调用指定的回调函数,并传递包含诊断值的 DiagnosticValue 结构体常量指针给回调函数进行处理。这可以帮助实时监控机器人的健康状态,并及时做出反应以处理可能的问题。 |
| 参数 | cb: 用于接收机器人诊断值的回调函数,其参数类型为 DiagnosticValueConstPtr,即指向 DiagnosticValue 结构体常量的共享指针。DiagnosticValue 结构体包含了机器人诊断值的信息,包括时间戳、级别、名称、代码和消息字段。 |
| 返回值 | 无 |
备注
- DiagnosticValue 数据结构原型如下:
/**
* @struct DiagnosticValue
*
* @brief 结构体,表示诊断值。
*
* 此结构体包含有关诊断级别、名称、代码和消息的信息。
*/
struct DiagnosticValue {
enum { OK = 0 }; // 正常状态的诊断级别
enum { WARN = 1 }; // 警告状态的诊断级别
enum { ERROR = 2 }; // 错误状态的诊断级别
uint64_t stamp; // 时间戳,单位为纳秒。
int32_t level; // 与诊断值相关联的级别。
std::string name; // 标识诊断值的名称。
int32_t code; // 与诊断值对应的代码。
std::string message; // 与诊断值相关的详细消息。
};
// DiagnosticValue 智能指针类型的定义
typedef std::shared_ptr<DiagnosticValue> DiagnosticValuePtr;
typedef std::shared_ptr<DiagnosticValue const> DiagnosticValueConstPtr;
- 常见诊断数据
| name | level | code | message
|-------------|--------|------|---------------------------------
| version | OK | 0 | - 包含软件版本信息
|-------------|--------|------|---------------------------------
| imu | OK | 0 | - IMU 工作正常
| imu | ERROR | -1 | - IMU 工作异常,包含异常信息
|-------------|--------|------|---------------------------------
| ethercat | OK | 0 | - EtherCAT 工作正常
| ethercat | ERROR | -1 | - EtherCAT 工作异常,包含异常信息
|-------------|--------|------|---------------------------------
| calibration | OK | 0 | - 机器人完成校零
| calibration | WARN | 1 | - 提示:机器人正在校准零中
| calibration | ERROR | -1 | - 错误:机器人校准零失败,包含失败原因
|-------------|--------|------|----------------------------------
代码示例
#include <thread>
// 包含 limxsdk::PointFoot 头文件,用于引入 PointFoot 类
#include "limxsdk/pointfoot.h"
// 使用 limxsdk 命名空间,简化对 PointFoot 类的引用
using namespace limxsdk;
int main(int argc, char *argv[]){
// 获取 PointFoot 类的单例实例
PointFoot* pf = PointFoot::getInstance();
// 默认机器人 IP 地址
std::string robot_ip = "127.0.0.1";
if (argc > 1)
{
// 如果提供了命令行参数,则使用命令行参数作为机器人 IP 地址
robot_ip = argv[1];
}
// 初始化运动控制算法程序的通信运行环境
if (!pf->init(robot_ip))
{
// 如果初始化失败,则退出程序
exit(1);
}
// 订阅机器人诊断数据
pf->subscribeDiagnosticValue([&](const DiagnosticValueConstPtr& msg) {
// 在这里处理机器人诊断值
// 例如,可以根据诊断值的级别和消息来采取相应的措施
std::cout << "Diagnostic Value: " << msg->name << std::endl;
std::cout << "Level: " << msg->level << std::endl;
std::cout << "Code: " << msg->code << std::endl;
std::cout << "Message: " << msg->message << std::endl;
});
// 无限循环以保持程序运行
while (true)
{
// 休眠 1000 毫秒
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
}
return 0;
}
4.1.9 setRobotLightEffect 接口介绍
| 函数名 | setRobotLightEffect |
|---|---|
| 函数原型 | bool setRobotLightEffect(int effect); |
| 功能概述 | 在真机部署中,该方法用于设置机器人的灯光效果。 |
| 参数 | effect: 一个整数,表示所需的机器人灯光效果,具体定义见 PointFoot::LightEffect 枚举。 |
| 返回值 | bool: 指示机器人灯光效果是否成功设置。 |
备注
PointFoot::LightEffect 枚举定义:
enum LightEffect : int {
STATIC_RED = 0, // 静态红光
STATIC_GREEN, // 静态绿光
STATIC_BLUE, // 静态蓝光
STATIC_CYAN, // 静态青光
STATIC_PURPLE, // 静态紫光
STATIC_YELLOW, // 静态黄光
STATIC_WHITE, // 静态白光
LOW_FLASH_RED, // 红光闪烁(慢闪)
LOW_FLASH_GREEN, // 绿光闪烁(慢闪)
LOW_FLASH_BLUE, // 蓝光闪烁(慢闪)
LOW_FLASH_CYAN, // 青光闪烁(慢闪)
LOW_FLASH_PURPLE, // 紫光闪烁(慢闪)
LOW_FLASH_YELLOW, // 黄光闪烁(慢闪)
LOW_FLASH_WHITE, // 白光闪烁(慢闪)
FAST_FLASH_RED, // 红光闪烁(快速闪)
FAST_FLASH_GREEN, // 绿光闪烁(快速闪)
FAST_FLASH_BLUE, // 蓝光闪烁(快速闪)
FAST_FLASH_CYAN, // 青光闪烁(快速闪)
FAST_FLASH_PURPLE, // 紫光闪烁(快速闪)
FAST_FLASH_YELLOW, // 黄光闪烁(快速闪)
FAST_FLASH_WHITE // 白光闪烁(快速闪)
};
代码示例
#include <thread>
// 包含 limxsdk::PointFoot 头文件,用于引入 PointFoot 类
#include "limxsdk/pointfoot.h"
// 使用 limxsdk 命名空间,简化对 PointFoot 类的引用
using namespace limxsdk;
int main(int argc, char *argv[]){
// 获取 PointFoot 类的单例实例
PointFoot* pf = PointFoot::getInstance();
// 默认机器人 IP 地址
std::string robot_ip = "127.0.0.1";
if (argc > 1)
{
// 如果提供了命令行参数,则使用命令行参数作为机器人 IP 地址
robot_ip = argv[1];
}
// 初始化运动控制算法程序的通信运行环境
if (!pf->init(robot_ip))
{
// 如果初始化失败,则退出程序
exit(1);
}
// 设置机器人灯光效果为静态红光
robot->setRobotLightEffect(limxsdk::PointFoot::STATIC_RED);
// 无限循环以保持程序运行
while (true)
{
// 休眠 1000 毫秒
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
}
return 0;
}
4.1.10 参考例程
- 单关节控制例程:
https://github.com/limxdynamics/limxsdk-lowlevel/blob/master/examples/pf_joint_move.cpp - 多关节控制例程:
https://github.com/limxdynamics/limxsdk-lowlevel/blob/master/examples/pf_groupJoints_move.cpp
4.2 Python 运动控制开发接口
4.2.1 概述
提供与C++相同功能的Python运动控制算法开发接口,使得不熟悉C++编程语言的开发者能够使用Python进行运动控制算法的开发。
Python语言易于学习,具有简洁清晰的语法和丰富的第三方库,使开发者能够更快速地上手并迅速实现算法。通过Python接口,开发者可以利用Python的动态特性进行快速原型设计和实验验证,加速算法的迭代和优化过程。同时,Python的跨平台性和强大的生态系统支持,使得运动算法能够更广泛地应用于不同平台和环境。
此外,RL(强化学习)模型的快速部署到仿真和真机环境中也得益于Python的灵活性,开发者可以使用Python轻松地将RL模型集成到各种仿真平台和真实硬件中,实现快速迭代和验证算法的性能。
4.2.2 安装运动控制开发库
- Linux x86_64 环境
git clone https://github.com/limxdynamics/limxsdk-lowlevel.git
pip install limxsdk-lowlevel/python3/amd64/limxsdk-*-py3-none-any.whl
- Linux aarch64 环境
git clone https://github.com/limxdynamics/limxsdk-lowlevel.git
pip install limxsdk-lowlevel/python3/aarch64/limxsdk-*-py3-none-any.whl
- Windows 环境
git clone https://github.com/limxdynamics/limxsdk-lowlevel.git
pip install limxsdk-lowlevel/python3/win/limxsdk-*-py3-none-any.whl
4.2.3 init 接口介绍
| 函数名 | init |
|---|---|
| 函数原型 | def __init__(self, robot_type: robot.RobotType) |
| 功能概述 | 在初始化时,指定机器人的类型,并创建一个相应类型的本地机器人实例。 |
| 参数 | robot_type:表示机器人类型的枚举值,点足为RobotType.PointFoot。 |
| 返回值 | 无 |
| 备注 | 无 |
代码示例
import sys
import limxsdk.robot.Robot as Robot
import limxsdk.robot.RobotType as RobotType
if __name__ == '__main__':
# 创建一个类型为PointFoot的Robot实例
robot = Robot(RobotType.PointFoot)
4.2.4 init 接口介绍
| 函数名 | init |
|---|---|
| 函数原型 | def init(self, robot_ip: str = "127.0.0.1") |
| 功能概述 | 初始化运动控制算法程序的通信运行环境,通常在主函数中调用其它接口之前调用,完成初始化工作。 |
| 参数 | robot_ip:机器人的 IP 地址。对于仿真,通常设置为 "127.0.0.1",而对于真实机器人,可能设置为 "10.192.1.2"。 |
| 返回值 | 成功:返回True 失败:返回False |
| 备注 | 无 |
代码示例
import sys
import limxsdk.robot.Robot as Robot
import limxsdk.robot.RobotType as RobotType
if __name__ == '__main__':
# 创建一个类型为PointFoot的Robot实例
robot = Robot(RobotType.PointFoot)
robot_ip = "127.0.0.1"
# 检查是否提供了命令行参数作为机器人IP
if len(sys.argv) > 1:
robot_ip = sys.argv[1]
# 使用IP地址初始化机器人的通信运行环境
if not robot.init(robot_ip):
sys.exit()
4.2.5 getMotorNumber 接口介绍
| 函数名 | getMotorNumber |
|---|---|
| 函数原型 | def getJointLimit(self, timeout: float = -1.0) |
| 功能概述 | 获取机器人中的电机数量。 |
| 参数 | 无 |
| 返回值 | 返回一个无符号整数,表示机器人中的总电机数量。 |
| 备注 | 通常情况下,点足机器人的电机数量为6个 |
| 代码示例 |
import sys
import limxsdk.robot.Robot as Robot
import limxsdk.robot.RobotType as RobotType
if __name__ == '__main__':
# 创建一个 PointFoot 类型的 Robot 实例
robot = Robot(RobotType.PointFoot)
robot_ip = "127.0.0.1"
# 检查是否提供了机器人 IP 的命令行参数
if len(sys.argv) > 1:
robot_ip = sys.argv[1]
# 使用 robot_ip 初始化机器人
if not robot.init(robot_ip):
sys.exit()
# 获取机器人中的电机数量
motor_number = robot.getMotorNumber()
4.2.6 subscribeImuData 接口介绍
| 函数名 | subscribeImuData |
|---|---|
| 函数原型 | def subscribeImuData(self, callback: Callable[[datatypes.ImuData], Any]) |
| 功能概述 | 订阅机器人的 IMU数据,并在接收到新的 IMU 数据时调用指定的回调函数。 |
| 参数 | callback:用于处理新 IMU 数据的回调函数。 |
| 返回值 | 成功:返回True 失败:返回False |
备注
datatypes.ImuData 数据结构原型如下:
import sys
class ImuData(object):
__slots__ = ['stamp','acc','gyro','quat']
def __init__(self):
self.stamp = 0 # 时间戳,通常表示记录或生成这些数据的时间,以纳秒为单位
self.acc = [0. for x in range(0, 3)] # 用于存储 IMU(惯性测量单元)加速度计数据,用于跟踪三个轴上的线性加速度
self.gyro = [0. for x in range(0, 3)] # 用于存储 IMU 陀螺仪数据,用于跟踪角速度或旋转速度
self.quat = [0. for x in range(0, 4)] # 用于存储 IMU 四元数数据,表示在三维空间中的方向
代码示例
import sys
from functools import partial
import limxsdk.robot.Robot as Robot
import limxsdk.robot.RobotType as RobotType
import limxsdk.datatypes as datatypes
class RobotReceiver:
# 订阅机器人的 IMU数据
def imuDataCallback(self, imu: datatypes.ImuData):
print("\n------\nrobot_state:" + \
"\n stamp: " + str(imu.stamp) + \
"\n acc: " + str(imu.acc) + \
"\n gyro: " + str(imu.gyro) + \
"\n quat: " + str(imu.quat))
if __name__ == '__main__':
# 创建一个 Robot 实例,类型为 PointFoot
robot = Robot(RobotType.PointFoot)
robot_ip = "127.0.0.1"
# 检查是否提供了机器人 IP 的命令行参数
if len(sys.argv) > 1:
robot_ip = sys.argv[1]
# 使用 robot_ip 初始化机器人
if not robot.init(robot_ip):
sys.exit()
# 创建一个 RobotReceiver 实例来处理回调
receiver = RobotReceiver()
# 创建回调函数的 partial 函数
imuDataCallback = partial(receiver.imuDataCallback)
# 订阅机器人IMU数据
robot.subscribeImuData(imuDataCallback)
# 休眠 1 秒,防止程序退出
import time
while True:
time.sleep(1)
4.2.7 subscribeRobotState 接口介绍
| 函数名 | subscribeRobotState |
|---|---|
| 函数原型 | def subscribeRobotState(self, callback: Callable[[datatypes.RobotState], Any]) |
| 功能概述 | 订阅接收关于机器人状态的更新。 |
| 参数 | callback:回调函数,当接收到机器人状态更新时将被调用。回调函数参数指向datatypes.RobotState 对象。机器人状态数据数组对应的电机顺序如下![]() |
| 返回值 | 成功:返回True 失败:返回False |
备注:
Callback回调函数
callback:回调函数,当接收到机器人状态更新时将被调用。
回调函数参数指向datatypes.RobotState 对象。
- 机器人状态数据数组对应的电机顺序如下:
- 点足 0: abad_L_Joint, 1: hip_L_Joint, 2: knee_L_Joint 3: abad_R_Joint, 4: hip_R_Joint, 5: knee_R_Joint
- 点轮足 0: abad_L_Joint, 1: hip_L_Joint, 2: knee_L_Joint, 3: wheel_L_Joint 4: abad_R_Joint, 5: hip_R_Joint, 6: knee_R_Joint, 6: wheel_R_Joint
- 点双足 0: abad_L_Joint, 1: hip_L_Joint, 2: knee_L_Joint, 3: ankle_L_Joint 4: abad_R_Joint, 5: hip_R_Joint, 6: knee_R_Joint, 6: ankle_R_Joint
- datatypes.RobotState数据结构字段:
- stamp:时间戳,通常表示记录或生成这些数据的时间。
- tau:用于存储当前估计的输出扭矩(以牛顿米为单位)的向量。
- q:用于存储当前角度(以弧度为单位)的向量。
- dq:用于存储当前速度(以弧度每秒为单位)的向量。
datatypes.RobotState 数据结构原型如下:
import sys
class RobotState(object):
__slots__ = ['stamp','tau','q','dq']
def __init__(self):
self.stamp = 0 # 时间戳,通常表示记录或生成这些数据的时间,以纳秒为单位
self.tau = [] # 用于存储当前估计的输出扭矩(以牛顿米为单位)的向量
self.q = [] # 用于存储当前角度(以弧度为单位)的向量
self.dq = [] # 用于存储当前速度(以弧度每秒为单位)的向量
代码示例:
import sys
from functools import partial
import limxsdk.robot.Robot as Robot
import limxsdk.robot.RobotType as RobotType
import limxsdk.datatypes as datatypes
class RobotReceiver:
# 用于接收机器人状态的回调函数
def robotStateCallback(self, robot_state: datatypes.RobotState):
print("\n------\nrobot_state:" + \
"\n stamp: " + str(robot_state.stamp) + \
"\n tau: " + str(robot_state.tau) + \
"\n q: " + str(robot_state.q) + \
"\n dq: " + str(robot_state.dq))
if __name__ == '__main__':
# 创建一个 Robot 实例,类型为 PointFoot
robot = Robot(RobotType.PointFoot)
robot_ip = "127.0.0.1"
# 检查是否提供了机器人 IP 的命令行参数
if len(sys.argv) > 1:
robot_ip = sys.argv[1]
# 使用 robot_ip 初始化机器人
if not robot.init(robot_ip):
sys.exit()
# 创建一个 RobotReceiver 实例来处理回调
receiver = RobotReceiver()
# 创建回调函数的 partial 函数
robotStateCallback = partial(receiver.robotStateCallback)
# 订阅机器人状态
robot.subscribeRobotState(robotStateCallback)
# 休眠 1 秒,防止程序退出
import time
while True:
time.sleep(1)
4.2.8 publishRobotCmd 接口介绍
| 函数名 | publishRobotCmd |
|---|---|
| 函数原型 | def publishRobotCmd(self, cmd: datatypes.RobotCmd) |
| 功能概述 | 发布一个命令来控制机器人的动作。 |
| 参数 | cmd:表示所需机器人命令的 datatypes.RobotCmd 对象,包含以下字段:![]() |
| 返回值 | 成功:返回True 失败:返回False |
备注
datatypes.RobotCmd 数据结构原型如下:
import sys
class RobotCmd(object):
__slots__ = ['stamp','mode','q','dq','tau','Kp','Kd']
def __init__(self):
self.stamp = 0 # 时间戳(以纳秒为单位),表示记录或生成数据时的时间。
self.mode = [] # 机器人的期望工作模式。
self.q = [] # 存储期望角度的向量(以弧度为单位)
self.dq = [] # 存储期望速度的向量(以弧度每秒为单位)。
self.tau = [] # 存储期望输出扭矩的向量(以牛顿米为单位)。
self.Kp = [] # 存储期望位置刚度的向量(以牛顿米每弧度为单位)。
self.Kd = [] # 存储期望速度刚度的向量(以牛顿米每弧度每秒为单位)。
代码示例
import sys
import time
import limxsdk.robot.Rate as Rate
import limxsdk.robot.Robot as Robot
import limxsdk.robot.RobotType as RobotType
import limxsdk.datatypes as datatypes
if __name__ == '__main__':
# 创建一个 Robot 实例,类型为 PointFoot
robot = Robot(RobotType.PointFoot)
robot_ip = "127.0.0.1"
# 检查是否提供了机器人 IP 的命令行参数
if len(sys.argv) > 1:
robot_ip = sys.argv[1]
# 使用 robot_ip 初始化机器人
if not robot.init(robot_ip):
sys.exit()
# 获取关节偏移、关节限制和电机数量信息
joint_offset = robot.getJointOffset()
joint_limit = robot.getJointLimit()
motor_number = robot.getMotorNumber()
# 主循环以连续发布机器人命令
rate = Rate(500) # 1500 Hz
cmd_msg = datatypes.RobotCmd()
while True:
# 设置时间戳、控制模式、关节位置、速度、力矩、Kp 和 Kd 的默认值
cmd_msg.stamp = time.time_ns()
cmd_msg.mode = [1.0 for _ in range(motor_number)]
cmd_msg.q = [1.0 for _ in range(motor_number)]
cmd_msg.dq = [1.0 for _ in range(motor_number)]
cmd_msg.tau = [1.0 for _ in range(motor_number)]
cmd_msg.Kp = [1.0 for _ in range(motor_number)]
cmd_msg.Kd = [1.0 for _ in range(motor_number)]
robot.publishRobotCmd(cmd_msg) # 发布机器人命令
rate.sleep() # 控制循环频率
4.2.9 subscribeSensorJoy 接口介绍
| 函数名 | subscribeSensorJoy |
|---|---|
| 函数原型 | def subscribeSensorJoy(self, callback: Callable[[datatypes.SensorJoy], Any]) |
| 功能概述 | 在真机部署中,该方法用于订阅来自机器人遥控器的数据。当机器人接收到遥控器的数据时,将会调用指定的回调函数,并传递包含遥控器数据的 datatypes.SensorJoy 结构体常量指针给回调函数进行处理。 |
| 参数 | callback: 表示回调函数,用于接收机器人遥控器的数据。回调函数的参数类型为 datatypes.SensorJoy |
| 返回值 | 成功:返回True 失败:返回False |
备注
- datatypes.SensorJoy 数据结构原型如下:
import sys
class SensorJoy(object):
__slots__ = ['stamp','axes','buttons']
def __init__(self):
self.stamp = 0 # 与传感器输入相关的时间戳,单位为纳秒。
self.axes = [] # 表示操纵摇杆的值。
self.buttons = [] # 表示操纵按钮状态的值。
- 遥控器摇杆映射
| 摇杆名 | Axes Index |
|----------------|-------------|
| left_horizon | 0 |
|----------------|-------------|
| left_vertival | 1 |
|----------------|-------------|
| right_horizon | 2 |
|----------------|-------------|
| right_vertival | 3 |
|----------------|-------------|
- 遥控器按键映射
| 按键名 | Buttons Index |
|----------------|----------------|
| X | 0 |
|----------------|----------------|
| 〇 | 1 |
|----------------|----------------|
| 口 | 2 |
|----------------|----------------|
| △ | 3 |
|----------------|----------------|
| L1 | 4 |
|----------------|----------------|
| L2 | 6 |
|----------------|----------------|
| R1 | 7 |
|----------------|----------------|
| R2 | 5 |
|----------------|----------------|
| SELECT | 8 |
|----------------|----------------|
| START | 9 |
|----------------|----------------|
| UP | 12 |
|----------------|----------------|
| DOWN | 13 |
|----------------|----------------|
| LEFT | 14 |
|----------------|----------------|
| RIGHT | 15 |
|----------------|----------------|
| MENU | 16 |
|----------------|----------------|
| BACK | 17 |
|----------------|----------------|
- 适配您自己的按键逻辑时,建议不要和系统预留按键功能冲突了。开发者模式下系统预留按键功能:
| 按键名 | 功能说明
|----------------|-------------------------------
| L1 + R1 | 机器人校零
|----------------|-------------------------------
| R1 + Left | 打开开发者模式
|----------------|-------------------------------
| R1 + Right | 打开RL运动控制模式
|----------------|-------------------------------
| R1 + Up | 打开MB运动控制模式
|----------------|-------------------------------
| 左 + 右摇杆按键 | 机器人紧急停止
|----------------|-------------------------------
| 右摇杆按键 | 解除机器人紧急停止
|----------------|-------------------------------
代码示例
import sys
import time
from functools import partial
import limxsdk.robot.Robot as Robot
import limxsdk.robot.RobotType as RobotType
import limxsdk.datatypes as datatypes
class RobotReceiver:
# 用于接收遥控器数据的回调函数
def sensorJoyCallback(self, sensor_joy: datatypes.SensorJoy):
print("\n------\nsensor_joy:" + \
"\n stamp: " + str(sensor_joy.stamp) + \
"\n axes: " + str(sensor_joy.axes) + \
"\n buttons: " + str(sensor_joy.buttons))
if __name__ == '__main__':
# 创建一个 Robot 实例,类型为 PointFoot
robot = Robot(RobotType.PointFoot)
robot_ip = "127.0.0.1"
# 检查是否提供了机器人 IP 的命令行参数
if len(sys.argv) > 1:
robot_ip = sys.argv[1]
# 使用 robot_ip 初始化机器人
if not robot.init(robot_ip):
sys.exit()
# 创建一个 RobotReceiver 实例来处理回调
receiver = RobotReceiver()
# 创建回调函数的 partial 函数
sensorJoyCallback = partial(receiver.sensorJoyCallback)
# 订阅机器人遥控数据
robot.subscribeSensorJoy(sensorJoyCallback)
# 休眠 1 秒,防止程序退出
import time
while True:
time.sleep(1)
4.2.10 subscribeDiagnosticValue 接口介绍
| 函数名 | subscribeDiagnosticValue |
|---|---|
| 函数原型 | def subscribeDiagnosticValue(self, callback: Callable[[datatypes.DiagnosticValue], Any]) |
| 功能概述 | 在真机部署中,该方法用于订阅机器人的诊断值和状态信息。当机器人发出诊断值时,系统会调用指定的回调函数,并传递包含诊断值的 datatypes.DiagnosticValue 结构对象给回调函数进行处理。这可以帮助实时监控机器人的健康状态,并及时做出反应以处理可能的问题。 |
| 参数 | callback: 用于接收机器人诊断值的回调函数,其参数类型为 datatypes.DiagnosticValue。datatypes.DiagnosticValue 结构体包含了机器人诊断值的信息,包括时间戳、级别、名称、代码和消息字段。 |
| 返回值 | 成功:返回True 失败:返回False |
备注
- datatypes.DiagnosticValue 数据结构原型如下:
import sys
class DiagnosticValue(object):
__slots__ = ['stamp','level','name','code','message']
def __init__(self):
self.stamp = 0 # 时间戳,单位为纳秒。
self.level = 0 # 与诊断值相关联的级别 - 0: OK, 1: WARN, 2: ERROR
self.name = '' # 标识诊断值的名称。
self.code = 0 # 与诊断值对应的代码。
self.message = '' # 与诊断值相关的详细消息。
- 常见诊断数据
| name | level | code | message
|-------------|--------|------|---------------------------------
| version | OK | 0 | - 包含软件版本信息
|-------------|--------|------|---------------------------------
| imu | OK | 0 | - IMU 工作正常
| imu | ERROR | -1 | - IMU 工作异常,包含异常信息
|-------------|--------|------|---------------------------------
| ethercat | OK | 0 | - EtherCAT 工作正常
| ethercat | ERROR | -1 | - EtherCAT 工作异常,包含异常信息
|-------------|--------|------|---------------------------------
| calibration | OK | 0 | - 机器人完成校零
| calibration | WARN | 1 | - 提示:机器人正在校准零中
| calibration | ERROR | -1 | - 错误:机器人校准零失败,包含失败原因
|-------------|--------|------|----------------------------------
代码示例
import sys
from functools import partial
import limxsdk.robot.Robot as Robot
import limxsdk.robot.RobotType as RobotType
import limxsdk.datatypes as datatypes
class RobotReceiver:
# 用于接收诊断值的回调函数
def diagnosticValueCallback(self, diagnostic_value: datatypes.DiagnosticValue):
print("\n------\ndiagnostic_value:" + \
"\n stamp: " + str(diagnostic_value.stamp) + \
"\n name: " + diagnostic_value.name + \
"\n level: " + str(diagnostic_value.level) + \
"\n code: " + str(diagnostic_value.code) + \
"\n message: " + diagnostic_value.message)
if __name__ == '__main__':
# 创建一个 Robot 实例,类型为 PointFoot
robot = Robot(RobotType.PointFoot)
robot_ip = "127.0.0.1"
# 检查是否提供了机器人 IP 的命令行参数
if len(sys.argv) > 1:
robot_ip = sys.argv[1]
# 使用 robot_ip 初始化机器人
if not robot.init(robot_ip):
sys.exit()
# 创建一个 RobotReceiver 实例来处理回调
receiver = RobotReceiver()
# 创建回调函数的 partial 函数
diagnosticValueCallback = partial(receiver.diagnosticValueCallback)
# 订阅机器人诊断信息
robot.subscribeDiagnosticValue(diagnosticValueCallback)
# 休眠 1 秒,防止程序退出
import time
while True:
time.sleep(1)
4.2.11 setRobotLightEffect 接口介绍
| 函数名 | setRobotLightEffect |
|---|---|
| 函数原型 | def setRobotLightEffect(self, effect: datatypes.LightEffect) |
| 功能概述 | 在真机部署中,该方法用于设置机器人的灯光效果。 |
| 参数 | effect: 一个整数,表示所需的机器人灯光效果,具体定义见 PointFoot::LightEffect 枚举。 |
| 返回值 | 成功:返回True 失败:返回False |
备注
datatypes.LightEffect 枚举定义:
class LightEffect(Enum):
STATIC_RED = 0 # 静态红光
STATIC_GREEN = 1 # 静态绿光
STATIC_BLUE = 2 # 静态蓝光
STATIC_CYAN = 3 # 静态青光
STATIC_PURPLE = 4 # 静态紫光
STATIC_YELLOW = 5 # 静态黄光
STATIC_WHITE = 6 # 静态白光
LOW_FLASH_RED = 7 # 红光闪烁(慢闪)
LOW_FLASH_GREEN = 8 # 绿光闪烁(慢闪)
LOW_FLASH_BLUE = 9 # 蓝光闪烁(慢闪)
LOW_FLASH_CYAN = 10 # 青光闪烁(慢闪)
LOW_FLASH_PURPLE = 11 # 紫光闪烁(慢闪)
LOW_FLASH_YELLOW = 12 # 黄光闪烁(慢闪)
LOW_FLASH_WHITE = 13 # 白光闪烁(慢闪)
FAST_FLASH_RED = 14 # 红光闪烁(快速闪)
FAST_FLASH_GREEN = 15 # 绿光闪烁(快速闪)
FAST_FLASH_BLUE = 16 # 蓝光闪烁(快速闪)
FAST_FLASH_CYAN = 17 # 青光闪烁(快速闪)
FAST_FLASH_PURPLE = 18 # 紫光闪烁(快速闪)
FAST_FLASH_YELLOW = 19 # 黄光闪烁(快速闪)
FAST_FLASH_WHITE = 20 # 白光闪烁(快速闪)
代码示例
import sys
from functools import partial
import limxsdk.robot.Robot as Robot
import limxsdk.robot.RobotType as RobotType
import limxsdk.datatypes as datatypes
class RobotReceiver:
if __name__ == '__main__':
# 创建一个 Robot 实例,类型为 PointFoot
robot = Robot(RobotType.PointFoot)
robot_ip = "127.0.0.1"
# 检查是否提供了机器人 IP 的命令行参数
if len(sys.argv) > 1:
robot_ip = sys.argv[1]
# 使用 robot_ip 初始化机器人
if not robot.init(robot_ip):
sys.exit()
# 设置机器人灯光效果为静态红光
robot.setRobotLightEffect(datatypes.LightEffect.STATIC_RED)
4.2.12 参考例程
- Python 接口参考例程
https://github.com/limxdynamics/limxsdk-lowlevel/blob/master/python3/examples/api/example.py
5. 上层应用开发接口
5.1 概述
在遥控模式下机器人通过WebSocket通信端口5000来接收用户端请求指令,例如让机器人站起、蹲下、行走等。WebSocket是一种实时通信协议,在机器人和用户端之间建立长连接,以便快速有效地传输控制信息和数据。如下图所示:

5.2 通信协议格式
当机器人通过 WebSocket 接收客户端指令时,采用 JSON 数据协议进行信息传递。这种方式具有显著优势:WebSocket 是一种全双工通信协议,能够在客户端与服务器之间建立实时、低延迟的连接,特别适合频繁交互的应用场景。JSON 数据协议则以其简洁、可读性强的结构,确保数据传输直观明了,且具有跨平台、跨语言的兼容性。WebSocket 与 JSON 的结合不仅与编程语言无关,适用于各种设备和系统,还能提升开发的灵活性和维护的便利性。
- 请求数据格式包含以下字段:
accid:机器人序列号,注意修为您机器人的序列号;title:指令名称,以“request_”为前缀;timestamp:指令发出时间戳,单位为毫秒;guid:指令的唯一标识符,用于区分不同的请求指令。如果是同步接口,则需要在“response_xxx”响应消息中通过guid字段将值带回给客户端。客户端接收到响应消息后,可以通过比较guid字段的值是否与请求指令中的值相同来判断指令是否执行完成;data:存放请求指令的数据内容。可以根据具体需求包含多个子字段,以存放请求指令所需的数据内容,例如执行动作的参数、发送消息的文本内容等等;- 示例如下:
{
"accid": "PF_TRON1A_075", # 机器人序列号,注意修为您机器人的序列号
"title": "request_xxx", # 指令名称,以“request_”为前缀
"timestamp": 1672373633989, # 指令发出时间戳,单位为毫秒
"guid": "746d937cd8094f6a98c9577aaf213d98", # 指令的唯一标识符,用于区分不同的请求指令
"data": {} # 存放请求指令的数据内容
}
- 响应数据格式包含以下字段:
accid:机器人序列号,注意修为您机器人的序列号;title:指令名称,以“response_”为前缀;timestamp:指令发出时间戳,单位为毫秒;guid:与对应请求指令的guid值相同;data:至少应该包含一个“result”子字段,用于存放请求指令的执行结果数据。如果有需要,还可以包含其他子字段,例如错误码、错误信息等用于描述操作结果的信息;- 示例如下:
{
"accid": "PF_TRON1A_075", # 机器人序列号,注意修为您机器人的序列号
"title": "response_xxx", # 指令名称,以“response_”为前缀
"timestamp": 1672373633989, # 指令发出时间戳,单位为毫秒
"guid": "746d937cd8094f6a98c9577aaf213d98", # 与对应请求指令的guid值相同
"data": { # 存放响应指令的具体数据内容
"result": "success" # “result” 用于存放请求指令处理是否成功,它的值为:“success 或 fail_xxx”
}
}
- 消息推送:它是机器人主动向客户端发送信息的过程。这些信息可以包括机器人的序列号、当前运行状态、执行的操作等数据。通过及时地向客户端发送这些信息,机器人可以帮助客户端更好地理解它的工作状态,从而更好地使用它提供的服务。它的数据格式包含以下字段:
accid:机器人序列号,注意修为您机器人的序列号;title:指令名称,以“notify_”为前缀;timestamp:消息发出时间戳,单位为毫秒;guid:消息的guid值,唯一标识这条消息;data:存放消息数据内容。可以根据具体需求包含多个子字段,以存放请求指令所需的数据内容;- 示例如下:
{
"accid": "PF_TRON1A_075", # 机器人序列号,注意修为您机器人的序列号
"title": "notify_xxx", # 消息名称,以“notify_”为前缀
"timestamp": 1672373633989, # 消息发出时间戳,单位为毫秒
"guid": "746d937cd8094f6a98c9577aaf213d98", # 消息的guid值,唯一标识这条消息
"data": { } # 存放消息数据内容
}
5.3 查看软件序列号(ACCID)
- 连接机器人无线网络
- 机器人开机完成后,使用个人电脑连接机器人Wi-Fi,名称格式通常为「PF_TRON1A_xxx」
- 输入Wi-Fi密码:
12345678
- 在浏览器中输入
http://10.192.1.2:8080可以进入“机器人信息页”,并查看机器人信息。如下图所示,页面中显示的SN (序列号) 为PF_TRON1A_075,其中PF_TRON1A_075便是此机器人的软件序列号。
5.4 通信测试方法
Postman是一个流行的API开发环境,可以用于测试WebSocket接口。使用Postman测试WebSocket接口,请按照以下步骤操作:
- 安装postman,下载地址:https://www.postman.com/downloads/?utm_source=postman-home;
- 打开Postman,并创建一个WebSocket的请求;
- 连接机器人无线网络
- 机器人开机完成后,使用个人电脑连接机器人Wi-Fi,名称格式通常为「WF_TRON1A_xxx」
- 输入Wi-Fi密码:12345678
- 在请求的URL中输入WebSocket接口的地址,例如,“ws://10.192.1.2:5000”;
- 在“Message”中,输入要发送的指令请求;
- 单击“Send”按钮,发送请求指令;
- 发送指令后,可以从服务器接收响应消息。使用Postman的响应窗口查看服务器返回的数据,并检查是否符合预期结果。

5.5 协议接口定义
该机器人接口设计遵循与遥控器操控一致的流程和状态流转,确保调用顺序、响应时序及状态过渡与遥控器控制逻辑严格对齐。用户通过接口调用可获得如同使用遥控器的直观体验,同时支持遥控器与接口间的无缝切换,实现统一、稳定的机器人操控效果。
5.5.1 蹲起状态
5.5.1.1 请求:request_stand_mode
{
"accid": "PF_TRON1A_075", // 机器人序列号,注意修为您机器人的序列号;
"title": "request_stand_mode",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {
}
}
5.5.1.2 响应:response_stand_mode
{
"accid": "PF_TRON1A_075",
"title": "response_stand_mode",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {
"result": "success" // success: 成功, fail_imu: IMU 错误, fail_motor: 电机错误
}
5.5.1.3 消息推送:notify_stand_mode
机器人站起过程失败或完成后,主动推送此消息。
{
"accid": "PF_TRON1A_075",
"title": "notify_stand_mode",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {
"result": "success" // success: 成功, fail_imu: IMU 错误, fail_motor: 电机错误
}
}
5.5.2 行走状态
5.2.2.1 请求:request_walk_mode
{
"accid": "PF_TRON1A_075", // 机器人序列号,注意修为您机器人的序列号;
"title": "request_walk_mode",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {
}
}
5.2.2.2 响应:response_walk_mode
{
"accid": "PF_TRON1A_075",
"title": "response_walk_mode",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {
"result": "success" // success: 成功, fail_imu: IMU 错误, fail_motor: 电机错误
}
5.5.2.3 消息推送:notify_walk_mode
机器人站起过程失败或完成后,主动推送此消息。
{
"accid": "PF_TRON1A_075",
"title": "notify_walk_mode",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {
"result": "success" // success: 成功, fail_imu: IMU 错误, fail_motor: 电机错误
}
}
5.5.3 控制行走
5.5.3.1 请求:request_twist
请按30Hz及以上发送指令。
- 点足和掌足模式下按如下规则:
{
"accid": "PF_TRON1A_075", // 机器人序列号,注意修为您机器人的序列号;
"title": "request_twist",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {
"x": 0.0, // 前进后退速度比值,取值范围[-1, 1]
"y": 0.0, // 横向行走速度比值,取值范围[-1, 1]
"z": 0.0 // 旋转角速度比值,取值范围[-1, 1]
}
}
- 轮足模式下按如下规则:
{
"accid": "WF_TRON1A_075", // 机器人序列号,注意修为您机器人的序列号;
"title": "request_twist",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {
"x": 0.0, // 前进后退速度(m/s),取值范围[-3.0, 3.0];同时注意,楼梯模式下发速度需要大于1.5,否则上不了楼梯
"y": 0.0, // 横向行走速度为0.0(m/s),轮足模式下不支持横向移动
"z": 0.0 // 旋转角速度(m/s),取值范围[-1.5, 1.5]
}
}
5.5.3.2 响应:无
5.5.3.3 消息推送:notify_twist
机器人行走失败时,主动推送此消息。
{
"accid": "PF_TRON1A_075",
"title": "notify_twist",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {
"result": "fail_motor" // fail_imu: IMU 错误, fail_motor: 电机错误
}
}
5.5.4 调整机器身高
5.5.4.1 请求:request_base_height
{
"accid": "PF_TRON1A_075", // 机器人序列号,注意修为您机器人的序列号;
"title": "request_base_height",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {
"direction": -1 // 1:表示升高,-1:表示降低
// 每次调用此请求会使机器人身高相应升高或降低 5cm
}
}
5.5.4.2 响应:response_base_height
{
"accid": "PF_TRON1A_075",
"title": "response_base_height",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {
"result": "success" // success: 成功, fail_status: 则表示机器人当前状态不允许调整身高
}
}
5.5.4.3 消息推送:无
5.5.5 蹲下
5.5.5.1 请求:request_sitdown
{
"accid": "PF_TRON1A_075", // 机器人序列号,注意修为您机器人的序列号;
"title": "request_sitdown",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {}
}
5.5.5.2 响应:response_sitdown
{
"accid": "PF_TRON1A_075",
"title": "response_sitdown",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {
"result": "success" // success: 成功, fail_imu: IMU 错误, fail_motor: 电机错误
}
}
5.5.5.3 消息推送:notify_sitdown
机器人蹲下过程失败或完成后,主动推送此消息。
{
"accid": "PF_TRON1A_075",
"title": "notify_sitdown",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {
"result": "success" // success: 成功, fail_imu: IMU 错误, fail_motor: 电机错误
}
}
5.5.6 开启楼梯模式
5.5.6.1 请求:request_stair_mode
本功能仅适用于 TRON1 型号的双轮足机器人。
{
"accid": "PF_TRON1A_075", // 机器人序列号,注意修为您机器人的序列号;
"title": "request_stair_mode",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {
"enable": true // true: 开启楼梯模式, false: 关闭楼梯模式
}
}
5.5.6.2 响应:response_stair_mode
{
"accid": "PF_TRON1A_075",
"title": "response_stair_mode",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {
"result": "success" // success: 成功, fail_imu: IMU 错误, fail_motor: 电机错误
}
}
5.5.6.3 消息推送:无
5.5.7 开启踏步模式
5.5.7.1 请求:request_marktime_mode
本功能仅适用于 TRON1 型号的双足机器人。
{
"accid": "PF_TRON1A_075", // 机器人序列号,注意修为您机器人的序列号;
"title": "request_marktime_mode",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {
"enable": true // true: 开启踏步模式, false: 关闭踏步模式
}
}
5.5.7.2 响应:response_marktime_mode
{
"accid": "PF_TRON1A_075",
"title": "response_marktime_mode",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {
"result": "success" // success: 成功, fail_imu: IMU 错误, fail_motor: 电机错误
}
}
5.5.7.3 消息推送:无
5.5.8 紧急停止
5.5.8.1 请求:request_emgy_stop
{
"accid": "PF_TRON1A_075", // 机器人序列号,注意修为您机器人的序列号;
"title": "request_emgy_stop",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {}
}
5.5.8.2 响应:response_emgy_stop
{
"accid": "PF_TRON1A_075",
"title": "response_emgy_stop",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {
"result": "success" // success: 成功, fail_imu: IMU 错误, fail_motor: 电机错误
}
}
5.5.8.3 消息推送:无
5.5.9 开启里程计
5.5.9.1 请求:request_enable_odom
该功能用于开启里程计推送,开启后系统将主动推送里程计数据(注意:轮足机器人才有里程计)。
{
"accid": "WF_TRON1A_075", // 机器人序列号,注意修为您机器人的序列号;
"title": "request_enable_odom",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {
"enable": true // true: 开启里程计, false: 禁用里程计
}
}
5.5.9.2 响应:response_enable_imu
{
"accid": "WF_TRON1A_075",
"title": "response_enable_odom",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {
"result": "success" // success: 成功, fail_odom: 不支持
}
}
5.5.9.3 消息推送:notify_odom
开启里程计后,系统将主动推送包含里程计数据的消息。
{
"accid": "WF_TRON1A_075",
"title": "notify_odom",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {
"pose_orientation": [0.0, 0.0, 0.0, 0.0], // 姿态 [x, y, z, w]
"pose_position": [0.0, 0.0, 0.0], // 位置 [x, y, z] in m
"twist_linear": [0.0, 0.0, 0.0], // 线速度 [x, y, z] in m/s
"twist_angular": [0.0, 0.0, 0.0] // 角速度 [x, y, z] in rad/s
}
}
5.5.10 开启IMU数据
5.5.10.1 请求:request_enable_imu
该功能用于开启IMU数据推送,开启后系统将主动推送IMU数据。
{
"accid": "PF_TRON1A_075", // 机器人序列号,注意修为您机器人的序列号;
"title": "request_enable_imu",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {
"enable": true // true: 开启IMU, false: 禁用IMU
}
}
5.5.10.2 响应:response_enable_imu
{
"accid": "PF_TRON1A_075",
"title": "response_enable_imu",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {
"result": "success" // success: 成功, fail_imu: IMU
}
}
5.5.10.3 消息推送:notify_imu
开启IMU数据后,系统将主动推送包含IMU状态的消息。
{
"accid": "PF_TRON1A_075",
"title": "notify_imu",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {
"euler": [0.0, 0.0, 0.0], // 欧拉角 [roll, pitch, yaw] in degrees
"acc": [0.0, 0.0, 0.0], // 加速度 [x, y, z] in m/s²
"gyro": [0.0, 0.0, 0.0], // 陀螺仪角速度 [x, y, z] in rad/s
"quat": [0.0, 0.0, 0.0, 0.0] // 四元数 [w, x, y, z]
}
}
5.5.11 摔倒恢复
5.5.11.1 请求:request_recover
当机器人摔倒后,可以调用此接口让机器人自动爬起来并恢复到行走模式。
{
"accid": "PF_TRON1A_075", // 机器人序列号,注意修为您机器人的序列号;
"title": "request_recover",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {}
}
5.5.11.2 响应:response_recover
{
"accid": "PF_TRON1A_075",
"title": "response_recover",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {
"result": "success" // success: 成功收到指令,开始恢复爬起, fail_no_fallover: 当前没摔倒
}
}
5.5.11.3 消息推送:notify_recover
完成恢复爬起后,推送此消息。
5.5.12 设置灯效
5.5.12.1 请求:request_light_effect
该接口用于设置机器人的灯光效果。用户可以通过此接口向机器人发送特定的灯光效果指令,机器人会根据指令调整其灯光显示。
{
"accid": "PF_TRON1A_075", // 机器人序列号,注意修为您机器人的序列号;
"title": "request_light_effect",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {
"effect": 1
}
}
| 参数名 | 类型 | 描述 |
|---|---|---|
| data.effect | 数字 | 灯光效果的编号,对应不同的灯光显示模式,具体映射关系如下: |
1: STATIC_RED(静态红色)
2: STATIC_GREEN(静态绿色)
3: STATIC_BLUE(静态蓝色)
4: STATIC_CYAN(静态青色)
5: STATIC_PURPLE(静态紫色)
6: STATIC_YELLOW(静态黄色)
7: STATIC_WHITE(静态白色)
8: LOW_FLASH_RED(低频闪烁红色)
9: LOW_FLASH_GREEN(低频闪烁绿色)
10: LOW_FLASH_BLUE(低频闪烁蓝色)
11: LOW_FLASH_CYAN(低频闪烁青色)
12: LOW_FLASH_PURPLE(低频闪烁紫色)
13: LOW_FLASH_YELLOW(低频闪烁黄色)
14: LOW_FLASH_WHITE(低频闪烁白色)
15: FAST_FLASH_RED(高频闪烁红色)
16: FAST_FLASH_GREEN(高频闪烁绿色)
17: FAST_FLASH_BLUE(高频闪烁蓝色)
18: FAST_FLASH_CYAN(高频闪烁青色)
19: FAST_FLASH_PURPLE(高频闪烁紫色)
20: FAST_FLASH_YELLOW(高频闪烁黄色)
21: FAST_FLASH_WHITE(高频闪烁白色)
5.5.12.2 响应:response_light_effect
{
"accid": "PF_TRON1A_075",
"title": "response_light_effect",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {
"result": "success" // success: 成功, fail_light_effect: 失败
}
}
5.5.13 全局消息
5.5.13.1 机器人基本信息
机器人基本信息每秒上报一次,包含以下内容:
accid:机器人序列号title:notify_robot_infotimestamp:消息发出时间戳,单位为毫秒guid:消息的guid值,唯一标识这条消息data:存放消息内容,示例如下:
{
"accid": "PF_TRON1A_075",
"title": "notify_robot_info",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {
"accid": "PF_TRON1A_075",
"sw_version": "robot-tron1-2.0.10.20241111103012",
"imu": "OK", # 机器人IMU诊断信息
"camera": "OK", # 机器人相机诊断信息
"motor": "OK", # 机器人电机诊断信息
"battery": 95, # 机器人电量
"status": "WALK" # 机器人运行模式
}
}
| 字段 | 说明 |
|---|---|
accid |
机器人序列号 |
sw_version |
机器人本体软件版本信息 |
imu |
机器人IMU诊断信息 |
camera |
机器人相机诊断信息 |
motor |
机器人电机诊断信息 |
battery |
机器人电池电量 |
status |
机器人运行模式:STAND、WALK、SIT、DAMPING、ROTATE、STAIR、ERROR_FALLOVER(摔倒)、RECOVER(摔倒恢复中), ERROR_RECOVER(摔倒恢复失败) |
5.5.13.2 非法指令消息
当机器人收到非法格式的请求指令时,发送此消息,包含以下内容:
- accid:机器人序列号
- title:notify_invalid_request
- timestamp:消息发出时间戳,单位为毫秒
- guid:消息的guid值,唯一标识这条消息
- data:存放消息内容
- 示例如下:
{
"accid": "PF_TRON1A_075",
"title": "notify_invalid_request",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": "返回原请求指令内容,便于客户端排查问题"
}
5.6 协议接口调用示例
5.6.1 Linux C++ 示例实现
- 安装依赖:以Ubuntu 20.04系统为例,安装websocketpp、nlohmann/json和boost依赖:
sudo apt-get install libboost-all-dev libwebsocketpp-dev nlohmann-json3-dev
- 编译代码
g++ -std=c++11 -o websocket_client websocket_client.cpp -lssl -lcrypto -lboost_system -lpthread
- 运行程序
./websocket_client
- websocket_client.cpp 实现
#include <iostream>
#include <atomic>
#include <string>
#include <thread>
#include <chrono>
#include <websocketpp/client.hpp>
#include <websocketpp/config/asio.hpp>
#include <nlohmann/json.hpp>
#include <boost/uuid/uuid.hpp>
#include <boost/uuid/uuid_generators.hpp>
#include <boost/uuid/uuid_io.hpp>
using json = nlohmann::json;
using websocketpp::client;
using websocketpp::connection_hdl;
// Replace this ACCID value with your robot's actual serial number (SN)
static std::string ACCID = "";
// WebSocket client instance
static client<websocketpp::config::asio> ws_client;
// Atomic flag for graceful exit
static std::atomic<bool> should_exit(false);
// Connection handle for sending messages
static connection_hdl current_hdl;
// Generate dynamic GUID
static std::string generate_guid() {
boost::uuids::random_generator gen;
boost::uuids::uuid u = gen();
return boost::uuids::to_string(u);
}
// Send WebSocket request with title and data
static void send_request(const std::string& title, const json& data = json::object()) {
json message;
// Adding necessary fields to the message
message["accid"] = ACCID;
message["title"] = title;
message["timestamp"] = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::system_clock::now().time_since_epoch()).count();
message["guid"] = generate_guid();
message["data"] = data;
std::string message_str = message.dump();
// Send the message through WebSocket
ws_client.send(current_hdl, message_str, websocketpp::frame::opcode::text);
}
// Handle user commands
static void handle_commands() {
while (!should_exit) {
std::string command;
std::cout << "Enter command ('stand', 'walk', 'twist', 'sit', 'stair', 'stop', 'imu') or 'exit' to quit:" << std::endl;
std::getline(std::cin, command); // Read user input
if (command == "exit") {
should_exit = true; // Exit flag to stop the loop
break;
} else if (command == "stand") {
send_request("request_stand_mode"); // Send stand mode request
} else if (command == "walk") {
send_request("request_walk_mode"); // Send walk mode request
} else if (command == "twist") {
float x, y, z;
std::cout << "Enter x, y, z values:" << std::endl;
std::cin >> x >> y >> z; // Get twist values from user
send_request("request_twist", {{"x", x}, {"y", y}, {"z", z}});
} else if (command == "sit") {
send_request("request_sitdown"); // Send sit down request
} else if (command == "stair") {
std::string enable;
std::cout << "Enable stair mode (true/false):" << std::endl;
std::cin >> enable; // Get stair mode enable flag from user
send_request("request_stair_mode", {{"enable", enable == "true" ? true : false}});
} else if (command == "stop") {
send_request("request_emgy_stop"); // Send emergency stop request
} else if (command == "imu") {
std::string enable;
std::cout << "Enable IMU (true/false):" << std::endl;
std::cin >> enable; // Get IMU enable flag from user
send_request("request_enable_imu", {{"enable", enable == "true" ? true : false}});
}
}
}
// WebSocket open callback
static void on_open(connection_hdl hdl) {
std::cout << "Connected!" << std::endl;
// Save connection handle for sending messages later
current_hdl = hdl;
// Start handling commands in a separate thread
std::thread(handle_commands).detach();
}
// WebSocket message callback
static void on_message(connection_hdl hdl, client<websocketpp::config::asio>::message_ptr msg) {
// Parse JSON data from message payload
json data = json::parse(msg->get_payload());
// Extract 'accid' field if present
if (data.contains("accid") && data["accid"].is_string() && ACCID.empty()) {
ACCID = data["accid"].get<std::string>();
}
std::cout << "Received: " << msg->get_payload() << std::endl; // Print received message
}
// WebSocket close callback
static void on_close(connection_hdl hdl) {
std::cout << "Connection closed." << std::endl;
}
// Close WebSocket connection
static void close_connection(connection_hdl hdl) {
ws_client.close(hdl, websocketpp::close::status::normal, "Normal closure"); // Close connection normally
}
int main() {
ws_client.init_asio(); // Initialize ASIO for WebSocket client
// Set WebSocket event handlers
ws_client.set_open_handler(&on_open); // Set open handler
ws_client.set_message_handler(&on_message); // Set message handler
ws_client.set_close_handler(&on_close); // Set close handler
std::string server_uri = "ws://10.192.1.2:5000"; // WebSocket server URI
websocketpp::lib::error_code ec;
client<websocketpp::config::asio>::connection_ptr con = ws_client.get_connection(server_uri, ec); // Get connection pointer
if (ec) {
std::cout << "Error: " << ec.message() << std::endl;
return 1; // Exit if connection error occurs
}
connection_hdl hdl = con->get_handle(); // Get connection handle
ws_client.connect(con); // Connect to server
std::cout << "Press Ctrl+C to exit." << std::endl;
// Run the WebSocket client loop
ws_client.run();
return 0;
}
5.6.2 Python 示例实现
- 环境准备:以Ubuntu 20.04系统为例,安装下面依赖
sudo apt install python3-dev python3-pip
sudo pip3 install websocket-client
- 运行脚本
python3 websocket_client.py
- websocket_client.py 实现
import json
import uuid
import threading
import time
import websocket
from datetime import datetime
# Replace this ACCID value with your robot's actual serial number (SN)
ACCID = None
# Atomic flag for graceful exit
should_exit = False
# WebSocket client instance
ws_client = None
# Generate dynamic GUID
def generate_guid():
return str(uuid.uuid4())
# Send WebSocket request with title and data
def send_request(title, data=None):
if data is None:
data = {}
# Create message structure with necessary fields
message = {
"accid": ACCID,
"title": title,
"timestamp": int(time.time() * 1000), # Current timestamp in milliseconds
"guid": generate_guid(),
"data": data
}
message_str = json.dumps(message)
# Send the message through WebSocket if client is connected
if ws_client:
ws_client.send(message_str)
# Handle user commands
def handle_commands():
global should_exit
while not should_exit:
command = input("Enter command ('stand', 'walk', 'twist', 'sit', 'stair', 'stop', 'imu') or 'exit' to quit:\n")
if command == "exit":
should_exit = True # Set exit flag to stop the loop
break
elif command == "stand":
send_request("request_stand_mode") # Send stand mode request
elif command == "walk":
send_request("request_walk_mode") # Send walk mode request
elif command == "twist":
# Get twist values from user
x = float(input("Enter x value:"))
y = float(input("Enter y value:"))
z = float(input("Enter z value:"))
for _ in range(30):
send_request("request_twist", {"x": x, "y": y, "z": z})
time.sleep(1/30)
elif command == "sit":
send_request("request_sitdown") # Send sit down request
elif command == "stair":
# Get stair mode enable flag from user
enable = input("Enable stair mode (true/false):").strip().lower() == 'true'
send_request("request_stair_mode", {"enable": enable})
elif command == "stop":
send_request("request_emgy_stop") # Send emergency stop request
elif command == "imu":
# Get IMU enable flag from user
enable = input("Enable IMU (true/false):").strip().lower() == 'true'
send_request("request_enable_imu", {"enable": enable})
# WebSocket on_open callback
def on_open(ws):
print("Connected!")
# Start handling commands in a separate thread
threading.Thread(target=handle_commands, daemon=True).start()
# WebSocket on_message callback
def on_message(ws, message):
global ACCID
root = json.loads(message)
ACCID = root.get("accid", None)
print(f"Received message: {message}") # Print the received message
# WebSocket on_close callback
def on_close(ws, close_status_code, close_msg):
print("Connection closed.")
# Close WebSocket connection
def close_connection(ws):
ws.close()
def main():
global ws_client
# Create WebSocket client instance
ws_client = websocket.WebSocketApp(
"ws://10.192.1.2:5000", # WebSocket server URI
on_open=on_open,
on_message=on_message,
on_close=on_close
)
# Run WebSocket client loop
print("Press Ctrl+C to exit.")
ws_client.run_forever()
if __name__ == "__main__":
main()
5.6.3 JavaScript 示例实现
- HTML 页面:为了便于与用户交互,您可以在 HTML 页面中添加一个输入框,用户可以在其中输入命令。以下是 index.html 实现:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WebSocket Robot Control</title>
<style>
#commandInput {
width: 400px; /* Adjust the width to make it wider */
padding: 10px;
font-size: 14px;
}
</style>
</head>
<body>
<h2>Robot Control Commands</h2>
<input type="text" id="commandInput" placeholder="Enter command ('stand', 'walk', 'twist', 'sit', 'stair', 'stop', 'imu')">
<p>Type a command and press Enter.</p>
<script src="robotControl.js"></script>
</body>
</html>
- robotControl.js 实现:
// Replace this ACCID value with your robot's actual serial number (SN)
let ACCID = "";
// WebSocket client instance
let wsClient = null;
// Generate dynamic GUID
function generateGuid() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
const r = Math.random() * 16 | 0,
v = c === 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
}
// Send WebSocket request with title and data
function sendRequest(title, data = {}) {
const message = {
accid: ACCID,
title: title,
timestamp: Date.now(), // Current timestamp in milliseconds
guid: generateGuid(),
data: data
};
// Send the message through WebSocket if client is connected
if (wsClient && wsClient.readyState === WebSocket.OPEN) {
wsClient.send(JSON.stringify(message));
}
}
// Handle user commands
function handleCommands() {
const commandInput = document.getElementById('commandInput');
commandInput.addEventListener('keydown', function(event) {
if (event.key === 'Enter') {
const command = commandInput.value.trim();
commandInput.value = ''; // Clear input field
switch (command) {
case 'stand':
sendRequest('request_stand_mode');
break;
case 'walk':
sendRequest('request_walk_mode');
break;
case 'twist':
const x = parseFloat(prompt("Enter x value:"));
const y = parseFloat(prompt("Enter y value:"));
const z = parseFloat(prompt("Enter z value:"));
sendRequest('request_twist', {x, y, z});
break;
case 'sit':
sendRequest('request_sitdown');
break;
case 'stair':
const enableStair = prompt("Enable stair mode (true/false):").toLowerCase() === 'true';
sendRequest('request_stair_mode', {enable: enableStair});
break;
case 'stop':
sendRequest('request_emgy_stop');
break;
case 'imu':
const enableImu = prompt("Enable IMU (true/false):").toLowerCase() === 'true';
sendRequest('request_enable_imu', {enable: enableImu});
break;
case 'exit':
wsClient.close();
break;
default:
alert("Invalid command. Try again.");
}
}
});
}
// WebSocket onOpen callback
function onOpen() {
console.log("Connected!");
handleCommands();
}
// WebSocket onMessage callback
function onMessage(event) {
console.log("Received message:", event.data);
const message = JSON.parse(event.data);
if (!ACCID && message.accid) {
ACCID = message.accid;
console.log(`ACCID set to: ${ACCID}`);
}
}
// WebSocket onClose callback
function onClose(event) {
console.log("Connection closed.");
}
// Initialize WebSocket client
function initWebSocket() {
// Replace this URL with your WebSocket server URI
wsClient = new WebSocket('ws://10.192.1.2:5000');
wsClient.onopen = onOpen;
wsClient.onmessage = onMessage;
wsClient.onclose = onClose;
console.log("Press Ctrl+C to exit.");
}
// Start WebSocket connection when the page loads
window.onload = initWebSocket;
- 运行程序
将index.html和robotControl.js文件保存到同一目录下,然后在浏览器中打开index.html运行。你可以通过浏览器的开发者工具查看接收到的详细信息。
5.6.4 Go 示例实现
- 首先需要安装
gorilla/websocket包:
go get github.com/gorilla/websocket
- Go实现的代码如下:
package main
import (
"encoding/json"
"fmt"
"github.com/gorilla/websocket"
"time"
"strings"
"github.com/google/uuid"
)
// Global variables
var wsClient *websocket.Conn
var shouldExit bool
// Replace with your robot's serial number
var ACCID = ""
// Generate dynamic GUID
func generateGUID() string {
return uuid.New().String()
}
// Send WebSocket request with title and data
func sendRequest(title string, data map[string]interface{}) {
if data == nil {
data = make(map[string]interface{})
}
message := map[string]interface{}{
"accid": ACCID,
"title": title,
"timestamp": time.Now().UnixMilli(),
"guid": generateGUID(),
"data": data,
}
messageBytes, err := json.Marshal(message)
if err != nil {
fmt.Println("Error marshaling message:", err)
return
}
// Send the message through WebSocket if client is connected
if wsClient != nil {
err = wsClient.WriteMessage(websocket.TextMessage, messageBytes)
if err != nil {
fmt.Println("Error sending message:", err)
}
}
}
// Handle user commands
func handleCommands() {
var command string
for !shouldExit {
fmt.Println("Enter command ('stand', 'walk', 'twist', 'sit', 'stair', 'stop', 'imu') or 'exit' to quit:")
fmt.Scanln(&command)
command = strings.TrimSpace(command)
switch command {
case "exit":
shouldExit = true
return
case "stand":
sendRequest("request_stand_mode", nil)
case "walk":
sendRequest("request_walk_mode", nil)
case "twist":
var x, y, z float64
fmt.Println("Enter x value:")
fmt.Scanln(&x)
fmt.Println("Enter y value:")
fmt.Scanln(&y)
fmt.Println("Enter z value:")
fmt.Scanln(&z)
sendRequest("request_twist", map[string]interface{}{"x": x, "y": y, "z": z})
case "sit":
sendRequest("request_sitdown", nil)
case "stair":
var enable bool
fmt.Println("Enable stair mode (true/false):")
var input string
fmt.Scanln(&input)
enable = strings.ToLower(input) == "true"
sendRequest("request_stair_mode", map[string]interface{}{"enable": enable})
case "stop":
sendRequest("request_emgy_stop", nil)
case "imu":
var enable bool
fmt.Println("Enable IMU (true/false):")
var input string
fmt.Scanln(&input)
enable = strings.ToLower(input) == "true"
sendRequest("request_enable_imu", map[string]interface{}{"enable": enable})
}
}
}
// WebSocket onOpen callback
func onOpen(ws *websocket.Conn) {
fmt.Println("Connected!")
wsClient = ws
go handleCommands()
}
// WebSocket onMessage callback
func onMessage(ws *websocket.Conn, message []byte) {
fmt.Println("Received message:", string(message))
if ACCID == "" {
var msgMap map[string]interface{}
if err := json.Unmarshal(message, &msgMap); err != nil {
fmt.Println("Error parsing message:", err)
return
}
ACCID = msgMap["accid"].(string)
fmt.Printf("ACCID initialized from server: %s\n", ACCID)
}
}
// WebSocket onClose callback
func onClose(ws *websocket.Conn, code int, text string) {
fmt.Println("Connection closed. Code:", code, "Message:", text)
}
// Connect to the WebSocket server
func connectWebSocket() {
url := "ws://10.192.1.2:5000" // WebSocket server URI
conn, _, err := websocket.DefaultDialer.Dial(url, nil)
if err != nil {
fmt.Println("Error connecting to WebSocket server:", err)
return
}
onOpen(conn)
// Start receiving messages from the server
go func() {
for {
_, message, err := conn.ReadMessage()
if err != nil {
fmt.Println("Error reading message:", err)
break
}
onMessage(conn, message)
}
}()
// Wait until WebSocket connection is closed
select {}
}
// Main function
func main() {
defer func() {
if wsClient != nil {
wsClient.Close()
}
}()
// Connect to WebSocket server
go connectWebSocket()
// Block main goroutine to allow handling commands
select {}
}
5.6.5 Java 示例实现
import org.java-websocket.client.WebSocketClient;
import org.java-websocket.handshake.ServerHandshake;
import org.json.JSONObject;
import java.net.URI;
import java.util.Scanner;
import java.util.UUID;
public class WebSocketExample {
// Replace this ACCID value with your robot's actual serial number (SN)
private static String ACCID = "";
// Atomic flag for graceful exit
private static volatile boolean shouldExit = false;
// WebSocket client instance
private static WebSocketClient wsClient = null;
// Generate dynamic GUID
private static String generateGuid() {
return UUID.randomUUID().toString();
}
// Send WebSocket request with title and data
private static void sendRequest(String title, JSONObject data) {
if (data == null) {
data = new JSONObject();
}
// Create message structure with necessary fields
JSONObject message = new JSONObject();
message.put("accid", ACCID);
message.put("title", title);
message.put("timestamp", System.currentTimeMillis()); // Current timestamp in milliseconds
message.put("guid", generateGuid());
message.put("data", data);
String messageStr = message.toString();
// Send the message through WebSocket if client is connected
if (wsClient != null && wsClient.isOpen()) {
wsClient.send(messageStr);
}
}
// Handle user commands
private static void handleCommands() {
Scanner scanner = new Scanner(System.in);
while (!shouldExit) {
System.out.println("Enter command ('stand', 'walk', 'twist', 'sit', 'stair', 'stop', 'imu') or 'exit' to quit:");
String command = scanner.nextLine().trim();
switch (command) {
case "exit":
shouldExit = true;
break;
case "stand":
sendRequest("request_stand_mode", null);
break;
case "walk":
sendRequest("request_walk_mode", null);
break;
case "twist":
System.out.print("Enter x value: ");
double x = scanner.nextDouble();
System.out.print("Enter y value: ");
double y = scanner.nextDouble();
System.out.print("Enter z value: ");
double z = scanner.nextDouble();
scanner.nextLine(); // Consume the newline
JSONObject twistData = new JSONObject();
twistData.put("x", x);
twistData.put("y", y);
twistData.put("z", z);
sendRequest("request_twist", twistData);
break;
case "sit":
sendRequest("request_sitdown", null);
break;
case "stair":
System.out.print("Enable stair mode (true/false): ");
boolean enableStair = scanner.nextLine().trim().equalsIgnoreCase("true");
JSONObject stairData = new JSONObject();
stairData.put("enable", enableStair);
sendRequest("request_stair_mode", stairData);
break;
case "stop":
sendRequest("request_emgy_stop", null);
break;
case "imu":
System.out.print("Enable IMU (true/false): ");
boolean enableImu = scanner.nextLine().trim().equalsIgnoreCase("true");
JSONObject imuData = new JSONObject();
imuData.put("enable", enableImu);
sendRequest("request_enable_imu", imuData);
break;
default:
System.out.println("Invalid command. Try again.");
break;
}
}
}
// WebSocket onOpen callback
private static void onOpen() {
System.out.println("Connected!");
// Start handling commands in a separate thread
new Thread(WebSocketExample::handleCommands).start();
}
// WebSocket onMessage callback
private static void onMessage(String message) {
System.out.println("Received message: " + message);
if (ACCID.isEmpty()) {
try {
JSONObject jsonMessage = new JSONObject(message);
ACCID = jsonMessage.getString("accid");
System.out.printf("ACCID initialized from server: %s%n", ACCID);
} catch (Exception e) {
System.out.println("Error parsing ACCID from message: " + e.getMessage());
}
}
}
// WebSocket onClose callback
private static void onClose(int code, String reason, boolean remote) {
System.out.println("Connection closed. Reason: " + reason);
}
public static void main(String[] args) {
// WebSocket server URI
URI serverUri = URI.create("ws://10.192.1.2:5000");
// Create WebSocket client instance
wsClient = new WebSocketClient(serverUri) {
@Override
public void onOpen(ServerHandshake handshakedata) {
onOpen();
}
@Override
public void onMessage(String message) {
onMessage(message);
}
@Override
public void onClose(int code, String reason, boolean remote) {
onClose(code, reason, remote);
}
@Override
public void onError(Exception ex) {
System.out.println("Error: " + ex.getMessage());
}
};
// Connect to the WebSocket server
wsClient.connect();
System.out.println("Press Ctrl+C to exit.");
}
}
5.6.6 Windows C++ 示例实现
- 在 Windows 上,确保您已经安装并配置了
websocketpp、nlohmann/json和boost库。如果使用vcpkg,您可以运行以下命令安装依赖:
vcpkg install websocketpp boost nlohmann-json
- 代码实现
#include <iostream>
#include <atomic>
#include <string>
#include <thread>
#include <chrono>
#include <websocketpp/client.hpp>
#include <websocketpp/config/asio.hpp>
#include <nlohmann/json.hpp>
#include <boost/uuid/uuid.hpp>
#include <boost/uuid/uuid_generators.hpp>
#include <boost/uuid/uuid_io.hpp>
using json = nlohmann::json;
using websocketpp::client;
using websocketpp::connection_hdl;
// Replace this ACCID value with your robot's actual serial number (SN)
static std::string ACCID = "";
// WebSocket client instance
static client<websocketpp::config::asio> ws_client;
// Atomic flag for graceful exit
static std::atomic<bool> should_exit(false);
// Connection handle for sending messages
static connection_hdl current_hdl;
// Generate dynamic GUID
static std::string generate_guid() {
boost::uuids::random_generator gen;
boost::uuids::uuid u = gen();
return boost::uuids::to_string(u);
}
// Send WebSocket request with title and data
static void send_request(const std::string& title, const json& data = json::object()) {
json message;
// Adding necessary fields to the message
message["accid"] = ACCID;
message["title"] = title;
message["timestamp"] = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::system_clock::now().time_since_epoch()).count();
message["guid"] = generate_guid();
message["data"] = data;
std::string message_str = message.dump();
// Send the message through WebSocket
ws_client.send(current_hdl, message_str, websocketpp::frame::opcode::text);
}
// Handle user commands
static void handle_commands() {
while (!should_exit) {
std::string command;
std::cout << "Enter command ('stand', 'walk', 'twist', 'sit', 'stair', 'stop', 'imu') or 'exit' to quit:" << std::endl;
std::getline(std::cin, command); // Read user input
if (command == "exit") {
should_exit = true; // Exit flag to stop the loop
break;
} else if (command == "stand") {
send_request("request_stand_mode"); // Send stand mode request
} else if (command == "walk") {
send_request("request_walk_mode"); // Send walk mode request
} else if (command == "twist") {
float x, y, z;
std::cout << "Enter x, y, z values:" << std::endl;
std::cin >> x >> y >> z; // Get twist values from user
send_request("request_twist", {{"x", x}, {"y", y}, {"z", z}});
} else if (command == "sit") {
send_request("request_sitdown"); // Send sit down request
} else if (command == "stair") {
bool enable;
std::cout << "Enable stair mode (true/false):" << std::endl;
std::cin >> enable; // Get stair mode enable flag from user
send_request("request_stair_mode", {{"enable", enable}});
} else if (command == "stop") {
send_request("request_emgy_stop"); // Send emergency stop request
} else if (command == "imu") {
std::string enable;
std::cout << "Enable IMU (true/false):" << std::endl;
std::cin >> enable; // Get IMU enable flag from user
send_request("request_enable_imu", {{"enable", enable == "true" ? true : false}});
}
}
}
// WebSocket open callback
static void on_open(connection_hdl hdl) {
std::cout << "Connected!" << std::endl;
// Save connection handle for sending messages later
current_hdl = hdl;
// Start handling commands in a separate thread
std::thread(handle_commands).detach();
}
// WebSocket message callback
static void on_message(connection_hdl hdl, client<websocketpp::config::asio>::message_ptr msg) {
// Parse JSON data from message payload
json data = json::parse(msg->get_payload());
// Extract 'accid' field if present
if (data.contains("accid") && data["accid"].is_string() && ACCID.empty()) {
ACCID = data["accid"].get<std::string>();
}
std::cout << "Received: " << msg->get_payload() << std::endl; // Print received message
}
// WebSocket close callback
static void on_close(connection_hdl hdl) {
std::cout << "Connection closed." << std::endl;
}
// Close WebSocket connection
static void close_connection(connection_hdl hdl) {
ws_client.close(hdl, websocketpp::close::status::normal, "Normal closure"); // Close connection normally
}
int main() {
ws_client.init_asio(); // Initialize ASIO for WebSocket client
// Set WebSocket event handlers
ws_client.set_open_handler(&on_open); // Set open handler
ws_client.set_message_handler(&on_message); // Set message handler
ws_client.set_close_handler(&on_close); // Set close handler
std::string server_uri = "ws://10.192.1.2:5000"; // WebSocket server URI
websocketpp::lib::error_code ec;
client<websocketpp::config::asio>::connection_ptr con = ws_client.get_connection(server_uri, ec); // Get connection pointer
if (ec) {
std::cout << "Error: " << ec.message() << std::endl;
return 1; // Exit if connection error occurs
}
connection_hdl hdl = con->get_handle(); // Get connection handle
ws_client.connect(con); // Connect to server
std::cout << "Press Ctrl+C to exit." << std::endl;
// Run the WebSocket client loop
ws_client.run();
return 0;
}
5.6.7 Windows C# 示例实现
using System;
using System.Net.WebSockets;
using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
class WebSocketClient
{
// Replace this ACCID value with your robot's actual serial number (SN)
private static string ACCID = "";
private static bool isAccidInitialized = false;
private static ClientWebSocket ws;
public static async Task Main(string[] args)
{
ws = new ClientWebSocket();
Uri serverUri = new Uri("ws://10.192.1.2:5000"); // WebSocket server URI
// Handle program exit to close WebSocket connection
Console.CancelKeyPress += async (sender, e) =>
{
e.Cancel = true;
await CloseConnection();
Environment.Exit(0);
};
try
{
await ws.ConnectAsync(serverUri, CancellationToken.None);
Console.WriteLine("Connected to WebSocket server!");
_ = Task.Run(async () => await ReceiveMessages()); // Start listening for messages
while (true)
{
Console.WriteLine("Enter a command ('stand', 'walk', 'twist', 'sit', 'stair', 'stop', 'imu') or 'exit' to quit:");
string command = Console.ReadLine();
if (command == "exit")
{
await CloseConnection();
break;
}
else if (command == "stand")
{
await RequestStandMode();
}
else if (command == "walk")
{
await RequestWalkMode();
}
else if (command == "twist")
{
Console.WriteLine("Enter x, y, z values:");
float x = float.Parse(Console.ReadLine());
float y = float.Parse(Console.ReadLine());
float z = float.Parse(Console.ReadLine());
await RequestTwist(x, y, z);
}
else if (command == "sit")
{
await RequestSitDown();
}
else if (command == "stair")
{
Console.WriteLine("Enter stair mode enable (true/false):");
bool enable = bool.Parse(Console.ReadLine());
await RequestStairMode(enable);
}
else if (command == "stop")
{
await RequestEmgyStop();
}
else if (command == "imu")
{
Console.WriteLine("Enable IMU (true/false):");
bool enable = bool.Parse(Console.ReadLine());
await RequestEnableImu(enable);
}
}
}
catch (Exception ex)
{
Console.WriteLine($"Exception: {ex.Message}");
}
finally
{
ws?.Dispose();
}
}
private static async Task RequestStandMode()
{
// Sends a stand mode request
var request = new
{
accid = ACCID,
title = "request_stand_mode",
timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),
guid = Guid.NewGuid().ToString("N"),
data = new { }
};
await SendMessage(JsonSerializer.Serialize(request));
}
private static async Task RequestWalkMode()
{
// Sends a walk mode request
var request = new
{
accid = ACCID,
title = "request_walk_mode",
timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),
guid = Guid.NewGuid().ToString("N"),
data = new { }
};
await SendMessage(JsonSerializer.Serialize(request));
}
private static async Task RequestTwist(float x, float y, float z)
{
// Sends a twist control request with x, y, z values
var request = new
{
accid = ACCID,
title = "request_twist",
timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),
guid = Guid.NewGuid().ToString("N"),
data = new
{
x = x,
y = y,
z = z
}
};
await SendMessage(JsonSerializer.Serialize(request));
}
private static async Task RequestSitDown()
{
// Sends a sit down request
var request = new
{
accid = ACCID,
title = "request_sitdown",
timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),
guid = Guid.NewGuid().ToString("N"),
data = new { }
};
await SendMessage(JsonSerializer.Serialize(request));
}
private static async Task RequestStairMode(bool enable)
{
// Sends a stair mode request, enabling or disabling
var request = new
{
accid = ACCID,
title = "request_stair_mode",
timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),
guid = Guid.NewGuid().ToString("N"),
data = new
{
enable = enable
}
};
await SendMessage(JsonSerializer.Serialize(request));
}
private static async Task RequestEmgyStop()
{
// Sends an emergency stop request
var request = new
{
accid = ACCID,
title = "request_emgy_stop",
timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),
guid = Guid.NewGuid().ToString("N"),
data = new { }
};
await SendMessage(JsonSerializer.Serialize(request));
}
private static async Task RequestEnableImu(bool enable)
{
// Sends a request to enable/disable IMU data streaming
var request = new
{
accid = ACCID,
title = "request_enable_imu",
timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),
guid = Guid.NewGuid().ToString("N"),
data = new
{
enable = enable
}
};
await SendMessage(JsonSerializer.Serialize(request));
}
private static async Task SendMessage(string message)
{
// Sends a message over WebSocket
byte[] buffer = Encoding.UTF8.GetBytes(message);
await ws.SendAsync(new ArraySegment<byte>(buffer), WebSocketMessageType.Text, true, CancellationToken.None);
Console.WriteLine($"Sent: {message}");
}
private static async Task ReceiveMessages()
{
// Receives messages from WebSocket server
byte[] buffer = new byte[1024];
while (ws.State == WebSocketState.Open)
{
WebSocketReceiveResult result = await ws.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
if (result.MessageType == WebSocketMessageType.Close)
{
await ws.CloseAsync(WebSocketCloseStatus.NormalClosure, "Server closed", CancellationToken.None);
Console.WriteLine("WebSocket server closed connection.");
break;
}
else
{
string message = Encoding.UTF8.GetString(buffer, 0, result.Count);
Console.WriteLine($"Received: {message}");
if (!isAccidInitialized)
{
try
{
var msgJson = JsonDocument.Parse(message).RootElement;
ACCID = msgJson.GetProperty("accid").GetString();
isAccidInitialized = true;
Console.WriteLine($"ACCID initialized from server: {ACCID}");
}
catch (Exception ex)
{
Console.WriteLine($"Error parsing ACCID: {ex.Message}");
}
}
}
}
}
private static async Task CloseConnection()
{
// Closes the WebSocket connection gracefully
if (ws.State == WebSocketState.Open)
{
await ws.CloseAsync(WebSocketCloseStatus.NormalClosure, "Client closing", CancellationToken.None);
Console.WriteLine("WebSocket connection closed.");
}
}
}
6. RL 环境搭建

6.1 推荐硬件配置
| **硬件组件 ** | **推荐规格 ** | ** 说明** |
|---|---|---|
| GPU | GeForce RTX 3080 (12 GB) 或更高 | 12 GB GDDR6 显存或更高,适合深度学习和强化学习任务。 - GeForce RTX 30系列: |
| CPU | 至少六核处理器 | 推荐型号: - Intel: Core i7-10700K 或更高版本 - AMD: Ryzen 5 5600X 或更高版本 |
| 内存 | 16 GB 至 32 GB | 系统内存:16 GB 至 32 GB。较大的内存可以支持更复杂的仿真和训练任务,同时提高系统的响应能力。 |
| 存储 | 512 GB 至 1 TB NVMe SSD | SSD 提高读写速度,缩短加载时间和提升系统性能。 |
| 操作系统 | Ubuntu 20.04 LTS | 确保与GPU驱动程序兼容。 |
| GPU 驱动程序 | / | 选择推荐的驱动版本进行安装。 |
| Python | Python 3.8 或更高版本 | 安装必需的 Python 包和库(如 PyTorch)。 |
| 网络环境 | 能正常访问Github | 环境搭建过程需要通过 GitHub 下载模型训练代码。 |
6.2 一键安装RL环境
- 一键安装RL环境
mkdir -p ~/limx_rl && cd ~/limx_rl && \
sudo apt update && sudo apt install -y git && \
if [ ! -d "pointfoot-legged-gym" ]; then \
git clone https://github.com/limxdynamics/pointfoot-legged-gym.git pointfoot-legged-gym; \
fi && \
cd pointfoot-legged-gym && bash install.sh && source ~/.bashrc
- 验证 NVIDIA 驱动是否安装成功
nvidia-smi
如果显示了 NVIDIA 驱动版本和 GPU 信息,说明驱动已成功安装。
- 验证Isaac Gym是否安装成功
运行Isaac Gym 的示例,它可以帮助确认 Isaac Gym 是否正确安装并配置。
conda activate pointfoot_legged_gym
cd ~/limx_rl/isaacgym/python/examples
python 1080_balls_of_solitude.py
- 验证pointfoot-legged-gym是否安装成功
当您安装完RL环境后,在~/limx_rl目录下包含如下所示的内容:
cd ~/limx_rl
.
├── isaacgym
├── pointfoot-legged-gym
└── rsl_rl
6.3 分步安装RL环境
6.3.1 安装显卡驱动
- 检查当前安装的 NVIDIA 驱动
如果系统中已经安装了 NVIDIA 驱动,可以通过以下命令检查:
nvidia-smi
如果显示了 NVIDIA 驱动版本和 GPU 信息,说明驱动已经安装。如果没有安装,请继续以下步骤。
- 添加官方的 NVIDIA 驱动 PPA(Personal Package Archive):
sudo add-apt-repository ppa:graphics-drivers/ppa
sudo apt update
- 使用以下命令列出可用的 NVIDIA 驱动版本:
ubuntu-drivers devices
- 安装 NVIDIA 驱动
选择推荐的驱动版本进行安装,例如:
将XXX替换为推荐的驱动版本号,例如nvidia-driver-535。 - 禁用 Nouveau 驱动(如果必要):某些情况下,您可能需要禁用默认的 Nouveau 驱动:
sudo bash -c "echo blacklist nouveau > /etc/modprobe.d/blacklist-nouveau.conf"
sudo bash -c "echo options nouveau modeset=0 >> /etc/modprobe.d/blacklist-nouveau.conf"
sudo update-initramfs -u
- 重启系统:完成驱动安装后,重启系统使驱动生效:
sudo reboot
- 验证驱动安装:重启后,使用以下命令验证 NVIDIA 驱动是否安装成功:
nvidia-smi
如果显示了 NVIDIA 驱动版本和 GPU 信息,说明驱动已成功安装。
6.3.2 Conda 环境配置
Conda 是一个跨平台的开源包和环境管理系统,它能够快速创建虚拟环境,并在其中进行包的安装、运行和更新操作,从而有效解决不同软件包对 Python 和依赖库版本的要求问题。请按照下列步骤操作下载并安装Conda:
- 下载 Anaconda 安装脚本:
使用 wget 下载 Anaconda 的安装脚本(请根据需要选择适合的版本):
cd && wget https://repo.anaconda.com/archive/Anaconda3-2024.06-1-Linux-x86_64.sh
- 运行安装脚本:
执行下载的安装脚本:
bash Anaconda3-2024.06-1-Linux-x86_64.sh
- 安装过程中:
- 按 Enter 键查看许可协议。
- 输入 yes 接受许可协议。
- 选择默认安装路径(通常为 ~/anaconda3),或根据需要自定义路径。
- 当脚本询问是否初始化 Conda 时,选择 yes 以自动配置环境。 - 初始化 Conda(如果安装过程中未自动配置):
~/anaconda3/bin/conda init
- 使 .bashrc 配置立即生效:
source ~/.bashrc
- 配置 Conda 环境用于 RL 训练
创建一个专门用于 RL 训练的 Conda 环境(例如,pointfoot_legged_gym),并指定 Python 版本:
conda create --name pointfoot_legged_gym python=3.8
- 激活刚刚创建的环境:
conda activate pointfoot_legged_gym
-
请按照以下步骤安装 PyTorch 库:
1.访问 yPyTorch安装页面。
2. 选择以下选项:- 操作系统:Linux
- 包管理器:Conda
- 语言:Python
- CUDA 版本:12.1

-
3.生成安装命令并运行以下命令进行安装:
conda install pytorch torchvision torchaudio pytorch-cuda=12.1 -c pytorch -c nvidia
6.3.3 安装Isaac Gym
下载Isaac Gym
- 下载页面:前往 Isaac Gym 下载页面。
- 接受条款和条件:您可能需要接受一些条款和条件。
- 下载:选择合适的版本下载 Isaac Gym 的安装包。
安装 Isaac Gym
- 安装setuptools:
setuptools 是一个用于 Python 包管理和分发的库,它帮助打包 Python 项目,使其易于安装和分发,并处理项目的依赖项。通过指定版本59.5.0,可以确保与其他依赖项的兼容性和稳定性。
pip install setuptools==59.5.0
- 安装TensorBoard
TensorBoard 是一个用于可视化 TensorFlow 图表、指标和其他相关数据的工具。为了确保兼容性和稳定性,我们指定安装版本为2.12.0。
pip install tensorboard==2.12.0
- 激活pointfoot_legged_gym的conda环境
conda activate pointfoot_legged_gym
- 创建一个用于存放训练代码的路径(如:
limx_rl)
mkdir -p ~/limx_rl
- 解压文件:将上一步下载的安装压缩文件解压到
limx_rl目录。注意:请将/path/to/IsaacGym_Preview_4_Package.tar.gz替换为安装包的实际路径。
tar -xzvf /path/to/IsaacGym_Preview_4_Package.tar.gz -C ~/limx_rl/
- 安装 Isaac Gym并配置:
cd ~/limx_rl/isaacgym/python
pip install -e .
- 解决NumPy兼容性问题:
在较新的版本的 NumPy 中,np.float被弃用,使用时会出现警告或错误。直接使用 Python 内置的float类型可以避免这些问题。
cd ~/limx_rl/isaacgym/python
sed -i 's/np.float/float/' isaacgym/torch_utils.py
- 验证安装
运行Isaac Gym 的示例,它可以帮助确认 Isaac Gym 是否正确安装并配置。
cd ~/limx_rl/isaacgym/python/examples
python 1080_balls_of_solitude.py
6.3.4 安装pointfoot-legged-gym
- 激活pointfoot_legged_gym的conda环境
conda activate pointfoot_legged_gym
- 安装pointfoot-legged-gym
cd ~/limx_rl
git clone https://github.com/limxdynamics/pointfoot-legged-gym.git pointfoot-legged-gym
cd ~/limx_rl/pointfoot-legged-gym && pip install -e .
- 验证安装
当您完成前面所有步骤的操作后,在~/limx_rl目录下包含如下所示的内容:
cd ~/limx_rl
.
├── isaacgym
└── pointfoot-legged-gym
6.4 部署RL Docker 容器环境
6.4.1 安装Docker服务
#### 更新包索引并安装依赖
# Add Docker's official GPG key:
sudo apt-get update
sudo apt-get install ca-certificates curl
sudo mkdir -p /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg
# Add the repository to Apt sources:
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update
#### 安装 Docker 引擎
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
6.4.2 安装 NVIDIA Docker 工具
#### 设置 NVIDIA Docker 仓库###
distribution=$(. /etc/os-release;echo $ID$VERSION_ID)
curl -s -L https://nvidia.github.io/nvidia-docker/gpgkey | sudo apt-key add -
curl -s -L https://nvidia.github.io/nvidia-docker/$distribution/nvidia-docker.list | sudo tee /etc/apt/sources.list.d/nvidia-docker.list
#### 更新包索引并安装 NVIDIA Docker 工具
sudo apt-get update
sudo apt-get install -y nvidia-docker2
#### 重启 Docker 服务
sudo systemctl restart docker
6.4.3 验证Docker环境安装是否正常
#### 运行带有 NVIDIA 支持的测试容器 查看是否能够在容器中成功执行 nvidia-smi命令
sudo docker run -it --rm --gpus all nvidia/cuda:12.1.1-base-ubuntu20.04 nvidia-smi
输出为如下信息 表明Docker环境安装正常:
拉取RL Docker环境基础镜像
如下RL 环境基础镜像预安装了conda issacgym pointfoot-legged-gym 等环境,拉取镜像后即可直接使用无需再配置相关环境
sudo docker pull crpi-yznuflhnzo12tr44.cn-shanghai.personal.cr.aliyuncs.com/limx-rl/base:v10
7. RL 模型训练
7.1 启动训练
- 打开终端
Ctrl+Alt+T - 设置机器人型号
- 请参考“查看/设置机器人型号”章节,查看您的机器人型号。如果尚未设置,请按照以下步骤进行设置。
- 通过 Shell 命令 tree -L 1 resources/robots/pointfoot 列出可用的机器人类型:
limx@limx:~$ cd ~/limx_rl/pointfoot-legged-gym
limx@limx:~$ tree -L 1 resources/robots/pointfoot
resources/robots/pointfoot
├── PF_P441A
├── PF_P441B
├── PF_P441C
├── PF_P441C2
├── PF_TRON1A
├── SF_TRON1A
└── WF_TRON1A
- 以 PF_TRON1A(请根据实际机器人类型进行替换)为例,设置机器人型号类型:
echo 'export ROBOT_TYPE=PF_TRON1A' >> ~/.bashrc && source ~/.bashrc
- 激活pointfoot_legged_gym的conda环境
conda activate pointfoot_legged_gym
- 进入训练代码所在路径,开始训练
参数选项:
- --task=pointfoot_flat:指定任务或环境类型为 pointfoot_flat。- --headless:以无头模式运行,即不显示任何图形界面。通常用于在没有显示器的服务器上运行,或者在需要高效计算而不需要图形渲染的情况下使用。
- --num_envs 8192:指定要创建的环境数量为 8192。
命令 1: 无头模式运行
cd ~/limx_rl/pointfoot-legged-gym
python legged_gym/scripts/train.py --task=pointfoot_flat --headless
- 命令 2: 图形模式下运行
cd ~/limx_rl/pointfoot-legged-gym
python legged_gym/scripts/train.py --task=pointfoot_flat

7.2 继续训练
如果您的训练中途终止,可以指定一个检查点文件的位置,接着继续训练。
- 首先,您需要激活pointfoot_legged_gym的conda环境
conda activate pointfoot_legged_gym
- 命令 1: 无头模式下继续训练
cd ~/limx_rl/pointfoot-legged-gym
python legged_gym/scripts/train.py --task=pointfoot_flat --resume --headless --load_run Dec23_17-38-22_ --checkpoint 200
- 命令 2: 图形模式下继续训练
cd ~/limx_rl/pointfoot-legged-gym
python legged_gym/scripts/train.py --task=pointfoot_flat --resume --load_run Dec23_17-38-22_ --checkpoint 200
- --load_run:
说明:指定要加载的训练运行的标识符(例如,训练运行的名字或ID)。这个标识符通常与训练过程相关联,用于从 logs 目录中找到相应的运行记录或配置。
获取方式:- 查看 logs 目录:进入 logs 目录查看子目录或文件,这些子目录或文件通常会以训练运行的标识符命名。
- 示例路径:
ls -l ~/limx_rl/pointfoot-legged-gym/logs/pointfoot_flat
您会看到以机器人类型命名的目录,训练结果会按照机器人类型分别存储于对应目录下,以PF_TRON1A为例,进入后可以看到类似于 Apr16_16-51-06_ 这样的目录,Apr16_16-51-06_ 就是 --load_run 参数的值。

- --checkpoint:
说明:指定要加载的检查点文件。检查点文件保存了模型的中间状态,可以用于恢复训练或进行推断。
获取方式:- 查看 logs 目录:在 logs 目录下的相应 --load_run 目录中,通常会有保存检查点的文件。这些文件通常以 .pt 或类似扩展名存在,文件名可能包含训练的轮次或时间戳。
- 示例路径:
ls -l ~/limx_rl/pointfoot-legged-gym/logs/pointfoot_flat/PF_TRON1A/Apr16_16-51-06_
- 会看到类似于model_200.pt 的文件,则200就是 --checkpoint 参数的值。

7.3 查看训练情况
- 打开终端
Ctrl+Alt+T - 激活pointfoot_legged_gym的conda环境
conda activate pointfoot_legged_gym
- 启动 tensorboard(以PF_TRON1A为例)
cd ~/limx_rl/pointfoot-legged-gym
tensorboard --logdir=logs/pointfoot_flat/PF_TRON1A
- 查看训练情况
在浏览器地址栏输入http://127.0.0.1:6006,查看训练情况。
7.4 导出训练结果
- 打开终端
Ctrl+Alt+T - 激活pointfoot_legged_gym的conda环境
conda activate pointfoot_legged_gym
- 安装ONNX
若您尚未安装 ONNX 库,请安装它。ONNX 允许在不同的深度学习框架(例如 PyTorch、TensorFlow、Caffe2、MXNet 等)之间轻松转换模型。
pip install onnx
- 完成训练后查看训练结果
默认读取最新的run和checkpoint,如需选择特定的run和checkpoint,请输入--load_run和--checkpoint参数。- --load_run:
说明:指定要加载的训练运行的标识符(例如,训练运行的名字或ID)。这个标识符通常与训练过程相关联,用于从logs目录中找到相应的运行记录或配置。
获取方式:- 查看
logs目录:进入logs目录查看子目录或文件,这些子目录或文件通常会以训练运行的标识符命名。 - 示例路径:
- 查看
- --load_run:
ls -l ~/limx_rl/pointfoot-legged-gym/logs/pointfoot_flat
您会看到以机器人类型命名的目录,训练结果会按照机器人类型分别存储于对应目录下,以PF_TRON1A为例,进入后可以看到类似于 Apr16_16-51-06_ 这样的目录,Apr16_16-51-06_ 就是 --load_run 参数的值。

- --checkpoint:
说明:指定要加载的检查点文件。检查点文件保存了模型的中间状态,可以用于恢复训练或进行推断。
获取方式:- 查看
logs目录:在logs目录下的相应--load_run目录中,通常会有保存检查点的文件。这些文件通常以.pt或类似扩展名存在,文件名可能包含训练的轮次或时间戳。 - 示例路径:
- 查看
ls -l ~/limx_rl/pointfoot-legged-gym/logs/pointfoot_flat/PF_TRON1A/Apr16_16-51-06_
会看到类似于model_200.pt 的文件,则200就是 --checkpoint 参数的值。
- 使用示例
cd ~/limx_rl/pointfoot-legged-gym
python legged_gym/scripts/play.py --task=pointfoot_flat --load_run Apr16_16-51-06_ --checkpoint 200
- 导出模型的ONNX格式文件
默认读取最新的run和checkpoint,如需选择特定的run和checkpoint,请输入--load_run和--checkpoint参数- 参数选项说明:
- --load_run:
说明:指定要加载的训练运行的标识符(例如,训练运行的名字或ID)。这个标识符通常与训练过程相关联,用于从logs目录中找到相应的运行记录或配置。
获取方式:- 查看
logs目录:进入logs目录查看子目录或文件,这些子目录或文件通常会以训练运行的标识符命名。 - 示例路径:
- 查看
- --load_run:
- 参数选项说明:
ls -l ~/limx_rl/pointfoot-legged-gym/logs/pointfoot_flat
您会看到以机器人类型命名的目录,训练结果会按照机器人类型分别存储于对应目录下,以PF_TRON1A为例,进入后可以看到类似于 Apr16_16-51-06_ 这样的目录,Apr16_16-51-06_ 就是 --load_run 参数的值。
- --checkpoint:
说明:指定要加载的检查点文件。检查点文件保存了模型的中间状态,可以用于恢复训练或进行推断。
获取方式:
- 查看 logs 目录:在 logs 目录下的相应 --load_run 目录中,通常会有保存检查点的文件。这些文件通常以 .pt 或类似扩展名存在,文件名可能包含训练的轮次或时间戳。
- 示例路径:
ls -l ~/limx_rl/pointfoot-legged-gym/logs/pointfoot_flat/PF_TRON1A/Apr16_16-51-06_
会看到类似于model_200.pt 的文件,则200就是 --checkpoint 参数的值。

- 使用示例
cd ~/limx_rl/pointfoot-legged-gym
python legged_gym/scripts/export_policy_as_onnx.py --task=pointfoot_flat --load_run Apr16_16-51-06_ --checkpoint 200
以PF_TRON1A为例,转换后的ONNX文件保存在目录:
ls -l ~/limx_rl/pointfoot-legged-gym/logs/pointfoot_flat/PF_TRON1A/exported/policies

7.5 Docker环境下启动训练
- 打开终端
Ctrl+Alt+T - 拉取RL Docker容器环境基础镜像
sudo docker pull crpi-yznuflhnzo12tr44.cn-shanghai.personal.cr.aliyuncs.com/limx-rl/base:v10
- 运行RL 容器环境
sudo docker run -it --gpus all --rm crpi-yznuflhnzo12tr44.cn-shanghai.personal.cr.aliyuncs.com/limx-rl/base:v10
- 设置机器人型号:请参考“查看/设置机器人型号”章节,查看您的机器人型号。如果尚未设置,请按照以下步骤进行设置。
$ cd limx_rl/pointfoot-legged-gym
$ tree -L 1 resources/robots/pointfoot/
resources/robots/pointfoot/
├── PF_P441A
├── PF_P441B
├── PF_P441C
├── PF_P441C2
├── PF_TRON1A
├── SF_TRON1A
└── WF_TRON1A
- 以 PF_TRON1A(请根据实际机器人类型进行替换)为例,设置机器人型号类型:
export ROBOT_TYPE=PF_TRON1A
** 备注:通过在容器中执行export命令设置环境变量 只是在当前容器环境中生效 重新启动容器后不再生效**
** 可在通过 -e 参数 带上要执行的环境变量信息 使得每次运行RL 容器环境时 环境变量自动生效**
- 进入训练代码所在路径,开始训练
参数选项:
- --task=pointfoot_flat:指定任务或环境类型为 pointfoot_flat。- --headless:以无头模式运行,即不显示任何图形界面。通常用于在没有显示器的服务器上运行,或者在需要高效计算而不需要图形渲染的情况下使用。
- --num_envs 1024:指定要创建的环境数量为 1024。
命令 1: 无头模式运行
cd limx_rl/pointfoot-legged-gym
python legged_gym/scripts/train.py --task=pointfoot_flat --headless
8. RL 训练结果部署
8.1 基于Python 部署
Python运动控制算法开发接口让不熟悉C++和ROS的开发者也能轻松使用Python进行算法开发。Python语言易学、语法简洁,并拥有丰富的第三方库,使开发者能够快速上手并高效实现算法。借助Python接口,开发者可利用其动态特性进行快速原型设计和实验验证,加速算法迭代与优化。同时,Python的跨平台性和强大生态系统,使运动算法能广泛应用于不同平台和环境。
此外,Python的灵活性大大简化了强化学习(RL)模型的快速部署。开发者可以轻松将RL模型集成到仿真和真实硬件中,快速验证和优化算法性能。
8.1.1 部署环境配置
- 配置 Conda 环境用于Python部署
创建一个专门用于Python部署运行的 Conda 环境(例如,pointfoot_deploy),并指定 Python 版本:
conda create --name pointfoot_deploy python=3.8
- 激活刚刚创建的环境:
conda activate pointfoot_deploy
8.1.2 创建工作空间
可以按照以下步骤,创建一个RL部署开发工作空间:
- 打开一个Bash终端。
- 创建一个新目录来存放工作空间。例如,可以在用户的主目录下创建一个名为“limx_ws”的目录:
mkdir -p ~/limx_ws
- 下载 MuJoCo 仿真器
cd ~/limx_ws
git clone --recurse https://github.com/limxdynamics/tron1-mujoco-sim.git
- 下载部署实现
cd ~/limx_ws
git clone --recurse https://github.com/limxdynamics/tron1-rl-deploy-python.git
- 安装运动控制开发库(如果尚未安装):
- Linux x86_64 环境
# 激活pointfoot_deploy的conda环境
conda activate pointfoot_deploy
# 安装limxsdk
pip install tron1-rl-deploy-python/limxsdk-lowlevel/python3/amd64/limxsdk-*-py3-none-any.whl
- Linux aarch64 环境
# 激活pointfoot_deploy的conda环境
conda activate pointfoot_deploy
# 安装limxsdk
pip install tron1-rl-deploy-python/limxsdk-lowlevel/python3/aarch64/limxsdk-*-py3-none-any.whl
- 设置机器人型号:请参考“查看/设置机器人型号”章节,查看您的机器人型号。如果尚未设置,请按照以下步骤进行设置。
- 通过 Shell 命令
tree -L 1 tron1-mujoco-sim/robot-description/pointfoot列出可用的机器人类型:
- 通过 Shell 命令
limx@limx:~$ tree -L 1 tron1-mujoco-sim/robot-description/pointfoot
tron1-mujoco-sim/robot-description/pointfoot
├── PF_P441A
├── PF_P441B
├── PF_P441C
├── PF_P441C2
├── PF_TRON1A
├── SF_TRON1A
└── WF_TRON1A
- 以 PF_TRON1A(请根据实际机器人类型进行替换)为例,设置机器人型号类型:
echo 'export ROBOT_TYPE=PF_TRON1A' >> ~/.bashrc && source ~/.bashrc
- 选择训练环境
目前支持的训练环境有isaacgym和isaaclab,以isaacgym为例,设置训练环境类型
echo 'export RL_TYPE=isaacgym' >> ~/.bashrc && source ~/.bashrc
8.1.3 更新RL训练模型
在您的工作空间中,以 PF_TRON1A机器人类型、isaacgym训练框架为例,RL模型所在路径为:~/limx_ws/tron1-rl-deploy-python/controllers/model/PF_TRON1A/policy/isaacgym,如下所示。请根据您的训练结果更新替换。
~/limx_ws/tron1-rl-deploy-python/controllers/model/PF_TRON1A/
├── params.yaml
└── policy
├── encoder.onnx
├── isaacgym
│ ├── encoder.onnx
│ └── policy.onnx
├── isaaclab
│ ├── encoder.onnx
│ └── policy.onnx
└── policy.onnx
8.1.4 仿真调试
- 打开一个Bash终端,运行 MuJoCo 仿真器。
# 激活pointfoot_deploy的conda环境
conda activate pointfoot_deploy
# 运行仿真器
cd ~/limx_ws
python tron1-mujoco-sim/simulator.py
- 打开一个Bash终端,运行控制算法。
如果您的机器人处于摔倒状态,请在启动运动控制后,单击 MuJoCo 仿真器左侧菜单栏的“Reset”按钮以重置机器人。此时,您将看到机器人恢复并开始行走。
# 激活pointfoot_deploy的conda环境
conda activate pointfoot_deploy
# 运行控制算法
python tron1-rl-deploy-python/main.py
- 打开一个Bash终端,运行虚拟遥控器:仿真的过程中,您可以使用虚拟遥控器来控制机器人运动。左摇杆:控制前进/后退/左转/右转运动;右遥控:可控制机器人左右横向运动。
./tron1-mujoco-sim/robot-joystick/robot-joystick
8.1.5 真机调试
- 修改开发者电脑IP:确保您的开发电脑与机器人本体通过外置网口连接。设置您的电脑IP地址为:
10.192.1.200,并通过Shell命令ping 10.192.1.2能够正常ping通。如下图所示对您的开发电脑进行IP设置:
- 打开开发者模式:机器人开机后,同时按下遥控器按键
R1 + Left,这时本体主机将会自动重启并切换机器人到开发者模式。在此模式下,用户可以开发自己的运动控制算法。模式设置掉电后不会失效,重新开机后仍然是开发者模式。遥控器切换工作模式的按键列表如下:
| 按键 | 模式 | 说明 |
|---|---|---|
| R1+Left | 开发者模式(需授权) | 用户使用运动控制开发接口开发自己的运动控制算法。 |
| R1+Right | 遥控模式 | 运行预安装的控制算法,实现复杂地形的平稳行走,如:上楼、下楼、过坎等。 |
- 进行校零动作:机器人开机启动后,执行运控程序之前,请进行校零,使机器人各个关节回到初始位置。校零对应的遥控器按键为
L1+R1。 - 实机部署运行。在Bash终端只需下面Shell命令启动控制算法(在进行实机部署运行时,确保机器人吊装非常重要):
# 激活pointfoot_deploy的conda环境
conda activate pointfoot_deploy
# 指定机器人IP地址,运行控制算法
python tron1-rl-deploy-python/main.py 10.192.1.2
- 这时您可以通过遥控器按键
L1 + △开启机器人行走功能。左摇杆:控制前进/后退/左转/右转运动;右遥控:可控制机器人左右横向运动。 - 遥控器按
L1 + □关闭机器人行走功能。
8.1.6 真机部署
当您完成仿真和真机调试后,可以将您的算法程序部署到机器人上(在部署测试完成之前,确保机器人始终保持吊装状态非常重要,以保障安全)。详细步骤如下:
- 准备工作
- 保持机器人继续处于开发者模式:确保机器人仍处于开发者模式,方便您进行程序部署和调试。
- 网络连接:确保开发电脑与机器人通过外置网口(Ethernet)连接,网络稳定且通信正常。部署完成后,网络连接将不再需要。
- 拷贝算法程序到机器人
- 在开发电脑上打开终端,进入到存放算法程序的工作目录,例如 ~/limx_ws。
- 使用 scp 命令将包含算法的目录拷贝到机器人中,默认机器人用户为 guest,密码为 123456。
cd ~/limx_ws
scp -r tron1-rl-deploy-python guest@10.192.1.2:/home/guest
- 安装运动控制开发库(如果尚未安装):
ssh guest@10.192.1.2 "pip install /home/guest/tron1-rl-deploy-python/limxsdk-lowlevel/python3/amd64/limxsdk-*-py3-none-any.whl"
- 配置算法自动启动
- SSH 登录机器人:使用 ssh 命令远程登录到机器人系统,密码为 123456。
ssh guest@10.192.1.2
- 修改自启动脚本:
- 打开自启动脚本 /home/guest/autolaunch/autolaunch.sh 进行编辑:
busybox vi /home/guest/autolaunch/autolaunch.sh
- 在脚本中找到启动 main.py 的命令:确保 python3 /home/guest/tron1-rl-deploy-python/main.py 10.192.1.2 这一行未被注释(没有 # 注释符号)。编辑完成后,保存并退出编辑器。
#!/bin/bash
while true; do
# 启动用于控制 Pointfoot 机器人的 Python 控制器脚本
# 参数 10.192.1.2 表示机器人所在的 IP 地址
# 该行被注释掉,若要启用自动控制器启动,请取消注释
# 请根据您控制器脚本的实际路径修改此路径
export ROBOT_TYPE=PF_TRON1A
export RL_TYPE=isaacgym #需要加上这两行环境变量,否则重启后脚本不会自启动
python3 /home/guest/tron1-rl-deploy-python/main.py 10.192.1.2
# 使用 roslaunch 启动机器人控制算法
# 若要启动机器人控制算法,请取消下面注释
# 请根据您安装的实际路径修改路径
# source /home/guest/install/setup.bash
# roslaunch robot_hw pointfoot_hw.launch
# 等待 3 秒后重新启动
sleep 3
done
- 重启机器人电脑。修改完成后,执行以下命令重启机器人:
reboot
- 控制机器人运动
等待系统启动后,您可以使用遥控器控制机器人:- L1 + △:启动机器人行走功能;
- L1 + □: 关闭机器人行走功能;
- 左摇杆:控制机器人前进、后退、左转、右转;
- 右摇杆:控制机器人左右横向运动。
8.2 基于ROS C++ 部署
8.2.1 部署环境配置
- 安装ROS Noetic:我们推荐在Ubuntu 20.04操作系统上建立基于ROS Noetic的算法开发环境。ROS提供了一系列工具和库,如核心库、通信库和仿真工具(如Gazebo),极大地便利了机器人算法的开发、测试和部署。这些资源为用户提供了一个丰富而完整的算法开发环境。
- ROS Noetic 安装请参考文档:https://wiki.ros.org/noetic/Installation/Ubuntu,选择“ros-noetic-desktop-full”进行安装。
- ROS Noetic 安装完成后,Bash终端输入以下Shell命令,安装开发环境所依赖的库:
sudo apt-get update
sudo apt install ros-noetic-urdf \
ros-noetic-kdl-parser \
ros-noetic-urdf-parser-plugin \
ros-noetic-hardware-interface \
ros-noetic-controller-manager \
ros-noetic-controller-interface \
ros-noetic-robot-state-* \
ros-noetic-joint-state-* \
ros-noetic-controller-manager-msgs \
ros-noetic-control-msgs \
ros-noetic-ros-control \
ros-noetic-gazebo-* \
ros-noetic-rqt-gui \
ros-noetic-rqt-controller-manager \
ros-noetic-plotjuggler* \
ros-noetic-joy-teleop ros-noetic-joy \
cmake build-essential libpcl-dev libeigen3-dev libopencv-dev libmatio-dev \
python3-pip libboost-all-dev libtbb-dev liburdfdom-dev liborocos-kdl-dev -y
- 安装onnxruntime依赖,下载链接:https://github.com/microsoft/onnxruntime/releases/tag/v1.10.0 。请您根据自己的操作系统和平台选择合适版本下载。如在Ubuntu 20.04 x86_64,请按下面步骤进行安装:
wget https://github.com/microsoft/onnxruntime/releases/download/v1.10.0/onnxruntime-linux-x64-1.10.0.tgz
tar xvf onnxruntime-linux-x64-1.10.0.tgz
sudo cp -a onnxruntime-linux-x64-1.10.0/include/* /usr/include
sudo cp -a onnxruntime-linux-x64-1.10.0/lib/* /usr/lib
8.2.2 创建工作空间
可以按照以下步骤,创建一个RL部署开发工作空间:
- 打开一个Bash终端。
- 创建一个新目录来存放工作空间。例如,可以在用户的主目录下创建一个名为“limx_ws”的目录:
mkdir -p ~/limx_ws/src
- 下载运动控制开发接口:
cd ~/limx_ws/src
git clone https://github.com/limxdynamics/limxsdk-lowlevel.git
- 下载Gazebo仿真器:
cd ~/limx_ws/src
git clone https://github.com/limxdynamics/tron1-gazebo-ros.git
- 下载机器人模型描述文件
cd ~/limx_ws/src
git clone https://github.com/limxdynamics/robot-description.git
- 下载可视化工具
cd ~/limx_ws/src
git clone https://github.com/limxdynamics/robot-visualization.git
- 下载RL部署源码:
cd ~/limx_ws/src
git clone https://github.com/limxdynamics/tron1-rl-deploy-ros.git
- 设置机器人型号:请参考“查看/设置机器人型号”章节,查看您的机器人型号。如果尚未设置,请按照以下步骤进行设置。
- 通过 Shell 命令
tree -L 1 src/robot-description/pointfoot列出可用的机器人类型:
- 通过 Shell 命令
limx@limx:~$ tree -L 1 src/robot-description/pointfoot
src/robot-description/pointfoot
├── PF_P441A
├── PF_P441B
├── PF_P441C
├── PF_P441C2
├── PF_TRON1A
├── SF_TRON1A
└── WF_TRON1A
- 以 PF_TRON1A(请根据实际机器人类型进行替换)为例,设置机器人型号类型:
echo 'export ROBOT_TYPE=PF_TRON1A' >> ~/.bashrc && source ~/.bashrc
- 选择训练环境
目前支持的训练环境有isaacgym和isaaclab,以isaacgym为例,设置训练环境类型
echo 'export RL_TYPE=isaacgym' >> ~/.bashrc && source ~/.bashrc
- 编译工程:
# 如您安装了Conda,请临时禁用 Conda 环境
# 因为 Conda 会干扰 ROS 的运行环境设置
conda deactivate
cd ~/limx_ws
catkin_make install
8.2.3 更新RL训练模型
在您的工作空间中,RL模型和配置文件所在路径为:~/limx_ws/src/tron1-rl-deploy-ros/robot_controllers/config,如下所示。请根据你的训练结果更新替换。
在您的工作空间中,以 PF_TRON1A机器人类型为例,RL模型和配置文件所在路径为:~/limx_ws/src/tron1-rl-deploy-ros/robot_controllers/config/pointfoot/PF_TRON1A,如下所示。请根据您的训练结果更新替换。
tree ~/limx_ws/src/tron1-rl-deploy-ros/robot_controllers/config/pointfoot/PF_TRON1A
.
├── params.yaml
└── policy
├── policy.onnx
└── encoder.onnx
8.2.4 仿真调试
- 请进到您的工作空间,完成编译:
# 如您安装了Conda,请临时禁用 Conda 环境
# 因为 Conda 会干扰 ROS 的运行环境设置
conda deactivate
cd ~/limx_ws
catkin_make install
- 运行部署仿真:启动后,您将看到如下所示的界面,包括Gazebo仿真器和Robot Steering交互窗口。
- 在Gazebo窗口中,您可以使用快捷键
Ctrl + Shift + R复位机器人; - 您还可以通过Robot Steering交互窗口设置,发布主题为/cmd_vel,控制机器人的移动。
- 在Gazebo窗口中,您可以使用快捷键
# 如您安装了Conda,请临时禁用 Conda 环境
# 因为 Conda 会干扰 ROS 的运行环境设置
conda deactivate
# 运行 robot_hw
source install/setup.bash
roslaunch robot_hw pointfoot_hw_sim.launch
#我们也提供了复杂地形的环境文件,可以通过下列指令启动:
#roslaunch pointfoot_gazebo mixed_terrain.launch
- 虚拟遥控器:如果您觉得使用
Robot Steering控制机器人不方便,可以使用虚拟遥控器来简化操作。以下是使用虚拟遥控器的具体步骤。- 下载并运行虚拟遥控器
# 下载虚拟遥控器
git clone https://github.com/limxdynamics/robot-joystick.git
# 运行虚拟遥控器
./robot-joystick/robot-joystick
- 这时,您可以使用虚拟遥控器来控制机器人运动。左摇杆:控制前进/后退/左转/右转运动;右遥控:可控制机器人左右横向运动。
8.2.5 真机调试
- 修改开发者电脑IP:确保您的开发电脑与机器人本体通过外置网口连接。设置您的电脑IP地址为:
10.192.1.200,并通过Shell命令ping 10.192.1.2能够正常ping通。如下图所示对您的开发电脑进行IP设置:
- 请进到您的工作空间,找到
pointfoot_hw.launch启动文件,修改机器人的IP地址为10.192.1.2,如下图所示:
完成修改后进行编译:
# 如您安装了Conda,请临时禁用 Conda 环境
# 因为 Conda 会干扰 ROS 的运行环境设置
conda deactivate
cd ~/limx_ws
catkin_make install
- 打开开发者模式:机器人开机后,同时按下遥控器按键
R1 + Left,这时本体主机将会自动重启并切换机器人到开发者模式。在此模式下,用户可以开发自己的运动控制算法。模式设置掉电后不会失效,重新开机后仍然是开发者模式。遥控器切换工作模式的按键列表如下:
| 按键 | 模式 | 说明 |
|---|---|---|
| R1+Left | 开发者模式(需授权) | 用户使用运动控制开发接口开发自己的运动控制算法。 |
| R1+Right | 遥控模式 | 运行预安装的控制算法,实现复杂地形的平稳行走,如:上楼、下楼、过坎等。 |
- 进行校零动作:机器人开机启动后,执行运控程序之前,请进行校零,使机器人各个关节回到初始位置。校零对应的遥控器按键为L1+R1。
- 实机部署运行。在Bash终端只需下面Shell命令启动控制算法(在进行实机部署运行时,确保机器人吊装非常重要):
# 如您安装了Conda,请临时禁用 Conda 环境
# 因为 Conda 会干扰 ROS 的运行环境设置
conda deactivate
# 运行 robot_hw
source install/setup.bash
roslaunch robot_hw pointfoot_hw.launch
- 这时您可以通过遥控器按键
L1 + △开启机器人行走功能。左摇杆:控制前进/后退/左转/右转运动;右遥控:可控制机器人左右横向运动。 - 遥控器按
L1 + □关闭机器人行走功能。
8.2.6 真机部署
当您完成仿真和真机调试后,可以将算法程序部署到机器人上(在部署测试完成之前,确保机器人始终保持吊装状态非常重要,以保障安全)。详细步骤如下:
当您完成仿真和真机调试后,可以将算法程序部署到机器人上(在部署测试完成之前,确保机器人始终保持吊装状态非常重要,以保障安全)。详细步骤如下:
- 准备工作
- 保持机器人继续处于开发者模式:确保机器人仍处于开发者模式,方便您进行程序部署和调试。
- 网络连接:确保开发电脑通过外置网口(Ethernet)与机器人稳定连接,通信正常。部署完成后,网络连接将不再需要。
- 拷贝算法程序到机器人
- 在开发电脑上打开终端,进入到存放算法程序的工作目录,例如 ~/limx_ws。
- 使用 scp 命令将包含算法的目录拷贝到机器人中,默认机器人用户为 guest,密码为 123456。
cd ~/limx_ws
scp -r install guest@10.192.1.2:/home/guest
- 配置算法自动启动
- SSH 登录机器人:使用 ssh 命令远程登录到机器人系统,密码为 123456。
ssh guest@10.192.1.2
- 修改自启动脚本:
- 打开自启动脚本 /home/guest/autolaunch/autolaunch.sh 进行编辑:
busybox vi /home/guest/autolaunch/autolaunch.sh
- 在脚本中找到启动 roslaunch 的命令:确保 source /home/guest/install/setup.bash 和roslaunch robot_hw pointfoot_hw.launch 这两行未被注释(没有 # 注释符号)。编辑完成后,保存并退出编辑器。
#!/bin/bash
# 请修改为实际机器人的类型
export ROBOT_TYPE="WF_TRON1A"
# 目前支持的训练环境有 isaacgym 和 isaaclab
# 请修改为实际训练环境
export RL_TYPE=isaacgym
while true; do
# 启动用于控制 Pointfoot 机器人的 Python 控制器脚本
# 参数 10.192.1.2 表示机器人所在的 IP 地址
# 该行被注释掉,若要启用自动控制器启动,请取消注释
# 请根据您控制器脚本的实际路径修改此路径
# 注意:如limxsdk与机器人本体版本不匹配,请pip uninstall limxsdk后,再pip install安装更新!
# python3 /home/guest/tron1-rl-deploy-python/main.py 10.192.1.2
# 使用 roslaunch 启动机器人控制算法
# 若要启动机器人控制算法,请取消下面注释
# 请根据您安装的实际路径修改路径
source /opt/ros/noetic/setup.bash
source /home/guest/install/setup.bash
roslaunch robot_hw pointfoot_hw.launch
# 等待 3 秒后重新启动
sleep 3
done
- 重启机器人:修改完成后,关机重启机器人。
- 控制机器人运动
在首次真机部署时,机器可能出现预期外的运动表现,请确保TRON1和操作者都处于安全的状态。
等待系统启动后,您可以使用遥控器控制机器人:
- L1 + △:启动机器人行走功能;
- L1 + □: 关闭机器人行走功能;
- 左摇杆:控制机器人前进、后退、左转、右转;
- 右摇杆:控制机器人左右横向运动。
9. RealSense 相机
我们在机器人上安装了RealSense D435i相机,它是一款强大的深度摄像头,可提供深度和图像数据,主要用于地形感知。摄像头视角范围如下:
通过本章介绍,您可以学习如何使用ROS的Noetic版本获取D435i相机数据。具体步骤如下:
-
确认您的电脑安装了ROS的Noetic版本系统(请参考文档:https://wiki.ros.org/noetic/Installation/Ubuntu,选择“ros-noetic-desktop-full”进行安装)。
-
配置网络连接:确保您的开发电脑与机器人本体通过外置网口连接。设置您的电脑IP地址为:
10.192.1.200,并通过Shell命令ping 10.192.1.2能够正常ping通。如下图所示对您的开发电脑进行IP设置:
-
通过机器人管理页面打开摄像头。
如果摄像头未启动,可以通过以下步骤启动:- 在浏览器地址栏输入:
http://10.192.1.2:8080进入机器人管理页面。 - 导航到“机器人信息”页面。
- 点击“打开摄像头”以启动D435i摄像头。
- 在浏览器地址栏输入:
-
配置ROS环境以获取相机数据。
为了使您的电脑通过ROS接收到机器人的相机数据,您需要设置ROS的环境变量:
- 设置 ROS_MASTER_URI 和 ROS_IP,以确保您的电脑能够正确连接到机器人的ROS主节点:您可以将下述命令添加到您电脑的 .bashrc 文件中,这样每次启动终端时都会自动应用设置。
export ROS_MASTER_URI=http://10.192.1.2:11311
export ROS_IP=10.192.1.200
- 通过ROS查看D435i相机发布的数据主题:此命令将列出所有相机发布的主题,您可以订阅感兴趣的主题来查看数据。
rostopic list
- 使用rqt或rviz可视化相机数据
可以使用 rqt_image_view 或 rviz 来可视化D435i相机的图像和深度数据:- 安装 rqt_image_view 工具:
sudo apt install ros-<ros版本>-rqt-image-view
- 然后启动该工具并选择相机主题查看数据:
rqt_image_view
- 使用 rviz 可视化工具查看深度数据:
rviz
在rviz中添加相应的 Image 和 PointCloud2 类型显示器,选择相机相关主题以进行可视化。
10. 机器人软件升级
我们通过浏览器进入机器人管理页面,选择本地提前下载好的机器人软件版本进行升级。具体步骤如下:
- 请选择并连接您机器人的 Wi-Fi 热点,密码为:
12345678
- 访问管理页面:
- 在浏览器地址栏输入:http://10.192.1.2:8080 进入机器人管理页面。
- 选择和升级软件:
- 依次选择“版本管理 -> 选择文件 -> 升级”。
- 升级完成后,机器人主控电脑将自动重启。


