ROS学习小结


2018.7.30

摘要:

本文是在开始学习ROS过程中所做的小结,以二话不说上来就干的方式,一步一步亲自动手实现ROS中创建工作空间和package。通过创建,编译和运行节点的方式带大家了解和体会ROS中消息、服务和动作的工作机制。文中动手内容偏多,参考了ROS官方网站和《Learning ROS for Robotics Programming》等相关书籍。水平有限,欢迎批评指正。
(注:系统Ubuntu 16.04,ROS kinetic)

创建工作空间(workspace)

在开始工作之前,我们需要创建自己工作空间,以添加自己的代码。假设我们的工作空间的名称为catkin_ws, 则创建的步骤如下

  1. 在用户文件夹(~)中创建/dev/catkin_ws/src文件夹,也可以在终端输入mkdir –p ~/dev/catkin_ws/src
  2. 在终端输入cd ~/dev/catkin_ws/src,进入到src目录下
  3. 在终端输入catkin_init_workspace命令对新工作区间进行初始化。完成后会在src文件夹下会新增CmakeLists.txt文件
  4. 在终端输入cd ~/dev/catkin_ws,或cd ..(仅当终端路径处于~/dev/catkin_ws/src目录下)进入~/dev/catkin_ws目录
  5. 在终端输入catkin_make对工作空间进行编译。完成后会在catkin_ws文件夹下生成build和devel文件夹
  6. 在终端输入gedit ~/.bashrc进入.bashrc隐藏文件,并在文件最后新建一行添加source ~/dev/catkin_ws/devel/setup.bash,或在终端键入echo "source ~/dev/catkin_ws/devel/setup.bash" >> ~/.bashrc
  7. 关闭终端,并重新打开,此时创建好新的工作空间

总结:新建终端依次输入的命令为:

  • mkdir –p ~/dev/catkin_ws/src
  • cd ~/dev/catkin_ws/src
  • catkin_init_workspace
  • cd ..
  • catkin_make
  • echo "source ~/dev/catkin_ws/devel/setup.bash" >> ~/.bashrc

工作区间目录说明:

catkin_ws

build :catkin_make产生的中间文件
devel :catkin_make生成的输出文件

include :自定义消息、服务和动作的头文件
lib :生成的可执行文件

src :源代码存放位置

package_one :包1(英文名叫package,中文姑且叫做包吧? )

action :存放动作(*.action)文件
include :存放库文件(*.hpp, *.cpp, *.h, *.c)
msg :存放消息文件(*.msg)
launch :存放启动文件(*.launch)
scripts :存放python文件(*.py)
src :存放节点源文件(*.cpp, *.c)
srv :存放服务文件(*.srv)
CmakeLists.txt :catkin_make文件
package.xml : 包描述文件

package_two :包2

… :与包1相同

创建ROS包(package)

ROS中的包,需要存放在工作空间中src文件夹下,假设包的名字为test,则创建步骤为:

  1. 终端输入cd ~/dev/catkin_ws/src,进入到src文件夹下
  2. catkin_create_pkg test std_msgs roscpp创建包

在步骤2中使用了catkin_create_pkg命令创建新的包,其中test为包的名称,std_msgs roscpp表示此包依赖ROS标准消息类型和C++编译环境,具体命令结构如下:
Catkin_create_pkg [package_name] [depend1] [depend2] [depend3]

ROS提供了以下命令方便对包的查看

  • rospack profile : 查看新添包的信息,在安装新的包后非常有用
  • rospack find [package_name] : 查找指定包的位置
  • rospack depends [package_name] : 查看指定包的依赖
  • rosls [package_name] : 查看指定包的内容
  • roscd [package_name] : 切换到指定包的目录

Listener And Talker

ROS以节点基础,通过建立P2P网络和规范化信息传递方式,实现节点之间的相互通讯。话不多说,运行下面两个节点来感受ROS的通讯机制。
Talker: 在test包中的src文件夹下,创建talker_node.cpp文件,粘贴如下内容

#include "ros/ros.h" // ros头文件
#include "std_msgs/String.h" // 消息头文件
#include <sstream> // stl字符串头文件
int main(int argc, char **argv)
{
    // ROS初始化,名称为talker
    ros::init(argc, argv, "talker"); 
    // 创建节点句柄
    ros::NodeHandle n; 
    // 创建消息发布变量,消息类型为std_msgs::String,名称为message,缓存1000字节
    ros::Publisher pub = n.advertise<std_msgs::String>("message", 1000);
    // 创建发布频率变量,频率为10Hz
    ros::Rate loop_rate(10);
    // 如果ROS出于开启状态,则保持循环
    while (ros::ok()){
        // 创建消息变量并,输入信息: I am the talker node 
        std_msgs::String msg;
        std::stringstream ss;
        ss << " I am the talker node ";
        msg.data = ss.str();
        // 发布消息
        pub.publish(msg);
        // 启动ROS消息回调处理函数
        ros::spinOnce();
        // 等待
        loop_rate.sleep();
    }
    return 0;
}

Listener: 在test包中的src文件夹下,创建listener_node.cpp文件,粘贴如下内容

#include "ros/ros.h"
#include "std_msgs/String.h"
// 消息回调处理函数
void Callback(const std_msgs::String::ConstPtr& msg)
{
    // 将收到的消息打印出来
    ROS_INFO("I heard: [%s]", msg->data.c_str());
}
int main(int argc, char **argv)
{
    ros::init(argc, argv, "listener");
    ros::NodeHandle n;
    // 创建消息订阅变量,消息名称为message,缓存1000字节,回调函数为CallBack
    ros::Subscriber sub = n.subscribe("message", 1000, Callback);
    // 启动ROS消息回调处理函数
    ros::spin();
    return 0;
}

CmakeList.txt最后添加以下内容

# 添加路径搜索目录
include_directories(
include
${catkin_INCLUDE_DIRS}
)
# 添加节点的可执行文件
add_executable(${PROJECT_NAME}_Listener_node src/listener_node.cpp)
add_executable(${PROJECT_NAME}_Talker_node   src/talker_node.cpp)

# 添加节点可指定文件作需要链接的库文件
target_link_libraries(${PROJECT_NAME}_Listener_node ${catkin_LIBRARIES})
target_link_libraries(${PROJECT_NAME}_Talker_node   ${catkin_LIBRARIES})

编译(catkin_make)

在写完源文件和修改完CmakeLists.txt文件后,即可对此包进行编译,在终端输入以下命令:

  • cd ~/dev/catkin_ws/
  • catkin_make

编译成功后,新建三个终端,依次在各个终端上运行

  • roscore
  • rosrun test test_Listener_node
  • rosrun test test_Talker_node

在运行listener的终端上会看到,talker节点发出的 I am the talker node信息

launch文件初探
每个终端点运行一个节点,再加上roscore的一个终端,有时十分不方便。ROS中提供了roslaunch函数,可以实现一个终端可同时运行多个节点的目的。此函数会自动运行roscore节点,并通过运行launch文件实现,具体方法为:

  1. 在包下的launch文件夹下创建node.launch文件。(没有launch文件夹可自行创建,launch文件的文件名可重新命名,本例的文件名为node)
  2. 在node.launch文件中粘贴以下内容
    <?xml version="1.0"  encoding="UTF-8" standalone="no"?>
    <launch>
        <!-- listener node, 节点名称,包名,节点。output=“screen“将输出打印到终端 -->
        <node name="Listener_node" pkg="test" type="test_Listener_node" output="screen"/>
        <!-- talker node -->
        <node name="Talker_node" pkg="test" type="test_Talker_node"/>
    </launch>
    
  3. 新建终端运行roslaunch test node.launch

通过在新终端中键入rosnode list可以查看当前运行的节点,rostopic list可一查看发布的消息名称

目前为止我们已经在ROS上运行了两个节点,并完成了数据的收发和处理,并使用launch文件在一个终端下实现了两个节点的运行。接下来学习ROS中三种信息传递的方式:消息、服务和动作。

python

ROS中可以使用python作为编程语言,其好处是省去了写CmakeLists的麻烦(但需要在CmakeLists中find_packag内添加rospy,然后从新对包编译,已获得对python的支持),并且可以使用丰富的python资源,提高编程效率和代码安全性。
python文件应放在scripts文件夹中,下面使用python对上面两个节点进行重写。
talker_node.py

#!/usr/bin/env python
# 添加ros函数库
import rospy
# 添加std_msgs中String类型
from std_msgs.msg import String
# 初始化节点
rospy.init_node('Talker_node')
# 初始化发布者,话题名称messaage,类型String,缓存1000字节
pub = rospy.Publisher('message', String, queue_size = 1000)
# 发布频率10Hz
rate = rospy.Rate(10)
# 定义发布的信息
msg = String()
msg = ' I am the talker node '
# 循环发布
while not rospy.is_shutdown():
	pub.publish(msg)
	rate.sleep()

listener_node.py

#!/usr/bin/env python
import rospy
from std_msgs.msg import String
# 话题回调函数
def CallBack(msg):
	rospy.info('I heard %s"', msg.data)

rospy.init_node('Listener_node')
# 初始化订阅者,订阅话题为message,类型String,回调函数CallBack
sub = rospy.Subscriber('message', String, CallBack)
rospy.spin()

将上面两个文件保存到制定目录,然后在三个终端中依次运行:

  • roscore
  • rosrun test listener_node.py
  • rosrun test talker_node.py

或者运行launch文件:

<?xml version="1.0"  encoding="UTF-8" standalone="no"?>
<launch>
    <!-- listener node, 节点名称,包名,节点。output=“screen“将输出打印到终端 -->
    <node name="Listener_node" pkg="test" type="listener_node.py" output="screen"/>
    <!-- talker node -->
    <node name="Talker_node" pkg="test" type="talker_node.py"/>
</launch>

##消息(message

###自定义消息
在package中的msg文件夹下新建Position_msg.msg文件,粘贴以下内容。

float64 latitude
float64 longitude
float64 altitude

该消息包含了经度、纬度和高度三个信息。

修改CmakeLists.txt文件

  1. 在find_packag中加入message_generation的依赖
  2. 在add_message_files中添加Position_msg.msg
  3. generate_messages中添加自定义消息的依赖

例如:

find_package(catkin REQUIRED COMPONENTS
#   其他依赖包(std_msgs, roscpp, rospy等)
    message_generation
}

add_message_files(
  FILES
  Position_msg.msg
)

generate_messages(
  DEPENDENCIES
  std_msgs
)

修改package.xml文件

找到

<!--   <build_depend>message_generation</build_depend> -->
<!--   <exec_depend>message_runtime</exec_depend> -->

去掉注释后并保存

<build_depend>message_generation</build_depend>
<exec_depend>message_runtime</exec_depend>

使用消息

Publisher :新建gnss_node.cpp文件,内容如下

/*gnss_node.cpp*/
#include "ros/ros.h"
#include "test/Position_msg.h" // 自定义的消息类型
int main(int argc, char **argv)
{
    ros::init(argc, argv, "Gnss");
    ros::NodeHandle n;
    ros::Publisher pub = n.advertise<test::Position_msg>("Position_msg", 1000);
    ros::Rate loop_rate(10);
    test::Position_msg pos;
    pos.latitude  = 39.2;
    pos.longitude = 40.5;
    pos.altitude  = 100.6;
    while (ros::ok()){
        pub.publish(pos);
        ros::spinOnce();
        loop_rate.sleep();
    }
    return 0;
}

Subscriber:新建controller_node.cpp文件,内容如下

/*controller_node.cpp*/
#include "ros/ros.h"
#include "test/Position_msg.h"
void Callback(const test::Position_msg::ConstPtr& msg)
{
    ROS_INFO("Lat : %f, Lng : %f, Alt : %f", 
    	msg->latitude, msg->longitude, msg->altitude);
}
int main(int argc, char **argv)
{
    ros::init(argc, argv, "Control");
    ros::NodeHandle n;
    ros::Subscriber sub = n.subscribe("Position_msg", 1000, Callback);
    ros::spin();
    return 0;
}

修改CmakeLists.txt,添加需要新的节点,并添加节点的依赖关系,本例中添加项为:

include_directories(
  include
  ${catkin_INCLUDE_DIRS}
)
# 添加节点的可执行文件
add_executable(${PROJECT_NAME}_Gnss_node src/gnss_node.cpp)
add_executable(${PROJECT_NAME}_Controller_node   src/controller_node.cpp)
# 添加节点依赖关系
add_dependencies(${PROJECT_NAME}_Gnss_node       ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})
add_dependencies(${PROJECT_NAME}_Controller_node ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})
# 添加节点可指定文件作需要链接的库文件
target_link_libraries(${PROJECT_NAME}_Gnss_node       ${catkin_LIBRARIES})
target_link_libraries(${PROJECT_NAME}_Controller_node ${catkin_LIBRARIES})

相关知识

消息传输原理
ROS话题传输一共分7个步骤进行,分别为:

  1. 发布者注册
  2. 订阅者注册
  3. ROS Master信息匹配
  4. 订阅者发送请求
  5. 发布者确认请求
  6. 订阅者建立TCP链接
  7. 发布者发送消息到订阅者

下图中Tom为发布者,Bob为订阅者,简述了话题传递的原理

Tom ROS Master Bob 1.大哥!我知道时间 2.大哥!我想知道时间 3.Tom知道时间 4.哥们!你知道时间吗? 5.老弟!我知道 6.好的,保持通话 7.现在时间12点整 Tom ROS Master Bob

消息数据类型

std_msgs文件中的变量类型与C++中的变量类型对应表如下:

msg typeC++msg typeC++
boolboolint64long long
int8charuint64unsigned long long
uint8unsigned charfloat32float
int16shortfloat64double
uint16unsigned shortstringstd::string
int32inttimeros::Time
uint32unsigned intdurationros::Duration

此外msg还可以使用其他包中的消息类型,例如sensor_msgs中的Image.msg、PointCloud2.msg、PointCloud.msg等,或用户自定义的消息类型,此时需要在generate_messages中添加对这些包的依赖。并且在去掉add_dependencies注释。

注:在编译时,如果当前包中用到了其他包中所定义的消息、服务和动作类型,需要添加add_dependencies项。

服务

自定义服务类型

服务(Server)官方教程
在package中的srv文件夹下新建Add_srv.srv文件,粘贴以下内容。
Add_srv.srv

# request
float64 apple
float64 banana
---
#response
float64 sum

服务文件分请求和回答两个部分,之间以三个横线(—)分隔,上半部分为请求部分,即输入到服务器的数据,下半部分为回答部分,即返回给客户端的数据。

修改CmakeLists.txt文件

  1. 在find_packag中加入message_generation的依赖
  2. 在add_service_files中添加Add_srv.srv
  3. generate_messages中添加自定义消息的依赖

例如:

find_package(catkin REQUIRED COMPONENTS
#   其他依赖包(std_msgs, roscpp, rospy等)
    message_generation
}

add_service_files(
  FILES
  Add_srv.srv
)

generate_messages(
  DEPENDENCIES
  std_msgs
)

修改package.xml文件的方式同上。

使用服务

Service :新建add_server_node.cpp文件,内容如下

/*add_server_node.cpp*/
#include "ros/ros.h"
#include "test/Add_srv.h"

bool AddServer(test::Add_srv::Request  &req, test::Add_srv::Response &res)
{
    res.sum = req.apple + req.banana;
	return true;
}

int main(int argc, char **argv)
{
    ros::init(argc, argv, "Add_srv_node");
    ros::NodeHandle n;
    // 创建一个加法服务,名称为Add_srv,对应函数为AddServer
    ros::ServiceServer srv = n.advertiseService("Add_srv", AddServer);
    ros::spin();
    return 0;
}

Client :新建add_client_node.cpp文件,内容如下

/*add_client_node.cpp*/
#include "ros/ros.h"
#include "test/Add_srv.h"

int main(int argc, char **argv)
{
    ros::init(argc, argv, "Add_node");
    ros::NodeHandle n;
    // 创建客户端,服务类型为test::Add_srv,请求服务的名称为Add_srv
    ros::ServiceClient client =  n.serviceClient<test::Add_srv>("Add_srv");
    ros::Rate loop_rate(1);
    // 创建服务变量,并输入需要相加的两个数
    test::Add_srv add_srv;
    add_srv.request.apple  = 100;
    add_srv.request.banana = 50;
    for (;;){
        // 请求服务
        if ( client.call(add_srv) ) {
            // 打印计算结果
            ROS_INFO("Sum : %f", add_srv.response.sum);
        } else {
            // 请求失败,打印错误信息
            ROS_ERROR("Failed to call service");
        }
        loop_rate.sleep();
    }
    ros::spin();
    return 0;
}

修改CmakeLists.txt部分,添加需要新的节点,并编译。

include_directories(
  include
  ${catkin_INCLUDE_DIRS}
)
# 添加节点的可执行文件
add_executable(${PROJECT_NAME}_AddServer_node src/add_server_node.cpp)
add_executable(${PROJECT_NAME}_AddClient_node src/add_client_node.cpp)
# 添加节点依赖关系
add_dependencies(${PROJECT_NAME}_AddServer_node ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})
add_dependencies(${PROJECT_NAME}_AddClient_node ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})
# 添加节点可指定文件作需要链接的库文件
target_link_libraries(${PROJECT_NAME}_AddServer_node ${catkin_LIBRARIES})
target_link_libraries(${PROJECT_NAME}_AddClient_node ${catkin_LIBRARIES})

服务流程

ROS中服务相当于多线程编程中的同步模型,客户端在发送请求和收到回到的期间,出于等待状态,不能进行下一步操作。

李婶儿 店员称重 这些苹果和香蕉称一下 疯狂操作机器中。。。 贴条:150斤,300块(不熟悉菜价,瞎写的? ) 李婶儿 店员称重

想象一下李婶儿去水果店买水果的过程,挑水果和付钱时李婶儿亲自操作,而称重标价则是由店员完成。在称重的过程中,李婶儿只能等待其完成才可以去收银台付款。此时李婶儿为服务中的客户端,店员为服务端,水果为店员的输入,贴好的签为店员的输出。在服务器执行的过程中客户端出于等待状态。

服务有自己的标准输入输出接口,可以对应多个节点的请求。

动作

自定义动作

动作(Action)官方教程
在package中的ation文件夹下新建Count.srv文件,粘贴以下内容。

# Define the goal
int32 length
---
# Define the result
int32 cnt
---
# Define a feedback message 
float64 percent

与srv文件不同,action文件氛围三个部分,从上倒下分别为目标、结果和反馈,各个部分之间同样以三个横线(—)分隔。其中目标为服务端的输入,结果为服务端的输出,动作服务器在运行的过程中可以通过反馈,项客户端发送反馈信息(例如当前运行的百分比)。
修改CmakeLists.txt文件

  1. 在find_packag中加入message_generation,genmsg,actionlib_msgs,actionlib的依赖
  2. 在add_action_files中添加Count.action
  3. generate_messages中添加actionlib_msgs依赖

例如:

find_package(catkin REQUIRED COMPONENTS
  #其他依赖
  message_generation
  genmsg
  actionlib_msgs
  actionlib
)

add_action_files(
  FILES
  Count.action
)

generate_messages(
  DEPENDENCIES
  std_msgs
  actionlib_msgs
)

修改package.xml
在package.xml中<package>标签内,添加下面内容

<build_depend>actionlib</build_depend>
<build_depend>actionlib_msgs</build_depend>
<exec_depend>actionlib</exec_depend> 
<exec_depend>actionlib_msgs</exec_depend>

使用动作

动作服务器:在action文件夹下添加action_server_node.cpp,内容如下:


#include "ros/ros.h"
// 动作服务头文件
#include <actionlib/server/simple_action_server.h>
// 自定动作义头文件
#include "test/CountAction.h"
// 定义自定义动作服务类型
typedef actionlib::SimpleActionServer<test::CountAction> Server;
/*
 * 根据goal中的length值,以10Hz的速度进行计数,并每计数一次,返回进度状态
 */
void Execute(const test::CountGoalConstPtr& goal, Server* as)
{
    ros::Rate loop_rate(10);
    // 创建反馈变量
    test::CountFeedback feedback;
    // 创建结果变量
    test::CountResult res;
    for (int i = 0; i != goal->length; ++i){
        ROS_INFO("In Action Server Node");
        loop_rate.sleep();
        // 计算已完成任务的百分比
        feedback.percent = static_cast<float>(i) / goal->length;
        // 发布反馈信息
        as->publishFeedback(feedback);
    }
    // 记录运算结果,并发布完成信息
    res.cnt = goal->length;
    as->setSucceeded(res);
}

int main(int argc, char** argv)
{
    ros::init(argc, argv, "count_server");
    ros::NodeHandle n;
    // 创建动作服务器,名称为count_action,过bind函数适配器,将server变量传入Execute函数,_1为占位符
    Server server(n, "count_action", boost::bind(&Execute, _1, &server), false);
    // 开启服务器
    server.start();
    ros::spin();
    return 0;
}

动作客户端:在action文件夹下添加action_client_node.cpp,内容如下:

#include "ros/ros.h"
#include <actionlib/client/simple_action_client.h>
#include "test/CountAction.h"
// 定义自定义动作客户端类型
typedef actionlib::SimpleActionClient<test::CountAction> Client;
/*
 * 动作完成时调用,打印结果信息
 */
void Finish(const actionlib::SimpleClientGoalState& state,
            const test::CountResultConstPtr& result)
{
    ROS_INFO("result : %d", result->cnt);
}
/*
 * 动作发出反馈时调用,打印反馈信息
 */
void Feedback(const test::CountFeedbackConstPtr& feedback)
{
    ROS_INFO("Got Feedback : %f", feedback->percent);
}
/*
 * 动作启动调用,打印启动信息
 */
void Active()
{
    ROS_INFO("Goal just went active");
}

int main(int argc, char** argv)
{
    ros::init(argc, argv, "count_client");
    // 创建客户端变量,目标服务器名称为count_action, ture : 不需要ros::spin()
    Client client("count_action", true);
    // 等待服务器启动
    client.waitForServer();
    // 设置目标值
    test::CountGoal goal;
    goal.length = 100;
    /* 发送目标到动作服务器,
     * 运行开始执行Active函数,
     * 收到反馈信息执行Feedback函数,
     * 运行结束执行Finish函数,
     * 除了goal之外,其他位置可以使用nullptr代替 */
    client.sendGoal(goal, Finish, Active, Feedback);
    ros::Rate loop_rate(2);
    while (ros::ok()){
        ros::spinOnce();
        ROS_INFO("In Action Client Node");
        loop_rate.sleep();
    }
    return 0;
}

修改CmakeLists.txt部分,添加需要新的节点,并编译。

include_directories(
  include
  ${catkin_INCLUDE_DIRS}
)
# 添加节点的可执行文件
add_executable(${PROJECT_NAME}_ActionServer_node src/action_server_node.cpp)
add_executable(${PROJECT_NAME}_ActionClient_node src/action_client_node.cpp)
# 添加节点依赖关系
add_dependencies(${PROJECT_NAME}_ActionServer_node ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})
add_dependencies(${PROJECT_NAME}_ActionClient_node ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})
# 添加节点可指定文件作需要链接的库文件
target_link_libraries(${PROJECT_NAME}_ActionServer_node ${catkin_LIBRARIES})
target_link_libraries(${PROJECT_NAME}_ActionClient_node ${catkin_LIBRARIES})

动作介绍

ROS中的动作模型相当于多线程编程中的异步模型,动作客户端启动动作后无需等待动作完成,可以继续执行后续工作,中途只需处理一下动作的反馈结果和最终结果即可。

小新
定外卖
饿了
打游戏
打游戏
做饭
吃饭
打游戏

上图以一个生活中简单的例子,讲述异步的运行机制。小新在玩游戏的时候感觉自己饿了,于是在某app上定了一份外卖(sendGoal发送目标请求),之后继续打游戏(不等待,继续执行)。期间,app会发布某些消息以告知外卖的进度(反馈),小新看过一眼后得知外卖按照预定计划执行,所以并没有打客服投诉(反馈处理函数),而是继续打游戏,外卖小哥敲门时(动作执行完毕),小新开门结果外卖开始吃饭(处理执行结果),饭后继续打游戏(继续执行任务)。图中实现表示客户端进程,虚线表示动作服务端进程。

launch文件

launch文件的基本模版为:

<?xml version="1.0"  encoding="UTF-8" standalone="no"?>
<launch>
    <node name=" " pkg=" " type=" "/>
    <node name=" " pkg=" " type=" "/> 
    <node name=" " pkg=" " type=" "/>
    <!-- ... -->
<\launch>

node标签代表一个节点,在模版中的节点名称(name),包名称(pkg),和可执行节点名称(type)中输入对应的值即可,按顺序执行每个节点。其中每个节点的name是唯一的,但可以使用相同的type,例如:

<?xml version="1.0"  encoding="UTF-8" standalone="no"?>
<launch>
    <!-- listener node, 节点名称,包名,节点。output=“screen“将输出打印到终端 -->
    <node name="Listener1_node" pkg="test" type="test_Listener_node" output="screen"/>
    <node name="Listener2_node" pkg="test" type="test_Listener_node" output="screen"/>
    <!-- talker node -->
    <node name="Talker_node" pkg="test" type="test_Talker_node"/>
</launch>

传入参数
launch除了可以用于启动多个节点外,还可以设置节点的启动参数,例如:

<?xml version="1.0"  encoding="UTF-8" standalone="no"?>
<launch>
    <node name="GNSS_node" pkg="gnss" type="gnss_node">
        <param name="port"      type="string" value="/dev/ttyS1"/>
        <param name="baud_rate" type="int"    value="115200"/>
    </node>
</launch>

由于xml语法的原因,如果node标签未能在一行内定义完全则需要使用</node>作为标签的结束符号。在节点中使用

#include "ros/ros.h"
#include <string>

int32 main(int32 argc, int8 **argv)
{
    ros::init(argc, argv, "GNSS_node");
    ros::NodeHandle nLocal("~");
    std::string port     = "/dev/ttyS0";
    int32       baudRate = 9600;
    nLocal.getParam("port", port);
    nLocal.getParam("baud_rate", baudRate);
    ros::spin()
    return 0
}

上述例子简单介绍了launch文件设置节点参数的方法。函数中初始串口为/dev/ttyS0,波特率为9600,经过launch启动重新对端口号和波特率赋值后,串口号和波特率变更为/dev/ttyS1,115200。

有时value中的变量会很长,或很多参数使用同一个值,此时需要arg标签定义变量,然后在param标签中使用,定义方式如下:

<?xml version="1.0"  encoding="UTF-8" standalone="no"?>
<launch>
    <!-- ARGUMENTS -->
    <arg name="gnss_port"      value="/dev/ttyS1">
    <arg name="gnss_baud_rate" value="115200">
    <!-- NODES -->
    <node name="GNSS_node" pkg="gnss" type="gnss_node">
        <param name="port"      type="string" value="$(arg gnss_port)"/>
        <param name="baud_rate" type="int"    value="$(arg gnss_baud_rate)"/>
    </node>
</launch>

其他参数

  • output=”screen”配置了该属性的节点会将标准输出显示在屏幕上而不是记录到日志文档
  • respawn=”true”,这样当节点停止的时候,roslaunch会重新启动该节点
  • required="true",当这个节点退出后,roslaunch会关闭所有的节点,并退出
  • launch-prefix="xterm -e"让该个节点在单独的终端窗口中启动

具体内容请参考官方说明文档,和相关博客等资料。

yaml文件

yaml是一种更加简洁的表达数据的方式,常被当做配置文件,用于参数多精度高等场合,在节点运行的过程中可以多次读取其中数据,以实现对参数的更新和设置。使用yaml文件需要在CmakeLists中对使用该文件节点的target_link_libraries中添加yaml-cpp依赖

由于yaml文件语言内容比较多,本文仅以一个简单的例子要介绍,具体内容请参阅官方文档和相关博客

文件名为:config.yaml

- 
  name    : tank # I am a comment
  attack  : 1000
  defence : 1000
- 
  name    : V3 
  attack  : 2000
  defence : 500

通过C++读取yaml文件中的数据

#include "yaml-cpp/yaml.h"
#include <iostream>

int main(int argc, char **argv)
{
    // yaml节点
    YAML::Node config = YAML::LoadFile(argv[1]);
    std::string name;

    for (int i = 0; i != config.size(); ++i){
        // 读取第一个数组的name参数
        name = config[i]["name"].as<std::string>();
        // 打印数组信息
        std::cout << name 
            << "\n  attack  : " << config[i]["attack"].as<int>()
            << "\n  defence : " << config[i]["defence"].as<int>()
            << std::endl;
    }
    return 0;
}

输出为:

tank
  attack  : 1000
  defence : 1000
V3
  attack  : 2000
  defence : 500
  • 9
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值