Ubuntu 20.04 における ROS Noetic の導入と Topic/Service 通信の実践

1. ROS Noetic のインストール手順

1.1 ソフトウェアソースの設定

インストールを円滑に進めるため、システムのソフトウェアソースを国内サーバー(例:Aliyun)に変更します。 システム設定 → 「About」 → 「Software & Updates」 → 「Download from」 で Aliyun を選択し、「Reload」を実行します。

1.2 ROS Noetic のインストール

1.2.1 sources.list の追加

sudo sh -c 'echo "deb http://packages.ros.org/ros/ubuntu $(lsb_release -sc) main" > /etc/apt/sources.list.d/ros-latest.list'

1.2.2 キーの設定

sudo apt-key adv --keyserver 'hkp://keyserver.ubuntu.com:80' --recv-key C1CF6E31E6BADE8868B172B4F42ED6FBAB17C654

成功すると以下のメッセージが表示されます。

gpg: 総数処理数: 1
gpg: インポート: 1

1.2.3 パッケージリストの更新

sudo apt update

1.2.4 ROS Noetic フルパッケージのインストール

sudo apt install ros-noetic-desktop-full

ダウンロードに時間がかかる場合があります。パッケージのダウンロードに失敗した場合は、手順1.1に戻りソースを変更するか、ネットワーク接続を確認してください。

1.2.5 環境変数の設定

echo "source /opt/ros/noetic/setup.bash" >> ~/.bashrc
source ~/.bashrc

2. ROS の動作確認と Turtlesim の実行

新しいターミナルで roscore を起動し、ROS マスターとログ出力の準備を確認します。 続いて別のターミナルで rosrun turtlesim turtlesim_node を実行すると、亀のウィンドウが表示されます。 さらに別のターミナルで rosrun turtlesim turtle_teleop_key を起動し、矢印キーで亀を操作します。壁に衝突すると turtlesim_node 側にログが出力されます。

3. ROS の基本操作

# ROS マスター起動
roscore

# トピック一覧表示
rostopic list

# トピックデータの表示
rostopic echo /camera_raw

# トピックヘッダー表示
rostopic echo /camera_raw/header

# compressed 形式を raw 形式に変換
rosrun image_transport republish compressed in:=/Mate/camera/ raw out:=/Mate/camera_raw/

# トピックのパブリッシュレート制限(4Hz)
rosrun topic_tools throttle messages /Mate/camera_raw 4.0 /left

# rosbag の記録
rosbag record -O bagpath.bag /camera_raw /imu

# rosbag の再生(100倍速)
rosbag play -r 100 bagpath.bag

# rviz 起動
rviz

4. ROS の通信メカニズム

ROS の中核は分散通信フレームワークです。主要な通信方式は Topic、Service、そしてパラメータ共有の3つです。

4.1 トピック通信 (Topic)

パブリッシャー(Publisher)がメッセージを発行し、サブスクライバー(Subscriber)が受信する一方向・非同期の通信です。連続的なデータストリームに適しています。

  • Talker(パブリッシャー):データを送信
  • Listener(サブスクライバー):データを受信
  • ROS Master:ノード情報を管理し、同一トピック名のノード同士をマッチング

接続フロー:

  1. Publisher が Master に自身のトピック名とアドレスを登録
  2. Subscriber が Master に購読したいトピック名を登録
  3. Master が Publisher のアドレスを Subscriber に通知
  4. Subscriber が Publisher に接続要求を送信
  5. Publisher が応答し、TCP アドレス情報を送信
  6. TCP 接続確立後、Publisher から Subscriber へデータ送信

補足: 発行側と購読側の起動順序は任意です。トピック名が一致する必要があります。Master が停止しても、確立済みの接続は維持されます。

4.2 サービス通信 (Service)

クライアント(Client)がリクエストを送信し、サーバー(Server)が応答を返す同期型通信です。偶発的な処理や論理演算が必要な場面に適しています。

  • Client:リクエスト送信と応答受信
  • Server:リクエスト処理と応答生成
  • ROS Master:サービス名に基づき Client と Server をマッチング

接続フロー:

  1. Server が Master にサービス名を登録
  2. Client が Master にサービス名を登録
  3. Master が Server のアドレスを Client に通知
  4. Client が Server にリクエストを送信
  5. Server が処理し、応答を Client に返送

補足: クライアントはサーバーが起動している状態でリクエスト可能です。Server と Client は複数存在できます。

5. サービス通信のプログラミング実装

5.1 パッケージの作成

cd ~/ROS_ws/src
catkin_create_pkg communication_pkg rospy roscpp std_msgs std_srvs
cd ~/ROS_ws
catkin_make
source devel/setup.bash

5.2 サービスデータ定義

communication_pkg/srv/ ディレクトリを作成し、AddTwoInts.srv ファイルを作成します。

int64 a
int64 b
---
int64 sum

5.3 クライアントノードの実装

communication_pkg/src/client.cpp を作成し、以下のコードを記述します。

#include <cstdlib>
#include "ros/ros.h"
#include "communication_pkg/AddTwoInts.h"

int main(int argc, char **argv)
{
    ros::init(argc, argv, "add_two_ints_client");
    if(argc != 3)
    {
        ROS_INFO("usage: add_two_ints_client X Y");
        return 1;
    }

    ros::NodeHandle n;
    ros::ServiceClient client = n.serviceClient<communication_pkg::AddTwoInts>("add_two_ints");

    communication_pkg::AddTwoInts srv;
    srv.request.a = atoll(argv[1]);
    srv.request.b = atoll(argv[2]);

    if(client.call(srv))
    {
        ROS_INFO("Sum: %ld", (long int)srv.response.sum);
    }
    else
    {
        ROS_ERROR("Failed to call service add_two_ints");
        return 1;
    }
    return 0;
}

5.4 サーバーノードの実装

communication_pkg/src/server.cpp を作成します。

#include "ros/ros.h"
#include "communication_pkg/AddTwoInts.h"

bool add(communication_pkg::AddTwoInts::Request  &req,
         communication_pkg::AddTwoInts::Response &res)
{
    res.sum = req.a + req.b;
    ROS_INFO("request: x=%ld, y=%ld", (long int)req.a, (long int)req.b);
    ROS_INFO("sending back response: [%ld]", (long int)res.sum);
    return true;
}

int main(int argc, char **argv)
{
    ros::init(argc, argv, "add_two_ints_server");
    ros::NodeHandle n;
    ros::ServiceServer service = n.advertiseService("add_two_ints", add);
    ROS_INFO("Ready to add two ints.");
    ros::spin();
    return 0;
}

5.5 ビルド設定

CMakeLists.txt の該当箇所を編集します。

find_package(catkin REQUIRED COMPONENTS roscpp rospy std_msgs std_srvs message_generation)
add_service_files(FILES AddTwoInts.srv)
generate_messages(DEPENDENCIES std_msgs)

add_executable(server src/server.cpp)
target_link_libraries(server ${catkin_LIBRARIES})
add_dependencies(server ${PROJECT_NAME}_gencpp)

add_executable(client src/client.cpp)
target_link_libraries(client ${catkin_LIBRARIES})
add_dependencies(client ${PROJECT_NAME}_gencpp)

package.xml に依存関係を追加します。

<build_depend>message_generation</build_depend>
<exec_depend>message_runtime</exec_depend>

5.6 コンパイルと実行

cd ~/ROS_ws
catkin_make
source devel/setup.bash
# ターミナル1
roscore
# ターミナル2
rosrun communication_pkg server
# ターミナル3
rosrun communication_pkg client 3 5

6. トピック通信のプログラミング実装

6.1 パッケージの作成

cd ~/ROS_ws/src
catkin_create_pkg test_pkg rospy roscpp std_msgs std_srvs
cd ~/ROS_ws
catkin_make
source devel/setup.bash

6.2 カスタムメッセージ定義

test_pkg/msg/Person.msg を作成します。

string name
uint8 sex
uint8 age

uint8 unknown = 0
uint8 male = 1
uint8 female = 2

6.3 パブリッシャーの実装

test_pkg/src/publisher.cpp を作成します。

#include "ros/ros.h"
#include "test_pkg/Person.h"

int main(int argc, char **argv)
{
    ros::init(argc, argv, "person_publisher");
    ros::NodeHandle n;
    ros::Publisher pub = n.advertise<test_pkg::Person>("/person_info", 10);
    ros::Rate rate(1);

    while(ros::ok())
    {
        test_pkg::Person msg;
        msg.name = "Alice";
        msg.age  = 25;
        msg.sex  = test_pkg::Person::female;

        pub.publish(msg);
        ROS_INFO("Published: name=%s, age=%d, sex=%d", msg.name.c_str(), msg.age, msg.sex);
        rate.sleep();
    }
    return 0;
}

6.4 サブスクライバーの実装

test_pkg/src/subscriber.cpp を作成します。

#include "ros/ros.h"
#include "test_pkg/Person.h"

void callback(const test_pkg::Person::ConstPtr& msg)
{
    ROS_INFO("Received: name=%s, age=%d, sex=%d", msg->name.c_str(), msg->age, msg->sex);
}

int main(int argc, char **argv)
{
    ros::init(argc, argv, "person_subscriber");
    ros::NodeHandle n;
    ros::Subscriber sub = n.subscribe("/person_info", 10, callback);
    ros::spin();
    return 0;
}

6.5 ビルド設定

CMakeLists.txt を編集します。

find_package(catkin REQUIRED COMPONENTS roscpp rospy std_msgs message_generation)
add_message_files(FILES Person.msg)
generate_messages(DEPENDENCIES std_msgs)

add_executable(publisher src/publisher.cpp)
target_link_libraries(publisher ${catkin_LIBRARIES})
add_dependencies(publisher ${PROJECT_NAME}_generate_messages_cpp)

add_executable(subscriber src/subscriber.cpp)
target_link_libraries(subscriber ${catkin_LIBRARIES})
add_dependencies(subscriber ${PROJECT_NAME}_generate_messages_cpp)

package.xmlmessage_generationmessage_runtime の依存を追加します。

6.6 コンパイルと実行

cd ~/ROS_ws
catkin_make
source devel/setup.bash
# ターミナル1
roscore
# ターミナル2
rosrun test_pkg publisher
# ターミナル3
rosrun test_pkg subscriber

6.7 トピックの可視化

rqt_graph を実行すると、現在のノードとトピックの関係がグラフィカルに表示されます。

参考資料

  • ROS Wiki: http://wiki.ros.org/
  • ROS 通信チュートリアル

タグ: ROS Noetic Ubuntu 20.04 Topic Communication Service Communication C++

6月5日 22:52 投稿