选点、对准、加载与录像分散在两个面板,状态靠隐藏相位判断。现改由不依赖 Qt 的采集控制器驱动,并加入录像选项、汇总平铺副本、保持到时自动结束、会话结束回到空闲,以及无设备时的模拟采集。已运行 Autocal 非 Qt 测试并通过范围内门禁;界面测试待 Windows 复核,未做实机验收。
16 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 → 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_pathinto the connect for exactly this purpose; - the controller answers a
RUNnaming 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.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.
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:
- 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.
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
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