ROS与C++入门教程-发布和订阅
ROS与C++入门教程-发布和订阅
说明:
- 介绍发布话题和订阅话题
发布话题
查阅相关API:
创建一个句柄发布消息到一个话题,是使用ros::NodeHandle类,更详细查阅:NodeHandles。
NodeHandle::advertise()函数用于创建 ros::Publisher,它用于发布话题:
ros::Publisher pub = nh.advertise<std_msgs::String>("topic_name", 5);
std_msgs::String str;
str.data = "hello world";
pub.publish(str);
- 注:这是可能的(尽管很少)为 NodeHandle::advertise()返回一个空的ros::Publisher。
- 在将来,这些情况下可能会抛出异常,但现在他们只是打印一个错误。你可以检查这个:
if (!pub)
{
...
}
Intraprocess Publishing(进程内发布)
- 当发布服务器和订阅服务器作用在相同节点的同一个话题,roscpp可以跳过序列化/反序列化步骤(可能节省大量的处理和延迟)。但只有当消息被发布为shared_ptr,才会这样处理。
- 示例:
ros::Publisher pub = nh.advertise<std_msgs::String>("topic_name", 5);
std_msgs::StringPtr str(new std_msgs::String);
str->data = "hello world";
pub.publish(str);
- 注意,当用这种方式发布,你和roscpp之间的隐性契约:你不可以修改你发送后的消息,因为指针会直接传递到用户的任何进程内。如果你想发送另一条消息,你必须分配一个新的。
发布选项
- advertise() 简单用法:
template<class M>
ros::Publisher advertise(const std::string& topic, uint32_t queue_size, bool latch = false);
- M [required] ,这是指定要在话题上发布的消息类型的模板参数
- topic [required] ,要发布的话题
- queue_size [required] ,消息队列的大小。
- 如果发布话题的速度快过roscpp发送消息的速度,roscpp就会丢弃旧的消息。
- 如果为0值意味着无限队列,这样会很危险的。
- latch [optional] ,在连接上激活锁定
- 当连接锁定,最后一条发布的信息就会保存,并自动发送到未来任何连接的订阅器。
- 这个对于缓慢变化到静态数据是有用的,例如地图。
- 注意:如果有关于同一话题的多个发布器,在同一个节点实例化,那么只有最后发布的消息从节点会发送,相对于最后发布的消息来自每个发布器单独的话题。
发布句柄的其他操作
- ros::Publisher有一个内部引用计数。也就是说复制非常容易,而不用创建新的ros::Publisher。
- 当所有的ros::Publisher版本销毁,话题就会关闭。例如:
- ros::shutdown() 调用,这个会关闭所有发布器及其他
- ros::Publisher::shutdown()调用,会关闭对应的话题
- 带有相同类型的相同话题多次调用NodeHandle::advertise() ,在这种情况下所有ros::Publishers都当作是另一个副本
- ros::Publisher可以执行== ,!= 和 < 操作。可以在std::map, std::set中使用。
- 你可以获取发布器的话题通过ros::Publisher::getTopic()方法
publish()行为和队列
- publish()在roscpp是异步的,当有订阅器连接到话题才会工作。
- publish()本身是非常快的,所以做的工作不多:
- 序列化的消息到缓冲区
- 将缓冲区推送到队列上以便后期处理
- roscpp内部线程能快速处理发布队列,能推送内容到已连接的订阅器的队列。
- 队列的大小通过advertise()的queue_size参数来设置。
- 当队列被消息填满,再增加新的消息前会先丢弃旧的消息。
- 注意,也可能有操作系统级别的队列在传输层,如TCP/UDP发送缓冲区。
订阅话题
查阅API:
订阅话题同样通过ros::NodeHandle类实现
示例:
void callback(const std_msgs::StringConstPtr& str)
{
...
}
...
ros::Subscriber sub = nh.subscribe("my_topic", 1, callback);
订阅选项
- 有多个 ros::NodeHandle::subscribe()的版本,简单示例如下:
template<class M>
ros::Subscriber subscribe(const std::string& topic, uint32_t queue_size, <callback, which may involve multiple arguments>, const ros::TransportHints& transport_hints = ros::TransportHints());
- M [usually unnecessary] ,这是指定要在话题上发布的消息类型的模板参数
- 对于subscribe()你不需要显式地定义这个,大多数版本的编译器,可以从你指定的回调中推断出来。
- topic ,订阅的话题
- queue_size,队列大小
- roscpp在回调中使用的传入消息队列的大小。
- 如果消息来得太快,处理无法跟上,roscpp将开始丢弃消息。
- 这里的0值意味着无限队列,这可能是危险的。
,回调函数 - 最常见的是类方法指针和指向类的实例的指针。
- transport_hints,允许你指定hints到roscpp的传输层
- 如更喜欢使用UPD传输,使用没延迟的TCP等
回调用法
- 用法示例:
void callback(const boost::shared_ptr<Message const>&);
- 每个生成的消息都为共享指针类型提供typedefs,你也可以使用。示例:
void callback(const std_msgs::StringConstPtr&);
- 另一个用法Other Valid Signatures [ROS 1.1+]
void 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>&);
- 你也可以要求一个非const的消息,在这种情况下,将副本如有必要(即出现单个节点的多个订阅相同的话题):
void callback(const boost::shared_ptr<std_msgs::String>&);
void callback(boost::shared_ptr<std_msgs::String>);
void callback(const std_msgs::StringPtr&);
void callback(const std_msgs::String::Ptr&);
void callback(std_msgs::StringPtr);
void callback(std_msgs::String::Ptr);
void callback(const ros::MessageEvent<std_msgs::String>&);
回调类型
- roscpp支持被boost::function支持的任何回调函数::
- 函数
- 类的方法
- 函数对象(包括boost::bind)
(1)函数
- 示例:
void callback(const std_msgs::StringConstPtr& str)
{
...
}
...
ros::Subscriber sub = nh.subscribe("my_topic", 1, callback);
(2)类方法
- 示例:
void Foo::callback(const std_msgs::StringConstPtr& message)
{
}
...
Foo foo_object;
ros::Subscriber sub = nh.subscribe("my_topic", 1, &Foo::callback, &foo_object);
(3)函数对象
- 示例:
class Foo
{
public:
void operator()(const std_msgs::StringConstPtr& message)
{
}
};
ros::Subscriber sub = nh.subscribe<std_msgs::String>("my_topic", 1, Foo());
- 注意:当使用函数对象(如boost::bind)你必须明确指定消息类型作为模板参数,因为编译器无法推断它。
MessageEvent [ROS 1.1+]
- MessageEvent类允许你在订阅的回调函数内获取信息的元数据
- 示例:
void callback(const ros::MessageEvent<std_msgs::String const>& event)
{
const std::string& publisher_name = event.getPublisherName();
const ros::M_string& header = event.getConnectionHeader();
ros::Time receipt_time = event.getReceiptTime();
const std_msgs::StringConstPtr& msg = event.getMessage();
}
排队和懒惰的反序列化
- 当消息第一次到达话题,它会放入到队列,队列大小由subscribe()中的queue_size参数确定。
- 当队列填满,新的消息到达,旧的信息就会丢弃。
- 此外,该消息不会被反序列化,直到需要的第一个回调被调用。
Transport Hints
- 查阅API:ros::TransportHints API docs
- ros::TransportHints被用于指定hints,确定传输层的作用话题的方式。例如你想去指定一个“unreliable”的连接。
- 示例:
ros::Subscriber sub = nh.subscribe("my_topic", 1, callback, ros::TransportHints().unreliable());
- 注意:ros::TransportHints使用Named Parameter Idiom,一个方法链的形式。
- 示例:
ros::TransportHints()
.unreliable()
.reliable()
.maxDatagramSize(1000)
.tcpNoDelay();
- 正如这个例子,你可以指定多个传输参数,unreliable和reliable。如果你订阅的主题发布服务器不支持第一个连接(不可靠),第二个如果支持,将与第二进行(可靠的)连接。在这种情况下,这两种方法的顺序是重要的,因为它决定了考虑传输的顺序。
- 当前无法在发布器指定transport hints。这可能是未来的一个选择。
获取最新文章: 扫一扫右上角的二维码加入“创客智造”公众号