feat: add uind_mode device #15

Merged
wakonig_k merged 3 commits from feature/uind_mode into main 2026-06-19 09:04:35 +02:00
8 changed files with 599 additions and 18 deletions
+102
View File
@@ -0,0 +1,102 @@
valve:
readoutPriority: baseline
description: 'Endstation valve'
deviceClass: xtreme_bec.devices.VacuumValve
deviceConfig:
prefix: 'X07MA-OP-VG13:'
onFailure: retry
enabled: true
readOnly: false
softwareTrigger: false
goldmesh1:
readoutPriority: baseline
description: 'Gold mesh 1'
deviceClass: ophyd.EpicsMotor
deviceConfig:
prefix: 'X07MA-OP-IO1:TR1'
onFailure: retry
enabled: true
readOnly: false
softwareTrigger: false
goldmesh2:
readoutPriority: baseline
description: 'Gold mesh 2'
deviceClass: ophyd.EpicsMotor
deviceConfig:
prefix: 'X07MA-OP-IO2:TR1'
onFailure: retry
enabled: true
readOnly: false
softwareTrigger: false
field_x:
readoutPriority: baseline
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
field_z:
readoutPriority: baseline
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
keithley_1:
readoutPriority: baseline
description: 'Keithley 1 / 428 tey'
deviceClass: ophyd.EpicsSignal
deviceConfig:
read_pv: 'X07MA-PC-K428:1:setGain'
onFailure: retry
enabled: true
readOnly: false
softwareTrigger: false
keithley_2:
readoutPriority: baseline
description: 'Keithley 2 / 428 i0'
deviceClass: ophyd.EpicsSignal
deviceConfig:
read_pv: 'X07MA-PC-K428:2:setGain'
onFailure: retry
enabled: true
readOnly: false
softwareTrigger: false
keithley_3:
readoutPriority: baseline
description: 'Keithley 3 / 428 diode'
deviceClass: ophyd.EpicsSignal
deviceConfig:
read_pv: 'X07MA-PC-K428:3:setGain'
onFailure: retry
enabled: true
readOnly: false
softwareTrigger: false
temperature:
readoutPriority: baseline
description: 'Temperature controller'
deviceClass: xtreme_bec.devices.X07MATemperatureController
deviceConfig:
prefix: 'X07MA-PC-TC:'
onFailure: retry
enabled: true
readOnly: false
softwareTrigger: false
+209
View File
@@ -0,0 +1,209 @@
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
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
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
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
energy:
readoutPriority: baseline
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
energy_mode:
readoutPriority: baseline
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
sl1xi:
description: 'slit 1 (frontend) x ring'
deviceClass: ophyd_devices.EpicsMotorEC
deviceConfig:
prefix: 'X07MA-FE-SL1:TRXR'
onFailure: raise
enabled: true
readoutPriority: baseline
readOnly: false
softwareTrigger: false
deviceTags:
- xtreme
- frontend
sl1xo:
description: 'slit 1 (frontend) x wall'
deviceClass: ophyd_devices.EpicsMotorEC
deviceConfig:
prefix: 'X07MA-FE-SL1:TRXW'
onFailure: raise
enabled: true
readoutPriority: baseline
readOnly: false
softwareTrigger: false
deviceTags:
- xtreme
- frontend
sl1yb:
description: 'slit 1 (frontend) y bottom'
deviceClass: ophyd_devices.EpicsMotorEC
deviceConfig:
prefix: 'X07MA-FE-SL1:TRYB'
onFailure: raise
enabled: true
readoutPriority: baseline
readOnly: false
softwareTrigger: false
deviceTags:
- xtreme
- frontend
sl1yt:
description: 'slit 1 (frontend) y top'
deviceClass: ophyd_devices.EpicsMotorEC
deviceConfig:
prefix: 'X07MA-FE-SL1:TRYT'
onFailure: raise
enabled: true
readoutPriority: baseline
readOnly: false
softwareTrigger: false
deviceTags:
- xtreme
- frontend
sl1xc:
description: 'slit 1 (frontend) x center'
deviceClass: ophyd_devices.EpicsMotorEC
deviceConfig:
prefix: 'X07MA-FE-SL1:CENTERX'
onFailure: raise
enabled: true
readoutPriority: baseline
readOnly: false
softwareTrigger: false
deviceTags:
- xtreme
- frontend
sl1xs:
description: 'slit 1 (frontend) x size'
deviceClass: ophyd_devices.EpicsMotorEC
deviceConfig:
prefix: 'X07MA-FE-SL1:SIZEX'
onFailure: raise
enabled: true
readoutPriority: baseline
readOnly: false
softwareTrigger: false
deviceTags:
- xtreme
- frontend
sl1yc:
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
deviceTags:
- xtreme
- frontend
sl1ys:
description: 'slit 1 (frontend) y size'
deviceClass: ophyd_devices.EpicsMotorEC
deviceConfig:
prefix: 'X07MA-FE-SL1:SIZEY'
onFailure: raise
enabled: true
readoutPriority: baseline
readOnly: false
softwareTrigger: false
deviceTags:
- xtreme
- frontend
xbpm1x:
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
deviceTags:
- xtreme
- frontend
xbpm1y:
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
deviceTags:
- xtreme
- frontend
+49
View File
@@ -0,0 +1,49 @@
##############################################
################## Machine ###################
##############################################
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
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
shutter:
description: X07MA shutter status
deviceClass: ophyd_devices.EpicsSignalRO
deviceConfig:
read_pv: X07M-OP-PSYS:SH-A-OPEN
auto_monitor: true
onFailure: buffer
enabled: true
readoutPriority: baseline
readOnly: true
softwareTrigger: false
operator:
description: SLS operator messages
deviceClass: ophyd_devices.devices.sls_devices.SLSOperatorMessages
deviceConfig:
onFailure: buffer
enabled: true
readoutPriority: on_request
readOnly: true
softwareTrigger: false
+12
View File
@@ -0,0 +1,12 @@
machine:
- !include ./machine.yaml
frontend:
- !include ./frontend.yaml
optics:
- !include ./optics.yaml
endstation:
- !include ./endstation.yaml
+10
View File
@@ -0,0 +1,10 @@
mono:
readoutPriority: baseline
description: 'PGM Monochromator'
deviceClass: xtreme_bec.devices.PGMMonochromator
deviceConfig:
prefix: 'X07MA-'
onFailure: retry
enabled: true
readOnly: false
softwareTrigger: false
@@ -243,22 +243,4 @@ tcontrol:
readOnly: false
softwareTrigger: false
sls_info:
readoutPriority: on_request
description: 'sls info'
deviceClass: ophyd_devices.SLSInfo
deviceConfig: {}
onFailure: buffer
enabled: true
readOnly: false
softwareTrigger: false
sls_operator:
readoutPriority: on_request
description: 'sls operator messages'
deviceClass: ophyd_devices.SLSOperatorMessages
deviceConfig: {}
onFailure: buffer
enabled: true
readOnly: false
softwareTrigger: false
+146
View File
@@ -0,0 +1,146 @@
from __future__ import annotations
from ophyd import Component as Cpt
from ophyd import Device, EpicsSignal, EpicsSignalRO, Kind
from ophyd_devices import AsyncSignal, DeviceStatus, PSIDeviceBase, StatusBase, TransitionStatus
class PGMOtfControl(Device):
"""
Ophyd device for controlling the PGM OTF (On-The-Fly) scan.
"""
e1 = Cpt(EpicsSignal, "E1", kind=Kind.config)
e2 = Cpt(EpicsSignal, "E2", kind=Kind.config)
time = Cpt(EpicsSignal, "TIME", kind=Kind.config)
folder = Cpt(EpicsSignal, "FOLDER", kind=Kind.config)
file = Cpt(EpicsSignal, "FILE", kind=Kind.config)
acquire = Cpt(EpicsSignal, "START", auto_monitor=True)
raw_edata = Cpt(EpicsSignalRO, "EDATA", kind=Kind.hinted, auto_monitor=True)
raw_data = Cpt(EpicsSignalRO, "DATA", kind=Kind.hinted, auto_monitor=True)
raw_idata = Cpt(EpicsSignalRO, "IDATA", kind=Kind.hinted, auto_monitor=True)
raw_fdata = Cpt(EpicsSignalRO, "FDATA", kind=Kind.hinted, auto_monitor=True)
count = Cpt(EpicsSignalRO, "COUNT", kind=Kind.omitted, auto_monitor=True)
class PGMOtf(PSIDeviceBase, PGMOtfControl):
"""
Ophyd device for controlling the PGM OTF (On-The-Fly) scan.
Inherits from PSIDeviceBase and PGMOtfControl.
"""
edata = Cpt(
AsyncSignal,
name="raw_edata",
ndim=1,
async_update={"type": "replace", "max_shape": [None]},
max_size=1000,
docs="Aggregated edata signal from the PGM OTF scan.",
)
data = Cpt(
AsyncSignal,
name="raw_data",
ndim=1,
async_update={"type": "replace", "max_shape": [None]},
max_size=1000,
docs="Aggregated data signal from the PGM OTF scan.",
)
idata = Cpt(
AsyncSignal,
name="raw_idata",
ndim=1,
async_update={"type": "replace", "max_shape": [None]},
max_size=1000,
docs="Aggregated idata signal from the PGM OTF scan.",
)
fdata = Cpt(
AsyncSignal,
name="raw_fdata",
ndim=1,
async_update={"type": "replace", "max_shape": [None]},
max_size=1000,
docs="Aggregated fdata signal from the PGM OTF scan.",
)
# FIXME: Once we have the proper energy readback, we should set up the progress update
# to use the energy progress between e1 and e2.
# energy = Cpt(EpicsSignalRO, "ENERGY_RBV", kind=Kind.omitted, auto_monitor=True)
# progress = Cpt(ProgressSignal)
########################################
# Beamline Specific Implementations #
########################################
def on_init(self) -> None:
"""
Called when the device is initialized.
No signals are connected at this point. If you like to
set default values on signals, please use on_connected instead.
"""
self.complete_status = None
def on_connected(self) -> None:
"""
Called after the device is connected and its signals are connected.
Default values for signals should be set here.
"""
self.raw_edata.subscribe(self._update_data, run=False)
self.raw_data.subscribe(self._update_data, run=False)
self.raw_idata.subscribe(self._update_data, run=False)
self.raw_fdata.subscribe(self._update_data, run=False)
def on_stage(self) -> DeviceStatus | StatusBase | None:
"""
Called while staging the device.
Information about the upcoming scan can be accessed from the scan_info (self.scan_info.msg) object.
"""
if self.acquire.get() != 0:
raise RuntimeError("PGM OTF scan is already running. Please stop it before staging.")
self.complete_status = TransitionStatus(self.acquire, transitions=[0, 1, 0])
self.cancel_on_stop(self.complete_status)
def on_unstage(self) -> DeviceStatus | StatusBase | None:
"""Called while unstaging the device."""
if self.complete_status is not None and not self.complete_status.done:
self.complete_status.set_finished()
self.acquire.put(0)
self.complete_status = None
def on_pre_scan(self) -> DeviceStatus | StatusBase | None:
"""Called right before the scan starts on all devices automatically."""
def on_trigger(self) -> DeviceStatus | StatusBase | None:
"""Called when the device is triggered."""
def on_complete(self) -> DeviceStatus | StatusBase | None:
"""Called to inquire if a device has completed a scans."""
return self.complete_status
def on_kickoff(self) -> DeviceStatus | StatusBase | None:
"""Called to kickoff a device for a fly scan. Has to be called explicitly."""
self.acquire.put(1, use_complete=True)
def on_stop(self) -> None:
"""Called when the device is stopped."""
self.acquire.put(0)
def on_destroy(self) -> None:
"""Called when the device is destroyed. Cleanup resources here."""
def _update_data(self, value, obj, **kwargs):
"""
Callback function to update the aggregated data signals when the raw signals change.
"""
match obj.name:
case "raw_edata":
self.edata.put(value)
case "raw_data":
self.data.put(value)
case "raw_idata":
self.idata.put(value)
case "raw_fdata":
self.fdata.put(value)
+71
View File
@@ -0,0 +1,71 @@
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)