30代エンジニアの備忘録のカバー画像

30代エンジニアの備忘録

組み込み開発・信号処理・プログラミングの技術メモと短編小説

📝 【ROS2入門⑤】カスタムインターフェース定義 - 独自の.msg/.srv/.actionファイルをつくる

シリーズ この記事は「ROS2入門シリーズ」の一部です。 シリーズ目次を見る

Publisher/Subscriber を実装済みの状態から、カスタム .msg/.srv/.action を定義し rosidl 生成型を import するまでを示します。正本は Custom ROS 2 interfaces です。2026年5月時点、Ubuntu 22.04 + ROS 2 Humble を想定しています。


なぜカスタムインターフェースが必要なのか?

ROS 2 には std_msgs/String や geometry_msgs/Twist など標準インターフェースがあります。Interface packages で一覧を確認し、上書きできる型がないか先に確かめてから .msg を書くと手戻りが減ります。

  • 複数フィールドをまとめたい: robot_id, battery_level, error_code をひとつのメッセージに集約
  • サービスのRequest/Responseを自分で設計したい
  • アクションのGoal / Result / Feedbackの内容を自由に定義したい

標準だけでは載せきれないロボット固有情報は、プロジェクト専用のインターフェース用パッケージにまとめると、後からノードを増やしても依存を追いやすくなることがあります。

標準インターフェース vs カスタムインターフェース
標準インターフェース vs カスタムインターフェース

ROS2 のインターフェース種類とは?.msg/.srv/.action

種類 拡張子 用途 通信パターン
Message .msg トピックのデータ構造 一方向(Pub→Sub)
Service .srv Request/Response定義 同期に近い Request/Response が中心(実装側はディストリで非同期APIも)
Action .action Goal/Result/Feedback定義 長時間処理と Feedback を扱いやすい

インターフェース専用パッケージの作成手順

💡
運用メモ: インターフェース定義だけを ament_cmake のインターフェース用パッケージにまとめる構成がよく見られます。Python の実行ノードとは別パッケージにすると、複数ノードから同じ .msg/.srv/.action を参照しやすいです。Custom interfaces tutorial も参照。
cd ~/ros2_ws/src
ros2 pkg create --build-type ament_cmake my_interfaces
mkdir -p ~/ros2_ws/src/my_interfaces/msg
mkdir -p ~/ros2_ws/src/my_interfaces/srv
mkdir -p ~/ros2_ws/src/my_interfaces/action

作成後のディレクトリ構成:

my_interfaces/
├── msg/
│   └── RobotStatus.msg
├── srv/
│   └── SetSpeed.srv
├── action/
│   └── MoveTo.action
├── CMakeLists.txt
└── package.xml

.msg ファイルでカスタムメッセージを定義する方法

# msg/RobotStatus.msg
string  robot_id        # ロボットの識別子
float32 battery_level   # バッテリー残量(%)
bool    is_moving       # 移動中か否か
int32   error_code      # エラーコード(0=正常)

# 以降はコメントです。フィールドは 型名 フィールド名 の順で記述します。

RobotStatus.msgのデータ構造
RobotStatus.msgのデータ構造
使用できる型
型 説明 使用例
bool 真偽値 is_moving, is_ready
int8/16/32/64 符号付き整数 error_code, count
uint8/16/32/64 符号なし整数 sequence_id, flags
float32/64 浮動小数点数 battery_level, speed
string 文字列 robot_id, status_msg
float32[] 可変長配列 distances, scan_data
float32[3] 固定長配列 position, rgb_color

.srv ファイルでカスタムサービスを定義する方法

# srv/SetSpeed.srv
float32 linear_speed   # 直進速度 [m/s]
float32 angular_speed  # 旋回速度 [rad/s]
---
bool    success        # 成功/失敗
string  message        # ステータスメッセージ

---(3本のハイフン)で Request と Response を区切ります。上がリクエスト、下がレスポンスです。

サービスのRequest/Response構造
サービスのRequest/Response構造

.action ファイルでカスタムアクションを定義する方法

# action/MoveTo.action
# ─── Goal(目標指示) ───
float32 target_x
float32 target_y
float32 tolerance     # 到達判定の許容誤差 [m]
---
# ─── Result(最終結果) ───
bool    success
float32 elapsed_time  # 所要時間 [s]
---
# ─── Feedback(途中経過) ───
float32 current_x
float32 current_y
float32 remaining_distance

アクションファイルは --- が 2箇所 あり、Goal / Result / Feedback の3セクションに分かれます。

Actionの3セクション構造
Actionの3セクション構造

CMakeLists.txt と package.xml の設定方法

CMakeLists.txt
cmake_minimum_required(VERSION 3.8)
project(my_interfaces)

find_package(ament_cmake REQUIRED)
find_package(rosidl_default_generators REQUIRED)

rosidl_generate_interfaces(${PROJECT_NAME}
  "msg/RobotStatus.msg"
  "srv/SetSpeed.srv"
  "action/MoveTo.action"
)

ament_package()
package.xml
<?xml version="1.0"?>
<package format="3">
  <name>my_interfaces</name>
  <version>0.0.1</version>
  <description>Custom ROS2 interfaces</description>
  <maintainer email="[email protected]">Your Name</maintainer>
  <license>Apache-2.0</license>

  <buildtool_depend>ament_cmake</buildtool_depend>
  <buildtool_depend>rosidl_default_generators</buildtool_depend>
  <exec_depend>rosidl_default_runtime</exec_depend>
  <member_of_group>rosidl_interface_packages</member_of_group>
</package>

インターフェースパッケージのビルドと確認手順

cd ~/ros2_ws
colcon build --packages-select my_interfaces
source install/setup.bash

生成されたインターフェースを確認します。

# インターフェース一覧
ros2 interface list | grep my_interfaces
# my_interfaces/msg/RobotStatus
# my_interfaces/srv/SetSpeed
# my_interfaces/action/MoveTo

# 詳細確認
ros2 interface show my_interfaces/msg/RobotStatus

# デフォルト値を確認
ros2 interface proto my_interfaces/msg/RobotStatus
# robot_id: ''
# battery_level: 0.0
# is_moving: false
# error_code: 0

Python ノードでカスタムインターフェースを使う方法

利用側パッケージのpackage.xmlに依存を追加
<depend>my_interfaces</depend>
💡
ament_python ノードと pip の install_requires: 生成されたメッセージ型は ROS パッケージ依存(package.xml / setup.cfg の exec_depend 等)で引き込むのが典型で、install_requires にメッセージパッケージを足す必要はありません。無理に足すと apt/rosdep と二重管理になりやすいです。
カスタムメッセージのPublisher
# status_publisher.py
import rclpy
from rclpy.node import Node
from my_interfaces.msg import RobotStatus  # カスタムメッセージをインポート


class StatusPublisher(Node):

    def __init__(self):
        super().__init__('status_publisher')
        self.pub = self.create_publisher(RobotStatus, 'robot_status', 10)
        self.timer = self.create_timer(1.0, self.publish_status)

    def publish_status(self):
        msg = RobotStatus()
        msg.robot_id = 'robot_001'
        msg.battery_level = 85.5
        msg.is_moving = True
        msg.error_code = 0
        self.pub.publish(msg)
        self.get_logger().info(f'Battery: {msg.battery_level:.1f}%')


def main(args=None):
    rclpy.init(args=args)
    rclpy.spin(StatusPublisher())
    rclpy.shutdown()
カスタムサービスのサーバーノード
# speed_service.py
import rclpy
from rclpy.node import Node
from my_interfaces.srv import SetSpeed  # カスタムサービスをインポート


class SpeedServer(Node):

    def __init__(self):
        super().__init__('speed_server')
        self.srv = self.create_service(
            SetSpeed, 'set_speed', self.callback)

    def callback(self, request, response):
        if abs(request.linear_speed) > 1.0:
            response.success = False
            response.message = 'linear_speed must be <= 1.0 m/s'
            return response
        self.get_logger().info(
            f'Speed set: linear={request.linear_speed} angular={request.angular_speed}')
        response.success = True
        response.message = 'OK'
        return response


def main(args=None):
    rclpy.init(args=args)
    rclpy.spin(SpeedServer())
    rclpy.shutdown()
カスタムインターフェースの全体フロー
カスタムインターフェースの全体フロー

カスタムインターフェース定義の注意点

💡
インターフェースパッケージを先にビルドする

カスタムインターフェースを使うノードをビルドする前に、必ず my_interfaces を先にビルドして source してください。順序を間違えると ImportError になります。

colcon build --packages-select my_interfaces
source install/setup.bash
colcon build --packages-select my_robot_node
💡
ファイル名はUpperCamelCase

.msg / .srv / .action のファイル名は RobotStatus.msg のように UpperCamelCase で命名します。スネークケース(robot_status.msg)は慣習に反するため避けてください。


検証環境とつまずきやすい点

  • my_interfaces をビルド → source install/setup.bash → 利用側パッケージ の順。逆順だと生成物が無く ImportError に落ちやすい
  • フィールド名や型を変えたら再利用側との齟齬防止のためインターフェース側のビルドをやり直すのが無難

標準インターフェースを確認する CLI コマンド

独自定義の前に `ros2 interface list` / `ros2 interface show` で既存型を確認してください。

# インターフェースパッケージ一覧
ros2 interface packages

# よく使うパッケージと型
# std_msgs      → String, Float32, Int32, Bool, Header
# geometry_msgs → Twist, Pose, Point, Quaternion, Vector3
# sensor_msgs   → Image, LaserScan, Imu, JointState
# nav_msgs      → Odometry, Path, OccupancyGrid

# 詳細確認
ros2 interface show geometry_msgs/msg/Twist

カスタムインターフェース定義のまとめ

  • .msg はトピック用データ構造、区切り不要
  • .srv はRequest/Responseを --- で1箇所区切り
  • .action はGoal/Result/Feedbackを --- で2箇所区切り
  • ament_cmake パッケージに集約し rosidl_generate_interfaces() でビルド
  • 利用側の package.xml に <depend>my_interfaces</depend> を追加
  • 次回(シリーズ⑥)では、サービス/アクションを Python で繋ぐ流れへ進む想定です