Switched Timelapses to using Eventbus instead of direct connection
parent
ed9e93f379
commit
236e26979f
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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"])
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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> | <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> | <a href="#" class="icon-download" data-bind="attr: {href: url}"></a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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):
|
||||
|
|
Loading…
Reference in New Issue