0. 简介

本文档用于记录现ROS1与ROS2之间的区别,以及如何向ROS2移植。整体架构基于该文章

ROS2的教程参见:​​https://docs.ros.org/en/ros2_packages/rolling/api/​


1 工程构建

1.1 CMakeList的编写

ROS2采用ament cmake系统,最主要的区别是原先的catkin Cmake宏被取代

find_package(catkin REQUIRED COMPONENTS 
...)

catkin_package(
INCLUDE_DIRS
LIBRARIES
CATKIN_DEPENDS)

add_library(controller src/controller.cpp)
add_dependencies(obot_controller obot_msgs_generate_messages_cpp)

install(TARGETS
obot_controller
obot_controller_node
LIBRARY DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION}
RUNTIME DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
)

############################################ 改变为

find_package(ament_cmake REQUIRED)
find_package(... REQUIRED)

ament_export_include_directories(include)# 标记导出的include目录对应的目录(这是通过目标install调用中的INCLUDES DESTINATION来实现)。
ament_export_libraries(my_library)# 标记安装的库的位置(这是由ament_export_targets调用中的HAS_LIBRARY_TARGET参数来完成)
ament_export_dependencies(some_dependency)
ament_target_dependencies(${PROJECT_NAME} ${DEPS})

ament_package()

install(TARGETS ${PROJECT_NAME}_node
ARCHIVE DESTINATION lib
LIBRARY DESTINATION lib
RUNTIME DESTINATION bin)

若想构建一个兼容ROS1/2的CMakeList.txt,在不同ROS版本有区别的部分通过环境变量触发条件编译

if($ENV{ROS_VERSION} EQUAL 2)
########################
## BULD VERSION: ROS2 ##
########################
else()
########################
## BULD VERSION: ROS1 ##
########################
endif()

1.2 package.xml

<buildtool_depend>catkin</buildtool_depend>
<!--对应的包-->
<build_depend>message_generation</build_depend>
<exec_depend>message_runtime</exec_depend>
<!--##################-->
<buildtool_depend>ament_cmake</buildtool_depend>
<build_type>ament_cmake</build_type>
<!--对应的包-->
<build_export_depend>rclpy</build_export_depend>
<exec_depend>rclpy</exec_depend>

若想构建一个兼容ROS1/2的package.xml,在不同ROS版本有区别的部分通过环境变量设置条件

<export>
<build_type condition="$ROS_VERSION == 1">catkin</build_type>
<build_type condition="$ROS_VERSION == 2">ament_cmake</build_type>
</export>

1.3 launch 文件

ROS2较ROS1,在launch方面进行了比较大的改动。原先ROS1是使用xml格式来编写launch文件,而ROS2却是用python来编写launch文件.

# 导入库
from launch import LaunchDescription
from launch_ros.actions import Node
from ament_index_python.packages import get_package_share_directory
import os

# 定义函数名称为:generate_launch_description
def generate_launch_description():
ns = os.environ.get('ROBOT_ID')##read namespace from env
return LaunchDescription([
Node(
# 创建Node对象fusion_localizer_node,标明所在位置fusion_localizer,将可执行文件重命名为fusion_localizer_nodes,同时可以将运行节点node放在同一个自定义的命名空间ns当中。
package='fusion_localizer',
namespace=ns,
executable='fusion_localizer_node',
name='fusion_localizer_nodes',
parameters,
),
])

##少量parameters也可以用以下方法
parameters=[ {'use_sim_time': True},
{'lane_filename': "tianjin_special_lanes.yaml"},
{'qc_info_topic': "/b_info"},
{'service_lane_in_buffer': 3.0}]

ROS2与ROS1比较及移植指南_ros2


或者通过以下办法来实现多个节点的调用

def generate_launch_description():
# 创建Actions.Node对象li_node,标明李四所在位置
li4_node = Node(
package="village_li",
executable="li4_node"
)
# 创建Actions.Node对象wang2_node,标明王二所在位置
wang2_node = Node(
package="village_wang",
executable="wang2_node"
)
# 创建LaunchDescription对象launch_description,用于描述launch文件
launch_description = LaunchDescription([li4_node,wang2_node])
# 返回让ROS2根据launch描述执行节点
return launch_description

对于ROS1而言,launch文件的编写则比较简单

<launch> 
<node pkg="beginner1" name="tempspeak" type="speak" output="screen">
<remap from="speaker" to="listener"/>
</node>
<node pkg="beginner1" name="templisten" type="listen" output="screen">
<remap from="listener" to="listener"/>
</node>

<group ns="demo_1">
<node name="demo_1" pkg="demo_1" type="demo_pub_1" output="screen"/>
<node name="demo_1" pkg="demo_1" type="demo_sub_1" output="screen"/>
</group>
<group ns="demo_2">
<node name="demo_2" pkg="demo_2" type="demo_pub_2" output="screen"/>
<node name="demo_2" pkg="demo_2" type="demo_sub_2" output="screen"/>
</group>

<rosparam file="***.yaml" command="load" ns="XXX">
</launch>

1.4 parameter参数

我们可以在1.3中看到在launch文件中同样可以对parameter进行少量的赋值,但是大量的parameter参数则是需要通过yaml文件来进行读取

launch形式

# 导入库
from launch import LaunchDescription
from launch_ros.actions import Node
from ament_index_python.packages import get_package_share_directory
import os

# 定义函数名称为:generate_launch_description
def generate_launch_description():
para_dir = os.path.join(get_package_share_directory('fusion_localizer'), 'config', 'turtlesim.yaml')##yaml file contains parameters
ns = os.environ.get('ROBOT_ID')##read namespace from env
return LaunchDescription([
Node(
# 创建Node对象fusion_localizer_node,标明所在位置fusion_localizer,将可执行文件重命名为fusion_localizer_nodes,同时可以将运行节点node放在同一个自定义的命名空间ns当中。
package='fusion_localizer',
namespace=ns,
executable='fusion_localizer_node',
name='fusion_localizer_nodes',
parameters=[para_dir]
),
])

命令行形式

ros2 run turtlesim turtlesim_node --ros-args --params-file ./turtlesim.yaml

在ROS2中yaml文件格式则是不一样的:第一层为你的namespace第二层为你的node_name第三层为ros__parameters,该层名称为固定不可修改的,第四层为具体的参数,接收包括int, double, bool, string等类型。同一namespace的node应放置于同一层相邻。

your_node_namespace:
your_node_name_1:
ros__parameters:
ip: "192.168.0.10"
port: 12002
output: false

your_node_name_2:
ros__parameters:
subscribe_topic: "/node_namespace_1/topic"

而在ROS1中则是很简单的yaml格式,直接是**namespace层(可省略)**对应的具体的参数。

local_costmap:
global_frame: odom
robot_base_frame: base_link

update_frequency: 5.0
publish_frequency: 5.0
transform_tolerance: 0.5

同时,在ROS1中,Parameter参数机制默认是无法实现动态监控的(需要配合专门的动态机制),比如正在使用的参数被其他节点改变了,如果不重新查询的话,就无法确定改变之后的值。ROS2最新版本中添加了参数的事件触发机制​​ParameterEventHandler​​​, 当参数被改变后,可以通过回调函数的方式,动态发现参数修改结果。这里古月老师讲的很详细了,具体