コード解説(コントローラ)
---------------------------------------------------------------------------

.. literalinclude:: ../../../src/drone_controller.cpp
    :linenos:
    :language: cpp
    :caption: drone_controller.cpp

.. code-block:: cpp

    #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のヘッダファイルもインクルードしています。

.. code-block:: cpp

    //! 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 <http://docs.ros.org/api/moveit_msgs/html/action/ExecuteTrajectory.html>`_ アクションを使用するので、アクションサーバを宣言する際のテンプレート引数として与えています。

.. code-block:: cpp

    //! Position command publisher
    ros::Publisher cmd_pos_pub_;

位置指令を送信するためのパブリッシャです。

.. code-block:: cpp

    //! 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_;

ドローンの現在位置と状態を取得するためのパブリッシャとそれを格納するための変数です。

.. code-block:: cpp

    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の `パラメータサーバから取得 <http://wiki.ros.org/roscpp/Overview/Parameter%20Server>`_ します。

サブスクライバとアクションサーバはコールバック関数を使用するので、メソッドをコールバック関数に指定するためにboost::functionオブジェクトを受け取る `関数 <http://docs.ros.org/jade/api/roscpp/html/classros_1_1NodeHandle.html#a93b0fe95db250c45fdfbc5fd9f4c0159>`_ と、`コンストラクタ <http://docs.ros.org/melodic/api/actionlib/html/classactionlib_1_1SimpleActionServer.html#a0f047d3a7474d3c2c1d3a0c7ef6adf16>`_ をそれぞれ使用して初期化しています。

.. code-block:: cpp

    void executeCb(const moveit_msgs::ExecuteTrajectoryGoalConstPtr &goal)

アクションのコールバック関数です。
アクションゴールが届いたタイミングで実行されます。

.. code-block:: cpp

    std::vector<trajectory_msgs::MultiDOFJointTrajectoryPoint> trajectory;
    trajectory = goal->trajectory.multi_dof_joint_trajectory.points;

アクションゴールは、`moveit_msgs/RobotTrajectory <http://docs.ros.org/melodic/api/moveit_msgs/html/msg/RobotTrajectory.html>`_ 型のメッセージなので、そこから経由点の配列を取り出しています。

.. code-block:: cpp

    ROS_INFO("Waiting for FCU connection...");
    while (ros::ok() and not current_state_.connected)
    {
        ros::spinOnce();
        rate.sleep();
    }
    ROS_INFO("FCU connection established.");

ドローンに接続するまで待機します。

.. code-block:: cpp

    for(int i=0; i<10; ++i)
    {
        cmd_pos_pub_.publish(getPoseFromTrajectory(trajectory.front()));
    }

OFFBOARDモードに変更するためには予め ``setpoint_position/local`` トピックにメッセージがパブリッシュされている必要があるので、予めパブリッシュしておきます。

.. code-block:: cpp

    for(int i=0; i < trajectory.size()-1; ++i)
    {
        ROS_INFO("Moving to waypoint No. %d", i);

各経由点を処理していきます。

.. code-block:: cpp

    feedback_.state = std::to_string(i);
    as_.publishFeedback(feedback_);

現在処理している経由点の番号をアクションのフィードバックとして登録します。

.. code-block:: cpp

    geometry_msgs::PoseStamped start = getPoseFromTrajectory(trajectory.at(i));
    geometry_msgs::PoseStamped goal = getPoseFromTrajectory(trajectory.at(i+1));

``trajectory_msgs/MultiDOFJointTrajectoryPoint`` メッセージを、``geometry_msgs/PoseStamped`` メッセージに変換します。

.. code-block:: cpp

    std::vector<geometry_msgs::PoseStamped> path = getBilinearPath(start, goal);

`バイリニア補間 <https://en.wikipedia.org/wiki/Bilinear_interpolation>`_ を行って経由点間の点の配列を取得します。

.. code-block:: cpp

    for(auto pose: path)

取得した配列の各点について処理を行います。

.. code-block:: cpp

    while(not isGoalReached(pose) and not as_.isPreemptRequested())
    {
        cmd_pos_pub_.publish(pose);
        rate.sleep();
    }

ドローンが次の点に到達するか、アクションを中止するリクエストが届くまで目標位置を送信し続けます。

.. code-block:: cpp

    if(as_.isPreemptRequested() or not ros::ok())
    {
        result_.error_code.val = moveit_msgs::MoveItErrorCodes::PREEMPTED;
        as_.setPreempted();
        ROS_INFO("Action preempted.");
        break;
    }

アクションの中止がリクエストされた時には、状態を反映してアクションサーバを終了します。

.. code-block:: cpp

    result_.error_code.val = moveit_msgs::MoveItErrorCodes::SUCCESS;
    as_.setSucceeded(result_);
    ROS_INFO("Action completed.");

ゴールまで移動したので結果を反映してクライアントへと送信します。

.. code-block:: cpp

    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`` 型で表されたスタート地点とゴール地点の座標を受け取って、バイリニア補間を行い、補間された点の配列を返す関数です。

.. code-block:: cpp

    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;
    }

二点の二次元座標を受け取って線形補間を行い、補間された点の座標の配列を返す関数です。

.. code-block:: cpp

    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`` メッセージへ変更して返す関数です。

.. code-block:: bash

    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以内の球体内にドローンが存在する場合に目標位置へ到達したと判定します。

.. code-block:: cpp

    void localPosCb(const geometry_msgs::PoseStamped::ConstPtr &msg)
    {
        current_pose_ = *msg;
    }

    void stateCb(const mavros_msgs::State::ConstPtr &msg)
    {
        current_state_ = *msg;
    }

ドローンの位置と状態のコールバック関数です。
それぞれメンバ変数に取得したメッセージを格納しています。

.. code-block:: cpp

    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`` 以下でやりとりされるので、これをコントローラのコンストラクタへ与えます。