WIP: streamlining and documenting custom controls
This commit is contained in:
parent
dee205fc2f
commit
8f6784d5d2
7 changed files with 352 additions and 14 deletions
135
docs/configuration/custom_controls.rst
Normal file
135
docs/configuration/custom_controls.rst
Normal file
|
|
@ -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 <http://jinja.pocoo.org/>`_.
|
||||
|
||||
.. 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-yaml>`.
|
||||
|
||||
.. _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 <sec-configuration-custom_controls-types-section>`, :ref:`commands <sec-configuration-custom_controls-commands>`,
|
||||
:ref:`parametric commands <sec-configuration-custom_controls-types-parametric_command>` and
|
||||
:ref:`feedback commands <sec-configuration-custom_controls-types-feedback_command>`. 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
|
||||
.................
|
||||
|
||||
11
docs/configuration/index.rst
Normal file
11
docs/configuration/index.rst
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
.. _sec-configuration:
|
||||
|
||||
#############
|
||||
Configuration
|
||||
#############
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
custom_controls.rst
|
||||
yaml.rst
|
||||
175
docs/configuration/yaml.rst
Normal file
175
docs/configuration/yaml.rst
Normal file
|
|
@ -0,0 +1,175 @@
|
|||
.. _sec-configuration-yaml:
|
||||
|
||||
A YAML Primer
|
||||
=============
|
||||
|
||||
Most of OctoPrint's configuration is done under the hood through `YAML <https://en.wikipedia.org/wiki/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
|
||||
BIN
docs/images/configuration-custom_controls-example.png
Normal file
BIN
docs/images/configuration-custom_controls-example.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 22 KiB |
|
|
@ -16,6 +16,7 @@ Contents
|
|||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
configuration/index.rst
|
||||
api/index.rst
|
||||
events/index.rst
|
||||
plugins/index.rst
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue