コード解説(コントローラ)¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 | /**
* @file drone_controller.cpp
* @brief PX4 based UAV controller for moveit
*/
#include <vector>
#include <array>
#include <string>
#include <cmath>
#include <ros/ros.h>
#include <geometry_msgs/Transform.h>
#include <geometry_msgs/PoseStamped.h>
#include <moveit_msgs/ExecuteTrajectoryAction.h>
#include <moveit_msgs/RobotTrajectory.h>
#include <mavros_msgs/State.h>
#include <trajectory_msgs/MultiDOFJointTrajectory.h>
#include <trajectory_msgs/MultiDOFJointTrajectoryPoint.h>
#include <actionlib/server/simple_action_server.h>
class DroneController{
private:
ros::NodeHandle nh_;
//! Action name of ExecuteTrajectory
std::string action_name_;
//! Server of ExecuteTrajectoryAction
actionlib::SimpleActionServer<moveit_msgs::ExecuteTrajectoryAction> as_;
//! Feedback message of ExecuteTrajectoryAction
moveit_msgs::ExecuteTrajectoryFeedback feedback_;
//! Result message of ExecuteTrajectoryAction
moveit_msgs::ExecuteTrajectoryResult result_;
//! Position command publisher
ros::Publisher cmd_pos_pub_;
//! Subscriber of local position
ros::Subscriber local_pos_sub_;
//! Subscriber of UAV state
ros::Subscriber state_sub_;
//! Current pose of UAV
geometry_msgs::PoseStamped current_pose_;
//! Current state of UAV
mavros_msgs::State current_state_;
public:
/**
* @brief Constructor
* @param action_name Action name
*/
DroneController(std::string action_name) :
action_name_(action_name),
as_(nh_, action_name, boost::bind(&DroneController::executeCb, this, _1), false)
{
as_.start();
// Position command publisher setup
std::string cmd_pos_topic;
nh_.param<std::string>("mavros_setpoint_topic", cmd_pos_topic, "/mavros/setpoint_position/local");
cmd_pos_pub_ = nh_.advertise<geometry_msgs::PoseStamped>(cmd_pos_topic, 10);
// Local position subscriber setup
std::string local_pos_topic;
nh_.param<std::string>("mavros_localpos_topic", local_pos_topic, "/mavros/local_position/pose");
auto local_pos_cb = boost::bind(&DroneController::localPosCb, this, _1);
local_pos_sub_ = nh_.subscribe<geometry_msgs::PoseStamped>(local_pos_topic, 10, local_pos_cb);
// UAV state subscriber setup
std::string state_topic;
nh_.param<std::string>("mavros_state_topic", state_topic, "/mavros/state");
auto state_cb = boost::bind(&DroneController::stateCb, this, _1);
state_sub_ = nh_.subscribe<mavros_msgs::State>(state_topic, 10, state_cb);
ROS_INFO("Action server initialized.");
};
/**
* @brief Destructor
*/
~DroneController()
{
};
private:
/**
* @brief Callback of ExecuteTrajectory action
* @param goal Goal of ExecuteTrajectory action
*/
void executeCb(const moveit_msgs::ExecuteTrajectoryGoalConstPtr &goal)
{
ROS_INFO("Action received.");
ros::Rate rate(20);
std::vector<trajectory_msgs::MultiDOFJointTrajectoryPoint> trajectory;
trajectory = goal->trajectory.multi_dof_joint_trajectory.points;
// Wait for connection
ROS_INFO("Waiting for FCU connection...");
while (ros::ok() and not current_state_.connected)
{
ros::spinOnce();
rate.sleep();
}
ROS_INFO("FCU connection established.");
// Position command need to be published to switch mode to OFFBOARD
for(int i=0; i<10; ++i)
{
cmd_pos_pub_.publish(getPoseFromTrajectory(trajectory.front()));
}
for(int i=0; i < trajectory.size()-1; ++i)
{
ROS_INFO("Moving to waypoint No. %d", i);
// Send feedback (progress of path)
feedback_.state = std::to_string(i);
as_.publishFeedback(feedback_);
// Get PoseStamped message from MultiDOFJointTrajectoryPoint
geometry_msgs::PoseStamped start = getPoseFromTrajectory(trajectory.at(i));
geometry_msgs::PoseStamped goal = getPoseFromTrajectory(trajectory.at(i+1));
// Get interpolated path from two waypoints
std::vector<geometry_msgs::PoseStamped> path = getBilinearPath(start, goal);
// Publish all interpolated points
for(auto pose: path)
{
// Publish same message till drone arrives to local goal
while(not isGoalReached(pose) and not as_.isPreemptRequested())
{
cmd_pos_pub_.publish(pose);
ros::spinOnce();
rate.sleep();
}
// Exit loop if new action arrived
if(as_.isPreemptRequested() or not ros::ok())
{
result_.error_code.val = moveit_msgs::MoveItErrorCodes::PREEMPTED;
as_.setPreempted();
ROS_INFO("Action preempted.");
break;
}
}
// Exit loop if new action arrived
if(as_.isPreemptRequested() or not ros::ok())
{
result_.error_code.val = moveit_msgs::MoveItErrorCodes::PREEMPTED;
as_.setPreempted();
ROS_INFO("Action preempted.");
break;
}
rate.sleep();
}
// Success
result_.error_code.val = moveit_msgs::MoveItErrorCodes::SUCCESS;
as_.setSucceeded(result_);
ROS_INFO("Action completed.");
}
/**
* @brief Return interpolated path using bilinear interpolation from two waypoints
* @param start Start waypoint
* @param goal Goal waypoint
* @param step Step size of interpolation
* @return Vector of interpolated path
*/
std::vector<geometry_msgs::PoseStamped> getBilinearPath(const geometry_msgs::PoseStamped &start,
const geometry_msgs::PoseStamped &goal,
const double step=0.05)
{
std::vector<geometry_msgs::PoseStamped> bilinear_path;
// Store x-y and x-z coordinates of start point
std::array<double, 2> start_xy = {start.pose.position.x, start.pose.position.y};
std::array<double, 2> start_xz = {start.pose.position.x, start.pose.position.z};
// Store x-y and x-z coordinates of goal point
std::array<double, 2> goal_xy = {goal.pose.position.x, goal.pose.position.y};
std::array<double, 2> goal_xz = {goal.pose.position.x, goal.pose.position.z};
// x-y and x-z coordinates of interpolated points
std::array<std::vector<double>, 2> points_xy = linearInterp(start_xy, goal_xy, step);
std::array<std::vector<double>, 2> points_xz = linearInterp(start_xz, goal_xz, step);
// Number of generated points by interpolation
int num_points = points_xy.at(0).size();
try
{
// Generate PoseStamped message from std::array
for(int i=0; i<num_points; ++i)
{
geometry_msgs::PoseStamped pose;
pose.pose.orientation = start.pose.orientation;
pose.pose.position.x = points_xy.front().at(i);
pose.pose.position.y = points_xy.back().at(i);
pose.pose.position.z = points_xz.back().at(i);
bilinear_path.push_back(pose);
}
}
catch (std::out_of_range &ex)
{
ROS_ERROR("%s", ex.what());
}
return bilinear_path;
}
/**
* @brief Perform linear interpolation
* @param p1 First point
* @param p2 Second point
* @param step Step size of interpolation
* @return Array of interpolated points in the shape of [x-points, y-points]
*/
std::array<std::vector<double>, 2> linearInterp(const std::array<double, 2> &p1,
const std::array<double, 2> &p2, const double step)
{
// Gradient
double a = (p1.at(1) - p2.at(1)) / (p1.at(0) - p2.at(0));
// Intercept
double b = p1.at(1) - a*p1.at(0);
// Number of steps
int num_steps = std::floor((p2.at(0) - p1.at(0))/step);
// Initialize container for interpolated points
std::vector<double> points_x(num_steps+1);
// Set interpolated points
points_x.front() = p1.at(0);
for(int i=1; i<num_steps; ++i)
{
points_x.at(i) = step * i + p1.at(0);
}
points_x.back() = p2.at(0);
// Initialize container for interpolated points
std::vector<double> points_y(num_steps+1);
// Set interpolated points
points_y.front() = p1.at(1);
for(int i=1; i<num_steps; ++i)
{
points_y.at(i) = a*(p1.at(0) + i*step) + b;
}
points_y.back() = p2.at(1);
// Initialize container for vector of points
std::array<std::vector<double>, 2> points;
points.front() = points_x;
points.back() = points_y;
return points;
}
/**
* @brief Convert MultiDOFJointTrajectoryPoint message to PoseStamped message
* @param trajectory_pt MultiDOFJointTrajectoryPoint message
* @return Pose converted from Trajectory msg
*/
geometry_msgs::PoseStamped getPoseFromTrajectory(const trajectory_msgs::MultiDOFJointTrajectoryPoint &trajectory_pt)
{
geometry_msgs::PoseStamped pose;
pose.header.stamp = ros::Time::now();
pose.pose.position.x = trajectory_pt.transforms.front().translation.x;
pose.pose.position.y = trajectory_pt.transforms.front().translation.y;
pose.pose.position.z = trajectory_pt.transforms.front().translation.z;
pose.pose.orientation = trajectory_pt.transforms.front().rotation;
return pose;
}
/**
* @brief Returns true if drone is reached goal
* @param goal
* @param tolerance Consider drone reached goal if drone is within the circle with diameter of this value
* @return True if drone is reched goal, false if else
*/
inline bool isGoalReached(const geometry_msgs::PoseStamped &goal, const double tolerance=0.1)
{
double error = std::sqrt(std::pow(goal.pose.position.x - current_pose_.pose.position.x, 2)
+ std::pow(goal.pose.position.y - current_pose_.pose.position.y, 2)
+ std::pow(goal.pose.position.z - current_pose_.pose.position.z, 2));
return error < tolerance ? true : false;
}
/**
* @brief Callback for local position subscriber
* @msg Incoming message
*/
void localPosCb(const geometry_msgs::PoseStamped::ConstPtr &msg)
{
current_pose_ = *msg;
}
/**
* @brief Callback for state subscriber
* @msg Incoming message
*/
void stateCb(const mavros_msgs::State::ConstPtr &msg)
{
current_state_ = *msg;
}
};
int main(int argv, char **argc)
{
ros::init(argv, argc, "drone_controller");
DroneController controller("iris_group_controller/follow_multi_dof_joint_trajectory");
ros::spin();
return 0;
}
|
#include <vector>
#include <array>
#include <string>
#include <cmath>
#include <ros/ros.h>
#include <geometry_msgs/Transform.h>
#include <geometry_msgs/PoseStamped.h>
#include <moveit_msgs/ExecuteTrajectoryAction.h>
#include <moveit_msgs/RobotTrajectory.h>
#include <mavros_msgs/State.h>
#include <trajectory_msgs/MultiDOFJointTrajectory.h>
#include <trajectory_msgs/MultiDOFJointTrajectoryPoint.h>
#include <actionlib/server/simple_action_server.h>
必要なヘッダファイルをインクルードします。 コントローラ内でアクションサーバを実装するので、actionlibのヘッダファイルもインクルードしています。
//! Action name of ExecuteTrajectory
std::string action_name_;
//! Server of ExecuteTrajectoryAction
actionlib::SimpleActionServer<moveit_msgs::ExecuteTrajectoryAction> as_;
//! Feedback message of ExecuteTrajectoryAction
moveit_msgs::ExecuteTrajectoryFeedback feedback_;
//! Result message of ExecuteTrajectoryAction
moveit_msgs::ExecuteTrajectoryResult result_;
アクションサーバとサーバ名、サーバが使用するフィードバックとリザルトのメッセージを初期化します。 FollowMultiDOFJointTrajectoryインターフェースは、ExecuteTrajectory アクションを使用するので、アクションサーバを宣言する際のテンプレート引数として与えています。
//! Position command publisher
ros::Publisher cmd_pos_pub_;
位置指令を送信するためのパブリッシャです。
//! Subscriber of local position
ros::Subscriber local_pos_sub_;
//! Subscriber of UAV state
ros::Subscriber state_sub_;
//! Current pose of UAV
geometry_msgs::PoseStamped current_pose_;
//! Current state of UAV
mavros_msgs::State current_state_;
ドローンの現在位置と状態を取得するためのパブリッシャとそれを格納するための変数です。
DroneController(std::string action_name) :
action_name_(action_name),
as_(nh_, action_name, boost::bind(&DroneController::executeCb, this, _1), false)
{
as_.start();
// Position command publisher setup
std::string cmd_pos_topic;
nh_.param<std::string>("mavros_setpoint_topic", cmd_pos_topic, "/mavros/setpoint_position/local");
cmd_pos_pub_ = nh_.advertise<geometry_msgs::PoseStamped>(cmd_pos_topic, 10);
// Local position subscriber setup
std::string local_pos_topic;
nh_.param<std::string>("mavros_localpos_topic", local_pos_topic, "/mavros/local_position/pose");
auto local_pos_cb = boost::bind(&DroneController::localPosCb, this, _1);
local_pos_sub_ = nh_.subscribe<geometry_msgs::PoseStamped>(local_pos_topic, 10, local_pos_cb);
// UAV state subscriber setup
std::string state_topic;
nh_.param<std::string>("mavros_state_topic", state_topic, "/mavros/state");
auto state_cb = boost::bind(&DroneController::stateCb, this, _1);
state_sub_ = nh_.subscribe<mavros_msgs::State>(state_topic, 10, state_cb);
ROS_INFO("Action server initialized.");
};
DroneController
クラスのコンストラクタです。
内部ではパブリッシャとサブスクライバ、アクションサーバの初期化を行っています。
使用するトピック名は、ROSの パラメータサーバから取得 します。
サブスクライバとアクションサーバはコールバック関数を使用するので、メソッドをコールバック関数に指定するためにboost::functionオブジェクトを受け取る 関数 と、コンストラクタ をそれぞれ使用して初期化しています。
void executeCb(const moveit_msgs::ExecuteTrajectoryGoalConstPtr &goal)
アクションのコールバック関数です。 アクションゴールが届いたタイミングで実行されます。
std::vector<trajectory_msgs::MultiDOFJointTrajectoryPoint> trajectory;
trajectory = goal->trajectory.multi_dof_joint_trajectory.points;
アクションゴールは、moveit_msgs/RobotTrajectory 型のメッセージなので、そこから経由点の配列を取り出しています。
ROS_INFO("Waiting for FCU connection...");
while (ros::ok() and not current_state_.connected)
{
ros::spinOnce();
rate.sleep();
}
ROS_INFO("FCU connection established.");
ドローンに接続するまで待機します。
for(int i=0; i<10; ++i)
{
cmd_pos_pub_.publish(getPoseFromTrajectory(trajectory.front()));
}
OFFBOARDモードに変更するためには予め setpoint_position/local
トピックにメッセージがパブリッシュされている必要があるので、予めパブリッシュしておきます。
for(int i=0; i < trajectory.size()-1; ++i)
{
ROS_INFO("Moving to waypoint No. %d", i);
各経由点を処理していきます。
feedback_.state = std::to_string(i);
as_.publishFeedback(feedback_);
現在処理している経由点の番号をアクションのフィードバックとして登録します。
geometry_msgs::PoseStamped start = getPoseFromTrajectory(trajectory.at(i));
geometry_msgs::PoseStamped goal = getPoseFromTrajectory(trajectory.at(i+1));
trajectory_msgs/MultiDOFJointTrajectoryPoint
メッセージを、geometry_msgs/PoseStamped
メッセージに変換します。
std::vector<geometry_msgs::PoseStamped> path = getBilinearPath(start, goal);
バイリニア補間 を行って経由点間の点の配列を取得します。
for(auto pose: path)
取得した配列の各点について処理を行います。
while(not isGoalReached(pose) and not as_.isPreemptRequested())
{
cmd_pos_pub_.publish(pose);
rate.sleep();
}
ドローンが次の点に到達するか、アクションを中止するリクエストが届くまで目標位置を送信し続けます。
if(as_.isPreemptRequested() or not ros::ok())
{
result_.error_code.val = moveit_msgs::MoveItErrorCodes::PREEMPTED;
as_.setPreempted();
ROS_INFO("Action preempted.");
break;
}
アクションの中止がリクエストされた時には、状態を反映してアクションサーバを終了します。
result_.error_code.val = moveit_msgs::MoveItErrorCodes::SUCCESS;
as_.setSucceeded(result_);
ROS_INFO("Action completed.");
ゴールまで移動したので結果を反映してクライアントへと送信します。
std::vector<geometry_msgs::PoseStamped> getBilinearPath(const geometry_msgs::PoseStamped &start,
const geometry_msgs::PoseStamped &goal,
const double step=0.05)
{
std::vector<geometry_msgs::PoseStamped> bilinear_path;
// Store x-y and x-z coordinates of start point
std::array<double, 2> start_xy = {start.pose.position.x, start.pose.position.y};
std::array<double, 2> start_xz = {start.pose.position.x, start.pose.position.z};
// Store x-y and x-z coordinates of goal point
std::array<double, 2> goal_xy = {goal.pose.position.x, goal.pose.position.y};
std::array<double, 2> goal_xz = {goal.pose.position.x, goal.pose.position.z};
// x-y and x-z coordinates of interpolated points
std::array<std::vector<double>, 2> points_xy = linearInterp(start_xy, goal_xy, step);
std::array<std::vector<double>, 2> points_xz = linearInterp(start_xz, goal_xz, step);
// Number of generated points by interpolation
int num_points = points_xy.at(0).size();
try
{
// Generate PoseStamped message from std::array
for(int i=0; i<num_points; ++i)
{
geometry_msgs::PoseStamped pose;
pose.pose.orientation = start.pose.orientation;
pose.pose.position.x = points_xy.front().at(i);
pose.pose.position.y = points_xy.back().at(i);
pose.pose.position.z = points_xz.back().at(i);
bilinear_path.push_back(pose);
}
}
catch (std::out_of_range &ex)
{
ROS_ERROR("%s", ex.what());
}
return bilinear_path;
}
geometry_msgs/PoseStamped
型で表されたスタート地点とゴール地点の座標を受け取って、バイリニア補間を行い、補間された点の配列を返す関数です。
std::array<std::vector<double>, 2> linearInterp(const std::array<double, 2> &p1,
const std::array<double, 2> &p2, const double step)
{
// Gradient
double a = (p1.at(1) - p2.at(1)) / (p1.at(0) - p2.at(0));
// Intercept
double b = p1.at(1) - a*p1.at(0);
// Number of steps
int num_steps = std::floor((p2.at(0) - p1.at(0))/step);
// Initialize container for interpolated points
std::vector<double> points_x(num_steps+1);
// Set interpolated points
points_x.front() = p1.at(0);
for(int i=1; i<num_steps; ++i)
{
points_x.at(i) = step * i + p1.at(0);
}
points_x.back() = p2.at(0);
// Initialize container for interpolated points
std::vector<double> points_y(num_steps+1);
// Set interpolated points
points_y.front() = p1.at(1);
for(int i=1; i<num_steps; ++i)
{
points_y.at(i) = a*(p1.at(0) + i*step) + b;
}
points_y.back() = p2.at(1);
// Initialize container for vector of points
std::array<std::vector<double>, 2> points;
points.front() = points_x;
points.back() = points_y;
return points;
}
二点の二次元座標を受け取って線形補間を行い、補間された点の座標の配列を返す関数です。
geometry_msgs::PoseStamped getPoseFromTrajectory(const trajectory_msgs::MultiDOFJointTrajectoryPoint &trajectory_pt)
{
geometry_msgs::PoseStamped pose;
pose.header.stamp = ros::Time::now();
pose.pose.position.x = trajectory_pt.transforms.front().translation.x;
pose.pose.position.y = trajectory_pt.transforms.front().translation.y;
pose.pose.position.z = trajectory_pt.transforms.front().translation.z;
pose.pose.orientation = trajectory_pt.transforms.front().rotation;
return pose;
}
trajectory_msgs/MultiDOFJointTrajectoryPoint
メッセージを geometry_msgs/PoseStamped
メッセージへ変更して返す関数です。
inline bool isGoalReached(const geometry_msgs::PoseStamped &goal, const double tolerance=0.1)
{
double error = std::sqrt(std::pow(goal.pose.position.x - current_pose_.pose.position.x, 2)
+ std::pow(goal.pose.position.y - current_pose_.pose.position.y, 2)
+ std::pow(goal.pose.position.z - current_pose_.pose.position.z, 2));
return error < tolerance ? true : false;
}
ドローンが目標位置に到達したかどうかを判断する関数です。 デフォルトでは、目標位置を中心とした0.1 m以内の球体内にドローンが存在する場合に目標位置へ到達したと判定します。
void localPosCb(const geometry_msgs::PoseStamped::ConstPtr &msg)
{
current_pose_ = *msg;
}
void stateCb(const mavros_msgs::State::ConstPtr &msg)
{
current_state_ = *msg;
}
ドローンの位置と状態のコールバック関数です。 それぞれメンバ変数に取得したメッセージを格納しています。
int main(int argv, char **argc)
{
ros::init(argv, argc, "drone_controller");
DroneController controller("iris_group_controller/follow_multi_dof_joint_trajectory");
ros::spin();
return 0;
}
drone_controller
ノード内でコントローラを初期化します。
FollowMultiDOFJointTrajectoryインターフェースが利用するメッセージは、iris_group_controller/follow_multi_dof_joint_trajectory
以下でやりとりされるので、これをコントローラのコンストラクタへ与えます。