机器人操作系统ROS从入门到放弃(五):在类中发布和接收消息

许久没更新了,放假回家没想到还要被老师催论文,连玩耍的时间都不够= =.好了,闲话少说

例1

上一讲讲了一些类什么的基础.这一讲我们就谈谈怎么在类中发布和接收消息了.当你遇到稍微大型一点的程序时,在类中发布和接收消息才是主流,毕竟不在类中的callback函数返回值能做的有限.代码结构其实很类似,咱们直接先上代码再来找不同.同样以发布一个整型数Int8为例子.
同样先在 pub_sub_test/src中建立一个发布消息和一个接收消息的文件.分别叫pub_int8_class.cpp和sub_int8_class.cpp.在pub_int8_class.cpp中输入以下内容.

#include "ros/ros.h"
#include "std_msgs/Int8.h"

class pubInt8{
public:
   pubInt8(ros::NodeHandle& nh){
       chatter_pub = nh.advertise<std_msgs::Int8>("chatter", 1000); //assign value to chatter_pub
   };

   void pub(){
       int count = 0;
       ros::Rate loop_rate(10);
       while (ros::ok())
       {
           std_msgs::Int8 msg; 

           msg.data = count;// msg.data = ss.str();

           ROS_INFO("%d", msg.data);

           chatter_pub.publish(msg);

           ros::spinOnce();

           loop_rate.sleep();
           ++count;
       }
   }

private:
   ros::Publisher chatter_pub;//set the publisher as a member
};

int main(int argc, char **argv)
{
 ros::init(argc, argv, "talker");

 ros::NodeHandle n;

 pubInt8 p8(n);

 p8.pub();

 return 0;
}

对比我们在第二讲中的pub_int8.cpp中的内容, 总的来说大同小异,来看看不同的地方.
我们把ros::Publisher chatter_pub作为类的一个私有成员成员变量定义在了类中.这时候并没有对它赋值.然后在构造函数中才对他赋值的.
对publisher赋值需要rosNodeHandle,所以我们在创建对象时把rosNodeHandle作为一个参数传入了构造函数,这样我们就可以在构造函数中为chatter_pub赋值.
之后在主函数中我们使用类的pub函数实现信息的发布,发布的部分的代码和pub_int8.cpp中的一模一样.

下面来看看sub_int8_class.cpp

#include "ros/ros.h"
#include "std_msgs/Int8.h" 

class subInt8{
public:
    subInt8(){};
    void chatterCallback(const std_msgs::Int8::ConstPtr& msg){
        ROS_INFO("I heard: [%d]", msg->data);
    }
};

int main(int argc, char **argv)
{
  ros::init(argc, argv, "listener");

  ros::NodeHandle n;

  subInt8 s8;

  ros::Subscriber sub = n.subscribe("chatter", 1000, &subInt8::chatterCallback, &s8);

  ros::spin();

  return 0;
}

同样来看看和sub_int8.cpp不同的地方.
callback函数内容一模一样,只是放到了类中.
在定义subscriber时和不使用类时有很大的不同.
不使用类时,我们是这么写的
ros::Subscriber sub = n.subscribe("chatter", 1000, chatterCallback);
使用类时,我们是这么写的
ros::Subscriber sub = n.subscribe("chatter", 1000, &subInt8::chatterCallback, &s8);

即原来我们只需要把回调函数名作为参数写在n.subscribe中,现在我们需要用&类名::函数名代替原来那个参数,之后还需要增加一个参数,即在定义subscriber之前定义好的类的对象名&对象名

把这两个程序写入CMakeLists中,catkin_make后使用rosrun跑起来.会得到第二讲pub_int8程序一样的效果.
基本的类中发布接收消息就是这样了,注意不要死板,使用要灵活.比如说你写了一个在类中发布消息的程序,你不仅需要在构造函数当中使用rosNodeHandle,还需要在其他函数中使用,怎么做?你可以把ros::NodeHandle也作为一个私有成员(或者公有成员)写在类中,使用构造函数把外来这个nodeHandle传值给私有成员nodeHandle.或者甚至不在构造函数传值,专门写一个register nodehandle的函数

void registerNodeHandle(ros::NodeHandle& _nh){
    nh = _nh; //_nh is a member in class, _nh is the nodehandle you define, for example, in main function.
}

这样nh属于你的类,你就可以在类的其他函数进行任何有关它的操作了.
在类外,你只需要定义一个该类的对象,通过 对象.registerNodeHandle(主函数中(或者其他位置)定义的NodeHandle)把nodehandle传入类中.

例2

为了方便大家运用,我们再写一个例子.ros程序经常出现我们在接收到一个消息之后经过代码中一系列处理再发布给另一个topic.即咱们在同一个文件中既发布消息,又接收来自其他topic(对topic陌生了?翻回第一讲去看看)的消息.发布接收的消息使用double即float64吧.
首先写一个叫pubSub_class_example2A.cpp的程序,代码如下

#include "ros/ros.h"
#include "std_msgs/Float64.h"
#include <unistd.h>

class tl1{
public:
    tl1();
    void registerNodeHandle(ros::NodeHandle& _nh);
    void registerPubSub();
    void iniPub();
    void fCallback(const std_msgs::Float64::ConstPtr& msg);
private:
    ros::Publisher pub_f64;
    ros::Subscriber sub_f64;
    ros::NodeHandle nh;
};

int main(int argc, char **argv){

    ros::init(argc, argv, "talker_listener1");

    ros::NodeHandle nh;

    tl1 pubSub1;

    pubSub1.registerNodeHandle(nh);
    pubSub1.registerPubSub();

    pubSub1.iniPub();

    ros::spin();
}

tl1::tl1(){};

void tl1::registerNodeHandle(ros::NodeHandle& _nh){
    nh = _nh;
};

void tl1::registerPubSub(){
    pub_f64 = nh.advertise<std_msgs::Float64>("chatter1to2",1000);
    sub_f64 = nh.subscribe("chatter2to1",1000,&tl1::fCallback, this); //this pointer here means the class itself
};

//When you receive some data, it is highly possible you do some operations on that data and then publish something in/after the call back function
void tl1::fCallback(const std_msgs::Float64::ConstPtr& msg){
    std::cout<<"talker_listener1 heard "<<msg->data<<std::endl;

    std_msgs::Float64 pubData;

    pubData.data = msg->data - 1;

    ros::Rate sr(10);
    pub_f64.publish(pubData);
    sr.sleep();
};

void tl1::iniPub(){
    std::cout<<"publish the first data "<<std::endl;

    std_msgs::Float64 pubData;

    pubData.data = 1;

    usleep(500000);//wait for connection
    pub_f64.publish(pubData);
}

接下来写一个pubSub_class_example2B.cpp的程序.

#include "ros/ros.h"
#include "std_msgs/Float64.h"

class tl1{
public:
    tl1();
    void registerNodeHandle(ros::NodeHandle& _nh);
    void registerPubSub();
    void fCallback(const std_msgs::Float64::ConstPtr& msg);
private:
    ros::Publisher pub_f64;
    ros::Subscriber sub_f64;
    ros::NodeHandle nh;
};

int main(int argc, char **argv){

    ros::init(argc, argv, "talker_listener2");

    ros::NodeHandle nh;

    tl1 pubSub1;

    pubSub1.registerNodeHandle(nh);
    pubSub1.registerPubSub();

    ros::spin();
}

tl1::tl1(){};

void tl1::registerNodeHandle(ros::NodeHandle& _nh){
    nh = _nh;
};

void tl1::registerPubSub(){
    pub_f64 = nh.advertise<std_msgs::Float64>("chatter2to1",1000);
    sub_f64 = nh.subscribe("chatter1to2",1000,&tl1::fCallback, this); //this pointer here means the class itself
};

//When you receive some data, it is highly possible you do some operations on that data and then publish something in/after the call back function
void tl1::fCallback(const std_msgs::Float64::ConstPtr& msg){
    std::cout<<"talker_listener2 heard "<<msg->data<<std::endl;

    std_msgs::Float64 pubData;

    pubData.data = msg->data + 1;

    ros::Rate sr(10);
    pub_f64.publish(pubData);
    sr.sleep();
};

两个文件代码基本一样,
不同的地方是
1:...B中没有iniPub这个函数.
2:两个程序的node名字和topic名字有些差异,方便建立连接,自行观察,一会儿用rqt_graph就可以更明了地看到.
针对pubSub_class_example2A把代码中比较重要的点说明一下
1:在tl1的类中我们把ros NodeHandle也作为类的成员了,专门谢了一个registerNodeHandle的函数把nodeHandle传递到类中.另外类成员不仅有了publisher还有subscriber.我们也专门写了一个registerPubSub函数来给publisher和subscriber赋值.这样写虽然我们麻烦了一些,但是代码读起来就更加明了.
2:这里注意一下registerPubSub中给sub_f64赋值的方法,最后一个参数是this指针.this指针具体的作用大家去查.我们的第一个例子在类外定义了subscriber,最后一个参数是类的对象,我们要在类中定义subscriber,不可能给一个类的对象了,毕竟这时候我们都还不知道对象叫啥.最流行的说法是this指针指针指向类本身.当我们在类中需要指定类对象的时候,就可以用this指针代替那个我们不知道的对象.
3:回调函数fCallback中我做了一个很无聊的事儿,就是把接收到的数据减1而已.这代表了对你接收到的数据以操作.实际运用中你可能有很复杂的操作.这一系列操作完成后你需要将结果发布出去.我们再以每秒10次的频率发布出去.
4:iniPub函数用来初始化发布一个数据,这样2B程序的subcriber接收到后经过一系列操作(我其实就是加了1==),发布出来再被我们这个程序的subcriber接收,即fCallback接收,"一系列操作"(其实就是减1...)再发布出去,如此循环.
这里注意到有一个usleep(500000)代表休眠500ms,至关重要.两个节点的通过topic建立起来联系是需要时间的.我们这个程序,如果没有休眠一下,可以说程序一跑起来瞬间消息就发布出去了.但是我们接下来要写的另一个节点和这个节点还没连接起来,说白了就是ROS没反应过来.那这个消息就漏掉了.有些程序漏掉一两个消息没关系,我们这个程序第一个消息漏掉了就没有然后了.因为B程序没有接收到消息就没办法发布,A程序就没法接收,也没法继续发布了.
当你一则消息都不想漏掉时让程序睡一会儿,等待节点连接起来再发布消息
把两个代码写进CMakeLists跑起来,注意先跑B程序,如果先跑A程序,因为只休眠了500ms,你A程序iniPub的消息在你慢悠悠地在另一个terminal中跑B程序的时间之前也已经发布出去了并被漏掉了.跑起来后B可以看到一个程序一直接收1,另一个一直接收0.这时候再开一个terminal,像第一讲一样输入rqt_graph,可以看到下图.

pubsub.png

程序A,即节点talker_listener1中通过topic chatter1to2把消息发送给节点talker_listener2,talker_listener2把消息接收到后,在callback函数中一系列操作再通过topic chatter2to1把消息发布回talker_listener1.可以回看代码中两个程序的subcriber和publisher赋值时他们对应的topic名字与这个图联系起来.
大家可以尝试把iniPub中的usleep(500000)注释掉再跑程序,看看会发生什么.

总结

这一讲讲了类中发布和接收消息的方法.1到5讲关于发布接收消息我们也说了这么多了.ROS其实很大程度上就是方便大家通讯各种传感器的东西,所以咱们关于发布接收消息讲了这么多.在类中操作才是使用ROS的主流.其实还有很多细节,以后如果有机会会慢慢说到.没有机会大家通过以后自己写程序出的毛病慢慢领悟hhhhhh.下一讲咱们谈谈使用roslaunch了.针对roslaunch大概就讲两个东西吧,一个当然是roslaunch跑程序的基本方法,另一个是使用roslaunch传递外界参数,这个东西帮了我很多忙.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,222评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,455评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,720评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,568评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,696评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,879评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,028评论 3 409
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,773评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,220评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,550评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,697评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,360评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,002评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,782评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,010评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,433评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,587评论 2 350

推荐阅读更多精彩内容