.. include:: vars.rst .. _section-gsg_intro_cpp11: Publish/Subscribe ***************** .. list-table:: :name: TableContentFiltersPrerequisites :widths: 20 80 :header-rows: 0 * - Prerequisites - * `Install git `__ * Install release 6.1.0 (see :ref:`section-gsg_before`) * - Time to complete - 30 minutes * - Concepts covered in this module - * Introduction to publish/subscribe * Introduction to |DWs|, and |DRs|, and *Topics* * Using the code generator * Using Waitsets * Viewing your data in *RTI Admin Console* The most basic communication pattern supported by *RTI® Connext® DDS* is the publish/subscribe model. Publish/Subscribe is a communications model where data producers "publish" data and data consumers "subscribe" to data. These publishers and subscribers don’t need to know about each other ahead of time; they discover each other dynamically at runtime. The data they share is described by a "topic," and publishers and subscribers send and receive data only for the topics they are interested in. In this pattern, many publishers may publish the same topic, and many subscribers may subscribe to the same topic. Subscribers receive data from all of the publishers that they share a topic with. Publishers send data directly to subscribers, with no need for a broker or centralized application to mediate communications. Introduction to DataWriters, DataReaders, and Topics ==================================================== In DDS, the objects that actually publish data are called |DWs|, and the objects that subscribe to data are |DRs|. |DWs| and |DRs| are associated with a single *Topic* object that describes that data. (DDS also has *Publisher* and *Subscriber* objects, but we will talk about them later.) An application typically has a combination of |DWs| and |DRs|. .. figure:: static/writer_reader_topic.png :scale: 50 % :alt: Writers Readers Overview :name: FigureWritersReaders :align: center |DWs| write data and |DRs| read data of a *Topic*. |DWs| of the “ChocolateTemperature” *Topic* communicate with |DRs| of the “ChocolateTemperature” *Topic*. |DWs| of the “ChocolateLotState” *Topic* communicate with |DRs| of the “ChocolateLotState” *Topic*. In a chocolate factory, for example, there might be a sensor that measures and publishes the current temperature of the tempering machine. Other applications monitor the temperature by subscribing to it. In this example, your *Topic* might be “ChocolateTemperature.” The sensor’s |DW| will be associated with the “ChocolateTemperature” *Topic*. In a similar way, other |DWs| and |DRs| share different types of data using additional *Topics*. |CONNEXT| is responsible for discovering |DWs| and |DRs| in a system, checking if they have a matching *Topic* (and compatible quality of service, which we will discuss later) and then providing the communication between those |DWs| and |DRs|. Logically, this means you can visualize your applications as having |DWs| and |DRs| that connect to a “databus,” because your applications are not specifying exactly which other applications they communicate with – they only specify which *Topics* they read from and write to the databus, and |CONNEXT| sets up the communication. Note that there is no "databus" object in your system – it is a logical way to visualize systems in which you don’t have to configure each communication path. .. _section-gsg_intro_handson1: Hands-On 1: Your First DataWriter and DataReader ================================================ We are going to start with a simple “Hello World” application to show how to use the code generator, and how to create a |DW| and a |DR|. .. tip:: .. only:: cpp11 By the end of this exercise, a publishing application will send data, and a subscribing application will receive and print it to the console using ``std::cout``. .. only:: cpp98 By the end of this exercise, a publishing application will send data, and a subscribing application will receive and print it to the console using ``HelloWorldTypeSupport::print_data()``. .. _section-gsg_clone: Get Example Files ----------------- The files you need to perform the hands-on exercises are installed in your ``rti_workspace`` directory, in an ``examples`` directory. You can also get the example files from `GitHub `_. We recommend you use the ``getting_started`` examples in your ``rti_workspace`` directory, but check for updates in GitHub later. .. note:: New languages and modules for this Getting Started Guide will be added to `GitHub `_ in between |CONNEXT| releases. To get the installed ``getting_started`` examples, look in the following location: .. tabs:: .. group-tab:: Linux ``/home//rti_workspace/6.1.0/examples/getting_started`` .. group-tab:: macOS ``/Users//rti_workspace/6.1.0/examples/getting_started`` .. group-tab:: Windows ``\rti_workspace\6.1.0\examples\getting_started`` Where ```` depends on your version of Windows. For example, on Windows 10, the folder is ``C:\Users\\Documents``. To get the ``getting_started`` examples from the GitHub repository, either download the repository as a Zip file from the webpage `here `_ or clone the repository from GitHub with the following command: .. tabs:: .. group-tab:: Linux .. code-block:: console $ git clone https://github.com/rticommunity/rticonnextdds-getting-started.git .. group-tab:: macOS .. code-block:: console $ git clone https://github.com/rticommunity/rticonnextdds-getting-started.git .. group-tab:: Windows .. code-block:: doscon > git clone https://github.com/rticommunity/rticonnextdds-getting-started.git .. _section-gsg_rtisetenv: Set Up Environment Variables (rtisetenv) ---------------------------------------- Set up the environment variables for running the code generator and compiling your application: #. Open a command prompt window, if you haven't already. |br| |br| #. Run this script: .. tabs:: .. group-tab:: Linux .. code-block:: console $ source /resource/scripts/rtisetenv_.bash If you're using the Z shell, run this: .. code-block:: console $ source /resource/scripts/rtisetenv_.zsh .. group-tab:: macOS .. code-block:: console $ source /resource/scripts/rtisetenv_.bash If you're using the Z shell, run this: .. code-block:: console $ source /resource/scripts/rtisetenv_.zsh .. group-tab:: Windows .. code-block:: doscon > \resource\scripts\rtisetenv_.bat When a directory name has a space, enclose the path in quotation marks. For example: ``"C:\Program Files\rti_connext_dds-6.1.0\resource\scripts\rtisetenv_x64Win64VS2017.bat"``. ```` refers to the installation directory for |CONNEXT|. The ``rtisetenv`` script adds the location of the SDK libraries (``/lib/``) to your library path, sets the environment variable (see :ref:`section-gsg_paths`), and puts the *RTI Code Generator* tool in your path. You’ll need this tool in the next step. Your architecture (such as ``x64Win64VS2017`` or ``x64Linux3gcc5.4.0``) is the combination of a processor, OS, and compiler version that you will use to build your application. The architecture you choose should match the target libraries you installed (see :ref:`section-gsg_install`). See also :ref:`section-gsg_check_install`. To see the full list of available architectures, run ``rtiddsgen -help`` or see the "RTI Architecture Abbreviation" columns in the :link_platform_notes:`RTI Connext DDS Core Libraries Platform Notes <>`. If this script does not exist in your installation, it means that you need to install a target. See :ref:`section-gsg_install`. .. _section-gsg_intro_runcodegen: Run Code Generator ------------------- Inside the repository you have cloned, there is a directory named ``2_hello_world``, which contains the **HelloWorld** type definition in a file named ``hello_world.idl``. #. Open ``hello_world.idl`` to see the definition for our **HelloWorld** type: .. code-block:: omg-idl // Hello world! struct HelloWorld { // String with maximum length of 256 characters string<256> msg; }; This language-independent interface is written in IDL, the Interface Definition Language. IDL allows you to declare data types used for communication (we'll cover this more in :ref:`section-gsg_intro_datatypes`). |CONNEXT| includes a code generator that translates from this language-independent data type into code specific for your language. The generated code serializes and deserializes your data into and out of a network format. |br| |br| #. From a terminal or command prompt, run *rtiddsgen*, which runs the code generator on ``hello_world.idl``: .. only:: cpp11 .. tabs:: .. group-tab:: Linux .. code-block:: console $ cd 2_hello_world $ rtiddsgen -language c++11 -platform -create makefiles -create typefiles -d c++11 hello_world.idl .. group-tab:: macOS .. code-block:: console $ cd 2_hello_world $ rtiddsgen -language c++11 -platform -create makefiles -create typefiles -d c++11 hello_world.idl .. group-tab:: Windows .. code-block:: doscon > cd 2_hello_world > rtiddsgen -language c++11 -platform -create makefiles -create typefiles -d c++11 -ppDisable hello_world.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 .. tabs:: .. group-tab:: Linux .. code-block:: console $ cd 2_hello_world $ rtiddsgen -language c++ -platform -create makefiles -create typefiles -d c++98 hello_world.idl .. group-tab:: macOS .. code-block:: console $ cd 2_hello_world $ rtiddsgen -language c++ -platform -create makefiles -create typefiles -d c++98 hello_world.idl .. group-tab:: Windows .. code-block:: doscon > cd 2_hello_world > rtiddsgen -language c++ -platform -create makefiles -create typefiles -d c++98 -ppDisable hello_world.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. You don't have to type the full path (``/bin/rtiddsgen``) because you ran ``rtisetenv_`` earlier, as described in :ref:`section-gsg_rtisetenv`. Your ``architecture`` is the combination of a processor, OS, and compiler version that you will use to build your application. (See :ref:`section-gsg_rtisetenv`.) .. only:: cpp11 ``-d c++11`` specifies the directory where the code will be generated. .. only:: cpp98 ``-d c++98`` specifies the directory where the code will be generated. .. note:: If you are using Visual Studio 2019, use "VS2017" in your ``architecture`` name. #. .. only:: cpp11 Open the ``c++11`` directory to review the code. .. only:: cpp98 Open the ``c++98`` directory to review the code. Overview of Generated and Example Code ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. only:: cpp11 The code you just generated includes the files in :numref:`TableGeneratedCodeFiles`, in the ``c++11`` directory. Some of the files are generated by *rtiddsgen*, some came from the examples directory as described in :ref:`section-gsg_clone`. .. only:: cpp98 The code you just generated includes the files in :numref:`TableGeneratedCodeFiles`, in the ``c++98`` directory. Some of the files are generated by *rtiddsgen*, some came from the examples directory as described in :ref:`section-gsg_clone`. .. tabularcolumns:: |\X{50}{120}|\X{50}{120}|\X{20}{120}| .. list-table:: Generated and Example Code Files :name: TableGeneratedCodeFiles :widths: 20 70 30 :header-rows: 1 * - Files - Description - Generated by *rtiddsgen*? * - .. only:: cpp11 - ``hello_world.hpp`` - ``hello_world.cxx`` - ``hello_worldPlugin.hpp`` - ``hello_worldPlugin.cxx`` .. only:: cpp98 - ``hello_world.h`` - ``hello_world.cxx`` - ``hello_worldPlugin.h`` - ``hello_worldPlugin.cxx`` - ``hello_worldSupport.h`` - ``hello_worldSupport.cxx`` - .. only:: cpp11 The C++11 definition of your data type, and the code used to serialize and deserialize it (convert it to a format for the network). This is the type-specific code that will be used in your real application. These files were generated when you specified ``-create typefiles`` when running *rtiddsgen*. .. only:: cpp98 The C++98 definition of your data type, and the code used to serialize and deserialize it (convert it to a format for the network). This is the type-specific code that will be used in your real application. These files were generated when you specified ``-create typefiles`` when running *rtiddsgen*. - Yes * - - ``application.hpp`` - ``hello_world_publisher.cxx`` - ``hello_world_subscriber.cxx`` - Example code from the repository you cloned. It includes code for two applications you can read and modify. These will compile into separate applications, **hello_world_publisher** and **hello_world_subscriber**. - No [#]_ * - - makefiles, Visual Studio solutions, or project files - Used to build your application. These files were generated because you specified ``-platform -create makefiles`` when running *rtiddsgen*. For more information on how to build, see the ``README_.txt`` file generated with the code. [#]_ - Yes * - - ``USER_QOS_PROFILES.xml`` - Configuration file for Quality of Service (to be discussed more later). This file came from the repository you cloned. - No * - - ``README_.txt`` - Instructions for how to open and modify the files, compile, and run the example for your specific IDE and operating system. This file was generated when you ran *rtiddsgen*. - Yes .. [#] *rtiddsgen* can also generate simple example code, but we are using our own code examples for this Getting Started Guide. .. [#] These files are generated by *rtiddsgen*. If you want to create your own project files or makefiles, see the :link_platform_notes:`RTI Connext DDS Core Libraries Platform Notes <>` to know what compilation and linker settings to use in them. .. _section-gsg_intro_openpub: Open/Modify Publishing Application ---------------------------------- #. .. only:: cpp11 In the ``2_hello_world/c++11`` directory, open ``hello_world_publisher.cxx`` in your IDE of choice. (See the ``README_.txt`` file generated with the code for information on how to modify this file in your IDE and operating system.) .. only:: cpp98 In the ``2_hello_world/c++98`` directory, open ``hello_world_publisher.cxx`` in your IDE of choice. (See the ``README_.txt`` file generated with the code for information on how to modify this file in your IDE and operating system.) This snippet shows how to create a *Topic* (with a name and data type) and create a |DW| for that *Topic*: .. only:: cpp11 .. code-block:: C++ // A Topic has a name and a datatype. Create a Topic named // "HelloWorld Topic" with type HelloWorld dds::topic::Topic topic(participant, "Example HelloWorld"); // A Publisher allows an application to create one or more DataWriters // Publisher QoS is configured in USER_QOS_PROFILES.xml dds::pub::Publisher publisher(participant); // This DataWriter will write data on Topic "HelloWorld Topic" // DataWriter QoS is configured in USER_QOS_PROFILES.xml dds::pub::DataWriter writer(publisher, topic); .. tip:: In this example, *Topic*, *Publisher*, and |DW| look like ordinary objects being created on the stack. However, these types are actually reference types that behave like shared pointers. Copying a reference does not copy the entity it is referring to, and creating additional references from the existing reference(s) is a relatively inexpensive operation. You can find more information about reference type semantics in the :link_api_cpp2_conventions:`Modern C++ API reference<>`, along with documentation conventions for distinguishing between reference types and value types. .. only:: cpp98 .. code-block:: C++ // A Topic has a name and a datatype. Create a Topic called // "HelloWorld Topic" with your registered data type DDSTopic *topic = participant->create_topic( "Example HelloWorld", type_name, DDS_TOPIC_QOS_DEFAULT, NULL /* listener */, DDS_STATUS_MASK_NONE); if (topic == NULL) { return shutdown(participant, "create_topic error", EXIT_FAILURE); } // This DataWriter will write data on Topic "HelloWorld Topic" // DataWriter QoS is configured in USER_QOS_PROFILES.xml DDSDataWriter *writer = publisher->create_datawriter( topic, DDS_DATAWRITER_QOS_DEFAULT, NULL /* listener */, DDS_STATUS_MASK_NONE); if (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() HelloWorldDataWriter *hello_world_writer = HelloWorldDataWriter::narrow(writer); if (hello_world_writer == NULL) { return shutdown(participant, "DataWriter narrow error", EXIT_FAILURE); } #. Change the *Topic* name from “Example HelloWorld” to “HelloWorld Topic”: .. only:: cpp11 .. code-block:: C++ :emphasize-lines: 3 // A Topic has a name and a datatype. Create a Topic named // "HelloWorld Topic" with type HelloWorld dds::topic::Topic topic(participant, "HelloWorld Topic"); .. only:: cpp98 .. code-block:: C++ :emphasize-lines: 4 // A Topic has a name and a datatype. Create a Topic called // "HelloWorld Topic" with your registered data type DDSTopic *topic = participant->create_topic( "HelloWorld Topic", type_name, DDS_TOPIC_QOS_DEFAULT, NULL /* listener */, DDS_STATUS_MASK_NONE); if (topic == NULL) { return shutdown(participant, "create_topic error", EXIT_FAILURE); } #. Modify the code to send the message "Hello world!" with a count. The following snippet shows how to write a HelloWorld update using the |DW|'s write method. .. only:: cpp11 In the **for** loop in the snippet, add the highlighted line, just after the comment ``// Modify the data to be written here``. This will set **sample.msg** to “Hello world!” with a count: .. code-block:: C++ :emphasize-lines: 6 // Create data sample for writing HelloWorld sample; for (int count = 0; running && (count < sample_count || sample_count == 0); count++) { // Modify the data to be written here sample.msg("Hello world! " + std::to_string(count)); std::cout << "Writing HelloWorld, count " << count << std::endl; writer.write(sample); rti::util::sleep(dds::core::Duration(4)); } .. only:: cpp98 In the **for** loop in the snippet, add the highlighted line, just after the comment ``// Modify the data to be written here``. This will set **sample.msg** to “Hello world!” with a count: .. code-block:: C++ :emphasize-lines: 16 // Create data sample for writing HelloWorld *sample = HelloWorldTypeSupport::create_data(); if (sample == NULL) { return shutdown( participant, "HelloWorldTypeSupport::create_data error", EXIT_FAILURE); } // Main loop, write data // --------------------- for (unsigned int count = 0; running && ((sample_count == 0) || (count < sample_count)); ++count) { // Modify the data to be written here snprintf(sample->msg, 256, "Hello world! %d", count); std::cout << "Writing HelloWorld, count " << count << std::endl; retcode = hello_world_writer->write(*sample, DDS_HANDLE_NIL); if (retcode != DDS_RETCODE_OK) { std::cerr << "write error " << retcode << std::endl; } // Send every 4 seconds DDS_Duration_t send_period = { 4, 0 }; NDDSUtility::sleep(send_period); } Recall that your "HelloWorld Topic" describes your data. This Topic is associated with the data type **HelloWorld**, which is defined in the IDL file (see :ref:`section-gsg_intro_runcodegen`). The data type **HelloWorld** contains a string field named **msg**. In this step, you have just added code to set a value for the **msg** field. Now, when the |DW| writes data, the **msg** field in the data will contain the string "Hello world! 1", "Hello world! 2", etc. .. admonition:: Definition :class: definition-alert A **sample** is a single update to a *Topic*, such as "Hello world! 1". Every time an application calls ``write()``, it is "writing a sample." Every time an application receives data, it is "receiving a sample." Note that samples don't necessarily overwrite each other. For example, if you set up a RELIABLE Quality of Service (QoS) with a History **kind** of KEEP_ALL, all samples will be saved and accumulate. You can find more details in :ref:`section-gsg_intro_qos`. .. _section-gsg_intro_opensub: Open/Modify Subscribing Application ----------------------------------- The subscriber application also creates a *Topic*, in ``hello_world_subscriber.cxx``. #. Open ``hello_world_subscriber.cxx``. This snippet shows how to create a Topic (with a name and data type) and create a |DR| for that Topic: .. only:: cpp11 .. code-block:: C++ // A Topic has a name and a datatype. Create a Topic named // "HelloWorld Topic" with type HelloWorld dds::topic::Topic topic(participant, "Example HelloWorld"); // A Subscriber allows an application to create one or more DataReaders // Subscriber QoS is configured in USER_QOS_PROFILES.xml dds::sub::Subscriber subscriber(participant); // This DataReader will read data of type HelloWorld on Topic // "HelloWorld Topic". DataReader QoS is configured in // USER_QOS_PROFILES.xml dds::sub::DataReader reader(subscriber, topic); .. only:: cpp98 .. code-block:: C++ // A Topic has a name and a datatype. Create a Topic called // "HelloWorld Topic" with your registered data type DDSTopic *topic = participant->create_topic( "Example HelloWorld", type_name, DDS_TOPIC_QOS_DEFAULT, NULL /* listener */, DDS_STATUS_MASK_NONE); if (topic == NULL) { shutdown(participant, "create_topic error", EXIT_FAILURE); } // This DataReader will read data of type HelloWorld on Topic // "HelloWorld Topic". DataReader QoS is configured in // USER_QOS_PROFILES.xml DDSDataReader *reader = subscriber->create_datareader( topic, DDS_DATAREADER_QOS_DEFAULT, NULL, DDS_STATUS_MASK_NONE); if (reader == NULL) { shutdown(participant, "create_datareader error", EXIT_FAILURE); } #. Change the *Topic* name from "Example HelloWorld" to "HelloWorld Topic", just as you did in the publishing application. .. note:: The *Topic* names must match between the publishing and subscribing applications for the |DW| and |DR| to communicate. .. only:: cpp11 .. code-block:: C++ :emphasize-lines: 3 // A Topic has a name and a datatype. Create a Topic named // "HelloWorld Topic" with type HelloWorld dds::topic::Topic topic(participant, "HelloWorld Topic"); .. only:: cpp98 .. code-block:: C++ :emphasize-lines: 4 // A Topic has a name and a datatype. Create a Topic called // "HelloWorld Topic" with your registered data type DDSTopic *topic = participant->create_topic( "HelloWorld Topic", type_name, DDS_TOPIC_QOS_DEFAULT, NULL /* listener */, DDS_STATUS_MASK_NONE); if (topic == NULL) { shutdown(participant, "create_topic error", EXIT_FAILURE); } .. _section-gsg_intro_rcvdata: Details of Receiving Data ^^^^^^^^^^^^^^^^^^^^^^^^^ You don't need to make any changes here, but look at the ``hello_world_subscriber.cxx`` code to see how it receives data. The |DR| is being notified of new data using an object called a WaitSet: .. only:: cpp98 .. code-block:: C++ // Get status condition: Each entity has a Status Condition, which // gets triggered when a status becomes true DDSStatusCondition *status_condition = reader->get_statuscondition(); if (status_condition == NULL) { shutdown(participant, "get_statuscondition error", EXIT_FAILURE); } // Enable only the status we are interested in: // DDS_DATA_AVAILABLE_STATUS retcode = status_condition->set_enabled_statuses(DDS_DATA_AVAILABLE_STATUS); if (retcode != DDS_RETCODE_OK) { shutdown(participant, "set_enabled_statuses error", EXIT_FAILURE); } // Create the WaitSet and attach the Status Condition to it. The WaitSet // will be woken when the condition is triggered. DDSWaitSet waitset; retcode = waitset.attach_condition(status_condition); if (retcode != DDS_RETCODE_OK) { shutdown(participant, "attach_condition error", EXIT_FAILURE); } // A narrow is a cast from a generic DataReader to one that is specific // to your type. Use the type specific DataReader to read data HelloWorldDataReader *hello_world_reader = HelloWorldDataReader::narrow(reader); if (hello_world_reader == NULL) { shutdown(participant, "DataReader narrow error", EXIT_FAILURE); } // Main loop. Wait for data to arrive, and process when it arrives. // ---------------------------------------------------------------- unsigned int samples_read = 0; while (running && (samples_read < sample_count || sample_count == 0)) { DDSConditionSeq active_conditions_seq; // wait() blocks execution of the thread until one or more attached // Conditions become true, or until a user-specified timeout expires. DDS_Duration_t wait_timeout = { 4, 0 }; retcode = waitset.wait(active_conditions_seq, wait_timeout); This WaitSet object is a way for the application to sleep until some "condition" becomes true. When the application calls ``waitset.wait(active_conditions_seq, wait_timeout)``, it will sleep for up to the duration time (4 seconds in this example), unless it is woken up due to a condition becoming true. There are multiple types of conditions that you can attach to a WaitSet, but this example shows a StatusCondition. In this example, we are saying that we are interested in waking up when the "data available" status becomes true. This means that when data arrives: - The reader’s ``data_available`` status becomes true - The application is woken up from the ``wait()`` call - The application code to process the status condition gets called Here is the code that gets run when the StatusCondition becomes true: .. code-block:: C++ // Get the status changes to check which status condition // triggered the WaitSet to wake DDS_StatusMask triggeredmask = hello_world_reader->get_status_changes(); // If the status is "Data Available" if (triggeredmask & DDS_DATA_AVAILABLE_STATUS) { samples_read += process_data(hello_world_reader); } This code checks that the ``data_available`` status became true, and then calls a ``process_data`` function. A closer look at ``process_data``: .. code-block:: C++ // Process data. Returns number of samples processed. unsigned int process_data(HelloWorldDataReader *hello_world_reader) { HelloWorldSeq data_seq; DDS_SampleInfoSeq info_seq; unsigned int samples_read = 0; // Take available data from DataReader's queue DDS_ReturnCode_t retcode = hello_world_reader->take(data_seq, info_seq); if (retcode != DDS_RETCODE_OK) { std::cerr << "take error " << retcode << std::endl; return 0; } // Iterate over all available data for (int i = 0; i < data_seq.length(); ++i) { // Check if a sample is an instance lifecycle event if (!info_seq[i].valid_data) { std::cout << "Received instance state notification" << std::endl; continue; } // Print data HelloWorldTypeSupport::print_data(&data_seq[i]); samples_read++; } // Data sequence was loaned from middleware for performance. // Return loan when application is finished with data. hello_world_reader->return_loan(data_seq, info_seq); } A closer look at the code for taking data: .. code-block:: C++ // Take available data from DataReader's queue DDS_ReturnCode_t retcode = hello_world_reader->take(data_seq, info_seq); if (retcode != DDS_RETCODE_OK) { std::cerr << "take error " << retcode << std::endl; return 0; } This code calls ``hello_world_reader->take()``, which removes any available samples out of the |DR| queue, and returns them in a sequence. If data is arriving quickly, it is likely this sequence will contain multiple samples. In this example, the sample’s data is being printed to the screen with ``HelloWorldTypeSupport::print_data``. .. only:: cpp11 .. code-block:: C++ // Create a WaitSet and attach the StatusCondition dds::core::cond::WaitSet waitset; waitset += status_condition; while (running && (samples_read < sample_count || sample_count == 0)) { // Dispatch will call the handlers associated to the WaitSet conditions // when they activate std::cout << "HelloWorld subscriber sleeping for 4 sec..." << std::endl; waitset.dispatch(dds::core::Duration(4)); // Wait up to 4s each time } This WaitSet object is a way for the application to sleep until some "condition" becomes true. When the application calls ``waitset.dispatch(dds::core::Duration(4))``, it will sleep for up to the duration time (4 seconds in this example), unless it is woken up due to a condition becoming true. There are multiple types of conditions that you can attach to a WaitSet, but this example shows a StatusCondition. Here is the code for the StatusCondition: .. code-block:: C++ // Obtain the DataReader's Status Condition dds::core::cond::StatusCondition status_condition(reader); // Enable the 'data available' status. status_condition.enabled_statuses( dds::core::status::StatusMask::data_available()); // Associate a handler with the status condition. This will run when the // condition is triggered. unsigned int samples_read = 0; status_condition.extensions().handler([&reader, &samples_read]() { samples_read += process_data(reader); }); In this example, we are saying that we are interested in waking up when the "data available" status becomes true. This means that when data arrives: - The reader’s ``data_available`` status becomes true - The application is woken up from the dispatch call - The lambda function is called A closer look at ``process_data``, called by the lambda function: .. code-block:: C++ unsigned int process_data(dds::sub::DataReader& reader) { // Take all samples. Samples are loaned to application, loan is // returned when LoanedSamples destructor called. unsigned int samples_read = 0; dds::sub::LoanedSamples samples = reader.take(); for (const auto& sample : samples) { if (sample.info().valid()) { samples_read++; std::cout << sample.data() << std::endl; } } return samples_read; } // The LoanedSamples destructor returns the loan This code calls ``reader.take()``, which removes any available samples out of the |DR| queue, and returns them in a collection. If data is arriving quickly, it is likely this collection will contain multiple samples. In this example, the sample’s data is being printed to the screen with ``std::cout``. .. _section-gsg_intro_compile: Compile Your Changes -------------------- Now that you have made changes to both the publisher and subscriber code, compile the code with your modifications. (For general instructions on how to compile on each operating system, see the ``README_.txt`` file included with your generated code.) .. note:: If you see linker errors, you may not have a complete installation. See :ref:`section-gsg_install`. You should see two applications in the ``objs/`` directory: - hello_world_publisher - hello_world_subscriber .. _section-gsg_intro_runapps: Run the Applications -------------------- #. 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| #. .. only:: cpp11 From within the ``2_hello_world/c++11`` directory, enter the following full path: .. only:: cpp98 From within the ``2_hello_world/c++98`` directory, enter the following full path: .. tabs:: .. group-tab:: Linux .. code-block:: console $ objs//hello_world_publisher .. group-tab:: macOS .. code-block:: console $ objs//hello_world_publisher .. group-tab:: Windows .. code-block:: doscon > objs\\hello_world_publisher.exe .. note:: You must be *in* the ``2_hello_world/`` directory and type the full path above. Do not run the publisher or subscriber application from within ``objs/``. You should run from the ``2_hello_world/`` directory because the examples use Quality of Service (QoS) information from the file ``USER_QOS_PROFILES.xml`` in the ``2_hello_world/`` directory. We'll talk more about QoS in a later module. You should see this in the window for the publisher: .. code-block:: text Writing HelloWorld, count 0 Writing HelloWorld, count 1 Writing HelloWorld, count 2 Writing HelloWorld, count 3 ... #. .. only:: cpp11 Open another command prompt window, run ``rtisetenv_`` if you haven't already in that window, and from within the ``2_hello_world/c++11`` directory, enter the following full path: .. only:: cpp98 Open another command prompt window, run ``rtisetenv_`` if you haven't already in that window, and from within the ``2_hello_world/c++98`` directory, enter the following full path: .. tabs:: .. group-tab:: Linux .. code-block:: console $ objs//hello_world_subscriber .. group-tab:: macOS .. code-block:: console $ objs//hello_world_subscriber .. group-tab:: Windows .. code-block:: doscon > objs\\hello_world_subscriber.exe You should see this in the window for the subscriber: .. only:: cpp98 .. code-block:: text msg: "Hello world! 1" Wait timed out after 4 seconds. msg: "Hello world! 2" Wait timed out after 4 seconds. msg: "Hello world! 3" Wait timed out after 4 seconds. msg: "Hello world! 4" ... .. note:: Since the ``wait_timeout`` for both the publisher and subscriber is up to 4 seconds, you may or may not get the ``Wait timed out after 4 seconds`` line between the samples, depending on the timing between the two applications. .. only:: cpp11 .. code-block:: text HelloWorld subscriber sleeping for 4 sec... [msg: Hello world! 1] HelloWorld subscriber sleeping for 4 sec... [msg: Hello world! 2] HelloWorld subscriber sleeping for 4 sec... [msg: Hello world! 3] ... .. note:: Since both the publisher and subscriber sleep for up to 4 seconds, you may get the ``HelloWorld subscriber sleeping for 4 sec...`` line twice in a row, depending on the timing between the two applications. .. only:: cpp11 The ``[msg: Hello world! ]`` line is the data being sent by the |DW|. If the |DW| weren't communicating with the |DR|, you would just see the ``HelloWorld subscriber sleeping for 4 sec`` lines and not the "msg" lines. (The subscribing application prints the "sleeping" lines before the WaitSet starts waiting for data, then it prints the "msg:" lines when it receives data from the |DW|.) .. only:: cpp98 The ``msg: "Hello world! "`` line is the data being sent by the |DW|. If the |DW| weren't communicating with the |DR|, you would just see the ``Wait timed out after 4 seconds`` lines and not the "msg" lines. (The subscribing application prints the "timed out" lines after the WaitSet times out while waiting for data, then it prints the "msg:" lines when it receives data from the |DW|.) .. note:: If you don't get the results described here, see :ref:`section-gsg_intro_trouble`. Taking It Further ----------------- Under the hood, the publishing and subscribing applications are doing a lot of work: Before communication starts, the |DW| and |DR| discover each other and check that they have the same *Topic* name, compatible data types, and compatible QoS. (We will talk more about discovery in a later module). After discovery, the |DW| sends data directly to the |DR|, with no message broker required. When you run the applications on the same machine, by default they communicate over shared memory. If you run one on another machine, they communicate over the network using UDP. Start up Multiple Publishing or Subscribing Applications ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Try starting up multiple publishing or subscribing applications, and you will see that they will also send or receive data. (Remember to run from the ``2_hello_world/`` directory.) .. only:: cpp11 .. figure:: static/hello_world_pubsub11.png :figwidth: 90 % :alt: Hello World Publisher Subscriber :name: FigureHelloWorldPubSubCpp11 :align: center Two applications publishing, and two subscribing, to the same *Topic*. Notice that each subscriber is receiving data from both publishers. .. only:: cpp98 .. figure:: static/hello_world_pubsub98.png :figwidth: 90 % :alt: Hello World Publisher Subscriber :name: FigureHelloWorldPubSubCpp98 :align: center Two applications publishing, and two subscribing, to the same *Topic*. Notice that each subscriber is receiving data from both publishers. .. figure:: static/twopub_twosub.png :scale: 50 % :alt: Two Applications Publishing and Subscribing :name: FigureApplicationsPubSub :align: center Two applications publishing, and two subscribing, to the same *Topic*. Publish/Subscribe across Multiple Machines ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ To publish/subscribe between two machines: #. Install or clone the ``getting_started`` examples on both machines. See :ref:`section-gsg_clone`. |br| |br| #. In the ``2_hello_world`` directory on one machine, modify and run the publishing application as described in :ref:`section-gsg_intro_openpub`. See also :ref:`section-gsg_intro_compile` and :ref:`section-gsg_intro_runapps`. |br| |br| #. In the ``2_hello_world`` directory on the other machine, modify and run the subscribing application as described in :ref:`section-gsg_intro_opensub`. See also :ref:`section-gsg_intro_compile` and :ref:`section-gsg_intro_runapps`. .. note:: If you are running both applications and they aren’t communicating (you don’t see the “msg:” lines shown at the end of :ref:`section-gsg_intro_runapps`), see :ref:`section-gsg_intro_trouble`. Create Multiple DataWriters, DataReaders, and Topics in a Single Application ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ So far, you have created two applications: one that uses a |DR| to subscribe to "HelloWorld Topic" and one that uses a |DW| to publish "HelloWorld Topic." You have seen that these applications automatically discover each other and communicate, and that you can run multiple copies of them. In :ref:`section-gsg_intro_datatypes`, you'll add a second |DW| to an application, and in :ref:`section-gsg_intro_keys`, you'll create an application that contains multiple |DWs|, |DRs|, and *Topics*. .. tip:: Even though these initial example applications each do only one thing, typical real-world applications contain a combination of |DWs| and |DRs| for multiple *Topics* in a single application. We will illustrate this in later modules. .. figure:: static/temp_application.png :scale: 50 % :alt: Oven Application, Multiple Topics :name: FigureOvenApplication :align: center Sneak Preview: In a later module, we will create a tempering application that will write a "ChocolateTemperature" *Topic* and read a "ChocolateLotState" *Topic*. An application can write and read multiple *Topics* by creating more |DWs| and |DRs|. .. _section-gsg_intro_trouble: Troubleshooting =============== Why aren't my applications communicating? ----------------------------------------- If you are running both applications and they aren't communicating (you don't see the ``msg: Hello world`` lines shown at the end of :ref:`section-gsg_intro_runapps`), here are some things to check: - Did you change the *Topic* name in both applications before compiling? They should match. .. - Are you running both applications from the ``2_hello_world/`` directory, so they load the same ``USER_QOS_PROFILES.xml`` file? .. note:: You must be *in* the ``2_hello_world/`` directory when you type ``objs//hello_world_[.exe]``. Do not run the application from within ``objs/``. (When *rtiddsgen* generates a project for your IDE (e.g., for Eclipse™ or Visual Studio) and you run the application from that IDE, it will run from the correct directory.) - If you are running on multiple machines, does your network support multicast? If it does not, see :ref:`section-gsg_intro_trouble_peers` below, for how to specify the addresses of the remote machines your application plans to communicate with. .. - Check to see if one or both machines use a firewall. You may need to disable your firewall or configure it to allow multicast traffic for communication to be established. .. See also :ref:`section-gsg_intro_discovery`. .. _section-gsg_intro_trouble_peers: How do I set my discovery peers? -------------------------------- If you are running |CONNEXT| on multiple machines and your network does not support multicast, specify the address(es) of the remote machine(s) you want to communicate with: #. In the ``2_hello_world\`` directory, find USER_QOS_PROFILES.xml. |br| |br| #. Identify the following lines in USER_QOS_PROFILES.xml: .. code-block:: xml HelloWorldParticipant #. Add the ```` section as follows, and replace the IP address with the IP address or hostname of the other machine you want to communicate with: .. code-block:: xml :emphasize-lines: 10,11,12,13,14,15 HelloWorldParticipant 192.168.1.14 If you want more information about discovery peers, see :link_connext_peers_users_man:`Configuring the Peers List Used in Discovery, in the RTI Connext DDS Core Libraries User's Manual <>`. Why does the DataReader miss the first samples? ----------------------------------------------- Discovery is not an instantaneous event. It takes some time for the discovery process between applications to complete. The |DW| and |DR| must discover each other before they can start communicating. Therefore, if you send data immediately after creating the |CONNEXT| entities, |DRs| will not receive the first few samples because they are still in-process of discovering the |DWs| and vice versa. This is true even when the |DWs| and |DRs| are reliable, because the Reliability QoS on its own does not guarantee delivery to |DRs| that have not been discovered yet. You can overcome this behavior with the Durability QoS Policy, which can be set to deliver historical samples (that |DWs| already sent) to late-joining |DRs|. The |DRs| will then receive the first samples they originally missed. We'll talk about this more in :ref:`section-gsg_intro_qos`. .. Why do I get a "No source for License information" error? --------------------------------------------------------- If you get the following error, run ``rtisetenv_`` in your command prompt window: .. code-block:: text RTI Connext DDS No source for License information Please contact support@rti.com with any questions or comments. Exception in subscriber_main(): Failed to create DomainParticipant (You may need to run ``rtisetenv_`` for each new command prompt window you open, especially if you installed an ``lm`` package.) See :ref:`section-gsg_rtisetenv`. See also :ref:`section-gsg-license`. .. _section-gsg_view_data: Hands-On 2: Viewing Your Data ============================= Now that you’ve created applications that are publishing data, you can visualize that data using the *Admin Console* tool. .. note:: The applications from Hands-On 1 above should be running while you perform the *Admin Console* steps below. See :ref:`section-gsg_intro_runapps`. Open Admin Console ------------------ Start by opening *Admin Console* from *RTI Launcher*: .. figure:: static/open_adminconsole.png :figwidth: 90 % :alt: Open Admin Console in Launcher :name: FigureOpenAdminConsole :align: center The following sections guide you through *Admin Console* for the purposes of this exercise, but you can find more information in *Admin Console's* online Help: .. figure:: static/ac_help_anno.png :figwidth: 60 % :alt: Admin Console Help :name: AdminConsoleHelp :align: center Choose Automatically Join ------------------------- You may be prompted to automatically or manually join `domains `_ when you first open *Admin Console*. For the purposes of this exercise, choose to automatically join (the default). We'll discuss domains in a later module. Switch to Data Visualization Perspective ---------------------------------------- Select the Data Visualization Perspective entry under the Visualization menu. (You may need to close a Welcome tab first.) If you can't select the Data Visualization Perspective menu item, you may already be in that mode. .. figure:: static/ac_data_win.png :figwidth: 90 % :alt: Open Data Visualization :name: FigureOpenDataVisualization :align: center Open Topic Data Tab ------------------- From the DDS Logical View, click on the "HelloWorld Topic" to open the Topic View. .. figure:: static/open_topic_data_tab.png :figwidth: 90 % :alt: Open Topic Data Tab :name: FigureOpenTopicData :align: center In the Data Visualization Perspective, the main window will show the Topic Data tab, as seen below: .. figure:: static/topic_data_tab.png :figwidth: 90 % :alt: Topic Data Tab :name: FigureTopicDataTab :align: center Subscribe to "HelloWorld Topic" ------------------------------- The *Admin Console* tool itself is a DDS application, so it must also create |DRs| to subscribe to the "HelloWorld Topic." (See the *Admin Console* online Help for more information about this.) When you subscribe to the "HelloWorld Topic" in *Admin Console*, *Admin Console* will create a |DR| for this *Topic*. Click on the **Subscribe...** button or right-click on the *Topic* in the DDS Logical View and select Subscribe. This will open the **Create Subscription** dialog seen below. Click OK to subscribe to the *Topic*. .. figure:: static/create_subscription.png :figwidth: 90 % :alt: Create Subscription Dialog :name: FigureCreateSubscription :align: center Use Topic Data Tab ------------------ After the *Topic* is subscribed to, you will see your "Hello world" message. .. figure:: static/topic_data_message.png :figwidth: 90 % :alt: View Hello World Message :name: FigureViewHelloWorld :align: center In the *Admin Console* tool, you’re also able to inspect |DWs| and |DRs|, and even chart your live data. Use Admin Console across Machines --------------------------------- *Admin Console* is like any other |CONNEXT| application. It discovers what |DWs| and |DRs| are on the network, whether they're on the same machine as *Admin Console* or on different machines. If you have trouble viewing data in *Admin Console* across machines, the same troubleshooting tips apply to *Admin Console* as apply to |DWs| and |DRs| in :ref:`section-gsg_intro_trouble_peers`. If your network supports multicast, you'll see data on the other machines in *Admin Console*; otherwise, specify the IP addresses of the remote machines you want *Admin Console* to communicate with. .. figure:: static/ac_peers_anno.png :figwidth: 90 % :alt: Setting Peers in Admin Console :name: FigureAdminConsolePeers :align: center .. _section-gsg_intro_next: Next Steps ========== Congratulations! You’ve written your first DDS application, which publishes HelloWorld data. In this exercise, you’ve experienced a quick overview of the development process from defining a data type and using the code generator, to building an example application and using |CONNEXT| *Professional* tools to see that data is being published. We’ll continue to build on these skills and to use these tools in more depth in subsequent exercises. The next module takes a deeper dive into data types, including a little more information about how to define common types. We will be starting to work with an example that’s more complex than a "hello world"—we’re going to be using a chocolate factory example to showcase DDS concepts. See :ref:`section-gsg_intro_datatypes`.