🐢

ROSによるロボット操作法 Part-2 シミュレーション(Robot)編

2023/12/18に公開

0. 記事の内容

本記事の内容は、THK(株)社製の移動台車を備えた単腕のロボットのシミュレーションによる操作の手順等についてまとめています。

このPart-2は文字数の制限から1つの内容を前半のTurtle編と後半のRobot編に分けています。そのため、各章の番号の割振りも含め、前半と後半で連続した内容となっていますので、必要に応じてPart-2の前半をご覧下さい。Part-1およびPart-2の前半の内容は次のリンクからご覧頂けます(別のページか開きます)。
Part-1 実機編 [リンク]
Part-2 シミュレーション(Turtle)編 [リンク]

6.ロボットのGUIによる制御

この章では、Part-1 第4章で行ったTHK(株)のロボットの操作をシミュレーションにより行う手順を示します。実際にロボットを動かすときは、周囲の人やものとの衝突などの危険を伴いますが、シミュレーションでは、予期しない動きをしても問題ありませんので、事前の操作やプログラムの確認に用いることができます。

6-1.ロボットのマニュアル操作

Part-1 第4章およびPart-2 第3章、第4章で用いたGUI画面による、ロボットの前後進、左右回転の操作、およびロボットの昇降機構Seed-Lifter、アームSeed-Arm、ハンドTRXの各関節の操作を行います。

(1) Byobuの起動
Desktopの左側にあるタスクバーから「ターミナル」を起動し、フォルダを/home/seed/rosに変えて、「byobu」コマンドを入力します。

            cd /home/seed/ros
            byobu

(2) roscoreの起動
Byobuの最初のウインドウ0で次のコマンドを入力します。

            roscore

(3) ros controlの起動
F2を押して、Byobuにウインドウ1を追加し、次のコマンドを入力します。

            roslaunch seed_r7_bringup seed_r7_bringup.launch robot_model:=typeg_arm

(4) Rvizの起動
F2を押して、Byobuにウインドウ2を追加し、次のコマンドを入力します(図6-1)。

            rosrun rviz rviz -d `rospack find seed_r7_bringup`/rviz.rviz

この段階では、ロボットでなく白い台車が現れます。また、地図でなくフレーム枠が表示されます。


図6-1 Rvizの起動画面

(5) ロボットと地図の表示
F2を押して、Byobuにウインドウ3を追加し、次のコマンドを入力します(図6-2、図6-3)。

            roslaunch seed_r7_navigation wheel_with_dummy.launch

ロボットに軸や名前が表示されているときは、左側のDisplayペインで以下の設定を行います。

            Global OptionsのFixed Frame       : map
            TFのShow Names                    : チェックを外す
            TFのShow Axes                     : チェックを外す

ここで表示された地図は、事前に作成されたシステムのデフォルトのものです。


図6-2 Rivzの起動時のロボット


図6-3 ロボットの設定

(6) rqt_robot_steeringの起動
F2を押して、Byobuにウインドウ4を追加し、次のコマンドを入力します(図6-4、図6-5)。

            rosrun rqt_robot_steering rqt_robot_steering

左上のトピックメッセージを/cmd_velとします。これまでの第3章、第4章と同様の操作により、地図上の障害物を1周するなど、ロボットの移動制御を確認できます。

(7) rqt_joint_trajectory_controllerの起動
F2を押して、Byobuにウインドウ5を追加し、次のコマンドを入力します(図6-4)。

            rosrun rqt_joint_trajectory_controller rqt_joint_trajectory_controller

操作方法はPart-1 第4章「4-2. ロボットのマニュアル操作」(3)と同様です。現れた画面の上部の controller の項目を変更することにより、それぞれのスライダーの操作によりロボットの関節の動きを制御することができます。

ここで、操作する前に中央の赤い非常停止ボタンを押して、緑色にする必要があります。

      () arm_controller
            elbow_joint             肘の上下 
            shoulder_p_joint        ()の回転 
            shoulder_r_joint        ()の前後 
            wrist_p_joint           手首の上下
            wrist_r_joint           手首の左右(可動範囲は狭い) 
            wrist_y_jpint           手首の回転 

      () hand_controller
            r_thumb_joint           手の把持 

      () lifter_controller
            ankle_joint             足首(下段の関節) 
            knee_joint               (中段の関節)


図6-4 rqt_robot_steeringとrqt_joint_trajectory_controllerの起動


図6-5 ロボットの移動

6-2.ロボットの自律移動

Part-1 第4章「4-3. ロボットの自律移動」(3)で行ったロボットの自律移動を行います。
前節6-1のRvizの画面で、次の操作を行います(図6-6)。

a. [2D Nav Goal]のボタンを押す。
b. ロボットの移動位置でマウスをクリックする。
c. 現れた矢印をドラッグでロボットの向きを設定する。
d. ロボットがそこまでのルートを探索して移動を始める。

ルートの探査は、地図上の障害物の回避や目的地での最終的なロボットの向き等も考慮して行われます。目的地を障害物の反対側に設定したりすると確認できます。


図6-6 2D Nav Goalの設定によるロボットの自律移動

6-3.マウスによるアームの操作

Part-1 第4章「4-4. マウスによるアームの操作」(2)に対応して、Rvizに描かれたロボットのアームをマウスでドラックして目的位置を設定し、モーションプラニングツール(Motion Planning Tool)のMoveItにより途中の姿勢動作を計画して移動します。

以下の手順では、改めてROSを新規に起動します。
(1) Byobuの起動
Desktopの左側にあるタスクバーから「ターミナル」を起動し、フォルダを/home/seed/rosに変えて、「byobu」コマンドを入力します。

            cd /home/seed/ros
            byobu

(2) roscoreの起動
Byobuの最初のウインドウ0で次のコマンドを入力します。

            roscore

(3) Rvizの起動
F2を押して、Byobuにウインドウ1を追加し、次のコマンドを入力します(図6-7)。

            rosrun seed_r7_bringup moveit.launch robot_model:=typeg_arm


図6-7 Rvizの起動時のロボット

(4) アニメーションの設定
画面の左側のDisplayペインで、以下の項目の設定を行います(図6-8、図6-9)。

      MotionPlanning > Planned Path > Loop Animation            : チェックを外す
      また、必要に応じて以下の設定を行います。
      MotionPlanning > Planned Path > Robot Alpha               : 0.5sから0sに変更
      MotionPlanning > Planned Path > State Display Time        : 0.05sからREALTIMEに変更
      MotionPlanning > Planning Request > Query Start State     : スタート時の姿勢の表示/非表示
      MotionPlanning > Planning Request > Query Goal State      : ゴール時の姿勢の表示/非表示

MotionPlanningでは、現在の姿勢とマウスのドラッグで設定したゴールの姿勢に対し、途中の姿勢を計画して移動します。


図6-8 Loop Animation等の設定


図6-9 スタート時、ゴール時の姿勢の表示/非表示

(5) アームの操作
a. 画面の左側のMotion Planningペインで、以下の設定を行います(図6-10)。

            「Planning」タブ > Planning Group              : 「arm_with_torso」にする

「hand」と「lifter」を選択したときは、現状ではロボットにマーカー(Interactive Marker)が現れず、姿勢変更の操作ができません。

b. ロボットに表示のマーカーをマウスでドラッグします(図6-11)。マーカーの移動について、以下の操作ができます。

            3 軸に沿った矢印              : 軸方向の移動
            3 軸周りのドーナツ型の円板    : 軸周りの回転
            3 軸の中心の球                : 3 軸中心の移動

【注意】
マーカーを関節の関係から設定できない位置にドラッグしたりすると、突然、意図しないところに移動することがあるようです。

c. MotionPlanning ペインの「Plan & Execute」のボタンを押して実行します。

d. 移動途中の姿勢を表示させるには、画面の左側のDisplayペインで、以下の項目の設定を行います(図6-12)。

            MotionPlanning > Planned Path > Show Trail            : チェックします
            MotionPlanning > Planned Path > Robot Alpha           : 0.5等にする
            MotionPlanning > Planned Path > Trail Step Size       : 表示間隔を設定


(a) ゴール時の姿勢の設定


(b) 姿勢の移動後
図6-10 姿勢の移動


(a) 軸周りの回転1


(b) 軸周りの回転2


(c) 3 軸中心の移動
図6-11 マーカーの操作


図6-12 Show Trailの表示

7.ロボットのプログラムによる制御

この章では、Part-1 第4章で行ったTHK(株)のロボットの操作をシミュレーションで行うPythonプログラムの作成手順を示します。
プログラムは、シミュレーションでも実際のロボットでも、そのまま動作させることができ、ロボットのプログラム開発を効率的に行うことができます。

7-1.ロボットの移動

これまでにも、第3章「3-3. Turtlesimの仕組み」でロボットの移動プログラムでTurtleを移動させましたが、ここでは、実行環境の設定を含めたロボットを制御するプログラムの実行手順を示します。

(1) 実行環境
第6章「6-1. ロボットのマニュアル操作」と同じ設定を行います。

a. 「ターミナル」を起動し、フォルダをPythonのProgramを保存するフォルダに変更して、「byobu」コマンドを入力します。

            cd /home/seed/ros/melodic/src/dsl_ros_pkg/script
            byobu

b. Byobuの最初のウインドウ0で、次のコマンドを入力します。

            roscore

c. F2を押して、Byobuにウインドウ1を追加し、次のコマンドを入力します。

            roslaunch seed_r7_bringup seed_r7_bringup.launch robot_model:=typeg_arm

d. F2を押して、Byobuにウインドウ2を追加し、次のコマンドを入力します。

            rosrun rviz rviz -d `rospack find seed_r7_bringup`/rviz.rviz

e. F2を押して、Byobuにウインドウ3を追加し、次のコマンドを入力します。

            roslaunch seed_r7_navigation wheel_with_dummy.launch

Displayペインで次の設定を行います。

            Global OptionsのFixed Frame       : map
            TFのShow Names                    : チェックを外す
            TFのShow Axes                     : チェックを外す

f. F2を押して、Byobuにウインドウ4を追加し、次のコマンドを入力します。

            rosrun rqt_robot_steering rqt_robot_steering

トピックメッセージを/cmd_velに設定します。

(2) 既存プログラムの実行
a. Turtleのプロクラム
第5章「5-2. Turtleの移動」のTurtleが円を描くプログラムturtle_move01.pyを用いて、シミュレーションのロボットも同様に移動させることが出来ます。
F2を押して、Byobuにウインドウ5を追加し、次のコマンドを入力します(図7-1)。

            rosrun dsl_ros_pkg turtle_move01.py /turtle1/cmd_vel:=/cmd_vel

プログラムの終了は、コマンドを入力したウインドウでCTRL+Cを押します。


図7-1 ロボットの円軌道の移動

【参考】
・turtle_move02.pyでは、2つ目のロボットを作成できないため、turtle_move01.pyと同じ結果となります。ロボットに複数のTurtleを作成するサービス通信の/spawnに対応するものがありません。
・turtle_move03.pyでは、ロボットの位置情報の取得ができないため、プログラムは動きません。ロボットに位置情報のトピック通信の/turtle1/poseに対応するものがありません。

b. ロボットのプログラム
Part-1の第6章「6-4. ロボットのプログラム操作」での実際のロボットを矩形状に移動させるプログラムrobot_mover.pyを用いて、第3章「3-3. Turtlesimの仕組み」でTurtleを移動させることができることを示しました。シミュレーションのロボットも同様に実行することができます。
Byobuのウインドウ5で、次のコマンドを入力します。

            rosrun dsl_ros_pkg robot_mover.py

プログラムを改めてリスト7-1に示します。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import rospy
from geometry_msgs.msg import Twist

if __name__ == "__main__":
    rospy.init_node('move_forward_node')
    pubTwist = rospy.Publisher('/cmd_vel', Twist, queue_size=1)

    loop = rospy.Rate(1)

    vel = Twist()
    vel.linear.x =  0.1
    vel.linear.y =  0.0
    vel.linear.z =  0.0
    vel.angular.x = 0.0
    vel.angular.y = 0.0
    vel.angular.z = 0.0
    for _ in range(10):
        pubTwist.publish(vel)
        loop.sleep()

    vel.linear.x =  0.0
    vel.linear.y =  0.1
    vel.linear.z =  0.0
    vel.angular.x = 0.0
    vel.angular.y = 0.0
    vel.angular.z = 0.0
    for _ in range(10):
        pubTwist.publish(vel)
        loop.sleep()

    vel.linear.x = -0.1
    vel.linear.y =  0.0
    vel.linear.z =  0.0
    vel.angular.x = 0.0
    vel.angular.y = 0.0
    vel.angular.z = 0.0
    for _ in range(10):
        pubTwist.publish(vel)
        loop.sleep()

    vel.linear.x =  0.0
    vel.linear.y = -0.1
    vel.linear.z =  0.0
    vel.angular.x = 0.0
    vel.angular.y = 0.0
    vel.angular.z = 0.0
    for _ in range(10):
        pubTwist.publish(vel)
        loop.sleep()

    vel.linear.x =  0.0
    vel.linear.y =  0.0
    vel.linear.z =  0.0
    vel.angular.x = 0.0
    vel.angular.y = 0.0
    vel.angular.z = 0.0
    pubTwist.publish(vel)


リスト7-1 robot_mover.py

7-2.ロボットの関節の操作

(1) 操作環境
第6章「6-3. マウスによるアームの操作」と同じ設定を行います。

a. 「ターミナル」を起動し、
フォルダをPythonのProgramを保存するフォルダに変更して、「byobu」コマンドを入力します。

            cd /home/seed/ros/melodic/src/dsl_ros_pkg/script
            byobu

b. Byobuの最初のウインドウ0で、次のコマンドを入力します。

            roscore

c. F2を押して、Byobuにウインドウ1を追加し、次のコマンドを入力します。

            roslaunch seed_r7_bringup moveit.launch robot_model:=typeg_arm

Displayペインで、必要に応じて以下の項目の設定を行います。

      MotionPlanning > Planned Path > Loop Animation            : チェックを外す
      MotionPlanning > Planned Path > Robot Alpha               : 0.5sから0sに変更
      MotionPlanning > Planned Path > State Display Time        : 0.05sからREALTIMEに変更
      MotionPlanning > Planning Request > Query Start State     : スタート時の姿勢の表示/非表示
      MotionPlanning > Planning Request > Query Goal State      : ゴール時の姿勢の表示/非表示

      必要に応じてMotionPlanningペインで、以下の項目の設定を行います。
      「Planning」タブ > Planning Group                          : 「hand」を設定

d. F2を押して、Byobuにウインドウ2を追加し、次のコマンドを入力してプログラムを実行します。

            rosrun dsl_ros_pkg プログラム名.py

プログラムの終了は、起動したウインドウでCTRL+Cを押します。

(2) ロボットの膝、踵の操作(Seed-Lifter)
Part-1の第6章「6-4. ロボットのプログラム操作」(5)のプログラムrobot_lifter.pyを実行します。プログラムをリスト7-2に示します。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import rospy
import moveit_commander

if __name__ == "__main__":
    rospy.init_node("lifter_node")

    RobotLifter = moveit_commander.MoveGroupCommander("lifter")
    print("RobotLifter", RobotLifter.get_active_joints())
    RobotLifter.set_max_velocity_scaling_factor(1.0)

    current_lifter_joint = RobotLifter.get_current_joint_values()
    print("RobotLifter", current_lifter_joint)

    RobotLifter.set_joint_value_target("ankle_joint",  0.00)
    RobotLifter.set_joint_value_target("knee_joint",   0.00)
    RobotLifter.go(wait=True)

    RobotLifter.set_joint_value_target("ankle_joint",  0.80)
    RobotLifter.set_joint_value_target("knee_joint",  -0.80)
    RobotLifter.go(wait=True)

    RobotLifter.set_joint_value_target("ankle_joint",  0.00)
    RobotLifter.set_joint_value_target("knee_joint",   0.00)
    RobotLifter.go(wait=True)


リスト-2 robot_lifter.py

(3) ロボットの肘、肩、手首の操作(Seed-Arm)
Part-1の第6章「6-4. ロボットのプログラム操作」(6)のプログラムrobot_arm.pyを実行します。プログラムをリスト7-3に示します。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import rospy
import moveit_commander

if __name__ == "__main__":
    rospy.init_node("arm_node")

    RobotArm = moveit_commander.MoveGroupCommander("arm")
    print("RobotArm", RobotArm.get_active_joints())
    RobotArm.set_max_velocity_scaling_factor(1.0)

    current_arm_joint = RobotArm.get_current_joint_values()
    print("RobotArm", current_arm_joint)

    RobotArm.set_joint_value_target("wrist_p_joint",     0.00)
    RobotArm.set_joint_value_target("elbow_joint",       0.00)
    RobotArm.set_joint_value_target("shoulder_y_joint",  0.00)
    RobotArm.set_joint_value_target("shoulder_p_joint",  0.00)
    RobotArm.go(wait=True)

    RobotArm.set_joint_value_target("wrist_p_joint",     0.30)
    RobotArm.go()
    RobotArm.set_joint_value_target("wrist_p_joint",    -0.30)
    RobotArm.go()
    RobotArm.set_joint_value_target("wrist_p_joint",     0.00)
    RobotArm.go()

    RobotArm.set_joint_value_target("elbow_joint",      -1.57)
    RobotArm.go()
    RobotArm.set_joint_value_target("elbow_joint",       1.25)
    RobotArm.go()
    RobotArm.set_joint_value_target("elbow_joint",       0.00)
    RobotArm.go()

    RobotArm.set_joint_value_target("shoulder_y_joint", -2.00)
    RobotArm.go()
    RobotArm.set_joint_value_target("shoulder_y_joint",  2.00)
    RobotArm.go()
    RobotArm.set_joint_value_target("shoulder_y_joint",  0.00)
    RobotArm.go()

    RobotArm.set_joint_value_target("shoulder_p_joint",  0.00)
    RobotArm.go()
    RobotArm.set_joint_value_target("shoulder_p_joint",  1.57)
    RobotArm.go()
    RobotArm.set_joint_value_target("shoulder_p_joint",  0.00)
    RobotArm.go()

    RobotArm.set_joint_value_target("wrist_p_joint",     0.00)
    RobotArm.set_joint_value_target("elbow_joint",       0.00)
    RobotArm.set_joint_value_target("shoulder_y_joint",  0.00)
    RobotArm.set_joint_value_target("shoulder_p_joint",  0.00)
    RobotArm.go(wait=True)


リスト7-3 robot_arm.py

(4) ロボットの手の操作(TRX)
Part-1の第6章「6-4. ロボットのプログラム操作」【参考】のプログラムrobot_hand.pyを実行します。但し、「(1) 操作環境」で設定したDisplayペインの「Robot Alpha」の値を元の0.5sに戻す必要があります。また、元の手の画像が残っていたり、手の動きが止まらなかったり、うまく制御できないところがあります。プログラムをリスト7-4に示します。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import rospy
import moveit_commander
from seed_r7_ros_controller.srv import HandControl

if __name__ == "__main__":
    rospy.init_node("hand_node")

    RobotHand = moveit_commander.MoveGroupCommander("hand")
    print("RobotHand", RobotHand.get_active_joints())
    RobotHand.set_max_velocity_scaling_factor(1.0)

    RobotHand.set_joint_value_target("r_thumb_joint", -0.9)
    RobotHand.go(wait=False)
    rospy.sleep(5)

    try:
        rospy.wait_for_service('/seed_r7_ros_controller/hand_control')
        service = rospy.serviceProxy('/seed_r7_ros_controller/hand_control', HandControl)
    except:
        pass


リスト7-4 robot_hand.py

7-3.指定位置への移動

Rvizでは、「2D Nav Goal」のボタンを押して、ロボットを指定した位置に向きも指定して移動させることができます。この移動に対応したトピックメッセージを送信して、ロボットを移動させるプログラムを作成します。
操作環境は「7-1. ロボットの移動」(1)を設定します。このとき、Rvizに表示されている地図の座標系を図7-2に示します。起動時にロボットの現れるところが原点で、x座標、y座標の範囲は供に-5~+5まで、間隔1の格子が描かれています。


図7-2 地図の座標系行

(1) 目的地の設定
a. ノード名は、robot_goalです。

b. トピックメッセージは、move_base_simple/goalで、型はPoseStampedです。次の命令により、送信側のオブジェクトpubを作成します。

            pub = rospy.Publisher('move_base_simple/goal', PoseStamped, queue_size=10)

c. PoseStamped型のトピックメッセージには、座標x,y,zと向きx,y,z,wを設定します。
ここで、向きは、クォータニオン (Quaternion) で設定します。
クォータニオンは、任意の軸に対する回転を考慮して3次元空間内のどのような回転も表現できます。ここでは、回転軸はz軸となりますので、x軸からの回転角をθとしたときのクォータニオンは、(0, 0, sin(θ/2), cos(θ/2)) となります。座標系のx軸とy軸に沿った方向のクォータニオンの値を図7-3に示します。

d. ロボットを座標(3,2)の位置に移動し、x軸方向に向くときのプログラムrobot_mover_goal01.pyをリスト7-5に示します(図7-4)。


図7-3 x軸、y軸のクォータニオンの値


図7-4 ロボットの指定点への移動

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import rospy
from geometry_msgs.msg import PoseStamped

def seed_mover():
    rospy.init_node('robot_goal', anonymous=True)
    pub = rospy.Publisher('move_base_simple/goal', PoseStamped, queue_size=10)
    rospy.sleep(1)

    now = rospy.Time.now()
    goal_point = PoseStamped()

    goal_point.header.seq      = 0
    goal_point.header.stamp    = now
    goal_point.header.frame_id = 'map'

    goal_point.pose.position.x    =  3.0
    goal_point.pose.position.y    =  2.0
    goal_point.pose.position.z    =  0.0

    goal_point.pose.orientation.x =  0.0 
    goal_point.pose.orientation.y =  0.0
    goal_point.pose.orientation.z =  0.0
    goal_point.pose.orientation.w =  1.0

    pub.publish(goal_point)

if __name__ == '__main__':
    try:
        seed_mover()
    except rospy.ROSInterruptException:
        pass


リスト7-5 robot_mover_goal01.py

(2) 障害物を1周
a. ノード名は、robot_goalです。

b. トピックメッセージmove_base_simple/goalで設定された位置にロボットが到達すると、トピックメッセージmove_base/resultのstatusが3(success)になります。次の命令により、受信側のオブジェクトsubを作成し、メッセージが受信されたときにgoal_update関数が呼び出されるよう設定します。

            sub = rospy.Subscriber('move_base/result', MoveBaseActionResult, goal_update)

c. ロボットは目的地で指定の方向に向くように設定し、目的地に到達したら次の目的地の位置と向きを設定します。

d. 障害物を1周するように目的地と向きを設定したプログラムrobot_mover_goal02.pyのリストをリスト7-6に示します(図7-5)。


図7-5 障害物を1周 (robot_mover_goal02.py)

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import rospy
from geometry_msgs.msg import PoseStamped
from move_base_msgs.msg import MoveBaseActionResult

rt2 = 0.70710678
goal_msg = 0

def goal_update(data):
    global goal_msg
    goal_msg = data.status.status
    
def seed_mover():
    global goal_msg
    rospy.init_node('robot_goal', anonymous=True)
    pub = rospy.Publisher('move_base_simple/goal', PoseStamped, queue_size=10)
    sub = rospy.Subscriber('move_base/result', MoveBaseActionResult, goal_update)
    rospy.sleep(1)

    goal_point = PoseStamped()

    goal_point.header.seq      = 0
    goal_point.header.stamp    = rospy.Time.now()
    goal_point.header.frame_id = 'map'

    goal_point.pose.position.x    =  0.0
    goal_point.pose.position.y    = -4.0
    goal_point.pose.position.z    =  0.0

    goal_point.pose.orientation.x =  0.0 
    goal_point.pose.orientation.y =  0.0
    goal_point.pose.orientation.z =  1.0
    goal_point.pose.orientation.w =  0.0

    pub.publish(goal_point)
    goal_msg = 0
    while not rospy.is_shutdown():
        print("Point 1 : goal_msg {}".format(goal_msg))
        if (goal_msg == 3):
            break

    goal_point.header.seq      = 0
    goal_point.header.stamp    = rospy.Time.now()
    goal_point.header.frame_id = 'map'

    goal_point.pose.position.x    = -4.0
    goal_point.pose.position.y    = -4.0
    goal_point.pose.position.z    =  0.0

    goal_point.pose.orientation.x =  0.0 
    goal_point.pose.orientation.y =  0.0
    goal_point.pose.orientation.z =  rt2
    goal_point.pose.orientation.w =  rt2

    pub.publish(goal_point)
    goal_msg = 0
    while not rospy.is_shutdown():
        print("Point 2 : goal_msg {}".format(goal_msg))
        if (goal_msg == 3):
            break

    goal_point.header.seq      = 0
    goal_point.header.stamp    = rospy.Time.now()
    goal_point.header.frame_id = 'map'

    goal_point.pose.position.x    = -4.0
    goal_point.pose.position.y    =  0.0
    goal_point.pose.position.z    =  0.0

    goal_point.pose.orientation.x =  0.0 
    goal_point.pose.orientation.y =  0.0
    goal_point.pose.orientation.z =  0.0
    goal_point.pose.orientation.w =  1.0

    pub.publish(goal_point)
    goal_msg = 0
    while not rospy.is_shutdown():
        print("Point 3 : goal_msg {}".format(goal_msg))
        if (goal_msg == 3):
            break

    goal_point.header.seq      = 0
    goal_point.header.stamp    = rospy.Time.now()
    goal_point.header.frame_id = 'map'

    goal_point.pose.position.x    =  0.0
    goal_point.pose.position.y    =  0.0
    goal_point.pose.position.z    =  0.0

    goal_point.pose.orientation.x =  0.0 
    goal_point.pose.orientation.y =  0.0
    goal_point.pose.orientation.z =  0.0
    goal_point.pose.orientation.w =  1.0

    pub.publish(goal_point)
    goal_msg = 0
    while not rospy.is_shutdown():
        print("Point 4 : goal_msg {}".format(goal_msg))
        if (goal_msg == 3):
            break

if __name__ == '__main__':
    try:
        seed_mover()
    except rospy.ROSInterruptException:
        pass


リスト7-6 robot_mover_goal02.py

(3) 目的地で方向転換
a. ノード名は、robot_goalです。

b. ロボットが障害物を矩形を描くように1周させます。ここでは、(2)と異なり、ロボットが目的地の矩形の角に到達したら、回転するように、移動と回転を別々に実行します。プログラムrobot_mover_goal03.pyのリストをリスト7-7に示します。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import rospy
from geometry_msgs.msg import PoseStamped
from move_base_msgs.msg import MoveBaseActionResult

rt2 = 0.70710678
goal_msg = 0

def goal_update(data):
    global goal_msg
    goal_msg = data.status.status
    
def seed_mover():
    global goal_msg
    rospy.init_node('robot_goal', anonymous=True)
    pub = rospy.Publisher('move_base_simple/goal', PoseStamped, queue_size=10)
    sub = rospy.Subscriber('move_base/result', MoveBaseActionResult, goal_update)
    rospy.sleep(1)
    goal_point = PoseStamped()

#----- Point 0
    goal_point.header.seq      = 0
    goal_point.header.stamp    = rospy.Time.now()
    goal_point.header.frame_id = 'map'

    goal_point.pose.position.x    =  0.0
    goal_point.pose.position.y    =  0.0
    goal_point.pose.position.z    =  0.0

    goal_point.pose.orientation.x =  0.0 
    goal_point.pose.orientation.y =  0.0
    goal_point.pose.orientation.z = -rt2
    goal_point.pose.orientation.w =  rt2

    pub.publish(goal_point)
    goal_msg = 0
    while not rospy.is_shutdown():
        print("Point 0 : goal_msg {}".format(goal_msg))
        if (goal_msg == 3):
            break

#----- Point 1
    goal_point.header.seq      = 0
    goal_point.header.stamp    = rospy.Time.now()
    goal_point.header.frame_id = 'map'

    goal_point.pose.position.x    =  0.0
    goal_point.pose.position.y    = -4.0
    goal_point.pose.position.z    =  0.0

    goal_point.pose.orientation.x =  0.0 
    goal_point.pose.orientation.y =  0.0
    goal_point.pose.orientation.z = -rt2
    goal_point.pose.orientation.w =  rt2

    pub.publish(goal_point)
    goal_msg = 0
    while not rospy.is_shutdown():
        print("Point 1 : goal_msg {}".format(goal_msg))
        if (goal_msg == 3):
            break

    goal_point.header.seq      = 0
    goal_point.header.stamp    = rospy.Time.now()
    goal_point.header.frame_id = 'map'

    goal_point.pose.position.x    =  0.0
    goal_point.pose.position.y    = -4.0
    goal_point.pose.position.z    =  0.0

    goal_point.pose.orientation.x =  0.0 
    goal_point.pose.orientation.y =  0.0
    goal_point.pose.orientation.z =  1.0
    goal_point.pose.orientation.w =  0.0

    pub.publish(goal_point)
    goal_msg = 0
    while not rospy.is_shutdown():
        print("Point 1 : goal_msg {}".format(goal_msg))
        if (goal_msg == 3):
            break

#----- Point 2
    goal_point.header.seq      = 0
    goal_point.header.stamp    = rospy.Time.now()
    goal_point.header.frame_id = 'map'

    goal_point.pose.position.x    = -4.0
    goal_point.pose.position.y    = -4.0
    goal_point.pose.position.z    =  0.0

    goal_point.pose.orientation.x =  0.0 
    goal_point.pose.orientation.y =  0.0
    goal_point.pose.orientation.z =  1.0
    goal_point.pose.orientation.w =  0.0

    pub.publish(goal_point)
    goal_msg = 0
    while not rospy.is_shutdown():
        print("Point 2 : goal_msg {}".format(goal_msg))
        if (goal_msg == 3):
            break

    goal_point.header.seq      = 0
    goal_point.header.stamp    = rospy.Time.now()
    goal_point.header.frame_id = 'map'

    goal_point.pose.position.x    = -4.0
    goal_point.pose.position.y    = -4.0
    goal_point.pose.position.z    =  0.0

    goal_point.pose.orientation.x =  0.0 
    goal_point.pose.orientation.y =  0.0
    goal_point.pose.orientation.z =  rt2
    goal_point.pose.orientation.w =  rt2

    pub.publish(goal_point)
    goal_msg = 0
    while not rospy.is_shutdown():
        print("Point 2 : goal_msg {}".format(goal_msg))
        if (goal_msg == 3):
            break

#----- Point 3
    goal_point.header.seq      = 0
    goal_point.header.stamp    = rospy.Time.now()
    goal_point.header.frame_id = 'map'

    goal_point.pose.position.x    = -4.0
    goal_point.pose.position.y    =  0.0
    goal_point.pose.position.z    =  0.0

    goal_point.pose.orientation.x =  0.0 
    goal_point.pose.orientation.y =  0.0
    goal_point.pose.orientation.z =  rt2
    goal_point.pose.orientation.w =  rt2

    pub.publish(goal_point)
    goal_msg = 0
    while not rospy.is_shutdown():
        print("Point 3 : goal_msg {}".format(goal_msg))
        if (goal_msg == 3):
            break

    goal_point.header.seq      = 0
    goal_point.header.stamp    = rospy.Time.now()
    goal_point.header.frame_id = 'map'

    goal_point.pose.position.x    = -4.0
    goal_point.pose.position.y    =  0.0
    goal_point.pose.position.z    =  0.0

    goal_point.pose.orientation.x =  0.0 
    goal_point.pose.orientation.y =  0.0
    goal_point.pose.orientation.z =  0.0
    goal_point.pose.orientation.w =  1.0

    pub.publish(goal_point)
    goal_msg = 0
    while not rospy.is_shutdown():
        print("Point 3 : goal_msg {}".format(goal_msg))
        if (goal_msg == 3):
            break

#----- Point 4
    goal_point.header.seq      = 0
    goal_point.header.stamp    = rospy.Time.now()
    goal_point.header.frame_id = 'map'

    goal_point.pose.position.x    =  0.0
    goal_point.pose.position.y    =  0.0
    goal_point.pose.position.z    =  0.0

    goal_point.pose.orientation.x =  0.0 
    goal_point.pose.orientation.y =  0.0
    goal_point.pose.orientation.z =  0.0
    goal_point.pose.orientation.w =  1.0

    pub.publish(goal_point)
    goal_msg = 0
    while not rospy.is_shutdown():
        print("Point 4 : goal_msg {}".format(goal_msg))
        if (goal_msg == 3):
            break

if __name__ == '__main__':
    try:
        seed_mover()
    except rospy.ROSInterruptException:
        pass


リスト7-7 robot_mover_goal03.py

【参考】ロボットの回転方向
ロボットの回転方向の制御は行っていません。回転速度を設定して、ロボットを大まかに次の点に向かうように回転させるプログラムrobot_mover_goal04.pyをリスト7-8に示します。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import rospy
from geometry_msgs.msg import PoseStamped, Twist
from move_base_msgs.msg import MoveBaseActionResult

cmd_vel = Twist()
rt2 = 0.70710678
goal_msg = 0

def goal_update(data):
    global goal_msg
    goal_msg = data.status.status
    
def seed_mover():
    global goal_msg
    rospy.init_node('robot_goal', anonymous=True)
    pub  = rospy.Publisher('move_base_simple/goal', PoseStamped, queue_size=10)
    pub2 = rospy.Publisher('cmd_vel', Twist, queue_size=10)
    sub  = rospy.Subscriber('move_base/result', MoveBaseActionResult, goal_update)
    rospy.sleep(1)

    goal_point = PoseStamped()

#----- Point 0
    cmd_vel.linear.x   =  0.0
    cmd_vel.linear.y   =  0.0
    cmd_vel.linear.z   =  0.0
    cmd_vel.angular.x  =  0.0
    cmd_vel.angular.y  =  0.0
    cmd_vel.angular.z  = -1.5

    pub2.publish(cmd_vel)
    rospy.sleep(2)

#----- Point 1
    goal_point.header.seq      = 0
    goal_point.header.stamp    = rospy.Time.now()
    goal_point.header.frame_id = 'map'

    goal_point.pose.position.x    =  0.0
    goal_point.pose.position.y    = -4.0
    goal_point.pose.position.z    =  0.0

    goal_point.pose.orientation.x =  0.0 
    goal_point.pose.orientation.y =  0.0
    goal_point.pose.orientation.z = -rt2
    goal_point.pose.orientation.w =  rt2

    pub.publish(goal_point)
    goal_msg = 0
    while not rospy.is_shutdown():
        print("Point 1 : goal_msg {}".format(goal_msg))
        if (goal_msg == 3):
            break

    cmd_vel.linear.x   =  0.0
    cmd_vel.linear.y   =  0.0
    cmd_vel.linear.z   =  0.0
    cmd_vel.angular.x  =  0.0
    cmd_vel.angular.y  =  0.0
    cmd_vel.angular.z  = -1.2

    pub2.publish(cmd_vel)
    rospy.sleep(2)

#----- Point 2
    goal_point.header.seq      = 0
    goal_point.header.stamp    = rospy.Time.now()
    goal_point.header.frame_id = 'map'

    goal_point.pose.position.x    = -4.0
    goal_point.pose.position.y    = -4.0
    goal_point.pose.position.z    =  0.0

    goal_point.pose.orientation.x =  0.0 
    goal_point.pose.orientation.y =  0.0
    goal_point.pose.orientation.z =  1.0
    goal_point.pose.orientation.w =  0.0

    pub.publish(goal_point)
    goal_msg = 0
    while not rospy.is_shutdown():
        print("Point 2 : goal_msg {}".format(goal_msg))
        if (goal_msg == 3):
            break

    cmd_vel.linear.x   =  0.0
    cmd_vel.linear.y   =  0.0
    cmd_vel.linear.z   =  0.0
    cmd_vel.angular.x  =  0.0
    cmd_vel.angular.y  =  0.0
    cmd_vel.angular.z  = -1.2

    pub2.publish(cmd_vel)
    rospy.sleep(2)

#----- Point 3
    goal_point.header.seq      = 0
    goal_point.header.stamp    = rospy.Time.now()
    goal_point.header.frame_id = 'map'

    goal_point.pose.position.x    = -4.0
    goal_point.pose.position.y    =  0.0
    goal_point.pose.position.z    =  0.0

    goal_point.pose.orientation.x =  0.0 
    goal_point.pose.orientation.y =  0.0
    goal_point.pose.orientation.z =  rt2
    goal_point.pose.orientation.w =  rt2

    pub.publish(goal_point)
    goal_msg = 0
    while not rospy.is_shutdown():
        print("Point 3 : goal_msg {}".format(goal_msg))
        if (goal_msg == 3):
            break

    cmd_vel.linear.x   =  0.0
    cmd_vel.linear.y   =  0.0
    cmd_vel.linear.z   =  0.0
    cmd_vel.angular.x  =  0.0
    cmd_vel.angular.y  =  0.0
    cmd_vel.angular.z  = -1.3

    pub2.publish(cmd_vel)
    rospy.sleep(2)

#----- Point 4
    goal_point.header.seq      = 0
    goal_point.header.stamp    = rospy.Time.now()
    goal_point.header.frame_id = 'map'

    goal_point.pose.position.x    =  0.0
    goal_point.pose.position.y    =  0.0
    goal_point.pose.position.z    =  0.0

    goal_point.pose.orientation.x =  0.0 
    goal_point.pose.orientation.y =  0.0
    goal_point.pose.orientation.z =  0.0
    goal_point.pose.orientation.w =  1.0

    pub.publish(goal_point)
    goal_msg = 0
    while not rospy.is_shutdown():
        print("Point 4 : goal_msg {}".format(goal_msg))
        if (goal_msg == 3):
            break

if __name__ == '__main__':
    try:
        seed_mover()
    except rospy.ROSInterruptException:
        pass


リスト7-8 robot_mover_goal04.py

【参考】通過点の設定
a. ロボットが通過する地点を設定し、それを順番にたどるウェイポイントナビゲーション (Waypoint Navigation)のプログラムを示します。途中の障害物も回避して、指定の地点まで移動します。

b. アクション通信によりロボットの制御を行うため、プログラムでは、そのツールを提供するactionlibパッケージをimportします。

c. ノード名は、robot_goalです。

d. ロボットを指定点に移動させるアクションメッセージを送信するアクションクライアントのオブジェクトclientを次の命令により作成します。

            client = actionlib.SimpleActionClient('move_base', MoveBaseAction)

ここで、

            move_base         : 命令を受け取るアクションサーバーの名前
            MoveBaseAction    : 送信するアクションメッセージの型

e. アクションメッセージのオブジェクトgoalを次の命令より作成します。

            goal = MoveBaseGoal()

f. 通過点のデータ(位置x,yとラジアン単位で指定の向き)を2次元配列waypointsに設定します。データの終りは999とします。

g. アクションメッセージgoalに設定する角度は、クォータニオンで設定しますので、オイラー角(x軸、y軸、z軸)から変換を行います。

h. 次の命令により、アクションメッセージの送信と指定の位置への到達を待ちます。

            client.send_goal(goal)
            client.wait_for_result()

i. 1辺1mの矩形の4頂点を通過するプログラムrobot_mover_goal05.pyのリストをリスト7-9に示します。

j. 位置の設定を変更して障害物を矩形を描くように1周させることも通過地点のデータを変更することで可能です。プログラムrobot_mover_goal06.py のデータ部分をリスト7-10に示します。
ロボットの向きの設定により、robot_mover_goal02.pyの場合と動きが異なります。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import rospy
from geometry_msgs.msg  import Quaternion

import actionlib
from move_base_msgs.msg import MoveBaseAction, MoveBaseGoal

import tf
from tf.transformations import quaternion_from_euler
from math import pi

def seed_mover():
    rospy.init_node('robot_goal', anonymous=True)
    client = actionlib.SimpleActionClient('move_base', MoveBaseAction)
    client.wait_for_server()

    goal = MoveBaseGoal()
    goal.target_pose.header.frame_id = 'map'
    goal.target_pose.header.stamp = rospy.Time.now()
    waypoints = [
                  [ 0.0,  0.0,  0.0 * pi],
                  [ 1.0,  0.0,  0.0 * pi],
                  [ 1.0,  1.0,  0.5 * pi],
                  [ 0.0,  1.0,  1.0 * pi],
                  [ 0.0,  0.0,  1.5 * pi],
                  [ 999,  999,  999     ]
                ]

#----- Travel points
    point_no = 0
    while not rospy.is_shutdown():
        if waypoints[point_no][0] == 999:
            break

        goal.target_pose.pose.position.x = waypoints[point_no][0]
        goal.target_pose.pose.position.y = waypoints[point_no][1]

        qq = tf.transformations.quaternion_from_euler(0.0, 0.0, waypoints[point_no][2])
        goal.target_pose.pose.orientation = Quaternion(qq[0], qq[1], qq[2], qq[3])

        client.send_goal(goal)
        client.wait_for_result()
        point_no += 1

if __name__ == '__main__':
    try:
        seed_mover()
    except rospy.ROSInterruptException:
        pass


リスト7-9 robot_mover_goal05.py

    waypoints = [
                  [  0.0,  0.0,  0.0 * pi],
                  [  0.0,  0.0,  1.5 * pi],
                  [  0.0, -4.0,  1.0 * pi],
                  [ -4.0, -4.0,  0.5 * pi],
                  [ -4.0,  0.0,  0.0 * pi],
                  [  0.0,  0.0,  0.0 * pi],
                  [  999,  999,  999     ]
                ]


リスト7-10 障害物を1周するときの通過点のデータ (robot_mover_goal06.py)

7-4.LiDARのプログラム

LiDARの距離データから障害物までの距離を判定して、ロボットの向きを変更するプログラムを作成します。

(1) LiDAR
これまで第6章と第7章でで、ロボットの正面方向の概略-90度~+90度の範囲で地図の壁や障害物に表示された赤い点がLiDARのレーザー光の当たった輝点を表しています。
Seed-Moverは、LiDARのデータをトピックメッセージ名/scanで送信しています。メッセージの型は、sensor_msgs/LaserScanです。rqtのTopic Monitorで、トピックメッセージ/scanを確認したときの様子を図7-6に示します。
これより、走査するレーザー光の角度や距離の範囲等は次のようになっています。

            角度            : ロボットの正面を0度として、
                                左回りに-90(angle_min)+90(angle_max)の約180度の範囲
            距離            : 測定距離は、0.02m(range_min)30.00m(range_max)の範囲
            測定データ      : float32[]の実数配列に保存
                                距離データはranges、反射強度はintensities
                                配列の添え字は、0がangle_min、360が正面、719がangle_maxの角度の位置


図7-6 LiDARのデータ

(2) LiDARのデータの表示
操作環境は「7-1. ロボットの移動」(1)を設定します。ロボットを直進させたときのLiDARの設定データと位置のデータをウインドウに表示するプログラムを作成します(図7-7)。

a. ロボットは、(3, 0)の座標の位置に移動しますので、プログラムを実行する前に、「7-3. 指定位置への移動」のプログラムや「2D Nav Goal」のボタンで(0,0)等の位置に移動させておく必要があります。

b. ノード名は、robot_lidarです。

c. ロボットの移動は、目的地を設定するトピックメッセージを送信するオブジェクトpub1を次の命令により作成して行います。

            pub01 = rospy.Publisher('move_base_simple/goal', PoseStamped, queue_size=10)

ここで、

            move_base_simple/goal   : 送信するトピックメッセージ名
            RoseStamped             : 送信するメッセージの型

d. ロボットの目的地への到達の判定は、トピックメッセージを受信するオブジェクトsub1を次の命令により作成して行います。

            sub01 = rospy.Subscriber('move_base/result', MoveBaseActionResult, goal_update)

ここで、

            move_base/result        : 受信するトピックメッセージ名
            MoveBaseActionResult    : 受信するメッセージの型

トピックメッセージを受信すると、goal_update関数が実行されて、目的地に到達するとstatus値が3になります。

e. ロボットの位置の取得は、トピックメッセージ/scanを受信します。トピックメッセージを受信するオブジェクトsub2を次の命令により作成します。

            sub02 = rospy.Subscriber('/scan', LaserScan, callback)

ここで、

            /scan       : 受信するトピックメッセージ名
            LazerScan   : 受信するメッセージの型

トピックメッセージを受信すると、callback関数が実行されて、各種のデータが設定されます。

f. 0.1secごとに位置座標を表示するプログラムrobot_mover_lidar01.pyをリスト7-11に示します。


図7-7 LiDARのデータ

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import math
import rospy
from geometry_msgs.msg  import PoseStamped
from move_base_msgs.msg import MoveBaseActionResult
from sensor_msgs.msg    import LaserScan

goal_msg = 0

ranges    = []
angle_min = 0.0
angle_max = 0.0
angle_inc = 0.0
range_min = 0.0
range_max = 0.0

def goal_update(data):
    global goal_msg
    goal_msg = data.status.status

def callback(msg):
    global ranges
    global angle_min, angle_max, angle_inc
    global range_min, range_max
    
    ranges    = msg.ranges
    angle_min = msg.angle_min
    angle_max = msg.angle_max
    angle_inc = msg.angle_increment
    range_min = msg.range_min
    range_max = msg.range_max

def seed_mover():
    global goal_msg

    rospy.init_node('robot_lidar', anonymous=True)
    pub01 = rospy.Publisher('move_base_simple/goal', PoseStamped, queue_size=10)
    sub01 = rospy.Subscriber('move_base/result', MoveBaseActionResult, goal_update)
    sub02 = rospy.Subscriber('/scan', LaserScan, callback)
    rospy.sleep(1)

    now = rospy.Time.now()
    goal_point = PoseStamped()

    goal_point.header.seq      = 0
    goal_point.header.stamp    = now
    goal_point.header.frame_id = 'map'

    goal_point.pose.position.x    =  3.0
    goal_point.pose.position.y    =  0.0
    goal_point.pose.position.z    =  0.0

    goal_point.pose.orientation.x =  0.0 
    goal_point.pose.orientation.y =  0.0
    goal_point.pose.orientation.z =  0.0
    goal_point.pose.orientation.w =  1.0

    pub01.publish(goal_point)
    goal_msg = 0
    
    rospy.sleep(0.5)
    
    print "Number of Rays=%d" % (len(ranges))
    print "Angle [rad] min=%f max=%f" % (angle_min, angle_max)
    print "Angle [deg] increment=%.3f" % (math.degrees(angle_inc))
    print "Range [m]   min=%.3f max=%.3f" % (range_min, range_max)
    print "  0[deg]=%.3f[m]  90[deg]=%.3f[m]" % (ranges[0], ranges[180])
    print "180[deg]=%.3f[m] -90[deg]=%.3f[m]" % (ranges[360], ranges[540])
    print
    
    rate = rospy.Rate(10)
    while not rospy.is_shutdown():
        print "(%d) Distance (0, 90, 180)  : %.6f,  %.6f, %.6f [m]" % (goal_msg, ranges[0], ranges[360], ranges[719])
        if (goal_msg == 3) :
            break
        rate.sleep()

if __name__ == '__main__':
    try:
        seed_mover()
    except rospy.ROSInterruptException:
        pass


リスト7-11 robot_mover_lidar01.py

(3) 障害物の検出
障害物として地図上の壁との距離が1m以下になったら、ロボットが停止するプログラムを作成します(図7-8)。

a. ロボットは、x軸方向に移動しますので、事前にロボットを壁に垂直の向きに設定しておく必要があります。

b. ノード名は、robot_lidarです。

c. ロボットの移動と回転の速度の設定は、トピックメッセージを送信するオブジェクトpub01を次の命令により作成して行います。

            pub01 = rospy.Publisher('/cmd_vel', Twist, queue_size=10)

ここで、

            /cmd_vel    : 送信するトピックメッセージ名
            Twist       : 送信するメッセージの型

d. ロボットの位置の取得は、トピックメッセージ/scanを受信するオブジェクトsub01を次の命令により作成して行います。

            sub01 = rospy.Subscriber('/scan', LaserScan, callback)

ここで、

            /scan       : 受信するトピックメッセージ名
            LazerScan   : 受信するメッセージの型

トピックメッセージを受信すると、callback関数が実行されて、各種のデータが設定されます。

e. ロボットの正面でのレーザー光の反射による距離ranges[360]が1m以下となったら、プログラムを終了します。但し、レーザー光の反射がないときは、ranges[360]に0などが設定されることがあるため、レーザー光のレンジ内に入っているかを確認して、判定します。

f. 1m以内の距離になったときにロボットが停止するプログラムrobot_mover_lidar02.pyをリスト7-12に示します。


図7-8 ロボットの壁の検知 (robot_mover_lidar02.py)

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import rospy
from geometry_msgs.msg  import Twist
from sensor_msgs.msg    import LaserScan

ranges    = []
angle_min = 0.0
angle_max = 0.0
angle_inc = 0.0
range_min = 0.0
range_max = 0.0

cmd_vel01 = Twist()
cmd_vel01.linear.x  = 0.0
cmd_vel01.linear.y  = 0.0
cmd_vel01.linear.z  = 0.0
cmd_vel01.angular.x = 0.0
cmd_vel01.angular.y = 0.0
cmd_vel01.angular.z = 0.0

def callback(msg):
    global ranges
    global angle_min, angle_max, angle_inc
    global range_min, range_max
    
    ranges    = msg.ranges
    angle_min = msg.angle_min
    angle_max = msg.angle_max
    angle_inc = msg.angle_increment
    range_min = msg.range_min
    range_max = msg.range_max

def seed_mover():
    global cmd_vel01

    rospy.init_node('robot_lidar', anonymous=True)

    pub01 = rospy.Publisher('/cmd_vel', Twist, queue_size=10)
    sub01 = rospy.Subscriber('/scan', LaserScan, callback)

    pub01.publish(cmd_vel01)

    rospy.sleep(1)
    cmd_vel01.linear.x  =  0.2
    cmd_vel01.angular.z =  0.0

    rate = rospy.Rate(10)
    while not rospy.is_shutdown():
        pub01.publish(cmd_vel01)
        print "Distance (0, 90, 180)  : %.6f,  %.6f, %.6f [m]  (min : %.6f, max : %.6f)" % (ranges[0], ranges[360], ranges[719], range_min, range_max)
        if (range_min < ranges[360]) and (ranges[360] < range_max):
            if ranges[360] <= 1.0:
                break
        rate.sleep()

if __name__ == '__main__':
    try:
        seed_mover()
    except rospy.ROSInterruptException:
        pass


リスト7-12 robot_mover_lidar02.py

(4) 障害物で方向転換
障害物として地図上の壁との距離が1m以下となったら、ロボットが方向転換するプログラムを作成します(図7-9)。

a. ロボットは、はじめx軸方向に移動しますので、事前にロボットを壁に垂直の向きに設定しておく必要があります。

b. ノード名は、robot_lidarです。

c. ロボットの移動と回転の速度の設定は、トピックメッセージを送信するオブジェクトpub01を次の命令により作成して行います。

            pub01 = rospy.Publisher('/cmd_vel', Twist, queue_size=10)

d. ロボットの位置の取得は、トピックメッセージ/scanを受信するオブジェクトsub01を次の命令により作成して行います。

            sub01 = rospy.Subscriber('/scan', LaserScan, callback)

トピックメッセージを受信すると、callback関数が実行されて、各種のデータが設定されます。

e. ロボットの回転した角度は、トピックメッセージ/odomを受信するオブジェクトsub02を次の命令により作成して行います。

            sub02 = rospy.Subscriber('/odom', Odometry, callback_odom)

ここで、

            /odom       : 受信するトピックメッセージ名
            Odometry	: 受信するメッセージの型

トピックメッセージを受信すると、callback_odom関数が実行されて、各種のデータが設定されます。

f. ロボットの正面でのレーザー光の反射による距離ranges[360]が1m以下となったら、ロボットを回転させます。但し、レーザー光の反射がないときは、ranges[360]に0などが設定されることがあるため、レーザー光のレンジ内に入っているかを確認して、判定します。
ロボットの回転は、角度の変化の差分により判定しますが、回転に対応の差分は、調整をする必要があります。

g. 壁を2回検出して、方向転換して停止するプログラムrobot_mover_lidar03.pyをリスト7-13示します。


図7-9 ロボットの壁の検知と方向転換 (robot_mover_lidar03.py)

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import rospy
from geometry_msgs.msg  import Twist
from sensor_msgs.msg    import LaserScan
from nav_msgs.msg       import Odometry
import tf
from tf.transformations import euler_from_quaternion

ranges    = []
angle_min = 0.0
angle_max = 0.0
angle_inc = 0.0
range_min = 0.0
range_max = 0.0

cmd_vel01 = Twist()
cmd_vel01.linear.x  = 0.0
cmd_vel01.linear.y  = 0.0
cmd_vel01.linear.z  = 0.0
cmd_vel01.angular.x = 0.0
cmd_vel01.angular.y = 0.0
cmd_vel01.angular.z = 0.0

odom_x     = 0.0
odom_y     = 0.0
odom_theta = 0.0

def callback_odom(msg):
    global odom_x, odom_y, odom_theta

    odom_x = msg.pose.pose.position.x
    odom_y = msg.pose.pose.position.y

    qx = msg.pose.pose.orientation.x
    qy = msg.pose.pose.orientation.y
    qz = msg.pose.pose.orientation.z
    qw = msg.pose.pose.orientation.w
    qq = (qx, qy, qz, qw)
    ee = euler_from_quaternion(qq)
    odom_theta = ee[2]

def callback(msg):
    global ranges
    global angle_min, angle_max, angle_inc
    global range_min, range_max
    
    ranges    = msg.ranges
    angle_min = msg.angle_min
    angle_max = msg.angle_max
    angle_inc = msg.angle_increment
    range_min = msg.range_min
    range_max = msg.range_max

def seed_mover():
    global cmd_vel01

    rospy.init_node('robot_lidar', anonymous=True)

    pub01 = rospy.Publisher('/cmd_vel', Twist, queue_size=10)
    sub01 = rospy.Subscriber('/scan', LaserScan, callback)
    sub02 = rospy.Subscriber('/odom', Odometry, callback_odom) 

    pub01.publish(cmd_vel01)

#----- 1st
    rospy.sleep(1)
    cmd_vel01.linear.x  =  0.2
    cmd_vel01.angular.z =  0.0

    rate = rospy.Rate(100)
    while not rospy.is_shutdown():
        pub01.publish(cmd_vel01)
        if (range_min < ranges[360]) and (ranges[360] < range_max):
            if ranges[360] <= 1.0:
                break
        rate.sleep()

    cmd_vel01.linear.x  =  0.0
    cmd_vel01.angular.z = -0.2
    odom_theta0 = odom_theta
    while not rospy.is_shutdown():
        pub01.publish(cmd_vel01)
        if abs(odom_theta - odom_theta0) > 2.2:
            break
        rate.sleep()

#----- 2nd
    cmd_vel01.linear.x  =  0.2
    cmd_vel01.angular.z =  0.0

    rate = rospy.Rate(100)
    while not rospy.is_shutdown():
        pub01.publish(cmd_vel01)
        if (range_min < ranges[360]) and (ranges[360] < range_max):
            if ranges[360] <= 1.0:
                break
        rate.sleep()

    cmd_vel01.linear.x  =  0.0
    cmd_vel01.angular.z = -0.2
    odom_theta0 = odom_theta
    while not rospy.is_shutdown():
        pub01.publish(cmd_vel01)
        if abs(odom_theta - odom_theta0) > 1.8:
            break
        rate.sleep()
 
if __name__ == '__main__':
    try:
        seed_mover()
    except rospy.ROSInterruptException:
        pass


リスト7-13 robot_mover_lidar03.py

8.地図の作成

この章では、Part-1 第5章で行ったロボットの移動範囲を設定する地図の作成をシミュレーションで行います。これまで、第6章、第7章でロボットの移動を行ったときは、予め提供されていた図7-2に示した座標系の地図を利用して、可視化ツールのRvizで動作の確認を行っていました。
ここでは、3DモデルのバーチャルシミュレータのGazeboを利用して、障害物を含む壁に囲まれた区画の3Dモデルを作成し、Rvizを利用してロボットの移動領域の地図を作成します。そして、これまでのRvizに加え、Gazeboによる3Dでのロボットの移動の確認を行います。

8-1.Worldファイルの作成

Gazeboを用いて以下の手順によりシミュレーション用の3D画像を作成し、データをworldファイルとして保存します。

(1) Gazeboの起動
「ターミナル」を起動し、フォルダを/home/seed/rosに変えて、以下のコマンドを入力します(図8-1)。

            cd /home/seed/ros
            gazebo

起動時、「ターミナル」に次のエラーが表示されますが、無視して問題ありません。

            エラー [Err] [REST.cc:205] Error in REST request

また、現れた画面の向きや大きさは次のマウスの操作で調整できます。

            移動        :  マウスのドラッグ
            回転        :  SHIFT+ドラッグ
            拡大縮小 	:  マウスのホイール



図8-1 Gazeboの画面

(2) Building Editorの起動
3D区画の外形を作成します。Gazeboの上段のメニューから、次の項目を選択します(図8-2)。

            Edit > Building Editor

a. 画面の設定
画面は、上側が2D画面、下側が3D画面になります。2D画面の左下の長さのスケールを参照して、マウスのホイールで拡大縮小等を行います。3.00mm程度にスケールを調整すると以降の作業が行いやすいですが、適宜、見やすいように調整できます。


図8-2 Building Editorの画面

b. 線(3D画面では壁)の描画
実行時にロボットは、デフォルトで3D画面の原点の位置にx軸方向(赤線)が正面になるように現れます。画面の左側のタブから、「Create Walls」の「Wall」をマウスで押して、2D画面に線(壁)を描きます(図8-3)。
マウスの左クリックで始点を設定し、マウスを移動して長さを調整し、終点でダブルクリックまたはクリックして「esc」キーを押します。マウスのクリックで角点を指定して多角形を描くこともできます。ここでは、線を6本描き、概略の配置を構成します(図8-4)。


図8-3 線(壁)の描画


図8-4 概略の線の配置

c. 線の位置および長さの調整
線は、マウスでクリックして選択すると移動や長さの調整ができます。線を選択すると色が変わり、線の中央付近では、マウスが手の形に、線の端点では十字の形になります。前者で線の移動、後者で線の長さを調整できます。

d. 座標による線の設定
線を選択した状態で、マウスの右クリックメニューから「Open Wall Inspector」を選択して、現れた画面から線の端点の座標等を指定して設定することもできます(図8-5)。


図8-5 右クリックメニュー

e. 線の削除
線を選択した状態で、マウスの右クリックメニューから「Delete」を選択します。

f. 多角形の領域の作成
ここでは、描いた6本の線に対し、それぞれ「Open Wall Inspector」を選択して、始点(Start Point)と終点(End Point)に次の座標(x, y)を設定して「Apply」を押します(図8-6、図8-7)。

            ( 5,-5) - ( 5, 5)
            ( 5, 5) - (-2, 5)
            (-2, 5) - (-2,-2)
            (-2,-2) - (-5,-2)
            (-5,-2) - (-5,-5)
            (-5,-5) - ( 5,-5)

線の移動を確認して「OK」を押して、「Open Wall Inspector」を閉じます。


図8-6 Open Wall Inspectorによる座標の設定


(a) 1つの線(壁)の設定


(b) 全体の線(壁)の設定
図8-7 線(壁)による領域の作成

g. 壁の模様の設定
画面の左側のタブから、「Add Texture」の「Wood」をマウスで押して、3D画面の壁にマウスを合わせて押すことで、壁に模様を設定できます(図8-8)。


(a) 1つの壁への設定


(b) 全体に設定
図8-8 壁の模様の設定

h. 作成した画像の保存
(ア) 上段のメニューから、次の項目を選択します(図8-9)。

            File > Save As

(イ) 現れた画面で、Locationの右側の「Browse」を押します。

(ウ) 現れた画面で、次のフォルダを選択して「Choose」を押します。

            /home/seed/ros/melodic/src/seed_r7_ros_pkg/seed_r7_gazebo/models

(エ) 戻った画面で、Model Nameをdsl_model01とします。
これは作成した画像を区別するフォルダ名になります。ファイルの実体は、dsl_model01フォルダ内に、model.config、model.sdfとして保存されます。


(a) 保存フォルダの設定


(b) ファイル名の設定


(c) 保存されたファイル
図8-9 Build Editorのファイル保存

i. Building Editorの終了
上段のメニューから、次の項目を選択します(図8-10、図8-11)。

            File > Exit Building Editor

確認画面「Are you ready to exit ?」が現れますので、「Exit」を押します。但し、一度閉じると作成した画像の再編集ができませんので注意が必要です。


図8-10 Building Editorの終了時の警告


図8-11 Building Editorにより作成された3D画面

(3) Model Editorの起動
Building Editorで作成した画面に、障害物を追加します。Gazeboの上段のメニューから、次の項目を選択します(図8-12)。

            Edit > Model Editor


図8-12 Model Editorの画面

a. 障害物の追加
左側のInsertタブから、Simple ShapesのBoxを押します。3D画面でBoxを配置する位置でマウスを押します(図8-13(a))。

b. Boxの移動、回転、大きさの調整
上側のアイコンを選択して、配置したBoxの移動、回転、大きさの調整を行うことができます(図8-13(b))。

c. Construction Cornの追加
同様に、Construction Cornを押して、3D画面に配置します(図8-14)。


(a) Boxの配置


(b) Boxのサイズ変更
図8-13 Boxの設定


図8-14 Construction Coneの配置

d. ファイルの保存
(ア) 上段のメニューから、次の項目を選択します。

            File > Save As

(イ) Building Editorで「Save As」により保存したときと同じ画面が現れます(図8-15)。必要に応じて、Building Editorで保存したファイルを上書きするか、新たなファイル名とします。

(ウ) ここでは、上書きとしますので、次のメッセージが表示されます。

            Do you wish to overwrite the existing model files?

「Save」を押して、上書き保存します。


図8-15 Model Editorのファイル保存

e. Model Editorの終了
上段のメニューから、次の項目を選択します(図8-16)。

            File > Exit Model Editor

確認画面「Are you ready to exit ?」が現れますので、「Exit」を押します。


図8-16 Model Editorにより作成された3D画面

(4) Worldファイルの保存
a.Gazeboの上段のメニューから、次の項目を選択します(図8-17)。

            File > Save World As

b. フォルダの変更
現れた画面で、次のフォルダを選択します。

            /home/seed/ros/melodic/src/seed_r7_ros_pkg/seed_r7_gazebo/worlds

上側の名前にファイル名dsl_model01.worldを設定して、右上の「Save」を押します。必要に応じて、上書きするか、新たなファイル名とします。

c. Gazeboの終了
上段のメニューから、次の項目を選択します。

            File > Quit



(a) フォルダとファイル名の設定


(b) ファイルの保存の確認
図8-17 Worldファイル(Gazeboファイル)の保存

8-2.地図の作成

GazeboとRvizを起動して地図の作成を行い、地図データを保存します。

(1) 保存ファイルの設定
地図データを保存するため、次の手順でファイルの保存先のフォルダの設定を行います。

a. Ubuntuのデスクトップの左側のお気に入りから「ファイル」をクリックして起動し、フォルダを次の場所に変更します。

            /home/seed/ros/melodic/src/seed_r7_ros_pkg/seed_r7_navigation/launch

b. ファイルmap_saver.launchを以下の手順でコピーして、ファイル名を変更します(図8-18)。
(ア) ファイルmap_saver.launchをマウスでクリックして、右メニューから「コピー」を選択する。
(イ) フォルダの空いているところで、マウスをクリックして、右メニューから「貼り付け」を選択する。
(ウ) コピーしたファイルをマウスでクリックして、右メニューから「名前の変更」を選択する。
(エ) 現れた画面で、ファイル名をmap_saver_dsl_model01.launchにして、「変更」を押す。


(a) map_saver.launchファイルのコピー


(b) ファイルの貼り付け


(c) ファイルの名前の変更


(d) ファイル名の設定


(e) コビーされたファイル
図8-18 ファイルのコピーとファイル名の変更

c. ファイル名を変更したファイルmap_saver_dsl_model01.launchを選択して、右メニューから「テキストエディターで開く」を選択します(図8-19)。3行目の後ろの方を次のように変更して、上書き保存します(図8-20)。

            変更前  :  /maps/map
            変更後  :  /maps/dsl_model01


図8-19 テキストエディタの起動


(a) 変更前


(b) 変更後
図8-20 設定内容の変更

(2) 地図の作成
ロボットを移動させて、移動範囲の地図を作成します。但し、ここで現れるロボットは、Gazeboの設定からアームロボットではなく、双腕ロボットになります。

a. 「ターミナル」を起動し、フォルダを変更し、「byobu」コマンドを入力します。

            cd /home/seed/ros
            byobu

b. Byobuの最初のウインドウ0で、次のコマンドを入力します。

            roscore

c. F2を押して、Byobuにウインドウ1を追加し、次のコマンドを入力します。

            roslaunch seed_r7_gazebo seed_r7_example_world.launch WORLD_FILE:=dsl_model01.world

Gazeboが起動され、作成した3Dの画面が表示されます(図8-21)。
また、WORLD_FILEの設定を行わないときは、デフォルトのexample.worldが表示されます。


図8-21 Gazeboの画面

d. F2を押して、Byobuにウインドウ2を追加し、次のコマンドを入力します。

            roslaunch seed_r7_navigation wheel_with_making_map.launch

LiDARのデータがウインドウに出力されます。

e. F2を押して、Byobuにウインドウ3を追加し、次のコマンドを入力します。

            rosrun rviz rviz -d `rospack find seed_r7_bringup`/rviz.rviz

Rvizが起動し、双腕のロボットが現れます(図8-22)。


図8-22 Rvizの画面

f. F2を押して、Byobuにウインドウ4を追加し、次のコマンドを入力します。

            rosrun rqt_robot_steering rqt_robot_steering

トピックメッセージを/cmd_velに設定します。

g. rqt_robot_steeringを操作してロボットを移動させ、Rvizの画面でロボットの移動領域をグレーで塗りつぶします(図8-23)。このとき、Gazeboのロボットも同期して動きます。


(a) 塗りつぶし 1


(b) 塗りつぶし 2


(c) 塗りつぶし 3


(d) 塗りつぶし 4
図8-23 移動領域の塗りつぶし

h. F2を押して、Byobuにウインドウ5を追加し、次のコマンドを入力します(図8-24)。

            roslaunch seed_r7_navigation map_saver_dsl_model01.launch

これにより、次のフォルダにdsl_model01.pgm、dsl_model01.yamlのファイルが作成されます。前者は画像ファイル、後者は設定ファイルです(図8-25、図8-26)。

            /home/seed/ros/melodic/src/seed_r7_ros_pkg/seed_r7_gazebo/maps



図8-24 地図ファイルの保存コマンド


図8-25 地図ファイルの保存確認


図8-26 保存された地図画像(dsl_model01.pgm)

8-3.ロボットの移動(1)

GazeboとRvizを起動してロボットの移動を行います。

(1) 地図データの反映
作成した地図データを読み込むように、以下の手順で設定を行います。

a. Ubuntuのデスクトップの左側のお気に入りから「ファイル」をクリックして起動します。
フォルダを次の場所に変更します。

            /home/seed/ros/melodic/src/seed_r7_ros_pkg/seed_r7_navigation/launch

b. ファイルwheel_with_static_map.launchを以下の手順でコピーして、ファイル名を変更します。
(ア) ファイルwheel_with_static_map.launchをマウスでクリックして、右メニューから「コピー」を選択する(図8-27)。
(イ) フォルダの空いているところで、マウスをクリックして、右メニューから「貼り付け」を選択する。
(ウ) コピーしたファイルをマウスでクリックして、右メニューから「名前の変更」を選択する。
(エ) 現れた画面で、ファイル名をwheel_with_static_map_dsl_model01.launchにして、「変更」を押す。

c. ファイル名を変更したファイルwheel_with_static_map_dsl_model01.launchをクリックして、右メニューから「テキストエディターで開く」を選択します。
10行目と11行目の後ろの方を次のように変更して、上書き保存します(図8-28)。

            変更前  :  /maps/map.yaml
            変更後  :  /maps/dsl_model01.yaml



図8-27 コピーするファイル(wheel_with_static_map.launch)


(a) 変更前


(b) 変更後
図8-28 設定内容の変更 (wheel_with_static_map_dsl_model01.launch)

(2) ロボットの移動
作成した地図でロボットを移動します。

a. 「ターミナル」を起動し、フォルダを変更し、コマンド「byobu」を入力します。

            cd /home/seed/ros
            byobu

b. Byobuの最初のウインドウ0で、次のコマンドを入力します。

            roscore

c. F2を押して、Byobuにウインドウ1を追加し、次のコマンドを入力します。

            roslaunch seed_r7_gazebo seed_r7_example_world.launch WORLD_FILE:=dsl_model01.world

d. F2を押して、Byobuにウインドウ2を追加し、次のコマンドを入力します。

            roslaunch seed_r7_navigation wheel_with_static_map_dsl_model01.launch

e. F2を押して、Byobuにウインドウ3を追加し、次のコマンドを入力します。

            rosrun rviz rviz -d `rospack find seed_r7_bringup`/rviz.rviz

双腕のロボットが現れます。

f. F2を押して、Byobuにウインドウ4を追加し、次のコマンドを入力します。

            rosrun rqt_robot_steering rqt_robot_steering

トピックメッセージを/cmd_velに設定し、ロボットを移動させると両方のロボットが同期して動きます。また、「2D Nav Goal」でもロボットの同期した移動が可能です(図8-29)。


(a) GazeboとRvizの画面


(b) ロボットの移動
図8-29 GazeboとRvizで同期したロボットの移動

8-4.ロボットの移動(2)

第6章や第7章で行ったRvizでのアームロボットの移動を、作成した地図で行います。

(1) 地図データの反映
作成した地図データを読み込むように、以下の手順で設定を行います。
これまでと同様に設定のファイルをコピーして、ファイル名を変更します。

a. Ubuntuのデスクトップの左側のお気に入りから「ファイル」をクリックして起動します。
フォルダを次の場所に変更します。

            /home/seed/ros/melodic/src/seed_r7_ros_pkg/seed_r7_navigation/launch

b. ファイルwheel_with_dummy.launchを以下の手順でコピーして、ファイル名を変更します。
(ア) ファイルwheel_with_dummy.launchをマウスでクリックして、右メニューから「コピー」を選択する。
(イ) フォルダの空いているところで、マウスでクリックして、右メニューから「貼り付け」を選択する。
(ウ) コピーしたファイルをマウスでクリックして、右メニューから「名前の変更」を選択する。
(エ) 現れた画面で、ファイル名をwheel_with_dummy_dsl_model01.launchにして、「変更」を押す。

c. ファイル名を変更したファイルwheel_with_dummy_dsl_model01.launchをクリックして、右メニューから「テキストエディターで開く」を選択します。10行目と11行目の後ろの方を次のように変更して、上書き保存します(図8-30)。

            変更前  :  /maps/dummy.yaml
            変更後  :  /maps/dsl_model01.yaml



(a) 変更前


(b) 変更後
図8-30 設定内容の変更 (wheel_with_dummy_dsl_model01.launch)

(2) ロボットの移動
作成した地図でロボットを移動します。設定は6-1節と同じです。

a. 「ターミナル」を起動し、フォルダを変更し、コマンド「byobu」を入力します。

            cd /home/seed/ros
            byobu

b. Byobuの最初のウインドウ0で、次のコマンドを入力します。

            roscore

c. F2を押して、Byobuにウインドウ1を追加し、次のコマンドを入力します。

            roslaunch seed_r7_bringup seed_r7_bringup.launch robot_model:=typeg_arm

d. F2を押して、Byobuにウインドウ2を追加し、次のコマンドを入力します。

            rosrun rviz rviz -d `rospack find seed_r7_bringup`/rviz.rviz

白い台車が現れます。

e. F2を押して、Byobuにウインドウ3を追加し、次のコマンドを入力します。

            roslaunch seed_r7_navigation wheel_with_dummy_dsl_model01.launch

アームロボットが現れます。また、Displayペインで次の設定を行います。

            Global OptionsのFixed Frame   : map
            TFのShow Names                : チェックを外す
            TFのShow Axes                 : チェックを外す

f. F2を押して、Byobuにウインドウ4を追加し、次のコマンドを入力します。

            rosrun rqt_robot_steering rqt_robot_steering

トピックメッセージを/cmd_velに設定します。作成した地図に対して、これまでと同様にRvizに現れたアームロボットを移動させることができます(図8-31)。


図8-31 作成された地図のRvizとrqt_robot_steering

9.まとめ

本手順書では、群馬産業技術センターに導入されたTHK(株)社製の移動台車を備えた単腕のロボットの操作に対応したシミュレーションについて示しました。

ロボットを制御するためには、ROSという優れたミドルウェアのソフトを無償で利用することができます。ここでは、ROSに興味のある方がその仕組みを理解頂けるよう前半では、Turtlesimを用いてCUI環境でのコマンドによる制御について示しました。それから、GUI環境での制御を示し、CUIとGUIの繋がりを示しました。ROSの基本的な部分は、大まかにこの前半部分で分かると思います。実際にロボットを制御するプログラムを作成して行くと、適宜、CUIとGUIを使い分けることになると思います。CUIは行うべきことと必要なコマンドがはっきりしているときは、情報を適切に得ることができますが、ROSに慣れていない方には、限定的な利用に留まってしまうと思うからです。後半では、具体的にTHK(株)社製のロボットの制御について示しました。シミュレーションでもLiDARによる距離データを利用してロボットの動きを制御することができ、実機を移動させる準備として、物との衝突等もなく安全に動作の確認ができることを示しました。

ROSは、現在、ROS1の問題点等を改善し、製品化等も視野に入れた新しいROS2が開発されています。そして、LiDARやSLAMなどの技術に加え、今後もAIなどの様々な技術を取込んで、より安全に機能の強化された多様なロボット等の制御に利用されると思われます。初心者向けの手順書として細かな点まで含めたため、長くなってしまいましたが、この手順書がROSによるロボットの操作に関心のある方の参考になればと思います。

付録 参考資料

本手順書の作成ではインターネットにある多くのサイトに掲載の情報を利用しました。以下に参考にした主なWebサイトを示します。

第1章
[1-1] AGV(無人搬送車)とは?メリット・デメリット、AI活用の可能性を徹底解説
  https://ai-market.jp/purpose/agv/
[1-2] AGV(無人搬送車)とは?導入前に知っておきたい基礎知識
  https://www.zmp.co.jp/knowledge/column/20211220
[1-3] 第1回 ROSとは?基本的概念とROS2が必要になった背景
  https://www.eureka-box.com/media/column/a33

第2章
[2-1] NEDO特別講座用ISOファイル
  https://www.seed-solutions.net/?q=seed_jp/node/7

第3章
[3-1] ROS入門 (5) - ROS2のデータ通信の仕組み
  https://note.com/npaka/n/ne222761018ab
[3-2] ROS チュートリアル6:ROSトピックについて
  https://symfoware.blog.fc2.com/blog-entry-2272.html
[3-3] ROS チュートリアル7:ROSサービスとパラメータについての理解
  https://symfoware.blog.fc2.com/blog-entry-2273.html
[3-4] ROS2 チュートリアル turtlesimとrqt
  https://symfoware.blog.fc2.com/blog-entry-2671.html
[3-5] jaRemapping Arguments
  http://wiki.ros.org/ja/Remapping Arguments
[3-6] ROS2演習2-2021:亀で遊ぼう
  https://demura.net/robot/ros2/20678.html
[3-7] Windows で ROS のシミュレーションを行う (3) Turtlesim で ROS を体験する
  https://brain.cc.kogakuin.ac.jp/~kanamaru/lecture/ROS/index3.html
[3-8] ROS講座29 rosbagを使う
  https://qiita.com/srs/items/f6e2c36996e34bcc4d73

第4章
[4-1] ROS2演習2-2021:亀で遊ぼう
  https://demura.net/robot/ros2/20678.html
[4-2] ROSのトピック通信
  http://hara-jp.com/_default/ja/Topics/ROSのトピック通信.html

第5章
[5-1] Windows で ROS のシミュレーションを行う (3) Turtlesim で ROS を体験する
  https://brain.cc.kogakuin.ac.jp/~kanamaru/lecture/ROS/index3.html

第6章
[6-1] seed_r7_ros_pkg
  https://github.com/seed-solutions/seed_r7_ros_pkg

第7章
[7-1] クォータニオン (Quaternion) を総整理
  https://qiita.com/drken/items/0639cf34cce14e8d58a5
[7-2] ROS Pythonでオイラー角とクォータニオンの相互変換
  https://yura2.hateblo.jp/entry/2016/05/18/ROS_Pythonでオイラー角とクォータニオンの相互変換
[7-3] serviceとactionlib | ロボットシステム入門
  https://matsuolab.github.io/roomba_hack/course/chap6/service-actionlib/
[7-4] Tokyo Opensource Robotics Kyokai Association, “MoveIt Tutorial Documentation”
  https://robo-marc.github.io/tutorials/marc2023/tutorial-melodic-mycobot-0.1.1.pdf
[7-5] HARD2021:ルンバの位置をPythonプログラムで知ろう
  https://demura.net/robot/hard/20085.html
[7-6] HARD2021:ルンバをPythonプログラムでナビゲーションさせよう!
  https://demura.net/robot/hard/20114.html
[7-7] HARD2021:PythonプログラムでLiDARを使おう
  https://demura.net/robot/hard/20141.html

第8章
[8-1] gazeboシミュレータの使い方
  http://joe.ash.jp/program/ros/tutorial/tutorial_gazebo.htm
[8-2] Building Editor
  http://classic.gazebosim.org/tutorials?cat=build_world&tut=building_editor
[8-3] No.3-1:環境をつくろう ! (新規の環境を作る 編)
  http://cir-kit.github.io/blog/2015/02/03/gazebo-building-a-new-world/

以上


https://zenn.dev/dsl_gunma/articles/0e8b9b0049a4b6
https://zenn.dev/dsl_gunma/articles/1cd52b90ed52fe

Discussion