.. include:: vars.rst .. _section-gsg_intro_datatypes: Data Types ********** .. list-table:: :name: TableDatatypesPrerequisites :widths: 20 80 :header-rows: 0 * - Prerequisites - .. only:: cpp98 or cpp11 * :ref:`section-gsg_intro_cpp11`, including: * |DWs|, |DRs|, and *Topics* * Using the code generator .. only:: csharp * :ref:`section-gsg_intro_cpp11`, including: * |DWs|, |DRs|, and *Topics* * Using the code generator * Repository cloned from GitHub `here `_ * - 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 |DW| and read by a |DR| is associated with one data type. For example, in :ref:`section-gsg_intro_cpp11`, the data type was named **HelloWorld** and contained a single string. The "HelloWorld Topic" was associated with the **HelloWorld** data type. .. figure:: static/hello_msg_datatype.png :scale: 50 % :alt: HelloWorld Data Type :name: FigureHelloDatatype :align: center In :ref:`section-gsg_intro_cpp11`, 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*. .. figure:: static/reuse_datatype.png :scale: 50 % :alt: Multiple Topics Data Type :name: FigureMultipleDatatype :align: center Multiple Topics can share the same data type. In :ref:`section-gsg_intro_cpp11`, 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. 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 :link_connext_idl_users_man:`Creating User Data Types with IDL, in the RTI Connext DDS Core Libraries User's Manual <>`. .. tabularcolumns:: |\X{25}{120}|\X{35}{120}|\X{60}{120}| .. list-table:: Some IDL Types :name: TableSomeIDLTypes :widths: 20 30 70 :header-rows: 1 * - 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`` - ```` - Sequence of type, with a maximum of max length elements. Max length is optional. * - ``string`` - ```` - String that may contain data from length 0 to max length. Max length is optional. * - ``struct`` - - Structure containing other types 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 :ref:`section-gsg_intro_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 :ref:`section-gsg_intro_qos`. Hands-On 1: Streaming Data ========================== This Hands-On will use an example similar to the "Hello World" example in :ref:`section-gsg_intro_cpp11`, 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 :ref:`section-gsg_intro_cpp11`.) .. _section-gsg_intro_data_runcodegen: Run Code Generator ------------------ The code for this example is in the directory ``3_streaming_data``. (See :ref:`section-gsg_clone`.) .. only:: cpp11 #. 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: .. code-block:: omg-idl // 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++11``, open the ``chocolate_factory_publisher.cxx`` file to see that we've changed the *Topic* to "ChocolateTemperature": .. code-block:: C++ // A Topic has a name and a datatype. Create a Topic named // "ChocolateTemperature" with type Temperature dds::topic::Topic topic(participant, "ChocolateTemperature"); We have modified the application so that you can specify a "sensor ID" at the command-line when running your application, by passing ``-i ``. 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: .. code-block:: C++ // Modify the data to be written here sample.sensor_id(sensor_id); sample.degrees(rand() % 3 + 30); // Random number between 30 and 32 #. :ref:`section-gsg_intro_runcodegen` for this new example, but specify the ``chocolate_factory.idl`` file instead. First, run ``rtisetenv_``, as described in :ref:`section-gsg_rtisetenv`, if you haven't already. Then run the code generator on ``chocolate_factory.idl``: .. tabs:: .. group-tab:: Linux .. code-block:: console $ cd 3_streaming_data $ rtiddsgen -language c++11 -platform -d c++11 -create makefiles -create typefiles chocolate_factory.idl .. group-tab:: macOS .. code-block:: console $ cd 3_streaming_data $ rtiddsgen -language c++11 -platform -d c++11 -create makefiles -create typefiles chocolate_factory.idl .. group-tab:: Windows .. code-block:: doscon > cd 3_streaming_data > rtiddsgen -language c++11 -platform -create makefiles -create typefiles -ppDisable -d c++11 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 :link_connext_cmdline_codegen_man:`Command-Line Arguments for rtiddsgen, in the RTI Connext DDS Code Generator User's Manual <>` if you want more information. .. only:: cpp98 #. 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: .. code-block:: omg-idl // 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 the ``chocolate_factory_publisher.cxx`` file to see that we've changed the *Topic* to "ChocolateTemperature": .. code-block:: C++ // 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 ``. 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: .. code-block:: C++ // 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 #. :ref:`section-gsg_intro_runcodegen` for this new example, but specify the ``chocolate_factory.idl`` file instead. First, run ``rtisetenv_``, as described in :ref:`section-gsg_rtisetenv`, if you haven't already. Then run the code generator on ``chocolate_factory.idl``: .. tabs:: .. group-tab:: Linux .. code-block:: bash $ cd 3_streaming_data $ rtiddsgen -language c++ -platform -d c++98 -create makefiles -create typefiles chocolate_factory.idl .. group-tab:: macOS .. code-block:: bash $ cd 3_streaming_data $ rtiddsgen -language c++ -platform -d c++98 -create makefiles -create typefiles chocolate_factory.idl .. group-tab:: Windows .. code-block:: bat > cd 3_streaming_data > rtiddsgen -language c++ -platform -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 :link_connext_cmdline_codegen_man:`Command-Line Arguments for rtiddsgen, in the RTI Connext DDS Code Generator User's Manual <>` if you want more information. .. only:: csharp #. 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: .. code-block:: omg-idl // Temperature data type struct Temperature { // ID of the sensor sending the temperature string<256> sensor_id; // Degrees in Celsius int32 degrees; }; #. In the directory ``csharp/ChocolateFactoryPublisher``, open the ``ChocolateFactoryPublisher.cs`` file to see that we've changed the *Topic* to "ChocolateTemperature": .. code-block:: C# // A Topic has a name and a datatype. Create a Topic named // "ChocolateTemperature" with type Temperature Topic topic = participant.CreateTopic("ChocolateTemperature"); We have modified the application so that you can specify a "sensor ID" at the command-line when running your application, by passing ``-i ``. 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: .. code-block:: C# // Modify the data to be written here sample.sensor_id = sensorId; sample.degrees = rand.Next(30, 33); // Random number between 30 and 32 #. :ref:`section-gsg_intro_runcodegen` to generate C# code for the types defined in ``chocolate_factory.idl``. In this exercise we already have application code, so we will only generate the type files and project files. .. code-block:: console $ cd 3_streaming_data $ rtiddsgen -language c# -platform net5 -create typefiles -create makefiles -d csharp chocolate_factory.idl .. _section-gsg_types_modify_stream: 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. .. admonition:: Definition :class: definition-alert A **sample** is a single data update sent or received over DDS. For example: temperature = 32. .. figure:: static/intro_samples.png :scale: 50 % :alt: Temperature Samples :name: FigureSamples :align: center DataWriter sending temperature samples To make your application illustrate streaming data: .. only:: cpp11 #. Change ``chocolate_factory_publisher.cxx`` from 4 seconds to 100 milliseconds, as shown below: .. code-block:: C++ :emphasize-lines: 4 writer.write(sample); // Exercise #1.1: Change this to sleep 100 ms in between writing temperatures rti::util::sleep(std::chrono::milliseconds(100)); (If you're using Windows and need help on modifying this file in your IDE, see the ``README_.txt`` file generated with the code, in the ``3_streaming_data/`` directory.) |br| |br| #. 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 |DW| and |DR| to use QoS appropriate for streaming data. Do this by changing the ``base_name`` attribute from ``BuiltinQosLib::Generic.StrictReliable`` to ``BuiltinQosLib::Pattern.Streaming``: .. code-block:: xml :emphasize-lines: 15 .. 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/`` directory. Notice that the profile contains the attribute ``is_default_qos``—this means that this profile will be used by default by the |DW| and |DR|, 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 :ref:`section-gsg_intro_qos`. |br| |br| #. Build the example. If you need help compiling, review the generated ``README_.txt`` file in the ``3_streaming_data/`` directory. .. only:: cpp98 #. Change ``send_period`` in ``chocolate_factory_publisher.cxx`` from 4 seconds to 100 milliseconds (100000000 nanoseconds), as shown below: .. code-block:: C++ :emphasize-lines: 2 // 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_.txt`` file generated with the code (in the ``3_streaming_data/`` 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 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 |DW| and |DR| to use QoS appropriate for streaming data. Do this by changing the ``base_name`` attribute from ``BuiltinQosLib::Generic.StrictReliable`` to ``BuiltinQosLib::Pattern.Streaming``: .. code-block:: xml :emphasize-lines: 15 .. 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/`` directory. Notice that the profile contains the attribute ``is_default_qos``—this means that this profile will be used by default by the |DW| and |DR|, 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 :ref:`section-gsg_intro_qos`. |br| |br| #. Build the example. See :ref:`section-gsg_intro_compile`. (Review the generated ``README_.txt`` file in the ``3_streaming_data/`` directory if you need more help.) .. only:: csharp #. Change ``ChocolateFactoryPublisher.cs`` from 4 seconds to 100 milliseconds, as shown below: .. code-block:: C# :emphasize-lines: 4 writer.Write(sample); // Exercise #1.1: Change this to sleep 100 ms in between writing temperatures Threading.Thread.Sleep(100); (If you're using Windows and need help on modifying this file in your IDE, see the ``README_.txt`` file generated with the code, in the ``3_streaming_data/`` directory.) |br| |br| #. Open the ``USER_QOS_PROFILES.xml`` file, in the same directory that contains the ``ChocolateFactoryPublisher.cs`` and ``ChocolateFactorySubscriber.cs`` 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 |DW| and |DR| to use QoS appropriate for streaming data. Do this by changing the ``base_name`` attribute from ``BuiltinQosLib::Generic.StrictReliable`` to ``BuiltinQosLib::Pattern.Streaming``: .. code-block:: xml :emphasize-lines: 15 .. 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/`` directory. Notice that the profile contains the attribute ``is_default_qos``—this means that this profile will be used by default by the |DW| and |DR|, 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 a later module. Run the Applications -------------------- .. only:: cpp98 or cpp11 #. Make sure you have run ``rtisetenv_`` in any new command prompt window, to avoid issues with paths and licensing. See :ref:`section-gsg_rtisetenv`. |br| |br| #. From within the ``3_streaming_data/`` directory, enter the following full path, optionally specifying your own sensor ID to send with the data, such as "MySensor1": .. tabs:: .. group-tab:: Linux .. code-block:: bash $ objs//chocolate_factory_publisher -i .. group-tab:: macOS .. code-block:: bash $ objs//chocolate_factory_publisher -i .. group-tab:: Windows .. code-block:: bat > objs\\chocolate_factory_publisher.exe -i .. note:: You must be *in* the ``3_streaming_data/`` directory and enter the full path above. Do not run the publisher or subscriber application from within ``objs/``. You should run from the ``3_streaming_data/`` 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. #. Open another command prompt window, run ``rtisetenv_`` if you haven't already in that window, and from within the ``3_streaming_data/`` directory, enter the following full path: .. tabs:: .. group-tab:: Linux .. code-block:: bash $ objs//chocolate_factory_subscriber .. group-tab:: macOS .. code-block:: bash $ objs//chocolate_factory_subscriber .. group-tab:: Windows .. code-block:: bat > objs\\chocolate_factory_subscriber.exe After modifying the publishing and subscribing applications as described above, compiling, and running both applications from the ``3_streaming_data/`` directory where you generated code, you should see data rapidly arriving: .. only:: cpp11 .. code-block:: text ChocolateTemperature subscriber sleeping for 4 sec... [sensor_id: MySensor1, degrees: 32] ChocolateTemperature subscriber sleeping for 4 sec... [sensor_id: MySensor1, degrees: 32] ChocolateTemperature subscriber sleeping for 4 sec... [sensor_id: MySensor1, degrees: 30] ChocolateTemperature subscriber sleeping for 4 sec... [sensor_id: MySensor1, degrees: 31] .. only:: cpp98 .. code-block:: text sensor_id: "MySensor1" degrees: 31 sensor_id: "MySensor1" degrees: 32 sensor_id: "MySensor1" degrees: 32 sensor_id: "MySensor1" degrees: 31 .. only:: csharp #. From within the ``3_streaming_data/csharp`` directory, enter the following command, optionally specifying your own sensor ID to send with the data, such as "MySensor1": .. code-block:: bash $ dotnet run -- --pub --sensor-id .. note:: If you see a build or license error, see :ref:`section-gsg_intro_trouble`. You should run from the ``3_streaming_data/csharp`` 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. Note also that the arguments to the application are specified after ``--``. You can also build and run from Visual Studio and Visual Studio Code as outlined in the previous section (:ref:`section-gsg_intro_compile`). #. Open another command prompt window, and from within the ``3_streaming_data/csharp`` directory, enter the following command: .. code-block:: bash $ dotnet run -- --sub After modifying the publishing and subscribing applications as described above, and running both applications from the ``3_streaming_data/csharp`` directory where you generated code, you should see data rapidly arriving: .. code-block:: text ChocolateTemperature subscriber sleeping for 4 sec... [sensor_id: MySensor1, degrees: 32] ChocolateTemperature subscriber sleeping for 4 sec... [sensor_id: MySensor1, degrees: 32] ChocolateTemperature subscriber sleeping for 4 sec... [sensor_id: MySensor1, degrees: 30] ChocolateTemperature subscriber sleeping for 4 sec... [sensor_id: MySensor1, degrees: 31] .. figure:: static/streaming_data.png :scale: 50 % :alt: Streaming Data :name: FigureStreamingData98 :align: center In this exercise, a |DW| of the “ChocolateTemperature” *Topic* communicates with a |DR| of the “ChocolateTemperature” *Topic*. In the next Hands-On, you will add a "ChocolateLotState" Topic. Congratulations! You now have streaming Temperature data. .. _section-gsg_pub_sub_dp: 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 |DPs|. Most of the time in these hands-on exercises we will ignore these, and focus on |DWs|, |DRs|, and *Topics*. But it’s important to know that these other objects exist in every application. .. figure:: static/dp_structure.png :scale: 50 % :alt: DomainParticipants, Publishers, and Subscribers :name: DPPubSub :align: center |DPs| create and manage *Publishers* and *Subscribers*. *Publishers* create and manage |DWs|. *Subscribers* create and manage |DRs|. |DWs| and |DRs| send and receive your data. .. admonition:: Definition :class: definition-alert - A |DP| object in |CONNEXT| is used to create and manage one or more *Publishers* and *Subscribers*. The |DP| is a container for most other objects, and is responsible for the discovery process. In most applications, you will have only one |DP|, even if you have many |DWs| and |DRs|. |br| |br| - A *Publisher* object in |CONNEXT| is used to create and manage one or more |DWs|. A *Subscriber* object is used to create and manage one or more |DRs|. For more information, see :link_connext_publishers_users_man:`Publishers, in the RTI Connext DDS Core Libraries User's Manual <>` and :link_connext_subscribers_users_man:`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 |DW| in the next Hands-On section. We will see |DPs| 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. |DPs| also create *Topics*, which get used by your |DWs| and |DRs|. 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 |DP| per |DW| or |DR|. As you can see, it’s not necessary. You typically create one |DP| per application. It’s also a bad idea to use more than you need, because |DPs| use significant resources such as threads, and they use network bandwidth for discovery. We'll talk more about |DPs| in a later module on discovery. .. _section-gsg_datatypes_2dw: Hands-On 2: Add a Second DataWriter =================================== Now that you have created your first streaming data, we will add another |DW| to the **chocolate_factory_publisher** application. This will give you an idea how to add a new |DW| or |DR|, which will be useful because the code in the next module will have more-complex applications with multiple |DRs| and |DWs|. Add the New DataWriter ---------------------- .. only:: cpp11 Every |DW| needs to write on a *Topic*, and this new |DW| will use a different *Topic* and data type than the temperature |DW|. This new |DW| 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. |br| |br| #. Add a new *Topic*. Inside of ``chocolate_factory_publisher.cxx`` you should see this comment: .. code-block:: C++ // Exercise #2.1: Add new Topic Add the following code after the comment to create the new *Topic*: .. code-block:: C++ dds::topic::Topic lot_state_topic( participant, "ChocolateLotState"); #. Now, create the new |DW| using that *Topic*. Look for this comment: .. code-block:: C++ // Exercise #2.2: Add new DataWriter and data sample Right after the comment, add the new |DW| that writes on the new *Topic*, as well as the sample to write: .. code-block:: C++ dds::pub::DataWriter lot_state_writer( publisher, lot_state_topic); ChocolateLotState lot_state_sample; #. Finally, set some values in the ChocolateLotState data, and write the sample. Look for this comment: .. code-block:: C++ // Exercise #2.3 Write data with new ChocolateLotState DataWriter Add the following code after the comment: .. code-block:: C++ lot_state_sample.lot_id(count % 100); lot_state_sample.lot_status(LotStatusKind::WAITING); lot_state_writer.write(lot_state_sample); #. Now, compile and run the **chocolate_factory_publisher** application from the ``3_streaming_data/`` 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. .. only:: cpp98 Every |DW| needs to write on a *Topic*, and this new |DW| will write using a different *Topic* and data type than the temperature |DW|. This |DW| 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. |br| |br| #. Add a new data type, *Topic*, and |DW|. To support creating this new |DW|, you must: - Register your data type. - Create your *Topic* with the registered type name. - Create your |DW| with that *Topic*. - Use ``narrow()`` to cast from the generic |DW| to the type-specific |DW| that you will use to write. |br| |br| 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: .. code-block:: C++ // Exercise #2.1: Add new Topic, data type, and DataWriter Right after the comment, add this code to perform all the steps described above: .. code-block:: C++ // 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: .. code-block:: C++ // Exercise #2.2 Write data with new ChocolateLotState DataWriter Add the following code after the comment: .. code-block:: C++ 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/`` 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. .. only:: csharp Every |DW| needs to write on a *Topic*, and this new |DW| will use a different *Topic* and data type than the temperature |DW|. This new |DW| 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. |br| |br| #. Add a new *Topic*. Inside of ``ChocolateFactoryPublisher.cs`` you should see this comment: .. code-block:: C# // Exercise #2.1: Add new Topic Add the following code after the comment to create the new *Topic*: .. code-block:: C# Topic lotStateTopic = participant.CreateTopic( "ChocolateLotState"); #. Now, create the new |DW| using that *Topic*. Look for this comment: .. code-block:: C# // Exercise #2.2: Add new DataWriter and data sample Right after the comment, add the new |DW| that writes on the new *Topic*, as well as the sample to write: .. code-block:: C# DataWriter lotStateWriter = publisher.CreateDataWriter(lotStateTopic); var lotStateSample = new ChocolateLotState(); #. Finally, set some values in the ChocolateLotState data, and write the sample. Look for this comment: .. code-block:: C# // Exercise #2.3 Write data with new ChocolateLotState DataWriter Add the following code after the comment: .. code-block:: C# lotStateSample.lot_id = (uint) (count % 100); lotStateSample.lot_status = LotStatusKind.WAITING; lotStateWriter.Write(lotStateSample); #. Now, compile and run the **chocolate_factoryPublisher** application from the ``3_streaming_data/csharp`` directory where you generated code. You do not need to run the **chocolate_factorySubscriber** application, because next we will show you another way to visualize your data. .. figure:: static/two_writers.png :scale: 50 % :alt: Two DataWriters :name: FigureTwoDataWriters :align: center You added a second |DW| that writes on the "ChocolateLotState" Topic. Congratulations! You have added a second |DW| 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. 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 |DWs| and |DRs| in your system, but in a text format rather than the graphical format of *Admin Console*. #. Automatically creates |DRs| for any *Topic* being written on the network and prints out messages when its |DRs| 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). |br| |br| #. Click on the Utilities tab. |br| |br| #. Click on the DDS Spy icon. .. figure:: static/ac_spy_anno.png :figwidth: 90 % :alt: DDS Spy Icon :name: DDSSpyIcon :align: center #. In the dialog box that appears, select "Print samples" and click "Run." .. figure:: static/ac_spy_samples_anno.png :figwidth: 90 % :alt: Run DDS Spy :name: RunDDSSpy :align: center *rtiddsspy* will show you: - That it has discovered two |DWs| - The data being published by the two |DWs| .. code-block:: text 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 |DWs| in your **chocolate_factory_publisher** application. (You should see "W +N" for each new |DW| 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 |DWs| are writing. .. only:: cpp98 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 :link_api_cpp_rtiddsspy:`the rtiddsspy section in the API docs <>`. .. only:: cpp11 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 :link_api_cpp2_rtiddsspy:`the rtiddsspy section in the API docs <>`. .. list-table:: Understanding *rtiddsspy*'s Info Column :name: TableRtiddsspyNomenclature :widths: 20 100 :header-rows: 1 * - 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 Next Steps ========== Next, we will look a little farther into data design with :ref:`section-gsg_intro_keys`, then dive into more detail about Quality of Service (QoS) in :ref:`section-gsg_intro_qos`.