Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
5ce8100
Add preparation of script to be sent
URJala Mar 26, 2026
8f7efdd
Primary client example (WIP)
URJala Mar 30, 2026
1e6e631
Add KeyMessage and RuntimeException to abstract consumer
URJala Mar 30, 2026
0dccfb0
Add KeyMessage and RuntimeException to primary consumer
URJala Mar 30, 2026
6a7e0d1
Add callbacks for KeyMessage and RuntimeException to primary client
URJala Mar 30, 2026
39387ed
Add private sendScriptNoWrapping and use with member functions
URJala Mar 30, 2026
2adffdc
Check for robotmode in sendScript
URJala Mar 30, 2026
6cc65f9
fix name typo
URJala Apr 8, 2026
2d82abf
Save the lates runtime exception
URJala Apr 8, 2026
8697cdc
Add script execution feedback
URJala Apr 8, 2026
7c7a03f
switch two checks in if statement
URJala Apr 8, 2026
7a743c8
Example (very wip)
URJala Apr 8, 2026
3b2538b
Add ScriptCodeSyntaxException
URJala Apr 16, 2026
d44d62c
use ScriptCodeSyntaxException
URJala Apr 16, 2026
361a8e2
Add function safetyModeAllowsExecution
URJala Apr 16, 2026
a0c3649
Use std::chrono for timeout
URJala Apr 16, 2026
88aa3c9
more std::chrono
URJala Apr 16, 2026
391f110
Finish feedback loop
URJala Apr 16, 2026
1737b3e
Fix safety mode error prints
URJala Apr 16, 2026
6472755
Refactor test_stop_command
URJala Apr 16, 2026
60149c1
Refactor test_program_execution_reports_exception
URJala Apr 16, 2026
bc52927
Add new sendScript tests
URJala Apr 16, 2026
6c1ed3d
Rename variable and remove debug prints
URJala Apr 16, 2026
4dc6e75
return false on bad safety mode
URJala Apr 16, 2026
637e3cb
comment with question
URJala Apr 16, 2026
92e55e4
Refactor prepare_script
URJala Apr 16, 2026
efbaa74
Example, still wip
URJala Apr 16, 2026
2b1ee6e
Rename new sendScript to sendScriptBlocking
URJala Apr 23, 2026
6183bef
Remove script_type parameter from sendScriptBlocking
URJala Apr 23, 2026
46f2658
Fix tests after changing around function names
URJala Apr 23, 2026
6cb44f7
Add docstring to sencScriptBlocking
URJala Apr 23, 2026
9477026
Disable some compiler warnings for primary_client.cpp
URJala Apr 23, 2026
c4655c7
Improve error code handling
URJala Apr 23, 2026
15d5ca1
Add fail_on_warning parameter
URJala Apr 23, 2026
2369501
Rename some tests
URJala Apr 23, 2026
fd05d05
Refactor/clarify actual_script_name ternary
URJala Apr 30, 2026
3d7ad64
formatting
URJala Apr 30, 2026
29c6753
Fix data types to avoid implicit conversions
URJala Apr 30, 2026
f6808ec
Test for failure on bad script code
URJala Apr 30, 2026
d80d973
Ignore runtime exception timestamps
URJala Apr 30, 2026
db30adc
Separate check for 210 error code
URJala Apr 30, 2026
5eacfa8
Copy out key message queue before processing
URJala Apr 30, 2026
d5427f9
add script name to timeout error log
URJala Apr 30, 2026
943fb57
Update example file name and use same setup as other examples
URJala Apr 30, 2026
77881a0
Use namespace urcl in example
URJala Apr 30, 2026
7e06117
Add primary client to documentation
URJala Apr 30, 2026
0043615
Remove debug stuff that should not have been committed
URJala Apr 30, 2026
2179c01
Fix example
URJala Apr 30, 2026
fd82a3c
add test for ignoring warnings
URJala Apr 30, 2026
35058e9
comment out "ignore_warnings" test as it is unstable in CI
URJala Apr 30, 2026
6a8cbe8
assert that protective stop was cleared
URJala May 15, 2026
732df8f
Reset robot after protective stop
URJala May 15, 2026
d38ece8
Check that stripped script is not empty
URJala May 15, 2026
89e5557
Robustify script name checking and reporting
URJala May 15, 2026
69e72b7
Clear existing messages of all types
URJala May 15, 2026
f7dacf4
Implement tests for long names and empty scripts
URJala May 15, 2026
69ca2be
Change warning report from ERROR to WARN
URJala May 15, 2026
f87f95a
Rename function and move throw statement to end of prepare_script
URJala May 15, 2026
b402463
Change troublesome test to use protective_stop()
URJala May 15, 2026
58d7502
Add timeout waiting for robot mode
URJala May 15, 2026
811fc4d
Update header docs to include throws triggered by commandStop
URJala May 15, 2026
01cc0c7
Add note about protective stops to docs
URJala May 15, 2026
163f0ec
Check that function definition has '('
URJala May 15, 2026
60e3346
Test parentheses detection
URJala May 15, 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 @@ -87,6 +87,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. Note: protective stops are reported as warnings by the robot.
| 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 @@ -38,6 +38,8 @@
#include "ur_client_library/primary/robot_state/configuration_data.h"
#include "ur_client_library/primary/robot_state/masterboard_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 @@ -83,6 +85,8 @@ class AbstractPrimaryConsumer : public comm::IConsumer<PrimaryPackage>
virtual bool consume(ConfigurationData& pkg) = 0;
virtual bool consume(MasterboardData& pkg) = 0;
virtual bool consume(SafetyModeMessage& pkg) = 0;
virtual bool consume(KeyMessage& pkg) = 0;
virtual bool consume(RuntimeExceptionMessage& pkg) = 0;

private:
/* data */
Expand Down
62 changes: 62 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,35 @@ 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.
* \throw urcl::UrException if the stop command cannot be sent to the robot.
* \throw urcl::TimeoutException if the robot doesn't stop the program within the given timeout.
*
* \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 @@ -286,6 +330,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 @@ -298,6 +348,12 @@ 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);
std::string truncate_script_name(std::string candidate_name);

PrimaryParser parser_;
std::shared_ptr<PrimaryConsumer> consumer_;
Expand All @@ -311,6 +367,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 @@ -33,6 +33,7 @@
#include "ur_client_library/primary/robot_state/masterboard_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 @@ -210,6 +211,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 @@ -293,6 +322,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::mutex kinematics_info_mutex_;
std::unique_ptr<KinematicsInfo> kinematics_info_;
std::mutex robot_mode_mutex_;
Expand Down
Loading
Loading