diff --git a/docs/configuration/custom_controls.rst b/docs/configuration/custom_controls.rst new file mode 100644 index 00000000..eb9af385 --- /dev/null +++ b/docs/configuration/custom_controls.rst @@ -0,0 +1,135 @@ +.. _sec-configuration-custom_controls: + +Custom Controls +=============== + +OctoPrint allows you to add custom controls to the "Control" tab of its interface. Control types reach from simple +buttons which trigger sending of one or more lines of GCODE to the printer over more complex controls allowing +parameterization of these commands with values entered by the user to full blown GCODE script templates backed by +`Jinja2 `_. + +.. note:: + + At the current time, the configuration of custom controls is only possible through manually editing OctoPrint's + ``config.yaml`` file. + + To make sure that your ``config.yaml`` stays valid when adding custom controls, don't hesitate to take a look at the + :ref:`YAML Primer `. + +.. _sec-configuration-custom_controls-configyaml: + +config.yaml +----------- + +Custom controls are configured within ``config.yaml`` in a ``controls`` section which basically represents a hierarchical +structure of all configured custom controls of various types. + +The following example defines a control for enabling the cooling fan with a variable speed defined by the user +(default 255 and selectable through a slider UI element) and a control for disabling the fan, all within a section named +"Fan", two example controls with multiple commands in a section "Example for multiple commands" and a command with printer +feedback evaluation for the result of the M114 "Get Position" gcode inside a section named "Reporting". + +.. sourcecode:: yaml + + controls: + - name: Fan + type: section + children: + - name: Enable Fan + type: parametric_command + command: M106 S%(speed)s + input: + - name: Speed (0-255) + parameter: speed + default: 255 + slider: + min: 0 + max: 255 + - name: Disable Fan + type: command + command: M107 + - name: Example for multiple commands + type: section + children: + - name: Move X (static) + type: commands + commands: + - G91 + - G1 X10 F3000 + - G90 + - name: Move X (parametric) + type: parametric_commands + commands: + - G91 + - G1 X%(distance)s F%(speed)s + - G90 + input: + - default: 10 + name: Distance + parameter: distance + - default: 3000 + name: Speed + parameter: speed + - name: Reporting + type: section + children: + - command: M114 + name: Get Position + type: feedback_command + regex: "X:([0-9.]+) Y:([0-9.]+) Z:([0-9.]+) E:([0-9.]+)" + template: "Position: X={0}, Y={1}, Z={2}, E={3}" + +Adding this to ``config.yaml``, restarting the OctoPrint server and switching to the "Control" tab within its +interface yields the following visual representation: + +.. _fig-configuration-custom_controls-example: +.. figure:: ../images/configuration-custom_controls-example.png + :align: center + :alt: The rendered output created through the example configuration + +As you can see there are quite a number of different custom controls already in this small example, each with their own +attributes: :ref:`sections `, :ref:`commands `, +:ref:`parametric commands ` and +:ref:`feedback commands `. Two attributes are common for all +of the types: ``name`` and ``type``. + +.. _sec-configuration-custom_controls-types: + +Types +----- + +.. _sec-configuration-custom_controls-types: + +Sections +........ + +.. _sec-configuration-custom_controls-types: + +Rows +.... + +.. _sec-configuration-custom_controls-types: + +Section rows +............ + +.. _sec-configuration-custom_controls-types: + +Commands +........ + +.. _sec-configuration-custom_controls-types-parametric_command: + +Parametric commands +................... + +.. _sec-configuration-custom_controls-types-script: + +Scripts +....... + +.. _sec-configuration-custom_controls-types-feedback_command: + +Feedback commands +................. + diff --git a/docs/configuration/index.rst b/docs/configuration/index.rst new file mode 100644 index 00000000..7a078cef --- /dev/null +++ b/docs/configuration/index.rst @@ -0,0 +1,11 @@ +.. _sec-configuration: + +############# +Configuration +############# + +.. toctree:: + :maxdepth: 2 + + custom_controls.rst + yaml.rst \ No newline at end of file diff --git a/docs/configuration/yaml.rst b/docs/configuration/yaml.rst new file mode 100644 index 00000000..4fc37ef7 --- /dev/null +++ b/docs/configuration/yaml.rst @@ -0,0 +1,175 @@ +.. _sec-configuration-yaml: + +A YAML Primer +============= + +Most of OctoPrint's configuration is done under the hood through `YAML `_ files, +which is why it makes sense to shed some light on the basics of this data serialization format. + +YAML is a text based format which excels at representing the most common of data structures in an easy and very human +readable way, which is why it was chosen for OctoPrint's configuration files. A text editor is all you need in order +to write YAML configuration files. + +.. _sec-configuration-yaml-basic: + +Basic Rules +----------- + +First of all some basic things to know about working with YAML files: + + * Never use tabs outside of quoted strings, especially not for indentation. The tab characters is illegal within + YAML files. + * Whitespace and indentation matters and plays an important part in structuring the data, so take special care + to stay consistent here. + * YAML's comments start with a `#` and go until the end of the line. + +.. _sec-configuration-yaml-types: + +Interesting data types +---------------------- + +You will probably only come across the three most basic types of data within OctoPrint's YAML files: scalars +(such as strings, integers, ...), lists and associated arrays (aka key-value-paris, aka maps, aka dictionaries). + +.. _sec-configuration-yaml-types-scalar: + +Scalars +....... + +Scalars are the most basic of all data types and are simple string, integer, float or boolean values. + +For most scalars you don't need any quotes at all, but if you need to define some piece of data which contains characters +that could be mistaken with YAML syntax you need to quote it in either double ``"`` or single ``'`` quotes for the +YAML file to stay valid. As simple rule of thumb, if your data contains any of these characters ``:-{}[]!#|>&%@`` better +quote it. Also quote it if you want a string but it could be mistaken for a valid number (integer or float) or if +it consists only of "Yes", "No", "yes", "no", "true" or "false", which would be converted to a boolean without quotes. + +In double quoted strings if you need to include a literal double quote in your string you can escape it by prefixing +it with a backslash ``\`` (which you can in turn escape by itself). In single quoted strings the single quote character +can be escaped by prefixing it with another single quote, basically doubling it. Backslashes in single quoted strings +do not need to be escaped. + +Quoted strings can also span across multiple lines, just indent the following lines. Note that you'll need to add a +completely empty line in order for force a line break, the data will not be actually wrapped across multiple lines +just because you spread its representation across multiple lines. + +.. list-table:: + + - * **Data type** + * **Examples** + - * int + * .. sourcecode:: yaml + + 23 + + 42 + + - * float + * .. sourcecode:: yaml + + 23.5 + + 100.0 + + - * boolean + * .. sourcecode:: yaml + + true + + false + + Yes + + No + + yes + + no + + - * string + * .. sourcecode:: yaml + + a string + + "some quoted string with a : colon and a { bracket and a quote \" and a backslash \\ - phew" + + 'some single quoted string with a single quote '' and a backslash \ - yay' + + "and a multiline string - just because we can we'll make it span + across not two but four YAML lines! + + Including this paragraph. But in fact it will only be two lines :)" + + "23" + + "42.3" + + "Yes" + + "No" + + "true" + + "false" + + yes and no + + true or false + +.. _sec-configuration-yaml-types-lists: + +Lists +..... + +Lists allow to "collect" a number of similar things into one data structure. They are created by prefixing one or more +consecutive lines with a ``-``: + +.. sourcecode:: yaml + + - item 1 + - 23.42 + - 57 + - true + +Take special care to have all of your list items at the same indentation level! + +.. _sec-configuration-yaml-types-dicts: + +Dictionaries +............ + +Dictionaries (aka associative arrays aka maps) allow organizing the data in key value pairs, with the key and the value +being separated through a colon ``:``: + +.. sourcecode:: yaml + + key: value + anotherkey: another value + +.. _sec-configuration-yaml-examples: + +Examples +-------- + +Based on the three types explained above, quite complex data structures are possible: + +.. sourcecode:: yaml + + general: + some_setting: some_value + a_list: + - item 1 + - 23.42 + - 57 + - true + some_flag: true + quoted_string: "This string is quoted because {we have this here} and also > this and : that" + specific: + setting1: value1 + setting2: value2 + subsetting21: value11 + subsetting22: + - subsubsetting221 + - subsubsetting222 + - subsubsetting223 + the_end: yes diff --git a/docs/images/configuration-custom_controls-example.png b/docs/images/configuration-custom_controls-example.png new file mode 100644 index 00000000..eab2a58b Binary files /dev/null and b/docs/images/configuration-custom_controls-example.png differ diff --git a/docs/index.rst b/docs/index.rst index 7acbf1f5..2803415b 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -16,6 +16,7 @@ Contents .. toctree:: :maxdepth: 2 + configuration/index.rst api/index.rst events/index.rst plugins/index.rst diff --git a/src/octoprint/settings.py b/src/octoprint/settings.py index 816813a5..786ce971 100644 --- a/src/octoprint/settings.py +++ b/src/octoprint/settings.py @@ -466,7 +466,15 @@ class Settings(object): def _migrate_config(self): dirty = False - for migrate in (self._migrate_event_config, self._migrate_reverse_proxy_config, self._migrate_printer_parameters, self._migrate_gcode_scripts): + + migrators = ( + self._migrate_event_config, + self._migrate_reverse_proxy_config, + self._migrate_printer_parameters, + self._migrate_gcode_scripts + ) + + for migrate in migrators: dirty = migrate() or dirty if dirty: self.save(force=True) diff --git a/src/octoprint/static/js/app/viewmodels/control.js b/src/octoprint/static/js/app/viewmodels/control.js index 45a779fa..4c045b2a 100644 --- a/src/octoprint/static/js/app/viewmodels/control.js +++ b/src/octoprint/static/js/app/viewmodels/control.js @@ -122,18 +122,20 @@ $(function() { }; self._processControl = function (control) { - if (_.startsWith(control.type, "parametric_")) { + if (control.type == "feedback_command" || control.type == "feedback") { + control.output = ko.observable(""); + self.feedbackControlLookup[control.name] = control.output; + } else if (control.type == "section" || control.type == "row" || control.type == "section_row") { + control.children = self._processControls(control.children); + } + + if (control.hasOwnProperty("input")) { 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")) { control.input[i].slider = false; } } - } else if (control.type == "feedback_command" || control.type == "feedback") { - control.output = ko.observable(""); - self.feedbackControlLookup[control.name] = control.output; - } else if (control.type == "section" || control.type == "row" || control.type == "section_row") { - control.children = self._processControls(control.children); } var js; @@ -275,25 +277,31 @@ $(function() { }) }; var data = undefined; - if (command.type == "command" || command.type == "parametric_command" || command.type == "feedback_command") { + if (command.hasOwnProperty("command")) { // single command data = {"command": command.command}; - } else if (command.type == "commands" || command.type == "parametric_commands") { + } else if (command.hasOwnProperty("commands")) { // multi command data = {"commands": command.commands}; - } else if (command.type == "script" || command.type == "parametric_script") { + } else if (command.hasOwnProperty("script")) { data = {"script": command.script}; if (command.hasOwnProperty("context")) { data["context"] = command.context; } + } else { + return; } - if (command.type == "parametric_command" || command.type == "parametric_commands" || command.type == "parametric_script") { + if (command.hasOwnProperty("input")) { // parametric command(s) data["parameters"] = {}; - for (var i = 0; i < command.input.length; i++) { - data["parameters"][command.input[i].parameter] = command.input[i].value(); - } + _.each(command.input, function(input) { + if (!input.hasOwnProperty("parameter") || !input.hasOwnProperty("value")) { + return; + } + + data["parameters"][input.parameter] = input.value(); + }); } if (command.confirm) {