话题通信的媒介是消息,消息的产生和消费是解偶的,二者之间没有强行绑定的关系,ROS节点不在乎是谁在发布Topic,哪个节点在订阅Topic,它只关心topic的名字以及当前的消息类型时候和要求的匹配。所以说话题通信是多对多的方式
publisher注册时的消息类型,如果和发布时的消息类型不一致,编译不报错,运行报错
消息是以Boost共享指针的形式传输,我们可以存储它而又不需要复制数据。回调函数最常用的原型是这样的:1
void callback(const std_msgs::StringConstPtr& str)
其实它就是void callback(const boost::shared_ptr<std_msgs::String const>&);
,还有以下几种:1
2
3
4
5
6void callback(boost::shared_ptr<std_msgs::String const>);
void callback(std_msgs::StringConstPtr);
void callback(std_msgs::String::ConstPtr);
void callback(const std_msgs::String&);
void callback(std_msgs::String);
void callback(const ros::MessageEvent<std_msgs::String const>&);
MessageEvent类允许你在订阅的回调函数内获取信息的元数据。
其他常用的ros::Subscriber函数1
2
3
4
5
6// 注意: 只能判断话题是否发布,不能判断有没有发布消息
uint32_t getNumPublishers () const //获得连接订阅者的发布者个数
std::string getTopic () const //获得所订阅的话题
uint32_t getNumSubscribers () const //获得连接发布者的订阅者个数
订阅话题后,回调函数的参数类型必须是对应话题的消息类型,否则能订阅成功但不运行回调。
发布者的回调函数
用于检测是否有订阅者1
2
3
4
5
6
7
8
9
10
11
12ros::Publisher pub = nh.advertise<std_msgs::Int8>("topic", 50, connectCb,disconnectCb);
// 回调函数如下
void connectCb(const ros::SingleSubscriberPublisher& pub)
{
ROS_INFO("topic Name: %s", pub.getTopic().c_str());
ROS_INFO("Subscriber Name: %s", pub.getSubscriberName().c_str());
}
void disconnectCb(const ros::SingleSubscriberPublisher& pub)
{
ROS_INFO("disconnectCb");
}
ROS的回调函数里定义count
变量,居然报错ambiguous
,看来以后最好不用count
变量
特殊需求
有时需要在订阅者的回调函数中发布消息,而且只发布一次,这时没必要用service混合topic,用static变量就可以做到: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
28static ros::Publisher *pubPtr;
static bool published = false;
void callback(const std_msgs::Int8ConstPtr& msg)
{
int num = msg->data;
ROS_INFO("msg: %d", num);
std_msgs::String str;
str.data = std::string(to_string(num) );
if(!published && num>8) // 在num>8时发布一次,以后再也不发布
{
pubPtr->publish(str);
published = true;
}
sleep(1);
}
int main(int argc, char** argv)
{
ros::init(argc,argv, "Sub");
ros::NodeHandle nh;
ros::Subscriber sub = nh.subscribe<std_msgs::Int8>("topic",20,callback);
ros::Publisher pub = nh.advertise<std_msgs::String>("new",50,false);
pubPtr = &pub;
ros::spin();
return 0;
}
让订阅者停止订阅
ROS官方有这么一段话:1
2
3
4
5function subscribe() returns a Subscriber object that you must hold on to until you want to unsubscribe. When all copies of the Subscriber object go out of scope, this callback will automatically be unsubscribed from this topic.
function shutdown() Unsubscribe the callback associated with this Subscriber.
This method usually does not need to be explicitly called, as automatic shutdown happens when all copies of this Subscriber go out of scope
This method overrides the automatic reference counted unsubscribe, and immediately unsubscribes the callback associated with this Subscriber
根据说明,有以下三种方法:
一般让订阅者停止订阅的方法是等
subscribe()
生成的订阅者对象出作用域.另一种方法就是explicitly调用
shutdown()
,典型方法是声明订阅者对象为全局变量,在回调函数中满足某条件时,对此对象调用shutdown()
,但这样会直接退出程序,并不好第三种方法其实与第二种类似,声明一个bool全局变量ok,订阅时执行
1 | while(!ok) |
在回调函数中满足某条件时,让ok=true
queue_size和buff_size
发布者的原型是这样的1
2
3
4Publisher ros::NodeHandle::advertise(const std::string & topic,
uint32_t queue_size,
bool latch = false
)
- queue_size Maximum number of outgoing messages to be queued for delivery to subscribers。 消息队列是为了缓存发布节点发布的消息,一旦队列中消息的数量超过了queue_size,那么最先进入队列的(最老的)消息被舍弃。
- latch (optional) If true, the last message published on this topic will be saved and sent to new subscribers when they connect
订阅者Subscriber ros::NodeHandle::subscribe
函数里也有一个queue_size
: Number of incoming messages to queue up for processing,消息队列是为了缓存节点接收到的信息,一旦自己处理的速度过慢,接收到的消息数量超过了queue_size,那么最先进入队列的(最老的)消息会被舍弃。
机器人应用中难免会遇到运算起来很费时间的操作,比如图像的特征提取、点云的匹配等等。有时候,我们需要在ROS的Subscriber的Callback回调函数中进行这些费时的操作。如果我们要是没有取舍的对于每个消息都调用一次回调函数,那么势必会导致计算越来越不实时,很有可能当下在处理的还是几秒以前的数据,之前的消息被舍弃了。所以,如果我们想只处理最新的消息,实际上只需要把两个queue_size
都设置成1,那么系统不会缓存数据,自然处理的就是最新的消息。
rospy
的订阅者还有个buff_size
参数,要设置一个很大很大的数,这个缓冲区的大小是指消息队列使用的缓冲区物理内存空间大小。如果这个空间小于一个消息所需要的空间,比如消息是一副图片或者一帧点云,数据量超过了缓冲区的大小。这个时候为了保证通信不发生错误,就会触发网络通信的保护机制,TCP的Buffer会为你缓存消息。
但是roscpp里面没有buff_size
参数,它获得 incoming message的大小,分配所需的缓冲区大小,然后一次读取整个incoming message