diff --git a/octoprint/util/comm.py b/octoprint/util/comm.py index 7c5b29b..fc20263 100644 --- a/octoprint/util/comm.py +++ b/octoprint/util/comm.py @@ -581,220 +581,228 @@ class MachineCom(object): startSeen = not settings().getBoolean(["feature", "waitForStartOnConnect"]) heatingUp = False while True: - line = self._readline() - if line == None: - break + try: + line = self._readline() + if line == None: + break - ##~~ Error handling - # No matter the state, if we see an error, goto the error state and store the error for reference. - if line.startswith('Error:'): - #Oh YEAH, consistency. - # Marlin reports an MIN/MAX temp error as "Error:x\n: Extruder switched off. MAXTEMP triggered !\n" - # But a bed temp error is reported as "Error: Temperature heated bed switched off. MAXTEMP triggered !!" - # So we can have an extra newline in the most common case. Awesome work people. - if re.match('Error:[0-9]\n', line): - line = line.rstrip() + self._readline() - #Skip the communication errors, as those get corrected. - if 'checksum mismatch' in line \ - or 'Wrong checksum' in line \ - or 'Line Number is not Last Line Number' in line \ - or 'expected line' in line \ - or 'No Line Number with checksum' in line \ - or 'No Checksum with line number' in line \ - or 'Missing checksum' in line: - pass - elif not self.isError(): - self._errorValue = line[6:] - self._changeState(self.STATE_ERROR) + ##~~ Error handling + # No matter the state, if we see an error, goto the error state and store the error for reference. + if line.startswith('Error:'): + #Oh YEAH, consistency. + # Marlin reports an MIN/MAX temp error as "Error:x\n: Extruder switched off. MAXTEMP triggered !\n" + # But a bed temp error is reported as "Error: Temperature heated bed switched off. MAXTEMP triggered !!" + # So we can have an extra newline in the most common case. Awesome work people. + if re.match('Error:[0-9]\n', line): + line = line.rstrip() + self._readline() + #Skip the communication errors, as those get corrected. + if 'checksum mismatch' in line \ + or 'Wrong checksum' in line \ + or 'Line Number is not Last Line Number' in line \ + or 'expected line' in line \ + or 'No Line Number with checksum' in line \ + or 'No Checksum with line number' in line \ + or 'Missing checksum' in line: + pass + elif not self.isError(): + self._errorValue = line[6:] + self._changeState(self.STATE_ERROR) - ##~~ SD file list - # if we are currently receiving an sd file list, each line is just a filename, so just read it and abort processing - if self._sdFileList and not 'End file list' in line: - self._sdFiles.append(line.strip().lower()) - continue + ##~~ SD file list + # if we are currently receiving an sd file list, each line is just a filename, so just read it and abort processing + if self._sdFileList and not 'End file list' in line: + self._sdFiles.append(line.strip().lower()) + continue - ##~~ Temperature processing - if ' T:' in line or line.startswith('T:'): - try: - self._temp = float(re.search("-?[0-9\.]*", line.split('T:')[1]).group(0)) - if ' B:' in line: - self._bedTemp = float(re.search("-?[0-9\.]*", line.split(' B:')[1]).group(0)) - - self._callback.mcTempUpdate(self._temp, self._bedTemp, self._targetTemp, self._bedTargetTemp) - except ValueError: - # catch conversion issues, we'll rather just not get the temperature update instead of killing the connection - pass - - #If we are waiting for an M109 or M190 then measure the time we lost during heatup, so we can remove that time from our printing time estimate. - if not 'ok' in line: - heatingUp = True - if self._heatupWaitStartTime != 0: - t = time.time() - self._heatupWaitTimeLost = t - self._heatupWaitStartTime - self._heatupWaitStartTime = t - - ##~~ SD Card handling - elif 'SD init fail' in line: - self._sdAvailable = False - self._sdFiles = [] - self._callback.mcSdStateChange(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: - self._sdAvailable = True - self.refreshSdFiles() - self._callback.mcSdStateChange(self._sdAvailable) - elif 'Begin file list' in line: - self._sdFiles = [] - self._sdFileList = True - elif 'End file list' in line: - self._sdFileList = False - self._callback.mcSdFiles(self._sdFiles) - elif 'SD printing byte' in line: - # answer to M27, at least on Marlin, Repetier and Sprinter: "SD printing byte %d/%d" - match = re.search("([0-9]*)/([0-9]*)", line) - self._currentFile.setFilepos(int(match.group(1))) - self._callback.mcProgress() - elif 'File opened' in line: - # answer to M23, at least on Marlin, Repetier and Sprinter: "File opened:%s Size:%d" - match = re.search("File opened:\s*(.*?)\s+Size:\s*([0-9]*)", line) - self._currentFile = PrintingSdFileInformation(match.group(1), int(match.group(2))) - elif 'File selected' in line: - # final answer to M23, at least on Marlin, Repetier and Sprinter: "File selected" - self._callback.mcFileSelected(self._currentFile.getFilename(), self._currentFile.getFilesize(), True) - elif 'Writing to file' in line: - # anwer to M28, at least on Marlin, Repetier and Sprinter: "Writing to file: %s" - self._printSection = "CUSTOM" - self._changeState(self.STATE_PRINTING) - elif 'Done printing file' in line: - # printer is reporting file finished printing - self._sdFilePos = 0 - self._changeState(self.STATE_OPERATIONAL) - self._callback.mcPrintjobDone() - - ##~~ 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.mcMessage(line) - - ##~~ Parsing for feedback commands - if feedbackControls: - for name, matcher, template in feedbackControls: + ##~~ Temperature processing + if ' T:' in line or line.startswith('T:'): try: - match = matcher.search(line) - if match is not None: - self._callback.mcReceivedRegisteredMessage(name, str.format(template, *(match.groups("n/a")))) - except: - # ignored on purpose + self._temp = float(re.search("-?[0-9\.]*", line.split('T:')[1]).group(0)) + if ' B:' in line: + self._bedTemp = float(re.search("-?[0-9\.]*", line.split(' B:')[1]).group(0)) + + self._callback.mcTempUpdate(self._temp, self._bedTemp, self._targetTemp, self._bedTargetTemp) + except ValueError: + # catch conversion issues, we'll rather just not get the temperature update instead of killing the connection pass - if "ok" in line and heatingUp: - heatingUp = False + #If we are waiting for an M109 or M190 then measure the time we lost during heatup, so we can remove that time from our printing time estimate. + if not 'ok' in line: + heatingUp = True + if self._heatupWaitStartTime != 0: + t = time.time() + self._heatupWaitTimeLost = t - self._heatupWaitStartTime + self._heatupWaitStartTime = t - ### Baudrate detection - if self._state == self.STATE_DETECT_BAUDRATE: - if line == '' or time.time() > timeout: - if len(self._baudrateDetectList) < 1: - self.close() - self._errorValue = "No more baudrates to test, and no suitable baudrate found." - self._changeState(self.STATE_ERROR) - elif self._baudrateDetectRetry > 0: - self._baudrateDetectRetry -= 1 - self._serial.write('\n') - self._log("Baudrate test retry: %d" % (self._baudrateDetectRetry)) - self._sendCommand("M105") - self._testingBaudrate = True - else: - baudrate = self._baudrateDetectList.pop(0) + ##~~ SD Card handling + elif 'SD init fail' in line: + self._sdAvailable = False + self._sdFiles = [] + self._callback.mcSdStateChange(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: + self._sdAvailable = True + self.refreshSdFiles() + self._callback.mcSdStateChange(self._sdAvailable) + elif 'Begin file list' in line: + self._sdFiles = [] + self._sdFileList = True + elif 'End file list' in line: + self._sdFileList = False + self._callback.mcSdFiles(self._sdFiles) + elif 'SD printing byte' in line: + # answer to M27, at least on Marlin, Repetier and Sprinter: "SD printing byte %d/%d" + match = re.search("([0-9]*)/([0-9]*)", line) + self._currentFile.setFilepos(int(match.group(1))) + self._callback.mcProgress() + elif 'File opened' in line: + # answer to M23, at least on Marlin, Repetier and Sprinter: "File opened:%s Size:%d" + match = re.search("File opened:\s*(.*?)\s+Size:\s*([0-9]*)", line) + self._currentFile = PrintingSdFileInformation(match.group(1), int(match.group(2))) + elif 'File selected' in line: + # final answer to M23, at least on Marlin, Repetier and Sprinter: "File selected" + self._callback.mcFileSelected(self._currentFile.getFilename(), self._currentFile.getFilesize(), True) + elif 'Writing to file' in line: + # anwer to M28, at least on Marlin, Repetier and Sprinter: "Writing to file: %s" + self._printSection = "CUSTOM" + self._changeState(self.STATE_PRINTING) + elif 'Done printing file' in line: + # printer is reporting file finished printing + self._sdFilePos = 0 + self._changeState(self.STATE_OPERATIONAL) + self._callback.mcPrintjobDone() + + ##~~ 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.mcMessage(line) + + ##~~ Parsing for feedback commands + if feedbackControls: + for name, matcher, template in feedbackControls: try: - self._serial.baudrate = baudrate - self._serial.timeout = 0.5 - self._log("Trying baudrate: %d" % (baudrate)) - self._baudrateDetectRetry = 5 - self._baudrateDetectTestOk = 0 - timeout = time.time() + 5 + match = matcher.search(line) + if match is not None: + self._callback.mcReceivedRegisteredMessage(name, str.format(template, *(match.groups("n/a")))) + except: + # ignored on purpose + pass + + if "ok" in line and heatingUp: + heatingUp = False + + ### Baudrate detection + if self._state == self.STATE_DETECT_BAUDRATE: + if line == '' or time.time() > timeout: + if len(self._baudrateDetectList) < 1: + self.close() + self._errorValue = "No more baudrates to test, and no suitable baudrate found." + self._changeState(self.STATE_ERROR) + elif self._baudrateDetectRetry > 0: + self._baudrateDetectRetry -= 1 self._serial.write('\n') + self._log("Baudrate test retry: %d" % (self._baudrateDetectRetry)) self._sendCommand("M105") self._testingBaudrate = True - except: - self._log("Unexpected error while setting baudrate: %d %s" % (baudrate, getExceptionString())) - elif 'ok' in line and 'T:' in line: - self._baudrateDetectTestOk += 1 - if self._baudrateDetectTestOk < 10: - self._log("Baudrate test ok: %d" % (self._baudrateDetectTestOk)) - self._sendCommand("M105") + else: + baudrate = self._baudrateDetectList.pop(0) + try: + self._serial.baudrate = baudrate + self._serial.timeout = 0.5 + self._log("Trying baudrate: %d" % (baudrate)) + self._baudrateDetectRetry = 5 + self._baudrateDetectTestOk = 0 + timeout = time.time() + 5 + self._serial.write('\n') + self._sendCommand("M105") + self._testingBaudrate = True + except: + self._log("Unexpected error while setting baudrate: %d %s" % (baudrate, getExceptionString())) + elif 'ok' in line and 'T:' in line: + self._baudrateDetectTestOk += 1 + if self._baudrateDetectTestOk < 10: + self._log("Baudrate test ok: %d" % (self._baudrateDetectTestOk)) + self._sendCommand("M105") + else: + self._sendCommand("M999") + self._serial.timeout = 2 + self._changeState(self.STATE_OPERATIONAL) + if self._sdAvailable: + self.refreshSdFiles() else: - self._sendCommand("M999") - self._serial.timeout = 2 + self._testingBaudrate = False + + ### Connection attempt + elif self._state == self.STATE_CONNECTING: + if (line == "" or "wait" in line) and startSeen: + self._sendCommand("M105") + elif "start" in line: + startSeen = True + elif "ok" in line and startSeen: self._changeState(self.STATE_OPERATIONAL) if self._sdAvailable: self.refreshSdFiles() - else: - self._testingBaudrate = False + elif time.time() > timeout: + self.close() - ### Connection attempt - elif self._state == self.STATE_CONNECTING: - if (line == "" or "wait" in line) and startSeen: - self._sendCommand("M105") - elif "start" in line: - startSeen = True - elif "ok" in line and startSeen: - self._changeState(self.STATE_OPERATIONAL) - if self._sdAvailable: - self.refreshSdFiles() - elif time.time() > timeout: - self.close() - - ### Operational - elif self._state == self.STATE_OPERATIONAL or self._state == self.STATE_PAUSED: - #Request the temperature on comm timeout (every 5 seconds) when we are not printing. - if line == "" or "wait" in line: - if self._resendDelta is not None: - self._resendNextCommand() - elif not self._commandQueue.empty(): - self._sendCommand(self._commandQueue.get()) - else: - self._sendCommand("M105") - tempRequestTimeout = time.time() + 5 - # resend -> start resend procedure from requested line - elif "resend" in line.lower() or "rs" in line: - self._handleResendRequest(line) - - ### Printing - elif self._state == self.STATE_PRINTING: - if line == "" and time.time() > timeout: - self._log("Communication timeout during printing, forcing a line") - line = 'ok' - - if self.isSdPrinting(): - if time.time() > tempRequestTimeout and not heatingUp: - self._sendCommand("M105") - tempRequestTimeout = time.time() + 5 - - if time.time() > sdStatusRequestTimeout and not heatingUp: - self._sendCommand("M27") - sdStatusRequestTimeout = time.time() + 1 - - if 'ok' or 'SD printing byte' in line: - timeout = time.time() + 5 - else: - # Even when printing request the temperature every 5 seconds. - if time.time() > tempRequestTimeout and not self.isStreaming(): - self._commandQueue.put("M105") - tempRequestTimeout = time.time() + 5 - - if 'ok' in line: - timeout = time.time() + 5 + ### Operational + elif self._state == self.STATE_OPERATIONAL or self._state == self.STATE_PAUSED: + #Request the temperature on comm timeout (every 5 seconds) when we are not printing. + if line == "" or "wait" in line: if self._resendDelta is not None: self._resendNextCommand() - elif not self._commandQueue.empty() and not self.isStreaming(): + elif not self._commandQueue.empty(): self._sendCommand(self._commandQueue.get()) else: - self._sendNext() + self._sendCommand("M105") + tempRequestTimeout = time.time() + 5 + # resend -> start resend procedure from requested line elif "resend" in line.lower() or "rs" in line: self._handleResendRequest(line) + + ### Printing + elif self._state == self.STATE_PRINTING: + if line == "" and time.time() > timeout: + self._log("Communication timeout during printing, forcing a line") + line = 'ok' + + if self.isSdPrinting(): + if time.time() > tempRequestTimeout and not heatingUp: + self._sendCommand("M105") + tempRequestTimeout = time.time() + 5 + + if time.time() > sdStatusRequestTimeout and not heatingUp: + self._sendCommand("M27") + sdStatusRequestTimeout = time.time() + 1 + + if 'ok' or 'SD printing byte' in line: + timeout = time.time() + 5 + else: + # Even when printing request the temperature every 5 seconds. + if time.time() > tempRequestTimeout and not self.isStreaming(): + self._commandQueue.put("M105") + tempRequestTimeout = time.time() + 5 + + if 'ok' in line: + timeout = time.time() + 5 + if self._resendDelta is not None: + self._resendNextCommand() + elif not self._commandQueue.empty() and not self.isStreaming(): + self._sendCommand(self._commandQueue.get()) + else: + self._sendNext() + elif "resend" in line.lower() or "rs" in line: + self._handleResendRequest(line) + except: + self._logger.exception("Something crashed inside the serial connection loop, please report this in OctoPrint's bug tracker:") + + errorMsg = "Please see octoprint.log for details" + self._log(errorMsg) + self._errorValue = errorMsg + self._changeState(self.STATE_ERROR) self._log("Connection closed, closing down monitor") def _handleResendRequest(self, line): @@ -849,7 +857,7 @@ class MachineCom(object): return ret def close(self, isError = False): - if self._serial != None: + if self._serial is not None: self._serial.close() if isError: self._changeState(self.STATE_CLOSED_WITH_ERROR) @@ -867,7 +875,7 @@ class MachineCom(object): # Make sure we are only handling one sending job at a time with self._sendingLock: self._logger.debug("Resending line %d, delta is %d, history log is %s items strong" % (self._currentLine - self._resendDelta, self._resendDelta, len(self._lastLines))) - cmd = self._lastLines[-(self._resendDelta+1)] + cmd = self._lastLines[-(self._resendDelta + 1)] lineNumber = self._currentLine - self._resendDelta self._doSendWithChecksum(cmd, lineNumber)