フィードバック制御

このドキュメントでは、ROS2 Humble環境において、Pythonでフィードバック制御を実装し、移動ロボットを制御するプログラムの作り方を説明します。ノードは /kachaka/manual_control/cmd_vel トピックに対して geometry_msgs/Twist 型のメッセージを発行し、/kachaka/odometry/odometry トピックから nav_msgs/Odometry 型のメッセージを購読します。

1. プログラムの概要

  • 移動ロボットをフィードバック制御で制御します
  • オドメトリデータを使用して、ロボットの現在位置と姿勢を取得します
  • 目標位置や角度に基づいて、ロボットの動作を制御します
  • 直進、回転、停止などの基本的な動作を実装します

2. フィードバック制御とは

フィードバック制御(Feedback Control)は、制御対象の出力を測定し、目標値との差(誤差)に基づいて制御を行う方式です。センサーデータを使用して、現在の状態を把握し、目標状態に近づくように制御を行います。

フィードバック制御の特徴

  • 誤差検出: 目標値と実際の値の差を検出します
  • 誤差補正: 検出した誤差を補正するための制御入力を決定します

フィードフォワード制御との違い

フィードフォワード制御は、制御対象の出力を測定せずに、入力のみに基づいて制御を行います。一方、フィードバック制御は出力を測定し、目標値との差に基づいて制御を行います。

3. 前提条件

  • ROS2 Humble がインストール済み
  • Python3 が利用可能
  • ROS2ワークスペース(例: ~/ros2_ws)がセットアップ済み

必要なパッケージ

  • rclpy: ROS2のPythonクライアントライブラリ
  • geometry_msgs: 位置や速度などの幾何学メッセージを扱うためのパッケージ
  • nav_msgs: ナビゲーション関連のメッセージを扱うためのパッケージ
  • tf_transformations: 座標変換や四元数操作のためのライブラリ
  • numpy: 数値計算のためのライブラリ

4. パッケージの作成

まず、ROS2ワークスペースの src ディレクトリ内でパッケージを作成します。

cd ~/ros2_ws/src
ros2 pkg create --build-type ament_python kachaka_feedback_control --dependencies rclpy geometry_msgs nav_msgs tf_transformations numpy --node-name kachaka_feedback_control

このコマンドにより、以下の構造のパッケージが生成されます:

kachaka_feedback_control/
├── resource/
│   └── kachaka_feedback_control
├── kachaka_feedback_control/
│   ├── __init__.py
│   └── kachaka_feedback_control.py
├── test/
├── package.xml
└── setup.py

5. ノードの実装

kachaka_feedback_control パッケージ内の kachaka_feedback_control ディレクトリに、以下の内容の kachaka_feedback_control.py ファイルを作成します。

kachaka_feedback_control.py

#!/usr/bin/env python3
import math
import rclpy
from rclpy.node import Node
from geometry_msgs.msg import Twist
from nav_msgs.msg import Odometry
import numpy as np
from tf_transformations import euler_from_quaternion

# Imports for QoS settings
from rclpy.qos import QoSProfile, QoSReliabilityPolicy, QoSHistoryPolicy

class KachakaFeedbackControl(Node):
    def __init__(self):
        super().__init__('kachaka_feedback_control')

        # Define a QoS profile with BEST_EFFORT reliability
        qos_profile = QoSProfile(
            history=QoSHistoryPolicy.KEEP_LAST,
            depth=10,
            reliability=QoSReliabilityPolicy.BEST_EFFORT
        )

        # Publisher for velocity commands
        self.cmd_vel_pub = self.create_publisher(Twist, '/kachaka/manual_control/cmd_vel', 10)

        # Subscriber for odometry data with BEST_EFFORT QoS
        self.odom_sub = self.create_subscription(
            Odometry,
            '/kachaka/odometry/odometry',
            self.callback_odom,
            qos_profile
        )

        self.x = None
        self.y = None
        self.yaw = None

        # Wait for the first odometry message to be received
        while self.x is None or self.y is None or self.yaw is None:
            rclpy.spin_once(self, timeout_sec=0.1)

    def callback_odom(self, msg):
        """Callback function for receiving odometry data."""
        self.x = msg.pose.pose.position.x
        self.y = msg.pose.pose.position.y
        self.yaw = self.get_yaw_from_quaternion(msg.pose.pose.orientation)

    def get_yaw_from_quaternion(self, quaternion):
        # Convert quaternion to Euler angles and return the yaw
        e = euler_from_quaternion((quaternion.x, quaternion.y, quaternion.z, quaternion.w))
        return e[2]

    def go_straight(self, distance, velocity=0.3):
        """
        Move straight for the specified distance.
        """
        vel = Twist()
        x0 = self.x
        y0 = self.y
        while np.sqrt((self.x - x0)**2 + (self.y - y0)**2) < distance:
            vel.linear.x = velocity
            vel.angular.z = 0.0
            self.cmd_vel_pub.publish(vel)
            rclpy.spin_once(self, timeout_sec=0.1)
        self.stop()

    def turn_right(self, angle_degree, yawrate=-0.5):
        """
        Turn right by the specified angle (in degrees) using a negative yaw rate.
        """
        vel = Twist()
        yaw0 = self.yaw
        target_angle = math.radians(angle_degree)
        # Normalize the angle difference using atan2
        while abs(math.atan2(math.sin(self.yaw - yaw0), math.cos(self.yaw - yaw0))) < target_angle:
            vel.linear.x = 0.0
            vel.angular.z = yawrate
            self.cmd_vel_pub.publish(vel)
            rclpy.spin_once(self, timeout_sec=0.1)
        self.stop()

    def turn_left(self, angle_degree, yawrate=0.5):
        """
        Turn left by the specified angle (in degrees) using a positive yaw rate.
        """
        vel = Twist()
        yaw0 = self.yaw
        target_angle = math.radians(angle_degree)
        # Normalize the angle difference using atan2
        while abs(math.atan2(math.sin(self.yaw - yaw0), math.cos(self.yaw - yaw0))) < target_angle:
            vel.linear.x = 0.0
            vel.angular.z = yawrate
            self.cmd_vel_pub.publish(vel)
            rclpy.spin_once(self, timeout_sec=0.1)
        self.stop()

    def stop(self):
        """Stop the robot by publishing zero velocities."""
        vel = Twist()
        vel.linear.x = 0.0
        vel.angular.z = 0.0
        self.cmd_vel_pub.publish(vel)

def main(args=None):
    rclpy.init(args=args)
    kachaka_feedback_control = KachakaFeedbackControl()

    try:
        # Example movement commands: go straight, turn left, then turn right
        kachaka_feedback_control.go_straight(1.0)
        kachaka_feedback_control.turn_left(90)
        kachaka_feedback_control.turn_right(90)
    finally:
        kachaka_feedback_control.destroy_node()
        rclpy.shutdown()

if __name__ == '__main__':
    main()

6. setup.py の編集

–node-nameを使用していない場合はパッケージルートにある setup.py のエントリーポイントを設定します。

    entry_points={
        'console_scripts': [
            'kachaka_feedback_control = kachaka_feedback_control.kachaka_feedback_control:main'
        ],

7. パッケージのビルドと実行

パッケージのビルド

cd ~/ros2_ws
colcon build --packages-select kachaka_feedback_control

環境設定の読み込み

source install/setup.bash

ノードの実行

ros2 run kachaka_feedback_control kachaka_feedback_control

7. コードの詳細解説

クラスとメソッド

  • KachakaFeedbackControl: メインのノードクラス
    • __init__: ノードの初期化、パブリッシャーとサブスクライバーの設定
    • callback_odom: オドメトリデータを受信したときのコールバック関数
    • get_yaw_from_quaternion: 四元数からヨー角(yaw)を取得する関数
    • go_straight: 指定された距離だけ直進する関数
    • turn_right: 指定された角度だけ右に回転する関数
    • turn_left: 指定された角度だけ左に回転する関数
    • stop: ロボットを停止させる関数

制御パラメータ

  • distance: 直進する距離 [m]
  • velocity: 直進速度 [m/s]
  • angle_degree: 回転する角度 [度]
  • yawrate: 回転速度 [rad/s]

動作シーケンス

プログラムは以下の動作シーケンスを実行します:

  1. 1.0メートル直進
  2. 90度左回転
  3. 90度右回転

フィードバック制御の実装ポイント

  1. 誤差計算:

    • 目標位置と現在位置の差(距離)を計算
    • 目標方向と現在の向きの差(角度)を計算
  2. 目標値との比較:

    • go_straight メソッドでは、現在位置と開始位置の距離を計算し、目標距離と比較します
    • turn_rightturn_left メソッドでは、現在のヨー角と開始時のヨー角の差を計算し、目標角度と比較します
  3. 目標到達判定:

    • 距離と角度の誤差が許容誤差以下になったら目標到達と判定
    • 目標到達後はロボットを停止
  4. QoSプロファイルの設定:

    • オドメトリデータのサブスクライバーには、BEST_EFFORT 信頼性のQoSプロファイルを設定しています
    • これにより、データの遅延や欠落を許容し、リアルタイム性を重視します

演習

目標位置への移動

フィードバック制御を使用して、ロボットを指定された座標に移動させる機能を実装してみましょう。

三角形の軌跡

フィードバック制御を使用して、ロボットに正方形の軌跡を描かせる機能を実装してみましょう。

内容

好きな軌道を作成してみよう!