Getting Started Static API

This section serves as a comprehensive guide to assist in creating a basic publication/subscription application with static memory management 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.

Create a CMake project

Create a CMake project with a folder structure as shown below:

static_api
├── CMakeLists.txt
└── src
    └── main.cpp

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

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 created HelloWorldTopic topic using static API. 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/static/StaticDomainParticipant.hpp>
#include <safedds/dds/static/StaticTopic.hpp>
#include <safedds/dds/static/StaticPublisher.hpp>
#include <safedds/dds/static/StaticDataWriter.hpp>
#include <safedds/dds/static/StaticSubscriber.hpp>
#include <safedds/dds/static/StaticDataReader.hpp>

#include <safedds/execution/BasicExecutor.hpp>
#include <safedds/memory/container/StaticReferenceList.hpp>
#include <safedds/transport.hpp>

#include "HelloWorldTypeSupport.hpp"

#include <iostream>
#include <unistd.h>

using namespace eprosima;
using namespace eprosima::safedds;
using namespace eprosima::safedds::dds;

Static Transport and Static Platform

To ensure the lack of heap allocations, this application will override the default platform:

class StaticPlatform : public platform::IPlatform
{
public:

    StaticPlatform()
        : platform_(get_platform())
    {
    }

    execution::TimePoint get_current_timepoint() noexcept
    {
        return platform_.get_current_timepoint();
    }

    [[noreturn]] void fatal_error(
            const char* msg) noexcept
    {
        platform_.fatal_error(msg);
        while (true)
        {
        }
    }

    void* allocate(
            size_t /* size */) noexcept
    {
        // No allocations allowed
        std::cout << "Fatal error: no allocations allowed" << std::endl;
        while (1)
        {
        }

        return nullptr;
    }

private:

    // Overrided platform
    platform::IPlatform& platform_;
};

Also, it will generate a static transport that inherits from the POSIX UDPv4 transport:

#include <safedds/memory/container/StaticMap.hpp>
#include <safedds/transport/StaticGUIDLocatorDatabase.hpp>
#include <safedds/transport/posix/udpv4/UDPv4.hpp>

class StaticUDPTransport : public transport::posix::UDPv4
{
public:

    StaticUDPTransport()
        : transport::posix::UDPv4 (
            memory::byte_array::ByteArrayView(reception_buffer_, RECEPTION_MAX_SIZE),
            SENDING_MAX_SIZE,
            locator_database_,
            sockets_
            )
    {
    }

private:

    // Reception buffer
    static constexpr size_t SENDING_MAX_SIZE = 64000;
    static constexpr size_t RECEPTION_MAX_SIZE = 10000;
    uint8_t reception_buffer_[RECEPTION_MAX_SIZE];

    // Locator database
    static constexpr size_t TRANSPORT_MAX_GUIDS = 100;
    static constexpr size_t TRANSPORT_MAX_PARTICIPANTS = 100;
    transport::StaticGUIDLocatorDatabase<100, 100> locator_database_;

    // Sockets database
    static constexpr size_t MAX_SOCKETS = 100;
    memory::container::StaticMap<transport::Locator, struct pollfd, MAX_SOCKETS> sockets_;
};

In order to use both the static platform and the static transport, the application will set them at the beginning of the entry point:

StaticPlatform platform;
set_platform(platform);

StaticUDPTransport transport;
set_transport(transport);

StaticDomainParticipant

To create a DDS DomainParticipant for the application, a StaticDomainParticipant is manually instantiated:

DomainParticipantFactory factory;

// Create a DomainParticipant
DomainId domain_id = 0;
memory::container::StaticString256 participant_name("StaticHelloWorldParticipant");
DomainParticipantQos participant_qos{};
participant_qos.participant_name() = participant_name;
participant_qos.wire_protocol_qos().announced_locator = transport::Locator::from_ipv4({127, 0, 0, 1}, 8889);

factory.prepare_participant_qos(domain_id, participant_qos);

constexpr size_t STATIC_PARTICIPANT_MAX_REMOTE_PARTICIPANTS = 5;
constexpr size_t STATIC_PARTICIPANT_MAX_LOCAL_READERS = 20;
constexpr size_t STATIC_PARTICIPANT_MAX_REMOTE_READERS = 20;
constexpr size_t STATIC_PARTICIPANT_MAX_LOCAL_WRITERS = 20;
constexpr size_t STATIC_PARTICIPANT_MAX_REMOTE_WRITERS = 20;
constexpr size_t STATIC_PARTICIPANT_MAX_LOCAL_TOPICS = 20;

StaticDomainParticipant<
    STATIC_PARTICIPANT_MAX_REMOTE_PARTICIPANTS,
    STATIC_PARTICIPANT_MAX_LOCAL_READERS,
    STATIC_PARTICIPANT_MAX_REMOTE_READERS,
    STATIC_PARTICIPANT_MAX_LOCAL_WRITERS,
    STATIC_PARTICIPANT_MAX_REMOTE_WRITERS,
    STATIC_PARTICIPANT_MAX_LOCAL_TOPICS>
static_participant(
    domain_id,
    participant_qos,
    nullptr,
    NONE_STATUS_MASK,
    get_transport(),
    get_transport());

TypeSupport

Once the Static 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(static_participant, type_name);

Note

For this particular application, the used Type Support class is the one provided as reference in typesupport section

StaticTopic

Once the TypeSupport has been created, the next step is to create Static DDS Topic. Creating the DDS Topic involves specifying the name of the topic and the name of the type that it uses.

// Create a Topic
TopicQos topic_qos{};

constexpr size_t STATIC_TOPIC_MAX_SAMPLES_IN_TOPIC = 100;
constexpr size_t STATIC_TOPIC_MAX_SERIALIZED_SIZE = 1000;
constexpr size_t STATIC_TOPIC_MAX_LOCAL_READERS = 5;

StaticTopic<
    STATIC_TOPIC_MAX_SAMPLES_IN_TOPIC,
    STATIC_TOPIC_MAX_SERIALIZED_SIZE,
    STATIC_TOPIC_MAX_LOCAL_READERS> static_topic(
    static_participant,
    topic_name,
    type_name,
    topic_qos,
    nullptr,
    NONE_STATUS_MASK);

Note

On this example there is not listener attached to StaticTopic.

Publication entities

After the StaticTopic has been created, next step is to create static versions of DDS Publisher, and DataWriter, as demonstrated below:

// Create a Publisher
PublisherQos publisher_qos{};

StaticPublisher static_publisher(
    static_participant,
    publisher_qos,
    nullptr,
    NONE_STATUS_MASK);

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

constexpr size_t DATAWRITER_MAX_REMOTE_READERS = 5;
constexpr size_t DATAWRITER_MAX_INSTANCES = 10;
constexpr size_t DATAWRITER_MAX_SAMPLES_PER_INSTANCE = 100;

StaticDataWriter<
    HelloWorldTypeSupport,
    DATAWRITER_MAX_REMOTE_READERS,
    DATAWRITER_MAX_INSTANCES,
    DATAWRITER_MAX_SAMPLES_PER_INSTANCE>
static_datawriter(
    static_publisher,
    static_topic,
    datawriter_qos,
    nullptr,
    NONE_STATUS_MASK);

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 static version of Subscriber and a DDS DataReader can be created using the previously created DDS Topic as demonstrated below:

// Create a Subscriber
SubscriberQos subscriber_qos{};

StaticSubscriber static_subscriber(
    static_participant,
    subscriber_qos,
    nullptr,
    NONE_STATUS_MASK);

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

HelloWorldDataReaderListener listener{};

constexpr size_t DATAREADER_MAX_REMOTE_WRITERS = 5;
constexpr size_t DATAREADER_MAX_LOCAL_WRITERS = 5;
constexpr size_t DATAREADER_MAX_INSTANCES = 10;
constexpr size_t DATAREADER_MAX_SAMPLES_PER_INSTANCE = 400;

StaticDataReader<
    HelloWorldTypeSupport,
    DATAREADER_MAX_REMOTE_WRITERS,
    DATAREADER_MAX_LOCAL_WRITERS,
    DATAREADER_MAX_INSTANCES,
    DATAREADER_MAX_SAMPLES_PER_INSTANCE>
static_datareader(
    static_subscriber,
    static_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
static_topic.enable();
static_publisher.enable();
static_datawriter.enable();
static_subscriber.enable();
static_datareader.enable();
static_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(static_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 as shown below:

// Prepare the executor
memory::container::StaticReferenceList<execution::ISpinnable, 10> spinnable_list {};
spinnable_list.add(static_participant);
spinnable_list.add(static_publisher);
spinnable_list.add(static_datawriter);
spinnable_list.add(static_subscriber);
spinnable_list.add(static_datareader);

execution::BasicExecutor executor{get_transport(),
                                  static_participant,
                                  spinnable_list};
get_transport().set_message_observer(executor);

// Spin the entities
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_STATIC_API_FOLDER on the example) and generate the build files with CMake.

cmake "$GETTING_STARTED_STATIC_API_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_static

If the application is successfully connected to the network and running, the console output should show messages similar to the following:

[DW: 0] Message: HelloWorld with index: 0
[DW: 0] Message: HelloWorld with index: 1
[DW: 0] Message: HelloWorld with index: 2
[DW: 0] Message: HelloWorld with index: 3
[DW: 0] Message: HelloWorld with index: 4
[DW: 0] Message: HelloWorld with index: 5
[DW: 0] Message: HelloWorld with index: 6
[DW: 0] Message: HelloWorld with index: 7
[DW: 0] Message: HelloWorld with index: 8
[DW: 0] Message: HelloWorld with index: 9
[DW: 0] Message: HelloWorld with index: 10

The program will continue to publish messages every second until it is manually stopped.

In other terminal the example referred in Getting Started can be ran. The communication between both processes shall be established and the following output shall be shown:

[DW: 0] Message: HelloWorld with index: 0
[DW: 0] Message: HelloWorld with index: 1
[DW: 0] Message: HelloWorld with index: 2
[DW: 16777216] Message: HelloWorld with index: 0
[DW: 0] Message: HelloWorld with index: 3
[DW: 16777216] Message: HelloWorld with index: 1
[DW: 0] Message: HelloWorld with index: 4
[DW: 16777216] Message: HelloWorld with index: 2
[DW: 0] Message: HelloWorld with index: 5
[DW: 16777216] Message: HelloWorld with index: 3
[DW: 0] Message: HelloWorld with index: 6
[DW: 16777216] Message: HelloWorld with index: 4
[DW: 0] Message: HelloWorld with index: 7
[DW: 16777216] Message: HelloWorld with index: 5