2.4 動(dòng)作通信
場(chǎng)景
關(guān)于action通信,我們先從之前導(dǎo)航中的應(yīng)用場(chǎng)景開(kāi)始介紹,描述如下:
機(jī)器人導(dǎo)航到某個(gè)目標(biāo)點(diǎn),此過(guò)程需要一個(gè)節(jié)點(diǎn)A發(fā)布目標(biāo)信息,然后一個(gè)節(jié)點(diǎn)B接收到請(qǐng)求并控制移動(dòng),最終響應(yīng)目標(biāo)達(dá)成狀態(tài)信息。
乍一看,這好像是服務(wù)通信實(shí)現(xiàn),因?yàn)樾枨笾幸狝發(fā)送目標(biāo),B執(zhí)行并返回結(jié)果,這是一個(gè)典型的基于請(qǐng)求響應(yīng)的應(yīng)答模式,不過(guò),如果只是使用基本的服務(wù)通信實(shí)現(xiàn),存在一個(gè)問(wèn)題:導(dǎo)航是一個(gè)過(guò)程,是耗時(shí)操作,如果使用服務(wù)通信,那么只有在導(dǎo)航結(jié)束時(shí),才會(huì)產(chǎn)生響應(yīng)結(jié)果,而在導(dǎo)航過(guò)程中,節(jié)點(diǎn)A是不會(huì)獲取到任何反饋的,從而可能出現(xiàn)程序"假死"的現(xiàn)象,過(guò)程的不可控意味著不良的用戶體驗(yàn),以及邏輯處理的缺陷(比如:導(dǎo)航中止的需求無(wú)法實(shí)現(xiàn))。更合理的方案應(yīng)該是:導(dǎo)航過(guò)程中,可以連續(xù)反饋當(dāng)前機(jī)器人狀態(tài)信息,當(dāng)導(dǎo)航終止時(shí),再返回最終的執(zhí)行結(jié)果。在ROS中,該實(shí)現(xiàn)策略稱之為:action 通信。
概念
動(dòng)作通信適用于長(zhǎng)時(shí)間運(yùn)行的任務(wù)。就結(jié)構(gòu)而言動(dòng)作通信由目標(biāo)、反饋和結(jié)果三部分組成;就功能而言動(dòng)作通信類(lèi)似于服務(wù)通信,動(dòng)作客戶端可以發(fā)送請(qǐng)求到動(dòng)作服務(wù)端,并接收動(dòng)作服務(wù)端響應(yīng)的最終結(jié)果,不過(guò)動(dòng)作通信可以在請(qǐng)求響應(yīng)過(guò)程中獲取連續(xù)反饋,并且也可以向動(dòng)作服務(wù)端發(fā)送任務(wù)取消請(qǐng)求;就底層實(shí)現(xiàn)而言動(dòng)作通信是建立在話題通信和服務(wù)通信之上的,目標(biāo)發(fā)送實(shí)現(xiàn)是對(duì)服務(wù)通信的封裝,結(jié)果的獲取也是對(duì)服務(wù)通信的封裝,而連續(xù)反饋則是對(duì)話題通信的封裝。

作用
一般適用于耗時(shí)的請(qǐng)求響應(yīng)場(chǎng)景,用以獲取連續(xù)的狀態(tài)反饋。
2.4.1 案例以及案例分析
1.案例需求
需求:編寫(xiě)動(dòng)作通信,動(dòng)作客戶端提交一個(gè)整型數(shù)據(jù)N,動(dòng)作服務(wù)端接收請(qǐng)求數(shù)據(jù)并累加1-N之間的所有整數(shù),將最終結(jié)果返回給動(dòng)作客戶端,且每累加一次都需要計(jì)算當(dāng)前運(yùn)算進(jìn)度并反饋給動(dòng)作客戶端。

2.案例分析
在上述案例中,需要關(guān)注的要素有三個(gè):
動(dòng)作客戶端;
動(dòng)作服務(wù)端;
消息載體。
3.流程簡(jiǎn)介
案例實(shí)現(xiàn)前需要先自定義動(dòng)作接口,接口準(zhǔn)備完畢后,動(dòng)作通信實(shí)現(xiàn)主要步驟如下:
編寫(xiě)動(dòng)作服務(wù)端實(shí)現(xiàn);
編寫(xiě)動(dòng)作客戶端實(shí)現(xiàn);
編輯配置文件;
編譯;
執(zhí)行。
案例我們會(huì)采用C++和Python分別實(shí)現(xiàn),二者都遵循上述實(shí)現(xiàn)流程。
4.準(zhǔn)備工作
終端下進(jìn)入工作空間的src目錄,調(diào)用如下兩條命令分別創(chuàng)建C++功能包和Python功能包。
ros2 pkg create cpp03_action --build-type ament_cmake --dependencies rclcpp rclcpp_action base_interfaces_demo?
ros2 pkg create py03_action --build-type ament_python --dependencies rclpy base_interfaces_demo
2.4.2 動(dòng)作通信接口消息
定義動(dòng)作接口消息與定義話題或服務(wù)接口消息流程類(lèi)似,主要步驟如下:
創(chuàng)建并編輯
.action
文件;編輯配置文件;
編譯;
測(cè)試。
接下來(lái),我們可以參考案例編寫(xiě)一個(gè)action文件,該文件中包含請(qǐng)求數(shù)據(jù)(一個(gè)整型字段)、響應(yīng)數(shù)據(jù)(一個(gè)整型字段)和連續(xù)反饋數(shù)據(jù)(一個(gè)浮點(diǎn)型字段)。
1.創(chuàng)建并編輯 .action 文件
功能包base_interfaces_demo下新建action文件夾,action文件夾下新建Progress.action文件,文件中輸入如下內(nèi)容:
int64 num?
---?
int64 sum?
---?
float64 progress
2.編輯配置文件
1.package.xml
如果單獨(dú)構(gòu)建action功能包,需要在package.xml中需要添加一些依賴包,具體內(nèi)容如下:
<buildtool_depend>rosidl_default_generators</buildtool_depend> <depend>action_msgs</depend> <member_of_group>rosidl_interface_packages</member_of_group>
當(dāng)前使用的是 base_interfaces_demo 功能包,已經(jīng)為 msg 、srv 文件添加過(guò)了一些依賴,所以 package.xml 中添加如下內(nèi)容即可:
<buildtool_depend>rosidl_default_generators</buildtool_depend> <depend>action_msgs</depend>
2.CMakeLists.txt
如果是新建的功能包,與之前定義msg、srv文件同理,為了將.action
文件轉(zhuǎn)換成對(duì)應(yīng)的C++和Python代碼,還需要在CMakeLists.txt 中添加如下配置:
find_package(rosidl_default_generators REQUIRED)?
rosidl_generate_interfaces(${PROJECT_NAME} ?
? "action/Progress.action"?
)
不過(guò),我們當(dāng)前使用的base_interfaces_demo包,那么只需要修改rosidl_generate_interfaces函數(shù)即可,修改后的內(nèi)容如下:
rosidl_generate_interfaces(${PROJECT_NAME}
?
?"msg/Student.msg" ?
?"srv/AddInts.srv" ?
?"action/Progress.action"?
)
3.編譯
終端中進(jìn)入當(dāng)前工作空間,編譯功能包:
colcon build --packages-select base_interfaces_demo
4.測(cè)試
編譯完成之后,在工作空間下的 install 目錄下將生成Progress.action
文件對(duì)應(yīng)的C++和Python文件,我們也可以在終端下進(jìn)入工作空間,通過(guò)如下命令查看文件定義以及編譯是否正常:
. install/setup.bash?
ros2 interface show base_interfaces_demo/action/Progress
正常情況下,終端將會(huì)輸出與Progress.action
文件一致的內(nèi)容。
2.4.3 動(dòng)作通信(C++)
1.動(dòng)作服務(wù)端實(shí)現(xiàn)
功能包c(diǎn)pp03_action的src目錄下,新建C++文件demo01_action_server.cpp,并編輯文件,輸入如下內(nèi)容:
/* ? ?
需求:編寫(xiě)動(dòng)作服務(wù)端實(shí)習(xí),可以提取客戶端請(qǐng)求提交的整型數(shù)據(jù),并累加從1到該數(shù)據(jù)之間的所有整數(shù)以求和, ? ? ?
? ?每累加一次都計(jì)算當(dāng)前運(yùn)算進(jìn)度并連續(xù)反饋回客戶端,最后,在將求和結(jié)果返回給客戶端。 ?
步驟: ? ?
? 1.包含頭文件; ? ?
? 2.初始化 ROS2 客戶端; ? ?
? 3.定義節(jié)點(diǎn)類(lèi); ? ? ?
? ?3-1.創(chuàng)建動(dòng)作服務(wù)端; ? ? ?
? ?3-2.處理請(qǐng)求數(shù)據(jù); ? ? ?
? ?3-3.處理取消任務(wù)請(qǐng)求; ? ? ?
? ?3-4.生成連續(xù)反饋。 ? ?
? 4.調(diào)用spin函數(shù),并傳入節(jié)點(diǎn)對(duì)象指針; ? ?
? 5.釋放資源。
*/
// 1.包含頭文件;
#include "rclcpp/rclcpp.hpp"
#include "rclcpp_action/rclcpp_action.hpp"
#include "base_interfaces_demo/action/progress.hpp"
using namespace std::placeholders;
using base_interfaces_demo::action::Progress;
using GoalHandleProgress = rclcpp_action::ServerGoalHandle<Progress>;
// 3.定義節(jié)點(diǎn)類(lèi);
class MinimalActionServer : public rclcpp::Node
{
public:
explicit MinimalActionServer(const rclcpp::NodeOptions & options = rclcpp::NodeOptions()) ?
: Node("minimal_action_server", options) ?
{ ? ?
// 3-1.創(chuàng)建動(dòng)作服務(wù)端; ?
this->action_server_ = rclcpp_action::create_server<Progress>( ? ? ?
?this, ? ? ?
?"get_sum", ? ? ?
?std::bind(&MinimalActionServer::handle_goal, this, _1, _2), ? ? ?
?std::bind(&MinimalActionServer::handle_cancel, this, _1), ? ? ?
?std::bind(&MinimalActionServer::handle_accepted, this, _1)); ? ?
RCLCPP_INFO(this->get_logger(),"動(dòng)作服務(wù)端創(chuàng)建,等待請(qǐng)求..."); ?
}
private: ?
rclcpp_action::Server<Progress>::SharedPtr action_server_; ?
// 3-2.處理請(qǐng)求數(shù)據(jù); ?
rclcpp_action::GoalResponse handle_goal(const rclcpp_action::GoalUUID & uuid,std::shared_ptr<const Progress::Goal> goal) ?
{ ? ?
(void)uuid; ? ?
RCLCPP_INFO(this->get_logger(), "接收到動(dòng)作客戶端請(qǐng)求,請(qǐng)求數(shù)字為 %ld", goal->num); ? ?
if (goal->num < 1) { ? ? ?
?return rclcpp_action::GoalResponse::REJECT; ? ?
} ? ?
return rclcpp_action::GoalResponse::ACCEPT_AND_EXECUTE; ?
} ?
// 3-3.處理取消任務(wù)請(qǐng)求; ?
rclcpp_action::CancelResponse handle_cancel( ? ?
?const std::shared_ptr<GoalHandleProgress> goal_handle) ?
{ ? ?
?(void)goal_handle; ? ?
? RCLCPP_INFO(this->get_logger(), "接收到任務(wù)取消請(qǐng)求"); ? ?
? return rclcpp_action::CancelResponse::ACCEPT; ?
} ?
void execute(const std::shared_ptr<GoalHandleProgress> goal_handle) ?
{ ? ?
RCLCPP_INFO(this->get_logger(), "開(kāi)始執(zhí)行任務(wù)"); ? ?
rclcpp::Rate loop_rate(10.0); ? ?
const auto goal = goal_handle->get_goal(); ? ?
auto feedback = std::make_shared<Progress::Feedback>(); ? ?
auto result = std::make_shared<Progress::Result>(); ? ?
int64_t sum= 0; ? ?
for (int i = 1; (i <= goal->num) && rclcpp::ok(); i++) { ? ? ?
? ?sum += i; ? ? ?
? ?// Check if there is a cancel request ? ? ?
? ?if (goal_handle->is_canceling()) { ? ? ? ?
? ? ? result->sum = sum; ? ? ? ?
? ? ? goal_handle->canceled(result); ? ? ? ?
? ? ? RCLCPP_INFO(this->get_logger(), "任務(wù)取消"); ? ? ? ?return; ? ? ?} ? ? ?feedback->progress = (double_t)i / goal->num; ? ? ?goal_handle->publish_feedback(feedback); ? ? ?RCLCPP_INFO(this->get_logger(), "連續(xù)反饋中,進(jìn)度:%.2f", feedback->progress); ? ? ?
? ? ? loop_rate.sleep(); ? ?
? ? ?} ? ?
? ? ? if (rclcpp::ok()) { ? ? ?
? ? ? ? result->sum = sum; ? ? ?
? ? ? ? goal_handle->succeed(result); ? ? ?
? ? ? ? RCLCPP_INFO(this->get_logger(), "任務(wù)完成!"); ? ?
? ? ?} ?
? }
// 3-4.生成連續(xù)反饋。 ?
void handle_accepted(const std::shared_ptr<GoalHandleProgress> goal_handle) ?
{ ? ? ?
std::thread{std::bind(&MinimalActionServer::execute, this, _1), goal_handle}.detach(); ?
}
};
int main(int argc, char ** argv)
{ ?
// 2.初始化 ROS2 客戶端; ?
rclcpp::init(argc, argv); ?
// 4.調(diào)用spin函數(shù),并傳入節(jié)點(diǎn)對(duì)象指針; ?
auto action_server = std::make_shared<MinimalActionServer>(); ? ?
rclcpp::spin(action_server); ?
// 5.釋放資源。 ?
rclcpp::shutdown(); ?
return 0;
}
2.動(dòng)作客戶端實(shí)現(xiàn)
功能包c(diǎn)pp03_action的 src目錄下,新建C++文件demo02_action_client.cpp,并編輯文件,輸入如下內(nèi)容:
/* ? ?
需求:編寫(xiě)動(dòng)作客戶端實(shí)現(xiàn),可以提交一個(gè)整型數(shù)據(jù)到服務(wù)端,并處理服務(wù)端的連續(xù)反饋以及最終返回結(jié)果。 ?
步驟: ? ?
? 1.包含頭文件; ? ?
? 2.初始化 ROS2 客戶端; ? ?
? 3.定義節(jié)點(diǎn)類(lèi); ? ? ?
? ?3-1.創(chuàng)建動(dòng)作客戶端; ? ? ?
? ?3-2.發(fā)送請(qǐng)求; ? ? ?
? ?3-3.處理目標(biāo)發(fā)送后的反饋; ? ? ?
? ?3-4.處理連續(xù)反饋; ? ? ?
? ?3-5.處理最終響應(yīng)。 ? ?
? 4.調(diào)用spin函數(shù),并傳入節(jié)點(diǎn)對(duì)象指針; ? ?
? 5.釋放資源。
*/
// 1.包含頭文件;
#include "rclcpp/rclcpp.hpp"
#include "rclcpp_action/rclcpp_action.hpp"
#include "base_interfaces_demo/action/progress.hpp"
using base_interfaces_demo::action::Progress;
using GoalHandleProgress = rclcpp_action::ClientGoalHandle<Progress>;
using namespace std::placeholders;
// 3.定義節(jié)點(diǎn)類(lèi);
class MinimalActionClient : public rclcpp::Node
{
public: ?
explicit MinimalActionClient(const rclcpp::NodeOptions & node_options = rclcpp::NodeOptions()) ?
: Node("minimal_action_client", node_options) ?
{ ? ?
?// 3-1.創(chuàng)建動(dòng)作客戶端; ? ?
?this->client_ptr_ = rclcpp_action::create_client<Progress>(this,"get_sum"); ? ?} ?
?// 3-2.發(fā)送請(qǐng)求; ?
?void send_goal(int64_t num) ?
?{ ? ? ?
? ?if (!this->client_ptr_) { ? ?
? ? RCLCPP_ERROR(this->get_logger(), "動(dòng)作客戶端未被初始化。"); ? ?
? ?} ? ?
? ?if (!this->client_ptr_->wait_for_action_server(std::chrono::seconds(10))) { ? ? ?
? ? ?RCLCPP_ERROR(this->get_logger(), "服務(wù)連接失??!"); ? ? ?
? ? ?return; ? ?
? ? } ? ?
? ? ?auto goal_msg = Progress::Goal(); ? ?
? ? ?goal_msg.num = num; ? ?
? ? ?RCLCPP_INFO(this->get_logger(), "發(fā)送請(qǐng)求數(shù)據(jù)!"); ? ?
? ? ? auto send_goal_options = rclcpp_action::Client<Progress>::SendGoalOptions(); ? ? ? ?
? ? ? send_goal_options.goal_response_callback =std::bind(&MinimalActionClient::goal_response_callback, this, _1); ? ?
? ? ? send_goal_options.feedback_callback =std::bind(&MinimalActionClient::feedback_callback, this, _1, _2); ? ?
? ? ? send_goal_options.result_callback =std::bind(&MinimalActionClient::result_callback, this, _1); ? ?
? ? ? auto goal_handle_future = this->client_ptr_->async_send_goal(goal_msg, send_goal_options); ?
}
private:
rclcpp_action::Client<Progress>::SharedPtr client_ptr_;
// 3-3.處理目標(biāo)發(fā)送后的反饋; ?
void goal_response_callback(GoalHandleProgress::SharedPtr goal_handle) ?
{ ? ?
if (!goal_handle) { ? ?
? RCLCPP_ERROR(this->get_logger(), "目標(biāo)請(qǐng)求被服務(wù)器拒絕!"); ? ?} else { ? ? RCLCPP_INFO(this->get_logger(), "目標(biāo)被接收,等待結(jié)果中"); ? ?
? } ?
} ?
// 3-4.處理連續(xù)反饋; ?
void feedback_callback(GoalHandleProgress::SharedPtr,const std::shared_ptr<const Progress::Feedback> feedback) ?
{ ? ?
? int32_t progress = (int32_t)(feedback->progress * 100); ? ? ?
? RCLCPP_INFO(this->get_logger(), "當(dāng)前進(jìn)度: %d%%", progress); ?
?}
// 3-5.處理最終響應(yīng)。 ?
void result_callback(const GoalHandleProgress::WrappedResult & result) ?
{ ? ?
?switch (result.code) { ?
? ? case rclcpp_action::ResultCode::SUCCEEDED: ? ? ? ?
? ? ? ?break; ? ? ?
? ? case rclcpp_action::ResultCode::ABORTED: ? ? ?
? ? ? ?RCLCPP_ERROR(this->get_logger(), "任務(wù)被中止"); ? ? ? ?
? ? ? ?return; ? ? ?
? ? case rclcpp_action::ResultCode::CANCELED: ? ? ? ?
? ? ? ?RCLCPP_ERROR(this->get_logger(), "任務(wù)被取消"); ? ? ? ?
? ? ? ?return; ? ? ?
? ? ?default: ? ? ? ?
? ? ? ?RCLCPP_ERROR(this->get_logger(), "未知異常"); ? ? ? ?
? ? ? ?return; ?
? } ? ?
? ?RCLCPP_INFO(this->get_logger(), "任務(wù)執(zhí)行完畢,最終結(jié)果: %ld", result.result->sum); ?
? }
};
int main(int argc, char ** argv)
{ ?
// 2.初始化 ROS2 客戶端; ?
rclcpp::init(argc, argv); ?
// 4.調(diào)用spin函數(shù),并傳入節(jié)點(diǎn)對(duì)象指針; ?
auto action_client = std::make_shared<MinimalActionClient>(); ?
action_client->send_goal(10); ?
rclcpp::spin(action_client);
// 5.釋放資源。 ?
rclcpp::shutdown();
return 0;
}
3.編輯配置文件
1.packages.xml
在創(chuàng)建功能包時(shí),所依賴的功能包已經(jīng)自動(dòng)配置了,配置內(nèi)容如下:
<depend>rclcpp</depend>
<depend>rclcpp_action</depend>
<depend>base_interfaces_demo</depend>
2.CMakeLists.txt
CMakeLists.txt中服務(wù)端和客戶端程序核心配置如下:
find_package(rclcpp REQUIRED)?
find_package(rclcpp_action REQUIRED)?
find_package(base_interfaces_demo REQUIRED)
add_executable(demo01_action_server src/demo01_action_server.cpp)?
ament_target_dependencies( ?
?demo01_action_server ?
?"rclcpp" ?
?"rclcpp_action" ?
?"base_interfaces_demo"?
)
add_executable(demo02_action_client src/demo02_action_client.cpp)?
ament_target_dependencies( ?
?demo02_action_client ?
?"rclcpp"?
?"rclcpp_action"?
?"base_interfaces_demo"?
)
install(TARGETS?
?demo01_action_server?
?demo02_action_client?
?DESTINATION lib/${PROJECT_NAME})
4.編譯
終端中進(jìn)入當(dāng)前工作空間,編譯功能包:
colcon build --packages-select cpp03_action
5.執(zhí)行
當(dāng)前工作空間下,啟動(dòng)兩個(gè)終端,終端1執(zhí)行動(dòng)作服務(wù)端程序,終端2執(zhí)行動(dòng)作客戶端程序。
終端1輸入如下指令:
. install/setup.bash?
ros2 run cpp03_action demo01_action_server
終端2輸入如下指令:
. install/setup.bash?
ros2 run cpp03_action demo02_action_client
最終運(yùn)行結(jié)果與案例類(lèi)似。
2.4.4 動(dòng)作通信(Python)
1.動(dòng)作服務(wù)端實(shí)現(xiàn)
功能包py03_action的py03_action目錄下,新建Python文件demo01_action_server_py.py,并編輯文件,輸入如下內(nèi)容:
""" ? ?
? 需求:編寫(xiě)動(dòng)作服務(wù)端實(shí)習(xí),可以提取客戶端請(qǐng)求提交的整型數(shù)據(jù),并累加從1到該數(shù)據(jù)之間的所有整數(shù)以求和, ? ? ?
? 每累加一次都計(jì)算當(dāng)前運(yùn)算進(jìn)度并連續(xù)反饋回客戶端,最后,在將求和結(jié)果返回給客戶端。 ? ?
? 步驟: ? ? ? ?
? ? ? 1.導(dǎo)包; ? ? ? ?
? ? ? 2.初始化 ROS2 客戶端; ? ? ? ?
? ? ? 3.定義節(jié)點(diǎn)類(lèi); ? ? ? ? ? ?
? ? ? ? 3-1.創(chuàng)建動(dòng)作服務(wù)端; ? ? ? ? ? ?
? ? ? ? 3-2.生成連續(xù)反饋; ? ? ? ? ? ?
? ? ? ? 3-3.生成最終響應(yīng)。 ? ? ? ?
? ? ? 4.調(diào)用spin函數(shù),并傳入節(jié)點(diǎn)對(duì)象; ? ? ? ?
? ? ? 5.釋放資源。
"""
# 1.導(dǎo)包;
import time
import rclpy
from rclpy.action import ActionServer
from rclpy.node import Node
from base_interfaces_demo.action import Progress
# 3.定義節(jié)點(diǎn)類(lèi);
class ProgressActionServer(Node): ?
?def __init__(self): ? ?
? ? super().__init__('progress_action_server') ? ? ? ?
? ? # 3-1.創(chuàng)建動(dòng)作服務(wù)端; ? ? ? ?
? ? self._action_server = ActionServer( ? ? ? ? ? ?
? ? ? self, ? ? ? ? ? ?
? ? ? Progress, ? ? ? ? ? ?
? ? ? 'get_sum', ? ? ? ? ? ?
? ? ? self.execute_callback) ? ? ? ?
? ? self.get_logger().info('動(dòng)作服務(wù)已經(jīng)啟動(dòng)!') ? ?
? def execute_callback(self, goal_handle): ? ? ? ?
? ? ?self.get_logger().info('開(kāi)始執(zhí)行任務(wù)....') ? ? ? ?
? ? ?# 3-2.生成連續(xù)反饋; ? ? ? ?
? ? ?feedback_msg = Progress.Feedback() ? ? ? ?
? ? ?sum = 0 ? ? ? ?
? ? ?for i in range(1, goal_handle.request.num + 1): ? ? ? ? ? ?
? ? ? ? sum += i ? ? ? ? ? ?
? ? ? ? feedback_msg.progress = i / goal_handle.request.num ? ? ? ? ? ? ? ? ? ? self.get_logger().info('連續(xù)反饋: %.2f' % feedback_msg.progress) ? ? ? ? ? ?goal_handle.publish_feedback(feedback_msg) ? ? ? ? ? ? ?
? ? ? ? time.sleep(1) ? ? ? ?
? ? ? # 3-3.生成最終響應(yīng)。 ? ? ? ?
? ? ? goal_handle.succeed() ? ? ? ?
? ? ? result = Progress.Result() ? ? ? ?
? ? ? result.sum = sum ? ? ? ?
? ? ? self.get_logger().info('任務(wù)完成!') ? ? ? ?
? ? ? return result
def main(args=None): ? ?
? # 2.初始化 ROS2 客戶端; ?
? rclpy.init(args=args) ? ?
? # 4.調(diào)用spin函數(shù),并傳入節(jié)點(diǎn)對(duì)象; ?
? Progress_action_server = ProgressActionServer() ? ?
? rclpy.spin(Progress_action_server) ?
? # 5.釋放資源。 ?
? rclpy.shutdown()
if __name__ == '__main__': ?
main()
2.動(dòng)作客戶端實(shí)現(xiàn)
功能包py03_action的py03_action目錄下,新建Python文件demo02_action_client_py.py,并編輯文件,輸入如下內(nèi)容:
""" ? ?
?需求:編寫(xiě)動(dòng)作客戶端實(shí)現(xiàn),可以提交一個(gè)整型數(shù)據(jù)到服務(wù)端,并處理服務(wù)端的連續(xù)反饋以及最終返回結(jié)果。 ? ?
?步驟: ? ? ?
? ? ?1.導(dǎo)包; ? ? ? ?
? ? ?2.初始化 ROS2 客戶端; ? ? ? ?
? ? ?3.定義節(jié)點(diǎn)類(lèi); ? ? ? ? ? ?
? ? ? ?3-1.創(chuàng)建動(dòng)作客戶端; ? ? ? ? ? ?
? ? ? ?3-2.發(fā)送請(qǐng)求; ? ? ? ? ? ?
? ? ? ?3-3.處理目標(biāo)發(fā)送后的反饋; ? ? ? ? ? ?
? ? ? ?3-4.處理連續(xù)反饋; ? ? ? ? ? ?
? ? ? ?3-5.處理最終響應(yīng)。 ? ? ? ?
? ? ?4.調(diào)用spin函數(shù),并傳入節(jié)點(diǎn)對(duì)象; ? ? ? ?
? ? ?5.釋放資源。
"""
# 1.導(dǎo)包;i
mport rclpy
from rclpy.action import ActionClient
from rclpy.node import Node
from base_interfaces_demo.action import Progress
# 3.定義節(jié)點(diǎn)類(lèi);
class ProgressActionClient(Node):
? def __init__(self): ? ?
? ?super().__init__('progress_action_client') ?
? ?# 3-1.創(chuàng)建動(dòng)作客戶端; ?
? ? self._action_client = ActionClient(self, Progress, 'get_sum') ?
? def send_goal(self, num): ? ?
? ? ?# 3-2.發(fā)送請(qǐng)求; ?
? ? goal_msg = Progress.Goal() ? ?
? ? goal_msg.num = num ? ?
? ? self._action_client.wait_for_server() ? ?
? ? self._send_goal_future = self._action_client.send_goal_async(goal_msg, feedback_callback=self.feedback_callback) ? ? ? ?
? ? self._send_goal_future.add_done_callback(self.goal_response_callback) ?
? def goal_response_callback(self, future): ? ?
? ? ? # 3-3.處理目標(biāo)發(fā)送后的反饋; ? ?
? ? ? ?goal_handle = future.result() ? ?
? ? ? ?if not goal_handle.accepted: ? ? ? ? ? ?
? ? ? ? ? self.get_logger().info('請(qǐng)求被拒絕') ? ? ? ? ? ?
? ? ? ? ? return ? ? ?
? ? ? self.get_logger().info('請(qǐng)求被接收,開(kāi)始執(zhí)行任務(wù)!')
? ? ? ?
? ? ? ?self._get_result_future = goal_handle.get_result_async() ? ? ? ? ? ? ? ? ?self._get_result_future.add_done_callback(self.get_result_callback) ? ? ?
? ? ?# 3-5.處理最終響應(yīng)。 ?
? ? def get_result_callback(self, future): ? ?
? ? ? ?result = future.result().result ? ?
? ? ? ?self.get_logger().info('最終計(jì)算結(jié)果:sum = %d' % result.sum) ? ? ? ? ? ? ?# 5.釋放資源。 ? ? ?
? ? ? ?rclpy.shutdown() ? ?
? ? ?# 3-4.處理連續(xù)反饋; ? ?
? ? def feedback_callback(self, feedback_msg): ? ? ?
? ? ? ?feedback = (int)(feedback_msg.feedback.progress * 100) ? ? ? ?
? ? ? ?self.get_logger().info('當(dāng)前進(jìn)度: %d%%' % feedback)
def main(args=None): ?
? # 2.初始化 ROS2 客戶端; ?
? rclpy.init(args=args) ?
? # 4.調(diào)用spin函數(shù),并傳入節(jié)點(diǎn)對(duì)象; ?
?
? ?action_client = ProgressActionClient() ?
? ?action_client.send_goal(10) ?
? ?rclpy.spin(action_client) ?
? ?# rclpy.shutdown()
if __name__ == '__main__': ?
main()
3.編輯配置文件
1.package.xml
在創(chuàng)建功能包時(shí),所依賴的功能包已經(jīng)自動(dòng)配置了,配置內(nèi)容如下:
<depend>rclpy</depend>
<depend>base_interfaces_demo</depend>
2.setup.py
entry_points
字段的console_scripts
中添加如下內(nèi)容:
entry_points={
?
?'console_scripts': [
? ?
? ?'demo01_action_server_py = py03_action.demo01_action_server_py:main',
? ? ? ?'demo02_action_client_py = py03_action.demo02_action_client_py:main'
? ?
? ],?
},
4.編譯
終端中進(jìn)入當(dāng)前工作空間,編譯功能包:
colcon build --packages-select py03_action
5.執(zhí)行
當(dāng)前工作空間下,啟動(dòng)兩個(gè)終端,終端1執(zhí)行動(dòng)作服務(wù)端程序,終端2執(zhí)行動(dòng)作客戶端程序。
終端1輸入如下指令:
. install/setup.bash
ros2 run py03_action demo01_action_server_py
終端2輸入如下指令:
. install/setup.bash?
ros2 run py03_action demo02_action_client_py
最終運(yùn)行結(jié)果與案例類(lèi)似。


B站有完整的ros系列教程視頻,可以觀看完整內(nèi)容ros課程ROS2理論與實(shí)踐
更多內(nèi)容將在猛獅知識(shí)星球社區(qū)更新最新課程,后續(xù)將推出更多優(yōu)質(zhì)內(nèi)容——詳情可關(guān)注猛獅集訓(xùn)營(yíng)公眾號(hào)和猛獅集訓(xùn)營(yíng)官方網(wǎng)站。