< >
Home » ROS与C++入门教程 » ROS与C++入门教程-发布和订阅

ROS与C++入门教程-发布和订阅

ROS与C++入门教程-发布和订阅

说明:

  • 介绍发布话题和订阅话题

发布话题

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发送缓冲区。

订阅话题

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>&);

回调类型

(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()
        .unreliable()
        .reliable()
        .maxDatagramSize(1000)
        .tcpNoDelay();
  • 正如这个例子,你可以指定多个传输参数,unreliable和reliable。如果你订阅的主题发布服务器不支持第一个连接(不可靠),第二个如果支持,将与第二进行(可靠的)连接。在这种情况下,这两种方法的顺序是重要的,因为它决定了考虑传输的顺序。
  • 当前无法在发布器指定transport hints。这可能是未来的一个选择。

纠错,疑问,交流: 请进入讨论区点击加入Q群

获取最新文章: 扫一扫右上角的二维码加入“创客智造”公众号


标签: ROS与C++入门教程