ROS nodelet 介绍

ROS的数据通信在graph结构中以topic,service和param的方式传输数据,数据交互存在一定的延时和阻塞。Nodelet 包就是为改善这一状况设计的, 使得多个算法运行在同一个进程中,并且算法间数据传输无需拷贝就可实现。 详见http://wiki.ros.org/nodelet。 简单的讲就是可以将以前启动的多个node捆绑在一起manager,使得同一个manager里面的topic的数据传输更快,数据通讯中roscpp采用boost shared pointer方式进行publish调用,实现zero copy。Nodelet是借助pluginlib来实现插件动态加载的,文章pluginlib特别通俗易懂的说明了pluginlib的工作原理,在理解pluginlib的基础上看nodelet会更容易。

说明

nodelet的工作原理类似于pluginlib,可以参考上边所说的博客中的原理图。

实现一个nodelet插件类

nodelet的实现因为借助了pluginlib的插件动态加载,因此nodelet的实现遵循pluginlib的使用规则,主要步骤如下:

  1. 创建基类,定义统一的接口。如果是基于现有的基类,则不需要这个步骤
  2. 创建nodelet插件类,继承基类,实现统一的接口
  3. 注册nodelet插件类
  4. 编译生成插件的动态链接库
  5. 将nodelet加入ROS系统

接下来,我们就根据这几个步骤来实现一个简单的nodelet功能,在开始之前需要建立一个example_pkg的功能包。

1
catkin_create_pkg example_pkg

完整的功能包代码可以在github上下载。

创建基类

其中基类nodelet是现有的,所以我们不需要创建基类,只需要继承nodelet基类即可。

创建nodelet插件类并注册插件类

接下来我们来创建MyNodeletClass类(MyNodeletClass.h)定义,放置于目录example_pkg/include/example_pkg/下。

1
2
3
4
5
6
7
8
9
10
11
12
#include <nodelet/nodelet.h>
namespace example_pkg
{

class MyNodeletClass : public nodelet::Nodelet
{
public:
virtual void onInit();
//要求构造函数不能带有参数,所以调用OnInit来完成需要初始化的工作
};

}

创建MyNodeletClass类实现(MyNodeletClass.cpp),放置于目录example_pkg/src/下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// this should really be in the implementation (.cpp file)
#include <ros/ros.h>
#include <pluginlib/class_list_macros.h>
#include <example_pkg/MyNodeletClass.h>


namespace example_pkg
{

void MyNodeletClass::onInit()
{
NODELET_DEBUG("Initializing nodelet...");
ROS_INFO("Nodelet is Ok for test!!");
}
}

// 注册插件类
PLUGINLIB_DECLARE_CLASS(example_pkg, MyNodeletClass, example_pkg::MyNodeletClass, nodelet::Nodelet)

创建插件类的描述符(nodelet_plugins.xml),放置于目录example_pkg/plugins/下。

1
2
3
4
5
6
7
<library path="lib/libexample_pkg">
<class name="example_pkg/MyNodeletClass" type="example_pkg::MyNodeletClass" base_class_type="nodelet::Nodelet">
<description>
This is my nodelet.
</description>
</class>
</library>

可以看到,这个xml文件主要描述了nodelet的动态库路径,实现类,基类,描述等信息。

编译插件的动态连接库并将插件加入ROS系统

为了编译插件的功能包,需要修改CMakeLists.txt文件,修改一下内容,将插件编译为动态连接库。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
cmake_minimum_required(VERSION 2.8.3)
project(example_pkg)

## Find catkin macros and libraries
## if COMPONENTS list like find_package(catkin REQUIRED COMPONENTS xyz)
## is used, also find other catkin packages
find_package(catkin REQUIRED COMPONENTS nodelet roscpp)
find_package(Boost REQUIRED)

catkin_package(
INCLUDE_DIRS include
LIBRARIES ${PROJECT_NAME}
CATKIN_DEPENDS nodelet roscpp
)

###########
## Build ##
###########

## Specify additional locations of header files
## Your package locations should be listed before other locations
include_directories(
include
${catkin_INCLUDE_DIRS}
${Boost_INCLUDE_DIRS}
)

## Declare a C++ library
add_library(${PROJECT_NAME} src/MyNodeletClass.cpp)

add_dependencies(${PROJECT_NAME}
${${PROJECT_NAME}_EXPORTED_TARGETS}
${catkin_EXPORTED_TARGETS}
)

target_link_libraries(${PROJECT_NAME}
${catkin_LIBRARIES}
)

对package.xml文件进行修改,添加构建和运行依赖项(nodelet和roscpp)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<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>


<!-- The export tag contains other, unspecified, tags -->
<export>
<!-- Other tools can request additional information be placed here -->
<nodelet plugin="${prefix}/plugins/nodelet_plugins.xml" />
</export>

然后我们可以通过下面的命令来查看该功能包是否编译为nodelet的插件:

1
rospack plugins --attrib=plugin nodelet

如果没有问题,会出现一系列nodelet的插件路径,其中应该有上边添加的插件的路径,我的插件路径为:

1
2
3
4
5
...
/opt/ros/kinetic/share/pcl_ros/pcl_nodelets.xml
/home/username/catkin_turtlebot/src/example_pkg/plugins/nodelet_plugins.xml #创建的插件路径
/opt/ros/kinetic/share/image_publisher/nodelet_plugins.xml
...

插件的路径比较多,没有全列出来。

编写启动文件(mynodelet.launch):

1
2
3
4
5
6
<launch>
<node pkg="nodelet" type="nodelet" name="standalone_nodelet" args="manager" output="screen"/>

<node pkg="nodelet" type="nodelet" name="MyNodeletClass" args="load example_pkg/MyNodeletClass standalone_nodelet" output="screen">
</node>
</launch>

编译并测试

根目录下编译后,运行launch文件,如果没有问题,可以看到如下结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
username@username-HP-ProBook-440-G3:~/catkin_turtlebot$ roslaunch example_pkg mynodelet.launch 
... logging to /home/username/.ros/log/7e42af3c-3b9c-11e8-8278-a86bad0ea915/roslaunch-username-HP-ProBook-440-G3-13823.log
Checking log directory for disk usage. This may take awhile.
Press Ctrl-C to interrupt
Done checking log file disk usage. Usage is <1GB.

started roslaunch server http://username-HP-ProBook-440-G3:35479/

SUMMARY
========

PARAMETERS
* /rosdistro: kinetic
* /rosversion: 1.12.13

NODES
/
MyNodeletClass (nodelet/nodelet)
standalone_nodelet (nodelet/nodelet)

ROS_MASTER_URI=http://localhost:11311

process[standalone_nodelet-1]: started with pid [13840]
process[MyNodeletClass-2]: started with pid [13841]
[ INFO] [1523264216.026517404]: Loading nodelet /MyNodeletClass of type example_pkg/MyNodeletClass to manager standalone_nodelet with the following remappings:
[ INFO] [1523264216.027989431]: waitForService: Service [/standalone_nodelet/load_nodelet] has not been advertised, waiting...
[ INFO] [1523264216.071444856]: Initializing nodelet with 4 worker threads.
[ INFO] [1523264216.090658134]: waitForService: Service [/standalone_nodelet/load_nodelet] is now available.
[ INFO] [1523264216.092255988]: Nodelet is Ok for test!!

成功后会显示Nodelet is Ok for test!!
这样,我们就完成了nodelet插件的实现和调用。

如果我的文章对你有所帮助,那么不妨?