关于ROS基础的一些回答

本文详细介绍了ROS中的关键概念和函数,包括如何通过rosdep自动查找和安装依赖,ros::init()节点初始化的作用,ros::NodeHandle句柄的功能,nh.advertise()的发布机制,以及nh.spinOnce()和nh.spin()在主循环中的不同作用。此外,还解释了ros::Rate()如何帮助维持消息处理的频率。内容深入浅出,适合ROS初学者掌握基础操作。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


问:如何根据功能包自动查找更新下载依赖?

# 进入工作空间
cd <工作空间>
# 在src目录外面执行以下命令
rosdep install --from-paths src -i -y

问:ros::init( )有什么用?

ros::init()函数是ros程序调用的第一个函数,/* 用于初始化ros节点 */。它有三个重载,一般使用的长这样:

ros::init(argc, argv, "abc_node");/*此处是省略了第四个参数,一般用的时
我们大部分情况下也会省略第四个参数*/

ros::init()函数的声明如下(ros::init()函数的声明源码在./src/ros_comm/roscpp/include/ros/init.h文件中):

void ros::init	(	int & 	argc,
					char ** 	argv,
					const std::string & 	name,
					uint32_t 	options = 0 
)		

ros::init()函数的定义如下(ros::init()函数的定义源码在./src/ros_comm/roscpp/src/libros/init.cpp文件中,此处只是三个重载中最常用的一个):

void init(int& argc, char** argv, const std::string& name, uint32_t options)
 {
   M_string remappings;
  
   int full_argc = argc;
   // now, move the remapping argv's to the end, and decrement argc as needed
   for (int i = 0; i < argc; )
   {
     std::string arg = argv[i];
     size_t pos = arg.find(":=");
     if (pos != std::string::npos)
     {
       std::string local_name = arg.substr(0, pos);
       std::string external_name = arg.substr(pos + 2);
  
       ROSCPP_LOG_DEBUG("remap: %s => %s", local_name.c_str(), external_name.c_str());
       remappings[local_name] = external_name;
  
       // shuffle everybody down and stuff this guy at the end of argv
       char *tmp = argv[i];
       for (int j = i; j < full_argc - 1; j++)
         argv[j] = argv[j+1];
       argv[argc-1] = tmp;
       argc--;
     }
     else
     {
       i++; // move on, since we didn't shuffle anybody here to replace it
     }
   }
  
   init(remappings, name, options);
 }

ros::init()的参数分别是:

  • argc:remapping参数的个数
  • argv:remapping参数的列表
  • name:节点名,必须是一个基本名称,不能包含命名空间
  • options:[可选]用于启动节点的选项(ros::init_options中的一组位标志)

调用ros::init()函数后,它会调用五个函数:

  • network::init(remappings);
  • master::init(remappings);
  • this_node::init(name, remappings, options);
  • file_log::init(remappings);
  • param::init(remappings);

参考:https://blog.youkuaiyun.com/qq_42495740/article/details/117657794

问:ros::NodeHandle 句柄有什么用?

1、句柄可以让你通过构造函数指定命名空间

ros::NodeHandle nh("my_namespace");
这使得使用该句柄的任何相对名字都是相对<node_namespace>/my_namespace,而不是只相对<node_namespace>

你也可以指定一个父句柄和追加的命名空间
 
ros::NodeHandle nh1("ns1");
 
ros::NodeHandle nh2(nh1,"ns2");
这将把nh2放入到<node_namespace>/ns1/ns2命名空间

2、也可以指定全局名字,使用“/”(全局命名空间)

ros::NodeHandle nh("/my_global_namespace");
这种做法并不推荐,因为这样会使得节点无法被放入别的命名空间。只是有时在代码中使用全局名字有用。

3、私有名字,使用“~”(私有命名空间)

使用私有名字比直接调用有私有名的句柄方法更有技巧,你可以在一个私有命名空间中直接创建一个新的句柄。
 

ros::NodeHandle nh("~my_private_namespace");
 
ros::Subscriber sub = nh.subscribe("my_private_topic",....);

以上例子会订阅<node_name>/my_private_namespace/my_private_topic

注意:理解的重点上文中标注的部分,node_namespace和node_name是两回事!

node_name = node_namespace + nodename

问:nh.advertise是干什么的?

这是ROS官网给出的关于advertise的相关描述:
https://docs.ros.org/en/api/roscpp/html/classros_1_1NodeHandle.html#a6b655c04f32c4c967d49799ff9312ac6

Publisher ros::NodeHandle::advertise(	const 		std::string & 	topic,
										uint32_t 	queue_size,
										bool 		latch = false 
)		
  1. 此调用用于连接到主节点,以公开节点将发布有关给定主题的消息。
  2. 此方法返回一个发布服务器,该发布服务器允许您发布有关此主题的消息

问:nh.spinOnce( )和nh.spin( )是干什么的?

1、nh.spinOnce( )
这句话的意思是监听反馈函数(callback)。只能监听反馈,不能循环。所以当你需要监听一下的时候,就调用一下这个函数。
这个函数比较灵活,尤其是我想控制接收速度的时候。配合ros::ok()效果极佳

2、nh.spin( )
这句话的意思是循环且监听反馈函数(callback)。循环就是指程序运行到这里,就会一直在这里循环了。监听反馈函数的意思是,如果这个节点有callback函数,那写一句ros::spin()在这里,就可以在有对应消息到来的时候,运行callback函数里面的内容。
就目前而言,以我愚见,我觉得写这句话适用于写在程序的末尾(因为写在这句话后面的代码不会被执行),适用于订阅节点,且订阅速度没有限制的情况。

扩充:
ROS的主循环中需要不断调用ros::spin()或ros::spinOnce(),两者区别在于前者调用后不会再返回,而后者在调用后还可以继续执行之后的程序。

在使用ros::spin()的情况下,一般来说在初始化时已经设置好所有消息的回调,并且不需要其他背景程序运行。这样一来,每次消息到达时会执行用户的回调函数进行操作,相当于程序是消息事件驱动的;而在使用ros::spinOnce()的情况下,一般来说仅仅使用回调不足以完成任务,还需要其他辅助程序的执行:比如定时任务、数据处理、用户界面等。

关于消息接收回调机制在ROS官网上略有说明 (callbacks and spinning)。总体来说其原理是这样的:除了用户的主程序以外,ROS的socket连接控制进程会在后台接收订阅的消息,所有接收到的消息并不是立即处理,而是等到spin()或者spinOnce()执行时才集中处理。所以为了保证消息可以正常接收,需要尤其注意spinOnce()函数的使用 (对于spin()来说则不涉及太多的人为因素)。

I. 对于速度较快的消息,需要注意合理控制消息队列及spinOnce()的时间。例如,如果消息到达的频率是100Hz,而spinOnce()的执行频率是10Hz,那么就要至少保证消息队列中预留的大小大于10。

II. 如果对于用户自己的周期性任务,最好和spinOnce()并列调用。即使该任务是周期性的对于数据进行处理,例如对接收到的IMU数据进行Kalman滤波,也不建议直接放在回调函数中:因为存在通信接收的不确定性,不能保证该回调执行在时间上的稳定性。

III. 最后说明一下将ROS集成到其他程序架构时的情况。有些图形处理程序会将main()包裹起来,此时就需要找到一个合理的位置调用ros::spinOnce()。比如对于OpenGL来说,其中有一个方法就是采用设置定时器定时调用的方法:

// 示例代码
void timerCb(int value) {
  ros::spinOnce();
}
glutTimerFunc(10, timerCb, 0);
glutMainLoop(); // Never returns

消息到来并不会立即执行消息处理回调函数,而是在调用ros::spin()之后,才进行消息处理的轮转,消息回调函数统一处理订阅话题的消息。

roscpp不会在你的应用中明确一个线程模型:也就是说即使roscpp会在幕后使用多线程管理网络链接,调度等,但它不会将自己的线程暴露在你的应用中。

roscpp允许你的回调函数被任意多线程调用,如果你愿意。

最后的结果可能是你的回调函数将没有机会被调用,最常用的方法是使用ros::spin()调用。

注意:回调函数的排队和轮转,不会对内部的网路通信造成影响,它们仅仅会影响到用户的回调函数何时发生。它们会影响到订阅者队列。因为处理你回调函数的速度,你消息到来的速度,将会决定以前的消息会不会被丢弃。

参考:https://www.cnblogs.com/long5683/p/10003137.html

问:ros::Rate( )是干什么的?

它的功能就是先设定一个频率,然后通过睡眠度过一个循环中剩下的时间,来达到该设定频率。如果能够达到该设定频率则返回true,不能则返回false。

计时的起点是上一次睡眠的时间、构造函数被调用、或者调用void ros::Rate::reset()函数重置时间。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

JR_Sim

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值