Files
orisys-app/apps/autocal/docs/workflow.md
T
2026-09-27 19:20:43 +08:00

15 KiB
Raw Blame History

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 → adopt a program the controller is already running, or download one if BUS_TYPE is unreadable → watch Bus_InitStatus to completion or timeout → read BUS_TYPE, Bus_TotalAxisnum, and the node count. Step 2 is conditional because a controller usually still has the program in ROM from the last session, and a download is a bus re-scan the operator did not ask for.

Both controls live in the device setup dialog (设备设置), opened from the status strip's button, its connection/bus/init chips, or 文件 → 设备设置: they are used once per machine start-up, so the rail keeps only what is read while working. The strip continues to report connection, bus and init state while the dialog is open or shut.

Re-initialising without a download: STOPTASK <task> then RUN "<file>",<task>, on the task from bas_task in preference.json (default 4 — the task the vendor reference uses; stopping another task restarts nothing). Two things are easy to get wrong here, and both have been:

  • the file name must be the one the controller has. When the session adopted a running program, the controller does not report the name, so the app carries the remembered bas_path into the connect for exactly this purpose;
  • the controller answers a RUN naming a file it does not have with code 0 and the diagnosis in its reply text. The reply is checked, and its text is shown instead of reporting an initialisation that is not happening.

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 / reinit_bus / poll implement the state machine and the 25 s timeout.

Connection faults are a different family. A ZAux_* code in 20000–29999 is a PC-library fault (handshake, socket, address), not a controller code: ZDevelop's table cannot contain it, so the app answers it with cable/IP/no-second-client advice and retries an Ethernet open up to three times, because a first open on a cold route can fail and succeed immediately after. 20008 at the bench was that case.


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.json the 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_id matches 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:

  1. on the sensor — its sensor-frame position falls on the modelled surface, not off the edge and not inside a hole;
  2. within travel — the platform can reach it inside the P0 limits;
  3. 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.

The single-point dry run is built, in the finger debug screen: 执行动作 sends the selected point's solved pose to the platform — yaw, then pitch, then table X/Y — and does not command the press axis, so it cannot reach the sensor. The order is the model's own (R_pitch · R_yaw), so every intermediate pose is one the operator watched in the preview. Before it moves it checks the mapping, the target against the P0 limits (millimetres for x/y/z, degrees for yaw/pitch) and the rotary calibration — all in motion/service.py, so a later caller cannot walk around them — and the drives are commanded one at a time, each waiting for the previous to arrive, with a stall or an alarm aborting the rest. 停止 next to it is the same command as the strip's 全部停止; 回初始位 in the rail sends every axis to zero with the press axis first.


P7 — Run + acquire

Why: the only phase that touches the sensor.

Per step, in order:

  1. move to the point's XY at clear height;
  2. descend at approach speed;
  3. detect contact by position — the axis stalls / following error rises while descending;
  4. 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";
  5. press to the configured depth at press speed;
  6. dwell, then acquire the settle window;
  7. 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