Writing Code for Stretch
This tutorial introduces the two primary ways to develop software with Stretch SE4 — Python and ROS 2 — and walks you through writing your first Python programs using the Stretch 4 Body API. By the end, you will be able to command every joint on the robot, read sensor status, control the omnibase holonomically, and use the client/server architecture.
Background
Stretch 4 supports two approaches to software development:
Python (Stretch 4 Body) is the low-level direct interface to the robot hardware. It gives you fine-grained control over every joint and sensor, and is the fastest way to get started. The stretch4_body package is pre-installed on every Stretch SE4.
ROS 2 (Robot Operating System 2) is a robotics middleware framework providing a collection of tools, libraries, and conventions for building robot applications. Stretch SE4 ships with ROS 2 Jazzy and a full driver stack. It is well suited for navigation, SLAM, MoveIt, and multi-node architectures.
You can learn more about when to use each approach in the Developing with Stretch guide. This tutorial focuses on the Python API. ROS 2 examples are covered in the Demos section.
Software Architecture
All application code on Stretch SE4 uses the RobotClient class to communicate with a background stretch_body_server process running a 100 Hz control loop on the NUC.
The 100 Hz control loop follows this sequence every tick:
Pulls status from all hardware devices
Updates safety sentries (watchdogs)
Ingests commands from the Robot Client
Runs active controllers and behaviors
Computes safe motion limits
Pushes safe commands to motor controllers
Your code connects via RobotClient, which queues commands that the server loop ingests on the next tick. This means motion is asynchronous by default — your code keeps running while the robot moves. Use push_command() to flush the command queue and wait_on_motion_finish() to block until motion is done.
Prerequisites
Before running any code, the robot must be set up and ready.
1. Make sure the body server is running.
The body server is launched automatically at startup. Verify it is active:
If it is not running, start it:
2. Free up robot control from the gamepad demo.
If the Xbox gamepad teleoperation demo is running at startup, your code cannot control the robot. Free the robot:
3. Confirm the system is healthy.
4. Home the robot (required once per power cycle).
The robot will beep when homing is complete. Joints will not accept motion commands until they are homed. Once homed, the motors remember their homed state as long as the robot remains powered on.
Your First Program with RobotClient
Open a terminal and launch iPython, an interactive Python console where each line runs immediately:
Import and start the client:
startup() connects to the running body server. If successful, you now have full access to all robot subsystems.
Stow the robot to its compact travel pose:
This is a blocking call — it returns only when the robot is fully stowed.
Check if the robot is homed:
When you are done with a session, always call:
This cleanly disconnects from the server. Skipping stop() can leave stale connections that block other processes from controlling the robot.
Using RobotClient as a Context Manager
For scripts, the cleanest pattern is to use RobotClient as a context manager. This ensures stop() is always called, even if an exception occurs:
Moving Individual Joints
Arm
The arm telescopes horizontally, extending up to ~0.52 m (21.6 inches) beyond the base. Position is in meters (0.0 = fully retracted, ~0.52 = fully extended).
Move the arm to an absolute position:
Move the arm by a relative amount:
Move the arm back:
Lift
The lift translates the arm vertically, from floor level up to ~1.20 m (47 inches). Position is in meters.
Move the lift to mid height:
Move the lift up a little:
Commanding Multiple Joints Simultaneously
You can queue commands to several joints before pushing. They will execute simultaneously:
[!NOTE] If you queue two motion commands for the same joint before pushing, only the last one executes. Earlier commands are overwritten.
Velocity and Acceleration Limits
Every motion command accepts optional velocity and acceleration limits (in m/s and m/s² respectively):
If not specified, the robot uses its default motion profile.
The Omnibase (Holonomic Base)
Stretch SE4 has a triangular holonomic omnibase — three holonomic closed-loop stepper motors each driving one omnidirectional wheel. Unlike a differential drive, the omnibase can move forward, sideways, diagonally, and rotate in place — all simultaneously.
Wheel numbering increases counter-clockwise, with wheel 0 to the left of the forward direction. This is consistent with the ROS convention where X+ is forward and Y+ is left.
The base is accessed as robot.base or robot.omnibase.
Translate by a Relative Amount
Move the base 0.3 m forward (X):
Move sideways to the left (positive Y):
Rotate in Place
Rotate counter-clockwise by 90 degrees (π/2 radians):
Set Continuous Velocity
Set a continuous velocity (useful for teleoperation or reactive control). Units: m/s for linear, rad/s for rotation:
Hard Stop
Immediately stop all base motion:
The Dexterous Wrist and Gripper
Stretch SE4 has a 3-DOF dexterous wrist (yaw, pitch, roll) driven by Feetech servo motors over a TTL serial bus, plus a compliant spring-mechanism gripper.
All wrist and gripper joints are accessed through robot.end_of_arm. Positions are in radians unless noted.
wrist_yaw
±170 deg (340 deg total)
wrist_pitch
~100 deg
wrist_roll
±170 deg (340 deg total)
stretch_gripper
-100 to +100 (percent, where positive = open)
Move Wrist Joints
Move wrist yaw to 0.5 rad:
Move wrist pitch:
Move wrist roll by a relative amount:
Gripper
Open the gripper (positive values = open):
Close the gripper (negative values = closed):
Move to neutral (zero):
Disable/Enable Torque on a Wrist Joint
Making a joint backdrivable (torque off):
Re-enabling:
Homing the Wrist
If a wrist joint loses its homed state (e.g., after a Feetech motor error and reboot), home it:
Or home the entire end of arm at once:
[!NOTE] After a Feetech error: If a wrist or gripper motor enters an error state (LED blinks red, motor becomes limp), clear it with
stretch_feetech_rebootfrom a terminal, then re-home the wrist.
Reading Robot Status
Print Full Robot Status
Print all subsystem statuses in a human-readable format:
This outputs a lot of data. To read individual subsystem statuses, access robot.status directly:
Key Status Fields
Lift/Arm:
Omnibase:
Power Periph (IMU, battery, runstop):
Wrist Joint (via end_of_arm):
Waiting for Motion to Complete
Motion commands are asynchronous. Use these methods to synchronize:
Wait for Specific Subsystems to Finish Moving
Wait for multiple joints at once:
Wait for All Motion to Complete
To wait for the arm, lift, base, and end of arm to all finish moving:
Guarded Contact Sensitivity
Stretch SE4's lift, arm, and omnibase joints have a Guarded Contact safety system. This uses current sensing to detect when actuator effort exceeds a configurable threshold. When triggered, the joint halts until a new command is received.
This protects people and objects from excessive force. By default, contacts are set to a moderate sensitivity. You can change this per use case:
You can also pass per-move contact thresholds directly:
Power Periph: Beeps, Eyes, and Fan
The Power Periph board controls the speaker buzzer, LED eye animations, RGB lightbar, and chassis fans.
Trigger a Beep
Control LED Eyes
Set animations on the left and right eye rings. Animation indices are defined in the firmware (0 = off, see stretch_eye_animations for the full set):
Toggle the Runstop Programmatically
Control the Fan
Writing a Complete Script
Here is a complete standalone Python script that demonstrates a sequence of moves on Stretch SE4. Save this as my_first_stretch_script.py:
Run it with:
Feetech Motor Errors
Wrist and gripper Feetech motors can enter an error state after over-force or over-temperature events. When this happens:
The motor becomes limp and backdrivable
The LED on the motor body blinks red
Motion commands are ignored
Clear the error without powering down:
After rebooting, re-home the wrist and gripper:
You can also check Feetech motor health at any time:
ROS 2 with Stretch SE4
Stretch SE4 ships with ROS 2 Jazzy. The ROS 2 driver communicates with stretch_body_server via the same RobotClient interface, making Python and ROS 2 development fully interoperable.
Key ROS 2 topics published by the driver include:
/joint_states— positions and velocities for all joints/wheel_odom— odometry from the omnibase/scan_filtered— point clouds from the Hesai QT128 lidars/color/image_raw— RGB images from the head cameras
Key ROS 2 services include:
/home— trigger full robot homing/stow— stow the robot
The Jetson add-on (when present) subscribes to camera topics and returns inference results via Zenoh topic bridging. This enables GPU-accelerated perception (e.g., YOLO, pose estimation) without burdening the NUC.
For full ROS 2 examples, refer to the ROS 2 with Stretch tutorial track on Stretch Docs.
Learn More
Robot Overview — complete reference for all hardware, sensors, and CLI tools on Stretch SE4
Stretch Safety Guide — essential safety information before operating the robot
Next Steps
In the next tutorial, Demo #1 - Mapping & Navigation, we will look at two ways that Stretch can navigate within a map.
Last updated
Was this helpful?
