Chapter2 ROS通信机制----基础篇(Ⅰ)&vs配置及通信基础

目录

一、复习及launch

1.1 深入理解配置信息(非常重要)

1.2 launch文件演示

二、ROS通信机制-----基础

2.1 本节导论

2.2 话题通信

2.2.1 话题通信概述

 2.2.2 话题通信理论模型

 2.2.3 C++实现话题通信基本操作

2.2.4 python实现话题通信基本操作 


一、复习及launch

1.1 深入理解配置信息(非常重要)

温故而知新,可以为师矣。

-----在接触新知识前,回顾才是最重要的! 

 catkin_install_python(PROGRAMS
    scripts/hello_vscode_p.py
   DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
 )

还记得这段代码吗?我们是将自定义文件名.py改成了scripts下面的.py文件。

这段代码的作用是:可以正确安装python脚本、定义到合适的python解释器执行python脚本

 那么,如果不配置会出什么样的错误呢?笔者替大家尝试了一下并寻找解决办法

我们在scripts目录下建立一个py文件,为区别,起名为 hello_no_config.py,然后在scripts目录下调用终端并为其文件创立可执行文件权限

chmod +x *.py

按照上一章所说,接下来应该在cmakelist进行配置,但我们不进行这步操作,直接刷新环境变量

liuhongwei@liuhongwei-virtual-machine:~/demo02_ws$ source ./devel/setup.bash
rosrun hello_vscode hello_no_config.py

执行后,出现这样一行代码

/usr/bin/env : "python"没有那个文件或目录

于是得出结论:当不配置cmakelist.txt时,抛出异常,报错。

探赜索隐:还记得python前面声明的一行#! /usr/bin/env python吗?这行代码的意思是如果你要执行我这行脚本,那么/usr/bin/env路径下的python解释器去解释这个文件。

当我们不配置时,就去这个目录去找解释器,但是是找不到的,是软件版本问题。但如果配置了之后,配置文件会帮我们找到相应目录完成相应操作。

解决策略:直接修改解释器 #! /usr/bin/env python3(不建议)

                  通过软链接,将python链接到python3中(建议)

通过命令:

sudo ln -s /usr/bin/python3 /usr/bin/python

这样就可以执行脚本不用配置文件啦!!是不是方便很多

1.2 launch文件演示

 1.需求

一个程序中可能需要启动多个节点,比如小乌龟案例要启动多个窗口,分别是rosrun、乌龟界面节点、键盘控制节点,显然效率低下,如何改进?

官方给出的优化是使用launch文件:可以一次启动多个ros节点

 2.实现

Ⅰ.选定功能包-----添加launch文件夹

Chapter2 ROS通信机制----基础篇(Ⅰ)&vs配置及通信基础_第1张图片

Ⅱ.选定launch文件夹右击-----添加launch文件(xml类型文件)

Chapter2 ROS通信机制----基础篇(Ⅰ)&vs配置及通信基础_第2张图片

Ⅲ.编辑launch文件内容


    
    
    

对比于之前的三条指令,是不是方便很多!

roscore
rosrun turtlesim turtlesim_node
rosrun turtlesim turtle_teleop_key

参数名 作用
node 包含的某个节点
pkg 功能包
type 被运行的节点文件
name 为节点命名
output 设置日志的输出目标

Ⅳ.运行launch文件:  ctrl + Q

source ./devel/setup.bash

格式:roslaunch + 功能包名 + launch文件名

roslaunch hello_vscode start_turtle.launch

Ⅴ.结束

二、ROS通信机制-----基础

2.1 本节导论

1.本节主要内容

了解ROS中的基本通信机制的三种实现策略

话题通信(发布订阅模式):发布方与订阅方通过话题链接在一起,发布方发布关于话题的一些内容,接收方接收话题相关的一些内容。类似于今日头条的关注:我关注了美女,接下来就会把美女相关话题、图片发给我

服务通信(请求相应模式):类似C/S模型,客户端访问之后服务端才会相应;类似我输入一个网址才能获得网页内容

参数服务器(参数共享模式):开辟一个容器,容器中有一部分参数共享。有好多节点都可以向容器内存放/取出数据。类似于公司加班给了一些福利(小吃的)都可以吃

2.2 话题通信

2.2.1 话题通信概述

1.话题通信地位

是ROS中使用频率最高的一种通信方式,基于话题订阅模式,也即:一个节点发布消息,另一个节点接收消息。

比如 激光雷达信息采集处理为例:ROS中有一个节点需要时发布当前雷达采集到的数据,导航模块中也有节点会订阅并解析雷达数据。

2.适用范围

适用于不断更新的、少逻辑处理数据传输相关的应用场景 

 2.2.2 话题通信理论模型

1.话题通信的理论模型

Chapter2 ROS通信机制----基础篇(Ⅰ)&vs配置及通信基础_第3张图片

 Ⅰ.角色

ROS Master:管理者(管理与匹配话题)

Talker:发布者

Listener:订阅者

Ⅱ.流程

master起到撮合作用,可以根据话题建立发布者和订阅者之间的连接。(大龄青年相亲,master--媒婆 talker--男方 listener--女方)

Ⅲ.实现

①发布者在管理者进行注册操作,提交自己的话题以及RPC(远程调用地址)地址

②订阅者在管理者注册,提交自己关注的话题

③管理者将发布者与订阅者的话题进行比对,如果一致,会将发布者的RPC地址传送给订阅者

④订阅者根据RPC地址远程访问发布者

⑤发布者给订阅者一个响应,响应内容为发布者的TCP地址

⑥订阅者根据TCP地址访问发布者

⑦发布者发送内容通过TCP地址给订阅者

Ⅳ.注意

①使用的协议有RPC和TCP

②第一步和第二步谁先进行无所谓,无顺序关系

③发布方和订阅方都可以存在多个

④发布方和订阅方连接建立后,管理者即可关闭。

⑤上述实现流程已经封装了,直接调用即可。

Ⅴ.话题通信应用时的关注点

①话题设置

②发布者实现

③订阅者实现

④消息载体

 2.2.3 C++实现话题通信基本操作

1.需求

编写订阅发布实现,要求发布方以每秒10hz的频率发送文本信息,订阅方订阅消息内容并打印输出。

2.分析

 在模型实现中,ROS master不需要实现,而连接的建立已经被封装了,需要关注点有三

①发布方  ②接收方  ③数据(文本)

 3.流程

Ⅰ.编写发布方实现 

Ⅱ.编写订阅方实现

Ⅲ.编写配置文件

Ⅳ.编译并执行

4.发布方实现

Ⅰ. 建立文件夹 demo03_ws,在里面建立src文件夹,并且调用catkin_make命令(遗忘请翻阅chapter1)

Ⅱ.打开VSCODE,在src目录下面创建功能包 plumbing_pub_sub,导入依赖包rospy roscpp std_msgs(遗忘请翻阅chapter1)

Chapter2 ROS通信机制----基础篇(Ⅰ)&vs配置及通信基础_第4张图片

Ⅲ.编写发布者实现,在src的src目录下新建 demo01_pub.cpp 文件 

#include "ros/ros.h"
#include "std_msgs/String.h"
/*
        发布方实现
                 1.包含头文件
                 2.初始化ros节点
                 3.创建节点句柄
                 4.创建发布者对象
                 5.编写发布逻辑并发布数据
*/
int main(int argc ,char * argvs[])
{
    ros::init(argc,argvs,"erguizi");  //节点名称为erguizi
    ros::NodeHandle nh;                //创建节点句柄
    ros::Publisher pub = nh.advertise("house",10);    //话题为房子,队列中最多缓存十条数据

    //5-1.先创建没有被发布的信息
    std_msgs::String msg;
    //5-2.编写循环,循环中发布数据
    while(ros::ok())    //只要节点活着,则循环继续
    {
        msg.data = "hello";
        pub.publish(msg);
    }
    return 0;
}

Ⅳ.配置cmakelist.txt文件

add_executable(demo01_pub src/demo01_pub.cpp)

## Rename C++ executable without prefix
## The above recommended prefix causes long target names, the following renames the
## target back to the shorter version for ease of user use
## e.g. "rosrun someones_pkg node" instead of "rosrun someones_pkg someones_pkg_node"
# set_target_properties(${PROJECT_NAME}_node PROPERTIES OUTPUT_NAME node PREFIX "")

## Add cmake target dependencies of the executable
## same as for the library above
# add_dependencies(${PROJECT_NAME}_node ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})

## Specify libraries to link a library or executable target against
 target_link_libraries(demo01_pub
   ${catkin_LIBRARIES}
 )

Ⅴ.编译

Chapter2 ROS通信机制----基础篇(Ⅰ)&vs配置及通信基础_第5张图片

 Ⅵ.执行

①启动roscore

②进入工作目录

cd demo03_ws/

③配置环境变量

source ./devel/setup.bash

④运行

rosrun plumbing_pub_sub demo01_pub

⑤验证是否成功(下几节会介绍)

rostopic echo house

话题名称是房子,捕获它

⑥成功,发布者对象没有任何问题

Chapter2 ROS通信机制----基础篇(Ⅰ)&vs配置及通信基础_第6张图片

 5.发布逻辑实现

Ⅰ.要求以10hz实现发布数据,并且文本后添加编号

#include "ros/ros.h"
#include "std_msgs/String.h"
#include 
/*
        发布方实现
                 1.包含头文件
                 2.初始化ros节点
                 3.创建节点句柄
                 4.创建发布者对象
                 5.编写发布逻辑并发布数据
*/
int main(int argc ,char * argvs[])
{
    setlocale(LC_ALL,"");
    ros::init(argc,argvs,"erguizi");  //节点名称为erguizi
    ros::NodeHandle nh;
    ros::Publisher pub = nh.advertise("house",10);    //话题为房子,队列中最多缓存十条数据

    //5-1.先创建没有被发布的信息
    std_msgs::String msg;
    ros::Rate rate(10);        //10hz
    int count = 0;
    //5-2.编写循环,循环中发布数据
    while(ros::ok())    //只要节点活着,则循环继续
    {
        count ++;
        //实现字符串拼接
        std::stringstream ss;
        ss << "hello --->"<

Ⅱ.解释代码/接口用处

①setlocale:有中文,避免出现输出乱码必须要加的一行代码

②ros::NodeHandle nh; 创建节点句柄,句柄名称为nh

③ros::Publisher pub = nh.advertise("house",10);  创建发布者对象,为文本类型,话题名为house,缓冲区大小为10.

④std_msgs::String msg;   声明string类型数据

⑤ros::rate 初始化rate函数   rate.sleep()睡眠10hz

⑥ros::ok 状态信息,只要节点存在,就声明为真

⑦std::stringstream 处理字符串

⑧msg.data = ss.str(); 将ss转化为string类型赋值给msg.data

⑨pub.publish(msg);  发布者发布信息

Ⅲ.执行

Chapter2 ROS通信机制----基础篇(Ⅰ)&vs配置及通信基础_第7张图片

5.订阅方实现

Ⅰ.在src文件下建立 demo02_sub.cpp 文件,编写c++逻辑

#include "ros/ros.h"
#include"std_msgs/String.h"
/*
        订阅方实现
                 1.包含头文件
                 2.初始化ros节点
                 3.创建节点句柄
                 4.创建订阅者对象
                 5.处理订阅的数据
                 6.spin()函数
*/

void domessage(const std_msgs::String::ConstPtr & msg)
{
    //通过msg获取并操作订阅到的数据
    ROS_INFO("翠花订阅的数据是:%s",msg->data.c_str());

}

int main(int argc,char * argv[])
{
    ros::init(argc,argv,"cuihua");
    ros::NodeHandle nh;
    ros::Subscriber sub = nh.subscribe("house",10,domessage);
    ros::spin();

    return 0;
}

其中,spin函数的作用是 :

回头,main函数从上向下依次执行,执行到24语句时候,每订阅一次消息都会处理domessage函数。所以,这条语句执行一次是不行的,调用spin可以使其循环执行。

Ⅱ.修改、添加关于sub的配置文件

add_executable(demo01_pub src/demo01_pub.cpp)
 add_executable(demo02_sub src/demo02_sub.cpp)
## Rename C++ executable without prefix
## The above recommended prefix causes long target names, the following renames the
## target back to the shorter version for ease of user use
## e.g. "rosrun someones_pkg node" instead of "rosrun someones_pkg someones_pkg_node"
# set_target_properties(${PROJECT_NAME}_node PROPERTIES OUTPUT_NAME node PREFIX "")

## Add cmake target dependencies of the executable
## same as for the library above
# add_dependencies(${PROJECT_NAME}_node ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})

## Specify libraries to link a library or executable target against
 target_link_libraries(demo01_pub
   ${catkin_LIBRARIES}
 )
 target_link_libraries(demo02_sub
   ${catkin_LIBRARIES}
 )

Ⅲ.编译 

Ⅳ.执行

①启动roscore,先发布数据

rosrun plumbing_pub_sub demo01_pub

②更改环境变量

source ./devel/setup.bash

③执行订阅代码

rosrun plumbing_pub_sub demo02_sub

Chapter2 ROS通信机制----基础篇(Ⅰ)&vs配置及通信基础_第8张图片

成功!! 

Ⅴ.注意

①ros初始化节点不可以重名!

6.案例中的注意事项

 补充0:

vscode 中的 main 函数 声明 int main(int argc, char const *argv[]){},默认生成 argv 被 const 修饰,需要去除该修饰符

补充1:

ros/ros.h No such file or directory .....

检查 CMakeList.txt find_package 出现重复,删除内容少的即可

参考资料:fatal error: ros/ros.h: No such file or directory - ROS Answers: Open Source Q&A Forum

补充2:

find_package 不添加一些包,也可以运行啊, ros.wiki 答案如下

You may notice that sometimes your project builds fine even if you did not call find_package with all dependencies. This is because catkin combines all your projects into one, so if an earlier project calls find_package, yours is configured with the same values. But forgetting the call means your project can easily break when built in isolation.

补充3:

订阅时,前几条数据丢失

原因: 发送第一条数据时, publisher 还未在 roscore 注册完毕

解决: 注册后,加入休眠 ros::Duration(3.0).sleep(); 延迟第一条数据的发送

补充4:普通函数与回调函数(个人理解)

类似于操作系统中的信号系统,普通函数执行先函数声明,在main函数中发现调用则立即开辟函数栈并执行;而回调函数不然,在main函数虽然可能出现函数名称,但是不一定调用(不是马上执行),而是等待外部一个时机才会执行。

类比于打鬼子,函数调用相当于看见鬼子就开打,回调函数类似地雷!!


PS:可以使用 rqt_graph 查看节点关系。

修改后的代码----final版本

#include "ros/ros.h"
#include "std_msgs/String.h"
#include 
/*
        发布方实现
                 1.包含头文件
                 2.初始化ros节点
                 3.创建节点句柄
                 4.创建发布者对象
                 5.编写发布逻辑并发布数据
*/
int main(int argc ,char * argvs[])
{
    setlocale(LC_ALL,"");
    ros::init(argc,argvs,"erguizi");  //节点名称为erguizi
    ros::NodeHandle nh;
    ros::Publisher pub = nh.advertise("house",10);    //话题为房子,队列中最多缓存十条数据

    //5-1.先创建没有被发布的信息
    std_msgs::String msg;
    ros::Rate rate(10);        //10hz
    int count = 0;
    //5-2.编写循环,循环中发布数据
    ros::Duration(3).sleep();   //程序执行到这里休眠三秒钟
    while(ros::ok())    //只要节点活着,则循环继续
    {
        count ++;
        //实现字符串拼接
        std::stringstream ss;
        ss << "hello --->"<

 7.发布订阅模型

在执行时调用命令行,用图形化显示发布订阅之间的关系。

rqt_graph

2.2.4 python实现话题通信基本操作 

1.发布方实现

由于和C++实现大部分一样,这里只给出代码和适当标注

①在src的plumbing_pub_sub中建立存放python脚本的文件夹scripts,建立python的发布者对象 demo01_pub_p.py

#!  /usr/bin/env python
import rospy
from std_msgs.msg import String #发布消息的类型

"""
使用python实现消息发布
1.导包
2.初始化ros节点
3.创建发布者对象
4.编写发布逻辑并发布数据
"""


if __name__ ==  "__main__":
    rospy.init_node("lhw")           #传入节点名称
    pub = rospy.Publisher("car",String,queue_size=10)       #话题名称、类型、缓冲区长度
    #创建数据
    msg = String()
    #使用循环发布数据
    while not rospy.is_shutdown():
        msg.data = "hello"
        pub.publish(msg)

②添加文件的可执行权限:

chmod +x *.py

③修改配置文件:

 catkin_install_python(PROGRAMS
   scripts/demo01_pub_p.py
   DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
 )

④编译

⑤执行

source ./devel/setup.bash
rosrun plumbing_pub_sub demo01_pub_p.py

重点:此处在调试程序时,笔者一直编译不过,错误信息显示

Non-ASCII character '\xe9' in file /home/liuhongwei/demo03_ws/src/plumbing_pub_sub/scripts/demo01_pub_p.py on line 4, but no encoding declared; see http://python.org/dev/peps/pep-0263/ for details

我搜寻资料:结果如下

Chapter2 ROS通信机制----基础篇(Ⅰ)&vs配置及通信基础_第9张图片

于是在前面加上一条宏定义  # coding=UTF-8 即可解决

⑥进行发布逻辑补充:指定发布频率、拼接字符串更有逻辑性:

#1.导包 
import rospy
from std_msgs.msg import String

if __name__ == "__main__":
    #2.初始化 ROS 节点:命名(唯一)
    rospy.init_node("lhw")
    #3.实例化 发布者 对象
    pub = rospy.Publisher("car",String,queue_size=10)
    #4.组织被发布的数据,并编写逻辑发布数据
    msg = String()  #创建 msg 对象
    msg_front = "hello 你好"
    count = 0  #计数器 
    # 设置循环频率
    rate = rospy.Rate(1)
    while not rospy.is_shutdown():

        #拼接字符串
        msg.data = msg_front + str(count)

        pub.publish(msg)
        rate.sleep()
        rospy.loginfo("写出的数据:%s",msg.data)
        count += 1

2.订阅方实现

①在scripts下建立 demo02_sub_p.py文件实现订阅方逻辑代码

#! /usr/bin/env python
# coding=UTF-8
import rospy
from std_msgs.msg import String
 
 """
订阅实现流程
 1.导包
 2.初始化ros节点
 3.创建订阅对象
 4.回调函数处理数据
 5.spin()
 """

def domessageing(msg):
    rospy.loginfo("我订阅的数据:%s",msg.data)

 if  __name__ == __main__:
     rospy.init_node("huahua")
     sub = rospy.Subscriber("car",String,domessageing,queue_size=10)
     rospy.spin()

②修改配置文件

catkin_install_python(PROGRAMS
   scripts/demo01_pub_p.py
   scripts/demo02_sub_p.py
   DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
 )

③编译并添加可执行权限

④执行

source ./devel/setup.bash
rosrun plumbing_pub_sub demo02_sub_p.py

⑤联合执行

先执行发布,再执行订阅,查看结果

Chapter2 ROS通信机制----基础篇(Ⅰ)&vs配置及通信基础_第10张图片

3.注意事项(数据丢失处理)

与C++一样,发布之前需要进行休眠,python代码为

在while循环前加入代码,这样就不会缺前几个数据了

rospy.sleep(3)

4. 计算图查看

发送方订阅方启动后,打开终端输入

rqt_graph

5.解耦合

①概念:即使使用不同的语言,也是可以相互交互的(用C++写发布方,python写订阅方)

②方法:
一个窗口启动发送方,一个窗口启动订阅方,也可以实现,类似于黑盒!!!十分好用 

你可能感兴趣的:(ROS入门,python,c++)