Skip to content

Commit 6509961

Browse files
committed
Add a wrapper to handle all robot setup
1 parent ce779b3 commit 6509961

File tree

4 files changed

+413
-76
lines changed

4 files changed

+413
-76
lines changed

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ add_library(urcl SHARED
3939
src/ur/version_information.cpp
4040
src/rtde/rtde_writer.cpp
4141
src/default_log_handler.cpp
42+
src/example_robot_wrapper.cpp
4243
src/log.cpp
4344
src/helpers.cpp
4445
)

examples/freedrive_example.cpp

Lines changed: 15 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
#include <ur_client_library/ur/dashboard_client.h>
3737
#include <ur_client_library/ur/ur_driver.h>
3838
#include <ur_client_library/types.h>
39+
#include <ur_client_library/example_robot_wrapper.h>
3940

4041
#include <chrono>
4142
#include <cstdlib>
@@ -47,27 +48,17 @@
4748
using namespace urcl;
4849

4950
const std::string DEFAULT_ROBOT_IP = "192.168.56.101";
50-
const std::string SCRIPT_FILE = "resources/external_control.urscript";
5151
const std::string OUTPUT_RECIPE = "examples/resources/rtde_output_recipe.txt";
5252
const std::string INPUT_RECIPE = "examples/resources/rtde_input_recipe.txt";
53-
const std::string CALIBRATION_CHECKSUM = "calib_12788084448423163542";
5453

55-
std::unique_ptr<UrDriver> g_my_driver;
56-
std::unique_ptr<DashboardClient> g_my_dashboard;
57-
58-
// We need a callback function to register. See UrDriver's parameters for details.
59-
void handleRobotProgramState(bool program_running)
60-
{
61-
// Print the text in green so we see it better
62-
std::cout << "\033[1;32mProgram running: " << std::boolalpha << program_running << "\033[0m\n" << std::endl;
63-
}
54+
std::unique_ptr<ExampleRobotWrapper> g_my_robot;
6455

6556
void sendFreedriveMessageOrDie(const control::FreedriveControlMessage freedrive_action)
6657
{
67-
bool ret = g_my_driver->writeFreedriveControlMessage(freedrive_action);
58+
bool ret = g_my_robot->ur_driver_->writeFreedriveControlMessage(freedrive_action);
6859
if (!ret)
6960
{
70-
URCL_LOG_ERROR("Could not send joint command. Is the robot in remote control?");
61+
URCL_LOG_ERROR("Could not send freedrive command. Is the robot in remote control?");
7162
exit(1);
7263
}
7364
}
@@ -89,86 +80,34 @@ int main(int argc, char* argv[])
8980
second_to_run = std::chrono::seconds(std::stoi(argv[2]));
9081
}
9182

92-
// Making the robot ready for the program by:
93-
// Connect the robot Dashboard
94-
g_my_dashboard.reset(new DashboardClient(robot_ip));
95-
if (!g_my_dashboard->connect())
96-
{
97-
URCL_LOG_ERROR("Could not connect to dashboard");
98-
return 1;
99-
}
83+
bool headless_mode = true;
10084

101-
// // Stop program, if there is one running
102-
if (!g_my_dashboard->commandStop())
103-
{
104-
URCL_LOG_ERROR("Could not send stop program command");
105-
return 1;
106-
}
85+
g_my_robot = std::make_unique<ExampleRobotWrapper>(robot_ip, OUTPUT_RECIPE, INPUT_RECIPE, headless_mode,
86+
"external_control.urp");
10787

108-
// Power it off
109-
if (!g_my_dashboard->commandPowerOff())
88+
if (!g_my_robot->waitForProgramRunning())
11089
{
111-
URCL_LOG_ERROR("Could not send Power off command");
90+
URCL_LOG_ERROR("External Control script not running.");
11291
return 1;
11392
}
11493

115-
// Power it on
116-
if (!g_my_dashboard->commandPowerOn())
117-
{
118-
URCL_LOG_ERROR("Could not send Power on command");
119-
return 1;
120-
}
121-
122-
// Release the brakes
123-
if (!g_my_dashboard->commandBrakeRelease())
124-
{
125-
URCL_LOG_ERROR("Could not send BrakeRelease command");
126-
return 1;
127-
}
128-
129-
// Now the robot is ready to receive a program
130-
std::unique_ptr<ToolCommSetup> tool_comm_setup;
131-
const bool headless = true;
132-
g_my_driver.reset(new UrDriver(robot_ip, SCRIPT_FILE, OUTPUT_RECIPE, INPUT_RECIPE, &handleRobotProgramState, headless,
133-
std::move(tool_comm_setup), CALIBRATION_CHECKSUM));
134-
135-
// Once RTDE communication is started, we have to make sure to read from the interface buffer, as
136-
// otherwise we will get pipeline overflows. Therefore, do this directly before starting your main
137-
// loop.
138-
g_my_driver->startRTDECommunication();
94+
URCL_LOG_INFO("Starting freedrive mode");
95+
sendFreedriveMessageOrDie(control::FreedriveControlMessage::FREEDRIVE_START);
13996

14097
std::chrono::duration<double> time_done(0);
14198
std::chrono::duration<double> timeout(second_to_run);
14299
auto stopwatch_last = std::chrono::steady_clock::now();
143100
auto stopwatch_now = stopwatch_last;
144-
g_my_driver->writeKeepalive();
145-
sendFreedriveMessageOrDie(control::FreedriveControlMessage::FREEDRIVE_START);
146101

147-
while (true)
102+
while (time_done < timeout || second_to_run.count() == 0)
148103
{
149-
// Read latest RTDE package. This will block for a hard-coded timeout (see UrDriver), so the
150-
// robot will effectively be in charge of setting the frequency of this loop.
151-
// In a real-world application this thread should be scheduled with real-time priority in order
152-
// to ensure that this is called in time.
153-
std::unique_ptr<rtde_interface::DataPackage> data_pkg = g_my_driver->getDataPackage();
154-
if (data_pkg)
155-
{
156-
sendFreedriveMessageOrDie(control::FreedriveControlMessage::FREEDRIVE_NOOP);
157-
158-
if (time_done > timeout && second_to_run.count() != 0)
159-
{
160-
URCL_LOG_INFO("Timeout reached.");
161-
break;
162-
}
163-
}
164-
else
165-
{
166-
URCL_LOG_WARN("Could not get fresh data package from robot");
167-
}
104+
sendFreedriveMessageOrDie(control::FreedriveControlMessage::FREEDRIVE_NOOP);
168105

169106
stopwatch_now = std::chrono::steady_clock::now();
170107
time_done += stopwatch_now - stopwatch_last;
171108
stopwatch_last = stopwatch_now;
172109
}
110+
111+
URCL_LOG_INFO("Stopping freedrive mode");
173112
sendFreedriveMessageOrDie(control::FreedriveControlMessage::FREEDRIVE_STOP);
174113
}
Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
// -- BEGIN LICENSE BLOCK ----------------------------------------------
2+
// Copyright 2025 Universal Robots A/S
3+
//
4+
// Redistribution and use in source and binary forms, with or without
5+
// modification, are permitted provided that the following conditions are met:
6+
//
7+
// * Redistributions of source code must retain the above copyright
8+
// notice, this list of conditions and the following disclaimer.
9+
//
10+
// * Redistributions in binary form must reproduce the above copyright
11+
// notice, this list of conditions and the following disclaimer in the
12+
// documentation and/or other materials provided with the distribution.
13+
//
14+
// * Neither the name of the {copyright_holder} nor the names of its
15+
// contributors may be used to endorse or promote products derived from
16+
// this software without specific prior written permission.
17+
//
18+
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19+
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20+
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21+
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
22+
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23+
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24+
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25+
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26+
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27+
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28+
// POSSIBILITY OF SUCH DAMAGE.
29+
// -- END LICENSE BLOCK ------------------------------------------------
30+
31+
#ifndef UR_CLIENT_LIBRARY_EXAMPLE_ROBOT_WRAPPER_H_INCLUDED
32+
#define UR_CLIENT_LIBRARY_EXAMPLE_ROBOT_WRAPPER_H_INCLUDED
33+
34+
#include <ur_client_library/ur/dashboard_client.h>
35+
#include <ur_client_library/ur/ur_driver.h>
36+
#include <ur_client_library/log.h>
37+
38+
namespace urcl
39+
{
40+
/*!
41+
* \class ExampleRobotWrapper
42+
* \brief This class is a high-level abstraction around UrDriver and DashboardClient. It's main
43+
* purpose is to help us avoiding repetitive robot initialization code in our examples and tests.
44+
*
45+
* It is therefore not intended to be used in production code, but rather as a helper class for
46+
* developers. If you want to use this wrapper in your own code, please make sure to understand the
47+
* logic behind it and adjust it to your needs.
48+
*
49+
* Since this is mainly intended for internal use, don't count on the API being stable for this
50+
* class!
51+
*/
52+
class ExampleRobotWrapper
53+
{
54+
public:
55+
inline static const std::string DEFAULT_ROBOT_IP = "192.168.56.101";
56+
inline static const std::string SCRIPT_FILE = "resources/external_control.urscript";
57+
58+
ExampleRobotWrapper() = delete;
59+
/*!
60+
* \brief Construct a new Example Robot Wrapper object
61+
*
62+
* This will connect to a robot and initialize it. In headless mode the program will be running
63+
* instantly, in teach pendant mode the from \p autostart_program will be started.
64+
*
65+
* Note: RTDE communication has to be started separately.
66+
*
67+
* \param robot_ip IP address of the robot to connect to
68+
* \param output_recipe_file Output recipe file for RTDE communication
69+
* \param input_recipe_file Input recipe file for RTDE communication
70+
* \param headless_mode Should the driver be started in headless mode or not?
71+
* \param autostart_program Program to start automatically after initialization when not in
72+
* headless mode. This flag is ignored in headless mode.
73+
* \param script_file URScript file to send to the robot. That should be script code
74+
* communicating to the driver's reverse interface and trajectory interface.
75+
*/
76+
ExampleRobotWrapper(const std::string& robot_ip, const std::string& output_recipe_file,
77+
const std::string& input_recipe_file, const bool headless_mode = true,
78+
const std::string& autostart_program = "", const std::string& script_file = SCRIPT_FILE);
79+
~ExampleRobotWrapper();
80+
81+
/**
82+
* @brief Initializes the robot in order to be able to start a program.
83+
*
84+
* The robot will be power-cycled once and end up switched on, breaks released.
85+
*/
86+
void initializeRobotWithDashboard();
87+
88+
/**
89+
* @brief Starts RTDE communication with the robot.
90+
*
91+
* @param consume_data Once the RTDE client is started, it's data has to be consumed. If you
92+
* don't actually care about that data, this class can silently consume RTDE data when `true` is
93+
* passed. This can be stopped and started at any time using the startConsumingRTDEData() and
94+
* stopConsumingRTDEData() methods.
95+
*/
96+
void startRTDECommununication(const bool consume_data = false);
97+
98+
/**
99+
* @brief Start consuming RTDE data in the background.
100+
*/
101+
void startConsumingRTDEData();
102+
103+
/**
104+
* @brief Stop consuming RTDE data in the background. Note that data has to be consumed manually
105+
* using readDataPackage().
106+
*/
107+
void stopConsumingRTDEData();
108+
109+
/**
110+
* @brief Get the latest RTDE package.
111+
*
112+
* Do not call this, while RTDE data is being consumed in the background. In doubt, call
113+
* stopConsumingRTDEData() before calling this function.
114+
*
115+
* @param[out] data_pkg The data package will be stored in that object
116+
* @return true on a successful read, false if no package can be read or when RTDE data is
117+
* already being consumed in the background.
118+
*/
119+
bool readDataPackage(std::unique_ptr<rtde_interface::DataPackage>& data_pkg);
120+
121+
/**
122+
* @brief Blocks until there is a robot program connected to the driver's reverse interface or
123+
* until the timeout is hit.
124+
*
125+
* @param milliseconds How long to wait for a successful connection.
126+
* @return True on a successful connection, false if not connection could be detected before the
127+
* timeout.
128+
*/
129+
bool waitForProgramRunning(int milliseconds = 100);
130+
131+
/**
132+
* @brief Blocks until there is a disconnection event from the driver's reverse interface
133+
* detected or until the timeout is hit.
134+
*
135+
* @param milliseconds How long to wait for a disconnection.
136+
* @return True on a disconnection event has been detected, false if no event could be detected before the
137+
* timeout.
138+
*/
139+
bool waitForProgramNotRunning(int milliseconds = 100);
140+
141+
/**
142+
* @brief Depending on whether it is headless or not start autostart_program or call driver's resendRobotProgram
143+
* function.
144+
*
145+
* @return True on successful program start, false otherwise.
146+
*/
147+
bool resendRobotProgram();
148+
149+
/**
150+
* @brief Start the program \p program_file_name on the robot.
151+
*
152+
* The program has be be present on the robot, otherwise this call does not succeed. The robot
153+
* needs to be in remote_control mode for this to work properly.
154+
*
155+
* @param program_file_name Filename on the robot including the ".urp" extension.
156+
* @return True on successful program start, false otherwise.
157+
*/
158+
bool startRobotProgram(const std::string& program_file_name);
159+
160+
std::shared_ptr<urcl::DashboardClient> dashboard_client_; /*!< Dashboard client to interact with the robot */
161+
std::shared_ptr<urcl::UrDriver> ur_driver_; /*!< UR driver to interact with the robot */
162+
163+
private:
164+
void handleRobotProgramState(bool program_running);
165+
166+
std::atomic<bool> rtde_communication_started_ = false;
167+
std::atomic<bool> consume_rtde_packages_ = false;
168+
std::mutex read_package_mutex_;
169+
std::unique_ptr<rtde_interface::DataPackage> data_pkg_;
170+
171+
bool program_running_;
172+
std::condition_variable program_running_cv_;
173+
std::condition_variable program_not_running_cv_;
174+
std::mutex program_running_mutex_;
175+
std::mutex program_not_running_mutex_;
176+
177+
std::thread rtde_consumer_thread_;
178+
179+
bool headless_mode_;
180+
std::string autostart_program_;
181+
};
182+
} // namespace urcl
183+
184+
#endif

0 commit comments

Comments
 (0)