Skip to content

Commit 64e7010

Browse files
authored
Merge branch 'master' into master
2 parents 9413821 + 59db585 commit 64e7010

52 files changed

Lines changed: 1964 additions & 822 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/ci.yml

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ jobs:
4040
- uses: actions/checkout@v6
4141
- name: start ursim
4242
run: |
43-
scripts/start_ursim.sh -m $ROBOT_MODEL -v $URSIM_VERSION -p $PROGRAM_FOLDER -d
43+
scripts/start_ursim.sh -m $ROBOT_MODEL -v $URSIM_VERSION -p $PROGRAM_FOLDER -d -f DISABLED
4444
env:
4545
DOCKER_RUN_OPTS: --network ursim_net
4646
ROBOT_MODEL: ${{matrix.env.ROBOT_MODEL}}
@@ -64,6 +64,7 @@ jobs:
6464
CFLAGS: -g -O2 -fprofile-arcs -ftest-coverage
6565
LDFLAGS: -fprofile-arcs -ftest-coverage
6666
- name: build
67+
id: build
6768
run: cmake --build build --config Debug
6869
- name: Create folder for test artifacts
6970
run: mkdir -p test_artifacts
@@ -75,18 +76,21 @@ jobs:
7576
env:
7677
URSIM_VERSION: ${{matrix.env.URSIM_VERSION}}
7778
- name: Upload test results to Codecov
78-
if: ${{ !cancelled() }}
79+
if: ${{ !cancelled() && steps.build.outcome == 'success' }}
7980
uses: codecov/test-results-action@v1
8081
with:
8182
token: ${{ secrets.CODECOV_TOKEN }}
8283
fail_ci_if_error: true
8384
- name: run examples
8485
run: ./run_examples.sh "192.168.56.101" 1
8586
- name: install gcovr
87+
if: ${{ !cancelled() && steps.build.outcome == 'success' }}
8688
run: sudo apt-get install -y gcovr
8789
- name: gcovr
90+
if: ${{ !cancelled() && steps.build.outcome == 'success' }}
8891
run: cd build && gcovr -r .. --gcov-ignore-parse-errors negative_hits.warn_once_per_file --exclude "../3rdparty"
8992
- name: Upload coverage to Codecov
93+
if: ${{ !cancelled() && steps.build.outcome == 'success' }}
9094
uses: codecov/codecov-action@v5
9195
with:
9296
fail_ci_if_error: true
@@ -108,15 +112,15 @@ jobs:
108112
mkdir -p ursim_logs/flightreports
109113
docker cp ursim:/ursim/flightreports/. ursim_logs/flightreports/
110114
- name: Upload logfiles
111-
uses: actions/upload-artifact@v6
115+
uses: actions/upload-artifact@v7
112116
if: ${{ always() && steps.check_polyscopex.outputs.is_polyscopex == 'false' }}
113117
with:
114118
name: ${{matrix.env.ROBOT_MODEL}}_${{matrix.env.URSIM_VERSION}}_URSim_Logs
115119
path: ursim_logs
116120
if-no-files-found: error
117121
retention-days: 10
118122
- name: Upload test artifacts
119-
uses: actions/upload-artifact@v6
123+
uses: actions/upload-artifact@v7
120124
if: ${{ always() && steps.check_polyscopex.outputs.is_polyscopex == 'false' }}
121125
with:
122126
name: ${{matrix.env.ROBOT_MODEL}}_${{matrix.env.URSIM_VERSION}}_test_artifacts

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ add_library(urcl
4343
src/rtde/get_urcontrol_version.cpp
4444
src/rtde/request_protocol_version.cpp
4545
src/rtde/rtde_package.cpp
46+
src/rtde/rtde_parser.cpp
4647
src/rtde/text_message.cpp
4748
src/rtde/rtde_client.cpp
4849
src/ur/ur_driver.cpp

doc/architecture/rtde_client.rst

Lines changed: 44 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,34 @@ RTDEClient
77

88
The Real Time Data Exchange Client, ``RTDEClient``, class serves as a standalone
99
`RTDE <https://www.universal-robots.com/articles/ur-articles/real-time-data-exchange-rtde-guide/>`_
10-
client. To use the RTDE-Client, you'll have to initialize and start it separately:
10+
client. To use the RTDE-Client, you'll have to initialize and start it separately. When starting
11+
it, it can be chosen whether data should be read in a background thread or if the user has to poll
12+
data in each cycle.
13+
14+
- **Background read**: When background read is enabled (``start(true)``) (default), the RTDE client
15+
will start a background thread that continuously reads data from the robot. The latest data
16+
package can be fetched using the ``getDataPackage()`` method. This method returns immediately
17+
with the latest data package received from the robot. If no data has been received since last
18+
calling this function, it will block for a specified timeout waiting for new data to arrive.
19+
20+
- **Blocking synchronous read**: When background read is not enabled (``start(false)``), data can
21+
(and has to be) fetched using the ``getDataPackageBlocking()`` method. This call waits for a new
22+
data package to arrive and parses that into the passed ``DataPackage`` object. This has to be
23+
called with the RTDE control frequency, as the robot will shutdown RTDE communication if data is
24+
not read by the client.
25+
26+
The following example uses the background read method to fetch data from the RTDE interface. See
27+
the :ref:`rtde_client_example` for an example of the blocking read method.
1128

1229
.. code-block:: c++
1330

1431
rtde_interface::RTDEClient my_client(ROBOT_IP, notifier, OUTPUT_RECIPE_FILE, INPUT_RECIPE_FILE);
1532
my_client.init();
16-
my_client.start();
33+
my_client.start(true); // Start background read
34+
rtde_interface::DataPackage data_pkg(my_client.getOutputRecipe());
1735
while (true)
1836
{
19-
std::unique_ptr<rtde_interface::DataPackage> data_pkg = my_client.getDataPackage(READ_TIMEOUT);
20-
if (data_pkg)
37+
if (my_client.getDataPackage(data_pkg, READ_TIMEOUT))
2138
{
2239
std::cout << data_pkg->toString() << std::endl;
2340
}
@@ -28,27 +45,32 @@ outputs. Please refer to the `RTDE
2845
guide <https://www.universal-robots.com/articles/ur-articles/real-time-data-exchange-rtde-guide/>`_
2946
on which elements are available.
3047

31-
.. note::
48+
The recipes can be either passed as a filename or as a list of strings directly. E.g. the
49+
following will work
3250

33-
The recipes can be either passed as a filename or as a list of strings directly. E.g. the
34-
following will work
51+
.. code-block:: c++
52+
53+
rtde_interface::RTDEClient my_client(
54+
ROBOT_IP,
55+
notifier,
56+
{"timestamp", "actual_q"},
57+
{"speed_slider_mask", "speed_slider_fraction"}
58+
);
59+
60+
.. note::
61+
``timestamp`` will always be a part of the output recipe and will be added afterwards, if not defined. As the ``timestamp`` used for verifying the connectivity.
3562

36-
.. code-block:: c++
63+
Reading data
64+
------------
3765

38-
rtde_interface::RTDEClient my_client(
39-
ROBOT_IP,
40-
notifier,
41-
{"timestamp", "actual_q"},
42-
{"speed_slider_mask", "speed_slider_fraction"}
43-
);
66+
After calling ``my_client.start()``, data can be read from the
67+
``RTDEClient`` by calling ``getDataPackage()`` (with background thread running) or ``getDataPackageBlocking()`` (without background thread running) respectively.
4468

45-
Inside the ``RTDEclient`` data is received in a separate thread, parsed by the ``RTDEParser`` and
46-
added to a pipeline queue.
69+
Remember that, when not using a background thread, data has to be polled regularly, as the robot
70+
will shutdown RTDE communication if the receiving side doesn't empty its buffer.
4771

48-
Right after calling ``my_client.start()``, it should be made sure to read the buffer from the
49-
``RTDEClient`` by calling ``getDataPackage()`` frequently. The Client's queue can only contain a
50-
restricted number of items at a time, so a ``Pipeline producer overflowed!`` error will be raised
51-
if the buffer isn't read frequently enough.
72+
Writing data
73+
------------
5274

5375
For writing data to the RTDE interface, use the ``RTDEWriter`` member of the ``RTDEClient``. It can be
5476
retrieved by calling ``getWriter()`` method. The ``RTDEWriter`` provides convenience methods to write
@@ -83,11 +105,11 @@ an empty input recipe, like this:
83105
// Alternatively, pass an empty filename when using recipe files
84106
// rtde_interface::RTDEClient my_client(ROBOT_IP, notifier, OUTPUT_RECIPE_FILE, "");
85107
my_client.init();
108+
auto data_pkg = std::make_unique<rtde_interface::DataPackage>(my_client->getOutputRecipe());
86109
my_client.start();
87110
while (true)
88111
{
89-
std::unique_ptr<rtde_interface::DataPackage> data_pkg = my_client.getDataPackage(READ_TIMEOUT);
90-
if (data_pkg)
112+
if (my_client.getDataPackage(data_package, READ_TIMEOUT))
91113
{
92114
std::cout << data_pkg->toString() << std::endl;
93115
}

doc/examples/direct_torque_control.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,4 +77,4 @@ To send the control command, the robot's :ref:`reverse_interface` is used via th
7777
:linenos:
7878
:lineno-match:
7979
:start-at: // Setting the RobotReceiveTimeout
80-
:end-before: URCL_LOG_DEBUG("data_pkg:\n%s", data_pkg->toString().c_str());
80+
:end-before: URCL_LOG_DEBUG("data_pkg:\n%s", data_pkg.toString().c_str());

doc/examples/rtde_client.rst

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,6 @@ to initialize the RTDE client.
2525
:start-at: const std::string OUTPUT_RECIPE
2626
:end-at: const std::string INPUT_RECIPE
2727

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-
3328
Creating an RTDE Client
3429
-----------------------
3530

@@ -45,25 +40,32 @@ omitted, RTDE communication will be established at the robot's control frequency
4540
:start-at: comm::INotifier notifier;
4641
:end-at: my_client.init();
4742

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-
5143

5244
Reading data from the RTDE client
5345
---------------------------------
5446

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.
47+
To read data received by the RTDE client, it has to be polled. See the :ref:`rtde_client` section
48+
for details on two possible strategies. In this example, we do not use background read and instead
49+
fetch data synchronously. Hence, we pass ``false`` to the ``start()`` method.
5850

5951
.. literalinclude:: ../../examples/rtde_client.cpp
6052
:language: c++
6153
:caption: examples/rtde_client.cpp
6254
:linenos:
6355
:lineno-match:
64-
:start-at: // Once RTDE communication is started
56+
:start-at: auto data_pkg = std::make_unique<rtde_interface::DataPackage>(my_client.getOutputRecipe());
6557
:end-before: // Change the speed slider
6658

59+
In our main loop, we wait for a new data package to arrive using the blocking read method. Once
60+
received, data from the received package can be accessed using the ``getData()`` method of the
61+
``DataPackage`` object. This method takes the key of the data to be accessed as a parameter and
62+
returns the corresponding value.
63+
64+
.. note:: The key used to access data has to be part of the output recipe used to initialize the RTDE
65+
client. Passing a string literal, e.g. ``"actual_q"``, is possible but not recommended as it is
66+
converted to an ``std::string`` automatically, causing heap allocations which should be avoided
67+
in Real-Time contexts.
68+
6769
Writing Data to the RTDE client
6870
-------------------------------
6971

@@ -84,6 +86,12 @@ initialize the RTDE client has to contain the keys necessary to send that specif
8486
:end-at: }
8587

8688

87-
.. note:: Many RTDE inputs require setting up the data key and a mask key. See the `RTDE guide
89+
.. note:: Many RTDE inputs require setting up the data key and a mask key. That is done
90+
internally, but the mask keys have to be part of the input recipe, as well. See the `RTDE guide
8891
<https://www.universal-robots.com/articles/ur/interface-communication/real-time-data-exchange-rtde-guide/>`_
8992
for more information.
93+
94+
.. note:: Every ``send...`` call to the RTDEWriter triggers a package sent to the robot. If you
95+
want to modify more than one input at a time, it is recommended to use the ``sendPackage()``
96+
method. That allows setting up the complete data package with its input recipe and sending that
97+
to the robot at once.

doc/examples/ur_driver.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,4 +70,4 @@ To send the control command, the robot's :ref:`reverse_interface` is used via th
7070
:linenos:
7171
:lineno-match:
7272
:start-at: // Setting the RobotReceiveTimeout
73-
:end-before: URCL_LOG_DEBUG("data_pkg:\n%s", data_pkg->toString().c_str());
73+
:end-before: URCL_LOG_DEBUG("data_pkg:\n%s", data_pkg.toString().c_str());

examples/direct_torque_control.cpp

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -96,20 +96,22 @@ int main(int argc, char* argv[])
9696
// loop.
9797
g_my_robot->getUrDriver()->startRTDECommunication();
9898
auto start_time = std::chrono::system_clock::now();
99+
100+
urcl::rtde_interface::DataPackage data_pkg(g_my_robot->getUrDriver()->getRTDEOutputRecipe());
101+
99102
while (!(passed_positive_part && passed_negative_part))
100103
{
101104
// Read latest RTDE package. This will block for a hard-coded timeout (see UrDriver), so the
102105
// robot will effectively be in charge of setting the frequency of this loop.
103106
// In a real-world application this thread should be scheduled with real-time priority in order
104107
// to ensure that this is called in time.
105-
std::unique_ptr<urcl::rtde_interface::DataPackage> data_pkg = g_my_robot->getUrDriver()->getDataPackage();
106-
if (!data_pkg)
108+
if (!g_my_robot->getUrDriver()->getDataPackage(data_pkg))
107109
{
108110
URCL_LOG_WARN("Could not get fresh data package from robot");
109111
return 1;
110112
}
111113
// Read current joint positions from robot data
112-
if (!data_pkg->getData("actual_q", g_joint_positions))
114+
if (!data_pkg.getData("actual_q", g_joint_positions))
113115
{
114116
// This throwing should never happen unless misconfigured
115117
std::string error_msg = "Did not find 'actual_q' in data sent from robot. This should not happen!";
@@ -146,7 +148,7 @@ int main(int argc, char* argv[])
146148
URCL_LOG_ERROR("Could not send joint command. Is the robot in remote control?");
147149
return 1;
148150
}
149-
URCL_LOG_DEBUG("data_pkg:\n%s", data_pkg->toString().c_str());
151+
URCL_LOG_DEBUG("data_pkg:\n%s", data_pkg.toString().c_str());
150152
if (second_to_run.count() > 0 && (std::chrono::system_clock::now() - start_time) > second_to_run)
151153
{
152154
URCL_LOG_WARN("Time limit reached, stopping movement. This is expected on a simualted robot, as it doesn't move "

examples/external_fts_through_rtde.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -211,11 +211,12 @@ void rtdeWorker(const int second_to_run)
211211

212212
vector6d_t actual_tcp_force;
213213
auto start_time = std::chrono::steady_clock::now();
214+
std::unique_ptr<rtde_interface::DataPackage> data_pkg =
215+
std::make_unique<rtde_interface::DataPackage>(g_my_robot->getUrDriver()->getRTDEOutputRecipe());
214216
while (g_RUNNING)
215217
{
216218
urcl::vector6d_t local_ft_vec = g_FT_VEC;
217-
std::unique_ptr<rtde_interface::DataPackage> data_pkg = g_my_robot->getUrDriver()->getDataPackage();
218-
if (data_pkg)
219+
if (g_my_robot->getUrDriver()->getDataPackageBlocking(data_pkg))
219220
{
220221
// Data fields in the data package are accessed by their name. Only names present in the
221222
// output recipe can be accessed. Otherwise this function will return false.

examples/full_driver.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -87,20 +87,20 @@ int main(int argc, char* argv[])
8787
// otherwise we will get pipeline overflows. Therefor, do this directly before starting your main
8888
// loop.
8989
g_my_robot->getUrDriver()->startRTDECommunication();
90+
rtde_interface::DataPackage data_pkg(g_my_robot->getUrDriver()->getRTDEOutputRecipe());
9091
while (!(passed_positive_part && passed_negative_part))
9192
{
9293
// Read latest RTDE package. This will block for a hard-coded timeout (see UrDriver), so the
9394
// robot will effectively be in charge of setting the frequency of this loop.
9495
// In a real-world application this thread should be scheduled with real-time priority in order
9596
// to ensure that this is called in time.
96-
std::unique_ptr<rtde_interface::DataPackage> data_pkg = g_my_robot->getUrDriver()->getDataPackage();
97-
if (!data_pkg)
97+
if (!g_my_robot->getUrDriver()->getDataPackage(data_pkg))
9898
{
9999
URCL_LOG_WARN("Could not get fresh data package from robot");
100100
return 1;
101101
}
102102
// Read current joint positions from robot data
103-
if (!data_pkg->getData("actual_q", g_joint_positions))
103+
if (!data_pkg.getData("actual_q", g_joint_positions))
104104
{
105105
// This throwing should never happen unless misconfigured
106106
std::string error_msg = "Did not find 'actual_q' in data sent from robot. This should not happen!";
@@ -140,7 +140,7 @@ int main(int argc, char* argv[])
140140
URCL_LOG_ERROR("Could not send joint command. Is the robot in remote control?");
141141
return 1;
142142
}
143-
URCL_LOG_DEBUG("data_pkg:\n%s", data_pkg->toString().c_str());
143+
URCL_LOG_DEBUG("data_pkg:\n%s", data_pkg.toString().c_str());
144144
}
145145
g_my_robot->getUrDriver()->stopControl();
146146
URCL_LOG_INFO("Movement done");

examples/rtde_client.cpp

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,9 @@ using namespace urcl;
3939
const std::string DEFAULT_ROBOT_IP = "192.168.56.101";
4040
const std::string OUTPUT_RECIPE = "examples/resources/rtde_output_recipe.txt";
4141
const std::string INPUT_RECIPE = "examples/resources/rtde_input_recipe.txt";
42-
const std::chrono::milliseconds READ_TIMEOUT{ 100 };
42+
43+
// Preallocation of string to avoid allocation in main loop
44+
const std::string TARGET_SPEED_FRACTION = "target_speed_fraction";
4345

4446
void printFraction(const double fraction, const std::string& label, const size_t width = 20)
4547
{
@@ -81,28 +83,27 @@ int main(int argc, char* argv[])
8183
double target_speed_fraction = 1.0;
8284
double speed_slider_increment = 0.01;
8385

86+
auto data_pkg = std::make_unique<rtde_interface::DataPackage>(my_client.getOutputRecipe());
8487
// Once RTDE communication is started, we have to make sure to read from the interface buffer, as
8588
// otherwise we will get pipeline overflows. Therefor, do this directly before starting your main
8689
// loop.
87-
my_client.start();
90+
my_client.start(false); // false -> do not start background read thread.
8891

8992
auto start_time = std::chrono::steady_clock::now();
9093
while (second_to_run <= 0 ||
9194
std::chrono::duration_cast<std::chrono::seconds>(std::chrono::steady_clock::now() - start_time).count() <
9295
second_to_run)
9396
{
94-
// Read latest RTDE package. This will block for READ_TIMEOUT, so the
95-
// robot will effectively be in charge of setting the frequency of this loop unless RTDE
96-
// communication doesn't work in which case the user will be notified.
97-
// In a real-world application this thread should be scheduled with real-time priority in order
98-
// to ensure that this is called in time.
99-
std::unique_ptr<rtde_interface::DataPackage> data_pkg = my_client.getDataPackage(READ_TIMEOUT);
100-
if (data_pkg)
97+
// Wait for a DataPackage. In a real-world application this thread should be scheduled with real-time priority in
98+
// order to ensure that this is called in time.
99+
bool success = my_client.getDataPackageBlocking(data_pkg);
100+
if (success)
101101
{
102102
// Data fields in the data package are accessed by their name. Only names present in the
103103
// 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");
104+
// We preallocated the string TARGET_SPEED_FRACTION to avoid allocations in the main loop.
105+
data_pkg->getData(TARGET_SPEED_FRACTION, target_speed_fraction);
106+
printFraction(target_speed_fraction, TARGET_SPEED_FRACTION);
106107
}
107108
else
108109
{
@@ -139,5 +140,7 @@ int main(int argc, char* argv[])
139140
// Resetting the speedslider back to 100%
140141
my_client.getWriter().sendSpeedSlider(1);
141142

143+
URCL_LOG_INFO("Exiting RTDE read/write example.");
144+
142145
return 0;
143146
}

0 commit comments

Comments
 (0)