From eb48a50139e308466e0440787fac68b03bb5074a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Mon, 15 Feb 2016 19:44:21 +0100 Subject: [PATCH 01/46] Fixed wrong tracking of current tool for M109 Tn heat-ups --- src/octoprint/util/comm.py | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/src/octoprint/util/comm.py b/src/octoprint/util/comm.py index cf950aa2..3fd85b73 100644 --- a/src/octoprint/util/comm.py +++ b/src/octoprint/util/comm.py @@ -221,6 +221,7 @@ class MachineCom(object): self._pauseWaitStartTime = None self._pauseWaitTimeLost = 0.0 self._currentTool = 0 + self._formerTool = None self._long_running_command = False self._heating = False @@ -994,10 +995,14 @@ class MachineCom(object): 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 'ok' in line and self._heatupWaitStartTime: - self._heatupWaitTimeLost = self._heatupWaitTimeLost + (time.time() - self._heatupWaitStartTime) - self._heatupWaitStartTime = None - self._heating = False + if 'ok' in line: + if self._formerTool is not None: + self._currentTool = self._formerTool + self._formerTool = None + if self._heatupWaitStartTime: + self._heatupWaitTimeLost = self._heatupWaitTimeLost + (time.time() - self._heatupWaitStartTime) + self._heatupWaitStartTime = None + self._heating = False ##~~ SD Card handling elif 'SD init fail' in line or 'volume.init failed' in line or 'openRoot failed' in line: @@ -1814,11 +1819,17 @@ class MachineCom(object): return None, # Don't send bed commands if we don't have a heated bed _gcode_M190_queuing = _gcode_M140_queuing - def _gcode_M104_sent(self, cmd, cmd_type=None): + def _gcode_M104_sent(self, cmd, cmd_type=None, wait=False): toolNum = self._currentTool toolMatch = regexes_parameters["intT"].search(cmd) + if toolMatch: toolNum = int(toolMatch.group("value")) + + if wait: + self._formerTool = self._currentTool + self._currentTool = toolNum + match = regexes_parameters["floatS"].search(cmd) if match: try: @@ -1831,7 +1842,7 @@ class MachineCom(object): except ValueError: pass - def _gcode_M140_sent(self, cmd, cmd_type=None): + def _gcode_M140_sent(self, cmd, cmd_type=None, wait=False): match = regexes_parameters["floatS"].search(cmd) if match: try: @@ -1848,13 +1859,13 @@ class MachineCom(object): self._heatupWaitStartTime = time.time() self._long_running_command = True self._heating = True - self._gcode_M104_sent(cmd, cmd_type) + self._gcode_M104_sent(cmd, cmd_type, wait=True) def _gcode_M190_sent(self, cmd, cmd_type=None): self._heatupWaitStartTime = time.time() self._long_running_command = True self._heating = True - self._gcode_M140_sent(cmd, cmd_type) + self._gcode_M140_sent(cmd, cmd_type, wait=True) def _gcode_M110_sending(self, cmd, cmd_type=None): newLineNumber = None From 632724a023a1163d62ef846648ae799d5108423f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Tue, 16 Feb 2016 08:42:05 +0100 Subject: [PATCH 02/46] Updated CONTRIBUTING.md * Don't add red herrings to tickets * Provide all information when actually experiencing the same symptoms as an existing ticket --- CONTRIBUTING.md | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 80f5773e..8c8080f0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -106,12 +106,26 @@ following section *completely*. Thank you! :) fixing it. Take the time to filter through possible duplicates and be really sure that your problem definitely is a new one. Try more than one search query (e.g. do not only search for "webcam" if you happen to run into an issue - with your webcam, also search for "timelapse" etc). + with your webcam, also search for "timelapse" etc). Do not only read the subject lines + of tickets that look like they might be related, but also read the ticket itself! + + **Very important:** Please make absolutely sure that if you find a bug that looks like + it is the same as your's, it actually behaves the same as your's. E.g. if someone gives steps + to reproduce his bug that looks like your's, reproduce the bug like that if possible, + and only add a "me too" if you actually can reproduce the same + issue. Also **provide all information** as [described below](#what-should-i-include-in-a-bug-report) + and whatever was additionally requested over the course of the ticket + even if you "only" add to an existing ticket. The more information available regarding a bug, the higher + the chances of reproducing and solving it. But "me too" on an actually unrelated ticket + makes it more difficult due to on top of having to figure out the original problem + there's now also a [red herring](https://en.wikipedia.org/wiki/Red_herring) interfering - so please be + very diligent here! ### What should I include in a bug report? Always use the following template (you can remove what's within `[...]`, that's -only provided here as some additional information for you): +only provided here as some additional information for you), **even if only adding a +"me too" to an existing ticket**: #### What were you doing? @@ -157,6 +171,8 @@ only provided here as some additional information for you): I have read the FAQ. +Copy-paste this template **completely**. Do not skip any lines! + ### Where can I find which version and branch I'm on? You can find out all of them by taking a look into the lower left corner of the @@ -324,6 +340,8 @@ the local version identifier to allow for an exact determination of the active c * 2015-12-01: Heavily reworked to include examples, better structure and all information in one document. * 2016-02-10: Added information about branch structure and versioning. + * 2016-02-16: Added requirement to add information from template to existing + tickets as well, explained issue with "me too" red herrings. ## Footnotes * [1] - If you are wondering why, the problem is that anything that you add From d267a752cd0f5483e9e6db3d387feb0948cb720d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Tue, 16 Feb 2016 09:13:22 +0100 Subject: [PATCH 03/46] Capture NoSuchStorage error on SD card fails We currently do not have a storage configured for SD card since that hasn't yet been ported to the storage interface. So on an SD card fail we need to capture that error when attempting to log the print recovery data, or there will be issues processing the cancel properly. A bit more of error handling is a good idea here in any case too. Fixes #1226 --- src/octoprint/printer/standard.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/octoprint/printer/standard.py b/src/octoprint/printer/standard.py index 1d50e519..ec847278 100644 --- a/src/octoprint/printer/standard.py +++ b/src/octoprint/printer/standard.py @@ -17,7 +17,7 @@ import time from octoprint import util as util from octoprint.events import eventManager, Events -from octoprint.filemanager import FileDestinations +from octoprint.filemanager import FileDestinations, NoSuchStorage from octoprint.plugin import plugin_manager, ProgressPlugin from octoprint.printer import PrinterInterface, PrinterCallback, UnknownScript from octoprint.printer.estimation import TimeEstimationHelper @@ -865,7 +865,12 @@ class Printer(PrinterInterface, comm.MachineComPrintCallback): self.disconnect() def on_comm_record_fileposition(self, origin, name, pos): - self._fileManager.save_recovery_data(origin, name, pos) + try: + self._fileManager.save_recovery_data(origin, name, pos) + except NoSuchStorage: + pass + except: + self._logger.exception("Error while trying to persist print recovery data") class StateMonitor(object): def __init__(self, interval=0.5, on_update=None, on_add_temperature=None, on_add_log=None, on_add_message=None): From 49f4a0c5b0407277bdbbccdf193fcd0e49b0b997 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Tue, 16 Feb 2016 09:21:07 +0100 Subject: [PATCH 04/46] line == "ok" => line.startswith("ok") Fixes an issue introduced with M109 fix that causes "SD card ok" not to be parsed correctly anymore. --- src/octoprint/util/comm.py | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/src/octoprint/util/comm.py b/src/octoprint/util/comm.py index 3fd85b73..8687693e 100644 --- a/src/octoprint/util/comm.py +++ b/src/octoprint/util/comm.py @@ -920,13 +920,20 @@ class MachineCom(object): else: continue + def convert_line(line): + if line is None: + return None, None + stripped_line = line.strip() + return stripped_line, stripped_line.lower() + ##~~ Error handling line = self._handleErrors(line) + line, lower_line = convert_line(line) ##~~ 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: - preprocessed_line = line.strip().lower() + preprocessed_line = lower_line fileinfo = preprocessed_line.rsplit(None, 1) if len(fileinfo) > 1: # we might have extended file information here, so let's split filename and size and try to make them a bit nicer @@ -995,7 +1002,7 @@ class MachineCom(object): 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 'ok' in line: + if line.startswith("ok"): if self._formerTool is not None: self._currentTool = self._formerTool self._formerTool = None @@ -1051,7 +1058,7 @@ class MachineCom(object): elif 'Writing to file' in line and self.isStreaming(): self._changeState(self.STATE_PRINTING) self._clear_to_send.set() - line = "ok" + line, lower_line = convert_line("ok") elif 'Done printing file' in line and self.isSdPrinting(): # printer is reporting file finished printing self._sdFilePos = 0 @@ -1076,8 +1083,9 @@ class MachineCom(object): self._clear_to_send.set() ##~~ Message handling - elif line.strip() != '' \ - and line.strip() != 'ok' and not line.startswith("wait") \ + elif line != '' \ + and not line.startswith("ok") \ + and not line.startswith("wait") \ and not line.startswith('Resend:') \ and line != 'echo:Unknown command:""\n' \ and self.isOperational(): @@ -1140,18 +1148,18 @@ class MachineCom(object): startSeen = True self._sendCommand("M110") self._clear_to_send.set() - elif "ok" in line: + elif line.startswith("ok"): self._onConnected() elif time.time() > self._timeout: self.close() ### Operational elif self._state == self.STATE_OPERATIONAL or self._state == self.STATE_PAUSED: - if "ok" in line: + if line.startswith("ok"): self._handle_ok() # resend -> start resend procedure from requested line - elif line.lower().startswith("resend") or line.lower().startswith("rs"): + elif lower_line.startswith("resend") or lower_line.startswith("rs"): self._handleResendRequest(line) ### Printing @@ -1164,12 +1172,12 @@ class MachineCom(object): else: self._logger.debug("Ran into a communication timeout, but a command known to be a long runner is currently active") - if "ok" in line or (supportWait and "wait" in line): + if line.startswith("ok") or (supportWait and line.startswith("wait")): # a wait while printing means our printer's buffer ran out, probably due to some ok getting # swallowed, so we treat it the same as an ok here to take up communication again self._handle_ok() - elif line.lower().startswith("resend") or line.lower().startswith("rs"): + elif lower_line.startswith("resend") or lower_line.startswith("rs"): self._handleResendRequest(line) except: self._logger.exception("Something crashed inside the serial connection loop, please report this in OctoPrint's bug tracker:") From 1ea0cf9213a02460f3d80c132aaeb037dc6ed006 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Tue, 16 Feb 2016 09:52:56 +0100 Subject: [PATCH 05/46] Always reset the job data on disconnect If former state was "Printing", so far the full disconnect state handling wasn't done properly. --- src/octoprint/printer/standard.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/octoprint/printer/standard.py b/src/octoprint/printer/standard.py index ec847278..6912e1f4 100644 --- a/src/octoprint/printer/standard.py +++ b/src/octoprint/printer/standard.py @@ -784,7 +784,8 @@ class Printer(PrinterInterface, comm.MachineComPrintCallback): self._analysisQueue.resume() # printing done, put those cpu cycles to good use elif state == comm.MachineCom.STATE_PRINTING: self._analysisQueue.pause() # do not analyse files while printing - elif state == comm.MachineCom.STATE_CLOSED or state == comm.MachineCom.STATE_CLOSED_WITH_ERROR: + + if state == comm.MachineCom.STATE_CLOSED or state == comm.MachineCom.STATE_CLOSED_WITH_ERROR: if self._comm is not None: self._comm = None From beb5850361fb64b8192ca9825711d9b478a077b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Tue, 16 Feb 2016 10:03:02 +0100 Subject: [PATCH 06/46] Only try to record recovery data if a print was ongoing --- src/octoprint/util/comm.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/octoprint/util/comm.py b/src/octoprint/util/comm.py index 8687693e..9fddd6b5 100644 --- a/src/octoprint/util/comm.py +++ b/src/octoprint/util/comm.py @@ -313,7 +313,8 @@ class MachineCom(object): self._callback.on_comm_sd_files([]) if self._currentFile is not None: - self._recordFilePosition() + if self.isBusy(): + self._recordFilePosition() self._currentFile.close() oldState = self.getStateString() From 21034b381cd65192a2f32b505138f4c27c9fafdf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Tue, 16 Feb 2016 10:04:06 +0100 Subject: [PATCH 07/46] Cancelling a print that is not ongoing doesn't make sense --- src/octoprint/util/comm.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/octoprint/util/comm.py b/src/octoprint/util/comm.py index 9fddd6b5..6bea89a9 100644 --- a/src/octoprint/util/comm.py +++ b/src/octoprint/util/comm.py @@ -665,6 +665,10 @@ class MachineCom(object): if not self.isOperational() or self.isStreaming(): return + if not self.isBusy() or self._currentFile is None: + # we aren't even printing, nothing to cancel... + return + self._changeState(self.STATE_OPERATIONAL) if self.isSdFileSelected(): From 5c06bd50369ab4660a156c271b4b73354dac9efb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Tue, 16 Feb 2016 15:09:22 +0100 Subject: [PATCH 08/46] Kill background tasks of virtual printer on disconnect Otherwise there might be errors with wait-for-heatup or sd printing situations where self.outgoing is gone suddenly. --- .../plugins/virtual_printer/virtual.py | 77 +++++++++++-------- 1 file changed, 44 insertions(+), 33 deletions(-) diff --git a/src/octoprint/plugins/virtual_printer/virtual.py b/src/octoprint/plugins/virtual_printer/virtual.py index c0cb74fe..71fb3c1e 100644 --- a/src/octoprint/plugins/virtual_printer/virtual.py +++ b/src/octoprint/plugins/virtual_printer/virtual.py @@ -591,49 +591,59 @@ class VirtualPrinter(): def _sdPrintingWorker(self): self._selectedSdFilePos = 0 - with open(self._selectedSdFile, "r") as f: - for line in iter(f.readline, ""): - if self._killed: - break + try: + with open(self._selectedSdFile, "r") as f: + for line in iter(f.readline, ""): + if self._killed: + break - # reset position if requested by client - if self._newSdFilePos is not None: - f.seek(self._newSdFilePos) - self._newSdFilePos = None + # reset position if requested by client + if self._newSdFilePos is not None: + f.seek(self._newSdFilePos) + self._newSdFilePos = None - # read current file position - self._selectedSdFilePos = f.tell() + # read current file position + self._selectedSdFilePos = f.tell() - # if we are paused, wait for unpausing - self._sdPrintingSemaphore.wait() + # if we are paused, wait for unpausing + self._sdPrintingSemaphore.wait() - # set target temps - if 'M104' in line or 'M109' in line: - self._parseHotendCommand(line) - if 'M140' in line or 'M190' in line: - self._parseBedCommand(line) + # set target temps + if 'M104' in line or 'M109' in line: + self._parseHotendCommand(line) + if 'M140' in line or 'M190' in line: + self._parseBedCommand(line) - time.sleep(settings().getFloat(["devel", "virtualPrinter", "throttle"])) + time.sleep(settings().getFloat(["devel", "virtualPrinter", "throttle"])) + except AttributeError: + if self.outgoing is not None: + raise - self._sdPrintingSemaphore.clear() - self._selectedSdFilePos = 0 - self._sdPrinter = None - self.outgoing.put("Done printing file") + if not self._killed: + self._sdPrintingSemaphore.clear() + self._selectedSdFilePos = 0 + self._sdPrinter = None + self.outgoing.put("Done printing file") def _waitForHeatup(self, heater): delta = 1 delay = 1 - if heater.startswith("tool"): - toolNum = int(heater[len("tool"):]) - while not self._killed and (self.temp[toolNum] < self.targetTemp[toolNum] - delta or self.temp[toolNum] > self.targetTemp[toolNum] + delta): - self._simulateTemps(delta=delta) - self.outgoing.put("T:%0.2f" % self.temp[toolNum]) - time.sleep(delay) - elif heater == "bed": - while not self._killed and (self.bedTemp < self.bedTargetTemp - delta or self.bedTemp > self.bedTargetTemp + delta): - self._simulateTemps(delta=delta) - self.outgoing.put("B:%0.2f" % self.bedTemp) - time.sleep(delay) + + try: + if heater.startswith("tool"): + toolNum = int(heater[len("tool"):]) + while not self._killed and (self.temp[toolNum] < self.targetTemp[toolNum] - delta or self.temp[toolNum] > self.targetTemp[toolNum] + delta): + self._simulateTemps(delta=delta) + self.outgoing.put("T:%0.2f" % self.temp[toolNum]) + time.sleep(delay) + elif heater == "bed": + while not self._killed and (self.bedTemp < self.bedTargetTemp - delta or self.bedTemp > self.bedTargetTemp + delta): + self._simulateTemps(delta=delta) + self.outgoing.put("B:%0.2f" % self.bedTemp) + time.sleep(delay) + except AttributeError: + if self.outgoing is not None: + raise def _deleteSdFile(self, filename): if filename.startswith("/"): @@ -704,6 +714,7 @@ class VirtualPrinter(): return "" def close(self): + self._killed = True self.incoming = None self.outgoing = None self.buffered = None From c82ec3d3afced3d6fd6e93b65d53edffe60e8552 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Wed, 17 Feb 2016 11:33:49 +0100 Subject: [PATCH 09/46] Improved connecting phase M110 is now sent every second until either a connection is established or the connection attempt times out. That should prevent connection handshakes from failing due to some initial garbage on the line that swallows an ok. --- src/octoprint/util/comm.py | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/src/octoprint/util/comm.py b/src/octoprint/util/comm.py index 6bea89a9..2cea2d2a 100644 --- a/src/octoprint/util/comm.py +++ b/src/octoprint/util/comm.py @@ -887,10 +887,25 @@ class MachineCom(object): connection_timeout = settings().getFloat(["serial", "timeout", "connection"]) detection_timeout = settings().getFloat(["serial", "timeout", "detection"]) - # enqueue an M105 first thing + # M110 polling during connection phase if try_hello: - self._sendCommand("M110") - self._clear_to_send.set() + # we do this repeatedly since depending on firmware/bootloader/cable it might happen + # that stuff gets lost during connection initialization, and if that stuff happens + # to be an "ok", we'll get stuck + # + # so let's make things a bit more robust here and just have it send an M110 every second + # while in "connecting" state so that we can retrigger an M110 processing if the "ok" + # for the first one gets lost + + def poll_connection(): + self._logger.debug("Performing M110 poll") + self._sendCommand("M110") + self._clear_to_send.set() + connection_poller = RepeatedTimer(1, poll_connection, + run_first=True, + condition=lambda: self._state == self.STATE_CONNECTING, + on_finish=lambda: self._logger.debug("Stopping M110 poll, no longer connecting")) + connection_poller.start() while self._monitoring_active: try: @@ -1116,6 +1131,10 @@ class MachineCom(object): ### Baudrate detection if self._state == self.STATE_DETECT_BAUDRATE: + # TODO this needs to be rewritten to make use of a repeated timer and to move it outside of the monitor thread + # makes way more sense to have it as a separate function that is part of the connection attempt, not of + # the protocol implementation + # that whole _monitor loop is soooo ugly... if line == '' or time.time() > self._timeout: if self._baudrateDetectRetry > 0: self._serial.timeout = detection_timeout @@ -1151,8 +1170,6 @@ class MachineCom(object): elif self._state == self.STATE_CONNECTING: if "start" in line and not startSeen: startSeen = True - self._sendCommand("M110") - self._clear_to_send.set() elif line.startswith("ok"): self._onConnected() elif time.time() > self._timeout: From 35e20162ec8dc38cb69a4ae929745ab654e10186 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Mon, 22 Feb 2016 13:33:34 +0100 Subject: [PATCH 10/46] Revert "Improved connecting phase" Improved connecting for some printers, destroyed it completely for others, so this needs some more work/ thought before it's ready for prime time. This reverts commit c82ec3d3afced3d6fd6e93b65d53edffe60e8552. --- src/octoprint/util/comm.py | 27 +++++---------------------- 1 file changed, 5 insertions(+), 22 deletions(-) diff --git a/src/octoprint/util/comm.py b/src/octoprint/util/comm.py index 2cea2d2a..6bea89a9 100644 --- a/src/octoprint/util/comm.py +++ b/src/octoprint/util/comm.py @@ -887,25 +887,10 @@ class MachineCom(object): connection_timeout = settings().getFloat(["serial", "timeout", "connection"]) detection_timeout = settings().getFloat(["serial", "timeout", "detection"]) - # M110 polling during connection phase + # enqueue an M105 first thing if try_hello: - # we do this repeatedly since depending on firmware/bootloader/cable it might happen - # that stuff gets lost during connection initialization, and if that stuff happens - # to be an "ok", we'll get stuck - # - # so let's make things a bit more robust here and just have it send an M110 every second - # while in "connecting" state so that we can retrigger an M110 processing if the "ok" - # for the first one gets lost - - def poll_connection(): - self._logger.debug("Performing M110 poll") - self._sendCommand("M110") - self._clear_to_send.set() - connection_poller = RepeatedTimer(1, poll_connection, - run_first=True, - condition=lambda: self._state == self.STATE_CONNECTING, - on_finish=lambda: self._logger.debug("Stopping M110 poll, no longer connecting")) - connection_poller.start() + self._sendCommand("M110") + self._clear_to_send.set() while self._monitoring_active: try: @@ -1131,10 +1116,6 @@ class MachineCom(object): ### Baudrate detection if self._state == self.STATE_DETECT_BAUDRATE: - # TODO this needs to be rewritten to make use of a repeated timer and to move it outside of the monitor thread - # makes way more sense to have it as a separate function that is part of the connection attempt, not of - # the protocol implementation - # that whole _monitor loop is soooo ugly... if line == '' or time.time() > self._timeout: if self._baudrateDetectRetry > 0: self._serial.timeout = detection_timeout @@ -1170,6 +1151,8 @@ class MachineCom(object): elif self._state == self.STATE_CONNECTING: if "start" in line and not startSeen: startSeen = True + self._sendCommand("M110") + self._clear_to_send.set() elif line.startswith("ok"): self._onConnected() elif time.time() > self._timeout: From 48d74ef2fd1dfad437a573ba9e76bfa2d3abd621 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Thu, 13 Aug 2015 13:07:18 +0200 Subject: [PATCH 11/46] Improved handshake procedure on comm layer "Hello" command sent to printer to trigger initial handshake can now be configured. Commands that _always_ necessitate to be sent with checksum/ line number (e.g. M110 on Marlin) can be configured as such too. Also fixed an issue causing the "Hello" command to not be actually enqueued first thing on opening a connection. Seems to not have caused harm in the wild, but was unintentional. (cherry picked from commit 5c2ae37) --- docs/configuration/config_yaml.rst | 21 +++++++++++++ src/octoprint/settings.py | 1 + src/octoprint/util/comm.py | 47 +++++++++++++++++++----------- 3 files changed, 52 insertions(+), 17 deletions(-) diff --git a/docs/configuration/config_yaml.rst b/docs/configuration/config_yaml.rst index e182e6bb..2b75ba93 100644 --- a/docs/configuration/config_yaml.rst +++ b/docs/configuration/config_yaml.rst @@ -611,6 +611,27 @@ Use the following settings to configure the serial connection to the printer: additionalPorts: - /dev/myPrinterSymlink + # Commands which are known to take a long time to be acknowledged by the firmware. E.g. + # homing, dwelling, auto leveling etc. Defaults to the below commands. + longRunningCommands: + - G4 + - G28 + - G29 + - G30 + - G32 + - M400 + - M226 + + # Commands which need to always be send with a checksum. Defaults to only M110 + checksumRequiringCommands: + - M110 + + # Command to send in order to initiate a handshake with the printer. + # Defaults to "M110 N0" which simply resets the line numbers in the firmware and which + # should be acknowledged with a simple "ok". + helloCommand: + - M110 N0 + .. _sec-configuration-config_yaml-server: Server diff --git a/src/octoprint/settings.py b/src/octoprint/settings.py index 9454e483..38a197f0 100644 --- a/src/octoprint/settings.py +++ b/src/octoprint/settings.py @@ -86,6 +86,7 @@ default_settings = { }, "additionalPorts": [], "longRunningCommands": ["G4", "G28", "G29", "G30", "G32"], + "checksumRequiringCommands": ["M110"], "disconnectOnErrors": True, "ignoreErrorsFromFirmware": False }, diff --git a/src/octoprint/util/comm.py b/src/octoprint/util/comm.py index 6bea89a9..4cb8894d 100644 --- a/src/octoprint/util/comm.py +++ b/src/octoprint/util/comm.py @@ -229,6 +229,8 @@ class MachineCom(object): self._timeout = None + self._hello_command = settings().get(["serial", "helloCommand"]) + self._alwaysSendChecksum = settings().getBoolean(["feature", "alwaysSendChecksum"]) self._sendChecksumWithUnknownCommands = settings().getBoolean(["feature", "sendChecksumWithUnknownCommands"]) self._unknownCommandsNeedAck = settings().getBoolean(["feature", "unknownCommandsNeedAck"]) @@ -246,6 +248,9 @@ class MachineCom(object): self._disconnect_on_errors = settings().getBoolean(["serial", "disconnectOnErrors"]) self._ignore_errors = settings().getBoolean(["serial", "ignoreErrorsFromFirmware"]) + self._long_running_commands = settings().get(["serial", "longRunningCommands"]) + self._checksum_requiring_commands = settings().get(["serial", "checksumRequiringCommands"]) + self._clear_to_send = CountedEvent(max=10, name="comm.clear_to_send") self._send_queue = TypedQueue() self._temperature_timer = None @@ -277,10 +282,6 @@ class MachineCom(object): # print job self._currentFile = None - # regexes - - self._long_running_commands = settings().get(["serial", "longRunningCommands"]) - # multithreading locks self._sendNextLock = threading.Lock() self._sendingLock = threading.RLock() @@ -498,7 +499,7 @@ class MachineCom(object): def fakeOk(self): self._clear_to_send.set() - def sendCommand(self, cmd, cmd_type=None, processed=False): + def sendCommand(self, cmd, cmd_type=None, processed=False, force=False): cmd = to_unicode(cmd, errors="replace") if not processed: cmd = process_gcode_line(cmd) @@ -507,7 +508,7 @@ class MachineCom(object): if self.isPrinting() and not self.isSdFileSelected(): self._commandQueue.put((cmd, cmd_type)) - elif self.isOperational(): + elif self.isOperational() or force: self._sendCommand(cmd, cmd_type=cmd_type) def sendGcodeScript(self, scriptName, replacements=None): @@ -579,7 +580,7 @@ class MachineCom(object): self._changeState(self.STATE_PRINTING) - self.sendCommand("M110 N0") + self.resetLineNumbers() payload = { "file": self._currentFile.getFilename(), @@ -809,6 +810,16 @@ class MachineCom(object): self._callback.on_comm_sd_state_change(self._sdAvailable) self._callback.on_comm_sd_files(self._sdFiles) + def sayHello(self): + self.sendCommand(self._hello_command, force=True) + self._clear_to_send.set() + + def resetLineNumbers(self, number=0): + if not self.isOperational(): + return + + self.sendCommand("M110 N%d" % number) + ##~~ record aborted file positions def _recordFilePosition(self): @@ -887,10 +898,9 @@ class MachineCom(object): connection_timeout = settings().getFloat(["serial", "timeout", "connection"]) detection_timeout = settings().getFloat(["serial", "timeout", "detection"]) - # enqueue an M105 first thing + # enqueue the "hello command" first thing if try_hello: - self._sendCommand("M110") - self._clear_to_send.set() + self.sayHello() while self._monitoring_active: try: @@ -1122,8 +1132,7 @@ class MachineCom(object): self._baudrateDetectRetry -= 1 self._serial.write('\n') self._log("Baudrate test retry: %d" % (self._baudrateDetectRetry)) - self._sendCommand("M110") - self._clear_to_send.set() + self.sayHello() elif len(self._baudrateDetectList) > 0: baudrate = self._baudrateDetectList.pop(0) try: @@ -1134,8 +1143,7 @@ class MachineCom(object): self._baudrateDetectRetry = 5 self._timeout = get_new_timeout("communication") self._serial.write('\n') - self._sendCommand("M110") - self._clear_to_send.set() + self.sayHello() except: self._log("Unexpected error while setting baudrate: %d %s" % (baudrate, get_exception_string())) else: @@ -1151,8 +1159,7 @@ class MachineCom(object): elif self._state == self.STATE_CONNECTING: if "start" in line and not startSeen: startSeen = True - self._sendCommand("M110") - self._clear_to_send.set() + self.sayHello() elif line.startswith("ok"): self._onConnected() elif time.time() > self._timeout: @@ -1276,6 +1283,8 @@ class MachineCom(object): self._changeState(self.STATE_OPERATIONAL) + self.resetLineNumbers() + if self._sdAvailable: self.refreshSdFiles() else: @@ -1653,8 +1662,12 @@ class MachineCom(object): continue # now comes the part where we increase line numbers and send stuff - no turning back now + command_requiring_checksum = gcode is not None and gcode in self._checksum_requiring_commands + command_allowing_checksum = gcode is not None or self._sendChecksumWithUnknownCommands + checksum_enabled = self.isPrinting() or self._alwaysSendChecksum + command_to_send = command.encode("ascii", errors="replace") - if (gcode is not None or self._sendChecksumWithUnknownCommands) and (self.isPrinting() or self._alwaysSendChecksum): + if command_requiring_checksum or (command_allowing_checksum and checksum_enabled): self._doIncrementAndSendWithChecksum(command_to_send) else: self._doSendWithoutChecksum(command_to_send) From b36290a1f218d240d30b0067dabe0bddcf78ffd3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Thu, 13 Aug 2015 13:08:20 +0200 Subject: [PATCH 12/46] Added new serial config options to UI Also pushed more advanced config option into an initially hidden "Advanced Options" section, similar to other places. (cherry picked from commit 84b343a) --- src/octoprint/server/api/settings.py | 4 +++ .../static/js/app/viewmodels/settings.js | 6 ++++ .../dialogs/settings/serialconnection.jinja2 | 35 +++++++++++++++---- 3 files changed, 38 insertions(+), 7 deletions(-) diff --git a/src/octoprint/server/api/settings.py b/src/octoprint/server/api/settings.py index e108452c..49d92e98 100644 --- a/src/octoprint/server/api/settings.py +++ b/src/octoprint/server/api/settings.py @@ -86,6 +86,8 @@ def getSettings(): "log": s.getBoolean(["serial", "log"]), "additionalPorts": s.get(["serial", "additionalPorts"]), "longRunningCommands": s.get(["serial", "longRunningCommands"]), + "checksumRequiringCommands": s.get(["serial", "checksumRequiringCommands"]), + "helloCommand": s.get(["serial", "helloCommand"]), "ignoreErrorsFromFirmware": s.getBoolean(["serial", "ignoreErrorsFromFirmware"]), "disconnectOnErrors": s.getBoolean(["serial", "disconnectOnErrors"]), }, @@ -228,6 +230,8 @@ def setSettings(): if "timeoutSdStatus" in data["serial"].keys(): s.setFloat(["serial", "timeout", "sdStatus"], data["serial"]["timeoutSdStatus"]) if "additionalPorts" in data["serial"] and isinstance(data["serial"]["additionalPorts"], (list, tuple)): s.set(["serial", "additionalPorts"], data["serial"]["additionalPorts"]) 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"]) diff --git a/src/octoprint/static/js/app/viewmodels/settings.js b/src/octoprint/static/js/app/viewmodels/settings.js index 0ed23431..4e7b79dc 100644 --- a/src/octoprint/static/js/app/viewmodels/settings.js +++ b/src/octoprint/static/js/app/viewmodels/settings.js @@ -136,6 +136,8 @@ $(function() { self.serial_log = ko.observable(undefined); self.serial_additionalPorts = ko.observable(undefined); 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); @@ -452,6 +454,8 @@ $(function() { self.serial_log(response.serial.log); self.serial_additionalPorts(response.serial.additionalPorts.join("\n")); self.serial_longRunningCommands(response.serial.longRunningCommands.join(", ")); + self.serial_checksumRequiringCommands(response.serial.checksumRequiringCommands.join(", ")); + self.serial_helloCommand(response.serial.helloCommand); self.serial_ignoreErrorsFromFirmware(response.serial.ignoreErrorsFromFirmware); self.serial_disconnectOnErrors(response.serial.disconnectOnErrors); @@ -541,6 +545,8 @@ $(function() { "log": self.serial_log(), "additionalPorts": commentableLinesToArray(self.serial_additionalPorts()), "longRunningCommands": splitTextToArray(self.serial_longRunningCommands(), ",", true), + "checksumRequiringCommands": splitTextToArray(self.serial_checksumRequiringCommands(), ",", true), + "helloCommand": self.serial_helloCommand(), "ignoreErrorsFromFirmware": self.serial_ignoreErrorsFromFirmware(), "disconnectOnErrors": self.serial_disconnectOnErrors() }, diff --git a/src/octoprint/templates/dialogs/settings/serialconnection.jinja2 b/src/octoprint/templates/dialogs/settings/serialconnection.jinja2 index afa14445..e871184f 100644 --- a/src/octoprint/templates/dialogs/settings/serialconnection.jinja2 +++ b/src/octoprint/templates/dialogs/settings/serialconnection.jinja2 @@ -70,13 +70,6 @@ -
- -
- - {{ _('Use this to specify the commands known to take a long time to complete without output from your printer and hence might cause timeout issues. Just the G or M code, comma separated.')|format(glob_url="http://docs.python.org/2/library/glob.html") }} -
-
@@ -98,4 +91,32 @@
+ +
+ +
+
+ +
+ + {{ _('Use this to specify a different command than the default M110 to send to the printer on initial connection to trigger a communication handshake.') }} +
+
+
+ +
+ + {{ _('Use this to specify the commands known to take a long time to complete without output from your printer and hence might cause timeout issues. Just the G or M code, comma separated.') }} +
+
+
+ +
+ + {{ _('Use this to specify which commands always need to be sent with a checksum. Comma separated list.') }} +
+
+
+
+ From 2a3d9c291d4d0c02c0f98fa7f56cf256da7a4122 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Wed, 9 Mar 2016 11:54:05 +0100 Subject: [PATCH 13/46] Fixed a merge error --- src/octoprint/settings.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/octoprint/settings.py b/src/octoprint/settings.py index 38a197f0..afe81c2e 100644 --- a/src/octoprint/settings.py +++ b/src/octoprint/settings.py @@ -85,8 +85,9 @@ default_settings = { "sdStatus": 1 }, "additionalPorts": [], - "longRunningCommands": ["G4", "G28", "G29", "G30", "G32"], + "longRunningCommands": ["G4", "G28", "G29", "G30", "G32", "M400", "M226"], "checksumRequiringCommands": ["M110"], + "helloCommand": "M110 N0", "disconnectOnErrors": True, "ignoreErrorsFromFirmware": False }, From 07d8d4b88d58fbfd6829786fb1904a58b388d775 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Wed, 9 Mar 2016 12:09:44 +0100 Subject: [PATCH 14/46] Made virtual printer a bit faster --- .../plugins/virtual_printer/virtual.py | 30 +++++++++++++++---- 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/src/octoprint/plugins/virtual_printer/virtual.py b/src/octoprint/plugins/virtual_printer/virtual.py index 71fb3c1e..7815288a 100644 --- a/src/octoprint/plugins/virtual_printer/virtual.py +++ b/src/octoprint/plugins/virtual_printer/virtual.py @@ -61,6 +61,7 @@ class VirtualPrinter(): self._selectedSdFileSize = None self._selectedSdFilePos = None self._writingToSd = False + self._writingToSdHandle = None self._newSdFilePos = None self._heatupThread = None @@ -164,9 +165,8 @@ class VirtualPrinter(): 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) + if self._writingToSdHandle is not None and not "M29" in data: + self._writingToSdHandle.write(data) self._sendOk() continue @@ -580,11 +580,28 @@ class VirtualPrinter(): else: self.outgoing.put("error writing to file") + handle = None + try: + handle = open(file, "w") + except: + self.outgoing.put("error writing to file") + if handle is not None: + try: + handle.close() + except: + pass + self._writingToSdHandle = handle self._writingToSd = True self._selectedSdFile = file self.outgoing.put("Writing to file: %s" % filename) def _finishSdFile(self): + try: + self._writingToSdHandle.close() + except: + pass + finally: + self._writingToSdHandle = None self._writingToSd = False self._selectedSdFile = None self.outgoing.put("Done saving file") @@ -611,10 +628,12 @@ class VirtualPrinter(): # set target temps if 'M104' in line or 'M109' in line: self._parseHotendCommand(line) - if 'M140' in line or 'M190' in line: + elif 'M140' in line or 'M190' in line: self._parseBedCommand(line) + elif line.startswith("G0") or line.startswith("G1") or line.startswith("G2") or line.startswith("G3"): + # simulate reprap buffered commands via a Queue with maxsize which internally simulates the moves + self.buffered.put(line) - time.sleep(settings().getFloat(["devel", "virtualPrinter", "throttle"])) except AttributeError: if self.outgoing is not None: raise @@ -708,7 +727,6 @@ class VirtualPrinter(): try: line = self.outgoing.get(timeout=self._read_timeout) - time.sleep(settings().getFloat(["devel", "virtualPrinter", "throttle"])) return line except Queue.Empty: return "" From 1616fb4de82e0ee4795daaf1665ad3f727b703f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Wed, 9 Mar 2016 15:22:19 +0100 Subject: [PATCH 15/46] Better error reporting in comm layer Option to log last terminal lines before a resend (defaults to off for now), more exception logging in octoprint.log in various error cases, also log unsolvable resend requests with requested and current line number. --- src/octoprint/settings.py | 3 +- src/octoprint/util/comm.py | 59 ++++++++++++++++++++++++++++++++------ 2 files changed, 52 insertions(+), 10 deletions(-) diff --git a/src/octoprint/settings.py b/src/octoprint/settings.py index afe81c2e..72ef69ec 100644 --- a/src/octoprint/settings.py +++ b/src/octoprint/settings.py @@ -89,7 +89,8 @@ default_settings = { "checksumRequiringCommands": ["M110"], "helloCommand": "M110 N0", "disconnectOnErrors": True, - "ignoreErrorsFromFirmware": False + "ignoreErrorsFromFirmware": False, + "logResends": False }, "server": { "host": "0.0.0.0", diff --git a/src/octoprint/util/comm.py b/src/octoprint/util/comm.py index 4cb8894d..b7d0dbfe 100644 --- a/src/octoprint/util/comm.py +++ b/src/octoprint/util/comm.py @@ -245,9 +245,19 @@ class MachineCom(object): self._resendSwallowRepetitions = settings().getBoolean(["feature", "ignoreIdenticalResends"]) self._resendSwallowRepetitionsCounter = 0 + self._terminal_log = deque([], 20) + self._disconnect_on_errors = settings().getBoolean(["serial", "disconnectOnErrors"]) self._ignore_errors = settings().getBoolean(["serial", "ignoreErrorsFromFirmware"]) + self._log_resends = settings().getBoolean(["serial", "logResends"]) + + # don't log more resends than 5 / 10s + self._log_resends_rate_start = None + self._log_resends_rate_count = 0 + self._log_resends_max = 5 + self._log_resends_rate_frame = 10 + self._long_running_commands = settings().get(["serial", "longRunningCommands"]) self._checksum_requiring_commands = settings().get(["serial", "checksumRequiringCommands"]) @@ -324,9 +334,16 @@ class MachineCom(object): self._callback.on_comm_state_change(newState) def _log(self, message): + self._terminal_log.append(message) self._callback.on_comm_log(message) self._serialLogger.debug(message) + def _to_logfile_with_terminal(self, message=None, level=logging.INFO): + log = "Last lines in terminal:\n" + "\n".join(map(lambda x: "| " + x, self._terminal_log)) + if message is not None: + log = message + "\n| " + log + self._logger.log(level, log) + def _addToLastLines(self, cmd): self._lastLines.append(cmd) @@ -1145,7 +1162,8 @@ class MachineCom(object): self._serial.write('\n') self.sayHello() except: - self._log("Unexpected error while setting baudrate: %d %s" % (baudrate, get_exception_string())) + self._log("Unexpected error while setting baudrate {}: {}".format(baudrate, get_exception_string())) + self._logger.exception("Unexpceted error while setting baudrate {}".format(baudrate)) else: self.close() self._errorValue = "No more baudrates to test, and no suitable baudrate found." @@ -1330,10 +1348,13 @@ class MachineCom(object): programmer.connect(p) serial_obj = programmer.leaveISP() except ispBase.IspError as (e): - self._log("Error while connecting to %s: %s" % (p, str(e))) + error_message = "Error while connecting to %s: %s" % (p, str(e)) + self._log(error_message) + self._logger.exception(error_message) except: - self._log("Unexpected error while connecting to serial port: %s %s" % (p, get_exception_string())) - + error_message = "Unexpected error while connecting to serial port: %s %s" % (p, get_exception_string()) + self._log(error_message) + self._logger.exception(error_message) if serial_obj is not None: if (close): serial_obj.close() @@ -1380,7 +1401,9 @@ class MachineCom(object): self._changeState(self.STATE_ERROR) eventManager().fire(Events.ERROR, {"error": self.getErrorString()}) - self._log("Unexpected error while connecting to serial port: %s %s (hook %s)" % (self._port, exception_string, name)) + error_message = "Unexpected error while connecting to serial port: %s %s (hook %s)" % (self._port, exception_string, name) + self._log(error_message) + self._logger.exception(error_message) if "failed to set custom baud rate" in exception_string.lower(): self._log("Your installation does not support custom baudrates (e.g. 250000) for connecting to your printer. This is a problem of the pyserial library that OctoPrint depends on. Please update to a pyserial version that supports your baudrate or switch your printer's firmware to a standard baudrate (e.g. 115200). See https://github.com/foosel/OctoPrint/wiki/OctoPrint-support-for-250000-baud-rate-on-Raspbian") @@ -1403,7 +1426,7 @@ class MachineCom(object): lower_line = line.lower() # No matter the state, if we see an error, goto the error state and store the error for reference. - if line.startswith('Error:') or line.startswith('!!'): + if lower_line.startswith('error:') or line.startswith('!!'): #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 !!" @@ -1413,7 +1436,7 @@ class MachineCom(object): if 'line number' in lower_line or 'checksum' in lower_line or 'format error' in lower_line or 'expected line' in lower_line: #Skip the communication errors, as those get corrected. - self._lastCommError = line[6:] if line.startswith("Error:") else line[2:] + self._lastCommError = line[6:] if lower_line.startswith("error:") else line[2:] pass elif 'volume.init' in lower_line or "openroot" in lower_line or 'workdir' in lower_line\ or "error writing to file" in lower_line or "cannot open" in lower_line\ @@ -1424,7 +1447,8 @@ class MachineCom(object): #Ignore unkown command errors, it could be a typo or some missing feature pass elif not self.isError(): - error_text = line[6:] if line.startswith("Error:") else line[2:] + error_text = line[6:] if lower_line.startswith("error:") else line[2:] + self._to_logfile_with_terminal("Received an error from the printer's firmware: {}".format(error_text), level=logging.WARN) if not self._ignore_errors: if self._disconnect_on_errors: self._errorValue = error_text @@ -1544,7 +1568,8 @@ class MachineCom(object): 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) + 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) @@ -1555,6 +1580,22 @@ class MachineCom(object): else: self._resendNextCommand() + # 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 + def _resendNextCommand(self): self._lastCommError = None From 27d1a6ca37749ba4f30e56de4410dabdeff5cccd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Wed, 9 Mar 2016 15:33:17 +0100 Subject: [PATCH 16/46] Virtual printer: request correct line in dummy resend --- src/octoprint/plugins/virtual_printer/virtual.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/octoprint/plugins/virtual_printer/virtual.py b/src/octoprint/plugins/virtual_printer/virtual.py index 7815288a..45f89062 100644 --- a/src/octoprint/plugins/virtual_printer/virtual.py +++ b/src/octoprint/plugins/virtual_printer/virtual.py @@ -154,7 +154,7 @@ class VirtualPrinter(): if linenumber != expected: self._triggerResend(actual=linenumber) continue - elif self.currentLine == 101: + elif self.currentLine == 100: # simulate a resend at line 100 self._triggerResend(expected=100) continue From 13098dde00de930746dda87bbab147942b4fd4cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Wed, 9 Mar 2016 15:37:33 +0100 Subject: [PATCH 17/46] Removed unused "{start|end}SdFileTransfer" --- src/octoprint/util/comm.py | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/src/octoprint/util/comm.py b/src/octoprint/util/comm.py index b7d0dbfe..4292d2cf 100644 --- a/src/octoprint/util/comm.py +++ b/src/octoprint/util/comm.py @@ -758,25 +758,6 @@ class MachineCom(object): def getSdFiles(self): return self._sdFiles - def startSdFileTransfer(self, filename): - if not self._sdEnabled: - return - - if not self.isOperational() or self.isBusy(): - return - self._changeState(self.STATE_TRANSFERING_FILE) - self.sendCommand("M28 %s" % filename.lower()) - - def endSdFileTransfer(self, filename): - if not self._sdEnabled: - return - - if not self.isOperational() or self.isBusy(): - return - self.sendCommand("M29 %s" % filename.lower()) - self._changeState(self.STATE_OPERATIONAL) - self.refreshSdFiles() - def deleteSdFile(self, filename): if not self._sdEnabled: return From 1a308a1bc7094aaf4fe8c0b8f780d9eec0b9f335 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Wed, 9 Mar 2016 15:44:23 +0100 Subject: [PATCH 18/46] Reset line numbers for sd streaming as well --- src/octoprint/util/comm.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/octoprint/util/comm.py b/src/octoprint/util/comm.py index 4292d2cf..a96534f0 100644 --- a/src/octoprint/util/comm.py +++ b/src/octoprint/util/comm.py @@ -648,6 +648,7 @@ class MachineCom(object): self._currentFile = StreamingGcodeFileInformation(filename, localFilename, remoteFilename) self._currentFile.start() + self.resetLineNumbers() self.sendCommand("M28 %s" % remoteFilename) eventManager().fire(Events.TRANSFER_STARTED, {"local": localFilename, "remote": remoteFilename}) self._callback.on_comm_file_transfer_started(remoteFilename, self._currentFile.getFilesize()) From 23d2cdec760b98708f2307e42a956b80b8dc4a3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Thu, 10 Mar 2016 11:46:34 +0100 Subject: [PATCH 19/46] Use 0 as default bed temperature in slicer Should prevent slicing result to contain bed temperature settings even if printer has a heated bed configured unless slicing profile also contains a temperature. Fixes #1268 --- src/octoprint/plugins/cura/profile.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/octoprint/plugins/cura/profile.py b/src/octoprint/plugins/cura/profile.py index 1f223b39..ee1304b6 100644 --- a/src/octoprint/plugins/cura/profile.py +++ b/src/octoprint/plugins/cura/profile.py @@ -50,7 +50,7 @@ defaults = dict( wall_thickness=0.8, solid_layer_thickness=0.6, print_temperature=[220, 0, 0, 0], - print_bed_temperature=70, + print_bed_temperature=0, platform_adhesion=PlatformAdhesionTypes.NONE, filament_diameter=[2.85, 0, 0, 0], filament_flow=100.0, From 069bfdd9f889c0a55a1806fda5867cb826ea382e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Thu, 10 Mar 2016 15:58:59 +0100 Subject: [PATCH 20/46] New flag to have M29 response inject "ok" Adjusted virtual printer to allow marking of M29 as broken re sending of ok. Fixes #1273 --- src/octoprint/plugins/virtual_printer/virtual.py | 4 ++++ src/octoprint/server/api/settings.py | 2 ++ src/octoprint/settings.py | 8 ++++++-- src/octoprint/static/js/app/viewmodels/settings.js | 5 ++++- .../templates/dialogs/settings/serialconnection.jinja2 | 5 +++++ src/octoprint/util/comm.py | 7 +++++++ 6 files changed, 28 insertions(+), 3 deletions(-) diff --git a/src/octoprint/plugins/virtual_printer/virtual.py b/src/octoprint/plugins/virtual_printer/virtual.py index 45f89062..6a2edba6 100644 --- a/src/octoprint/plugins/virtual_printer/virtual.py +++ b/src/octoprint/plugins/virtual_printer/virtual.py @@ -73,6 +73,8 @@ class VirtualPrinter(): self._echoOnM117 = settings().getBoolean(["devel", "virtualPrinter", "echoOnM117"]) + self._brokenM29 = settings().getBoolean(["devel", "virtualPrinter", "brokenM29"]) + self.currentLine = 0 self.lastN = 0 @@ -223,6 +225,8 @@ class VirtualPrinter(): elif 'M29' in data: if self._sdCardReady: self._finishSdFile() + if self._brokenM29: + continue elif 'M30' in data: if self._sdCardReady: filename = data.split(None, 1)[1].strip() diff --git a/src/octoprint/server/api/settings.py b/src/octoprint/server/api/settings.py index 49d92e98..2a7a29e2 100644 --- a/src/octoprint/server/api/settings.py +++ b/src/octoprint/server/api/settings.py @@ -90,6 +90,7 @@ def getSettings(): "helloCommand": s.get(["serial", "helloCommand"]), "ignoreErrorsFromFirmware": s.getBoolean(["serial", "ignoreErrorsFromFirmware"]), "disconnectOnErrors": s.getBoolean(["serial", "disconnectOnErrors"]), + "triggerOkForM29": s.getBoolean(["serial", "triggerOkForM29"]) }, "folder": { "uploads": s.getBaseFolder("uploads"), @@ -234,6 +235,7 @@ def setSettings(): 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"]) + if "triggerOkForM29" in data["serial"]: s.setBoolean(["serial", "triggerOkForM29"], data["serial"]["triggerOkForM29"]) 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 72ef69ec..863dede0 100644 --- a/src/octoprint/settings.py +++ b/src/octoprint/settings.py @@ -90,7 +90,10 @@ default_settings = { "helloCommand": "M110 N0", "disconnectOnErrors": True, "ignoreErrorsFromFirmware": False, - "logResends": False + "logResends": False, + + # command specific flags + "triggerOkForM29": True }, "server": { "host": "0.0.0.0", @@ -308,7 +311,8 @@ default_settings = { "sendWait": True, "waitInterval": 1.0, "supportM112": True, - "echoOnM117": True + "echoOnM117": True, + "brokenM29": True } } } diff --git a/src/octoprint/static/js/app/viewmodels/settings.js b/src/octoprint/static/js/app/viewmodels/settings.js index 4e7b79dc..34fc874e 100644 --- a/src/octoprint/static/js/app/viewmodels/settings.js +++ b/src/octoprint/static/js/app/viewmodels/settings.js @@ -140,6 +140,7 @@ $(function() { self.serial_helloCommand = ko.observable(undefined); self.serial_ignoreErrorsFromFirmware = ko.observable(undefined); self.serial_disconnectOnErrors = ko.observable(undefined); + self.serial_triggerOkForM29 = ko.observable(undefined); self.folder_uploads = ko.observable(undefined); self.folder_timelapse = ko.observable(undefined); @@ -458,6 +459,7 @@ $(function() { self.serial_helloCommand(response.serial.helloCommand); self.serial_ignoreErrorsFromFirmware(response.serial.ignoreErrorsFromFirmware); self.serial_disconnectOnErrors(response.serial.disconnectOnErrors); + self.serial_triggerOkForM29(response.serial.triggerOkForM29); self.folder_uploads(response.folder.uploads); self.folder_timelapse(response.folder.timelapse); @@ -548,7 +550,8 @@ $(function() { "checksumRequiringCommands": splitTextToArray(self.serial_checksumRequiringCommands(), ",", true), "helloCommand": self.serial_helloCommand(), "ignoreErrorsFromFirmware": self.serial_ignoreErrorsFromFirmware(), - "disconnectOnErrors": self.serial_disconnectOnErrors() + "disconnectOnErrors": self.serial_disconnectOnErrors(), + "triggerOkForM29": self.serial_triggerOkForM29() }, "folder": { "uploads": self.folder_uploads(), diff --git a/src/octoprint/templates/dialogs/settings/serialconnection.jinja2 b/src/octoprint/templates/dialogs/settings/serialconnection.jinja2 index e871184f..799796f9 100644 --- a/src/octoprint/templates/dialogs/settings/serialconnection.jinja2 +++ b/src/octoprint/templates/dialogs/settings/serialconnection.jinja2 @@ -116,6 +116,11 @@ {{ _('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 a96534f0..0da78fbe 100644 --- a/src/octoprint/util/comm.py +++ b/src/octoprint/util/comm.py @@ -230,6 +230,7 @@ class MachineCom(object): self._timeout = None self._hello_command = settings().get(["serial", "helloCommand"]) + self._trigger_ok_for_m29 = settings().getBoolean(["serial", "triggerOkForM29"]) self._alwaysSendChecksum = settings().getBoolean(["feature", "alwaysSendChecksum"]) self._sendChecksumWithUnknownCommands = settings().getBoolean(["feature", "sendChecksumWithUnknownCommands"]) @@ -1091,6 +1092,12 @@ class MachineCom(object): pass elif 'Done saving file' in line: self.refreshSdFiles() + + if self._trigger_ok_for_m29: + # workaround for most versions of Marlin out in the wild + # not sending an ok after saving a file + self._clear_to_send.set() + line, lower_line = convert_line("ok") elif 'File deleted' in line and line.strip().endswith("ok"): # buggy Marlin version that doesn't send a proper \r after the "File deleted" statement, fixed in # current versions From d393a6730ca5c6a494da59b9f0dfdab240be5a2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Thu, 10 Mar 2016 16:03:52 +0100 Subject: [PATCH 21/46] Don't have M28 response generate extra "ok" Fixes #1272 --- src/octoprint/util/comm.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/octoprint/util/comm.py b/src/octoprint/util/comm.py index 0da78fbe..27541b20 100644 --- a/src/octoprint/util/comm.py +++ b/src/octoprint/util/comm.py @@ -1072,8 +1072,6 @@ class MachineCom(object): }) elif 'Writing to file' in line and self.isStreaming(): self._changeState(self.STATE_PRINTING) - self._clear_to_send.set() - line, lower_line = convert_line("ok") elif 'Done printing file' in line and self.isSdPrinting(): # printer is reporting file finished printing self._sdFilePos = 0 From c9009496f610c15e2cfbed02113845fbb8e8b6ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Wed, 9 Mar 2016 19:44:40 +0100 Subject: [PATCH 22/46] Don't trigger an M105 on timeout during a resend Had to reorder message processing a bit in order to be able to properly handle further resends in timeout situations. Has the added benefit that "ok" now gets processed very early and now extra state based processing is needed anymore. --- .../plugins/virtual_printer/virtual.py | 18 +- src/octoprint/util/comm.py | 258 +++++++++++------- 2 files changed, 171 insertions(+), 105 deletions(-) diff --git a/src/octoprint/plugins/virtual_printer/virtual.py b/src/octoprint/plugins/virtual_printer/virtual.py index 6a2edba6..527cb9bc 100644 --- a/src/octoprint/plugins/virtual_printer/virtual.py +++ b/src/octoprint/plugins/virtual_printer/virtual.py @@ -91,6 +91,10 @@ class VirtualPrinter(): self._killed = False + self._triggerResendAt100 = True + self._triggerResendWithTimeoutAt105 = True + self._triggeredResendWithTimeoutAt105 = False + waitThread = threading.Thread(target=self._sendWaitAfterTimeout) waitThread.start() @@ -148,6 +152,10 @@ class VirtualPrinter(): linenumber = int(re.search("N([0-9]+)", data).group(1)) self.lastN = linenumber self.currentLine = linenumber + + self._triggerResendAt100 = True + self._triggerResendWithTimeoutAt105 = True + self._sendOk() continue elif data.startswith("N"): @@ -156,10 +164,18 @@ class VirtualPrinter(): if linenumber != expected: self._triggerResend(actual=linenumber) continue - elif self.currentLine == 100: + elif linenumber == 100 and self._triggerResendAt100: # simulate a resend at line 100 + self._triggerResendAt100 = False self._triggerResend(expected=100) continue + elif linenumber == 105 and self._triggerResendWithTimeoutAt105 and not self._writingToSd: + # simulate a resend with timeout at line 105 + self._triggerResendWithTimeoutAt105 = False + self._triggerResend(expected=105) + self._dont_answer = True + self.lastN = linenumber + continue else: self.lastN = linenumber data = data.split(None, 1)[1].strip() diff --git a/src/octoprint/util/comm.py b/src/octoprint/util/comm.py index 27541b20..d278027f 100644 --- a/src/octoprint/util/comm.py +++ b/src/octoprint/util/comm.py @@ -246,6 +246,8 @@ class MachineCom(object): self._resendSwallowRepetitions = settings().getBoolean(["feature", "ignoreIdenticalResends"]) self._resendSwallowRepetitionsCounter = 0 + self._resendActive = False + self._terminal_log = deque([], 20) self._disconnect_on_errors = settings().getBoolean(["serial", "disconnectOnErrors"]) @@ -340,7 +342,7 @@ class MachineCom(object): self._serialLogger.debug(message) def _to_logfile_with_terminal(self, message=None, level=logging.INFO): - log = "Last lines in terminal:\n" + "\n".join(map(lambda x: "| " + x, self._terminal_log)) + log = "Last lines in terminal:\n" + "\n".join(map(lambda x: "| " + x, list(self._terminal_log))) if message is not None: log = message + "\n| " + log self._logger.log(level, log) @@ -974,10 +976,29 @@ class MachineCom(object): self._sdFiles.append((filename, size)) continue - ##~~ process oks - if line.strip().startswith("ok") or (self.isPrinting() and supportWait and line.strip().startswith("wait")): - self._clear_to_send.set() - self._long_running_command = False + handled = False + + # process oks + if line.startswith("ok") or (self.isPrinting() and supportWait and line == "wait"): + # ok only considered handled if it's alone on the line, might be + # a response to an M105 or an M114 + self._handle_ok() + handled = (line == "wait" or line == "ok" or not ("T:" in line or "T0:" in line or "B:" in line or "C:" in line)) + + # process resends + elif lower_line.startswith("resend") or lower_line.startswith("rs"): + self._handleResendRequest(line) + handled = True + + # process timeouts + elif line == "" and time.time() > self._timeout: + # timeout only considered handled if the printer is printing + self._handle_timeout() + handled = self.isPrinting() + + # we don't have to process the rest if the line has already been handled fully + if handled and self._state not in (self.STATE_CONNECTING, self.STATE_DETECT_BAUDRATE): + continue ##~~ Temperature processing if ' T:' in line or line.startswith('T:') or ' T0:' in line or line.startswith('T0:') or ' B:' in line or line.startswith('B:'): @@ -1016,16 +1037,6 @@ class MachineCom(object): except ValueError: 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 line.startswith("ok"): - if self._formerTool is not None: - self._currentTool = self._formerTool - self._formerTool = None - if self._heatupWaitStartTime: - self._heatupWaitTimeLost = self._heatupWaitTimeLost + (time.time() - self._heatupWaitStartTime) - self._heatupWaitStartTime = None - self._heating = False - ##~~ SD Card handling elif 'SD init fail' in line or 'volume.init failed' in line or 'openRoot failed' in line: self._sdAvailable = False @@ -1094,12 +1105,11 @@ class MachineCom(object): if self._trigger_ok_for_m29: # workaround for most versions of Marlin out in the wild # not sending an ok after saving a file - self._clear_to_send.set() - line, lower_line = convert_line("ok") + self._handle_ok() elif 'File deleted' in line and line.strip().endswith("ok"): - # buggy Marlin version that doesn't send a proper \r after the "File deleted" statement, fixed in + # buggy Marlin version that doesn't send a proper line break after the "File deleted" statement, fixed in # current versions - self._clear_to_send.set() + self._handle_ok() ##~~ Message handling elif line != '' \ @@ -1158,7 +1168,8 @@ class MachineCom(object): eventManager().fire(Events.ERROR, {"error": self.getErrorString()}) elif 'start' in line or 'ok' in line: self._onConnected() - self._clear_to_send.set() + if 'start' in line: + self._clear_to_send.set() ### Connection attempt elif self._state == self.STATE_CONNECTING: @@ -1170,32 +1181,6 @@ class MachineCom(object): elif time.time() > self._timeout: self.close() - ### Operational - elif self._state == self.STATE_OPERATIONAL or self._state == self.STATE_PAUSED: - if line.startswith("ok"): - self._handle_ok() - - # resend -> start resend procedure from requested line - elif lower_line.startswith("resend") or lower_line.startswith("rs"): - self._handleResendRequest(line) - - ### Printing - elif self._state == self.STATE_PRINTING: - if line == "" and time.time() > self._timeout: - if not self._long_running_command: - self._log("Communication timeout during printing, forcing a line") - self._sendCommand("M105") - self._clear_to_send.set() - else: - self._logger.debug("Ran into a communication timeout, but a command known to be a long runner is currently active") - - if line.startswith("ok") or (supportWait and line.startswith("wait")): - # a wait while printing means our printer's buffer ran out, probably due to some ok getting - # swallowed, so we treat it the same as an ok here to take up communication again - self._handle_ok() - - elif lower_line.startswith("resend") or lower_line.startswith("rs"): - self._handleResendRequest(line) except: self._logger.exception("Something crashed inside the serial connection loop, please report this in OctoPrint's bug tracker:") @@ -1207,16 +1192,54 @@ class MachineCom(object): self._log("Connection closed, closing down monitor") def _handle_ok(self): + self._clear_to_send.set() + + # reset long running commands, persisted current tools and heatup counters on ok + + self._long_running_command = False + + if self._formerTool is not None: + self._currentTool = self._formerTool + self._formerTool = None + + if self._heatupWaitStartTime: + self._heatupWaitTimeLost = self._heatupWaitTimeLost + (time.time() - self._heatupWaitStartTime) + self._heatupWaitStartTime = None + self._heating = False + if not self._state in (self.STATE_PRINTING, self.STATE_OPERATIONAL, self.STATE_PAUSED): return - if self._resendSwallowNextOk: - self._resendSwallowNextOk = False - elif self._resendDelta is not None: + # process queues ongoing resend requests and queues if we are operational + + if self._resendDelta is not None: self._resendNextCommand() else: + self._resendActive = False self._continue_sending() + return + + def _handle_timeout(self): + if self._state not in (self.STATE_PRINTING,): + return + + if self._long_running_command: + self._logger.debug("Ran into a communication timeout, but a command known to be a long runner is currently active") + + general_message = "Configure long running commands or increase communication timeout if that happens regularly on specific commands or long moves." + if self._resendActive: + self._log("Communication timeout while printing and during an active resend, resending same line again to trigger response from printer. " + general_message) + self._resendSameCommand() + self._clear_to_send.set() + + else: + self._log("Communication timeout while printing, trying to trigger response from printer. " + general_message) + self._sendCommand("M105") + self._clear_to_send.set() + + return + def _continue_sending(self): if self._state == self.STATE_PRINTING: if not self._sendFromQueue() and not self.isSdPrinting(): @@ -1524,70 +1547,97 @@ class MachineCom(object): if "rs" in line: lineToResend = int(line.split()[1]) - if lineToResend is not None: - self._resendSwallowNextOk = True + if lineToResend is None: + 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 - - # 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 - - 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 - else: - self._resendNextCommand() - - # if we log resends, make sure we don't log more resends than the set rate within a window + 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 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 + # 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 - if new_rate_window or in_rate: - if new_rate_window: - self._log_resends_rate_start = now - self._log_resends_rate_count = 0 + lastCommError = self._lastCommError + self._lastCommError = None - 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 + resendDelta = self._currentLine - lineToResend - def _resendNextCommand(self): + 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 + + def _resendSameCommand(self): + self._resendNextCommand(again=True) + + def _resendNextCommand(self, again=False): self._lastCommError = None # Make sure we are only handling one sending job at a time with self._sendingLock: + if again: + # If we are about to last line from the active resend request + # again, we first need to increment resend delta. It might already + # be set to None if the last resend line was already sent, so + # if that's the case we set it to 0. It will then be incremented, + # the last line will be sent again, and then the delta will be + # decremented and set to None again, completing the cycle. + if self._resendDelta is None: + self._resendDelta = 0 + self._resendDelta += 1 + cmd = self._lastLines[-self._resendDelta] lineNumber = self._currentLine - self._resendDelta From 67362249627b8524b8c344b0c2291ed377bc219c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Wed, 9 Mar 2016 15:40:50 +0100 Subject: [PATCH 23/46] Removed logging from CountedEvent --- src/octoprint/util/__init__.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/octoprint/util/__init__.py b/src/octoprint/util/__init__.py index 3426ed67..02d96793 100644 --- a/src/octoprint/util/__init__.py +++ b/src/octoprint/util/__init__.py @@ -816,12 +816,9 @@ class RepeatedTimer(threading.Thread): class CountedEvent(object): - def __init__(self, value=0, max=None, name=None): - logger_name = __name__ + ".CountedEvent" + (".{name}".format(name=name) if name is not None else "") - self._logger = logging.getLogger(logger_name) - + def __init__(self, value=0, maximum=None, **kwargs): self._counter = 0 - self._max = max + self._max = kwargs.get("max", maximum) self._mutex = threading.Lock() self._event = threading.Event() @@ -846,17 +843,14 @@ class CountedEvent(object): return self._counter == 0 def _internal_set(self, value): - self._logger.debug("New counter value: {value}".format(value=value)) self._counter = value if self._counter <= 0: self._counter = 0 self._event.clear() - self._logger.debug("Cleared event") else: if self._max is not None and self._counter > self._max: self._counter = self._max self._event.set() - self._logger.debug("Set event") class InvariantContainer(object): From 47b7552f91a1172643d8e91b4b7bdf55ab8079a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Mon, 7 Mar 2016 18:24:24 +0100 Subject: [PATCH 24/46] Decouple comm progress reporting from processing --- src/octoprint/printer/standard.py | 82 +++++++++++++++++++++---------- 1 file changed, 56 insertions(+), 26 deletions(-) diff --git a/src/octoprint/printer/standard.py b/src/octoprint/printer/standard.py index 6912e1f4..a301e117 100644 --- a/src/octoprint/printer/standard.py +++ b/src/octoprint/printer/standard.py @@ -62,10 +62,6 @@ class Printer(PrinterInterface, comm.MachineComPrintCallback): self._currentZ = None - self._progress = None - self._printTime = None - self._printTimeLeft = None - self._printAfterSelect = False self._posAfterSelect = None @@ -93,7 +89,8 @@ class Printer(PrinterInterface, comm.MachineComPrintCallback): on_update=self._sendCurrentDataCallbacks, on_add_temperature=self._sendAddTemperatureCallbacks, on_add_log=self._sendAddLogCallbacks, - on_add_message=self._sendAddMessageCallbacks + on_add_message=self._sendAddMessageCallbacks, + on_get_progress=self._updateProgressDataCallback ) self._stateMonitor.reset( state={"text": self.get_state_string(), "flags": self._getStateFlags()}, @@ -597,7 +594,24 @@ class Printer(PrinterInterface, comm.MachineComPrintCallback): return result - def _setProgressData(self, progress, filepos, printTime, cleanedPrintTime): + def _setProgressData(self, completion=None, filepos=None, printTime=None, printTimeLeft=None): + self._stateMonitor.set_progress(dict(completion=int(completion * 100) if completion is not None else None, + filepos=filepos, + printTime=int(printTime) if printTime is not None else None, + printTimeLeft=int(printTimeLeft) if printTimeLeft is not None else None)) + + def _updateProgressDataCallback(self): + if self._comm is None: + progress = None + filepos = None + printTime = None + cleanedPrintTime = None + else: + progress = self._comm.getPrintProgress() + filepos = self._comm.getPrintFilepos() + printTime = self._comm.getPrintTime() + cleanedPrintTime = self._comm.getCleanedPrintTime() + estimatedTotalPrintTime = self._estimateTotalPrintTime(progress, cleanedPrintTime) totalPrintTime = estimatedTotalPrintTime @@ -613,16 +627,7 @@ class Printer(PrinterInterface, comm.MachineComPrintCallback): sub_progress = 1.0 totalPrintTime = (1 - sub_progress) * statisticalTotalPrintTime + sub_progress * estimatedTotalPrintTime - self._progress = progress - self._printTime = printTime - self._printTimeLeft = totalPrintTime - cleanedPrintTime if (totalPrintTime is not None and cleanedPrintTime is not None) else None - - self._stateMonitor.set_progress({ - "completion": self._progress * 100 if self._progress is not None else None, - "filepos": filepos, - "printTime": int(self._printTime) if self._printTime is not None else None, - "printTimeLeft": int(self._printTimeLeft) if self._printTimeLeft is not None else None - }) + printTimeLeft = totalPrintTime - cleanedPrintTime if (totalPrintTime is not None and cleanedPrintTime is not None) else None if progress: progress_int = int(progress * 100) @@ -630,6 +635,10 @@ class Printer(PrinterInterface, comm.MachineComPrintCallback): self._lastProgressReport = progress_int self._reportPrintProgressToPlugins(progress_int) + return dict(completion=progress * 100 if progress is not None else None, + filepos=filepos, + printTime=int(printTime) if printTime is not None else None, + printTimeLeft=int(printTimeLeft) if printTimeLeft is not None else None) def _addTemperatureData(self, temp, bedTemp): currentTimeUtc = int(time.time()) @@ -808,7 +817,7 @@ class Printer(PrinterInterface, comm.MachineComPrintCallback): Triggers storage of new values for printTime, printTimeLeft and the current progress. """ - self._setProgressData(self._comm.getPrintProgress(), self._comm.getPrintFilepos(), self._comm.getPrintTime(), self._comm.getCleanedPrintTime()) + self._stateMonitor.trigger_progress_update() def on_comm_z_change(self, newZ): """ @@ -874,12 +883,13 @@ class Printer(PrinterInterface, comm.MachineComPrintCallback): self._logger.exception("Error while trying to persist print recovery data") class StateMonitor(object): - def __init__(self, interval=0.5, on_update=None, on_add_temperature=None, on_add_log=None, on_add_message=None): + def __init__(self, interval=0.5, on_update=None, on_add_temperature=None, on_add_log=None, on_add_message=None, on_get_progress=None): self._interval = interval self._update_callback = on_update self._on_add_temperature = on_add_temperature self._on_add_log = on_add_log self._on_add_message = on_add_message + self._on_get_progress = on_get_progress self._state = None self._job_data = None @@ -888,16 +898,24 @@ class StateMonitor(object): self._current_z = None self._progress = None + self._progress_dirty = False + self._offsets = {} self._change_event = threading.Event() self._state_lock = threading.Lock() + self._progress_lock = threading.Lock() self._last_update = time.time() self._worker = threading.Thread(target=self._work) self._worker.daemon = True self._worker.start() + def _get_current_progress(self): + if callable(self._on_get_progress): + return self._on_get_progress() + return self._progress + def reset(self, state=None, job_data=None, progress=None, current_z=None): self.set_state(state) self.set_job_data(job_data) @@ -929,9 +947,16 @@ class StateMonitor(object): self._job_data = job_data self._change_event.set() + def trigger_progress_update(self): + with self._progress_lock: + self._progress_dirty = True + self._change_event.set() + def set_progress(self, progress): - self._progress = progress - self._change_event.set() + with self._progress_lock: + self._progress_dirty = False + self._progress = progress + self._change_event.set() def set_temp_offsets(self, offsets): self._offsets = offsets @@ -941,19 +966,24 @@ class StateMonitor(object): while True: self._change_event.wait() - with self._state_lock: - now = time.time() - delta = now - self._last_update - additional_wait_time = self._interval - delta - if additional_wait_time > 0: - time.sleep(additional_wait_time) + now = time.time() + delta = now - self._last_update + additional_wait_time = self._interval - delta + if additional_wait_time > 0: + time.sleep(additional_wait_time) + with self._state_lock: data = self.get_current_data() self._update_callback(data) self._last_update = time.time() self._change_event.clear() def get_current_data(self): + with self._progress_lock: + if self._progress_dirty: + self._progress = self._get_current_progress() + self._progress_dirty = False + return { "state": self._state, "job": self._job_data, From 337329eda07fccdb79b6611fff605d8ca7ecb4a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Fri, 11 Mar 2016 10:38:52 +0100 Subject: [PATCH 25/46] Added test stub for quick performance tests of the comm layer Running comm.py with parameters port, baudrate, local file, remote file will upload local file to remote file on the printer's sd using the provided connection parameters. Example: python -m octoprint.util.comm /dev/ttyUSB0 115200 /path/to/some.gcode test.gco No plugin support! Only regular serial connection supported. --- src/octoprint/util/comm.py | 107 +++++++++++++++++++++++++++++++++++-- 1 file changed, 104 insertions(+), 3 deletions(-) diff --git a/src/octoprint/util/comm.py b/src/octoprint/util/comm.py index d278027f..abc78331 100644 --- a/src/octoprint/util/comm.py +++ b/src/octoprint/util/comm.py @@ -2170,8 +2170,6 @@ class PrintingGcodeFileInformation(PrintingFileInformation): self._handle = None - self._first_line = None - self._offsets_callback = offsets_callback self._current_tool_callback = current_tool_callback @@ -2179,6 +2177,7 @@ class PrintingGcodeFileInformation(PrintingFileInformation): raise IOError("File %s does not exist" % self._filename) self._size = os.stat(self._filename).st_size self._pos = 0 + self._read_lines = 0 def seek(self, offset): if self._handle is None: @@ -2186,12 +2185,14 @@ class PrintingGcodeFileInformation(PrintingFileInformation): self._handle.seek(offset) self._pos = self._handle.tell() + self._read_lines = 0 def start(self): """ Opens the file for reading and determines the file size. """ PrintingFileInformation.start(self) + self._read_lines = 0 self._handle = bom_aware_open(self._filename, encoding="utf-8", errors="replace") def close(self): @@ -2221,19 +2222,30 @@ class PrintingGcodeFileInformation(PrintingFileInformation): while processed is None: if self._handle is None: # file got closed just now + self._pos = self._size + self._report_stats() return None line = to_unicode(self._handle.readline()) if not line: self.close() processed = process_gcode_line(line, offsets=offsets, current_tool=current_tool) - self._pos = self._handle.tell() + self._pos = self._handle.tell() + self._read_lines += 1 return processed except Exception as e: self.close() self._logger.exception("Exception while processing line") raise e + def _report_stats(self): + duration = time.time() - self._start_time + stats = dict(lines=self._read_lines, + rate=float(self._read_lines) / duration, + time_per_line=duration * 1000.0 / float(self._read_lines), + duration=duration) + self._logger.info("Finished in {duration:.3f} s. Approx. transfer rate of {rate:.3f} lines/s or {time_per_line:.3f} ms per line".format(**stats)) + class StreamingGcodeFileInformation(PrintingGcodeFileInformation): def __init__(self, path, localFilename, remoteFilename): PrintingGcodeFileInformation.__init__(self, path) @@ -2584,3 +2596,92 @@ def gcode_command_for_cmd(cmd): # this should never happen return None + +# --- Test code for speed testing the comm layer via command line follows + + +def upload_cli(): + """ + Usage: python -m octoprint.util.comm + + Uploads to on SD card of printer on port , using baudrate . + """ + + import sys + from octoprint.util import Object + + logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s") + logger = logging.getLogger(__name__) + + # fetch port, baudrate, filename and target from commandline + if len(sys.argv) < 5: + print("Usage: comm.py ") + sys.exit(-1) + + port = sys.argv[1] + baudrate = sys.argv[2] + path = sys.argv[3] + target = sys.argv[4] + + # init settings & plugin manager + settings(init=True) + octoprint.plugin.plugin_manager(init=True) + + # create dummy callback + class MyMachineComCallback(MachineComPrintCallback): + progress_interval = 1 + + def __init__(self, path, target): + self.finished = threading.Event() + self.finished.clear() + + self.comm = None + self.error = False + self.started = False + + self._path = path + self._target = target + + def on_comm_file_transfer_started(self, filename, filesize): + # transfer started, report + logger.info("Started file transfer of {}, size {}B".format(filename, filesize)) + self.started = True + + def on_comm_file_transfer_done(self, filename): + # transfer done, report, print stats and finish + logger.info("Finished file transfer of {}".format(filename)) + self.finished.set() + + def on_comm_state_change(self, state): + if state in (MachineCom.STATE_ERROR, MachineCom.STATE_CLOSED_WITH_ERROR): + # report and exit on errors + logger.error("Error/closed with error, exiting.") + self.error = True + self.finished.set() + + elif state in (MachineCom.STATE_OPERATIONAL,) and not self.started: + # start transfer once we are operational + self.comm.startFileTransfer(self._path, os.path.basename(self._path), self._target) + + callback = MyMachineComCallback(path, target) + + # mock printer profile manager + profile = dict(heatedBed=False, + extruder=dict(count=1)) + printer_profile_manager = Object() + printer_profile_manager.get_current_or_default = lambda: profile + + # initialize serial + comm = MachineCom(port=port, baudrate=baudrate, callbackObject=callback, printerProfileManager=printer_profile_manager) + callback.comm = comm + + # wait for file transfer to finish + callback.finished.wait() + + # close connection + comm.close() + + logger.info("Done, exiting...") + +if __name__ == "__main__": + upload_cli() From 7b16f38ba3abe36ff92f163d4fc71ad275c4254a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Fri, 11 Mar 2016 11:16:27 +0100 Subject: [PATCH 26/46] Fetch timeouts for communication only once from settings --- src/octoprint/util/comm.py | 32 ++++++++++++++------------------ 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/src/octoprint/util/comm.py b/src/octoprint/util/comm.py index abc78331..0db2e0a9 100644 --- a/src/octoprint/util/comm.py +++ b/src/octoprint/util/comm.py @@ -228,6 +228,12 @@ class MachineCom(object): self._connection_closing = False self._timeout = None + self._timeout_intervals = dict() + for key, value in settings().get(["serial", "timeout"], merged=True, asdict=True).items(): + try: + self._timeout_intervals[key] = float(value) + except: + pass self._hello_command = settings().get(["serial", "helloCommand"]) self._trigger_ok_for_m29 = settings().getBoolean(["serial", "triggerOkForM29"]) @@ -625,7 +631,7 @@ class MachineCom(object): self.sendCommand("M24") - self._sd_status_timer = RepeatedTimer(lambda: get_interval("sdStatus", default_value=1.0), self._poll_sd_status, run_first=True) + self._sd_status_timer = RepeatedTimer(self._timeout_intervals.get("sdStatus", 1.0), self._poll_sd_status, run_first=True) self._sd_status_timer.start() else: if pos is not None and isinstance(pos, int) and pos > 0: @@ -891,7 +897,7 @@ class MachineCom(object): self._changeState(self.STATE_CONNECTING) #Start monitoring the serial port. - self._timeout = get_new_timeout("communication") + self._timeout = get_new_timeout("communication", self._timeout_intervals) startSeen = False supportRepetierTargetTemp = settings().getBoolean(["feature", "repetierTargetTemp"]) @@ -910,7 +916,7 @@ class MachineCom(object): if line is None: break if line.strip() is not "": - self._timeout = get_new_timeout("communication") + self._timeout = get_new_timeout("communication", self._timeout_intervals) ##~~ debugging output handling if line.startswith("//"): @@ -1155,7 +1161,7 @@ class MachineCom(object): self._serial.timeout = connection_timeout self._log("Trying baudrate: %d" % (baudrate)) self._baudrateDetectRetry = 5 - self._timeout = get_new_timeout("communication") + self._timeout = get_new_timeout("communication", self._timeout_intervals) self._serial.write('\n') self.sayHello() except: @@ -1306,7 +1312,7 @@ class MachineCom(object): def _onConnected(self): self._serial.timeout = settings().getFloat(["serial", "timeout", "communication"]) - self._temperature_timer = RepeatedTimer(lambda: get_interval("temperature", default_value=4.0), self._poll_temperature, run_first=True) + self._temperature_timer = RepeatedTimer(self._timeout_intervals.get("temperature", 4.0), self._poll_temperature, run_first=True) self._temperature_timer.start() self._changeState(self.STATE_OPERATIONAL) @@ -2029,7 +2035,7 @@ class MachineCom(object): _timeout = float(p_match.group("value")) / 1000.0 elif s_match: _timeout = float(s_match.group("value")) - self._timeout = get_new_timeout("communication") + _timeout + self._timeout = get_new_timeout("communication", self._timeout_intervals) + _timeout ##~~ command phase handlers @@ -2297,21 +2303,11 @@ class TypeAlreadyInQueue(Exception): self.type = t -def get_new_timeout(type): +def get_new_timeout(type, intervals): now = time.time() - return now + get_interval(type) + return now + intervals.get(type, 0.0) -def get_interval(type, default_value=0.0): - if type not in default_settings["serial"]["timeout"]: - return default_value - else: - value = settings().getFloat(["serial", "timeout", type]) - if not value: - return default_value - else: - return value - _temp_command_regex = re.compile("^M(?P104|109|140|190)(\s+T(?P\d+)|\s+S(?P[-+]?\d*\.?\d*))+") def apply_temperature_offsets(line, offsets, current_tool=None): From d701c261c36db761d17817be60b1ec858366e5a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Fri, 11 Mar 2016 12:35:53 +0100 Subject: [PATCH 27/46] Less processing overhead when uploading to SD Better structured "don't handle phases" checks and adjusted job wrapper that doesn't try to apply tool tracking or temperature offsets. --- src/octoprint/util/comm.py | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/src/octoprint/util/comm.py b/src/octoprint/util/comm.py index 0db2e0a9..da7bd4f7 100644 --- a/src/octoprint/util/comm.py +++ b/src/octoprint/util/comm.py @@ -1662,24 +1662,22 @@ class MachineCom(object): return False gcode = None - if not self.isStreaming(): - # trigger the "queuing" phase only if we are not streaming to sd right now - cmd, cmd_type, gcode = self._process_command_phase("queuing", cmd, cmd_type, gcode=gcode) - if cmd is None: - # command is no more, return - return False + # trigger the "queuing" phase only if we are not streaming to sd right now + cmd, cmd_type, gcode = self._process_command_phase("queuing", cmd, cmd_type, gcode=gcode) - if gcode and gcode in gcodeToEvent: - # if this is a gcode bound to an event, trigger that now - eventManager().fire(gcodeToEvent[gcode]) + if cmd is None: + # command is no more, return + return False + + if not self.isStreaming() and gcode and gcode in gcodeToEvent: + # if this is a gcode bound to an event, trigger that now + eventManager().fire(gcodeToEvent[gcode]) # actually enqueue the command for sending self._enqueue_for_sending(cmd, command_type=cmd_type) - if not self.isStreaming(): - # trigger the "queued" phase only if we are not streaming to sd right now - self._process_command_phase("queued", cmd, cmd_type, gcode=gcode) + self._process_command_phase("queued", cmd, cmd_type, gcode=gcode) return True @@ -1781,7 +1779,7 @@ class MachineCom(object): self._log("Closing down send loop") def _process_command_phase(self, phase, command, command_type=None, gcode=None): - if phase not in ("queuing", "queued", "sending", "sent"): + if self.isStreaming() or phase not in ("queuing", "queued", "sending", "sent"): return command, command_type, gcode if gcode is None: @@ -2234,8 +2232,7 @@ class PrintingGcodeFileInformation(PrintingFileInformation): line = to_unicode(self._handle.readline()) if not line: self.close() - processed = process_gcode_line(line, offsets=offsets, current_tool=current_tool) - + processed = self._process(line, offsets, current_tool) self._pos = self._handle.tell() self._read_lines += 1 return processed @@ -2244,6 +2241,9 @@ class PrintingGcodeFileInformation(PrintingFileInformation): self._logger.exception("Exception while processing line") raise e + def _process(self, line, offsets, current_tool): + return process_gcode_line(line, offsets=offsets, current_tool=current_tool) + def _report_stats(self): duration = time.time() - self._start_time stats = dict(lines=self._read_lines, @@ -2268,6 +2268,9 @@ class StreamingGcodeFileInformation(PrintingGcodeFileInformation): def getRemoteFilename(self): return self._remoteFilename + def _process(self, line, offsets, current_tool): + return process_gcode_line(line) + class TypedQueue(queue.Queue): From 6d8eb682c132774fc86b8c19af0fed735103409a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Fri, 11 Mar 2016 12:45:30 +0100 Subject: [PATCH 28/46] Use concatenation instead of format strings in comm layer --- src/octoprint/util/comm.py | 46 ++++++++++++++++++-------------------- 1 file changed, 22 insertions(+), 24 deletions(-) diff --git a/src/octoprint/util/comm.py b/src/octoprint/util/comm.py index da7bd4f7..c5604e70 100644 --- a/src/octoprint/util/comm.py +++ b/src/octoprint/util/comm.py @@ -1491,15 +1491,13 @@ class MachineCom(object): self._errorValue = get_exception_string() self.close(True) return None - if ret == '': - #self._log("Recv: TIMEOUT") - return '' - try: - self._log("Recv: %s" % sanitize_ascii(ret)) - except ValueError as e: - self._log("WARN: While reading last line: %s" % e) - self._log("Recv: %r" % ret) + if ret != "": + try: + self._log("Recv: " + sanitize_ascii(ret)) + except ValueError as e: + self._log("WARN: While reading last line: %s" % e) + self._log("Recv: " + repr(ret)) return ret @@ -1726,7 +1724,7 @@ class MachineCom(object): if linenumber is not None: # line number predetermined - this only happens for resends, so we'll use the number and # send directly without any processing (since that already took place on the first sending!) - self._doSendWithChecksum(command, linenumber) + self._do_send_with_checksum(command, linenumber) else: # trigger "sending" phase @@ -1750,9 +1748,9 @@ class MachineCom(object): command_to_send = command.encode("ascii", errors="replace") if command_requiring_checksum or (command_allowing_checksum and checksum_enabled): - self._doIncrementAndSendWithChecksum(command_to_send) + self._do_increment_and_send_with_checksum(command_to_send) else: - self._doSendWithoutChecksum(command_to_send) + self._do_send_without_checksum(command_to_send) # trigger "sent" phase and use up one "ok" self._process_command_phase("sent", command, command_type, gcode=gcode) @@ -1843,24 +1841,24 @@ class MachineCom(object): ##~~ actual sending via serial - def _doIncrementAndSendWithChecksum(self, cmd): + def _do_increment_and_send_with_checksum(self, cmd): with self._line_mutex: linenumber = self._currentLine self._addToLastLines(cmd) self._currentLine += 1 - self._doSendWithChecksum(cmd, linenumber) + self._do_send_with_checksum(cmd, linenumber) - def _doSendWithChecksum(self, cmd, lineNumber): - commandToSend = "N%d %s" % (lineNumber, cmd) - checksum = reduce(lambda x,y:x^y, map(ord, commandToSend)) - commandToSend = "%s*%d" % (commandToSend, checksum) - self._doSendWithoutChecksum(commandToSend) + def _do_send_with_checksum(self, command, linenumber): + command_to_send = "N" + str(linenumber) + " " + command + checksum = reduce(lambda x, y: x ^ y, map(ord, command_to_send)) + command_to_send = command_to_send + "*" + str(checksum) + self._do_send_without_checksum(command_to_send) - def _doSendWithoutChecksum(self, cmd): + def _do_send_without_checksum(self, cmd): if self._serial is None: return - self._log("Send: %s" % cmd) + self._log("Send: " + str(cmd)) try: self._serial.write(cmd + '\n') except serial.SerialTimeoutException: @@ -1996,8 +1994,8 @@ class MachineCom(object): def _gcode_M112_queuing(self, cmd, cmd_type=None): # emergency stop, jump the queue with the M112 - self._doSendWithoutChecksum("M112") - self._doIncrementAndSendWithChecksum("M112") + self._do_send_without_checksum("M112") + self._do_increment_and_send_with_checksum("M112") # No idea if the printer is still listening or if M112 won. Just in case # we'll now try to also manually make sure all heaters are shut off - better @@ -2005,9 +2003,9 @@ class MachineCom(object): # is irrelevant whether the printer has sent enough ack's or not, we # are going to shutdown the connection in a second anyhow. for tool in range(self._printerProfileManager.get_current_or_default()["extruder"]["count"]): - self._doIncrementAndSendWithChecksum("M104 T{tool} S0".format(tool=tool)) + self._do_increment_and_send_with_checksum("M104 T{tool} S0".format(tool=tool)) if self._printerProfileManager.get_current_or_default()["heatedBed"]: - self._doIncrementAndSendWithChecksum("M140 S0") + self._do_increment_and_send_with_checksum("M140 S0") # close to reset host state self._errorValue = "Closing serial port due to emergency stop M112." From 542ccb5421a9578177d8355204751745af78b347 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Fri, 11 Mar 2016 16:29:59 +0100 Subject: [PATCH 29/46] Slight simplification of setProgressData calls in standard printer --- src/octoprint/printer/standard.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/octoprint/printer/standard.py b/src/octoprint/printer/standard.py index a301e117..b6492bde 100644 --- a/src/octoprint/printer/standard.py +++ b/src/octoprint/printer/standard.py @@ -361,7 +361,7 @@ class Printer(PrinterInterface, comm.MachineComPrintCallback): self._printAfterSelect = printAfterSelect self._posAfterSelect = pos self._comm.selectFile("/" + path if sd else path, sd) - self._setProgressData(0, None, None, None) + self._setProgressData(completion=0) self._setCurrentZ(None) def unselect_file(self): @@ -369,7 +369,7 @@ class Printer(PrinterInterface, comm.MachineComPrintCallback): return self._comm.unselectFile() - self._setProgressData(0, None, None, None) + self._setProgressData(completion=0) self._setCurrentZ(None) def start_print(self, pos=None): @@ -400,7 +400,7 @@ class Printer(PrinterInterface, comm.MachineComPrintCallback): self._fileManager.delete_recovery_data() self._lastProgressReport = None - self._setProgressData(0, None, None, None) + self._setProgressData(completion=0) self._setCurrentZ(None) self._comm.startPrint(pos=pos) @@ -424,7 +424,7 @@ class Printer(PrinterInterface, comm.MachineComPrintCallback): # reset progress, height, print time self._setCurrentZ(None) - self._setProgressData(None, None, None, None) + self._setProgressData() # mark print as failure if self._selectedFile is not None: @@ -798,7 +798,7 @@ class Printer(PrinterInterface, comm.MachineComPrintCallback): if self._comm is not None: self._comm = None - self._setProgressData(0, None, None, None) + self._setProgressData(completion=0) self._setCurrentZ(None) self._setJobData(None, None, None) @@ -847,7 +847,7 @@ class Printer(PrinterInterface, comm.MachineComPrintCallback): def on_comm_print_job_done(self): self._fileManager.log_print(FileDestinations.SDCARD if self._selectedFile["sd"] else FileDestinations.LOCAL, self._selectedFile["filename"], time.time(), self._comm.getPrintTime(), True, self._printerProfileManager.get_current_or_default()["id"]) - self._setProgressData(1.0, self._selectedFile["filesize"], self._comm.getPrintTime(), 0) + self._setProgressData(completion=1.0, filepos=self._selectedFile["filesize"], printTime=self._comm.getPrintTime(), printTimeLeft=0) self._stateMonitor.set_state({"text": self.get_state_string(), "flags": self._getStateFlags()}) self._fileManager.delete_recovery_data() @@ -855,7 +855,7 @@ class Printer(PrinterInterface, comm.MachineComPrintCallback): self._sdStreaming = True self._setJobData(filename, filesize, True) - self._setProgressData(0.0, 0, 0, None) + self._setProgressData(completion=0.0, filepos=0, printTime=0) self._stateMonitor.set_state({"text": self.get_state_string(), "flags": self._getStateFlags()}) def on_comm_file_transfer_done(self, filename): @@ -868,7 +868,7 @@ class Printer(PrinterInterface, comm.MachineComPrintCallback): self._setCurrentZ(None) self._setJobData(None, None, None) - self._setProgressData(None, None, None, None) + self._setProgressData() self._stateMonitor.set_state({"text": self.get_state_string(), "flags": self._getStateFlags()}) def on_comm_force_disconnect(self): From 6e01a361c5666c69bc33e58998aba5ce2abd15a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Fri, 11 Mar 2016 16:35:15 +0100 Subject: [PATCH 30/46] Make "Fake Acknowledge" button really fake an ok So far it only set the clear_to_send flag, now it actually does everything else a read ok would do. --- src/octoprint/util/comm.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/octoprint/util/comm.py b/src/octoprint/util/comm.py index c5604e70..fa839e1a 100644 --- a/src/octoprint/util/comm.py +++ b/src/octoprint/util/comm.py @@ -523,7 +523,7 @@ class MachineCom(object): self._tempOffsets.update(offsets) def fakeOk(self): - self._clear_to_send.set() + self._handle_ok() def sendCommand(self, cmd, cmd_type=None, processed=False, force=False): cmd = to_unicode(cmd, errors="replace") From 29aefdec087416bb6e11a550a8618993648de633 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Fri, 11 Mar 2016 16:48:03 +0100 Subject: [PATCH 31/46] Don't send M20 twice after streaming to SD Fixes #1274 --- src/octoprint/util/comm.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/octoprint/util/comm.py b/src/octoprint/util/comm.py index fa839e1a..1405a0f0 100644 --- a/src/octoprint/util/comm.py +++ b/src/octoprint/util/comm.py @@ -1106,8 +1106,6 @@ class MachineCom(object): except: pass elif 'Done saving file' in line: - self.refreshSdFiles() - if self._trigger_ok_for_m29: # workaround for most versions of Marlin out in the wild # not sending an ok after saving a file From 85060b972a3d22b127ac31d46ae997ed9fc1aa57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Fri, 11 Mar 2016 17:20:13 +0100 Subject: [PATCH 32/46] Fix _handle_timeout not correctly honoring long runners Issue was introduced through #1271 fix. --- src/octoprint/util/comm.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/octoprint/util/comm.py b/src/octoprint/util/comm.py index 1405a0f0..fb26c5bb 100644 --- a/src/octoprint/util/comm.py +++ b/src/octoprint/util/comm.py @@ -1230,6 +1230,7 @@ class MachineCom(object): if self._long_running_command: self._logger.debug("Ran into a communication timeout, but a command known to be a long runner is currently active") + return general_message = "Configure long running commands or increase communication timeout if that happens regularly on specific commands or long moves." if self._resendActive: From cbaf84871167f374ef9fa97a617581b64a53b210 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Fri, 11 Mar 2016 17:16:39 +0100 Subject: [PATCH 33/46] Move TypedQueue to octoprint.util & make generic put and get are now overwritten to create t-tuple out of provided item and item_type on put, and to only return item on get. _put and _get extract item_type and use that for managing the lookup set. --- src/octoprint/util/__init__.py | 41 ++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/src/octoprint/util/__init__.py b/src/octoprint/util/__init__.py index 02d96793..9de498e9 100644 --- a/src/octoprint/util/__init__.py +++ b/src/octoprint/util/__init__.py @@ -18,6 +18,7 @@ import threading from functools import wraps import warnings import contextlib +import Queue as queue logger = logging.getLogger(__name__) @@ -884,3 +885,43 @@ class InvariantContainer(object): def __iter__(self): return self._data.__iter__() + + +class TypedQueue(queue.Queue): + + def __init__(self, maxsize=0): + queue.Queue.__init__(self, maxsize=maxsize) + self._lookup = set() + + def put(self, item, item_type=None, *args, **kwargs): + queue.Queue.put(self, (item, item_type), *args, **kwargs) + + def get(self, *args, **kwargs): + item, _ = queue.Queue.get(self, *args, **kwargs) + return item + + def _put(self, item): + _, item_type = item + if item_type is not None: + if item_type in self._lookup: + raise TypeAlreadyInQueue(item_type, "Type {} is already in queue".format(item_type)) + else: + self._lookup.add(item_type) + + queue.Queue._put(self, item) + + def _get(self): + item = queue.Queue._get(self) + _, item_type = item + + if item_type is not None: + self._lookup.discard(item_type) + + return item + + +class TypeAlreadyInQueue(Exception): + def __init__(self, t, *args, **kwargs): + Exception.__init__(self, *args, **kwargs) + self.type = t + From baab1ce70aff2c7653b6255979081dd44119e338 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Fri, 11 Mar 2016 17:18:48 +0100 Subject: [PATCH 34/46] Use new generic TypedQueue for send queue and command queue That way no M105 cascades can be triggered by the M105 polling timer (or the M27 polling timer). Adjusted logging output to make clear which queue rejected typed entry. Made timeout handler send M105 with type "temperature" as well to make sure we don't suddenly have two M105 directly after each other in the send_queue, one from the polling timer, one from the timeout handler. Fixes #1275 --- src/octoprint/util/comm.py | 54 +++++++++----------------------------- 1 file changed, 12 insertions(+), 42 deletions(-) diff --git a/src/octoprint/util/comm.py b/src/octoprint/util/comm.py index fb26c5bb..d07f50f1 100644 --- a/src/octoprint/util/comm.py +++ b/src/octoprint/util/comm.py @@ -24,7 +24,8 @@ from octoprint.settings import settings, default_settings from octoprint.events import eventManager, Events from octoprint.filemanager import valid_file_type from octoprint.filemanager.destinations import FileDestinations -from octoprint.util import get_exception_string, sanitize_ascii, filter_non_ascii, CountedEvent, RepeatedTimer, to_unicode, bom_aware_open +from octoprint.util import get_exception_string, sanitize_ascii, filter_non_ascii, CountedEvent, RepeatedTimer, \ + to_unicode, bom_aware_open, TypedQueue, TypeAlreadyInQueue try: import _winreg @@ -214,7 +215,7 @@ class MachineCom(object): self._temp = {} self._bedTemp = None self._tempOffsets = dict() - self._commandQueue = queue.Queue() + self._command_queue = TypedQueue() self._currentZ = None self._heatupWaitStartTime = None self._heatupWaitTimeLost = 0.0 @@ -533,7 +534,10 @@ class MachineCom(object): return if self.isPrinting() and not self.isSdFileSelected(): - self._commandQueue.put((cmd, cmd_type)) + try: + self._command_queue.put((cmd, cmd_type), item_type=cmd_type) + except TypeAlreadyInQueue as e: + self._logger.debug("Type already in command queue: " + e.type) elif self.isOperational() or force: self._sendCommand(cmd, cmd_type=cmd_type) @@ -1240,7 +1244,7 @@ class MachineCom(object): else: self._log("Communication timeout while printing, trying to trigger response from printer. " + general_message) - self._sendCommand("M105") + self._sendCommand("M105", cmd_type="temperature") self._clear_to_send.set() return @@ -1332,11 +1336,11 @@ class MachineCom(object): # from the queue, we'll send the second (if there is one). We do not # want to get stuck here by throwing away commands. while True: - if self._commandQueue.empty() or self.isStreaming(): + if self._command_queue.empty() or self.isStreaming(): # no command queue or irrelevant command queue => return return False - entry = self._commandQueue.get() + entry = self._command_queue.get() if isinstance(entry, tuple): if not len(entry) == 2: # something with that entry is broken, ignore it and fetch @@ -1691,9 +1695,9 @@ class MachineCom(object): """ try: - self._send_queue.put((command, linenumber, command_type)) + self._send_queue.put((command, linenumber, command_type), item_type=command_type) except TypeAlreadyInQueue as e: - self._logger.debug("Type already in queue: " + e.type) + self._logger.debug("Type already in send queue: " + e.type) def _send_loop(self): """ @@ -2269,40 +2273,6 @@ class StreamingGcodeFileInformation(PrintingGcodeFileInformation): return process_gcode_line(line) -class TypedQueue(queue.Queue): - - def __init__(self, maxsize=0): - queue.Queue.__init__(self, maxsize=maxsize) - self._lookup = [] - - def _put(self, item): - if isinstance(item, tuple) and len(item) == 3: - cmd, line, cmd_type = item - if cmd_type is not None: - if cmd_type in self._lookup: - raise TypeAlreadyInQueue(cmd_type, "Type {cmd_type} is already in queue".format(**locals())) - else: - self._lookup.append(cmd_type) - - queue.Queue._put(self, item) - - def _get(self): - item = queue.Queue._get(self) - - if isinstance(item, tuple) and len(item) == 3: - cmd, line, cmd_type = item - if cmd_type is not None and cmd_type in self._lookup: - self._lookup.remove(cmd_type) - - return item - - -class TypeAlreadyInQueue(Exception): - def __init__(self, t, *args, **kwargs): - Exception.__init__(self, *args, **kwargs) - self.type = t - - def get_new_timeout(type, intervals): now = time.time() return now + intervals.get(type, 0.0) From 5cadf85401f5cd9b7ccd0548dee49a6059805900 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Fri, 11 Mar 2016 17:58:00 +0100 Subject: [PATCH 35/46] Only report transfer rates in the log for sd uploads For real prints they are misleading - mechanical movement takes time. --- src/octoprint/util/comm.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/octoprint/util/comm.py b/src/octoprint/util/comm.py index d07f50f1..8dddf219 100644 --- a/src/octoprint/util/comm.py +++ b/src/octoprint/util/comm.py @@ -2247,11 +2247,8 @@ class PrintingGcodeFileInformation(PrintingFileInformation): def _report_stats(self): duration = time.time() - self._start_time - stats = dict(lines=self._read_lines, - rate=float(self._read_lines) / duration, - time_per_line=duration * 1000.0 / float(self._read_lines), - duration=duration) - self._logger.info("Finished in {duration:.3f} s. Approx. transfer rate of {rate:.3f} lines/s or {time_per_line:.3f} ms per line".format(**stats)) + self._logger.info("Finished in {:.3f} s.".format(duration)) + pass class StreamingGcodeFileInformation(PrintingGcodeFileInformation): def __init__(self, path, localFilename, remoteFilename): @@ -2272,6 +2269,13 @@ class StreamingGcodeFileInformation(PrintingGcodeFileInformation): def _process(self, line, offsets, current_tool): return process_gcode_line(line) + def _report_stats(self): + duration = time.time() - self._start_time + stats = dict(lines=self._read_lines, + rate=float(self._read_lines) / duration, + time_per_line=duration * 1000.0 / float(self._read_lines), + duration=duration) + self._logger.info("Finished in {duration:.3f} s. Approx. transfer rate of {rate:.3f} lines/s or {time_per_line:.3f} ms per line".format(**stats)) def get_new_timeout(type, intervals): now = time.time() From e6f900354921e0ef8caf3fec43f78f848b7097cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Fri, 11 Mar 2016 18:01:22 +0100 Subject: [PATCH 36/46] Reset line numbers before entering sd upload state Otherwise M110 N0 will not be processed to be prefixed with N0 and hence not reset the line numbers on the printer to 0. That was as bug in 1a308a1bc7094aaf4fe8c0b8f780d9eec0b9f335 --- src/octoprint/util/comm.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/octoprint/util/comm.py b/src/octoprint/util/comm.py index 8dddf219..54ab12d6 100644 --- a/src/octoprint/util/comm.py +++ b/src/octoprint/util/comm.py @@ -658,10 +658,11 @@ class MachineCom(object): logging.info("Printer is not operation or busy") return + self.resetLineNumbers() + self._currentFile = StreamingGcodeFileInformation(filename, localFilename, remoteFilename) self._currentFile.start() - self.resetLineNumbers() self.sendCommand("M28 %s" % remoteFilename) eventManager().fire(Events.TRANSFER_STARTED, {"local": localFilename, "remote": remoteFilename}) self._callback.on_comm_file_transfer_started(remoteFilename, self._currentFile.getFilesize()) From 38a47bb2a86cedf2a5b2a84a36da532a28bf924c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Fri, 11 Mar 2016 18:06:14 +0100 Subject: [PATCH 37/46] Change wording: not firmware error, but communication error "Error:" lines can also be produced by OctoPrint itself in case of serial exceptions or connection failures, so "the firmware reported" was misleading. --- src/octoprint/static/js/app/dataupdater.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/octoprint/static/js/app/dataupdater.js b/src/octoprint/static/js/app/dataupdater.js index a62295be..643164cb 100644 --- a/src/octoprint/static/js/app/dataupdater.js +++ b/src/octoprint/static/js/app/dataupdater.js @@ -347,16 +347,16 @@ function DataUpdater(allViewModels) { } 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), + title: gettext("Unhandled communication error"), + text: _.sprintf(gettext("There was an unhandled error while talking to the printer. 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), + title: gettext("Unhandled communication error"), + text: _.sprintf(gettext("The was an unhandled error while talking to the printer. Due to that OctoPrint disconnected. Error: %(error)s"), payload), type: "error", hide: false }); From 56caa7750c08f2187493f4d88e4961c9ac552123 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Sat, 12 Mar 2016 09:40:33 +0100 Subject: [PATCH 38/46] Twice as fast checksum calculation Thanks @nophead! --- src/octoprint/util/comm.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/octoprint/util/comm.py b/src/octoprint/util/comm.py index 54ab12d6..d484f6fa 100644 --- a/src/octoprint/util/comm.py +++ b/src/octoprint/util/comm.py @@ -1854,7 +1854,9 @@ class MachineCom(object): def _do_send_with_checksum(self, command, linenumber): command_to_send = "N" + str(linenumber) + " " + command - checksum = reduce(lambda x, y: x ^ y, map(ord, command_to_send)) + checksum = 0 + for c in command_to_send: + checksum ^= ord(c) command_to_send = command_to_send + "*" + str(checksum) self._do_send_without_checksum(command_to_send) From 4a933228c7c8c8dd38f94f2b26f5a4d7f45fbc01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Mon, 14 Mar 2016 11:43:15 +0100 Subject: [PATCH 39/46] Added PR and issue templates, updated CONTRIBUTING.md --- .github/ISSUE_TEMPLATE.md | 60 ++++++++++++++++++++++++++++++++ .github/PULL_REQUEST_TEMPLATE.md | 50 ++++++++++++++++++++++++++ CONTRIBUTING.md | 55 +++++++++++++++++++++-------- 3 files changed, 150 insertions(+), 15 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE.md create mode 100644 .github/PULL_REQUEST_TEMPLATE.md diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 00000000..d4c79c2e --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,60 @@ +Please read the "guidelines for contributing" that are linked ^-- just +up there. Also read the FAQ: https://github.com/foosel/OctoPrint/wiki/FAQ. + +This is a bug and feature tracker, please only use it to report bugs +or request features within OctoPrint (not OctoPi, not any OctoPrint +plugins and not unofficial OctoPrint versions). Mark requests with +a [Request] prefix in the title please. Fully fill out the bug reporting +template for bug reports. + +Do not seek support here ("I need help with ..."), that belongs on +the mailing list or the G+ community (both linked in the "guidelines +for contributing" linked above, read it!), NOT here. + +Thank you! + +---- + +#### What were you doing? + +[Please be as specific as possible here. The maintainers will need to reproduce +your issue in order to fix it and that is not possible if they don't know +what you did to get it to happen in the first place. If you encountered +a problem with specific files of any sorts, make sure to also include a link to a file +with which to reproduce the problem.] + +#### What did you expect to happen? + +#### What happened instead? + +#### Branch & Commit or Version of OctoPrint + +[Can be found in the lower left corner of the web interface.] + +#### Printer model & used firmware incl. version + +[If applicable, always include if unsure.] + +#### Browser and Version of Browser, Operating System running Browser + +[If applicable, always include if unsure.] + +#### Link to octoprint.log + +[On gist.github.com or pastebin.com. Always include and never truncate.] + +#### Link to contents of terminal tab or serial.log + +[On gist.github.com or pastebin.com. If applicable, always include if unsure or +reporting communication issues. Never truncate.] + +#### Link to contents of Javascript console in the browser + +[On gist.github.com or pastebin.com or alternatively a screenshot. If applicable - +always include if unsure or reporting UI issues.] + +#### Screenshot(s) showing the problem: + +[If applicable. Always include if unsure or reporting UI issues.] + +I have read the FAQ. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000..b4d5c417 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,50 @@ +Thank you for your interest into contributing to OctoPrint, it's +highly appreciated! + +Please make sure you have read the "guidelines for contributing" as +linked just above this form, there's a section on Pull Requests in there +as well that contains important information. + +As a summary, please make sure you have ticked all points on this +checklist: + + * [ ] Your changes are not possible to do through a plugin and relevant + to a large audience (ideally all users of OctoPrint) + * [ ] If your changes are large or otherwise disruptive: You have + made sure your changes don't interfere with current development by + talking it through with the maintainers, e.g. through a + Brainstorming ticket + * [ ] Your PR targets OctoPrint's devel branch (not master, + maintenance or anything else) + * [ ] Your PR was opened from a custom branch on your repository + (no PRs from your version of master, maintenance or devel please), + e.g. dev/my_new_feature + * [ ] Your PR only contains relevant changes: no unrelated files, + no dead code, ideally only one commit - rebase your PR if necessary! + * [ ] Your changes follow the coding style + * [ ] If your changes include style sheets: You have modified the + .less source files, not the .css files (those are generated with + lessc) + * [ ] You have tested your changes (please state how!) - ideally you + have added unit tests + * [ ] You have run the existing unit tests against your changes and + nothing broke + * [ ] You have added yourself to the AUTHORS.md file :) + +Feel free to delete all this help text, then describe +your PR further. You may use the template provided below to do that. +The more details the better! + +---- + +#### What does this PR do and why is it necessary? + +#### How was it tested? How can it be tested by the reviewer? + +#### Any background context you want to provide? + +#### What are the relevant tickets if any? + +#### Screenshots (if appropriate) + +#### Further notes diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8c8080f0..2ece77e2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -232,43 +232,67 @@ See [How to open the Javascript Console in different browsers](https://webmaster implement your feature as a plugin, create a "Brainstorming" ticket to get the discussion going on how best to solve *this* in OctoPrint's plugin system - maybe that's the actual PR you have been waiting for to contribute :) -2. If you plan to make **any large changes to the code or appearance, please - open a "Brainstorming" ticket first** so that we can determine if it's a - good time for your specific pull request. It might be that we're currently - in the process of making heavy changes to the code locations you'd target - as well, or your approach doesn't fit the general "project vision", and - that would just cause unnecessary work and frustration for everyone or +2. If you plan to make **any large or otherwise disruptive changes to the + code or appearance, please open a "Brainstorming" ticket first** so + that we can determine if it's a good time for your specific pull + request. It might be that we're currently in the process of making + heavy changes to the code locations you'd target as well, or your + approach doesn't fit the general "project vision", and that would + just cause unnecessary work and frustration for everyone or possibly get the PR rejected. 3. Create your pull request **from a custom branch** on your end (e.g. `dev/myNewFeature`)[1] **against the `devel` branch**. Create **one pull request per feature/bug fix**. If your PR contains an important bug fix, we will make sure to backport it to the `maintenance` branch to also include it in the next release. -4. Make sure you **follow the current coding style**. This means: - +4. Make sure there are **only relevant changes** included in your PR. No + changes to unrelated files, no additional files that don't belong (e.g. + commits of your full virtual environment). Make sure your PR consists + **ideally of only one commit** (use git's rebase and squash functionality). +5. Make sure you **follow the current coding style**. This means: * Tabs instead of spaces in the Python files[2] * Spaces instead of tabs in the Javascript sources * English language (code, variables, comments, ...) * Comments where necessary: Tell *why* the code does something like it does it, structure your code * Following the general architecture - - If your PR needs to make changes to the Stylesheets, change the ``.less`` files - from which the CSS is compiled. -5. **Test your changes thoroughly**. That also means testing with usage + * If your PR needs to make changes to the Stylesheets, change the + ``.less`` files from which the CSS is compiled. + * Make sure you do not add dead code (e.g. commented out left-overs + from experiments). +6. Ensure your changes **pass the existing unit tests**. PRs that break + those cannot be accepted. +7. **Test your changes thoroughly**. That also means testing with usage scenarios you don't normally use, e.g. if you only use access control, test without and vice versa. If you only test with your printer, test with the virtual printer and vice versa. State in your pull request how your tested your changes. Ideally **add unit tests** - OctoPrint severly lacks in that department, but we are trying to change that, so any new code already covered with a test suite helps a lot! -6. In your pull request's description, **state what your pull request does**, +8. In your pull request's description, **state what your pull request does**, as in, what feature does it implement, what bug does it fix. The more thoroughly you explain your intent behind the PR here, the higher the - chances it will get merged fast. -7. Important: Don't forget to **add yourself to the [AUTHORS](./AUTHORS.md) + chances it will get merged fast. There is a template provided below + that can help you here. +9. Don't forget to **add yourself to the [AUTHORS](./AUTHORS.md) file** :) +Template to use for Pull Request descriptions: + +``` +#### What does this PR do and why is it necessary? + +#### How was it tested? How can it be tested by the reviewer? + +#### Any background context you want to provide? + +#### What are the relevant tickets if any? + +#### Screenshots (if appropriate) + +#### Further notes +``` + ## What do the branches mean? There are three main branches in OctoPrint: @@ -342,6 +366,7 @@ the local version identifier to allow for an exact determination of the active c * 2016-02-10: Added information about branch structure and versioning. * 2016-02-16: Added requirement to add information from template to existing tickets as well, explained issue with "me too" red herrings. + * 2016-03-14: Some more requirements for PRs, and a PR template. ## Footnotes * [1] - If you are wondering why, the problem is that anything that you add From 398eaf58a904eabd58e5b893ba11d3a3216a3900 Mon Sep 17 00:00:00 2001 From: Andrew Malota Date: Mon, 7 Mar 2016 14:44:11 +0100 Subject: [PATCH 40/46] allow download/management of mp4 timelapse too (cherry picked from commit a00faf2) --- src/octoprint/server/__init__.py | 2 +- src/octoprint/timelapse.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/octoprint/server/__init__.py b/src/octoprint/server/__init__.py index d458f039..063f321c 100644 --- a/src/octoprint/server/__init__.py +++ b/src/octoprint/server/__init__.py @@ -370,7 +370,7 @@ class Server(): server_routes = self._router.urls + [ # various downloads - (r"/downloads/timelapse/([^/]*\.mpg)", util.tornado.LargeResponseHandler, joined_dict(dict(path=s.getBaseFolder("timelapse")), download_handler_kwargs, no_hidden_files_validator)), + (r"/downloads/timelapse/([^/]*\.mp[g4])", util.tornado.LargeResponseHandler, joined_dict(dict(path=s.getBaseFolder("timelapse")), download_handler_kwargs, no_hidden_files_validator)), (r"/downloads/files/local/(.*)", util.tornado.LargeResponseHandler, joined_dict(dict(path=s.getBaseFolder("uploads")), download_handler_kwargs, no_hidden_files_validator, additional_mime_types)), (r"/downloads/logs/([^/]*)", util.tornado.LargeResponseHandler, joined_dict(dict(path=s.getBaseFolder("logs")), download_handler_kwargs, admin_validator)), # camera snapshot diff --git a/src/octoprint/timelapse.py b/src/octoprint/timelapse.py index d635d23a..b24bd5a1 100644 --- a/src/octoprint/timelapse.py +++ b/src/octoprint/timelapse.py @@ -62,7 +62,7 @@ def get_finished_timelapses(): files = [] basedir = settings().getBaseFolder("timelapse") for osFile in os.listdir(basedir): - if not fnmatch.fnmatch(osFile, "*.mpg"): + if not fnmatch.fnmatch(osFile, "*.mp[g4]"): continue statResult = os.stat(os.path.join(basedir, osFile)) files.append({ From 5807b606f075aec45e60d8069d4c6370df0f55ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Mon, 14 Mar 2016 11:50:50 +0100 Subject: [PATCH 41/46] Added @2bitoperations to AUTHORS.md Was missing from PR #1255 --- AUTHORS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS.md b/AUTHORS.md index 37e6ae71..4a741f2d 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -59,6 +59,7 @@ date of first contribution): * ["bwgan"](https://github.com/bwgan) * [Siim Raud](https://github.com/2ndalpha) * ["geoporalis"](https://github.com/geoporalis) + * [Andrew Malota](https://github.com/2bitoperations) OctoPrint started off as a fork of [Cura](https://github.com/daid/Cura) by [Daid Braam](https://github.com/daid). Parts of its communication layer and From b5cf20a1deba846863adb78add4097107e7661f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Mon, 14 Mar 2016 11:59:02 +0100 Subject: [PATCH 42/46] Have comm layer wait until all lines are sent before disconnect Added parameter "wait" to "close" method, defaults to True. Will have the close method wait for all lines to be sent that are currently in the send queue. In case of an error, no waiting will be done. Made it necessary to correctly track task completion in the send queue, also made that change to the command queue while at it. Backported from devel, commit 7f2476e51313185e38a1ff1cc9a7055692d41e99 --- src/octoprint/util/comm.py | 199 ++++++++++++++++++++++--------------- 1 file changed, 121 insertions(+), 78 deletions(-) diff --git a/src/octoprint/util/comm.py b/src/octoprint/util/comm.py index d484f6fa..146de505 100644 --- a/src/octoprint/util/comm.py +++ b/src/octoprint/util/comm.py @@ -473,7 +473,25 @@ class MachineCom(object): ##~~ external interface - def close(self, isError = False): + def close(self, is_error=False, wait=True, *args, **kwargs): + """ + Closes the connection to the printer. + + If ``is_error`` is False, will attempt to send the ``beforePrinterDisconnected`` + gcode script. If ``is_error`` is False and ``wait`` is True, will wait + until all messages in the send queue (including the ``beforePrinterDisconnected`` + gcode script) have been sent to the printer. + + Arguments: + is_error (bool): Whether the closing takes place due to an error (True) + or not (False, default) + wait (bool): Whether to wait for all messages in the send + queue to be processed before closing (True, default) or not (False) + """ + + # legacy parameters + is_error = kwargs.get("isError", is_error) + if self._connection_closing: return self._connection_closing = True @@ -490,23 +508,33 @@ class MachineCom(object): except: pass - self._monitoring_active = False - self._send_queue_active = False + def deactivate_monitoring_and_send_queue(): + self._monitoring_active = False + self._send_queue_active = False printing = self.isPrinting() or self.isPaused() if self._serial is not None: + if not is_error and wait: + self._logger.info("Waiting for send queue to finish processing") + self._send_queue.join() + + deactivate_monitoring_and_send_queue() + try: self._serial.close() except: self._logger.exception("Error while trying to close serial port") - isError = True - if isError: + is_error = True + + if is_error: self._changeState(self.STATE_CLOSED_WITH_ERROR) else: self._changeState(self.STATE_CLOSED) + else: + deactivate_monitoring_and_send_queue() self._serial = None - if settings().get(["feature", "sdSupport"]): + if settings().getBoolean(["feature", "sdSupport"]): self._sdFileList = [] if printing: @@ -1337,25 +1365,33 @@ class MachineCom(object): # from the queue, we'll send the second (if there is one). We do not # want to get stuck here by throwing away commands. while True: - if self._command_queue.empty() or self.isStreaming(): - # no command queue or irrelevant command queue => return + if self.isStreaming(): + # command queue irrelevant return False - entry = self._command_queue.get() - if isinstance(entry, tuple): - if not len(entry) == 2: - # something with that entry is broken, ignore it and fetch - # the next one - continue - cmd, cmd_type = entry - else: - cmd = entry - cmd_type = None + try: + entry = self._command_queue.get(block=False) + except queue.Empty: + # nothing in command queue + return False - if self._sendCommand(cmd, cmd_type=cmd_type): - # we actually did add this cmd to the send queue, so let's - # return, we are done here - return True + try: + if isinstance(entry, tuple): + if not len(entry) == 2: + # something with that entry is broken, ignore it and fetch + # the next one + continue + cmd, cmd_type = entry + else: + cmd = entry + cmd_type = None + + if self._sendCommand(cmd, cmd_type=cmd_type): + # we actually did add this cmd to the send queue, so let's + # return, we are done here + return True + finally: + self._command_queue.task_done() def _detectPort(self, close): programmer = stk500v2.Stk500v2() @@ -1713,66 +1749,73 @@ class MachineCom(object): # wait until we have something in the queue entry = self._send_queue.get() - # make sure we are still active - if not self._send_queue_active: - break + try: - # fetch command and optional linenumber from queue - command, linenumber, command_type = entry + # make sure we are still active + if not self._send_queue_active: + break - # some firmwares (e.g. Smoothie) might support additional in-band communication that will not - # stick to the acknowledgement behaviour of GCODE, so we check here if we have a GCODE command - # at hand here and only clear our clear_to_send flag later if that's the case - gcode = gcode_command_for_cmd(command) + # fetch command and optional linenumber from queue + command, linenumber, command_type = entry - if linenumber is not None: - # line number predetermined - this only happens for resends, so we'll use the number and - # send directly without any processing (since that already took place on the first sending!) - self._do_send_with_checksum(command, linenumber) + # some firmwares (e.g. Smoothie) might support additional in-band communication that will not + # stick to the acknowledgement behaviour of GCODE, so we check here if we have a GCODE command + # at hand here and only clear our clear_to_send flag later if that's the case + gcode = gcode_command_for_cmd(command) - else: - # trigger "sending" phase - command, _, gcode = self._process_command_phase("sending", command, command_type, gcode=gcode) + if linenumber is not None: + # line number predetermined - this only happens for resends, so we'll use the number and + # send directly without any processing (since that already took place on the first sending!) + self._do_send_with_checksum(command, linenumber) - if command is None: - # No, we are not going to send this, that was a last-minute bail. - # However, since we already are in the send queue, our _monitor - # loop won't be triggered with the reply from this unsent command - # now, so we try to tickle the processing of any active - # command queues manually + else: + # trigger "sending" phase + command, _, gcode = self._process_command_phase("sending", command, command_type, gcode=gcode) + + if command is None: + # No, we are not going to send this, that was a last-minute bail. + # However, since we already are in the send queue, our _monitor + # loop won't be triggered with the reply from this unsent command + # now, so we try to tickle the processing of any active + # command queues manually + self._continue_sending() + + # and now let's fetch the next item from the queue + continue + + # now comes the part where we increase line numbers and send stuff - no turning back now + command_requiring_checksum = gcode is not None and gcode in self._checksum_requiring_commands + command_allowing_checksum = gcode is not None or self._sendChecksumWithUnknownCommands + checksum_enabled = self.isPrinting() or self._alwaysSendChecksum + + command_to_send = command.encode("ascii", errors="replace") + if command_requiring_checksum or (command_allowing_checksum and checksum_enabled): + self._do_increment_and_send_with_checksum(command_to_send) + else: + self._do_send_without_checksum(command_to_send) + + # trigger "sent" phase and use up one "ok" + self._process_command_phase("sent", command, command_type, gcode=gcode) + + # we only need to use up a clear if the command we just sent was either a gcode command or if we also + # require ack's for unknown commands + use_up_clear = self._unknownCommandsNeedAck + if gcode is not None: + use_up_clear = True + + if use_up_clear: + # if we need to use up a clear, do that now + self._clear_to_send.clear() + else: + # Otherwise we need to tickle the read queue - there might not be a reply + # to this command, so our _monitor loop will stay waiting until timeout. We + # definitely do not want that, so we tickle the queue manually here self._continue_sending() - # and now let's fetch the next item from the queue - continue - - # now comes the part where we increase line numbers and send stuff - no turning back now - command_requiring_checksum = gcode is not None and gcode in self._checksum_requiring_commands - command_allowing_checksum = gcode is not None or self._sendChecksumWithUnknownCommands - checksum_enabled = self.isPrinting() or self._alwaysSendChecksum - - command_to_send = command.encode("ascii", errors="replace") - if command_requiring_checksum or (command_allowing_checksum and checksum_enabled): - self._do_increment_and_send_with_checksum(command_to_send) - else: - self._do_send_without_checksum(command_to_send) - - # trigger "sent" phase and use up one "ok" - self._process_command_phase("sent", command, command_type, gcode=gcode) - - # we only need to use up a clear if the command we just sent was either a gcode command or if we also - # require ack's for unknown commands - use_up_clear = self._unknownCommandsNeedAck - if gcode is not None: - use_up_clear = True - - if use_up_clear: - # if we need to use up a clear, do that now - self._clear_to_send.clear() - else: - # Otherwise we need to tickle the read queue - there might not be a reply - # to this command, so our _monitor loop will stay waiting until timeout. We - # definitely do not want that, so we tickle the queue manually here - self._continue_sending() + finally: + # no matter _how_ we exit this block, we signal that we + # are done processing the last fetched queue entry + self._send_queue.task_done() # now we just wait for the next clear and then start again self._clear_to_send.wait() @@ -1876,13 +1919,13 @@ class MachineCom(object): self._logger.exception("Unexpected error while writing to serial port") self._log("Unexpected error while writing to serial port: %s" % (get_exception_string())) self._errorValue = get_exception_string() - self.close(True) + self.close(is_error=True) except: if not self._connection_closing: self._logger.exception("Unexpected error while writing to serial port") self._log("Unexpected error while writing to serial port: %s" % (get_exception_string())) self._errorValue = get_exception_string() - self.close(True) + self.close(is_error=True) ##~~ command handlers @@ -2016,7 +2059,7 @@ class MachineCom(object): # close to reset host state self._errorValue = "Closing serial port due to emergency stop M112." self._log(self._errorValue) - self.close(isError=True) + self.close(is_error=True) # fire the M112 event since we sent it and we're going to prevent the caller from seeing it gcode = "M112" From 6305c4d2eb98e9fe7a93350648dbdf6b8969af88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Tue, 15 Mar 2016 17:09:01 +0100 Subject: [PATCH 43/46] Timeout for comm closing, also wait for send queue on close We don't want to wait indefinitely for our send and command queues to empty, so we don't use .join but instead a busy timeout wait routine. Also fixed a couple of small issues found while preparing comm unit tests. --- src/octoprint/util/comm.py | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/src/octoprint/util/comm.py b/src/octoprint/util/comm.py index 146de505..5c25a726 100644 --- a/src/octoprint/util/comm.py +++ b/src/octoprint/util/comm.py @@ -473,7 +473,7 @@ class MachineCom(object): ##~~ external interface - def close(self, is_error=False, wait=True, *args, **kwargs): + def close(self, is_error=False, wait=True, timeout=10.0, *args, **kwargs): """ Closes the connection to the printer. @@ -515,8 +515,14 @@ class MachineCom(object): printing = self.isPrinting() or self.isPaused() if self._serial is not None: if not is_error and wait: - self._logger.info("Waiting for send queue to finish processing") - self._send_queue.join() + self._logger.info("Waiting for command and send queue to finish processing (timeout={}s)".format(timeout)) + if timeout is not None: + stop = time.time() + timeout + while (self._command_queue.unfinished_tasks or self._send_queue.unfinished_tasks) and time.time() < stop: + time.sleep(0.1) + else: + self._command_queue.join() + self._send_queue.join() deactivate_monitoring_and_send_queue() @@ -1324,22 +1330,22 @@ class MachineCom(object): """ Polls the temperature after the temperature timeout, re-enqueues itself. - If the printer is not operational, not printing from sd, busy with a long running command or heating, no poll - will be done. + If the printer is not operational, closing the connection, not printing from sd, busy with a long running + command or heating, no poll will be done. """ - if self.isOperational() and not self.isStreaming() and not self._long_running_command and not self._heating and not self._manualStreaming: + if self.isOperational() and not self._connection_closing and not self.isStreaming() and not self._long_running_command and not self._heating and not self._manualStreaming: self.sendCommand("M105", cmd_type="temperature_poll") def _poll_sd_status(self): """ Polls the sd printing status after the sd status timeout, re-enqueues itself. - If the printer is not operational, not printing from sd, busy with a long running command or heating, no poll - will be done. + If the printer is not operational, closing the connection, not printing from sd, busy with a long running + command or heating, no poll will be done. """ - if self.isOperational() and self.isSdPrinting() and not self._long_running_command and not self._heating: + if self.isOperational() and not self._connection_closing and self.isSdPrinting() and not self._long_running_command and not self._heating: self.sendCommand("M27", cmd_type="sd_status_poll") def _onConnected(self): @@ -2390,6 +2396,9 @@ def process_gcode_line(line, offsets=None, current_tool=None): return line def convert_pause_triggers(configured_triggers): + if not configured_triggers: + return dict() + triggers = { "enable": [], "disable": [], @@ -2419,6 +2428,9 @@ def convert_pause_triggers(configured_triggers): def convert_feedback_controls(configured_controls): + if not configured_controls: + return dict(), None + def preprocess_feedback_control(control, result): if "key" in control and "regex" in control and "template" in control: # key is always the md5sum of the regex From 69dca5b58c2dd061f4ae94e4e359f646ec9d4687 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Wed, 16 Mar 2016 09:22:10 +0100 Subject: [PATCH 44/46] Updated german translation --- .../translations/de/LC_MESSAGES/messages.mo | Bin 60943 -> 62505 bytes .../translations/de/LC_MESSAGES/messages.po | 77 +++++++++++---- translations/de/LC_MESSAGES/messages.mo | Bin 60943 -> 62505 bytes translations/de/LC_MESSAGES/messages.po | 77 +++++++++++---- translations/messages.pot | 90 +++++++++++++----- 5 files changed, 182 insertions(+), 62 deletions(-) diff --git a/src/octoprint/translations/de/LC_MESSAGES/messages.mo b/src/octoprint/translations/de/LC_MESSAGES/messages.mo index 22c641e2703e0d8dead063dac51fc227197db550..dd2b7896b27449b4de382c9fc794ba45f5e9216f 100644 GIT binary patch delta 11481 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 diff --git a/src/octoprint/translations/de/LC_MESSAGES/messages.po b/src/octoprint/translations/de/LC_MESSAGES/messages.po index edeec609..6a96ee7d 100644 --- a/src/octoprint/translations/de/LC_MESSAGES/messages.po +++ b/src/octoprint/translations/de/LC_MESSAGES/messages.po @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: OctoPrint\n" "Report-Msgid-Bugs-To: i18n@octoprint.org\n" -"POT-Creation-Date: 2016-02-10 11:12+0100\n" -"PO-Revision-Date: 2016-02-10 11:25+0100\n" +"POT-Creation-Date: 2016-03-16 09:14+0100\n" +"PO-Revision-Date: 2016-03-16 09:21+0100\n" "Last-Translator: Gina Häußge \n" "Language: de\n" "Language-Team: German (http://www.transifex.com/projects/p/octoprint/language/de/)\n" @@ -521,6 +521,7 @@ msgstr "Das sieht nicht aus wie ein valides Pluginarchiv. Valide Pluginarchive s #: src/octoprint/plugins/pluginmanager/templates/pluginmanager_settings.jinja2:182 #: src/octoprint/plugins/softwareupdate/templates/softwareupdate_settings.jinja2:63 +#: src/octoprint/templates/dialogs/settings/serialconnection.jinja2:96 #: src/octoprint/templates/tabs/terminal.jinja2:27 msgid "Advanced options" msgstr "Erweiterte Optionen" @@ -1041,18 +1042,18 @@ msgstr "%(local)s nach %(remote)s gestreamt, dauerte %(time).2f Sekunden" #: src/octoprint/static/js/app/dataupdater.js:352 #: src/octoprint/static/js/app/dataupdater.js:360 -msgid "Unhandled firmware error" -msgstr "Unbehandelter Fehler der Firmware" +msgid "Unhandled communication error" +msgstr "Unbehandelter Kommunikationsfehler" #: src/octoprint/static/js/app/dataupdater.js:353 #, python-format -msgid "The firmware reported an unhandled error. Due to that the ongoing print job was cancelled. Error: %(firmwareError)s" -msgstr "Die Firmware hat einen durch OctoPrint unbehandelten Fehler gemeldet. Daher wurder der laufende Druckauftrag abgebrochen. Fehler: %(firmwareError)s" +msgid "There was an unhandled error while talking to the printer. Due to that the ongoing print job was cancelled. Error: %(firmwareError)s" +msgstr "Es gab einen unbehandelten Fehler bei der Kommunikation mit dem Drucker. Daher wurder der laufende Druckauftrag abgebrochen. Fehler: %(firmwareError)s" #: src/octoprint/static/js/app/dataupdater.js:361 #, python-format -msgid "The firmware reported an unhandled error. Due to that OctoPrint disconnected. Error: %(error)s" -msgstr "Die Firmware hat einen durch OctoPrint unbehandelten Fehler gemeldet. Daher hat OctoPrint die Verbindung getrennt. Fehler: %(error)s" +msgid "The was an unhandled error while talking to the printer. Due to that OctoPrint disconnected. Error: %(error)s" +msgstr "Es gab einen unbehandelten Fehler bei der Kommunikation mit dem Drucker. Daher hat OctoPrint die Verbindung getrennt. Fehler: %(error)s" #: src/octoprint/static/js/app/helpers.js:385 #, python-format @@ -1923,7 +1924,7 @@ msgstr "Falls der freie Plattenplatz unter diese Schwellwerte fallen sollte wird #: src/octoprint/templates/dialogs/settings/folders.jinja2:47 #: src/octoprint/templates/dialogs/settings/serialconnection.jinja2:69 -#: src/octoprint/templates/dialogs/settings/serialconnection.jinja2:97 +#: src/octoprint/templates/dialogs/settings/serialconnection.jinja2:90 #: src/octoprint/templates/tabs/gcodeviewer.jinja2:66 #: src/octoprint/templates/tabs/timelapse.jinja2:13 msgid "Warning" @@ -2132,30 +2133,66 @@ msgid "Log communication to serial.log (might negatively impact performance)" msgstr "Logge die Kommunikation in das serial.log (kann die Performance negativ beeinflussen)" #: src/octoprint/templates/dialogs/settings/serialconnection.jinja2:74 -msgid "Long running commands" -msgstr "Lang laufende Befehle" - -#: src/octoprint/templates/dialogs/settings/serialconnection.jinja2:77 -msgid "Use this to specify the commands known to take a long time to complete without output from your printer and hence might cause timeout issues. Just the G or M code, comma separated." -msgstr "Nutze diese Option, um solche Befehle zu definieren, von denen Du weißt, dass sie eine längere Zeit lang laufen, währenddessen keinen Output produzieren und daher Timeoutprobleme verursachen könnten. Nur den G- oder M-Code, kommasepariert." - -#: src/octoprint/templates/dialogs/settings/serialconnection.jinja2:81 msgid "Additional serial ports" msgstr "Zusätzliche serielle Ports" -#: src/octoprint/templates/dialogs/settings/serialconnection.jinja2:84 +#: src/octoprint/templates/dialogs/settings/serialconnection.jinja2:77 #, python-format msgid "Use this to define additional glob patterns matching serial ports to list for connecting against, e.g. /dev/ttyAMA*. One entry per line." msgstr "Nutze diese Einstellung um zusätzliche glob patterns zu konfigurieren, die auf serielle Ports deines Druckers matchen, z.B. /dev/ttyAMA*. Ein Eintrag pro Zeile." -#: src/octoprint/templates/dialogs/settings/serialconnection.jinja2:90 +#: src/octoprint/templates/dialogs/settings/serialconnection.jinja2:83 msgid "Not only cancel ongoing prints but also disconnect on unhandled errors from the firmware." msgstr "Bei unbehalten Firmwarefehlern nicht nur den Druckauftrag abbrechen, sondern auch die Verbindung zum Drucker trennen." -#: src/octoprint/templates/dialogs/settings/serialconnection.jinja2:97 +#: src/octoprint/templates/dialogs/settings/serialconnection.jinja2:90 msgid "Ignore any unhandled errors from the firmware. Only use this if your firmware sends stuff prefixed with \"Error\" that is not an actual error. Might mask printer issues, be careful!" msgstr "Alle unbehalten Firmwarefehler ignorieren. Nur nutzen wenn Deine Firmware Dinge mit \"Error\" sendet die nicht wirklich Fehler sind. Könnte Druckerprobleme maskieren, vorsicht!" +#: src/octoprint/templates/dialogs/settings/serialconnection.jinja2:98 +msgid "Command to send to the firmware on first handshake attempt." +msgstr "Kommando, das als erster Handshakeversuch an die Firmware gesendet werden soll" + +#: src/octoprint/templates/dialogs/settings/serialconnection.jinja2:99 +msgid "\"Hello\" command" +msgstr "\"Hallo\"-Befehl" + +#: src/octoprint/templates/dialogs/settings/serialconnection.jinja2:102 +msgid "Use this to specify a different command than the default M110 to send to the printer on initial connection to trigger a communication handshake." +msgstr "Nutze diese Einstellung um einen anderen Befehl als M110 beim initialen Verbindungsaufbau zum drucker zu senden." + +#: src/octoprint/templates/dialogs/settings/serialconnection.jinja2:105 +msgid "Commands that are know to run long and hence should suppress communication timeouts from being triggered." +msgstr "Befehle, von denen bekannt ist, dass sie lang zur Ausführung benötigen und daher das Auslösen von Kommunikationstimeouts unterdrücken sollten." + +#: src/octoprint/templates/dialogs/settings/serialconnection.jinja2:106 +msgid "Long running commands" +msgstr "Lang laufende Befehle" + +#: src/octoprint/templates/dialogs/settings/serialconnection.jinja2:109 +msgid "Use this to specify the commands known to take a long time to complete without output from your printer and hence might cause timeout issues. Just the G or M code, comma separated." +msgstr "Nutze diese Option, um solche Befehle zu definieren, von denen Du weißt, dass sie eine längere Zeit lang laufen, währenddessen keinen Output produzieren und daher Timeoutprobleme verursachen könnten. Nur den G- oder M-Code, kommasepariert." + +#: src/octoprint/templates/dialogs/settings/serialconnection.jinja2:112 +msgid "Commands that always require a line number and checksum to be sent with them." +msgstr "Befehle, die immer mit einer Prüfsumme und Zeilennummer gesendet werden müssen." + +#: src/octoprint/templates/dialogs/settings/serialconnection.jinja2:113 +msgid "Commands that always require a checksum" +msgstr "Befehle, die immer eine Prüfsumme benötigen" + +#: src/octoprint/templates/dialogs/settings/serialconnection.jinja2:116 +msgid "Use this to specify which commands always need to be sent with a checksum. Comma separated list." +msgstr "Nutze diese Einstellung um Befehle zu spezifizieren, die immer mit Prüfsumme gesendet werden müssen. Komma-separierte Liste." + +#: src/octoprint/templates/dialogs/settings/serialconnection.jinja2:121 +msgid "Generate additional ok for M29" +msgstr "Zusätzliches ok für M29 generieren" + +#: src/octoprint/templates/dialogs/settings/serialconnection.jinja2:121 +msgid "Most Marlin < v1.1.0" +msgstr "Viele Marlin < v1.1.0" + #: src/octoprint/templates/dialogs/settings/server.jinja2:2 msgid "Commands" msgstr "Befehle" diff --git a/translations/de/LC_MESSAGES/messages.mo b/translations/de/LC_MESSAGES/messages.mo index 22c641e2703e0d8dead063dac51fc227197db550..dd2b7896b27449b4de382c9fc794ba45f5e9216f 100644 GIT binary patch delta 11481 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 diff --git a/translations/de/LC_MESSAGES/messages.po b/translations/de/LC_MESSAGES/messages.po index edeec609..6a96ee7d 100644 --- a/translations/de/LC_MESSAGES/messages.po +++ b/translations/de/LC_MESSAGES/messages.po @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: OctoPrint\n" "Report-Msgid-Bugs-To: i18n@octoprint.org\n" -"POT-Creation-Date: 2016-02-10 11:12+0100\n" -"PO-Revision-Date: 2016-02-10 11:25+0100\n" +"POT-Creation-Date: 2016-03-16 09:14+0100\n" +"PO-Revision-Date: 2016-03-16 09:21+0100\n" "Last-Translator: Gina Häußge \n" "Language: de\n" "Language-Team: German (http://www.transifex.com/projects/p/octoprint/language/de/)\n" @@ -521,6 +521,7 @@ msgstr "Das sieht nicht aus wie ein valides Pluginarchiv. Valide Pluginarchive s #: src/octoprint/plugins/pluginmanager/templates/pluginmanager_settings.jinja2:182 #: src/octoprint/plugins/softwareupdate/templates/softwareupdate_settings.jinja2:63 +#: src/octoprint/templates/dialogs/settings/serialconnection.jinja2:96 #: src/octoprint/templates/tabs/terminal.jinja2:27 msgid "Advanced options" msgstr "Erweiterte Optionen" @@ -1041,18 +1042,18 @@ msgstr "%(local)s nach %(remote)s gestreamt, dauerte %(time).2f Sekunden" #: src/octoprint/static/js/app/dataupdater.js:352 #: src/octoprint/static/js/app/dataupdater.js:360 -msgid "Unhandled firmware error" -msgstr "Unbehandelter Fehler der Firmware" +msgid "Unhandled communication error" +msgstr "Unbehandelter Kommunikationsfehler" #: src/octoprint/static/js/app/dataupdater.js:353 #, python-format -msgid "The firmware reported an unhandled error. Due to that the ongoing print job was cancelled. Error: %(firmwareError)s" -msgstr "Die Firmware hat einen durch OctoPrint unbehandelten Fehler gemeldet. Daher wurder der laufende Druckauftrag abgebrochen. Fehler: %(firmwareError)s" +msgid "There was an unhandled error while talking to the printer. Due to that the ongoing print job was cancelled. Error: %(firmwareError)s" +msgstr "Es gab einen unbehandelten Fehler bei der Kommunikation mit dem Drucker. Daher wurder der laufende Druckauftrag abgebrochen. Fehler: %(firmwareError)s" #: src/octoprint/static/js/app/dataupdater.js:361 #, python-format -msgid "The firmware reported an unhandled error. Due to that OctoPrint disconnected. Error: %(error)s" -msgstr "Die Firmware hat einen durch OctoPrint unbehandelten Fehler gemeldet. Daher hat OctoPrint die Verbindung getrennt. Fehler: %(error)s" +msgid "The was an unhandled error while talking to the printer. Due to that OctoPrint disconnected. Error: %(error)s" +msgstr "Es gab einen unbehandelten Fehler bei der Kommunikation mit dem Drucker. Daher hat OctoPrint die Verbindung getrennt. Fehler: %(error)s" #: src/octoprint/static/js/app/helpers.js:385 #, python-format @@ -1923,7 +1924,7 @@ msgstr "Falls der freie Plattenplatz unter diese Schwellwerte fallen sollte wird #: src/octoprint/templates/dialogs/settings/folders.jinja2:47 #: src/octoprint/templates/dialogs/settings/serialconnection.jinja2:69 -#: src/octoprint/templates/dialogs/settings/serialconnection.jinja2:97 +#: src/octoprint/templates/dialogs/settings/serialconnection.jinja2:90 #: src/octoprint/templates/tabs/gcodeviewer.jinja2:66 #: src/octoprint/templates/tabs/timelapse.jinja2:13 msgid "Warning" @@ -2132,30 +2133,66 @@ msgid "Log communication to serial.log (might negatively impact performance)" msgstr "Logge die Kommunikation in das serial.log (kann die Performance negativ beeinflussen)" #: src/octoprint/templates/dialogs/settings/serialconnection.jinja2:74 -msgid "Long running commands" -msgstr "Lang laufende Befehle" - -#: src/octoprint/templates/dialogs/settings/serialconnection.jinja2:77 -msgid "Use this to specify the commands known to take a long time to complete without output from your printer and hence might cause timeout issues. Just the G or M code, comma separated." -msgstr "Nutze diese Option, um solche Befehle zu definieren, von denen Du weißt, dass sie eine längere Zeit lang laufen, währenddessen keinen Output produzieren und daher Timeoutprobleme verursachen könnten. Nur den G- oder M-Code, kommasepariert." - -#: src/octoprint/templates/dialogs/settings/serialconnection.jinja2:81 msgid "Additional serial ports" msgstr "Zusätzliche serielle Ports" -#: src/octoprint/templates/dialogs/settings/serialconnection.jinja2:84 +#: src/octoprint/templates/dialogs/settings/serialconnection.jinja2:77 #, python-format msgid "Use this to define additional glob patterns matching serial ports to list for connecting against, e.g. /dev/ttyAMA*. One entry per line." msgstr "Nutze diese Einstellung um zusätzliche glob patterns zu konfigurieren, die auf serielle Ports deines Druckers matchen, z.B. /dev/ttyAMA*. Ein Eintrag pro Zeile." -#: src/octoprint/templates/dialogs/settings/serialconnection.jinja2:90 +#: src/octoprint/templates/dialogs/settings/serialconnection.jinja2:83 msgid "Not only cancel ongoing prints but also disconnect on unhandled errors from the firmware." msgstr "Bei unbehalten Firmwarefehlern nicht nur den Druckauftrag abbrechen, sondern auch die Verbindung zum Drucker trennen." -#: src/octoprint/templates/dialogs/settings/serialconnection.jinja2:97 +#: src/octoprint/templates/dialogs/settings/serialconnection.jinja2:90 msgid "Ignore any unhandled errors from the firmware. Only use this if your firmware sends stuff prefixed with \"Error\" that is not an actual error. Might mask printer issues, be careful!" msgstr "Alle unbehalten Firmwarefehler ignorieren. Nur nutzen wenn Deine Firmware Dinge mit \"Error\" sendet die nicht wirklich Fehler sind. Könnte Druckerprobleme maskieren, vorsicht!" +#: src/octoprint/templates/dialogs/settings/serialconnection.jinja2:98 +msgid "Command to send to the firmware on first handshake attempt." +msgstr "Kommando, das als erster Handshakeversuch an die Firmware gesendet werden soll" + +#: src/octoprint/templates/dialogs/settings/serialconnection.jinja2:99 +msgid "\"Hello\" command" +msgstr "\"Hallo\"-Befehl" + +#: src/octoprint/templates/dialogs/settings/serialconnection.jinja2:102 +msgid "Use this to specify a different command than the default M110 to send to the printer on initial connection to trigger a communication handshake." +msgstr "Nutze diese Einstellung um einen anderen Befehl als M110 beim initialen Verbindungsaufbau zum drucker zu senden." + +#: src/octoprint/templates/dialogs/settings/serialconnection.jinja2:105 +msgid "Commands that are know to run long and hence should suppress communication timeouts from being triggered." +msgstr "Befehle, von denen bekannt ist, dass sie lang zur Ausführung benötigen und daher das Auslösen von Kommunikationstimeouts unterdrücken sollten." + +#: src/octoprint/templates/dialogs/settings/serialconnection.jinja2:106 +msgid "Long running commands" +msgstr "Lang laufende Befehle" + +#: src/octoprint/templates/dialogs/settings/serialconnection.jinja2:109 +msgid "Use this to specify the commands known to take a long time to complete without output from your printer and hence might cause timeout issues. Just the G or M code, comma separated." +msgstr "Nutze diese Option, um solche Befehle zu definieren, von denen Du weißt, dass sie eine längere Zeit lang laufen, währenddessen keinen Output produzieren und daher Timeoutprobleme verursachen könnten. Nur den G- oder M-Code, kommasepariert." + +#: src/octoprint/templates/dialogs/settings/serialconnection.jinja2:112 +msgid "Commands that always require a line number and checksum to be sent with them." +msgstr "Befehle, die immer mit einer Prüfsumme und Zeilennummer gesendet werden müssen." + +#: src/octoprint/templates/dialogs/settings/serialconnection.jinja2:113 +msgid "Commands that always require a checksum" +msgstr "Befehle, die immer eine Prüfsumme benötigen" + +#: src/octoprint/templates/dialogs/settings/serialconnection.jinja2:116 +msgid "Use this to specify which commands always need to be sent with a checksum. Comma separated list." +msgstr "Nutze diese Einstellung um Befehle zu spezifizieren, die immer mit Prüfsumme gesendet werden müssen. Komma-separierte Liste." + +#: src/octoprint/templates/dialogs/settings/serialconnection.jinja2:121 +msgid "Generate additional ok for M29" +msgstr "Zusätzliches ok für M29 generieren" + +#: src/octoprint/templates/dialogs/settings/serialconnection.jinja2:121 +msgid "Most Marlin < v1.1.0" +msgstr "Viele Marlin < v1.1.0" + #: src/octoprint/templates/dialogs/settings/server.jinja2:2 msgid "Commands" msgstr "Befehle" diff --git a/translations/messages.pot b/translations/messages.pot index df0a199a..a56736bb 100644 --- a/translations/messages.pot +++ b/translations/messages.pot @@ -6,9 +6,9 @@ #, fuzzy msgid "" msgstr "" -"Project-Id-Version: OctoPrint 1.2.9.dev71+g92c65cb.dirty\n" +"Project-Id-Version: OctoPrint 1.2.10.dev50+g6305c4d\n" "Report-Msgid-Bugs-To: i18n@octoprint.org\n" -"POT-Creation-Date: 2016-02-10 11:12+0100\n" +"POT-Creation-Date: 2016-03-16 09:14+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -552,6 +552,7 @@ msgstr "" #: src/octoprint/plugins/pluginmanager/templates/pluginmanager_settings.jinja2:182 #: src/octoprint/plugins/softwareupdate/templates/softwareupdate_settings.jinja2:63 +#: src/octoprint/templates/dialogs/settings/serialconnection.jinja2:96 #: src/octoprint/templates/tabs/terminal.jinja2:27 msgid "Advanced options" msgstr "" @@ -1126,21 +1127,21 @@ msgstr "" #: src/octoprint/static/js/app/dataupdater.js:352 #: src/octoprint/static/js/app/dataupdater.js:360 -msgid "Unhandled firmware error" +msgid "Unhandled communication error" msgstr "" #: src/octoprint/static/js/app/dataupdater.js:353 #, python-format msgid "" -"The firmware reported an unhandled error. Due to that the ongoing print " -"job was cancelled. Error: %(firmwareError)s" +"There was an unhandled error while talking to the printer. Due to that " +"the ongoing print job was cancelled. Error: %(firmwareError)s" msgstr "" #: src/octoprint/static/js/app/dataupdater.js:361 #, python-format msgid "" -"The firmware reported an unhandled error. Due to that OctoPrint " -"disconnected. Error: %(error)s" +"The was an unhandled error while talking to the printer. Due to that " +"OctoPrint disconnected. Error: %(error)s" msgstr "" #: src/octoprint/static/js/app/helpers.js:385 @@ -2038,7 +2039,7 @@ msgstr "" #: src/octoprint/templates/dialogs/settings/folders.jinja2:47 #: src/octoprint/templates/dialogs/settings/serialconnection.jinja2:69 -#: src/octoprint/templates/dialogs/settings/serialconnection.jinja2:97 +#: src/octoprint/templates/dialogs/settings/serialconnection.jinja2:90 #: src/octoprint/templates/tabs/gcodeviewer.jinja2:66 #: src/octoprint/templates/tabs/timelapse.jinja2:13 msgid "Warning" @@ -2266,21 +2267,10 @@ msgid "Log communication to serial.log (might negatively impact performance)" msgstr "" #: src/octoprint/templates/dialogs/settings/serialconnection.jinja2:74 -msgid "Long running commands" -msgstr "" - -#: src/octoprint/templates/dialogs/settings/serialconnection.jinja2:77 -msgid "" -"Use this to specify the commands known to take a long time to complete " -"without output from your printer and hence might cause timeout issues. " -"Just the G or M code, comma separated." -msgstr "" - -#: src/octoprint/templates/dialogs/settings/serialconnection.jinja2:81 msgid "Additional serial ports" msgstr "" -#: src/octoprint/templates/dialogs/settings/serialconnection.jinja2:84 +#: src/octoprint/templates/dialogs/settings/serialconnection.jinja2:77 #, python-format msgid "" "Use this to define additional glob patterns" @@ -2288,19 +2278,75 @@ msgid "" "/dev/ttyAMA*. One entry per line." msgstr "" -#: src/octoprint/templates/dialogs/settings/serialconnection.jinja2:90 +#: src/octoprint/templates/dialogs/settings/serialconnection.jinja2:83 msgid "" "Not only cancel ongoing prints but also disconnect on unhandled errors " "from the firmware." msgstr "" -#: src/octoprint/templates/dialogs/settings/serialconnection.jinja2:97 +#: src/octoprint/templates/dialogs/settings/serialconnection.jinja2:90 msgid "" "Ignore any unhandled errors from the firmware. Only use this if your " "firmware sends stuff prefixed with \"Error\" that is not an actual error." " Might mask printer issues, be careful!" msgstr "" +#: src/octoprint/templates/dialogs/settings/serialconnection.jinja2:98 +msgid "Command to send to the firmware on first handshake attempt." +msgstr "" + +#: src/octoprint/templates/dialogs/settings/serialconnection.jinja2:99 +msgid "\"Hello\" command" +msgstr "" + +#: src/octoprint/templates/dialogs/settings/serialconnection.jinja2:102 +msgid "" +"Use this to specify a different command than the default " +"M110 to send to the printer on initial connection to trigger" +" a communication handshake." +msgstr "" + +#: src/octoprint/templates/dialogs/settings/serialconnection.jinja2:105 +msgid "" +"Commands that are know to run long and hence should suppress " +"communication timeouts from being triggered." +msgstr "" + +#: src/octoprint/templates/dialogs/settings/serialconnection.jinja2:106 +msgid "Long running commands" +msgstr "" + +#: src/octoprint/templates/dialogs/settings/serialconnection.jinja2:109 +msgid "" +"Use this to specify the commands known to take a long time to complete " +"without output from your printer and hence might cause timeout issues. " +"Just the G or M code, comma separated." +msgstr "" + +#: src/octoprint/templates/dialogs/settings/serialconnection.jinja2:112 +msgid "" +"Commands that always require a line number and checksum to be sent with " +"them." +msgstr "" + +#: src/octoprint/templates/dialogs/settings/serialconnection.jinja2:113 +msgid "Commands that always require a checksum" +msgstr "" + +#: src/octoprint/templates/dialogs/settings/serialconnection.jinja2:116 +msgid "" +"Use this to specify which commands always need to be " +"sent with a checksum. Comma separated list." +msgstr "" + +#: src/octoprint/templates/dialogs/settings/serialconnection.jinja2:121 +msgid "Generate additional ok for M29" +msgstr "" + +#: src/octoprint/templates/dialogs/settings/serialconnection.jinja2:121 +msgid "Most Marlin < v1.1.0" +msgstr "" + #: src/octoprint/templates/dialogs/settings/server.jinja2:2 msgid "Commands" msgstr "" From 692166f067329cd3d6fdc84389e0dd76184c5e0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Wed, 16 Mar 2016 09:38:58 +0100 Subject: [PATCH 45/46] Preparing release of 1.2.10 --- CHANGELOG.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4866ca00..170ae0ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,33 @@ # OctoPrint Changelog +## 1.2.10 (2016-03-16) + +### Improvements + + * Improved performance of console output during plugin installation/deinstallation + * Slight performance improvements in the communication layer + * Log small log excerpt to `octoprint.log` upon encountering a communication error. + * Changed wording in "firmware error" notifications to better reflect that there was an error while communicating with the printer, since the error condition can also be triggered by serial errors while trying to establish a connection to the printer or when already connected. + * Support downloading ".mp4" timelapse files. You'll need a [custom wrapper script for timelapse rendering](https://github.com/guysoft/OctoPi/issues/184) for this to be relevant to you. See also [#1255](https://github.com/foosel/OctoPrint/pull/1255) + * The communication layer will now wait up to 10s after clicking disconnect in order to send any left-over lines from its buffers. + * Moved less commonly used configuration options in Serial settings into "Advanced options" roll-out. + +### Bug Fixes + + * [#1224](https://github.com/foosel/OctoPrint/issues/1224) - Fixed an issue introduced by the fix for [#1196](https://github.com/foosel/OctoPrint/issues/1196) that had the "Upload to SD" button stop working correctly. + * [#1226](https://github.com/foosel/OctoPrint/issues/1226) - Fixed an issue causing an error on disconnect after or cancelling of an SD print, caused by the unsuccessful attempt to record print recovery data for the file on the printer's SD card. + * [#1268](https://github.com/foosel/OctoPrint/issues/1268) - Only add bed temperature line to temperature management specific start gcode in CuraEngine invocation if a bed temperature is actually set in the slicing profile. + * [#1271](https://github.com/foosel/OctoPrint/issues/1271) - If a communication timeout occurs during an active resend request, OctoPrint will now not send an `M105` with an increased line number anymore but repeat the last resent command instead. + * [#1272](https://github.com/foosel/OctoPrint/issues/1272) - Don't add an extra `ok` for `M28` response. + * [#1273](https://github.com/foosel/OctoPrint/issues/1273) - Add an extra `ok` for `M29` response, but only if configured such in "Settings" > "Serial" > "Advanced options" > "Generate additional ok for M29" + * [#1274](https://github.com/foosel/OctoPrint/issues/1274) - Trigger `M20` only once after finishing uploading to SD + * [#1275](https://github.com/foosel/OctoPrint/issues/1275) - Prevent `M105` "cascade" due to communication timeouts + * Fixed wrong tracking of extruder heating up for `M109 Tn` commands in multi-extruder setups. + * Fixed start of SD file uploads not sending an `M110`. + * Fixed job data not being reset when disconnecting while printing. + +([Commits](https://github.com/foosel/OctoPrint/compare/1.2.9...1.2.10)) + ## 1.2.9 (2016-02-10) ### Improvements From a229cbfd49985611a2a2a72e59043d82fa1333c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Wed, 16 Mar 2016 10:55:08 +0100 Subject: [PATCH 46/46] maintenance branch is now 1.2.11.dev --- .versioneer-lookup | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.versioneer-lookup b/.versioneer-lookup index 3172981e..148123e0 100644 --- a/.versioneer-lookup +++ b/.versioneer-lookup @@ -14,11 +14,11 @@ master HEAD \(detached.* -# maintenance is currently the branch for preparation of maintenance release 1.2.10 +# maintenance is currently the branch for preparation of maintenance release 1.2.11 # so are any fix/... and improve/... branches -maintenance 1.2.10 abe68adac8f465a31bf4f3f3933190c1fc242cda pep440-dev -fix/.* 1.2.10 abe68adac8f465a31bf4f3f3933190c1fc242cda pep440-dev -improve/.* 1.2.10 abe68adac8f465a31bf4f3f3933190c1fc242cda pep440-dev +maintenance 1.2.11 692166f067329cd3d6fdc84389e0dd76184c5e0c pep440-dev +fix/.* 1.2.11 692166f067329cd3d6fdc84389e0dd76184c5e0c pep440-dev +improve/.* 1.2.11 692166f067329cd3d6fdc84389e0dd76184c5e0c pep440-dev # every other branch is a development branch and thus gets resolved to 1.3.0-dev for now .* 1.3.0 198d3450d94be1a2 pep440-dev