话题/服务/动作 三大通信

话题

《实例1-Helloworld》

发布结点:创建发布者.定时回调.发布消息

class MyNode(Node):
    def __init__(self):
        super().__init__('my_node')
        self.publisher = self.create_publisher(String, 'chatter', 10)//创建发布者
        self.timer = self.create_timer(1.0, self.timer_callback)//定时回调

# 创建数据-发布数据    
def timer_callback(self):
        msg = String()                //以某种格式实例化
        msg.data = 'Hello ROS2'       //填充数据
        self.publisher.publish(msg)   //发布数据
        self.get_logger().info('Publishing: "%s"' % msg.data)

def main(args=None):
    rclpy.init(args=args)
    node = MyNode()  //核心步骤
    rclpy.spin(node)
    node.destroy_node()
    rclpy.shutdown()

订阅结点:创建订阅者.订阅回调函数(处理消息)

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

《实例2-传递图片识别红色》

import rclpy                        # ROS2必使用库1
from rclpy.node import Node         # ROS2必使用库2
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)                                           
        # 创建发布者对象+创建定时器
        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()               

    def timer_callback(self):
        # 读取图像数据(opencv的numpy格式)
        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):                                

    # 初始化大接口
    rclpy.init(args=args)                           
    # 实例化小结点
    node = ImagePublisher("topic_webcam_pub")   

    # 循环执行小结点-直至CtrlC被按下
    rclpy.spin(node)                            

    # 摧毁小结点
    node.destroy_node() 
    # 关闭大结点
    rclpy.shutdown()         

注意:在init构造函数中,定时器只是被自动创建了,但是并没有使用,它的使用时机在rclpy.spin


import rclpy                            # 大接口
from rclpy.node import Node             # 小结点
from sensor_msgs.msg import Image       # 图像消息类型

from cv_bridge import CvBridge          # 为图像转换格式的库
import cv2                              # 为读取图像的库
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)              
        # 创建订阅者对象
        self.sub = self.create_subscription(Image, 'image_raw', self.listener_callback, 10)     
        # 创建格式转换对象
        self.cv_bridge = CvBridge()                             

        # 探测红色轮廓功能函数
    def object_detect(self, image):
        hsv_img = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)        
        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)               
            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)                            
        cv2.waitKey(10)

    # 订阅者回调函数-处理data数据
    def listener_callback(self, data):
        self.get_logger().info('Receiving video frame')         
        image = self.cv_bridge.imgmsg_to_cv2(data, 'bgr8')      
        self.object_detect(image)                               


def main(args=None):                                        
    # 初始化大接口
    rclpy.init(args=args)                                   
    # 实例化小结点
    node = ImageSubscriber("topic_webcam_sub")              
    # 循环执行结点
    rclpy.spin(node)                                        
    # 摧毁小结点
    node.destroy_node()                                     
    # 关闭大接口
    rclpy.shutdown()                                        

《摄像头应用》
1.让虚拟机连接到usb摄像头

"这步操作是为了将主机的usb连接的摄像头来透传给虚拟机,使用虚拟机独占该设备"

# 2.安装ros2的usb相机驱动(若已安装则不用)
sudo apt install ros-humble-usb-cam 
# 3.运行usb相机驱动(并采集和发布图像数据到img_raw话题)-图像数据来源必须启动
ros2 run usb_cam usb_cam_node_exe   
# 4.运行订阅者节点处理数据
ros2 run learning_topic topic_webcam_sub 

usb_came_node_exe结点会捕获USB摄像头采集的图像Image,然后发布到名为“image_raw”的话题之中,订阅者结点可以接收该图像并进行相应的处理
...待补充

服务

 《服务器端》

import rclpy                                     # 导入ros-python库
from rclpy.node   import Node                    # 导入结点
from learning_interface.srv import AddTwoInts    # 自定义的服务接口

class adderServer(Node):
    #构造函数
    def __init__(self, name):
        super().__init__(name)                                                             
        # 创建服务端对象_
        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):                                     
    rclpy.init(args=args)                            #初始化大接口
    node = adderServer("service_adder_server")       #实例化小结点
    rclpy.spin(node)                                 #循环执行该结点
    node.destroy_node()                              #摧毁小结点
    rclpy.shutdown()                                 #关闭大接口

!服务端的回调函数的形参是固定的(self,request,response)无需声明,ROS2自动为接口创建

《客户端》

import sys
import rclpy                                                                      
from rclpy.node import Node                                                     
from learning_interface.srv import AddTwoInts                                     

class adderClient(Node):
    # 1.结点的初始化函数(构造函数)
    def __init__(self, name):
        super().__init__(name)                                                    
        #1.1创建客户端对象
        self.client = self.create_client(AddTwoInts, 'add_two_ints')              
        #1.2等待服务端打开
        while not self.client.wait_for_service(timeout_sec=1.0):                  
            self.get_logger().info('service not available, waiting again...') 
        #1.2创建数据对象
        self.request = AddTwoInts.Request()                                       

    # 2.必有的结点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)                                                         
    #实例化小结点
    node = adderClient("service_adder_client")                                    
    #调用结点功能函数
    node.send_request()                                                              
    #接口正常运行
    while rclpy.ok():                                                             
        #处理node的所有回调函数,这里是接口一次服务端的响应
        rclpy.spin_once(node)                                                     
        # 异步操作是否完成?
        if node.future.done():                                                                                
            # 尝试通过future获取response
            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接口

<if:try-exception-else结构>

《对程序的两种使用方式是什么-需要掌握》

1.传统的运行节点方式

#1.首先打开摄像机驱动获取数据,并发布数据,(并不在画面上打开摄像头)
ros2 run usb_cam usb_cam_node_exe
#2.然后运行服务端脚本-订阅数据目标检测-并等待客户端数据传来,(通过cv2.imshow(xx,img)来显示图像)
ros2 run learning_service(包) service_object_server(命令名)
#3.运行客户端脚本-发送数据并接收结果展示
ros2 run learning_service(包) service_object_client(命令名)

动作

 

《小海龟action通信实践》

一号终端
# 执行小海龟节点
ros2 run turtlesim turtlesim_node

二号终端
# 执行小海龟的键盘控制结点
ros2 run turtlesim turtle_teleop_key

命令的形式为:ros2 run 功能包名称 结点名称
底层通讯机制是:
键盘控制结点通过向/turtle1/cmd_vel话题
发送
消息类型为 geometry_msgs/msg/Twist的数据
 

# 查看所有Action名称                  
ros2 action list                 
-------输出---------------
/turtle1/rotate_absolute

# 查看某一Action的详细信息
ros2 action info /turtle1/rotate_absolute

# 发送请求-固定前缀+服务名称+数据格式+具体数据
ros2 action send_goal /turtle1/rotate_absolute turtlesim/action/RotateAbsolute "{theta: 3.14 }" 

#添加反馈
ros2 action send_goal /turtle1/rotate_absolute turtlesim/action/RotateAbsolute "{theta: 3.14 }" --feedback

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):                     
            # “填充”反馈数据.state并打印
            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()                    
        # “填充”结果数据容器.finish
        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接口


 

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):
        # ROS2节点父类初始化
        super().__init__(name)                   
        #创建客户端对象-数据结构,动作名称即可
        self._action_client = ActionClient(self, MoveCircle, 'move_circle') 
    # 向服务端发送goal
    def send_goal(self, enable):                 
        # 声明goal数据容器
        goal_msg = MoveCircle.Goal()             
        # 填充实际内容
        goal_msg.enable = enable                 
        # 等待服务端开启
        self._action_client.wait_for_server()    
        # 异步发送goal数据
        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) 
   
    # 回调1——服务端是否接收到goal消息
    def goal_response_callback(self, future):           
        #获取goal_handle
        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)  
       
    # 回调2——创建处理周期反馈消息的回调函数
    def feedback_callback(self, feedback_msg):                                
        # 读取反馈的数据
        feedback = feedback_msg.feedback                                     
        # 处理反馈的数据
        self.get_logger().info('Received feedback: {%d}' % feedback.state)



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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值