Files
orisys-app/apps/autocal/docs/workflow.md
T
ykcheungab bcc55a3b97 修正朝上判断、接触高度与过期姿态目标
朝上判断误用平台轴、接触高度报旋转前数值、多点对准复用缓存姿态,且多点会话被复核入口拒绝;
现按修正计划改正并接通 autocal-multipoint-1.0 只读复核,缺失帧源如实报告而不补零。

离线验证:新回归在 dc5a6f8 上失败、此处通过,panel_check 通过,两截图点接触高度复现
19.0510 / 21.5781 mm;实机项未做,详见 .scratch/autocal-motion-fix/verification.md。
2026-10-15 06:52:28 +08:00

373 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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?".
```text
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 — the stages together with the table
X/Y, and the press axis only once its zero is calibrated (设备设置 → 压轴); until then
it is held, so the move cannot reach the sensor by accident. The order follows the
model's own composition (`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.
停止 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
```text
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
```