feat: consolidate devices and improve undulator handling #28

Merged
wakonig_k merged 3 commits from feature/device_config_update into main 2026-10-06 13:55:06 +02:00
16 changed files with 1012 additions and 543 deletions

No files matched your search

+57
View File
@@ -0,0 +1,57 @@
from unittest.mock import patch
import pytest
from ophyd.sim import make_fake_device
from ophyd.status import Status
from xtreme_bec.devices.cff import Cff
@pytest.fixture
def cff():
return make_fake_device(Cff)("X07MA-", name="cff")
def test_set_confirms_energy_without_waiting_for_cff_readback(cff):
cff.cff.sim_put(1.8)
def confirm(value):
cff_put.assert_called_once_with(2.25, wait=True)
assert cff.cff.get() == 1.8
assert value == 1
status = Status()
status.set_finished()
return status
with (
patch.object(cff.cff, "put") as cff_put,
patch.object(cff.confirm_energy, "set", side_effect=confirm) as confirm_set,
):
status = cff.set(2.25)
status.wait(timeout=1)
assert status.success
confirm_set.assert_called_once_with(1)
def test_failed_cff_write_does_not_confirm_energy(cff):
with (
patch.object(cff.cff, "put", side_effect=RuntimeError("CFF write failed")),
patch.object(cff.confirm_energy, "set") as confirm_set,
):
with pytest.raises(RuntimeError, match="CFF write failed"):
cff.set(2.25)
confirm_set.assert_not_called()
def test_set_returns_confirmation_status(cff):
confirmation = Status()
with patch.object(cff.confirm_energy, "set", return_value=confirmation):
status = cff.set(2.25)
assert status is confirmation
assert not status.done
confirmation.set_exception(RuntimeError("Energy confirmation failed"))
with pytest.raises(RuntimeError, match="Energy confirmation failed"):
status.wait(timeout=1)
+59
View File
@@ -0,0 +1,59 @@
from unittest import mock
import pytest
from bec_server.scan_server.tests.scan_fixtures import (
MockCustomDevice,
device_manager,
nth_done_status_mock,
readout_priority,
v4_scan_assembler,
)
@pytest.fixture
def otf_scan_assembler(device_manager, readout_priority, v4_scan_assembler):
"""Use BEC's assembler with the beamline-specific signals and RPC responses."""
device_signals = {
"mono": ["sync_with_undulator"],
"mirror_theta": ["base_velocity", "velocity"],
"grating_beta": ["base_velocity", "velocity"],
"signals": ["store_updates"],
}
for name, signals in device_signals.items():
device = MockCustomDevice(
name,
device_info={
"signals": {
signal: {
"obj_name": f"{name}_{signal}",
"kind_str": "config",
"describe": {"precision": 3},
}
for signal in signals
}
},
limits=(250, 2000) if name == "mono" else (-10, 10),
)
for signal in signals:
getattr(device, signal).put = mock.MagicMock()
device_manager.add_device(device)
mono = device_manager.devices["mono"]
mono.set = mock.MagicMock()
mono.get_max_speeds = mock.MagicMock(return_value=(0.02, 0.03))
mono.set_progress_range = mock.MagicMock()
readout_priority.monitored_devices = ["mono", "signals"]
def assemble(e1=500.0, e2=800.0, time=2.0):
scan = v4_scan_assembler("otf_scan", e1=e1, e2=e2, time=time)
# The shared fixture's device dictionary needs aliases for dev.<name> access.
for name in device_signals:
setattr(scan.dev, name, scan.dev[name])
return scan
return assemble
@pytest.fixture
def otf_scan(otf_scan_assembler):
return otf_scan_assembler()
+164
View File
@@ -0,0 +1,164 @@
from unittest import mock
import pytest
from bec_lib.bec_errors import ScanInputValidationError
from bec_server.scan_server.scans.scan_base import ScanType
from bec_server.scan_server.tests.scan_hook_tests import DEFAULT_HOOK_TESTS, run_scan_tests
from xtreme_bec.scans.otf_scan import OTFScan
@pytest.mark.parametrize(("hook_name", "hook_tests"), DEFAULT_HOOK_TESTS)
def test_default_hooks(otf_scan, nth_done_status_mock, hook_name, hook_tests):
run_scan_tests(otf_scan, [(hook_name, hook_tests)], nth_done_status_mock)
def test_assembly(otf_scan):
assert isinstance(otf_scan, OTFScan)
assert otf_scan.scan_info.scan_type == ScanType.HARDWARE_TRIGGERED
assert (otf_scan.e1, otf_scan.e2, otf_scan.time) == (500.0, 800.0, 2.0)
assert otf_scan.mono is otf_scan.dev["mono"]
assert otf_scan._baseline_readout_status is None
@pytest.mark.parametrize(
"parameters",
[{"e1": 249.0}, {"e1": 2001.0}, {"e2": 249.0}, {"e2": 2001.0}, {"time": -1.0}, {"time": 0.0}],
)
def test_assembly_rejects_invalid_scan_parameters(otf_scan_assembler, parameters):
with pytest.raises(ScanInputValidationError):
otf_scan_assembler(**parameters)
@pytest.mark.parametrize(("e1", "e2"), [(250.0, 2000.0), (2000.0, 250.0)])
def test_assembly_accepts_energy_boundaries(otf_scan_assembler, e1, e2):
scan = otf_scan_assembler(e1=e1, e2=e2, time=0.1)
assert (scan.e1, scan.e2, scan.time) == (e1, e2, 0.1)
def test_prepare_routes_adc_to_async_and_reports_mono_progress(otf_scan, nth_done_status_mock):
baseline_status = nth_done_status_mock()
otf_scan.actions.read_baseline_devices = mock.MagicMock(return_value=baseline_status)
otf_scan.prepare_scan()
assert otf_scan.scan_info.readout_priority_modification["async"] == ["signals"]
assert otf_scan.scan_info.scan_report_instructions == [{"device_progress": ["mono"]}]
priority = otf_scan.scan_info.readout_priority_modification
assert [device.name for device in otf_scan.dev.monitored_devices(priority)] == ["mono"]
assert [device.name for device in otf_scan.dev.async_devices(priority)] == ["signals"]
assert otf_scan._baseline_readout_status is baseline_status
assert baseline_status.wait_calls == 0
@pytest.mark.parametrize(("e1", "e2"), [(500.0, 800.0), (800.0, 500.0)])
def test_pre_scan_positions_before_configuring_scan_speed(otf_scan_assembler, e1, e2):
scan = otf_scan_assembler(e1=e1, e2=e2, time=1.5)
calls = mock.MagicMock()
calls.attach_mock(scan.dev.mirror_theta.base_velocity.put, "theta_base")
calls.attach_mock(scan.dev.grating_beta.base_velocity.put, "beta_base")
calls.attach_mock(scan.dev.mirror_theta.velocity.put, "theta_speed")
calls.attach_mock(scan.dev.grating_beta.velocity.put, "beta_speed")
calls.attach_mock(scan.mono.set, "move")
calls.attach_mock(scan.mono.get_max_speeds, "max_speeds")
calls.attach_mock(scan.mono.set_progress_range, "progress")
calls.attach_mock(scan.mono.sync_with_undulator.put, "sync")
scan.actions.pre_scan_all_devices = mock.MagicMock()
calls.attach_mock(scan.actions.pre_scan_all_devices, "pre_scan")
scan.pre_scan()
assert calls.mock_calls == [
mock.call.theta_base(0.008),
mock.call.beta_base(0.008),
mock.call.theta_speed(0.08),
mock.call.beta_speed(0.08),
mock.call.move(e1),
mock.call.move().wait(),
mock.call.max_speeds(e1=e1, e2=e2, travel_time=90.0),
mock.call.progress(e1=e1, e2=e2),
mock.call.theta_base(0.002),
mock.call.beta_base(0.003),
mock.call.theta_speed(0.02),
mock.call.beta_speed(0.03),
mock.call.sync(1),
mock.call.pre_scan(),
]
@pytest.mark.parametrize("resolve_after", [1, 2, 4])
def test_scan_core_stores_adc_and_reads_while_moving(otf_scan, nth_done_status_mock, resolve_after):
status = nth_done_status_mock(resolve_after=resolve_after)
calls = mock.MagicMock()
calls.attach_mock(otf_scan.dev.signals.store_updates.put, "store")
otf_scan.actions.set = mock.MagicMock(return_value=status)
otf_scan.actions.read_monitored_devices = mock.MagicMock()
calls.attach_mock(otf_scan.actions.set, "move")
calls.attach_mock(otf_scan.actions.read_monitored_devices, "read")
with mock.patch("xtreme_bec.scans.otf_scan.time.sleep") as sleep:
calls.attach_mock(sleep, "sleep")
otf_scan.scan_core()
assert calls.mock_calls == [
mock.call.store(1),
mock.call.move(otf_scan.mono, 800.0, wait=False),
*[
call
for _ in range(resolve_after - 1)
for call in (mock.call.read(), mock.call.sleep(1))
],
]
def test_at_each_point_reads_monitored_devices(otf_scan):
otf_scan.actions.read_monitored_devices = mock.MagicMock()
otf_scan.at_each_point()
otf_scan.actions.read_monitored_devices.assert_called_once_with()
@pytest.mark.parametrize("hook", ["post_scan", "on_exception"])
def test_cleanup_disables_acquisition_and_restores_velocity(otf_scan, hook):
calls = mock.MagicMock()
calls.attach_mock(otf_scan.dev.signals.store_updates.put, "store")
otf_scan.actions.complete_all_devices = mock.MagicMock()
calls.attach_mock(otf_scan.actions.complete_all_devices, "complete")
calls.attach_mock(otf_scan.mono.sync_with_undulator.put, "sync")
calls.attach_mock(otf_scan.dev.mirror_theta.base_velocity.put, "theta_base")
calls.attach_mock(otf_scan.dev.grating_beta.base_velocity.put, "beta_base")
calls.attach_mock(otf_scan.dev.mirror_theta.velocity.put, "theta_speed")
calls.attach_mock(otf_scan.dev.grating_beta.velocity.put, "beta_speed")
if hook == "on_exception":
otf_scan.on_exception(RuntimeError("scan interrupted"))
else:
otf_scan.post_scan()
assert calls.mock_calls == [
mock.call.store(0),
*([mock.call.complete()] if hook == "post_scan" else []),
mock.call.sync(0),
mock.call.theta_base(0.008),
mock.call.beta_base(0.008),
mock.call.theta_speed(0.08),
mock.call.beta_speed(0.08),
]
@pytest.mark.parametrize("has_baseline", [False, True])
def test_close_waits_for_baseline_before_closing_and_checking_statuses(otf_scan, has_baseline):
calls = mock.MagicMock()
otf_scan.actions.close_scan = calls.close
otf_scan.actions.check_for_unchecked_statuses = calls.check
otf_scan._baseline_readout_status = calls.baseline if has_baseline else None
otf_scan.close_scan()
assert calls.mock_calls == [
*([mock.call.baseline.wait()] if has_baseline else []),
mock.call.close(),
mock.call.check(),
]
+123 -112
View File
@@ -1,146 +1,157 @@
valve:
readoutPriority: baseline
description: "Endstation valve"
deviceClass: xtreme_bec.devices.VacuumValve
deviceConfig:
prefix: "X07MA-OP-VG13:"
onFailure: retry
enabled: true
readOnly: false
softwareTrigger: false
deviceTags:
- endstation
# BEC Device Configuration File
#
# The device config consists of a mapping of device names to their configurations.
#
# The following fields are required for each device:
# - deviceClass: The class of the device (e.g., ophyd_devices.SimPositioner).
# - enabled: A boolean indicating if the device is enabled.
# - readoutPriority: Determines how the device is read out during a scan. Possible values are ["monitored", "baseline", "async", "on_request", "continuous"].
#
# Optional fields (defaults shown):
# - connectionTimeout: Connection timeout in seconds. Default is 5.
# - description: A string description of the device. Default is "" (empty string).
# - deviceConfig: A dictionary of configuration parameters specific to the device class. Default is None.
# - deviceTags: A list/set of tags associated with the device. Default is an empty list/set.
# - needs: A list of other device names that this device depends on. Default is [].
# - onFailure: The action to take on failure. Possible values are ["buffer", "retry", "raise"]. Default is "retry".
# - readOnly: A boolean indicating if the device is read-only. Default is false.
# - softwareTrigger: A boolean indicating if the device uses software triggering. Default is false.
# - userParameter: A dictionary of user-defined parameters. Default is {}.
#
# Default values may be omitted for brevity.
##########################################################################################################################
filter:
readoutPriority: baseline
description: "Filter"
deviceClass: ophyd_devices.EpicsUserMotorVME
deviceConfig:
prefix: "X07MA-OP-DI2:TR1"
onFailure: retry
enabled: true
readOnly: false
softwareTrigger: false
deviceTags:
- endstation
goldmesh1:
readoutPriority: baseline
description: "Gold mesh 1"
deviceClass: ophyd_devices.EpicsMotor
deviceConfig:
prefix: "X07MA-OP-IO1:TR1"
onFailure: retry
enabled: true
readOnly: false
softwareTrigger: false
deviceTags:
- endstation
# An example device configuration with all fields:
# device1:
# deviceClass: ophyd_devices.SimPositioner
# description: Sample positioner device
# deviceConfig:
# delay: 1
# update_frequency: 10
# deviceTags:
# - frontend
# - motor
# enabled: true
# connectionTimeout: 20
# readoutPriority: baseline
# needs:
# - device2
# - device3
# onFailure: retry
# readOnly: false
# softwareTrigger: false
# userParameter:
# in: 23.1
# out: -50
##########################################################################################################################
goldmesh2:
readoutPriority: baseline
description: "Gold mesh 2"
deviceClass: ophyd_devices.EpicsMotor
deviceConfig:
prefix: "X07MA-OP-IO2:TR1"
onFailure: retry
enabled: true
readOnly: false
softwareTrigger: false
deviceTags:
- endstation
field_x:
readoutPriority: baseline
description: "Magnetic field x"
description: Magnetic field x
deviceClass: xtreme_bec.devices.X07MAMagnetAxis
deviceConfig:
axis_id: X
ps_prefix: "X07MA-PC-PS2:"
prefix: "X07MA-PC-MAG:"
onFailure: retry
enabled: true
readOnly: false
softwareTrigger: false
prefix: 'X07MA-PC-MAG:'
ps_prefix: 'X07MA-PC-PS2:'
deviceTags:
- endstation
field_z:
- endstation
enabled: true
readoutPriority: baseline
description: "Magnetic field z"
field_z:
description: Magnetic field z
deviceClass: xtreme_bec.devices.X07MAMagnetAxis
deviceConfig:
axis_id: Z
ps_prefix: "X07MA-PC-PS1:"
prefix: "X07MA-PC-MAG:"
onFailure: retry
enabled: true
readOnly: false
softwareTrigger: false
prefix: 'X07MA-PC-MAG:'
ps_prefix: 'X07MA-PC-PS1:'
deviceTags:
- endstation
- endstation
enabled: true
readoutPriority: baseline
filter:
description: Filter
deviceClass: ophyd_devices.EpicsUserMotorVME
deviceConfig:
prefix: X07MA-OP-DI2:TR1
deviceTags:
- endstation
enabled: true
readoutPriority: baseline
goldmesh1:
description: Gold mesh 1
deviceClass: ophyd_devices.EpicsMotor
deviceConfig:
prefix: X07MA-OP-IO1:TR1
deviceTags:
- endstation
enabled: true
readoutPriority: baseline
goldmesh2:
description: Gold mesh 2
deviceClass: ophyd_devices.EpicsMotor
deviceConfig:
prefix: X07MA-OP-IO2:TR1
deviceTags:
- endstation
enabled: true
readoutPriority: baseline
keithley_1:
readoutPriority: baseline
description: "Keithley 1 / 428 tey"
description: Keithley 1 / 428 gain
deviceClass: ophyd.EpicsSignal
deviceConfig:
read_pv: "X07MA-PC-K428:1:setGain"
onFailure: retry
enabled: true
readOnly: false
softwareTrigger: false
auto_monitor: true
read_pv: X07MA-PC-K428:1:setGain
deviceTags:
- endstation
- endstation
enabled: true
readoutPriority: baseline
keithley_2:
readoutPriority: baseline
description: "Keithley 2 / 428 i0"
description: Keithley 2 / 428 i0
deviceClass: ophyd.EpicsSignal
deviceConfig:
read_pv: "X07MA-PC-K428:2:setGain"
onFailure: retry
enabled: true
readOnly: false
softwareTrigger: false
auto_monitor: true
read_pv: X07MA-PC-K428:2:setGain
deviceTags:
- endstation
- endstation
enabled: true
readoutPriority: baseline
keithley_3:
readoutPriority: baseline
description: "Keithley 3 / 428 diode"
description: Keithley 3 / 428 diode
deviceClass: ophyd.EpicsSignal
deviceConfig:
read_pv: "X07MA-PC-K428:3:setGain"
onFailure: retry
enabled: true
readOnly: false
softwareTrigger: false
auto_monitor: true
read_pv: X07MA-PC-K428:3:setGain
deviceTags:
- endstation
- endstation
enabled: true
readoutPriority: baseline
signals:
readoutPriority: monitored
description: 'ADC signals'
description: ADC signals
deviceClass: xtreme_bec.devices.adc_signals.ADCSignals
deviceConfig:
prefix: 'X07MA-'
onFailure: retry
enabled: true
readOnly: false
softwareTrigger: false
prefix: X07MA-
deviceTags:
- endstation
- endstation
enabled: true
readoutPriority: monitored
temperature:
readoutPriority: baseline
description: "Temperature controller"
description: Temperature controller
deviceClass: xtreme_bec.devices.X07MATemperatureController
deviceConfig:
prefix: "X07MA-PC-TC:"
onFailure: retry
enabled: true
readOnly: false
softwareTrigger: false
prefix: 'X07MA-PC-TC:'
deviceTags:
- endstation
- endstation
enabled: true
readoutPriority: baseline
valve:
description: Endstation valve
deviceClass: xtreme_bec.devices.VacuumValve
deviceConfig:
prefix: 'X07MA-OP-VG13:'
deviceTags:
- endstation
enabled: true
readoutPriority: baseline
+171 -162
View File
@@ -1,209 +1,218 @@
idgap:
description: "Motor to control the IDGap of X07MA"
deviceClass: ophyd_devices.devices.undulator.UndulatorGap
deviceConfig:
prefix: "X07MA-UIND:"
onFailure: retry
enabled: true
readoutPriority: baseline
readOnly: false
softwareTrigger: false
deviceTags:
- frontend
# BEC Device Configuration File
#
# The device config consists of a mapping of device names to their configurations.
#
# The following fields are required for each device:
# - deviceClass: The class of the device (e.g., ophyd_devices.SimPositioner).
# - enabled: A boolean indicating if the device is enabled.
# - readoutPriority: Determines how the device is read out during a scan. Possible values are ["monitored", "baseline", "async", "on_request", "continuous"].
#
# Optional fields (defaults shown):
# - connectionTimeout: Connection timeout in seconds. Default is 5.
# - description: A string description of the device. Default is "" (empty string).
# - deviceConfig: A dictionary of configuration parameters specific to the device class. Default is None.
# - deviceTags: A list/set of tags associated with the device. Default is an empty list/set.
# - needs: A list of other device names that this device depends on. Default is [].
# - onFailure: The action to take on failure. Possible values are ["buffer", "retry", "raise"]. Default is "retry".
# - readOnly: A boolean indicating if the device is read-only. Default is false.
# - softwareTrigger: A boolean indicating if the device uses software triggering. Default is false.
# - userParameter: A dictionary of user-defined parameters. Default is {}.
#
# Default values may be omitted for brevity.
##########################################################################################################################
uind_mode:
description: "UIND mode of X07MA"
deviceClass: xtreme_bec.devices.uind_mode.UIndMode
deviceConfig:
onFailure: retry
enabled: true
readoutPriority: baseline
readOnly: false
softwareTrigger: false
deviceTags:
- frontend
uind_harmonic:
readoutPriority: baseline
description: "Undulator harmonic"
deviceClass: ophyd.EpicsSignalRO
deviceConfig:
read_pv: "X07MA-UIND:HARMONIC"
auto_monitor: true
onFailure: retry
enabled: true
readOnly: false
softwareTrigger: false
deviceTags:
- frontend
# An example device configuration with all fields:
# device1:
# deviceClass: ophyd_devices.SimPositioner
# description: Sample positioner device
# deviceConfig:
# delay: 1
# update_frequency: 10
# deviceTags:
# - frontend
# - motor
# enabled: true
# connectionTimeout: 20
# readoutPriority: baseline
# needs:
# - device2
# - device3
# onFailure: retry
# readOnly: false
# softwareTrigger: false
# userParameter:
# in: 23.1
# out: -50
##########################################################################################################################
uind_energy:
readoutPriority: baseline
description: "Undulator energy"
deviceClass: ophyd.EpicsSignalRO
deviceConfig:
read_pv: "X07MA-UIND:ENERGY-RBV"
auto_monitor: true
onFailure: retry
enabled: true
readOnly: false
softwareTrigger: false
deviceTags:
- frontend
energy:
readoutPriority: baseline
description: "Energy in eV"
description: Energy in eV
deviceClass: ophyd.EpicsSignal
deviceConfig:
read_pv: "X07MA-PHS-E:GO.A"
auto_monitor: true
onFailure: retry
enabled: true
readOnly: false
softwareTrigger: false
read_pv: X07MA-PHS-E:GO.A
deviceTags:
- frontend
energy_mode:
- frontend
enabled: true
readoutPriority: baseline
description: "Energy mode. Either PGM = 0 or PGM+ID = 1"
energy_mode:
description: Energy mode. Either PGM = 0 or PGM+ID = 1
deviceClass: ophyd.EpicsSignal
deviceConfig:
read_pv: "X07MA-PHS-E:OPT"
onFailure: retry
enabled: true
readOnly: false
softwareTrigger: false
auto_monitor: true
read_pv: X07MA-PHS-E:OPT
deviceTags:
- frontend
sl1xi:
description: "slit 1 (frontend) x ring"
deviceClass: ophyd_devices.EpicsMotorEC
deviceConfig:
prefix: "X07MA-FE-SL1:TRXR"
onFailure: raise
- frontend
enabled: true
readoutPriority: baseline
readOnly: false
softwareTrigger: false
deviceTags:
- frontend
sl1xo:
description: "slit 1 (frontend) x wall"
fe_slxc:
description: slit 1 (frontend) x center
deviceClass: ophyd_devices.EpicsMotorEC
deviceConfig:
prefix: "X07MA-FE-SL1:TRXW"
onFailure: raise
enabled: true
readoutPriority: baseline
readOnly: false
softwareTrigger: false
prefix: X07MA-FE-SL1:CENTERX
deviceTags:
- frontend
sl1yb:
description: "slit 1 (frontend) y bottom"
- frontend
enabled: true
onFailure: raise
readoutPriority: baseline
fe_slxi:
description: slit 1 (frontend) x ring
deviceClass: ophyd_devices.EpicsMotorEC
deviceConfig:
prefix: "X07MA-FE-SL1:TRYB"
onFailure: raise
enabled: true
readoutPriority: baseline
readOnly: false
softwareTrigger: false
prefix: X07MA-FE-SL1:TRXR
deviceTags:
- frontend
sl1yt:
description: "slit 1 (frontend) y top"
- frontend
enabled: true
onFailure: raise
readoutPriority: baseline
fe_slxo:
description: slit 1 (frontend) x wall
deviceClass: ophyd_devices.EpicsMotorEC
deviceConfig:
prefix: "X07MA-FE-SL1:TRYT"
onFailure: raise
enabled: true
readoutPriority: baseline
readOnly: false
softwareTrigger: false
prefix: X07MA-FE-SL1:TRXW
deviceTags:
- frontend
sl1xc:
description: "slit 1 (frontend) x center"
- frontend
enabled: true
onFailure: raise
readoutPriority: baseline
fe_slxs:
description: slit 1 (frontend) x size
deviceClass: ophyd_devices.EpicsMotorEC
deviceConfig:
prefix: "X07MA-FE-SL1:CENTERX"
onFailure: raise
enabled: true
readoutPriority: baseline
readOnly: false
softwareTrigger: false
prefix: X07MA-FE-SL1:SIZEX
deviceTags:
- frontend
sl1xs:
description: "slit 1 (frontend) x size"
- frontend
enabled: true
onFailure: raise
readoutPriority: baseline
fe_slyb:
description: slit 1 (frontend) y bottom
deviceClass: ophyd_devices.EpicsMotorEC
deviceConfig:
prefix: "X07MA-FE-SL1:SIZEX"
onFailure: raise
enabled: true
readoutPriority: baseline
readOnly: false
softwareTrigger: false
prefix: X07MA-FE-SL1:TRYB
deviceTags:
- frontend
sl1yc:
description: "slit 1 (frontend) y center"
- frontend
enabled: true
onFailure: raise
readoutPriority: baseline
fe_slyc:
description: slit 1 (frontend) y center
deviceClass: ophyd_devices.EpicsMotorEC
deviceConfig:
prefix: "X07MA-FE-SL1:CENTERY"
onFailure: raise
enabled: true
readoutPriority: baseline
readOnly: false
softwareTrigger: false
prefix: X07MA-FE-SL1:CENTERY
deviceTags:
- frontend
sl1ys:
description: "slit 1 (frontend) y size"
- frontend
enabled: true
onFailure: raise
readoutPriority: baseline
fe_slys:
description: slit 1 (frontend) y size
deviceClass: ophyd_devices.EpicsMotorEC
deviceConfig:
prefix: "X07MA-FE-SL1:SIZEY"
prefix: X07MA-FE-SL1:SIZEY
deviceTags:
- frontend
enabled: true
onFailure: raise
readoutPriority: baseline
fe_slyt:
description: slit 1 (frontend) y top
deviceClass: ophyd_devices.EpicsMotorEC
deviceConfig:
prefix: X07MA-FE-SL1:TRYT
deviceTags:
- frontend
enabled: true
onFailure: raise
readoutPriority: baseline
id_alpha:
description: Angle of linear polarization relative to the horizontal plane
deviceClass: xtreme_bec.devices.undulator.UndulatorAlpha
deviceConfig:
prefix: 'X07MA-UIND:'
deviceTags:
- frontend
enabled: true
readoutPriority: baseline
readOnly: false
softwareTrigger: false
id_energy:
description: Undulator energy
deviceClass: ophyd.EpicsSignal
deviceConfig:
auto_monitor: true
read_pv: X07MA-UIND:ENERGY-RBV
write_pv: X07MA-UIND:ENERGY-SP
deviceTags:
- frontend
- frontend
enabled: true
readoutPriority: baseline
id_gap:
description: Motor to control the IDGap of X07MA
deviceClass: xtreme_bec.devices.undulator.UndulatorGap
deviceConfig:
prefix: 'X07MA-UIND:'
deviceTags:
- frontend
enabled: true
readoutPriority: baseline
id_harmonic:
description: Undulator harmonic
deviceClass: xtreme_bec.devices.undulator.UndulatorHarmonic
deviceConfig:
prefix: 'X07MA-UIND:'
deviceTags:
- frontend
enabled: true
readoutPriority: baseline
id_mode:
description: UIND mode of X07MA
deviceClass: xtreme_bec.devices.undulator.UndulatorMode
deviceConfig:
prefix: 'X07MA-UIND:'
deviceTags:
- frontend
enabled: true
readoutPriority: baseline
xbpm1x:
description: "X-ray BPM1 in frontend translation x"
description: X-ray BPM1 in frontend translation x
deviceClass: ophyd_devices.EpicsMotorEC
deviceConfig:
prefix: "X07MA-FE-XBPM1:TRX"
onFailure: raise
enabled: true
readoutPriority: baseline
readOnly: false
softwareTrigger: false
prefix: X07MA-FE-XBPM1:TRX
deviceTags:
- frontend
- frontend
enabled: true
onFailure: raise
readoutPriority: baseline
xbpm1y:
description: "X-ray BPM1 in frontend translation y"
description: X-ray BPM1 in frontend translation y
deviceClass: ophyd_devices.EpicsMotorEC
deviceConfig:
prefix: "X07MA-FE-XBPM1:TRY"
onFailure: raise
enabled: true
readoutPriority: baseline
readOnly: false
softwareTrigger: false
prefix: X07MA-FE-XBPM1:TRY
deviceTags:
- frontend
- frontend
enabled: true
onFailure: raise
readoutPriority: baseline
+76 -37
View File
@@ -1,57 +1,96 @@
##############################################
################## Machine ###################
##############################################
# BEC Device Configuration File
#
# The device config consists of a mapping of device names to their configurations.
#
# The following fields are required for each device:
# - deviceClass: The class of the device (e.g., ophyd_devices.SimPositioner).
# - enabled: A boolean indicating if the device is enabled.
# - readoutPriority: Determines how the device is read out during a scan. Possible values are ["monitored", "baseline", "async", "on_request", "continuous"].
#
# Optional fields (defaults shown):
# - connectionTimeout: Connection timeout in seconds. Default is 5.
# - description: A string description of the device. Default is "" (empty string).
# - deviceConfig: A dictionary of configuration parameters specific to the device class. Default is None.
# - deviceTags: A list/set of tags associated with the device. Default is an empty list/set.
# - needs: A list of other device names that this device depends on. Default is [].
# - onFailure: The action to take on failure. Possible values are ["buffer", "retry", "raise"]. Default is "retry".
# - readOnly: A boolean indicating if the device is read-only. Default is false.
# - softwareTrigger: A boolean indicating if the device uses software triggering. Default is false.
# - userParameter: A dictionary of user-defined parameters. Default is {}.
#
# Default values may be omitted for brevity.
##########################################################################################################################
# An example device configuration with all fields:
# device1:
# deviceClass: ophyd_devices.SimPositioner
# description: Sample positioner device
# deviceConfig:
# delay: 1
# update_frequency: 10
# deviceTags:
# - frontend
# - motor
# enabled: true
# connectionTimeout: 20
# readoutPriority: baseline
# needs:
# - device2
# - device3
# onFailure: retry
# readOnly: false
# softwareTrigger: false
# userParameter:
# in: 23.1
# out: -50
##########################################################################################################################
curr:
description: SLS ring current
deviceClass: ophyd_devices.EpicsSignalRO
deviceConfig:
read_pv: AGEBD-PARAMS:CURRENT
auto_monitor: true
onFailure: buffer
enabled: true
readoutPriority: monitored
readOnly: true
softwareTrigger: false
read_pv: AGEBD-PARAMS:CURRENT
deviceTags:
- machine
- machine
enabled: true
onFailure: buffer
readOnly: true
readoutPriority: monitored
ofb:
description: SLS OFB status
deviceClass: ophyd_devices.EpicsSignalRO
deviceConfig:
read_pv: ARSGE-CECL-FOFB1:FOFB-OPERATE
auto_monitor: true
onFailure: buffer
enabled: true
readoutPriority: baseline
readOnly: true
softwareTrigger: false
read_pv: ARSGE-CECL-FOFB1:FOFB-OPERATE
deviceTags:
- machine
- machine
enabled: true
onFailure: buffer
readOnly: true
readoutPriority: baseline
operator:
description: SLS operator messages
deviceClass: ophyd_devices.devices.sls_devices.SLSOperatorMessages
deviceTags:
- machine
enabled: true
onFailure: buffer
readOnly: true
readoutPriority: on_request
shutter:
description: X07MA shutter status
deviceClass: ophyd_devices.EpicsSignalRO
deviceConfig:
read_pv: X07M-OP-PSYS:SH-A-OPEN
auto_monitor: true
onFailure: buffer
read_pv: X07M-OP-PSYS:SH-A-OPEN
deviceTags:
- machine
enabled: true
onFailure: buffer
readOnly: true
readoutPriority: baseline
readOnly: true
softwareTrigger: false
deviceTags:
- machine
operator:
description: SLS operator messages
deviceClass: ophyd_devices.devices.sls_devices.SLSOperatorMessages
deviceConfig:
onFailure: buffer
enabled: true
readoutPriority: on_request
readOnly: true
softwareTrigger: false
deviceTags:
- machine
+5 -6
View File
@@ -1,12 +1,11 @@
machine:
- !include ./machine.yaml
endstation:
- !include ./endstation.yaml
frontend:
- !include ./frontend.yaml
machine:
- !include ./machine.yaml
optics:
- !include ./optics.yaml
endstation:
- !include ./endstation.yaml
+156 -117
View File
@@ -1,191 +1,230 @@
cmu_y:
description: CMU y pseudo motor
deviceClass: xtreme_bec.devices.mirror_pseudo.MirrorPseudo
# BEC Device Configuration File
#
# The device config consists of a mapping of device names to their configurations.
#
# The following fields are required for each device:
# - deviceClass: The class of the device (e.g., ophyd_devices.SimPositioner).
# - enabled: A boolean indicating if the device is enabled.
# - readoutPriority: Determines how the device is read out during a scan. Possible values are ["monitored", "baseline", "async", "on_request", "continuous"].
#
# Optional fields (defaults shown):
# - connectionTimeout: Connection timeout in seconds. Default is 5.
# - description: A string description of the device. Default is "" (empty string).
# - deviceConfig: A dictionary of configuration parameters specific to the device class. Default is None.
# - deviceTags: A list/set of tags associated with the device. Default is an empty list/set.
# - needs: A list of other device names that this device depends on. Default is [].
# - onFailure: The action to take on failure. Possible values are ["buffer", "retry", "raise"]. Default is "retry".
# - readOnly: A boolean indicating if the device is read-only. Default is false.
# - softwareTrigger: A boolean indicating if the device uses software triggering. Default is false.
# - userParameter: A dictionary of user-defined parameters. Default is {}.
#
# Default values may be omitted for brevity.
##########################################################################################################################
# An example device configuration with all fields:
# device1:
# deviceClass: ophyd_devices.SimPositioner
# description: Sample positioner device
# deviceConfig:
# delay: 1
# update_frequency: 10
# deviceTags:
# - frontend
# - motor
# enabled: true
# connectionTimeout: 20
# readoutPriority: baseline
# needs:
# - device2
# - device3
# onFailure: retry
# readOnly: false
# softwareTrigger: false
# userParameter:
# in: 23.1
# out: -50
##########################################################################################################################
cff:
description: PGM CFF
deviceClass: xtreme_bec.devices.Cff
deviceConfig:
prefix: X07MA-OP-CMU
override_suffixes:
user_readback: :oy
user_setpoint: :ty.A
prefix: X07MA-
deviceTags:
- optics
enabled: true
readoutPriority: baseline
deviceTags:
- optics
cmu_z:
description: CMU z pseudo motor
deviceClass: xtreme_bec.devices.mirror_pseudo.MirrorPseudo
deviceConfig:
prefix: X07MA-OP-CMU
override_suffixes:
user_readback: :oz
user_setpoint: :tz.A
enabled: true
readoutPriority: baseline
deviceTags:
- optics
cmu_rx:
description: CMU Rx pseudo motor
deviceClass: xtreme_bec.devices.mirror_pseudo.MirrorPseudo
deviceConfig:
prefix: X07MA-OP-CMU
override_suffixes:
user_readback: :oRx
user_setpoint: :tRx.A
prefix: X07MA-OP-CMU
deviceTags:
- optics
enabled: true
readoutPriority: baseline
deviceTags:
- optics
cmu_ry:
description: CMU Ry pseudo motor
deviceClass: xtreme_bec.devices.mirror_pseudo.MirrorPseudo
deviceConfig:
prefix: X07MA-OP-CMU
override_suffixes:
user_readback: :oRy
user_setpoint: :tRy.A
prefix: X07MA-OP-CMU
deviceTags:
- optics
enabled: true
readoutPriority: baseline
deviceTags:
- optics
cmu_rz:
description: CMU Rz pseudo motor
deviceClass: xtreme_bec.devices.mirror_pseudo.MirrorPseudo
deviceConfig:
prefix: X07MA-OP-CMU
override_suffixes:
user_readback: :oRz
user_setpoint: :tRz.A
prefix: X07MA-OP-CMU
deviceTags:
- optics
enabled: true
readoutPriority: baseline
deviceTags:
- optics
pgm_id_coupling:
description: "PGM ID coupling"
deviceClass: ophyd_devices.EpicsSignal
deviceConfig:
read_pv: X07MA-PHS-E:OPT
enabled: true
readoutPriority: baseline
deviceTags:
- optics
mono:
readoutPriority: baseline
description: "PGM Monochromator"
deviceClass: xtreme_bec.devices.PGMMonochromator
deviceConfig:
prefix: "X07MA-"
onFailure: retry
enabled: true
readOnly: false
softwareTrigger: false
deviceTags:
- optics
mirror_theta:
readoutPriority: baseline
description: "Mirror theta"
deviceClass: ophyd_devices.EpicsUserMotorVME
deviceConfig:
prefix: "X07MA:m1"
onFailure: retry
enabled: true
readOnly: false
softwareTrigger: false
deviceTags:
- optics
grating_beta:
readoutPriority: baseline
description: "Grating beta"
deviceClass: ophyd_devices.EpicsUserMotorVME
deviceConfig:
prefix: "X07MA:m2"
onFailure: retry
enabled: true
readOnly: false
softwareTrigger: false
deviceTags:
- optics
fmu_y:
description: FMU y pseudo motor
cmu_y:
description: CMU y pseudo motor
deviceClass: xtreme_bec.devices.mirror_pseudo.MirrorPseudo
deviceConfig:
prefix: X07MA-OP-FMU
override_suffixes:
user_readback: :oy
user_setpoint: :ty.A
prefix: X07MA-OP-CMU
deviceTags:
- optics
enabled: true
readoutPriority: baseline
deviceTags:
- optics
fmu_z:
description: FMU z pseudo motor
cmu_z:
description: CMU z pseudo motor
deviceClass: xtreme_bec.devices.mirror_pseudo.MirrorPseudo
deviceConfig:
prefix: X07MA-OP-FMU
override_suffixes:
user_readback: :oz
user_setpoint: :tz.A
prefix: X07MA-OP-CMU
deviceTags:
- optics
enabled: true
readoutPriority: baseline
exit_slit:
description: Exit slit
deviceClass: ophyd_devices.EpicsSignal
deviceConfig:
auto_monitor: true
read_pv: X07MA-OP-SL1SV1:TR_ISAP
write_pv: X07MA-OP-SL1SV1:TR_AP
deviceTags:
- optics
- optics
enabled: true
readoutPriority: baseline
exit_slit_long:
description: Longitudinal translation exit slit
deviceClass: ophyd_devices.EpicsUserMotorVME
deviceConfig:
limits:
- 180.0
- 500.0
prefix: X07MA-OP-SL1:TRX
deviceTags:
- optics
enabled: true
readoutPriority: baseline
fmu_rx:
description: FMU Rx pseudo motor
deviceClass: xtreme_bec.devices.mirror_pseudo.MirrorPseudo
deviceConfig:
prefix: X07MA-OP-FMU
override_suffixes:
user_readback: :oRx
user_setpoint: :tRx.A
prefix: X07MA-OP-FMU
deviceTags:
- optics
enabled: true
readoutPriority: baseline
deviceTags:
- optics
fmu_ry:
description: FMU Ry pseudo motor
deviceClass: xtreme_bec.devices.mirror_pseudo.MirrorPseudo
deviceConfig:
prefix: X07MA-OP-FMU
override_suffixes:
user_readback: :oRy
user_setpoint: :tRy.A
prefix: X07MA-OP-FMU
deviceTags:
- optics
enabled: true
readoutPriority: baseline
deviceTags:
- optics
fmu_rz:
description: FMU Rz pseudo motor
deviceClass: xtreme_bec.devices.mirror_pseudo.MirrorPseudo
deviceConfig:
prefix: X07MA-OP-FMU
override_suffixes:
user_readback: :oRz
user_setpoint: :tRz.A
prefix: X07MA-OP-FMU
deviceTags:
- optics
enabled: true
readoutPriority: baseline
deviceTags:
- optics
exit_slit:
readoutPriority: baseline
description: "Aperture between focusing and refocusing mirror"
deviceClass: ophyd_devices.EpicsSignal
fmu_y:
description: FMU y pseudo motor
deviceClass: xtreme_bec.devices.mirror_pseudo.MirrorPseudo
deviceConfig:
read_pv: "X07MA-OP-SL1SV1:TR_AP"
onFailure: retry
enabled: true
readOnly: false
softwareTrigger: false
override_suffixes:
user_readback: :oy
user_setpoint: :ty.A
prefix: X07MA-OP-FMU
deviceTags:
- optics
- optics
enabled: true
readoutPriority: baseline
fmu_z:
description: FMU z pseudo motor
deviceClass: xtreme_bec.devices.mirror_pseudo.MirrorPseudo
deviceConfig:
override_suffixes:
user_readback: :oz
user_setpoint: :tz.A
prefix: X07MA-OP-FMU
deviceTags:
- optics
enabled: true
readoutPriority: baseline
grating_beta:
description: Grating beta
deviceClass: ophyd_devices.EpicsUserMotorVME
deviceConfig:
prefix: X07MA:m2
deviceTags:
- optics
enabled: true
readoutPriority: baseline
mirror_theta:
description: Mirror theta
deviceClass: ophyd_devices.EpicsUserMotorVME
deviceConfig:
prefix: X07MA:m1
deviceTags:
- optics
enabled: true
readoutPriority: baseline
mono:
description: PGM Monochromator
deviceClass: xtreme_bec.devices.PGMMonochromator
deviceConfig:
prefix: X07MA-
deviceTags:
- optics
enabled: true
readoutPriority: baseline
+2
View File
@@ -1,2 +1,4 @@
from .cff import Cff
from .pgm import PGMMonochromator
from .undulator import UndulatorAlpha, UndulatorGap, UndulatorHarmonic, UndulatorMode
from .x07ma_devices import *
+28 -4
View File
@@ -37,8 +37,20 @@ class NormSignal(Signal):
class NormTEYSignals(Device):
signal1 = Cpt(EpicsSignalRO, name="signal1", read_pv="X07MA-ES1-AI:SIGNAL0", kind="omitted")
signal2 = Cpt(EpicsSignalRO, name="signal2", read_pv="X07MA-ES1-AI:SIGNAL1", kind="omitted")
signal1 = Cpt(
EpicsSignalRO,
name="signal1",
read_pv="X07MA-ES1-AI:SIGNAL0",
kind="omitted",
auto_monitor=True,
)
signal2 = Cpt(
EpicsSignalRO,
name="signal2",
read_pv="X07MA-ES1-AI:SIGNAL1",
kind="omitted",
auto_monitor=True,
)
norm = Cpt(NormSignal, name="norm", kind="hinted")
def __init__(self, *args, **kwargs):
@@ -47,8 +59,20 @@ class NormTEYSignals(Device):
class NormDIODESignals(Device):
signal1 = Cpt(EpicsSignalRO, name="signal1", read_pv="X07MA-ES1-AI:SIGNAL3", kind="omitted")
signal2 = Cpt(EpicsSignalRO, name="signal2", read_pv="X07MA-ES1-AI:SIGNAL2", kind="omitted")
signal1 = Cpt(
EpicsSignalRO,
name="signal1",
read_pv="X07MA-ES1-AI:SIGNAL3",
kind="omitted",
auto_monitor=True,
)
signal2 = Cpt(
EpicsSignalRO,
name="signal2",
read_pv="X07MA-ES1-AI:SIGNAL2",
kind="omitted",
auto_monitor=True,
)
norm = Cpt(NormSignal, name="norm", kind="hinted")
def __init__(self, *args, **kwargs):
+22
View File
@@ -0,0 +1,22 @@
from ophyd import Component as Cpt
from ophyd import Device, EpicsSignal, Kind, StatusBase
class Cff(Device):
"""
CFF device for X-Treme. Setting the cff will also confirm the energy with PGM.setE.
"""
cff = Cpt(EpicsSignal, "PGM:rbkcff", write_pv="PGM:cff.A", kind=Kind.hinted, auto_monitor=True)
confirm_energy = Cpt(
EpicsSignal, "PGM:setE.PROC", kind=Kind.config, auto_monitor=True, put_complete=True
)
def __init__(self, prefix: str = "", **kwargs):
super().__init__(prefix, **kwargs)
self.cff.name = self.name
def set(self, value: float) -> StatusBase:
"""Write the CFF value, then confirm the energy and return its write status."""
self.cff.put(value, wait=True)
return self.confirm_energy.set(1)
+1 -15
View File
@@ -1,23 +1,9 @@
from ophyd import Component as Cpt
from ophyd import EpicsSignal
from ophyd_devices.devices.simple_positioner import PSISimplePositionerBase
from ophyd_devices.utils.psi_device_base_utils import TransitionStatus
class MirrorPseudo(PSISimplePositionerBase):
user_readback = Cpt(EpicsSignal, "R")
user_setpoint = Cpt(EpicsSignal, "S")
motor_done_move = Cpt(EpicsSignal, ":BUSY")
def _setup_move(self, position):
"""Move and do not wait until motion is complete (asynchronous)"""
def _done_cb(status, *args, **kwargs):
self._done_moving(success=status.success)
self._move_completion_status = None
self._move_completion_status = TransitionStatus(self.motor_done_move, transitions=[1, 0])
self.user_setpoint.put(position, wait=False)
self.cancel_on_stop(self._move_completion_status)
self._move_completion_status.add_callback(_done_cb)
use_put_complete = True
-71
View File
@@ -1,71 +0,0 @@
from enum import Enum
from typing import Literal
from ophyd import Component as Cpt
from ophyd import Device, EpicsSignal, Kind, StatusBase
class UIndModeEnum(str, Enum):
OFF = "Off"
LINEAR_H = "Linear H"
LINEAR_V_PLUS = "Linear V+"
LINEAR_V_MINUS = "Linear V-"
CIRCULAR_PLUS = "Circular+"
CIRCULAR_MINUS = "Circular-"
PLUS_45_DEG = "+45 deg"
MINUS_45_DEG = "-45 deg"
class UIndMode(Device):
USER_ACCESS = ["set_mode"]
value = Cpt(EpicsSignal, "X07MA-UIND:MODE", auto_monitor=True, kind=Kind.hinted)
value_string = Cpt(EpicsSignal, "X07MA-UIND:MODE", auto_monitor=True, string=True)
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.value.name = self.name
def set_mode(
self,
mode: (
Literal[
"Off",
"Linear H",
"Linear V+",
"Linear V-",
"Circular+",
"Circular-",
"+45 deg",
"-45 deg",
]
| int
),
) -> StatusBase:
"""
Set the undulator polarization mode.
Args:
mode (Literal["Off", "Linear H", "Linear V+", "Linear V-", "Circular+", "Circular-", "+45 deg", "-45 deg"] | int): The desired polarization mode, either as a string or an integer index.
"""
if isinstance(mode, str):
try:
mode_enum = UIndModeEnum(mode)
mode_val = mode_enum.value
except ValueError:
# pylint: disable=raise-missing-from
raise ValueError(
f"Invalid mode string: {mode}. Valid options are: {[m.value for m in UIndModeEnum]}"
)
elif isinstance(mode, int):
try:
mode_enum = list(UIndModeEnum)[mode]
mode_val = mode_enum.value
except IndexError:
# pylint: disable=raise-missing-from
raise ValueError(
f"Invalid mode index: {mode}. Valid indices are: 0 to {len(UIndModeEnum) - 1}, corresponding to: {[m.value for m in UIndModeEnum]}"
)
else:
raise TypeError("Mode must be either a string or an integer index.")
return self.value.set(mode_val)
+128
View File
@@ -0,0 +1,128 @@
from collections.abc import Callable
from enum import Enum
from typing import Literal
from ophyd import Component as Cpt
from ophyd import Device, EpicsSignalRO
from ophyd_devices.devices.undulator import UndulatorAlpha as _UndulatorAlpha
from ophyd_devices.devices.undulator import UndulatorBase
from ophyd_devices.devices.undulator import UndulatorGap as _UndulatorGap
from ophyd_devices.devices.undulator import UndulatorHarmonic as _UndulatorHarmonic
from ophyd_devices.devices.undulator import UndulatorSetpointSignal, UndulatorStopSignal
from ophyd_devices.utils.psi_device_base_utils import StatusBase
__all__ = [
"UndulatorAlpha",
"UndulatorGap",
"UndulatorHarmonic",
"UndulatorMode",
"UndulatorModeEnum",
]
_CMU_ONLINE_VALIDATIONS = (
("cmu_online", 4, "CMU must be On-line (X07MA-OP-CMU:ONLINE = 4) to control the undulator."),
)
class _CMUOnlineMixin(Device):
"""Expose the CMU state required by X-Treme undulator control validations."""
cmu_online = Cpt(
EpicsSignalRO, "X07MA-OP-CMU:ONLINE", add_prefix=(), auto_monitor=True, kind="omitted"
)
class UndulatorGap(_CMUOnlineMixin, _UndulatorGap):
"""X-Treme undulator gap control requiring the CMU to be On-line."""
control_validations = _UndulatorGap.control_validations + _CMU_ONLINE_VALIDATIONS
class UndulatorHarmonic(_CMUOnlineMixin, _UndulatorHarmonic):
"""X-Treme undulator harmonic control requiring the CMU to be On-line."""
control_validations = _UndulatorHarmonic.control_validations + _CMU_ONLINE_VALIDATIONS
class UndulatorAlpha(_CMUOnlineMixin, _UndulatorAlpha):
"""X-Treme undulator alpha control requiring the CMU to be On-line."""
control_validations = _UndulatorAlpha.control_validations + _CMU_ONLINE_VALIDATIONS
class UndulatorModeEnum(str, Enum):
OFF = "Off"
LINEAR_H = "Linear H"
LINEAR_V_PLUS = "Linear V+"
LINEAR_V_MINUS = "Linear V-"
CIRCULAR_PLUS = "Circular+"
CIRCULAR_MINUS = "Circular-"
PLUS_45_DEG = "+45 deg"
MINUS_45_DEG = "-45 deg"
class UndulatorMode(_CMUOnlineMixin, UndulatorBase):
"""X-Treme undulator polarization positioner, completed by the IOC's DONE signal.
The CMU must be On-line and control must belong to the beamline.
Use ``set("Circular+")`` or ``set(4)`` to select a mode. As with harmonic
and alpha control, every request writes the target and waits for DONE,
including requests for the currently selected mode.
"""
control_validations = UndulatorBase.control_validations + _CMU_ONLINE_VALIDATIONS
setpoint = Cpt(UndulatorSetpointSignal, "MODE", auto_monitor=True, kind="hinted")
value_string = Cpt(EpicsSignalRO, "MODE", auto_monitor=True, string=True)
stop_signal = Cpt(UndulatorStopSignal, "STOP", auto_monitor=True, kind="omitted")
done = Cpt(EpicsSignalRO, "DONE", auto_monitor=True, kind="omitted")
select_control = Cpt(EpicsSignalRO, "SCTRL", auto_monitor=True)
def __init__(self, prefix: str = "X07MA-UIND:", **kwargs) -> None:
super().__init__(prefix=prefix, **kwargs)
self.setpoint.name = self.name
def move(
self,
position: (
Literal[
"Off",
"Linear H",
"Linear V+",
"Linear V-",
"Circular+",
"Circular-",
"+45 deg",
"-45 deg",
]
| int
),
wait: bool = True,
timeout: float | None = None,
moved_cb: Callable | None = None,
) -> StatusBase:
"""Select a mode by label or index and wait for undulator completion.
``set()`` delegates here with ``wait=False`` and returns a pending status.
Invalid labels and indices raise ValueError before issuing a command.
"""
modes = list(UndulatorModeEnum)
if isinstance(position, str):
try:
mode = modes.index(UndulatorModeEnum(position))
except ValueError as exc:
raise ValueError(
f"Invalid mode string: {position}. "
f"Valid options are: {[mode.value for mode in modes]}"
) from exc
elif isinstance(position, int) and not isinstance(position, bool):
if not 0 <= position < len(modes):
raise ValueError(
f"Invalid mode index: {position}. Valid indices are: 0 to {len(modes) - 1}."
)
mode = position
else:
raise TypeError("Mode must be either a string or an integer index.")
self.validate_control()
return super().move(mode, wait=wait, timeout=timeout, moved_cb=moved_cb)
+19 -18
View File
@@ -12,7 +12,6 @@ class XtremeNeXusFormat(DefaultFormat):
"rmu": ("refocusing_mirror", "Refocusing mirror", 29.8),
}
_MIRROR_AXES = ("y", "z", "rx", "ry", "rz")
_SLIT_SIGNALS = ("sl1xi", "sl1xo", "sl1yb", "sl1yt", "sl1xc", "sl1xs", "sl1yc", "sl1ys")
def format(self) -> None:
entry = self.storage.create_group("entry")
@@ -67,10 +66,16 @@ class XtremeNeXusFormat(DefaultFormat):
insertion_device.attrs["NX_class"] = "NXinsertion_device"
insertion_device.create_dataset(name="type", data="undulator")
insertion_device.create_dataset(name="description", data="Apple II undulator")
self.safe_dataset(group=insertion_device, name="polarization", device="uind_mode")
self.safe_dataset(group=insertion_device, name="energy", device="uind_energy")
self.safe_dataset(group=insertion_device, name="gap", device="idgap")
self.safe_dataset(group=insertion_device, name="harmonic", device="uind_harmonic")
self.safe_dataset(
group=insertion_device,
name="polarization",
device="id_mode",
signal="id_mode_value_string",
)
self.safe_dataset(group=insertion_device, name="alpha", device="id_alpha")
self.safe_dataset(group=insertion_device, name="energy", device="id_energy")
self.safe_dataset(group=insertion_device, name="gap", device="id_gap")
self.safe_dataset(group=insertion_device, name="harmonic", device="id_harmonic")
xbpm = instrument.create_group("xbpm_1")
xbpm.attrs["NX_class"] = "NXpositioner"
@@ -96,14 +101,14 @@ class XtremeNeXusFormat(DefaultFormat):
front_end_slit = instrument.create_group("front_end_slit")
front_end_slit.attrs["NX_class"] = "NXslit"
front_end_slit.create_dataset(name="description", data="Front-end slit 1")
self.safe_dataset(group=front_end_slit, name="x_gap", device="sl1xs")
self.safe_dataset(group=front_end_slit, name="y_gap", device="sl1ys")
self.safe_dataset(group=front_end_slit, name="x_translation", device="sl1xc")
self.safe_dataset(group=front_end_slit, name="y_translation", device="sl1yc")
self.safe_dataset(group=front_end_slit, name="x_ring_blade", device="sl1xi")
self.safe_dataset(group=front_end_slit, name="x_wall_blade", device="sl1xo")
self.safe_dataset(group=front_end_slit, name="y_bottom_blade", device="sl1yb")
self.safe_dataset(group=front_end_slit, name="y_top_blade", device="sl1yt")
self.safe_dataset(group=front_end_slit, name="x_gap", device="fe_slxs")
self.safe_dataset(group=front_end_slit, name="y_gap", device="fe_slys")
self.safe_dataset(group=front_end_slit, name="x_translation", device="fe_slxc")
self.safe_dataset(group=front_end_slit, name="y_translation", device="fe_slyc")
self.safe_dataset(group=front_end_slit, name="x_ring_blade", device="fe_slxi")
self.safe_dataset(group=front_end_slit, name="x_wall_blade", device="fe_slxo")
self.safe_dataset(group=front_end_slit, name="y_bottom_blade", device="fe_slyb")
self.safe_dataset(group=front_end_slit, name="y_top_blade", device="fe_slyt")
##############################
#### Optics ####
@@ -113,7 +118,7 @@ class XtremeNeXusFormat(DefaultFormat):
self.safe_dataset(group=monochromator, name="energy", device="mono")
self.safe_dataset(group=monochromator, name="beamline_energy", device="energy")
self.safe_dataset(group=monochromator, name="mode", device="energy_mode")
self.safe_dataset(group=monochromator, name="cff", device="mono", signal="cff")
self.safe_dataset(group=monochromator, name="cff", device="cff")
plane_mirror = monochromator.create_group("plane_mirror")
plane_mirror.attrs["NX_class"] = "NXmirror"
@@ -198,15 +203,11 @@ class XtremeNeXusFormat(DefaultFormat):
"async", []
)
print(f"signal is async: {signals_is_async}")
if signals_is_async:
prefix = "signals_data_"
else:
prefix = "signals_"
print(self.written_async_signals)
self.safe_dataset(group=group, name="s1", device="signals", signal=f"{prefix}s1")
self.safe_dataset(group=group, name="s2", device="signals", signal=f"{prefix}s2")
self.safe_dataset(group=group, name="s3", device="signals", signal=f"{prefix}s3")
+1 -1
View File
@@ -49,7 +49,7 @@ class OTFScan(ScanBase):
#fmt: off
e1: Annotated[float, ScanArgument(display_name="Energy 1", description="Start energy", units=Units.eV, ge=250, le=2000)],
e2: Annotated[float, ScanArgument(display_name="Energy 2", description="End energy", units=Units.eV, ge=250, le=2000)],
time: Annotated[float, ScanArgument(display_name="Time", description="Travel time", units=Units.min, ge=0)],
time: Annotated[float, ScanArgument(display_name="Time", description="Travel time", units=Units.min, gt=0)],
**kwargs,
#fmt: on
):