From c398afa6b6865fb674a3d308ba2443151ffd45e7 Mon Sep 17 00:00:00 2001 From: appleb_m Date: Wed, 22 Apr 2026 16:08:34 +0200 Subject: [PATCH] DAQ: organising operation handlers, state changes --- src/aare/daq/daq.py | 92 +++++++++++++++++++++++++++++++-------------- 1 file changed, 64 insertions(+), 28 deletions(-) diff --git a/src/aare/daq/daq.py b/src/aare/daq/daq.py index eefd5b63..ad158267 100644 --- a/src/aare/daq/daq.py +++ b/src/aare/daq/daq.py @@ -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: