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.添加可执行权限,编辑配置文件并执行
这里省略了

至此第三章介绍结束
咱们下章见