ROS节点介绍
- 在ROS的世界里,最小的进程单元就是节点(node)
- 一个软件包里可以有多个可执行文件,可执行文件在运行之后就成了一个进程(process),这个进程在ROS中就叫做节点
- 从程序角度来说,node就是一个可执行文件(通常为C++编译生成的可执行文件、Python脚本)被执行,加载到了内存之中
- 从功能角度来说,通常一个node负责者机器人的某一个单独的功能。由于机器人的功能模块非常复杂,我们往往不会把所有功能都集中到一个node上,而会采用分布式的方式,把鸡蛋放到不同的篮子里
- 例如有一个node来控制底盘轮子的运动,有一个node驱动摄像头获取图像,有一个node驱动激光雷达,有一个node根据传感器信息进行路径规划,这样做可以降低程序发生崩溃的可能性,试想一下如果把所有功能都写到一个程序中,模块间的通信、异常处理将会很麻烦。
主节点
- 由于机器人的元器件很多,功能庞大,因此实际运行时往往会运行众多的node,负责感知世界、控制运动、决策和计算等功能。
- 那么如何合理的进行调配、管理这些node?这就要利用ROS提供给我们的节点管master,master在整个网络通信架构里相当于管理中心,管理着各个node
- node首先master处进行注册,之后master会将该node纳入整个ROS程序中
- node之间的通信也是先由master进行“牵线”,才能两两的进行点对点通信
- 当ROS程序启动时,第一步首先启动master,由节点管理器处理依次启动node
节点和主节点之间的关系
基本命令
roscore
- 启动ros主节点,在运行ros程序之前都必须运行该命令启动主节点
- 运行
roscore
时ROS master启动,同时启动的还有 rosout 和 parameter server ,- 其中rosout 是负责日志输出的一个节点,其作用是告知用户当前系统的状态,包括输出系统的error、warning等等,并且将log记录于日志文件中
- parameter server 即是参数服务器,它并不是一个node,而是存储参数配置的一个服务器
- 每一次我们运行ROS的节点前,都需要把master启动起来,这样才能够让节点启动和注册
rosrun
- 运行某一节点
- 用法:
rosrun [--prefix cmd] [--debug] pkg_name node_name [ARGS]
rosrun
将会寻找PACKAGE下的名为EXECUTABLE
的可执行程序,将可选参数ARGS
传入- 例如在GDB下运行ros程序:
rosrun --prefix 'gdb -ex run --args' pkg_name node_name
rosnode
# 列出当前运行的node信息
rosnode list
# 显示出node的详细信息
rosnode info node_name
# 结束某个node
rosnode kill node_name
# 测试连接节点
rosnode ping
# 列出在特定机器或列表机器上运行的节点
rosnode machine
# 清除不可到达节点的注册信息
rosnode cleanup
# 查看rosnode命令的用法
rosnode help
编写和运行ROS节点
编写节点
# 在home目录下运行以下命令创建一个名字为ros_workspace的工作空间
$ mkdir –p ros_workspace/src
# 切换到工作工件目录
$ cd ros_workspace
# 编译工作空间
$ catkin_make
# 设置环境变量
$ echo “source ~/ros_workspace/devel/setup.bash” >> ~/.bashrc
$ source ~/.bashrc
# 切换到/src目录,创建一个test_pkg的功能包
$ catkin_create_pkg test_pkg std_msgs rospy roscpp
注意:
ROS 包的命名遵循一个命名规范,只允许使用小写字母、数字和下划线,而且首字符必须是一个小写字母。一些 ROS工具,包括 catkin,不支持那些不遵循此命名规范的包
ROS node头文件
- 头文件
- 头文件类型有gencpp模块根据msg定义自动生成
- std_msgs/String.h
- 使用rosmsg 查看具体内容
ROS node 基本结构
ros::init()
:初始化ros::Publisher
:发布消息ros::Subscriber
:订阅消息ros::Rate loop_rate
:循环速率ros::spin()
:事件循环ros::spinOnce()
:单次事件ros::start()
:启动ros::shutdown()
:关闭
ros::init()
- 通过调用
ros::init()
函数来初始化node- 此函数向ROS系统传递命令行参数、定义node名字及其它参数
- 在调用roscpp其它函数前必须先调用
ros::init()
,其函数原型为:
ros::Publisher
将节点设置成发布者,并将所发布主题的类型和名称告知节点管理器
ros::Subscriber
将节点设置成接收者,并将所接收主题的类型和名称告知节点管理器
主循环
- 循环条件
ros::ok()
: 当接受到ctrl+c 信号 或者ros::shutdown()
调用时,为false,终止运行节点- 主循环中,使用
ros::rate
实现将循环一次的时间控制在一个我们设置的一个周期内ROS_INFO()
;
操作小记
操作内容
Hello World
- 节点:
- 节点talker:包含一个发布者
- 节点间的通讯:
- Topic(话题的名称):“chatter”
- Message(传递的数据类型):std_msgs::String
- String为 ROS自定义的数据类型
- String中包含唯一变量为 std::string data
编写节点测试代码
编译软件包
我们主要需要修改CMakeLists.txt这个编译配置文件
编译
当修改完CMakeLists.txt文件后 就可以在工作空间的根目录下使用catkin_make
进行编译了
运行节点
当编译过程没有错误时,接下来就可以使用rosrun命令来启动节点了
如何添加头文件和库文件
如何引用自定义头文件
引用当前软件包内的头文件
在编写代码时我们经常会需要引用头文件,引用公用的头文件很容易,因为它们已经在标准库头文件路径中。但 如果要引用自定义的头文件就稍微麻烦点,我们首先查看软件包的目录结构,需要在正确的目录下创建头文件并修改CMakeLists.txt文件这样才能正确编译,下面来举例说明:
在test_pkg.h内写入以下内容
修改源码文件,引用自定义头文件
修改CMakeLists.txt文件
运行效果
引用同一工作空间中其他软件包的头文件
在一些情况我们需要引用其他软件包中提供的函数或宏定义,这样可以一定程度上减少我们在两个节点之间需要进行通信的话题个数,下面我们通过举例来进行说明:
创建一个功能包
新建相关文件及文件夹
修改源码文件
修改两个软件包的编译配置文件
运行效果
如何引用第三方库文件
我们经常在开发软件包时需要引入第三方的so动态库,但是将其放到系统默认库路径中或者使用绝对路径比较简单省事。但是这样的话我们软件包的移植性就会变差,当需要移植到其他机器上时需要重新配置该软件包的 依赖库路径
手工自己创建测试用的动态库
我们创建一个简单的动态库,里面只有一个简单的函数来计算两个输入整数的乘积并返回,我们首先创建multiply.cpp和multiply.h两个文件
multiply.cpp
multiply.h
接下来准备开始将multiply.cpp编译成为动态库libmultiply.so供我们测试用,注意-shared
参数和-fPIC
参数很重要:
-shared
:告诉gcc要生成的是动态链接库;-fPIC
:告诉gcc生成的代码是非位置依赖的,方便用于动态链接。
将编译好的动态库复制到当前软件包对应的目录下
编辑代码和配置文件来引用动态库
修改编译配置文件,编译后运行效果演示