ROS學習筆記(二):ROS通信種類和編程

ROS通信種類:話題(Topic)、服務(Service)、動作(Action)。

三種通信類型自定義消息類型時:
分別需要創建msg、srv、action文件夾並在文件夾下創建相應的 .msg 、.srv、.action文件;
通信程序文件均放在src文件夾下。
編譯時添加相應文件及依賴 如:

add_message_files(FILESPerson.msg)
generate_messages(DEPENDENCIES std_msgs)

具體步驟:

1.話題編程(Topic)

話題編程:

在這裏插入圖片描述
流程:
1.0 自定義話題消息

  • 定義msg文件;
    • 在msg文件夾中創建Person.msg文件
    • 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> 
  • 在CMakeList.txt添加編譯選項。
find_package(....... message_generation)

catkin_package(CATKIN_DEPENDS geometry_msgs roscpp rospy std_msgs message_runtime)

#下兩項區別於服務、動作編程
add_message_files(FILESPerson.msg)
generate_messages(DEPENDENCIES std_msgs)

1.1 創建發佈者(Talker)

程序流程:

  • 初始化ROS節點;
  • 創建話題。(向ROS_Master註冊節點信息);
  • 按照設置好的頻率循環發佈消息
/**
 * 該例程將發佈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;
}

1.2 創建訂閱者(Listener)

程序流程:

  • 初始化ROS節點;
  • 訂閱話題;
  • 循環等待話題消息>>當收到消息後進入回調函數>>在回調函數中完成消息處理。
/**
 * 該例程將訂閱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;
}

1.3 編譯
(在CMakeLists.txt添加編譯選項)

  • 設置需要編譯的代碼和生成的可執行文件;
  • 設置鏈接庫;
  • 設置依賴;
add_executable(talker src/talker.cpp)
target_link_libraries(talker ${catkin_LIBRARIES})
#add_dependencies(talker ${PROJECT_NAME}_generate_messages_cpp)

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

1.4.運行

  • 運行發佈者
$ rosrun learning_communication talker
  • 運行訂閱者
$ rosrun learning_communication listener

話題自定義消息類型

2.服務編程(Service)

服務編程在這裏插入圖片描述
流程:

2.0 自定義服務請求與應答

  • 定義srv文件;

    • 在srv文件夾中創建AddTwolnts.srv文件
    • AddTwolnts.srv內容:
      int64 a
      int64 b
      ---
      int64 sum
    
  • package.xml中添加功能包依賴;

  <build_depend>message_generation</build_depend>
  <exec_depend>message_runtime</exec_depend> 
  • 在CMakeList.txt添加編譯選項。
find_package(....... message_generation)
catkin_package(CATKIN_DEPENDS geometry_msgs roscpp rospy std_msgs message_runtime)
add_service_files(FILES AddTwoInts.srv)

2.1 創建服務器(Server)

程序流程:

  • 初始化ROS節點;
  • 創建服務器;
  • 循環等待服務請求,進入回調函數>>在回調函數內完成服務功能的處理,反饋應答數據。
/**
 * AddTwoInts Server
 */
 
#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.2 創建客戶端(Client)

程序流程:

  • 初始化ROS節點;
  • 創建客戶端;
  • 發佈服務請求數據;
  • 等待服務器(Server)處理後的應答結果。
/**
 * AddTwoInts Client
 */
 
#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;
}

2.3 編譯

  • 設置需要編譯的代碼和生成的可執行文件;
  • 設置鏈接庫;
  • 設置依賴;
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)

2.4.運行

  • 運行服務器節點
$ rosrun learning_communication server
  • 運行客戶端
$ rosrun learning_communication client

3.動作編程(Action)

  • Action與Service和Topic區別(相似於服務):帶有連續反饋;可以在任務過程終止運行。
    基本結構:
    在這裏插入圖片描述
    Action的接口:

  • goal : 發佈任務目標;

  • cancel : 請求取消任務;

  • status : 通知客戶端當前的狀態;

  • feedback : 週期反饋任務運行的監控數據;

  • result : 向客戶端發送任務的執行結果,只發布一次。

在這裏插入圖片描述
流程:

3.0 自定義動作消息

  • 定義action文件;
    • 在action文件夾中創建DoDishes.action文件
    • DoDishes.action內容:
# Define the goal
uint32 dishwasher_id  # Specify which dishwasher we want to use
---
# 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>
  • 在CMakeList.txt添加編譯選項。
find_package(catkin REQUIRED actionlib_msgs actionlib)
add_action_files(DIRECTORY action FILES DoDishes.action)
generate_messages(DEPENDENCIES actionlib_msgs)

3.1 創建動作服務器

程序流程:

  • 初始化ROS節點;
  • 創建動作服務器;
  • 啓動服務器,循環等待動作請求;
  • 進入回調函數>>在回調函數內完成動作服務功能的處理,反饋進度信息
#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 n;

    // 定義一個服務器
    Server server(n, "do_dishes", boost::bind(&execute, _1, &server), false);
    
    // 服務器開始運行
    server.start();

    ros::spin();

    return 0;
}

3.2 創建動作客戶端

程序流程:

  • 初始化ROS節點;
  • 創建客戶端;
  • 連接動作服務器;
  • 發送動作目標;
  • 根據不同類型服務端反饋處理回調函數。
#include <actionlib/client/simple_action_client.h>
#include "learning_communication/DoDishesAction.h"

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

// 當action完成後會調用該回調函數一次
void doneCb(const actionlib::SimpleClientGoalState& state,
        const learning_communication::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_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,  &doneCb, &activeCb, &feedbackCb);

    ros::spin();

    return 0;
}

3.3 編譯

  • 設置需要編譯的代碼和生成的可執行文件;
  • 設置鏈接庫;
  • 設置依賴;
add_executable(DoDishes_client src/DoDishes_client.cpp)
target_link_libraries( DoDishes_client ${catkin_LIBRARIES})
add_dependencies(DoDishes_client ${${PROJECT_NAME}_EXPORTED_TARGETS})

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

3.4.運行

  • 運行服務器節點
$ rosrun learning_communication DoDishes_server
  • 運行客戶端
$ rosrun learning_communication DoDishes_client
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章