以下の内容はhttps://daisuke20240310.hatenablog.com/entry/ros2_sourceより取得しました。


ROS2のソースコードを確認した(rclpy、rcl、rmw)

前回 は、ROS2 のユーザノードのトピックの実装をやってみました。

今回は、ROS2 の中身を見ていきたいと思います。

それでは、やっていきます。

参考文献

環境構築については、こちらを参考にしました。Python よりも C++ の解説が多かったです。ROS2 について、しっかりと詳しく解説されていた印象です。

こちらは、逆に、Python が多く、ROS2 について、基本的なことは解説されていますが、応用的なところとして、AI と関連付けての説明に重点が置かれていた印象です。

はじめに

ROS の公式ページです。ただ、インストール手順や、その他の情報は、ROS の各ディストリビューションごとのページに書かれているので、使用するディストリビューションに応じた公式のドキュメントを参照する必要があります。このページで紹介されているディストリビューションは、Ubuntu と同じように 5年間の長期サポートをしている、最新の Jazzy Jalisco(以下、Jazzy)と、最初に長期サポートを行った Humble Hawksbill(以下、Humble)でした。現状、長期サポートしているのは、この 2つのディストリビューションのようです。

www.ros.org

今回は、Humble を使っていこうと思います。Humble が対応している Ubuntu のバージョンは、22.04 のみであり、現在、私の PC の Virtual Box に入ってる Ubuntu が 22.04 だからです。ちなみに、Jazzy の方は、Ubuntu のバージョンは、24.04 のみ対応しています。ちなみに、書籍(改訂新版 ROS 2ではじめよう 次世代ロボットプログラミング〜ロボットアプリケーション開発のための基礎から実践まで)では、Jazzy のインストール方法が解説されています。

Humble のドキュメントは以下です。

docs.ros.org

参考文献の書籍(改訂新版 ROS 2ではじめよう 次世代ロボットプログラミング〜ロボットアプリケーション開発のための基礎から実践まで)のサポートサイトです。

gihyo.jp

この書籍のサンプルコードは、以下の GitHub で公開されています。

github.com

また、前回 使用した、書籍のサンプルコードのもとにもなっている、公式のサンプルコードは、以下です。

github.com

それでは、やっていきます。

ROS2のアーキテクチャ

ROS2 のアーキテクチャについて、書籍 改訂新版 ROS 2ではじめよう 次世代ロボットプログラミング〜ロボットアプリケーション開発のための基礎から実践まで で書かれていた図が分かりやすかったので、それをもとに以下の図を作成しました。

ROS2アーキテクチャ
ROS2アーキテクチャ

この図を参考にソースコードを見ていきます(確認した内容を図に追記しました)。

サンプルコードが参照しているROS2のソースコード

Python のインタラクティブモードで調べてみます。

Python が参照できるライブラリに、ROS2 のパスが追加されています。

$ python3
Python 3.10.12 (main, Feb  4 2025, 14:57:36) [GCC 11.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.

>>> import sys

>>> sys.path
['', '/opt/ros/humble/lib/python3.10/site-packages', '/opt/ros/humble/local/lib/python3.10/dist-packages', '/usr/lib/python310.zip', '/usr/lib/python3.10', '/usr/lib/python3.10/lib-dynload', '/home/daisuke/.local/lib/python3.10/site-packages', '/usr/local/lib/python3.10/dist-packages', '/usr/lib/python3/dist-packages']

/opt/ros/humble/lib/ を見ると、前回実装した publisher_member_function が既に含まれていたことが分かりました。ROS2 のインストールで入ったのだと思います。実際に動かしてみると、前回確認した内容と同じ結果でした。

$ ll /opt/ros/humble/lib/examples_rclpy_minimal_publisher/
合計 56K
drwxr-xr-x  2 root root 4.0K  531 22:25 ./
drwxr-xr-x 76 root root  36K  531 22:26 ../
-rwxr-xr-x  1 root root 1.1K  430 03:56 publisher_local_function*
-rwxr-xr-x  1 root root 1.1K  430 03:56 publisher_member_function*
-rwxr-xr-x  1 root root 1.1K  430 03:56 publisher_old_school*

$ cd svn_/ros2/ros2_ws/

$ source install/setup.bash

$ ros2 run examples_rclpy_minimal_publisher publisher_member_function
[INFO] [1749366199.345828166] [minimal_publisher]: Publishing: "Hello World: 0"
[INFO] [1749366199.771954251] [minimal_publisher]: Publishing: "Hello World: 1"
[INFO] [1749366200.269907259] [minimal_publisher]: Publishing: "Hello World: 2"
[INFO] [1749366200.769315738] [minimal_publisher]: Publishing: "Hello World: 3"
[INFO] [1749366201.292444344] [minimal_publisher]: Publishing: "Hello World: 4"
^CTraceback (most recent call last):
  File "/opt/ros/humble/lib/examples_rclpy_minimal_publisher/publisher_member_function", line 33, in <module>
    sys.exit(load_entry_point('examples-rclpy-minimal-publisher==0.15.3', 'console_scripts', 'publisher_member_function')())
  File "/opt/ros/humble/lib/python3.10/site-packages/examples_rclpy_minimal_publisher/publisher_member_function.py", line 43, in main
    rclpy.spin(minimal_publisher)
  File "/opt/ros/humble/local/lib/python3.10/dist-packages/rclpy/__init__.py", line 226, in spin
    executor.spin_once()
  File "/opt/ros/humble/local/lib/python3.10/dist-packages/rclpy/executors.py", line 751, in spin_once
    self._spin_once_impl(timeout_sec)
  File "/opt/ros/humble/local/lib/python3.10/dist-packages/rclpy/executors.py", line 740, in _spin_once_impl
    handler, entity, node = self.wait_for_ready_callbacks(timeout_sec=timeout_sec)
  File "/opt/ros/humble/local/lib/python3.10/dist-packages/rclpy/executors.py", line 723, in wait_for_ready_callbacks
    return next(self._cb_iter)
  File "/opt/ros/humble/local/lib/python3.10/dist-packages/rclpy/executors.py", line 620, in _wait_for_ready_callbacks
    wait_set.wait(timeout_nsec)
KeyboardInterrupt
[ros2run]: Interrupt

次は、前回実装したライブラリのソースコードを探します。from rclpy.node import Node というようにインポートしてるので、rclpy というディレクトリに、node.py というファイルがあり、Node というクラスが定義されているはずです(たぶん)。

rclpy

Nodeクラス

実際に、パスが通ってるところで、検索してみます。4件ヒットして、4件目が探してるファイルだと思います。4件目のファイルを開いてみると、Nodeクラスが定義されているだけのファイルでしたが、約2000行ありました。このファイルで間違いなさそうです。このファイルは、上の図(ROS2アーキテクチャ)の rclpy のブロックに該当すると思います。

$ find /opt/ros/humble/ -name 'node.py'
/opt/ros/humble/lib/python3.10/site-packages/launch_ros/actions/node.py
/opt/ros/humble/lib/python3.10/site-packages/ros2node/command/node.py
/opt/ros/humble/local/lib/python3.10/dist-packages/rclpy/lifecycle/node.py
/opt/ros/humble/local/lib/python3.10/dist-packages/rclpy/node.py

2000行を見るのも大変なので、node.py の中を def で検索すると、81件ヒットしました。その中で、前回実装したときに使用した「create_publisherメソッド」を見て、作成されたインスタンスの publishメソッドを見ていきたいと思います。

以下が、create_publisherメソッドです。

全部理解するのは難しいですが、Publisherクラスのインスタンスを作って、それを返しているようです。

    def create_publisher(
        self,
        msg_type,
        topic: str,
        qos_profile: Union[QoSProfile, int],
        *,
        callback_group: Optional[CallbackGroup] = None,
        event_callbacks: Optional[PublisherEventCallbacks] = None,
        qos_overriding_options: Optional[QoSOverridingOptions] = None,
        publisher_class: Type[Publisher] = Publisher,
    ) -> Publisher:
        """
        Create a new publisher.

        :param msg_type: The type of ROS messages the publisher will publish.
        :param topic: The name of the topic the publisher will publish to.
        :param qos_profile: A QoSProfile or a history depth to apply to the publisher.
          In the case that a history depth is provided, the QoS history is set to
          KEEP_LAST, the QoS history depth is set to the value
          of the parameter, and all other QoS settings are set to their default values.
        :param callback_group: The callback group for the publisher's event handlers.
            If ``None``, then the node's default callback group is used.
        :param event_callbacks: User-defined callbacks for middleware events.
        :return: The new publisher.
        """
        qos_profile = self._validate_qos_or_depth_parameter(qos_profile)

        callback_group = callback_group or self.default_callback_group

        failed = False
        try:
            final_topic = self.resolve_topic_name(topic)
        except RuntimeError:
            # if it's name validation error, raise a more appropriate exception.
            try:
                self._validate_topic_or_service_name(topic)
            except InvalidTopicNameException as ex:
                raise ex from None
            # else reraise the previous exception
            raise

        if qos_overriding_options is None:
            qos_overriding_options = QoSOverridingOptions([])
        _declare_qos_parameters(
            Publisher, self, final_topic, qos_profile, qos_overriding_options)

        # this line imports the typesupport for the message module if not already done
        failed = False
        check_is_valid_msg_type(msg_type)
        try:
            with self.handle:
                publisher_object = _rclpy.Publisher(
                    self.handle, msg_type, topic, qos_profile.get_c_qos_profile())
        except ValueError:
            failed = True
        if failed:
            self._validate_topic_or_service_name(topic)

        try:
            publisher = publisher_class(
                publisher_object, msg_type, topic, qos_profile,
                event_callbacks=event_callbacks or PublisherEventCallbacks(),
                callback_group=callback_group)
        except Exception:
            publisher_object.destroy_when_not_in_use()
            raise
        self._publishers.append(publisher)
        self._wake_executor()

        for event_callback in publisher.event_handlers:
            self.add_waitable(event_callback)

        return publisher
Publisherクラス

node.py と同じディレクトリの rclpyディレクトリに publisher.py があり、そこに Publisherクラスが定義されていました。

publisher.py は、120行ぐらいのファイルでした。Publisherクラスの publishメソッドに注目します。__publisher というオブジェクトの publishメソッドを実行しています。これをたどっていくと、同じ rclpyディレクトリの implディレクトリの implementation_singleton.py だということが分かります。

    def publish(self, msg: Union[MsgType, bytes]) -> None:
        """
        Send a message to the topic for the publisher.

        :param msg: The ROS message to publish.
        :raises: TypeError if the type of the passed message isn't an instance
          of the provided type when the publisher was constructed.
        """
        with self.handle:
            if isinstance(msg, self.msg_type):
                self.__publisher.publish(msg)
            elif isinstance(msg, bytes):
                self.__publisher.publish_raw(msg)
            else:
                raise TypeError('Expected {}, got {}'.format(self.msg_type, type(msg)))
implementation_singleton.py

implementation_singleton.py は、とても小さいファイルです。以下のように 3行だけでした。ChatGPT によると、これは、クライアントライブラリの rcl を呼び出している _rclpy_pybind11.so を読んでいるとのことです。

from rpyutils import import_c_library
package = 'rclpy'

rclpy_implementation = import_c_library('._rclpy_pybind11', package)

rcl

また、ChatGPT に、さらに聞いてみると、rcl のソースは以下にあるようです。

github.com

rcl_publish()

対象のソースコードは、rcl/src/rcl/publisher.c にありました。おそらく、publishメソッドの変換先は、以下の rcl_publish() のようです。最終的には、rmw の rmw_publish関数を呼び出しています。

rcl_ret_t
rcl_publish(
  const rcl_publisher_t * publisher,
  const void * ros_message,
  rmw_publisher_allocation_t * allocation)
{
  RCUTILS_CAN_RETURN_WITH_ERROR_OF(RCL_RET_PUBLISHER_INVALID);
  RCUTILS_CAN_RETURN_WITH_ERROR_OF(RCL_RET_ERROR);

  if (!rcl_publisher_is_valid(publisher)) {
    return RCL_RET_PUBLISHER_INVALID;  // error already set
  }
  RCL_CHECK_ARGUMENT_FOR_NULL(ros_message, RCL_RET_INVALID_ARGUMENT);
  TRACEPOINT(rcl_publish, (const void *)publisher, (const void *)ros_message);
  if (rmw_publish(publisher->impl->rmw_handle, ros_message, allocation) != RMW_RET_OK) {
    RCL_SET_ERROR_MSG(rmw_get_error_string().str);
    return RCL_RET_ERROR;
  }
  return RCL_RET_OK;
}

rmw

rmw のソースコードは、同じく ros2 のリポジトリにありました。

github.com

rmw_publish()

rmw_publish関数は、ヘッダファイルに API だけが書かれていました。ChatGPT によると、使用している RMW の実装によるとのことです。

DDS

次に、DDS について調べます。DDS(Data Distribution Service)とは、ROS2 で作られたのではなく、既に、世の中で使われている通信のミドルウェアです。ROS2 では、DDS を採用することにより、ROS2 で新たに実装する量を削減したのだと思います。

ROS2 では、使用する DDS の実装を選ぶことができます。もちろん、自分で実装することも出来ます。では、現在使われている DDS は何なのかを調べてみます。

ちょっと出力されるログが多いので、最後のところだけ貼ります。

RMW MIDDLEWARE のところを見ると、rmw_fastrtps_cpp と出力されています。これは、通称 Fast DDS と呼ばれているもので、ROS2 のデフォルトで使われる DDS であり、現在使っている DDS ということになります。

$ ros2 doctor --report
   PLATFORM INFORMATION
system           : Linux
platform info    : Linux-6.8.0-57-generic-x86_64-with-glibc2.35
release          : 6.8.0-57-generic
processor        : x86_64

   QOS COMPATIBILITY LIST
compatibility status    : No publisher/subscriber pairs found

   RMW MIDDLEWARE
middleware name    : rmw_fastrtps_cpp

   ROS 2 INFORMATION
distribution name      : humble
distribution type      : ros2
distribution status    : active
release platforms      : {'rhel': ['8'], 'ubuntu': ['jammy']}

   TOPIC LIST
topic               : none
publisher count     : 0
subscriber count    : 0

使用する DDS を変更する 1つの方法としては、環境変数の RMW_IMPLEMENTATION に使用したい DDS を指定することです。とはいえ、特に今は困ってないので、デフォルトの DDS である Fast DDS(rmw_fastrtps_cpp)を使っていきます。

今回は以上になります。

おわりに

今回は、ROS2 のソースコードを見ていきました。なんとなく、全体像は見えた気がしました。今後は、ROS2 で強化されたセキュリティについて、情報収集していきたいと思います。

最後になりましたが、エンジニアグループのランキングに参加中です。

気楽にポチッとよろしくお願いいたします🙇

今回は以上です!

最後までお読みいただき、ありがとうございました。




以上の内容はhttps://daisuke20240310.hatenablog.com/entry/ros2_sourceより取得しました。
このページはhttp://font.textar.tv/のウェブフォントを使用してます

不具合報告/要望等はこちらへお願いします。
モバイルやる夫Viewer Ver0.14