From 1fa0d18f7f2dca91f2732b59c6a79841ea9fc5ee Mon Sep 17 00:00:00 2001 From: David Kalish Date: Tue, 3 Sep 2024 14:35:05 -0400 Subject: [PATCH] queryPump directly in SEC_GUI\ code, threaded pump action commands todo: add this to WASH_GUI code. Threaded the 4 Adjustments buttons, like Dispense Identified that issues #31 and #32 exist --- SEC_GUI/mainwindow.py | 26 +++----- SEC_GUI/mainwindow.ui | 28 ++++----- SEC_GUI/octasome_gui.py | 131 +++++++++++++++++++++++----------------- 3 files changed, 93 insertions(+), 92 deletions(-) diff --git a/SEC_GUI/mainwindow.py b/SEC_GUI/mainwindow.py index 099040a..131d75b 100644 --- a/SEC_GUI/mainwindow.py +++ b/SEC_GUI/mainwindow.py @@ -2,7 +2,7 @@ # Form implementation generated from reading ui file 'mainwindow.ui' # -# Created by: PyQt5 UI code generator 5.15.9 +# Created by: PyQt5 UI code generator 5.15.11 # # WARNING: Any manual changes made to this file will be lost when pyuic5 is # run again. Do not edit this file unless you know what you are doing. @@ -80,7 +80,6 @@ def setupUi(self, MainWindow): self.columnSelectBox.setGeometry(QtCore.QRect(10, 90, 441, 261)) font = QtGui.QFont() font.setBold(False) - font.setWeight(50) self.columnSelectBox.setFont(font) self.columnSelectBox.setObjectName("columnSelectBox") self.layoutWidget2 = QtWidgets.QWidget(self.columnSelectBox) @@ -96,7 +95,6 @@ def setupUi(self, MainWindow): font = QtGui.QFont() font.setPointSize(12) font.setBold(False) - font.setWeight(50) self.allCheckBox.setFont(font) self.allCheckBox.setStyleSheet("QCheckBox::indicator {\n" " width: 40px;\n" @@ -141,7 +139,6 @@ def setupUi(self, MainWindow): font = QtGui.QFont() font.setPointSize(12) font.setBold(False) - font.setWeight(50) self.column1CheckBox.setFont(font) self.column1CheckBox.setStyleSheet("QCheckBox::indicator {\n" " width: 40px;\n" @@ -184,7 +181,6 @@ def setupUi(self, MainWindow): font = QtGui.QFont() font.setPointSize(12) font.setBold(False) - font.setWeight(50) self.column2CheckBox.setFont(font) self.column2CheckBox.setStyleSheet("QCheckBox::indicator {\n" " width: 40px;\n" @@ -227,7 +223,6 @@ def setupUi(self, MainWindow): font = QtGui.QFont() font.setPointSize(12) font.setBold(False) - font.setWeight(50) self.column3CheckBox.setFont(font) self.column3CheckBox.setStyleSheet("QCheckBox::indicator {\n" " width: 40px;\n" @@ -270,7 +265,6 @@ def setupUi(self, MainWindow): font = QtGui.QFont() font.setPointSize(12) font.setBold(False) - font.setWeight(50) self.column4CheckBox.setFont(font) self.column4CheckBox.setStyleSheet("QCheckBox::indicator {\n" " width: 40px;\n" @@ -316,7 +310,6 @@ def setupUi(self, MainWindow): font = QtGui.QFont() font.setPointSize(12) font.setBold(False) - font.setWeight(50) self.column5CheckBox.setFont(font) self.column5CheckBox.setStyleSheet("QCheckBox::indicator {\n" " width: 40px;\n" @@ -359,7 +352,6 @@ def setupUi(self, MainWindow): font = QtGui.QFont() font.setPointSize(12) font.setBold(False) - font.setWeight(50) self.column6CheckBox.setFont(font) self.column6CheckBox.setStyleSheet("QCheckBox::indicator {\n" " width: 40px;\n" @@ -402,7 +394,6 @@ def setupUi(self, MainWindow): font = QtGui.QFont() font.setPointSize(12) font.setBold(False) - font.setWeight(50) self.column7CheckBox.setFont(font) self.column7CheckBox.setStyleSheet("QCheckBox::indicator {\n" " width: 40px;\n" @@ -445,7 +436,6 @@ def setupUi(self, MainWindow): font = QtGui.QFont() font.setPointSize(12) font.setBold(False) - font.setWeight(50) self.column8CheckBox.setFont(font) self.column8CheckBox.setStyleSheet("QCheckBox::indicator {\n" " width: 40px;\n" @@ -503,7 +493,6 @@ def setupUi(self, MainWindow): font = QtGui.QFont() font.setPointSize(14) font.setBold(True) - font.setWeight(75) self.dispenseSpinBox.setFont(font) self.dispenseSpinBox.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) self.dispenseSpinBox.setDecimals(2) @@ -514,7 +503,6 @@ def setupUi(self, MainWindow): font = QtGui.QFont() font.setPointSize(14) font.setBold(True) - font.setWeight(75) self.dispenseUnits.setFont(font) self.dispenseUnits.setObjectName("dispenseUnits") self.horizontalLayout_4.addWidget(self.dispenseUnits) @@ -549,7 +537,6 @@ def setupUi(self, MainWindow): font = QtGui.QFont() font.setPointSize(14) font.setBold(True) - font.setWeight(75) self.drawSpeedSpinBox.setFont(font) self.drawSpeedSpinBox.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) self.drawSpeedSpinBox.setDecimals(0) @@ -560,7 +547,6 @@ def setupUi(self, MainWindow): font = QtGui.QFont() font.setPointSize(14) font.setBold(True) - font.setWeight(75) self.drawSpeedUnits.setFont(font) self.drawSpeedUnits.setObjectName("drawSpeedUnits") self.horizontalLayout_6.addWidget(self.drawSpeedUnits) @@ -580,7 +566,6 @@ def setupUi(self, MainWindow): font = QtGui.QFont() font.setPointSize(14) font.setBold(True) - font.setWeight(75) self.dispenseSpeedSpinBox.setFont(font) self.dispenseSpeedSpinBox.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) self.dispenseSpeedSpinBox.setDecimals(0) @@ -591,7 +576,6 @@ def setupUi(self, MainWindow): font = QtGui.QFont() font.setPointSize(14) font.setBold(True) - font.setWeight(75) self.dispenseSpeedUnits.setFont(font) self.dispenseSpeedUnits.setObjectName("dispenseSpeedUnits") self.horizontalLayout_7.addWidget(self.dispenseSpeedUnits) @@ -623,7 +607,7 @@ def setupUi(self, MainWindow): self.horizontalLayout_8.addLayout(self.verticalLayout) MainWindow.setCentralWidget(self.centralwidget) self.menubar = QtWidgets.QMenuBar(MainWindow) - self.menubar.setGeometry(QtCore.QRect(0, 0, 480, 21)) + self.menubar.setGeometry(QtCore.QRect(0, 0, 480, 19)) self.menubar.setObjectName("menubar") self.menuFile = QtWidgets.QMenu(self.menubar) self.menuFile.setObjectName("menuFile") @@ -636,7 +620,11 @@ def setupUi(self, MainWindow): self.actionConsole = QtWidgets.QAction(MainWindow) self.actionConsole.setEnabled(False) self.actionConsole.setObjectName("actionConsole") + self.actionQueryPump = QtWidgets.QAction(MainWindow) + self.actionQueryPump.setObjectName("actionQueryPump") self.menuFile.addAction(self.actionConsole) + self.menuFile.addAction(self.actionQueryPump) + self.menuFile.addSeparator() self.menuFile.addAction(self.actionQuit) self.menubar.addAction(self.menuFile.menuAction()) @@ -728,6 +716,8 @@ def retranslateUi(self, MainWindow): self.actionQuit.setShortcut(_translate("MainWindow", "Ctrl+Q")) self.actionConsole.setText(_translate("MainWindow", "Console")) self.actionConsole.setStatusTip(_translate("MainWindow", "Open a console for manual serial commands. Connection must be open.")) + self.actionQueryPump.setText(_translate("MainWindow", "queryPump")) + self.actionQueryPump.setShortcut(_translate("MainWindow", "Ctrl+U")) if __name__ == "__main__": diff --git a/SEC_GUI/mainwindow.ui b/SEC_GUI/mainwindow.ui index 18feb68..bb907ee 100644 --- a/SEC_GUI/mainwindow.ui +++ b/SEC_GUI/mainwindow.ui @@ -222,7 +222,6 @@ - 50 false @@ -249,7 +248,6 @@ 12 - 50 false @@ -302,7 +300,6 @@ QCheckBox::indicator:unchecked:disabled 12 - 50 false @@ -353,7 +350,6 @@ QCheckBox::indicator:unchecked:disabled 12 - 50 false @@ -404,7 +400,6 @@ QCheckBox::indicator:unchecked:disabled 12 - 50 false @@ -455,7 +450,6 @@ QCheckBox::indicator:unchecked:disabled 12 - 50 false @@ -510,7 +504,6 @@ QCheckBox::indicator:unchecked:disabled 12 - 50 false @@ -561,7 +554,6 @@ QCheckBox::indicator:unchecked:disabled 12 - 50 false @@ -612,7 +604,6 @@ QCheckBox::indicator:unchecked:disabled 12 - 50 false @@ -663,7 +654,6 @@ QCheckBox::indicator:unchecked:disabled 12 - 50 false @@ -760,7 +750,6 @@ QCheckBox::indicator:unchecked:disabled 14 - 75 true @@ -780,7 +769,6 @@ QCheckBox::indicator:unchecked:disabled 14 - 75 true @@ -847,7 +835,6 @@ QCheckBox::indicator:unchecked:disabled 14 - 75 true @@ -867,7 +854,6 @@ QCheckBox::indicator:unchecked:disabled 14 - 75 true @@ -904,7 +890,6 @@ QCheckBox::indicator:unchecked:disabled 14 - 75 true @@ -924,7 +909,6 @@ QCheckBox::indicator:unchecked:disabled 14 - 75 true @@ -1012,7 +996,7 @@ QCheckBox::indicator:unchecked:disabled 0 0 480 - 21 + 19 @@ -1020,6 +1004,8 @@ QCheckBox::indicator:unchecked:disabled File + + @@ -1047,6 +1033,14 @@ QCheckBox::indicator:unchecked:disabled Open a console for manual serial commands. Connection must be open. + + + queryPump + + + Ctrl+U + + comPortComboBox diff --git a/SEC_GUI/octasome_gui.py b/SEC_GUI/octasome_gui.py index 769a90a..42dcb9e 100644 --- a/SEC_GUI/octasome_gui.py +++ b/SEC_GUI/octasome_gui.py @@ -32,6 +32,8 @@ def __init__(self, debug=False, busy_debug=False, file_name=None): self.file = open(self.file_name, "w") print("File opened: {}".format(self.file.name)) # self.openFile(self.write) + self.thread_id = 0 + self.baud = None # self.max_steps = 6000 # connect GUI signals to methods @@ -44,21 +46,23 @@ def __init__(self, debug=False, busy_debug=False, file_name=None): # initialize pump and valve select motors: self.ui.initializeButton.clicked.connect(self.initializePump) # fill the syringe barrel with fluid: - self.ui.fillButton.clicked.connect(self.fillPump) + self.ui.fillButton.clicked.connect(self.fillPumpThread) # empty the syringe barrel into storage reservoir: - self.ui.emptyButton.clicked.connect(self.emptyPump) + self.ui.emptyButton.clicked.connect(self.emptyPumpThread) # auto-prime fluid lines: - self.ui.primeButton.clicked.connect(self.primeLines) + self.ui.primeButton.clicked.connect(self.primeLinesThread) # Remove all fluid from lines back to reservoir: - self.ui.emptyLinesButton.clicked.connect(self.emptyPumpLines) + self.ui.emptyLinesButton.clicked.connect(self.emptyPumpLinesThread) # dispense specified volumes to specified columns: self.ui.dispenseVolumeButton.clicked.connect(self.dispenseThread) # toggle column checkbox states: self.ui.allCheckBox.stateChanged.connect(self.enableColumnSelect) # send halt command to pump: - self.ui.stopButton.clicked.connect(self.stopPump) + self.ui.stopButton.clicked.connect(self.stopPumpThread) # quit application self.ui.actionQuit.triggered.connect(qApp.quit) + # query pump + self.ui.actionQueryPump.triggered.connect(self.queryPump) # Set tooltips self.setConnectTooltip() @@ -82,14 +86,6 @@ def __init__(self, debug=False, busy_debug=False, file_name=None): self.ui.allCheckBox.setChecked(True) for box in self.dispense_checkboxes: box.setEnabled(False) - # self.ui.column1CheckBox.setEnabled(False) - # self.ui.column2CheckBox.setEnabled(False) - # self.ui.column3CheckBox.setEnabled(False) - # self.ui.column4CheckBox.setEnabled(False) - # self.ui.column5CheckBox.setEnabled(False) - # self.ui.column6CheckBox.setEnabled(False) - # self.ui.column7CheckBox.setEnabled(False) - # self.ui.column8CheckBox.setEnabled(False) # set dispense volume precision (set in mainwindow.*) # self.ui.dispenseSpinBox.setSingleStep(0.1) @@ -166,6 +162,7 @@ def serialConnect(self): self.serial.open(QIODevice.ReadWrite) print("Connection Successful") print("Baud: {}".format(baud)) + self.baud = baud self.serialInfo = QtSerialPort.QSerialPortInfo(portname) print(self.serialInfo.description()) print(self.serialInfo.manufacturer()) @@ -357,6 +354,7 @@ def enableAllBoxes(self): def queryPump(self): """sends query command.. 0 = pump busy executing another command""" response = self.write("/1Q") + print("{}: queryPump response: {}".format(threading.current_thread().name, response)) try: status_bit = bin(response[3] >> 5 &0b1) except: @@ -364,6 +362,12 @@ def queryPump(self): pumpstatus = status_bit return status_bit + def fillPumpThread(self): + thread = threading.Thread(target=self.fillPump) + thread.name = "thread-{:03d}".format(self.thread_id) + self.thread_id += 1 + thread.start() + def fillPump(self): """fills pump by changing to first valve, moving to position 6000 (400steps per second) @@ -371,11 +375,18 @@ def fillPump(self): # check if pump is busy if self.checkBusy(): return + print("{}: fillPump()".format(threading.current_thread().name)) # build command string speed = int(self.ui.drawSpeedSpinBox.value()) # get speed from GUI command_string = "/1I1V" + str(speed) + "A6000" self.write(command_string) + def emptyPumpThread(self): + thread = threading.Thread(target=self.emptyPump) + thread.name = "thread-{:03d}".format(self.thread_id) + self.thread_id += 1 + thread.start() + def emptyPump(self): """empties pump by changing to first valve, moving to position 0 (1000 steps per second) @@ -383,16 +394,24 @@ def emptyPump(self): # check if pump is busy if self.checkBusy(): return + print("{}: emptyPump".format(threading.current_thread().name)) # build command string speed = int(self.ui.dispenseSpeedSpinBox.value()) # get speed from GUI command_string = "/1I1V" + str(speed) + "A0" self.write(command_string) + def primeLinesThread(self): + thread = threading.Thread(target=self.primeLines) + thread.name = "thread-{:03d}".format(self.thread_id) + self.thread_id += 1 + thread.start() + def primeLines(self): """primes lines by dispensing a fixed volume through each channel""" # check if pump is busy if self.checkBusy(): return + print("{}: primeLines".format(threading.current_thread().name)) drawspeed = int(self.ui.drawSpeedSpinBox.value()) # get speed from GUI dispensespeed = int(self.ui.dispenseSpeedSpinBox.value()) # get speed from GUI command_string = "/1I1V" + str(drawspeed) + "A1200" + "I2V" + str(dispensespeed) + "A0I1V" + str(drawspeed) + "A6000" @@ -403,12 +422,19 @@ def primeLines(self): command_string += "I1V" + str(drawspeed)# + "A6000" self.write(command_string) + def emptyLinesThread(self): + thread = threading.Thread(target=self.emptyLines) + thread.name = "thread-{:03d}".format(self.thread_id) + self.thread_id += 1 + thread.start() + def emptyLines(self): """empties lines""" # empty pump # self.emptyPump() # do not build and send command until pump is no longer busy # self._waitReady(1,10,1) + print("{}: emptyLines".format(threading.current_thread().name)) # build command to draw from each line speed = int(self.ui.drawSpeedSpinBox.value()) # get speed from GUI command_string = "/1I1V" + str(speed) @@ -420,6 +446,12 @@ def emptyLines(self): # self.emptyPump() self.write(command_string) + def emptyPumpLinesThread(self): + thread = threading.Thread(target=self.emptyPumpLines) + thread.name = "thread-{:03d}".format(self.thread_id) + self.thread_id += 1 + thread.start() + def emptyPumpLines(self): """empties pumps and lines""" # check if pump is busy @@ -445,6 +477,7 @@ def cleanLines(self): # check if pump is busy if self.checkBusy(): return + print("{}: cleanLines".format(threading.current_thread().name)) drawspeed = int(self.ui.drawSpeedSpinBox.value()) # get speed from GUI dispensespeed = int(self.ui.dispenseSpeedSpinBox.value()) # get speed from GUI command_string = "/1I1V" + str(drawspeed) + "A6000" @@ -455,54 +488,36 @@ def cleanLines(self): command_string += "I1V" + str(drawspeed)# + "A6000" self.write(command_string) - def flushLines(self): - """flushes lines a couple of times...""" - # check if pump is busy - if self.checkBusy(): - return - self.cleanLines() def enableColumnSelect(self): """toggles column checkbox enable states based off of "all" checkbox""" if self.ui.allCheckBox.checkState(): for box in self.dispense_checkboxes: box.setEnabled(False) - # self.ui.column1CheckBox.setEnabled(False) - # self.ui.column2CheckBox.setEnabled(False) - # self.ui.column3CheckBox.setEnabled(False) - # self.ui.column4CheckBox.setEnabled(False) - # self.ui.column5CheckBox.setEnabled(False) - # self.ui.column6CheckBox.setEnabled(False) - # self.ui.column7CheckBox.setEnabled(False) - # self.ui.column8CheckBox.setEnabled(False) else: for box in self.dispense_checkboxes: box.setEnabled(True) - # self.ui.column1CheckBox.setEnabled(True) - # self.ui.column2CheckBox.setEnabled(True) - # self.ui.column3CheckBox.setEnabled(True) - # self.ui.column4CheckBox.setEnabled(True) - # self.ui.column5CheckBox.setEnabled(True) - # self.ui.column6CheckBox.setEnabled(True) - # self.ui.column7CheckBox.setEnabled(True) - # self.ui.column8CheckBox.setEnabled(True) def dispenseThread(self): thread = threading.Thread(target=self.dispense) + thread.name = "thread-{:03d}".format(self.thread_id) + self.thread_id += 1 thread.start() def dispense(self): """dispenses to the columns (all or individually selected)""" # TODO: make sure this deals with all reasonable cases.. may want to query pump status before commands are written # check if pump is busy + thread_name = threading.current_thread().name + print("{}: dispense() called NOW!!!!".format(thread_name)) if self.checkBusy(): - print("immediate return checkBusy") + print("{}: immediate return checkBusy".format(thread_name)) return drawspeed = int(self.ui.drawSpeedSpinBox.value()) # get speed from GUI dispensespeed = int(self.ui.dispenseSpeedSpinBox.value()) # get speed from GUI # if the "all" check box is selected, dispense to all columns if self.ui.allCheckBox.checkState(): - print("all columns") + print("{}: all columns".format(thread_name)) columns = [True]*8 # [True, True, True, ...] # else just the selected columns else: @@ -558,7 +573,7 @@ def dispense(self): while self.checkBusy(attempts=20, timeout=1, popup=False): self._nonBlockingTime(3) # self._waitReady(delay=3000) - print("in while loop") + print("{}: in while loop".format(thread_name)) self._nonBlockingTime(.5) # self._waitReady(delay=500) self.write(cmd_str) @@ -566,7 +581,7 @@ def dispense(self): print(sleeplength) self._nonBlockingTime(sleeplength) # self._waitReady(delay=sleeplength*1000) - print("Dispense done") + print("{}: Dispense done".format(thread_name)) return def getColumnCheckBoxesSelected(self): @@ -577,19 +592,17 @@ def getColumnCheckBoxesSelected(self): result = [] for checkbox in self.dispense_checkboxes: result.append(checkbox.isChecked()) - # result = [self.ui.column1CheckBox.isChecked(), - # self.ui.column2CheckBox.isChecked(), - # self.ui.column3CheckBox.isChecked(), - # self.ui.column4CheckBox.isChecked(), - # self.ui.column5CheckBox.isChecked(), - # self.ui.column6CheckBox.isChecked(), - # self.ui.column7CheckBox.isChecked(), - # self.ui.column8CheckBox.isChecked()] return result + def stopPumpThread(self): + thread = threading.Thread(target=self.stopPump) + thread.name = "thread-{:03d}".format(self.thread_id) + self.thread_id += 1 + thread.start() + def stopPump(self): """interrupts the pump and stops operation""" - print("STOP PUMP!!!!") + print("{}: STOP PUMP!!!!".format(threading.current_thread().name)) cmd = "/1TR\r\n" if self.debug: self.dbprint("stopped") @@ -597,6 +610,8 @@ def stopPump(self): if self.file_name: self.file.write("> {}".format(cmd)) else: + print("serial.write({})".format(cmd.encode())) + print("baud: {}".format(self.baud)) self.serial.write(cmd.encode()) def volumeToSteps(self, volume_ml): @@ -627,6 +642,7 @@ def checkBusy(self, attempts=3, timeout=.1, busy_debug=None, popup=True): seconds between attempts. If it is busy after that, display a warning popup and stop attempting. """ + thread_name = threading.current_thread().name if busy_debug is None: busy_debug = self.busy_debug if self.debug and not busy_debug: @@ -634,25 +650,25 @@ def checkBusy(self, attempts=3, timeout=.1, busy_debug=None, popup=True): return False # Try (attempts) times and wait (timeout) sec between for attempt in range(attempts): - print("Busy check {}".format(attempt)) + print("{}: Busy check {}".format(thread_name, attempt)) # check for debugging busy flag or try for real if busy_debug: - print("busy_debug = {}".format(busy_debug)) + print("{}: busy_debug = {}".format(thread_name, busy_debug)) self.dbprint("busy") busy = bin(0) else: busy = self.queryPump() - print("busy = {}".format(busy)) + print("{}: queryPump = busy = {}".format(thread_name, busy)) # return if not busy or sleep if busy if busy != bin(0): - print("busy return False") + print("{}: busy return False".format(thread_name)) return False else: - print("busy entering _nonBlockingTime({})".format(timeout)) + print("{}: busy entering _nonBlockingTime({})".format(thread_name, timeout)) self._nonBlockingTime(timeout) # self._waitReady(delay=timeout*1000) # if it reaches here, it IS busy, display popup if popup is True - print("Pump is busy!!!!!") + print("{}: Pump is busy!!!!!".format(thread_name)) if popup is True: dlg = QMessageBox(self) dlg.setWindowTitle("Warning") @@ -660,7 +676,7 @@ def checkBusy(self, attempts=3, timeout=.1, busy_debug=None, popup=True): dlg.setIcon(QMessageBox.Information) button = dlg.exec() if button == QMessageBox.Ok: - print("OK!") + print("{}: OK!".format(thread_name)) return True def _waitReady(self, polling_interval=1, timeout=10, delay=None): @@ -683,10 +699,11 @@ def _waitReady(self, polling_interval=1, timeout=10, delay=None): return def _nonBlockingTime(self, seconds): - print("waiting...\n") + thread_name = threading.current_thread().name + print("{}: waiting...\n".format(thread_name)) for x in range(int(seconds*10)): time.sleep(.1) - print("done waiting") + print("{}: done waiting".format(thread_name)) # def openFile(self, filename): # """Open the file `filename` (and create if needed) for saving serial