位置: IT常识 - 正文

最全ROS 入门(ros入门21讲)

编辑:rootadmin
最全ROS 入门

目录

简介

ROS诞生背景

ROS的设计目标

ROS与ROS2

安装ROS

1.配置ubuntu的软件和更新

2.设置安装源

3.设置key

4.安装

5.配置环境变量

安装可能出现的问题

安装构建依赖

卸载

ROS架构

1.设计者

2.维护者

3. 立足系统架构: ROS 可以划分为三层

ROS通信机制

话题通信

理论模型

流程

通信样例

自定义消息的通信

服务通信

理论模型

服务通信自定义srv

参数服务器

理论模型

参数操作

常用命令

简介

rosnode

rostopic

rosmsg

rosservice

rossrv

rosparam

常用API

初始化

话题与服务相关对象

C++

回旋函数

C++

对比

时间

1.时刻

2.持续时间

3.持续时间与时刻运算

4.设置运行频率(非常常用)

5.定时器

其他

ROS 的运行管理

ROS元功能包

概念

作用

实现

ROS节点运行管理launch文件

概念

作用

使用

launch文件 结构

ROS工作空间覆盖

实现

原因

结论

隐患

ROS节点名称重名

rosrun设置命名空间与重映射

launch文件设置命名空间与重映射

编码设置命名空间与重映射

ROS话题名称重复

概念

rosrun设置话题重映射

launch文件设置话题重映射

编码设置话题名称

ROS参数名称重复

概念

rosrun设置参数

launch文件设置参数

编码设置参数

ROS分布式通信

实现

ROS 常用组件

TF坐标变换

rosbag

概念

作用

本质

rosbag使用_命令行

rosbag使用_编码

ROS进阶通信

action通信

概念

作用

action通信自定义action文件

动态参数

概念

作用

动态参数客户端

动态参数服务端A(C++)

pluginlib

概念

作用

实现流程:

创建基类

创建插件

注册插件

构建插件库

使插件可用于ROS工具链

Nodelet

概念

作用

流程说明

准备

创建插件类并注册插件

构建插件库

使插件可用于ROS工具链


简介ROS诞生背景

机器人是一种高度复杂的系统性实现,机器人设计包含了机械加工、机械结构设计、硬件设计、嵌入式软件设计、上层软件设计....是各种硬件与软件集成,甚至可以说机器人系统是当今工业体系的集大成者。

机器人体系是相当庞大的,其复杂度之高,以至于没有任何个人、组织甚至公司能够独立完成系统性的机器人研发工作。

一种更合适的策略是:让机器人研发者专注于自己擅长的领域,其他模块则直接复用相关领域更专业研发团队的实现,当然自身的研究也可以被他人继续复用。这种基于"复用"的分工协作,遵循了不重复发明轮子的原则,显然是可以大大提高机器人的研发效率的,尤其是随着机器人硬件越来越丰富,软件库越来越庞大,这种复用性和模块化开发需求也愈发强烈。

ROS的设计目标代码复用:ROS的目标不是成为具有最多功能的框架,ROS的主要目标是支持机器人技术研发中的代码重用。分布式:ROS是进程(也称为Nodes)的分布式框架,ROS中的进程可分布于不同主机,不同主机协同工作,从而分散计算压力松耦合:ROS中功能模块封装于独立的功能包或元功能包,便于分享,功能包内的模块以节点为单位运行,以ROS标准的IO作为接口,开发者不需要关注模块内部实现,只要了解接口规则就能实现复用,实现了模块间点对点的松耦合连接精简:ROS被设计为尽可能精简,以便为ROS编写的代码可以与其他机器人软件框架一起使用。ROS易于与其他机器人软件框架集成:ROS已与OpenRAVE,Orocos和Player集成。语言独立性:包括Java,C++,Python等。为了支持更多应用开发和移植,ROS设计为一种语言弱相关的框架结构,使用简洁,中立的定义语言描述模块间的消息接口,在编译中再产生所使用语言的目标文件,为消息交互提供支持,同时允许消息接口的嵌套使用易于测试:ROS具有称为rostest的内置单元/集成测试框架,可轻松安装和拆卸测试工具。大型应用:ROS适用于大型运行时系统和大型开发流程。丰富的组件化工具包:ROS可采用组件化方式集成一些工具和软件到系统中并作为一个组件直接使用,如RVIZ(3D可视化工具),开发者根据ROS定义的接口在其中显示机器人模型等,组件还包括仿真环境和消息查看工具等免费且开源:开发者众多,功能包多ROS与ROS2

ROS 目前已经发布了ROS1 的终极版本: noetic,并建议后期过渡至 ROS2 版本。noetic 版本之前默认使用的是 Python2,noetic 支持 Python3。

ROS本身存在很多问题,比如:

强依赖master节点,一旦master节点挂掉,通信就可能出现问题。通信基于TCP实现,实时性差、系统开销大消息机制不兼容,想要使用protobuf就需要改其中的源码没有加密机制、安全性不高

而ROS2做了很多的改进:

从原来的只支持linux平台变成了支持windows、mac甚至是嵌入式RTOS平台去中心化master,ROS和ROS2中间件不同之处在于,ROS2取消了master节点。去中心化后,各个节点之间可以通过DDS的节点相互发现,各个节点都是平等的,且可以1对1、1对n、n对n进行互相通信。通信直接更换为DDS进行实现,这里的DDS不是具体的哪一个模块,而是一种协议。编译系统的改进(catkin到ament)软件包更新到c++11可用Python编写的Launch文件多机器人协同通信支持支持安全加密通信同一个进程支持多个节点支持Qos服务质量支持节点生命周期管理安装ROS

Ubuntu 安装完毕后,就可以安装 ROS 操作系统了,大致步骤如下:

配置ubuntu的软件和更新;设置安装源;设置key;安装;配置环境变量。
1.配置ubuntu的软件和更新

配置ubuntu的软件和更新,允许安装不经认证的软件。

首先打开“软件和更新”对话框,具体可以在 Ubuntu 搜索按钮中搜索。

打开后按照下图进行配置(确保勾选了"restricted", "universe," 和 "multiverse.")

2.设置安装源

官方默认安装源:

sudo sh -c 'echo "deb http://packages.ros.org/ros/ubuntu $(lsb_release -sc) main" > /etc/apt/sources.list.d/ros-latest.list'

或来自国内清华的安装源

sudo sh -c '. /etc/lsb-release && echo "deb http://mirrors.tuna.tsinghua.edu.cn/ros/ubuntu/ `lsb_release -cs` main" > /etc/apt/sources.list.d/ros-latest.list'

或来自国内中科大的安装源

sudo sh -c '. /etc/lsb-release && echo "deb http://mirrors.ustc.edu.cn/ros/ubuntu/ `lsb_release -cs` main" > /etc/apt/sources.list.d/ros-latest.list'

PS:

回车后,可能需要输入管理员密码建议使用国内资源,安装速度更快。3.设置key

sudo apt-key adv --keyserver 'hkp://keyserver.ubuntu.com:80' --recv-key C1CF6E31E6BADE8868B172B4F42ED6FBAB17C654

4.安装

首先需要更新 apt(以前是 apt-get, 官方建议使用 apt 而非 apt-get),apt 是用于从互联网仓库搜索、安装、升级、卸载软件或操作系统的工具。

sudo apt update

等待...

然后,再安装所需类型的 ROS:ROS 多个类型:Desktop-Full、Desktop、ROS-Base。这里介绍较为常用的Desktop-Full(官方推荐)安装: ROS, rqt, rviz, robot-generic libraries, 2D/3D simulators, navigation and 2D/3D perception

sudo apt install ros-noetic-desktop-full

等待......(比较耗时)

友情提示: 由于网络原因,导致连接超时,可能会安装失败,如下所示:

可以多次重复调用 更新 和 安装命令,直至成功。

5.配置环境变量

配置环境变量,方便在任意 终端中使用 ROS。

echo "source /opt/ros/noetic/setup.bash" >> ~/.bashrc source ~/.bashrc

安装可能出现的问题安装构建依赖

在 noetic 最初发布时,和其他历史版本稍有差异的是:没有安装构建依赖这一步骤。随着 noetic 不断完善,官方补齐了这一操作。

首先安装构建依赖的相关工具

sudo apt install python3-rosdep python3-rosinstall python3-rosinstall-generator python3-wstool build-essential

ROS中使用许多工具前,要求需要初始化rosdep(可以安装系统依赖) -- 上一步实现已经安装过了。

sudo apt install python3-rosdep

初始化rosdep

sudo rosdep init rosdep update

如果一切顺利的话,rosdep 初始化与更新的打印结果如下:


但是,在 rosdep 初始化时,多半会抛出异常。

问题:

原因:

境外资源被屏蔽。

解决:

百度或google搜索,解决方式有多种(https://github.com/ros/rosdistro/issues/9721),可惜在 ubuntu20.04 下,集体失效。

新思路:将相关资源备份到 gitee,修改 rosdep 源码,重新定位资源。

实现:

1.先打开资源备份路径:赵虚左/rosdistro,打开 rosdistro/rosdep/sources.list.d/20-default.list文件留作备用(主要是复用URL的部分内容:gitee.com/zhao-xuzuo/rosdistro/raw/master)。

2.进入"/usr/lib/python3/dist-packages/" 查找rosdep中和raw.githubusercontent.com相关的内容,调用命令:

find . -type f | xargs grep "raw.githubusercontent"

3.修改相关文件,主要有: ./rosdistro/__init__.py、./rosdep2/gbpdistro_support.py、./rosdep2/sources_list.py 、./rosdep2/rep3.py。可以使用sudo gedit命令修改文件:

文件中涉及的 URL 内容,如果是:raw.githubusercontent.com/ros/rosdistro/master都替换成步骤1中准备的gitee.com/zhao-xuzuo/rosdistro/raw/master即可。

修改完毕,再重新执行命令:

sudo rosdep init rosdep update

就可以正常实现 rosdep 的初始化与更新了。


卸载

如果需要卸载ROS可以调用如下命令:

sudo apt remove ros-noetic-*

注意: 在 ROS 版本 noetic 中无需构建软件包的依赖关系,没有rosdep的相关安装与配置。


另请参考:noetic/Installation/Ubuntu - ROS Wiki。

ROS架构

一般我们可以从设计者、维护者、系统结构与自身结构4个角度来描述ROS结构:

1.设计者

ROS设计者将ROS表述为“ROS = Plumbing + Tools + Capabilities + Ecosystem”

Plumbing: 通讯机制(实现ROS不同节点之间的交互)Tools :工具软件包(ROS中的开发和调试工具)Capabilities :机器人高层技能(ROS中某些功能的集合,比如:导航)Ecosystem:机器人生态系统(跨地域、跨软件与硬件的ROS联盟)2.维护者

立足维护者的角度: ROS 架构可划分为两大部分

main:核心部分,主要由Willow Garage 和一些开发者设计、提供以及维护。它提供了一些分布式计算的基本工具,以及整个ROS的核心部分的程序编写。universe:全球范围的代码,有不同国家的ROS社区组织开发和维护。一种是库的代码,如OpenCV、PCL等;库的上一层是从功能角度提供的代码,如人脸识别,他们调用下层的库;最上层的代码是应用级的代码,让机器人完成某一确定的功能。3. 立足系统架构: ROS 可以划分为三层OS 层,也即经典意义的操作系统ROS 只是元操作系统,需要依托真正意义的操作系统,目前兼容性最好的是 Linux 的 Ubuntu,Mac、Windows 也支持 ROS 的较新版本中间层是 ROS 封装的关于机器人开发的中间件,比如:基于 TCP/UDP 继续封装的 TCPROS/UDPROS 通信系统用于进程间通信 Nodelet,为数据的实时性传输提供支持另外,还提供了大量的机器人开发实现库,如:数据类型定义、坐标变换、运动控制....应用层功能包,以及功能包内的节点,比如: master、turtlesim的控制与运动节点...ROS通信机制话题通信

话题通信是ROS中使用频率最高的一种通信模式,话题通信是基于发布订阅模式的,也即:一个节点发布消息,另一个节点订阅该消息。话题通信的应用场景也极其广泛。

用于不断更新的、少逻辑处理的数据传输场景。

理论模型

话题通信实现模型是比较复杂的,该模型如下图所示,该模型中涉及到三个角色:

ROS Master (管理者)Talker (发布者)Listener (订阅者)

ROS Master 负责保管 Talker 和 Listener 注册的信息,并匹配话题相同的 Talker 与 Listener,帮助 Talker 与 Listener 建立连接,连接建立后,Talker 可以发布消息,且发布的消息会被 Listener 订阅。

流程

推荐整理分享最全ROS 入门(ros入门21讲),希望有所帮助,仅作参考,欢迎阅读内容。

文章相关热门搜索词:ros快速入门,ros教程推荐,ros快速入门,ros入门中文官方教程,ros教程推荐,ros教程推荐,ros入门21讲,ros入门教程,内容如对您有帮助,希望把文章链接给更多的朋友!

0.Talker注册

Talker启动后,会通过RPC在 ROS Master 中注册自身信息,其中包含所发布消息的话题名称。ROS Master 会将节点的注册信息加入到注册表中。

1.Listener注册

Listener启动后,也会通过RPC在 ROS Master 中注册自身信息,包含需要订阅消息的话题名。ROS Master 会将节点的注册信息加入到注册表中。

2.ROS Master实现信息匹配

ROS Master 会根据注册表中的信息匹配Talker 和 Listener,并通过 RPC 向 Listener 发送 Talker 的 RPC 地址信息。

3.Listener向Talker发送请求

Listener 根据接收到的 RPC 地址,通过 RPC 向 Talker 发送连接请求,传输订阅的话题名称、消息类型以及通信协议(TCP/UDP)。

4.Talker确认请求

Talker 接收到 Listener 的请求后,也是通过 RPC 向 Listener 确认连接信息,并发送自身的 TCP 地址信息。

5.Listener与Talker件里连接

Listener 根据步骤4 返回的消息使用 TCP 与 Talker 建立网络连接。

6.Talker向Listener发送消息

连接建立后,Talker 开始向 Listener 发布消息。

注意1:上述实现流程中,前五步使用的 RPC协议,最后两步使用的是 TCP 协议

注意2: Talker 与 Listener 的启动无先后顺序要求

注意3: Talker 与 Listener 都可以有多个

注意4: Talker 与 Listener 连接建立后,不再需要 ROS Master。也即,即便关闭ROS Master,Talker 与 Listern 照常通信。

通信样例

发布方

/*需求: 实现基本的话题通信,一方发布数据,一方接收数据,实现的关键点:1.发送方2.接收方3.数据(此处为普通文本)PS: 二者需要设置相同的话题消息发布方:循环发布信息:HelloWorld 后缀数字编号实现流程:1.包含头文件2.初始化 ROS 节点:命名(唯一)3.实例化 ROS 句柄4.实例化 发布者 对象5.组织被发布的数据,并编写逻辑发布数据*/// 1.包含头文件#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);//5.组织被发布的数据,并编写逻辑发布数据//数据(动态组织)std_msgs::String msg;// msg.data = "你好啊!!!";std::string msg_front = "Hello 你好!"; //消息前缀int count = 0; //消息计数器//逻辑(一秒10次)ros::Rate r(1);//节点不死while (ros::ok()){//使用 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;}

订阅方

/*需求: 实现基本的话题通信,一方发布数据,一方接收数据,实现的关键点:1.发送方2.接收方3.数据(此处为普通文本)消息订阅方:订阅话题并打印接收到的消息实现流程:1.包含头文件2.初始化 ROS 节点:命名(唯一)3.实例化 ROS 句柄4.实例化 订阅者 对象5.处理订阅的消息(回调函数)6.设置循环调用回调函数*/// 1.包含头文件#include "ros/ros.h"#include "std_msgs/String.h"void doMsg(const std_msgs::String::ConstPtr& msg_p){ROS_INFO("我听见:%s",msg_p->data.c_str());// ROS_INFO("我听见:%s",(*msg_p).data.c_str());}int main(int argc, char *argv[]){setlocale(LC_ALL,"");//2.初始化 ROS 节点:命名(唯一)ros::init(argc,argv,"listener");//3.实例化 ROS 句柄ros::NodeHandle nh;//4.实例化 订阅者 对象ros::Subscriber sub = nh.subscribe<std_msgs::String>("chatter",10,doMsg);//5.处理订阅的消息(回调函数)// 6.设置循环调用回调函数ros::spin();//循环读取接收的数据,并调用回调函数处理return 0;}

CmakeList

add_executable(Hello_pubsrc/Hello_pub.cpp)add_executable(Hello_subsrc/Hello_sub.cpp)target_link_libraries(Hello_pub${catkin_LIBRARIES})target_link_libraries(Hello_sub${catkin_LIBRARIES})

执行过程

1.启动 roscore;

2.启动发布节点;

3.启动订阅节点。

自定义消息的通信

简介

在 ROS 通信协议中,数据载体是一个较为重要组成部分,ROS 中通过 std_msgs 封装了一些原生的数据类型,比如:String、Int32、Int64、Char、Bool、Empty.... 但是,这些数据一般只包含一个 data 字段,结构的单一意味着功能上的局限性,当传输一些复杂的数据,比如: 激光雷达的信息... std_msgs 由于描述性较差而显得力不从心,这种场景下可以使用自定义的消息类型

msgs只是简单的文本文件,每行具有字段类型和字段名称,可以使用的字段类型有:

int8, int16, int32, int64 (或者无符号类型: uint*)float32, float64stringtime, durationother msg filesvariable-length array[] and fixed-length array[C]

ROS中还有一种特殊类型:Header,标头包含时间戳和ROS中常用的坐标帧信息。会经常看到msg文件的第一行具有Header标头。

1.定义msg文件

string nameuint16 agefloat64 height

2.编辑配置文件

<build_depend>message_generation</build_depend><exec_depend>message_runtime</exec_depend><!--exce_depend 以前对应的是 run_depend 现在非法-->find_package(catkin REQUIRED COMPONENTSroscpprospystd_msgsmessage_generation)# 需要加入 message_generation,必须有 std_msgs## 配置 msg 源文件add_message_files(FILESPerson.msg)# 生成消息时依赖于 std_msgsgenerate_messages(DEPENDENCIESstd_msgs)#执行时依赖catkin_package(# INCLUDE_DIRS include# LIBRARIES demo02_talker_listenerCATKIN_DEPENDS roscpp rospy std_msgs message_runtime# DEPENDS system_lib)

发布方

/*需求: 循环发布人的信息*/#include "ros/ros.h"#include "demo02_talker_listener/Person.h"int main(int argc, char *argv[]){setlocale(LC_ALL,"");//1.初始化 ROS 节点ros::init(argc,argv,"talker_person");//2.创建 ROS 句柄ros::NodeHandle nh;//3.创建发布者对象ros::Publisher pub = nh.advertise<demo02_talker_listener::Person>("chatter_person",1000);//4.组织被发布的消息,编写发布逻辑并发布消息demo02_talker_listener::Person p;p.name = "sunwukong";p.age = 2000;p.height = 1.45;ros::Rate r(1);while (ros::ok()){pub.publish(p);p.age += 1;ROS_INFO("我叫:%s,今年%d岁,高%.2f米", p.name.c_str(), p.age, p.height);r.sleep();ros::spinOnce();}return 0;}

订阅方

/*需求: 订阅人的信息*/#include "ros/ros.h"#include "demo02_talker_listener/Person.h"void doPerson(const demo02_talker_listener::Person::ConstPtr& person_p){ROS_INFO("订阅的人信息:%s, %d, %.2f", person_p->name.c_str(), person_p->age, person_p->height);}int main(int argc, char *argv[]){setlocale(LC_ALL,"");//1.初始化 ROS 节点ros::init(argc,argv,"listener_person");//2.创建 ROS 句柄ros::NodeHandle nh;//3.创建订阅对象ros::Subscriber sub = nh.subscribe<demo02_talker_listener::Person>("chatter_person",10,doPerson);//4.回调函数中处理 person//5.ros::spin();ros::spin();return 0;}

Cmakelist

add_executable(person_talker src/person_talker.cpp)add_executable(person_listener src/person_listener.cpp)add_dependencies(person_talker ${PROJECT_NAME}_generate_messages_cpp)add_dependencies(person_listener ${PROJECT_NAME}_generate_messages_cpp)target_link_libraries(person_talker${catkin_LIBRARIES})target_link_libraries(person_listener${catkin_LIBRARIES})

Vscode配置

为了方便代码提示以及避免误抛异常,需要先配置 vscode,将前面生成的 head 文件路径配置进 c_cpp_properties.json 的 includepath属性:

{"configurations": [{"browse": {"databaseFilename": "","limitSymbolsToIncludedHeaders": true},"includePath": ["/opt/ros/noetic/include/**","/usr/include/**","/xxx/yyy工作空间/devel/include/**" //配置 head 文件的路径],"name": "ROS","intelliSenseMode": "gcc-x64","compilerPath": "/usr/bin/gcc","cStandard": "c11","cppStandard": "c++17"}],"version": 4}服务通信

服务通信也是ROS中一种极其常用的通信模式,服务通信是基于请求响应模式的,是一种应答机制。也即: 一个节点A向另一个节点B发送请求,B接收处理请求并产生响应结果返回给A。比如如下场景:

机器人巡逻过程中,控制系统分析传感器数据发现可疑物体或人... 此时需要拍摄照片并留存。

在上述场景中,就使用到了服务通信。

一个节点需要向相机节点发送拍照请求,相机节点处理请求,并返回处理结果

与上述应用类似的,服务通信更适用于对时时性有要求、具有一定逻辑处理的应用场景。

理论模型

服务通信较之于话题通信更简单些,理论模型如下图所示,该模型中涉及到三个角色:

ROS master(管理者)Server(服务端)Client(客户端)

ROS Master 负责保管 Server 和 Client 注册的信息,并匹配话题相同的 Server 与 Client ,帮助 Server 与 Client 建立连接,连接建立后,Client 发送请求信息,Server 返回响应信息。

整个流程由以下步骤实现:

0.Server注册

Server 启动后,会通过RPC在 ROS Master 中注册自身信息,其中包含提供的服务的名称。ROS Master 会将节点的注册信息加入到注册表中。

1.Client注册

Client 启动后,也会通过RPC在 ROS Master 中注册自身信息,包含需要请求的服务的名称。ROS Master 会将节点的注册信息加入到注册表中。

2.ROS Master实现信息匹配

ROS Master 会根据注册表中的信息匹配Server和 Client,并通过 RPC 向 Client 发送 Server 的 TCP地址信息。

最全ROS 入门(ros入门21讲)

3.Client发送请求

Client 根据步骤2 响应的信息,使用 TCP 与 Server 建立网络连接,并发送请求数据。

4.Server发送响应

Server 接收、解析请求的数据,并产生响应结果返回给 Client。

注意:

1.客户端请求被处理时,需要保证服务器已经启动;

2.服务端和客户端都可以存在多个。

服务通信自定义srv

流程:

srv 文件内的可用数据类型与 msg 文件一致,且定义 srv 实现流程与自定义 msg 实现流程类似:

按照固定格式创建srv文件编辑配置文件编译生成中间文件

1.定义srv文件

服务通信中,数据分成两部分,请求与响应,在 srv 文件中请求和响应使用---分割,具体实现如下:

功能包下新建 srv 目录,添加 xxx.srv 文件,内容:

# 客户端请求时发送的两个数字int32 num1int32 num2---# 服务器响应发送的数据int32 sum

2.编辑配置文件

package.xml中添加编译依赖与执行依赖

<build_depend>message_generation</build_depend><exec_depend>message_runtime</exec_depend><!-- exce_depend 以前对应的是 run_depend 现在非法 -->

CMakeLists.txt编辑 srv 相关配置

find_package(catkin REQUIRED COMPONENTSroscpprospystd_msgsmessage_generation)# 需要加入 message_generation,必须有 std_msgsadd_service_files(FILESAddInts.srv)generate_messages(DEPENDENCIESstd_msgs)

3.编译

编译后的中间文件查看:

C++ 需要调用的中间文件(.../工作空间/devel/include/包名/xxx.h)

Python 需要调用的中间文件(.../工作空间/devel/lib/python3/dist-packages/包名/srv)

后续调用相关 srv 时,是从这些中间文件调用的

4. 服务端

/*需求:编写两个节点实现服务通信,客户端节点需要提交两个整数到服务器服务器需要解析客户端提交的数据,相加后,将结果响应回客户端,客户端再解析服务器实现:1.包含头文件2.初始化 ROS 节点3.创建 ROS 句柄4.创建 服务 对象5.回调函数处理请求并产生响应6.由于请求有多个,需要调用 ros::spin()*/#include "ros/ros.h"#include "demo03_server_client/AddInts.h"// bool 返回值由于标志是否处理成功bool doReq(demo03_server_client::AddInts::Request& req,demo03_server_client::AddInts::Response& resp){int num1 = req.num1;int num2 = req.num2;ROS_INFO("服务器接收到的请求数据为:num1 = %d, num2 = %d",num1, num2);//逻辑处理if (num1 < 0 || num2 < 0){ROS_ERROR("提交的数据异常:数据不可以为负数");return false;}//如果没有异常,那么相加并将结果赋值给 respresp.sum = num1 + num2;return true;}int main(int argc, char *argv[]){setlocale(LC_ALL,"");// 2.初始化 ROS 节点ros::init(argc,argv,"AddInts_Server");// 3.创建 ROS 句柄ros::NodeHandle nh;// 4.创建 服务 对象ros::ServiceServer server = nh.advertiseService("AddInts",doReq);ROS_INFO("服务已经启动....");// 5.回调函数处理请求并产生响应// 6.由于请求有多个,需要调用 ros::spin()ros::spin();return 0;}

5. 客户端

/*需求:编写两个节点实现服务通信,客户端节点需要提交两个整数到服务器服务器需要解析客户端提交的数据,相加后,将结果响应回客户端,客户端再解析服务器实现:1.包含头文件2.初始化 ROS 节点3.创建 ROS 句柄4.创建 客户端 对象5.请求服务,接收响应*/// 1.包含头文件#include "ros/ros.h"#include "demo03_server_client/AddInts.h"int main(int argc, char *argv[]){setlocale(LC_ALL,"");// 调用时动态传值,如果通过 launch 的 args 传参,需要传递的参数个数 +3if (argc != 3)// if (argc != 5)//launch 传参(0-文件路径 1传入的参数 2传入的参数 3节点名称 4日志路径){ROS_ERROR("请提交两个整数");return 1;}// 2.初始化 ROS 节点ros::init(argc,argv,"AddInts_Client");// 3.创建 ROS 句柄ros::NodeHandle nh;// 4.创建 客户端 对象ros::ServiceClient client = nh.serviceClient<demo03_server_client::AddInts>("AddInts");//等待服务启动成功//方式1ros::service::waitForService("AddInts");//方式2// client.waitForExistence();// 5.组织请求数据demo03_server_client::AddInts ai;ai.request.num1 = atoi(argv[1]);ai.request.num2 = atoi(argv[2]);// 6.发送请求,返回 bool 值,标记是否成功bool flag = client.call(ai);// 7.处理响应if (flag){ROS_INFO("请求正常处理,响应结果:%d",ai.response.sum);}else{ROS_ERROR("请求处理失败....");return 1;}return 0;}

6. CmakeList

add_executable(AddInts_Server src/AddInts_Server.cpp)add_executable(AddInts_Client src/AddInts_Client.cpp)add_dependencies(AddInts_Server ${PROJECT_NAME}_gencpp)add_dependencies(AddInts_Client ${PROJECT_NAME}_gencpp)target_link_libraries(AddInts_Server${catkin_LIBRARIES})target_link_libraries(AddInts_Client${catkin_LIBRARIES})

7. 注意(客户端等待服务端连接)

在客户端发送请求前添加:client.waitForExistence();

或:ros::service::waitForService("AddInts");

这是一个阻塞式函数,只有服务启动成功后才会继续执行

此处可以使用 launch 文件优化,但是需要注意 args 传参特点

参数服务器

简单的说就是全局变量。

参数服务器在ROS中主要用于实现不同节点之间的数据共享。参数服务器相当于是独立于所有节点的一个公共容器,可以将数据存储在该容器中,被不同的节点调用,当然不同的节点也可以往其中存储数据,关于参数服务器的典型应用场景如下:

导航实现时,会进行路径规划,比如: 全局路径规划,设计一个从出发点到目标点的大致路径。本地路径规划,会根据当前路况生成时时的行进路径

上述场景中,全局路径规划和本地路径规划时,就会使用到参数服务器:

路径规划时,需要参考小车的尺寸,我们可以将这些尺寸信息存储到参数服务器,全局路径规划节点与本地路径规划节点都可以从参数服务器中调用这些参数

参数服务器,一般适用于存在数据共享的一些应用场景。

理论模型

参数服务器实现是最为简单的,该模型如下图所示,该模型中涉及到三个角色:

ROS Master (管理者)Talker (参数设置者)Listener (参数调用者)

ROS Master 作为一个公共容器保存参数,Talker 可以向容器中设置参数,Listener 可以获取参数。

整个流程由以下步骤实现:

1.Talker 设置参数

Talker 通过 RPC 向参数服务器发送参数(包括参数名与参数值),ROS Master 将参数保存到参数列表中。

2.Listener 获取参数

Listener 通过 RPC 向参数服务器发送参数查找请求,请求中包含要查找的参数名。

3.ROS Master 向 Listener 发送参数值

ROS Master 根据步骤2请求提供的参数名查找参数值,并将查询结果通过 RPC 发送给 Listener。


参数可使用数据类型:

32-bit integersbooleansstringsdoublesiso8601 dateslistsbase64-encoded binary data字典参数操作

设置

在 C++ 中实现参数服务器数据的增删改查,可以通过两套 API 实现:

ros::NodeHandleros::param/*参数服务器操作之新增与修改(二者API一样)_C++实现:在 roscpp 中提供了两套 API 实现参数操作ros::NodeHandlesetParam("键",值)ros::paramset("键","值")示例:分别设置整形、浮点、字符串、bool、列表、字典等类型参数修改(相同的键,不同的值)*/#include "ros/ros.h"int main(int argc, char *argv[]){ros::init(argc,argv,"set_update_param");std::vector<std::string> stus;stus.push_back("zhangsan");stus.push_back("李四");stus.push_back("王五");stus.push_back("孙大脑袋");std::map<std::string,std::string> friends;friends["guo"] = "huang";friends["yuang"] = "xiao";//NodeHandle--------------------------------------------------------ros::NodeHandle nh;nh.setParam("nh_int",10); //整型nh.setParam("nh_double",3.14); //浮点型nh.setParam("nh_bool",true); //boolnh.setParam("nh_string","hello NodeHandle"); //字符串nh.setParam("nh_vector",stus); // vectornh.setParam("nh_map",friends); // map//修改演示(相同的键,不同的值)nh.setParam("nh_int",10000);//param--------------------------------------------------------ros::param::set("param_int",20);ros::param::set("param_double",3.14);ros::param::set("param_string","Hello Param");ros::param::set("param_bool",false);ros::param::set("param_vector",stus);ros::param::set("param_map",friends);//修改演示(相同的键,不同的值)ros::param::set("param_int",20000);return 0;}

获取

在 roscpp 中提供了两套 API 实现参数操作

ros::NodeHandle

param(键,默认值)

存在,返回对应结果,否则返回默认值

getParam(键,存储结果的变量)

存在,返回 true,且将值赋值给参数2

若果键不存在,那么返回值为 false,且不为参数2赋值

getParamCached键,存储结果的变量)--提高变量获取效率

存在,返回 true,且将值赋值给参数2

若果键不存在,那么返回值为 false,且不为参数2赋值

getParamNames(std::vector<std::string>)

获取所有的键,并存储在参数 vector 中

hasParam(键)

是否包含某个键,存在返回 true,否则返回 false

searchParam(参数1,参数2)

搜索键,参数1是被搜索的键,参数2存储搜索结果的变量

ros::param ----- 与 NodeHandle 类似

#include "ros/ros.h"int main(int argc, char *argv[]){setlocale(LC_ALL,"");ros::init(argc,argv,"get_param");//NodeHandle--------------------------------------------------------/*ros::NodeHandle nh;// param 函数int res1 = nh.param("nh_int",100); // 键存在int res2 = nh.param("nh_int2",100); // 键不存在ROS_INFO("param获取结果:%d,%d",res1,res2);// getParam 函数int nh_int_value;double nh_double_value;bool nh_bool_value;std::string nh_string_value;std::vector<std::string> stus;std::map<std::string, std::string> friends;nh.getParam("nh_int",nh_int_value);nh.getParam("nh_double",nh_double_value);nh.getParam("nh_bool",nh_bool_value);nh.getParam("nh_string",nh_string_value);nh.getParam("nh_vector",stus);nh.getParam("nh_map",friends);ROS_INFO("getParam获取的结果:%d,%.2f,%s,%d",nh_int_value,nh_double_value,nh_string_value.c_str(),nh_bool_value);for (auto &&stu : stus){ROS_INFO("stus 元素:%s",stu.c_str());}for (auto &&f : friends){ROS_INFO("map 元素:%s = %s",f.first.c_str(), f.second.c_str());}// getParamCached()nh.getParamCached("nh_int",nh_int_value);ROS_INFO("通过缓存获取数据:%d",nh_int_value);//getParamNames()std::vector<std::string> param_names1;nh.getParamNames(param_names1);for (auto &&name : param_names1){ROS_INFO("名称解析name = %s",name.c_str());}ROS_INFO("----------------------------");ROS_INFO("存在 nh_int 吗? %d",nh.hasParam("nh_int"));ROS_INFO("存在 nh_intttt 吗? %d",nh.hasParam("nh_intttt"));std::string key;nh.searchParam("nh_int",key);ROS_INFO("搜索键:%s",key.c_str());*///param--------------------------------------------------------ROS_INFO("++++++++++++++++++++++++++++++++++++++++");int res3 = ros::param::param("param_int",20); //存在int res4 = ros::param::param("param_int2",20); // 不存在返回默认ROS_INFO("param获取结果:%d,%d",res3,res4);// getParam 函数int param_int_value;double param_double_value;bool param_bool_value;std::string param_string_value;std::vector<std::string> param_stus;std::map<std::string, std::string> param_friends;ros::param::get("param_int",param_int_value);ros::param::get("param_double",param_double_value);ros::param::get("param_bool",param_bool_value);ros::param::get("param_string",param_string_value);ros::param::get("param_vector",param_stus);ros::param::get("param_map",param_friends);ROS_INFO("getParam获取的结果:%d,%.2f,%s,%d",param_int_value,param_double_value,param_string_value.c_str(),param_bool_value);for (auto &&stu : param_stus){ROS_INFO("stus 元素:%s",stu.c_str());}for (auto &&f : param_friends){ROS_INFO("map 元素:%s = %s",f.first.c_str(), f.second.c_str());}// getParamCached()ros::param::getCached("param_int",param_int_value);ROS_INFO("通过缓存获取数据:%d",param_int_value);//getParamNames()std::vector<std::string> param_names2;ros::param::getParamNames(param_names2);for (auto &&name : param_names2){ROS_INFO("名称解析name = %s",name.c_str());}ROS_INFO("----------------------------");ROS_INFO("存在 param_int 吗? %d",ros::param::has("param_int"));ROS_INFO("存在 param_intttt 吗? %d",ros::param::has("param_intttt"));std::string key;ros::param::search("param_int",key);ROS_INFO("搜索键:%s",key.c_str());return 0;}

删除

ros::NodeHandle

deleteParam("键")

根据键删除参数,删除成功,返回 true,否则(参数不存在),返回 false

ros::param

del("键")

根据键删除参数,删除成功,返回 true,否则(参数不存在),返回 false

#include "ros/ros.h"int main(int argc, char *argv[]){setlocale(LC_ALL,"");ros::init(argc,argv,"delete_param");ros::NodeHandle nh;bool r1 = nh.deleteParam("nh_int");ROS_INFO("nh 删除结果:%d",r1);bool r2 = ros::param::del("param_int");ROS_INFO("param 删除结果:%d",r2);return 0;}常用命令简介

在 ROS 同提供了一些实用的命令行工具,可以用于获取不同节点的各类信息,常用的命令如下:

rosnode : 操作节点rostopic : 操作话题rosservice : 操作服务rosmsg : 操作msg消息rossrv : 操作srv消息rosparam : 操作参数

ROS/CommandLineTools - ROS Wiki

rosnode

rosnode 是用于获取节点信息的命令

rosnode ping 测试到节点的连接状态rosnode list 列出活动节点rosnode info 打印节点信息rosnode machine 列出指定设备上节点rosnode kill 杀死某个节点rosnode cleanup 清除不可连接的节点rosnode ping测试到节点的连接状态rosnode list列出活动节点rosnode info打印节点信息rosnode machine列出指定设备上的节点rosnode kill杀死某个节点rosnode cleanup清除无用节点,启动乌龟节点,然后 ctrl + c 关闭,该节点并没被彻底清除,可以使用 cleanup 清除节点rostopic

rostopic包含rostopic命令行工具,用于显示有关ROS 主题的调试信息,包括发布者,订阅者,发布频率和ROS消息。它还包含一个实验性Python库,用于动态获取有关主题的信息并与之交互。

rostopic bw 显示主题使用的带宽rostopic delay 显示带有 header 的主题延迟rostopic echo 打印消息到屏幕rostopic find 根据类型查找主题rostopic hz 显示主题的发布频率rostopic info 显示主题相关信息rostopic list 显示所有活动状态下的主题rostopic pub 将数据发布到主题rostopic type 打印主题类型rosmsg

rosmsg是用于显示有关 ROS消息类型的 信息的命令行工具。

rosmsg show 显示消息描述rosmsg info 显示消息信息rosmsg list 列出所有消息rosmsg md5 显示 md5 加密后的消息rosmsg package 显示某个功能包下的所有消息rosmsg packages 列出包含消息的功能包rosservicerosservice args 打印服务参数rosservice call 使用提供的参数调用服务rosservice find 按照服务类型查找服务rosservice info 打印有关服务的信息rosservice list 列出所有活动的服务rosservice type 打印服务类型rosservice uri 打印服务的 ROSRPC urirossrv

对标rosmsg

rossrv是用于显示有关ROS服务类型的信息的命令行工具,与 rosmsg 使用语法高度雷同。

rossrv show 显示服务消息详情rossrv info 显示服务消息相关信息rossrv list 列出所有服务信息rossrv md5 显示 md5 加密后的服务消息rossrv package 显示某个包下所有服务消息rossrv packages 显示包含服务消息的所有包rosparamrosparam set 设置参数rosparam get 获取参数rosparam load 从外部文件加载参数rosparam dump 将参数写出到外部文件rosparam delete 删除参数rosparam list 列出所有参数常用API初始化/** @brief ROS初始化函数。** 该函数可以解析并使用节点启动时传入的参数(通过参数设置节点名称、命名空间...)** 该函数有多个重载版本,如果使用NodeHandle建议调用该版本。** \param argc 参数个数* \param argv 参数列表* \param name 节点名称,需要保证其唯一性,不允许包含命名空间* \param options 节点启动选项,被封装进了ros::init_options**/void init(int &argc, char **argv, const std::string& name, uint32_t options = 0);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"""话题与服务相关对象C++

在 roscpp 中,话题和服务的相关对象一般由 NodeHandle 创建。

1.发布对象

/*** \brief 根据话题生成发布对象** 在 ROS master 注册并返回一个发布者对象,该对象可以发布消息** 使用示例如下:** ros::Publisher pub = handle.advertise<std_msgs::Empty>("my_topic", 1);** \param topic 发布消息使用的话题** \param queue_size 等待发送给订阅者的最大消息数量** \param latch (optional) 如果为 true,该话题发布的最后一条消息将被保存,并且后期当有订阅者连接时会将该消息发送给订阅者** \return 调用成功时,会返回一个发布对象***/template <class M>Publisher advertise(const std::string& topic, uint32_t queue_size, bool latch = false)

2.订阅对象

/*** \brief 生成某个话题的订阅对象** 该函数将根据给定的话题在ROS master 注册,并自动连接相同主题的发布方,每接收到一条消息,都会调用回调* 函数,并且传入该消息的共享指针,该消息不能被修改,因为可能其他订阅对象也会使用该消息。** 使用示例如下:void callback(const std_msgs::Empty::ConstPtr& message){}ros::Subscriber sub = handle.subscribe("my_topic", 1, callback);** \param M [template] M 是指消息类型* \param topic 订阅的话题* \param queue_size 消息队列长度,超出长度时,头部的消息将被弃用* \param fp 当订阅到一条消息时,需要执行的回调函数* \return 调用成功时,返回一个订阅者对象,失败时,返回空对象*void callback(const std_msgs::Empty::ConstPtr& message){...}ros::NodeHandle nodeHandle;ros::Subscriber sub = nodeHandle.subscribe("my_topic", 1, callback);if (sub) // Enter if subscriber is valid{...}*/template<class M>Subscriber subscribe(const std::string& topic, uint32_t queue_size, void(*fp)(const boost::shared_ptr<M const>&), const TransportHints& transport_hints = TransportHints())

3.服务对象

/*** \brief 生成服务端对象** 该函数可以连接到 ROS master,并提供一个具有给定名称的服务对象。** 使用示例如下:\verbatimbool callback(std_srvs::Empty& request, std_srvs::Empty& response){return true;}ros::ServiceServer service = handle.advertiseService("my_service", callback);\endverbatim** \param service 服务的主题名称* \param srv_func 接收到请求时,需要处理请求的回调函数* \return 请求成功时返回服务对象,否则返回空对象:\verbatimbool Foo::callback(std_srvs::Empty& request, std_srvs::Empty& response){return true;}ros::NodeHandle nodeHandle;Foo foo_object;ros::ServiceServer service = nodeHandle.advertiseService("my_service", callback);if (service) // Enter if advertised service is valid{...}\endverbatim*/template<class MReq, class MRes>ServiceServer advertiseService(const std::string& service, bool(*srv_func)(MReq&, MRes&))

4.客户端对象

对象获取:

/*** @brief 创建一个服务客户端对象** 当清除最后一个连接的引用句柄时,连接将被关闭。** @param service_name 服务主题名称*/template<class Service>ServiceClient serviceClient(const std::string& service_name, bool persistent = false,const M_string& header_values = M_string())

请求发送函数:

/*** @brief 发送请求* 返回值为 bool 类型,true,请求处理成功,false,处理失败。*/template<class Service>bool call(Service& service)

等待服务函数1:

/*** ros::service::waitForService("addInts");* \brief 等待服务可用,否则一致处于阻塞状态* \param service_name 被"等待"的服务的话题名称* \param timeout 等待最大时常,默认为 -1,可以永久等待直至节点关闭* \return 成功返回 true,否则返回 false。*/ROSCPP_DECL bool waitForService(const std::string& service_name, ros::Duration timeout = ros::Duration(-1));

等待服务函数2:

/*** client.waitForExistence();* \brief 等待服务可用,否则一致处于阻塞状态* \param timeout 等待最大时常,默认为 -1,可以永久等待直至节点关闭* \return 成功返回 true,否则返回 false。*/bool waitForExistence(ros::Duration timeout = ros::Duration(-1));回旋函数C++

在ROS程序中,频繁的使用了 ros::spin() 和 ros::spinOnce() 两个回旋函数,可以用于处理回调函数。

1.spinOnce()

/*** \brief 处理一轮回调** 一般应用场景:* 在循环体内,处理所有可用的回调函数**/ROSCPP_DECL void spinOnce();

2.spin()

/*** \brief 进入循环处理回调*/ROSCPP_DECL void spin();对比

相同点:二者都用于处理回调函数;

不同点:ros::spin() 是进入了循环执行回调函数,而 ros::spinOnce() 只会执行一次回调函数(没有循环),在 ros::spin() 后的语句不会执行到,而 ros::spinOnce() 后的语句可以执行。

时间1.时刻

获取时刻,或是设置指定时刻:

ros::init(argc,argv,"hello_time");ros::NodeHandle nh;//必须创建句柄,否则时间没有初始化,导致后续API调用失败ros::Time right_now = ros::Time::now();//将当前时刻封装成对象ROS_INFO("当前时刻:%.2f",right_now.toSec());//获取距离 1970年01月01日 00:00:00 的秒数ROS_INFO("当前时刻:%d",right_now.sec);//获取距离 1970年01月01日 00:00:00 的秒数ros::Time someTime(100,100000000);// 参数1:秒数 参数2:纳秒ROS_INFO("时刻:%.2f",someTime.toSec()); //100.10ros::Time someTime2(100.3);//直接传入 double 类型的秒数ROS_INFO("时刻:%.2f",someTime2.toSec()); //100.302.持续时间

设置一个时间区间(间隔):

ROS_INFO("当前时刻:%.2f",ros::Time::now().toSec());ros::Duration du(10);//持续10秒钟,参数是double类型的,以秒为单位du.sleep();//按照指定的持续时间休眠ROS_INFO("持续时间:%.2f",du.toSec());//将持续时间换算成秒ROS_INFO("当前时刻:%.2f",ros::Time::now().toSec());3.持续时间与时刻运算

为了方便使用,ROS中提供了时间与时刻的运算:

ROS_INFO("时间运算");ros::Time now = ros::Time::now();ros::Duration du1(10);ros::Duration du2(20);ROS_INFO("当前时刻:%.2f",now.toSec());//1.time 与 duration 运算ros::Time after_now = now + du1;ros::Time before_now = now - du1;ROS_INFO("当前时刻之后:%.2f",after_now.toSec());ROS_INFO("当前时刻之前:%.2f",before_now.toSec());//2.duration 之间相互运算ros::Duration du3 = du1 + du2;ros::Duration du4 = du1 - du2;ROS_INFO("du3 = %.2f",du3.toSec());ROS_INFO("du4 = %.2f",du4.toSec());//PS: time 与 time 不可以运算// ros::Time nn = now + before_now;//异常4.设置运行频率(非常常用)ros::Rate rate(1);//指定频率while (true){ROS_INFO("-----------code----------");rate.sleep();//休眠,休眠时间 = 1 / 频率。}5.定时器

ROS 中内置了专门的定时器,可以实现与 ros::Rate 类似的效果:

ros::NodeHandle nh;//必须创建句柄,否则时间没有初始化,导致后续API调用失败// ROS 定时器/*** \brief 创建一个定时器,按照指定频率调用回调函数。** \param period 时间间隔* \param callback 回调函数* \param oneshot 如果设置为 true,只执行一次回调函数,设置为 false,就循环执行。* \param autostart 如果为true,返回已经启动的定时器,设置为 false,需要手动启动。*///Timer createTimer(Duration period, const TimerCallback& callback, bool oneshot = false,// bool autostart = true) const;// ros::Timer timer = nh.createTimer(ros::Duration(0.5),doSomeThing);ros::Timer timer = nh.createTimer(ros::Duration(0.5),doSomeThing,true);//只执行一次// ros::Timer timer = nh.createTimer(ros::Duration(0.5),doSomeThing,false,false);//需要手动启动// timer.start();ros::spin(); //必须 spin其他

在发布实现时,一般会循环发布消息,循环的判断条件一般由节点状态来控制,C++中可以通过 ros::ok() 来判断节点状态是否正常,而 python 中则通过 rospy.is_shutdown() 来实现判断,导致节点退出的原因主要有如下几种:

节点接收到了关闭信息,比如常用的 ctrl + c 快捷键就是关闭节点的信号;同名节点启动,导致现有节点退出;程序中的其他部分调用了节点关闭相关的API(C++中是ros::shutdown(),python中是rospy.signal_shutdown())

另外,日志相关的函数也是极其常用的,在ROS中日志被划分成如下级别:

DEBUG(调试):只在调试时使用,此类消息不会输出到控制台;INFO(信息):标准消息,一般用于说明系统内正在执行的操作;WARN(警告):提醒一些异常情况,但程序仍然可以执行;ERROR(错误):提示错误信息,此类错误会影响程序运行;FATAL(严重错误):此类错误将阻止节点继续运行。ROS 的运行管理ROS元功能包

显而易见的,逐一安装功能包的效率低下,在ROS中,提供了一种方式可以将不同的功能包打包成一个功能包,当安装某个功能模块时,直接调用打包后的功能包即可,该包又称之为元功能包(metapackage)。

概念

MetaPackage是Linux的一个文件管理系统的概念。是ROS中的一个虚包,里面没有实质性的内容,但是它依赖了其他的软件包,通过这种方法可以把其他包组合起来,我们可以认为它是一本书的目录索引,告诉我们这个包集合中有哪些子包,并且该去哪里下载。

作用

方便用户的安装,我们只需要这一个包就可以把其他相关的软件包组织到一起安装了。

实现

首先:新建一个功能包

然后:修改package.xml ,内容如下:

<exec_depend>被集成的功能包</exec_depend>.....<export><metapackage /></export>

最后:修改 CMakeLists.txt,内容如下:

cmake_minimum_required(VERSION 3.0.2)project(demo)find_package(catkin REQUIRED)catkin_metapackage()ROS节点运行管理launch文件

一个程序中可能需要启动多个节点,比如:ROS 内置的小乌龟案例,如果要控制乌龟运动,要启动多个窗口,分别启动 roscore、乌龟界面节点、键盘控制节点。如果每次都调用 rosrun 逐一启动,显然效率低下,如何优化?

采用的优化策略便是使用roslaunch 命令集合 launch 文件启动管理节点

概念

launch 文件是一个 XML 格式的文件,可以启动本地和远程的多个节点,还可以在参数服务器中设置参数。

作用

简化节点的配置与启动,提高ROS程序的启动效率。

使用

以 turtlesim 为例演示

1.新建launch文件

在功能包下添加 launch目录, 目录下新建 xxxx.launch 文件,编辑 launch 文件

<launch><node pkg="turtlesim" type="turtlesim_node" name="myTurtle" output="screen" /><node pkg="turtlesim" type="turtle_teleop_key" name="myTurtleContro" output="screen" /></launch>

2.调用 launch 文件

roslaunch 包名 xxx.launch

注意:roslaunch 命令执行launch文件时,首先会判断是否启动了 roscore,如果启动了,则不再启动,否则,会自动调用 roscore

launch文件 结构

launch文件标签之launch

<launch>标签是所有 launch 文件的根标签,充当其他标签的容器

1.属性

deprecated = "弃用
本文链接地址:https://www.jiuchutong.com/zhishi/296158.html 转载请保留说明!

上一篇:ChatGPT API接口使用+fine tune微调+prompt介绍(api接口长什么样)

下一篇:Vue3 项目中使用setup()函数报错,script setup cannot contain ES module exports(vue3使用教程)

  • 关联方的借款用什么科目核算
  • 土地价值计入房产税文件
  • 增值税普通发票怎么开
  • 建筑业的账务处理
  • 职工个人所得税
  • 税控系统技术维护费会计处理280
  • 出纳单据交接表怎么填写
  • 无偿划拨资产的入账价值
  • 印花税没有在我的待办里是不是就不用申报
  • 中小企业增值税税率
  • 支付的保证金
  • 管家婆进销存软件免费版
  • 普通发票打歪了可以用吗
  • 工资哪些扣款应该扣税
  • 购销合同印花税最新政策2023
  • 投资收益如何做账
  • 个人股权激励收入计入年度综合个人所得税吗
  • 营改增后不动产进项税额抵扣
  • 国税退税怎么做账
  • 股票交易的印花税和手续费是多少
  • 进项构成比例是啥
  • 拆迁公司属于什么性质
  • 营改增项目
  • 信息化投入包括什么投入和什么投入
  • 投入法和产出法的确定属于会计估计变更吗
  • 暂估成本能挂账多久
  • 新手会计怎么做分录
  • 国有土地使用证到期了怎么办
  • cuda10.1下载
  • 生产企业外销收入账务处理
  • 宝塔面板安装zabbix
  • js身份证正则验证
  • 合同权益转让 范本
  • php用于输出的有哪些方式
  • 购买仓库计入什么科目
  • 代收资金清算过期怎么办
  • 通过云服务器租号安全吗
  • 购车哪些费用可以免
  • 增值税专用发票和普通发票的区别
  • 如何计提所得税费用科目
  • 小规模季报成功后怎么缴费
  • 零星开支是什么会计科目
  • 超市小票可以作为证据吗
  • 公司认缴的钱可以动吗
  • 银行本票存款属于款项吗
  • 拓展费用入什么科目
  • 出口运保费是什么费用
  • 公司雇佣退休人员工资怎么发
  • 进项税额大于销项税额期末留抵
  • 用信用卡消费扣谁的手续费
  • 固定资产是否可调剂
  • 预收账款借贷方向增减
  • 以前年度应收账款无法收回
  • mysql事件调用存储过程
  • 磁盘缓存技术
  • mysql的日志
  • win10系统问题怎么修复
  • win7系统如何打开
  • freebsd软件安装
  • 用u盘装系统怎么操作步骤
  • 误删了分区怎么恢复
  • Windows虚拟机怎么打开
  • 苹果机的mac地址
  • win7系统怎么把c盘的内存加到d盘
  • pkjobs.exe - pkjobs是什么进程 有什么用
  • win10地图不显示
  • win10升级版本后还要激活吗
  • win10系统可以不用杀毒软件吗
  • 注册表 windows
  • win10内置管理员账户禁用
  • android打包v1v2
  • nodejs 调用python代码
  • 开发 工具
  • javascript判断
  • android开发中默认的数据库
  • js的判断类型
  • 中国税务稽查官网
  • 交通运输业的税率9%和13%
  • 为什么医保卡显示无效
  • 大兴区地方税务局
  • 免责声明:网站部分图片文字素材来源于网络,如有侵权,请及时告知,我们会第一时间删除,谢谢! 邮箱:opceo@qq.com

    鄂ICP备2023003026号

    网站地图: 企业信息 工商信息 财税知识 网络常识 编程技术

    友情链接: 武汉网站建设