06e06126ca
选点、对准、加载与录像分散在两个面板,状态靠隐藏相位判断。现改由不依赖 Qt 的采集控制器驱动,并加入录像选项、汇总平铺副本、保持到时自动结束、会话结束回到空闲,以及无设备时的模拟采集。已运行 Autocal 非 Qt 测试并通过范围内门禁;界面测试待 Windows 复核,未做实机验收。
390 lines
16 KiB
Markdown
390 lines
16 KiB
Markdown
# 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 geometry & mounting screen (几何与安装,
|
||
which no longer carries a point table): 执行动作 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.
|
||
|
||
**As built today** (manual, through the capture page): the operator keeps one
|
||
queue of points — a single point is a queue with one row. `Ready` must be all
|
||
green before the first point. The page shows one primary button whose label is
|
||
the next step (align → start → at the target → hold finished → confirm liftoff →
|
||
finish) and states the reason whenever it is disabled. Alignment only requests a
|
||
guarded pose move, and a capture may not start until that move has completed, so
|
||
a repeat can never be recorded on a pose nobody confirmed. Remeasure, skip and
|
||
stop are the secondary actions. What the button says is derived from the campaign
|
||
state by `capture_controller.py`, never by the widgets.
|
||
|
||
Changing the mounting offset, the tool XY or the travel limits re-solves every
|
||
queued pose: while the queue is still editable the new solution is stored, and a
|
||
point the stage can no longer reach is marked and refused for alignment. The
|
||
exported operation table comes from the same queue (queue → 导出测点操作表…), so
|
||
the table and the run cannot describe different points.
|
||
|
||
---
|
||
|
||
## 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
|
||
```
|