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