diff --git a/src/octoprint/plugins/virtual_printer/virtual.py b/src/octoprint/plugins/virtual_printer/virtual.py index 28ec22f1..75681fea 100644 --- a/src/octoprint/plugins/virtual_printer/virtual.py +++ b/src/octoprint/plugins/virtual_printer/virtual.py @@ -454,6 +454,8 @@ class VirtualPrinter(object): self._triggerResend(expected=self.lastN) elif data == "drop_connection": self._debug_drop_connection = True + elif data == "maxtemp_error": + self.outgoing.put("Error: MAXTEMP triggered!") else: try: sleep_match = VirtualPrinter.sleep_regex.match(data) diff --git a/src/octoprint/server/api/settings.py b/src/octoprint/server/api/settings.py index 3419c11f..fd44d701 100644 --- a/src/octoprint/server/api/settings.py +++ b/src/octoprint/server/api/settings.py @@ -90,7 +90,9 @@ def getSettings(): "additionalBaudrates": s.get(["serial", "additionalBaudrates"]), "longRunningCommands": s.get(["serial", "longRunningCommands"]), "checksumRequiringCommands": s.get(["serial", "checksumRequiringCommands"]), - "helloCommand": s.get(["serial", "helloCommand"]) + "helloCommand": s.get(["serial", "helloCommand"]), + "ignoreErrorsFromFirmware": s.getBoolean(["serial", "ignoreErrorsFromFirmware"]), + "disconnectOnErrors": s.getBoolean(["serial", "disconnectOnErrors"]), }, "folder": { "uploads": s.getBaseFolder("uploads"), @@ -242,6 +244,8 @@ def _saveSettings(data): if "longRunningCommands" in data["serial"] and isinstance(data["serial"]["longRunningCommands"], (list, tuple)): s.set(["serial", "longRunningCommands"], data["serial"]["longRunningCommands"]) if "checksumRequiringCommands" in data["serial"] and isinstance(data["serial"]["checksumRequiringCommands"], (list, tuple)): s.set(["serial", "checksumRequiringCommands"], data["serial"]["checksumRequiringCommands"]) if "helloCommand" in data["serial"]: s.set(["serial", "helloCommand"], data["serial"]["helloCommand"]) + if "ignoreErrorsFromFirmware" in data["serial"]: s.setBoolean(["serial", "ignoreErrorsFromFirmware"], data["serial"]["ignoreErrorsFromFirmware"]) + if "disconnectOnErrors" in data["serial"]: s.setBoolean(["serial", "disconnectOnErrors"], data["serial"]["disconnectOnErrors"]) 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 7c8b2c64..6ba0534d 100644 --- a/src/octoprint/settings.py +++ b/src/octoprint/settings.py @@ -90,6 +90,8 @@ default_settings = { "longRunningCommands": ["G4", "G28", "G29", "G30", "G32", "M400", "M226"], "checksumRequiringCommands": ["M110"], "helloCommand": "M110 N0", + "disconnectOnErrors": True, + "ignoreErrorsFromFirmware": False }, "server": { "host": "0.0.0.0", diff --git a/src/octoprint/static/js/app/dataupdater.js b/src/octoprint/static/js/app/dataupdater.js index f4cb7d55..cf8d8d03 100644 --- a/src/octoprint/static/js/app/dataupdater.js +++ b/src/octoprint/static/js/app/dataupdater.js @@ -231,6 +231,22 @@ function DataUpdater(allViewModels) { text: _.sprintf(gettext("Streamed %(local)s to %(remote)s on SD, took %(time).2f seconds"), payload), type: "success" }); + } else if (type == "PrintCancelled") { + if (payload.firmwareError) { + new PNotify({ + title: gettext("Unhandled firmware error"), + text: _.sprintf(gettext("The firmware reported an unhandled error. Due to that the ongoing print job was cancelled. Error: %(firmwareError)s"), payload), + type: "error", + hide: false + }); + } + } else if (type == "Error") { + new PNotify({ + title: gettext("Unhandled firmware error"), + text: _.sprintf(gettext("The firmware reported an unhandled error. Due to that OctoPrint disconnected. Error: %(error)s"), payload), + type: "error", + hide: false + }); } var legacyEventHandlers = { diff --git a/src/octoprint/static/js/app/viewmodels/settings.js b/src/octoprint/static/js/app/viewmodels/settings.js index d7973486..3d6f63ae 100644 --- a/src/octoprint/static/js/app/viewmodels/settings.js +++ b/src/octoprint/static/js/app/viewmodels/settings.js @@ -143,6 +143,8 @@ $(function() { self.serial_longRunningCommands = ko.observable(undefined); self.serial_checksumRequiringCommands = ko.observable(undefined); self.serial_helloCommand = ko.observable(undefined); + self.serial_ignoreErrorsFromFirmware = ko.observable(undefined); + self.serial_disconnectOnErrors = 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 dbeb1703..3530d32f 100644 --- a/src/octoprint/templates/dialogs/settings/serialconnection.jinja2 +++ b/src/octoprint/templates/dialogs/settings/serialconnection.jinja2 @@ -92,6 +92,20 @@ {{ _('Use this to define additional serial port baud rates to list for connecting with, e.g. 123456. Comma separated.')|format(glob_url="http://docs.python.org/2/library/glob.html") }} +
+
+ +
+
+
+
+ +
+
{{ _('Advanced options') }}
diff --git a/src/octoprint/util/comm.py b/src/octoprint/util/comm.py index 87f2765e..c7c647e6 100644 --- a/src/octoprint/util/comm.py +++ b/src/octoprint/util/comm.py @@ -256,6 +256,9 @@ class MachineCom(object): self._resendSwallowRepetitionsCounter = 0 self._checksum_requiring_commands = settings().get(["serial", "checksumRequiringCommands"]) + self._disconnect_on_errors = settings().getBoolean(["serial", "disconnectOnErrors"]) + self._ignore_errors = settings().getBoolean(["serial", "ignoreErrorsFromFirmware"]) + self._clear_to_send = CountedEvent(max=10, name="comm.clear_to_send") self._send_queue = TypedQueue() self._temperature_timer = None @@ -704,7 +707,7 @@ class MachineCom(object): eventManager().fire(Events.FILE_DESELECTED) self._callback.on_comm_file_selected(None, None, False) - def cancelPrint(self): + def cancelPrint(self, firmware_error=None): if not self.isOperational() or self.isStreaming(): return @@ -722,7 +725,8 @@ class MachineCom(object): payload = { "file": self._currentFile.getFilename(), "filename": os.path.basename(self._currentFile.getFilename()), - "origin": self._currentFile.getFileLocation() + "origin": self._currentFile.getFileLocation(), + "firmwareError": firmware_error } self.sendGcodeScript("afterPrintCancelled", replacements=dict(event=payload)) @@ -1443,9 +1447,18 @@ class MachineCom(object): #Ignore unkown command errors, it could be a typo or some missing feature pass elif not self.isError(): - self._errorValue = line[6:] if line.startswith("Error:") else line[2:] - self._changeState(self.STATE_ERROR) - eventManager().fire(Events.ERROR, {"error": self.getErrorString()}) + error_text = line[6:] if line.startswith("Error:") else line[2:] + if not self._ignore_errors: + if self._disconnect_on_errors: + self._errorValue = error_text + self._changeState(self.STATE_ERROR) + eventManager().fire(Events.ERROR, {"error": self.getErrorString()}) + elif self.isPrinting(): + self.cancelPrint(firmware_error=error_text) + self._clear_to_send.set() + else: + self._log("WARNING! Received an error from the printer's firmware, ignoring that as configured but you might want to investigate what happened here! Error: {}".format(error_text)) + self._clear_to_send.set() return line def _readline(self):