ROS 程序框架
基本思路(以C++为例)
- 使用catkin_create_pkg创建一个软件包
- 在软件包的src文件夹下创建一个节点的cpp源码文件
- 在节点的源码文件中include包含ROS的头文件
- 构建一个main函数,并在函数的开头执行ros::init()
- 构建while循环,循环条件为ros::ok()
- 在CMakeLists.txt中设置节点源码的编译规则
- 编译运行
代码框架
ROS(Robot Operating System)程序的代码框架主要由以下几个核心部分组成,无论是使用 C++ 还是 Python 编写。下面是 ROS 程序的一般代码框架的概述:
1. 包(Package)的创建
- 每个 ROS 程序都位于一个包内。包是 ROS 程序的基本组织单位,包含代码、节点、库、配置文件等。
- 使用
catkin_create_pkg
创建一个新包,指定依赖项:catkin_create_pkg <package_name> [dependencies]
2. 包的配置文件
-
package.xml:定义包的元数据和依赖关系。
<package><name>my_package</name><version>0.0.0</version><description>A simple ROS package</description><depend>roscpp</depend><depend>std_msgs</depend>... </package>
-
CMakeLists.txt(仅限 C++):定义如何构建代码和安装目标。
cmake_minimum_required(VERSION 3.0.2) project(my_package)find_package(catkin REQUIRED COMPONENTSroscppstd_msgs... )catkin_package()include_directories(${catkin_INCLUDE_DIRS})add_executable(${PROJECT_NAME}_node src/my_node.cpp) target_link_libraries(${PROJECT_NAME}_node ${catkin_LIBRARIES})
3. 节点(Node)的实现
- 节点是 ROS 网络中的一个独立运行的实体,负责特定的功能(比如数据处理、传感器控制等)。
C++ 节点示例
#include "ros/ros.h"
#include "std_msgs/String.h"int main(int argc, char **argv) {ros::init(argc, argv, "node_name");ros::NodeHandle nh;ros::Publisher pub = nh.advertise<std_msgs::String>("topic_name", 1000);ros::Rate loop_rate(10);while (ros::ok()) {std_msgs::String msg;msg.data = "hello world";pub.publish(msg);ros::spinOnce();loop_rate.sleep();}return 0;
}
Python 节点示例
#!/usr/bin/env python
import rospy
from std_msgs.msg import Stringdef talker():pub = rospy.Publisher('chatter', String, queue_size=10)rospy.init_node('talker', anonymous=True)rate = rospy.Rate(10) # 10hzwhile not rospy.is_shutdown():hello_str = "hello world %s" % rospy.get_time()rospy.loginfo(hello_str)pub.publish(hello_str)rate.sleep()if __name__ == '__main__':talker()
4. 构建和运行
- 使用
catkin_make
在 ROS 工作空间中构建包。 - 运行节点:
- C++:
rosrun my_package my_node
- Python:
rosrun my_package my_script.py
- C++:
5. 其他文件和目录
- scripts:用于存放 Python 脚本。
- src:存放 C++ 源代码。
- msg 和 srv:定义自定义消息和服务类型。
- launch:包含启动多个节点的 ROS launch 文件。
这个框架提供了一个标准的方法来组织和构建 ROS 程序,确保代码的模块化和易于管理。每个节点在 ROS 网络中执行特定的任务,并通过消息、服务和动作与其他节点通信。
C++版本分析
这段代码是一个使用 C++ 编写的简单 ROS 节点示例,它展示了如何使用 roscpp
(ROS 的 C++ 客户端库)创建一个发布消息的节点。以下是对代码的详细解释及 ROS 程序的基本框架概述。
代码解释
-
包含 ROS 头文件:
#include "ros/ros.h" #include "std_msgs/String.h"
这里包含了 ROS 的主要头文件和用于发布字符串消息的标准消息类型。
-
主函数:
int main(int argc, char **argv) {... }
定义了程序的入口点。
-
初始化 ROS 节点:
ros::init(argc, argv, "node_name");
初始化 ROS 并命名节点为
"node_name"
。argc
和argv
参数用于处理任何 ROS 参数和重映射。 -
创建节点句柄:
ros::NodeHandle nh;
节点句柄是与 ROS 系统通信的主要接入点。
-
创建发布者:
ros::Publisher pub = nh.advertise<std_msgs::String>("topic_name", 1000);
通过节点句柄
nh
,创建一个发布者pub
。这个发布者将会向"topic_name"
话题发布std_msgs::String
类型的消息。1000
是发布队列的大小。 -
设置循环频率:
ros::Rate loop_rate(10);
设置循环频率为 10Hz。
-
节点的主循环:
while (ros::ok()) {... }
在 ROS 运行正常时持续循环。
-
创建消息并发布:
std_msgs::String msg; msg.data = "hello world"; pub.publish(msg);
在每次循环中,创建一个
std_msgs::String
消息,填充数据,并通过发布者pub
发布。 -
调用 ros::spinOnce():
ros::spinOnce();
允许 ROS 处理任何待处理的回调函数,适用于需要同时发布和订阅的节点。
-
睡眠以维持循环频率:
loop_rate.sleep();
控制循环频率,使其保持在设定的 10Hz。
-
返回:
return 0;
当 ROS 关闭或节点被终止时,程序退出。
ROS 程序的基本框架
从这个示例中,我们可以归纳出 ROS 程序的基本框架:
- 包含必要的头文件:包括 ROS 的核心头文件和任何消息类型的头文件。
- 初始化 ROS 节点:设置节点的名称,并处理任何 ROS 命令行参数。
- 创建节点句柄:用于管理 ROS 资源和通信。
- 创建通信接口:如发布者、订阅者或服务。
- 设置循环频率(可选):对于需要定时执行任务的节点。
- 实现节点功能:在循环中进行消息的发布、接收或其他操作。
- 维护循环频率和响应回调:通过
ros::spinOnce()
和循环睡眠来实现。
这种结构为编写 ROS 节点提供了一个清晰的模板,无论是发布者、订阅者还是服务的实现。通过这个框架,可以确保 ROS 节点能够正确地与 ROS 网络交互,执行其预定的任务。
Python版本分析
这段代码是一个 ROS 节点的 Python 实现示例,它演示了如何使用 rospy
创建一个简单的发布者(Publisher)。让我们逐步解释这段代码,并从中归纳 ROS 程序的基本框架。
代码解释
-
脚本启动行:
#!/usr/bin/env python
这行告诉你的系统如何找到 Python 解释器。它是 Unix 和类 Unix 系统中的标准技巧,用于指定解释器路径。
-
导入模块:
import rospy from std_msgs.msg import String
这里导入了
rospy
模块,它是 ROS 的 Python 客户端库,以及从std_msgs
包中导入String
消息类型。 -
定义 talker 函数:
def talker():...
定义了一个名为
talker
的函数,它是节点的主体。 -
创建发布者:
pub = rospy.Publisher('chatter', String, queue_size=10)
创建了一个发布者对象
pub
,它将发布String
类型的消息到名为chatter
的话题上。 -
初始化节点:
rospy.init_node('talker', anonymous=True)
初始化一个名为
talker
的 ROS 节点。设置anonymous=True
可以确保节点有一个唯一的名称,避免名称冲突。 -
设置循环频率:
rate = rospy.Rate(10) # 10hz
设置一个循环率为 10Hz 的
Rate
对象。 -
节点的主循环:
while not rospy.is_shutdown():...
在 ROS 没有关闭的情况下循环执行以下操作。
-
创建和发布消息:
hello_str = "hello world %s" % rospy.get_time() rospy.loginfo(hello_str) pub.publish(hello_str)
在每次循环中,创建一个含有当前时间的字符串消息,记录这个消息,并将其发布到
chatter
话题。 -
维持循环率:
rate.sleep()
使循环以设定的 10Hz 频率运行。
-
脚本主入口:
if __name__ == '__main__':talker()
当脚本直接运行时,调用
talker
函数。
ROS 程序的基本框架
从这个示例中,我们可以归纳出 ROS 程序的基本框架:
- 导入必要的 ROS 模块和消息类型。
- 定义节点的主要功能(如发布或订阅)。
- 初始化 ROS 节点。
- 创建 ROS 通信接口的实例(发布者、订阅者、服务服务器或客户端)。
- 使用循环(对于持续运行的任务)来执行节点的功能,比如发布消息。
- 在脚本的主部分调用这个功能,以确保当脚本被执行时,节点开始运行。
这种结构提供了 ROS 程序开发的标准方法,可以应用于创建各种类型的节点,无论是发布者、订阅者、服务的提供者还是客户端。
CmakeLists.txt
在ROS(机器人操作系统)中,CMakeLists.txt
文件是一个非常重要的组成部分,用于定义构建ROS包的过程。这个文件是基于CMake,一个跨平台的安装(构建)系统。下面我将详细解释如何修改 CMakeLists.txt
文件,并给出一个例子。
CMakeLists.txt
文件的基本结构
一个典型的 CMakeLists.txt
文件包括以下部分:
- 基本的CMake命令:指定CMake的最低版本要求和项目名称。
- 查找依赖项:使用
find_package()
命令查找编译和运行所需的包。 - 设置全局属性:例如,包含目录和C++标准。
- 定义消息、服务和动作:如果你的包定义了自定义的消息、服务或动作。
- 声明ROS节点和库:使用
add_executable()
和add_library()
声明你的可执行文件和库。 - 添加依赖关系:使用
add_dependencies()
确保所有消息和服务在你的节点之前生成。 - 指定链接库:使用
target_link_libraries()
指定链接到你的可执行文件或库的库。
当然,我会将这些详细的代码解释整合到之前关于 CMakeLists.txt
文件的解释中。
CMakeLists.txt
文件的基本结构和详细解释
在ROS(机器人操作系统)中,CMakeLists.txt
文件用于定义构建ROS包的过程。它是基于CMake,一个跨平台的安装(构建)系统。这个文件的基本结构包括以下部分,我将逐一解释每一部分的作用:
-
基本的CMake命令:
cmake_minimum_required(VERSION 3.0.2) project(my_ros_package)
cmake_minimum_required
指定了CMake的最低版本要求,确保构建过程的兼容性。project
设置了项目的名称,用于在后续的脚本中引用整个项目。
-
查找依赖项:
find_package(catkin REQUIRED COMPONENTSroscpprospystd_msgs )
find_package
用于查找并设置用于项目构建的外部项目(包),这里是catkin
和一些基本的ROS组件。
-
设置全局属性:
catkin_package(CATKIN_DEPENDS roscpp rospy std_msgs )
catkin_package
定义了关于包依赖的元数据,如其他catkin项目依赖的组件。
-
包含目录:
include_directories(include${catkin_INCLUDE_DIRS} )
include_directories
添加包含目录,确保编译时可以找到项目和依赖的头文件。
-
定义消息、服务和动作:如果你的包定义了自定义的消息、服务或动作,你需要在这里指定它们。这个例子中没有涉及。
-
声明ROS节点和库:
add_executable(publisher_node src/publisher.cpp) target_link_libraries(publisher_node ${catkin_LIBRARIES})add_executable(subscriber_node src/subscriber.cpp) target_link_libraries(subscriber_node ${catkin_LIBRARIES})
add_executable
定义了可执行文件(节点),如发布者和订阅者。target_link_libraries
指定了链接到你的可执行文件的库。
-
添加依赖关系和指定链接库:通常这些步骤是通过
add_dependencies
和target_link_libraries
实现的,确保所有消息和服务在你的节点之前生成,并且正确链接必要的库。
修改 CMakeLists.txt
的例子
假设你需要添加一个新的依赖,如 sensor_msgs
,你可以这样修改:
find_package(catkin REQUIRED COMPONENTSroscpprospystd_msgssensor_msgs # 新增依赖
)catkin_package(CATKIN_DEPENDS roscpp rospy std_msgs sensor_msgs # 新增依赖
)
如果你要添加一个新的节点:
add_executable(another_node src/another_node.cpp)
target_link_libraries(another_node ${catkin_LIBRARIES})
通过这些步骤,你可以根据需要修改你的 CMakeLists.txt
文件来适应你的ROS项目。每当你添加新文件或修改依赖关系时,都需要更新这个文件。
ros::init()
在 ROS(Robot Operating System,机器人操作系统)中,ros::init()
函数是初始化ROS节点的重要步骤。这个函数负责设置节点的名称,并且处理ROS与命令行参数有关的所有事务。下面我将详细解释 ros::init()
的功能,并提供一个实例。
ros::init()
函数的作用
-
初始化节点: 在ROS中,每个可执行文件都被视为一个节点。
ros::init()
初始化这个节点,并允许它与ROS Master进行通信。 -
处理命令行参数: 这个函数解析传递给ROS节点的命令行参数(例如,节点名称、ROS Master的URI等)。
-
设置节点名称: 它为节点设置一个唯一的名称,这是节点在ROS网络中被识别的方式。
-
初始化节点的通信功能: 设置节点与ROS系统其他部分的通信功能,包括与ROS Master的通信。
ros::init()
函数的使用
ros::init()
有几种不同的重载形式。最常见的形式是接受命令行参数和节点名称。例如:
int main(int argc, char **argv) {ros::init(argc, argv, "my_node_name");// ...
}
在这个例子中:
argc
和argv
是传递给main
函数的标准命令行参数。"my_node_name"
是你要为这个节点指定的名称。
这样,当你的节点启动时,它会注册到ROS Master,并被指定为 my_node_name
。
实例
假设我们要创建一个简单的ROS节点,该节点定期打印一条消息。我们的程序将包括以下几个步骤:
- 初始化ROS节点。
- 创建节点句柄。
- 设置循环频率。
- 在循环中发布消息。
实现代码如下:
#include <ros/ros.h>int main(int argc, char **argv) {// Step 1: Initialize the ROS noderos::init(argc, argv, "talker");// Step 2: Create a node handleros::NodeHandle nh;// Step 3: Set loop frequencyros::Rate loop_rate(10); // 10 Hzwhile (ros::ok()) {// Step 4: Publish a message (e.g., to a topic)ROS_INFO("Hello, ROS!");// ...// Here you might publish a message to a topic// ...ros::spinOnce();loop_rate.sleep();}return 0;
}
在这个例子中:
- 我们使用
ros::init
初始化了一个名为 “talker” 的节点。 - 创建了一个
ros::NodeHandle
对象,这是与ROS系统交互的主要途径。 - 使用
ros::Rate
设置了循环频率。 - 在
while
循环中,使用ROS_INFO
打印消息,并可能向某个主题发布消息。 ros::spinOnce()
允许ROS处理任何挂起的回调,loop_rate.sleep()
保持循环频率。
这是一个非常基础的ROS程序结构,演示了如何初始化节点并在循环中执行任务。在实际应用中,你可能会添加更多的功能,如订阅主题、提供服务等。
ros::ok()
在 ROS(Robot Operating System,机器人操作系统)中,ros::ok()
函数是一个非常重要的工具,用于检查ROS节点的状态。这个函数在ROS程序中经常被用来控制循环和确保优雅地关闭节点。下面我将详细解释 ros::ok()
的功能,并提供一个实例。
ros::ok()
函数的作用
-
检查节点状态:
ros::ok()
返回一个布尔值,表示ROS节点的状态。如果返回true
,表示节点正在正常运行。如果返回false
,则意味着节点应该被关闭。 -
响应外部中断:它能够检测到如
Ctrl+C
这样的用户中断,或者ROS系统要求节点关闭的信号。 -
处理异常和错误:如果ROS遇到异常或错误(例如,无法与ROS Master通信),
ros::ok()
也会返回false
。
使用 ros::ok()
的实例
让我们考虑一个简单的ROS节点,它在一个循环中执行任务,直到接收到关闭信号或遇到错误。以下是实现的基本结构:
#include <ros/ros.h>int main(int argc, char **argv) {// 初始化ROS节点ros::init(argc, argv, "my_ros_node");// 创建节点句柄ros::NodeHandle nh;// 循环频率ros::Rate loop_rate(10); // 10 Hzwhile (ros::ok()) {// 执行一些任务// 例如,读取传感器数据、计算、发布消息等ROS_INFO("Node is running...");// 其他ROS相关操作// ...ros::spinOnce(); // 处理任何挂起的ROS事件loop_rate.sleep(); // 根据设定的频率休眠}// 如果ros::ok() 返回false,退出循环ROS_INFO("Node is shutting down...");return 0;
}
在这个例子中:
- 使用
ros::init
初始化了一个名为 “my_ros_node” 的节点。 - 创建了一个
ros::NodeHandle
对象。 - 设置了循环频率为10 Hz。
while (ros::ok())
循环确保只要节点正常运行,循环就会继续。- 在循环内部,你可以执行任何必要的任务,例如读取传感器数据、计算或发布ROS消息。
ros::spinOnce()
允许ROS处理任何挂起的回调,loop_rate.sleep()
保持循环频率。- 一旦
ros::ok()
返回false
,循环终止,程序可以优雅地关闭。
这个结构在ROS程序中非常常见,因为它提供了一种简单而有效的方法来持续执行任务,同时保持对系统状态的检查,确保在需要时能够正确地关闭节点。