Added triggering GCODE scripts to the REST API and custom controls

This commit is contained in:
Gina Häußge 2015-03-06 16:04:43 +01:00
parent e79fd99a41
commit aecc7a4734
8 changed files with 97 additions and 26 deletions

View file

@ -1,8 +1,8 @@
.. _sec-api:
###
API
###
########
REST API
########
.. toctree::
:maxdepth: 2

View file

@ -953,8 +953,20 @@ Arbitrary Command Request
* - ``command``
- 0..1
- String
- Single command to send to the printer, mutually exclusive with ``commands``.
- Single command to send to the printer, mutually exclusive with ``commands`` and ``script``.
* - ``commands``
- 0..*
- Array of String
- Multiple commands to send to the printer (in the given order), mutually exclusive with ``command``.
- Multiple commands to send to the printer (in the given order), mutually exclusive with ``command`` and ``script``.
* - ``script``
- 0..*
- String
- Name of the GCODE script template to send to the printer, mutually exclusive with ``command`` and ``commands``.
* - ``parameters``
- 0..1
- Map of key value pairs
- Key value pairs of parameters to replace in sent commands/provide to the script renderer
* - ``context``
- 0..1
- Map of key value pairs
- (only if ``script`` is set) additional template variables to provide to the script renderer

View file

@ -104,6 +104,24 @@ class PrinterInterface(object):
"""
raise NotImplementedError()
def script(self, name, context=None):
"""
Sends the GCODE script ``name`` to the printer.
The script will be run through the template engine, the rendering context can be extended by providing a
``context`` with additional template variables to use.
If the script is unknown, an :class:`UnknownScriptException` will be raised.
Arguments:
name (string): The name of the GCODE script to render.
context (dict): An optional context of additional template variables to provide to the renderer.
Raises:
UnknownScriptException: There is no GCODE script with name ``name``
"""
raise NotImplementedError()
def jog(self, axis, amount):
"""
Jogs the specified printer ``axis`` by the specified ``amount`` in mm.
@ -438,4 +456,6 @@ class PrinterCallback(object):
"""
pass
class UnknownScript(BaseException):
def __init__(self, name, *args, **kwargs):
self.name = name

View file

@ -19,7 +19,7 @@ from octoprint import util as util
from octoprint.events import eventManager, Events
from octoprint.filemanager import FileDestinations
from octoprint.plugin import plugin_manager, ProgressPlugin
from octoprint.printer import PrinterInterface, PrinterCallback
from octoprint.printer import PrinterInterface, PrinterCallback, UnknownScript
from octoprint.printer.estimation import TimeEstimationHelper
from octoprint.settings import settings
from octoprint.util import comm as comm
@ -230,6 +230,17 @@ class Printer(PrinterInterface, comm.MachineComPrintCallback):
for command in commands:
self._comm.sendCommand(command)
def script(self, name, context=None):
if self._comm is None:
return
if name is None or not name:
raise ValueError("name must be set")
result = self._comm.sendGcodeScript(name, replacements=context)
if not result:
raise UnknownScript(name)
def jog(self, axis, amount):
if not isinstance(axis, (str, unicode)):
raise ValueError("axis must be a string: {axis}".format(axis=axis))

View file

@ -15,6 +15,8 @@ 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
@ -332,30 +334,47 @@ def printerCommand():
except JSONBadRequest:
return make_response("Malformed JSON body in request", 400)
parameters = dict()
if "parameters" in data.keys(): parameters = data["parameters"]
if "command" in data and "commands" in data:
return make_response("'command' and 'commands' are mutually exclusive", 400)
elif "command" in data:
commands = [data["command"]]
elif "commands" in data and isinstance(data["commands"], (list, tuple)):
commands = data["commands"]
else:
return make_response("Need either single 'command' or list of 'commands'", 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)
commandsToSend = []
for command in commands:
commandToSend = command
if len(parameters) > 0:
commandToSend = command % parameters
commandsToSend.append(commandToSend)
parameters = dict()
if "parameters" in data:
parameters = data["parameters"]
printer.commands(commandsToSend)
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

View file

@ -810,6 +810,7 @@ class Settings(object):
def loadScript(self, script_type, name, context=None, source=False):
if context is None:
context = dict()
context.update(dict(script=dict(type=script_type, name=name)))
template = self._get_script_template(script_type, name, source=source)
if template is None:

View file

@ -122,7 +122,7 @@ $(function() {
};
self._processControl = function (control) {
if (control.type == "parametric_command" || control.type == "parametric_commands") {
if (_.startsWith(control.type, "parametric_")) {
for (var i = 0; i < control.input.length; i++) {
control.input[i].value = ko.observable(control.input[i].default);
if (!control.input[i].hasOwnProperty("slider")) {
@ -281,9 +281,14 @@ $(function() {
} else if (command.type == "commands" || command.type == "parametric_commands") {
// multi command
data = {"commands": command.commands};
} else if (command.type == "script" || command.type == "parametric_script") {
data = {"script": command.script};
if (command.hasOwnProperty("context")) {
data["context"] = command.context;
}
}
if (command.type == "parametric_command" || command.type == "parametric_commands") {
if (command.type == "parametric_command" || command.type == "parametric_commands" || command.type == "parametric_script") {
// parametric command(s)
data["parameters"] = {};
for (var i = 0; i < command.input.length; i++) {
@ -319,9 +324,11 @@ $(function() {
return "customControls_sectionRowTemplate";
case "command":
case "commands":
case "script":
return "customControls_commandTemplate";
case "parametric_command":
case "parametric_commands":
case "parametric_script":
return "customControls_parametricCommandTemplate";
case "feedback_command":
return "customControls_feedbackCommandTemplate";

View file

@ -472,6 +472,7 @@ class MachineCom(object):
for line in scriptLines:
self.sendCommand(line)
return "\n".join(scriptLines)
def startPrint(self):
if not self.isOperational() or self.isPrinting():