Skip to content

Commit fc79456

Browse files
Adapt RTDE output recipe based on robot response (#221)
Not all RTDE outputs are available in all SW versions. When working with a fleet of robots with heterogeneous SW versions it's not practical to have multiple RTDE output recipes to account for this. This PR aims at solving this problem by adapting the RTDE output recipe on-the-fly based on the robot response to the RTDE request. Outputs which are not available for this particular SW version are simply removed from the output recipe and a trimmed down output recipe is sent to the robot. Potential issues / improvements: - variables removed from the output recipe only cause a warning. When these variables are "optional" this is a fine behaviour but it could potentially be masking incorrect variable names. Hence, this is an opt-in feature. - an alternate implementation could be to hardcode the compatibility information for RTDE outputs in `data_package.cpp` and validate the output recipe beforehand. This seems less flexible though. - the same logic could potentially be applied to the input recipe. Having optional inputs seems less likely though.
1 parent bbac0b2 commit fc79456

File tree

7 files changed

+500
-435
lines changed

7 files changed

+500
-435
lines changed

include/ur_client_library/comm/pipeline.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ template <typename T>
4343
class IConsumer
4444
{
4545
public:
46+
virtual ~IConsumer() = default;
47+
4648
/*!
4749
* \brief Set-up functionality of the consumer.
4850
*/
@@ -170,6 +172,8 @@ template <typename T>
170172
class IProducer
171173
{
172174
public:
175+
virtual ~IProducer() = default;
176+
173177
/*!
174178
* \brief Set-up functionality of the producers.
175179
*

include/ur_client_library/rtde/rtde_client.h

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@
2929
#ifndef UR_CLIENT_LIBRARY_RTDE_CLIENT_H_INCLUDED
3030
#define UR_CLIENT_LIBRARY_RTDE_CLIENT_H_INCLUDED
3131

32+
#include <memory>
33+
3234
#include "ur_client_library/comm/pipeline.h"
3335
#include "ur_client_library/rtde/package_header.h"
3436
#include "ur_client_library/rtde/rtde_package.h"
@@ -102,9 +104,12 @@ class RTDEClient
102104
* \param output_recipe_file Path to the file containing the output recipe
103105
* \param input_recipe_file Path to the file containing the input recipe
104106
* \param target_frequency Frequency to run at. Defaults to 0.0 which means maximum frequency.
107+
* \param ignore_unavailable_outputs Configure the behaviour when a variable of the output recipe is not available
108+
* from the robot: output is silently ignored if true, a UrException is raised otherwise.
105109
*/
106110
RTDEClient(std::string robot_ip, comm::INotifier& notifier, const std::string& output_recipe_file,
107-
const std::string& input_recipe_file, double target_frequency = 0.0);
111+
const std::string& input_recipe_file, double target_frequency = 0.0,
112+
bool ignore_unavailable_outputs = false);
108113

109114
/*!
110115
* \brief Creates a new RTDEClient object, including a used URStream and Pipeline to handle the
@@ -115,9 +120,12 @@ class RTDEClient
115120
* \param output_recipe Vector containing the output recipe
116121
* \param input_recipe Vector containing the input recipe
117122
* \param target_frequency Frequency to run at. Defaults to 0.0 which means maximum frequency.
123+
* \param ignore_unavailable_outputs Configure the behaviour when a variable of the output recipe is not available
124+
* from the robot: output is silently ignored if true, a UrException is raised otherwise.
118125
*/
119126
RTDEClient(std::string robot_ip, comm::INotifier& notifier, const std::vector<std::string>& output_recipe,
120-
const std::vector<std::string>& input_recipe, double target_frequency = 0.0);
127+
const std::vector<std::string>& input_recipe, double target_frequency = 0.0,
128+
bool ignore_unavailable_outputs = false);
121129
~RTDEClient();
122130
/*!
123131
* \brief Sets up RTDE communication with the robot. The handshake includes negotiation of the
@@ -210,10 +218,12 @@ class RTDEClient
210218
private:
211219
comm::URStream<RTDEPackage> stream_;
212220
std::vector<std::string> output_recipe_;
221+
bool ignore_unavailable_outputs_;
213222
std::vector<std::string> input_recipe_;
214223
RTDEParser parser_;
215-
comm::URProducer<RTDEPackage> prod_;
216-
comm::Pipeline<RTDEPackage> pipeline_;
224+
std::unique_ptr<comm::URProducer<RTDEPackage>> prod_;
225+
comm::INotifier notifier_;
226+
std::unique_ptr<comm::Pipeline<RTDEPackage>> pipeline_;
217227
RTDEWriter writer_;
218228

219229
VersionInformation urcontrol_version_;
@@ -241,6 +251,14 @@ class RTDEClient
241251
void setupInputs();
242252
void disconnect();
243253

254+
/*!
255+
* \brief Updates the output recipe to the given one and recreates all the objects which depend on it.
256+
* It should only be called while setting up the communication.
257+
*
258+
* \param new_recipe the new output recipe to use
259+
*/
260+
void resetOutputRecipe(const std::vector<std::string> new_recipe);
261+
244262
/*!
245263
* \brief Checks whether the robot is booted, this is done by looking at the timestamp from the robot controller, this
246264
* will show the time in seconds since the controller was started. If the timestamp is below 40, we will read from

include/ur_client_library/ur/ur_driver.h

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -605,10 +605,13 @@ class UrDriver
605605
*
606606
* \param output_recipe Vector containing the output recipe
607607
* \param input_recipe Vector containing the input recipe
608-
* \param target_frequency Frequency to run the RTDE client at. Defaults to 0.0 which means maximum frequency.
608+
* \param target_frequency
609+
* Frequency to run the RTDE client at. Defaults to 0.0 which means maximum frequency.
610+
* \param ignore_unavailable_outputs Configure the behaviour when a variable of the output recipe is not available
611+
* from the robot: output is silently ignored if true, a UrException is raised otherwise.
609612
*/
610613
void resetRTDEClient(const std::vector<std::string>& output_recipe, const std::vector<std::string>& input_recipe,
611-
double target_frequency = 0.0);
614+
double target_frequency = 0.0, bool ignore_unavailable_outputs = false);
612615

613616
/**
614617
* \brief Reset the RTDE client. As during initialization the driver will start RTDE communication
@@ -621,9 +624,11 @@ class UrDriver
621624
* \param output_recipe_filename Filename where the output recipe is stored in.
622625
* \param input_recipe_filename Filename where the input recipe is stored in.
623626
* \param target_frequency Frequency to run the RTDE client at. Defaults to 0.0 which means maximum frequency.
627+
* \param ignore_unavailable_outputs Configure the behaviour when a variable of the output recipe is not available
628+
* from the robot: output is silently ignored if true, a UrException is raised otherwise.
624629
*/
625630
void resetRTDEClient(const std::string& output_recipe_filename, const std::string& input_recipe_filename,
626-
double target_frequency = 0.0);
631+
double target_frequency = 0.0, bool ignore_unavailable_outputs = false);
627632

628633
private:
629634
static std::string readScriptFile(const std::string& filename);

src/rtde/rtde_client.cpp

Lines changed: 96 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,15 @@ namespace urcl
3535
namespace rtde_interface
3636
{
3737
RTDEClient::RTDEClient(std::string robot_ip, comm::INotifier& notifier, const std::string& output_recipe_file,
38-
const std::string& input_recipe_file, double target_frequency)
38+
const std::string& input_recipe_file, double target_frequency, bool ignore_unavailable_outputs)
3939
: stream_(robot_ip, UR_RTDE_PORT)
4040
, output_recipe_(ensureTimestampIsPresent(readRecipe(output_recipe_file)))
41+
, ignore_unavailable_outputs_(ignore_unavailable_outputs)
4142
, input_recipe_(readRecipe(input_recipe_file))
4243
, parser_(output_recipe_)
43-
, prod_(stream_, parser_)
44-
, pipeline_(prod_, PIPELINE_NAME, notifier, true)
44+
, prod_(std::make_unique<comm::URProducer<RTDEPackage>>(stream_, parser_))
45+
, notifier_(notifier)
46+
, pipeline_(std::make_unique<comm::Pipeline<RTDEPackage>>(*prod_, PIPELINE_NAME, notifier, true))
4547
, writer_(&stream_, input_recipe_)
4648
, max_frequency_(URE_MAX_FREQUENCY)
4749
, target_frequency_(target_frequency)
@@ -50,13 +52,16 @@ RTDEClient::RTDEClient(std::string robot_ip, comm::INotifier& notifier, const st
5052
}
5153

5254
RTDEClient::RTDEClient(std::string robot_ip, comm::INotifier& notifier, const std::vector<std::string>& output_recipe,
53-
const std::vector<std::string>& input_recipe, double target_frequency)
55+
const std::vector<std::string>& input_recipe, double target_frequency,
56+
bool ignore_unavailable_outputs)
5457
: stream_(robot_ip, UR_RTDE_PORT)
5558
, output_recipe_(ensureTimestampIsPresent(output_recipe))
59+
, ignore_unavailable_outputs_(ignore_unavailable_outputs)
5660
, input_recipe_(input_recipe)
5761
, parser_(output_recipe_)
58-
, prod_(stream_, parser_)
59-
, pipeline_(prod_, PIPELINE_NAME, notifier, true)
62+
, prod_(std::make_unique<comm::URProducer<RTDEPackage>>(stream_, parser_))
63+
, notifier_(notifier)
64+
, pipeline_(std::make_unique<comm::Pipeline<RTDEPackage>>(*prod_, PIPELINE_NAME, notifier, true))
6065
, writer_(&stream_, input_recipe_)
6166
, max_frequency_(URE_MAX_FREQUENCY)
6267
, target_frequency_(target_frequency)
@@ -96,8 +101,8 @@ void RTDEClient::setupCommunication(const size_t max_num_tries, const std::chron
96101
{
97102
client_state_ = ClientState::INITIALIZING;
98103
// A running pipeline is needed inside setup
99-
pipeline_.init(max_num_tries, reconnection_time);
100-
pipeline_.run();
104+
pipeline_->init(max_num_tries, reconnection_time);
105+
pipeline_->run();
101106

102107
uint16_t protocol_version = MAX_RTDE_PROTOCOL_VERSION;
103108
while (!negotiateProtocolVersion(protocol_version) && client_state_ == ClientState::INITIALIZING)
@@ -151,7 +156,7 @@ void RTDEClient::setupCommunication(const size_t max_num_tries, const std::chron
151156
return;
152157

153158
// We finished communication for now
154-
pipeline_.stop();
159+
pipeline_->stop();
155160
client_state_ = ClientState::INITIALIZED;
156161
}
157162

@@ -174,7 +179,7 @@ bool RTDEClient::negotiateProtocolVersion(const uint16_t protocol_version)
174179
while (num_retries < MAX_REQUEST_RETRIES)
175180
{
176181
std::unique_ptr<RTDEPackage> package;
177-
if (!pipeline_.getLatestProduct(package, std::chrono::milliseconds(1000)))
182+
if (!pipeline_->getLatestProduct(package, std::chrono::milliseconds(1000)))
178183
{
179184
URCL_LOG_ERROR("failed to get package from rtde interface, disconnecting");
180185
disconnect();
@@ -220,7 +225,7 @@ void RTDEClient::queryURControlVersion()
220225
std::unique_ptr<RTDEPackage> package;
221226
while (num_retries < MAX_REQUEST_RETRIES)
222227
{
223-
if (!pipeline_.getLatestProduct(package, std::chrono::milliseconds(1000)))
228+
if (!pipeline_->getLatestProduct(package, std::chrono::milliseconds(1000)))
224229
{
225230
URCL_LOG_ERROR("No answer to urcontrol version query was received from robot, disconnecting");
226231
disconnect();
@@ -249,39 +254,52 @@ void RTDEClient::queryURControlVersion()
249254
throw UrException(ss.str());
250255
}
251256

257+
void RTDEClient::resetOutputRecipe(const std::vector<std::string> new_recipe)
258+
{
259+
prod_->teardownProducer();
260+
disconnect();
261+
262+
output_recipe_.assign(new_recipe.begin(), new_recipe.end());
263+
parser_ = RTDEParser(output_recipe_);
264+
prod_ = std::make_unique<comm::URProducer<RTDEPackage>>(stream_, parser_);
265+
pipeline_ = std::make_unique<comm::Pipeline<RTDEPackage>>(*prod_, PIPELINE_NAME, notifier_, true);
266+
}
267+
252268
void RTDEClient::setupOutputs(const uint16_t protocol_version)
253269
{
254270
unsigned int num_retries = 0;
255271
size_t size;
256272
size_t written;
257273
uint8_t buffer[8192];
258274
URCL_LOG_INFO("Setting up RTDE communication with frequency %f", target_frequency_);
259-
if (protocol_version == 2)
260-
{
261-
size = ControlPackageSetupOutputsRequest::generateSerializedRequest(buffer, target_frequency_, output_recipe_);
262-
}
263-
else
275+
276+
while (num_retries < MAX_REQUEST_RETRIES)
264277
{
265-
if (target_frequency_ != max_frequency_)
278+
URCL_LOG_DEBUG("Sending output recipe");
279+
if (protocol_version == 2)
266280
{
267-
URCL_LOG_WARN("It is not possible to set a target frequency when using protocol version 1. A frequency "
268-
"equivalent to the maximum frequency will be used instead.");
281+
size = ControlPackageSetupOutputsRequest::generateSerializedRequest(buffer, target_frequency_, output_recipe_);
282+
}
283+
else
284+
{
285+
if (target_frequency_ != max_frequency_)
286+
{
287+
URCL_LOG_WARN("It is not possible to set a target frequency when using protocol version 1. A frequency "
288+
"equivalent to the maximum frequency will be used instead.");
289+
}
290+
size = ControlPackageSetupOutputsRequest::generateSerializedRequest(buffer, output_recipe_);
269291
}
270-
size = ControlPackageSetupOutputsRequest::generateSerializedRequest(buffer, output_recipe_);
271-
}
272292

273-
// Send output recipe to robot
274-
if (!stream_.write(buffer, size, written))
275-
{
276-
URCL_LOG_ERROR("Could not send RTDE output recipe to robot, disconnecting");
277-
disconnect();
278-
return;
279-
}
293+
// Send output recipe to robot
294+
if (!stream_.write(buffer, size, written))
295+
{
296+
URCL_LOG_ERROR("Could not send RTDE output recipe to robot, disconnecting");
297+
disconnect();
298+
return;
299+
}
280300

281-
while (num_retries < MAX_REQUEST_RETRIES)
282-
{
283301
std::unique_ptr<RTDEPackage> package;
284-
if (!pipeline_.getLatestProduct(package, std::chrono::milliseconds(1000)))
302+
if (!pipeline_->getLatestProduct(package, std::chrono::milliseconds(1000)))
285303
{
286304
URCL_LOG_ERROR("Did not receive confirmation on RTDE output recipe, disconnecting");
287305
disconnect();
@@ -293,18 +311,53 @@ void RTDEClient::setupOutputs(const uint16_t protocol_version)
293311

294312
{
295313
std::vector<std::string> variable_types = splitVariableTypes(tmp_output->variable_types_);
314+
std::vector<std::string> available_variables;
315+
std::vector<std::string> unavailable_variables;
296316
assert(output_recipe_.size() == variable_types.size());
297317
for (std::size_t i = 0; i < variable_types.size(); ++i)
298318
{
299-
URCL_LOG_DEBUG("%s confirmed as datatype: %s", output_recipe_[i].c_str(), variable_types[i].c_str());
319+
const std::string variable_name = output_recipe_[i];
320+
URCL_LOG_DEBUG("%s confirmed as datatype: %s", variable_name.c_str(), variable_types[i].c_str());
321+
300322
if (variable_types[i] == "NOT_FOUND")
301323
{
302-
std::string message = "Variable '" + output_recipe_[i] +
303-
"' not recognized by the robot. Probably your output recipe contains errors";
304-
throw UrException(message);
324+
unavailable_variables.push_back(variable_name);
325+
}
326+
else
327+
{
328+
available_variables.push_back(variable_name);
305329
}
306330
}
307-
return;
331+
332+
if (!unavailable_variables.empty())
333+
{
334+
std::stringstream error_message;
335+
error_message << "The following variables are not recognized by the robot: ";
336+
std::for_each(unavailable_variables.begin(), unavailable_variables.end(),
337+
[&error_message](const std::string& variable_name) { error_message << variable_name << " "; });
338+
error_message << ". Either your output recipe contains errors "
339+
"or the urcontrol version does not support "
340+
"them.";
341+
342+
if (ignore_unavailable_outputs_)
343+
{
344+
error_message << " They will be removed from the output recipe.";
345+
URCL_LOG_WARN("%s", error_message.str().c_str());
346+
347+
// Some variables are not available so retry setting up the communication with a stripped-down output recipe
348+
resetOutputRecipe(available_variables);
349+
}
350+
else
351+
{
352+
URCL_LOG_ERROR("%s", error_message.str().c_str());
353+
throw UrException(error_message.str());
354+
}
355+
}
356+
else
357+
{
358+
// All variables are accounted for in the RTDE package
359+
return;
360+
}
308361
}
309362
else
310363
{
@@ -339,7 +392,7 @@ void RTDEClient::setupInputs()
339392
while (num_retries < MAX_REQUEST_RETRIES)
340393
{
341394
std::unique_ptr<RTDEPackage> package;
342-
if (!pipeline_.getLatestProduct(package, std::chrono::milliseconds(1000)))
395+
if (!pipeline_->getLatestProduct(package, std::chrono::milliseconds(1000)))
343396
{
344397
URCL_LOG_ERROR("Did not receive confirmation on RTDE input recipe, disconnecting");
345398
disconnect();
@@ -395,7 +448,7 @@ void RTDEClient::disconnect()
395448
if (client_state_ > ClientState::UNINITIALIZED)
396449
{
397450
sendPause();
398-
pipeline_.stop();
451+
pipeline_->stop();
399452
stream_.disconnect();
400453
}
401454
client_state_ = ClientState::UNINITIALIZED;
@@ -421,7 +474,7 @@ bool RTDEClient::isRobotBooted()
421474
{
422475
// Set timeout based on target frequency, to make sure that reading doesn't timeout
423476
int timeout = static_cast<int>((1 / target_frequency_) * 1000) * 10;
424-
if (pipeline_.getLatestProduct(package, std::chrono::milliseconds(timeout)))
477+
if (pipeline_->getLatestProduct(package, std::chrono::milliseconds(timeout)))
425478
{
426479
rtde_interface::DataPackage* tmp_input = dynamic_cast<rtde_interface::DataPackage*>(package.get());
427480
tmp_input->getData("timestamp", timestamp);
@@ -451,7 +504,7 @@ bool RTDEClient::start()
451504
return false;
452505
}
453506

454-
pipeline_.run();
507+
pipeline_->run();
455508

456509
if (sendStart())
457510
{
@@ -501,7 +554,7 @@ bool RTDEClient::sendStart()
501554
unsigned int num_retries = 0;
502555
while (num_retries < MAX_REQUEST_RETRIES)
503556
{
504-
if (!pipeline_.getLatestProduct(package, std::chrono::milliseconds(1000)))
557+
if (!pipeline_->getLatestProduct(package, std::chrono::milliseconds(1000)))
505558
{
506559
URCL_LOG_ERROR("Could not get response to RTDE communication start request from robot");
507560
return false;
@@ -543,7 +596,7 @@ bool RTDEClient::sendPause()
543596
int seconds = 5;
544597
while (std::chrono::steady_clock::now() - start < std::chrono::seconds(seconds))
545598
{
546-
if (!pipeline_.getLatestProduct(package, std::chrono::milliseconds(1000)))
599+
if (!pipeline_->getLatestProduct(package, std::chrono::milliseconds(1000)))
547600
{
548601
URCL_LOG_ERROR("Could not get response to RTDE communication pause request from robot");
549602
return false;
@@ -605,7 +658,7 @@ std::vector<std::string> RTDEClient::ensureTimestampIsPresent(const std::vector<
605658
std::unique_ptr<rtde_interface::DataPackage> RTDEClient::getDataPackage(std::chrono::milliseconds timeout)
606659
{
607660
std::unique_ptr<RTDEPackage> urpackage;
608-
if (pipeline_.getLatestProduct(urpackage, timeout))
661+
if (pipeline_->getLatestProduct(urpackage, timeout))
609662
{
610663
rtde_interface::DataPackage* tmp = dynamic_cast<rtde_interface::DataPackage*>(urpackage.get());
611664
if (tmp != nullptr)

0 commit comments

Comments
 (0)