From bbad030a9227db6ca2df42d393c38fb4db6817a4 Mon Sep 17 00:00:00 2001 From: Bryan Mayland Date: Thu, 20 Jun 2013 17:41:10 +0200 Subject: [PATCH 1/6] Resend correct gcode line when resend is requested (manually cherry picked from commit c8875fd) --- octoprint/util/comm.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/octoprint/util/comm.py b/octoprint/util/comm.py index c846e930..74296f5d 100644 --- a/octoprint/util/comm.py +++ b/octoprint/util/comm.py @@ -40,7 +40,13 @@ def serialList(): i+=1 except: pass - baselist = baselist + glob.glob("/dev/ttyUSB*") + glob.glob("/dev/ttyACM*") + glob.glob("/dev/tty.usb*") + glob.glob("/dev/cu.*") + glob.glob("/dev/rfcomm*") + baselist = baselist \ + + glob.glob("/dev/ttyUSB*") \ + + glob.glob("/dev/ttyACM*") \ + + glob.glob("/dev/ttyAMA*") \ + + glob.glob("/dev/tty.usb*") \ + + glob.glob("/dev/cu.*") \ + + glob.glob("/dev/rfcomm*") prev = settings().get(["serial", "port"]) if prev in baselist: baselist.remove(prev) @@ -766,10 +772,11 @@ class MachineCom(object): if lineToResend is not None: self._resendDelta = self._currentLine - lineToResend - if self._resendDelta > len(self._lastLines): + if self._resendDelta >= len(self._lastLines): self._errorValue = "Printer requested line %d but history is only available up to line %d" % (lineToResend, self._currentLine - len(self._lastLines)) - self._changeState(self.STATE_ERROR) self._logger.warn(self._errorValue) + if self.isPrinting(): + self._changeState(self.STATE_ERROR) else: self._resendNextCommand() @@ -821,13 +828,13 @@ 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] + cmd = self._lastLines[-(self._resendDelta+1)] lineNumber = self._currentLine - self._resendDelta self._doSendWithChecksum(cmd, lineNumber) self._resendDelta -= 1 - if self._resendDelta <= 0: + if self._resendDelta < 0: self._resendDelta = None def _sendCommand(self, cmd, sendChecksum=False): From 66ddf77be8711e4403f265a74b0d32385ec3959f Mon Sep 17 00:00:00 2001 From: Bryan Mayland Date: Thu, 20 Jun 2013 17:41:10 +0200 Subject: [PATCH 2/6] Be able to cope with M999 causing a Resend: 1 (cherry picked from commit 8b702db) --- octoprint/util/comm.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/octoprint/util/comm.py b/octoprint/util/comm.py index 74296f5d..4b77a777 100644 --- a/octoprint/util/comm.py +++ b/octoprint/util/comm.py @@ -152,6 +152,12 @@ class VirtualPrinter(): # reset current line self.currentLine = int(re.search('N([0-9]+)', data).group(1)) self.readList.append("ok\n") + elif "M114" in data: + # send dummy position report + self.readList.append("ok C: X:10.00 Y:3.20 Z:5.20 E:1.24") + elif "M999" in data: + # mirror Marlin behaviour + self.readList.append("Resend: 1") elif self.currentLine == 100: # simulate a resend at line 100 of the last 5 lines self.readList.append("Error: Line Number is not Last Line Number\n") @@ -773,10 +779,14 @@ class MachineCom(object): if lineToResend is not None: self._resendDelta = self._currentLine - lineToResend if self._resendDelta >= len(self._lastLines): - self._errorValue = "Printer requested line %d but history is only available up to line %d" % (lineToResend, self._currentLine - len(self._lastLines)) + self._errorValue = "Printer requested line %d but no sufficient history is available, can't resend" % lineToResend self._logger.warn(self._errorValue) if self.isPrinting(): + # abort the print, there's nothing we can do to rescue it now self._changeState(self.STATE_ERROR) + else: + # reset resend delta, we can't do anything about it + self._resendDelta = None else: self._resendNextCommand() From e5389d0f0f55c6c31af739be3f9fe3636ea3d96f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Mon, 24 Jun 2013 18:24:45 +0200 Subject: [PATCH 3/6] Reverted bbad030a9227db6ca2df42d393c38fb4db6817a4 Correct in devel branch, wrong in master branch (different counting). Closes #166 --- octoprint/util/comm.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/octoprint/util/comm.py b/octoprint/util/comm.py index 4b77a777..9cb41c45 100644 --- a/octoprint/util/comm.py +++ b/octoprint/util/comm.py @@ -778,7 +778,7 @@ class MachineCom(object): if lineToResend is not None: self._resendDelta = self._currentLine - lineToResend - if self._resendDelta >= len(self._lastLines): + if self._resendDelta > len(self._lastLines): self._errorValue = "Printer requested line %d but no sufficient history is available, can't resend" % lineToResend self._logger.warn(self._errorValue) if self.isPrinting(): @@ -838,13 +838,13 @@ 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] lineNumber = self._currentLine - self._resendDelta self._doSendWithChecksum(cmd, lineNumber) self._resendDelta -= 1 - if self._resendDelta < 0: + if self._resendDelta <= 0: self._resendDelta = None def _sendCommand(self, cmd, sendChecksum=False): From c9fb4e1dedf7b7a5874a8698febb50454f4d78e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Tue, 25 Jun 2013 19:07:43 +0200 Subject: [PATCH 4/6] Also send identity_changed event on passive login (cherry picked from commit 695f3f3) --- octoprint/server.py | 1 + 1 file changed, 1 insertion(+) diff --git a/octoprint/server.py b/octoprint/server.py index 4ede4edc..e038adc8 100644 --- a/octoprint/server.py +++ b/octoprint/server.py @@ -655,6 +655,7 @@ def login(): elif "passive" in request.values.keys(): user = current_user if user is not None and not user.is_anonymous(): + identity_changed.send(current_app._get_current_object(), identity=Identity(user.get_id())) return jsonify(user.asDict()) return jsonify(SUCCESS) From 24bb0fdf05e1c97daed98223da3fc1751eac5056 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Thu, 27 Jun 2013 21:05:01 +0200 Subject: [PATCH 5/6] Added missing return statement that caused an exception --- octoprint/settings.py | 1 + 1 file changed, 1 insertion(+) diff --git a/octoprint/settings.py b/octoprint/settings.py index 71251b4c..f260667c 100644 --- a/octoprint/settings.py +++ b/octoprint/settings.py @@ -239,6 +239,7 @@ class Settings(object): def setInt(self, path, value, force=False): if value is None: self.set(path, None, force) + return try: intValue = int(value) From dd3e9030faeabcb22a36c44766f1d33bc7e38a60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Thu, 27 Jun 2013 21:12:06 +0200 Subject: [PATCH 6/6] Yet another fix for the M999 resend issue Should hopefully now be also fixed in case of a newly established connection with the printer, which was a regression due to the fix of the resending code. --- octoprint/util/comm.py | 356 +++++++++++++++++++++-------------------- 1 file changed, 182 insertions(+), 174 deletions(-) diff --git a/octoprint/util/comm.py b/octoprint/util/comm.py index 9cb41c45..9fa25b6e 100644 --- a/octoprint/util/comm.py +++ b/octoprint/util/comm.py @@ -578,194 +578,202 @@ class MachineCom(object): sdStatusRequestTimeout = timeout startSeen = not settings().getBoolean(["feature", "waitForStartOnConnect"]) 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) - - ##~~ 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) - 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 and 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 '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._sdFilePos = int(match.group(1)) - self._sdFileSize = int(match.group(2)) - 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._sdFile = match.group(1) - self._sdFileSize = int(match.group(2)) - elif 'File selected' in line: - # final answer to M23, at least on Marlin, Repetier and Sprinter: "File selected" - self._callback.mcSdSelected(self._sdFile, self._sdFileSize) - elif 'Done printing file' in line: - # printer is reporting file finished printing - self._sdPrinting = False - self._sdFilePos = 0 - self._changeState(self.STATE_OPERATIONAL) - self._callback.mcSdPrintingDone() - - ##~~ 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) - - ### 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." + ##~~ 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) - 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) - 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 + + ##~~ 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) + 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 and 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 '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._sdFilePos = int(match.group(1)) + self._sdFileSize = int(match.group(2)) + 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._sdFile = match.group(1) + self._sdFileSize = int(match.group(2)) + elif 'File selected' in line: + # final answer to M23, at least on Marlin, Repetier and Sprinter: "File selected" + self._callback.mcSdSelected(self._sdFile, self._sdFileSize) + elif 'Done printing file' in line: + # printer is reporting file finished printing + self._sdPrinting = False + self._sdFilePos = 0 + self._changeState(self.STATE_OPERATIONAL) + self._callback.mcSdPrintingDone() + + ##~~ 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) + + ### 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) 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) - 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) - 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._sdPrinting: - if time.time() > tempRequestTimeout: - self._sendCommand("M105") - tempRequestTimeout = time.time() + 5 - - if time.time() > sdStatusRequestTimeout: - 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: - 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(): 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._sdPrinting: + if time.time() > tempRequestTimeout: + self._sendCommand("M105") + tempRequestTimeout = time.time() + 5 + + if time.time() > sdStatusRequestTimeout: + 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: + 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(): + 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 = "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): @@ -778,7 +786,7 @@ class MachineCom(object): if lineToResend is not None: self._resendDelta = self._currentLine - lineToResend - if self._resendDelta > len(self._lastLines): + if self._resendDelta > len(self._lastLines) or len(self._lastLines) == 0: self._errorValue = "Printer requested line %d but no sufficient history is available, can't resend" % lineToResend self._logger.warn(self._errorValue) if self.isPrinting():