MrDraw/src/octoprint/server/api/printer.py
2015-03-26 12:04:05 +01:00

406 lines
11 KiB
Python

# coding=utf-8
from __future__ import absolute_import
__author__ = "Gina Häußge <osd@foosel.net>"
__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html'
__copyright__ = "Copyright (C) 2014 The OctoPrint Project - Released under terms of the AGPLv3 License"
from flask import request, jsonify, make_response, Response
from flask.exceptions import JSONBadRequest
import re
from octoprint.settings import settings, valid_boolean_trues
from octoprint.server import printer, NO_CONTENT
from octoprint.server.api import api
from octoprint.server.util.flask import restricted_access, get_json_command_from_request
import octoprint.util as util
from octoprint.printer import UnknownScript
#~~ Printer
@api.route("/printer", methods=["GET"])
def printerState():
if not printer.is_operational():
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.is_sd_ready()}})
# add state information
if not "state" in excludes:
state = printer.get_current_data()["state"]
result.update({"state": state})
return jsonify(result)
#~~ Tool
@api.route("/printer/tool", methods=["POST"])
@restricted_access
def printerToolCommand():
if not printer.is_operational():
return make_response("Printer is not operational", 409)
valid_commands = {
"select": ["tool"],
"target": ["targets"],
"offset": ["offsets"],
"extrude": ["amount"],
"flowrate": ["factor"]
}
command, data, response = get_json_command_from_request(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.change_tool(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.set_temperature(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.set_temperature_offset(validated_values)
##~~ extrusion
elif command == "extrude":
if printer.is_printing():
# 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)
elif command == "flowrate":
factor = data["factor"]
if not isinstance(factor, (int, long, float)):
return make_response("Not a number for flow rate: %r" % factor, 400)
try:
printer.flow_rate(factor)
except ValueError as e:
return make_response("Invalid value for flow rate: %s" % str(e), 400)
return NO_CONTENT
@api.route("/printer/tool", methods=["GET"])
def printerToolState():
if not printer.is_operational():
return make_response("Printer is not operational", 409)
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.is_operational():
return make_response("Printer is not operational", 409)
valid_commands = {
"target": ["target"],
"offset": ["offset"]
}
command, data, response = get_json_command_from_request(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.set_temperature("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.set_temperature_offset({"bed": offset})
return NO_CONTENT
@api.route("/printer/bed", methods=["GET"])
def printerBedState():
if not printer.is_operational():
return make_response("Printer is not operational", 409)
def deleteTools(x):
data = dict(x)
for k in data.keys():
if k.startswith("tool"):
del data[k]
return data
data = _getTemperatureData(deleteTools)
if isinstance(data, Response):
return data
else:
return jsonify(data)
##~~ Print head
@api.route("/printer/printhead", methods=["POST"])
@restricted_access
def printerPrintheadCommand():
valid_commands = {
"jog": [],
"home": ["axes"],
"feedrate": ["factor"]
}
command, data, response = get_json_command_from_request(request, valid_commands)
if response is not None:
return response
if not printer.is_operational() or (printer.is_printing() and command != "feedrate"):
# 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_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)
elif command == "feedrate":
factor = data["factor"]
if not isinstance(factor, (int, long, float)):
return make_response("Not a number for feed rate: %r" % factor, 400)
try:
printer.feed_rate(factor)
except ValueError as e:
return make_response("Invalid value for feed rate: %s" % str(e), 400)
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.is_operational() or printer.is_printing() or printer.is_paused():
return make_response("Printer is not operational or currently busy", 409)
valid_commands = {
"init": [],
"refresh": [],
"release": []
}
command, data, response = get_json_command_from_request(request, valid_commands)
if response is not None:
return response
if command == "init":
printer.init_sd_card()
elif command == "refresh":
printer.refresh_sd_files()
elif command == "release":
printer.release_sd_card()
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.is_sd_ready())
##~~ Commands
@api.route("/printer/command", methods=["POST"])
@restricted_access
def printerCommand():
if not printer.is_operational():
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)
try:
data = request.json
except JSONBadRequest:
return make_response("Malformed JSON body in request", 400)
if "command" in data and "commands" in data:
return make_response("'command' and 'commands' are mutually exclusive", 400)
elif ("command" in data or "commands" in data) and "script" in data:
return make_response("'command'/'commands' and 'script' are mutually exclusive", 400)
elif not ("command" in data or "commands" in data or "script" in data):
return make_response("Need one of 'command', 'commands' or 'script'", 400)
parameters = dict()
if "parameters" in data:
parameters = data["parameters"]
if "command" in data or "commands" in data:
if "command" in data:
commands = [data["command"]]
else:
if not isinstance(data["commands"], (list, tuple)):
return make_response("'commands' needs to be a list", 400)
commands = data["commands"]
commandsToSend = []
for command in commands:
commandToSend = command
if len(parameters) > 0:
commandToSend = command % parameters
commandsToSend.append(commandToSend)
printer.commands(commandsToSend)
elif "script" in data:
script_name = data["script"]
context = dict(parameters=parameters)
if "context" in data:
context["context"] = data["context"]
try:
printer.script(script_name, context=context)
except UnknownScript:
return make_response("Unknown script: {script_name}".format(**locals()), 404)
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.is_operational():
return make_response("Printer is not operational", 409)
tempData = printer.get_current_temperatures()
if "history" in request.values.keys() and request.values["history"] in valid_boolean_trues:
tempHistory = printer.get_temperature_history()
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)