目录
一、复习及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文件夹
Ⅱ.选定launch文件夹右击-----添加launch文件(xml类型文件)
Ⅲ.编辑launch文件内容
<launch> <node pkg = "turtlesim" type = "turtlesim_node" name = "turtle_GUI" /> <node pkg = "turtlesim" type = "turtle_teleop_key" name = "turtle_MOVE" /> <node pkg = "hello_vscode" type ="hello_vscode_c.cpp" name = "hello" output = "screen"/> </launch>
对比于之前的三条指令,是不是方便很多!
roscore rosrun turtlesim turtlesim_node rosrun turtlesim turtle_teleop_key
参数名 作用 node 包含的某个节点 pkg 功能包 type 被运行的节点文件 name 为节点命名 output 设置日志的输出目标 Ⅳ.运行launch文件:
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.话题通信的理论模型
Ⅰ.角色
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)
Ⅲ.编写发布者实现,在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<std_msgs::String>("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} )
Ⅴ.编译
Ⅵ.执行
①启动roscore
②进入工作目录
cd demo03_ws/
③配置环境变量
source ./devel/setup.bash
④运行
rosrun plumbing_pub_sub demo01_pub
⑤验证是否成功(下几节会介绍)
rostopic echo house
话题名称是房子,捕获它
⑥成功,发布者对象没有任何问题
5.发布逻辑实现
Ⅰ.要求以10hz实现发布数据,并且文本后添加编号
#include "ros/ros.h" #include "std_msgs/String.h" #include <sstream> /* 发布方实现 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<std_msgs::String>("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 --->"<<count; msg.data = ss.str(); //msg.data = "hello"; pub.publish(msg); //添加日志 ROS_INFO("发布的数据是:%s",ss.str().c_str()); rate.sleep(); //睡觉0.1s } return 0; }
Ⅱ.解释代码/接口用处
①setlocale:有中文,避免出现输出乱码必须要加的一行代码
②ros::NodeHandle nh; 创建节点句柄,句柄名称为nh
③ros::Publisher pub = nh.advertise<std_msgs::String>("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); 发布者发布信息
Ⅲ.执行
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
成功!!
Ⅴ.注意
①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 <sstream> /* 发布方实现 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<std_msgs::String>("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 --->"<<count; msg.data = ss.str(); //msg.data = "hello"; pub.publish(msg); //添加日志 ROS_INFO("发布的数据是:%s",ss.str().c_str()); rate.sleep(); //睡觉0.1s } return 0; }
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
我搜寻资料:结果如下
于是在前面加上一条宏定义 # 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
⑤联合执行
先执行发布,再执行订阅,查看结果
3.注意事项(数据丢失处理)
与C++一样,发布之前需要进行休眠,python代码为
在while循环前加入代码,这样就不会缺前几个数据了
rospy.sleep(3)
4. 计算图查看
发送方订阅方启动后,打开终端输入
rqt_graph
5.解耦合
①概念:即使使用不同的语言,也是可以相互交互的(用C++写发布方,python写订阅方)
②方法:
一个窗口启动发送方,一个窗口启动订阅方,也可以实现,类似于黑盒!!!十分好用