From aecc7a47348f033d629796d418257a35ec4109a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Fri, 6 Mar 2015 16:04:43 +0100 Subject: [PATCH] Added triggering GCODE scripts to the REST API and custom controls --- docs/api/index.rst | 6 +-- docs/api/printer.rst | 16 +++++- src/octoprint/printer/__init__.py | 22 +++++++- src/octoprint/printer/standard.py | 13 ++++- src/octoprint/server/api/printer.py | 53 +++++++++++++------ src/octoprint/settings.py | 1 + .../static/js/app/viewmodels/control.js | 11 +++- src/octoprint/util/comm.py | 1 + 8 files changed, 97 insertions(+), 26 deletions(-) diff --git a/docs/api/index.rst b/docs/api/index.rst index 4f1d643f..f0295e0e 100644 --- a/docs/api/index.rst +++ b/docs/api/index.rst @@ -1,8 +1,8 @@ .. _sec-api: -### -API -### +######## +REST API +######## .. toctree:: :maxdepth: 2 diff --git a/docs/api/printer.rst b/docs/api/printer.rst index f6750fe2..48072249 100644 --- a/docs/api/printer.rst +++ b/docs/api/printer.rst @@ -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 diff --git a/src/octoprint/printer/__init__.py b/src/octoprint/printer/__init__.py index 19de90f9..cf7348b9 100644 --- a/src/octoprint/printer/__init__.py +++ b/src/octoprint/printer/__init__.py @@ -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 diff --git a/src/octoprint/printer/standard.py b/src/octoprint/printer/standard.py index 906f2fb4..5ec59b77 100644 --- a/src/octoprint/printer/standard.py +++ b/src/octoprint/printer/standard.py @@ -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)) diff --git a/src/octoprint/server/api/printer.py b/src/octoprint/server/api/printer.py index aec0569b..e260c762 100644 --- a/src/octoprint/server/api/printer.py +++ b/src/octoprint/server/api/printer.py @@ -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 diff --git a/src/octoprint/settings.py b/src/octoprint/settings.py index b1a82a99..816813a5 100644 --- a/src/octoprint/settings.py +++ b/src/octoprint/settings.py @@ -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: diff --git a/src/octoprint/static/js/app/viewmodels/control.js b/src/octoprint/static/js/app/viewmodels/control.js index 69882b80..45a779fa 100644 --- a/src/octoprint/static/js/app/viewmodels/control.js +++ b/src/octoprint/static/js/app/viewmodels/control.js @@ -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"; diff --git a/src/octoprint/util/comm.py b/src/octoprint/util/comm.py index f9216116..77066212 100644 --- a/src/octoprint/util/comm.py +++ b/src/octoprint/util/comm.py @@ -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():