ESC
输入关键词搜索文章标题和内容

Gazebo仿真与ros2_control:从虚拟环境到闭环控制

本文由 linuxROS 整理发布,首发于 linuxros.cn,转载请注明出处。

Gazebo仿真与ros2_control:从虚拟环境到闭环控制

> 基于Jazzy Jalisco LTS + Gazebo Harmonic,讲解仿真环境搭建、ros_gz桥接机制、自定义机器人SDF建模、ros2_control闭环控制框架和传感器插件集成。所有命令可直接运行验证。

一、Gazebo Harmonic概述

Gazebo Harmonic是Open Robotics推出的新一代仿真器,替代已退役的Gazebo Classic(Gazebo 11)。架构重构后,物理引擎可插拔、渲染管线升级、ROS2集成方式从直连改为桥接——不是换个版本号,是换了一套骨架。

Gazebo Classic vs Gazebo Harmonic

flowchart LR subgraph Classic["Gazebo Classic(已退役)"] direction TB C1["gazebo_ros_pkgs<br/>ROS直连集成"] C2["ODE单一引擎"] C3["OGRE 1.x渲染"] C4["URDF/Xacro为主"] C5["已于2025年退出"] C1 --> C2 --> C3 --> C4 --> C5 end Classic -.->|"架构升级"| Harmonic subgraph Harmonic["Gazebo Harmonic(当前推荐)"] direction TB H1["ros_gz桥接<br/>消息翻译"] H2["DART/Bullet/ODE<br/>可插拔"] H3["OGRE 2.x PBR"] H4["SDFormat原生"] H5["活跃维护"] H1 --> H2 --> H3 --> H4 --> H5 end style Classic fill:#FFEBEE,stroke:#D32F2F style Harmonic fill:#E8F5E9,stroke:#388E3C

核心差异

特性 Gazebo Classic Gazebo Harmonic
版本号 Gazebo 11 Gazebo 8(Harmonic)
模型格式 URDF/Xacro SDFormat(原生)
ROS2集成 gazebo_ros_pkgs ros_gz(桥接)
物理引擎 ODE DART(默认)/ Bullet / ODE
渲染引擎 OGRE 1.x OGRE 2.x(PBR材质)
推荐Ubuntu 22.04 24.04
维护状态 退役 活跃

二、环境安装与配置

安装Gazebo Harmonic

# Ubuntu 24.04 + ROS2 Jazzy

# 1. 安装Gazebo Harmonic
sudo apt update
sudo apt install gz-harmonic

# 2. 安装ros_gz桥接器
sudo apt install ros-jazzy-ros-gz-bridge
sudo apt install ros-jazzy-ros-gz-sim
sudo apt install ros-jazzy-ros-gz

# 3. 安装ros2_control相关包
sudo apt install ros-jazzy-ros2-control
sudo apt install ros-jazzy-ros2-controllers
sudo apt install ros-jazzy-gz-ros2-control

# 4. 验证安装
gz sim --version
# 输出: Gazebo Sim, version 8.x.x

验证ros_gz桥接

# 加载ROS2环境
source /opt/ros/jazzy/setup.bash

# 检查桥接包是否安装
dpkg -l | grep ros-jazzy-ros-gz
# 期望输出: ros-jazzy-ros-gz-bridge / ros-jazzy-ros-gz-sim

三、ros_gz桥接架构

ros_gz_bridge是ROS2和Gazebo之间的消息翻译器。ROS2用DDS通信,Gazebo用自己的Transport协议,两者不能直接对话——bridge在中间做双向翻译。

桥接架构图

flowchart TB subgraph ROS2["ROS2侧"] NODE["ROS2节点<br/>rclcpp/rclpy"] TF["TF2坐标变换"] CTRL["ros2_control<br/>控制器框架"] end subgraph BRIDGE["ros_gz桥接器"] GZ_BRIDGE["ros_gz_bridge<br/>消息双向转换"] PARAM["parameter_bridge<br/>命令行/YAML配置"] end subgraph GAZEBO["Gazebo Harmonic侧"] GZ_SIM["gz-sim仿真引擎"] PHYSICS["物理引擎<br/>DART"] SENSORS["传感器插件<br/>激光/相机/IMU"] end NODE <-->|"ROS2话题/服务"| GZ_BRIDGE TF <-->|"/tf"| GZ_BRIDGE CTRL <-->|"控制指令"| GZ_BRIDGE GZ_BRIDGE <-->|"Gazebo Transport"| GZ_SIM GZ_SIM --> PHYSICS GZ_SIM --> SENSORS style ROS2 fill:#E3F2FD,stroke:#1976D2 style BRIDGE fill:#FFF8E1,stroke:#F57C00 style GAZEBO fill:#E8F5E9,stroke:#388E3C

parameter_bridge命令行用法

parameter_bridge是ros_gz_bridge提供的命令行工具,用于快速建立单条话题的桥接。

# 语法: 话题名@ROS2消息类型@Gazebo消息类型
# 双向桥接(两个@)
ros2 run ros_gz_bridge parameter_bridge \
  /cmd_vel@geometry_msgs/msg/Twist@gz.msgs.Twist

# 单向桥接(Gazebo→ROS2,一个@)
ros2 run ros_gz_bridge parameter_bridge \
  /scan@sensor_msgs/msg/LaserScan[gz.msgs.LaserScan

# 单向桥接(ROS2→Gazebo,一个@)
ros2 run ros_gz_bridge parameter_bridge \
  /cmd_vel@geometry_msgs/msg/Twist]gz.msgs.Twist

> 注意:方括号方向表示数据流向。[ 表示Gazebo→ROS2,] 表示ROS2→Gazebo。双向用两个 @ 分隔。

四、TurtleBot3仿真实战

TurtleBot3是ROS2仿真的标准入门平台,ROBOTIS官方提供了完整的Gazebo Harmonic适配包。

安装TurtleBot3仿真包

# 安装TurtleBot3全部相关包
sudo apt install ros-jazzy-turtlebot3*

设置环境变量

# 设置TurtleBot3型号(burger/waffle/waffle_pi)
echo 'export TURTLEBOT3_MODEL=waffle' >> ~/.bashrc
source ~/.bashrc

启动仿真

# 终端1:启动Gazebo仿真世界
ros2 launch turtlebot3_gazebo turtlebot3_world.launch.py

# 终端2:启动键盘遥控
ros2 run turtlebot3_teleop teleop_keyboard

# 终端3:启动RViz2可视化
ros2 launch turtlebot3_gazebo turtlebot3 rviz.launch.py

桥接话题验证

# 查看Gazebo发布到ROS2的话题
ros2 topic list | grep -E "cmd_vel|scan|odom|camera"

# 期望输出:
# /cmd_vel
# /odom
# /scan
# /camera/image_raw

# 查看激光雷达数据
ros2 topic echo /scan --once

# 查看里程计
ros2 topic echo /odom --once

# 手动发送速度指令
ros2 topic pub /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.2}, angular: {z: 0.5}}"

五、自定义机器人仿真

SDFormat模型文件

Gazebo Harmonic原生使用SDFormat(Simulation Description Format)。相比URDF,SDF支持多模型、传感器插件、物理材质等URDF不具备的特性。

SDF示例:底盘+激光雷达+差速驱动

<?xml version="1.0"?>
<sdf version="1.8">
  <model name="diff_bot">
    <!-- 底盘连杆 -->
    <link name="chassis">
      <pose>0 0 0.1 0 0 0</pose>
      <inertial>
        <mass>5.0</mass>
        <inertia>
          <ixx>0.1</ixx><ixy>0</ixy><ixz>0</ixz>
          <iyy>0.1</iyy><iyz>0</iyz>
          <izz>0.1</izz>
        </inertia>
      </inertial>
      <visual name="visual">
        <geometry><box><size>0.4 0.3 0.2</size></box></geometry>
        <material><ambient>0 0 0.8 1</ambient></material>
      </visual>
      <collision name="collision">
        <geometry><box><size>0.4 0.3 0.2</size></box></geometry>
      </collision>
    </link>

    <!-- 左轮 -->
    <link name="left_wheel">
      <pose>-0.1 0.175 0.1 -1.5707 0 0</pose>
      <inertial>
        <mass>0.5</mass>
        <inertia>
          <ixx>0.001</ixx><ixy>0</ixy><ixz>0</ixz>
          <iyy>0.001</iyy><iyz>0</iyz>
          <izz>0.001</izz>
        </inertia>
      </inertial>
      <visual name="visual">
        <geometry><cylinder><radius>0.05</radius><length>0.04</length></cylinder></geometry>
        <material><ambient>0.5 0.5 0.5 1</ambient></material>
      </visual>
      <collision name="collision">
        <geometry><cylinder><radius>0.05</radius><length>0.04</length></cylinder></geometry>
      </collision>
    </link>

    <!-- 右轮 -->
    <link name="right_wheel">
      <pose>-0.1 -0.175 0.1 -1.5707 0 0</pose>
      <inertial>
        <mass>0.5</mass>
        <inertia>
          <ixx>0.001</ixx><ixy>0</ixy><ixz>0</ixz>
          <iyy>0.001</iyy><iyz>0</iyz>
          <izz>0.001</izz>
        </inertia>
      </inertial>
      <visual name="visual">
        <geometry><cylinder><radius>0.05</radius><length>0.04</length></cylinder></geometry>
        <material><ambient>0.5 0.5 0.5 1</ambient></material>
      </visual>
      <collision name="collision">
        <geometry><cylinder><radius>0.05</radius><length>0.04</length></cylinder></geometry>
      </collision>
    </link>

    <!-- 万向轮(前) -->
    <link name="caster">
      <pose>0.15 0 0.05 0 0 0</pose>
      <inertial>
        <mass>0.2</mass>
        <inertia>
          <ixx>0.0001</ixx><ixy>0</ixy><ixz>0</ixz>
          <iyy>0.0001</iyy><iyz>0</iyz>
          <izz>0.0001</izz>
        </inertia>
      </inertial>
      <visual name="visual">
        <geometry><sphere><radius>0.05</radius></sphere></geometry>
        <material><ambient>0.3 0.3 0.3 1</ambient></material>
      </visual>
      <collision name="collision">
        <geometry><sphere><radius>0.05</radius></sphere></geometry>
        <surface>
          <friction>
            <ode><mu>0</mu><mu2>0</mu2></ode>
          </friction>
        </surface>
      </collision>
    </link>

    <!-- 关节定义 -->
    <joint name="left_wheel_joint" type="revolute">
      <parent>chassis</parent>
      <child>left_wheel</child>
      <axis><xyz>0 1 0</xyz></axis>
      <limit><lower>-1e+16</lower><upper>1e+16</upper></limit>
    </joint>

    <joint name="right_wheel_joint" type="revolute">
      <parent>chassis</parent>
      <child>right_wheel</child>
      <axis><xyz>0 1 0</xyz></axis>
      <limit><lower>-1e+16</lower><upper>1e+16</upper></limit>
    </joint>

    <joint name="caster_joint" type="ball">
      <parent>chassis</parent>
      <child>caster</child>
    </joint>

    <!-- 激光雷达传感器 -->
    <link name="laser">
      <pose>0.15 0 0.15 0 0 0</pose>
      <sensor name="laser_sensor" type="lidar">
        <lidar>
          <scan>
            <horizontal>
              <samples>360</samples>
              <resolution>1</resolution>
              <min_angle>-3.14159</min_angle>
              <max_angle>3.14159</max_angle>
            </horizontal>
          </scan>
          <range>
            <min>0.1</min><max>12.0</max>
            <resolution>0.01</resolution>
          </range>
        </lidar>
        <always_on>true</always_on>
        <update_rate>10</update_rate>
        <visualize>true</visualize>
        <topic>/scan</topic>
      </sensor>
    </link>

    <joint name="laser_joint" type="fixed">
      <parent>chassis</parent>
      <child>laser</child>
    </joint>

    <!-- 差速驱动插件 -->
    <plugin
      filename="gz-sim-diff-drive-system"
      name="gz::sim::systems::DiffDrive">
      <left_joint>left_wheel_joint</left_joint>
      <right_joint>right_wheel_joint</right_joint>
      <wheel_separation>0.35</wheel_separation>
      <wheel_radius>0.05</wheel_radius>
      <topic>/cmd_vel</topic>
      <odom_topic>/odom</odom_topic>
      <frame_id>odom</frame_id>
      <child_frame_id>base_link</child_frame_id>
    </plugin>
  </model>
</sdf>

启动自定义机器人

# 创建世界文件
cat > my_world.sdf << 'EOF'
<?xml version="1.0"?>
<sdf version="1.8">
  <world name="my_world">
    <include>
      <uri>https://fuel.gazebosim.org/1.0/OpenRobotics/models/diff_bot</uri>
    </include>
    <include>
      <uri>https://fuel.gazebosim.org/1.0/OpenRobotics/models/ground_plane</uri>
    </include>
    <include>
      <uri>https://fuel.gazebosim.org/1.0/OpenRobotics/models/sun</uri>
    </include>
  </world>
</sdf>
EOF

# 启动Gazebo仿真
gz sim my_world.sdf

# 新终端:启动ros_gz桥接
ros2 run ros_gz_bridge parameter_bridge \
  /cmd_vel@geometry_msgs/msg/Twist@gz.msgs.Twist \
  /scan@sensor_msgs/msg/LaserScan[gz.msgs.LaserScan \
  /odom@nav_msgs/msg/Odometry[gz.msgs.Odometry

六、ros2_control框架

ros2_control是ROS2的机器人控制框架。它将"控制器逻辑"与"硬件驱动"解耦——控制器只管算,硬件接口只管执行,中间通过CommandInterface和StateInterface对接。仿真里用GazeboSystem替代真实硬件,代码不用改。

控制架构

flowchart TB subgraph ROS2["ROS2控制器"] CMD["/cmd_vel<br/>速度指令输入"] CM["ControllerManager<br/>控制器管理器"] DIFF["DiffDriveController<br/>差速控制器"] JSB["JointStateBroadcaster<br/>关节状态广播"] end subgraph HW["硬件接口层"] GZ_SYS["GazeboSystem<br/>ros2_control Gazebo插件"] CMD_IF["CommandInterface<br/>速度/位置指令"] STATE_IF["StateInterface<br/>关节状态反馈"] end subgraph GZ["Gazebo物理引擎"] JOINTS["关节<br/>left_wheel_joint<br/>right_wheel_joint"] PHY["物理引擎<br/>DART"] end CMD --> CM CM --> DIFF CM --> JSB DIFF -->|"写指令"| CMD_IF CMD_IF --> GZ_SYS GZ_SYS -->|"驱动关节"| JOINTS JOINTS --> PHY PHY -->|"状态反馈"| STATE_IF STATE_IF --> GZ_SYS GZ_SYS -->|"读状态"| CM style ROS2 fill:#E3F2FD,stroke:#1976D2 style HW fill:#FFF8E1,stroke:#F57C00 style GZ fill:#E8F5E9,stroke:#388E3C

控制器配置YAML

# diff_drive_controller.yaml
controller_manager:
  ros__parameters:
    update_rate: 50  # 控制循环频率50Hz

    diff_drive_controller:
      type: diff_drive_controller/DiffDriveController

    joint_state_broadcaster:
      type: joint_state_broadcaster/JointStateBroadcaster

diff_drive_controller:
  ros__parameters:
    left_wheel_names: ["left_wheel_joint"]
    right_wheel_names: ["right_wheel_joint"]
    wheel_separation: 0.35
    wheel_radius: 0.05

    # 速度和加速度限制
    linear.x.max_velocity: 0.5
    angular.z.max_velocity: 1.0
    linear.x.max_acceleration: 2.0
    angular.z.max_acceleration: 2.0

    # 里程计配置
    publish_rate: 50.0
    odom_frame_id: odom
    base_frame_id: base_link
    enable_odom_tf: true

    # 指令话题
    cmd_vel_timeout: 0.5  # 0.5秒无指令则停止
    use_stamped_vel: false  # 使用Unstamped速度指令

URDF中声明ros2_control硬件接口

在URDF中需要声明<ros2_control> 标签,告诉ControllerManager有哪些关节、用什么接口:

<!-- 添加到URDF的<robot>标签内 -->
<ros2_control name="GazeboSystem" type="system">
  <hardware>
    <plugin>gz_ros2_control/GazeboSystem</plugin>
  </hardware>
  <joint name="left_wheel_joint">
    <command_interface name="velocity"/>
    <state_interface name="position"/>
    <state_interface name="velocity"/>
  </joint>
  <joint name="right_wheel_joint">
    <command_interface name="velocity"/>
    <state_interface name="position"/>
    <state_interface name="velocity"/>
  </joint>
</ros2_control>

启动带控制的仿真

# 安装ros2_control相关包(如未安装)
sudo apt install ros-jazzy-ros2-control ros-jazzy-ros2-controllers
sudo apt install ros-jazzy-gz-ros2-control

# 启动仿真(包含ros2_control)
# 假设已有完整的launch文件
ros2 launch my_robot_gazebo my_robot_sim.launch.py

# 查看控制器状态
ros2 control list_controllers
# 期望输出:
# diff_drive_controller [diff_drive_controller/DiffDriveController] active
# joint_state_broadcaster [joint_state_broadcaster/JointStateBroadcaster] active

# 查看硬件接口
ros2 control list_hardware_interfaces
# 期望输出:
# left_wheel_joint/velocity [command]
# right_wheel_joint/velocity [command]
# left_wheel_joint/position [state]
# left_wheel_joint/velocity [state]
# right_wheel_joint/position [state]
# right_wheel_joint/velocity [state]

# 发送速度指令控制机器人
ros2 topic pub /diff_drive_controller/cmd_vel_unstamped \
  geometry_msgs/msg/Twist "{linear: {x: 0.2}, angular: {z: 0.0}}" \
  --rate 10

ros2 control常用命令

# 列出所有控制器
ros2 control list_controllers

# 列出硬件接口
ros2 control list_hardware_interfaces

# 列出控制器类型
ros2 control list_controller_types

# 手动激活/停用控制器
ros2 control set_controller_state diff_drive_controller active
ros2 control set_controller_state diff_drive_controller inactive

# 查看控制器参数
ros2 param get /controller_manager diff_drive_controller.type

七、常用传感器插件

Gazebo Harmonic通过插件系统提供传感器仿真,数据通过ros_gz_bridge发布到ROS2话题。
| 传感器类型 | Gazebo插件 | ROS2话题 | ROS2消息类型 | Gazebo消息类型 |
|:-----------|:-----------|:---------|:-------------|:---------------|
| 2D激光雷达 | gz-sim-lidar-system | /scan | sensor_msgs/LaserScan | gz.msgs.LaserScan |
| 深度相机 | gz-sim-depth-camera-system | /depth_camera/image | sensor_msgs/Image | gz.msgs.Image |
| RGB相机 | gz-sim-camera-system | /camera/image_raw | sensor_msgs/Image | gz.msgs.Image |
| IMU | gz-sim-imu-system | /imu | sensor_msgs/Imu | gz.msgs.IMU |
| 接触传感器 | gz-sim-contact-system | /contact | gazebo_msgs/ContactsState | gz.msgs.Contacts |

传感器桥接命令示例

# 激光雷达
ros2 run ros_gz_bridge parameter_bridge \
  /scan@sensor_msgs/msg/LaserScan[gz.msgs.LaserScan

# RGB相机
ros2 run ros_gz_bridge parameter_bridge \
  /camera/image_raw@sensor_msgs/msg/Image[gz.msgs.Image

# 深度相机(需桥接多个话题)
ros2 run ros_gz_bridge parameter_bridge \
  /depth_camera/image@sensor_msgs/msg/Image[gz.msgs.Image \
  /depth_camera/points@sensor_msgs/msg/PointCloud2[gz.msgs.PointCloudPacked

# IMU
ros2 run ros_gz_bridge parameter_bridge \
  /imu@sensor_msgs/msg/Imu[gz.msgs.IMU

八、URDF与SDF转换

Gazebo Harmonic原生使用SDF,但ROS2生态大量使用URDF。实际项目中两种格式需要互相转换。

URDF转SDF

# 使用gz工具将URDF转为SDF
gz sdf -p my_robot.urdf > my_robot.sdf

# 检查SDF是否有效
gz sdf -k my_robot.sdf

robot_state_publisher在Gazebo中的使用

robot_state_publisher读取URDF并发布TF变换树。在Gazebo仿真中,它仍然有用——负责发布机器人各连杆之间的静态TF。

来自 linuxros.cn · linuxROS
# launch文件中同时使用robot_state_publisher和Gazebo
from launch import LaunchDescription
from launch_ros.actions import Node
from launch.actions import IncludeLaunchDescription
from launch.launch_description_sources import PythonLaunchDescriptionSource
from ament_index_python.packages import get_package_share_directory
import os

def generate_launch_description():
    pkg_share = get_package_share_directory('my_robot_description')

    # robot_state_publisher:发布URDF的TF
    robot_state_publisher = Node(
        package='robot_state_publisher',
        executable='robot_state_publisher',
        parameters=[{
            'robot_description': open(
                os.path.join(pkg_share, 'urdf', 'my_robot.urdf')
            ).read()
        }]
    )

    # Gazebo仿真
    gazebo = IncludeLaunchDescription(
        PythonLaunchDescriptionSource(
            os.path.join(
                get_package_share_directory('ros_gz_sim'),
                'launch', 'gz_sim.launch.py'
            )
        ),
        launch_arguments={
            'gz_args': os.path.join(pkg_share, 'worlds', 'my_world.sdf')
        }.items()
    )

    # ros_gz桥接
    bridge = Node(
        package='ros_gz_bridge',
        executable='parameter_bridge',
        arguments=[
            '/cmd_vel@geometry_msgs/msg/Twist@gz.msgs.Twist',
            '/scan@sensor_msgs/msg/LaserScan[gz.msgs.LaserScan',
            '/odom@nav_msgs/msg/Odometry[gz.msgs.Odometry',
        ]
    )

    return LaunchDescription([
        robot_state_publisher,
        gazebo,
        bridge,
    ])

URDF与SDF选择建议

场景 推荐格式 原因
ROS2节点间通信 URDF robot_state_publisher原生支持
Gazebo仿真 SDF 传感器插件、物理材质等SDF独有特性
MoveIt2运动规划 URDF MoveIt2依赖URDF+SRDF
项目同时需要两种 URDF为主,运行时转SDF 维护一份URDF,用 gz sdf -p 自动转换

九、常见问题

Q1:Gazebo Harmonic启动后黑屏无渲染?

检查显卡驱动和OpenGL支持。虚拟机中Gazebo使用CPU渲染(llvmpipe),性能低但能跑。WSL2需要配置GPU直通(参考WSL2搭建文档)。排查命令:

# 查看详细日志
gz sim -v 4

# 检查渲染后端
gz sim --render-engine-gui-api-backend opengl

Q2:ros_gz_bridge桥接话题无数据?

消息类型必须精确匹配。用 ros2 topic list 和 gz topic -l 对比两侧话题,确认桥接进程已启动且参数正确:

# 检查ROS2侧话题
ros2 topic list

# 检查Gazebo侧话题
gz topic -l

# 查看桥接节点是否在运行
ros2 node list | grep bridge

Q3:URDF模型无法在Gazebo Harmonic中加载?

Gazebo Harmonic原生支持SDFormat。URDF需要先转换。

# URDF转SDF
gz sdf -p my_robot.urdf > my_robot.sdf

# 或使用ros_gz_sim工具加载URDF
ros2 run ros_gz_sim create -file my_robot.urdf -topic robot_description

Q4:ros2_control控制器无法激活?

检查URDF中<ros2_control> 标签是否正确声明了硬件接口,关节名称是否与SDF/URDF中的joint name完全一致:

# 查看控制器状态和错误信息
ros2 control list_controllers -v

# 检查硬件接口是否注册
ros2 control list_hardware_interfaces

十、总结

Gazebo Harmonic + ros_gz桥接 + ros2_control,构成了ROS2 Jazzy时代仿真验证的完整链路:Gazebo负责物理仿真和传感器模拟,ros_gz_bridge做消息翻译,ros2_control做闭环控制。仿真代码和真机代码共享同一套控制器逻辑,切换硬件接口即可从仿真迁移到实车。

速查表

仿真任务 核心工具
启动仿真环境 gz sim + ros2 launch
ROS2↔Gazebo消息桥接 ros_gz_bridge / parameter_bridge
机器人建模 SDFormat(.sdf)原生,URDF用 gz sdf -p 转换
差速运动控制 ros2_control + DiffDriveController
关节状态发布 JointStateBroadcaster
传感器数据获取 Gazebo传感器插件→ros_gz_bridge→ROS2话题
可视化调试 RViz2 + Gazebo GUI
控制器管理 ros2 control list_controllers

本文首发于linuxros.cn,转载请注明出处。

版权声明

作者linuxROS
协议本作品采用 CC BY-NC-SA 4.0 许可协议:署名-非商业性使用-相同方式共享
关注欢迎关注微信公众号 linuxROS,获取更多机器人 / 嵌入式 / Linux 干货
返回首页