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.
"""
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)
}

View File

@ -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"])

View File

@ -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) {

View File

@ -256,12 +256,12 @@
<div class="form-horizontal span6">
<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">
<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">
<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>
@ -284,16 +284,24 @@
</ul>
</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 class="form-horizontal span6">
<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">
<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">
<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>
@ -316,6 +324,14 @@
</ul>
</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>

View File

@ -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