Added SD state evaluation and SD commands

master
Gina Häußge 2013-05-26 18:53:43 +02:00
parent 16f5e54bd7
commit b645073f1d
7 changed files with 200 additions and 51 deletions

View File

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

View File

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

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;

View File

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

View File

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

View File

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

View File

@ -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,27 +105,41 @@ 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:
self._listSd()
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:
filename = data.split(None, 1)[1].strip()
self._selectSdFile(filename)
if self._sdCardReady:
filename = data.split(None, 1)[1].strip()
self._selectSdFile(filename)
elif 'M24' in data:
self._startSdPrint()
if self._sdCardReady:
self._startSdPrint()
elif 'M25' in data:
self._pauseSdPrint()
if self._sdCardReady:
self._pauseSdPrint()
elif 'M26' in data:
pos = int(re.search("S([0-9]+)", data).group(1))
self._setSdPos(pos)
if self._sdCardReady:
pos = int(re.search("S([0-9]+)", data).group(1))
self._setSdPos(pos)
elif 'M27' in data:
self._reportSdStatus()
if self._sdCardReady:
self._reportSdStatus()
elif 'M28' in data:
filename = data.split(None, 1)[1].strip()
self._writeSdFile(filename)
if self._sdCardReady:
filename = data.split(None, 1)[1].strip()
self._writeSdFile(filename)
elif 'M29' in data:
self._finishSdFile()
if self._sdCardReady:
self._finishSdFile()
elif 'M30' in data:
filename = data.split(None, 1)[1].strip()
self._deleteSdFile(filename)
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))
@ -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,24 +726,28 @@ class MachineCom(object):
self._log("Communication timeout during printing, forcing a line")
line = 'ok'
# 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() > tempRequestTimeout:
self._sendCommand("M105")
tempRequestTimeout = time.time() + 5
if time.time() > sdStatusRequestTimeout:
self._commandQueue.put("M27")
self._sendCommand("M27")
sdStatusRequestTimeout = time.time() + 1
if 'ok' in line and not self._commandQueue.empty():
self._sendCommand(self._commandQueue.get())
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():
if self._resendDelta is not None:
self._resendNextCommand()
elif not self._commandQueue.empty():
self._sendCommand(self._commandQueue.get())
else:
self._sendNext()
@ -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])