From 5eb70fa57de40ca09b1c0ad5d1c9746ea40c3395 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Wed, 20 Nov 2013 22:00:47 +0100 Subject: [PATCH 01/14] Properly calculate time deltas (forgot the days) (cherry picked from commit 84012d1) --- src/octoprint/util/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/octoprint/util/__init__.py b/src/octoprint/util/__init__.py index 79de1d60..3f5af26b 100644 --- a/src/octoprint/util/__init__.py +++ b/src/octoprint/util/__init__.py @@ -30,7 +30,7 @@ def isAllowedFile(filename, extensions): def getFormattedTimeDelta(d): if d is None: return None - hours = d.seconds // 3600 + hours = d.days * 24 + d.seconds // 3600 minutes = (d.seconds % 3600) // 60 seconds = d.seconds % 60 return "%02d:%02d:%02d" % (hours, minutes, seconds) From 3ddfc93c1bc3756825a91fcd124118c4fc3732c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Sun, 24 Nov 2013 13:12:02 +0100 Subject: [PATCH 02/14] Use G28 for homing (G1 was a copy-paste-error) Fixes #314 (cherry picked from commit 5b8126a) --- src/octoprint/server/ajax/control.py | 32 ++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/octoprint/server/ajax/control.py b/src/octoprint/server/ajax/control.py index efc7e99e..2b38835f 100644 --- a/src/octoprint/server/ajax/control.py +++ b/src/octoprint/server/ajax/control.py @@ -145,6 +145,38 @@ def jog(): length = request.values["extrude"] printer.commands(["G91", "G1 E%s F%d" % (length, movementSpeedE), "G90"]) + movementSpeed = settings().get(["printerParameters", "movementSpeed", ["x", "y", "z"]], asdict=True) + + valid_axes = ["x", "y", "z"] + ##~~ jog command + if command == "jog": + # validate all jog instructions, make sure that the values are numbers + validated_values = {} + for axis in valid_axes: + if axis in data: + value = data[axis] + if not isinstance(value, (int, long, float)): + return make_response("Not a number for axis %s: %r" % (axis, value), 400) + validated_values[axis] = value + + # execute the jog commands + for axis, value in validated_values.iteritems(): + # TODO make this a generic method call (printer.jog(axis, value)) to get rid of gcode here + printer.commands(["G91", "G1 %s%.4f F%d" % (axis.upper(), value, movementSpeed[axis]), "G90"]) + + ##~~ home command + elif command == "home": + validated_values = [] + axes = data["axes"] + for axis in axes: + if not axis in valid_axes: + return make_response("Invalid axis: %s" % axis, 400) + validated_values.append(axis) + + # execute the home command + # TODO make this a generic method call (printer.home(axis, ...)) to get rid of gcode here + printer.commands(["G91", "G28 %s" % " ".join(map(lambda x: "%s0" % x.upper(), validated_values)), "G90"]) + return jsonify(SUCCESS) From 722b03dca8715b8439eca5ca402e66a167444ae1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Sun, 24 Nov 2013 16:55:14 +0100 Subject: [PATCH 03/14] Disable webcam stream when control tab is not in focus. Fixes #316 (cherry picked from commit 1b4a7c7) --- src/octoprint/static/js/app/main.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/octoprint/static/js/app/main.js b/src/octoprint/static/js/app/main.js index e4cd786e..39e503a1 100644 --- a/src/octoprint/static/js/app/main.js +++ b/src/octoprint/static/js/app/main.js @@ -51,6 +51,17 @@ $(function() { terminalViewModel.updateOutput(); }); + $('#tabs a[data-toggle="tab"]').on('show', function (e) { + var current = e.target; + var previous = e.relatedTarget; + + if (current.hash == "#control") { + $("#webcam_image").attr("src", CONFIG_WEBCAM_STREAM + "?" + new Date().getTime()); + } else if (previous.hash == "#control") { + $("#webcam_image").attr("src", "#"); + } + }); + //~~ Gcode upload function gcode_upload_done(e, data) { From df269eb4f7ec519e4966818bc9f458da06219fbf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Sun, 24 Nov 2013 17:33:05 +0100 Subject: [PATCH 04/14] More intelligent handling of disabling and re-enabling the webcam stream. See #314 (cherry picked from commit 1dd0aba) --- src/octoprint/static/js/app/dataupdater.js | 5 ++++- src/octoprint/static/js/app/main.js | 14 ++++++++++++-- src/octoprint/templates/index.jinja2 | 2 +- 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/octoprint/static/js/app/dataupdater.js b/src/octoprint/static/js/app/dataupdater.js index 0fd509d9..43fdec4e 100644 --- a/src/octoprint/static/js/app/dataupdater.js +++ b/src/octoprint/static/js/app/dataupdater.js @@ -40,9 +40,12 @@ function DataUpdater(loginStateViewModel, connectionViewModel, printerStateViewM if ($("#offline_overlay").is(":visible")) { $("#offline_overlay").hide(); self.timelapseViewModel.requestData(); - $("#webcam_image").attr("src", CONFIG_WEBCAM_STREAM + "?" + new Date().getTime()); self.loginStateViewModel.requestData(); self.gcodeFilesViewModel.requestData(); + + if ($('#tabs li[class="active"] a').attr("href") == "#control") { + $("#webcam_image").attr("src", CONFIG_WEBCAM_STREAM + "?" + new Date().getTime()); + } } } diff --git a/src/octoprint/static/js/app/main.js b/src/octoprint/static/js/app/main.js index 39e503a1..5779bbf4 100644 --- a/src/octoprint/static/js/app/main.js +++ b/src/octoprint/static/js/app/main.js @@ -51,14 +51,24 @@ $(function() { terminalViewModel.updateOutput(); }); + var webcamDisableTimeout; $('#tabs a[data-toggle="tab"]').on('show', function (e) { var current = e.target; var previous = e.relatedTarget; if (current.hash == "#control") { - $("#webcam_image").attr("src", CONFIG_WEBCAM_STREAM + "?" + new Date().getTime()); + clearTimeout(webcamDisableTimeout); + var webcamImage = $("#webcam_image"); + var currentSrc = webcamImage.attr("src"); + if (currentSrc === undefined || currentSrc.trim() == "") { + webcamImage.attr("src", CONFIG_WEBCAM_STREAM + "?" + new Date().getTime()); + } } else if (previous.hash == "#control") { - $("#webcam_image").attr("src", "#"); + // only disable webcam stream if tab is out of focus for more than 5s, otherwise we might cause + // more load by the constant connection creation than by the actual webcam stream + webcamDisableTimeout = setTimeout(function() { + $("#webcam_image").attr("src", ""); + }, 5000); } }); diff --git a/src/octoprint/templates/index.jinja2 b/src/octoprint/templates/index.jinja2 index c7d25b2a..b5573b9e 100644 --- a/src/octoprint/templates/index.jinja2 +++ b/src/octoprint/templates/index.jinja2 @@ -339,7 +339,7 @@
{% if webcamStream %}
- +
{% endif %} From 64084bb628381af5a2557bedadcda40b5b055ac6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Sat, 21 Dec 2013 15:51:32 +0100 Subject: [PATCH 05/14] A couple of manual merges... --- src/octoprint/gcodefiles.py | 29 +++++--- src/octoprint/printer.py | 33 ++++----- src/octoprint/server/ajax/gcodefiles.py | 67 ++++++++++++------- src/octoprint/static/js/app/main.js | 7 +- .../static/js/app/viewmodels/gcodefiles.js | 4 +- 5 files changed, 80 insertions(+), 60 deletions(-) diff --git a/src/octoprint/gcodefiles.py b/src/octoprint/gcodefiles.py index 42e1bdf8..52ac2cf9 100644 --- a/src/octoprint/gcodefiles.py +++ b/src/octoprint/gcodefiles.py @@ -14,6 +14,7 @@ import octoprint.util.gcodeInterpreter as gcodeInterpreter from octoprint.settings import settings from octoprint.events import eventManager +from octoprint.filemanager.destinations import FileDestinations from werkzeug.utils import secure_filename @@ -165,9 +166,15 @@ class GcodeManager: #~~ file handling - def addFile(self, file, destination): - from octoprint.filemanager.destinations import FileDestinations + def addFile(self, file, destination, uploadCallback=None): + """ + Adds the given file for the given destination to the systems. Takes care of slicing if enabled and + necessary. + If the file's processing won't be finished directly with the return from this method but happen + asynchronously in the background (e.g. due to slicing), returns a tuple containing the just added file's + filename and False. Otherwise returns a tuple (filename, True). + """ if not file or not destination: return None, True @@ -183,11 +190,10 @@ class GcodeManager: file.save(absolutePath) if gcode: - return self.processGcode(absolutePath), True + return self.processGcode(absolutePath, destination, uploadCallback), True else: - local = (destination == FileDestinations.LOCAL) - if curaEnabled and isSTLFileName(filename) and local: - self.processStl(absolutePath) + if curaEnabled and isSTLFileName(filename): + self.processStl(absolutePath, destination, uploadCallback) return filename, False @@ -202,7 +208,7 @@ class GcodeManager: return self._getBasicFilename(absolutePath) - def processStl(self, absolutePath): + def processStl(self, absolutePath, destination, uploadCallback=None): from octoprint.slicers.cura import CuraFactory cura = CuraFactory.create_slicer() @@ -210,6 +216,7 @@ class GcodeManager: config = self._settings.get(["cura", "config"]) slicingStart = time.time() + def stlProcessed(stlPath, gcodePath, error=None): if error: eventManager().fire("SlicingFailed", {"stl": self._getBasicFilename(stlPath), "gcode": self._getBasicFilename(gcodePath), "reason": error}) @@ -218,13 +225,13 @@ class GcodeManager: else: slicingStop = time.time() eventManager().fire("SlicingDone", {"stl": self._getBasicFilename(stlPath), "gcode": self._getBasicFilename(gcodePath), "time": "%.2f" % (slicingStop - slicingStart)}) - self.processGcode(gcodePath) + self.processGcode(gcodePath, destination, uploadCallback) eventManager().fire("SlicingStarted", {"stl": self._getBasicFilename(absolutePath), "gcode": self._getBasicFilename(gcodePath)}) cura.process_file(config, gcodePath, absolutePath, stlProcessed, [absolutePath, gcodePath]) - def processGcode(self, absolutePath): + def processGcode(self, absolutePath, destination, uploadCallback=None): if absolutePath is None: return None @@ -238,7 +245,9 @@ class GcodeManager: self._metadataAnalyzer.addFileToQueue(os.path.basename(absolutePath)) - return filename + if uploadCallback is not None: + uploadCallback(filename, absolutePath, destination) + return filename def getFutureFilename(self, file): if not file: diff --git a/src/octoprint/printer.py b/src/octoprint/printer.py index 99ceb4fb..1418d66a 100644 --- a/src/octoprint/printer.py +++ b/src/octoprint/printer.py @@ -17,6 +17,8 @@ import octoprint.util as util from octoprint.settings import settings from octoprint.events import eventManager +from octoprint.filemanager.destinations import FileDestinations + def getConnectionOptions(): """ Retrieves the available ports, baudrates, prefered port and baudrate for connecting to the printer. @@ -71,6 +73,8 @@ class Printer(): self._sdPrinting = False self._sdStreaming = False self._sdFilelistAvailable = threading.Event() + self._sdRemoteName = None + self._streamingFinishedCallback = None self._selectedFile = None @@ -458,6 +462,10 @@ class Printer(): def mcFileTransferDone(self): self._sdStreaming = False + if self._streamingFinishedCallback is not None: + self._streamingFinishedCallback(self._sdRemoteName, FileDestinations.SDCARD) + + self._sdRemoteName = None self._setCurrentZ(None) self._setJobData(None, None, None) self._setProgressData(None, None, None, None) @@ -473,35 +481,18 @@ class Printer(): return [] return self._comm.getSdFiles() - def addSdFile(self, filename, absolutePath): - from octoprint.gcodefiles import isGcodeFileName - from octoprint.gcodefiles import isSTLFileName - + def addSdFile(self, filename, absolutePath, streamingFinishedCallback): if not self._comm or self._comm.isBusy() or not self._comm.isSdReady(): logging.error("No connection to printer or printer is busy") return - if isGcodeFileName(filename): - self.streamSdFile(filename, absolutePath) - - if isSTLFileName(filename): - gcodePath = util.genGcodeFileName(absolutePath) - gcodeFileName = util.genGcodeFileName(filename) - callBackArgs = [gcodeFileName, gcodePath] - callBack = self.streamSdFile - - self._gcodeManager.processStl( - absolutePath, callBack, callBackArgs) - - def streamSdFile(self, filename, path): - if not self._comm or self._comm.isBusy() or not self._comm.isSdReady(): - return + self._streamingFinishedCallback = streamingFinishedCallback self.refreshSdFiles(blocking=True) existingSdFiles = self._comm.getSdFiles() - sdFilename = util.getDosFilename(filename, existingSdFiles) - self._comm.startFileTransfer(path, sdFilename) + self._sdRemoteName = util.getDosFilename(filename, existingSdFiles) + self._comm.startFileTransfer(absolutePath, self._sdRemoteName) def deleteSdFile(self, filename): if not self._comm or not self._comm.isSdReady(): diff --git a/src/octoprint/server/ajax/gcodefiles.py b/src/octoprint/server/ajax/gcodefiles.py index 870a996c..e13ae41f 100644 --- a/src/octoprint/server/ajax/gcodefiles.py +++ b/src/octoprint/server/ajax/gcodefiles.py @@ -33,18 +33,19 @@ def readGcodeFiles(): return jsonify(files=files, free=util.getFormattedSize(util.getFreeBytes(settings().getBaseFolder("uploads")))) -@ajax.route("/gcodefiles/", methods=["GET"]) -def readGcodeFile(filename): - return redirectToTornado(request, url_for("index") + "downloads/gcode/" + filename) - - -@ajax.route("/gcodefiles/upload", methods=["POST"]) +@ajax.route("/gcodefiles/", methods=["POST"]) @restricted_access -def uploadGcodeFile(): +def uploadGcodeFile(target): + if not target in [FileDestinations.LOCAL, FileDestinations.SDCARD]: + return make_response("Invalid target: %s" % target, 400) + if "gcode_file" in request.files.keys(): file = request.files["gcode_file"] - sd = "target" in request.values.keys() and request.values["target"] == "sd"; + sd = target == FileDestinations.SDCARD + selectAfterUpload = "select" in request.values.keys() and request.values["select"] in valid_boolean_trues + printAfterSelect = "print" in request.values.keys() and request.values["print"] in valid_boolean_trues + # determine current job currentFilename = None currentSd = None currentJob = printer.getCurrentJob() @@ -52,34 +53,52 @@ def uploadGcodeFile(): currentFilename = currentJob["filename"] currentSd = currentJob["sd"] + # determine future filename of file to be uploaded, abort if it can't be uploaded futureFilename = gcodeManager.getFutureFilename(file) if futureFilename is None or (not settings().getBoolean(["cura", "enabled"]) and not gcodefiles.isGcodeFileName(futureFilename)): return make_response("Can not upload file %s, wrong format?" % file.filename, 400) + # prohibit overwriting currently selected file while it's being printed if futureFilename == currentFilename and sd == currentSd and printer.isPrinting() or printer.isPaused(): - # trying to overwrite currently selected file, but it is being printed return make_response("Trying to overwrite file that is currently being printed: %s" % currentFilename, 403) + filename = None + + def fileProcessingFinished(filename, absFilename, destination): + """ + Callback for when the file processing (upload, optional slicing, addition to analysis queue) has + finished. + + Depending on the file's destination triggers either streaming to SD card or directly calls selectOrPrint. + """ + sd = destination == FileDestinations.SDCARD + if sd: + printer.addSdFile(filename, absFilename, selectAndOrPrint) + else: + selectAndOrPrint(absFilename, destination) + + def selectAndOrPrint(nameToSelect, destination): + """ + Callback for when the file is ready to be selected and optionally printed. For SD file uploads this only + the case after they have finished streaming to the printer, which is why this callback is also used + for the corresponding call to addSdFile. + + Selects the just uploaded file if either selectAfterUpload or printAfterSelect are True, or if the + exact file is already selected, such reloading it. + """ + sd = destination == FileDestinations.SDCARD + if selectAfterUpload or printAfterSelect or (currentFilename == filename and currentSd == sd): + printer.selectFile(nameToSelect, sd, printAfterSelect) + destination = FileDestinations.SDCARD if sd else FileDestinations.LOCAL - - filename, done = gcodeManager.addFile(file, destination) - + filename, done = gcodeManager.addFile(file, destination, fileProcessingFinished) if filename is None: return make_response("Could not upload the file %s" % file.filename, 500) - absFilename = gcodeManager.getAbsolutePath(filename) - if sd: - printer.addSdFile(filename, absFilename) - - if currentFilename == filename and currentSd == sd: - # reload file as it was updated - if sd: - printer.selectFile(filename, sd, False) - else: - printer.selectFile(absFilename, sd, False) - eventManager.fire("Upload", filename) - return jsonify(files=gcodeManager.getAllFileData(), filename=filename, done=done) + return jsonify(files=gcodeManager.getAllFileData(), filename=filename, done=done) + else: + return make_response("No gcode_file included", 400) @ajax.route("/gcodefiles/load", methods=["POST"]) diff --git a/src/octoprint/static/js/app/main.js b/src/octoprint/static/js/app/main.js index 5779bbf4..ec9adf31 100644 --- a/src/octoprint/static/js/app/main.js +++ b/src/octoprint/static/js/app/main.js @@ -107,9 +107,9 @@ $(function() { function enable_local_dropzone() { $("#gcode_upload").fileupload({ + url: AJAX_BASEURL + "gcodefiles/local", dataType: "json", dropZone: localTarget, - formData: {target: "local"}, done: gcode_upload_done, fail: gcode_upload_fail, progressall: gcode_upload_progress @@ -118,9 +118,9 @@ $(function() { function disable_local_dropzone() { $("#gcode_upload").fileupload({ + url: AJAX_BASEURL + "gcodefiles/local", dataType: "json", dropZone: null, - formData: {target: "local"}, done: gcode_upload_done, fail: gcode_upload_fail, progressall: gcode_upload_progress @@ -129,9 +129,9 @@ $(function() { function enable_sd_dropzone() { $("#gcode_upload_sd").fileupload({ + url: AJAX_BASEURL + "gcodefiles/sdcard", dataType: "json", dropZone: $("#drop_sd"), - formData: {target: "sd"}, done: gcode_upload_done, fail: gcode_upload_fail, progressall: gcode_upload_progress @@ -140,6 +140,7 @@ $(function() { function disable_sd_dropzone() { $("#gcode_upload_sd").fileupload({ + url: AJAX_BASEURL + "gcodefiles/sdcard", dataType: "json", dropZone: null, formData: {target: "sd"}, diff --git a/src/octoprint/static/js/app/viewmodels/gcodefiles.js b/src/octoprint/static/js/app/viewmodels/gcodefiles.js index c14ad2a2..a643259b 100644 --- a/src/octoprint/static/js/app/viewmodels/gcodefiles.js +++ b/src/octoprint/static/js/app/viewmodels/gcodefiles.js @@ -43,10 +43,10 @@ function GcodeFilesViewModel(printerStateViewModel, loginStateViewModel) { return !(file["prints"] && file["prints"]["success"] && file["prints"]["success"] > 0); }, "sd": function(file) { - return file["origin"] && file["origin"] == "sd"; + return file["origin"] && file["origin"] == "sdcard"; }, "local": function(file) { - return !(file["origin"] && file["origin"] == "sd"); + return !(file["origin"] && file["origin"] == "sdcard"); } }, "name", From 0fd9b3354aa768471a4f171cca9710336cc6d57c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Sat, 21 Dec 2013 16:01:13 +0100 Subject: [PATCH 06/14] Changed version... --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 4d95aca2..c0a23e98 100644 --- a/setup.py +++ b/setup.py @@ -3,7 +3,7 @@ from setuptools import setup, find_packages -VERSION = "1.1.0-dev" +VERSION = "1.0.0-rc2" def params(): name = "OctoPrint" From b282a18f6bb09bfba86ada1a4e002cac58455ce9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Thu, 9 Jan 2014 20:06:07 +0100 Subject: [PATCH 07/14] Also recognize --iknowwhatimdoing when running as daemon Fixes #337 --- run | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/run b/run index ca1ec2f1..35693ee8 100755 --- a/run +++ b/run @@ -4,7 +4,7 @@ from octoprint.daemon import Daemon from octoprint.server import Server class Main(Daemon): - def __init__(self, pidfile, configfile, basedir, host, port, debug): + def __init__(self, pidfile, configfile, basedir, host, port, debug, allowRoot): Daemon.__init__(self, pidfile) self._configfile = configfile @@ -12,9 +12,10 @@ class Main(Daemon): self._host = host self._port = port self._debug = debug + self._allowRoot = allowRoot def run(self): - octoprint = Server(self._configfile, self._basedir, self._host, self._port, self._debug) + octoprint = Server(self._configfile, self._basedir, self._host, self._port, self._debug, self._allowRoot) octoprint.run() def main(): @@ -50,7 +51,7 @@ def main(): print >> sys.stderr, "Sorry, daemon mode is only supported under Linux right now" sys.exit(2) - daemon = Main(args.pidfile, args.config, args.basedir, args.host, args.port, args.debug) + daemon = Main(args.pidfile, args.config, args.basedir, args.host, args.port, args.debug, args.allowRoot) if "start" == args.daemon: daemon.start() elif "stop" == args.daemon: From bba625a68f6c5a3353b2507cd69db9379d25a6da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Thu, 9 Jan 2014 20:04:08 +0100 Subject: [PATCH 08/14] Also recognize --iknowwhatimdoing when running as daemon Fixes #337 (cherry picked from commit 417487e) --- src/octoprint/__init__.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/octoprint/__init__.py b/src/octoprint/__init__.py index 1a7747fd..dbd06e7d 100644 --- a/src/octoprint/__init__.py +++ b/src/octoprint/__init__.py @@ -3,8 +3,9 @@ import sys from octoprint.daemon import Daemon from octoprint.server import Server + class Main(Daemon): - def __init__(self, pidfile, configfile, basedir, host, port, debug): + def __init__(self, pidfile, configfile, basedir, host, port, debug, allowRoot): Daemon.__init__(self, pidfile) self._configfile = configfile @@ -12,9 +13,10 @@ class Main(Daemon): self._host = host self._port = port self._debug = debug + self._allowRoot = allowRoot def run(self): - octoprint = Server(self._configfile, self._basedir, self._host, self._port, self._debug) + octoprint = Server(self._configfile, self._basedir, self._host, self._port, self._debug, self._allowRoot) octoprint.run() def main(): @@ -50,7 +52,7 @@ def main(): print >> sys.stderr, "Sorry, daemon mode is only supported under Linux right now" sys.exit(2) - daemon = Main(args.pidfile, args.config, args.basedir, args.host, args.port, args.debug) + daemon = Main(args.pidfile, args.config, args.basedir, args.host, args.port, args.debug, args.allowRoot) if "start" == args.daemon: daemon.start() elif "stop" == args.daemon: From 0747336c0f8fd29989bdfaf892696b8fb1336800 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Mon, 24 Feb 2014 10:00:11 +0100 Subject: [PATCH 09/14] Bugfix: Do not run gcode analyzer when a print is ongoing Evaluate active flag on gcode analyzer AFTER fetching an item from the work queue, otherwise it will always start working once it finds something if the active flag was true once but then switched to false while the queue was still empty. Thanks to @Salandora for spotting this. Fixes #357 (manually cherry-picked from 592f3dc) --- octoprint/gcodefiles.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/octoprint/gcodefiles.py b/octoprint/gcodefiles.py index 3b371213..474ac58f 100644 --- a/octoprint/gcodefiles.py +++ b/octoprint/gcodefiles.py @@ -358,8 +358,6 @@ class MetadataAnalyzer: def _work(self): aborted = None while True: - self._active.wait() - if aborted is not None: filename = aborted aborted = None @@ -368,6 +366,8 @@ class MetadataAnalyzer: (priority, filename) = self._queue.get() self._logger.debug("Processing file %s from queue (priority %d)" % (filename, priority)) + self._active.wait() + try: self._analyzeGcode(filename) self._queue.task_done() From 89cae22b9130865a75d1e8eee15c7924b1a89402 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Mon, 3 Mar 2014 09:10:10 +0100 Subject: [PATCH 10/14] Extracted all used regexes in the comm layer and precompiled them Possible improvement of #390 --- octoprint/util/comm.py | 50 +++++++++++++++++++++++++++--------------- 1 file changed, 32 insertions(+), 18 deletions(-) diff --git a/octoprint/util/comm.py b/octoprint/util/comm.py index 5be9c2a5..0a2b2737 100644 --- a/octoprint/util/comm.py +++ b/octoprint/util/comm.py @@ -149,6 +149,18 @@ class MachineCom(object): # print job self._currentFile = None + # regexes + floatPattern = "[-+]?[0-9]*\.?[0-9]+" + intPattern = "[0-9]+" + self._regex_command = re.compile("^\s*([GM]\d+)") + self._regex_float = re.compile(floatPattern) + self._regex_paramZFloat = re.compile("Z(%s)" % floatPattern) + self._regex_paramSInt = re.compile("S(%s)" % intPattern) + self._regex_paramNInt = re.compile("N(%s)" % intPattern) + self._regex_minMaxError = re.compile("Error:[0-9]\n") + self._regex_sdPrintingByte = re.compile("([0-9]*)/([0-9]*)") + self._regex_sdFileOpened = re.compile("File opened:\s*(.*?)\s+Size:\s*([0-9]*)") + def __del__(self): self.close() @@ -531,7 +543,7 @@ class MachineCom(object): # Marlin reports an MIN/MAX temp error as "Error:x\n: Extruder switched off. MAXTEMP triggered !\n" # But a bed temp error is reported as "Error: Temperature heated bed switched off. MAXTEMP triggered !!" # So we can have an extra newline in the most common case. Awesome work people. - if re.match('Error:[0-9]\n', line): + if self._regex_minMaxError.match(line): line = line.rstrip() + self._readline() #Skip the communication errors, as those get corrected. if 'checksum mismatch' in line \ @@ -556,9 +568,9 @@ class MachineCom(object): ##~~ Temperature processing if ' T:' in line or line.startswith('T:'): try: - self._temp = float(re.search("-?[0-9\.]*", line.split('T:')[1]).group(0)) + self._temp = float(self._regex_float.search(line.split('T:')[1]).group(0)) if ' B:' in line: - self._bedTemp = float(re.search("-?[0-9\.]*", line.split(' B:')[1]).group(0)) + self._bedTemp = float(self._regex_float.search(line.split(' B:')[1]).group(0)) self._callback.mcTempUpdate(self._temp, self._bedTemp, self._targetTemp, self._bedTargetTemp) except ValueError: @@ -595,12 +607,12 @@ class MachineCom(object): self._callback.mcSdFiles(self._sdFiles) elif 'SD printing byte' in line: # answer to M27, at least on Marlin, Repetier and Sprinter: "SD printing byte %d/%d" - match = re.search("([0-9]*)/([0-9]*)", line) + match = self._regex_sdPrintingByte.search("([0-9]*)/([0-9]*)", line) self._currentFile.setFilepos(int(match.group(1))) self._callback.mcProgress() elif 'File opened' in line: # answer to M23, at least on Marlin, Repetier and Sprinter: "File opened:%s Size:%d" - match = re.search("File opened:\s*(.*?)\s+Size:\s*([0-9]*)", line) + match = self._regex_sdFileOpened.search(line) self._currentFile = PrintingSdFileInformation(match.group(1), int(match.group(2))) elif 'File selected' in line: # final answer to M23, at least on Marlin, Repetier and Sprinter: "File selected" @@ -855,7 +867,7 @@ class MachineCom(object): return if not self.isStreaming(): - gcode = re.search("^\s*([GM]\d+)", cmd) + gcode = self._regex_command.search(cmd) if gcode: gcode = gcode.group(1) @@ -905,13 +917,15 @@ class MachineCom(object): def _gcode_G0(self, cmd): if 'Z' in cmd: - try: - z = float(re.search('Z([0-9\.]*)', cmd).group(1)) - if self._currentZ != z: - self._currentZ = z - self._callback.mcZChange(z) - except ValueError: - pass + match = self._regex_paramZFloat.search(cmd) + if match: + try: + z = float(match.group(1)) + if self._currentZ != z: + self._currentZ = z + self._callback.mcZChange(z) + except ValueError: + pass return cmd _gcode_G1 = _gcode_G0 @@ -921,7 +935,7 @@ class MachineCom(object): _gcode_M1 = _gcode_M0 def _gcode_M104(self, cmd): - match = re.search('S([0-9]+)', cmd) + match = self._regex_paramSInt.search(cmd) if match: try: self._targetTemp = float(match.group(1)) @@ -930,7 +944,7 @@ class MachineCom(object): return cmd def _gcode_M140(self, cmd): - match = re.search('S([0-9]+)', cmd) + match = self._regex_paramSInt.search(cmd) if match: try: self._bedTargetTemp = float(match.group(1)) @@ -948,7 +962,7 @@ class MachineCom(object): def _gcode_M110(self, cmd): newLineNumber = None - match = re.search("N([0-9]+)", cmd) + match = self._regex_paramNInt.search(cmd) if match: try: newLineNumber = int(match.group(1)) @@ -1083,7 +1097,7 @@ class PrintingGcodeFileInformation(PrintingFileInformation): self._firstLine = None self._offsetCallback = offsetCallback - self._tempCommandPattern = re.compile("^\s*M(104|109|140|190)\s+S([0-9\.]+)") + self._regex_tempCommand = re.compile("^\s*M(104|109|140|190)\s+S([0-9\.]+)") if not os.path.exists(self._filename) or not os.path.isfile(self._filename): raise IOError("File %s does not exist" % self._filename) @@ -1140,7 +1154,7 @@ class PrintingGcodeFileInformation(PrintingFileInformation): if self._offsetCallback is not None: (tempOffset, bedTempOffset) = self._offsetCallback() if tempOffset != 0 or bedTempOffset != 0: - tempMatch = self._tempCommandPattern.match(line) + tempMatch = self._regex_tempCommand.match(line) if tempMatch is not None: if tempMatch.group(1) == "104" or tempMatch.group(1) == "109": offset = tempOffset From f09d44d8a095a86fda84a168f77bab283d1397be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Mon, 3 Mar 2014 16:58:15 +0100 Subject: [PATCH 11/14] Hopefully fixed a race condition that could occur when pressing "Print" (file not opened yet, but comm layer tries to fetch next command) Possible fix for #400 --- octoprint/util/comm.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/octoprint/util/comm.py b/octoprint/util/comm.py index 0a2b2737..c0b31f96 100644 --- a/octoprint/util/comm.py +++ b/octoprint/util/comm.py @@ -341,14 +341,14 @@ class MachineCom(object): if self._currentFile is None: raise ValueError("No file selected for printing") - wasPaused = self.isPaused() - self._printSection = "CUSTOM" - self._changeState(self.STATE_PRINTING) - eventManager().fire("PrintStarted", self._currentFile.getFilename()) try: self._currentFile.start() + + self._changeState(self.STATE_PRINTING) + eventManager().fire("PrintStarted", self._currentFile.getFilename()) + if self.isSdFileSelected(): if wasPaused: self.sendCommand("M26 S0") @@ -620,7 +620,6 @@ class MachineCom(object): eventManager().fire("FileSelected", self._currentFile.getFilename()) elif 'Writing to file' in line: # anwer to M28, at least on Marlin, Repetier and Sprinter: "Writing to file: %s" - self._printSection = "CUSTOM" self._changeState(self.STATE_PRINTING) line = "ok" elif 'Done printing file' in line: From d7f2075b1dc79e168b735e59706734b9815debb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Mon, 17 Feb 2014 10:25:40 +0100 Subject: [PATCH 12/14] Only list those SD files that have a ASCII filename According to the RepRap protocol we should actually not ever get anything other than those anyways, as the protocol is defined as ASCII-only. In the future there might be a way to somehow handle such files too, for the time being this fixes issues though where non-ascii files on the SD made the whole SD file handling not work. Closes #381 (cherry picked from commit b115b6f) --- src/octoprint/util/__init__.py | 36 ++++++++++++++++++++++++++++++++++ src/octoprint/util/comm.py | 12 ++++++++---- 2 files changed, 44 insertions(+), 4 deletions(-) diff --git a/src/octoprint/util/__init__.py b/src/octoprint/util/__init__.py index 3f5af26b..0c5e66d2 100644 --- a/src/octoprint/util/__init__.py +++ b/src/octoprint/util/__init__.py @@ -8,6 +8,7 @@ import sys import time import re import tempfile +from flask import make_response from octoprint.settings import settings @@ -188,3 +189,38 @@ def silentRemove(file): os.remove(file) except OSError: pass + + +def sanitizeAscii(line): + return unicode(line, 'ascii', 'replace').encode('ascii', 'replace').rstrip() + + +def filterNonAscii(line): + """ + Returns True if the line contains non-ascii characters, false otherwise + + @param line the line to test + """ + + try: + unicode(line, 'ascii').encode('ascii') + return False + except ValueError: + return True + + +def getJsonCommandFromRequest(request, valid_commands): + if not "application/json" in request.headers["Content-Type"]: + return None, None, make_response("Expected content-type JSON", 400) + + data = request.json + if not "command" in data.keys() or not data["command"] in valid_commands.keys(): + return None, None, make_response("Expected valid command", 400) + + command = data["command"] + for parameter in valid_commands[command]: + if not parameter in data: + return None, None, make_response("Mandatory parameter %s missing for command %s" % (parameter, command), 400) + + return command, data, None + diff --git a/src/octoprint/util/comm.py b/src/octoprint/util/comm.py index 924fef29..57599ed6 100644 --- a/src/octoprint/util/comm.py +++ b/src/octoprint/util/comm.py @@ -20,7 +20,7 @@ from octoprint.util.avr_isp import ispBase from octoprint.settings import settings from octoprint.events import eventManager from octoprint.gcodefiles import isGcodeFileName -from octoprint.util import getExceptionString, getNewTimeout +from octoprint.util import getExceptionString, getNewTimeout, sanitizeAscii, filterNonAscii from octoprint.util.virtual import VirtualPrinter try: @@ -513,7 +513,11 @@ class MachineCom(object): ##~~ 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 isGcodeFileName(line.strip().lower()) and not 'End file list' in line: - self._sdFiles.append(line.strip().lower()) + filename = line.strip().lower() + if filterNonAscii(filename): + self._logger.warn("Got a file from printer's SD that has a non-ascii filename (%s), that shouldn't happen according to the protocol" % filename) + else: + self._sdFiles.append(filename) continue ##~~ Temperature processing @@ -826,7 +830,7 @@ class MachineCom(object): if ret == '': #self._log("Recv: TIMEOUT") return '' - self._log("Recv: %s" % (unicode(ret, 'ascii', 'replace').encode('ascii', 'replace').rstrip())) + self._log("Recv: %s" % sanitizeAscii(ret)) return ret def _sendNext(self): @@ -1202,4 +1206,4 @@ class PrintingGcodeFileInformation(PrintingFileInformation): class StreamingGcodeFileInformation(PrintingGcodeFileInformation): def __init__(self, filename): - PrintingGcodeFileInformation.__init__(self, filename, None) \ No newline at end of file + PrintingGcodeFileInformation.__init__(self, filename, None) From e068012cfbc67b3037667c77ebf3982e8e9aa02c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Mon, 10 Mar 2014 13:04:12 +0100 Subject: [PATCH 13/14] Fixed a regex matcher (cherry picked from commit 8fbb304) --- 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 57599ed6..8f829872 100644 --- a/src/octoprint/util/comm.py +++ b/src/octoprint/util/comm.py @@ -562,7 +562,7 @@ class MachineCom(object): self._callback.mcSdFiles(self._sdFiles) elif 'SD printing byte' in line: # answer to M27, at least on Marlin, Repetier and Sprinter: "SD printing byte %d/%d" - match = self._regex_sdPrintingByte.search("([0-9]*)/([0-9]*)", line) + match = self._regex_sdPrintingByte.search(line) self._currentFile.setFilepos(int(match.group(1))) self._callback.mcProgress() elif 'File opened' in line: From d7e37cf6589a178a08573c6f9d94c88cf0d6eb4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Thu, 13 Mar 2014 20:15:29 +0100 Subject: [PATCH 14/14] origin is "sd" for files from the SD card, not "sdcard". Fixes #411 --- src/octoprint/static/js/app/viewmodels/gcodefiles.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/octoprint/static/js/app/viewmodels/gcodefiles.js b/src/octoprint/static/js/app/viewmodels/gcodefiles.js index a643259b..c14ad2a2 100644 --- a/src/octoprint/static/js/app/viewmodels/gcodefiles.js +++ b/src/octoprint/static/js/app/viewmodels/gcodefiles.js @@ -43,10 +43,10 @@ function GcodeFilesViewModel(printerStateViewModel, loginStateViewModel) { return !(file["prints"] && file["prints"]["success"] && file["prints"]["success"] > 0); }, "sd": function(file) { - return file["origin"] && file["origin"] == "sdcard"; + return file["origin"] && file["origin"] == "sd"; }, "local": function(file) { - return !(file["origin"] && file["origin"] == "sdcard"); + return !(file["origin"] && file["origin"] == "sd"); } }, "name",