Project 3 — Forward Kinematics¶
Overview¶
Build the part of a ROS-style robot software stack that answers the question "where is every part of my robot right now?" Your runtime receives a robot as the text of a real URDF file, builds the kinematic tree it describes, and turns the robot's joint positions into the pose of every link: forward kinematics (FK).
As in ROS, the work is split across a few small nodes. A parameter server holds the robot's
description. A robot_state_publisher reads that description, listens to /joint_states,
and publishes every joint's parent-to-child transform on /tf. A
robot_world_state_publisher composes those transforms, together with the robot's pose in
the world, into every link's pose in one global frame on /xform_world. Two more nodes are
recommended so your robot can move on its own: a joint_state_publisher that servos the joints
toward setpoints, and a finite_state_machine that runs a choreography of setpoints. You use
those two to record your portfolio video.
It all speaks the same ROS-like publish/subscribe protocol your Project 1 and 2 runtimes do, on
the same 127.0.0.1:9095 TCP/JSON gateway: newline-delimited JSON over a plain TCP socket, not
HTTP and not WebSocket. Bring your own Project 1 middleware and gateway.
The starters do not include one.
Do not use a kinematics, transform, or URDF-model library. Implement the rotation math, the tree traversal, and the transform composition yourself. A submission that includes such a library may not receive credit and may not be eligible for the mutation challenge. The one piece of plumbing the course supplies is an XML parser: each starter vendors a small parser (or, for Python, uses the standard library's), which you may use or replace. Parsing XML is not the learning objective; interpreting URDF is. You may use Python, C, C++, or Rust, and internal process/thread topology is your choice, as in Projects 1 and 2.
Learning goals¶
- Read a real robot description (URDF) and build the kinematic tree it describes: find the root, connect parents to children, and reject descriptions that are not valid robots.
- Implement 3D rigid-body transforms: fixed-axis roll-pitch-yaw, axis-angle rotation, unit quaternions, and homogeneous transform composition.
- Compute forward kinematics by traversing the tree from the root, composing each joint's
T_origin · T_motion(q)down every branch (the matrix-stack traversal from lecture). - Split a robot system into cooperating nodes the way ROS does: a parameter server, a
robot_state_publisher(/joint_statesin,/tfout), and a world-state publisher. - Drive a robot through a choreography with a joint servo and a finite state machine, and show it off in a portfolio video.
- Reuse a publish/subscribe transport across a third course assignment: the same protocol, a new domain.
System architecture¶
External clients (Autograder, your own test scripts and viewer)
|
TCP / JSON
|
rosbridge-style gateway <- your Project 1 gateway
|
publish / subscribe system <- your Project 1 middleware
|
param_server (/param_server/set_param, /param_server/get_param)
| robot_description
v
robot_state_publisher <-- /joint_states -- joint_state_publisher (recommended)
| /tf: every joint, parent -> child ^
v | /joint_trajectory
robot_world_state_publisher <-- /global_pose finite_state_machine (recommended)
|
v
/xform_world: every link in global_frame, as a 4x4 matrix
| Node | Required | Started by | Job |
|---|---|---|---|
param_server |
yes, graded | make run, make demo |
Stores named parameters, including robot_description (the URDF text). |
robot_state_publisher |
yes, graded | make run, make demo |
Reads robot_description, reports a description status, applies /joint_states, publishes /tf. |
robot_world_state_publisher |
yes, graded | make run, make demo |
Composes /tf and /global_pose into /xform_world. |
joint_state_publisher |
recommended, not graded | make demo only |
Servos the joints toward /joint_trajectory setpoints and publishes /joint_states. |
finite_state_machine |
recommended, not graded | make demo only |
Runs a choreography of joint setpoints and publishes /joint_trajectory. |
The transport is the rosbridge TCP/JSON protocol, reused unchanged from Projects 1 and 2, with one practical consequence spelled out in the line-size box below. From outside, your runtime must behave like the node graph above, reachable through the gateway. Whether that is one process or several is up to you.
The exact contract is in two specs. This handout explains them; if the two ever seem to disagree, the specs win:
spec/FK_API.md: the node graph, the parameter server's services,robot_descriptionand the description status,/joint_states,/tf,/global_pose,/xform_world, the message shapes and accuracy tolerances, whatmake runandmake demostart, and the recommended nodes.spec/ROBOT_DESCRIPTION.md: which parts of a URDF are read, the XML features you must accept, the kinematic conventions, and the validity rules.
Starter projects¶
Each starter (starter/{python,c,cpp,rust}) is a placeholder, as in Project 2: a
Makefile (build, run, demo, clean), a main program that prints a TODO line and
idles, and a small working example of reading a URDF with that language's supplied XML parser:
| Language | Supplied XML parser | Example |
|---|---|---|
| Python | standard library xml.etree.ElementTree |
src/urdf_example.py |
| C | ezxml 0.8.6 (vendored in third_party/ezxml/) |
src/urdf_example.c |
| C++ | pugixml 1.14 (vendored in third_party/pugixml/) |
src/urdf_example.cpp |
| Rust | roxmltree 0.20.0 (vendored in vendor/roxmltree/) |
print_links in src/main.rs |
The starters contain no middleware and no nodes. Bring your Project 1 hub, rosbridge
gateway, and node client, write the nodes above, and extend the Makefile so make build
compiles everything and make run and make demo start what "Submission, building, and
running" lists. How you organize files, classes, and processes is up to you. See
starter/README.md.
The student kit¶
This handout, the specs it links to, the four starters, the Fetch robot described below, and a
submission script are available pre-packaged as the student kit: a bundle with no other
course-repo material, distributed by course staff. Its README.md lists what is inside.
The kit includes one real robot to try your runtime on: the Fetch mobile manipulator,
robots/fetch/fetch.urdf, redistributed unmodified. The Fetch robot description is copyright
Fetch Robotics, Inc., from fetch_ros, and is
licensed CC BY-NC-SA 4.0 (non-commercial use only); see robots/fetch/LICENSE.md for the
full attribution and terms, and keep that license with the file if you share it.
Submission, building, and running¶
Submit a project root with a top-level Makefile providing:
and, recommended for your portfolio video, make demo.
Choose one supported implementation language, as in Projects 1 and 2: your
submission.tar.gz extracts to one project root whose direct contents include that submission's
Makefile. There is no make map target for Project 3. If you have the student kit,
./make_submission.sh <c|cpp|python|rust> at its root builds a correctly shaped
submission.tar.gz from your completed starter directory; see the kit's README.md.
make buildis noninteractive, repeatable, and offline: the grading environment has no network. It must finish within the setup time limit (180 s, including extraction). Use only the course image's toolchain and libraries (the same ones Project 1 allowed) plus source you include in your submission, such as the vendored XML parser.make runlaunches your runtime in the foreground, exposing127.0.0.1:9095: your gateway,param_server,robot_state_publisher, androbot_world_state_publisher. It needs no files, environment variables, or command-line arguments, and it starts with no robot loaded: every robot arrives as aset_paramofrobot_description. Your runtime must be ready within 10 seconds ofmake runstarting, and "ready" means the whole pipeline works: a small valid robot set through the parameter server is accepted and shows up on/tfand/xform_world(seespec/FK_API.md, "Runtime and Make ABI").make runmust not startjoint_state_publisher,finite_state_machine, or anything else that publishes/joint_states,/joint_trajectory, or/global_pose, or that setsrobot_description. During grading, those come only from outside your runtime.make demostarts everythingmake rundoes, plusjoint_state_publisherandfinite_state_machine.make demois not graded.make cleanremoves generated artifacts.
Autograder.io¶
Grading is black-box using Autograder.io, exactly like Projects 1 and 2: it extracts your
project, runs the Make targets in the offline course environment, launches your runtime with
make run, and connects externally to 127.0.0.1:9095 using the documented TCP/JSON protocol.
It loads robots through /param_server/set_param, publishes /joint_states and /global_pose,
and reads the description status, /tf, and /xform_world. It never reads your files, logs, or
output, and it never starts make demo. No frontend or visualization is part of grading.
There are two hosted projects: the checkpoint and the final project. Each test is all-or-nothing and has a time limit, so keep startup fast.
Do not hard-code version numbers. Other robots may already have been loaded into your
runtime before the one you care about. A version means something only relative to the version
set_param returned.
Request line size¶
Every hop in your runtime MUST carry lines of at least 1 MiB (checkpoint and final).
A URDF travels as one JSON string, inside one line: the
set_paramrequest line, and again theget_paramreply thatrobot_state_publisherreads back. Real robots are big: the PR2's request line is about 110 KiB, and a description can come close to the 1 MiB bound. The Project 1 rosbridge protocol already permits lines up to 4 MiB, but many Project 1 gateways buffer much less.A fixed 64 KiB line buffer will fail. The fix: read lines until the newline, growing your buffer as needed, instead of reading into a fixed-size array and treating whatever fits as a line.
Check every hop a big message takes: the gateway, the hub, and every node-to-node connection, in both directions.
Project checkpoint — Zero Configuration FK Transforms¶
Project checkpoint: complete the parameter server, description handling, and
zero-configuration FK by Mon Oct 12, 2026. At zero configuration every joint is at
position 0, so no /joint_states are involved.
The checkpoint is a separate hosted project. It grades:
- The parameter server:
set_paramandget_param, per-name versions, and rejecting a request with no validname. - Description handling:
robot_state_publisherpicks up each newrobot_description, validates it againstspec/ROBOT_DESCRIPTION.md, and reports the result in/robot_state_publisher/description_status. A rejected description leaves the previous robot in effect; an accepted one replaces it completely. Real-world XML syntax must load. - Zero-configuration FK on
/tfand/xform_world: with every joint at0,/tfcarries every joint'sT_origin, and/xform_worldcarries every link's pose inglobal_frame(with no/global_pose, the root sits at the origin). This includes real robot URDFs sent unmodified, such as the PR2, whose request lines are well over 64 KiB, so fix your line buffer now.
All three graded nodes have to work for the checkpoint, because a runtime is ready only when a
robot set through the parameter server reaches /tf and /xform_world.
What the checkpoint does not grade (the final project does):
- subscribing to
/joint_states, and joint motionT_motion(q)(atq = 0it is the identity for every joint type, so each joint's transform is exactly itsT_origin); /global_pose;/tfand/xform_worldtiming: the publishing rate, and serving a subscriber that joins late;- a description near the 1 MiB line bound.
You still have to parse and validate every joint's axis and <limit> for the checkpoint
(a malformed axis, or a revolute or prismatic joint with no <limit>, must be rejected), even
though nothing moves yet.
The checkpoint's features are graded again in the final project, so this work counts toward both.
The parameter server and robot_description¶
Both parameter-server services use the standard rosbridge envelope: every response is
{"values": {...}, "result": <bool>, "status": <string>}.
/param_server/set_param, request{"name": "<name>", "value": <any JSON>}: store the value under the name and reply{"version": <n>}. Each name has its own version, starting at0; the first set makes it1, and every set adds one. A missing or non-stringnameis rejected (result: false, non-emptystatus). The server never interprets the value: a broken URDF is still stored, and judging it isrobot_state_publisher's job./param_server/get_param, request{"name": "<name>"}: reply{"value": <stored value>, "version": <n>}, orresult: falsewith{"value": null, "version": 0}for a name that was never set.
robot_state_publisher reads robot_description through get_param (polling is fine) and
processes every new version it sees. After a set_param of robot_description returns version
V, it must report a status for some version >= V within 2 seconds. It reports by setting
the parameter /robot_state_publisher/description_status:
versionis therobot_descriptionversion just processed, andacceptedsays whether it loaded.erroris""when accepted and a non-empty reason when not.loaded_versionandroot_linkdescribe the robot now in effect. After a rejection they still describe the previous robot (0and""if there is none).- Write the status only after an accepted robot is fully in effect, so every
/tfmessage published after the status reflects it. - Validate the whole description before touching the current robot. A rejected description
leaves the previous robot, its joint state, and your
/tfexactly as they were.
Robot descriptions (URDF)¶
A description is a real, flat (already xacro-expanded) URDF, exactly as a robot ships it. You
parse it as it arrives; you never expand xacro yourself, and every description you are sent is
already flat. Links carry <visual>, <collision>, and <inertial> elements. We encourage you to
write a parser for the full URDF, since later projects use <collision> (for motion planning)
and <inertial> (for simulation). In Project 3, though, only a link's first <visual> is read:
whatever <collision> and <inertial> contain must never cause a rejection. The full rules are in
spec/ROBOT_DESCRIPTION.md; the essentials:
- Only the direct children of
<robot>count.<link>and<joint>elements nested anywhere else, for example inside<gazebo>or<transmission>blocks, are not links or joints of the robot. Real URDFs, including the PR2's, contain such hollow<joint>elements. Iterate over<robot>'s children; never search the whole document for<joint>. - What is read: each joint's
type,<parent>,<child>,<origin>,<axis>, and<limit>, and each link's first<visual>. Everything else is ignored: collisions, inertials, materials,<gazebo>,<transmission>,<mimic>,<safety_controller>, unknown attributes, and so on. - Defaults: a missing
<origin>,xyz, orrpymeans zeros; a missing<axis>means1 0 0, as in URDF.nameon<robot>may be absent. - Joint types:
revolute,continuous,prismatic, andfixed. Anything else, including URDF'sfloatingandplanar, is rejected. Arevoluteorprismaticjoint must have a<limit>. The limit is informational: FK never clamps to it. - Visuals: a link's first
<visual>, if present, must hold a validbox,cylinder,sphere, ormesh. It never changes FK; it exists so a viewer, such as the one you build for your portfolio, can draw the robot. - Numbers:
xyz,rpy, andaxis xyzhold exactly three decimal numbers separated by any XML whitespace, in any ordinary spelling (1,-0.5,.25,3.,1.5e-1). A wrong count, or a token that is not a number, is rejected. - Real-world XML: an XML declaration, comments (including comments that contain markup),
xmlns:xacro="..."on<robot>, single or double quotes, entity and character references, CDATA, paired or self-closing tags, CRLF line endings, and joints that appear before their links. Names are compared after entity decoding, soa&bis the linka&b. - Validity: the spec's numbered "Validity" list is the authority. A description that passes every check must be accepted. A few inputs with no well-defined FK (duplicate names, cycles, zero-length axes on movable joints) are UNSPECIFIED: do anything you like with them except crash or hang.
Kinematics¶
Use unit quaternions for 3D rotation. This is part of the assignment, and /tf and
/global_pose already carry rotations as quaternions. A submission built on another
representation, such as Denavit-Hartenberg parameters, may not receive credit and may not
be eligible for the mutation challenge. Every value you publish follows the URDF conventions
below.
rpyis fixed-axis roll-pitch-yaw:R = Rz(yaw) · Ry(pitch) · Rx(roll).- Joint transform:
T_joint(q) = T_origin · T_motion(q), withT_origin = Trans(xyz) · Rot(rpy)in the parent link's frame. The motion happens after the origin, in the joint's own frame, so a revolute joint pivots about its origin. T_motion(q):revoluteandcontinuousrotate byqradians aboutaxisnormalized to unit length (axes such as0 0 2or1 1 0are legal);prismatictranslates byq · axis, with the axis as written, not normalized: an axis of2 0 0atq = 0.7moves1.4m (real URDFs use unit axes, where this makes no difference);fixedis the identity.- The root is the unique link that is no joint's child. The description does not name it,
and it is often not the first
<link>in the file. - FK: a link's pose relative to the root is the product of the
T_jointedges along the path from the root to that link, composed parent first:T_root_child = T_root_parent · T_joint. - Mimic joints are driven by their own names, like any other joint.
Topics¶
/joint_states (robot_state_publisher subscribes)¶
sensor_msgs/JointState:
{"header": {"stamp": {...}, "frame_id": ""}, "name": [...], "position": [...], "velocity": [...], "effort": [...]}.
- Each message replaces the joint state. Joint
name[i]is atposition[i], and a movable joint the latest message does not name is at0. - Ignore, don't fail: names that are not movable joints of the loaded robot (unknown names, and fixed joints) are ignored; the rest of the message still applies.
velocityandeffortmay be absent or empty; ignore them.- The stamp: the latest message's
header.stampbecomes the stamp of every/tfentry, verbatim. Do not replace it with your own clock. Before any message, the stamp is{"sec": 0, "nanosec": 0}. - No clamping: use positions exactly as given.
- Promptly: a message must show up on
/tfwithin a few seconds at most, and the same message may arrive more than once.
/tf (robot_state_publisher publishes)¶
{"transforms": [ <TransformStamped>, ... ]}, where a TransformStamped is the ROS
geometry_msgs/TransformStamped shape:
{"header": {"stamp": {"sec": 0, "nanosec": 0}, "frame_id": "<parent link>"},
"child_frame_id": "<child link>",
"transform": {"translation": {"x": 0.0, "y": 0.0, "z": 0.0},
"rotation": {"x": 0.0, "y": 0.0, "z": 0.0, "w": 1.0}}}
- While a robot is loaded, publish
/tfat least 5 times per second, whether or not anything changed. Do not publish it while no robot is loaded. - Every message is complete: exactly one entry per joint, including
fixedjoints, from the joint's parent link to its child link, holdingT_joint(q)at the current joint state. Every entry carries the same stamp. - A client that subscribes late must receive a complete, current message within one publishing period.
/global_pose and /xform_world (robot_world_state_publisher)¶
/global_pose (subscribe) is a geometry_msgs/Pose, the pose of the robot's root link in
global_frame: {"position": {"x", "y", "z"}, "orientation": {"x", "y", "z", "w"}}. Until one
arrives the global pose is the identity; after that, use the most recent one.
/xform_world (publish) is {"transforms": [ <MatrixTransform>, ... ]}:
{"header": {"stamp": {"sec": 0, "nanosec": 0}, "frame_id": "global_frame"},
"child_frame_id": "<link name>",
"matrix": [m00, m10, m20, m30, m01, m11, m21, m31, m02, m12, m22, m32, m03, m13, m23, m33]}
matrixis the link's 4x4 poseT_global_link = T_global_root · T_root_link, flattened in column-major order (matrix[4*c + r] = M[r][c], so the translation ismatrix[12..14]).- Compute from the most recent
/tfmessage alone: find its root (the frame that is some entry's parent and no entry's child), and compose its edges down to every link. Do not merge in edges from older messages, or a previous robot's links linger after a reload. - Every message is complete: one entry for every link, including the root, each stamped
with the stamp of the
/tfmessage it came from. Publish at least 5 times per second once you have received a/tf, and serve late subscribers within one period, as for/tf. global_frameis z-up, like every URDF frame. Do not add a rendering correction for a 3D viewer; the viewer applies its own.
Accuracy¶
Every translation must be within 1e-4 m of the exact value, and every rotation within
1e-4 rad of it (the angle between the two rotation matrices). Quaternions must have norm
within 1e-4 of 1, and their sign does not matter; /xform_world rotation blocks must be
orthonormal within 1e-4 with determinant +1. Printing numbers with 6 or more decimal places
is precise enough. Every numeric field must be a JSON number.
Recommended nodes: joint servo and choreography¶
joint_state_publisher and finite_state_machine are recommended, not graded: nothing
launches, calls, or listens for them during grading. They are how your robot moves on its own
in make demo, which you need for your portfolio video, and later projects build on them.
spec/FK_API.md, "Recommended nodes", describes the course reference's design; your own design
is fine.
joint_state_publisherreadsrobot_description, subscribes/joint_trajectory(trajectory_msgs/JointTrajectory; the last point is the setpoint for the named joints), and publishes/joint_statesfor every movable joint at about 10 Hz, moving each joint toward its setpoint. The reference uses a proportional servo with gain 50 per second, clamped to the joint's limits, and offers/joint_state_publisher/reset.finite_state_machineruns a choreography: a table of states, each a joint-space setpoint with named transitions. On entering a state it publishes the setpoint on/joint_trajectory; when/joint_statesshows every named joint within the state'sepsilon, it follows the state'son_arrivaltransition. The reference offers/fsm/update_logic,/fsm/get_logic,/fsm/set_running,/fsm/fire_transition,/fsm/save_to_file,/fsm/load_from_file, and/fsm/list_saved, and publishes/fsm/status.
FSM files. A saved choreography lives in fsm/<identifier>.json, where the identifier
matches [A-Za-z0-9_-]{1,64} (check it, so a request cannot write outside fsm/). The FSM
document format, with a worked example, is in spec/FK_API.md, "finite_state_machine".
Portfolio deliverable¶
Add a page to your course portfolio website with a video of your robot performing an FSM
choreography: run make demo, load a robot and a choreography of your own, and record the
robot moving. No viewer is supplied: build your own, as an external client of your runtime that
draws each link from /xform_world (the matrices are column-major, the layout three.js's
Matrix4.fromArray reads, so a small three.js page is one option). Briefly describe the robot,
the choreography, and how you drew it on the page. If you show the kit's Fetch robot, credit it
on the page as "Fetch robot description © Fetch Robotics, Inc., CC BY-NC-SA 4.0".
The video (15%) and the portfolio page (10%) are required. Autograder.io does not grade them; course staff do. They are due [portfolio due date TBD].
Testing¶
Test the way Autograder.io does: as an external TCP/JSON client of your running runtime on
127.0.0.1:9095. A few lines of Python are enough to load a robot, wait for its status, and read
one /xform_world message:
import itertools, json, socket, sys, time
sock = socket.create_connection(("127.0.0.1", 9095))
stream = sock.makefile("rwb")
ids = itertools.count()
def send(message):
stream.write((json.dumps(message) + "\n").encode())
stream.flush()
def call(service, args):
call_id = f"call-{next(ids)}"
send({"op": "call_service", "service": service, "id": call_id, "args": args})
for line in stream: # skip status and publish traffic until our reply arrives
reply = json.loads(line)
if reply.get("op") == "service_response" and reply.get("id") == call_id:
return reply
with open(sys.argv[1], encoding="utf-8") as f: # any .urdf file
reply = call("/param_server/set_param", {"name": "robot_description", "value": f.read()})
version = reply["values"]["version"]
while True: # wait for robot_state_publisher to process our version
status = call("/param_server/get_param",
{"name": "/robot_state_publisher/description_status"})["values"]["value"]
if status and status["version"] >= version:
break
time.sleep(0.1)
print(status)
send({"op": "subscribe", "topic": "/xform_world", "type": "xform_world/MatrixTransformArray"})
for line in stream:
message = json.loads(line)
if message.get("op") == "publish" and message.get("topic") == "/xform_world":
for entry in message["msg"]["transforms"]:
print(entry["child_frame_id"], entry["matrix"][12:15])
break
To move joints, advertise /joint_states (sensor_msgs/JointState) and publish a message
naming them; to watch /tf, subscribe to it the same way.
Things worth testing yourself:
- Hand-checkable robots. The example in
spec/ROBOT_DESCRIPTION.md, and small robots of your own: a single translated link, a singlerpyrotation about each axis, a two-link planar arm whose tool pose you can compute on paper, a branching tree, a root that is not the first link. - Every validity rule. Write one broken description per rule, check that each is rejected in
the description status, and check that
/tfand/xform_worldstill show the previous robot. - Real robots. Load the kit's
robots/fetch/fetch.urdf, and the flat URDF of another real robot such as the PR2 (make sure it is expanded URDF, not.xacro), unmodified. Their request lines are well over 64 KiB, so they also test your line-size fix. - Joint motion (final): revolute joints with non-unit axes, prismatic joints with non-unit axes under a rotated origin, continuous joints past ±π, messages that name only some joints, and names your robot does not have.
- World pose (final): publish a
/global_poseand check that every/xform_worldmatrix moves with it, root included. - Topic behavior (final): check the publishing rate with nothing changing, subscribe late and check the first message is complete, and reload a different robot and check that no old link remains.
Common pitfalls¶
- A fixed-size line buffer in the gateway, the hub, or any node connection. Read until the newline.
- Searching the whole document for
<joint>. Only direct children of<robot>count. - Assuming the first
<link>is the root. Compute it: the one link that is no joint's child. - Wrong
rpyorder or a transposed rotation. It isRz(yaw) · Ry(pitch) · Rx(roll), fixed axes. Test each axis alone, then a combination, against a hand computation. - Composing in the wrong order. Child poses are
parent · T_joint, neverT_joint · parent; and within a joint,T_origin · T_motion, neverT_motion · T_origin. - Axis handling. Normalize revolute and continuous axes, but not prismatic ones. A
missing
<axis>is1 0 0, not0 0 1. - Dropping fixed joints. They belong in
/tf, and their child links in/xform_world. - Forgetting the root in
/xform_world. It needs its own entry, too. - Partial loads. Build the new robot completely, validate it, and only then swap it in.
- Reporting the status too early. Set
description_statusonly once the new robot is what/tfpublishes. - Merging
/tfmessages inrobot_world_state_publisher. Use only the latest one, or a previous robot's links linger. - Keeping joints the latest
/joint_statesmessage does not name. They go back to0. - Using your own clock for the stamp. Copy the latest
/joint_statesstamp verbatim. - Clamping to
<limit>, or wrapping continuous joints, in FK. Use positions exactly as given. - Hard-coding version numbers. Compare against the version
set_paramreturned. - Publishing only when something changes.
/tfand/xform_worldmust keep coming at 5 Hz or more with nothing changing, and every message must be complete. - Writing JSON by hand. Link and joint names can contain
",&,<, and other characters that must be escaped in JSON output. Use your language's JSON library, and never emitNaNorInfinity, which are not JSON numbers. - XML parser error checks. In C, ezxml is lenient: check
ezxml_error()after parsing so text that is not well-formed XML is still rejected, and pass it a writable copy of the string. In C++, check thepugi::xml_parse_result. - Converting a rotation matrix to a quaternion with the trace formula only. It divides by
a number near zero for rotations near 180°. Use the four-branch version (pick the largest of
w,x,y,zfirst), or build the quaternion directly from the rpy and axis-angle parts. - Not setting
SO_REUSEADDRon your gateway's listening socket. Your runtime is started again and again on port 9095. If your side closed a connection first, the old socket lingers in TIME_WAIT for up to a minute, and a plainbindthen fails with "address already in use". Rust'sTcpListenersets the option for you; in Python setallow_reuse_address = True(socketserver) orsetsockopt(SOL_SOCKET, SO_REUSEADDR, 1); in C and C++ callsetsockoptbeforebind. - A slow start. The whole pipeline must be ready within 10 s of
make run, so do not do heavy work at startup or poll with long sleeps.
Grading¶
| Category | Weight | Graded by |
|---|---|---|
| A. Parameter server and description handling | 15% | Autograder.io |
| B. Zero-configuration FK | 25% | Autograder.io |
| C. Joint-state FK | 25% | Autograder.io |
| D. Topic behavior and global pose | 10% | Autograder.io |
| E. FSM choreography video | 15% | course staff |
| F. Project portfolio page | 10% | course staff |
| Total | 100% |
The checkpoint grades categories A and B, except B's near-1 MiB description, which is graded only in the final project.
A. Parameter server and description handling covers set_param and get_param with
per-name versions; the description status for every load; rejecting every structural and value
error in the validity rules while accepting valid descriptions that rely on defaults, unknown
elements, and every legal number spelling; a rejected load leaving the previous robot in
effect; a reload replacing, not merging, the previous robot; and the same robots written in
real-world XML styles.
B. Zero-configuration FK checks /tf and /xform_world with every joint at 0:
translation-only and rpy origins, chains and branching trees with fixed joints, real robot
URDFs sent unmodified, and a description near the 1 MiB line bound.
C. Joint-state FK publishes /joint_states and checks /tf and /xform_world against the
expected FK: revolute joints about axis-aligned and general (non-unit) axes, continuous joints,
prismatic joints, origin-then-motion order, stamps reported verbatim, each message replacing
the joint state while unknown names are ignored, and real robots at configurations within their
joint limits.
D. Topic behavior and global pose checks that /tf matches the current joint state, that
complete messages keep arriving at the required rate with no new trigger, that a late subscriber
is served, and that /global_pose is composed into every /xform_world matrix.
E and F, the choreography video and the portfolio page around it, are graded by course staff, not by Autograder.io. See "Portfolio deliverable".
For reference, see the archived 2025 version of this project (KinEval/JavaScript-based, pinned to the Winter 2025 commit for archival purposes).