DAQ: organising operation handlers, state changes

This commit is contained in:
2026-04-22 16:08:34 +02:00
parent 405806518c
commit c398afa6b6
+64 -28
View File
@@ -97,6 +97,9 @@ class AareDAQ:
f"ML bundle metadata for {context}: target_point={target_point}, focus={focus}"
)
#--------------------------------------------
# Operation Handlers
#--------------------------------------------
#TODO make sure this is implemented currectly
def _handle_operation_error(
self,
@@ -107,7 +110,7 @@ class AareDAQ:
additional_comment: Optional[str] = None,
) -> None:
"""
Centralized error handling for all operations.
Centralized databse maessage error handling for all operations.
Args:
operation: Name of the operation that failed (e.g., "mount", "alc", "raster")
@@ -131,29 +134,40 @@ class AareDAQ:
logger.error(f"Failed to report {operation.value} error to database: {db_error}")
#todo make sure these functions are correctly implemented!
#Operation handlers should handle database communication and beamline state changes,
#where possible/between operations. For example in raster, we may use XtalSnapshot to take a screenshot, then
#return to Datacollection.
#Busy stats are handled by the public function call i.e. sample() or by automation i.e. measure()
def _execute_mount_and_prepare(self, sample: SampleShortInfo) -> bool:
"""
Execute mounting and take screenshot.
Operation handler for executing mounting and take screenshot.
Returns:
True if successful, False otherwise
"""
try:
if sample.db_id is not None:
self.__set_state(BeamlineStateEnum.RobotSampleExchange)
if sample is not None and sample.db_id is not None:
self.__aare.send_sample_event(sample, SampleEventType.MOUNTING)
self.__mount(sample)
if sample.db_id is not None:
if sample is not None and sample.db_id is not None:
self.__aare.send_sample_event(sample, SampleEventType.MOUNTED)
self.save_screenshot_db(sample.db_id, f"{sample.db_id}_mounted")
self.__set_state(BeamlineStateEnum.SampleAlignment)
return True
except Exception as e:
logger.error(f"Mount failed: {e}")
self._handle_operation_error(
operation=DAQOperation.MOUNT,
sample=sample,
error=e,
event_type=SampleEventType.MOUNTFAILED
)
try:
self.__set_state(BeamlineStateEnum.SampleAlignment)
except Exception:
logger.exception("Failed to set state to SampleAlignment")
finally:
self._handle_operation_error(
operation=DAQOperation.MOUNT,
sample=sample,
error=e,
event_type=SampleEventType.MOUNTFAILED
)
return False
def _execute_loop_centering(self, sample: SampleShortInfo) -> bool:
@@ -169,6 +183,9 @@ class AareDAQ:
logger.error("Loop centering failed: no sample provided")
return False
self.__set_state(BeamlineStateEnum.SampleAlignment)
self.__devs.lamp_light = 2.5
try:
self.__aare.send_sample_event(sample, SampleEventType.CENTERING)
result = self.__loop_center_sequence(sample.db_id)
@@ -213,12 +230,23 @@ class AareDAQ:
CompletedRasterGrid result or None if failed
"""
try:
self.__setup_datacollection(request=grid_request)
if auto_center:
result = self.__auto_center(grid_request)
else:
raster_result = self.__raster(grid_request)
result = CompletedRasterGrid(r=[raster_result])
return result
except JFJochCommunicationError as e:
logger.error(f"Raster sequence failed due to JFJoch Communication error: {e}")
self._handle_operation_error(
operation=DAQOperation.RASTER,
sample=self.sample,
error=e,
event_type=SampleEventType.RASTERINGFAILED,
additional_comment=f"JFJoch communication error: {e}"
)
return None
except Exception as e:
logger.error(f"Raster sequence failed: {e}")
self._handle_operation_error(
@@ -240,6 +268,7 @@ class AareDAQ:
CompletedRotationScan result or None if failed
"""
try:
self.__setup_datacollection(request=rotation_request)
if self.sample is not None and self.sample.db_id is not None:
self.__aare.send_sample_event(self.sample, SampleEventType.COLLECTING)
result = self.__rotation(rotation_request)
@@ -555,7 +584,7 @@ class AareDAQ:
def save_abr_meas_pos(self):
self.__cfg.set_busy(BeamlineStateEnum.SampleAlignment)
try:
self.__cfg.abr_meas_pos = self.__devs.aerotech_pos
self.__cfg.abr_meas_pos = AerotechCoordinate(at_mm=self.__devs.aerotech_pos.at_mm, omega_deg=0.0)
self.__cfg.state_busy = False
except Exception:
self.__cfg.state_busy = False
@@ -590,6 +619,7 @@ class AareDAQ:
def park_and_dry(self):
self.__cfg.try_set_busy(timeout=360)
try:
self.__devs.tell.check_enable_motion()
self.__devs.tell.wait_not_busy()
self.__devs.tell.set_in_mount_position(True)
self.__devs.tell.unmount(wait=True, timeout=60.0)
@@ -632,19 +662,23 @@ class AareDAQ:
self.__devs.aerotech_pos = ABR_POS_MOUNT
#collimator should be down!!!
self.__magnet_position_sensor_check(timeout=360.0)
self.__devs.tell.check_enable_motion()
self.__devs.tell.wait_not_busy()
self.__devs.tell.set_in_mount_position(True)
if target is None:
logger.debug(f"Unmounting sample: {self.__cfg.current_sample} (address: {self.__cfg.current_sample.tell_address()})")
try:
self.__devs.tell.unmount(wait=True, timeout=60.0)
self.__aare.send_sample_event(self.__cfg.current_sample, SampleEventType.UNMOUNTED)
self.__cfg.current_sample = None
if self.__cfg.current_sample is not None and self.__cfg.current_sample.db_id is not None:
self.__aare.send_sample_event(self.__cfg.current_sample, SampleEventType.UNMOUNTED)
self.__cfg.current_sample = target
except Exception as e:
raise UnmountingFailed(f"Failed to unmount: {e}")
else:
try:
#TODO: how to differentiate between mounting and unmounting fails
# during a typucal mount call as this is handled by Tell?
logger.debug(f"Mounting sample: {target} (address: {target.tell_address()})")
value = self.__devs.tell.mount(address=target.tell_address(), force=True, auto_unmount=True, read_dm=False,
wait=True, timeout=360.0)
self.__mount_failure_handler(value)
@@ -662,6 +696,7 @@ class AareDAQ:
self.__cfg.try_set_busy(timeout=360)
try:
self.__set_state(BeamlineStateEnum.RobotSampleExchange)
self.__devs.tell.check_enable_motion()
self.__devs.tell.wait_not_busy()
self.__devs.tell.set_in_mount_position(True)
self.__devs.tell.unmount(wait=True, timeout=360.0)
@@ -676,19 +711,22 @@ class AareDAQ:
def sample(self, target: SampleShortInfo | None):
self.__cfg.try_set_busy(timeout=360)
try:
workflows.common_2rse(devs=self.__devs, cfg=self.__cfg)
logger.debug(target)
logger.debug(f"Mount target {target}")
if target is None:
logger.debug("Unmounting sample")
type = "Unmount"
else:
logger.debug(f"Mounting sample: {target}")
type = "Mount"
if not self._execute_mount_and_prepare(target):
raise MountingFailed("Failed to mount sample")
raise MountingFailed(f"Failed to {type} sample {target or self.__cfg.current_sample}")
logger.info(f"Sample mounted: {target}")
self.__cfg.state_busy = False
except Exception as e:
workflows.rse2sa(devs=self.__devs, cfg=self.__cfg)
self.__cfg.state_busy = False
logger.debug(f"Failed to mount sample: {e}")
raise
workflows.rse2sa(devs=self.__devs, cfg=self.__cfg)
self.__cfg.state_busy = False
@property
def camera_image(self) -> np.ndarray | None:
@@ -786,6 +824,12 @@ class AareDAQ:
def __setup_datacollection(self, request: RasterGridRequest | RotationScanRequest, screening: bool = False):
if request.omega_deg is not None:
self.__devs.aerotech_omega = request.omega_deg
else:
omega = self.omega
if request.dtz is not None:
logger.info(f'requesting dtz to move to {request.dtz}')
self.__cfg.dtz = request.dtz
@@ -859,9 +903,6 @@ class AareDAQ:
)
def __raster(self, request: RasterGridRequest) -> CompletedRasterGridElem:
self.__devs.aerotech_omega = request.omega_deg
self.__setup_datacollection(request=request)
status = self.status
logger.info(f"raster status {status}")
logger.info(f'raster grid request: {request}')
@@ -1029,8 +1070,6 @@ class AareDAQ:
self.__devs.aerotech.wait_till_done(timeout=int(round(total_time + 60,0)))
self.__devs.aerotech_omega = omega_start
# self.__aare.sample_collected(self.sample)
if self.__cfg.simulated_detector:
logger.warning("Detector in simulation mode, returning fake zero rotation result.")
result = self._build_fake_rotation_result(request)
@@ -1547,9 +1586,6 @@ class AareDAQ:
trace_all_alc_moves: bool = False
) -> LoopCenteringResult:
self.__set_state(BeamlineStateEnum.SampleAlignment)
self.__devs.lamp_light = 2.5
found_classes_count: dict[int, int] = {0: 0, 1: 0, 2: 0, 3: 0, 4: 0, 5: 0}
try: