核心概念:ROS 和 Python
在开始写代码之前,你必须理解几个核心概念:
- ROS (Robot Operating System):这不是一个真正的操作系统,而是一个用于编写机器人软件的灵活框架,它提供了硬件抽象、设备驱动、库、可视化工具、消息传递、包管理等功能。
- 节点:一个可执行文件,是 ROS 执行计算的基本单元,你的 Python 脚本就是一个节点。
- 话题:节点之间用于发送和接收数据的异步通信机制,你可以把它想象成一个广播电台,一个节点“发布”消息到某个“频道”(话题),其他节点可以“订阅”这个频道来接收消息。
- 消息:一个数据结构,用于在节点之间传递,速度消息包含线速度和角速度。
- 包:一个包含 ROS 节点、库、配置文件等的软件单元,用于组织代码。
前提条件
在运行任何 Python 脚本之前,请确保你已经:
-
设置好 TurtleBot:你已经成功配置了你的 TurtleBot (TurtleBot3),并且可以通过
roscore和turtlebot3_core等节点让它正常工作。 -
安装必要的 Python 库:
# ROS 已经内置了 rospy,但你可能需要安装一些额外的工具 sudo apt-get install ros-noetic-turtlebot3-msgs sudo apt-get install ros-noetic-turtlebot3
-
设置环境变量:每次打开新终端时,都需要加载 ROS 和 TurtleBot 的环境变量。
# 加载 ROS Noetic 环境 (如果你的 ROS 版本是 Noetic) source /opt/ros/noetic/setup.bash # 加载你的工作空间环境 (如果你有的话) source ~/catkin_ws/devel/setup.bash # 设置 TurtleBot3 型号 (Burger, Waffle, Waffle Pi) export TURTLEBOT3_MODEL=burger
提示:为了避免每次都输入,可以将最后两行添加到你家目录下的
.bashrc文件中。
第一个 Python 脚本:发布速度命令
这是最基础也是最常用的操作:让机器人移动,我们通过向 /cmd_vel 话题发布 geometry_msgs/Twist 类型的消息来控制机器人。
创建一个 ROS 包
你需要一个地方来存放你的代码,在 catkin_ws/src 目录下创建一个新的包。
cd ~/catkin_ws/src catkin_create_pkg my_turtlebot_scripts rospy std_msgs geometry_msgs
这会创建一个名为 my_turtlebot_scripts 的包,并依赖了 rospy (ROS Python 库)、std_msgs (标准消息) 和 geometry_msgs (几何消息,如 Twist)。
编写 Python 脚本
在 my_turtlebot_scripts 包中创建一个 scripts 文件夹,并在里面创建你的 Python 脚本。
cd my_turtlebot_scripts mkdir scripts cd scripts touch move_forward.py
用你喜欢的编辑器打开 move_forward.py 并粘贴以下代码:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import rospy
from geometry_msgs.msg import Twist
def move_turtlebot():
"""
一个简单的函数,用于让 TurtleBot 向前移动。
"""
# 1. 初始化 ROS 节点
# anonymous=True 参数确保节点名称是唯一的,即使你多次运行同一个脚本
rospy.init_node('move_forward_node', anonymous=True)
# 2. 创建一个 Publisher 对象
# 它将发布消息到 '/cmd_vel' 话题
# Twist 是消息类型,队列大小为 10
pub = rospy.Publisher('/cmd_vel', Twist, queue_size=10)
# 3. 设置循环的频率
# 10Hz 意味着这个循环每秒运行 10 次
rate = rospy.Rate(10)
# 4. 创建一个 Twist 消息对象
# 这个对象将包含我们想要发送的速度命令
move_cmd = Twist()
# 5. 设置速度值
# Twist 消息有两个主要部分:linear (线性) 和 angular (角速度)
# 我们只设置线性速度的 x 分量
move_cmd.linear.x = 0.2 # 前进速度 (m/s)
move_cmd.linear.y = 0.0
move_cmd.linear.z = 0.0
move_cmd.angular.x = 0.0
move_cmd.angular.y = 0.0
move_cmd.angular.z = 0.0 # 角速度 (rad/s)
# 6. 在节点运行时持续发布消息
# rospy.is_ok() 在按下 Ctrl+C 时会返回 False
while not rospy.is_shutdown():
rospy.loginfo("Moving forward...")
pub.publish(move_cmd)
rate.sleep() # 确保循环以设定的频率运行
if __name__ == '__main__':
try:
move_turtlebot()
except rospy.ROSInterruptException:
pass
使脚本可执行
chmod +x move_forward.py
运行脚本
你可以运行它了。打开三个终端:
终端 1:启动 ROS 核心
roscore
终端 2:启动 TurtleBot3 模拟器 (或真实机器人) 如果你使用的是 Gazebo 仿真器:
# 启动仿真环境 roslaunch turtlebot3_gazebo turtlebot3_world.launch # 如果你使用的是真实机器人,则运行: # roslaunch turtlebot3_bringup turtlebot3_robot.launch
终端 3:运行你的 Python 脚本
# 确保你在 catkin_ws 的根目录下 source devel/setup.bash rosrun my_turtlebot_scripts move_forward.py
你应该会看到你的 TurtleBot (或仿真中的机器人) 开始向前移动,按 Ctrl+C 可以停止脚本,机器人也会停止。
进阶示例:基于键盘控制的避障
这个例子更复杂一些,它将订阅激光雷达的数据,并根据数据来决定是前进还是转向避障。
编写脚本
在 scripts 文件夹中创建 obstacle_avoidance.py:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import rospy
from geometry_msgs.msg import Twist
from sensor_msgs.msg import LaserScan
class ObstacleAvoider:
def __init__(self):
# 初始化节点
rospy.init_node('obstacle_avoider_node', anonymous=True)
# 创建 Publisher 和 Subscriber
self.vel_pub = rospy.Publisher('/cmd_vel', Twist, queue_size=10)
self.scan_sub = rospy.Subscriber('/scan', LaserScan, self.scan_callback)
# 设置循环频率
self.rate = rospy.Rate(10)
# 速度命令对象
self.cmd = Twist()
# 安全距离 (单位: 米)
self.safe_distance = 0.5
def scan_callback(self, scan_data):
"""
处理接收到的激光雷达数据
"""
# 激光雷达数据是一个数组,索引 0 是正前方
# 我们取前方一小片区域的平均值
front_distance = scan_data.ranges[0]
rospy.loginfo("Front distance: {:.2f} m".format(front_distance))
if front_distance < self.safe_distance:
# 如果检测到障碍物,后退并转向
rospy.logwarn("Obstacle detected! Turning...")
self.cmd.linear.x = -0.1 # 后退
self.cmd.angular.z = 0.5 # 转向
else:
# 如果没有障碍物,前进
rospy.loginfo("Path is clear. Moving forward...")
self.cmd.linear.x = 0.2 # 前进
self.cmd.angular.z = 0.0 # 不转向
def run(self):
"""
主循环,持续发布速度命令
"""
while not rospy.is_shutdown():
self.vel_pub.publish(self.cmd)
self.rate.sleep()
if __name__ == '__main__':
try:
avoider = ObstacleAvoider()
avoider.run()
except rospy.ROSInterruptException:
pass
运行脚本
同样,打开三个终端:
终端 1:
roscore
终端 2: (启动仿真器,它已经包含了激光雷达传感器)
roslaunch turtlebot3_gazebo turtlebot3_world.launch
终端 3: (运行新的脚本)
rosrun my_turtlebot_scripts obstacle_avoidance.py
你的机器人会向前移动,当它接近墙壁或障碍物时,会自动后退并转向,以避开障碍物。
调试和可视化工具
-
rostopic:一个强大的命令行工具,用于查看和调试话题。rostopic list:列出所有活动的话题。rostopic echo /scan:实时打印/scan话题的内容(激光雷达数据)。rostopic echo /cmd_vel:实时打印你发布的速度命令。rostopic hz /scan:检查/scan话题的发布频率。
-
rqt_graph:可视化 ROS 节点和话题的连接关系。 在一个终端中运行:rosrun rqt_graph rqt_graph
你会看到一个动态图,清晰地显示了你的 Python 节点 (
/move_forward_node) 如何连接到/cmd_vel话题,以及/scan节点如何连接到你的obstacle_avoider_node。
控制 TurtleBot 的 Python 脚本遵循一个固定的模式:
rospy.init_node(...):初始化你的节点。rospy.Publisher(...):创建发布者,用于发送命令(如速度)。rospy.Subscriber(...):创建订阅者,用于接收传感器数据(如激光雷达、摄像头图像)。while not rospy.is_shutdown()::创建主循环。rate.sleep():在循环中使用,以控制代码执行频率。pub.publish(message):在循环中发布消息。- 在回调函数中处理数据:订阅者接收到的数据会自动传递给你定义的回调函数。
从发布简单的速度命令开始,然后尝试订阅传感器数据,这是掌握 ROS 和 TurtleBot 编程的关键一步,祝你玩得开心!
