ROS2简要笔记

ROS2基础-古月居

1. 在Ubuntu上创建ROS2工作空间

在这里插入图片描述

mkdir -p ~/dev_ws/src         # 创建工作空间目录 空间名dev_ws 自己命令
cd ~/dev_ws                 #进入工作空间目录

每次修改程序需要运行colcon build。编写代码在功能包中,下面有建立功能包的步骤。
它会将src的代码复制到install。ros2 run的时候是运行的install的文件
colcon build                   #构建工作空间 确保路径在ros2_ws文件下,不是在src里面,产生三个文件夹build、install、log。

echo "source ~/dev_ws/install/setup.bash" >> ~/.bashrc #设置环境变量
source ~/.bashrc                                         #使环境变量生效 不需要在每次新的终端会话中手动运行 source 命令 
 
不配置上面两行可以每次用以下命令
source install/local_setup.sh  

2. 功能包

建立功能包
cd ~/dev_ws/src   在scr下,不是在dev_ws里建立
ros2 pkg create --build-type ament_cmake learning_pkg_c       # C++ 包名叫learning_pkg_c 自己命令
ros2 pkg create --build-type ament_python learning_pkg_python # Python 包名叫learning_pkg_python自己命令

编译功能包
cd ~/dev_ws
colcon build   # 编译工作空间所有功能包
source install/local_setup.bash #可以用上面的配置,不用每次都使用它

python版本
包名叫learning_pkg_python的话,编写程序直接在learning_pkg_python下的learning_pkg_python里面建立程序文件夹。
在这里插入图片描述)

3. 节点

进入工作空间下,编译
colcon build   #确保路径在dev_ws文件下,不是在src里面,后面教程不再设置环境变量。
ros2 run learning_node node_helloworld     #运行的是learning_node功能包的node_helloworld节点打印Hello World 
ros2 run learning_node node_helloworld_class #面向对象的,效果与上面一样

面向过程编程
节点实现流程,利用VS CODE打开古月居提供的代码 在learning_node/learning_node/node_helloworld_class.py
(1)面向对象编程

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节点对象并进行初始化
    node.destroy_node()                              # 销毁节点对象
    rclpy.shutdown()                                 # 关闭ROS2 Python接口


(2)程序入口配置

在learning_node对应的setup.py里面
告诉node_helloworld_class节点在learning_node文件下的node_helloworld_class程序的main函数 ,node_helloworld_class,py不带.py后缀。

    entry_points={
        'console_scripts': [
                 'node_helloworld_class = learning_node.node_helloworld_class:main',
        ],
    },
)

4 话题(传输协议)

例如:一个节点 用摄像头拍摄图像 一个节点显示图像。传输图像数据,利用话题可以将他们联系起来

举个例子:想象一个机器人的摄像头(发布者)持续捕捉图像并发布到一个话题。同时,一个图像处理节点(订阅者)订阅这个话题。摄像头可以持续捕捉和发布图像,而不需要等待图像处理节点完成每次的处理。图像处理节点也可以按照自己的节奏处理接收到的图像,不会影响摄像头的工作。

特点:

  1. 单向通信:数据从发布者流向订阅者。
  2. 异步通信
  3. 多个发布者可以发布到同一个话题,多个订阅者也可以订阅同一个话题

重点:
4. 数据类型: 每个话题都有一个特定的消息类型,定义了可以在该话题上发送的数据结构
设置数据类型就像是在每个"信封"上贴上明确的标签,告诉系统和开发者这里面装的是什么,应该如何处理。

消息类型.msg文件

发布者 与订阅者程序运行后,使用以下命令查看节点与话题关系
rqt_graph

在这里插入图片描述 · 1. 发布Hello World和接收它

发布者


ros2 run learning_topic topic_helloworld_pub #发布节点

ros2 run learning_topic topic_helloworld_sub #订阅节点,另开一个终端输入

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接口

订阅者

import rclpy                                       # ROS2 Python接口库
from rclpy.node   import Node                      # ROS2 节点类
from std_msgs.msg import String                    # 字符串消息类型
from learning_interface.msg import ObjectPosition  # 自定义的目标位置消息

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

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

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

5 服务(双向,同步通信机制)

服务是一对一通信
请求苹果位置,反馈苹果位置。
服务使用.srv文件定义请求和应答数据结构
1.加法求解器

ros2 run learning_service service_adder_server

ros2 run learning_service service_adder_client 2 3

在这里插入图片描述
服务端

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接口

客服端

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接口

6通信接口

官方有标准定义的通信接口
在这里插入图片描述
话题(Topic):异步、单向通信 、持续数据流、适用于频繁更新的数据,如传感器读数
服务(Service):同步、双向通信、一次性请求和响应、适用于简短的查询或计算任务
动作(Action):异步、双向通信,带持续反馈、长期运行的任务、适用于复杂操作,如机器人导航

7 动作

  1. 非阻塞操作:当客户端发送一个动作目标时,它不需要等待动作完成就可以继续执行其他任务。这与同步的服务调用不同,服务调用会阻塞直到收到响应。
  2. 并发执行:客户端可以同时发送多个动作目标,而不需要等待前一个动作完成。
  3. 周期性反馈:在动作执行过程中,服务器可以持续地发送反馈信息给客户端,而客户端可以异步地处理这些反馈。
  4. 结果处理:动作完成后,结果会异步地返回给客户端。客户端可以设置一个回调函数来处理这个结果,而不是同步等待。
  5. 取消机制:客户端可以在任何时候异步地发送取消请求,而不会阻塞其他操作。
    机器人画圆
$ ros2 run learning_action action_move_server 
$ ros2 run learning_action action_move_client

终端中,我们可以看到客户端发送动作目标之后,服务器端开始模拟机器人运动,每30度发送一次反馈信息,最终完成运动,并反馈结束运动的信息。
接口定义,其余和上面一样
learning_interface/action/MoveCircle.action

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

8 参数(相当于全局变量)

9 分布式通信(十分简单,不需要做任何配置)

只要树莓派和pc在同一局域网下,安装好ROS2,即可实现通讯。可以在电脑端设置小乌龟,树莓派上设置键盘控制海龟,是没有问题的。

ROS2提供了一个DOMAIN的机制,就类似分组一样,处于同一个DOMAIN中的计算机才能通信,我们可以在电脑和树莓派端的.bashrc中加入这样一句配置,即可将两者分配到一个小组中:

$ export ROS_DOMAIN_ID=<your_domain_id># export ROS_DOMAIN_ID=31 类似这样

通过树莓派对相机做驱动,同时把图像发布出来, 电脑做订阅并且完成图像的识别。

10 DDS:数据分发服务 机器人的神经网络

1. 类似于旋转火锅,每个人只拿自己想要的东西。

启动第一个终端,我们使用best_effort创建一个发布者节点,循环发布任意数据,在另外一个终端中,如果我们使用reliable模型订阅同一话题,无法实现数据通信,如果修改为同样的best_effort,才能实现数据传输。

$ ros2 topic pub /chatter std_msgs/msg/Int32 "data: 42" --qos-reliability best_effort 
$ ros2 topic echo /chatter --qos-reliability reliable
$ ros2 topic echo /chatter --qos-reliability best_effort

如何去查看ROS2系统中每一个发布者或者订阅者的QoS策略呢,在topic命令后边跟一个"–verbose"参数就行了。

$ ros2 topic info /chatter --verbose

11 Launch文件

一次性启动多个节点的脚本
/home/hw/dev_ws/src/ros2_21_tutorials-master/learning_launch/launch/simple.launch.py

from launch import LaunchDescription           # launch文件的描述类
from launch_ros.actions import Node            # 节点启动的描述类

def generate_launch_description():             # 自动生成launch文件的函数
    return LaunchDescription([                 # 返回launch文件的描述信息
        Node(                                  # 配置一个节点的启动
            package='learning_topic',          # 节点所在的功能包
            executable='topic_helloworld_pub', # 节点的可执行文件
        ),
        Node(                                  # 配置一个节点的启动
            package='learning_topic',          # 节点所在的功能包
            executable='topic_helloworld_sub', # 节点的可执行文件名
        ),
    ])

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值