ros通信机制整理
ros通信机制分为话题,服务和参数三部分,本文把动作action也列入其中,主要介绍话题topic,服务server和动作action:
话题通信
话题通信相对来说比较简单,就是一个talker和一个listener,前者发布话题,后者订阅相同名称的话题,并绑定一个回调函数,进阶的部分就是自定义消息类型
自定义消息类型
与src并列建立msg文件夹,自定义name.msg,并把数据类型写进去,比如自定义Num.msg:
string first_name
string last_name
uint8 age
uint32 score
写入后分别修改CMakeLists.txt和package.xml,修改内容如下:
- find_package中加入message_generation
add_message_files(FILES Num.msg) generate_messages( DEPENDENCIES std_msgs )
这样才能生成头文件- catkin_package中加入message_runtime
<build_depend>message_generation</build_depend>
<exec_depend>message_runtime</exec_depend>
catkin_make之后会在workspace的devel/include/项目名称 中找到Num.h
编写节点
在src中自定义sendtest.cpp 和 recvtest.cpp ,分别写入:(这里没有用到自定义msg,可以用)
sendtest.cpp
int main(int argc,char **argv)
{
ros::init(argc,argv,"sendtest");
ros::NodeHandle n;
ros::Publisher chatter_pub=n.advertise<std_msgs::String>("velocity",1000);
//定义一个话题名字为velocity,类型为std_msgs::String,名称和类型都可以根据自己的修改
ros::Rate loop_rate(10);
while(ros::ok())
{
std_msgs::String msg;
std::stringstream ss;
ss<<"1";
msg.data=ss.str();
ROS_INFO("%s",msg.data.c_str());
chatter_pub.publish(msg); //将类型为std_msgs::String的msg发布出去
ros::spinOnce();
//loop_rate.Sleep();
}
return 0;
}
recvtest.cpp
//定义回调函数
void chatterCallback(const std_msgs::String::ConstPtr& msg)
{
ROS_INFO("receive velocity: [%s]",msg->data.c_str());
}
int main(int argc, char **argv)
{
ros::init(argc,argv,"recvtest");
ros::NodeHandle n;
ros::Subscriber sub=n.subscribe("velocity",1000,chatterCallback);
//订阅名称为velocity的话题,并绑定名称为chatterCallback的回调函数
ros::spin();
return 0;
}
写完后CMakeLists中修改,生成可执行文件:
add_executable(${PROJECT_NAME}_send src/sendtest.cpp)
add_executable(${PROJECT_NAME}_recv src/recvtest.cpp)
## Rename C++ executable without prefix
## Add cmake target dependencies of the executable
## same as for the library above
add_dependencies(${PROJECT_NAME}_send ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS} commtest_generate_messages_cpp)
add_dependencies(${PROJECT_NAME}_recv ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS} commtest_generate_messages_cpp)
## Specify libraries to link a library or executable target against
target_link_libraries(${PROJECT_NAME}_send ${catkin_LIBRARIES})
target_link_libraries(${PROJECT_NAME}_recv ${catkin_LIBRARIES})
catkin_make之后rosrun就可以啦!
话题通信
在我看来话题通信比话题通信多了一个应答,不会一直发布一直订阅,占用内存,有应答的时候再去做处理
自定义server
在src并列建立文件夹srv,并建立AddTwoInts.srv,将如下内容写入:
int64 a
int64 b
---
int64 sum
分界线以上为client发的request,以下为server相应的response
同样的修改CMakeLists.txt和package.xml,修改内容如下:
- find_package中加入message_generation
add_service_files( FILES AddTwoInts.srv ) generate_messages( DEPENDENCIES std_msgs )
这样才能生成头文件- catkin_package中加入message_runtime
<build_depend>message_generation</build_depend>
<exec_depend>message_runtime</exec_depend>
catkin_make之后会在workspace的devel/include/项目名称 中找到三个头文件:
- AddTwoInts.h
- AddTwoIntsRequest.h
- AddTwoIntsResponse.h
编写服务
在src中自定义server.cpp 和 client.cpp,分别写入:
client.cpp
#include"ros/ros.h"
#include"std_msgs/String.h"
#include"srvclient/AddTwoInts.h" //头文件
int main(int argc, char **argv)
{
ros::init(argc,argv,"client");
ros::NodeHandle n;
ros::ServiceClient client = n.serviceClient<srvclient::AddTwoInts>("add_ints");
//client的类型为srvclient::AddTwoInts 就是工程名称::srv名称
//client的名称为add_ints
srvclient::AddTwoInts add_srv; //对add_srv的request赋值
add_srv.request.a = 5;
add_srv.request.b = 4;
if(client.call(add_srv)){ //类似回调函数,client.call
ROS_INFO("SERVICE SUCCESS RESULT: %d", add_srv.response.sum);
}
else{
ROS_INFO("SERVICE FAIL");
return 1;
}
ros::spin();
return 0;
}
server.cpp
#include"ros/ros.h"
#include"std_msgs/String.h"
#include"srvclient/AddTwoInts.h"
//add函数的定义,传进来的参数一个是request类型的引用,一个是response类型的引用
bool add(srvclient::AddTwoInts::Request &req,
srvclient::AddTwoInts::Response &res)
{
res.sum = req.a + req.b;
ROS_INFO("a=%d, b=%d a+b=%d", (int)req.a, (int)req.b, (int)res.sum);
return true;
}
int main(int argc, char **argv)
{
ros::init(argc,argv,"server");
ros::NodeHandle n;
ros::ServiceServer server = n.advertiseService("add_ints", add);
//server的类型为srvclient::AddTwoInts 就是工程名称::srv名称
//server的名称为add_ints 并绑定add函数
ROS_INFO("add 2 ints. ");
ros::spin();
return 0;
}
修改CMakeLists
add_executable(server src/srv.cpp)
add_executable(client src/client.cpp)
add_dependencies(server srvclient_generate_message_cpp)
add_dependencies(client srvclient_generate_message_cpp)
target_link_libraries(server ${catkin_LIBRARIES})
target_link_libraries(client ${catkin_LIBRARIES})
就可以啦!
动作通信
在我看来,动作通信比服务通信多了反馈,知道过程中的情况
client向server发布目标和取消任务;
server向client发布当前状态,实时反馈和任务执行的最终结果
自定义action
在src并列建立文件夹action 建立readbook.action 写入:
#define the goal 定义目标
uint32 total_pages
---
#define the result 定义结果
bool is_finish
---
#define a feedback message 定义反馈消息
uint32 reading_page
同样修改:
- find_package中加入actionlib actionlib_msgs
add_action_files( FILES readbook.action ) generate_messages( DEPENDENCIES actionlib_msgs )
这样才能生成头文件- catkin_package中加入actionlib和 actionlib_msgs
<build_depend>actionlib</build_depend> <build_depend>actionlib_msgs</build_depend>
<exec_depend>actionlib</exec_depend> <exec_depend>actionlib_msgs</exec_depend>
应该就可以了,会生成好几个:- readbookAction.h
- readbookActionFeedback.h
- readbookActionGoal.h
- readbookActionResult.h
- readbookFeedback.h
- readbookGoal.h
- readbookResult.h
编写动作
在src中定义read_book_server.cpp和read_book_client.cpp,分别写入:
read_book_client.cpp定义Client客户端,发出action请求:
#include <ros/ros.h>
#include <actionlib/client/simple_action_client.h> //注意头文件
#include "actionlib_test/readbookAction.h" //包含的是readbookAction.h
//完成action后,传入的两个参量,类型分别是state和result
void donecb(const actionlib::SimpleClientGoalState& state,
const actionlib_test::readbookResultConstPtr& result){
ROS_INFO("FINISH READING!");
ros::shutdown();
}
//激活后调用
void activecb(){
ROS_INFO("Goal is active! Begin to Read!");
}
//收到feedback后调用该回调函数,传入的类型为feedback
void feedbackcb(const actionlib_test::readbookFeedbackConstPtr& feedback){
ROS_INFO("Reading page: %d", feedback->reading_page);
}
int main(int argc, char** argv)
{
ros::init(argc, argv, "read_book_client");
actionlib::SimpleActionClient<actionlib_test::readbookAction> client("read_book",true);
// 定义了一个客户端,注意其类型
ROS_INFO("WAIT FOR ACTION SERVER TO START");
//等待服务器
client.waitForServer();
ROS_INFO("Action server started");
//创建一个action的目标goal,注意类型
actionlib_test::readbookGoal goal;
goal.total_pages = 10;
//发送action的goal给服务器,并设置回调函数,有三个:完成 激活 反馈
client.sendGoal(goal, &donecb, &activecb, &feedbackcb);
ros::spin();
return 0;
}
read_book_server.cpp实现服务器节点,完成action,并反馈
#include <ros/ros.h>
#include <actionlib/server/simple_action_server.h>
#include "actionlib_test/readbookAction.h"
//定义excute函数,传入参量一个为goal,一个为server类型的指针,as(action server
void execute(const actionlib_test::readbookGoalConstPtr& goal,
actionlib::SimpleActionServer<actionlib_test::readbookAction>* as)
{
ros::Rate r(1);
actionlib_test::readbookFeedback feedback; //定义反馈
ROS_INFO("Begin to read %d pages.", goal->total_pages);
for(int i =0; i < goal->total_pages ; i++){
feedback.reading_page = i;
as->publishFeedback(feedback); //由于 ros::Rate r(1)所以以1Hz发布反馈
r.sleep();
}
//action完成后,设置成功
ROS_INFO("All pages is read.");
as->setSucceeded();
}
int main(int argc, char** argv)
{
ros::init(argc, argv, "read_book_server");
ros::NodeHandle n;
actionlib::SimpleActionServer<actionlib_test::readbookAction> server(n, "read_book", boost::bind(&execute, _1, &server), false);
//定义一个server服务器,,绑定excute函数
server.start();//server开始运行
ros::spin();
return 0;
}
修改CMakeLists
add_executable(${PROJECT_NAME}_client src/read_book_client.cpp)
add_executable(${PROJECT_NAME}_server src/read_book_server.cpp)
## Add cmake target dependencies of the executable
## same as for the library above
add_dependencies(${PROJECT_NAME}_client ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})
add_dependencies(${PROJECT_NAME}_server ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})
## Specify libraries to link a library or executable target against
target_link_libraries(${PROJECT_NAME}_client ${catkin_LIBRARIES} )
target_link_libraries(${PROJECT_NAME}_server ${catkin_LIBRARIES} )
就可以啦!