自作ノードを実行する(C++)

この章ではシミュレーション上のドローンを操作するROSノードをC++で作成します。

ROSパッケージを作る

はじめに、自作ノードを格納するためのROSパッケージを作ります。 パッケージ名はpx4_sim_pkgとします。 また、roscpp、geometry_msgs、mavros_msgsパッケージを使用するので依存パッケージに追加しておきます。

cd ~/catkin_ws/src
catkin_create_pkg px4_sim_pkg roscpp geometry_msgs mavros_msgs

ノードを書く

それでは実際にノードを書いてみましょう。 今回は、ドローンを2m上空に離陸させるノードを書きます。 以下のコードは、 MAVROS Offboard control example - PX4 Developer Guide にあるのと同じものです。

このコードを offboard_sample.cpp という名前で、 px4_sim_pkg/src に保存してください。

 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
/**
 * @file offb_node.cpp
 * @brief Offboard control example node, written with MAVROS version 0.19.x, PX4 Pro Flight
 * Stack and tested in Gazebo SITL
 */

#include <ros/ros.h>
#include <geometry_msgs/PoseStamped.h>
#include <mavros_msgs/CommandBool.h>
#include <mavros_msgs/SetMode.h>
#include <mavros_msgs/State.h>

mavros_msgs::State current_state;
void state_cb(const mavros_msgs::State::ConstPtr& msg){
    current_state = *msg;
}

int main(int argc, char **argv)
{
    ros::init(argc, argv, "offb_node");
    ros::NodeHandle nh;

    ros::Subscriber state_sub = nh.subscribe<mavros_msgs::State>
            ("mavros/state", 10, state_cb);
    ros::Publisher local_pos_pub = nh.advertise<geometry_msgs::PoseStamped>
            ("mavros/setpoint_position/local", 10);
    ros::ServiceClient arming_client = nh.serviceClient<mavros_msgs::CommandBool>
            ("mavros/cmd/arming");
    ros::ServiceClient set_mode_client = nh.serviceClient<mavros_msgs::SetMode>
            ("mavros/set_mode");

    //the setpoint publishing rate MUST be faster than 2Hz
    ros::Rate rate(20.0);

    // wait for FCU connection
    while(ros::ok() && !current_state.connected){
        ros::spinOnce();
        rate.sleep();
    }

    geometry_msgs::PoseStamped pose;
    pose.pose.position.x = 0;
    pose.pose.position.y = 0;
    pose.pose.position.z = 2;

    //send a few setpoints before starting
    for(int i = 100; ros::ok() && i > 0; --i){
        local_pos_pub.publish(pose);
        ros::spinOnce();
        rate.sleep();
    }

    mavros_msgs::SetMode offb_set_mode;
    offb_set_mode.request.custom_mode = "OFFBOARD";

    mavros_msgs::CommandBool arm_cmd;
    arm_cmd.request.value = true;

    ros::Time last_request = ros::Time::now();

    while(ros::ok()){
        if( current_state.mode != "OFFBOARD" &&
            (ros::Time::now() - last_request > ros::Duration(5.0))){
            if( set_mode_client.call(offb_set_mode) &&
                offb_set_mode.response.mode_sent){
                ROS_INFO("Offboard enabled");
            }
            last_request = ros::Time::now();
        } else {
            if( !current_state.armed &&
                (ros::Time::now() - last_request > ros::Duration(5.0))){
                if( arming_client.call(arm_cmd) &&
                    arm_cmd.response.success){
                    ROS_INFO("Vehicle armed");
                }
                last_request = ros::Time::now();
            }
        }

        local_pos_pub.publish(pose);

        ros::spinOnce();
        rate.sleep();
    }

    return 0;
}

コードの解説

#include <ros/ros.h>
#include <geometry_msgs/PoseStamped.h>
#include <mavros_msgs/CommandBool.h>
#include <mavros_msgs/SetMode.h>
#include <mavros_msgs/State.h>

mavrosパッケージで提供されるトピックやサービスを使うためのメッセージは mavros_msgs パッケージで定義されています。

ros::init(argc, argv, "offb_node");
ros::NodeHandle nh;

ROSノードを offb_node という名前で初期化しています。

ros::Subscriber state_sub = nh.subscribe<mavros_msgs::State>
        ("mavros/state", 10, state_cb);

mavros/state トピックにパブリッシュされる、 mavros_msgs::State 型のメッセージをサブスクライブするためのサブスクライバを初期化しています。 第二引数は受け取ったメッセージを保持しておくキューのサイズです。 第三引数の state_cb は受け取ったメッセージを処理するためのコールバック関数です。 state_cb は次のように定義されています。

mavros_msgs::State 型のメッセージには、機体の接続状況やモータの動作状況などが格納されています。

mavros_msgs::State current_state;
void state_cb(const mavros_msgs::State::ConstPtr& msg){
    current_state = *msg;
}

state_cbconst mavros_msgs::State::ConstPtr& 型の変数を受け取り、それを mavros_msgs::State 型のグローバル変数 current_state に格納する関数です。 コールバック関数では、 パッケージ名::メッセージ型::ConstPtr& のような型の変数を受け取るようにするのが一般的です。 細かいことはC++の本で勉強しましょう。

ros::Publisher local_pos_pub = nh.advertise<geometry_msgs::PoseStamped>
        ("mavros/setpoint_position/local", 10);

mavros/setpoint_position/local トピックに geometry_msgs::PoseStamped 型のメッセージをパブリッシュするパブリッシャを初期化しています。 第二引数は受け取ったメッセージを保持しておくキューのサイズです。

ros::ServiceClient arming_client = nh.serviceClient<mavros_msgs::CommandBool>
        ("mavros/cmd/arming");
ros::ServiceClient set_mode_client = nh.serviceClient<mavros_msgs::SetMode>
        ("mavros/set_mode");

モータの起動に用いる mavros/cmd/arming と モードの変更に用いる mavros/set_mode サービスのクライアントを初期化しています。 それぞれ、 mavros_msgs::CommandBool 型と mavros_msgs::SetMode 型のメッセージを使います。

ros::Rate rate(20.0);

Offboardコマンドのタイムアウト時間は500msなので、2Hz以上の周波数で機体にコマンドを送る必要があります。 また、Offboardコマンドがタイムアウトした場合は、その直前の飛行モードに切り替わるので、PositionモードからOffboardモードに切り替えるようにしておけば、Offboardモードへの移行が失敗してもその場にとどまってくれます。

while(ros::ok() && !current_state.connected){
    ros::spinOnce();
    rate.sleep();
}

FCUと接続するまで待ちます。 current_state.connected はFCUと接続されているときに true になるので、接続されるとwhileループから抜けます。 ros::spinOnce() を使ってループ中でもコールバック関数がきちんと呼ばれるようにします。 また、 rate.sleep() は、このノードが20Hzで動作するように必要な時間だけノードを停止します。

geometry_msgs::PoseStamped pose;
pose.pose.position.x = 0;
pose.pose.position.y = 0;
pose.pose.position.z = 2;

//send a few setpoints before starting
for(int i = 100; ros::ok() && i > 0; --i){
    local_pos_pub.publish(pose);
    ros::spinOnce();
    rate.sleep();
}

あらかじめ移動先の位置をパブリッシュしておきます。 移動先の位置をパブリッシュしておかないとOffboardモードへの移行がリジェクトされます。 ここでは、移動先の位置は上方向に2mとなっています。 geometry_msgs::PoseStamped の各フィールドについては geometry_msgs/PoseStamped Message を見てください。

mavros_msgs::SetMode offb_set_mode;
offb_set_mode.request.custom_mode = "OFFBOARD";

SetMode メッセージの custom_mode フィールドを OFFBOARD にします。 このメッセージをサービスサーバに送信することでモードの切り替えをおこないます。 mavros_msgs::SetMode メッセージの定義は mavros_msgs/SetMode Service から見れます。

mavros_msgs::CommandBool arm_cmd;
arm_cmd.request.value = true;

/mavros/cmd/arming サービスへ送信するためのメッセージを作成します。 mavros_msgs::CommandBool メッセージの定義は mavros_msgs/CommandBool Service にあります。 mavrosパッケージで使用するメッセージの定義は、 mavrosのROS wikiページ から見ることができます。

 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
ros::Time last_request = ros::Time::now();

while(ros::ok()){
    if( current_state.mode != "OFFBOARD" &&
        (ros::Time::now() - last_request > ros::Duration(5.0))){

        if( set_mode_client.call(offb_set_mode) &&
            offb_set_mode.response.mode_sent){
            ROS_INFO("Offboard enabled");
        }
        last_request = ros::Time::now();
    } else {
        if( !current_state.armed &&
            (ros::Time::now() - last_request > ros::Duration(5.0))){
            if( arming_client.call(arm_cmd) &&
                arm_cmd.response.success){
                ROS_INFO("Vehicle armed");
            }
            last_request = ros::Time::now();
        }
    }

    local_pos_pub.publish(pose);

    ros::spinOnce();
    rate.sleep();
}

最初のif節は現在のモードが OFFBOARD かつ、モード切替が行われたのが5秒以上前であるときに実行されます。 最後のモード切替からの経過時間は last_request 変数を使って判断しています。

set_mode_client.call(offb_set_mode) で、 offb_set_mode 変数のメッセージを使って mavros/set_mode サービスをコールしています。 サービスをコールした場合、サービスコールが成功すると true が帰ってきて、失敗すると false が帰ってきます。なので、戻り値を調べることでサービスコールが成功したかを知ることができます。 また、サービスにはリクエストのフィールド(サービスコールの際に必要な情報を与えるために使う)と、レスポンスのフィールド(処理結果等を格納するのに使う)があり、今回はレスポンスの mode_sent を調べることでモード切り替えの命令がきちんと認識され、送信されたかを判断しています。

モードが OFFBOARD の時は機体にモータを起動するように命令を送っています。 この部分の処理はOffboardコマンドを送るところとほぼ同じです。

最後に、ループ毎に目標位置をパブリッシュしています。

CMakeLists.txtの編集

次にCMakeLists.txtの編集をおこないます。 CMakeLists.txtの末尾に以下の行を追加します。

add_executable(offboard_sample src/offboard_sample.cpp)
target_link_libraries(offboard_sample ${catkin_LIBRARIES})

ビルドする

cd ~/catkin_ws
catkin_make

ノードの起動

はじめにシミュレータを起動します。 ROSノードとの通信を行うためにはmavrosが必要なので、 mavros_posix_sitl.launch を使います。

roslaunch px4 mavros_posix_sitl.launch

次に先ほど作成したノードを起動します。

rosrun px4_sim_pkg offboard_sample
../_images/runningnode_cpp.png

launchファイルを書く

シミュレータとOffboardノードを別々に起動するのは面倒なので、launchファイルにまとめて一つのコマンドで起動できるようにしましょう。

px4_sim_pkg/launch 以下に cpp_offb_sample.launch という名前で以下の内容を保存してください。

<launch>

    <include file="$(find px4)/launch/mavros_posix_sitl.launch" />
    <node name="offb_node" pkg="px4_sim_pkg" type="offboard_sample" />

</launch>

以下のコマンドを使ってこのlaunchファイルからシミュレータとOffboardノードを起動できます。

roslaunch px4_sim_pkg cpp_offb_sample.launch

参考

Callbacks and Spinning - ROS Wiki

ros::spinOnce()について

mavros - ROS Wiki

mavrosのROS Wikiページ