## 下準備 ### (1) 新しいROSパッケージ robot2\_task\_名字 を作成し、その中にlaunchフォルダーを追加してください。先週の課題で作ったlaunchファイルをそのフォルダーにコピーしてください。(名前を変更しても構いません。) そのlaunchファイルを開いて、先週作成したロボット位置をプリントするノードのタグを完全に削除してください。 そして次を追加しましょう(シミュレーションされているロボットのTF情報をブロードキャストするrobot_state_publisher)。 ```xml <node pkg = "robot_state_publisher" type = "robot_state_publisher" name = "robot_state_publisher"> <param name = "publish_frequency" type = "double" value = "50.0" /> </ node> ``` このlaunchファイルを実行し、問題なく動いていることを確認してください。 ## RVizの使用 ### (2) 上記のlaunchファイルを起動したまま、Rvizを実行してください。 グローバルオプションで、フレーム(座標系)を odom に設定してください。 次に、以下のディスプレイを追加しましょう: - ロボットモデル(RobotModel) - オドメトリ(Odometry) - Topic:/odom - Covariance のところのチェックボックスのチェックを外す - Keep(過去の値を何個まで表示する)はより小さい値にしたほうがいいです(例えば 5) - レーザースキャン (LaserScan) - Topic:/scan - 加速度計・ジャイロスコープ(Imu) - Topic:/imu 試しに、TFも追加してみてください。(しかし、動画を作るときは外してください。) ### (3) ロボットを(キーボードなどで)動かして、表示がどのように変化するかを確認してください。次の点に注目してください。 - オドメトリは常に正解です。いくら移動しても、ロボットの位置を正しく追跡しています。実際のロボットの場合はオドメトリのエラーは徐々に大きくなります。 - レーザースキャンにはノイズが含まれています(測定されたポイントは「ちらつき」のように見えます)→ これは実際のセンサの測定に似ています。 (スキャンを表示するには、壁や障害物のあるワールドでロボットをシミュレートする必要があります。) ### (4) パッケージにrvizというフォルダを作成してください。 RVizのメニューから File -> Save Config As を選び、RVizの設定をrvizフォルダに保存しましょう。 次に、launchファイルにRVizを起動するための次のような行を追加してください。 ```xml <node name="rviz" pkg="rviz" type="rviz" args="-d $(find robot2_task_名字)/rviz/rviz設定ファイル名.rviz"/> ``` launchファイルを保存して、起動してみましょう。RVizも起動するはずです。 最後に、launchファイルの中のincludeタグ内のguiパラメータをfalseに設定してから、もう一度実行してみてください。この設定でGazeboのGUIが起動しませんので、インターフェイスとしてRVizのみが使用できます。 ### (5) RVizの「2D Nav Goal」ボタンを押してからRViz内のどこかをクリックすれば、クリックした点の位置が入ったメッセージがトピック /move_base_simple/goal に配信されます。 この機能を使ってみてください。コンソールで`rostopic echo /move_base_simple/goal` を起動して、クリックした点の位置が届いていることを確認してください。 以下はこの機能をロボットに移動の目的地を与えるために使います。 ## ロボットの移動制御 ### (6) ここでは、ロボットが指定された目的地まで移動するための制御プログラムを作ります。 プログラムが /move_base_simple/goal トピックを購読し、目的地の位置のメッセージを読み取ります。そして、ロボットに適宜な速度コマンドを配信します。 実装の詳細な説明: - 目的地の位置をグローバル変数に保存します。(位置だけで十分です。/move_base_simple/goalには向きも入っていますが、それは無視していいです。) - 目的地の新しいメッセージが届いたら、コールバック関数の中で目的地のグローバル変数を更新します。コールバック関数が行うことは以上です。 - 制御はメッセージ配信ループの中に実装実装します。必要な速度と角速度を計算し、 /cmd_vel に配信します。(1週目の課題と同じようなコードを使います。ただ、ros.Rateの周波数を20にしてください。また、このページの下にあるヒントも読んでください。) - ループ内の速度を計算する方法は自分で考えてください。一つの提案は以下の通りです。 #### 速度の計算方法(案) ([週2のイントロ](v2_intro.html)で説明した制御方法2です) - ロボットから目的地までの角度と距離を計算します - 注1:これを計算するやり方はいくつかあります。いずれを使っても大丈夫です。 - → ロボットのポーズ(位置と向き)を /odom トピックから読んで、それに基づいて計算する - → TFを利用して目的地の位置をロボットの座標系(フレーム/base_link)に変換する - 注2:ROSでは角度が通常「クォータニオン」として使われています。クォータニオンから普通の角度(オイラー角度)へ変換するため、TFのAPIのメソッド `tf.transformations.euler_from_quaternion()` が利用できます。 - 目的地までの角度とロボットの現在の向きの差を計算します。 - 角速度の計算: - 角速度 = K * 角度の差 - → Kの適切な値を試行錯誤で決定します。 - ロボットが速く回転しすぎないように、角速度を制限します(0.5 rad/s以下) - 直線速度の計算: - 角度の差(の絶対値)が45度より大きい場合、速度は 0 にするのがお勧めです。 (ロボットがだいたい目的地に向かっていることを待ちます) - それ以外の場合、線形速度は一定にします(最大 0.25 m/s) - ロボットがターゲットに近づいたら、速度を下げます ### (7) 課題のパッケージのlaunchファイルに上記のプログラムを新しいノードとして追加しましょう。 launchファイルを実行し、RVizで目的地を与えたらロボットがそちらに移動することを確認してください。 ## (任意)自分でオドメトリを計算する ### (8) 簡単なオドメトリ計算プログラムを自分で実装してみましょう。以下の手順に従い、新しいROSノードを作成してください。 - ロボットの線速度 v と角速度 ω をトピック(例:/cmd_vel)から取得します。 - オドメトリの初期値として、位置 x=0、y=0、向き θ=0 を設定します。 - [週2のイントロ](v2_intro.html)で紹介した式を使って、時間間隔 T ごとに位置と向きを更新します。 - 計算したオドメトリを/my_odomトピックに配信し、nav_msgs/OdometryメッセージとしてRVizで可視化できるようにします(詳細なやり方はこの下に書いています)。 -トピック /odom RVizで可視化して、ロボットを動かしながら/odom と /my_odom のデータを比較してください。 #### Odometryメッセージの作成 トピック /my_odom を可視化するため nav_msgs/Odometry メッセージとして配信したほうがいいです(/odom トピックと同様)。 以下は計算した x,y,θ を nav_msgs/Odometry メッセージに変換するコードの例です。 ```python from nav_msgs.msg import Odometry import rospy from tf.transformations import quaternion_from_euler odom_msg = Odometry() odom_msg.header.stamp = rospy.Time.now() odom_msg.header.frame_id = "odom" # Pose odom_msg.pose.pose.position.x = x odom_msg.pose.pose.position.y = y quaternion = quaternion_from_euler(0, 0, theta) odom_msg.pose.pose.orientation.x = quaternion[0] odom_msg.pose.pose.orientation.y = quaternion[1] odom_msg.pose.pose.orientation.z = quaternion[2] odom_msg.pose.pose.orientation.w = quaternion[3] # Twist odom_msg.twist.twist.linear.x = v odom_msg.twist.twist.angular.z = omega ``` --- ## 提出するビデオで何を示せばいいのか? (すべての段階の結果がビデオにはっきりと表示されていることに注意してください!) - launchファイルを実行し、Rvizが起動するのを待ちます(タスク(8)をできた場合、トピック/my_odomも表示して) - Rvizでは、2D NavGoalボタンを使用して移動目的地を設定します - ロボットが与えられた目的地まで移動するのを待ちます - あと2つぐらいの目的地を与えてください --- リマインダー:提出するのは - 上記のビデオ(スクリーンキャスト) - この課題で作ったパッケージ (robot2\_task\_名字 フォルダ) が入った圧縮ファイルです。 --- ### ヒント 1周目の実習では、メッセージの購読と配信を説明しました。しかし、同じノードで配信と購読の両方を行う必要がある場合が多いです(この課題もそうです)。 以下はそれを行うノードの基本的な例です。 ```python #!/usr/bin/env python import rospy from std_msgs.msg import String def callbackTopic1(message): # input_topic1に届いたメッセージの処理 # ... def callbackTopic2(message): # input_topic1に届いたメッセージの処理 # ... # コールバック関数内でメッセージを発信する場合がある pub2.publish(pub_msg2) def hello_all(): rospy.init_node('hello_all') pub1 = rospy.Publisher('output_topic1', OutputTopicType1) pub2 = rospy.Publisher('output_topic2', OutputTopicType2) rospy.Subscriber("input_topic1", InputTopicType1, callbackTopic1) rospy.Subscriber("input_topic2", InputTopicType2, callbackTopic2) # rospy.spin() は使いません # (spinは無限ループなので、ここで使ったら以下のループに入れなくなります) r = rospy.Rate(XX) # XXhz while not rospy.is_shutdown(): # ... pub1.publish(pub_msg1) r.sleep() if __name__ == '__main__': hello_all() ``` --- ← [実習その2](v2_2.html) ↑ [ホームページ](index.html)