# TRON1 SDK开发指南

> 来源: https://www.limxdynamics.com/zh/documents/799585387524788224
> 发布时间: 2026-01-15
> 更新时间: 2026-03-05
> 标签: TRON 1 EDU 版

| 版本  | 修订日期  | 修改内容  | 备注  |
| --- | --- | --- | --- |
| 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 训练、控制算法以及仿真器程序时，您将能获取到正确的机器人型号信息。
```bash
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命令，安装开发环境所依赖的库：

```Bash
```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”的目录：
```bash
mkdir -p ~/limx_ws/src
```
- 下载运动控制开发接口：
```bash
cd ~/limx_ws/src
git clone https://github.com/limxdynamics/limxsdk-lowlevel.git
```
- 下载Gazebo仿真器：
```bash
cd ~/limx_ws/src
git clone https://github.com/limxdynamics/tron1-gazebo-ros.git
```
- 下载机器人模型描述文件
```bash
cd ~/limx_ws/src
git clone https://github.com/limxdynamics/robot-description.git
```
- 下载可视化调试工具
```bash
cd ~/limx_ws/src
git clone https://github.com/limxdynamics/robot-visualization.git
```
- 编译工程：
```bash
# 如您安装了Conda，请临时禁用 Conda 环境
# 因为 Conda 会干扰 ROS 的运行环境设置
conda deactivate

cd ~/limx_ws
catkin_make install
```

## 3.3 仿真调试
- 设置机器人型号：请参考“查看/设置机器人型号”章节，查看您的机器人型号。如果尚未设置，请按照以下步骤进行设置。
  - 通过 Shell 命令 `tree -L 1 src/robot-description/pointfoot` 列出可用的机器人类型：
```
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
```
- 运行仿真器：
```bash
# 如您安装了Conda，请临时禁用 Conda 环境
# 因为 Conda 会干扰 ROS 的运行环境设置
conda deactivate

cd ~/limx_ws
source install/setup.bash
roslaunch pointfoot_gazebo empty_world.launch
```
- 运行控制例程，确保仿真器中机器人有运动，说明仿真环境搭建完成：
```bash
# 如您安装了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` 列出可用的机器人类型：
```
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地址运行例程，实现实机部署（在进行实机部署运行时，**确保机器人吊装非常重要**）：
```bash
# 如您安装了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](https://github.com/limxdynamics/robot-visualization)

您可以轻松获取并使用这些工具。这些工具的使用简单直观，有助于提高开发效率和代码调试的便利性。

按照以下步骤，创建一个可视化工具工作空间：

  - 打开一个Bash终端。
  - 创建一个新目录来存放工作空间。例如，可以在用户的主目录下创建一个名为“limx_ws”的目录：

```Bash
mkdir -p ~/limx_ws/src
```
  - 下载机器人模型描述文件

```Bash
cd ~/limx_ws/src
git clone https://github.com/limxdynamics/robot-description.git
```

  - 下载可视化工具

```Bash
cd ~/limx_ws/src
git clone https://github.com/limxdynamics/robot-visualization.git
```
  - 编译可视化工具

```Bash
   # 如您安装了Conda，请临时禁用 Conda 环境
# 因为 Conda 会干扰 ROS 的运行环境设置
conda deactivate

cd ~/limx_ws
catkin_make install
```

### 3.5.1 Plotjuggler的使用

PlotJuggler 是一个功能强大的数据可视化工具，为用户提供直观的界面，方便加载、显示和分析各种类型的数据。用户可以通过图表、曲线和图形来展示数据，从而更好地理解数据之间的关系和趋势。PlotJuggler 不仅支持实时数据的可视化，还可以加载和处理大量的历史数据。官网地址：https://plotjuggler.io

  - 仿真运行：运行Plotjugler的方法如下

```Bash
# 如您安装了Conda，请临时禁用 Conda 环境
# 因为 Conda 会干扰 ROS 的运行环境设置
conda deactivate

source install/setup.bash
roslaunch robot_visualization pointfoot_plot_sim.launch
```
  - 真机运行：运行Plotjugler的方法如下
    - 打开pointfoot_plot_hw.launch文件，修改机器人的 IP 地址为实际机器人地址：
    - ![](https://limx-ows.oss-cn-beijing.aliyuncs.com/uploads/image/2026/01/20/1768880425917_image.png)
    - 然后重新编译工具，执行以下命令：
  ```Bash
# 如您安装了Conda，请临时禁用 Conda 环境
# 因为 Conda 会干扰 ROS 的运行环境设置
conda deactivate

cd ~/limx_ws
catkin_make install
```
   - 编译完成后，可以通过以下 Shell 命令运行 PlotJuggler 工具：

```Bash
- # 如您安装了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 |机器人状态实时数据 |

![](https://limx-ows.oss-cn-beijing.aliyuncs.com/uploads/image/2026/01/20/1768880698493_image.png)

### 3.5.2 RViz的使用

在实际机器部署中，除了使用 PlotJuggler 进行数据可视化外，还可以利用 RViz 工具来实时查看机器人的运行情况。RViz 是一款强大的3D可视化工具，可用于呈现机器人的传感器数据、运动状态和环境感知。通过 RViz，用户可以实时观察 **机器人的运动轨迹、传感器数据的变化情况**，帮助调试和监控机器人的运行状态。在部署过程中，可以将 RViz 配置为订阅机器人的传感器数据话题，并显示在3D场景中，以便实时监测机器人的行为并进行必要的调整和优化。按以下步骤运行RViz：
  - 打开pointfoot_rviz_hw.launch文件，修改机器人的 IP 地址为实际机器人地址：
  - ![](https://limx-ows.oss-cn-beijing.aliyuncs.com/uploads/image/2026/01/20/1768889586850_image.png)
  - 然后重新编译工具，执行以下命令：
```Bash
# 如您安装了Conda，请临时禁用 Conda 环境
# 因为 Conda 会干扰 ROS 的运行环境设置
conda deactivate

catkin_make install
```

  - 编译完成后，可以通过以下 Shell 命令运行 RViz：

```Bash
# 如您安装了Conda，请临时禁用 Conda 环境
# 因为 Conda 会干扰 ROS 的运行环境设置
conda deactivate

source install/setup.bash
roslaunch robot_visualization pointfoot_rviz_hw.launch
```
![](https://limx-ows.oss-cn-beijing.aliyuncs.com/uploads/image/2026/01/20/1768889658552_image.png)

## 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加载。
```bash
rosbag reindex your_file.bag.active
mv your_file.bag.active your_file.bag
```
- **可视化查看：**通过Shell命令`rosrun plotjuggler plotjuggler -n`启动PlotJuggler可视化工具。如下图所示加载数据包并分析数据。

### 3.6.2 日志及诊断埋点数据

如下图所示分别为日志和埋点结构化数据。在需要时可以用于故障排查和性能优化。
![](https://limx-ows.oss-cn-beijing.aliyuncs.com/uploads/image/2026/01/21/1768959794541_image.png)

## 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` 列出可用的机器人类型：
```
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` 列出可用的机器人类型：
```
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`为例，按下面方法解压并安装到指定目录：（注意下方软件包名称，请替换为实际的软件包名称）
```shell
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仿真器。
```shell
# 如您安装了Conda，请临时禁用 Conda 环境
# 因为 Conda 会干扰 ROS 的运行环境设置
conda deactivate

cd ~/limx_ws
source install/setup.bash
roslaunch pointfoot_gazebo empty_world.launch
```
- 打开一个终端，运行机器人算法软件。如机器人摔倒状态，通过虚拟遥控器组合按键`L2 + Y`恢复。请根据机器人类型选择对应的启动命令：
  - WF_TRON1A 启动命令：
```shell
# 为仿真需要，设置一个假SN
setRobotSN WF_TRON1A_001

# 启动算法软件
mroslaunch ${MROS_ETC_PATH}/tron1_controllers/tron1a_wheelfoot_controllers_sim.launch
```
  - PF_TRON1A 启动命令：
```shell
# 为仿真需要，设置一个假SN
setRobotSN PF_TRON1A_001

# 启动算法软件
mroslaunch ${MROS_ETC_PATH}/tron1_controllers/tron1a_pointfoot_controllers_sim.launch
```
  - SF_TRON1A 启动命令：
```shell
# 为仿真需要，设置一个假SN
setRobotSN SF_TRON1A_001

# 启动算法软件
mroslaunch ${MROS_ETC_PATH}/tron1_controllers/tron1a_solefoot_controllers_sim.launch
```
- 打开一个终端，下载并运行虚拟遥控器
```bash
# 下载虚拟遥控器
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 类只有一个实例存在于程序中  |
| 代码示例  |   |

```cpp
#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。  |
| 备注  | 无  |
| 代码示例  |   |

```cpp
#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个  |
| 代码示例  |   |

```cpp
#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 数据结构原型如下：

```cpp
/**
 * @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;
```

代码示例

```cpp
#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 数据结构原型如下：

```cpp
/**
 * @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

代码示例

```cpp
#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 对象参数如下。![](https://limx-ows.oss-cn-beijing.aliyuncs.com/uploads/image/2026/01/20/1768889850764_image.png)
   |
| 返回值  | 无  |

备注

cmd：表示所需机器人命令的 RobotCmd 对象。

```language

命令数据数组对应的电机顺序如下：
- 点足 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 数据结构原型如下：

```cpp
  /**
   * @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;
```

代码示例

```cpp
#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 数据结构原型如下：
```cpp
/**
 * @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;
```

- 遥控器摇杆映射
```bash
| 摇杆名          | 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        |
|----------------|----------------|

```

- 适配您自己的按键逻辑时，建议不要和系统预留按键功能冲突了。开发者模式下系统预留按键功能：

```bash

| 按键名          | 功能说明
|----------------|-------------------------------
| L1 + R1        | 机器人校零
|----------------|-------------------------------
| R1 + Left      | 打开开发者模式
|----------------|-------------------------------
| R1 + Right     | 打开运动控制模式
|----------------|-------------------------------
| 左 + 右摇杆按键  | 机器人紧急停止
|----------------|-------------------------------
| 右摇杆按键       | 解除机器人紧急停止
|----------------|-------------------------------

```

代码示例

```cpp
#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 数据结构原型如下：
```cpp
/**
 * @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;
```

- 常见诊断数据
```bash

| 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   | - 错误：机器人校准零失败，包含失败原因
|-------------|--------|------|----------------------------------

```

代码示例

```cpp
#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 枚举定义：

```cpp
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    // 白光闪烁（快速闪）
};
```

代码示例

```cpp
#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运动控制算法开发接口](https://github.com/limxdynamics/pointfoot-sdk-lowlevel/tree/master/python3)，**使得不熟悉C++编程语言的开发者能够使用Python进行运动控制算法的开发**。

Python语言易于学习，具有简洁清晰的语法和丰富的第三方库，使开发者能够更快速地上手并迅速实现算法。通过Python接口，开发者可以利用Python的动态特性进行快速原型设计和实验验证，加速算法的迭代和优化过程。同时，Python的跨平台性和强大的生态系统支持，使得运动算法能够更广泛地应用于不同平台和环境。

此外，RL（强化学习）模型的快速部署到仿真和真机环境中也得益于Python的灵活性，开发者可以使用Python轻松地将RL模型集成到各种仿真平台和真实硬件中，实现快速迭代和验证算法的性能。

### 4.2.2 安装运动控制开发库
- Linux x86_64 环境
```bash
git clone https://github.com/limxdynamics/limxsdk-lowlevel.git
pip install limxsdk-lowlevel/python3/amd64/limxsdk-*-py3-none-any.whl
```
- Linux aarch64 环境
```bash
git clone https://github.com/limxdynamics/limxsdk-lowlevel.git
pip install limxsdk-lowlevel/python3/aarch64/limxsdk-*-py3-none-any.whl
```
- Windows 环境
```bash
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。  |
| 返回值  | 无  |
| 备注  | 无  |

代码示例

```python
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  |
| 备注  | 无  |

代码示例

```python
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个  |
| 代码示例  |   |

```python
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 数据结构原型如下：

```python
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 四元数数据，表示在三维空间中的方向
```

代码示例

```python
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 对象。机器人状态数据数组对应的电机顺序如下![](https://limx-ows.oss-cn-beijing.aliyuncs.com/uploads/image/2026/01/20/1768890041457_image.png)|
| 返回值  | 成功：返回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 数据结构原型如下：

```python
import sys

class RobotState(object):
    __slots__ = ['stamp','tau','q','dq']
    def __init__(self):
        self.stamp = 0 # 时间戳，通常表示记录或生成这些数据的时间，以纳秒为单位
        self.tau = []  # 用于存储当前估计的输出扭矩（以牛顿米为单位）的向量
        self.q = []    # 用于存储当前角度（以弧度为单位）的向量
        self.dq = []   # 用于存储当前速度（以弧度每秒为单位）的向量
```

代码示例：

```python
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 对象，包含以下字段：![](https://limx-ows.oss-cn-beijing.aliyuncs.com/uploads/image/2026/01/20/1768890135453_image.png)|
| 返回值  | 成功：返回True 失败：返回False  |

备注

datatypes.RobotCmd 数据结构原型如下：

```python
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 = []   # 存储期望速度刚度的向量（以牛顿米每弧度每秒为单位）。
```

代码示例

```python
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 数据结构原型如下：
```python
import sys

class SensorJoy(object):
    __slots__ = ['stamp','axes','buttons']
    def __init__(self):
        self.stamp = 0     # 与传感器输入相关的时间戳，单位为纳秒。
        self.axes = []     # 表示操纵摇杆的值。
        self.buttons = []  # 表示操纵按钮状态的值。
```

- 遥控器摇杆映射
```bash
| 摇杆名          | Axes Index  |
|----------------|-------------|
| left_horizon   |      0      |
|----------------|-------------|
| left_vertival  |      1      |
|----------------|-------------|
| right_horizon  |      2      |
|----------------|-------------|
| right_vertival |      3      |
|----------------|-------------|

```

- 遥控器按键映射

```bash

|      按键名     | 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        |
|----------------|----------------|

```

- 适配您自己的按键逻辑时，建议不要和系统预留按键功能冲突了。开发者模式下系统预留按键功能：
```bash

| 按键名          | 功能说明
|----------------|-------------------------------
| L1 + R1        | 机器人校零
|----------------|-------------------------------
| R1 + Left      | 打开开发者模式
|----------------|-------------------------------
| R1 + Right     | 打开RL运动控制模式
|----------------|-------------------------------
| R1 + Up        | 打开MB运动控制模式
|----------------|-------------------------------
| 左 + 右摇杆按键  | 机器人紧急停止
|----------------|-------------------------------
| 右摇杆按键       | 解除机器人紧急停止
|----------------|-------------------------------

```

代码示例

```python
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 数据结构原型如下：
```python
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 = ''  # 与诊断值相关的详细消息。
```

- 常见诊断数据
```bash
| 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   | - 错误：机器人校准零失败，包含失败原因
|-------------|--------|------|----------------------------------

```

代码示例

```python
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 枚举定义：

```cpp

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  # 白光闪烁（快速闪）
```

代码示例

```python
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是一种实时通信协议，在机器人和用户端之间建立长连接，以便快速有效地传输控制信息和数据。如下图所示：

![](https://limx-ows.oss-cn-beijing.aliyuncs.com/uploads/image/2026/01/15/1768465354128_image.png)

## 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”子字段，用于存放请求指令的执行结果数据。如果有需要，还可以包含其他子字段，例如错误码、错误信息等用于描述操作结果的信息；
  - 示例如下：
```json
{
  "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`：存放消息数据内容。可以根据具体需求包含多个子字段，以存放请求指令所需的数据内容；
  - 示例如下：
```json
{
  "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的响应窗口查看服务器返回的数据，并检查是否符合预期结果。
  - ![](https://limx-ows.oss-cn-beijing.aliyuncs.com/uploads/image/2026/01/21/1768960479586_image.png)

## 5.5 协议接口定义

该机器人接口设计遵循与遥控器操控一致的流程和状态流转，确保调用顺序、响应时序及状态过渡与遥控器控制逻辑严格对齐。用户通过接口调用可获得如同使用遥控器的直观体验，同时支持遥控器与接口间的无缝切换，实现统一、稳定的机器人操控效果。

### 5.5.1 蹲起状态

#### 5.5.1.1 请求：request_stand_mode
```json
{
  "accid": "PF_TRON1A_075",  // 机器人序列号，注意修为您机器人的序列号；
  "title": "request_stand_mode",
  "timestamp": 1672373633989,
  "guid": "746d937cd8094f6a98c9577aaf213d98",
  "data": {
  }
}
```

#### 5.5.1.2 响应：response_stand_mode
```json
{
  "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
机器人站起过程失败或完成后，主动推送此消息。
```json
{
  "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
```json
{
  "accid": "PF_TRON1A_075", // 机器人序列号，注意修为您机器人的序列号；
  "title": "request_walk_mode",
  "timestamp": 1672373633989,
  "guid": "746d937cd8094f6a98c9577aaf213d98",
  "data": {
  }
}
```

#### 5.2.2.2 响应：response_walk_mode
```json
{
  "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
机器人站起过程失败或完成后，主动推送此消息。
```json
{
  "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及以上发送指令。
- 点足和掌足模式下按如下规则：
```json
{
  "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]
  }
}
```
- 轮足模式下按如下规则：
```json
{
  "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
机器人行走失败时，主动推送此消息。
```json
{
  "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
```json
{
  "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
```json
{
  "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
```json
{
  "accid": "PF_TRON1A_075", // 机器人序列号，注意修为您机器人的序列号；
  "title": "request_sitdown",
  "timestamp": 1672373633989,
  "guid": "746d937cd8094f6a98c9577aaf213d98",
  "data": {}
}
```

#### 5.5.5.2 响应：response_sitdown
```json
{
  "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
机器人蹲下过程失败或完成后，主动推送此消息。
```json
{
  "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 型号的双轮足机器人。
```json
{
  "accid": "PF_TRON1A_075", // 机器人序列号，注意修为您机器人的序列号；
  "title": "request_stair_mode",
  "timestamp": 1672373633989,
  "guid": "746d937cd8094f6a98c9577aaf213d98",
  "data": {
      "enable": true  // true: 开启楼梯模式, false: 关闭楼梯模式
  }
}
```

#### 5.5.6.2 响应：response_stair_mode
```json
{
  "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 型号的双足机器人。
```json
{
  "accid": "PF_TRON1A_075", // 机器人序列号，注意修为您机器人的序列号；
  "title": "request_marktime_mode",
  "timestamp": 1672373633989,
  "guid": "746d937cd8094f6a98c9577aaf213d98",
  "data": {
      "enable": true  // true: 开启踏步模式, false: 关闭踏步模式
  }
}
```

#### 5.5.7.2 响应：response_marktime_mode
```json
{
  "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
```json
{
  "accid": "PF_TRON1A_075", // 机器人序列号，注意修为您机器人的序列号；
  "title": "request_emgy_stop",
  "timestamp": 1672373633989,
  "guid": "746d937cd8094f6a98c9577aaf213d98",
  "data": {}
}
```

#### 5.5.8.2 响应：response_emgy_stop

```json
{
  "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
该功能用于开启里程计推送，开启后系统将主动推送里程计数据（注意：轮足机器人才有里程计）。
```json
{
  "accid": "WF_TRON1A_075", // 机器人序列号，注意修为您机器人的序列号；
  "title": "request_enable_odom",
  "timestamp": 1672373633989,
  "guid": "746d937cd8094f6a98c9577aaf213d98",
  "data": {
      "enable": true  // true: 开启里程计, false: 禁用里程计
  }
}
```

#### 5.5.9.2 响应：response_enable_imu
```json
{
  "accid": "WF_TRON1A_075",
  "title": "response_enable_odom",
  "timestamp": 1672373633989,
  "guid": "746d937cd8094f6a98c9577aaf213d98",
  "data": {
    "result": "success"  // success: 成功, fail_odom: 不支持
  }
}
```

#### 5.5.9.3 消息推送：notify_odom
开启里程计后，系统将主动推送包含里程计数据的消息。
```json
{
  "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数据。
```json
{
  "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
```json
{
  "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状态的消息。
```json
{
  "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
当机器人摔倒后，可以调用此接口让机器人自动爬起来并恢复到行走模式。
```json
{
  "accid": "PF_TRON1A_075", // 机器人序列号，注意修为您机器人的序列号；
  "title": "request_recover",
  "timestamp": 1672373633989,
  "guid": "746d937cd8094f6a98c9577aaf213d98",
  "data": {}
}
```

#### 5.5.11.2 响应：response_recover
```json
{
  "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
该接口用于设置机器人的灯光效果。用户可以通过此接口向机器人发送特定的灯光效果指令，机器人会根据指令调整其灯光显示。
```json
{
  "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
```json
{
  "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_info
- `timestamp`：消息发出时间戳，单位为毫秒
- `guid`：消息的guid值，唯一标识这条消息
- `data`：存放消息内容，示例如下：

```json
{
  "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：存放消息内容
  - 示例如下：

```json
 {
  "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依赖：
```bash
sudo apt-get install libboost-all-dev libwebsocketpp-dev nlohmann-json3-dev
```

- 编译代码
```bash
g++ -std=c++11 -o websocket_client websocket_client.cpp -lssl -lcrypto -lboost_system -lpthread
```

- 运行程序
```bash
./websocket_client
```

- websocket_client.cpp 实现
```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系统为例，安装下面依赖
```python
sudo apt install python3-dev python3-pip
sudo pip3 install websocket-client
```

- 运行脚本
```python
python3 websocket_client.py
```

- websocket_client.py 实现
```python
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 实现：
```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 实现：
```javascript
// 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`包：
```javascript
go get github.com/gorilla/websocket
```

- Go实现的代码如下：
```javascript
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 示例实现
```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`，您可以运行以下命令安装依赖：
```powershell
vcpkg install websocketpp boost nlohmann-json
```

- 代码实现
```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") {
            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# 示例实现
```csharp
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 环境搭建
![](https://limx-ows.oss-cn-beijing.aliyuncs.com/uploads/image/2026/02/25/1772015112045_image.png)

## 6.1 推荐硬件配置
| **硬件组件 **  | **推荐规格 **  | **  说明**  |
| --- | --- | --- |
| **GPU**  | GeForce RTX 3080 (12 GB) 或更高   | 12  GB GDDR6 显存或更高，适合深度学习和强化学习任务。 - [GeForce RTX 30系列](https://www.nvidia.com/en-us/geforce/graphics-cards/30-series/)：  - [GeForce RTX 40系列](https://www.nvidia.com/en-us/geforce/graphics-cards/40-series/)[：](https://www.nvidia.com/en-us/geforce/graphics-cards/40-series/)   |
| **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环境
```bash
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 驱动是否安装成功
```yaml
nvidia-smi
```
如果显示了 NVIDIA 驱动版本和 GPU 信息，说明驱动已成功安装。

- 验证Isaac Gym是否安装成功
运行Isaac Gym 的示例，它可以帮助确认 Isaac Gym 是否正确安装并配置。
```bash
conda activate pointfoot_legged_gym
cd ~/limx_rl/isaacgym/python/examples
python 1080_balls_of_solitude.py
```

- 验证pointfoot-legged-gym是否安装成功
当您安装完RL环境后，在 `~/limx_rl`目录下包含如下所示的内容：

```bash
cd ~/limx_rl
.
├── isaacgym
├── pointfoot-legged-gym
└── rsl_rl
```

## 6.3 分步安装RL环境

### 6.3.1 安装显卡驱动
- 检查当前安装的 NVIDIA 驱动
如果系统中已经安装了 NVIDIA 驱动，可以通过以下命令检查：
```bash
nvidia-smi
```
如果显示了 NVIDIA 驱动版本和 GPU 信息，说明驱动已经安装。如果没有安装，请继续以下步骤。
- 添加官方的 NVIDIA 驱动 PPA（Personal Package Archive）：
```bash
sudo add-apt-repository ppa:graphics-drivers/ppa
sudo apt update
```
- 使用以下命令列出可用的 NVIDIA 驱动版本：
```bash
ubuntu-drivers devices
```
- 安装 NVIDIA 驱动
选择推荐的驱动版本进行安装，例如：
将 `XXX` 替换为推荐的驱动版本号，例如 `nvidia-driver-535`。
- 禁用 Nouveau 驱动（如果必要）：某些情况下，您可能需要禁用默认的 Nouveau 驱动：
```bash
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
```
- 重启系统：完成驱动安装后，重启系统使驱动生效：
```bash
sudo reboot
```
- 验证驱动安装：重启后，使用以下命令验证 NVIDIA 驱动是否安装成功：

```bash
nvidia-smi
```
如果显示了 NVIDIA 驱动版本和 GPU 信息，说明驱动已成功安装。

### 6.3.2 Conda 环境配置

Conda 是一个跨平台的开源包和环境管理系统，它能够快速创建虚拟环境，并在其中进行包的安装、运行和更新操作，从而有效解决不同软件包对 Python 和依赖库版本的要求问题。请按照下列步骤操作下载并安装Conda：
  - 下载 Anaconda 安装脚本：
    使用 wget 下载 Anaconda 的安装脚本（请根据需要选择适合的版本）：

```bash
cd && wget  https://repo.anaconda.com/archive/Anaconda3-2024.06-1-Linux-x86_64.sh
```
  - 运行安装脚本：
    执行下载的安装脚本：

```bash
bash Anaconda3-2024.06-1-Linux-x86_64.sh
```

   - 安装过程中：
    - 按 Enter 键查看许可协议。
    - 输入 yes 接受许可协议。
    - 选择默认安装路径（通常为 ~/anaconda3），或根据需要自定义路径。
    - 当脚本询问是否初始化 Conda 时，选择 yes 以自动配置环境。
  - 初始化 Conda（如果安装过程中未自动配置）：

```bash
~/anaconda3/bin/conda init
```
  - 使 .bashrc 配置立即生效：

```bash
source ~/.bashrc
```
   - 配置 Conda 环境用于 RL 训练
    创建一个专门用于 RL 训练的 Conda 环境（例如，pointfoot_legged_gym），并指定 Python 版本：

```bash
conda create --name pointfoot_legged_gym python=3.8
```
  - 激活刚刚创建的环境：

```bash
conda activate pointfoot_legged_gym
```
  - 请按照以下步骤安装 PyTorch 库：
    1.访问 yPyTorch安装页面。
    2. 选择以下选项：
      - 操作系统：Linux
      - 包管理器：Conda
      - 语言：Python
      - CUDA 版本：12.1
      - ![](https://limx-ows.oss-cn-beijing.aliyuncs.com/uploads/image/2026/01/21/1768961860695_image.png)

- 3.生成安装命令并运行以下命令进行安装：

```bash
conda install pytorch torchvision torchaudio pytorch-cuda=12.1 -c pytorch -c nvidia
```

### 6.3.3 安装Isaac Gym

#### 下载Isaac Gym
- **下载页面**：前往 [Isaac Gym 下载页面](https://developer.nvidia.com/isaac-gym/download)。
- **接受条款和条件**：您可能需要接受一些条款和条件。
- **下载**：选择合适的版本下载 Isaac Gym 的安装包。

#### 安装 Isaac Gym
- 安装setuptools：
setuptools 是一个用于 Python 包管理和分发的库，它帮助打包 Python 项目，使其易于安装和分发，并处理项目的依赖项。通过指定版本 `59.5.0`，可以确保与其他依赖项的兼容性和稳定性。
```bash
pip install setuptools==59.5.0
```
- 安装TensorBoard
TensorBoard 是一个用于可视化 TensorFlow 图表、指标和其他相关数据的工具。为了确保兼容性和稳定性，我们指定安装版本为 `2.12.0`。
```bash
pip install tensorboard==2.12.0
```
- 激活pointfoot_legged_gym的conda环境
```bash
conda activate pointfoot_legged_gym
```
- 创建一个用于存放训练代码的路径（如：`limx_rl`）
```bash
mkdir -p ~/limx_rl
```
- 解压文件：将上一步下载的安装压缩文件解压到 `limx_rl` 目录。注意：请将 `/path/to/IsaacGym_Preview_4_Package.tar.gz` 替换为安装包的实际路径。
```bash
tar -xzvf /path/to/IsaacGym_Preview_4_Package.tar.gz -C ~/limx_rl/
```
- 安装 Isaac Gym并配置：
```bash
cd ~/limx_rl/isaacgym/python
pip install -e .
```
- 解决NumPy兼容性问题：
在较新的版本的 NumPy 中，`np.float` 被弃用，使用时会出现警告或错误。直接使用 Python 内置的 `float` 类型可以避免这些问题。
```bash
cd ~/limx_rl/isaacgym/python
sed -i 's/np.float/float/' isaacgym/torch_utils.py
```
- 验证安装
运行Isaac Gym 的示例，它可以帮助确认 Isaac Gym 是否正确安装并配置。
```bash
cd ~/limx_rl/isaacgym/python/examples
python 1080_balls_of_solitude.py
```

### 6.3.4 安装pointfoot-legged-gym
- 激活pointfoot_legged_gym的conda环境
```bash
conda activate pointfoot_legged_gym
```
- 安装pointfoot-legged-gym
```bash
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`目录下包含如下所示的内容：

```bash
cd ~/limx_rl
.
├── isaacgym
└── pointfoot-legged-gym
```

##  6.4 部署RL Docker 容器环境

### 6.4.1 安装Docker服务
```bash
#### 更新包索引并安装依赖
# 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 工具
```bash
#### 设置 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环境安装是否正常
```bash
#### 运行带有 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 等环境，拉取镜像后即可直接使用无需再配置相关环境
```bash
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 列出可用的机器人类型：

```plain text
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（请根据实际机器人类型进行替换）为例，设置机器人型号类型：

```plain text
echo 'export ROBOT_TYPE=PF_TRON1A' >> ~/.bashrc && source ~/.bashrc
```

- 激活pointfoot_legged_gym的conda环境

```bash
conda activate pointfoot_legged_gym
```
- 进入训练代码所在路径，开始训练
     参数选项：
      - --task=pointfoot_flat：指定任务或环境类型为 pointfoot_flat。
    - --headless：以无头模式运行，即不显示任何图形界面。通常用于在没有显示器的服务器上运行，或者在需要高效计算而不需要图形渲染的情况下使用。
    - --num_envs 8192：指定要创建的环境数量为 8192。
    **命令 1:** 无头模式运行

```bash
cd ~/limx_rl/pointfoot-legged-gym
python legged_gym/scripts/train.py --task=pointfoot_flat --headless
```
  - **命令 2:** 图形模式下运行

```bash
cd ~/limx_rl/pointfoot-legged-gym
python legged_gym/scripts/train.py --task=pointfoot_flat
```
![](https://limx-ows.oss-cn-beijing.aliyuncs.com/uploads/image/2026/01/21/1768962532751_image.png)

## 7.2 继续训练

如果您的训练中途终止，可以指定一个检查点文件的位置，接着继续训练。
  - 首先，您需要激活pointfoot_legged_gym的conda环境

```bash
conda activate pointfoot_legged_gym
```
  - 命令 1: 无头模式下继续训练

```bash
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: 图形模式下继续训练

```bash
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 目录查看子目录或文件，这些子目录或文件通常会以训练运行的标识符命名。
      - 示例路径：

```bash
ls -l ~/limx_rl/pointfoot-legged-gym/logs/pointfoot_flat
```
您会看到以机器人类型命名的目录，训练结果会按照机器人类型分别存储于对应目录下，以PF_TRON1A为例，进入后可以看到类似于 Apr16_16-51-06_ 这样的目录，Apr16_16-51-06_ 就是 --load_run 参数的值。
![](https://limx-ows.oss-cn-beijing.aliyuncs.com/uploads/image/2026/01/21/1768962695874_image.png)

  - --checkpoint:
      说明：指定要加载的检查点文件。检查点文件保存了模型的中间状态，可以用于恢复训练或进行推断。
      获取方式：
      - 查看 logs 目录：在 logs 目录下的相应 --load_run 目录中，通常会有保存检查点的文件。这些文件通常以 .pt 或类似扩展名存在，文件名可能包含训练的轮次或时间戳。
      - 示例路径：

```bash
ls -l ~/limx_rl/pointfoot-legged-gym/logs/pointfoot_flat/PF_TRON1A/Apr16_16-51-06_
```
- 会看到类似于model_200.pt 的文件，则200就是 --checkpoint 参数的值。
- ![](https://limx-ows.oss-cn-beijing.aliyuncs.com/uploads/image/2026/01/21/1768962751188_image.png)

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

![](https://limx-ows.oss-cn-beijing.aliyuncs.com/uploads/image/2026/01/21/1768962871077_image.png)

  - --checkpoint:
说明：指定要加载的检查点文件。检查点文件保存了模型的中间状态，可以用于恢复训练或进行推断。
获取方式：
    - 查看 `logs` 目录：在 `logs` 目录下的相应 `--load_run` 目录中，通常会有保存检查点的文件。这些文件通常以 `.pt` 或类似扩展名存在，文件名可能包含训练的轮次或时间戳。
    - 示例路径：
```bash
ls -l ~/limx_rl/pointfoot-legged-gym/logs/pointfoot_flat/PF_TRON1A/Apr16_16-51-06_
```
会看到类似于`model_200.pt` 的文件，则`200`就是 `--checkpoint` 参数的值。
  - 使用示例
```bash
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` 目录查看子目录或文件，这些子目录或文件通常会以训练运行的标识符命名。
      - 示例路径：
```bash
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` 或类似扩展名存在，文件名可能包含训练的轮次或时间戳。
      - 示例路径：
```bash
ls -l ~/limx_rl/pointfoot-legged-gym/logs/pointfoot_flat/PF_TRON1A/Apr16_16-51-06_
```
会看到类似于`model_200.pt` 的文件，则`200`就是 `--checkpoint` 参数的值。
![](https://limx-ows.oss-cn-beijing.aliyuncs.com/uploads/image/2026/01/21/1768962911753_image.png)

  - 使用示例
```bash
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文件保存在目录：

```go
ls -l ~/limx_rl/pointfoot-legged-gym/logs/pointfoot_flat/PF_TRON1A/exported/policies
```
![](https://limx-ows.oss-cn-beijing.aliyuncs.com/uploads/image/2026/01/21/1768962960201_image.png)

## 7.5 Docker环境下启动训练
- 打开终端`Ctrl+Alt+T`
- 拉取RL Docker容器环境基础镜像
```bash
sudo docker pull crpi-yznuflhnzo12tr44.cn-shanghai.personal.cr.aliyuncs.com/limx-rl/base:v10
```
- 运行RL 容器环境
```bash
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: 无头模式运行

```bash
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 版本：
```bash
conda create --name pointfoot_deploy python=3.8
```
- 激活刚刚创建的环境：
```bash
conda activate pointfoot_deploy
```

### 8.1.2 创建工作空间
可以按照以下步骤，创建一个RL部署开发工作空间：
- 打开一个Bash终端。
- 创建一个新目录来存放工作空间。例如，可以在用户的主目录下创建一个名为“limx_ws”的目录：
```bash
mkdir -p ~/limx_ws
```
- 下载 MuJoCo 仿真器
```bash
cd ~/limx_ws
git clone --recurse https://github.com/limxdynamics/tron1-mujoco-sim.git
```
- 下载部署实现
```bash
cd ~/limx_ws
git clone --recurse https://github.com/limxdynamics/tron1-rl-deploy-python.git
```
- 安装运动控制开发库（如果尚未安装）：
  - Linux x86_64 环境
```bash
# 激活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 环境
```bash
# 激活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` 列出可用的机器人类型：
```
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，如下所示。请根据您的训练结果更新替换。

```bash
~/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 仿真器。
```python
# 激活pointfoot_deploy的conda环境
conda activate pointfoot_deploy

# 运行仿真器
cd ~/limx_ws
python tron1-mujoco-sim/simulator.py
```
- 打开一个Bash终端，运行控制算法。
如果您的机器人处于摔倒状态，请在启动运动控制后，单击 MuJoCo 仿真器左侧菜单栏的`“Reset”`按钮以重置机器人。此时，您将看到机器人恢复并开始行走。
```bash
# 激活pointfoot_deploy的conda环境
conda activate pointfoot_deploy

# 运行控制算法
python tron1-rl-deploy-python/main.py
```
- 打开一个Bash终端，运行虚拟遥控器：仿真的过程中，您可以使用虚拟遥控器来控制机器人运动。左摇杆：控制前进/后退/左转/右转运动；右遥控：可控制机器人左右横向运动。
```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命令启动控制算法（在进行实机部署运行时，确保机器人吊装非常重要）：
```bash
# 激活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。

```bash
cd ~/limx_ws
scp -r tron1-rl-deploy-python guest@10.192.1.2:/home/guest
```
  - 安装运动控制开发库（如果尚未安装）：
  -
```bash
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。

```bash
 ssh guest@10.192.1.2
```

   - 修改自启动脚本：
      - 打开自启动脚本 /home/guest/autolaunch/autolaunch.sh 进行编辑：

```bash
busybox vi /home/guest/autolaunch/autolaunch.sh
```
   - 在脚本中找到启动 main.py 的命令：确保 python3 /home/guest/tron1-rl-deploy-python/main.py 10.192.1.2 这一行未被注释（没有 # 注释符号）。编辑完成后，保存并退出编辑器。

```bash
#!/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

```
  - 重启机器人电脑。修改完成后，执行以下命令重启机器人：

```bash
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命令，安装开发环境所依赖的库：
```bash
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，请按下面步骤进行安装：

```bash
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”的目录：
```bash
mkdir -p ~/limx_ws/src
```
- 下载运动控制开发接口：
```bash
cd ~/limx_ws/src
git clone https://github.com/limxdynamics/limxsdk-lowlevel.git
```
- 下载Gazebo仿真器：
```bash
cd ~/limx_ws/src
git clone https://github.com/limxdynamics/tron1-gazebo-ros.git
```
- 下载机器人模型描述文件
```bash
cd ~/limx_ws/src
git clone https://github.com/limxdynamics/robot-description.git
```
- 下载可视化工具
```bash
cd ~/limx_ws/src
git clone https://github.com/limxdynamics/robot-visualization.git
```
- 下载RL部署源码：
```bash
cd ~/limx_ws/src
git clone https://github.com/limxdynamics/tron1-rl-deploy-ros.git
```
- 设置机器人型号：请参考“查看/设置机器人型号”章节，查看您的机器人型号。如果尚未设置，请按照以下步骤进行设置。
  - 通过 Shell 命令 `tree -L 1 src/robot-description/pointfoot` 列出可用的机器人类型：
```
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
```
- 编译工程：
```bash
# 如您安装了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，如下所示。请根据您的训练结果更新替换。

```bash
tree ~/limx_ws/src/tron1-rl-deploy-ros/robot_controllers/config/pointfoot/PF_TRON1A
.
├── params.yaml
└── policy
    ├── policy.onnx
    └── encoder.onnx

```

### 8.2.4 仿真调试
- 请进到您的工作空间，完成编译：
```bash
# 如您安装了Conda，请临时禁用 Conda 环境
# 因为 Conda 会干扰 ROS 的运行环境设置
conda deactivate

cd ~/limx_ws
catkin_make install
```
- 运行部署仿真：启动后，您将看到如下所示的界面，包括Gazebo仿真器和Robot Steering交互窗口。
  - 在Gazebo窗口中，您可以使用快捷键`Ctrl + Shift + R`复位机器人；
  - 您还可以通过Robot Steering交互窗口设置，发布主题为/cmd_vel，控制机器人的移动。
```bash
# 如您安装了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` 控制机器人不方便，可以使用虚拟遥控器来简化操作。以下是使用虚拟遥控器的具体步骤。
  - 下载并运行虚拟遥控器
```bash
# 下载虚拟遥控器
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`，如下图所示：

完成修改后进行编译：
```bash
# 如您安装了Conda，请临时禁用 Conda 环境
# 因为 Conda 会干扰 ROS 的运行环境设置
conda deactivate

cd ~/limx_ws
catkin_make install
```
- 打开开发者模式：机器人开机后，同时按下遥控器按键 `R1 + Left`，这时本体主机将会自动重启并切换机器人到开发者模式。在此模式下，用户可以开发自己的运动控制算法。模式设置掉电后不会失效，重新开机后仍然是开发者模式。遥控器切换工作模式的按键列表如下：

| **按键**  | **模式**  | **说明**  |
| --- | --- | --- |
| R1+Left  | 开发者模式（需授权）  | 用户使用运动控制开发接口开发自己的运动控制算法。  |
| R1+Right  | 遥控模式  | 运行预安装的控制算法，实现复杂地形的平稳行走，如：上楼、下楼、过坎等。  |

- 进行校零动作：机器人开机启动后，执行运控程序之前，请进行校零，使机器人各个关节回到初始位置。校零对应的遥控器按键为L1+R1。
- 实机部署运行。在Bash终端只需下面Shell命令启动控制算法（在进行实机部署运行时，确保机器人吊装非常重要）：
```bash
# 如您安装了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。

```bash
cd ~/limx_ws
scp -r install guest@10.192.1.2:/home/guest
```
  - 配置算法自动启动
    - SSH 登录机器人：使用 ssh 命令远程登录到机器人系统，密码为 123456。

```bash
ssh guest@10.192.1.2
```
  - 修改自启动脚本：
      - 打开自启动脚本 /home/guest/autolaunch/autolaunch.sh 进行编辑：

```bash
busybox vi /home/guest/autolaunch/autolaunch.sh
```
 - 在脚本中找到启动 roslaunch 的命令：确保 source /home/guest/install/setup.bash 和roslaunch robot_hw pointfoot_hw.launch 这两行未被注释（没有 # 注释符号）。编辑完成后，保存并退出编辑器。

```bash
#!/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 文件中，这样每次启动终端时都会自动应用设置。

```bash
export ROS_MASTER_URI=http://10.192.1.2:11311
export ROS_IP=10.192.1.200
```
  - 通过ROS查看D435i相机发布的数据主题：此命令将列出所有相机发布的主题，您可以订阅感兴趣的主题来查看数据。

```bash
rostopic list
```
  - 使用rqt或rviz可视化相机数据
    可以使用 rqt_image_view 或 rviz 来可视化D435i相机的图像和深度数据：
    - 安装 rqt_image_view 工具：

```bash
sudo apt install ros-<ros版本>-rqt-image-view
```
  - 然后启动该工具并选择相机主题查看数据：

```bash
rqt_image_view
```
   - 使用 rviz 可视化工具查看深度数据：

```bash
rviz
```
 在rviz中添加相应的 Image 和 PointCloud2 类型显示器，选择相机相关主题以进行可视化。

# 10. 机器人软件升级
我们通过浏览器进入机器人管理页面，选择本地提前下载好的机器人软件版本进行升级。具体步骤如下：
- 请选择并连接您机器人的 Wi-Fi 热点，密码为：`12345678`

1. 访问管理页面：
  - 在浏览器地址栏输入：[http://10.192.1.2:8080](http://10.192.1.2:8080/) 进入机器人管理页面。
2. 选择和升级软件：
  - 依次选择“版本管理 -> 选择文件 -> 升级”。
  - 升级完成后，机器人主控电脑将自动重启。
