keimlab’s diary

KEIMLABの電気・電子・制御の開発備忘録と日常生活記録

ROS上でプログラムを書いてみる

はじめに

前回の記事にて、ROSをインストールできたので、プログラムを動かしてみたいと思います。 ROSの基本動作であるPublishとSubscribeのプログラムを動かします。

環境

・ボード : Raspberry Pi 3
・OS : Raspbian/Buster
・ROSディストリビューション : Kinetic
※ROSのセットアップはこちらで行ったものを使っています。
keimlab.hatenablog.com

catkinワークスペースを作成する

ROSではcatkinワークスペースというディレクトリにて作業を行うのが推奨されているようで、その下に様々なパッケージをぶら下げてプログラムを管理するようになっています。まずは、catkinワークスペースを作成します。

mkdir -p ~/catkin_ws/src
cd ~/catkin_ws/src
catkin_init_workspace
catkin_make

パッケージを作成する

次に、catkinワークスペースにテスト用のパッケージを追加します。catkin_create_pkgの1つ目の引数がパッケージ名

catkin_create_pkg hello_world roscpp rospy std_msgs

パッケージを作成すると、以下のようなファイル・ディレクトリが生成されます。

f:id:keimlab:20200501144944p:plain
ファイルツリー構造

プログラムを作成する

publish、subscribeでプロセス間での情報の授受をするプログラムで、プログラミングの世界でいう『Hello World!』の代替にします。公式サイトのTutorialから引用させてもらいますが、以下のプログラムを作成し、下図黄色部のように配置します。

f:id:keimlab:20200501155245p:plain
hello_world パッケージファイル構成

// File name : talker.cpp
#include "ros/ros.h"
#include "std_msgs/String.h"
#include <sstream>

int main(int argc, char **argv){
  ros::init(argc, argv, "talker");
  ros::NodeHandle n;
  ros::Publisher chatter_pub = n.advertise<std_msgs::String>("chatter", 1000);
  ros::Rate loop_rate(10);

  int count = 0;
  while (ros::ok()){
    std_msgs::String msg;
    std::stringstream ss;
    ss << "hello world " << count;
    msg.data = ss.str();
    ROS_INFO("%s", msg.data.c_str());
    chatter_pub.publish(msg);
    ros::spinOnce();
    loop_rate.sleep();
    ++count;
  }

  return 0;
}
// File name : listener.cpp
#include "ros/ros.h"
#include "std_msgs/String.h"

void chatterCallback(const std_msgs::String::ConstPtr& msg){
  ROS_INFO("I heard: [%s]", msg->data.c_str());
}

int main(int argc, char **argv){
  ros::init(argc, argv, "listener");
  ros::NodeHandle n;
  ros::Subscriber sub = n.subscribe("chatter", 1000, chatterCallback);
  ros::spin();
  
  return 0;
}

メイクファイルを編集する

書いたソースをコンパイル・リンクするように、~/catkin_ws/src/hello_world/CMakeLists.txt(上図のオレンジ部)に以下の記述を追加します。

add_executable(talker src/talker.cpp)
target_link_libraries(talker ${catkin_LIBRARIES})
add_dependencies(talker hello_world_generate_messages_cpp)

add_executable(listener src/listener.cpp)
target_link_libraries(listener ${catkin_LIBRARIES})
add_dependencies(listener hello_world_generate_messages_cpp)

catkin_makeする

catkinワークスペースのルートディレクトリに戻って、makeをします。これで、書いたソースコードコンパイル・リンクされて実行形式のファイルができます。普通のCプログラムのコンパイル時より、エラーメッセージが出ても少々見づらいですが、これはやむなし...

cd ~/catkin_ws
catkin_make

実行する

makeが通ったら、実行してみましょう。今回作成したプログラムは2つのノード間でやり取りするプログラムなので、ターミナルを2つ起動して、実行します。

まずは、最初のターミナルで、roscoreを実行しておき、listener側のプログラムを実行します。 このとき、roscoreを普通に実行してしまうとターミナルのコントロールを失ってしまいますので、以下のように実行してバックグラウンドで実行させるようにします (これが推奨されるのかどうかは...?)。こうすることで無駄にターミナル画面を表示させておく必要がなくなります。roscoreの出力は、~/catkin_ws/roscore.txtに出力されますので、何か気になることがあれば、こちらを参照しましょう。

nohup roscore >> ~/catkin_ws/roscore.txt &

ちなみに、roscoreを終了させたいときには、以下のようにします。

ps aux | grep roscore

これを入力すると以下のような出力を得られます。

username    3808  6.4  3.0  62168 28448 pts/0    Sl   16:07   0:02 /usr/bin/python /opt/ros/melodic/bin/roscore
username    3850  0.0  0.0   5228   596 pts/0    S+   16:08   0:00 grep --color=auto roscore

この出力の中の1行目がroscoreのプロセスを指しています。左から2番目の項目がプロセスIDで、これ使って、プロセスを殺します。上記の例でいうと、3808がroscoreのプロセスIDで、以下のようにコマンドを入力します。

kill -INT 3808

roscoreが起動できたら、先ほどcatkin_makeしてできたhello_worldパッケージのlistenerを実行します。

rosrun hello_world listener

これを実行した直後には何も起きません。このままの状態にして、次に、2つ目のターミナルで、talker側のプログラムを実行します。すると以下のような出力を得ることができ、最初に起動したターミナルの方を見てみると、listener側のプログラムにも表示が随時更新されます。ここまでできればROSプログラムの実行が成功です。(私の環境では、listener側のプログラムで、なぜか最初の3つの内容が取りこぼされてしまっていますが...)。終了させるには、Ctrl + cにて終了させます。

$ rosrun hello_world talker
[ INFO] [1588317288.437682266]: hello world 0
[ INFO] [1588317288.537732649]: hello world 1
[ INFO] [1588317288.637634388]: hello world 2
[ INFO] [1588317288.737640345]: hello world 3
[ INFO] [1588317288.837654010]: hello world 4
[ INFO] [1588317288.937638873]: hello world 5
[ INFO] [1588317289.037641393]: hello world 6
[ INFO] [1588317289.137640267]: hello world 7
[ INFO] [1588317289.237632422]: hello world 8
[ INFO] [1588317289.337640775]: hello world 9
[ INFO] [1588317289.437630378]: hello world 10
$ rosrun hello_world listener
[ INFO] [1588317501.555192296]: I heard: [hello world 3]
[ INFO] [1588317501.654821160]: I heard: [hello world 4]
[ INFO] [1588317501.754862987]: I heard: [hello world 5]
[ INFO] [1588317501.854811639]: I heard: [hello world 6]
[ INFO] [1588317501.954760863]: I heard: [hello world 7]
[ INFO] [1588317502.054814930]: I heard: [hello world 8]
[ INFO] [1588317502.154751812]: I heard: [hello world 9]
[ INFO] [1588317502.254813431]: I heard: [hello world 10]
[ INFO] [1588317502.354735208]: I heard: [hello world 11]
[ INFO] [1588317502.454727088]: I heard: [hello world 12]
[ INFO] [1588317502.554747875]: I heard: [hello world 13]
[ INFO] [1588317502.654783088]: I heard: [hello world 14]
[ INFO] [1588317502.754856738]: I heard: [hello world 15]
[ INFO] [1588317502.854771953]: I heard: [hello world 16]
[ INFO] [1588317502.954808781]: I heard: [hello world 17]

おわりに

ここまで見ていただき、ありがとうございました。プロセス間通信をしようと思ったら、もっとたくさんコードを書かなきゃいけないようなイメージを持っていましたが、ROSを使うとお手軽にプロセス間の情報共有が楽にできそうですね。これが複数のノード間でもできるというようで、これから挑戦していきたいと思います。ここまで、ネット検索して多くの先人たちの知恵を参考にさせていただきましたが、無償で公開してくれている皆さんに感謝です。私も皆さんに役立つような知恵を発信していきたいですね。

参考サイト

wiki.ros.org qiita.com

公開日 : 2020年5月1日