概述
【奥特学园】ROS机器人入门课程《ROS理论与实践》零基础教程_哔哩哔哩_bilibili
下一篇:奥特学园ROS笔记--2(76-120节)_echo_gou的博客-CSDN博客
目录
25节 基本配置
28节(1.4.2):
30节 launch文件 1.4.3
----------话题通信---------
38节 发布者和订阅者的具体建立流程
40节 发布方实现
41节 发布方按频率发送数据
42节 订阅方实现
43节 没有添加依赖/前几条数据丢失/回调函数
44节 计算图
45、46、48节 python实现发布者
47节 python实现订阅者
50节 解耦合
51节 自定义msg
52节 自定义msg具体实现
53节 C++使用自定义msg(配置)
54节 使用C++自定义msg 发布方
55节 使用c++自定义msg 订阅方
57节 使用python自定义msg (配置)
58节 使用python自定义msg 发布方
59节 使用python自定义msg 订阅方
61节 总结
----------服务通信---------
63节 理论模型
64、65节 自定义srv(准备)
66节 自定义srv(服务端)
67、68节 自定义srv(客户端)
69节 先启动客户端
70 节 python实现srv(准备)
71节 python实现srv(服务端)
72、73节 python实现srv(客户端)
74节 先启动客户端(python)
25节 基本配置
1 建立文件夹,文件夹下建立src
2 在src文件外面:catkin_make后生成build和devel
3 code .打开vscode,然后vscode中ctrl+shift+b,选择后面小齿轮,将以下代码对tasks.json进行覆盖。这里目的是配置编译相关的参数,之后ctrl+shift+b即可编译。
{
// 有关 tasks.json 格式的文档,请参见
// https://go.microsoft.com/fwlink/?LinkId=733558
"version": "2.0.0",
"tasks": [
{
"label": "catkin_make:debug", //代表提示的描述性信息
"type": "shell", //可以选择shell或者process,如果是shell代码是在shell里面运行一个命令,如果是process代表作为一个进程来运行
"command": "catkin_make",//这个是我们需要运行的命令
"args": [],//如果需要在命令后面加一些后缀,可以写在这里,比如-DCATKIN_WHITELIST_PACKAGES=“pac1;pac2”
"group": {"kind":"build","isDefault":true},
"presentation": {
"reveal": "always"//可选always或者silence,代表是否输出信息
},
"problemMatcher": "$msCompile"
}
]
}
4 右键src创建ros功能包:,create catkin package ,分别输入名字和依赖(roscpp rospy std_mags)。
运行步骤
1 roscore
2 source ./devel/setup.bash
3 rosun ......
修改代码之后一定记得重新编译
注:在写代码的时候有红色波浪线则,如果确实没有错那就ctrl+shift+b编译一下。
python步骤:
1 编写代码,代码中第一二行应该加入
#! /usr/bin/env python
#coding=UTF-8
2 添加可执行权限:
1打开scripts所在的文件夹的终端
2输入:chmod +x *.py
3 cmakelist修改:
改成对应的文件名称
4 ctrl+shift+b编译
5 然后运行
1 roscore
2 source ./devel/setup.bash
rosrun plumbing_pub_sub demo01_pub_p.py
28节(1.4.2):
1 ROSnoetic版本不配置CMakeLists的python的话,只能支持python2不能支持python3
2 出现问题:/usr/bin/env:没有那个文件或者目录
30节 launch文件 1.4.3
1 这个文件可以一次性启动多个节点
2 是一个XML文件
3 在功能包下面创建文件夹launch,然后创建.launch文件
<!-- 启动乌龟gui和键盘控制节点 -->
<launch>
<!--添加被执行的节点-->
<node pkg="turtlesim" type="turtlesim_node" name="turtle1" output="screen"/>
<node pkg="turtlesim" type="turtle_teleop_key" name="key" output="screen"/>
</launch>
这三步就相当于之前启动的三个终端执行的语句,其中前后两个<launch>对应的是之前启动roscore的步骤
然后在命令行source以下后
roslaunch xxx文件夹 xxx.launch 即可
----------话题通信---------
38节 发布者和订阅者的具体建立流程
1发布者和订阅者分别在master中进行注册
2master根据注册表信息对两者进行匹配,匹配后master给订阅者发送发布者的rpc地址
3订阅者通过地址和发布者进行链接,然后开始通信
注:使用的协议有rpc和tcp协议;发布者和订阅者建立链接后,master就可以关闭了;上述实现流程已经封装了,了解即可;我们关注的是发布者和订阅者的实现和消息载体;master的作用可以理解为管理和匹配话题、建立两者之间的连接用的。
40节 发布方实现
图中一个是发布者一个是订阅者,创建在src中。
实现流程:
1.包含头文件
2.初始化 ROS 节点:命名(唯一)
3.实例化 ROS 句柄
4.实例化 发布者 对象
5.组织被发布的数据,并编写逻辑发布数据
//初始化节点
ros::init(argc,argv,"ergouzi");
//创建节点句柄
ros::NodeHandle nh;
//创建发布对象
ros::Publisher pub = nh.advertise<std_msgs::String>("fang",10); //10是队列长度,如果发出还没有被接受就会放到队列里面。
//创建消息对象的变量,用于发布数据
std_msgs::String msg;
//发布消息
while(ros::ok()) //节点是否存活,节点死亡ctrl+c结束就死亡
{
msg.data="hello";
pub.publish(msg); //
}
其中ros::ok()代表节点是否还存活;
41节 发布方按频率发送数据
上一节的补充
#include "ros/ros.h"
#include "std_msgs/String.h"
#include <sstream>
//发布方实现
int main(int argc, char *argv[])
{
setlocale(LC_ALL,"");
//初始化节点
ros::init(argc,argv,"ergouzi");
//创建节点句柄
ros::NodeHandle nh;
//创建发布对象
ros::Publisher pub = nh.advertise<std_msgs::String>("fang",10);
//创建消息对象的变量
std_msgs::String msg;
//发布频率
ros::Rate rate(10);
int count=0;
//发布消息
while(ros::ok())
{
count++;
//msg.data="hello";
//实现字符串拼接数字
std::stringstream ss;
ss<<"hello --- -"<<count; //将hello字符和count数字输入到ss
msg.data=ss.str();
pub.publish(msg);
//添加日志
ROS_INFO("发布的数据是%s",ss.str().c_str()); //打印到控制台上
rate.sleep(); //睡眠0.1秒
}
return 0;
}
42节 订阅方实现
步骤和上面两节相似
实现流程:
1.包含头文件
2.初始化 ROS 节点:命名(唯一)
3.实例化 ROS 句柄
4.实例化 订阅者 对象
5.处理订阅的消息(回调函数)
6.设置循环调用回调函数
#include "ros/ros.h"
#include "std_msgs/String.h"
#include <sstream>
void doMsg(const std_msgs::String::ConstPtr &msg){ //通过msg这个参数获取并且操作订阅道到的数据
ROS_INFO("cuihua订阅到的数据是:%s",msg->data.c_str());
}
int main(int argc, char *argv[])
{
setlocale(LC_ALL,"");
ros::init(argc,argv,"cuihua"); //节点名称具有唯一性
ros::NodeHandle nh; //句柄
ros::Subscriber sub=nh.subscribe("fang",10,doMsg); //topic一定和ergouzi保持一致才可通信.发布者那边的泛型可以不添加,订阅者这边可以自动识别。
ros::spin(); //因为上一行的回调函数doMsg需要被频繁的执行去获取数据,所以这一行作用就是处理回调函数;
// while(ros::ok()){ 这样效果也一样
// ros::spinOnce();
// }
return 0;
}
43节 没有添加依赖/前几条数据丢失/回调函数
上一节出现的问题的一个补充
1 在创建功能包的时候如果没有添加依赖,建议直接删除这个包重新创建。
2 订阅者订阅发布者的数据时,前面几条数据可能会丢失。
原因是publisher还没有在roscore中注册完毕。
解决:可以在注册后加入休眠步骤,来延迟第一条数据的发送。
即ros::Duration(3).sleep(); 这里休眠3秒。
3 回调函数:
每次订阅者订阅到外部的消息就执行一次回调函数,而不是代码上出现一次就调用一次
44节 查看计算图
终端输入
rqt_graph
显示计算图,了解节点之间的关系
注:计算图的查看必须要在程序运行的时候才可以查看
45、46、48节 python实现发布者
45节:
1导包
2初始化ros节点
3创建发布者对象
4编写发布逻辑并且发送数据
46节:
设置发布频率
48节:
设置sleep,从而保证订阅者可以订阅到发布者发布的所有数据。
#! /usr/bin/env python
#coding=UTF-8
import rospy
from std_msgs.msg import String
if __name__=="__main__":
rospy.init_node("sandai") #传入节点名称 #初始化节点
pub=rospy.Publisher("che",String,queue_size=10) #创建发布者对象
msg=String()
rate=rospy.Rate(1) #一秒一次
count=0 #计数
rospy.sleep(3)
while not rospy.is_shutdown():
count+=1
msg.data="hello"+str(count)
pub.publish(msg) #向外发布数据msg
rospy.loginfo("写出的数据:%s",msg.data)
rate.sleep()
代码中第一行和第二行都必须加上
注:python在终端打印使用的是rospy.loginfo而不是rospy.INFO
47节 python实现订阅者
47节:
导包
初始化ros节点
创建订阅者对象
回调函数处理数据
spin()
48节:
休眠
#! /usr/bin/env python
#coding=UTF-8
import rospy
from std_msgs.msg import String
def doMsg(msg): #回调函数,msg只是一个参数,随便起什么名字都行
rospy.loginfo("我订阅到的数据:%s",msg.data)
if __name__=="__main__":
rospy.init_node("huahua") #初始化节点
sub=rospy.Subscriber("che",String,doMsg,queue_size=10) #创建订阅者对象
rospy.spin() #回调
50节 解耦合
不同编程语言编写的节点之间也可以进行数据交换,只需要两者的话题一致即可,如下图中topic都是“fang”。
可以发现计算图输出,一边是c++编写的一边是python编写的。
51节 自定义msg
在 ROS 通信协议中,数据载体是一个较为重要组成部分,ROS 中通过 std_msgs 封装了一些原生的数据类型,比如:String、Int32、Int64、Char、Bool、Empty.... 但是,这些数据一般只包含一个 data 字段,结构的单一意味着功能上的局限性,当传输一些复杂的数据,比如: 激光雷达的信息... std_msgs 由于描述性较差而显得力不从心,这种场景下可以使用自定义的消息类型
msgs只是简单的文本文件,每行具有字段类型和字段名称,可以使用的字段类型有:
-
int8, int16, int32, int64 (或者无符号类型: uint*)
-
float32, float64
-
string
-
time, duration
-
other msg files //其他msg
-
variable-length array[] and fixed-length array[C] //变长和定长数组
ROS中还有一种特殊类型:Header
,标头包含时间戳和ROS中常用的坐标帧信息。会经常看到msg文件的第一行具有Header标头
。
52节 自定义msg具体实现
msg文件可以理解成一个结构体的作用,用于一次性传输多个不同类型的数据。
1 定义msg文件
创建文件夹msg和在下面创建msg文件,并且输入数据格式类型。
2 编辑配置文件
理解:package我理解为事先声明,cmakelists为实际编译执行过程,要先有package声明了之后cmakelists执行的时候才找得到相关文件。
package.xml中添加了56和65行,前者表示添加了编译的时候需要依赖的包message_generation,后者表示执行的时候需要的包message_runtime。
cmakelists.txt中:
添加14行,表示编译包的时候需要依赖于message_generation
修改51-54行,,108行,添加自己定义的msg文件
放开注释71-74行,表示如果我要编译自己定义的Person.msg需要依赖于std_msgs,因为自己建立的复合的消息实现Person.msg是由标准消息std_msgs组成的
放开108行并加上message_runtime,表示运行的时候依赖
弹幕:使用roboware直接配置好你的文件?
3 编译
ctrl+shift+b
生成了Person.h和_Person.py
53节 C++使用自定义msg(配置)
1 vscode配置
不配置也是可以运行,但为了方便代码提示以及避免误抛异常,需要先配置 vscode,将前面生成的 head 文件路径配置进 c_cpp_properties.json 的 includepath属性:
首先找到路径,右键include选择在终端中打开。
输入pwd之后复制路径
将路径包含到.vscode中的.json文件中的includepath中,注意格式和逗号。
54节 使用C++自定义msg 发布方
建立文件
编写代码
1包含头文件#include "plumbing_pub_sub/Person.h"
2初始化节点 3创建句柄 4创建发布者对象 5编写发布逻辑
#include "ros/ros.h"
#include "plumbing_pub_sub/Person.h"
int main(int argc, char *argv[])
{
setlocale(LC_ALL,"");
ROS_INFO("这是消息发布方");
ros::init(argc,argv,"banzhuren"); //班主任
ros::NodeHandle nh;
ros::Publisher pub=nh.advertise<plumbing_pub_sub::Person>("liaotian",10);
plumbing_pub_sub::Person person;
person.age=1;
person.height=1.73;
person.name="张三";
ros::Rate rate(1);
while(ros::ok()){
ROS_INFO("发布的消息:%s,%d,%.2f",person.name.c_str(),person.age,person.height); //注意这里要转化格式:person.name.c_str()
pub.publish(person);
person.age+=1;
rate.sleep();
ros::spinOnce();
}
return 0;
}
配置CMakeLists
上面三图前两图和之前非自定义msg一样,第三图中的作用(也是和之前的普通发布订阅所不同的地方)是可以保证我们调用的时候的依赖关系:因为demo03_pub_person.cpp需要依赖于msg中的person.msg文件即demo03中#include "plumbing_pub_sub/Person.h",所以我们需要在编译的时候保证person.msg文件要在demo03_pub_person.cpp的编译之前进行编译,这一句就是起这个作用。
然后执行结果:
进入工作空间source一下再用rostopic打印出来
55节 使用c++自定义msg 订阅方
代码实现
#include "ros/ros.h"
#include "plumbing_pub_sub/Person.h"
void doperson(const plumbing_pub_sub::Person::ConstPtr& person){ //常引用,只能读不能通过引用来修改
ROS_INFO("订阅的信息是:%s,%d,%.2f",person->name.c_str(),person->age,person->height);
}
int main(int argc, char *argv[])
{
setlocale(LC_ALL,"");
ROS_INFO("订阅方实现:");
ros::init(argc,argv,"jiazhang"); //家长
ros::NodeHandle nh;
ros::Subscriber sub = nh.subscribe("liaotian",10,doperson);
ros::spin();
return 0;
}
配置文件demo04_sub_person
57节 使用python自定义msg (配置)
和53节一样要先配置vscode避免导包的时候抛出异常和出现导包的提示,具体是将前面生成的 python 文件路径配置进 settings.json(之前c++是配置c_cpp_properties.json)
右键dist-packages ,在终端中打开
利用pwd获取路径并且复制
在.json文件中加入到第四和第八行中(和视频中不一样):
58节 使用python自定义msg 发布方
步骤
1导包 2创建节点 3创建对象 4编写发布逻辑
代码
#! /usr/bin/env python
#coding=UTF-8
import rospy
from plumbing_pub_sub.msg import Person
if __name__=="__main__":
rospy.init_node("dama")
pub = rospy.Publisher("jiaosheyou",Person,queue_size=10) #创建发布者对象
p = Person()
p.age=8
p.name="奥特曼"
p.height=1.85
rate = rospy.Rate(1)
while not rospy.is_shutdown():
pub.publish(p)
rospy.loginfo("发布的消息:%s,%d,%.2f",p.name,p.age,p.height)
rate.sleep()
权限
cmake编写
运行
59节 使用python自定义msg 订阅方
#! /usr/bin/env python
#coding-UTF_8
import rospy
from plumbing_pub_sub.msg import Person
def doperson(p):
rospy.loginfo("接受的数据是:%s,%d,%.2f",p.name,p.age,p.height)
if __name__=="__main__":
rospy.init_node("daye")
sub =rospy.Subscriber("jiaoshetou",Person,doperson,queue_size=10)
rospy.spin()
然后chmod +x *.py
然后cmake(注:开始的时候忘了这一步也可以执行和正常运行,为什么?)
然后执行
61节 总结
----------服务通信---------
服务通信应用于对实时性有一定要求的场景,双向通信。
63节 理论模型
理论模型和之前话题通信类似,只不过服务端必须在客户端之前启动,服务端和客户端都可以多个。最后建立链接后,是客户端先发出请求,然后得到服务端的响应。客户端请求一次,服务端响应一次。
master就相当于服务平台
服务端类比保洁公司
客户端类比客户
64、65节 自定义srv(准备)
接下来实现两个数的求和并且返回
1 创建新的功能包plumbing_server_client(依赖roscpp rospy std_msgs)
2 创建一个文件夹,并且在文件夹下创建AddInts.srv文件,在文件中输入自定义的消息格式。其中请求和响应的格式中间用“---”隔开
3编辑package.xml和CMakeLists文件
package中和之前一样添加55、62行
CMakeLists中分别添加14行、修改60行、71-74放开注释、108加上message_runtime
4 配置vscode,方便导包(添加11行,因为之前写过话题通行,所以已经添加好了)
66节 自定义srv(服务端)
1代码编写
#include "ros/ros.h"
#include "plumbing_server_client/AddInts.h"
bool doNums(plumbing_server_client::AddInts::Request &request,
plumbing_server_client::AddInts::Response &response){
int num1= request.num1;
int num2=request.num2;
ROS_INFO("收到的数据为%d,%d n求和为:%d",num1,num2,num1+num2);
response.sum=num1+num2;
return true;
}
int main(int argc, char *argv[])
{
setlocale(LC_ALL,"");
ros::init(argc,argv,"heishui"); //“保洁公司”
ros::NodeHandle nh;
ros::ServiceServer server =nh.advertiseService("addints",doNums); //注意这里是ServiceServer和advertiseService
ROS_INFO("服务器端启动");
ros::spin();
return 0;
}
2 配置CMakeLists(136、146、149-151、):
其中${PROJECT_NAME}是调用变量,因为开头有写着他的name
3运行
先编译
然后运行
然后再打开一个终端使用 rosservice call addints 模拟客户端发送请求这个步骤 来进行测试:将其中num1和num2
然后服务器端的窗口:
67、68节 自定义srv(客户端)
服务端有spin回调函数而客户端没有
67节:
代码
#include "ros/ros.h"
#include "plumbing_server_client/AddInts.h"
int main(int argc, char *argv[])
{
setlocale(LC_ALL,"");
ros::init(argc,argv,"dabao");
ros::NodeHandle nh;
ros::ServiceClient client= nh.serviceClient<plumbing_server_client::AddInts>("addints");
//提交请求
plumbing_server_client::AddInts ai; //创建对象,利用这个对象来进行数据通信
ai.request.num1=100;
ai.request.num2=292;
//处理响应
bool flag = client.call(ai); //通过client.call来对server提交申请,如果成功响应则返回true
if(flag){
ROS_INFO("响应成功");
ROS_INFO("结果为%d",ai.response.sum);
}
else{
ROS_INFO("处理失败");
}
return 0;
}
配置CMakeLists(三部分添加)
68节:
修改67节代码实现动态提交
代码:
#include "ros/ros.h"
#include "plumbing_server_client/AddInts.h"
int main(int argc, char *argv[])
{
//优化实现,获取命令中参数
setlocale(LC_ALL,"");
if(argc!=3){
ROS_INFO("提交的参数个数不对");
return 1;
}
ros::init(argc,argv,"dabao");
ros::NodeHandle nh;
ros::ServiceClient client= nh.serviceClient<plumbing_server_client::AddInts>("addints");
//提交请求
plumbing_server_client::AddInts ai; //创建对象,利用这个对象来进行数据通信
ai.request.num1=atoi(argv[1]);
ai.request.num2=atoi(argv[2]);
//处理响应
bool flag = client.call(ai); //通过client.call来对server提交申请,如果成功响应则返回true
if(flag){
ROS_INFO("响应成功");
ROS_INFO("结果为%d",ai.response.sum);
}
else{
ROS_INFO("处理失败");
}
return 0;
}
然后分别运行服务端和客户端,其中客户端的运行和之前有区别:
rosrun plumbing_server_client demo02_client 12 34
其中要在最后加上想要求和的两个数,不然argc传入的参数个数就是1
argc表示的是传入的参数的个数,本例中为3,一个是文件名,另外两个是传入的参数。代码argv[1]和argv[2]表示传入的两个数的值,argv[0]表示函数的名称。我们将argv[0]进行输出得到:/home/gzy/ROS/autolabor/demo3_ws/devel/lib/plumbing_server_client/demo02_client
69节 先启动客户端
上个例子中我们如果先启动客户端会报错处理失败
方法一: 我们在客户端发送请求的代码之前添加一个用于判断服务器状态的函数即可(24行),服务器如果没有启动则不继续往下执行,
方法二:在同样的位置添加以下代码(和方法一不同的是需要传参):
ros::service::waitForService("addints");
70 节 python实现srv(准备)
之前在使用自定义msg的时候已经配置过了.json文件 ,这里也是同样的配置
71节 python实现srv(服务端)
代码:其中注意导包
#! /usr/bin/env python
#coding=UTF-8
import rospy
from plumbing_server_client.srv import AddInts,AddIntsRequest,AddIntsResponse
#from plumbing_server_client.srv import * #这两种方式都可
#回调函数的参数(request)封装了请求数据,返回为响应数据
def doNum(request):
num1=request.num1
num2=request.num2
sum=num1+num2
response=AddIntsResponse()
response.sum=sum
rospy.loginfo("解析的数据num1=%d,num2=%d,结果为%d",num1,num2,sum)
return response
if __name__=="__main__":
rospy.init_node("heishui")
server = rospy.Service("addints",AddInts,doNum)
rospy.loginfo("服务器已经启动")
rospy.spin()
添加执行权限:
配置CMakeLists:
测试运行
72、73节 python实现srv(客户端)
代码:
#! /usr/bin/env python
#coding=UTF-8
import rospy
from plumbing_server_client.srv import AddInts
if __name__=="__main__":
rospy.init_node("erhei")
client = rospy.ServiceProxy("addints",AddInts) //注意这里的函数名称为ServiceProxy
response = client.call(12,34)
rospy.loginfo("响应的数据为:%d",response.sum)
权限:
执行
73 动态传入两个参数,这部分对应68节的内容
代码,需要import sys
#! /usr/bin/env python
#coding=UTF-8
import rospy
from plumbing_server_client.srv import AddInts
import sys
if __name__=="__main__":
if len(sys.argv)!=3:
rospy.loginfo("传入参数个数不对")
sys.exit(1)
rospy.init_node("erhei")
client = rospy.ServiceProxy("addints",AddInts)
num1=int(sys.argv[1])
num2=int(sys.argv[2])
response=client.call(num1,num2)
rospy.loginfo("响应的数据为:%d",response.sum)
执行
74节 先启动客户端(python)
对应69节
#! /usr/bin/env python
#coding=UTF-8
import rospy
from plumbing_server_client.srv import AddInts
import sys
if __name__=="__main__":
if len(sys.argv)!=3:
rospy.loginfo("传入参数个数不对")
sys.exit(1)
rospy.init_node("erhei")
client = rospy.ServiceProxy("addints",AddInts)
num1=int(sys.argv[1])
num2=int(sys.argv[2])
#用于判断服务器状态
#client.wait_for_service()
rospy.wait_for_service("addints")
response=client.call(num1,num2)
rospy.loginfo("响应的数据为:%d",response.sum)
最后
以上就是淡然小天鹅为你收集整理的奥特学园ROS笔记--1(25-74节)------发布、订阅、服务/客户端、25节 基本配置28节(1.4.2):30节 launch文件 1.4.3----------话题通信---------38节 发布者和订阅者的具体建立流程40节 发布方实现41节 发布方按频率发送数据42节 订阅方实现43节 没有添加依赖/前几条数据丢失/回调函数44节 查看计算图45、46、48节 python实现发布者47节 python实现订阅者50节 解耦合51节 自定义msg52节 自定义msg具体实现5的全部内容,希望文章能够帮你解决奥特学园ROS笔记--1(25-74节)------发布、订阅、服务/客户端、25节 基本配置28节(1.4.2):30节 launch文件 1.4.3----------话题通信---------38节 发布者和订阅者的具体建立流程40节 发布方实现41节 发布方按频率发送数据42节 订阅方实现43节 没有添加依赖/前几条数据丢失/回调函数44节 查看计算图45、46、48节 python实现发布者47节 python实现订阅者50节 解耦合51节 自定义msg52节 自定义msg具体实现5所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复