class MainApp(QWidget): def __init__(self, parent=None): logging.debug("MainApp:init() instantiated") super().__init__() self.baseWidgets = {} self.vmWidgets = {} self.materialWidgets = {} self.cf = SystemConfigIO() self.ec = ExperimentConfigIO.getInstance() self.statusBar = QStatusBar() self.setMinimumSize(670, 565) quit = QAction("Quit", self) quit.triggered.connect(self.closeEvent) self.setWindowTitle("ARL South RES v0.1") self.tabWidget = QtWidgets.QTabWidget() self.tabWidget.setGeometry(QtCore.QRect(0, 15, 668, 565)) self.tabWidget.setObjectName("tabWidget") # Configuration Window (windowBox) contains: ## windowBoxHLayout contains: ###experimentTree (Left) ###basedataStackedWidget (Right) self.windowWidget = QtWidgets.QWidget() self.windowWidget.setObjectName("windowWidget") self.windowBoxHLayout = QtWidgets.QHBoxLayout() #self.windowBoxHLayout.setContentsMargins(0, 0, 0, 0) self.windowBoxHLayout.setObjectName("windowBoxHLayout") self.windowWidget.setLayout(self.windowBoxHLayout) self.experimentTree = QtWidgets.QTreeWidget() self.experimentTree.itemSelectionChanged.connect(self.onItemSelected) self.experimentTree.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) self.experimentTree.customContextMenuRequested.connect( self.showContextMenu) self.experimentTree.setEnabled(True) self.experimentTree.setMinimumSize(200, 521) self.experimentTree.setMaximumWidth(350) self.experimentTree.setObjectName("experimentTree") self.experimentTree.headerItem().setText(0, "Experiments") self.experimentTree.setSortingEnabled(False) self.windowBoxHLayout.addWidget(self.experimentTree) self.basedataStackedWidget = QStackedWidget() self.basedataStackedWidget.setObjectName("basedataStackedWidget") self.basedataStackedWidget.setEnabled(False) self.windowBoxHLayout.addWidget(self.basedataStackedWidget) self.tabWidget.addTab(self.windowWidget, "Configuration") # VBox Actions Tab self.experimentActionsWidget = ExperimentActionsWidget( statusBar=self.statusBar) self.experimentActionsWidget.setObjectName("experimentActionsWidget") self.tabWidget.addTab(self.experimentActionsWidget, "Experiment Actions") # Remote Connections Tab self.connectionWidget = ConnectionWidget(statusBar=self.statusBar) self.connectionWidget.setObjectName("connectionsWidget") self.tabWidget.addTab(self.connectionWidget, "Remote Connections") #Create the bottom layout so that we can access the status bar self.bottomLayout = QHBoxLayout() self.statusBar.showMessage("Loading GUI...") self.bottomLayout.addWidget(self.statusBar) self.saveButton = QtWidgets.QPushButton("Save Current") self.saveButton.clicked.connect(self.saveExperimentButton) self.saveButton.setEnabled(False) self.bottomLayout.addWidget(self.saveButton) self.populateUi() self.setupContextMenus() self.initMenu() self.mainLayout = QVBoxLayout() self.mainLayout.addWidget(self.mainMenu) self.mainLayout.addWidget(self.tabWidget) self.mainLayout.addLayout(self.bottomLayout) self.setLayout(self.mainLayout) #self.setCentralWidget(self.outerBox) self.tabWidget.setCurrentIndex(0) #self.statusBar.showMessage("Finished Loading GUI Components") # Plugin Section self.tabWidget.addTab(CTFi2GUI(), "CTFi2") def readSystemConfig(self): logging.debug("MainApp:readSystemConfig() instantiated") self.vboxPath = self.cf.getConfig()['VBOX']['VMANAGE_PATH'] self.experimentPath = self.cf.getConfig( )['EXPERIMENTS']['EXPERIMENTS_PATH'] self.statusBar.showMessage("Finished reading system config") def setupContextMenus(self): logging.debug("MainApp:setupContextMenus() instantiated") # Context menu for blank space self.blankTreeContextMenu = QtWidgets.QMenu() self.addExperiment = self.blankTreeContextMenu.addAction( "New Experiment") self.addExperiment.triggered.connect(self.addExperimentActionEvent) self.importExperiment = self.blankTreeContextMenu.addAction( "Import Experiment from RES archive") self.importExperiment.triggered.connect(self.importActionEvent) # Experiment context menu self.experimentContextMenu = QtWidgets.QMenu() self.addVMContextSubMenu = QtWidgets.QMenu() self.experimentContextMenu.addMenu(self.addVMContextSubMenu) self.addVMContextSubMenu.setTitle("Add") self.addVM = self.addVMContextSubMenu.addAction("Virtual Machines") self.addVM.triggered.connect(self.addVMActionEvent) self.addMaterial = self.addVMContextSubMenu.addAction("Material Files") self.addMaterial.triggered.connect(self.addMaterialActionEvent) # Add line separator here self.removeExperiment = self.experimentContextMenu.addAction( "Remove Experiment") self.removeExperiment.triggered.connect( self.removeExperimentItemActionEvent) self.exportExperiment = self.experimentContextMenu.addAction( "Export Experiment") self.exportExperiment.triggered.connect(self.exportActionEvent) # VM/Material context menu self.itemContextMenu = QtWidgets.QMenu() self.removeItem = self.itemContextMenu.addAction( "Remove Experiment Item") self.removeItem.triggered.connect(self.removeExperimentItemActionEvent) def populateUi(self): logging.debug("MainApp:populateUi() instantiated") self.statusBar.showMessage("Populating UI") self.readSystemConfig() #####Create the following based on the config file result = self.ec.getExperimentXMLFilenames() if result == None: return [xmlExperimentFilenames, xmlExperimentNames] = result if xmlExperimentFilenames == [] or xmlExperimentNames == []: self.statusBar.showMessage("No configs found") return #For all experiment files found for configname in xmlExperimentNames: ####Read Experiment Config Data and Populate Tree self.loadConfigname(configname) self.statusBar.showMessage( "Completed populating the User Interface from " + str(len(xmlExperimentNames)) + " config files read", 6000) ############################### def loadConfigname(self, configname): logging.debug("MainApp(): loadConfigname instantiated") logging.info("Reading XML data for " + str(configname)) jsondata = self.ec.getExperimentXMLFileData(configname) self.statusBar.showMessage("Finished reading experiment config") ##########testbed-setup data###### if jsondata == None: jsondata = {} if "xml" not in jsondata or jsondata["xml"] == None or str( jsondata["xml"]).strip() == "": jsondata["xml"] = {} if "testbed-setup" not in jsondata["xml"]: jsondata["xml"]["testbed-setup"] = {} if "vm-set" not in jsondata["xml"]["testbed-setup"]: jsondata["xml"]["testbed-setup"]["vm-set"] = {} #Temporary fix for older xml/json files. if "users-filename" not in jsondata["xml"]["testbed-setup"]["vm-set"]: jsondata["xml"]["testbed-setup"]["vm-set"]["users-filename"] = "" if "rdp-broker-ip" not in jsondata["xml"]["testbed-setup"]["vm-set"]: jsondata["xml"]["testbed-setup"]["vm-set"]["rdp-broker-ip"] = "" if "chat-server-ip" not in jsondata["xml"]["testbed-setup"]["vm-set"]: jsondata["xml"]["testbed-setup"]["vm-set"]["chat-server-ip"] = "" configTreeWidgetItem = QtWidgets.QTreeWidgetItem(self.experimentTree) configTreeWidgetItem.setText(0, configname) self.experimentActionsWidget.addExperimentItem( configname, config_jsondata=jsondata) self.connectionWidget.addExperimentItem(configname, config_jsondata=jsondata) basejsondata = jsondata["xml"] # Base Config Widget self.baseWidget = BaseWidget(self, configname, configname, basejsondata) self.baseWidgets[configname] = { "BaseWidget": {}, "VMWidgets": {}, "MaterialWidgets": {} } self.baseWidgets[configname]["BaseWidget"] = self.baseWidget self.basedataStackedWidget.addWidget(self.baseWidget) ##########vm data###### if "vm" in jsondata["xml"]["testbed-setup"]["vm-set"]: vmsjsondata = jsondata["xml"]["testbed-setup"]["vm-set"]["vm"] if isinstance(vmsjsondata, list): for vm in vmsjsondata: vm_item = QtWidgets.QTreeWidgetItem(configTreeWidgetItem) vmlabel = "V: " + vm["name"] vm_item.setText(0, vmlabel) # VM Config Widget vmWidget = VMWidget(None, configname, vm["name"], vm) self.baseWidgets[configname]["VMWidgets"][ vmlabel] = vmWidget self.basedataStackedWidget.addWidget(vmWidget) else: vm_item = QtWidgets.QTreeWidgetItem(configTreeWidgetItem) vmlabel = "V: " + vmsjsondata["name"] vm_item.setText(0, vmlabel) # VM Config Widget vmWidget = VMWidget(None, configname, vmsjsondata["name"], vmsjsondata) self.baseWidgets[configname]["VMWidgets"][vmlabel] = vmWidget self.basedataStackedWidget.addWidget(vmWidget) ##########material data###### if "material" in jsondata["xml"]["testbed-setup"]["vm-set"]: materialsjsondata = jsondata["xml"]["testbed-setup"]["vm-set"][ "material"] if isinstance(materialsjsondata, list): for material in materialsjsondata: material_item = QtWidgets.QTreeWidgetItem( configTreeWidgetItem) materiallabel = "M: " + material["name"] material_item.setText(0, materiallabel) # Material Config Widget materialWidget = MaterialWidget(None, configname, material["name"], material) self.baseWidgets[configname]["MaterialWidgets"][ materiallabel] = materialWidget self.basedataStackedWidget.addWidget(materialWidget) else: material_item = QtWidgets.QTreeWidgetItem(configTreeWidgetItem) materiallabel = "M: " + materialsjsondata["name"] material_item.setText(0, materiallabel) # Material Config Widget materialWidget = MaterialWidget(None, configname, materialsjsondata["name"], materialsjsondata) self.baseWidgets[configname]["MaterialWidgets"][ materiallabel] = materialWidget self.basedataStackedWidget.addWidget(materialWidget) logging.debug("MainApp(): Finished loading configname: " + str(configname)) def onItemSelected(self): logging.debug("MainApp:onItemSelected instantiated") # Get the selected item selectedItem = self.experimentTree.currentItem() if selectedItem == None: logging.debug("MainApp:onItemSelected no configurations left") self.statusBar.showMessage( "No configuration items selected or available.") return self.basedataStackedWidget.setEnabled(True) # Now enable the save button self.saveButton.setEnabled(True) self.saveExperimentMenuButton.setEnabled(True) #Check if it's the case that an experiment name was selected parentSelectedItem = selectedItem.parent() if (parentSelectedItem == None): #A base widget was selected self.basedataStackedWidget.setCurrentWidget( self.baseWidgets[selectedItem.text(0)]["BaseWidget"]) else: #Check if it's the case that a VM Name was selected if (selectedItem.text(0)[0] == "V"): logging.debug("Setting right widget: " + str(self.baseWidgets[parentSelectedItem.text(0)] ["VMWidgets"][selectedItem.text(0)])) self.basedataStackedWidget.setCurrentWidget( self.baseWidgets[parentSelectedItem.text(0)]["VMWidgets"][ selectedItem.text(0)]) #Check if it's the case that a Material Name was selected elif (selectedItem.text(0)[0] == "M"): logging.debug("Setting right widget: " + str(self.baseWidgets[parentSelectedItem.text(0)] ["MaterialWidgets"][selectedItem.text(0)])) self.basedataStackedWidget.setCurrentWidget( self.baseWidgets[parentSelectedItem.text( 0)]["MaterialWidgets"][selectedItem.text(0)]) def showContextMenu(self, position): logging.debug("MainApp:showContextMenu() instantiated: " + str(position)) if (self.experimentTree.itemAt(position) == None): self.blankTreeContextMenu.popup( self.experimentTree.mapToGlobal(position)) elif (self.experimentTree.itemAt(position).parent() == None): self.experimentContextMenu.popup( self.experimentTree.mapToGlobal(position)) else: self.itemContextMenu.popup( self.experimentTree.mapToGlobal(position)) def addExperimentActionEvent(self): logging.debug("MainApp:addExperimentActionEvent() instantiated") configname = ExperimentAddDialog().experimentAddDialog( self, self.baseWidgets.keys()) if configname != None: logging.debug( "configureVM(): OK pressed and valid configname entered: " + str(configname)) else: logging.debug("configureVM(): Cancel pressed or no VM selected") return ##Now add the item to the tree widget and create the baseWidget configTreeWidgetItem = QtWidgets.QTreeWidgetItem(self.experimentTree) configTreeWidgetItem.setText(0, configname) self.experimentActionsWidget.addExperimentItem(configname) self.connectionWidget.addExperimentItem(configname) # Base Config Widget self.baseWidget = BaseWidget(self, configname, configname) self.baseWidgets[configname] = { "BaseWidget": {}, "VMWidgets": {}, "MaterialWidgets": {} } self.baseWidgets[configname]["BaseWidget"] = self.baseWidget self.basedataStackedWidget.addWidget(self.baseWidget) self.statusBar.showMessage("Added new experiment: " + str(configname)) def importActionEvent(self): logging.debug("MainApp:importActionEvent() instantiated") #Check if it's the case that an experiment name was selected confignamesChosen = PackageImportDialog().packageImportDialog( self, self.baseWidgets.keys()) if confignamesChosen == []: logging.debug( "importActionEvent(): Canceled or a file could not be imported. make sure file exists." ) return #for fileChosen in filesChosen: logging.debug( "MainApp: importActionEvent(): Files choosen (getting only first): " + confignamesChosen) firstConfignameChosen = confignamesChosen self.loadConfigname(firstConfignameChosen) #Add the items to the tree self.statusBar.showMessage("Imported " + firstConfignameChosen) def addVMActionEvent(self): logging.debug("MainApp:addVMActionEvent() instantiated") selectedItem = self.experimentTree.currentItem() if selectedItem == None: logging.debug("MainApp:addVMActionEvent no configurations left") self.statusBar.showMessage( "Could not add VM. No configuration items selected or available." ) return selectedItemName = selectedItem.text(0) #Now allow the user to choose the VM: (response, vmsChosen) = VMRetrieveDialog(self).exec_() if response == QMessageBox.Ok and vmsChosen != None: logging.debug("configureVM(): OK pressed and VMs selected " + str(vmsChosen)) else: logging.debug("configureVM(): Cancel pressed or no VM selected") return if vmsChosen == []: logging.debug( "configureVM(): Canceled or a file could not be added. Try again later or check permissions" ) return for vmChosen in vmsChosen: logging.debug("MainApp: addVMActionEvent(): File choosen: " + str(vmChosen)) #Add the item to the tree vmItem = QtWidgets.QTreeWidgetItem(selectedItem) vmlabel = "V: " + vmChosen vmItem.setText(0, vmlabel) # VM Config Widget #Now add the item to the stack and list of baseWidgets vmjsondata = {"name": vmChosen} vmWidget = VMWidget(self, selectedItemName, vmChosen, vmjsondata) self.baseWidgets[selectedItemName]["VMWidgets"][vmlabel] = vmWidget self.basedataStackedWidget.addWidget(vmWidget) #Now add data to the experimentActionWidget associated with the current config #Check if it's the case that an experiment name was selected parentSelectedItem = selectedItem.parent() if parentSelectedItem != None: selectedItem = parentSelectedItem configname = selectedItem.text(0) config_jsondata = self.getWritableData(configname) self.experimentActionsWidget.resetExperiment( configname, config_jsondata=config_jsondata) self.connectionWidget.resetExperiment(configname, config_jsondata=config_jsondata) self.statusBar.showMessage("Added " + str(len(vmsChosen)) + " VM files to experiment: " + str(selectedItemName)) def startHypervisorActionEvent(self): logging.debug("MainApp:startHypervisorActionEvent() instantiated") logging.debug( "MainApp:startHypervisorActionEvent no configurations left") # Try to open the hypervisor and check if it worked or not result = HypervisorOpenDialog().hypervisorOpenDialog(self) if result != "success": logging.debug( "startHypervisorActionEvent(): Could not start the hypervisor") self.statusBar.showMessage("Hypervisor could not be started.") return self.statusBar.showMessage("Started hypervisor.") def addMaterialActionEvent(self): logging.debug("MainApp:addMaterialActionEvent() instantiated") selectedItem = self.experimentTree.currentItem() if selectedItem == None: logging.debug( "MainApp:addMaterialActionEvent no configurations left") self.statusBar.showMessage( "Could not add item. No configuration items selected or available." ) return selectedItemName = selectedItem.text(0) #Check if it's the case that an experiment name was selected filesChosen = MaterialAddFileDialog().materialAddFileDialog( selectedItemName) if filesChosen == []: logging.debug( "addMaterialActionEvent(): Canceled or a file could not be added. Try again later or check permissions" ) return for fileChosen in filesChosen: fileChosen = os.path.basename(fileChosen) logging.debug("MainApp: addMaterialActionEvent(): File choosen: " + fileChosen) #Add the item to the tree material_item = QtWidgets.QTreeWidgetItem(selectedItem) materiallabel = "M: " + fileChosen material_item.setText(0, materiallabel) # Material Config Widget #Now add the item to the stack and list of baseWidgets materialsjsondata = {"name": fileChosen} materialWidget = MaterialWidget(self, selectedItemName, fileChosen, materialsjsondata) self.baseWidgets[selectedItem.text( 0)]["MaterialWidgets"][materiallabel] = materialWidget self.basedataStackedWidget.addWidget(materialWidget) self.statusBar.showMessage("Added " + str(len(filesChosen)) + " material files to experiment: " + str(selectedItemName)) def removeExperimentItemActionEvent(self): logging.debug("MainApp:removeExperimentItemActionEvent() instantiated") selectedItem = self.experimentTree.currentItem() if selectedItem == None: logging.debug("MainApp:onItemSelected no configurations left") self.statusBar.showMessage( "Could not remove. No configuration items selected or available." ) return selectedItemName = selectedItem.text(0) #Check if it's the case that an experiment name was selected parentSelectedItem = selectedItem.parent() if (parentSelectedItem == None): #A base widget was selected successfilenames = ExperimentRemoveFileDialog( ).experimentRemoveFileDialog(selectedItemName) if successfilenames == [] or successfilenames == "": logging.debug( "removeExperimentItemActionEvent(): Canceled or a file could not be removed. Try again later or check permissions" ) return self.experimentTree.invisibleRootItem().removeChild(selectedItem) self.basedataStackedWidget.removeWidget( self.baseWidgets[selectedItemName]["BaseWidget"]) del self.baseWidgets[selectedItemName] self.experimentActionsWidget.removeExperimentItem(selectedItemName) self.experimentActionsWidget.removeExperimentItem(selectedItemName) self.statusBar.showMessage("Removed experiment: " + str(selectedItemName)) else: #Check if it's the case that a VM Name was selected if (selectedItem.text(0)[0] == "V"): parentSelectedItem.removeChild(selectedItem) configname = parentSelectedItem.text(0) self.basedataStackedWidget.removeWidget( self.baseWidgets[configname]["VMWidgets"][ selectedItem.text(0)]) del self.baseWidgets[configname]["VMWidgets"][ selectedItem.text(0)] self.statusBar.showMessage("Removed VM: " + str(selectedItemName) + " from experiment: " + str(parentSelectedItem.text(0))) #Also remove from the experiment action widget: config_jsondata = self.getWritableData(configname) self.experimentActionsWidget.resetExperiment( configname, config_jsondata=config_jsondata) self.connectionWidget.resetExperiment( configname, config_jsondata=config_jsondata) #Check if it's the case that a Material Name was selected elif (selectedItem.text(0)[0] == "M"): materialName = selectedItemName.split("M: ")[1] successfilenames = MaterialRemoveFileDialog( ).materialRemoveFileDialog(parentSelectedItem.text(0), materialName) if successfilenames == []: logging.debug( "Canceled or a file could not be removed. Try again later or check permissions" ) return parentSelectedItem.removeChild(selectedItem) self.basedataStackedWidget.removeWidget( self.baseWidgets[parentSelectedItem.text( 0)]["MaterialWidgets"][selectedItem.text(0)]) del self.baseWidgets[parentSelectedItem.text( 0)]["MaterialWidgets"][selectedItem.text(0)] self.statusBar.showMessage("Removed Material: " + str(materialName) + " from experiment: " + str(parentSelectedItem.text(0))) def exportActionEvent(self): logging.debug("MainApp:exportActionEvent() instantiated") #Check if it's the case that an experiment name was selected selectedItem = self.experimentTree.currentItem() if selectedItem == None: logging.debug("MainApp:exportActionEvent no configurations left") self.statusBar.showMessage( "Could not export experiment. No configuration items selected or available." ) return selectedItemName = selectedItem.text(0) folderChosen = PackageExportDialog().packageExportDialog( self, selectedItemName) if folderChosen == []: logging.debug( "exportActionEvent(): Canceled or the experiment could not be exported. Check folder permissions." ) return folderChosen = os.path.basename(folderChosen[0]) logging.debug("MainApp: exportActionEvent(): File choosen: " + folderChosen) #Add the items to the tree self.statusBar.showMessage("Exported to " + folderChosen) def editPathActionEvent(self): logging.debug("MainApp:editPathActionEvent() instantiated") result = ConfigurationDialog(self).exec_() def closeEvent(self, event): logging.debug("MainApp:closeEvent(): instantiated") logging.debug("closeEvent(): returning accept") event.accept() qApp.quit() return def initMenu(self): self.mainMenu = QMenuBar() self.fileMenu = self.mainMenu.addMenu("File") self.editMenu = self.mainMenu.addMenu("Edit") self.hypervisorMenu = self.mainMenu.addMenu("Hypervisor") self.newExperimentMenuButton = QAction(QIcon(), "New Experiment", self) self.newExperimentMenuButton.setShortcut("Ctrl+N") self.newExperimentMenuButton.setStatusTip("Create New Experiment") self.newExperimentMenuButton.triggered.connect( self.addExperimentActionEvent) self.fileMenu.addAction(self.newExperimentMenuButton) self.importExperimentMenuButton = QAction(QIcon(), "Import Experiment", self) self.importExperimentMenuButton.setShortcut("Ctrl+I") self.importExperimentMenuButton.setStatusTip( "Import Experiment from RES File") self.importExperimentMenuButton.triggered.connect( self.importActionEvent) self.fileMenu.addAction(self.importExperimentMenuButton) self.saveExperimentMenuButton = QAction(QIcon(), "Save Experiment", self) self.saveExperimentMenuButton.setShortcut("Ctrl+I") self.saveExperimentMenuButton.setStatusTip( "Save currently selected experiment") self.saveExperimentMenuButton.triggered.connect( self.saveExperimentButton) self.saveExperimentMenuButton.setEnabled(False) self.fileMenu.addAction(self.saveExperimentMenuButton) self.exitMenuButton = QAction(QIcon("exit24.png"), "Exit", self) self.exitMenuButton.setShortcut("Ctrl+Q") self.exitMenuButton.setStatusTip("Exit application") self.exitMenuButton.triggered.connect(self.close) self.fileMenu.addAction(self.exitMenuButton) self.pathMenuButton = QAction(QIcon(), "Edit Paths", self) self.pathMenuButton.setShortcut("Ctrl+E") self.pathMenuButton.setStatusTip("Edit Paths") self.pathMenuButton.triggered.connect(self.editPathActionEvent) self.editMenu.addAction(self.pathMenuButton) self.startHypervisorMenuButton = QAction(QIcon(), "Instantiate Hypervisor", self) self.startHypervisorMenuButton.setShortcut("Ctrl+O") self.startHypervisorMenuButton.setStatusTip( "Start the hypervisor that is currently configured") self.startHypervisorMenuButton.triggered.connect( self.startHypervisorActionEvent) self.hypervisorMenu.addAction(self.startHypervisorMenuButton) def getWritableData(self, configname): logging.debug("MainApp: getWritableData() instantiated") jsondata = {} jsondata["xml"] = {} #get baseWidget data baseWidget = self.baseWidgets[configname]["BaseWidget"] ###TODO: make this work for multiple experiments (current testing assumes only one) if isinstance(baseWidget, BaseWidget): jsondata["xml"] = baseWidget.getWritableData() ###Setup the dictionary if "testbed-setup" not in jsondata["xml"]: jsondata["xml"]["testbed-setup"] = {} if "vm-set" not in jsondata["xml"]["testbed-setup"]: jsondata["xml"]["testbed-setup"]["vm-set"] = {} if "vm" not in jsondata["xml"]["testbed-setup"]["vm-set"]: jsondata["xml"]["testbed-setup"]["vm-set"]["vm"] = [] if "material" not in jsondata["xml"]["testbed-setup"]["vm-set"]: jsondata["xml"]["testbed-setup"]["vm-set"]["material"] = [] for vmData in self.baseWidgets[configname]["VMWidgets"].values(): jsondata["xml"]["testbed-setup"]["vm-set"]["vm"].append( vmData.getWritableData()) for materialData in self.baseWidgets[configname][ "MaterialWidgets"].values(): jsondata["xml"]["testbed-setup"]["vm-set"]["material"].append( materialData.getWritableData()) return jsondata def saveExperimentButton(self): logging.debug("MainApp: saveExperiment() instantiated") self.saveExperiment() def saveExperiment(self, configname=None): logging.debug("MainApp: saveExperiment() instantiated") selectedItem = self.experimentTree.currentItem() if selectedItem == None: logging.debug("MainApp:onItemSelected no configurations left") self.statusBar.showMessage( "Could not save. No configuration items selected or available." ) return #Check if it's the case that an experiment name was selected parentSelectedItem = selectedItem.parent() if parentSelectedItem != None: selectedItem = parentSelectedItem configname = selectedItem.text(0) jsondata = self.getWritableData(configname) self.ec.writeExperimentXMLFileData(jsondata, configname) self.ec.writeExperimentJSONFileData(jsondata, configname) self.ec.getExperimentVMRolledOut(configname, jsondata, force_refresh=True) res = self.ec.getExperimentServerInfo(configname) #Now reset the experimentActions view self.experimentActionsWidget.resetExperiment(configname, jsondata) self.connectionWidget.resetExperiment(configname, jsondata) self.statusBar.showMessage( "Succesfully saved experiment file for " + str(configname), 2000)
class ExperimentActionsWidget(QtWidgets.QWidget): def __init__(self, parent=None, statusBar=None): logging.debug("ExperimentActionsWidget instantiated") QtWidgets.QWidget.__init__(self, parent=None) self.statusBar = statusBar self.experimentItemNames = {} self.experimentActionsBaseWidgets = {} self.eco = ExperimentConfigIO.getInstance() self.rolledoutjson = None self.setObjectName("ExperimentActionsWidget") self.windowWidget = QtWidgets.QWidget() self.windowWidget.setObjectName("windowWidget") self.windowBoxHLayout = QtWidgets.QHBoxLayout() #self.windowBoxHLayout.setContentsMargins(0, 0, 0, 0) self.windowBoxHLayout.setObjectName("windowBoxHLayout") self.windowWidget.setLayout(self.windowBoxHLayout) self.experimentTree = QtWidgets.QTreeWidget(parent) self.experimentTree.setObjectName("experimentTree") self.experimentTree.header().resizeSection(0, 150) self.experimentTree.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) self.experimentTree.customContextMenuRequested.connect( self.showContextMenu) self.experimentTree.itemSelectionChanged.connect(self.onItemSelected) self.experimentTree.setEnabled(True) self.experimentTree.setMinimumSize(200, 521) self.experimentTree.setMaximumWidth(350) self.experimentTree.setObjectName("experimentTree") self.experimentTree.headerItem().setText(0, "Experiments New") self.experimentTree.setSortingEnabled(False) self.windowBoxHLayout.addWidget(self.experimentTree) self.windowBoxVLayout = QtWidgets.QVBoxLayout() #self.windowBoxHLayout.setContentsMargins(0, 0, 0, 0) self.windowBoxVLayout.setObjectName("windowBoxVLayout") self.basedataStackedWidget = QStackedWidget() self.basedataStackedWidget.setObjectName("basedataStackedWidget") self.basedataStackedWidget.setEnabled(False) self.windowBoxVLayout.addWidget(self.basedataStackedWidget) self.refreshVMsButton = QtWidgets.QPushButton("Refresh Status") self.refreshVMsButton.clicked.connect(self.refreshVMStatus) self.refreshVMsButton.setEnabled(False) self.windowBoxVLayout.addWidget(self.refreshVMsButton) self.windowBoxHLayout.addLayout(self.windowBoxVLayout) # Context menus self.experimentMenu = QtWidgets.QMenu() self.startupContextMenu = QtWidgets.QMenu("Startup") self.shutdownContextMenu = QtWidgets.QMenu("Shutdown") self.stateContextMenu = QtWidgets.QMenu("State") self.experimentMenu.addMenu(self.startupContextMenu) self.experimentMenu.addMenu(self.shutdownContextMenu) self.experimentMenu.addMenu(self.stateContextMenu) self.cloneExperiment = self.startupContextMenu.addAction( "Signal - Create Clones") self.cloneExperiment.triggered.connect(self.menuItemSelected) self.startVMs = self.startupContextMenu.addAction( "Signal - Start VMs (headless)") self.startVMs.triggered.connect(self.menuItemSelected) self.restoreSnapshots = self.startupContextMenu.addAction( "Signal - Restore Snapshots") self.restoreSnapshots.triggered.connect(self.menuItemSelected) self.pauseVMs = self.shutdownContextMenu.addAction( "Signal - Pause VMs") self.pauseVMs.triggered.connect(self.menuItemSelected) self.suspendVMs = self.shutdownContextMenu.addAction( "Signal - Suspend & Save States") self.suspendVMs.triggered.connect(self.menuItemSelected) self.poweroffVMs = self.shutdownContextMenu.addAction( "Signal - Power Off VMs") self.poweroffVMs.triggered.connect(self.menuItemSelected) self.deleteClones = self.shutdownContextMenu.addAction( "Signal - Delete Clones") self.deleteClones.triggered.connect(self.menuItemSelected) self.shutdownContextMenu.addAction(self.deleteClones) self.snapshotVMs = self.stateContextMenu.addAction( "Signal - Snapshot VMs") self.snapshotVMs.triggered.connect(self.menuItemSelected) self.setLayout(self.windowBoxHLayout) self.retranslateUi() def retranslateUi(self): logging.debug("ExperimentActionsWidget: retranslateUi(): instantiated") self.setWindowTitle("ExperimentActionsWidget") self.experimentTree.headerItem().setText(0, "Experiments") self.experimentTree.setSortingEnabled(False) def onItemSelected(self): logging.debug("MainApp:onItemSelected instantiated") self.basedataStackedWidget.setEnabled(True) self.refreshVMsButton.setEnabled(True) # Get the selected item selectedItem = self.experimentTree.currentItem() if selectedItem == None: logging.debug("MainApp:onItemSelected no configurations left") self.statusBar.showMessage( "No configuration items selected or available.") return #Check if it's the case that an experiment name was selected parentparentSelectedItem = None parentSelectedItem = selectedItem.parent() if parentSelectedItem != None: parentparentSelectedItem = selectedItem.parent().parent() if parentSelectedItem == None: #A base widget was selected self.basedataStackedWidget.setCurrentWidget( self.experimentActionsBaseWidgets[selectedItem.text( 0)]["ExperimentActionsBaseWidget"]) self.experimentTree.resizeColumnToContents(0) elif parentparentSelectedItem == None: #A base widget was selected self.basedataStackedWidget.setCurrentWidget( self.experimentActionsBaseWidgets[parentSelectedItem.text( 0)]["ExperimentActionsBaseWidget"]) self.experimentTree.resizeColumnToContents(0) else: #Check if it's the case that a VM Name was selected if (selectedItem.text(0)[0] == "V"): logging.debug( "Setting right widget: " + str(self.experimentActionsBaseWidgets[ parentparentSelectedItem.text(0)] ["ExperimentActionsVMWidgets"][selectedItem.text(0)])) self.basedataStackedWidget.setCurrentWidget( self.experimentActionsBaseWidgets[ parentparentSelectedItem.text(0)] ["ExperimentActionsVMWidgets"][selectedItem.text(0)]) if (selectedItem.text(0)[0] == "S"): logging.debug( "Setting right widget: " + str(self.experimentActionsBaseWidgets[ parentparentSelectedItem.text(0)] ["ExperimentActionsSetWidgets"][selectedItem.text(0)])) self.basedataStackedWidget.setCurrentWidget( self.experimentActionsBaseWidgets[ parentparentSelectedItem.text(0)] ["ExperimentActionsSetWidgets"][selectedItem.text(0)]) if (selectedItem.text(0)[0] == "T"): logging.debug("Setting right widget: " + str(self.experimentActionsBaseWidgets[ parentparentSelectedItem.text(0)] ["ExperimentActionsTemplateWidgets"][ selectedItem.text(0)])) self.basedataStackedWidget.setCurrentWidget( self.experimentActionsBaseWidgets[ parentparentSelectedItem.text(0)] ["ExperimentActionsTemplateWidgets"][selectedItem.text(0)]) if (selectedItem.text(0)[0] == "U"): logging.debug("Setting right widget: " + str( self.experimentActionsBaseWidgets[ parentparentSelectedItem.text(0)] ["ExperimentActionsUserWidgets"][selectedItem.text(0)])) self.basedataStackedWidget.setCurrentWidget( self.experimentActionsBaseWidgets[ parentparentSelectedItem.text(0)] ["ExperimentActionsUserWidgets"][selectedItem.text(0)]) def getExperimentVMRolledOut(self, configname, config_json): logging.debug( "ExperimentActionsWidget(): getExperimentVMRolledOut(): retranslateUi(): instantiated" ) self.rolledoutjson = self.eco.getExperimentVMRolledOut( configname, config_json) def addExperimentItem(self, configname, config_jsondata=None): logging.debug("addExperimentItem(): retranslateUi(): instantiated") if configname in self.experimentItemNames: logging.error( "addExperimentItem(): Item already exists in tree: " + str(configname)) return userpool = UserPool() ##Now add the item to the tree widget and create the baseWidget experimentTreeWidgetItem = QtWidgets.QTreeWidgetItem( self.experimentTree) experimentTreeWidgetItem.setText(0, configname) experimentSetTreeItem = QtWidgets.QTreeWidgetItem( experimentTreeWidgetItem) experimentSetTreeItem.setText(0, "Sets") experimentCloneTreeItem = QtWidgets.QTreeWidgetItem( experimentTreeWidgetItem) experimentCloneTreeItem.setText(0, "Templates") experimentVMTreeItem = QtWidgets.QTreeWidgetItem( experimentTreeWidgetItem) experimentVMTreeItem.setText(0, "VMs") experimentUserTreeItem = QtWidgets.QTreeWidgetItem( experimentTreeWidgetItem) experimentUserTreeItem.setText(0, "Users") self.experimentItemNames[configname] = experimentTreeWidgetItem #get all rolled out and then get them by VM funcs = [] funcs.append( (self.getExperimentVMRolledOut, configname, config_jsondata)) GUIFunctionExecutingDialog(None, "Processing VMs for " + str(configname), funcs).exec_() rolledoutjson = self.rolledoutjson if rolledoutjson != None: # if file was specified, but it doesn't exist, prepend usernames invalid_userfile = False users_filename = config_jsondata["xml"]["testbed-setup"]["vm-set"][ "users-filename"] if users_filename != None and users_filename.strip() != "": if os.path.exists(users_filename) == False: invalid_userfile = True #get the usersConn associations first: usersConns = userpool.generateUsersConns( configname, creds_file=users_filename, rolledout_json=rolledoutjson) vmuser_mapping = {} for (username, password) in usersConns: for conn in usersConns[(username, password)]: cloneVMName = conn[0] if invalid_userfile == False: vmuser_mapping[cloneVMName] = (username, password) else: vmuser_mapping[cloneVMName] = "userfile_not_found" #create the status widgets (tables) self.experimentActionsBaseWidget = ExperimentActionsVMStatusWidget( self, configname, rolledoutjson=rolledoutjson, interest_vmnames=[], vmuser_mapping=vmuser_mapping, status_bar=self.statusBar) self.experimentActionsBaseWidgets[configname] = { "ExperimentActionsBaseWidget": {}, "ExperimentActionsSetWidgets": {}, "ExperimentActionsTemplateWidgets": {}, "ExperimentActionsVMWidgets": {}, "ExperimentActionsUserWidgets": {} } self.experimentActionsBaseWidgets[configname][ "ExperimentActionsBaseWidget"] = self.experimentActionsBaseWidget self.basedataStackedWidget.addWidget( self.experimentActionsBaseWidget) #Set-based view (template_vms, num_clones) = rolledoutjson #First create the sets from the rolled out data sets = self.eco.getExperimentSetDictFromRolledOut( configname, rolledoutjson) for set in sets: set_item = QtWidgets.QTreeWidgetItem(experimentSetTreeItem) setlabel = "S: Set " + set set_item.setText(0, setlabel) # Set Widget experimentActionsSetStatusWidget = ExperimentActionsVMStatusWidget( self, configname, rolledoutjson=rolledoutjson, interest_vmnames=sets[set], vmuser_mapping=vmuser_mapping, status_bar=self.statusBar) self.experimentActionsBaseWidgets[configname][ "ExperimentActionsSetWidgets"][ setlabel] = experimentActionsSetStatusWidget self.basedataStackedWidget.addWidget( experimentActionsSetStatusWidget) templates = self.eco.getExperimentVMNamesFromTemplateFromRolledOut( configname, rolledoutjson) for templatename in templates: template_item = QtWidgets.QTreeWidgetItem( experimentCloneTreeItem) templatelabel = "T: " + templatename template_item.setText(0, templatelabel) # Set Widget experimentActionsTemplateStatusWidget = ExperimentActionsVMStatusWidget( self, configname, rolledoutjson=rolledoutjson, interest_vmnames=templates[templatename], vmuser_mapping=vmuser_mapping, status_bar=self.statusBar) self.experimentActionsBaseWidgets[configname][ "ExperimentActionsTemplateWidgets"][ templatelabel] = experimentActionsTemplateStatusWidget self.basedataStackedWidget.addWidget( experimentActionsTemplateStatusWidget) #Individual VM-based view vms_list = self.eco.getExperimentVMListsFromRolledOut( configname, rolledoutjson) for vm in vms_list: vmname = vm["name"] vm_item = QtWidgets.QTreeWidgetItem(experimentVMTreeItem) vmlabel = "V: " + vmname vm_item.setText(0, vmlabel) # VM Config Widget experimentActionsVMStatusWidget = ExperimentActionsVMStatusWidget( self, configname, rolledoutjson=rolledoutjson, interest_vmnames=[vmname], vmuser_mapping=vmuser_mapping, status_bar=self.statusBar) self.experimentActionsBaseWidgets[configname][ "ExperimentActionsVMWidgets"][ vmlabel] = experimentActionsVMStatusWidget self.basedataStackedWidget.addWidget( experimentActionsVMStatusWidget) #Individual Users-based view num = 1 for (username, password) in usersConns: vmnames = [ tuple[0] for tuple in usersConns[(username, password)] ] user_item = QtWidgets.QTreeWidgetItem(experimentUserTreeItem) user_label = "U: " + username + " (Set " + str(num) + ")" num += 1 user_item.setText(0, user_label) # VM Config Widget experimentActionsUserStatusWidget = ExperimentActionsVMStatusWidget( self, configname, rolledoutjson=rolledoutjson, interest_vmnames=vmnames, vmuser_mapping=vmuser_mapping, status_bar=self.statusBar) self.experimentActionsBaseWidgets[configname][ "ExperimentActionsUserWidgets"][ user_label] = experimentActionsUserStatusWidget self.basedataStackedWidget.addWidget( experimentActionsUserStatusWidget) self.statusBar.showMessage("Added new experiment: " + str(configname)) logging.debug("addExperimentItem(): retranslateUi(): Completed") def resetExperiment(self, configname, config_jsondata): logging.debug("updateExperimentItem(): retranslateUi(): instantiated") if configname not in self.experimentItemNames: logging.error( "removeExperimentItem(): Item does not exist in tree: " + str(configname)) return self.removeExperimentItem(configname) self.addExperimentItem(configname, config_jsondata) def removeExperimentItem(self, configname): logging.debug("removeExperimentItem(): retranslateUi(): instantiated") if configname not in self.experimentItemNames: logging.error( "removeExperimentItem(): Item does not exist in tree: " + str(configname)) return experimentTreeWidgetItem = self.experimentItemNames[configname] self.experimentTree.invisibleRootItem().removeChild( experimentTreeWidgetItem) del self.experimentItemNames[configname] logging.debug("removeExperimentItem(): Completed") def showContextMenu(self, position): logging.debug( "ExperimentActionsWidget(): showContextMenu(): instantiated") self.experimentMenu.popup(self.experimentTree.mapToGlobal(position)) def getTypeNameFromSelection(self): configname = "" itype = "" name = "" #configname selected if self.experimentTree.currentItem().parent() == None: configname = self.experimentTree.currentItem().text(0) itype = "set" name = "all" #sets, clones, or VMs label selected elif self.experimentTree.currentItem().parent().parent() == None: configname = self.experimentTree.currentItem().parent().text(0) itype = "set" name = "all" #specific item selected elif self.experimentTree.currentItem().parent().parent().parent( ) == None: configname = self.experimentTree.currentItem().parent().parent( ).text(0) currItemText = self.experimentTree.currentItem().text(0) if currItemText.startswith("S: Set "): itype = "set" name = currItemText.split("S: Set ")[1:] name = " ".join(name) elif currItemText.startswith("V: "): itype = "vm" name = currItemText.split("V: ")[1:] name = "\"" + " ".join(name) + "\"" elif currItemText.startswith("T: "): itype = "template" name = currItemText.split("T: ")[1:] name = "\"" + " ".join(name) + "\"" elif currItemText.startswith("U: "): itype = "set" name = currItemText.split("(Set ")[1].split(")")[0:-1] name = " ".join(name) return configname, itype, name def menuItemSelected(self): logging.debug("menuItemSelected(): instantiated") actionlabelname = self.sender().text() configname, itype, name = self.getTypeNameFromSelection() ExperimentActions().experimentActionEvent(configname, actionlabelname, itype, name) self.statusBar.showMessage("Executed " + str(actionlabelname) + " on " + configname) def refreshVMStatus(self): logging.debug("refreshVMStatus(): instantiated") #Get the configname based on selected item: selectedItem = self.experimentTree.currentItem() #Check if an experiment name is selected if selectedItem == None: logging.error("No experiment label was selected.") return #If so, get the configname associated with it while selectedItem.parent() != None: selectedItem = selectedItem.parent() configname = selectedItem.text(0) s = VMRetrievingDialog(self).exec_() self.vms = s["vmstatus"] #Update all vm status in the subtrees #First the "all" view for widget in self.experimentActionsBaseWidgets[configname].values(): if isinstance(widget, ExperimentActionsVMStatusWidget): widget.updateVMStatus(self.vms) #The Sets: for widget in self.experimentActionsBaseWidgets[configname][ "ExperimentActionsSetWidgets"].values(): if isinstance(widget, ExperimentActionsVMStatusWidget): widget.updateVMStatus(self.vms) #The Templates: for widget in self.experimentActionsBaseWidgets[configname][ "ExperimentActionsTemplateWidgets"].values(): if isinstance(widget, ExperimentActionsVMStatusWidget): widget.updateVMStatus(self.vms) #The VMs for widget in self.experimentActionsBaseWidgets[configname][ "ExperimentActionsVMWidgets"].values(): if isinstance(widget, ExperimentActionsVMStatusWidget): widget.updateVMStatus(self.vms) #The Users for widget in self.experimentActionsBaseWidgets[configname][ "ExperimentActionsUserWidgets"].values(): if isinstance(widget, ExperimentActionsVMStatusWidget): widget.updateVMStatus(self.vms)
class QKeithleyBiasWidget: def __init__(self, _app, _name): # Cache a reference to the calling application self._app = _app self._name = _name # Set thread variables self.thread, self.thread_running = None, False # Generate widgets self.gen_ctrl_widget() self.gen_plot_widget() self.gen_output_widget() # Reset the keithley if self._name != "__none__": self.keithley().rst() self.keithley().set_voltage(self.voltage_bias.value()) self.keithley().current_cmp(self.voltage_cmpl.value()) def keithley(self): return self._app.get_device_by_name(self._name) ##################################### # APPLICATION HELPER METHODS # def get_output_widget(self): return self.output_widget[0] def get_ctrl_widget(self): return self.ctl_widget def get_plot_widget(self): return self.plot_stack # Create a QStateMachine and output button for each connected insturment def gen_output_widget(self): if self._name != "__none__": # Each output is a tuple [QPushButton, QStateMachine] self.output_widget = (QPushButton(), QStateMachine()) self.output_widget[0].setStyleSheet( "background-color: #dddddd; border-style: solid; border-width: 1px; border-color: #aaaaaa; padding: 7px;" ) # Create output states output_off = QState() output_on = QState() # Attach states to output button and define state transitions output_off.assignProperty(self.output_widget[0], 'text', "%s Output On" % self._name.split()[1]) output_off.addTransition(self.output_widget[0].clicked, output_on) output_off.entered.connect(self.exec_output_off) output_on.assignProperty(self.output_widget[0], 'text', '%s Output Off' % self._name.split()[1]) output_on.addTransition(self.output_widget[0].clicked, output_off) output_on.entered.connect(self.exec_output_on) # Add states, set initial state, and start machine self.output_widget[1].addState(output_off) self.output_widget[1].addState(output_on) self.output_widget[1].setInitialState(output_off) self.output_widget[1].start() else: self.output_widget = (QPushButton(), False) self.output_widget[0].setStyleSheet( "background-color: #dddddd; border-style: solid; border-width: 1px; border-color: #aaaaaa; padding: 7px;" ) self.output_widget[0].setEnabled(False) self.output_widget[0].setText("Keithley not Initialized") # Generate bias control widget def gen_ctrl_widget(self): # Control layout self.ctl_widget = QWidget() self.ctl_layout = QVBoxLayout() # Main mode selctor self.src_select_label = QLabel("Bias Mode") self.src_select = QComboBox() self.src_select.setFixedWidth(200) self.src_select.addItems(["Voltage", "Current"]) self.src_select.currentTextChanged.connect(self.update_bias_ctrl) # Generate voltage and current source widgets self.gen_voltage_src() # self.voltage_src self.gen_current_src() # self.current_src # Add to stacked widget self.src_pages = QStackedWidget() self.src_pages.addWidget(self.voltage_src) self.src_pages.addWidget(self.current_src) self.src_pages.setCurrentIndex(0) # Disable controls if "__none__ passed as name" if self._name == "__none__": self.src_select_label.setEnabled(False) self.src_select.setEnabled(False) self.src_pages.setEnabled(False) ##################################### # ADD CONTROLS # # Main output and controls self.ctl_layout.addWidget( self._app._gen_hbox_widget( [self.src_select, self.src_select_label])) self.ctl_layout.addWidget(self.src_pages) self.ctl_layout.setContentsMargins(0, 0, 0, 0) # Set layouth self.ctl_widget.setLayout(self.ctl_layout) # Generate voltage and current sources def gen_voltage_src(self): # New QWidget self.voltage_src = QWidget() self.voltage_layout = QVBoxLayout() # Configuration for bias level unit box self.voltage_bias_config = { "unit": "V", "min": "u", "max": "", "label": "Bias Level", "limit": 20.0, "signed": True, "default": [0.0, ""] } self.voltage_bias = QVisaUnitSelector.QVisaUnitSelector( self.voltage_bias_config) self.voltage_bias.unit_value.valueChanged.connect(self.update_bias) self.voltage_bias.unit_select.currentTextChanged.connect( self.update_bias) # Compliance Spinbox self.voltage_cmpl_config = { "unit": "A", "min": "u", "max": "", "label": "Compliance", "limit": 1.0, "signed": False, "default": [100, "m"] } self.voltage_cmpl = QVisaUnitSelector.QVisaUnitSelector( self.voltage_cmpl_config) self.voltage_cmpl.unit_value.valueChanged.connect(self.update_cmpl) self.voltage_cmpl.unit_select.currentTextChanged.connect( self.update_cmpl) # Measurement Delay self.voltage_delay_config = { "unit": "__DOUBLE__", "label": "Measurement Interval (s)", "limit": 60.0, "signed": False, "default": [0.1] } self.voltage_delay = QVisaUnitSelector.QVisaUnitSelector( self.voltage_delay_config) # Pack selectors into layout self.voltage_layout.addWidget(self.voltage_bias) self.voltage_layout.addWidget(self.voltage_cmpl) self.voltage_layout.addWidget(self.voltage_delay) self.voltage_layout.setContentsMargins(0, 0, 0, 0) # Set layout self.voltage_src.setLayout(self.voltage_layout) def gen_current_src(self): # New QWidget self.current_src = QWidget() self.current_layout = QVBoxLayout() # Configuration for bias level unit box self.current_bias_config = { "unit": "A", "min": "u", "max": "", "label": "Bias Level", "limit": 1.0, "signed": True, "default": [1.0, "m"] } self.current_bias = QVisaUnitSelector.QVisaUnitSelector( self.current_bias_config) self.current_bias.unit_value.valueChanged.connect(self.update_bias) self.current_bias.unit_select.currentTextChanged.connect( self.update_bias) # Compliance Spinbox self.current_cmpl_config = { "unit": "V", "min": "u", "max": "", "label": "Compliance", "limit": 1.0, "signed": False, "default": [1, ""] } self.current_cmpl = QVisaUnitSelector.QVisaUnitSelector( self.current_cmpl_config) self.current_cmpl.unit_value.valueChanged.connect(self.update_cmpl) self.current_cmpl.unit_select.currentTextChanged.connect( self.update_cmpl) # Measurement Delay self.current_delay_config = { "unit": "__DOUBLE__", "label": "Measurement Interval (s)", "limit": 60.0, "signed": False, "default": [0.1] } self.current_delay = QVisaUnitSelector.QVisaUnitSelector( self.current_delay_config) # Pack selectors into layout self.current_layout.addWidget(self.current_bias) self.current_layout.addWidget(self.current_cmpl) self.current_layout.addWidget(self.current_delay) self.current_layout.setContentsMargins(0, 0, 0, 0) # Set layout self.current_src.setLayout(self.current_layout) # Dynamic Plotting Capability def gen_plot_widget(self): # Create QVisaDynamicPlot Object (inherits QWidget) self.voltage_plot = QVisaDynamicPlot.QVisaDynamicPlot(self._app) self.voltage_plot.add_subplot("111") self.voltage_plot.set_axes_labels("111", "Time (s)", "Current (A)") self.voltage_plot.refresh_canvas(supress_warning=True) # Create QVisaDynamicPlot Object (inherits QWidget) self.current_plot = QVisaDynamicPlot.QVisaDynamicPlot(self._app) self.current_plot.add_subplot("111") self.current_plot.set_axes_labels("111", "Time (s)", "Voltage (V)") self.current_plot.refresh_canvas(supress_warning=True) # Add to plot stack self.plot_stack = QStackedWidget() self.plot_stack.addWidget(self.voltage_plot) self.plot_stack.addWidget(self.current_plot) self.plot_stack.setCurrentIndex(0) # Sync plot clear data button with application data self.voltage_plot.sync_application_data(True) self.current_plot.sync_application_data(True) # Sync meta widget when clearing data from plots self.voltage_plot.set_mpl_refresh_callback( "_sync_meta_widget_to_data_object") self.current_plot.set_mpl_refresh_callback( "_sync_meta_widget_to_data_object") ##################################### # BIAS CONTROL UPDATE METHODS # # Update bias values def update_bias(self): if self.src_select.currentText() == "Voltage": self.keithley().set_voltage(self.voltage_bias.value()) if self.src_select.currentText() == "Current": self.keithley().set_current(self.current_bias.value()) # Update compliance values def update_cmpl(self): if self.src_select.currentText() == "Voltage": self.keithley().current_cmp(self.voltage_cmpl.value()) if self.src_select.currentText() == "Current": self.keithley().voltage_cmp(self.current_cmpl.value()) # Update bias control selectors def update_bias_ctrl(self): # Switch to voltage page if self.src_select.currentText() == "Voltage": # Update src_pages and plot self.src_pages.setCurrentIndex(0) self.plot_stack.setCurrentIndex(0) # Keithley to voltage source if self.keithley() is not None: self.keithley().voltage_src() self.update_bias() self.update_cmpl() # Switch to current page if self.src_select.currentText() == "Current": # Update src_pages and plot self.src_pages.setCurrentIndex(1) self.plot_stack.setCurrentIndex(1) # Keithley to current source if self.keithley() is not None: self.keithley().current_src() self.update_bias() self.update_cmpl() ##################################### # MEASUREMENT EXECUTION THREADS # # Measurement thread def exec_output_thread(self): # Check mesurement type for datafile if self.src_select.currentText() == "Voltage": _type = "v-bias" if self.src_select.currentText() == "Current": _type = "i-bias" # Get QVisaDataObject data = self._app._get_data_object() key = data.add_hash_key(_type) # Add key to meta widget self._app.meta_widget.add_meta_key(key) # Add data fields to key data.set_subkeys(key, ["t", "V", "I", "P"]) data.set_metadata(key, "__type__", _type) # Voltage and current arrays _plot = self.plot_stack.currentWidget() handle = _plot.add_axes_handle("111", key) start = time.time() # Thread loop while self.thread_running: # Get data from buffer _buffer = self.keithley().meas().split(",") # If in current mode, plot voltage if self.src_select.currentText() == "Current": _p = _buffer[0] # Measurement delay if self.current_delay.value() != 0: time.sleep(self.current_delay.value()) # It in voltage mode plot current if self.src_select.currentText() == "Voltage": _p = _buffer[1] # Measurement delay if self.voltage_delay.value() != 0: time.sleep(self.voltage_delay.value()) # Extract data from buffer _now = float(time.time() - start) # Append measured values to data arrays data.append_subkey_data(key, "t", _now) data.append_subkey_data(key, "V", float(_buffer[0])) data.append_subkey_data(key, "I", float(_buffer[1])) data.append_subkey_data(key, "P", float(_buffer[0]) * float(_buffer[1])) # Append data to handle _plot.append_handle_data("111", key, _now, float(_p)) _plot.update_canvas() # UI output on state (measurement) def exec_output_on(self): if self.keithley() is not None: # Update UI for ON state self.output_widget[0].setStyleSheet( "background-color: #cce6ff; border-style: solid; border-width: 1px; border-color: #1a75ff; padding: 7px;" ) # Disable controls self.src_select.setEnabled(False) self.voltage_cmpl.setEnabled(False) self.current_cmpl.setEnabled(False) _plot = self.plot_stack.currentWidget() _plot.mpl_refresh_setEnabled(False) # Disable save widget if it exists if hasattr(self._app, 'save_widget'): self._app.save_widget.setEnabled(False) # Turn output ON self.keithley().output_on() # Each output is a list [QPushButton, QStateMachine, thrading.Thread, threadRunning(bool)] # Create execution thread for measurement self.thread = threading.Thread(target=self.exec_output_thread, args=()) self.thread.daemon = True # Daemonize thread self.thread.start() # Start the execution self.thread_running = True # UI output on state def exec_output_off(self): if self.keithley() is not None: # Get output name from inst_widget self.output_widget[0].setStyleSheet( "background-color: #dddddd; border-style: solid; border-width: 1px; border-color: #aaaaaa; padding: 7px;" ) # Set thread halt boolean self.thread_running = False # Wait for thread termination if self.thread is not None: self.thread.join() # Enable controls self.src_select.setEnabled(True) self.voltage_cmpl.setEnabled(True) self.current_cmpl.setEnabled(True) _plot = self.plot_stack.currentWidget() _plot.mpl_refresh_setEnabled(True) # Enable save widget if it exists if hasattr(self._app, 'save_widget'): self._app.save_widget.setEnabled(True) # Turn output OFF self.keithley().output_off()