2013-10-28 21:08:36 +00:00
|
|
|
# coding=utf-8
|
|
|
|
|
__author__ = "Gina Häußge <osd@foosel.net>"
|
|
|
|
|
__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html'
|
|
|
|
|
|
2013-11-19 21:53:26 +00:00
|
|
|
from flask import request, jsonify, make_response
|
2013-10-28 21:08:36 +00:00
|
|
|
|
2013-12-02 16:04:00 +00:00
|
|
|
from octoprint.settings import settings
|
2013-10-28 21:08:36 +00:00
|
|
|
from octoprint.printer import getConnectionOptions
|
2013-12-21 13:46:20 +00:00
|
|
|
from octoprint.server import printer, restricted_access, NO_CONTENT
|
|
|
|
|
from octoprint.server.api import api
|
2013-11-19 21:53:26 +00:00
|
|
|
import octoprint.util as util
|
2013-10-28 21:08:36 +00:00
|
|
|
|
2013-12-21 13:46:20 +00:00
|
|
|
|
2013-12-22 01:01:48 +00:00
|
|
|
#~~ Heater
|
2013-10-28 21:08:36 +00:00
|
|
|
|
|
|
|
|
|
2013-12-22 01:01:48 +00:00
|
|
|
@api.route("/printer/heater", methods=["POST"])
|
2013-10-28 21:08:36 +00:00
|
|
|
@restricted_access
|
2013-11-19 21:53:26 +00:00
|
|
|
def controlPrinterHotend():
|
|
|
|
|
if not printer.isOperational():
|
2013-12-21 13:46:20 +00:00
|
|
|
return make_response("Printer is not operational", 409)
|
2013-11-19 21:53:26 +00:00
|
|
|
|
|
|
|
|
valid_commands = {
|
|
|
|
|
"temp": ["temps"],
|
|
|
|
|
"offset": ["offsets"]
|
|
|
|
|
}
|
|
|
|
|
command, data, response = util.getJsonCommandFromRequest(request, valid_commands)
|
|
|
|
|
if response is not None:
|
|
|
|
|
return response
|
|
|
|
|
|
|
|
|
|
valid_targets = ["hotend", "bed"]
|
|
|
|
|
|
|
|
|
|
##~~ temperature
|
|
|
|
|
if command == "temp":
|
|
|
|
|
temps = data["temps"]
|
|
|
|
|
|
|
|
|
|
# make sure the targets are valid and the values are numbers
|
|
|
|
|
validated_values = {}
|
|
|
|
|
for type, value in temps.iteritems():
|
|
|
|
|
if not type in valid_targets:
|
|
|
|
|
return make_response("Invalid target for setting temperature: %s" % type, 400)
|
|
|
|
|
if not isinstance(value, (int, long, float)):
|
|
|
|
|
return make_response("Not a number for %s: %r" % (type, value), 400)
|
|
|
|
|
validated_values[type] = value
|
|
|
|
|
|
|
|
|
|
# perform the actual temperature commands
|
|
|
|
|
# TODO make this a generic method call (printer.setTemperature(type, value)) to get rid of gcode here
|
|
|
|
|
if "hotend" in validated_values:
|
|
|
|
|
printer.command("M104 S%f" % validated_values["hotend"])
|
|
|
|
|
if "bed" in validated_values:
|
|
|
|
|
printer.command("M140 S%f" % validated_values["bed"])
|
|
|
|
|
|
|
|
|
|
##~~ 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 type, value in offsets.iteritems():
|
|
|
|
|
if not type in valid_targets:
|
|
|
|
|
return make_response("Invalid target for setting temperature: %s" % type, 400)
|
|
|
|
|
if not isinstance(value, (int, long, float)):
|
|
|
|
|
return make_response("Not a number for %s: %r" % (type, value), 400)
|
|
|
|
|
if not -50 <= value <= 50:
|
|
|
|
|
return make_response("Offset %s not in range [-50, 50]: %f" % (type, value), 400)
|
|
|
|
|
validated_values[type] = value
|
|
|
|
|
|
|
|
|
|
# set the offsets
|
|
|
|
|
if "hotend" in validated_values and "bed" in validated_values:
|
|
|
|
|
printer.setTemperatureOffset(validated_values["hotend"], validated_values["bed"])
|
|
|
|
|
elif "hotend" in validated_values:
|
|
|
|
|
printer.setTemperatureOffset(validated_values["hotend"], None)
|
|
|
|
|
elif "bed" in validated_values:
|
|
|
|
|
printer.setTemperatureOffset(None, validated_values["bed"])
|
2013-10-28 21:08:36 +00:00
|
|
|
|
2013-12-21 13:46:20 +00:00
|
|
|
return NO_CONTENT
|
2013-10-28 21:08:36 +00:00
|
|
|
|
|
|
|
|
|
2013-12-22 01:01:48 +00:00
|
|
|
##~~ Print head
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@api.route("/printer/printhead", methods=["POST"])
|
2013-10-28 21:08:36 +00:00
|
|
|
@restricted_access
|
2013-11-19 21:53:26 +00:00
|
|
|
def controlPrinterPrinthead():
|
2013-10-28 21:08:36 +00:00
|
|
|
if not printer.isOperational() or printer.isPrinting():
|
|
|
|
|
# do not jog when a print job is running or we don't have a connection
|
2013-12-21 13:46:20 +00:00
|
|
|
return make_response("Printer is not operational or currently printing", 409)
|
2013-11-19 21:53:26 +00:00
|
|
|
|
|
|
|
|
valid_commands = {
|
|
|
|
|
"jog": [],
|
|
|
|
|
"home": ["axes"]
|
|
|
|
|
}
|
|
|
|
|
command, data, response = util.getJsonCommandFromRequest(request, valid_commands)
|
|
|
|
|
if response is not None:
|
|
|
|
|
return response
|
|
|
|
|
|
|
|
|
|
movementSpeed = settings().get(["printerParameters", "movementSpeed", ["x", "y", "z"]], asdict=True)
|
|
|
|
|
|
|
|
|
|
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():
|
|
|
|
|
# TODO make this a generic method call (printer.jog(axis, value)) to get rid of gcode here
|
|
|
|
|
printer.commands(["G91", "G1 %s%.4f F%d" % (axis.upper(), value, movementSpeed[axis]), "G90"])
|
|
|
|
|
|
|
|
|
|
##~~ 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
|
|
|
|
|
# TODO make this a generic method call (printer.home(axis, ...)) to get rid of gcode here
|
2013-11-24 12:12:02 +00:00
|
|
|
printer.commands(["G91", "G28 %s" % " ".join(map(lambda x: "%s0" % x.upper(), validated_values)), "G90"])
|
2013-11-19 21:53:26 +00:00
|
|
|
|
2013-12-21 13:46:20 +00:00
|
|
|
return NO_CONTENT
|
2013-11-19 21:53:26 +00:00
|
|
|
|
|
|
|
|
|
2013-12-22 01:01:48 +00:00
|
|
|
##~~ Feeder
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@api.route("/printer/feeder", methods=["POST"])
|
2013-11-19 21:53:26 +00:00
|
|
|
@restricted_access
|
|
|
|
|
def controlPrinterFeeder():
|
|
|
|
|
if not printer.isOperational() or printer.isPrinting():
|
|
|
|
|
# do not jog when a print job is running or we don't have a connection
|
2013-12-21 13:46:20 +00:00
|
|
|
return make_response("Printer is not operational or currently printing", 409)
|
2013-11-19 21:53:26 +00:00
|
|
|
|
|
|
|
|
valid_commands = {
|
|
|
|
|
"extrude": ["amount"]
|
|
|
|
|
}
|
|
|
|
|
command, data, response = util.getJsonCommandFromRequest(request, valid_commands)
|
|
|
|
|
if response is not None:
|
|
|
|
|
return response
|
|
|
|
|
|
|
|
|
|
extrusionSpeed = settings().get(["printerParameters", "movementSpeed", "e"])
|
|
|
|
|
|
|
|
|
|
if command == "extrude":
|
|
|
|
|
amount = data["amount"]
|
|
|
|
|
if not isinstance(amount, (int, long, float)):
|
|
|
|
|
return make_response("Not a number for extrusion amount: %r" % amount, 400)
|
|
|
|
|
|
|
|
|
|
# TODO make this a generic method call (printer.extruder([hotend,] amount)) to get rid of gcode here
|
|
|
|
|
printer.commands(["G91", "G1 E%s F%d" % (data["amount"], extrusionSpeed), "G90"])
|
2013-10-28 21:08:36 +00:00
|
|
|
|
2013-12-21 13:46:20 +00:00
|
|
|
return NO_CONTENT
|
2013-10-28 21:08:36 +00:00
|
|
|
|
2013-12-22 01:01:48 +00:00
|
|
|
|
|
|
|
|
##~~ SD Card
|
2013-10-28 21:08:36 +00:00
|
|
|
|
|
|
|
|
|
2013-12-22 01:01:48 +00:00
|
|
|
@api.route("/printer/sd", methods=["POST"])
|
2013-10-28 21:08:36 +00:00
|
|
|
@restricted_access
|
|
|
|
|
def sdCommand():
|
2013-12-21 13:46:20 +00:00
|
|
|
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)
|
2013-11-19 21:53:26 +00:00
|
|
|
|
|
|
|
|
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()
|
2013-10-28 21:08:36 +00:00
|
|
|
|
2013-12-21 13:46:20 +00:00
|
|
|
return NO_CONTENT
|
|
|
|
|
|
2013-12-22 01:01:48 +00:00
|
|
|
@api.route("/printer/sd", methods=["GET"])
|
2013-12-21 13:46:20 +00:00
|
|
|
def sdState():
|
|
|
|
|
if not settings().getBoolean(["feature", "sdSupport"]):
|
|
|
|
|
return make_response("SD support is disabled", 404)
|
2013-10-28 21:08:36 +00:00
|
|
|
|
2013-12-21 13:46:20 +00:00
|
|
|
return jsonify(ready=printer.isSdReady())
|
2013-10-28 21:08:36 +00:00
|
|
|
|
2013-12-22 01:01:48 +00:00
|
|
|
|
|
|
|
|
##~~ 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)
|
|
|
|
|
|
|
|
|
|
|