feat: remove all busy-state handling

This commit is contained in:
2026-09-09 10:09:10 +02:00
committed by perl_d
parent 5db5e228af
commit fbfe773954
6 changed files with 79 additions and 324 deletions
-1
View File
@@ -20,7 +20,6 @@ dependencies = [
"requests",
"pyepics~=3.5",
"redis",
"python-redis-lock",
"fastapi",
"uvicorn",
"aaredb>=0.83.1",
-5
View File
@@ -1,5 +0,0 @@
from aaredaq.config import BeamlineConfig
from aaredaqlib.beamline import MXBeamline
c = BeamlineConfig(MXBeamline.X06DA)
c.state_busy = False
+12 -52
View File
@@ -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
+67 -243
View File
@@ -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:
-20
View File
@@ -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(
-3
View File
@@ -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