2012-12-25 10:55:00 +00:00
|
|
|
# coding=utf-8
|
2012-12-28 19:37:40 +00:00
|
|
|
__author__ = "Gina Häußge <osd@foosel.net>"
|
2012-12-31 12:18:54 +00:00
|
|
|
__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html'
|
2012-12-25 10:55:00 +00:00
|
|
|
|
2013-01-04 12:11:00 +00:00
|
|
|
from flask import Flask, request, render_template, jsonify, send_from_directory, abort, url_for
|
2013-01-30 19:56:17 +00:00
|
|
|
from werkzeug.utils import secure_filename
|
2013-01-06 15:51:04 +00:00
|
|
|
import tornadio2
|
2012-12-25 10:55:00 +00:00
|
|
|
|
2012-12-25 19:49:10 +00:00
|
|
|
import os
|
2013-01-11 23:00:58 +00:00
|
|
|
import threading
|
2013-02-03 20:14:22 +00:00
|
|
|
import logging, logging.config
|
2013-03-10 16:04:05 +00:00
|
|
|
import subprocess
|
2013-01-06 15:51:04 +00:00
|
|
|
|
2013-02-03 20:14:22 +00:00
|
|
|
from octoprint.printer import Printer, getConnectionOptions
|
2013-04-07 20:06:17 +00:00
|
|
|
from octoprint.settings import settings, valid_boolean_trues
|
2013-01-18 22:23:50 +00:00
|
|
|
import octoprint.timelapse as timelapse
|
2013-01-30 19:56:17 +00:00
|
|
|
import octoprint.gcodefiles as gcodefiles
|
|
|
|
import octoprint.util as util
|
2012-12-25 19:49:10 +00:00
|
|
|
|
2013-01-04 17:38:50 +00:00
|
|
|
SUCCESS = {}
|
2013-03-10 13:14:37 +00:00
|
|
|
BASEURL = "/ajax/"
|
2013-01-18 22:23:50 +00:00
|
|
|
app = Flask("octoprint")
|
2013-03-10 13:40:53 +00:00
|
|
|
# Only instantiated by the Server().run() method
|
|
|
|
# In order that threads don't start too early when running as a Daemon
|
2013-03-10 13:14:37 +00:00
|
|
|
printer = None
|
|
|
|
gcodeManager = None
|
2012-12-25 10:55:00 +00:00
|
|
|
|
2012-12-25 19:49:10 +00:00
|
|
|
#~~ Printer state
|
2012-12-25 10:55:00 +00:00
|
|
|
|
2013-02-03 20:14:22 +00:00
|
|
|
class PrinterStateConnection(tornadio2.SocketConnection):
|
2013-01-11 23:00:58 +00:00
|
|
|
def __init__(self, session, endpoint=None):
|
|
|
|
tornadio2.SocketConnection.__init__(self, session, endpoint)
|
|
|
|
|
2013-02-03 20:14:22 +00:00
|
|
|
self._logger = logging.getLogger(__name__)
|
|
|
|
|
2013-01-11 23:00:58 +00:00
|
|
|
self._temperatureBacklog = []
|
|
|
|
self._temperatureBacklogMutex = threading.Lock()
|
|
|
|
self._logBacklog = []
|
|
|
|
self._logBacklogMutex = threading.Lock()
|
|
|
|
self._messageBacklog = []
|
|
|
|
self._messageBacklogMutex = threading.Lock()
|
|
|
|
|
2013-01-06 15:51:04 +00:00
|
|
|
def on_open(self, info):
|
2013-02-03 20:14:22 +00:00
|
|
|
self._logger.info("New connection from client")
|
2013-03-10 13:14:37 +00:00
|
|
|
# Use of global here is smelly
|
2013-01-06 15:51:04 +00:00
|
|
|
printer.registerCallback(self)
|
2013-02-03 20:14:22 +00:00
|
|
|
gcodeManager.registerCallback(self)
|
2013-01-06 15:51:04 +00:00
|
|
|
|
|
|
|
def on_close(self):
|
2013-02-03 20:14:22 +00:00
|
|
|
self._logger.info("Closed client connection")
|
2013-03-10 13:14:37 +00:00
|
|
|
# Use of global here is smelly
|
2013-01-06 15:51:04 +00:00
|
|
|
printer.unregisterCallback(self)
|
2013-02-03 20:14:22 +00:00
|
|
|
gcodeManager.unregisterCallback(self)
|
2013-01-06 15:51:04 +00:00
|
|
|
|
2013-01-06 21:10:59 +00:00
|
|
|
def on_message(self, message):
|
|
|
|
pass
|
|
|
|
|
2013-01-10 22:40:00 +00:00
|
|
|
def sendCurrentData(self, data):
|
2013-01-11 23:00:58 +00:00
|
|
|
# add current temperature, log and message backlogs to sent data
|
|
|
|
with self._temperatureBacklogMutex:
|
|
|
|
temperatures = self._temperatureBacklog
|
|
|
|
self._temperatureBacklog = []
|
|
|
|
|
|
|
|
with self._logBacklogMutex:
|
|
|
|
logs = self._logBacklog
|
|
|
|
self._logBacklog = []
|
|
|
|
|
|
|
|
with self._messageBacklogMutex:
|
|
|
|
messages = self._messageBacklog
|
|
|
|
self._messageBacklog = []
|
|
|
|
|
|
|
|
data.update({
|
|
|
|
"temperatures": temperatures,
|
|
|
|
"logs": logs,
|
|
|
|
"messages": messages
|
|
|
|
})
|
2013-01-10 22:40:00 +00:00
|
|
|
self.emit("current", data)
|
|
|
|
|
|
|
|
def sendHistoryData(self, data):
|
|
|
|
self.emit("history", data)
|
2013-01-06 20:19:39 +00:00
|
|
|
|
2013-02-03 20:14:22 +00:00
|
|
|
def sendUpdateTrigger(self, type):
|
|
|
|
self.emit("updateTrigger", type)
|
|
|
|
|
2013-01-11 23:00:58 +00:00
|
|
|
def addLog(self, data):
|
|
|
|
with self._logBacklogMutex:
|
|
|
|
self._logBacklog.append(data)
|
|
|
|
|
|
|
|
def addMessage(self, data):
|
|
|
|
with self._messageBacklogMutex:
|
|
|
|
self._messageBacklog.append(data)
|
|
|
|
|
|
|
|
def addTemperature(self, data):
|
|
|
|
with self._temperatureBacklogMutex:
|
|
|
|
self._temperatureBacklog.append(data)
|
|
|
|
|
2013-03-10 13:14:37 +00:00
|
|
|
# Did attempt to make webserver an encapsulated class but ended up with __call__ failures
|
|
|
|
|
|
|
|
@app.route("/")
|
|
|
|
def index():
|
|
|
|
return render_template(
|
|
|
|
"index.html",
|
|
|
|
webcamStream=settings().get(["webcam", "stream"]),
|
|
|
|
enableTimelapse=(settings().get(["webcam", "snapshot"]) is not None and settings().get(["webcam", "ffmpeg"]) is not None),
|
2013-03-10 18:11:16 +00:00
|
|
|
enableGCodeVisualizer=settings().get(["feature", "gCodeVisualizer"]),
|
|
|
|
enableSystemMenu=settings().get(["system"]) is not None and settings().get(["system", "actions"]) is not None and len(settings().get(["system", "actions"])) > 0
|
2013-03-10 13:14:37 +00:00
|
|
|
)
|
|
|
|
|
2012-12-25 19:49:10 +00:00
|
|
|
#~~ Printer control
|
|
|
|
|
2012-12-28 19:37:40 +00:00
|
|
|
@app.route(BASEURL + "control/connectionOptions", methods=["GET"])
|
|
|
|
def connectionOptions():
|
|
|
|
return jsonify(getConnectionOptions())
|
2012-12-25 10:55:00 +00:00
|
|
|
|
2012-12-28 19:37:40 +00:00
|
|
|
@app.route(BASEURL + "control/connect", methods=["POST"])
|
|
|
|
def connect():
|
|
|
|
port = None
|
|
|
|
baudrate = None
|
2013-02-16 19:28:09 +00:00
|
|
|
if "port" in request.values.keys():
|
2012-12-28 19:37:40 +00:00
|
|
|
port = request.values["port"]
|
2013-02-16 19:28:09 +00:00
|
|
|
if "baudrate" in request.values.keys():
|
2012-12-28 19:37:40 +00:00
|
|
|
baudrate = request.values["baudrate"]
|
2013-02-16 19:28:09 +00:00
|
|
|
if "save" in request.values.keys():
|
|
|
|
settings().set(["serial", "port"], port)
|
|
|
|
settings().setInt(["serial", "baudrate"], baudrate)
|
2013-01-01 20:04:00 +00:00
|
|
|
settings().save()
|
2012-12-28 19:37:40 +00:00
|
|
|
printer.connect(port=port, baudrate=baudrate)
|
|
|
|
return jsonify(state="Connecting")
|
|
|
|
|
|
|
|
@app.route(BASEURL + "control/disconnect", methods=["POST"])
|
2012-12-25 19:49:10 +00:00
|
|
|
def disconnect():
|
|
|
|
printer.disconnect()
|
2012-12-28 19:37:40 +00:00
|
|
|
return jsonify(state="Offline")
|
2012-12-25 19:49:10 +00:00
|
|
|
|
2012-12-28 19:37:40 +00:00
|
|
|
@app.route(BASEURL + "control/command", methods=["POST"])
|
2012-12-25 19:49:10 +00:00
|
|
|
def printerCommand():
|
2013-02-23 17:08:59 +00:00
|
|
|
if "application/json" in request.headers["Content-Type"]:
|
|
|
|
data = request.json
|
2013-01-27 17:28:11 +00:00
|
|
|
|
2013-02-23 17:08:59 +00:00
|
|
|
parameters = {}
|
|
|
|
if "parameters" in data.keys(): parameters = data["parameters"]
|
2013-01-27 17:28:11 +00:00
|
|
|
|
2013-02-23 17:08:59 +00:00
|
|
|
commands = []
|
|
|
|
if "command" in data.keys(): commands = [data["command"]]
|
|
|
|
elif "commands" in data.keys(): commands = data["commands"]
|
|
|
|
|
|
|
|
commandsToSend = []
|
|
|
|
for command in commands:
|
|
|
|
commandToSend = command
|
|
|
|
if len(parameters) > 0:
|
|
|
|
commandToSend = command % parameters
|
|
|
|
commandsToSend.append(commandToSend)
|
|
|
|
|
|
|
|
printer.commands(commandsToSend)
|
2013-01-27 17:28:11 +00:00
|
|
|
|
2012-12-25 19:49:10 +00:00
|
|
|
return jsonify(SUCCESS)
|
|
|
|
|
2012-12-28 19:37:40 +00:00
|
|
|
@app.route(BASEURL + "control/print", methods=["POST"])
|
2012-12-25 10:55:00 +00:00
|
|
|
def printGcode():
|
|
|
|
printer.startPrint()
|
2012-12-25 19:49:10 +00:00
|
|
|
return jsonify(SUCCESS)
|
2012-12-25 10:55:00 +00:00
|
|
|
|
2012-12-28 19:37:40 +00:00
|
|
|
@app.route(BASEURL + "control/pause", methods=["POST"])
|
2012-12-25 10:55:00 +00:00
|
|
|
def pausePrint():
|
|
|
|
printer.togglePausePrint()
|
2012-12-25 19:49:10 +00:00
|
|
|
return jsonify(SUCCESS)
|
2012-12-25 10:55:00 +00:00
|
|
|
|
2012-12-28 19:37:40 +00:00
|
|
|
@app.route(BASEURL + "control/cancel", methods=["POST"])
|
2012-12-25 10:55:00 +00:00
|
|
|
def cancelPrint():
|
|
|
|
printer.cancelPrint()
|
2012-12-25 19:49:10 +00:00
|
|
|
return jsonify(SUCCESS)
|
|
|
|
|
2012-12-28 19:37:40 +00:00
|
|
|
@app.route(BASEURL + "control/temperature", methods=["POST"])
|
2012-12-26 14:03:34 +00:00
|
|
|
def setTargetTemperature():
|
|
|
|
if not printer.isOperational():
|
|
|
|
return jsonify(SUCCESS)
|
|
|
|
|
2013-02-23 01:09:30 +00:00
|
|
|
elif request.values.has_key("temp"):
|
2013-03-10 13:14:37 +00:00
|
|
|
# set target temperature
|
2013-01-30 19:56:17 +00:00
|
|
|
temp = request.values["temp"]
|
2012-12-26 14:03:34 +00:00
|
|
|
printer.command("M104 S" + temp)
|
|
|
|
|
2013-02-23 01:09:30 +00:00
|
|
|
elif request.values.has_key("bedTemp"):
|
2012-12-26 14:03:34 +00:00
|
|
|
# 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():
|
2013-02-16 19:28:09 +00:00
|
|
|
# do not jog when a print job is running or we don't have a connection
|
2012-12-26 14:03:34 +00:00
|
|
|
return jsonify(SUCCESS)
|
|
|
|
|
2013-02-16 19:28:09 +00:00
|
|
|
(movementSpeedX, movementSpeedY, movementSpeedZ, movementSpeedE) = settings().get(["printerParameters", "movementSpeed", ["x", "y", "z", "e"]])
|
2013-01-30 19:56:17 +00:00
|
|
|
if "x" in request.values.keys():
|
2012-12-26 14:03:34 +00:00
|
|
|
# jog x
|
|
|
|
x = request.values["x"]
|
2013-02-16 19:28:09 +00:00
|
|
|
printer.commands(["G91", "G1 X%s F%d" % (x, movementSpeedX), "G90"])
|
2013-01-30 19:56:17 +00:00
|
|
|
if "y" in request.values.keys():
|
2012-12-26 14:03:34 +00:00
|
|
|
# jog y
|
|
|
|
y = request.values["y"]
|
2013-02-16 19:28:09 +00:00
|
|
|
printer.commands(["G91", "G1 Y%s F%d" % (y, movementSpeedY), "G90"])
|
2013-01-30 19:56:17 +00:00
|
|
|
if "z" in request.values.keys():
|
2012-12-26 14:03:34 +00:00
|
|
|
# jog z
|
|
|
|
z = request.values["z"]
|
2013-02-16 19:28:09 +00:00
|
|
|
printer.commands(["G91", "G1 Z%s F%d" % (z, movementSpeedZ), "G90"])
|
2013-01-30 19:56:17 +00:00
|
|
|
if "homeXY" in request.values.keys():
|
2012-12-26 14:03:34 +00:00
|
|
|
# home x/y
|
|
|
|
printer.command("G28 X0 Y0")
|
2013-01-30 19:56:17 +00:00
|
|
|
if "homeZ" in request.values.keys():
|
2012-12-26 14:03:34 +00:00
|
|
|
# home z
|
|
|
|
printer.command("G28 Z0")
|
2013-02-09 23:40:06 +00:00
|
|
|
if "extrude" in request.values.keys():
|
|
|
|
# extrude/retract
|
|
|
|
length = request.values["extrude"]
|
2013-02-16 19:28:09 +00:00
|
|
|
printer.commands(["G91", "G1 E%s F%d" % (length, movementSpeedE), "G90"])
|
2012-12-26 14:03:34 +00:00
|
|
|
|
|
|
|
return jsonify(SUCCESS)
|
|
|
|
|
2013-01-27 17:28:11 +00:00
|
|
|
@app.route(BASEURL + "control/speed", methods=["GET"])
|
|
|
|
def getSpeedValues():
|
2013-01-30 19:56:17 +00:00
|
|
|
return jsonify(feedrate=printer.feedrateState())
|
2013-01-27 17:28:11 +00:00
|
|
|
|
2012-12-29 14:41:23 +00:00
|
|
|
@app.route(BASEURL + "control/speed", methods=["POST"])
|
|
|
|
def speed():
|
|
|
|
if not printer.isOperational():
|
|
|
|
return jsonify(SUCCESS)
|
|
|
|
|
|
|
|
for key in ["outerWall", "innerWall", "fill", "support"]:
|
2013-01-30 19:56:17 +00:00
|
|
|
if key in request.values.keys():
|
2012-12-29 14:41:23 +00:00
|
|
|
value = int(request.values[key])
|
|
|
|
printer.setFeedrateModifier(key, value)
|
|
|
|
|
2013-01-27 17:28:11 +00:00
|
|
|
return getSpeedValues()
|
2012-12-29 14:41:23 +00:00
|
|
|
|
2013-01-27 10:12:28 +00:00
|
|
|
@app.route(BASEURL + "control/custom", methods=["GET"])
|
|
|
|
def getCustomControls():
|
2013-02-16 19:28:09 +00:00
|
|
|
customControls = settings().get(["controls"])
|
2013-01-30 19:56:17 +00:00
|
|
|
return jsonify(controls=customControls)
|
2013-01-27 10:12:28 +00:00
|
|
|
|
2012-12-25 19:49:10 +00:00
|
|
|
#~~ GCODE file handling
|
|
|
|
|
2012-12-28 19:37:40 +00:00
|
|
|
@app.route(BASEURL + "gcodefiles", methods=["GET"])
|
2012-12-25 19:49:10 +00:00
|
|
|
def readGcodeFiles():
|
2013-01-30 19:56:17 +00:00
|
|
|
return jsonify(files=gcodeManager.getAllFileData())
|
2012-12-25 19:49:10 +00:00
|
|
|
|
2013-02-03 21:01:11 +00:00
|
|
|
@app.route(BASEURL + "gcodefiles/<path:filename>", methods=["GET"])
|
2013-02-01 22:13:44 +00:00
|
|
|
def readGcodeFile(filename):
|
2013-03-11 20:00:43 +00:00
|
|
|
return send_from_directory(settings().getBaseFolder("uploads"), filename, as_attachment=True)
|
2013-02-01 22:13:44 +00:00
|
|
|
|
2012-12-28 19:37:40 +00:00
|
|
|
@app.route(BASEURL + "gcodefiles/upload", methods=["POST"])
|
2012-12-25 19:49:10 +00:00
|
|
|
def uploadGcodeFile():
|
2013-02-04 21:24:32 +00:00
|
|
|
filename = None
|
2013-01-30 19:56:17 +00:00
|
|
|
if "gcode_file" in request.files.keys():
|
2013-01-04 12:11:00 +00:00
|
|
|
file = request.files["gcode_file"]
|
2013-02-04 21:24:32 +00:00
|
|
|
filename = gcodeManager.addFile(file)
|
|
|
|
return jsonify(files=gcodeManager.getAllFileData(), filename=filename)
|
2012-12-25 19:49:10 +00:00
|
|
|
|
2012-12-28 19:37:40 +00:00
|
|
|
@app.route(BASEURL + "gcodefiles/load", methods=["POST"])
|
2012-12-25 19:49:10 +00:00
|
|
|
def loadGcodeFile():
|
2013-01-30 19:56:17 +00:00
|
|
|
if "filename" in request.values.keys():
|
2013-03-30 17:21:49 +00:00
|
|
|
printAfterLoading = False
|
2013-04-07 20:06:17 +00:00
|
|
|
if "print" in request.values.keys() and request.values["print"] in valid_boolean_trues:
|
2013-03-30 17:21:49 +00:00
|
|
|
printAfterLoading = True
|
2013-01-30 19:56:17 +00:00
|
|
|
filename = gcodeManager.getAbsolutePath(request.values["filename"])
|
|
|
|
if filename is not None:
|
2013-03-30 17:21:49 +00:00
|
|
|
printer.loadGcode(filename, printAfterLoading)
|
2013-03-26 05:09:36 +00:00
|
|
|
return jsonify(SUCCESS)
|
|
|
|
|
2012-12-28 19:37:40 +00:00
|
|
|
@app.route(BASEURL + "gcodefiles/delete", methods=["POST"])
|
2012-12-25 19:49:10 +00:00
|
|
|
def deleteGcodeFile():
|
2013-01-30 19:56:17 +00:00
|
|
|
if "filename" in request.values.keys():
|
2012-12-26 23:04:12 +00:00
|
|
|
filename = request.values["filename"]
|
2013-01-30 19:56:17 +00:00
|
|
|
gcodeManager.removeFile(filename)
|
2012-12-25 19:49:10 +00:00
|
|
|
return readGcodeFiles()
|
|
|
|
|
2013-01-04 12:11:00 +00:00
|
|
|
#~~ timelapse handling
|
2013-01-03 14:25:20 +00:00
|
|
|
|
|
|
|
@app.route(BASEURL + "timelapse", methods=["GET"])
|
2013-01-04 12:11:00 +00:00
|
|
|
def getTimelapseData():
|
|
|
|
lapse = printer.getTimelapse()
|
2013-01-03 14:25:20 +00:00
|
|
|
|
|
|
|
type = "off"
|
|
|
|
additionalConfig = {}
|
2013-01-04 12:11:00 +00:00
|
|
|
if lapse is not None and isinstance(lapse, timelapse.ZTimelapse):
|
2013-01-03 14:25:20 +00:00
|
|
|
type = "zchange"
|
2013-01-04 12:11:00 +00:00
|
|
|
elif lapse is not None and isinstance(lapse, timelapse.TimedTimelapse):
|
2013-01-03 14:25:20 +00:00
|
|
|
type = "timed"
|
|
|
|
additionalConfig = {
|
2013-03-11 08:39:54 +00:00
|
|
|
"interval": lapse.interval()
|
2013-01-03 14:25:20 +00:00
|
|
|
}
|
|
|
|
|
2013-01-04 12:11:00 +00:00
|
|
|
files = timelapse.getFinishedTimelapses()
|
|
|
|
for file in files:
|
|
|
|
file["url"] = url_for("downloadTimelapse", filename=file["name"])
|
|
|
|
|
2013-01-03 14:25:20 +00:00
|
|
|
return jsonify({
|
2013-01-04 12:11:00 +00:00
|
|
|
"type": type,
|
|
|
|
"config": additionalConfig,
|
|
|
|
"files": files
|
2013-01-03 14:25:20 +00:00
|
|
|
})
|
|
|
|
|
2013-01-04 12:11:00 +00:00
|
|
|
@app.route(BASEURL + "timelapse/<filename>", methods=["GET"])
|
|
|
|
def downloadTimelapse(filename):
|
2013-01-30 19:56:17 +00:00
|
|
|
if util.isAllowedFile(filename, set(["mpg"])):
|
2013-01-04 12:11:00 +00:00
|
|
|
return send_from_directory(settings().getBaseFolder("timelapse"), filename, as_attachment=True)
|
|
|
|
|
|
|
|
@app.route(BASEURL + "timelapse/<filename>", methods=["DELETE"])
|
|
|
|
def deleteTimelapse(filename):
|
2013-01-30 19:56:17 +00:00
|
|
|
if util.isAllowedFile(filename, set(["mpg"])):
|
2013-01-04 12:11:00 +00:00
|
|
|
secure = os.path.join(settings().getBaseFolder("timelapse"), secure_filename(filename))
|
|
|
|
if os.path.exists(secure):
|
|
|
|
os.remove(secure)
|
|
|
|
return getTimelapseData()
|
|
|
|
|
|
|
|
@app.route(BASEURL + "timelapse/config", methods=["POST"])
|
2013-01-03 14:25:20 +00:00
|
|
|
def setTimelapseConfig():
|
2013-01-04 12:11:00 +00:00
|
|
|
if request.values.has_key("type"):
|
|
|
|
type = request.values["type"]
|
|
|
|
lapse = None
|
|
|
|
if "zchange" == type:
|
|
|
|
lapse = timelapse.ZTimelapse()
|
|
|
|
elif "timed" == type:
|
|
|
|
interval = 10
|
|
|
|
if request.values.has_key("interval"):
|
|
|
|
try:
|
|
|
|
interval = int(request.values["interval"])
|
|
|
|
except ValueError:
|
|
|
|
pass
|
|
|
|
lapse = timelapse.TimedTimelapse(interval)
|
|
|
|
printer.setTimelapse(lapse)
|
|
|
|
|
|
|
|
return getTimelapseData()
|
2013-01-03 14:25:20 +00:00
|
|
|
|
2013-01-01 20:04:00 +00:00
|
|
|
#~~ settings
|
|
|
|
|
|
|
|
@app.route(BASEURL + "settings", methods=["GET"])
|
|
|
|
def getSettings():
|
|
|
|
s = settings()
|
2013-02-17 21:30:34 +00:00
|
|
|
|
|
|
|
[movementSpeedX, movementSpeedY, movementSpeedZ, movementSpeedE] = s.get(["printerParameters", "movementSpeed", ["x", "y", "z", "e"]])
|
|
|
|
|
2013-01-01 20:04:00 +00:00
|
|
|
return jsonify({
|
2013-03-02 11:42:35 +00:00
|
|
|
"appearance": {
|
|
|
|
"name": s.get(["appearance", "name"]),
|
|
|
|
"color": s.get(["appearance", "color"])
|
|
|
|
},
|
2013-02-17 21:30:34 +00:00
|
|
|
"printer": {
|
|
|
|
"movementSpeedX": movementSpeedX,
|
|
|
|
"movementSpeedY": movementSpeedY,
|
|
|
|
"movementSpeedZ": movementSpeedZ,
|
|
|
|
"movementSpeedE": movementSpeedE,
|
|
|
|
},
|
|
|
|
"webcam": {
|
|
|
|
"streamUrl": s.get(["webcam", "stream"]),
|
|
|
|
"snapshotUrl": s.get(["webcam", "snapshot"]),
|
|
|
|
"ffmpegPath": s.get(["webcam", "ffmpeg"]),
|
2013-03-08 23:23:52 +00:00
|
|
|
"bitrate": s.get(["webcam", "bitrate"]),
|
|
|
|
"watermark": s.getBoolean(["webcam", "watermark"])
|
2013-02-17 21:30:34 +00:00
|
|
|
},
|
|
|
|
"feature": {
|
|
|
|
"gcodeViewer": s.getBoolean(["feature", "gCodeVisualizer"]),
|
|
|
|
"waitForStart": s.getBoolean(["feature", "waitForStartOnConnect"])
|
|
|
|
},
|
|
|
|
"folder": {
|
|
|
|
"uploads": s.getBaseFolder("uploads"),
|
|
|
|
"timelapse": s.getBaseFolder("timelapse"),
|
|
|
|
"timelapseTmp": s.getBaseFolder("timelapse_tmp"),
|
|
|
|
"logs": s.getBaseFolder("logs")
|
2013-02-23 01:09:30 +00:00
|
|
|
},
|
|
|
|
"temperature": {
|
2013-02-23 17:47:01 +00:00
|
|
|
"profiles": s.get(["temperature", "profiles"])
|
2013-03-10 16:04:05 +00:00
|
|
|
},
|
|
|
|
"system": {
|
|
|
|
"actions": s.get(["system", "actions"])
|
2013-05-09 21:05:48 +00:00
|
|
|
},
|
|
|
|
"system_commands": {
|
|
|
|
"print_done": s.get(["system_commands", "print_done"]),
|
|
|
|
"cancelled": s.get(["system_commands", "cancelled"]),
|
|
|
|
"print_started": s.get(["system_commands", "print_started"]),
|
|
|
|
"z_change": s.get(["system_commands", "z_change"])
|
|
|
|
|
2013-02-23 17:47:01 +00:00
|
|
|
}
|
2013-05-09 21:05:48 +00:00
|
|
|
|
2013-01-01 20:04:00 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
@app.route(BASEURL + "settings", methods=["POST"])
|
|
|
|
def setSettings():
|
2013-02-17 21:30:34 +00:00
|
|
|
if "application/json" in request.headers["Content-Type"]:
|
|
|
|
data = request.json
|
|
|
|
s = settings()
|
|
|
|
|
2013-03-02 10:56:32 +00:00
|
|
|
if "appearance" in data.keys():
|
|
|
|
if "name" in data["appearance"].keys(): s.set(["appearance", "name"], data["appearance"]["name"])
|
|
|
|
if "color" in data["appearance"].keys(): s.set(["appearance", "color"], data["appearance"]["color"])
|
|
|
|
|
2013-02-17 21:30:34 +00:00
|
|
|
if "printer" in data.keys():
|
|
|
|
if "movementSpeedX" in data["printer"].keys(): s.setInt(["printerParameters", "movementSpeed", "x"], data["printer"]["movementSpeedX"])
|
|
|
|
if "movementSpeedY" in data["printer"].keys(): s.setInt(["printerParameters", "movementSpeed", "y"], data["printer"]["movementSpeedY"])
|
|
|
|
if "movementSpeedZ" in data["printer"].keys(): s.setInt(["printerParameters", "movementSpeed", "z"], data["printer"]["movementSpeedZ"])
|
|
|
|
if "movementSpeedE" in data["printer"].keys(): s.setInt(["printerParameters", "movementSpeed", "e"], data["printer"]["movementSpeedE"])
|
|
|
|
|
|
|
|
if "webcam" in data.keys():
|
|
|
|
if "streamUrl" in data["webcam"].keys(): s.set(["webcam", "stream"], data["webcam"]["streamUrl"])
|
2013-03-08 22:24:30 +00:00
|
|
|
if "snapshotUrl" in data["webcam"].keys(): s.set(["webcam", "snapshot"], data["webcam"]["snapshotUrl"])
|
2013-03-08 23:29:07 +00:00
|
|
|
if "ffmpegPath" in data["webcam"].keys(): s.set(["webcam", "ffmpeg"], data["webcam"]["ffmpegPath"])
|
2013-02-17 21:30:34 +00:00
|
|
|
if "bitrate" in data["webcam"].keys(): s.set(["webcam", "bitrate"], data["webcam"]["bitrate"])
|
2013-03-08 23:23:52 +00:00
|
|
|
if "watermark" in data["webcam"].keys(): s.setBoolean(["webcam", "watermark"], data["webcam"]["watermark"])
|
2013-02-17 21:30:34 +00:00
|
|
|
|
|
|
|
if "feature" in data.keys():
|
|
|
|
if "gcodeViewer" in data["feature"].keys(): s.setBoolean(["feature", "gCodeVisualizer"], data["feature"]["gcodeViewer"])
|
|
|
|
if "waitForStart" in data["feature"].keys(): s.setBoolean(["feature", "waitForStartOnConnect"], data["feature"]["waitForStart"])
|
|
|
|
|
|
|
|
if "folder" in data.keys():
|
|
|
|
if "uploads" in data["folder"].keys(): s.setBaseFolder("uploads", data["folder"]["uploads"])
|
|
|
|
if "timelapse" in data["folder"].keys(): s.setBaseFolder("timelapse", data["folder"]["timelapse"])
|
|
|
|
if "timelapseTmp" in data["folder"].keys(): s.setBaseFolder("timelapse_tmp", data["folder"]["timelapseTmp"])
|
|
|
|
if "logs" in data["folder"].keys(): s.setBaseFolder("logs", data["folder"]["logs"])
|
|
|
|
|
2013-02-23 01:09:30 +00:00
|
|
|
if "temperature" in data.keys():
|
2013-02-23 17:47:01 +00:00
|
|
|
if "profiles" in data["temperature"].keys(): s.set(["temperature", "profiles"], data["temperature"]["profiles"])
|
2013-02-23 01:09:30 +00:00
|
|
|
|
2013-03-10 16:04:05 +00:00
|
|
|
if "system" in data.keys():
|
|
|
|
if "actions" in data["system"].keys(): s.set(["system", "actions"], data["system"]["actions"])
|
|
|
|
|
2013-05-09 21:05:48 +00:00
|
|
|
if "system_commands" in data.keys():
|
|
|
|
if "z_change" in data["system_commands"].keys(): s.set(["system_commands", "z_change"], data["system_commands"]["z_change"])
|
|
|
|
if "print_started" in data["system_commands"].keys(): s.set(["system_commands", "print_started"], data["system_commands"]["print_started"])
|
|
|
|
if "cancelled" in data["system_commands"].keys(): s.set(["system_commands", "cancelled"], data["system_commands"]["cancelled"])
|
|
|
|
if "print_done" in data["system_commands"].keys(): s.set(["system_commands", "print_done"], data["system_commands"]["print_done"])
|
|
|
|
|
2013-02-17 21:30:34 +00:00
|
|
|
s.save()
|
2013-01-01 20:04:00 +00:00
|
|
|
|
|
|
|
return getSettings()
|
|
|
|
|
2013-03-10 16:04:05 +00:00
|
|
|
#~~ system control
|
|
|
|
|
|
|
|
@app.route(BASEURL + "system", methods=["POST"])
|
|
|
|
def performSystemAction():
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
if request.values.has_key("action"):
|
|
|
|
action = request.values["action"]
|
|
|
|
availableActions = settings().get(["system", "actions"])
|
|
|
|
for availableAction in availableActions:
|
|
|
|
if availableAction["action"] == action:
|
|
|
|
logger.info("Performing command: %s" % availableAction["command"])
|
|
|
|
try:
|
2013-03-10 16:17:50 +00:00
|
|
|
subprocess.check_output(availableAction["command"], shell=True)
|
2013-03-10 16:04:05 +00:00
|
|
|
except subprocess.CalledProcessError, e:
|
|
|
|
logger.warn("Command failed with return code %i: %s" % (e.returncode, e.message))
|
|
|
|
return app.make_response(("Command failed with return code %i: %s" % (e.returncode, e.message), 500, []))
|
|
|
|
except Exception, ex:
|
|
|
|
logger.exception("Command failed")
|
|
|
|
return app.make_response(("Command failed: %r" % ex, 500, []))
|
|
|
|
return jsonify(SUCCESS)
|
|
|
|
|
2013-01-01 20:04:00 +00:00
|
|
|
#~~ startup code
|
2013-03-10 13:14:37 +00:00
|
|
|
class Server():
|
2013-03-11 20:00:43 +00:00
|
|
|
def __init__(self, configfile=None, basedir=None, host="0.0.0.0", port=5000, debug=False):
|
|
|
|
self._configfile = configfile
|
|
|
|
self._basedir = basedir
|
|
|
|
self._host = host
|
|
|
|
self._port = port
|
|
|
|
self._debug = debug
|
|
|
|
|
|
|
|
def run(self):
|
2013-03-10 13:14:37 +00:00
|
|
|
# Global as I can't work out a way to get it into PrinterStateConnection
|
|
|
|
global printer
|
|
|
|
global gcodeManager
|
|
|
|
|
|
|
|
from tornado.wsgi import WSGIContainer
|
|
|
|
from tornado.httpserver import HTTPServer
|
|
|
|
from tornado.ioloop import IOLoop
|
|
|
|
from tornado.web import Application, FallbackHandler
|
|
|
|
|
2013-03-11 20:00:43 +00:00
|
|
|
# first initialize the settings singleton and make sure it uses given configfile and basedir if available
|
|
|
|
self._initSettings(self._configfile, self._basedir)
|
|
|
|
|
|
|
|
# then initialize logging
|
|
|
|
self._initLogging(self._debug)
|
|
|
|
|
2013-03-10 13:14:37 +00:00
|
|
|
gcodeManager = gcodefiles.GcodeManager()
|
|
|
|
printer = Printer(gcodeManager)
|
|
|
|
|
2013-03-11 20:00:43 +00:00
|
|
|
if self._host is None:
|
|
|
|
self._host = settings().get(["server", "host"])
|
|
|
|
if self._port is None:
|
|
|
|
self._port = settings().getInt(["server", "port"])
|
|
|
|
|
|
|
|
logging.getLogger(__name__).info("Listening on http://%s:%d" % (self._host, self._port))
|
|
|
|
app.debug = self._debug
|
2013-03-10 13:14:37 +00:00
|
|
|
|
|
|
|
self._router = tornadio2.TornadioRouter(PrinterStateConnection)
|
|
|
|
|
|
|
|
self._tornado_app = Application(self._router.urls + [
|
|
|
|
(".*", FallbackHandler, {"fallback": WSGIContainer(app)})
|
|
|
|
])
|
|
|
|
self._server = HTTPServer(self._tornado_app)
|
2013-03-11 20:00:43 +00:00
|
|
|
self._server.listen(self._port, address=self._host)
|
2013-03-10 13:14:37 +00:00
|
|
|
IOLoop.instance().start()
|
|
|
|
|
2013-03-11 20:00:43 +00:00
|
|
|
def _initSettings(self, configfile, basedir):
|
|
|
|
s = settings(init=True, basedir=basedir, configfile=configfile)
|
|
|
|
|
|
|
|
def _initLogging(self, debug):
|
2013-03-10 13:14:37 +00:00
|
|
|
self._config = {
|
|
|
|
"version": 1,
|
|
|
|
"formatters": {
|
|
|
|
"simple": {
|
|
|
|
"format": "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
|
|
|
}
|
2013-02-17 16:47:06 +00:00
|
|
|
},
|
2013-03-10 13:14:37 +00:00
|
|
|
"handlers": {
|
|
|
|
"console": {
|
|
|
|
"class": "logging.StreamHandler",
|
|
|
|
"level": "DEBUG",
|
|
|
|
"formatter": "simple",
|
|
|
|
"stream": "ext://sys.stdout"
|
|
|
|
},
|
|
|
|
"file": {
|
|
|
|
"class": "logging.handlers.TimedRotatingFileHandler",
|
|
|
|
"level": "DEBUG",
|
|
|
|
"formatter": "simple",
|
|
|
|
"when": "D",
|
|
|
|
"backupCount": "1",
|
|
|
|
"filename": os.path.join(settings().getBaseFolder("logs"), "octoprint.log")
|
2013-03-16 01:24:33 +00:00
|
|
|
},
|
|
|
|
"serialFile": {
|
|
|
|
"class": "logging.handlers.RotatingFileHandler",
|
|
|
|
"level": "DEBUG",
|
|
|
|
"formatter": "simple",
|
|
|
|
"maxBytes": 2 * 1024 * 1024, # let's limit the serial log to 2MB in size
|
|
|
|
"filename": os.path.join(settings().getBaseFolder("logs"), "serial.log")
|
2013-03-10 13:14:37 +00:00
|
|
|
}
|
|
|
|
},
|
|
|
|
"loggers": {
|
|
|
|
},
|
|
|
|
"root": {
|
|
|
|
"level": "INFO",
|
|
|
|
"handlers": ["console", "file"]
|
2013-02-03 20:14:22 +00:00
|
|
|
}
|
|
|
|
}
|
2013-03-17 21:32:35 +00:00
|
|
|
|
|
|
|
if debug:
|
|
|
|
self._config["loggers"]["SERIAL"] = {
|
|
|
|
"level": "DEBUG",
|
|
|
|
"handlers": ["serialFile"],
|
|
|
|
"propagate": False
|
|
|
|
}
|
|
|
|
|
2013-03-10 13:14:37 +00:00
|
|
|
logging.config.dictConfig(self._config)
|
2013-02-03 20:14:22 +00:00
|
|
|
|
2012-12-31 12:18:54 +00:00
|
|
|
if __name__ == "__main__":
|
2013-03-10 13:14:37 +00:00
|
|
|
octoprint = Server()
|
2013-03-11 20:00:43 +00:00
|
|
|
octoprint.run()
|