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


    前言

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

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

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

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

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

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

    一、Action通信

    1.1、认识Action通信

    介绍

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

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

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

    原理

    action结构:

    image-20220919210738590

    action通信接口图解:

    image-20220919210803265

    • 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文件

    image-20220918191644374

    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目录

    image-20220918191909497

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

    image-20220918192133295

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

    image-20220918192234062

    action通信实现(C++)

    服务端

    image-20220918201520662

    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 execute_callback, 
                        bool auto_start)  
            参数1:nodehandle
            参数2:action服务名称
            参数3:回调函数
            参数4:是否自动启动
        */
        // actionlib::SimpleActionServer 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
    

    image-20220918201555538

    # 查看话题列表
    rostopic list
    

    image-20220918195019841

    测试

    客户端测试

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

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

    image-20220918201018365

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

    source ./devel/setup.bash
    
    # 接收连续反馈数据
    rostopic echo /addInts/feedback
    

    image-20220918201259221

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

    客户端

    image-20220918202921373

    demo01_action_client.cpp

    #include "ros/ros.h"
    #include "actionlib/client/simple_action_client.h"
    #include "demo_action/AddIntsAction.h"
    
    //封装
    typedef actionlib::SimpleActionClient 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 done_cb, 
                boost::function active_cb, 
                boost::function 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
    

    image-20220918203240856

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

    image-20220918202908902

    image-20220918203334017

    二、动态参数

    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
    

    image-20220919211413916

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

    image-20220919211611959

    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配置文件

    image-20220918204320520

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

    image-20220918204615202

    python的配置文件

    image-20220918204653984


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

    服务端实现流程:

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

    image-20220918210944913

    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 server;
        //2、创建回调函数(使用回调函数,打印修改后的参数)
        dynamic_reconfigure::Server::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
    

    image-20220918211435347

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

    image-20220918211452275

    三、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
      • 导出插件
    7. 使用插件;
    8. 执行。

    3.2.1、注册插件

    # 进入到工程下的src目录
    cd /home/workspace/roslearn/src
    
    # 创建名为demo_pluginlib的包
    catkin_create_pkg --rosdistro melodic demo_pluginlib roscpp pluginlib
    

    202209182139621

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

    image-20220918212848307

    image-20220918213129671

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

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

    image-20220918215400313

    plus.xml:编写我们对应的libplus以及相应的实现类、基类

    
    <library path="lib/libplus">
      
      <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>
        
        
        <demo_pluginlib plugin="${prefix}/plus.xml" />
    export>
    

    接下来我们就可以使用catkin_make来进行编译整个工程,编译完成之后我们可以来使用rospack工具来检查下是否已经将插件集成到了ROS工具链:

    rospack help
    
    # demo_pluginlib就是当前的包名
    rospack plugins --attrib=plugin demo_pluginlib
    

    image-20220918215734991

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


    3.3.3、实际使用插件

    image-20220918221027686

    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 loader("demo_pluginlib", "dbx_base_ns::Dbx_Base");
    
        //2、使用类加载实例化某个插件对象
        //注册插件:san
        boost::shared_ptr san = loader.createInstance("dbx_plugins_ns::SanBian");
    
        //3、使用插件
        san->init(10);
        double length = san->getLength();
        ROS_INFO("三角形周长:%.2f", length);
    
        //测试:注册插件 sibian
        boost::shared_ptr 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
    

    image-20220918221123967

    四、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 /n2/out
    

    实操命令+效果截图演示

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

    image-20220919204956583

    首先启动``roscore`。

    接着来启动管理器:

    rosrun nodelet nodelet manager __name:=mymanager
    

    image-20220919205310528

    此时我们来启动节点1:

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

    image-20220919205327940

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

    rostopic list
    

    image-20220919205510875

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

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

    image-20220919205539566

    image-20220919205557420

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

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

    image-20220919205711825

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

    rostopic echo /n2/out
    

    image-20220919205731428

    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)

    image-20220919180048159

    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("out", 100);//话题名称:/节点名/out
                    //订阅(in就是传入的数据,value就是本身发布结点时存储参数服务器的数据)
                    sub = nh.subscribe("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文件:

    image-20220919180448061

    4.3.3、插件作用ROS工具链

    将插件应用于Ros工具链:

    在包目录下创建配置文件:myplus.xml

    <library path="lib/libmyplus" >
        
        <class type="my_nodelet::MyPlus" base_class_type="nodelet::Nodelet" name="demo_nodelet/Myplus">
                <description>我的nodelet测试节点description>
        class>
    library>
    

    修改package.xml:

    <export>
        
        <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中调用工具包是效果一致的,这里不再重复演示效果!

  • 相关阅读:
    创建线程并启动-创建线程的前三种方式
    sql 注入(4), 盲注
    js第八章
    单向链表(c/c++)
    C++赋值\拷贝\构造\初始化\...总结
    c#事件(event)
    GeoServer扩展功能之发布矢量瓦片
    JavaScript数组常用方法解析和深层次js数组扁平化
    java 的jar打包方式(exe安装版,免安装版(zip包里有exe和jre),bat双击运行版本),运行在没有jre环境中
    DELTA热金属检测器维修V5G-JC-R1激光测量传感器/检测仪原理分析
  • 原文地址:https://blog.csdn.net/cl939974883/article/details/126962853