From 1bbf392fb935f69a1c9341b04fa7379d51e1b569 Mon Sep 17 00:00:00 2001 From: Shekar V Date: Mon, 15 Jul 2024 16:46:42 -0400 Subject: [PATCH 1/2] Added mounting status indicator with Enum --- config_params.py | 20 +++++++++++++- daq_lib.py | 65 ++++++++++++++++++++++++++++++--------------- gui/control_main.py | 18 ++++++------- gui/dewar_tree.py | 17 ++++++++---- 4 files changed, 84 insertions(+), 36 deletions(-) diff --git a/config_params.py b/config_params.py index 8b2a9181..12b8f505 100644 --- a/config_params.py +++ b/config_params.py @@ -135,4 +135,22 @@ class OnMountAvailOptions(Enum): BEAMSIZE_OPTIONS = { "S": ["V0", "H0"], "L": ["V1", "H1"] -} \ No newline at end of file +} + +class MountState(Enum): + CURRENTLY_MOUNTING = "cm" + FAILED_MOUNTING = "fm" + MOUNTED = "m" + CURRENTLY_UNMOUNTING = "cu" + FAILED_UNMOUNTING = "fu" + + @classmethod + def get_text(cls, enum_value): + text_values = { + cls.CURRENTLY_MOUNTING: "\n(Currently Mounting)", + cls.FAILED_MOUNTING: "\n(Failed Mounting)", + cls.MOUNTED: "\n(Mounted)", + cls.CURRENTLY_UNMOUNTING: "\n(Currently Unmounting)", + cls.FAILED_UNMOUNTING: "\n(Failed Unmounting)" + } + return text_values.get(enum_value, "\n(Unknown State)") \ No newline at end of file diff --git a/daq_lib.py b/daq_lib.py index eb95b72b..cd107448 100644 --- a/daq_lib.py +++ b/daq_lib.py @@ -261,19 +261,21 @@ def mountSample(sampID): if (sampID!=currentMountedSampleID and not robot_lib.multiSampleGripper()): puckPos = mountedSampleDict["puckPos"] pinPos = mountedSampleDict["pinPos"] + # Set status as currently unmounting + set_mounted_pin_data(currentMountedSampleID, mount_state=MountState.CURRENTLY_UNMOUNTING.value) if robot_lib.unmountRobotSample(gov_robot, puckPos,pinPos,currentMountedSampleID): db_lib.deleteCompletedRequestsforSample(currentMountedSampleID) - set_field("mounted_pin","") - db_lib.beamlineInfo(daq_utils.beamline, 'mountedSample', info_dict={'puckPos':0,'pinPos':0,'sampleID':""}) (puckPos,pinPos,puckID) = db_lib.getCoordsfromSampleID(daq_utils.beamline,sampID) if (warmUpNeeded): gui_message("Warming gripper. Please stand by.") mountCounter = 0 + # About to mount next sample + set_mounted_pin_data(sampID, mount_state=MountState.CURRENTLY_MOUNTING.value, sample_pos={'puckPos':puckPos,'pinPos':pinPos}) mountStat = robot_lib.mountRobotSample(gov_robot, puckPos,pinPos,sampID,init=0,warmup=warmUpNeeded) if (warmUpNeeded): destroy_gui_message() - if (mountStat == 1): - set_field("mounted_pin",sampID) + if (mountStat == MOUNT_SUCCESSFUL): + set_mounted_pin_data(sampID, sample_pos={'puckPos':puckPos,'pinPos':pinPos}) detDist = beamline_lib.motorPosFromDescriptor("detectorDist") if (detDist != saveDetDist): if (getBlConfig("HePath") == 0): @@ -282,29 +284,34 @@ def mountSample(sampID): # Only run mount options when the robot is online and queue collect is off daq_macros.run_on_mount_option(sampID) gov_status = gov_lib.setGovRobot(gov_robot, 'SA') - elif(mountStat == 2): - return 2 + elif(mountStat == MOUNT_UNRECOVERABLE_ERROR): + clearMountedSample() + return MOUNT_UNRECOVERABLE_ERROR else: - return 0 + clearMountedSample() + return MOUNT_FAILURE else: - return 0 + set_mounted_pin_data(currentMountedSampleID, mount_state=MountState.FAILED_UNMOUNTING.value) + return UNMOUNT_FAILURE else: #desired sample is mounted, nothing to do - return 1 + return MOUNT_SUCCESSFUL else: #nothing mounted (puckPos,pinPos,puckID) = db_lib.getCoordsfromSampleID(daq_utils.beamline,sampID) + set_mounted_pin_data(sampID, mount_state=MountState.CURRENTLY_MOUNTING.value, sample_pos={'puckPos':puckPos,'pinPos':pinPos}) mountStat = robot_lib.mountRobotSample(gov_robot, puckPos,pinPos,sampID,init=1) - if (mountStat == 1): - set_field("mounted_pin",sampID) + if (mountStat == MOUNT_SUCCESSFUL): + set_mounted_pin_data(sampID, sample_pos={'puckPos':puckPos,'pinPos':pinPos}) if getBlConfig('robot_online') and getBlConfig("queueCollect") == 0: # Only run mount options when the robot is online and queue collect is off daq_macros.run_on_mount_option(sampID) gov_status = gov_lib.setGovRobot(gov_robot, 'SA') - elif(mountStat == 2): - return 2 + elif(mountStat == MOUNT_UNRECOVERABLE_ERROR): + clearMountedSample() + return MOUNT_UNRECOVERABLE_ERROR else: - return 0 - db_lib.beamlineInfo(daq_utils.beamline, 'mountedSample', info_dict={'puckPos':puckPos,'pinPos':pinPos,'sampleID':sampID}) - return 1 + clearMountedSample() + return MOUNT_FAILURE + return MOUNT_SUCCESSFUL def clearMountedSample(): @@ -312,6 +319,17 @@ def clearMountedSample(): db_lib.beamlineInfo(daq_utils.beamline, 'mountedSample', info_dict={'puckPos':0,'pinPos':0,'sampleID':""}) +def set_mounted_pin_data(sample_id, mount_state=MountState.MOUNTED.value, sample_pos=None): + current_pin_data = f"{sample_id},{mount_state}" + logger.info(f"current pin data = {current_pin_data}") + set_field("mounted_pin", current_pin_data) + + if sample_pos: + info_dict = { 'puckPos' : sample_pos["puckPos"], + 'pinPos' : sample_pos["pinPos"], + 'sampleID': sample_id } + db_lib.beamlineInfo(daq_utils.beamline, 'mountedSample', info_dict=info_dict) + def unmountSample(): global mountCounter @@ -322,13 +340,15 @@ def unmountSample(): if (currentMountedSampleID != ""): puckPos = mountedSampleDict["puckPos"] pinPos = mountedSampleDict["pinPos"] + # Set status as currently unmounting + set_mounted_pin_data(currentMountedSampleID, mount_state=MountState.CURRENTLY_UNMOUNTING.value) if robot_lib.unmountRobotSample(gov_robot, puckPos,pinPos,currentMountedSampleID): db_lib.deleteCompletedRequestsforSample(currentMountedSampleID) robot_lib.finish() - set_field("mounted_pin","") - db_lib.beamlineInfo(daq_utils.beamline, 'mountedSample', info_dict={'puckPos':0,'pinPos':0,'sampleID':""}) + clearMountedSample() return 1 else: + set_mounted_pin_data(currentMountedSampleID, mount_state=MountState.FAILED_UNMOUNTING.value) return 0 def unmountCold(): @@ -337,14 +357,17 @@ def unmountCold(): if (currentMountedSampleID != ""): puckPos = mountedSampleDict["puckPos"] pinPos = mountedSampleDict["pinPos"] + # Set status as currently unmounting + set_mounted_pin_data(currentMountedSampleID, mount_state=MountState.CURRENTLY_UNMOUNTING.value) if robot_lib.unmountRobotSample(gov_robot, puckPos,pinPos,currentMountedSampleID): db_lib.deleteCompletedRequestsforSample(currentMountedSampleID) - robot_lib.parkGripper() - set_field("mounted_pin","") - db_lib.beamlineInfo(daq_utils.beamline, 'mountedSample', info_dict={'puckPos':0,'pinPos':0,'sampleID':""}) + if getBlConfig("robot_online"): + robot_lib.parkGripper() + clearMountedSample() setPvDesc("robotGovActive",1) return 1 else: + set_mounted_pin_data(currentMountedSampleID, mount_state=MountState.FAILED_UNMOUNTING.value) return 0 def waitBeam(): diff --git a/gui/control_main.py b/gui/control_main.py index 56f6a4f9..9ad89a77 100644 --- a/gui/control_main.py +++ b/gui/control_main.py @@ -1744,7 +1744,7 @@ def saveVidSnapshotCB( beamline=daq_utils.beamline, ) else: # the user pushed the snapshot button on the gui - mountedSampleID = self.mountedPin_pv.get() + mountedSampleID = self.mountedPin_pv.get().split(",")[0] if mountedSampleID != "": db_lib.addResulttoSample( "snapshotResult", @@ -3783,7 +3783,7 @@ def addRequestsToAllSelectedCB(self): self.addSampleRequestCB(selectedSampleID=self.selectedSampleID) samplesConsidered.add(self.selectedSampleID) else: # If queue collect is off does not matter how many requests you select only one will be added to current pin - self.selectedSampleID = self.mountedPin_pv.get() + self.selectedSampleID = self.mountedPin_pv.get().split(",")[0] self.selectedSampleRequest = daq_utils.createDefaultRequest( self.selectedSampleID ) @@ -3818,8 +3818,8 @@ def addSampleRequestCB(self, rasterDef=None, selectedSampleID=None): daq_utils.setProposalID(propNum, createVisit=True) if getBlConfig("queueCollect") == 0: - if self.mountedPin_pv.get() != self.selectedSampleID: - self.selectedSampleID = self.mountedPin_pv.get() + if self.mountedPin_pv.get().split(",")[0] != self.selectedSampleID: + self.selectedSampleID = self.mountedPin_pv.get().split(",")[0] if not self.validateAllFields(): return @@ -3909,7 +3909,7 @@ def addSampleRequestCB(self, rasterDef=None, selectedSampleID=None): return # I don't like the code duplication, but one case is the mounted sample and selected centerings - so it's in a loop for multiple reqs, the other requires autocenter. - if (self.mountedPin_pv.get() == self.selectedSampleID) and ( + if (self.mountedPin_pv.get().split(",")[0] == self.selectedSampleID) and ( len(self.centeringMarksList) != 0 ): selectedCenteringFound = 0 @@ -4049,7 +4049,7 @@ def addSampleRequestCB(self, rasterDef=None, selectedSampleID=None): reqObj["centeringOption"] = centeringOption if ( centeringOption == "Interactive" - and self.mountedPin_pv.get() == self.selectedSampleID + and self.mountedPin_pv.get().split(",")[0] == self.selectedSampleID ) or centeringOption == "Testing": # user centered manually reqObj["pos_x"] = float(self.sampx_pv.get()) reqObj["pos_y"] = float(self.sampy_pv.get()) @@ -4596,7 +4596,7 @@ def refreshCollectionParams(self, selectedSampleRequest, validate_hdf5=True): str(self.govStateMessagePV.get(as_string=True)) == "state SA" and self.controlEnabled() # Move only in SA (Any other way for GUI to detect governor state?) and self.selectedSampleRequest["sample"] # with control enabled - == self.mountedPin_pv.get() + == self.mountedPin_pv.get().split(",")[0] ): # And the sample of the selected request is mounted self.processSampMove(self.sampx_pv.get(), "x") self.processSampMove(self.sampy_pv.get(), "y") @@ -4683,7 +4683,7 @@ def row_clicked( sample_name = db_lib.getSampleNamebyID(self.selectedSampleID) logger.info("sample in pos " + str(itemData)) if ( - sample["uid"] != self.mountedPin_pv.get() + sample["uid"] != self.mountedPin_pv.get().split(",")[0] and getBlConfig("queueCollect") == 0 ): # Don't fill data paths if an unmounted sample is clicked and queue collect is off return @@ -4730,7 +4730,7 @@ def row_clicked( self.selectedSampleID = self.selectedSampleRequest["sample"] sample = db_lib.getSampleByID(self.selectedSampleID) if ( - sample["uid"] != self.mountedPin_pv.get() + sample["uid"] != self.mountedPin_pv.get().split(",")[0] and getBlConfig("queueCollect") == 0 ): # Don't fill request data if unmounted sample and queuecollect is off return diff --git a/gui/dewar_tree.py b/gui/dewar_tree.py index 6efd1760..1f60bad3 100644 --- a/gui/dewar_tree.py +++ b/gui/dewar_tree.py @@ -14,6 +14,7 @@ IS_STAFF, PUCKS_PER_DEWAR_SECTOR, SAMPLE_TIMER_DELAY, + MountState ) if typing.TYPE_CHECKING: @@ -136,7 +137,7 @@ def keyPressEvent(self, event): def refreshTree(self): self.parent.dewarViewToggleCheckCB() - def set_mounted_sample(self, item): + def set_mounted_sample(self, item, sample_name=None): # Formats the text of the item that is passed in as the mounted sample item.setForeground(QtGui.QColor("red")) font = QtGui.QFont() @@ -144,6 +145,12 @@ def set_mounted_sample(self, item): font.setItalic(True) font.setOverline(True) item.setFont(font) + if len(self.parent.mountedPin_pv.get().split(",")) == 2: + pin, state = self.parent.mountedPin_pv.get().split(",") + mount_state = MountState(state) + if sample_name is None: + sample_name = item.text() + item.setText(sample_name + MountState.get_text(mount_state)) def refreshTreeDewarView(self, get_latest_pucks=False): puck = "" @@ -229,10 +236,11 @@ def add_samples_to_puck_tree( # just stuck sampleID there, but negate it to diff from reqID item.setData(sample_id, 32) item.setData("sample", 33) - if sample_id == self.parent.mountedPin_pv.get(): - self.set_mounted_sample(item) + if sample_id == self.parent.mountedPin_pv.get().split(",")[0]: + self.set_mounted_sample(item, position_s) parentItem.appendRow(item) - if sample_id == self.parent.mountedPin_pv.get(): + + if sample_id == self.parent.mountedPin_pv.get().split(",")[0]: mountedIndex = self.model.indexFromItem(item) # looking for the selected item if sample_id == self.parent.selectedSampleID: @@ -261,7 +269,6 @@ def add_samples_to_puck_tree( elif mountedIndex: current_index = mountedIndex item = self.model.itemFromIndex(mountedIndex) - self.set_mounted_sample(item) elif selectedIndex: current_index = selectedIndex elif collectionRunning and mountedIndex: From 2ec7892f01d2fb2dd0dd55e5ee57c19590a67fb9 Mon Sep 17 00:00:00 2001 From: Shekar V Date: Wed, 31 Jul 2024 15:52:33 -0400 Subject: [PATCH 2/2] Custom class for MountedPinPV so that a lot of the code does not need to change --- gui/control_main.py | 22 +++++++++++----------- gui/dewar_tree.py | 8 ++++---- utils/custom_pv.py | 14 ++++++++++++++ 3 files changed, 29 insertions(+), 15 deletions(-) create mode 100644 utils/custom_pv.py diff --git a/gui/control_main.py b/gui/control_main.py index 9ad89a77..272dedf4 100644 --- a/gui/control_main.py +++ b/gui/control_main.py @@ -58,7 +58,7 @@ from gui.raster import RasterCell, RasterGroup from QPeriodicTable import QPeriodicTable from threads import RaddoseThread, ServerCheckThread, VideoThread -from utils import validation +from utils import validation, custom_pv logger = logging.getLogger() @@ -1744,7 +1744,7 @@ def saveVidSnapshotCB( beamline=daq_utils.beamline, ) else: # the user pushed the snapshot button on the gui - mountedSampleID = self.mountedPin_pv.get().split(",")[0] + mountedSampleID = self.mountedPin_pv.get() if mountedSampleID != "": db_lib.addResulttoSample( "snapshotResult", @@ -3783,7 +3783,7 @@ def addRequestsToAllSelectedCB(self): self.addSampleRequestCB(selectedSampleID=self.selectedSampleID) samplesConsidered.add(self.selectedSampleID) else: # If queue collect is off does not matter how many requests you select only one will be added to current pin - self.selectedSampleID = self.mountedPin_pv.get().split(",")[0] + self.selectedSampleID = self.mountedPin_pv.get() self.selectedSampleRequest = daq_utils.createDefaultRequest( self.selectedSampleID ) @@ -3818,8 +3818,8 @@ def addSampleRequestCB(self, rasterDef=None, selectedSampleID=None): daq_utils.setProposalID(propNum, createVisit=True) if getBlConfig("queueCollect") == 0: - if self.mountedPin_pv.get().split(",")[0] != self.selectedSampleID: - self.selectedSampleID = self.mountedPin_pv.get().split(",")[0] + if self.mountedPin_pv.get() != self.selectedSampleID: + self.selectedSampleID = self.mountedPin_pv.get() if not self.validateAllFields(): return @@ -3909,7 +3909,7 @@ def addSampleRequestCB(self, rasterDef=None, selectedSampleID=None): return # I don't like the code duplication, but one case is the mounted sample and selected centerings - so it's in a loop for multiple reqs, the other requires autocenter. - if (self.mountedPin_pv.get().split(",")[0] == self.selectedSampleID) and ( + if (self.mountedPin_pv.get() == self.selectedSampleID) and ( len(self.centeringMarksList) != 0 ): selectedCenteringFound = 0 @@ -4049,7 +4049,7 @@ def addSampleRequestCB(self, rasterDef=None, selectedSampleID=None): reqObj["centeringOption"] = centeringOption if ( centeringOption == "Interactive" - and self.mountedPin_pv.get().split(",")[0] == self.selectedSampleID + and self.mountedPin_pv.get() == self.selectedSampleID ) or centeringOption == "Testing": # user centered manually reqObj["pos_x"] = float(self.sampx_pv.get()) reqObj["pos_y"] = float(self.sampy_pv.get()) @@ -4596,7 +4596,7 @@ def refreshCollectionParams(self, selectedSampleRequest, validate_hdf5=True): str(self.govStateMessagePV.get(as_string=True)) == "state SA" and self.controlEnabled() # Move only in SA (Any other way for GUI to detect governor state?) and self.selectedSampleRequest["sample"] # with control enabled - == self.mountedPin_pv.get().split(",")[0] + == self.mountedPin_pv.get() ): # And the sample of the selected request is mounted self.processSampMove(self.sampx_pv.get(), "x") self.processSampMove(self.sampy_pv.get(), "y") @@ -4683,7 +4683,7 @@ def row_clicked( sample_name = db_lib.getSampleNamebyID(self.selectedSampleID) logger.info("sample in pos " + str(itemData)) if ( - sample["uid"] != self.mountedPin_pv.get().split(",")[0] + sample["uid"] != self.mountedPin_pv.get() and getBlConfig("queueCollect") == 0 ): # Don't fill data paths if an unmounted sample is clicked and queue collect is off return @@ -4730,7 +4730,7 @@ def row_clicked( self.selectedSampleID = self.selectedSampleRequest["sample"] sample = db_lib.getSampleByID(self.selectedSampleID) if ( - sample["uid"] != self.mountedPin_pv.get().split(",")[0] + sample["uid"] != self.mountedPin_pv.get() and getBlConfig("queueCollect") == 0 ): # Don't fill request data if unmounted sample and queuecollect is off return @@ -5021,7 +5021,7 @@ def initCallbacks(self): self.treeChanged_pv = PV(daq_utils.beamlineComm + "live_q_change_flag") self.refreshTreeSignal.connect(self.dewarTree.refreshTree) self.treeChanged_pv.add_callback(self.treeChangedCB) - self.mountedPin_pv = PV(daq_utils.beamlineComm + "mounted_pin") + self.mountedPin_pv = custom_pv.MountedPinPV(daq_utils.beamlineComm + "mounted_pin") self.mountedPinSignal.connect(self.processMountedPin) self.mountedPin_pv.add_callback(self.mountedPinChangedCB) det_stop_pv = daq_utils.pvLookupDict["stopEiger"] diff --git a/gui/dewar_tree.py b/gui/dewar_tree.py index 1f60bad3..bbdb9b55 100644 --- a/gui/dewar_tree.py +++ b/gui/dewar_tree.py @@ -145,8 +145,8 @@ def set_mounted_sample(self, item, sample_name=None): font.setItalic(True) font.setOverline(True) item.setFont(font) - if len(self.parent.mountedPin_pv.get().split(",")) == 2: - pin, state = self.parent.mountedPin_pv.get().split(",") + if self.parent.mountedPin_pv.get_pin_state() is not None: + state = self.parent.mountedPin_pv.get_pin_state() mount_state = MountState(state) if sample_name is None: sample_name = item.text() @@ -236,11 +236,11 @@ def add_samples_to_puck_tree( # just stuck sampleID there, but negate it to diff from reqID item.setData(sample_id, 32) item.setData("sample", 33) - if sample_id == self.parent.mountedPin_pv.get().split(",")[0]: + if sample_id == self.parent.mountedPin_pv.get(): self.set_mounted_sample(item, position_s) parentItem.appendRow(item) - if sample_id == self.parent.mountedPin_pv.get().split(",")[0]: + if sample_id == self.parent.mountedPin_pv.get(): mountedIndex = self.model.indexFromItem(item) # looking for the selected item if sample_id == self.parent.selectedSampleID: diff --git a/utils/custom_pv.py b/utils/custom_pv.py new file mode 100644 index 00000000..a7330a5b --- /dev/null +++ b/utils/custom_pv.py @@ -0,0 +1,14 @@ +from epics import PV + + +class MountedPinPV(PV): + + def get(self, *args, **kwargs): + value = str(super().get(*args, **kwargs)) + return value.split(",")[0] + + def get_pin_state(self): + value = str(super().get()).split(",") + if len(value) == 2: + return value[1] + return None