- Gcode for printjob is now loaded asynchronously, including a progress indicator in the frontend

- New connection section which also allows selection of port and baudrate to use for connecting to printer (default values from profile file are pre-selected if available)
- Connect button now switches to disconnect-functionality while connected
- Bugfix: Jog controls are now also enabled if no job is loaded
- Cleanups: More consistent quotes, some documentation
master
Gina Häußge 2012-12-28 20:37:40 +01:00
parent 083498865d
commit f40460d797
5 changed files with 404 additions and 159 deletions

View File

@ -1,11 +1,11 @@
#!/usr/bin/env python
# coding=utf-8
__author__ = 'Gina Häußge <osd@foosel.net>'
__author__ = "Gina Häußge <osd@foosel.net>"
from flask import Flask, request, render_template, jsonify, make_response
from werkzeug import secure_filename
from printer import Printer
from printer import Printer, getConnectionOptions
import sys
import os
@ -16,15 +16,15 @@ BASEURL="/ajax/"
SUCCESS={}
# taken from http://stackoverflow.com/questions/1084697/how-do-i-store-desktop-application-data-in-a-cross-platform-way-for-python
if sys.platform == 'darwin':
if sys.platform == "darwin":
from AppKit import NSSearchPathForDirectoriesInDomains
# http://developer.apple.com/DOCUMENTATION/Cocoa/Reference/Foundation/Miscellaneous/Foundation_Functions/Reference/reference.html#//apple_ref/c/func/NSSearchPathForDirectoriesInDomains
# NSApplicationSupportDirectory = 14
# NSUserDomainMask = 1
# True for expanding the tilde into a fully qualified path
appdata = os.path.join(NSSearchPathForDirectoriesInDomains(14, 1, True)[0], APPNAME)
elif sys.platform == 'win32':
appdata = os.path.join(os.environ['APPDATA'], APPNAME)
elif sys.platform == "win32":
appdata = os.path.join(os.environ["APPDATA"], APPNAME)
else:
appdata = os.path.expanduser(os.path.join("~", "." + APPNAME.lower()))
@ -36,94 +36,111 @@ ALLOWED_EXTENSIONS = set(["gcode"])
app = Flask("Cura.webui")
printer = Printer()
@app.route('/')
@app.route("/")
def index():
return render_template('index.html')
return render_template("index.html")
#~~ Printer state
@app.route(BASEURL + 'state', methods=['GET'])
@app.route(BASEURL + "state", methods=["GET"])
def printerState():
temp = printer.currentTemp
bedTemp = printer.currentBedTemp
targetTemp = printer.currentTargetTemp
bedTargetTemp = printer.currentBedTargetTemp
jobData = printer.jobData()
gcodeState = printer.gcodeState()
result = {
'state': printer.getStateString(),
'temp': temp,
'bedTemp': bedTemp,
'targetTemp': targetTemp,
'targetBedTemp': bedTargetTemp,
'operational': printer.isOperational(),
'closedOrError': printer.isClosedOrError(),
'error': printer.isError(),
'printing': printer.isPrinting(),
'paused': printer.isPaused(),
'ready': printer.isReady()
"state": printer.getStateString(),
"temp": temp,
"bedTemp": bedTemp,
"targetTemp": targetTemp,
"targetBedTemp": bedTargetTemp,
"operational": printer.isOperational(),
"closedOrError": printer.isClosedOrError(),
"error": printer.isError(),
"printing": printer.isPrinting(),
"paused": printer.isPaused(),
"ready": printer.isReady(),
"loading": printer.isLoading()
}
if (jobData != None):
result['job'] = jobData
if jobData is not None:
jobData["filename"] = jobData["filename"].replace(UPLOAD_FOLDER + os.sep, "")
result["job"] = jobData
if (request.values.has_key('temperatures')):
result['temperatures'] = printer.temps
if gcodeState is not None:
gcodeState["filename"] = gcodeState["filename"].replace(UPLOAD_FOLDER + os.sep, "")
result["gcode"] = gcodeState
if (request.values.has_key('log')):
result['log'] = printer.log
if request.values.has_key("temperatures"):
result["temperatures"] = printer.temps
if (request.values.has_key('messages')):
result['messages'] = printer.messages
if request.values.has_key("log"):
result["log"] = printer.log
if request.values.has_key("messages"):
result["messages"] = printer.messages
return jsonify(result)
@app.route(BASEURL + 'state/messages', methods=['GET'])
@app.route(BASEURL + "state/messages", methods=["GET"])
def printerMessages():
return jsonify(messages=printer.messages)
@app.route(BASEURL + 'state/log', methods=['GET'])
@app.route(BASEURL + "state/log", methods=["GET"])
def printerLogs():
return jsonify(log=printer.log)
@app.route(BASEURL + 'state/temperatures', methods=['GET'])
@app.route(BASEURL + "state/temperatures", methods=["GET"])
def printerTemperatures():
return jsonify(temperatures = printer.temps)
#~~ Printer control
@app.route(BASEURL + 'control/connect', methods=['POST'])
def connect():
printer.connect()
return jsonify(state='Connecting')
@app.route(BASEURL + "control/connectionOptions", methods=["GET"])
def connectionOptions():
return jsonify(getConnectionOptions())
@app.route(BASEURL + 'control/disconnect', methods=['POST'])
@app.route(BASEURL + "control/connect", methods=["POST"])
def connect():
port = None
baudrate = None
if request.values.has_key("port"):
port = request.values["port"]
if request.values.has_key("baudrate"):
baudrate = request.values["baudrate"]
printer.connect(port=port, baudrate=baudrate)
return jsonify(state="Connecting")
@app.route(BASEURL + "control/disconnect", methods=["POST"])
def disconnect():
printer.disconnect()
return jsonify(state='Offline')
return jsonify(state="Offline")
@app.route(BASEURL + 'control/command', methods=['POST'])
@app.route(BASEURL + "control/command", methods=["POST"])
def printerCommand():
command = request.form['command']
command = request.form["command"]
printer.command(command)
return jsonify(SUCCESS)
@app.route(BASEURL + 'control/print', methods=['POST'])
@app.route(BASEURL + "control/print", methods=["POST"])
def printGcode():
printer.startPrint()
return jsonify(SUCCESS)
@app.route(BASEURL + 'control/pause', methods=['POST'])
@app.route(BASEURL + "control/pause", methods=["POST"])
def pausePrint():
printer.togglePausePrint()
return jsonify(SUCCESS)
@app.route(BASEURL + 'control/cancel', methods=['POST'])
@app.route(BASEURL + "control/cancel", methods=["POST"])
def cancelPrint():
printer.cancelPrint()
return jsonify(SUCCESS)
@app.route(BASEURL + 'control/temperature', methods=['POST'])
@app.route(BASEURL + "control/temperature", methods=["POST"])
def setTargetTemperature():
if not printer.isOperational():
return jsonify(SUCCESS)
@ -143,7 +160,7 @@ def setTargetTemperature():
@app.route(BASEURL + "control/jog", methods=["POST"])
def jog():
if not printer.isOperational() or printer.isPrinting():
# do not jog when a print job is running or we don't have a connection
# do not jog when a print job is running or we don"t have a connection
return jsonify(SUCCESS)
if request.values.has_key("x"):
@ -169,7 +186,7 @@ def jog():
#~~ GCODE file handling
@app.route(BASEURL + 'gcodefiles', methods=['GET'])
@app.route(BASEURL + "gcodefiles", methods=["GET"])
def readGcodeFiles():
files = []
for osFile in os.listdir(UPLOAD_FOLDER):
@ -181,22 +198,22 @@ def readGcodeFiles():
})
return jsonify(files=files)
@app.route(BASEURL + 'gcodefiles/upload', methods=['POST'])
@app.route(BASEURL + "gcodefiles/upload", methods=["POST"])
def uploadGcodeFile():
file = request.files['gcode_file']
file = request.files["gcode_file"]
if file and allowed_file(file.filename):
secure = secure_filename(file.filename)
filename = os.path.join(UPLOAD_FOLDER, secure)
file.save(filename)
return readGcodeFiles()
@app.route(BASEURL + 'gcodefiles/load', methods=['POST'])
@app.route(BASEURL + "gcodefiles/load", methods=["POST"])
def loadGcodeFile():
filename = request.values["filename"]
printer.loadGcode(UPLOAD_FOLDER + os.sep + filename)
return jsonify(SUCCESS)
@app.route(BASEURL + 'gcodefiles/delete', methods=['POST'])
@app.route(BASEURL + "gcodefiles/delete", methods=["POST"])
def deleteGcodeFile():
if request.values.has_key("filename"):
filename = request.values["filename"]
@ -210,14 +227,15 @@ def sizeof_fmt(num):
"""
Taken from http://stackoverflow.com/questions/1094841/reusable-library-to-get-human-readable-version-of-file-size
"""
for x in ['bytes','KB','MB','GB']:
for x in ["bytes","KB","MB","GB"]:
if num < 1024.0:
return "%3.1f%s" % (num, x)
num /= 1024.0
return "%3.1f%s" % (num, 'TB')
return "%3.1f%s" % (num, "TB")
def allowed_file(filename):
return "." in filename and filename.rsplit(".", 1)[1] in ALLOWED_EXTENSIONS
def run():
app.debug = True
app.run(host="0.0.0.0", port=5000)

View File

@ -1,20 +1,33 @@
# coding=utf-8
__author__ = 'Gina Häußge <osd@foosel.net>'
__author__ = "Gina Häußge <osd@foosel.net>"
import time
import os
from threading import Thread
import Cura.util.machineCom as machineCom
from Cura.util import gcodeInterpreter
from Cura.util import profile
def getConnectionOptions():
"""
Retrieves the available ports, baudrates, prefered port and baudrate for connecting to the printer.
"""
return {
"ports": sorted(machineCom.serialList(), key=str.lower),
"baudrates": sorted(machineCom.baudrateList(), key=int, reverse=True),
"portPreference": profile.getPreference('serial_port_auto'),
"baudratePreference": int(profile.getPreference('serial_baud_auto'))
}
class Printer():
def __init__(self):
# state
self.temps = {
'actual': [],
'target': [],
'actualBed': [],
'targetBed': []
"actual": [],
"target": [],
"actualBed": [],
"targetBed": []
}
self.messages = []
self.log = []
@ -32,44 +45,68 @@ class Printer():
self.gcodeList = None
self.filename = None
self.gcodeLoader = None
# comm
self.comm = None
def connect(self):
if self.comm != None:
def connect(self, port=None, baudrate=None):
"""
Connects to the printer. If port and/or baudrate is provided, uses these settings, otherwise autodetection
will be attempted.
"""
if self.comm is not None:
self.comm.close()
self.comm = machineCom.MachineCom(callbackObject=self)
self.comm = machineCom.MachineCom(port, baudrate, callbackObject=self)
def disconnect(self):
if self.comm != None:
"""
Closes the connection to the printer.
"""
if self.comm is not None:
self.comm.close()
self.comm = None
def command(self, command):
"""
Sends a single gcode command to the printer.
"""
self.commands([command])
def commands(self, commands):
"""
Sends multiple gcode commands (provided as a list) to the printer.
"""
for command in commands:
self.comm.sendCommand(command)
def mcLog(self, message):
"""
Callback method for the comm object, called upon log output.
Log line is stored in internal buffer, which is truncated to the last 300 lines.
"""
self.log.append(message)
self.log = self.log[-300:]
def mcTempUpdate(self, temp, bedTemp, targetTemp, bedTargetTemp):
"""
Callback method for the comm object, called upon receiving new temperature information.
Temperature information (actual and target) for print head and print bed is stored in corresponding
temperature history (including timestamp), history is truncated to 300 entries.
"""
currentTime = int(time.time() * 1000)
self.temps['actual'].append((currentTime, temp))
self.temps['actual'] = self.temps['actual'][-300:]
self.temps["actual"].append((currentTime, temp))
self.temps["actual"] = self.temps["actual"][-300:]
self.temps['target'].append((currentTime, targetTemp))
self.temps['target'] = self.temps['target'][-300:]
self.temps["target"].append((currentTime, targetTemp))
self.temps["target"] = self.temps["target"][-300:]
self.temps['actualBed'].append((currentTime, bedTemp))
self.temps['actualBed'] = self.temps['actualBed'][-300:]
self.temps["actualBed"].append((currentTime, bedTemp))
self.temps["actualBed"] = self.temps["actualBed"][-300:]
self.temps['targetBed'].append((currentTime, bedTargetTemp))
self.temps['targetBed'] = self.temps['targetBed'][-300:]
self.temps["targetBed"].append((currentTime, bedTargetTemp))
self.temps["targetBed"] = self.temps["targetBed"][-300:]
self.currentTemp = temp
self.currentTargetTemp = targetTemp
@ -77,22 +114,55 @@ class Printer():
self.currentBedTargetTemp = bedTargetTemp
def mcStateChange(self, state):
"""
Callback method for the comm object, called if the connection state changes.
New state is stored for retrieval by the frontend.
"""
self.state = state
def mcMessage(self, message):
"""
Callback method for the comm object, called upon message exchanges via serial.
Stores the message in the message buffer, truncates buffer to the last 300 lines.
"""
self.messages.append(message)
self.messages = self.messages[-300:]
def mcProgress(self, lineNr):
"""
Callback method for the comm object, called upon any change in progress of the printjob.
Triggers storage of new values for printTime, printTimeLeft and the current line.
"""
self.printTime = self.comm.getPrintTime()
self.printTimeLeft = self.comm.getPrintTimeRemainingEstimate()
self.progress = self.comm.getPrintPos()
def mcZChange(self, newZ):
"""
Callback method for the comm object, called upon change of the z-layer.
"""
self.currentZ = newZ
def onGcodeLoaded(self, gcodeLoader):
"""
Callback method for the gcode loader, gets called when the gcode for the new printjob has finished loading.
Takes care to set filename, gcode and commandlist from the gcode loader and reset print job progress.
"""
self.filename = gcodeLoader.filename
self.gcode = gcodeLoader.gcode
self.gcodeList = gcodeLoader.gcodeList
self.currentZ = None
self.progress = None
self.printTime = None
self.printTimeLeft = None
self.gcodeLoader = None
def jobData(self):
if self.gcode != None:
"""
Returns statistics regarding the currently loaded printjob, or None if no printjob is loaded.
"""
if self.gcode is not None:
formattedPrintTime = None
if (self.printTime):
formattedPrintTime = "%02d:%02d" % (int(self.printTime / 60), int(self.printTime % 60))
@ -102,16 +172,17 @@ class Printer():
formattedPrintTimeLeft = "%02d:%02d" % (int(self.printTimeLeft / 60), int(self.printTimeLeft % 60))
data = {
'currentZ': self.currentZ,
'line': self.progress,
'totalLines': len(self.gcodeList),
'printTime': formattedPrintTime,
'printTimeLeft': formattedPrintTimeLeft,
'filament': "%.2fm %.2fg" % (
"filename": self.filename,
"currentZ": self.currentZ,
"line": self.progress,
"totalLines": len(self.gcodeList),
"printTime": formattedPrintTime,
"printTimeLeft": formattedPrintTimeLeft,
"filament": "%.2fm %.2fg" % (
self.gcode.extrusionAmount / 1000,
self.gcode.calculateWeight() * 1000
),
'estimatedPrintTime': "%02d:%02d" % (
"estimatedPrintTime": "%02d:%02d" % (
int(self.gcode.totalMoveTimeMinute / 60),
int(self.gcode.totalMoveTimeMinute % 60)
)
@ -120,64 +191,68 @@ class Printer():
data = None
return data
def gcodeState(self):
if self.gcodeLoader is not None:
return {
"filename": self.gcodeLoader.filename,
"progress": self.gcodeLoader.progress
}
else:
return None
def getStateString(self):
if self.comm == None:
return 'Offline'
"""
Returns a human readable string corresponding to the current communication state.
"""
if self.comm is None:
return "Offline"
else:
return self.comm.getStateString()
def isClosedOrError(self):
return self.comm == None or self.comm.isClosedOrError()
return self.comm is None or self.comm.isClosedOrError()
def isOperational(self):
return self.comm != None and self.comm.isOperational()
return self.comm is not None and self.comm.isOperational()
def isPrinting(self):
return self.comm != None and self.comm.isPrinting()
return self.comm is not None and self.comm.isPrinting()
def isPaused(self):
return self.comm != None and self.comm.isPaused()
return self.comm is not None and self.comm.isPaused()
def isError(self):
return self.comm != None and self.comm.isError()
return self.comm is not None and self.comm.isError()
def isReady(self):
return self.gcodeList and len(self.gcodeList) > 0
return self.gcodeLoader is None and self.gcodeList and len(self.gcodeList) > 0
def isLoading(self):
return self.gcodeLoader is not None
def loadGcode(self, file):
if self.comm != None and self.comm.isPrinting():
"""
Loads the gcode from the given file as the new print job.
Aborts if the printer is currently printing or another gcode file is currently being loaded.
"""
if (self.comm is not None and self.comm.isPrinting()) or (self.gcodeLoader is not None):
return
#Send an initial M110 to reset the line counter to zero.
prevLineType = lineType = 'CUSTOM'
gcodeList = ["M110"]
for line in open(file, 'r'):
if line.startswith(';TYPE:'):
lineType = line[6:].strip()
if ';' in line:
line = line[0:line.find(';')]
line = line.strip()
if len(line) > 0:
if prevLineType != lineType:
gcodeList.append((line, lineType, ))
else:
gcodeList.append(line)
prevLineType = lineType
gcode = gcodeInterpreter.gcode()
gcode.loadList(gcodeList)
#print "Loaded: %s (%d)" % (filename, len(gcodeList))
self.filename = file
self.gcode = gcode
self.gcodeList = gcodeList
self.currentZ = None
self.progress = None
self.printTime = None
self.printTimeLeft = None
self.filename = None
self.gcode = None
self.gcodeList = None
self.gcodeLoader = GcodeLoader(file, self)
self.gcodeLoader.start()
def startPrint(self):
if self.comm == None or not self.comm.isOperational():
"""
Starts the currently loaded print job.
Only starts if the printer is connected and operational, not currently printing and a printjob is loaded
"""
if self.comm is None or not self.comm.isOperational():
return
if self.gcodeList == None:
if self.gcodeList is None:
return
if self.comm.isPrinting():
return
@ -185,13 +260,72 @@ class Printer():
self.comm.printGCode(self.gcodeList)
def togglePausePrint(self):
if self.comm == None:
"""
Pause the current printjob.
"""
if self.comm is None:
return
self.comm.setPause(not self.comm.isPaused())
def cancelPrint(self):
if self.comm == None:
def cancelPrint(self, disableMotorsAndHeater=True):
"""
Cancel the current printjob.
"""
if self.comm is None:
return
self.comm.cancelPrint()
self.comm.sendCommands(["M84", "M104 S0", "M140 S0"]) # disable motors, switch off heaters
if disableMotorsAndHeater:
self.commands(["M84", "M104 S0", "M140 S0"]) # disable motors, switch off heaters
# reset line, height, print time
self.currentZ = None
self.progress = None
self.printTime = None
self.printTimeLeft = None
class GcodeLoader(Thread):
"""
The GcodeLoader takes care of loading a gcode-File from disk and parsing it into a gcode object in a separate
thread while constantly notifying interested listeners about the current progress.
The progress is returned as a float value between 0 and 1 which is to be interpreted as the percentage of completion.
"""
def __init__(self, filename, printerCallback):
Thread.__init__(self);
self.printerCallback = printerCallback;
self.filename = filename
self.progress = None
self.gcode = None
self.gcodeList = None
def run(self):
#Send an initial M110 to reset the line counter to zero.
prevLineType = lineType = "CUSTOM"
gcodeList = ["M110"]
with open(self.filename, "r") as file:
for line in file:
if line.startswith(";TYPE:"):
lineType = line[6:].strip()
if ";" in line:
line = line[0:line.find(";")]
line = line.strip()
if len(line) > 0:
if prevLineType != lineType:
gcodeList.append((line, lineType, ))
else:
gcodeList.append(line)
prevLineType = lineType
self.gcodeList = gcodeList
self.gcode = gcodeInterpreter.gcode()
self.gcode.progressCallback = self.onProgress
self.gcode.loadList(self.gcodeList)
self.printerCallback.onGcodeLoaded(self)
def onProgress(self, progress):
self.progress = progress

View File

@ -50,4 +50,8 @@ table th.gcode_files_action, table td.gcode_files_action {
#temp_newTemp {
text-align: right;
}
#connection_ports, #connection_baudrates {
width: 100%;
}

View File

@ -1,3 +1,65 @@
function ConnectionViewModel() {
var self = this;
self.portOptions = ko.observableArray(undefined);
self.baudrateOptions = ko.observableArray(undefined);
self.selectedPort = ko.observable(undefined);
self.selectedBaudrate = ko.observable(undefined);
self.isErrorOrClosed = ko.observable(undefined);
self.isOperational = ko.observable(undefined);
self.isPrinting = ko.observable(undefined);
self.isPaused = ko.observable(undefined);
self.isError = ko.observable(undefined);
self.isReady = ko.observable(undefined);
self.isLoading = ko.observable(undefined);
self.buttonText = ko.computed(function() {
if (self.isErrorOrClosed())
return "Connect";
else
return "Disconnect";
})
self.fromResponse = function(response) {
self.portOptions(response.ports);
self.baudrateOptions(response.baudrates);
if (!self.selectedPort() && response.ports && response.ports.indexOf(response.portPreference) >= 0)
self.selectedPort(response.portPreference);
if (!self.selectedBaudrate() && response.baudrates && response.baudrates.indexOf(response.baudratePreference) >= 0)
self.selectedBaudrate(response.baudratePreference);
}
self.fromStateResponse = function(response) {
self.isErrorOrClosed(response.closedOrError);
self.isOperational(response.operational);
self.isPaused(response.paused);
self.isPrinting(response.printing);
self.isError(response.error);
self.isReady(response.ready);
self.isLoading(response.loading);
}
self.connect = function() {
if (self.isErrorOrClosed()) {
$.ajax({
url: AJAX_BASEURL + "control/connect",
type: "POST",
dataType: "json",
data: { "port": self.selectedPort(), "baudrate": self.selectedBaudrate() }
})
} else {
$.ajax({
url: AJAX_BASEURL + "control/disconnect",
type: "POST",
dataType: "json"
})
}
}
}
var connectionViewModel = new ConnectionViewModel();
function PrinterStateViewModel() {
var self = this;
@ -8,7 +70,9 @@ function PrinterStateViewModel() {
self.isPaused = ko.observable(undefined);
self.isError = ko.observable(undefined);
self.isReady = ko.observable(undefined);
self.isLoading = ko.observable(undefined);
self.filename = ko.observable(undefined);
self.filament = ko.observable(undefined);
self.estimatedPrintTime = ko.observable(undefined);
self.printTime = ko.observable(undefined);
@ -35,14 +99,6 @@ function PrinterStateViewModel() {
return "Pause";
});
self.connect = function() {
$.ajax({
url: AJAX_BASEURL + "control/connect",
type: 'POST',
dataType: 'json'
})
}
self.fromResponse = function(response) {
self.stateString(response.state);
self.isErrorOrClosed(response.closedOrError);
@ -51,8 +107,10 @@ function PrinterStateViewModel() {
self.isPrinting(response.printing);
self.isError(response.error);
self.isReady(response.ready);
self.isLoading(response.loading);
if (response.job) {
self.filename(response.job.filename);
self.filament(response.job.filament);
self.estimatedPrintTime(response.job.estimatedPrintTime);
self.printTime(response.job.printTime);
@ -61,11 +119,17 @@ function PrinterStateViewModel() {
self.totalLines(response.job.totalLines ? response.job.totalLines : 0);
self.currentHeight(response.job.currentZ);
} else {
if (response.loading && response.gcode) {
self.filename("Loading... (" + Math.round(response.gcode.progress * 100) + "%)");
} else {
self.filename(undefined);
}
self.filament(undefined);
self.estimatedPrintTime(undefined);
self.printTime(undefined);
self.printTimeLeft(undefined);
self.currentLine(undefined);
self.totalLines(undefined);
self.currentHeight(undefined);
}
}
@ -79,12 +143,14 @@ function TemperatureViewModel() {
self.bedTemp = ko.observable(undefined);
self.targetTemp = ko.observable(undefined);
self.bedTargetTemp = ko.observable(undefined);
self.isErrorOrClosed = ko.observable(undefined);
self.isOperational = ko.observable(undefined);
self.isPrinting = ko.observable(undefined);
self.isPaused = ko.observable(undefined);
self.isError = ko.observable(undefined);
self.isReady = ko.observable(undefined);
self.isLoading = ko.observable(undefined);
self.tempString = ko.computed(function() {
if (!self.temp())
@ -116,7 +182,6 @@ function TemperatureViewModel() {
},
xaxis: {
mode: "time",
timeformat: "%H:%M:%S",
minTickSize: [2, "minute"],
tickFormatter: function(val, axis) {
var now = new Date();
@ -146,6 +211,7 @@ function TemperatureViewModel() {
self.isPrinting(response.printing);
self.isError(response.error);
self.isReady(response.ready);
self.isLoading(response.loading);
self.updatePlot();
}
@ -174,9 +240,9 @@ function TerminalViewModel() {
}
self.updateOutput = function() {
var output = '';
var output = "";
for (var i = 0; i < self.log.length; i++) {
output += self.log[i] + '\n';
output += self.log[i] + "\n";
}
var container = $("#terminal-output");
@ -227,13 +293,14 @@ function GcodeFilesViewModel() {
}
var gcodeFilesViewModel = new GcodeFilesViewModel();
function DataUpdater(printerStateViewModel, temperatureViewModel, terminalViewModel) {
function DataUpdater(connectionViewModel, printerStateViewModel, temperatureViewModel, terminalViewModel) {
var self = this;
self.updateInterval = 500;
self.includeTemperatures = true;
self.includeLogs = true;
self.connectionViewModel = connectionViewModel;
self.printerStateViewModel = printerStateViewModel;
self.temperatureViewModel = temperatureViewModel;
self.terminalViewModel = terminalViewModel;
@ -248,48 +315,51 @@ function DataUpdater(printerStateViewModel, temperatureViewModel, terminalViewMo
$.ajax({
url: AJAX_BASEURL + "state",
type: 'GET',
dataType: 'json',
type: "GET",
dataType: "json",
data: parameters,
success: function(response) {
self.printerStateViewModel.fromResponse(response);
self.connectionViewModel.fromStateResponse(response);
if (response.temperatures)
self.temperatureViewModel.fromResponse(response);
if (response.log)
self.terminalViewModel.fromResponse(response);
},
error: function(jqXHR, textState, errorThrows) {
//alert(textState);
}
});
setTimeout(self.requestData, self.updateInterval);
}
}
var dataUpdater = new DataUpdater(printerStateViewModel, temperatureViewModel, terminalViewModel);
var dataUpdater = new DataUpdater(connectionViewModel, printerStateViewModel, temperatureViewModel, terminalViewModel);
$(function() {
$("#printer_connect").click(printerStateViewModel.connect);
$("#job_print").click(function() {
$.ajax({
url: AJAX_BASEURL + "control/print",
type: 'POST',
dataType: 'json',
type: "POST",
dataType: "json",
success: function(){}
})
})
$("#job_pause").click(function() {
$("#job_pause").button('toggle');
$("#job_pause").button("toggle");
$.ajax({
url: AJAX_BASEURL + "control/pause",
type: 'POST',
dataType: 'json',
type: "POST",
dataType: "json"
})
})
$("#job_cancel").click(function() {
$.ajax({
url: AJAX_BASEURL + "control/cancel",
type: 'POST',
dataType: 'json',
type: "POST",
dataType: "json"
})
})
@ -343,26 +413,24 @@ $(function() {
var command = $("#terminal-command").val();
$.ajax({
url: AJAX_BASEURL + "control/command",
type: 'POST',
dataType: 'json',
data: 'command=' + command,
type: "POST",
dataType: "json",
data: "command=" + command
})
})
$('#gcode_upload').fileupload({
dataType: 'json',
$("#gcode_upload").fileupload({
dataType: "json",
done: function (e, data) {
gcodeFilesViewModel.fromResponse(data.result);
},
progressall: function (e, data) {
var progress = parseInt(data.loaded / data.total * 100, 10);
$('#gcode_upload_progress .bar').css(
'width',
progress + '%'
);
$("#gcode_upload_progress .bar").css("width", progress + "%");
}
});
ko.applyBindings(connectionViewModel, document.getElementById("connection"));
ko.applyBindings(printerStateViewModel, document.getElementById("state"));
ko.applyBindings(gcodeFilesViewModel, document.getElementById("files"));
ko.applyBindings(temperatureViewModel, document.getElementById("temp"));
@ -372,12 +440,20 @@ $(function() {
dataUpdater.requestData();
$.ajax({
url: AJAX_BASEURL + "gcodefiles",
method: 'GET',
dataType: 'json',
method: "GET",
dataType: "json",
success: function(response) {
self.gcodeFilesViewModel.fromResponse(response);
}
});
$.ajax({
url: AJAX_BASEURL + "control/connectionOptions",
method: "GET",
dataType: "json",
success: function(response) {
connectionViewModel.fromResponse(response);
}
})
}
);

View File

@ -32,15 +32,28 @@
<div class="container">
<div class="row">
<div class="accordion span4">
<div class="accordion-group">
<div class="accordion-heading">
<a class="accordion-toggle" data-toggle="collapse" href="#connection"><i class="icon-signal"></i> Connection</a>
</div>
<div class="accordion-body collapse in" id="connection">
<div class="accordion-inner">
<label for="connection_ports" data-bind="css: {disabled: !isErrorOrClosed}, enable: isErrorOrClosed">Serial Port</label>
<select id="connection_ports" data-bind="options: portOptions, optionsCaption: 'AUTO', value: selectedPort, css: {disabled: !isErrorOrClosed}, enable: isErrorOrClosed"></select>
<label for="connection_baudrates" data-bind="css: {disabled: !isErrorOrClosed}, enable: isErrorOrClosed">Baudrate</label>
<select id="connection_baudrates" data-bind="options: baudrateOptions, optionsCaption: 'AUTO', value: selectedBaudrate, css: {disabled: !isErrorOrClosed}, enable: isErrorOrClosed"></select>
<button class="btn btn-block" id="printer_connect" data-bind="click: connect, text: buttonText()">Connect</button>
</div>
</div>
</div>
<div class="accordion-group">
<div class="accordion-heading">
<a class="accordion-toggle" data-toggle="collapse" href="#state"><i class="icon-info-sign"></i> State</a>
</div>
<div class="accordion-body collapse in" id="state">
<div class="accordion-inner">
<button class="btn btn-block" id="printer_connect" data-bind="css: {disabled: !isErrorOrClosed}, enable: isErrorOrClosed">Connect</button>
Machine State: <strong data-bind="text: stateString"></strong><br>
File: <strong data-bind="text: filename"></strong><br>
Filament: <strong data-bind="text: filament"></strong><br>
Estimated Print Time: <strong data-bind="text: estimatedPrintTime"></strong><br>
Line: <strong data-bind="text: lineString"></strong><br>
@ -144,21 +157,21 @@
<div class="tab-pane" id="jog">
<div style="width: 350px; height: 70px">
<div style="width: 70px; float: left;">&nbsp;</div>
<div style="width: 70px; float: left;"><button class="btn btn-block" id="jog_y_inc" data-bind="enable: isOperational() && isReady() && !isPrinting()">Up</button></div>
<div style="width: 70px; float: left;"><button class="btn btn-block" id="jog_y_inc" data-bind="enable: isOperational() && !isPrinting()">Up</button></div>
<div style="width: 70px; float: left;">&nbsp;</div>
<div style="width: 70px; float: left; margin-left: 20px"><button class="btn btn-block" id="jog_z_inc" data-bind="enable: isOperational() && isReady() && !isPrinting()">Z+</button></div>
<div style="width: 70px; float: left; margin-left: 20px"><button class="btn btn-block" id="jog_z_inc" data-bind="enable: isOperational() && !isPrinting()">Z+</button></div>
</div>
<div style="width: 350px; height: 70px">
<div style="width: 70px; float: left;"><button class="btn btn-block" id="jog_x_dec" data-bind="enable: isOperational() && isReady() && !isPrinting()">Left</button></div>
<div style="width: 70px; float: left;"><button class="btn btn-block" id="jog_xy_home" data-bind="enable: isOperational() && isReady() && !isPrinting()">Home</button></div>
<div style="width: 70px; float: left;"><button class="btn btn-block" id="jog_x_inc" data-bind="enable: isOperational() && isReady() && !isPrinting()">Right</button></div>
<div style="width: 70px; float: left; margin-left: 20px"><button class="btn btn-block" id="jog_z_home" data-bind="enable: isOperational() && isReady() && !isPrinting()">Home</button></div>
<div style="width: 70px; float: left;"><button class="btn btn-block" id="jog_x_dec" data-bind="enable: isOperational() && !isPrinting()">Left</button></div>
<div style="width: 70px; float: left;"><button class="btn btn-block" id="jog_xy_home" data-bind="enable: isOperational() && !isPrinting()">Home</button></div>
<div style="width: 70px; float: left;"><button class="btn btn-block" id="jog_x_inc" data-bind="enable: isOperational() && !isPrinting()">Right</button></div>
<div style="width: 70px; float: left; margin-left: 20px"><button class="btn btn-block" id="jog_z_home" data-bind="enable: isOperational() && !isPrinting()">Home</button></div>
</div>
<div style="width: 350px; height: 70px">
<div style="width: 70px; float: left;">&nbsp;</div>
<div style="width: 70px; float: left;"><button class="btn btn-block" id="jog_y_dec" data-bind="enable: isOperational() && isReady() && !isPrinting()">Down</button></div>
<div style="width: 70px; float: left;"><button class="btn btn-block" id="jog_y_dec" data-bind="enable: isOperational() && !isPrinting()">Down</button></div>
<div style="width: 70px; float: left;">&nbsp;</div>
<div style="width: 70px; float: left; margin-left: 20px"><button class="btn btn-block" id="jog_z_dec" data-bind="enable: isOperational() && isReady() && !isPrinting()">Z-</button></div>
<div style="width: 70px; float: left; margin-left: 20px"><button class="btn btn-block" id="jog_z_dec" data-bind="enable: isOperational() && !isPrinting()">Z-</button></div>
</div>
</div>
<div class="tab-pane" id="speed">