ROS2学习笔记 |如何应用ROS2 (小白入门)
最近做的项目需要用到ROS,又开始捣鼓ROS啦。仅有单片机基础,让我看看在怎么个事儿。搞机器人就是要不断学习呐~
最近做的项目需要用到ROS,又开始捣鼓ROS啦。仅有单片机基础,让我看看在怎么个事儿。搞机器人就是要不断学习呐~
目录
一
准备
无论是使用C++还是Python编写ROS2程序,都需要依赖于工作空间,在此,我们先实现工作空间的创建与编译,打开终端,输入如下指令:
mkdir -p ws00_helloworld/src #创建工作空间以及子级目录 src,工作空间名称可以自定义
cd ws00_helloworld #进入工作空间
colcon build #编译
上述指令执行完毕,将创建ws00_helloworld目录,且该目录下包含build、install、log、src共四个子级目录。
ROS2常用命令
创建
进入到想要创建文件夹的空间下(cd 文件夹/),创建一个名为pkg03_hellovscode_py的文件夹,里面名为hellovscode的py文件
-
--node-name参数:在包创建时生成一个默认的节点文件和节点类,提供一个起点。
ros2 pkg create pkg03_hellovscode_py(文件夹名) --build-type ament_python --dependencies rclpy --node-name hellovscode(文件名)
不写--node-name 文件名就后面自己添加,如下:
单独创建py文件:
在vscode里右键新建文件 命名.py
添加路径
1.package.xml
在创建功能包时,所依赖的功能包已经自动配置了,配置内容如下:
<depend>rclcpp</depend>
...
2.setup.py
entry_points字段的console_scripts中添加如下内容:
entry_points={
'console_scripts': [
'文件名 = 文件夹.文件名:main',
'demo01_talker_str_py = py01_topic.demo01_talker_str_py:main',
],
},
编译
每次更改完代码后要编译一下哇
colcon build (全部文件都编译)
colcon build --packages-select 文件名 (选中文件编译)
. install/setup.bash
执行
"""
ros2 run 文件夹名 文件名
"""
ros2 run pkg02_helloworld_py helloworld
查找
ros2 pkg --h #获取pkg的帮助命令
ros2 pkg executables 包名 #输出所有功能包下的可执行程序
ros2 pkg list #列出所有功能包
ros2 pkg prefix 包名 #列出功能包路径
ros2 pkg xml #输出功能包的package.xml内容
创建节点模板(python):
#导包
import rclpy
from rclpy.node import Node
#自定义类
class Mynode(Node):
def __init__(self):
super().__init__("hello_node_py") #ROS节点名称
#获取当前节点的日志记录器,并调用info方法输出一条日志信息。
self.get_logger().info("hello world!(py继承)")
def main():
#初始化
rclpy.init()
#创建对象
node = Mynode()
#...
#资源释放
rclpy.shutdown()
pass
if __name__ == '__main__':
main()
二、通讯模块
我们知道直接在KEIL上编写程序输入单片机就可以实现对小车的控制,那么ROS2是怎么实现对小车控制的?各种传感器数据如何通过ROS传输交互?带着这些疑问学习:
功能包安装
二进制安装
sudo apt install ros-ROS2版本号-功能包名称
源码安装
git clone 仓库地址
自实现
自己写代码好吧
通讯机制术语
msg文件
msg文件是用于定义话题通信中数据载体的接口文件,一个典型的.msg文件示例如下。
int64 num1
int64 num2
srv文件
srv(service)文件是用于定义服务通信中数据载体的接口文件,一个典型的.srv文件示例如下。
int64 num1
int64 num2
---
int64 sum
文件中声明的数据被---分割为两部分,上半部分用于声明请求数据,下半部分用于声明响应数据。
action文件
action文件使用用于定义动作通信中数据载体的接口文件,一个典型的.action文件示例如下。
int64 num
---
int64 sum
---
float64 progress
文件中声明的数据被---分割为三部分,上半部分用于声明请求数据,中间部分用于声明响应数据,下半部分用于声明连续反馈数据。
.变量类型
不管是何种接口文件,在文件中每行声明的数据都由字段类型和字段名称组成,可以使用的字段类型有:
int8, int16, int32, int64 (或者无符号类型: uint*)
float32, float64
string
time, duration
其他msg文件
变长数组和定长数组
ROS中还有一种特殊类型:Header,标头包含时间戳和ROS2中常用的坐标帧信息。许多接口文件的第一行包含Header标头。
另外,需要说明的是:
参数通信的数据无需定义接口文件,参数通信时数据会被封装为参数对象,参数客户端和服务端操作的都是参数对象。
话题通信
发送文本消息(Python)
在src下再建一个py01_topic文件夹 加入std_msgs 等文件的依赖 ,再创建demo01_talker_str_py的py文件
ros2 pkg create py01_topic --build-type ament_python --dependencies rclpy std_msgs --node-name demo01_taker_str_py
package.xml中会有:
<depend>rclcpp</depend>
<depend>std_msgs</depend>
<depend>base_interfaces_demo</depend>
setup.py里有:
entry_points={
'console_scripts': [
'demo01_talker_str_py = py01_topic.demo01_talker_str_py:main',
],
},
"""
需求:以某个固定频率发送文本“hello world!”,文本后缀编号,每发送一条消息,编号递增1。
步骤:
1.导包;
2.初始化 ROS2 客户端;
3.定义节点类;
3-1.创建发布方;
3-2.创建定时器;
3-3.组织消息并发布。
4.调用spin函数,并传入节点对象;
5.释放资源。
"""
# 1.导包;
import rclpy
from rclpy.node import Node
#导入字符串类型
from std_msgs.msg import String
# 3.定义节点类;
class MinimalPublisher(Node):
def __init__(self):
super().__init__('minimal_publisher_py')
# 3-1.创建发布方;
self.publisher_ = self.create_publisher(String, 'topic', 10)
# 3-2.创建定时器;
timer_period = 0.5
self.timer = self.create_timer(timer_period, self.timer_callback)
self.i = 0
# 3-3.组织消息并发布。
def timer_callback(self):
msg = String()
msg.data = 'Hello World(py): %d' % self.i
self.publisher_.publish(msg)
self.get_logger().info('发布的消息: "%s"' % msg.data)
self.i += 1
def main(args=None):
# 2.初始化 ROS2 客户端;
rclpy.init(args=args)
# 4.调用spin函数,并传入节点对象;
minimal_publisher = MinimalPublisher()
rclpy.spin(minimal_publisher)
# 5.释放资源。
rclpy.shutdown()
if __name__ == '__main__':
main()
-
super().__init__('minimal_publisher_py'):调用父类的初始化方法,设置节点名称为"minimal_publisher_py" -
self.create_publisher()方法用于创建一个发布者(Publisher),它允许节点将消息发送到指定的主题。这个方法返回一个Publisher对象,你可以通过这个对象来发布消息。 -
self.create_timer() 方法用于创建一个定时器(Timer),它会在指定的时间间隔后触发一个回调函数。这个方法返回一个 Timer 对象,你可以通过这个对象来管理定时器的行为。
-
rclpy.spin(node)的主要作用是进入一个无限循环,等待并处理 ROS2 的事件。
订阅消息
放在一个topic下就可以收到消息啦:发送和订阅都在“chatter”下
# 1.导包;
import rclpy
from rclpy.node import Node
from std_msgs.msg import String
# 3.定义节点类;
class Listener(Node):
def __init__(self):
super().__init__('listener_node_py')
self.get_logger().info('订阅方创建了py')
# 3-1.创建订阅方;
"""
参数:
消息类型
话题名称
回调函数
Qos队列长度
"""
self.subscription = self.create_subscription(String,"chatter",self.do_callback,10)
self.subscription # 防止未使用的警告
def do_callback(self,msg):
# 3-2.处理订阅到的消息。
self.get_logger().info("订阅的数据:%s" % msg.data)
def main():
rclpy.init()
rclpy.spin(Listener())
rclpy.shutdown()
if __name__ == 'main':
main()
收数据直接调用msg.data打印即可,简单的订阅一般用不上订阅对象
自定义接口
创建话题通信自定义接口消息
0.创建一个c++文件夹(不用依赖),用于存放接口文件信息
在src下创建base_interfaces_demo文件夹
ros2 pkg create --build-type ament_cmake base_interfaces_demo
在工作文件夹下加入依赖
package.xml中会有:
<depend>rclcpp</depend>
<depend>std_msgs</depend>
<depend>base_interfaces_demo</depend>
1.功能包base_interfaces_demo文件夹下新建 msg 文件夹,msg文件夹下新建Student.msg文件(首字母大写),文件中输入如下内容:
string name
int32 age
float64 height
2.编辑配置文件
1.package.xml文件
在package.xml中需要添加一些依赖包,具体内容如下:
<!-- 编译依赖-->
<build_depend>rosidl_default_generators</build_depend>
<!-- 执行依赖-->
<exec_depend>rosidl_default_runtime</exec_depend>
<!-- 所属功能包-->
<member_of_group>rosidl_interface_packages</member_of_group>
2.CMakeLists.txt文件
为了将.msg文件转换成对应的C++和Python代码,还需要在CMakeLists.txt中添加如下配置:
find_package(rosidl_default_generators REQUIRED)
#为接口文件生成源代码
rosidl_generate_interfaces(${PROJECT_NAME}
"msg/Student.msg"
)
"msg/Student.msg"是自己建的包和文件,这里根据自己文件名更改
3.编译
终端中进入当前工作空间,编译功能包:
colcon build --packages-select base_interfaces_demo
如果报错,问一下deepseek,之前遇到了conda与python冲突问题,解决:在VScode里输入命令
# 临时禁用 Conda 环境
conda deactivate
# 确认使用的是系统 Python
which python3 # 应显示 /usr/bin/python3
# 重新编译
rm -rf build/ install/ log/
colcon build --packages-select base_interfaces_demo
4.测试
编译完成之后,在工作空间下的install目录下将生成Student.msg文件对应的C++和Python文件,我们也可以在终端下进入工作空间,通过如下命令查看文件定义以及编译是否正常:
. install/setup.bash
ros2 interface show base_interfaces_demo/msg/Student
Import自定义接口
from base_interfaces_demo.msg import Student
如果没有Student()提示的话, 配置VSCode中settings.json文件,在文件中的python.autoComplete.extraPaths和python.analysis.extraPaths属性下添加j绝对路径
ctrl+shift+P打开,输入settings.josn

绝对路径是 在install/local下打开的那个base_interfaces_demo,获取:
右键在集成终端打开
输入pwd,复制到dist-packages就可以


"python.autoComplete.extraPaths":[
"/home/jo/ros2_pluming/install/base_interfaces_demo/local/lib/python3.10/dist-packages"
],
"python.analysis.extraPaths":[
"/home/jo/ros2_pluming/install/base_interfaces_demo/local/lib/python3.10/dist-packages"
]

服务通信
服务通信也是ROS中一种极其常用的通信模式,服务通信是基于请求响应模式的,是一种应答机制。也即:一个节点A向另一个节点B发送请求,B接收处理请求并产生响应结果返回给A。
服务通信接口消息
1.创建并编辑 .srv 文件
功能包base_interfaces_demo下新建srv文件夹,srv文件夹下新建AddInts.srv文件,文件中输入如下内容:
int32 num1
int32 num2
---
int32 sum
2.编辑配置文件
1.package.xml 文件
srv文件与msg文件的包依赖一致,如果你是新建的功能包添加srv文件,那么直接参考定义msg文件时package.xml 配置即可。由于我们使用的是base_interfaces_demo该包已经为msg文件配置过了依赖包,所以package.xml不需要做修改。
2.CMakeLists.txt 文件
如果是新建的功能包,与之前定义msg文件同理,为了将.srv文件转换成对应的C++和Python代码,还需要在CMakeLists.txt中添加如下配置:
find_package(rosidl_default_generators REQUIRED)
rosidl_generate_interfaces(${PROJECT_NAME}
"srv/AddInts.srv"
)
不过,我们当前使用的base_interfaces_demo包,那么你只需要修改rosidl_generate_interfaces函数即可,修改后的内容如下:
rosidl_generate_interfaces(${PROJECT_NAME}
"msg/Student.msg"
"srv/AddInts.srv"
)
3.编译
终端中进入当前工作空间,编译功能包:
colcon build --packages-select base_interfaces_demo
4.测试
编译完成之后,在工作空间下的 install 目录下将生成AddInts.srv文件对应的C++和Python文件,我们也可以在终端下进入工作空间,通过如下命令查看文件定义以及编译是否正常:
. install/setup.bash
ros2 interface show base_interfaces_demo/srv/AddInts
正常情况下,终端将会输出与AddInts.srv文件一致的内容。
1.服务端实现
"""
需求:编写服务端,接收客户端发送请求,提取其中两个整型数据,相加后将结果响应回客户端。
步骤:
1.导包;
2.初始化 ROS2 客户端;
3.定义节点类;
3-1.创建服务端;
3-2.处理请求数据并响应结果。
4.调用spin函数,并传入节点对象;
5.释放资源。
"""
# 1.导包;
import rclpy
from rclpy.node import Node
from base_interfaces_demo.srv import AddInts
# 3.定义节点类;
class Service(Node):
def __init__(self):
super().__init__('minimal_service_py')
# 3-1.创建服务端;
self.srv = self.create_service(AddInts, 'add_ints', self.add_two_ints_callback)
self.get_logger().info("服务端启动!")
# 3-2.处理请求数据并响应结果。
def add_two_ints_callback(self, request, response):
response.sum = request.num1 + request.num2
self.get_logger().info('请求数据:(%d,%d),响应结果:%d' % (request.num1, request.num2, response.sum))
return response
def main():
# 2.初始化 ROS2 客户端;
rclpy.init()
# 4.调用spin函数,并传入节点对象;
rclpy.spin(Service())
# 5.释放资源。
rclpy.shutdown()
if __name__ == '__main__':
main()
# AddInts.srv
int64 num1 # 第一个数字
int64 num2 # 第二个数字
---
int64 sum # 返回的和
其中---作用就是划分Request与Response, 自动生成python类,可以直接.sum或者.num
class AddInts_Request: # 请求类
def __init__(self):
self.num1 = 0 # int64
self.num2 = 0 # int64
class AddInts_Response: # 响应类
def __init__(self):
self.sum = 0 # int64

注意!
self.create_service(AddInts, 'add_ints', self.add_two_ints_callback)中 add_two_ints_callback回调函数必须严格接受 2 个参数(request 和 response),不能多、不能少,也不能改变顺序。这是由 ROS2 底层机制强制规定的。命名可以自定义,但是其含义是内定的。
ROS2 服务端回调函数的返回值必须是 response 对象,这是 ROS2 的强制要求,不能返回其他类型或 None

客户端解析
"""
需求:编写客户端,发送两个整型变量作为请求数据,并处理响应结果。
步骤:
1.导包;
2.初始化 ROS2 客户端;
3.定义节点类;
3-1.创建客户端;
3-2.等待服务连接;
3-3.组织请求数据并发送;
4.创建对象调用其功能,处理响应结果;
5.释放资源。
"""
# 1.导包;
import sys
import rclpy
from rclpy.logging import get_logger
from rclpy.node import Node
from base_interfaces_demo.srv import AddInts
# 3.定义节点类;
class Client(Node):
def __init__(self):
super().__init__('minimal_client_py')
# 3-1.创建客户端;
self.cli = self.create_client(AddInts, 'add_ints')
# 3-2.等待服务连接;
while not self.cli.wait_for_service(timeout_sec=1.0):
self.get_logger().info('服务连接中,请稍候...')
self.req = AddInts.Request()
# 3-3.组织请求数据并发送;
def send_request(self):
self.req.num1 = int(sys.argv[1])
self.req.num2 = int(sys.argv[2])
self.future = self.cli.call_async(self.req)
def main():
#校验工作
if len(sys.argv)!= 3:
get_logger("rclpy").info("请提交2个整形参数")
# 2.初始化 ROS2 客户端;
rclpy.init()
# 4.创建对象并调用其功能;
minimal_client = Client()
minimal_client.send_request()
# 处理响应
rclpy.spin_until_future_complete(minimal_client,minimal_client.future)
try:
response = minimal_client.future.result()
except Exception as e:
minimal_client.get_logger().info(
'服务请求失败: %r' % (e,))
else:
minimal_client.get_logger().info(
'响应结果: %d + %d = %d' %
(minimal_client.req.num1, minimal_client.req.num2, response.sum))
# 5.释放资源。
rclpy.shutdown()
更多推荐




所有评论(0)