前言
上一篇文章链接(构建导航地图并且使用navigation2进行导航-CSDN博客)
上一篇文件介绍了如何创建2D地图,然后编写launch文件加载参数配置启动navigation2、rviz2,实现了机器人能导航到地图中的目标点(初学可以从第一篇文章开始阅读--机器人建模,因为文章中提到的一些文件\代码是在前面几篇文章就已经编写好的,如果从这篇文章开始看的话可能不太容易理解。我的学习还在入门阶段,写这些文章主要是想要回顾总结,写的不好不对的地方欢迎指正)。这篇文章的主要内容是:使用节点初始化机器人的位姿;设置目标点参数机器人动态获取目标点并且依次导航到目标点;采集目标点图像数据并且保存;添加语音播报功能。最终实现的效果是启动仿真和自动巡检功能后,机器人获取参数文件中的目标点位并且依次巡检目标点,到达目标点后采集图像并此过程中会持续进行语音播报。
一、初始化机器人位姿
上一篇文章中,仿真启动导航需要提供一个机器人的初始位姿来进行初始,为了方便测试。我们可以通过编写程序来实现,启动仿真和导航后自动获取机器人当前的位置信息来初始化机器人的位置姿态。
先来创建一个功能包,在工作空间的src目录下输入命令
ros2 pkg create autopatrol_robot1 --build-type ament_python deoendencies rclpy nav2_simple_commander
创建了一个叫autopatrol_robot1的功能包,使用ament构建系统构建的是Python包·添加依赖项rclpy和nva2_simple_commander。
在功能包中的autopartol目录下,创建一个叫partol_node.py的节点文件
在文件中编写以下内容
import rclpy #导入ros2的python客户端库
from rclpy.node import Node #导入节点类
from geometry_msgs.msg import PoseStamped #导入标准位置信息
from nav2_simple_commander.robot_navigator import BasicNavigator #导入nav2下机器人导航的基层导航库
from tf_transformations import quaternion_from_euler #导入坐标变换工具从欧拉转换为四元数
#创建一个类继承节点类
class PatrolNode(BasicNavigator):
#初始化函数
def __init__(self,name):
super().__init__(name)
#声明参数
self.declare_parameter('init_point',[0.0, 0.0, 0.0]) #初始点参数
#目标点参数
#图像存放路径
#获取参数
self.init_point_=self.get_parameter('init_point').value
#订阅图像信息
#发布文字信息到合成语言的服务器(后面会在创建一个语音合成的节点)
def get_pose_by_xyyaw(self,x,y,yaw):
#返回PoseStamped对象
pose=PoseStamped() #定义一个标准位置
pose.header.frame_id='map' #map下的位置
pose.pose.position.x=x
pose.pose.position.y=y
quat=quaternion_from_euler(0.0,0.0,yaw) #欧拉角转换为四元数
#返回的顺序是xyzw
pose.pose.orientation.x=quat[0]
pose.pose.orientation.y=quat[1]
pose.pose.orientation.z=quat[2]
pose.pose.orientation.w=quat[3]
return pose
def init_pose(self):
#初始化位姿
self.init_point_=self.get_parameter('init_point').value #获取参数
init_pose=self.get_pose_by_xyyaw(self.init_point_[0],self.init_point_[1],self.init_point_[2]) #调用函数转换欧拉角为四元数
self.setInitialPose(init_pose) #调用函数初始化位置
self.waitUntilNav2Active()
#获取目标点集合的函数
#导航到目标点的函数
#获取最新的图像的函数
#保存图像的函数
#将需要语音播报的内容发布给语音合成服务器的函数
def main(args=None):
rclpy.init(args=args)
node=PatrolNode('patrol_node') #实例化节点类
node.init_pose() #调用函数初始化位置
rclpy.spin(node)
rclpy.shutdown()
代码添加了比较详细的注释这里不再一一解释,代码主要实现了一下内容:创建了一个类继承了BasicNavigator类,编写了构造函数使用父类的构造方法,编写了一个传入欧拉角获得四元数的函数以及初始化机器人位姿的函数,在主函数中实例化PatrolNode,并且调用初始化函数。实际编写代码的时需要先理清楚代码的框架结构、核心功能如何实现、分为哪些模块、各个功能之间的逻辑关系等,然后搭建好框架,在逐一实现各个模块和功能。我们这里功能比较简单,我就直接简单通过注释提前表明了一下还有哪些功能需实现。
编写好节点之后,需要在setup.py设置entry_points
'patrol_node=autopatrol_robot1.patrol_node:main',
这一步主要作用是创建命令行可执行入口
接下来编写launch文件启动节点,这里只有一个节点可以直接使用命令行启动,这里为了方便后面语音合成服务节点的启动这里直接使用launch文件来启动。
在功能包的根目录下创建一个launch文件夹,在文件夹下创建patrol.launch.py
在文件中编写
import launch
import launch_ros
def generate_launch_description():
action_patrol_node=launch_ros.actions.Node(
package='autopatrol_robot1',
executable='patrol_node',
)
return launch.LaunchDescription(
[
action_patrol_node,
]
)
这是ros2中launch文件最常见的用法之一启动节点
编写好launch文件之后在setup文件中添加
('share/'+package_name+'launch/',glob('launch/*.launch.py'))
这一步是将launch文件安装到install\share\package\launch目录下,这是ROS 2工具链默认的搜索路径,如果没有这一步那么在启动launch的时候就会报错找不到launch文件。
编写完成后保存所有文件,构建一下代码,接下来先启动仿真
ros2 launch robot_sim robot1_gazebo_sim.launch.py
在启动navigation2和rivz2
ros2 launch robot1_navigation2 navigation2.launch.py
在启动patrol_node初始化位置
ros2 launch autopatrol_robot1 patrol.launch.py
如果初始化位置成功代价地图成功加载那么第一步就完成了。
二、导航到目标点
在patrol_node.py中添加以下内容
在构造函数中添加代码,声明目标点参数,并且获取参数值
self.declare_parameter('target_points',[0.0, 0.0, 0.0, 1.0, 1.0, 1.57]) #声明目标点参数
self.target_points_=self.get_parameter('target_points').value #获取参数
编写获取目标点集合的函数和导航到目标点的函数
#获取目标点集合的函数
def get_target_points(self):
points=[] #声明数组存放目标点
self.target_points_=self.get_parameter('target_points').value #获取参数
for index in range(int(len(self.target_points_)/3)): #解析参数中的x\y\yaw
x=self.target_points_[index*3]
y=self.target_points_[index*3+1]
yaw=self.target_points_[index*3+2]
points.append([x,y,yaw]) #将目标点添加到容器
self.get_logger().info(f'获取到目标点{index}:{x},{y},{yaw}') #打印获取到的点位
return points
#导航到目标点的函数
def nav_to_pose(self,target_point):
self.goToPose(target_point) #导航到目标点
while not self.isTaskComplete(): #当导航进行中时持续打印feedback中的剩余距离
feedback=self.getFeedback()
self.get_logger().info(f'剩余导航距离:{feedback.distance_remaining}')
result=self.getResult()
self.get_logger().info(f'导航结果:{result}') #打印导航的结果
修改main函数
def main(args=None):
rclpy.init(args=args)
node=PatrolNode('patrol_node')
node.init_pose()
while rclpy.ok(): #当ROS2正常运行时
points=node.get_target_points() #获取目标点集合
for point in points: #for循环遍历所有点
x,y,yaw=point[0],point[1],point[2]
target_point=node.get_pose_by_xyyaw(x,y,yaw) #获得PoseStamped格式的目标点
node.nav_to_pose(target_point) #导航到目标点
rclpy.shutdown()
在mian函数中初始化节点前添加
rclpy.spin(node)
保存后构建代码,在终端中输入
ros2 run autopatrol_robot1 patrol_node
节点启动之后,打开新的终端输入
ros2 param list -t
可以看到 patrol_node 节点所需的参数
再在终端中输入
ros2 param dump /patrol_node
这样可以将patrol_node节点运行时的所有参数以yaml格式输出
在autopatrol_robot1功能包下创建config文件夹,创建一个叫patrol_param.yaml的文件,将终端中输出的yaml格式的参数复制到该文件下,并做修改
/patrol_node:
ros__parameters:
init_point: [0.0, 0.0, 0.0]
target_points: [0.0, 0.0, 0.0,
4.2, 1.2, 0.0,
5.0, -3.0, 0.0,
-2.0, 4.0, -1.57,
-2.0, -3.0, 1.57,
1.7, -6.0, 3.14]
use_sim_time: false
这里的target_points是我根据自己的地图做的设置(地图中一小格代表1),你们在设置的时候也需要根据自己的地图设置,确保机器人能到达目标点,不能在地图外或者在障碍物内。(这里注意导航的时候可能会出现机器人明明能通过却在“面壁思过”,我调了挺久没有调出来,最终只能选择换一个点)
编写完patrol_param.yaml还需要再setup文件下添加一句代码,将config文件像launch文件一样拷贝到share目录下
添加这一句之后,记得将刚刚的patrol_node的mian函数下的这一句删除掉
rclpy.spin(node)
删除之后,保存文件构建代码之后,就可以运行实践一下你的机器人是否按照你设置好的目标点进行巡航了。
先启动仿真的launch文件,在启动navigation2和rviz2的launch文件,最后启动patrol的launch文件。初始化位姿的时候放过代码了,这里还是一样就不放代码了。
这里补充一下,为什么我们要将欧拉角转换成四元数:
- 欧拉角有万向锁,在接近正负九十度时,会丢失自由度(也称为奇异性)
- 四元数没有万向锁这一弊端,并且相比欧拉角四元数的计算效率更高
-并且ROS原生支持四元数
三、采集图像保存
接下来我们实现机器人到达点位之后采集图像的功能,首先要订阅传感器的数据。然后使用一个变量存放最新的图像,到达点位后使用cv2作为桥梁存储图像文件,为了区分不同点位的图像我们还需要获取机器人实时的位置,将图片名称中加入机器人的坐标位置这样就可以判断出每一张图像是哪个点位采集的了。
在patrol_node中添加以下内容:
导入datetime获取时间信息,采集图显示记录采集的时间点
self.declare_parameter('img_path','')
self.img_path=self.get_parameter('img_path').value
定义一个参数方便设置图片存放的位置信息
self.sub_img_= self.create_subscription(Image,'/camera_sensor/image_raw',self.img_callback,1) #订阅图像信息
self.last_image_=None
self.buffer_=Buffer() #创建buffer
self.tf_listener_=TransformListener(self.buffer_,self)
创建一个订阅者对象,订阅相机的数据,创建一个回调函数,用于获取最新的图像信息。
创建一个对象用于存放最新图像信息。创建一个TF缓冲区,创建一个TF监听者订阅TF信息,并且更新缓冲区的数据。
#获取最新的图像信息
def img_callback(self,image):
self.last_image_=image #获取最新的图像信息
#保存图像
def save_image(self):
self.cv_bridge=CvBridge() #实例化CvBridge
cv_image=self.cv_bridge.imgmsg_to_cv2(self.last_image_) #将图像格式进行转换
formatted_time = datetime.now().strftime("%Y-%m-%d_%H-%M-%S") #获取当前时间并且转换时间的格式为年月日十分秒
pose=self.get_current_pose() #获取当前位置信息
cv2.imwrite( #保存图像并且拼接图像名字
f'{self.img_path}_img{pose.translation.x:.2f}_{pose.translation.y:.2f}_{formatted_time}.png',cv_image
)
#获取当前的位置信息
def get_current_pose(self):
while rclpy.ok:
try:
result=self.buffer_.lookup_transform('map','base_footprint',
rclpy.time.Time(seconds=0.0),rclpy.time.Duration(seconds=1.0)) #获取当map和base_footprint之间的关系
transform=result.transform #获取TF
self.get_logger().info(f'平移变换{transform.translation}')
self.get_logger().info(f'旋转变换{transform.rotation}')
rotation_euler=euler_from_quaternion([ #将四元数转换成欧拉
transform.rotation.x,
transform.rotation.y,
transform.rotation.z,
transform.rotation.w,]
)
self.get_logger().info(f'旋转RPY{rotation_euler}')
return transform #返回TF
except Exception as e:
self.get_logger().info(f'获取旋转矩阵失败的原因{str(e)}') #打印错误信息
main函数中调用函数采集图像信息
def main(args=None):
rclpy.init(args=args)
node=PatrolNode('patrol_node') #实例化节点类
node.init_pose() #初始化位姿
while rclpy.ok():
points=node.get_target_points() #每一次循环获取目标点参数
for point in points:
x,y,yaw=point[0],point[1],point[2]
target_point=node.get_pose_by_xyyaw(x,y,yaw) #转换目标点
node.nav_to_pose(target_point) #导航到目标点
node.save_image() #采集目标点的参数
rclpy.shutdown()
添加好这些代码之后,保存并且重新构建。启动gazebo仿真在启动,navigation2导航再启动patrol节点。运行后采集的图像文件会保存到工作空间下,如下图所示:
四、语音播报
接下来编写语音播报的功能,这里将语音播报做成一个服务节点,方便功能的模块化。在开始编写服务器节点之前需要先自定义接口。跳转到src文件夹下,创建一个功能包patrol_interfaces添加依赖项rosidl_default_generators。
ros2 pkg create patrol_interfaces --dependencies rosidl_default_generators
打开功能包,删除src和include文件夹,创建一个src文件夹,在src文件夹下创建一个SpeechText.srv文件,在文件中编写以下内容
string text
---
bool result
srv服务接口文件,使用三个横杆分割,string和bool表示消息类型,上面的表示请求(request),下面表示结果(result)。编写好接口文件之后需要去修改CMakeList文件,在文件中添加
rosidl_generate_interfaces(
${PROJECT_NAME} "srv/SpeechText.srv")
这是用于使用CMake根据.srv文件生成接口的指令。
还需要package.xml包描述文件中添加
<member_of_group>rosidl_interface_packages</member_of_group>
表明这是一个定义接口的功能包,编写好这些之后保存代码,并在工作空间下重新构建代码。构建完成后查看install/patrol_interfaces/include/patrol_interfaces/patrol_interfaces/srv/detail/dds_fastrtps$ 目录下如果构建无误,可以在该目录下生成以下文件:
接下来开始编写语音合成服务节点,在autopatrol_robot1/autopatrol_robot目录下创建speaker.py文件,在编写文件前需要先安装espeakng,它是一个开源的文字转语音合成引擎。在终端中输入
sudo apt install espeak-ng
安装完成后打开speaker.py文件编写以下内容
import rclpy
from patrol_interfaces.srv import SpeechText
from rclpy.node import Node
import espeakng
class SpeakNode(Node):
def __init__(self,name):
super().__init__(name)
self.peak_srv_=self.create_service(SpeechText,'speech_text',self.speechtext_callback) #创建一个服务器对象,订阅speech_text话题,收到客户端请求触发
self.speaker_=espeakng.Speaker() #实例化Speaker
self.speaker_.voice='zh' #设置语言为中文
def speechtext_callback(self,request,response):
self.get_logger().info(f'正在准备朗读{request.text}')
self.speaker_.say(request.text) #传入客户端请求进行朗读
self.speaker_.wait() #等待文字朗读完毕
response.result=True #语音合成成功标志置一
return response
def main(args=None):
rclpy.init(args=args)
node=SpeakNode('speaker') #实例化SpeakNode
rclpy.spin(node) #阻塞等待node运行
node.destroy_node()
rclpy.shutdown() #关闭服务
patrol_node.py文件添加以下内容,创建一个客户端对象,设置接口类,订阅speech_text服务
self.speaker_client_=self.create_client(SpeechText,'speech_text') #创建一个客户端对象,设置接口类,订阅speech_text服务
等待语音合成服务上线之后,定义标准消息对象,使用异步发送请求给服务节点,等待服务节点执行完成,判断执行结果并且打印。
#将需要语音播报的内容发布给语音合成服务器的函数
def speech_text(self,text):
while not self.speaker_client_.wait_for_service(timeout_sec=1.0): #如果服务器未上线那么将等待服务器上线
self.get_logger().info('等待语音合成服务上线中...')
request=SpeechText.Request() #创建请求信息
request.text=text #传入请求
future=self.speaker_client_.call_async(request) #异步发送请求
rclpy.spin_until_future_complete(self,future) #等待服务完成
if future.result() is not None: #判断result是否为空,为空则服务器响应失败
response=future.result().result
if response ==True: #判断是否成功并且打印结果信息
self.get_logger().info(f'合成{text}成功')
else:
self.get_logger().info(f'合成{text}失败')
else:
self.get_logger().info('语音合成服务响应失败')
在main函数中调用语音播报函数进行语音播报
def main(args=None):
rclpy.init(args=args)
node=PatrolNode('patrol_node') #实例化节点类
node.speech_text('正在初始化位置') #请求语音合成服务进行播报
node.init_pose()
node.speech_text('初始化位置完成') #初始化位姿
while rclpy.ok():
points=node.get_target_points() #每一次循环获取目标点参数
for point in points:
x,y,yaw=point[0],point[1],point[2]
target_point=node.get_pose_by_xyyaw(x,y,yaw) #转换目标点
node.speech_text(f'正在前往{x},{y},{yaw}')
node.nav_to_pose(target_point) #导航到目标点
node.speech_text(f'到达{x},{y},{yaw},正在采集图像')
node.save_image() #采集目标点的参数
node.speech_text('图像采集完成')
rclpy.shutdown()
编写好文件后还需要在setup.py中添加speaker的entry_point(可执行命令进入点)
'speaker=autopatrol_robot1.speaker:main',
保存好文件,重新构建后,启动gazebo仿真
ros2 launch robot_sim robot1_gazebo_sim.launch.py
再启动navigation2和rviz2
ros2 launch robot1_navigation2 navigation2.launch.py
最后启动patrol和speaker节点
ros2 launch autopatrol_robot1 patrol.launch.py
完成所有的代码编写之后,启动以上所有launch文件后你的机器人就可以一边自动巡航边进行语音播报,并且采集目标点的图像信息。
这里还是放上完整的代码作为参考
patrol_node.py
import rclpy #导入ros2的python客户端库
from rclpy.node import Node #导入节点类
from geometry_msgs.msg import PoseStamped #导入标准位置信息
from nav2_simple_commander.robot_navigator import BasicNavigator #导入nav2下机器人导航的基层导航库
from tf_transformations import quaternion_from_euler,euler_from_quaternion #导入坐标变换工具从欧拉转换为四元数
import rclpy.time
from tf2_ros import Buffer,TransformListener
from sensor_msgs.msg import Image
from cv_bridge import CvBridge
import cv2
from datetime import datetime
from patrol_interfaces.srv import SpeechText
#创建一个类继承节点类
class PatrolNode(BasicNavigator):
#初始化函数
def __init__(self,name):
super().__init__(name)
#声明参数
self.declare_parameter('init_point',[0.0, 0.0, 0.0]) #初始点参数
self.declare_parameter('target_points',[0.0, 0.0, 0.0, 1.0, 1.0, 1.57]) #目标点参数
self.declare_parameter('img_path','') #图像存放路径
#获取参数
self.init_point_=self.get_parameter('init_point').value
self.target_points_=self.get_parameter('target_points').value
self.img_path=self.get_parameter('img_path').value
self.sub_img_= self.create_subscription(Image,'/camera_sensor/image_raw',self.img_callback,1) #订阅图像信息
self.last_image_=None
self.buffer_=Buffer() #创建buffer
self.tf_listener_=TransformListener(self.buffer_,self) #订阅TF
self.speaker_client_=self.create_client(SpeechText,'speech_text') #创建一个客户端对象,设置接口类,订阅speech_text服务 #发布文字信息到合成语言的服务器(后面会在创建一个语音合成的节点)
#获取最新的图像信息
def img_callback(self,image):
self.last_image_=image #获取最新的图像信息
#保存图像
def save_image(self):
self.cv_bridge=CvBridge() #实例化CvBridge
cv_image=self.cv_bridge.imgmsg_to_cv2(self.last_image_) #将图像格式进行转换
formatted_time = datetime.now().strftime("%Y-%m-%d_%H-%M-%S") #获取当前时间并且转换时间的格式为年月日十分秒
pose=self.get_current_pose() #获取当前位置信息
cv2.imwrite( #保存图像并且拼接图像名字
f'{self.img_path}_img{pose.translation.x:.2f}_{pose.translation.y:.2f}_{formatted_time}.png',cv_image
)
#获取当前的位置信息
def get_current_pose(self):
while rclpy.ok:
try:
result=self.buffer_.lookup_transform('map','base_footprint',
rclpy.time.Time(seconds=0.0),rclpy.time.Duration(seconds=1.0)) #获取当map和base_footprint之间的关系
transform=result.transform #获取TF
self.get_logger().info(f'平移变换{transform.translation}')
self.get_logger().info(f'旋转变换{transform.rotation}')
rotation_euler=euler_from_quaternion([ #将四元数转换成欧拉
transform.rotation.x,
transform.rotation.y,
transform.rotation.z,
transform.rotation.w,]
)
self.get_logger().info(f'旋转RPY{rotation_euler}')
return transform #返回TF
except Exception as e:
self.get_logger().info(f'获取旋转矩阵失败的原因{str(e)}') #打印错误信息
def get_pose_by_xyyaw(self,x,y,yaw):
#返回PoseStamped对象
pose=PoseStamped() #定义标准位置
pose.header.frame_id='map' #map坐标系下的位姿
pose.pose.position.x=x
pose.pose.position.y=y
quat=quaternion_from_euler(0.0,0.0,yaw) #将欧拉角转换为四元数
#返回的顺序是xyzw
pose.pose.orientation.x=quat[0]
pose.pose.orientation.y=quat[1]
pose.pose.orientation.z=quat[2]
pose.pose.orientation.w=quat[3]
return pose #return PoseStamped
def init_pose(self):
#初始化位姿
self.init_point_=self.get_parameter('init_point').value #获取参数
init_pose=self.get_pose_by_xyyaw(self.init_point_[0],self.init_point_[1],self.init_point_[2]) #转化欧拉角为四元数
self.setInitialPose(init_pose) #调用函数初始化位姿
self.waitUntilNav2Active() #等待导航激活
#获取目标点集合的函数
def get_target_points(self):
points=[] #创建一个容器
self.target_points_=self.get_parameter('target_points').value #获取目标点参数
for index in range(int(len(self.target_points_)/3)): #将参数组合成三个一组的数组
x=self.target_points_[index*3]
y=self.target_points_[index*3+1]
yaw=self.target_points_[index*3+2]
points.append([x,y,yaw]) #将点位放到一个容器中
self.get_logger().info(f'获取到目标点{index}:{x},{y},{yaw}')
return points
#导航到目标点的函数
def nav_to_pose(self,target_point):
self.goToPose(target_point) #导航到目标点
while not self.isTaskComplete(): #导航过程中
feedback=self.getFeedback()
self.get_logger().info(f'剩余导航距离:{feedback.distance_remaining}') #持续反馈距离信息
result=self.getResult()
self.get_logger().info(f'导航结果:{result}') #打印导航结果
#将需要语音播报的内容发布给语音合成服务器的函数
def speech_text(self,text):
while not self.speaker_client_.wait_for_service(timeout_sec=1.0): #如果服务器未上线那么将等待服务器上线
self.get_logger().info('等待语音合成服务上线中...')
request=SpeechText.Request() #创建请求信息
request.text=text #传入请求
future=self.speaker_client_.call_async(request) #异步发送请求
rclpy.spin_until_future_complete(self,future) #等待服务完成
if future.result() is not None: #判断result是否为空,为空则服务器响应失败
response=future.result().result
if response ==True: #判断是否成功并且打印结果信息
self.get_logger().info(f'合成{text}成功')
else:
self.get_logger().info(f'合成{text}失败')
else:
self.get_logger().info('语音合成服务响应失败')
def main(args=None):
rclpy.init(args=args)
node=PatrolNode('patrol_node') #实例化节点类
node.speech_text('正在初始化位置') #请求语音合成服务进行播报
node.init_pose()
node.speech_text('初始化位置完成') #初始化位姿
while rclpy.ok():
points=node.get_target_points() #每一次循环获取目标点参数
for point in points:
x,y,yaw=point[0],point[1],point[2]
target_point=node.get_pose_by_xyyaw(x,y,yaw) #转换目标点
node.speech_text(f'正在前往{x},{y},{yaw}')
node.nav_to_pose(target_point) #导航到目标点
node.speech_text(f'到达{x},{y},{yaw},正在采集图像')
node.save_image() #采集目标点的参数
node.speech_text('图像采集完成')
rclpy.shutdown()
patrol.launch.py
import launch
import launch_ros
from ament_index_python.packages import get_package_share_directory
def generate_launch_description():
pkg_path=get_package_share_directory('autopatrol_robot1')
param_path=pkg_path+'/config/patrol_param.yaml'
action_patrol_node=launch_ros.actions.Node(
package='autopatrol_robot1',
executable='patrol_node',
output='screen',
parameters=[param_path],
)
action_speaker_node=launch_ros.actions.Node(
package='autopatrol_robot1',
executable='speaker',
output='screen',
)
return launch.LaunchDescription(
[
action_patrol_node,
action_speaker_node,
]
)
总结
当你从第一篇文章机器人建模开始学习到这篇文章的机器人自动巡检,并且完成了其中的所有内容,那么你已经使用ROS2实现了这些,从机器人建模,到传感器仿真,再到使用插件控制机器人移动,安装ros2_control将gazebo接入ros2_control,使用控制器控制机器人移动,使用slam_toolbox构建2D地图,使用navigation2导航到目标点,编写功能包使得机器人可以读取参数文件,自动对目标点进行巡检。完成这些想必你已经对ROS2框架的使用有了基本的了解,接下来你可以尝试自己进行机器人建模仿真导航等操作,进一步加深对ROS2的理解。
学习需要一点一滴的积累,加油!!!