スポンサー広告
※ご注意:古い情報が含まれる場合があります

本サイトの記事には、過去の技術情報や設定手順が含まれています。現在の環境では動作しない可能性もありますので、ご利用の際はご注意ください。

Docker 上で ROS2 ノードを実装する:乱数温度センサーで学ぶ pub/sub(前回の続き)

本記事は、前回構築した「Windows 11 + WSL2 + Docker Desktop」で ROS2(Humble)が実行できる環境の続きです。今回は Docker コンテナ内で ROS2 のノードを実装し、2種類の仮想温度センサー(乱数)subscriber を動かして、ROS2 の基本である「ノード」「トピック」「publisher」「subscriber」を体験します。

また、ROS2 ノード開発をより効率化するため、カスタム ROS2 イメージの作成と、永続化されたワークスペースを使った一発起動スクリプトも前準備として導入します。これにより、毎回エディタをインストールする必要がなく、安定した ROS2 開発が可能になります。

前回からの続きとなりますので、全体的な構成や具体的な環境構築方法は以下の前回の記事を参照してください。
Windows 11 で ROS2 を最速体験する:Docker環境構築ガイド


1. 前準備:カスタム ROS2 イメージを作り、永続化環境を整える

公式イメージ ros:humble-ros-base には nano や vim が入っていません。そのため ROS2 ノード開発時には毎回インストールが必要ですが、これは効率が悪いため、自分専用のカスタム ROS2 イメージを作成します。

■ Docker Desktopを起動

まず始めにWindows上でDocker Desktopを起動します。
Docker Desktopを起動した状態の参考スクリーンショットです。

■ Dockerfile を作成

※ここからはwsl上の作業です。WindowsのターミナルからwslコマンドでUbuntuを起動してください。

$ mkdir -p ~/docker_ros2
$ cd ~/docker_ros2
$ nano Dockerfile

これでnanoエディタが開いた状態になりますので、以下の内容を記述します。

FROM ros:humble-ros-base
RUN apt update && \
    apt install -y nano vim && \
    rm -rf /var/lib/apt/lists/*

■ Dockerイメージをビルド

$ docker build -t ros2-humble-dev .

参考スクリーンショットです。必要な工程の経過が表示されます。

これで nano/vim入りの ROS2 開発用イメージが完成します。


2. 永続化ワークスペースのディレクトリ確認

ROS2 のソースコードは Docker コンテナ内に置くと消えてしまうため、WSL2 の Ubuntu 側のディレクトリを永続化として使います。ここではさらにWindows側でも利用できるように、/mnt/c (これでWindowsのCドライブ配下)を利用します。ここでは、Cドライブ配下を使うということだけ確認します。


3. 一発起動スクリプトで ROS2 開発を開始する

毎回 docker run を入力しなくて済むよう、以下のような一発で起動するスクリプトをここで作成します。

$ nano ~/run_ros2.sh

これでnanoエディタが開いた状態になりますので、以下の内容を記述します。

#!/bin/bash
docker run -it --rm \
  -v /mnt/c/ros2common:/root \
  ros2-humble-dev bash

ここで、/mnt/c/ros2common とROSコンテナの/rootディレクトリを共有する指定をしています。必要に応じて任意のディレクトリを設定してください。
この場合は各配下で以下のディレクトリとして認識出来ます。
※全て同じ場所になるので、同じファイルを参照、編集できます。

対象 ディレクトリ名
Windows c:\roscommon
WSL /mnt/c/ros2common
ROSコンテナ /root

編集した起動スクリプトに実行権限を付与します。

$ chmod +x ~/run_ros2.sh

これで WSLから、ROS2 開発を始めるときは次の一行だけです。

$ ./run_ros2.sh

nano / vim が最初から使え、ワークスペースも永続化されているため、最も効率的な構成になります。

ここまでを整理すると以下の手順でROS2コンテナでROS2が使えます。
1. WindowsでDocker Desktopを起動する。
2. ターミナルからWSLを起動する。
3. cd と入力してWSLのホームディレクトリへ移動
4. $ ./run_ros2.sh
(ROS2コンテナ起動完了)
5. $ source /opt/ros/humble/setup.bash
6. $ cd /root/ros2_ws
長いと思いますが、ひとまず覚えるためにも慣れてください。
終了する場合は、exitと入力するか、ウインドウを閉じてください。


7. ROS2 ワークスペースを作成する(ここから本題)

まず、wsl2のUbuntuからコンテナを起動します。前項の1行を使います。

$ ./run_ros2.sh

これで、コンテナが起動するはずです。
※ここからはROS2のDockerコンテナ上での作業です。

参考スクリーンショットです。

コマンドプロンプトの色と文字で、wslのUbuntuかROS2コンテナか判断出来ると思います。

ROS2 ワークスペースを作成します。

$ source /opt/ros/humble/setup.bash
$ mkdir -p /root/ros2_ws/src
$ cd /root/ros2_ws

8. Python パッケージの作成(random_temperature_sensor)

ROS2 では、ノードをまとめて管理するために「パッケージ」を作成します。ここで作成するパッケージの中に、publisher や subscriber の Python コードを配置していきます。以下を入力します。

$ cd /root/ros2_ws/src
$ ros2 pkg create --build-type ament_python random_temperature_sensor

9. Publisher(センサーA:0〜40℃)を実装

「仮想温度センサーA」を想定し、0〜40℃の乱数を生成して temperature トピックへ publish するノードです。実際のセンサーが無くても ROS2 の動作を理解できるように、ランダム値を用いてセンサーを模擬しています。

以下のファイルを編集します。
/root/ros2_ws/src/random_temperature_sensor/random_temperature_sensor/temperature_publisher.py

下記プログラムを記述します。

import rclpy
from rclpy.node import Node
from std_msgs.msg import Float32
import random

class TemperaturePublisher(Node):
    def __init__(self):
        super().__init__('temperature_publisher')
        self.publisher = self.create_publisher(Float32, 'temperature', 10)
        self.timer = self.create_timer(1.0, self.publish_temperature)

    def publish_temperature(self):
        temp = random.uniform(0.0, 40.0)
        msg = Float32()
        msg.data = temp
        self.publisher.publish(msg)
        self.get_logger().info(f'[Sensor A] Publish: {temp:.2f} ℃')

def main(args=None):
    rclpy.init(args=args)
    node = TemperaturePublisher()
    rclpy.spin(node)
    node.destroy_node()
    rclpy.shutdown()

if __name__ == '__main__':
    main()

10. Subscriber を実装

publisher が送信した温度データを受信する側のノードです。temperature トピックを購読し、受信した値をログに表示します。subscriber のコードはセンサーの種類に依存しないため、この後に追加する「センサーB」も同じまま受信できます。

以下のファイルを編集します。
/root/ros2_ws/src/random_temperature_sensor/random_temperature_sensor/temperature_subscriber.py

下記プログラムを記述します。

import rclpy
from rclpy.node import Node
from std_msgs.msg import Float32

class TemperatureSubscriber(Node):
    def __init__(self):
        super().__init__('temperature_subscriber')
        self.subscription = self.create_subscription(
            Float32,
            'temperature',
            self.listener_callback,
            10
        )

    def listener_callback(self, msg):
        self.get_logger().info(f'Subscribed: {msg.data:.2f} ℃')

def main(args=None):
    rclpy.init(args=args)
    node = TemperatureSubscriber()
    rclpy.spin(node)
    node.destroy_node()
    rclpy.shutdown()

if __name__ == '__main__':
    main()

11. setup.py にエントリーポイントを追加

作成した Python ファイルを ros2 run コマンドで実行できるようにするための設定です。「どのコマンドでどの Python 関数を起動するか」を定義します。

/root/ros2_ws/src/random_temperature_sensor/setup.py

entry_points={
    'console_scripts': [
        'temperature_publisher = random_temperature_sensor.temperature_publisher:main',
        'temperature_subscriber = random_temperature_sensor.temperature_subscriber:main',
    ],
},

12. ビルドと実行

colcon は ROS2 のビルドツールです。Python でも一度ビルドステップが必要です。ビルド後は、生成された環境を読み込み、各ノードを実行できます。

$ cd /root/ros2_ws
$ colcon build
$ source install/setup.bash

■ publisher

温度データ(0〜40℃ランダム値)を publish します。&を忘れずに入力してください。

$ ros2 run random_temperature_sensor temperature_publisher &

■ subscriber

続けて、publisher が送ったデータを受信しログ出力します。

$ ros2 run random_temperature_sensor temperature_subscriber

スクリーンショットの例です。終了する場合は、exitと入力するか、ウィンドウを閉じてください。


13. 発展:2種類目の仮想センサー(センサーB:-10〜10℃)を追加

次に、異なる範囲(-10〜10℃)の温度を publish する「センサーB」を追加します。subscriber のコードはまったく変更する必要がありません。この構造が ROS2 の「疎結合性」を理解するポイントです。

以下のファイルを編集します。
/root/ros2_ws/src/random_temperature_sensor/random_temperature_sensor/temperature_publisher_b.py

下記プログラムを記述します。

import rclpy
from rclpy.node import Node
from std_msgs.msg import Float32
import random

class TemperaturePublisherB(Node):
    def __init__(self):
        super().__init__('temperature_publisher_b')
        self.publisher = self.create_publisher(Float32, 'temperature', 10)
        self.timer = self.create_timer(1.0, self.publish_temperature)

    def publish_temperature(self):
        temp = random.uniform(-10.0, 10.0)
        msg = Float32()
        msg.data = temp
        self.publisher.publish(msg)
        self.get_logger().info(f'[Sensor B] Publish: {temp:.2f} ℃')

def main(args=None):
    rclpy.init(args=args)
    node = TemperaturePublisherB()
    rclpy.spin(node)
    node.destroy_node()
    rclpy.shutdown()

if __name__ == '__main__':
    main()

14. setup.py にセンサーBを追加

センサーBも ros2 run で実行できるよう、setup.py にエントリーポイントを追記します。以下がフルパスです。
/root/ros2_ws/src/random_temperature_sensor/setup.py

'console_scripts': [
    'temperature_publisher = random_temperature_sensor.temperature_publisher:main',
    'temperature_publisher_b = random_temperature_sensor.temperature_publisher_b:main',
    'temperature_subscriber = random_temperature_sensor.temperature_subscriber:main',
],

15. 再ビルド

新しい Publisher(センサーB)を認識させるため、再度ビルドします。

$ cd /root/ros2_ws
$ colcon build
$ source install/setup.bash

16. 3 ノードを同時に実行(A/B+subscriber)

ターミナルを3つ開き(※この場合のターミナルはROS2が実行できるROS2コンテナでのターミナルです。)2種類のセンサーと subscriber を同時に実行します。subscriber はコードを変えていませんが、両方のセンサーのデータを受信できます。

念のためWindowsからROS2コンテナの起動とnodeの起動までの準備は以下の手順です。
1. WindowsでDocker Desktopを起動する。
2. ターミナルからWSLを起動する。
3. cd と入力してwslのホームディレクトリへ移動
4. $ ./run_ros2.sh
(ROS2コンテナ起動完了)
5. $ source /opt/ros/humble/setup.bash
6. $ cd /root/ros2_ws
7. $ source install/setup.bash
終了する場合は、exitと入力するか、ウインドウを閉じてください。

7.が増えましたが、やはり慣れるためにもひとまずここは手動で実行してください。7.まで完了して以下が実行出来ます。

■ センサーA(0〜40℃)

$ ros2 run random_temperature_sensor temperature_publisher

スクリーンショットの例です。

■ センサーB(-10〜10℃)

$ ros2 run random_temperature_sensor temperature_publisher_b

スクリーンショットの例です。

■ subscriber

$ ros2 run random_temperature_sensor temperature_subscriber

スクリーンショットの例です。

■ 実行例

テキストでの実行例です。

[Sensor A] Publish: 22.41 ℃
[Sensor B] Publish: -5.32 ℃
Subscribed: 22.41 ℃
Subscribed: -5.32 ℃

このように、subscriber はトピック名だけに依存し、データを送るノード(publisher)が何であるかは気にしません。これが ROS2 の強力な特徴である「疎結合」です。

■ (補足)ROSコマンドでの確認

ROS2コマンドでの確認です。
以下のように、ros2 node list と ros2 node topic で一覧が取得できます。

以下のように、ros2 topic echo topic名 でtopicの内容が確認できます。


【補足】コンテナ環境を便利にするために .bashrc を編集する

ROS2コンテナを起動するための長い手順の中で、ROS2コンテナを起動してからの以下の手順は自動化できます。

$ source /opt/ros/humble/setup.bash
$ cd /root/ros2_ws
$ source install/setup.bash

ここではWindow側で設定します。
c:\roscommon\.bashrc ファイルを編集します。
ファイルが存在しなければ新規作成します。
そこで以下の2行を追記します。
source /opt/ros/humble/setup.bash
source /root/ros2_ws/install/setup.bash

この.bashrcファイルはログイン時に自動で実行されるため、これでROS2コマンドと、nodeで利用出来るようになります。
(※もし、c:\roscommon\ 以外に共有ディレクトリを設定した場合はディレクトリ名を読み替えてぐださい。)

これで少しはROS2コンテナ起動時の操作が簡単になると思います。

ROS2 の「疎結合」について — もう少し踏み込んだ解説

ROS2 の pub/sub では、subscriber はトピック名(例:temperature)とメッセージ型(Float32)にのみ依存し、
「どのノードが送っているか」には依存しません。
この仕組みにより、ノード同士をゆるく結びつける疎結合(loose coupling)が実現します。


1. センサー仕様が変わっても subscriber は無変更で動く

今回の例では、センサーA(0〜40℃)とセンサーB(-10〜10℃)を作成しましたが、subscriber はまったく同じコードで動きました。

たとえば、センサー側が次のように変更されても subscriber はそのまま動作します:

  • 周期を 1秒 → 0.1秒 に変更
  • 実機センサーに置き換える
  • 精度を 1桁 → 3桁に変更
  • センサー内部の計算処理が複雑化する

重要なのは、subscriber が依存しているのは「トピック」と「型」だけであり、
センサーの実装(クラス名、コード内容、処理方式)には依存していないという点です。


2. subscriber が増えても publisher のコード変更は不要

温度データを必要とする機能が増えた場合(ログ保存、異常検知、UI 表示など)、それぞれが同じトピックを購読すればよく、publisher 側のコードは一切変更しません。

  • 必要なところが勝手に購読するだけでよい
  • subscribe するノードが何台でも追加できる
  • publisher に負荷やコード変更が発生しない

これは ROS2 の pub/sub モデルの大きな利点です。


3. publisher も subscriber も「相手の存在を知らない」

pub/sub では、publisher は「受信しているノードの数」を知りませんし、subscriber も「誰が送っているのか」を知りません。
この完全な独立性が、柔軟で変更に強いシステムを作る基盤になっています。

この設計により:

  • センサーを入れ替えても動作継続
  • 別チームが作ったノードとも自然に接続
  • ノード単体でテスト可能
  • ノードの実行場所が別PCでも動作可能(DDS が吸収)

4. ノードを組み合わせてシステムを柔軟に再構成できる

ROS2では、データ生成(センサー)、ロジック処理、制御(アクチュエーター)をノードとして分割し、
それらを「トピック」でつなぐだけで自由に再構成できます。

実際の応用例:

  • 温度データ → 異常検知ノード → アラーム通知
  • 温度データ → Web UI ノード → リアルタイム可視化
  • 温度データ → ロガーノード → CSV 蓄積
  • 温度 + 湿度 → 制御ロジックノード → 空調制御

どれだけノードが増えても、同じトピックを購読するだけで参加可能という点が非常に強力です。


5. まとめ:疎結合なノード設計こそ ROS2 の最大の特徴

このように、publisher と subscriber が互いの存在に依存せずに動作するため、ROS2 では以下のメリットが得られます:

  • 機能の追加・拡張が容易
  • 他チームとの分業がスムーズ
  • ノード単位でテスト・保守が簡単
  • 構成変更や実装変更に強い

今回の温度センサー例はシンプルですが、ROS2 のロボット開発における設計思想そのものを象徴しています。

まとめ

  • ROS2 パッケージの作成と基本構造を理解できた
  • 仮想温度センサーA/Bを作成し、乱数で publish を実現した
  • subscriber は変更なしで複数センサーのデータを受信できた
  • ROS2 の pub/sub と疎結合の仕組みが実体験として理解できた

また、続きはいろいろと公開できたらと思います。
ありがとうございます。