MODE-2: DMM physical insert (Mono) / bypass (Pink) is done by setting the three DMM Y motors (m26=dmm_usy_ob, m27=dmm_usy_ib, m29=dmm_dsy) to 0 (in) or -10 mm (out) in parallel with the Bragg-arm park values, via the same energy set coordinated move; no special sequencing between in/out and Bragg-arm moves (all motors get put() commands in dict-iteration order wit
h ~0.1 s spacing then a single AllDone wait); the A-shutter is currently operator-managed (close/open calls commented out as a temporary XANES patch — intended permanent behaviour is conditional close for large moves to prevent filter-frame vacuum bumps); no interlock
MODE-2: Inspected the deployed move.py::motors() directly at /home/beams/2BMB/epics/synApps/support/energy/src/energy/move.py (decarlof/energy fork) — this is the canonical "energy change" workflow that handles mode switching too. Concrete answers below.
What moves
When the operator runs energy set --mode <Mono|Pink> --energy <value> and the mode changes (or the energy changes within a mode), the IOC writes 18 PVs in sequence: the 17 keys matching energy_move* and the 1 key matching energy_pos* (currently just fltr1select, the downstream filter paddle) for the target (mode, energy) row of energy2bm.json store_0:
| # |
Key |
PV |
Mono value (typical) |
Pink value (typical) |
| 1 |
energy_move_m1angl |
virtual mirror angle |
2.615 mrad |
2.615 mrad (constant) |
| 2 |
energy_move_m1avg |
virtual mirror Y average |
0.0 mm |
0.0 mm (constant) |
| 3 |
energy_move_dmm_usx |
2bma:m25 |
111.0 mm |
111.0 mm (constant) |
| 4 |
energy_move_dmm_dsx |
2bma:m28 |
104.0 mm |
104.0 mm (constant) |
| 5 |
energy_move_dmm_usy_ob |
2bma:m26 |
0.0 mm (in) |
-10.0 mm (out) |
| 6 |
energy_move_dmm_usy_ib |
2bma:m27 |
0.0 mm (in) |
-10.0 mm (out) |
| 7 |
energy_move_dmm_dsy |
2bma:m29 |
0.0 mm (in) |
-10.0 mm (out) |
| 8 |
energy_move_dmm_us_arm |
2bma:m30 |
per energy (e.g. 1.131° @ 13.374 keV) |
0.740° (parked) across all 4 Pink |
| 9 |
energy_move_dmm_ds_arm |
2bma:m31 |
per energy |
0.751° (parked) across all 4 Pink |
| 10 |
energy_move_dmm_m2_y |
2bma:m32 |
per energy |
17.020045 mm (parked) across all 4 Pink |
| 11 |
energy_move_table3y |
2bma:? downstream table Y |
per energy |
per energy |
| 12 |
energy_move_flag |
2bma:m44 |
per energy (e.g. 23.0 @ 13.374) |
0.0 (parked) across all 4 Pink |
| 13 |
energy_move_b_slit_top |
2bma:m9 |
per energy |
10.0 (wide open) across all 4 Pink |
| 14 |
energy_move_b_slit_bot |
2bma:m10 |
per energy |
-10.0 (wide open) across all 4 Pink |
| 15 |
energy_move_m1mox |
2bma:m1 (table-X support corner 0) |
8.0 mm |
per Pink energy (8/10/10/29) |
| 16 |
energy_move_m1_horizontal |
2bma:m3 (mirror stripe selector) |
1.0 mm (stripe a) |
per Pink energy (3.039/13/39/49) |
| 17 |
energy_move_m1m2x |
2bma:m4 (table-X support corner 2) |
8.0 mm |
per Pink energy (8/10/10/29) |
| 18 |
energy_pos_fltr1select |
downstream filter paddle index |
per (mode, energy) |
per (mode, energy) |
The DMM Y motors m26 / m27 / m29 are the physical in/out actuators — drive to 0 mm to insert the DMM into the beam (Mono), drive to -10 mm to retract (Pink). The 10 mm retraction is enough to clear the beam envelope. The X motors m25 / m28 stay at their fixed alignment positions in both modes — they don't translate the DMM laterally as a mode-switch action (see ENERGY-
5 / cora#254).
The DMM Bragg arms m30 / m31 and m2_y (m32) are also touched on every mode change — in Pink they're parked at fixed retracted values (m30 = 0.740°, m31 = 0.751°, m32 = 17.020045 mm) regardless of the requested Pink energy; in Mono they're set per energy from the calibration table (see ENERGY-1 / cora#249).
In what order
The move.py::motors() loop iterates the dict in insertion order (Python 3.7+ guaranteed), which matches the JSON key order shown in the table above. For each key it does:
epics_pvs[key].put(pos) # asynchronous, no wait
time.sleep(.1) # 0.1 s spacing between puts
So all 18 put() commands fire over ~1.8 seconds total, then the IOC waits for AllDoneA AND AllDoneB to clear before returning. All motors move in parallel for most of the move time — there's no sequencing between, for example, "DMM Y retracts" (step 5-7) and "Bragg arms park" (step 8-10). They start moving within ~0.5 s of each other and proceed concurrently.
Important: there is no interlock between the DMM Y retraction and the Bragg-arm retraction. In a Mono → Pink switch, the Y motors drop the tank while the Bragg arms simultaneously swing to their parked positions. The substrate geometry clears in both directions throughout the motion (the crystals don't collide during retraction) but there's no software gate that says "wait fo
r DMM Y to be at -10 before letting the Bragg arms move".
A-shutter handling — currently operator-managed (XANES-friendly patch); intended behaviour is conditional close based on energy-change magnitude
The deployed move.py has both the front-end shutter close AND open calls explicitly commented out:
log.warning('closing A-shutter')
# epics_pvs['CloseShutter'].put(1, wait=True)
# close_frontend_shutter(epics_pvs)
log.warning('opening shutter after energy change has been disabled')
# open_frontend_shutter(epics_pvs)
So the A-shutter (front-end shutter S02BM-PSS:FES:) is NOT touched during the energy / mode change in the current deployment. Note that the close-side log line still prints "closing A-shutter" misleadingly; the open-side log says "disabled" explicitly.
This is a temporary patch, not the intended permanent behaviour. Operator clarification:
-
The commented-out close+open was introduced specifically to support XANES (X-ray absorption near-edge spectroscopy) workflows. In XANES the per-scan energy step is very small (~eV-scale around an absorption edge), and the operator wants the A-shutter to stay open continuously to preserve the thermal stability of the upstream optics. Auto-closing the FES for every XANES step
would introduce a thermal transient larger than the energy change itself.
-
The intended permanent behaviour is conditional close based on the requested energy-change magnitude:
| Energy-change magnitude |
A-shutter handling |
| Small (XANES, eV-scale step within or near a single calibrated energy) |
Keep open. Current commented-out behaviour is correct. |
| Large (between calibrated energies, Mono <-> Pink switch, any move covering > ~few keV) |
Close the A-shutter for the duration of the move to prevent vacuum bumps from the filter-holder frames crossing the beam during motion. |
The vacuum-bump risk comes from the filter paddle's mechanical frame intercepting the white beam as the assembly moves between calibrated positions — local heating, outgassing, pressure spike. Closing the FES upstream avoids it.
Until conditional logic is restored to move.py, the A-shutter is operator-managed. For large energy changes (including Mono <-> Pink mode switches) the operator should manually close the FES (S02BM-PSS:FES:CloseEPICSC) before invoking energy set and re-open it (OpenEPICSC) afterwards.
What is interlocked
Nothing software-level. The AllDoneA AND AllDoneB wait at the end of the move is the only post-move check; it's a "did all motors get to their targets within tolerance" check, not a safety interlock.
Hardware-level interlocks (PSS / ACIS / BLEPS) are upstream of the energy CLI and don't gate it — they gate the shutters and the source. If the PSS dropped during the mode switch, the FES would close on its own (independent of move.py), but the energy CLI wouldn't know and would continue commanding motor moves.
CORA-side application
The cora question's existing assumption ("DMM Y to about -10 out / 0 in, Bragg arms parked in pink; exact positions and sequence unknown") is structurally right — exact positions and (lack of) sequencing now confirmed. Recommended model:
- Mode-switch Method on the
Monochromator Asset: parameters are the target mode (Mono / Pink) and target energy. Internally invokes the same coordinated move as a per-energy change (no separate mode-switch primitive in the IOC).
- Per-motor mode/energy targets: model each of the 18 motors with its (mode, energy) → position lookup. Per ENERGY-5 / cora#254 some are mode-constant (DMM X motors), some are mode-state-only (DMM Y motors, two values), some are mode + energy curves (Bragg arms, mirror stripe in Pink).
- No mode-switch interlock to model on the cora side either — the move is "fire 18 put commands, wait for AllDone". Cora's Method completion predicate is the AllDone state, not a sequencing barrier.
- Operator-managed A-shutter (today) — but the natural future state is conditional automation. The cora model today should treat the A-shutter as a Plan-level wrapper around the
energy set Method: pre-step "close FES" + post-step "open FES" for any large move (between calibrated energies, Mono <-> Pink switch). Small XANES-style intra-energy scans should NOT wrap. When the
conditional close logic is eventually restored to move.py, the wrap can move into the Method itself; until then it's the operator's (or Plan author's) responsibility.
Doc updates landed in the same commit
procedures/item_005.rst — the set_energy_to_preselect stub previously claimed the A-shutter is auto-closed during the move; corrected to flag that the deployed code has both the close and open calls commented out and the A-shutter is operator-managed.
Cross-references
Net
MODE-2 answer: DMM physical insertion is via 3 Y motors (m26/m27/m29) driven to 0 (in / Mono) or -10 mm (out / Pink), in the same coordinated energy set move as the Bragg-arm park (no sequencing between Y-retraction and arm-park — all 18 motors fire put commands in ~1.8 s then single AllDone wait). No software interlock. A-shutter handling is currently o
perator-managed — the IOC close/open calls are commented out as a temporary XANES-friendly patch (small XANES steps should NOT close the shutter, to preserve optical thermal stability). Intended permanent behaviour is conditional close for large moves (between calibrated energies, Mono <-> Pink switch) to prevent vacuum bumps when the filter-holder frames cross the beam dur
ing motion. For large moves today, the operator should manually close + reopen the FES around energy set.
MODE-2: DMM physical insert (Mono) / bypass (Pink) is done by setting the three DMM Y motors (
m26=dmm_usy_ob,m27=dmm_usy_ib,m29=dmm_dsy) to 0 (in) or -10 mm (out) in parallel with the Bragg-arm park values, via the sameenergy setcoordinated move; no special sequencing between in/out and Bragg-arm moves (all motors getput()commands in dict-iteration order with ~0.1 s spacing then a single
AllDonewait); the A-shutter is currently operator-managed (close/open calls commented out as a temporary XANES patch — intended permanent behaviour is conditional close for large moves to prevent filter-frame vacuum bumps); no interlockMODE-2: Inspected the deployed
move.py::motors()directly at/home/beams/2BMB/epics/synApps/support/energy/src/energy/move.py(decarlof/energyfork) — this is the canonical "energy change" workflow that handles mode switching too. Concrete answers below.What moves
When the operator runs
energy set --mode <Mono|Pink> --energy <value>and the mode changes (or the energy changes within a mode), the IOC writes 18 PVs in sequence: the 17 keys matchingenergy_move*and the 1 key matchingenergy_pos*(currently justfltr1select, the downstream filter paddle) for the target (mode, energy) row ofenergy2bm.jsonstore_0:energy_move_m1anglenergy_move_m1avgenergy_move_dmm_usx2bma:m25energy_move_dmm_dsx2bma:m28energy_move_dmm_usy_ob2bma:m26energy_move_dmm_usy_ib2bma:m27energy_move_dmm_dsy2bma:m29energy_move_dmm_us_arm2bma:m30energy_move_dmm_ds_arm2bma:m31energy_move_dmm_m2_y2bma:m32energy_move_table3y2bma:?downstream table Yenergy_move_flag2bma:m44energy_move_b_slit_top2bma:m9energy_move_b_slit_bot2bma:m10energy_move_m1mox2bma:m1(table-X support corner 0)energy_move_m1_horizontal2bma:m3(mirror stripe selector)energy_move_m1m2x2bma:m4(table-X support corner 2)energy_pos_fltr1selectThe DMM Y motors
m26/m27/m29are the physical in/out actuators — drive to 0 mm to insert the DMM into the beam (Mono), drive to -10 mm to retract (Pink). The 10 mm retraction is enough to clear the beam envelope. The X motorsm25/m28stay at their fixed alignment positions in both modes — they don't translate the DMM laterally as a mode-switch action (see ENERGY-5 / cora#254).
The DMM Bragg arms
m30/m31andm2_y(m32) are also touched on every mode change — in Pink they're parked at fixed retracted values (m30 = 0.740°,m31 = 0.751°,m32 = 17.020045 mm) regardless of the requested Pink energy; in Mono they're set per energy from the calibration table (see ENERGY-1 / cora#249).In what order
The
move.py::motors()loop iterates the dict in insertion order (Python 3.7+ guaranteed), which matches the JSON key order shown in the table above. For each key it does:So all 18
put()commands fire over ~1.8 seconds total, then the IOC waits forAllDoneA AND AllDoneBto clear before returning. All motors move in parallel for most of the move time — there's no sequencing between, for example, "DMM Y retracts" (step 5-7) and "Bragg arms park" (step 8-10). They start moving within ~0.5 s of each other and proceed concurrently.Important: there is no interlock between the DMM Y retraction and the Bragg-arm retraction. In a Mono → Pink switch, the Y motors drop the tank while the Bragg arms simultaneously swing to their parked positions. The substrate geometry clears in both directions throughout the motion (the crystals don't collide during retraction) but there's no software gate that says "wait fo
r DMM Y to be at -10 before letting the Bragg arms move".
A-shutter handling — currently operator-managed (XANES-friendly patch); intended behaviour is conditional close based on energy-change magnitude
The deployed
move.pyhas both the front-end shutter close AND open calls explicitly commented out:So the A-shutter (front-end shutter
S02BM-PSS:FES:) is NOT touched during the energy / mode change in the current deployment. Note that the close-side log line still prints "closing A-shutter" misleadingly; the open-side log says "disabled" explicitly.This is a temporary patch, not the intended permanent behaviour. Operator clarification:
The commented-out close+open was introduced specifically to support XANES (X-ray absorption near-edge spectroscopy) workflows. In XANES the per-scan energy step is very small (~eV-scale around an absorption edge), and the operator wants the A-shutter to stay open continuously to preserve the thermal stability of the upstream optics. Auto-closing the FES for every XANES step
would introduce a thermal transient larger than the energy change itself.
The intended permanent behaviour is conditional close based on the requested energy-change magnitude:
The vacuum-bump risk comes from the filter paddle's mechanical frame intercepting the white beam as the assembly moves between calibrated positions — local heating, outgassing, pressure spike. Closing the FES upstream avoids it.
Until conditional logic is restored to
move.py, the A-shutter is operator-managed. For large energy changes (including Mono <-> Pink mode switches) the operator should manually close the FES (S02BM-PSS:FES:CloseEPICSC) before invokingenergy setand re-open it (OpenEPICSC) afterwards.What is interlocked
Nothing software-level. The
AllDoneA AND AllDoneBwait at the end of the move is the only post-move check; it's a "did all motors get to their targets within tolerance" check, not a safety interlock.Hardware-level interlocks (PSS / ACIS / BLEPS) are upstream of the energy CLI and don't gate it — they gate the shutters and the source. If the PSS dropped during the mode switch, the FES would close on its own (independent of
move.py), but the energy CLI wouldn't know and would continue commanding motor moves.CORA-side application
The cora question's existing assumption ("DMM Y to about -10 out / 0 in, Bragg arms parked in pink; exact positions and sequence unknown") is structurally right — exact positions and (lack of) sequencing now confirmed. Recommended model:
MonochromatorAsset: parameters are the target mode (Mono / Pink) and target energy. Internally invokes the same coordinated move as a per-energy change (no separate mode-switch primitive in the IOC).energy setMethod: pre-step "close FES" + post-step "open FES" for any large move (between calibrated energies, Mono <-> Pink switch). Small XANES-style intra-energy scans should NOT wrap. When theconditional close logic is eventually restored to
move.py, the wrap can move into the Method itself; until then it's the operator's (or Plan author's) responsibility.Doc updates landed in the same commit
procedures/item_005.rst— theset_energy_to_preselectstub previously claimed the A-shutter is auto-closed during the move; corrected to flag that the deployed code has both the close and open calls commented out and the A-shutter is operator-managed.Cross-references
Net
MODE-2 answer: DMM physical insertion is via 3 Y motors (
m26/m27/m29) driven to0(in / Mono) or-10mm (out / Pink), in the same coordinatedenergy setmove as the Bragg-arm park (no sequencing between Y-retraction and arm-park — all 18 motors fireputcommands in ~1.8 s then singleAllDonewait). No software interlock. A-shutter handling is currently operator-managed — the IOC close/open calls are commented out as a temporary XANES-friendly patch (small XANES steps should NOT close the shutter, to preserve optical thermal stability). Intended permanent behaviour is conditional close for large moves (between calibrated energies, Mono <-> Pink switch) to prevent vacuum bumps when the filter-holder frames cross the beam dur
ing motion. For large moves today, the operator should manually close + reopen the FES around
energy set.