Skip to content

STAR: act firmware commands out in the viewer (motion playback) - #1468

Open
stefangolas wants to merge 10 commits into
PyLabRobot:mainfrom
stefangolas:star-motion
Open

stefangolas wants to merge 10 commits into
PyLabRobot:mainfrom
stefangolas:star-motion

Conversation

@stefangolas

@stefangolas stefangolas commented Oct 3, 2026 •

Copy link
Copy Markdown
Contributor

STAR: act firmware commands out in the viewer (motion playback)

The viewer can play what a command does while the device does it. Each command a simulated STAR driver sends is read for the motion its drives make, sent to the pages, and the command is held until every page has played it; the model then records where the command ended, which is where the pages have just brought everything. Timings come from measured profiles (MOTION_PROFILES.md).

Architecture: how a command becomes a motion

The path a command takes has five hops:

  1. The simulated STAR driver sends a command; before it is answered, the driver hands (module, command, params) to its optional motion_listener.
  2. star_motion() (in pylabrobot/hamilton/star/motion.py) decodes the command into a plain dict - a kind, what moves (arm X, channel Y and heights, grip widths, tip handovers), and the timings from MOTION_PROFILES.md. A command that moves nothing decodes to None.
  3. The viewer's act_out(command, motion) holds the command and broadcasts the payload to the pages over the websocket. With no page connected, nothing is waited for.
  4. Each page plays the payload: playMotion applies the profiled progress per frame, and reports motion_done. The slowest page sets the pace; a background page answers at once.
  5. When every page has played (or timed out), the model records where the command ended - which is where the pages have brought everything - and the driver answers the command.
  • Machine-specific decode lives under pylabrobot/hamilton/star/ - the viewer package stays machine-agnostic. visualizer3D/server.py takes any device's motion payload through a generic act_out(command, motion); it knows no STAR types.
  • The payload contract is a plain dict (kind, moves, ...). The JS side is pure kinematics: the page decides nothing about machines, it only plays what it is told.
  • The driver-side hook is inert plumbing: STARSimulationDriver.motion_listener is None unless a viewer is attached (see motion_demo.py). With no viewer, behaviour is identical to main.

Files this PR adds, and what each is for

File Purpose
pylabrobot/hamilton/star/motion.py The decoder: reads each firmware command for what its drives do - arm X, each channel's Y and heights, grip widths, tip handovers - as local targets with durations, from the resource model.
pylabrobot/hamilton/star/motion_tests.py The decoder's battery: per-command assertions that the decoded motion ends where the model records it ending, dwell/fixed-time checks, and an end-to-end run through a real viewer.
pylabrobot/hamilton/star/MOTION_PROFILES.md The timing model: how each command's duration is built from fixed parts, pumping time and settling time, with the constants fitted from HxUsbComm traces. The decoder and the tests cite it section by section.
pylabrobot/visualizer3D/static/motion_profile.js Pure kinematics, no page: trapezoid and jerk-limited S-curve progress functions - how far along a move a drive is at time t. Parameterized per move by the payload.
pylabrobot/visualizer3D/static/motion_player.js Turns a motion payload into a player: pieces, poses and a step(dt) that applies profiled progress each frame, and reports when the motion is over.
pylabrobot/visualizer3D/static/motion.js The page's motion state machine: holds the current motion, steps it per frame, drops it when a new scene arrives, and carries out what the command means for the tree (a tip onto a shaft, a plate into the gripper's jaws).
pylabrobot/visualizer3D/static/carry.js The grip-side scene semantics: which resources a gripper may hold, where a carried thing stands in the jaws, and the site under a point - the geometry motion.js needs to hand a plate over and put it back.
pylabrobot/visualizer3D/motion_player_tests.mjs The player's and the profiles' own tests, checked in Node (node --test), run from motion_tests.py where Node is present.
pylabrobot/visualizer3D/motion_demo.py The demo: a facility, a viewer, motions played as the run goes.

Files this PR touches, and why

  • hamilton/star/driver/simulator.py - the motion_listener hook: an optional awaited callback before each command is answered. Inert when unset.
  • hamilton/star/driver/features/pipettes.py - two pure helpers: a CO-RE grip tool's face distance and grip-line overhang, read off the resource model.
  • hamilton/star/driver/features/head96.py - _spots_under_shafts(offset): which rack spot each shaft stands over for an offset (a pure query; behaviour is unchanged).
  • hamilton/star/driver/features/core_grippers.py - a _handover stash (what a grip takes, where it will hang; what a release puts down, where it lands) so a viewer can act the command out, and the _hang_location helper factored out of _hang_held_resource_on_the_front_tool.
  • visualizer3D/server.py - the generic motion channel: act_out, motion_done bookkeeping, and the models_drawn handshake (a run waits until a page has its models on screen).
  • visualizer3D/static/transport.js - send() to the server, and the motion message dispatch.
  • visualizer3D/static/live.js - scene operations the motions need: readAxis/setAxis (draw a resource along one axis), reattach (hand a resource to a new holder), turnTo.
  • visualizer3D/static/models.js - the models_drawn report once every model file of a scene is on screen.
  • visualizer3D/static/app.js - the registrations: step the motion per frame, play motion messages, drop motions on a scene rebuild.
  • visualizer3D/browser_tests.py - the Windows headless-Chrome install paths, so the browser battery runs where Chrome is installed but not on PATH.

Behaviour changes

None. Nothing here changes what firmware is sent to a live or simulated STAR: the driver change is an optional awaited callback; every other change is additive (new files, a new server method, viewer JS). With no viewer attached, a simulated run sends the same commands in the same order and answers them from the same model.

Demos

Five runnable demos exercise the playback end to end (python -m pylabrobot.visualizer3D.<name>):

  • motion_demo.py - a facility, a viewer, motions played as the run goes.
  • ripple_demo.py - the Y ripple: one aspirate over five plates, the channels fanning out in Y and rippling back along the direction of travel.
  • head96_demo.py - the 96 head: a full plate of tips, a full-plate aspirate, a full-plate dispense.
  • iswap_demo.py - the iSWAP: a plate moved between the carrier sites the turned gripper can reach, on an otherwise empty deck.
  • core_gripper_demo.py - the CO-RE gripper: grip, carry, and set a plate down.

Notes for reviewers

  • The commits are thematic and each stands alone: (1) driver hook + helpers, (2) decoder + tests + timing model, (3) the viewer, (4) the demos.
  • Comment density and voice follow the packages the files live in (the driver and the viewer both carry full-sentence prose comments and Google-style Args/Returns/Raises docstrings; no lint rule enforces docstrings).

@stefangolas
stefangolas marked this pull request as ready for review October 4, 2026 14:17
@stefangolas
stefangolas requested review from a team and BioCam as code owners October 4, 2026 14:17

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant