Shared interface definitions for robot arm control. waldoctl defines the contract between robot arm backends (hardware communication, motion planning) and frontend applications (control interfaces, scripting, visualization).
Named after Robert A. Heinlein's 1942 short story Waldo, in which the protagonist invents remote manipulator arms -- the origin of the real-world term "waldo" for teleoperated mechanical hands.
pip install "waldoctl @ git+https://github.com/Jepson2k/waldoctl.git"The single entry point for a backend. One object gives the frontend access to everything: joint configuration (limits, home position), tool definitions, forward/inverse kinematics, URDF paths for 3D rendering, capability flags, and factories for creating clients.
Async control interface spanning motion (moveJ, moveL, home), streaming position targets (servoJ, servoL), velocity jog (jogJ, jogL), queries, I/O, and synchronization. Async keeps operations like jogging, status streaming, and motion commands concurrent. For simple automation scripts where async/await would be unnecessary ceremony, backends also provide a synchronous client.
Control operations are @abstractmethod: every backend implements them. What varies by hardware is exposed as a has_* flag on Robot (has_force_torque, has_freedrive, has_collision_checking), and the handful of methods those gate have defaults that raise NotImplementedError.
Every method carries a @command(kind) marker and command_table() reads them back: MOTION and QUEUED calls enter the controller's queue and return an index a program may wait_command; SYSTEM calls apply at once and return 1/0/negative; CONTROL acts on the queue (stop and estop cancel it); QUERY reads state a plan knows; OBSERVATION reads live state only a running controller can answer; SYNC waits. A program runs unchanged against a live client or a preview because every wrapper that stands in for a client, the stepping wrapper, the path preview, a backend's dry run, reads its behaviour for a call from this table instead of its own list, and the conformance test refuses an unclassified method.
The offline client a program runs against for a preview. It takes the same calls as a live client and answers them the same way -- a queue index, a code, a refusal -- and gives two tick-indexed records for the program so far. plan() is the commanded record, the planner's answer, fast enough to run behind a keystroke; simulate() is the predicted one, the same commands through the backend's control loop and plant. A backend with no plant returns the plan from simulate() too. Both are a TickIndex on one row axis with a TickBlock per command, and following_error(commanded, predicted) is the gap between them, compared by command so a run that settles longer than its plan still lines up.
is_dry_run(client) tells a skill whether the client it was handed previews or drives; it resolves the protocol's members the way a call would, so a wrapper that forwards attribute access counts where isinstance would not.
A Shape (Box, Sphere, Cylinder, Capsule, Cone, Ellipsoid) is one thing in the robot's world, in metres and radians. What it is follows from what it declares: collision=False is a visual marker, a plain shape is a keep-out, and a shape carrying physics=Physical(...) is also a body in a backend's contact simulation -- a static fixture without mass, a free object with one. ShapeWorld is a backend's applied world as read back: the installation layer from its robot config, the program layer the last set_shapes applied, and floor_z_m, the installation floor the backend enforces and rests objects on. waldoctl.world is the one JSON codec for a saved world, a library object or an import/export document. ObjectTicks on a predicted record reports where a physical object went during a previewed program, and SceneHandle is a plugin's window into the host's 3D scene, including proposing shapes for the installation layer.
A ToolSpec describes an end-of-arm tool: TCP offset, 3D mesh descriptors for visualization, motion descriptors (linear jaws, rotary spindles) for animation, UI button configuration, process data channels, and named variants for swapping configurations (e.g. different jaw sets). The typed hierarchy (ToolSpec → GripperTool → PneumaticGripperTool / ElectricGripperTool) lets frontends render tool controls and animate tool parts generically without hard-coding knowledge of specific tools.
| Module | Contents |
|---|---|
robot |
Robot ABC -- identity, joints, tools, kinematics, lifecycle, client factories |
client |
RobotClient ABC -- async control interface |
dry_run |
DryRunClient protocol -- the offline client whose plan() and simulate() return the commanded and predicted records |
tools |
Tool hierarchy, mesh/motion descriptors, enums, ToolStatus |
joints |
Frozen dataclasses for joint configuration and limits |
status |
StatusBuffer protocol for real-time state, query result types |
recordings |
Immutable joint/tool observations, capture termination reasons and explicit gap/span inspection |
results |
IKResult protocol and its concrete dataclass |
shapes |
Shape kinds, Physical, ShapeWorld, the wire form and the reporting vocabulary |
world |
JSON codec for a ShapeWorld -- saved worlds, library entries, import/export |
scene |
SceneHandle protocol -- a plugin's window into the host's 3D scene |
dry_run_state |
PathSegment, ToolAction and the other dry-run records a host keeps |
ticks |
TickIndex, TickBlock, ObjectTicks, align_rows and following_error -- the records a dry run returns and how the two compare |
types |
Frame and Axis type aliases |
sync_tools |
Sync wrappers for async tool methods |
For guides on implementing a backend or building scripts, see the PAROL Web Commander documentation.
SetupSnapshot.tcp_calibrations stores named TcpCalibration values, tool/variant
bindings and measurement provenance. The setup codec validates one versioned
schema; snapshots and exports retain their fixed values. The pivot solve and
orientation teaching that produce a calibration belong to the host application.
Backends implement set_tcp_transform and tcp_transform: six values in
millimetres and intrinsic XYZ degrees, composed as T_registered_tool @ T_user.
Wait for the setter's returned command index before readback. The legacy
set_tcp_offset(x, y, z) clears user rotation; failed readback raises instead
of substituting a zero transform. Physical tool meshes and inertial frames
remain attached to their registered links.
Apache-2.0. See NOTICE for the attribution notices redistributors must preserve.
SetupSnapshot.signals maps names to immutable DigitalSignal values: backend,
input/output bank, zero-based channel, expected bank sizes, and active-high or
active-low polarity. Decoding refuses a different controller layout. The E-stop
status bit is excluded from the channel banks.
RobotClient.io(timeout=...) bounds the entire query and returns None without
a reply. write_io(..., timeout=...) bounds acceptance and raises TimeoutError
when it remains unconfirmed. Both retain their configured client timeouts when
omitted. Backends implementing this contract advertise io.digital; preview
clients additionally advertise execution.preview. SignalObservation and
SignalWaitResult carry logical levels, host receipt timestamps, and distinct
matched/timeout outcomes. The I/O skills live in waldo_commander.skills.
SetupSnapshot.cameras stores CameraCalibration values from waldoctl.camera,
with immutable pinhole intrinsics, measurement quality and provenance. A tool
camera binds its camera→TCP pose to the backend, tool variant and full TCP
transform. A fixed camera binds its pose to a named static frame and a snapshot
of that frame's WRF transform. validate checks current acquisition and setup
bindings; world_pose additionally resolves the camera to WRF, using an explicit
observed TCP pose for tool cameras. Both reject changed relevant bindings.
These helpers perform no capture, storage or robot I/O. Acquisition must supply
fresh observations and account for physical camera/lens/mount changes that
software cannot detect. Camera data is included in explicit setup snapshot
serialization and export.