The channel interface is how WASM controllers interact with robot hardware. Instead of robot-specific APIs (set_joint_velocity, set_thrust), controllers read and write through numbered channels that are defined by the robot’s manifest. The same controller pattern works across manipulators, drones, and mobile robots.
This follows the ros2_control / MuJoCo / Drake pattern: named, typed, bounded channels with discovery.
How It Works
Command channels are written by the controller each tick. They carry velocity, position, or effort values to actuators.
State channels are read by the controller each tick. They carry position, velocity, or effort feedback from sensors.
ChannelManifest
Every robot is described by a ChannelManifest that lists its command and state channels, along with safety limits and metadata.
ChannelDescriptor
Each channel has a name, type, unit, limits, default value, and optional rate-of-change cap.
The InterfaceType enum determines what kind of value a channel carries:
| Type | Description | Typical unit |
|---|
Position | Angular or linear position | rad, m |
Velocity | Angular or linear velocity | rad/s, m/s |
Effort | Torque or force | Nm, N |
WASM Host Functions
Controllers access channels through these host functions, imported from the command and state modules:
| Module | Function | Signature | Description |
|---|
command | set | (i32, f64) -> i32 | Write a value to command channel i |
command | count | () -> i32 | Number of command channels |
command | limit_min | (i32) -> f64 | Minimum allowed value for channel i |
command | limit_max | (i32) -> f64 | Maximum allowed value for channel i |
state | get | (i32) -> f64 | Read the current value of state channel i |
state | count | () -> i32 | Number of state channels |
command::set returns 0 on success and 1 if the value was clamped to the channel’s limits. The safety filter applies additional clamping downstream.
Built-in Manifests
roz includes factory methods for common robot types:
UR5 Manipulator
Quadcopter
Differential Drive
Generic
ChannelManifest::ur5() — 6-DOF arm6 command channels (velocity):| Index | Name | Limits | Unit |
|---|
| 0 | shoulder_pan_joint/velocity | +/-3.14 | rad/s |
| 1 | shoulder_lift_joint/velocity | +/-3.14 | rad/s |
| 2 | elbow_joint/velocity | +/-3.14 | rad/s |
| 3 | wrist_1_joint/velocity | +/-3.14 | rad/s |
| 4 | wrist_2_joint/velocity | +/-3.14 | rad/s |
| 5 | wrist_3_joint/velocity | +/-3.14 | rad/s |
12 state channels (6 position + 6 velocity):| Index | Name | Type | Unit |
|---|
| 0-5 | {joint}/position | Position | rad |
| 6-11 | {joint}/velocity | Velocity | rad/s |
Each command channel has max_rate_of_change: 0.5 (50 rad/s^2 at 100 Hz) and a position_state_index pairing it with its corresponding position state channel for position limit enforcement.ChannelManifest::quadcopter() — body velocity control4 command channels:| Index | Name | Limits | Unit |
|---|
| 0 | body/velocity.x | +/-5.0 | m/s |
| 1 | body/velocity.y | +/-5.0 | m/s |
| 2 | body/velocity.z | +/-3.0 | m/s |
| 3 | body/yaw_rate | +/-1.57 | rad/s |
4 state channels:| Index | Name | Type | Unit |
|---|
| 0 | body/position.x | Position | m |
| 1 | body/position.y | Position | m |
| 2 | body/position.z | Position | m |
| 3 | body/yaw | Position | rad |
ChannelManifest::diff_drive() — twist control2 command channels:| Index | Name | Limits | Unit |
|---|
| 0 | base/linear.x | +/-1.0 | m/s |
| 1 | base/angular.z | +/-2.0 | rad/s |
3 state channels:| Index | Name | Type | Unit |
|---|
| 0 | base/odom.x | Position | m |
| 1 | base/odom.y | Position | m |
| 2 | base/odom.yaw | Position | rad |
ChannelManifest::generic_velocity(n, max_vel) — N joints with symmetric velocity limits and no state channels. Useful for testing or robots where only the joint count and velocity limit are known.
Custom Manifests with robot.toml
For robots not covered by the built-in factories, define a custom manifest in robot.toml:
Source