diff --git a/doc/architecture/rtde_client.rst b/doc/architecture/rtde_client.rst
index c5ebce50..49790647 100644
--- a/doc/architecture/rtde_client.rst
+++ b/doc/architecture/rtde_client.rst
@@ -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)
@@ -28,6 +28,20 @@ outputs. Please refer to the `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.
@@ -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 data_pkg = my_client.getDataPackage(READ_TIMEOUT);
+ if (data_pkg)
+ {
+ std::cout << data_pkg->toString() << std::endl;
+ }
+ }
+
RTDEWriter
----------
@@ -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``.
diff --git a/include/ur_client_library/rtde/data_package.h b/include/ur_client_library/rtde/data_package.h
index a7c90eca..ce2b991d 100644
--- a/include/ur_client_library/rtde/data_package.h
+++ b/include/ur_client_library/rtde/data_package.h
@@ -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.
*
@@ -82,6 +98,7 @@ class DataPackage : public RTDEPackage
: RTDEPackage(PackageType::RTDE_DATA_PACKAGE), recipe_(recipe), protocol_version_(protocol_version)
{
}
+
virtual ~DataPackage() = default;
/*!
diff --git a/include/ur_client_library/rtde/rtde_writer.h b/include/ur_client_library/rtde/rtde_writer.h
index 77a562d0..dc9f0537 100644
--- a/include/ur_client_library/rtde/rtde_writer.h
+++ b/include/ur_client_library/rtde/rtde_writer.h
@@ -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& recipe);
+
/*!
* \brief Starts the writer thread, which periodically clears the queue to write packages to the
* robot.
@@ -162,7 +171,7 @@ class RTDEWriter
uint8_t pinToMask(uint8_t pin);
comm::URStream* stream_;
std::vector recipe_;
- uint8_t recipe_id_;
+ uint8_t recipe_id_ = 0;
moodycamel::BlockingReaderWriterQueue> queue_;
std::thread writer_thread_;
bool running_;
diff --git a/src/rtde/rtde_client.cpp b/src/rtde/rtde_client.cpp
index 425dc981..de92c64d 100644
--- a/src/rtde/rtde_client.cpp
+++ b/src/rtde/rtde_client.cpp
@@ -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>(stream_, parser_))
, notifier_(notifier)
@@ -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& output_recipe,
@@ -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();
diff --git a/src/rtde/rtde_writer.cpp b/src/rtde/rtde_writer.cpp
index ea543ab8..de0eae95 100644
--- a/src/rtde/rtde_writer.cpp
+++ b/src/rtde/rtde_writer.cpp
@@ -37,6 +37,14 @@ RTDEWriter::RTDEWriter(comm::URStream* stream, const std::vector& recipe)
+{
+ std::lock_guard guard(package_mutex_);
+ recipe_ = recipe;
+ package_ = DataPackage(recipe_);
+ package_.initEmpty();
+}
+
void RTDEWriter::init(uint8_t recipe_id)
{
recipe_id_ = recipe_id;
diff --git a/tests/test_rtde_client.cpp b/tests/test_rtde_client.cpp
index cd87c235..865fa572 100644
--- a/tests/test_rtde_client.cpp
+++ b/tests/test_rtde_client.cpp
@@ -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)
@@ -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 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 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);