main.yaml loads sastt_aerotech.yaml instead of ptycho_lamni.yaml. aeroz and
aerorot are commented out in sastt_aerotech.yaml (not used at the beamline now).
sastt_aerotech_panda.yaml (PandA test setup without beamline timing) moved to
legacy/.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017NrH3poJsYBCTVq2Y9fuHZ
At the beamline (DDG1 trigger) every line started two bursts: the window was
switched to the next line when the axis passed trigger_high, just after the DDG1
cycle (as long as the travel trigger_low -> trigger_high), and the edge started a
second burst. MCS and detectors counted twice the frames, so their frames no
longer matched the captured positions (progress bar done at half the scan).
The window is now moved to a parking range beyond the line end as soon as the axis
has entered it, while DDG1 / PandA run the line and ignore edges; the next line's
window is set beyond the exposures as before, outside both windows. The per-line
MCS check also fails on more frames than the line has.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017NrH3poJsYBCTVq2Y9fuHZ
The scan server builds the Minimum/Full examples itself; ScanArgument(example=...)
now gives aerox / aeroy, a 3.5 x 3 mm field at 40 um and 20 ms. trigger_device
defaults to None (pandatrig with trigger_source="panda"), so the Full example no
longer shows pandatrig next to the default "ddg".
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017NrH3poJsYBCTVq2Y9fuHZ
The IPython client prints client-info messages only from a scan's progress
display. With trigger_source="panda" or None the scan registered none, so the
end-of-scan statistics were sent but never shown (scan 8511). The position
capture now publishes a ProgressSignal (lines read / num_points) and the scan
uses it for the progress bar when there is no MCS card.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M8zoDzDfsCTY2o5bT3J7Wv
The motors only updated their readback at connect and during BEC moves, so a
move in the smargopolo web interface never reached BEC (a read returned the
stored value). The shared controller now polls /readbackSCS (all axes, one
request) every monitor_interval (motor config, default 0.5 s, 0 = off) and
publishes the axes that changed; it skips while BEC moves an axis, logs a
failure once, and stops when the last axis is destroyed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M8zoDzDfsCTY2o5bT3J7Wv
- PandA line trigger: shutter_delay (default 8 ms, as DDG1) between the line
trigger and the first gate, get/set_shutter_to_open_delay like DDG1; the scan
now reads it for trigger_source="panda" (was 0, so the test series ran with
the first gate at the trigger, unlike the beamline)
- a shutter_delay scan argument with "ddg" only moved the trigger positions,
DDG1 kept its own delay; it is now set on DDG1 before staging and restored
in post_scan / on_exception
- docs: DDG1/DDG2 timing chain (exp_time = gate period, readout subtracted),
note that the 2026-10-29 series ran without shutter delay
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M8zoDzDfsCTY2o5bT3J7Wv
Both series of 2026-10-29 (8486-8494 default, 8501-8509 minimal_run_up):
timing and positioning per case, why the saving is about half the shorter
travel (Y step no longer hidden in the run-out), the constant direction
offset after the velocity transient and the 20 mm/s oscillation, deviation
and zoom plots, and the split of the line period with the next levers.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M8zoDzDfsCTY2o5bT3J7Wv
A closed plot made every redraw fail with a warning for each line of each
scan. The first failure now stops following the scans (unsubscribes) with one
message to reopen the plot with csaxs.aerotech.plot().
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M8zoDzDfsCTY2o5bT3J7Wv
- the column-wise connecting lines suggested a vertical fast axis; a NaN after
every point now breaks the curve, so only the exposure markers are drawn (X
stays finite and sorted for clip-to-view)
- a second plot() call added a second Waveform to the window (new() with the
same object_name does not reuse it); the existing one is now looked up with
widget_map() and cleared
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M8zoDzDfsCTY2o5bT3J7Wv
The Waveform's async device curves switch on automatic "peak" downsampling and
clip-to-view above 1000 points; both assume X increasing along the curve, so a
snake scan (8494, 105651 points) showed a diamond pattern and paired markers.
csaxs.aerotech.plot() now reads the scan file (history) or follows the
aerocapture async stream of the running scan (live, from_start, redraw in a
worker thread at <= 2 Hz) and sends plain X/Y data: forward and backward lines
as two curves, points ordered by cell column, alternately up and down, so X
increases along each curve (clip-to-view works when zoomed) and the thin line
runs along the columns. csaxs.aerotech.stop_plot() stops following.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M8zoDzDfsCTY2o5bT3J7Wv
Opens (or reuses) the GUI window "aerotech" with a Waveform of the async
aerocapture centres, enc2_centre vs enc6_centre, updated per line during
aerotech_cont_grid; scan_number=... shows a finished scan.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M8zoDzDfsCTY2o5bT3J7Wv
aerotech_cont_grid(..., minimal_run_up=True): run-up / run-out = v^2 / (2a),
without the 2x margin and settle_time, for analysis with unevenly spaced
(captured) positions; at least 0.01 so the fast axis never stops and reverses
at the edge of the next line's PSO window (0.25 um at 0.5 mm/s otherwise).
Shown in the statistics header and the limit message.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M8zoDzDfsCTY2o5bT3J7Wv
User guide (SAXS / Aerotech stages): initialisation on the A3200 PC, config,
moves, aerotech_cont_grid with copy-paste examples for the planned fields of
view, run-up and soft limits, data written, timing / positioning section
(results of the 2026-10-29 series pending; overhead and uneven-positions
estimate defined). Developer guide: interface, moves, scan design and measured
choices, position capture, PandABox line trigger, pitfalls, commissioning.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M8zoDzDfsCTY2o5bT3J7Wv
- scan_info / data file recorded readout_time 0.0 although the gates and the
centres used the trigger hardware's 0.2 ms; now the value the scan uses
- statistics: "line mean" was line start .. trigger checked (0.60 s for a
0.78 s line in scan 8477) and "scan" ran from pre_scan only (10.0 s for a
13.5 s file); now line period start to start incl. capture readout and step,
with motion / readout split, and the total of the lines labelled as such
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M8zoDzDfsCTY2o5bT3J7Wv
wait_lines_done warned after every line with the total of ignored edges since
arm (20/10, 23/11, 26/13 ... in a 2026-10-29 scan), which read like an
escalating trigger error. These edges are harmless (encoder noise at the PSO
window entry while the line runs; lines and pulses are checked and raise on a
mismatch), so only the edges new since the last line are logged, at info level.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M8zoDzDfsCTY2o5bT3J7Wv
aerocapture published the latched positions (and centres) in controller coordinates;
with sign: -1 in the device config they had the opposite sign of the motor readback
and the scan statistics compared different frames. The capture device now looks up
the A3200Motor of each channel (same controller, same axis) on the device server and
applies its sign; without such a motor it keeps sign 1 and logs a warning.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M8zoDyJxbBLmjZCEPUo5FM
The scan worker only logs a ScanAbortion; the client then shows just "Scan ...
was interrupted" (seen 2026-10-29 with the run-up limit check). The checks of
aerotech_cont_grid now raise LimitError (limits), ValueError (parameters,
missing devices) and RuntimeError (missing line trigger), which the scan worker
turns into an alarm with the message, and still runs on_exception.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M8zoDzDfsCTY2o5bT3J7Wv
aerotech_cont_grid switches the PSO window per line through RPC, which only
exposes USER_ACCESS methods: the scan failed after the first line with
"'Positioner' object has no attribute 'set_line_trigger_window'".
New test: every method the scan calls on the fast axis, the capture device and
the PandA is in the device's USER_ACCESS.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M8zoDzDfsCTY2o5bT3J7Wv
calibrate() returned {channel: offset} with integer keys. The scan server cannot
decode an RPC reply with integer map keys (msgpack strict_map_key), so the
calibrate status never completed and aerotech_cont_grid waited forever in
pre_scan. Now {"enc<channel>": offset}; new test round-trips the RPC return
values of the capture device through the BEC serializer.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M8zoDzDfsCTY2o5bT3J7Wv
software_trigger is a DeviceBase name in BEC, so the device server refused to
load pandatrig ("User access method name {'software_trigger'} is protected").
New test: USER_ACCESS of the PandA and A3200 devices has no protected names.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M8zoDzDfsCTY2o5bT3J7Wv
As soon as the fast axis has left the exposure range (beyond the trigger position),
the PSO window of the next line is set and the step axis starts; the next line starts
when both have arrived. Saves ~0.15 s per line (2 mm/s, 0.8 mm: 1.07 -> 0.92 s per
line incl. step; 20 mm/s: 0.85 -> 0.65-0.73 s). settle_time stays 0.15 s: shorter
values at 20 mm/s gave -24 / +10 / +4.5 / -21 um backward - forward (0.08 / 0.04 /
0.02 / 0 s), a ~10 Hz oscillation after the ramp.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M8zoDyJxbBLmjZCEPUo5FM
After a hard stop (5 mm/s, 500 mm/s^2) X rings at ~10 Hz with ~20 um. The single
position error check caught a zero crossing (-0.19 um), the move was declared
settled 3.5 um off the target and failed (premove of a scan aborted). Now position
error and distance to the target must stay within the tolerance for
in_position_hold (0.1 s) without interruption.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M8zoDyJxbBLmjZCEPUo5FM
- readout time from the trigger hardware (DDG2 effective_readout_times()["ab"], PandA
device config readout_time, default 0.2 ms), no scan argument; the trigger positions
move inward by v * readout / 2 so the gates are centred in their cells and forward /
backward lines have the same exposure centres
- aerotech_cont_grid always captures the positions (capture_positions removed):
aerocapture publishes the captured gate starts (enc<ch>) and the exposure centres
(enc<ch>_centre, + v * gate_width / 2 in the direction of motion)
- statistics at the end of the scan (sent to the client): centre - nominal cell centre
per direction, backward - forward with slope and peak-peak, scan and line times
- aerox acceleration 500 mm/s^2 (1000 left a speed drift in the lines at 20 mm/s);
aerocapture enabled, channels X and Y only
Hardware 2026-10-29 (PandA, 0.2 ms readout, 6 snake lines each), backward - forward:
2 mm/s +2.3 / +3.2 um, 0.5 mm/s -0.1 / -0.75 um, no slope; 20 mm/s -1.2 um,
slope -1.2 um/mm (was +24 um, 5 um/mm with 1000 mm/s^2 and no compensation).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M8zoDyJxbBLmjZCEPUo5FM
Measured with TTL gates on J12 at 2 mm/s: trigger 0 ("UINT1 rising" in the A3200
help) latched at the gate end (+17.2 um for a 10 ms gate, expected +20), trigger 3
at the gate start (-3.0 um). The Npaq UINT input is inverted compared with the help.
Default trigger_input is now 3.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M8zoDyJxbBLmjZCEPUo5FM
A replayed PULSE train also came 1.3 s after an unchanged parameter write by a new
device instance (connect / configure_line), outside the 1 s quiet wait, and blocked
the first line trigger. Fields are now read first and written only if the PandA holds
a different value, so a reconnect on a configured PandA writes nothing; the quiet wait
after a real change is 2 s.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M8zoDyJxbBLmjZCEPUo5FM
The PSO opto output (J7-6/7 sourcing from J7-25 +5 V) into a 50 Ohm input is high
inside the window in both directions (measured on the PandA TTLIN3, 2026-10-29).
DDG1 EXT is 50 Ohm as well, so the tested wiring needs a rising edge. "falling"
stays available for the open collector / differential outputs (not tested).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M8zoDyJxbBLmjZCEPUo5FM
At 2 mm/s an extra edge at the PSO window entry arrived during the running line
(small field of view, line 4); it was ignored as designed, like on the DDG, but
wait_lines_done aborted the scan. Wrong line or pulse counts still fail.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M8zoDyJxbBLmjZCEPUo5FM
With one closed window per scan the window exit coincided with the end of the
exposures; encoder noise at the exit edge gave extra trigger edges after the train
(2 in 11 lines on the PandA, one started an extra line). Now the window is open on the
far side of the line and only its range is switched before each line (PSOWINDOW RANGE,
no reload, so the position reference of the arm stays). Hardware test: 2 x 11 lines,
22 edges, 22 lines, 2222 gates, 0 dropped.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M8zoDyJxbBLmjZCEPUo5FM
The PULSE blocks replay a full train ~0.3-0.6 s after a parameter write, without a
trigger. configure_line returned after 50 ms, so the train ran into the first line and
blocked its trigger (line counter 0). Now both PULSE outputs must stay low for
PARAM_QUIET_TIME (1 s); only on a parameter change, i.e. once per scan.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M8zoDyJxbBLmjZCEPUo5FM
- A3200Motor.max_scan_velocity (default: velocity) limits the scan velocity of
aerotech_cont_grid; the move velocity is restored after the scan, also after
an aborted scan
- run-up / run-out: 2x ramp distance + v * settle_time (was 1.2x ramp)
- the full line range including run-up / run-out and the step axis range are
checked against the soft limits in prepare_scan
- aerox: acceleration 500 mm/s^2, max_scan_velocity 50 mm/s, moves stay 5 mm/s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M8zoDzDfsCTY2o5bT3J7Wv
aerox settles to 10-30 nm within 0.5 s, but the controller's InPositionDistance
for X is 10 nm, so In Position took several seconds or flickered and every move
logged "not in position 2.0 s after the end of the move". The move now waits
until |PositionError| is within the axis tolerance.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M8zoDzDfsCTY2o5bT3J7Wv
Integer YAML map keys made the DeviceConfigMessage undecodable on SciHub
(msgpack strict_map_key), so update_session_with_file hung until timeout.
A3200PositionCapture already converts the keys with int().
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M8zoDzDfsCTY2o5bT3J7Wv
scan_type HARDWARE_TRIGGERED: the MCS card and the detectors are armed once at stage for
all lines x frames (existing hardware_triggered mode of mcs_card_csaxs / ddg_1) instead of
per line; per line the scan only moves the axes. With trigger_source="ddg" each line is
still checked: the MCS must have reached the line's last channel, so a missing line
trigger aborts after the first line.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M8zoDyJxbBLmjZCEPUo5FM
aerotech_cont_grid:
- snake=True (default): alternating line direction. One PSO window per scan,
[fast_start - v*delay, exposure_end + v*delay], armed once in pre_scan; its output rises
on entering the window, i.e. at the low edge forward and the high edge backward.
snake=False switches the PSO off during the flyback.
- trigger_source "ddg" (beamline), "panda" (PandA configured and armed once, every line
checked) or None (no trigger hardware); trigger_edge for DDG1, default falling (active
low open collector / differential PSO output).
- settle_time (default 0.15 s) for the run-up at constant speed, measured on the hardware
2026-10-28; capture_positions defaults to False until the sampling is commissioned.
- the fast axis does not wait for In Position at the turnarounds (wait_in_position signal
of A3200Motor, off during the scan); restore steps are all tried.
PandaLineTrigger: trigger_edge config, "falling" inverts TTLIN3.
Hardware test of the scan sequence (trigger_source=None, 1x1 mm, 11 lines, X 1 mm/s):
15.3 s instead of 23.1 s; PSO window switched on at X=0.25 forward and X=0.76 backward.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M8zoDyJxbBLmjZCEPUo5FM
First moves on the SAXS-TT hardware (X, Z, Y, +-0.5 mm, 2026-10-28): the move was
evaluated when the motion profile ended (MoveDone), X was still settling (1.2 um off) and
the move failed. After MoveDone, move() now waits for DriveStatus In Position
(InPositionDistance / InPositionTime, timeout in_position_timeout = 2 s) before comparing
with the target. axis_status() also reports in_position and encoder_clipping.
Tolerances per axis in the configs: X 1 um, Z/Y 5 um (InPositionDistance 5 um).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M8zoDyJxbBLmjZCEPUo5FM
configure_line() once, arm() once, then every line trigger edge (TTLIN3 or software)
starts a line; a scan no longer re-arms per line. LUT7 accepts an edge only while armed
and while no line is running (PULSE3/PULSE4 low), so edges during a line are ignored like
on the DDG and never reach the PULSE blocks (a dropped trigger puts the FPGA 4.1 PULSE
block into the replay state). COUNTER6/7 count raw edges and accepted lines,
wait_lines_done() checks lines, gates and ignored edges. arm(one_shot=True) keeps the
single-line mode. Design SASTT_line_trigger updated on omny-panda.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M8zoDyJxbBLmjZCEPUo5FM
Device PandaLineTrigger (control port only): on the first line trigger edge (TTLIN3 or the
software trigger BITS.A) after arm_line(), PULSE3 -> TTLOUT2 sends a shutter pulse and
PULSE4 -> TTLOUT3 n detector pulses; COUNTER8 counts them, SRGATE1 is a one-shot arm gate.
PULSE parameters are written only when they change: on PandA FPGA 4.1 an ENABLE edge or a
parameter write often replays the previous train without a trigger.
Design SASTT_line_trigger saved on omny-panda, copy in layouts/sastt_line_trigger.ini.
Config sastt_aerotech_panda.yaml = sastt_aerotech.yaml + pandatrig.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M8zoDyJxbBLmjZCEPUo5FM
From 153722-A-1-1.prma: A3200 PC pc14643.psi.ch, axis indices X 0, Z 1, Y 2, U 3
(UU gantry slave, ZZ encoder input used by Z), limits, default speeds and ramp rates.
PSO input and DATAACQ source are now configured separately, they have different
numberings on the Npaq: motor pso_input (0-5 local counter, 6-8 MXR emulated
quadrature) + pso_counts_divider (EmulatedQuadratureDivider), capture channels are
DATAACQ sources 0-11 (6-11 = MXR channel 0-5). GlobalDoubles is 255 on SAXS-TT, so
the scratch variable moves to $global[254].
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M8zoDyJxbBLmjZCEPUo5FM
This reverts commit bf77d6f6fe.
The RIO timing (1 ms steps, ~50 Hz opto outputs) has too much jitter to judge
whether the position sampling works, so the test setup will not use it. The
DMC program stays in the OMNY Galil repository (RioDDG).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M8zoDyJxbBLmjZCEPUo5FM
- GalilRIOBurst (galil_rio_ddg.py): Galil RIO-47120 running
RioDDG/rio_ddg.dmc (OMNY Galil repo) as line burst generator: burst mode
switching, arm_burst, burst status, wait with pulse count check
- aerotech_cont_grid: trigger_source="ddg" (beamline) | "rio" (test setup
without DDG/MCS)
- simulated RIO triggered by the PSO window of the simulated A3200
- configs: sastt_aerotech_test.yaml (A3200 + RIO), RIO in the simulated config
- tests
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M8zoDyJxbBLmjZCEPUo5FM
- A3200Motor: arm_line_trigger/disarm_line_trigger (PSO window on the fast
axis drives DDG1 EXT), acceleration (RAMP RATE before each move),
encoder_channel
- A3200PositionCapture: Npaq DATAACQ latches the encoder counters on each
detector trigger (UINT1), read back per line via ARRAY READ and published
as async data; offset calibrated with a software PSO fire
- aerotech_cont_grid: cont_grid geometry, line start triggered by position
instead of a software sleep
- simulator support for RAMP, $global, UNITSTOCOUNTS, PSO, DATAACQ, ARRAY READ
- configs, tests, README with wiring and commissioning checklist
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M8zoDyJxbBLmjZCEPUo5FM
Npaq/A3200 via the ASCII command interface on the A3200 Windows PC:
enable, home (HOMEASYNC), MOVEABS, abort, fault ack, ~STATUS readback.
Simulated controller (sim_a3200), device configs for hardware and
simulation, tests. Commissioning checklist in devices/aerotech/README.md.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M8zoDyJxbBLmjZCEPUo5FM