导图社区 ROS基础
ROS技术技术及应用第三章ros基础内容,如ROS中的节点、参数、话题和服务统称为计算图源,其命名方式采用灵活的分层结构。
编辑于2023-06-09 17:31:18 江苏省ROS基础
小乌龟仿真
turtlesim功能包
核心为turtlesim_node节点
提供可视化的乌龟仿真器
每个ROS功能包都是一个独立的功能
这些功能对外使用话题、服务、参数等作为接口
话题与服务
turtlesim功能包订阅速度控制指令
发布乌龟的实时位姿信息
通过服务调用
实现删除、新生乌龟等功能
参数
创建工作空间和功能包
工作空间
存放工程开发相关文件的文件夹
默认使用Catkin编译系统
四个目录空间
src:代码空间
存储源码文件
build:编译空间
存储工作空间编译过程中产生的缓存信息和中间文件
devel:开发空间
放置编译生成的可执行文件
install:安装空间
不是必需的
创建工作空间
系统命令创建工作空间目录
mkdir -p~/catkin_ws/src
cd ~/caktin_ws/src
运行ROS的工作空间初始化命令
catkin_init_workspace
在工作空间根目录下使用caktin_make命令编译整个工作空间
cd~/caktin_ws
caktin_make
编译过程中,在工作空间根目录里会自动产生build和devel两个文件夹以及其中的文件
在devel文件夹中已经产生几个setuop.*sh形式的环境变量设置脚本
使用source命令运行脚本文件,让工作空间的环境变量生效
在终端中使用source命令设置的环境变量只能在当前终端中生效
如果想在所有终端中生效,则需要在终端的配置文件中加入环境变量的设置
echo"source/WORKSPACE/devel/setup.bash">>~/.bashrc
请使用工作空间路径代替WORKSPACE
source devel/setup.bash
使用该命令检查变量是否生效
echo $ROS_PACKAGR_PATH
创建功能包
CMakeList.txt文件记录了功能包的编译规则
ROS不允许在某个功能包中嵌套其他功能包,多个功能包必须平行放置在代码空间中
ROS直接创建功能包的命令
caktin_create_pkg<package_name>[depend1][depend2][depend3]
功能包的名称与依赖项
工作空间的覆盖
ROS中工作空间的覆盖
工作空间机制-Overlaying
工作空间的覆盖
所有工作空间的路径会依次在ROS_PACKAGE_PATH环境变量中记录
新设置的路径会在ROS_PACKAGE_PATH中自动放置在最前端
运行时,ROS优先查找 最前端工作空间
可用如下命令查看所有ROS相关的环境变量
env|grep ros
示例
使用rospack命令查看功能包所放置的工作空间
话题中的Publisher与Subscriber
乌龟例程中的Publisher和Subscriber
使用rqt_graph查看节点关系图
两个节点
teleop_turtle
创建了一个Publisher,发布键盘控制的速度指令
turtlesim
创建Subscriber,订阅速度指令
如何创建Publisher
Publisher节点实现流程
初始化ROS节点
向ROS Master 注册节点信息,包括发布的话题名和话题中的消息类型
按照一定频率循环发布消息
代码解析
头文件部分
#include "ros/ros.h"
包含了大部分ROS中通用的头文件
#include "std_msgs/String.h"
节点发布Stiring类型消息所需包含
该头文件根据String.msg的消息结构定义自动生成
初始化部分
ros::init(argc, argv, "talker");
包含三个参数
前两个是命令行或launch文件输入的参数
argc 是argument count的缩写表示传入函数中的参数个数;
argv 是 argument vector的缩写表示传入函数中的参数列表。
第三个参数定义了Publisher节点的名称
该名称在运行的ROS中必须是独一无二的,不允许同时存在相同名称的两个节点
ros::NodeHandle n;
创建一个节点句柄,方便对节点资源的使用和管理。
ros::Publisher chatter_pub = n.advertise<std_msgs::String>("chatter", 1000);
在ROS Master端注册一个Publisher
告诉系统Publisher节点将会发布以chatter为话题的String类型消息
第二个参数表示消息发布队列的大小
如果消息数量超过队列大小时,ROS会自动删除队列中最早入队的消息。
ros::Rate loop_rate(10);
设置循环的频率,单位是Hz
循环部分
int count = 0; while (ros::ok()) {
进入节点的主循环,在节点未发生异常的情况下将一直在循环中运行,一旦发生异常,ros::ok()就会返回false,跳出循环。
异常情况包括
收到SIGINT信号(Ctrl+C)
被另外一个相同名称的节点踢掉线
节点调用了关闭函数ros::shutdown()
所有ros::NodeHandles句柄被销毁
std_msgs::String msg; std::stringstream ss; ss << "hello world " << count; msg.data = ss.str();
初始化即将发布的消息。
使用了最为简单的String消息类型,该消息类型只有一个成员,即data,用来存储字符串数据
chatter_pub.publish(msg);
发布封装完毕的消息msg。
ROS_INFO("%s", msg.data.c_str())
类似于C/C++中的printf/cout函数,用来打印日志信息。
ros::spinOnce();
来处理节点订阅话题的所有回调函数。
虽然目前的发布节点并没有订阅任何消息,spinOnce函数不是必需的,但是为了保证功能无误,建议所有节点都默认加入该函数。
loop_rate.sleep();
在Publisher一个周期的工作已经完成,可以让节点休息一段时间,调用休眠函数,节点进入休眠状态。
如何创建Subscriber
实现Subscriber的简要流程:
初始化ROS节点
订阅需要的话题
循环等待话题消息,接收到消息后进入回调函数
在回调函数中完成消息处理
代码解析
回调函数部分
void chatterCallback(const std_msgs::String::ConstPtr& msg) { // 将接收到的消息打印出来 ROS_INFO("I heard: [%s]", msg->data.c_str()); }
回调函数是订阅节点接收消息的基础机制,当有消息到达时会自动以消息指针作为参数,再调用回调函数,完成对消息内容的处理。
如上是一个简单的回调函数,用来接收Publisher发布的String消息,并将消息数据 打印出来。
主函数部分
ros::Subscriber sub = n.subscribe("chatter", 1000, chatterCallback);
订阅节点首先需要声明自己订阅的消息话题,该信息会在ROS Master中注册。
NodeHandle::subscribe()用来创建一个Subscriber。
第一个参数即为消息话题;
第二个参数是接收消息队列的大小
第三个参数是接收到话题消息后的回调函数
ros::spin();
节点将进入循环状态,当有消息到达时,会尽快调用回调函数完成处理。ros:spin()在ros::ok()返回false时退出。
编译功能包
,C++是一种编译语言,在运行之前需要将代码编译成可执行文件,如果使用Python等解析语言编写代码,则不需要进行编译
ROS编译器使用CMake
编译规则通过功能包中的CMakeList.txt文件设置
使用catkin命令创建的功能包中会自动生成该文件
主要用到了四种编译配置项
include_directories
设置头文件的相对路径
add_executable
设置需要编译的代码和生成的可执行文件
第一个参数为期望生成的可执行文件的名称,后边的参数为参与 编译的源码文件(cpp)
target_link_libraries
设置链接库
很多功能需要使用系统或者第三方的库函数,通过该选项可以配置执行文件链接的库文件
第一个参数与add_executable相同,是可执行文件的名称,后面依次列出需要链接的库。
add_dependencies
设置依赖
定义语言无关的消息类型
消息类型会在编译过程中产生相应语言的代码 , 如 果 编 译 的 可 执 行 文 件 依 赖 这 些 动 态 生 成 的 代 码 , 则 需 要 使 用 add_dependencies 添 加${PROJECT_NAME}_generate_messages_cpp配置
该编译规则也可以添加其他需要依赖的功能包
以 上 编 译 内 容 会 帮 助 系 统 生 成 两 个 可 执 行 文 件 : talker 和 listener , 放 置 在 工 作 空 间 的~/catkin_ws/devel/lib/<package name>路径下。
CMakeLists.txt修改完成后,在工作空间的根路径下开始编译:
cd ~/catkin_ws catkin_make
运行Publisher和Subscriber
首先在终端设置环境变量
cd ~/catkin_ws source ./devel/setup.bash
启动roscore
roscore
启动publisher
rosrun learning_communication talker
启动Subscriber
rosrun learning_communication listener
调换两者的运行顺序
自定义话题消息
元功能包common_msgs中提供了许多不同消息类型的功能包
std_msgs(标准数据类型)
geometry_msgs(几何数据类型)
sensor_msgs(传感器数据类型)
语言无关的消息类型定义方法
msg文件就是ROS中定义消息类型的文件
编译过程中可以使用msg文件生成不同编程语言使用的代码文件
在msg文件中可以定义常量
消息定义中还会包含一个标准格式的头信息std_msgs/Header
#Standard metadata for higher-level flow data types uint32 seq time stamp string frame_id
seq是消息的顺序标识,不需要手动设置,Publisher在发布消息时会自动累加
stamp是消息中与数据相关联的时间戳,可以用于时间同步
frame_id是消息中与数据相关联的参考坐标系id
编译msg文件,以使用自定义的消息类型
在package.xml中添加功能包依赖
在CMakeLists.txt中添加编译选项
使用如下命令查看自定义的person消息类型
rosmsg show Person
服务中的Server和Client
乌龟例程中的服务
使用如下命令查看系统中的服务列表
rosservice list
使用如下命令调用/spawn服务新生一只乌龟
$ rosservice call /clear $ rosservice type /spawn | rossrv show $ rosservice call /spawn 8 8 0 "turtle2"
服务的请求数据是新生乌龟的位置、姿态以及名称
如何自定义服务数据
ROS中的服务数据可以通过srv文件进行语言无关的接口定义
一般放置在功能包根目录下的srv文件夹中
该文件包含请求与应答两个数据域
数据域中的内容与话题消息的数据类型相同,只是在请求与应答的描述之间,需要使用“---”进行分割
在功能包的package.xml和CMakeLists.txt文件中配置依赖与编译规则
开package.xml文件
<build_depend>message_generation</build_depend> <run_depend>message_runtime</run_depend>
开CMakeLists.txt文
find_package(catkin REQUIRED COMPONENTS geometry_msgs roscpp rospy std_msgs message_generation ) add_service_files( FILES AddTwoInts.srv )
message_generation包不仅可以针对话题消息产生相应的代码,还可以根据服务消息的类型描述文件产生相关的代码。
如何创建serve
服务中的server实现流程如下
初始化ROS节点
创建Server实例
循环等待服务请求,进入回调函数
在回调函数中完成服务功能的处理并反馈应答数据
代码解析
头文件部分
#include "ros/ros.h" #include "learning-communication/AddTwoInts.h"
这里使用的头文件是learning_communication/AddTwoInts.h,该头文件根据 我们之前创建的服务数据类型的描述文件AddTwoInts.srv自动生成。
主函数部分
ros::ServiceServer service = n.advertiseService("add_two_ints", add);
先初始化节点,创建节点句柄
要创建一个服务的Server,指定服务的名称以及接收到服务数据后的回调函数
然后开始循环等待服务请求
回调函数部分
bool add(learning_communication::AddTwoInts::Request &req, learning_communication::AddTwoInts::Response &res)
回调函数是真正实现服务功能的部分,也是设计的重点。
add()函数用于完成两个变量相加的功能,其传入参数便是我们在服务数据类型描述文件中声明的请求与应答的数据结构
{ res.sum = req.a + req.b; ROS_INFO("request: x=%ld, y=%ld", (long int)req.a, (long int)req.b); ROS_INFO("sending back response: [%ld]", (long int)res.sum); return true; }
在完成加法运算后,求和结果会放到应答数据中,反馈到Client,回调函数返回true
如何创建Client
Client实现流程
初始化ROS节点。
创建一个Client实例。
·发布服务请求数据。
·等待Server处理之后的应答结果。
代码解析
创建Client
ros::ServiceClient client =n.serviceClient<learning_communication::AddTwoI nts
首先需要创建一个add_two_ints的Client实例,指定服务类型为learning_communication::AddTwoInts。
.发布服务请求
learning_communication::AddTwoInts srv; srv.request.a = atoll(argv[1]); srv.request.b = atoll(argv[2]);
实例化一个服务数据类型的变量
该变量包含两个 成员:request和response
将节点运行时输入的两个参数 作为需要相加的两个整型数存储到变量中
if (client.call(srv))
进行服务调用
该调用过程会发生阻塞,调用成功后返回true,访问srv.response即可获取服务请求的结果
如果调用失败会返回false,srv.response则不可使用
编译功能包
编辑CMakeLists.txt
add_executable(server src/server.cpp) target_link_libraries(server ${catkin_LIBRARIES}) add_dependencies(server ${PROJECT_NAME}_gencpp) add_executable(client src/client.cpp) target_link_libraries(client ${catkin_LIBRARIES}) add_dependencies(client ${PROJECT_NAME}_gencpp)
运行Server和Client
设置环境变量
启动roscore
运行Server节点
rosrun learning_communication server
运行Client节点
rosrun learning_communication client 3 5
ROS中的命名空间
ROS中的节点、参数、话题和服务统称为计算图源
其命名方式采用灵活的分层结构
/foo /stanford/robot/name /wg/node1
计算图源命名是ROS封装的一种重要机制
每个资源都定义在一个命名空间内,该命名空间内还可以创建更多资源。但是处于不同命名空间内的资源不仅可以在所处命名空间内使用,还可以在全局范围内访问
有效避免不同命名空间内的命名冲突
有效的命名
首字符必须是字母([a-z|A-Z])、波浪线(~)或者左斜杠(/)
后续字符可以是字母或数字([0-9|a-z|A-Z])、下划线(_)或者左斜杠(/)
命名解析
计算图源的名称可以分为以下四种
基础(base)名称,例如:base
用来描述资源本身,可以看作相对名称的一个子类
全局(global)名称,例如:/global/name
首字符是左斜杠(/)的名称是全局名称
由左斜杠分开一系列命名空间
全局名称之所以称为全局,是因为它的解析度最高,可以在全局范围内直接访问
但是在系统中全局名称越少越好,因为过多的全局名称会直接影响功能包的可移植性
相对(relative)名称,例如:relative/name
全局名称需要列出所有命名空间,在命名空间繁多的复杂系统中使用较为不便,所以可以使用相对名称代替
相对名称由ROS提供默认的命名空间,不需要带有开头的左斜杠
相对名称的重点是如何确定默认的命名空间
通过命令参数设置
调用ros::init()的ROS程序会接收名为__ns的命令行参数,可以为程序设置默认的命名空间
赋值的方式为__ns:=default-namespace
在launch文件中设置
在launch文件中可通过设置ns参数来确定默认命名空间
<node name="turtlesim_node" pkg="turtlesim " type="turtlesim_node" ns="sim1" />
使用环境变量设置
可以在执行ROS程序的终端中设置默认命名空间的环境变量
export ROS_NAMESPACE=default-namespace
相比全局名称,相对名称具备良好的移植性,用户可以直接将一个相对命名的节点移植到其他命名空间内,有效防止命名冲突。
私有(private)名称,例如:~private/name
私有名称是一个节点内部私有的资源名称,只会在节点内部使用
私有名称以波浪线“~”开始,与相对名称一样,其并不包含本身所在的命名空间,需要ROS为其解析
私有名称并不使用当前默认命名空间,而是用节点的全局名称作为命名空间
例如有一个节点的全局名称是/sim1/pubvel,其中的私有名称 ~max_vel解析成全局名称即为/sim1/pubvel/max_vel
命名重映射
所有ROS节点内的资源名称都可以在节点启动时进行重映射。ROS这一强大的特性甚至可以支持我们同时打开多个相同的节点,而不会发生命名冲突。
命名重映射采用如下语法
name:=new_name
例如将chatter重映射为/wg/chatter,在节点启动时可以使用如下方式重映射命名:
$ rosrun rospy_tutorials talker chatter:=/wg/chatter
ROS的命名解析是在命名重映射之前发生
重映射与命名解析之间的关系
分布式多机通信
ROS中只允许存在一个Master
在多机系统中Master只能运行在一台机器上,其他机器需要通过ssh的 方式和Master取得联系
计算机hcx-pc作为主机运行Master,计算机raspi2作为从机运行节点。
设置IP地址
首先需要确定ROS多机系统中的所有计算机处于同一网络
分别在计算机hcx-pc、raspi2上使用ifconfig命令查看计算机的局域网IP地址
分别在两台计算机系统的/etc/hosts文件中加入对方的IP地址和对应的计算机名
# @hcx-pc,/etc/hosts 192.168.31.14 raspi2 # @raspi2,/etc/hosts 192.168.31.198 hcx-pc
设置完毕后,分别在两台计算机上使用ping命令测试网络是否连通
设置ROS_MASTER_URI
从机raspi2需要知道Master的位置
ROS Master的位置可以使用环境变量ROS_MASTER_URI进行定义
在从机raspi2上使用如下命令设置ROS_MASTER_URI
export ROS_MASTER_URI=http://hcx-pc:11311
但是以上设置只能在输入的终端中生效,为了让所有打开的终端都能识别,最好使用如下命令将环境变量的设置加入终端的配置文件中
echo "export ROS_MASTER_URI=http://hcx-pc:11311" >> ~/.bashrc
多机通信测试
使用小乌龟例程进行测试
roscore rosrun turtlesim turtlesim_node
从机raspi2上使用“rostopic list”命令查看ROS系统中的话题列表
在从机raspi2上发布一只小乌龟的速度控制消息
rostopic pub -1 /turtle1/cmd_vel geometry_msgs/Twist -- ‘[0.5, 0.0, 0.0]' '[0.0, 0.0, 0.5]'
主机hcx-pc中的小乌龟应该就开始移动了,ROS多机系统配置成功