class MainWindow(QtWidgets.QMainWindow): serialRxQu = Queue() # serial FIFO RX Queue serialWo = SerialWorker(serialRxQu) # serial Worker Thread def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # Momentarily choose to load the ui from file for flexibility during # development, eventually it could be converted in a .py if stable. uic.loadUi("test_serial.ui", self) self.refreshButton.clicked.connect(self.handle_refresh_button) self.connectButton.clicked.connect(self.handle_connect_button) self.disconnectButton.clicked.connect(self.handle_disconnect_button) axes = self.canvas.figure.add_subplot(1, 1, 1) line2, = axes.plot([], []) # Temporarily create the figure here and pass it to the view thread. # fig = plt.figure() # ax = fig.add_subplot(111) # ax.autoscale() # line1, = ax.plot([], []) # fig.show() # Visualization Worker Thread, started as soon as # the thread pool is started. Pass the figure to plot on. # self.viewWo = ViewWorker(self.serialRxQu, fig, line1) # debug with an external figure. self.viewWo = ViewWorker(self.serialRxQu, self.canvas.figure, line2) self.threadpool = QThreadPool() self.threadpool.start(self.viewWo) def handle_refresh_button(self): """Get list of serial ports available.""" ls = self.serialWo.get_port_list() if ls: print(ls) self.serialPortsComboBox.clear() self.serialPortsComboBox.addItems(ls) else: print('No serial ports available.') self.serialPortsComboBox.clear() def handle_connect_button(self): """Connect button opens the selected serial port and creates the serial worker thread. If the thread was already created previously and paused, it revives it.""" print(self.serialPortsComboBox.currentText()) self.serialWo.open_port(self.serialPortsComboBox.currentText()) if self.serialWo.no_serial_worker: self.serialWo.revive_it() self.threadpool.start(self.serialWo) self.serialWo.thread_is_started() if self.serialWo.is_paused: self.serialWo.revive_it() def handle_disconnect_button(self): """Disconnect button closes the serial port.""" self.serialWo.close_port()
class WorkHandler(object): """ Class to handle threading of "work" (concretely this is just the evaluation of a function of the form func(*args, **kwargs). The process ID identifies (uniquely) each instance of a Worker; but the caller also defines an ID which is then used to identify the Worker through the API. """ class WorkListener(metaclass=ABCMeta): """ This abstract base class defines methods which must be overriden and which handle responses to certain worker actions such as raised errors, or completion. """ def __init__(self): pass @abstractmethod def on_processing_finished(self, result): pass @abstractmethod def on_processing_error(self, error): pass def __init__(self): self.thread_pool = None self._listener = {} self._worker = {} self.thread_pool = QThreadPool() def _add_listener(self, listener, process_id, id): if not isinstance(listener, self.WorkListener): raise ValueError("The listener is not of type " "WorkListener but rather {}".format( type(listener))) self._listener.update({process_id: {'id': id, 'listener': listener}}) @Slot() def on_finished(self, process_id): if process_id not in self._worker: return result = self._worker.pop(process_id)['worker'].result self._listener.pop(process_id)['listener'].on_processing_finished( result) @Slot() def on_error(self, process_id, error): if process_id not in self._worker: return self._listener[process_id]['listener'].on_processing_error(error) def process(self, caller, func, id, *args, **kwargs): """ Process a function call with arbitrary arguments on a new thread. :param caller: ?? :param func: The function to be evaluated. :param id: An identifying integer for the task. :param args: args for func. :param kwargs: keyword args for func. """ self.remove_already_processing(id) process_id = uuid.uuid4() # Add the caller self._add_listener(caller, process_id, id) finished_callback = functools.partial(self.on_finished, process_id) error_callback = functools.partial(self.on_error, process_id) worker = Worker(func, *args, **kwargs) worker.signals.finished.connect(finished_callback) worker.signals.error.connect(error_callback) self._worker.update({process_id: {'id': id, 'worker': worker}}) self.thread_pool.start(self._worker[process_id]['worker']) def remove_already_processing(self, id): """ Remove workers with ID :param id: """ for key, process in list(self._listener.items()): if process['id'] == id: self._listener.pop(key) self._worker.pop(key) def wait_for_done(self): self.thread_pool.waitForDone() def __eq__(self, other): return self.__dict__ == other.__dict__ def __ne__(self, other): return self.__dict__ != other.__dict__
class WorkHandler(object): """ Class to handle threading of "work" (concretely this is just the evaluation of a function of the form func(*args, **kwargs). The process ID identifies (uniquely) each instance of a Worker; but the caller also defines an ID which is then used to identify the Worker through the API. """ class WorkListener(with_metaclass(ABCMeta, object)): """ This abstract base class defines methods which must be overriden and which handle responses to certain worker actions such as raised errors, or completion. """ def __init__(self): pass @abstractmethod def on_processing_finished(self, result): pass @abstractmethod def on_processing_error(self, error): pass def __init__(self): self.thread_pool = None self._listener = {} self._worker = {} self.thread_pool = QThreadPool() def _add_listener(self, listener, process_id, id): if not isinstance(listener, WorkHandler.WorkListener): raise ValueError("The listener is not of type " "WorkListener but rather {}".format(type(listener))) self._listener.update({process_id: {'id': id, 'listener': listener}}) @Slot() def on_finished(self, process_id): if process_id not in self._worker: return result = self._worker.pop(process_id)['worker'].result self._listener.pop(process_id)['listener'].on_processing_finished(result) @Slot() def on_error(self, process_id, error): if process_id not in self._worker: return self._listener[process_id]['listener'].on_processing_error(error) def process(self, caller, func, id, *args, **kwargs): """ Process a function call with arbitrary arguments on a new thread. :param caller: ?? :param func: The function to be evaluated. :param id: An identifying integer for the task. :param args: args for func. :param kwargs: keyword args for func. """ self.remove_already_processing(id) process_id = uuid.uuid4() # Add the caller self._add_listener(caller, process_id, id) finished_callback = functools.partial(self.on_finished, process_id) error_callback = functools.partial(self.on_error, process_id) worker = Worker(func, *args, **kwargs) worker.signals.finished.connect(finished_callback) worker.signals.error.connect(error_callback) self._worker.update({process_id: {'id': id, 'worker': worker}}) self.thread_pool.start(self._worker[process_id]['worker']) def remove_already_processing(self, id): """ Remove workers with ID :param id: """ for key, process in list(self._listener.items()): if process['id'] == id: self._listener.pop(key) self._worker.pop(key) def wait_for_done(self): self.thread_pool.waitForDone() def __eq__(self, other): return self.__dict__ == other.__dict__ def __ne__(self, other): return self.__dict__ != other.__dict__