Made communication mode more error resilient
parent
8b702db0d0
commit
50914df07a
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue