前言
马上开学,目前学校很多实验室都是人工智能这块,大部分都是和机器人相关,然后软件这块就是和cv、ros相关,就打算开始学习一下。
本章节是虚拟机安装Ubuntu18.04以及安装ROS的环境。
学习教程:【Autolabor初级教程】ROS机器人入门,博客中一些知识点是来源于赵老师的笔记在线笔记,本博客主要是做归纳总结,如有侵权请联系删除。
视频中的案例都基本敲了遍,这里给出我自己的源代码文件:
链接:https://pan.baidu.com/s/13CAzXk0vAWuBsc4oABC-_g
提取码:0hws
所有博客文件目录索引:博客目录索引(持续更新)
一、Action通信
1.1、认识Action通信
介绍
技术产生缘由:若是某一个请求需要处理的时间过程,也就是说响应因为处理时间长导致未能快速响应返回,就可能出现程序"假死"的现象。
- 比较合理的效果:导航过程中,可以连续反馈当前机器人状态信息,当导航终止时,再返回最终的执行结果。
Action通信特点:在一个请求响应过程中间添加连续反馈及响应。
原理
action
结构:
action
通信接口图解:
- goal:目标任务;
- cacel:取消任务;
- status:服务端状态;
- result:最终执行结果(只会发布一次);
- feedback:连续反馈(可以发布多次)
1.2、实践
新建工程包:
# 进入到工程下的src目录
cd /home/workspace/roslearn/src
# 创建名为demo_action的包
catkin_create_pkg --rosdistro melodic demo_action roscpp rospy std_msgs actionlib actionlib_msgs
自定义Action文件
AddInts.action
:
#目标值goal
int32 num
---
#最终结果result
int32 result
---
#连续反馈Feedback
float64 progress_bar
接着配置CMakeLists.txt:
find_package(catkin REQUIRED COMPONENTS
actionlib
actionlib_msgs
roscpp
rospy
std_msgs
)
add_action_files(
FILES
AddInts.action
)
generate_messages(
DEPENDENCIES
std_msgs
actionlib_msgs
)
catkin_package(
# INCLUDE_DIRS include
# LIBRARIES demo_action
CATKIN_DEPENDS actionlib actionlib_msgs roscpp rospy std_msgs
# DEPENDS system_lib
)
此时我们来编译整个工程文件,目的就是来得到一些消息的结构体文件以及C++、Python的调用文件。
消息结构体文件:包含之前Action文件的一些数据信息,拆分编译得到了多个结构体供之后进行调用,在share目录
C++调用文件:在devel/include目录
Python调用文件:在devel/lib目录
action通信实现(C++)
服务端
demo01_action_server.cpp
:
#include "ros/ros.h"
#include "actionlib/server/simple_action_server.h"
#include "demo_action/AddIntsAction.h"
//封装
typedef actionlib::SimpleActionServer<demo_action::AddIntsAction> Server;
void cb(const demo_action::AddIntsGoalConstPtr &goal, Server* server) {
//获取到目标值
int num = goal->num;
ROS_INFO("目标值:%d", num);
//累加来进行连续反馈
int result = 0;
demo_action::AddIntsFeedback feedback;//用于连续反馈的消息对象
ros::Rate rate(10);
for (int i = 0; i <= num; i++) {
result += i;
//进度数据
feedback.progress_bar = i / (double)num;
//组织连续数据发布
server->publishFeedback(feedback);
rate.sleep();
}
//最终处理结果后发送最终结果集
demo_action::AddIntsResult r;
r.result = result;
server->setSucceeded(r);
ROS_INFO("最终结果为:%d", r.result);
}
int main(int argc, char *argv[])
{
setlocale(LC_ALL, "");
ROS_INFO("action服务端启动...");
//1、初始化ros节点
ros::init(argc, argv, "AddInts_server");
//2、初始化NodeHandle
ros::NodeHandle nh;
//3、创建action服务对象
/* SimpleActionServer(ros::NodeHandle n,
std::string name,
boost::function<void (const demo01_action::AddIntsGoalConstPtr &)> execute_callback,
bool auto_start)
参数1:nodehandle
参数2:action服务名称
参数3:回调函数
参数4:是否自动启动
*/
// actionlib::SimpleActionServer<demo_action::AddIntsAction> server
Server server(nh, "addInts", boost::bind(&cb, _1, &server), false);
server.start();
//4、处理请求,进行反馈与响应
ros::spin();
return 0;
}
运行节点:
# 启动roscore核心
roscore
source ./devel/setup.bash
# 运行server服务节点
rosrun demo_action demo01_action_server
# 查看话题列表
rostopic list
测试
客户端测试:
接着我们可以尝试去使用命令行像/addInts/goal使用指定的结构体来发布消息:
source ./devel/setup.bash
# rostopic pub 主题发布 /addInts/goal表示主题目标 demo_action/AddIntsActionGoal消息结构体
# 最后就消息结构
rostopic pub /addInts/goal demo_action/AddIntsActionGoal "header:
seq: 0
stamp:
secs: 0
nsecs: 0
frame_id: ''
goal_id:
stamp:
secs: 0
nsecs: 0
id: ''
goal:
num: 100"
此时在服务端就可以收到消息:
获取反馈数据响应:也就是一个消息在完成前可以拿到的数据反馈
source ./devel/setup.bash
# 接收连续反馈数据
rostopic echo /addInts/feedback
- 其中progress_bar就是不断连续反馈得到的结果值。
客户端
demo01_action_client.cpp
:
#include "ros/ros.h"
#include "actionlib/client/simple_action_client.h"
#include "demo_action/AddIntsAction.h"
//封装
typedef actionlib::SimpleActionClient<demo_action::AddIntsAction> Client;
//最终处理结果函数
//参数:结果状态对象 结果集
void done_cb (const actionlib::SimpleClientGoalState &state, const demo_action::AddIntsResultConstPtr &result) {
if (state.state_ == state.SUCCEEDED) {
ROS_INFO("最终结果为:%d", result->result);
}else {
ROS_INFO("任务失败!");
}
}
//服务激活
void active_cb() {
ROS_INFO("服务已被激活!");
}
//处理连续反馈
void feedback_cb (const demo_action::AddIntsFeedbackConstPtr &feedback) {
ROS_INFO("当前进度:%.2f", feedback->progress_bar);
}
int main(int argc, char *argv[])
{
setlocale(LC_ALL, "");
ros::init(argc, argv, "AddInts_client");
ros::NodeHandle nh;
//创建action客户端对象
// SimpleActionClient(ros::NodeHandle & n, const std::string & name, bool spin_thread = true)
Client client(nh, "addInts", true);
//等待服务启动
client.waitForServer();
//发送目标,处理反馈以及最终结果
/*
void sendGoal(const demo01_action::AddIntsGoal &goal,
boost::function<void (const actionlib::SimpleClientGoalState &state, const demo01_action::AddIntsResultConstPtr &result)> done_cb,
boost::function<void ()> active_cb,
boost::function<void (const demo01_action::AddIntsFeedbackConstPtr &feedback)> feedback_cb)
*/
demo_action::AddIntsGoal goal;
goal.num = 10;
//向服务发送数据
client.sendGoal(goal, &done_cb, &active_cb, &feedback_cb);
//回调函数处理
ros::spin();
return 0;
}
修改CMakeLists.txt配置:
add_executable(demo01_action_client src/demo01_action_client.cpp)
add_dependencies(demo01_action_client ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})
target_link_libraries(demo01_action_client
${catkin_LIBRARIES}
)
编译项目,接着先启动服务节点,在运行client节点:
# 启动roscore核心
roscore
source ./devel/setup.bash
# 运行server服务节点
rosrun demo_action demo01_action_server
# 启动client节点来进行发送数据
rosrun demo_action demo01_action_client
二、动态参数
2.1、认识动态参数
此前出现描述:再此之前我们学习的param server,每次去get得到相应的参数值之后,该参数值就已经直接保存到了内存,若是我们此时去修改param server的参数值,那么此时原本程序之前获取到的值并没有能够进行刷新,除非重启才有效果!
实际效果:实时动态更新参数。
应用场景:全局地图中就使用到了动态参数,可使用rqt来进行动态修改。
原理分析:动态配置参数,之所以能够实现即时更新,因为被设计成 CS 架构,客户端修改参数就是向服务器发送请求,服务器接收到请求之后,读取修改后的是参数。
2.2、实际应用演示效果
案例效果演示:全局地图动态参数配置修改。
借助于之前的案例,我们来直接启动仿真环境:
# 启动gazebo(第7章节部分的,复用之前的)
roslaunch 07urdf_gazebo demo03_evn.launch
# 深度图像转激光数据
roslaunch 08nav_demo nav07_depthimage_to_laserscan.launch
# 运行路径规划测试launch(地图加载、amcl、movebase、rviz)
roslaunch 08nav_demo nav05_path_test.launch
打开rqt:
rqt
选择其中``plugins->configuration->Dynamic Reconfigure`,接着我们来修改inflation_radius的值即可,只要修改好配置之后,可以看到rviz中的地图效果也发生了改变!
2.2、实践动态参数
动态参数客户端
客户端实现流程:
- 新建并编辑 .cfg 文件;
- 编辑CMakeLists.txt;
- 编译。
新建功能包:
# 进入到工程下的src目录
cd /home/workspace/roslearn/src
# 创建名为demo_dynamicparameter的包
catkin_create_pkg --rosdistro melodic demo_dynamicparameter roscpp rospy std_msgs dynamic_reconfigure
步骤一:新建cfg文件夹与创建cfg配置文件
dr.cfg
:本质就是python代码,主要用于生成对应的消息结构体
#! /usr/bin/env python
# coding=utf-8
# 1.导包
from dynamic_reconfigure.parameter_generator_catkin import *
PACKAGE = "demo_dynamicparameter"
# 2.创建生成器
gen = ParameterGenerator()
# 3.向生成器添加若干参数
#add(name, paramtype, level, description, default=None, min=None, max=None, edit_method="")
gen.add("int_param" ,int_t ,0 ,"整型参数" ,50 ,0 ,100)
gen.add("double_param",double_t,0,"浮点参数",1.57,0,3.14)
gen.add("string_param",str_t,0,"字符串参数","hello world ")
gen.add("bool_param",bool_t,0,"bool参数",True)
many_enum = gen.enum([gen.const("small",int_t,0,"a small size"),
gen.const("mediun",int_t,1,"a medium size"),
gen.const("big",int_t,2,"a big size")
],"a car size set")
gen.add("list_param",int_t,0,"列表参数",0,0,2, edit_method=many_enum)
# 4.生成中间文件并退出
# generate(pkgname, nodename, name)
# 分别是包名、节点名以及cfg名称
exit(gen.generate(PACKAGE,"dr_client","dr"))
步骤二:修改CMakeLists.txt文件
generate_dynamic_reconfigure_options(
cfg/dr.cfg
)
接着我们编译整个项目,此时就会根据我们的cfg文件来进行生成相应的动态参数客户端。
编译得到的文件内容如下:
c++的头文件
python的配置文件:
动态参数服务端(C++)
服务端实现流程:
- 新建并编辑 c++ 文件;
- 编辑CMakeLists.txt;
- 编译并执行。
demo_dr_server.cpp
·:
#include "ros/ros.h"
#include "dynamic_reconfigure/server.h"
#include "demo_dynamicparameter/drConfig.h"
//回调函数
void cb (demo_dynamicparameter::drConfig& config, uint32_t level) {
ROS_INFO("动态参数解析数据:%d,%.2f,%d,%s,%d",
config.int_param,
config.double_param,
config.bool_param,
config.string_param.c_str(),
config.list_param
);
}
int main(int argc, char *argv[])
{
setlocale(LC_ALL, "");
ros::init(argc, argv, "dr");
//1、创建服务器对象(demo_dynamicparameter::drConfig)
dynamic_reconfigure::Server<demo_dynamicparameter::drConfig> server;
//2、创建回调函数(使用回调函数,打印修改后的参数)
dynamic_reconfigure::Server<demo_dynamicparameter::drConfig>::CallbackType cbType;
cbType = boost::bind(&cb, _1, _2);
//3、服务器对象采用回调函数
server.setCallback(cbType);
//4、回旋回调函数
ros::spin();
return 0;
}
修改CMakeLists.txt配置文件:
add_executable(demo_dr_server src/demo_dr_server.cpp)
add_dependencies(demo_dr_server ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})
target_link_libraries(demo_dr_server
${catkin_LIBRARIES}
)
接着编译整个项目,运行结点:
# 启动roscore核心
roscore
source ./devel/setup.bash
# 运行server服务节点
rosrun demo_dynamicparameter demo_dr_server
我们可以来进行在线测试,来对一些参数来进行修改,看我们的服务端是否能够进行响应:
# 快捷打开 或者rqt来指定打开
rosrun rqt_gui rqt_gui -s rqt_reconfigure
修改某个参数,此时就会在客户端有响应:
三、pluginlib
3.1、认识pluginlib
介绍:pluginlib是一个c++库, 用来从一个ROS功能包中加载和卸载插件(plugin)。插件是指从运行时库中动态加载的类。通过使用Pluginlib,不必将某个应用程序显式地链接到包含某个类的库,Pluginlib可以随时打开包含类的库,而不需要应用程序事先知道包含类定义的库或者头文件。
- pluginlib直译是插件库,所谓插件字面意思就是可插拔的组件。
基本原理:就是通过规范化的USB接口协议实现计算机与USB设备的自由组合。同理,在软件编程中,插件是一种遵循一定规范的应用程序接口编写出来的程序,插件程序依赖于某个应用程序,且应用程序可以与不同的插件程序自由组合。
实际场景:
1.导航插件:在导航中,涉及到路径规划模块,路径规划算法有多种,也可以自实现,导航应用时,可能需要测试不同算法的优劣以选择更合适的实现,这种场景下,ROS中就是通过插件的方式来实现不同算法的灵活切换的。
2.rviz插件:在rviz中已经提供了丰富的功能实现,但是即便如此,特定场景下,开发者可能需要实现某些定制化功能并集成到rviz中,这一集成过程也是基于插件的。
3.2、实操
实现流程:
- 准备;
- 创建基类;
- 创建插件类;
- 注册插件;
- 构建插件库;
- 使插件可用于ROS工具链;
- 配置xml
- 导出插件
- 使用插件;
- 执行。
3.2.1、注册插件
# 进入到工程下的src目录
cd /home/workspace/roslearn/src
# 创建名为demo_pluginlib的包
catkin_create_pkg --rosdistro melodic demo_pluginlib roscpp pluginlib
dbx_base.h
:基类编写,定义两个方法,之后的实现类去实现相应的方法
#ifndef DBX_BASE_H
#define DBX_BASE_H
namespace dbx_base_ns {
class Dbx_Base {
protected:
Dbx_Base() {}
public:
//计算周长的函数
virtual double getLength() = 0;
//初始化边长的函数
virtual void init(double side_length) = 0;
};
}
#endif
dbx_plugins.h
:两个实现类
#ifndef DBX_PLUGINS_H
#define DBX_PLUGIN_H
#include "demo_pluginlib/dbx_base.h"
namespace dbx_plugins_ns {
//三边
class SanBian: public dbx_base_ns::Dbx_Base {
private:
double side_length;
public:
SanBian(){
side_length = 0.0;
}
void init(double size_length) {
this->side_length = side_length;
}
double getLength() {
return side_length * 3;
}
};
//四边
class SiBian: public dbx_base_ns::Dbx_Base {
private:
double side_length;
public:
SiBian(){
side_length = 0.0;
}
void init(double size_length) {
this->side_length = side_length;
}
double getLength() {
return side_length * 4;
}
};
}
#endif
plus.cpp
:注册插件
#include "pluginlib/class_list_macros.h"
#include "demo_pluginlib/dbx_base.h"
#include "demo_pluginlib/dbx_plugins.h"
//参数1:子类 参数2:父类
PLUGINLIB_EXPORT_CLASS(dbx_plugins_ns::SanBian, dbx_base_ns::Dbx_Base)
PLUGINLIB_EXPORT_CLASS(dbx_plugins_ns::SiBian, dbx_base_ns::Dbx_Base)
接着我们来修改CMakeLists.txt:
include_directories(
include
${catkin_INCLUDE_DIRS}
)
# 指定我们的plus.cpp文件
add_library(plus
src/plus.cpp
)
- 上面放开的include就是我们之前在include目录下添加的文件内容。
接着我们来编译整个项目,该文件会将两个衍生类注册为插件,即生成相关的.so文件。
3.2.2、将构建的插件用于ROS工具链
接下来工作:将插件用于ROS工具链。
plus.xml
:编写我们对应的libplus以及相应的实现类、基类
<!-- 插件库的相对路径
path:指的是devel/lib/libplus.so
-->
<library path="lib/libplus">
<!-- type="插件类" base_class_type="基类"
命名空间:相应类名
-->
<class type="dbx_plugins_ns::SanBian" base_class_type="dbx_base_ns::Dbx_Base">
<!-- 描述信息 -->
<description>这是一个三边类</description>
</class>
<class type="dbx_plugins_ns::SiBian" base_class_type="dbx_base_ns::Dbx_Base">
<description>这是一个四边类</description>
</class>
</library>
在package.xml中进行导出插件配置:
<export>
<!-- Other tools can request additional information be placed here -->
<!-- 标签名指的是包名,plugin就是对应的插件目录 -->
<demo_pluginlib plugin="${prefix}/plus.xml" />
</export>
接下来我们就可以使用catkin_make来进行编译整个工程,编译完成之后我们可以来使用rospack工具来检查下是否已经将插件集成到了ROS工具链:
rospack help
# demo_pluginlib就是当前的包名
rospack plugins --attrib=plugin demo_pluginlib
若是返回 .xml 文件的完整路径,这意味着插件已经正确的集成到了ROS工具链。
3.3.3、实际使用插件
use_plugin.cpp
:使用类加载来进行加载插件
#include "ros/ros.h"
#include "pluginlib/class_loader.h"
#include "demo_pluginlib/dbx_base.h"
int main(int argc, char *argv[])
{
setlocale(LC_ALL, "");
//1、创建类加载器
pluginlib::ClassLoader<dbx_base_ns::Dbx_Base> loader("demo_pluginlib", "dbx_base_ns::Dbx_Base");
//2、使用类加载实例化某个插件对象
//注册插件:san
boost::shared_ptr<dbx_base_ns::Dbx_Base> san = loader.createInstance("dbx_plugins_ns::SanBian");
//3、使用插件
san->init(10);
double length = san->getLength();
ROS_INFO("三角形周长:%.2f", length);
//测试:注册插件 sibian
boost::shared_ptr<dbx_base_ns::Dbx_Base> si = loader.createInstance("dbx_plugins_ns::SiBian");
si->init(10);
length = si->getLength();
ROS_INFO("四边形周长:%.2f", length);
return 0;
}
编译整个工程,然后去运行节点:
source ./devel/setup.bash
rosrun demo_pluginlib use_plugin
四、nodelet
4.1、认识nodelet
解决问题:实际问题就是在不同进程之间数据交互产生的延时与阻塞问题。
- 不同Node之间数据交互其实是不同进程之间的数据交互,当传输类似于图片、点云的大容量数据时,会出现延时与阻塞的情况,
实际场景:现在需要编写一个相机驱动,在该驱动中有两个节点实现:其中节点A负责发布原始图像数据,节点B订阅原始图像数据并在图像上标注人脸。如果节点A与节点B仍按照之前实现,两个节点分别对应不同的进程,在两个进程之间传递容量可观图像数据,可能就会出现延时的情况,那么该如何优化呢?
Nodelet介绍:
- nodelet软件包旨在提供在同一进程中运行多个算法(节点)的方式,不同算法之间通过传递指向数据的指针来代替了数据本身的传输(类似于编程传值与传址的区别),从而实现零成本的数据拷贝。
- 通过Nodelet可以将多个节点集成进一个进程。
核心实现:同样也是插件,是对插件的进一步封装。
- 不同算法被封装进插件类,可以像单独的节点一样运行;
- 在该功能包中提供插件类实现的基类:Nodelet;
- 并且提供了加载插件类的类加载器:NodeletLoader。
效果:应用于大容量数据传输的场景,提高节点间的数据交互效率,避免延时与阻塞。
4.2、命令行演示—基于nodelet_tutorial_math/Plus
效果:通过两个节点来实现数据相加并进行通信。
完整命令行展示
roscore
# 启动节点服务,命名为mymanager
rosrun nodelet nodelet manager __name:=mymanager
# 测试节点1
# nodelet_tutorial_math/Plus:指的是运行的指定节点插件
# __name:=n1:表示设置别名n1,对应发布的主题为/n1/in、/n1/out
# _value:=100:向param服务器中存储key为value,value值为100
rosrun nodelet nodelet load nodelet_tutorial_math/Plus mymanager __name:=n1 _value:=100
rostopic list
# 向节点1按照一定的频率(n1/in,相当于入口)来发布消息
rostopic pub -r 10 /n1/in std_msgs/Float64 "data: 10.0"
# 看对应n1的出口是否有响应值
rostopic echo /n1/out
# 测试节点2
# __name:=n2:表示设置别名n2,对应发布的主题为/n2/in、/n2/out
# _value:=-50:向param服务器中存储key为value,value值为-50
rosrun nodelet nodelet load nodelet_tutorial_math/Plus mymanager __name:=n2 _value:=-50 /n2/in:=/n1/out
rostopic list
# 来看n2的出口的结果值是否正确
rostopic echo /n2/out
实操命令+效果截图演示
打开多个窗口,其中没有划横线的就是测试窗口:
首先启动``roscore`。
接着来启动管理器:
rosrun nodelet nodelet manager __name:=mymanager
此时我们来启动节点1:
rosrun nodelet nodelet load nodelet_tutorial_math/Plus mymanager __name:=n1 _value:=100
此时我们来看一看主题节点的情况:
rostopic list
接着我们来测试节点1,尝试向节点1的主题/n1/in来发送数据,接着去打印/n1/out的输出信息:
rostopic pub -r 10 /n1/in std_msgs/Float64 "data: 10.0"
rostopic echo /n1/out
节点1的输入与输出都正常,来启动节点2:
rosrun nodelet nodelet load nodelet_tutorial_math/Plus mymanager __name:=n2 _value:=-50 /n2/in:=/n1/out
接着我们来看节点1的out是否转发到了结点2的in:
rostopic echo /n2/out
ok,最终我们演示结束!
4.3、自定义代码实操(实现4.2的节点通信效果)
实现目的:与4.2效果一致,能够实现中间数相加进行数据传递。
4.3.1、创建工程包
# 进入到工程下的src目录
cd /home/workspace/roslearn/src
# 创建名为demo_nodelet的包
catkin_create_pkg --rosdistro melodic demo_nodelet roscpp nodelet
4.3.2、自定义构建插件库(基于nodelet)
myplus.cpp
:编写代码
#include "ros/ros.h"
#include "nodelet/nodelet.h"
#include "pluginlib/class_list_macros.h"
#include "std_msgs/Float64.h"
namespace my_nodelet {
class MyPlus: public nodelet::Nodelet {
private:
ros::Publisher pub;
ros::Subscriber sub;
double value;
public:
MyPlus(){
value = 0.0;
}
void onInit() {
ROS_INFO("MyPlus init ...");
//2、获取NodeHandle
ros::NodeHandle nh = getPrivateNodeHandle();
//3、通过NodeHandle创建订阅对象和发布对象,解析参数
nh.getParam("value", value);
//发布
pub = nh.advertise<std_msgs::Float64>("out", 100);//话题名称:/节点名/out
//订阅(in就是传入的数据,value就是本身发布结点时存储参数服务器的数据)
sub = nh.subscribe<std_msgs::Float64>("in", 100, &MyPlus::cb, this);
//4、订阅对象的回调函数需要处理数据,并通过发布对象发布
}
//处理订阅的回调函数
void cb(const std_msgs::Float64::ConstPtr& p) {
//1、解析订阅的数据
double in = p->data;
//2、和参数相加
double out = in + value;
//3、发布
std_msgs::Float64 out_msg;
out_msg.data = out;
pub.publish(out_msg);
}
};
}
PLUGINLIB_EXPORT_CLASS(my_nodelet::MyPlus, nodelet::Nodelet)
构建插件库,修改CMakeLists.txt:
add_library(myplus
src/myplus.cpp
)
target_link_libraries(myplus
${catkin_LIBRARIES}
)
此时进行编译,就会在lib目录下得到一个so文件:
4.3.3、插件作用ROS工具链
将插件应用于Ros工具链:
在包目录下创建配置文件:myplus.xml
<library path="lib/libmyplus" >
<!--
class.type表示的是:命名空间::类。myplus.cpp中最后一行PLUGINLIB_EXPORT_CLASS(my_nodelet::MyPlus, nodelet::Nodelet),中的【my_nodelet::MyPlus】
base_class_type:就是固定的:nodelet::Nodelet
name:一般是包名/类名
-->
<class type="my_nodelet::MyPlus" base_class_type="nodelet::Nodelet" name="demo_nodelet/Myplus">
<description>我的nodelet测试节点</description>
</class>
</library>
修改package.xml:
<export>
<!-- 标签名为基类,plugin表示为路径 -->
<nodelet plugin="${prefix}/myplus.xml" />
</export>
此时来去进行编译,即可应用于工具类。
4.3.4、最终测试
完整测试代码:
# 测试工具链(需要去查基类的工具包),若是返回相应的xml就没有问题
rospack plugins --attrib=plugin nodelet
rosrun nodelet nodelet manager __name:=mymanager
# 测试节点1
# load后指定的在myplus的配置文件中来进行引入(demo_nodelet/Myplus)
rosrun nodelet nodelet load demo_nodelet/Myplus mymanager __name:=plus _value:=-50
rostopic list
rostopic pub -r 10 /plus/in std_msgs/Float64 "data: 10.0"
# 测试节点2
# 预计:-40
rostopic echo /plus/out
rosrun nodelet nodelet load demo_nodelet/Myplus mymanager __name:=plus2 _value:=100 /plus2/in:=/plus/out
rostopic list
# 预计:60
rostopic echo /plus2/out
上述的完整测试代码实际与4.2中调用工具包是效果一致的,这里不再重复演示效果!