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 pass
def _getStateFlags(self): def _getStateFlags(self):
if not settings().getBoolean(["feature", "sdSupport"]) or self._comm is None:
sdReady = False
else:
sdReady = self._comm.isSdReady()
return { return {
"operational": self.isOperational(), "operational": self.isOperational(),
"printing": self.isPrinting(), "printing": self.isPrinting(),
@ -355,7 +360,8 @@ class Printer():
"error": self.isError(), "error": self.isError(),
"loading": self.isLoading(), "loading": self.isLoading(),
"paused": self.isPaused(), "paused": self.isPaused(),
"ready": self.isReady() "ready": self.isReady(),
"sdReady": sdReady
} }
#~~ callbacks triggered from self._comm #~~ callbacks triggered from self._comm
@ -434,6 +440,9 @@ class Printer():
self._setCurrentZ(newZ) self._setCurrentZ(newZ)
def mcSdStateChange(self, sdReady):
self._stateMonitor.setState({"state": self._state, "stateString": self.getStateString(), "flags": self._getStateFlags()})
def mcSdFiles(self, files): def mcSdFiles(self, files):
self._sendTriggerUpdateCallbacks("gcodeFiles") self._sendTriggerUpdateCallbacks("gcodeFiles")
@ -480,6 +489,21 @@ class Printer():
self._sdPrintAfterSelect = printAfterSelect self._sdPrintAfterSelect = printAfterSelect
self._comm.selectSdFile(filename) 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 #~~ callbacks triggered by sdFileStreamer
def _onSdFileStreamProgress(self, filename, progress): def _onSdFileStreamProgress(self, filename, progress):

View File

@ -254,6 +254,23 @@ def getCustomControls():
customControls = settings().get(["controls"]) customControls = settings().get(["controls"])
return jsonify(controls=customControls) 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 #~~ GCODE file handling
@app.route(BASEURL + "gcodefiles", methods=["GET"]) @app.route(BASEURL + "gcodefiles", methods=["GET"])
@ -315,6 +332,11 @@ def deleteGcodeFile():
gcodeManager.removeFile(filename) gcodeManager.removeFile(filename)
return readGcodeFiles() return readGcodeFiles()
@app.route(BASEURL + "gcodefiles/refresh", methods=["POST"])
def refreshFiles():
printer.updateSdFiles()
return jsonify(SUCCESS)
#~~ timelapse handling #~~ timelapse handling
@app.route(BASEURL + "timelapse", methods=["GET"]) @app.route(BASEURL + "timelapse", methods=["GET"])

View File

@ -111,10 +111,10 @@ body {
.octoprint-container { .octoprint-container {
.accordion-heading { .accordion-heading {
.settings-trigger { .accordion-heading-button {
float: right; float: right;
.dropdown-toggle { a {
display: inline-block; display: inline-block;
padding: 8px 15px; padding: 8px 15px;
font-size: 14px; font-size: 14px;

View File

@ -213,6 +213,7 @@ function PrinterStateViewModel(loginStateViewModel) {
self.isError = ko.observable(undefined); self.isError = ko.observable(undefined);
self.isReady = ko.observable(undefined); self.isReady = ko.observable(undefined);
self.isLoading = ko.observable(undefined); self.isLoading = ko.observable(undefined);
self.isSdReady = ko.observable(undefined);
self.filename = ko.observable(undefined); self.filename = ko.observable(undefined);
self.filament = ko.observable(undefined); self.filament = ko.observable(undefined);
@ -268,6 +269,7 @@ function PrinterStateViewModel(loginStateViewModel) {
self.isError(data.flags.error); self.isError(data.flags.error);
self.isReady(data.flags.ready); self.isReady(data.flags.ready);
self.isLoading(data.flags.loading); self.isLoading(data.flags.loading);
self.isSdReady(data.flags.sdReady);
} }
self._processJobData = function(data) { self._processJobData = function(data) {
@ -756,6 +758,7 @@ function GcodeFilesViewModel(loginStateViewModel) {
self.isError = ko.observable(undefined); self.isError = ko.observable(undefined);
self.isReady = ko.observable(undefined); self.isReady = ko.observable(undefined);
self.isLoading = ko.observable(undefined); self.isLoading = ko.observable(undefined);
self.isSdReady = ko.observable(undefined);
// initialize list helper // initialize list helper
self.listHelper = new ItemListHelper( self.listHelper = new ItemListHelper(
@ -821,6 +824,7 @@ function GcodeFilesViewModel(loginStateViewModel) {
self.isError(data.flags.error); self.isError(data.flags.error);
self.isReady(data.flags.ready); self.isReady(data.flags.ready);
self.isLoading(data.flags.loading); self.isLoading(data.flags.loading);
self.isSdReady(data.flags.sdReady);
} }
self.requestData = function() { 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) { self.getPopoverContent = function(data) {
var output = "<p><strong>Uploaded:</strong> " + data["date"] + "</p>"; var output = "<p><strong>Uploaded:</strong> " + data["date"] + "</p>";
if (data["gcodeAnalysis"]) { if (data["gcodeAnalysis"]) {

View File

@ -132,7 +132,7 @@
<div class="accordion-heading"> <div class="accordion-heading">
<a class="accordion-toggle" data-toggle="collapse" href="#files"><i class="icon-list"></i> Files</a> <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="#"> <a class="dropdown-toggle" data-toggle="dropdown" href="#">
<span class="icon-wrench"></span> <span class="icon-wrench"></span>
</a> </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> <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> </ul>
</div> </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>
<div class="accordion-body collapse in overflow_visible" id="files"> <div class="accordion-body collapse in overflow_visible" id="files">
<div class="accordion-inner"> <div class="accordion-inner">

View File

@ -100,6 +100,13 @@
</label> </label>
</div> </div>
</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="control-group">
<div class="controls"> <div class="controls">
<label class="checkbox"> <label class="checkbox">
@ -121,13 +128,6 @@
</label> </label>
</div> </div>
</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> </form>
</div> </div>
<div class="tab-pane" id="settings_folder"> <div class="tab-pane" id="settings_folder">

View File

@ -57,7 +57,7 @@ def baudrateList():
class VirtualPrinter(): class VirtualPrinter():
def __init__(self): 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.temp = 0.0
self.targetTemp = 0.0 self.targetTemp = 0.0
self.lastTempAt = time.time() self.lastTempAt = time.time()
@ -65,6 +65,7 @@ class VirtualPrinter():
self.bedTargetTemp = 1.0 self.bedTargetTemp = 1.0
self._virtualSd = settings().getBaseFolder("virtualSd") self._virtualSd = settings().getBaseFolder("virtualSd")
self._sdCardReady = False
self._sdPrinter = None self._sdPrinter = None
self._sdPrintingSemaphore = threading.Event() self._sdPrintingSemaphore = threading.Event()
self._selectedSdFile = None self._selectedSdFile = None
@ -104,27 +105,41 @@ class VirtualPrinter():
# send simulated temperature 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)) self.readList.append("ok T:%.2f /%.2f B:%.2f /%.2f @:64\n" % (self.temp, self.targetTemp, self.bedTemp, self.bedTargetTemp))
elif 'M20' in data: 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: elif 'M23' in data:
filename = data.split(None, 1)[1].strip() if self._sdCardReady:
self._selectSdFile(filename) filename = data.split(None, 1)[1].strip()
self._selectSdFile(filename)
elif 'M24' in data: elif 'M24' in data:
self._startSdPrint() if self._sdCardReady:
self._startSdPrint()
elif 'M25' in data: elif 'M25' in data:
self._pauseSdPrint() if self._sdCardReady:
self._pauseSdPrint()
elif 'M26' in data: elif 'M26' in data:
pos = int(re.search("S([0-9]+)", data).group(1)) if self._sdCardReady:
self._setSdPos(pos) pos = int(re.search("S([0-9]+)", data).group(1))
self._setSdPos(pos)
elif 'M27' in data: elif 'M27' in data:
self._reportSdStatus() if self._sdCardReady:
self._reportSdStatus()
elif 'M28' in data: elif 'M28' in data:
filename = data.split(None, 1)[1].strip() if self._sdCardReady:
self._writeSdFile(filename) filename = data.split(None, 1)[1].strip()
self._writeSdFile(filename)
elif 'M29' in data: elif 'M29' in data:
self._finishSdFile() if self._sdCardReady:
self._finishSdFile()
elif 'M30' in data: elif 'M30' in data:
filename = data.split(None, 1)[1].strip() if self._sdCardReady:
self._deleteSdFile(filename) filename = data.split(None, 1)[1].strip()
self._deleteSdFile(filename)
elif "M110" in data: elif "M110" in data:
# reset current line # reset current line
self.currentLine = int(re.search('N([0-9]+)', data).group(1)) self.currentLine = int(re.search('N([0-9]+)', data).group(1))
@ -196,7 +211,7 @@ class VirtualPrinter():
def _sdPrintingWorker(self): def _sdPrintingWorker(self):
self._selectedSdFilePos = 0 self._selectedSdFilePos = 0
with open(self._selectedSdFile, "rb") as f: with open(self._selectedSdFile, "r") as f:
for line in f: for line in f:
# reset position if requested by client # reset position if requested by client
if self._newSdFilePos is not None: if self._newSdFilePos is not None:
@ -284,6 +299,9 @@ class MachineComPrintCallback(object):
def mcZChange(self, newZ): def mcZChange(self, newZ):
pass pass
def mcSdStateChange(self, sdReady):
pass
def mcSdFiles(self, files): def mcSdFiles(self, files):
pass pass
@ -355,6 +373,7 @@ class MachineCom(object):
self.thread.daemon = True self.thread.daemon = True
self.thread.start() self.thread.start()
self._sdAvailable = False
self._sdPrinting = False self._sdPrinting = False
self._sdFileList = False self._sdFileList = False
self._sdFile = None self._sdFile = None
@ -443,6 +462,9 @@ class MachineCom(object):
def isBusy(self): def isBusy(self):
return self.isPrinting() or self._state == self.STATE_RECEIVING_FILE return self.isPrinting() or self._state == self.STATE_RECEIVING_FILE
def isSdReady(self):
return self._sdAvailable
def getPrintPos(self): def getPrintPos(self):
if self._sdPrinting: if self._sdPrinting:
return self._sdFilePos return self._sdFilePos
@ -462,10 +484,11 @@ class MachineCom(object):
if self._sdPrinting: if self._sdPrinting:
printTime = (time.time() - self._printStartTime) / 60 printTime = (time.time() - self._printStartTime) / 60
if self._sdFilePos > 0: if self._sdFilePos > 0:
printTimeTotal = printTime * (self._sdFileSize / self._sdFilePos) printTimeTotal = printTime * self._sdFileSize / self._sdFilePos
else: else:
printTimeTotal = printTime * self._sdFileSize printTimeTotal = printTime * self._sdFileSize
printTimeLeft = printTimeTotal - printTime 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 return printTimeLeft
else: else:
# for host printing we only start counting the print time at gcode line 100, so we need to calculate stuff # 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 self._heatupWaitStartTime = t
##~~ SD Card handling ##~~ 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: elif 'Begin file list' in line:
self._sdFiles = [] self._sdFiles = []
self._sdFileList = True self._sdFileList = True
@ -671,8 +702,6 @@ class MachineCom(object):
startSeen = True startSeen = True
elif "ok" in line and startSeen: elif "ok" in line and startSeen:
self._changeState(self.STATE_OPERATIONAL) self._changeState(self.STATE_OPERATIONAL)
if settings().get(["feature", "sdSupport"]):
self._sendCommand("M20")
elif time.time() > timeout: elif time.time() > timeout:
self.close() self.close()
@ -697,24 +726,28 @@ class MachineCom(object):
self._log("Communication timeout during printing, forcing a line") self._log("Communication timeout during printing, forcing a line")
line = 'ok' 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 self._sdPrinting:
if time.time() > tempRequestTimeout:
self._sendCommand("M105")
tempRequestTimeout = time.time() + 5
if time.time() > sdStatusRequestTimeout: if time.time() > sdStatusRequestTimeout:
self._commandQueue.put("M27") self._sendCommand("M27")
sdStatusRequestTimeout = time.time() + 1 sdStatusRequestTimeout = time.time() + 1
if 'ok' in line and not self._commandQueue.empty(): if 'ok' or 'SD printing byte' in line:
self._sendCommand(self._commandQueue.get()) timeout = time.time() + 5
else: 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: if 'ok' in line:
timeout = time.time() + 5 timeout = time.time() + 5
if self._resendDelta is not None: if self._resendDelta is not None:
self._resendNextCommand() self._resendNextCommand()
elif not self._commandQueue.empty(): elif not self._commandQueue.empty():
self._sendCommand(self._commandQueue.get()) self._sendCommand(self._commandQueue.get())
else: else:
self._sendNext() self._sendNext()
@ -908,7 +941,7 @@ class MachineCom(object):
self._log("Unexpected error: %s" % (getExceptionString())) self._log("Unexpected error: %s" % (getExceptionString()))
self._sendCommand(line, True) self._sendCommand(line, True)
self._gcodePos += 1 self._gcodePos += 1
self._callback.mcProgress(self._gcodePos) self._callback.mcProgress()
def sendCommand(self, cmd): def sendCommand(self, cmd):
cmd = cmd.encode('ascii', 'replace') cmd = cmd.encode('ascii', 'replace')
@ -929,14 +962,6 @@ class MachineCom(object):
self._printStartTime = time.time() self._printStartTime = time.time()
self._sendNext() 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): def printSdFile(self):
if not self.isOperational() or self.isPrinting(): if not self.isOperational() or self.isPrinting():
return return
@ -979,24 +1004,64 @@ class MachineCom(object):
result.update(self._feedRateModifier) result.update(self._feedRateModifier)
return result return result
##~~ SD card
def getSdFiles(self): def getSdFiles(self):
return self._sdFiles return self._sdFiles
def startSdFileTransfer(self, filename): def startSdFileTransfer(self, filename):
if self.isPrinting() or self.isPaused(): if not self.isOperational() or self.isPrinting() or self.isPaused():
return return
self._changeState(self.STATE_RECEIVING_FILE) self._changeState(self.STATE_RECEIVING_FILE)
self.sendCommand("M28 %s" % filename.lower()) self.sendCommand("M28 %s" % filename.lower())
def endSdFileTransfer(self, filename): def endSdFileTransfer(self, filename):
if not self.isOperational() or self.isPrinting() or self.isPaused():
return
self.sendCommand("M29 %s" % filename.lower()) self.sendCommand("M29 %s" % filename.lower())
self._changeState(self.STATE_OPERATIONAL) 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): 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.sendCommand("M30 %s" % filename.lower())
self.refreshSdFiles()
def refreshSdFiles(self):
if not self.isOperational() or self.isPrinting() or self.isPaused():
return
self.sendCommand("M20") 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(): def getExceptionString():
locationInfo = traceback.extract_tb(sys.exc_info()[2])[0] 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]) 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])