diff --git a/debye_bec/devices/mo1_bragg.py b/debye_bec/devices/mo1_bragg.py index 35744d6..d089723 100644 --- a/debye_bec/devices/mo1_bragg.py +++ b/debye_bec/devices/mo1_bragg.py @@ -26,14 +26,21 @@ from ophyd_devices.utils import bec_scaninfo_mixin, bec_utils logger = bec_logger.logger -# TODO check this with the PVs class ScanControlScanStatus(int, enum.Enum): """Enum class for the scan status of the Bragg positioner""" - WAITING_FOR_PARAMETER = 0 - PARAMETER_VALIDATED = 1 - RUNNING = 2 - FINISHED = 3 + PARAMETER_WRONG = 0 + VALIDATION_PENDING = 1 + READY = 2 + RUNNING = 3 + +class ScanControlLoadMessage(int, enum.Enum): + """ Validation of loading the scan parameters """ + PENDING = 0 + STARTED = 1 + SUCCESS = 2 + #TODO add here specific errors above 3-14 + INVALID_SCAN_MODE = 15 class Mo1BraggError(Exception): @@ -173,7 +180,7 @@ class Mo1BraggScanControl(Device): scan_duration = Cpt( EpicsSignalWithRBV, suffix="scan_duration", kind="normal", auto_monitor=True ) - scan_load = Cpt(EpicsSignal, suffix="scan_load", kind="normal") + scan_load = Cpt(EpicsSignal, suffix="scan_load.PROC", kind="normal", put_complete=True) scan_msg = Cpt(EpicsSignalRO, suffix="scan_msg_ENUM_RBV", kind="normal", auto_monitor=True) scan_start_infinite = Cpt(EpicsSignal, suffix="scan_start_infinite", kind="normal") scan_start_timer = Cpt(EpicsSignal, suffix="scan_start_timer", kind="normal") @@ -185,9 +192,12 @@ class Mo1BraggScanControl(Device): EpicsSignalRO, suffix="scan_time_left_RBV", kind="normal", auto_monitor=True ) scan_done = Cpt(EpicsSignalRO, suffix="scan_done_RBV", kind="normal", auto_monitor=True) - scan_val_reset = Cpt(EpicsSignal, suffix="scan_val_reset", kind="config") - + scan_val_reset = Cpt(EpicsSignal, suffix="scan_val_reset.PROC", kind="config", put_complete=True) + scan_progress = Cpt(EpicsSignalRO, suffix="scan_progress_RBV", kind="normal", auto_monitor=True) + scan_spectra_done = Cpt(EpicsSignalRO, suffix="scan_n_osc_RBV", kind="normal", auto_monitor=True) + scan_spectra_left = Cpt(EpicsSignalRO, suffix="scan_n_osc_left_RBV", kind="normal", auto_monitor=True) +#TODO Review kind for all PVs class Mo1Bragg(Device, PositionerBase): """Class for the Mo1 Bragg positioner of the Debye beamline""" @@ -229,7 +239,7 @@ class Mo1Bragg(Device, PositionerBase): ) # Execute motion - move_abs = Cpt(EpicsSignal, suffix="move_abs", kind="config") + move_abs = Cpt(EpicsSignal, suffix="move_abs.PROC", kind="config", put_complete=True) move_stop = Cpt(EpicsSignal, suffix="move_stop", kind="config") motor_is_moving = Cpt( EpicsSignalRO, suffix="move_abs_done_RBV", kind="normal", auto_monitor=True @@ -237,6 +247,7 @@ class Mo1Bragg(Device, PositionerBase): SUB_READBACK = "readback" _default_sub = SUB_READBACK + SUB_PROGRESS = 'progress' def __init__( self, prefix="", *, name: str, kind: Kind = None, device_manager=None, parent=None, **kwargs @@ -254,6 +265,7 @@ class Mo1Bragg(Device, PositionerBase): self.scan_duration = None self.start = None self.end = None + self.timeout_for_pvwait = 2.5 if device_manager: self.device_manager = device_manager @@ -262,6 +274,15 @@ class Mo1Bragg(Device, PositionerBase): self.connector = self.device_manager.connector self._update_scaninfo() + self._init() + + def _init(self): + self.scan_control.scan_progress.subscribe(self._progress_update, run=False) + + def _progress_update(self, value, **kwargs): + max_value=100 + logger.info(f"Progress at {value}") + self._run_subs(sub_type=self.SUB_PROGRESS, value=value, max_value = max_value, done=bool(max_value == value)) def _update_scaninfo(self) -> None: """Update scaninfo from BecScaninfoMixing @@ -316,6 +337,8 @@ class Mo1Bragg(Device, PositionerBase): def stop(self, *, success=False) -> None: """Stop any motion on the positioner""" # Stop the motion + #TODO decide appropriate stop procedure + self.stop_scan() self.move_stop.put(1) # Move thread is stopped too self._stopped = True @@ -338,6 +361,7 @@ class Mo1Bragg(Device, PositionerBase): target_pos (float) : target position for the motion status (DeviceStatus) : status object to set the status of the motion """ + #TODO if called in sequence, the move resolves directly. success = True exception = None try: @@ -346,7 +370,7 @@ class Mo1Bragg(Device, PositionerBase): # Start motion self.move_abs.put(1) # Sleep needed due to updated frequency to soft IOC - time.sleep(0.3) + time.sleep(0.5) while self.motor_is_moving.get() == 0: val = read_cpt.get() self._run_subs(sub_type=self.SUB_READBACK, value=val) @@ -355,12 +379,14 @@ class Mo1Bragg(Device, PositionerBase): success = False break time.sleep(update_frequency) + # 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._finished(success=success) exception = exc - status.set_exception(exc=exception) + #status.set_exception(exc=exc) finally: if exception: status.set_exception(exc=exception) @@ -478,24 +504,12 @@ class Mo1Bragg(Device, PositionerBase): mode (Literal["simple", "advanced"]): Pick a mode for the oscillation motion, either simple or advanced (needs to be configured) scan_duration (float): Duration of the scan """ - timeout = kwargs.get("timeout", 1.5) # TODO check if maybe we want an extra argument for infinite or finite motion self.set_xas_settings(low=low, high=high, scan_time=scan_time) self.set_xrd_settings(False) self.set_scan_control_settings(mode=mode, scan_duration=scan_duration) self.scan_control.scan_load.put(1) - # TODO The wait/sleep here could be move to pre_scan actions to avoid potentially slow signals - time.sleep(0.2) - if not self.wait_for_signals( - signal_conditions=[ - (self.scan_control.scan_status.get, ScanControlScanStatus.PARAMETER_VALIDATED) - ], - timeout=timeout, - check_stopped=True, - ): - raise TimeoutError( - f"Scan parameter validation run into timeout after {timeout} with {self.scan_control.scan_status.get()}" - ) + def kickoff(self): """Kickoff the device, called from BEC.""" @@ -504,7 +518,6 @@ class Mo1Bragg(Device, PositionerBase): # If the scan_duration is less than 0.1s, the scan will be started infinite # This logic should move in future to the controller, but for now it is implemented here # It is necessary since the current NIDAQ implementation relies on it - timeout = 3 scan_duration = self.scan_control.scan_duration.get() start_func = ( self.scan_control.scan_start_infinite.put @@ -512,11 +525,9 @@ class Mo1Bragg(Device, PositionerBase): else self.scan_control.scan_start_timer.put ) start_func(1) - # TODO check if sleep is needed - time.sleep(0.2) status = self.wait_with_status( - signal_conditions=[(self.scan_control.scan_done.get, ScanControlScanStatus.RUNNING)], - timeout=timeout, + signal_conditions=[(self.scan_control.scan_status.get, ScanControlScanStatus.RUNNING)], + timeout=self.timeout_for_pvwait, check_stopped=True, ) return status @@ -551,16 +562,17 @@ class Mo1Bragg(Device, PositionerBase): """Actions to be executed when the device is staged.""" if not self.scaninfo.scan_type == "xas": return - # Could become redudant if we use this in unstage - self.scan_val_reset.put(1) + # Assume that scan_val_reset was usee in unstage, if required, do it again but then we have to introduce a sleep ~1 + if self.scan_control.scan_msg.get() != ScanControlLoadMessage.PENDING: + self.scan_control.scan_val_reset.put(1)#.set() + time.sleep(1) self._get_scaninfo_parameters() - # Brief sleep to allow for PV to update (FYI, reliable set makes this redundant) - time.sleep(0.2) + # Wait for check to go to Pending if not self.wait_for_signals( signal_conditions=[ - (self.scan_control.scan_status.get, ScanControlScanStatus.WAITING_FOR_PARAMETER) + (self.scan_control.scan_msg.get, ScanControlLoadMessage.PENDING) ], - timeout=1.5, + timeout=self.timeout_for_pvwait, check_stopped=True, ): raise TimeoutError( @@ -574,6 +586,17 @@ class Mo1Bragg(Device, PositionerBase): mode=ScanControlMode.SIMPLE, scan_duration=self.scan_duration, ) + #Wait for params to be checked from controller + if not self.wait_for_signals( + signal_conditions=[ + (self.scan_control.scan_msg.get, ScanControlLoadMessage.SUCCESS) + ], + timeout=self.timeout_for_pvwait, + check_stopped=True, + ): + raise TimeoutError( + f"Scan parameter validation run into timeout after {self.timeout_for_pvwait} with {self.scan_control.scan_status.get()}" + ) def complete(self) -> None: """Complete the acquisition, called from BEC. @@ -597,7 +620,7 @@ class Mo1Bragg(Device, PositionerBase): """ # Create DeviceStatus object with hook to finishing the scan. status = self.wait_with_status( - signal_conditions=[(self.scan_control.scan_done.get, ScanControlScanStatus.FINISHED)], + signal_conditions=[(self.scan_control.scan_done.get, 1)], timeout=None, check_stopped=True, ) @@ -605,8 +628,9 @@ class Mo1Bragg(Device, PositionerBase): def stop_scan(self) -> None: """Stop the scan manually.""" - # TODO Check this signal self.scan_control.scan_stop.put(1) + #TODO Check if a single variable for stop scan and stop motion is sufficient. + self._stopped = True def unstage(self) -> list[object]: """ @@ -625,7 +649,7 @@ class Mo1Bragg(Device, PositionerBase): def on_unstage(self) -> None: """Actions to be executed when the device is unstaged.""" # Reset setting of paramters after scan to be ready and clean for the next scan - self.scan_val_reset.put(1) + self.scan_control.scan_val_reset.put(1) def check_scan_id(self) -> None: """Checks if scan_id has changed and set stopped flagged to True if it has.""" diff --git a/debye_bec/scans/mono_bragg_scans.py b/debye_bec/scans/mono_bragg_scans.py index 83e76e2..2a73ee4 100644 --- a/debye_bec/scans/mono_bragg_scans.py +++ b/debye_bec/scans/mono_bragg_scans.py @@ -18,6 +18,7 @@ class MonoBraggOscillationScan(AsyncFlyScanBase): scan_name = "energy_oscillation_scan" scan_type = "xas" + scan_report_hint = "device_progress" required_kwargs = [] use_scan_progress_report = False pre_move = False @@ -66,8 +67,9 @@ class MonoBraggOscillationScan(AsyncFlyScanBase): """ Return the instructions for the scan report. """ + yield from self.stubs.scan_report_instruction({"device_progress" : [self.motor]}) # TODO implement which instructions the scan should listen to for the table printout if wanted. - yield None + # yield None def scan_core(self): """Run the scan core.