H2 1. 为什么具身智能开发者绕不开 ROS2H3 1.1 具身智能需要什么样的机器人软件框架H3 1.2 ROS2 与 ROS1 的核心差异H3 1.3 本文的适合人群与学习目标
进入 2026 年,具身智能(Embodied AI)已经从学术热词变成了机器人行业真实的研发方向。无论是人形机器人、机械臂抓取,还是移动底盘导航,背后都需要一套能把感知、决策、控制、通信串起来的软件框架。而目前综合生态最完整、社区最活跃、资料最多的框架,仍然是 ROS2。
很多读者第一次接触 ROS2 时会有一个困惑:我到底是直接学具身智能算法,还是先学 ROS2?答案是,如果你做的是真实机器人,哪怕只是仿真环境中的机器人,ROS2 都会成为你和硬件、传感器、算法模块之间的“中间层”。它本身不解决具体的视觉模型怎么做、路径规划怎么算,但它负责让这些模块能够互相通信、协同运行、可视化调试。可以把 ROS2 理解为机器人领域的“操作系统级软件底座”。
本文不是泛泛介绍,而是一份从零开始的 ROS2 上手教程。我们会在 Ubuntu 环境下完成 ROS2 安装,创建工作空间和功能包,再通过完整的 Python 示例跑通三种最核心的通信机制:话题(Topic)、服务(Service)、动作(Action)。最后补充坐标变换 tf2 和 Rviz2、Gazebo 等常用工具的使用思路。看完之后,你会具备独立搭建一个 ROS2 工程、编写节点并完成基础联调的能力。
在正式动手前,先简单区分一下 ROS1 和 ROS2。ROS1 最早诞生于 2007 年左右,设计上偏向单机、学术研究,通信依赖一个中心节点(roscore)。ROS2 则从 2017 年开始逐步成熟,底层通信改用了 DDS(Data Distribution Service),不再需要中心节点,支持多机通信、实时性要求更高的场景,也更适合现代机器人架构。2022 年发布的 ROS1 Noetic 是最后一个 ROS1 版本,ROS1 已经停止维护。所以现在新入门,不需要纠结,直接学 ROS2 就好。
本文的示例以 Ubuntu 22.04 + ROS2 Humble 为主,也会说明 Ubuntu 24.04 + ROS2 Jazzy 的对应关系。如果你之前没有任何 ROS 基础,也不用担心,下一章我们从环境安装开始,每一步都可以照着操作。
1. ROS2 环境搭建:版本选型与安装验证
1.1 Ubuntu 与 ROS2 版本对应关系
ROS2 的 LTS(Long Term Support,长期支持)版本与 Ubuntu 系统的 LTS 版本有严格对应关系。版本不匹配虽然可以通过源码编译强行安装,但对初学者来说,强烈建议直接选择官方支持的组合。
| Ubuntu 版本 | 推荐 ROS2 版本 | 说明 |
|---|---|---|
| Ubuntu 22.04 LTS | ROS2 Humble Hawksbill | 当前教程资料最多的版本,使用稳定 |
| Ubuntu 24.04 LTS | ROS2 Jazzy Jalisco | 2024 年发布的新 LTS,适合新项目 |
| Ubuntu 20.04 LTS | ROS2 Foxy Fitzroy | 较老版本,不建议新入门使用 |
本文命令以 Humble 为例,如果你使用的是 Ubuntu 24.04,把命令里的humble全部替换成jazzy即可,核心章节的 Python 代码在两个版本下基本通用。
需要注意的是,不要在公司内部生产机器上直接做破坏性实验,安装 ROS2 属于系统环境变更,建议先在虚拟机、Docker 容器或专用测试机上进行。
1.2 以 Humble 为例的完整安装步骤
打开终端,按顺序执行以下命令。
第一步,更新系统软件源并安装基础工具:
sudo apt update sudo apt install software-properties-common -y第二步,添加 ROS2 官方软件源。这里先把 ROS2 的 GPG 密钥下载到系统 keyring 目录:
sudo apt update && sudo apt install curl -y sudo curl -sSL https://raw.githubusercontent.com/ros/rosdistro/master/ros.key -o /usr/share/keyrings/ros-archive-keyring.gpg然后配置 apt 使用 ROS2 软件源:
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/ros-archive-keyring.gpg] http://packages.ros.org/ros2/ubuntu $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/ros2.list > /dev/null如果你的网络访问packages.ros.org不稳定,可以把这个源地址换成国内镜像源,一般常见的是清华 TUNA 镜像:
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/ros-archive-keyring.gpg] https://mirrors.tuna.tsinghua.edu.cn/ros2/ubuntu $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/ros2.list > /dev/null注意,镜像源的完整配置方式要以镜像站自己的说明为准,这里仅提供思路。
第三步,更新软件索引并安装 ROS2 桌面版:
sudo apt update sudo apt upgrade -y sudo apt install ros-humble-desktop -yros-humble-desktop是桌面完整版,包含了 Rviz2、turtlesim 小海龟、demo 示例、常用消息库等,适合开发和可视化调试。如果你做的是非常精简的嵌入式部署,可以只安装:
sudo apt install ros-humble-ros-base -y第四步,安装开发工具:
sudo apt install ros-dev-tools -yros-dev-tools包含colcon、rosdep、ros2命令行等常用工具,后面创建功能包和编译项目都离不开它们。
1.3 安装验证与常用环境变量
安装完成后,需要把 ROS2 的环境脚本加载到当前终端:
source /opt/ros/humble/setup.bash为了以后每次打开终端都自动加载,可以把这行写入.bashrc:
echo "source /opt/ros/humble/setup.bash" >> ~/.bashrc source ~/.bashrc验证安装是否成功:
ros2 --help如果能看到 ROS2 命令行帮助信息,说明安装成功。接着跑一个最简单的小海龟测试:
ros2 run turtlesim turtlesim_node另开一个终端:
ros2 run turtlesim turtle_teleop_key这时会出现一只小海龟,用方向键控制它移动,就说明 ROS2 的核心通信链路已经正常工作了。
另外,很多教程会建议设置ROS_DOMAIN_ID环境变量。同一台机器或同一局域网内多个 ROS2 系统通信时,不同ROS_DOMAIN_ID可以隔离消息,避免互相干扰。单机学习时默认值足够。
2. 工作空间与功能包:ROS2 项目的组织方式
2.1 工作空间结构
ROS2 的项目组织方式和 ROS1 类似,核心单位是“功能包(Package)”,多个功能包放在同一个工作空间(Workspace)下统一编译。典型结构如下:
ros2_ws/ ├── src/ │ ├── pkg_a/ │ ├── pkg_b/ │ └── ... ├── build/ ├── install/ └── log/src目录存放你自己写的功能包,build、install、log目录是执行colcon build后自动生成的。install目录下会有setup.bash,编译完成后需要 source 它,才能让新功能包被当前终端识别。
创建工作空间的命令:
mkdir -p ~/ros2_ws/src cd ~/ros2_ws colcon build此时src目录为空,直接colcon build也能成功,但没有任何包被编译。
2.2 创建第一个功能包
进入src目录,使用ros2 pkg create创建一个 Python 类型的功能包:
cd ~/ros2_ws/src ros2 pkg create py_talker_listener --build-type ament_python --dependencies rclpy std_msgs这个命令会生成如下结构:
py_talker_listener/ ├── package.xml ├── py_talker_listener/ │ └── __init__.py ├── resource/ ├── setup.cfg ├── setup.py └── test/参数解释:
--build-type ament_python:指定包类型为 Python 包。如果你更熟悉 C++,可以换成ament_cmake。--dependencies rclpy std_msgs:声明依赖,rclpy是 ROS2 的 Python 客户端库,std_msgs提供标准消息类型。
2.3 使用 colcon 构建
回到工作空间根目录,编译刚才创建的功能包:
cd ~/ros2_ws colcon build --packages-select py_talker_listener--packages-select只编译指定的包,在包多的时候可以节省时间。编译完成后:
source install/setup.bash之后用ros2 pkg list可以查看到该包。
2.4 功能包中的关键配置文件
Python 功能包中最重要的配置是setup.py里的entry_points。这个字段决定了ros2 run命令能否找到你的可执行程序。修改setup.py,添加如下内容:
entry_points={ 'console_scripts': [ 'talker = py_talker_listener.talker:main', 'listener = py_talker_listener.listener:main', ], },其中talker是命令别名,后面这部分是“模块名:函数名”。比如py_talker_listener.talker:main,表示从py_talker_listener/talker.py模块中执行main函数。写错路径会导致运行时提示找不到入口点。
另外,setup.py中的packages字段需要保证 ROS2 的 Python 源码目录被正确打包。默认模板通常已经处理好了,如果改动目录结构,需要同步检查。
3. 节点与话题:ROS2 最核心的通信机制
3.1 节点与话题的概念
节点(Node)是 ROS2 中的最小执行单元。一个功能包可以包含多个节点,每个节点负责一个独立任务。节点之间通过话题(Topic)进行异步通信。
话题是一种发布/订阅模型。一个节点在话题上发布数据,其他节点订阅该话题接收数据。发布方和订阅方互不知道对方的存在,这种解耦设计让机器人系统可以灵活扩展。类似地,你把一个传感器节点接入系统,其他模块只需要订阅对应的话题,不需要修改。
消息类型(Message)决定了话题数据的结构。例如std_msgs/msg/String是一个非常基础的消息类型,只包含一个字符串字段data。
3.2 用 Python 实现一个话题发布者
在py_talker_listener/py_talker_listener/目录下新建talker.py:
#!/usr/bin/env python3 import rclpy from rclpy.node import Node from std_msgs.msg import String class TalkerNode(Node): def __init__(self): super().__init__('talker') self.publisher_ = self.create_publisher(String, 'chatter', 10) self.timer = self.create_timer(0.5, self.timer_callback) self.count = 0 def timer_callback(self): msg = String() msg.data = 'Hello ROS2: %d' % self.count self.publisher_.publish(msg) self.get_logger().info('Publishing: "%s"' % msg.data) self.count += 1 def main(args=None): rclpy.init(args=args) node = TalkerNode() try: rclpy.spin(node) except KeyboardInterrupt: pass finally: node.destroy_node() rclpy.shutdown() if __name__ == '__main__': main()代码要点:
create_publisher(String, 'chatter', 10)创建一个话题发布者,第一个参数是消息类型,第二个是话题名,第三个是消息队列长度。create_timer(0.5, self.timer_callback)每 0.5 秒调用一次回调函数。rclpy.spin(node)让节点持续运行,处理回调,直到被 Ctrl+C 中断。
3.3 用 Python 实现一个话题订阅者
在相同目录下新建listener.py:
#!/usr/bin/env python3 import rclpy from rclpy.node import Node from std_msgs.msg import String class ListenerNode(Node): def __init__(self): super().__init__('listener') self.subscription = self.create_subscription( String, 'chatter', self.listener_callback, 10 ) def listener_callback(self, msg): self.get_logger().info('I heard: "%s"' % msg.data) def main(args=None): rclpy.init(args=args) node = ListenerNode() rclpy.spin(node) node.destroy_node() rclpy.shutdown() if __name__ == '__main__': main()注意,一个进程里如果创建了多个节点,rclpy.spin默认只会处理全局 executor 中的节点。上面的写法保持一个进程一个节点,是最稳妥的入门方式。
3.4 运行与验证
修改完setup.py和两个 Python 文件后,重新编译并 source:
cd ~/ros2_ws colcon build --packages-select py_talker_listener --symlink-install source install/setup.bash--symlink-install对 Python 包非常有用,后续修改.py文件不用重新 build,直接生效。
先运行发布者:
ros2 run py_talker_listener talker再开一个终端,source 环境后运行订阅者:
source /opt/ros/humble/setup.bash source ~/ros2_ws/install/setup.bash ros2 run py_talker_listener listener预期输出中,订阅者终端会不断打印:
[INFO] I heard: "Hello ROS2: 0" [INFO] I heard: "Hello ROS2: 1"此时可以用第三终端查看话题状态:
ros2 topic list ros2 topic echo /chatter ros2 topic info /chatter --verbose3.5 QoS 策略与话题通信排错
QoS(Quality of Service,服务质量)是 ROS2 中非常重要的概念。它定义了消息传输的可靠性、历史数据保留策略等。
常见参数:
reliability:RELIABLE(可靠传输,保证送达,适合控制指令)或BEST_EFFORT(尽力传输,延迟更低,适合图像、点云等大流量传感器数据)。durability:VOLATILE(不保存历史数据)或TRANSIENT_LOCAL(保存最近数据,晚加入的订阅者也能收到)。
如果发布者的 QoS 和订阅者不兼容,会出现“双方都能启动,但订阅者收不到数据”的情况。排查时用这个命令:
ros2 topic info /chatter --verbose它会显示各端声明的 QoS 策略,能帮你快速定位问题。在实际项目中,图像传输常用BEST_EFFORT,导航指令用RELIABLE。
4. 服务机制:同步请求与应答
4.1 服务机制解决的问题
话题是异步单向通信,机器人里很多场景需要“请求-应答”模式,例如:调用一个服务让机械臂运动到某个位置,运动完成后返回结果;或者查询机器人当前的电池电量。
ROS2 的服务(Service)提供的就是这种同步 RPC 式通信。客户端(Client)发送请求,服务端(Server)处理请求并返回响应。一个服务端可以同时服务多个客户端,但同一个请求只对应一个响应。
4.2 服务端与客户端代码实战
这里用一个加法服务作为示例。创建服务功能包:
cd ~/ros2_ws/src ros2 pkg create py_service_demo --build-type ament_python --dependencies rclpy example_interfacesexample_interfaces中提供了AddTwoInts.srv,包含两个整型请求字段a和b,以及一个整型响应字段sum。
在py_service_demo/py_service_demo/下新建server.py:
#!/usr/bin/env python3 import rclpy from rclpy.node import Node from example_interfaces.srv import AddTwoInts class AdderServer(Node): def __init__(self): super().__init__('adder_server') self.srv = self.create_service(AddTwoInts, 'add_two_ints', self.add_callback) def add_callback(self, request, response): response.sum = request.a + request.b self.get_logger().info('Incoming request: a=%d, b=%d' % (request.a, request.b)) return response def main(args=None): rclpy.init(args=args) node = AdderServer() rclpy.spin(node) node.destroy_node() rclpy.shutdown() if __name__ == '__main__': main()新建client.py:
#!/usr/bin/env python3 import rclpy from rclpy.node import Node from example_interfaces.srv import AddTwoInts class AdderClient(Node): def __init__(self): super().__init__('adder_client') self.cli = self.create_client(AddTwoInts, 'add_two_ints') while not self.cli.wait_for_service(timeout_sec=1.0): self.get_logger().info('service not available, waiting...') def send_request(self, a, b): request = AddTwoInts.Request() request.a = a request.b = b future = self.cli.call_async(request) rclpy.spin_until_future_complete(self, future) return future.result() def main(args=None): rclpy.init(args=args) client = AdderClient() response = client.send_request(3, 5) client.get_logger().info('Result: %d + %d = %d' % (3, 5, response.sum)) client.destroy_node() rclpy.shutdown() if __name__ == '__main__': main()与话题回调不同的是,客户端的call_async返回的是一个 Future 对象,需要用spin_until_future_complete阻塞等待服务端返回。
4.3 配置与运行
修改setup.py的entry_points:
entry_points={ 'console_scripts': [ 'server = py_service_demo.server:main', 'client = py_service_demo.client:main', ], },然后编译:
cd ~/ros2_ws colcon build --packages-select py_service_demo --symlink-install source install/setup.bash先启动服务端:
ros2 run py_service_demo server再启动客户端:
ros2 run py_service_demo client客户端会打印Result: 3 + 5 = 8。也可以直接用命令行工具测试服务:
ros2 service call /add_two_ints example_interfaces/srv/AddTwoInts "{a: 10, b: 20}"这种方式在调试时很有用,不需要写任何代码就能验证服务端是否正常。
5. 动作机制:长时间任务的通信方案
5.1 动作机制的三段式结构
话题适合高频单向数据,服务适合短时同步调用。但如果是一个需要执行几十秒甚至更久的任务,比如“把机械臂移动到目标点”“导航到二楼会议室”,使用服务会有问题:调用方需要一直阻塞等待,中途无法取消,也无法获得过程反馈。
ROS2 的动作(Action)机制就是为了解决长任务控制而设计的。它由三部分组成:
- 目标(Goal):客户端告诉服务端要做什么。
- 反馈(Feedback):服务端在执行过程中持续返回当前进度。
- 结果(Result):任务完成后返回最终结果。
动作的底层仍然借助了话题和服务,但对上层用户来说,你只需要关心目标、反馈、结果这三个阶段。
5.2 动作服务端实现
ROS2 中常见的动作接口是example_interfaces/action/Fibonacci。它接收一个整数order,表示要计算的斐波那契数列项数,执行过程中不断发布当前已经算出的序列,最终返回完整序列。
继续在py_service_demo包中演示,先新建action_server.py:
#!/usr/bin/env python3 import rclpy from rclpy.action import ActionServer from rclpy.node import Node from example_interfaces.action import Fibonacci class FibonacciServer(Node): def __init__(self): super().__init__('fibonacci_server') self._action_server = ActionServer( self, Fibonacci, 'fibonacci', self.execute_callback ) self.get_logger().info('Action server is running.') def execute_callback(self, goal_handle): self.get_logger().info('Executing goal: order=%d' % goal_handle.request.order) feedback_msg = Fibonacci.Feedback() feedback_msg.sequence = [0, 1] for i in range(1, goal_handle.request.order): feedback_msg.sequence.append( feedback_msg.sequence[i - 1] + feedback_msg.sequence[i] ) goal_handle.publish_feedback(feedback_msg) self.get_logger().info('Feedback: {0}'.format(feedback_msg.sequence)) goal_handle.succeed() result = Fibonacci.Result() result.sequence = feedback_msg.sequence return result def main(args=None): rclpy.init(args=args) node = FibonacciServer() try: rclpy.spin(node) except KeyboardInterrupt: pass finally: node.destroy_node() rclpy.shutdown() if __name__ == '__main__': main()这里的execute_callback是动作服务端核心,它会在收到目标后执行任务循环,并通过goal_handle.publish_feedback发布反馈。
5.3 动作客户端实现
新建action_client.py:
#!/usr/bin/env python3 import rclpy from rclpy.action import ActionClient from rclpy.node import Node from example_interfaces.action import Fibonacci class FibonacciClient(Node): def __init__(self): super().__init__('fibonacci_client') self._action_client = ActionClient(self, Fibonacci, 'fibonacci') def send_goal(self, order): goal_msg = Fibonacci.Goal() goal_msg.order = order self._action_client.wait_for_server() self._send_goal_future = self._action_client.send_goal_async( goal_msg, feedback_callback=self.feedback_callback ) self._send_goal_future.add_done_callback(self.goal_response_callback) def goal_response_callback(self, future): goal_handle = future.result() if not goal_handle.accepted: self.get_logger().info('Goal rejected :(') return self.get_logger().info('Goal accepted :)') self._get_result_future = goal_handle.get_result_async() self._get_result_future.add_done_callback(self.get_result_callback) def get_result_callback(self, future): result = future.result().result self.get_logger().info('Result: {0}'.format(result.sequence)) rclpy.shutdown() def feedback_callback(self, feedback_msg): feedback = feedback_msg.feedback self.get_logger().info('Received feedback: {0}'.format(feedback.sequence)) def main(args=None): rclpy.init(args=args) node = FibonacciClient() node.send_goal(10) rclpy.spin(node) if __name__ == '__main__': main()客户端发送目标后,通过回调分别处理“服务端是否接受目标”“执行过程中的实时反馈”“最终结果”三个阶段。这种异步模型非常适合导航和机械臂控制。
修改setup.py的entry_points:
entry_points={ 'console_scripts': [ 'server = py_service_demo.server:main', 'client = py_service_demo.client:main', 'action_server = py_service_demo.action_server:main', 'action_client = py_service_demo.action_client:main', ], },编译并运行:
cd ~/ros2_ws colcon build --packages-select py_service_demo --symlink-install source install/setup.bash ros2 run py_service_demo action_server另一个终端:
source /opt/ros/humble/setup.bash source ~/ros2_ws/install/setup.bash ros2 run py_service_demo action_client客户端会持续收到斐波那契数列的中间结果,最后打印完整序列。
6. 坐标变换与常用工具:tf2、Rviz2、Gazebo、小海龟
6.1 tf2 坐标变换基础
在机器人系统中,不同传感器、关节、部件都有自己的坐标系。例如激光雷达在车顶,摄像头在车头,机械臂末端在手臂末端,要融合这些数据,必须知道每个坐标系之间的相对位置和姿态。
ROS2 中负责这件事的库是 tf2。它维护了一棵坐标变换树,每个变换关系都带时间戳,机器人实时查询“激光点云在 base_link 坐标系下的位置”这类问题时,tf2 会自动完成坐标转换。
tf2 中的两个关键概念:
frame_id:父坐标系名称,例如world或map。child_frame_id:子坐标系名称,例如base_link、laser_frame。
坐标变换分为静态变换(两个坐标系相对关系固定)和动态变换(关系随时间变化),例如底盘到激光雷达之间是静态,而机械臂关节之间的变换是动态。
6.2 发布静态与动态坐标变换
最简单的静态变换可以通过命令行发布。在终端中执行:
ros2 run tf2_ros static_transform_publisher 1.0 0.0 0.5 0 0 0 world robot_base这条命令表示把robot_base坐标系固定在world坐标系下 x=1.0、y=0.0、z=0.5 的位置,旋转角为 0。不同 ROS2 版本中 static_transform_publisher 的参数顺序可能略有差异,建议先用ros2 run tf2_ros static_transform_publisher --help确认。
如果需要在代码中动态发布坐标变换,可以新建一个节点:
#!/usr/bin/env python3 import rclpy from rclpy.node import Node from geometry_msgs.msg import TransformStamped from tf2_ros import TransformBroadcaster class DynamicTFBroadcaster(Node): def __init__(self): super().__init__('dynamic_tf_broadcaster') self.tf_broadcaster = TransformBroadcaster(self) self.timer = self.create_timer(0.1, self.broadcast_timer_callback) def broadcast_timer_callback(self): t = TransformStamped() t.header.stamp = self.get_clock().now().to_msg() t.header.frame_id = 'world' t.child_frame_id = 'robot_base' t.transform.translation.x = 1.0 t.transform.translation.y = 0.0 t.transform.translation.z = 0.5 # 四元数表示姿态,这里为单位四元数 t.transform.rotation.x = 0.0 t.transform.rotation.y = 0.0 t.transform.rotation.z = 0.0 t.transform.rotation.w = 1.0 self.tf_broadcaster.sendTransform(t) def main(args=None): rclpy.init(args=args) node = DynamicTFBroadcaster() rclpy.spin(node) node.destroy_node() rclpy.shutdown() if __name__ == '__main__': main()这里特别注意,tf2 中的旋转使用的是四元数,不是欧拉角。如果你习惯用 Roll-Pitch-Yaw 欧拉角,需要先做转换。ROS2 的tf_transformations库提供了quaternion_from_euler方法。
6.3 Rviz2 可视化
Rviz2 是 ROS2 官方的 3D 可视化工具。它最大的价值在于,你可以直观看到机器人的模型、传感器数据、坐标变换关系,而不需要把每个数据打印到终端。
运行:
ros2 run rviz2 rviz2在 Rviz2 界面左侧的Displays面板点击Add,添加TF显示,就能看到世界坐标和各个 frame 之间的坐标轴关系。添加RobotModel可以加载机器人 URDF 模型,添加LaserScan、PointCloud2可以显示雷达和点云数据。
在具身智能相关项目中,Rviz2 几乎是必用的调试工具。很多看似复杂的传感器对齐问题,打开 Rviz2 看一眼 TF 树就明白了。
6.4 Gazebo 仿真与小海龟示例
Gazebo 是目前 ROS2 生态中最常用的机器人仿真环境之一,可以模拟物理碰撞、重力、传感器噪声。安装 Gazebo 相关插件:
sudo apt install ros-humble-gazebo-ros-pkgs -y启动一个空的 Gazebo 世界:
ros2 launch gazebo_ros gazebo.launch.pyGazebo 和 Rviz2 配合使用时,通常还需要 camera、laser 等传感器插件,这部分会涉及 URDF 和 xacro 建模,属于进阶内容。入门阶段,推荐先用小海龟把 ROS2 基础通信彻底跑熟,再进入仿真环境。
小海龟虽然简单,却是理解 ROS2 通信机制最好的教材:
ros2 run turtlesim turtlesim_node另外终端执行:
ros2 run turtlesim turtle_teleop_key然后在新终端查看:
ros2 node list ros2 topic list ros2 topic echo /turtle1/pose通过ros2 topic echo /turtle1/pose,你能看到小海龟每一帧的位置和角度数据,这其实就是移动机器人里程计信息的最简化版本。
7. 常见问题与排查思路
7.1 安装与构建类问题
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
sudo apt update时 ROS2 源报错 | 软件源地址不可达或 key 路径不对 | 检查/etc/apt/sources.list.d/ros2.list,切换国内镜像源 |
ros2: command not found | 没有 source ROS2 环境 | 执行source /opt/ros/humble/setup.bash,并写入~/.bashrc |
colcon: command not found | 没有安装ros-dev-tools | 执行sudo apt install ros-dev-tools |
ros2 run提示找不到入口点 | setup.py中entry_points路径写错 | 检查入口点 = 模块路径:函数名是否与文件实际路径一致 |
| 不同 ROS2 LTS 混用导致编译报错 | 系统存在多版本 ROS2 环境变量冲突 | 一个终端只 source 一个 ROS2 版本,工作空间内包保持一致 |
7.2 通信与运行类问题
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
| 发布者和订阅者都启动,但订阅不到数据 | QoS 不兼容,或话题名拼写不一致 | 使用ros2 topic info /话题名 --verbose检查 QoS |
| 多机通信时节点互相看不见 | ROS_DOMAIN_ID不同,或 DDS 发现机制受阻 | 把多台机器设置为相同ROS_DOMAIN_ID,确保在同一网段 |
| 服务客户端一直打印 waiting | 服务端未启动,或服务名不一致 | 先启动服务端,用ros2 service list检查服务名 |
| 程序运行时卡住,Ctrl+C 无法退出 | 回调中写了阻塞逻辑,或spin使用不当 | 保持spin在主线,避免在回调中做耗时操作 |
7.3 仿真与可视化类问题
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
| Rviz2 中看不到机器人模型 | 未添加 RobotModel,或模型描述话题未发布 | 确认/robot_description话题存在,并在 Rviz2 中正确添加显示 |
| Gazebo 启动后机器人掉落或穿模 | 模型缺少碰撞属性,或物理参数配置错误 | 检查 URDF 中<collision>和<inertial>配置 |
| tf2 查询不到坐标变换 | 变换发布方未启动,或 parent/child 关系写反 | 用ros2 run tf2_ros tf2_echo 父坐标系 子坐标系调试 |
另外,在 Windows 系统中安装 ROS2 桌面相关工具时,有读者遇到过“此应用包不支持通过应用安装程序安装,因为它使用了某些受限制的功能”的提示。这类问题通常是系统缺少开发人员模式或应用安装权限受限导致的。如果你不是在 Windows 下编译源码,我更推荐直接用 WSL2 + Docker 或 Linux 虚拟机作为初学环境,可以避开大量系统兼容问题。
8. 最佳实践与学习路线建议
8.1 工程化开发建议
在学习阶段就能养成的 ROS2 开发习惯,会直接影响后续做项目的效率。
第一,包名、节点名、话题名使用蛇形命名法,例如py_talker_listener、`