I 基础概念与环境准备
定义:
机器人操作系统第二代
理解:
机器人是个有不同模块组成的整体,每个模块有不同的软件控制代码和硬件设备。并且可能很复杂,所以需要一个用户和硬件之间的中间者来屏蔽底层细节,这就是ROS
ROS调度这些进程,任务,模块,使他们成为一个整体
ROS2提供:
1.丰富生态的社区
2.统一的通讯支持 DDS
3.各种实用轮子工具
4.强大的应用功能
ROS2使用的操作系统:
跨平台支持linux,windows,macos
但最好还是linux
使用ubuntu系统最好
ROS的安装
一个长期ubuntu系统对应一个ROS2版本,当前我使用的是jazzy对应24.04 支持到2029年
II 工程创建与工作空间 (Workspace)
创建 ROS 工程全流程
1.初始化依赖工具 非必要
rosdep init 一台电脑就用一次,已经执行过
rosdep update 规则变化或者网络波动需要再更新一次
连不上用rosdepc
2.创建工作空间(工程)
在合适地点创建一个文件夹,这就是工作空间
接着在工作空间里创建src文件夹,切换到里面
3.src内部创建功能包
有cpp版本和python版本,一个功能包就是类似于java里面的包,是一个库,导入别人的就是第三方库,自己写的就是自己的模块
ros2 pkg create --build-type ament_python my_pkg
ros2 pkg create --build-type ament_cmake learning_pkg_c
4.在功能包内写代码
5.安装第三方依赖
(1)之前的功能包是第三方的
(2)自己的包里面有一些别人封装好的功能
cd ~/工程名
rosdep install --from-paths src --ignore-src -r -y
注意:需要先回到父级目录下
6.编译项目
仍在父级目录下
colcon build
编译成功后就会生成另外三个部分,build log install
用Python的时候,第一次编译时加上 —symlink-install 参数(注意最前面是两个-),修改源码保存后如果没有新增文件可以不用再编译
以后修改自己的节点、URDF 或 launch 文件后,执行:
python包
cd /home/asher/car_study
colcon build --symlink-install
source install/setup.bash
cpp包
cd /home/asher/car_cpp
colcon build --symlink-install
source install/setup.bash
7.加载自己的工作空间
source install/local_setup.sh
echo " source ~/工程名/install/local_setup.sh" >> ~/.bashrc # 所有终端均生效
8.运行节点
ros2 run my_pkg 节点名
工作空间目录架构
工程的所在地就是工作空间
一个工作空间由build log install src
功能包在src中
III 功能包机制 (Package)
CPP功能包
包含功能包的版权描述,和各种依赖的声明,功能是什么,谁创建了他 也就是一个各种杂项信息的结合
因为有依赖声明
rosdep就可以看这个自动解决依赖
告诉编译器如何组织cpp代码,如何编译等等
python功能包
一些基本配置
py需要把py可执行文件送到代码执行器的手里让他知道程序入口在哪,这个是setup文件里写的进入点入口
就是你在功能包里每写一个py文件都需要到这个setup里加上那个文件的运行入口,语法如下
前面是在外部启动这个节点程序要使用的名字 =这个程序入口,文件之间的层级关系用. 表示
'node_helloworld = learning_node.node_helloworld:main',
IV 节点架构 (Node) - 核心模块
其实就是一个程序或者说一个功能
节点是一个大功能的集合体,他负责管理内部的小功能,比如话题,服务等
这些小功能必须依存于节点,通过节点对象调用,存在
节点一般封装为类
为什么要有节点
完整的机器人系统可能在物理上不在一个整体
在机器人身体里搭载了一台计算机A,它可以通过机器人的眼睛——摄像头,获取外界环境的信息,也可以控制机器人的腿——轮子,让机器人移动到想要去的地方。除此之外,可能还会有另外一台计算机B,他不在机器人里面,而是放在你的桌子上,它可以远程监控机器人看到的信息,也可以远程配置机器人的速度和某些参数(仍然可以控制机器人,通过一些远程控制协议),还可以连接一个摇杆,人为控制机器人前后左右运动。
这些功能虽然位于不同的计算机中,但都是这款机器人的工作细胞,也就是节点,他们共同组成了一个完整的机器人系统。
节点也可以比喻是一个一个的工人,分别完成不同的任务,他们有的在一线厂房工作,有的在后勤部门提供保障,他们互相可能并不认识,但却一起推动机器人这座“工厂”,完成更为复杂的任务。
创建节点流程
所以总体而言,节点的实现方式依然是这四个步骤,只不过编码方式做了一些改变而已。
一般在main函数中
rclpy.init(args=args)
这个一般在自己定义的节点类中写
super().__init__(name)
在节点类中写
main函数写
node.destroy_node()
rclpy.shutdown() # 销毁节点对象
spin循环
spin循环是在ros2退出才结束,这样节点的代码就可以一直执行
注意:
每次在节点里写代码后都需要重新编译,编译时,他会把可执行文件放到install里,我们在外面调用run时,是去install里找可执行文件的
节点常用命令
ros2 node list 节点列表
ros2 node info 节点名
这个注意一般是在另一台终端上启动并且查看的
V 话题通信 (Topic) - 异步发布订阅
节点之间传递数据的桥梁
话题的相关概念
就是话题中数据的数据结构或者说格式
是话题通信采用的一种机制,发送数据的对象称为发布者,他把数据封装为某一种类型发送到某个话题上,相当于把货物装到了这个道路上送出去,作为接受者的节点只需要订阅这个话题,相当于把道路连到那个话题道路上,这样就能接受这个和道路上奔流的数据并且解析,成功实现两个节点之间的通信
订阅者和发布者可以不唯一,是可以称为一张通信网的(同一个节点可以成为A话题的发布者也可以B话题的接受者)
采用基于DDS的异步通信
异步代表没有单独的时钟线控制发送者和接受者同步通信,而是双方约定好发送数据的速率,对方按照这个速率去读数据才能正确解析
这种异步通信模式适合一些周期性发布的数据,比如一些传感器数据,不需要感很强的实时性的这种
但对于指令这种实时性要求很高的,并且要求明确解析的数据就不适合异步通信了
话题通信数据的描述格式称之为消息,对应编程语言中数据结构的概念。比如这里的一个图像数据,就会包含图像的长宽像素值、每个像素的RGB等等,在ROS中都有标准定义。
定义数据类型等等
消息是ROS中的一种接口定义方式,与编程语言无关,我们也可以通过.msg后缀的文件自行定义,有了这样的接口,各种节点就像积木块一样,通过各种各样的接口进行拼接,组成复杂的机器人系统。
创建话题—伴生发布者与接受者
1.创建发布者对象,用构造函数,参1:消息的类型 参2:话题的名字 参3:缓冲队列长度(如果有发送失败的会在这里暂存)
1. self.pub = self.create_publisher(String, "chatter", 10)
2.创建定时器对象,定时执行回调函数(参数2) 参1:定时器间隔时间
回调函数就是嵌入式中的定时器函数,定时器到一个周期标志位置为1就会调用回调函数执行一些动作
self.timer = self.create_timer(0.5, self.timer_callback)
3.填充回调函数
def timer_callback(self):
msg = String() 创建消息对象,消息对象在话题中流动
msg.data = 'Hello World' 填充消息的数据
self.pub.publish(msg) 把消息对象通过话题对象的发布方法发出去
self.get_logger().info('Publishing: "%s"' % msg.data) # 输出日志信息,提示已经完成话题发布
流程总结
对以上程序进行分析,如果我们想要实现一个发布者,流程如下:
接受者
创建接受者类(消息类型,话题名,回调函数,消息队列数)
类中进行初始化和属性的填写,功能的实现
填充回调函数
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) # 输出日志信息,提示订阅收到的话题消息
话题解耦合的操作,原始数据不要在发布者手里处理,而是发给接受者在底层处理
ros2库中的相机读取节点
ros2 run usb_cam usb_cam_node_exe
常用指令
$ ros2 topic list # 查看话题列表
$ ros2 topic info <topic_name> # 查看话题信息
$ ros2 topic hz <topic_name> # 查看话题发布频率
$ ros2 topic bw <topic_name> # 查看话题传输带宽
$ ros2 topic echo <topic_name> # 查看话题数据
$ ros2 topic pub <topic_name> <msg_type> <msg_data> # 发布话题消息
局限性
话题通信的特性是单向传输,适合周期性的数据传递,
VI 服务通信 (Service) - 同步请求响应
定义:
发送请求,立刻得到回答,高实时性
客户端/服务器模型
从服务的实现机制上来看,这种你问我答的形式叫做客户端/服务器模型,简称为CS模型,客户端在需要某些数据的时候,针对某个具体的服务,发送请求信息,服务器端收到请求之后,就会进行处理并反馈应答信息。
客户端不唯一,服务端唯一,为多个客户端提供服务
客户端可以通过接收到的应答信息,判断服务器端的状态,我们也称之为同步通信
数据的结构和类型需要在srv文件中定义
接口类型
就是数据的数据类型 可以是基本数据类型,也可以是对象
创建客户端过程
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])
main函数会把终端启动时后面的参数传入main函数,这里的sys.argv就是获取main函数的参数
self.future = self.client.call_async(self.request) # 异步方式发送服务请求
这里的异步方式就是发完之后不会等待回复
main函数
node = adderClient("service_adder_client")
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 # 创建ROS2节点对象并进行初始化
创建客服端请求
类似于订阅者
代码:
注意:服务端不需要调用显式回复函数,他接收到请求,做完服务会自动返回回复给客户端,客户端那边直接用response就行
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 a: %d b: %d' % (request.a, request.b)) # 输出日志信息,提示已经完成加法求和计算
return response # 反馈应答信息
服务相关命令行
ros2 service list # 查看服务列表
$ ros2 service type <service_name> # 查看服务数据类型
$ ros2 service call <service_name> <service_type> <service_data> # 发送服务请求
VII 数据接口 (Interface) - 通信标准结构
(数据的格式和类型)
定义:就是各种自定义的数据如何封装成数据类型
不用关心底层细节,只需要按照接口文档使用数据就行,类似的,键盘控制我们也可以安装一个ROS包,如何实现的呢?不用关心,反正它发布出来的肯定是线速度和角速度。
接口的组织结构
这个也是一个软件包
如果是服务里面创建name.srv文件
如果是话题创建msg文件
如果是行为创建action文件
里面的内容就是定义的数据的格式
用的时候from learning_interface.srv import AddTwoInts # 自定义的服务接口
导入这个格式并命名为AddTwoInts 这样这个就相当于自创了一个数据类型,类似string这种,可以传入一些需要数据类型的函数中
语言无关
为了保证每一个节点可以使用不同语言编程,ROS将这些接口的设计做成了和语言无关的,比如这里看到的int32表示32位的整型数,int64表示64位的整型数,bool表示布尔值,还可以定义数组、结构体,这些定义在编译过程中,会自动生成对应到C++、Python等语言里的数据结构。
使用他人包
使用他人的功能包时会用到一些接口,这些他们都会提供,你用的时候直接提取数据就行,比如cam包,直接获取image数据就能看图像
接口命令
接口命令的常用操作如下:
$ ros2 interface list # 查看系统接口列表
$ ros2 interface show <interface_name> # 查看某个接口的详细定义
$ ros2 interface package <package_name> # 查看某个功能包中的接口定义
VIII 动作机制 (Action) - 完整流程过程管理
定义:
也是客户端,服务端模型
目标,反馈,结果
底层是基于两个服务,一个话题进行的
这种通信机制的目的就是便于对机器人某一完整行为的流程进行管理。
就像装了一个进度条,我们可以随时把控进度
一对多通信
同步通信
ORIGINAL CITATION & REFERENCES
评论交流
0实践经验 · 技术讨论
登录读者账号后,可参与交流与回复。
登录后评论↗注册账号 ↗围绕本文内容分享经验或展开讨论。