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

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

View File

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

View File

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