导图社区 ROS2系统架构及相关定义
这是一篇关于ROS2系统架构及相关定义的思维导图,主要内容包括:服务(Service):节点间的你问我答,系统架构,工作空间(Workspace),开发过程的大本营,参数(Parameter) :机器人系统的全局字典,分布式通信(Distributed Communication) :多计算平台的任务分配。
编辑于2025-03-28 11:02:44这是一篇关于十二月计划(11.29-12.31)的思维导图,主要内容包括:计划,目标,每日任务,调整。通过系统的学习和积累,提升各科成绩。
这是一篇关于十一月计划11.1-11.30的思维导图,主要内容包括:计划,目标,每日任务(1 2),调整。通过系统的学习和积累,提升各科成绩,
"十月冲刺计划:高效突破薄弱项,稳步提升各科成绩!我的1031031计划围绕三大核心:主科巩固与理科强化。数学主攻函数和不等式英语每天过单词 续写训练,目标120分物理专练步步高十个专题,冲刺75分生物通过试卷打卡突破80分。技术科目保持85分,语文利用零碎时间积累素材。每日固定任务包括国师第二阶段、英语3500词、续写素材收集,同时兼顾语文课、演讲等校园事务。时间管理上注重排队、早饭前等碎片化利用。
社区模板帮助中心,点此进入>>
这是一篇关于十二月计划(11.29-12.31)的思维导图,主要内容包括:计划,目标,每日任务,调整。通过系统的学习和积累,提升各科成绩。
这是一篇关于十一月计划11.1-11.30的思维导图,主要内容包括:计划,目标,每日任务(1 2),调整。通过系统的学习和积累,提升各科成绩,
"十月冲刺计划:高效突破薄弱项,稳步提升各科成绩!我的1031031计划围绕三大核心:主科巩固与理科强化。数学主攻函数和不等式英语每天过单词 续写训练,目标120分物理专练步步高十个专题,冲刺75分生物通过试卷打卡突破80分。技术科目保持85分,语文利用零碎时间积累素材。每日固定任务包括国师第二阶段、英语3500词、续写素材收集,同时兼顾语文课、演讲等校园事务。时间管理上注重排队、早饭前等碎片化利用。
ROS2系统架构及相关定义
服务(Service):节点间的你问我答
客户端/服务器模型
从服务的实现机制上来看,这种你问我答的形式叫做客户端/服务器模型 ,简称为CS模型,客户端在需要某些数据的时候,针对某个具体的服务,发送请求信息,服务器端收到请求之后,就会进行处理并反馈应答信息。
同步通信
这个过程一般要求越快越好,假设服务器半天没有反应,你的浏览器一直转圈圈,那有可能是服务器宕机了,或者是网络不好,所以相比话题通信,在服务通信中,客户端可以通过接收到的应答信息,判断服务器端的状态,我们也称之为同步通信。
一对多通信
服务器端唯一,客户端不唯一
服务接口
和话题通信类似,服务通信的核心还是要传递数据,数据变成了两个部分,一个请求的数据 ,比如请求苹果位置的命令,还有一个 反馈的数据 ,比如反馈苹果坐标位置的数据,这些数据和话题消息一样,在ROS中也是要标准定义的,话题使用.msg文件定义,服务使用的是.srv文件定义
案例
加法求解器
#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ @作者: 古月居(www.guyuehome.com) @说明: ROS2服务示例-发送两个加数,请求加法器计算 """ 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接口
entry_points={ 'console_scripts': [ 'service_adder_client = learning_service.service_adder_client:main', ], },
客户端流程
编程接口初始化
创建节点并初始化
创建客户端对象
创建并发送请求数据
等待服务器端应答数据
销毁节点并关闭接口
#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ @作者: 古月居(www.guyuehome.com) @说明: ROS2服务示例-提供加法器的服务器处理功能 """ 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接口
entry_points={ 'console_scripts': [ 'service_adder_client = learning_service.service_adder_client:main', 'service_adder_server = learning_service.service_adder_server:main', ], },
服务端流程
编程接口初始化
创建节点并初始化
创建服务器端对象
通过回调函数处进行服务
向客户端反馈应答结果
销毁节点并关闭接口
机器视觉识别
此时有三个节点
相机驱动节点,发布图像数据; 视觉识别节点,订阅图像数据,并且集成了一个服务器端对象,随时准备提供目标位置; 客户端节点,我们可以认为是一个机器人目标跟踪的节点,当需要根据目标运动时,就发送一次请求,然后拿到一个当前的目标位置。
客户端
#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ @作者: 古月居(www.guyuehome.com) @说明: ROS2服务示例-请求目标识别,等待目标位置应答 """ import rclpy # ROS2 Python接口库 from rclpy.node import Node # ROS2 节点类 from learning_interface.srv import GetObjectPosition # 自定义的服务接口 class objectClient(Node): def __init__(self, name): super().__init__(name) # ROS2节点父类初始化 self.client = self.create_client(GetObjectPosition, 'get_target_position') while not self.client.wait_for_service(timeout_sec=1.0): self.get_logger().info('service not available, waiting again...') self.request = GetObjectPosition.Request() def send_request(self): self.request.get = True self.future = self.client.call_async(self.request) def main(args=None): rclpy.init(args=args) # ROS2 Python接口初始化 node = objectClient("service_object_client") # 创建ROS2节点对象并进行初始化 node.send_request() while rclpy.ok(): 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 object position:\n x: %d y: %d' % (response.x, response.y)) break node.destroy_node() # 销毁节点对象 rclpy.shutdown() # 关闭ROS2 Python接口
entry_points={ 'console_scripts': [ 'service_adder_client = learning_service.service_adder_client:main', 'service_adder_server = learning_service.service_adder_server:main', 'service_object_client = learning_service.service_object_client:main', ], },
服务端
#!/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 # 图像消息类型 import numpy as np # Python数值计算库 from cv_bridge import CvBridge # ROS与OpenCV图像转换类 import cv2 # Opencv图像处理库 from learning_interface.srv import GetObjectPosition # 自定义的服务接口 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.srv = self.create_service(GetObjectPosition, # 创建服务器对象(接口类型、服务名、服务器回调函数) 'get_target_position', self.object_position_callback) self.objectX = 0 self.objectY = 0 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) # 将苹果的图像中心点画出来 self.objectX = int(x+w/2) self.objectY = int(y+h/2) 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 object_position_callback(self, request, response): # 创建回调函数,执行收到请求后对数据的处理 if request.get == True: response.x = self.objectX # 目标物体的XY坐标 response.y = self.objectY self.get_logger().info('Object position\nx: %d y: %d' % (response.x, response.y)) # 输出日志信息,提示已经反馈 else: response.x = 0 response.y = 0 self.get_logger().info('Invalid command') # 输出日志信息,提示已经反馈 return response def main(args=None): # ROS2节点主入口main函数 rclpy.init(args=args) # ROS2 Python接口初始化 node = ImageSubscriber("service_object_server") # 创建ROS2节点对象并进行初始化 rclpy.spin(node) # 循环等待ROS2退出 node.destroy_node() # 销毁节点对象 rclpy.shutdown() # 关闭ROS2 Python接口
entry_points={ 'console_scripts': [ 'service_adder_client = learning_service.service_adder_client:main', 'service_adder_server = learning_service.service_adder_server:main', 'service_object_client = learning_service.service_object_client:main', 'service_object_server = learning_service.service_object_server:main', ], },
话题和服务是ROS中最为常用的两种数据通信方法,前者适合传感器、控制指令等周期性、单向传输的数据,后者适合一问一答,同步性要求更高的数据,比如获取机器视觉识别到的目标位置。
系统架构
ROS/ROS2定义及对比
对比
管理
ROS1的架构下所有节点需要使用Master进行管理
ROS2使用基于DDS的Discovery机制,不用Master
系统
ROS1使用rosbuild,catkin管理项目
ROS2使用升级版的ament,colcon
ROS2命令行操作
工作空间(Workspace),开发过程的大本营
src
代码空间
install
安装空间
build
编译空间
log
日志空间
参数(Parameter) :机器人系统的全局字典
分布式通信(Distributed Communication) :多计算平台的任务分配
DDS(Data Distribution Service) :机器人的神经网络
话题(Topic) :节点间传递数据的桥梁
从话题本身的实现角度来看,使用了基于DDS的发布/订阅模型
创建步骤
编程接口初始化
创建节点并初始化
创建发布者对象
创建并填充话题消息
发布话题消息
销毁节点并关闭接口
例子
#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ @作者: 古月居(www.guyuehome.com) @说明: ROS2话题示例-发布“Hello World”话题 """ 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接口
完成代码的编写后需要设置功能包的编译选项,让系统知道Python程序的入口,打开功能包的setup.py文件,加入如下入口点的配置:
entry_points={ 'console_scripts': [ 'topic_helloworld_pub = learning_topic.topic_helloworld_pub:main', ], },
节点(Node):机器人的工作细胞
概念理解
节点在机器人系统中的职责就是执行某些具体的任务 ,从计算机操作系统的角度来看,也叫做进程;
每个节点都是一个可以独立运行的可执行文件 ,比如执行某一个python程序,或者执行C++编译生成的结果,都算是运行了一个节点;
既然每个节点都是独立的执行文件,那自然就可以想到,得到这个执行文件的编程语言可以是不同的 ,比如C++、Python,乃至Java、Ruby等更多语言。
这些节点是功能各不相同的细胞,根据系统设计的不同,可能位于计算机A,也可能位于计算机B,还有可能运行在云端,这叫做分布式 ,也就是可以分布在不同的硬件载体上;
每一个节点都需要有唯一的命名 ,当我们想要去找到某一个节点的时候,或者想要查询某一个节点的状态时,可以通过节点的名称来做查询。
节点也可以比喻是一个一个的工人,分别完成不同的任务,他们有的在一线厂房工作,有的在后勤部门提供保障,他们互相可能并不认识,但却一起推动机器人这座“工厂”,完成更为复杂的任务。
创建节点流程
编程接口初始化
创建节点并初始化
实现节点功能
销毁节点并关闭接口
节点常用命令行
$ ros2 node list # 查看节点列表 $ ros2 node info <node_name> # 查看节点信息
案例
Hello World节点(面向过程)
#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ @作者: 古月居(www.guyuehome.com) @说明: ROS2节点示例-发布“Hello World”日志信息, 使用面向过程的实现方式 """ 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接口
entry_points={ 'console_scripts': [ 'node_helloworld = learning_node.node_helloworld:main', ],
$ ros2 run learning_node node_helloworld
Hello World节点(面向对象)
#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ @作者: 古月居(www.guyuehome.com) @说明: ROS2节点示例-发布“Hello World”日志信息, 使用面向对象的实现方式 """ 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节点对象并进行初始化 rclpy.spin(node) # 循环等待ROS2退出 node.destroy_node() # 销毁节点对象 rclpy.shutdown() # 关闭ROS2 Python接口
entry_points={ 'console_scripts': [ 'node_helloworld = learning_node.node_helloworld:main', 'node_helloworld_class = learning_node.node_helloworld_class:main', ],
$ ros2 run learning_node node_helloworld_class
功能包(Package) :功能源码的聚集地
创建功能包的指令
ros2 pkg create --build-type <build-type> <package_name>
pkg
表示功能包相关的功能;
create
表示创建功能包;
build-type
表示新创建的功能包是C++还是Python的,如果使用C++或者C,那这里就跟ament_cmake,如果使用Python,就跟ament_python;
package_name
新建功能包的名字。
例子
$ 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
编译功能包
$ cd ~/dev_ws
$ colcon build # 编译工作空间所有功能包
$ source install/local_setup.bash
功能包结构
C++功能包
必然存在两个文件:package.xml和CMakerLists.txt
package.xml文件的主要内容如下,包含功能包的版权描述,和各种依赖的声明。
CMakeLists.txt文件是编译规则,C++代码需要编译才能运行,所以必须要在该文件中设置如何编译,使用CMake语法
Python功能包
存在package.xml和setup.py
package.xml文件的主要内容和C++版本功能包一样,包含功能包的版权描述,和各种依赖的声明。
setup.py文件里边也包含一些版权信息
动作(Action)︰完整行为的流程管理
特点
一对多通信
和服务一样,动作通信中的客户端可以有多个,大家都可以发送运动命令,但是服务器端只能有一个,毕竟只有一个机器人,先执行完成一个动作,才能执行下一个动作。
同步通信
动作过程中的数据通信接口,使用.action文件进行定义。
由服务和话题合成
接口定义
第一块是动作的目标,enable为true时,表示开始运动; 第二块是动作的执行结果,finish为true,表示动作执行完成; 第三块是动作的周期反馈,表示当前机器人旋转到的角度。
bool enable # 定义动作的目标,表示动作开始的指令 --- bool finish # 定义动作的结果,表示是否成功执行 --- int32 state # 定义动作的反馈,表示当前执行到的位置
常用命令行
$ ros2 action list # 查看服务列表
$ ros2 action info <action_name> # 查看服务数据类型
$ ros2 action send_goal <action_name> <action_type> <action_data> # 发送服务请求
通信接口(Interface) :数据传递的标准结构
给传递的数据定义一个标准的结构
话题通信接口的定义使用的是.msg文件,由于是单向传输,只需要描述传输的每一帧数据是什么就行,比如在这个定义里,会传输两个32位的整型数,x、y,我们可以用来传输二维坐标的数值。
服务通信接口的定义使用的是.srv文件,包含请求和应答两部分定义,通过中间的“---”区分,比如之前我们学习的加法求和功能,请求数据是两个64位整型数a和b,应答是求和的结果sum。
动作是另外一种通信机制,用来描述机器人的一个运动过程,使用.action文件定义,比如我们让小海龟转90度,一边转一边周期反馈当前的状态,此时接口的定义分成了三个部分,分别是动作的目标,比如是开始运动,运动的结果,最终旋转的90度是否完成,还有一个周期反馈,比如每隔1s反馈一下当前转到第10度、20度还是30度了,让我们知道运动的进度。
接口命令行操作
$ ros2 interface list # 查看系统接口列表
$ ros2 interface show <interface_name> # 查看某个接口的详细定义
$ ros2 interface package <package_name> # 查看某个功能包中的接口定义