【ROS】ROS2中的概念和名词解释

【ROS】郭老二博文之:ROS目录

0、ROS官网

ROS2中文网:http://dev.ros2.fishros.com/
ROS2官方文档:http://dev.ros2.fishros.com/doc/
ROS2教程:http://dev.ros2.fishros.com/doc/Tutorials.html
API接口:https://docs.ros.org/en/rolling/API-Docs.html
ROS2 Support on NVIDIA Jetson:https://nvidia-ai-iot.github.io/ros2_jetson/

官方源码:
https://github.com/ros2
https://github.com/ros2/examples

1、工作空间 workspace

ROS以固定的目录结构创建项目工程,项目根目录称为工作空间

1.1 典型工作空间结构

src:	 代码空间;
build:	 编译空间,保存编译过程中产生的中间文件;
install:安装空间,放置编译得到的可执行文件和脚本;
log:	 日志空间,编译和运行过程中,保存各种警告、错误、信息等日志。

1.2 创建工作空间

就是创建两个目录
例如本人的工作空间:

mkdir -p ~/ros/src

典型的工作空间结构中只需创建src目录即可,其它三个目录会在编译过程中自动生成

下载古月居老师ROS2入门课程21讲的demo

cd ~/ros/src
git clone https://gitee.com/guyuehome/ros2_21_tutorials.git

1.3 安装依赖

所谓的依赖就是ROS中已经完成的、可复用的功能包,可以使用工具rosdep来自动安装依赖,受网络影响,国内的用户可以使用rosdepc来安装,这里以rosdepc为例:
1)rosdepc是python3编写的,先要安装python3-pip

sudo apt install  python3-pip

2)安装rosdepc

sudo pip3 install rosdepc

3)初始化、更新

sudo rosdepc init
rosdepc update

4)安装依赖
安装依赖时,一定要在工作空间的根目录下执行rosdepc命令

cd ~/ros/
rosdepc install -i --from-path src --rosdistro humble -y

为src目录下的功能包安装依赖;
注意:参数humble是指ROS2在Ubuntu22.04系统下的版本代号,需要根据自己Ubuntu系统版本来更改

附上不同Ubuntu版本下的ROS版本代号
【ROS】ROS2中的概念和名词解释_第1张图片

1.4 安装编译工具colcon

ROS2中使用colcon来编译ROS项目

sudo apt install python3-colcon-ros

1.5 编译

编译时,需要在工作空间根目录下执行编译命令

cd ~/ros
colcon build

编译完成后,自动生存build、install、log三个目录

~/ros$ ls
build  install  log  src

1.6 配置环境变量

ROS2是以目录结构管理项目,因此ROS2系统需要知道我们自己创建工作空间的目录所在的位置;
有两种方法:
1)手动执行命令,仅在当前终端生效,需要每次打开终端时都要执行一遍,注意要在工作空间的根目录下执行

~$ cd ~/ros
~/ros$ source install/local_setup.sh

2)自动执行命令,将执行命令保存到系统脚本中,在系统启动时,自动执行,并且所有终端有效

~$ echo " source ~/dev_ws/install/local_setup.sh" >> ~/.bashrc

2、功能包package

ROS注重、强调功能复用,将各个功能的源码放入各自的目录来管理,这样可以达到代码复用的效果

2.1 创建功能包

创建功能包的命令格式如下:

$ ros2 pkg create --build-type  

参数说明:

pkg:	ros2子命令,执行与功能包相关的操作;
create:ros2 pkg 的子命令,用来创建功能包;
build-type:表示新创建的功能包是C++还是Python的,可选参数:ament_cmake(表示创建C++或C包)、ament_python(表示创建python包)
package_name:新建功能包的名字。

例如:在终端中分别创建C++和Python版本的功能包:
注意执行创建功能包的命令时,需要在工作空间的src目录或其子目录中下执行:

$ cd ~/ros/src
$ ros2 pkg create --build-type ament_cmake learning_pkg_c 
$ ros2 pkg create --build-type ament_python learning_pkg_python

2.2 编译功能包

在工作空间的根木下执行编译操作:

$ cd ~/ros/src
$ colcon build

2.3 更新环境变量

新创建的功能包还没有被ROS2系统识别,需要手动更新下环境变量

$ source install/local_setup.bash

2.4 包结构

自动创建的包结构如下,其中

package.xml:ROS2功能包依赖描述文档
CMakeLists.txt:C++或C项目中使用cmake来编译时的工程描述文档
setup.py:Python项目入口文档
~/ros/src$ tree
.
├── learning_pkg_c
│   ├── CMakeLists.txt
│   ├── include
│   │   └── learning_pkg_c
│   ├── package.xml
│   └── src
├── learning_pkg_python
│   ├── learning_pkg_python
│   │   └── __init__.py
│   ├── package.xml
│   ├── resource
│   │   └── learning_pkg_python
│   ├── setup.cfg
│   ├── setup.py
│   └── test
│       ├── test_copyright.py
│       ├── test_flake8.py
│       └── test_pep257.py

3、 节点node

ROS由多个可执行程序来协作完成整体功能,可以将一个个单独的程序视为一个个节点,由这些节点协作完成整体功能

3.1 运行、查看节点

在终端1中启动一个小海龟的节点

~$ ros2 run turtlesim turtlesim_node 

在终端2中启动控制小海龟的按键节点:

~$ ros2 run turtlesim turtle_teleop_key

在终端3中查看节点列表

~$ ros2 node list 
/teleop_turtle
/turtlesim

在终端3中查看节点连接图

rqt_graph

【ROS】ROS2中的概念和名词解释_第2张图片

3.2 节点编程

下面是复制古月居老师用python写的demo

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
@作者: 古月居(www.guyuehome.com)
@说明: ROS2节点示例-发布“Hello World”日志信息, 使用面向对象的实现方式
"""

import rclpy                                     # ROS2 Python接口库
from rclpy.node import Node                      # ROS2 节点类
import time

"""
创建一个HelloWorld节点, 初始化时输出“hello world”日志
"""
class HelloWorldNode(Node):
    def __init__(self, name):
        super().__init__(name)                     # ROS2节点父类初始化
        while rclpy.ok():                          # ROS2系统是否正常运行
            self.get_logger().info("Hello World")  # ROS2日志输出
            time.sleep(0.5)                        # 休眠控制循环时间

def main(args=None):                               # ROS2节点主入口main函数
    rclpy.init(args=args)                          # ROS2 Python接口初始化
    node = HelloWorldNode("node_helloworld_class") # 创建ROS2节点对象并进行初始化
    rclpy.spin(node)                               # 循环等待ROS2退出
    node.destroy_node()                            # 销毁节点对象
    rclpy.shutdown()                               # 关闭ROS2 Python接口

4、话题topic

以话题订阅、发布的形式来实现节点间单向传输数据

4.1 运行节点、查看话题

在终端1中启动一个小海龟的节点

~$ ros2 run turtlesim turtlesim_node 

在终端2中启动控制小海龟的按键节点:

~$ ros2 run turtlesim turtle_teleop_key

在终端3中查看话题列表

~$ ros2 topic list
/parameter_events
/rosout
/turtle1/cmd_vel
/turtle1/color_sensor
/turtle1/pose

在终端3中查看指定话题信息

~$ ros2 topic info /turtle1/pose
Type: turtlesim/msg/Pose
Publisher count: 1
Subscription count: 0

在终端3中查看指定话题内容

~$ ros2 topic echo /turtle1/pose
x: 7.758955478668213
y: 2.675198793411255
theta: -0.3631852865219116
linear_velocity: 0.0
angular_velocity: 0.0
……

4.2 话题编程

4.2.1 话题发布者

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
@作者: 古月居(www.guyuehome.com)
@说明: ROS2话题示例-发布“Hello World”话题
"""

import rclpy                                     # ROS2 Python接口库
from rclpy.node import Node                      # ROS2 节点类
from std_msgs.msg import String                  # 字符串消息类型

"""
创建一个发布者节点
"""
class PublisherNode(Node):

    def __init__(self, name):
        super().__init__(name)                                    # ROS2节点父类初始化
        self.pub = self.create_publisher(String, "chatter", 10)   # 创建发布者对象(消息类型、话题名、队列长度)
        self.timer = self.create_timer(0.5, self.timer_callback)  # 创建一个定时器(单位为秒的周期,定时执行的回调函数)

    def timer_callback(self):                                     # 创建定时器周期执行的回调函数
        msg = String()                                            # 创建一个String类型的消息对象
        msg.data = 'Hello World'                                  # 填充消息对象中的消息数据
        self.pub.publish(msg)                                     # 发布话题消息
        self.get_logger().info('Publishing: "%s"' % msg.data)     # 输出日志信息,提示已经完成话题发布

def main(args=None):                                 # ROS2节点主入口main函数
    rclpy.init(args=args)                            # ROS2 Python接口初始化
    node = PublisherNode("topic_helloworld_pub")     # 创建ROS2节点对象并进行初始化
    rclpy.spin(node)                                 # 循环等待ROS2退出
    node.destroy_node()                              # 销毁节点对象
    rclpy.shutdown()                                 # 关闭ROS2 Python接口

4.2.2 话题订阅者

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
@作者: 古月居(www.guyuehome.com)
@说明: ROS2话题示例-订阅“Hello World”话题消息
"""

import rclpy                      # ROS2 Python接口库
from rclpy.node   import Node     # ROS2 节点类
from std_msgs.msg import String   # ROS2标准定义的String消息

"""
创建一个订阅者节点
"""
class SubscriberNode(Node):

    def __init__(self, name):
        super().__init__(name)                             # ROS2节点父类初始化
        self.sub = self.create_subscription(\
            String, "chatter", self.listener_callback, 10) # 创建订阅者对象(消息类型、话题名、订阅者回调函数、队列长度)

    def listener_callback(self, msg):                      # 创建回调函数,执行收到话题消息后对数据的处理
        self.get_logger().info('I heard: "%s"' % msg.data) # 输出日志信息,提示订阅收到的话题消息

def main(args=None):                               # ROS2节点主入口main函数
    rclpy.init(args=args)                          # ROS2 Python接口初始化
    node = SubscriberNode("topic_helloworld_sub")  # 创建ROS2节点对象并进行初始化
    rclpy.spin(node)                               # 循环等待ROS2退出
    node.destroy_node()                            # 销毁节点对象
    rclpy.shutdown()                               # 关闭ROS2 Python接口

5、服务service

以服务端、客户端之间一问一答的形式来双向传输数据

5.1 运行节点、查看服务

在终端1中启动一个小海龟的节点

~$ ros2 run turtlesim turtlesim_node 

在终端2中启动控制小海龟的按键节点:

~$ ros2 run turtlesim turtle_teleop_key

在终端3中查看服务列表

~$ ros2 service list
/clear
/kill
……
/turtle1/set_pen
……

使用rqt图形化工具调用(call)/turtle1/set_pen服务来改变小海龟画笔的颜色和宽度
在终端3中输入命令

rqt

选择插件Service Caller
【ROS】ROS2中的概念和名词解释_第3张图片
选择服务“/turtle1/set_pen” ,并设置画笔的颜色rgb和宽度width的值,然后点击Call
【ROS】ROS2中的概念和名词解释_第4张图片
在终端2中使用键盘控制小海龟运动,可见画笔颜色已修改
【ROS】ROS2中的概念和名词解释_第5张图片

5.2 服务编程

5.2.1 客户端

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
@作者: 古月居(www.guyuehome.com)
@说明: ROS2服务示例-发送两个加数,请求加法器计算
"""

import sys

import rclpy                                  # ROS2 Python接口库
from rclpy.node   import Node                 # ROS2 节点类
from learning_interface.srv import AddTwoInts # 自定义的服务接口

class adderClient(Node):
    def __init__(self, name):
        super().__init__(name)                                       # ROS2节点父类初始化
        self.client = self.create_client(AddTwoInts, 'add_two_ints') # 创建服务客户端对象(服务接口类型,服务名)
        while not self.client.wait_for_service(timeout_sec=1.0):     # 循环等待服务器端成功启动
            self.get_logger().info('service not available, waiting again...') 
        self.request = AddTwoInts.Request()                          # 创建服务请求的数据对象

    def send_request(self):                                          # 创建一个发送服务请求的函数
        self.request.a = int(sys.argv[1])
        self.request.b = int(sys.argv[2])
        self.future = self.client.call_async(self.request)           # 异步方式发送服务请求

def main(args=None):
    rclpy.init(args=args)                        # ROS2 Python接口初始化
    node = adderClient("service_adder_client")   # 创建ROS2节点对象并进行初始化
    node.send_request()                          # 发送服务请求

    while rclpy.ok():                            # ROS2系统正常运行
        rclpy.spin_once(node)                    # 循环执行一次节点

        if node.future.done():                   # 数据是否处理完成
            try:
                response = node.future.result()  # 接收服务器端的反馈数据
            except Exception as e:
                node.get_logger().info(
                    'Service call failed %r' % (e,))
            else:
                node.get_logger().info(          # 将收到的反馈信息打印输出
                    'Result of add_two_ints: for %d + %d = %d' % 
                    (node.request.a, node.request.b, response.sum))
            break

    node.destroy_node()                          # 销毁节点对象
    rclpy.shutdown()                             # 关闭ROS2 Python接口

5.2.2 服务端

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
@作者: 古月居(www.guyuehome.com)
@说明: ROS2服务示例-提供加法器的服务器处理功能
"""

import rclpy                                     # ROS2 Python接口库
from rclpy.node   import Node                    # ROS2 节点类
from learning_interface.srv import AddTwoInts    # 自定义的服务接口

class adderServer(Node):
    def __init__(self, name):
        super().__init__(name)                                                           # ROS2节点父类初始化
        self.srv = self.create_service(AddTwoInts, 'add_two_ints', self.adder_callback)  # 创建服务器对象(接口类型、服务名、服务器回调函数)

    def adder_callback(self, request, response):   # 创建回调函数,执行收到请求后对数据的处理
        response.sum = request.a + request.b       # 完成加法求和计算,将结果放到反馈的数据中
        self.get_logger().info('Incoming request\na: %d b: %d' % (request.a, request.b))   # 输出日志信息,提示已经完成加法求和计算
        return response                          # 反馈应答信息

def main(args=None):                             # ROS2节点主入口main函数
    rclpy.init(args=args)                        # ROS2 Python接口初始化
    node = adderServer("service_adder_server")   # 创建ROS2节点对象并进行初始化
    rclpy.spin(node)                             # 循环等待ROS2退出
    node.destroy_node()                          # 销毁节点对象
    rclpy.shutdown()                             # 关闭ROS2 Python接口

6、通信接口interface

用于定义传输数据的结构形式
ROS有三种常用的通信机制,分别是话题、服务、动作,通过每一种通信种定义的接口,各种节点才能有机的联系到一起。

6.1 运行节点、查看通信接口

在终端1中启动一个小海龟的节点

~$ ros2 run turtlesim turtlesim_node 

在终端2中启动控制小海龟的按键节点:

~$ ros2 run turtlesim turtle_teleop_key

在终端3中查看通信列表,一共有三类:Message、Services、Actions,分别对应话题、服务、动作三种通信机制

~$ ros2 interface list
Messages:
    turtlesim/msg/Color
    ……
Services:
 	turtlesim/srv/SetPen
 	……
Actions:
	turtlesim/action/RotateAbsolute
 	……

在终端3中查看指定通信接口的定义
1)话题通信接口

~$ ros2 interface show turtlesim/msg/Pose
float32 x
float32 y

2)服务通信接口

~$ ros2 interface show turtlesim/srv/TeleportAbsolute
float32 x
float32 y
float32 theta
---

3)动作通信接口

~$ ros2 interface show turtlesim/action/RotateAbsolute
float32 theta
---
float32 delta
---
float32 remaining

6.2 自定义通信接口

6.2.1 话题通信接口

话题的通信接口文件一般在功能包目录的msg子目录中,内容示例如下

int32 x      # 表示目标的X坐标
int32 y      # 表示目标的Y坐标

6.2.2 服务通信接口

服务的通信接口文件一般在功能包目录的srv子目录中,内容示例如下

bool get      # 获取目标位置的指令
---
int32 x       # 目标的X坐标
int32 y       # 目标的Y坐标

通信接口文件中有两个部分,上边是发送的指令,下边是反馈的信息

6.2.3 动作通信接口

动作的通信接口文件一般在功能包目录的action子目录中,内容示例如下

bool enable     # 定义动作的目标,表示动作开始的指令
---
bool finish     # 定义动作的结果,表示是否成功执行
---
int32 state     # 定义动作的反馈,表示当前执行到的位置

动作通信接口文包含三个部分:

第一块是动作的目标,enable为true时,表示开始运动;
第二块是动作的执行结果,finish为true,表示动作执行完成;
第三块是动作的周期反馈,表示当前机器人旋转到的角度。

7、参数paramter

参数在ROS系统中表示:全局变量,可以在多个节点中共享数据

7.1 运行节点、查看参数

在终端1中启动一个小海龟的节点

~$ ros2 run turtlesim turtlesim_node 

在终端2中启动控制小海龟的按键节点:

~$ ros2 run turtlesim turtle_teleop_key

在终端3中查看参数列表

~$ ros2 param list
/turtlesim:
	background_b
	background_g
	background_r
……

在终端3中查看、设置参数的值,以修改小海龟的背景颜色值为例

查看
~$ ros2 param get /turtlesim background_b
Integer value is: 255
设置
~$ ros2 param set /turtlesim background_b 125
Set parameter successful
再次查看
~$ ros2 param get /turtlesim background_b
Integer value is: 125

【ROS】ROS2中的概念和名词解释_第6张图片

7.2 保存、加载参数

将某个节点的参数保存到参数文件中

~$ ros2 param dump turtlesim >> turtlesim.yaml 

一次性加载某一个文件中的所有参数

~$ ros2 param load turtlesim turtlesim.yaml  

参考参数文件的内容

~$ cat turtlesim.yaml 
/turtlesim:
	ros__parameters:
	background_b: 125
	background_g: 86
	background_r: 69
	qos_overrides:
  		/parameter_events:
    		publisher:
      			depth: 1000
      			durability: volatile
      			history: keep_last
      		reliability: reliable
	use_sim_time: false

7.3 参数编程

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
@作者: 古月居(www.guyuehome.com)
@说明: ROS2参数示例-创建、读取、修改参数
"""

import rclpy                                     # ROS2 Python接口库
from rclpy.node   import Node                    # ROS2 节点类

class ParameterNode(Node):
    def __init__(self, name):
        super().__init__(name)                                    # ROS2节点父类初始化
        self.timer = self.create_timer(2, self.timer_callback)    # 创建一个定时器(单位为秒的周期,定时执行的回调函数)
        self.declare_parameter('robot_name', 'mbot')              # 创建一个参数,并设置参数的默认值

    def timer_callback(self):                                      # 创建定时器周期执行的回调函数
        robot_name_param = self.get_parameter('robot_name').get_parameter_value().string_value   # 从ROS2系统中读取参数的值

        self.get_logger().info('Hello %s!' % robot_name_param)     # 输出日志信息,打印读取到的参数值

        new_name_param = rclpy.parameter.Parameter('robot_name',   # 重新将参数值设置为指定值
                            rclpy.Parameter.Type.STRING, 'mbot')
        all_new_parameters = [new_name_param]
        self.set_parameters(all_new_parameters)                    # 将重新创建的参数列表发送给ROS2系统

def main(args=None):                                 # ROS2节点主入口main函数
    rclpy.init(args=args)                            # ROS2 Python接口初始化
    node = ParameterNode("param_declare")            # 创建ROS2节点对象并进行初始化
    rclpy.spin(node)                                 # 循环等待ROS2退出
    node.destroy_node()                              # 销毁节点对象
    rclpy.shutdown()                                 # 关闭ROS2 Python接口

8、动作action

模拟执行一个完整动作,有话题和服务协作完成,例如:开始执行动作、动作是否响应、传输执行动作时的过程数据、动作完成反馈等。

8.1 运行节点、测试动作

在终端1中启动一个小海龟的节点

~$ ros2 run turtlesim turtlesim_node 

在终端2中启动控制小海龟的按键节点:

~$ ros2 run turtlesim turtle_teleop_key

在终端3中查看动作列表

~$ ros2 action list
/turtle1/rotate_absolute

在终端3中查看动作详细信息

~$ ros2 action info /turtle1/rotate_absolute 
Action: /turtle1/rotate_absolute
Action clients: 1
	/teleop_turtle
Action servers: 1
	/turtlesim

在终端3中执行一个动作:发送执行命令 --> 反馈1 --> …… --> 反馈n --> 执行结果

~$ ros2 action send_goal /turtle1/rotate_absolute turtlesim/action/RotateAbsolute "{theta: -4.57}" --feedback	
Sending goal:
 	theta: -4.57
Feedback:
	remaining: -3.016444206237793
Feedback:
	remaining: -3.0004444122314453
……
Result:
	delta: 3.007999897003174
Goal finished with status: SUCCEEDED

8.2 编程

执行动作时的通信流程图:
【ROS】ROS2中的概念和名词解释_第7张图片

8.2.1 服务端代码

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
@作者: 古月居(www.guyuehome.com)
@说明: ROS2动作示例-负责执行圆周运动动作的服务端
"""

import time

import rclpy                                      # ROS2 Python接口库
from rclpy.node   import Node                     # ROS2 节点类
from rclpy.action import ActionServer             # ROS2 动作服务器类
from learning_interface.action import MoveCircle  # 自定义的圆周运动接口

class MoveCircleActionServer(Node):
    def __init__(self, name):
        super().__init__(name)                   # ROS2节点父类初始化
        self._action_server = ActionServer(      # 创建动作服务器(接口类型、动作名、回调函数)
            self,
            MoveCircle,
            'move_circle',
            self.execute_callback)

    def execute_callback(self, goal_handle):            # 执行收到动作目标之后的处理函数
        self.get_logger().info('Moving circle...')
        feedback_msg = MoveCircle.Feedback()            # 创建一个动作反馈信息的消息

        for i in range(0, 360, 30):                     # 从0到360度,执行圆周运动,并周期反馈信息
            feedback_msg.state = i                      # 创建反馈信息,表示当前执行到的角度
            self.get_logger().info('Publishing feedback: %d' % feedback_msg.state)
            goal_handle.publish_feedback(feedback_msg)  # 发布反馈信息
            time.sleep(0.5)

        goal_handle.succeed()                           # 动作执行成功
        result = MoveCircle.Result()                    # 创建结果消息
        result.finish = True                            
        return result                                   # 反馈最终动作执行的结果

def main(args=None):                                    # ROS2节点主入口main函数
    rclpy.init(args=args)                               # ROS2 Python接口初始化
    node = MoveCircleActionServer("action_move_server") # 创建ROS2节点对象并进行初始化
    rclpy.spin(node)                                    # 循环等待ROS2退出
    node.destroy_node()                                 # 销毁节点对象
    rclpy.shutdown()                                    # 关闭ROS2 Python接口

8.2.2 客户端代码

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
@作者: 古月居(www.guyuehome.com)
@说明: ROS2动作示例-请求执行圆周运动动作的客户端
"""

import rclpy                                      # ROS2 Python接口库
from rclpy.node   import Node                     # ROS2 节点类
from rclpy.action import ActionClient             # ROS2 动作客户端类

from learning_interface.action import MoveCircle  # 自定义的圆周运动接口

class MoveCircleActionClient(Node):
    def __init__(self, name):
        super().__init__(name)                   # ROS2节点父类初始化
        self._action_client = ActionClient(      # 创建动作客户端(接口类型、动作名)
            self, MoveCircle, 'move_circle') 

    def send_goal(self, enable):                 # 创建一个发送动作目标的函数
        goal_msg = MoveCircle.Goal()             # 创建一个动作目标的消息
        goal_msg.enable = enable                 # 设置动作目标为使能,希望机器人开始运动

        self._action_client.wait_for_server()    # 等待动作的服务器端启动
        self._send_goal_future = self._action_client.send_goal_async(   # 异步方式发送动作的目标
            goal_msg,                                                   # 动作目标
            feedback_callback=self.feedback_callback)                   # 处理周期反馈消息的回调函数

        self._send_goal_future.add_done_callback(self.goal_response_callback) # 设置一个服务器收到目标之后反馈时的回调函数

    def goal_response_callback(self, future):           # 创建一个服务器收到目标之后反馈时的回调函数
        goal_handle = future.result()                   # 接收动作的结果
        if not goal_handle.accepted:                    # 如果动作被拒绝执行
            self.get_logger().info('Goal rejected :(')
            return

        self.get_logger().info('Goal accepted :)')                            # 动作被顺利执行

        self._get_result_future = goal_handle.get_result_async()              # 异步获取动作最终执行的结果反馈
        self._get_result_future.add_done_callback(self.get_result_callback)   # 设置一个收到最终结果的回调函数 

    def get_result_callback(self, future):                                    # 创建一个收到最终结果的回调函数
        result = future.result().result                                       # 读取动作执行的结果
        self.get_logger().info('Result: {%d}' % result.finish)                # 日志输出执行结果

    def feedback_callback(self, feedback_msg):                                # 创建处理周期反馈消息的回调函数
        feedback = feedback_msg.feedback                                      # 读取反馈的数据
        self.get_logger().info('Received feedback: {%d}' % feedback.state) 

def main(args=None):                                       # ROS2节点主入口main函数
    rclpy.init(args=args)                                  # ROS2 Python接口初始化
    node = MoveCircleActionClient("action_move_client")    # 创建ROS2节点对象并进行初始化
    node.send_goal(True)                                   # 发送动作目标
    rclpy.spin(node)                                       # 循环等待ROS2退出
    node.destroy_node()                                    # 销毁节点对象
    rclpy.shutdown()                                       # 关闭ROS2 Python接口

9、分布式通信Distributed Communication

将ROS的节点放到多个平台运行,通过分布式通信来实现

9.1 演示

略……
本人只有一台电脑,懒的搭建测试环境……

9.2 分布式网络分组

ROS2提供了一个DOMAIN的机制,就类似分组一样,处于同一个DOMAIN中的计算机才能通信;
在系统中设置环境变量ROS_DOMAIN_ID,即可将两者分配到一个小组中:

$ export ROS_DOMAIN_ID=

10、DDS(Data Distribution Service)

10.1 DDS简介

DDS的全称是Data Distribution Service(数据分发服务),ROS2底层通信机制,可以比喻为机器人的神经网络。

DDS是2004年由对象管理组织OMG发布和维护,是一套专门为实时系统设计的数据分发/订阅标准,最早应用于美国海军, 解决舰船复杂网络环境中大量软件升级的兼容性问题,现在已经成为强制标准。

DDS强调以数据为中心,可以提供丰富的服务质量策略,以保障数据进行实时、高效、灵活地分发,可满足各种分布式实时通信应用需求。
【ROS】ROS2中的概念和名词解释_第8张图片

10.2 DDS质量服务策略:QoS

1)DEADLINE策略,表示通信数据必须要在每次截止时间内完成一次通信;

2)HISTORY策略,表示针对历史数据的一个缓存大小;

3)RELIABILITY策略,表示数据通信的模式,配置成BEST_EFFORT,就是尽力传输模式,网络情况不好的时候,也要保证数据流畅,此时可能会导致数据丢失,配置成RELIABLE,就是可信赖模式,可以在通信中尽量保证图像的完整性,我们可以根据应用功能场景选择合适的通信模式;

4)DURABILITY策略,可以配置针对晚加入的节点,也保证有一定的历史数据发送过去,可以让新节点快速适应系统。

10.3 运行节点、查看策略

在终端1中启动一个小海龟的节点

~$ ros2 run turtlesim turtlesim_node 

在终端2中启动控制小海龟的按键节点:

~$ ros2 run turtlesim turtle_teleop_key

在终端3中查看话题策略

~$ ros2 topic info /turtle1/pose --verbose 
Type: turtlesim/msg/Pose

Publisher count: 1

Node name: turtlesim
Node namespace: /
Topic type: turtlesim/msg/Pose
Endpoint type: PUBLISHER
GID: 01.0f.76.64.91.0f.22.79.01.00.00.00.00.00.1c.03.00.00.00.00.00.00.00.00
QoS profile:
	Reliability: RELIABLE
	History (Depth): UNKNOWN
	Durability: VOLATILE
	Lifespan: Infinite
	Deadline: Infinite
	Liveliness: AUTOMATIC
	Liveliness lease duration: Infinite

Subscription count: 0

10.4 编程

10.4.1 发布者代码

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
@作者: 古月居(www.guyuehome.com)
@说明: ROS2 QoS示例-发布“Hello World”话题
"""

import rclpy                     # ROS2 Python接口库
from rclpy.node import Node      # ROS2 节点类
from std_msgs.msg import String  # 字符串消息类型
from rclpy.qos import QoSProfile, QoSReliabilityPolicy, QoSHistoryPolicy # ROS2 QoS类

"""
创建一个发布者节点
"""
class PublisherNode(Node):

    def __init__(self, name):
        super().__init__(name)        # ROS2节点父类初始化

        qos_profile = QoSProfile(     # 创建一个QoS原则
            # reliability=QoSReliabilityPolicy.BEST_EFFORT,
            reliability=QoSReliabilityPolicy.RELIABLE,
            history=QoSHistoryPolicy.KEEP_LAST,
            depth=1
        )
        self.pub = self.create_publisher(String, "chatter", qos_profile) # 创建发布者对象(消息类型、话题名、QoS原则)
        self.timer = self.create_timer(0.5, self.timer_callback)         # 创建一个定时器(单位为秒的周期,定时执行的回调函数)

    def timer_callback(self):                                # 创建定时器周期执行的回调函数
        msg = String()                                       # 创建一个String类型的消息对象
        msg.data = 'Hello World'                             # 填充消息对象中的消息数据
        self.pub.publish(msg)                                # 发布话题消息
        self.get_logger().info('Publishing: "%s"' % msg.data)# 输出日志信息,提示已经完成话题发布

def main(args=None):                           # ROS2节点主入口main函数
    rclpy.init(args=args)                      # ROS2 Python接口初始化
    node = PublisherNode("qos_helloworld_pub") # 创建ROS2节点对象并进行初始化
    rclpy.spin(node)                           # 循环等待ROS2退出
    node.destroy_node()                        # 销毁节点对象
    rclpy.shutdown()                           # 关闭ROS2 Python接口

10.4.2 订阅者代码

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
@作者: 古月居(www.guyuehome.com)
@说明: ROS2 QoS示例-订阅“Hello World”话题消息
"""

import rclpy                                     # ROS2 Python接口库
from rclpy.node   import Node                    # ROS2 节点类
from std_msgs.msg import String                  # ROS2标准定义的String消息
from rclpy.qos import QoSProfile, QoSReliabilityPolicy, QoSHistoryPolicy  # ROS2 QoS类

"""
创建一个订阅者节点
"""
class SubscriberNode(Node):

    def __init__(self, name):
        super().__init__(name)         # ROS2节点父类初始化

        qos_profile = QoSProfile(      # 创建一个QoS原则
            # reliability=QoSReliabilityPolicy.BEST_EFFORT,
            reliability=QoSReliabilityPolicy.RELIABLE,
            history=QoSHistoryPolicy.KEEP_LAST,
            depth=1
        )

        self.sub = self.create_subscription(\
            String, "chatter", self.listener_callback, qos_profile) # 创建订阅者对象(消息类型、话题名、订阅者回调函数、QoS原则)

    def listener_callback(self, msg):                      # 创建回调函数,执行收到话题消息后对数据的处理
        self.get_logger().info('I heard: "%s"' % msg.data) # 输出日志信息,提示订阅收到的话题消息

def main(args=None):                               # ROS2节点主入口main函数
    rclpy.init(args=args)                          # ROS2 Python接口初始化
    node = SubscriberNode("qos_helloworld_sub")    # 创建ROS2节点对象并进行初始化
    rclpy.spin(node)                               # 循环等待ROS2退出
    node.destroy_node()                            # 销毁节点对象
    rclpy.shutdown()                               # 关闭ROS2 Python接口

你可能感兴趣的:(ROS,ubuntu,git,linux)