ROS2机器人开发入门

发布时间:2024年01月19日

ROS2学习

ROS2对比ROS1的区别

架构
  • ROS1的架构下,所有节点需要使用Master进行管理
  • ROS2使用基于DDS的Discovery机制
API
  • ROS1中的大部分代码都是基于2009年2月设计的API

  • ROS2重新设计了用户API,但使用方法类似

编译系统
  • ROS1使用rosbuild、catkin管理项目
  • ROS2使用升级版的ament、colcon
OS
  • ROS1:Linux
  • ROS2:Linux、Windows、MAC、RTOS
通讯
  • ROS1:TCPROS/UDPROS
  • ROS2:DDS
节点模型
  • ROS1:publish/subscribe
  • ROS2:discovery
进程
  • ROS1:Nodelet
  • ROS2:Intra-process

image-20240117210110692

DDS是一种国际标准,常用于物联网系统,类似4G和5G

image-20240117210245498

ROS Middleware是一种中间件,它制定了一种接口来规范例如发数据、收数据、配置数据属性,任何厂家需要接入ROS社区需要按照片这种标准来写一种适配接口

ROS2中的核心概念与ROS1类似

image-20240117210634723

安装命令
# 设置编码
sudo apt update&&sudo apt install locales

sudo locale-gen en_US en_US.UTF-8

sudo update-locale LC_ALL=en_US.UTF-8 LANG=en_US.UTF-8

export LANG=en_US.UTF-8

# 添加源
sudo apt update && sudo apt install curl gnupg lsb-release
# 配置密钥
sudo curl -sSL https://raw.githubusercontent.com/ros/rosdistro/master/ros.key -o/usr/share/keyrings/ros-archive-keyring.gpg
# 配置ROS软件源
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/ros-archive-keyring.gpg] http://packages.ros.org/ros2/ubuntu $(source /etc/os-release && echo $UBUNTU_CODENAME) main" sudo tee /etc/apt/sources.list.d/ros2.list > /dev/null

# 正式安装ROS2
sudo apt update
sudo apt upgrade
sudo apt install ros-humble-desktop
# 设置环境变量
:WQ /opt/ros/humble/setup.bash
echo " source /opt/ros/humble/setup.bash" >> ~/.bashrc 
# 安装ROS2软件包
sudo apt update
sudo apt install ros-humble-ros1-bridge
sudo apt install ros-humble-****

ros2 run (ROS2)==rosrun(ROS1)

ros2 node + enter查看命令的用法

ros2 node info 节点 查看某一节点的详细信息

ROS工作空间(workspace):存放项目开发相关的文件夹是开发过程的大本营

image-20240118150901951

# 创建工作空间
mkdir -p ~/dev_ws/src
cd ~/dev_ws/src
git clone https://gitee.com/guyuehome/ros2_21_tutorials.git
# 安装依赖
sudo apt install -y python3-pip
sudo pip3 install rosdepc
sudo rosdepc init
rosdepc update
cd ..
rosdepc install -i --from-path src --rosdistro humble -y
# 编译工作空间
sudo apt install python3-colcon-ros # 安装编译器
cd ~/dev_ws/
colcon build

编译完成后为了让系统能找到功能包和可执行文件,需要设置环境变量

$ source install/local_setup.sh # 仅在当前终端生效
$ echo " source ~/dev_ws/install/local_setup.sh" >> ~/.bashrc # 所有终端均生效

创建功能包

支持C++11 和Python3.8

cd ~/dev_ws/src
ros2 pkg create --build-type ament_cmake learning_pkg_c               #C++
ros2 pkg create --build-type ament_python learning_pkg_python # Python

C++功能包

image-20240118154651126

package.xml描述了功能包的基本信息,例如,名字,版本号,功能,维护者,许可证

image-20240118154734253

描述该功能包会有哪些依赖

image-20240118155028423

CMakeLists.txt

设置一些编译规则,告诉系统如何编译c/c++代码

image-20240118155349089

Python功能包

image-20240118160317892

setup.py描述的是对功能包的基本信息以及对入口程序点的描述

image-20240118160407301

setup.cfg基本的工作空间的信息

image-20240118160627931

节点

  • 多个节点组成了整个机器人
  • 每个节点都是一个可以独立运行的可执行文件

  • 生成这些节点的编程语言是可以不同的

  • 节点可以运行在不同主机

  • 通过节点名称进行管理

image-20240118160933908

第一个节点实现(面向过程编程)

ros2 run 使用方法

image-20240118162109611

步骤

  1. 初始化编程接口
  2. 创建节点并初始化
  3. 实现节点功能
  4. 销毁节点并关闭接口
import rclpy                                     # ROS2 Python接口库
from rclpy.node import Node                      # ROS2 节点类
import time

def main(args=None):                             # ROS2节点主入口main函数
    rclpy.init(args=args)                        # ROS2 Python接口初始化
    node = Node("node_helloworld")               # 创建ROS2节点对象并进行初始化
    
    while rclpy.ok():                            # ROS2系统是否正常运行
        node.get_logger().info("Hello World")    # ROS2日志输出
        time.sleep(0.5)                          # 休眠控制循环时间
    
    node.destroy_node()                          # 销毁节点对象    
    rclpy.shutdown()                             # 关闭ROS2 Python接口

当我们新增加了一个Python程序之后,需要在这里面添加入口点

image-20240118163046752

面向对象的编程方式

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()          

同样需要添加入口点

image-20240118163651264

编写完代码后需要重新编译,将src中的代码copy到install中,后期ros2 run 的都是install中的代码

ros2 node list 查看当前节点

ros2 node info /node_object_webcam 查看/node_object_webcam节点的信息

话题:节点间传输数据的桥梁

  • 使用了基于DDS的发布订阅模型

  • 发布数据的对象称为发布者

  • 接收数据的对象称为订阅者

  • 传输的数据有固定的数据类型.msg文件

  • 发布者和订阅者可以不唯一

  • 使用异步通信机制

  • 消息是ROS中一种接口定义的方式,与编程语言无关

image-20240118194015438

image-20240118194521364

image-20240118204726922

发布者Publisher

流程

  1. 编程接口初始化
  2. 创建节点并初始化
  3. 创建发布者对象
  4. 创建并填充话题消息
  5. 发布话题消息
  6. 销毁节点并关闭接口
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接口
订阅者Subscriber
  1. 编程接口初始化
  2. 创建节点并初始化
  3. 创建订阅者对象
  4. 回调函数处理话题数据
  5. 销毁节点并关闭对象
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()   

image-20240118194816155

ROS2话题示例-发布图像话题

CvBridge用于转换ROS2和opencv中图像类型的转换

import rclpy                        # ROS2 Python接口库
from rclpy.node import Node         # ROS2 节点类
from sensor_msgs.msg import Image   # 图像消息类型
from cv_bridge import CvBridge      # ROS与OpenCV图像转换类
import cv2                          # Opencv图像处理库

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

    def __init__(self, name):
        super().__init__(name)                                           # ROS2节点父类初始化
        self.publisher_ = self.create_publisher(Image, 'image_raw', 10)  # 创建发布者对象(消息类型、话题名、队列长度)
        self.timer = self.create_timer(0.1, self.timer_callback)         # 创建一个定时器(单位为秒的周期,定时执行的回调函数)
        self.cap = cv2.VideoCapture(0)                                   # 创建一个视频采集对象,驱动相机采集图像(相机设备号)
        self.cv_bridge = CvBridge()                                      # 创建一个图像转换对象,用于稍后将OpenCV的图像转换成ROS的图像消息

    def timer_callback(self):
        ret, frame = self.cap.read()                                     # 一帧一帧读取图像
        
        if ret == True:                                                  # 如果图像读取成功
            self.publisher_.publish(
                self.cv_bridge.cv2_to_imgmsg(frame, 'bgr8'))             # 发布图像消息

        self.get_logger().info('Publishing video frame')                 # 输出日志信息,提示已经完成图像话题发布

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

ROS2话题示例-订阅图像话题
import rclpy                            # ROS2 Python接口库
from rclpy.node import Node             # ROS2 节点类
from sensor_msgs.msg import Image       # 图像消息类型
from cv_bridge import CvBridge          # ROS与OpenCV图像转换类
import cv2                              # Opencv图像处理库
import numpy as np                      # Python数值计算库

lower_red = np.array([0, 90, 128])      # 红色的HSV阈值下限
upper_red = np.array([180, 255, 255])   # 红色的HSV阈值上限

"""
创建一个订阅者节点
"""
class ImageSubscriber(Node):
    def __init__(self, name):
        super().__init__(name)                                  # ROS2节点父类初始化
        self.sub = self.create_subscription(
            Image, 'image_raw', self.listener_callback, 10)     # 创建订阅者对象(消息类型、话题名、订阅者回调函数、队列长度)
        self.cv_bridge = CvBridge()                             # 创建一个图像转换对象,用于OpenCV图像与ROS的图像消息的互相转换

    def object_detect(self, image):
        hsv_img = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)        # 图像从BGR颜色模型转换为HSV模型
        mask_red = cv2.inRange(hsv_img, lower_red, upper_red)   # 图像二值化
        contours, hierarchy = cv2.findContours(
            mask_red, cv2.RETR_LIST, cv2.CHAIN_APPROX_NONE)     # 图像中轮廓检测

        for cnt in contours:                                    # 去除一些轮廓面积太小的噪声
            if cnt.shape[0] < 150:
                continue

            (x, y, w, h) = cv2.boundingRect(cnt)                # 得到苹果所在轮廓的左上角xy像素坐标及轮廓范围的宽和高
            cv2.drawContours(image, [cnt], -1, (0, 255, 0), 2)  # 将苹果的轮廓勾勒出来
            cv2.circle(image, (int(x+w/2), int(y+h/2)), 5,
                       (0, 255, 0), -1)                         # 将苹果的图像中心点画出来

        cv2.imshow("object", image)                             # 使用OpenCV显示处理后的图像效果
        cv2.waitKey(10)

    def listener_callback(self, data):
        self.get_logger().info('Receiving video frame')         # 输出日志信息,提示已进入回调函数
        image = self.cv_bridge.imgmsg_to_cv2(data, 'bgr8')      # 将ROS的图像消息转化成OpenCV图像
        self.object_detect(image)                               # 苹果检测


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

usb相机的标准驱动
 sudo apt install ros-humble-usb-cam
 ros2 run usb_cam usb_cam_node_exe
 ros2 run learning_topic topic_webcam_sub

服务

  • 服务通信:CS模型

  • 同步通信机制

  • 服务器是唯一的,客户端可以不唯一

  • .srv文件定义请求和应答数据结构

image-20240118204939164

image-20240118205214907

服务器端
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接口

AddTwoInts类似话题中的消息

客户端

流程

  1. 编程接口初始化
  2. 创建节点并初始化
  3. 创建客户端对象
  4. 创建并发送请求数据
  5. 等待服务器端应答数据
  6. 销毁节点并关闭接口
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接口

查看服务接口类型 ros2 service type /get_target_position

通过命令行对服务进行请求 ros2 service call /get_target_position learning_interface/srv/GetObjectPosition

通信接口

image-20240118213025290

为了实现每个节点都可以使用不同的语言编程,使用了与编程语言无关数据接口,这些接口在编译过程中会自动生成对应的C++、Python等语言中的数据结构

话题

image-20240118213216843

服务

包括请求和应答,通过中间的—区分

image-20240118213237133

ros2

用来描述机器人的动作过程,动作的目标,最终的结果,周期的反馈

image-20240118213328799

ros2 interface list 查看系统中所有的接口定义

ros2 interface show sensor_msgs/msg/Image 查看具体的定义

ros2 interface package learning_interface查看learning_interface功能包中定义的接口

CMakeList.txt

python中通过import使用

image-20240118221037748

从learning_interface功能包下的srv文件夹下引入GetObjectPosition

image-20240118220127373

动作

  • CS客户端服务器模型

  • 客户端发送请求想让机器人做什么

  • 服务端控制机器人达到控制的目标,同时去周期反馈动作执行过程中的状态

  • 同步通信机制

image-20240118225101897

image-20240118225322654

当客户端发送运动目标时,使用的时服务的请求调用,服务器端会反馈应答,表示收到命令,动作的反馈过程就是会提的周期发布,服务器端时发布者,客户端是订阅者,动作是一种应用层的通信机制,其底层是根据话题跟服务来实现的

ros2 action list 查看当前存在的动作

image-20240118230246525

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

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

参数
  • ROS机器人的是全局共享字典

  • 不同节点都可以访问

  • 由键与值组成

  • 可实现动态监控

image-20240118232217168

ros2 param describe turtlesim background_b查看turtlesim 的参数background_b的作用

ros2 param get turtlesim background_b 查看turtlesim 的参数background_b的具体值

ros2 param set turtlesim background_b 设置turtlesim 的参数background_b的具体值

使用配置文件

ros2 param dump turtlesim >> turtlesim.yaml 将turtlesim 中所有的参数保存到 turtlesim.yaml中

ros2 param load turtlesim turtlesim.yaml 将turtlesim.yaml中的参数加载到turtlesim中

在程序中设置参数和读取参数
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接口
参数在机器人中的应用

可以在命令行修改参数值

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

"""
@作者: 古月居(www.guyuehome.com)
@说明: ROS2参数示例-设置目标识别的颜色阈值参数
"""

import rclpy                      # ROS2 Python接口库
from rclpy.node import Node       # ROS2 节点类
from sensor_msgs.msg import Image # 图像消息类型
from cv_bridge import CvBridge    # ROS与OpenCV图像转换类
import cv2                        # Opencv图像处理库
import numpy as np                # Python数值计算库

lower_red = np.array([0, 90, 128])     # 红色的HSV阈值下限
upper_red = np.array([180, 255, 255])  # 红色的HSV阈值上限

"""
创建一个订阅者节点
"""
class ImageSubscriber(Node):
  def __init__(self, name):
    super().__init__(name)                                  # ROS2节点父类初始化    
    self.sub = self.create_subscription(Image,              # 创建订阅者对象(消息类型、话题名、订阅者回调函数、队列长度)     
                  'image_raw', self.listener_callback, 10) 
    self.cv_bridge = CvBridge()                             # 创建一个图像转换对象,用于OpenCV图像与ROS的图像消息的互相转换

    self.declare_parameter('red_h_upper', 0)                # 创建一个参数,表示阈值上限
    self.declare_parameter('red_h_lower', 0)                # 创建一个参数,表示阈值下限
    
  def object_detect(self, image):
    upper_red[0] = self.get_parameter('red_h_upper').get_parameter_value().integer_value      # 读取阈值上限的参数值
    lower_red[0] = self.get_parameter('red_h_lower').get_parameter_value().integer_value      # 读取阈值下限的参数值
    self.get_logger().info('Get Red H Upper: %d, Lower: %d' % (upper_red[0], lower_red[0]))   # 通过日志打印读取到的参数值
    
    hsv_img = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)                                          # 图像从BGR颜色模型转换为HSV模型
    mask_red = cv2.inRange(hsv_img, lower_red, upper_red)                                     # 图像二值化
    contours, hierarchy = cv2.findContours(mask_red, cv2.RETR_LIST, cv2.CHAIN_APPROX_NONE)    # 图像中轮廓检测
    for cnt in contours:                                                                      # 去除一些轮廓面积太小的噪声
        if cnt.shape[0] < 150:
            continue
            
        (x, y, w, h) = cv2.boundingRect(cnt)                                      # 得到苹果所在轮廓的左上角xy像素坐标及轮廓范围的宽和高
        cv2.drawContours(image, [cnt], -1, (0, 255, 0), 2)                        # 将苹果的轮廓勾勒出来
        cv2.circle(image, (int(x+w/2), int(y+h/2)), 5, (0, 255, 0), -1)           # 将苹果的图像中心点画出来
        
    cv2.imshow("object", image)                                                   # 使用OpenCV显示处理后的图像效果
    cv2.waitKey(50)
       
  def listener_callback(self, data):
    self.get_logger().info('Receiving video frame')     # 输出日志信息,提示已进入回调函数
    image = self.cv_bridge.imgmsg_to_cv2(data, "bgr8")  # 将ROS的图像消息转化成OpenCV图像
    self.object_detect(image)                            # 苹果检测
  
def main(args=None):                                    # ROS2节点主入口main函数
    rclpy.init(args=args)                               # ROS2 Python接口初始化
    node = ImageSubscriber("param_object_detect")       # 创建ROS2节点对象并进行初始化
    rclpy.spin(node)                                    # 循环等待ROS2退出
    node.destroy_node()                                 # 销毁节点对象
    rclpy.shutdown()                                    # 关闭ROS2 Python接口

分布式通信

处于同一个网络中就可以通信

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

image-20240119001134289

export ROS_DOMAIN_ID=<your_domain_id>

两台设备要在同一个domain中,就可以在同一台设备中一样通信

DDS

  • DDS是一种通信模式,数据为中心模式,DDS,Data Distribution Service,即数据分发服务

  • 专门为实时系统设计的数据分发/订阅标准

  • DDS强调以数据为中心,提供丰富的服务质量策略(QoS),以保障数据进行实时、高效、灵活地分发,可满足各种分布式实时通信应用需求。

  • 是类似4G、5G的一种通信标准

image-20240119003820776

image-20240119003851927

image-20240119004046400

ROS Middleware是一个接口,把兼容性的问题转移给了厂商,各大厂商想要将自己的DDS接入ROS2系统中,需要按照ROS2的接口标准来开发驱动

,根本目的是为了提高软件代码的复用性

image-20240119004500182

DDS将各个应用程序绑定到了一起,只有在一个Domain小组里面的节点菜才能互相通信

image-20240119004922703

dds的qos的数据传输不一样无法产生通信的数据连接

image-20240119005043687

qos数据传输模式一样可以产生通信连接

image-20240119005120909

在代码中配置QOS
发布者
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接口

订阅者
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接口

Launch多节点启动与配置脚本

Launch文件可以自由组合和配置各个结点

使用python脚本描述

image-20240119005945314

启动时指定配置文件

image-20240119010300355

image-20240119010448649

image-20240119010536024

找到系统的learning_launch文件夹里的rviz文件夹里的turtle_rviz.rviz文件

ROS中不允许同名的节点存在,通过命名空间做一个区分

image-20240119010947306

image-20240119012104959

launch也需要在setup.py中配置

文章来源:https://blog.csdn.net/Johnor/article/details/135696735
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。