Nodelet
Nodelet旨在提供一种在单机器单进程运行多个算法而不会在进程中传递消息时产生复制成本的方法。
为什么需要Nodelet
1、ROS是一种基于分布式网络通讯的操作系统,整个机器人控制系统是由一个Master主节点和若干个功能相对独立的Node子节点组成,这也是ROS系统最主要的特点就是分布式以及模块化的设计。在ROS通讯过程中Master节点存储着各个子节点的topics和services的注册信息,每个功能节点在请求服务之前先向主节点进行注册,然后节点之间就可以直接进行信息传递。ROS的底层通信都是基于XML-RPC协议实现的。
XML-RPC协议是XML Remote Prodecure Call的简称,是一种远程过程调用的分布式网络协议。它允许跨平台的软件间通过发送和接收XML格式的消息进行远程调用,即允许不同的操作系统、不同环境中的程序实现基于Internet过程调用的规范和一系列方法的实现。这种远程过程调用使用http作为传输协议,XML作为传送信息的编码格式。
XML-RPC的远程调用过程为:首先客户端发起请求后需要按照协议格式对请求信息进行填充;填充完毕以后XML格式的信息会被转化为数据流,通过传输层进行传输。服务端收到客户端发出来的数据流,会将其再转化为XML格式的信息,然后按照XML-RPC协议获取客户端的请求信息,并对请求信息进行处理,处理完毕以后将反馈信息发送给客户端。
以XML-RPC的方式传输数据存在一定的延时和阻塞。在数据量小、频率低的情况下,传输耗费的时间可以忽略不计。但当传输图像流,点云等数据量较大的消息,或者执行有一定的实时性要求的任务时,因传输而耗费的时间就不得不考虑。Nodelet包就是为改善这一状况设计的,它提供一种方法,可以让多个算法程序在一个进程中用 shared_ptr 实现零拷贝通信(zero copy transport),以降低因为传输大数据而损耗的时间。简单讲就是可以将多个node捆绑在一起管理,使得同一个manager里面的topic的数据传输更快。
以上内容摘自:ROS nodelet的使用
一、Nodelet基本用法
1、nodelet语法
2、启动nodelet manager管理器
#__name:=nodelet_manager 表示将管理器重命名为nodelet_manager
rosrun nodelet nodelet manager __name:=nodelet_manager
运行结果:
3、启动nodelet节点
#load nodelet_tutorial_math/Plus 表示启动这个节点
#后面接着nodelet_manager,表示本节点加入到名为nodelet_manager的nodelet管理器
#__name:=nodelet1 重命名本节点
#nodelet1/in:=foo _value:=1.1 传参
rosrun nodelet nodelet load nodelet_tutorial_math/Plus nodelet_manager __name:=nodelet1 nodelet1/in:=foo _value:=1.1
4、查看当前运行的节点
rosnode list
5、 测试nodelet1
刚刚启动的nodelet1节点的功能是:接受“foo”这个topic的消息,收到消息后,将该值与给定初始值1.1相加,然后输出
启动一个终端,用来监nodelet1的输出
rostopic echo /nodelet1/out
下面使用rostopic发布话题,进行测试
rostopic pub /foo std_msgs/Float64 5.0
执行之后,可以看到,输出值为6.1
二、实现一个Nodelet(C++)
1、概述
Nodelet节点与传统的ros节点有点不一样,不一样在于,节点的源文件cpp里面并没有main函数。
其特点是:是一个类(Class)的形式
所以,编译的时候,并不是编译成可执行文件,而是编译成库文件
2、项目文件树
最终的Package文件树大概长这样
3、创建Package
在catkin_ws/src目录下,创建package
catkin_create_pkg base_nodelet
分别创建src、plugins、launch文件夹
4、在src目录下,开始编写源代码
cd src
nodeletclass1.h文件
#include <nodelet/nodelet.h>
class nodeletclass1 :public nodelet::Nodelet //继承父类nodelet::Nodelet
{
public:
nodeletclass1(); //构造函数,可有可无?
public:
virtual void onInit(); //这个虚函数,在启动本Nodelet节点时,自动调用
};
nodeletclass1.cpp文件
#include "nodeletclass1.h"
#include <pluginlib/class_list_macros.h>
#include <ros/ros.h>
nodeletclass1::nodeletclass1()
{
}
//重载虚函数,启动时自动调用
void nodeletclass1::onInit()
{
//输出信息
NODELET_DEBUG("Init nodelet...");
ROS_INFO("Nodelet is OK for test");
}
//nodelet的本质是把节点作为插件来调用,因此需要PLUGINLIB的宏定义、
//第一个参数是类名,第二个参数是父类
PLUGINLIB_EXPORT_CLASS(nodeletclass1, nodelet::Nodelet);
5、进入plugins文件夹,创建插件的引用xml文件
cd plugins
nodelet_plugins.xml
<!--这里的path="",修改成path="lib/lib{项目名}",
项目名就是CMakeLists.txt里面定义的project(base_nodelet)
我这里就是path="lib/libbase_nodelet"
-->
<library path="lib/libbase_nodelet" >
<!-- name: launch文件里面 load 后面接着的插件名
type: c++文件定义的类名
如 name="aaa/nodeletclass1",那么,launch文件对应启动如下:
<node pkg="nodelet" type="nodelet" name="nodeletclass1"
args="load aaa/nodeletclass1 nodelet_manager" output="screen">
-->
<class name="aaa/nodeletclass1" type="nodeletclass1" base_class_type="nodelet::Nodelet">
<description>
This is my nodelet.
</description>
</class>
</library>
6、回到项目根目录
cd base_nodelet
修改package.xml
加入内容:
<build_depend>nodelet</build_depend>
<build_depend>roscpp</build_depend>
<exec_depend>nodelet</exec_depend>
<exec_depend>roscpp</exec_depend>
在<export></export>标签中间加入如下内容:
<nodelet plugin="${prefix}/plugins/nodelet_plugins.xml" />
修改完成后的package.xml内容如下:
<?xml version="1.0"?>
<package format="2">
<name>base_nodelet</name>
<version>0.1.0</version>
<description>The base_nodelet package</description>
<maintainer email="msi@todo.todo">msi</maintainer>
<license>Apache 2.0</license>
<buildtool_depend>catkin</buildtool_depend>
<!-- 插入内容 -->
<build_depend>nodelet</build_depend>
<build_depend>roscpp</build_depend>
<exec_depend>nodelet</exec_depend>
<exec_depend>roscpp</exec_depend>
<!-- 指定nodelet插件xml路径 -->
<export>
<nodelet plugin="${prefix}/plugins/nodelet_plugins.xml" />
</export>
</package>
7、编写CMakeLists.txt
与普通的ros节点对比,nodelet节点的CMakeLists.txt:
- 不生成二进制文件,即去掉
add_executable(${PROJECT_NAME} src/nodeletclass1.cpp)
- 编译成库形式
add_library(${PROJECT_NAME} src/nodeletclass1.cpp)
- 增加
add_dependencies(${PROJECT_NAME} ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS} )
完整的CMakeLists.txt如下:
cmake_minimum_required(VERSION 2.8.3)
project(base_nodelet)
#启用c++11
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
# List C++ dependencies on ros packages
set(ROS_CXX_DEPENDENCIES
roscpp
nodelet
)
# Find catkin and all required ROS components
find_package(catkin REQUIRED COMPONENTS
${ROS_CXX_DEPENDENCIES}
)
# Set include directories
include_directories(include)
INCLUDE_DIRECTORIES(/opt/ros/kinetic/include/)
catkin_package(
#INCLUDE_DIRS include
#CATKIN_DEPENDS ${ROS_CXX_DEPENDENCIES}
)
#Create node
#add_executable(${PROJECT_NAME} src/nodeletclass1.cpp)
add_library(${PROJECT_NAME} src/nodeletclass1.cpp)
add_dependencies(${PROJECT_NAME} ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS} )
target_link_libraries(${PROJECT_NAME} ${catkin_LIBRARIES})
8、编写lanuch文件
进入launch目录
cd launch
创建nodeletclass1.launch
<launch>
<node pkg="nodelet" type="nodelet" name="nodelet_manager" args="manager" output="screen"/>
<node pkg="nodelet" type="nodelet" name="nodeletclass1" args="load aaa/nodeletclass1 nodelet_manager" output="screen">
</node>
</launch>
- launch文件启动了两个节点
- 一个是nodelet manager,并重命名为 nodelet_manager
- 另外一个则是编写完成的Nodelet节点插件
9、编译、测试
回到工作空间根目录
catkin_make
编译完成后,
source devel/setup.bash
启动launch文件
roslaunch base_nodelet nodeletclass1.launch
最终看到调用了虚函数onInit()打印输出