13 KiB
AutoCal workflow — platform bring-up to calibration data
This is the operating workflow the application is built around. It is written to be argued with: each phase names what it needs, what it produces, why it exists, and what must be true before the next one starts.
A gate is a checkpoint. The app's job at a gate is to make the operator's answer trustworthy, not to ask "are you sure?".
P0 Session & safety baseline [gate]
P1 ECAT setup [gate]
P2 Homing + test motion [gate]
P3 Sensor & probe setup [gate]
P4 Coordinate setup: sensor <-> platform [gate]
P5 Sequence design [gate]
P6 Simulate & dry run [gate]
P7 Run + acquire [gate]
P8 Store raw
P9 Post-process [gate]
P10 Calibration export
P11 Verify [gate]
P0 — Session & safety baseline
Why: the machine can move from P2 onward. Everything that limits that motion should be decided while nothing is moving.
Inputs: operator identity, machine/bench identifier, units per axis,
units_per_mm.
Safety limits, set here, enforced from P2:
| Limit | Meaning |
|---|---|
| maximum travel per axis | never commanded past this, in user units |
| maximum Z descent per step | a single approach may not exceed this |
| press-depth ceiling | the press depth a sequence profile is allowed to request |
| no-contact abort | if contact is not detected within the descent budget, stop the run |
| following-error bound | how far a drive may lag before the move counts as stalled |
Outputs: a session record — who, when, which machine, and the limits in force. Stamped onto everything the session produces.
Gate: every limit has a value; no limit is "unset" (an unset limit is an unlimited machine).
Currently missing from the workflow: this phase did not exist. Without it, step 1 went from bus setup straight to jogging five axes.
P1 — ECAT setup
Why: nothing else can be trusted until the controller has actually found its slaves. A missing drive discovered at P7 wastes the whole run.
Inputs: controller address, the matching .bas program
(ECAT.bas / RTEX.bas).
Steps: connect → download and run the BAS program → watch
Bus_InitStatus to completion or timeout → read BUS_TYPE,
Bus_TotalAxisnum, and the node count.
Outputs: bus type, node count, axis count, init status.
Gate: init status is success, and the node/axis counts match what this bench is expected to have. A mismatch is a finding, not a warning.
Existing code: MotionService.download_bas / poll already implement the
state machine and the 25 s timeout.
P2 — Homing + test motion
Why: a drive that homes and jogs cleanly at low speed is the precondition for letting a sequence drive it.
Inputs: per-axis homing mode, speeds, offset; a test-motion profile.
Steps: enable → home each axis → low-speed jog each axis in both directions → confirm movement and return.
Outputs: per-axis pass/fail with the evidence (distance moved, following error, alarm state).
Gate — the test motion needs a criterion. This was missing. Proposed:
- the axis moved at least a configured minimum distance;
- it returned within a tolerance of its start position;
- following error stayed under the bound from P0;
- no alarm before, during or after.
Existing code: the axis panel does the jog/homing; the criterion is new.
P3 — Sensor & probe setup
Why: which sensor is under test, what touches it, and how far the touching point is from the axis reference. Contact depth is meaningless without the tool.
Inputs:
- sensor type and config reference (the same
config/sensors/<type>/sensor.jsonthe studio uses); - the sensor's 3D scene package and its
geometry_id; - the probe tip (tool): which axis presses, and the tip offset from the platform's reference point;
- the mounting pose: how the sensor is fixtured on the platform.
Outputs: a saved setup block.
Gate:
- the calibration's
geometry_idmatches the scene's, or the app says which is which rather than colouring the wrong geometry; - the probe tip offset is set (a zero offset that was never entered is not the same as a probe at the origin).
P4 — Coordinate setup: sensor ↔ platform
Why this is the load-bearing phase. "Which points on the sensor should be tested" is a question in the sensor's frame. The platform moves in platform coordinates. Without the transform between them:
- a generated grid cannot be checked against the sensor's surface;
- a measured point cannot be placed on that surface for the sensitivity map;
- a calibration cannot be re-applied to a re-fixtured sensor.
Inputs: the probe tip from P3, and a set of at least four non-collinear known features on the sensor or its fixture, probed in platform coordinates.
Steps: probe each feature → solve for the rigid transform (rotation and translation; scale if the units are not yet trusted) → report the residual.
Outputs: a 4×4 transform from sensor frame to platform frame, with its residual and the feature set that produced it.
Gate: residual under a configured tolerance, and at least four non-collinear points (four collinear points determine a rotation about that line, not a transform — the app should refuse that set rather than return a number).
Not built in this step: the solver. Only the workflow step, the stored transform, and the place its residual is displayed.
P5 — Sequence design
Why: this is the decision the whole run exists to carry out.
Inputs (the profile, persisted and reusable):
| Parameter | Meaning |
|---|---|
| mode | flat surface or curved (a cylindrical/spherical sensor area) |
| grid pitch | spacing between points, in mm |
| margin / radius | how far in from the active edge the grid stays; radius for curved |
| clear height | standoff above the surface to travel at |
| approach speed | descent speed before contact |
| press speed | descent speed after contact |
| press depth | how far past contact to press |
| dwell | how long to hold the press |
| settle window | how long to wait before the measurement is considered settled |
| repeats | how many times each point is measured |
| point order | row-wise, serpentine, or by distance |
Steps: generate the grid → validate every point three ways:
- on the sensor — its sensor-frame position falls on the modelled surface, not off the edge and not inside a hole;
- within travel — the platform can reach it inside the P0 limits;
- confirmable — the sensor's sensing area is under the point, so a contact there can actually be seen by the sensor.
Outputs: a point list with a sensor-frame position and a platform-frame target for each, plus a validation verdict per point.
Gate: no point may be unusable without being marked as such. A run may proceed with skipped points if the operator says so explicitly; it may not proceed with unvalidated ones.
The interface: three zones in the Sequence tab — profile parameters, a 3D
preview of the surface with the points and their visiting order, and the
validation list. See apps/autocal/README.md.
P6 — Simulate & dry run
Why: a wrong Z or a wrong grid is much cheaper to find now.
Simulate: play the full toolpath in the 3D view — a moving marker with a phase label (standoff → descend → press → dwell → retract), the estimated duration, and per-point reachability. No motion.
Dry run: execute the real XY path, descending only to the clear height, never pressing. This proves the geometry, the ordering, and the timing.
Outputs: estimated duration, per-point reachability, and (dry run) the measured time per point.
Gate: the operator has seen the path and a dry run has completed without a guard violation.
P7 — Run + acquire
Why: the only phase that touches the sensor.
Per step, in order:
- move to the point's XY at clear height;
- descend at approach speed;
- detect contact by position — the axis stalls / following error rises while descending;
- confirm with the sensor under test — its reading must change when the probe presses. This is the second opinion: position says "I stopped moving", the sensor says "something real is happening";
- press to the configured depth at press speed;
- dwell, then acquire the settle window;
- retract, move to the next point.
Failure semantics (per point, because resume needs them):
| Outcome | Run behaviour |
|---|---|
| contact confirmed | record the point as measured |
| contact detected, sensor shows no change | mark suspect, continue; a suspect point is visible, not silently averaged in |
| no contact within the descent budget | abort the run (P0 limit) and leave the point unfinished |
| guard violation | abort the run, name the step and the limit |
Writes are incremental: each point is written as it completes, with its status, so an interrupted run resumes at the first unfinished point and partial data stays valid.
Existing code: the motion layer has the guards and the poller; the step machine, the contact detection and the acquisition binding are new.
P8 — Store raw
Why: post-processing will change. The raw record must not.
Contents:
- the sequence profile and the instantiated point list (positions and order);
- per point: the position trace, the raw sensor series, the settle window used, timings, the contact position, and the status;
- the setup: sensor identity, geometry id, probe tip, the P4 transform and its residual;
- the session record and app/version stamps from P0.
Nothing derived is authoritative. The requirement that makes this reproducible: a dataset can be regenerated from the profile, the setup and the raw series, with no dependency on a previously computed result.
P9 — Post-process
Why: turning samples into a number is a decision, and it has to be visible.
Steps: choose the settle window per point → stability check (is the point actually settled, or drifting?) → sensitivity per point per axis → outlier detection → residual per point → fit the surface across points.
Outputs: per-point sensitivity with a quality verdict, and a surface field over the sensor's geometry ready for the 3D colour map.
Gate: every point is either valid or explicitly excluded, with a reason. A point with too few samples or a drifting window is not a coefficient.
P10 — Calibration export
Per-point results and the fitted surface, explicitly linked. They are one calibration; exporting them as unrelated artefacts is how a map ends up in a report that does not match the table beside it.
P11 — Verify
Why this exists: P9 and P10 produce numbers. Nothing yet checks they are right. "Post process → calibration data" had no acceptance test.
Steps: apply the calibration → re-measure a small subset of the points → compare the measured sensitivity against what the calibration predicts → report the discrepancy per point.
Gate: the discrepancy is within tolerance, or the verification is recorded as failed. A calibration that was never verified is labelled as such wherever it is used.
Gaps this document closes
| # | Gap | Phase |
|---|---|---|
| 1 | no limits before motion | P0 |
| 2 | no session identity (who/when/which machine) | P0 |
| 3 | the sensor↔platform transform was absent | P4 |
| 4 | the probe tip (tool) was never defined | P3 |
| 5 | the test motion had no pass/fail criterion | P2 |
| 6 | grid validity was not checked per point | P5 |
| 7 | the settle window and stability criterion were undefined | P9 |
| 8 | a point's failure had no defined behaviour | P7 |
| 9 | the sequence was transient, not a saved, versioned artefact | P8 |
| 10 | no verification pass | P11 |
Data flow
P0 limits ─┬───────────────────────────────> enforced at P2, P6, P7
P1 bus ────┴─> axis availability ─────────> P2
P3 sensor setup ─┬─ geometry id ──────────> P5 validation, P10 surface
└─ probe tip ────────────> P4, P7 press depth
P4 transform ─────> sensor <-> platform ──> P5 point generation, P9 map
(residual) P11 re-fixture check
P5 profile + points ──> P6 simulate/dry run ──> P7 run
P7 raw series ─────> P8 dataset ──> P9 post-process ──> P10 export
└─> P11 verify