def _pop_next_cmd(self): """In active mode, return manual command if one is available otherwise pop next command from tasks. In safe mode, only consider commands from task. If no command at all or in another mode, return None. """ if self.is_active(): try: manual_cmd = self.manual_cmd_queue.get_nowait() except queue.Empty: pass else: if self.cur_task is None: self.cur_task = Task([manual_cmd]) else: return manual_cmd if self.is_active() or self.in_safe_mode(): try: return self.cur_task.pop() except IndexError: self.cur_task.close() try: self.cur_task = self.task_list.pop(0) except IndexError: self.cur_task = None self.state = self.ST_INAC else: return self.cur_task.pop() return None
def main_producer(): job = Job(config.JOB_ID) for i in range(config.TASK_NUM): job.tasks.append(Task(i)) p = ProducerClient() t1 = threading.Thread(target=p.submit_job, args=(job,)) t1.start() p.monitor_job(job)
def run_file(self, filename): """Run a raw GCode from file. """ if self.is_inactive(): with open(filename) as f: cmd_list = f.read().splitlines() if cmd_list: self.mutex.lock() self._kickstart(Task(cmd_list)) self.mutex.unlock()
def send_manual_cmd(self, cmd): """Push a manual command to waiting queue and eventually kickstart it. """ self.mutex.lock() # remove leading/trailing spaces cmd = re.sub(r'(^\s*)|(\s*$)', '', cmd) if self.is_active(): self.manual_cmd_queue.put(cmd) elif self.is_inactive(): self._kickstart(Task([cmd])) self.mutex.unlock()
class ControllerBase(QtCore.QObject): link_state_update = QtCore.pyqtSignal() ST_UNCO = 0 ST_INAC = 1 ST_ACTI = 2 ST_SAFE = 3 def __init__(self, project, post_processor): super().__init__() self.project = project self.post_processor = post_processor self.keep_workers = False self.input_thread = GenericThread(self._input_worker) self.output_thread = GenericThread(self._output_worker) self.send_cond = QtCore.QWaitCondition() self.mutex = QtCore.QMutex() # NOTE better use SimpleQueue in 3.7 self.manual_cmd_queue = queue.Queue() self.com_logger = CommunicationLogger() self.state = self.ST_UNCO def __del__(self): self.disconnect() def run_file(self, filename): """Run a raw GCode from file. """ if self.is_inactive(): with open(filename) as f: cmd_list = f.read().splitlines() if cmd_list: self.mutex.lock() self._kickstart(Task(cmd_list)) self.mutex.unlock() def run(self, dry_run): """Make post-processor generate project's GCode as a list of tasks and start running it. """ self.mutex.lock() if self.is_inactive(): tasks = self.project.generate_tasks(self.post_processor, dry_run) self.task_list = tasks try: task = self.task_list.pop(0) except: print('No job to run.') else: self._kickstart(task) self.send_cond.wakeOne() self.mutex.unlock() def stop(self): """Discard any tasks and manual commands except the one running. """ self.mutex.lock() if self.is_active(): self.manual_cmd_queue.queue.clear() self.task_list = [] self.mutex.unlock() def abort(self): """Discard all tasks and commands, setup emergency task instead and switch to safe mode to prevent any interruption of the emergency task. """ self.mutex.lock() if self.is_active(): self._enter_safe_mode() self.mutex.unlock() def _abort_internal(self, message): """Internal version of abort with mutex acquired on caller side and error logging. """ if self.is_active(): self._enter_safe_mode() self.com_logger.incident.emit(message) def _enter_safe_mode(self): """Factorization of abort and _abort_internal behaviour. """ self.manual_cmd_queue.queue.clear() self.cur_task.fail() self.task_list = [self.post_processor.emergency_task()] self.state = self.ST_SAFE def send_manual_cmd(self, cmd): """Push a manual command to waiting queue and eventually kickstart it. """ self.mutex.lock() # remove leading/trailing spaces cmd = re.sub(r'(^\s*)|(\s*$)', '', cmd) if self.is_active(): self.manual_cmd_queue.put(cmd) elif self.is_inactive(): self._kickstart(Task([cmd])) self.mutex.unlock() @abstractmethod def _link_open(self, *args, **kwargs): """Open communication link.""" pass @abstractmethod def _link_close(self): """Close communication link.""" pass @abstractmethod def _link_read(self): """Blocking read with timeout. Return complete line or empty string.""" pass @abstractmethod def _link_send(self, cmd): """Sends a command and eventually adds end characters to it.""" pass @abstractmethod def _link_busy(self): """Return True when machine controller is ready for next command to be sent. Implementation can use self.next_cmd if length information is needed. """ pass @abstractmethod def _process_input(self, input): """Parse a line from the machine controller.""" pass def is_unconnected(self): return self.state == self.ST_UNCO def is_inactive(self): return self.state == self.ST_INAC def is_active(self): return self.state == self.ST_ACTI def in_safe_mode(self): return self.state == self.ST_SAFE def connect(self, *args, **kwargs): """Connect to machine controller.""" if self.is_unconnected(): try: self._link_open(args, kwargs) except Exception as e: print('Connection failed: ' + str(e), file=sys.stderr) else: self.task_list = [] self.cur_task = None self.next_cmd = None self.keep_workers = True self.input_thread.start() self.output_thread.start() self.state = self.ST_INAC self.link_state_update.emit() def disconnect(self): """Close link with machine controller. It can be called either from external thread or its self worker threads. """ if self.is_inactive(): self.mutex.lock() self.keep_workers = False self.send_cond.wakeOne() self.mutex.unlock() if QtCore.QThread.currentThread() != self.input_thread: self.input_thread.wait() if QtCore.QThread.currentThread() != self.output_thread: self.output_thread.wait() self._link_close() self.state = self.ST_UNCO self.link_state_update.emit() def _input_worker(self): """Input thread working loop.""" while self.keep_workers: line = '' while (not line or line[-1] != '\n') and self.keep_workers: try: line += self._link_read() except Exception as e: print('Link read failed: ' + str(e), file=sys.stderr) self.disconnect() return if self.keep_workers: line = line.rstrip() self.mutex.lock() self._process_input(line) self.com_logger.log_received_data(line) self.mutex.unlock() def _output_worker(self): """Output thread working loop.""" while self.keep_workers: self.mutex.lock() while ((self.next_cmd is None or self._link_busy()) and self.keep_workers): self.send_cond.wait(self.mutex) if self.keep_workers: try: self._link_send(self.next_cmd) except Exception as e: self.mutex.unlock() print('Link send failed: ' + str(e), file=sys.stderr) self.disconnect() return self.com_logger.log_sent_data(self.next_cmd) self.next_cmd = None self.mutex.unlock() def _kickstart(self, task): """Wake up worker thread to run task.""" self.cur_task = task self.next_cmd = self.cur_task.pop() self.state = self.ST_ACTI self.send_cond.wakeOne() def _pop_next_cmd(self): """In active mode, return manual command if one is available otherwise pop next command from tasks. In safe mode, only consider commands from task. If no command at all or in another mode, return None. """ if self.is_active(): try: manual_cmd = self.manual_cmd_queue.get_nowait() except queue.Empty: pass else: if self.cur_task is None: self.cur_task = Task([manual_cmd]) else: return manual_cmd if self.is_active() or self.in_safe_mode(): try: return self.cur_task.pop() except IndexError: self.cur_task.close() try: self.cur_task = self.task_list.pop(0) except IndexError: self.cur_task = None self.state = self.ST_INAC else: return self.cur_task.pop() return None def _complete_cmd(self): """Function to be called by input parser when a command is completed. """ self.next_cmd = self._pop_next_cmd() self.send_cond.wakeOne()
def emergency_task(self): return Task(self._abort_seq)
def init_task(self): return Task(self._init_seq)