Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 43 additions & 1 deletion doc/architecture/rtde_client.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ client. To use the RTDE-Client, you'll have to initialize and start it separatel

.. code-block:: c++

rtde_interface::RTDEClient my_client(ROBOT_IP, notifier, OUTPUT_RECIPE, INPUT_RECIPE);
rtde_interface::RTDEClient my_client(ROBOT_IP, notifier, OUTPUT_RECIPE_FILE, INPUT_RECIPE_FILE);
my_client.init();
my_client.start();
while (true)
Expand All @@ -28,6 +28,20 @@ outputs. Please refer to the `RTDE
guide <https://www.universal-robots.com/articles/ur-articles/real-time-data-exchange-rtde-guide/>`_
on which elements are available.

.. note::

The recipes can be either passed as a filename or as a list of strings directly. E.g. the
following will work

.. code-block:: c++

rtde_interface::RTDEClient my_client(
ROBOT_IP,
notifier,
{"timestamp", "actual_q"},
{"speed_slider_mask", "speed_slider_fraction"}
);

Inside the ``RTDEclient`` data is received in a separate thread, parsed by the ``RTDEParser`` and
added to a pipeline queue.

Expand Down Expand Up @@ -56,6 +70,29 @@ sure to
frequency, please use the ``resetRTDEClient()`` method after the ``UrDriver`` object has been
created.

Read-Only RTDEClient
--------------------

While RTDE allows multiple clients to connect to the same robot, only one client is allowed to
write data to the robot. To create a read-only RTDE client, the ``RTDEClient`` can be created with
an empty input recipe, like this:

.. code-block:: c++

rtde_interface::RTDEClient my_client(ROBOT_IP, notifier, OUTPUT_RECIPE, {});
// Alternatively, pass an empty filename when using recipe files
// rtde_interface::RTDEClient my_client(ROBOT_IP, notifier, OUTPUT_RECIPE_FILE, "");
my_client.init();
my_client.start();
while (true)
{
std::unique_ptr<rtde_interface::DataPackage> data_pkg = my_client.getDataPackage(READ_TIMEOUT);
if (data_pkg)
{
std::cout << data_pkg->toString() << std::endl;
}
}

RTDEWriter
----------

Expand All @@ -66,3 +103,8 @@ The class offers specific methods for every RTDE input possible to write.

Data is sent asynchronously to the RTDE interface.

.. note::

The ``RTDEWriter`` will return ``false`` on any writing attempts for fields that have not been
setup in the ``INPUT_RECIPE``. When no input recipe was provided, all write operations will
return ``false``.
17 changes: 17 additions & 0 deletions include/ur_client_library/rtde/data_package.h
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,22 @@ class DataPackage : public RTDEPackage
this->protocol_version_ = other.protocol_version_;
}

DataPackage& operator=(DataPackage& other)
{
this->data_ = other.data_;
this->recipe_ = other.recipe_;
this->protocol_version_ = other.protocol_version_;
return *this;
}

DataPackage operator=(const DataPackage& other)
{
this->data_ = other.data_;
this->recipe_ = other.recipe_;
this->protocol_version_ = other.protocol_version_;
return *this;
}

/*!
* \brief Creates a new DataPackage object, based on a given recipe.
*
Expand All @@ -82,6 +98,7 @@ class DataPackage : public RTDEPackage
: RTDEPackage(PackageType::RTDE_DATA_PACKAGE), recipe_(recipe), protocol_version_(protocol_version)
{
}

virtual ~DataPackage() = default;

/*!
Expand Down
11 changes: 10 additions & 1 deletion include/ur_client_library/rtde/rtde_writer.h
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,15 @@ class RTDEWriter
writer_thread_.join();
}
}

/*!
* \brief Sets a new input recipe. This can be used to change the input recipe on the fly, if
* needed.
*
* \param recipe The new recipe to use
*/
void setInputRecipe(const std::vector<std::string>& recipe);

/*!
* \brief Starts the writer thread, which periodically clears the queue to write packages to the
* robot.
Expand Down Expand Up @@ -162,7 +171,7 @@ class RTDEWriter
uint8_t pinToMask(uint8_t pin);
comm::URStream<RTDEPackage>* stream_;
std::vector<std::string> recipe_;
uint8_t recipe_id_;
uint8_t recipe_id_ = 0;
moodycamel::BlockingReaderWriterQueue<std::unique_ptr<DataPackage>> queue_;
std::thread writer_thread_;
bool running_;
Expand Down
15 changes: 11 additions & 4 deletions src/rtde/rtde_client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ RTDEClient::RTDEClient(std::string robot_ip, comm::INotifier& notifier, const st
: stream_(robot_ip, UR_RTDE_PORT)
, output_recipe_(ensureTimestampIsPresent(readRecipe(output_recipe_file)))
, ignore_unavailable_outputs_(ignore_unavailable_outputs)
, input_recipe_(readRecipe(input_recipe_file))
, parser_(output_recipe_)
, prod_(std::make_unique<comm::URProducer<RTDEPackage>>(stream_, parser_))
, notifier_(notifier)
Expand All @@ -51,6 +50,11 @@ RTDEClient::RTDEClient(std::string robot_ip, comm::INotifier& notifier, const st
, target_frequency_(target_frequency)
, client_state_(ClientState::UNINITIALIZED)
{
if (!input_recipe_file.empty())
{
input_recipe_ = readRecipe(input_recipe_file);
writer_.setInputRecipe(input_recipe_);
}
}

RTDEClient::RTDEClient(std::string robot_ip, comm::INotifier& notifier, const std::vector<std::string>& output_recipe,
Expand Down Expand Up @@ -169,9 +173,12 @@ void RTDEClient::setupCommunication(const size_t max_num_tries, const std::chron
return;
}

setupInputs();
if (client_state_ == ClientState::UNINITIALIZED)
return;
if (input_recipe_.size() > 0)
{
setupInputs();
if (client_state_ == ClientState::UNINITIALIZED)
return;
}

// We finished communication for now
pipeline_->stop();
Expand Down
8 changes: 8 additions & 0 deletions src/rtde/rtde_writer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,14 @@ RTDEWriter::RTDEWriter(comm::URStream<RTDEPackage>* stream, const std::vector<st
{
}

void RTDEWriter::setInputRecipe(const std::vector<std::string>& recipe)
{
std::lock_guard<std::mutex> guard(package_mutex_);
recipe_ = recipe;
package_ = DataPackage(recipe_);
package_.initEmpty();
}

void RTDEWriter::init(uint8_t recipe_id)
{
recipe_id_ = recipe_id;
Expand Down
51 changes: 48 additions & 3 deletions tests/test_rtde_client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -120,9 +120,12 @@ TEST_F(RTDEClientTest, no_recipe)
UrException);

// Only input recipe is unconfigured
EXPECT_THROW(
client_.reset(new rtde_interface::RTDEClient(g_ROBOT_IP, notifier_, output_recipe_file_, input_recipe_file)),
UrException);
EXPECT_NO_THROW(
client_.reset(new rtde_interface::RTDEClient(g_ROBOT_IP, notifier_, output_recipe_file_, input_recipe_file)));

EXPECT_THROW(client_.reset(new rtde_interface::RTDEClient(g_ROBOT_IP, notifier_, output_recipe_file_,
"/i/do/not/exist/urclrtdetest.txt")),
UrException);
}

TEST_F(RTDEClientTest, empty_recipe_file)
Expand Down Expand Up @@ -413,6 +416,48 @@ TEST_F(RTDEClientTest, check_unknown_rtde_output_variable)
EXPECT_THROW(client_->init(), UrException);
}

TEST_F(RTDEClientTest, empty_input_recipe)
{
std::vector<std::string> empty_input_recipe = {};
client_.reset(new rtde_interface::RTDEClient(g_ROBOT_IP, notifier_, resources_output_recipe_, empty_input_recipe));
client_->init();
client_->start();

// Test that we can receive and parse the timestamp from the received package to prove the setup was successful
const std::chrono::milliseconds read_timeout{ 100 };
std::unique_ptr<rtde_interface::DataPackage> data_pkg = client_->getDataPackage(read_timeout);

if (data_pkg == nullptr)
{
std::cout << "Failed to get data package from robot" << std::endl;
GTEST_FAIL();
}

double timestamp;
EXPECT_TRUE(data_pkg->getData("timestamp", timestamp));

EXPECT_FALSE(client_->getWriter().sendStandardDigitalOutput(1, false));

client_->pause();

client_.reset(new rtde_interface::RTDEClient(g_ROBOT_IP, notifier_, output_recipe_file_, ""));
client_->init();
client_->start();

data_pkg = client_->getDataPackage(read_timeout);

if (data_pkg == nullptr)
{
std::cout << "Failed to get data package from robot" << std::endl;
GTEST_FAIL();
}
EXPECT_TRUE(data_pkg->getData("timestamp", timestamp));

EXPECT_FALSE(client_->getWriter().sendStandardDigitalOutput(1, false));

client_->pause();
}

int main(int argc, char* argv[])
{
::testing::InitGoogleTest(&argc, argv);
Expand Down
Loading