You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 

20 KiB

Stretch Body API Reference

Stretch Body is the Python interface for working with Stretch. This page serves as a reference for the interfaces defined in the stretch_body library. See the Stretch Body Tutorials for additional information on working with this library.

The Robot Class

The most common interface to Stretch is the Robot class. This class encapsulates all devices on the robot. It is typically initialized as:

import stretch_body.robot

r = stretch_body.robot.Robot()
if not r.startup():
    exit() # failed to start robot!

# home the joints to find zero, if necessary
if not r.is_calibrated():
    r.home()

# interact with the robot here

The startup() and home() methods start communication with and home each of the robot's devices, respectively. Through the Robot class, users can interact with all devices on the robot. For example, continuing the example above:

# moving joints on the robot
r.arm.pretty_print()
r.lift.pretty_print()
r.base.pretty_print()
r.head.pretty_print()
r.end_of_arm.pretty_print()

# other devices on the robot
r.wacc.pretty_print()
r.pimu.pretty_print()

r.stop()

Each of these devices is defined in separate modules within stretch_body. In the following section, we'll look at the API of these classes. The stop() method shuts down communication with the robot's devices.

All methods in the Robot class are documented below.

::: stretch_body.robot.Robot

The Device Class

The stretch_body library is modular in design. Each subcomponent of Stretch is defined in its class and the Robot class provides an interface that ties all of these classes together. This modularity allows users to plug in new/modified subcomponents into the Robot interface by extending the Device class.

It is possible to interface with a single subcomponent of Stretch by initializing its device class directly. In this section, we'll look at the API of seven subclasses of the Device class: the Arm, Lift, Base, Head, EndOfArm, Wacc, and Pimu subcomponents of Stretch.

Using the Arm class

Top down Stretch arm blueprint Top down Stretch arm blueprint

The interface to Stretch's telescoping arm is the Arm class. It is typically initialized as:

import stretch_body.arm

a = stretch_body.arm.Arm()
a.motor.disable_sync_mode()
if not a.startup():
    exit() # failed to start arm!

a.home()

# interact with the arm here

Since both Arm and Robot are subclasses of the Device class, the same startup() and stop() methods are available here, as well as other Device methods such as home(). Using the Arm class, we can read the arm's current state and send commands to the joint. For example, continuing the example above:

starting_position = a.status['pos']

# move out by 10cm
a.move_to(starting_position + 0.1)
a.push_command()
a.motor.wait_until_at_setpoint()

# move back to starting position quickly
a.move_to(starting_position, v_m=0.2, a_m=0.25)
a.push_command()
a.motor.wait_until_at_setpoint()

a.move_by(0.1) # move out by 10cm
a.push_command()
a.motor.wait_until_at_setpoint()

The move_to() and move_by() methods queue absolute and relative position commands to the arm, respectively, while the nonblocking push_command() method pushes the queued position commands to the hardware for execution.

The attribute motor, an instance of the Stepper class, has the method wait_until_at_setpoint() which blocks program execution until the joint reaches the commanded goal. With firmware P1 or greater installed, it is also possible to queue a waypoint trajectory for the arm to follow:

starting_position = a.status['pos']

# queue a trajectory consisting of four waypoints
a.trajectory.add(t_s=0, x_m=starting_position)
a.trajectory.add(t_s=3, x_m=0.15)
a.trajectory.add(t_s=6, x_m=0.1)
a.trajectory.add(t_s=9, x_m=0.2)

# trigger trajectory execution
a.follow_trajectory()
import time; time.sleep(9)

The attribute trajectory, an instance of the PrismaticTrajectory class, has the method add() which adds a single waypoint in a linear sliding trajectory. For a well-formed trajectory (see is_valid()), the follow_trajectory() method starts tracking the trajectory for the telescoping arm.

It is also possible to dynamically restrict the arm joint range:

range_upper_limit = 0.3 # meters

# set soft limits on arm's range
a.set_soft_motion_limit_min(0)
a.set_soft_motion_limit_max(range_upper_limit)
a.push_command()

# command the arm outside the valid range
a.move_to(0.4)
a.push_command()
a.motor.wait_until_at_setpoint()
print(a.status['pos']) # we should expect to see ~0.3

a.stop()

The set_soft_motion_limit_min/max() methods form the basis of an experimental self-collision avoidance system built into Stretch Body.

All methods in the Arm class are documented below.

::: stretch_body.arm.Arm

Using the Lift class

Stretch lift blueprint Stretch lift blueprint

The interface to Stretch's lift is the Lift class. It is typically initialized as:

import stretch_body.lift

l = stretch_body.lift.Lift()
l.motor.disable_sync_mode()
if not l.startup():
    exit() # failed to start lift!

l.home()

# interact with the lift here

The startup() and home() methods are extended from the Device class. Reading the lift's current state and sending commands to the joint occurs similarly to the Arm class:

starting_position = l.status['pos']

# move up by 10cm
l.move_to(starting_position + 0.1)
l.push_command()
l.motor.wait_until_at_setpoint()

The attribute status is a dictionary of the joint's current status. This state information is updated in the background in real-time by default (disable by initializing as startup(threading=False)). Use the pretty_print() method to print out this state info in a human-interpretable format. Setting up waypoint trajectories for the lift is also similar to the Arm:

starting_position = l.status['pos']

# queue a trajectory consisting of three waypoints
l.trajectory.add(t_s=0, x_m=starting_position, v_m=0.0)
l.trajectory.add(t_s=3, x_m=0.5,               v_m=0.0)
l.trajectory.add(t_s=6, x_m=0.6,               v_m=0.0)

# trigger trajectory execution
l.follow_trajectory()
import time; time.sleep(6)

The attribute trajectory is also an instance of the PrismaticTrajectory class, and by providing the instantaneous velocity argument v_m to the add() method, a cubic spline can be loaded into the joint's trajectory. The call to follow_trajectory() begins hardware tracking of the spline.

Finally, setting soft motion limits for the lift's range can be done using:

# cut out 0.2m from the top and bottom of the lift's range
l.set_soft_motion_limit_min(0.2)
l.set_soft_motion_limit_max(0.8)
l.push_command()

l.stop()

The set_soft_motion_limit_min/max() methods perform clipping of the joint's range at the firmware level (can persist across reboots).

All methods in the Lift class are documented below.

::: stretch_body.lift.Lift

Using the Base class

Stretch mobile base diagram Stretch mobile base diagram

Item Notes
A Drive wheels 4 inch diameter, urethane rubber shore 60A
B Cliff sensors Sharp GP2Y0A51SK0F, Analog, range 2-15 cm
C Mecanum wheel Diameter 50mm

The interface to Stretch's mobile base is the Base class. It is typically initialized as:

import stretch_body.base

b = stretch_body.base.Base()
b.left_wheel.disable_sync_mode()
b.right_wheel.disable_sync_mode()
if not b.startup():
    exit() # failed to start base!

# interact with the base here

Stretch's mobile base is a differential drive configuration. The left and right wheels are accessible through Base left_wheel and right_wheel attributes, both of which are instances of the Stepper class. The startup() method is extended from the Device class. Since the mobile base is unconstrained, there is no homing method. The pretty_print() method prints out mobile base state information in a human-interpretable format. We can read the base's current state and send commands using:

b.pretty_print()

# translate forward by 10cm
b.translate_by(0.1)
b.push_command()
b.left_wheel.wait_until_at_setpoint()

# rotate counter-clockwise by 90 degrees
b.rotate_by(1.57)
b.push_command()
b.left_wheel.wait_until_at_setpoint()

The translate_by() and rotate_by() methods send relative commands similar to the way move_by() behaves for the single degree of freedom joints.

The mobile base also supports velocity control:

# command the base to translate forward at 5cm / second
b.set_translate_velocity(0.05)
b.push_command()
import time; time.sleep(1)

# command the base to rotate counter-clockwise at 0.1rad / second
b.set_rotational_velocity(0.1)
b.push_command()
time.sleep(1)

# command the base with translational and rotational velocities
b.set_velocity(0.05, 0.1)
b.push_command()
time.sleep(1)

# stop base motion
b.enable_freewheel_mode()
b.push_command()

The set_translate_velocity() and set_rotational_velocity() methods give velocity control over the translational and rotational components of the mobile base independently. The set_velocity() method gives control over both of these components simultaneously.

To halt motion, you can command zero velocities or command the base into freewheel mode using enable_freewheel_mode(). The mobile base also supports waypoint trajectory following, but the waypoints are part of the SE2 group, where a desired waypoint is defined as an (x, y) point and a theta orientation:

# reset odometry calculation
b.first_step = True
b.pull_status()

# queue a trajectory consisting of three waypoints
b.trajectory.add(time=0, x=0.0, y=0.0, theta=0.0)
b.trajectory.add(time=3, x=0.1, y=0.0, theta=0.0)
b.trajectory.add(time=6, x=0.0, y=0.0, theta=0.0)

# trigger trajectory execution
b.follow_trajectory()
import time; time.sleep(6)
print(b.status['x'], b.status['y'], b.status['theta']) # we should expect to see around (0.0, 0.0, 0.0 or 6.28)

b.stop()

!!! warning The Base waypoint trajectory following has no notion of obstacles in the environment. It will blindly follow the commanded waypoints. For obstacle avoidance, we recommend employing perception and a path planner.

The attribute trajectory is an instance of the DiffDriveTrajectory class. The call to follow_trajectory() begins hardware tracking of the spline.

All methods of the Base class are documented below.

::: stretch_body.base.Base

Using the Head class

Stretch head blueprint Stretch head blueprint

The interface to Stretch's head is the Head class. The head contains an Intel Realsense D435i depth camera. The pan and tilt joints in the head allow Stretch to swivel and capture depth imagery of its surrounding. The head is typically initialized as:

import stretch_body.head

h = stretch_body.head.Head()
if not h.startup():
    exit() # failed to start head!

h.home()

# interact with the head here

Head is a subclass of the DynamixelXChain class, which in turn is a subclass of the Device class. Therefore, some of Head's methods, such as startup() and home() are extended from the Device class, while others come from the DynamixelXChain class. Reading the head's current state and sending commands to its revolute joints (head pan and tilt) can be achieved using:

starting_position = h.status['head_pan']['pos']

# look right by 90 degrees
h.move_to('head_pan', starting_position + 1.57)
h.get_joint('head_pan').wait_until_at_setpoint()

# tilt up by 30 degrees
h.move_by('head_tilt', -1.57 / 3)
h.get_joint('head_tilt').wait_until_at_setpoint()

# look down towards the wheels
h.pose('wheels')
import time; time.sleep(3)

# look ahead
h.pose('ahead')
time.sleep(3)

The attribute status is a dictionary of dictionaries, where each subdictionary is the status of one of the head's joints. This state information is updated in the background in real-time by default (disable by initializing as startup(threading=False)). Use the pretty_print() method to print out this state information in a human-interpretable format.

Commanding the head's revolute joints is done through the move_to() and move_by() methods. Notice that, unlike the previous joints, no push command call is required here. These joints are Dynamixel servos, which behave differently than the Hello Robot steppers. Their commands are not queued and are executed as soon as they're received.

Head's two joints, the 'head_pan' and 'head_tilt' are instances of the DynamixelHelloXL430 class and are retrievable using the get_joint() method. They have the wait_until_at_setpoint() method, which blocks program execution until the joint reaches the commanded goal.

The pose() method makes it easy to command the head to common head poses (e.g. looking 'ahead', at the end-of-arm 'tool', obstacles in front of the 'wheels', or 'up').

The head supports waypoint trajectories as well:

# queue a trajectory consisting of three waypoints
h.get_joint('head_tilt').trajectory.add(t_s=0, x_r=0.0)
h.get_joint('head_tilt').trajectory.add(t_s=3, x_r=-1.0)
h.get_joint('head_tilt').trajectory.add(t_s=6, x_r=0.0)
h.get_joint('head_pan').trajectory.add(t_s=0, x_r=0.1)
h.get_joint('head_pan').trajectory.add(t_s=3, x_r=-0.9)
h.get_joint('head_pan').trajectory.add(t_s=6, x_r=0.1)

# trigger trajectory execution
h.follow_trajectory()
import time; time.sleep(6)

The head pan and tilt DynamixelHelloXL430 instances have an attribute trajectory, which is an instance of the RevoluteTrajectory class. The call to follow_trajectory() begins software tracking of the spline.

Finally, setting soft motion limits for the head's pan and tilt range can be achieved using:

# clip the head_pan's range
h.get_joint('head_pan').set_soft_motion_limit_min(-1.0)
h.get_joint('head_pan').set_soft_motion_limit_max(1.0)

# clip the head_tilt's range
h.get_joint('head_tilt').set_soft_motion_limit_min(-1.0)
h.get_joint('head_tilt').set_soft_motion_limit_max(0.1)

h.stop()

The set_soft_motion_limit_min/max() methods perform clipping of the joint's range at the software level (cannot persist across reboots).

All methods of the Head class are documented below.

::: stretch_body.head.Head

Using the EndOfArm class

The interface to Stretch's end-of-arm is the EndOfArm class. It is typically initialized as:

import stretch_body.end_of_arm

e = stretch_body.end_of_arm.EndOfArm()
if not e.startup(threaded=True):
    exit() # failed to start end of arm!

# interact with the end of arm here

e.stop()

All methods of the EndOfArm class are documented below.

::: stretch_body.end_of_arm.EndOfArm

Using the Wacc class

The interface to Stretch's wrist board is the Wacc (wrist + accelerometer) class. This board provides an Arduino and accelerometer sensor that is accessible from the Wacc class. It is typically initialized as:

import stretch_body.wacc

w = stretch_body.wacc.Wacc()
if not w.startup(threaded=True):
    exit() # failed to start wacc!

# interact with the wacc here

w.stop()

All methods of the Wacc class are documented below.

::: stretch_body.wacc.Wacc

Using the Pimu class

The interface to Stretch's power board is the Pimu (power + IMU) class. This board provides a 9-DOF IMU that is accessible from the Pimu class. It is typically initialized as:

import stretch_body.pimu

p = stretch_body.pimu.Pimu()
if not p.startup(threaded=True):
    exit() # failed to start pimu!

# interact with the pimu here

p.stop()

All methods of the Pimu class are documented below.

::: stretch_body.pimu.Pimu


All materials are Copyright 2022 by Hello Robot Inc. Hello Robot and Stretch are registered trademarks.