Ros2手册(古月居教程)

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#目标、结果、反馈

创建接口功能包

  1. 创建动作通讯接口功能包
    动作通讯需要先创建动作通讯接口,接下来创建动作通讯接口
    cd ~/yahboomcar_ros2_ws/yahboomcar_ws/src
    ros2 pkg create --build-type ament_cmake pkg_interfaces
  2. 接着在pkg_interfaces功能包下面创建一个action的文件夹,并在action文件夹内新建【Progress.action】文件,文件内容如下:
bool enable     # 定义动作的目标,表示动作开始的指令
---
bool finish     # 定义动作的结果,表示是否成功执行
---
int32 state     # 定义动作的反馈,表示当前执行到的位置
  1. 在package.xml中需要添加一些依赖包,具体内容如下:
<buildtool_depend>rosidl_default_generators</buildtool_depend>
<depend>action_msgs</depend>
<member_of_group>rosidl_interface_packages</member_of_group>
  1. 在CMakeLists.txt 中添加如下配置:
find_package(rosidl_default_generators REQUIRED)

rosidl_generate_interfaces(${PROJECT_NAME}
  "action/Progress.action"
)
  1. 编译功能包:
    cd ~/yahboomcar_ros2_ws/yahboomcar_ws/src
    colcon build --packages-select pkg_interfaces
  2. 编译完成之后,在工作空间下的 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
在这里插入图片描述

日志显示

在这里插入图片描述

图像显示

​​​​​​​在这里插入图片描述

发布话题数据/调用服务请求

在这里插入图片描述

绘制数据曲线

在这里插入图片描述

数据包管理

在这里插入图片描述

节点可视化

​​​​​​​
在这里插入图片描述

1: command
2: guyuehome

如果您想在 Unity 中使用 ROS2,可以使用 ros2-for-unity 库。下面是一些步骤,帮助您开始使用 ros2-for-unity: 1. 安装 ROS2:请根据您的操作系统在 ROS2 官方网站上安装 ROS2。 2. 安装 Unity:请在 Unity 官方网站上下载并安装 Unity。 3. 安装 ros2-for-unity:请参考 ros2-for-unity 的安装指南。 4. 创建 ROS2 节点:请在 Unity 中创建一个新的 C# 脚本,并使用 ros2-for-unity 创建 ROS2 节点。您可以使用以下代码示例: ``` using UnityEngine; using System.Collections.Generic; using ROS2; public class MyNode : MonoBehaviour { Node node; Publisher<std_msgs.msg.String> publisher; void Start() { // Initialize ROS2 ROS2.Init(); // Create a ROS2 node node = ROS2.CreateNode("my_node"); // Create a ROS2 publisher publisher = node.CreatePublisher<std_msgs.msg.String>("my_topic"); } void Update() { // Publish a ROS2 message std_msgs.msg.String message = new std_msgs.msg.String(); message.data = "Hello, ROS2!"; publisher.Publish(message); } void OnDestroy() { // Shutdown ROS2 ROS2.Shutdown(); } } ``` 5. 运行 ROS2:在您的 ROS2 安装目录中打开新的终端,并运行以下命令: ``` $ ros2 run demo_nodes_cpp talker ``` 这将启动一个 ROS2 Talker 节点,它将发布一个字符串消息到 "my_topic" 话题中。 6. 运行 Unity:在 Unity 中打开您的项目,并运行您的场景。您应该能够在 Unity 控制台中看到 "Hello, ROS2!" 消息。 这只是一个简单的示例,您可以根据您的需求和项目来实现更复杂的功能。希望这能帮助您开始使用 ros2-for-unity。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值