ROS工作空间和通信编程

一. 什么是工作空间:

我们开发时会把所有的代码文件、解释文档等各种东西放在一个文件夹下,这个存放工程开发所涉及到的文件夹可以称作是一个工作空间。

工作空间
代码空间src: 存放所有功能包源码的一个文件夹
编译空间build: 存放在编译过程中产生的中间文件
开发空间devel:所有编译生成的可执行文件, 环境变量配置的脚本
安装空间install: 类似devel里边的文件,也是一些可执行文件
二. 如何创建完整的工作空间:
  • 创建工作空间文件夹
    进入到任意准备放工作空间的文件夹界面,右键点击打开终端
    创建工作空间文件夹(文件夹名称:catkin_ws):
mkdir catkin_ws

进入工作空间文件夹:

cd catkin_ws

创建代码空间文件夹(代码空间文件夹名称:src)

mkdir src
  • 将文件夹初始化为ROS的工作空间文件夹
    进入到代码空间src文件夹:
cd src

初始化工作空间:

catkin_init_workspace

初始化后在src文件夹下会生成一个CMakeLists.txt的文件

  • 编译这个工作空间
    回到工作空间的根文件夹(catkin_ws)下:
cd ..

编译工作空间:

catkin_make

编译后会出现编译空间build文件夹和开发空间devel文件夹

  • 设置环境变量
    为了让linux系统能够找到功能包,需要设置环境变量,让系统知道功能包的位置:
source devel/setup.zsh

但是这样设置只是在当前打开的终端内有用,再另外打开终端后会失效,所以最好在终端的配置文件中加载这个命令,这样就可以在进入其他终端环境,系统依旧能够功能包的相对位置。
进入配置文件:

vi ~/.bashrc

按下键Insert进入编辑模式
在最下边添加:

source ~/catkin_ws/devel/setup.bash

按下键Esc,再直接输入, 从而到文件们最后一行
在写入 x , 按下键Enter,退出vi模式
再重新运行以下配置文件:

source ~/.bashrc

如果出错,再vi进入配置文件看看里边是不是多写了什么东西。
检查是否配置好环境变量了:

echo $ROS_PACKAGE_PATH
三. 如何创建功能包:
  • 进入代码空间src文件夹:
cd src

创建功能包:

格式:catkin_create_pkg package_name depend1 depend2 depend3

例如创建功能包名称learning,依赖于ros的标准信息格式,c++,python:

catkin_create_pkg learning std_msgs rospy roscpp

这时候在src文件夹中就出现了learning功能包的文件夹,打开里边四项内容:
include:功能包需要的一些头文件
src:功能报内的相关源码文件
CMakeLists.txt:放置我们如何去编译这个功能包的一些编译选项,比方我们现在写了一个c++的程序文件,我们必须要在这个文件中添加一些语句,使得在编译时能够将我们的代码文件生成可执行文件。另外里边还包含了我们所依赖的一些package,都在里边有所编译。
package.xml:描述这个功能包的具体信息,功能包的名字,版本号,列出依赖那些其他的功能包


注意事项:在同一个工作空间下不能创建两个名字相同的功能包。


  • 编译这个功能包:
    回到工作空间根目录下:
cd ..

再编译工作空间:

catkin_make

在编译的信息当中我们可以看到我们刚才已经创建好的功能包:

-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-- ~~  traversing 1 packages in topological order:
-- ~~  - learning
-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  • 关于环境变量的查找
    –工作空间的路径依次会存储到ROS_PACKAGE_PATH中
    –新创建的环境变量都会放置在所有环境变量的最前端
    –查找时,ROS会优先从前边往后开始查找环境变量
四. 如何创建话题
  • 创建话题的发布者
    在catkin_ws /src /learning /src 文件夹下创建c++的程序文件,这里命名为talker.cpp
    在该文件夹下右键打开终端:
touch talker.cpp

再打开在里边添加程序:

//发布话题:chatter  消息类型:String

#include<sstream>           //字符串类的头文件
#include<ros/ros.h>
#include "std_msgs/String.h" //ROS字符串型消息的头文件

int main(int argc, char **argv)
{
    //ROS节点初始化,节点的名称就是talker
    ros::init(argc,argv,"talker");
    
    //创建节点句柄
    ros::NodeHandle n;
    
    //创建一个publisher,发布名为chatter的topic,消息的类型为std_msgs::String
    ros::Publisher chatter_pub = n.advertise<std_msgs::String>("chatter",1000);
    
    //设置循环的频率
    ros::Rate loop_rate(10);
    
    int count = 0;
    while(ros::ok())
    {
         //初始化std_msgs::String类型的信息
         std_msgs::String msg;
         std::stringstream ss;
         ss<<"hello world"<<count;
         msg.data = ss.str();
         
         //发布消息
         ROS_INFO("%s",msg.data.c_str());
         chatter_pub.publish(msg);
         
         //循环等待回调函数
         ros::spinOnce();
         
         //按照循环频率延时
         loop_rate.sleep();
         ++count;
    }
    
    return 0; 
}
  • 创建话题的订阅者
    在catkin_ws /src /learning /src 文件夹下创建c++的程序文件,这里命名为listener.cpp
    在该文件夹下右键打开终端:
touch listener.cpp

再打开在里边添加程序:

//订阅chatter话题,消息类型:String

#include<ros/ros.h>
#include"std_msgs/String.h"

//接收到消息把消息打印出来的函数消息回调函数
void chatterCallback(const std_msgs::String::ConstPtr& msg)
{
   ROS_INFO("I heard: [%s]",msg->data.c_str());
}

int main(int argc, char **argv)
{
   //初始化ROS节点,节点名称listener
   ros::init(argc,argv,"listener");
   
   //创建节点句柄
   ros::NodeHandle n;
   
   //创建一个Subscriber,订阅名为chatter的topic,注册回调函数chatterCallback
   ros::Subscriber sub = n.subscribe("chatter",1000,chatterCallback);
   
   //循环等待回调函数
   ros::spin();
   
   return 0; 
}
  • 编译代码
    打开catkin_ws /src /learning /src 文件夹的CMakeLists.txt文件,在其中添加:
add_executable(talker src/talker.cpp)
target_link_libraries(talker ${catkin_LIBRARIES})

add_executable(listener src/listener.cpp)
target_link_libraries(listener ${catkin_LIBRARIES})
add_executable是为了将代码生成执行的文件
格式:add_executable(project_name src/program_name.cpp )

回到catkin_ws文件夹下的终端进行编译:

catkin_make

如果编译有错误的话可能会是代码错误,比方说字母的大小写等都会编译不成功。

与我们设置的是一样的。

五. 如何自定义话题消息类型
  • 定义msg文件
    回到功能包learning的文件夹中,打开终端,新建一个msg文件夹,一般自定义话题消息都会放在这个文件夹下,约定俗成的。
mkdir msg

在msg文件夹下再新建一个msg文件,命名Person

cd msg
touch Person.msg

在里边添加自己想要定义的消息类型:

string name
uint8 sex
uint8 age

uint8 unknown = 0
uint8 male    = 1
uint8 female  = 2
  • 添加功能包依赖:
    打开package.xml文件,加入代码:
  <build_depend>message_generation</build_depend>
  <exec_depend>message_runtime</exec_depend>
  • 添加编译选项
    打开CmakeLists.txt文件,找到find_package语句,在里边添加:
message_generation

添加后如下:

find_package(catkin REQUIRED COMPONENTS
  roscpp
  rospy
  std_msgs
  message_generation
)

找到catkin_package语句,修改成这个样子(就是去掉那一行注视符,添加了一个message_runtime):

catkin_package(
#  INCLUDE_DIRS include
#  LIBRARIES learning
   CATKIN_DEPENDS roscpp rospy std_msgs message_runtime
#  DEPENDS system_lib
)

再在文件中添加语句:

 add_message_files(
   FILES
   Person.msg
 )
 
 generate_messages(
   DEPENDENCIES
   std_msgs
 )

回到catkin_ws的终端进行编译:

catkin_make

如果有错误,再回头看是不是哪一个步骤有错误,正常来说会正常编译。
查看msg话题消息是否自定义成功:

rosmsg show Person

显示信息:

liuhuan@liuhuan-G5-5587:~/catkin_ws$ rosmsg show Person
[learning/Person]:
uint8 unknown=0
uint8 male=1
uint8 female=2
string name
uint8 sex
uint8 age

表明自定义成功。

六. 如何自定义服务请求和应答
  • 定义srv文件
    回到功能包learning的文件夹中,打开终端,新建一个srv文件夹,一般自定义服务请求和应答都会放在这个文件夹下,约定俗成的。
mkdir srv

在srv文件夹下再新建一个srv文件,命名MathAdd

cd srv
touch MathAdd.srv

在里边添加自己想要自定义的服务请求和应答类型:

int64 a
int64 b
---
int64 sum

三个横线把上下分为两大部分,上边是请求数据,下边是应答数据

  • 添加功能包依赖:
    打开package.xml文件,加入代码(和自定义话题是一样的):
  <build_depend>message_generation</build_depend>
  <exec_depend>message_runtime</exec_depend>
  • 添加编译选项
    打开CmakeLists.txt文件,找到find_package语句,在里边添加(和自定义话题是一样的):
message_generation

添加后如下:

find_package(catkin REQUIRED COMPONENTS
  roscpp
  rospy
  std_msgs
  message_generation
)

找到catkin_package语句,修改成这个样子(就是去掉那一行注视符,添加了一个message_runtime)(和自定义话题是一样的):

catkin_package(
#  INCLUDE_DIRS include
#  LIBRARIES learning
   CATKIN_DEPENDS roscpp rospy std_msgs message_runtime
#  DEPENDS system_lib
)

再在文件中添加语句:

  add_service_files(
   FILES
   MathAdd.srv
 )
 
 generate_messages(
   DEPENDENCIES
   std_msgs
 )

注意事项:add_service_files 一定要在 generate_messages的前边,如果既有自定义话题,又有自定义的服务,一定要注意这一点。


回到catkin_ws的终端进行编译:

catkin_make

如果有错误,再回头看是不是哪一个步骤有错误,正常来说会正常编译。
查看msg话题消息是否自定义成功:

rossrv show MathAdd

显示信息:

liuhuan@liuhuan-G5-5587:~/catkin_ws$ rossrv show MathAdd
[learning/MathAdd]:
int64 a
int64 b
---
int64 sum

表明自定义成功。

七. 如何创建服务
  • 创建服务器:
    在catkin_ws /src /learning /src 文件夹里边创建MathAddServer的c++文件
    在该文件夹下打开终端:
touch MathAddServer.cpp

在里边添加代码:

/**
 * MathAddServer
 */
 
#include "ros/ros.h"
#include "learning/MathAdd.h"

// service回调函数,输入参数req,输出参数res
bool add(learning::MathAdd::Request  &req,
         learning::MathAdd::Response &res)
{
  // 将输入参数中的请求数据相加,结果放到应答变量中
  res.sum = req.a + req.b;
  ROS_INFO("request: x=%ld, y=%ld", (long int)req.a, (long int)req.b);
  ROS_INFO("sending back response: [%ld]", (long int)res.sum);
  
  return true;
}

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

  // 创建一个名为add_two_ints的server,注册回调函数add()
  ros::ServiceServer service = n.advertiseService("add_two_ints", add);
  
  // 循环等待回调函数
  ROS_INFO("Ready to add two ints.");
  ros::spin();

  return 0;
}
  • 创建客户端:
    在catkin_ws /src /learning /src 文件夹里边创建MathAddClient的c++文件
    在该文件夹下打开终端:
touch MathAddClient.cpp

在里边添加代码:

/**
 * MathAddClient
 */
 
#include <cstdlib>
#include "ros/ros.h"
#include "learning/MathAdd.h"

int main(int argc, char **argv)
{
  // ROS节点初始化
  ros::init(argc, argv, "add_two_ints_client");
  
  // 从终端命令行获取两个加数
  if (argc != 3)
  {
    ROS_INFO("usage: add_two_ints_client X Y");
    return 1;
  }

  // 创建节点句柄
  ros::NodeHandle n;
  
  // 创建一个client,请求add_two_int service,service消息类型是learning::MathAdd
  ros::ServiceClient client = n.serviceClient<learning::MathAdd>("add_two_ints");
  
  // 创建learning::MathAdd类型的service消息
  learning::MathAdd srv;
  srv.request.a = atoll(argv[1]);
  srv.request.b = atoll(argv[2]);
  
  // 发布service请求,等待加法运算的应答结果
  if (client.call(srv))
  {
    ROS_INFO("Sum: %ld", (long int)srv.response.sum);
  }
  else
  {
    ROS_ERROR("Failed to call service add_two_ints");
    return 1;
  }

  return 0;
}
  • 编译代码
    打开catkin_ws /src /learning /src 文件夹的CMakeLists.txt文件,在其中添加:
add_executable(MathAddServer src/MathAddServer.cpp)
target_link_libraries(MathAddServer ${catkin_LIBRARIES})
add_dependencies(MathAddServer ${PROJECT_NAME}_generate_messages_cpp)

add_executable(MathAddClient src/MathAddClient.cpp)
target_link_libraries(MathAddClient ${catkin_LIBRARIES})
add_dependencies(MathAddClient ${PROJECT_NAME}_generate_messages_cpp)

可以在catkin_ws /devel /lib /learning中看到生成的可执行文件。
回到原来catkin_ws的终端进行编译:

catkin_make
  • 进行MathAddServer和MathAddClient的通信
    新建终端,启动节点管理器:
roscore

新建终端,运行MathAddServer:

rosrun learning MathAddServer

再新建终端,运行MathAddClient:

rosrun learning MathAddClient 1 2

可以看到实现的两个数字的加法。

八. 什么是动作编程
  • 什么是动作(action):
    一种具有连续反馈的问答通信机制,比如我们在做机器人时候,机器人能够实时回复自身的状态
  • 动作的接口:
goal:发布任务目标
cancel:请求取消任务
status:通知客户端当前状态
feedback:周期反馈任务运行的监控数据
result:向客户端发送任务的执行结果,只发布一次
Action客户端
Action服务器
九. 如何自定义动作消息:
  • 定义action文件
    回到功能包learning的文件夹中,打开终端,新建一个action文件夹,一般自定义服务请求和应答都会放在这个文件夹下,约定俗成的。
mkdir action

在srv文件夹下再新建一个action文件,命名DoDishes

cd srv
touch DoDishes.action

在里边添加自己想要自定义的动作消息类型:

# define the goal
uint32 dishwasher_id
---
# define the result
uint32 total_dishes_cleaned
---
# define a feedback message
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>
  • 添加编译选项
    打开CmakeLists.txt文件,找到find_package语句,在里边添加(和自定义话题是一样的):
actionlib
actionlib_msgs

添加后如下:

find_package(catkin REQUIRED COMPONENTS
  roscpp
  rospy
  std_msgs
  message_generation
  actionlib
  actionlib_msgs
)

再在文件中添加语句(必须在generate_messages语句的前边):

  add_action_files(
   FILES
   DoDishes.action
 )

找到generate_messages语句,在里边添加

actionlib_msgs

添加好后是这个样子的:

 generate_messages(
   DEPENDENCIES
   std_msgs
   actionlib_msgs
 )

回到catkin_ws的终端进行编译:

catkin_make

如果有错误,再回头看是不是哪一个步骤有错误,正常来说会正常编译。
查看msg话题消息是否自定义成功:

rossrv show MathAdd

显示信息:

liuhuan@liuhuan-G5-5587:~/catkin_ws$ rossrv show MathAdd
[learning/MathAdd]:
int64 a
int64 b
---
int64 sum

表明自定义成功。

十. 如何实现动作编程
  • 创建动作服务器:
    在catkin_ws /src /learning /src 文件夹里边创建DoDishesServer的c++文件
    在该文件夹下打开终端:
touch DoDishesServer.cpp

在里边添加代码:

#include <ros/ros.h>
#include <actionlibrver/simple_action_server.h>
#include "learning/DoDishesAction.h"

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

// 收到action的goal后调用该回调函数
void execute(const learning::DoDishesGoalConstPtr& goal, Server* as)
{
    ros::Rate r(1);
    learning::DoDishesFeedback feedback;

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

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

    // 当action完成后,向客户端返回结果
    ROS_INFO("Dishwasher %d finish working.", goal->dishwasher_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;
}
  • 创建action客户端:
    在catkin_ws /src /learning /src 文件夹里边创建DoDishesClient的c++文件
    在该文件夹下打开终端:
touch DoDishesClient.cpp

在里边添加代码:

#include <actionlib/client/simple_action_client.h>
#include "learning/DoDishesAction.h"

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

// 当action完成后会调用该回调函数一次
void doneCb(const actionlib::SimpleClientGoalState& state,
        const learning::DoDishesResultConstPtr& result)
{
    ROS_INFO("Yay! The dishes are now clean");
    ros::shutdown();
}

// 当action激活后会调用该回调函数一次
void activeCb()
{
    ROS_INFO("Goal just went active");
}

// 收到feedback后调用该回调函数
void feedbackCb(const learning::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
    learning::DoDishesGoal goal;
    goal.dishwasher_id = 1;

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

    ros::spin();

    return 0;
}
  • 编译代码
    打开catkin_ws /src /learning /src 文件夹的CMakeLists.txt文件,在其中添加:
add_executable(DoDishesServer src/DoDishesServer.cpp)
target_link_libraries(DoDishesServer ${catkin_LIBRARIES})
add_dependencies(DoDishesServer ${PROJECT_NAME}_generate_messages_cpp)

add_executable(DoDishesClient src/DoDishesClient.cpp)
target_link_libraries(DoDishesClient ${catkin_LIBRARIES})
add_dependencies(DoDishesClient ${PROJECT_NAME}_generate_messages_cpp)

可以在catkin_ws /devel /lib /learning中看到生成的可执行文件。
回到原来catkin_ws的终端进行编译:

catkin_make
  • 进行DoDishesServer和DoDishesClient的通信
    新建终端,启动节点管理器:
roscore

新建终端,运行DoDishesServer:

rosrun learning DoDishesServer

再新建终端,运行DoDishesClient:

rosrun learning DoDishesClient

本文章是自己在学习胡春旭老师资料期间所做的学习笔记,特别感谢胡春旭老师的讲解。本问章中的很多内容还是跟着胡老师的资料内容是一样的,如果有侵权的地方会立即删除。如果上边错误的地方,还请大家多多指导,一起学习交流!

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值