文章目录

前言

马上开学,目前学校很多实验室都是人工智能这块,大部分都是和机器人相关,然后软件这块就是和cv、ros相关,就打算开始学习一下。

本章节是虚拟机安装Ubuntu18.04以及安装ROS的环境。

学习教程:​​【Autolabor初级教程】ROS机器人入门​​,博客中一些知识点是来源于赵老师的笔记在线笔记,本博客主要是做归纳总结,如有侵权请联系删除。

视频中的案例都基本敲了遍,这里给出我自己的源代码文件:

链接:https://pan.baidu.com/s/13CAzXk0vAWuBsc4oABC-_g 
提取码:0hws

所有博客文件目录索引:​​博客目录索引(持续更新)​

一、Action通信

1.1、认识Action通信

介绍

技术产生缘由:若是某一个请求需要处理的时间过程,也就是说响应因为处理时间长导致未能快速响应返回,就可能出现程序"假死"的现象。

  • 比较合理的效果:导航过程中,可以连续反馈当前机器人状态信息,当导航终止时,再返回最终的执行结果。

Action通信特点:在一个请求响应过程中间添加连续反馈及响应。

原理

​action​​结构:

ROS学习笔记09、ROS进阶(Action通信、动态参数、pluginlib、nodelet)_ros

​action​​通信接口图解:

ROS学习笔记09、ROS进阶(Action通信、动态参数、pluginlib、nodelet)_学习_02

  • goal:目标任务;
  • cacel:取消任务;
  • status:服务端状态;
  • result:最终执行结果(只会发布一次);
  • feedback:连续反馈(可以发布多次)

1.2、实践

新建工程包:

# 进入到工程下的src目录
cd /home/workspace/roslearn/src

# 创建名为demo_action的包

自定义Action文件

ROS学习笔记09、ROS进阶(Action通信、动态参数、pluginlib、nodelet)_c++_03

​AddInts.action​​:

#目标值goal
int32 num
---
#最终结果result
int32 result
---
#连续反馈Feedback

接着配置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目录

ROS学习笔记09、ROS进阶(Action通信、动态参数、pluginlib、nodelet)_c++_04

C++调用文件:在devel/include目录

ROS学习笔记09、ROS进阶(Action通信、动态参数、pluginlib、nodelet)_服务端_05

Python调用文件:在devel/lib目录

ROS学习笔记09、ROS进阶(Action通信、动态参数、pluginlib、nodelet)_学习_06

action通信实现(C++)

服务端

ROS学习笔记09、ROS进阶(Action通信、动态参数、pluginlib、nodelet)_服务端_07

​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服务节点

ROS学习笔记09、ROS进阶(Action通信、动态参数、pluginlib、nodelet)_ros_08

# 查看话题列表

ROS学习笔记09、ROS进阶(Action通信、动态参数、pluginlib、nodelet)_服务端_09

测试

客户端测试

接着我们可以尝试去使用命令行像/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"

此时在服务端就可以收到消息:

ROS学习笔记09、ROS进阶(Action通信、动态参数、pluginlib、nodelet)_ros_10

获取反馈数据响应:也就是一个消息在完成前可以拿到的数据反馈

source ./devel/setup.bash

# 接收连续反馈数据
rostopic echo

ROS学习笔记09、ROS进阶(Action通信、动态参数、pluginlib、nodelet)_c++_11

  • 其中progress_bar就是不断连续反馈得到的结果值。

客户端

ROS学习笔记09、ROS进阶(Action通信、动态参数、pluginlib、nodelet)_ros_12

​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服务节点

ROS学习笔记09、ROS进阶(Action通信、动态参数、pluginlib、nodelet)_ros_13

# 启动client节点来进行发送数据

ROS学习笔记09、ROS进阶(Action通信、动态参数、pluginlib、nodelet)_ros_14

ROS学习笔记09、ROS进阶(Action通信、动态参数、pluginlib、nodelet)_机器人_15

二、动态参数

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)

打开rqt:

rqt

ROS学习笔记09、ROS进阶(Action通信、动态参数、pluginlib、nodelet)_机器人_16

选择其中``plugins->configuration->Dynamic Reconfigure`,接着我们来修改inflation_radius的值即可,只要修改好配置之后,可以看到rviz中的地图效果也发生了改变!

ROS学习笔记09、ROS进阶(Action通信、动态参数、pluginlib、nodelet)_ros_17

2.2、实践动态参数

动态参数客户端

客户端实现流程:

  • 新建并编辑 .cfg 文件;
  • 编辑CMakeLists.txt;
  • 编译。

新建功能包:

# 进入到工程下的src目录
cd /home/workspace/roslearn/src

# 创建名为demo_dynamicparameter的包

步骤一:新建cfg文件夹与创建cfg配置文件

ROS学习笔记09、ROS进阶(Action通信、动态参数、pluginlib、nodelet)_c++_18

​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++的头文件

ROS学习笔记09、ROS进阶(Action通信、动态参数、pluginlib、nodelet)_机器人_19

python的配置文件

ROS学习笔记09、ROS进阶(Action通信、动态参数、pluginlib、nodelet)_学习_20


动态参数服务端(C++)

服务端实现流程:

  • 新建并编辑 c++ 文件;
  • 编辑CMakeLists.txt;
  • 编译并执行。

ROS学习笔记09、ROS进阶(Action通信、动态参数、pluginlib、nodelet)_机器人_21

​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服务节点

我们可以来进行在线测试,来对一些参数来进行修改,看我们的服务端是否能够进行响应:

# 快捷打开 或者rqt来指定打开
rosrun rqt_gui rqt_gui -s rqt_reconfigure

ROS学习笔记09、ROS进阶(Action通信、动态参数、pluginlib、nodelet)_学习_22

修改某个参数,此时就会在客户端有响应:

ROS学习笔记09、ROS进阶(Action通信、动态参数、pluginlib、nodelet)_ros_23

三、pluginlib

3.1、认识pluginlib

介绍pluginlib是一个c++库, 用来从一个ROS功能包中加载和卸载插件(plugin)。插件是指从运行时库中动态加载的类。通过使用Pluginlib,不必将某个应用程序显式地链接到包含某个类的库,Pluginlib可以随时打开包含类的库,而不需要应用程序事先知道包含类定义的库或者头文件。

  • pluginlib直译是插件库,所谓插件字面意思就是可插拔的组件。

基本原理:就是通过规范化的USB接口协议实现计算机与USB设备的自由组合。同理,在软件编程中,插件是一种遵循一定规范的应用程序接口编写出来的程序,插件程序依赖于某个应用程序,且应用程序可以与不同的插件程序自由组合。

实际场景

1.导航插件:在导航中,涉及到路径规划模块,路径规划算法有多种,也可以自实现,导航应用时,可能需要测试不同算法的优劣以选择更合适的实现,这种场景下,ROS中就是通过插件的方式来实现不同算法的灵活切换的。

2.rviz插件:在rviz中已经提供了丰富的功能实现,但是即便如此,特定场景下,开发者可能需要实现某些定制化功能并集成到rviz中,这一集成过程也是基于插件的。

3.2、实操

实现流程:

  1. 准备;
  2. 创建基类;
  3. 创建插件类;
  4. 注册插件;
  5. 构建插件库;
  6. 使插件可用于ROS工具链;
  • 配置xml
  • 导出插件
  1. 使用插件;
  2. 执行。

3.2.1、注册插件

# 进入到工程下的src目录
cd /home/workspace/roslearn/src

# 创建名为demo_pluginlib的包

ROS学习笔记09、ROS进阶(Action通信、动态参数、pluginlib、nodelet)_c++_24

​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文件。

ROS学习笔记09、ROS进阶(Action通信、动态参数、pluginlib、nodelet)_ros_25

ROS学习笔记09、ROS进阶(Action通信、动态参数、pluginlib、nodelet)_服务端_26

3.2.2、将构建的插件用于ROS工具链

接下来工作:将插件用于ROS工具链。

ROS学习笔记09、ROS进阶(Action通信、动态参数、pluginlib、nodelet)_学习_27

​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

ROS学习笔记09、ROS进阶(Action通信、动态参数、pluginlib、nodelet)_服务端_28

若是返回 .xml 文件的完整路径,这意味着插件已经正确的集成到了ROS工具链。


3.3.3、实际使用插件

ROS学习笔记09、ROS进阶(Action通信、动态参数、pluginlib、nodelet)_服务端_29

​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

ROS学习笔记09、ROS进阶(Action通信、动态参数、pluginlib、nodelet)_ros_30

四、nodelet

4.1、认识nodelet

解决问题:实际问题就是在不同进程之间数据交互产生的延时与阻塞问题。

  • 不同Node之间数据交互其实是不同进程之间的数据交互,当传输类似于图片、点云的大容量数据时,会出现延时与阻塞的情况,

实际场景:现在需要编写一个相机驱动,在该驱动中有两个节点实现:其中节点A负责发布原始图像数据,节点B订阅原始图像数据并在图像上标注人脸。如果节点A与节点B仍按照之前实现,两个节点分别对应不同的进程,在两个进程之间传递容量可观图像数据,可能就会出现延时的情况,那么该如何优化呢?

Nodelet介绍

  1. nodelet软件包旨在提供在同一进程中运行多个算法(节点)的方式,不同算法之间通过传递指向数据的指针来代替了数据本身的传输(类似于编程传值与传址的区别),从而实现零成本的数据拷贝。
  2. 通过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

实操命令+效果截图演示

打开多个窗口,其中没有划横线的就是测试窗口:

ROS学习笔记09、ROS进阶(Action通信、动态参数、pluginlib、nodelet)_c++_31

首先启动``roscore`。

接着来启动管理器:

rosrun nodelet nodelet manager __name:=mymanager

ROS学习笔记09、ROS进阶(Action通信、动态参数、pluginlib、nodelet)_服务端_32

此时我们来启动节点1:

rosrun nodelet nodelet load nodelet_tutorial_math/Plus mymanager __name:=n1 _value:=100

ROS学习笔记09、ROS进阶(Action通信、动态参数、pluginlib、nodelet)_服务端_33

此时我们来看一看主题节点的情况:

rostopic list

ROS学习笔记09、ROS进阶(Action通信、动态参数、pluginlib、nodelet)_学习_34

接着我们来测试节点1,尝试向节点1的主题/n1/in来发送数据,接着去打印/n1/out的输出信息:

rostopic pub -r 10 /n1/in std_msgs/Float64 "data: 10.0"

rostopic echo

ROS学习笔记09、ROS进阶(Action通信、动态参数、pluginlib、nodelet)_机器人_35

ROS学习笔记09、ROS进阶(Action通信、动态参数、pluginlib、nodelet)_c++_36

节点1的输入与输出都正常,来启动节点2:

rosrun nodelet nodelet load nodelet_tutorial_math/Plus mymanager __name:=n2 _value:=-50 /n2/in:=/n1/out

ROS学习笔记09、ROS进阶(Action通信、动态参数、pluginlib、nodelet)_c++_37

接着我们来看节点1的out是否转发到了结点2的in:

rostopic echo

ROS学习笔记09、ROS进阶(Action通信、动态参数、pluginlib、nodelet)_学习_38

ok,最终我们演示结束!


4.3、自定义代码实操(实现4.2的节点通信效果)

实现目的:与4.2效果一致,能够实现中间数相加进行数据传递。

4.3.1、创建工程包

# 进入到工程下的src目录
cd /home/workspace/roslearn/src

# 创建名为demo_nodelet的包

4.3.2、自定义构建插件库(基于nodelet)

ROS学习笔记09、ROS进阶(Action通信、动态参数、pluginlib、nodelet)_ros_39

​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文件:

ROS学习笔记09、ROS进阶(Action通信、动态参数、pluginlib、nodelet)_机器人_40

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

上述的完整测试代码实际与4.2中调用工具包是效果一致的,这里不再重复演示效果!