Commit Graph
1092 Commits
Author SHA1 Message Date
x12sa f035f074e2 changes during beamtime e23514
CI for csaxs_bec / test (push) Successful in 2m0s
2026-09-08 12:25:20 +02:00
x12sa 47050de1a5 increase velocity of X-ray eye Owis stages in x and y
CI for csaxs_bec / test (push) Successful in 2m1s
2026-09-07 14:31:43 +02:00
menzelandClaude Opus 5 020039dd44 fix(macros): no module-level assignment, or the loader refuses the file
The previous commit added `logger = bec_logger.logger` at module level. The
macro loader rejects any module-level ast.Assign
(bec_lib.macro_update_handler.has_executable_code) and then refuses the whole
file, so run_cont_grid_scan_for_table_row stopped being loaded at all:

    Macro file .../run_cont_grid_scan_for_table_row.py contains executable code
    at module level (line 16) and will not be loaded for security reasons.

Imports, defs, classes, annotated assignments and docstrings are permitted;
plain assignments are not. The logger is now fetched inside the functions.

Adds a test that runs the real has_executable_code over every macro, so this is
caught by the suite instead of by a WARNING in the log stream. Verified to fail
when a module-level assignment is reintroduced.

Noticed only because bec-log-monitor happened to be running at the time -- a
refused macro is otherwise indistinguishable from one that was never installed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0148tn6uK6oiTH25mzLfJcyc
2026-09-04 20:28:50 +02:00
menzelandClaude Opus 5 d5d41d9eac fix(macros): report failures fully instead of a one-line summary
When a table row failed, the macro printed a headline, sent the actual error
text to SciLog alone, and left both the SciLog post and the SMS unguarded. So
the detail existed in exactly one place that nobody was watching, and if SciLog
was unreachable its exception replaced the one being reported -- the bare
`raise` at the end never ran. Combined with @scan_repeat retrying three times,
a deterministic one-line DeviceConfigError produced three identical
context-free messages and survived several hours of beamtime.

Failures now go through _report_failure, which:

  - prints the exception type, message and full traceback to the console;
  - logs the same through bec_logger, so it reaches the log files AND Redis and
    is therefore visible in `bec-log-monitor` and afterwards in the logs, rather
    than only on whichever console ran the macro;
  - includes _row_context: sample, template, both scan axes with ranges and step
    sizes, exposure time, and for tensor rows the rotation axes and angles, so a
    report identifies the row without needing the table alongside it;
  - guards SciLog and SMS separately, each reporting its own failure without
    touching the original exception.

The caller still re-raises, so scan_repeat and the queue behave as before.

Tests cover the two masking cases that mattered -- an unreachable SciLog and a
failing SMS must not replace the original error -- plus the tensor context and
that no SMS is attempted without phone numbers.

Not changed, but flagged: @scan_repeat(max_repeats=3, default=True) retries any
error three times, including deterministic ones. The file's own TODO warns about
this. It triples the noise while diagnosing a reliably failing scan.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0148tn6uK6oiTH25mzLfJcyc
2026-09-04 20:17:59 +02:00
menzelandClaude Opus 5 e07320ce5d fix(macros): look devices up in dev, not dev.devices
The tensor-tomography branch of run_cont_grid_scan_for_table_row has never
worked. `dev` is already the device container -- bec_lib binds
dev = device_manager.devices in the client namespace -- so dev.devices[name]
asks DeviceContainer for a device literally called "devices" and its
__getattr__ raises DeviceConfigError before any motor moves.

Observed at the beamline on a tensor table using sgchi/sgphi as the rotation
axes:

    --> 72  roty_motor = dev.devices[row["roty_axis"]]
    DeviceConfigError: Device devices does not exist.

The failure is deterministic, so @scan_repeat(max_repeats=3, default=True)
retried it three times, and the surrounding except reported only "Error while
moving motors to starting position for sample ..." -- the traceback goes to
SciLog and nowhere else, which is why this survived undetected.

Adds tests/tests_macros, which had no equivalent: macros run in the client
namespace and are not executed by any test, so mistakes in them reach the
beamline unfiltered. The check parses each macro and flags dev.devices
attribute access rather than matching text, so comments and docstrings that
mention the pattern do not trip it. Verified to fail against the unfixed macro.

The rotation axes themselves are not hard-coded: rotx_axis/roty_axis are row
fields holding a device name chosen from the SAXS widget's positioner combo
boxes. Only the four row keys are fixed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0148tn6uK6oiTH25mzLfJcyc
2026-09-04 20:17:59 +02:00
menzelandClaude Opus 5 934d8d25cf docs(eiger): record that triggering emulates gating, and why
The Eiger is triggered rather than gated for stability, and the pulse train is
shaped so its internal timer coincides with the gate -- it is meant to behave as
if gated, so that every detector in a scan integrates the same window. Nothing
in the code said so, which is why sending the full exp_time as image_time_us
looked reasonable and stayed wrong until a scope showed the 173 us overhang.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KLnmUurqcNd1FiDY5M2uZr
2026-09-03 09:41:31 +02:00
menzelandClaude Opus 5 f450f29e39 fix(eiger): let each model keep its own readout time, and make it configurable
Readout is a property of the detector, not a beamline constant: a 9M has more
modules than a 1.5M, and the Falcon needs 3 ms against the delay generator's
200 us. The per-model constants therefore stay, with comments saying the
duplication is deliberate so nobody consolidates them again. They all hold 2e-4
today only because no measured per-model value exists yet.

Writing a test for the deviceConfig override exposed that it never worked. Both
subclasses passed readout_time to super() while also forwarding **kwargs, so
supplying it raised "got multiple values for keyword argument" -- and through
BEC it never even got that far, because readout_time was not a named parameter
of the subclass signature and the device server drops config keys it cannot see
(the same rule behind the recent prefix incident). Both subclasses now name it
with the model constant as default, and a test asserts the signatures keep it.

Also documents frame_time_us in DetectorSettings as required-but-ignored for the
Eiger, and warns that its 500 is microseconds while every other time in the
module is seconds.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KLnmUurqcNd1FiDY5M2uZr
2026-09-03 09:41:31 +02:00
menzelandClaude Opus 5 011d403920 feat(eiger,ddg2): acquire over the gated window, not the whole period
scan_info's exp_time is the trigger PERIOD. DDG2 gates for exp_time minus a
readout time, but the Eiger was sent the full exp_time as image_time_us, so
the detector acquired 180 us past the falling edge of its own gate and
finished only 20 us before the next trigger -- that 20 us being JungfrauJoch's
internal board-readout allowance, and the entire margin available. Measured on
a scope as a 172.8 us overhang.

Four independent notions of "readout time" existed:

  500 us  EIGER*_READOUT_TIME_US   validation floor only, no effect on anything
  200 us  DDG2 DEFAULT_READOUT_TIMES["ab"]   sets the gate width
   20 us  JungfrauJoch deployment config     applied internally by JFJoch
    -     scan parameter readout_time        honoured by the Falcon, ignored by DDG2

They are now one. DDG2 derives the gap from the scan's readout_time, floored by
its configured value (scans default it to 0), and exposes effective_readout_times().
The Eiger subtracts the same number from image_time_us. The Falcon already used
the scan value, so it needs no change.

The Eiger's 500 us constant is split in two, because it was doing two jobs: a
MIN_EXP_TIME validation floor keeps today's behaviour exactly, while the new
EIGER_READOUT_TIME defaults to 2e-4 to match the delay generator. The _US suffix
on constants holding seconds is dropped in all three modules.

Both devices log the effective exposure window at on_stage, so a period/exposure
mismatch is visible in the logs rather than only on an oscilloscope.

NOTE: this shortens the delivered exposure by the readout time -- 200 us, i.e.
0.5% at 40 ms -- for scans that do not set readout_time. Users should be told
before this is deployed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KLnmUurqcNd1FiDY5M2uZr
2026-09-03 09:41:30 +02:00
menzelandClaude Opus 5 10be2b5585 fix(falcon): name prefix in __init__ so the device server stops dropping it
The __init__ added in the previous commit took (*args, **kwargs). The device
server builds a device's init kwargs by intersecting the deviceConfig keys
with the NAMED parameters of the class signature
(bec_server/device_server/devices/devicemanager.py:469-475), so 'prefix' was
silently discarded and the Falcon was constructed with an empty prefix.

Every signal then pointed at a bare suffix -- HDF1:FilePath_RBV instead of
X12SA-SITORO:HDF1:FilePath_RBV -- and instantiation failed with
"TimeoutError: Failed to connect to all signals" listing several hundred PVs.
That reads like an unreachable IOC, which is how it was diagnosed at the
beamline for two hours, while caget from the same host worked perfectly.

The signature now names name, prefix, scan_info, device_manager and
xml_file_name explicitly, matching DDG1. Two tests guard it: one asserts every
deviceConfig key is a named parameter, the other that a configured prefix
reaches the signal PV names.

Reported-by: Klaus Wakonig <klaus.wakonig@psi.ch>

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KLnmUurqcNd1FiDY5M2uZr
2026-09-01 22:20:39 +02:00
menzelandClaude Opus 4.8 98bfeb6586 config(smargon): REST API port is 3000, not 8080 (:8080 is the web GUI)
Confirmed with the smargopolo maintainer: the JSON REST API is on :3000; the
:8080 server is the web GUI (returns HTML, all API paths 404). Point host/port
back to tt-smargopolo:3000 on the coordinator and all axis motors.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-09-01 17:37:52 +02:00
menzelandClaude Opus 4.8 8733d2e6e6 config(smargon): point beamline config at tt-smargopolo:8080
The cSAXS beamline smargopolo server is http://tt-smargopolo:8080 (port 8080,
not the MX-lab reference's :3000). Set host/port on the coordinator and all axis
motors so smargon.yaml loads as-is at the beamline.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-09-01 17:37:52 +02:00
menzelandClaude Opus 4.8 7b06bc48df chore(smargon): tidy sim session config (reflow, comment out sgomega)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-09-01 17:37:52 +02:00
menzelandClaude Opus 4.8 6fcb2f359f refactor(smargon): per-axis motors + shared controller (smaract pattern)
Expose each SCS axis as its own top-level BEC motor sharing a singleton
SmargopoloController per host:port, plus a thin `dev.smargon` coordinator for
whole-goniometer ops. Replaces the single-device-with-axis-subcomponents design.

Why: BEC's motor UX (dev.wm, umv, limits, GUI motor widgets) is keyed on
top-level devices. With sub-components, dev.wm showed `readback N/A` (it looks up
a key equal to the device name) and `limits [0,0]` (the client reads limits from
the root device's low/high_limit_travel signals). General, not a sim artifact.

- controller.py: SmargopoloController(OphydObject), singleton per (host,port,sim);
  owns transport + lock + mode; referencing / move_scs / nudge / q1..q6 diagnostics.
  Reachable as dev.<axis>.controller.<op>() (USER_ACCESS).
- positioner.py: SmargopoloMotor(Device, PositionerBase) -- top-level per-axis motor.
  scs_axis field binds the (user-chosen) BEC name to the fixed SCS axis. Exposes
  low/high_limit_travel signals and aliases readback to the device name, so dev.wm
  shows real per-axis readback + limits (low==high => unbounded). egu auto from axis.
- smargon.py: SmarGon(PSIDeviceBase) reduced to a thin coordinator delegating to the
  shared controller; keeps read-only mode/rosout signals.
- YAML: smargon.yaml + smargon_sim_session.yaml rewritten to per-axis entries
  (sgx/sgy/sgz/sgchi/sgphi[/sgomega]) + coordinator. Names user-configurable; SCS
  binding explicit via scs_axis.
- transport/ layer + fake unchanged. Tests rewritten: 29 pass vs FakeTransport
  (singleton reset fixture; covers sharing, wm readback aliasing, limits, fault paths).

Still DRAFT / untested against hardware.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-09-01 17:37:51 +02:00
menzelandClaude Opus 4.8 eb440d3f92 feat(smargon): ground SCS limits + host guidance from MX-lab reference client
resources_2/Smargon.py (the MX-lab SmarGonController) gives real per-axis SCS
limits and the server hostname convention. Update smargon.yaml:
- SHX/SHY [-2,2], SHZ [10,22] (home ~18, not centred on 0), CHI [0,65],
  PHI [-180,180]  (was placeholder [-2.5,2.5] for the SH axes; SHZ was wrong).
- host: note it must be the actual reachable server (ref used mxlab1-smargopolo).
- Note PHI is continuous and the reference client wraps to [-180,180) before
  sending -- flagged as an open design question, not yet handled here.

Still DRAFT / untested against hardware.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-09-01 17:37:51 +02:00
menzelandClaude Opus 4.8 b4053f532b feat(smargon): scaffold SmarGon goniometer device (DRAFT, untested vs hardware)
Initial BEC ophyd integration for the SmarGon goniometer via the smargopolo
RESTful API. Controls the virtual SCS axes (the server runs the kinematics; the
underlying q1..q6 stages are never commanded directly). Structure mirrors the
Canon CR-N300 device: an ophyd-free transport (real RestTransport over urllib +
in-memory FakeTransport) with threaded poll-to-tolerance positioners and a
PSIDeviceBase parent.

Scope / decisions:
- v1 movable axes: SHX SHY SHZ CHI PHI; OMEGA optional via has_omega (YAML).
- Referencing is a deliberate operator action (reference()); moves refuse unless
  Mode.READY.
- Soft limits are user-set; smargopolo owns the coupled hardware limits. Both
  fault paths handled: up-front PUT rejection fails the move immediately, and a
  mid-move fault (mode -> 99 ERROR) aborts the in-flight move with the rosout
  detail (SmarGon._raise_if_error).
- Move completion = readback within (user-set) tolerance, done on first
  in-tolerance sample (robust to active position-hold dither).

Status: DRAFT. 27 unit tests pass against FakeTransport, but NOTHING has been
tested against a real smargopolo server or hardware. Do not commission as-is.

Open questions (to confirm with the smargopolo maintainer, W. Glettig):
- Stop semantics: no explicit stop endpoint; we abort by retargeting an axis to
  its current readback ("Follow Target" halt). Confirm this is the intended way.
- Mid-move fault signalling: we rely on /mode -> 99 (ERROR) with the reason in
  /readbackMCS rosout.msg. Confirm an out-of-range / coupled-limit violation
  reliably drives this, so the abort path is dependable.
- Per-poll cost: the mid-move check adds a /readbackMCS GET alongside the
  /readbackSCS position read (~20 req/s per moving axis at 0.1s). Revisit once we
  know whether /readbackSCS also carries mode, or once the per-motor `state`
  strings can give a truer moving/settled flag.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-09-01 17:37:50 +02:00
menzelandClaude Opus 5 b49c699ab6 feat(ddg2): make the detector-trigger readout time configurable
The gap between consecutive detector triggers is exp_time minus a readout
time that was a module constant fixed at 0.2 ms. Some detectors need more:
FalconcSAXS declares MIN_READOUT = 3 ms, fifteen times longer. Nothing
reconciled the two -- DDG2 does not know which detectors are in the scan,
and the falcon only validates its exposure time, never the gap -- so a
detector that cannot keep up silently dropped frames.

There was also no way to change it. on_stage recomputes the pulse width
from the constant on every scan, so a value set by hand from the client did
not survive to the next acquisition.

The readout times are now per-instance, settable two ways: a readout_times
key in deviceConfig for a per-deployment default, and set_readout_times()
via USER_ACCESS for a change between scans. Raising the value widens the gap
and shortens the exposure by the same amount; burst_period stays at exp_time,
so the frame rate is unaffected.

Channel pair 'ab' is the one multiplexed to the detectors and normally the
only one worth changing. A value that would exceed the exposure time is
rejected, and a non-default value is logged at on_stage so a scan taken with
a widened gap is recoverable from the logs.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KLnmUurqcNd1FiDY5M2uZr
2026-09-01 17:37:50 +02:00
x12sa 7d0375b216 config(falcon): disable pending EPICS reachability from the BEC host
CI for csaxs_bec / test (push) Successful in 2m52s
2026-09-01 17:35:17 +02:00
menzelandClaude Opus 5 d7ee8ef031 fix(falcon): set the layout the IOC can actually resolve, and verify it
_initialize_detector_backend put a bare "layout.xml" into the HDF5 plugin.
The IOC resolves that relative to its own working directory, where no such
file exists: the cSAXS SITORO IOC ships the layout as cfg/layout.xml
(installed at /ioc/X12SA-CPCL-FALCONX1/cfg/layout.xml), and that is what the
IOC configures at startup. BEC was overwriting a correct value with a broken
one on every device init.

The failure was silent and badly signposted. An unreadable layout does not
raise; the plugin simply refuses to open the output file later, reporting
"Error opening file ..., status=-1" with the actual cause buried in
XMLErrorMsg_RBV. Because a caget taken before a BEC device reload showed the
IOC's own valid value, the two readings disagreed and the layout looked
innocent.

The default is now cfg/layout.xml, overridable per deployment with an
xml_file_name key in deviceConfig ("" selects the plugin's built-in layout),
and on_connected reads XMLValid_RBV back and logs an error naming the file
and the IOC's message if the layout was rejected.

The existing on_connected test asserted the broken value; it now asserts the
default constant.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KLnmUurqcNd1FiDY5M2uZr
2026-09-01 16:37:59 +02:00
menzelandClaude Opus 5 f95b56cb62 feat(falcon): add prime() to arm the HDF5 plugin after an IOC restart
The areaDetector file plugin refuses to be staged until one NDArray has
passed through it, so every restart of the SITORO IOC leaves the Falcon
raising ophyd's UnprimedPlugin at the start of a scan. The generic
HDF5Plugin.warmup() cannot be used, as it drives parent.cam, which the
Falcon does not have.

prime() pushes a single spectrum through the plugin using user-advanced
pixels, so no external gate signal is required and a disconnected trigger
cable cannot mask the priming. No file is written.

The trigger configuration is captured beforehand and restored in a finally
block. on_stage never resets pixel_advance_mode, ignore_gate or
pixels_per_buffer -- only on_connected does -- so a prime that bailed out
halfway would otherwise leave the detector deaf to the gate and silently
starve every subsequent scan.

prime() and is_primed() are exposed via USER_ACCESS, so they are callable
from the BEC client as dev.falcon.prime().

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KLnmUurqcNd1FiDY5M2uZr
2026-09-01 16:37:58 +02:00
x12sa c17cae7458 config(falcon): add the Falcon SITORO fluorescence detector 2026-09-01 16:35:53 +02:00
menzelandClaude Opus 5 703a9b1939 feat(filters): accept fil_comb(1411) as well as fil_comb(1, 4, 1, 1)
The four-digit code is already the currency of this module: it is what
_all_combinations builds, what fil_trans prints as the selected
combination, and what _fil_trans_report prints for what is currently in
the beam. Requiring commas on the way back in meant reading a code off
the screen and retyping it as four separate arguments.

fil_comb now takes 1411, "1411", (1, 4, 1, 1) or [1, 4, 1, 1], so a
reported combination can be pasted straight back.

A code of the wrong length is rejected with the length it needs rather
than falling through to "expects 4 positions, got 1", which was the
unhelpful error a code would have produced before. bool is excluded from
the integer branch so fil_comb(True) does not quietly become "True".
Digits outside 1..6 are left to the existing per-unit range check, which
names the offending unit.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017kLGTqTyXzTT4swt3CfTkV
2026-08-30 13:50:50 +02:00
menzelandClaude Opus 5 5c2c0770a9 fix(filters): use the measured thicknesses and keep calibration foils out of fil_trans
Two ways the filter table disagreed with filter.mac rev 1.19.

The Si thicknesses were nominal where spec used values measured at
18.58 keV: 400/200/3200/100/1600/800 against 345.6/234.6/3303/137.5/
1661.5/833.8. Si100 vs Si137.5 is 37% out, and at 17 keV the pair
Si200+Si1600 gives T=0.060 where the measured Si234.6+Si1661.5 gives
0.052 -- a 16% error, larger than any distinction the search was trying
to make between neighbouring combinations.

The Zr and Cu calibration foils were selectable. filter.mac flagged them
disabled so that selection by transmission could not pick them, and at
17 keV fil_trans(0.05) duly returned 5246 -- a combination built on the
Zr foil, 1 keV below its K edge, where its transmission moves with any
small change in energy.

They are excluded through a new _EXCLUDE_FROM_SEARCH rather than by
clearing their 'enabled' flag, because those two things are not the same
and the flag is load-bearing elsewhere: _fil_trans_report substitutes
T=1.0 for a disabled position, so clearing the flag would have made the
report silently ignore a foil parked in the beam -- precisely the state
an edge scan puts it in. The foils stay enabled, stay addressable via
fil_comb as they were in spec, and are skipped only by the automatic
search.

At 17 keV fil_trans(0.05) now returns 2146 (Si345.6 + Ti200 + Ti20,
T=0.0494) and fil_trans(0.01) returns 2234 (T=0.0099), both matching an
independent calculation over the same CXRO tables.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017kLGTqTyXzTT4swt3CfTkV
2026-08-30 13:50:50 +02:00
menzelandClaude Opus 5 f2db469c9b refactor(filters): share the attenuation interlock between fil_trans and fil_comb
The DMM-through / CCM-active check now lives in _attenuation_allowed and
fil_trans calls it, so there is one copy rather than two drifting ones.
A guard that is duplicated is a guard that gets fixed in one place only.

Behaviour is unchanged: the same three PVs are read with the same
fallbacks, the same warning is printed, the same default-NO prompt is
raised through OMNYTools.yesno, the same "Safe fallback" of refusing
when no prompt is available applies, and fil_trans(1) still bypasses the
check entirely.

Split from the previous commit so that this half can be reverted on its
own if it misbehaves at the beamline, without taking fil_comb with it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017kLGTqTyXzTT4swt3CfTkV
2026-08-30 13:50:49 +02:00
menzelandClaude Opus 5 ffb49b664b feat(filters): add fil_comb for explicit filter combinations
fil_trans selects by transmission, which is the wrong tool when a
particular foil is wanted: it optimises against the thickness table and
which combination wins is not something to rely on. The alternative was
four hand-copied stage coordinates, where the out positions differ per
unit (25.0 / 25.5 / 25.8 / 25.0) and getting one wrong leaves the stage
off any tabulated slot -- _fil_trans_report matches within 0.1, so the
combination then reports as unidentified.

fil_comb takes one slot number per unit, 1..6 with 1 = out, as the SPEC
fil_comb did, and looks the coordinates up. It reuses
_position_transmission, _print_combination and _execute_combination
unchanged, so it reports and moves exactly as fil_trans does, including
the dry run and the default-yes prompt.

Every slot is validated before the safety prompt is reached, so a typo
raises rather than putting a question about an unexecutable combination
in front of someone. Disabled slots -- Fe5 and the redundant opens --
raise too: _all_combinations skips them, so fil_comb must refuse them
rather than move somewhere fil_trans would never choose.

_attenuation_allowed is added here but not yet wired into fil_trans;
that is the next commit, so this one cannot change existing behaviour.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017kLGTqTyXzTT4swt3CfTkV
2026-08-30 13:50:49 +02:00
x12sa 732c77222f comment out X-ray eye
CI for csaxs_bec / test (push) Successful in 1m59s
2026-08-27 15:08:30 +02:00
x12sa ddb4e64f21 add in position values and recommended gain for XBPM3 2026-08-27 15:07:49 +02:00
menzel 648c0e9a2f fix(cont_grid): wait for the move to reach the motor before triggering
CI for csaxs_bec / test (push) Successful in 2m5s
at_each_point issues the line move and then sleeps before firing the
burst. The sleep was acc_time alone, which covers the acceleration ramp
and nothing else -- while the move still has to cross the scan server ->
device server -> EPICS -> controller path first. acc_time shrinks with
the scan velocity; that path does not. On a slow scan the burst
therefore began well before the stage moved, and the first points of
every line piled up at the line start.

Measured from the position readback of five commissioning scans, as the
distance the stage was behind the trigger grid once the lag stopped
growing:

    scan   exp_time   v_cmd         lag       latency
     254     20 ms    0.5   mm/s   29.7 pts   0.594 s
     450     50 ms    0.1   mm/s   11.9 pts   0.595 s
     324     15 ms    0.667 mm/s   41.5 pts   0.623 s
     473    100 ms    0.065 mm/s    6.3 pts   0.630 s
     411     50 ms    0.1   mm/s   12.7 pts   0.635 s

Constant to +-3.5% across a 6.7x range of exposure and a 10x range of
velocity, on two axes. Scan 473 is sampled 74 times per line and shows
the shape plainly: the lag appears in the first interval and then holds
at 6.3-6.4 points for the rest of the line while the velocity sits at
exactly the commanded 0.065 mm/s. A start-up offset, not a velocity
error, and the stage itself is blameless.

Deliberately not solved by polling for motion. Observing the readback
costs a round trip of this same ~0.6 s, so it would trade a systematic
offset for a jitter of similar size -- and a constant offset displaces
every line equally, where a varying one shears the image line by line
and cannot be undone afterwards. The reproducibility is the asset here,
not the enemy.

The value lives on ddg1 next to the shutter delay, with a setter in
USER_ACCESS, because that is where cont_grid already fetches its trigger
timing. It is not a property of the delay generator and the docstring
says so. Setting it to 0 reproduces exactly the previous timing without
a redeploy, which is the intended way back if this makes things worse.

The real fix is to trigger the DDG from the motor
(scan_type: hardware_triggered), taking the round trip out of the timing
chain rather than compensating for it. This is the stopgap until then,
and it rests on the latency staying constant -- which is worth
re-checking with the same measurement whenever the deployment changes.
2026-08-25 23:35:09 +02:00
menzel f17af60a27 fix(ddg1): give the shutter the time it actually needs to open
The first point of every line is under-exposed. Four commissioning scans
put a number on it, against the 2e-3 head start that was in place:

    scan   exp_time   first/near   lost
     324     15 ms       0.655     5.18 ms
     254     20 ms       0.732     5.36 ms
     411     50 ms       0.881     5.93 ms
     450     50 ms       0.879     6.04 ms

A fixed time, not a fixed fraction: it varies by 17% across a 3.3x range
of exposure while the fraction varies by 2.9x. The same deficit appears
on the integrated scattering, a different detector behind a different
gate, so the cause is upstream of both readout chains rather than in
either of them. 2e-3 allowed and ~5.6e-3 still lost means the shutter
needs about 7.6 ms, rounded up to 8 ms: the spread across the four scans
is 0.9 ms, so the third digit is not meaningful, and overshooting costs
only the difference in dead time at the start of each line.

The trigger scheme was already right -- the shutter fires on cd at t0
and the acquisition on ab is held back by _shutter_to_open_delay, with
the widths, burst_period and cont_grid's acc_time and premove all
derived from it. Only the value was wrong, and it was a literal in two
places, so setting one and not the other would have been silently undone
by keep_shutter_open_during_scan.

Lifts it to DEFAULT_SHUTTER_TO_OPEN_DELAY next to the other defaults,
with the measurement recorded, and adds set_shutter_to_open_delay to
USER_ACCESS so the value can be converged from the client instead of by
redeploying the device server. It is bounded, because a delay is paid on
every line and a fat-fingered value would stretch the scan rather than
fail.

Cost at the new value is 6 ms per line -- 0.26 s over scan 450 -- and
0.8 um of extra premove.

The existing stage test asserted the 2e-3 literal and now asserts the
constant. New tests cover the default, the bound, the USER_ACCESS entry,
that a set value actually reaches the ab channel while cd still fires at
t0, and that keep_shutter_open_during_scan discards a tuned value, which
is a sharp edge worth pinning rather than leaving to be rediscovered.
2026-08-25 23:35:08 +02:00
x12sa 5ba59818d8 added comment for nominal xbpm2x position according to Excel file
CI for csaxs_bec / test (push) Successful in 1m49s
2026-08-25 11:44:57 +02:00
x12sa 58939eac7e added comment for settings of microstepping card 2026-08-25 11:43:06 +02:00
x12sa 72bb31951c update IDS camera ID 2026-08-25 11:42:14 +02:00
x12saandClaude Sonnet 5 2b581c1a9c feat(flomni): add A4 report Print button to sample-storage widget
CI for csaxs_bec / test (pull_request) Successful in 1m44s
Read the Docs Deploy Trigger / trigger-rtd-webhook (push) Successful in 1s
CI for csaxs_bec / test (push) Successful in 3m4s
Adds a Print button to OMNY_SampleStorage that renders an A4 PDF
replica of the on-screen stage/gripper/magazine grid (via
bec_lib.pdf_writer.PDFWriter) and sends it to the local CUPS queue
WSLA_X12SA via `lp`. Checks printer availability with a quick local
`lpstat` query first, so a host without that queue gets a plain
"printer not available" message instead of a raw CUPS error; the
actual `lp` call still offers Retry/Ignore on failure, mirroring the
existing P-touch label-print fail-soft pattern.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BgQoxFiJRZi1FnmnF8epKh
2026-08-20 16:02:37 +02:00
wakonig_k 71a1ac7ec5 feat(hexapod): add hexapod configuration and pivot device class
CI for csaxs_bec / test (pull_request) Successful in 1m47s
Read the Docs Deploy Trigger / trigger-rtd-webhook (push) Successful in 1s
CI for csaxs_bec / test (push) Successful in 1m40s
2026-08-19 16:17:10 +02:00
x12saandClaude Sonnet 5 a20be61251 fix(tests): stub measured_log in flomni tomo-angle test helper
CI for csaxs_bec / test (pull_request) Successful in 1m40s
Read the Docs Deploy Trigger / trigger-rtd-webhook (push) Successful in 2s
CI for csaxs_bec / test (push) Successful in 1m39s
make_flomni_for_golden_resume() builds a bare Flomni via object.__new__,
bypassing __init__, so it never got measured_log or a working
sample_get_measured_log_key -- both added by the measured-sample-tracking
commit for the P-touch label printer.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-19 15:34:58 +02:00
x12saandClaude Sonnet 5 a31fe481a7 feat(flomni): track measured samples and offer a P-touch label print on clear/replace
CI for csaxs_bec / test (push) Failing after 1m39s
CI for csaxs_bec / test (pull_request) Failing after 1m44s
Records whether a tomo scan was started/completed for the sample
currently on the stage (a new _MeasuredSampleLog, redis-backed via
BEC global vars, keyed by the packed name|owner identity so it
survives ftransfer_get_sample/put_sample moves between slots). When a
tray slot's occupant is cleared or replaced via
ftransfer_modify_storage() (CLI) or the sample-storage widget's
Clear/Change-name actions (GUI), and that sample was measured, offer
to print an account/date/samplename label via PTouchLabelPrinter.
Also surfaces measured status in ftransfer_show_all() and the
widget's slot cells, including the stage line.

GUI failures retry via QMessageBox Retry/Ignore (a modal dialog runs
its own Qt event loop, so this can't hang the process the way the
CLI's input()-based ensure_ready() would) instead of losing the label
on the first failed attempt.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-19 15:28:16 +02:00
bec-gitea 3f74191411 Updating to template version 1.5.1
CI for csaxs_bec / test (pull_request) Successful in 1m42s
Read the Docs Deploy Trigger / trigger-rtd-webhook (push) Successful in 2s
CI for csaxs_bec / test (push) Successful in 1m43s
2026-08-19 14:07:57 +02:00
holler fbc93e2876 Upload files to "csaxs_bec/bec_ipython_client/plugins/OMNY_shared"
Read the Docs Deploy Trigger / trigger-rtd-webhook (push) Successful in 2s
CI for csaxs_bec / test (push) Successful in 1m45s
2026-08-19 13:54:51 +02:00
x12saandClaude Sonnet 5 0ddab287c3 feat(flomni): add PTouchLabelPrinter for Brother PT-P950NW labels
Fills the named text objects of a pre-transferred P-touch Template and
triggers a print over raw TCP:9100, validated end-to-end against the
real printer at BRN94DDF8AAB8EC.psi.ch. Shared OMNY_shared utility, not
an ophyd device, per docs/developer/ptouch_label_printer_plan.md.

Not yet wired into flomni.py -- that's the next step.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-19 13:50:46 +02:00
holler b34ecb9b6c Upload files to "docs/developer" 2026-08-19 12:03:18 +02:00
x01dcandClaude Sonnet 5 1cbfdc40ea docs(flomni): add design plan for Brother P-touch label printer integration
Feasibility/design session output: print account+date labels on
sample-tray unload via P-touch Template mode, as a shared OMNY_shared
utility rather than an ophyd device.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UGC1TfxZJvJHs6Qz1V8w1h
2026-08-19 12:03:18 +02:00
x01dcandClaude Sonnet 5 8936071766 fix(flomni): stop OSA collision at fosa_in(), correct stale foptz init position
CI for csaxs_bec / test (pull_request) Successful in 1m45s
Read the Docs Deploy Trigger / trigger-rtd-webhook (push) Successful in 2s
CI for csaxs_bec / test (push) Successful in 1m44s
fosa_in() moved fosax/fosay/fosaz straight to their configured "in"
positions with no collision check. ffzp_info() already computed OSA-to-pin
clearance but never gated the move on it. Add a shared
_osa_remaining_space() helper (also used to deduplicate ffzp_info()'s own
calculation) and raise FlomniOpticsError in fosa_in() when the computed
clearance at the target position is <= 0.

Root cause of the specific collision seen: stage init's _align_setup()
hardcoded foptz to 23 instead of its calibrated "in" value of 17
(ptycho_flomni.yaml), 6 mm off from what fosaz's own "in" calibration
assumes. Hardcode foptz to 17 instead, and lock foptz.limits to +-0.1 mm
around it in both set_limits() and _align_setup() so any future move away
from 17 requires deliberately widening the limits first. Also corrects
the stale "in: 23" in ptycho_flomni.yaml's foptz userParameter to 17.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-19 10:15:52 +02:00
x01dcandClaude Sonnet 5 308b4edacd fix(flomni): abort sample transfer when stage-out is declined
CI for csaxs_bec / test (push) Successful in 1m45s
ftransfer_gripper_move() silently returned on "No" to the stage-out
confirmation, but its return was indistinguishable from success to its
callers (ftransfer_get_sample/ftransfer_put_sample), which then proceeded
to command the physical get/mount sequence on the controller -- moving
the gripper without the stage ever having moved out or the gripper being
positioned at the transfer coordinates. Now raises FlomniError instead,
which propagates out of both callers and stops the transfer before any
controller command is sent.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-18 15:19:17 +02:00
x01dcandClaude Sonnet 5 0a2c6a429c docs(omny_shared): update webpage-upload doc for omny.psi.ch cutover
CI for csaxs_bec / test (pull_request) Successful in 1m46s
Read the Docs Deploy Trigger / trigger-rtd-webhook (push) Successful in 3s
CI for csaxs_bec / test (push) Successful in 1m44s
Reflects the single-URL omny.psi.ch config (myfritz/omny-test list retired)
and replaces the omny-test.psi.ch-specific .svg note with the known
permission and intermittent-WAF issues on omny.psi.ch.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-18 14:54:57 +02:00
x01dcandClaude Sonnet 5 50d4f8e425 feat(flomni,lamni): repoint all omny endpoints from myfritz/omny-test to omny.psi.ch
CI for csaxs_bec / test (push) Successful in 1m51s
Cuts over the webpage status-page mirror, TomoIDManager's sample-counter
registration, and the samples-folder PDF upload to omny.psi.ch exclusively,
replacing v1p0zyg2w9n2k9c1.myfritz.net and omny-test.psi.ch everywhere.

Note: omny.psi.ch currently has known server-side issues (a permission
error on /upload.php, and a WAF blocking /samples/* outright) tracked
separately with PSI admins -- this switch is expected to be broken until
those are fixed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-18 14:42:10 +02:00
x01dcandClaude Sonnet 5 6bb8d8027d feat(flomni,lamni): mirror status-page uploads to omny.psi.ch
CI for csaxs_bec / test (push) Successful in 1m45s
Add omny.psi.ch as a third upload target alongside production and the
omny-test.psi.ch trial mirror.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-18 13:24:54 +02:00
x01dcandClaude Sonnet 5 5af6f82bb1 feat(bec_widgets): add data_viewer widget; rename OMNY widget classes
CI for csaxs_bec / test (pull_request) Successful in 1m57s
Read the Docs Deploy Trigger / trigger-rtd-webhook (push) Successful in 2s
CI for csaxs_bec / test (push) Successful in 1m48s
Add DataViewer, ported from debye_bec's bec_widgets/widgets/data_viewer,
adapted to this repo's parent()-first __init__ convention required by
bw-generate-cli.

Drop the "Widget" suffix from SampleStorage, SlitControl, and TomoParams
for naming consistency with DataViewer, then further rename for OMNY
namespacing and Designer-list ordering:
- SampleStorage -> OMNY_SampleStorage
- TomoParams -> OMNY_TomoParams
- XRayEye -> OMNY_XRayEye (XRayEye2DControl untouched)
- ConsoleButtonsWidget -> z_ConsoleButtonsWidget (sorts last)

Update the live gui.<area>.new("...") string-based widget lookups in
flomni/LamNI gui_tools.py that construct XRayEye/ConsoleButtonsWidget by
name, so they keep resolving after the rename. Delete now-stale generated
plugin/register/pyproject trios and regenerate everything via
`bw-generate-cli --target csaxs_bec` (client.py, designer_plugins.py, and
fresh per-widget trios under the new snake_case names).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VZHQaRmthXmyfn2pxP3dF9
2026-08-17 17:20:21 +02:00
x01dcandClaude Sonnet 5 67769d5ff8 fix(flomni,lamni): use .get() in tomo_queue_show()'s job-line formatter
CI for csaxs_bec / test (pull_request) Successful in 2m14s
Read the Docs Deploy Trigger / trigger-rtd-webhook (push) Successful in 2s
CI for csaxs_bec / test (push) Successful in 1m49s
A job's params dict only has whatever _TOMO_SCAN_PARAM_NAMES held when it
was snapshotted by tomo_queue_add(); a job persisted before a key was
added (e.g. fovx/fovy, tomo_circfov) raised KeyError on the hard p['key']
lookup, making the whole queue un-inspectable instead of just missing a
field.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-17 15:47:51 +02:00
x01dc 75cd5d0d00 fix(flomni,omny): expose motor tolerance to scan-server device proxy
CI for csaxs_bec / test (pull_request) Successful in 1m54s
Read the Docs Deploy Trigger / trigger-rtd-webhook (push) Successful in 2s
CI for csaxs_bec / test (push) Successful in 1m56s
self.dev.fsamroy/.osamroy on the scan server is a generic bec_lib
Positioner proxy rebuilt from serialized device info, not the real
ophyd instance. tolerance was only a plain __init__ attribute, so it
was dropped during serialization and flomni_rotation()/rotation()
crashed with AttributeError: 'Positioner' object has no attribute
'tolerance'. Register tolerance in USER_ACCESS so it serializes and
is reachable via RPC like controller already is.
2026-08-17 11:31:04 +02:00
x01dcandClaude Sonnet 5 4ac20013b8 fix(tomo_params): debounce beamline-busy banner to stop flicker
Read the Docs Deploy Trigger / trigger-rtd-webhook (push) Successful in 2s
CI for csaxs_bec / test (push) Successful in 1m49s
_refresh_busy_banner() toggled the banner's visibility synchronously
on every poll tick and scan-queue push message, so brief blips in the
underlying queue status made it flicker on/off.

Show busy immediately, but hold the banner up for 5s of continuous
idle before clearing it, mirroring the debounce pattern already used
for XRayEye's queue-guarded toggles (_queue_idle_timer).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-14 10:52:33 +02:00
x01dcandClaude Sonnet 5 abb933e10d fix(tests): patch input() where yesno() actually calls it
CI for csaxs_bec / test (pull_request) Successful in 1m52s
Read the Docs Deploy Trigger / trigger-rtd-webhook (push) Successful in 2s
CI for csaxs_bec / test (push) Successful in 1m48s
find_rotation_center()'s y/n prompts now go through OMNYTools.yesno()
(omny_general_tools.py) instead of a raw input() in x_ray_eye_align.py,
but these tests were still patching input() on the old module -- so
the real input() got called under pytest's captured stdin and raised
"reading from stdin while output is captured".

Patch input() on omny_general_tools instead, where yesno() actually
calls it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-14 10:39:12 +02:00