学习机器人SLAM导航核心技术(二)之ROS

第1章:ROS入门必备知识

ROS全称Robot Operating System,是一个用于机器人应用开发的开源框架。ROS提供了一系列工具和库,使得机器人开发者可以更轻松地编写软件,从而实现机器人的感知、控制、定位、导航等功能。ROS使用publisher-subscriber模型实现消息传递,支持多种编程语言。由于它拥有方便的模块化设计,可以让开发者更方便地调试和修改程序,大大缩短机器人应用的开发时间。ROS在机器人领域得到了广泛应用,也成为了学术界和工业界的标准工具之一。

ROS概述

  • ROS是适用于机器人的开源元操作系统
  • ROS集成了大量的工具,库,协议,提供类似OS所提供的功能,简化对机器人的控制
  • 提供用于在多台计算机上获取,构建,编写和运行代码的工具和库,ROS在某些方面类似于“机器人框架”
  • ROS设计者将ROS表述为“ROS = Plumbing + Tools + Capabilities +
  • Ecosystem”,即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发展历程

ROS是一个由来已久、贡献者众多的大型软件项目。在ROS诞生之前,很多学者认为,机器人研究需要一个开放式的协作框架,并且已经有不少类似的项目致力于实现这样的框架。在这些工作中,斯坦福大学在2000年年中开展了一系列相关研究项目,如斯坦福人工智能机器人(STandford AI Robot, STAIR)项目、个人机器人(Personal Robots, PR)项目等,在上述项目中,在研究具有代表性、集成式人工智能系统的过程中,创立了用于室内场景的高灵活性、动态软件系统,其可以用于机器人学研究。

扫描二维码关注公众号,回复: 15510097 查看本文章
  • 2007年:ROS的前身“Switchyard”项目开始由斯坦福大学人工智能实验室的Morgan Quigley、Brian Gerkey和William D. Smart开发。
  • 2009年:ROS被正式发布,并且在it-robots Exhibition大会上进行展示。ROS的第一个稳定版本,即ROS 1.0,也在同年发布。
  • 2010年:由Google主办的DARPA Virtual Robotics Challenge中,许多选手都使用了ROS作为他们的机器人控制平台。
  • 2012年:ROS最重要的更新之一,即Groovy Galapagos版本发布。
  • 2013年:ROS的发展迅速,用户数量和软件包数量都有大幅度的增长。
  • 2014年:ROS新版本Indigo Igloo发布,此版本改进了ROS的核心工具,并增加了面向工业的功能。ROS进入了工业界,并在很多项目中得到了应用。
  • 2016年:ROS的新版本Kinetic发布,该版本支持Ubuntu Xenial 16.04 LTS操作系统,将ROS系统的稳定性和可靠性提高到了一个新的水平。
  • 2020年:ROS2的稳定版本Dashing Diademata发布,与ROS1相比,ROS2支持更多的平台和操作系统,并且具有更好的实时性和安全性。

ROS的发行版本(ROS distribution)指ROS软件包的版本,其与Linux的发行版本(如Ubuntu)的概念类似。推出ROS发行版本的目的在于使开发人员可以使用相对稳定的代码库,直到其准备好将所有内容进行版本升级为止。因此,每个发行版本推出后,ROS开发者通常仅对这一版本的bug进行修复,同时提供少量针对核心软件包的改进。
版本特点按照英文字母顺序命名,ROS 目前已经发布了ROS1 的终极版本: noetic,并建议后期过渡至 ROS2 版本。noetic 版本之前默认使用的是 Python2,noetic 支持 Python3。建议版本: noetic 或 melodic 或 kinetic

总的来说,自2007年以来,ROS已经在机器人领域飞速发展,并且成为了最受欢迎的机器人操作系统之一,也得到了学术界和工业界的广泛应用。

ROS简单使用

ROS中涉及的编程语言以C++和Python为主,ROS中的大多数程序两者都可以实现。ROS 当前的代码统计量,总行数超过 1400 万,作者超过2477 名。代码语言以C++为主,63.98%的代码是用 C++编写的,排名第二的是 python,占13.57%,可以说ROS 基本上都是使用这两种语言,来实现大部分的功能。

在本系列教程中,每一个案例也都会分别使用C++和Python两种方案演示,大家可以根据自身情况选择合适的实现方案。ROS中的程序即便使用不同的编程语言,实现流程也大致类似,以当前HelloWorld程序为例,实现流程大致如下:

在ROS中实现HelloWorld程序,可以采取以下大致流程:

  • 创建ROS工作空间(ROS workspace):首先需要创建ROS工作空间来承载本次工程。

  • 创建一个ROS功能包(ROS package):在工作空间中创建一个新的ROS包,这个包将包含我们的代码和依赖包。

  • 编写ROS节点:在ROS包中创建ROS节点,这个节点将会发送Hello World消息。

  • 编译和构建(build)ROS包:将ROS节点打包成一个可执行的二进制文件。

  • 运行ROS节点:在终端中运行ROS节点,此时ROS节点将会发送Hello World消息。

详细实现流程可以参考官方文档或者教程,这里只是一个大致的流程概述。虽然实现同一功能时,C++和Python可以互换,但是具体选择哪种语言,需要视需求而定,因为两种语言相较而言:C++运行效率高但是编码效率低,而Python则反之,基于二者互补的特点,ROS设计者分别设计了roscpp与rospy库,前者旨在成为ROS的高性能库,而后者则一般用于对性能无要求的场景,旨在提高开发效率。

创建工作空间并初始化

创建一个工作空间以及一个 src 子目录,然后再进入工作空间调用 catkin_make命令编译。

mkdir -p 自定义空间名称/src
cd 自定义空间名称
catkin_make

进入 src 创建 ros 包并添加依赖

在工作空间下生成一个功能包,该功能包依赖于 roscpp、rospy 与 std_msgs,其中roscpp是使用C++实现的库,而rospy则是使用python实现的库,std_msgs是标准消息库,创建ROS功能包时,一般都会依赖这三个库实现

cd src
catkin_create_pkg 自定义ROS包名 roscpp rospy std_msgs

HelloWorld(C++版)

假设你已经创建了ROS的工作空间,并且创建了ROS的功能包,那么就可以进入核心步骤了,使用C++编写程序实现:

进入 ros 包的 src 目录编辑源文件后,C++源码实现(文件名自定义)

// 导入ROS的头文件
#include "ros/ros.h"

int main(int argc, char *argv[])
{
    
    
    // 初始化ROS节点
    ros::init(argc, argv, "hello_world");

    // 创建句柄
    ros::NodeHandle nh;

    // 输出Hello World!
    ROS_INFO("Hello World!");

    return 0;
}

编辑 ros 包下的 Cmakelist.txt文件

add_executable(步骤3的源文件名
  src/步骤3的源文件名.cpp
)
target_link_libraries(步骤3的源文件名
  ${
    
    catkin_LIBRARIES}
)

进入工作空间目录并编译

cd 自定义空间名称
catkin_make

生成 build devel ....

执行
先启动命令行1:

roscore

再启动命令行2:

cd 工作空间
source ./devel/setup.bash
rosrun 包名 C++节点

HelloWorld(Python版)

假设你已经创建了ROS的工作空间,并且创建了ROS的功能包,那么就可以进入核心步骤了,使用Python编写程序实现:

进入 ros 包添加 scripts 目录并编辑 python 文件

cd ros包
mkdir scripts

新建 python 文件: (文件名自定义)

#! /usr/bin/env python

"""
    Python 版 HelloWorld

"""
import rospy

if __name__ == "__main__":
    rospy.init_node("Hello")
    rospy.loginfo("Hello World!!!!")

为 python 文件添加可执行权限

chmod +x 自定义文件名.py

编辑 ros 包下的 CamkeList.txt 文件

catkin_install_python(PROGRAMS scripts/自定义文件名.py
  DESTINATION ${
    
    CATKIN_PACKAGE_BIN_DESTINATION}
)

进入工作空间目录并编译

cd 自定义空间名称
catkin_make

进入工作空间目录并执行
先启动命令行1:

roscore

再启动命令行2:

cd 工作空间
source ./devel/setup.bash

rosrun 包名 自定义文件名.py

输出结果:Hello World!!!

ROS架构

ROS采用的是分布式系统结构,由多个进程(节点)组成,其中各个节点之间通过ROS通信机制(topic、Service)来进行交互。这些节点可以部署在同一台机器上,也可以部署在不同机器上,还可以部署在互联网上。下面是ROS架构中涉及到的主要组件:

  • 节点(Node):是ROS架构中的基本组件,通常指一个运行着的进程,负责完成特定的任务。每个节点可以有多个ROS里程碑。ROS 提供了处理节点的工具,用于节点信息、状态、可用性等的查询操作,例如可以用下面的命令对正在运行的节点进行操作。

    • rosnode info <node_name>:用于输出当前节点信息。
    • rosnode kill <node_name>:用于杀死正在运行节点进程来结束节点的运行。rosnode list:用于列出当前活动的节点。
    • rosnode machine :用于列出指定计算机上运行的节点。rosnode ping <node_name>:用于测试节点间的网络连通性。
    • rosnode cleanup:用于将无法访问节点的注册信息清除。
  • 主节点(Master):是ROS系统的重要组件,负责维护运行节点的注册,管理通信连接和服务请求等,同时也提供一些监控和调试功能。

  • 参数服务器(Parameter Server):是ROS中的全局参数管理组件,可以在不同节点之间共享参数数据,提高了程序的复用性和可扩展性。参数服务器能够使数据通过关键词存储在一个系统的核心位置。通过使用参数,就能够在节点运行时动态配置节点或改变节点的工作任务。参数服务器是可通过网络访问的共享的多变量字典,节点使用此服务器来存储和检索运行时的参数。ROS 中关于参数服务器的命令行工具,请看下面的常用命令。

    • rosparam list:用于列出参数服务器中的所有参数。
    • rosparam get <parameter_name>:用于获取参数服务器中的参数值。
    • rosparam set <parameter_name> * :用于设置参数服务器中参数的值。
    • rosparam delete <parameter_name>:用于将参数从参数服务器中删除。
    • rosparam dump :用于将参数服务器的参数保存到一个文件。
    • rosparam load :用于从文件将参数加载到参数服务器。
  • 话题(Topic):是ROS中的一种发布/订阅模式的通信机制,用于实现节点之间的数据交互和数据传输。ROS 提供了操作主题的命令工具,这里列举出一些常用的命令。

    • rostopic bw </topic_name>:用于显示主题所使用的带宽。
    • rostopic echo </topic_name>:用于将主题中的消息数据输出到屏幕。rostopic find <message_type>:用于按照消息类型查找主题。
    • rostopic hz </topic_name>:用于显示主题的发布频率。
    • rostopic info </topic_name>:用于输出活动主题、发布的主题、主题订阅者和服务的信息。rostopic list:用于列出当前活动主题的列表。
    • rostopic pub </topic_name> <message_type> :用于通过命令行将数据发布到主题。rostopic type </topic_name>:用于输出主题中发布的消息类型。
  • 服务(Service):是ROS中的一种请求/响应模式的通信机制,用于实现节点之间的函数调用和参数传递。
    ROS 提供了操作服务的命令工具,这里列举出一些常用的命令。

    • rosservice call </service_name> :用于通过命令行参数调用服务。
    • rosservice find <service_type>:用于根据服务类型查询服务。
    • rosservice info </service_name>:用于输出服务信息。
    • rosservice list:用于列出活动服务清单。
    • rosservice type </service_name>:用于输出服务类型。
    • rosservice uri </service_name>:用于输出服务的 ROSRPC URI。
  • 行为(Action):是ROS中的一种高级通信机制,用于实现节点之间复杂的交互行为,例如控制机器人完成一系列动作。

  • 消息(Message):是ROS中的一种数据结构格式,用于在话题、服务和行为等通信机制中传输数据。ROS 提供了获取消息相关信息的命令工具,这里列举出一些常用的命令,来具体看看吧。

    • rosmsg show <message_type>:用于显示一条消息的字段。
    • rosmsg list:用于列出所有消息。
    • rosmsg package :用于列出功能包的所有消息。
    • rosmsg packages:用于列出所有具有该消息的功能包。
    • rosmsg users <message_type>:用于搜索使用该消息类型的代码文件。rosmsg md5 <message_type>:用于显示一条消息的 MD5 求和结果。
  • ROS工具(ROS Tools):ROS提供了一系列便于开发、调试和管理的工具,例如rqt、rqt_plot、rqt_graph、rviz等。

此外,ROS还具有丰富的核心模块。 其核心模块包括:通信结构基础、机器人特性功能、工具集。通信结构基础主要是消息传递、记录回放消息、远程过程调用、分布式参数系统;机器人特性功能主要是标准机器人消息、机器人几何库、机器人描述语言、抢占式远程过程调用、诊断、位置估计、定位导航;工具集主要是命令式工具、可视化工具、图形化接口。
ROS 核心工具很丰富,ROS 常用命令工具是 rostopic、rosservice、rosnode、rosparam、rosmsg、rossrv、roswtf;ROS 常用可视化工具是 rqt、rviz;ROS 用于存储与回放数据的工具rosbag;ROS 的 log 系统记录软件运行的相关信息;ROS 还拥有强大的第三方工具支持:三维仿真环境 Gazebo、计算机视觉库 OpenCV、点云库 PCL、机械臂控制库MoveIt、工业应用库 Industrial、机器人编程工具箱 MRPT、实时控制库 Orocos。
以上就是ROS架构中的主要组件,ROS 的核心概念主要是节点和用于节点间通信的话题与服务。它们协同工作,构成了强大而灵活的ROS系统,可以支持各种种类的机器人和应用领域。一些常见的ROS应用领域包括机器人控制、自动驾驶、智能家居、医疗健康等。

从文件系统级理解 ROS 架构

文件系统级理解 ROS 架构

ROS 文件系统是 ROS 架构的关键部分之一,被称为包(package)。包文件夹包含 ROS 节点、主题、服务、参数和其他机器人相关的文件。每个包可以具有子目录来组织它的组件。ROS 节点可以跨包通信。

ROS 文件系统的基本结构如下:

  • /opt/ros//:ROS 安装文件夹,其中 是 ROS 版本号。
  • ~/catkin_ws/:ROS 工作空间,通常位于用户的主文件夹下。其中包括 src 文件夹、build 文件夹和 devel 文件夹。
  • ~/catkin_ws/src/:ROS 工作空间下的源代码文件夹。每个 ROS 包都必须放在此目录下。
  • ~/catkin_ws/build/:即编译文件夹。该目录包含在构建和编译 ROS 包时生成的所有中间文件和构建输出。
  • ~/catkin_ws/devel/:即开发者概念。该目录包含了从源文件编译的文件夹。通常用于为机器人开发软件等。

(1)工作空间

工作空间是一个包含功能包、可编辑源文件和编译包的文件夹,当你想同时编译不同的功能包时非常有用,并且可以保存本地开发包。当然,用户可以根据自己的需要创建多个工作空间,在每个工作空间中开发不同用途的功能包。不过作为学习,我们先以一个工作空间为例。如上图 ,创建了一个名为 catkin_ws 的工作空间,该工作空间下会有3 个文件夹:src、build、devel。

  • src 源文件空间:这个文件夹放置各个功能包和一个用于这些功能包的CMake 配置文件CMakeLists.txt。这里做一下说明,由于 ROS 中的源码采用 catkin 工具进行编译,而catkin工具又是基于 cmake 技术的,所以我们会在 src 源文件空间和各个功能包中都会见到一个文件CMakeLists.txt,这个文件就是起编译配置的作用。
  • build 编译空间:这个文件夹放置 CMake 和 catkin 编译功能包时产生的缓存、配置、中间文件等。
  • devel 开发空间:这个文件夹放置编译好的可执行程序,这些可执行程序是不需要安装就能直接运行的。一旦功能包源码编译和测试通过后,可以将这些编译好的可执行文件直接导出与其他开发人员分享。

(2)功能包

在ROS中,功能包是一种用于组织、管理和共享ROS程序和相关资源的标准化机制。一个ROS功能包可以包含ROS程序的最小工作单元,通常包括ROS的进程(节点)、发布和订阅的消息、服务定义、参数文件以及其他必要文件和资源。功能包是ROS强大和灵活的架构的基本组成部分之一。如上图,一个功能包中主要包含这几个文件:

  • CMakeLists.txt 功能包配置文件:用于这个功能包 cmake 编译时的配置文件。
  • package.xml 功能包清单文件:用 xml 的标签格式标记这个功能包的各类相关信息,比如包的名称、依赖关系等。主要作用是为了更容易的安装和分发功能包。
  • include/<package_name>功能包头文件目录:你可以把你的功能包程序包含的*.h 头文件放在这里,include 下之所以还要加一级路径<package_name>是为了更好的区分自己定义的头文件和系统标准头文件,<package_name>用实际功能包的名称替代。
  • msg 非标准消息定义目录:消息是 ROS 中一个进程(节点)发送到其他进程(节点)的信息,消息类型是消息的数据结构,ROS 系统提供了很多标准类型的消息可以直接使用,如果你要使用一些非标准类型的消息,就需要自己来定义该类型的消息,并把定义的文件放在这里。不过这个文件夹不是必要项,比如程序中只使用标准类型的消息的情况。
  • srv 服务类型定义目录:服务是 ROS 中进程(节点)间的请求/响应通信过程,服务类型是服务请求/响应的数据结构,服务类型的定义放在这里。如果要调用此服务,你需要使用该功能包名称和服务名称。不过这个文件夹不是必要项,比如程序中不使用服务的情况。
  • scripts 可执行脚本文件存放目录:这里用于存放 bash、python 或其他脚本的可执行文件。不过这个文件夹不是必要项,比如程序中不使用可执行脚本的情况。
  • launch 文件目录:这里用于存放*.launch 文件,*.launch 文件用于启动ROS 功能包中的一个或多个节点,在含有多个节点启动的大型项目中很有用。不过这个文件夹不是必要项,节点也可以不通过 launch 文件启动。
  • src 功能包中节点源文件存放目录:一个功能包中可以有多个进程(节点)程序来完成不同的功能,每个进程(节点)程序都是可以单独运行的,这里用于存放这些进程(节点)程序的源文件,你可以在这里再创建文件夹和文件来按你的需求组织源文件,源文件可以用c++、python 等来书写。
    为了创建、修改、使用功能包,ROS 给我们提供了一些实用的工具,常用的有下面这些工具。
  • rospack:用于获取信息或在系统中查找工作空间。
  • catkin_create_pkg:用于在工作空间的 src 源空间下创建一个新的功能包。catkin_make:用于编译工作空间中的功能包。
  • rosdep:用于安装功能包的系统依赖项。
  • rqt_dep:用于查看功能包的依赖关系图。

(3)消息

一个示例消息定义文件
消息是 ROS 中一个进程(节点)发送到其他进程(节点)的信息,消息类型是消息的数据结构,ROS 系统提供了很多标准类型的消息可以直接使用,如果你要使用一些非标准类型的消息,就需要自己来定义该类型的消息。
ROS 使用了一种精简的消息类型描述语言来描述 ROS 进程(节点)发布的数据值。通过这种描述语言对消息类型的定义,ROS 可以在不同的编程语言(如c++、python 等)书写的程序中使用此消息。不管是 ROS 系统提供的标准类型消息,还是用户自定义的非标准类型消息,定义文件都是以*.msg 作为扩展名。消息类型的定义分为两个主要部分:字段的数据类型和字段的名称,简单点说就是结构体中的变量类型和变量名称。比如下面的一个示例消息定义文件 example.msg 的内容,如上图 ,int32、float32、string 就是字段的数据类型,id、vel、name 就是字段的名称。

话题(Topic)

话题通信是ROS中使用频率最高的一种分布式通信模式,用于传输实时消息。话题是一种数据流对象,它具有订阅-发布模型,允许节点之间通过它们之间异步的消息传输进行通信。也即:一个节点发布消息,另一个节点订阅该消息。
像雷达、摄像头、GPS… 等等一些传感器数据的采集,也都是使用了话题通信,换言之,话题通信适用于不断更新的数据传输相关的应用场景。

理论模型

是一个命名的数据通道,通过它进行的通信中传递的是消息。话题名是话题通信中最重要的部分,它被用作识别和链接的标准。该模型如下图所示,该模型中涉及到三个角色:

  • ROS Master (管理者)
  • Talker (发布者)是一种ROS节点,它将消息发送到一个或多个话题上。发布者使用 ROS 客户端库将消息发布到 ROS master 上,并要求它将该消息分发到相应的话题上。
  • Listener (订阅者) 在订阅者节点中,用户可以注册一个回调函数监听指定话题,一旦有新消息到达该话题,回调函数就会被激活,从而实现消息的接收和处理。

ROS Master 负责保管 Talker 和 Listener 注册的信息,并匹配话题相同的 Talker 与 Listener,帮助 Talker 与 Listener 建立连接,连接建立后,Talker 可以发布消息,且发布的消息会被 Listener 订阅。
话题通讯模型
Talker注册
Talker启动后,会通过RPC在 ROS Master 中注册自身信息,其中包含所发布消息的话题名称。ROS Master 会将节点的注册信息加入到注册表中。
Listener注册
Listener启动后,也会通过RPC在 ROS Master 中注册自身信息,包含需要订阅消息的话题名。ROS Master 会将节点的注册信息加入到注册表中。
ROS Master实现信息匹配
ROS Master 会根据注册表中的信息匹配Talker 和 Listener,并通过 RPC 向 Listener 发送 Talker 的 RPC 地址信息。
Listener向Talker发送请求
Listener 根据接收到的 RPC 地址,通过 RPC 向 Talker 发送连接请求,传输订阅的话题名称、消息类型以及通信协议(TCP/UDP)。
Talker确认请求
Talker 接收到 Listener 的请求后,也是通过 RPC 向 Listener 确认连接信息,并发送自身的 TCP 地址信息。
Listener与Talker件里连接
Listener 根据步骤4 返回的消息使用 TCP 与 Talker 建立网络连接。
Talker向Listener发送消息
连接建立后,Talker 开始向 Listener 发布消息。

话题通信基本操作A(C++)

编写发布订阅实现,要求发布方以10HZ(每秒10次)的频率发布文本消息,订阅方订阅消息并将消息内容打印输出。
流程:

  • 编写发布方实现;
  • 编写订阅方实现;
  • 编辑配置文件;
  • 编译并执行。

需要注意的是,这是一个简单的发布者程序示例,该示例基于ROS C++客户端库编写。其中涉及到的类和库函数都需要进行正确的初始化、配置、构造和调用,需要确保在ROS系统中实现正确的发布流程和相关操作。
消息发布与订阅 ROS 通信网络结构图 上图是消息发布与订阅 ROS 通信网络结构图

发布方

/*
    需求: 实现基本的话题通信,一方发布数据,一方接收数据,
         实现的关键点:
         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.包含头文件 
#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;
}

配置 CMakeLists.txt
首先,add_executable用于创建可执行文件。在这个例子中,它分别创建了两个可执行文件Hello_pub和Hello_sub。src/Hello_pub.cpp和src/Hello_sub.cpp分别是两个可执行文件的源代码。

然后,target_link_libraries用于链接所需的库。${catkin_LIBRARIES}是一个变量,在ROS中指代需要链接的所有库。

这段代码的作用是在ROS的工作空间中创建两个可执行文件,并将${catkin_LIBRARIES}作为链接库进行链接。这些可执行文件可能是ROS节点或其他形式的程序。

//发布者的可执行文件的源代码
add_executable(Hello_pub
  src/Hello_pub.cpp
)
//订阅者的可执行文件的源代码
add_executable(Hello_sub
  src/Hello_sub.cpp
)

//发布者所需的链接库
target_link_libraries(Hello_pub
  ${
    
    catkin_LIBRARIES}
)
//订阅者所需的链接库
target_link_libraries(Hello_sub
  ${
    
    catkin_LIBRARIES}
)

#! /usr/bin/env python
"""
    需求: 实现基本的话题通信,一方发布数据,一方接收数据,
         实现的关键点:
         1.发送方
         2.接收方
         3.数据(此处为普通文本)

         PS: 二者需要设置相同的话题


    消息发布方:
        循环发布信息:HelloWorld 后缀数字编号

    实现流程:
        1.导包 
        2.初始化 ROS 节点:命名(唯一)
        3.实例化 发布者 对象
        4.组织被发布的数据,并编写逻辑发布数据


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

if __name__ == "__main__":
    #2.初始化 ROS 节点:命名(唯一)
    rospy.init_node("talker_p")
    #3.实例化 发布者 对象
    pub = rospy.Publisher("chatter",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

#! /usr/bin/env python
"""
    需求: 实现基本的话题通信,一方发布数据,一方接收数据,
         实现的关键点:
         1.发送方
         2.接收方
         3.数据(此处为普通文本)


    消息订阅方:
        订阅话题并打印接收到的消息

    实现流程:
        1.导包 
        2.初始化 ROS 节点:命名(唯一)
        3.实例化 订阅者 对象
        4.处理订阅的消息(回调函数)
        5.设置循环调用回调函数



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

def doMsg(msg):
    rospy.loginfo("I heard:%s",msg.data)

if __name__ == "__main__":
    #2.初始化 ROS 节点:命名(唯一)
    rospy.init_node("listener_p")
    #3.实例化 订阅者 对象
    sub = rospy.Subscriber("chatter",String,doMsg,queue_size=10)
    #4.处理订阅的消息(回调函数)
    #5.设置循环调用回调函数
    rospy.spin()

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

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

在ROS中,自定义的消息类型是通过在.msg文件中定义的。该文件包含一个或多个字段的类型和名称,以及一个消息名称。在将消息定义保存到msg文件中后,您需要在您的CMakeLists.txt文件和package.xml中添加必要的信息以使消息生效。
在ROS中,自定义消息通常由不同的基本类型组成。 以下是ROS中自定义消息支持的基本类型:

Integers: 整数类型,包括byte、char、int8、int16、int32和int64。

Floating point numbers: 浮点数类型,包括float32和float64。

Boolean values: 布尔类型,只有两种取值:True或False。

Strings: 字符串类型,包括string。

Time and duration: ROS中的时间和时间间隔类型,包括time和duration。

Arrays: 数组类型,也称为 sequence,包括使用尖括号<>括起来的类型(例如:int16[])。

Fixed-length arrays: 固定长度数组类型,也称为 array,用大括号{}括起来,后面紧跟,其中n是数组长度(例如:int16{4})。

Header: ROS中的消息头,包括std_msgs/Header。包含时间戳和ROS中常用的坐标帧信息。会经常看到msg文件的第一行具有Header标头。

首先创建一个空的package单独存放msg类型(当然也可以在任意的package中自定义msg类型)这里为便于说明,建立一个名为test_msgs的包,用于对自定义msg类型的用法举例。

流程:
新建msg文件
然后在test_msgs中创建msg文件夹,在msg文件夹其中新建一个名为Test.msg消息类型文件

$ cd catkin_ws/src
$ catkin_create_pkg test_msgs

$ cd test_msgs
$ mkdir msg

Test.msg的内容

float32[] data
float32 vel
geometry_msgs/Pose pose
string name

修改package.xml
接下来需要message_generation生成C++或Python能使用的代码,需要message_runtime提供运行时的支持,所以package.xml中添加以下两句

<build_depend>message_generation</build_depend>
<run_depend>message_runtime</run_depend>

修改CMakeLists.txt
修改CMakeLists.txt
CMakeLists.txt要注意四个地方

(1)首先调用find_package查找依赖的包,必备的有roscpp rospy message_generation,其他根据具体类型添加,比如上面的msg文件中用到了geometry_msgs/Pose pose类型,那么必须查找geometry_msgs

find_package(catkin REQUIRED COMPONENTS roscpp rospy message_generation std_msgs geometry_msgs)

(2)然后是add_message_files,指定msg文件

add_message_files(
  FILES
  Test.msg
  # Message2.msg
)

(3)然后是generate_messages,指定生成消息文件时的依赖项,比如上面嵌套了其他消息类型geometry_msgs,那么必须注明

#generate_messages必须在catkin_package前面
generate_messages(
 DEPENDENCIES
 geometry_msgs
)

(4)然后是catkin_package设置运行依赖

catkin_package(

CATKIN_DEPENDS message_runtime

)

编译

注意:要使用自定义的消息类型必须source自定义消息所在的工作空间,否则rosmsg show test_msgs/Test和rostopic echo /test_msg(/test_msg是节点中使用自定义消息类型test_msgs/Test的topic)都会报错,因为没有source的情况下自定义消息类型是不可见的,被认为是未定义类型

未完待续!

猜你喜欢

转载自blog.csdn.net/qq_38915354/article/details/130175254