3. Data Types¶
Prerequisites |
|
Time to complete |
30 minutes |
Concepts covered in this module |
|
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](_images/hello_msg_datatype.png)
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](_images/reuse_datatype.png)
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. 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.
Type |
Parameters |
Description |
---|---|---|
|
Boolean value (true or false) |
|
|
8-bit quantity |
|
|
64-bit double precision floating-point number |
|
|
Enumerated value |
|
|
32-bit single precision floating-point number |
|
|
16-bit numeric type |
|
|
32-bit numeric type |
|
|
64-bit numeric type |
|
|
8-bit type, used for storing binary data that should not be serialized/deserialized by the middleware. Usually a sequence. |
|
|
|
Sequence of type, with a maximum of max length elements. Max length is optional. |
|
|
String that may contain data from length 0 to max length. Max length is optional. |
|
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.)
In
3_streaming_data
, openchocolate_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; };
In the directory called
c++98
, open thechocolate_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
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 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.
![]()
Figure 3.3 DataWriter sending temperature samples¶
To make your application illustrate streaming data:
Change
send_period
inchocolate_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 the3_streaming_data/<language>
directory) if you need more information on how to open and modify the file.Open the
USER_QOS_PROFILES.xml
file, in the same directory that contains thechocolate_factory_publisher.cxx
andchocolate_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 thebase_name
attribute fromBuiltinQosLib::Generic.StrictReliable
toBuiltinQosLib::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 attributeis_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 delivers your data from being reliable to “best effort.” We will cover QoS in more depth in Basic QoS.
Build the example.
See Compile Your Changes. (Review the generated
README_<architecture>.txt
file in the3_streaming_data/<language>
directory if you need more help.)
3.3.3. Run the Applications¶
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).
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 withinobjs/<architecture>
. You should run from the3_streaming_data/<language>
directory because the examples use Quality of Service (QoS) information from the fileUSER_QOS_PROFILES.xml
in that directory. We’ll talk more about QoS in a later module.Open another command prompt window, run
rtisetenv_<architecture>
if you haven’t already in that window, and from within the3_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](_images/streaming_data.png)
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](_images/dp_structure.png)
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 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 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 Core Libraries User’s Manual and Subscribers, in the RTI Connext 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.
Stop running both of the applications from the previous Hands-On if you haven’t already.
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); }
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);
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](_images/two_writers.png)
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:
Displays the DataWriters and DataReaders in your system, but in a text format rather than the graphical format of Admin Console.
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.
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).
Click on the Utilities tab.
Click on the DDS Spy icon.
![]()
In the dialog box that appears, select “Print samples” and click “Run.”
![]()
rtiddsspy will show you:
That it has discovered two DataWriters
The data being published by the two DataWriters
RTI DDS Spy built with DDS version: 7.0.0
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
rtiddsspy is listening for data, press CTRL+C to stop it.
22:20:59 New writer from 10.30.1.134 : topic="ChocolateTemperature" type="Temperature" name="ChocolateTemperatureDataWriter"
22:20:59 New writer from 10.30.1.134 : topic="ChocolateLotState" type="ChocolateLotState" name="ChocolateTemperatureDataWriter"
22:21:37 New data from 10.30.1.134 : topic="ChocolateLotState" type="ChocolateLotState"
lot_id: 46
lot_status: WAITING
22:21:37 New data from 10.30.1.134 : topic="ChocolateTemperature" type="Temperature"
sensor_id: "MySensor1"
degrees: 30
22:21:38 New data from 10.30.1.134 : topic="ChocolateLotState" type="ChocolateLotState"
lot_id: 47
lot_status: WAITING
22:21:38 New data from 10.30.1.134 : topic="ChocolateTemperature" type="Temperature"
sensor_id: "MySensor1"
degrees: 31
22:21:38 New data from 10.30.1.134 : topic="ChocolateLotState" type="ChocolateLotState"
lot_id: 48
lot_status: WAITING
The first two lines indicate that rtiddsspy has discovered the two DataWriters in your chocolate_factory_publisher application. The subsequent lines 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.
After you stop (CTRL+C) rtiddsspy, you will see the number of DataWriters and DataReaders discovered, and samples received:
---- Statistics ----
Discovered 10 DataWriters and 7 DataReaders
Received samples (Data, Dispose, NoWriters):
366, 0, 0 (Topic="ChocolateTemperature" Type="Temperature")
366, 0, 0 (Topic="ChocolateLotState" Type="ChocolateLotState")
There are additional values that rtiddsspy can display if you use keys and instances (which we haven’t talked about yet). For an overview of all the output of rtiddsspy, see the RTI DDS Spy documentation.
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.