diff --git a/octoprint/server.py b/octoprint/server.py index adfaadb2..047d1f38 100644 --- a/octoprint/server.py +++ b/octoprint/server.py @@ -590,7 +590,8 @@ def getSettings(): "temperatureGraph": s.getBoolean(["feature", "temperatureGraph"]), "waitForStart": s.getBoolean(["feature", "waitForStartOnConnect"]), "alwaysSendChecksum": s.getBoolean(["feature", "alwaysSendChecksum"]), - "sdSupport": s.getBoolean(["feature", "sdSupport"]) + "sdSupport": s.getBoolean(["feature", "sdSupport"]), + "swallowOkAfterResend": s.getBoolean(["feature", "swallowOkAfterResend"]) }, "serial": { "port": connectionOptions["portPreference"], @@ -656,6 +657,7 @@ def setSettings(): if "waitForStart" in data["feature"].keys(): s.setBoolean(["feature", "waitForStartOnConnect"], data["feature"]["waitForStart"]) if "alwaysSendChecksum" in data["feature"].keys(): s.setBoolean(["feature", "alwaysSendChecksum"], data["feature"]["alwaysSendChecksum"]) if "sdSupport" in data["feature"].keys(): s.setBoolean(["feature", "sdSupport"], data["feature"]["sdSupport"]) + if "swallowOkAfterResend" in data["feature"].keys(): s.setBoolean(["feature", "swallowOkAfterResend"], data["feature"]["swallowOkAfterResend"]) if "serial" in data.keys(): if "autoconnect" in data["serial"].keys(): s.setBoolean(["serial", "autoconnect"], data["serial"]["autoconnect"]) diff --git a/octoprint/settings.py b/octoprint/settings.py index 4cc80222..f95ed104 100644 --- a/octoprint/settings.py +++ b/octoprint/settings.py @@ -53,7 +53,8 @@ default_settings = { "temperatureGraph": True, "waitForStartOnConnect": False, "alwaysSendChecksum": False, - "sdSupport": True + "sdSupport": True, + "swallowOkAfterResend": False }, "folder": { "uploads": None, @@ -109,7 +110,15 @@ default_settings = { "terminalFilters": [ { "name": "Suppress M105 requests/responses", "regex": "(Send: M105)|(Recv: ok T:)" }, { "name": "Suppress M27 requests/responses", "regex": "(Send: M27)|(Recv: SD printing byte)" } - ] + ], + "devel": { + "virtualPrinter": { + "enabled": False, + "okAfterResend": False, + "forceChecksum": False, + "okWithLinenumber": False + } + } } valid_boolean_trues = ["true", "yes", "y", "1"] diff --git a/octoprint/static/js/app/viewmodels/settings.js b/octoprint/static/js/app/viewmodels/settings.js index 2790e40d..c8cdd214 100644 --- a/octoprint/static/js/app/viewmodels/settings.js +++ b/octoprint/static/js/app/viewmodels/settings.js @@ -31,6 +31,7 @@ function SettingsViewModel(loginStateViewModel, usersViewModel) { self.feature_waitForStart = ko.observable(undefined); self.feature_alwaysSendChecksum = ko.observable(undefined); self.feature_sdSupport = ko.observable(undefined); + self.feature_swallowOkAfterResend = ko.observable(undefined); self.serial_port = ko.observable(); self.serial_baudrate = ko.observable(); @@ -103,6 +104,7 @@ function SettingsViewModel(loginStateViewModel, usersViewModel) { self.feature_waitForStart(response.feature.waitForStart); self.feature_alwaysSendChecksum(response.feature.alwaysSendChecksum); self.feature_sdSupport(response.feature.sdSupport); + self.feature_swallowOkAfterResend(response.feature.swallowOkAfterResend); self.serial_port(response.serial.port); self.serial_baudrate(response.serial.baudrate); @@ -156,7 +158,8 @@ function SettingsViewModel(loginStateViewModel, usersViewModel) { "temperatureGraph": self.feature_temperatureGraph(), "waitForStart": self.feature_waitForStart(), "alwaysSendChecksum": self.feature_alwaysSendChecksum(), - "sdSupport": self.feature_sdSupport() + "sdSupport": self.feature_sdSupport(), + "swallowOkAfterResend": self.feature_swallowOkAfterResend() }, "serial": { "port": self.serial_port(), diff --git a/octoprint/templates/settings.jinja2 b/octoprint/templates/settings.jinja2 index a015f5bc..f543793d 100644 --- a/octoprint/templates/settings.jinja2 +++ b/octoprint/templates/settings.jinja2 @@ -203,6 +203,13 @@ +
+
+ +
+
diff --git a/octoprint/util/comm.py b/octoprint/util/comm.py index 65d53690..45882d98 100644 --- a/octoprint/util/comm.py +++ b/octoprint/util/comm.py @@ -49,7 +49,7 @@ def serialList(): if prev in baselist: baselist.remove(prev) baselist.insert(0, prev) - if isDevVersion(): + if settings().getBoolean(["devel", "virtualPrinter", "enabled"]): baselist.append("VIRTUAL") return baselist @@ -121,7 +121,7 @@ class MachineCom(object): self._heatupWaitTimeLost = 0.0 self._alwaysSendChecksum = settings().getBoolean(["feature", "alwaysSendChecksum"]) - self._currentLine = 0 + self._currentLine = 1 self._resendDelta = None self._lastLines = deque([], 50) @@ -499,6 +499,7 @@ class MachineCom(object): sdStatusRequestTimeout = timeout startSeen = not settings().getBoolean(["feature", "waitForStartOnConnect"]) heatingUp = False + swallowOk = False while True: try: line = self._readline() @@ -704,6 +705,8 @@ class MachineCom(object): tempRequestTimeout = getNewTimeout("communication") # resend -> start resend procedure from requested line elif line.lower().startswith("resend") or line.lower().startswith("rs"): + if settings().get(["feature", "swallowOkAfterResend"]): + swallowOk = True self._handleResendRequest(line) ### Printing @@ -729,7 +732,9 @@ class MachineCom(object): self._commandQueue.put("M105") tempRequestTimeout = getNewTimeout("communication") - if 'ok' in line: + if "ok" in line and swallowOk: + swallowOk = False + elif "ok" in line: timeout = getNewTimeout("communication") if self._resendDelta is not None: self._resendNextCommand() @@ -738,6 +743,8 @@ class MachineCom(object): else: self._sendNext() elif line.lower().startswith("resend") or line.lower().startswith("rs"): + if settings().get(["feature", "swallowOkAfterResend"]): + swallowOk = True self._handleResendRequest(line) except: self._logger.exception("Something crashed inside the serial connection loop, please report this in OctoPrint's bug tracker:") @@ -799,7 +806,7 @@ class MachineCom(object): if lineToResend is not None: self._resendDelta = self._currentLine - lineToResend - if self._resendDelta > len(self._lastLines) or len(self._lastLines) == 0: + if self._resendDelta > len(self._lastLines) or len(self._lastLines) == 0 or self._resendDelta <= 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(): diff --git a/octoprint/util/virtual.py b/octoprint/util/virtual.py index cf699f6c..6a445593 100644 --- a/octoprint/util/virtual.py +++ b/octoprint/util/virtual.py @@ -32,6 +32,7 @@ class VirtualPrinter(): self._newSdFilePos = None self.currentLine = 0 + self.lastN = 0 waitThread = threading.Thread(target=self._sendWaitAfterTimeout) waitThread.start() @@ -40,18 +41,49 @@ class VirtualPrinter(): if self.readList is None: return - # strip checksum data = data.strip() + + # strip checksum if "*" in data: data = data[:data.rfind("*")] self.currentLine += 1 + elif settings().getBoolean(["devel", "virtualPrinter", "forceChecksum"]): + self.readList.append("Error: Missing checksum") + return + + # track N = N + 1 + if data.startswith("N") and "M110" in data: + linenumber = int(re.search("N([0-9]+)", data).group(1)) + self.lastN = linenumber + self.currentLine = linenumber + return + elif data.startswith("N"): + linenumber = int(re.search("N([0-9]+)", data).group(1)) + expected = self.lastN + 1 + if linenumber != expected: + self.readList.append("Error: expected line %d got %d" % (expected, linenumber)) + self.readList.append("Resend:%d" % expected) + if settings().getBoolean(["devel", "virtualPrinter", "okAfterResend"]): + self.readList.append("ok") + return + elif self.currentLine == 100: + # simulate a resend at line 100 of the last 5 lines + self.lastN = 94 + self.readList.append("Error: Line Number is not Last Line Number\n") + self.readList.append("rs %d\n" % (self.currentLine - 5)) + if settings().getBoolean(["devel", "virtualPrinter", "okAfterResend"]): + self.readList.append("ok") + return + else: + self.lastN = linenumber + data += "\n" # shortcut for writing to SD if self._writingToSd and not self._selectedSdFile is None and not "M29" in data: with open(self._selectedSdFile, "a") as f: f.write(data) - self.readList.append("ok") + self._sendOk() return #print "Send: %s" % (data.rstrip()) @@ -105,11 +137,6 @@ class VirtualPrinter(): if self._sdCardReady: filename = data.split(None, 1)[1].strip() self._deleteSdFile(filename) - elif "M110" in data: - # reset current line - self.currentLine = int(re.search('^N([0-9]+)', data).group(1)) - self.readList.append("reset line to %r\n" % self.currentLine) - 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") @@ -119,19 +146,15 @@ class VirtualPrinter(): 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") - self.readList.append("rs %d\n" % (self.currentLine - 5)) elif len(data.strip()) > 0: - self.readList.append("ok\n") + self._sendOk() def _listSd(self): self.readList.append("Begin file list") for osFile in os.listdir(self._virtualSd): self.readList.append(osFile.upper()) self.readList.append("End file list") - self.readList.append("ok") + self._sendOk() def _selectSdFile(self, filename): file = os.path.join(self._virtualSd, filename).lower() @@ -149,11 +172,11 @@ class VirtualPrinter(): self._sdPrinter = threading.Thread(target=self._sdPrintingWorker) self._sdPrinter.start() self._sdPrintingSemaphore.set() - self.readList.append("ok") + self._sendOk() def _pauseSdPrint(self): self._sdPrintingSemaphore.clear() - self.readList.append("ok") + self._sendOk() def _setSdPos(self, pos): self._newSdFilePos = pos @@ -175,12 +198,12 @@ class VirtualPrinter(): self._writingToSd = True self._selectedSdFile = file self.readList.append("Writing to file: %s" % filename) - self.readList.append("ok") + self._sendOk() def _finishSdFile(self): self._writingToSd = False self._selectedSdFile = None - self.readList.append("ok") + self._sendOk() def _sdPrintingWorker(self): self._selectedSdFilePos = 0 @@ -220,7 +243,7 @@ class VirtualPrinter(): file = os.path.join(self._virtualSd, filename) if os.path.exists(file) and os.path.isfile(file): os.remove(file) - self.readList.append("ok") + self._sendOk() def readline(self): if self.readList is None: @@ -249,7 +272,14 @@ class VirtualPrinter(): def close(self): self.readList = None + def _sendOk(self): + if settings().getBoolean(["devel", "virtualPrinter", "okWithLinenumber"]): + self.readList.append("ok %d" % self.lastN) + else: + self.readList.append("ok") + def _sendWaitAfterTimeout(self, timeout=5): time.sleep(timeout) - self.readList.append("wait") + if self.readList is not None: + self.readList.append("wait")