その他

ROS2とは?

その他
この記事は約11分で読めます。

はじめに

こんにちは。

ロボットの実習第4回目です。

前回はRaspbot V2という機体を制御するために、Raspbot専用ライブラリを用いて簡単なプログラムを作成してみました。

しかし、ロボットの制御が複雑になってくると、ライブラリ単体での開発ではデータのやり取りや並列処理の管理が難しくなってきます。

そこで今回は、ロボット開発の標準フレームワークであるROS2について紹介します。

今回やること

今回は、ロボット開発を効率化・高度化させるためのフレームワーク「ROS2(Robot Operating System 2)」について解説します。

ロボット開発では、カメラ、センサー、モーターといった多様なパーツを同時に制御する必要があります。

しかし、これらを一つひとつ独自のライブラリで制御しようとすると、プログラムの依存関係が複雑になり、拡張性やメンテナンス性が失われがちです。

そこで、ROS2を活用することでどのようなメリットがあるのか紹介していきます。

ROS2とは?

概要

ROS2という名前からOSと想像してしまいますが、実際にはロボット開発のためのミドルウェアのようです。

文献を読むと「ROS = 通信ライブラリ + ツール + 基盤 + エコシステム」と表現されており、以下のような役割を担っています。

通信ライブラリ(Publish/Subscribe通信)
 ロボット内の各パーツが、互いにデータをやり取りするための標準化された基盤。
ツール
 分散システムの状態監視、ログ取り、データの可視化などを行うための豊富な開発・デバッグ用ツール群。
基盤
 ロボットのナビゲーションや認識アルゴリズムなど、世界中で共有されている高機能なライブラリ群
エコシステム
 世界中の開発者が公開しているパッケージを sudo apt install で簡単に利用できる。

また、ROS2を使うことで、ロボット開発の各機能をコンポーネント単位でクラスを使って実装し、ノード化することでそれぞれの機能が独立して起動することができるようになります。
(web開発でいうところの、マイクロサービス化に近い概念だと思っています。)

さらに、ROS2の特徴である分散処理を用いることで、重くなりがちな処理を手持ちのPCで処理させたりすることが特徴のようです。

https://www.jstage.jst.go.jp/article/jrsj/30/9/30_30_830/_pdf

マイクロサービスのような柔軟なアーキテクチャ

ROS2の最大の特徴は、ロボットの機能を「ノード(Node)」という小さな単位に分割して開発することです。

それぞれの機能は独立して動作するため、例えば「カメラ処理用のノード」「モーター制御用のノード」を分けて開発し、必要に応じて組み合わせることができます。

さらに、ROS2は「DDS」という分散通信プロトコルを採用しており、ロボット内部の処理だけでなく、画像処理などを外部のPCに任せるといった分散処理も容易に行えるようになり、ラズパイで十分なロボット制御を実現できます。

用語

ノード
 ロボットシステムにおける最小単位の機能モジュール

ターゲット
 ROS2における実行単位。コンパイルされた実行ファイルやライブラリの実体を指す

ライブラリ
 複数のプログラムから呼び出される関数やクラスの集まりで、ノードの中に部品として組み込む

パッケージ
 ROS2の機能配布の最小単位。ノードやライブラリ、設定ファイル等をフォルダ単位でまとめたもの

アンダーレイ
 ROS2の標準ライブラリやツールがインストールされた、システムの「土台」となる領域

オーバーレイ
 アンダーレイの上に構築される、ユーザーが開発したプログラムを配置・実行するための作業領域

実装

プロジェクト構成

ROS2では colcon というツールを使い、ワークスペース全体を一度にビルドします。以下に標準的なディレクトリ構成の一般例をまとめてみました。

・src/
 ソースコード置き場。必ずパッケージ単位(package.xmlがある単位)でディレクトリを分ける

・config/
 パラメータ定義。コードへの直書きを避け、.yamlファイルとして外部管理するのが一般的

・launch
 ノード起動やパラメータ設定を一括管理するための起動ファイル群

・build/, install/, log/
 colcon buildコマンドで自動生成される領域。直接編集する必要はない。

robot_ws/                     # ワークスペース(ここが colcon build の対象)
├── src/                      # ソースコード置き場
│   ├── robot_description/    # URDFやメッシュデータ
│   │   ├── urdf/             # ロボットモデル定義
│   │   ├── meshes/           # 3Dモデル
│   │   └── CMakeLists.txt
│   │
│   ├── robot_control/        # 制御ロジック(パッケージ例)
│   │   ├── include/          # ヘッダーファイル
│   │   ├── src/              # C++ソースコード
│   │   ├── launch/           # 起動ファイル
│   │   ├── config/           # パラメータ設定ファイル(yaml)
│   │   ├── package.xml       # 必須
│   │   └── CMakeLists.txt    # 必須
│   │
│   └── robot_msgs/           # 独自のメッセージ定義
│       ├── msg/              # .msgファイル
│       └── srv/              # .srvファイル
│
├── build/                    # ビルド生成物(自動生成)
├── install/                  # インストール先(自動生成)
└── log/                      # ログ(自動生成)

モーター制御のノード化

座学が長くなってしまいましたが、ここからは実際にコードを動かしていきます。

前回と同じようなソースコードで申し訳ないですが、車輪とカメラそれぞれの動作をクラスで定義し、ROS2(rclpy)を使ってノード化しました。

さらに、このソースコードにおけるROS2の要素を表にまとめました。

import time
import rclpy
from rclpy.node import Node
from raspbot_lib import Raspbot
#ログの追加
from rclpy.logging import get_logger


class motor_node(Node): #クラスをノード化
    def __init__(self):
        super().__init__('motor_node')
        self.rb = Raspbot()

    def move_forward(self, speed):
        self.rb.Ctrl_Muto(0, speed)
        self.rb.Ctrl_Muto(1, speed)
        self.rb.Ctrl_Muto(2, speed)
        self.rb.Ctrl_Muto(3, speed)

    def move_backward(self, speed):
        self.rb.Ctrl_Muto(0, -speed)
        self.rb.Ctrl_Muto(1, -speed)
        self.rb.Ctrl_Muto(2, -speed)
        self.rb.Ctrl_Muto(3, -speed)

    def move_left(self, speed):
        self.rb.Ctrl_Muto(0, -speed)
        self.rb.Ctrl_Muto(1, -speed)
        self.rb.Ctrl_Muto(2, speed)
        self.rb.Ctrl_Muto(3, speed) 
    
    def move_right(self, speed):
        self.rb.Ctrl_Muto(0, speed)
        self.rb.Ctrl_Muto(1, speed)
        self.rb.Ctrl_Muto(2, -speed)
        self.rb.Ctrl_Muto(3, -speed)

    def stop(self):
        self.rb.Ctrl_Muto(0, 0)
        self.rb.Ctrl_Muto(1, 0)
        self.rb.Ctrl_Muto(2, 0)
        self.rb.Ctrl_Muto(3, 0)


class camera_node(Node):
    def __init__(self):
        super().__init__('camera_node')
        self.cam = Raspbot()

    def pan_left(self, angle):
        # 中央(90度)から左方向へ
        self.cam.Ctrl_Servo(1, 90 - angle)

    def pan_right(self, angle):
        # 中央(90度)から右方向へ
        self.cam.Ctrl_Servo(1, 90 + angle)

    def pan_center(self):
        self.cam.Ctrl_Servo(1, 90)


def main(args=None):
    rclpy.init(args=args)
    motor = motor_node()
    camera = camera_node()
    try:
        while rclpy.ok():
            motor.move_forward(50)
            camera.pan_left(40)
            time.sleep(1)
            motor.stop()
            camera.pan_center()
            time.sleep(1)
            motor.move_backward(50)
            camera.pan_right(40)
            time.sleep(1)
            motor.stop()
            camera.pan_center()
            time.sleep(1)
    finally:
        motor.stop()
        camera.pan_center()
        motor.destroy_node()
        camera.destroy_node()
        rclpy.shutdown()

    logger = get_logger('motor_node')


if __name__ == "__main__":
    main()
要素使用箇所内容
rclpy.init()62行目ROS2 の初期化
Node の継承11, 47行目motor_node と camera_node が Node を継承
rclpy.ok()66行目ROS2 が動作中かどうかをループ条件に使用
destroy_node()77~78行目ノードの破棄
rclpy.shutdown()79行目ROS2 の終了処理
get_logger()81行目ロガー取得(ただし未使用)
ROS2使用状況

実行

それではプログラムを動かしてみます。

以下は私の環境での実行例ですので、ディレクトリ構成などが異なる場合は、ご自身の環境に合わせてパスを調整してください。

# 1. ワークスペースへ移動
cd ~/ros2_ws

# 2. ビルドの実行(symlink-installで開発効率アップ!)
colcon build --symlink-install

# 3. 環境変数の反映
source install/setup.bash

# 4. ノードの実行
ros2 run [パッケージ名] [ノード名]

おわりに

いかがでしたでしょうか。

今回はロボット開発で用いられる標準的なフレームワーク「ROS2」について紹介しました。

まだまだ触り始めなので、このブログの内容には間違いが多いと思いますので随時アップデートしていく所存です。

次回はROS2の通信方法について紹介する予定です。

参考URL

https://swest.toppers.jp/SWEST20/program/pdfs/s2a_public.pdf
@NeK's My Page - Qiita
rclpy — rclpy 3.2.1 documentation