前言
本系列将依托赵虚左老师的ROS课程,写下自己的一些心得与笔记。
课程链接:https://www.bilibili.com/video/BV1Ci4y1L7ZZ
讲义链接:http://www.autolabor.com.cn/book/ROSTutorials/index.html
文章可能存在疏漏的地方,恳请大家指出。
前面 URDF 文件构建机器人模型的过程中,存在若干问题。
问题1:设计易出错。
问题2:代码复用性问题。
…
如果在编程语言中,可以通过变量结合函数直接解决上述问题,在 ROS 中,已经给出了类似编程的优化方案,称之为:Xacro
概念
Xacro 是 XML Macros 的缩写,Xacro 是一种 XML 宏语言,是可编程的 XML。
原理
Xacro 可以声明变量,可以通过数学运算求解,使用流程控制控制执行顺序,还可以通过类似函数的实现,封装固定的逻辑,将逻辑中需要的可变的数据以参数的方式暴露出去,从而提高代码复用率以及程序的安全性。
作用
较之于纯粹的 URDF 实现,可以编写更安全、精简、易读性更强的机器人模型文件,且可以提高编写效率。
需求描述:
使用xacro优化上一节案例中驱动轮实现,需要使用变量封装底盘的半径、高度,使用数学公式动态计算底盘的关节点坐标,使用 Xacro 宏封装轮子重复的代码并调用宏创建两个轮子(注意: 在此,演示 Xacro 的基本使用,不必要生成合法的 URDF )。
编写 Xacro 文件,以变量的方式封装属性(常量半径、高度、车轮半径…),以函数的方式封装重复实现(车轮的添加)。
<robot name="mycar" xmlns:xacro="http://wiki.ros.org/xacro">
<xacro:property name="wheel_radius" value="0.0325" />
<xacro:property name="wheel_length" value="0.0015" />
<xacro:property name="PI" value="3.1415927" />
<xacro:property name="base_link_length" value="0.08" />
<xacro:property name="lidi_space" value="0.015" />
<xacro:macro name="wheel_func" params="wheel_name flag" >
<link name="${wheel_name}_wheel">
<visual>
<geometry>
<cylinder radius="${wheel_radius}" length="${wheel_length}" />
geometry>
<origin xyz="0 0 0" rpy="${PI / 2} 0 0" />
<material name="wheel_color">
<color rgba="0 0 0 0.3" />
material>
visual>
link>
<joint name="${wheel_name}2link" type="continuous">
<parent link="base_link" />
<child link="${wheel_name}_wheel" />
<origin xyz="0 ${0.1 * flag} ${(base_link_length / 2 + lidi_space - wheel_radius) * -1}" rpy="0 0 0" />
<axis xyz="0 1 0" />
joint>
xacro:macro>
<xacro:wheel_func wheel_name="left" flag="1" />
<xacro:wheel_func wheel_name="right" flag="-1" />
robot>
执行:rosrun xacro xacro xxx.xacro > xxx.urdf, 会将 xacro 文件解析为 urdf 文件,内容如下:
<robot name="mycar">
<link name="left_wheel">
<visual>
<geometry>
<cylinder length="0.0015" radius="0.0325"/>
geometry>
<origin rpy="1.57079635 0 0" xyz="0 0 0"/>
<material name="wheel_color">
<color rgba="0 0 0 0.3"/>
material>
visual>
link>
<joint name="left2link" type="continuous">
<parent link="base_link"/>
<child link="left_wheel"/>
<origin rpy="0 0 0" xyz="0 0.1 -0.0225"/>
<axis xyz="0 1 0"/>
joint>
<link name="right_wheel">
<visual>
<geometry>
<cylinder length="0.0015" radius="0.0325"/>
geometry>
<origin rpy="1.57079635 0 0" xyz="0 0 0"/>
<material name="wheel_color">
<color rgba="0 0 0 0.3"/>
material>
visual>
link>
<joint name="right2link" type="continuous">
<parent link="base_link"/>
<child link="right_wheel"/>
<origin rpy="0 0 0" xyz="0 -0.1 -0.0225"/>
<axis xyz="0 1 0"/>
joint>
robot>
xacro 提供了可编程接口,类似于计算机语言,包括变量声明调用、函数声明与调用等语法实现。在使用 xacro 生成 urdf 时,根标签robot中必须包含命名空间声明:xmlns:xacro="http://wiki.ros.org/xacro"
用于封装 URDF 中的一些字段,比如: PAI 值,小车的尺寸,轮子半径 …
格式
<xacro:property name="wheel_radius" value="0.0325" />
<xacro:property name="wheel_length" value="0.0015" />
<xacro:property name="PI" value="3.1415927" />
<xacro:property name="base_link_length" value="0.08" />
<xacro:property name="lidi_space" value="0.015" />
调用
<cylinder radius="${wheel_radius}" length="${wheel_length}" />
<origin xyz="0 0 0" rpy="${PI / 2} 0 0" />
<origin xyz="0 ${0.1 * flag} ${(base_link_length / 2 + lidi_space - wheel_radius) * -1}" rpy="0 0 0" />
类似于函数实现,提高代码复用率,优化代码结构,提高安全性
定义
<xacro:macro name="wheel_func" params="wheel_name flag" >
.....
.....
xacro:macro>
<xacro:macro name="wheel_func" params="wheel_name flag" >
<link name="${wheel_name}_wheel">
<visual>
<geometry>
<cylinder radius="${wheel_radius}" length="${wheel_length}" />
geometry>
<origin xyz="0 0 0" rpy="${PI / 2} 0 0" />
<material name="wheel_color">
<color rgba="0 0 0 0.3" />
material>
visual>
link>
<joint name="${wheel_name}2link" type="continuous">
<parent link="base_link" />
<child link="${wheel_name}_wheel" />
<origin xyz="0 ${0.1 * flag} ${(base_link_length / 2 + lidi_space - wheel_radius) * -1}" rpy="0 0 0" />
<axis xyz="0 1 0" />
joint>
xacro:macro>
调用
<xacro:宏名称 参数1=xxx 参数2=xxx/>
<xacro:wheel_func wheel_name="left" flag="1" />
<xacro:wheel_func wheel_name="right" flag="-1" />
机器人由多部件组成,不同部件可能封装为单独的 xacro 文件,最后再将不同的文件集成,组合为完整机器人,可以使用文件包含实现
文件包含
<robot name="xxx" xmlns:xacro="http://wiki.ros.org/xacro">
<xacro:include filename="my_base.xacro" />
<xacro:include filename="my_camera.xacro" />
<xacro:include filename="my_laser.xacro" />
....
robot>
<robot name="mycar" xmlns:xacro="http://wiki.ros.org/xacro">
<xacro:property name="footprint_radius" value="0.001"/>
<link name = "base_footprint">
<visual>
<geometry>
<sphere radius="${footprint_radius}"/>
geometry>
visual>
link>
<xacro:property name="base_length" value="0.08"/>
<xacro:property name="base_radius" value="0.1"/>
<xacro:property name="distance_ground" value="0.015"/>
<xacro:property name="base_joint_z" value="${distance_ground+base_length / 2}"/>
<link name = "base_link">
<visual>
<geometry>
<cylinder radius = "0.1" length="0.08"/>
geometry>
<origin xyz = "0 0 0" rpy="0 0 0"/>
<material name="robot_color">
<color rgba="0.2 0.2 0.2 1"/>
material>
visual>
link>
<joint name="baselink2basefootprint" type="fixed">
<parent link = "base_footprint"/>
<child link = "base_link" />
<origin xyz="0 0 ${base_joint_z}" rpy="0 0 0" />
<axis xyz="0 0 0" />
joint>
robot>
方式1:先将 xacro 文件转换出 urdf 文件,然后集成
先将 xacro 文件解析成 urdf 文件:rosrun xacro xacro xxx.xacro > xxx.urdf
然后再按照之前的集成方式直接整合 launch 文件
方式2:在 launch 文件中直接加载 xacro(建议使用)
<launch>
<param name="robot_description" command="$(find xacro)/xacro $(find urdf01_rviz)/urdf/xacro/demo06_base_footprint.urdf.xacro" />
<node pkg = "rviz" type = "rviz" name="rviz" args="-d $(find urdf01_rviz)/config/demo01.rviz"/>
<node pkg="joint_state_publisher" type="joint_state_publisher" name="joint_state_publisher" />
<node pkg="robot_state_publisher" type="robot_state_publisher" name="robot_state_publisher" />
<node pkg="joint_state_publisher_gui" type="joint_state_publisher_gui" name="joint_state_publisher_gui" />
launch>
完整效果
demo06_base_footprint.urdf.xacro
<robot name="mycar" xmlns:xacro="http://wiki.ros.org/xacro">
<xacro:property name="footprint_radius" value="0.001"/>
<material name="black">
<color rgba="0 0 0 1"/>
material>
<link name = "base_footprint">
<visual>
<geometry>
<sphere radius="${footprint_radius}"/>
geometry>
visual>
link>
<xacro:property name="base_length" value="0.08"/>
<xacro:property name="base_radius" value="0.1"/>
<xacro:property name="distance_ground" value="0.015"/>
<xacro:property name="base_joint_z" value="${distance_ground+base_length / 2}"/>
<link name = "base_link">
<visual>
<geometry>
<cylinder radius = "0.1" length="0.08"/>
geometry>
<origin xyz = "0 0 0" rpy="0 0 0"/>
<material name="robot_color">
<color rgba="0.2 0.2 0.2 1"/>
material>
visual>
link>
<joint name="baselink2basefootprint" type="fixed">
<parent link = "base_footprint"/>
<child link = "base_link" />
<origin xyz="0 0 ${base_joint_z}" rpy="0 0 0" />
<axis xyz="0 0 0" />
joint>
<xacro:property name="PI" value="3.1415927"/>
<xacro:property name="wheel_radius" value="0.0325"/>
<xacro:property name="wheel_length" value="0.015"/>
<xacro:property name="wheel_joint_z" value="${(base_length / 2 + distance_ground - wheel_radius) * -1}"/>
<xacro:macro name="wheel_func" params="wheel_name flag">
<link name = "${wheel_name}">
<visual>
<geometry>
<cylinder radius = "${wheel_radius}" length="${wheel_length}"/>
geometry>
<origin xyz = "0 0 0" rpy="${PI / 2} 0 0"/>
<material name="black"/>
visual>
link>
<joint name="${wheel_name}2baselink" type="continuous">
<parent link = "base_link"/>
<child link = "${wheel_name}" />
<origin xyz="0 ${flag*0.1} ${wheel_joint_z}" />
<axis xyz="0 1 0" />
joint>
xacro:macro>
<xacro:wheel_func wheel_name="left_wheel" flag = "1" />
<xacro:wheel_func wheel_name="right_wheel" flag = "-1" />
<xacro:property name="small_wheel_radius" value="0.0075"/>
<xacro:property name="small_joint_z" value="${(base_length / 2 + distance_ground - small_wheel_radius) * -1}"/>
<xacro:macro name="small_wheel_func" params="small_wheel_name flag">
<link name = "${small_wheel_name}">
<visual>
<geometry>
<sphere radius="${small_wheel_radius}" />
geometry>
<origin xyz = "0 0 0" rpy="0 0 0"/>
<material name="black"/>
visual>
link>
<joint name="${small_wheel_name}2baselink" type="continuous">
<parent link = "base_link"/>
<child link = "${small_wheel_name}" />
<origin xyz="${(base_radius - small_wheel_radius)*flag} 0 ${small_joint_z}" />
<axis xyz="1 1 1" />
joint>
xacro:macro>
<xacro:small_wheel_func small_wheel_name ="front_small_wheel" flag = "1" />
<xacro:small_wheel_func small_wheel_name ="back_small_wheel" flag = "-1" />
robot>
实现流程:
摄像头 xacro 文件:
<robot name="my_camera" xmlns:xacro="http://wiki.ros.org/xacro">
<xacro:property name="camera_length" value="0.01" />
<xacro:property name="camera_width" value="0.025" />
<xacro:property name="camera_height" value="0.025" />
<xacro:property name="joint_camera_x" value="0.08" />
<xacro:property name="joint_camera_y" value="0.0" />
<xacro:property name="joint_camera_z" value="${base_length / 2 + camera_height / 2}" />
<link name="camera">
<visual>
<geometry>
<box size="${camera_length} ${camera_width} ${camera_height}" />
geometry>
<origin xyz="0.0 0.0 0.0" rpy="0.0 0.0 0.0" />
<material name="black" />
visual>
link>
<joint name="camera2base_link" type="fixed">
<parent link="base_link" />
<child link="camera" />
<origin xyz="${joint_camera_x} ${joint_camera_y} ${joint_camera_z}" />
joint>
robot>
雷达 xacro 文件:
<robot name="my_laser" xmlns:xacro="http://wiki.ros.org/xacro">
<xacro:property name="support_length" value="0.15" />
<xacro:property name="support_radius" value="0.01" />
<xacro:property name="joint_support_x" value="0.0" />
<xacro:property name="joint_support_y" value="0.0" />
<xacro:property name="joint_support_z" value="${base_length / 2 + support_length / 2}" />
<link name="support">
<visual>
<geometry>
<cylinder radius="${support_radius}" length="${support_length}" />
geometry>
<origin xyz="0.0 0.0 0.0" rpy="0.0 0.0 0.0" />
<material name="red">
<color rgba="0.8 0.2 0.0 0.8" />
material>
visual>
link>
<joint name="support2base_link" type="fixed">
<parent link="base_link" />
<child link="support" />
<origin xyz="${joint_support_x} ${joint_support_y} ${joint_support_z}" />
joint>
<xacro:property name="laser_length" value="0.05" />
<xacro:property name="laser_radius" value="0.03" />
<xacro:property name="joint_laser_x" value="0.0" />
<xacro:property name="joint_laser_y" value="0.0" />
<xacro:property name="joint_laser_z" value="${support_length / 2 + laser_length / 2}" />
<link name="laser">
<visual>
<geometry>
<cylinder radius="${laser_radius}" length="${laser_length}" />
geometry>
<origin xyz="0.0 0.0 0.0" rpy="0.0 0.0 0.0" />
<material name="black" />
visual>
link>
<joint name="laser2support" type="fixed">
<parent link="support" />
<child link="laser" />
<origin xyz="${joint_laser_x} ${joint_laser_y} ${joint_laser_z}" />
joint>
robot>
组合底盘摄像头与雷达的 xacro 文件
<robot name="mycar" xmlns:xacro="http://wiki.ros.org/xacro">
<xacro:include filename="demo06_base_footprint.urdf.xacro" />
<xacro:include filename="demo07_car_camera.urdf.xacro" />
<xacro:include filename="demo08_car_lidar.urdf.xacro" />
robot>
launch 文件
<launch>
<param name="robot_description" command="$(find xacro)/xacro $(find urdf01_rviz)/urdf/xacro/car.urdf.xacro" />
<node pkg = "rviz" type = "rviz" name="rviz" args="-d $(find urdf01_rviz)/config/demo01.rviz"/>
<node pkg="joint_state_publisher" type="joint_state_publisher" name="joint_state_publisher" />
<node pkg="robot_state_publisher" type="robot_state_publisher" name="robot_state_publisher" />
<node pkg="joint_state_publisher_gui" type="joint_state_publisher_gui" name="joint_state_publisher_gui" />
launch>