Getting Started¶
The “Getting Started” section of this documentation serves as a comprehensive guide to assist in creating a basic publication/subscription application using Safe DDS. By following the step-by-step instructions provided, it is possible to quickly establish a functional example of Safe DDS and gain a better understanding of the library.
It is important to note that in order to successfully install and run Safe DDS, certain minimum requirements must be met. These requirements are outlined in the Minimum Requirements section, which should be referred prior to installation.
Note
This example is ready to be compatible with Fast DDS Hello World example.
Note
To obtain a copy of Safe DDS library please contact support@eProsima.com.
Download Code
Try this example by downloading the code from this CMake project.
Build and install library¶
To build and install Safe DDS, the standard CMake procedure is employed, as follows:
cmake "$SAFEDDS_PATH" \
-B"$BUILD_SAFEDDS_FOLDER" \
-DCMAKE_INSTALL_PREFIX="$INSTALL_SAFEDDS_FOLDER"
cmake --build "$BUILD_SAFEDDS_FOLDER" --target install
Once the installation process is completed, the libsafedds.a file and an include directory can be found in the designated installation directory.
Create a CMake project¶
To get started with Safe DDS, the first step is to create a CMake project with a folder structure as shown below:
safe_hello_world
├── CMakeLists.txt
└── src
└── main.cpp
After the project structure folder has been created, a minimal version of the CMakeLists.txt file can be generated as follows:
mkdir safe_hello_world
cd safe_hello_world
touch CMakeLists.txt
The CMakeLists.txt file is a crucial component, as it contains the build instructions for the project. The following example CMakeLists.txt file is provided to assist in properly configuring the project’s build infrastructure:
cmake_minimum_required(VERSION 3.5)
project(safedds_getting_started)
find_package(safedds REQUIRED)
if(NOT DEFINED SAFEDDS_SIGNATURE)
message(FATAL_ERROR "SAFEDDS_SIGNATURE variable is not set. Please set it to the path of the signature file `safe-dds-signature.cpp` provided by eProsima.")
endif()
file(GLOB SRCS ./src/*.cpp)
list(APPEND SRCS ${SAFEDDS_SIGNATURE})
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -fno-exceptions -fno-rtti -Wall -Werror -Wextra -Wpedantic")
add_executable(${CMAKE_PROJECT_NAME} ${SRCS})
target_include_directories(${CMAKE_PROJECT_NAME} PRIVATE include)
target_link_libraries(${CMAKE_PROJECT_NAME} safedds)
Safe DDS application¶
After the project folder and CMakeLists.txt file have been set up, the next step is to create a main.cpp file that will contain the application’s source code:
mkdir src
cd src
touch main.cpp
To further illustrate the usage of Safe DDS, a simple application that publishes a HelloWorld message on a HelloWorldTopic topic is presented.
To achieve this, the application requires both the DDS headers and a type support.
The specifics of typesupport are be covered in the upcoming TypeSupport section.
#include <safedds/dds/DomainParticipantFactory.hpp>
#include <safedds/dds/Subscriber.hpp>
#include <safedds/dds/Topic.hpp>
#include <safedds/dds/Publisher.hpp>
#include <safedds/dds/DataWriter.hpp>
#include <safedds/dds/DataReader.hpp>
#include <safedds/dds/DataReaderListener.hpp>
#include <safedds/dds/TypedDataReader.hpp>
#include <safedds/dds/TypedDataWriter.hpp>
#include "HelloWorldTypeSupport.hpp"
#include <iostream>
#include <unistd.h>
#include <cstdlib>
#include <ctime>
using namespace eprosima;
using namespace eprosima::safedds;
using namespace eprosima::safedds::dds;
DomainParticipant¶
To create a DDS DomainParticipant for the application, a DomainParticipantFactory is used:
DomainParticipantFactory factory;
// Create a DomainParticipant
DomainId domain_id = 0;
memory::container::StaticString256 participant_name("HelloWorldParticipant");
DomainParticipantQos participant_qos{};
participant_qos.participant_name() = participant_name;
participant_qos.wire_protocol_qos().announced_locator = transport::Locator::from_ipv4({127, 0, 0, 1}, port);
DomainParticipant* participant = factory.create_participant(
domain_id, participant_qos, nullptr, NONE_STATUS_MASK);
if (nullptr == participant)
{
std::cerr << "Error creating DomainParticipant" << std::endl;
return 1;
}
TypeSupport¶
Once the Domain Participant has been created, the next step is to register the Type Support. More information on how to handle Type Supports in Safe DDS can be found in Typesupport.
// Topic and type names
memory::container::StaticString256 topic_name("HelloWorldTopic");
memory::container::StaticString256 type_name("HelloWorld");
// Register the type
HelloWorldTypeSupport type_support{};
type_support.register_type(*participant, type_name);
Note
For this particular application, the used Type Support class is the one provided as reference in typesupport section
Publication entities¶
After the Type Support has been registered, the next step is to create a DDS Topic, Publisher, and DataWriter, as demonstrated below:
// Create a Topic
TopicQos topic_qos{};
Topic* topic = participant->create_topic(
topic_name, type_name, topic_qos, nullptr, NONE_STATUS_MASK);
if (nullptr == topic)
{
std::cerr << "Error creating Topic" << std::endl;
return 1;
}
// Create a Publisher
PublisherQos publisher_qos{};
Publisher* publisher = participant->create_publisher(
publisher_qos, nullptr, NONE_STATUS_MASK);
if (nullptr == publisher)
{
std::cerr << "Error creating Publisher" << std::endl;
return 1;
}
// Create a DataWriter
DataWriterQos datawriter_qos{};
datawriter_qos.reliability().kind = ReliabilityQosPolicyKind::RELIABLE_RELIABILITY_QOS;
datawriter_qos.history().kind = HistoryQosPolicyKind::KEEP_LAST_HISTORY_QOS;
datawriter_qos.history().depth = 10;
DataWriter* datawriter = publisher->create_datawriter(
*topic, datawriter_qos, nullptr, NONE_STATUS_MASK);
if (nullptr == datawriter)
{
std::cerr << "Error creating DataWriter" << std::endl;
return 1;
}
Creating the DDS Topic involves specifying the name of the topic and the name of the type that it uses. Once the Topic is created, a DDS Publisher is created, and finally, a DDS DataWriter is created, which is used to write messages to the topic.
Subscription entities¶
Before a DDS DataReader is created, an optional DataReader Listener class is implemented. This class will be notified when a new message is received.
An example implementation HelloWorldListener is shown below:
struct HelloWorldDataReaderListener :
public DataReaderListener
{
void on_data_available(
DataReader& reader) noexcept override
{
auto* typed_reader = TypedDataReader<HelloWorldTypeSupport>::downcast(reader);
if (nullptr == typed_reader)
{
std::cout << "Error downcasting TypedDataReader" << std::endl;
return;
}
HelloWorldTypeSupport::DataType data{};
dds::SampleInfo info{};
while (typed_reader->take_next_sample(data, info) == dds::ReturnCode::OK)
{
if (info.valid_data)
{
std::cout << "[DW: " << info.publication_handle << "] Message: "
<< data.message << " with index: " << data.index << std::endl;
}
}
}
};
Once the Listener class has been implemented, a Subscriber and a DDS DataReader can be created using the previously created DDS Topic as demonstrated below:
// Create a Subscriber
SubscriberQos subscriber_qos{};
Subscriber* subscriber = participant->create_subscriber(
subscriber_qos, nullptr, NONE_STATUS_MASK);
if (nullptr == subscriber)
{
std::cerr << "Error creating Subscriber" << std::endl;
return 1;
}
// Create a DataReader
DataReaderQos datareader_qos{};
datareader_qos.reliability().kind = ReliabilityQosPolicyKind::RELIABLE_RELIABILITY_QOS;
datareader_qos.history().kind = HistoryQosPolicyKind::KEEP_LAST_HISTORY_QOS;
datareader_qos.history().depth = 10;
HelloWorldDataReaderListener listener{};
DataReader* datareader = subscriber->create_datareader(
*topic, datareader_qos, &listener, DATA_AVAILABLE_STATUS);
if (nullptr == datareader)
{
std::cerr << "Error creating DataReader" << std::endl;
return 1;
}
Creating the DDS DataReader involves specifying the required Topic, as well as the previously created Listener. Once the DataReader has been created, it can be used to read messages from the topic.
Enabling entities¶
Once all entities have been created, they need to be enabled in order to start processing messages.
// Enable the entities
bool enabled = true;
enabled = enabled && (dds::ReturnCode::OK == datareader->enable());
enabled = enabled && (dds::ReturnCode::OK == datawriter->enable());
enabled = enabled && (dds::ReturnCode::OK == subscriber->enable());
enabled = enabled && (dds::ReturnCode::OK == publisher->enable());
enabled = enabled && (dds::ReturnCode::OK == topic->enable());
enabled = enabled && (dds::ReturnCode::OK == participant->enable());
if (!enabled)
{
std::cerr << "Error enabling entities" << std::endl;
return 1;
}
Publishing¶
To enable periodic publication of messages, a Safe DDS Timer can be used to trigger a publication at regular intervals.
The publish function is responsible for creating a message and writing it to the DDS DataWriter, as shown below:
auto* typed_writer = TypedDataWriter<HelloWorldTypeSupport>::downcast(*datawriter);
if (nullptr == typed_writer)
{
std::cout << "Error downcasting TypedDataWriter" << std::endl;
return 1;
}
auto publish = [&]()
{
static uint32_t index = 0;
// Create a data sample
HelloWorldTypeSupport::DataType data{};
data.index = index++;
snprintf(data.message,
sizeof(HelloWorldTypeSupport::DataType::message), "HelloWorld");
data.message_size = strlen(data.message) + 1;
// Write the data sample
typed_writer->write(data, HANDLE_NIL);
};
execution::Timer publish_timer = execution::Timer(execution::TimePeriod{1, 0});
Note that the message is created using the Type Support class and filled with the desired data. Once the message is created, it is written into the a type aware DDS DataWriter named TypedDataWriter, which will publish it to the topic.
Spinning entities¶
Once the DDS entities have been created and the timer has been set up, they need to be spun in order to start processing messages. Spinning refers to the process of dispatching DDS events, such as incoming messages, timeouts, and status changes, to the appropriate DDS entities.
The easiest way to spin the DDS entities is using the default executor provided by the DDS DomainParticipantFactory, as shown below:
execution::ISpinnable* executor = factory.create_default_executor();
if (nullptr == executor)
{
std::cerr << "Error creating executor" << std::endl;
return 1;
}
while (true)
{
if (publish_timer.is_triggered_and_reset())
{
publish();
}
while (executor->has_pending_work())
{
executor->spin(execution::TIME_ZERO);
}
executor->spin(
execution::TimePoint::min(publish_timer.next_trigger(), executor->get_next_work_timepoint())
);
}
This creates an executor that will internally dispatch events to the appropriate DDS entities, such as the DataReader and DataWriter.
More information about Safe DDS execution can be found at Execution module section.
By spinning the DDS entities, the application is now able to publish messages at regular intervals.
The DDS DataReader created will receive the messages from local and remote DDS DomainParticipants and will notify the HelloWorldListener class when a new message is received.
Running the application¶
To run the application, first navigate to the project directory, create a build folder (GETTING_STARTED_FOLDER` on the example) and generate the build files with CMake.
cmake "$GETTING_STARTED_FOLDER" -Bbuild -DCMAKE_PREFIX_PATH="$INSTALL_SAFEDDS_FOLDER" -DSAFEDDS_SIGNATURE="${SIGNATURE_FILE}"
cmake --build build
This command will configure the project with the default settings, including the path to the Safe DDS library, and it will then build the application. Once the build is complete, the application can be run by executing the binary file from the build directory:
./safedds_getting_started
If the application is successfully connected to the network and running, the console output should show messages similar to the following:
[DW: 0] Message: Hello World 0 with index: 0
[DW: 0] Message: Hello World 1 with index: 1
[DW: 0] Message: Hello World 2 with index: 2
[DW: 0] Message: Hello World 3 with index: 3
[DW: 0] Message: Hello World 4 with index: 4
[DW: 0] Message: Hello World 5 with index: 5
[DW: 16777216] Message: HelloWorld with index: 2
[DW: 16777216] Message: HelloWorld with index: 3
[DW: 16777216] Message: HelloWorld with index: 4
[DW: 16777216] Message: HelloWorld with index: 5
[DW: 16777216] Message: HelloWorld with index: 6
[DW: 16777216] Message: HelloWorld with index: 7
[DW: 16777216] Message: HelloWorld with index: 8
[DW: 16777216] Message: HelloWorld with index: 9
[DW: 0] Message: Hello World 6 with index: 6
[DW: 16777216] Message: HelloWorld with index: 10
[DW: 0] Message: Hello World 7 with index: 7
[DW: 0] Message: Hello World 8 with index: 8
[DW: 0] Message: Hello World 9 with index: 9
The program will continue to publish messages every second until it is manually stopped.