def __init__(self, parent=None): QtGui.QWidget.__init__(self, parent) self.ui = Ui_MainWindow() self.ui.setupUi(self) # TODO: figure out way to configure for different comm types (TCP, MAVLINK, etc) self.comm = AQSerial() # Default main window conditions self.ui.buttonDisconnect.setEnabled(False) self.ui.buttonConnect.setEnabled(True) self.ui.comPort.setEnabled(True) self.ui.baudRate.setEnabled(True) self.ui.status.setText("Not connected to the AeroQuad") self.availablePorts = [] self.updateComPortSelection() self.updateBaudRates() self.boardConfiguration = {} self.manualConnect = True # Update comm port combo box to use last used comm port defaultComPort = xml.find("./Settings/DefaultComPort").text commIndex = self.ui.comPort.findText(defaultComPort) if commIndex == -1: commIndex = 0 self.ui.comPort.setCurrentIndex(commIndex) # Load splash screen splash = Ui_splashScreen() splash.setupUi(splash) self.ui.subPanel.addWidget(splash) # Dynamically configure board type menu and subPanel menu from XML configuration file self.configureSubPanelMenu() self.activeSubPanel = None self.activeSubPanelName = "" # Connect GUI slots and signals self.ui.comPort.return_handler = self.connectBoard self.ui.buttonConnect.clicked.connect(self.connectBoard) self.ui.buttonDisconnect.clicked.connect(self.disconnectBoard) self.ui.actionExit.triggered.connect(QtGui.qApp.quit) self.ui.comPort.currentIndexChanged.connect(self.updateDetectedPorts) self.ui.actionBootUpDelay.triggered.connect(self.updateBootUpDelay) self.ui.actionCommTimeout.triggered.connect(self.updateCommTimeOut)
def __init__(self, parent=None): QtGui.QWidget.__init__(self, parent) self.ui = Ui_MainWindow() self.ui.setupUi(self) background = xml.find("./Settings/Background").text self.ui.subPanel.setStyleSheet("QStackedWidget{background-image: url(" + background + ");}") # TODO: figure out way to configure for different comm types (TCP, MAVLINK, etc) self.comm = AQSerial() # Default main window conditions self.ui.buttonDisconnect.setEnabled(False) self.ui.buttonConnect.setEnabled(True) self.ui.comPort.setEnabled(True) self.ui.baudRate.setEnabled(True) self.ui.status.setText("Not connected to the AeroQuad") self.availablePorts = [] self.updateComPortSelection() self.updateBaudRates() self.boardConfiguration = {} self.manualConnect = True # Update comm port combo box to use last used comm port defaultComPort = xml.find("./Settings/DefaultComPort").text commIndex = self.ui.comPort.findText(defaultComPort) if commIndex == -1: commIndex = 0 self.ui.comPort.setCurrentIndex(commIndex) # Load splash screen splash = Ui_splashScreen() splash.setupUi(splash) self.ui.subPanel.addWidget(splash) # Dynamically configure board type menu and subPanel menu from XML configuration file self.configureSubPanelMenu() self.activeSubPanel = None self.activeSubPanelName = "" # Connect GUI slots and signals self.ui.comPort.return_handler = self.connectBoard self.ui.buttonConnect.clicked.connect(self.connectBoard) self.ui.buttonDisconnect.clicked.connect(self.disconnectBoard) self.ui.actionExit.triggered.connect(QtGui.qApp.quit) self.ui.comPort.currentIndexChanged.connect(self.updateDetectedPorts) self.ui.actionBootUpDelay.triggered.connect(self.updateBootUpDelay) self.ui.actionCommTimeout.triggered.connect(self.updateCommTimeOut) self.ui.buttonMenu.clicked.connect(self.returnToMenu)
class AQMain(QtGui.QMainWindow): def __init__(self, parent=None): QtGui.QWidget.__init__(self, parent) self.ui = Ui_MainWindow() self.ui.setupUi(self) # TODO: figure out way to configure for different comm types (TCP, MAVLINK, etc) self.comm = AQSerial() # Default main window conditions self.ui.buttonDisconnect.setEnabled(False) self.ui.buttonConnect.setEnabled(True) self.ui.comPort.setEnabled(True) self.ui.baudRate.setEnabled(True) self.ui.status.setText("Not connected to the AeroQuad") self.availablePorts = [] self.updateComPortSelection() self.updateBaudRates() self.boardConfiguration = {} self.manualConnect = True # Update comm port combo box to use last used comm port defaultComPort = xml.find("./Settings/DefaultComPort").text commIndex = self.ui.comPort.findText(defaultComPort) if commIndex == -1: commIndex = 0 self.ui.comPort.setCurrentIndex(commIndex) # Load splash screen splash = Ui_splashScreen() splash.setupUi(splash) self.ui.subPanel.addWidget(splash) # Dynamically configure board type menu and subPanel menu from XML configuration file self.configureSubPanelMenu() self.activeSubPanel = None self.activeSubPanelName = "" # Connect GUI slots and signals self.ui.comPort.return_handler = self.connectBoard self.ui.buttonConnect.clicked.connect(self.connectBoard) self.ui.buttonDisconnect.clicked.connect(self.disconnectBoard) self.ui.actionExit.triggered.connect(QtGui.qApp.quit) self.ui.comPort.currentIndexChanged.connect(self.updateDetectedPorts) self.ui.actionBootUpDelay.triggered.connect(self.updateBootUpDelay) self.ui.actionCommTimeout.triggered.connect(self.updateCommTimeOut) ####### Communication Methods ####### def connectBoard(self): '''Initiates communication with the AeroQuad''' # Setup GUI self.ui.status.setText("Connecting...") self.ui.buttonDisconnect.setEnabled(True) self.ui.buttonConnect.setEnabled(False) self.ui.comPort.setEnabled(False) self.ui.baudRate.setEnabled(False) # Update the GUI app.processEvents() # Setup serial port bootupDelay = float(xml.find("./Settings/BootUpDelay").text) commTimeOut = float(xml.find("./Settings/CommTimeOut").text) try: self.comm.connect(str(self.ui.comPort.currentText()), int(self.ui.baudRate.currentText()), bootupDelay, commTimeOut) # Stop and flush any previous telemetry being streamed stopTelemetry = xml.find("./Settings/StopTelemetry").text self.comm.write(stopTelemetry) self.comm.flushResponse() # Request version number to identify AeroQuad board versionRequest = xml.find("./Settings/SoftwareVersion").text self.comm.write(versionRequest) version = self.comm.waitForRead() if version != "": self.storeComPortSelection() self.ui.status.setText("Connected to AeroQuad Flight Software v" + version) # Read board configuration config = xml.find("./Settings/BoardConfiguration").text self.comm.write(config) size = int(self.comm.waitForRead()) for index in range(size): response = self.comm.waitForRead() configuration = response.split(':') self.boardConfiguration[configuration[0]] = configuration[1].strip() # Hide menu items that don't match board configuration for index in range(len(self.subPanelMenu)): hide = self.checkRequirementsMatch(self.subPanelList[index]) self.subPanelMenu[index].setVisible(hide) # Load configuration screen self.selectSubPanel("Vehicle Configuration") self.restartSubPanel() return True else: self.disconnectBoard() self.ui.status.setText("Not connected to the AeroQuad") if self.manualConnect: QtGui.QMessageBox.information(self, "Connection Error", "Unable to connect to the AeroQuad. Try increasing the Boot Up Delay.\nThis is found under File->Preferences->Boot Up Delay.") return False except SerialException: self.ui.buttonDisconnect.setEnabled(False) self.ui.buttonConnect.setEnabled(True) self.ui.comPort.setEnabled(True) self.ui.baudRate.setEnabled(True) self.ui.status.setText("Connection Failed") self.boardConfiguration = {} return False def disconnectBoard(self): '''Disconnect from the AeroQuad''' self.comm.write(xml.find("./Settings/StopTelemetry").text) self.comm.disconnect() # Update GUI self.ui.buttonDisconnect.setEnabled(False) self.ui.buttonConnect.setEnabled(True) self.ui.comPort.setEnabled(True) self.ui.baudRate.setEnabled(True) self.ui.status.setText("Disconnected from the AeroQuad") self.boardConfiguration = {} self.restartSubPanel() def updateDetectedPorts(self): '''Cycles through 256 ports and checks if there is a response from them.''' selection = self.ui.comPort.currentText() if selection == "Refresh": self.updateComPortSelection() self.ui.comPort.setCurrentIndex(0) self.ui.status.setText("Updated list of available COM ports") elif selection == "Autoconnect": self.updateComPortSelection() self.ui.comPort.setCurrentIndex(0) self.ui.status.setText("Beginning autoconnect...") self.autoConnect() def autoConnect(self): self.manualConnect = False for port in xrange(self.ui.comPort.count() - 2): self.ui.comPort.setCurrentIndex(port) self.ui.status.setText("Attempting to connect to " + self.ui.comPort.currentText() + "...") if self.connectBoard(): self.ui.status.setText("Autoconnect successful!") break else: self.ui.status.setText("Autoconnect not successful...") self.manualConnect = True def updateBootUpDelay(self): '''Creates dialog box to ask user for desired boot up delay. This delay waits for Arduino based boards to finish booting up before sending commands. ''' bootUpDelay = float(xml.find("./Settings/BootUpDelay").text) data, ok = QtGui.QInputDialog.getDouble(self, "Boot Up Delay", "Boot Up Delay:", bootUpDelay, 0, 60, 3) if ok: xml.find("./Settings/BootUpDelay").text = str(data) xml.write("AeroQuadConfigurator.xml") def updateCommTimeOut(self): '''Creates dialog box to ask user for desired comm timeout. This is timeout value used by serial drivers to wait for response from device ''' commTimeOut = float(xml.find("./Settings/CommTimeOut").text) data, ok = QtGui.QInputDialog.getDouble(self, "Comm Time Out", "Comm Time Out:", commTimeOut, 0, 60, 3) if ok: xml.find("./Settings/CommTimeOut").text = str(data) xml.write("AeroQuadConfigurator.xml") def updateComPortSelection(self): '''Look for available comm ports and updates combo box''' self.ui.comPort.clear() for n in self.comm.detectPorts(): self.ui.comPort.addItem(n) self.ui.comPort.insertSeparator(self.ui.comPort.count()) self.ui.comPort.addItem("Autoconnect") self.ui.comPort.addItem("Refresh") def storeComPortSelection(self): '''Stores comm port selection to xml file for later recall''' xml.find("./Settings/DefaultBaudRate").text = str(self.ui.baudRate.currentText()) xml.find("./Settings/DefaultComPort").text = str(self.ui.comPort.currentText()) xml.write("AeroQuadConfigurator.xml") def updateBaudRates(self): '''Reads baud rates from xml and displays in combo box. Updates the xml file to display different baud rates ''' defaultBaudRate = xml.find("./Settings/DefaultBaudRate").text baudRates = xml.find("./Settings/AvailableBaudRates").text baudRate = baudRates.split(',') for i in baudRate: self.ui.baudRate.addItem(i) self.ui.baudRate.setCurrentIndex(baudRate.index(defaultBaudRate)) ####### SubPanel Methods ####### def configureSubPanelMenu(self): '''Dynamically add subpanels to View menu based on XML file configuration This also adds the subpanel to a stacked widget and stores object instances so that they can run when selected''' subPanels = xml.findall("./Subpanels/Subpanel") subPanelCount = 1 self.subPanelList = [] # Stores subpanel names self.subPanelClasses = [] # Stores subpanel object instances for subPanel in subPanels: self.subPanelList.append(subPanel.get("Name")) pathName = xml.find("./Subpanels/Subpanel/[@Name='" + subPanel.get("Name") +"']/Path").text className = xml.find("./Subpanels/Subpanel/[@Name='" + subPanel.get("Name") +"']/Class").text packageList = pathName.split('.') packageList.insert(0, 'subpanel') packageString = packageList[0] + '.' + packageList[1] + '.' + packageList[2] module = __import__(packageString) for package in packageList[1:]: # In case the module is buried into a deep package folder, loop until module is reached module = getattr(module, package) module = getattr(module, className) tempSubPanel = module() tempSubPanel.initialize(self.comm, xml, self.ui) self.ui.subPanel.addWidget(tempSubPanel) self.subPanelClasses.append(tempSubPanel) subPanelCount += 1 self.subPanelMapper = QtCore.QSignalMapper(self) self.subPanelMenu = [] for subPanelName in self.subPanelList: subPanel = self.ui.menuView.addAction(subPanelName) self.subPanelMenu.append(subPanel) # Need to store this separately because Python only binds stuff at runtime self.subPanelMapper.setMapping(subPanel, subPanelName) subPanel.triggered.connect(self.subPanelMapper.map) subPanel.setCheckable(True) self.subPanelMapper.mapped[str].connect(self.selectSubPanel) def selectSubPanel(self, subPanelName): '''Places check mark beside selected subpanel name Menu item instances stored in dedicated list because Python only updates during runtime making everything point to the last item in the list ''' if self.activeSubPanel != None: self.activeSubPanel.stop() types = len(self.subPanelList) for index in range(types): self.subPanelMenu[index].setChecked(False) selected = self.subPanelList.index(subPanelName) self.subPanelMenu[selected].setChecked(True) self.ui.subPanel.setCurrentIndex(selected+1) # index 0 is splash screen self.activeSubPanel = self.subPanelClasses[selected] self.activeSubPanelName = "./Subpanels/Subpanel/[@Name='" + str(subPanelName) + "']" self.activeSubPanel.start(self.activeSubPanelName, self.boardConfiguration) self.ui.status.setText(subPanelName) app.processEvents() def clearSubPanelMenu(self): ''' Clear subPanel menu and disconnect subPanel related signals''' self.ui.menuView.clear() self.subPanelMapper.mapped[str].disconnect(self.selectSubPanel) def restartSubPanel(self): if self.activeSubPanel != None: # Restart any running subpanels self.activeSubPanel.stop() self.activeSubPanel.start(self.activeSubPanelName, self.boardConfiguration) app.processEvents() def checkRequirementsMatch(self, subPanelName): # Read requirements for the specified subpanel form the XML config file xmlRequirement = "./Subpanels/Subpanel/[@Name='" + subPanelName +"']/Requirement" subPanelRequirements = xml.findall(xmlRequirement) panelRequirements = {} booleanOperation = {} for requirements in subPanelRequirements: requirement = requirements.text.split(':') if requirement[0] == "All": # Need element 1 populated if "All" detected requirement.append("All") panelRequirements[requirement[0]] = requirement[1].strip() booleanOperation[requirement[0]] = requirements.get("type") # Go through each subpanel requirement and check against board configuration # If no boolean type defined, assume AND requirementType = panelRequirements.keys() # If no Requirement found, assume ALL try: if (requirementType[0] == "All"): check = True else: check = any(panelRequirements[requirementType[0]] in s for s in self.boardConfiguration.values()) for testRequirement in requirementType[1:]: if (booleanOperation[testRequirement] == "or") or (booleanOperation[testRequirement] == "OR"): check = check or any(panelRequirements[testRequirement] in s for s in self.boardConfiguration.values()) else: check = check and any(panelRequirements[testRequirement] in s for s in self.boardConfiguration.values()) except: check = True return check ####### Housekeeping Functions ####### def exit(self): self.comm.disconnect() sys.exit(app.exec_()) def center(self): qr = self.frameGeometry() cp = QtGui.QDesktopWidget().availableGeometry().center() qr.moveCenter(cp) self.move(qr.topLeft())
def __init__(self, ): super(MainWindow, self).__init__() self.ui = Ui_MainWindow() self.ui.setupUi(self) self.modelArray = [QStandardItemModel(), QStandardItemModel()] self.filterProxyArray = [RecursiveProxyModel(), RecursiveProxyModel()] self.treeViewArray = [self.ui.treeView, self.ui.treeView_2] self.pathLabelArray = [self.ui.labelPath, self.ui.labelPath_2] self.setWindowTitle('QDICOMDiffer ' + version) # Read the settings from the settings.ini file system_location = os.path.dirname(os.path.abspath(sys.argv[0])) QSettings.setPath(QSettings.IniFormat, QSettings.SystemScope, system_location) self.settings = QSettings("settings.ini", QSettings.IniFormat) if os.path.exists(system_location + "/settings.ini"): print("Loading settings from " + system_location + "/settings.ini") col = self.settings.value('Appearance/directMatchColour') if col is None: direct_match_colour = default_direct_match_colour else: direct_match_colour = QColor(col) col = self.settings.value('Appearance/indirectMatchColour') if col is None: indirect_match_colour = default_indirect_match_colour else: indirect_match_colour = QColor(col) font = self.settings.value('Appearance/new_font') for i in range(2): self.modelArray[i].setHorizontalHeaderLabels( ['Tag', 'Description', 'Value', 'Different', 'Index']) self.filterProxyArray[i].setSourceModel(self.modelArray[i]) self.ui.lineEditTagFilter.textChanged.connect( self.filterProxyArray[i].set_tag_filter) self.ui.lineEditDescFilter.textChanged.connect( self.filterProxyArray[i].set_desc_filter) self.ui.lineEditValFilter.textChanged.connect( self.filterProxyArray[i].set_value_filter) self.treeViewArray[i].setModel(self.filterProxyArray[i]) self.treeViewArray[i].setEditTriggers( QAbstractItemView.NoEditTriggers) self.treeViewArray[i].setColumnHidden(3, True) self.treeViewArray[i].setColumnHidden(4, True) # the i=i here is needed to ensure i is within the local namespace, without it i evaluates to 1 both times self.ui.checkBoxShowOnlyDifferent.stateChanged.connect( lambda state, i=i: self.filterProxyArray[ i].set_show_only_different(bool(state))) self.treeViewArray[i].file_dropped.connect( lambda filepath, i=i: self.load_file(filepath, i)) self.treeViewArray[i].indirect_match_colour = indirect_match_colour self.treeViewArray[i].direct_match_colour = direct_match_colour if font is not None: self.treeViewArray[i].setFont(font) self.ui.splitter.setSizes([100, 0]) self.ui.actionOpen.triggered.connect(self.open_files) self.ui.actionDiff.triggered.connect(self.do_diff) self.ui.actionHTML_diff.triggered.connect(self.open_html_diff_window) self.ui.actionAbout.triggered.connect(self.open_about_window) self.ui.actionAppearance.triggered.connect(self.open_appearance_window) self.raw_diff_window = None self.html_diff_window = None self.appearance_window = None self.ui.actionText_diff.triggered.connect(self.open_text_diff_window) self.ui.actionExpand_all.triggered.connect(self.expand_all) self.ui.actionCollapse_all.triggered.connect(self.collapse_all) self.dc_array = [None] * 2 self.diff_result = None self.html_diff_result = None # If we were given command line arguments, try and load them arguments = sys.argv[1:] for number, line in enumerate(arguments): self.load_file(line, number) # Only ever handle two files if number == 1: self.ui.splitter.setSizes([50, 50]) break self.show()
class MainWindow(QtWidgets.QMainWindow): def __init__(self, ): super(MainWindow, self).__init__() self.ui = Ui_MainWindow() self.ui.setupUi(self) self.modelArray = [QStandardItemModel(), QStandardItemModel()] self.filterProxyArray = [RecursiveProxyModel(), RecursiveProxyModel()] self.treeViewArray = [self.ui.treeView, self.ui.treeView_2] self.pathLabelArray = [self.ui.labelPath, self.ui.labelPath_2] self.setWindowTitle('QDICOMDiffer ' + version) # Read the settings from the settings.ini file system_location = os.path.dirname(os.path.abspath(sys.argv[0])) QSettings.setPath(QSettings.IniFormat, QSettings.SystemScope, system_location) self.settings = QSettings("settings.ini", QSettings.IniFormat) if os.path.exists(system_location + "/settings.ini"): print("Loading settings from " + system_location + "/settings.ini") col = self.settings.value('Appearance/directMatchColour') if col is None: direct_match_colour = default_direct_match_colour else: direct_match_colour = QColor(col) col = self.settings.value('Appearance/indirectMatchColour') if col is None: indirect_match_colour = default_indirect_match_colour else: indirect_match_colour = QColor(col) font = self.settings.value('Appearance/new_font') for i in range(2): self.modelArray[i].setHorizontalHeaderLabels( ['Tag', 'Description', 'Value', 'Different', 'Index']) self.filterProxyArray[i].setSourceModel(self.modelArray[i]) self.ui.lineEditTagFilter.textChanged.connect( self.filterProxyArray[i].set_tag_filter) self.ui.lineEditDescFilter.textChanged.connect( self.filterProxyArray[i].set_desc_filter) self.ui.lineEditValFilter.textChanged.connect( self.filterProxyArray[i].set_value_filter) self.treeViewArray[i].setModel(self.filterProxyArray[i]) self.treeViewArray[i].setEditTriggers( QAbstractItemView.NoEditTriggers) self.treeViewArray[i].setColumnHidden(3, True) self.treeViewArray[i].setColumnHidden(4, True) # the i=i here is needed to ensure i is within the local namespace, without it i evaluates to 1 both times self.ui.checkBoxShowOnlyDifferent.stateChanged.connect( lambda state, i=i: self.filterProxyArray[ i].set_show_only_different(bool(state))) self.treeViewArray[i].file_dropped.connect( lambda filepath, i=i: self.load_file(filepath, i)) self.treeViewArray[i].indirect_match_colour = indirect_match_colour self.treeViewArray[i].direct_match_colour = direct_match_colour if font is not None: self.treeViewArray[i].setFont(font) self.ui.splitter.setSizes([100, 0]) self.ui.actionOpen.triggered.connect(self.open_files) self.ui.actionDiff.triggered.connect(self.do_diff) self.ui.actionHTML_diff.triggered.connect(self.open_html_diff_window) self.ui.actionAbout.triggered.connect(self.open_about_window) self.ui.actionAppearance.triggered.connect(self.open_appearance_window) self.raw_diff_window = None self.html_diff_window = None self.appearance_window = None self.ui.actionText_diff.triggered.connect(self.open_text_diff_window) self.ui.actionExpand_all.triggered.connect(self.expand_all) self.ui.actionCollapse_all.triggered.connect(self.collapse_all) self.dc_array = [None] * 2 self.diff_result = None self.html_diff_result = None # If we were given command line arguments, try and load them arguments = sys.argv[1:] for number, line in enumerate(arguments): self.load_file(line, number) # Only ever handle two files if number == 1: self.ui.splitter.setSizes([50, 50]) break self.show() def open_about_window(self): msgBox = QMessageBox() msgBox.setWindowTitle("About") msgBox.setTextFormat(Qt.RichText) msgBox.setText( "QDICOMDiffer version " + version + "<br> Written by Keith Offer" + "<br> Relies heavily on the <a href='http://www.pydicom.org/'>pydicom</a> library" ) msgBox.setIcon(QMessageBox.Information) msgBox.exec() def open_appearance_window(self): self.appearance_window = AppearanceWindow(self.settings, parent=self) if self.appearance_window.exec(): self.settings.setValue('Appearance/new_font', self.appearance_window.new_font) self.settings.setValue( 'Appearance/directMatchColour', self.appearance_window.direct_match_colour.name()) self.settings.setValue( 'Appearance/indirectMatchColour', self.appearance_window.indirect_match_colour.name()) for i in range(2): self.treeViewArray[ i].direct_match_colour = self.appearance_window.direct_match_colour self.treeViewArray[ i].indirect_match_colour = self.appearance_window.indirect_match_colour if self.appearance_window.new_font is not None: self.treeViewArray[i].setFont( self.appearance_window.new_font) self.treeViewArray[i].repaint() def open_text_diff_window(self): if self.diff_result is not None: self.raw_diff_window = TextDiffWindow(self.diff_result) else: if self.dc_array[0] is not None and self.dc_array[0] is not None: self.ui.splitter.setSizes([50, 50]) self.do_diff() # Note that the IDE complains that self.diff_result is still None here # It should have a value set by self.do_diff(), so this is not the case if self.diff_result is not None: self.raw_diff_window = TextDiffWindow(self.diff_result) def open_html_diff_window(self): if self.html_diff_result is not None: self.html_diff_window = HTMLDiffWindow(self.html_diff_result) else: if self.dc_array[0] is not None and self.dc_array[0] is not None: self.ui.splitter.setSizes([50, 50]) self.do_diff() # Note that the IDE complains that self.diff_result is still None here # It should have a value set by self.do_diff(), so this is not the case if self.diff_result is not None: self.html_diff_window = HTMLDiffWindow( self.html_diff_result) def collapse_all(self): for i in range(2): self.treeViewArray[i].collapseAll() for n in range(3): self.treeViewArray[i].resizeColumnToContents(n) def expand_all(self): for i in range(2): self.treeViewArray[i].expandAll() for n in range(3): self.treeViewArray[i].resizeColumnToContents(n) self.treeViewArray[i].resizeColumnToContents(n) def set_value_filter(self, filter): for i in range(2): self.filterProxyArray[i].setFilterKeyColumn(2) self.filterProxyArray[i].setFilterFixedString(filter) def show_only_diff(self, new_state): state = (new_state == 2) for i in range(2): self.filterProxyArray[i].setFilterKeyColumn(3) if state: self.filterProxyArray[i].setFilterFixedString(str(int(state))) else: self.filterProxyArray[i].setFilterFixedString('') def open_files(self): filepaths = self.get_file_paths() if len(filepaths) > 2: msgBox = QMessageBox() msgBox.setWindowTitle("Warning") msgBox.setText('Ignoring all but the first two files') msgBox.setIcon(QMessageBox.Warning) msgBox.exec() if len(filepaths) != 0: folder = os.path.dirname(filepaths[0]) if os.path.exists(folder): self.settings.setValue('Browse/LastOpenedLocation', folder) if len(filepaths) == 1: filepath = filepaths[0] if self.dc_array[0] is not None: # We are loading a file and there is already one loaded. Do we want to just view or diff? msgBox = QMessageBox() msgBox.setText( "A file is already loaded. Do you want to diff with the currently loaded file or just load the new file?" ) load_button = QPushButton('Just load the new file') msgBox.addButton(load_button, QMessageBox.YesRole) diff_button = QPushButton( 'Diff with the currently loaded file') msgBox.addButton(diff_button, QMessageBox.NoRole) msgBox.addButton(QPushButton('Cancel'), QMessageBox.RejectRole) msgBox.exec() # Note if no button was pressed, no action is taken if msgBox.clickedButton() == load_button: self.load_file(filepath, 0) elif msgBox.clickedButton() == diff_button: self.load_file(filepath, 1) self.ui.splitter.setSizes([50, 50]) else: pass return for file_number, filepath in enumerate(filepaths): self.load_file(filepath, file_number) if file_number == 1: # If more than two files selected, just break after handling the first two self.ui.splitter.setSizes([50, 50]) break def get_file_paths(self): path_from_settings = self.settings.value('Browse/LastOpenedLocation') default_location = '.' if path_from_settings is not None: if os.path.exists( self.settings.value('Browse/LastOpenedLocation')): default_location = self.settings.value( 'Browse/LastOpenedLocation') """ This looks a bit strange, but filenames are the first return value of this function so we need the [0] on the end to grab what we need """ return QFileDialog.getOpenFileNames(self, 'Open DICOM file ...', default_location)[0] def load_file(self, filepath, file_number): try: dc = pydicom.read_file(filepath) self.dc_array[file_number] = dc self.modelArray[file_number].removeRows( 0, self.modelArray[file_number].rowCount( )) # Remove any rows from old loaded files dict_to_tree( dc, parent=self.modelArray[file_number].invisibleRootItem()) self.pathLabelArray[file_number].setText(filepath) for n in range(3): self.treeViewArray[file_number].resizeColumnToContents(n) if file_number == 1: reset_tree_diff_state( self.modelArray[0].invisibleRootItem()) else: reset_tree_diff_state( self.modelArray[1].invisibleRootItem()) self.diff_result = None self.html_diff_result = None except pydicom.errors.InvalidDicomError: msgBox = QMessageBox() msgBox.setWindowTitle("Error") msgBox.setText('Failed to open ' + filepath + ' (is it a valid DICOM file?)') msgBox.setIcon(QMessageBox.Critical) msgBox.exec() def do_diff(self): self.diffProgressWindow = DiffProgressWindow(self.dc_array, self.modelArray, parent=self) if self.diffProgressWindow.exec(): self.html_diff_result = self.diffProgressWindow.get_html_diff_result( ) self.diff_result = self.diffProgressWindow.get_diff_result()
def __init__(self): super(MainWindow, self).__init__() self.ui = Ui_MainWindow() self.ui.setupUi(self) self.setWindowTitle('QDICOMMiner ' + __version__) # TODO: Find a better way of stopping the selection of list items. This only sortof works self.ui.listWidget.setSelectionMode(QAbstractItemView.NoSelection) system_location = os.path.dirname(os.path.realpath(__file__)) plugin_locations = [os.path.join(system_location, 'Plugins')] self.plugin_manager = PluginManager() self.plugin_manager.setPluginPlaces(plugin_locations) self.plugin_manager.collectPlugins() self.plugin_list = [] for plugin in self.plugin_manager.getAllPlugins(): self.plugin_list.append(plugin.name) # Read the settings from the settings.ini file system_location = os.path.dirname(os.path.realpath(__file__)) QSettings.setPath(QSettings.IniFormat, QSettings.SystemScope, system_location) self.settings = QSettings("settings.ini", QSettings.IniFormat) if os.path.exists(system_location + "/settings.ini"): print("Loading settings from " + system_location + "/settings.ini") # Set the last used output file and analyse folder locations output_file = self.settings.value('main/lastOutputFile') if output_file is None: output_file = 'data.csv' if not os.path.isabs(output_file): abs_output_location = os.path.join(system_location, output_file) self.ui.labelOutputFile.setText(abs_output_location) else: self.ui.labelOutputFile.setText(output_file) folder_to_analyse = self.settings.value('main/lastAnalyseFolder') if folder_to_analyse is None: folder_to_analyse = '' if not os.path.isabs(folder_to_analyse): abs_folder_to_analyse = os.path.join(system_location, folder_to_analyse) self.ui.labelFolderToAnalysePath.setText(abs_folder_to_analyse) else: self.ui.labelFolderToAnalysePath.setText(folder_to_analyse) self.ui.labelFolderToAnalysePath.clicked.connect( lambda: self.open_folder_in_explorer( self.ui.labelFolderToAnalysePath.text())) self.ui.labelOutputFile.clicked.connect( lambda: self.open_folder_in_explorer(self.ui.labelOutputFile.text( ))) # Setup a dictionary of key DICOM description and value the DICOM tag names = [] self.DICOM_dic = {} for key in dicom_dict.DicomDictionary: names.append(dicom_dict.DicomDictionary[key][2]) self.DICOM_dic[dicom_dict.DicomDictionary[key][2]] = key self.completer = QCompleter() self.completer.setCaseSensitivity(Qt.CaseInsensitive) self.model = QStringListModel() self.model.setStringList(names) self.completer.setModel(self.model) self.ui.pushButtonAddListWidget.clicked.connect( self.add_new_list_widget) self.ui.pushButtonBrowseFolderToAnalysePath.clicked.connect( self.browse_for_input_folder) self.ui.pushButtonBrowseOutputFilePath.clicked.connect( self.browse_for_output_file) self.ui.pushButtonDoAnalysis.clicked.connect(self.do_analysis) self.count_num_of_files_thread = CountFilesThread() self.count_num_of_files_thread.num_of_files.connect( self.update_number_of_files) self.count_file_number.connect(self.count_num_of_files_thread.count) self.count_file_number.emit(self.ui.labelFolderToAnalysePath.text() ) # Using a signal to keep thread safety self.ui.progressBar.setFormat(' %v/%m (%p%)') self.ui.progressBar.hide() self.ui.actionSave_Template.triggered.connect(self.save_template) self.ui.actionLoad_Template.triggered.connect(self.load_template) self.ui.actionAbout.triggered.connect(self.open_about_window) self.analyse_and_output_data_thread = AnalyseAndOutputDataThread() self.show()
class MainWindow(QtWidgets.QMainWindow): def __init__(self): super(MainWindow, self).__init__() self.ui = Ui_MainWindow() self.ui.setupUi(self) self.setWindowTitle('QDICOMMiner ' + __version__) # TODO: Find a better way of stopping the selection of list items. This only sortof works self.ui.listWidget.setSelectionMode(QAbstractItemView.NoSelection) system_location = os.path.dirname(os.path.realpath(__file__)) plugin_locations = [os.path.join(system_location, 'Plugins')] self.plugin_manager = PluginManager() self.plugin_manager.setPluginPlaces(plugin_locations) self.plugin_manager.collectPlugins() self.plugin_list = [] for plugin in self.plugin_manager.getAllPlugins(): self.plugin_list.append(plugin.name) # Read the settings from the settings.ini file system_location = os.path.dirname(os.path.realpath(__file__)) QSettings.setPath(QSettings.IniFormat, QSettings.SystemScope, system_location) self.settings = QSettings("settings.ini", QSettings.IniFormat) if os.path.exists(system_location + "/settings.ini"): print("Loading settings from " + system_location + "/settings.ini") # Set the last used output file and analyse folder locations output_file = self.settings.value('main/lastOutputFile') if output_file is None: output_file = 'data.csv' if not os.path.isabs(output_file): abs_output_location = os.path.join(system_location, output_file) self.ui.labelOutputFile.setText(abs_output_location) else: self.ui.labelOutputFile.setText(output_file) folder_to_analyse = self.settings.value('main/lastAnalyseFolder') if folder_to_analyse is None: folder_to_analyse = '' if not os.path.isabs(folder_to_analyse): abs_folder_to_analyse = os.path.join(system_location, folder_to_analyse) self.ui.labelFolderToAnalysePath.setText(abs_folder_to_analyse) else: self.ui.labelFolderToAnalysePath.setText(folder_to_analyse) self.ui.labelFolderToAnalysePath.clicked.connect( lambda: self.open_folder_in_explorer( self.ui.labelFolderToAnalysePath.text())) self.ui.labelOutputFile.clicked.connect( lambda: self.open_folder_in_explorer(self.ui.labelOutputFile.text( ))) # Setup a dictionary of key DICOM description and value the DICOM tag names = [] self.DICOM_dic = {} for key in dicom_dict.DicomDictionary: names.append(dicom_dict.DicomDictionary[key][2]) self.DICOM_dic[dicom_dict.DicomDictionary[key][2]] = key self.completer = QCompleter() self.completer.setCaseSensitivity(Qt.CaseInsensitive) self.model = QStringListModel() self.model.setStringList(names) self.completer.setModel(self.model) self.ui.pushButtonAddListWidget.clicked.connect( self.add_new_list_widget) self.ui.pushButtonBrowseFolderToAnalysePath.clicked.connect( self.browse_for_input_folder) self.ui.pushButtonBrowseOutputFilePath.clicked.connect( self.browse_for_output_file) self.ui.pushButtonDoAnalysis.clicked.connect(self.do_analysis) self.count_num_of_files_thread = CountFilesThread() self.count_num_of_files_thread.num_of_files.connect( self.update_number_of_files) self.count_file_number.connect(self.count_num_of_files_thread.count) self.count_file_number.emit(self.ui.labelFolderToAnalysePath.text() ) # Using a signal to keep thread safety self.ui.progressBar.setFormat(' %v/%m (%p%)') self.ui.progressBar.hide() self.ui.actionSave_Template.triggered.connect(self.save_template) self.ui.actionLoad_Template.triggered.connect(self.load_template) self.ui.actionAbout.triggered.connect(self.open_about_window) self.analyse_and_output_data_thread = AnalyseAndOutputDataThread() self.show() @staticmethod def open_folder_in_explorer(location): if os.path.isfile(location): folder_location = os.path.dirname(location) else: folder_location = location QDesktopServices.openUrl(QUrl.fromLocalFile(folder_location)) def save_template(self): dic = { 'DICOM_tag': [], 'File_information': [], 'Custom_plugins': [], 'Version': __version__ } for index in range(self.ui.listWidget.count()): custom_widget = self.ui.listWidget.itemWidget( self.ui.listWidget.item(index)) if custom_widget.comboBoxAttributeChoice.currentText( ) == AttributeOptions.DICOM_TAG.value: text = custom_widget.lineEdit.text() dic['DICOM_tag'].append(text) elif custom_widget.comboBoxAttributeChoice.currentText( ) == AttributeOptions.FILE_INFORMATION.value: text = custom_widget.comboBoxFileOption.currentText() dic['File_information'].append(text) elif custom_widget.comboBoxAttributeChoice.currentText( ) == AttributeOptions.CUSTOM_PLUGIN.value: text = custom_widget.comboBoxPluginOption.currentText() dic['Custom_plugins'].append(text) else: raise NotImplementedError filepath = QFileDialog.getSaveFileName(self, 'Save template file', '.', '(*.json)')[0] if filepath != '': if not filepath.endswith('.json'): filepath += '.json' with open(filepath, 'w') as f: json.dump(dic, f) def load_template(self): filepath = QFileDialog.getOpenFileName(self, 'Load template file', '.', '*.json')[0] if filepath != '': with open(filepath, 'r') as f: try: dic = json.load(f) except json.JSONDecodeError: msg_box = QMessageBox() msg_box.setWindowTitle("Error") msg_box.setText('Failed to open ' + filepath + ' (not a valid JSON file)') msg_box.setIcon(QMessageBox.Critical) msg_box.exec() return self.ui.listWidget.clear() try: if 'DICOM_tag' in dic: for tag in dic['DICOM_tag']: self.add_new_list_widget(default_text=tag) if 'File_information' in dic: for file_option in dic['File_information']: self.add_new_list_widget( attribute_type=AttributeOptions. FILE_INFORMATION, combo_box_text=file_option) if 'Custom_plugins' in dic: for plugin_name in dic['Custom_plugins']: if plugin_name not in [ plugin.name for plugin in self.plugin_manager.getAllPlugins() ]: msg_box = QMessageBox() msg_box.setWindowTitle("Warning") msg_box.setText( 'A required custom plugin (' + plugin_name + ") isn't installed and will be skipped") msg_box.setIcon(QMessageBox.Warning) msg_box.exec() else: self.add_new_list_widget( attribute_type=AttributeOptions. CUSTOM_PLUGIN, combo_box_text=plugin_name) except KeyError: msg_box = QMessageBox() msg_box.setWindowTitle("Error") msg_box.setText('Failed to apply template file ' + filepath + ' (Missing information in template file)') msg_box.setIcon(QMessageBox.Critical) msg_box.exec() return @staticmethod def open_about_window(): msg_box = QMessageBox() msg_box.setWindowTitle("About") msg_box.setTextFormat(Qt.RichText) msg_box.setText( "QDICOMMiner version " + __version__ + "<br> Written by Keith Offer" + "<br> Relies heavily on the <a href='http://www.pydicom.org/'>pydicom</a> library" ) msg_box.setIcon(QMessageBox.Information) msg_box.exec() def update_number_of_files(self, num): self.ui.labelNumberOfFiles.setText(str(num) + ' files') self.ui.progressBar.setMaximum(num) # The checked variable is emitted from the signal, but we don't use it here def add_new_list_widget(self, checked=False, default_text='', attribute_type=AttributeOptions.DICOM_TAG, combo_box_text=None): new_list_widget_item = QListWidgetItem() custom_widget = CustomListWidget(self, self.plugin_list) new_list_widget_item.setSizeHint(custom_widget.sizeHint()) custom_widget.lineEdit.setCompleter(self.completer) custom_widget.lineEdit.textChanged.connect(self.line_edit_text_changed) custom_widget.pushButton.clicked.connect( lambda: self.remove_widget_from_list(new_list_widget_item)) if attribute_type == AttributeOptions.DICOM_TAG: custom_widget.lineEdit.setText(default_text) elif attribute_type == AttributeOptions.FILE_INFORMATION: # TODO: Should I throw an exception / error message if the index isn't >= 0? file_info_index = custom_widget.comboBoxFileOption.findText( combo_box_text) if file_info_index >= 0: custom_widget.comboBoxFileOption.setCurrentIndex( file_info_index) custom_widget.comboBoxFileOption.setVisible(True) custom_widget.lineEdit.setVisible(False) elif attribute_type == AttributeOptions.CUSTOM_PLUGIN: plugin_index = custom_widget.comboBoxPluginOption.findText( combo_box_text) if plugin_index >= 0: custom_widget.comboBoxPluginOption.setCurrentIndex( plugin_index) custom_widget.comboBoxPluginOption.setVisible(True) custom_widget.lineEdit.setVisible(False) else: raise NotImplementedError attribute_index = custom_widget.comboBoxAttributeChoice.findText( attribute_type.value) if attribute_index >= 0: custom_widget.comboBoxAttributeChoice.setCurrentIndex( attribute_index) self.ui.listWidget.addItem(new_list_widget_item) self.ui.listWidget.setItemWidget(new_list_widget_item, custom_widget) def line_edit_text_changed(self, new_string): sending_line_edit = self.sender() if new_string != '' and (new_string in self.DICOM_dic or re.match(dicom_tag_regex, new_string)): sending_line_edit.setStyleSheet( "QLineEdit { background: rgb(0, 255, 0); }") else: sending_line_edit.setStyleSheet( "QLineEdit { background: rgb(255, 0, 0); }") def remove_widget_from_list(self, list_widget_item): self.ui.listWidget.takeItem(self.ui.listWidget.row(list_widget_item)) def browse_for_input_folder(self): starting_location = self.settings.value('main/lastAnalyseFolder') if starting_location is None: starting_location = '.' filepath = QFileDialog.getExistingDirectory(self, 'Input directory', starting_location) if filepath != '': self.ui.labelFolderToAnalysePath.setText(filepath) self.count_file_number.emit(filepath) self.settings.setValue('main/lastAnalyseFolder', filepath) def browse_for_output_file(self): starting_location = self.settings.value('main/lastOutputFile') if starting_location is None: starting_location = '.' # This looks a bit strange, but filenames are the first return value of this function # so we need the [0] on the end to grab what we need filepath = QFileDialog.getSaveFileName(self, 'Output file', starting_location, '(*.csv)')[0] if filepath != '': if not filepath.endswith('.csv'): filepath += '.csv' self.ui.labelOutputFile.setText(filepath) self.settings.setValue('main/lastOutputFile', filepath) def do_analysis(self): if os.path.exists(self.ui.labelOutputFile.text()): msg_box = QMessageBox() msg_box.setIcon(QMessageBox.Question) msg_box.setText( "The output file " + self.ui.labelOutputFile.text() + " already exists. Are you sure you want to overwrite it?") overwrite_button = QPushButton('Overwrite') msg_box.addButton(overwrite_button, QMessageBox.YesRole) msg_box.addButton(QPushButton('Cancel'), QMessageBox.RejectRole) msg_box.exec() if msg_box.clickedButton() != overwrite_button: return header_DICOM = '' header_file_info = '' header_custom_plugins = '' dicom_tags = [] file_attributes = [] selected_plugins = [] # Generate the CSV header for index in range(self.ui.listWidget.count()): custom_widget = self.ui.listWidget.itemWidget( self.ui.listWidget.item(index)) if custom_widget.comboBoxAttributeChoice.currentText( ) == AttributeOptions.FILE_INFORMATION.value: # Handle file attributes (e.g. path, size etc) header_file_info += custom_widget.comboBoxFileOption.currentText( ) + ',' file_attributes.append( custom_widget.comboBoxFileOption.currentText()) elif custom_widget.comboBoxAttributeChoice.currentText( ) == AttributeOptions.DICOM_TAG.value: # Handle DICOM tags text = custom_widget.lineEdit.text() try: if text == '': # We have to manually raise this as searching for '' won't throw an exception but won't work raise KeyError if re.match(dicom_tag_regex, text): search_results = re.search(dicom_tag_regex, text) # Note that group 0 is the whole match that we don't want dicom_tags.append( (search_results.group(1), search_results.group(2))) else: dicom_tags.append( self.DICOM_dic[custom_widget.lineEdit.text()]) except KeyError: msg_box = QMessageBox() msg_box.setWindowTitle("Error") msg_box.setText('"' + text + '" is not a valid attribute') msg_box.setIcon(QMessageBox.Critical) msg_box.exec() return header_DICOM += text.replace(',', ' ') + ',' elif custom_widget.comboBoxAttributeChoice.currentText( ) == AttributeOptions.CUSTOM_PLUGIN.value: plugin_name = custom_widget.comboBoxPluginOption.currentText() if plugin_name != '': header_custom_plugins += self.plugin_manager.getPluginByName( plugin_name).plugin_object.column_headers() selected_plugins.append(plugin_name) else: raise NotImplementedError csv_header = (header_file_info + header_DICOM + header_custom_plugins)[0:-1] # Remove the last comma self.ui.progressBar.show() self.analyse_and_output_data_thread.current_file.connect( lambda num: self.ui.progressBar.setValue(num)) self.create_csv.connect(self.analyse_and_output_data_thread.run) self.analyse_and_output_data_thread.finished.connect( self.csv_making_finished) self.create_csv.emit(self.ui.labelOutputFile.text(), self.ui.labelFolderToAnalysePath.text(), csv_header, dicom_tags, file_attributes, selected_plugins) def csv_making_finished(self): self.ui.progressBar.hide() create_csv = pyqtSignal(str, str, str, list, list, list) count_file_number = pyqtSignal(str)
self.ui.pushButton.setProperty('enabled', False) self.ui.progressBar.setProperty('value', 0) cursor = self.ui.textBrowser.textCursor() cursor.select(QTextCursor.Document) cursor.removeSelectedText() self.ui.textBrowser.setTextCursor(cursor) @pyqtSlot(dict) def callback(self, params: dict): self.ui.textBrowser.insertPlainText(params['msg'] + '\n') self.ui.textBrowser.moveCursor(QTextCursor.End) if 'num' in params.keys(): self.ui.progressBar.setProperty('value', params['num'] if params['num'] >= 0 else 0) self.progress_num = params['num'] if self.progress_num >= 100 or self.progress_num == -1: self.ui.pushButton.setProperty('enabled', True) self.thread.exit() if __name__ == '__main__': app = QApplication(sys.argv) file = QFile("./BreezeStyleSheets/light.qss") file.open(QFile.ReadOnly | QFile.Text) stream = QTextStream(file) app.setStyleSheet(stream.readAll()) main = MainWindow(Ui_MainWindow()) main.show() sys.exit(app.exec_())
import sys from controller.MyMainWindow import MyMainWindow from ui.mainWindow import Ui_MainWindow from PyQt5 import QtWidgets if __name__ == '__main__': app = QtWidgets.QApplication(sys.argv) mainWindow = MyMainWindow() Ui_MainWindow().setupUi(mainWindow) mainWindow.show() sys.exit(app.exec_())
class AQMain(QtGui.QMainWindow): def __init__(self, parent=None): QtGui.QWidget.__init__(self, parent) self.ui = Ui_MainWindow() self.ui.setupUi(self) # TODO: figure out way to configure for different comm types (TCP, MAVLINK, etc) self.comm = AQSerial() # Default main window conditions self.ui.buttonDisconnect.setEnabled(False) self.ui.buttonConnect.setEnabled(True) self.ui.comPort.setEnabled(True) self.ui.baudRate.setEnabled(True) self.ui.status.setText("Not connected to the AeroQuad") self.availablePorts = [] self.updateComPortSelection() self.updateBaudRates() self.boardConfiguration = [] # Update comm port combo box to use last used comm port defaultComPort = xml.find("./Settings/DefaultComPort").text commIndex = self.ui.comPort.findText(defaultComPort) if commIndex == -1: commIndex = 0 self.ui.comPort.setCurrentIndex(commIndex) # Load splash screen splash = Ui_splashScreen() splash.setupUi(splash) self.ui.subPanel.addWidget(splash) # Dynamically configure board type menu and subPanel menu from XML configuration file self.configureSubPanelMenu() self.activeSubPanel = None self.activeSubPanelName = "" # Connect GUI slots and signals self.ui.comPort.return_handler = self.connect self.ui.buttonConnect.clicked.connect(self.connect) self.ui.buttonDisconnect.clicked.connect(self.disconnect) self.ui.actionExit.triggered.connect(QtGui.qApp.quit) self.ui.comPort.currentIndexChanged.connect(self.updateDetectedPorts) self.ui.actionBootUpDelay.triggered.connect(self.updateBootUpDelay) self.ui.actionCommTimeout.triggered.connect(self.updateCommTimeOut) ####### Communication Methods ####### def connect(self): '''Initiates communication with the AeroQuad''' # Setup GUI self.ui.status.setText("Connecting...") self.ui.buttonDisconnect.setEnabled(True) self.ui.buttonConnect.setEnabled(False) self.ui.comPort.setEnabled(False) self.ui.baudRate.setEnabled(False) # Update the GUI app.processEvents() # Setup serial port bootupDelay = float(xml.find("./Settings/BootUpDelay").text) commTimeOut = float(xml.find("./Settings/CommTimeOut").text) try: self.comm.connect(str(self.ui.comPort.currentText()), int(self.ui.baudRate.currentText()), bootupDelay, commTimeOut) # Stop and flush any previous telemetry being streamed stopTelemetry = xml.find("./Settings/StopTelemetry").text self.comm.write(stopTelemetry) self.comm.flushResponse() # Request version number to identify AeroQuad board versionRequest = xml.find("./Settings/SoftwareVersion").text self.comm.write(versionRequest) version = self.comm.waitForRead() if version != "": self.storeComPortSelection() self.ui.status.setText( "Connected to AeroQuad Flight Software v" + version) # Read board configuration config = xml.find("./Settings/BoardConfiguration").text self.comm.write(config) size = int(self.comm.waitForRead()) for index in range(size): response = self.comm.waitForRead() self.boardConfiguration.append(response) # Hide menu items that don't match board configuration for index in range(len(self.subPanelMenu)): hide = self.checkRequirementsMatch( self.subPanelList[index]) self.subPanelMenu[index].setVisible(hide) # Load configuration screen self.selectSubPanel("Vehicle Configuration") self.restartSubPanel() else: self.disconnect() self.ui.status.setText("Not connected to the AeroQuad") QtGui.QMessageBox.information( self, "Connection Error", "Unable to connect to the AeroQuad. Try increasing the Boot Up Delay.\nThis is found under File->Preferences->Boot Up Delay." ) except SerialException, se: self.ui.buttonDisconnect.setEnabled(False) self.ui.buttonConnect.setEnabled(True) self.ui.comPort.setEnabled(True) self.ui.baudRate.setEnabled(True) self.ui.status.setText("Connection Failed") self.boardConfiguration = []
if mode == 'CLI': #CLI logger.info("Mode selected ({})".format(mode)) from Console import Console console = Console() console.run() elif mode == 'GUI': #GUI logger.info("Mode selected ({})".format(mode)) from PyQt5.QtWidgets import QApplication, QMainWindow from ui.mainWindow import Ui_MainWindow app = QApplication(sys.argv) logger.info("QApp initialized...") MainWindow = QMainWindow() logger.info("QMainWindow Initialized") ui = Ui_MainWindow() ui.setupUi(MainWindow) logger.info("ui setup with MainWindow as parent...") MainWindow.show() logger.info("MainWindow shown...") sys.exit(app.exec_()) elif mode == 'TEST': #Debug mode in CLI output_stream.setLevel(logging.DEBUG) logger.info("Mode selected ({})".format(mode)) fsm = FSM("fsm_setup.txt") result = fsm.evaluate("213") logger.info("Result: {}".format(result)) #from Console import Console #console = Console()
def __init__(self, model): ''' initialization of class ''' super().__init__() self.value = str() # Current Model we use self._model = model # Application's UI main window self._ui = Ui_MainWindow() # Setup main window self._ui.setupUi(self) #setup numpad self.qDialog = QDialog() self.numpadWindow = Ui_Numpad() self.numpadWindow.setupUi(self.qDialog) self.numpadWindow.numberToSet.setAlignment(QtCore.Qt.AlignRight) self.numpadWindow.numberToSet.setMaxLength(6) self.numpadWindow.numberToSet.setReadOnly(True) self.qLineValidator = QtGui.QIntValidator self.numpadButtons = [ self.numpadWindow.button0, self.numpadWindow.button1, self.numpadWindow.button2, self.numpadWindow.button3, self.numpadWindow.button4, self.numpadWindow.button5, self.numpadWindow.button6, self.numpadWindow.button7, self.numpadWindow.button8, self.numpadWindow.button9 ] for idx, item in enumerate(self.numpadButtons): #print(type(item)) #print(idx) item.setText(str(idx)) item.clicked.connect(lambda: self._buttonpres()) self.numpadWindow.buttonBackspace.clicked.connect(self._dellast) self.numpadWindow.buttonSub.clicked.connect(self._negateValue) # Set UI input objects to default state/value self._setUiInputsToDefault() # Connect UI elements/objects signals and slots self._connectSignalSlots() # Add menu bar items self._addMenubarItems() # Setup Plots self._bvddPlot = self._ui.graphicsViewBVDD.plot([], [], pen=(0, 0, 255)) self._ui.graphicsViewBVDD.setLabel('left', 'BVDD', units='V') self._ui.graphicsViewBVDD.setLabel('bottom', 'Time', units='s') self._ui.graphicsViewBVDD.showGrid(x=True, y=True) self._ui.graphicsViewBVDD.setYRange( DefinedValues.BVDD_Y_MIN_RANGE.value, DefinedValues.BVDD_Y_MAX_RANGE.value, 0, True) self._temperaturePlot = self._ui.graphicsViewTemperature.plot([], [], pen=(0, 255, 0)) self._ui.graphicsViewTemperature.setLabel('left', 'Tj', units='°C') self._ui.graphicsViewTemperature.setLabel('bottom', 'Time', units='s') self._ui.graphicsViewTemperature.showGrid(x=True, y=True) self._ui.graphicsViewTemperature.setYRange( DefinedValues.TJ_Y_MIN_RANGE.value, DefinedValues.TJ_Y_MAX_RANGE.value, 0, True) self._rotorSpeedPlot = self._ui.graphicsViewRotorSpeed.plot([], [], pen=(255, 0, 0)) self._ui.graphicsViewRotorSpeed.setLabel('left', 'Rotor Speed', units='PPS') self._ui.graphicsViewRotorSpeed.setLabel('bottom', 'Time', units='s') self._ui.graphicsViewRotorSpeed.showGrid(x=True, y=True) self._ui.graphicsViewRotorSpeed.setYRange( DefinedValues.RS_Y_MIN_RANGE.value, DefinedValues.RS_Y_MAX_RANGE.value, 0, True) self.__rawCurrentSpeed = 0 self.__currentSpeed = 0 self._picIndex = 0 # first image to be replaced self._picTimer = QTimer() self._picTimer.timeout.connect(self.updatePic) self._picTimer.start(DefinedValues.PIC_SWAP_TIME.value)
class Controller(QtWidgets.QMainWindow): ''' Responsible for updating of UI/View. Control and updates of model with new data. ''' def __init__(self, model): ''' initialization of class ''' super().__init__() self.value = str() # Current Model we use self._model = model # Application's UI main window self._ui = Ui_MainWindow() # Setup main window self._ui.setupUi(self) #setup numpad self.qDialog = QDialog() self.numpadWindow = Ui_Numpad() self.numpadWindow.setupUi(self.qDialog) self.numpadWindow.numberToSet.setAlignment(QtCore.Qt.AlignRight) self.numpadWindow.numberToSet.setMaxLength(6) self.numpadWindow.numberToSet.setReadOnly(True) self.qLineValidator = QtGui.QIntValidator self.numpadButtons = [ self.numpadWindow.button0, self.numpadWindow.button1, self.numpadWindow.button2, self.numpadWindow.button3, self.numpadWindow.button4, self.numpadWindow.button5, self.numpadWindow.button6, self.numpadWindow.button7, self.numpadWindow.button8, self.numpadWindow.button9 ] for idx, item in enumerate(self.numpadButtons): #print(type(item)) #print(idx) item.setText(str(idx)) item.clicked.connect(lambda: self._buttonpres()) self.numpadWindow.buttonBackspace.clicked.connect(self._dellast) self.numpadWindow.buttonSub.clicked.connect(self._negateValue) # Set UI input objects to default state/value self._setUiInputsToDefault() # Connect UI elements/objects signals and slots self._connectSignalSlots() # Add menu bar items self._addMenubarItems() # Setup Plots self._bvddPlot = self._ui.graphicsViewBVDD.plot([], [], pen=(0, 0, 255)) self._ui.graphicsViewBVDD.setLabel('left', 'BVDD', units='V') self._ui.graphicsViewBVDD.setLabel('bottom', 'Time', units='s') self._ui.graphicsViewBVDD.showGrid(x=True, y=True) self._ui.graphicsViewBVDD.setYRange( DefinedValues.BVDD_Y_MIN_RANGE.value, DefinedValues.BVDD_Y_MAX_RANGE.value, 0, True) self._temperaturePlot = self._ui.graphicsViewTemperature.plot([], [], pen=(0, 255, 0)) self._ui.graphicsViewTemperature.setLabel('left', 'Tj', units='°C') self._ui.graphicsViewTemperature.setLabel('bottom', 'Time', units='s') self._ui.graphicsViewTemperature.showGrid(x=True, y=True) self._ui.graphicsViewTemperature.setYRange( DefinedValues.TJ_Y_MIN_RANGE.value, DefinedValues.TJ_Y_MAX_RANGE.value, 0, True) self._rotorSpeedPlot = self._ui.graphicsViewRotorSpeed.plot([], [], pen=(255, 0, 0)) self._ui.graphicsViewRotorSpeed.setLabel('left', 'Rotor Speed', units='PPS') self._ui.graphicsViewRotorSpeed.setLabel('bottom', 'Time', units='s') self._ui.graphicsViewRotorSpeed.showGrid(x=True, y=True) self._ui.graphicsViewRotorSpeed.setYRange( DefinedValues.RS_Y_MIN_RANGE.value, DefinedValues.RS_Y_MAX_RANGE.value, 0, True) self.__rawCurrentSpeed = 0 self.__currentSpeed = 0 self._picIndex = 0 # first image to be replaced self._picTimer = QTimer() self._picTimer.timeout.connect(self.updatePic) self._picTimer.start(DefinedValues.PIC_SWAP_TIME.value) def updatePic(self): ''' cyclical switch of banner pic ''' self._picIndex += 1 # next image if (self._picIndex > 5): # last image reached ? self._picIndex = 0 if (self._picIndex == 0): self._ui.ImageButton.setStyleSheet( "border-image: url(:/Image/resources/NT_BannerSW31.png);") elif (self._picIndex == 1): self._ui.ImageButton.setStyleSheet( "border-image: url(:/Image/resources/NT_BannerSW32.png);") elif (self._picIndex == 2): self._ui.ImageButton.setStyleSheet( "border-image: url(:/Image/resources/NT_BannerSW33.png);") elif (self._picIndex == 3): self._ui.ImageButton.setStyleSheet( "border-image: url(:/Image/resources/NT_BannerSW34.png);") elif (self._picIndex == 4): self._ui.ImageButton.setStyleSheet( "border-image: url(:/Image/resources/NT_BannerSW35.png);") elif (self._picIndex == 5): self._ui.ImageButton.setStyleSheet( "border-image: url(:/Image/resources/NT_BannerSW36.png);") else: self._picIndex = 0 #default never reached def updateCurrentPosition(self, position): ''' Set current position displayed in 'Current Status' LCD element. ''' self._ui.currentPositionLCD.display(position) def setStatusIndicator(self, statusIndicator): ''' Set status indicator elements. ''' if Status.OVER_CURRENT == statusIndicator: self._ui.labelOverCurrentLED.setPixmap( QtGui.QPixmap(":/led/resources/red-led-on.png")) elif Status.NO_OVER_CURRENT == statusIndicator: self._ui.labelOverCurrentLED.setPixmap( QtGui.QPixmap(":/led/resources/red-led-off.png")) elif Status.OVER_TEMPERATURE == statusIndicator: self._ui.labelOverTemperatureLED.setPixmap( QtGui.QPixmap(":/led/resources/red-led-on.png")) elif Status.NO_OVER_TEMPERATURE == statusIndicator: self._ui.labelOverTemperatureLED.setPixmap( QtGui.QPixmap(":/led/resources/red-led-off.png")) elif Status.ERROR == statusIndicator: self._ui.labelErrorLED.setPixmap( QtGui.QPixmap(":/led/resources/red-led-on.png")) elif Status.NO_ERROR == statusIndicator: self._ui.labelErrorLED.setPixmap( QtGui.QPixmap(":/led/resources/red-led-off.png")) elif Status.TARGET_ONLINE == statusIndicator: self._ui.labelTargetOnlineLED.setPixmap( QtGui.QPixmap(":/led/resources/green-led-on.png")) elif Status.TARGET_OFFLINE == statusIndicator: self._ui.labelTargetOnlineLED.setPixmap( QtGui.QPixmap(":/led/resources/green-led-off.png")) else: #Nothing to do here pass def updateBvddPlot(self, plotData): ''' Updates BVDD plot with plotData. ''' self._bvddPlot.setData(plotData.x, plotData.y) def updateTemeperatuePlot(self, plotData): ''' Updates Temperature plot with plotData. ''' self._temperaturePlot.setData(plotData.x, plotData.y) def updateRotorSpeedPlot(self, plotData): ''' Updates Rotor Speed plot with plotData. ''' self._rotorSpeedPlot.setData(plotData.x, plotData.y) def updateCurrentSpeed(self, speed): ''' get current speed value ''' self.__rawCurrentSpeed = speed def getCurrentSpeedRPM(self): ''' returns the current speed''' self.__currentSpeedRPM = (self.__rawCurrentSpeed * DefinedValues.RPM_FACTOR.value) return self.__currentSpeedRPM def buttonLearnMore(self): ''' open micronas webaddress ''' webbrowser.open(DefinedValues.LEARN_MORE_WEB_ADDRESS.value) def showErrorDialog(self, errorMsg): ''' Shows a modal error message dialog. errorMsg: Message (string) displayed in dialog. ''' msg = QtWidgets.QErrorMessage() msg.setWindowTitle("ERROR MESSAGE") msg.setWindowModality(QtCore.Qt.ApplicationModal) msg.showMessage(errorMsg) msg.exec_() def _addMenubarItems(self): ''' create sub items in menu bar and connect with actions ''' action = QtWidgets.QAction("Connect", self) action.triggered.connect(self._onMenuBarItemSelectComPort) self._ui.menuSettings.addAction(action) action = QtWidgets.QAction("License", self) action.triggered.connect(self._onLicense) self._ui.menuClose.addAction(action) action = QtWidgets.QAction("Exit", self) action.triggered.connect(self._closeApp) self._ui.menuClose.addAction(action) def _connectSignalSlots(self): ''' Connect UI's signals and slots connections. ''' # Connect push buttons self._ui.pushButtonEnableMotorCtrl.clicked.connect( self._onPushButtonEnableMotorCtrl) self._ui.pushButtonResetInputs.clicked.connect( self._onPushButtonResetInputs) # Connect Direction radio buttons self._ui.radioButtonDirectionStop.clicked.connect( self._onRadioButtonDirection) self._ui.radioButtonDirectionClockWise.clicked.connect( self._onRadioButtonDirection) self._ui.radioButtonDirectionAntiClockwise.clicked.connect( self._onRadioButtonDirection) # Connect Control radio buttons self._ui.radioButtonPositionCtrl.clicked.connect( self._onRadioButtonControlSelection) self._ui.radioButtonSpeedCtrl.clicked.connect( self._onRadioButtonControlSelection) # Connect speed slider control self._ui.speedSlider.valueChanged.connect(self._updateSpeedGroupBox) # Connect speed spinBox self._ui.speedspinBox.editingFinished.connect(self._updateSpeedSpinBox) self._ui.speedChangeButton.clicked.connect(self._ChangeSpeed) # Connect position slider control self._ui.positionSlider.valueChanged.connect( self._updatePositionGroupBox) # Connect position spinBox self._ui.positionspinBox.editingFinished.connect( self._updatePositionSpinBox) self._ui.posChangeButton.clicked.connect(self._ChangePos) # Connect pushButton_learnMore self._ui.ImageButton.clicked.connect(self.buttonLearnMore) def _onPushButtonEnableMotorCtrl(self): ''' action for Button to Enable/Disable Motor control''' if self._ui.pushButtonEnableMotorCtrl.isChecked(): self._ui.pushButtonEnableMotorCtrl.setText("Disable Motor Control") self._model.ctrlFrame.motorEnabled = True #we want only a change of speed and position if the motor is enabled if self._ui.radioButtonPositionCtrl.isChecked(): self._ui.groupBoxAcceleration.setEnabled(True) self._ui.groupBoxPosition.setEnabled(True) self._ui.groupBoxSpeed.setEnabled(True) self._model.ctrlFrame.opMode = rpc.OpMode.POSITION_CTRL else: self._ui.groupBoxAcceleration.setEnabled(True) self._ui.groupBoxPosition.setEnabled(False) self._ui.groupBoxDirection.setEnabled(True) self._ui.groupBoxSpeed.setEnabled(True) self._model.ctrlFrame.opMode = rpc.OpMode.SPEED_CTRL else: self._ui.pushButtonEnableMotorCtrl.setText("Enable Motor Control") self._model.ctrlFrame.motorEnabled = False #motor disable -> no changes on speed and position self._ui.groupBoxPosition.setEnabled(False) self._ui.groupBoxSpeed.setEnabled(False) self._ui.groupBoxDirection.setEnabled(False) self._ui.groupBoxAcceleration.setEnabled(False) if self._ui.radioButtonPositionCtrl.isChecked(): self._model.ctrlFrame.opMode = rpc.OpMode.POSITION_CTRL #pass elif self._ui.radioButtonSpeedCtrl.isChecked(): self._model.ctrlFrame.opMode = rpc.OpMode.SPEED_CTRL #pass def _onPushButtonResetInputs(self): ''' Reset inputs to default values ''' self._setUiInputsToDefault() def _onRadioButtonControlSelection(self): ''' action to activate speed or position control''' if self._ui.pushButtonEnableMotorCtrl.isChecked(): if self._ui.radioButtonPositionCtrl.isChecked( ) and self.getCurrentSpeedRPM() < 1: self._ui.groupBoxDirection.setEnabled(False) self._ui.groupBoxPosition.setEnabled(True) self._ui.groupBoxSpeed.setEnabled(True) self._model.ctrlFrame.opMode = rpc.OpMode.POSITION_CTRL elif self._ui.radioButtonSpeedCtrl.isChecked( ) and self.getCurrentSpeedRPM() < 1: self._ui.groupBoxDirection.setEnabled(True) self._ui.groupBoxPosition.setEnabled(False) self._ui.groupBoxSpeed.setEnabled(True) self._model.ctrlFrame.opMode = rpc.OpMode.SPEED_CTRL elif self._ui.radioButtonPositionCtrl.isChecked( ) and self.getCurrentSpeedRPM() > 1: self._ui.radioButtonPositionCtrl.setChecked(False) self._ui.radioButtonSpeedCtrl.setChecked(True) elif self._ui.radioButtonSpeedCtrl.isChecked( ) and self.getCurrentSpeedRPM() > 1: self._ui.radioButtonSpeedCtrl.setChecked(False) self._ui.radioButtonPositionCtrl.setChecked(True) def _onRadioButtonDirection(self): ''' Update model with selected direction. ''' if self._ui.radioButtonDirectionStop.isChecked(): self._model.ctrlFrame.direction = rpc.Direction.STOP elif self._ui.radioButtonDirectionClockWise.isChecked(): self._model.ctrlFrame.direction = rpc.Direction.CLOCKWISE elif self._ui.radioButtonDirectionAntiClockwise.isChecked(): self._model.ctrlFrame.direction = rpc.Direction.ANTI_CLOCKWISE else: #Nothing to do here pass def _updateSpeedGroupBox(self): ''' Updates Speed Group Box elements. ''' speed = self._ui.speedSlider.value() #self._ui.speedLCD.display(speed) self._model.ctrlFrame.speed = int(speed / DefinedValues.RPM_FACTOR.value) # Sync between slider and Spinbox self._syncSpeedSpinBoxAndSpeedSlider(speed) def _updateSpeedSpinBox(self): ''' update speed in speed display''' speed = self._ui.speedspinBox.value() #self._ui.speedLCD.display(speed) self._model.ctrlFrame.speed = int(speed / DefinedValues.RPM_FACTOR.value) # Sync between slider and Spinbox self._syncSpeedSpinBoxAndSpeedSlider(speed) def _syncSpeedSpinBoxAndSpeedSlider(self, speed): ''' Update and Sync of the GUI-Values ''' self._ui.speedSlider.setValue(speed) self._ui.speedspinBox.setValue(speed) def _updatePositionGroupBox(self): ''' Updates Position Group Box elements. ''' position = self._ui.positionSlider.value() #self._ui.positionLCD.display(position) self._model.ctrlFrame.newPosition = position # Sync between slider and Spinbox self._syncPositionSpinBoxAndPositionSlider(position) def _updatePositionSpinBox(self): '''update position in position display''' position = self._ui.positionspinBox.value() #self._ui.positionLCD.display(position) self._model.ctrlFrame.newPosition = position # Sync between slider and Spinbox self._syncPositionSpinBoxAndPositionSlider(position) def _syncPositionSpinBoxAndPositionSlider(self, position): ''' Update and Sync of the GUI-Values ''' self._ui.positionSlider.setValue(position) self._ui.positionspinBox.setValue(position) def _setUiInputsToDefault(self): ''' Set UI elements to default values. ''' self._ui.pushButtonEnableMotorCtrl.setChecked( model.DefaultValues.MOTOR_ENABLED) self._onPushButtonEnableMotorCtrl() self._ui.speedSlider.setValue(model.DefaultValues.SPEED) self._ui.positionSlider.setValue(model.DefaultValues.INIT_CURRENT_POS) #self._ui.radioButtonAcceleration1.setChecked(model.DefaultValues.ACCELERATION) self._ui.radioButtonDirectionStop.setChecked( model.DefaultValues.DIRECTION) self._onRadioButtonControlSelection() def _onLicense(self): ''' open message box and show license information ''' # create message box msg = QtWidgets.QMessageBox() # icon information (I) msg.setIcon(QtWidgets.QMessageBox.Information) # text format RichText to interprete links in html style msg.setTextFormat(QtCore.Qt.RichText) # Text string with license text msg.setText(LicenseText) # title of window msg.setWindowTitle("License Information") # show message box msg.exec_() def _onMenuBarItemSelectComPort(self): ''' Selection of COM port. Starts model on valid port selection. ''' #Stop model self._model.stop() #We are no more connected self.setStatusIndicator(Status.TARGET_OFFLINE) #Create dialog for COM port selection qDialog = QDialog() settingWindow = Ui_AppSettings() settingWindow.setupUi(qDialog) #Get COM ports with connected devices ports = sorted([port.device for port in list_ports.comports()]) #Populate comboBox with found COM ports settingWindow.comboBoxComPorts.clear() settingWindow.comboBoxComPorts.addItems(ports) #Show dialog qDialog.exec_() #Evaluate user selection if QDialog.Accepted == qDialog.result(): # User has accepted by pressing OK button #Get selected COM-Port selectedIdx = settingWindow.comboBoxComPorts.currentIndex() comPort = ports[selectedIdx] self._model.start(comPort) else: # User has rejected by pressing CANCEL button pass def _buttonpres(self): ''' action asoziated with Button 1 on the numpad ''' #print(type(self.sender())) buttonpressed = self.sender() #print(type(buttonpressed)) #print(self.sender().text()) self.value = self.numpadWindow.numberToSet.text() self.value = self.value + buttonpressed.text() self.numpadWindow.numberToSet.setText(self.value) def _dellast(self): ''' usebackspace from qLineEdit to remove last number ''' self.numpadWindow.numberToSet.backspace() def _negateValue(self): '''negate value for numpad window ''' currentstring = self.numpadWindow.numberToSet.text() if currentstring: number = int(currentstring) else: number = 0 number = number * (-1) self.numpadWindow.numberToSet.setText(str(number)) def _ChangeSpeed(self): ''' Change speed via Numpad. ''' self.numpadWindow.numberToSet.setValidator(self.qLineValidator( 0, 4992)) # needs to be the same range as set in UI of qt designer #Show dialog self.qDialog.exec_() #Evaluate user selection if QDialog.Accepted == self.qDialog.result(): # User has accepted by pressing OK button #Get selected COM-Port if (self.numpadWindow.numberToSet.text()): speed = int(self.numpadWindow.numberToSet.text()) self._syncSpeedSpinBoxAndSpeedSlider(speed) self.value = str() self.numpadWindow.numberToSet.setText(self.value) else: self._syncSpeedSpinBoxAndSpeedSlider(0) self.value = str() self.numpadWindow.numberToSet.setText(self.value) else: # User has rejected by pressing CANCEL button self.value = str() self.numpadWindow.numberToSet.setText(self.value) pass def _ChangePos(self): ''' Change Position via Numpad ''' self.numpadWindow.numberToSet.setValidator( self.qLineValidator(-16000, 16000) ) # needs to be the same range as set in UI of qt designer #Show dialog self.qDialog.exec_() #Evaluate user selection if QDialog.Accepted == self.qDialog.result(): # User has accepted by pressing OK button #Get selected COM-Port if (self.numpadWindow.numberToSet.text()): pos = int(self.numpadWindow.numberToSet.text() ) #need to protect against no return value self._syncPositionSpinBoxAndPositionSlider(pos) self.value = str() self.numpadWindow.numberToSet.setText(self.value) else: self._syncPositionSpinBoxAndPositionSlider(0) self.value = str() self.numpadWindow.numberToSet.setText(self.value) else: # User has rejected by pressing CANCEL button self.value = str() self.numpadWindow.numberToSet.setText(self.value) pass def _closeApp(self): ''' Close and exit application. ''' sys.exit()
class AQMain(QtGui.QMainWindow): def __init__(self, parent=None): QtGui.QWidget.__init__(self, parent) self.ui = Ui_MainWindow() self.ui.setupUi(self) # TODO: figure out way to configure for different comm types (TCP, MAVLINK, etc) self.comm = AQSerial() # Default main window conditions self.ui.buttonDisconnect.setEnabled(False) self.ui.buttonConnect.setEnabled(True) self.ui.comPort.setEnabled(True) self.ui.baudRate.setEnabled(True) self.ui.status.setText("Not connected to the AeroQuad") self.availablePorts = [] self.updateComPortSelection() self.updateBaudRates() self.boardConfiguration = [] # Update comm port combo box to use last used comm port defaultComPort = xml.find("./Settings/DefaultComPort").text commIndex = self.ui.comPort.findText(defaultComPort) if commIndex == -1: commIndex = 0 self.ui.comPort.setCurrentIndex(commIndex) # Load splash screen splash = Ui_splashScreen() splash.setupUi(splash) self.ui.subPanel.addWidget(splash) # Dynamically configure board type menu and subPanel menu from XML configuration file self.configureSubPanelMenu() self.activeSubPanel = None self.activeSubPanelName = "" # Connect GUI slots and signals self.ui.comPort.return_handler = self.connect self.ui.buttonConnect.clicked.connect(self.connect) self.ui.buttonDisconnect.clicked.connect(self.disconnect) self.ui.actionExit.triggered.connect(QtGui.qApp.quit) self.ui.comPort.currentIndexChanged.connect(self.updateDetectedPorts) self.ui.actionBootUpDelay.triggered.connect(self.updateBootUpDelay) self.ui.actionCommTimeout.triggered.connect(self.updateCommTimeOut) ####### Communication Methods ####### def connect(self): '''Initiates communication with the AeroQuad''' # Setup GUI self.ui.status.setText("Connecting...") self.ui.buttonDisconnect.setEnabled(True) self.ui.buttonConnect.setEnabled(False) self.ui.comPort.setEnabled(False) self.ui.baudRate.setEnabled(False) # Update the GUI app.processEvents() # Setup serial port bootupDelay = float(xml.find("./Settings/BootUpDelay").text) commTimeOut = float(xml.find("./Settings/CommTimeOut").text) try: self.comm.connect(str(self.ui.comPort.currentText()), int(self.ui.baudRate.currentText()), bootupDelay, commTimeOut) # Stop and flush any previous telemetry being streamed stopTelemetry = xml.find("./Settings/StopTelemetry").text self.comm.write(stopTelemetry) self.comm.flushResponse() # Request version number to identify AeroQuad board versionRequest = xml.find("./Settings/SoftwareVersion").text self.comm.write(versionRequest) version = self.comm.waitForRead() if version != "": self.storeComPortSelection() self.ui.status.setText("Connected to AeroQuad Flight Software v" + version) # Read board configuration config = xml.find("./Settings/BoardConfiguration").text self.comm.write(config) size = int(self.comm.waitForRead()) for index in range(size): response = self.comm.waitForRead() self.boardConfiguration.append(response) # Hide menu items that don't match board configuration for index in range(len(self.subPanelMenu)): hide = self.checkRequirementsMatch(self.subPanelList[index]) self.subPanelMenu[index].setVisible(hide) # Load configuration screen self.selectSubPanel("Vehicle Configuration") self.restartSubPanel() else: self.disconnect() self.ui.status.setText("Not connected to the AeroQuad") QtGui.QMessageBox.information(self, "Connection Error", "Unable to connect to the AeroQuad. Try increasing the Boot Up Delay.\nThis is found under File->Preferences->Boot Up Delay.") except SerialException, se: self.ui.buttonDisconnect.setEnabled(False) self.ui.buttonConnect.setEnabled(True) self.ui.comPort.setEnabled(True) self.ui.baudRate.setEnabled(True) self.ui.status.setText("Connection Failed") self.boardConfiguration = []