diff --git a/.github/docker/ddsenabler/Dockerfile b/.github/docker/ddsenabler/Dockerfile
index c085e8a0..582e2f35 100644
--- a/.github/docker/ddsenabler/Dockerfile
+++ b/.github/docker/ddsenabler/Dockerfile
@@ -55,12 +55,12 @@ RUN echo "fastcdr branch: <${fastcdr_branch}>" && \
RUN mkdir src && \
wget https://raw.githubusercontent.com/eProsima/DDS-Enabler/${ddsenabler_branch}/ddsenabler.repos && \
vcs import src < ddsenabler.repos && \
- cd src/Fast-CDR && git checkout ${fastcdr_branch} && cd ../.. && \
- cd src/Fast-DDS && git checkout ${fastdds_branch} && cd ../.. && \
- cd src/Dev-Utils && git checkout ${devutils_branch} && cd ../.. && \
- cd src/DDS-Pipe && git checkout ${ddspipe_branch} && cd ../.. && \
- cd src/DDS-Enabler && git checkout ${ddsenabler_branch} && cd ../.. && \
- cp -r src/Fast-DDS/examples/cpp/configuration src/. && \
+ cd src/fastcdr && git checkout ${fastcdr_branch} && cd ../.. && \
+ cd src/fastdds && git checkout ${fastdds_branch} && cd ../.. && \
+ cd src/dev-utils && git checkout ${devutils_branch} && cd ../.. && \
+ cd src/ddspipe && git checkout ${ddspipe_branch} && cd ../.. && \
+ cd src/ddsenabler && git checkout ${ddsenabler_branch} && cd ../.. && \
+ cp -r src/fastdds/examples/cpp/configuration src/. && \
colcon build --packages-skip ddsenabler ddsenabler_docs --event-handlers console_direct+ --cmake-args -DLOG_INFO=ON -DSECURITY=ON && \
colcon build --packages-select ddsenabler --event-handlers console_direct+ --cmake-args -DLOG_INFO=ON -DCOMPILE_EXAMPLES=ON
diff --git a/README.md b/README.md
index 34318da7..b94b34bc 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-[](https://eprosima.com/middleware/tools/DDS-Enabler)
+[](https://dds-enabler.readthedocs.io/en/latest/02-formalia/titlepage.html)
@@ -60,7 +60,6 @@ Find more about us at [eProsima’s webpage](https://eprosima.com/).
Explore the full user and developer guide hosted on ReadTheDocs:
-- [Introduction](https://dds-enabler.readthedocs.io/en/latest/rst/02-formalia/titlepage.html)
-- [Project Overview](https://dds-enabler.readthedocs.io/en/latest/rst/getting_started/project_overview.html)
-- [User Manual](https://dds-enabler.readthedocs.io/en/latest/rst/user_manual/context_broker_interface.html)
-- [API Reference](https://dds-enabler.readthedocs.io/en/latest/rst/ddsenabler/api_reference/api_reference.html)
+- [Introduction](https://dds-enabler.readthedocs.io/en/latest/02-formalia/titlepage.html)
+- [Installation Guide](https://dds-enabler.readthedocs.io/en/latest/ddsenabler/installation/linux.html)
+- [API Reference](https://dds-enabler.readthedocs.io/en/latest/ddsenabler/api_reference/api_reference.html)
diff --git a/ddsenabler.repos b/ddsenabler.repos
index 89f512f4..7d14338f 100644
--- a/ddsenabler.repos
+++ b/ddsenabler.repos
@@ -3,23 +3,23 @@ repositories:
type: git
url: https://github.com/eProsima/foonathan_memory_vendor.git
version: v1.3.1
- Fast-CDR:
+ fastcdr:
type: git
url: https://github.com/eProsima/Fast-CDR.git
version: master
- Fast-DDS:
+ fastdds:
type: git
url: https://github.com/eProsima/Fast-DDS.git
version: master
- Dev-Utils:
+ dev-utils:
type: git
url: https://github.com/eProsima/dev-utils.git
version: main
- DDS-Pipe:
+ ddspipe:
type: git
url: https://github.com/eProsima/DDS-Pipe.git
version: main
- DDS-Enabler:
+ ddsenabler:
type: git
url: https://github.com/eProsima/DDS-Enabler.git
version: main
diff --git a/ddsenabler/examples/CMakeLists.txt b/ddsenabler/examples/CMakeLists.txt
index fc89d7fe..8d8fa754 100644
--- a/ddsenabler/examples/CMakeLists.txt
+++ b/ddsenabler/examples/CMakeLists.txt
@@ -12,38 +12,15 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-cmake_minimum_required(VERSION 3.20)
-project(ddsenabler_example LANGUAGES CXX)
-
-add_executable(ddsenabler_example main.cpp)
-
-target_link_libraries(ddsenabler_example PRIVATE ddsenabler)
-
-# Install rule
-install(TARGETS ddsenabler_example
- RUNTIME DESTINATION bin
+# Install persistence directory
+install(
+ DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/persistence
+ DESTINATION examples
)
-# Copy the json files over to the build directory
-file(GLOB_RECURSE JSON_FILES ${CMAKE_CURRENT_SOURCE_DIR}/*.json)
-foreach(JSON_FILE_COMPLETE_PATH ${JSON_FILES})
- get_filename_component(JSON_FILE ${JSON_FILE_COMPLETE_PATH} NAME_WE)
- configure_file(
- ${JSON_FILE_COMPLETE_PATH}
- ${CMAKE_CURRENT_BINARY_DIR}/${JSON_FILE}.json
- COPYONLY)
- install(FILES ${JSON_FILE_COMPLETE_PATH}
- DESTINATION ${DATA_INSTALL_DIR}/ddsenabler/examples/ddsenabler/${BIN_INSTALL_DIR})
-endforeach()
+# Add subdirectories for examples
+add_subdirectory(utils)
+add_subdirectory(publish)
+add_subdirectory(service)
+add_subdirectory(action)
-# Copy the yaml files over to the build directory
-file(GLOB_RECURSE YAML_FILES ${CMAKE_CURRENT_SOURCE_DIR}/*.yml)
-foreach(YAML_FILE_COMPLETE_PATH ${YAML_FILES})
- get_filename_component(YAML_FILE ${YAML_FILE_COMPLETE_PATH} NAME_WE)
- configure_file(
- ${YAML_FILE_COMPLETE_PATH}
- ${CMAKE_CURRENT_BINARY_DIR}/${YAML_FILE}.yml
- COPYONLY)
- install(FILES ${YAML_FILE_COMPLETE_PATH}
- DESTINATION ${DATA_INSTALL_DIR}/ddsenabler/examples/ddsenabler/${BIN_INSTALL_DIR})
-endforeach()
diff --git a/ddsenabler/examples/action/CLIParser.hpp b/ddsenabler/examples/action/CLIParser.hpp
new file mode 100644
index 00000000..387c7350
--- /dev/null
+++ b/ddsenabler/examples/action/CLIParser.hpp
@@ -0,0 +1,339 @@
+// Copyright 2025 Proyectos y Sistemas de Mantenimiento SL (eProsima).
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include
+#include
+#include
+#include
+
+#include
+
+#pragma once
+
+class CLIParser
+{
+public:
+
+ CLIParser() = delete;
+
+ struct example_config
+ {
+ std::string config_file_path = "";
+ std::string action_name = "fibonacci/_action/";
+ bool announce_server = true;
+ uint32_t timeout = 30;
+ std::string persistence_path = "";
+ std::string goals_path = "";
+ uint32_t expected_requests = 0;
+ uint32_t request_initial_wait = 0;
+ bool cancel_requests = false;
+ };
+
+ /**
+ * @brief Print usage help message and exit with the given return code
+ *
+ * @param return_code return code to exit with
+ *
+ * @warning This method finishes the execution of the program with the input return code
+ */
+ static void print_help(
+ uint8_t return_code)
+ {
+ std::cout << "Usage: ddsenabler_example_action [options]" <<
+ std::endl;
+ std::cout << "" <<
+ std::endl;
+ std::cout << "--config Path to the configuration file" <<
+ std::endl;
+ std::cout << " (optional, if not provided default config is used)" <<
+ std::endl;
+ std::cout << "--action-name Name of the action to be registered" <<
+ std::endl;
+ std::cout << " (Default: 'Fibonacci/_action/')" <<
+ std::endl;
+ std::cout << "--timeout Time (seconds) to wait before stopping the" <<
+ std::endl;
+ std::cout << " program if expectations are not met" <<
+ std::endl;
+ std::cout << " (Default: 30)" <<
+ std::endl;
+ std::cout << "--persistence-path Path to the persistence directory" <<
+ std::endl;
+ std::cout << " (No default value as it is required)" <<
+ std::endl;
+ std::cout << "\n-------------------------------------SERVER OPTIONS------------------------------------\n" <<
+ std::endl;
+ std::cout << "--expected-requests Number of requests expected to be received" <<
+ std::endl;
+ std::cout << " (Default: 0, meaning infinite)" <<
+ std::endl;
+ std::cout << "\n-------------------------------------CLIENT OPTIONS------------------------------------\n" <<
+ std::endl;
+ std::cout << "--request-initial-wait Time (seconds) to wait before starting" <<
+ std::endl;
+ std::cout << " requests publication since server matching" <<
+ std::endl;
+ std::cout << " (Default: 0)" <<
+ std::endl;
+ std::cout << "--cancel-requests Whether to cancel requests after sending them" <<
+ std::endl;
+ std::cout << " (Default: false)" <<
+ std::endl;
+ std::cout << "--goals-path Directory containing goal JSON files" <<
+ std::endl;
+ std::cout << " (No default value as it is required for client mode)" <<
+ std::endl;
+ std::exit(return_code);
+ }
+
+ /**
+ * @brief Parse the command line options and return the configuration_config object
+ *
+ * @param argc number of arguments
+ * @param argv array of arguments
+ * @return configuration_config object with the parsed options
+ *
+ * @warning This method finishes the execution of the program if the input arguments are invalid
+ */
+ static example_config parse_cli_options(
+ int argc,
+ char* argv[])
+ {
+ example_config config;
+
+ if (argc < 2)
+ {
+ std::cerr << "Mode is required as the first argument: 'client' or 'server'" << std::endl;
+ print_help(EXIT_FAILURE);
+ }
+
+ bool client_flag = false;
+ bool server_flag = false;
+
+ // First positional argument must be the mode
+ std::string mode = argv[1];
+ if (mode == "-h" || mode == "--help")
+ {
+ print_help(EXIT_SUCCESS);
+ }
+ else if (mode == "client")
+ {
+ client_flag = true;
+ config.announce_server = false;
+ }
+ else if (mode == "server")
+ {
+ server_flag = true;
+ config.announce_server = true;
+ }
+ else
+ {
+ std::cerr << "Invalid mode '" << mode << "'. First argument must be 'client' or 'server'" << std::endl;
+ print_help(EXIT_FAILURE);
+ }
+
+ // Parse optional arguments starting from index 2
+ for (int i = 2; i < argc; ++i)
+ {
+ std::string arg = argv[i];
+
+ if (arg == "-h" || arg == "--help")
+ {
+ print_help(EXIT_SUCCESS);
+ }
+ else if (arg == "--config")
+ {
+ if (++i < argc)
+ {
+ config.config_file_path = argv[i];
+ if (!std::filesystem::exists(config.config_file_path))
+ {
+ std::cerr << "Invalid configuration file path: " << config.config_file_path << std::endl;
+ print_help(EXIT_FAILURE);
+ }
+ }
+ else
+ {
+ std::cerr << "Failed to parse --config argument" << std::endl;
+ print_help(EXIT_FAILURE);
+ }
+ }
+ else if (arg == "--action-name")
+ {
+ if (++i < argc)
+ {
+ config.action_name = argv[i];
+ }
+ else
+ {
+ std::cerr << "Failed to parse --action-name argument" << std::endl;
+ print_help(EXIT_FAILURE);
+ }
+ }
+ else if (arg == "--timeout")
+ {
+ if (++i < argc)
+ {
+ try
+ {
+ config.timeout = static_cast(std::stoi(argv[i]));
+ }
+ catch (const std::exception& e)
+ {
+ std::cerr << "Invalid --timeout argument " << argv[i] << ": " << e.what() << std::endl;
+ print_help(EXIT_FAILURE);
+ }
+ }
+ else
+ {
+ std::cerr << "Failed to parse --timeout argument" << std::endl;
+ print_help(EXIT_FAILURE);
+ }
+ }
+ else if (arg == "--persistence-path")
+ {
+ if (++i < argc)
+ {
+ config.persistence_path = argv[i];
+ }
+ else
+ {
+ std::cerr << "Failed to parse --persistence-path argument" << std::endl;
+ print_help(EXIT_FAILURE);
+ }
+ }
+ else if (arg == "--expected-requests")
+ {
+ if (++i < argc)
+ {
+ try
+ {
+ config.expected_requests = static_cast(std::stoi(argv[i]));
+ }
+ catch (const std::exception& e)
+ {
+ std::cerr << "Invalid --expected-requests argument " << argv[i] << ": " << e.what() <<
+ std::endl;
+ print_help(EXIT_FAILURE);
+ }
+ }
+ else
+ {
+ std::cerr << "Failed to parse --expected-requests argument" << std::endl;
+ print_help(EXIT_FAILURE);
+ }
+ }
+ else if (arg == "--request-initial-wait")
+ {
+ if (++i < argc)
+ {
+ try
+ {
+ config.request_initial_wait = static_cast(std::stoi(argv[i]));
+ }
+ catch (const std::exception& e)
+ {
+ std::cerr << "Invalid --request-initial-wait argument " << argv[i] << ": " << e.what() <<
+ std::endl;
+ print_help(EXIT_FAILURE);
+ }
+ }
+ else
+ {
+ std::cerr << "Failed to parse --request-initial-wait argument" << std::endl;
+ print_help(EXIT_FAILURE);
+ }
+ }
+ else if (arg == "--cancel-requests")
+ {
+ if (++i < argc)
+ {
+ config.cancel_requests = (std::string(argv[i]) == "true") ||
+ (std::string(argv[i]) == "True") ||
+ (std::string(argv[i]) == "1");
+ }
+ else
+ {
+ std::cerr << "Failed to parse --cancel-requests argument" << std::endl;
+ print_help(EXIT_FAILURE);
+ }
+ }
+ else if (arg == "--goals-path")
+ {
+ if (++i < argc)
+ {
+ config.goals_path = argv[i];
+ if (!std::filesystem::exists(config.goals_path) ||
+ !std::filesystem::is_directory(config.goals_path))
+ {
+ std::cerr << "Invalid --goals-path directory: " << config.goals_path << std::endl;
+ print_help(EXIT_FAILURE);
+ }
+ }
+ else
+ {
+ std::cerr << "Failed to parse --goals-path argument" << std::endl;
+ print_help(EXIT_FAILURE);
+ }
+ }
+ else
+ {
+ std::cerr << "Failed to parse unknown argument: " << arg << std::endl;
+ print_help(EXIT_FAILURE);
+ }
+ }
+
+ // Sanity check (should be set from the positional mode)
+ if (!client_flag && !server_flag)
+ {
+ std::cerr << "Either 'client' or 'server' must be specified as the first argument" << std::endl;
+ print_help(EXIT_FAILURE);
+ }
+
+ if (client_flag && config.goals_path.empty())
+ {
+ std::cerr << "--goals-path is required in client mode" << std::endl;
+ print_help(EXIT_FAILURE);
+ }
+
+ return config;
+ }
+
+ /**
+ * @brief Parse the signal number into the signal name
+ *
+ * @param signum signal number
+ * @return std::string signal name
+ */
+ static std::string parse_signal(
+ const int& signum)
+ {
+ switch (signum)
+ {
+ case SIGINT:
+ return "SIGINT";
+ case SIGTERM:
+ return "SIGTERM";
+#ifndef _WIN32
+ case SIGQUIT:
+ return "SIGQUIT";
+ case SIGHUP:
+ return "SIGHUP";
+#endif // _WIN32
+ default:
+ return "UNKNOWN SIGNAL";
+ }
+ }
+
+};
diff --git a/ddsenabler/examples/action/CMakeLists.txt b/ddsenabler/examples/action/CMakeLists.txt
new file mode 100644
index 00000000..97e1d660
--- /dev/null
+++ b/ddsenabler/examples/action/CMakeLists.txt
@@ -0,0 +1,32 @@
+# Copyright 2025 Proyectos y Sistemas de Mantenimiento SL (eProsima).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+cmake_minimum_required(VERSION 3.20)
+project(ddsenabler_example_action LANGUAGES CXX)
+
+add_executable(ddsenabler_example_action main.cpp)
+
+target_include_directories(ddsenabler_example_action PRIVATE ${PROJECT_SOURCE_DIR}/../utils)
+target_link_libraries(ddsenabler_example_action PRIVATE ddsenabler utils)
+
+# Install rule
+install(TARGETS ddsenabler_example_action
+ RUNTIME DESTINATION examples/action
+)
+
+# Install goals directory
+install(
+ DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/goals
+ DESTINATION examples/action
+)
diff --git a/ddsenabler/examples/action/Readme.md b/ddsenabler/examples/action/Readme.md
new file mode 100644
index 00000000..9e7fa8b3
--- /dev/null
+++ b/ddsenabler/examples/action/Readme.md
@@ -0,0 +1,31 @@
+# Action Example Readme
+
+This example demonstrates the usage of the DDS Enabler for implementing an action. The example includes a client and a server setup for the desired action.
+
+By default, the example is prepared for the `Fibonacci` action, providing a standard reply with a constant result of the Fibonacci sequence of order 5 for any request.
+
+Users are encouraged to implement their own functional server logic as needed in the `server_specific_logic` function. The persistence/goals directory must be modified to suit the specific action type and its expected goal requests.
+
+## Example Commands
+
+### CLIENT
+```bash
+./install/ddsenabler/examples/action/ddsenabler_example_action client \
+ --persistence-path ./install/ddsenabler/examples/persistence/ \
+ --goals-path ./install/ddsenabler/examples/action/goals/ \
+ --request-initial-wait 3
+ --cancel-requests false
+ --config $PATH_TO_CONFIG_FILE
+ --action-name $ACTION_NAME
+ --timeout N
+```
+
+### SERVER
+```bash
+./install/ddsenabler/examples/action/ddsenabler_example_action server \
+ --persistence-path ./install/ddsenabler/examples/persistence/
+ --config $PATH_TO_CONFIG_FILE
+ --expected-requests N
+ --action-name $ACTION_NAME
+ --timeout N
+```
diff --git a/ddsenabler/examples/action/goals/1 b/ddsenabler/examples/action/goals/1
new file mode 100644
index 00000000..12762e95
--- /dev/null
+++ b/ddsenabler/examples/action/goals/1
@@ -0,0 +1,3 @@
+{
+ "order": 5
+}
diff --git a/ddsenabler/examples/action/goals/2 b/ddsenabler/examples/action/goals/2
new file mode 100644
index 00000000..b733ce25
--- /dev/null
+++ b/ddsenabler/examples/action/goals/2
@@ -0,0 +1,3 @@
+{
+ "order": 6
+}
diff --git a/ddsenabler/examples/action/goals/3 b/ddsenabler/examples/action/goals/3
new file mode 100644
index 00000000..327d3eb8
--- /dev/null
+++ b/ddsenabler/examples/action/goals/3
@@ -0,0 +1,3 @@
+{
+ "order": 7
+}
diff --git a/ddsenabler/examples/action/main.cpp b/ddsenabler/examples/action/main.cpp
new file mode 100644
index 00000000..1b571a28
--- /dev/null
+++ b/ddsenabler/examples/action/main.cpp
@@ -0,0 +1,614 @@
+// Copyright 2025 Proyectos y Sistemas de Mantenimiento SL (eProsima).
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/**
+ * @file main.cpp
+ *
+ */
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "Utils.hpp"
+
+#include "ddsenabler/dds_enabler_runner.hpp"
+#include "ddsenabler/DDSEnabler.hpp"
+
+#include "CLIParser.hpp"
+
+CLIParser::example_config config;
+bool action_discovered_ = false;
+uint32_t received_results_ = 0;
+std::vector> received_requests_;
+std::mutex app_mutex_;
+std::condition_variable app_cv_;
+bool stop_app_ = false;
+
+const std::string REQUESTS_SUBDIR = "goals";
+const std::string TYPES_SUBDIR = "types";
+const std::string ACTION_SUBDIR = "actions";
+
+void signal_handler(
+ int signum)
+{
+ std::cout << "Signal " << CLIParser::parse_signal(signum) << " received, stopping..." << std::endl;
+ {
+ std::lock_guard lock(app_mutex_);
+ stop_app_ = true;
+ }
+ app_cv_.notify_all();
+}
+
+// Static log callback
+void test_log_callback(
+ const char* file_name,
+ int line_no,
+ const char* func_name,
+ int category,
+ const char* msg)
+{
+ // NOTE: Default stdout logs can be disabled via configuration ("logging": {"stdout": false}) to avoid duplicated traces
+
+ std::stringstream ss;
+ ss << file_name << ":" << line_no << " (" << func_name << "): " << msg << std::endl;
+ if (category == eprosima::utils::Log::Kind::Warning)
+ {
+ std::cerr << "[WARNING] " << ss.str();
+ }
+ else if (category == eprosima::utils::Log::Kind::Error)
+ {
+ std::cerr << "[ERROR] " << ss.str();
+ }
+}
+
+// Static type notification callback
+static void test_type_notification_callback(
+ const char* type_name,
+ const char* serialized_type,
+ const unsigned char* serialized_type_internal,
+ uint32_t serialized_type_internal_size,
+ const char* data_placeholder)
+{
+ std::lock_guard lock(app_mutex_);
+ std::cout << "Type callback received: " << type_name << std::endl;
+ if (!config.persistence_path.empty() &&
+ !utils::save_type_to_file((std::filesystem::path(config.persistence_path) / TYPES_SUBDIR).string(),
+ type_name,
+ serialized_type_internal, serialized_type_internal_size))
+ {
+ std::cerr << "Failed to save type: " << type_name << std::endl;
+ }
+}
+
+// Static type query callback
+static bool test_type_query_callback(
+ const char* type_name,
+ std::unique_ptr& serialized_type_internal,
+ uint32_t& serialized_type_internal_size)
+{
+ if (config.persistence_path.empty())
+ {
+ std::cerr << "Persistence path is not set, cannot query type: " << type_name << std::endl;
+ return false;
+ }
+
+ // Load the type from file
+ if (!utils::load_type_from_file((std::filesystem::path(config.persistence_path) / TYPES_SUBDIR).string(), type_name,
+ serialized_type_internal, serialized_type_internal_size))
+ {
+ std::cerr << "Failed to load type: " << type_name << std::endl;
+ return false;
+ }
+ return true;
+}
+
+// Static data notification callback
+static void test_data_notification_callback(
+ const char* topic_name,
+ const char* json,
+ int64_t publish_time)
+{
+}
+
+// Static topic notification callback
+static void test_topic_notification_callback(
+ const char* topic_name,
+ const eprosima::ddsenabler::participants::TopicInfo& topic_info)
+{
+}
+
+// Static type query callback
+static bool test_topic_query_callback(
+ const char* topic_name,
+ eprosima::ddsenabler::participants::TopicInfo& topic_info)
+{
+ return false;
+}
+
+// Static action notification callback
+static void test_action_notification_callback(
+ const char* action_name,
+ const eprosima::ddsenabler::participants::ActionInfo& action_info)
+{
+ std::lock_guard lock(app_mutex_);
+ if (config.action_name == std::string(action_name))
+ {
+ std::cout << "Action callback received: " << action_name << std::endl;
+
+ std::string action_file = (std::filesystem::path(
+ config.persistence_path) /
+ ACTION_SUBDIR
+ ).string();
+ if (!config.persistence_path.empty())
+ {
+ utils::save_action_to_file(
+ action_file,
+ action_name,
+ action_info);
+ }
+ action_discovered_ = true;
+ app_cv_.notify_all();
+ }
+ else
+ {
+ std::cout << "Ignoring action callback for: " << action_name << std::endl;
+ }
+}
+
+// Static action query callback
+static bool test_action_query_callback(
+ const char* action_name,
+ eprosima::ddsenabler::participants::ActionInfo& action_info)
+{
+ std::lock_guard lock(app_mutex_);
+ if (config.action_name == std::string(action_name))
+ {
+ std::cout << "Action query callback received: " << action_name << std::endl;
+
+ if (!config.persistence_path.empty())
+ {
+ std::string action_file = (std::filesystem::path(
+ config.persistence_path) /
+ ACTION_SUBDIR
+ ).string();
+ if (!utils::load_action_from_file(
+ action_file,
+ action_name,
+ action_info))
+ {
+ std::cerr << "Failed to load action: " << action_name << std::endl;
+ return false;
+ }
+ return true;
+ }
+ }
+ else
+ {
+ std::cout << "Ignoring action query callback for: " << action_name << std::endl;
+ }
+ return false;
+}
+
+// Static action goal request notification callback
+static bool test_action_goal_request_notification_callback(
+ const char* action_name,
+ const char* json,
+ const eprosima::ddsenabler::participants::UUID& goal_id,
+ int64_t publish_time)
+{
+ std::lock_guard lock(app_mutex_);
+ if (config.action_name == std::string(action_name))
+ {
+ std::cout << "Action goal request callback received: " << action_name << std::endl;
+ received_requests_.emplace_back(goal_id, json);
+ app_cv_.notify_all();
+ return true;
+ }
+ return false;
+}
+
+// Static action result notification callback
+static void test_action_result_notification_callback(
+ const char* action_name,
+ const char* json,
+ const eprosima::ddsenabler::participants::UUID& goal_id,
+ int64_t publish_time)
+{
+ std::lock_guard lock(app_mutex_);
+ std::cout << "Action result callback received: " << action_name << std::endl;
+ received_results_++;
+ app_cv_.notify_all();
+}
+
+// Static action feedback notification callback
+static void test_action_feedback_notification_callback(
+ const char* action_name,
+ const char* json,
+ const eprosima::ddsenabler::participants::UUID& goal_id,
+ int64_t publish_time)
+{
+ std::lock_guard lock(app_mutex_);
+ std::cout << "Action feedback callback received for action: " << action_name << std::endl;
+}
+
+// Static action status notification callback
+static void test_action_status_notification_callback(
+ const char* action_name,
+ const eprosima::ddsenabler::participants::UUID& goal_id,
+ eprosima::ddsenabler::participants::StatusCode statusCode,
+ const char* statusMessage,
+ int64_t publish_time)
+{
+ std::lock_guard lock(app_mutex_);
+ std::cout << "Action status callback received: " << statusMessage << std::endl;
+}
+
+// Static action cancel request notification callback
+static void test_action_cancel_request_notification_callback(
+ const char* action_name,
+ const eprosima::ddsenabler::participants::UUID& goal_id,
+ int64_t timestamp,
+ uint64_t request_id,
+ int64_t publish_time)
+{
+ // NOT IMPLEMENTED
+}
+
+bool wait_for_action_discovery(
+ uint32_t timeout,
+ std::mutex& app_mutex,
+ std::condition_variable& app_cv)
+{
+ std::unique_lock lock(app_mutex);
+ if (!app_cv.wait_for(lock, std::chrono::seconds(timeout),
+ []()
+ {
+ return stop_app_ || action_discovered_;
+ }) || stop_app_)
+ {
+ if (!stop_app_)
+ {
+ std::cerr << "Timeout waiting for service discovery." << std::endl;
+ }
+ return false;
+ }
+ return true;
+}
+
+bool wait_for_action_request(
+ uint32_t timeout,
+ std::mutex& app_mutex,
+ std::condition_variable& app_cv,
+ eprosima::ddsenabler::participants::UUID& request_id,
+ std::string& goal_json)
+{
+ std::unique_lock lock(app_mutex);
+ if (!app_cv.wait_for(lock, std::chrono::seconds(timeout),
+ []()
+ {
+ return stop_app_ || !received_requests_.empty();
+ }) || stop_app_)
+ {
+ if (!stop_app_)
+ {
+ std::cerr << "Timeout waiting for service request." << std::endl;
+ }
+ return false;
+ }
+
+ request_id = received_requests_.back().first;
+ goal_json = received_requests_.back().second;
+ received_requests_.pop_back();
+ return true;
+}
+
+bool wait_for_action_result(
+ uint32_t timeout,
+ std::mutex& app_mutex,
+ std::condition_variable& app_cv,
+ uint32_t sent_requests)
+{
+ std::unique_lock lock(app_mutex);
+ if (!app_cv.wait_for(lock, std::chrono::seconds(timeout),
+ [&sent_requests]()
+ {
+ return stop_app_ || received_results_ >= sent_requests;
+ }) || stop_app_)
+ {
+ if (!stop_app_)
+ {
+ std::cerr << "Timeout waiting for action result." << std::endl;
+ }
+ return false;
+ }
+ return true;
+}
+
+bool client_routine(
+ std::shared_ptr enabler,
+ const std::string& action_name,
+ const std::string& goal_path,
+ uint32_t timeout,
+ uint32_t request_initial_wait,
+ bool cancel_requests)
+{
+ // Wait for service to be discovered
+ if (!wait_for_action_discovery(timeout, app_mutex_, app_cv_))
+ {
+ return false;
+ }
+
+ // Wait a bit before starting to publish so types and topics can be discovered
+ std::this_thread::sleep_for(std::chrono::seconds(request_initial_wait));
+
+ // Get collection of files to publish, sorted in increasing order by their name (assumed to be numeric)
+ std::vector> sample_files;
+ utils::get_sorted_files(goal_path, sample_files);
+ uint32_t sent_requests = 0;
+ for (const auto& [path, number] : sample_files)
+ {
+ eprosima::ddsenabler::participants::UUID request_id;
+ std::ifstream file(path, std::ios::binary);
+ if (file)
+ {
+ std::string file_content((std::istreambuf_iterator(file)), std::istreambuf_iterator());
+ if (enabler->send_action_goal(action_name, file_content, request_id))
+ {
+ std::cout << "Published content from file: " << path.filename() << " in service: "
+ << action_name << std::endl;
+ sent_requests++;
+ }
+ else
+ {
+ std::cerr << "Failed to publish content from file: " << path.filename() << " in service: "
+ << action_name << std::endl;
+ return false;
+ }
+ }
+ else
+ {
+ std::cerr << "Failed to open file: " << path << std::endl;
+ }
+ if (cancel_requests)
+ {
+ std::this_thread::sleep_for(std::chrono::milliseconds(500)); // Simulate processing time
+ // Cancel the request after sending it
+ if (!enabler->cancel_action_goal(action_name, request_id))
+ {
+ std::cerr << "Failed to send action cancel request." << std::endl;
+ return false;
+ }
+ std::cout << "Sent cancel request for action: " << action_name << std::endl;
+ }
+
+ // Wait publish period or until stop signal is received
+ else if (!wait_for_action_result(timeout, app_mutex_, app_cv_, sent_requests))
+ {
+ return false;
+ }
+ }
+ std::this_thread::sleep_for(std::chrono::seconds(1)); // Wait for any last feedback/result/cancel notifications
+ return true;
+}
+
+bool server_specific_logic(
+ std::shared_ptr enabler,
+ const std::string& action_name,
+ uint64_t fibonacci_number,
+ const eprosima::ddsenabler::participants::UUID& request_id)
+{
+ std::vector fibonacci_sequence = {0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610};
+ std::string json = "{\"sequence\": [";
+ std::string feedback_json = "{\"partial_sequence\": [";
+ for (size_t i = 0; i < fibonacci_number; ++i)
+ {
+ {
+ std::lock_guard lock(app_mutex_);
+ if (stop_app_)
+ {
+ return false;
+ }
+ }
+
+ json += std::to_string(fibonacci_sequence[i]);
+ feedback_json += std::to_string(fibonacci_sequence[i]);
+
+ std::string feedback_tmp = feedback_json;
+ feedback_tmp += "]}";
+ if (!enabler->send_action_feedback(
+ action_name.c_str(),
+ feedback_tmp.c_str(),
+ request_id))
+ {
+ std::cerr << "Failed to send action feedback" << std::endl;
+ return false;
+ }
+ std::this_thread::sleep_for(std::chrono::milliseconds(500));
+ if (i != fibonacci_number - 1)
+ {
+ json += ", ";
+ feedback_json += ", ";
+ }
+ }
+ json += "]}";
+
+ if (!enabler->send_action_result(
+ action_name.c_str(),
+ request_id,
+ eprosima::ddsenabler::participants::StatusCode::SUCCEEDED,
+ json.c_str()))
+ {
+ std::cerr << "Failed to send action result" << std::endl;
+ return false;
+ }
+
+ return true;
+}
+
+bool server_routine(
+ std::shared_ptr enabler,
+ const std::string& action_name,
+ uint32_t expected_requests,
+ uint32_t timeout)
+{
+ // Announce action
+ if (!enabler->announce_action(action_name))
+ {
+ std::cerr << "Failed to announce action: " << action_name << std::endl;
+ return false;
+ }
+
+ std::cout << "Action announced: " << action_name << std::endl;
+ bool stop_app = false;
+ {
+ std::lock_guard lock(app_mutex_);
+ stop_app = stop_app_;
+ }
+ while (!stop_app)
+ {
+ eprosima::ddsenabler::participants::UUID request_id;
+ std::string goal_json;
+ if (!wait_for_action_request(timeout, app_mutex_, app_cv_, request_id, goal_json))
+ {
+ return false;
+ }
+
+ uint64_t fibonacci_number = 5; // Default Fibonacci number, can be parsed from json if needed
+ std::cout << "Received request for action: " << action_name << std::endl;
+ std::this_thread::sleep_for(std::chrono::milliseconds(500)); // Simulate processing time
+
+ // Send Feedback & Result
+ if (!server_specific_logic(
+ enabler,
+ action_name,
+ fibonacci_number,
+ request_id))
+ {
+ std::cerr << "Failed to process action: " << action_name << std::endl;
+ return false;
+ }
+
+ // Check if we have received the expected number of requests (or run indefinitely if expected_requests is 0)
+ {
+ std::lock_guard lock(app_mutex_);
+ if (expected_requests != 0 && ++received_results_ >= expected_requests)
+ {
+ break;
+ }
+ stop_app = stop_app_;
+ }
+ }
+
+ // Revoke service
+ if (!enabler->revoke_action(action_name))
+ {
+ std::cerr << "Failed to revoke service: " << action_name << std::endl;
+ return false;
+ }
+
+ return true;
+}
+
+int main(
+ int argc,
+ char** argv)
+{
+ signal(SIGINT, signal_handler);
+ signal(SIGTERM, signal_handler);
+#ifndef _WIN32
+ signal(SIGQUIT, signal_handler);
+ signal(SIGHUP, signal_handler);
+#endif // _WIN32
+
+ using namespace eprosima::ddsenabler;
+
+ eprosima::utils::Log::ReportFilenames(true);
+
+ // Parse CLI options
+ config = CLIParser::parse_cli_options(argc, argv);
+
+ // Initialize persistence if required
+ std::vector subdirs = {TYPES_SUBDIR, ACTION_SUBDIR};
+ utils::init_persistence(config.persistence_path, subdirs);
+
+ // Set up callbacks
+ CallbackSet callbacks{
+ test_log_callback,
+ {
+ test_type_notification_callback,
+ test_topic_notification_callback,
+ test_data_notification_callback,
+ test_type_query_callback,
+ test_topic_query_callback
+ },
+ {
+ // NOTE: Service callbacks are not used in this example, but can be added if needed
+ },
+ {
+ test_action_notification_callback,
+ test_action_goal_request_notification_callback,
+ test_action_feedback_notification_callback,
+ test_action_cancel_request_notification_callback,
+ test_action_result_notification_callback,
+ test_action_status_notification_callback,
+ test_action_query_callback
+ }
+ };
+
+ std::shared_ptr enabler;
+ bool enabler_created = false;
+ if (config.config_file_path.empty())
+ {
+ enabler_created = create_dds_enabler(yaml::EnablerConfiguration(""), callbacks, enabler);
+ }
+ else
+ {
+ enabler_created = create_dds_enabler(config.config_file_path.c_str(), callbacks, enabler);
+ }
+ if (!enabler_created)
+ {
+ std::cerr << "Failed to create DDSEnabler instance." << std::endl;
+ return EXIT_FAILURE;
+ }
+
+ bool ret = false;
+ // Service logic based on announce_server flag
+ if (config.announce_server)
+ {
+
+ ret = server_routine(enabler, config.action_name, config.expected_requests, config.timeout);
+ }
+ else
+ {
+ if (config.goals_path.empty())
+ {
+ std::cerr << "Request path is not set." << std::endl;
+ return EXIT_FAILURE;
+ }
+
+ ret = client_routine(enabler, config.action_name, config.goals_path, config.timeout,
+ config.request_initial_wait,
+ config.cancel_requests);
+ }
+
+ return ret ? EXIT_SUCCESS : EXIT_FAILURE;
+}
diff --git a/ddsenabler/examples/config.yml b/ddsenabler/examples/config.yml
deleted file mode 100644
index 5cc4050d..00000000
--- a/ddsenabler/examples/config.yml
+++ /dev/null
@@ -1,26 +0,0 @@
-dds:
- domain: 0
-
- allowlist:
- - name: "topic_name"
- - name: "HelloWorldTopic"
- type: "HelloWorld"
-
- blocklist:
- - name: "topic_to_block"
- type: "type_to_block"
-
- topics:
- - name: "temperature/*"
- type: "temperature/types/*"
- qos:
- max-rx-rate: 15
- downsampling: 2
-
- ignore-participant-flags: filter_same_process
- transport: shm
- whitelist-interfaces:
- - "127.0.0.1"
-
-ddsenabler:
- initial-publish-wait: 500
diff --git a/ddsenabler/examples/persistence/actions/fibonacci__action_ b/ddsenabler/examples/persistence/actions/fibonacci__action_
new file mode 100644
index 00000000..22db6867
--- /dev/null
+++ b/ddsenabler/examples/persistence/actions/fibonacci__action_
@@ -0,0 +1,19 @@
+{
+ "action_name": "fibonacci/_action/",
+ "cancel_reply_action_serialized_qos": "reliability: true\ndurability: false\nownership: false\nkeyed: false",
+ "cancel_reply_action_type": "action_msgs::srv::dds_::CancelGoal_Response_",
+ "cancel_request_action_serialized_qos": "reliability: true\ndurability: false\nownership: false\nkeyed: false",
+ "cancel_request_action_type": "action_msgs::srv::dds_::CancelGoal_Request_",
+ "feedback_action_serialized_qos": "reliability: true\ndurability: false\nownership: false\nkeyed: false",
+ "feedback_action_type": "action_tutorials_interfaces::action::dds_::Fibonacci_FeedbackMessage_",
+ "goal_reply_action_serialized_qos": "reliability: true\ndurability: false\nownership: false\nkeyed: false",
+ "goal_reply_action_type": "action_tutorials_interfaces::action::dds_::Fibonacci_SendGoal_Response_",
+ "goal_request_action_serialized_qos": "reliability: true\ndurability: false\nownership: false\nkeyed: false",
+ "goal_request_action_type": "action_tutorials_interfaces::action::dds_::Fibonacci_SendGoal_Request_",
+ "result_reply_action_serialized_qos": "reliability: true\ndurability: false\nownership: false\nkeyed: false",
+ "result_reply_action_type": "action_tutorials_interfaces::action::dds_::Fibonacci_GetResult_Response_",
+ "result_request_action_serialized_qos": "reliability: true\ndurability: false\nownership: false\nkeyed: false",
+ "result_request_action_type": "action_tutorials_interfaces::action::dds_::Fibonacci_GetResult_Request_",
+ "status_action_serialized_qos": "reliability: true\ndurability: true\nownership: false\nkeyed: false",
+ "status_action_type": "action_msgs::msg::dds_::GoalStatusArray_"
+}
\ No newline at end of file
diff --git a/ddsenabler/examples/persistence/services/add_two_ints b/ddsenabler/examples/persistence/services/add_two_ints
new file mode 100644
index 00000000..5af99fa0
--- /dev/null
+++ b/ddsenabler/examples/persistence/services/add_two_ints
@@ -0,0 +1,7 @@
+{
+ "reply_serialized_qos": "reliability: true\ndurability: false\nownership: false\nkeyed: false",
+ "reply_type_name": "example_interfaces::srv::dds_::AddTwoInts_Response_",
+ "request_serialized_qos": "reliability: true\ndurability: false\nownership: false\nkeyed: false",
+ "request_type_name": "example_interfaces::srv::dds_::AddTwoInts_Request_",
+ "service_name": "add_two_ints"
+}
\ No newline at end of file
diff --git a/ddsenabler/examples/persistence/topics/rt_chatter.bin b/ddsenabler/examples/persistence/topics/rt_chatter.bin
new file mode 100644
index 00000000..87325663
Binary files /dev/null and b/ddsenabler/examples/persistence/topics/rt_chatter.bin differ
diff --git a/ddsenabler/examples/persistence/types/action_msgs__msg__dds___GoalStatusArray_.bin b/ddsenabler/examples/persistence/types/action_msgs__msg__dds___GoalStatusArray_.bin
new file mode 100644
index 00000000..9b2666b4
Binary files /dev/null and b/ddsenabler/examples/persistence/types/action_msgs__msg__dds___GoalStatusArray_.bin differ
diff --git a/ddsenabler/examples/persistence/types/action_msgs__srv__dds___CancelGoal_Request_.bin b/ddsenabler/examples/persistence/types/action_msgs__srv__dds___CancelGoal_Request_.bin
new file mode 100644
index 00000000..3312fb7d
Binary files /dev/null and b/ddsenabler/examples/persistence/types/action_msgs__srv__dds___CancelGoal_Request_.bin differ
diff --git a/ddsenabler/examples/persistence/types/action_msgs__srv__dds___CancelGoal_Response_.bin b/ddsenabler/examples/persistence/types/action_msgs__srv__dds___CancelGoal_Response_.bin
new file mode 100644
index 00000000..9b9e0b42
Binary files /dev/null and b/ddsenabler/examples/persistence/types/action_msgs__srv__dds___CancelGoal_Response_.bin differ
diff --git a/ddsenabler/examples/persistence/types/action_tutorials_interfaces__action__dds___Fibonacci_FeedbackMessage_.bin b/ddsenabler/examples/persistence/types/action_tutorials_interfaces__action__dds___Fibonacci_FeedbackMessage_.bin
new file mode 100644
index 00000000..fac6aee9
Binary files /dev/null and b/ddsenabler/examples/persistence/types/action_tutorials_interfaces__action__dds___Fibonacci_FeedbackMessage_.bin differ
diff --git a/ddsenabler/examples/persistence/types/action_tutorials_interfaces__action__dds___Fibonacci_GetResult_Request_.bin b/ddsenabler/examples/persistence/types/action_tutorials_interfaces__action__dds___Fibonacci_GetResult_Request_.bin
new file mode 100644
index 00000000..423f3111
Binary files /dev/null and b/ddsenabler/examples/persistence/types/action_tutorials_interfaces__action__dds___Fibonacci_GetResult_Request_.bin differ
diff --git a/ddsenabler/examples/persistence/types/action_tutorials_interfaces__action__dds___Fibonacci_GetResult_Response_.bin b/ddsenabler/examples/persistence/types/action_tutorials_interfaces__action__dds___Fibonacci_GetResult_Response_.bin
new file mode 100644
index 00000000..62adda76
Binary files /dev/null and b/ddsenabler/examples/persistence/types/action_tutorials_interfaces__action__dds___Fibonacci_GetResult_Response_.bin differ
diff --git a/ddsenabler/examples/persistence/types/action_tutorials_interfaces__action__dds___Fibonacci_SendGoal_Request_.bin b/ddsenabler/examples/persistence/types/action_tutorials_interfaces__action__dds___Fibonacci_SendGoal_Request_.bin
new file mode 100644
index 00000000..4bffcfdd
Binary files /dev/null and b/ddsenabler/examples/persistence/types/action_tutorials_interfaces__action__dds___Fibonacci_SendGoal_Request_.bin differ
diff --git a/ddsenabler/examples/persistence/types/action_tutorials_interfaces__action__dds___Fibonacci_SendGoal_Response_.bin b/ddsenabler/examples/persistence/types/action_tutorials_interfaces__action__dds___Fibonacci_SendGoal_Response_.bin
new file mode 100644
index 00000000..63bb3f25
Binary files /dev/null and b/ddsenabler/examples/persistence/types/action_tutorials_interfaces__action__dds___Fibonacci_SendGoal_Response_.bin differ
diff --git a/ddsenabler/examples/persistence/types/example_interfaces__srv__dds___AddTwoInts_Request_.bin b/ddsenabler/examples/persistence/types/example_interfaces__srv__dds___AddTwoInts_Request_.bin
new file mode 100644
index 00000000..4b4e5e7d
Binary files /dev/null and b/ddsenabler/examples/persistence/types/example_interfaces__srv__dds___AddTwoInts_Request_.bin differ
diff --git a/ddsenabler/examples/persistence/types/example_interfaces__srv__dds___AddTwoInts_Response_.bin b/ddsenabler/examples/persistence/types/example_interfaces__srv__dds___AddTwoInts_Response_.bin
new file mode 100644
index 00000000..6f5f40ef
Binary files /dev/null and b/ddsenabler/examples/persistence/types/example_interfaces__srv__dds___AddTwoInts_Response_.bin differ
diff --git a/ddsenabler/examples/persistence/types/rcl_interfaces__msg__dds___Log_.bin b/ddsenabler/examples/persistence/types/rcl_interfaces__msg__dds___Log_.bin
new file mode 100644
index 00000000..6e56d56a
Binary files /dev/null and b/ddsenabler/examples/persistence/types/rcl_interfaces__msg__dds___Log_.bin differ
diff --git a/ddsenabler/examples/persistence/types/rcl_interfaces__msg__dds___ParameterEvent_.bin b/ddsenabler/examples/persistence/types/rcl_interfaces__msg__dds___ParameterEvent_.bin
new file mode 100644
index 00000000..98649ae7
Binary files /dev/null and b/ddsenabler/examples/persistence/types/rcl_interfaces__msg__dds___ParameterEvent_.bin differ
diff --git a/ddsenabler/examples/persistence/types/rcl_interfaces__srv__dds___DescribeParameters_Request_.bin b/ddsenabler/examples/persistence/types/rcl_interfaces__srv__dds___DescribeParameters_Request_.bin
new file mode 100644
index 00000000..d33a48d9
Binary files /dev/null and b/ddsenabler/examples/persistence/types/rcl_interfaces__srv__dds___DescribeParameters_Request_.bin differ
diff --git a/ddsenabler/examples/persistence/types/rcl_interfaces__srv__dds___DescribeParameters_Response_.bin b/ddsenabler/examples/persistence/types/rcl_interfaces__srv__dds___DescribeParameters_Response_.bin
new file mode 100644
index 00000000..f7a41219
Binary files /dev/null and b/ddsenabler/examples/persistence/types/rcl_interfaces__srv__dds___DescribeParameters_Response_.bin differ
diff --git a/ddsenabler/examples/persistence/types/rcl_interfaces__srv__dds___GetParameterTypes_Request_.bin b/ddsenabler/examples/persistence/types/rcl_interfaces__srv__dds___GetParameterTypes_Request_.bin
new file mode 100644
index 00000000..f1cf3f19
Binary files /dev/null and b/ddsenabler/examples/persistence/types/rcl_interfaces__srv__dds___GetParameterTypes_Request_.bin differ
diff --git a/ddsenabler/examples/persistence/types/rcl_interfaces__srv__dds___GetParameterTypes_Response_.bin b/ddsenabler/examples/persistence/types/rcl_interfaces__srv__dds___GetParameterTypes_Response_.bin
new file mode 100644
index 00000000..55815c1f
Binary files /dev/null and b/ddsenabler/examples/persistence/types/rcl_interfaces__srv__dds___GetParameterTypes_Response_.bin differ
diff --git a/ddsenabler/examples/persistence/types/rcl_interfaces__srv__dds___GetParameters_Request_.bin b/ddsenabler/examples/persistence/types/rcl_interfaces__srv__dds___GetParameters_Request_.bin
new file mode 100644
index 00000000..fe10b8fb
Binary files /dev/null and b/ddsenabler/examples/persistence/types/rcl_interfaces__srv__dds___GetParameters_Request_.bin differ
diff --git a/ddsenabler/examples/persistence/types/rcl_interfaces__srv__dds___GetParameters_Response_.bin b/ddsenabler/examples/persistence/types/rcl_interfaces__srv__dds___GetParameters_Response_.bin
new file mode 100644
index 00000000..ac082bff
Binary files /dev/null and b/ddsenabler/examples/persistence/types/rcl_interfaces__srv__dds___GetParameters_Response_.bin differ
diff --git a/ddsenabler/examples/persistence/types/rcl_interfaces__srv__dds___ListParameters_Request_.bin b/ddsenabler/examples/persistence/types/rcl_interfaces__srv__dds___ListParameters_Request_.bin
new file mode 100644
index 00000000..9b9387d7
Binary files /dev/null and b/ddsenabler/examples/persistence/types/rcl_interfaces__srv__dds___ListParameters_Request_.bin differ
diff --git a/ddsenabler/examples/persistence/types/rcl_interfaces__srv__dds___ListParameters_Response_.bin b/ddsenabler/examples/persistence/types/rcl_interfaces__srv__dds___ListParameters_Response_.bin
new file mode 100644
index 00000000..a1212fb4
Binary files /dev/null and b/ddsenabler/examples/persistence/types/rcl_interfaces__srv__dds___ListParameters_Response_.bin differ
diff --git a/ddsenabler/examples/persistence/types/rcl_interfaces__srv__dds___SetParametersAtomically_Request_.bin b/ddsenabler/examples/persistence/types/rcl_interfaces__srv__dds___SetParametersAtomically_Request_.bin
new file mode 100644
index 00000000..c19eeb6a
Binary files /dev/null and b/ddsenabler/examples/persistence/types/rcl_interfaces__srv__dds___SetParametersAtomically_Request_.bin differ
diff --git a/ddsenabler/examples/persistence/types/rcl_interfaces__srv__dds___SetParametersAtomically_Response_.bin b/ddsenabler/examples/persistence/types/rcl_interfaces__srv__dds___SetParametersAtomically_Response_.bin
new file mode 100644
index 00000000..1043f036
Binary files /dev/null and b/ddsenabler/examples/persistence/types/rcl_interfaces__srv__dds___SetParametersAtomically_Response_.bin differ
diff --git a/ddsenabler/examples/persistence/types/rcl_interfaces__srv__dds___SetParameters_Request_.bin b/ddsenabler/examples/persistence/types/rcl_interfaces__srv__dds___SetParameters_Request_.bin
new file mode 100644
index 00000000..da5208da
Binary files /dev/null and b/ddsenabler/examples/persistence/types/rcl_interfaces__srv__dds___SetParameters_Request_.bin differ
diff --git a/ddsenabler/examples/persistence/types/rcl_interfaces__srv__dds___SetParameters_Response_.bin b/ddsenabler/examples/persistence/types/rcl_interfaces__srv__dds___SetParameters_Response_.bin
new file mode 100644
index 00000000..7b8a8ec8
Binary files /dev/null and b/ddsenabler/examples/persistence/types/rcl_interfaces__srv__dds___SetParameters_Response_.bin differ
diff --git a/ddsenabler/examples/persistence/types/rmw_dds_common__msg__dds___ParticipantEntitiesInfo_.bin b/ddsenabler/examples/persistence/types/rmw_dds_common__msg__dds___ParticipantEntitiesInfo_.bin
new file mode 100644
index 00000000..c3236d96
Binary files /dev/null and b/ddsenabler/examples/persistence/types/rmw_dds_common__msg__dds___ParticipantEntitiesInfo_.bin differ
diff --git a/ddsenabler/examples/persistence/types/std_msgs__msg__dds___String_.bin b/ddsenabler/examples/persistence/types/std_msgs__msg__dds___String_.bin
new file mode 100644
index 00000000..991d0cdb
Binary files /dev/null and b/ddsenabler/examples/persistence/types/std_msgs__msg__dds___String_.bin differ
diff --git a/ddsenabler/examples/persistence/types/type_description_interfaces__srv__dds___GetTypeDescription_Request_.bin b/ddsenabler/examples/persistence/types/type_description_interfaces__srv__dds___GetTypeDescription_Request_.bin
new file mode 100644
index 00000000..ba4d778e
Binary files /dev/null and b/ddsenabler/examples/persistence/types/type_description_interfaces__srv__dds___GetTypeDescription_Request_.bin differ
diff --git a/ddsenabler/examples/persistence/types/type_description_interfaces__srv__dds___GetTypeDescription_Response_.bin b/ddsenabler/examples/persistence/types/type_description_interfaces__srv__dds___GetTypeDescription_Response_.bin
new file mode 100644
index 00000000..f68b4410
Binary files /dev/null and b/ddsenabler/examples/persistence/types/type_description_interfaces__srv__dds___GetTypeDescription_Response_.bin differ
diff --git a/ddsenabler/examples/CLIParser.hpp b/ddsenabler/examples/publish/CLIParser.hpp
similarity index 97%
rename from ddsenabler/examples/CLIParser.hpp
rename to ddsenabler/examples/publish/CLIParser.hpp
index a9469f4f..8dfae01d 100644
--- a/ddsenabler/examples/CLIParser.hpp
+++ b/ddsenabler/examples/publish/CLIParser.hpp
@@ -51,7 +51,7 @@ class CLIParser
static void print_help(
uint8_t return_code)
{
- std::cout << "Usage: ddsenabler_example [options]" << std::endl;
+ std::cout << "Usage: ddsenabler_example_publish [options]" << std::endl;
std::cout << "" << std::endl;
std::cout << "--config Path to the configuration file" << std::endl;
std::cout << " (Default: '')" << std::endl;
@@ -302,12 +302,6 @@ class CLIParser
}
}
- if (config.config_file_path.empty())
- {
- std::cerr << "Configuration file path is required" << std::endl;
- print_help(EXIT_FAILURE);
- }
-
// Check that if publish path is set, publish topic is also set
if (!config.publish_path.empty() && config.publish_topic.empty())
{
diff --git a/ddsenabler/examples/publish/CMakeLists.txt b/ddsenabler/examples/publish/CMakeLists.txt
new file mode 100644
index 00000000..3839f418
--- /dev/null
+++ b/ddsenabler/examples/publish/CMakeLists.txt
@@ -0,0 +1,32 @@
+# Copyright 2025 Proyectos y Sistemas de Mantenimiento SL (eProsima).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+cmake_minimum_required(VERSION 3.20)
+project(ddsenabler_example_publish LANGUAGES CXX)
+
+add_executable(ddsenabler_example_publish main.cpp)
+
+target_include_directories(ddsenabler_example_publish PRIVATE ${PROJECT_SOURCE_DIR}/../utils)
+target_link_libraries(ddsenabler_example_publish PRIVATE ddsenabler utils)
+
+# Install rule
+install(TARGETS ddsenabler_example_publish
+ RUNTIME DESTINATION examples/publish
+)
+
+# Install samples directory
+install(
+ DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/samples
+ DESTINATION examples/publish
+)
diff --git a/ddsenabler/examples/publish/Readme.md b/ddsenabler/examples/publish/Readme.md
new file mode 100644
index 00000000..91e21945
--- /dev/null
+++ b/ddsenabler/examples/publish/Readme.md
@@ -0,0 +1,30 @@
+# Publish Example Readme
+
+This example demonstrates the usage of the DDS Enabler for implementing a publish-subscribe pattern. The example includes a publisher and a subscriber setup for the desired topic.
+
+The following example commands illustrate how to run the publisher and subscriber to match with the `talker` and `listener` examples provided in the ROS2 demos.
+
+## Example Commands
+
+### PUBLISHER
+```bash
+./install/ddsenabler/examples/publish/ddsenabler_example_publish \
+ --persistence-path ./install/ddsenabler/examples/persistence/ \
+ --publish-path ./install/ddsenabler/examples/publish/samples \
+ --publish-period 200 \
+ --publish-initial-wait 1000 \
+ --publish-topic rt/chatter
+ --timeout 5
+ --config $PATH_TO_CONFIG_FILE
+```
+
+### SUBSCRIBER
+```bash
+./install/ddsenabler/examples/publish/ddsenabler_example_publish \
+ --persistence-path ./install/ddsenabler/examples/persistence/ \
+ --expected-types 1 \
+ --expected-topics 1 \
+ --expected-data 5
+ --timeout 5
+ --config $PATH_TO_CONFIG_FILE
+```
diff --git a/ddsenabler/examples/main.cpp b/ddsenabler/examples/publish/main.cpp
similarity index 77%
rename from ddsenabler/examples/main.cpp
rename to ddsenabler/examples/publish/main.cpp
index 9976c3f6..e144dd75 100644
--- a/ddsenabler/examples/main.cpp
+++ b/ddsenabler/examples/publish/main.cpp
@@ -28,11 +28,12 @@
#include
#include
+#include
+
#include "ddsenabler/dds_enabler_runner.hpp"
#include "ddsenabler/DDSEnabler.hpp"
#include "CLIParser.hpp"
-#include "utils.hpp"
CLIParser::example_config config;
uint32_t received_types_ = 0;
@@ -87,7 +88,8 @@ static void test_type_notification_callback(
std::cout << "Type callback received: " << type_name << ", Total types: " <<
received_types_ << std::endl << serialized_type << std::endl << std::endl;
if (!config.persistence_path.empty() &&
- !save_type_to_file((std::filesystem::path(config.persistence_path) / TYPES_SUBDIR).string(), type_name,
+ !utils::save_type_to_file((std::filesystem::path(config.persistence_path) / TYPES_SUBDIR).string(),
+ type_name,
serialized_type_internal, serialized_type_internal_size))
{
std::cerr << "Failed to save type: " << type_name << std::endl;
@@ -112,7 +114,7 @@ static bool test_type_query_callback(
}
// Load the type from file
- if (!load_type_from_file((std::filesystem::path(config.persistence_path) / TYPES_SUBDIR).string(), type_name,
+ if (!utils::load_type_from_file((std::filesystem::path(config.persistence_path) / TYPES_SUBDIR).string(), type_name,
serialized_type_internal, serialized_type_internal_size))
{
std::cerr << "Failed to load type: " << type_name << std::endl;
@@ -124,19 +126,19 @@ static bool test_type_query_callback(
// Static topic notification callback
static void test_topic_notification_callback(
const char* topic_name,
- const char* type_name,
- const char* serialized_qos)
+ const eprosima::ddsenabler::participants::TopicInfo& topic_info)
{
bool notify = false;
{
std::lock_guard lock(app_mutex_);
notify = ++received_topics_ >= config.expected_topics;
- std::cout << "Topic callback received: " << topic_name << " of type " << type_name << ", Total topics: " <<
- received_topics_ << std::endl << serialized_qos << std::endl << std::endl;
+ std::cout << "Topic callback received: " << topic_name << " of type " << topic_info.type_name <<
+ ", Total topics: " <<
+ received_topics_ << std::endl << topic_info.serialized_qos << std::endl << std::endl;
if (!config.persistence_path.empty() &&
- !save_topic_to_file((std::filesystem::path(config.persistence_path) / TOPICS_SUBDIR).string(),
+ !utils::save_topic_to_file((std::filesystem::path(config.persistence_path) / TOPICS_SUBDIR).string(),
topic_name,
- type_name, serialized_qos))
+ topic_info))
{
std::cerr << "Failed to save topic: " << topic_name << std::endl;
}
@@ -150,8 +152,7 @@ static void test_topic_notification_callback(
// Static type query callback
static bool test_topic_query_callback(
const char* topic_name,
- std::string& type_name,
- std::string& serialized_qos)
+ eprosima::ddsenabler::participants::TopicInfo& topic_info)
{
if (config.persistence_path.empty())
{
@@ -160,9 +161,9 @@ static bool test_topic_query_callback(
}
// Load the topic from file
- if (!load_topic_from_file((std::filesystem::path(config.persistence_path) / TOPICS_SUBDIR).string(), topic_name,
- type_name,
- serialized_qos))
+ if (!utils::load_topic_from_file((std::filesystem::path(config.persistence_path) / TOPICS_SUBDIR).string(),
+ topic_name,
+ topic_info))
{
std::cerr << "Failed to load topic: " << topic_name << std::endl;
return false;
@@ -183,7 +184,7 @@ static void test_data_notification_callback(
std::cout << "Data callback received: " << topic_name << ", Total data: " <<
received_data_ << std::endl << json << std::endl << std::endl;
if (!config.persistence_path.empty() &&
- !save_data_to_file((std::filesystem::path(config.persistence_path) / SAMPLES_SUBDIR).string(),
+ !utils::save_data_to_file((std::filesystem::path(config.persistence_path) / SAMPLES_SUBDIR).string(),
topic_name, json,
publish_time))
{
@@ -215,57 +216,6 @@ bool expected_received(
return validate_received(config);
}
-void init_persistence(
- const std::string& persistence_path)
-{
- auto ensure_directory_exists = [](const std::filesystem::path& path)
- {
- if (!std::filesystem::exists(path) && !std::filesystem::create_directories(path))
- {
- std::cerr << "Failed to create directory: " << path << std::endl;
- }
- };
-
- if (!persistence_path.empty())
- {
- ensure_directory_exists(persistence_path);
- std::vector subdirs = {SAMPLES_SUBDIR, TYPES_SUBDIR, TOPICS_SUBDIR};
- for (const auto& sub : subdirs)
- {
- ensure_directory_exists(std::filesystem::path(persistence_path) / sub);
- }
- }
-}
-
-void get_sorted_files(
- const std::string& directory,
- std::vector>& files)
-{
- for (const auto& entry : std::filesystem::directory_iterator(directory))
- {
- if (entry.is_regular_file())
- {
- std::string filename = entry.path().filename().string();
- try
- {
- // assumes name is just a number
- files.emplace_back(entry.path(), static_cast(std::stoll(filename)));
- }
- catch (const std::invalid_argument& e)
- {
- std::cerr << "Skipping non-numeric file: " << filename << std::endl;
- }
- }
- }
-
- // Sort files by numeric value
- std::sort(files.begin(), files.end(),
- [](const auto& a, const auto& b)
- {
- return a.second < b.second;
- });
-}
-
void publish_routine(
std::shared_ptr enabler,
const std::string& publish_path,
@@ -281,7 +231,7 @@ void publish_routine(
// Get collection of files to publish, sorted in increasing order by their name (assumed to be numeric)
std::vector> sample_files;
- get_sorted_files(publish_path, sample_files);
+ utils::get_sorted_files(publish_path, sample_files);
for (const auto& [path, number] : sample_files)
{
@@ -341,7 +291,8 @@ int main(
config = CLIParser::parse_cli_options(argc, argv);
- init_persistence(config.persistence_path);
+ std::vector subdirs = {SAMPLES_SUBDIR, TYPES_SUBDIR, TOPICS_SUBDIR};
+ utils::init_persistence(config.persistence_path, subdirs);
CallbackSet callbacks{
test_log_callback,
@@ -355,9 +306,18 @@ int main(
};
std::shared_ptr enabler;
- if (!create_dds_enabler(config.config_file_path.c_str(), callbacks, enabler))
+ bool enabler_created = false;
+ if (config.config_file_path.empty())
+ {
+ enabler_created = create_dds_enabler(yaml::EnablerConfiguration(""), callbacks, enabler);
+ }
+ else
+ {
+ enabler_created = create_dds_enabler(config.config_file_path.c_str(), callbacks, enabler);
+ }
+ if (!enabler_created)
{
- std::cerr << "Failed to create DDS Enabler with the provided configuration." << std::endl;
+ std::cerr << "Failed to create DDSEnabler instance." << std::endl;
return EXIT_FAILURE;
}
diff --git a/ddsenabler/examples/publish/samples/1 b/ddsenabler/examples/publish/samples/1
new file mode 100644
index 00000000..2f813e6c
--- /dev/null
+++ b/ddsenabler/examples/publish/samples/1
@@ -0,0 +1,3 @@
+{
+ "data": "Hello World: 1"
+}
diff --git a/ddsenabler/examples/publish/samples/2 b/ddsenabler/examples/publish/samples/2
new file mode 100644
index 00000000..35641d7f
--- /dev/null
+++ b/ddsenabler/examples/publish/samples/2
@@ -0,0 +1,3 @@
+{
+ "data": "Hello World: 2"
+}
diff --git a/ddsenabler/examples/publish/samples/3 b/ddsenabler/examples/publish/samples/3
new file mode 100644
index 00000000..cae03a51
--- /dev/null
+++ b/ddsenabler/examples/publish/samples/3
@@ -0,0 +1,3 @@
+{
+ "data": "Hello World: 3"
+}
diff --git a/ddsenabler/examples/publish/samples/4 b/ddsenabler/examples/publish/samples/4
new file mode 100644
index 00000000..112b310d
--- /dev/null
+++ b/ddsenabler/examples/publish/samples/4
@@ -0,0 +1,3 @@
+{
+ "data": "Hello World: 4"
+}
diff --git a/ddsenabler/examples/publish/samples/5 b/ddsenabler/examples/publish/samples/5
new file mode 100644
index 00000000..5c53f169
--- /dev/null
+++ b/ddsenabler/examples/publish/samples/5
@@ -0,0 +1,3 @@
+{
+ "data": "Hello World: 5"
+}
diff --git a/ddsenabler/examples/service/CLIParser.hpp b/ddsenabler/examples/service/CLIParser.hpp
new file mode 100644
index 00000000..f874daa5
--- /dev/null
+++ b/ddsenabler/examples/service/CLIParser.hpp
@@ -0,0 +1,326 @@
+// Copyright 2025 Proyectos y Sistemas de Mantenimiento SL (eProsima).
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include
+#include
+#include
+#include
+
+#include
+
+#pragma once
+
+class CLIParser
+{
+public:
+
+ CLIParser() = delete;
+
+ struct example_config
+ {
+ std::string config_file_path = "";
+ std::string service_name = "add_two_ints";
+ bool announce_server = true;
+ uint32_t timeout = 30;
+ std::string persistence_path = "";
+ std::string requests_path = "";
+ uint32_t expected_requests = 0;
+ uint32_t request_initial_wait = 0;
+ };
+
+ /**
+ * @brief Print usage help message and exit with the given return code
+ *
+ * @param return_code return code to exit with
+ *
+ * @warning This method finishes the execution of the program with the input return code
+ */
+ static void print_help(
+ uint8_t return_code)
+ {
+ std::cout << "Usage: ddsenabler_example_service [options]" <<
+ std::endl;
+ std::cout << "" <<
+ std::endl;
+ std::cout << "--config Path to the configuration file" <<
+ std::endl;
+ std::cout << " (optional, if not provided default config is used)" <<
+ std::endl;
+ std::cout << "--service-name Name of the service to be registered" <<
+ std::endl;
+ std::cout << " (Default: 'add_two_ints')" <<
+ std::endl;
+ std::cout << "--timeout Time (seconds) to wait before stopping the" <<
+ std::endl;
+ std::cout << " program if expectations are not met" <<
+ std::endl;
+ std::cout << " (Default: 30)" <<
+ std::endl;
+ std::cout << "--persistence-path Path to the persistence directory" <<
+ std::endl;
+ std::cout << " (No default value as it is required)" <<
+ std::endl;
+ std::cout << "\n-------------------------------------SERVER OPTIONS------------------------------------\n" <<
+ std::endl;
+ std::cout << "--expected-requests Number of requests expected to be received" <<
+ std::endl;
+ std::cout << " (Default: 0, meaning infinite)" <<
+ std::endl;
+ std::cout << "\n-------------------------------------CLIENT OPTIONS------------------------------------\n" <<
+ std::endl;
+ std::cout << "--request-initial-wait Time (seconds) to wait before starting" <<
+ std::endl;
+ std::cout << " requests publication since server matching" <<
+ std::endl;
+ std::cout << " (Default: 0)" <<
+ std::endl;
+ std::cout << "--requests-path Directory containing request JSON files" <<
+ std::endl;
+ std::cout << " (No default value as it is required for client mode)" <<
+ std::endl;
+ std::exit(return_code);
+ }
+
+ /**
+ * @brief Parse the command line options and return the configuration_config object
+ *
+ * @param argc number of arguments
+ * @param argv array of arguments
+ * @return configuration_config object with the parsed options
+ *
+ * @warning This method finishes the execution of the program if the input arguments are invalid
+ */
+ static example_config parse_cli_options(
+ int argc,
+ char* argv[])
+ {
+ example_config config;
+
+ if (argc < 2)
+ {
+ std::cerr << "Mode is required as the first argument: 'client' or 'server'" << std::endl;
+ print_help(EXIT_FAILURE);
+ }
+
+ bool client_flag = false;
+ bool server_flag = false;
+
+ // First positional argument must be the mode
+ std::string mode = argv[1];
+ if (mode == "-h" || mode == "--help")
+ {
+ print_help(EXIT_SUCCESS);
+ }
+ else if (mode == "client")
+ {
+ client_flag = true;
+ config.announce_server = false;
+ }
+ else if (mode == "server")
+ {
+ server_flag = true;
+ config.announce_server = true;
+ }
+ else
+ {
+ std::cerr << "Invalid mode '" << mode << "'. First argument must be 'client' or 'server'" << std::endl;
+ print_help(EXIT_FAILURE);
+ }
+
+ // Parse optional arguments starting from index 2
+ for (int i = 2; i < argc; ++i)
+ {
+ std::string arg = argv[i];
+
+ if (arg == "-h" || arg == "--help")
+ {
+ print_help(EXIT_SUCCESS);
+ }
+ else if (arg == "--config")
+ {
+ if (++i < argc)
+ {
+ config.config_file_path = argv[i];
+ if (!std::filesystem::exists(config.config_file_path))
+ {
+ std::cerr << "Invalid configuration file path: " << config.config_file_path << std::endl;
+ print_help(EXIT_FAILURE);
+ }
+ }
+ else
+ {
+ std::cerr << "Failed to parse --config argument" << std::endl;
+ print_help(EXIT_FAILURE);
+ }
+ }
+ else if (arg == "--service-name")
+ {
+ if (++i < argc)
+ {
+ config.service_name = argv[i];
+ }
+ else
+ {
+ std::cerr << "Failed to parse --service-name argument" << std::endl;
+ print_help(EXIT_FAILURE);
+ }
+ }
+ else if (arg == "--timeout")
+ {
+ if (++i < argc)
+ {
+ try
+ {
+ config.timeout = static_cast(std::stoi(argv[i]));
+ }
+ catch (const std::exception& e)
+ {
+ std::cerr << "Invalid --timeout argument " << argv[i] << ": " << e.what() << std::endl;
+ print_help(EXIT_FAILURE);
+ }
+ }
+ else
+ {
+ std::cerr << "Failed to parse --timeout argument" << std::endl;
+ print_help(EXIT_FAILURE);
+ }
+ }
+ else if (arg == "--persistence-path")
+ {
+ if (++i < argc)
+ {
+ config.persistence_path = argv[i];
+ }
+ else
+ {
+ std::cerr << "Failed to parse --persistence-path argument" << std::endl;
+ print_help(EXIT_FAILURE);
+ }
+ }
+ else if (arg == "--expected-requests")
+ {
+ if (++i < argc)
+ {
+ try
+ {
+ config.expected_requests = static_cast(std::stoi(argv[i]));
+ }
+ catch (const std::exception& e)
+ {
+ std::cerr << "Invalid --expected-requests argument " << argv[i] << ": " << e.what() <<
+ std::endl;
+ print_help(EXIT_FAILURE);
+ }
+ }
+ else
+ {
+ std::cerr << "Failed to parse --expected-requests argument" << std::endl;
+ print_help(EXIT_FAILURE);
+ }
+ }
+ else if (arg == "--request-initial-wait")
+ {
+ if (++i < argc)
+ {
+ try
+ {
+ config.request_initial_wait = static_cast(std::stoi(argv[i]));
+ }
+ catch (const std::exception& e)
+ {
+ std::cerr << "Invalid --request-initial-wait argument " << argv[i] << ": " << e.what() <<
+ std::endl;
+ print_help(EXIT_FAILURE);
+ }
+ }
+ else
+ {
+ std::cerr << "Failed to parse --request-initial-wait argument" << std::endl;
+ print_help(EXIT_FAILURE);
+ }
+ }
+ else if (arg == "--requests-path")
+ {
+ if (++i < argc)
+ {
+ config.requests_path = argv[i];
+ if (!std::filesystem::exists(config.requests_path) ||
+ !std::filesystem::is_directory(config.requests_path))
+ {
+ std::cerr << "Invalid --requests-path directory: " << config.requests_path << std::endl;
+ print_help(EXIT_FAILURE);
+ }
+ }
+ else
+ {
+ std::cerr << "Failed to parse --requests-path argument" << std::endl;
+ print_help(EXIT_FAILURE);
+ }
+ }
+ else
+ {
+ std::cerr << "Failed to parse unknown argument: " << arg << std::endl;
+ print_help(EXIT_FAILURE);
+ }
+ }
+
+ // Sanity check (should be set from the positional mode)
+ if (!client_flag && !server_flag)
+ {
+ std::cerr << "Either 'client' or 'server' must be specified as the first argument" << std::endl;
+ print_help(EXIT_FAILURE);
+ }
+
+ if (client_flag && config.requests_path.empty())
+ {
+ std::cerr << "--requests-path is required in client mode" << std::endl;
+ print_help(EXIT_FAILURE);
+ }
+
+ if (config.persistence_path.empty())
+ {
+ std::cerr << "Warning: persistence path is not set, persistence features will be disabled" <<
+ std::endl;
+ }
+
+ return config;
+ }
+
+ /**
+ * @brief Parse the signal number into the signal name
+ *
+ * @param signum signal number
+ * @return std::string signal name
+ */
+ static std::string parse_signal(
+ const int& signum)
+ {
+ switch (signum)
+ {
+ case SIGINT:
+ return "SIGINT";
+ case SIGTERM:
+ return "SIGTERM";
+#ifndef _WIN32
+ case SIGQUIT:
+ return "SIGQUIT";
+ case SIGHUP:
+ return "SIGHUP";
+#endif // _WIN32
+ default:
+ return "UNKNOWN SIGNAL";
+ }
+ }
+
+};
diff --git a/ddsenabler/examples/service/CMakeLists.txt b/ddsenabler/examples/service/CMakeLists.txt
new file mode 100644
index 00000000..f3c0a259
--- /dev/null
+++ b/ddsenabler/examples/service/CMakeLists.txt
@@ -0,0 +1,32 @@
+# Copyright 2025 Proyectos y Sistemas de Mantenimiento SL (eProsima).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+cmake_minimum_required(VERSION 3.20)
+project(ddsenabler_example_service LANGUAGES CXX)
+
+add_executable(ddsenabler_example_service main.cpp)
+
+target_include_directories(ddsenabler_example_service PRIVATE ${PROJECT_SOURCE_DIR}/../utils)
+target_link_libraries(ddsenabler_example_service PRIVATE ddsenabler utils)
+
+# Install rule
+install(TARGETS ddsenabler_example_service
+ RUNTIME DESTINATION examples/service
+)
+
+# Install requests directory
+install(
+ DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/requests
+ DESTINATION examples/service
+)
diff --git a/ddsenabler/examples/service/Readme.md b/ddsenabler/examples/service/Readme.md
new file mode 100644
index 00000000..4805c1d9
--- /dev/null
+++ b/ddsenabler/examples/service/Readme.md
@@ -0,0 +1,30 @@
+# Service Example Readme
+
+This example demonstrates the usage of the DDS Enabler for implementing a service. The example includes a client and a server setup for the desired service.
+
+By default, the example is prepared for the `add_two_ints` service, providing a standard reply with a constant result of 3 for any request.
+
+Users are encouraged to implement their own functional server logic as needed in the `server_specific_logic` function. The persistence/requests directory must be modified to suit the specific service type and its expected requests.
+
+## Example Commands
+
+### CLIENT
+```bash
+./install/ddsenabler/examples/service/ddsenabler_example_service client \
+ --persistence-path ./install/ddsenabler/examples/persistence/ \
+ --requests-path ./install/ddsenabler/examples/service/requests \
+ --request-initial-wait 3
+ --config $PATH_TO_CONFIG_FILE
+ --service-name $SERVICE_NAME
+ --timeout N
+```
+
+### SERVER
+```bash
+./install/ddsenabler/examples/service/ddsenabler_example_service server \
+ --persistence-path ./install/ddsenabler/examples/persistence/
+ --expected-requests N
+ --config $PATH_TO_CONFIG_FILE
+ --service-name $SERVICE_NAME
+ --timeout N
+```
diff --git a/ddsenabler/examples/service/main.cpp b/ddsenabler/examples/service/main.cpp
new file mode 100644
index 00000000..03864ae3
--- /dev/null
+++ b/ddsenabler/examples/service/main.cpp
@@ -0,0 +1,547 @@
+// Copyright 2025 Proyectos y Sistemas de Mantenimiento SL (eProsima).
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/**
+ * @file main.cpp
+ *
+ */
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+
+#include "ddsenabler/dds_enabler_runner.hpp"
+#include "ddsenabler/DDSEnabler.hpp"
+
+#include "CLIParser.hpp"
+
+CLIParser::example_config config;
+bool service_discovered_ = false;
+uint32_t received_replies_ = 0;
+uint32_t sent_replies_ = 0;
+std::vector> received_requests_;
+std::mutex app_mutex_;
+std::condition_variable app_cv_;
+bool stop_app_ = false;
+
+const std::string REQUESTS_SUBDIR = "requests";
+const std::string TYPES_SUBDIR = "types";
+const std::string SERVICES_SUBDIR = "services";
+
+void signal_handler(
+ int signum)
+{
+ std::cout << "Signal " << CLIParser::parse_signal(signum) << " received, stopping..." << std::endl;
+ {
+ std::lock_guard lock(app_mutex_);
+ stop_app_ = true;
+ }
+ app_cv_.notify_all();
+}
+
+// Static log callback
+void test_log_callback(
+ const char* file_name,
+ int line_no,
+ const char* func_name,
+ int category,
+ const char* msg)
+{
+ // NOTE: Default stdout logs can be disabled via configuration ("logging": {"stdout": false}) to avoid duplicated traces
+
+ std::stringstream ss;
+ ss << file_name << ":" << line_no << " (" << func_name << "): " << msg << std::endl;
+ if (category == eprosima::utils::Log::Kind::Warning)
+ {
+ std::cerr << "[WARNING] " << ss.str();
+ }
+ else if (category == eprosima::utils::Log::Kind::Error)
+ {
+ std::cerr << "[ERROR] " << ss.str();
+ }
+}
+
+// Static type notification callback
+static void test_type_notification_callback(
+ const char* type_name,
+ const char* serialized_type,
+ const unsigned char* serialized_type_internal,
+ uint32_t serialized_type_internal_size,
+ const char* data_placeholder)
+{
+ std::lock_guard lock(app_mutex_);
+ std::cout << "Type callback received: " << type_name << std::endl;
+ if (!config.persistence_path.empty() &&
+ !utils::save_type_to_file((std::filesystem::path(config.persistence_path) / TYPES_SUBDIR).string(),
+ type_name,
+ serialized_type_internal, serialized_type_internal_size))
+ {
+ std::cerr << "Failed to save type: " << type_name << std::endl;
+ }
+}
+
+// Static type query callback
+static bool test_type_query_callback(
+ const char* type_name,
+ std::unique_ptr& serialized_type_internal,
+ uint32_t& serialized_type_internal_size)
+{
+ if (config.persistence_path.empty())
+ {
+ std::cerr << "Persistence path is not set, cannot query type: " << type_name << std::endl;
+ return false;
+ }
+
+ // Load the type from file
+ if (!utils::load_type_from_file((std::filesystem::path(config.persistence_path) / TYPES_SUBDIR).string(), type_name,
+ serialized_type_internal, serialized_type_internal_size))
+ {
+ std::cerr << "Failed to load type: " << type_name << std::endl;
+ return false;
+ }
+ return true;
+}
+
+// Static data notification callback
+static void test_data_notification_callback(
+ const char* topic_name,
+ const char* json,
+ int64_t publish_time)
+{
+}
+
+// Static topic notification callback
+static void test_topic_notification_callback(
+ const char* topic_name,
+ const eprosima::ddsenabler::participants::TopicInfo& topic_info)
+{
+}
+
+// Static type query callback
+static bool test_topic_query_callback(
+ const char* topic_name,
+ eprosima::ddsenabler::participants::TopicInfo& topic_info)
+{
+ return false;
+}
+
+// Static service notification callback
+static void test_service_notification_callback(
+ const char* service_name,
+ const eprosima::ddsenabler::participants::ServiceInfo& service_info)
+{
+ std::lock_guard lock(app_mutex_);
+ if (config.service_name == std::string(service_name))
+ {
+ std::cout << "Service callback received: " << service_name << std::endl;
+
+ std::string service_file = (std::filesystem::path(
+ config.persistence_path) / SERVICES_SUBDIR
+ ).string();
+ if (!config.persistence_path.empty())
+ {
+ utils::save_service_to_file(
+ service_file,
+ service_name,
+ service_info);
+ }
+ service_discovered_ = true;
+ app_cv_.notify_all();
+ }
+ else
+ {
+ std::cout << "Ignoring service callback for: " << service_name << std::endl;
+ }
+}
+
+// Static service query callback
+static bool test_service_query_callback(
+ const char* service_name,
+ eprosima::ddsenabler::participants::ServiceInfo& service_info)
+{
+ std::lock_guard lock(app_mutex_);
+
+ if (config.persistence_path.empty())
+ {
+ std::cerr << "Persistence path is not set, cannot query service: " << service_name << std::endl;
+ return false;
+ }
+
+ if (config.service_name == std::string(service_name))
+ {
+ std::cout << "Service type request callback received: " << service_name << std::endl;
+
+ std::string service_file = (std::filesystem::path(
+ config.persistence_path) /
+ SERVICES_SUBDIR
+ ).string();
+ if (utils::load_service_from_file(
+ service_file,
+ service_name,
+ service_info))
+ {
+ app_cv_.notify_all();
+ return true;
+ }
+ std::cout << "ERROR: fail to load service from file: " << service_file << std::endl;
+ return false;
+ }
+ else
+ {
+ std::cout << "Ignoring service type request callback for: " << service_name << std::endl;
+ return false;
+ }
+}
+
+// Static service reply notification callback
+static void test_service_reply_notification_callback(
+ const char* service_name,
+ const char* json,
+ uint64_t request_id,
+ int64_t publish_time)
+{
+ std::lock_guard lock(app_mutex_);
+ received_replies_++;
+ std::cout << "Reply callback received with id: " << request_id << " for service: " << service_name << std::endl;
+ std::cout << "Reply: " << json << std::endl;
+
+ app_cv_.notify_all();
+}
+
+// Static service request notification callback
+static void test_service_request_notification_callback(
+ const char* service_name,
+ const char* json,
+ uint64_t request_id,
+ int64_t publish_time)
+{
+ std::lock_guard lock(app_mutex_);
+
+ std::cout << "Request callback received with id: " << request_id << " for service: " << service_name << std::endl;
+ received_requests_.emplace_back(request_id, json);
+ app_cv_.notify_all();
+}
+
+bool wait_for_service_discovery(
+ uint32_t timeout,
+ std::mutex& app_mutex,
+ std::condition_variable& app_cv)
+{
+ std::unique_lock lock(app_mutex);
+ if (!app_cv.wait_for(lock, std::chrono::seconds(timeout),
+ []()
+ {
+ return stop_app_ || service_discovered_;
+ }) || stop_app_)
+ {
+ if (!stop_app_)
+ {
+ std::cerr << "Timeout waiting for service discovery." << std::endl;
+ }
+ return false;
+ }
+ return true;
+}
+
+bool wait_for_service_request(
+ uint32_t timeout,
+ std::mutex& app_mutex,
+ std::condition_variable& app_cv,
+ uint64_t& request_id,
+ std::string& request)
+{
+ std::unique_lock lock(app_mutex);
+ if (!app_cv.wait_for(lock, std::chrono::seconds(timeout),
+ []()
+ {
+ return stop_app_ || !received_requests_.empty();
+ }) || stop_app_)
+ {
+ if (!stop_app_)
+ {
+ std::cerr << "Timeout waiting for service request." << std::endl;
+ }
+ return false;
+ }
+
+ // Pop the last request from the received requests vector
+ request_id = received_requests_.back().first;
+ request = received_requests_.back().second;
+ received_requests_.pop_back();
+ return true;
+}
+
+bool wait_for_service_reply(
+ uint32_t timeout,
+ std::mutex& app_mutex,
+ std::condition_variable& app_cv,
+ uint32_t sent_requests)
+{
+ std::unique_lock lock(app_mutex);
+ if (!app_cv.wait_for(lock, std::chrono::seconds(timeout),
+ [&sent_requests]()
+ {
+ return stop_app_ || received_replies_ >= sent_requests;
+ }) || stop_app_)
+ {
+ if (!stop_app_)
+ {
+ std::cerr << "Timeout waiting for service reply." << std::endl;
+ }
+ return false;
+ }
+ return true;
+}
+
+bool client_routine(
+ std::shared_ptr enabler,
+ const std::string& service_name,
+ const std::string& requests_path,
+ uint32_t timeout,
+ uint32_t request_initial_wait)
+{
+ // Wait for service to be discovered
+ if (!wait_for_service_discovery(timeout, app_mutex_, app_cv_))
+ {
+ return false;
+ }
+
+ // Wait a bit before starting to publish so types and topics can be discovered
+ std::this_thread::sleep_for(std::chrono::seconds(request_initial_wait));
+
+ // Get collection of files to publish, sorted in increasing order by their name (assumed to be numeric)
+ std::vector> sample_files;
+ utils::get_sorted_files(requests_path, sample_files);
+ uint32_t sent_requests = 0;
+ for (const auto& [path, number] : sample_files)
+ {
+ {
+ std::lock_guard lock(app_mutex_);
+ if (stop_app_)
+ {
+ return false;
+ }
+ }
+
+ std::ifstream file(path, std::ios::binary);
+ if (file)
+ {
+ std::string file_content((std::istreambuf_iterator(file)), std::istreambuf_iterator());
+ uint64_t request_id = 0;
+ if (enabler->send_service_request(service_name, file_content, request_id))
+ {
+ std::cout << "Published content from file: " << path.filename() << " in service: "
+ << service_name << " with request ID: " << request_id << std::endl;
+ sent_requests++;
+ }
+ else
+ {
+ std::cerr << "Failed to publish content from file: " << path.filename() << " in service: "
+ << service_name << std::endl;
+ }
+ }
+ else
+ {
+ std::cerr << "Failed to open file: " << path << std::endl;
+ return false;
+ }
+
+ // Wait publish period or until stop signal is received
+ if (!wait_for_service_reply(timeout, app_mutex_, app_cv_, sent_requests))
+ {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool server_specific_logic(
+ std::shared_ptr enabler,
+ const std::string& service_name,
+ const std::string& request,
+ uint64_t request_id)
+{
+ std::this_thread::sleep_for(std::chrono::milliseconds(200)); // Simulate processing time
+
+ // Add_two_ints service logic
+ int a = 1; // Default values in case of parsing failure
+ int b = 2; // Default values in case of parsing failure
+ try
+ {
+ auto request_json = nlohmann::json::parse(request);
+ a = request_json["rq/add_two_intsRequest"]["data"]["0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0"]["a"].get();
+ b = request_json["rq/add_two_intsRequest"]["data"]["0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0"]["b"].get();
+ }
+ catch (const std::exception& e)
+ {
+ std::cerr << "Failed to parse request JSON: " << e.what() << " Using default values a=1, b=2." << std::endl;
+ }
+ nlohmann::json response_json;
+ response_json["sum"] = a + b;
+ std::string json = response_json.dump();
+
+ if (!enabler->send_service_reply(service_name, json, request_id))
+ {
+ std::cerr << "Failed to send service reply for request ID: " << request_id << std::endl;
+ return false;
+ }
+ return true;
+}
+
+bool server_routine(
+ std::shared_ptr enabler,
+ const std::string& service_name,
+ uint32_t expected_requests,
+ uint32_t timeout)
+{
+ // Announce service
+ if (!enabler->announce_service(service_name))
+ {
+ std::cerr << "Failed to announce service: " << service_name << std::endl;
+ return false;
+ }
+
+ std::cout << "Service announced: " << service_name << std::endl;
+
+ bool stop_app = false;
+ {
+ std::lock_guard lock(app_mutex_);
+ stop_app = stop_app_;
+ }
+
+ while (!stop_app)
+ {
+ uint64_t request_id = 0;
+ std::string request;
+ if (!wait_for_service_request(timeout, app_mutex_, app_cv_, request_id, request))
+ {
+ return false;
+ }
+
+ std::cout << "Received request for service: " << service_name << " with request ID: " << request_id <<
+ std::endl;
+
+ // Example response
+ server_specific_logic(
+ enabler,
+ service_name,
+ request,
+ request_id);
+
+ // Check if we have received the expected number of requests (or run indefinitely if expected_requests is 0)
+ {
+ std::lock_guard lock(app_mutex_);
+ if (expected_requests != 0 && ++sent_replies_ >= expected_requests)
+ {
+ // Wait to ensure the last sent reply reaches destination
+ std::this_thread::sleep_for(std::chrono::milliseconds(1000));
+ return true;
+ }
+ stop_app = stop_app_;
+ }
+ }
+
+ // Revoke service
+ if (!enabler->revoke_service(service_name))
+ {
+ std::cerr << "Failed to revoke service: " << service_name << std::endl;
+ return false;
+ }
+ return true;
+}
+
+int main(
+ int argc,
+ char** argv)
+{
+ signal(SIGINT, signal_handler);
+ signal(SIGTERM, signal_handler);
+#ifndef _WIN32
+ signal(SIGQUIT, signal_handler);
+ signal(SIGHUP, signal_handler);
+#endif // _WIN32
+
+ using namespace eprosima::ddsenabler;
+
+ eprosima::utils::Log::ReportFilenames(true);
+
+ // Parse CLI options
+ config = CLIParser::parse_cli_options(argc, argv);
+
+ // Initialize persistence if required
+ std::vector subdirs = {TYPES_SUBDIR, SERVICES_SUBDIR};
+ utils::init_persistence(config.persistence_path, subdirs);
+
+ // Set up callbacks
+ CallbackSet callbacks{
+ test_log_callback,
+ {
+ test_type_notification_callback,
+ test_topic_notification_callback,
+ test_data_notification_callback,
+ test_type_query_callback,
+ test_topic_query_callback
+ },
+ {
+ test_service_notification_callback,
+ test_service_request_notification_callback,
+ test_service_reply_notification_callback,
+ test_service_query_callback
+ }
+ };
+
+ std::shared_ptr enabler;
+ bool enabler_created = false;
+ if (config.config_file_path.empty())
+ {
+ enabler_created = create_dds_enabler(yaml::EnablerConfiguration(""), callbacks, enabler);
+ }
+ else
+ {
+ enabler_created = create_dds_enabler(config.config_file_path.c_str(), callbacks, enabler);
+ }
+ if (!enabler_created)
+ {
+ std::cerr << "Failed to create DDSEnabler instance." << std::endl;
+ return EXIT_FAILURE;
+ }
+
+ bool ret = false;
+ // Service logic based on announce_server flag
+ if (config.announce_server)
+ {
+ ret = server_routine(enabler, config.service_name, config.expected_requests, config.timeout);
+ }
+ else
+ {
+ if (config.requests_path.empty())
+ {
+ std::cerr << "Request path is not set." << std::endl;
+ return EXIT_FAILURE;
+ }
+ ret = client_routine(enabler, config.service_name, config.requests_path, config.timeout,
+ config.request_initial_wait);
+ }
+
+ return ret ? EXIT_SUCCESS : EXIT_FAILURE;
+}
diff --git a/ddsenabler/examples/service/requests/1 b/ddsenabler/examples/service/requests/1
new file mode 100644
index 00000000..fa79c13a
--- /dev/null
+++ b/ddsenabler/examples/service/requests/1
@@ -0,0 +1,4 @@
+{
+ "a": 1,
+ "b": 2
+}
diff --git a/ddsenabler/examples/service/requests/2 b/ddsenabler/examples/service/requests/2
new file mode 100644
index 00000000..4f64570c
--- /dev/null
+++ b/ddsenabler/examples/service/requests/2
@@ -0,0 +1,4 @@
+{
+ "a": 2,
+ "b": 3
+}
diff --git a/ddsenabler/examples/service/requests/3 b/ddsenabler/examples/service/requests/3
new file mode 100644
index 00000000..c5250e29
--- /dev/null
+++ b/ddsenabler/examples/service/requests/3
@@ -0,0 +1,4 @@
+{
+ "a": 3,
+ "b": 4
+}
diff --git a/ddsenabler/examples/utils/CMakeLists.txt b/ddsenabler/examples/utils/CMakeLists.txt
new file mode 100644
index 00000000..937e9877
--- /dev/null
+++ b/ddsenabler/examples/utils/CMakeLists.txt
@@ -0,0 +1,28 @@
+# Copyright 2025 Proyectos y Sistemas de Mantenimiento SL (eProsima).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+cmake_minimum_required(VERSION 3.20)
+project(ddsenabler_example_utils LANGUAGES CXX)
+
+find_package(ddsenabler_participants)
+
+add_library(utils STATIC
+ Utils.cpp
+)
+
+target_include_directories(utils PUBLIC
+ ${CMAKE_CURRENT_SOURCE_DIR}
+)
+
+target_link_libraries(utils PUBLIC ddsenabler_participants)
diff --git a/ddsenabler/examples/utils.hpp b/ddsenabler/examples/utils/Utils.cpp
similarity index 50%
rename from ddsenabler/examples/utils.hpp
rename to ddsenabler/examples/utils/Utils.cpp
index e49c73ed..acf3daf7 100644
--- a/ddsenabler/examples/utils.hpp
+++ b/ddsenabler/examples/utils/Utils.cpp
@@ -12,15 +12,18 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-#include
-#include
-#include
+/**
+ * @file Utils.cpp
+ */
-#include
+#include "Utils.hpp"
-#pragma once
+#include
+#include
+#include
+#include
-// TODO: move these methods somewhere they can be reused
+namespace utils {
std::string safe_file_name(
const std::string& name)
@@ -147,8 +150,7 @@ bool load_type_from_file(
bool save_topic_to_file(
const std::string& directory,
const char* topic_name,
- const char* type_name,
- const char* serialized_qos)
+ const TopicInfo& topic_info)
{
// Check if directory exists
if (!std::filesystem::exists(directory))
@@ -179,14 +181,14 @@ bool save_topic_to_file(
}
// Write type name
- uint32_t len_type_name = static_cast(std::strlen(type_name));
+ uint32_t len_type_name = static_cast(topic_info.type_name.size());
ofs.write(reinterpret_cast(&len_type_name), sizeof(len_type_name));
- ofs.write(type_name, len_type_name);
+ ofs.write(topic_info.type_name.data(), len_type_name);
// Write serialized QoS
- uint32_t len_serialized_qos = static_cast(std::strlen(serialized_qos));
+ uint32_t len_serialized_qos = static_cast(topic_info.serialized_qos.size());
ofs.write(reinterpret_cast(&len_serialized_qos), sizeof(len_serialized_qos));
- ofs.write(serialized_qos, len_serialized_qos);
+ ofs.write(topic_info.serialized_qos.data(), len_serialized_qos);
if (!ofs.good())
{
@@ -201,8 +203,7 @@ bool save_topic_to_file(
bool load_topic_from_file(
const std::string& directory,
const char* topic_name,
- std::string& type_name,
- std::string& serialized_qos)
+ TopicInfo& topic_info)
{
// Check if directory exists
if (!std::filesystem::exists(directory))
@@ -268,8 +269,8 @@ bool load_topic_from_file(
return false;
}
- type_name.assign(type_name_buffer.begin(), type_name_buffer.end());
- serialized_qos.assign(serialized_qos_buffer.begin(), serialized_qos_buffer.end());
+ topic_info.type_name.assign(type_name_buffer.begin(), type_name_buffer.end());
+ topic_info.serialized_qos.assign(serialized_qos_buffer.begin(), serialized_qos_buffer.end());
return true;
}
@@ -362,3 +363,256 @@ bool save_data_to_file(
ofs.close();
return true;
}
+
+void save_service_to_file(
+ const std::string& directory,
+ const char* service_name,
+ const ServiceInfo& service_info)
+{
+ // Check if directory exists
+ if (!std::filesystem::exists(directory))
+ {
+ std::cerr << "Directory does not exist: " << directory << std::endl;
+ return;
+ }
+
+ // Remove problematic characters
+ std::string safe_service_name = safe_file_name(service_name);
+
+ // Construct full file path
+ auto file_path = std::filesystem::path(directory) / safe_service_name;
+
+ // Check if already exists, do nothing if it does
+ if (std::filesystem::exists(file_path))
+ {
+ std::cout << "File already exists: " << file_path << std::endl;
+ return;
+ }
+
+ nlohmann::json j;
+ j["service_name"] = service_name;
+ j["request_type_name"] = service_info.request.type_name;
+ j["reply_type_name"] = service_info.reply.type_name;
+ j["request_serialized_qos"] = service_info.request.serialized_qos;
+ j["reply_serialized_qos"] = service_info.reply.serialized_qos;
+
+ std::ofstream ofs(file_path);
+ if (ofs.is_open())
+ {
+ ofs << j.dump(4);
+ }
+}
+
+bool load_service_from_file(
+ const std::string& directory,
+ const char* service_name,
+ ServiceInfo& service_info)
+{
+ // Check if directory exists
+ if (!std::filesystem::exists(directory))
+ {
+ std::cerr << "Directory does not exist: " << directory << std::endl;
+ return false;
+ }
+
+ // Remove problematic characters
+ std::string safe_service_name = safe_file_name(service_name);
+
+ // Construct full file path
+ auto file_path = std::filesystem::path(directory) / safe_service_name;
+
+ // Check if already exists, do nothing if it does not
+ if (!std::filesystem::exists(file_path))
+ {
+ std::cout << "File does not exist: " << file_path << std::endl;
+ return false;
+ }
+
+ std::ifstream ifs(file_path);
+ if (!ifs.is_open())
+ {
+ return false;
+ }
+
+ nlohmann::json j;
+ ifs >> j;
+
+ std::string file_service_name = j["service_name"].get();
+ if (file_service_name != std::string(service_name))
+ {
+ return false; // Service name does not match
+ }
+
+ service_info.request.type_name = j["request_type_name"].get();
+ service_info.reply.type_name = j["reply_type_name"].get();
+ service_info.request.serialized_qos = j["request_serialized_qos"].get();
+ service_info.reply.serialized_qos = j["reply_serialized_qos"].get();
+ ifs.close();
+
+ return true;
+}
+
+void save_action_to_file(
+ const std::string& directory,
+ const char* action_name,
+ const ActionInfo& action_info)
+{
+ // Check if directory exists
+ if (!std::filesystem::exists(directory))
+ {
+ std::cerr << "Directory does not exist: " << directory << std::endl;
+ return;
+ }
+
+ // Remove problematic characters
+ std::string safe_action_name = safe_file_name(action_name);
+
+ // Construct full file path
+ auto file_path = std::filesystem::path(directory) / safe_action_name;
+
+ // Check if already exists, do nothing if it does
+ if (std::filesystem::exists(file_path))
+ {
+ std::cout << "File already exists: " << file_path << std::endl;
+ return;
+ }
+
+ nlohmann::json j;
+ j["action_name"] = action_name;
+ j["goal_request_action_type"] = action_info.goal.request.type_name;
+ j["goal_reply_action_type"] = action_info.goal.reply.type_name;
+ j["cancel_request_action_type"] = action_info.cancel.request.type_name;
+ j["cancel_reply_action_type"] = action_info.cancel.reply.type_name;
+ j["result_request_action_type"] = action_info.result.request.type_name;
+ j["result_reply_action_type"] = action_info.result.reply.type_name;
+ j["feedback_action_type"] = action_info.feedback.type_name;
+ j["status_action_type"] = action_info.status.type_name;
+ j["goal_request_action_serialized_qos"] = action_info.goal.request.serialized_qos;
+ j["goal_reply_action_serialized_qos"] = action_info.goal.reply.serialized_qos;
+ j["cancel_request_action_serialized_qos"] = action_info.cancel.request.serialized_qos;
+ j["cancel_reply_action_serialized_qos"] = action_info.cancel.reply.serialized_qos;
+ j["result_request_action_serialized_qos"] = action_info.result.request.serialized_qos;
+ j["result_reply_action_serialized_qos"] = action_info.result.reply.serialized_qos;
+ j["feedback_action_serialized_qos"] = action_info.feedback.serialized_qos;
+ j["status_action_serialized_qos"] = action_info.status.serialized_qos;
+
+ std::ofstream ofs(file_path);
+ if (ofs.is_open())
+ {
+ ofs << j.dump(4);
+ ofs.close();
+ }
+}
+
+bool load_action_from_file(
+ const std::string& directory,
+ const char* action_name,
+ ActionInfo& action_info)
+{
+ // Check if directory exists
+ if (!std::filesystem::exists(directory))
+ {
+ std::cerr << "Directory does not exist: " << directory << std::endl;
+ return false;
+ }
+
+ // Remove problematic characters
+ std::string safe_action_name = safe_file_name(action_name);
+
+ // Construct full file path
+ auto file_path = std::filesystem::path(directory) / safe_action_name;
+
+ // Check if already exists, do nothing if it does not
+ if (!std::filesystem::exists(file_path))
+ {
+ std::cout << "File does not exist: " << file_path << std::endl;
+ return false;
+ }
+
+ std::ifstream ifs(file_path);
+ if (!ifs.is_open())
+ {
+ return false;
+ }
+
+ nlohmann::json j;
+ ifs >> j;
+
+ std::string file_action_name = j["action_name"].get();
+ if (file_action_name != std::string(action_name))
+ {
+ return false; // Action name does not match
+ }
+ action_info.goal.request.type_name = j["goal_request_action_type"].get();
+ action_info.goal.reply.type_name = j["goal_reply_action_type"].get();
+ action_info.cancel.request.type_name = j["cancel_request_action_type"].get();
+ action_info.cancel.reply.type_name = j["cancel_reply_action_type"].get();
+ action_info.result.request.type_name = j["result_request_action_type"].get();
+ action_info.result.reply.type_name = j["result_reply_action_type"].get();
+ action_info.feedback.type_name = j["feedback_action_type"].get();
+ action_info.status.type_name = j["status_action_type"].get();
+ action_info.goal.request.serialized_qos = j["goal_request_action_serialized_qos"].get();
+ action_info.goal.reply.serialized_qos = j["goal_reply_action_serialized_qos"].get();
+ action_info.cancel.request.serialized_qos = j["cancel_request_action_serialized_qos"].get();
+ action_info.cancel.reply.serialized_qos = j["cancel_reply_action_serialized_qos"].get();
+ action_info.result.request.serialized_qos = j["result_request_action_serialized_qos"].get();
+ action_info.result.reply.serialized_qos = j["result_reply_action_serialized_qos"].get();
+ action_info.feedback.serialized_qos = j["feedback_action_serialized_qos"].get();
+ action_info.status.serialized_qos = j["status_action_serialized_qos"].get();
+ ifs.close();
+
+ return true;
+}
+
+void init_persistence(
+ const std::string& persistence_path,
+ std::vector subdirs)
+{
+ auto ensure_directory_exists = [](const std::filesystem::path& path)
+ {
+ if (!std::filesystem::exists(path) && !std::filesystem::create_directories(path))
+ {
+ std::cerr << "Failed to create directory: " << path << std::endl;
+ }
+ };
+
+ if (!persistence_path.empty())
+ {
+ ensure_directory_exists(persistence_path);
+ for (const auto& sub : subdirs)
+ {
+ ensure_directory_exists(std::filesystem::path(persistence_path) / sub);
+ }
+ }
+}
+
+void get_sorted_files(
+ const std::string& directory,
+ std::vector>& files)
+{
+ for (const auto& entry : std::filesystem::directory_iterator(directory))
+ {
+ if (entry.is_regular_file())
+ {
+ std::string filename = entry.path().filename().string();
+ try
+ {
+ // assumes name is just a number
+ files.emplace_back(entry.path(), static_cast(std::stoll(filename)));
+ }
+ catch (const std::invalid_argument& e)
+ {
+ std::cerr << "Skipping non-numeric file: " << filename << std::endl;
+ }
+ }
+ }
+
+ // Sort files by numeric value
+ std::sort(files.begin(), files.end(),
+ [](const auto& a, const auto& b)
+ {
+ return a.second < b.second;
+ });
+}
+
+} // namespace utils
diff --git a/ddsenabler/examples/utils/Utils.hpp b/ddsenabler/examples/utils/Utils.hpp
new file mode 100644
index 00000000..f8a0bed4
--- /dev/null
+++ b/ddsenabler/examples/utils/Utils.hpp
@@ -0,0 +1,89 @@
+// Copyright 2025 Proyectos y Sistemas de Mantenimiento SL (eProsima).
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include
+
+#include
+#include
+#include
+#include
+
+#pragma once
+
+namespace utils {
+
+using eprosima::ddsenabler::participants::TopicInfo;
+using eprosima::ddsenabler::participants::ServiceInfo;
+using eprosima::ddsenabler::participants::ActionInfo;
+
+std::string safe_file_name(
+ const std::string& name);
+
+bool save_type_to_file(
+ const std::string& directory,
+ const char* type_name,
+ const unsigned char*& serialized_type_internal,
+ uint32_t serialized_type_internal_size);
+
+bool load_type_from_file(
+ const std::string& directory,
+ const char* type_name,
+ std::unique_ptr& serialized_type_internal,
+ uint32_t& serialized_type_internal_size);
+
+bool save_topic_to_file(
+ const std::string& directory,
+ const char* topic_name,
+ const TopicInfo& topic_info);
+
+bool load_topic_from_file(
+ const std::string& directory,
+ const char* topic_name,
+ TopicInfo& topic_info);
+
+bool save_data_to_file(
+ const std::string& directory,
+ const std::string& topic_name,
+ const std::string& json,
+ int64_t publish_time);
+
+void save_service_to_file(
+ const std::string& directory,
+ const char* service_name,
+ const ServiceInfo& service_info);
+
+bool load_service_from_file(
+ const std::string& directory,
+ const char* service_name,
+ ServiceInfo& service_info);
+
+void save_action_to_file(
+ const std::string& directory,
+ const char* action_name,
+ const ActionInfo& action_info);
+
+bool load_action_from_file(
+ const std::string& directory,
+ const char* action_name,
+ ActionInfo& action_info);
+
+void init_persistence(
+ const std::string& persistence_path,
+ std::vector subdirs);
+
+void get_sorted_files(
+ const std::string& directory,
+ std::vector>& files);
+
+} // namespace utils
diff --git a/ddsenabler/include/ddsenabler/CallbackSet.hpp b/ddsenabler/include/ddsenabler/CallbackSet.hpp
index ef1e3e12..1ede155d 100644
--- a/ddsenabler/include/ddsenabler/CallbackSet.hpp
+++ b/ddsenabler/include/ddsenabler/CallbackSet.hpp
@@ -44,6 +44,45 @@ struct DdsCallbacks
participants::DdsTopicQuery topic_query{nullptr};
};
+struct ServiceCallbacks
+{
+ //! Callback for notifying the discovery of DDS services
+ participants::ServiceNotification service_notification{nullptr};
+
+ //! Callback for notifying the reception of service requests
+ participants::ServiceRequestNotification service_request_notification{nullptr};
+
+ //! Callback for notifying the reception of service replies
+ participants::ServiceReplyNotification service_reply_notification{nullptr};
+
+ //! Callback for requesting information of a DDS service
+ participants::ServiceQuery service_query{nullptr};
+};
+
+struct ActionCallbacks
+{
+ //! Callback for notifying the discovery of DDS actions
+ participants::ActionNotification action_notification{nullptr};
+
+ //! Callback for notifying the reception of action goal requests
+ participants::ActionGoalRequestNotification action_goal_request_notification{nullptr};
+
+ //! Callback for notifying the reception of action feedback
+ participants::ActionFeedbackNotification action_feedback_notification{nullptr};
+
+ //! Callback for notifying the reception of action cancel requests
+ participants::ActionCancelRequestNotification action_cancel_request_notification{nullptr};
+
+ //! Callback for notifying the reception of action results
+ participants::ActionResultNotification action_result_notification{nullptr};
+
+ //! Callback for notifying the reception of action status notifications
+ participants::ActionStatusNotification action_status_notification{nullptr};
+
+ //! Callback for requesting information of a DDS action
+ participants::ActionQuery action_query{nullptr};
+};
+
/**
* @brief Struct that encapsulates all the callbacks used by the DDS Enabler.
*/
@@ -54,6 +93,10 @@ struct CallbackSet
//! DDS related callbacks
DdsCallbacks dds;
+ //! Service related callbacks
+ ServiceCallbacks service;
+ //! Action related callbacks
+ ActionCallbacks action;
};
} /* namespace ddsenabler */
diff --git a/ddsenabler/include/ddsenabler/DDSEnabler.hpp b/ddsenabler/include/ddsenabler/DDSEnabler.hpp
index d53b12dd..9bf0d875 100644
--- a/ddsenabler/include/ddsenabler/DDSEnabler.hpp
+++ b/ddsenabler/include/ddsenabler/DDSEnabler.hpp
@@ -38,6 +38,7 @@
#include
#include
#include
+#include
#include
@@ -96,6 +97,7 @@ class DDSEnabler
*
* @param topic_name: The name of the topic to publish to.
* @param json: The JSON message to publish.
+ *
* @return \c true if the message was published successfully, \c false otherwise.
*/
DDSENABLER_DllAPI
@@ -103,8 +105,323 @@ class DDSEnabler
const std::string& topic_name,
const std::string& json);
+ /*****************************************/
+ /* SERVICE */
+ /*****************************************/
+
+ /**
+ * @brief Creates a server for the given service.
+ *
+ * This function announces a service by setting up a server for it.
+ * It returns a boolean indicating whether the operation was successful.
+ * Failure may occur if there is an issue requesting the data types to user's app
+ * for the corresponding request and reply topics.
+ *
+ * @param service_name The name of the service to be announced.
+ * @param Protocol The RPC protocol to be used (overloaded function, without this parameter uses ROS2 by default).
+ *
+ * @return true if the service was successfully announced, false otherwise.
+ */
+ DDSENABLER_DllAPI
+ bool announce_service(
+ const std::string& service_name,
+ participants::Protocol Protocol);
+
+ DDSENABLER_DllAPI
+ bool announce_service(
+ const std::string& service_name);
+
+ /**
+ * @brief Stops the server for the given service.
+ *
+ * This function revokes the server associated with the specified service.
+ * It returns a boolean indicating whether the operation was successful.
+ * The operation will fail if the service was not previously announced.
+ *
+ * @param service_name The name of the service to be stopped.
+ * @return true if the service was successfully stopped, false otherwise.
+ */
+ DDSENABLER_DllAPI
+ bool revoke_service(
+ const std::string& service_name);
+
+ /**
+ * @brief Sends a request to a specified service.
+ *
+ * Sends a request containing the given JSON data to the specified service.
+ * If successful, the request identifier is stored in the provided reference
+ * and will match the one received in the reply callback.
+ *
+ * The function fails if there is no server for the service or if the request is malformed.
+ * The request identifier is incremented sequentially across all services.
+ *
+ * @param service_name The target service name.
+ * @param json The JSON-formatted request data.
+ * @param request_id Reference to store the unique request identifier.
+ * @param Protocol The RPC protocol to be used (overloaded function, without this parameter uses ROS2 by default).
+ *
+ * @return true if the request was successfully sent, false otherwise.
+ */
+ DDSENABLER_DllAPI
+ bool send_service_request(
+ const std::string& service_name,
+ const std::string& json,
+ uint64_t& request_id,
+ participants::Protocol Protocol);
+
+ DDSENABLER_DllAPI
+ bool send_service_request(
+ const std::string& service_name,
+ const std::string& json,
+ uint64_t& request_id);
+
+ /**
+ * @brief Sends a reply to the given service.
+ *
+ * This function sends a reply to the specified service with the provided JSON data.
+ * It returns a boolean indicating whether the operation was successful.
+ * Failure may occur if the service was not previously created or if the request ID does not match any request.
+ *
+ * @param service_name The name of the service to send the reply to.
+ * @param json The JSON data to be sent with the reply.
+ * @param request_id The unique identifier of the request to which this reply corresponds.
+ *
+ * @return true if the reply was successfully sent, false otherwise.
+ *
+ * @note The request_id must coincide with the one received in the request.
+ */
+ DDSENABLER_DllAPI
+ bool send_service_reply(
+ const std::string& service_name,
+ const std::string& json,
+ const uint64_t request_id);
+
+ /*****************************************/
+ /* ACTION SERVER */
+ /*****************************************/
+
+ /**
+ * @brief Creates server for the given action.
+ *
+ * This function announces an action by setting up a server for it.
+ * It returns a boolean indicating whether the operation was successful.
+ * Failure may occur if there is an issue requesting the data types to user's app.
+ *
+ * @param action_name The name of the action to be announced.
+ * @param Protocol The RPC protocol to be used (overloaded function, without this parameter uses ROS2 by default).
+ *
+ * @return true if the action was successfully announced, false otherwise.
+ */
+ DDSENABLER_DllAPI
+ bool announce_action(
+ const std::string& action_name,
+ participants::Protocol Protocol);
+
+ DDSENABLER_DllAPI
+ bool announce_action(
+ const std::string& action_name);
+
+ /**
+ * @brief Stops the server for the given action.
+ *
+ * This function revokes the server associated with the specified action.
+ * It returns a boolean indicating whether the operation was successful.
+ * Failure may occur if the action was not previously announced.
+ *
+ * @param action_name The name of the action to be stopped.
+ *
+ * @return true if the action was successfully stopped, false otherwise.
+ */
+ DDSENABLER_DllAPI
+ bool revoke_action(
+ const std::string& action_name);
+
+ /**
+ * @brief Send feedback for the specified goal_id action.
+ *
+ * This function sends feedback for the specified action goal identified by the given goal ID.
+ * It returns a boolean indicating whether the operation was successful.
+ *
+ * @param action_name The name of the action for which the feedback is being sent.
+ * @param json The JSON data to be sent as feedback.
+ * @param goal_id The unique identifier of the action goal for which the feedback is being sent.
+ *
+ * @return true if the feedback was successfully sent, false otherwise.
+ */
+ DDSENABLER_DllAPI
+ bool send_action_feedback(
+ const char* action_name,
+ const char* json,
+ const participants::UUID& goal_id);
+
+ /**
+ * @brief Send result for the specified goal_id action.
+ *
+ * This function sends the result for the specified action goal identified by the given goal ID.
+ * It returns a boolean indicating whether the operation was successful.
+ *
+ * @param action_name The name of the action for which the result is being sent.
+ * @param goal_id The unique identifier of the action goal for which the result is being sent.
+ * @param status_code The status code representing the result of the action.
+ * @param json The JSON data to be sent as the result.
+ *
+ * @return true if the result was successfully sent, false otherwise.
+ */
+ DDSENABLER_DllAPI
+ bool send_action_result(
+ const char* action_name,
+ const participants::UUID& goal_id,
+ const participants::StatusCode& status_code,
+ const char* json);
+
+ /**
+ * @brief Sends a cancel goal reply for the specified action.
+ *
+ * This function sends a reply for a cancel goal request for the specified action.
+ * It returns a boolean indicating whether the operation was successful.
+ *
+ * @param action_name The name of the action for which the cancel goal reply is being sent.
+ * @param goal_ids The unique identifiers of the action goals for which the cancel goal reply is being sent.
+ * @param cancel_code The cancel code representing the result of the cancel goal request.
+ * @param request_id The unique identifier of the cancel goal request.
+ *
+ * @return true if the cancel goal reply was successfully sent, false otherwise.
+ */
+ DDSENABLER_DllAPI
+ bool send_action_cancel_goal_reply(
+ const char* action_name,
+ const std::vector& goal_ids,
+ const participants::CancelCode& cancel_code,
+ const uint64_t request_id);
+
+ /**
+ * @brief Publishes an update for the status of an action.
+ *
+ * This function publishes a msg update for the status of an action.
+ * It returns a boolean indicating whether the operation was successful.
+ * Failure may occur if the action was not previously announced or if the goal ID is not currently active.
+ *
+ * @param action_name The name of the action for which the status is being updated.
+ * @param goal_id The unique identifier of the action goal for which the status is being updated.
+ * @param status_code The status code representing the current state of the action.
+ *
+ * @return true if the status update was successfully sent, false otherwise.
+ *
+ */
+ DDSENABLER_DllAPI
+ bool update_action_status(
+ const std::string& action_name,
+ const participants::UUID& goal_id,
+ const participants::StatusCode& status_code);
+
+ /*****************************************/
+ /* ACTION CLIENT */
+ /*****************************************/
+
+ /**
+ * @brief Sends an action goal to the specified action.
+ *
+ * This function sends an action goal to the specified action with the provided JSON data.
+ * It returns a boolean indicating whether the operation was successful.
+ * Failure may occur if the action server was not previously created, if the action types are unknown
+ * or if the action goal is malformed.
+ *
+ * The goal_id is a unique identifier for the action goal and is generated by the function.
+ *
+ * @param action_name The name of the action to send the goal to.
+ * @param json The JSON data to be sent with the action goal.
+ * @param goal_id Reference to store the unique identifier of the action goal.
+ * @param Protocol The RPC protocol to be used (overloaded function, without this parameter uses ROS2 by default).
+ *
+ * @return true if the action goal was successfully sent, false otherwise.
+ */
+ DDSENABLER_DllAPI
+ bool send_action_goal(
+ const std::string& action_name,
+ const std::string& json,
+ participants::UUID& goal_id,
+ participants::Protocol Protocol);
+
+ DDSENABLER_DllAPI
+ bool send_action_goal(
+ const std::string& action_name,
+ const std::string& json,
+ participants::UUID& goal_id);
+
+ /**
+ * @brief Cancels an action goal for the specified action.
+ *
+ * This function sends a request to an action server to cancel:
+ * If the goal ID is empty and timestamp is zero, cancel all goals
+ * If the goal ID is empty and timestamp is not zero, cancel all goals accepted at or before the timestamp
+ * If the goal ID is not empty and timestamp is zero, cancel the goal with the given ID regardless of the time it was accepted
+ * If the goal ID is not empty and timestamp is not zero, cancel the goal with the given ID and all goals accepted at or before the timestamp
+ * The success of the operation only indicates that the cancel request was sent, not that it was processed.
+ * The actual cancellation of the goals would be notified via a status update of each individual action.
+ *
+ * @param action_name The name of the action for which the goal is to be canceled.
+ * @param goal_id The unique identifier of the action goal to be canceled or empty to cancel all goals.
+ * @param timestamp The acceptance timestamp of the goal to be canceled.
+ *
+ * @return true if the cancel action request was successfully sent, false otherwise.
+ */
+ DDSENABLER_DllAPI
+ bool cancel_action_goal(
+ const std::string& action_name,
+ const participants::UUID& goal_id,
+ const int64_t timestamp = 0);
+
protected:
+ /**
+ * @brief Sends a request to get the result of an action.
+ *
+ * This function sends a request to get the result of an action identified by the given action name and goal ID.
+ * It returns a boolean indicating whether the operation was successful.
+ *
+ * It is invoked via lambda from the writer_ when the send_goal_reply is received and positive.
+ *
+ * @param action_name The name of the action for which the result is being requested.
+ * @param goal_id The unique identifier of the action goal for which the result is being requested.
+ *
+ * @return true if the request to get the result was successfully sent, false otherwise.
+ */
+ bool send_action_get_result_request(
+ const std::string& action_name,
+ const participants::UUID& goal_id);
+
+ /**
+ * @brief Sends a reply to an action goal request.
+ *
+ * This function sends a reply to an action goal request, indicating whether the goal was accepted or not.
+ *
+ * It is invoked via lambda from the writer_ when request is notified.
+ *
+ * @param action_name The name of the action for which the goal reply is being sent.
+ * @param goal_id The unique identifier of the action goal for which the reply is being sent.
+ * @param accepted A boolean indicating whether the goal was accepted (true) or rejected (false).
+ */
+ void send_action_send_goal_reply(
+ const std::string& action_name,
+ const uint64_t goal_id,
+ bool accepted);
+
+ /**
+ * @brief Actually sends the result reply for an action.
+ *
+ * This function is only called when all the conditions are met to send the result reply.
+ *
+ * @param action_name The name of the action for which the result reply is being sent.
+ * @param goal_id The unique identifier of the action goal for which the result reply is being sent.
+ * @param reply_json The JSON data to be sent as the result reply.
+ * @param request_id The unique identifier of the request to which this reply corresponds.
+ */
+ bool send_action_get_result_reply(
+ const std::string& action_name,
+ const participants::UUID& goal_id,
+ const std::string& reply_json,
+ const uint64_t request_id);
+
/**
* Load the Enabler's internal topics into a configuration object.
*
@@ -131,15 +448,15 @@ class DDSEnabler
//! Payload Pool
std::shared_ptr payload_pool_;
- //! Thread Pool
- std::shared_ptr thread_pool_;
-
//! Discovery Database
std::shared_ptr discovery_database_;
//! Participants Database
std::shared_ptr participants_database_;
+ //! Thread Pool
+ std::shared_ptr thread_pool_;
+
//! Handler
std::shared_ptr handler_;
diff --git a/ddsenabler/src/cpp/DDSEnabler.cpp b/ddsenabler/src/cpp/DDSEnabler.cpp
index b97edfa3..3a5577d1 100644
--- a/ddsenabler/src/cpp/DDSEnabler.cpp
+++ b/ddsenabler/src/cpp/DDSEnabler.cpp
@@ -12,12 +12,14 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+#include
+
#include
#include
#include
-#include "ddsenabler/DDSEnabler.hpp"
+#include
namespace eprosima {
namespace ddsenabler {
@@ -61,6 +63,30 @@ DDSEnabler::DDSEnabler(
handler_config,
payload_pool_);
+ handler_->set_send_action_get_result_request_callback(
+ [this](const std::string& action_name, const UUID& action_id)
+ {
+ if (this->send_action_get_result_request(action_name, action_id))
+ {
+ return true;
+ }
+ this->cancel_action_goal(action_name, action_id, 0);
+ return false;
+ });
+
+ handler_->set_send_action_send_goal_reply_callback(
+ [this](const std::string& action_name, const uint64_t goal_id, bool accepted)
+ {
+ return this->send_action_send_goal_reply(action_name, goal_id, accepted);
+ });
+
+ handler_->set_send_action_get_result_reply_callback(
+ [this](const std::string& action_name, const UUID& goal_id, const std::string& reply_json,
+ const uint64_t request_id)
+ {
+ return this->send_action_get_result_reply(action_name, goal_id, reply_json, request_id);
+ });
+
// Create Enabler Participant
enabler_participant_ = std::make_shared(
configuration_.enabler_configuration,
@@ -209,6 +235,55 @@ void DDSEnabler::set_internal_callbacks_(
{
enabler_participant_->set_topic_query_callback(callbacks.dds.topic_query);
}
+ if (callbacks.service.service_notification)
+ {
+ handler_->set_service_notification_callback(callbacks.service.service_notification);
+ }
+ if (callbacks.service.service_request_notification)
+ {
+ handler_->set_service_request_notification_callback(callbacks.service.service_request_notification);
+ }
+ if (callbacks.service.service_reply_notification)
+ {
+ handler_->set_service_reply_notification_callback(callbacks.service.service_reply_notification);
+ }
+ if (callbacks.service.service_query)
+ {
+ enabler_participant_->set_service_query_callback(callbacks.service.service_query);
+ }
+ if (callbacks.action.action_notification)
+ {
+ handler_->set_action_notification_callback(callbacks.action.action_notification);
+ }
+ if (callbacks.action.action_goal_request_notification)
+ {
+ handler_->set_action_goal_request_notification_callback(
+ callbacks.action.action_goal_request_notification);
+ }
+ if (callbacks.action.action_feedback_notification)
+ {
+ handler_->set_action_feedback_notification_callback(
+ callbacks.action.action_feedback_notification);
+ }
+ if (callbacks.action.action_cancel_request_notification)
+ {
+ handler_->set_action_cancel_request_notification_callback(
+ callbacks.action.action_cancel_request_notification);
+ }
+ if (callbacks.action.action_result_notification)
+ {
+ handler_->set_action_result_notification_callback(
+ callbacks.action.action_result_notification);
+ }
+ if (callbacks.action.action_status_notification)
+ {
+ handler_->set_action_status_notification_callback(
+ callbacks.action.action_status_notification);
+ }
+ if (callbacks.action.action_query)
+ {
+ enabler_participant_->set_action_query_callback(callbacks.action.action_query);
+ }
}
bool DDSEnabler::publish(
@@ -218,5 +293,196 @@ bool DDSEnabler::publish(
return enabler_participant_->publish(topic_name, json);
}
+bool DDSEnabler::send_service_request(
+ const std::string& service_name,
+ const std::string& json,
+ uint64_t& request_id)
+{
+ return send_service_request(
+ service_name,
+ json,
+ request_id,
+ participants::Protocol::ROS2);
+}
+
+bool DDSEnabler::send_service_request(
+ const std::string& service_name,
+ const std::string& json,
+ uint64_t& request_id,
+ participants::Protocol Protocol)
+{
+ return enabler_participant_->send_service_request(
+ service_name,
+ json,
+ request_id,
+ Protocol);
+}
+
+bool DDSEnabler::announce_service(
+ const std::string& service_name,
+ participants::Protocol Protocol)
+{
+ return enabler_participant_->announce_service(service_name, Protocol);
+}
+
+bool DDSEnabler::announce_service(
+ const std::string& service_name)
+{
+ return announce_service(service_name, participants::Protocol::ROS2);
+}
+
+bool DDSEnabler::revoke_service(
+ const std::string& service_name)
+{
+ return enabler_participant_->revoke_service(service_name);
+}
+
+bool DDSEnabler::send_service_reply(
+ const std::string& service_name,
+ const std::string& json,
+ const uint64_t request_id)
+{
+ return enabler_participant_->send_service_reply(
+ service_name,
+ json,
+ request_id);
+}
+
+bool DDSEnabler::send_action_goal(
+ const std::string& action_name,
+ const std::string& json,
+ UUID& action_id)
+{
+ return send_action_goal(
+ action_name,
+ json,
+ action_id,
+ participants::Protocol::ROS2);
+}
+
+bool DDSEnabler::send_action_goal(
+ const std::string& action_name,
+ const std::string& json,
+ UUID& action_id,
+ participants::Protocol Protocol)
+{
+ return enabler_participant_->send_action_goal(
+ action_name,
+ json,
+ action_id,
+ Protocol);
+}
+
+bool DDSEnabler::send_action_get_result_request(
+ const std::string& action_name,
+ const UUID& action_id)
+{
+ return enabler_participant_->send_action_get_result_request(
+ action_name,
+ action_id);
+}
+
+bool DDSEnabler::cancel_action_goal(
+ const std::string& action_name,
+ const participants::UUID& goal_id,
+ const int64_t timestamp)
+{
+ return enabler_participant_->cancel_action_goal(
+ action_name,
+ goal_id,
+ timestamp);
+}
+
+bool DDSEnabler::announce_action(
+ const std::string& action_name)
+{
+ return announce_action(action_name, participants::Protocol::ROS2);
+}
+
+bool DDSEnabler::announce_action(
+ const std::string& action_name,
+ participants::Protocol Protocol)
+{
+ return enabler_participant_->announce_action(action_name, Protocol);
+}
+
+bool DDSEnabler::revoke_action(
+ const std::string& action_name)
+{
+ return enabler_participant_->revoke_action(action_name);
+}
+
+void DDSEnabler::send_action_send_goal_reply(
+ const std::string& action_name,
+ const uint64_t goal_id,
+ bool accepted)
+{
+ enabler_participant_->send_action_send_goal_reply(
+ action_name,
+ goal_id,
+ accepted);
+}
+
+bool DDSEnabler::send_action_cancel_goal_reply(
+ const char* action_name,
+ const std::vector& goal_ids,
+ const participants::CancelCode& cancel_code,
+ const uint64_t request_id)
+{
+ return enabler_participant_->send_action_cancel_goal_reply(
+ action_name,
+ goal_ids,
+ cancel_code,
+ request_id);
+}
+
+bool DDSEnabler::send_action_result(
+ const char* action_name,
+ const participants::UUID& goal_id,
+ const participants::StatusCode& status_code,
+ const char* json)
+{
+ return enabler_participant_->send_action_result(
+ action_name,
+ goal_id,
+ status_code,
+ json);
+}
+
+bool DDSEnabler::send_action_get_result_reply(
+ const std::string& action_name,
+ const participants::UUID& goal_id,
+ const std::string& reply_json,
+ const uint64_t request_id)
+{
+ return enabler_participant_->send_action_get_result_reply(
+ action_name,
+ goal_id,
+ reply_json,
+ request_id);
+}
+
+bool DDSEnabler::send_action_feedback(
+ const char* action_name,
+ const char* json,
+ const participants::UUID& goal_id)
+{
+ return enabler_participant_->send_action_feedback(
+ action_name,
+ json,
+ goal_id);
+}
+
+bool DDSEnabler::update_action_status(
+ const std::string& action_name,
+ const participants::UUID& goal_id,
+ const participants::StatusCode& status_code)
+{
+ return enabler_participant_->update_action_status(
+ action_name,
+ goal_id,
+ status_code);
+}
+
} /* namespace ddsenabler */
} /* namespace eprosima */
diff --git a/ddsenabler/test/DDSEnablerTester.hpp b/ddsenabler/test/DDSEnablerTester.hpp
index a3c65e43..50cc9cdf 100644
--- a/ddsenabler/test/DDSEnablerTester.hpp
+++ b/ddsenabler/test/DDSEnablerTester.hpp
@@ -13,6 +13,9 @@
// limitations under the License.
#include
+#include
+#include
+
#include
#include
@@ -26,6 +29,8 @@
#include "ddsenabler/dds_enabler_runner.hpp"
+#include
+
using namespace eprosima::ddspipe;
using namespace eprosima::ddsenabler;
using namespace eprosima::ddsenabler::participants;
@@ -47,6 +52,8 @@ static int write_delay_ms_ = 20;
static int wait_for_ack_ns_ = 1000000000;
static int wait_after_publication_ms_ = 200;
+static const std::filesystem::path input_file_path = std::filesystem::current_path() / "test_files";
+
class DDSEnablerTester : public ::testing::Test
{
public:
@@ -90,6 +97,21 @@ class DDSEnablerTester : public ::testing::Test
test_data_notification_callback,
test_type_query_callback,
test_topic_query_callback
+ },
+ {
+ test_service_notification_callback,
+ test_service_request_notification_callback,
+ test_service_reply_notification_callback,
+ test_service_query_callback
+ },
+ {
+ test_action_notification_callback,
+ test_action_goal_request_notification_callback,
+ test_action_feedback_notification_callback,
+ test_action_cancel_request_notification_callback,
+ test_action_result_notification_callback,
+ test_action_status_notification_callback,
+ test_action_query_callback
}
};
@@ -130,6 +152,21 @@ class DDSEnablerTester : public ::testing::Test
test_data_notification_callback,
test_type_query_callback,
test_topic_query_callback
+ },
+ {
+ test_service_notification_callback,
+ test_service_request_notification_callback,
+ test_service_reply_notification_callback,
+ test_service_query_callback
+ },
+ {
+ test_action_notification_callback,
+ test_action_goal_request_notification_callback,
+ test_action_feedback_notification_callback,
+ test_action_cancel_request_notification_callback,
+ test_action_result_notification_callback,
+ test_action_status_notification_callback,
+ test_action_query_callback
}
};
@@ -299,8 +336,7 @@ class DDSEnablerTester : public ::testing::Test
// eprosima::ddsenabler::participants::DdsTopicNotification topic_notification
static void test_topic_notification_callback(
const char* topic_name,
- const char* type_name,
- const char* serialized_qos)
+ const eprosima::ddsenabler::participants::TopicInfo& topic_info)
{
if (current_test_instance_)
{
@@ -315,8 +351,7 @@ class DDSEnablerTester : public ::testing::Test
// eprosima::ddsenabler::participants::DdsTopicQuery topic_query;
static bool test_topic_query_callback(
const char* topic_name,
- std::string& type_name,
- std::string& serialized_qos)
+ eprosima::ddsenabler::participants::TopicInfo& topic_info)
{
return false;
}
@@ -327,6 +362,16 @@ class DDSEnablerTester : public ::testing::Test
std::unique_ptr& serialized_type_internal,
uint32_t& serialized_type_internal_size)
{
+ std::string persistence_dir = input_file_path.string();
+ if (utils::load_type_from_file(
+ persistence_dir,
+ type_name,
+ serialized_type_internal,
+ serialized_type_internal_size))
+ {
+ return true;
+ }
+ std::cout << "ERROR: fail to load type from directory: " << persistence_dir << std::endl;
return false;
}
@@ -382,6 +427,119 @@ class DDSEnablerTester : public ::testing::Test
}
}
+ // SERVICES
+ static void test_service_notification_callback(
+ const char* service_name,
+ const eprosima::ddsenabler::participants::ServiceInfo& service_info)
+ {
+ }
+
+ static bool test_service_query_callback(
+ const char* service_name,
+ eprosima::ddsenabler::participants::ServiceInfo& service_info)
+ {
+ std::string persistence_dir = input_file_path.string();
+ if (utils::load_service_from_file(
+ persistence_dir,
+ service_name,
+ service_info))
+ {
+ return true;
+ }
+ std::cout << "ERROR: fail to load service from directory: " << persistence_dir << std::endl;
+ return false;
+ }
+
+ static void test_service_reply_notification_callback(
+ const char* service_name,
+ const char* json,
+ uint64_t request_id,
+ int64_t publish_time)
+ {
+ }
+
+ static void test_service_request_notification_callback(
+ const char* service_name,
+ const char* json,
+ uint64_t request_id,
+ int64_t publish_time)
+ {
+ }
+
+ // ACTIONS
+
+ // Static action notification callback
+ static void test_action_notification_callback(
+ const char* action_name,
+ const eprosima::ddsenabler::participants::ActionInfo& action_info)
+ {
+ }
+
+ // Static action query callback
+ static bool test_action_query_callback(
+ const char* action_name,
+ eprosima::ddsenabler::participants::ActionInfo& action_info)
+ {
+ std::string persistence_dir = input_file_path.string();
+ if (!utils::load_action_from_file(
+ persistence_dir,
+ action_name,
+ action_info))
+ {
+ std::cerr << "Failed to load action: " << action_name << std::endl;
+ return false;
+ }
+ return true;
+ }
+
+ // Static action goal request notification callback
+ static bool test_action_goal_request_notification_callback(
+ const char* action_name,
+ const char* json,
+ const eprosima::ddsenabler::participants::UUID& goal_id,
+ int64_t publish_time)
+ {
+ return false;
+ }
+
+ // Static action result notification callback
+ static void test_action_result_notification_callback(
+ const char* action_name,
+ const char* json,
+ const eprosima::ddsenabler::participants::UUID& goal_id,
+ int64_t publish_time)
+ {
+ }
+
+ // Static action feedback notification callback
+ static void test_action_feedback_notification_callback(
+ const char* action_name,
+ const char* json,
+ const eprosima::ddsenabler::participants::UUID& goal_id,
+ int64_t publish_time)
+ {
+ }
+
+ // Static action status notification callback
+ static void test_action_status_notification_callback(
+ const char* action_name,
+ const eprosima::ddsenabler::participants::UUID& goal_id,
+ eprosima::ddsenabler::participants::StatusCode statusCode,
+ const char* statusMessage,
+ int64_t publish_time)
+ {
+ }
+
+ // Static action cancel request notification callback
+ static void test_action_cancel_request_notification_callback(
+ const char* action_name,
+ const eprosima::ddsenabler::participants::UUID& goal_id,
+ int64_t timestamp,
+ uint64_t request_id,
+ int64_t publish_time)
+ {
+ }
+
// Pointer to the current test instance (for use in the static callback)
static DDSEnablerTester* current_test_instance_;
@@ -394,7 +552,13 @@ class DDSEnablerTester : public ::testing::Test
std::mutex type_received_mutex_;
std::mutex topic_received_mutex_;
std::mutex data_received_mutex_;
+ std::mutex rpc_mutex_;
};
+
+
+
+
+
} // namespace ddsenablertester
diff --git a/ddsenabler/test/ddsEnablerTests/CMakeLists.txt b/ddsenabler/test/ddsEnablerTests/CMakeLists.txt
index 5a0d230c..31e09e60 100644
--- a/ddsenabler/test/ddsEnablerTests/CMakeLists.txt
+++ b/ddsenabler/test/ddsEnablerTests/CMakeLists.txt
@@ -40,6 +40,10 @@ set(TEST_LIST
send_history_bigger_than_writer
send_history_smaller_than_writer
send_history_multiple_types
+ service_client
+ service_server
+ action_client
+ action_server
)
set(TEST_NEEDED_SOURCES
@@ -48,6 +52,7 @@ set(TEST_NEEDED_SOURCES
set(TEST_EXTRA_HEADERS
${PROJECT_SOURCE_DIR}/test
${PROJECT_SOURCE_DIR}/test/ddsEnablerTests/types
+ ${PROJECT_SOURCE_DIR}/examples/utils
)
add_blackbox_executable(
@@ -59,6 +64,29 @@ add_blackbox_executable(
"${TEST_LIBRARY_SOURCES}"
)
+target_sources(blackbox_DDSEnablerTest PRIVATE ${PROJECT_SOURCE_DIR}/examples/utils/Utils.cpp)
+target_include_directories(blackbox_DDSEnablerTest PRIVATE ${PROJECT_SOURCE_DIR}/examples/utils)
+
+# Copy test resources next to the test binary so runtime can use a relative path
+add_custom_command(TARGET blackbox_DDSEnablerTest POST_BUILD
+ COMMAND ${CMAKE_COMMAND} -E copy_directory
+ ${PROJECT_SOURCE_DIR}/test/ddsEnablerTests/test_files
+ $/test_files
+)
+
+# Also copy test resources to the test target binary directory (configuration-agnostic)
+# Rationale: On single-config generators (Linux), CTest runs from the target dir, which matches
+# $. On multi-config generators (Windows/MSVC), CTest's working directory is usually
+# the CMake current binary dir (e.g., .../ddsEnablerTests) while the executable lives under a
+# configuration subfolder (e.g., .../ddsEnablerTests/Debug). The tests locate resources via
+# std::filesystem::current_path()/"test_files"; thus, we ensure the resources are available from
+# the CTest working dir as well.
+add_custom_command(TARGET blackbox_DDSEnablerTest POST_BUILD
+ COMMAND ${CMAKE_COMMAND} -E copy_directory
+ ${PROJECT_SOURCE_DIR}/test/ddsEnablerTests/test_files
+ ${CMAKE_CURRENT_BINARY_DIR}/test_files
+)
+
##################################################################################
diff --git a/ddsenabler/test/ddsEnablerTests/DdsEnablerTest.cpp b/ddsenabler/test/ddsEnablerTests/DdsEnablerTest.cpp
index 85305197..c44f02a3 100644
--- a/ddsenabler/test/ddsEnablerTests/DdsEnablerTest.cpp
+++ b/ddsenabler/test/ddsEnablerTests/DdsEnablerTest.cpp
@@ -385,10 +385,76 @@ TEST_F(DDSEnablerTest, send_history_multiple_types)
ASSERT_EQ(get_received_data(), types * history_depth);
}
+// SERVICES
+
+TEST_F(DDSEnablerTest, service_client)
+{
+ auto enabler = create_ddsenabler();
+ ASSERT_TRUE(enabler != nullptr);
+
+ std::this_thread::sleep_for(std::chrono::seconds(2));
+
+ std::string json = "{\"a\": 1, \"b\": 2}";
+ std::string service_name = "add_two_ints";
+ uint64_t request_id = 0;
+ ASSERT_FALSE(enabler->send_service_request(service_name, json, request_id));
+}
+
+
+TEST_F(DDSEnablerTest, service_server)
+{
+ auto enabler = create_ddsenabler();
+ ASSERT_TRUE(enabler != nullptr);
+
+ std::string service_name = "add_two_ints";
+
+ ASSERT_FALSE(enabler->revoke_service(service_name));
+ ASSERT_TRUE(enabler->announce_service(service_name));
+ ASSERT_FALSE(enabler->announce_service(service_name));
+
+ std::this_thread::sleep_for(std::chrono::seconds(2));
+
+ ASSERT_TRUE(enabler->revoke_service(service_name));
+ ASSERT_FALSE(enabler->revoke_service(service_name));
+}
+
+// ACTIONS
+
+TEST_F(DDSEnablerTest, action_client)
+{
+ auto enabler = create_ddsenabler();
+ ASSERT_TRUE(enabler != nullptr);
+
+ std::this_thread::sleep_for(std::chrono::seconds(2));
+
+ std::string json = "{\"order\": 20}";
+ std::string action_name = "fibonacci/_action/";
+ UUID action_id;
+
+ ASSERT_FALSE(enabler->send_action_goal(action_name, json, action_id));
+}
+
+TEST_F(DDSEnablerTest, action_server)
+{
+ auto enabler = create_ddsenabler();
+ ASSERT_TRUE(enabler != nullptr);
+
+ std::string action_name = "fibonacci/_action/";
+
+ ASSERT_FALSE(enabler->revoke_action(action_name));
+ ASSERT_TRUE(enabler->announce_action(action_name));
+ ASSERT_FALSE(enabler->announce_action(action_name));
+
+ std::this_thread::sleep_for(std::chrono::seconds(2));
+
+ ASSERT_TRUE(enabler->revoke_action(action_name));
+ ASSERT_FALSE(enabler->revoke_action(action_name));
+}
+
int main(
int argc,
char** argv)
{
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
-}
\ No newline at end of file
+}
diff --git a/ddsenabler/test/ddsEnablerTests/ReloadConfig.cpp b/ddsenabler/test/ddsEnablerTests/ReloadConfig.cpp
index e41581d9..1b6afb2e 100644
--- a/ddsenabler/test/ddsEnablerTests/ReloadConfig.cpp
+++ b/ddsenabler/test/ddsEnablerTests/ReloadConfig.cpp
@@ -16,6 +16,7 @@
#include
#include "dds_enabler_runner.hpp"
+#include "ddsenabler_participants/Callbacks.hpp"
#include
#include
@@ -47,16 +48,14 @@ void test_type_notification_callback(
// eprosima::ddsenabler::participants::DdsTopicNotification topic_notification;
void test_topic_notification_callback(
const char* topic_name,
- const char* type_name,
- const char* serialized_qos)
+ const eprosima::ddsenabler::participants::TopicInfo& topic_info)
{
}
// eprosima::ddsenabler::participants::DdsTopicQuery topic_query;
bool test_topic_query_callback(
const char* topic_name,
- std::string& type_name,
- std::string& serialized_qos)
+ eprosima::ddsenabler::participants::TopicInfo& topic_info)
{
return false;
}
diff --git a/ddsenabler/test/ddsEnablerTests/test_files/action_msgs__msg__dds___GoalStatusArray_.bin b/ddsenabler/test/ddsEnablerTests/test_files/action_msgs__msg__dds___GoalStatusArray_.bin
new file mode 100644
index 00000000..9b2666b4
Binary files /dev/null and b/ddsenabler/test/ddsEnablerTests/test_files/action_msgs__msg__dds___GoalStatusArray_.bin differ
diff --git a/ddsenabler/test/ddsEnablerTests/test_files/action_msgs__srv__dds___CancelGoal_Request_.bin b/ddsenabler/test/ddsEnablerTests/test_files/action_msgs__srv__dds___CancelGoal_Request_.bin
new file mode 100644
index 00000000..3312fb7d
Binary files /dev/null and b/ddsenabler/test/ddsEnablerTests/test_files/action_msgs__srv__dds___CancelGoal_Request_.bin differ
diff --git a/ddsenabler/test/ddsEnablerTests/test_files/action_msgs__srv__dds___CancelGoal_Response_.bin b/ddsenabler/test/ddsEnablerTests/test_files/action_msgs__srv__dds___CancelGoal_Response_.bin
new file mode 100644
index 00000000..9b9e0b42
Binary files /dev/null and b/ddsenabler/test/ddsEnablerTests/test_files/action_msgs__srv__dds___CancelGoal_Response_.bin differ
diff --git a/ddsenabler/test/ddsEnablerTests/test_files/action_tutorials_interfaces__action__dds___Fibonacci_FeedbackMessage_.bin b/ddsenabler/test/ddsEnablerTests/test_files/action_tutorials_interfaces__action__dds___Fibonacci_FeedbackMessage_.bin
new file mode 100644
index 00000000..fac6aee9
Binary files /dev/null and b/ddsenabler/test/ddsEnablerTests/test_files/action_tutorials_interfaces__action__dds___Fibonacci_FeedbackMessage_.bin differ
diff --git a/ddsenabler/test/ddsEnablerTests/test_files/action_tutorials_interfaces__action__dds___Fibonacci_GetResult_Request_.bin b/ddsenabler/test/ddsEnablerTests/test_files/action_tutorials_interfaces__action__dds___Fibonacci_GetResult_Request_.bin
new file mode 100644
index 00000000..423f3111
Binary files /dev/null and b/ddsenabler/test/ddsEnablerTests/test_files/action_tutorials_interfaces__action__dds___Fibonacci_GetResult_Request_.bin differ
diff --git a/ddsenabler/test/ddsEnablerTests/test_files/action_tutorials_interfaces__action__dds___Fibonacci_GetResult_Response_.bin b/ddsenabler/test/ddsEnablerTests/test_files/action_tutorials_interfaces__action__dds___Fibonacci_GetResult_Response_.bin
new file mode 100644
index 00000000..62adda76
Binary files /dev/null and b/ddsenabler/test/ddsEnablerTests/test_files/action_tutorials_interfaces__action__dds___Fibonacci_GetResult_Response_.bin differ
diff --git a/ddsenabler/test/ddsEnablerTests/test_files/action_tutorials_interfaces__action__dds___Fibonacci_SendGoal_Request_.bin b/ddsenabler/test/ddsEnablerTests/test_files/action_tutorials_interfaces__action__dds___Fibonacci_SendGoal_Request_.bin
new file mode 100644
index 00000000..4bffcfdd
Binary files /dev/null and b/ddsenabler/test/ddsEnablerTests/test_files/action_tutorials_interfaces__action__dds___Fibonacci_SendGoal_Request_.bin differ
diff --git a/ddsenabler/test/ddsEnablerTests/test_files/action_tutorials_interfaces__action__dds___Fibonacci_SendGoal_Response_.bin b/ddsenabler/test/ddsEnablerTests/test_files/action_tutorials_interfaces__action__dds___Fibonacci_SendGoal_Response_.bin
new file mode 100644
index 00000000..63bb3f25
Binary files /dev/null and b/ddsenabler/test/ddsEnablerTests/test_files/action_tutorials_interfaces__action__dds___Fibonacci_SendGoal_Response_.bin differ
diff --git a/ddsenabler/test/ddsEnablerTests/test_files/add_two_ints b/ddsenabler/test/ddsEnablerTests/test_files/add_two_ints
new file mode 100644
index 00000000..5af99fa0
--- /dev/null
+++ b/ddsenabler/test/ddsEnablerTests/test_files/add_two_ints
@@ -0,0 +1,7 @@
+{
+ "reply_serialized_qos": "reliability: true\ndurability: false\nownership: false\nkeyed: false",
+ "reply_type_name": "example_interfaces::srv::dds_::AddTwoInts_Response_",
+ "request_serialized_qos": "reliability: true\ndurability: false\nownership: false\nkeyed: false",
+ "request_type_name": "example_interfaces::srv::dds_::AddTwoInts_Request_",
+ "service_name": "add_two_ints"
+}
\ No newline at end of file
diff --git a/ddsenabler/test/ddsEnablerTests/test_files/example_interfaces__srv__dds___AddTwoInts_Request_.bin b/ddsenabler/test/ddsEnablerTests/test_files/example_interfaces__srv__dds___AddTwoInts_Request_.bin
new file mode 100644
index 00000000..4b4e5e7d
Binary files /dev/null and b/ddsenabler/test/ddsEnablerTests/test_files/example_interfaces__srv__dds___AddTwoInts_Request_.bin differ
diff --git a/ddsenabler/test/ddsEnablerTests/test_files/example_interfaces__srv__dds___AddTwoInts_Response_.bin b/ddsenabler/test/ddsEnablerTests/test_files/example_interfaces__srv__dds___AddTwoInts_Response_.bin
new file mode 100644
index 00000000..6f5f40ef
Binary files /dev/null and b/ddsenabler/test/ddsEnablerTests/test_files/example_interfaces__srv__dds___AddTwoInts_Response_.bin differ
diff --git a/ddsenabler/test/ddsEnablerTests/test_files/fibonacci__action_ b/ddsenabler/test/ddsEnablerTests/test_files/fibonacci__action_
new file mode 100644
index 00000000..22db6867
--- /dev/null
+++ b/ddsenabler/test/ddsEnablerTests/test_files/fibonacci__action_
@@ -0,0 +1,19 @@
+{
+ "action_name": "fibonacci/_action/",
+ "cancel_reply_action_serialized_qos": "reliability: true\ndurability: false\nownership: false\nkeyed: false",
+ "cancel_reply_action_type": "action_msgs::srv::dds_::CancelGoal_Response_",
+ "cancel_request_action_serialized_qos": "reliability: true\ndurability: false\nownership: false\nkeyed: false",
+ "cancel_request_action_type": "action_msgs::srv::dds_::CancelGoal_Request_",
+ "feedback_action_serialized_qos": "reliability: true\ndurability: false\nownership: false\nkeyed: false",
+ "feedback_action_type": "action_tutorials_interfaces::action::dds_::Fibonacci_FeedbackMessage_",
+ "goal_reply_action_serialized_qos": "reliability: true\ndurability: false\nownership: false\nkeyed: false",
+ "goal_reply_action_type": "action_tutorials_interfaces::action::dds_::Fibonacci_SendGoal_Response_",
+ "goal_request_action_serialized_qos": "reliability: true\ndurability: false\nownership: false\nkeyed: false",
+ "goal_request_action_type": "action_tutorials_interfaces::action::dds_::Fibonacci_SendGoal_Request_",
+ "result_reply_action_serialized_qos": "reliability: true\ndurability: false\nownership: false\nkeyed: false",
+ "result_reply_action_type": "action_tutorials_interfaces::action::dds_::Fibonacci_GetResult_Response_",
+ "result_request_action_serialized_qos": "reliability: true\ndurability: false\nownership: false\nkeyed: false",
+ "result_request_action_type": "action_tutorials_interfaces::action::dds_::Fibonacci_GetResult_Request_",
+ "status_action_serialized_qos": "reliability: true\ndurability: true\nownership: false\nkeyed: false",
+ "status_action_type": "action_msgs::msg::dds_::GoalStatusArray_"
+}
\ No newline at end of file
diff --git a/ddsenabler/test/ddsEnablerTests/test_files/rcl_interfaces__msg__dds___Log_.bin b/ddsenabler/test/ddsEnablerTests/test_files/rcl_interfaces__msg__dds___Log_.bin
new file mode 100644
index 00000000..6e56d56a
Binary files /dev/null and b/ddsenabler/test/ddsEnablerTests/test_files/rcl_interfaces__msg__dds___Log_.bin differ
diff --git a/ddsenabler/test/ddsEnablerTests/test_files/rcl_interfaces__msg__dds___ParameterEvent_.bin b/ddsenabler/test/ddsEnablerTests/test_files/rcl_interfaces__msg__dds___ParameterEvent_.bin
new file mode 100644
index 00000000..98649ae7
Binary files /dev/null and b/ddsenabler/test/ddsEnablerTests/test_files/rcl_interfaces__msg__dds___ParameterEvent_.bin differ
diff --git a/ddsenabler/test/ddsEnablerTests/test_files/rcl_interfaces__srv__dds___DescribeParameters_Request_.bin b/ddsenabler/test/ddsEnablerTests/test_files/rcl_interfaces__srv__dds___DescribeParameters_Request_.bin
new file mode 100644
index 00000000..d33a48d9
Binary files /dev/null and b/ddsenabler/test/ddsEnablerTests/test_files/rcl_interfaces__srv__dds___DescribeParameters_Request_.bin differ
diff --git a/ddsenabler/test/ddsEnablerTests/test_files/rcl_interfaces__srv__dds___DescribeParameters_Response_.bin b/ddsenabler/test/ddsEnablerTests/test_files/rcl_interfaces__srv__dds___DescribeParameters_Response_.bin
new file mode 100644
index 00000000..f7a41219
Binary files /dev/null and b/ddsenabler/test/ddsEnablerTests/test_files/rcl_interfaces__srv__dds___DescribeParameters_Response_.bin differ
diff --git a/ddsenabler/test/ddsEnablerTests/test_files/rcl_interfaces__srv__dds___GetParameterTypes_Request_.bin b/ddsenabler/test/ddsEnablerTests/test_files/rcl_interfaces__srv__dds___GetParameterTypes_Request_.bin
new file mode 100644
index 00000000..f1cf3f19
Binary files /dev/null and b/ddsenabler/test/ddsEnablerTests/test_files/rcl_interfaces__srv__dds___GetParameterTypes_Request_.bin differ
diff --git a/ddsenabler/test/ddsEnablerTests/test_files/rcl_interfaces__srv__dds___GetParameterTypes_Response_.bin b/ddsenabler/test/ddsEnablerTests/test_files/rcl_interfaces__srv__dds___GetParameterTypes_Response_.bin
new file mode 100644
index 00000000..55815c1f
Binary files /dev/null and b/ddsenabler/test/ddsEnablerTests/test_files/rcl_interfaces__srv__dds___GetParameterTypes_Response_.bin differ
diff --git a/ddsenabler/test/ddsEnablerTests/test_files/rcl_interfaces__srv__dds___GetParameters_Request_.bin b/ddsenabler/test/ddsEnablerTests/test_files/rcl_interfaces__srv__dds___GetParameters_Request_.bin
new file mode 100644
index 00000000..fe10b8fb
Binary files /dev/null and b/ddsenabler/test/ddsEnablerTests/test_files/rcl_interfaces__srv__dds___GetParameters_Request_.bin differ
diff --git a/ddsenabler/test/ddsEnablerTests/test_files/rcl_interfaces__srv__dds___GetParameters_Response_.bin b/ddsenabler/test/ddsEnablerTests/test_files/rcl_interfaces__srv__dds___GetParameters_Response_.bin
new file mode 100644
index 00000000..ac082bff
Binary files /dev/null and b/ddsenabler/test/ddsEnablerTests/test_files/rcl_interfaces__srv__dds___GetParameters_Response_.bin differ
diff --git a/ddsenabler/test/ddsEnablerTests/test_files/rcl_interfaces__srv__dds___ListParameters_Request_.bin b/ddsenabler/test/ddsEnablerTests/test_files/rcl_interfaces__srv__dds___ListParameters_Request_.bin
new file mode 100644
index 00000000..9b9387d7
Binary files /dev/null and b/ddsenabler/test/ddsEnablerTests/test_files/rcl_interfaces__srv__dds___ListParameters_Request_.bin differ
diff --git a/ddsenabler/test/ddsEnablerTests/test_files/rcl_interfaces__srv__dds___ListParameters_Response_.bin b/ddsenabler/test/ddsEnablerTests/test_files/rcl_interfaces__srv__dds___ListParameters_Response_.bin
new file mode 100644
index 00000000..a1212fb4
Binary files /dev/null and b/ddsenabler/test/ddsEnablerTests/test_files/rcl_interfaces__srv__dds___ListParameters_Response_.bin differ
diff --git a/ddsenabler/test/ddsEnablerTests/test_files/rcl_interfaces__srv__dds___SetParametersAtomically_Request_.bin b/ddsenabler/test/ddsEnablerTests/test_files/rcl_interfaces__srv__dds___SetParametersAtomically_Request_.bin
new file mode 100644
index 00000000..c19eeb6a
Binary files /dev/null and b/ddsenabler/test/ddsEnablerTests/test_files/rcl_interfaces__srv__dds___SetParametersAtomically_Request_.bin differ
diff --git a/ddsenabler/test/ddsEnablerTests/test_files/rcl_interfaces__srv__dds___SetParametersAtomically_Response_.bin b/ddsenabler/test/ddsEnablerTests/test_files/rcl_interfaces__srv__dds___SetParametersAtomically_Response_.bin
new file mode 100644
index 00000000..1043f036
Binary files /dev/null and b/ddsenabler/test/ddsEnablerTests/test_files/rcl_interfaces__srv__dds___SetParametersAtomically_Response_.bin differ
diff --git a/ddsenabler/test/ddsEnablerTests/test_files/rcl_interfaces__srv__dds___SetParameters_Request_.bin b/ddsenabler/test/ddsEnablerTests/test_files/rcl_interfaces__srv__dds___SetParameters_Request_.bin
new file mode 100644
index 00000000..da5208da
Binary files /dev/null and b/ddsenabler/test/ddsEnablerTests/test_files/rcl_interfaces__srv__dds___SetParameters_Request_.bin differ
diff --git a/ddsenabler/test/ddsEnablerTests/test_files/rcl_interfaces__srv__dds___SetParameters_Response_.bin b/ddsenabler/test/ddsEnablerTests/test_files/rcl_interfaces__srv__dds___SetParameters_Response_.bin
new file mode 100644
index 00000000..7b8a8ec8
Binary files /dev/null and b/ddsenabler/test/ddsEnablerTests/test_files/rcl_interfaces__srv__dds___SetParameters_Response_.bin differ
diff --git a/ddsenabler/test/ddsEnablerTests/test_files/rmw_dds_common__msg__dds___ParticipantEntitiesInfo_.bin b/ddsenabler/test/ddsEnablerTests/test_files/rmw_dds_common__msg__dds___ParticipantEntitiesInfo_.bin
new file mode 100644
index 00000000..c3236d96
Binary files /dev/null and b/ddsenabler/test/ddsEnablerTests/test_files/rmw_dds_common__msg__dds___ParticipantEntitiesInfo_.bin differ
diff --git a/ddsenabler/test/ddsEnablerTests/test_files/type_description_interfaces__srv__dds___GetTypeDescription_Request_.bin b/ddsenabler/test/ddsEnablerTests/test_files/type_description_interfaces__srv__dds___GetTypeDescription_Request_.bin
new file mode 100644
index 00000000..ba4d778e
Binary files /dev/null and b/ddsenabler/test/ddsEnablerTests/test_files/type_description_interfaces__srv__dds___GetTypeDescription_Request_.bin differ
diff --git a/ddsenabler/test/ddsEnablerTests/test_files/type_description_interfaces__srv__dds___GetTypeDescription_Response_.bin b/ddsenabler/test/ddsEnablerTests/test_files/type_description_interfaces__srv__dds___GetTypeDescription_Response_.bin
new file mode 100644
index 00000000..f68b4410
Binary files /dev/null and b/ddsenabler/test/ddsEnablerTests/test_files/type_description_interfaces__srv__dds___GetTypeDescription_Response_.bin differ
diff --git a/ddsenabler/test/ddsEnablerTypedTests/CMakeLists.txt b/ddsenabler/test/ddsEnablerTypedTests/CMakeLists.txt
index 0de57b01..c272ae70 100644
--- a/ddsenabler/test/ddsEnablerTypedTests/CMakeLists.txt
+++ b/ddsenabler/test/ddsEnablerTypedTests/CMakeLists.txt
@@ -686,6 +686,7 @@ set(TEST_NEEDED_SOURCES
set(TEST_EXTRA_HEADERS
${PROJECT_SOURCE_DIR}/test
${PROJECT_SOURCE_DIR}/test/ddsEnablerTypedTests/types
+ ${PROJECT_SOURCE_DIR}/examples/utils
)
add_blackbox_executable(
@@ -697,3 +698,6 @@ add_blackbox_executable(
"${TEST_LIBRARY_SOURCES}"
)
+target_sources(blackbox_DDSEnablerTypedTest PRIVATE ${PROJECT_SOURCE_DIR}/examples/utils/Utils.cpp)
+target_include_directories(blackbox_DDSEnablerTypedTest PRIVATE ${PROJECT_SOURCE_DIR}/examples/utils)
+
diff --git a/ddsenabler/test/ddsEnablerTypedTests/DdsEnablerTypedTest.cpp b/ddsenabler/test/ddsEnablerTypedTests/DdsEnablerTypedTest.cpp
index 01aa630a..040780c2 100644
--- a/ddsenabler/test/ddsEnablerTypedTests/DdsEnablerTypedTest.cpp
+++ b/ddsenabler/test/ddsEnablerTypedTests/DdsEnablerTypedTest.cpp
@@ -46,6 +46,9 @@ class DDSEnablerTypedTest : public ddsenablertester::DDSEnablerTester
a_type.type_sup_.reset(new Type()); \
\
ASSERT_TRUE(create_publisher(a_type)); \
+\
+ /* Wait some time for discovery */ \
+ std::this_thread::sleep_for(std::chrono::milliseconds(wait_after_publication_ms)); \
\
ASSERT_EQ(get_received_types(), 1); \
ASSERT_EQ(get_received_topics(), 1); \
diff --git a/ddsenabler_docs/docs/ddsenabler/api_reference/api_reference.rst b/ddsenabler_docs/docs/ddsenabler/api_reference/api_reference.rst
index 1f991434..271ecaea 100644
--- a/ddsenabler_docs/docs/ddsenabler/api_reference/api_reference.rst
+++ b/ddsenabler_docs/docs/ddsenabler/api_reference/api_reference.rst
@@ -19,4 +19,6 @@ The following pages document the classes, functions, and types exposed by *DDS E
/ddsenabler/api_reference/dds_enabler/dds_enabler
/ddsenabler/api_reference/callback_set/callback_set
/ddsenabler/api_reference/dds_callbacks/dds_callbacks
+ /ddsenabler/api_reference/dds_callbacks/service_callbacks
+ /ddsenabler/api_reference/dds_callbacks/action_callbacks
/ddsenabler/api_reference/dds_callbacks/callbacks
diff --git a/ddsenabler_docs/docs/ddsenabler/api_reference/dds_callbacks/action_callbacks.rst b/ddsenabler_docs/docs/ddsenabler/api_reference/dds_callbacks/action_callbacks.rst
new file mode 100644
index 00000000..967fc5c8
--- /dev/null
+++ b/ddsenabler_docs/docs/ddsenabler/api_reference/dds_callbacks/action_callbacks.rst
@@ -0,0 +1,12 @@
+
+.. _api_pim_action_callbacks:
+
+.. rst-class:: api-ref
+
+Action Callbacks
+----------------
+
+.. doxygenstruct:: eprosima::ddsenabler::ActionCallbacks
+ :project: DDSEnabler
+ :members:
+
diff --git a/ddsenabler_docs/docs/ddsenabler/api_reference/dds_callbacks/service_callbacks.rst b/ddsenabler_docs/docs/ddsenabler/api_reference/dds_callbacks/service_callbacks.rst
new file mode 100644
index 00000000..59ecd67f
--- /dev/null
+++ b/ddsenabler_docs/docs/ddsenabler/api_reference/dds_callbacks/service_callbacks.rst
@@ -0,0 +1,12 @@
+
+.. _api_pim_service_callbacks:
+
+.. rst-class:: api-ref
+
+Service Callbacks
+-----------------
+
+.. doxygenstruct:: eprosima::ddsenabler::ServiceCallbacks
+ :project: DDSEnabler
+ :members:
+
diff --git a/ddsenabler_docs/docs/ddsenabler/installation/linux.rst b/ddsenabler_docs/docs/ddsenabler/installation/linux.rst
index d8b7c2cd..ebb7c377 100644
--- a/ddsenabler_docs/docs/ddsenabler/installation/linux.rst
+++ b/ddsenabler_docs/docs/ddsenabler/installation/linux.rst
@@ -272,6 +272,10 @@ Run an example
In this section, we will run a publish example mimicking the behavior of the classic *Hello World* ROS 2 talker in a specific domain. For simplicity, we will use the *eProsima DDS Enabler* example application
with the already provided configuration file and sample data from a test case in the *dds_enabler_test* package.
+.. note::
+
+ To run a service or action example, please refer to the corresponding readme in the examples folder.
+
To run this *eProsima DDS Enabler* example, source the installation path and execute the executable file that has been installed in :code:`/dds_enabler_tool/bin/dds_enabler`:
.. code-block:: bash
@@ -286,7 +290,7 @@ To run this *eProsima DDS Enabler* example, source the installation path and exe
cd
source install/setup.bash
export TEST_PATH=$PWD/src/DDS-Enabler/ddsenabler_test/compose/test_cases/publish/discovered_type
- ./build/ddsenabler/examples/ddsenabler_example --config $TEST_PATH/config.yml --timeout 10 --expected-types 1 --expected-topics 1 --publish-path $TEST_PATH/samples --publish-topic rt/chatter --publish-period 200 --publish-initial-wait 5000
+ ./install/ddsenabler/examples/publish/ddsenabler_example_publish --config $TEST_PATH/config.yml --timeout 10 --expected-types 1 --expected-topics 1 --publish-path $TEST_PATH/samples --publish-topic rt/chatter --publish-period 200 --publish-initial-wait 5000
.. important::
diff --git a/ddsenabler_participants/include/ddsenabler_participants/Callbacks.hpp b/ddsenabler_participants/include/ddsenabler_participants/Callbacks.hpp
index ed71b6a0..99347e17 100644
--- a/ddsenabler_participants/include/ddsenabler_participants/Callbacks.hpp
+++ b/ddsenabler_participants/include/ddsenabler_participants/Callbacks.hpp
@@ -22,10 +22,38 @@
#include
#include
+#include
+
namespace eprosima {
namespace ddsenabler {
namespace participants {
+
+/*********************/
+/* DDS */
+/*********************/
+
+/**
+ * @brief Struct that contains information about a DDS topic.
+ *
+ * This struct is used to encapsulate the type name and serialized QoS of a DDS topic.
+ */
+struct TopicInfo
+{
+ TopicInfo() = default;
+
+ TopicInfo (
+ const std::string& _type_name,
+ const std::string& _serialized_qos)
+ : type_name(_type_name)
+ , serialized_qos(_serialized_qos)
+ {
+ }
+
+ std::string type_name;
+ std::string serialized_qos;
+};
+
/**
* DdsLogFunc - callback executed when consuming log messages
*
@@ -62,13 +90,11 @@ typedef void (* DdsTypeNotification)(
* DdsTopicNotification - callback for notifying the reception of DDS topics
*
* @param [in] topic_name Name of the received topic
- * @param [in] type_name Name of the type associated with the topic
- * @param [in] serialized_qos Serialized Quality of Service (QoS) of the topic
+ * @param [in] topic_info Information about the topic, including type name and serialized QoS
*/
typedef void (* DdsTopicNotification)(
const char* topic_name,
- const char* type_name,
- const char* serialized_qos);
+ const TopicInfo& topic_info);
/**
* DdsDataNotification - callback for notifying the reception of DDS data
@@ -99,14 +125,247 @@ typedef bool (* DdsTypeQuery)(
* DdsTopicQuery - callback for requesting information (type and QoS) of a DDS topic
*
* @param [in] topic_name Name of the topic to query
- * @param [out] type_name Name of the type associated with the topic
- * @param [out] serialized_qos Serialized Quality of Service (QoS) of the topic
+ * @param [out] topic_info Information about the topic, including type name and serialized QoS
* @return \c true if the topic was found and the information was retrieved successfully, \c false otherwise
*/
typedef bool (* DdsTopicQuery)(
const char* topic_name,
- std::string& type_name,
- std::string& serialized_qos);
+ TopicInfo& topic_info);
+
+
+/**********************/
+/* SERVICES */
+/**********************/
+
+/**
+ * @brief Struct that contains information about a DDS service.
+ *
+ * This struct is used to encapsulate the request and reply topics of a DDS service.
+ */
+struct ServiceInfo
+{
+ ServiceInfo() = default;
+
+ ServiceInfo(
+ const TopicInfo& _request,
+ const TopicInfo& _reply)
+ : request(_request)
+ , reply(_reply)
+ {
+ }
+
+ TopicInfo request;
+ TopicInfo reply;
+};
+
+/**
+ * @brief Callback for notification of service discovery and its request and reply types.
+ *
+ * This callback is used to notify the discovery of a service and its associated request and reply types.
+ *
+ * @param [in] service_name The name of the service that was discovered.
+ * @param [in] service_info Information about the service, including request and reply types and their serialized QoS.
+ */
+typedef void (* ServiceNotification)(
+ const char* service_name,
+ const ServiceInfo& service_info);
+
+/**
+ * @brief Callback for reception of service request data.
+ *
+ * This callback is used to notify the reception of a request for a specific service.
+ *
+ * @param [in] service_name The name of the service for which the request was received.
+ * @param [in] json The JSON data received in the request.
+ * @param [in] request_id The unique identifier of the request.
+ * @param [in] publish_time The time at which the request was published.
+ *
+ * @note The request_id is unique for each request and must be later used to identify the reply.
+ */
+typedef void (* ServiceRequestNotification)(
+ const char* service_name,
+ const char* json,
+ uint64_t request_id,
+ int64_t publish_time);
+
+/**
+ * @brief Callback for reception of RPC reply data.
+ *
+ * This callback is used to notify the reception of a reply for a specific service.
+ *
+ * @param [in] service_name The name of the service for which the reply was received.
+ * @param [in] json The JSON data received in the reply.
+ * @param [in] request_id The unique identifier of the request for which this is a reply.
+ * @param [in] publish_time The time at which the reply was published.
+ */
+typedef void (* ServiceReplyNotification)(
+ const char* service_name,
+ const char* json,
+ uint64_t request_id,
+ int64_t publish_time);
+
+/**
+ * @brief Callback requesting the information of a given service's request and reply.
+ *
+ * This callback is used to request the information for a service's request and reply.
+ *
+ * @param [in] service_name The name of the service for which the information is requested.
+ * @param [out] service_info Information about the service, including request and reply types and their serialized QoS.
+ * @return \c true if the service was found and the information was retrieved successfully, \c false otherwise.
+ */
+typedef bool (* ServiceQuery)(
+ const char* service_name,
+ ServiceInfo& service_info);
+
+
+/**********************/
+/* ACTIONS */
+/**********************/
+
+/**
+ * @brief Struct that contains information about a DDS action.
+ *
+ * This struct is used to encapsulate the goal, result and cancel services as well as the feedback and status topics of a DDS action.
+ */
+struct ActionInfo
+{
+ ActionInfo() = default;
+
+ ActionInfo(
+ const ServiceInfo& _goal,
+ const ServiceInfo& _result,
+ const ServiceInfo& _cancel,
+ const TopicInfo& _feedback,
+ const TopicInfo& _status)
+ : goal(_goal)
+ , result(_result)
+ , cancel(_cancel)
+ , feedback(_feedback)
+ , status(_status)
+ {
+ }
+
+ ServiceInfo goal;
+ ServiceInfo result;
+ ServiceInfo cancel;
+ TopicInfo feedback;
+ TopicInfo status;
+};
+
+/**
+ * @brief Callback for notification of action discovery and its associated types.
+ *
+ * This callback is used to notify the discovery of an action and its associated types.
+ *
+ * @param [in] action_name The name of the action that was discovered.
+ * @param [in] action_info Information about the action, including goal, result, cancel, feedback, and status types and their serialized QoS.
+ */
+typedef void (* ActionNotification)(
+ const char* action_name,
+ const ActionInfo& action_info);
+
+/**
+ * @brief Callback for notification of an action goal request.
+ *
+ * This callback is used to notify the request of an action goal.
+ *
+ * @param [in] action_name The name of the action for which the goal is being requested.
+ * @param [in] json The JSON data representing the goal request.
+ * @param [in] goal_id The unique identifier of the goal associated with the action.
+ * @param [in] publish_time The time at which the goal request was published.
+ * @return \c true if the goal request has been accepted, \c false otherwise.
+ */
+typedef bool (* ActionGoalRequestNotification)(
+ const char* action_name,
+ const char* json,
+ const UUID& goal_id,
+ int64_t publish_time);
+
+/**
+ * @brief Callback for notification of an action cancel request.
+ *
+ * This callback is used to notify the request to cancel an action goal:
+ *
+ * If the goal ID is empty and timestamp is zero, cancel all goals
+ * If the goal ID is empty and timestamp is not zero, cancel all goals accepted at or before the timestamp
+ * If the goal ID is not empty and timestamp is zero, cancel the goal with the given ID regardless of the time it was accepted
+ * If the goal ID is not empty and timestamp is not zero, cancel the goal with the given ID and all goals accepted at or before the timestamp
+ *
+ * @param [in] action_name The name of the action for which the cancel request is being made.
+ * @param [in] goal_id The unique identifier of the goal associated with the action.
+ * @param [in] timestamp The timestamp used for discriminating which goals to cancel.
+ * @param [in] request_id The identifier of the cancel request used for dicriminating which goals to cancel.
+ * @param [in] publish_time The time at which the cancel request was published.
+ */
+typedef void (* ActionCancelRequestNotification)(
+ const char* action_name,
+ const UUID& goal_id,
+ int64_t timestamp,
+ uint64_t request_id,
+ int64_t publish_time);
+
+/**
+ * @brief Callback for notification of action feedback.
+ *
+ * This callback is used to notify the feedback of an action.
+ *
+ * @param [in] action_name The name of the action for which the feedback is being notified.
+ * @param [in] json The JSON data representing the feedback of the action.
+ * @param [in] goal_id The unique identifier of the goal associated with the action.
+ * @param [in] publish_time The time at which the feedback was published.
+ */
+typedef void (* ActionFeedbackNotification)(
+ const char* action_name,
+ const char* json,
+ const UUID& goal_id,
+ int64_t publish_time);
+
+/**
+ * @brief Callback for notification of an update for an action status.
+ *
+ * This callback is used to notify the update of the status of an action.
+ *
+ * @param [in] action_name The name of the action for which the status is being notified.
+ * @param [in] goal_id The unique identifier of the goal associated with the action.
+ * @param [in] status_code The status code representing the current state of the action.
+ * @param [in] status_message A message providing additional information about the status.
+ * @param [in] publish_time The time at which the status was published.
+ */
+typedef void (* ActionStatusNotification)(
+ const char* action_name,
+ const UUID& goal_id,
+ StatusCode status_code,
+ const char* status_message,
+ int64_t publish_time);
+
+/**
+ * @brief Callback for notification of action result.
+ *
+ * This callback is used to notify the result of an action in case of success.
+ *
+ * @param [in] action_name The name of the action for which the result is being notified.
+ * @param [in] json The JSON data representing the result of the action.
+ * @param [in] goal_id The unique identifier of the goal associated with the action.
+ * @param [in] publish_time The time at which the result was published.
+ */
+typedef void (* ActionResultNotification)(
+ const char* action_name,
+ const char* json,
+ const UUID& goal_id,
+ int64_t publish_time);
+
+/**
+ * @brief Callback for requesting action's information.
+ *
+ * This callback is used to request information about a specific action.
+ *
+ * @param [in] action_name The name of the action for which the information is being requested.
+ * @param [out] action_info Information about the action, including goal, result, cancel, feedback, and status types and their serialized QoS.
+ * @return \c true if the action was found and the information was retrieved successfully, \c false otherwise.
+ */
+typedef bool (* ActionQuery)(
+ const char* action_name,
+ ActionInfo& action_info);
} /* namespace participants */
} /* namespace ddsenabler */
diff --git a/ddsenabler_participants/include/ddsenabler_participants/Constants.hpp b/ddsenabler_participants/include/ddsenabler_participants/Constants.hpp
index acbf84ac..df74515f 100644
--- a/ddsenabler_participants/include/ddsenabler_participants/Constants.hpp
+++ b/ddsenabler_participants/include/ddsenabler_participants/Constants.hpp
@@ -28,6 +28,28 @@ constexpr const char* QOS_SERIALIZATION_DURABILITY("durability");
constexpr const char* QOS_SERIALIZATION_OWNERSHIP("ownership");
constexpr const char* QOS_SERIALIZATION_KEYED("keyed");
+// Topic mangling
+constexpr const char* ROS_TOPIC_PREFIX("rt/");
+constexpr const char* FASTDDS_TOPIC_PREFIX("");
+
+// Service mangling
+constexpr const char* ROS_REQUEST_PREFIX("rq/");
+constexpr const char* ROS_REQUEST_SUFFIX("Request");
+constexpr const char* ROS_REPLY_PREFIX("rr/");
+constexpr const char* ROS_REPLY_SUFFIX("Reply");
+
+constexpr const char* DDS_REQUEST_PREFIX("");
+constexpr const char* DDS_REQUEST_SUFFIX("_Request");
+constexpr const char* DDS_REPLY_PREFIX("");
+constexpr const char* DDS_REPLY_SUFFIX("_Reply");
+
+// Action mangling
+constexpr const char* ACTION_GOAL_SUFFIX("send_goal");
+constexpr const char* ACTION_RESULT_SUFFIX("get_result");
+constexpr const char* ACTION_CANCEL_SUFFIX("cancel_goal");
+constexpr const char* ACTION_FEEDBACK_SUFFIX("feedback");
+constexpr const char* ACTION_STATUS_SUFFIX("status");
+
} /* namespace participants */
} /* namespace ddsenabler */
} /* namespace eprosima */
diff --git a/ddsenabler_participants/include/ddsenabler_participants/EnablerParticipant.hpp b/ddsenabler_participants/include/ddsenabler_participants/EnablerParticipant.hpp
index 2e67b8a7..51282e2f 100644
--- a/ddsenabler_participants/include/ddsenabler_participants/EnablerParticipant.hpp
+++ b/ddsenabler_participants/include/ddsenabler_participants/EnablerParticipant.hpp
@@ -28,6 +28,10 @@
#include
#include
#include
+#include
+#include
+#include
+
namespace eprosima {
namespace ddsenabler {
@@ -44,6 +48,12 @@ class EnablerParticipant : public ddspipe::participants::SchemaParticipant
std::shared_ptr discovery_database,
std::shared_ptr schema_handler);
+ DDSENABLER_PARTICIPANTS_DllAPI
+ bool is_rtps_kind() const noexcept override
+ {
+ return true; // Temporal workaround until Pipe refactor
+ }
+
DDSENABLER_PARTICIPANTS_DllAPI
std::shared_ptr create_reader(
const ddspipe::core::ITopic& topic) override;
@@ -53,6 +63,12 @@ class EnablerParticipant : public ddspipe::participants::SchemaParticipant
const std::string& topic_name,
const std::string& json);
+ DDSENABLER_PARTICIPANTS_DllAPI
+ bool publish_rpc(
+ const std::string& topic_name,
+ const std::string& json,
+ const uint64_t request_id);
+
DDSENABLER_PARTICIPANTS_DllAPI
void set_topic_query_callback(
participants::DdsTopicQuery callback)
@@ -60,22 +76,188 @@ class EnablerParticipant : public ddspipe::participants::SchemaParticipant
topic_query_callback_ = callback;
}
+ DDSENABLER_PARTICIPANTS_DllAPI
+ void set_service_query_callback(
+ participants::ServiceQuery callback)
+ {
+ service_query_callback_ = callback;
+ }
+
+ DDSENABLER_PARTICIPANTS_DllAPI
+ void set_action_query_callback(
+ participants::ActionQuery callback)
+ {
+ action_query_callback_ = callback;
+ }
+
+ DDSENABLER_PARTICIPANTS_DllAPI
+ bool announce_service(
+ const std::string& service_name,
+ Protocol Protocol);
+
+ DDSENABLER_PARTICIPANTS_DllAPI
+ bool revoke_service(
+ const std::string& service_name);
+
+ DDSENABLER_PARTICIPANTS_DllAPI
+ bool send_service_request(
+ const std::string& service_name,
+ const std::string& json,
+ uint64_t& request_id,
+ participants::Protocol Protocol);
+
+ DDSENABLER_PARTICIPANTS_DllAPI
+ bool send_service_reply(
+ const std::string& service_name,
+ const std::string& json,
+ const uint64_t request_id);
+
+ DDSENABLER_PARTICIPANTS_DllAPI
+ Protocol get_service_protocol(
+ const std::string& service_name);
+
+ DDSENABLER_PARTICIPANTS_DllAPI
+ bool announce_action(
+ const std::string& action_name,
+ Protocol Protocol);
+
+ DDSENABLER_PARTICIPANTS_DllAPI
+ bool revoke_action(
+ const std::string& action_name);
+
+ DDSENABLER_PARTICIPANTS_DllAPI
+ bool send_action_goal(
+ const std::string& action_name,
+ const std::string& json,
+ UUID& action_id,
+ participants::Protocol Protocol);
+
+ DDSENABLER_PARTICIPANTS_DllAPI
+ bool cancel_action_goal(
+ const std::string& action_name,
+ const UUID& goal_id,
+ const int64_t timestamp);
+
+ DDSENABLER_PARTICIPANTS_DllAPI
+ bool send_action_get_result_request(
+ const std::string& action_name,
+ const UUID& action_id);
+
+ DDSENABLER_PARTICIPANTS_DllAPI
+ void send_action_send_goal_reply(
+ const std::string& action_name,
+ const uint64_t goal_id,
+ bool accepted);
+
+ DDSENABLER_PARTICIPANTS_DllAPI
+ bool send_action_cancel_goal_reply(
+ const char* action_name,
+ const std::vector& goal_ids,
+ const CancelCode& cancel_code,
+ const uint64_t request_id);
+
+ DDSENABLER_PARTICIPANTS_DllAPI
+ bool send_action_result(
+ const char* action_name,
+ const UUID& goal_id,
+ const StatusCode& status_code,
+ const char* json);
+
+ DDSENABLER_PARTICIPANTS_DllAPI
+ bool send_action_get_result_reply(
+ const std::string& action_name,
+ const UUID& goal_id,
+ const std::string& reply_json,
+ const uint64_t request_id);
+
+ DDSENABLER_PARTICIPANTS_DllAPI
+ bool send_action_feedback(
+ const char* action_name,
+ const char* json,
+ const UUID& goal_id);
+
+ DDSENABLER_PARTICIPANTS_DllAPI
+ bool update_action_status(
+ const std::string& action_name,
+ const UUID& goal_id,
+ const StatusCode& status_code);
+
protected:
- std::shared_ptr lookup_reader_nts_(
+ bool query_topic_nts_(
+ const std::string& topic_name,
+ ddspipe::core::types::DdsTopic& topic);
+
+ bool query_service_nts_(
+ std::shared_ptr service,
+ Protocol Protocol);
+
+ bool query_action_nts_(
+ ActionDiscovered& action,
+ Protocol Protocol,
+ std::unique_lock& lck);
+
+ bool create_topic_writer_nts_(
+ const ddspipe::core::types::DdsTopic& topic,
+ std::shared_ptr& reader,
+ std::unique_lock& lck);
+
+ bool create_topic_writer_nts_(
+ const ddspipe::core::types::DdsTopic& topic,
+ std::shared_ptr& reader,
+ ddspipe::core::types::Endpoint& request_edp,
+ std::unique_lock& lck);
+
+
+ bool create_service_request_writer_nts_(
+ std::shared_ptr service,
+ std::unique_lock& lck);
+
+ bool fill_topic_struct_nts_(
+ const std::string& topic_name,
+ const TopicInfo& topic_info,
+ ddspipe::core::types::DdsTopic& topic);
+
+ bool fill_service_type_nts_(
+ const ServiceInfo& service_info,
+ std::shared_ptr service,
+ Protocol Protocol);
+
+ std::shared_ptr lookup_reader_nts_(
const std::string& topic_name,
std::string& type_name) const;
- std::shared_ptr lookup_reader_nts_(
+ std::shared_ptr lookup_reader_nts_(
const std::string& topic_name) const;
+ bool service_discovered_nts_(
+ const std::shared_ptr rpc_info,
+ const ddspipe::core::types::DdsTopic& topic);
+
+ bool action_discovered_nts_(
+ const std::shared_ptr rpc_info,
+ const ddspipe::core::types::DdsTopic& topic);
+
+ bool revoke_service_nts_(
+ const std::string& service_name);
+
std::map> readers_;
+ std::map> services_;
+
+ std::map> actions_;
+
std::mutex mtx_;
std::condition_variable cv_;
DdsTopicQuery topic_query_callback_;
+
+ ServiceQuery service_query_callback_;
+
+ ActionQuery action_query_callback_;
+
+ std::shared_ptr handler_;
};
} /* namespace participants */
diff --git a/ddsenabler_participants/include/ddsenabler_participants/Handler.hpp b/ddsenabler_participants/include/ddsenabler_participants/Handler.hpp
index 99a4e6f4..92fc5324 100644
--- a/ddsenabler_participants/include/ddsenabler_participants/Handler.hpp
+++ b/ddsenabler_participants/include/ddsenabler_participants/Handler.hpp
@@ -21,11 +21,13 @@
#include
#include
#include
-#include