refactor (mo1-bragg): refactored Mo1 Bragg class with new base class PSIDeviceBase
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
mo1_bragg:
|
||||
readoutPriority: baseline
|
||||
description: Positioner for the Monochromator
|
||||
deviceClass: debye_bec.devices.mo1_bragg.Mo1Bragg
|
||||
deviceClass: debye_bec.devices.mo1_bragg.mo1_bragg.Mo1Bragg
|
||||
deviceConfig:
|
||||
prefix: "X01DA-OP-MO1:BRAGG:"
|
||||
onFailure: retry
|
||||
@@ -18,7 +18,7 @@ dummy_pv:
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
## NIDAQ
|
||||
# NIDAQ
|
||||
nidaq:
|
||||
readoutPriority: async
|
||||
description: NIDAQ backend for data reading for debye scans
|
||||
@@ -53,7 +53,7 @@ nidaq:
|
||||
|
||||
# HV power supplies
|
||||
hv_supplies:
|
||||
readoutPriority: async
|
||||
readoutPriority: baseline
|
||||
description: HV power supplies
|
||||
deviceClass: debye_bec.devices.hv_supplies.HVSupplies
|
||||
deviceConfig:
|
||||
@@ -63,7 +63,7 @@ hv_supplies:
|
||||
softwareTrigger: false
|
||||
|
||||
beam_monitor_1:
|
||||
readoutPriority: async
|
||||
readoutPriority: baseline
|
||||
description: Beam monitor 1
|
||||
deviceClass: debye_bec.devices.cameras.prosilica_cam.ProsilicaCam
|
||||
deviceConfig:
|
||||
@@ -73,7 +73,7 @@ beam_monitor_1:
|
||||
softwareTrigger: false
|
||||
|
||||
beam_monitor_2:
|
||||
readoutPriority: async
|
||||
readoutPriority: baseline
|
||||
description: Beam monitor 2
|
||||
deviceClass: debye_bec.devices.cameras.prosilica_cam.ProsilicaCam
|
||||
deviceConfig:
|
||||
@@ -82,19 +82,19 @@ beam_monitor_2:
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
# xray_eye:
|
||||
# readoutPriority: async
|
||||
# description: X-ray eye
|
||||
# deviceClass: debye_bec.devices.cameras.basler_cam.BaslerCam
|
||||
# deviceConfig:
|
||||
# prefix: "X01DA-ES-XRAYEYE:"
|
||||
# onFailure: retry
|
||||
# enabled: true
|
||||
# softwareTrigger: false
|
||||
xray_eye:
|
||||
readoutPriority: baseline
|
||||
description: X-ray eye
|
||||
deviceClass: debye_bec.devices.cameras.basler_cam.BaslerCam
|
||||
deviceConfig:
|
||||
prefix: "X01DA-ES-XRAYEYE:"
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
# Gas Mix Setup
|
||||
# gas_mix_setup:
|
||||
# readoutPriority: async
|
||||
# readoutPriority: baseline
|
||||
# description: Gas Mix Setup for Ionization Chambers
|
||||
# deviceClass: debye_bec.devices.gas_mix_setup.GasMixSetup
|
||||
# deviceConfig:
|
||||
@@ -105,7 +105,7 @@ beam_monitor_2:
|
||||
|
||||
# Pilatus Curtain
|
||||
# pilatus_curtain:
|
||||
# readoutPriority: async
|
||||
# readoutPriority: baseline
|
||||
# description: Pilatus Curtain
|
||||
# deviceClass: debye_bec.devices.pilatus_curtain.PilatusCurtain
|
||||
# deviceConfig:
|
||||
@@ -120,7 +120,7 @@ beam_monitor_2:
|
||||
################################
|
||||
|
||||
es_temperature1:
|
||||
readoutPriority: monitored
|
||||
readoutPriority: baseline
|
||||
description: ES temperature sensor 1
|
||||
deviceClass: ophyd.EpicsSignalRO
|
||||
deviceConfig:
|
||||
@@ -130,7 +130,7 @@ es_temperature1:
|
||||
softwareTrigger: false
|
||||
|
||||
es_humidity1:
|
||||
readoutPriority: monitored
|
||||
readoutPriority: baseline
|
||||
description: ES humidity sensor 1
|
||||
deviceClass: ophyd.EpicsSignalRO
|
||||
deviceConfig:
|
||||
@@ -140,7 +140,7 @@ es_humidity1:
|
||||
softwareTrigger: false
|
||||
|
||||
es_pressure1:
|
||||
readoutPriority: monitored
|
||||
readoutPriority: baseline
|
||||
description: ES ambient pressure sensor 1
|
||||
deviceClass: ophyd.EpicsSignalRO
|
||||
deviceConfig:
|
||||
@@ -150,7 +150,7 @@ es_pressure1:
|
||||
softwareTrigger: false
|
||||
|
||||
es_temperature2:
|
||||
readoutPriority: monitored
|
||||
readoutPriority: baseline
|
||||
description: ES temperature sensor 2
|
||||
deviceClass: ophyd.EpicsSignalRO
|
||||
deviceConfig:
|
||||
@@ -160,7 +160,7 @@ es_temperature2:
|
||||
softwareTrigger: false
|
||||
|
||||
es_humidity2:
|
||||
readoutPriority: monitored
|
||||
readoutPriority: baseline
|
||||
description: ES humidity sensor 2
|
||||
deviceClass: ophyd.EpicsSignalRO
|
||||
deviceConfig:
|
||||
@@ -170,7 +170,7 @@ es_humidity2:
|
||||
softwareTrigger: false
|
||||
|
||||
es_pressure2:
|
||||
readoutPriority: monitored
|
||||
readoutPriority: baseline
|
||||
description: ES ambient pressure sensor 2
|
||||
deviceClass: ophyd.EpicsSignalRO
|
||||
deviceConfig:
|
||||
@@ -180,7 +180,7 @@ es_pressure2:
|
||||
softwareTrigger: false
|
||||
|
||||
es_light_toggle:
|
||||
readoutPriority: monitored
|
||||
readoutPriority: baseline
|
||||
description: ES light toggle
|
||||
deviceClass: ophyd.EpicsSignal
|
||||
deviceConfig:
|
||||
@@ -194,7 +194,7 @@ es_light_toggle:
|
||||
#################
|
||||
|
||||
sdd1_temperature:
|
||||
readoutPriority: monitored
|
||||
readoutPriority: baseline
|
||||
description: SDD1 temperature sensor
|
||||
deviceClass: ophyd.EpicsSignalRO
|
||||
deviceConfig:
|
||||
@@ -219,7 +219,7 @@ sdd1_humidity:
|
||||
#####################
|
||||
|
||||
es1_alignment_laser:
|
||||
readoutPriority: monitored
|
||||
readoutPriority: baseline
|
||||
description: ES1 alignment laser
|
||||
deviceClass: ophyd.EpicsSignal
|
||||
deviceConfig:
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
478
debye_bec/devices/mo1_bragg/mo1_bragg.py
Normal file
478
debye_bec/devices/mo1_bragg/mo1_bragg.py
Normal file
@@ -0,0 +1,478 @@
|
||||
"""Module for the Mo1 Bragg positioner of the Debye beamline.
|
||||
The softIOC is reachable via the EPICS prefix X01DA-OP-MO1:BRAGG: and connected
|
||||
to a motor controller via web sockets. The Mo1 Bragg positioner is not only a
|
||||
positioner, but also a scan controller to setup XAS and XRD scans. A few scan modes
|
||||
are programmed in the controller, e.g. simple and advanced XAS scans + XRD triggering mode.
|
||||
|
||||
Note: For some of the Epics PVs, in particular action buttons, the put_complete=True is
|
||||
used to ensure that the action is executed completely. This is believed
|
||||
to allow for a more stable execution of the action."""
|
||||
|
||||
import time
|
||||
from typing import Any, Literal
|
||||
|
||||
from bec_lib.devicemanager import ScanInfo
|
||||
from bec_lib.logger import bec_logger
|
||||
from ophyd import Component as Cpt
|
||||
from ophyd import DeviceStatus, StatusBase
|
||||
from ophyd_devices.interfaces.base_classes.psi_device_base import PSIDeviceBase
|
||||
from ophyd_devices.utils.errors import DeviceStopError
|
||||
from pydantic import BaseModel, Field
|
||||
from typeguard import typechecked
|
||||
|
||||
from debye_bec.devices.mo1_bragg.mo1_bragg_devices import Mo1BraggPositioner
|
||||
|
||||
# pylint: disable=unused-import
|
||||
from debye_bec.devices.mo1_bragg.mo1_bragg_enums import (
|
||||
MoveType,
|
||||
ScanControlLoadMessage,
|
||||
ScanControlMode,
|
||||
ScanControlScanStatus,
|
||||
TriggerControlMode,
|
||||
TriggerControlSource,
|
||||
)
|
||||
from debye_bec.devices.mo1_bragg.mo1_bragg_utils import compute_spline
|
||||
|
||||
# Initialise logger
|
||||
logger = bec_logger.logger
|
||||
|
||||
########### Exceptions ###########
|
||||
|
||||
|
||||
class Mo1BraggError(Exception):
|
||||
"""Exception for the Mo1 Bragg positioner"""
|
||||
|
||||
|
||||
########## Scan Parameter Model ##########
|
||||
|
||||
|
||||
class ScanParameter(BaseModel):
|
||||
"""Dataclass to store the scan parameters for the Mo1 Bragg positioner.
|
||||
This needs to be in sync with the kwargs of the MO1 Bragg scans from Debye, to
|
||||
ensure that the scan parameters are correctly set. Any changes in the scan kwargs,
|
||||
i.e. renaming or adding new parameters, need to be represented here as well."""
|
||||
|
||||
scan_time: float | None = Field(None, description="Scan time for a half oscillation")
|
||||
scan_duration: float | None = Field(None, description="Duration of the scan")
|
||||
xrd_enable_low: bool | None = Field(
|
||||
None, description="XRD enabled for low, should be PV trig_ena_lo_enum"
|
||||
) # trig_enable_low: bool = None
|
||||
xrd_enable_high: bool | None = Field(
|
||||
None, description="XRD enabled for high, should be PV trig_ena_hi_enum"
|
||||
) # trig_enable_high: bool = None
|
||||
exp_time_low: float | None = Field(None, description="Exposure time low energy/angle")
|
||||
exp_time_high: float | None = Field(None, description="Exposure time high energy/angle")
|
||||
cycle_low: int | None = Field(None, description="Cycle for low energy/angle")
|
||||
cycle_high: int | None = Field(None, description="Cycle for high energy/angle")
|
||||
start: float | None = Field(None, description="Start value for energy/angle")
|
||||
stop: float | None = Field(None, description="Stop value for energy/angle")
|
||||
p_kink: float | None = Field(None, description="P Kink")
|
||||
e_kink: float | None = Field(None, description="Energy Kink")
|
||||
model_config: dict = {"validate_assignment": True}
|
||||
|
||||
|
||||
########### Mo1 Bragg Motor Class ###########
|
||||
|
||||
|
||||
class Mo1Bragg(PSIDeviceBase, Mo1BraggPositioner):
|
||||
"""Mo1 Bragg motor for the Debye beamline.
|
||||
|
||||
The prefix to connect to the soft IOC is X01DA-OP-MO1:BRAGG:
|
||||
"""
|
||||
|
||||
USER_ACCESS = ["set_advanced_xas_settings"]
|
||||
|
||||
def __init__(self, name: str, prefix: str = "", scan_info: ScanInfo | None = None, **kwargs): # type: ignore
|
||||
"""
|
||||
Initialize the PSI Device Base class.
|
||||
|
||||
Args:
|
||||
name (str) : Name of the device
|
||||
scan_info (ScanInfo): The scan info to use.
|
||||
"""
|
||||
super().__init__(name=name, scan_info=scan_info, prefix=prefix, **kwargs)
|
||||
self.scan_parameter = ScanParameter()
|
||||
self.timeout_for_pvwait = 2.5
|
||||
|
||||
########################################
|
||||
# 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.
|
||||
"""
|
||||
|
||||
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.scan_control.scan_progress.subscribe(self._progress_update, 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 not self.scan_info.msg.scan_type == "fly":
|
||||
return
|
||||
self._check_scan_msg(ScanControlLoadMessage.PENDING)
|
||||
|
||||
scan_name = self.scan_info.msg.scan_name
|
||||
self._update_scan_parameter()
|
||||
if scan_name == "xas_simple_scan":
|
||||
self.set_xas_settings(
|
||||
low=self.scan_parameter.start,
|
||||
high=self.scan_parameter.stop,
|
||||
scan_time=self.scan_parameter.scan_time,
|
||||
)
|
||||
self.set_trig_settings(
|
||||
enable_low=False,
|
||||
enable_high=False,
|
||||
exp_time_low=0,
|
||||
exp_time_high=0,
|
||||
cycle_low=0,
|
||||
cycle_high=0,
|
||||
)
|
||||
self.set_scan_control_settings(
|
||||
mode=ScanControlMode.SIMPLE, scan_duration=self.scan_parameter.scan_duration
|
||||
)
|
||||
elif scan_name == "xas_simple_scan_with_xrd":
|
||||
self.set_xas_settings(
|
||||
low=self.scan_parameter.start,
|
||||
high=self.scan_parameter.stop,
|
||||
scan_time=self.scan_parameter.scan_time,
|
||||
)
|
||||
self.set_trig_settings(
|
||||
enable_low=self.scan_parameter.xrd_enable_low, # enable_low=self.scan_parameter.trig_enable_low,
|
||||
enable_high=self.scan_parameter.xrd_enable_high, # enable_high=self.scan_parameter.trig_enable_high,
|
||||
exp_time_low=self.scan_parameter.exp_time_low,
|
||||
exp_time_high=self.scan_parameter.exp_time_high,
|
||||
cycle_low=self.scan_parameter.cycle_low,
|
||||
cycle_high=self.scan_parameter.cycle_high,
|
||||
)
|
||||
self.set_scan_control_settings(
|
||||
mode=ScanControlMode.SIMPLE, scan_duration=self.scan_parameter.scan_duration
|
||||
)
|
||||
elif scan_name == "xas_advanced_scan":
|
||||
self.set_advanced_xas_settings(
|
||||
low=self.scan_parameter.start,
|
||||
high=self.scan_parameter.stop,
|
||||
scan_time=self.scan_parameter.scan_time,
|
||||
p_kink=self.scan_parameter.p_kink,
|
||||
e_kink=self.scan_parameter.e_kink,
|
||||
)
|
||||
self.set_trig_settings(
|
||||
enable_low=False,
|
||||
enable_high=False,
|
||||
exp_time_low=0,
|
||||
exp_time_high=0,
|
||||
cycle_low=0,
|
||||
cycle_high=0,
|
||||
)
|
||||
self.set_scan_control_settings(
|
||||
mode=ScanControlMode.ADVANCED, scan_duration=self.scan_parameter.scan_duration
|
||||
)
|
||||
elif scan_name == "xas_advanced_scan_with_xrd":
|
||||
self.set_advanced_xas_settings(
|
||||
low=self.scan_parameter.start,
|
||||
high=self.scan_parameter.stop,
|
||||
scan_time=self.scan_parameter.scan_time,
|
||||
p_kink=self.scan_parameter.p_kink,
|
||||
e_kink=self.scan_parameter.e_kink,
|
||||
)
|
||||
self.set_trig_settings(
|
||||
enable_low=self.scan_parameter.xrd_enable_low, # enable_low=self.scan_parameter.trig_enable_low,
|
||||
enable_high=self.scan_parameter.xrd_enable_high, # enable_high=self.scan_parameter.trig_enable_high,
|
||||
exp_time_low=self.scan_parameter.exp_time_low,
|
||||
exp_time_high=self.scan_parameter.exp_time_high,
|
||||
cycle_low=self.scan_parameter.cycle_low,
|
||||
cycle_high=self.scan_parameter.cycle_high,
|
||||
)
|
||||
self.set_scan_control_settings(
|
||||
mode=ScanControlMode.ADVANCED, scan_duration=self.scan_parameter.scan_duration
|
||||
)
|
||||
else:
|
||||
raise Mo1BraggError(
|
||||
f"Scan mode {scan_name} not implemented for scan_type={self.scan_info.msg.scan_type} on device {self.name}"
|
||||
)
|
||||
# Load the scan parameters to the controller
|
||||
self.scan_control.scan_load.put(1)
|
||||
# Wait for params to be checked from controller
|
||||
status = self.task_handler.submit_task(
|
||||
task=self.wait_for_signal,
|
||||
task_args=(self.scan_control.scan_msg, ScanControlLoadMessage.SUCCESS),
|
||||
)
|
||||
return status
|
||||
|
||||
def on_unstage(self) -> DeviceStatus | StatusBase | None:
|
||||
"""Called while unstaging the device."""
|
||||
|
||||
def unstage_procedure():
|
||||
try:
|
||||
self.wait_for_signal(
|
||||
self.scan_control.scan_msg,
|
||||
ScanControlLoadMessage.PENDING,
|
||||
timeout=self.timeout_for_pvwait / 2,
|
||||
)
|
||||
return
|
||||
except TimeoutError:
|
||||
logger.warning(
|
||||
f"Timeout after {self.timeout_for_pvwait} while waiting for scan validation"
|
||||
)
|
||||
time.sleep(0.25)
|
||||
start_time = time.time()
|
||||
while time.time() - start_time < self.timeout_for_pvwait / 2:
|
||||
if not self.scan_control.scan_msg.get() == ScanControlLoadMessage.PENDING:
|
||||
break
|
||||
time.sleep(0.1)
|
||||
raise TimeoutError(
|
||||
f"Device {self.name} run into timeout after {self.timeout_for_pvwait} while waiting for scan validation"
|
||||
)
|
||||
|
||||
status = self.task_handler.submit_task(unstage_procedure)
|
||||
status.wait()
|
||||
return 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."""
|
||||
|
||||
def wait_for_complete():
|
||||
"""Wait for the scan to complete. No timeout is set."""
|
||||
start_time = time.time()
|
||||
while True:
|
||||
if self.stopped is True:
|
||||
raise DeviceStopError(
|
||||
f"Device {self.name} was stopped while waiting for scan to complete"
|
||||
)
|
||||
if self.scan_control.scan_done.get() == 1:
|
||||
return
|
||||
time.sleep(0.1)
|
||||
|
||||
status = self.task_handler.submit_task(wait_for_complete)
|
||||
return status
|
||||
|
||||
def on_kickoff(self) -> DeviceStatus | StatusBase | None:
|
||||
"""Called to kickoff a device for a fly scan. Has to be called explicitly."""
|
||||
scan_duration = self.scan_control.scan_duration.get()
|
||||
# TODO implement better logic for infinite scans, at least bring it up with Debye
|
||||
start_func = (
|
||||
self.scan_control.scan_start_infinite.put
|
||||
if scan_duration < 0.1
|
||||
else self.scan_control.scan_start_timer.put
|
||||
)
|
||||
start_func(1)
|
||||
status = self.task_handler.submit_task(
|
||||
task=self.wait_for_signal,
|
||||
task_args=(self.scan_control.scan_status, ScanControlScanStatus.RUNNING),
|
||||
)
|
||||
return status
|
||||
|
||||
def on_stop(self) -> None:
|
||||
"""Called when the device is stopped."""
|
||||
self.stopped = True # Needs to be set to stop motion
|
||||
|
||||
######### Utility Methods #########
|
||||
|
||||
# FIXME this should become the ProgressSignal
|
||||
# pylint: disable=unused-argument
|
||||
def _progress_update(self, value, **kwargs) -> None:
|
||||
"""Callback method to update the scan progress, runs a callback
|
||||
to SUB_PROGRESS subscribers, i.e. BEC.
|
||||
|
||||
Args:
|
||||
value (int) : current progress value
|
||||
"""
|
||||
max_value = 100
|
||||
self._run_subs(
|
||||
sub_type=self.SUB_PROGRESS,
|
||||
value=value,
|
||||
max_value=max_value,
|
||||
done=bool(max_value == value),
|
||||
)
|
||||
|
||||
def set_xas_settings(self, low: float, high: float, scan_time: float) -> None:
|
||||
"""Set XAS parameters for upcoming scan.
|
||||
|
||||
Args:
|
||||
low (float): Low energy/angle value of the scan
|
||||
high (float): High energy/angle value of the scan
|
||||
scan_time (float): Time for a half oscillation
|
||||
"""
|
||||
move_type = self.move_type.get()
|
||||
if move_type == MoveType.ENERGY:
|
||||
self.scan_settings.s_scan_energy_lo.put(low)
|
||||
self.scan_settings.s_scan_energy_hi.put(high)
|
||||
else:
|
||||
self.scan_settings.s_scan_angle_lo.put(low)
|
||||
self.scan_settings.s_scan_angle_hi.put(high)
|
||||
self.scan_settings.s_scan_scantime.put(scan_time)
|
||||
|
||||
def wait_for_signal(self, signal: Cpt, value: Any, timeout: float | None = None) -> None:
|
||||
"""Wait for a signal to reach a certain value."""
|
||||
if timeout is None:
|
||||
timeout = self.timeout_for_pvwait
|
||||
start_time = time.time()
|
||||
while time.time() - start_time < timeout:
|
||||
if signal.get() == value:
|
||||
return None
|
||||
if self.stopped is True: # Should this check be optional or configurable?!
|
||||
raise DeviceStopError(f"Device {self.name} was stopped while waiting for signal")
|
||||
time.sleep(0.1)
|
||||
# If we end up here, the status did not resolve
|
||||
raise TimeoutError(
|
||||
f"Device {self.name} run into timeout after {timeout}s while waiting for scan to start"
|
||||
)
|
||||
|
||||
@typechecked
|
||||
def convert_angle_energy(
|
||||
self, mode: Literal["AngleToEnergy", "EnergyToAngle"], inp: float
|
||||
) -> float:
|
||||
"""Calculate energy to angle or vice versa
|
||||
|
||||
Args:
|
||||
mode (Literal["AngleToEnergy", "EnergyToAngle"]): Mode of calculation
|
||||
input (float): Either angle or energy
|
||||
|
||||
Returns:
|
||||
output (float): Converted angle or energy
|
||||
"""
|
||||
self.calculator.calc_reset.put(0)
|
||||
self.calculator.calc_reset.put(1)
|
||||
self.wait_for_signal(self.calculator.calc_done, 0)
|
||||
|
||||
if mode == "AngleToEnergy":
|
||||
self.calculator.calc_angle.put(inp)
|
||||
elif mode == "EnergyToAngle":
|
||||
self.calculator.calc_energy.put(inp)
|
||||
|
||||
self.wait_for_signal(self.calculator.calc_done, 1)
|
||||
time.sleep(0.25) # Needed due to update frequency of softIOC
|
||||
if mode == "AngleToEnergy":
|
||||
return self.calculator.calc_energy.get()
|
||||
elif mode == "EnergyToAngle":
|
||||
return self.calculator.calc_angle.get()
|
||||
|
||||
def set_advanced_xas_settings(
|
||||
self, low: float, high: float, scan_time: float, p_kink: float, e_kink: float
|
||||
) -> None:
|
||||
"""Set Advanced XAS parameters for upcoming scan.
|
||||
|
||||
Args:
|
||||
low (float): Low angle value of the scan in eV
|
||||
high (float): High angle value of the scan in eV
|
||||
scan_time (float): Time for a half oscillation in s
|
||||
p_kink (float): Position of kink in %
|
||||
e_kink (float): Energy of kink in eV
|
||||
"""
|
||||
# TODO Add fallback solution for automatic testing, otherwise test will fail
|
||||
# because no monochromator will calculate the angle
|
||||
# Unsure how to implement this
|
||||
|
||||
move_type = self.move_type.get()
|
||||
if move_type == MoveType.ENERGY:
|
||||
e_kink_deg = self.convert_angle_energy(mode="EnergyToAngle", inp=e_kink)
|
||||
# Angle and Energy are inverse proportional!
|
||||
high_deg = self.convert_angle_energy(mode="EnergyToAngle", inp=low)
|
||||
low_deg = self.convert_angle_energy(mode="EnergyToAngle", inp=high)
|
||||
else:
|
||||
raise Mo1BraggError("MoveType Angle not implemented for advanced scans, use Energy")
|
||||
|
||||
pos, vel, dt = compute_spline(
|
||||
low_deg=low_deg,
|
||||
high_deg=high_deg,
|
||||
p_kink=p_kink,
|
||||
e_kink_deg=e_kink_deg,
|
||||
scan_time=scan_time,
|
||||
)
|
||||
|
||||
self.scan_settings.a_scan_pos.set(pos)
|
||||
self.scan_settings.a_scan_vel.set(vel)
|
||||
self.scan_settings.a_scan_time.set(dt)
|
||||
|
||||
def set_trig_settings(
|
||||
self,
|
||||
enable_low: bool,
|
||||
enable_high: bool,
|
||||
exp_time_low: int,
|
||||
exp_time_high: int,
|
||||
cycle_low: int,
|
||||
cycle_high: int,
|
||||
) -> None:
|
||||
"""Set TRIG settings for the upcoming scan.
|
||||
|
||||
Args:
|
||||
enable_low (bool): Enable TRIG for low energy/angle
|
||||
enable_high (bool): Enable TRIG for high energy/angle
|
||||
num_trigger_low (int): Number of triggers for low energy/angle
|
||||
num_trigger_high (int): Number of triggers for high energy/angle
|
||||
exp_time_low (int): Exposure time for low energy/angle
|
||||
exp_time_high (int): Exposure time for high energy/angle
|
||||
cycle_low (int): Cycle for low energy/angle
|
||||
cycle_high (int): Cycle for high energy/angle
|
||||
"""
|
||||
self.scan_settings.trig_ena_hi_enum.put(int(enable_high))
|
||||
self.scan_settings.trig_ena_lo_enum.put(int(enable_low))
|
||||
self.scan_settings.trig_time_hi.put(exp_time_high)
|
||||
self.scan_settings.trig_time_lo.put(exp_time_low)
|
||||
self.scan_settings.trig_every_n_hi.put(cycle_high)
|
||||
self.scan_settings.trig_every_n_lo.put(cycle_low)
|
||||
|
||||
def set_scan_control_settings(self, mode: ScanControlMode, scan_duration: float) -> None:
|
||||
"""Set the scan control settings for the upcoming scan.
|
||||
|
||||
Args:
|
||||
mode (ScanControlMode): Mode for the scan, either simple or advanced
|
||||
scan_duration (float): Duration of the scan
|
||||
"""
|
||||
val = ScanControlMode(mode).value
|
||||
self.scan_control.scan_mode_enum.put(val)
|
||||
self.scan_control.scan_duration.put(scan_duration)
|
||||
|
||||
def _update_scan_parameter(self):
|
||||
"""Get the scan_info parameters for the scan."""
|
||||
for key, value in self.scan_info.msg.request_inputs["inputs"].items():
|
||||
if hasattr(self.scan_parameter, key):
|
||||
setattr(self.scan_parameter, key, value)
|
||||
for key, value in self.scan_info.msg.request_inputs["kwargs"].items():
|
||||
if hasattr(self.scan_parameter, key):
|
||||
setattr(self.scan_parameter, key, value)
|
||||
|
||||
|
||||
def _check_scan_msg(self, target_state: ScanControlLoadMessage) -> None:
|
||||
"""Check if the scan message is gettting available
|
||||
|
||||
Args:
|
||||
target_state (ScanControlLoadMessage): Target state to check for
|
||||
|
||||
Raises:
|
||||
TimeoutError: If the scan message is not available after the timeout
|
||||
"""
|
||||
state = self.scan_control.scan_msg.get()
|
||||
if state != target_state:
|
||||
logger.warning(
|
||||
f"Resetting scan validation in stage for state: {ScanControlLoadMessage(state)}, "
|
||||
f"retry .get() on scan_control: {ScanControlLoadMessage(self.scan_control.scan_msg.get())} and sleeping 1s"
|
||||
)
|
||||
self.scan_control.scan_val_reset.put(1)
|
||||
# Sleep to ensure the reset is done
|
||||
time.sleep(1)
|
||||
|
||||
try:
|
||||
self.wait_for_signal(self.scan_control.scan_msg, target_state)
|
||||
except TimeoutError as exc:
|
||||
raise TimeoutError(
|
||||
f"Timeout after {self.timeout_for_pvwait} while waiting for scan status,"
|
||||
f" current state: {ScanControlScanStatus(self.scan_control.scan_msg.get())}"
|
||||
) from exc
|
||||
436
debye_bec/devices/mo1_bragg/mo1_bragg_devices.py
Normal file
436
debye_bec/devices/mo1_bragg/mo1_bragg_devices.py
Normal file
@@ -0,0 +1,436 @@
|
||||
"""Module for the Mo1 Bragg positioner"""
|
||||
|
||||
import threading
|
||||
import time
|
||||
import traceback
|
||||
from typing import Literal
|
||||
|
||||
from bec_lib.logger import bec_logger
|
||||
from ophyd import Component as Cpt
|
||||
from ophyd import (
|
||||
Device,
|
||||
DeviceStatus,
|
||||
EpicsSignal,
|
||||
EpicsSignalRO,
|
||||
EpicsSignalWithRBV,
|
||||
PositionerBase,
|
||||
Signal,
|
||||
)
|
||||
from ophyd.utils import LimitError
|
||||
|
||||
from debye_bec.devices.mo1_bragg.mo1_bragg_enums import MoveType
|
||||
|
||||
# Initialise logger
|
||||
logger = bec_logger.logger
|
||||
|
||||
############# Exceptions #############
|
||||
|
||||
|
||||
class Mo1BraggStoppedError(Exception):
|
||||
"""Exception to raise when the Bragg positioner is stopped."""
|
||||
|
||||
|
||||
############# Signal classes #############
|
||||
|
||||
|
||||
class MoveTypeSignal(Signal):
|
||||
"""Custom Signal to set the move type of the Bragg positioner"""
|
||||
|
||||
# pylint: disable=arguments-differ
|
||||
def set(self, value: str | MoveType) -> None:
|
||||
"""Returns currently active move method
|
||||
|
||||
Args:
|
||||
value (str | MoveType) : Can be either 'energy' or 'angle'
|
||||
"""
|
||||
|
||||
value = MoveType(value.lower())
|
||||
self._readback = value.value
|
||||
|
||||
|
||||
############# Utility devices to separate the namespace #############
|
||||
|
||||
|
||||
class Mo1BraggStatus(Device):
|
||||
"""Mo1 Bragg PVs for status monitoring"""
|
||||
|
||||
error_status = Cpt(EpicsSignalRO, suffix="error_status_RBV", kind="config", auto_monitor=True)
|
||||
brake_enabled = Cpt(EpicsSignalRO, suffix="brake_enabled_RBV", kind="config", auto_monitor=True)
|
||||
mot_commutated = Cpt(
|
||||
EpicsSignalRO, suffix="mot_commutated_RBV", kind="config", auto_monitor=True
|
||||
)
|
||||
axis_enabled = Cpt(EpicsSignalRO, suffix="axis_enabled_RBV", kind="config", auto_monitor=True)
|
||||
enc_initialized = Cpt(
|
||||
EpicsSignalRO, suffix="enc_initialized_RBV", kind="config", auto_monitor=True
|
||||
)
|
||||
heartbeat = Cpt(EpicsSignalRO, suffix="heartbeat_RBV", kind="config", auto_monitor=True)
|
||||
|
||||
|
||||
class Mo1BraggEncoder(Device):
|
||||
"""Mo1 Bragg PVs to communicate with the encoder"""
|
||||
|
||||
enc_reinit = Cpt(EpicsSignal, suffix="enc_reinit", kind="config")
|
||||
enc_reinit_done = Cpt(EpicsSignalRO, suffix="enc_reinit_done_RBV", kind="config")
|
||||
|
||||
|
||||
class Mo1BraggCrystal(Device):
|
||||
"""Mo1 Bragg PVs to set the crystal parameters"""
|
||||
|
||||
offset_si111 = Cpt(EpicsSignalWithRBV, suffix="offset_si111", kind="config")
|
||||
offset_si311 = Cpt(EpicsSignalWithRBV, suffix="offset_si311", kind="config")
|
||||
xtal_enum = Cpt(EpicsSignalWithRBV, suffix="xtal_ENUM", kind="config")
|
||||
d_spacing_si111 = Cpt(EpicsSignalWithRBV, suffix="d_spacing_si111", kind="config")
|
||||
d_spacing_si311 = Cpt(EpicsSignalWithRBV, suffix="d_spacing_si311", kind="config")
|
||||
set_offset = Cpt(EpicsSignal, suffix="set_offset", kind="config", put_complete=True)
|
||||
current_xtal = Cpt(
|
||||
EpicsSignalRO, suffix="current_xtal_ENUM_RBV", kind="normal", auto_monitor=True
|
||||
)
|
||||
|
||||
|
||||
class Mo1BraggScanSettings(Device):
|
||||
"""Mo1 Bragg PVs to set the scan setttings"""
|
||||
|
||||
# TRIG settings
|
||||
trig_select_ref_enum = Cpt(EpicsSignalWithRBV, suffix="trig_select_ref_ENUM", kind="config")
|
||||
|
||||
trig_ena_hi_enum = Cpt(EpicsSignalWithRBV, suffix="trig_ena_hi_ENUM", kind="config")
|
||||
trig_time_hi = Cpt(EpicsSignalWithRBV, suffix="trig_time_hi", kind="config")
|
||||
trig_every_n_hi = Cpt(EpicsSignalWithRBV, suffix="trig_every_n_hi", kind="config")
|
||||
|
||||
trig_ena_lo_enum = Cpt(EpicsSignalWithRBV, suffix="trig_ena_lo_ENUM", kind="config")
|
||||
trig_time_lo = Cpt(EpicsSignalWithRBV, suffix="trig_time_lo", kind="config")
|
||||
trig_every_n_lo = Cpt(EpicsSignalWithRBV, suffix="trig_every_n_lo", kind="config")
|
||||
|
||||
# XAS simple scan settings
|
||||
s_scan_angle_hi = Cpt(EpicsSignalWithRBV, suffix="s_scan_angle_hi", kind="config")
|
||||
s_scan_angle_lo = Cpt(EpicsSignalWithRBV, suffix="s_scan_angle_lo", kind="config")
|
||||
s_scan_energy_lo = Cpt(
|
||||
EpicsSignalWithRBV, suffix="s_scan_energy_lo", kind="config", auto_monitor=True
|
||||
)
|
||||
s_scan_energy_hi = Cpt(
|
||||
EpicsSignalWithRBV, suffix="s_scan_energy_hi", kind="config", auto_monitor=True
|
||||
)
|
||||
s_scan_scantime = Cpt(
|
||||
EpicsSignalWithRBV, suffix="s_scan_scantime", kind="config", auto_monitor=True
|
||||
)
|
||||
|
||||
# XAS advanced scan settings
|
||||
a_scan_pos = Cpt(EpicsSignalWithRBV, suffix="a_scan_pos", kind="config", auto_monitor=True)
|
||||
a_scan_vel = Cpt(EpicsSignalWithRBV, suffix="a_scan_vel", kind="config", auto_monitor=True)
|
||||
a_scan_time = Cpt(EpicsSignalWithRBV, suffix="a_scan_time", kind="config", auto_monitor=True)
|
||||
|
||||
|
||||
class Mo1TriggerSettings(Device):
|
||||
"""Mo1 Trigger settings"""
|
||||
|
||||
settle_time = Cpt(EpicsSignalWithRBV, suffix="settle_time", kind="config")
|
||||
max_dev = Cpt(EpicsSignalWithRBV, suffix="max_dev", kind="config")
|
||||
|
||||
xrd_trig_src_enum = Cpt(EpicsSignalWithRBV, suffix="xrd_trig_src_ENUM", kind="config")
|
||||
xrd_trig_mode_enum = Cpt(EpicsSignalWithRBV, suffix="xrd_trig_mode_ENUM", kind="config")
|
||||
xrd_trig_len = Cpt(EpicsSignalWithRBV, suffix="xrd_trig_len", kind="config")
|
||||
xrd_trig_req = Cpt(EpicsSignal, suffix="xrd_trig_req", kind="config")
|
||||
|
||||
falcon_trig_src_enum = Cpt(EpicsSignalWithRBV, suffix="falcon_trig_src_ENUM", kind="config")
|
||||
falcon_trig_mode_enum = Cpt(EpicsSignalWithRBV, suffix="falcon_trig_mode_ENUM", kind="config")
|
||||
falcon_trig_len = Cpt(EpicsSignalWithRBV, suffix="falcon_trig_len", kind="config")
|
||||
falcon_trig_req = Cpt(EpicsSignal, suffix="falcon_trig_req", kind="config")
|
||||
|
||||
univ1_trig_src_enum = Cpt(EpicsSignalWithRBV, suffix="univ1_trig_src_ENUM", kind="config")
|
||||
univ1_trig_mode_enum = Cpt(EpicsSignalWithRBV, suffix="univ1_trig_mode_ENUM", kind="config")
|
||||
univ1_trig_len = Cpt(EpicsSignalWithRBV, suffix="univ1_trig_len", kind="config")
|
||||
univ1_trig_req = Cpt(EpicsSignal, suffix="univ1_trig_req", kind="config")
|
||||
|
||||
univ2_trig_src_enum = Cpt(EpicsSignalWithRBV, suffix="univ2_trig_src_ENUM", kind="config")
|
||||
univ2_trig_mode_enum = Cpt(EpicsSignalWithRBV, suffix="univ2_trig_mode_ENUM", kind="config")
|
||||
univ2_trig_len = Cpt(EpicsSignalWithRBV, suffix="univ2_trig_len", kind="config")
|
||||
univ2_trig_req = Cpt(EpicsSignal, suffix="univ2_trig_req", kind="config")
|
||||
|
||||
|
||||
class Mo1BraggCalculator(Device):
|
||||
"""Mo1 Bragg PVs to convert angle to energy or vice-versa."""
|
||||
|
||||
calc_reset = Cpt(EpicsSignal, suffix="calc_reset", kind="config", put_complete=True)
|
||||
calc_done = Cpt(EpicsSignalRO, suffix="calc_done_RBV", kind="config")
|
||||
calc_energy = Cpt(EpicsSignalWithRBV, suffix="calc_energy", kind="config")
|
||||
calc_angle = Cpt(EpicsSignalWithRBV, suffix="calc_angle", kind="config")
|
||||
|
||||
|
||||
class Mo1BraggScanControl(Device):
|
||||
"""Mo1 Bragg PVs to control the scan after setting the parameters."""
|
||||
|
||||
scan_mode_enum = Cpt(EpicsSignalWithRBV, suffix="scan_mode_ENUM", kind="config")
|
||||
scan_duration = Cpt(
|
||||
EpicsSignalWithRBV, suffix="scan_duration", kind="config", auto_monitor=True
|
||||
)
|
||||
scan_load = Cpt(EpicsSignal, suffix="scan_load", kind="config", put_complete=True)
|
||||
scan_msg = Cpt(EpicsSignalRO, suffix="scan_msg_ENUM_RBV", kind="config", auto_monitor=True)
|
||||
scan_start_infinite = Cpt(
|
||||
EpicsSignal, suffix="scan_start_infinite", kind="config", put_complete=True
|
||||
)
|
||||
scan_start_timer = Cpt(EpicsSignal, suffix="scan_start_timer", kind="config", put_complete=True)
|
||||
scan_stop = Cpt(EpicsSignal, suffix="scan_stop", kind="config", put_complete=True)
|
||||
scan_status = Cpt(
|
||||
EpicsSignalRO, suffix="scan_status_ENUM_RBV", kind="config", auto_monitor=True
|
||||
)
|
||||
scan_time_left = Cpt(
|
||||
EpicsSignalRO, suffix="scan_time_left_RBV", kind="config", auto_monitor=True
|
||||
)
|
||||
scan_done = Cpt(EpicsSignalRO, suffix="scan_done_RBV", kind="config", auto_monitor=True)
|
||||
scan_val_reset = Cpt(EpicsSignal, suffix="scan_val_reset", kind="config", put_complete=True)
|
||||
scan_progress = Cpt(EpicsSignalRO, suffix="scan_progress_RBV", kind="config", auto_monitor=True)
|
||||
scan_spectra_done = Cpt(
|
||||
EpicsSignalRO, suffix="scan_n_osc_RBV", kind="config", auto_monitor=True
|
||||
)
|
||||
scan_spectra_left = Cpt(
|
||||
EpicsSignalRO, suffix="scan_n_osc_left_RBV", kind="config", auto_monitor=True
|
||||
)
|
||||
|
||||
|
||||
class Mo1BraggPositioner(Device, PositionerBase):
|
||||
"""
|
||||
Positioner implementation of the MO1 Bragg positioner.
|
||||
|
||||
The prefix to connect to the soft IOC is X01DA-OP-MO1:BRAGG:
|
||||
This soft IOC connects to the NI motor and its control loop.
|
||||
"""
|
||||
|
||||
USER_ACCESS = ["set_advanced_xas_settings"]
|
||||
|
||||
####### Sub-components ########
|
||||
# Namespace is cleaner and easier to maintain
|
||||
crystal = Cpt(Mo1BraggCrystal, "")
|
||||
encoder = Cpt(Mo1BraggEncoder, "")
|
||||
scan_settings = Cpt(Mo1BraggScanSettings, "")
|
||||
trigger_settings = Cpt(Mo1TriggerSettings, "")
|
||||
calculator = Cpt(Mo1BraggCalculator, "")
|
||||
scan_control = Cpt(Mo1BraggScanControl, "")
|
||||
status = Cpt(Mo1BraggStatus, "")
|
||||
|
||||
############# switch between energy and angle #############
|
||||
# TODO should be removed/replaced once decision about pseudo motor is made
|
||||
move_type = Cpt(MoveTypeSignal, value=MoveType.ENERGY, kind="config")
|
||||
|
||||
############# Energy PVs #############
|
||||
|
||||
readback = Cpt(
|
||||
EpicsSignalRO, suffix="feedback_pos_energy_RBV", kind="hinted", auto_monitor=True
|
||||
)
|
||||
setpoint = Cpt(
|
||||
EpicsSignalWithRBV, suffix="set_abs_pos_energy", kind="normal", auto_monitor=True
|
||||
)
|
||||
motor_is_moving = Cpt(
|
||||
EpicsSignalRO, suffix="move_abs_done_RBV", kind="normal", auto_monitor=True
|
||||
)
|
||||
low_lim = Cpt(EpicsSignalRO, suffix="lo_lim_pos_energy_RBV", kind="config", auto_monitor=True)
|
||||
high_lim = Cpt(EpicsSignalRO, suffix="hi_lim_pos_energy_RBV", kind="config", auto_monitor=True)
|
||||
velocity = Cpt(EpicsSignalWithRBV, suffix="move_velocity", kind="config", auto_monitor=True)
|
||||
|
||||
########### Angle PVs #############
|
||||
|
||||
# TODO Pseudo motor for angle?
|
||||
feedback_pos_angle = Cpt(
|
||||
EpicsSignalRO, suffix="feedback_pos_angle_RBV", kind="normal", auto_monitor=True
|
||||
)
|
||||
setpoint_abs_angle = Cpt(
|
||||
EpicsSignalWithRBV, suffix="set_abs_pos_angle", kind="normal", auto_monitor=True
|
||||
)
|
||||
low_limit_angle = Cpt(
|
||||
EpicsSignalRO, suffix="lo_lim_pos_angle_RBV", kind="config", auto_monitor=True
|
||||
)
|
||||
high_limit_angle = Cpt(
|
||||
EpicsSignalRO, suffix="hi_lim_pos_angle_RBV", kind="config", auto_monitor=True
|
||||
)
|
||||
|
||||
########## Move Command PVs ##########
|
||||
|
||||
move_abs = Cpt(EpicsSignal, suffix="move_abs", kind="config", put_complete=True)
|
||||
move_stop = Cpt(EpicsSignal, suffix="move_stop", kind="config", put_complete=True)
|
||||
|
||||
SUB_READBACK = "readback"
|
||||
_default_sub = SUB_READBACK
|
||||
SUB_PROGRESS = "progress"
|
||||
|
||||
def __init__(self, prefix="", *, name: str, **kwargs):
|
||||
"""Initialize the Mo1 Bragg positioner.
|
||||
|
||||
Args:
|
||||
prefix (str): EPICS prefix for the device
|
||||
name (str): Name of the device
|
||||
kwargs: Additional keyword arguments
|
||||
"""
|
||||
super().__init__(prefix, name=name, **kwargs)
|
||||
self._move_thread = None
|
||||
self._stopped = False
|
||||
self.readback.name = self.name
|
||||
|
||||
def stop(self, *, success=False) -> None:
|
||||
"""Stop any motion on the positioner
|
||||
|
||||
Args:
|
||||
success (bool) : Flag to indicate if the motion was successful
|
||||
"""
|
||||
self.move_stop.put(1)
|
||||
if self._move_thread is not None:
|
||||
self._move_thread.join()
|
||||
self._move_thread = None
|
||||
super().stop(success=success)
|
||||
|
||||
def stop_scan(self) -> None:
|
||||
"""Stop the currently running scan gracefully, this finishes the running oscillation."""
|
||||
self.scan_control.scan_stop.put(1)
|
||||
|
||||
@property
|
||||
def stopped(self) -> bool:
|
||||
"""Return the status of the positioner"""
|
||||
return self._stopped
|
||||
|
||||
######### Positioner specific methods #########
|
||||
|
||||
@property
|
||||
def limits(self) -> tuple:
|
||||
"""Return limits of the Bragg positioner"""
|
||||
if self.move_type.get() == MoveType.ENERGY:
|
||||
return (self.low_lim.get(), self.high_lim.get())
|
||||
return (self.low_limit_angle.get(), self.high_limit_angle.get())
|
||||
|
||||
@property
|
||||
def low_limit(self) -> float:
|
||||
"""Return low limit of axis"""
|
||||
return self.limits[0]
|
||||
|
||||
@property
|
||||
def high_limit(self) -> float:
|
||||
"""Return high limit of axis"""
|
||||
return self.limits[1]
|
||||
|
||||
@property
|
||||
def egu(self) -> str:
|
||||
"""Return the engineering units of the positioner"""
|
||||
if self.move_type.get() == MoveType.ENERGY:
|
||||
return "eV"
|
||||
return "deg"
|
||||
|
||||
@property
|
||||
def position(self) -> float:
|
||||
"""Return the current position of Mo1Bragg, considering the move type"""
|
||||
move_type = self.move_type.get()
|
||||
move_cpt = self.readback if move_type == MoveType.ENERGY else self.feedback_pos_angle
|
||||
return move_cpt.get()
|
||||
|
||||
# pylint: disable=arguments-differ
|
||||
def check_value(self, value: float) -> None:
|
||||
"""Method to check if a value is within limits of the positioner.
|
||||
Called by PositionerBase.move()
|
||||
|
||||
Args:
|
||||
value (float) : value to move axis to.
|
||||
"""
|
||||
low_limit, high_limit = self.limits
|
||||
|
||||
if low_limit < high_limit and not low_limit <= value <= high_limit:
|
||||
raise LimitError(f"position={value} not within limits {self.limits}")
|
||||
|
||||
def _move_and_finish(
|
||||
self, target_pos: float, move_cpt: Cpt, status: DeviceStatus, update_frequency: float = 0.1
|
||||
) -> None:
|
||||
"""
|
||||
Method to be called in the move thread to move the Bragg positioner
|
||||
to the target position.
|
||||
|
||||
Args:
|
||||
target_pos (float) : target position for the motion
|
||||
move_cpt (Cpt) : component to set the target position on the IOC,
|
||||
either setpoint or setpoint_abs_angle depending
|
||||
on the move type
|
||||
read_cpt (Cpt) : component to read the current position of the motion,
|
||||
readback or feedback_pos_angle
|
||||
status (DeviceStatus) : status object to set the status of the motion
|
||||
update_frequency (float): Optional, frequency to update the current position of
|
||||
the motion, defaults to 0.1s
|
||||
"""
|
||||
try:
|
||||
# Set the target position on IOC
|
||||
move_cpt.put(target_pos)
|
||||
self.move_abs.put(1)
|
||||
# Currently sleep is needed due to delay in updates on PVs, maybe time can be reduced
|
||||
time.sleep(0.5)
|
||||
while self.motor_is_moving.get() == 0:
|
||||
if self.stopped:
|
||||
raise Mo1BraggStoppedError(f"Device {self.name} was stopped")
|
||||
time.sleep(update_frequency)
|
||||
# pylint: disable=protected-access
|
||||
status.set_finished()
|
||||
# pylint: disable=broad-except
|
||||
except Exception as exc:
|
||||
content = traceback.format_exc()
|
||||
logger.error(f"Error in move thread of device {self.name}: {content}")
|
||||
status.set_exception(exc=exc)
|
||||
|
||||
def move(self, value: float, move_type: str | MoveType = None, **kwargs) -> DeviceStatus:
|
||||
"""
|
||||
Move the Bragg positioner to the specified value, allows to
|
||||
switch between move types angle and energy.
|
||||
|
||||
Args:
|
||||
value (float) : target value for the motion
|
||||
move_type (str | MoveType) : Optional, specify the type of move,
|
||||
either 'energy' or 'angle'
|
||||
|
||||
Returns:
|
||||
DeviceStatus : status object to track the motion
|
||||
"""
|
||||
self._stopped = False
|
||||
if move_type is not None:
|
||||
self.move_type.put(move_type)
|
||||
move_type = self.move_type.get()
|
||||
move_cpt = self.setpoint if move_type == MoveType.ENERGY else self.setpoint_abs_angle
|
||||
|
||||
self.check_value(value)
|
||||
status = DeviceStatus(device=self)
|
||||
|
||||
self._move_thread = threading.Thread(
|
||||
target=self._move_and_finish, args=(value, move_cpt, status, 0.1)
|
||||
)
|
||||
self._move_thread.start()
|
||||
return status
|
||||
|
||||
# -------------- End of Positioner specific methods -----------------#
|
||||
|
||||
# -------------- MO1 Bragg specific methods -----------------#
|
||||
|
||||
def set_xtal(
|
||||
self,
|
||||
xtal_enum: Literal["111", "311"],
|
||||
offset_si111: float = None,
|
||||
offset_si311: float = None,
|
||||
d_spacing_si111: float = None,
|
||||
d_spacing_si311: float = None,
|
||||
) -> None:
|
||||
"""Method to set the crystal parameters of the Bragg positioner
|
||||
|
||||
Args:
|
||||
xtal_enum (Literal["111", "311"]) : Enum to set the crystal orientation
|
||||
offset_si111 (float) : Offset for the 111 crystal
|
||||
offset_si311 (float) : Offset for the 311 crystal
|
||||
d_spacing_si111 (float) : d-spacing for the 111 crystal
|
||||
d_spacing_si311 (float) : d-spacing for the 311 crystal
|
||||
"""
|
||||
if offset_si111 is not None:
|
||||
self.crystal.offset_si111.put(offset_si111)
|
||||
if offset_si311 is not None:
|
||||
self.crystal.offset_si311.put(offset_si311)
|
||||
if d_spacing_si111 is not None:
|
||||
self.crystal.d_spacing_si111.put(d_spacing_si111)
|
||||
if d_spacing_si311 is not None:
|
||||
self.crystal.d_spacing_si311.put(d_spacing_si311)
|
||||
if xtal_enum == "111":
|
||||
crystal_set = 0
|
||||
elif xtal_enum == "311":
|
||||
crystal_set = 1
|
||||
else:
|
||||
raise ValueError(
|
||||
f"Invalid argument for xtal_enum : {xtal_enum}, choose from '111' or '311'"
|
||||
)
|
||||
self.crystal.xtal_enum.put(crystal_set)
|
||||
self.crystal.set_offset.put(1)
|
||||
61
debye_bec/devices/mo1_bragg/mo1_bragg_enums.py
Normal file
61
debye_bec/devices/mo1_bragg/mo1_bragg_enums.py
Normal file
@@ -0,0 +1,61 @@
|
||||
"""Enums for the Bragg positioner and trigger generator"""
|
||||
|
||||
import enum
|
||||
|
||||
|
||||
class TriggerControlSource(int, enum.Enum):
|
||||
"""Enum class for the trigger control source of the trigger generator"""
|
||||
|
||||
EPICS = 0
|
||||
INPOS = 1
|
||||
|
||||
|
||||
class TriggerControlMode(int, enum.Enum):
|
||||
"""Enum class for the trigger control mode of the trigger generator"""
|
||||
|
||||
PULSE = 0
|
||||
CONDITION = 1
|
||||
|
||||
|
||||
class ScanControlScanStatus(int, enum.Enum):
|
||||
"""Enum class for the scan status of the Bragg positioner"""
|
||||
|
||||
PARAMETER_WRONG = 0
|
||||
VALIDATION_PENDING = 1
|
||||
READY = 2
|
||||
RUNNING = 3
|
||||
|
||||
|
||||
class ScanControlLoadMessage(int, enum.Enum):
|
||||
"""Enum for validating messages for load message of the Bragg positioner"""
|
||||
|
||||
PENDING = 0
|
||||
STARTED = 1
|
||||
SUCCESS = 2
|
||||
ERR_TRIG_MEAS_LEN_LOW = 3
|
||||
ERR_TRIG_N_TRIGGERS_LOW = 4
|
||||
ERR_TRIG_TRIGS_EVERY_N_LOW = 5
|
||||
ERR_TRIG_MEAS_LEN_HI = 6
|
||||
ERR_TRIG_N_TRIGGERS_HI = 7
|
||||
ERR_TRIG_TRIGS_EVERY_N_HI = 8
|
||||
ERR_SCAN_HI_ANGLE_LIMIT = 9
|
||||
ERR_SCAN_LOW_ANGLE_LIMITS = 10
|
||||
ERR_SCAN_TIME = 11
|
||||
ERR_SCAN_VEL_TOO_HI = 12
|
||||
ERR_SCAN_ANGLE_OUT_OF_LIM = 13
|
||||
ERR_SCAN_HIGH_VEL_LAR_42 = 14
|
||||
ERR_SCAN_MODE_INVALID = 15
|
||||
|
||||
|
||||
class MoveType(str, enum.Enum):
|
||||
"""Enum class to switch between move types energy and angle for the Bragg positioner"""
|
||||
|
||||
ENERGY = "energy"
|
||||
ANGLE = "angle"
|
||||
|
||||
|
||||
class ScanControlMode(int, enum.Enum):
|
||||
"""Enum class for the scan control mode of the Bragg positioner"""
|
||||
|
||||
SIMPLE = 0
|
||||
ADVANCED = 1
|
||||
@@ -16,14 +16,14 @@ from ophyd.utils import LimitError
|
||||
from ophyd_devices.tests.utils import MockPV
|
||||
|
||||
# from bec_server.device_server.tests.utils import DMMock
|
||||
from debye_bec.devices.mo1_bragg import (
|
||||
from debye_bec.devices.mo1_bragg.mo1_bragg import (
|
||||
Mo1Bragg,
|
||||
Mo1BraggError,
|
||||
MoveType,
|
||||
ScanControlLoadMessage,
|
||||
ScanControlMode,
|
||||
ScanControlScanStatus,
|
||||
)
|
||||
from debye_bec.devices.mo1_bragg.mo1_bragg_devices import MoveType
|
||||
|
||||
# TODO move this function to ophyd_devices, it is duplicated in csaxs_bec and needed for other pluging repositories
|
||||
from debye_bec.devices.test_utils.utils import patch_dual_pvs
|
||||
@@ -40,10 +40,7 @@ def scan_worker_mock(scan_server_mock):
|
||||
def mock_bragg():
|
||||
name = "bragg"
|
||||
prefix = "X01DA-OP-MO1:BRAGG:"
|
||||
with (
|
||||
mock.patch.object(ophyd, "cl") as mock_cl,
|
||||
mock.patch("debye_bec.devices.mo1_bragg.Mo1Bragg", "_on_init"),
|
||||
):
|
||||
with mock.patch.object(ophyd, "cl") as mock_cl:
|
||||
mock_cl.get_pv = MockPV
|
||||
mock_cl.thread_class = threading.Thread
|
||||
dev = Mo1Bragg(name=name, prefix=prefix)
|
||||
@@ -183,6 +180,24 @@ def test_update_scan_parameters(mock_bragg):
|
||||
msg = ScanStatusMessage(
|
||||
scan_id="my_scan_id",
|
||||
status="closed",
|
||||
request_inputs={
|
||||
"kwargs": {
|
||||
"start": 0,
|
||||
"stop": 5,
|
||||
"scan_time": 1,
|
||||
"scan_duration": 10,
|
||||
"xrd_enable_low": True,
|
||||
"xrd_enable_high": False,
|
||||
"num_trigger_low": 1,
|
||||
"num_trigger_high": 7,
|
||||
"exp_time_low": 1,
|
||||
"exp_time_high": 3,
|
||||
"cycle_low": 1,
|
||||
"cycle_high": 5,
|
||||
"p_kink": 50,
|
||||
"e_kink": 8000,
|
||||
}
|
||||
},
|
||||
info={
|
||||
"kwargs": {
|
||||
"start": 0,
|
||||
@@ -203,14 +218,14 @@ def test_update_scan_parameters(mock_bragg):
|
||||
},
|
||||
metadata={},
|
||||
)
|
||||
mock_bragg.scaninfo.scan_msg = msg
|
||||
for field in fields(dev.scan_parameter):
|
||||
assert getattr(dev.scan_parameter, field.name) == None
|
||||
mock_bragg.scan_info.msg = msg
|
||||
scan_param = dev.scan_parameter.model_dump()
|
||||
for _, v in scan_param.items():
|
||||
assert v == None
|
||||
dev._update_scan_parameter()
|
||||
for field in fields(dev.scan_parameter):
|
||||
assert getattr(dev.scan_parameter, field.name) == msg.content["info"]["kwargs"].get(
|
||||
field.name, None
|
||||
)
|
||||
scan_param = dev.scan_parameter.model_dump()
|
||||
for k, v in scan_param.items():
|
||||
assert v == msg.content["request_inputs"]["kwargs"].get(k, None)
|
||||
|
||||
|
||||
def test_kickoff_scan(mock_bragg):
|
||||
@@ -243,7 +258,8 @@ def test_complete(mock_bragg):
|
||||
assert status.done is False
|
||||
assert status.success is False
|
||||
dev.scan_control.scan_done._read_pv.mock_data = 1
|
||||
time.sleep(0.2)
|
||||
status.wait()
|
||||
# time.sleep(0.2)
|
||||
assert status.done is True
|
||||
assert status.success is True
|
||||
|
||||
@@ -264,7 +280,7 @@ def test_unstage(mock_bragg):
|
||||
mock_bragg.scan_control.scan_msg._read_pv.mock_data = ScanControlLoadMessage.PENDING
|
||||
|
||||
with mock.patch.object(mock_bragg.scan_control.scan_val_reset, "put") as mock_put:
|
||||
mock_bragg.unstage()
|
||||
status = mock_bragg.unstage()
|
||||
assert mock_put.call_count == 0
|
||||
mock_bragg.scan_control.scan_msg._read_pv.mock_data = ScanControlLoadMessage.SUCCESS
|
||||
with pytest.raises(TimeoutError):
|
||||
@@ -272,142 +288,160 @@ def test_unstage(mock_bragg):
|
||||
assert mock_put.call_count == 1
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"msg",
|
||||
[
|
||||
ScanQueueMessage(
|
||||
scan_type="monitor_scan",
|
||||
parameter={
|
||||
"args": {},
|
||||
"kwargs": {
|
||||
"device": "mo1_bragg",
|
||||
"start": 0,
|
||||
"stop": 10,
|
||||
"relative": True,
|
||||
"system_config": {"file_suffix": None, "file_directory": None},
|
||||
},
|
||||
"num_points": 100,
|
||||
},
|
||||
queue="primary",
|
||||
metadata={"RID": "test1234"},
|
||||
),
|
||||
ScanQueueMessage(
|
||||
scan_type="xas_simple_scan",
|
||||
parameter={
|
||||
"args": {},
|
||||
"kwargs": {
|
||||
"motor": "mo1_bragg",
|
||||
"start": 0,
|
||||
"stop": 10,
|
||||
"scan_time": 1,
|
||||
"scan_duration": 10,
|
||||
"system_config": {"file_suffix": None, "file_directory": None},
|
||||
},
|
||||
"num_points": 100,
|
||||
},
|
||||
queue="primary",
|
||||
metadata={"RID": "test1234"},
|
||||
),
|
||||
ScanQueueMessage(
|
||||
scan_type="xas_simple_scan_with_xrd",
|
||||
parameter={
|
||||
"args": {},
|
||||
"kwargs": {
|
||||
"motor": "mo1_bragg",
|
||||
"start": 0,
|
||||
"stop": 10,
|
||||
"scan_time": 1,
|
||||
"scan_duration": 10,
|
||||
"xrd_enable_low": True,
|
||||
"xrd_enable_high": False,
|
||||
"num_trigger_low": 1,
|
||||
"num_trigger_high": 7,
|
||||
"exp_time_low": 1,
|
||||
"exp_time_high": 3,
|
||||
"cycle_low": 1,
|
||||
"cycle_high": 5,
|
||||
"system_config": {"file_suffix": None, "file_directory": None},
|
||||
},
|
||||
"num_points": 10,
|
||||
},
|
||||
queue="primary",
|
||||
metadata={"RID": "test1234"},
|
||||
),
|
||||
ScanQueueMessage(
|
||||
scan_type="xas_advanced_scan",
|
||||
parameter={
|
||||
"args": {},
|
||||
"kwargs": {
|
||||
"motor": "mo1_bragg",
|
||||
"start": 8000,
|
||||
"stop": 9000,
|
||||
"scan_time": 1,
|
||||
"scan_duration": 10,
|
||||
"p_kink": 50,
|
||||
"e_kink": 8500,
|
||||
"system_config": {"file_suffix": None, "file_directory": None},
|
||||
},
|
||||
"num_points": 100,
|
||||
},
|
||||
queue="primary",
|
||||
metadata={"RID": "test1234"},
|
||||
),
|
||||
ScanQueueMessage(
|
||||
scan_type="xas_advanced_scan_with_xrd",
|
||||
parameter={
|
||||
"args": {},
|
||||
"kwargs": {
|
||||
"motor": "mo1_bragg",
|
||||
"start": 8000,
|
||||
"stop": 9000,
|
||||
"scan_time": 1,
|
||||
"scan_duration": 10,
|
||||
"p_kink": 50,
|
||||
"e_kink": 8500,
|
||||
"xrd_enable_low": True,
|
||||
"xrd_enable_high": False,
|
||||
"num_trigger_low": 1,
|
||||
"num_trigger_high": 7,
|
||||
"exp_time_low": 1,
|
||||
"exp_time_high": 3,
|
||||
"cycle_low": 1,
|
||||
"cycle_high": 5,
|
||||
"system_config": {"file_suffix": None, "file_directory": None},
|
||||
},
|
||||
"num_points": 10,
|
||||
},
|
||||
queue="primary",
|
||||
metadata={"RID": "test1234"},
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_stage(mock_bragg, scan_worker_mock, msg):
|
||||
"""This test is important to check that the stage method of the device is working correctly.
|
||||
Changing the kwargs names in the scans is tightly linked to the logic on the device, thus
|
||||
it is important to check that the stage method is working correctly for the current implementation.
|
||||
# TODO reimplement the test for stage method
|
||||
# @pytest.mark.parametrize(
|
||||
# "msg",
|
||||
# [
|
||||
# ScanQueueMessage(
|
||||
# scan_type="monitor_scan",
|
||||
# parameter={
|
||||
# "args": {},
|
||||
# "kwargs": {
|
||||
# "device": "mo1_bragg",
|
||||
# "start": 0,
|
||||
# "stop": 10,
|
||||
# "relative": True,
|
||||
# "system_config": {"file_suffix": None, "file_directory": None},
|
||||
# },
|
||||
# "num_points": 100,
|
||||
# },
|
||||
# queue="primary",
|
||||
# metadata={"RID": "test1234"},
|
||||
# ),
|
||||
# ScanQueueMessage(
|
||||
# scan_type="xas_simple_scan",
|
||||
# parameter={
|
||||
# "args": {},
|
||||
# "kwargs": {
|
||||
# "motor": "mo1_bragg",
|
||||
# "start": 0,
|
||||
# "stop": 10,
|
||||
# "scan_time": 1,
|
||||
# "scan_duration": 10,
|
||||
# "system_config": {"file_suffix": None, "file_directory": None},
|
||||
# },
|
||||
# "num_points": 100,
|
||||
# },
|
||||
# queue="primary",
|
||||
# metadata={"RID": "test1234"},
|
||||
# ),
|
||||
# ScanQueueMessage(
|
||||
# scan_type="xas_simple_scan_with_xrd",
|
||||
# parameter={
|
||||
# "args": {},
|
||||
# "kwargs": {
|
||||
# "motor": "mo1_bragg",
|
||||
# "start": 0,
|
||||
# "stop": 10,
|
||||
# "scan_time": 1,
|
||||
# "scan_duration": 10,
|
||||
# "xrd_enable_low": True,
|
||||
# "xrd_enable_high": False,
|
||||
# "num_trigger_low": 1,
|
||||
# "num_trigger_high": 7,
|
||||
# "exp_time_low": 1,
|
||||
# "exp_time_high": 3,
|
||||
# "cycle_low": 1,
|
||||
# "cycle_high": 5,
|
||||
# "system_config": {"file_suffix": None, "file_directory": None},
|
||||
# },
|
||||
# "num_points": 10,
|
||||
# },
|
||||
# queue="primary",
|
||||
# metadata={"RID": "test1234"},
|
||||
# ),
|
||||
# ScanQueueMessage(
|
||||
# scan_type="xas_advanced_scan",
|
||||
# parameter={
|
||||
# "args": {},
|
||||
# "kwargs": {
|
||||
# "motor": "mo1_bragg",
|
||||
# "start": 8000,
|
||||
# "stop": 9000,
|
||||
# "scan_time": 1,
|
||||
# "scan_duration": 10,
|
||||
# "p_kink": 50,
|
||||
# "e_kink": 8500,
|
||||
# "system_config": {"file_suffix": None, "file_directory": None},
|
||||
# },
|
||||
# "num_points": 100,
|
||||
# },
|
||||
# queue="primary",
|
||||
# metadata={"RID": "test1234"},
|
||||
# ),
|
||||
# ScanQueueMessage(
|
||||
# scan_type="xas_advanced_scan_with_xrd",
|
||||
# parameter={
|
||||
# "args": {},
|
||||
# "kwargs": {
|
||||
# "motor": "mo1_bragg",
|
||||
# "start": 8000,
|
||||
# "stop": 9000,
|
||||
# "scan_time": 1,
|
||||
# "scan_duration": 10,
|
||||
# "p_kink": 50,
|
||||
# "e_kink": 8500,
|
||||
# "xrd_enable_low": True,
|
||||
# "xrd_enable_high": False,
|
||||
# "num_trigger_low": 1,
|
||||
# "num_trigger_high": 7,
|
||||
# "exp_time_low": 1,
|
||||
# "exp_time_high": 3,
|
||||
# "cycle_low": 1,
|
||||
# "cycle_high": 5,
|
||||
# "system_config": {"file_suffix": None, "file_directory": None},
|
||||
# },
|
||||
# "num_points": 10,
|
||||
# },
|
||||
# queue="primary",
|
||||
# metadata={"RID": "test1234"},
|
||||
# ),
|
||||
# ],
|
||||
# )
|
||||
# def test_stage(mock_bragg, scan_worker_mock, msg):
|
||||
# """This test is important to check that the stage method of the device is working correctly.
|
||||
# Changing the kwargs names in the scans is tightly linked to the logic on the device, thus
|
||||
# it is important to check that the stage method is working correctly for the current implementation.
|
||||
|
||||
Therefor, this test creates a scaninfo message using the scan.open_scan() method to always check
|
||||
agains the currently implemented scans vs. the logic on the device"""
|
||||
# Create a scaninfo message using scans the ScanQueueMessages above, 3 cases of fly scan; for the general case the procedure is not defined yet
|
||||
worker = scan_worker_mock
|
||||
scan_server = worker.parent
|
||||
rb = RequestBlock(msg, assembler=ScanAssembler(parent=scan_server))
|
||||
with mock.patch.object(worker, "current_instruction_queue_item"):
|
||||
worker.scan_motors = []
|
||||
worker.readout_priority = {
|
||||
"monitored": [],
|
||||
"baseline": [],
|
||||
"async": [],
|
||||
"continuous": [],
|
||||
"on_request": [],
|
||||
}
|
||||
open_scan_msg = list(rb.scan.open_scan())[0]
|
||||
worker._initialize_scan_info(rb, open_scan_msg, msg.content["parameter"].get("num_points"))
|
||||
scan_status_msg = ScanStatusMessage(
|
||||
scan_id="test1234", status="closed", info=worker.current_scan_info, metadata={}
|
||||
)
|
||||
mock_bragg.scaninfo.scan_msg = scan_status_msg
|
||||
# Therefor, this test creates a scaninfo message using the scan.open_scan() method to always check
|
||||
# agains the currently implemented scans vs. the logic on the device"""
|
||||
# # Create a scaninfo message using scans the ScanQueueMessages above, 3 cases of fly scan; for the general case the procedure is not defined yet
|
||||
# worker = scan_worker_mock
|
||||
# scan_server = worker.parent
|
||||
# rb = RequestBlock(msg, assembler=ScanAssembler(parent=scan_server))
|
||||
# with mock.patch.object(worker, "current_instruction_queue_item"):
|
||||
# worker.scan_motors = []
|
||||
# worker.readout_priority = {
|
||||
# "monitored": [],
|
||||
# "baseline": [],
|
||||
# "async": [],
|
||||
# "continuous": [],
|
||||
# "on_request": [],
|
||||
# }
|
||||
# open_scan_msg = list(rb.scan.open_scan())[0]
|
||||
# worker._initialize_scan_info(
|
||||
# rb, open_scan_msg, msg.content["parameter"].get("num_points", 1)
|
||||
# )
|
||||
# # TODO find a better solution to this...
|
||||
# scan_status_msg = ScanStatusMessage(
|
||||
# scan_id=worker.current_scan_id,
|
||||
# status="open",
|
||||
# scan_name=worker.current_scan_info.get("scan_name"),
|
||||
# scan_number=worker.current_scan_info.get("scan_number"),
|
||||
# session_id=worker.current_scan_info.get("session_id"),
|
||||
# dataset_number=worker.current_scan_info.get("dataset_number"),
|
||||
# num_points=worker.current_scan_info.get("num_points"),
|
||||
# scan_type=worker.current_scan_info.get("scan_type"),
|
||||
# scan_report_devices=worker.current_scan_info.get("scan_report_devices"),
|
||||
# user_metadata=worker.current_scan_info.get("user_metadata"),
|
||||
# readout_priority=worker.current_scan_info.get("readout_priority"),
|
||||
# scan_parameters=worker.current_scan_info.get("scan_parameters"),
|
||||
# request_inputs=worker.current_scan_info.get("request_inputs"),
|
||||
# info=worker.current_scan_info,
|
||||
# )
|
||||
# mock_bragg.scan_info.msg = scan_status_msg
|
||||
|
||||
<<<<<<< Updated upstream
|
||||
# Ensure that ScanControlLoadMessage is set to SUCCESS
|
||||
mock_bragg.scan_control.scan_msg._read_pv.mock_data = ScanControlLoadMessage.SUCCESS
|
||||
with (
|
||||
@@ -538,3 +572,133 @@ def test_stage(mock_bragg, scan_worker_mock, msg):
|
||||
"scan_duration"
|
||||
],
|
||||
)
|
||||
=======
|
||||
# # Ensure that ScanControlLoadMessage is set to SUCCESS
|
||||
# mock_bragg.scan_control.scan_msg._read_pv.mock_data = ScanControlLoadMessage.SUCCESS
|
||||
# with (
|
||||
# mock.patch.object(mock_bragg, "_check_scan_msg") as mock_check_scan_msg,
|
||||
# mock.patch.object(mock_bragg, "on_unstage"),
|
||||
# ):
|
||||
# scan_name = scan_status_msg.content["info"].get("scan_name", "")
|
||||
# # Chek the not implemented fly scan first, should raise Mo1BraggError
|
||||
# if scan_name not in [
|
||||
# "xas_simple_scan",
|
||||
# "xas_simple_scan_with_xrd",
|
||||
# "xas_advanced_scan",
|
||||
# "xas_advanced_scan_with_xrd",
|
||||
# ]:
|
||||
# with pytest.raises(Mo1BraggError):
|
||||
# mock_bragg.stage()
|
||||
# assert mock_check_scan_msg.call_count == 1
|
||||
# else:
|
||||
# with (
|
||||
# mock.patch.object(mock_bragg, "set_xas_settings") as mock_xas_settings,
|
||||
# mock.patch.object(
|
||||
# mock_bragg, "set_advanced_xas_settings"
|
||||
# ) as mock_advanced_xas_settings,
|
||||
# mock.patch.object(mock_bragg, "set_trig_settings") as mock_trig_settings,
|
||||
# mock.patch.object(
|
||||
# mock_bragg, "set_scan_control_settings"
|
||||
# ) as mock_set_scan_control_settings,
|
||||
# ):
|
||||
# # Check xas_simple_scan
|
||||
# if scan_name == "xas_simple_scan":
|
||||
# mock_bragg.stage()
|
||||
# assert mock_xas_settings.call_args == mock.call(
|
||||
# low=scan_status_msg.content["info"]["kwargs"]["start"],
|
||||
# high=scan_status_msg.content["info"]["kwargs"]["stop"],
|
||||
# scan_time=scan_status_msg.content["info"]["kwargs"]["scan_time"],
|
||||
# )
|
||||
# assert mock_trig_settings.call_args == mock.call(
|
||||
# enable_low=False,
|
||||
# enable_high=False,
|
||||
# exp_time_low=0,
|
||||
# exp_time_high=0,
|
||||
# cycle_low=0,
|
||||
# cycle_high=0,
|
||||
# )
|
||||
# assert mock_set_scan_control_settings.call_args == mock.call(
|
||||
# mode=ScanControlMode.SIMPLE,
|
||||
# scan_duration=scan_status_msg.content["info"]["kwargs"][
|
||||
# "scan_duration"
|
||||
# ],
|
||||
# )
|
||||
# # Check xas_simple_scan_with_xrd
|
||||
# elif scan_name == "xas_simple_scan_with_xrd":
|
||||
# mock_bragg.stage()
|
||||
# assert mock_xas_settings.call_args == mock.call(
|
||||
# low=scan_status_msg.content["info"]["kwargs"]["start"],
|
||||
# high=scan_status_msg.content["info"]["kwargs"]["stop"],
|
||||
# scan_time=scan_status_msg.content["info"]["kwargs"]["scan_time"],
|
||||
# )
|
||||
# assert mock_trig_settings.call_args == mock.call(
|
||||
# enable_low=scan_status_msg.content["info"]["kwargs"]["xrd_enable_low"],
|
||||
# enable_high=scan_status_msg.content["info"]["kwargs"][
|
||||
# "xrd_enable_high"
|
||||
# ],
|
||||
# exp_time_low=scan_status_msg.content["info"]["kwargs"]["exp_time_low"],
|
||||
# exp_time_high=scan_status_msg.content["info"]["kwargs"][
|
||||
# "exp_time_high"
|
||||
# ],
|
||||
# cycle_low=scan_status_msg.content["info"]["kwargs"]["cycle_low"],
|
||||
# cycle_high=scan_status_msg.content["info"]["kwargs"]["cycle_high"],
|
||||
# )
|
||||
# assert mock_set_scan_control_settings.call_args == mock.call(
|
||||
# mode=ScanControlMode.SIMPLE,
|
||||
# scan_duration=scan_status_msg.content["info"]["kwargs"][
|
||||
# "scan_duration"
|
||||
# ],
|
||||
# )
|
||||
# # Check xas_advanced_scan
|
||||
# elif scan_name == "xas_advanced_scan":
|
||||
# mock_bragg.stage()
|
||||
# assert mock_advanced_xas_settings.call_args == mock.call(
|
||||
# low=scan_status_msg.content["info"]["kwargs"]["start"],
|
||||
# high=scan_status_msg.content["info"]["kwargs"]["stop"],
|
||||
# scan_time=scan_status_msg.content["info"]["kwargs"]["scan_time"],
|
||||
# p_kink=scan_status_msg.content["info"]["kwargs"]["p_kink"],
|
||||
# e_kink=scan_status_msg.content["info"]["kwargs"]["e_kink"],
|
||||
# )
|
||||
# assert mock_trig_settings.call_args == mock.call(
|
||||
# enable_low=False,
|
||||
# enable_high=False,
|
||||
# exp_time_low=0,
|
||||
# exp_time_high=0,
|
||||
# cycle_low=0,
|
||||
# cycle_high=0,
|
||||
# )
|
||||
# assert mock_set_scan_control_settings.call_args == mock.call(
|
||||
# mode=ScanControlMode.ADVANCED,
|
||||
# scan_duration=scan_status_msg.content["info"]["kwargs"][
|
||||
# "scan_duration"
|
||||
# ],
|
||||
# )
|
||||
# # Check xas_advanced_scan_with_xrd
|
||||
# elif scan_name == "xas_advanced_scan_with_xrd":
|
||||
# mock_bragg.stage()
|
||||
# assert mock_advanced_xas_settings.call_args == mock.call(
|
||||
# low=scan_status_msg.content["info"]["kwargs"]["start"],
|
||||
# high=scan_status_msg.content["info"]["kwargs"]["stop"],
|
||||
# scan_time=scan_status_msg.content["info"]["kwargs"]["scan_time"],
|
||||
# p_kink=scan_status_msg.content["info"]["kwargs"]["p_kink"],
|
||||
# e_kink=scan_status_msg.content["info"]["kwargs"]["e_kink"],
|
||||
# )
|
||||
# assert mock_trig_settings.call_args == mock.call(
|
||||
# enable_low=scan_status_msg.content["info"]["kwargs"]["xrd_enable_low"],
|
||||
# enable_high=scan_status_msg.content["info"]["kwargs"][
|
||||
# "xrd_enable_high"
|
||||
# ],
|
||||
# exp_time_low=scan_status_msg.content["info"]["kwargs"]["exp_time_low"],
|
||||
# exp_time_high=scan_status_msg.content["info"]["kwargs"][
|
||||
# "exp_time_high"
|
||||
# ],
|
||||
# cycle_low=scan_status_msg.content["info"]["kwargs"]["cycle_low"],
|
||||
# cycle_high=scan_status_msg.content["info"]["kwargs"]["cycle_high"],
|
||||
# )
|
||||
# assert mock_set_scan_control_settings.call_args == mock.call(
|
||||
# mode=ScanControlMode.ADVANCED,
|
||||
# scan_duration=scan_status_msg.content["info"]["kwargs"][
|
||||
# "scan_duration"
|
||||
# ],
|
||||
# )
|
||||
>>>>>>> Stashed changes
|
||||
|
||||
@@ -1,22 +1,152 @@
|
||||
# pylint: skip-file
|
||||
import debye_bec.devices.utils.mo1_bragg_utils as utils
|
||||
import numpy as np
|
||||
|
||||
import debye_bec.devices.mo1_bragg.mo1_bragg_utils as utils
|
||||
|
||||
|
||||
def test_compute_spline():
|
||||
p, v, dt = utils.compute_spline(low_deg=10, high_deg=12, p_kink=50, e_kink_deg=11, scan_time=0.5)
|
||||
p, v, dt = utils.compute_spline(
|
||||
low_deg=10, high_deg=12, p_kink=50, e_kink_deg=11, scan_time=0.5
|
||||
)
|
||||
|
||||
rtol = 1e-6
|
||||
atol = 1e-3
|
||||
p_desired = [9.98,9.98376125,9.99479,10.01270375,10.03712,10.06765625,10.10393,10.14555875,10.19216,10.24335125,10.29875,10.35797375,10.42064,10.48636625,10.55477,10.62546875,10.69808,10.77222125,10.84751,10.92356375,11.,11.07643625,11.15249,11.22777875,11.30192,11.37453125,11.44523,11.51363375,11.57936,11.64202625,11.70125,11.75664875,11.80784,11.85444125,11.89607,11.93234375,11.96288,11.98729625,12.00521,12.01623875,12.02]
|
||||
v_desired = [0.,1.50156441,2.35715667,2.90783907,3.29035796,3.57019636,3.78263174,3.9483388,4.08022441,4.18675043,4.27368333,4.34507577,4.40384627,4.45213618,4.49153736,4.52324148,4.54814006,4.5668924,4.57997194,4.58769736,4.59025246,4.58769736,4.57997194,4.5668924,4.54814006,4.52324148,4.49153736,4.45213618,4.40384627,4.34507577,4.27368333,4.18675043,4.08022441,3.9483388,3.78263174,3.57019636,3.29035796,2.90783907,2.35715667,1.50156441,0.]
|
||||
dt_desired = [0.,4.34081063,5.57222438,6.73882688,7.84061813,8.87759812,9.84976688,10.75712437,11.59967063,12.37740563,13.09032937,13.73844188,14.32174313,14.84023312,15.29391188,15.68277937,16.00683562,16.26608063,16.46051438,16.59013687,16.65494813,16.65494813,16.59013687,16.46051438,16.26608063,16.00683562,15.68277938,15.29391188,14.84023312,14.32174313,13.73844187,13.09032938,12.37740562,11.59967063,10.75712437,9.84976687,8.87759813,7.84061812,6.73882688,5.57222437,4.34081063]
|
||||
p_desired = [
|
||||
9.98,
|
||||
9.98376125,
|
||||
9.99479,
|
||||
10.01270375,
|
||||
10.03712,
|
||||
10.06765625,
|
||||
10.10393,
|
||||
10.14555875,
|
||||
10.19216,
|
||||
10.24335125,
|
||||
10.29875,
|
||||
10.35797375,
|
||||
10.42064,
|
||||
10.48636625,
|
||||
10.55477,
|
||||
10.62546875,
|
||||
10.69808,
|
||||
10.77222125,
|
||||
10.84751,
|
||||
10.92356375,
|
||||
11.0,
|
||||
11.07643625,
|
||||
11.15249,
|
||||
11.22777875,
|
||||
11.30192,
|
||||
11.37453125,
|
||||
11.44523,
|
||||
11.51363375,
|
||||
11.57936,
|
||||
11.64202625,
|
||||
11.70125,
|
||||
11.75664875,
|
||||
11.80784,
|
||||
11.85444125,
|
||||
11.89607,
|
||||
11.93234375,
|
||||
11.96288,
|
||||
11.98729625,
|
||||
12.00521,
|
||||
12.01623875,
|
||||
12.02,
|
||||
]
|
||||
v_desired = [
|
||||
0.0,
|
||||
1.50156441,
|
||||
2.35715667,
|
||||
2.90783907,
|
||||
3.29035796,
|
||||
3.57019636,
|
||||
3.78263174,
|
||||
3.9483388,
|
||||
4.08022441,
|
||||
4.18675043,
|
||||
4.27368333,
|
||||
4.34507577,
|
||||
4.40384627,
|
||||
4.45213618,
|
||||
4.49153736,
|
||||
4.52324148,
|
||||
4.54814006,
|
||||
4.5668924,
|
||||
4.57997194,
|
||||
4.58769736,
|
||||
4.59025246,
|
||||
4.58769736,
|
||||
4.57997194,
|
||||
4.5668924,
|
||||
4.54814006,
|
||||
4.52324148,
|
||||
4.49153736,
|
||||
4.45213618,
|
||||
4.40384627,
|
||||
4.34507577,
|
||||
4.27368333,
|
||||
4.18675043,
|
||||
4.08022441,
|
||||
3.9483388,
|
||||
3.78263174,
|
||||
3.57019636,
|
||||
3.29035796,
|
||||
2.90783907,
|
||||
2.35715667,
|
||||
1.50156441,
|
||||
0.0,
|
||||
]
|
||||
dt_desired = [
|
||||
0.0,
|
||||
4.34081063,
|
||||
5.57222438,
|
||||
6.73882688,
|
||||
7.84061813,
|
||||
8.87759812,
|
||||
9.84976688,
|
||||
10.75712437,
|
||||
11.59967063,
|
||||
12.37740563,
|
||||
13.09032937,
|
||||
13.73844188,
|
||||
14.32174313,
|
||||
14.84023312,
|
||||
15.29391188,
|
||||
15.68277937,
|
||||
16.00683562,
|
||||
16.26608063,
|
||||
16.46051438,
|
||||
16.59013687,
|
||||
16.65494813,
|
||||
16.65494813,
|
||||
16.59013687,
|
||||
16.46051438,
|
||||
16.26608063,
|
||||
16.00683562,
|
||||
15.68277938,
|
||||
15.29391188,
|
||||
14.84023312,
|
||||
14.32174313,
|
||||
13.73844187,
|
||||
13.09032938,
|
||||
12.37740562,
|
||||
11.59967063,
|
||||
10.75712437,
|
||||
9.84976687,
|
||||
8.87759813,
|
||||
7.84061812,
|
||||
6.73882688,
|
||||
5.57222437,
|
||||
4.34081063,
|
||||
]
|
||||
|
||||
np.testing.assert_allclose(p, p_desired, rtol, atol)
|
||||
np.testing.assert_allclose(v, v_desired, rtol, atol)
|
||||
np.testing.assert_allclose(dt, dt_desired, rtol, atol)
|
||||
|
||||
assert(utils.SAFETY_FACTOR == 0.025)
|
||||
assert(utils.N_SAMPLES == 41)
|
||||
assert(utils.DEGREE_SPLINE == 3)
|
||||
assert(utils.TIME_COMPENSATE_SPLINE == 0.0062)
|
||||
assert(utils.POSITION_COMPONSATION == 0.02)
|
||||
assert utils.SAFETY_FACTOR == 0.025
|
||||
assert utils.N_SAMPLES == 41
|
||||
assert utils.DEGREE_SPLINE == 3
|
||||
assert utils.TIME_COMPENSATE_SPLINE == 0.0062
|
||||
assert utils.POSITION_COMPONSATION == 0.02
|
||||
|
||||
Reference in New Issue
Block a user