フィードバック制御
このドキュメントでは、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
ファイルを作成します。
#!/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.0メートル直進
- 90度左回転
- 90度右回転
フィードバック制御の実装ポイント
誤差計算:
- 目標位置と現在位置の差(距離)を計算
- 目標方向と現在の向きの差(角度)を計算
目標値との比較:
go_straight
メソッドでは、現在位置と開始位置の距離を計算し、目標距離と比較しますturn_right
とturn_left
メソッドでは、現在のヨー角と開始時のヨー角の差を計算し、目標角度と比較します
目標到達判定:
- 距離と角度の誤差が許容誤差以下になったら目標到達と判定
- 目標到達後はロボットを停止
QoSプロファイルの設定:
- オドメトリデータのサブスクライバーには、
BEST_EFFORT
信頼性のQoSプロファイルを設定しています - これにより、データの遅延や欠落を許容し、リアルタイム性を重視します
- オドメトリデータのサブスクライバーには、
演習
目標位置への移動
フィードバック制御を使用して、ロボットを指定された座標に移動させる機能を実装してみましょう。
三角形の軌跡
フィードバック制御を使用して、ロボットに正方形の軌跡を描かせる機能を実装してみましょう。
内容
好きな軌道を作成してみよう!