はじめに
本記事は、著者がROS2のAction通信の勉強のため、choreonoidに自作Action通信を実装した時の知見をまとめたものです。
本記事値が、筆者のようなROS2諸学者の学習に寄与できれば幸いです。
完成形
本記事では、最終的にchoreonoiシミュレータ上のカートに対して、下記ができることを目指します。
- Action clientがAction server(choreonoidコントローラ)に向けてGoal(Feedback送信回数)を送り、それと同時にカートが旋回走行すること
- 旋回走行中、Action server(choreonoidコントローラ)がAction clientへ向け、一定周期でFeedback(片方の車輪トルク)を送ること
- Action server(choreonoidコントローラ)がAction clientへResult(全Feedbackの配列)を送ること。それと同時にcartが停止すること
動作環境は下記です。
- Ubuntu 24.04.3 LTS ※ Virtual Box 7.1.10上で起動
- Choreonoid 2.3.0
- ROS2 Jazzy
Action通信プログラム作成
事前準備
以降の内容を進める前に、まずはChoreonoid公式から出されているROS2モバイルロボットチュートリアルを参照し、
- choreonoidコントローラのビルド
- choreonoidコントローラのシミュレータへの適用
が正常にできるところまで進めてください。
実装方針
Action通信では、Goal、Result、Feedbackの3種類の通信が、Action server~Action client間でやり取りされます(下図参照)。
本記事では、
- Goal:Feedbackを送る回数
- Result:全Feedbackの配列
- Feedback:車輪(片輪)のトルク
となるよう、Action clientとAction server(choreonoidコントローラ)プログラムを作成します。本記事で作成するプログラムを図にすると以下のようになります。
Action通信プログラム
Action interface
ROS2のAction通信では、以下のようにAction interfaceを定義する必要があります。
# Goal
int32 order
---
# Result
float32[] wheel_torque_vector
---
# Feedback
float32[] wheel_torque
Action intarfaceは「---」で区切ります。すると上からGoal、Result、Feedbackの順で型式と変数名が定義されます。
Action clientプログラム
Action clientプログラム(cart_action_client.cpp)を機能ごとに抜粋して説明します。
なお、Action clientプログラムの全体像は、Githubリンクを参照ください。
Action clientプログラム(cart_action_client.cpp)はyoutalkさんのプログラムを参考に作成しました。
まず、下記記述でAction clientを作成します。
// create Action client
this->client_ptr_ = rclcpp_action::create_client<CartAction>(
this->get_node_base_interface(),
this->get_node_graph_interface(),
this->get_node_logging_interface(),
this->get_node_waitables_interface(),
"CartAction");
次に、Action clientプログラムの主要な機能である、「Action server(choreonoidコントローラ)への「Goal」送信機能」を実装するため、下記を追加します。
// Send Goal to Action server (=choreonid controller)
this->client_ptr_->async_send_goal(goal_msg, send_goal_options);
goal_msg、send_goal_optionsの中身は下記です。
- goal_msg:Action serverへ送るGoalの中身
- send_goal_options:Goal、Feedback、Resultのコールバック
これは下記のように記述します。
// define goal message
auto goal_msg = CartAction::Goal();
goal_msg.order = 10; // Number of times feedback is sent
auto send_goal_options = rclcpp_action::Client<CartAction>::SendGoalOptions();
// callback for goal response
send_goal_options.goal_response_callback = [this](const GoalHandleCartAction::SharedPtr & goal_handle)
{
if (!goal_handle)
{
RCLCPP_ERROR(this->get_logger(), "Goal was rejected by server");
}
else
{
RCLCPP_INFO(this->get_logger(), "Goal accepted by server, waiting for result");
}
};
// callback for feedback response
send_goal_options.feedback_callback = [this](GoalHandleCartAction::SharedPtr,const std::shared_ptr<const CartAction::Feedback> feedback)
{
RCLCPP_INFO(this->get_logger(), "Subscribed wheel now data : %f", feedback->partial_sequence.back());
};
// callback for result response
send_goal_options.result_callback = [this](const GoalHandleCartAction::WrappedResult & result)
{
switch (result.code)
{
case rclcpp_action::ResultCode::SUCCEEDED:
break;
case rclcpp_action::ResultCode::ABORTED:
RCLCPP_ERROR(this->get_logger(), "Goal was aborted");
return;
case rclcpp_action::ResultCode::CANCELED:
RCLCPP_ERROR(this->get_logger(), "Goal was canceled");
return;
default:
RCLCPP_ERROR(this->get_logger(), "Unknown result code");
return;
}
std::stringstream ss;
ss << "Result received: ";
for (auto number : result.result->sequence)
{
ss << number << " ";
}
RCLCPP_INFO(this->get_logger(), "%s", ss.str().c_str());
rclcpp::shutdown();
};
Action clientの実行部分は、下記です。今回はコンポーネント化して実行します。
RCLCPP_COMPONENTS_REGISTER_NODE(action_tutorials_cpp::CartActionClient)
Action server(choreonoidコントローラ)プログラム
Action serverプログラム(choreonoidコントローラ、MobileRobotDriveController.cpp)を機能ごとに抜粋して説明します。
なお、Action sever(choreonoidコントローラ)プログラムの全体像は、Githubリンクを参照ください。
プログラムはchoreonoidチュートリアルのプログラムに編集を加えました。
まず、下記記述でAction severを作成します。
// create Action server
this->action_server_ = rclcpp_action::create_server<CartAction>(
node, // node pointer
"CartAction", // name of action
handle_goal, // function that is ececuted when a "Goal" is received
handle_cancel, // function that is ececuted when a "Cancel" is received
handle_accepted // function wfunction that is ececuted when a "Goal" is accepted
);
「handle_goal」、「handle_cancel」、「handle_accepted」は下記記述で定義します。
// function that is ececuted when a "Goal" is received
auto handle_goal = [this](const rclcpp_action::GoalUUID & uuid, std::shared_ptr<const CartAction::Goal> goal)
{
(void)uuid;
RCLCPP_INFO(node->get_logger(), "Received goal request with order %d", goal->order);
if (goal->order > 46)
{
return rclcpp_action::GoalResponse::REJECT;
}
command.linear.x = 0.5;
command.angular.z = 1.0;
return rclcpp_action::GoalResponse::ACCEPT_AND_EXECUTE;
};
// function that is ececuted when a "Cancel" is received
auto handle_cancel = [this](const std::shared_ptr<GoalHandleCartAction> goal_handle)
{
RCLCPP_INFO(node->get_logger(), "Received request to cancel goal");
(void)goal_handle;
return rclcpp_action::CancelResponse::ACCEPT;
};
// function wfunction that is ececuted when a "Goal" is accepted
auto handle_accepted = [this](const std::shared_ptr<GoalHandleCartAction> goal_handle)
{
auto execute_in_thread = [this, goal_handle]()
{
return this->execute(goal_handle);
};
std::thread{execute_in_thread}.detach();
};
Action server(choreonoidコントローラ)の主要な機能である、「Action clientへのResult、Feedbackの送信」は、「handle_accepted」内にある「execute(goal_handle)」に実装します。記述は下記となります。
void execute(const std::shared_ptr<GoalHandleCartAction> goal_handle)
{
RCLCPP_INFO(node->get_logger(), "Executing goal");
rclcpp::Rate loop_rate(1);
const auto goal = goal_handle->get_goal();
auto feedback = std::make_shared<CartAction::Feedback>();
auto & sequence = feedback->partial_sequence;
auto result = std::make_shared<CartAction::Result>();
for (int i = 1; (i < goal->order) && rclcpp::ok(); ++i)
{
// Confirm cancel request
if (goal_handle->is_canceling())
{
result->sequence = sequence;
goal_handle->canceled(result);
RCLCPP_INFO(node->get_logger(), "Goal canceled");
return;
}
// Send Feedback to Action sclient
sequence.push_back( wheels[0]->u() );
goal_handle->publish_feedback(feedback);
RCLCPP_INFO(node->get_logger(), "Published wheel now data: %f", feedback->partial_sequence.back());
loop_rate.sleep();
}
if (rclcpp::ok())
{
// Stop cart
command.linear.x = 0.0;
command.angular.z = 0.0;
// Send Result to Action client
result->sequence = sequence;
goal_handle->succeed(result);
RCLCPP_INFO(node->get_logger(), "Goal succeeded");
}
}
その他プログラム
Action clientとAction server(choreonoidコントローラ)以外にも、
- CMakeLists.txt
- package.xml
- visibility_control.h
が必要となりますが、詳しくは以下をご参照ください。
動作確認
ビルド
実行環境は、
の2つになります。これを下記の様なフォルダ構成で保存してください。
└── ros_ws <- Choreonoidチュートリアルを進める過程で作ります
├── build
├── install
├── log
└── src
├── cart_action_com <- Githubからダウンロードします
| ├── include
│ │ └── visibility_control.h
│ ├── meshes
│ ├── model
│ ├── project
│ ├── src
│ │ ├── cart_action_client.cpp
│ │ ├── CMakeLists.txt
│ │ └── MobileRobotDriveController.cpp
│ ├── CMakeLists.txt
│ ├── package.xml
│ └── README.md
└── cart_action_interface <- Githubからダウンロードします
├── action
│ └── CartActioninterface.action
├── CMakeLists.txt
├── package.xml
└── README.md
「ros2_ws」ディレクトリ上で端末を起動し、下記コマンドを実行してビルドします。
~/Document/ros2_ws$ colcon build --packages-select cart_action_interface
~/Document/ros2_ws$ colcon build --packages-select cart_action_com
ビルドに成功すると、「install」ディレクトリ内にファイルが生成されます。
└── ros_ws <- Choreonoidチュートリアルを進める過程で作ります
├── build
├── install
| ├── cart_action_com
| | └── lib
| | ├── cart_action_com
| | | └── cart_action_client <- 生成物
| | └── choreonoid-2.3
| | └── simplecontroller
| | └── MobileRobotDriveController.so <- 生成物
| └── cart_action_interface <- 生成物
├── log
└── src
├── cart_action_com <- Githubからダウンロードします
└── cart_action_interface <- Githubからダウンロードします
カートロボットの走行実演
choreonoidシミュレータ上のカートが狙いの動きをするかを確認します。
まず、「ros2_ws」上で端末を起動し、下記コマンドを実行して「Choreonoid-ROS」を起動します。
~/Document/ros2_ws/$ source ./setup.bash
~/Document/ros2_ws/$ ros2 run choreonoid_ros choreonoid
起動後、以下の場所にある「cart_action_com_simu.cnoid」を読み込ませてください。
└── ros_ws <- Choreonoidチュートリアルを進める過程で作ります
├── build
├── install
├── log
└── src
├── cart_action_com <- Githubからダウンロードします
| ├── include
│ ├── meshes
│ ├── model
│ ├── project
| | └── cart_action_com_simu.cnoid <- これを読み込ませる
│ ├── src
│ ├── CMakeLists.txt
│ ├── package.xml
│ └── README.md
└── cart_action_interface <- Githubからダウンロードします
次に、別端末を起動し、下記コマンドを実行します。
~/Documents/ros2_ws/ source ./install/setup.bash
~/Documents/ros2_ws/ ros2 run cart_action_com cart_action_client
その後、「Choreonoid-ROS」でシミュレーションを実行すると、cartがGoalのやり取りと同時に旋回走行を開始します。先ほど起動した端末を確認すると、2つの端末間でカートの片輪トルクの値がやり取り(=Feedback)されていることがわかります。
片輪トルクのやり取り(=Feedback)が完了すると、やり取りされた全ての片輪トルクの配列が出力されます(=Result)。
以上、一連の動作確認方法は以下の動画にまとめました。