ROS通信模式编程

一、话题、服务模式的ROS程序设计

1、话题编程

打开终端,在catkin_ws/src路径下新建learning_communication包:

 cd ~/catkin_ws/src/
 catkin_create_pkg learning_communication std_msgs roscpp rospy

在这里插入图片描述

功能包格式:
catkin_create_pkg package_name depend1 depend2 depend2
package_name:功能表名称
depend1、2、3:依赖项
std_msgs:包含常见消息类型
roscpp:使用C++实现ROS各种功能
rospy:使用python实现ROS各种功能

编译功能包:

 cd ~/catkin_ws
 catkin_make

编译成功会显示以下图片:
在这里插入图片描述
在 catkin_ws/src/learning_communication/src 中创建cpp文件,分别命名为talker.cpp 、listener.cpp:

$ cd ~/catkin_ws/src/learning_communication/src
$ touch talker.cpp
$ touch listener.cpp

在这里插入图片描述

编写发布者节点:

  • 初始化ROS节点;
  • 向ROS Master注册节点信息,包括发布的话题名和话题中的消息类型;
  • 按照一定频率循环发布消息。

打开talker.cpp文件,实现一个发布者:

$ gedit talker.cpp

代码:

/**
 * 该例程将发布chatter话题,消息类型String
 */
 
#include <sstream>
#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
  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;
}

编写订阅者节点:

  • 初始化ROS节点;
  • 订阅需要的话题;
  • 循环等待话题消息,接收到消息后进入回调函数;
  • 在回调函数中完成消息处理。

打开listener.cpp文件,实现一个订阅者:

$ gedit 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节点
  ros::init(argc, argv, "listener");
 
  // 创建节点句柄
  ros::NodeHandle n;
 
  // 创建一个Subscriber,订阅名为chatter的topic,注册回调函数chatterCallback
  ros::Subscriber sub = n.subscribe("chatter", 1000, chatterCallback);
 
  // 循环等待回调函数
  ros::spin();
 
  return 0;
}

编译代码:

  • 设置需要编译的代码和生成的可执行文件;
  • 设置链接库;
  • 设置依赖。

在 learning_communication 文件夹里面的 CMakeLists.txt 文件末尾加入几条语句:

$ cd ~/catkin_ws/src/learning_communication
$ gedit CMakeLists.txt
include_directories(include ${catkin_INCLUDE_DIRS})
 
add_executable(talker src/talker.cpp)
target_link_libraries(talker ${catkin_LIBRARIES})
 
add_executable(listener src/listener.cpp)
target_link_libraries(listener ${catkin_LIBRARIES})

在这里插入图片描述
这会生成两个可执行文件, talker 和 listener,具体在:~/catkin_ws/devel/lib/learning_communication 中

编译节点:

cd ~/catkin_ws/
catkin_make

编译成功:
在这里插入图片描述
运行节点:
启动ros:

$ roscore

打开两个新的终端分别输入:

$ cd  ~/catkin_ws/
$ source devel/setup.bash
$  rosrun learning_communication talker 
$ cd  ~/catkin_ws/
$ source devel/setup.bash
$  rosrun learning_communication listener 

在这里插入图片描述

2、服务编程

服务编程流程:

  • 创建服务器
  • 创建客户端
  • 添加编译选项
  • 运行可执行程序

创建一个自定义消息 msg:
(1)在 catkin_ws/src/learning_communication 文件中,创建一个文件夹,命名为 msg,在这个msg文件夹里面,创建一个 .msg 文件,命名为msg.msg:

$ cd ~/catkin_ws/src/learning_communication 
$ mkdir ./msg
$ cd ./msg
$ touch msg.msg

在这里插入图片描述
(2)打开msg.msg,复制如下内容,当然,你可以仿造上面的形式多增加几行以得到更为复杂的消息:

$ gedit msg.msg
int64 num
string first_name
string last_name
uint8 age
uint32 score

在这里插入图片描述

编辑 package.xml:
目的是确保msg文件被转换成为C++,Python和其他语言的源代码:
打开 catkin_ws/src/learning_communication 下的 package.xml
删除:

  <!--    <build_depend>message_generation</build_depend> -->

  <!--   <exec_depend>message_runtime</exec_depend> -->

这两行的 <!-- 、--> ,如下图所示:
在这里插入图片描述
编辑CMakeLists.txt:
设置find_packag函数:
增加对 message_generation 的依赖,这样就可以生成消息了
打开 catkin_ws/src/learning_communication 下的CMakeLists.txt
在find_package()函数中加入 message_generation:
在这里插入图片描述
设置运行依赖:
找到如下行,并加入 message_runtime
在这里插入图片描述
加入新消息名称:
在这里插入图片描述

编译:

cd ~/catkin_ws/
catkin_make

编译成功如下:
在这里插入图片描述
使用rosmsg:

rosmsg show msg

在这里插入图片描述
消息类型包含两部分:
learning_communication/msg-- 消息所在的位置

int64 num
string first_name
string last_name
uint8 age
uint32 score

创建一个服务 srv:
(1)在 catkin_ws/src/learning_communication 文件中,创建一个文件夹,命名为 srv,在这个srv文件夹里面,创建一个 .srv 文件,命名为 AddTwoInts.srv:

$ cd ~/catkin_ws/src/learning_communication
$ mkdir ./srv
$ cd ./srv
$ touch AddTwoInts.srv

在这里插入图片描述
(2)打开 AddTwoInts.srv,复制如下内容
srv文件分为请求和响应两部分,由’—'分隔,其中 a 和 b 是请求, 而sum 是响应。

$ gedit AddTwoInts.srv
int64 a
int64 b
---
int64 sum

在这里插入图片描述
编辑 package.xml

与创建msg方法相同

编辑CMakeLists.txt
设置find_packag函数: 同创建msg
设置运行依赖:同创建msg
加入新消息名称:
在这里插入图片描述

msg和srv都需要的步骤
去掉注释并附加上所有你消息文件所依赖的那些含有.msg文件的package
(这个例子是依赖std_msgs,不要添加roscpp,rospy),结果如下:
在这里插入图片描述
编译

cd ~/catkin_ws/
catkin_make

编译成功如下:
在这里插入图片描述
使用 rossrv

 rossrv show learning_communication/AddTwoInts

在这里插入图片描述

在 catkin_ws/src/learning_communication/src 中创建cpp文件,分别命名为add_two_ints_server.cpp 、add_two_ints_client.cpp:

$ cd ~/catkin_ws/src/learning_communication/src
$ touch add_two_ints_server.cpp
$ touch add_two_ints_client.cpp

在这里插入图片描述

编写服务器:

  • 初始化ROS节点;
  • 创建Server实例;
  • 循环等待服务请求,进入回调函数;
  • 在回调函数中完成服务功能的处理,并反馈应答数据。

打开add_two_ints_server.cpp ,实现一个服务器:

$ gedit add_two_ints_server.cpp 

代码:

#include "ros/ros.h"
#include "learning_communication/AddTwoInts.h"
 
// service回调函数,输入参数req,输出参数res
bool add(learning_communication::AddTwoInts::Request  &req,
         learning_communication::AddTwoInts::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;
}

在这里插入图片描述

编写客户端:

  • 初始化ROS节点;
  • 创建一个Client实例;
  • 发布服务请求数据;
  • 等待Server处理之后的应答结果。

打开add_two_ints_client.cpp,实现一个客户端:

$ gedit add_two_ints_client.cpp

代码:

#include <cstdlib>
#include "ros/ros.h"
#include "learning_communication/AddTwoInts.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_communication::AddTwoInts
  ros::ServiceClient client = n.serviceClient<learning_communication::AddTwoInts>("add_two_ints");
  
  // 创建learning_communication::AddTwoInts类型的service消息
  learning_communication::AddTwoInts 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;
}

在这里插入图片描述

编译代码:
在 learning_communication 文件夹里面的 CMakeLists.txt 文件末尾加入几条语句:
这会生成两个可执行文件,add_two_ints_server和add_two_ints_client具体在:~/catkin_ws/devel/lib/learning_communication 中

$ cd ~/catkin_ws/src/learning_communication
$ gedit CMakeLists.txt
add_executable(add_two_ints_server src/add_two_ints_server.cpp)
target_link_libraries(add_two_ints_server ${catkin_LIBRARIES})
add_dependencies(add_two_ints_server learning_communication_gencpp)
 
add_executable(add_two_ints_client src/add_two_ints_client.cpp)
target_link_libraries(add_two_ints_client ${catkin_LIBRARIES})
add_dependencies(add_two_ints_client learning_communication_gencpp)

在这里插入图片描述

编译节点:

cd ~/catkin_ws/
catkin_make

编译成功如下:
在这里插入图片描述
运行节点:
启动ros:

roscore

打开两个新的终端分别运行运行Service,Client(并附带两个加数):

cd  ~/catkin_ws/
source devel/setup.bash
rosrun learning_communication add_two_ints_server
cd  ~/catkin_ws/
source devel/setup.bash
rosrun learning_communication add_two_ints_client 123 321

在这里插入图片描述

二、ROS动作编程

什么是动作(action):

  • —种问答通信机制;
  • 带有连续反馈;
  • 可以在任务过程中止运行;
  • 基于ROS的消息机制实现。

在这里插入图片描述
Action的接口:

  • goal:发布任务目标;
  • cancel:请求取消任务;
  • status:通知客户端当前的状态;
  • feedback:周期反馈任务运行的监控数据;
  • result:向客户端发送任务的执行结果,只发布一次。
    在这里插入图片描述
    定义Action文件:
    在 catkin_ws/src/learning_communication 文件中,创建一个文件夹,命名为 action,在这个action文件夹里面,创建一个 .action 文件,命名为DoDishes.action:
$ cd ~/catkin_ws/src/learning_communication 
$ mkdir ./action
$ cd ./action
$ touch DoDishes.action

在这里插入图片描述
DoDishes.action代码如下:

$ gedit DoDishes.action
#定义目标信息
uint32 dishwasher_id
---
#定义结果信息
uint32 total_dishes_cleaned
---
#定义周期反馈的消息
float32 percent_complete

在这里插入图片描述
编辑 package.xml:
打开 catkin_ws/src/learning_communication 下的 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:
打开 catkin_ws/src/learning_communication 下的CMakeLists.txt:
在这里插入图片描述

添加 actionlib_msgs 和actionlib功能包,输入

add_action_files(DIRECTORY action FILES DoDishes.action)
generate_messages(DEPENDENCIES std_msgs actionlib_msgs)

在这里插入图片描述
编译:

$ cd ~/catkin_ws/
$ catkin_make

编译成功:
在这里插入图片描述
实现一个动作服务器:

  • 初始化ROS节点;
  • 创建动作服务器实例;
  • 启动服务器,等待动作请求;在回调函数中完成动作服务功能的处理,并反馈进度信息;
  • 动作完成,发送结束信息。

在 catkin_ws/src/learning_communication/src 中创建cpp文件,命名为DoDishes_server.cpp:

$ cd ~/catkin_ws/src/learning_communication/src
$ touch DoDishes_server.cpp
$ gedit DoDishes_server.cpp

DoDishes_server.cpp代码:

#include "ros/ros.h"
#include "actionlib/server/simple_action_server.h"
#include "learning_communication/DoDishesAction.h"
typedef actionlib::SimpleActionServer<learning_communication::DoDishesAction> Server;
// 收到action的goal后调用该回调函数
void execute(const learning_communication::DoDishesGoalConstPtr &goal, Server *as)
{
	ros::Rate r(1);
	learning_communication::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 hNode;
	// 定义一个服务器
	Server server(hNode, "do_dishes", boost::bind(&execute, _1, &server), false);
	// 服务器开始运行
	server.start();
	ros::spin();
	return 0;
}

实现一个动作客户端:

  • 初始化ROS节点;
  • 创建动作客户端实例;
  • 连接动作服务端;
  • 发送动作目标;
  • 根据不同类型的服务端反馈处理回调函数。

在 catkin_ws/src/learning_communication/src 中创建cpp文件,命名为DoDishes_client.cpp:

$ touch DoDishes_client.cpp
$ gedit DoDishes_client.cpp

DoDishes_client.cpp代码:

#include "ros/ros.h"
#include "actionlib/client/simple_action_client.h"
#include "learning_communication/DoDishesAction.h"
typedef actionlib::SimpleActionClient<learning_communication::DoDishesAction> Client;
// 当action完成后会调用该回调函数一次
void doneCallback(const actionlib::SimpleClientGoalState &state
	, const learning_communication::DoDishesResultConstPtr &result)
{
	ROS_INFO("Yay! The dishes are now clean");
	ros::shutdown();
}
// 当action激活后会调用该回调函数一次
void activeCallback()
{
	ROS_INFO("Goal just went active");
}
// 收到feedback后调用该回调函数
void feedbackCallback(const learning_communication::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_communication::DoDishesGoal goal;
	goal.dishwasher_id = 1;
	// 发送action的goal给服务端,并且设置回调函数
	client.sendGoal(goal, &doneCallback, &activeCallback, &feedbackCallback);
	ros::spin();
	return 0;
}

编译代码:
在 learning_communication 文件夹里面的 CMakeLists.txt 文件末尾加入几条语句:
这会生成两个可执行文件,DoDishes_server和DoDishes_client具体在:~/catkin_ws/devel/lib/learning_communication 中

$ cd ~/catkin_ws/src/learning_communication
$ gedit CMakeLists.txt
add_executable(DoDishes_server src/DoDishes_server.cpp)
target_link_libraries(DoDishes_server ${catkin_LIBRARIES})
add_dependencies(DoDishes_server ${${PROJECT_NAME}_EXPORTED_TARGETS})

add_executable(DoDishes_client src/DoDishes_client.cpp)
target_link_libraries(DoDishes_client ${catkin_LIBRARIES})
add_dependencies(DoDishes_client ${${PROJECT_NAME}_EXPORTED_TARGETS})

在这里插入图片描述
编译节点:

cd ~/catkin_ws/
catkin_make

编译成功如下:
在这里插入图片描述
运行节点:
打开三个终端分别执行:

roscore
rosrun learning_communication DoDishes_client
rosrun learning_communication DoDishes_server

在这里插入图片描述

三、分布式通信

ROS是一种分布式软件框架,节点之间通过松耦合的方式进行组合。

查看主机以及从机的ip地址:

ifconfig

主机ip地址:
10.60.241.234
在这里插入图片描述
从机ip地址:
10.60.241.250
在这里插入图片描述
验证主机和从机是否互通:
主机ping从机:
在这里插入图片描述
从机ping主机:
在这里插入图片描述
分布式通信(主机):
打开一个终端启动ros:

roscore

打开一个新的终端

export ROS_IP=10.60.241.234 (主机的IP)
export ROS_MASTER_URI=http://10.60.241.234:11311/
rosrun turtlesim turtlesim_node __name:=my_turtle

分布式通信(从机):
打开终端输入:

export ROS_IP=10.60.241.250 (该电脑ip)
export ROS_MASTER_URI=http://10.60.241.234:11311/(主机地址)
rosrun turtlesim turtle_teleop_key

配置成功,此时在从机移动键盘,主机的小海龟会实现移动
在这里插入图片描述

在这里插入图片描述

总结

本次通过对ROS话题、服务、动作模式编程程序设计以及并实践,在编译代码时需要设置需要编译的代码和生成的可执行文件、链接库以及依赖。在做分布式通信时将自己的电脑作为主机,室友的虚拟机作为从机来完成,在本次联系中遇到很多问题,例如需要配置虚拟机的ip,收获颇多。

https://blog.csdn.net/xiongmingkang/article/details/81203329

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值