MrDraw/printer_webui/server.py
Gina Häußge a85ea69fb0 Established settings mechanism
Allows reading and writing default serial port and baudrate (this also is available via the web interface) and setting the host and port on which the server should listen. Might allow persisting more options in the future.

The configuration file is stored in ~/.printerwebui/config.ini under Linux, in %APPDATA%/PrinterWebUI/config.ini under Windows and should be stored in ~/Library/Application Support/config.ini under MacOS X

Closes #1
2013-01-01 21:04:00 +01:00

293 lines
No EOL
8.3 KiB
Python

# coding=utf-8
__author__ = "Gina Häußge <osd@foosel.net>"
__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html'
from flask import Flask, request, render_template, jsonify, make_response
from werkzeug import secure_filename
from printer_webui.printer import Printer, getConnectionOptions
from printer_webui.settings import settings
import sys
import os
import fnmatch
BASEURL="/ajax/"
SUCCESS={}
UPLOAD_FOLDER = os.path.join(settings().settings_dir, "uploads")
if not os.path.isdir(UPLOAD_FOLDER):
os.makedirs(UPLOAD_FOLDER)
ALLOWED_EXTENSIONS = set(["gcode"])
app = Flask("printer_webui")
printer = Printer()
@app.route("/")
def index():
return render_template("index.html")
#~~ Printer state
@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()
feedrateState = printer.feedrateState()
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(),
"loading": printer.isLoading()
}
if jobData is not None:
jobData["filename"] = jobData["filename"].replace(UPLOAD_FOLDER + os.sep, "")
result["job"] = jobData
if gcodeState is not None:
gcodeState["filename"] = gcodeState["filename"].replace(UPLOAD_FOLDER + os.sep, "")
result["gcode"] = gcodeState
if feedrateState is not None:
result["feedrate"] = feedrateState
if request.values.has_key("temperatures"):
result["temperatures"] = printer.temps
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"])
def printerMessages():
return jsonify(messages=printer.messages)
@app.route(BASEURL + "state/log", methods=["GET"])
def printerLogs():
return jsonify(log=printer.log)
@app.route(BASEURL + "state/temperatures", methods=["GET"])
def printerTemperatures():
return jsonify(temperatures = printer.temps)
#~~ Printer control
@app.route(BASEURL + "control/connectionOptions", methods=["GET"])
def connectionOptions():
return jsonify(getConnectionOptions())
@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"]
if request.values.has_key("save"):
settings().set("serial", "port", port)
settings().set("serial", "baudrate", baudrate)
settings().save()
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")
@app.route(BASEURL + "control/command", methods=["POST"])
def printerCommand():
command = request.form["command"]
printer.command(command)
return jsonify(SUCCESS)
@app.route(BASEURL + "control/print", methods=["POST"])
def printGcode():
printer.startPrint()
return jsonify(SUCCESS)
@app.route(BASEURL + "control/pause", methods=["POST"])
def pausePrint():
printer.togglePausePrint()
return jsonify(SUCCESS)
@app.route(BASEURL + "control/cancel", methods=["POST"])
def cancelPrint():
printer.cancelPrint()
return jsonify(SUCCESS)
@app.route(BASEURL + "control/temperature", methods=["POST"])
def setTargetTemperature():
if not printer.isOperational():
return jsonify(SUCCESS)
if request.values.has_key("temp"):
# set target temperature
temp = request.values["temp"];
printer.command("M104 S" + temp)
if request.values.has_key("bedTemp"):
# set target bed temperature
bedTemp = request.values["bedTemp"]
printer.command("M140 S" + bedTemp)
return jsonify(SUCCESS)
@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
return jsonify(SUCCESS)
if request.values.has_key("x"):
# jog x
x = request.values["x"]
printer.commands(["G91", "G1 X" + x + " F6000", "G90"])
if request.values.has_key("y"):
# jog y
y = request.values["y"]
printer.commands(["G91", "G1 Y" + y + " F6000", "G90"])
if request.values.has_key("z"):
# jog z
z = request.values["z"]
printer.commands(["G91", "G1 Z" + z + " F200", "G90"])
if request.values.has_key("homeXY"):
# home x/y
printer.command("G28 X0 Y0")
if request.values.has_key("homeZ"):
# home z
printer.command("G28 Z0")
return jsonify(SUCCESS)
@app.route(BASEURL + "control/speed", methods=["POST"])
def speed():
if not printer.isOperational():
return jsonify(SUCCESS)
for key in ["outerWall", "innerWall", "fill", "support"]:
if request.values.has_key(key):
value = int(request.values[key])
printer.setFeedrateModifier(key, value)
return jsonify(feedrate = printer.feedrateState())
#~~ GCODE file handling
@app.route(BASEURL + "gcodefiles", methods=["GET"])
def readGcodeFiles():
files = []
for osFile in os.listdir(UPLOAD_FOLDER):
if not fnmatch.fnmatch(osFile, "*.gcode"):
continue
files.append({
"name": osFile,
"size": sizeof_fmt(os.stat(os.path.join(UPLOAD_FOLDER, osFile)).st_size)
})
return jsonify(files=files)
@app.route(BASEURL + "gcodefiles/upload", methods=["POST"])
def uploadGcodeFile():
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"])
def loadGcodeFile():
filename = request.values["filename"]
printer.loadGcode(os.path.join(UPLOAD_FOLDER, filename))
return jsonify(SUCCESS)
@app.route(BASEURL + "gcodefiles/delete", methods=["POST"])
def deleteGcodeFile():
if request.values.has_key("filename"):
filename = request.values["filename"]
if allowed_file(filename):
secure = os.path.join(UPLOAD_FOLDER, secure_filename(filename))
if os.path.exists(secure):
os.remove(secure)
return readGcodeFiles()
#~~ settings
@app.route(BASEURL + "settings", methods=["GET"])
def getSettings():
s = settings()
return jsonify({
"serial_port": s.get("serial", "port"),
"serial_baudrate": s.get("serial", "baudrate")
})
@app.route(BASEURL + "settings", methods=["POST"])
def setSettings():
s = settings()
if request.values.has_key("serial_port"):
s.set("serial", "port", request.values["serial_port"])
if request.values.has_key("serial_baudrate"):
s.set("serial", "baudrate", request.values["serial_baudrate"])
s.save()
return getSettings()
#~~ helper functions
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"]:
if num < 1024.0:
return "%3.1f%s" % (num, x)
num /= 1024.0
return "%3.1f%s" % (num, "TB")
def allowed_file(filename):
return "." in filename and filename.rsplit(".", 1)[1] in ALLOWED_EXTENSIONS
#~~ startup code
def run(host = "0.0.0.0", port = 5000, debug = False):
app.debug = debug
app.run(host=host, port=port, use_reloader=False)
def main():
from optparse import OptionParser
defaultHost = settings().get("server", "host")
defaultPort = settings().get("server", "port")
parser = OptionParser(usage="usage: %prog [options]")
parser.add_option("-d", "--debug", action="store_true", dest="debug",
help="Enable debug mode")
parser.add_option("--host", action="store", type="string", default=defaultHost, dest="host",
help="Specify the host on which to bind the server, defaults to %s if not set" % (defaultHost))
parser.add_option("--port", action="store", type="int", default=defaultPort, dest="port",
help="Specify the port on which to bind the server, defaults to %s if not set" % (defaultPort))
(options, args) = parser.parse_args()
run(host=options.host, port=options.port, debug=options.debug)
if __name__ == "__main__":
main()