devrel:arm-recorder:recorder is a Viam sensor component that records a configured arm's joint positions at a configurable frequency into named session JSON files, and replays those sessions by commanding the arm back through the captured positions — all driven via DoCommand. It is designed as a proof-of-concept demonstration tool for motion capture and replay on Viam-connected arms. Optionally, it also records and replays a gripper's position in parallel with the arm.
See the recorder documentation for information about that resource.
See the reactor documentation for information about the devrel:arm-recorder:reactor generic service, which polls a detector vision service and autonomously triggers the recorder to play a matching session when a configured label is detected.
- Single activity at a time. Only one operation (recording or playback) runs at a time. Attempting to start a second while one is active returns an error.
- Playback engine — two-phase motion.
- Safe-entry move. The arm is commanded to the first recorded frame via a single-waypoint
MoveThroughJointPositionscall so that the configuredmax_velocity_rads_per_sec/max_acceleration_rads_per_seclimits apply. If a gripper is in use, it is simultaneously set to its first recorded position. Both run concurrently; an error in either aborts the entry and stops the arm. Safety note: when no velocity/acceleration limits are configured, this initial move runs at the arm driver's default speed and can be a large sweep from the arm's current pose — configure limits (or manually position the arm near the start pose) before testing on hardware. - Smooth main motion. The remaining frames are passed to a single
MoveThroughJointPositionscall, which blends through all waypoints in one continuous motion. If a gripper is in use, its positions are stepped on a parallel ticker running at the session's recordedfrequency_hz.
- Safe-entry move. The arm is commanded to the first recorded frame via a single-waypoint
- Playback duration is governed by arm speed, not wall clock.
max_velocity_rads_per_secandmax_acceleration_rads_per_secapply to the entire playback motion — including the initial safe-entry move to the first recorded frame — when those attributes are set; otherwise the arm driver's defaults apply to all moves. Total playback duration will therefore differ from recording duration. Gripper sync is best-effort wall-clock aligned to the recording frequency — it may drift relative to arm motion. - Playback failures stop the arm. Any error during playback (arm motion, gripper command, or context cancellation from
stop_playback/Close) causes the arm to be halted immediately. Internal failures (not user-requested stops) are surfaced inReadings()aslast_error. - Session storage. Sessions are stored as
<session>.jsonunder$VIAM_MODULE_DATA. If that environment variable is not set (e.g. on a dev workstation), the component falls back to a temporary directory underos.TempDir()and logs a warning. Viam setsVIAM_MODULE_DATAautomatically when the module is deployed via the Viam platform. - Invalid
frequency_hzin a session file. If a loaded session file contains a zero, negative,NaN, or infinitefrequency_hz, the component logs a warning and falls back to the default (10 Hz) for that playback. - Session name safety. Session names must not contain path separators (
/,\) or the sequence... Invalid names are rejected with an error. - Session without gripper data. If a session was recorded without a gripper (or is an older file without gripper fields) but a gripper is currently configured, playback proceeds arm-only and a log message is emitted. If a session has gripper data but no gripper is configured, playback is rejected with an error.
MoveThroughJointPositions blends continuously through waypoints, which means it can corner-cut past sparse recorded frames — the actual arm path may deviate from the recorded path between distant waypoints. The playback_interpolation_steps attribute controls how many linearly-interpolated waypoints are inserted between each consecutive pair of recorded frames before the blended move is issued.
- Default is 10. A value of 10 inserts 10 intermediate points between each pair of recorded frames, which is a good starting point for typical recordings.
- Set to 0 to disable. With
playback_interpolation_steps: 0, recorded frames are passed directly toMoveThroughJointPositions— this exactly reproduces behavior from before this feature was added. - Recommended range: 5–20. Raise the value for faster-moving or sparser recordings where the arm covers more distance between frames. Very large values produce more waypoints and may slightly increase the time spent building the waypoint list, but do not change overall playback duration (which is governed by
max_velocity_rads_per_sec/max_acceleration_rads_per_secor the arm driver's defaults). - Applies to the arm path only. The gripper track is driven by a parallel wall-clock ticker aligned to the recording frequency and is not affected by interpolation.
Session files are JSON written to $VIAM_MODULE_DATA/<session>.json. The top-level fields are:
| Field | Type | Description |
|---|---|---|
session |
string | Session name |
frequency_hz |
number | Recording sample rate; also used as the gripper ticker rate during playback |
recorded_at |
string | RFC 3339 UTC timestamp of when the session was saved |
joint_count |
number | Number of joints per frame |
frames |
array of arrays | Arm joint positions; each element is a []float64 of length joint_count |
has_gripper |
bool | true when gripper data was recorded (omitted / falsy in older files) |
gripper_position_key |
string | The DoCommand key used during recording (omitted in older files) |
gripper_positions |
array of numbers | Gripper positions parallel to frames; one entry per frame (omitted in older files) |
Older session files that predate gripper support load correctly — the absence of has_gripper, gripper_position_key, and gripper_positions is treated as no gripper data, and playback proceeds arm-only.
These steps verify the module on a real or simulated arm connected to a Viam machine. Use the Control tab in the Viam app or the viam machine part run CLI to send DoCommands.
Prerequisites: the component is configured and the machine is online. The arm named in attributes.arm must be present and reachable. If validating gripper support, the gripper must also be configured and reachable, listed in both attributes.gripper and depends_on.
-
Confirm
Readings()returns live joints. Open the Control tab, find therecordersensor, and click Get Readings. Verifystateis"idle"andjointscontains the expected number of joint positions. -
Record a session. In the DoCommand panel, send:
{"command": "start_recording", "session": "test1"}Jog the arm to several positions (via the arm's own control panel or by physically moving a back-drivable arm). Then send:
{"command": "stop_recording"}Verify the response shows a non-zero
frame_count. Optionally inspect$VIAM_MODULE_DATA/test1.jsonon the machine to confirm the file was written. -
List sessions.
{"command": "list_sessions"}Confirm
"test1"appears in the response. -
Move the arm away from the recorded start position. Use the arm's control panel to jog it to a different pose so the safe-entry move is observable.
-
Replay the session.
{"command": "play", "session": "test1"}Observe the arm move to the first recorded frame (safe-entry), then execute a single smooth blended motion through the remaining frames. During playback,
Readings()showsstate: "playing". After completion,statereturns to"idle". -
Stop playback early (optional). While playback is running, send:
{"command": "stop_playback"}Confirm the arm stops promptly and
Readings()returns tostate: "idle". -
Delete the session.
{"command": "delete_session", "session": "test1"}Confirm
list_sessionsno longer includes"test1".
These steps assume the module is configured with a gripper attribute pointing to a gripper component that implements get_position and set_position via DoCommand.
-
Confirm gripper appears in
Readings(). Click Get Readings and verifyhas_gripperistrueandgripper_positioncontains a numeric value. Ifgripper_errorappears instead, check that the gripper component is reachable and implements the expected DoCommand keys. -
Record a session with gripper.
{"command": "start_recording", "session": "grip-test"}Move the arm and open/close the gripper to several positions. Then:
{"command": "stop_recording"}Inspect
$VIAM_MODULE_DATA/grip-test.jsonand confirm the file contains"has_gripper": trueand agripper_positionsarray of the same length asframes. -
Move to a different configuration. Jog the arm away from the recorded start and set the gripper to a different position so that the safe-entry move is visible on both.
-
Replay the session.
{"command": "play", "session": "grip-test"}Observe the arm and gripper both move to their first recorded positions concurrently (safe entry). Then watch the arm execute its smooth blended motion while the gripper steps through its recorded positions. Check
Readings()during playback —gripper_positionshould update as playback proceeds. -
Verify no errors. After playback completes, confirm
Readings()does not containlast_error. If it does, the error message describes which phase failed.
You can also drive DoCommands from the terminal using viam machine part run:
viam machine part run \
--machine <machine-id> \
--part <part-id> \
--component recorder \
do-command '{"command":"list_sessions"}'Replace <machine-id>, <part-id>, and recorder with your machine's values.