ROS2机器人开发入门

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中配置

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/333280.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

如何压缩视频到50m以内?这几个参数设置了吗?

在我们的日常生活中&#xff0c;视频文件经常占据较大的存储空间&#xff0c;给我们存储和传输带来了困扰&#xff0c;那么如何将视频文件压缩至50m以下呢&#xff1f;下面就为大家分享三个实用的方法&#xff0c;轻松解决视频过大问题。 方法一&#xff1a;调整视频分辨率 视…

亚马逊鲲鹏系统:强大防指纹技术引领全自动账号管理新时代

亚马逊作为全球最大的电商平台之一&#xff0c;一直都很受客户欢迎&#xff0c;而亚马逊鲲鹏系统的全新推出&#xff0c;旨在解决买家账号过多时的管理难题。据了解&#xff0c;这一系统不仅能够有效防止账号关联&#xff0c;而且在保障每个账号独立运行的同时&#xff0c;还拥…

JAVA——数据类型与运算符

数据类型 注意事项&#xff1a;1.初始化操作是可选的, 但是建议创建变量的时候都显式初始化. 2.最后不要忘记分号, 否则会编译失败. 3.初始化设定的值为 10L , 表示一个长整型的数字. 10l 也可以. 4.float 类型在 Java 中占四个字节, 遵守 IEEE 754 标准. 由于表示的数据精度范…

k8s的坑,从这里开始

转载说明&#xff1a;如果您喜欢这篇文章并打算转载它&#xff0c;请私信作者取得授权。感谢您喜爱本文&#xff0c;请文明转载&#xff0c;谢谢。 以前刚接触k8s时踩了不少坑&#xff0c;比如这些&#xff1a; 问题1 1、在master节点使用kubectl命令时&#xff0c;报错&…

新手如何学习单片机入行?

新手如何学习单片机入行&#xff1f; 在开始前我有一些资料&#xff0c;是我根据网友给的问题精心整理了一份「单片机的资料从专业入门到高级教程」&#xff0c; 点个关注在评论区回复“888”之后私信回复“888”&#xff0c;全部无偿共享给大家&#xff01;&#xff01;&…

使用 Docker 部署 的WAF: 雷池社区版

Web应用防火墙&#xff08;WAF&#xff09;是保护网站不受恶意攻击的关键组件。 使用 Docker 部署雷池社区版&#xff0c;可以大大简化安全管理工作。 一、WAF 雷池社区版简介 雷池社区版是一种流行的开源 Web 应用防火墙&#xff0c;它提供基本的安全保护&#xff0c;如防止…

10个常考的前端手写题,你全都会吗?

前言 &#x1f4eb; 大家好&#xff0c;我是南木元元&#xff0c;热爱技术和分享&#xff0c;欢迎大家交流&#xff0c;一起学习进步&#xff01; &#x1f345; 个人主页&#xff1a;南木元元 今天来分享一下10个常见的JavaScript手写功能。 目录 1.实现new 2.call、apply、…

CentOS 6.10 安装图解

特特特别的说明 CentOS发行版已经不再适合应用于生产环境&#xff0c;客观条件不得不用的话&#xff0c;优选7.9版本&#xff0c;8.5版本次之&#xff0c;最次6.10版本&#xff08;比如说Oracle 11GR2就建议在6版本上部署&#xff09;&#xff01; 引导和开始安装 选择倒计时结…

python爬虫--网页代码抓取

我回来了。 目录 前言一、爬虫是什么&#xff1f;二、使用步骤代码讲解第一版第二版第三版 总结 前言 爬虫&#xff0c;第一章 一、爬虫是什么&#xff1f; 爬虫是指一种自动化程序&#xff0c;通常被用于互联网上的数据采集。这些程序会模拟人类用户的行为&#xff0c;通过…

(2023版)斯坦福CS231n学习笔记:DL与CV教程 (12) | 视觉模型可视化与可解释性(Visualizing and Understanding)

前言 &#x1f4da; 笔记专栏&#xff1a;斯坦福CS231N&#xff1a;面向视觉识别的卷积神经网络&#xff08;23&#xff09;&#x1f517; 课程链接&#xff1a;https://www.bilibili.com/video/BV1xV411R7i5&#x1f4bb; CS231n: 深度学习计算机视觉&#xff08;2017&#xf…

【题解 优化dp】 B - Base Station Construction

题目描述&#xff1a; 分析&#xff1a; 当dp状态设定不好的时候&#xff0c;我们不妨从最简单的部分出发 设 f i f_i fi​表示必须在第i个点建设基站&#xff0c;并且i号点之前的线段全部满足要求时所需要的最小代价 为什么这么设呢&#xff1f; 这道题想要入手&#xff0c;…

数据结构Java版(2)——栈Stack

一、概念 栈也是一种线性数据结构&#xff0c;最主要的特点是入栈顺序和出栈顺序是相反的&#xff0c;操作时只能从栈顶进行操作&#xff0c;在Java中给我们提供了一个泛型栈——Stack&#xff0c;其中最常用的方法有&#xff1a; void push(E):进栈E pop():退栈E peek():查看…

Java中创建List接口、ArrayList类和LinkedList类的常用方法(一)

List接口 要了解List接口&#xff0c;就不得不说起Java的集合框架。 &#xff08;该图来自菜鸟教程&#xff09; Collection接口和Map接口 Java 集合框架主要包括两种类型的容器&#xff0c;集合Collection和图Map。 Collection接口代表了单列集合&#xff0c;它包含了一组…

并发编程知识点梳理

并发编程 在了解并发编程基本知识&#xff0c;先了解几本书&#xff0c;方便学习提高&#xff0c;分别为&#xff1a;java编程思想、企业应用架构模式、并发编程实战。 多线程中的设计模式有&#xff1a;Future、Master-Worker、生产者-消费者。 本次课程分为以下几部分进行…

js - - - - - getSelection 对光标和选区的操作

window.getSelection getSelection示例代码属性方法 getSelection 官方MDN地址 示例代码 <template><div>这里是一段默认的文字内容</div> </template> <script> export default {name: "Home",mounted() {document.addEventListen…

在PyCharm中创建Flask项目

在 PyCharm 中创建 Flask 项目的步骤如下&#xff1a; 打开 PyCharm&#xff0c;并选择 "Create New Project"&#xff08;新建项目&#xff09;。在弹出的窗口中&#xff0c;选择左侧的 "Python" 选项&#xff0c;然后选择右侧的 "Flask" 项目…

iPerf3 使用指南

文章目录 iPerf3 使用指南1 iPerf3 简介2 安装指令2.1 Windows2.2 Linux 3 入门用法4 进阶用法4.1 启动服务端4.2 TCP 带宽测试4.3 UDP 带宽测试 5 iPerf3 命令说明 iPerf3 使用指南 1 iPerf3 简介 iPerf3 是用于主动测试 IP 网络上最大可用带宽的工具。它支持时序、缓冲区、…

快速评估您的IT事件管理实践

您在寻找提高事件响应效率和有效性的方法吗&#xff1f;为什么需要评估IT事件管理&#xff1f; 尽管改进组织的事件管理实践不会一蹴而就&#xff0c;但我们还是要从制定明确的事件响应计划开始。 使用ServiceDesk Plus&#xff0c;快速检查您的事件管理实践是否符合行业标准&a…

SpiderFlow爬虫平台漏洞利用分析(CVE-2024-0195)

1. 漏洞介绍 SpiderFlow爬虫平台项目中spider-flow-web\src\main\java\org\spiderflow\controller\FunctionController.java文件的FunctionService.saveFunction函数调用了saveFunction函数&#xff0c;该调用了自定义函数validScript&#xff0c;该函数中用户能够控制 functi…

学习vue3(二)

三、其它 Composition API 1.shallowReactive 与 shallowRef shallowReactive&#xff1a;只处理对象最外层属性的响应式&#xff08;浅响应式&#xff09;。 shallowRef&#xff1a;只处理基本数据类型的响应式, 不进行对象的响应式处理。 什么时候使用? 如果有一个对象数…