3. Data Types

Prerequisites

  • Publish/Subscribe, including:

    • DataWriters, DataReaders, and Topics

    • Using the code generator

Time to complete

30 minutes

Concepts covered in this module

  • Typed data

  • Interface Definition language (IDL)

  • Introduction to data flows

  • Streaming data

The first step in creating a DDS application is to define the interface between applications. In DDS, the interface is the data itself rather than the bits and bytes that make up a protocol. In DDS, the Topic written by a DataWriter and read by a DataReader is associated with one data type. For example, in Publish/Subscribe, the data type was named HelloWorld and contained a single string. The “HelloWorld Topic” was associated with the HelloWorld data type.

HelloWorld Data Type

Figure 3.1 In Publish/Subscribe, you started two applications that published the “HelloWorld Topic” and two that subscribed to the “HelloWorld Topic.” The “HelloWorld Topic” uses the HelloWorld data type.

The same data type can be reused across multiple Topics. For example, a data type named Temperature might be associated with the Topics “ChocolateTemperature” and “FactoryTemperature.” Although “ChocolateTemperature” and “FactoryTemperature” measure two different things, they are both temperature data types. Thus, the data type can be reused for both of these Topics.

Multiple Topics Data Type

Figure 3.2 Multiple Topics can share the same data type.

In Publish/Subscribe, you opened an IDL (.idl) file that contained the HelloWorld data type. IDL is the “Interface Definition Language,” defined by the Object Management Group. It allows you to define data types in a way that is not specific to the language your applications are written in, enabling applications that are written in C, C++, Java, etc., to communicate.

3.1. Common IDL Types

A few common IDL types are listed in the table below to help you get started. Many more are supported by Connext DDS. More information on working with IDL data types can be found in Creating User Data Types with IDL, in the RTI Connext DDS Core Libraries User’s Manual.

Table 3.1 Some IDL Types

Type

Parameters

Description

boolean

Boolean value (true or false)

char

8-bit quantity

double

64-bit double precision floating-point number

enum

Enumerated value

float

32-bit single precision floating-point number

int16

16-bit numeric type

int32

32-bit numeric type

int64

64-bit numeric type

octet

8-bit type, used for storing binary data that should not be serialized/deserialized by the middleware. Usually a sequence.

sequence

<type, max length>

Sequence of type, with a maximum of max length elements. Max length is optional.

string

<max length>

String that may contain data from length 0 to max length. Max length is optional.

struct

Structure containing other types

3.2. Introduction to Data Flows

To design your data types, decide how many you need and what each one will be used for. Consider the relationships of all your application data. Some questions to consider:

  • Is this data produced and consumed in the same places?

  • Can this data logically be described by the same Topic?

  • Does this data have the same data flow characteristics?

To answer the third question, it’s important to discuss what data flow characteristics are. Some of these characteristics include:

  • How frequently data is sent

  • Whether data is sent periodically or asynchronously

  • Whether it is okay to miss an update

There are additional data flow characteristics that we will cover later when we talk about Quality of Service (in Basic QoS). An example of a common data flow pattern is “Streaming Sensor Data.”

Tip

Streaming sensor data:

  • Usually sent rapidly

  • Usually sent periodically

  • When data is lost over the network, it is more important to get the next update rather than wait for the lost update

Other data flows include “State Data” and “Event and Alarm Data.”

All these data flows will be discussed in more detail in Basic QoS.

3.3. Hands-On 1: Streaming Data

This Hands-On will use an example similar to the “Hello World” example in Publish/Subscribe, but with a few modifications. (Instructions in the following exercises are a little less detailed because we assume you have already performed the exercises in Publish/Subscribe.)

3.3.1. Run Code Generator

The code for this example is in the directory 3_streaming_data. (See Get Example Files.)

  1. In 3_streaming_data, open chocolate_factory.idl to see the definition for our temperature type. (There is also a ChocolateLotState type that we will use later).

    In the IDL file, we’ve changed the data type from a message string to a Temperature that includes both a sensor ID and degrees:

    // Temperature data type
    struct Temperature {
        // ID of the sensor sending the temperature
        string<256> sensor_id;
    
        // Degrees in Celsius
        int32 degrees;
    };
    
  2. In the directory called c++98, open the chocolate_factory_publisher.cxx file to see that we’ve changed the Topic to “ChocolateTemperature”:

    // A Topic has a name and a datatype. Create a Topic called
    // "ChocolateTemperature" with your registered data type
    DDSTopic *topic = participant->create_topic(
            "ChocolateTemperature",
            type_name,
            DDS_TOPIC_QOS_DEFAULT,
            NULL /* listener */,
            DDS_STATUS_MASK_NONE);
    if (topic == NULL) {
        return shutdown(participant, "create_topic error", EXIT_FAILURE);
    }
    

    We have modified the application so that you can specify a “sensor ID” at the command-line when running your app, by passing -i <some sensor name>. In that file, we’ve also modified the data being sent so that it includes both that sensor ID and a temperature ranging between 30 and 32 degrees:

    // Modify the data to be written here
    snprintf(sample->sensor_id, 255, "%s", sensor_id);
    sample->degrees = rand() % 3 + 30;  // Random number between 30 and 32
    
  3. Run Code Generator for this new example, but specify the chocolate_factory.idl file instead.

    First, run rtisetenv_<architecture>, as described in Set Up Environment Variables (rtisetenv), if you haven’t already.

    Then run the code generator on chocolate_factory.idl:

    $ cd 3_streaming_data
    $ rtiddsgen -language c++ -platform <architecture> -d c++98 -create makefiles -create typefiles chocolate_factory.idl
    
    $ cd 3_streaming_data
    $ rtiddsgen -language c++ -platform <architecture> -d c++98 -create makefiles -create typefiles chocolate_factory.idl
    
    > cd 3_streaming_data
    > rtiddsgen -language c++ -platform <architecture> -create makefiles -create typefiles -ppDisable -d c++98 chocolate_factory.idl
    

    -ppDisable disables the preprocessor. It is necessary for running rtiddsgen on a Windows system if the preprocessor is not in your path. You can only use -ppDisable if your IDL is simple, as it is here—otherwise you must add the preprocessor to your path. See Command-Line Arguments for rtiddsgen, in the RTI Connext DDS Code Generator User’s Manual if you want more information.

3.3.2. Modify for Streaming Data

In this step, you will modify your applications to support one of the common design patterns that most applications need: Streaming Data. This pattern is characterized by:

  • Data that is sent frequently and periodically.

  • No need for reliability: if a sample is lost on the network, it is better to drop it than possibly delay the next one.

This pattern is usually seen with sensor data.

Definition

A sample is a single data update sent or received over DDS. For example: temperature = 32.

Temperature Samples

Figure 3.3 DataWriter sending temperature samples

To make your application illustrate streaming data:

  1. Change send_period in chocolate_factory_publisher.cxx from 4 seconds to 100 milliseconds (100000000 nanoseconds), as shown below:

    // Exercise #1.1: Change this to sleep 100 ms in between writing temperatures
    DDS_Duration_t send_period = { 0, 100000000 };
    NDDSUtility::sleep(send_period);
    

    Note

    See the README_<architecture>.txt file generated with the code (in the 3_streaming_data/<language> directory) if you need more information on how to open and modify the file.

  2. Open the USER_QOS_PROFILES.xml file, in the same directory that contains the chocolate_factory_publisher.cxx and chocolate_factory_subscriber.cxx files. We will cover Quality of Service (QoS) in greater depth in a later module, but for now we will use this file to change our DataWriter and DataReader to use QoS appropriate for streaming data. Do this by changing the base_name attribute from BuiltinQosLib::Generic.StrictReliable to BuiltinQosLib::Pattern.Streaming:

    <!--
      QoS profile used to configure reliable communication between the
      DataWriter and DataReader created in the example code.
    
      base_name:
      Communication is reliable because this profile inherits from
      the built-in profile "BuiltinQosLib::Generic.StrictReliable"
    
      is_default_qos:
      These QoS profiles will be used as the default, as long as this
      file is in the working directory when running the example.
    -->
    <!-- Exercise #1.2: Use Streaming profile -->
    <qos_profile name="ChocolateTemperatureProfile"
               base_name="BuiltinQosLib::Pattern.Streaming"
               is_default_qos="true">
    

    Tip

    This XML file is loaded from your working directory when you run your applications—this is why we specify that you run your applications from the 3_streaming_data/<language> directory. Notice that the profile contains the attribute is_default_qos—this means that this profile will be used by default by the DataWriter and DataReader, as long as it is in your working directory. Later when we talk about QoS, we will show you how to specify a particular QoS profile instead of loading the default.

    This modification to the QoS XML file will change the way Connext DDS delivers your data from being reliable to “best effort.” We will cover QoS in more depth in Basic QoS.

  3. Build the example.

    See Compile Your Changes. (Review the generated README_<architecture>.txt file in the 3_streaming_data/<language> directory if you need more help.)

3.3.3. Run the Applications

  1. Make sure you have run rtisetenv_<architecture> in any new command prompt window, to avoid issues with paths and licensing. See Set Up Environment Variables (rtisetenv).

  2. From within the 3_streaming_data/<language> directory, enter the following full path, optionally specifying your own sensor ID to send with the data, such as “MySensor1”:

    $ objs/<architecture>/chocolate_factory_publisher -i <some string>
    
    $ objs/<architecture>/chocolate_factory_publisher -i <some string>
    
    > objs\<architecture>\chocolate_factory_publisher.exe -i <some string>
    

    Note

    You must be in the 3_streaming_data/<language> directory and enter the full path above. Do not run the publisher or subscriber application from within objs/<architecture>. You should run from the 3_streaming_data/<language> directory because the examples use Quality of Service (QoS) information from the file USER_QOS_PROFILES.xml in that directory. We’ll talk more about QoS in a later module.

  3. Open another command prompt window, run rtisetenv_<architecture> if you haven’t already in that window, and from within the 3_streaming_data/<language> directory, enter the following full path:

    $ objs/<architecture>/chocolate_factory_subscriber
    
    $ objs/<architecture>/chocolate_factory_subscriber
    
    > objs\<architecture>\chocolate_factory_subscriber.exe
    

    After modifying the publishing and subscribing applications as described above, compiling, and running both applications from the 3_streaming_data/<language> directory where you generated code, you should see data rapidly arriving:

    sensor_id: "MySensor1"
    degrees: 31
    
    sensor_id: "MySensor1"
    degrees: 32
    
    sensor_id: "MySensor1"
    degrees: 32
    
    sensor_id: "MySensor1"
    degrees: 31
    
Streaming Data

Figure 3.4 In this exercise, a DataWriter of the “ChocolateTemperature” Topic communicates with a DataReader of the “ChocolateTemperature” Topic. In the next Hands-On, you will add a “ChocolateLotState” Topic.

Congratulations! You now have streaming Temperature data.

3.4. Publishers, Subscribers, and DomainParticipants

Before we go any farther, it’s important that we define a few more objects that you will see in your DDS applications. You may have noticed some of these objects already in the code, and you’ll be using one of them in the next Hands-On. These objects are: Publishers, Subscribers, and DomainParticipants. Most of the time in these hands-on exercises we will ignore these, and focus on DataWriters, DataReaders, and Topics. But it’s important to know that these other objects exist in every application.

DomainParticipants, Publishers, and Subscribers

Figure 3.5 DomainParticipants create and manage Publishers and Subscribers. Publishers create and manage DataWriters. Subscribers create and manage DataReaders. DataWriters and DataReaders send and receive your data.

Definition

  • A DomainParticipant object in Connext DDS is used to create and manage one or more Publishers and Subscribers. The DomainParticipant is a container for most other objects, and is responsible for the discovery process. In most applications, you will have only one DomainParticipant, even if you have many DataWriters and DataReaders.

  • A Publisher object in Connext DDS is used to create and manage one or more DataWriters. A Subscriber object is used to create and manage one or more DataReaders. For more information, see Publishers, in the RTI Connext DDS Core Libraries User’s Manual and Subscribers, in the RTI Connext DDS Core Libraries User’s Manual.

We will be using the Publisher object in your temperature_publisher application to create a new DataWriter in the next Hands-On section.

We will see DomainParticipants again when we talk about QoS, and then when we talk about discovery and domains. Since they are used to create nearly every other DDS object in your system, they’re one of the first objects you create when creating a DDS application. DomainParticipants also create Topics, which get used by your DataWriters and DataReaders. You’ll see that when you add a second Topic in the next Hands-On.

Note

It’s a common beginner’s mistake to create one DomainParticipant per DataWriter or DataReader. As you can see, it’s not necessary. You typically create one DomainParticipant per application. It’s also a bad idea to use more than you need, because DomainParticipants use significant resources such as threads, and they use network bandwidth for discovery. We’ll talk more about DomainParticipants in a later module on discovery.

3.5. Hands-On 2: Add a Second DataWriter

Now that you have created your first streaming data, we will add another DataWriter to the chocolate_factory_publisher application. This will give you an idea how to add a new DataWriter or DataReader, which will be useful because the code in the next module will have more-complex applications with multiple DataReaders and DataWriters.

3.5.1. Add the New DataWriter

Every DataWriter needs to write on a Topic, and this new DataWriter will write using a different Topic and data type than the temperature DataWriter. This DataWriter will write the Topic “ChocolateLotState” with the data type ChocolateLotState that is defined in the IDL file. We will use this new “ChocolateLotState” Topic again in the next module.

  1. Stop running both of the applications from the previous Hands-On if you haven’t already.

  2. Add a new data type, Topic, and DataWriter.

    To support creating this new DataWriter, you must:

    • Register your data type.

    • Create your Topic with the registered type name.

    • Create your DataWriter with that Topic.

    • Use narrow() to cast from the generic DataWriter to the type-specific DataWriter that you will use to write.

    In this code, you will also allocate a ChocolateLotState sample that you will use to write.

    Inside of chocolate_factory_publisher.cxx you should see this comment:

    // Exercise #2.1: Add new Topic, data type, and DataWriter
    

    Right after the comment, add this code to perform all the steps described above:

    // Register the datatype to use when creating the Topic
    const char *lot_state_type_name =
            ChocolateLotStateTypeSupport::get_type_name();
    retcode = ChocolateLotStateTypeSupport::register_type(
            participant,
            lot_state_type_name);
    if (retcode != DDS_RETCODE_OK) {
        return shutdown(participant, "register_type error", EXIT_FAILURE);
    }
    
    // A Topic has a name and a datatype. Create a Topic called
    // "ChocolateLotState" with your registered data type
    DDSTopic *lot_state_topic = participant->create_topic(
            "ChocolateLotState",
            lot_state_type_name,
            DDS_TOPIC_QOS_DEFAULT,
            NULL /* listener */,
            DDS_STATUS_MASK_NONE);
    if (lot_state_topic == NULL) {
        return shutdown(participant, "create_topic error", EXIT_FAILURE);
    }
    
    // This DataWriter writes data on Topic "ChocolateLotState"
    // DataWriter QoS is configured in USER_QOS_PROFILES.xml
    DDSDataWriter *generic_state_writer = publisher->create_datawriter(
            lot_state_topic,
            DDS_DATAWRITER_QOS_DEFAULT,
            NULL /* listener */,
            DDS_STATUS_MASK_NONE);
    if (generic_state_writer == NULL) {
        return shutdown(participant, "create_datawriter error", EXIT_FAILURE);
    }
    
    // A narrow is a cast from a generic DataWriter to one that is specific
    // to your type. Use the type specific DataWriter to write()
    ChocolateLotStateDataWriter *lot_state_writer =
            ChocolateLotStateDataWriter::narrow(generic_state_writer);
    if (lot_state_writer == NULL) {
        return shutdown(participant, "DataWriter narrow error", EXIT_FAILURE);
    }
    
    // Create data sample for writing
    ChocolateLotState *lot_state_sample =
            ChocolateLotStateTypeSupport::create_data();
    if (lot_state_sample == NULL) {
        return shutdown(
                participant,
                "ChocolateLotStateTypeSupport::create_data error",
                EXIT_FAILURE);
    }
    
  3. Finally, set some values in the ChocolateLotState data, and write the sample. Look for this comment:

    // Exercise #2.2 Write data with new ChocolateLotState DataWriter
    

    Add the following code after the comment:

    lot_state_sample->lot_id = samples_written % 100;
    lot_state_sample->lot_status = WAITING;
    lot_state_writer->write(*lot_state_sample, DDS_HANDLE_NIL);
    
  4. Now, compile and run the chocolate_factory_publisher application from the 3_streaming_data/<language> directory where you generated code. You do not need to run the chocolate_factory_subscriber application, because next we will show you another way to visualize your data.

Two DataWriters

Figure 3.6 You added a second DataWriter that writes on the “ChocolateLotState” Topic.

Congratulations! You have added a second DataWriter that writes on a new Topic with a new data type! In the next module, you will continue adding to these applications to make them more complete.

3.5.2. Visualize the Data in rtiddsspy

The rtiddsspy utility is a quick way to visualize data when you just need a simple text view. This utility does two things:

  1. Displays the DataWriters and DataReaders in your system, but in a text format rather than the graphical format of Admin Console.

  2. Automatically creates DataReaders for any Topic being written on the network and prints out messages when its DataReaders receive data.

rtiddsspy does both of these without requiring very much configuration, making it a convenient tool for debugging when your applications are not communicating, or when you need to quickly see your data. Unlike Admin Console, rtiddsspy can be run directly on an embedded machine, which makes it useful if you need to debug applications that are not on the same network as a Windows, Linux, or macOS machine.

  1. To open rtiddsspy, start by opening the Launcher tool. (rtiddsspy can also be run from the command-line, but Launcher provides a useful front-end).

  2. Click on the Utilities tab.

  3. Click on the DDS Spy icon.

    DDS Spy Icon
  4. In the dialog box that appears, select “Print samples” and click “Run.”

    Run DDS Spy

rtiddsspy will show you:

  • That it has discovered two DataWriters

  • The data being published by the two DataWriters

Copyright 2012 Real-Time Innovations, Inc.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
rtiddsspy is listening for data, press CTRL+C to stop it.

source_timestamp   Info  Src HostId  topic               type
-----------------  ----  ----------  ------------------  ------------------
1591810321.027530  W +N  0A00000C    ChocolateTemperatu  Temperature
                                ...  re
1591810321.028560  W +N  0A00000C    ChocolateLotState   ChocolateLotState
1591815886.745398  d +N  0A00000C    ChocolateLotState   ChocolateLotState
lot_id: 1
lot_status: WAITING

1591815886.745627  d +N  0A00000C    ChocolateTemperatu  Temperature
                                ...  re
sensor_id: "MySensor1"
degrees: 31
1591815890.749505  d +M  0A00000C    ChocolateLotState   ChocolateLotState
lot_id: 2
lot_status: WAITING

1591815890.749652  d +M  0A00000C    ChocolateTemperatu  Temperature
                                ...  re
sensor_id: "MySensor1"
degrees: 32

The first two lines where rtiddsspy’s Info says “W +N” indicate that rtiddsspy has discovered the two DataWriters in your chocolate_factory_publisher application. (You should see “W +N” for each new DataWriter discovered.) The lines where rtiddsspy’s info says “d +N” and “d +M” indicate that rtiddsspy is receiving data. Since you selected the “Print Samples” option in Launcher, you can also see the contents of the ChocolateLotState data and the Temperature data your DataWriters are writing.

Here is an overview of the “Info” output that rtiddsspy can display for your current data types. There are some additional values that it can display if you use keys and instances (which we haven’t talked about yet). If you would like an overview of all the output of rtiddsspy, please see the rtiddsspy section in the API docs.

Table 3.2 Understanding rtiddsspy’s Info Column

Info

Meaning

W +N

Discovered a new (+N) DataWriter (W)

W ?M or W -M

Discovered that a DataWriter has lost connection or been shutdown (this uses some of the mechanisms of keys and instances discussed in the next module).

R +N

Discovered a new (+N) DataReader (R)

R ?M or R -M

Discovered that a DataReader has lost connection or been shutdown (this uses some of the mechanisms of keys and instances we will discuss in the next module).

d +N

First time rtiddsspy receives a sample of this Topic

d +M

rtiddsspy received a sample of this Topic

3.6. Next Steps

Next, we will look a little farther into data design with Keys and Instances, then dive into more detail about Quality of Service (QoS) in Basic QoS.