话题
《实例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接口