Skip to content

Commit f112d72

Browse files
committed
Add example documentation for RTDE client
1 parent aa97110 commit f112d72

File tree

3 files changed

+126
-13
lines changed

3 files changed

+126
-13
lines changed

doc/examples.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,5 @@ may be running forever until manually stopped.
2020
examples/instruction_executor
2121
examples/primary_pipeline
2222
examples/primary_pipeline_calibration
23+
examples/rtde_client
2324
examples/ur_driver

doc/examples/rtde_client.rst

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
:github_url: https://github.com/UniversalRobots/Universal_Robots_Client_Library/blob/master/doc/examples/rtde_client.rst
2+
3+
.. _rtde_client_example:
4+
5+
RTDE Client example
6+
===================
7+
8+
This example shows how to use the ``RTDEClient`` class for the robot's `Real-Time Data Exchange
9+
(RTDE) interface
10+
<https://www.universal-robots.com/articles/ur/interface-communication/real-time-data-exchange-rtde-guide/>`_.
11+
12+
The RTDE client has to be initialized with a list of keys that should be streamed from the robot
13+
and a list of keys that should be sent to the robot. The client will then start a background thread
14+
establishing communication.
15+
16+
In this example, those keys are stored in two text files relative to this repository's root:
17+
``rtde_input_keys.txt`` and ``rtde_output_keys.txt``. The example will read those files and use them
18+
to initialize the RTDE client.
19+
20+
.. literalinclude:: ../../examples/rtde_client.cpp
21+
:language: c++
22+
:caption: examples/rtde_client.cpp
23+
:linenos:
24+
:lineno-match:
25+
:start-at: const std::string OUTPUT_RECIPE
26+
:end-at: const std::string INPUT_RECIPE
27+
28+
29+
Internally, the RTDE client uses the same producer / consumer architecture as show in the
30+
:ref:`primary_pipeline_example` example. However, it doesn't have a consumer thread, so data has to
31+
be read by the user to avoid the pipeline's queue from overflowing.
32+
33+
Creating an RTDE Client
34+
-----------------------
35+
36+
An RTDE client can be directly created passing the robot's IP address, a ``INotifier`` object, an
37+
output and an input recipe. Optionally, a communication frequency can be passed as well. If that is
38+
omitted, RTDE communication will be established at the robot's control frequency.
39+
40+
.. literalinclude:: ../../examples/rtde_client.cpp
41+
:language: c++
42+
:caption: examples/rtde_client.cpp
43+
:linenos:
44+
:lineno-match:
45+
:start-at: comm::INotifier notifier;
46+
:end-at: my_client.init();
47+
48+
An RTDE data package containing every key-value pair from the output recipe can be fetched using
49+
the ``getDataPackage()`` method. This method will block until a new package is available.
50+
51+
52+
Reading data from the RTDE client
53+
---------------------------------
54+
55+
Once the RTDE client is initialized, we'll have to start communication separately. As mentioned
56+
above, we'll have to read data from the client once communication is started, hence we start
57+
communication right before a loop reading data.
58+
59+
.. literalinclude:: ../../examples/rtde_client.cpp
60+
:language: c++
61+
:caption: examples/rtde_client.cpp
62+
:linenos:
63+
:lineno-match:
64+
:start-at: // Once RTDE communication is started
65+
:end-before: // Change the speed slider
66+
67+
Writing Data to the RTDE client
68+
-------------------------------
69+
70+
In this example, we use the RTDE client to oscillate the speed slider on the teach pendant between
71+
0 and 1. While this doesn't bear any practical use it shows how sending data to the RTDE interface
72+
works.
73+
74+
To send data to the RTDE client, we can use ``RTDEWriter`` object stored in the RTDE client. This
75+
has methods implemented for each data type that can be sent to the robot. The input recipe used to
76+
initialize the RTDE client has to contain the keys necessary to send that specific data.
77+
78+
.. literalinclude:: ../../examples/rtde_client.cpp
79+
:language: c++
80+
:caption: examples/rtde_client.cpp
81+
:linenos:
82+
:lineno-match:
83+
:start-at: my_client.getWriter().sendSpeedSlider
84+
:end-at: }
85+
86+
87+
.. note:: Many RTDE inputs require setting up the data key and a mask key. See the `RTDE guide
88+
<https://www.universal-robots.com/articles/ur/interface-communication/real-time-data-exchange-rtde-guide/>`_
89+
for more information.

examples/rtde_client.cpp

Lines changed: 36 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727

2828
#include <ur_client_library/rtde/rtde_client.h>
2929

30+
#include <cmath>
3031
#include <iostream>
3132
#include <memory>
3233
#include <ctime>
@@ -40,6 +41,20 @@ const std::string OUTPUT_RECIPE = "examples/resources/rtde_output_recipe.txt";
4041
const std::string INPUT_RECIPE = "examples/resources/rtde_input_recipe.txt";
4142
const std::chrono::milliseconds READ_TIMEOUT{ 100 };
4243

44+
void printFraction(const double fraction, const std::string& label, const size_t width = 20)
45+
{
46+
std::cout << "\r" << label << ": [";
47+
for (size_t i = 0; i < std::ceil(fraction * width); i++)
48+
{
49+
std::cout << "#";
50+
}
51+
for (size_t i = 0; i < std::floor((1.0 - fraction) * width); i++)
52+
{
53+
std::cout << "-";
54+
}
55+
std::cout << "]" << std::flush;
56+
}
57+
4358
int main(int argc, char* argv[])
4459
{
4560
// Parse the ip arguments if given
@@ -56,22 +71,25 @@ int main(int argc, char* argv[])
5671
second_to_run = std::stoi(argv[2]);
5772
}
5873

59-
// TODO: Write good docstring for notifier
6074
comm::INotifier notifier;
61-
rtde_interface::RTDEClient my_client(robot_ip, notifier, OUTPUT_RECIPE, INPUT_RECIPE);
75+
const double rtde_frequency = 50; // Hz
76+
rtde_interface::RTDEClient my_client(robot_ip, notifier, OUTPUT_RECIPE, INPUT_RECIPE, rtde_frequency);
6277
my_client.init();
6378

6479
// We will use the speed_slider_fraction as an example how to write to RTDE
6580
double speed_slider_fraction = 1.0;
81+
double target_speed_fraction = 1.0;
6682
double speed_slider_increment = 0.01;
6783

6884
// Once RTDE communication is started, we have to make sure to read from the interface buffer, as
6985
// otherwise we will get pipeline overflows. Therefor, do this directly before starting your main
7086
// loop.
7187
my_client.start();
7288

73-
unsigned long start_time = clock();
74-
while (second_to_run < 0 || ((clock() - start_time) / CLOCKS_PER_SEC) < static_cast<unsigned int>(second_to_run))
89+
auto start_time = std::chrono::steady_clock::now();
90+
while (second_to_run <= 0 ||
91+
std::chrono::duration_cast<std::chrono::seconds>(std::chrono::steady_clock::now() - start_time).count() <
92+
second_to_run)
7593
{
7694
// Read latest RTDE package. This will block for READ_TIMEOUT, so the
7795
// robot will effectively be in charge of setting the frequency of this loop unless RTDE
@@ -81,22 +99,19 @@ int main(int argc, char* argv[])
8199
std::unique_ptr<rtde_interface::DataPackage> data_pkg = my_client.getDataPackage(READ_TIMEOUT);
82100
if (data_pkg)
83101
{
84-
std::cout << data_pkg->toString() << std::endl;
102+
// Data fields in the data package are accessed by their name. Only names present in the
103+
// output recipe can be accessed. Otherwise this function will return false.
104+
data_pkg->getData("target_speed_fraction", target_speed_fraction);
105+
printFraction(target_speed_fraction, "target_speed_fraction");
85106
}
86107
else
87108
{
109+
// The client isn't connected properly anymore / doesn't receive any data anymore. Stop the
110+
// program.
88111
std::cout << "Could not get fresh data package from robot" << std::endl;
89112
return 1;
90113
}
91114

92-
if (!my_client.getWriter().sendSpeedSlider(speed_slider_fraction))
93-
{
94-
// This will happen for example, when the required keys are not configured inside the input
95-
// recipe.
96-
std::cout << "\033[1;31mSending RTDE data failed." << "\033[0m\n" << std::endl;
97-
return 1;
98-
}
99-
100115
// Change the speed slider so that it will move between 0 and 1 all the time. This is for
101116
// demonstration purposes only and gains no real value.
102117
if (speed_slider_increment > 0)
@@ -111,6 +126,14 @@ int main(int argc, char* argv[])
111126
speed_slider_increment *= -1;
112127
}
113128
speed_slider_fraction += speed_slider_increment;
129+
130+
if (!my_client.getWriter().sendSpeedSlider(speed_slider_fraction))
131+
{
132+
// This will happen for example, when the required keys are not configured inside the input
133+
// recipe.
134+
std::cout << "\033[1;31mSending RTDE data failed." << "\033[0m\n" << std::endl;
135+
return 1;
136+
}
114137
}
115138

116139
// Resetting the speedslider back to 100%

0 commit comments

Comments
 (0)