Ros2 手册
功能包(Package)
工作空间->功能包->同名文件夹->py文件
常用命令
ros2 pkg create
功能:创建功能包,创建时候需要指定包名、编译方式、依赖项等。
命令格式:ros2 pkg create --build-type ament_python pkg_name rclpy std_msgs sensor_msgs
ros2 pkg list
功能:查看系统中功能包列表
命令格式:ros2 pkg list
ros2 pkg executeables
命令功能:查看包内可执行文件列表
命令格式: ros2 pkg executables pkg_name
节点运行 ros2 run
命令功能:运行功能包节点程序
命令格式:ros2 run pkg_name node_name
- pkg_name:功能包名字
- node_name:可执行程序的名字
节点(Node)
打印文本使用node.get_logger().info(“”),代替print
创建节点流程
- 编程接口初始化
- 定义class
- 填充内容
- 销毁节点
- 关闭接口
注意:需要在setup.py增加节点名字绑定节点程序(配置信息)
参考代码
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节点对象并进行初始化
node.destroy_node() # 销毁节点对象
rclpy.shutdown() # 关闭ROS2 Python接口
常用命令
ros2 node list
命令功能: 罗列出所有在当前域内节点名称
命令格式: ros2 node list
ros2 node info
命令功能: 查看节点详细信息,包括订阅、发布的消息,开启的服务和动作等
命令格式:ros2 node info node_name
- node_name:需要查看的节点名称
通信接口(interface)
与语言无关的代码,分隔符:—,文件名一定要大写
- 话题:.msg #通信数据
- 服务:.srv#请求数据、应答数据
- 动作:.action#目标、结果、反馈
创建接口功能包
- 创建动作通讯接口功能包
动作通讯需要先创建动作通讯接口,接下来创建动作通讯接口
cd ~/yahboomcar_ros2_ws/yahboomcar_ws/src
ros2 pkg create --build-type ament_cmake pkg_interfaces
- 接着在pkg_interfaces功能包下面创建一个action的文件夹,并在action文件夹内新建【Progress.action】文件,文件内容如下:
bool enable # 定义动作的目标,表示动作开始的指令
---
bool finish # 定义动作的结果,表示是否成功执行
---
int32 state # 定义动作的反馈,表示当前执行到的位置
- 在package.xml中需要添加一些依赖包,具体内容如下:
<buildtool_depend>rosidl_default_generators</buildtool_depend>
<depend>action_msgs</depend>
<member_of_group>rosidl_interface_packages</member_of_group>
- 在CMakeLists.txt 中添加如下配置:
find_package(rosidl_default_generators REQUIRED)
rosidl_generate_interfaces(${PROJECT_NAME}
"action/Progress.action"
)
- 编译功能包:
cd ~/yahboomcar_ros2_ws/yahboomcar_ws/src
colcon build --packages-select pkg_interfaces
- 编译完成之后,在工作空间下的 install 目录下将生成Progress.action文件对应的C++和Python文件,我们也可以在终端下进入工作空间,通过如下命令查看文件定义以及编译是否正常:
source install/setup.bash
ros2 interface show pkg_interfaces/action/Progress
正常情况下,终端将会输出与Progress.action文件一致的内容
动作接口参考格式
# 目标
# 文件名:Fibonacci.action
int32 order
---
# 结果
int32[] sequence
---
# 反馈
int32[] partial_sequence
常用命令
ros2 interface list
功能:列出所有可用的接口类型
使用格式:ros2 interface list
ros2 interface package
功能:输出一个包内可用接口类型的列表
使用格式:ros2 interface package package_name
- package_name:包含接口类型的包名称
ros2 interface packages
功能:输出提供接口的包列表
- 使用格式:ros2 interface packages
ros2 interface proto
功能:输出一个接口原型
使用格式:ros2 interface proto interface_name
- interface_name:接口的名称
ros2 interface show
功能:输出接口定义
使用格式:ros2 interface show interface_name
- interface_name:接口的名称
话题(topic)
数据文件接口:.msg格式
参考代码
发布者
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接口
订阅者
import rclpy # ROS2 Python接口库
from rclpy.node import Node # ROS2 节点类
from std_msgs.msg import String # ROS2标准定义的String消息
"""
创建一个订阅者节点
"""
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接口
常用命令
ros2 topic list
命令功能:罗列出当前域内的所有主题
命令格式:ros2 topic list
ros2 topic info
命令功能:显示主题消息类型,订阅者/发布者数量
命令格式:ros2 topic info topic_name
- topic_name:需要查询的话题的名字
ros2 topic type
命令功能:查看话题的消息类型
命令格式:ros2 topic type topic_name
- topic_name:需要查询话题类型的名字
ros2 topic hz
命令功能:显示主题平均发布频率
命令格式:ros2 topic hz topic_name
- topic_name:需要查询话题频率的名字
ros2 topic bw
命令功能:显示主题带宽
命令格式:ros2 topic bw topic_name
- topic_name:需要查询话题频率的名字
ros2 topic echo
命令功能:在终端打印主题消息,类似于一个订阅者
命令格式:ros2 topic echo topic_name
- topic_name:需要打印消息的话题的名字
###ros2 topic pub
命令功能:在终端发布指定话题消息
命令格式:ros2 topic pub topic_name message_type message_content - topic_name:需要发布话题消息的话题的名字 message_type:话题的数据类型 message_content:消息内容
默认是以1Hz的频率循环发布,可以设置以下参数,
- 参数-1只发布一次,
ros2 topic pub -1 topic_name message_type message_content
- 参数-t count循环发布count次结束,
ros2 topic pub -t count topic_name message_type message_content
参数-r count以count Hz的频率循环发布,ros2 topic pub -r count topic_name message_type message_content
ros2 topic pub turtle1/cmd_vel geometry_msgs/msg/Twist "{linear: {x: 0.5, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.2}}"
这里需要注意的是冒号后是有个空格。
服务(service)
数据文件接口:.srv格式
客户端/服务端:一对多
参考代码
服务端实现数字的相加
客户端实现发送两个数字,获取返回结果
服务端
reference: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接口
客户端
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接口
常用命令
ros2 service list
命令功能:列出当前节点可用的所有服务
命令格式:ros2 service list
ros2 service find
命令功能:查找指定服务名称的服务
命令格式:ros2 service find service_name
- service_name:要查找的服务名称
ros2 service type
命令功能:查看特定服务的接口类型
命令格式:ros2 service type service_name
- service_name:需要查询信息的服务名称
ros2 service call
命令功能:调用服务并查看其响应
命令格式:ros2 service call service_name service_type “{request_data}”
- service_name:需要调用的服务名称
- service_type:服务的类型
- request_data:请求数据
动作(action)
两个服务+一个话题
参考代码
服务端:收到命令转圈
客户端:发送命令
服务端
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): # 从0到360度,执行圆周运动,并周期反馈信息
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() # 创建结果消息
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):
super().__init__(name) # ROS2节点父类初始化
self._action_client = ActionClient( # 创建动作客户端(接口类型、动作名)
self, MoveCircle, 'move_circle')
def send_goal(self, enable): # 创建一个发送动作目标的函数
goal_msg = MoveCircle.Goal() # 创建一个动作目标的消息
goal_msg.enable = enable # 设置动作目标为使能,希望机器人开始运动
self._action_client.wait_for_server() # 等待动作的服务器端启动
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) # 设置一个服务器收到目标之后反馈时的回调函数
def goal_response_callback(self, future): # 创建一个服务器收到目标之后反馈时的回调函数
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) # 设置一个收到最终结果的回调函数
def get_result_callback(self, future): # 创建一个收到最终结果的回调函数
result = future.result().result # 读取动作执行的结果
self.get_logger().info('Result: {%d}' % result.finish) # 日志输出执行结果
def feedback_callback(self, feedback_msg): # 创建处理周期反馈消息的回调函数
feedback = feedback_msg.feedback # 读取反馈的数据
self.get_logger().info('Received feedback: {%d}' % feedback.state)
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接口
常用命令
ros2 action info
功能:打印有关动作的信息
使用格式:ros2 action info
ros2 action list
功能:输出动作名称列表
使用格式:ros2 action list
ros2 action send_goal
功能:发送一个动作目标
使用格式:ros2 action send_goal action_name action_type “{request_data}”
- action_name:需要调用的action名称
- action_type:action的类型
- request_data:请求数据
全局字典(parameter)
参考代码
创建、读取、修改参数
“”"
import rclpy # ROS2 Python接口库
from rclpy.node import Node # ROS2 节点类
class ParameterNode(Node):
def __init__(self, name):
super().__init__(name) # ROS2节点父类初始化
self.timer = self.create_timer(2, self.timer_callback) # 创建一个定时器(单位为秒的周期,定时执行的回调函数)
self.declare_parameter('robot_name', 'mbot') # 创建一个参数,并设置参数的默认值
def timer_callback(self): # 创建定时器周期执行的回调函数
robot_name_param = self.get_parameter('robot_name').get_parameter_value().string_value # 从ROS2系统中读取参数的值
self.get_logger().info('Hello %s!' % robot_name_param) # 输出日志信息,打印读取到的参数值
new_name_param = rclpy.parameter.Parameter('robot_name', # 重新将参数值设置为指定值
rclpy.Parameter.Type.STRING, 'mbot')
all_new_parameters = [new_name_param]
self.set_parameters(all_new_parameters) # 将重新创建的参数列表发送给ROS2系统
def main(args=None): # ROS2节点主入口main函数
rclpy.init(args=args) # ROS2 Python接口初始化
node = ParameterNode("param_declare") # 创建ROS2节点对象并进行初始化
rclpy.spin(node) # 循环等待ROS2退出
node.destroy_node() # 销毁节点对象
rclpy.shutdown() # 关闭ROS2 Python接口
常用命令
ros2 param delete
功能:删除参数
使用格式:ros2 param delete node_name parameter_names
- node_name Name of the ROS node
- parameter_names Names of the parameters
ros2 param describe
功能:显示有关已声明参数的描述信息
使用格式:ros2 param describe node_name parameter_names
- node_name Name of the ROS node
- parameter_names Names of the parameters
ros2 param dump
功能:将节点的参数转储到一个 YAML 文件中
使用格式:ros2 param dump node_name
- node_name Name of the ROS node
ros2 param get
功能:获取参数的值
使用格式:ros2 param get node_name parameter_names
- node_name Name of the ROS node
- parameter_names Names of the parameters
ros2 param list
功能:输出可用参数的列表
使用格式:ros2 param list
ros2 param load
功能:为节点加载参数文件
使用格式:ros2 param load node_name parameter_file
- node_name Name of the ROS node
- parameter_file File of the parameters
ros2 param set
功能:设置参数的值
使用格式:ros2 param set node_name parameter_names num
- node_name Name of the ROS node
- parameter_names Names of the parameters
- num number of the parameters
分布式通信
代码不需要进行更改。
实验环境
两台装有Ros2的设备,在同一网络环境下(ping通)
- 设备A:
ros2 run demo_nodes_py talker
(ros2自带例程) - 设备B:
ros2 run demo_nodes_py listener
(ros2自带例程)
多设备定义:Domain 机制
通信域定义分组。
- 在.bashrc文件中设置
export ROS_DOMAIN_ID = 31
。 source .bashrc
设置同一Domain ID。
数据分发服务DDS(Data Distribution Service)
质量服务策略QoS
DDS中的基本结构是Domain,Domain将各个应用程序绑定在一起进行通信,回忆下之前我们配置树莓派和电脑通信的时候,配置的那个DOMAIN ID,就是对全局数据空间的分组定义,只有处于同一个DOMAIN小组中的节点才能互相通信。这样可以避免无用数据占用的资源。
DDS中另外一个重要特性就是质量服务策略:QoS。
QoS是一种网络传输策略,应用程序指定所需要的网络传输质量行为,QoS服务实现这种行为要求,尽可能地满足客户对通信质量的需求,可以理解为数据提供者和接收者之间的合约。
策略如下:
- DEADLINE策略,表示通信数据必须要在每次截止时间内完成一次通信;
- HISTORY策略,表示针对历史数据的一个缓存大小;
- RELIABILITY策略,表示数据通信的模式,配置成BEST_EFFORT,就是尽力传输模式,网络情况不好的时候,也要保证数据流畅,此时可能会导致数据丢失,配置成RELIABLE,就是可信赖模式,可以在通信中尽量保证图像的完整性,我们可以根据应用功能场景选择合适的通信模式;
- DURABILITY策略,可以配置针对晚加入的节点,也保证有一定的历史数据发送过去,可以让新节点快速适应系统。
参考代码
发布者
import rclpy # ROS2 Python接口库
from rclpy.node import Node # ROS2 节点类
from std_msgs.msg import String # 字符串消息类型
from rclpy.qos import QoSProfile, QoSReliabilityPolicy, QoSHistoryPolicy # ROS2 QoS类
"""
创建一个发布者节点
"""
class PublisherNode(Node):
def __init__(self, name):
super().__init__(name) # ROS2节点父类初始化
qos_profile = QoSProfile( # 创建一个QoS原则
# reliability=QoSReliabilityPolicy.BEST_EFFORT,
reliability=QoSReliabilityPolicy.RELIABLE,
history=QoSHistoryPolicy.KEEP_LAST,
depth=1
)
self.pub = self.create_publisher(String, "chatter", qos_profile) # 创建发布者对象(消息类型、话题名、QoS原则)
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("qos_helloworld_pub") # 创建ROS2节点对象并进行初始化
rclpy.spin(node) # 循环等待ROS2退出
node.destroy_node() # 销毁节点对象
rclpy.shutdown() # 关闭ROS2 Python接口
订阅者
import rclpy # ROS2 Python接口库
from rclpy.node import Node # ROS2 节点类
from std_msgs.msg import String # ROS2标准定义的String消息
from rclpy.qos import QoSProfile, QoSReliabilityPolicy, QoSHistoryPolicy # ROS2 QoS类
"""
创建一个订阅者节点
"""
class SubscriberNode(Node):
def __init__(self, name):
super().__init__(name) # ROS2节点父类初始化
qos_profile = QoSProfile( # 创建一个QoS原则
# reliability=QoSReliabilityPolicy.BEST_EFFORT,
reliability=QoSReliabilityPolicy.RELIABLE,
history=QoSHistoryPolicy.KEEP_LAST,
depth=1
)
self.sub = self.create_subscription(\
String, "chatter", self.listener_callback, qos_profile) # 创建订阅者对象(消息类型、话题名、订阅者回调函数、QoS原则)
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("qos_helloworld_sub") # 创建ROS2节点对象并进行初始化
rclpy.spin(node) # 循环等待ROS2退出
node.destroy_node() # 销毁节点对象
rclpy.shutdown() # 关闭ROS2 Python接口
常用命令
查看QoS方式
ros2 topic info topic_name --verbose
Launch文件
- 实现多节点的配置和启动
- 设置rviz等软件
- 文件命名
.launch.py
结尾
参考代码
配置单个节点
from launch import LaunchDescription
from launch_ros.actions import Node
#参数可选,包含package和executable即可。
def generate_launch_description():
node = Node(
package='pkg_helloworld_py',
executable='helloworld',
name='my_node', # 设置节点的名称,重命名,更改节点名字,(以实现对于同一个节点同时启动多次)
namespace='my_ns', # 设置节点的命名空间
output='screen', # 设置节点输出的日志级别,如 'screen', 'log', 'both', 'none'
parameters=[{'param_name': 'param_value'}], # 设置节点的参数
remappings=[('/original_topic0', '/new_topic0'),
('/original_topic1', '/new_topic1')], # 设置节点的话题重映射,对话题名字重命名
arguments=['--arg1', 'value1'], # 设置节点的命令行参数
prefix='bash -c "source /opt/ros/foxy/setup.bash;"', # 设置节点启动前执行的命令
on_exit=Node.Shutdown.action, # 设置节点退出时的操作,如 Node.Shutdown.action、Node.RESTART、Node.RESTART_DELAYED
respawn=True, # 设置节点在退出后是否重启
)
return LaunchDescription([node])
设置节点parameters
在launch文件中,直接设置和导入参数文件,两种方式。
#直接设置
from launch import LaunchDescription # launch文件的描述类
from launch.actions import DeclareLaunchArgument # 声明launch文件内使用的Argument类
from launch.substitutions import LaunchConfiguration, TextSubstitution
from launch_ros.actions import Node # 节点启动的描述类
def generate_launch_description(): # 自动生成launch文件的函数
background_r_launch_arg = DeclareLaunchArgument(
'background_r', default_value=TextSubstitution(text='0') # 创建一个Launch文件内参数(arg)background_r
)
return LaunchDescription([ # 返回launch文件的描述信息
background_r_launch_arg, # 调用以上创建的参数(arg)
Node( # 配置一个节点的启动
package='turtlesim',
executable='turtlesim_node', # 节点所在的功能包
name='sim', # 对节点重新命名
parameters=[{ # ROS参数列表
'background_r': LaunchConfiguration('background_r'), # 创建参数background_r
}]
),
])
#导入文件
import os
from ament_index_python.packages import get_package_share_directory # 查询功能包路径的方法
from launch import LaunchDescription # launch文件的描述类
from launch_ros.actions import Node # 节点启动的描述类
def generate_launch_description(): # 自动生成launch文件的函数
config = os.path.join( # 找到参数文件的完整路径
get_package_share_directory('learning_launch'),
'config',
'turtlesim.yaml'
)
return LaunchDescription([ # 返回launch文件的描述信息
Node( # 配置一个节点的启动
package='turtlesim', # 节点所在的功能包
executable='turtlesim_node', # 节点的可执行文件名
namespace='turtlesim2', # 节点所在的命名空间
name='sim', # 对节点重新命名
parameters=[config] # 加载参数文件
)
])
加载其他launch文件
import os
from ament_index_python.packages import get_package_share_directory # 查询功能包路径的方法
from launch import LaunchDescription # launch文件的描述类
from launch.actions import IncludeLaunchDescription # 节点启动的描述类
from launch.launch_description_sources import PythonLaunchDescriptionSource
from launch.actions import GroupAction # launch文件中的执行动作
from launch_ros.actions import PushRosNamespace # ROS命名空间配置
def generate_launch_description(): # 自动生成launch文件的函数
parameter_yaml = IncludeLaunchDescription( # 包含指定路径下的另外一个launch文件
PythonLaunchDescriptionSource([os.path.join(
get_package_share_directory('learning_launch'), 'launch'),
'/parameters_nonamespace.launch.py'])
)
parameter_yaml_with_namespace = GroupAction( # 对指定launch文件中启动的功能加上命名空间,防止命名重叠
actions=[
PushRosNamespace('turtlesim2'),
parameter_yaml]
)
return LaunchDescription([ # 返回launch文件的描述信息
parameter_yaml_with_namespace
])
设置rviz2
import os
from ament_index_python.packages import get_package_share_directory # 查询功能包路径的方法
from launch import LaunchDescription # launch文件的描述类
from launch_ros.actions import Node # 节点启动的描述类
def generate_launch_description(): # 自动生成launch文件的函数
rviz_config = os.path.join( # 找到配置文件的完整路径
get_package_share_directory('learning_launch'),
'rviz',
'turtle_rviz.rviz'
)
return LaunchDescription([ # 返回launch文件的描述信息
Node( # 配置一个节点的启动
package='rviz2', # 节点所在的功能包
executable='rviz2', # 节点的可执行文件名
name='rviz2', # 对节点重新命名
arguments=['-d', rviz_config] # 加载命令行参数
)
])
配置setup.py
配置setup.py,将launch文件等其他文件的路径,加载进去
from setuptools import setup
import os
from glob import glob
package_name = 'learning_launch'
setup(
name=package_name, # 包的名称
version='0.0.0', # 版本号
packages=[package_name], # 包含的Python包
data_files=[
('share/ament_index/resource_index/packages', ['resource/' + package_name]), # 安装资源文件
('share/' + package_name, ['package.xml']), # 安装 package.xml 文件
(os.path.join('share', package_name, 'launch'), glob(os.path.join('launch', '*.launch.py'))), # 安装 launch 文件
(os.path.join('share', package_name, 'config'), glob(os.path.join('config', '*.*'))), # 安装 config 文件
(os.path.join('share', package_name, 'rviz'), glob(os.path.join('rviz', '*.*'))), # 安装 rviz 文件
],
install_requires=['setuptools'], # 安装所需的依赖
zip_safe=True, # 安装包是否为zip安全
maintainer='hcx', # 维护者名称
maintainer_email='huchunxu@guyuehome.com', # 维护者邮箱
description='TODO: Package description', # 包的描述
license='TODO: License declaration', # 许可证声明
tests_require=['pytest'], # 测试所需的依赖
entry_points={
'console_scripts': [ # 可执行的命令行脚本
],
},
)
#TF坐标系变换
- map:地图坐标系
- odom:里程计坐标系
- base_:其他坐标系
在rviz2中可以查看坐标系变换,增加TF,或者参数加载$ ros2 run rviz2 rviz2 -d $(ros2 pkg prefix --share turtle_tf2_py)/rviz/turtle_rviz.rviz
参考代码
发布静态变换
import rclpy # ROS2 Python接口库
from rclpy.node import Node # ROS2 节点类
from geometry_msgs.msg import TransformStamped # 坐标变换消息
import tf_transformations # TF坐标变换库
from tf2_ros import TransformBroadcaster # TF坐标变换广播器
from turtlesim.msg import Pose # turtlesim小海龟位置消息
class TurtleTFBroadcaster(Node):
def __init__(self, name):
super().__init__(name) # ROS2节点父类初始化
self.declare_parameter('turtlename', 'turtle') # 创建一个海龟名称的参
self.turtlename = self.get_parameter( # 优先使用外部设置的参数值,否则用默认值
'turtlename').get_parameter_value().string_value
self.tf_broadcaster = TransformBroadcaster(self) # 创建一个TF坐标变换的广播对象并初始化
self.subscription = self.create_subscription( # 创建一个订阅者,订阅海龟的位置消息
Pose,
f'/{self.turtlename}/pose', # 使用参数中获取到的海龟名称
self.turtle_pose_callback, 1)
def turtle_pose_callback(self, msg): # 创建一个处理海龟位置消息的回调函数,将位置消息转变成坐标变换
transform = TransformStamped() # 创建一个坐标变换的消息对象
transform.header.stamp = self.get_clock().now().to_msg() # 设置坐标变换消息的时间戳
transform.header.frame_id = 'world' # 设置一个坐标变换的源坐标系
transform.child_frame_id = self.turtlename # 设置一个坐标变换的目标坐标系
transform.transform.translation.x = msg.x # 设置坐标变换中的X、Y、Z向的平移
transform.transform.translation.y = msg.y
transform.transform.translation.z = 0.0
q = tf_transformations.quaternion_from_euler(0, 0, msg.theta) # 将欧拉角转换为四元数(roll, pitch, yaw)
transform.transform.rotation.x = q[0] # 设置坐标变换中的X、Y、Z向的旋转(四元数)
transform.transform.rotation.y = q[1]
transform.transform.rotation.z = q[2]
transform.transform.rotation.w = q[3]
# Send the transformation
self.tf_broadcaster.sendTransform(transform) # 广播坐标变换,海龟位置变化后,将及时更新坐标变换信息
def main(args=None):
rclpy.init(args=args) # ROS2 Python接口初始化
node = TurtleTFBroadcaster("turtle_tf_broadcaster") # 创建ROS2节点对象并进行初始化
rclpy.spin(node) # 循环等待ROS2退出
node.destroy_node() # 销毁节点对象
rclpy.shutdown() # 关闭ROS2 Python接口
查看静态变换
import rclpy # ROS2 Python接口库
from rclpy.node import Node # ROS2 节点类
import tf_transformations # TF坐标变换库
from tf2_ros import TransformException # TF左边变换的异常类
from tf2_ros.buffer import Buffer # 存储坐标变换信息的缓冲类
from tf2_ros.transform_listener import TransformListener # 监听坐标变换的监听器类
class TFListener(Node):
def __init__(self, name):
super().__init__(name) # ROS2节点父类初始化
self.declare_parameter('source_frame', 'world') # 创建一个源坐标系名的参数
self.source_frame = self.get_parameter( # 优先使用外部设置的参数值,否则用默认值
'source_frame').get_parameter_value().string_value
self.declare_parameter('target_frame', 'house') # 创建一个目标坐标系名的参数
self.target_frame = self.get_parameter( # 优先使用外部设置的参数值,否则用默认值
'target_frame').get_parameter_value().string_value
self.tf_buffer = Buffer() # 创建保存坐标变换信息的缓冲区
self.tf_listener = TransformListener(self.tf_buffer, self) # 创建坐标变换的监听器
self.timer = self.create_timer(1.0, self.on_timer) # 创建一个固定周期的定时器,处理坐标信息
def on_timer(self):
try:
now = rclpy.time.Time() # 获取ROS系统的当前时间
trans = self.tf_buffer.lookup_transform( # 监听当前时刻源坐标系到目标坐标系的坐标变换
self.target_frame,
self.source_frame,
now)
except TransformException as ex: # 如果坐标变换获取失败,进入异常报告
self.get_logger().info(
f'Could not transform {self.target_frame} to {self.source_frame}: {ex}')
return
pos = trans.transform.translation # 获取位置信息
quat = trans.transform.rotation # 获取姿态信息(四元数)
euler = tf_transformations.euler_from_quaternion([quat.x, quat.y, quat.z, quat.w])
self.get_logger().info('Get %s --> %s transform: [%f, %f, %f] [%f, %f, %f]'
% (self.source_frame, self.target_frame, pos.x, pos.y, pos.z, euler[0], euler[1], euler[2]))
def main(args=None):
rclpy.init(args=args) # ROS2 Python接口初始化
node = TFListener("tf_listener") # 创建ROS2节点对象并进行初始化
rclpy.spin(node) # 循环等待ROS2退出
node.destroy_node() # 销毁节点对象
rclpy.shutdown() # 关闭ROS2 Python接口
动态广播TF
import rclpy # ROS2 Python接口库
from rclpy.node import Node # ROS2 节点类
from geometry_msgs.msg import TransformStamped # 坐标变换消息
import tf_transformations # TF坐标变换库
from tf2_ros import TransformBroadcaster # TF坐标变换广播器
from turtlesim.msg import Pose # turtlesim小海龟位置消息
class TurtleTFBroadcaster(Node):
def __init__(self, name):
super().__init__(name) # ROS2节点父类初始化
self.declare_parameter('turtlename', 'turtle') # 创建一个海龟名称的参数
self.turtlename = self.get_parameter( # 优先使用外部设置的参数值,否则用默认值
'turtlename').get_parameter_value().string_value
self.tf_broadcaster = TransformBroadcaster(self) # 创建一个TF坐标变换的广播对象并初始化
self.subscription = self.create_subscription( # 创建一个订阅者,订阅海龟的位置消息
Pose,
f'/{self.turtlename}/pose', # 使用参数中获取到的海龟名称
self.turtle_pose_callback, 1)
def turtle_pose_callback(self, msg): # 创建一个处理海龟位置消息的回调函数,将位置消息转变成坐标变换
transform = TransformStamped() # 创建一个坐标变换的消息对象
transform.header.stamp = self.get_clock().now().to_msg() # 设置坐标变换消息的时间戳
transform.header.frame_id = 'world' # 设置一个坐标变换的源坐标系
transform.child_frame_id = self.turtlename # 设置一个坐标变换的目标坐标系
transform.transform.translation.x = msg.x # 设置坐标变换中的X、Y、Z向的平移
transform.transform.translation.y = msg.y
transform.transform.translation.z = 0.0
q = tf_transformations.quaternion_from_euler(0, 0, msg.theta) # 将欧拉角转换为四元数(roll, pitch, yaw)
transform.transform.rotation.x = q[0] # 设置坐标变换中的X、Y、Z向的旋转(四元数)
transform.transform.rotation.y = q[1]
transform.transform.rotation.z = q[2]
transform.transform.rotation.w = q[3]
# Send the transformation
self.tf_broadcaster.sendTransform(transform) # 广播坐标变换,海龟位置变化后,将及时更新坐标变换信息
def main(args=None):
rclpy.init(args=args) # ROS2 Python接口初始化
node = TurtleTFBroadcaster("turtle_tf_broadcaster") # 创建ROS2节点对象并进行初始化
rclpy.spin(node) # 循环等待ROS2退出
node.destroy_node() # 销毁节点对象
rclpy.shutdown() # 关闭ROS2 Python接口
动态监听TF
import math
import rclpy # ROS2 Python接口库
from rclpy.node import Node # ROS2 节点类
import tf_transformations # TF坐标变换库
from tf2_ros import TransformException # TF左边变换的异常类
from tf2_ros.buffer import Buffer # 存储坐标变换信息的缓冲类
from tf2_ros.transform_listener import TransformListener # 监听坐标变换的监听器类
from geometry_msgs.msg import Twist # ROS2 速度控制消息
from turtlesim.srv import Spawn # 海龟生成的服务接口
class TurtleFollowing(Node):
def __init__(self, name):
super().__init__(name) # ROS2节点父类初始化
self.declare_parameter('source_frame', 'turtle1') # 创建一个源坐标系名的参数
self.source_frame = self.get_parameter( # 优先使用外部设置的参数值,否则用默认值
'source_frame').get_parameter_value().string_value
self.tf_buffer = Buffer() # 创建保存坐标变换信息的缓冲区
self.tf_listener = TransformListener(self.tf_buffer, self) # 创建坐标变换的监听器
self.spawner = self.create_client(Spawn, 'spawn') # 创建一个请求产生海龟的客户端
self.turtle_spawning_service_ready = False # 是否已经请求海龟生成服务的标志位
self.turtle_spawned = False # 海龟是否产生成功的标志位
self.publisher = self.create_publisher(Twist, 'turtle2/cmd_vel', 1) # 创建跟随运动海龟的速度话题
self.timer = self.create_timer(1.0, self.on_timer) # 创建一个固定周期的定时器,控制跟随海龟的运动
def on_timer(self):
from_frame_rel = self.source_frame # 源坐标系
to_frame_rel = 'turtle2' # 目标坐标系
if self.turtle_spawning_service_ready: # 如果已经请求海龟生成服务
if self.turtle_spawned: # 如果跟随海龟已经生成
try:
now = rclpy.time.Time() # 获取ROS系统的当前时间
trans = self.tf_buffer.lookup_transform( # 监听当前时刻源坐标系到目标坐标系的坐标变换
to_frame_rel,
from_frame_rel,
now)
except TransformException as ex: # 如果坐标变换获取失败,进入异常报告
self.get_logger().info(
f'Could not transform {to_frame_rel} to {from_frame_rel}: {ex}')
return
msg = Twist() # 创建速度控制消息
scale_rotation_rate = 1.0 # 根据海龟角度,计算角速度
msg.angular.z = scale_rotation_rate * math.atan2(
trans.transform.translation.y,
trans.transform.translation.x)
scale_forward_speed = 0.5 # 根据海龟距离,计算线速度
msg.linear.x = scale_forward_speed * math.sqrt(
trans.transform.translation.x ** 2 +
trans.transform.translation.y ** 2)
self.publisher.publish(msg) # 发布速度指令,海龟跟随运动
else: # 如果跟随海龟没有生成
if self.result.done(): # 查看海龟是否生成
self.get_logger().info(
f'Successfully spawned {self.result.result().name}')
self.turtle_spawned = True
else: # 依然没有生成跟随海龟
self.get_logger().info('Spawn is not finished')
else: # 如果没有请求海龟生成服务
if self.spawner.service_is_ready(): # 如果海龟生成服务器已经准备就绪
request = Spawn.Request() # 创建一个请求的数据
request.name = 'turtle2' # 设置请求数据的内容,包括海龟名、xy位置、姿态
request.x = float(4)
request.y = float(2)
request.theta = float(0)
self.result = self.spawner.call_async(request) # 发送服务请求
self.turtle_spawning_service_ready = True # 设置标志位,表示已经发送请求
else:
self.get_logger().info('Service is not ready') # 海龟生成服务器还没准备就绪的提示
def main(args=None):
rclpy.init(args=args) # ROS2 Python接口初始化
node = TurtleFollowing("turtle_following") # 创建ROS2节点对象并进行初始化
rclpy.spin(node) # 循环等待ROS2退出
node.destroy_node() # 销毁节点对象
rclpy.shutdown() # 关闭ROS2 Python接口
常用命令
安装功能包:
$ sudo apt install ros-humble-turtle-tf2-py ros-humble-tf2-tools
$ sudo pip3 install transforms3d
ros2 run tf2_tools view_frames
查看当前坐标系关系
ros2 run tf2_ros tf2_echo frame_1_name frame_2_name
查看frame_1_name 和frame_2_name 的坐标系变换关系
机器人模型URDF
URDF(Unified Robot Description Format)是一种用于描述机器人模型的XML文件格式,常用于ROS(Robot Operating System)中。URDF文件定义了机器人的几何形状、连接关系、关节信息以及传感器等。URDF文件通常用于仿真、运动控制和视觉感知等应用中。
包括:link和joint
参考代码
link
- 外观状态
- 物理状态
joint
<?xml version="1.0"?>
<!-- URDF文件定义了机器人模型的结构、关节和连接关系,用于在ROS中进行仿真和控制 -->
<robot name="my_robot">
<!-- 定义机器人模型的名称为 my_robot -->
<!-- Link 1 -->
<link name="link1">
<!-- 定义第一个链接 link1,代表机器人的一个部件 -->
<!-- Visual representation of link1 -->
<visual>
<!-- 可视化表示 link1 的外观 -->
<geometry>
<cylinder radius="0.1" length="0.4"/>
<!-- 使用圆柱体作为 link1 的几何形状 -->
</geometry>
</visual>
<!-- Inertial properties of link1 -->
<inertial>
<!-- 惯性属性定义 link1 的质量、惯性矩阵以及质心位置 -->
<mass value="1.0"/>
<!-- 链接的质量为 1.0 单位 -->
<inertia ixx="0.1" iyy="0.1" izz="0.1"/>
<!-- 惯性矩阵的主对角线元素 -->
<origin xyz="0 0 0"/>
<!-- 质心位置 -->
</inertial>
<!-- Collision properties of link1 -->
<collision>
<!-- 定义 link1 的碰撞属性 -->
<geometry>
<cylinder radius="0.1" length="0.4"/>
<!-- 使用圆柱体作为碰撞几何形状 -->
</geometry>
</collision>
</link>
<!-- Joint 1 connecting base_link and link1 -->
<joint name="joint1" type="revolute">
<!-- 定义连接 base_link 和 link1 的关节,为旋转关节类型 -->
<parent link="base_link"/>
<!-- 关节的父链接为 base_link -->
<child link="link1"/>
<!-- 关节的子链接为 link1 -->
<!-- Joint origin and axis -->
<origin rpy="0 0 0" xyz="0 0 0.1"/>
<!-- 定义关节的位置和方向 -->
<axis xyz="0 0 1"/>
<!-- 定义关节的旋转轴 -->
<!-- Joint motion limits -->
<limit lower="-3.1416" upper="3.1416"/>
<!-- 定义关节的运动范围 -->
</joint>
</robot>
常用命令
查看URDF模型构成
$ urdf_to_graphviz mbot_base.urdf # 在模型文件夹下运行
XACRO模型文件
安装$ sudo apt install ros-humble-xacro
以下是一些常用的XACRO文件语法,大家了解下。
常量定义
xacro:property标签用来定义一些常量,比如这样定义一个PI的常量名为“M_PI”,值为“3.14159”,在调用的时候,通过$加大括号,里边就可以使用定义好的常量了。
针对原本移动机器人的URDF文件,我们就可以把底盘的质量、尺寸,轮子的质量、尺寸、安装位置,这些不会变化的数据,都通过常量定义,未来需要修改的时候也很方便,就不需要在模型文件中一行一行找了。
数学计算
- 如果需要做数学计算,同样是在“${}”中进行,比如某一个位置,我们可以通过这两个常量做运算得到,就加入了加法和除法运算。
- 在移动机器人的模型中,很多有相对关系的数据,我们尽量都改成公式计算,如果直接写结果的数值,未来修改的时候,可能根本想不起来这个数据是怎么来的。
- 所有数学运算都会转换成浮点数进行,以保证运算精度
宏定义
机器人的轮子我们也做成宏定义,定义方式是通过这个xacro:macro标签描述的,还可以像函数一样,设置里边会用到的一些参数,比如这里的A、B、C。
当需要使用这个宏的时候,就可以像这样,通过宏名字的标签,来调用,同时要记得把几个参数设置好。
比如在模型中,轮子的宏定义是这样的,包含了link描述和joint关节设置,link的名称和关节的位置,是通过输入的参数来区分的,在使用的时候,通过这两句调用,两个轮子就出现了。
宏定义是可以嵌套的,于是我们把机器人的底盘也做成了一个宏,然后使用另外一个模型文件,对底盘宏定义的文件做了一个包含,然后再调用。
Gazebo仿真平台
安装$ sudo apt install ros-humble-gazebo-*
启动ros2 launch gazebo_ros gazebo.launch.py
配置步骤
1. 完善物理参数
第一步是确保每一个link都有惯性参数和碰撞属性,因为Gazebo是物理仿真平台,必要的物理参数是一定需要的。
<link name="${prefix}_wheel_link">
<visual>
<origin xyz="0 0 0" rpy="${M_PI/2} 0 0" />
<geometry>
<cylinder radius="${wheel_radius}" length = "${wheel_length}"/>
</geometry>
<material name="gray" />
</visual>
<collision>
<origin xyz="0 0 0" rpy="${M_PI/2} 0 0" />
<geometry>
<cylinder radius="${wheel_radius}" length = "${wheel_length}"/>
</geometry>
</collision>
<xacro:cylinder_inertial_matrix m="${wheel_mass}" r="${wheel_radius}" h="${wheel_length}" />
</link>
2. 添加Gazebo标签(需要)
第二步是为link添加gazebo标签,主要是为了可以在gazebo中渲染每一个link的颜色,因为URDF中的颜色系统和gazebo中的不同,所以得做一步这样的冗余配置。
<gazebo reference="${prefix}_wheel_link">
<material>Gazebo/Gray</material>
<mu1>100000.0</mu1>
<mu2>100000.0</mu2>
</gazebo>
3. 配置传动装置(可选)
第三步是要给运动的joint配置传动装置,可以理解为仿真了一个电机。
<transmission name="${prefix}_wheel_joint_trans">
<type>transmission_interface/SimpleTransmission</type>
<joint name="${prefix}_wheel_joint" >
<hardwareInterface>hardware_interface/VelocityJointInterface</hardwareInterface>
</joint>
<actuator name="${prefix}_wheel_joint_motor">
<hardwareInterface>hardware_interface/VelocityJointInterface</hardwareInterface>
<mechanicalReduction>1</mechanicalReduction>
</actuator>
</transmission>
4. 添加控制器插件(可选)
第四步,要添加一个gazebo的控制器插件,小车是差速控制的,那就添加差速控制器插件,这样在不同角度下两个电机的速度分配,就可以交给控制器插件来完成了。
<!-- controller -->
<gazebo>
<plugin name="differential_drive_controller"
filename="libgazebo_ros_diff_drive.so">
<update_rate>30</update_rate>
<left_joint>left_wheel_joint</left_joint>
<right_joint>right_wheel_joint</right_joint>
<wheel_separation>${wheel_joint_y*2}</wheel_separation>
<wheel_diameter>${2*wheel_radius}</wheel_diameter>
<max_wheel_torque>20</max_wheel_torque>
<max_wheel_acceleration>1.0</max_wheel_acceleration>
<command_topic>cmd_vel</command_topic>
<publish_odom>true</publish_odom>
<publish_odom_tf>true</publish_odom_tf>
<publish_wheel_tf>true</publish_wheel_tf>
<odometry_topic>odom</odometry_topic>
<odometry_frame>odom</odometry_frame>
<robot_base_frame>base_footprint</robot_base_frame>
<odometry_source>1</odometry_source>
</plugin>
</gazebo>
常用命令
launch启动gazebo
import os
from ament_index_python.packages import get_package_share_directory
from launch import LaunchDescription
from launch.actions import IncludeLaunchDescription
from launch.launch_description_sources import PythonLaunchDescriptionSource
from launch_ros.actions import Node
def generate_launch_description():
# 包含我们自己的包提供的 robot_state_publisher 启动文件。强制启用仿真时间
# !!! 确保正确设置包名 !!!
package_name = 'learning_gazebo' # <--- 修改这里
world_file_path = 'worlds/neighborhood.world'
pkg_path = os.path.join(get_package_share_directory(package_name))
world_path = os.path.join(pkg_path, world_file_path)
# 机器人生成位置
spawn_x_val = '0.0'
spawn_y_val = '0.0'
spawn_z_val = '0.0'
spawn_yaw_val = '0.0'
# 包含 mbot 启动文件
mbot = IncludeLaunchDescription(
PythonLaunchDescriptionSource([
os.path.join(get_package_share_directory(package_name), 'launch', 'mbot.launch.py')
]), launch_arguments={'use_sim_time': 'true', 'world': world_path}.items()
)
# 包含由 gazebo_ros 包提供的 Gazebo 启动文件
gazebo = IncludeLaunchDescription(
PythonLaunchDescriptionSource([
os.path.join(get_package_share_directory('gazebo_ros'), 'launch', 'gazebo.launch.py')
])
)
# 运行来自 gazebo_ros 包的 spawner 节点。如果只有一个机器人,则实体名称并不重要。
spawn_entity = Node(
package='gazebo_ros',
executable='spawn_entity.py',
arguments=[
'-topic', 'robot_description',
'-entity', 'mbot',
'-x', spawn_x_val,
'-y', spawn_y_val,
'-z', spawn_z_val,
'-Y', spawn_yaw_val
],
output='screen'
)
# 启动所有组件!
return LaunchDescription([
mbot,
gazebo,
spawn_entity,
])
键盘控制节点(仿真)
ros2 run teleop_twist_keyboard teleop_twist_keyboard
Rviz2
启动ros2 run rviz2 rviz2
订阅话题,并渲染可视化出来
示例代码仿真器件包含:
- 摄像头
- 深度相机
- 雷达
由xacro文件和gazabo仿真,再到rviz2显示。
Gazebo:创造数据
Rviz:显示数据
Rqt
安装:sudo apt install ros-humble-rqt
运行:rqt
日志显示
图像显示
发布话题数据/调用服务请求
绘制数据曲线
数据包管理
节点可视化