Skip to content

Commit 08c33c3

Browse files
authored
[RTDE client] Test reconnect (UniversalRobots#433)
This adds an integration test for reconnecting to the RTDE server. For this, a fake RTDEServer is added that can be stopped and restarted inside the tests easier than managing a URSim instance. The fake server doesn't have full functionality which is why it lives inside the test repository and is not part of the client library shared library target. I've added a refactoring to merge two existing `splitStrings` functions along the way before adding a third one.
1 parent 68fe8be commit 08c33c3

13 files changed

Lines changed: 516 additions & 75 deletions

include/ur_client_library/helpers.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,5 +99,15 @@ void waitFor(std::function<bool()> condition, const std::chrono::milliseconds ti
9999
*/
100100
bool parseBoolean(const std::string& str);
101101

102+
/*!
103+
* \brief Splits a at each delimiter found
104+
*
105+
* \param string_to_split String containing the delimiter and other characters
106+
* \param delimiter Chars at which the string should be split
107+
*
108+
* \returns A vector of characters that were between the delimiters
109+
*/
110+
std::vector<std::string> splitString(const std::string& string_to_split, const std::string& delimiter = ",");
111+
102112
} // namespace urcl
103113
#endif // ifndef UR_CLIENT_LIBRARY_HELPERS_H_INCLUDED

include/ur_client_library/rtde/rtde_client.h

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,8 @@ enum class ClientState
8484
INITIALIZING = 1,
8585
INITIALIZED = 2,
8686
RUNNING = 3,
87-
PAUSED = 4
87+
PAUSED = 4,
88+
CONNECTION_LOST = 5
8889
};
8990

9091
/*!
@@ -105,11 +106,12 @@ class RTDEClient
105106
* \param input_recipe_file Path to the file containing the input recipe
106107
* \param target_frequency Frequency to run at. Defaults to 0.0 which means maximum frequency.
107108
* \param ignore_unavailable_outputs Configure the behaviour when a variable of the output recipe is not available
109+
* \param port Optionally specify a different port
108110
* from the robot: output is silently ignored if true, a UrException is raised otherwise.
109111
*/
110112
RTDEClient(std::string robot_ip, comm::INotifier& notifier, const std::string& output_recipe_file,
111113
const std::string& input_recipe_file, double target_frequency = 0.0,
112-
bool ignore_unavailable_outputs = false);
114+
bool ignore_unavailable_outputs = false, const uint32_t port = UR_RTDE_PORT);
113115

114116
/*!
115117
* \brief Creates a new RTDEClient object, including a used URStream and Pipeline to handle the
@@ -121,11 +123,12 @@ class RTDEClient
121123
* \param input_recipe Vector containing the input recipe
122124
* \param target_frequency Frequency to run at. Defaults to 0.0 which means maximum frequency.
123125
* \param ignore_unavailable_outputs Configure the behaviour when a variable of the output recipe is not available
126+
* \param port Optionally specify a different port
124127
* from the robot: output is silently ignored if true, a UrException is raised otherwise.
125128
*/
126129
RTDEClient(std::string robot_ip, comm::INotifier& notifier, const std::vector<std::string>& output_recipe,
127130
const std::vector<std::string>& input_recipe, double target_frequency = 0.0,
128-
bool ignore_unavailable_outputs = false);
131+
bool ignore_unavailable_outputs = false, const uint32_t port = UR_RTDE_PORT);
129132
~RTDEClient();
130133
/*!
131134
* \brief Sets up RTDE communication with the robot. The handshake includes negotiation of the
@@ -235,6 +238,11 @@ class RTDEClient
235238
// Reads output or input recipe from a file
236239
static std::vector<std::string> readRecipe(const std::string& recipe_file);
237240

241+
ClientState getClientState() const
242+
{
243+
return client_state_;
244+
}
245+
238246
private:
239247
comm::URStream<RTDEPackage> stream_;
240248
std::vector<std::string> output_recipe_;
@@ -295,21 +303,16 @@ class RTDEClient
295303
bool sendStart();
296304
bool sendPause();
297305

298-
/*!
299-
* \brief Splits a variable_types string as reported from the robot into single variable type
300-
* strings
301-
*
302-
* \param variable_types String as reported from the robot
303-
*
304-
* \returns A vector of variable variable_names
305-
*/
306-
std::vector<std::string> splitVariableTypes(const std::string& variable_types) const;
307-
308306
/*!
309307
* \brief Reconnects to the RTDE interface and set the input and output recipes again.
310308
*/
311309
void reconnect();
312310
void reconnectCallback();
311+
312+
size_t max_connection_attempts_;
313+
std::chrono::milliseconds reconnection_timeout_;
314+
size_t max_initialization_attempts_;
315+
std::chrono::milliseconds initialization_timeout_;
313316
};
314317

315318
} // namespace rtde_interface

include/ur_client_library/ur/version_information.h

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,6 @@ class VersionInformation
7979
uint32_t build; ///< Build number
8080
};
8181

82-
std::vector<std::string> splitString(std::string input, const std::string& delimiter = ".");
8382
} // namespace urcl
8483

85-
#endif // ifndef UR_CLIENT_LIBRARY_UR_VERSION_INFORMATION_H_INCLUDED
84+
#endif // ifndef UR_CLIENT_LIBRARY_UR_VERSION_INFORMATION_H_INCLUDED

src/helpers.cpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,4 +140,22 @@ bool parseBoolean(const std::string& str)
140140
throw UrException(ss.str().c_str());
141141
}
142142
}
143+
144+
std::vector<std::string> splitString(const std::string& input, const std::string& delimiter)
145+
{
146+
std::vector<std::string> result;
147+
size_t pos = 0;
148+
size_t pos_end = pos;
149+
std::string substring;
150+
while ((pos_end = input.find(delimiter, pos)) != std::string::npos)
151+
{
152+
substring = input.substr(pos, pos_end - pos);
153+
result.push_back(substring);
154+
pos = pos_end + delimiter.length();
155+
}
156+
substring = input.substr(pos);
157+
result.push_back(substring);
158+
return result;
159+
}
160+
143161
} // namespace urcl

src/rtde/rtde_client.cpp

Lines changed: 31 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -31,14 +31,16 @@
3131
#include "ur_client_library/log.h"
3232
#include <algorithm>
3333
#include <chrono>
34+
#include <string>
3435

3536
namespace urcl
3637
{
3738
namespace rtde_interface
3839
{
3940
RTDEClient::RTDEClient(std::string robot_ip, comm::INotifier& notifier, const std::string& output_recipe_file,
40-
const std::string& input_recipe_file, double target_frequency, bool ignore_unavailable_outputs)
41-
: stream_(robot_ip, UR_RTDE_PORT)
41+
const std::string& input_recipe_file, double target_frequency, bool ignore_unavailable_outputs,
42+
const uint32_t port)
43+
: stream_(robot_ip, port)
4244
, output_recipe_(ensureTimestampIsPresent(readRecipe(output_recipe_file)))
4345
, ignore_unavailable_outputs_(ignore_unavailable_outputs)
4446
, parser_(output_recipe_)
@@ -61,8 +63,8 @@ RTDEClient::RTDEClient(std::string robot_ip, comm::INotifier& notifier, const st
6163

6264
RTDEClient::RTDEClient(std::string robot_ip, comm::INotifier& notifier, const std::vector<std::string>& output_recipe,
6365
const std::vector<std::string>& input_recipe, double target_frequency,
64-
bool ignore_unavailable_outputs)
65-
: stream_(robot_ip, UR_RTDE_PORT)
66+
bool ignore_unavailable_outputs, const uint32_t port)
67+
: stream_(robot_ip, port)
6668
, output_recipe_(ensureTimestampIsPresent(output_recipe))
6769
, ignore_unavailable_outputs_(ignore_unavailable_outputs)
6870
, input_recipe_(input_recipe)
@@ -103,6 +105,11 @@ bool RTDEClient::init(const size_t max_connection_attempts, const std::chrono::m
103105
return true;
104106
}
105107

108+
max_connection_attempts_ = max_connection_attempts;
109+
reconnection_timeout_ = reconnection_timeout;
110+
max_initialization_attempts_ = max_initialization_attempts;
111+
initialization_timeout_ = initialization_timeout;
112+
106113
prod_->setReconnectionCallback(nullptr);
107114

108115
unsigned int attempts = 0;
@@ -132,24 +139,27 @@ bool RTDEClient::init(const size_t max_connection_attempts, const std::chrono::m
132139

133140
bool RTDEClient::setupCommunication(const size_t max_num_tries, const std::chrono::milliseconds reconnection_time)
134141
{
135-
// The state initializing is used inside disconnect to stop the pipeline again.
136-
client_state_ = ClientState::INITIALIZING;
137-
// A running pipeline is needed inside setup.
142+
client_state_ = ClientState::UNINITIALIZED;
138143
try
139144
{
140145
pipeline_->init(max_num_tries, reconnection_time);
141146
}
142147
catch (const UrException& exc)
143148
{
144-
URCL_LOG_ERROR("Caught exception %s, while trying to initialize pipeline", exc.what());
149+
URCL_LOG_ERROR("Caught exception '%s', while trying to initialize pipeline", exc.what());
145150
return false;
146151
}
152+
// The state initializing is used inside disconnect to stop the pipeline again.
153+
// A running pipeline is needed inside setup.
154+
client_state_ = ClientState::INITIALIZING;
155+
147156
pipeline_->run();
148157

149158
uint16_t protocol_version = negotiateProtocolVersion();
150159
// Protocol version must be above zero
151160
if (protocol_version == 0)
152161
{
162+
client_state_ = ClientState::UNINITIALIZED;
153163
return false;
154164
}
155165

@@ -286,7 +296,8 @@ void RTDEClient::setTargetFrequency()
286296
else if (target_frequency_ <= 0.0 || target_frequency_ > max_frequency_)
287297
{
288298
// Target frequency outside valid range
289-
throw UrException("Invalid target frequency of RTDE connection");
299+
std::string error = "Invalid target frequency of RTDE connection: " + std::to_string(target_frequency_);
300+
throw UrException(error.c_str());
290301
}
291302
}
292303

@@ -348,7 +359,7 @@ bool RTDEClient::setupOutputs(const uint16_t protocol_version)
348359
dynamic_cast<rtde_interface::ControlPackageSetupOutputs*>(package.get()))
349360

350361
{
351-
std::vector<std::string> variable_types = splitVariableTypes(tmp_output->variable_types_);
362+
std::vector<std::string> variable_types = splitString(tmp_output->variable_types_, ",");
352363
std::vector<std::string> available_variables;
353364
std::vector<std::string> unavailable_variables;
354365
assert(output_recipe_.size() == variable_types.size());
@@ -441,7 +452,7 @@ bool RTDEClient::setupInputs()
441452
dynamic_cast<rtde_interface::ControlPackageSetupInputs*>(package.get()))
442453

443454
{
444-
std::vector<std::string> variable_types = splitVariableTypes(tmp_input->variable_types_);
455+
std::vector<std::string> variable_types = splitString(tmp_input->variable_types_, ",");
445456
assert(input_recipe_.size() == variable_types.size());
446457
for (std::size_t i = 0; i < variable_types.size(); ++i)
447458
{
@@ -745,36 +756,24 @@ RTDEWriter& RTDEClient::getWriter()
745756
return writer_;
746757
}
747758

748-
std::vector<std::string> RTDEClient::splitVariableTypes(const std::string& variable_types) const
749-
{
750-
std::vector<std::string> result;
751-
std::stringstream ss(variable_types);
752-
std::string substr = "";
753-
while (getline(ss, substr, ','))
754-
{
755-
result.push_back(substr);
756-
}
757-
return result;
758-
}
759-
760759
void RTDEClient::reconnect()
761760
{
762761
URCL_LOG_INFO("Reconnecting to the RTDE interface");
763762
// Locking mutex to ensure that calling getDataPackage doesn't influence the communication needed for reconfiguring
764763
// the RTDE connection
765764
std::lock_guard<std::mutex> lock(reconnect_mutex_);
766765
ClientState cur_client_state = client_state_;
766+
client_state_ = ClientState::CONNECTION_LOST;
767767
disconnect();
768768

769-
const size_t max_initialization_attempts = 3;
770769
size_t cur_initialization_attempt = 0;
771770
bool client_reconnected = false;
772-
while (cur_initialization_attempt < max_initialization_attempts)
771+
while (cur_initialization_attempt < max_initialization_attempts_)
773772
{
774773
bool is_communication_setup = false;
775774
try
776775
{
777-
is_communication_setup = setupCommunication(1, std::chrono::milliseconds{ 10000 });
776+
is_communication_setup = setupCommunication(max_connection_attempts_, reconnection_timeout_);
778777
}
779778
catch (const UrException& exc)
780779
{
@@ -797,24 +796,22 @@ void RTDEClient::reconnect()
797796
break;
798797
}
799798

800-
auto duration = std::chrono::seconds(1);
801799
if (stream_.getState() != comm::SocketState::Connected)
802800
{
803801
// We don't wanna count it as an initialization attempt if we cannot connect to the socket and we want to wait
804802
// longer before reconnecting.
805-
duration = std::chrono::seconds(10);
806-
URCL_LOG_ERROR("Failed to connect to the RTDE server, retrying in %i seconds", duration.count());
803+
URCL_LOG_ERROR("Failed to connect to the RTDE server, retrying in %i seconds", reconnection_timeout_.count());
807804
}
808805
else
809806
{
810-
URCL_LOG_ERROR("Failed to initialize RTDE client, retrying in %i second", duration.count());
807+
URCL_LOG_ERROR("Failed to initialize RTDE client, retrying in %i second", initialization_timeout_.count());
811808
cur_initialization_attempt += 1;
812809
}
813810

814811
disconnect();
815812

816813
auto start_time = std::chrono::steady_clock::now();
817-
while (std::chrono::steady_clock::now() - start_time < duration)
814+
while (std::chrono::steady_clock::now() - start_time < initialization_timeout_)
818815
{
819816
std::this_thread::sleep_for(std::chrono::milliseconds(250));
820817
if (stop_reconnection_)
@@ -828,12 +825,14 @@ void RTDEClient::reconnect()
828825
if (client_reconnected == false)
829826
{
830827
URCL_LOG_ERROR("Failed to initialize RTDE client after %i attempts, unable to reconnect",
831-
max_initialization_attempts);
828+
max_initialization_attempts_);
832829
disconnect();
833830
reconnecting_ = false;
834831
return;
835832
}
836833

834+
URCL_LOG_INFO("Successfully reconnected to the RTDE interface, starting communication again");
835+
837836
start();
838837
if (cur_client_state == ClientState::PAUSED)
839838
{

src/ur/version_information.cpp

Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -27,24 +27,11 @@
2727
//----------------------------------------------------------------------
2828

2929
#include <ur_client_library/exceptions.h>
30+
#include <ur_client_library/helpers.h>
3031
#include <ur_client_library/ur/version_information.h>
3132

3233
namespace urcl
3334
{
34-
std::vector<std::string> splitString(std::string input, const std::string& delimiter)
35-
{
36-
std::vector<std::string> result;
37-
size_t pos = 0;
38-
std::string substring;
39-
while ((pos = input.find(delimiter)) != std::string::npos)
40-
{
41-
substring = input.substr(0, pos);
42-
result.push_back(substring);
43-
input.erase(0, pos + delimiter.length());
44-
}
45-
result.push_back(input);
46-
return result;
47-
}
4835

4936
VersionInformation::VersionInformation()
5037
{
@@ -58,7 +45,7 @@ VersionInformation::VersionInformation()
5845

5946
VersionInformation VersionInformation::fromString(const std::string& str)
6047
{
61-
auto components = splitString(str);
48+
auto components = splitString(str, ".");
6249
VersionInformation info;
6350
if (components.size() >= 2)
6451
{
@@ -149,4 +136,4 @@ bool operator>=(const VersionInformation& v1, const VersionInformation& v2)
149136
{
150137
return !(v1 < v2);
151138
}
152-
} // namespace urcl
139+
} // namespace urcl

tests/CMakeLists.txt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ FetchContent_Declare(
1414
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
1515
FetchContent_MakeAvailable(googletest)
1616

17+
add_executable(fake_rtde_server fake_rtde_server.cpp fake_rtde_server_main.cpp)
18+
target_link_libraries(fake_rtde_server PRIVATE ur_client_library::urcl)
19+
1720
include(GoogleTest)
1821

1922
option(INTEGRATION_TESTS "Build the integration tests that require a running robot / URSim" OFF)
@@ -25,7 +28,7 @@ if (INTEGRATION_TESTS)
2528
find_package(Python3 COMPONENTS Interpreter REQUIRED)
2629

2730
add_custom_target(generate_outputs ALL COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/resources/generate_rtde_outputs.py)
28-
add_executable(rtde_tests test_rtde_client.cpp)
31+
add_executable(rtde_tests test_rtde_client.cpp fake_rtde_server.cpp)
2932
add_dependencies(rtde_tests generate_outputs)
3033
target_link_libraries(rtde_tests PRIVATE ur_client_library::urcl GTest::gtest_main)
3134
gtest_add_tests(TARGET rtde_tests

0 commit comments

Comments
 (0)