Decoupled communication thread and frontend serving via update queue, extracted history to separate initial event sent upon connecting, making updates of temperatures, logs and messages smaller

This commit is contained in:
Gina Häußge 2013-01-06 21:19:39 +01:00
parent 534a48ffd7
commit 7567734e1c
3 changed files with 157 additions and 68 deletions

View file

@ -4,6 +4,7 @@ __license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agp
import time
from threading import Thread
import Queue
import printer_webui.util.comm as comm
from printer_webui.util import gcodeInterpreter
@ -66,11 +67,13 @@ class Printer():
# callbacks
self._callbacks = []
# callback throttling
self._lastProgressReport = None
#~~ callback registration
self._updateQueue = Queue.Queue()
self._updateQueueWorker = Thread(target=self._processQueue)
self._updateQueueWorker.start()
#~~ callback handling
def registerCallback(self, callback):
self._callbacks.append(callback)
@ -80,7 +83,57 @@ class Printer():
if callback in self._callbacks:
self._callbacks.remove(callback)
#~~ printer commands
def _sendZChangeCallbacks(self, data):
for callback in self._callbacks:
try: callback.zChangeCB(data["currentZ"])
except: pass
def _sendStateCallbacks(self, data):
for callback in self._callbacks:
try: callback.stateChangeCB(data["state"], data["stateString"], data["stateFlags"])
except: pass
def _sendTemperatureCallbacks(self, data):
for callback in self._callbacks:
try: callback.temperatureChangeCB(data["currentTime"], data["temp"], data["bedTemp"], data["targetTemp"], data["targetBedTemp"])
except: pass
def _sendLogCallbacks(self, data):
for callback in self._callbacks:
try: callback.logChangeCB(data["log"])
except: pass
def _sendMessageCallbacks(self, data):
for callback in self._callbacks:
try: callback.messageChangeCB(data["message"])
except: pass
def _sendProgressCallbacks(self, data):
for callback in self._callbacks:
try: callback.progressChangeCB(data["progress"], data["printTime"], data["printTimeLeft"])
except: pass
def _sendJobCallbacks(self, data):
for callback in self._callbacks:
try: callback.jobDataChangeCB(data["filename"], data["lines"], data["estimatedPrintTime"], data["filament"])
except: pass
def _sendGcodeCallbacks(self, data):
for callback in self._callbacks:
try: callback.gcodeChangeCB(data["filename"], data["progress"])
except:
pass
def _addUpdate(self, target, data):
self._updateQueue.put((target, data))
def _processQueue(self):
while True:
(target, data) = self._updateQueue.get()
target(data)
self._updateQueue.task_done()
#~~ printer commands
def connect(self, port=None, baudrate=None):
"""
@ -180,19 +233,12 @@ class Printer():
return self._timelapse
def _setCurrentZ(self, currentZ):
print("Setting currentZ=%s" % str(currentZ))
self._currentZ = currentZ
for callback in self._callbacks:
try: callback.zChangeCB(self._currentZ)
except: pass
self._addUpdate(self._sendZChangeCallbacks, {"currentZ": self._currentZ})
def _setState(self, state):
self._state = state
for callback in self._callbacks:
try: callback.stateChangeCB(self._state, self.getStateString(), self._getStateFlags())
except: pass
self._addUpdate(self._sendStateCallbacks, {"state": self._state, "stateString": self.getStateString(), "stateFlags": self._getStateFlags()})
def _addLog(self, log):
"""
@ -201,33 +247,22 @@ class Printer():
self._latestLog = log
self._log.append(log)
self._log = self._log[-300:]
for callback in self._callbacks:
try: callback.logChangeCB(log, self._log)
except: pass
self._addUpdate(self._sendLogCallbacks, {"log": self._latestLog})
def _addMessage(self, message):
self._latestMessage = message
self._messages.append(message)
self._messages = self._messages[-300:]
for callback in self._callbacks:
try: callback.messageChangeCB(message, self._messages)
except: pass
self._addUpdate(self._sendLogCallbacks, {"message": self._latestLog})
def _setProgressData(self, progress, printTime, printTimeLeft):
self._progress = progress
self._printTime = printTime
self._printTimeLeft = printTimeLeft
if self._lastProgressReport and self._lastProgressReport + 0.5 > time.time():
return
for callback in self._callbacks:
try: callback.progressChangeCB(self._progress, self._printTime, self._printTimeLeft)
except: pass
self._lastProgressReport = time.time()
#if not self._lastProgressReport or self._lastProgressReport + 0.5 <= time.time():
self._addUpdate(self._sendProgressCallbacks, {"progress": self._progress, "printTime": self._printTime, "printTimeLeft": self._printTimeLeft})
# self._lastProgressReport = time.time()
def _addTemperatureData(self, temp, bedTemp, targetTemp, bedTargetTemp):
"""
@ -253,18 +288,24 @@ class Printer():
self._targetTemp = targetTemp
self._targetBedTemp = bedTargetTemp
for callback in self._callbacks:
try: callback.temperatureChangeCB(self._temp, self._bedTemp, self._targetTemp, self._targetBedTemp, self._temps)
except: pass
self._addUpdate(self._sendTemperatureCallbacks, {"currentTime": currentTime, "temp": self._temp, "bedTemp": self._bedTemp, "targetTemp": self._targetTemp, "targetBedTemp": self._targetBedTemp, "history": self._temps})
def _setJobData(self, filename, gcode, gcodeList):
self._filename = filename
self._gcode = gcode
self._gcodeList = gcodeList
for callback in self._callbacks:
try: callback.jobDataChangeCB(filename, len(gcodeList), self._gcode.totalMoveTimeMinute, self._gcode.extrusionAmount)
except: pass
lines = None
if self._gcodeList:
lines = len(self._gcodeList)
estimatedPrintTime = None
filament = None
if self._gcode:
estimatedPrintTime = self._gcode.totalMoveTimeMinute
filament = self._gcode.extrusionAmount
self._addUpdate(self._sendJobCallbacks, {"filename": self._filename, "lines": lines, "estimatedPrintTime": estimatedPrintTime, "filament": filament})
def _sendInitialStateUpdate(self, callback):
lines = None
@ -280,11 +321,12 @@ class Printer():
try:
callback.zChangeCB(self._currentZ)
callback.stateChangeCB(self._state, self.getStateString(), self._getStateFlags())
callback.logChangeCB(self._latestLog, self._log)
callback.messageChangeCB(self._latestMessage, self._messages)
callback.logChangeCB(self._latestLog)
callback.messageChangeCB(self._latestMessage)
callback.progressChangeCB(self._progress, self._printTime, self._printTimeLeft)
callback.temperatureChangeCB(self._temp, self._bedTemp, self._targetTemp, self._targetBedTemp, self._temps)
callback.temperatureChangeCB(time.time() * 1000, self._temp, self._bedTemp, self._targetTemp, self._targetBedTemp)
callback.jobDataChangeCB(self._filename, lines, estimatedPrintTime, filament)
callback.sendHistoryData(self._temps, self._log, self._messages)
except Exception, err:
import sys
sys.stderr.write("ERROR: %s\n" % str(err))
@ -347,12 +389,10 @@ class Printer():
self._setProgressData(self._comm.getPrintPos(), self._comm.getPrintTime(), self._comm.getPrintTimeRemainingEstimate())
def mcZChange(self, newZ):
"""
Callback method for the comm object, called upon change of the z-layer.
"""
print("Got callback for z change: " + str(newZ))
oldZ = self._currentZ
if self._timelapse is not None:
self._timelapse.onZChange(oldZ, newZ)
@ -362,23 +402,15 @@ class Printer():
#~~ callbacks triggered by gcodeLoader
def onGcodeLoadingProgress(self, progress):
for callback in self._callbacks:
try: callback.gcodeChangeCB(self._gcodeLoader._filename, progress)
except Exception, err:
import sys
sys.stderr.write("ERROR: %s\n" % str(err))
pass
self._addUpdate(self._sendGcodeCallbacks, {"filename": self._gcodeLoader._filename, "progress": progress})
def onGcodeLoaded(self):
self._setJobData(self._gcodeLoader._filename, self._gcodeLoader._gcode, self._gcodeLoader._gcodeList)
self._setCurrentZ(None)
self._setProgressData(None, None, None)
self._gcodeLoader = None
for callback in self._callbacks:
try: callback.stateChangeCB(self._state, self.getStateString(), self._getStateFlags())
except: pass
self._addUpdate(self._sendStateCallbacks, {"state": self._state, "stateString": self.getStateString(), "stateFlags": self._getStateFlags()})
#~~ state reports
@ -443,9 +475,9 @@ class GcodeLoader(Thread):
"""
def __init__(self, filename, printerCallback):
Thread.__init__(self);
Thread.__init__(self)
self._printerCallback = printerCallback;
self._printerCallback = printerCallback
self._filename = filename
self._progress = None
@ -489,20 +521,23 @@ class PrinterCallback(object):
def progressChangeCB(self, currentLine, printTime, printTimeLeft):
pass
def temperatureChangeCB(self, temp, bedTemp, targetTemp, bedTargetTemp, history):
def temperatureChangeCB(self, currentTime, temp, bedTemp, targetTemp, bedTargetTemp):
pass
def stateChangeCB(self, state, stateString, booleanStates):
pass
def logChangeCB(self, line, history):
def logChangeCB(self, line):
pass
def messageChangeCB(self, line, history):
def messageChangeCB(self, line):
pass
def gcodeChangeCB(self, filename, progress):
pass
def jobDataChangeCB(self, filename, lines, estimatedPrintTime, filamentLength):
pass
def sendHistoryData(self, tempHistory, logHistory, messageHistory):
pass

View file

@ -34,9 +34,11 @@ def index():
class PrinterStateConnection(tornadio2.SocketConnection, PrinterCallback):
def on_open(self, info):
print("Opened socket")
printer.registerCallback(self)
def on_close(self):
print("Closed socket")
printer.unregisterCallback(self)
def zChangeCB(self, currentZ):
@ -44,6 +46,7 @@ class PrinterStateConnection(tornadio2.SocketConnection, PrinterCallback):
if currentZ:
formattedCurrentZ = "%.2f mm" % (currentZ)
print("Sending zChange...")
self.emit("zChange", {"currentZ": formattedCurrentZ})
def progressChangeCB(self, currentLine, printTimeInSeconds, printTimeLeftInMinutes):
@ -55,31 +58,37 @@ class PrinterStateConnection(tornadio2.SocketConnection, PrinterCallback):
if (printTimeLeftInMinutes):
formattedPrintTimeLeft = _getFormattedTimeDelta(datetime.timedelta(minutes=printTimeLeftInMinutes))
print("Sending progressChange...")
self.emit("printProgress", {
"currentLine": currentLine,
"printTime": formattedPrintTime,
"printTimeLeft": formattedPrintTimeLeft
})
def temperatureChangeCB(self, temp, bedTemp, targetTemp, bedTargetTemp, history):
def temperatureChangeCB(self, currentTime, temp, bedTemp, targetTemp, targetBedTemp):
print("Sending temperatureChange...")
self.emit("temperature", {
"currentTemp": temp,
"currentBedTemp": bedTemp,
"currentTargetTemp": targetTemp,
"currentTargetBedTemp": bedTargetTemp,
"history": history
"currentTime": currentTime,
"temp": temp,
"bedTemp": bedTemp,
"targetTemp": targetTemp,
"targetBedTemp": targetBedTemp
})
def stateChangeCB(self, state, stateString, booleanStates):
print("Sending stateChange...")
self.emit("state", {"currentState": stateString, "flags": booleanStates})
def logChangeCB(self, line, history):
self.emit("log", {"line": line, "history": history})
def logChangeCB(self, line):
print("Sending logChange...")
self.emit("log", {"line": line})
def messageChangeCB(self, line, history):
self.emit("message", {"line": line, "history": history})
def messageChangeCB(self, line):
print("Sending messageChange...")
self.emit("message", {"line": line})
def gcodeChangeCB(self, filename, progress):
print("Sending gcodeChange...")
self.emit("jobData", {"filename": "Loading... (%d%%)" % (round(progress * 100)), "lineCount": None, "estimatedPrintTime": None, "filament": None})
def jobDataChangeCB(self, filename, lines, estimatedPrintTimeInMinutes, filamentLengthInMillimeters):
@ -95,8 +104,13 @@ class PrinterStateConnection(tornadio2.SocketConnection, PrinterCallback):
if filename:
formattedFilename = filename.replace(UPLOAD_FOLDER + os.sep, "")
print("Sending jobDataChange...")
self.emit("jobData", {"filename": formattedFilename, "lineCount": lines, "estimatedPrintTime": formattedPrintTimeEstimation, "filament": formattedFilament})
def sendHistoryData(self, tempHistory, logHistory, messageHistory):
print("Sending history...")
self.emit("history", {"temperature": tempHistory, "log": logHistory, "message": messageHistory})
#~~ Printer control
@app.route(BASEURL + "control/connectionOptions", methods=["GET"])

View file

@ -250,8 +250,34 @@ function TemperatureViewModel() {
self.bedTemp(data.bedTemp);
self.targetTemp(data.targetTemp);
self.bedTargetTemp(data.bedTargetTemp);
self.temperatures = (data.history);
// plot
if (!self.temperatures)
self.temperatures = [];
if (!self.temperatures.actual)
self.temperatures.actual = [];
if (!self.temperatures.target)
self.temperatures.target = [];
if (!self.temperatures.actualBed)
self.temperatures.actualBed = [];
if (!self.temperatures.targetBed)
self.temperatures.targetBed = [];
self.temperatures.actual.push([data.currentTime, data.temp])
self.temperatures.target.push([data.currentTime, data.targetTemp])
self.temperatures.actualBed.push([data.currentTime, data.bedTemp])
self.temperatures.targetBed.push([data.currentTime, data.bedTargetTemp])
self.temperatures.actual = self.temperatures.actual.slice(-300);
self.temperatures.target = self.temperatures.target.slice(-300);
self.temperatures.actualBed = self.temperatures.actualBed.slice(-300);
self.temperatures.targetBed = self.temperatures.targetBed.slice(-300);
self.updatePlot();
}
self.fromHistoryEvent = function(data) {
self.temperatures = data;
self.updatePlot();
}
@ -312,7 +338,7 @@ var speedViewModel = new SpeedViewModel();
function TerminalViewModel() {
var self = this;
self.log = undefined;
self.log = [];
self.isErrorOrClosed = ko.observable(undefined);
self.isOperational = ko.observable(undefined);
@ -333,11 +359,21 @@ function TerminalViewModel() {
}
self.fromLogEvent = function(data) {
self.log = data.history;
if (!self.log)
self.log = []
self.log.push(data.log)
self.updateOutput();
}
self.fromHistoryEvent = function(data) {
self.log = data;
self.updateOutput();
}
self.updateOutput = function() {
if (!self.log)
return;
var output = "";
for (var i = 0; i < self.log.length; i++) {
output += self.log[i] + "\n";
@ -516,6 +552,10 @@ function DataUpdater(connectionViewModel, printerStateViewModel, temperatureView
self.socket.on("zChange", function(data) {
self.printerStateViewModel.fromZChangeEvent(data);
})
self.socket.on("history", function(data) {
self.temperatureViewModel.fromHistoryEvent(data.temperature)
self.terminalViewModel.fromHistoryEvent(data.log)
})
self.requestData = function() {
var parameters = {};