diff --git a/src/octoprint/server/api/settings.py b/src/octoprint/server/api/settings.py index 8937f35d..34a7dffb 100644 --- a/src/octoprint/server/api/settings.py +++ b/src/octoprint/server/api/settings.py @@ -93,7 +93,8 @@ def getSettings(): "helloCommand": s.get(["serial", "helloCommand"]), "ignoreErrorsFromFirmware": s.getBoolean(["serial", "ignoreErrorsFromFirmware"]), "disconnectOnErrors": s.getBoolean(["serial", "disconnectOnErrors"]), - "triggerOkForM29": s.getBoolean(["serial", "triggerOkForM29"]) + "triggerOkForM29": s.getBoolean(["serial", "triggerOkForM29"]), + "supportResendsWithoutOk": s.getBoolean(["serial", "supportResendsWithoutOk"]) }, "folder": { "uploads": s.getBaseFolder("uploads"), @@ -248,6 +249,7 @@ def _saveSettings(data): if "ignoreErrorsFromFirmware" in data["serial"]: s.setBoolean(["serial", "ignoreErrorsFromFirmware"], data["serial"]["ignoreErrorsFromFirmware"]) if "disconnectOnErrors" in data["serial"]: s.setBoolean(["serial", "disconnectOnErrors"], data["serial"]["disconnectOnErrors"]) if "triggerOkForM29" in data["serial"]: s.setBoolean(["serial", "triggerOkForM29"], data["serial"]["triggerOkForM29"]) + if "supportResendsWithoutOk" in data["serial"]: s.setBoolean(["serial", "supportResendsWithoutOk"], data["serial"]["supportResendsWithoutOk"]) oldLog = s.getBoolean(["serial", "log"]) if "log" in data["serial"].keys(): s.setBoolean(["serial", "log"], data["serial"]["log"]) diff --git a/src/octoprint/settings.py b/src/octoprint/settings.py index 3faa63ad..a94e9072 100644 --- a/src/octoprint/settings.py +++ b/src/octoprint/settings.py @@ -92,7 +92,8 @@ default_settings = { "helloCommand": "M110 N0", "disconnectOnErrors": True, "ignoreErrorsFromFirmware": False, - "logResends": False, + "logResends": True, + "supportResendsWithoutOk": False, # command specific flags "triggerOkForM29": True diff --git a/src/octoprint/static/js/app/viewmodels/files.js b/src/octoprint/static/js/app/viewmodels/files.js index ebcca1e0..30da0ada 100644 --- a/src/octoprint/static/js/app/viewmodels/files.js +++ b/src/octoprint/static/js/app/viewmodels/files.js @@ -506,7 +506,12 @@ $(function() { self.uploadProgress = $("#gcode_upload_progress"); self.uploadProgressBar = $(".bar", self.uploadProgress); - self.localTarget = CONFIG_SD_SUPPORT ? $("#drop_locally") : $("#drop"); + if (CONFIG_SD_SUPPORT) { + self.localTarget = $("#drop_locally"); + } else { + self.localTarget = $("#drop"); + self.listHelper.removeFilter('sd'); + } self.sdTarget = $("#drop_sd"); function evaluateDropzones() { diff --git a/src/octoprint/static/js/app/viewmodels/settings.js b/src/octoprint/static/js/app/viewmodels/settings.js index 3bb9242a..ea3dcef0 100644 --- a/src/octoprint/static/js/app/viewmodels/settings.js +++ b/src/octoprint/static/js/app/viewmodels/settings.js @@ -146,6 +146,7 @@ $(function() { self.serial_ignoreErrorsFromFirmware = ko.observable(undefined); self.serial_disconnectOnErrors = ko.observable(undefined); self.serial_triggerOkForM29 = ko.observable(undefined); + self.serial_supportResendsWithoutOk = ko.observable(undefined); self.folder_uploads = ko.observable(undefined); self.folder_timelapse = ko.observable(undefined); diff --git a/src/octoprint/templates/dialogs/settings/serialconnection.jinja2 b/src/octoprint/templates/dialogs/settings/serialconnection.jinja2 index f628a485..cfac73a4 100644 --- a/src/octoprint/templates/dialogs/settings/serialconnection.jinja2 +++ b/src/octoprint/templates/dialogs/settings/serialconnection.jinja2 @@ -131,6 +131,16 @@ {{ _('Use this to specify which commands always need to be sent with a checksum. Comma separated list.') }} +
+ +
+
+ +
diff --git a/src/octoprint/util/comm.py b/src/octoprint/util/comm.py index d67c07cf..27181edb 100644 --- a/src/octoprint/util/comm.py +++ b/src/octoprint/util/comm.py @@ -262,11 +262,12 @@ class MachineCom(object): self._lastCommError = None self._lastResendNumber = None self._currentResendCount = 0 - self._resendSwallowNextOk = False self._resendSwallowRepetitions = settings().getBoolean(["feature", "ignoreIdenticalResends"]) self._resendSwallowRepetitionsCounter = 0 self._checksum_requiring_commands = settings().get(["serial", "checksumRequiringCommands"]) + self._supportResendsWithoutOk = settings().getBoolean(["serial", "supportResendsWithoutOk"]) + self._resendActive = False self._terminal_log = deque([], 20) @@ -276,11 +277,11 @@ class MachineCom(object): self._log_resends = settings().getBoolean(["serial", "logResends"]) - # don't log more resends than 5 / 10s + # don't log more resends than 5 / 60s self._log_resends_rate_start = None self._log_resends_rate_count = 0 self._log_resends_max = 5 - self._log_resends_rate_frame = 10 + self._log_resends_rate_frame = 60 self._long_running_commands = settings().get(["serial", "longRunningCommands"]) self._checksum_requiring_commands = settings().get(["serial", "checksumRequiringCommands"]) @@ -1644,84 +1645,89 @@ class MachineCom(object): self._callback.on_comm_progress() def _handleResendRequest(self, line): - lineToResend = None try: - lineToResend = int(line.replace("N:", " ").replace("N", " ").replace(":", " ").split()[-1]) - except: - if "rs" in line: - lineToResend = int(line.split()[1]) + lineToResend = None + try: + lineToResend = int(line.replace("N:", " ").replace("N", " ").replace(":", " ").split()[-1]) + except: + if "rs" in line: + lineToResend = int(line.split()[1]) - if lineToResend is None: - return False + if lineToResend is None: + return False - if self._resendDelta is None and lineToResend == self._currentLine: - # We don't expect to have an active resend request and the printer is requesting a resend of - # a line we haven't yet sent. + if self._resendDelta is None and lineToResend == self._currentLine: + # We don't expect to have an active resend request and the printer is requesting a resend of + # a line we haven't yet sent. + # + # This means the printer got a line from us with N = self._currentLine - 1 but had already + # acknowledged that. This can happen if the last line was resent due to a timeout during + # an active (prior) resend request. + # + # We will ignore this resend request and just continue normally. + self._logger.debug("Ignoring resend request for line %d == current line, we haven't sent that yet so the printer got N-1 twice from us, probably due to a timeout" % lineToResend) + return False + + lastCommError = self._lastCommError + self._lastCommError = None + + resendDelta = self._currentLine - lineToResend + + if lastCommError is not None \ + and ("line number" in lastCommError.lower() or "expected line" in lastCommError.lower()) \ + and lineToResend == self._lastResendNumber \ + and self._resendDelta is not None and self._currentResendCount < self._resendDelta: + self._logger.debug("Ignoring resend request for line %d, that still originates from lines we sent before we got the first resend request" % lineToResend) + self._currentResendCount += 1 + return True + + # If we ignore resend repetitions (Repetier firmware...), check if we + # need to do this now. If the same line number has been requested we + # already saw and resent, we'll ignore it up to times. + if self._resendSwallowRepetitions and lineToResend == self._lastResendNumber and self._resendSwallowRepetitionsCounter > 0: + self._logger.debug("Ignoring resend request for line %d, that is probably a repetition sent by the firmware to ensure it arrives, not a real request" % lineToResend) + self._resendSwallowRepetitionsCounter -= 1 + return True + + self._resendActive = True + self._resendDelta = resendDelta + self._lastResendNumber = lineToResend + self._currentResendCount = 0 + self._resendSwallowRepetitionsCounter = settings().getInt(["feature", "identicalResendsCountdown"]) + + 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._log(self._errorValue) + self._logger.warn(self._errorValue + ". Printer requested line {}, current line is {}, line history has {} entries.".format(lineToResend, self._currentLine, len(self._lastLines))) + if self.isPrinting(): + # abort the print, there's nothing we can do to rescue it now + self._changeState(self.STATE_ERROR) + eventManager().fire(Events.ERROR, {"error": self.getErrorString()}) + else: + # reset resend delta, we can't do anything about it + self._resendDelta = None + + # if we log resends, make sure we don't log more resends than the set rate within a window # - # This means the printer got a line from us with N = self._currentLine - 1 but had already - # acknowledged that. This can happen if the last line was resent due to a timeout during - # an active (prior) resend request. - # - # We will ignore this resend request and just continue normally. - self._logger.debug("Ignoring resend request for line %d == current line, we haven't sent that yet so the printer got N-1 twice from us, probably due to a timeout" % lineToResend) - return False + # this it to prevent the log from getting flooded for extremely bad communication issues + if self._log_resends: + now = time.time() + new_rate_window = self._log_resends_rate_start is None or self._log_resends_rate_start + self._log_resends_rate_frame < now + in_rate = self._log_resends_rate_count < self._log_resends_max - lastCommError = self._lastCommError - self._lastCommError = None + if new_rate_window or in_rate: + if new_rate_window: + self._log_resends_rate_start = now + self._log_resends_rate_count = 0 - resendDelta = self._currentLine - lineToResend + self._to_logfile_with_terminal("Got a resend request from the printer: requested line = {}, current line = {}".format(lineToResend, self._currentLine)) + self._log_resends_rate_count += 1 - if lastCommError is not None \ - and ("line number" in lastCommError.lower() or "expected line" in lastCommError.lower()) \ - and lineToResend == self._lastResendNumber \ - and self._resendDelta is not None and self._currentResendCount < self._resendDelta: - self._logger.debug("Ignoring resend request for line %d, that still originates from lines we sent before we got the first resend request" % lineToResend) - self._currentResendCount += 1 return True - - # If we ignore resend repetitions (Repetier firmware...), check if we - # need to do this now. If the same line number has been requested we - # already saw and resent, we'll ignore it up to times. - if self._resendSwallowRepetitions and lineToResend == self._lastResendNumber and self._resendSwallowRepetitionsCounter > 0: - self._logger.debug("Ignoring resend request for line %d, that is probably a repetition sent by the firmware to ensure it arrives, not a real request" % lineToResend) - self._resendSwallowRepetitionsCounter -= 1 - return True - - self._resendActive = True - self._resendDelta = resendDelta - self._lastResendNumber = lineToResend - self._currentResendCount = 0 - self._resendSwallowRepetitionsCounter = settings().getInt(["feature", "identicalResendsCountdown"]) - - 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._log(self._errorValue) - self._logger.warn(self._errorValue + ". Printer requested line {}, current line is {}, line history has {} entries.".format(lineToResend, self._currentLine, len(self._lastLines))) - if self.isPrinting(): - # abort the print, there's nothing we can do to rescue it now - self._changeState(self.STATE_ERROR) - eventManager().fire(Events.ERROR, {"error": self.getErrorString()}) - else: - # reset resend delta, we can't do anything about it - self._resendDelta = None - - # if we log resends, make sure we don't log more resends than the set rate within a window - # - # this it to prevent the log from getting flooded for extremely bad communication issues - if self._log_resends: - now = time.time() - new_rate_window = self._log_resends_rate_start is None or self._log_resends_rate_start + self._log_resends_rate_frame < now - in_rate = self._log_resends_rate_count < self._log_resends_max - - if new_rate_window or in_rate: - if new_rate_window: - self._log_resends_rate_start = now - self._log_resends_rate_count = 0 - - self._to_logfile_with_terminal("Got a resend request from the printer: requested line = {}, current line = {}".format(lineToResend, self._currentLine)) - self._log_resends_rate_count += 1 - - return True + finally: + if self._supportResendsWithoutOk: + # simulate an ok if our flags indicate that the printer needs that for resend requests to work + self._handle_ok() def _resendSameCommand(self): self._resendNextCommand(again=True)