From 8ad20a0168417de77a0c3d41fa1b930dab3f9b11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Sun, 8 Sep 2013 17:49:01 +0200 Subject: [PATCH] Allow definition of temperature offsets for extruder and bed Session based temperature offsets that are only applied to temperature definitions in GCODE files being printed, in order to quickly experiment with temperature settings. Closes #97 --- octoprint/printer.py | 35 +++++++++++-- octoprint/server.py | 18 +++++++ .../static/js/app/viewmodels/temperature.js | 23 ++++++++- octoprint/templates/index.jinja2 | 28 ++++++++--- octoprint/util/comm.py | 50 +++++++++++++------ 5 files changed, 128 insertions(+), 26 deletions(-) diff --git a/octoprint/printer.py b/octoprint/printer.py index d5fc445..323546a 100644 --- a/octoprint/printer.py +++ b/octoprint/printer.py @@ -171,9 +171,19 @@ class Printer(): """ Sends multiple gcode commands (provided as a list) to the printer. """ + if self._comm is None: + return + for command in commands: self._comm.sendCommand(command) + def setTemperatureOffset(self, extruder, bed): + if self._comm is None: + return + + self._comm.setTemperatureOffset(extruder, bed) + self._stateMonitor.setTempOffsets(extruder, bed) + def selectFile(self, filename, sd, printAfterSelect=False): if self._comm is None or (self._comm.isBusy() or self._comm.isStreaming()): return @@ -503,14 +513,22 @@ class Printer(): return currentData["job"] def getCurrentTemperatures(self): + if self._comm is not None: + (tempOffset, bedTempOffset) = self._comm.getOffsets() + else: + tempOffset = 0 + bedTempOffset = 0 + return { "extruder": { "current": self._temp, - "target": self._targetTemp + "target": self._targetTemp, + "offset": tempOffset }, "bed": { "current": self._bedTemp, - "target": self._targetBedTemp + "target": self._targetBedTemp, + "offset": bedTempOffset } } @@ -627,6 +645,9 @@ class StateMonitor(object): self._currentZ = None self._progress = None + self._tempOffset = 0 + self._bedTempOffset = 0 + self._changeEvent = threading.Event() self._lastUpdate = time.time() @@ -668,6 +689,13 @@ class StateMonitor(object): self._progress = progress self._changeEvent.set() + def setTempOffsets(self, tempOffset, bedTempOffset): + if tempOffset is not None: + self._tempOffset = tempOffset + if bedTempOffset is not None: + self._bedTempOffset = bedTempOffset + self._changeEvent.set() + def _work(self): while True: self._changeEvent.wait() @@ -688,6 +716,7 @@ class StateMonitor(object): "state": self._state, "job": self._jobData, "currentZ": self._currentZ, - "progress": self._progress + "progress": self._progress, + "offsets": (self._tempOffset, self._bedTempOffset) } diff --git a/octoprint/server.py b/octoprint/server.py index 87f9ab1..54ff8ba 100644 --- a/octoprint/server.py +++ b/octoprint/server.py @@ -277,6 +277,24 @@ def setTargetTemperature(): bedTemp = request.values["bedTemp"] printer.command("M140 S" + bedTemp) + if "tempOffset" in request.values.keys(): + # set target temperature offset + try: + tempOffset = float(request.values["tempOffset"]) + if tempOffset >= -50 and tempOffset <= 50: + printer.setTemperatureOffset(tempOffset, None) + except: + pass + + if "bedTempOffset" in request.values.keys(): + # set target bed temperature offset + try: + bedTempOffset = float(request.values["bedTempOffset"]) + if bedTempOffset >= -50 and bedTempOffset <= 50: + printer.setTemperatureOffset(None, bedTempOffset) + except: + pass + return jsonify(SUCCESS) @app.route(BASEURL + "control/jog", methods=["POST"]) diff --git a/octoprint/static/js/app/viewmodels/temperature.js b/octoprint/static/js/app/viewmodels/temperature.js index 4698e7c..6890914 100644 --- a/octoprint/static/js/app/viewmodels/temperature.js +++ b/octoprint/static/js/app/viewmodels/temperature.js @@ -11,6 +11,11 @@ function TemperatureViewModel(loginStateViewModel, settingsViewModel) { self.newTemp = ko.observable(undefined); self.newBedTemp = ko.observable(undefined); + self.newTempOffset = ko.observable(undefined); + self.tempOffset = ko.observable(0); + self.newBedTempOffset = ko.observable(undefined); + self.bedTempOffset = ko.observable(0); + self.isErrorOrClosed = ko.observable(undefined); self.isOperational = ko.observable(undefined); self.isPrinting = ko.observable(undefined); @@ -78,11 +83,13 @@ function TemperatureViewModel(loginStateViewModel, settingsViewModel) { self.fromCurrentData = function(data) { self._processStateData(data.state); self._processTemperatureUpdateData(data.temperatures); + self._processOffsetData(data.offsets); } self.fromHistoryData = function(data) { self._processStateData(data.state); self._processTemperatureHistoryData(data.temperatureHistory); + self._processOffsetData(data.offsets); } self._processStateData = function(data) { @@ -138,6 +145,11 @@ function TemperatureViewModel(loginStateViewModel, settingsViewModel) { self.updatePlot(); } + self._processOffsetData = function(data) { + self.tempOffset(data[0]); + self.bedTempOffset(data[1]); + } + self.updatePlot = function() { var graph = $("#temperature-graph"); if (graph.length) { @@ -166,6 +178,10 @@ function TemperatureViewModel(loginStateViewModel, settingsViewModel) { self._updateTemperature(0, "temp", function(){self.targetTemp(0); self.newTemp("");}); } + self.setTempOffset = function() { + self._updateTemperature(self.newTempOffset(), "tempOffset", function() {self.tempOffset(self.newTempOffset()); self.newTempOffset("");}); + } + self.setBedTempFromProfile = function(profile) { self._updateTemperature(profile.bed, "bedTemp"); } @@ -178,6 +194,10 @@ function TemperatureViewModel(loginStateViewModel, settingsViewModel) { self._updateTemperature(0, "bedTemp", function() {self.bedTargetTemp(0); self.newBedTemp("");}); } + self.setBedTempOffset = function() { + self._updateTemperature(self.newBedTempOffset(), "bedTempOffset", function() {self.bedTempOffset(self.newBedTempOffset()); self.newBedTempOffset("");}); + } + self._updateTemperature = function(temp, type, callback) { var data = {}; data[type] = temp; @@ -185,10 +205,9 @@ function TemperatureViewModel(loginStateViewModel, settingsViewModel) { $.ajax({ url: AJAX_BASEURL + "control/temperature", type: "POST", - dataType: "json", data: data, success: function() { if (callback !== undefined) callback(); } - }) + }); } self.handleEnter = function(event, type) { diff --git a/octoprint/templates/index.jinja2 b/octoprint/templates/index.jinja2 index 51b61e3..320252a 100644 --- a/octoprint/templates/index.jinja2 +++ b/octoprint/templates/index.jinja2 @@ -256,12 +256,12 @@

Temperature

- + - +
- +
°C @@ -284,16 +284,24 @@
+
+ +
+ + °C +
+ +

Bed Temperature

- + - +
- +
°C @@ -316,6 +324,14 @@
+
+ +
+ + °C +
+ +
diff --git a/octoprint/util/comm.py b/octoprint/util/comm.py index 5de6172..bb3f9aa 100644 --- a/octoprint/util/comm.py +++ b/octoprint/util/comm.py @@ -120,6 +120,8 @@ class MachineCom(object): self._bedTemp = 0 self._targetTemp = 0 self._bedTargetTemp = 0 + self._tempOffset = 0 + self._bedTempOffset = 0 self._commandQueue = queue.Queue() self._currentZ = None self._heatupWaitStartTime = 0 @@ -285,6 +287,9 @@ class MachineCom(object): def getBedTemp(self): return self._bedTemp + def getOffsets(self): + return (self._tempOffset, self._bedTempOffset) + ##~~ external interface def close(self, isError = False): @@ -304,6 +309,13 @@ class MachineCom(object): eventManager().fire("PrintFailed") eventManager().fire("Disconnected") + def setTemperatureOffset(self, extruder=None, bed=None): + if extruder is not None: + self._tempOffset = extruder + + if bed is not None: + self._bedTempOffset = bed + def sendCommand(self, cmd): cmd = cmd.encode('ascii', 'replace') if self.isPrinting() and not self.isSdFileSelected(): @@ -357,7 +369,7 @@ class MachineCom(object): return self.sendCommand("M23 %s" % filename) else: - self._currentFile = PrintingGcodeFileInformation(filename) + self._currentFile = PrintingGcodeFileInformation(filename, self.getOffsets) eventManager().fire("FileSelected", filename) self._callback.mcFileSelected(filename, self._currentFile.getFilesize(), False) @@ -794,10 +806,6 @@ class MachineCom(object): eventManager().fire("PrintDone", self._currentFile.getFilename()) return - if type(line) is tuple: - self._printSection = line[1] - line = line[0] - self._sendCommand(line, True) self._callback.mcProgress() @@ -1065,12 +1073,14 @@ class PrintingGcodeFileInformation(PrintingFileInformation): that the file is closed in case of an error. """ - def __init__(self, filename): + def __init__(self, filename, offsetCallback): PrintingFileInformation.__init__(self, filename) self._filehandle = None self._lineCount = None self._firstLine = None - self._prevLineType = None + + self._offsetCallback = offsetCallback + self._tempCommandPattern = 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) @@ -1082,7 +1092,6 @@ class PrintingGcodeFileInformation(PrintingFileInformation): """ self._filehandle = open(self._filename, "r") self._lineCount = None - self._prevLineType = "CUSTOM" self._startTime = None def getNext(self): @@ -1121,17 +1130,28 @@ class PrintingGcodeFileInformation(PrintingFileInformation): raise e def _processLine(self, line): - lineType = self._prevLineType - if line.startswith(";TYPE:"): - lineType = line[6:].strip() if ";" in line: line = line[0:line.find(";")] line = line.strip() if len(line) > 0: - if self._prevLineType != lineType: - return line, lineType - else: - return line + (tempOffset, bedTempOffset) = self._offsetCallback() + if tempOffset != 0 or bedTempOffset != 0: + tempMatch = self._tempCommandPattern.match(line) + if tempMatch is not None: + if tempMatch.group(1) == "104" or tempMatch.group(1) == "109": + offset = tempOffset + elif tempMatch.group(1) == "140" or tempMatch.group(1) == "190": + offset = bedTempOffset + else: + offset = 0 + + try: + temp = float(tempMatch.group(2)) + newTemp = temp + offset + line = line.replace("S" + tempMatch.group(2), "S%f" % newTemp) + except ValueError: + pass + return line else: return None