diff --git a/pyproject.toml b/pyproject.toml index 124fa5cb..dbf57401 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,7 +20,6 @@ dependencies = [ "requests", "pyepics~=3.5", "redis", - "python-redis-lock", "fastapi", "uvicorn", "aaredb>=0.83.1", diff --git a/scripts/free_busy_state.py b/scripts/free_busy_state.py deleted file mode 100644 index 27eb9e31..00000000 --- a/scripts/free_busy_state.py +++ /dev/null @@ -1,5 +0,0 @@ -from aaredaq.config import BeamlineConfig -from aaredaqlib.beamline import MXBeamline - -c = BeamlineConfig(MXBeamline.X06DA) -c.state_busy = False diff --git a/src/aare/daq/config.py b/src/aare/daq/config.py index 1d0bfdda..6c0bb1ca 100644 --- a/src/aare/daq/config.py +++ b/src/aare/daq/config.py @@ -2,16 +2,13 @@ import base64 import io import json import time -import traceback from dataclasses import asdict, is_dataclass from datetime import datetime import numpy as np import redis -import redis_lock from aarecommon.config.beamline import cfg_get from aarecommon.config.logger import setup_logger -from aarecommon.errors.exception_handler import BeamlineBusyException from aarecommon.math.coordinate import AerotechCoordinate, Coordinate from aarecommon.models.auth import ( BatonHolderInfo, @@ -40,6 +37,7 @@ from aarecommon.models.models import ( ZoomModel, zoom_manager, ) +from redis.lock import Lock as RedisLock from aare.daq.config_model import LocalContactConfigModel @@ -344,7 +342,7 @@ class BeamlineConfig: return SessionsStateEnum.OwnedByElse def try_set_active_session(self, session: int, expiry_sec: int) -> None: - with redis_lock.Lock(self._client, f"{self._bl}:active_session_lock", expire=10): + with RedisLock(self.redis, f"{self._bl}:active_session_lock", expire=10): active = self.active_session if active is None: self._client.set(f"{self._bl}:active_session", session) @@ -356,7 +354,7 @@ class BeamlineConfig: # TODO finish setting this up! def try_extend_active_session(self, session: int, expiry_sec: int) -> None: - with redis_lock.Lock(self._client, f"{self._bl}:active_session_lock", expire=10): + with RedisLock.Lock(self.redis, f"{self._bl}:active_session_lock", expire=10): active = self.active_session if active is None: raise RuntimeError("There is no active session with given id. Try again later.") @@ -378,7 +376,7 @@ class BeamlineConfig: ) def end_active_session(self, session: int) -> None: - with redis_lock.Lock(self._client, f"{self._bl}:active_session_lock", expire=10): + with RedisLock.Lock(self.redis, f"{self._bl}:active_session_lock", expire=10): active = self.active_session if active is None: return @@ -388,9 +386,9 @@ class BeamlineConfig: def force_set_active_session(self, session: int, expiry_sec: int) -> None: # Ensure that there is no active try-set for active session - with redis_lock.Lock(self._client, f"{self._bl}:active_session_lock", expire=10): - self._client.set(f"{self._bl}:active_session", session) - self._client.expire(f"{self._bl}:active_session", expiry_sec) + with RedisLock.Lock(self.redis, f"{self._bl}:active_session_lock", expire=10): + self.redis.set(f"{self._bl}:active_session", session) + self.redis.expire(f"{self._bl}:active_session", expiry_sec) # ========== BATON SYSTEM ========== @@ -463,7 +461,7 @@ class BeamlineConfig: # Can't transfer while beamline is busy # Add automation queue check here when you implement it # return not (self.state_busy or self.automation_queue_running) - return not self.state_busy + raise NotImplementedError("Checking for busy state is not implemented") def execute_baton_transfer( self, @@ -477,7 +475,7 @@ class BeamlineConfig: Atomically transfer the baton to a new holder. Use existing active_session_lock for consistency. """ - with redis_lock.Lock(self._client, f"{self._bl}:active_session_lock", expire=10): + with RedisLock(self._client, f"{self._bl}:active_session_lock", expire=10): self._client.set(f"{self._bl}:active_session", to_session) self._client.expire(f"{self._bl}:active_session", expiry_sec) self.baton_holder = BatonHolderInfo( @@ -532,31 +530,11 @@ class BeamlineConfig: else: self._client.delete(f"{self._bl}:commissioning_mode") - # Beamline state management - # Atomic check if beamline is busy and if not set state to busy - def try_set_busy(self, timeout: int | None = None): - with redis_lock.Lock(self._client, f"{self._bl}:move_state_lock", expire=10): - if self.state_busy: - raise BeamlineBusyException("Beamline is busy") - self.state_busy = True - if timeout is not None: - self._client.expire(f"{self._bl}:busy", timeout) - - def set_busy(self, target: BeamlineStateEnum, timeout: int | None = None): - self.try_set_busy(timeout=timeout) - curr_state = self.state - if curr_state != target: - self.state_busy = False - raise RuntimeError("Beamline is not in a proper state") - def start_moving( self, target: BeamlineStateEnum, timeout: int | None = None ) -> BeamlineStateEnum: - self.try_set_busy(timeout=timeout) curr_state = self.state - if target == curr_state: - self.state_busy = False - else: + if target != curr_state: self.state = BeamlineStateEnum.Moving return curr_state @@ -578,23 +556,6 @@ class BeamlineConfig: def state(self, state: BeamlineStateEnum): self._client.set(f"{self._bl}:state", state.value) - @property - def state_busy(self) -> bool: - return self._client.get(f"{self._bl}:busy") is not None - - @state_busy.setter - def state_busy(self, i: bool): - - logger.debug( - f"Busy flag switched to: {i}, at:\n{''.join(traceback.format_stack(limit=5)[:-2])}" - ) - if i: - self._client.set(f"{self._bl}:busy", "1") - else: - self._client.delete(f"{self._bl}:busy") - - # Other beamline settings - @property def tell_mount_count(self) -> int: return int(self._client.incr(f"{self._bl}:tell_mount_count")) @@ -667,12 +628,12 @@ class BeamlineConfig: @property def settings(self) -> BeamlineSettingsModel: - with redis_lock.Lock(self._client, f"{self._bl}:settings_lock", expire=10): + with RedisLock.Lock(self._client, f"{self._bl}:settings_lock", expire=10): return self._get_settings() @settings.setter def settings(self, data: BeamlineSettingsModel): - with redis_lock.Lock(self._client, f"{self._bl}:settings_lock", expire=10): + with RedisLock.Lock(self._client, f"{self._bl}:settings_lock", expire=10): current = self._get_settings() updated_data = current.model_copy(update=data.model_dump(exclude_unset=True)) self._client.set(f"{self._bl}:settings", updated_data.model_dump_json()) @@ -1270,6 +1231,5 @@ if __name__ == "__main__": cfg = BeamlineConfig(bl=mx_beamline()) # cfg.allow_non_staff_request_from_staff = True - # cfg.state_busy = False # fg.abr_meas_pos = AerotechCoordinate(at_mm=Coordinate(x=0.0,y=0.0,z=0.0)) cfg.dtz_safe_position = 300.0 diff --git a/src/aare/daq/daq.py b/src/aare/daq/daq.py index a9adf94e..f7ff08e1 100644 --- a/src/aare/daq/daq.py +++ b/src/aare/daq/daq.py @@ -368,50 +368,26 @@ class AareDAQ: return self._devs.read_current_state_from_bec() def restart_bec_worker(self) -> dict[str, object]: - self._cfg.try_set_busy(timeout=360) - try: - self._devs.restart_bec_worker(simulated=self._cfg.simulate_bec) - return {"ok": True, "device": "bec", "simulated": self._cfg.simulate_bec} - finally: - self._cfg.state_busy = False + self._devs.restart_bec_worker(simulated=self._cfg.simulate_bec) + return {"ok": True, "device": "bec", "simulated": self._cfg.simulate_bec} def restart_detector(self) -> dict[str, object]: - self._cfg.try_set_busy(timeout=360) - try: - beamline = MXBeamline.SIMULATED if self._cfg.simulated_detector else self._beamline - logger.info(f"Restarting JFJoch wrapper with simulated={self._cfg.simulated_detector}") - self._jfjoch = JFJochWrapper(beamline) - return { - "ok": True, - "device": "detector", - "simulated": bool(self._cfg.simulated_detector), - } - finally: - self._cfg.state_busy = False + beamline = MXBeamline.SIMULATED if self._cfg.simulated_detector else self._beamline + logger.info(f"Restarting JFJoch wrapper with simulated={self._cfg.simulated_detector}") + self._jfjoch = JFJochWrapper(beamline) + return {"ok": True, "device": "detector", "simulated": bool(self._cfg.simulated_detector)} def restart_tell(self) -> dict[str, object]: - self._cfg.try_set_busy(timeout=360) - try: - self._devs.restart_tell(simulated=self._cfg.simulate_tell) - return {"ok": True, "device": "tell", "simulated": self._cfg.simulate_tell} - finally: - self._cfg.state_busy = False + self._devs.restart_tell(simulated=self._cfg.simulate_tell) + return {"ok": True, "device": "tell", "simulated": self._cfg.simulate_tell} def restart_aerotech(self) -> dict[str, object]: - self._cfg.try_set_busy(timeout=360) - try: - self._devs.restart_aerotech(simulated=self._cfg.simulate_aerotech) - return {"ok": True, "device": "aerotech", "simulated": self._cfg.simulate_aerotech} - finally: - self._cfg.state_busy = False + self._devs.restart_aerotech(simulated=self._cfg.simulate_aerotech) + return {"ok": True, "device": "aerotech", "simulated": self._cfg.simulate_aerotech} def restart_smargon(self) -> dict[str, object]: - self._cfg.try_set_busy(timeout=360) - try: - self._devs.restart_smargon(simulated=self._cfg.simulate_smargon) - return {"ok": True, "device": "smargon", "simulated": self._cfg.simulate_smargon} - finally: - self._cfg.state_busy = False + self._devs.restart_smargon(simulated=self._cfg.simulate_smargon) + return {"ok": True, "device": "smargon", "simulated": self._cfg.simulate_smargon} def set_runtime_simulation(self, device: str, enabled: bool) -> dict[str, object]: device = str(device).strip().lower() @@ -1561,10 +1537,6 @@ class AareDAQ: def state(self) -> BeamlineStateEnum: return self._cfg.state - @property - def busy(self) -> bool: - return self._cfg.state_busy - @state.setter def state(self, target: BeamlineStateEnum): if target == BeamlineStateEnum.Moving: @@ -1575,11 +1547,7 @@ class AareDAQ: raise RuntimeError("Cannot explicitly move to busy state") start = time.perf_counter() - self._cfg.try_set_busy(timeout=300) - try: - self._set_state(target) - finally: - self._cfg.state_busy = False + self._set_state(target) end = time.perf_counter() @@ -1675,22 +1643,17 @@ class AareDAQ: if -2000 < val < 2000: try: self._devs.aerotech_omega = val - self._cfg.state_busy = False except Exception: logger.exception("Omega move timed out") - self._cfg.state_busy = False else: - self._cfg.state_busy = False logger.error("Omega has to be between -2000 and 2000 degrees") raise ValueError("Omega has to be between -2000 and 2000 degrees (for now)") @omega.setter def omega(self, val: float): - self._cfg.set_busy(BeamlineStateEnum.SampleAlignment) self._omega(val) def omega_rel(self, val: float): - self._cfg.set_busy(BeamlineStateEnum.SampleAlignment) curr_omega = self._devs.aerotech_omega self._omega(curr_omega + val) @@ -1783,52 +1746,26 @@ class AareDAQ: self._devs.samcam_settings = s def tweak_abr_meas_pos(self, c: AerotechCoordinate): - self._cfg.set_busy(BeamlineStateEnum.SampleAlignment) - try: - new_meas_pos = AerotechCoordinate(at_mm=self._cfg.abr_meas_pos.at_mm + c.at_mm) - self._cfg.abr_meas_pos = new_meas_pos - self._devs.aerotech_pos = new_meas_pos - self._saved_box = None - self._cfg.state_busy = False - except Exception: - self._cfg.state_busy = False - raise + new_meas_pos = AerotechCoordinate(at_mm=self._cfg.abr_meas_pos.at_mm + c.at_mm) + self._cfg.abr_meas_pos = new_meas_pos + self._devs.aerotech_pos = new_meas_pos + self._saved_box = None def save_abr_meas_pos(self): - self._cfg.set_busy(BeamlineStateEnum.SampleAlignment) - try: - self._cfg.abr_meas_pos = AerotechCoordinate(at_mm=self._devs.aerotech_pos.at_mm) - self._devs.bec_worker.save_current_aerotech_position() - self._cfg.state_busy = False - except Exception: - self._cfg.state_busy = False - raise + self._cfg.abr_meas_pos = AerotechCoordinate(at_mm=self._devs.aerotech_pos.at_mm) + self._devs.bec_worker.save_current_aerotech_position() def goto_abr_meas_pos(self): - self._cfg.set_busy(BeamlineStateEnum.SampleAlignment) - try: - self._devs.aerotech_pos = self._cfg.abr_meas_pos - self._cfg.state_busy = False - except Exception: - self._cfg.state_busy = False - raise + self._devs.aerotech_pos = self._cfg.abr_meas_pos def create_sample(self, target: SampleShortInfo): + curr_sample = self._cfg.current_sample - self._cfg.try_set_busy(timeout=360) + if curr_sample is not None and curr_sample.location is not None: + raise RuntimeError("Sample from TELL is loaded") - try: - curr_sample = self._cfg.current_sample - - if curr_sample is not None and curr_sample.location is not None: - raise RuntimeError("Sample from TELL is loaded") - - self._aare.create_manual_sample(target) - self._cfg.current_sample = target - self._cfg.state_busy = False - except Exception: - self._cfg.state_busy = False - raise + self._aare.create_manual_sample(target) + self._cfg.current_sample = target def check_tell_mount_start_conditions(self) -> None: self._devs.tell.validate_mount_start_conditions() @@ -1838,12 +1775,9 @@ class AareDAQ: def park_and_dry(self, park=True): """External API for dry and park only""" - self._cfg.try_set_busy(timeout=360) try: self._execute_dry(park=park, unmount=False) - self._cfg.state_busy = False except Exception as e: - self._cfg.state_busy = False logger.error(f"Failed to park and dry: {e}") raise @@ -1854,27 +1788,20 @@ class AareDAQ: logger.exception("Failed to turn off blower") def initialise_smargon(self): - self._cfg.try_set_busy(timeout=360) try: self._devs.smargon_initialize() - self._cfg.state_busy = False except Exception as e: - self._cfg.state_busy = False logger.error(f"Failed to initialise Smargon: {e}") raise def initialise_detector(self): - self._cfg.try_set_busy(timeout=360) try: self._jfjoch.initialize() - self._cfg.state_busy = False except Exception as e: - self._cfg.state_busy = False logger.error(f"Failed to initialise detector: {e}") raise def recovery_unmount_sample(self) -> None: - self._cfg.try_set_busy(timeout=360) try: self._set_state(BeamlineStateEnum.RobotSampleExchange) self._devs.tell.check_enable_motion() @@ -1883,14 +1810,11 @@ class AareDAQ: self._devs.tell.unmount(wait=True, timeout=360.0) self._cfg.current_sample = None self._set_state(BeamlineStateEnum.SampleAlignment) - self._cfg.state_busy = False except Exception: - self._cfg.state_busy = False raise @sample.setter def sample(self, target: SampleShortInfo | None): - self._cfg.try_set_busy(timeout=360) try: logger.debug(f"Mount target {target}") @@ -1909,10 +1833,8 @@ class AareDAQ: raise MountingFailed(f"Failed to {operation_name.lower()} {sample_name}") logger.info(f"Sample operation completed: {target}") - self._cfg.state_busy = False except Exception as e: - self._cfg.state_busy = False logger.debug(f"Failed to change mounted sample: {e}") raise @@ -1946,11 +1868,9 @@ class AareDAQ: self._cfg.set_busy(BeamlineStateEnum.SampleAlignment) try: best_z = self._auto_focus(settings) - self._cfg.state_busy = False return best_z except Exception as e: logger.error(f"Autofocus failed: {e}") - self._cfg.state_busy = False raise def auto_exposure(self): @@ -2057,18 +1977,15 @@ class AareDAQ: sample_log_context(self.sample), raster_request_log_context(request) ), ) - self._cfg.try_set_busy(timeout=ceil(360)) try: result = self._execute_raster_sequence(request, auto_center=auto_center) if result is None: raise RasterScanException("Raster scan failed") self._set_state(BeamlineStateEnum.SampleAlignment) - self._cfg.state_busy = False return result except Exception: self._set_state(BeamlineStateEnum.SampleAlignment) - self._cfg.state_busy = False raise def _rotation(self, request: RotationScanRequest) -> CompletedRotationScan: @@ -2157,7 +2074,6 @@ class AareDAQ: rotation_request_log_context(request, total_time_s=total_time), ), ) - self._cfg.try_set_busy(timeout=ceil(total_time + 360)) try: result = self._execute_rotation_sequence(request) @@ -2166,16 +2082,13 @@ class AareDAQ: logger.error("Rotation scan failed, no result returned") raise DataCollectionException("Rotation scan failed, no result returned") self._set_state(BeamlineStateEnum.SampleAlignment) - self._cfg.state_busy = False return result finally: try: - if self._cfg.state_busy and self._cfg.state != BeamlineStateEnum.Maintenance: - self._set_state(BeamlineStateEnum.SampleAlignment) + self._set_state(BeamlineStateEnum.SampleAlignment) except Exception: logger.exception("Failed to restore SampleAlignment after rotation") - finally: - self._cfg.state_busy = False + raise @property def dtz(self) -> float: @@ -2187,7 +2100,6 @@ class AareDAQ: @dtz.setter def dtz(self, val: float): - self._cfg.try_set_busy(timeout=360) state = self._cfg.state dtz_low = self._cfg.cached_dtz_low @@ -2198,27 +2110,22 @@ class AareDAQ: try: self.refresh_detector_metadata_cache() except Exception as e: - self._cfg.state_busy = False raise RuntimeError(f"DTZ limits unavailable and refresh failed: {e}") from e dtz_low = self._cfg.cached_dtz_low dtz_high = self._cfg.cached_dtz_high if dtz_low is None or dtz_high is None: - self._cfg.state_busy = False raise RuntimeError("DTZ limits are unavailable") if val < dtz_low or val > dtz_high: - self._cfg.state_busy = False raise RuntimeError(f"dtz={val} outside limits {dtz_low} to {dtz_high}") if state == BeamlineStateEnum.DataCollection: - self._cfg.state_busy = False raise RuntimeError("Cannot set dtz during data collection") elif state == BeamlineStateEnum.SampleAlignment: self._devs.set_dtz(val, wait=False) self._cfg.dtz = val - self._cfg.state_busy = False @property def smargon(self) -> SmargonCoordinate: @@ -2226,29 +2133,15 @@ class AareDAQ: @smargon.setter def smargon(self, sc: SmargonCoordinate): - self._cfg.set_busy(BeamlineStateEnum.SampleAlignment) - try: - self._saved_box = None - self._devs.smargon_pos = sc - self._devs.smargon_wait() - self._cfg.state_busy = False - except Exception: - self._cfg.state_busy = False - raise + self._saved_box = None + self._devs.smargon_pos = sc + self._devs.smargon_wait() def mark_beam(self, x_pxl: float, y_pxl: float): - self._cfg.set_busy(BeamlineStateEnum.BeamLocation) - try: - self._cfg.mark_beam(x_pxl, y_pxl, self._devs.zoom) - self._cfg.state_busy = False - except Exception: - self._cfg.state_busy = False - raise + self._cfg.mark_beam(x_pxl, y_pxl, self._devs.zoom) def clear_mark_beam(self): - self._cfg.set_busy(BeamlineStateEnum.BeamLocation) self._cfg.clear_mark_beam() - self._cfg.state_busy = False @property def sample_geometry(self) -> SampleGeometryModel: @@ -2290,24 +2183,17 @@ class AareDAQ: Returns: RasterGridRequest object representing the found bounding box, or None if failed. """ - try: - self._cfg.try_set_busy(timeout=360) - r = get_ml_bounding_box( - mlbox=self._mlbox, - sample=self.sample, - sample_geometry=self.sample_geometry, - filename=filename, - upload_image=self._aare.upload_image, - logger=logger, - max_images=self.AUTO_RASTER_MAX_IMAGES, - min_cell_size_mm=self.AUTO_RASTER_MAX_IMAGES, - skip_if_exceed_max_image_threshold=self.AUTO_RASTER_SKIP_IF_EXCEED_MAX_IMAGE_THRESHOLD, - ) - self._cfg.state_busy = False - return r - except Exception: - self._cfg.state_busy = False - raise + return get_ml_bounding_box( + mlbox=self._mlbox, + sample=self.sample, + sample_geometry=self.sample_geometry, + filename=filename, + upload_image=self._aare.upload_image, + logger=logger, + max_images=self.AUTO_RASTER_MAX_IMAGES, + min_cell_size_mm=self.AUTO_RASTER_MAX_IMAGES, + skip_if_exceed_max_image_threshold=self.AUTO_RASTER_SKIP_IF_EXCEED_MAX_IMAGE_THRESHOLD, + ) def face_detection( self, steps: int = 14, step_size: int = 15, face_min_ratio: float = 0.3 @@ -2323,14 +2209,9 @@ class AareDAQ: Returns: Dictionary containing face detection results, including found samples and fits. """ - self._cfg.try_set_busy(timeout=360) - try: - result = self._execute_face_detection( - steps=steps, step_size=step_size, face_min_ratio=face_min_ratio, report_error=True - ) - return result.payload - finally: - self._cfg.state_busy = False + return self._execute_face_detection( + steps=steps, step_size=step_size, face_min_ratio=face_min_ratio, report_error=True + ).payload @log_timing(logger, "Auto loop center") def auto_loop_center(self, sample: SampleShortInfo | None = None) -> float: @@ -2349,7 +2230,6 @@ class AareDAQ: """ start = time.perf_counter() try: - self._cfg.try_set_busy(timeout=360) if sample is None: if self.sample is None: raise LoopCenteringFailed( @@ -2361,12 +2241,8 @@ class AareDAQ: if not self._execute_loop_centering(sample): raise LoopCenteringFailed - - self._cfg.state_busy = False - except Exception: self._cfg.zoom_mode = ZoomModeEnum.User - self._cfg.state_busy = False raise finally: @@ -2564,23 +2440,16 @@ class AareDAQ: msg += "with an error" logger.error(f"{msg}, time taken {time.perf_counter() - start} seconds.") try: - if self._cfg.state_busy: - self._set_state(BeamlineStateEnum.RobotSampleExchange) - else: - logger.warning( - "Skipping recovery transition to RobotSampleExchange: " - "beamline is no longer busy. The busy key may have expired" - "befor recovery could run" - ) + self._set_state(BeamlineStateEnum.RobotSampleExchange) except Exception: logger.exception( "Failed to transition to RobotSampleExchange during error recovery" ) + raise else: msg += " successfully" logger.info(f"{msg}, time taken {time.perf_counter() - start} seconds.") - self._cfg.state_busy = False end = time.perf_counter() return end - start @@ -2610,7 +2479,6 @@ class AareDAQ: try: logger.info("Automation-measure starting sequence") self._validate_automation_state(context="automation start") - self._cfg.try_set_busy(timeout=self.AUTOMATION_BUSY_TIMEOUT_S) self._validate_automation_state(context="after acquiring automation busy state") logger.info("Cancelling any pending jfjoch operations") @@ -2676,7 +2544,6 @@ class AareDAQ: logger.info( f"Automation-measure - loop Centering done at {time.perf_counter() - start}" ) - logger.debug(f"Automation-measure - current busy-state: {self._cfg.state_busy}") face_detection_result = self._execute_face_detection( steps=7, step_size=30, face_min_ratio=0.3, report_error=True @@ -2937,8 +2804,7 @@ class AareDAQ: extra={"from_state": curr_state, "to_state": target}, ) - if not self._cfg.state_busy: - raise RuntimeError("Beamline should be busy") + # TODO: CHECK BUSY LOCK if target == BeamlineStateEnum.Maintenance: self._cfg.state = BeamlineStateEnum.Maintenance @@ -3137,7 +3003,6 @@ class AareDAQ: extra={"from_state": curr_state, "to_state": target}, ) self._cfg.state = BeamlineStateEnum.Maintenance - self._cfg.state_busy = False raise StateTransitionFailed( f"State transition failed: {curr_state} -> {target}. " f"Beamline moved to Maintenance. Original error: {e}" @@ -3408,32 +3273,16 @@ class AareDAQ: self._jfjoch.cancel() def anneal(self, time_s: float): - self._cfg.set_busy(BeamlineStateEnum.SampleAlignment) - try: - self._devs.anneal(time_s) - finally: - self._cfg.state_busy = False + self._devs.anneal(time_s) def mono_pitch_scan(self, plot: bool = False) -> None: - self._cfg.try_set_busy(timeout=360) - try: - self._devs.bec_worker.mono_pitch_scan_runner(plot=plot) - finally: - self._cfg.state_busy = False + self._devs.bec_worker.mono_pitch_scan_runner(plot=plot) def change_energy(self, value: float, plot: bool = False) -> None: - self._cfg.try_set_busy(timeout=360) - try: - self._devs.bec_worker.change_energy(value=value, plot=plot) - finally: - self._cfg.state_busy = False + self._devs.bec_worker.change_energy(value=value, plot=plot) def bec_load_user_macros(self) -> None: - self._cfg.try_set_busy(timeout=360) - try: - self._devs.bec_worker.load_user_macros() - finally: - self._cfg.state_busy = False + self._devs.bec_worker.load_user_macros() def bec_list_all_user_macros(self) -> list[str]: macros = self._devs.bec_worker.list_all_user_macros() @@ -3448,33 +3297,17 @@ class AareDAQ: return [str(item) for item in devices] def bec_reinitialise_planner_and_position_devices(self, method: str = "auto") -> list[str]: - self._cfg.try_set_busy(timeout=360) - try: - self._devs.bec_worker.load_user_macros() - return self._devs.bec_worker.reinitialise_planner_and_position_devices(method=method) - finally: - self._cfg.state_busy = False + self._devs.bec_worker.load_user_macros() + return self._devs.bec_worker.reinitialise_planner_and_position_devices(method=method) def bec_save_current_bs_pos(self) -> None: - self._cfg.try_set_busy(timeout=360) - try: - self._devs.bec_worker.save_current_bs_pos() - finally: - self._cfg.state_busy = False + self._devs.bec_worker.save_current_bs_pos() def bec_save_current_collimator_pos(self) -> None: - self._cfg.try_set_busy(timeout=360) - try: - self._devs.bec_worker.save_current_collimator_pos() - finally: - self._cfg.state_busy = False + self._devs.bec_worker.save_current_collimator_pos() def bec_save_current_aerotech_position(self) -> None: - self._cfg.try_set_busy(timeout=360) - try: - self._devs.bec_worker.save_current_aerotech_position() - finally: - self._cfg.state_busy = False + self._devs.bec_worker.save_current_aerotech_position() def steer_beam_available(self) -> bool: return "beam_steering" in self._devs.bec_worker.dev @@ -3483,27 +3316,20 @@ class AareDAQ: """Run the routine to move the beam to the sample location. Update the location if provided.""" if "beam_steering" not in self._devs.bec_worker.dev: raise BECCommunicationError("Beam steering device does not exist in the BEC config.") - self._cfg.try_set_busy(timeout=360) - - try: - if not self.shutter: - raise AareException( - "Shutter not open! Please open the shutter before running beam steering." - ) - self._dispatch.bec_macros.auto_exposure() - if x is not None: - self._devs.bec_worker.dev.beam_steering.sample_loc_x_px.set(x).wait() - if y is not None: - self._devs.bec_worker.dev.beam_steering.sample_loc_y_px.set(y).wait() - self._devs.bec_worker.dev.beam_steering.trigger().wait() - finally: - self._cfg.state_busy = False + if not self.shutter: + raise AareException( + "Shutter not open! Please open the shutter before running beam steering." + ) + self._dispatch.bec_macros.auto_exposure() + if x is not None: + self._devs.bec_worker.dev.beam_steering.sample_loc_x_px.set(x).wait() + if y is not None: + self._devs.bec_worker.dev.beam_steering.sample_loc_y_px.set(y).wait() + self._devs.bec_worker.dev.beam_steering.trigger().wait() def fluorimeter_take_spectrum( self, fm: FluorescenceSpectrumParameterModel ) -> FluorescenceSpectrumOutputModel: - self._cfg.try_set_busy(timeout=360) - try: self._set_state(BeamlineStateEnum.XrayFluorescence) @@ -3513,11 +3339,9 @@ class AareDAQ: # TODO: Fill self._set_state(BeamlineStateEnum.SampleAlignment) - self._cfg.state_busy = False return None except Exception: self._set_state(BeamlineStateEnum.SampleAlignment) - self._cfg.state_busy = False raise def get_local_contact_config(self) -> LocalContactConfigModel: diff --git a/src/aare/daq/server.py b/src/aare/daq/server.py index ce4a1c6d..b1a8b19d 100644 --- a/src/aare/daq/server.py +++ b/src/aare/daq/server.py @@ -731,34 +731,25 @@ async def bec_save_current_aerotech_position(token: str = Depends(oauth2_scheme) def initialise_aerotech(self): - self._cfg.try_set_busy(timeout=360) try: self._devs.aerotech.home_aerotech() - self._cfg.state_busy = False except Exception as e: - self._cfg.state_busy = False logger.error(f"Failed to initialise Aerotech: {e}") raise def detector_take_pedestal(self): - self._cfg.try_set_busy(timeout=360) try: self._jfjoch.take_pedestal() - self._cfg.state_busy = False except Exception as e: - self._cfg.state_busy = False logger.error(f"Failed to take detector pedestal: {e}") raise def initialise_detector(self): - self._cfg.try_set_busy(timeout=360) try: self._jfjoch.initialize() - self._cfg.state_busy = False except Exception as e: - self._cfg.state_busy = False logger.error(f"Failed to initialise detector: {e}") raise @@ -1500,7 +1491,6 @@ async def force_clear_busy( data = auth.parse_token(token) auth.check_jwt_staff_only(data) _validate_recovery_code(payload.confirmation_code) - cfg.state_busy = False logger.warning( "Beamline busy flag cleared via protected endpoint.", extra={"session": getattr(data, "session", None)}, @@ -1528,10 +1518,8 @@ async def force_maintenance_state( sample_mounted = _sample_is_mounted() prev_state = cfg.state - prev_busy = cfg.state_busy auth.force_current_sesion(cfg, data) - cfg.state_busy = False cfg.state = BeamlineStateEnum.Maintenance logger.warning( @@ -1539,7 +1527,6 @@ async def force_maintenance_state( extra={ "session": getattr(data, "session", None), "previous_state": getattr(prev_state, "name", str(prev_state)), - "previous_busy": prev_busy, "sample_mounted": sample_mounted, }, ) @@ -1548,7 +1535,6 @@ async def force_maintenance_state( "ok": True, "sample_mounted": sample_mounted, "previous_state": getattr(prev_state, "name", str(prev_state)), - "previous_busy": prev_busy, "new_state": BeamlineStateEnum.Maintenance.name, } @@ -1573,12 +1559,6 @@ async def recovery_unmount_sample( auth.force_current_sesion(cfg, data) - if cfg.state_busy: - raise HTTPException( - status_code=api_status.HTTP_409_CONFLICT, - detail="Beamline is busy. Clear or recover the beamline before attempting recovery unmount.", - ) - status = daq.status if not getattr(status, "tell_connected", False): raise HTTPException( diff --git a/tests/unit/daq/test_face_detection.py b/tests/unit/daq/test_face_detection.py index 72a55736..8ccdc090 100644 --- a/tests/unit/daq/test_face_detection.py +++ b/tests/unit/daq/test_face_detection.py @@ -76,8 +76,6 @@ def test_public_face_detection_uses_execute_face_detection(monkeypatch): from aare.daq.daq import AareDAQ daq = object.__new__(AareDAQ) - cfg = types.SimpleNamespace(try_set_busy=lambda timeout=360: None, state_busy=False) - daq._cfg = cfg daq._execute_face_detection = lambda **kwargs: FaceDetectionResult( success=True, @@ -88,4 +86,3 @@ def test_public_face_detection_uses_execute_face_detection(monkeypatch): assert result["running"] is False assert result["samples"] == [{"angle": 45}] - assert cfg.state_busy is False