Skip to content

Commit 1847e7e

Browse files
committed
Add sphinx documentation and an example for the ScriptReader
1 parent 2470ea9 commit 1847e7e

File tree

5 files changed

+150
-54
lines changed

5 files changed

+150
-54
lines changed

doc/architecture.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ well as a couple of standalone modules to directly use subsets of the library's
1313
architecture/reverse_interface
1414
architecture/rtde_client
1515
architecture/script_command_interface
16+
architecture/script_reader
1617
architecture/script_sender
1718
architecture/trajectory_point_interface
1819
architecture/ur_driver

doc/architecture/script_reader.rst

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
:github_url: https://github.com/UniversalRobots/Universal_Robots_Client_Library/blob/master/doc/architecture/script_reader.rst
2+
3+
.. _script_reader:
4+
5+
ScriptReader
6+
============
7+
8+
Script code used by the :ref:`script_sender` is read from a file. That script code might have to be
9+
dynamically modified based on some configuration input. For example, if the script code contains
10+
connections to a remote PC, that PC's IP address might be configured by the user. Another example
11+
would be to include certain parts of the script code only if the robot's software version supports
12+
that.
13+
14+
For that purpose the ``ScriptReader`` class is provided. It reads the script code from a file and
15+
performs the following substitutions:
16+
17+
- Replaces variables in the form of ``{{variable_name}}`` with the value of the variable
18+
from a provided dictionary.
19+
- Includes other script files using the directive ``{% include file_name %}``. The included file is
20+
read from the same directory as the main script file. Nested includes are also possible.
21+
- Use conditionals in order to add certain parts of the script code only if a condition matches.
22+
23+
The supported substitutions use a basic implementation of the `Jinja2 templating engine` syntax.
24+
25+
.. note::
26+
One special literal is defined for **version information**. Use a software version prefixed with a
27+
``v`` character, e.g. ``v10.8.0`` to encode a software version. Version information entries can be
28+
compared with each other.
29+
30+
**Do not** wrap version information into quotes, as this will be interpreted as a string.
31+
32+
Example
33+
-------
34+
35+
Given two script files:
36+
37+
.. literalinclude:: ../../tests/resources/example_urscript_main.urscript
38+
:caption: tests/resources/example_urscript_main.urscript
39+
:linenos:
40+
:lineno-match:
41+
42+
.. literalinclude:: ../../tests/resources/example_urscript_feature.urscript
43+
:language: python
44+
:caption: tests/resources/example_urscript_feature.urscript
45+
:linenos:
46+
:lineno-match:
47+
48+
The dictionary entry for ``feature_name`` is "torque control".
49+
50+
Depending on the ``SOFTWARE_VERSION`` entry in the dictionary passed to the
51+
``ScriptReader``, the script code will be read as follows:
52+
53+
Given ``SOFTWARE_VERSION = v5.21.0``, the script code will be:
54+
55+
.. code-block:: python
56+
57+
popup("The cool new feature is not supported on Software version 5.23.0")
58+
59+
60+
Given ``SOFTWARE_VERSION = v5.23.0``, the script code will be:
61+
62+
.. code-block:: python
63+
64+
textmsg("torque control is a very cool feature!")
65+
66+
Supported Data
67+
--------------
68+
69+
Data dictionary (C++ side)
70+
~~~~~~~~~~~~~~~~~~~~~~~~~~
71+
72+
The data dictionary supports the following types
73+
74+
- ``str``: A string value, e.g. "Hello World"
75+
- ``int``: An integer value, e.g. 42
76+
- ``double``: A floating point value, e.g. 3.14
77+
- ``bool``: A boolean value, e.g. ``true`` or ``false``
78+
- ``VersionInformation``: A version information value, e.g. ``VersionInformation::fromString("10.8.0")``
79+
80+
Script code side
81+
~~~~~~~~~~~~~~~~
82+
83+
Boolean expressions
84+
^^^^^^^^^^^^^^^^^^^
85+
86+
Boolean expressions have to follow one of two possible syntax variations
87+
88+
- Direct evaluation of a boolean variable from the data dictionary:
89+
90+
.. code-block::
91+
92+
boolean_variable_name
93+
94+
- Comparison of a variable with a value using an operator.
95+
96+
.. code-block::
97+
98+
variable_name operator value
99+
100+
The operator has to be one of ``==``, ``!=``, ``<``, ``<=``, ``>``, ``>=``. On the lefthand side
101+
of the operator there has to be a variable from the data dictionary. The right hand side can be
102+
either a variable name or a value. If the right hand side is a variable name, it has to be
103+
defined in the data dictionary as well. Values will be parsed as follows:
104+
105+
- Strings: Wrapped in quotes, e.g. ``"Hello World"`` or ``'Universal Robots'``.
106+
- Numerical values such as ``42``, ``3.14``, ``-1``, ``1e-12``.
107+
- Boolean values: See below.
108+
- Version information: Prefixed with a ``v`` character, e.g. ``v10.8.0``, ``v5.23.0``.
109+
110+
Boolean values parsing
111+
^^^^^^^^^^^^^^^^^^^^^^
112+
113+
Boolean values can be parsed from the following strings:
114+
115+
- ``true``, ``True``, ``TRUE``
116+
- ``on``, ``On``, ``ON``
117+
- ``yes``, ``Yes``, ``YES``
118+
- ``1``
119+
- ``false``, ``False``, ``FALSE``
120+
- ``off``, ``Off``, ``OFF``
121+
- ``no``, ``No``, ``NO``
122+
- ``0``
123+
124+
Conditional blocks
125+
^^^^^^^^^^^^^^^^^^
126+
127+
Conditional blocks have to be started with a ``{% if condition %}`` directive and closed with a
128+
``{% endif %}`` directive. The condition can be any boolean expression as described above.
129+
130+
The ``{% elif condition %}`` and ``{% else %}`` directives can be used to add alternative paths.
131+
132+
Conditional blocks can be nested.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
textmsg("{{ feature_name }} is a very cool feature!")
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{% if SOFTWARE_VERSION >= v5.23.0 %}
2+
{% include "example_urscript_feature.urscript" %}
3+
{% else %}
4+
popup("The cool new feature is not supported on Software version 5.23.0")
5+
{% endif %}

tests/test_script_reader.cpp

Lines changed: 11 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232

3333
#include <gtest/gtest.h>
3434
#include "ur_client_library/control/script_reader.h"
35+
#include "ur_client_library/ur/version_information.h"
3536

3637
#include <fstream>
3738

@@ -165,6 +166,7 @@ TEST_F(ScriptReaderTest, ReplaceVariables)
165166
// By default std::to_string will convert double to 6 decimal places
166167
EXPECT_EQ(script, "movej([value1, 42, 6.280000, 0, 0, 0])\nlocal is_true = True\nlocal is_false = False\nThis is "
167168
"just a line without any replacement");
169+
std::remove(existing_script_file);
168170
}
169171

170172
TEST_F(ScriptReaderTest, VariableNotInDictThrowsError)
@@ -186,6 +188,7 @@ TEST_F(ScriptReaderTest, VariableNotInDictThrowsError)
186188
ofs.close();
187189

188190
EXPECT_THROW(reader.readScriptFile(existing_script_file, data), urcl::UnknownVariable);
191+
std::remove(existing_script_file);
189192
}
190193

191194
TEST_F(ScriptReaderTest, ReplaceConditionals)
@@ -235,6 +238,7 @@ Please log in.
235238
data["is_guest"] = false;
236239
script = reader.readScriptFile(existing_script_file, data);
237240
EXPECT_EQ(script, "Please log in.");
241+
std::remove(existing_script_file);
238242
}
239243

240244
TEST_F(ScriptReaderTest, CheckCondition)
@@ -323,25 +327,6 @@ TEST_F(ScriptReaderTest, CheckCondition)
323327
EXPECT_THROW(reader.evaluateExpression("X >= True", data), std::invalid_argument);
324328
}
325329

326-
TEST_F(ScriptReaderTest, ParseBoolean)
327-
{
328-
EXPECT_TRUE(ScriptReader::parseBoolean("true"));
329-
EXPECT_TRUE(ScriptReader::parseBoolean("True"));
330-
EXPECT_TRUE(ScriptReader::parseBoolean("TRUE"));
331-
EXPECT_TRUE(ScriptReader::parseBoolean("on"));
332-
EXPECT_TRUE(ScriptReader::parseBoolean("On"));
333-
EXPECT_TRUE(ScriptReader::parseBoolean("ON"));
334-
EXPECT_TRUE(ScriptReader::parseBoolean("1"));
335-
EXPECT_FALSE(ScriptReader::parseBoolean("false"));
336-
EXPECT_FALSE(ScriptReader::parseBoolean("False"));
337-
EXPECT_FALSE(ScriptReader::parseBoolean("FALSE"));
338-
EXPECT_FALSE(ScriptReader::parseBoolean("off"));
339-
EXPECT_FALSE(ScriptReader::parseBoolean("Off"));
340-
EXPECT_FALSE(ScriptReader::parseBoolean("OFF"));
341-
EXPECT_FALSE(ScriptReader::parseBoolean("0"));
342-
EXPECT_THROW(ScriptReader::parseBoolean("notabool"), urcl::UrException);
343-
}
344-
345330
TEST_F(ScriptReaderTest, DataVariantOperators)
346331
{
347332
ScriptReader::DataDict data;
@@ -403,49 +388,21 @@ TEST_F(ScriptReaderTest, DataVariantOperators)
403388
EXPECT_THROW(data["double1"] == data["str1"], std::invalid_argument);
404389
}
405390

406-
TEST_F(ScriptReaderTest, ConditionalInclude)
391+
TEST_F(ScriptReaderTest, Example)
407392
{
408393
ScriptReader reader;
409-
410-
char existing_script_file[] = "main_script.XXXXXX";
411-
std::ignore = mkstemp(existing_script_file);
412-
char existing_include_file[] = "included_script.XXXXXX";
413-
std::ignore = mkstemp(existing_include_file);
414-
std::ofstream ofs(existing_script_file);
415-
if (ofs.bad())
416-
{
417-
std::cout << "Failed to create temporary files" << std::endl;
418-
GTEST_FAIL();
419-
}
420-
ofs << "textmsg(\"This is a test script\")" << std::endl;
421-
ofs << "{% if ROBOT_VERSION > 10.7 %}" << std::endl;
422-
ofs << "{% include '" << std::string(existing_include_file) << "' %}" << std::endl;
423-
ofs << "{% endif %}" << std::endl;
424-
ofs.close();
425-
426-
// Create a temporary included script
427-
std::ofstream ofs_included(existing_include_file);
428-
if (ofs_included.bad())
429-
{
430-
std::cout << "Failed to create temporary files" << std::endl;
431-
GTEST_FAIL();
432-
}
433-
ofs_included << "movej([1,2,3,4,5,6])";
434-
ofs_included.close();
394+
std::string existing_script_file = "resources/example_urscript_main.urscript";
435395

436396
ScriptReader::DataDict data;
437-
data["ROBOT_VERSION"] = 10.8; // Set a version greater than 10.7 to include the script
397+
data["SOFTWARE_VERSION"] = urcl::VersionInformation::fromString("5.9");
398+
data["feature_name"] = "torque control";
438399

439400
std::string processed_script = reader.readScriptFile(existing_script_file, data);
440-
std::string expected_script = "textmsg(\"This is a test script\")\n"
441-
"movej([1,2,3,4,5,6])";
401+
std::string expected_script = " popup(\"The cool new feature is not supported on Software version 5.23.0\")";
442402
EXPECT_EQ(processed_script, expected_script);
443403

444-
data["ROBOT_VERSION"] = 10.6;
404+
data["SOFTWARE_VERSION"] = urcl::VersionInformation::fromString("5.23.0");
445405
processed_script = reader.readScriptFile(existing_script_file, data);
446-
expected_script = "textmsg(\"This is a test script\")";
406+
expected_script = " textmsg(\"torque control is a very cool feature!\")";
447407
EXPECT_EQ(processed_script, expected_script);
448-
449-
std::remove(existing_script_file);
450-
std::remove(existing_include_file);
451408
}

0 commit comments

Comments
 (0)