ROS:话题、消息、服务、动作 、launch、tf文件编写方法

1.工作空间

目录结构

  • src:代码空间(Source Space)存放工程文件的文件夹。
  • build:编译空间(Build Space)编译产生的中间文件(很少打开)。
  • devel:开发空间(Development Space)编译完成的可执行文件和链接、环境配置的脚本。

创建工作空间

#创建工作空间
mkrdir -p ~/catkin_ws/src
cd ~/catkin_ws/src
catkin_init_workspace

#编译工作空间
cd ~/catkin_ws
catkin_make

#设置环境变量
sudo vim ~/.zshrc
source ~/catkin_ws/devel/setup.zsh
source ~/.zshrc

#检查环境变量
echo $ROS_PACKAGE_PATH

#查看所有的变量
env | grep ros

创建功能包和编译代码

#创建功能包
cd ~/catkin_ws/src
catkin_create_pkg myPackName std_msgs rospy roscpp

#编译功能包
cd ~/catkin_ws
catkin_make
source ~/catkin_ws/devel/setup.bash
  • CMakeLists.txt:编译功能包的编译选项
  • package.xml:管理功能包信息的文件(包括依赖等信息)

2.ROS通信编程

话题通信编程

  • 创建发布者talker.cpp
#include <ros/ros.h>
#include <std_msgs/String.h>

int main(int argc,char **argv)
{
    //ROS节点初始化
    ros::init(argc,argv,"talker");  
    //创建节点句柄 
    ros::NodeHandle n;            

    //创建一个Publisher,发布名为chatter的topic,消息类型为std_msgs::String,缓存空间1000
    ros::Publisher chatter_pub = n.advertise<std_msgs::String>("chatter",1000);

    //设置循环的频率10Hz
    ros::Rate loop_rate(10);

    int count = 0;
    while (ros::ok())   //监测但前节点是否正常运行
    {
        //初始化消息
        std_msgs::String msg;
        std::stringstream ss;
        ss << "hello ros" << count;
        msg.data = ss.str();

        //终端打印变量       
        ROS_INFO("I publish a message: %s",msg.data.c_str());

         //发布消息
        chatter_pub.publish(msg);

        //循环等待回调函数
        ros::spinOnce();

        //按照巡回频率延时
        loop_rate.sleep();
        ++count;
    }
    
    return 0;
}
  • 创建订阅者listener.cpp
#include <ros/ros.h>
#include <std_msgs/String.h>

//受到订阅的消息后,会进入消息回调函数
void chatterCallBack(const std_msgs::String::ConstPtr& msg)
{
    //将收到的消息打印出来
    ROS_INFO("I hear a message: %s",msg->data.c_str());
}

int main(int argc,char **argv)
{
    //初始化ros节点
    ros::init(argc,argv,"listener");

    //创建节点句柄
    ros::NodeHandle n;

    //创建一个订阅者,订阅名为chatter的topic,消息类型为std_msgs::String,缓存空间1000,回调函数为chatterCallBack
    ros::Subscriber sub = n.subscribe("chatter",1000,chatterCallBack);

    //循环并等待回调函数
    ros::spin();

    return 0;
}
  • 添加编译配置文件CMake_Lists.txt,并编译
include_directories(include ${catkin_INCLUDE_DIRS})   #包含ROS路径的依赖(ros的头文件)
add_executable(talker src/talker.cpp)                 #可执行文件名 文件目录
target_link_libraries(talker ${catkin_LIBRARIES})     #把库文件链接到可执行文件中

add_executable(listener src/listener.cpp)                 #可执行文件名 文件目录
target_link_libraries(listener ${catkin_LIBRARIES})     #把库文件链接到可执行文件中
  • 运行可执行文件
roscore
rosrun myPackName talker
rosrun myPackName listener

自定义话题消息

  • 在包下面创建一个msg文件加,再在文件夹里创建一个.msg文件,我这里创建了student.msg
string name
bool sex
Float32 height

bool male = true
bool female = false
  • 在包管理文件package.xml中添加依赖
<build_depend>message_generation</build_depend>
<exec_depend>message_runtime</exec_depend>
<!-- 部分ROS版本中exec_depend需改成run_depend -->
  • 修改编译配置文件CMake_Lists.txt
#添加功能包
find_package(catkin REQUIRED COMPONENTS
	...
	message_generation	
)

#添加具体依赖
catkin_package(
	CATKIN_DEPENDS roscpp rospy std_msgs message_runtime	
)

#添加具体msg文件
add_message_files(FILES student.msg)		
generate_messages(DEPENDENCIES std_msgs)

服务请求编程

  • 在包下面创建一个srv文件加,再在文件夹里创建一个.srv文件,我这里创建了AddTwoInts.srv
int64 a
int64 b
---
int64 sum
  • 在包管理文件package.xml中添加依赖
<build_depend>message_generation</build_depend>
<exec_depend>message_runtime</exec_depend>
<!-- 部分ROS版本中exec_depend需改成run_depend -->
  • 修改编译配置文件CMake_Lists.txt,并编译工程
#添加功能包
find_package(catkin REQUIRED COMPONENTS
	...
	message_generation	
)

#添加具体依赖
catkin_package(
	CATKIN_DEPENDS roscpp rospy std_msgs message_runtime	
)

#添加具体文件
add_service_files(FILES AddTwoInts.srv)
  • 创建服务器程序server.cpp
#include <ros/ros.h>
#include "my_first_pack/Add.h"

//service回调函数,输入参数req,输出参赛res
bool add(my_first_pack::Add::Request &req,
            my_first_pack::Add::Response &res)
{
    //将输入参数中的请求数据相加
    res.sum = req.a + req.b;
    ROS_INFO("request:a=%ld,b=%ld    response:%ld",(long int)req.a,(long int)req.b,(long int)res.sum);
    return true;
}

int main(int argc,char **argv)
{
    //ROS节点初始化
    ros::init(argc,argv,"addfunction_server");

    //创建节点句柄
    ros::NodeHandle n;

    //创建一个名为addfunction的服务,注册回调函数add()
    ros::ServiceServer service = n.advertiseService("addfunction",add);

    //循环等待回调函数
    ROS_INFO("Ready to add two ints.");
    ros::spin();

    return 0;
}

  • 创建客户端程序client.cpp
#include <cstdlib>
#include <ros/ros.h>
#include "my_first_pack/Add.h"

int main(int argc,char** argv)
{
    //ROS节点初始化
    ros::init(argc,argv,"addfunction_client");

    //从终端命令行获取两个加数
    if(argc != 3)
    {
        ROS_INFO("usage: addfunction_client a b");
        return 1;
    }

    //创建节点句柄
    ros::NodeHandle n;

    //创建一个client,请求addfunction_server,service消息类型是my_first_pack::Add
    ros::ServiceClient client = n.serviceClient<my_first_pack::Add>("addfunction");

    //创建my_first_pack::Add典型的服务消息
    my_first_pack::Add srv;
    srv.request.a = atoll(argv[1]);     //把字符串转换成长长整型数
    srv.request.b = atoll(argv[2]);

    //发布服务请求,等待加法运算应答结果
    if(client.call(srv))
    {
        ROS_INFO("Sum: %ld",(long int)srv.response.sum);
    }else{
        ROS_ERROR("Failed to call service Add");
        return 1;
    }

    return 0;
}
  • 添加编译依赖,并编译
add_executable(server src/server.cpp)                 #可执行文件名 文件目录
target_link_libraries(server ${catkin_LIBRARIES})     #把库文件链接到可执行文件中
add_dependencies(server ${PROJECT_NAME}_gencpp)		  #生产自定义请求类型,添加依赖到可执行程序中

add_executable(client src/client.cpp)                 #可执行文件名 文件目录
target_link_libraries(client ${catkin_LIBRARIES})     #把库文件链接到可执行文件中
add_dependencies(client ${PROJECT_NAME}_gencpp)
  • 运行可执行文件
roscore
rosrun myPackName server
rosrun myPackName client 5 6

动作编程

  • 在包下面创建一个action文件加,再在文件夹里创建一个.action文件,我这里创建了DoDishes.action
uint32 ds_id				#定义目标信息
---
uint32 total_cleaned		#定义结果信息
---
float32 percent_complete	#定义周期反馈消息
  • 在包管理文件package.xml中添加依赖
<build_depend>actionlib</build_depend>
<build_depend>actionlib_msgs</build_depend>
<exec_depend>actionlib</exec_depend>
<exec_depend>actionlib_msgs</exec_depend>
  • 添加编译依赖,并编译
#添加功能包
find_package(catkin REQUIRED COMPONENTS
	...
	actionlib_msgs 
	actionlib
)			

#添加具体文件
add_action_files(DIRECTORY action FILES DoDishes.action)		
generate_messages(DEPENDENCIES actionlib_msgs)
  • 创建服务器程序DoDishes_server.cpp
#include <actionlib/server/simple_action_server.h>
#include "my_first_pack/DoDishesAction.h"

typedef actionlib::SimpleActionServer<my_first_pack::DoDishesAction> Server;

// 收到action的goal后调用该回调函数
void execute(const my_first_pack::DoDishesGoalConstPtr& goal,
                actionlib::SimpleActionServer<my_first_pack::DoDishesAction> *as)
{
    ros::Rate r(1);
    my_first_pack::DoDishesFeedback freedback;

    ROS_INFO("Dishwasher %d is  working.",goal->ds_id);

    // 模拟洗盘子的进度,并且按照1hz的频率发布进度feedback
    for(int i=1;i<=10;i++)
    {
        freedback.percent_complete = i*10;
        as->publishFeedback(freedback);
        r.sleep();
    }

    // 当action完成后,向客户端返回结果
    ROS_INFO("Dishwasher %d finish working.",goal->ds_id);
    as->setSucceeded();
}

int main(int argc,char**argv)
{
    ros::init(argc,argv,"do_dishes_server");
    ros::NodeHandle n;

    //定义一个服务器
    Server server(n,"do_dishes",boost::bind(&execute,_1,&server),false);

    //服务器开始运行
    server.start();

    ros::spin();
    return 0;
}
  • 创建客户端程序DoDishes_client.cpp
#include <actionlib/client/simple_action_client.h>
#include "my_first_pack/DoDishesAction.h"

typedef actionlib::SimpleActionClient<my_first_pack::DoDishesAction> Client;

//完成
void doneCb(const actionlib::SimpleClientGoalState& state,
                const my_first_pack::DoDishesResultConstPtr& result)
{
    ROS_INFO("Yay! The dishes are clean now");
    ros::shutdown();
}

//开始
void activeCb()
{
    ROS_INFO("Goal just went active");
}

// 进度feedback
void feedbackCb(const my_first_pack::DoDishesFeedbackConstPtr& feedback)
{
    ROS_INFO(" percent_complete : %f ",feedback->percent_complete);
}

int main(int argc,char **argv)
{
    ros::init(argc,argv,"do_dishes_client");

    // 定义一个客户端
    Client client("do_dishes",true);

    // 等待服务端
    ROS_INFO("Waiting for action server to start.");
    client.waitForServer();
    ROS_INFO("Action server started, sending goal.");

    //创建一个action的goal
    my_first_pack::DoDishesGoal goal;
    goal.ds_id = 1;

    //发送action的goal给服务器端,并且设置回调函数
    client.sendGoal(goal,&doneCb,&activeCb,&feedbackCb);

    ros::spin();

    return 0;
}
  • 添加编译依赖,并编译
add_executable(DoDishes_client src/DoDishes_client.cpp)        		#可执行文件名 文件目录
target_link_libraries(DoDishes_client ${catkin_LIBRARIES})     		#把库文件链接到可执行文件中
add_dependencies(DoDishes_client ${PROJECT_NAME}_gencpp)		  	#生产自定义请求类型,添加依赖到可执行程序中

add_executable(DoDishes_server src/DoDishes_server.cpp)        		#可执行文件名 文件目录
target_link_libraries(DoDishes_server ${catkin_LIBRARIES})     		#把库文件链接到可执行文件中
add_dependencies(DoDishes_server ${PROJECT_NAME}_gencpp)		  	#生产自定义请求类型,添加依赖到可执行程序中
  • 运行可执行文件
roscore
rosrun myPackName DoDishes_client
rosrun myPackName DoDishes_server

3.ROS中关键组件

Launch文件

  • 启动节点
<launch>
    <node pkg="节点所在的功能包名称"  type="节点的可执行文件名称"  name="节点运行时的名称"/>
</launch>

<!-- 
    output:打印输出位置(screen)
    respawn:失效后是否重新启动
    required:是否把此节点设置为必须的节点
    ns:命名空间
    args:输入参数
 -->
  • 参数
<!--  参数设置:单个参数设置、多个参数设置  -->
<param name="参数名" value="参数值"/>
<rosparam file="params.yaml" command="load" ns="params"/>

<!--  局部变量参数设置与调用  -->
<arg name="参数名" value="参数值" />
<param name="foo" value="$(arg arg-name)">
  • 重映射ROS计算图资源的命名
<remap from="原名" to="映射后的名称" />
  • 嵌套保护其他launch文件
<include file="$(dirname)other.launch"/>

TF坐标变换

  • 创建小乌龟广播文件turtle_tf_broadcaster.cpp。
#include <ros/ros.h>
#include <tf/transform_broadcaster.h>
#include <turtlesim/Pose.h>

std::string turtle_name;

void poseCallback(const turtlesim::PoseConstPtr& msg)
{
    //定义tf广播器
    static tf::TransformBroadcaster bc;

    //根据乌龟当前的姿态,创建相对于世界坐标系的坐标变换
    tf::Transform mtf;
    mtf.setOrigin(tf::Vector3(msg->x,msg->y,0.0));

    tf::Quaternion q;
    q.setRPY(0,0,msg->theta);
    mtf.setRotation(q);

    //发布坐标变换
    bc.sendTransform(tf::StampedTransform(mtf,ros::Time::now(),"world",turtle_name));
}


int main(int argc,char** argv)
{
    //初始化节点
    ros::init(argc,argv,"my_tf_broadcaster");
    if(argc != 2)
    {
        ROS_ERROR("need turtle name as argument");
        return -1;
    }
    turtle_name = argv[1];

    // 订阅乌龟的pose信息
    ros::NodeHandle node;
    ros::Subscriber sub = node.subscribe(turtle_name + "/pose",10,&poseCallback);

    ros::spin();

    return 0;
}
  • 创建小乌龟监听文件turtle_tf_listener.cpp
#include <ros/ros.h>
#include <tf/transform_listener.h>
#include <geometry_msgs/Twist.h>
#include <turtlesim/Spawn.h>

int main(int argc,char** argv)
{
    //初始化节点
    ros::init(argc,argv,"my_tf_listener");
    ros::NodeHandle node;

    //通过服务调用,产生第二只乌龟turtle2
    ros::service::waitForService("spawn");
    ros::ServiceClient add_turtle = node.serviceClient<turtlesim::Spawn>("spawn");
    turtlesim::Spawn srv;

    //发布服务请求,等待应答结果
    add_turtle.call(srv);

    //定义turtle2的速度控制发布器
    ros::Publisher turtle_vel = node.advertise<geometry_msgs::Twist>("turtle2/cmd_vel",10);
    node.advertise<geometry_msgs::Twist>("turtle2/cmd_vel",10);

    //tf监听器
    tf::TransformListener listener;

    ros::Rate rate(10.0);
    while (node.ok())
    {
        tf::StampedTransform mtf;
        try
        {
            //查找turtle2与turtle1的坐标变换
            listener.waitForTransform("/turtle2","/turtle1",ros::Time(0),ros::Duration(3.0));
            listener.lookupTransform("/turtle2","/turtle1",ros::Time(0),mtf);
        }
        catch(const tf::TransformException &ex)
        {
            ROS_ERROR("%s",ex.what());
            ros::Duration(1.0).sleep();
            continue;
        }
        
        //根据turtle1和turtle2之间的坐标变换,计算turtle2需要运动的线速度和角速度
        //并发布速度控制指令,使turtle2向turtle1移动
        geometry_msgs::Twist vel_msg;
        vel_msg.angular.z = 4.0 * atan2(mtf.getOrigin().y(),mtf.getOrigin().x());
        vel_msg.linear.x = 0.5 * sqrt(pow(mtf.getOrigin().x(),2) + pow(mtf.getOrigin().y(),2));  
        turtle_vel.publish(vel_msg);

        rate.sleep();
    }
    
    return 0;
}
  • 添加编译依赖,并编译
## is used, also find other catkin packages
find_package(catkin REQUIRED COMPONENTS
  ...
  roscpp
  tf
  turtlesim
  geometry_msgs
)

add_executable(turtle_tf_broadcaster src/turtle_tf_broadcaster.cpp)
target_link_libraries(turtle_tf_broadcaster ${catkin_LIBRARIES})

add_executable(turtle_tf_listener src/turtle_tf_listener.cpp)
target_link_libraries(turtle_tf_listener ${catkin_LIBRARIES})
  • 编写launch文件
<launch>
    <!-- 海龟仿真器 -->
    <node pkg="turtlesim" type="turtlesim_node" name="sim"/>

    <!-- 键盘控制 -->
    <node pkg="turtlesim" type="turtle_teleop_key" name="teleop" output="screen" />

    <!-- 两只海龟的tf广播 -->
    <node pkg="my_first_pack" type="turtle_tf_broadcaster" args="/turtle1" name="turtle1_tf_broadcaster"/>
    <node pkg="my_first_pack" type="turtle_tf_broadcaster" args="/turtle2" name="turtle2_tf_broadcaster"/

    <!-- 监听tf广播,并且控制turtle2移动 -->
    <node pkg="my_first_pack" type="turtle_tf_listener" name="listener" />
</launch>
  • 启动查看效果
 roslaunch my_first_pack my_turtle.launch

posted @ 2021-07-23 17:30  09w09  阅读(602)  评论(0)    收藏  举报