ROS入门跟着我就够了(三)ROS通信机制进阶
上一章内容,主要介绍了ROS通信的实现,内容偏向于粗粒度的通信框架的讲解,没有详细介绍涉及的API,也没有封装代码,鉴于此,本章主要内容如下:
ROS常用API介绍;
ROS中自定义头文件与源文件的使用。
预期达成的学习目标:
熟练掌握ROS常用API;
掌握ROS中自定义头文件与源文件的配置。
3.1 常用API
应用程序编程接口
首先,建议参考官方API文档或参考源码:
ROS节点的初始化相关API;
NodeHandle 的基本使用相关API;
话题的发布方,订阅方对象相关API;
服务的服务端,客户端对象相关API;
时间相关API;
日志输出相关API。
参数服务器相关API在第二章已经有详细介绍和应用,在此不再赘述。
另请参考:
3.1.1 初始化
c++
ros::init 共4个参数:
argc ——- 封装实参的个数(n+1)
argv ——— 封装参数的数组
name ——-给节点命名
options— 节点启动选项
使用:
1.argc 和argv的使用
如果按照ros中的特定格式传入实参,那么ros可以加以使用:比如可以用来设置全局参数(参数服务器),给节点重命名等等
rosrun 包 文件名 _参数名:=2
那么参数直接传给argv,设置进参数服务器
2.options的使用
同一个节点不能重复启动,重复启动会导致之前节点关闭
需求:特定场景下,当节点需要启动多次时
解决:设置启动项,第四个参数
ros::init_opions::AnonymousName
当创建ros节点时,会在用户自定义的节点后加上随机数,避免了重名问题
多个节点名是原节点名+随机数(根据时间戳设置)
python:
def init_node(name, argv=None, anonymous=False, log_level=None, disable_rostime=False, disable_rosout=False, disable_signals=False, xmlrpc_port=0, tcpros_port=0):
"""
在ROS msater中注册节点
@param name: 节点名称,必须保证节点名称唯一,节点名称中不能使用命名空间(不能包含 '/')
@type name: str
@param anonymous: 取值为 true 时,为节点名称后缀随机编号
@type anonymous: bool
"""
1.argc 和argv的使用
如果按照ros中的特定格式传入实参,那么ros可以加以使用:比如可以用来设置全局参数(参数服务器),给节点重命名等等
rosrun 包 文件名 _参数名:=2
那么参数直接传给argv,设置进参数服务器,在节点名下
2. anonymous=true 可以重复调用节点
这里我就不演示了,会C++的操作发现这部分特别简单
3.1.2 话题与服务相关对象
C++:
在 roscpp 中,话题和服务的相关对象一般由 NodeHandle 创建。
NodeHandle有一个重要作用是可以用于设置命名空间,这是后期的重点,但是本章暂不介绍。
发布者对象,advertise 函数第三个参数 latch 为true时,订阅方只收到最后一条数据
Publisher advertise(const std::string& topic, uint32_t queue_size, bool latch = false)
#include "ros/ros.h"
#include "std_msgs/String.h" //普通文本类型的消息
#include <sstream>
int main(int argc, char *argv[])
{
//设置编码
setlocale(LC_ALL,"");
//2.初始化 ROS 节点:命名(唯一)
// 参数1和参数2 后期为节点传值会使用
// 参数3 是节点名称,是一个标识符,需要保证运行后,在 ROS 网络拓扑中唯一
ros::init(argc,argv,"talker");
//3.实例化 ROS 句柄
ros::NodeHandle nh;//该类封装了 ROS 中的一些常用功能
//4.实例化 发布者 对象
//泛型: 发布的消息类型
//参数1: 要发布到的话题
//参数2: 队列中最大保存的消息数,超出此阀值时,先进的先销毁(时间早的先销毁)
ros::Publisher pub = nh.advertise<std_msgs::String>("chatter",10,true);
//5.组织被发布的数据,并编写逻辑发布数据
//数据(动态组织)
std_msgs::String msg;
// msg.data = "你好啊!!!";
std::string msg_front = "Hello 你好!"; //消息前缀
int count = 0; //消息计数器
ros::Duration(3).sleep();
//逻辑(一秒10次)
ros::Rate r(1);
//持续三秒睡觉,通过这个时间注册成功
ros::Duration(3.0).sleep();
//节点不死
while (ros::ok())
{
//使用 stringstream 拼接字符串与编号
std::stringstream ss;
ss << msg_front << count;
msg.data = ss.str();
//发布消息
if(count<=10)
{
pub.publish(msg);
//添加日志
//加入调试,打印发送的消息
ROS_INFO("发送的消息:%s",msg.data.c_str());
}
//根据前面制定的发送贫频率自动休眠 休眠时间 = 1/频率;
r.sleep();
count++;//循环结束前,让 count 自增
//暂无应用
ros::spinOnce();
}
return 0;
}
先发十条
订阅只有最后一条
Python:
class Publisher(Topic):
"""
在ROS master注册为相关话题的发布方
"""
def __init__(self, name, data_class, subscriber_listener=None, tcp_nodelay=False, latch=False, headers=None, queue_size=None):
"""
Constructor
@param name: 话题名称
@type name: str
@param data_class: 消息类型
@param latch: 如果为 true,该话题发布的最后一条消息将被保存,并且后期当有订阅者连接时会将该消息发送给订阅者
@type latch: bool
@param queue_size: 等待发送给订阅者的最大消息数量
@type queue_size: int
"""
注意这里的Publisher()函数的latch参量,如果设置为True,可以将发布的最后一条数据保存,后期发给订阅者
测试:rostopic echo 话题名字
3.1.3 回旋函数
C++
在ROS程序中,频繁的使用了 ros::spin() 和 ros::spinOnce() 两个回旋函数,可以用于处理回调函数。
1.spinOnce()
/**
* \brief 处理一轮回调
*
* 一般应用场景:
* 在循环体内,处理所有可用的回调函数
*
*/
ROSCPP_DECL void spinOnce();
2.spin()
/**
* \brief 进入循环处理回调
*/
ROSCPP_DECL void spin();
3.二者比较
相同点:二者都用于处理回调函数(callback);
不同点:
ros::spin() 是进入了循环执行回调函数,不会退出,那么spin()后面代码执行不到了
而 ros::spinOnce() 只会执行一次回调函数(没有循环), ros::spinOnce() 后的语句可以执行。
Python
def spin():
"""
进入循环处理回调
"""
3.1.4 时间
ROS中时间相关的API是极其常用,比如:获取当前时刻、持续时间的设置、执行频率、休眠、定时器…都与时间相关。
C++
1.时刻
获取时刻,或是设置指定时刻:
#include "ros/ros.h"
// 获取当前时刻,设置指定时刻
int main(int argc,char *argv[])
{
setlocale(LC_ALL,"");
ros::init(argc,argv,"hello_time");
ros::NodeHandle nh;//必须,否则会导致时间API调用失败,在NodeHandle中要初始化时间操作
ros::Time right_now=ros::Time::now();//time类中的now函数
//now函数会将当前时刻封装返回
//当前时刻:now被调用执行的那一刻
//参考系:
// 参考系 1970年 01月01日 00:00:00
ROS_INFO("当前时刻:%.2f",right_now.toSec());
ROS_INFO("当前时刻:%d",right_now.sec);
return 0;
}
这里是秒数
设置指定时刻
#include "ros/ros.h"
// 获取当前时刻,设置指定时刻
int main(int argc,char *argv[])
{
setlocale(LC_ALL,"");
ros::init(argc,argv,"hello_time");
ros::NodeHandle nh;//必须,否则会导致时间API调用失败,在NodeHandle中要初始化时间操作
ros::Time right_now=ros::Time::now();//time类中的now函数
//now函数会将当前时刻封装返回
//当前时刻:now被调用执行的那一刻
//参考系:
// 参考系 1970年 01月01日 00:00:00
ROS_INFO("当前时刻:%.2f",right_now.toSec());
ROS_INFO("当前时刻:%d",right_now.sec);
//3.设置制定时间
ros::Time t1(20,312345678);//过去了20.31秒
ros::Time t2(100.35);
ROS_INFO("t1=%.2f",t1.toSec());
ROS_INFO("t2=%.2f",t2.toSec());
return 0;
}
2.持续时间
设置一个时间区间(间隔):
程序中停顿五秒
代码如下:
#include "ros/ros.h"
// 获取当前时刻,设置指定时刻
int main(int argc,char *argv[])
{
setlocale(LC_ALL,"");
ros::init(argc,argv,"hello_time");
ros::NodeHandle nh;//必须,否则会导致时间API调用失败,在NodeHandle中要初始化时间操作
ros::Time right_now=ros::Time::now();//time类中的now函数
//now函数会将当前时刻封装返回
//当前时刻:now被调用执行的那一刻
//参考系:
// 参考系 1970年 01月01日 00:00:00
ROS_INFO("当前时刻:%.2f",right_now.toSec());
ROS_INFO("当前时刻:%d",right_now.sec);
//3.设置制定时间
ros::Time t1(20,312345678);//过去了20.31秒
ros::Time t2(100.35);
ROS_INFO("t1=%.2f",t1.toSec());
ROS_INFO("t2=%.2f",t2.toSec());
// 程序执行中停顿5秒
// 1.创建持续时间的对象
// 2.休眠
//---------------------------------------------------------------------------------
ROS_INFO("-----------------持续时间---------------------");
ros::Time start=ros::Time::now();
ROS_INFO("-----------------开始休眠:%.2f---------------------",start.toSec());
ros::Duration du(4.5);
du.sleep();
ros::Time end=ros::Time::now();
ROS_INFO("---------------- 结束休眠:%.2f---------------------",end.toSec());
return 0;
}
Duration +sleep 实现持续休眠
3.持续时间与时刻运算
为了方便使用,ROS中提供了时间与时刻的运算:
C++
#include "ros/ros.h"
//需求1 获取当前时刻,设置指定时刻
int main(int argc,char *argv[])
{
setlocale(LC_ALL,"");
ros::init(argc,argv,"hello_time");
ros::NodeHandle nh;//必须,否则会导致时间API调用失败,在NodeHandle中要初始化时间操作
ros::Time right_now=ros::Time::now();//time类中的now函数
//now函数会将当前时刻封装返回
//当前时刻:now被调用执行的那一刻
//参考系:
// 参考系 1970年 01月01日 00:00:00
ROS_INFO("当前时刻:%.2f",right_now.toSec());
ROS_INFO("当前时刻:%d",right_now.sec);
//3.设置制定时间
ros::Time t1(20,312345678);//过去了20.31秒
ros::Time t2(100.35);
ROS_INFO("t1=%.2f",t1.toSec());
ROS_INFO("t2=%.2f",t2.toSec());
//需求2
// 程序执行中停顿5秒
// 1.创建持续时间的对象
// 2.休眠
//---------------------------------------------------------------------------------
ROS_INFO("-----------------持续时间---------------------");
ros::Time start=ros::Time::now();
ROS_INFO("-----------------开始休眠:%.2f---------------------",start.toSec());
ros::Duration du(4.5);
du.sleep();
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());
return 0;
}
实验结果如下所示
时刻运算:
#include "ros/ros.h"
//需求1 获取当前时刻,设置指定时刻
int main(int argc,char *argv[])
{
setlocale(LC_ALL,"");
ros::init(argc,argv,"hello_time");
ros::NodeHandle nh;//必须,否则会导致时间API调用失败,在NodeHandle中要初始化时间操作
ros::Time right_now=ros::Time::now();//time类中的now函数
//now函数会将当前时刻封装返回
//当前时刻:now被调用执行的那一刻
//参考系:
// 参考系 1970年 01月01日 00:00:00
ROS_INFO("当前时刻:%.2f",right_now.toSec());
ROS_INFO("当前时刻:%d",right_now.sec);
//3.设置制定时间
ros::Time t1(20,312345678);//过去了20.31秒
ros::Time t2(100.35);
ROS_INFO("t1=%.2f",t1.toSec());
ROS_INFO("t2=%.2f",t2.toSec());
//需求2
// 程序执行中停顿5秒
// 1.创建持续时间的对象
// 2.休眠
//---------------------------------------------------------------------------------
ROS_INFO("-----------------持续时间---------------------");
ros::Time start=ros::Time::now();
ROS_INFO("-----------------开始休眠:%.2f---------------------",start.toSec());
ros::Duration du(4.5);
du.sleep();
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; //时刻之间相减,返回的是duration格式
ROS_INFO("时刻相减:%.2f",du2.toSec());
//持续时间和持续时间的运算
ros::Duration du3=du1+du2;
ros::Duration du4=du1-du2;
ROS_INFO("du1+du2=%.2f",du3.toSec());
ROS_INFO("du1-du2=%.2f",du4.toSec());
return 0;
}
总结:
时刻和持续时间之间可以执行加减
持续时间之间也可以执行加减
时刻之间值可以减但不能加
4.设置运行频率
# 设置执行频率
rate = rospy.Rate(0.5)
while not rospy.is_shutdown():
rate.sleep() #休眠
rospy.loginfo("+++++++++++++++")
5.定时器
ROS 中内置了专门的定时器,可以实现与 ros::Rate 类似的效果:
#include "ros/ros.h"
//需求1 获取当前时刻,设置指定时刻
//回调函数
void cb(const ros::TimerEvent &event)
{
ROS_INFO("-------");
}
int main(int argc,char *argv[])
{
setlocale(LC_ALL,"");
ros::init(argc,argv,"hello_time");
ros::NodeHandle nh;//必须,否则会导致时间API调用失败,在NodeHandle中要初始化时间操作
ros::Time right_now=ros::Time::now();//time类中的now函数
//now函数会将当前时刻封装返回
//当前时刻:now被调用执行的那一刻
//参考系:
// 参考系 1970年 01月01日 00:00:00
ROS_INFO("当前时刻:%.2f",right_now.toSec());
ROS_INFO("当前时刻:%d",right_now.sec);
//3.设置制定时间
ros::Time t1(20,312345678);//过去了20.31秒
ros::Time t2(100.35);
ROS_INFO("t1=%.2f",t1.toSec());
ROS_INFO("t2=%.2f",t2.toSec());
//需求2
// 程序执行中停顿5秒
// 1.创建持续时间的对象
// 2.休眠
//---------------------------------------------------------------------------------
ROS_INFO("-----------------持续时间---------------------");
ros::Time start=ros::Time::now();
ROS_INFO("-----------------开始休眠:%.2f---------------------",start.toSec());
ros::Duration du(4.5);
du.sleep();
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; //时刻之间相减,返回的是duration格式
ROS_INFO("时刻相减:%.2f",du2.toSec());
//持续时间和持续时间的运算
ros::Duration du3=du1+du2;
ros::Duration du4=du1-du2;
ROS_INFO("du1+du2=%.2f",du3.toSec());
ROS_INFO("du1-du2=%.2f",du4.toSec());
//需求4 :每隔1秒钟,在控制台输出一段文本
// 实现:
// 策略1:ros::Rate()
// 策略2:定时器
//-----------------------------------------------------------------------------------
ROS_INFO("-----------------定时器---------------------");
//ros::Timer createTimer(ros::Duration period, const ros::TimerCallback &callback, bool oneshot = false, bool autostart = true) const
//持续时间:1s ; callback 回调函数 (封装业务); 定时器是否是一次性的, true只是执行一次 ;是否自动启动
ros::Timer timer=nh.createTimer(ros::Duration(1),cb);
ros::spin();//有回调函数就伴随回旋
return 0;
}
定时器参数进阶
//回调函数
void cb(const ros::TimerEvent &event)
{
ROS_INFO("-------");
ROS_INFO("函数被调用的时刻:%.2f",event.current_real.toSec());
}
ROS_INFO("-----------------定时器---------------------");
//ros::Timer createTimer(ros::Duration period, const ros::TimerCallback &callback, bool oneshot = false, bool autostart = true) const
//持续时间:1s ; callback 回调函数 (封装业务); 定时器是否是一次性的, true只是执行一次 ;是否自动启动,当设置为false时,需要手动调用time.start();
//另外定时器启动后,需要使用ros::spin()
ros::Timer timer=nh.createTimer(ros::Duration(1),cb,false,false);
timer.start();//手动启动,和自动启动类似
执行结果:
**Python:(不演示)
1.时刻**
获取时刻,或是设置指定时刻:
# 获取当前时刻 参考1970.1.1 0.0
right_now = rospy.Time.now()#返回一个time对象,将当前获取,封装成对象
rospy.loginfo("当前时刻:%.2f",right_now.to_sec())#转化成秒
rospy.loginfo("当前时刻:%d",right_now.secs)#转化成秒
rospy.loginfo("当前时刻:%.2f",right_now.to_nsec())#转化成纳秒
# 自定义时刻 参考1970.1.1 0.0
some_time1 = rospy.Time(1234.567891011)#将时间封装成time对象
some_time2 = rospy.Time(1234,567891011)#和上面一样
rospy.loginfo("设置时刻1:%.2f",some_time1.to_sec())
rospy.loginfo("设置时刻2:%.2f",some_time2.to_sec())
# 从时间创建对象
# some_time3 = rospy.Time.from_seconds(543.21)
some_time3 = rospy.Time.from_sec(543.21) # from_sec 替换了 from_seconds
rospy.loginfo("设置时刻3:%.2f",some_time3.to_sec())
2.持续时间
设置一个时间区间(间隔):
# 持续时间相关API
rospy.loginfo("持续时间测试开始.....")
du = rospy.Duration(3.3)
rospy.loginfo("du1 持续时间:%.2f",du.to_sec())
rospy.sleep(du) #休眠函数 du没有sleep函数,得rospy.sleep
rospy.loginfo("持续时间测试结束.....")
3.持续时间与时刻运算
为了方便使用,ROS中提供了时间与时刻的运算:
rospy.loginfo("时间运算")
now = rospy.Time.now()
du1 = rospy.Duration(10)
du2 = rospy.Duration(20)
rospy.loginfo("当前时刻:%.2f",now.to_sec())
before_now = now - du1
after_now = now + du1
dd = du1 + du2
# now = now + now #非法 时刻不能和时刻相加,会报错
rospy.loginfo("之前时刻:%.2f",before_now.to_sec())
rospy.loginfo("之后时刻:%.2f",after_now.to_sec())
rospy.loginfo("持续时间相加:%.2f",dd.to_sec())
4.设置运行频率
# 设置执行频率
rate = rospy.Rate(0.5)
while not rospy.is_shutdown():
rate.sleep() #休眠
rospy.loginfo("+++++++++++++++")
5.定时器
ROS 中内置了专门的定时器,可以实现与 ros::Rate 类似的效果:
#定时器设置
"""
def __init__(self, period, callback, oneshot=False, reset=False):
Constructor.
@param period: 回调函数的时间间隔
@type period: rospy.Duration
@param callback: 回调函数
@type callback: function taking rospy.TimerEvent 定时器事件
@param oneshot: 设置为True,就只执行一次,否则循环执行
@type oneshot: bool 执行一次
@param reset: if True, timer is reset when rostime moved backward. [default: False]
@type reset: bool
"""
rospy.Timer(rospy.Duration(1),doMsg)
# rospy.Timer(rospy.Duration(1),doMsg,True) # 只执行一次
rospy.spin()
回调函数:
def doMsg(event):
rospy.loginfo("+++++++++++")
rospy.loginfo("当前时刻:%s",str(event.current_real))#调用回调的时刻,返回时刻
3.1.5 其他函数
在发布实现时,一般会循环发布消息,循环的判断条件一般由节点状态来控制,C++中可以通过 ros::ok() 来判断节点状态是否正常,而 python 中则通过 rospy.is_shutdown() 来实现判断,导致节点退出的原因主要有如下几种:
节点接收到了关闭信息,比如常用的 ctrl + c 快捷键就是关闭节点的信号;
同名节点启动,导致现有节点退出;
程序中的其他部分调用了节点关闭相关的API(C++中是ros::shutdown(),python中是rospy.signal_shutdown())
**C++
ros::shutdown()关闭节点演示:**
#include "ros/ros.h"
#include "std_msgs/String.h" //普通文本类型的消息
#include <sstream>
int main(int argc, char *argv[])
{
//设置编码
setlocale(LC_ALL,"");
//2.初始化 ROS 节点:命名(唯一)
// 参数1和参数2 后期为节点传值会使用
// 参数3 是节点名称,是一个标识符,需要保证运行后,在 ROS 网络拓扑中唯一
ros::init(argc,argv,"talker");
//3.实例化 ROS 句柄
ros::NodeHandle nh;//该类封装了 ROS 中的一些常用功能
//4.实例化 发布者 对象
//泛型: 发布的消息类型
//参数1: 要发布到的话题
//参数2: 队列中最大保存的消息数,超出此阀值时,先进的先销毁(时间早的先销毁)
ros::Publisher pub = nh.advertise<std_msgs::String>("chatter",10,true);
//5.组织被发布的数据,并编写逻辑发布数据
//数据(动态组织)
std_msgs::String msg;
// msg.data = "你好啊!!!";
std::string msg_front = "Hello 你好!"; //消息前缀
int count = 0; //消息计数器
ros::Duration(3).sleep();
//逻辑(一秒10次)
ros::Rate r(1);
//持续三秒睡觉,通过这个时间注册成功
ros::Duration(3.0).sleep();
//节点不死
while (ros::ok())
{
//如果计数器>=50关闭节点
if (count>=50)
{
ros::shutdown();
}
//----------------------------------
//使用 stringstream 拼接字符串与编号
std::stringstream ss;
ss << msg_front << count;
msg.data = ss.str();
//发布消息
pub.publish(msg);
//添加日志
//加入调试,打印发送的消息
ROS_INFO("发送的消息:%s",msg.data.c_str());
//根据前面制定的发送贫频率自动休眠 休眠时间 = 1/频率;
r.sleep();
count++;//循环结束前,让 count 自增
//暂无应用
ros::spinOnce(); //处理回调函数
}
return 0;
}
日志:
日志级别演示
#include "ros/ros.h"
//演示不同级别日志的基本使用
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;
}
**Python
1.节点状态判断**
def is_shutdown():
"""
@return: True 如果节点已经被关闭
@rtype: bool
"""
def on_shutdown(h):
"""
节点被关闭时调用的函数,会调用回调函数
@param h: 关闭时调用的回调函数,此函数无参
@type h: fn()
"""
3.日志函数
使用示例
rospy.logdebug("hello,debug") #不会输出
rospy.loginfo("hello,info") #默认白色字体
rospy.logwarn("hello,warn") #默认黄色字体
rospy.logerr("hello,error") #默认红色字体
rospy.logfatal("hello,fatal") #默认红色字体
3.2 ROS中的头文件与源文件(重点)
本节主要介绍ROS的C++实现中,如何使用头文件与源文件的方式封装代码,具体内容如下:
1设置头文件,可执行文件作为源文件;
2分别设置头文件,源文件与可执行文件。
在ROS中关于头文件的使用,核心内容在于CMakeLists.txt文件的配置,两种不同的封装方式,配置上也有差异。
3.2.1 自定义头文件调用 C++
需求:设计头文件,可执行文件本身作为源文件。
流程:
编写头文件;
编写可执行文件(同时也是源文件);
编辑配置文件并执行。
1.头文件
2.配置路径
在 VScode 中,为了后续包含头文件时不抛出异常,请配置 .vscode 下 c_cpp_properties.json 的 includepath属性
"/home/用户/工作空间/src/功能包/include/**"
2.配置路径
在 VScode 中,为了后续包含头文件时不抛出异常,请配置 .vscode 下 c_cpp_properties.json 的 includepath属性
"/home/用户/工作空间/src/功能包/include/**"
3.头文件配置
#ifndef _HELLO_H
#define _HELLO_H
// 声明namespace
// class
// run
namespace hello_ns
{
class MyHello{
public:
void run();
};
}
#endif
4.源文件配置
#include "ros/ros.h"
#include "plumbinghead/hello.h"
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;
}
5.配置cmake
有头文件多配置一个cmake
还有之前的
实现:
3.2.2 自定义源文件调用C++
需求:设计头文件与源文件,在可执行文件中包含头文件。
流程:
编写头文件;
编写源文件;
编写可执行文件;
编辑配置文件并执行。
1 改路径
2 头文件 hello.h
#ifndef _HELLO_H
#define _HELLO_H
// 声明namespace
// class
// run
namespace hello_ns
{
class MyHello{
public:
void run();
};
}
#endif
3 源文件 hello1.cpp
#include "ros/ros.h"
#include "plumbing_head_src/hello.h"
namespace hello_ns{
void MyHello::run(){
ROS_INFO("源文件中的run函数。。。。");
}
}
4 可执行文件 usehello.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;
}
5 配置cmake
这里比较繁琐
5.1
这里注意127行src后面不要接projectname,不然会报错,projectname就是包名
声明C++库library要包括头文件名,源文件名
head_src是随意的名字,也是库名字,不过要和后面呼应
5.2
库配置完要配置可执行文件
这里target_link_libraris需要配置两个参数
这里比较复杂,建议反复比较,观看!
6 执行:
大文件中,头文件用来声明类,源文件拿来定义,可执行文件拿定义好的来用
3.3 Python模块导入
与C++类似的,在Python中导入其他模块时,也需要相关处理。
需求:首先新建一个Python文件A,再创建Python文件UseA,在UseA中导入A并调用A的实现。
实现:
新建两个Python文件,使用 import 实现导入关系;
添加可执行权限、编辑配置文件并执行UseA。
1.新建两个Python文件并使用import导入
文件A实现(包含一个变量):
#! /usr/bin/env python
num = 1000
文件B核心实现:
#设置临时环境变量
import os #有获取路径的功能
import sys #需要调用sys下的函数
path = os.path.abspath(".")#先获取当前工作空间的绝对路径
# 核心
sys.path.insert(0,path + "/src/plumbing_pub_sub/scripts") #动态获取路径,拼接路径
import tools
....
....
rospy.loginfo("num = %d",tools.num)
2.添加可执行权限,编辑配置文件并执行
这里省略了
至此第三章介绍结束
咱们下章见
评论(0)
您还未登录,请登录后发表或查看评论