こんにちは、倉澤ズズくんです。
本記事では、Raspberry Pi Mouseのraspimouse_slam_navigation_ros2パッケージに新たに追加された、Nav2を使用したWaypointによるナビゲーションのサンプルを紹介します。
また、前回の記事では、衝突監視機能のCollision Monitor
を紹介しています。よろしければ、こちらもご覧ください。
Raspberry Pi Mouseの紹介
Raspberry Pi Mouse(ラズパイマウス)はメインボードにRaspberry Piを使用した左右独立二輪方式の小型移動プラットフォームロボットです。全長13cm、重さ700gとコンパクトであるため、持ち運びや開発スペースの確保も簡単です。LiDARやカメラを搭載可能で、学習・研究用途の幅広い開発が可能です。
ROS 2のサンプルパッケージを使うと、基本的な制御やSLAMによる地図作成も簡単に始められます。
Raspberry Pi Mouseの詳細な特徴については、下記の製品ページをご覧ください。
Raspberry Pi Mouseは下記の販売ページから購入できます。ROS 2パッケージは無償で公開しているため、購入前の参考にご利用ください。
Waypointサンプルの紹介
今回は、raspimouse_slam_navigation_ros2
パッケージに新たに追加されたWaypointによるナビゲーションサンプルを紹介します。
ナビゲーションパッケージに関する説明やインストール方法の詳細は、以下のブログをご参照ください。
(本記事で紹介するサンプルはROS 2 Jazzy対応ですが、下記の記事はROS 2 Humble向けであり、実行するコマンドに差異がある点にご注意ください)

Waypointとは
ナビゲーションにおけるWaypointとは、ロボットの移動先を示す目標地点のことです。
複数のWaypointを指定することで、ロボットは各目標地点を順にたどって移動できます。
Nav2 Simple Commanderによるナビゲーションの実行
RVizからWaypointを指定することもできますが、本サンプルではNav2 Simple Commanderパッケージを使ってWaypointを指定し、ナビゲーションを実行します。
Nav2 Simple Commander
は、Nav2によるナビゲーションをPythonのスクリプトから簡単に行うためのライブラリです。ユーザーは、ROS 2のノード管理やアクション通信の詳細を意識せずに、Nav2のナビゲーション機能を自分のアプリケーションから呼び出せます。
プログラムの説明
Waypointサンプルの動作は、raspimouse_navigation_examplesパッケージ内の waypoint.py に記述されています。
以下では、項目ごとの処理を簡単に説明します。
ロボットの位置姿勢の定義
generate_pose()
は、Waypointに必要なロボットの位置姿勢(PoseStamped
型)を定義する関数です。平面上の位置(x, y)とロボットの向きを示すヨー角を引数として受け取り、姿勢をクォータニオンに変換します。
def generate_pose(navigator, x: float, y: float, deg: float) -> PoseStamped: wp = PoseStamped() wp.header.frame_id = 'map' wp.header.stamp = navigator.get_clock().now().to_msg() wp.pose.position.x = x wp.pose.position.y = y wp.pose.position.z = 0.0 rad = math.radians(deg) wp.pose.orientation.x = 0.0 wp.pose.orientation.y = 0.0 wp.pose.orientation.z = math.sin(rad / 2.0) wp.pose.orientation.w = math.cos(rad / 2.0) return wp
初期位置姿勢とWaypointの指定
先程定義したgenerate_pose()
で、初期位置姿勢と4つのWaypointを指定しています。これらの座標は実際の環境に合わせて調整してください。この後の実験では、指定するWaypointを変更した上で実施します。
# Initial pose initial_pose = generate_pose(navigator=nav, x=0.0, y=0.0, deg=0.0) nav.setInitialPose(initial_pose) # Wait for the navigation stack to come up nav.waitUntilNav2Active() # Set goal_1 goal_poses = [] goal_pose1 = generate_pose(navigator=nav, x=0.0, y=0.5, deg=90.0) goal_poses.append(goal_pose1) # Set goal_2 goal_pose2 = generate_pose(navigator=nav, x=1.0, y=0.5, deg=0.0) goal_poses.append(goal_pose2) # Set goal_3 goal_pose3 = generate_pose(navigator=nav, x=1.0, y=-0.5, deg=-90.0) goal_poses.append(goal_pose3) # Set goal_4 goal_pose4 = generate_pose(navigator=nav, x=0.0, y=-0.5, deg=180.0) goal_poses.append(goal_pose4)
上記サンプルプログラムのWaypointは、シミュレーション環境のLake Houseを想定したものです。Raspberry Pi Mouseのシミュレーションに関しては、raspimouse_simパッケージを参照してください。
ナビゲーションの開始
Commander APIのfollowWaypoint()
メソッドを使ってナビゲーションを開始します。本メソッドは、与えられたWaypointを順番にたどるように移動ロボットをナビゲーションします。
nav.followWaypoints(goal_poses)
followWaypoint()
の他にも、単一の目標位置姿勢に向かって移動するgoToPose()
や、一括で全ての目標位置姿勢を考慮した経路を計画して移動するgoThroughPoses()
といったメソッドがあります(Commander API)。
フィードバック処理
followWaypoints()
は非ブロッキングな処理なので、完了までwhile文で待機します。ナビゲーション実行中はgetFeedback()によって今何番目のWaypointに向かっているか取得することができます。
また、本サンプルではwhile文中で実行時間を計測することで、90秒経過時に新しいWaypointを登録したり、180秒経過時にナビゲーション動作を終了させたりしています。
i = 0 root_changed = False while not nav.isTaskComplete(): # Display feedback every 5 cycles i = i + 1 feedback = nav.getFeedback() if feedback and i % 5 == 0: print( 'Executing current waypoint: ' + str(feedback.current_waypoint + 1) + '/' + str(len(goal_poses)), flush=True, ) # Update timestamp now = nav.get_clock().now() # Some navigation timeout to demo cancellation if now - nav_start > Duration(seconds=180.0): nav.cancelTask() # Some follow waypoints request change to demo preemption if now - nav_start > Duration(seconds=90.0): if not root_changed: goal_pose = generate_pose( navigator=nav, x=0.0, y=0.0, deg=0.0 ) goal_poses = [goal_pose] nav_start = now nav.followWaypoints(goal_poses) print('Changed goal!!') root_changed = True
結果の表示
ナビゲーションの終了後、結果に応じたメッセージを出力して終了します。
# Do something depending on the return code result = nav.getResult() if result == TaskResult.SUCCEEDED: print('Goal succeeded!') elif result == TaskResult.CANCELED: print('Goal was canceled!') elif result == TaskResult.FAILED: print('Goal failed!') else: print('Goal has an invalid return status!') nav.lifecycleShutdown() exit(0)
Raspberry Pi Mouseで動作確認
今回は、RPLIDAR A1を使用してRaspberry Pi Mouseで動作確認を行います。
実験環境
以下の画像のような迷路を作成しました。
画像右下のワイプは、RViz上の表示です。黄色い矢印は4つのWaypointを示しています。
ソースコード中のWaypointは、以下のように編集しました。
# Set goal_1 goal_poses = [] goal_pose1 = generate_pose(navigator=nav, x=0.25, y=-0.3, deg=-40.0) goal_poses.append(goal_pose1) # Set goal_2 goal_pose2 = generate_pose(navigator=nav, x=0.8, y=-0.36, deg=75.0) goal_poses.append(goal_pose2) # Set goal_3 goal_pose3 = generate_pose(navigator=nav, x=1.2, y=-0.3, deg=-80.0) goal_poses.append(goal_pose3) # Set goal_4 goal_pose4 = generate_pose(navigator=nav, x=1.25, y=-1.0, deg=-90.0) goal_poses.append(goal_pose4)
実行方法
以下のコマンドを実行します。Waypointスクリプトを起動するlaunch
ファイルも用意しています。
ros2 launch raspimouse_navigation robot_navigation.launch.py lidar:=rplidar
# ターミナル1 ros2 launch raspimouse_navigation pc_navigation.launch.py map:=$HOME/MAP_NAME.yaml # ターミナル2 ros2 launch raspimouse_navigation_examples example.launch.py example:=waypoint
シミュレーション環境下で動作させるには、以下のコマンドを実行します。
# ターミナル1 ros2 launch raspimouse_gazebo raspimouse_with_lakehouse.launch.py lidar:=rplidar # ターミナル2 ros2 launch raspimouse_navigation pc_navigation.launch.py use_sim_time:=true map:=$HOME/MAP_NAME.yaml # ターミナル3 ros2 launch raspimouse_navigation_examples example.launch.py example:=waypoint
動作の様子
4つのWaypointに対して、逐次ナビゲーションが実行されていることが確認できます。
おわりに
今回は、新たに追加されたWaypointによるナビゲーションのサンプルを紹介しました。本サンプルは、スクリプトからWaypointを設定してナビゲーションを実行する構成になっており、自動巡回などの処理も手軽に構築できます。
ナビゲーションに使用しているNav2 Simple Commanderには、様々な機能が用意されているので、是非試してみてください。