6e8c1cb7a2
The operator frames a good spot, confirms the two DC levels the detector reads there, and the rig then measures its own tilt: step 1.5 mm either side on X and then on Y, and tilt the platform until those levels come back. The correction that fixes an offset point is the correction that levels the whole travel — height error and tilt effect are both proportional to the offset — so the procedure ends by applying it and leaving it applied. Both directions are measured from the same starting tilt and averaged, which makes their disagreement a flatness read-out rather than something averaged away silently. core/auto_align.py holds the geometry and the search, Qt-free. The three T-axes' azimuths are the whole geometry: T1 lies along +X so it alone tilts along X, and T0/T2 move as an equal-and-opposite pair to tilt along Y without touching X (tilt_response derives that, and the tests pin it — an axis map that drifts would still converge, on the wrong axis). The search is a secant null on the split-detector difference: probe once to learn what a microstep is worth, sign included, then step at the null. It refuses to servo on a scope that has not re-triggered, escalates a probe that reads as no response before calling an axis dead, and stops at a per-axis travel limit. gui/align_bridge.py runs it on a worker thread; stopping is a threading.Event rather than a queued command, because the worker is inside a long handler for the whole run. The camera window carries the button and the progress window, and locks the scan panel and the jog pads while a run owns the stage. Adds immediate MEAN measurements and an acquisition count to the scope driver, and read_bias_mv to core/scope_inspect — the one scalar the inspection state was missing. KNOWN_ISSUES.md records what only the rig can settle: the probe step, the travel limit, the hold current, and whether the piston the X phase applies alongside its tilt matters. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
117 lines
6.1 KiB
Markdown
117 lines
6.1 KiB
Markdown
# Known issues requiring on-rig verification
|
||
|
||
Questions that cannot be answered from the code alone. Check these the next
|
||
time the hardware is available; each one gates a small code change.
|
||
|
||
## uC480 camera: gain/exposure during active capture
|
||
|
||
The driver used to carry an (unused) `_capture_paused` context manager whose
|
||
docstring claimed many IDS cameras return `IS_CANT_COMMUNICATE_WITH_DRIVER`
|
||
(17) or `IS_NO_SUCCESS` (-1) when gain/exposure commands are issued during
|
||
active capture. `set_exposure()` and `set_gain()` never used it, and the
|
||
helper was deleted in the Phase-1 cleanup.
|
||
|
||
**Bench check:** with live streaming running, move the exposure and gain
|
||
sliders in `camera_test_app.py` and watch the log for those error codes.
|
||
If they appear, the setters need a stop-live/apply/restart sequence
|
||
(re-create the helper around the two call sites in
|
||
[uc480_camera.py](hardware/uc480_camera.py)).
|
||
|
||
## Helios: no output-power query
|
||
|
||
`docs/hardware/HELIOS_DRIVER_README.md` documents `driver.get_power_mw()`,
|
||
but `HeliosLaser` has no such method and no output-power mnemonic appears
|
||
anywhere in this repo's protocol notes. `helios_test_app.py` called it
|
||
anyway and raised `AttributeError` into a popup; the button is now disabled
|
||
and the handler reports the gap instead.
|
||
|
||
**Bench check:** find the power-read command in the Helios manual (the
|
||
other reads are three-letter mnemonics like `LDO`, `LDS`, `LTA`). If one
|
||
exists, add `get_power_mw()` to `hardware/helios_laser.py` using
|
||
`_query_int`, then re-enable the button. If it doesn't, delete the Power
|
||
Monitoring group from the test app and fix the README.
|
||
|
||
## Genesis laser: forked protocol implementations disagree
|
||
|
||
`hardware/genesis_core.py` and the reference implementation
|
||
`tools/genesis_laser_gui.py` disagree on ADC command bytes, LDD enable
|
||
polarity, shutter semantics, filtering, and scaling. Do not modify either
|
||
until the checklist in [docs/genesis_verification.md](docs/genesis_verification.md)
|
||
has been run on the bench.
|
||
|
||
## `lib/ueye_loader.so` — still needed?
|
||
|
||
`lib/ueye_loader.c` is an `LD_PRELOAD` shim that dlopens
|
||
`/usr/lib/libueye_api.so` — yet nothing in the repo references it, and the
|
||
vendored SDK copy is `lib/libueye_api64.so.3.82` (a different file). On the
|
||
rig, check whether the camera apps run without the shim; if they do, delete
|
||
`lib/ueye_loader.{c,so}`. Either way, record in SETUP.md where
|
||
`libueye_api64.so.3.82` came from (IDS SDK version) and how the loader is
|
||
meant to be used.
|
||
|
||
## Per-angle background: trigger round trip mid-scan
|
||
|
||
Every angle now captures its own background, so the scope switches from the
|
||
scan-time logic-AND trigger back to the single-record edge trigger and
|
||
returns to it once per angle (`core/scope_sras.py`:
|
||
`configure_background_trigger` → `capture_background` →
|
||
`configure_scan_trigger`). Before this, that transition happened once per
|
||
scan, with the stage idle and nothing depending on how long it took.
|
||
|
||
**Bench check:** run a multi-angle scan and watch the first row after each
|
||
background. If frames go missing at the start of an angle, the 0.2 s settle
|
||
in `configure_scan_trigger` is not enough for FastFrame to re-arm after an
|
||
AVERAGE-mode sequence, and the row-packing warning ("N frames acquired, M
|
||
expected") will say so in the log. Raise the settle rather than the ramp
|
||
buffer — the stage geometry is not what changed.
|
||
|
||
## Auto-align: constants that are guesses until the rig confirms them
|
||
|
||
`core/auto_align.py` closes a loop over hardware whose gain nobody has
|
||
measured. Three numbers in `AlignSettings`/`TAxisSettings` are reasoned
|
||
defaults, not readings:
|
||
|
||
- `probe_steps = 200` — the first move of every search, made only to learn how
|
||
many millivolts a microstep is worth. Too small and each search wastes
|
||
iterations doubling it (the status line says so: "moved N microsteps and the
|
||
DC difference did not change"); too large and the first move overshoots by
|
||
more than the platform should be asked to travel in one go.
|
||
- `max_excursion_steps = 20000` — the per-axis safety limit, measured from
|
||
wherever the axis started. It exists to stop a runaway before the actuator
|
||
reaches its end stop, so it has to be smaller than the real travel.
|
||
- `hold_current_ma = 300` — the run current (600 mA) and microstepping (32)
|
||
are specified; the standstill current is half the run current by analogy
|
||
with the GR axis, and has not been checked against the platform's weight.
|
||
|
||
**Bench check:** run one auto-align and read the log. The first search's
|
||
iteration count is the probe verdict — 3 or 4 steps means the probe is about
|
||
right, and a "did not change by 2 mV" line means it is too small. Convert the
|
||
applied corrections into actuator travel and compare against the T-axis
|
||
travel to set the excursion limit. Watch whether the platform holds its tilt
|
||
between the two phases; if it sags, raise the hold current.
|
||
|
||
## Auto-align: does the X phase's piston matter, and where is the pivot?
|
||
|
||
The X phase moves T1 alone, as specified. T1 is the only axis lying along X,
|
||
so it does tilt the platform along X — but moving one leg of three also lifts
|
||
the platform by a third of the move (`tilt_response(X_TILT)` returns a piston
|
||
of 1/3 alongside the 2/3 tilt). The search nulls the split-detector
|
||
difference, which a piston should not move, so the assumption is that the
|
||
piston is harmless. The piston-free alternative is T1 +1 with T0 and T2 at
|
||
−0.5 each.
|
||
|
||
Separately, the procedure assumes the tilt pivot is under the beam: if it is
|
||
not, applying the correction shifts the DC levels at the reference point
|
||
itself, and the Y phase then chases levels that no longer describe the rig.
|
||
The code reports this rather than compensating for it — `AxisResult`'s
|
||
"back at the reference" reading after the X phase is exactly that
|
||
measurement.
|
||
|
||
**Bench check:** during an X search, watch DC1 + DC2 (the sum, not the
|
||
difference) on the scope. If the sum moves as T1 moves, the piston is
|
||
changing the amount of collected light and `X_TILT` should become the
|
||
piston-free triple. Then read the X phase's reference residual out of the
|
||
log: more than a few millivolts means the pivot is not under the beam, and
|
||
the Y phase's reference should be re-measured after the X correction instead
|
||
of reusing the operator's original numbers.
|