Add a robot model
On this page
This guide creates a new robot description in robot_description/robots/<model_id>/, checks it, and pins it from an rt-core machine config so a cell can run it and OLP can plan for it. Read Robot description and coordinate frames first; every field is in Robot description files.
The checks along the way:
python3 robot_description/tools/manifest.py --check # manifest and meshes
(cd robot_description/go && go run ./cmd/identity ../robots/<model_id>)
(cd rt-core && build/rtctl compile --config config/machines/<your-machine>.json --out build/config)Before you start#
- Pick a model id: ASCII letters, digits and underscores, for example
rosie_1600_v1. It is the directory name, the URDF robot name, themodel_idin every file, and the cell id that programs carry. Changing it later means a new model. - Have the robot's kinematics, joint limits and meshes, and its drive gearing, encoder scale and joint directions from your mechanical design.
- Build
rt-core(make -C rt-core control) and install the weld planner'smotionenvironment if you will fit spheres (see Run the weld planner).
Everything you register becomes part of the model's identity, and changing it later invalidates every plan made against it. That is intended: finish the files before you pin them.
1. Create the directory#
mkdir -p robot_description/robots/rosie_1600_v1/meshesPut the link meshes in meshes/. Keep CAD exports, the scripts that produced the files and measurement reports in a provenance/ folder. They are not registered, so editing them never changes the identity.
2. Write robot.urdf and robot.srdf#
- Set
<robot name="rosie_1600_v1">, equal to the directory name. - Give every movable joint a bounded
<limit lower upper velocity>, in rad and rad/s. rt-core takes each robot-bound axis's travel and maximum velocity from here, and refuses a joint without bounded limits. - Reference meshes as
package://<anything>/meshes/<file>. - End the arm in a
tool0link at the tool centre point, as the Rosie models do, and put the torch's electrode along +x of that frame. - In
robot.srdf, define amanipulatorgroup with the planned joints and ahomegroup state.
Name the arm links link_1 to link_6 if you can. The sphere-fitting tool assumes those names.
The sphere tool in urdf/v1/sphere_tool can also help find joint limits by driving each joint to where the arm stops. See step 5.
3. Write config.json#
Copy a sibling's config.json and replace every value with this robot's. State only what URDF cannot: do not repeat a URDF number.
{
"schema": "rosie.robot-config.v1",
"model_id": "rosie_1600_v1",
"frames": { "base": "world", "tool": "tool0", "work": "world" },
"kinematic_correction_caps": { "xyz_m": 0.002, "rpy_rad": 0.017453292519943295 },
"planning": {
"speed_ceiling_rad_s": 1.5,
"joints": { "J1": { "velocity_rad_s": 1.0, "acceleration_rad_s2": 2.0 } }
},
"axes": { "driven": ["J1", "J2", "J3", "J4", "J5", "J6"], "held": {} },
"reset_pose": { "J1": 0.0 },
"torch": { "tool_frame": "tool0", "electrode_axis": [1.0, 0.0, 0.0] }
}The loader refuses:
- a
schemaormodel_idthat does not match - a planning
velocity_rad_sabove that joint's URDFvelocity, or aspeed_ceiling_rad_sabove the fastest URDF joint - a non-positive rate, a negative cap, or a non-finite held value
- any joint in
planning,axesorreset_posethat is not a revolute URDF joint - unknown fields
Give every joint an acceleration_rad_s2. rt-core uses it for trajectories and jog on each robot-bound axis and refuses to compile without it (robot_acceleration_required).
For a positioner, add frames.work_surface so the work frame sits on the table surface rather than at the link origin, and hold any axis the planner should not move. See Cells, machines and positioners for the Rosie 1400 example.
4. Write rtcore_definition.json#
This file tells rt-core how each joint maps to a drive. Start from the annotated template rt-core/config/templates/robot.json: every field there has a _doc sibling with its unit, range and default. Remove the _doc fields and fill in your robot.
Note
The gear ratio, encoder counts, joint signs and encoder battery facts come from your robot's mechanical design and drives. These docs describe the fields but give no values. Every numeric fact needs a _source ("cited: …") or _unverified ("unverified: …") sibling, or compile refuses it.
Checklist:
schemaisrosie.robot-definition.v1, androbot_idequals the directory name.calibrationis{"identity": "home_calibration", "note": "…"}. Home offsets are never stored here.defaultsnames the drive config (drive_profile),coordinate_evidence_modeandhome_policy(requiredunless you have a reason).joints[]has one entry per movable URDF joint, at most 16:name,kind(arm,positionerorexternal),urdf_joint,cartesian_participates,gear_ratio,motor_encoder_counts_per_rev,sign, and, for joints on a supported absolute-encoder drive,encoder_batterywith its source.armjoints must map to a URDF joint. Only anexternaljoint with no URDF joint may declaremechanical_travel.
A robot-bound machine cannot override any of these, so get them right here.
5. Fit the collision spheres#
The weld planner refuses a model without spheres.json. Author a first model in the sphere tool, then refine it:
(cd urdf/v1/sphere_tool && npm ci && npm run build)
python3 urdf/v1/sphere_tool/serve.py 8795 rosie_1600_v1 # open http://127.0.0.1:8795/
# save the result as robot_description/robots/rosie_1600_v1/spheres.json
cd weld_planner/v1
pixi run -e motion python tools/fit_spheres.py --cell rosie_1600_v1
pixi run -e motion motion-spheresfit_spheres.py refits an existing spheres.json in place so every sphere sits inside its link, and stamps the URDF and mesh hashes it fitted against into provenance. By default it refits link_3 to link_6; pass --links to choose others. After a URDF edit that moves no mesh, --restamp updates the hashes and changes nothing else. motion-spheres reports which links are covered.
6. Register the files#
python3 robot_description/tools/manifest.py rosie_1600_v1
# manifest: rosie_1600_v1 <n> files sha256:<64 hex>
python3 robot_description/tools/manifest.py --checkThe manifest registers robot.urdf, robot.srdf, config.json, spheres.json, rtcore_definition.json and everything in meshes/. Do not add a machine_planning_calibration.json to the description: that file belongs to a cell. --check also fails if the URDF names a mesh that is not in meshes/.
Print the identity with the Go tool too; both must agree:
(cd robot_description/go && go run ./cmd/identity ../robots/rosie_1600_v1)7. Pin it from a machine config#
Create a machine config under rt-core/config/machines/ that pins the directory and its identity, and maps one axis per definition joint:
{
"schema_version": 1,
"backend": "live",
"cycle_ns": 1000000,
"robot": {
"robot_description": {
"path": "robot_description/robots/rosie_1600_v1",
"sha256": "sha256:<identity from step 6>",
"source": "cited: robot_description/robots/rosie_1600_v1"
}
},
"axes": [
{ "robot_joint": "J1", "slave_position": 0,
"limits": { "max_target_lead": 0.02, "following_error": 0.04, "following_error_timeout_ms": 100,
"completion_tolerance": 0.0002, "completion_timeout_ms": 500 } }
],
"max_cycle_lateness_ns": 20000000,
"runtime": { "cpu": 2, "priority": 90 }
}Robot-bound axes carry only robot_joint, slave_position and the tracking limits. They inherit travel, velocity, acceleration, gearing and Home policy from the description, and compile refuses any attempt to redeclare them. List a definition joint that has no drive on this machine in robot.absent_joints.
Compile it:
cd rt-core
build/rtctl compile --config config/machines/bench/rosie1600.json --out build/config| If compile says | Fix |
|---|---|
robot_description_mismatch: … hashes to X and the machine pins Y | Update sha256 to the current identity. |
robot_definition_unavailable | Register rtcore_definition.json in the manifest. |
robot_acceleration_required | Add acceleration_rad_s2 for that joint in config.json. |
robot_joint_missing | Add an axis for the joint, or list it in robot.absent_joints. |
robot_urdf_limits_required | Remove the limit, velocity or gearing field from the axis. |
Every reason is listed in robot compile refusals. Then deploy it as in Install rt-core on a cell host, and update every cell pin with the new configuration digest.
8. Plan against it#
OLP lists every description with a URDF, so the new model appears in its robot catalogue. The planner finds it by directory name. A program planned for it records the model id and identity, and Load refuses it on a cell with any other description (robot_cell_mismatch).
To try the model in simulation, write a "backend": "simulation" machine that pins it the same way. Home works on a simulated robot-bound machine only if the definition's drive profile supports Home on the simulated bus. The shipped Rosie 1400 simulation machine does not, which is why the dev stack's default simulated cell uses flat axes. See Run everything in simulation.
When the robot changes#
Edit the files, rerun manifest.py <model_id>, and update the identity in every machine config that pins the model. Recompile, redeploy and update the cell pins. Plans made against the old identity are refused and must be planned again. That is the point: the plan was for a different robot.