Switched Timelapses to using Eventbus instead of direct connection

master
Gina Häußge 2013-06-16 21:50:50 +02:00
parent ed9e93f379
commit 236e26979f
8 changed files with 105 additions and 62 deletions

View File

@ -40,8 +40,9 @@ Available Events:
* ''PowerOn'': the GCode has turned on the printer power via M80
* ''PowerOff'': the GCode has turned on the printer power via M81
* ''Upload'': a gcode file upload has been uploaded (data is filename)
* ''LoadStart'': a gcode file load has started (data is filename)
* ''LoadDone'': a gcode file load has finished (data is filename)
* ''FileSelected'': a gcode file has been selected for printing (data is filename)
* ''TransferStart'': a gcode file transfer to SD has started (data is filename)
* ''TransferDone'': a gcode file transfer to SD has finished (data is filename)
* ''PrintStarted'': a print has started
* ''PrintFailed'': a print failed
* ''PrintDone'': a print completed successfully

View File

@ -41,7 +41,7 @@ class EventManager(object):
if not event in self._registeredListeners.keys():
return
self._logger.debug("Firing event: %s (%r)" % (event, payload))
self._logger.debug("Firing event: %s (Payload: %r)" % (event, payload))
eventListeners = self._registeredListeners[event]
for listener in eventListeners:
@ -107,6 +107,20 @@ class GenericEventListener(object):
"""
pass
class DebugEventListener(GenericEventListener):
def __init__(self):
GenericEventListener.__init__(self)
events = ["Startup", "Connected", "Disconnected", "ClientOpen", "ClientClosed", "PowerOn", "PowerOff", "Upload",
"FileSelected", "TransferStarted", "TransferDone", "PrintStarted", "PrintDone", "PrintFailed",
"Cancelled", "Home", "ZChange", "Paused", "Waiting", "Cooling", "Alert", "Conveyor", "Eject",
"CaptureStart", "CaptureDone", "MovieDone", "EStop", "Error"]
self.subscribe(events)
def eventCallback(self, event, payload):
GenericEventListener.eventCallback(self, event, payload)
self._logger.debug("Received event: %s (Payload: %r)" % (event, payload))
class CommandTrigger(GenericEventListener):
def __init__(self, triggerType, printer):
GenericEventListener.__init__(self)
@ -194,9 +208,8 @@ class CommandTrigger(GenericEventListener):
if "job" in currentData.keys() and currentData["job"] is not None:
params["filename"] = currentData["job"]["filename"]
if "progress" in currentData.keys() and currentData["progress"] is not None \
and "progress" in currentData["progress"].keys() and currentData["progress"]["progress"] is not None \
and currentData["job"]["lines"] is not None:
params["progress"] = str(round(currentData["progress"]["progress"] * 100 / currentData["job"]["lines"]))
and "progress" in currentData["progress"].keys() and currentData["progress"]["progress"] is not None:
params["progress"] = str(round(currentData["progress"]["progress"] * 100))
return command % params

View File

@ -217,7 +217,7 @@ class Printer():
def setTimelapse(self, timelapse):
if self._timelapse is not None and self.isPrinting():
self._timelapse.onPrintjobStopped()
self._timelapse.stopTimelapse()
del self._timelapse
self._timelapse = timelapse
@ -368,13 +368,6 @@ class Printer():
"""
oldState = self._state
# forward relevant state changes to timelapse
if self._timelapse is not None:
if oldState == self._comm.STATE_PRINTING and state != self._comm.STATE_PAUSED:
self._timelapse.onPrintjobStopped()
elif state == self._comm.STATE_PRINTING and oldState != self._comm.STATE_PAUSED:
self._timelapse.onPrintjobStarted(self._selectedFile["filename"])
# forward relevant state changes to gcode manager
if self._comm is not None and oldState == self._comm.STATE_PRINTING:
if self._selectedFile is not None:
@ -409,11 +402,9 @@ class Printer():
"""
oldZ = self._currentZ
# only do this if we hit a new Z peak level. Some slicers do a Z-lift when retracting / moving without printing
# and some do ananti-backlash up-then-down movement when advancing layers
# and some do anti-backlash up-then-down movement when advancing layers
if newZ > self.peakZ:
self.peakZ = newZ
if self._timelapse is not None:
self._timelapse.onZChange(oldZ, newZ)
eventManager().fire("ZChange", newZ)
self._setCurrentZ(newZ)

View File

@ -64,6 +64,7 @@ class PrinterStateConnection(tornadio2.SocketConnection):
gcodeManager.registerCallback(self)
self._eventManager.fire("ClientOpened")
self._eventManager.subscribe("MovieDone", self._onMovieDone)
def on_close(self):
self._logger.info("Closed client connection")
@ -72,6 +73,7 @@ class PrinterStateConnection(tornadio2.SocketConnection):
gcodeManager.unregisterCallback(self)
self._eventManager.fire("ClientClosed")
self._eventManager.unsubscribe("MovieDone", self._onMovieDone)
def on_message(self, message):
pass
@ -115,6 +117,9 @@ class PrinterStateConnection(tornadio2.SocketConnection):
with self._temperatureBacklogMutex:
self._temperatureBacklog.append(data)
def _onMovieDone(self, event, payload):
self.sendUpdateTrigger("timelapseFiles")
# Did attempt to make webserver an encapsulated class but ended up with __call__ failures
@app.route("/")
@ -364,9 +369,9 @@ def getTimelapseData():
file["url"] = url_for("downloadTimelapse", filename=file["name"])
return jsonify({
"type": type,
"config": additionalConfig,
"files": files
"type": type,
"config": additionalConfig,
"files": files
})
@app.route(BASEURL + "timelapse/<filename>", methods=["GET"])
@ -719,6 +724,8 @@ class Server():
# setup system and gcode command triggers
events.SystemCommandTrigger(printer)
events.GcodeCommandTrigger(printer)
if self._debug:
events.DebugEventListener()
if settings().getBoolean(["accessControl", "enabled"]):
userManagerName = settings().get(["accessControl", "userManager"])

View File

@ -999,8 +999,7 @@ function TimelapseViewModel(loginStateViewModel) {
self.isLoading(data.flags.loading);
}
self.removeFile = function() {
var filename = this.name;
self.removeFile = function(filename) {
$.ajax({
url: AJAX_BASEURL + "timelapse/" + filename,
type: "DELETE",
@ -1526,6 +1525,8 @@ function DataUpdater(loginStateViewModel, connectionViewModel, printerStateViewM
self._socket.on("updateTrigger", function(type) {
if (type == "gcodeFiles") {
gcodeFilesViewModel.requestData();
} else if (type == "timelapseFiles") {
timelapseViewModel.requestData();
}
})
@ -1737,28 +1738,28 @@ function ItemListHelper(listType, supportedSorting, supportedFilters, defaultSor
self._saveCurrentSortingToLocalStorage = function() {
if ( self._initializeLocalStorage() ) {
var currentSorting = self.currentSorting();
if (currentSorting !== undefined)
localStorage[self.listType + "." + "currentSorting"] = currentSorting;
else
localStorage[self.listType + "." + "currentSorting"] = undefined;
var currentSorting = self.currentSorting();
if (currentSorting !== undefined)
localStorage[self.listType + "." + "currentSorting"] = currentSorting;
else
localStorage[self.listType + "." + "currentSorting"] = undefined;
}
}
self._loadCurrentSortingFromLocalStorage = function() {
if ( self._initializeLocalStorage() ) {
if (_.contains(_.keys(supportedSorting), localStorage[self.listType + "." + "currentSorting"]))
self.currentSorting(localStorage[self.listType + "." + "currentSorting"]);
else
self.currentSorting(defaultSorting);
}
if (_.contains(_.keys(supportedSorting), localStorage[self.listType + "." + "currentSorting"]))
self.currentSorting(localStorage[self.listType + "." + "currentSorting"]);
else
self.currentSorting(defaultSorting);
}
}
self._saveCurrentFiltersToLocalStorage = function() {
if ( self._initializeLocalStorage() ) {
var filters = _.intersection(_.keys(self.supportedFilters), self.currentFilters());
localStorage[self.listType + "." + "currentFilters"] = JSON.stringify(filters);
}
var filters = _.intersection(_.keys(self.supportedFilters), self.currentFilters());
localStorage[self.listType + "." + "currentFilters"] = JSON.stringify(filters);
}
}
self._loadCurrentFiltersFromLocalStorage = function() {

View File

@ -542,7 +542,7 @@
<tr data-bind="attr: {title: name}">
<td class="timelapse_files_name" data-bind="text: name"></td>
<td class="timelapse_files_size" data-bind="text: size"></td>
<td class="timelapse_files_action"><a href="#" class="icon-trash" data-bind="click: function() { if ($root.loginState.isUser()) { $parent.removeFile(); } else { return; } }, css: {disabled: !$root.loginState.isUser()}"></a>&nbsp;|&nbsp;<a href="#" class="icon-download" data-bind="attr: {href: url}"></a></td>
<td class="timelapse_files_action"><a href="#" class="icon-trash" data-bind="click: function() { if ($root.loginState.isUser()) { $parent.removeFile($data.name); } else { return; } }, css: {disabled: !$root.loginState.isUser()}"></a>&nbsp;|&nbsp;<a href="#" class="icon-download" data-bind="attr: {href: url}"></a></td>
</tr>
</tbody>
</table>

View File

@ -47,13 +47,30 @@ class Timelapse(object):
self._renderThread = None
self._captureMutex = threading.Lock()
def onPrintjobStarted(self, gcodeFile):
self.startTimelapse(gcodeFile)
eventManager().subscribe("PrintStarted", self.onPrintStarted)
eventManager().subscribe("PrintFailed", self.onPrintDone)
eventManager().subscribe("PrintDone", self.onPrintDone)
self.subscribeToEvents()
def onPrintjobStopped(self):
def onPrintStarted(self, event, payload):
"""
Override this to perform additional actions upon start of a print job.
"""
self.startTimelapse(payload)
def onPrintDone(self, event, payload):
"""
Override this to perform additional actions upon the stop of a print job.
"""
self.stopTimelapse()
def onZChange(self, oldZ, newZ):
def subscribeToEvents(self):
"""
Override this method to subscribe to additional events. Events that are already subscribed:
* PrintStarted - self.onPrintStarted
* PrintFailed - self.onPrintDone
* PrintDone - self.onPrintDone
"""
pass
def startTimelapse(self, gcodeFile):
@ -96,7 +113,7 @@ class Timelapse(object):
ffmpeg = settings().get(["webcam", "ffmpeg"])
bitrate = settings().get(["webcam", "bitrate"])
if ffmpeg is None or bitrate is None:
self._logger.warn("Cannot create movie, path to ffmpeg is unset")
self._logger.warn("Cannot create movie, path to ffmpeg or desired bitrate is unset")
return
input = os.path.join(self._captureDir, "tmp_%05d.jpg")
@ -119,8 +136,11 @@ class Timelapse(object):
# finalize command with output file
self._logger.debug("Rendering movie to %s" % output)
command.append(output)
subprocess.call(command)
eventManager().fire("MovieDone", output);
try:
subprocess.check_call(command)
eventManager().fire("MovieDone", output);
except subprocess.CalledProcessError as (e):
self._logger.warn("Could not render movie, got return code %r" % e.returncode)
def cleanCaptureDir(self):
if not os.path.isdir(self._captureDir):
@ -137,35 +157,38 @@ class ZTimelapse(Timelapse):
Timelapse.__init__(self)
self._logger.debug("ZTimelapse initialized")
def onZChange(self, oldZ, newZ):
self._logger.debug("Z change detected, capturing image")
def subscribeToEvents(self):
eventManager().subscribe("ZChange", self._onZChange)
def _onZChange(self, event, payload):
self.captureImage()
class TimedTimelapse(Timelapse):
def __init__(self, interval=1):
Timelapse.__init__(self)
self._interval = interval
if self._interval < 1:
self._interval = 1 # force minimum interval of 1s
self._timerThread = None
self._logger.debug("TimedTimelapse initialized")
def interval(self):
return self._interval
def onPrintjobStarted(self, filename):
Timelapse.onPrintjobStarted(self, filename)
def onPrintStarted(self, event, payload):
Timelapse.onPrintStarted(self, event, payload)
if self._timerThread is not None:
return
self._timerThread = threading.Thread(target=self.timerWorker)
self._timerThread = threading.Thread(target=self._timerWorker)
self._timerThread.daemon = True
self._timerThread.start()
def timerWorker(self):
def onPrintDone(self, event, payload):
Timelapse.onPrintDone(self, event, payload)
self._timerThread = None
def _timerWorker(self):
self._logger.debug("Starting timer for interval based timelapse")
while self._inTimelapse:
self.captureImage()

View File

@ -345,7 +345,7 @@ class MachineCom(object):
STATE_CLOSED = 8
STATE_ERROR = 9
STATE_CLOSED_WITH_ERROR = 10
STATE_RECEIVING_FILE = 11
STATE_TRANSFERING_FILE = 11
def __init__(self, port = None, baudrate = None, callbackObject = None):
self._logger = logging.getLogger(__name__)
@ -445,8 +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"
if self._state == self.STATE_TRANSFERING_FILE:
return "Transfering file to SD"
return "?%d?" % (self._state)
def getShortErrorString(self):
@ -464,7 +464,7 @@ 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 or self._state == self.STATE_RECEIVING_FILE
return self._state == self.STATE_OPERATIONAL or self._state == self.STATE_PRINTING or self._state == self.STATE_PAUSED or self._state == self.STATE_TRANSFERING_FILE
def isPrinting(self):
return self._state == self.STATE_PRINTING
@ -611,7 +611,7 @@ class MachineCom(object):
eventManager().fire("Error", self.getErrorString())
##~~ SD file list
##~~ 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.strip().lower())
@ -669,6 +669,7 @@ class MachineCom(object):
elif 'File selected' in line:
# final answer to M23, at least on Marlin, Repetier and Sprinter: "File selected"
self._callback.mcFileSelected(self._currentFile.getFilename(), self._currentFile.getFilesize(), True)
eventManager().fire("FileSelected", self._currentFile.getFilename())
elif 'Writing to file' in line:
# anwer to M28, at least on Marlin, Repetier and Sprinter: "Writing to file: %s"
self._printSection = "CUSTOM"
@ -676,8 +677,9 @@ class MachineCom(object):
elif 'Done printing file' in line:
# printer is reporting file finished printing
self._sdFilePos = 0
self._changeState(self.STATE_OPERATIONAL)
self._callback.mcPrintjobDone()
self._changeState(self.STATE_OPERATIONAL)
eventManager().fire("PrintDone")
##~~ 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():
@ -971,10 +973,12 @@ class MachineCom(object):
self._sendCommand("M29")
self._currentFile = None
self._callback.mcFileTransferDone()
self._changeState(self.STATE_OPERATIONAL)
eventManager().fire("TransferDone", self._currentFile.getFilename())
else:
self._callback.mcPrintjobDone()
self._changeState(self.STATE_OPERATIONAL)
eventManager().fire('PrintDone')
self._changeState(self.STATE_OPERATIONAL)
eventManager().fire("PrintDone", self._currentFile.getFilename())
return
if type(line) is tuple:
@ -1012,7 +1016,7 @@ class MachineCom(object):
self._printSection = "CUSTOM"
self._changeState(self.STATE_PRINTING)
eventManager().fire("PrintStarted")
eventManager().fire("PrintStarted", self._currentFile.getFilename())
try:
self._currentFile.start()
@ -1035,6 +1039,7 @@ class MachineCom(object):
self._currentFile.start()
self.sendCommand("M28 %s" % remoteFilename)
eventManager().fire("TransferStart", remoteFilename)
self._callback.mcFileTransferStarted(remoteFilename, self._currentFile.getFilesize())
def selectFile(self, filename, sd):
@ -1048,6 +1053,7 @@ class MachineCom(object):
self.sendCommand("M23 %s" % filename)
else:
self._currentFile = PrintingGcodeFileInformation(filename)
eventManager().fire("FileSelected", filename)
self._callback.mcFileSelected(filename, self._currentFile.getFilesize(), False)
def cancelPrint(self):
@ -1087,7 +1093,7 @@ class MachineCom(object):
if not self.isOperational() or self.isBusy():
return
self._changeState(self.STATE_RECEIVING_FILE)
self._changeState(self.STATE_TRANSFERING_FILE)
self.sendCommand("M28 %s" % filename.lower())
def endSdFileTransfer(self, filename):
@ -1251,6 +1257,7 @@ class PrintingGcodeFileInformation(PrintingFileInformation):
except Exception as (e):
if self._filehandle is not None:
self._filehandle.close()
self._filehandle = None
raise e
def getLineCount(self):