一、创建工作空间
1.什么是工作空间
工作空间(workspace)是一个存放工程开发相关文件的文件夹。
src:代码空间(Source Space)
build:编译空间(Build Space)
devel:开发空间(Development Space)
install:安装空间(Install Space)
2.创建工作空间 catkin_ws
mkdir -p ~/catkin_ws/src
cd ~/catkin_ws/src
catkin_init_workspace
3.编译工作空间 catkin_make
cd ~/catkin_ws/
catkin_make
4.设置环境变量
echo "source ~/catkin_ws/devel/setup.bash" >> ~/.bashrc
source ~/.bashrc
5.检查环境变量
echo $ROS_PACKAGE_PATH
6.创建功能包
cd ~/catkin_ws/src/
catkin_create_pkg learning_communication std_msgs roscpp rospy
std_msgs:包含常见消息类型
roscpp:使用C++实现ROS各种功能
rospy:使用python实现ROS各种功能
7.编译功能包
cd ~/catkin_ws
catkin_make
编译成功:
二.ROS通信编程
创建两个节点,一个收,一个发。
先在 catkin_ws/src/learning_communication/src 功能包下建两个 .cpp 文件,分别命名talker.cpp 、listener.cpp
(1)编写发布器节点
打开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;
}
(2)编写订阅器节点
打开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;
}
(3)编译节点
在 learning_communication 文件夹里面的 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
(4)运行节点
启动ROS
roscore
在不同终端中输入:
rosrun learning_communication talker
rosrun learning_communication listener
可以看到两个终端显示如下,表示一个发,一个收:
(5)创建一个自定义消息msg
(1)在 catkin_ws/src/learning_communication 文件中,创建一个文件夹,命名为 msg,在这个msg文件夹里面,创建一个 .msg 文件,命名为msg.msg
(2)打开msg.msg,复制如下内容,当然,你可以仿造上面的形式多增加几行以得到更为复杂的消息
int64 num
string first_name
string last_name
uint8 age
uint32 score
(3)编辑 package.xml
目的是确保msg文件被转换成为C++,Python和其他语言的源代码:
打开 catkin_ws/src/learning_communication 下的 package.xml
删除:
<!-- <build_depend>message_generation</build_depend> -->
<!-- <exec_depend>message_runtime</exec_depend> -->
(4)编辑CMakeLists.txt
设置find_packag函数:
增加对 message_generation 的依赖,这样就可以生成消息了
打开 catkin_ws/src/learning_communication 下的CMakeLists.txt
在find_package()函数中加入 message_generation
设置运行依赖:
找到如下行,并加入 message_runtime和新消息名称 ,如下图:
(5)编译
cd ~/catkin_ws/
catkin_make
(6)使用 rosmsg
rosmsg show msg
2.服务编程
创建一个服务 srv
(1)在 catkin_ws/src/learning_communication 文件中,创建一个文件夹,命名为 srv,在这个srv文件夹里面,创建一个 .srv 文件,命名为 AddTwoInts.srv
(2)打开 AddTwoInts.srv,复制如下内容
srv文件分为请求和响应两部分,由’—'分隔,其中 a 和 b 是请求, 而sum 是响应。
(3)编辑 package.xml
同创建msg
(4)编辑CMakeLists.txt
设置find_packag函数: 同创建msg
设置运行依赖:同创建msg
加入新消息名称:
(5)编译
cd ~/catkin_ws/
catkin_make
(6)使用 rossrv
rossrv show learning_communication/AddTwoInts
(1)编写Service节点
打开add_two_ints_server.cppp文件,复制如下内容
#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;
}
(2)编写Client节点
打开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;
}
(3)编译节点
在 learning_communication 文件夹里面的 CMakeLists.txt 文件末尾加入几条语句:
这会生成两个可执行文件, 在:~/catkin_ws/devel/lib/learning_communication 中
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
(4)运行节点
启动ROS
roscore
运行Service
rosrun learning_communication add_two_ints_server
运行Client,并附带两个加数
rosrun learning_communication add_two_ints_client 1314 521
二.动作编程
什么是动作(action)
一种问答通信机制
带有连续反馈
可以在任务过程中止运行
基于ROS的消息机制实现
Action的接口
goal:发布任务目标 cancel:请求取消任务
status:通知客户端当前的状态
feedback:周期反馈任务运行的监控数据
result:向客户端发送任务的执行结果,只发布一次
(1)定义action文件
cd ~/catkin_ws/src/learning_communication
mkdir action
cd action
gedit DoDishes.action
DoDishes.action代码如下
#定义目标信息
uint32 dishwasher_id
---
#定义结果信息
uint32 total_dishes_cleaned
---
#定义周期反馈的消息
float32 percent_complete
(2)在package.xml中添加功能包依赖
<build_depend>actionlib</build_depend>
<build_depend>actionlib_msgs</build_depend>
<exec_depend>actionlib</exec_depend>
<exec_depend>actionlib_msgs</exec_depend>
(3)编辑CMakeLists.txt:
(4)编译
$ cd ~/catkin_ws/
$ catkin_make
编译成功如下
(6)实现一个动作服务器
初始化ROS节点
创建动作服务器实例
启动服务器,等待动作请求
在回调函数中完成动作服务功能的处理,并反馈进度信息
动作完成,发送结束信息
cd ~/catkin_ws/src/learning_communication/src
gedit DoDishes_server.cpp
gedit DODishes_client.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;
}
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;
}
设置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_action DoDishes_client
rosrun learning_action DoDishes_server
三.分布式通信
查看主机以及从机的ip地址:
ifconfig
主机ip地址:10.60.254.75
从机ip地址:10.60.241.250
验证主机和从机是否互通:
主机ping从机:
分布式通信(主机):
打开一个终端启动ros:
roscore
打开一个新的终端
export ROS_IP=10.60.254.75 (主机的IP)
export ROS_MASTER_URI=http://10.60.254.75:11311/
rosrun turtlesim turtlesim_node __name:=my_turtle
分布式通信(从机):
打开终端输入:
export ROS_IP=10.60.241.250 (该电脑ip)
export ROS_MASTER_URI=http://10.60.254.75:11311/主机地址)
rosrun turtlesim turtle_teleop_key
配置成功,此时在从机移动键盘,主机的小海龟会实现移动
四.总结
这次实验使我了解了ROS通信模式编程中的话题编程,服务编程和动作编程,收获了很多。
参考链接:https://blog.csdn.net/scout11123/article/details/129621970?spm=1001.2014.3001.5502