Merge branch 'sdcard' into events

Conflicts:
	octoprint/printer.py
	octoprint/server.py
	octoprint/util/comm.py
master
Gina Häußge 2013-05-27 01:33:54 +02:00
commit d571aa588a
10 changed files with 1259 additions and 183 deletions

View File

@ -66,6 +66,11 @@ class Printer():
self._filename = None
self._gcodeLoader = None
# sd handling
self._sdPrinting = False
self._sdFile = None
self._sdStreamer = None
# feedrate
self._feedrateModifierMapping = {"outerWall": "WALL-OUTER", "innerWall": "WALL_INNER", "fill": "FILL", "support": "SUPPORT"}
@ -90,6 +95,7 @@ class Printer():
state={"state": None, "stateString": self.getStateString(), "flags": self._getStateFlags()},
jobData={"filename": None, "lines": None, "estimatedPrintTime": None, "filament": None},
gcodeData={"filename": None, "progress": None},
sdUploadData={"filename": None, "progress": None},
progress={"progress": None, "printTime": None, "printTimeLeft": None},
currentZ=None
)
@ -124,6 +130,11 @@ class Printer():
try: callback.sendCurrentData(copy.deepcopy(data))
except: pass
def _sendTriggerUpdateCallbacks(self, type):
for callback in self._callbacks:
try: callback.sendUpdateTrigger(type)
except: pass
#~~ printer commands
def connect(self, port=None, baudrate=None):
@ -171,6 +182,7 @@ class Printer():
if (self._comm is not None and self._comm.isPrinting()) or (self._gcodeLoader is not None):
return
self._sdFile = None
self._setJobData(None, None)
onGcodeLoadedCallback = self._onGcodeLoaded
@ -189,14 +201,19 @@ class Printer():
"""
if self._comm is None or not self._comm.isOperational():
return
if self._gcodeList is None:
if self._gcodeList is None and self._sdFile is None:
return
if self._comm.isPrinting():
return
self._setCurrentZ(-1)
self._comm.printGCode(self._gcodeList)
if self._sdFile is not None:
# we are working in sd mode
self._sdPrinting = True
self._comm.printSdFile()
else:
# we are working in local mode
self._comm.printGCode(self._gcodeList)
def togglePausePrint(self):
"""
@ -212,16 +229,20 @@ class Printer():
"""
if self._comm is None:
return
if self._sdPrinting:
self._sdPrinting = False
self._comm.cancelPrint()
if disableMotorsAndHeater:
self.commands(["M84", "M104 S0", "M140 S0", "M106 S0"]) # disable motors, switch off heaters and fan
# reset line, height, print time
self._setCurrentZ(None)
self._setProgressData(None, None, None)
self._setProgressData(None, None, None, None)
# mark print as failure
if self._filename:
if self._filename is not None:
self._gcodeManager.printFailed(self._filename)
eventManager().fire("PrintFailed", self._filename)
@ -258,7 +279,7 @@ class Printer():
self._messages = self._messages[-300:]
self._stateMonitor.addMessage(message)
def _setProgressData(self, progress, printTime, printTimeLeft):
def _setProgressData(self, progress, currentLine, printTime, printTimeLeft):
self._progress = progress
self._printTime = printTime
self._printTimeLeft = printTimeLeft
@ -271,7 +292,7 @@ class Printer():
if (self._printTimeLeft):
formattedPrintTimeLeft = util.getFormattedTimeDelta(datetime.timedelta(minutes=self._printTimeLeft))
self._stateMonitor.setProgress({"progress": self._progress, "printTime": formattedPrintTime, "printTimeLeft": formattedPrintTimeLeft})
self._stateMonitor.setProgress({"progress": self._progress, "currentLine": currentLine, "printTime": formattedPrintTime, "printTimeLeft": formattedPrintTimeLeft})
def _addTemperatureData(self, temp, bedTemp, targetTemp, bedTargetTemp):
currentTimeUtc = int(time.time() * 1000)
@ -333,6 +354,11 @@ class Printer():
pass
def _getStateFlags(self):
if not settings().getBoolean(["feature", "sdSupport"]) or self._comm is None:
sdReady = False
else:
sdReady = self._comm.isSdReady()
return {
"operational": self.isOperational(),
"printing": self.isPrinting(),
@ -340,7 +366,8 @@ class Printer():
"error": self.isError(),
"loading": self.isLoading(),
"paused": self.isPaused(),
"ready": self.isReady()
"ready": self.isReady(),
"sdReady": sdReady
}
def getCurrentData(self):
@ -376,13 +403,12 @@ class Printer():
self._gcodeManager.printSucceeded(self._filename)
elif state == self._comm.STATE_CLOSED or state == self._comm.STATE_ERROR or state == self._comm.STATE_CLOSED_WITH_ERROR:
self._gcodeManager.printFailed(self._filename)
self._gcodeManager.resumeAnalysis() # do not analyse gcode while printing
self._gcodeManager.resumeAnalysis() # printing done, put those cpu cycles to good use
elif self._comm is not None and state == self._comm.STATE_PRINTING:
self._gcodeManager.pauseAnalysis() # printing done, put those cpu cycles to good use
self._gcodeManager.pauseAnalysis() # do not analyse gcode while printing
self._setState(state)
def mcMessage(self, message):
"""
Callback method for the comm object, called upon message exchanges via serial.
@ -390,18 +416,28 @@ class Printer():
"""
self._addMessage(message)
def mcProgress(self, lineNr):
def mcProgress(self):
"""
Callback method for the comm object, called upon any change in progress of the printjob.
Triggers storage of new values for printTime, printTimeLeft and the current line.
"""
oldProgress = self._progress
if self._timelapse is not None:
try: self._timelapse.onPrintjobProgress(oldProgress, self._progress, int(round(self._progress * 100 / len(self._gcodeList))))
except: pass
if self._sdPrinting:
newLine = None
(filePos, fileSize) = self._comm.getSdProgress()
if fileSize > 0:
newProgress = float(filePos) / float(fileSize)
else:
newProgress = 0.0
else:
newLine = self._comm.getPrintPos()
if self._gcodeList is not None:
newProgress = float(newLine) / float(len(self._gcodeList))
else:
newProgress = 0.0
self._setProgressData(self._comm.getPrintPos(), self._comm.getPrintTime(), self._comm.getPrintTimeRemainingEstimate())
self._setProgressData(newProgress, newLine, self._comm.getPrintTime(), self._comm.getPrintTimeRemainingEstimate())
def mcZChange(self, newZ):
"""
@ -418,6 +454,83 @@ class Printer():
self._setCurrentZ(newZ)
def mcSdStateChange(self, sdReady):
self._stateMonitor.setState({"state": self._state, "stateString": self.getStateString(), "flags": self._getStateFlags()})
def mcSdFiles(self, files):
self._sendTriggerUpdateCallbacks("gcodeFiles")
def mcSdSelected(self, filename, filesize):
self._sdFile = filename
self._setJobData(filename, None)
self._stateMonitor.setState({"state": self._state, "stateString": self.getStateString(), "flags": self._getStateFlags()})
if self._sdPrintAfterSelect:
self.startPrint()
def mcSdPrintingDone(self):
self._sdPrinting = False
self._setProgressData(1.0, None, self._comm.getPrintTime(), self._comm.getPrintTimeRemainingEstimate())
self._stateMonitor.setState({"state": self._state, "stateString": self.getStateString(), "flags": self._getStateFlags()})
#~~ sd file handling
def getSdFiles(self):
if self._comm is None:
return
return self._comm.getSdFiles()
def addSdFile(self, filename, file):
if not self._comm:
return
self._sdStreamer = SdFileStreamer(self._comm, filename, file, self._onSdFileStreamProgress, self._onSdFileStreamFinish)
self._sdStreamer.start()
def deleteSdFile(self, filename):
if not self._comm:
return
if self._sdFile == filename:
self._sdFile = None
self._comm.deleteSdFile(filename)
def selectSdFile(self, filename, printAfterSelect):
if not self._comm:
return
self._sdPrintAfterSelect = printAfterSelect
self._comm.selectSdFile(filename)
def initSdCard(self):
if not self._comm:
return
self._comm.initSdCard()
def releaseSdCard(self):
if not self._comm:
return
self._comm.releaseSdCard()
def refreshSdFiles(self):
if not self._comm:
return
self._comm.refreshSdFiles()
#~~ callbacks triggered by sdFileStreamer
def _onSdFileStreamProgress(self, filename, progress):
self._stateMonitor.setSdUploadData({"filename": filename, "progress": progress})
def _onSdFileStreamFinish(self, filename):
self._setCurrentZ(None)
self._setProgressData(None, None, None, None)
self._sdStreamer = None
self._stateMonitor.setSdUploadData({"filename": None, "progress": None})
self._stateMonitor.setState({"state": self._state, "stateString": self.getStateString(), "flags": self._getStateFlags()})
#~~ callbacks triggered by gcodeLoader
def _onGcodeLoadingProgress(self, filename, progress, mode):
@ -430,7 +543,7 @@ class Printer():
def _onGcodeLoaded(self, filename, gcodeList):
self._setJobData(filename, gcodeList)
self._setCurrentZ(None)
self._setProgressData(None, None, None)
self._setProgressData(None, None, None, None)
self._gcodeLoader = None
self._stateMonitor.setGcodeData({"filename": None, "progress": None})
self._stateMonitor.setState({"state": self._state, "stateString": self.getStateString(), "flags": self._getStateFlags()})
@ -481,10 +594,10 @@ class Printer():
return self._comm is not None and self._comm.isError()
def isReady(self):
return self._gcodeLoader is None and self._gcodeList and len(self._gcodeList) > 0
return self._gcodeLoader is None and self._sdStreamer is None and ((self._gcodeList and len(self._gcodeList) > 0) or self._sdFile)
def isLoading(self):
return self._gcodeLoader is not None
return self._gcodeLoader is not None or self._sdStreamer is not None
@ -507,7 +620,7 @@ class GcodeLoader(threading.Thread):
def run(self):
#Send an initial M110 to reset the line counter to zero.
prevLineType = lineType = "CUSTOM"
gcodeList = ["M110"]
gcodeList = ["M110 N0"]
filesize = os.stat(self._filename).st_size
with open(self._filename, "r") as file:
for line in file:
@ -533,6 +646,38 @@ class GcodeLoader(threading.Thread):
def _onParsingProgress(self, progress):
self._progressCallback(self._filename, progress, "parsing")
class SdFileStreamer(threading.Thread):
def __init__(self, comm, filename, file, progressCallback, finishCallback):
threading.Thread.__init__(self)
self._comm = comm
self._filename = filename
self._file = file
self._progressCallback = progressCallback
self._finishCallback = finishCallback
def run(self):
if self._comm.isBusy():
return
name = self._filename[:self._filename.rfind(".")]
sdFilename = name[:8] + ".GCO"
try:
size = os.stat(self._file).st_size
with open(self._file, "r") as f:
self._comm.startSdFileTransfer(sdFilename)
for line in f:
if ";" in line:
line = line[0:line.find(";")]
line = line.strip()
if len(line) > 0:
self._comm.sendCommand(line)
time.sleep(0.001) # do not send too fast
self._progressCallback(sdFilename, float(f.tell()) / float(size))
finally:
self._comm.endSdFileTransfer(sdFilename)
self._finishCallback(sdFilename)
class StateMonitor(object):
def __init__(self, ratelimit, updateCallback, addTemperatureCallback, addLogCallback, addMessageCallback):
self._ratelimit = ratelimit
@ -544,6 +689,7 @@ class StateMonitor(object):
self._state = None
self._jobData = None
self._gcodeData = None
self._sdUploadData = None
self._currentZ = None
self._peakZ = -1
self._progress = None
@ -555,10 +701,11 @@ class StateMonitor(object):
self._worker.daemon = True
self._worker.start()
def reset(self, state=None, jobData=None, gcodeData=None, progress=None, currentZ=None):
def reset(self, state=None, jobData=None, gcodeData=None, sdUploadData=None, progress=None, currentZ=None):
self.setState(state)
self.setJobData(jobData)
self.setGcodeData(gcodeData)
self.setSdUploadData(sdUploadData)
self.setProgress(progress)
self.setCurrentZ(currentZ)
@ -590,6 +737,10 @@ class StateMonitor(object):
self._gcodeData = gcodeData
self._changeEvent.set()
def setSdUploadData(self, uploadData):
self._sdUploadData = uploadData
self._changeEvent.set()
def setProgress(self, progress):
self._progress = progress
self._changeEvent.set()
@ -614,6 +765,7 @@ class StateMonitor(object):
"state": self._state,
"job": self._jobData,
"gcode": self._gcodeData,
"sdUpload": self._sdUploadData,
"currentZ": self._currentZ,
"progress": self._progress
}

View File

@ -126,7 +126,8 @@ def index():
enableTimelapse=(settings().get(["webcam", "snapshot"]) is not None and settings().get(["webcam", "ffmpeg"]) is not None),
enableGCodeVisualizer=settings().get(["feature", "gCodeVisualizer"]),
enableSystemMenu=settings().get(["system"]) is not None and settings().get(["system", "actions"]) is not None and len(settings().get(["system", "actions"])) > 0,
enableAccessControl=userManager is not None
enableAccessControl=userManager is not None,
enableSdSupport=settings().get(["feature", "sdSupport"])
)
#~~ Printer control
@ -261,11 +262,40 @@ def getCustomControls():
customControls = settings().get(["controls"])
return jsonify(controls=customControls)
@app.route(BASEURL + "control/sd", methods=["POST"])
@login_required
def sdCommand():
if not settings().getBoolean(["feature", "sdSupport"]) or not printer.isOperational() or printer.isPrinting():
return jsonify(SUCCESS)
if "command" in request.values.keys():
command = request.values["command"]
if command == "init":
printer.initSdCard()
elif command == "refresh":
printer.refreshSdFiles()
elif command == "release":
printer.releaseSdCard()
return jsonify(SUCCESS)
#~~ GCODE file handling
@app.route(BASEURL + "gcodefiles", methods=["GET"])
def readGcodeFiles():
return jsonify(files=gcodeManager.getAllFileData())
files = gcodeManager.getAllFileData()
sdFileList = printer.getSdFiles()
if sdFileList is not None:
for sdFile in sdFileList:
files.append({
"name": sdFile,
"size": "n/a",
"bytes": 0,
"date": "n/a",
"origin": "sd"
})
return jsonify(files=files)
@app.route(BASEURL + "gcodefiles/<path:filename>", methods=["GET"])
def readGcodeFile(filename):
@ -278,6 +308,8 @@ def uploadGcodeFile():
if "gcode_file" in request.files.keys():
file = request.files["gcode_file"]
filename = gcodeManager.addFile(file)
if filename and "target" in request.values.keys() and request.values["target"] == "sd":
printer.addSdFile(filename, gcodeManager.getAbsolutePath(filename))
global eventManager
eventManager.fire("Upload", filename)
@ -290,12 +322,17 @@ def loadGcodeFile():
printAfterLoading = False
if "print" in request.values.keys() and request.values["print"] in valid_boolean_trues:
printAfterLoading = True
filename = gcodeManager.getAbsolutePath(request.values["filename"])
if filename is not None:
printer.loadGcode(filename, printAfterLoading)
global eventManager
eventManager.fire("LoadStart", filename)
if "target" in request.values.keys() and request.values["target"] == "sd":
filename = request.values["filename"]
printer.selectSdFile(filename, printAfterLoading)
else:
filename = gcodeManager.getAbsolutePath(request.values["filename"])
if filename is not None:
printer.loadGcode(filename, printAfterLoading)
global eventManager
eventManager.fire("LoadStart", filename)
return jsonify(SUCCESS)
@app.route(BASEURL + "gcodefiles/delete", methods=["POST"])
@ -303,9 +340,17 @@ def loadGcodeFile():
def deleteGcodeFile():
if "filename" in request.values.keys():
filename = request.values["filename"]
gcodeManager.removeFile(filename)
if "target" in request.values.keys() and request.values["target"] == "sd":
printer.deleteSdFile(filename)
else:
gcodeManager.removeFile(filename)
return readGcodeFiles()
@app.route(BASEURL + "gcodefiles/refresh", methods=["POST"])
def refreshFiles():
printer.updateSdFiles()
return jsonify(SUCCESS)
#~~ timelapse handling
@app.route(BASEURL + "timelapse", methods=["GET"])
@ -394,7 +439,10 @@ def getSettings():
},
"feature": {
"gcodeViewer": s.getBoolean(["feature", "gCodeVisualizer"]),
"waitForStart": s.getBoolean(["feature", "waitForStartOnConnect"])
"waitForStart": s.getBoolean(["feature", "waitForStartOnConnect"]),
"alwaysSendChecksum": s.getBoolean(["feature", "alwaysSendChecksum"]),
"resetLineNumbersWithPrefixedN": s.getBoolean(["feature", "resetLineNumbersWithPrefixedN"]),
"sdSupport": s.getBoolean(["feature", "sdSupport"])
},
"folder": {
"uploads": s.getBaseFolder("uploads"),
@ -439,6 +487,9 @@ def setSettings():
if "feature" in data.keys():
if "gcodeViewer" in data["feature"].keys(): s.setBoolean(["feature", "gCodeVisualizer"], data["feature"]["gcodeViewer"])
if "waitForStart" in data["feature"].keys(): s.setBoolean(["feature", "waitForStartOnConnect"], data["feature"]["waitForStart"])
if "alwaysSendChecksum" in data["feature"].keys(): s.setBoolean(["feature", "alwaysSendChecksum"], data["feature"]["alwaysSendChecksum"])
if "resetLineNumbersWithPrefixedN" in data["feature"].keys(): s.setBoolean(["feature", "resetLineNumbersWithPrefixedN"], data["feature"]["resetLineNumbersWithPrefixedN"])
if "sdSupport" in data["feature"].keys(): s.setBoolean(["feature", "sdSupport"], data["feature"]["sdSupport"])
if "folder" in data.keys():
if "uploads" in data["folder"].keys(): s.setBaseFolder("uploads", data["folder"]["uploads"])

View File

@ -39,13 +39,18 @@ default_settings = {
},
"feature": {
"gCodeVisualizer": True,
"waitForStartOnConnect": False
"waitForStartOnConnect": False,
"waitForWaitOnConnect": False,
"alwaysSendChecksum": False,
"resetLineNumbersWithPrefixedN": False,
"sdSupport": False
},
"folder": {
"uploads": None,
"timelapse": None,
"timelapse_tmp": None,
"logs": None
"logs": None,
"virtualSd": None
},
"temperature": {
"profiles":

View File

@ -111,10 +111,10 @@ body {
.octoprint-container {
.accordion-heading {
.settings-trigger {
.accordion-heading-button {
float: right;
.dropdown-toggle {
a {
display: inline-block;
padding: 8px 15px;
font-size: 14px;
@ -145,6 +145,9 @@ body {
padding-right: 4px;
}
.upload-buttons .btn {
margin-right: 0;
}
/** Tables */
@ -399,3 +402,127 @@ ul.dropdown-menu li a {
overflow: visible !important;
}
#drop_overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 10000;
display: none;
&.in {
display: block;
}
#drop_overlay_background {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: #000000;
filter:alpha(opacity=50);
-moz-opacity:0.5;
-khtml-opacity: 0.5;
opacity: 0.5;
}
#drop_overlay_wrapper {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
padding-top: 60px;
@dropzone_width: 400px;
@dropzone_height: 400px;
@dropzone_distance: 50px;
@dropzone_border: 2px;
#drop, #drop_background {
position: absolute;
top: 50%;
left: 50%;
margin-left: -1 * @dropzone_width / 2;
margin-top: -1 * @dropzone_height / 2;
}
#drop_locally, #drop_locally_background {
position: absolute;
top: 50%;
left: 50%;
margin-left: -1 * @dropzone_width - @dropzone_distance / 2;
margin-top: -1 * @dropzone_height / 2;
}
#drop_sd, #drop_sd_background {
position: absolute;
top: 50%;
left: 50%;
margin-left: @dropzone_distance / 2;
margin-top: -1 * @dropzone_height / 2;
}
.dropzone {
width: @dropzone_width + 2 * @dropzone_border;
height: @dropzone_height + 2 * @dropzone_border;
z-index: 10001;
color: #ffffff;
font-size: 30px;
i {
font-size: 50px;
}
.centered {
display: table-cell;
text-align: center;
vertical-align: middle;
width: @dropzone_width;
height: @dropzone_height;
line-height: 40px;
filter:alpha(opacity=100);
-moz-opacity:1.0;
-khtml-opacity: 1.0;
opacity: 1.0;
}
}
.dropzone_background {
width: @dropzone_width;
height: @dropzone_height;
border: 2px dashed #eeeeee;
-webkit-border-radius: 10px;
-moz-border-radius: 10px;
border-radius: 10px;
background-color: #000000;
filter:alpha(opacity=25);
-moz-opacity:0.25;
-khtml-opacity: 0.25;
opacity: 0.25;
&.hover {
background-color: #000000;
filter:alpha(opacity=50);
-moz-opacity:0.5;
-khtml-opacity: 0.5;
opacity: 0.5;
}
&.fade {
-webkit-transition: all 0.3s ease-out;
-moz-transition: all 0.3s ease-out;
-ms-transition: all 0.3s ease-out;
-o-transition: all 0.3s ease-out;
transition: all 0.3s ease-out;
opacity: 1;
}
}
}
}

View File

@ -213,12 +213,14 @@ function PrinterStateViewModel(loginStateViewModel) {
self.isError = ko.observable(undefined);
self.isReady = ko.observable(undefined);
self.isLoading = ko.observable(undefined);
self.isSdReady = ko.observable(undefined);
self.filename = ko.observable(undefined);
self.filament = ko.observable(undefined);
self.estimatedPrintTime = ko.observable(undefined);
self.printTime = ko.observable(undefined);
self.printTimeLeft = ko.observable(undefined);
self.progress = ko.observable(undefined);
self.currentLine = ko.observable(undefined);
self.totalLines = ko.observable(undefined);
self.currentHeight = ko.observable(undefined);
@ -229,10 +231,10 @@ function PrinterStateViewModel(loginStateViewModel) {
var currentLine = self.currentLine() ? self.currentLine() : "-";
return currentLine + " / " + self.totalLines();
});
self.progress = ko.computed(function() {
if (!self.currentLine() || !self.totalLines())
self.progressString = ko.computed(function() {
if (!self.progress())
return 0;
return Math.round(self.currentLine() * 100 / self.totalLines());
return self.progress();
});
self.pauseString = ko.computed(function() {
if (self.isPaused())
@ -253,6 +255,7 @@ function PrinterStateViewModel(loginStateViewModel) {
self._processStateData(data.state)
self._processJobData(data.job);
self._processGcodeData(data.gcode);
self._processSdUploadData(data.sdUpload);
self._processProgressData(data.progress);
self._processZData(data.currentZ);
}
@ -266,6 +269,7 @@ function PrinterStateViewModel(loginStateViewModel) {
self.isError(data.flags.error);
self.isReady(data.flags.ready);
self.isLoading(data.flags.loading);
self.isSdReady(data.flags.sdReady);
}
self._processJobData = function(data) {
@ -286,8 +290,20 @@ function PrinterStateViewModel(loginStateViewModel) {
}
}
self._processSdUploadData = function(data) {
if (self.isLoading()) {
var progress = Math.round(data.progress * 100);
self.filename("Streaming... (" + progress + "%)");
}
}
self._processProgressData = function(data) {
self.currentLine(data.progress);
if (data.progress) {
self.progress(Math.round(data.progress * 100));
} else {
self.progress(undefined);
}
self.currentLine(data.currentLine);
self.printTime(data.printTime);
self.printTimeLeft(data.printTimeLeft);
}
@ -742,6 +758,7 @@ function GcodeFilesViewModel(loginStateViewModel) {
self.isError = ko.observable(undefined);
self.isReady = ko.observable(undefined);
self.isLoading = ko.observable(undefined);
self.isSdReady = ko.observable(undefined);
// initialize list helper
self.listHelper = new ItemListHelper(
@ -754,14 +771,14 @@ function GcodeFilesViewModel(loginStateViewModel) {
return 0;
},
"upload": function(a, b) {
// sorts descending
if (a["date"] > b["date"]) return -1;
if (a["date"] < b["date"]) return 1;
return 0;
// sorts descending
if (b["date"] === undefined || a["date"] > b["date"]) return -1;
if (a["date"] < b["date"]) return 1;
return 0;
},
"size": function(a, b) {
// sorts descending
if (a["bytes"] > b["bytes"]) return -1;
if (b["bytes"] === undefined || a["bytes"] > b["bytes"]) return -1;
if (a["bytes"] < b["bytes"]) return 1;
return 0;
}
@ -769,10 +786,17 @@ function GcodeFilesViewModel(loginStateViewModel) {
{
"printed": function(file) {
return !(file["prints"] && file["prints"]["success"] && file["prints"]["success"] > 0);
},
"sd": function(file) {
return file["origin"] && file["origin"] == "sd";
},
"local": function(file) {
return !(file["origin"] && file["origin"] == "sd");
}
},
"name",
[],
[["sd", "local"]],
CONFIG_GCODEFILESPERPAGE
);
@ -800,6 +824,7 @@ function GcodeFilesViewModel(loginStateViewModel) {
self.isError(data.flags.error);
self.isReady(data.flags.ready);
self.isLoading(data.flags.loading);
self.isSdReady(data.flags.sdReady);
}
self.requestData = function() {
@ -823,24 +848,51 @@ function GcodeFilesViewModel(loginStateViewModel) {
}
self.loadFile = function(filename, printAfterLoad) {
var file = self.listHelper.getItem(function(item) {return item.name == filename});
if (!file) return;
$.ajax({
url: AJAX_BASEURL + "gcodefiles/load",
type: "POST",
dataType: "json",
data: {filename: filename, print: printAfterLoad}
data: {filename: filename, print: printAfterLoad, target: file.origin}
})
}
self.removeFile = function(filename) {
var file = self.listHelper.getItem(function(item) {return item.name == filename});
if (!file) return;
$.ajax({
url: AJAX_BASEURL + "gcodefiles/delete",
type: "POST",
dataType: "json",
data: {filename: filename},
data: {filename: filename, target: file.origin},
success: self.fromResponse
})
}
self.initSdCard = function() {
self._sendSdCommand("init");
}
self.releaseSdCard = function() {
self._sendSdCommand("release");
}
self.refreshSdFiles = function() {
self._sendSdCommand("refresh");
}
self._sendSdCommand = function(command) {
$.ajax({
url: AJAX_BASEURL + "control/sd",
type: "POST",
dataType: "json",
data: {command: command}
});
}
self.getPopoverContent = function(data) {
var output = "<p><strong>Uploaded:</strong> " + data["date"] + "</p>";
if (data["gcodeAnalysis"]) {
@ -917,6 +969,7 @@ function TimelapseViewModel(loginStateViewModel) {
},
"name",
[],
[],
CONFIG_TIMELAPSEFILESPERPAGE
)
@ -996,13 +1049,15 @@ function GcodeViewModel(loginStateViewModel) {
self.status = 'idle';
self.enabled = false;
self.errorCount = 0;
self.initialize = function(){
self.enabled = true;
GCODE.ui.initHandlers();
}
self.loadFile = function(filename){
if (self.status == 'idle') {
if (self.status == 'idle' && self.errorCount < 3) {
self.status = 'request';
$.ajax({
url: AJAX_BASEURL + "gcodefiles/" + filename,
@ -1016,6 +1071,7 @@ function GcodeViewModel(loginStateViewModel) {
},
error: function() {
self.status = 'idle';
self.errorCount++;
}
})
}
@ -1045,6 +1101,7 @@ function GcodeViewModel(loginStateViewModel) {
GCODE.renderer.render(cmdIndex.layer, 0, cmdIndex.cmd);
GCODE.ui.updateLayerInfo(cmdIndex.layer);
}
self.errorCount = 0
} else if (data.job.filename) {
self.loadFile(data.job.filename);
}
@ -1198,7 +1255,7 @@ function UsersViewModel(loginStateViewModel) {
if (user === undefined) return;
if (user.name == loginStateViewModel.username()) {
// we do not allow to delete ourself
// we do not allow to delete ourselves
$.pnotify({title: "Not possible", text: "You may not delete your own account.", type: "error"});
return;
}
@ -1267,6 +1324,9 @@ function SettingsViewModel(loginStateViewModel, usersViewModel) {
self.feature_gcodeViewer = ko.observable(undefined);
self.feature_waitForStart = ko.observable(undefined);
self.feature_alwaysSendChecksum = ko.observable(undefined);
self.feature_resetLineNumbersWithPrefixedN = ko.observable(undefined);
self.feature_sdSupport = ko.observable(undefined);
self.folder_uploads = ko.observable(undefined);
self.folder_timelapse = ko.observable(undefined);
@ -1311,6 +1371,9 @@ function SettingsViewModel(loginStateViewModel, usersViewModel) {
self.feature_gcodeViewer(response.feature.gcodeViewer);
self.feature_waitForStart(response.feature.waitForStart);
self.feature_alwaysSendChecksum(response.feature.alwaysSendChecksum);
self.feature_resetLineNumbersWithPrefixedN(response.feature.resetLineNumbersWithPrefixedN);
self.feature_sdSupport(response.feature.sdSupport);
self.folder_uploads(response.folder.uploads);
self.folder_timelapse(response.folder.timelapse);
@ -1343,7 +1406,11 @@ function SettingsViewModel(loginStateViewModel, usersViewModel) {
},
"feature": {
"gcodeViewer": self.feature_gcodeViewer(),
"waitForStart": self.feature_waitForStart()
"waitForStart": self.feature_waitForStart(),
"alwaysSendChecksum": self.feature_alwaysSendChecksum(),
"resetLineNumbersWithPrefixedN": self.feature_resetLineNumbersWithPrefixedN(),
"waitForStart": self.feature_waitForStart(),
"sdSupport": self.feature_sdSupport()
},
"folder": {
"uploads": self.folder_uploads(),
@ -1427,6 +1494,7 @@ function DataUpdater(loginStateViewModel, connectionViewModel, printerStateViewM
self.timelapseViewModel.requestData();
$("#webcam_image").attr("src", CONFIG_WEBCAM_STREAM + "?" + new Date().getTime());
self.loginStateViewModel.requestData();
self.gcodeFilesViewModel.requestData();
}
})
self._socket.on("disconnect", function() {
@ -1475,7 +1543,7 @@ function DataUpdater(loginStateViewModel, connectionViewModel, printerStateViewM
}
}
function ItemListHelper(listType, supportedSorting, supportedFilters, defaultSorting, defaultFilters, filesPerPage) {
function ItemListHelper(listType, supportedSorting, supportedFilters, defaultSorting, defaultFilters, exclusiveFilters, filesPerPage) {
var self = this;
self.listType = listType;
@ -1483,6 +1551,7 @@ function ItemListHelper(listType, supportedSorting, supportedFilters, defaultSor
self.supportedFilters = supportedFilters;
self.defaultSorting = defaultSorting;
self.defaultFilters = defaultFilters;
self.exclusiveFilters = exclusiveFilters;
self.allItems = [];
self.items = ko.observableArray([]);
@ -1574,6 +1643,17 @@ function ItemListHelper(listType, supportedSorting, supportedFilters, defaultSor
}
}
self.getItem = function(matcher) {
var itemList = self.items();
for (var i = 0; i < itemList.length; i++) {
if (matcher(itemList[i])) {
return itemList[i];
}
}
return undefined;
}
//~~ sorting
self.changeSorting = function(sorting) {
@ -1604,6 +1684,16 @@ function ItemListHelper(listType, supportedSorting, supportedFilters, defaultSor
if (!_.contains(_.keys(self.supportedFilters), filter))
return;
for (var i = 0; i < self.exclusiveFilters.length; i++) {
if (_.contains(self.exclusiveFilters[i], filter)) {
for (var j = 0; j < self.exclusiveFilters[i].length; j++) {
if (self.exclusiveFilters[i][j] == filter)
continue;
self.removeFilter(self.exclusiveFilters[i][j]);
}
}
}
var filters = self.currentFilters();
filters.push(filter);
self.currentFilters(filters);
@ -1613,7 +1703,7 @@ function ItemListHelper(listType, supportedSorting, supportedFilters, defaultSor
}
self.removeFilter = function(filter) {
if (filter != "printed")
if (!_.contains(_.keys(self.supportedFilters), filter))
return;
var filters = self.currentFilters();
@ -1765,8 +1855,6 @@ $(function() {
return false;
})
//~~ Print job control (should move to PrinterStateViewModel)
//~~ Temperature control (should really move to knockout click binding)
$("#temp_newTemp_set").click(function() {
@ -1828,23 +1916,103 @@ $(function() {
//~~ Gcode upload
function gcode_upload_done(e, data) {
gcodeFilesViewModel.fromResponse(data.result);
$("#gcode_upload_progress .bar").css("width", "0%");
$("#gcode_upload_progress").removeClass("progress-striped").removeClass("active");
$("#gcode_upload_progress .bar").text("");
}
function gcode_upload_progress(e, data) {
var progress = parseInt(data.loaded / data.total * 100, 10);
$("#gcode_upload_progress .bar").css("width", progress + "%");
$("#gcode_upload_progress .bar").text("Uploading ...");
if (progress >= 100) {
$("#gcode_upload_progress").addClass("progress-striped").addClass("active");
$("#gcode_upload_progress .bar").text("Saving ...");
}
}
var localTarget;
if (CONFIG_SD_SUPPORT) {
localTarget = $("#drop_locally");
} else {
localTarget = $("#drop");
}
$("#gcode_upload").fileupload({
dataType: "json",
done: function (e, data) {
gcodeFilesViewModel.fromResponse(data.result);
$("#gcode_upload_progress .bar").css("width", "0%");
$("#gcode_upload_progress").removeClass("progress-striped").removeClass("active");
$("#gcode_upload_progress .bar").text("");
},
progressall: function (e, data) {
var progress = parseInt(data.loaded / data.total * 100, 10);
$("#gcode_upload_progress .bar").css("width", progress + "%");
$("#gcode_upload_progress .bar").text("Uploading ...");
if (progress >= 100) {
$("#gcode_upload_progress").addClass("progress-striped").addClass("active");
$("#gcode_upload_progress .bar").text("Saving ...");
}
dropZone: localTarget,
formData: {target: "local"},
done: gcode_upload_done,
progressall: gcode_upload_progress
});
if (CONFIG_SD_SUPPORT) {
$("#gcode_upload_sd").fileupload({
dataType: "json",
dropZone: $("#drop_sd"),
formData: {target: "sd"},
done: gcode_upload_done,
progressall: gcode_upload_progress
});
}
$(document).bind("dragover", function (e) {
var dropOverlay = $("#drop_overlay");
var dropZone = $("#drop");
var dropZoneLocal = $("#drop_locally");
var dropZoneSd = $("#drop_sd");
var dropZoneBackground = $("#drop_background");
var dropZoneLocalBackground = $("#drop_locally_background");
var dropZoneSdBackground = $("#drop_sd_background");
var timeout = window.dropZoneTimeout;
if (!timeout) {
dropOverlay.addClass('in');
} else {
clearTimeout(timeout);
}
var foundLocal = false;
var foundSd = false;
var found = false
var node = e.target;
do {
if (dropZoneLocal && node === dropZoneLocal[0]) {
foundLocal = true;
break;
} else if (dropZoneSd && node === dropZoneSd[0]) {
foundSd = true;
break;
} else if (dropZone && node === dropZone[0]) {
found = true;
break;
}
node = node.parentNode;
} while (node != null);
if (foundLocal) {
dropZoneLocalBackground.addClass("hover");
dropZoneSdBackground.removeClass("hover");
} else if (foundSd) {
dropZoneSdBackground.addClass("hover");
dropZoneLocalBackground.removeClass("hover");
} else if (found) {
dropZoneBackground.addClass("hover");
} else {
if (dropZoneLocalBackground) dropZoneLocalBackground.removeClass("hover");
if (dropZoneSdBackground) dropZoneSdBackground.removeClass("hover");
if (dropZoneBackground) dropZoneBackground.removeClass("hover");
}
window.dropZoneTimeout = setTimeout(function () {
window.dropZoneTimeout = null;
dropOverlay.removeClass("in");
if (dropZoneLocal) dropZoneLocalBackground.removeClass("hover");
if (dropZoneSd) dropZoneSdBackground.removeClass("hover");
if (dropZone) dropZoneBackground.removeClass("hover");
}, 100);
});
//~~ Offline overlay
@ -1908,7 +2076,7 @@ $(function() {
} else {
$("#gcode_upload").fileupload("disable");
}
})
});
//~~ UI stuff
@ -1930,10 +2098,13 @@ $(function() {
}
// Fix input element click problem on login dialog
$('.dropdown input, .dropdown label').click(function(e) {
$(".dropdown input, .dropdown label").click(function(e) {
e.stopPropagation();
});
$(document).bind("drop dragover", function (e) {
e.preventDefault();
});
}
);

View File

@ -17,6 +17,21 @@
</div>
</div>
<div id="drop_overlay">
<div id="drop_overlay_background"></div>
<div id="drop_overlay_wrapper">
{% if enableSdSupport %}
<div class="dropzone" id="drop_locally"><span class="centered"><i class="icon-upload-alt"></i><br>Upload locally</span></div>
<div class="dropzone_background" id="drop_locally_background"></div>
<div class="dropzone" id="drop_sd"><span class="centered"><i class="icon-upload-alt"></i><br>Upload to SD</span></div>
<div class="dropzone_background" id="drop_sd_background"></div>
{% else %}
<div class="dropzone" id="drop"><span class="centered"><i class="icon-upload-alt"></i><br>Upload</span></div>
<div class="dropzone_background" id="drop_background"></div>
{% endif %}
</div>
</div>
<div id="confirmation_dialog" class="modal hide fade">
<div class="modal-header">
<a href="#" class="close" data-dismiss="modal" aria-hidden="true">&times;</a>

View File

@ -24,6 +24,7 @@
var CONFIG_USERSPERPAGE = 10;
var CONFIG_WEBCAM_STREAM = "{{ webcamStream }}";
var CONFIG_ACCESS_CONTROL = {% if enableAccessControl -%} true; {% else %} false; {%- endif %}
var CONFIG_SD_SUPPORT = {% if enableSdSupport -%} true; {% else %} false; {%- endif %}
var WEB_SOCKET_SWF_LOCATION = "{{ url_for('static', filename='js/socket.io/WebSocketMain.swf') }}";
var WEB_SOCKET_DEBUG = true;
@ -116,7 +117,7 @@
Print Time Left: <strong data-bind="text: printTimeLeft"></strong><br>
<div class="progress">
<div class="bar" id="job_progressBar" data-bind="style: { width: progress() + '%' }"></div>
<div class="bar" id="job_progressBar" data-bind="style: { width: progressString() + '%' }"></div>
</div>
<div class="row-fluid print-control" style="display: none;" data-bind="visible: loginState.isUser">
@ -131,7 +132,7 @@
<div class="accordion-heading">
<a class="accordion-toggle" data-toggle="collapse" href="#files"><i class="icon-list"></i> Files</a>
<div class="settings-trigger btn-group">
<div class="settings-trigger accordion-heading-button btn-group">
<a class="dropdown-toggle" data-toggle="dropdown" href="#">
<span class="icon-wrench"></span>
</a>
@ -139,10 +140,28 @@
<li><a href="#" data-bind="click: function() { $root.listHelper.changeSorting('name'); }"><i class="icon-ok" data-bind="style: {visibility: listHelper.currentSorting() == 'name' ? 'visible' : 'hidden'}"></i> Sort by name (ascending)</a></li>
<li><a href="#" data-bind="click: function() { $root.listHelper.changeSorting('upload'); }"><i class="icon-ok" data-bind="style: {visibility: listHelper.currentSorting() == 'upload' ? 'visible' : 'hidden'}"></i> Sort by upload date (descending)</a></li>
<li><a href="#" data-bind="click: function() { $root.listHelper.changeSorting('size'); }"><i class="icon-ok" data-bind="style: {visibility: listHelper.currentSorting() == 'size' ? 'visible' : 'hidden'}"></i> Sort by file size (descending)</a></li>
{% if enableSdSupport %}
<li class="divider"></li>
<li><a href="#" data-bind="click: function() { $root.listHelper.toggleFilter('local'); }"><i class="icon-ok" data-bind="style: {visibility: _.contains(listHelper.currentFilters(), 'local') ? 'visible' : 'hidden'}"></i> Only show files stored locally</a></li>
<li><a href="#" data-bind="click: function() { $root.listHelper.toggleFilter('sd'); }"><i class="icon-ok" data-bind="style: {visibility: _.contains(listHelper.currentFilters(), 'sd') ? 'visible' : 'hidden'}"></i> Only show files stored on SD</a></li>
{% endif %}
<li class="divider"></li>
<li><a href="#" data-bind="click: function() { $root.listHelper.toggleFilter('printed'); }"><i class="icon-ok" data-bind="style: {visibility: _.contains(listHelper.currentFilters(), 'printed') ? 'visible' : 'hidden'}"></i> Hide successfully printed files</a></li>
</ul>
</div>
{% if enableSdSupport %}
<div class="sd-trigger accordion-heading-button btn-group">
<a class="dropdown-toggle" data-toggle="dropdown" href="#">
<span class="icon-hdd"></span>
</a>
<ul class="dropdown-menu">
<li data-bind="visible: !isSdReady()"><a href="#" data-bind="click: function() { $root.initSdCard(); }"><i class="icon-flag"></i> Initialize SD card</a></li>
<li data-bind="visible: isSdReady()"><a href="#" data-bind="click: function() { $root.refreshSdFiles(); }"><i class="icon-refresh"></i> Refresh SD files</a></li>
<li data-bind="visible: isSdReady()"><a href="#" data-bind="click: function() { $root.releaseSdCard(); }"><i class="icon-eject"></i> Release SD card</a></li>
</ul>
</div>
{% endif %}
</div>
<div class="accordion-body collapse in overflow_visible" id="files">
<div class="accordion-inner">
@ -176,11 +195,26 @@
</ul>
</div>
<div style="display: none;" data-bind="visible: loginState.isUser">
<span class="btn btn-primary btn-block fileinput-button" data-bind="css: {disabled: !$root.loginState.isUser()}" style="margin-bottom: 10px">
<i class="icon-upload icon-white"></i>
<span>Upload</span>
<input id="gcode_upload" type="file" name="gcode_file" class="fileinput-button" data-url="/ajax/gcodefiles/upload" data-bind="enable: loginState.isUser()">
</span>
<div class="row-fluid upload-buttons">
{% if enableSdSupport %}
<button class="btn btn-primary fileinput-button span6" data-bind="css: {disabled: !$root.loginState.isUser()}" style="margin-bottom: 10px">
<i class="icon-upload-alt icon-white"></i>
<span>Upload</span>
<input id="gcode_upload" type="file" name="gcode_file" class="fileinput-button" data-url="/ajax/gcodefiles/upload" data-bind="enable: loginState.isUser()">
</button>
<button class="btn btn-primary fileinput-button span6" data-bind="css: {disabled: !$root.loginState.isUser()}" style="margin-bottom: 10px">
<i class="icon-upload-alt icon-white"></i>
<span>Upload to SD</span>
<input id="gcode_upload_sd" type="file" name="gcode_file" class="fileinput-button" data-url="/ajax/gcodefiles/upload" data-bind="enable: loginState.isUser()">
</button>
{% else %}
<button class="btn btn-primary fileinput-button span12" data-bind="css: {disabled: !$root.loginState.isUser()}" style="margin-bottom: 10px">
<i class="icon-upload-alt icon-white"></i>
<span>Upload</span>
<input id="gcode_upload" type="file" name="gcode_file" class="fileinput-button" data-url="/ajax/gcodefiles/upload" data-bind="enable: loginState.isUser()">
</button>
{% endif %}
</div>
<div id="gcode_upload_progress" class="progress" style="width: 100%;">
<div class="bar" style="width: 0%"></div>
</div>

View File

@ -103,7 +103,28 @@
<div class="control-group">
<div class="controls">
<label class="checkbox">
<input type="checkbox" data-bind="checked: feature_waitForStart" id="settings-featureWaitForStart"> Wait for start on connect
<input type="checkbox" data-bind="checked: feature_sdSupport" id="settings-featureSdSupport"> Enable SD support
</label>
</div>
</div>
<div class="control-group">
<div class="controls">
<label class="checkbox">
<input type="checkbox" data-bind="checked: feature_waitForStart" id="settings-featureWaitForStart"> Wait for <code>start</code> on connect
</label>
</div>
</div>
<div class="control-group">
<div class="controls">
<label class="checkbox">
<input type="checkbox" data-bind="checked: feature_alwaysSendChecksum" id="settings-featureAlwaysSendChecksum"> Send a checksum with <strong>every</strong> command <span class="label">Repetier</span>
</label>
</div>
</div>
<div class="control-group">
<div class="controls">
<label class="checkbox">
<input type="checkbox" data-bind="checked: feature_resetLineNumbersWithPrefixedN" id="settings-resetLineNumbersWithPrefixedN"> Send M110 commands with target line number as N-prefix <span class="label">Repetier</span>
</label>
</div>
</div>

View File

@ -53,9 +53,6 @@ class Timelapse(object):
def onPrintjobStopped(self):
self.stopTimelapse()
def onPrintjobProgress(self, oldPos, newPos, percentage):
pass
def onZChange(self, oldZ, newZ):
pass

View File

@ -73,16 +73,38 @@ gcodeToEvent = {
class VirtualPrinter():
def __init__(self):
self.readList = ['start\n', 'Marlin: Virtual Marlin!\n', '\x80\n']
self.readList = ['start\n', 'Marlin: Virtual Marlin!\n', '\x80\n', 'SD init fail\n'] # no sd card as default startup scenario
self.temp = 0.0
self.targetTemp = 0.0
self.lastTempAt = time.time()
self.bedTemp = 1.0
self.bedTargetTemp = 1.0
self._virtualSd = settings().getBaseFolder("virtualSd")
self._sdCardReady = False
self._sdPrinter = None
self._sdPrintingSemaphore = threading.Event()
self._selectedSdFile = None
self._selectedSdFileSize = None
self._selectedSdFilePos = None
self._writingToSd = False
self._newSdFilePos = None
self.currentLine = 0
waitThread = threading.Thread(target=self._sendWaitAfterTimeout)
waitThread.start()
def write(self, data):
if self.readList is None:
return
# shortcut for writing to SD
if self._writingToSd and not self._selectedSdFile is None and not "M29" in data:
with open(self._selectedSdFile, "a") as f:
f.write(data)
return
#print "Send: %s" % (data.rstrip())
if 'M104' in data or 'M109' in data:
try:
@ -94,11 +116,155 @@ class VirtualPrinter():
self.bedTargetTemp = float(re.search('S([0-9]+)', data).group(1))
except:
pass
if 'M105' in data:
# send simulated temperature data
self.readList.append("ok T:%.2f /%.2f B:%.2f /%.2f @:64\n" % (self.temp, self.targetTemp, self.bedTemp, self.bedTargetTemp))
elif 'M20' in data:
if self._sdCardReady:
self._listSd()
elif 'M21' in data:
self._sdCardReady = True
self.readList.append("SD card ok")
elif 'M22' in data:
self._sdCardReady = False
elif 'M23' in data:
if self._sdCardReady:
filename = data.split(None, 1)[1].strip()
self._selectSdFile(filename)
elif 'M24' in data:
if self._sdCardReady:
self._startSdPrint()
elif 'M25' in data:
if self._sdCardReady:
self._pauseSdPrint()
elif 'M26' in data:
if self._sdCardReady:
pos = int(re.search("S([0-9]+)", data).group(1))
self._setSdPos(pos)
elif 'M27' in data:
if self._sdCardReady:
self._reportSdStatus()
elif 'M28' in data:
if self._sdCardReady:
filename = data.split(None, 1)[1].strip()
self._writeSdFile(filename)
elif 'M29' in data:
if self._sdCardReady:
self._finishSdFile()
elif 'M30' in data:
if self._sdCardReady:
filename = data.split(None, 1)[1].strip()
self._deleteSdFile(filename)
elif "M110" in data:
# reset current line
self.currentLine = int(re.search('N([0-9]+)', data).group(1))
self.readList.append("ok\n")
elif self.currentLine == 100:
# simulate a resend at line 100 of the last 5 lines
self.readList.append("Error: Line Number is not Last Line Number\n")
self.readList.append("rs %d\n" % (self.currentLine - 5))
elif len(data.strip()) > 0:
self.readList.append("ok\n")
if "*" in data:
self.currentLine += 1
def _listSd(self):
self.readList.append("Begin file list")
for osFile in os.listdir(self._virtualSd):
self.readList.append(osFile.upper())
self.readList.append("End file list")
self.readList.append("ok")
def _selectSdFile(self, filename):
file = os.path.join(self._virtualSd, filename).lower()
if not os.path.exists(file) or not os.path.isfile(file):
self.readList.append("open failed, File: %s." % filename)
else:
self._selectedSdFile = file
self._selectedSdFileSize = os.stat(file).st_size
self.readList.append("File opened: %s Size: %d" % (filename, self._selectedSdFileSize))
self.readList.append("File selected")
def _startSdPrint(self):
if self._selectedSdFile is not None:
if self._sdPrinter is None:
self._sdPrinter = threading.Thread(target=self._sdPrintingWorker)
self._sdPrinter.start()
self._sdPrintingSemaphore.set()
self.readList.append("ok")
def _pauseSdPrint(self):
self._sdPrintingSemaphore.clear()
self.readList.append("ok")
def _setSdPos(self, pos):
self._newSdFilePos = pos
def _reportSdStatus(self):
if self._sdPrinter is not None and self._sdPrintingSemaphore.is_set:
self.readList.append("SD printing byte %d/%d" % (self._selectedSdFilePos, self._selectedSdFileSize))
else:
self.readList.append("Not SD printing")
def _writeSdFile(self, filename):
file = os.path.join(self._virtualSd, filename).lower()
if os.path.exists(file):
if os.path.isfile(file):
os.remove(file)
else:
self.readList.append("error writing to file")
self._writingToSd = True
self._selectedSdFile = file
self.readList.append("ok")
def _finishSdFile(self):
self._writingToSd = False
self._selectedSdFile = None
self.readList.append("ok")
def _sdPrintingWorker(self):
self._selectedSdFilePos = 0
with open(self._selectedSdFile, "r") as f:
for line in f:
# reset position if requested by client
if self._newSdFilePos is not None:
f.seek(self._newSdFilePos)
self._newSdFilePos = None
# read current file position
self._selectedSdFilePos = f.tell()
# if we are paused, wait for unpausing
self._sdPrintingSemaphore.wait()
# set target temps
if 'M104' in line or 'M109' in line:
try:
self.targetTemp = float(re.search('S([0-9]+)', line).group(1))
except:
pass
if 'M140' in line or 'M190' in line:
try:
self.bedTargetTemp = float(re.search('S([0-9]+)', line).group(1))
except:
pass
time.sleep(0.01)
self._sdPrintingSemaphore.clear()
self._selectedSdFilePos = 0
self._sdPrinter = None
self.readList.append("Done printing file")
def _deleteSdFile(self, filename):
file = os.path.join(self._virtualSd, filename)
if os.path.exists(file) and os.path.isfile(file):
os.remove(file)
self.readList.append("ok")
def readline(self):
if self.readList is None:
return ''
@ -121,12 +287,15 @@ class VirtualPrinter():
if self.readList is None:
return ''
time.sleep(0.001)
#print "Recv: %s" % (self.readList[0].rstrip())
return self.readList.pop(0)
def close(self):
self.readList = None
def _sendWaitAfterTimeout(self, timeout=5):
time.sleep(timeout)
self.readList.append("wait")
class MachineComPrintCallback(object):
def mcLog(self, message):
pass
@ -140,12 +309,24 @@ class MachineComPrintCallback(object):
def mcMessage(self, message):
pass
def mcProgress(self, lineNr):
def mcProgress(self):
pass
def mcZChange(self, newZ):
pass
def mcSdStateChange(self, sdReady):
pass
def mcSdFiles(self, files):
pass
def mcSdSelected(self, filename, size):
pass
def mcSdPrintingDone(self):
pass
class MachineCom(object):
STATE_NONE = 0
STATE_OPEN_SERIAL = 1
@ -158,6 +339,7 @@ class MachineCom(object):
STATE_CLOSED = 8
STATE_ERROR = 9
STATE_CLOSED_WITH_ERROR = 10
STATE_RECEIVING_FILE = 11
def __init__(self, port = None, baudrate = None, callbackObject = None):
self._logger = logging.getLogger(__name__)
@ -193,15 +375,42 @@ class MachineCom(object):
self._currentZ = -1
self._heatupWaitStartTime = 0
self._heatupWaitTimeLost = 0.0
self._printStartTime100 = None
self._printStartTime = None
self._alwaysSendChecksum = settings().getBoolean(["feature", "alwaysSendChecksum"])
self._currentLine = 1
self._resendDelta = None
self._lastLines = []
self._sendNextLock = threading.Lock()
self._sendingLock = threading.Lock()
self.thread = threading.Thread(target=self._monitor)
self.thread.daemon = True
self.thread.start()
self._sdAvailable = False
self._sdPrinting = False
self._sdFileList = False
self._sdFile = None
self._sdFilePos = None
self._sdFileSize = None
self._sdFiles = []
def _changeState(self, newState):
if self._state == newState:
return
if newState == self.STATE_CLOSED or newState == self.STATE_CLOSED_WITH_ERROR:
if settings().get(["feature", "sdSupport"]):
self._sdPrinting = False
self._sdFileList = False
self._sdFile = None
self._sdFilePos = None
self._sdFileSize = None
self._sdFiles = []
self._callback.mcSdFiles([])
oldState = self.getStateString()
self._state = newState
self._log('Changing monitoring state from \'%s\' to \'%s\'' % (oldState, self.getStateString()))
@ -224,7 +433,10 @@ class MachineCom(object):
if self._state == self.STATE_OPERATIONAL:
return "Operational"
if self._state == self.STATE_PRINTING:
return "Printing"
if self._sdPrinting:
return "Printing from SD"
else:
return "Printing"
if self._state == self.STATE_PAUSED:
return "Paused"
if self._state == self.STATE_CLOSED:
@ -233,6 +445,8 @@ class MachineCom(object):
return "Error: %s" % (self.getShortErrorString())
if self._state == self.STATE_CLOSED_WITH_ERROR:
return "Error: %s" % (self.getShortErrorString())
if self._state == self.STATE_RECEIVING_FILE:
return "Sending file to SD"
return "?%d?" % (self._state)
def getShortErrorString(self):
@ -250,31 +464,61 @@ class MachineCom(object):
return self._state == self.STATE_ERROR or self._state == self.STATE_CLOSED_WITH_ERROR
def isOperational(self):
return self._state == self.STATE_OPERATIONAL or self._state == self.STATE_PRINTING or self._state == self.STATE_PAUSED
return self._state == self.STATE_OPERATIONAL or self._state == self.STATE_PRINTING or self._state == self.STATE_PAUSED or self._state == self.STATE_RECEIVING_FILE
def isPrinting(self):
return self._state == self.STATE_PRINTING
def isSdPrinting(self):
return self._sdPrinting
def isPaused(self):
return self._state == self.STATE_PAUSED
def isBusy(self):
return self.isPrinting() or self._state == self.STATE_RECEIVING_FILE
def isSdReady(self):
return self._sdAvailable
def getPrintPos(self):
return self._gcodePos
if self._sdPrinting:
return self._sdFilePos
else:
return self._gcodePos
def getPrintTime(self):
if self._printStartTime100 == None:
if self._printStartTime == None:
return 0
else:
return time.time() - self._printStartTime100
return time.time() - self._printStartTime
def getPrintTimeRemainingEstimate(self):
if self._printStartTime100 == None or self.getPrintPos() < 200:
if self._printStartTime == None:
return None
printTime = (time.time() - self._printStartTime100) / 60
printTimeTotal = printTime * (len(self._gcodeList) - 100) / (self.getPrintPos() - 100)
printTimeLeft = printTimeTotal - printTime
return printTimeLeft
if self._sdPrinting:
printTime = (time.time() - self._printStartTime) / 60
if self._sdFilePos > 0:
printTimeTotal = printTime * self._sdFileSize / self._sdFilePos
else:
printTimeTotal = printTime * self._sdFileSize
printTimeLeft = printTimeTotal - printTime
self._logger.info("printTime: %f, sdFileSize: %f, sdFilePos: %f, printTimeTotal: %f, printTimeLeft: %f" % (printTime, self._sdFileSize, self._sdFilePos, printTimeTotal, printTimeLeft))
return printTimeLeft
else:
# for host printing we only start counting the print time at gcode line 100, so we need to calculate stuff
# a bit different here
if self.getPrintPos() < 200:
return None
printTime = (time.time() - self._printStartTime) / 60
printTimeTotal = printTime * (len(self._gcodeList) - 100) / (self.getPrintPos() - 100)
printTimeLeft = printTimeTotal - printTime
return printTimeLeft
def getSdProgress(self):
return (self._sdFilePos, self._sdFileSize)
def getTemp(self):
return self._temp
@ -335,13 +579,15 @@ class MachineCom(object):
#Start monitoring the serial port.
timeout = time.time() + 5
tempRequestTimeout = timeout
sdStatusRequestTimeout = timeout
startSeen = not settings().getBoolean(["feature", "waitForStartOnConnect"])
while True:
line = self._readline()
if line == None:
break
#No matter the state, if we see an error, goto the error state and store the error for reference.
##~~ Error handling
# No matter the state, if we see an error, goto the error state and store the error for reference.
if line.startswith('Error:'):
#Oh YEAH, consistency.
# Marlin reports an MIN/MAX temp error as "Error:x\n: Extruder switched off. MAXTEMP triggered !\n"
@ -350,7 +596,13 @@ class MachineCom(object):
if re.match('Error:[0-9]\n', line):
line = line.rstrip() + self._readline()
#Skip the communication errors, as those get corrected.
if 'checksum mismatch' in line or 'Line Number is not Last Line Number' in line or 'No Line Number with checksum' in line or 'No Checksum with line number' in line:
if 'checksum mismatch' in line \
or 'Wrong checksum' in line \
or 'Line Number is not Last Line Number' in line \
or 'expected line' in line \
or 'No Line Number with checksum' in line \
or 'No Checksum with line number' in line \
or 'Missing checksum' in line:
pass
elif not self.isError():
if self.isPrinting():
@ -358,20 +610,74 @@ class MachineCom(object):
self._errorValue = line[6:]
self._changeState(self.STATE_ERROR)
eventManager().fire("Error", self.getErrorString())
##~~ 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 not 'End file list' in line:
self._sdFiles.append(line)
continue
##~~ Temperature processing
if ' T:' in line or line.startswith('T:'):
self._temp = float(re.search("-?[0-9\.]*", line.split('T:')[1]).group(0))
if ' B:' in line:
self._bedTemp = float(re.search("-?[0-9\.]*", line.split(' B:')[1]).group(0))
self._callback.mcTempUpdate(self._temp, self._bedTemp, self._targetTemp, self._bedTargetTemp)
try:
self._temp = float(re.search("-?[0-9\.]*", line.split('T:')[1]).group(0))
if ' B:' in line:
self._bedTemp = float(re.search("-?[0-9\.]*", line.split(' B:')[1]).group(0))
self._callback.mcTempUpdate(self._temp, self._bedTemp, self._targetTemp, self._bedTargetTemp)
except ValueError:
# catch conversion issues, we'll rather just not get the temperature update instead of killing the connection
pass
#If we are waiting for an M109 or M190 then measure the time we lost during heatup, so we can remove that time from our printing time estimate.
if not 'ok' in line and self._heatupWaitStartTime != 0:
t = time.time()
self._heatupWaitTimeLost = t - self._heatupWaitStartTime
self._heatupWaitStartTime = t
elif line.strip() != '' and line.strip() != 'ok' and not line.startswith('Resend:') and line != 'echo:Unknown command:""\n' and self.isOperational():
##~~ SD Card handling
elif 'SD init fail' in line:
self._sdAvailable = False
self._sdFiles = []
self._callback.mcSdStateChange(self._sdAvailable)
elif 'SD card ok' in line:
self._sdAvailable = True
self.refreshSdFiles()
self._callback.mcSdStateChange(self._sdAvailable)
elif 'Begin file list' in line:
self._sdFiles = []
self._sdFileList = True
elif 'End file list' in line:
self._sdFileList = False
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)
self._sdFilePos = int(match.group(1))
self._sdFileSize = int(match.group(2))
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)
self._sdFile = match.group(1)
self._sdFileSize = int(match.group(2))
elif 'File selected' in line:
# final answer to M23, at least on Marlin, Repetier and Sprinter: "File selected"
self._callback.mcSdSelected(self._sdFile, self._sdFileSize)
elif 'Done printing file' in line:
# printer is reporting file finished printing
self._sdPrinting = False
self._sdFilePos = 0
self._changeState(self.STATE_OPERATIONAL)
self._callback.mcSdPrintingDone()
##~~ Message handling
elif line.strip() != '' and line.strip() != 'ok' and not line.startswith("wait") and not line.startswith('Resend:') and line != 'echo:Unknown command:""\n' and self.isOperational():
self._callback.mcMessage(line)
### Baudrate detection
if self._state == self.STATE_DETECT_BAUDRATE:
if line == '' or time.time() > timeout:
if len(self._baudrateDetectList) < 1:
@ -411,43 +717,86 @@ class MachineCom(object):
eventManager().fire("Connected", "%s at %s baud" % (self._port, self._baudrate))
else:
self._testingBaudrate = False
### Connection attempt
elif self._state == self.STATE_CONNECTING:
if line == '' and startSeen:
if (line == "" or "wait" in line) and startSeen:
self._sendCommand("M105")
elif 'start' in line:
elif "start" in line:
startSeen = True
elif 'ok' in line and startSeen:
elif "ok" in line and startSeen:
self._changeState(self.STATE_OPERATIONAL)
eventManager().fire("Connected", "%s at %s baud" % (self._port, self._baudrate))
elif time.time() > timeout:
self.close()
### Operational
elif self._state == self.STATE_OPERATIONAL or self._state == self.STATE_PAUSED:
#Request the temperature on comm timeout (every 5 seconds) when we are not printing.
if line == '':
self._sendCommand("M105")
tempRequestTimeout = time.time() + 5
elif self._state == self.STATE_PRINTING:
if line == '' and time.time() > timeout:
self._log("Communication timeout during printing, forcing a line")
line = 'ok'
#Even when printing request the temperture every 5 seconds.
if time.time() > tempRequestTimeout:
self._commandQueue.put("M105")
tempRequestTimeout = time.time() + 5
if 'ok' in line:
timeout = time.time() + 5
if not self._commandQueue.empty():
if line == "" or "wait" in line:
if self._resendDelta is not None:
self._resendNextCommand()
elif not self._commandQueue.empty():
self._sendCommand(self._commandQueue.get())
else:
self._sendNext()
self._sendCommand("M105")
tempRequestTimeout = time.time() + 5
# resend -> start resend procedure from requested line
elif "resend" in line.lower() or "rs" in line:
try:
self._gcodePos = int(line.replace("N:"," ").replace("N"," ").replace(":"," ").split()[-1])
except:
if "rs" in line:
self._gcodePos = int(line.split()[1])
self._handleResendRequest(line)
### Printing
elif self._state == self.STATE_PRINTING:
if line == "" and time.time() > timeout:
self._log("Communication timeout during printing, forcing a line")
line = 'ok'
if self._sdPrinting:
if time.time() > tempRequestTimeout:
self._sendCommand("M105")
tempRequestTimeout = time.time() + 5
if time.time() > sdStatusRequestTimeout:
self._sendCommand("M27")
sdStatusRequestTimeout = time.time() + 1
if 'ok' or 'SD printing byte' in line:
timeout = time.time() + 5
else:
# Even when printing request the temperature every 5 seconds.
if time.time() > tempRequestTimeout:
self._commandQueue.put("M105")
tempRequestTimeout = time.time() + 5
if 'ok' in line:
timeout = time.time() + 5
if self._resendDelta is not None:
self._resendNextCommand()
elif not self._commandQueue.empty():
self._sendCommand(self._commandQueue.get())
else:
self._sendNext()
elif "resend" in line.lower() or "rs" in line:
self._handleResendRequest(line)
self._log("Connection closed, closing down monitor")
def _handleResendRequest(self, line):
lineToResend = None
try:
lineToResend = int(line.replace("N:"," ").replace("N"," ").replace(":"," ").split()[-1])
except:
if "rs" in line:
lineToResend = int(line.split()[1])
if lineToResend is not None:
self._resendDelta = self._currentLine - lineToResend
if self._resendDelta > len(self._lastLines):
self._errorValue = "Printer requested line %d but history is only available up to line %d" % (lineToResend, self._currentLine - len(self._lastLines))
self._changeState(self.STATE_ERROR)
self._logger.warn(self._errorValue)
else:
self._resendNextCommand()
def _log(self, message):
self._callback.mcLog(message)
self._serialLogger.debug(message)
@ -487,34 +836,105 @@ class MachineCom(object):
self._changeState(self.STATE_CLOSED)
self._serial = None
if settings().get(["feature", "sdSupport"]):
self._sdFileList = []
if printing:
eventManager().fire("PrintFailed")
eventManager().fire("Disconnected")
def __del__(self):
self.close()
def _sendCommand(self, cmd):
if self._serial is None:
return
for gcode in gcodeToEvent.keys():
if gcode in cmd:
eventManager().fire(gcodeToEvent[gcode])
def _resendNextCommand(self):
# Make sure we are only handling one sending job at a time
with self._sendingLock:
self._logger.debug("Resending line %d, delta is %d, history log is %s items strong" % (self._currentLine - self._resendDelta, self._resendDelta, len(self._lastLines)))
cmd = self._lastLines[-self._resendDelta]
lineNumber = self._currentLine - self._resendDelta
if 'M109' in cmd or 'M190' in cmd:
self._heatupWaitStartTime = time.time()
if 'M104' in cmd or 'M109' in cmd:
try:
self._targetTemp = float(re.search('S([0-9]+)', cmd).group(1))
except:
pass
if 'M140' in cmd or 'M190' in cmd:
try:
self._bedTargetTemp = float(re.search('S([0-9]+)', cmd).group(1))
except:
pass
self._log('Send: %s' % (cmd))
self._doSendWithChecksum(cmd, lineNumber)
self._resendDelta -= 1
if self._resendDelta <= 0:
self._resendDelta = None
def _sendCommand(self, cmd, sendChecksum=False):
# Make sure we are only handling one sending job at a time
with self._sendingLock:
if self._serial is None:
return
for gcode in gcodeToEvent.keys():
if gcode in cmd:
eventManager().fire(gcodeToEvent[gcode])
if 'M109' in cmd or 'M190' in cmd:
self._heatupWaitStartTime = time.time()
if 'M104' in cmd or 'M109' in cmd:
try:
self._targetTemp = float(re.search('S([0-9]+)', cmd).group(1))
except:
pass
if 'M140' in cmd or 'M190' in cmd:
try:
self._bedTargetTemp = float(re.search('S([0-9]+)', cmd).group(1))
except:
pass
if "M110" in cmd:
newLineNumber = None
if " N" in cmd:
try:
newLineNumber = int(re.search("N([0-9]+)", cmd).group(1))
except:
pass
else:
newLineNumber = 0
if settings().getBoolean(["feature", "resetLineNumbersWithPrefixedN"]) and newLineNumber is not None:
# let's rewrite the M110 command to fit repetier syntax
self._addToLastLines(cmd)
self._doSendWithChecksum("M110", newLineNumber)
else:
self._doSend(cmd, sendChecksum)
if newLineNumber is not None:
self._currentLine = newLineNumber + 1
# after a reset of the line number we have no way to determine what line exactly the printer now wants
self._lastLines = []
self._resendDelta = None
else:
self._doSend(cmd, sendChecksum)
def _addToLastLines(self, cmd):
self._lastLines.append(cmd)
if len(self._lastLines) > 50:
self._lastLines = self._lastLines[-50:] # only keep the last 50 lines in memory
self._logger.debug("Got %d lines of history in memory" % len(self._lastLines))
def _doSend(self, cmd, sendChecksum=False):
if sendChecksum or self._alwaysSendChecksum:
if self._alwaysSendChecksum:
lineNumber = self._currentLine
else:
lineNumber = self._gcodePos
self._addToLastLines(cmd)
self._currentLine += 1
self._doSendWithChecksum(cmd, lineNumber)
else:
self._doSendWithoutChecksum(cmd)
def _doSendWithChecksum(self, cmd, lineNumber):
self._logger.debug("Sending cmd '%s' with lineNumber %r" % (cmd, lineNumber))
checksum = reduce(lambda x,y:x^y, map(ord, "N%d%s" % (lineNumber, cmd)))
commandToSend = "N%d%s*%d" % (lineNumber, cmd, checksum)
self._doSendWithoutChecksum(commandToSend)
def _doSendWithoutChecksum(self, cmd):
self._log("Send: %s" % cmd)
try:
self._serial.write(cmd + '\n')
except serial.SerialTimeoutException:
@ -529,36 +949,36 @@ class MachineCom(object):
self._log("Unexpected error while writing serial port: %s" % (getExceptionString()))
self._errorValue = getExceptionString()
self.close(True)
def _sendNext(self):
if self._gcodePos >= len(self._gcodeList):
self._changeState(self.STATE_OPERATIONAL)
eventManager().fire('PrintDone')
return
if self._gcodePos == 100:
self._printStartTime100 = time.time()
line = self._gcodeList[self._gcodePos]
if type(line) is tuple:
self._printSection = line[1]
line = line[0]
try:
if line == 'M0' or line == 'M1':
self.setPause(True)
line = 'M105' #Don't send the M0 or M1 to the machine, as M0 and M1 are handled as an LCD menu pause.
if self._printSection in self._feedRateModifier:
line = re.sub('F([0-9]*)', lambda m: 'F' + str(int(int(m.group(1)) * self._feedRateModifier[self._printSection])), line)
if ('G0' in line or 'G1' in line) and 'Z' in line:
z = float(re.search('Z([0-9\.]*)', line).group(1))
if self._currentZ != z:
self._currentZ = z
self._callback.mcZChange(z)
except:
self._log("Unexpected error: %s" % (getExceptionString()))
checksum = reduce(lambda x,y:x^y, map(ord, "N%d %s " % (self._gcodePos, line)))
self._sendCommand("N%d %s *%d" % (self._gcodePos, line, checksum)) # added spaces between line # and command and checksum, because some firmware needs it and it's more readable in the terminal window
self._gcodePos += 1
self._callback.mcProgress(self._gcodePos)
def _sendNext(self):
with self._sendNextLock:
if self._gcodePos >= len(self._gcodeList):
self._changeState(self.STATE_OPERATIONAL)
eventManager().fire('PrintDone')
return
if self._gcodePos == 100:
self._printStartTime100 = time.time()
line = self._gcodeList[self._gcodePos]
if type(line) is tuple:
self._printSection = line[1]
line = line[0]
try:
if line == 'M0' or line == 'M1':
self.setPause(True)
line = 'M105' #Don't send the M0 or M1 to the machine, as M0 and M1 are handled as an LCD menu pause.
if self._printSection in self._feedRateModifier:
line = re.sub('F([0-9]*)', lambda m: 'F' + str(int(int(m.group(1)) * self._feedRateModifier[self._printSection])), line)
if ('G0' in line or 'G1' in line) and 'Z' in line:
z = float(re.search('Z([0-9\.]*)', line).group(1))
if self._currentZ != z:
self._currentZ = z
self._callback.mcZChange(z)
except:
self._log("Unexpected error: %s" % (getExceptionString()))
self._sendCommand(line, True)
self._gcodePos += 1
self._callback.mcProgress()
def sendCommand(self, cmd):
cmd = cmd.encode('ascii', 'replace')
@ -570,28 +990,53 @@ class MachineCom(object):
def printGCode(self, gcodeList):
if not self.isOperational() or self.isPrinting():
return
if self._sdPrinting:
self._sdPrinting = False
self._gcodeList = gcodeList
self._gcodePos = 0
self._printStartTime100 = None
self._printSection = 'CUSTOM'
self._changeState(self.STATE_PRINTING)
self._printStartTime = time.time()
for i in xrange(0, 6):
self._sendNext()
self._sendNext()
eventManager().fire("PrintStarted")
def printSdFile(self):
if not self.isOperational() or self.isPrinting():
return
if self.isPaused():
self.sendCommand("M26 S0") # reset position in file to byte 0
self.sendCommand("M24")
self._printSection = 'CUSTOM'
self._sdPrinting = True
self._changeState(self.STATE_PRINTING)
self._printStartTime = time.time()
eventManager().fire("PrintStarted")
def cancelPrint(self):
if self.isOperational():
self._changeState(self.STATE_OPERATIONAL)
if self._sdPrinting:
self._sdPrinting = False
self.sendCommand("M25") # pause print
self.sendCommand("M26 S0") # reset position in file to byte 0
eventManager().fire("PrintCancelled")
def setPause(self, pause):
if not pause and self.isPaused():
self._changeState(self.STATE_PRINTING)
for i in xrange(0, 6):
self._sendNext()
if self._sdPrinting:
self.sendCommand("M24")
else:
for i in xrange(0, 6):
self._sendNext()
if pause and self.isPrinting():
self._changeState(self.STATE_PAUSED)
if self._sdPrinting:
self.sendCommand("M25") # pause print
eventManager().fire("Paused")
def setFeedrateModifier(self, type, value):
@ -602,6 +1047,64 @@ class MachineCom(object):
result.update(self._feedRateModifier)
return result
##~~ SD card
def getSdFiles(self):
return self._sdFiles
def startSdFileTransfer(self, filename):
if not self.isOperational() or self.isPrinting() or self.isPaused():
return
self._changeState(self.STATE_RECEIVING_FILE)
self.sendCommand("M28 %s" % filename.lower())
def endSdFileTransfer(self, filename):
if not self.isOperational() or self.isPrinting() or self.isPaused():
return
self.sendCommand("M29 %s" % filename.lower())
self._changeState(self.STATE_OPERATIONAL)
self.refreshSdFiles()
def selectSdFile(self, filename):
if not self.isOperational() or self.isPrinting() or self.isPaused():
return
self._sdFile = None
self._sdFilePos = 0
self.sendCommand("M23 %s" % filename.lower())
def deleteSdFile(self, filename):
if not self.isOperational() or ((self.isPrinting() or self.isPaused()) and self._sdFile == filename.lower()):
# do not delete a file from sd we are currently printing from
return
self.sendCommand("M30 %s" % filename.lower())
self.refreshSdFiles()
def refreshSdFiles(self):
if not self.isOperational() or self.isPrinting() or self.isPaused():
return
self.sendCommand("M20")
def initSdCard(self):
if not self.isOperational():
return
self.sendCommand("M21")
def releaseSdCard(self):
if not self.isOperational() or ((self.isPrinting() or self.isPaused()) and self._sdPrinting):
# do not release the sd card if we are currently printing from it
return
self.sendCommand("M22")
self._sdAvailable = False
self._sdFiles = []
self._callback.mcSdStateChange(self._sdAvailable)
self._callback.mcSdFiles(self._sdFiles)
def getExceptionString():
locationInfo = traceback.extract_tb(sys.exc_info()[2])[0]
return "%s: '%s' @ %s:%s:%d" % (str(sys.exc_info()[0].__name__), str(sys.exc_info()[1]), os.path.basename(locationInfo[0]), locationInfo[2], locationInfo[1])