From d0f6f9341a81999eda2c3ac684eed9d50f33b166 Mon Sep 17 00:00:00 2001 From: vshekar1 Date: Wed, 8 Oct 2025 13:54:49 -0400 Subject: [PATCH 01/12] Fixes to floco recovery processes --- daq_lib.py | 4 +- daq_macros.py | 88 +++++++++++++++++++++++++++++--------- daq_main_common.py | 6 ++- embl_robot.py | 30 ++++++++++++- gui/control_main.py | 23 +++++++++- gui/dialog/staff_screen.py | 52 +++++++++++++++------- robot_lib.py | 6 +++ 7 files changed, 167 insertions(+), 42 deletions(-) diff --git a/daq_lib.py b/daq_lib.py index 172a685b..2e42987d 100644 --- a/daq_lib.py +++ b/daq_lib.py @@ -237,14 +237,14 @@ def flocoUnlock(): unlockGUI() govMonOn() -def flocoStopOperations(): +def floco_stop_operations(): try: daq_macros.run_recovery_procedure(stop=True) lockGUI() except Exception as e: logger.exception("Error encountered while running flocoStopOperations. Stopping...") -def flocoContinueOperations(): +def floco_continue_operations(): try: daq_macros.run_recovery_procedure(stop=False) flocoUnlock() diff --git a/daq_macros.py b/daq_macros.py index 7045ed93..374960ae 100644 --- a/daq_macros.py +++ b/daq_macros.py @@ -301,10 +301,22 @@ def run_recovery_procedure(stop=True): """ Manual recovery procedure used in daq_lib.flocoStopOperations and daq_lib.flocoContinueOperations """ + RESET = "\033[0m" + BOLD = "\033[1m" + RED = "\033[31m" + GREEN = "\033[32m" + YELLOW = "\033[33m" logger.info(f"Running recovery procedure: {'stop operations' if stop else 'continue operations'}") + def check_robot(): - if not getBlConfig("robot_online") or not getBlConfig("mountEnabled"): - raise ValueError("Robot is offline or mount is disabled, sample found in gripper. Stopping recovery...") + if not getBlConfig("robot_online"): + logger.info(f"{YELLOW}Robot was off, running robotOn(){RESET}") + robotOn() + + if not getBlConfig("mountEnabled"): + logger.info(f"{YELLOW}Mounting was disabled. Running enableMount(){RESET}") + enableMount() + # raise ValueError(f"{RED}{BOLD}Robot is offline or mount is disabled. Stopping recovery...{RESET}") def run_home_pin(): from start_bs import home_pins @@ -315,31 +327,61 @@ def gov_state_to_se(): def check_robot_speed(): if not robot_arm.is_full_speed(): - logger.error("Robot arm speed is NOT 100%") + logger.error(f"{RED}{BOLD}Robot arm speed is NOT 100%{RESET}") def check_beam(): if not (getPvDesc("beamAvailable") or getBlConfig(BEAM_CHECK) == 0): - logger.error("Beam not available, please open shutter") + logger.error(f"{RED}{BOLD}Beam not available, please open shutter{RESET}") + + def check_gonio(): + print(f"BoostStatus: {getPvDesc('boostStatus') == 0}" ) + print(f'Mount status {daq_lib.get_field("mounted_pin") != ""}') + print(f'Sample detected: {getPvDesc("sampleDetected") == 0}') + if (getPvDesc("boostStatus") != 0 + or daq_lib.get_field("mounted_pin") != '' + or getPvDesc("sampleDetected") == 0): + logger.error("Pin is mounted on gonio") + raise Exception(f"{RED}{BOLD}Gonio has a sample, stopping recovery procedure{RESET}") + + def set_fts(): + # Value is set to 9 because 0.1 seconds is the 10th option in the css dropdown! + force_torque_sensor.put(9) - steps_to_run = {"Recover robot": robot_lib.recoverRobot, - "Check robot status": check_robot, - "Dry gripper": robot_lib.dryGripper, - "Home pin": run_home_pin, } if stop: - steps_to_run.update({"Disable mount": disableMount, "Robot off": robotOff}) + steps_to_run = { + "Logging": logMe, + "Recover robot": robot_lib.recoverRobot, + "Setting FTS to 0.1 sec polling": set_fts, + "Disable mount": disableMount, + "Robot off": robotOff} else: - steps_to_run.update({"Enable mount": enableMount, "Robot on": robotOn}) - - steps_to_run.update({"Move Governor to SE": gov_state_to_se, }) - if not stop: - steps_to_run.update({"Checking robot arm speed": check_robot_speed, - "Checking beam": check_beam}) - - for i, (step_message, func) in enumerate(steps_to_run.items()): - logger.info(f"Step {i+1} of {len(steps_to_run)}: {step_message}") - func() - logger.info(f"Completed step: {step_message}") - + steps_to_run = { + "Logging": logMe, + "Checking sample in gonio": check_gonio, + "Recover robot": robot_lib.recoverRobotFloco, + "Setting FTS to 0.1 sec polling": set_fts, + "Check robot status": check_robot, + "Dry gripper": robot_lib.dryGripperFloco, + "Home pin": run_home_pin, + "Enable mount": enableMount, + "Robot on": robotOn, + "Move Governor to SE": gov_state_to_se, + "Checking robot arm speed": check_robot_speed, + "Checking beam": check_beam} + + try: + daq_lib.set_field("program_state","Running floco recovery") + for i, (step_message, func) in enumerate(steps_to_run.items()): + logger.info(f"{YELLOW}Executing step {i+1} of {len(steps_to_run)}:{RESET} {GREEN}{step_message}{RESET}") + func() + # logger.info(f"{GREEN}Completed step: {step_message}{RESET}") + except Exception as e: + logger.info(f"{RED}Exception while running floco recovery{RESET} : {e}") + if "unrecognized location" in str(e).lower(): + message = "Unrecognized location, Manual recovery required" + logger.info(f"{RED}Recovery aborted: {message}{RESET}") + finally: + daq_lib.set_field("program_state","Program Ready") def run_top_view_optimized(): RE(topview_optimized()) @@ -4349,11 +4391,13 @@ def setAttenRI(): def robotOn(): """robotOn() : use the robot to mount samples""" setBlConfig("robot_online",1) + daq_lib.gui_message(json.dumps({"update_robot_settings": True})) def robotOff(): """robotOff() : fake mounting samples""" setBlConfig("robot_online",0) + daq_lib.gui_message(json.dumps({"update_robot_settings": True})) def zebraVecDaqSetup(angle_start,imgWidth,exposurePeriodPerImage,numImages,filePrefix,data_directory_name,file_number_start,scanEncoder=3): #scan encoder 0=x, 1=y,2=z,3=omega @@ -4527,10 +4571,12 @@ def backoffDetector(): def disableMount(): """disableMount() : turn off robot mounting. Usually done in an error situation where we want staff intervention before resuming.""" setBlConfig("mountEnabled",0) + daq_lib.gui_message(json.dumps({"update_enable_mount_setting": True})) def enableMount(): """enableMount() : allow robot mounting""" setBlConfig("mountEnabled",1) + daq_lib.gui_message(json.dumps({"update_enable_mount_setting": True})) def set_beamsize(sizeV, sizeH): if (sizeV == 'V0'): diff --git a/daq_main_common.py b/daq_main_common.py index dd459370..5c49d7c1 100755 --- a/daq_main_common.py +++ b/daq_main_common.py @@ -83,8 +83,12 @@ def setGovState(state): unlatchGov, backoffDetector, enableMount, + disableMount, robotOn, - set_energy + robotOff, + set_energy, + insertRasterResult, + anneal, ] if daq_utils.beamline != "nyx": diff --git a/embl_robot.py b/embl_robot.py index a8225d1e..4d513265 100644 --- a/embl_robot.py +++ b/embl_robot.py @@ -74,13 +74,29 @@ def recoverRobot(self): if bLoaded: daq_macros.robotOff() daq_macros.disableMount() - daq_lib.gui_message("Found a sample in the gripper - CALL STAFF! disableMount() executed.") + daq_lib.gui_message("FATAL ROBOT ERROR Found a sample in the gripper - CALL STAFF! disableMount() and robotOff() executed.") else: RobotControlLib.runCmd("goHome") except Exception as e: e_s = str(e) daq_lib.gui_message("ROBOT Recover failed! " + e_s) + def recoverRobotFloco(self): + try: + self.rebootEMBL() + time.sleep(8.0) + _,bLoaded,_ = RobotControlLib.recover() + if bLoaded: + daq_macros.robotOff() + daq_macros.disableMount() + daq_lib.gui_message("FATAL ROBOT ERROR Found a sample in the gripper - CALL STAFF! disableMount() and robotOff() executed.") + raise Exception("FATAL ROBOT ERROR Found a sample in the gripper - CALL STAFF! disableMount() and robotOff() executed.") + else: + RobotControlLib.runCmd("goHome") + except Exception as e: + e_s = str(e) + daq_lib.gui_message("ROBOT Recover failed! " + e_s) + raise def dryGripper(self): try: @@ -93,6 +109,18 @@ def dryGripper(self): daq_lib.gui_message("Dry gripper failed! " + e_s) setPvDesc("warmupThreshold",saveThreshold) + def dryGripperFloco(self): + try: + saveThreshold = getPvDesc("warmupThresholdRBV") + setPvDesc("warmupThreshold",50) + _thread.start_new_thread(self.warmupGripperRecoverThread,(saveThreshold,0)) + self.warmupGripperForDry() + except Exception as e: + e_s = str(e) + daq_lib.gui_message("Dry gripper failed! " + e_s) + setPvDesc("warmupThreshold",saveThreshold) + raise + def DewarAutoFillOn(self): RobotControlLib.runCmd("turnOnAutoFill") diff --git a/gui/control_main.py b/gui/control_main.py index 2995109e..08b7e8a7 100644 --- a/gui/control_main.py +++ b/gui/control_main.py @@ -5455,7 +5455,28 @@ def popupServerMessage(self, message_s): if message_s == "killMessage": return else: - self.popupMessage.showMessage(message_s) + try: + broadcast_command = json.loads(message_s) + self.processServerCommand(broadcast_command) + except json.JSONDecodeError: + self.popupMessage.showMessage(message_s) + except Exception as e: + logger.error(f"Could not process command: {e}") + + def processServerCommand(self, command: "dict[str, Any]"): + """ + Process a command broadcasted from the server + """ + if "highlight_cells" in command: + if self.rasterList: + raster_item: RasterGroup = self.rasterList[-1]["graphicsItem"] + raster_item.set_highlighted_cells(command["highlight_cells"]) + elif "update_robot_settings" in command: + self.staffScreenDialog.update_robot_state_checkbox() + elif "update_enable_mount_setting" in command: + self.staffScreenDialog.update_enable_mount_state_checkbox() + else: + raise ValueError(f"Command not found: {command}") def printServerMessage(self, message_s): if self.textWindowMessageInit: diff --git a/gui/dialog/staff_screen.py b/gui/dialog/staff_screen.py index 1375f7be..2630a265 100644 --- a/gui/dialog/staff_screen.py +++ b/gui/dialog/staff_screen.py @@ -43,11 +43,8 @@ def __init__(self, parent: "ControlMain", **kwargs): hBoxColParams0.addWidget(puckToDewarButton) hBoxColParams0.addWidget(removePuckButton) self.robotOnCheckBox = QCheckBox("Robot (On)") - if getBlConfig("robot_online") == 1: - self.robotOnCheckBox.setChecked(True) - else: - self.robotOnCheckBox.setChecked(False) - self.robotOnCheckBox.stateChanged.connect(self.robotOnCheckCB) + self.update_robot_state_checkbox() + self.robotOnCheckBox.clicked.connect(self.robotOnCheckCB) self.topViewCheckOnCheckBox = QCheckBox("TopViewCheck (On)") if getBlConfig(TOP_VIEW_CHECK) == 1: self.topViewCheckOnCheckBox.setChecked(True) @@ -112,11 +109,8 @@ def __init__(self, parent: "ControlMain", **kwargs): hBoxColParams1.addWidget(self.albulaDispCheckBox) self.enableMountCheckBox = QCheckBox("Enable Mount") - if getBlConfig("mountEnabled") == 1: - self.enableMountCheckBox.setChecked(True) - else: - self.enableMountCheckBox.setChecked(False) - self.enableMountCheckBox.stateChanged.connect(self.enableMountCheckCB) + self.update_enable_mount_state_checkbox() + self.enableMountCheckBox.clicked.connect(self.enableMountCheckCB) self.unmountColdButton = QtWidgets.QPushButton("Unmount Cold") self.unmountColdButton.clicked.connect(self.unmountColdCB) self.openPort1Button = QtWidgets.QPushButton("Open Port 1") @@ -301,10 +295,36 @@ def homePinsCB(self): self.parent.send_to_server("homePins") def robotOnCheckCB(self, state): - if state == QtCore.Qt.Checked: - setBlConfig("robot_online", 1) + if state: + self.parent.send_to_server("robotOn") + else: + self.parent.send_to_server("robotOff") + + def update_robot_state_checkbox(self): + if getBlConfig("robot_online") == 1: + self.robotOnCheckBox.setChecked(True) + else: + self.robotOnCheckBox.setChecked(False) + + def update_enable_mount_state_checkbox(self): + if getBlConfig("mountEnabled") == 1: + self.enableMountCheckBox.setChecked(True) + else: + self.enableMountCheckBox.setChecked(False) + + + def update_robot_state_checkbox(self): + if getBlConfig("robot_online") == 1: + self.robotOnCheckBox.setChecked(True) else: - setBlConfig("robot_online", 0) + self.robotOnCheckBox.setChecked(False) + + def update_enable_mount_state_checkbox(self): + if getBlConfig("mountEnabled") == 1: + self.enableMountCheckBox.setChecked(True) + else: + self.enableMountCheckBox.setChecked(False) + def beamCheckOnCheckCB(self, state): if state == QtCore.Qt.Checked: @@ -380,10 +400,10 @@ def checkQueueCollect(self): self.gripperUnmountColdCheckBox.setEnabled(False) def enableMountCheckCB(self, state): - if state == QtCore.Qt.Checked: - setBlConfig("mountEnabled", 1) + if state: + self.parent.send_to_server("enabledMount") else: - setBlConfig("mountEnabled", 0) + self.parent.send_to_server("disableMount") def screenDefaultsCancelCB(self): self.hide() diff --git a/robot_lib.py b/robot_lib.py index e92478c4..260c999f 100644 --- a/robot_lib.py +++ b/robot_lib.py @@ -52,6 +52,12 @@ def recoverRobot(): def dryGripper(): robot.dryGripper() +def recoverRobotFloco(): + robot.recoverRobotFloco() + +def dryGripperFloco(): + robot.dryGripperFloco() + # TODO ask Edwin about these Dewar functions def DewarRefill(hours): From ec4a675f4b7f72281eb4447f2fe65481f5a0f0b4 Mon Sep 17 00:00:00 2001 From: vshekar1 Date: Tue, 30 Dec 2025 15:59:24 -0500 Subject: [PATCH 02/12] Added more commands to update server rather than database from gui --- daq_main_common.py | 7 +++- gui/control_main.py | 66 ++++++++++++++++++++++++++++++++++---- gui/dialog/staff_screen.py | 62 +++++++---------------------------- 3 files changed, 76 insertions(+), 59 deletions(-) diff --git a/daq_main_common.py b/daq_main_common.py index 5c49d7c1..2b53f77f 100755 --- a/daq_main_common.py +++ b/daq_main_common.py @@ -87,8 +87,13 @@ def setGovState(state): robotOn, robotOff, set_energy, - insertRasterResult, anneal, + beamCheckOn, + beamCheckOff, + queueCollectOn, + queueCollectOff, + unmountColdOn, + unmountColdOff ] if daq_utils.beamline != "nyx": diff --git a/gui/control_main.py b/gui/control_main.py index 08b7e8a7..a665dae2 100644 --- a/gui/control_main.py +++ b/gui/control_main.py @@ -160,6 +160,8 @@ class ControlMain(QtWidgets.QMainWindow): lowMagCursorChangeSignal = QtCore.Signal(int, str) cryostreamTempSignal = QtCore.Signal(object) sampleZoomChangeSignal = QtCore.Signal(object) + gov_state_change_signal = QtCore.Signal(str) + dewar_plate_change_signal = QtCore.Signal(int) def __init__(self): super(ControlMain, self).__init__() @@ -183,6 +185,8 @@ def __init__(self): if daq_utils.beamline != "nyx": self.albulaInterface = AlbulaInterface(ip=os.environ["EIGER_DCU_IP"], gov_message_pv_name=daq_utils.pvLookupDict["governorMessage"],) + + self.dewar_plate_pos_pv = PV(daq_utils.pvLookupDict["dewarPlatePos"]) self.initUI() self.initOphyd() self.govStateMessagePV = PV(daq_utils.pvLookupDict["governorMessage"]) @@ -1609,6 +1613,27 @@ def annealButtonCB(self): except: pass + def manage_gov_state_change(self, state: str): + # This function reacts to changes in governor state + # Currently changes what camera angle is shown in the center + if state in ("state SE", "transition SA to SE"): + print(f"Govstate: {state}") + self.sampleCameraThread.updateCam("http://xf17id1b-webcam1.nsls2.bnl.local/axis-cgi/mjpg/video.cgi?resolution=640x360") + elif state in ("transition SE to TA"): + print(f"Govstate: {state}") + self.sampleCameraThread.updateCam("http://xf17id1b-webcam4.nsls2.bnl.local/axis-cgi/mjpg/video.cgi?resolution=640x360") + elif state in ("state TA"): + print(f"Govstate: {state}") + self.sampleCameraThread.updateCam(self.capture) + elif state in ("state SA"): + print(f"Govstate: {state}") + self.sampleCameraThread.updateCam(self.capture) + + + def update_dewar_plate_position(self, state: int): + position = int(state) if state else "Rotating" + self.dewar_plate_position_status_widget.setText(f"Plate Position: {position}") + def hideRastersCB(self, state): if state == QtCore.Qt.Checked: self.eraseRastersCB() @@ -5131,6 +5156,12 @@ def pauseButtonStateCB(self, value=None, char_value=None, **kw): pauseButtonStateVar = value self.pauseButtonStateSignal.emit(pauseButtonStateVar) + def manage_gov_state_change_cb(self, value=None, char_value=None, **kw): + self.gov_state_change_signal.emit(char_value) + + def dewar_plate_position_cb(self, value=None, char_value=None, **kw): + self.dewar_plate_change_signal.emit(value) + def initOphyd(self): if daq_utils.beamline == "nyx": # initialize devices @@ -5200,8 +5231,18 @@ def initUI(self): exitAction.setStatusTip("Exit application") exitAction.triggered.connect(self.closeAll) self.statusBar() - self.queue_collect_status_widget = QtWidgets.QLabel("Queue Collect: ON") + self.statusBar().setStyleSheet("QStatusBar { font-size: 14pt; }") + queue_collect_status = "ON" if getBlConfig("queueCollect") else "OFF" + self.queue_collect_status_widget = QtWidgets.QLabel(f"Queue Collect: {queue_collect_status}") + self.dewar_plate_position_status_widget = QtWidgets.QLabel(f"Plate Position: {int(self.dewar_plate_pos_pv.get())}") + sep = QtWidgets.QFrame() + sep.setFrameShape(QtWidgets.QFrame.VLine) + sep.setFrameShadow(QtWidgets.QFrame.Sunken) + sep.setLineWidth(1) self.statusBar().addPermanentWidget(self.queue_collect_status_widget) + self.statusBar().addPermanentWidget(sep) + self.statusBar().addPermanentWidget(self.dewar_plate_position_status_widget) + menubar = self.menuBar() fileMenu = menubar.addMenu("&File") @@ -5446,6 +5487,12 @@ def initCallbacks(self): self.lowMagCursorChangeSignal.connect(self.processLowMagCursorChange) self.lowMagCursorX_pv.add_callback(self.processLowMagCursorChangeCB, ID="x") self.lowMagCursorY_pv.add_callback(self.processLowMagCursorChangeCB, ID="y") + + self.gov_state_change_signal.connect(self.manage_gov_state_change) + self.govStateMessagePV.add_callback(self.manage_gov_state_change_cb) + + self.dewar_plate_change_signal.connect(self.update_dewar_plate_position) + self.dewar_plate_pos_pv.add_callback(self.dewar_plate_position_cb) def popupServerMessage(self, message_s): if self.popUpMessageInit: @@ -5471,12 +5518,17 @@ def processServerCommand(self, command: "dict[str, Any]"): if self.rasterList: raster_item: RasterGroup = self.rasterList[-1]["graphicsItem"] raster_item.set_highlighted_cells(command["highlight_cells"]) - elif "update_robot_settings" in command: - self.staffScreenDialog.update_robot_state_checkbox() - elif "update_enable_mount_setting" in command: - self.staffScreenDialog.update_enable_mount_state_checkbox() - else: - raise ValueError(f"Command not found: {command}") + if "robot_online" in command: + self.staffScreenDialog.robotOnCheckBox.setChecked(command["robot_online"]) + if "enable_mount" in command: + self.staffScreenDialog.enableMountCheckBox.setChecked(command["enable_mount"]) + if "beam_check" in command: + self.staffScreenDialog.beamCheckOnCheckBox.setChecked(command["beam_check"]) + if "queue_collect" in command: + self.staffScreenDialog.queueCollectOnCheckBox.setChecked(command["queue_collect"]) + self.userScreenDialog.queueCollectOnCheckBox.setChecked(command["queue_collect"]) + if "unmount_cold" in command: + self.staffScreenDialog.gripperUnmountColdCheckBox.setChecked(command["unmount_cold"]) def printServerMessage(self, message_s): if self.textWindowMessageInit: diff --git a/gui/dialog/staff_screen.py b/gui/dialog/staff_screen.py index 2630a265..22ece5c8 100644 --- a/gui/dialog/staff_screen.py +++ b/gui/dialog/staff_screen.py @@ -294,45 +294,17 @@ def closeGripper_CB(self): def homePinsCB(self): self.parent.send_to_server("homePins") - def robotOnCheckCB(self, state): - if state: - self.parent.send_to_server("robotOn") - else: - self.parent.send_to_server("robotOff") - - def update_robot_state_checkbox(self): - if getBlConfig("robot_online") == 1: - self.robotOnCheckBox.setChecked(True) - else: - self.robotOnCheckBox.setChecked(False) - - def update_enable_mount_state_checkbox(self): - if getBlConfig("mountEnabled") == 1: - self.enableMountCheckBox.setChecked(True) - else: - self.enableMountCheckBox.setChecked(False) - - - def update_robot_state_checkbox(self): - if getBlConfig("robot_online") == 1: - self.robotOnCheckBox.setChecked(True) - else: - self.robotOnCheckBox.setChecked(False) - - def update_enable_mount_state_checkbox(self): - if getBlConfig("mountEnabled") == 1: - self.enableMountCheckBox.setChecked(True) - else: - self.enableMountCheckBox.setChecked(False) + def checkbox_toggle_cb(self, state: bool, on_function_name: str, off_function_name: str): + # Based on the value of state, call on_function_name if state is true or off_function_name + function_name = on_function_name if state else off_function_name + logger.info(f"Checkbox state: {state}. Running: {function_name}") + self.parent.send_to_server(function_name) + def robotOnCheckCB(self, state): + self.checkbox_toggle_cb(state, "robotOn", "robotOff") def beamCheckOnCheckCB(self, state): - if state == QtCore.Qt.Checked: - setBlConfig(BEAM_CHECK, 1) - logger.debug(f"{BEAM_CHECK} on") - else: - setBlConfig(BEAM_CHECK, 0) - logger.debug(f"{BEAM_CHECK} off") + self.checkbox_toggle_cb(state, "beamCheckOn", "beamCheckOff") def set_energy_check_cb(self, state): if state == QtCore.Qt.Checked: @@ -347,12 +319,7 @@ def set_energy_check_cb(self, state): msg_box.setDefaultButton(QtWidgets.QMessageBox.StandardButton.Ok) def unmountColdCheckCB(self, state): - if state == QtCore.Qt.Checked: - logger.info("unmountColdCheckCB On") - setBlConfig(UNMOUNT_COLD_CHECK, 1) - else: - logger.info("unmountColdCheckCB Off") - setBlConfig(UNMOUNT_COLD_CHECK, 0) + self.checkbox_toggle_cb(state, "unmountColdOn", "unmountColdOff") def topViewOnCheckCB(self, state): if state == QtCore.Qt.Checked: @@ -379,14 +346,7 @@ def guiRemoteOnCheckCB(self, state): setBlConfig("omegaMonitorPV", "RBV") def queueCollectOnCheckCB(self, state): - if state == QtCore.Qt.Checked: - setBlConfig("queueCollect", 1) - self.gripperUnmountColdCheckBox.setEnabled(True) - self.parent.queue_collect_status_widget.setText("Queue Collect: ON") - else: - setBlConfig("queueCollect", 0) - self.gripperUnmountColdCheckBox.setEnabled(False) - self.parent.queue_collect_status_widget.setText("Queue Collect: OFF") + self.checkbox_toggle_cb(state, "queueCollectOn", "queueCollectOff") self.parent.row_clicked( 0 ) # This is so that appropriate boxes are filled when toggling queue collect @@ -401,7 +361,7 @@ def checkQueueCollect(self): def enableMountCheckCB(self, state): if state: - self.parent.send_to_server("enabledMount") + self.parent.send_to_server("enableMount") else: self.parent.send_to_server("disableMount") From dc0cdea1a4ce0a26d6ac0f698320f5e416b74fc8 Mon Sep 17 00:00:00 2001 From: vshekar1 Date: Tue, 30 Dec 2025 17:07:20 -0500 Subject: [PATCH 03/12] imported json and fixed function names --- daq_macros.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/daq_macros.py b/daq_macros.py index 374960ae..4e97d2bf 100644 --- a/daq_macros.py +++ b/daq_macros.py @@ -48,6 +48,7 @@ from fmx_annealer import govStatusGet, govStateSet, fmxAnnealer, amxAnnealer # for using annealer specific to FMX and AMX from config_params import ON_MOUNT_OPTION, OnMountAvailOptions, BEAMSIZE_OPTIONS from mxbluesky.plans import detect_loop, topview_optimized +import json if daq_utils.beamline == 'fmx': from setenergy_lsdc import setELsdc @@ -4391,13 +4392,13 @@ def setAttenRI(): def robotOn(): """robotOn() : use the robot to mount samples""" setBlConfig("robot_online",1) - daq_lib.gui_message(json.dumps({"update_robot_settings": True})) + daq_lib.gui_message(json.dumps({"robot_online": True})) def robotOff(): """robotOff() : fake mounting samples""" setBlConfig("robot_online",0) - daq_lib.gui_message(json.dumps({"update_robot_settings": True})) + daq_lib.gui_message(json.dumps({"robot_online": True})) def zebraVecDaqSetup(angle_start,imgWidth,exposurePeriodPerImage,numImages,filePrefix,data_directory_name,file_number_start,scanEncoder=3): #scan encoder 0=x, 1=y,2=z,3=omega @@ -4571,12 +4572,12 @@ def backoffDetector(): def disableMount(): """disableMount() : turn off robot mounting. Usually done in an error situation where we want staff intervention before resuming.""" setBlConfig("mountEnabled",0) - daq_lib.gui_message(json.dumps({"update_enable_mount_setting": True})) + daq_lib.gui_message(json.dumps({"enable_mount": True})) def enableMount(): """enableMount() : allow robot mounting""" setBlConfig("mountEnabled",1) - daq_lib.gui_message(json.dumps({"update_enable_mount_setting": True})) + daq_lib.gui_message(json.dumps({"enable_mount": True})) def set_beamsize(sizeV, sizeH): if (sizeV == 'V0'): From 513fa8301fc9e48a505f89b58874745700bd3e67 Mon Sep 17 00:00:00 2001 From: vshekar1 Date: Tue, 30 Dec 2025 17:23:43 -0500 Subject: [PATCH 04/12] Changed how check boxes are first intialized --- gui/dialog/staff_screen.py | 48 ++++++++++++-------------------------- 1 file changed, 15 insertions(+), 33 deletions(-) diff --git a/gui/dialog/staff_screen.py b/gui/dialog/staff_screen.py index 22ece5c8..10e73e88 100644 --- a/gui/dialog/staff_screen.py +++ b/gui/dialog/staff_screen.py @@ -43,24 +43,18 @@ def __init__(self, parent: "ControlMain", **kwargs): hBoxColParams0.addWidget(puckToDewarButton) hBoxColParams0.addWidget(removePuckButton) self.robotOnCheckBox = QCheckBox("Robot (On)") - self.update_robot_state_checkbox() + self.robotOnCheckBox.setChecked(True if getBlConfig("robot_online") else False) self.robotOnCheckBox.clicked.connect(self.robotOnCheckCB) self.topViewCheckOnCheckBox = QCheckBox("TopViewCheck (On)") - if getBlConfig(TOP_VIEW_CHECK) == 1: - self.topViewCheckOnCheckBox.setChecked(True) - else: - self.topViewCheckOnCheckBox.setChecked(False) - self.topViewCheckOnCheckBox.stateChanged.connect(self.topViewOnCheckCB) + self.topViewCheckOnCheckBox.setChecked(True if getBlConfig(TOP_VIEW_CHECK) else False) + self.topViewCheckOnCheckBox.clicked.connect(self.topViewOnCheckCB) # BeamCheck check box self.beamCheckOnCheckBox = QCheckBox("BeamCheck (On)") - if getBlConfig(BEAM_CHECK) == 1: - self.beamCheckOnCheckBox.setChecked(True) - else: - self.beamCheckOnCheckBox.setChecked(False) - self.beamCheckOnCheckBox.stateChanged.connect(self.beamCheckOnCheckCB) + self.beamCheckOnCheckBox.setChecked(True if getBlConfig(BEAM_CHECK) else False) + self.beamCheckOnCheckBox.clicked.connect(self.beamCheckOnCheckCB) self.gripperUnmountColdCheckBox = QCheckBox("Unmount Cold") - self.gripperUnmountColdCheckBox.stateChanged.connect(self.unmountColdCheckCB) + self.gripperUnmountColdCheckBox.clicked.connect(self.unmountColdCheckCB) if getBlConfig(UNMOUNT_COLD_CHECK) == 1: self.gripperUnmountColdCheckBox.setEnabled(True) self.gripperUnmountColdCheckBox.setChecked(True) @@ -72,44 +66,32 @@ def __init__(self, parent: "ControlMain", **kwargs): if daq_utils.beamline == "fmx": self.set_energy_checkbox = QCheckBox("Set Energy") hBoxColParams1.addWidget(self.set_energy_checkbox) - if getBlConfig(SET_ENERGY_CHECK) == 1: - self.set_energy_checkbox.setChecked(True) - else: - self.set_energy_checkbox.setChecked(False) - self.set_energy_checkbox.stateChanged.connect(self.set_energy_check_cb) + self.set_energy_checkbox.setChecked(True if getBlConfig(SET_ENERGY_CHECK) else False) + self.set_energy_checkbox.clicked.connect(self.set_energy_check_cb) self.queueCollectOnCheckBox = QCheckBox("Queue Collect") hBoxColParams1.addWidget(self.queueCollectOnCheckBox) self.checkQueueCollect() - self.queueCollectOnCheckBox.stateChanged.connect(self.queueCollectOnCheckCB) + self.queueCollectOnCheckBox.clicked.connect(self.queueCollectOnCheckCB) self.vertRasterOnCheckBox = QCheckBox("Vert. Raster") hBoxColParams1.addWidget(self.vertRasterOnCheckBox) - if getBlConfig("vertRasterOn") == 1: - self.vertRasterOnCheckBox.setChecked(True) - else: - self.vertRasterOnCheckBox.setChecked(False) - self.vertRasterOnCheckBox.stateChanged.connect(self.vertRasterOnCheckCB) + self.vertRasterOnCheckBox.setChecked(True if getBlConfig("vertRasterOn") else False) + self.vertRasterOnCheckBox.clicked.connect(self.vertRasterOnCheckCB) self.procRasterOnCheckBox = QCheckBox("Process Raster") hBoxColParams1.addWidget(self.procRasterOnCheckBox) - if getBlConfig("rasterProcessFlag") == 1: - self.procRasterOnCheckBox.setChecked(True) - else: - self.procRasterOnCheckBox.setChecked(False) - self.procRasterOnCheckBox.stateChanged.connect(self.procRasterOnCheckCB) + self.procRasterOnCheckBox.setChecked(True if getBlConfig("rasterProcessFlag") else False) + self.procRasterOnCheckBox.clicked.connect(self.procRasterOnCheckCB) self.guiRemoteOnCheckBox = QCheckBox("GUI Remote") hBoxColParams1.addWidget(self.guiRemoteOnCheckBox) - if getBlConfig("omegaMonitorPV") == "VAL": - self.guiRemoteOnCheckBox.setChecked(True) - else: - self.guiRemoteOnCheckBox.setChecked(False) + self.guiRemoteOnCheckBox.setChecked(True if getBlConfig("omegaMonitorPV") == "VAL" else False) self.guiRemoteOnCheckBox.stateChanged.connect(self.guiRemoteOnCheckCB) self.albulaDispCheckBox = QCheckBox("Display Data (Albula)") self.albulaDispCheckBox.setChecked(True) hBoxColParams1.addWidget(self.albulaDispCheckBox) self.enableMountCheckBox = QCheckBox("Enable Mount") - self.update_enable_mount_state_checkbox() + self.enableMountCheckBox.setChecked(True if getBlConfig("mountEnabled") else False) self.enableMountCheckBox.clicked.connect(self.enableMountCheckCB) self.unmountColdButton = QtWidgets.QPushButton("Unmount Cold") self.unmountColdButton.clicked.connect(self.unmountColdCB) From 116b5a363d95185831d81e59cf583c75d283a229 Mon Sep 17 00:00:00 2001 From: vshekar1 Date: Wed, 31 Dec 2025 13:10:40 -0500 Subject: [PATCH 05/12] Added unmountCold on-off functions --- daq_macros.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/daq_macros.py b/daq_macros.py index 4e97d2bf..86358209 100644 --- a/daq_macros.py +++ b/daq_macros.py @@ -4512,6 +4512,12 @@ def queueCollectOff(): """queueCollectOff() : do not allow creating requests for samples that are not mounted""" setBlConfig("queueCollect",0) +def unmountColdOn(): + setBlConfig(UNMOUNT_COLD_CHECK, 1) + +def unmountColdOff(): + setBlConfig(UNMOUNT_COLD_CHECK, 0) + def guiLocal(): #monitor omega RBV """guiLocal() : show the readback of the Omega motor as it's moving. Can lead to lags when operating remotely with reduced bandwidth.""" setBlConfig("omegaMonitorPV","RBV") From 37693b5601f25ab432a8709816c8298e0e871cd9 Mon Sep 17 00:00:00 2001 From: vshekar1 Date: Wed, 31 Dec 2025 14:43:07 -0500 Subject: [PATCH 06/12] Using cam url and attaching resolution --- gui/control_main.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/gui/control_main.py b/gui/control_main.py index a665dae2..e8d97458 100644 --- a/gui/control_main.py +++ b/gui/control_main.py @@ -1576,7 +1576,7 @@ def createSampleTab(self): self.hutchCornerCamThread = VideoThread( - parent=self, delay=HUTCH_TIMER_DELAY, url=getBlConfig("hutchCornerCamURL") + parent=self, delay=HUTCH_TIMER_DELAY, url=getBlConfig("hutchCornerCamURL") + "?resolution=320x180" ) self.hutchCornerCamThread.frame_ready.connect( lambda frame: self.updateCam(self.pixmap_item_HutchCorner, frame) @@ -1584,7 +1584,7 @@ def createSampleTab(self): self.hutchCornerCamThread.start() self.hutchTopCamThread = VideoThread( - parent=self, delay=HUTCH_TIMER_DELAY, url=getBlConfig("hutchTopCamURL") + parent=self, delay=HUTCH_TIMER_DELAY, url=getBlConfig("hutchTopCamURL") + "?resolution=320x180" ) self.hutchTopCamThread.frame_ready.connect( lambda frame: self.updateCam(self.pixmap_item_HutchTop, frame) @@ -1618,10 +1618,10 @@ def manage_gov_state_change(self, state: str): # Currently changes what camera angle is shown in the center if state in ("state SE", "transition SA to SE"): print(f"Govstate: {state}") - self.sampleCameraThread.updateCam("http://xf17id1b-webcam1.nsls2.bnl.local/axis-cgi/mjpg/video.cgi?resolution=640x360") + self.sampleCameraThread.updateCam(getBlConfig("hutchCornerCamURL") + "?resolution=640x360") elif state in ("transition SE to TA"): print(f"Govstate: {state}") - self.sampleCameraThread.updateCam("http://xf17id1b-webcam4.nsls2.bnl.local/axis-cgi/mjpg/video.cgi?resolution=640x360") + self.sampleCameraThread.updateCam(getBlConfig("hutchTopCamURL") + "?resolution=640x360") elif state in ("state TA"): print(f"Govstate: {state}") self.sampleCameraThread.updateCam(self.capture) From bc5cc9962ee369dfadbe69706cd430f4bc407f96 Mon Sep 17 00:00:00 2001 From: Shekar V Date: Wed, 31 Dec 2025 14:53:20 -0500 Subject: [PATCH 07/12] Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- gui/control_main.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/gui/control_main.py b/gui/control_main.py index e8d97458..e9f04c27 100644 --- a/gui/control_main.py +++ b/gui/control_main.py @@ -1617,16 +1617,16 @@ def manage_gov_state_change(self, state: str): # This function reacts to changes in governor state # Currently changes what camera angle is shown in the center if state in ("state SE", "transition SA to SE"): - print(f"Govstate: {state}") - self.sampleCameraThread.updateCam(getBlConfig("hutchCornerCamURL") + "?resolution=640x360") + logger.info("Govstate: %s", state) + self.sampleCameraThread.updateCam("http://xf17id1b-webcam1.nsls2.bnl.local/axis-cgi/mjpg/video.cgi?resolution=640x360") elif state in ("transition SE to TA"): - print(f"Govstate: {state}") - self.sampleCameraThread.updateCam(getBlConfig("hutchTopCamURL") + "?resolution=640x360") + logger.info("Govstate: %s", state) + self.sampleCameraThread.updateCam("http://xf17id1b-webcam4.nsls2.bnl.local/axis-cgi/mjpg/video.cgi?resolution=640x360") elif state in ("state TA"): - print(f"Govstate: {state}") + logger.info("Govstate: %s", state) self.sampleCameraThread.updateCam(self.capture) elif state in ("state SA"): - print(f"Govstate: {state}") + logger.info("Govstate: %s", state) self.sampleCameraThread.updateCam(self.capture) From 18e0b2988ef2f261e785c6e043ff75370e2abb41 Mon Sep 17 00:00:00 2001 From: Shekar V Date: Wed, 31 Dec 2025 14:53:47 -0500 Subject: [PATCH 08/12] Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- daq_lib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daq_lib.py b/daq_lib.py index 2e42987d..fb17107b 100644 --- a/daq_lib.py +++ b/daq_lib.py @@ -242,7 +242,7 @@ def floco_stop_operations(): daq_macros.run_recovery_procedure(stop=True) lockGUI() except Exception as e: - logger.exception("Error encountered while running flocoStopOperations. Stopping...") + logger.exception("Error encountered while running floco_stop_operations. Stopping...") def floco_continue_operations(): try: From d565f23ca7b9591ea9323f4ce280168b635b5479 Mon Sep 17 00:00:00 2001 From: Shekar V Date: Wed, 31 Dec 2025 14:54:07 -0500 Subject: [PATCH 09/12] Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- daq_macros.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daq_macros.py b/daq_macros.py index 86358209..210e3b55 100644 --- a/daq_macros.py +++ b/daq_macros.py @@ -300,7 +300,7 @@ def recoverCS8(): def run_recovery_procedure(stop=True): """ - Manual recovery procedure used in daq_lib.flocoStopOperations and daq_lib.flocoContinueOperations + Manual recovery procedure used in daq_lib.floco_stop_operations and daq_lib.floco_continue_operations """ RESET = "\033[0m" BOLD = "\033[1m" From a13efbebc069c478cf69d3f502895db78a7f8243 Mon Sep 17 00:00:00 2001 From: Shekar V Date: Wed, 31 Dec 2025 15:22:42 -0500 Subject: [PATCH 10/12] Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- daq_macros.py | 4 ++-- gui/control_main.py | 5 +++++ gui/dialog/staff_screen.py | 2 +- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/daq_macros.py b/daq_macros.py index 210e3b55..b0dc2524 100644 --- a/daq_macros.py +++ b/daq_macros.py @@ -4398,7 +4398,7 @@ def robotOn(): def robotOff(): """robotOff() : fake mounting samples""" setBlConfig("robot_online",0) - daq_lib.gui_message(json.dumps({"robot_online": True})) + daq_lib.gui_message(json.dumps({"robot_online": False})) def zebraVecDaqSetup(angle_start,imgWidth,exposurePeriodPerImage,numImages,filePrefix,data_directory_name,file_number_start,scanEncoder=3): #scan encoder 0=x, 1=y,2=z,3=omega @@ -4578,7 +4578,7 @@ def backoffDetector(): def disableMount(): """disableMount() : turn off robot mounting. Usually done in an error situation where we want staff intervention before resuming.""" setBlConfig("mountEnabled",0) - daq_lib.gui_message(json.dumps({"enable_mount": True})) + daq_lib.gui_message(json.dumps({"enable_mount": False})) def enableMount(): """enableMount() : allow robot mounting""" diff --git a/gui/control_main.py b/gui/control_main.py index e9f04c27..477b13ac 100644 --- a/gui/control_main.py +++ b/gui/control_main.py @@ -5527,6 +5527,11 @@ def processServerCommand(self, command: "dict[str, Any]"): if "queue_collect" in command: self.staffScreenDialog.queueCollectOnCheckBox.setChecked(command["queue_collect"]) self.userScreenDialog.queueCollectOnCheckBox.setChecked(command["queue_collect"]) + # Update status widget and unmount-cold control based on queue_collect state + self.queue_collect_status_widget.setText( + f"Queue Collect: {'ON' if command['queue_collect'] else 'OFF'}" + ) + self.staffScreenDialog.gripperUnmountColdCheckBox.setEnabled(command["queue_collect"]) if "unmount_cold" in command: self.staffScreenDialog.gripperUnmountColdCheckBox.setChecked(command["unmount_cold"]) diff --git a/gui/dialog/staff_screen.py b/gui/dialog/staff_screen.py index 10e73e88..4982d3d3 100644 --- a/gui/dialog/staff_screen.py +++ b/gui/dialog/staff_screen.py @@ -85,7 +85,7 @@ def __init__(self, parent: "ControlMain", **kwargs): self.guiRemoteOnCheckBox = QCheckBox("GUI Remote") hBoxColParams1.addWidget(self.guiRemoteOnCheckBox) self.guiRemoteOnCheckBox.setChecked(True if getBlConfig("omegaMonitorPV") == "VAL" else False) - self.guiRemoteOnCheckBox.stateChanged.connect(self.guiRemoteOnCheckCB) + self.guiRemoteOnCheckBox.clicked.connect(self.guiRemoteOnCheckCB) self.albulaDispCheckBox = QCheckBox("Display Data (Albula)") self.albulaDispCheckBox.setChecked(True) hBoxColParams1.addWidget(self.albulaDispCheckBox) From 2dcd643b52c39bc8f8182dcf45d01337897e7328 Mon Sep 17 00:00:00 2001 From: vshekar1 Date: Wed, 31 Dec 2025 15:25:20 -0500 Subject: [PATCH 11/12] Added message to gui when unmount cold is toggled --- daq_macros.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/daq_macros.py b/daq_macros.py index b0dc2524..eeeb42bc 100644 --- a/daq_macros.py +++ b/daq_macros.py @@ -4514,9 +4514,11 @@ def queueCollectOff(): def unmountColdOn(): setBlConfig(UNMOUNT_COLD_CHECK, 1) + daq_lib.gui_message(json.dumps({"unmount_cold": True})) def unmountColdOff(): setBlConfig(UNMOUNT_COLD_CHECK, 0) + daq_lib.gui_message(json.dumps({"unmount_cold": False})) def guiLocal(): #monitor omega RBV """guiLocal() : show the readback of the Omega motor as it's moving. Can lead to lags when operating remotely with reduced bandwidth.""" From 8ccbf86f49e5fc6feec8216afe7c564950ee2b35 Mon Sep 17 00:00:00 2001 From: Shekar V Date: Wed, 31 Dec 2025 15:31:54 -0500 Subject: [PATCH 12/12] Made recoverRobot and dryGripper compact --- embl_robot.py | 46 ++++++++++++++++++---------------------------- 1 file changed, 18 insertions(+), 28 deletions(-) diff --git a/embl_robot.py b/embl_robot.py index 4d513265..7c5a1205 100644 --- a/embl_robot.py +++ b/embl_robot.py @@ -66,7 +66,7 @@ def warmupGripperRecoverThread(self, savedThreshold,junk): setPvDesc("warmupThreshold",savedThreshold) - def recoverRobot(self): + def _recoverRobot(self, raise_on_error=False): try: self.rebootEMBL() time.sleep(8.0) @@ -74,31 +74,25 @@ def recoverRobot(self): if bLoaded: daq_macros.robotOff() daq_macros.disableMount() - daq_lib.gui_message("FATAL ROBOT ERROR Found a sample in the gripper - CALL STAFF! disableMount() and robotOff() executed.") + message = "FATAL ROBOT ERROR Found a sample in the gripper - CALL STAFF! disableMount() and robotOff() executed." + daq_lib.gui_message(message) + if raise_on_error: + raise Exception(message) else: RobotControlLib.runCmd("goHome") except Exception as e: e_s = str(e) daq_lib.gui_message("ROBOT Recover failed! " + e_s) + if raise_on_error: + raise + + def recoverRobot(self): + self._recoverRobot() def recoverRobotFloco(self): - try: - self.rebootEMBL() - time.sleep(8.0) - _,bLoaded,_ = RobotControlLib.recover() - if bLoaded: - daq_macros.robotOff() - daq_macros.disableMount() - daq_lib.gui_message("FATAL ROBOT ERROR Found a sample in the gripper - CALL STAFF! disableMount() and robotOff() executed.") - raise Exception("FATAL ROBOT ERROR Found a sample in the gripper - CALL STAFF! disableMount() and robotOff() executed.") - else: - RobotControlLib.runCmd("goHome") - except Exception as e: - e_s = str(e) - daq_lib.gui_message("ROBOT Recover failed! " + e_s) - raise + self._recoverRobot(raise_on_error=True) - def dryGripper(self): + def _dryGripper(self, raise_on_error=False): try: saveThreshold = getPvDesc("warmupThresholdRBV") setPvDesc("warmupThreshold",50) @@ -108,18 +102,14 @@ def dryGripper(self): e_s = str(e) daq_lib.gui_message("Dry gripper failed! " + e_s) setPvDesc("warmupThreshold",saveThreshold) + if raise_on_error: + raise + + def dryGripper(self): + self._dryGripper() def dryGripperFloco(self): - try: - saveThreshold = getPvDesc("warmupThresholdRBV") - setPvDesc("warmupThreshold",50) - _thread.start_new_thread(self.warmupGripperRecoverThread,(saveThreshold,0)) - self.warmupGripperForDry() - except Exception as e: - e_s = str(e) - daq_lib.gui_message("Dry gripper failed! " + e_s) - setPvDesc("warmupThreshold",saveThreshold) - raise + self._dryGripper(raise_on_error=True) def DewarAutoFillOn(self): RobotControlLib.runCmd("turnOnAutoFill")