ラズパイマウスでROS 2を動かしてみた

【Raspberry Pi Mouse】Nav2を使用したWaypointによるナビゲーションのサンプル紹介

ラズパイマウスでROS 2を動かしてみた

こんにちは、倉澤ズズくんです。
本記事では、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によるナビゲーションサンプルを紹介します。

GitHub - rt-net/raspimouse_slam_navigation_ros2 at jazzy
ROS 2 SLAM and Navigation packages for Raspberry Pi Mouse - GitHub - rt-net/raspimouse_slam_navigation_ros2 at jazzy

ナビゲーションパッケージに関する説明やインストール方法の詳細は、以下のブログをご参照ください。
(本記事で紹介するサンプルはROS 2 Jazzy対応ですが、下記の記事はROS 2 Humble向けであり、実行するコマンドに差異がある点にご注意ください)

Raspberry Pi MouseのシミュレータパッケージROS 2版にSLAM&Navigationサンプルを追加しました
こんにちは、アールティの加藤です。 アールティが販売する小型移動プラットフォームロボットRaspberry Pi MouseのシミュレータパッケージROS 2版に、SLAM&Navigationサンプルが追加されました。このサンプルはRas...

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パッケージを参照してください。

GitHub - rt-net/raspimouse_sim at jazzy
ROS package suite for Raspberry Pi Mouse Simulator - GitHub - rt-net/raspimouse_sim at jazzy

ナビゲーションの開始

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には、様々な機能が用意されているので、是非試してみてください。

タイトルとURLをコピーしました