文章目录
API:应用程序编程接口
学习目标
上一章内容,主要介绍了ROS通信的实现,内容偏向于粗粒度的通信框架的讲解,没有详细介绍涉及的API,也没有封装代码,鉴于此,本章主要内容如下:
- ROS常用API介绍
- ROS中自定义头文件与源文件的使用
预期达成的学习目标:
- 熟练掌握ROS常用API
- 掌握ROS中自定义头文件与源文件的配置
01 常用API —— plumbing_apis
建议参考官方API文档或参考源码:
- ROS节点的初始化相关API
- NodeHandle 的基本使用相关API
- 话题的发布方,订阅方对象相关API
- 服务的服务端,客户端对象相关API
- 时间相关API
- 日志输出相关API
1.1 初始化函数 —— demo01_apis_pub.cpp
- ROS 初始化函数
- ros::init(argc,argv,“erGouZi”,ros::init_options::AnonymousName)
ROS初始化函数
参数:
1. argc ----- 封装实参个数(传入n个参数,则 argc = n+1)第1个是文件自身
2. argv ----- 封装参数数组(字符串数组)
3. name ----- 为节点命名(唯一性,不允许出现重名节点)
4. options -- 节点启动选项
返回值:void
使用细节:
1. argc 与 argv 的使用
如果按照ROS中的特定格式传入实参,那么ROS可以加以使用,比如用来设置全局参数,给节点重命名
2. options的使用
节点名称需要保证唯一,会导致一个问题:同一个节点不能重复启动
问题:ROS中有重名的节点启动时,之前的节点就会被关闭
需求:特定场景下,需要一个节点多次启动且能正常运行,怎么办?
(场景:比如安装2个摄像头用的节点)
解决:设置启动项 ros::init_options::AnonymousName
当创建ROS节点时,会在用户自定义的节点名称后缀随机数,从而避免重名问题
1.2 话题与服务相关对象(发布方) —— demo01_apis_pub.cpp
- 在 roscpp 中,话题和服务的相关对象一般由 NodeHandle 创建
- ros::Publisher pub = nh.advertise<std_msgs::String>(“fang”,10,true)
作用:创建发布者对象
模板:被发布的消息的类型
参数:
1. 话题名称
2. 队列长度
3. latch(可选) 如果设置为true,会保存发布方的最后一条消息,并且
新的订阅对象连接到发布方时,发布方会将消息发送给订阅方
使用:
latch设置为true的作用?
以静态地图为例,
方案1:可以使用固定频率发送地图数据,但效率低
方案2:可以将地图发布对象的latch设置为true,并且发布方只发布一次数据,每当订阅者连接时,
就可以将地图数据发送给订阅者(只发送一次),提高数据的发送效率。
1.1.1&1.2.1 C++ 实现
#include"ros/ros.h"
#include"std_msgs/String.h"
#include<sstream>
/*
发布方实现:
1.包含头文件
ROS中文本类型--->std_msgs/String.h
2.初始化ROS节点
3.创建节点句柄
4.创建发布者对象
5.编写发布逻辑并发布数据
*/
int main(int argc,char *argv[])
{
// 解决中文乱码问题
setlocale(LC_ALL,"");
// 2.初始化ROS节点
/*
作用: ROS初始化函数
参数:
1. argc ----- 封装实参个数(传入n个参数,则 argc = n+1)第1个是文件自身
2. argv ----- 封装参数数组(字符串数组)
3. name ----- 为节点命名(唯一性)
4. options -- 节点启动选项
返回值:void
使用细节:
1. argc 与 argv的使用
如果按照ROS中的特定格式传入实参,那么ROS可以加以使用,比如用来设置全局参数,给节点重命名
2. options的使用
节点名称需要保证唯一,会导致一个问题:同一个节点不能重复启动
问题:ROS中有重名的节点启动时,之前的节点就会被关闭
需求:特定场景下,需要一个节点多次启动且能正常运行,怎么办?
(场景:比如安装2个摄像头用的节点)
解决:设置启动项 ros::init_options::AnonymousName
当创建ROS节点时,会在用户自定义的节点名称后缀随机数,从而避免重名问题
*/
ros::init(argc,argv,"erGouZi",ros::init_options::AnonymousName);//ergouzi是节点名称,相当于是相亲对象,这里的ergouzi就是talker
// 3.创建节点句柄
ros::NodeHandle nh;
// 4.创建发布者对象
/*
作用:创建发布者对象
模板:被发布的消息的类型
参数:
1. 话题名称
2. 队列长度
3. latch(可选) 如果设置为true,会保存发布方的最后一条消息,并且
新的订阅对象连接到发布方时,发布方会将消息发送给订阅方
使用:
latch设置为true的作用?
以静态地图为例,
方案1:可以使用固定频率发送地图数据,但效率低
方案2:可以将地图发布对象的latch设置为true,并且发布方只发布一次数据,每当订阅者连接时,
就可以将地图数据发送给订阅者(只发送一次),提高数据的发送效率。
*/
ros::Publisher pub = nh.advertise<std_msgs::String>("fang",10,true);
//缓存10条数据,话题名称是fang,队列长10,由于网络阻塞,没有发出去,则存储在这里,最大容量是10,超出阈值后先放进去的先销毁
// 5.编写发布逻辑并发布数据
// 5.1 创建被发布的消息
std_msgs::String msg;
// 5.2 要求以10HZ 的频率发布数据,并且文本后添加编号
// 5.2.1 发布频率
ros::Rate rate(1);//每秒发布1次
// 5.2.2 设置编号
int count = 0;
// 订阅时,第一条数据丢失;原因: 发送第一条数据时, publisher 还未在 roscore 注册完毕
ros::Duration(3).sleep();//延迟3秒
// 5.3 编写循环,循环要发布的消息
while(ros::ok())//如果节点还活着,循环成立
{
// 如果计数器 >= 30,那么关闭节点
if(count >= 30)
{
ros::shutdown();// 退出
}
// ------------
count++;
//msg.data = "hello";
//实现字符串的拼接
std::stringstream ss;
ss << "hello ---> " << count;
msg.data = ss.str();
// if(count <= 10)
// {
// pub.publish(msg);
// //添加日志:
// ROS_INFO("发布的数据是:%s",msg.data.c_str());
// }
pub.publish(msg);
//添加日志:
ROS_INFO("发布的数据是:%s",msg.data.c_str());
rate.sleep();// 发布一次,睡0.1秒
ros::spinOnce();//每次循环完都回头一次,spinOnce()只调用一次回调函数,之后会继续往下执行,官方建议,处理回调函数
ROS_INFO("一轮回调执行完毕");
}
return 0;
}
1.1.2&1.2.2 配置 CMakeLists.txt
add_executable(demo01_apis_pub src/demo01_apis_pub.cpp)
add_dependencies(demo01_apis_pub ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})
target_link_libraries(demo01_apis_pub
${catkin_LIBRARIES}
)
1.1.3&1.2.3 编译执行(重要)
- 启动 roscore(窗口1)
roscore
- 启动发布节点(窗口2):后面跟着设置的参数名(下划线"_" + 参数名)
cd demo01_ws/
source ./devel/setup.bash
rosrun plumbing_apis demo01_apis_pub _length:= 2(后面要加冒号:)
- 启动窗口3 rosparam list
1.3 回旋函数 —— demo02_apis_sub.cpp
- ros::spin() && ros::spinOnce
- 相同点:二者都用于处理回调函数
- 发布方用的 spinOnce(),订阅方使用的是spin()
- 不同点:ros::spin() 是进入了循环执行回调函数,而 ros::spinOnce() 只会执行一次回调函数(没有循环),在ros::spin() 后的语句不会执行到,而 ros::spinOnce() 后的语句可以执行
spinOnce:只回调一次,执行完就结束,可以继续执行spinOnce后面的代码。
- spin:进入cb开始回旋,一直循环执行
1.3.1 C++ 实现
#include "ros/ros.h"
#include"std_msgs/String.h"
/*
订阅方实现:
1.包含头文件
ROS中文本类型--->std_msgs/String.h
2.初始化ROS节点
3.创建节点句柄
4.创建订阅者对象
5.处理订阅到的数据
6.spin()函数
*/
void doMsq(const std_msgs::String::ConstPtr &msg)// std_msgs String类型常量指针的引用,保证传入的实参是常量
{
//通过msg获取并且操作订阅到的数据
ROS_INFO("订阅的数据是:%s",msg->data.c_str());//指针的引用
// ROS_INFO("我听见:%s",(*msg_p).data.c_str());
}
int main(int argc, char *argv[])
{
//解决乱码问题
setlocale(LC_ALL,"");
// 2.初始化ROS节点
ros::init(argc,argv,"cuiHua");//节点名称具有唯一性
// 3.创建节点句柄
ros::NodeHandle nh;
// 4.创建订阅者对象
ros::Subscriber sub = nh.subscribe("fang",10,doMsq);//fang是两者的共同话题,doMsq是回调函数
// 5.处理订阅到的数据
ros::spin();//spin意思是回头,main函数从上往下一次执行,回头调用doMsq函数,doMsq需要被频繁执行
ROS_INFO("spin后的语句"); // 理论上这条语句执行不到
return 0;
}
1.3.2 配置 CMakeLists.txt
add_executable(demo02_apis_sub src/demo02_apis_sub.cpp)
add_dependencies(demo02_apis_sub ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})
target_link_libraries(demo02_apis_sub
${catkin_LIBRARIES}
)
1.3.3 编译执行
- 启动 roscore(窗口1)
roscore
- 启动发布节点(窗口2)
cd demo01_ws/
source ./devel/setup.bash
rosrun plumbing_apis demo01_apis_pub
- 启动订阅节点(窗口3)
cd demo01_ws/
source ./devel/setup.bash
rosrun plumbing_apis demo02_apis_sub
1.4 时间函数
1.4.1 时刻
- 获取时刻,或是设置指定时刻
C++ 实现
#include"ros/ros.h"
/*
需求: 演示时间相关的操作(获取当前时刻 + 设置制定时刻)
实现:
1. 准备(头文件、节点初始化、NodeHandle创建)
2. 获取当前时刻
3. 设置指定时刻
*/
int main(int argc, char *argv[])
{
// 1. 准备(头文件、节点初始化、NodeHandle创建)
setlocale(LC_ALL,"");
ros::init(argc,argv,"hello_time");
ros::NodeHandle nh;// 注意:必须。否则会导致时间API调用失败(再NodeHandle会初始化时间操作)
// 2. 获取当前时刻
// now函数会将当前时刻封装并返回
// 当前时刻:now 被调用执行的那一刻
// 参考系:1970年1月1日 00:00:00
ros::Time right_now = ros::Time::now();//距离参考系逝去的时间
ROS_INFO("当前时刻:%.2f",right_now.toSec());// 将逝去的时间总长以秒为单位计算,dounle类型
ROS_INFO("当前时刻:%d",right_now.sec);// 将逝去的时间总长以秒为单位计算,整数类型
// 3. 设置指定时刻
ros::Time t1(20,312345678);//构造函数,意思是距离参考系过去20秒,以及312345678纳秒
ros::Time t2(100.35);//浮点型B
ROS_INFO("t1 = %.2f",t1.toSec());
ROS_INFO("t2 = %.2f",t2.toSec());
return 0;
}
Python 实现
if __name__ == "__main__":
# 需求1: 演示时间相关操作(获取当前时刻 + 设置指定时刻)
rospy.init_node("hello_time")
# 获取时刻
right_now = rospy.Time.now() # 获取当前时刻(1.now函数被执行的那一刻 2.参考1970年01月01日00:00)并封装成对象
rospy.loginfo("当前时刻:%.2f",right_now.to_sec()) # 浮点
rospy.loginfo("当前时刻:%.2f",right_now.secs) # 整型 secs后面没有()
# 设置指定
time1 = rospy.Time(100.5) # 将时间封装成time对象,将1970年01月01日00:00逝去100.5秒
time2 = rospy.Time(100,312345678) # 后面是纳秒,将1970年01月01日00:00逝去100.312345678秒
rospy.loginfo("指定时刻1:%.2f",time1.to_sec()) # 浮点
rospy.loginfo("指定时刻1:%.2f",time2.to_sec()) # 浮点
# 从某个时间值获取时间对象
time3 = rospy.Time.from_sec(210.12)
rospy.loginfo("指定时刻1:%.2f",time3.to_sec()) # 浮点
1.4.2 持续时间
C++ 实现
ROS_INFO("------------------持续时间--------------");
ros::Time start = ros::Time::now();
ROS_INFO("开始休眠: %.2f",start.toSec());
ros::Duration du(4.5); //持续时间封装了4.5秒
du.sleep();//睡眠4.5s
ros::Time end = ros::Time::now();
ROS_INFO("休眠结束:%.2f",end.toSec());
Python实现
# 需求2: 程序执行中停顿5秒
rospy.loginfo("休眠前-------------------")
# 1. 封装一个持续时间对象(5秒)
du = rospy.Duration(5,0) # 第二个位置纳秒
# 2. 再将持续时间休眠
rospy.sleep(du)
rospy.loginfo("休眠后-------------------")
1.4.3 持续时间与运算
-
- 时刻与持续时间可以执行加减
-
- 持续时间之间可以执行加减
-
- 时刻之间值可以相减,不可以相加:ros::Time sum = begin + stop 不可以相加,是错误语句
C++ 实现
// 时刻与持续时间运算
// 1. 获取开始执行的时刻
ros::Time begin = ros::Time::now();
// 2. 模拟运行时间(N秒)
ros::Duration du1(5);
// 3. 计算结束时刻 = 开始+持续时间
ros::Time stop = begin + du1; // 也可以用 -
ROS_INFO("开始时刻:%.2f",begin.toSec());
ROS_INFO("结束时刻:%.2f",stop.toSec());
// 时刻与时刻运算
// ros::Time sum = begin + stop;不可以相加
ros::Duration du2 = begin - stop;//-5
ROS_INFO("时刻相减:%.2f",du2.toSec());
// 持续时间与持续时间的运算
ros::Duration du3 = du1 + du2;// 0
ros::Duration du4 = du1 - du2;// 10
ROS_INFO("du1 + du2 = %.2f",du3.toSec());
ROS_INFO("du1 - du2 = %.2f",du4.toSec());
Python 实现
# 需求3: 获取程序开始执行的限制,且一直持续运行的时间,计算程序结束的时间
# 1. 获取一个时刻 t1
t1 = rospy.Time.now()
# 2. 设置一个持续时间 du1
du1 = rospy.Duration(5)
# 3. 结束时刻 t2 = t1 + du1
t2 = t1 + du1
rospy.loginfo("开始时刻:%.2f",t1.to_sec())
rospy.loginfo("结束时刻:%.2f",t2.to_sec())
du2 = du + du1
rospy.loginfo("持续时间相加:%.2f",du2.to_sec())
1.4.4 运行频率和定时器
定时器C++ 实现
注意:
创建: nh.createTimer()
参数1: 时间间隔
参数2: 回调函数(时间事件 TimerEvent)
参数3: 是否只执行1次
参数4: 是否自动启动(当设置false时,需要手动调用 timer.start())
定时器启动后: ros::spin()
/*
ros::Timer createTimer(ros::Duration period, // 时间间隔 --- 1
const ros::TimerCallback &callback, // 回调函数 --- 封装业务
bool oneshot = false, // 是否为1次性,dalse的话每隔1秒中执行1次回调函数,循环执行
bool autostart = true) // 自动启动
*/
// ros::Timer timer = nh.createTimer(ros::Duration(1),cb);//每隔1秒执行一次回调函数
// ros::Timer timer = nh.createTimer(ros::Duration(1),cb,true);//true只执行一次回调函数
ros::Timer timer = nh.createTimer(ros::Duration(1),cb,false,false);//false不会自动启动
timer.start();// 手动启动
ros::spin();// 需要回旋
定时器Python实现
def doMsg(event):
rospy.loginfo("+++++++++++++")
rospy.loginfo("调用回调函数的时刻:%.2f",event.current_real.to_sec()) # current_real 是rospy.Time类型
# 需求4: 创建定时器,实现类似于 ros::Rate 的功能(隔某个时间间隔执行某个操作)
"""
@param period: desired period between callbacks 回调函数之间调用时间间隔
@type period: rospy.Duration
@param callback: callback to be called
@type callback: function taking rospy.TimerEvent
@param oneshot: if True, fire only once, otherwise fire continuously until shutdown is called [default: False]
@type oneshot: bool
"""
# timer = rospy.Timer(rospy.Duration(2),doMsg,True) # 创建一个定时器对象,每个2秒钟执行一次回调
timer = rospy.Timer(rospy.Duration(2),doMsg) # 创建一个定时器对象,每个2秒钟执行一次回调
rospy.spin()
1.4.5 总的程序
1.4.5.1 C++ 实现 —— demo03_apis_time.cpp
#include"ros/ros.h"
/*
需求1: 演示时间相关的操作(获取当前时刻 + 设置制定时刻)
实现:
1. 准备(头文件、节点初始化、NodeHandle创建)
2. 获取当前时刻
3. 设置指定时刻
需求2: 程序执行中停顿5秒
实现:
1. 创建持续时间对象
2. 休眠
需求3: 已知程序开始运行的时刻和程序运行的时间,求运行结束的时刻
实现:
1. 获取开始执行的时刻
2. 模拟运行时间(N秒)
3. 计算结束时刻 = 开始+持续时间
注意:
1. 时刻与持续时间可以执行加减
2. 持续时间之间可以可以执行加减
3. 时刻之间值可以相减,不可以相加
需求4: 每隔1秒中,在控制台输出一段文本。
实现:
策略1:ros::Rate()
策略2:定时器
注意:
创建: nh.createTimer()
参数1: 时间间隔
参数2: 回调函数(时间事件 TimerEvent)
参数3: 是否只执行1次
参数4: 是否自动启动(当设置false时,需要手动调用 timer.start())
定时器启动后: ros::spin()
*/
//回调函数
void cb(const ros::TimerEvent& event){
ROS_INFO("----------");
ROS_INFO("函数被调用的时刻:%.2f",event.current_real.toSec());
}
int main(int argc, char *argv[])
{
// 需求1
// 1. 准备(头文件、节点初始化、NodeHandle创建)
setlocale(LC_ALL,"");
ros::init(argc,argv,"hello_time");
ros::NodeHandle nh;// 注意:必须。否则会导致时间API调用失败(再NodeHandle会初始化时间操作)
// 2. 获取当前时刻
// now函数会将当前时刻封装并返回
// 当前时刻:now 被调用执行的那一刻
// 参考系:1970年1月1日 00:00:00
ros::Time right_now = ros::Time::now();//距离参考系逝去的时间
ROS_INFO("当前时刻:%.2f",right_now.toSec());// 将逝去的时间总长以秒为单位计算,dounle类型
ROS_INFO("当前时刻:%d",right_now.sec);// 将逝去的时间总长以秒为单位计算,整数类型
// 3. 设置指定时刻
ros::Time t1(20,312345678);//构造函数,意思是距离参考系过去20秒,以及312345678纳秒
ros::Time t2(100.35);//浮点型B
ROS_INFO("t1 = %.2f",t1.toSec());
ROS_INFO("t2 = %.2f",t2.toSec());
// 需求2
ROS_INFO("------------------持续时间--------------");
ros::Time start = ros::Time::now();
ROS_INFO("开始休眠: %.2f",start.toSec());
ros::Duration du(4.5); //持续时间封装了4.5秒
du.sleep();//睡眠4.5s
ros::Time end = ros::Time::now();
ROS_INFO("休眠结束:%.2f",end.toSec());
// 需求3
ROS_INFO("------------------时间运算--------------");
// 时刻与持续时间运算
// 1. 获取开始执行的时刻
ros::Time begin = ros::Time::now();
// 2. 模拟运行时间(N秒)
ros::Duration du1(5);
// 3. 计算结束时刻 = 开始+持续时间
ros::Time stop = begin + du1; // 也可以用 -
ROS_INFO("开始时刻:%.2f",begin.toSec());
ROS_INFO("结束时刻:%.2f",stop.toSec());
// 时刻与时刻运算
// ros::Time sum = begin + stop;不可以相加
ros::Duration du2 = begin - stop;//-5
ROS_INFO("时刻相减:%.2f",du2.toSec());
// 持续时间与持续时间的运算
ros::Duration du3 = du1 + du2;// 0
ros::Duration du4 = du1 - du2;// 10
ROS_INFO("du1 + du2 = %.2f",du3.toSec());
ROS_INFO("du1 - du2 = %.2f",du4.toSec());
//需求4
ROS_INFO("------------------定时器--------------");
/*
ros::Timer createTimer(ros::Duration period, // 时间间隔 --- 1
const ros::TimerCallback &callback, // 回调函数 --- 封装业务
bool oneshot = false, // 是否为1次性,dalse的话每隔1秒中执行1次回调函数,循环执行
bool autostart = true) // 自动启动
*/
// ros::Timer timer = nh.createTimer(ros::Duration(1),cb);//每隔1秒执行一次回调函数
// ros::Timer timer = nh.createTimer(ros::Duration(1),cb,true);//true只执行一次回调函数
ros::Timer timer = nh.createTimer(ros::Duration(1),cb,false,false);//false不会自动启动
timer.start();//手动启动
ros::spin();//需要回旋
return 0;
}
1.4.5.2 配置 CMakeLists.txt
add_executable(demo03_apis_time src/demo03_apis_time.cpp)
add_dependencies(demo03_apis_time ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})
target_link_libraries(demo03_apis_time
${catkin_LIBRARIES}
)
1.4.5.3 编译执行
- 启动 roscore(窗口1)
roscore
- 启动时间节点(窗口2)
cd demo01_ws/
source ./devel/setup.bash
rosrun plumbing_apis demo03_apis_time
1.4.5.4 Python 实现 —— demo03_apis_time_p.py
#! /usr/bin/env python
import rospy
"""
需求1: 演示时间相关操作(获取当前时刻 + 设置指定时刻)
需求2: 程序执行中停顿5秒
需求3: 获取程序开始执行的限制,且一直持续运行的时间,计算程序结束的时间
需求4: 创建定时器,实现类似于 ros::Rate 的功能(隔某个时间间隔执行某个操作)
"""
def doMsg(event):
rospy.loginfo("+++++++++++++")
rospy.loginfo("调用回调函数的时刻:%.2f",event.current_real.to_sec()) # current_real 是rospy.Time类型
if __name__ == "__main__":
# 需求1: 演示时间相关操作(获取当前时刻 + 设置指定时刻)
rospy.init_node("hello_time")
# 获取时刻
right_now = rospy.Time.now() # 获取当前时刻(1.now函数被执行的那一刻 2.参考1970年01月01日00:00)并封装成对象
rospy.loginfo("当前时刻:%.2f",right_now.to_sec()) # 浮点
rospy.loginfo("当前时刻:%.2f",right_now.secs) # 整型 secs后面没有()
# 设置指定
time1 = rospy.Time(100.5) # 将时间封装成time对象,将1970年01月01日00:00逝去100.5秒
time2 = rospy.Time(100,312345678) # 后面是纳秒,将1970年01月01日00:00逝去100.312345678秒
rospy.loginfo("指定时刻1:%.2f",time1.to_sec()) # 浮点
rospy.loginfo("指定时刻1:%.2f",time2.to_sec()) # 浮点
# 从某个时间值获取时间对象
time3 = rospy.Time.from_sec(210.12)
rospy.loginfo("指定时刻1:%.2f",time3.to_sec()) # 浮点
# 需求2: 程序执行中停顿5秒
rospy.loginfo("休眠前-------------------")
# 1. 封装一个持续时间对象(5秒)
du = rospy.Duration(5,0) # 第二个位置纳秒
# 2. 再将持续时间休眠
# rospy.sleep(du)
rospy.loginfo("休眠后-------------------")
# 需求3: 获取程序开始执行的限制,且一直持续运行的时间,计算程序结束的时间
# 1. 获取一个时刻 t1
t1 = rospy.Time.now()
# 2. 设置一个持续时间 du1
du1 = rospy.Duration(5)
# 3. 结束时刻 t2 = t1 + du1
t2 = t1 + du1
rospy.loginfo("开始时刻:%.2f",t1.to_sec())
rospy.loginfo("结束时刻:%.2f",t2.to_sec())
du2 = du + du1
rospy.loginfo("持续时间相加:%.2f",du2.to_sec())
# 需求4: 创建定时器,实现类似于 ros::Rate 的功能(隔某个时间间隔执行某个操作)
"""
@param period: desired period between callbacks 回调函数之间调用时间间隔
@type period: rospy.Duration
@param callback: callback to be called
@type callback: function taking rospy.TimerEvent
@param oneshot: if True, fire only once, otherwise fire continuously until shutdown is called [default: False]
@type oneshot: bool
"""
# timer = rospy.Timer(rospy.Duration(2),doMsg,True) # 创建一个定时器对象,每个2秒钟执行一次回调
timer = rospy.Timer(rospy.Duration(2),doMsg) # 创建一个定时器对象,每个2秒钟执行一次回调
rospy.spin()
1.5 其他函数
1.5.1 日志
日志相关的函数是极其常用的,在ROS中日志被划分成如下级别:
- DEBUG(调试):只在调试时使用,此类消息不会输出到控制台;
- INFO(信息):标准消息,一般用于说明系统内正在执行的操作;
- WARN(警告):提醒一些异常情况,但程序仍然可以执行;
- ERROR(错误):提示错误信息,此类错误会影响程序运行;
- FATAL(严重错误):此类错误将阻止节点继续运行。
1.5.1.1 C++ 实现
#include"ros/ros.h"
/*
ROS中的日志:
演示不同级别日志的使用
*/
int main(int argc, char *argv[])
{
setlocale(LC_ALL,"");
ros::init(argc,argv,"hello_log");
ros::NodeHandle nh;
// 日志输出
ROS_DEBUG("调试信息");// 不会打印在控制台
ROS_INFO("一般信息");
ROS_WARN("警告信息");// 字体颜色为黄色
ROS_ERROR("错误信息");// 红色
ROS_FATAL("严重错误");
return 0;
}
1.5.2.1 Python 实现
#! /usr/bin/env python
import rospy
if __name__ == "__main__":
# 演示日志函数
rospy.init_node("hello_log")
rospy.logdebug("DEBUG消息...")
rospy.loginfo("INFO消息...")
rospy.logwarn("WARN消息...")
rospy.logerr("ERROR消息...")
rospy.logfatal("FATAL消息...")
1.5.2 节点生命周期
在发布实现时,一般会循环发布消息,循环的判断条件一般由节点状态来控制,C++中可以通过 ros::ok() 来判断节点状态是否正常,而 python 中则通过 rospy.is_shutdown() 来实现判断,导致节点退出的原因主要有如下几种:
- 节点接收到了关闭信息,比如常用的 ctrl + c 快捷键就是关闭节点的信号;
- 同名节点启动,导致现有节点退出;
- 程序中的其他部分调用了节点关闭相关的API(C++中是ros::shutdown(),python中是rospy.signal_shutdown())
1.5.2.1 C++ 实现
- 节点状态判断
/** \brief 检查节点是否已经退出
- - ros::shutdown() 被调用且执行完毕后,该函数将会返回 false
- - \return true 如果节点还健在, false 如果节点已经火化了。
*/
bool ok();
- 节点关闭
/*
* 关闭节点
*/
void shutdown();
02 ROS 中的源文件和头文件
本节主要介绍ROS的C++实现中,如何使用头文件与源文件的方式封装代码,具体内容如下:
- 设置头文件,可执行文件作为源文件;
- 分别设置头文件,源文件与可执行文件。
在ROS中关于头文件的使用,核心内容在于CMakeLists.txt文件的配置,不同的封装方式,配置上也有差异。
2.1 自定义头文件调用 —— plumbing_head
2.1.0 需求 - 流程
- 需求:设计头文件,可执行文件本身作为源文件
流程:
- 编写头文件;
- 编写可执行文件(同时也是源文件);
- 编辑配置文件并执行
2.1.1 头文件 —— hello.h
在功能包下的 include/plumbing_head 目录下新建头文件: hello.h
#ifndef _HELLO_H
#define _HELLO_H
/*
声明 namespace
|-- class
|-- run
*/
namespace hello_ns{
class MyHello{
public:
void run();
};
}
#endif
2.1.2 配置 c_cpp_properties.json(重要)
- 点击 plumbing_head 目录下的 include 文件,右击选择“在终端中打开”
- 输入 pwd
- 在c_cpp_properties.json输入
“/home/rosmelodic/demo01_ws/src/plumbing_head/include/**” - 如果还报错的话,建议重新启动 vscode
2.1.3 源文件(也是可执行文件) —— hello.cpp
#include"ros/ros.h"
#include"plumbing_head/hello.h" // c_cpp_properties配置路径
namespace hello_ns{
void MyHello::run(){
ROS_INFO("run 函数执行.....");
}
}
int main(int argc, char *argv[])
{
setlocale(LC_ALL,"");
ros::init(argc,argv,"hello_head");
// 函数调用
hello_ns::MyHello myHello;
myHello.run();
return 0;
}
2.1.4 配置文件 - 头文件/可执行文件
- 配置CMakeLists.txt文件,头文件相关配置如下
include_directories(
include
${catkin_INCLUDE_DIRS}
)
- **可执行配置文件(就是源文件 hello.cpp)**配置方式与之前一致
add_executable(hello src/hello.cpp)
add_dependencies(hello ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})
target_link_libraries(hello
${catkin_LIBRARIES}
)
2.1.5 编译执行
- Ctrl+ shift + B
- 启动 roscore(窗口1)
roscore
- 启动 (窗口2)
cd demo01_ws/
source ./devel/setup.bash
rosrun plumbing_head hello
2.2 自定义源文件的调用 —— plumbing_head_src
2.2.0 需求 - 流程
- 需求:设计头文件与源文件,在可执行文件中包含头文件
流程:
- 编写头文件;
- 编写源文件;
- 编写可执行文件;
- 编辑配置文件并执行
2.2.1 头文件 —— hello.h
在功能包下的 include/plumbing_head_src 目录下新建头文件: hello.h
在这里插入代码片
2.2.2 配置 c_cpp_properties.json(重要)
- 点击 plumbing_head_src 目录下的 include 文件,右击选择“在终端中打开”
- 输入 pwd
- 在c_cpp_properties.json输入
“/home/rosmelodic/demo01_ws/src/plumbing_head_src/include/**” - 如果还报错的话,建议重新启动 vscode
2.2.3 源文件 —— hello.cpp
在 src 目录下新建文件:hello.cpp
#include"plumbing_head_src/hello.h"
#include "ros/ros.h"
namespace hello_ns{
void MyHello::run(){
ROS_INFO("源文件中的 run 函数 ...");
}
}
2.2.4 可执行文件(不同于源文件) —— use_hello.cpp
在 src 目录下新建文件: use_hello.cpp
#include "ros/ros.h"
#include "plumbing_head_src/hello.h"
int main(int argc, char *argv[])
{
setlocale(LC_ALL,"");
ros::init(argc,argv,"hello_head_src");
hello_ns::MyHello myHello;
myHello.run();
return 0;
}
2.2.5 配置文件(重要!!!)
- 头文件与源文件相关配置:
include_directories(
include
${catkin_INCLUDE_DIRS}
)
# 配置C++库,head_src 表示库的名字
add_library(head_src # head_src 表示库的名字,可以随意取
# 头文件
include/${PROJECT_NAME}/hello.h
# 源文件
src/hello.cpp
)
# head_src 是库名字
add_dependencies(head_src ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})
target_link_libraries(head_src
${catkin_LIBRARIES}
)
- 可执行文件配置:
add_executable(use_hello src/use_hello.cpp)
add_dependencies(use_hello ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})
# 链接 可执行文件和C++库
target_link_libraries(use_hello
head_src
${catkin_LIBRARIES}
)
2.2.6 编译执行
- Ctrl+ shift + B
- 启动 roscore(窗口1)
roscore
- 启动 (窗口2)
cd demo01_ws/
source ./devel/setup.bash
rosrun plumbing_head_src use_hello