406 lines
11 KiB
Python
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)
|
|
|