Skip to main content

Simulation Container

docker pull bedrockdynamics/substrate-sim:ros2-manipulator
The container bundles Gazebo Harmonic, ROS 2 Humble, MoveIt2, and an MCP server. It exposes two ports:
PortProtocolPurpose
8090HTTPMCP tool server
9090gRPCros2-bridge (joint state streaming + trajectory relay)

MCP Tools

The MCP server exposes four tools for high-level arm control at 1-3Hz.

get_joint_state

Returns the current position and velocity of all 6 joints as a JSON object. No parameters.

move_to_named_target

Moves the arm to a predefined MoveIt2 named configuration.
ParameterTypeDescription
targetstringNamed target (e.g. "home", "up", "ready")

move_to_pose

Plans and executes a motion to a Cartesian pose using MoveIt2.
ParameterTypeDescription
x, y, zfloatTarget position in meters (base frame)
qx, qy, qz, qwfloatTarget orientation as quaternion

stop_arm

Immediately halts all joint motion. Sends zero-velocity commands and cancels any active MoveIt2 trajectory. No parameters.

WASM Channels

The ChannelManifest::ur5() manifest defines 6 velocity command channels and 12 state channels (6 position + 6 velocity), all running at 100Hz.

Command Channels (6)

IndexNameUnitLimits
0shoulder_pan_joint/velocityrad/s-pi to pi
1shoulder_lift_joint/velocityrad/s-pi to pi
2elbow_joint/velocityrad/s-pi to pi
3wrist_1_joint/velocityrad/s-pi to pi
4wrist_2_joint/velocityrad/s-pi to pi
5wrist_3_joint/velocityrad/s-pi to pi
Each command channel has a max_rate_of_change of 0.5 rad/s per tick (50 rad/s^2 acceleration limit at 100Hz). Each velocity command is paired with its corresponding position state channel for position limit enforcement.

State Channels (12)

IndexNameUnitType
0-5{joint}/positionradPosition
6-11{joint}/velocityrad/sVelocity
Position limits are +/- 2*pi rad. Position state channels come first (indices 0-5), followed by velocity state channels (indices 6-11).

Velocity Integration

The WASM controller writes velocity commands. The gRPC bridge relay converts these into position+velocity trajectory points for the scaled_joint_trajectory_controller in ros2_control. On each tick:
  1. Read current joint positions from state channels
  2. Integrate velocity over the tick period to produce a target position
  3. Send a JointTrajectory message with both the target position and velocity
This approach gives smooth motion because the controller receives both where to go and how fast to get there.

Example

A WASM controller that rotates the shoulder pan joint:
;; Set channel 0 (shoulder_pan_joint) to 0.2 rad/s
(call $set_command (i32.const 0) (f64.const 0.2))
The safety filter clamps this to the configured limits before it reaches the bridge. The bridge integrates the velocity into a trajectory point and sends it to the scaled_joint_trajectory_controller.

Real Hardware

For a physical UR5 (no Docker container):
  1. Run your robot’s ros2_control stack (UR driver + MoveIt2) on the robot or a companion computer
  2. Point roz at your ROS 2 environment — the MCP tools and WASM channels work the same way
  3. The scaled_joint_trajectory_controller is the same controller used in simulation
Real hardware support is under active development. The safety guarantees are currently simulation-validated only. Additional validation is required before deploying WASM velocity controllers on physical hardware.

Source Code