Added SD state evaluation and SD commands
parent
16f5e54bd7
commit
b645073f1d
|
@ -348,6 +348,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(),
|
||||
|
@ -355,7 +360,8 @@ class Printer():
|
|||
"error": self.isError(),
|
||||
"loading": self.isLoading(),
|
||||
"paused": self.isPaused(),
|
||||
"ready": self.isReady()
|
||||
"ready": self.isReady(),
|
||||
"sdReady": sdReady
|
||||
}
|
||||
|
||||
#~~ callbacks triggered from self._comm
|
||||
|
@ -434,6 +440,9 @@ 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")
|
||||
|
||||
|
@ -480,6 +489,21 @@ class Printer():
|
|||
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):
|
||||
|
|
|
@ -254,6 +254,23 @@ 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"])
|
||||
|
@ -315,6 +332,11 @@ def deleteGcodeFile():
|
|||
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"])
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -213,6 +213,7 @@ 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);
|
||||
|
@ -268,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) {
|
||||
|
@ -756,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(
|
||||
|
@ -821,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() {
|
||||
|
@ -868,6 +872,27 @@ function GcodeFilesViewModel(loginStateViewModel) {
|
|||
})
|
||||
}
|
||||
|
||||
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"]) {
|
||||
|
|
|
@ -132,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>
|
||||
|
@ -149,6 +149,19 @@
|
|||
<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">
|
||||
|
|
|
@ -100,6 +100,13 @@
|
|||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<div class="controls">
|
||||
<label class="checkbox">
|
||||
<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">
|
||||
|
@ -121,13 +128,6 @@
|
|||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<div class="controls">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" data-bind="checked: feature_sdSupport" id="settings-featureSdSupport"> Enable SD support
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="tab-pane" id="settings_folder">
|
||||
|
|
|
@ -57,7 +57,7 @@ def baudrateList():
|
|||
|
||||
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()
|
||||
|
@ -65,6 +65,7 @@ class VirtualPrinter():
|
|||
self.bedTargetTemp = 1.0
|
||||
|
||||
self._virtualSd = settings().getBaseFolder("virtualSd")
|
||||
self._sdCardReady = False
|
||||
self._sdPrinter = None
|
||||
self._sdPrintingSemaphore = threading.Event()
|
||||
self._selectedSdFile = None
|
||||
|
@ -104,25 +105,39 @@ class VirtualPrinter():
|
|||
# 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:
|
||||
|
@ -196,7 +211,7 @@ class VirtualPrinter():
|
|||
|
||||
def _sdPrintingWorker(self):
|
||||
self._selectedSdFilePos = 0
|
||||
with open(self._selectedSdFile, "rb") as f:
|
||||
with open(self._selectedSdFile, "r") as f:
|
||||
for line in f:
|
||||
# reset position if requested by client
|
||||
if self._newSdFilePos is not None:
|
||||
|
@ -284,6 +299,9 @@ class MachineComPrintCallback(object):
|
|||
def mcZChange(self, newZ):
|
||||
pass
|
||||
|
||||
def mcSdStateChange(self, sdReady):
|
||||
pass
|
||||
|
||||
def mcSdFiles(self, files):
|
||||
pass
|
||||
|
||||
|
@ -355,6 +373,7 @@ class MachineCom(object):
|
|||
self.thread.daemon = True
|
||||
self.thread.start()
|
||||
|
||||
self._sdAvailable = False
|
||||
self._sdPrinting = False
|
||||
self._sdFileList = False
|
||||
self._sdFile = None
|
||||
|
@ -443,6 +462,9 @@ class MachineCom(object):
|
|||
def isBusy(self):
|
||||
return self.isPrinting() or self._state == self.STATE_RECEIVING_FILE
|
||||
|
||||
def isSdReady(self):
|
||||
return self._sdAvailable
|
||||
|
||||
def getPrintPos(self):
|
||||
if self._sdPrinting:
|
||||
return self._sdFilePos
|
||||
|
@ -462,10 +484,11 @@ class MachineCom(object):
|
|||
if self._sdPrinting:
|
||||
printTime = (time.time() - self._printStartTime) / 60
|
||||
if self._sdFilePos > 0:
|
||||
printTimeTotal = printTime * (self._sdFileSize / self._sdFilePos)
|
||||
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
|
||||
|
@ -593,6 +616,14 @@ class MachineCom(object):
|
|||
self._heatupWaitStartTime = t
|
||||
|
||||
##~~ 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
|
||||
|
@ -671,8 +702,6 @@ class MachineCom(object):
|
|||
startSeen = True
|
||||
elif "ok" in line and startSeen:
|
||||
self._changeState(self.STATE_OPERATIONAL)
|
||||
if settings().get(["feature", "sdSupport"]):
|
||||
self._sendCommand("M20")
|
||||
elif time.time() > timeout:
|
||||
self.close()
|
||||
|
||||
|
@ -697,19 +726,23 @@ class MachineCom(object):
|
|||
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 self._sdPrinting:
|
||||
if time.time() > sdStatusRequestTimeout:
|
||||
self._commandQueue.put("M27")
|
||||
sdStatusRequestTimeout = time.time() + 1
|
||||
|
||||
if 'ok' in line and not self._commandQueue.empty():
|
||||
self._sendCommand(self._commandQueue.get())
|
||||
else:
|
||||
if 'ok' in line:
|
||||
timeout = time.time() + 5
|
||||
if self._resendDelta is not None:
|
||||
|
@ -908,7 +941,7 @@ class MachineCom(object):
|
|||
self._log("Unexpected error: %s" % (getExceptionString()))
|
||||
self._sendCommand(line, True)
|
||||
self._gcodePos += 1
|
||||
self._callback.mcProgress(self._gcodePos)
|
||||
self._callback.mcProgress()
|
||||
|
||||
def sendCommand(self, cmd):
|
||||
cmd = cmd.encode('ascii', 'replace')
|
||||
|
@ -929,14 +962,6 @@ class MachineCom(object):
|
|||
self._printStartTime = time.time()
|
||||
self._sendNext()
|
||||
|
||||
def selectSdFile(self, filename):
|
||||
if not self.isOperational() or self.isPrinting():
|
||||
return
|
||||
self._sdFile = None
|
||||
self._sdFilePos = 0
|
||||
|
||||
self.sendCommand("M23 %s" % filename.lower())
|
||||
|
||||
def printSdFile(self):
|
||||
if not self.isOperational() or self.isPrinting():
|
||||
return
|
||||
|
@ -979,24 +1004,64 @@ class MachineCom(object):
|
|||
result.update(self._feedRateModifier)
|
||||
return result
|
||||
|
||||
##~~ SD card
|
||||
def getSdFiles(self):
|
||||
return self._sdFiles
|
||||
|
||||
def startSdFileTransfer(self, filename):
|
||||
if self.isPrinting() or self.isPaused():
|
||||
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.sendCommand("M20")
|
||||
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])
|
||||
|
|
Loading…
Reference in New Issue