diff --git a/pyproject.toml b/pyproject.toml index c7f43ddc..2f97a85e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,7 +26,7 @@ dependencies = [ "psi-pshell==2.1.0", "bec_lib>=3.130.3", "bec-ipython-client>=3.130.3", - "aarescan-client==1.0.0rc5", + "aarescan-client>=1.0.0rc14", "aarelcinfer-client==0.1.1a8", "matplotlib>=3.10.3", "httpx>=0.28.1", diff --git a/src/aare/daq/daq.py b/src/aare/daq/daq.py index 4f301e4e..f41cadad 100644 --- a/src/aare/daq/daq.py +++ b/src/aare/daq/daq.py @@ -956,7 +956,7 @@ class AareDAQ: sample_id = getattr(sample, "db_id", None) logger.exception( - "Operation failed", + f"Operation {operation.value} failed with error {error}", extra={ "operation": operation.value, "event_type": str(event_type), @@ -965,6 +965,8 @@ class AareDAQ: "additional_comment": additional_comment, }, ) + logger.warning("cleaning up detector state after failed operation...") + self._jfjoch.cancel(wait_for_done=True) if sample is None or sample_id is None: logger.error(f"Error in {operation.value}: {error}") @@ -1236,6 +1238,15 @@ class AareDAQ: success=False, payload=payload, error=e, comment=additional_comment ) + def _validate_grid_params(self, request: RasterGridRequest): + self._devs.aerotech.validate_grid_scan( + grid_elem_size_y_um=request.grid_size_mm.y * 1000, + grid_elem_size_x_um=request.grid_size_mm.x * 1000, + grid_elem_count_x=request.n_x, + grid_elem_count_y=request.n_y, + time_sec=request.exp_time_s, + ) + def _execute_raster_sequence( self, grid_request: RasterGridRequest, auto_center: bool = False ) -> CompletedRasterGrid | None: @@ -1377,6 +1388,27 @@ class AareDAQ: ) return None + def _validate_rotation_params(self, request: RotationScanRequest): + total_time = request.exp_time_s * request.steps + logger.debug("Validating rotation parameters with AareScan") + if request.screening: + if request.wedge_omega_deg is None: + raise ValueError("Must supply a wedge angle for screening requests") + self._devs.aerotech.validate_screening_scan( + rotation_deg=request.incr_omega_deg, + wedge_deg=request.wedge_omega_deg, + time_sec=total_time, + steps=request.steps, + run_async=True, + ) + else: + self._devs.aerotech.rotation_scan( + rotation_deg=request.steps * request.incr_omega_deg, + time_sec=total_time, + start_pos_deg=request.start_omega_deg, + run_async=True, + ) + def _execute_rotation_sequence( self, rotation_request: RotationScanRequest ) -> CompletedRotationScan | None: @@ -1394,6 +1426,7 @@ class AareDAQ: logger.warning(f"Exposure shorter than default of {min_exp_time} s! Adjusting.") rotation_request.exp_time_s = min_exp_time try: + self._validate_rotation_params(rotation_request) return self._create_rotation_service().run(rotation_request) except JFJochCommunicationError as e: logger.error(f"Rotation sequence failed due to JFJoch Communication error: {e}") @@ -1990,74 +2023,6 @@ class AareDAQ: self._set_state(BeamlineStateEnum.SampleAlignment) raise - def _rotation(self, request: RotationScanRequest) -> CompletedRotationScan: - omega_start = self.omega - status = self.status - - if request.exp_time_s < 0.004: - logger.warning("Exposure time too short for PXII rotation scan") - request.exp_time_s = 0.004 - total_time = request.exp_time_s * request.steps - if self.sample is not None and self.sample.db_id is not None: - self._aare.create_rotation_run(self.sample, request, status) - - if not self._cfg.simulated_detector: - try: - self._jfjoch.wait_till_running(timeout=60.0) - except Exception as e: - self._raise_if_critical_jfjoch_detector_error(e, command="wait_till_running") - raise - try: - if request.screening: - self._devs.aerotech.screening_scan( - rotation_deg=request.steps * request.incr_omega_deg, - wedge_deg=request.wedge_omega_deg, - time_sec=total_time, - steps=request.steps, - run_async=True, - ) - else: - self._devs.aerotech.rotation_scan( - rotation_deg=request.steps * request.incr_omega_deg, - time_sec=total_time, - start_pos_deg=request.start_omega_deg, - run_async=True, - ) - - # Is this for helical scans...? do we do smargon scans? - if request.start is not None and request.end is not None: - smargon_time_step = request.exp_time_s / float(request.steps) - pos_step = (request.end.sh_mm - request.start.sh_mm) * ( - 1.0 / float(request.steps) - ) - - for i in range(request.steps): - self._devs.smargon.target = SmargonCoordinate( - sh_mm=request.start.sh_mm + pos_step * i - ) - time.sleep(smargon_time_step) - - self._devs.aerotech.wait_till_done(timeout=int(round(total_time + 60, 0))) - self._devs.aerotech_omega = omega_start - - if self._cfg.simulated_detector: - logger.warning("Detector in simulation mode, returning fake zero rotation result.") - return self._build_fake_rotation_result(request) - else: - try: - scan_result = self._jfjoch.wait_till_done(60) - except Exception as e: - self._raise_if_critical_jfjoch_detector_error(e, command="wait_till_done") - raise - return CompletedRotationScan(request=copy.deepcopy(request), result=scan_result) - - except JFJochCommunicationError as e: - logger.error(f"Exception during rotation scan related to JFJoch: {e}") - raise - except Exception as e: - logger.error(f"Exception during rotation scan: {e}") - raise - def measure_rotation(self, request: RotationScanRequest) -> CompletedRotationScan: """ Execute a rotation scan. diff --git a/src/aare/daq/operations/raster/service.py b/src/aare/daq/operations/raster/service.py index 6715e7f4..c0d26b69 100644 --- a/src/aare/daq/operations/raster/service.py +++ b/src/aare/daq/operations/raster/service.py @@ -421,19 +421,12 @@ class RasterService: if self.ctx.sample is not None and self.ctx.sample.db_id is not None: self.ctx.deps.aare.create_gridscan_run(self.ctx.sample, request, status) - if not self.ctx.deps.cfg.simulated_detector: - self.ctx.deps.jfjoch.wait_till_running(timeout=60.0) - else: - self.logger.info("Simulated detector mode enabled; faking jfjoch intilalisation.") + self.ctx.deps.jfjoch.wait_till_running(timeout=60.0) self.logger.debug( f"Starting grid scan with {request.n_x}x{request.n_y} points, exp time {request.exp_time_s}s" ) - if not self.ctx.deps.cfg.simulated_detector: - # The detector move (sa2dc) is non-blocking; ensure it has reached - # the requested distance before triggering the scan, so data is - # collected at the dtz already reported to JFJoch/DB. - self.ctx.deps.devs.set_dtz(request.dtz, wait=True) + self.ctx.deps.devs.set_dtz(request.dtz, wait=True) self.ctx.deps.devs.aerotech.grid_scan( grid_elem_size_y_um=request.grid_size_mm.y * 1000, grid_elem_size_x_um=request.grid_size_mm.x * 1000, @@ -747,12 +740,9 @@ class RasterService: grid.omega_deg = geom.omega_deg status = self.ctx.status - if not self.ctx.deps.cfg.simulated_detector: - self.logger.info("initialise detector") - self.ctx.deps.jfjoch.measure_raster(grid, status) - self.logger.info("detector initialised") - else: - self.logger.info("Simulated detector mode enabled; using fake raster result.") + self.logger.info("initialise detector") + self.ctx.deps.jfjoch.measure_raster(grid, status) + self.logger.info("detector initialised") if self.ctx.services.state is None: raise RuntimeError("RasterService requires services.state") diff --git a/src/aare/devices/aerotech.py b/src/aare/devices/aerotech.py index 3eac3bec..45da1694 100644 --- a/src/aare/devices/aerotech.py +++ b/src/aare/devices/aerotech.py @@ -190,10 +190,7 @@ class AerotechController: start_pos_deg=start_pos_deg, run_async=run_async, ) - logger.debug(f"Payload sent to AareScan for rotation: {payload}") - if self._simulated: - return payload - + logger.debug(f"Payload sent to AareScan for rotation execution: {payload}") try: return self._api.rotation_scan_post(payload) except Exception as e: @@ -204,6 +201,26 @@ class AerotechController: operation="POST", ) from e + def validate_rotation_scan( + self, rotation_deg: float, time_sec: float, start_pos_deg: float, run_async: bool = False + ): + payload = RotationRequest( + rotation_deg=rotation_deg, + time_sec=time_sec, + start_pos_deg=start_pos_deg, + run_async=run_async, + ) + logger.debug(f"Payload sent to AareScan for validating rotation: {payload}") + try: + return self._api.validate_rotation_scan_post(payload) + except Exception as e: + raise AerotechCommunicationError( + f"Aerotech rotation scan failed validation: {e}", + endpoint="rotation_scan_post", + base_url=self._base, + operation="POST", + ) from e + def grid_scan( self, grid_elem_count_y: int, @@ -221,14 +238,44 @@ class AerotechController: time_sec=time_sec, run_async=run_async, ) - logger.debug(f"Payload sent to AareScan for grid_scan: {payload}") + logger.debug(f"Payload sent to AareScan for grid_scan execution: {payload}") if self._simulated: return payload try: return self._api.grid_scan_post(payload) except Exception as e: raise AerotechCommunicationError( - "Aerotech grid scan failed", + f"Aerotech grid scan failed: {e}", + endpoint="grid_scan_post", + base_url=self._base, + operation="POST", + ) from e + + def validate_grid_scan( + self, + grid_elem_count_y: int, + grid_elem_size_y_um: float, + time_sec: float, + grid_elem_size_x_um: float, + grid_elem_count_x: int, + run_async: bool | None = False, + ): + payload = GridRequest( + grid_elem_count_x=grid_elem_count_x, + grid_elem_count_y=grid_elem_count_y, + grid_elem_size_x_um=grid_elem_size_x_um, + grid_elem_size_y_um=grid_elem_size_y_um, + time_sec=time_sec, + run_async=run_async, + ) + logger.debug(f"Payload sent to AareScan for grid_scan validation: {payload}") + if self._simulated: + return payload + try: + return self._api.validate_grid_scan_post(payload) + except Exception as e: + raise AerotechCommunicationError( + f"Aerotech grid scan validation failed: {e}", endpoint="grid_scan_post", base_url=self._base, operation="POST", @@ -252,7 +299,7 @@ class AerotechController: if self._simulated: return payload try: - logger.info("sending screening scan request to aerotech") + logger.info(f"sending screening scan request to aerotech with payload {payload}") return self._api.screening_post(payload) except Exception as e: raise AerotechCommunicationError( @@ -262,6 +309,34 @@ class AerotechController: operation="POST", ) from e + def validate_screening_scan( + self, + rotation_deg: float, + wedge_deg: float, + time_sec: float, + steps: int, + run_async: bool = False, + ): + payload = ScreenRequest( + rotation_deg=rotation_deg, + wedge_deg=wedge_deg, + time_sec=time_sec, + steps=steps, + run_async=run_async, + ) + try: + logger.info( + f"sending screening scan request to aarescan for validation with payload {payload}" + ) + return self._api.screening_post(payload) + except Exception as e: + raise AerotechCommunicationError( + f"Aerotech screening validation failed: {e}", + endpoint="screening_post", + base_url=self._base, + operation="POST", + ) from e + if __name__ == "__main__": beamline = mx_beamline() diff --git a/src/aare/devices/jfjoch.py b/src/aare/devices/jfjoch.py index 163a04c8..2d6d49fa 100644 --- a/src/aare/devices/jfjoch.py +++ b/src/aare/devices/jfjoch.py @@ -114,7 +114,7 @@ class JFJochWrapper: ) from e @needs_init - def cancel(self, on_init: bool = False): + def cancel(self, on_init: bool = False, wait_for_done: bool = False): try: self._api.cancel_post() except Exception as e: @@ -124,6 +124,8 @@ class JFJochWrapper: operation="POST", endpoint="cancel_post", ) from e + if wait_for_done: + self._api.wait_till_done_post(1) @needs_init def is_idle(self) -> bool: diff --git a/src/aare/devices/tell_client.py b/src/aare/devices/tell_client.py index 102f981e..baccfd14 100644 --- a/src/aare/devices/tell_client.py +++ b/src/aare/devices/tell_client.py @@ -425,7 +425,6 @@ class TellClient: def get_mounted_sample(self) -> SampleDewarAddress | None: ret = self.get_setting("mounted_sample_position").strip() - logger.debug(f"Get TELL setting 'mounted_sample_position' returned: {ret}") if not ret: return None