How to use the Atlas Sim Interface - modulabs/gazebo-tutorial GitHub Wiki

Introduction

์ด๋ฒˆ ์žฅ์—์„œ๋Š” ์–ด๋–ป๊ฒŒ Atlas๊ฐ€ ๋™์  ํ˜น์€ ์ •์ ์œผ๋กœ ๊ฑท๋„๋ก ํ•˜๊ธฐ ์œ„ํ•ด Atlas Sim ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์‚ฌ์šฉ ํ•˜๋Š”์ง€์— ๋Œ€ํ•ด์„œ ์„ค๋ช…ํ•œ๋‹ค.

Create a ROS Package Workspace

cd ~/catkin_ws/src
catkin_create_pkg atlas_sim_interface_tutorial rospy atlas_msgs

Create a ROS Node

walk.py์„ ๋‹ค์šด๋กœ๋“œ ํ•˜์—ฌ **~/ros/atlas_sim_interface_tutorial/src/walk.py**์— ์ €์žฅ ํ•œ๋‹ค. ์•„๋ž˜ ์ฝ”๋“œ ๋‚ด์šฉ์„ ํฌํ•จํ•œ๋‹ค.

#! /usr/bin/env python
import roslib; roslib.load_manifest('atlas_sim_interface_tutorial')

from atlas_msgs.msg import AtlasSimInterfaceCommand, AtlasSimInterfaceState, AtlasState
from geometry_msgs.msg import Pose, Point
from std_msgs.msg import String
from tf.transformations import quaternion_from_euler, euler_from_quaternion

import math
import rospy
import sys

class AtlasWalk():

    def walk(self):
        # Initialize atlas mode and atlas_sim_interface_command publishers        
        self.mode = rospy.Publisher('/atlas/mode', String, None, False, \
          True, None, queue_size=1)
        self.asi_command = rospy.Publisher('/atlas/atlas_sim_interface_command', AtlasSimInterfaceCommand, None, False, True, None, queue_size=1)

        # Assume that we are already in BDI Stand mode

        # Initialize some variables before starting.
        self.step_index = 0
        self.is_swaying = False

        # Subscribe to atlas_state and atlas_sim_interface_state topics.
        self.asi_state = rospy.Subscriber('/atlas/atlas_sim_interface_state', AtlasSimInterfaceState, self.asi_state_cb)
        self.atlas_state = rospy.Subscriber('/atlas/atlas_state', AtlasState, self.atlas_state_cb)

        # Walk in circles until shutdown.
        while not rospy.is_shutdown():
            rospy.spin()
        print("Shutting down")

    # /atlas/atlas_sim_interface_state callback. Before publishing a walk command, we need
    # the current robot position
    def asi_state_cb(self, state):
        try:
            x = self.robot_position.x
        except AttributeError:
            self.robot_position = Point()
            self.robot_position.x = state.pos_est.position.x
            self.robot_position.y = state.pos_est.position.y
            self.robot_position.z = state.pos_est.position.z

        if self.is_static:
            self.static(state)
        else:
            self.dynamic(state)

    # /atlas/atlas_state callback. This message provides the orientation of the robot from the torso IMU
    # This will be important if you need to transform your step commands from the robot's local frame to world frame
    def atlas_state_cb(self, state):
        # If you don't reset to harnessed, then you need to get the current orientation
        roll, pitch, yaw = euler_from_quaternion([state.orientation.x, state.orientation.y, state.orientation.z, state.orientation.w])

    # An example of commanding a dynamic walk behavior.     
    def dynamic(self, state):                
        command = AtlasSimInterfaceCommand()
        command.behavior = AtlasSimInterfaceCommand.WALK

        # k_effort is all 0s for full BDI controll of all joints.
        command.k_effort = [0] * 28

        # Observe next_step_index_needed to determine when to switch steps.
        self.step_index = state.walk_feedback.next_step_index_needed

        # A walk behavior command needs to know three additional steps beyond the current step needed to plan
        # for the best balance
        for i in range(4):
            step_index = self.step_index + i
            is_right_foot = step_index % 2

            command.walk_params.step_queue[i].step_index = step_index
            command.walk_params.step_queue[i].foot_index = is_right_foot

            # A duration of 0.63s is a good default value
            command.walk_params.step_queue[i].duration = 0.63

            # As far as I can tell, swing_height has yet to be implemented
            command.walk_params.step_queue[i].swing_height = 0.2

            # Determine pose of the next step based on the step_index
            command.walk_params.step_queue[i].pose = self.calculate_pose(step_index)

        # Publish this command every time we have a new state message
        self.asi_command.publish(command)
        # End of dynamic walk behavior

    # An example of commanding a static walk/step behavior.
    def static(self, state):

        # When the robot status_flags are 1 (SWAYING), you can publish the next step command.
        if (state.step_feedback.status_flags == 1 and not self.is_swaying):
            self.step_index += 1
            self.is_swaying = True
            print("Step " + str(self.step_index))
        elif (state.step_feedback.status_flags == 2):
            self.is_swaying = False

        is_right_foot = self.step_index % 2

        command = AtlasSimInterfaceCommand()
        command.behavior = AtlasSimInterfaceCommand.STEP

        # k_effort is all 0s for full bdi control of all joints
        command.k_effort = [0] * 28

        # step_index should always be one for a step command
        command.step_params.desired_step.step_index = 1
        command.step_params.desired_step.foot_index = is_right_foot

        # duration has far as I can tell is not observed
        command.step_params.desired_step.duration = 0.63

        # swing_height is not observed
        command.step_params.desired_step.swing_height = 0.1

        if self.step_index > 30:
            print(str(self.calculate_pose(self.step_index)))
        # Determine pose of the next step based on the number of steps we have taken
        command.step_params.desired_step.pose = self.calculate_pose(self.step_index)

        # Publish a new step command every time a state message is received
        self.asi_command.publish(command)
        # End of static walk behavior

    # This method is used to calculate a pose of step based on the step_index
    # The step poses just cause the robot to walk in a circle
    def calculate_pose(self, step_index):
        # Right foot occurs on even steps, left on odd
        is_right_foot = step_index % 2
        is_left_foot = 1 - is_right_foot

        # There will be 60 steps to a circle, and so our position along the circle is current_step
        current_step = step_index % 60

        # yaw angle of robot around circle
        theta = current_step * math.pi / 30

        R = 2 # Radius of turn
        W = 0.3 # Width of stride

        # Negative for inside foot, positive for outside foot
        offset_dir = 1 - 2 * is_left_foot

        # Radius from center of circle to foot
        R_foot = R + offset_dir * W/2

        # X, Y position of foot
        X = R_foot * math.sin(theta)
        Y = (R - R_foot*math.cos(theta))

        # Calculate orientation quaternion
        Q = quaternion_from_euler(0, 0, theta)
        pose = Pose()
        pose.position.x = self.robot_position.x + X
        pose.position.y = self.robot_position.y + Y

        # The z position is observed for static walking, but the foot
        # will be placed onto the ground if the ground is lower than z
        pose.position.z = 0

        pose.orientation.x = Q[0]
        pose.orientation.y = Q[1]
        pose.orientation.z = Q[2]
        pose.orientation.w = Q[3]

        return pose

if __name__ == '__main__':
    rospy.init_node('walking_tutorial')
    walk = AtlasWalk()
    if len(sys.argv) > 0:
        walk.is_static = (sys.argv[-1] == "static")
    else:
        walk.is_static = False
    walk.walk()

The code explained

์ด ๋…ธ๋“œ๋Š” ๋‹ค์Œ์„ ์ž„ํดํŠธ ํ•˜์—ฌ์•ผ ํ•œ๋‹ค.

#! /usr/bin/env python
import roslib; roslib.load_manifest('atlas_sim_interface_tutorial')

from atlas_msgs.msg import AtlasSimInterfaceCommand, AtlasSimInterfaceState, AtlasState
from geometry_msgs.msg import Pose, Point
from std_msgs.msg import String
from tf.transformations import quaternion_from_euler, euler_from_quaternion

import math
import rospy
import sys

์ด ๋…ธ๋“œ์— ๋Œ€ํ•œ ํผ๋ธ”๋ฆฌ์…”์™€ ์„œ๋ธŒ์Šคํฌ๋ผ์ด๋ฒ„๋ฅผ ์ดˆ๊ธฐํ™” ํ•œ๋‹ค. /atlas/atlas_sim_interface_command, /atlas/mode๋ฅผ ํผ๋ธ”๋ฆฌ์‹œํ•œ๋‹ค. /atlas/atlas_sim_interface_state, /atlas/state๋ฅผ ๋ฐ›๋Š”๋‹ค.

#! /usr/bin/env python
import roslib; roslib.load_manifest('atlas_sim_interface_tutorial')

from atlas_msgs.msg import AtlasSimInterfaceCommand, AtlasSimInterfaceState, AtlasState
from geometry_msgs.msg import Pose, Point
from std_msgs.msg import String
from tf.transformations import quaternion_from_euler, euler_from_quaternion

import math
import rospy
import sys

class AtlasWalk():

    def walk(self):
        # Initialize atlas mode and atlas_sim_interface_command publishers        
        self.mode = rospy.Publisher('/atlas/mode', String, None, False, \
          True, None, queue_size=1)
        self.asi_command = rospy.Publisher('/atlas/atlas_sim_interface_command', AtlasSimInterfaceCommand, None, False, True, None, queue_size=1)

        # Assume that we are already in BDI Stand mode

        # Initialize some variables before starting.
        self.step_index = 0
        self.is_swaying = False

        # Subscribe to atlas_state and atlas_sim_interface_state topics.
        self.asi_state = rospy.Subscriber('/atlas/atlas_sim_interface_state', AtlasSimInterfaceState, self.asi_state_cb)
        self.atlas_state = rospy.Subscriber('/atlas/atlas_state', AtlasState, self.atlas_state_cb)

        # Walk in circles until shutdown.
        while not rospy.is_shutdown():
            rospy.spin()
        print("Shutting down")

atlas_sim_interface_state๋ฅผ ์ฝœ๋ฐฑํ•˜๊ณ  ์ด๊ฒƒ์€ ์œ ์šฉํ•œ ์ •๋ณด๋ฅผ ์ œ๊ณตํ•œ๋‹ค. ๋กœ๋ด‡์˜ ํ˜„์ œ ์œ„์น˜(BID ์ œ์–ด๊ธฐ์— ์˜ํ•ด ์ถ”์ •๋œ)๋ฅผ ์–ป์„ ์ˆ˜ ์žˆ๋‹ค. ์ด ์œ„์น˜๋Š” This is the atlas_sim_interface_state callback. It provides a lot of useful information. We can get the robot's current position (as estimated by the BDI controller). ์ด ์œ„์น˜๋Š” ๋กœ์ปฌ ์Šคํ… ์ขŒํ‘œ๋ฅผ ๊ธ€๋กœ๋ฒŒ ์Šคํ… ์ขŒํ‘œ๋กœ ๋ณ€ํ™˜ํ•˜๋Š”๋ฐ ํ•„์š”ํ•˜๋‹ค.

    # /atlas/atlas_sim_interface_state callback. Before publishing a walk command, we need
    # the current robot position
    def asi_state_cb(self, state):
        try:
            x = self.robot_position.x
        except AttributeError:
            self.robot_position = Point()
            self.robot_position.x = state.pos_est.position.x
            self.robot_position.y = state.pos_est.position.y
            self.robot_position.z = state.pos_est.position.z

        if self.is_static:
            self.static(state)
        else:
            self.dynamic(state)

๊ฑธ์Œ์˜ ํ–‰๋™์— ์ •์  ๋™์  ๋‘๊ฐ€์ง€์˜ ์œ ํ˜•์ด ์žˆ๋‹ค. ๋™์ ์€ ํ›จ์”ฌ ๋น ๋ฅด์ง€๋งŒ ๋ฐœ์„ ๋†“๋Š” ์žฅ์†Œ๊ฐ€ ์ •ํ™•ํ•˜์ง€ ์•Š๋‹ค. atlas ๋กœ๋ด‡์ด ๋„˜์–ด์ง€๊ฒŒ ํ•˜๋Š” ์ข‹์ง€ ์•Š์€ ๊ฑท๊ธฐ ์œ„ํ•œ ์ง€๋ น ์ƒ์„ฑ์ด ๋” ๋งŽ์ด ๋‚˜์˜ฌ ์ˆ˜ ์žˆ๋‹ค. ์ •์ ์€ ์ „์ฒด ๊ฑธ์Œ trajectory๊ฐ€ ์•ˆ์ •์ ์ด๋‹ค.

    # /atlas/atlas_sim_interface_state callback. Before publishing a walk command, we need
    # the current robot position
    def asi_state_cb(self, state):
        try:
            x = self.robot_position.x
        except AttributeError:
            self.robot_position = Point()
            self.robot_position.x = state.pos_est.position.x
            self.robot_position.y = state.pos_est.position.y
            self.robot_position.z = state.pos_est.position.z

        if self.is_static:
            self.static(state)
        else:
            self.dynamic(state)

๋งŒ์•ฝ ๋กœ๋ด‡์ด world frame์—์„œ ํšŒ์ „ํ•œ๋‹ค๋ฉด ๋ฐฉํ–ฅ์ด ๊ฑธ์Œ๊ฑธ์ด๋ฅผ ์œ„์น˜ ์‹œํ‚ค๋Š”๋ฐ ๊ณ ๋ ค ๋  ํ•„์š”๊ฐ€ ์žˆ๋‹ค. ์ด ๋…ธ๋“œ๋Š” ๋ฐฉํ–ฅ์„ ์‚ฌ์šฉํ•˜์ง€๋Š” ์•Š๋Š”๋‹ค.

    # /atlas/atlas_state callback. This message provides the orientation of the robot from the torso IMU
    # This will be important if you need to transform your step commands from the robot's local frame to world frame
    def atlas_state_cb(self, state):
        # If you don't reset to harnessed, then you need to get the current orientation
        roll, pitch, yaw = euler_from_quaternion([state.orientation.x, state.orientation.y, state.orientation.z, state.orientation.w])

์ด ํ•จ์ˆ˜๋Š” ๋กœ๋ด‡์ด ์›์œผ๋กœ ๋™์ ์œผ๋กœ ๊ฑท๋Š”๋‹ค. ์ด๊ฒƒ์€ next_step_index_needed๋กœ ์‹œ์ž‘ํ•˜์—ฌ ์–ด๋–ค ์‹œ์ ์—์„œ 4๊ฐ€์ง€ step์„ ํผ๋ธ”๋ฆฌ์‹œ ํ•  ํ•„์š”๊ฐ€ ์žˆ๋‹ค. ์ด๊ฒƒ์€ ๊ฑท๊ธฐ ์œ„ํ•œ ์ œ์–ด๊ธฐ๊ฐ€ ์•ˆ์ •๋œ ๊ฑท๋Š” trajectory ์ƒ์„ฑ์— ๋„์›€์„ ์ค€๋‹ค. ์ผ๋ถ€ ๋ฉ”์„ธ์ง€๊ฐ€ ์ด ๊ฑท๊ธฐ ๋™์ž‘์— ์‚ฌ์šฉ ํ˜น์€ ์‹คํ˜„๋˜์ง€ ์•Š๋Š”๋‹ค. ๋™์  ๊ฑท๊ธฐ ๋™์ž‘์€ ์žฅ์• ๋ฌผ์ด ์—†๋Š” ํ‰ํ‰ํ•œ ํ‘œ๋ฉด์—์„œ ์ž˜ ์ž‘๋™ํ•œ๋‹ค.

    # An example of commanding a dynamic walk behavior.     
    def dynamic(self, state):                
        command = AtlasSimInterfaceCommand()
        command.behavior = AtlasSimInterfaceCommand.WALK

        # k_effort is all 0s for full BDI controll of all joints.
        command.k_effort = [0] * 28

        # Observe next_step_index_needed to determine when to switch steps.
        self.step_index = state.walk_feedback.next_step_index_needed

        # A walk behavior command needs to know three additional steps beyond the current step needed to plan
        # for the best balance
        for i in range(4):
            step_index = self.step_index + i
            is_right_foot = step_index % 2

            command.walk_params.step_queue[i].step_index = step_index
            command.walk_params.step_queue[i].foot_index = is_right_foot

            # A duration of 0.63s is a good default value
            command.walk_params.step_queue[i].duration = 0.63

            # As far as I can tell, swing_height has yet to be implemented
            command.walk_params.step_queue[i].swing_height = 0.2

            # Determine pose of the next step based on the step_index
            command.walk_params.step_queue[i].pose = self.calculate_pose(step_index)

        # Publish this command every time we have a new state message
        self.asi_command.publish(command)
        # End of dynamic walk behavior

์ด๊ฒƒ์€ ์ •์  ๊ฑท๊ธฐ ๋™์ž‘์˜ ์˜ˆ์ด๋‹ค. This is an example of static walking/step behavior. ํ•œ๋ฒˆ์— ํ•œ ๋‹จ๊ณ„๋งŒ ์ง€์ •ํ•˜๊ณ  ๋‹ค์Œ ๋‹จ๊ณ„ ๋ฉ”์„ธ์ง€๋ฅผ ์–ธ์ œ ๋ณด๋‚ผ์ง€ ๊ฒฐ์ •ํ•˜๊ธฐ ์œ„ํ•ด ์ƒํƒœ ๋ฉ”์„ธ์ง€์—์„œ step_feedback field๋ฅผ ํ™•์ธํ•˜์—ฌ์•ผ ํ•œ๋‹ค. ์ „์ฒด ๊ฑท๊ธฐ trajectory๋ฅผ ํ†ตํ•ด ์ ์ฐจ์ ์œผ๋กœ ์•ˆ์ •๋œ๋‹ค. ๋ฌผ์ฒด ์œ„๋กœ ๊ฑท๊ฑฐ๋‚˜ ์Šคํ…์„ ๋ฐŸ๋Š” ๋ฐ์— ์ด ๋™์ž‘์ด ํ•„์š”ํ•˜๋‹ค.

    # An example of commanding a static walk/step behavior.
    def static(self, state):

        # When the robot status_flags are 1 (SWAYING), you can publish the next step command.
        if (state.step_feedback.status_flags == 1 and not self.is_swaying):
            self.step_index += 1
            self.is_swaying = True
            print("Step " + str(self.step_index))
        elif (state.step_feedback.status_flags == 2):
            self.is_swaying = False

        is_right_foot = self.step_index % 2

        command = AtlasSimInterfaceCommand()
        command.behavior = AtlasSimInterfaceCommand.STEP

        # k_effort is all 0s for full bdi control of all joints
        command.k_effort = [0] * 28

        # step_index should always be one for a step command
        command.step_params.desired_step.step_index = 1
        command.step_params.desired_step.foot_index = is_right_foot

        # duration has far as I can tell is not observed
        command.step_params.desired_step.duration = 0.63

        # swing_height is not observed
        command.step_params.desired_step.swing_height = 0.1

        if self.step_index > 30:
            print(str(self.calculate_pose(self.step_index)))
        # Determine pose of the next step based on the number of steps we have taken
        command.step_params.desired_step.pose = self.calculate_pose(self.step_index)

        # Publish a new step command every time a state message is received
        self.asi_command.publish(command)
        # End of static walk behavior

์ด ํ•จ์ˆ˜๋Š” ํ˜„์žฌ step_index๋ฅผ ํ†ตํ•ด ์› ์ฃผ๋ณ€์„ ๊ฑท๋Š” ์ž์„ธ๋ฅผ ๊ณ„์‚ฐํ•œ๋‹ค.

    # This method is used to calculate a pose of step based on the step_index
    # The step poses just cause the robot to walk in a circle
    def calculate_pose(self, step_index):
        # Right foot occurs on even steps, left on odd
        is_right_foot = step_index % 2
        is_left_foot = 1 - is_right_foot

        # There will be 60 steps to a circle, and so our position along the circle is current_step
        current_step = step_index % 60

        # yaw angle of robot around circle
        theta = current_step * math.pi / 30

        R = 2 # Radius of turn
        W = 0.3 # Width of stride

        # Negative for inside foot, positive for outside foot
        offset_dir = 1 - 2 * is_left_foot

        # Radius from center of circle to foot
        R_foot = R + offset_dir * W/2

        # X, Y position of foot
        X = R_foot * math.sin(theta)
        Y = (R - R_foot*math.cos(theta))

        # Calculate orientation quaternion
        Q = quaternion_from_euler(0, 0, theta)
        pose = Pose()
        pose.position.x = self.robot_position.x + X
        pose.position.y = self.robot_position.y + Y

        # The z position is observed for static walking, but the foot
        # will be placed onto the ground if the ground is lower than z
        pose.position.z = 0

        pose.orientation.x = Q[0]
        pose.orientation.y = Q[1]
        pose.orientation.z = Q[2]
        pose.orientation.w = Q[3]

        return pose

๊ฑท๊ธฐ๋ฅผ ์ˆ˜ํ–‰ํ•˜๋Š” ๋ฉ”์ธ ํ•จ์ˆ˜ ์ด๋ฉฐ ์ด๊ฒƒ์€ static์ด ์ง€์ •๋˜์—ˆ๋Š”์ง€ ์ง€์ •๋˜์ง€ ์•Š์•˜๋Š”์ง€ ํ™•์ธ ํ•ฉ๋‹ˆ๋‹ค.

if __name__ == '__main__':
    rospy.init_node('walking_tutorial')
    walk = AtlasWalk()
    if len(sys.argv) > 0:
        walk.is_static = (sys.argv[-1] == "static")
    else:
        walk.is_static = False
    walk.walk()

Running

์œ„์˜ ํŒŒ์ด์ฌ ํŒŒ์ผ์ด ์‹คํ–‰ ๋˜๋„๋ก ํŒŒ์ผ ๊ถŒํ•œ์„ ์กฐ์ •ํ•œ๋‹ค.

chmod +x ~/catkin_ws/src/atlas_sim_interface_tutorial/src/walk.py

์‹œ๋ฎฌ๋ ˆ์ด์…˜์„ ์‹คํ–‰ํ•˜๋ผ.

roslaunch drcsim_gazebo atlas.launch hand_suffix:=_sandia_hands

๋™์ ์ธ ๊ฑธ์Œ์„ ์œ„ํ•ด ๋‹ค์Œ์„ ์ˆ˜ํ–‰ํ•˜๋ผ.

Dynamic

rosrun atlas_sim_interface_tutorial walk.py

์ •์ ์ธ ๊ฑธ์Œ์„ ์œ„ํ•ด์„œ๋Š” ๋‹ค์Œ์„ ์ˆ˜ํ–‰ํ•˜๋ผ.

Static

gz physics -i 60  # update physics iterations from 50 to 60
rosrun atlas_sim_interface_tutorial walk.py static

What you should see

Atlas๊ฐ€ ์›์„ ๊ทธ๋ฆฌ๋ฉฐ ๊ฑท๊ธฐ ์‹œ์ž‘ํ•œ๋‹ค. ๋™์  ๊ฑธ์Œ์—์„œ๋Š” ๋น ๋ฅด๊ฒŒ ์ •์  ๊ฑธ์Œ์—์„œ๋Š” ๋А๋ฆฌ๊ฒŒ ๊ฑท๋Š”๋‹ค.