目录
问:如何根据功能包自动查找更新下载依赖?
# 进入工作空间
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
)
- 此调用用于连接到主节点,以公开节点将发布有关给定主题的消息。
- 此方法返回一个发布服务器,该发布服务器允许您发布有关此主题的消息
问: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()函数重置时间。