From eca11e0bd6cc3780a55e833a4e144fc4c643a984 Mon Sep 17 00:00:00 2001 From: Felix Exner Date: Mon, 19 May 2025 10:35:09 +0200 Subject: [PATCH 1/3] Add an example using the script_command_interface --- doc/examples.rst | 1 + doc/examples/script_command_interface.rst | 71 +++++++++++ examples/CMakeLists.txt | 4 + examples/script_command_interface.cpp | 142 ++++++++++++++++++++++ 4 files changed, 218 insertions(+) create mode 100644 doc/examples/script_command_interface.rst create mode 100644 examples/script_command_interface.cpp diff --git a/doc/examples.rst b/doc/examples.rst index 159b2721..a2a36280 100644 --- a/doc/examples.rst +++ b/doc/examples.rst @@ -23,6 +23,7 @@ may be running forever until manually stopped. examples/primary_pipeline examples/primary_pipeline_calibration examples/rtde_client + examples/script_command_interface examples/script_sender examples/spline_example examples/tool_contact_example diff --git a/doc/examples/script_command_interface.rst b/doc/examples/script_command_interface.rst new file mode 100644 index 00000000..964e4e3b --- /dev/null +++ b/doc/examples/script_command_interface.rst @@ -0,0 +1,71 @@ +:github_url: https://github.com/UniversalRobots/Universal_Robots_Client_Library/blob/master/doc/examples/script_command_interface.rst + +Script command interface +======================== + +The :ref:`script_command_interface` allows sending predefined commands to the robot while there is +URScript running that is connected to it. + +An example to utilize the script command interface can be found in the `freedrive_example.cpp `_. + +In order to use the ``ScriptCommandInterface``, there has to be a script code running on the robot +that connects to the ``ScriptCommandInterface``. This happens as art of the big +`external_control.urscript `_. In order to reuse that with this example, we will create a full +``UrDriver`` and leverage the ``ScriptCommandInterface`` through this. + +At first, we create a ``ExampleRobotWrapper`` object in order to initialize communication with the +robot. + +.. literalinclude:: ../../examples/script_command_interface.cpp + :language: c++ + :caption: examples/script_command_interface.cpp + :linenos: + :lineno-match: + :start-at: g_my_robot = + :end-at: std::thread script_command_send_thread(sendScriptCommands); + +The script commands will be sent in a separate thread which will be explained later. + +Since the connection to the script command interface runs as part of the bigger external_control +script, we'll wrap the calls alongside a full ``ExampleRobotWrapper``. Hence, we'll have to send +keepalive signals regularly to keep the script running: + +.. literalinclude:: ../../examples/script_command_interface.cpp + :language: c++ + :caption: examples/script_command_interface.cpp + :linenos: + :lineno-match: + :start-at: std::chrono::duration time_done(0); + :end-at: g_my_robot->getUrDriver()->stopControl(); + +Sending script commands +----------------------- + +Once the script is running on the robot, a connection to the driver's script command interface +should be established. The ``UrDriver`` forwards most calls of the ``ScriptCommandInterface`` and +we will use that interface in this example. To send a script command, we can e.g. use +``g_my_robot->getUrDriver()->zeroFTSensor()``. + +In the example, we have wrapped the calls into a lambda function that will wait a specific timeout, +print a log output what command will be sent and then call the respective command: + +.. literalinclude:: ../../examples/script_command_interface.cpp + :language: c++ + :caption: examples/script_command_interface.cpp + :linenos: + :lineno-match: + :start-at: run_cmd( + :end-before: URCL_LOG_INFO("Script command thread finished."); + +The lambda itself looks like this: + +.. literalinclude:: ../../examples/script_command_interface.cpp + :language: c++ + :caption: examples/script_command_interface.cpp + :linenos: + :lineno-match: + :start-at: auto run_cmd = + :end-before: // Keep running all commands in a loop + +For a list of all available script commands, please refer to the ``ScriptCommandInterface`` class +`here `_. diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 48452197..e27ea26d 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -44,6 +44,10 @@ add_executable(script_sender_example script_sender.cpp) target_link_libraries(script_sender_example ur_client_library::urcl) +add_executable(script_command_interface_example +script_command_interface.cpp) +target_link_libraries(script_command_interface_example ur_client_library::urcl) + add_executable(trajectory_point_interface_example trajectory_point_interface.cpp) target_link_libraries(trajectory_point_interface_example ur_client_library::urcl) diff --git a/examples/script_command_interface.cpp b/examples/script_command_interface.cpp new file mode 100644 index 00000000..31f8cae4 --- /dev/null +++ b/examples/script_command_interface.cpp @@ -0,0 +1,142 @@ +// -- BEGIN LICENSE BLOCK ---------------------------------------------- +// Copyright 2025 Universal Robots A/S +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// * Neither the name of the {copyright_holder} nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. +// -- END LICENSE BLOCK ------------------------------------------------ + +#include +#include +#include "ur_client_library/ur/tool_communication.h" + +#include +#include + +using namespace urcl; + +const std::string DEFAULT_ROBOT_IP = "192.168.56.101"; +const std::string SCRIPT_FILE = "resources/external_control.urscript"; +const std::string OUTPUT_RECIPE = "examples/resources/rtde_output_recipe.txt"; +const std::string INPUT_RECIPE = "examples/resources/rtde_input_recipe.txt"; + +std::unique_ptr g_my_robot; +bool g_HEADLESS = true; +bool g_running = false; + +void sendScriptCommands() +{ + auto run_cmd = [](const std::string& log_output, std::function func) { + const std::chrono::seconds timeout(3); + if (g_running) + { + std::this_thread::sleep_for(timeout); + URCL_LOG_INFO(log_output.c_str()); + func(); + } + }; + + // Keep running all commands in a loop until g_running is set to false + while (g_running) + { + run_cmd("Setting tool voltage to 24V", + []() { g_my_robot->getUrDriver()->setToolVoltage(urcl::ToolVoltage::_24V); }); + run_cmd("Enabling tool contact mode", []() { g_my_robot->getUrDriver()->startToolContact(); }); + run_cmd("Setting friction_compensation variable to `false`", + []() { g_my_robot->getUrDriver()->setFrictionCompensation(false); }); + run_cmd("Setting tool voltage to 0V", []() { g_my_robot->getUrDriver()->setToolVoltage(urcl::ToolVoltage::OFF); }); + run_cmd("Zeroing the force torque sensor", []() { g_my_robot->getUrDriver()->zeroFTSensor(); }); + run_cmd("Disabling tool contact mode", []() { g_my_robot->getUrDriver()->endToolContact(); }); + run_cmd("Setting friction_compensation variable to `true`", + []() { g_my_robot->getUrDriver()->setFrictionCompensation(true); }); + } + URCL_LOG_INFO("Script command thread finished."); +} + +int main(int argc, char* argv[]) +{ + 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]); + } + + // Parse how many seconds to run + auto second_to_run = std::chrono::seconds(0); + if (argc > 2) + { + second_to_run = std::chrono::seconds(std::stoi(argv[2])); + } + + // Parse whether to run in headless mode + // When not using headless mode, the global variables can be watched on the teach pendant. + if (argc > 3) + { + g_HEADLESS = std::string(argv[3]) == "true" || std::string(argv[3]) == "1" || std::string(argv[3]) == "True" || + std::string(argv[3]) == "TRUE"; + } + + g_my_robot = + std::make_unique(robot_ip, OUTPUT_RECIPE, INPUT_RECIPE, g_HEADLESS, "external_control.urp"); + + if (!g_my_robot->isHealthy()) + { + URCL_LOG_ERROR("Something in the robot initialization went wrong. Exiting. Please check the output above."); + return 1; + } + + // We will send script commands from a separate thread. That will stay active as long as + // g_running is true. + g_running = true; + std::thread script_command_send_thread(sendScriptCommands); + + // We will need to keep the script running on the robot. As we use the "usual" external_control + // urscript, we'll have to send keepalive signals as long as we want to keep it active. + std::chrono::duration time_done(0); + std::chrono::duration timeout(second_to_run); + auto stopwatch_last = std::chrono::steady_clock::now(); + auto stopwatch_now = stopwatch_last; + while ((time_done < timeout || second_to_run.count() == 0) && g_my_robot->isHealthy()) + { + g_my_robot->getUrDriver()->writeKeepalive(); + + stopwatch_now = std::chrono::steady_clock::now(); + time_done += stopwatch_now - stopwatch_last; + stopwatch_last = stopwatch_now; + std::this_thread::sleep_for( + std::chrono::milliseconds(static_cast(1.0 / g_my_robot->getUrDriver()->getControlFrequency()))); + } + + URCL_LOG_INFO("Timeout reached."); + g_my_robot->getUrDriver()->stopControl(); + + // Stop the script command thread + g_running = false; + script_command_send_thread.join(); + + return 0; +} From 53d46a10b097441c15d8e9131702d0b5f9d00bd9 Mon Sep 17 00:00:00 2001 From: Felix Exner Date: Tue, 20 May 2025 14:16:10 +0200 Subject: [PATCH 2/3] Apply suggestions from code review Co-authored-by: Mads Holm Peters <79145214+urmahp@users.noreply.github.com> --- doc/examples/script_command_interface.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/examples/script_command_interface.rst b/doc/examples/script_command_interface.rst index 964e4e3b..a9014718 100644 --- a/doc/examples/script_command_interface.rst +++ b/doc/examples/script_command_interface.rst @@ -9,7 +9,7 @@ URScript running that is connected to it. An example to utilize the script command interface can be found in the `freedrive_example.cpp `_. In order to use the ``ScriptCommandInterface``, there has to be a script code running on the robot -that connects to the ``ScriptCommandInterface``. This happens as art of the big +that connects to the ``ScriptCommandInterface``. This happens as part of the big `external_control.urscript `_. In order to reuse that with this example, we will create a full ``UrDriver`` and leverage the ``ScriptCommandInterface`` through this. From 0fb41cac81fa2ec7ac6ab52e19cf55c688d57765 Mon Sep 17 00:00:00 2001 From: Felix Exner Date: Tue, 20 May 2025 14:23:12 +0200 Subject: [PATCH 3/3] Add a comment about why there is a sleep in the example --- examples/script_command_interface.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/examples/script_command_interface.cpp b/examples/script_command_interface.cpp index 31f8cae4..6ada63f2 100644 --- a/examples/script_command_interface.cpp +++ b/examples/script_command_interface.cpp @@ -52,6 +52,9 @@ void sendScriptCommands() const std::chrono::seconds timeout(3); if (g_running) { + // We wait a fixed time so that not each command is run directly behind each other. + // This is done for example purposes only, so users can follow the effect on the teach + // pendant. std::this_thread::sleep_for(timeout); URCL_LOG_INFO(log_output.c_str()); func();