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
master
Gina Häußge 2013-09-08 17:49:01 +02:00
parent b2587579ba
commit 8ad20a0168
5 changed files with 128 additions and 26 deletions

View File

@ -171,9 +171,19 @@ class Printer():
""" """
Sends multiple gcode commands (provided as a list) to the printer. Sends multiple gcode commands (provided as a list) to the printer.
""" """
if self._comm is None:
return
for command in commands: for command in commands:
self._comm.sendCommand(command) 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): def selectFile(self, filename, sd, printAfterSelect=False):
if self._comm is None or (self._comm.isBusy() or self._comm.isStreaming()): if self._comm is None or (self._comm.isBusy() or self._comm.isStreaming()):
return return
@ -503,14 +513,22 @@ class Printer():
return currentData["job"] return currentData["job"]
def getCurrentTemperatures(self): def getCurrentTemperatures(self):
if self._comm is not None:
(tempOffset, bedTempOffset) = self._comm.getOffsets()
else:
tempOffset = 0
bedTempOffset = 0
return { return {
"extruder": { "extruder": {
"current": self._temp, "current": self._temp,
"target": self._targetTemp "target": self._targetTemp,
"offset": tempOffset
}, },
"bed": { "bed": {
"current": self._bedTemp, "current": self._bedTemp,
"target": self._targetBedTemp "target": self._targetBedTemp,
"offset": bedTempOffset
} }
} }
@ -627,6 +645,9 @@ class StateMonitor(object):
self._currentZ = None self._currentZ = None
self._progress = None self._progress = None
self._tempOffset = 0
self._bedTempOffset = 0
self._changeEvent = threading.Event() self._changeEvent = threading.Event()
self._lastUpdate = time.time() self._lastUpdate = time.time()
@ -668,6 +689,13 @@ class StateMonitor(object):
self._progress = progress self._progress = progress
self._changeEvent.set() 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): def _work(self):
while True: while True:
self._changeEvent.wait() self._changeEvent.wait()
@ -688,6 +716,7 @@ class StateMonitor(object):
"state": self._state, "state": self._state,
"job": self._jobData, "job": self._jobData,
"currentZ": self._currentZ, "currentZ": self._currentZ,
"progress": self._progress "progress": self._progress,
"offsets": (self._tempOffset, self._bedTempOffset)
} }

View File

@ -277,6 +277,24 @@ def setTargetTemperature():
bedTemp = request.values["bedTemp"] bedTemp = request.values["bedTemp"]
printer.command("M140 S" + 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) return jsonify(SUCCESS)
@app.route(BASEURL + "control/jog", methods=["POST"]) @app.route(BASEURL + "control/jog", methods=["POST"])

View File

@ -11,6 +11,11 @@ function TemperatureViewModel(loginStateViewModel, settingsViewModel) {
self.newTemp = ko.observable(undefined); self.newTemp = ko.observable(undefined);
self.newBedTemp = 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.isErrorOrClosed = ko.observable(undefined);
self.isOperational = ko.observable(undefined); self.isOperational = ko.observable(undefined);
self.isPrinting = ko.observable(undefined); self.isPrinting = ko.observable(undefined);
@ -78,11 +83,13 @@ function TemperatureViewModel(loginStateViewModel, settingsViewModel) {
self.fromCurrentData = function(data) { self.fromCurrentData = function(data) {
self._processStateData(data.state); self._processStateData(data.state);
self._processTemperatureUpdateData(data.temperatures); self._processTemperatureUpdateData(data.temperatures);
self._processOffsetData(data.offsets);
} }
self.fromHistoryData = function(data) { self.fromHistoryData = function(data) {
self._processStateData(data.state); self._processStateData(data.state);
self._processTemperatureHistoryData(data.temperatureHistory); self._processTemperatureHistoryData(data.temperatureHistory);
self._processOffsetData(data.offsets);
} }
self._processStateData = function(data) { self._processStateData = function(data) {
@ -138,6 +145,11 @@ function TemperatureViewModel(loginStateViewModel, settingsViewModel) {
self.updatePlot(); self.updatePlot();
} }
self._processOffsetData = function(data) {
self.tempOffset(data[0]);
self.bedTempOffset(data[1]);
}
self.updatePlot = function() { self.updatePlot = function() {
var graph = $("#temperature-graph"); var graph = $("#temperature-graph");
if (graph.length) { if (graph.length) {
@ -166,6 +178,10 @@ function TemperatureViewModel(loginStateViewModel, settingsViewModel) {
self._updateTemperature(0, "temp", function(){self.targetTemp(0); self.newTemp("");}); 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.setBedTempFromProfile = function(profile) {
self._updateTemperature(profile.bed, "bedTemp"); self._updateTemperature(profile.bed, "bedTemp");
} }
@ -178,6 +194,10 @@ function TemperatureViewModel(loginStateViewModel, settingsViewModel) {
self._updateTemperature(0, "bedTemp", function() {self.bedTargetTemp(0); self.newBedTemp("");}); 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) { self._updateTemperature = function(temp, type, callback) {
var data = {}; var data = {};
data[type] = temp; data[type] = temp;
@ -185,10 +205,9 @@ function TemperatureViewModel(loginStateViewModel, settingsViewModel) {
$.ajax({ $.ajax({
url: AJAX_BASEURL + "control/temperature", url: AJAX_BASEURL + "control/temperature",
type: "POST", type: "POST",
dataType: "json",
data: data, data: data,
success: function() { if (callback !== undefined) callback(); } success: function() { if (callback !== undefined) callback(); }
}) });
} }
self.handleEnter = function(event, type) { self.handleEnter = function(event, type) {

View File

@ -256,12 +256,12 @@
<div class="form-horizontal span6"> <div class="form-horizontal span6">
<h1>Temperature</h1> <h1>Temperature</h1>
<label>Current: <strong data-bind="html: tempString"></strong></label> <label title="Current extruder temperature">Current: <strong data-bind="html: tempString"></strong></label>
<label>Target: <strong data-bind="html: targetTempString"></strong></label> <label title="Target extruder temperature">Target: <strong data-bind="html: targetTempString"></strong></label>
<div style="display: none;" data-bind="visible: loginState.isUser"> <div style="display: none;" data-bind="visible: loginState.isUser">
<label for="temp_newTemp">New Target</label> <label for="temp_newTemp" title="Sets the new target temperature for the extruder">New Target</label>
<div class="input-append"> <div class="input-append">
<input type="text" class="input-mini text-right" data-bind="value: newTemp, valueUpdate: 'afterkeydown', attr: {placeholder: targetTemp}, enable: isOperational() && loginState.isUser(), event: { keyup: function(d, e) {$root.handleEnter(e, 'temp');} }" class="tempInput"> <input type="text" class="input-mini text-right" data-bind="value: newTemp, valueUpdate: 'afterkeydown', attr: {placeholder: targetTemp}, enable: isOperational() && loginState.isUser(), event: { keyup: function(d, e) {$root.handleEnter(e, 'temp');} }" class="tempInput">
<span class="add-on">&deg;C</span> <span class="add-on">&deg;C</span>
@ -284,16 +284,24 @@
</ul> </ul>
</div> </div>
</div> </div>
<div style="display: none;" data-bind="visible: loginState.isUser">
<label title="Sets a temperature offset to apply to temperatures set via streamed GCODE, may be positive or negative, will not persist across restarts of OctoPrint">Offset</label>
<div class="input-append">
<input type="number" min="-50" max="50" class="input-mini text-right" data-bind="value: newTempOffset, valueUpdate: 'afterkeydown', attr: {placeholder: tempOffset}, enable: isOperational() && loginState.isUser(), event: { keyup: function(d, e) {$root.handleEnter(e, 'tempOffset');} }" class="tempInput">
<span class="add-on">&deg;C</span>
</div>
<button type="submit" class="btn" data-bind="click: setTempOffset, enable: newTempOffset() && isOperational() && loginState.isUser()">Set</button>
</div>
</div> </div>
<div class="form-horizontal span6"> <div class="form-horizontal span6">
<h1>Bed Temperature</h1> <h1>Bed Temperature</h1>
<label>Current: <strong data-bind="html: bedTempString"></strong></label> <label title="Current bed temperature">Current: <strong data-bind="html: bedTempString"></strong></label>
<label>Target: <strong data-bind="html: bedTargetTempString"></strong></label> <label title="Target bed temperature">Target: <strong data-bind="html: bedTargetTempString"></strong></label>
<div style="display: none;" data-bind="visible: loginState.isUser"> <div style="display: none;" data-bind="visible: loginState.isUser">
<label for="temp_newBedTemp">New Target</label> <label for="temp_newBedTemp" title="Sets the new target temperature for the bed">New Target</label>
<div class="input-append"> <div class="input-append">
<input type="text" class="input-mini text-right" data-bind="value: newBedTemp, valueUpdate: 'afterkeydown', attr: {placeholder: bedTargetTemp}, enable: isOperational() && loginState.isUser(), event: { keyup: function(d, e) {$root.handleEnter(event, 'bedTemp');} }" class="tempInput"> <input type="text" class="input-mini text-right" data-bind="value: newBedTemp, valueUpdate: 'afterkeydown', attr: {placeholder: bedTargetTemp}, enable: isOperational() && loginState.isUser(), event: { keyup: function(d, e) {$root.handleEnter(event, 'bedTemp');} }" class="tempInput">
<span class="add-on">&deg;C</span> <span class="add-on">&deg;C</span>
@ -316,6 +324,14 @@
</ul> </ul>
</div> </div>
</div> </div>
<div style="display: none;" data-bind="visible: loginState.isUser">
<label title="Sets a temperature offset to apply to bed temperatures set via streamed GCODE, may be positive or negative, will not persist across restarts of OctoPrint">Offset</label>
<div class="input-append">
<input type="number" min="-50" max="50" class="input-mini text-right" data-bind="value: newBedTempOffset, valueUpdate: 'afterkeydown', attr: {placeholder: bedTempOffset}, enable: isOperational() && loginState.isUser(), event: { keyup: function(d, e) {$root.handleEnter(e, 'bedTempOffset');} }" class="tempInput">
<span class="add-on">&deg;C</span>
</div>
<button type="submit" class="btn" data-bind="click: setBedTempOffset, enable: newBedTempOffset() && isOperational() && loginState.isUser()">Set</button>
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -120,6 +120,8 @@ class MachineCom(object):
self._bedTemp = 0 self._bedTemp = 0
self._targetTemp = 0 self._targetTemp = 0
self._bedTargetTemp = 0 self._bedTargetTemp = 0
self._tempOffset = 0
self._bedTempOffset = 0
self._commandQueue = queue.Queue() self._commandQueue = queue.Queue()
self._currentZ = None self._currentZ = None
self._heatupWaitStartTime = 0 self._heatupWaitStartTime = 0
@ -285,6 +287,9 @@ class MachineCom(object):
def getBedTemp(self): def getBedTemp(self):
return self._bedTemp return self._bedTemp
def getOffsets(self):
return (self._tempOffset, self._bedTempOffset)
##~~ external interface ##~~ external interface
def close(self, isError = False): def close(self, isError = False):
@ -304,6 +309,13 @@ class MachineCom(object):
eventManager().fire("PrintFailed") eventManager().fire("PrintFailed")
eventManager().fire("Disconnected") 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): def sendCommand(self, cmd):
cmd = cmd.encode('ascii', 'replace') cmd = cmd.encode('ascii', 'replace')
if self.isPrinting() and not self.isSdFileSelected(): if self.isPrinting() and not self.isSdFileSelected():
@ -357,7 +369,7 @@ class MachineCom(object):
return return
self.sendCommand("M23 %s" % filename) self.sendCommand("M23 %s" % filename)
else: else:
self._currentFile = PrintingGcodeFileInformation(filename) self._currentFile = PrintingGcodeFileInformation(filename, self.getOffsets)
eventManager().fire("FileSelected", filename) eventManager().fire("FileSelected", filename)
self._callback.mcFileSelected(filename, self._currentFile.getFilesize(), False) self._callback.mcFileSelected(filename, self._currentFile.getFilesize(), False)
@ -794,10 +806,6 @@ class MachineCom(object):
eventManager().fire("PrintDone", self._currentFile.getFilename()) eventManager().fire("PrintDone", self._currentFile.getFilename())
return return
if type(line) is tuple:
self._printSection = line[1]
line = line[0]
self._sendCommand(line, True) self._sendCommand(line, True)
self._callback.mcProgress() self._callback.mcProgress()
@ -1065,12 +1073,14 @@ class PrintingGcodeFileInformation(PrintingFileInformation):
that the file is closed in case of an error. that the file is closed in case of an error.
""" """
def __init__(self, filename): def __init__(self, filename, offsetCallback):
PrintingFileInformation.__init__(self, filename) PrintingFileInformation.__init__(self, filename)
self._filehandle = None self._filehandle = None
self._lineCount = None self._lineCount = None
self._firstLine = 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): if not os.path.exists(self._filename) or not os.path.isfile(self._filename):
raise IOError("File %s does not exist" % 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._filehandle = open(self._filename, "r")
self._lineCount = None self._lineCount = None
self._prevLineType = "CUSTOM"
self._startTime = None self._startTime = None
def getNext(self): def getNext(self):
@ -1121,17 +1130,28 @@ class PrintingGcodeFileInformation(PrintingFileInformation):
raise e raise e
def _processLine(self, line): def _processLine(self, line):
lineType = self._prevLineType
if line.startswith(";TYPE:"):
lineType = line[6:].strip()
if ";" in line: if ";" in line:
line = line[0:line.find(";")] line = line[0:line.find(";")]
line = line.strip() line = line.strip()
if len(line) > 0: if len(line) > 0:
if self._prevLineType != lineType: (tempOffset, bedTempOffset) = self._offsetCallback()
return line, lineType if tempOffset != 0 or bedTempOffset != 0:
else: tempMatch = self._tempCommandPattern.match(line)
return 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: else:
return None return None