Skip to content

Commit 627cf20

Browse files
authored
Primary client script execution feedback (UniversalRobots#484)
Add a blocking function to send script code to the primary client. With this, it is possible to monitor script execution on the robot, know when it's done and if execution was successful.
1 parent c6a671e commit 627cf20

19 files changed

Lines changed: 1975 additions & 4 deletions

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ else()
8787
src/ur/dashboard_client.cpp
8888
src/ur/dashboard_client_implementation_g5.cpp
8989
src/ur/dashboard_client_implementation_x.cpp
90+
src/primary/primary_client.cpp
9091
PROPERTIES COMPILE_OPTIONS "-Wno-maybe-uninitialized")
9192
endif()
9293

doc/architecture.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ well as a couple of standalone modules to directly use subsets of the library's
1010
:maxdepth: 1
1111

1212
architecture/dashboard_client
13+
architecture/primary_client
1314
architecture/reverse_interface
1415
architecture/rtde_client
1516
architecture/script_command_interface
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
:github_url: https://github.com/UniversalRobots/Universal_Robots_Client_Library/blob/master/doc/architecture/primary_client.rst
2+
3+
.. _primary_client:
4+
5+
PrimaryClient
6+
=============
7+
8+
The Primary Client serves as an interface to the robot's `primary interface <https://docs.universal-robots.com/tutorials/communication-protocol-tutorials/primary-secondary-guide.html>`_, present on port 30001.
9+
The ``PrimaryClient`` class supports, among other things, sending URScript code for execution on the robot through the primary interface. Currently it offers two methods of script execution: ``sendScript`` and ``sendScriptBlocking``.
10+
11+
Script execution without feedback
12+
---------------------------------
13+
Method signature:
14+
15+
.. code-block:: c++
16+
17+
bool sendScript(std::string program);
18+
19+
The ``sendScript`` method will accept valid URScript code, and send it to the robot through the primary interface. This is a non-blocking method, as it will return as soon as the program has been transferred to the robot. It returns true when the program is successfully transferred to the robot, and false otherwise.
20+
There is no feedback on whether the program is actually executed on the robot.
21+
22+
Script execution with feedback
23+
------------------------------
24+
Method signature:
25+
26+
.. code-block:: c++
27+
28+
bool sendScriptBlocking(
29+
std::string program,
30+
std::string script_name = "",
31+
std::chrono::milliseconds timeout = std::chrono::seconds(1),
32+
bool fail_on_warnings = true
33+
);
34+
35+
| The ``sendScriptBlocking`` method will also accept valid URScript code, but blocks until the execution result of the given program is available.
36+
| Prior to transferring the program it will first check that the robot is in a state where it can execute programs, if not it returns false.
37+
| If the robot is ready, the program is then transferred, and the method will wait for the robot to report that the program has either started, finished or encountered an error.
38+
| If the program has not started within the given ``timeout``, the method returns false.
39+
| If the robot encounters an error or runtime exception during program execution the method also returns false.
40+
| If ``fail_on_warnings`` is true, it will also return false, if the robot reports a warning during program execution. Note: protective stops are reported as warnings by the robot.
41+
| The method only returns true if the program is successfully executed on the robot.
42+
| This method also accepts secondary programs, but no feedback is available for those, so it will behave similarly to the ``sendScript`` method in those cases, except for the pre-transfer checks.

doc/examples.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ may be running forever until manually stopped.
2626
examples/external_fts_through_rtde
2727
examples/script_command_interface
2828
examples/script_sender
29+
examples/send_script
2930
examples/spline_example
3031
examples/tool_contact_example
3132
examples/direct_torque_control

doc/examples/send_script.rst

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
:github_url: https://github.com/UniversalRobots/Universal_Robots_Client_Library/blob/master/doc/examples/send_script.rst
2+
3+
.. _send_script_example:
4+
5+
Send script example
6+
===================
7+
8+
This example shows how to send arbitrary URScript code to the robot using the
9+
:ref:`primary_client`. It demonstrates both the blocking variant (``sendScriptBlocking``), which
10+
waits for execution feedback, and the non-blocking variant (``sendScript``), which only confirms
11+
that the script has been forwarded to the robot.
12+
13+
The full source code can be found in `send_script.cpp <https://github.com/UniversalRobots/Universal_Robots_Client_Library/blob/master/examples/send_script.cpp>`_.
14+
15+
Setting up the primary client
16+
-----------------------------
17+
18+
The example connects to the robot's primary interface by creating a ``PrimaryClient``. After
19+
starting the client, the robot's brakes are released so that motion scripts can actually run, and
20+
the safety state is checked before any script is sent:
21+
22+
.. literalinclude:: ../../examples/send_script.cpp
23+
:language: c++
24+
:caption: examples/send_script.cpp
25+
:linenos:
26+
:lineno-match:
27+
:start-at: auto notif = comm::INotifier();
28+
:end-at: }
29+
30+
Sending scripts with execution feedback
31+
---------------------------------------
32+
33+
The ``sendScriptBlocking`` function uploads URScript code to the robot and waits until the robot
34+
reports the result of the execution. The given code can be a fully defined script (with its own
35+
``def ... end`` block) or a snippet that will automatically be wrapped into a function on the
36+
client side. Comments and whitespace-only lines are stripped before the script is sent.
37+
38+
.. literalinclude:: ../../examples/send_script.cpp
39+
:language: c++
40+
:caption: examples/send_script.cpp
41+
:linenos:
42+
:lineno-match:
43+
:start-at: const std::string fully_defined_script
44+
:end-at: client.sendScriptBlocking(fully_defined_script)
45+
46+
If you don't provide a function definition, the library wraps the snippet in one for you. You can
47+
optionally pass a ``script_name`` (used in log messages on both the client and the robot) and a
48+
``start_timeout`` that limits how long the call waits for the robot to confirm that the script has
49+
started. A timeout of ``0`` means "wait indefinitely":
50+
51+
.. literalinclude:: ../../examples/send_script.cpp
52+
:language: c++
53+
:caption: examples/send_script.cpp
54+
:linenos:
55+
:lineno-match:
56+
:start-at: client.sendScriptBlocking(R"(textmsg("Successful program execution"))");
57+
:end-at: client.sendScriptBlocking(R"(textmsg("hello"))", "cool_function_name", std::chrono::milliseconds(0));
58+
59+
Secondary programs can also be uploaded through ``sendScriptBlocking``. Since the robot does not
60+
report execution feedback for secondary programs, the call returns as soon as the script has been
61+
accepted. Note that secondary programs must be *fully defined* by the user (``sec ... end``):
62+
63+
.. literalinclude:: ../../examples/send_script.cpp
64+
:language: c++
65+
:caption: examples/send_script.cpp
66+
:linenos:
67+
:lineno-match:
68+
:start-at: std::string secondary_script
69+
:end-at: client.sendScriptBlocking(secondary_script);
70+
71+
Reporting bad script code
72+
-------------------------
73+
74+
When a script contains errors (e.g. a typo or an undefined symbol), ``sendScriptBlocking`` will
75+
report this back to the caller. The example sends a script that uses an undefined variable
76+
``current_pos`` instead of ``current_pose``, and logs the result:
77+
78+
.. literalinclude:: ../../examples/send_script.cpp
79+
:language: c++
80+
:caption: examples/send_script.cpp
81+
:linenos:
82+
:lineno-match:
83+
:start-at: const std::string bad_script_code
84+
:end-before: // We can also send script code without any checks
85+
86+
Sending scripts without feedback
87+
--------------------------------
88+
89+
For situations where execution feedback is not needed, ``sendScript`` can be
90+
used. It returns ``true`` as soon as the script has been transferred to the robot. The library
91+
performs no further checks, so faulty script code will *not* be reported back here:
92+
93+
.. literalinclude:: ../../examples/send_script.cpp
94+
:language: c++
95+
:caption: examples/send_script.cpp
96+
:linenos:
97+
:lineno-match:
98+
:start-at: // We can also send script code without any checks
99+
:end-at: }

examples/CMakeLists.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ add_executable(primary_pipeline_example
1111
primary_pipeline.cpp)
1212
target_link_libraries(primary_pipeline_example ur_client_library::urcl)
1313

14+
add_executable(send_script
15+
send_script.cpp)
16+
target_link_libraries(send_script ur_client_library::urcl)
17+
1418
add_executable(primary_pipeline_calibration_example
1519
primary_pipeline_calibration.cpp)
1620
target_link_libraries(primary_pipeline_calibration_example ur_client_library::urcl)

examples/send_script.cpp

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
#include <ur_client_library/primary/primary_client.h>
2+
#include <chrono>
3+
4+
using namespace urcl;
5+
6+
std::string g_DEFAULT_ROBOT_IP = "192.168.56.101";
7+
8+
int main(int argc, char* argv[])
9+
{
10+
// Set the loglevel to info to print info logs
11+
urcl::setLogLevel(urcl::LogLevel::INFO);
12+
13+
// Parse the ip arguments if given
14+
std::string robot_ip = g_DEFAULT_ROBOT_IP;
15+
if (argc > 1)
16+
{
17+
robot_ip = std::string(argv[1]);
18+
}
19+
auto notif = comm::INotifier();
20+
auto client = primary_interface::PrimaryClient(robot_ip, notif);
21+
client.start(10);
22+
23+
// --------------- INITIALIZATION END -------------------
24+
25+
// Make sure the robot is running
26+
client.commandBrakeRelease();
27+
28+
if (!client.safetyModeAllowsExecution())
29+
{
30+
URCL_LOG_ERROR("Robot is not in a safety state where script execution is possible. Exiting.");
31+
return 1;
32+
}
33+
34+
// The sendScriptBlocking accepts script code, and will return true or false,
35+
// depending on whether the script is successfully executed
36+
const std::string fully_defined_script = R"""(
37+
# This is a fully defined script, function definition and all
38+
# All comments in this script will be stripped before sending the script to the robot
39+
40+
# Any whitespace-only lines will also be removed
41+
def example_fun():
42+
movej([0,-1.2,1.2,-0.1,1.57,0])
43+
sleep(0.1)
44+
current_pose = get_target_tcp_pose()
45+
relative_move = p[0,-0.1,0,0,0,0]
46+
movel(pose_trans(current_pose, relative_move), t=1)
47+
end)""";
48+
49+
if (client.sendScriptBlocking(fully_defined_script))
50+
{
51+
// The function definition can also be omitted
52+
// A function name will then be auto generated
53+
client.sendScriptBlocking(R"(textmsg("Successful program execution"))");
54+
}
55+
// A script-function name can also be passed to the method
56+
// A timeout can also be given to limit the wait for the passed function to start. If timeout = 0, it will
57+
// wait indefinitely.
58+
client.sendScriptBlocking(R"(textmsg("hello"))", "cool_function_name", std::chrono::milliseconds(0));
59+
// There is no feedback on secondary programs, so it will return successful as soon as the script is sent to the
60+
// robot (Behavior is the same the sendScript function, except that robot state is checked before script is sent)
61+
// Note that secondary scripts have to be "fully defined" by the user.
62+
std::string secondary_script = R"(
63+
sec sec_script():
64+
textmsg("Named secondary program")
65+
end
66+
)";
67+
client.sendScriptBlocking(secondary_script);
68+
69+
// Sending wrong script code will result in a clear error
70+
const std::string bad_script_code = R"""(
71+
def bad_code():
72+
current_pose = get_target_tcp_pose()
73+
movel(current_pos) # note pose vs pos
74+
end)""";
75+
URCL_LOG_INFO("Sending bad script code...");
76+
bool success = client.sendScriptBlocking(bad_script_code);
77+
{
78+
std::stringstream ss;
79+
ss << "Execution of bad code successful? " << std::boolalpha << success;
80+
URCL_LOG_INFO("%s", ss.str().c_str());
81+
}
82+
83+
// We can also send script code without any checks
84+
URCL_LOG_INFO("Executing motion without feedback");
85+
client.sendScript("movej([0.1,-0.9,0.9,0,0,0])");
86+
// But we won't know when that is done or even if our code was correct.
87+
// E.g. sending the bad script here will not give us any information
88+
// The return value will only tell us that the script code has been sent to the robot.
89+
URCL_LOG_INFO("Sending bad script code without feedback...");
90+
success = client.sendScript(bad_script_code);
91+
{
92+
std::stringstream ss;
93+
ss << "Bad code sent to robot successfully? " << std::boolalpha << success;
94+
URCL_LOG_INFO("%s", ss.str().c_str());
95+
}
96+
}

include/ur_client_library/exceptions.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,5 +319,22 @@ class RTDEInputConflictException : public UrException
319319
std::string key_;
320320
std::string message_;
321321
};
322+
323+
class ScriptCodeSyntaxException : public UrException
324+
{
325+
public:
326+
explicit ScriptCodeSyntaxException() = delete;
327+
328+
explicit ScriptCodeSyntaxException(const std::string& text) : std::runtime_error(text)
329+
{
330+
}
331+
332+
virtual ~ScriptCodeSyntaxException() = default;
333+
334+
virtual const char* what() const noexcept override
335+
{
336+
return std::runtime_error::what();
337+
}
338+
};
322339
} // namespace urcl
323340
#endif // ifndef UR_CLIENT_LIBRARY_EXCEPTIONS_H_INCLUDED

include/ur_client_library/primary/abstract_primary_consumer.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@
3838
#include "ur_client_library/primary/robot_state/configuration_data.h"
3939
#include "ur_client_library/primary/robot_state/masterboard_data.h"
4040
#include "ur_client_library/primary/robot_message/safety_mode_message.h"
41+
#include "ur_client_library/primary/robot_message/key_message.h"
42+
#include "ur_client_library/primary/robot_message/runtime_exception_message.h"
4143

4244
namespace urcl
4345
{
@@ -83,6 +85,8 @@ class AbstractPrimaryConsumer : public comm::IConsumer<PrimaryPackage>
8385
virtual bool consume(ConfigurationData& pkg) = 0;
8486
virtual bool consume(MasterboardData& pkg) = 0;
8587
virtual bool consume(SafetyModeMessage& pkg) = 0;
88+
virtual bool consume(KeyMessage& pkg) = 0;
89+
virtual bool consume(RuntimeExceptionMessage& pkg) = 0;
8690

8791
private:
8892
/* data */

0 commit comments

Comments
 (0)