Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
f2b9540
Add preparation of script to be sent
URJala Mar 26, 2026
d733e6e
Primary client example (WIP)
URJala Mar 30, 2026
363cc9e
Add KeyMessage and RuntimeException to abstract consumer
URJala Mar 30, 2026
fd396eb
Add KeyMessage and RuntimeException to primary consumer
URJala Mar 30, 2026
c4623f6
Add callbacks for KeyMessage and RuntimeException to primary client
URJala Mar 30, 2026
c237596
Add private sendScriptNoWrapping and use with member functions
URJala Mar 30, 2026
e137780
Check for robotmode in sendScript
URJala Mar 30, 2026
a8b8ac2
fix name typo
URJala Apr 8, 2026
3e367d4
Save the lates runtime exception
URJala Apr 8, 2026
189c98e
Add script execution feedback
URJala Apr 8, 2026
5e1fdb9
switch two checks in if statement
URJala Apr 8, 2026
ef845e7
Example (very wip)
URJala Apr 8, 2026
c10db95
Add ScriptCodeSyntaxException
URJala Apr 16, 2026
cfd5236
use ScriptCodeSyntaxException
URJala Apr 16, 2026
f52fbe0
Add function safetyModeAllowsExecution
URJala Apr 16, 2026
e77e5e2
Use std::chrono for timeout
URJala Apr 16, 2026
90506f4
more std::chrono
URJala Apr 16, 2026
4fdeb3e
Finish feedback loop
URJala Apr 16, 2026
61fad69
Fix safety mode error prints
URJala Apr 16, 2026
6fd8c7a
Refactor test_stop_command
URJala Apr 16, 2026
44e4434
Refactor test_program_execution_reports_exception
URJala Apr 16, 2026
9502647
Add new sendScript tests
URJala Apr 16, 2026
6e38747
Rename variable and remove debug prints
URJala Apr 16, 2026
6e1b13c
return false on bad safety mode
URJala Apr 16, 2026
98c735f
comment with question
URJala Apr 16, 2026
771c8de
Refactor prepare_script
URJala Apr 16, 2026
24327b9
Example, still wip
URJala Apr 16, 2026
cb5ed2d
Rename new sendScript to sendScriptBlocking
URJala Apr 23, 2026
07e52d2
Remove script_type parameter from sendScriptBlocking
URJala Apr 23, 2026
dea5921
Fix tests after changing around function names
URJala Apr 23, 2026
8fec91f
Add docstring to sencScriptBlocking
URJala Apr 23, 2026
549585e
Disable some compiler warnings for primary_client.cpp
URJala Apr 23, 2026
0524994
Improve error code handling
URJala Apr 23, 2026
6eec695
Add fail_on_warning parameter
URJala Apr 23, 2026
d6819ad
Rename some tests
URJala Apr 23, 2026
89f09ff
Refactor/clarify actual_script_name ternary
URJala Apr 30, 2026
e61e9ce
formatting
URJala Apr 30, 2026
8435ed1
Fix data types to avoid implicit conversions
URJala Apr 30, 2026
536f7e3
Test for failure on bad script code
URJala Apr 30, 2026
ef54f4e
Ignore runtime exception timestamps
URJala Apr 30, 2026
656154c
Separate check for 210 error code
URJala Apr 30, 2026
ff85b1b
Copy out key message queue before processing
URJala Apr 30, 2026
dd9b496
add script name to timeout error log
URJala Apr 30, 2026
c57838b
Update example file name and use same setup as other examples
URJala Apr 30, 2026
26bfa18
Use namespace urcl in example
URJala Apr 30, 2026
c29b8e7
Add primary client to documentation
URJala Apr 30, 2026
8c7658f
Remove debug stuff that should not have been committed
URJala Apr 30, 2026
3e1304e
Fix example
URJala Apr 30, 2026
8173598
add test for ignoring warnings
URJala Apr 30, 2026
7d912f5
comment out "ignore_warnings" test as it is unstable in CI
URJala Apr 30, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ else()
src/ur/dashboard_client.cpp
src/ur/dashboard_client_implementation_g5.cpp
src/ur/dashboard_client_implementation_x.cpp
src/primary/primary_client.cpp
PROPERTIES COMPILE_OPTIONS "-Wno-maybe-uninitialized")
endif()

Expand Down
1 change: 1 addition & 0 deletions doc/architecture.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ well as a couple of standalone modules to directly use subsets of the library's
:maxdepth: 1

architecture/dashboard_client
architecture/primary_client
architecture/reverse_interface
architecture/rtde_client
architecture/script_command_interface
Expand Down
42 changes: 42 additions & 0 deletions doc/architecture/primary_client.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
:github_url: https://github.com/UniversalRobots/Universal_Robots_Client_Library/blob/master/doc/architecture/primary_client.rst

.. _primary_client:

PrimaryClient
=============

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.
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``.

Script execution without feedback
---------------------------------
Method signature:

.. code-block:: c++

bool sendScript(std::string program);

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.
There is no feedback on whether the program is actually executed on the robot.

Script execution with feedback
------------------------------
Method signature:

.. code-block:: c++

bool sendScriptBlocking(
std::string program,
std::string script_name = "",
std::chrono::milliseconds timeout = std::chrono::seconds(1),
bool fail_on_warnings = true
);

| The ``sendScriptBlocking`` method will also accept valid URScript code, but blocks until the execution result of the given program is available.
| 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.
| 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.
| If the program has not started within the given ``timeout``, the method returns false.
| If the robot encounters an error or runtime exception during program execution the method also returns false.
| If ``fail_on_warnings`` is true, it will also return false, if the robot reports a warning during program execution.
| The method only returns true if the program is successfully executed on the robot.
| 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.
4 changes: 4 additions & 0 deletions examples/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ add_executable(primary_pipeline_example
primary_pipeline.cpp)
target_link_libraries(primary_pipeline_example ur_client_library::urcl)

add_executable(send_script_blocking
send_script_blocking.cpp)
target_link_libraries(send_script_blocking ur_client_library::urcl)

add_executable(primary_pipeline_calibration_example
primary_pipeline_calibration.cpp)
target_link_libraries(primary_pipeline_calibration_example ur_client_library::urcl)
Expand Down
68 changes: 68 additions & 0 deletions examples/send_script_blocking.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
#include <ur_client_library/primary/primary_client.h>
#include <thread>
#include <chrono>

using namespace urcl;

std::string DEFAULT_ROBOT_IP = "192.168.56.101";

int main(int argc, char* argv[])
{
// Set the loglevel to info to print info logs
urcl::setLogLevel(urcl::LogLevel::INFO);

// Parse the ip arguments if given
std::string robot_ip = DEFAULT_ROBOT_IP;
if (argc > 1)
{
robot_ip = std::string(argv[1]);
}
auto notif = comm::INotifier();
auto client = primary_interface::PrimaryClient(robot_ip, notif);
client.start(10);
std::cout << "Client connected" << std::endl;

// --------------- INITIALIZATION END -------------------

// Make sure the robot is running
client.commandBrakeRelease();

if (!client.safetyModeAllowsExecution())
{
std::cout << "Robot is not in a safety state where script execution is possible. Exiting." << std::endl;
return 0;
}

// The sendScriptBlocking accepts script code, and will return true or false,
// depending on whether the script is successfully executed
const std::string fully_defined_script = R"""(
# This is a fully defined script, function definition and all
# All comments in this script will be stripped before sending the script to the robot

# Any whitespace-only lines will also be removed
def example_fun():
movej([0,-0.75,0,0,0,0])
sleep(0.1)
movel([0,0,-1.5,0,0,0], t=5)
end)""";

if (client.sendScriptBlocking(fully_defined_script))
{
// The function definition can also be omitted
// A function name will then be auto generated
client.sendScriptBlocking(R"(textmsg("Successful program execution"))");
}
// A script-function name can also be passed to the method
// A timeout can also be given to limit the wait for the passed function to start. If timeout = 0, it will
// wait indefinitely.
client.sendScriptBlocking(R"(textmsg("hello"))", "cool_function_name", std::chrono::milliseconds(0));
// There is no feedback on secondary programs, so it will return successful as soon as the script is sent to the
// robot (Behavior is the same the sendScript function, except that robot state is checked before script is sent)
// Note that secondary scripts have to be "fully defined" by the user.
std::string secondary_script = R"(
sec sec_script():
textmsg("Named secondary program")
end
)";
client.sendScriptBlocking(secondary_script);
}
17 changes: 17 additions & 0 deletions include/ur_client_library/exceptions.h
Original file line number Diff line number Diff line change
Expand Up @@ -319,5 +319,22 @@ class RTDEInputConflictException : public UrException
std::string key_;
std::string message_;
};

class ScriptCodeSyntaxException : public UrException
{
public:
explicit ScriptCodeSyntaxException() = delete;

explicit ScriptCodeSyntaxException(const std::string& text) : std::runtime_error(text)
{
}

virtual ~ScriptCodeSyntaxException() = default;

virtual const char* what() const noexcept override
{
return std::runtime_error::what();
}
};
} // namespace urcl
#endif // ifndef UR_CLIENT_LIBRARY_EXCEPTIONS_H_INCLUDED
4 changes: 4 additions & 0 deletions include/ur_client_library/primary/abstract_primary_consumer.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@
#include "ur_client_library/primary/robot_state/robot_mode_data.h"
#include "ur_client_library/primary/robot_state/configuration_data.h"
#include "ur_client_library/primary/robot_message/safety_mode_message.h"
#include "ur_client_library/primary/robot_message/key_message.h"
#include "ur_client_library/primary/robot_message/runtime_exception_message.h"

namespace urcl
{
Expand Down Expand Up @@ -81,6 +83,8 @@ class AbstractPrimaryConsumer : public comm::IConsumer<PrimaryPackage>
virtual bool consume(RobotModeData& pkg) = 0;
virtual bool consume(ConfigurationData& pkg) = 0;
virtual bool consume(SafetyModeMessage& pkg) = 0;
virtual bool consume(KeyMessage& pkg) = 0;
virtual bool consume(RuntimeExceptionMessage& pkg) = 0;

private:
/* data */
Expand Down
59 changes: 59 additions & 0 deletions include/ur_client_library/primary/primary_client.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,21 @@ namespace urcl
{
namespace primary_interface
{

enum ScriptTypes
{
DEF = 0,
SEC = 1,
};

struct ScriptInfo
{
std::string script_name;
std::string script_code;
ScriptTypes script_type;
ScriptInfo(std::string name, std::string code, ScriptTypes type)
: script_name(name), script_code(code), script_type(type) {};
};
class PrimaryClient
{
public:
Expand Down Expand Up @@ -88,6 +103,33 @@ class PrimaryClient
*/
bool sendScript(const std::string& program);

/*!
* \brief Send a custom script program to the robot, and wait for the execution result.
*
* The given code must be valid according the UR Scripting Manual. The given script code will be automatically wrapped
* in a function definition, if it is not already. Secondary programs can also be passed to this function, but must be
* fully defined as a secondary program when calling. Secondary programs create no feedback, so this function will
* return true as soon as the program is uploaded successfully to the robot (same as the sendScript function).
*
* \param program URScript code that shall be executed by the robot.
*
* \param script_name Name of the script to be executed. This will be ignored, if the given script already defines a
* function name. The script name will be used in log messages in both the client library and in the robot logs. If no
* name is defined in any way, the script will be given a generic, but unique, name.
*
* \param timeout Amount of time to allow before the robot must have confirmed that the script has been started. If
* timeout is 0, it will be ignored. Default value: 1 second
*
* \param fail_on_warnings Whether or not the function should report a failure, if the robot reports a warning-level
* error during execution. Default true
*
* \throw urcl::ScriptCodeSyntaxException if the given script code has syntax errors, which are checked here.
*
* \returns true on successful execution of the script, false otherwise
*/
bool sendScriptBlocking(const std::string& program, std::string script_name = "",
std::chrono::milliseconds timeout = std::chrono::seconds(1), bool fail_on_warnings = true);

bool checkCalibration(const std::string& checksum);

/*!
Expand Down Expand Up @@ -272,6 +314,12 @@ class PrimaryClient
*/
RobotSeries getRobotSeries();

/* \brief Check if the current safety mode allows for script execution
*
* Safety modes allowing for execution are: NORMAL, REDUCED, RECOVERY, UNDEFINED_SAFETY_MODE
*/
bool safetyModeAllowsExecution();

private:
/*!
* \brief Reconnects the primary stream used to send program to the robot.
Expand All @@ -284,6 +332,11 @@ class PrimaryClient

// The function is called whenever an error code message is received
void errorMessageCallback(ErrorCode& code);
void keyMessageCallback(KeyMessage& msg);
void runtimeExceptionCallback(RuntimeExceptionMessage& msg);

ScriptInfo prepare_script(std::string script, std::string script_name);
std::vector<std::string> strip_comments_and_whitespace(std::vector<std::string> script_lines);

PrimaryParser parser_;
std::shared_ptr<PrimaryConsumer> consumer_;
Expand All @@ -297,6 +350,12 @@ class PrimaryClient

std::mutex error_code_queue_mutex_;
std::deque<ErrorCode> error_code_queue_;

std::mutex key_message_queue_mutex_;
std::deque<KeyMessage> key_message_queue_;

std::mutex runtime_exception_mutex_;
std::shared_ptr<primary_interface::RuntimeExceptionMessage> latest_runtime_exception_;
};

} // namespace primary_interface
Expand Down
31 changes: 31 additions & 0 deletions include/ur_client_library/primary/primary_consumer.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
#include "ur_client_library/primary/robot_state/robot_mode_data.h"
#include "ur_client_library/ur/datatypes.h"
#include "ur_client_library/ur/version_information.h"
#include "ur_client_library/primary/robot_message/key_message.h"

#include <functional>
#include <mutex>
Expand Down Expand Up @@ -196,6 +197,34 @@ class PrimaryConsumer : public AbstractPrimaryConsumer
error_code_message_callback_ = callback_function;
}

virtual bool consume(KeyMessage& pkg) override
{
if (key_message_callback_ != nullptr)
{
key_message_callback_(pkg);
}
return true;
}

void setKeyMessageCallback(std::function<void(KeyMessage&)> callback_function)
{
key_message_callback_ = callback_function;
}

virtual bool consume(RuntimeExceptionMessage& pkg) override
{
if (runtime_exception_callback_ != nullptr)
{
runtime_exception_callback_(pkg);
}
return true;
}

void setRuntimeExceptionCallback(std::function<void(RuntimeExceptionMessage&)> callback_function)
{
runtime_exception_callback_ = callback_function;
}

/*!
* \brief Get the kinematics info
*
Expand Down Expand Up @@ -258,6 +287,8 @@ class PrimaryConsumer : public AbstractPrimaryConsumer

private:
std::function<void(ErrorCode&)> error_code_message_callback_;
std::function<void(KeyMessage&)> key_message_callback_;
std::function<void(RuntimeExceptionMessage&)> runtime_exception_callback_;
std::shared_ptr<KinematicsInfo> kinematics_info_;
std::mutex robot_mode_mutex_;
std::shared_ptr<RobotModeData> robot_mode_;
Expand Down
Loading
Loading