def show_psg(self, target): """ 显示进度条窗口 :param target:线程处理函数 :return:None """ # 进度条 progress = QProgressDialog(parent=self.ui, maximum=0, minimum=0, cancelButtonText="取消", labelText="正在提取...", flags=Qt.WindowFlags()) progress.setWindowTitle("提取数据") run_psg_thd = Thread(target=target, args=(progress, )) run_psg_thd.start() progress.exec_() self.show_finish()
class AppStore(QObject): app_change = Signal() def __init__(self): super().__init__() self.settings = QSettings(self) qt_write_base = Path( QStandardPaths.writableLocation(QStandardPaths.AppConfigLocation) ) qt_write_base.mkdir(parents=True, exist_ok=True) self.icon_write_dir = qt_write_base / "app_icons" self.icon_write_dir.mkdir(exist_ok=True) def has_default_apps(self) -> bool: return self.settings.value("apps/_meta/isDefaultLoaded") def load_default_apps(self): def on_progress(value): if value != self.app_progress.maximum(): return self.settings.setValue("apps/_meta/isDefaultLoaded", True) logger.info("Default apps loaded") self.add_app_thread.progress.disconnect(on_progress) del self.app_progress del self.add_app_thread self.app_progress = QProgressDialog("Loading default apps", "Cancel", 0, 100) self.add_app_thread = _FetchRegistryThread(self, apps_manifest=REGISTRY_URL) self.add_app_thread.progress.connect(self.app_progress.setValue) self.add_app_thread.progress.connect(on_progress) self.add_app_thread.items.connect(self.app_progress.setMaximum) self.add_app_thread.label.connect(self.app_progress.setLabelText) self.app_progress.canceled.connect(self.add_app_thread.cancel) self.add_app_thread.start() self.app_progress.show() def add_app_ui(self, manifests: List[str]): def on_progress(value): if value != self.app_progress.maximum(): return self.add_app_thread.progress.disconnect(on_progress) del self.app_progress del self.add_app_thread def on_failed(exc_info): msg = QMessageBox( QMessageBox.Critical, "Fail to add application", f"Failed to add application: \n\n{exc_info[0].__name__}: {exc_info[1]}", ) msg.setDetailedText("".join(traceback.format_exception(*exc_info))) msg.exec_() self.app_progress = QProgressDialog("Installing app", "Cancel", 0, 100) self.add_app_thread = _FetchRegistryThread(self, apps=manifests) self.add_app_thread.progress.connect(self.app_progress.setValue) self.add_app_thread.progress.connect(on_progress) self.add_app_thread.items.connect(self.app_progress.setMaximum) self.add_app_thread.label.connect(self.app_progress.setLabelText) self.add_app_thread.failed.connect(on_failed) self.app_progress.canceled.connect(self.add_app_thread.cancel) self.add_app_thread.start() self.app_progress.exec_() def add_app(self, manifest_url: str, manifest: AppManifest): appid = app_id(manifest_url) try: manifest["appUrl"] = urljoin(manifest_url, manifest["appUrl"]) manifest["iconUrl"] = ( urljoin(manifest_url, manifest["iconUrl"]) if "iconUrl" in manifest and manifest["iconUrl"] else "" ) manifest["configUrl"] = urljoin(manifest_url, manifest["configUrl"]) except KeyError: raise AddAppError(manifest_url) if manifest["iconUrl"]: self.download_app_icon(appid, manifest["iconUrl"]) self.settings.setValue(f"apps/{appid}", json.dumps(manifest)) self.app_change.emit() logger.info( "Application %s (%s:%s) installed", manifest["appName"], appid, manifest_url ) def mkdir(self, folder: str): assert folder assert "/" not in folder settings = QSettings() settings.beginGroup(f"apps/_folder/{folder}") settings.setValue("_dir", "true") self.app_change.emit() def add_app_to_folder(self, appid: str, folder: str = "", _emit=True): assert "/" not in folder settings = QSettings() settings.beginGroup(f"apps/_folder/{folder}") last_id = 0 for key in settings.childKeys(): if "_" in key: continue last_id = max(int(key), last_id) if appid == settings.value(key): settings.endGroup() return keys = [int(x) for x in settings.childKeys() if "_" not in x] last_id = max(keys) if keys else 0 settings.endGroup() settings.setValue(f"apps/_folder/{folder}/{last_id + 1}", appid) if _emit: self.app_change.emit() def delete_app_from_folder(self, appid: str, folder: str): assert "/" not in folder settings = QSettings() settings.beginGroup(f"apps/_folder/{folder}") for key in settings.childKeys(): if appid == settings.value(key): settings.remove(key) self.app_change.emit() return def remove_app(self, appid: str): settings = QSettings() settings.beginGroup("apps/_folder/") def recurse(): for key in settings.childKeys(): if settings.value(key) == appid: settings.remove(key) for childGroup in settings.childGroups(): settings.beginGroup(childGroup) recurse() settings.endGroup() recurse() self.settings.remove(f"apps/{appid}") icon_file = self.icon_write_dir / (appid + ".png") icon_file.unlink(True) self.app_change.emit() def rmdir(self, folder: str): assert folder assert "/" not in folder settings = QSettings() settings.beginGroup(f"apps/_folder/{folder}") for key in settings.childKeys(): if "_" in key: continue try: int(key) except ValueError: continue self.add_app_to_folder(settings.value(key), "", _emit=False) settings.remove("") self.app_change.emit() def download_app_icon(self, appid: str, url: str): dest = self.icon_write_dir / (appid + ".png") req = requests.get(url) with dest.open("wb") as fp: fp.write(req.content) logger.info("App icon %s wrote to %s", appid, str(dest)) def icon(self, appid: str) -> Optional[QIcon]: fn = QStandardPaths.locate( QStandardPaths.AppConfigLocation, "app_icons/" + appid + ".png" ) if fn == "": return None return QIcon(QPixmap(fn)) def all_apps(self) -> Iterator[Tuple[str, AppManifest]]: settings = QSettings() settings.beginGroup("apps") for appid in settings.childKeys(): if appid.startswith("_"): continue manifest = json.loads(settings.value(appid)) yield appid, manifest settings.endGroup() def list_app(self, root: str) -> Iterator[Tuple[str, Union[AppManifest, None]]]: settings = QSettings() settings.beginGroup(f"apps/_folder/" + root) for key in settings.childGroups(): yield key, None for key in settings.childKeys(): try: int(key) except ValueError: continue appid = settings.value(key) yield appid, self[appid] def __iter__(self): yield from self.all_apps() def __getitem__(self, item: str) -> AppManifest: assert not item.startswith("_") return json.loads(self.settings.value(f"apps/{item}"))
class RootsApp(QMainWindow): standard_deviation_threshold = 0.1 # when I receive a measurement from the sensor I check if its standard deviation; if it's too low it means the sensor is not working temporary_database_filename = "temporary.db" # the current session is stored in a temporary database. When the user saves, it is copied at the desired location def __init__(self): super().__init__(); self.setWindowTitle("Roots") self.setFixedWidth(1200) self.resize(1200, 1200) self.threadpool = QThreadPool(); self.object_list = list() self.is_training_on = False self.interaction_under_training = None self.n_measurements_collected = 0 self.n_measurements_to_collect = 3 self.sensor_not_responding = True self.sensor_not_responding_timeout = 2000 # milliseconds self.database_connection = self.create_temporary_database() self.active_object = None self.number_of_objects_added = 0 self.sensor_start_freq = 250000 self.sensor_end_freq = 3000000 # creates the plot self.plotWidget = pyqtgraph.PlotWidget(title = "Sensor Response") self.plotWidget.setFixedHeight(300) self.plotWidget.getAxis("bottom").setLabel("Excitation frequency", "Hz") self.plotWidget.getAxis("left").setLabel("Volts", "V") self.dataPlot = self.plotWidget.plot() # timer used to see if the sensor is responding self.timer = QTimer() self.timer.setInterval(self.sensor_not_responding_timeout) self.timer.timeout.connect(self.timer_timeout) self.timer_timeout() # defines the actions in the file menu with button actions iconExit = QIcon("icons/icon_exit.png") btnActionExit = QAction(iconExit, "Exit", self) btnActionExit.setStatusTip("Click to terminate the program") btnActionExit.triggered.connect(self.exit) iconSave = QIcon("icons/icon_save.ico") buttonActionSave = QAction(iconSave, "Save current set of objects", self) # buttonActionSave.setStatusTip("Click to perform action 2") buttonActionSave.triggered.connect(self.save) iconOpen = QIcon("icons/icon_load.png") buttonActionOpen = QAction(iconOpen, "Load set of objects", self) buttonActionOpen.triggered.connect(self.open) # toolbar toolBar = QToolBar("Toolbar") toolBar.addAction(buttonActionSave) toolBar.addAction(buttonActionOpen) toolBar.setIconSize(QSize(64, 64)) toolBar.setStyleSheet(styles.toolbar) self.addToolBar(toolBar) # menu menuBar = self.menuBar() menuBar.setStyleSheet(styles.menuBar) menuFile = menuBar.addMenu("File") menuOptions = menuBar.addMenu("Options") menuView = menuBar.addMenu("View") menuConnect = menuBar.addMenu("Connect") menuFile.addAction(buttonActionSave) menuFile.addAction(buttonActionOpen) menuFile.addAction(btnActionExit) # status bar self.setStatusBar(QStatusBar(self)) # creates the "My Objects" label labelMyObjects = QLabel("My Objects") labelMyObjects.setFixedHeight(100) labelMyObjects.setAlignment(Qt.AlignCenter) labelMyObjects.setStyleSheet(styles.labelMyObjects) # button "add object" icon_plus = QIcon("icons/icon_add.png") self.btn_create_object = QPushButton("Add Object") self.btn_create_object.setCheckable(False) self.btn_create_object.setIcon(icon_plus) self.btn_create_object.setFixedHeight(80) self.btn_create_object.setStyleSheet(styles.addObjectButton) self.btn_create_object.clicked.connect(self.create_object) # defines the layout of the "My Objects" section self.verticalLayout = QVBoxLayout() self.verticalLayout.setContentsMargins(0,0,0,0) self.verticalLayout.addWidget(labelMyObjects) self.verticalLayout.addWidget(self.btn_create_object) self.spacer = QSpacerItem(0,2000, QSizePolicy.Expanding, QSizePolicy.Expanding) self.verticalLayout.setSpacing(0) self.verticalLayout.addSpacerItem(self.spacer) #adds spacer # defines the ComboBox which holds the names of the objects self.comboBox = QComboBox() self.comboBox.addItem("- no object selected") self.comboBox.currentIndexChanged.connect(self.comboBox_index_changed) self.comboBox.setFixedSize(300, 40) self.comboBox.setStyleSheet(styles.comboBox) self.update_comboBox() # defines the label "Selected Object" (above the comboBox) self.labelComboBox = QLabel() self.labelComboBox.setText("Selected Object:") self.labelComboBox.setStyleSheet(styles.labelComboBox) self.labelComboBox.adjustSize() # vertical layout for the combobox and its label self.VLayoutComboBox = QVBoxLayout() self.VLayoutComboBox.addWidget(self.labelComboBox) self.VLayoutComboBox.addWidget(self.comboBox) # label with the output text (the big one on the right) self.labelClassification = QLabel() self.labelClassification.setText("No interaction detected") self.labelClassification.setFixedHeight(80) self.labelClassification.setStyleSheet(styles.labelClassification) self.labelClassification.adjustSize() HLayoutComboBox = QHBoxLayout() HLayoutComboBox.addLayout(self.VLayoutComboBox) HLayoutComboBox.addSpacerItem(QSpacerItem(1000,0, QSizePolicy.Expanding, QSizePolicy.Expanding)); #adds spacer HLayoutComboBox.addWidget(self.labelClassification) # creates a frame that contains the combobox and the labels frame = QFrame() frame.setStyleSheet(styles.frame) frame.setLayout(HLayoutComboBox) # sets the window layout with the elements created before self.windowLayout = QVBoxLayout() self.windowLayout.addWidget(self.plotWidget) self.windowLayout.addWidget(frame) self.windowLayout.addLayout(self.verticalLayout) # puts everything into a frame and displays it on the window self.mainWindowFrame = QFrame() self.mainWindowFrame.setLayout(self.windowLayout) self.mainWindowFrame.setStyleSheet(styles.mainWindowFrame) self.setCentralWidget(self.mainWindowFrame) self.create_object() # creates one object at the beginning # ----------------------------------------------------------------------------------------------------------- # Shows a welcome message def show_welcome_msg(self): welcome_msg = QMessageBox() welcome_msg.setText("Welcome to the Roots application!") welcome_msg.setIcon(QMessageBox.Information) welcome_msg.setInformativeText(strings.welcome_text) welcome_msg.setWindowTitle("Welcome") welcome_msg.exec_() # ----------------------------------------------------------------------------------------------------------- # When the user changes the object in the combobox, updates the active object def comboBox_index_changed(self, index): object_name = self.comboBox.currentText() for object in self.object_list: if object.name == object_name: self.set_active_object(object) print("DEBUG: selected object changed. Object name: {0}".format(object.name)) return # ----------------------------------------------------------------------------------------------------------- # This function allows to save the current objects on a file def save(self): current_path = os.getcwd() directory_path = current_path + "/Saved_Workspaces" if not os.path.exists(directory_path): os.mkdir(directory_path) file_path = None [file_path, file_extension] = QFileDialog.getSaveFileName(self,"Roots", directory_path, "Roots database (*.db)") if file_path is None: return temp_database_path = current_path + "/" + RootsApp.temporary_database_filename shutil.copyfile(temp_database_path, file_path) # copies the temporary database to save the current workspace return # ----------------------------------------------------------------------------------------------------------- # this function creates a clean database where all the data of this session will be temporarily stored def create_temporary_database(self): current_path = os.getcwd() file_path = current_path + "/" + RootsApp.temporary_database_filename if os.path.exists(file_path): # if the database is already there it deletes it to reset it os.remove(file_path) print("DEBUG: removing database. (in 'RootsApp.create_temporary_database()'") database_connection = database.create_connection(RootsApp.temporary_database_filename) # creates the temporary database database.create_tables(database_connection) # initializes the database database.reset_db(database_connection) # resets the database (not needed but it doesn't cost anything to put it) return database_connection # ----------------------------------------------------------------------------------------------------------- # This function allows to load previously created objects from a file def open(self): current_path = os.getcwd() saved_files_directory = current_path + "/Saved_Workspaces" [file_path, file_extension] = QFileDialog.getOpenFileName(self,"Roots", saved_files_directory, "Roots database (*.db)"); if file_path == '': return for object in self.object_list.copy(): # deletes all the objects print("DEBUG: deleting object {0} (in 'open()')".format(object.name)) self.delete_object(object) temp_database_path = current_path + "/" + RootsApp.temporary_database_filename self.database_connection.close() os.remove(temp_database_path) shutil.copyfile(file_path, temp_database_path) # replaces the temporary database with the file to open self.database_connection = database.create_connection(temp_database_path) object_tuples = database.get_all_objects(self.database_connection) for object_tuple in object_tuples: object_ID, object_name = object_tuple location_IDs = database.get_locations_id_for_object(self.database_connection, object_ID) formatted_location_IDs = [] for location_ID in location_IDs: formatted_location_IDs.append(location_ID[0]) print("DEBUG: loading object {0} with location IDs {1}. (in 'RootsApp.open()')".format(object_name, formatted_location_IDs)) self.add_object(object_name, object_ID, formatted_location_IDs) self.train_classifiers() return # ----------------------------------------------------------------------------------------------------------- # This function updates the ComboBox whenever objects are created, destroyed or the active object has changed def update_comboBox(self): print("DEBUG: repainting ComboBox. (in 'RootsApp.update_comboBox()'") self.comboBox.clear() self.comboBox.addItem("none") for object in self.object_list: self.comboBox.addItem(object.name) self.comboBox.adjustSize() # ----------------------------------------------------------------------------------------------------------- # This is a timer which is restarted every time a measurement is received. If it elapses it means that the sesnor is not connected def timer_timeout(self): print("DEBUG: timer timeout. (in 'RootsApp.timer_timeout()'") self.sensor_not_responding = True self.statusBar().showMessage(strings.sensor_disconnected) self.statusBar().setStyleSheet(styles.statusBarError) self.plotWidget.setTitle("Sensor not connected") # ----------------------------------------------------------------------------------------------------------- # This function creates a new object in the database and then calls the "add_object" function, which adds the newly created object to the application def create_object(self): new_object_name = "Object {0}".format(self.number_of_objects_added + 1) [new_object_ID, location_IDs] = database.create_object(self.database_connection, new_object_name) self.add_object(new_object_name, new_object_ID, location_IDs) # ----------------------------------------------------------------------------------------------------------- # This function deletes an object from the database, and from the application object list. It alsos destroys the object def delete_object(self, object): print("DEBUG: deleting object {0}. (in 'RootsApp.delete_object()')".format(object.ID)) database.delete_object(self.database_connection, object.ID) self.object_list.remove(object) self.verticalLayout.removeItem(object.layout) self.update_comboBox() object.delete() # ----------------------------------------------------------------------------------------------------------- # This function adds an object to the current application. Note that if you want to create an object ex-novo you should call "create_object". This function is useful when loading existing objects from a file def add_object(self, name, object_ID, location_IDs): self.number_of_objects_added += 1 new_object = Object(name, object_ID, location_IDs, self) self.object_list.append(new_object) for ID in location_IDs: # initializes the measurements with 0 if the measurement is empty #print("DEBUG: initializing location ID {0}".format(ID)) measurements = database.get_measurements_for_location(self.database_connection, ID) print("DEBUG: location {0} of object {1} is trained: {2}. (in 'RootsApp.add_object()')".format(ID, new_object.name, database.is_location_trained(self.database_connection, ID))) if len(measurements) == 0: database.save_points(self.database_connection, [0], ID) database.set_location_trained(self.database_connection, ID, "FALSE") elif database.is_location_trained(self.database_connection, ID) == "TRUE": new_object.get_interaction_by_ID(ID).setCalibrated(True) # inserts the newly created object before the "Add Object" button index = self.verticalLayout.indexOf(self.btn_create_object) self.verticalLayout.insertLayout(index, new_object.layout) self.update_comboBox() print("DEBUG: object {0} added. (in 'RootsApp.add_object()')".format(new_object.name)) return # ----------------------------------------------------------------------------------------------------------- # This function takes as input the measurement data and formats it to plot it on the graph def update_graph(self, data): frequency_step = (self.sensor_end_freq - self.sensor_start_freq) / len(data) x_axis = numpy.arange(self.sensor_start_freq, self.sensor_end_freq, frequency_step) formatted_data = numpy.transpose(numpy.asarray([x_axis, data])) self.dataPlot.setData(formatted_data) # ----------------------------------------------------------------------------------------------------------- # This function starts the UDP server that receives the measurements def run_UDP_server(self, UDP_IP, UDP_PORT): self.UDPServer = UDPServer(UDP_IP, UDP_PORT) self.UDPServer.signals.measurementReceived.connect(self.process_measurement) self.threadpool.start(self.UDPServer) # ----------------------------------------------------------------------------------------------------------- # This function changes some global variables to tell the application to save the incoming measurements into the database. The measurements belong to the interaction passed as argument def start_collecting_measurements(self, interaction): if self.sensor_not_responding: print("DEBUG: warning! Can't start calibration, the sensor is not responding! (in 'RootsApp.start_collecting_measurements()')") error_msg = QMessageBox() error_msg.setText("Can't start calibration!") error_msg.setIcon(QMessageBox.Critical) error_msg.setInformativeText('The sensor is not responding, make sure it is connected') error_msg.setWindowTitle("Error") error_msg.exec_() else: print("starting to collect measurements into the database at location ID {0} (in 'RootsApp.start_collecting_measurements()')".format(interaction.ID)); self.is_training_on = True self.interaction_under_training = interaction database.delete_measurements_from_location(self.database_connection, interaction.ID) # resets the location measurements self.progress_dialog = QProgressDialog("Calibrating", "Abort", 0, self.n_measurements_to_collect, self) self.progress_dialog.setWindowModality(Qt.WindowModal) self.progress_dialog.setWindowTitle("Calibration") self.progress_dialog.setFixedSize(400, 200) self.progress_dialog.setValue(0) self.progress_dialog.exec_() # ----------------------------------------------------------------------------------------------------------- # This function is called by the UDP thread every time that a measurement is received. It does the following: # 1. Plots the incoming measurement # 2. IF training mode IS on: # Predicts the interaction (tries to guess where the user is touching) # ELSE: # Saves the measurement and retrains the classifier with the new data def process_measurement(self, received_data): self.sensor_not_responding = False self.plotWidget.setTitle("Sensor response") self.timer.start() # starts the timer that checks if we are receiving data from the sensor measurement = received_data.split(' ') # get rid of separator measurement = [float(i) for i in measurement] # convert strings to float self.update_graph(measurement) self.predict_interaction(measurement) # checks the standard deviation of the received data to see if the sensor is working well if (numpy.std(measurement) < self.standard_deviation_threshold): self.statusBar().showMessage(strings.sensor_not_working) self.statusBar().setStyleSheet(styles.statusBarError) else: self.statusBar().setStyleSheet(styles.statusBar) if self.is_training_on: print("saving measurement {0} into database at location_ID {1}. (in 'RootsApp.process_measurement()')".format(self.n_measurements_collected + 1, self.interaction_under_training.ID)) database.save_points(self.database_connection, measurement, self.interaction_under_training.ID) self.n_measurements_collected += 1 self.progress_dialog.setValue(self.n_measurements_collected) if (self.n_measurements_collected >= self.n_measurements_to_collect): self.is_training_on = False self.n_measurements_collected = 0 print("DEBUG: {0} measurements were saved at location_ID {1}. (in 'RootsApp.process_measurement()')".format(self.n_measurements_to_collect, self.interaction_under_training.ID)) self.train_classifiers() self.interaction_under_training.setCalibrated(True) # this makes the button "Calibrate" change coulour # ----------------------------------------------------------------------------------------------------------- # This function retrains the classifiers using all the measurements present in the database and assigns to each object its classifier def train_classifiers(self): #[objects_ID, classifiers] classifiers = classifier.get_classifiers(self.database_connection) print("DEBUG: the following classifiers were created: {0}. (in 'RootsApp.train_classifiers')".format(classifiers)) for object in self.object_list: for index, tuple in enumerate(classifiers): object_ID, classif = tuple; # extracts the object ID and the classifier from the tuple if object_ID == object.ID: object.classifier = classif del classifiers[index] # ----------------------------------------------------------------------------------------------------------- # This function changes the current active object (the software tries to guess where the user is touching using the calibration data from the active object) def set_active_object(self, active_object): self.active_object = active_object for obj in self.object_list: if obj == active_object: active_object.set_highlighted(True) else: obj.set_highlighted(False) index = self.comboBox.findText(self.active_object.name) # updates the index of the ComboBox self.comboBox.setCurrentIndex(index) # ----------------------------------------------------------------------------------------------------------- # This function changes the name of an object. It updates the database AND the application data structure. def rename_object(self, object, new_name): print("DEBUG: changing name of object '{0}' (in 'RootsApp.rename_object')".format(object.name)) object.set_name(new_name) database.rename_object(self.database_connection, object.ID, new_name) self.update_comboBox() # ----------------------------------------------------------------------------------------------------------- # This function uses the classifier of the active object to guess where the user is touching, based on the incoming measurement def predict_interaction(self, measurement): if (len(self.object_list) <= 0): self.labelClassification.setText("No objects available") self.statusBar().showMessage(strings.no_objects) return if self.active_object is None: self.labelClassification.setText("No object selected") self.statusBar().showMessage(strings.no_object_selected) return if self.active_object.classifier is None: self.labelClassification.setText("The object is not calibrated") self.statusBar().showMessage(strings.object_not_calibrated) return else: predicted_interaction_id = self.active_object.classifier(measurement) interaction = self.active_object.get_interaction_by_ID(predicted_interaction_id) self.labelClassification.setText(interaction.name) self.statusBar().showMessage("") #print("DEBUG: predicted interaction ID: ", interaction.ID) # ----------------------------------------------------------------------------------------------------------- # This is a system event that gets called whenever the user tries to close the application. It calls the "exit()" # function (just below) to open a dialog to make sure the user really wants to quit. def closeEvent(self, event): if not self.exit(): event.ignore() # ----------------------------------------------------------------------------------------------------------- # This function gets called when the user cliks on the "Exit" button in the "File" menu or when it tries to close the window (indirectly) # Here we open a dialog to make sure the user really wants to quit. def exit(self): dialogWindow = DialogExit() answer = dialogWindow.exec_() if (answer == True): self.UDPServer.stop() self.close() return answer
class Loader(QWidget): def __init__(self, parent=None): super(Loader, self).__init__(parent=parent) self.ui = Ui_Loader() self.ui.setupUi(self) self.dir = QDir(QDir.currentPath() + '/programs/') self.dir.setFilter(QDir.Files or QDir.NoDotAndDotDot) self.fs_watcher = QFileSystemWatcher(self.dir.path()) self.fs_watcher.addPath(self.dir.path()) self.fs_watcher.directoryChanged.connect(self.update_program_list) self.send_status = QProgressDialog self.sender = Sender self.serialpropertiesvalues = \ { 'baudrate': Serial.BAUDRATES, 'parity': Serial.PARITIES, 'databits': Serial.BYTESIZES, 'stopbits': Serial.STOPBITS, 'flowcontrol': ['NoControl', 'SoftwareControl', 'HardwareControl'] } self.update_program_list() self.update_serial_port_list() # self.set_serial_port_options() self.ui.updateProgramListButton.clicked.connect(self.refresh) self.ui.programListWidget.itemSelectionChanged.connect( self.selection_changed) self.ui.sendButton.clicked.connect(self.send_program) self.ui.serialPortChooser.currentTextChanged.connect( self.selection_changed) self.ui.serialPortChooser.currentTextChanged.connect(save_port) # self.ui.baudRateInput.textChanged.connect(save_baud) # self.ui.parityChooser.currentTextChanged.connect(save_parity) # self.ui.dataBitsChooser.currentTextChanged.connect(save_databits) # self.ui.stopBitsChooser.currentTextChanged.connect(save_stopbits) # self.ui.flowControlChooser.currentTextChanged.connect(save_flowcontrol) self.thread_pool = QThreadPool() def set_serial_port_options(self): for key in parities.keys(): self.ui.parityChooser.addItem(key) for key in bytesize.keys(): self.ui.dataBitsChooser.addItem(key) for key in stopbits.keys(): self.ui.stopBitsChooser.addItem(key) self.ui.flowControlChooser.addItems(flowcontrol) if globalSettings.contains('serialport/port'): self.selectpreviousvalues() else: self.saveconfig() def selectpreviousvalues(self): self.ui.serialPortChooser.setCurrentText( globalSettings.value('serialport/port')) self.ui.baudRateInput.setText( globalSettings.value('serialport/baudrate')) self.ui.parityChooser.setCurrentText( globalSettings.value('serialport/parity')) self.ui.dataBitsChooser.setCurrentText( globalSettings.value('serialport/databits')) self.ui.stopBitsChooser.setCurrentText( globalSettings.value('serialport/stopbits')) self.ui.flowControlChooser.setCurrentText( globalSettings.value('serialport/flowcontrol')) def saveconfig(self): save_port(self.ui.serialPortChooser.currentText()) save_baud(self.ui.baudRateInput.text()) save_parity(self.ui.parityChooser.currentText()) save_databits(self.ui.dataBitsChooser.currentText()) save_stopbits(self.ui.stopBitsChooser.currentText()) save_flowcontrol(self.ui.flowControlChooser.currentText()) def update_serial_port_list(self): self.ui.serialPortChooser.clear() for port in list_ports.comports(): self.ui.serialPortChooser.addItem(port.device) def update_program_list(self): self.ui.programListWidget.clear() self.dir.refresh() self.ui.programListWidget.addItems(self.dir.entryList()) self.ui.programListWidget.clearSelection() def selection_changed(self): if self.ui.serialPortChooser.currentText() is not None \ and self.ui.programListWidget.currentItem() is not None: self.ui.sendButton.setEnabled(True) else: self.ui.sendButton.setDisabled(True) def refresh(self): self.update_program_list() self.update_serial_port_list() def send_program(self): selections = self.ui.programListWidget.selectedItems() for selection in selections: filename = selection.text() filepath = self.dir.path() + '/' + filename port_chosen = self.ui.serialPortChooser.currentText() confirm = ConfirmSend(self) confirm.ui.dialogLabel.setText(f'Send program \'{filename}\'?') confirm.exec() if confirm.result() == QDialog.Accepted: self.send_status = QProgressDialog(self) self.sender = Sender( port_chosen, filepath, globalSettings.value('serialport/baudrate'), globalSettings.value('serialport/databits'), globalSettings.value('serialport/parity'), globalSettings.value('serialport/stopbits'), globalSettings.value('serialport/flowcontrol'), self) self.send_status.setMaximum(self.sender.file.size()) self.send_status.canceled.connect(self.sender.cancel) self.sender.signals.update_status.connect( self.send_status.setValue) self.thread_pool.start(self.sender) self.send_status.exec_() self.send_status.deleteLater()