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.

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)

file(GLOB SRCS ./src/*.cpp)

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>

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}, 8888);

DomainParticipant* participant = factory.create_participant(
    domain_id, participant_qos, nullptr, NONE_STATUS_MASK);

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);

// Create a Publisher
PublisherQos publisher_qos{};
Publisher* publisher = participant->create_publisher(
    publisher_qos, nullptr, NONE_STATUS_MASK);

// Create a DataWriter
DataWriterQos datawriter_qos{};
datawriter_qos.reliability().kind = ReliabilityQosPolicyKind::RELIABLE_RELIABILITY_QOS;

DataWriter* datawriter = publisher->create_datawriter(
    *topic, datawriter_qos, nullptr, NONE_STATUS_MASK);

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);

        HelloWorldTypeSupport::DataType data{};
        dds::SampleInfo info{};
        typed_reader->take_next_sample(data, info);

        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);

// Create a DataReader
DataReaderQos datareader_qos{};
datareader_qos.reliability().kind = ReliabilityQosPolicyKind::RELIABLE_RELIABILITY_QOS;

HelloWorldDataReaderListener listener{};
DataReader* datareader = subscriber->create_datareader(
    *topic, datareader_qos, &listener, DATA_AVAILABLE_STATUS);

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
publisher->enable();
datawriter->enable();
subscriber->enable();
datareader->enable();
participant->enable();

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);

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();

while (true)
{
    if (publish_timer.is_triggered_and_reset())
    {
        publish();
    }

    while (executor->has_pending_work())
    {
        executor->spin(execution::TIME_ZERO);
    }

    usleep(1000);
}

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"
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.

Further reading

An example on how to integrate Safe DDS with ROS 2 can be found in the ROS 2 Getting Started section.

An example on how to create a Safe DDS application with static memory management Getting Started Static API section.