def test_convert_feedback_controls(self): def md5sum(input): import hashlib m = hashlib.md5() m.update(input) return m.hexdigest() temp_regex = "T:((\d*\.)\d+)" temp_template = "Temp: {}" temp2_template = "Temperature: {}" temp_key = md5sum(temp_regex) temp_template_key = md5sum(temp_template) temp2_template_key = md5sum(temp2_template) x_regex = "X:(?P<x>\d+)" x_template = "X: {x}" x_key = md5sum(x_regex) x_template_key = md5sum(x_template) configured_controls = [ dict(key=temp_key, regex=temp_regex, template=temp_template, template_key=temp_template_key), dict(command="M117 Hello World", name="Test"), dict(children=[ dict(key=x_key, regex=x_regex, template=x_template, template_key=x_template_key), dict(key=temp_key, regex=temp_regex, template=temp2_template, template_key=temp2_template_key) ]) ] from octoprint.util import comm controls, matcher = comm.convert_feedback_controls(configured_controls) self.assertEquals(2, len(controls)) # temp_regex is used twice, so we should have two templates for it self.assertIn(temp_key, controls) temp = controls[temp_key] self.assertIsNotNone(temp["matcher"]) self.assertEquals(temp_regex, temp["matcher"].pattern) self.assertEquals(temp_regex, temp["pattern"]) self.assertEquals(2, len(temp["templates"])) self.assertIn(temp_template_key, temp["templates"]) self.assertEquals(temp_template, temp["templates"][temp_template_key]) self.assertIn(temp2_template_key, temp["templates"]) self.assertEquals(temp2_template, temp["templates"][temp2_template_key]) # x_regex is used once, so we should have only one template for it self.assertIn(x_key, controls) x = controls[x_key] self.assertIsNotNone(x["matcher"]) self.assertEquals(x_regex, x["matcher"].pattern) self.assertEquals(x_regex, x["pattern"]) self.assertEquals(1, len(x["templates"])) self.assertIn(x_template_key, x["templates"]) self.assertEquals(x_template, x["templates"][x_template_key]) self.assertEquals("(?P<group{temp_key}>{temp_regex})|(?P<group{x_key}>{x_regex})".format(**locals()), matcher.pattern)
def _monitor(self): """ Monitor thread of responses from the commands sent to the printer :return: """ feedback_controls, feedback_matcher = comm.convert_feedback_controls(settings().get(["controls"])) feedback_errors = [] pause_triggers = comm.convert_pause_triggers(settings().get(["printerParameters", "pauseTriggers"])) #exits if no connection is active if not self._beeConn.isConnected(): return startSeen = False supportWait = settings().getBoolean(["feature", "supportWait"]) while self._monitoring_active: try: line = self._getResponse() if line is None: continue ##~~ debugging output handling if line.startswith("//"): debugging_output = line[2:].strip() if debugging_output.startswith("action:"): action_command = debugging_output[len("action:"):].strip() if action_command == "pause": self._log("Pausing on request of the printer...") self.setPause(True) elif action_command == "resume": self._log("Resuming on request of the printer...") self.setPause(False) elif action_command == "disconnect": self._log("Disconnecting on request of the printer...") self._callback.on_comm_force_disconnect() else: for hook in self._printer_action_hooks: try: self._printer_action_hooks[hook](self, line, action_command) except: self._logger.exception("Error while calling hook {} with action command {}".format(self._printer_action_hooks[hook], action_command)) continue else: continue ##~~ Error handling line = self._handleErrors(line) ##~~ process oks if line.strip().startswith("ok") or (self.isPrinting() and supportWait and line.strip().startswith("wait")): self._clear_to_send.set() self._long_running_command = False ##~~ Temperature processing if ' T:' in line or line.startswith('T:') or ' T0:' in line or line.startswith('T0:') \ or ' B:' in line or line.startswith('B:'): self._processTemperatures(line) self._callback.on_comm_temperature_update(self._temp, self._bedTemp) ##~~ SD Card handling elif 'SD init fail' in line or 'volume.init failed' in line or 'openRoot failed' in line: self._sdAvailable = False self._sdFiles = [] self._callback.on_comm_sd_state_change(self._sdAvailable) elif 'Not SD printing' in line: if self.isSdFileSelected() and self.isPrinting(): # something went wrong, printer is reporting that we actually are not printing right now... self._sdFilePos = 0 self._changeState(self.STATE_OPERATIONAL) elif 'SD card ok' in line and not self._sdAvailable: self._sdAvailable = True self.refreshSdFiles() self._callback.on_comm_sd_state_change(self._sdAvailable) elif 'Begin file list' in line: self._sdFiles = [] self._sdFileList = True elif 'End file list' in line: self._sdFileList = False self._callback.on_comm_sd_files(self._sdFiles) elif 'SD printing byte' in line and self.isSdPrinting(): # answer to M27, at least on Marlin, Repetier and Sprinter: "SD printing byte %d/%d" match = regex_sdPrintingByte.search(line) self._currentFile.setFilepos(int(match.group(1))) self._callback.on_comm_progress() elif 'File opened' in line and not self._ignore_select: # answer to M23, at least on Marlin, Repetier and Sprinter: "File opened:%s Size:%d" match = regex_sdFileOpened.search(line) if self._sdFileToSelect: name = self._sdFileToSelect self._sdFileToSelect = None else: name = match.group(1) self._currentFile = comm.PrintingSdFileInformation(name, int(match.group(2))) elif 'File selected' in line: if self._ignore_select: self._ignore_select = False elif self._currentFile is not None: # final answer to M23, at least on Marlin, Repetier and Sprinter: "File selected" self._callback.on_comm_file_selected(self._currentFile.getFilename(), self._currentFile.getFilesize(), True) eventManager().fire(Events.FILE_SELECTED, { "file": self._currentFile.getFilename(), "origin": self._currentFile.getFileLocation() }) elif 'Writing to file' in line: # answer to M28, at least on Marlin, Repetier and Sprinter: "Writing to file: %s" self._changeState(self.STATE_PRINTING) self._clear_to_send.set() line = "ok" elif 'Done saving file' in line: self.refreshSdFiles() elif 'File deleted' in line and line.strip().endswith("ok"): # buggy Marlin version that doesn't send a proper \r after the "File deleted" statement, fixed in # current versions self._clear_to_send.set() ##~~ Message handling elif line.strip() != '' \ and line.strip() != 'ok' and not line.startswith("wait") \ and not line.startswith('Resend:') \ and line != 'echo:Unknown command:""\n' \ and self.isOperational(): self._callback.on_comm_message(line) ##~~ Parsing for feedback commands if feedback_controls and feedback_matcher and not "_all" in feedback_errors: try: self._process_registered_message(line, feedback_matcher, feedback_controls, feedback_errors) except: # something went wrong while feedback matching self._logger.exception("Error while trying to apply feedback control matching, disabling it") feedback_errors.append("_all") ##~~ Parsing for pause triggers if pause_triggers and not self.isStreaming(): if "enable" in pause_triggers.keys() and pause_triggers["enable"].search(line) is not None: self.setPause(True) elif "disable" in pause_triggers.keys() and pause_triggers["disable"].search(line) is not None: self.setPause(False) elif "toggle" in pause_triggers.keys() and pause_triggers["toggle"].search(line) is not None: self.setPause(not self.isPaused()) self.setPause(not self.isPaused()) ### Connection attempt elif self._state == self.STATE_CONNECTING: if "start" in line and not startSeen: startSeen = True self._sendCommand("M110") self._clear_to_send.set() elif "ok" in line: self._onConnected() elif time.time() > self._timeout: self.close() ### Operational elif self._state == self.STATE_OPERATIONAL or self._state == self.STATE_PAUSED: if "ok" in line: # if we still have commands to process, process them if self._resendDelta is not None: self._resendNextCommand() elif self._sendFromQueue(): pass # resend -> start resend procedure from requested line elif line.lower().startswith("resend") or line.lower().startswith("rs"): self._handleResendRequest(line) except Exception as ex: self._logger.exception("Something crashed inside the USB connection.") errorMsg = "See octoprint.log for details" self._log(ex.message) self._errorValue = errorMsg self._changeState(self.STATE_ERROR) eventManager().fire(Events.ERROR, {"error": self.getErrorString()}) self._log("Connection closed, closing down monitor")
def test_convert_feedback_controls(self): def md5sum(input): import hashlib m = hashlib.md5() m.update(input) return m.hexdigest() # rb'' doesn't exist in Python2 temp_regex = br"T:((\d*\.)\d+)" temp_template = b"Temp: {}" temp2_template = b"Temperature: {}" temp_key = md5sum(temp_regex) temp_template_key = md5sum(temp_template) temp2_template_key = md5sum(temp2_template) x_regex = br"X:(?P<x>\d+)" x_template = b"X: {x}" x_key = md5sum(x_regex) x_template_key = md5sum(x_template) configured_controls = [ { "key": temp_key, "regex": temp_regex, "template": temp_template, "template_key": temp_template_key, }, { "command": "M117 Hello World", "name": "Test" }, { "children": [ { "key": x_key, "regex": x_regex, "template": x_template, "template_key": x_template_key, }, { "key": temp_key, "regex": temp_regex, "template": temp2_template, "template_key": temp2_template_key, }, ] }, ] from octoprint.util import comm controls, matcher = comm.convert_feedback_controls(configured_controls) self.assertEqual(2, len(controls)) # temp_regex is used twice, so we should have two templates for it self.assertIn(temp_key, controls) temp = controls[temp_key] self.assertIsNotNone(temp["matcher"]) self.assertEqual(temp_regex, temp["matcher"].pattern) self.assertEqual(temp_regex, temp["pattern"]) self.assertEqual(2, len(temp["templates"])) self.assertIn(temp_template_key, temp["templates"]) self.assertEqual(temp_template, temp["templates"][temp_template_key]) self.assertIn(temp2_template_key, temp["templates"]) self.assertEqual(temp2_template, temp["templates"][temp2_template_key]) # x_regex is used once, so we should have only one template for it self.assertIn(x_key, controls) x = controls[x_key] self.assertIsNotNone(x["matcher"]) self.assertEqual(x_regex, x["matcher"].pattern) self.assertEqual(x_regex, x["pattern"]) self.assertEqual(1, len(x["templates"])) self.assertIn(x_template_key, x["templates"]) self.assertEqual(x_template, x["templates"][x_template_key]) self.assertEqual( "(?P<group{temp_key}>{temp_regex})|(?P<group{x_key}>{x_regex})". format(**locals()), matcher.pattern, )