348 lines
9.1 KiB
Python
348 lines
9.1 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 request, jsonify, make_response
|
|
import re
|
|
|
|
from octoprint.settings import settings, valid_boolean_trues
|
|
from octoprint.server import printer, restricted_access, NO_CONTENT
|
|
from octoprint.server.api import api
|
|
import octoprint.util as util
|
|
|
|
#~~ Printer
|
|
|
|
|
|
@api.route("/printer", methods=["GET"])
|
|
def printerState():
|
|
if not printer.isOperational():
|
|
return make_response("Printer is not operational", 409)
|
|
|
|
# process excludes
|
|
excludes = []
|
|
if "exclude" in request.values:
|
|
excludeStr = request.values["exclude"]
|
|
if len(excludeStr.strip()) > 0:
|
|
excludes = filter(lambda x: x in ["temperature", "sd", "state"], map(lambda x: x.strip(), excludeStr.split(",")))
|
|
|
|
result = {}
|
|
|
|
# add temperature information
|
|
if not "temperature" in excludes:
|
|
result.update({"temperature": _getTemperatureData(lambda x: x)})
|
|
|
|
# add sd information
|
|
if not "sd" in excludes and settings().getBoolean(["feature", "sdSupport"]):
|
|
result.update({"sd": {"ready": printer.isSdReady()}})
|
|
|
|
# add state information
|
|
if not "state" in excludes:
|
|
state = printer.getCurrentData()["state"]
|
|
result.update({"state": state})
|
|
|
|
return jsonify(result)
|
|
|
|
|
|
#~~ Tool
|
|
|
|
|
|
@api.route("/printer/tool", methods=["POST"])
|
|
@restricted_access
|
|
def printerToolCommand():
|
|
if not printer.isOperational():
|
|
return make_response("Printer is not operational", 409)
|
|
|
|
valid_commands = {
|
|
"select": ["tool"],
|
|
"target": ["targets"],
|
|
"offset": ["offsets"],
|
|
"extrude": ["amount"]
|
|
}
|
|
command, data, response = util.getJsonCommandFromRequest(request, valid_commands)
|
|
if response is not None:
|
|
return response
|
|
|
|
validation_regex = re.compile("tool\d+")
|
|
|
|
##~~ tool selection
|
|
if command == "select":
|
|
tool = data["tool"]
|
|
if re.match(validation_regex, tool) is None:
|
|
return make_response("Invalid tool: %s" % tool, 400)
|
|
if not tool.startswith("tool"):
|
|
return make_response("Invalid tool for selection: %s" % tool, 400)
|
|
|
|
printer.changeTool(tool)
|
|
|
|
##~~ temperature
|
|
elif command == "target":
|
|
targets = data["targets"]
|
|
|
|
# make sure the targets are valid and the values are numbers
|
|
validated_values = {}
|
|
for tool, value in targets.iteritems():
|
|
if re.match(validation_regex, tool) is None:
|
|
return make_response("Invalid target for setting temperature: %s" % tool, 400)
|
|
if not isinstance(value, (int, long, float)):
|
|
return make_response("Not a number for %s: %r" % (tool, value), 400)
|
|
validated_values[tool] = value
|
|
|
|
# perform the actual temperature commands
|
|
for tool in validated_values.keys():
|
|
printer.setTemperature(tool, validated_values[tool])
|
|
|
|
##~~ temperature offset
|
|
elif command == "offset":
|
|
offsets = data["offsets"]
|
|
|
|
# make sure the targets are valid, the values are numbers and in the range [-50, 50]
|
|
validated_values = {}
|
|
for tool, value in offsets.iteritems():
|
|
if re.match(validation_regex, tool) is None:
|
|
return make_response("Invalid target for setting temperature: %s" % tool, 400)
|
|
if not isinstance(value, (int, long, float)):
|
|
return make_response("Not a number for %s: %r" % (tool, value), 400)
|
|
if not -50 <= value <= 50:
|
|
return make_response("Offset %s not in range [-50, 50]: %f" % (tool, value), 400)
|
|
validated_values[tool] = value
|
|
|
|
# set the offsets
|
|
printer.setTemperatureOffset(validated_values)
|
|
|
|
##~~ extrusion
|
|
elif command == "extrude":
|
|
if printer.isPrinting():
|
|
# do not extrude when a print job is running
|
|
return make_response("Printer is currently printing", 409)
|
|
|
|
amount = data["amount"]
|
|
if not isinstance(amount, (int, long, float)):
|
|
return make_response("Not a number for extrusion amount: %r" % amount, 400)
|
|
printer.extrude(amount)
|
|
|
|
return NO_CONTENT
|
|
|
|
|
|
@api.route("/printer/tool", methods=["GET"])
|
|
def printerToolState():
|
|
def deleteBed(x):
|
|
data = dict(x)
|
|
|
|
if "bed" in data.keys():
|
|
del data["bed"]
|
|
return data
|
|
|
|
return jsonify(_getTemperatureData(deleteBed))
|
|
|
|
|
|
##~~ Heated bed
|
|
|
|
|
|
@api.route("/printer/bed", methods=["POST"])
|
|
@restricted_access
|
|
def printerBedCommand():
|
|
if not printer.isOperational():
|
|
return make_response("Printer is not operational", 409)
|
|
|
|
valid_commands = {
|
|
"target": ["target"],
|
|
"offset": ["offset"]
|
|
}
|
|
command, data, response = util.getJsonCommandFromRequest(request, valid_commands)
|
|
if response is not None:
|
|
return response
|
|
|
|
##~~ temperature
|
|
if command == "target":
|
|
target = data["target"]
|
|
|
|
# make sure the target is a number
|
|
if not isinstance(target, (int, long, float)):
|
|
return make_response("Not a number: %r" % target, 400)
|
|
|
|
# perform the actual temperature command
|
|
printer.setTemperature("bed", target)
|
|
|
|
##~~ temperature offset
|
|
elif command == "offset":
|
|
offset = data["offset"]
|
|
|
|
# make sure the offset is valid
|
|
if not isinstance(offset, (int, long, float)):
|
|
return make_response("Not a number: %r" % offset, 400)
|
|
if not -50 <= offset <= 50:
|
|
return make_response("Offset not in range [-50, 50]: %f" % offset, 400)
|
|
|
|
# set the offsets
|
|
printer.setTemperatureOffset({"bed": offset})
|
|
|
|
return NO_CONTENT
|
|
|
|
|
|
@api.route("/printer/bed", methods=["GET"])
|
|
def printerBedState():
|
|
def deleteTools(x):
|
|
data = dict(x)
|
|
|
|
for k in data.keys():
|
|
if k.startswith("tool"):
|
|
del data[k]
|
|
return data
|
|
|
|
return jsonify(_getTemperatureData(deleteTools))
|
|
|
|
|
|
##~~ Print head
|
|
|
|
|
|
@api.route("/printer/printhead", methods=["POST"])
|
|
@restricted_access
|
|
def printerPrintheadCommand():
|
|
if not printer.isOperational() or printer.isPrinting():
|
|
# do not jog when a print job is running or we don't have a connection
|
|
return make_response("Printer is not operational or currently printing", 409)
|
|
|
|
valid_commands = {
|
|
"jog": [],
|
|
"home": ["axes"]
|
|
}
|
|
command, data, response = util.getJsonCommandFromRequest(request, valid_commands)
|
|
if response is not None:
|
|
return response
|
|
|
|
valid_axes = ["x", "y", "z"]
|
|
##~~ jog command
|
|
if command == "jog":
|
|
# validate all jog instructions, make sure that the values are numbers
|
|
validated_values = {}
|
|
for axis in valid_axes:
|
|
if axis in data:
|
|
value = data[axis]
|
|
if not isinstance(value, (int, long, float)):
|
|
return make_response("Not a number for axis %s: %r" % (axis, value), 400)
|
|
validated_values[axis] = value
|
|
|
|
# execute the jog commands
|
|
for axis, value in validated_values.iteritems():
|
|
printer.jog(axis, value)
|
|
|
|
##~~ home command
|
|
elif command == "home":
|
|
validated_values = []
|
|
axes = data["axes"]
|
|
for axis in axes:
|
|
if not axis in valid_axes:
|
|
return make_response("Invalid axis: %s" % axis, 400)
|
|
validated_values.append(axis)
|
|
|
|
# execute the home command
|
|
printer.home(validated_values)
|
|
|
|
return NO_CONTENT
|
|
|
|
|
|
##~~ SD Card
|
|
|
|
|
|
@api.route("/printer/sd", methods=["POST"])
|
|
@restricted_access
|
|
def printerSdCommand():
|
|
if not settings().getBoolean(["feature", "sdSupport"]):
|
|
return make_response("SD support is disabled", 404)
|
|
|
|
if not printer.isOperational() or printer.isPrinting() or printer.isPaused():
|
|
return make_response("Printer is not operational or currently busy", 409)
|
|
|
|
valid_commands = {
|
|
"init": [],
|
|
"refresh": [],
|
|
"release": []
|
|
}
|
|
command, data, response = util.getJsonCommandFromRequest(request, valid_commands)
|
|
if response is not None:
|
|
return response
|
|
|
|
if command == "init":
|
|
printer.initSdCard()
|
|
elif command == "refresh":
|
|
printer.refreshSdFiles()
|
|
elif command == "release":
|
|
printer.releaseSdCard()
|
|
|
|
return NO_CONTENT
|
|
|
|
|
|
@api.route("/printer/sd", methods=["GET"])
|
|
def printerSdState():
|
|
if not settings().getBoolean(["feature", "sdSupport"]):
|
|
return make_response("SD support is disabled", 404)
|
|
|
|
return jsonify(ready=printer.isSdReady())
|
|
|
|
|
|
##~~ Commands
|
|
|
|
|
|
@api.route("/printer/command", methods=["POST"])
|
|
@restricted_access
|
|
def printerCommand():
|
|
# TODO: document me
|
|
if not printer.isOperational():
|
|
return make_response("Printer is not operational", 409)
|
|
|
|
if not "application/json" in request.headers["Content-Type"]:
|
|
return make_response("Expected content type JSON", 400)
|
|
|
|
data = request.json
|
|
|
|
parameters = {}
|
|
if "parameters" in data.keys():
|
|
parameters = data["parameters"]
|
|
|
|
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)
|
|
|
|
return NO_CONTENT
|
|
|
|
|
|
@api.route("/printer/command/custom", methods=["GET"])
|
|
def getCustomControls():
|
|
# TODO: document me
|
|
customControls = settings().get(["controls"])
|
|
return jsonify(controls=customControls)
|
|
|
|
|
|
def _getTemperatureData(filter):
|
|
if not printer.isOperational():
|
|
return make_response("Printer is not operational", 409)
|
|
|
|
tempData = printer.getCurrentTemperatures()
|
|
|
|
if "history" in request.values.keys() and request.values["history"] in valid_boolean_trues:
|
|
tempHistory = printer.getTemperatureHistory()
|
|
|
|
limit = 300
|
|
if "limit" in request.values.keys() and unicode(request.values["limit"]).isnumeric():
|
|
limit = int(request.values["limit"])
|
|
|
|
history = list(tempHistory)
|
|
limit = min(limit, len(history))
|
|
|
|
tempData.update({
|
|
"history": map(lambda x: filter(x), history[-limit:])
|
|
})
|
|
|
|
return filter(tempData)
|
|
|