From f444f6c2c827ea3438163e326cdf39016931e29c Mon Sep 17 00:00:00 2001 From: appleb_m Date: Mon, 15 Jun 2026 16:57:45 +0200 Subject: [PATCH] Beamline state panel: exposed new beamline state changes to GUI --- src/aare/common/models.py | 4 + src/aare/daq/daq.py | 146 +++++++++++++++++--- src/aare/daq/server.py | 30 ++++ src/aare/gui/panels/beamline_state_panel.py | 52 +++++-- src/aare/gui/threads/daq_worker.py | 8 ++ 5 files changed, 203 insertions(+), 37 deletions(-) diff --git a/src/aare/common/models.py b/src/aare/common/models.py index b061513d..746eb9c7 100644 --- a/src/aare/common/models.py +++ b/src/aare/common/models.py @@ -370,6 +370,8 @@ class BeamlineStateEnum(Enum): Moving = 8 RobotSampleExchange = 9 XtalSnapshot = 10 + BeamstopAlignment = 11 + FluxMeasurement = 12 def display_name(self) -> str: return { @@ -383,6 +385,8 @@ class BeamlineStateEnum(Enum): BeamlineStateEnum.Moving: "Moving", BeamlineStateEnum.RobotSampleExchange: "Robot sample exchange", BeamlineStateEnum.XtalSnapshot: "Xtal snapshot", + BeamlineStateEnum.BeamstopAlignment: "Beamstop alignment", + BeamlineStateEnum.FluxMeasurement: "Flux measurement", }.get(self, "-") diff --git a/src/aare/daq/daq.py b/src/aare/daq/daq.py index 84143a57..6e1f34aa 100644 --- a/src/aare/daq/daq.py +++ b/src/aare/daq/daq.py @@ -2827,6 +2827,41 @@ class AareDAQ: elif target == BeamlineStateEnum.SampleExchange: workflows.bl2sa(self.__devs, self.__cfg) workflows.sa2se(self.__devs, self.__cfg) + elif target == BeamlineStateEnum.BeamstopAlignment: + workflows.bl2ba(self.__devs, self.__cfg) + elif target == BeamlineStateEnum.FluxMeasurement: + workflows.bl2flux_measurement(self.__devs, self.__cfg) + else: + raise TransformationInvalidException( + f"Cannot go from {curr_state} to {target}, not implemented" + ) + case BeamlineStateEnum.BeamstopAlignment: + if target == BeamlineStateEnum.SampleAlignment: + workflows.ba2sa(self.__devs, self.__cfg) + elif target == BeamlineStateEnum.SampleExchange: + workflows.ba2sa(self.__devs, self.__cfg) + workflows.sa2se(self.__devs, self.__cfg) + elif target == BeamlineStateEnum.FluxMeasurement: + workflows.ba2flux_measurement(self.__devs, self.__cfg) + elif target == BeamlineStateEnum.BeamLocation: + workflows.ba2bl(self.__devs, self.__cfg) + elif target == BeamlineStateEnum.SampleExchange: + workflows.bl2sa(self.__devs, self.__cfg) + workflows.sa2se(self.__devs, self.__cfg) + else: + raise TransformationInvalidException( + f"Cannot go from {curr_state} to {target}, not implemented" + ) + case BeamlineStateEnum.FluxMeasurement: + if target == BeamlineStateEnum.SampleAlignment: + workflows.flux_measurement2sa(self.__devs, self.__cfg) + elif target == BeamlineStateEnum.SampleExchange: + workflows.flux_measurement2sa(self.__devs, self.__cfg) + workflows.sa2se(self.__devs, self.__cfg) + elif target == BeamlineStateEnum.BeamLocation: + workflows.flux_measurement2bl(self.__devs, self.__cfg) + elif target == BeamlineStateEnum.BeamstopAlignment: + workflows.flux_measurement2ba(self.__devs, self.__cfg) else: raise TransformationInvalidException( f"Cannot go from {curr_state} to {target}, not implemented" @@ -2844,6 +2879,10 @@ class AareDAQ: workflows.sa2xrf(self.__devs, self.__cfg) elif target == BeamlineStateEnum.BeamLocation: workflows.sa2bl(self.__devs, self.__cfg) + elif target == BeamlineStateEnum.BeamstopAlignment: + workflows.sa2ba(self.__devs, self.__cfg) + elif target == BeamlineStateEnum.FluxMeasurement: + workflows.sa2flux_measurement(self.__devs, self.__cfg) elif target == BeamlineStateEnum.XtalSnapshot: workflows.sa2xtal_snapshot(self.__devs, self.__cfg) else: @@ -2923,7 +2962,9 @@ class AareDAQ: @property def diffraction_geometry(self) -> DiffractionGeometry: try: + #start = time.perf_counter() det_cfg = self.__jfjoch.detector() + #logger.debug(f"Safe diffraction geometry call took {time.perf_counter() - start:.3f}s") return DiffractionGeometry( energy_keV=self.__devs.energy_kev, dtz_mm=self.__devs.dtz, @@ -2954,19 +2995,62 @@ class AareDAQ: @property def beamline_status(self) -> BeamlineStatus: try: + # start = time.perf_counter() + ring_current = self.__devs.ring_current + # logger.debug(f"Safe ring current call took {time.perf_counter() - start:.3f}s") + # start = time.perf_counter() + front_light = self.front_light + # logger.debug(f"Safe front light call took {time.perf_counter() - start:.3f}s") + # start = time.perf_counter() + back_light = self.back_light + # logger.debug(f"Safe back light call took {time.perf_counter() - start:.3f}s") + # start = time.perf_counter() + cryojet_temp = self.__devs.cryojet_temp + # logger.debug(f"Safe cryojet temp call took {time.perf_counter() - start:.3f}s") + # start = time.perf_counter() + shutter_open = self.__devs.shutter + # logger.debug(f"Safe shutter call took {time.perf_counter() - start:.3f}s") + # start = time.perf_counter() + exp_shutter_open = self.__devs.exp_shutter.state() + # logger.debug(f"Safe exp shutter call took {time.perf_counter() - start:.3f}s") + # start = time.perf_counter() + flux = self.__devs.full_flux + # logger.debug(f"Safe flux call took {time.perf_counter() - start:.3f}s") + # start = time.perf_counter() + samcam_settings = self.__devs.samcam_settings + # logger.debug(f"Safe samcam call took {time.perf_counter() - start:.3f}s") + # start = time.perf_counter() + bl = self.__bl + # logger.debug(f"Safe bl call took {time.perf_counter() - start:.3f}s") + # start = time.perf_counter() + transmission = self.__devs.transmission + # logger.debug(f"Safe transmission call took {time.perf_counter() - start:.3f}s") + # start = time.perf_counter() + zoom = self.__devs.zoom + # logger.debug(f"Safe zoom call took {time.perf_counter() - start:.3f}s") + # start = time.perf_counter() + commisioning_mode = self.__cfg.commissioning_mode + # logger.debug(f"Safe commissioning_mode call took {time.perf_counter() - start:.3f}s") + start = time.perf_counter() + #TODO work this one out, do it on startup! + dtz_min = self.__devs.dtz_low + logger.debug(f"Safe dtz_min call took {time.perf_counter() - start:.3f}s") + start = time.perf_counter() + dtz_max = self.__devs.dtz_high + logger.debug(f"Safe dtz_max call took {time.perf_counter() - start:.3f}s") return BeamlineStatus( - ring_current_mA=self.__devs.ring_current, - front_light=self.front_light, - back_light=self.back_light, - cryojet_K=self.__devs.cryojet_temp, - shutter_open=self.__devs.shutter, - exp_shutter_open=self.__devs.exp_shutter.state(), - flux_ph_s=self.__devs.full_flux, - sample_camera=self.__devs.samcam_settings, - name=self.__bl, - transmission=self.__devs.transmission, - zoom=self.__devs.zoom, - commissioning_mode=self.__cfg.commissioning_mode, + ring_current_mA=ring_current, + front_light=front_light, + back_light=back_light, + cryojet_K=cryojet_temp, + shutter_open=shutter_open, + exp_shutter_open=exp_shutter_open, + flux_ph_s=flux, + sample_camera=samcam_settings, + name=bl, + transmission=transmission, + zoom=zoom, + commissioning_mode=commisioning_mode, dtz_min=20, dtz_max=1000, ) @@ -3089,22 +3173,37 @@ class AareDAQ: @property def status(self) -> DAQStatusModel: try: + #og_start = time.perf_counter() safe_sample, tell_ok, tell_err = self._safe_sample() + #logger.debug(f"Safe sample info call took {time.perf_counter() - og_start:.3f}s") + #start = time.perf_counter() safe_geom, smargon_ok, smargon_err, aerotech_ok, aerotech_err = self._safe_geom() + #logger.debug(f"safe geom call took {time.perf_counter() - start:.3f}s") + #start = time.perf_counter() safe_tell_state = self._safe_tell_state() - - return DAQStatusModel( - state=self.state, - busy=self.busy, - geom=safe_geom, - bl=self._safe_beamline_status(), - sample=safe_sample, - session=SessionStatus( + #logger.debug(f"Safe tell call call took {time.perf_counter() - start:.3f}s") + #start = time.perf_counter() + safe_beamline_status = self._safe_beamline_status() + #logger.debug(f"Safe beamline status call took {time.perf_counter() - start:.3f}s") + #start = time.perf_counter() + safe_diffraction_geom = self._safe_diffraction_geometry() + #logger.debug(f"Safe diffraction geometry call took {time.perf_counter() - start:.3f}s") + #start = time.perf_counter() + session_status = SessionStatus( current_pgroup=self.__cfg.pgroup, session=self.__cfg.session_state(0), # 0 is dummy session staff=False - ), - diffraction=self._safe_diffraction_geometry(), + ) + #logger.debug(f"Safe session status call took {time.perf_counter() - start:.3f}s") + #start = time.perf_counter() + status = DAQStatusModel( + state=self.state, + busy=self.busy, + geom=safe_geom, + bl=safe_beamline_status, + sample=safe_sample, + session=session_status, + diffraction=safe_diffraction_geom, box=self.__saved_box, last_best_res=self.__cfg.last_best_res, last_best_b_factor=self.__cfg.last_best_b_factor, @@ -3117,6 +3216,9 @@ class AareDAQ: aerotech_connected=aerotech_ok, aerotech_error=aerotech_err, ) + #logger.debug(f"Creating DAQStatusModel took {time.perf_counter() - start:.3f}s") + #logger.debug(f"returning status call took {time.perf_counter() - og_start:.3f}s") + return status except Exception as e: logger.error(f"Failed to retrieve DAQ status: {e}") diff --git a/src/aare/daq/server.py b/src/aare/daq/server.py index 9fb4e0a9..5ee42868 100644 --- a/src/aare/daq/server.py +++ b/src/aare/daq/server.py @@ -1259,6 +1259,36 @@ async def beam_location(token: str = Depends(oauth2_scheme)): auth.check_jwt_staff(cfg, auth.parse_token(token)) daq.state = BeamlineStateEnum.BeamLocation +@app.post("/state/beamstop_alignment") +async def beamstop_alignment(token: str = Depends(oauth2_scheme)): + """ + Transition beamline state to BeamstopAlignment. + + Args: + token: OAuth2 access token. + + Returns: + "OK" on success. + """ + auth.check_jwt_staff(cfg, auth.parse_token(token)) + daq.state = BeamlineStateEnum.BeamstopAlignment + return "OK" + +@app.post("/state/flux_measurement") +async def flux_measurement(token: str = Depends(oauth2_scheme)): + """ + Transition beamline state to FluxMeasurement. + + Args: + token: OAuth2 access token. + + Returns: + "OK" on success. + """ + auth.check_jwt_staff(cfg, auth.parse_token(token)) + daq.state = BeamlineStateEnum.FluxMeasurement + return "OK" + @app.post("/state/data_collection") async def data_collection(token: str = Depends(oauth2_scheme)): """ diff --git a/src/aare/gui/panels/beamline_state_panel.py b/src/aare/gui/panels/beamline_state_panel.py index ddafb216..38f5a4b6 100644 --- a/src/aare/gui/panels/beamline_state_panel.py +++ b/src/aare/gui/panels/beamline_state_panel.py @@ -53,9 +53,11 @@ class BeamlineStatePanel(QFrame): xtal_snapshot = Signal() xray_fluorescence = Signal() robot_sample_exchange = Signal() + beamstop_alignment = Signal() + flux_measurement = Signal() set_width = 400 - map_height = 470 + map_height = 542 title_height = 50 collapsed_height = 50 station_radius = 8 @@ -85,14 +87,16 @@ class BeamlineStatePanel(QFrame): self._label_hover_bg = "rgba(244, 196, 48, 0.22)" self._group_colors: dict[BeamlineStateEnum, QColor] = { - BeamlineStateEnum.DewarTransfer: QColor(128, 90, 213), # Purple - BeamlineStateEnum.SampleExchange: QColor(237, 137, 54), # Orange - BeamlineStateEnum.RobotSampleExchange: QColor(237, 137, 54), # Orange - BeamlineStateEnum.SampleAlignment: QColor(72, 187, 120), # Green - BeamlineStateEnum.BeamLocation: QColor(72, 187, 120), # Green - BeamlineStateEnum.DataCollection: QColor(236, 72, 153), # Pink - BeamlineStateEnum.XtalSnapshot: QColor(236, 72, 153), # Pink - BeamlineStateEnum.XrayFluorescence: QColor(236, 72, 153), # Pink + BeamlineStateEnum.DewarTransfer: QColor(128, 90, 213), # Purple + BeamlineStateEnum.SampleExchange: QColor(237, 137, 54), # Orange + BeamlineStateEnum.RobotSampleExchange: QColor(237, 137, 54), # Orange + BeamlineStateEnum.SampleAlignment: QColor(72, 187, 120), # Green + BeamlineStateEnum.BeamLocation: QColor(72, 187, 120), # Green + BeamlineStateEnum.BeamstopAlignment: QColor(72, 187, 120), # Green + BeamlineStateEnum.FluxMeasurement: QColor(72, 187, 120), # Green + BeamlineStateEnum.DataCollection: QColor(236, 72, 153), # Pink + BeamlineStateEnum.XtalSnapshot: QColor(236, 72, 153), # Pink + BeamlineStateEnum.XrayFluorescence: QColor(236, 72, 153), # Pink } self._group_label_colors: dict[BeamlineStateEnum, str] = { @@ -105,6 +109,8 @@ class BeamlineStatePanel(QFrame): BeamlineStateEnum.RobotSampleExchange: "rgba(237, 137, 54, 0.16)", BeamlineStateEnum.SampleAlignment: "rgba(72, 187, 120, 0.16)", BeamlineStateEnum.BeamLocation: "rgba(72, 187, 120, 0.16)", + BeamlineStateEnum.BeamstopAlignment: "rgba(72, 187, 120, 0.16)", + BeamlineStateEnum.FluxMeasurement: "rgba(72, 187, 120, 0.16)", BeamlineStateEnum.DataCollection: "rgba(236, 72, 153, 0.14)", BeamlineStateEnum.XtalSnapshot: "rgba(236, 72, 153, 0.14)", BeamlineStateEnum.XrayFluorescence: "rgba(236, 72, 153, 0.14)", @@ -112,13 +118,20 @@ class BeamlineStatePanel(QFrame): self._stations = [ StationSpec(BeamlineStateEnum.DewarTransfer, "Dewar transfer", 54, 140, True, "Dewar transfer mode"), - StationSpec(BeamlineStateEnum.SampleExchange, "Manual sample exchange", 54, 176, True, "Manual sample exchange mode"), - StationSpec(BeamlineStateEnum.RobotSampleExchange, "Robot sample exchange", 54, 212, True, "Robot-assisted sample exchange"), - StationSpec(BeamlineStateEnum.SampleAlignment, "Sample alignment", 54, 248, True, "Sample centring and alignment mode"), + StationSpec(BeamlineStateEnum.SampleExchange, "Manual sample exchange", 54, 176, True, + "Manual sample exchange mode"), + StationSpec(BeamlineStateEnum.RobotSampleExchange, "Robot sample exchange", 54, 212, True, + "Robot-assisted sample exchange"), + StationSpec(BeamlineStateEnum.SampleAlignment, "Sample alignment", 54, 248, True, + "Sample centring and alignment mode"), StationSpec(BeamlineStateEnum.BeamLocation, "Beam location", 54, 284, True, "Beam location mode"), - StationSpec(BeamlineStateEnum.DataCollection, "Data collection", 54, 320, True, "Measurement / collection mode"), - StationSpec(BeamlineStateEnum.XtalSnapshot, "Crystal snapshot", 54, 356, True, "Crystal snapshot mode"), - StationSpec(BeamlineStateEnum.XrayFluorescence, "XRF", 54, 392, True, "X-ray fluorescence mode"), + StationSpec(BeamlineStateEnum.BeamstopAlignment, "Beamstop alignment", 54, 320, True, + "Beamstop alignment mode"), + StationSpec(BeamlineStateEnum.FluxMeasurement, "Flux measurement", 54, 356, True, "Flux measurement mode"), + StationSpec(BeamlineStateEnum.DataCollection, "Data collection", 54, 392, True, + "Measurement / collection mode"), + StationSpec(BeamlineStateEnum.XtalSnapshot, "Crystal snapshot", 54, 428, True, "Crystal snapshot mode"), + StationSpec(BeamlineStateEnum.XrayFluorescence, "XRF", 54, 464, True, "X-ray fluorescence mode"), ] self._segments = [ @@ -126,7 +139,12 @@ class BeamlineStatePanel(QFrame): (BeamlineStateEnum.SampleExchange, BeamlineStateEnum.RobotSampleExchange), (BeamlineStateEnum.RobotSampleExchange, BeamlineStateEnum.SampleAlignment), (BeamlineStateEnum.SampleAlignment, BeamlineStateEnum.BeamLocation), + (BeamlineStateEnum.SampleAlignment, BeamlineStateEnum.BeamstopAlignment), + (BeamlineStateEnum.SampleAlignment, BeamlineStateEnum.FluxMeasurement), (BeamlineStateEnum.SampleAlignment, BeamlineStateEnum.DataCollection), + (BeamlineStateEnum.BeamLocation, BeamlineStateEnum.BeamstopAlignment), + (BeamlineStateEnum.BeamLocation, BeamlineStateEnum.FluxMeasurement), + (BeamlineStateEnum.BeamstopAlignment, BeamlineStateEnum.FluxMeasurement), (BeamlineStateEnum.DataCollection, BeamlineStateEnum.XtalSnapshot), (BeamlineStateEnum.DataCollection, BeamlineStateEnum.XrayFluorescence), ] @@ -339,6 +357,10 @@ class BeamlineStatePanel(QFrame): self.robot_sample_exchange.emit() elif state == BeamlineStateEnum.BeamLocation: self.beam_location.emit() + elif state == BeamlineStateEnum.BeamstopAlignment: + self.beamstop_alignment.emit() + elif state == BeamlineStateEnum.FluxMeasurement: + self.flux_measurement.emit() elif state == BeamlineStateEnum.DataCollection: self.data_collection.emit() elif state == BeamlineStateEnum.XtalSnapshot: diff --git a/src/aare/gui/threads/daq_worker.py b/src/aare/gui/threads/daq_worker.py index 71c1d447..f03bf2a0 100644 --- a/src/aare/gui/threads/daq_worker.py +++ b/src/aare/gui/threads/daq_worker.py @@ -771,6 +771,14 @@ class DAQWorker(QObject): def beam_location(self): self.generic_post("state/beam_location") + @Slot() + def beamstop_alignment(self): + self.generic_post("state/beamstop_alignment") + + @Slot() + def flux_measurement(self): + self.generic_post("state/flux_measurement") + @Slot() def data_collection(self): self.generic_post("state/data_collection")