Application Design#

Introduction#

What you’ll learn

In this module, you’ll use System Designer to define your application’s domain entities (such as Topics, DataWriters, and DataReaders) and load them into your application code.

You’ll do the following:

  1. Review the data model for vehicle tracking defined in the Data Modeling module

  2. Extend that module by defining the application’s domain entities in System Designer

  3. Load those entities into your application code

  4. Complete the application to simulate vehicles that publish their state and metrics, and a dashboard that subscribes to this data

In the Data Modeling module, you created data types in System Designer, exported them to XML, and used them in your application. In this module, you’ll use System Designer to additionally define Topics, DataReaders, and DataWriters to define a full application in System Designer. You’ll export that full definition to XML, then load it into your application.

In System Designer, you’ll define two applications: one will publish data about a vehicle’s metrics and transit data; the other will subscribe to, process, and display this data. Your completed system will look like this in System Designer:

../_images/SystemDesigner-ApplicationDesign.png

System Designer will produce an XML file containing the definition of the types, quality of service (QoS), and domain entities with their configuration. You’ll load this file in your application and instantiate the DomainParticipant, Topics, DataWriters, and DataReaders defined in the file.

with qos_provider.create_participant_from_config(
    "ParticipantLibrary::PublisherApp"
) as participant:
    ...

How to complete this module#

To complete this module, you’ll need the following:

  • 20-30 minutes

  • A Connext installation (full installation).

  • System Designer (included in the installation) or a text editor.

It’s recommended to have previously completed the Data Modeling module, which demonstrates the concepts you will build upon here.

You can complete this module from scratch, but the final application along with instructions to run it is available in the GitHub repository:

Cloning the GitHub repository

Clone the GitHub repository with the following command:

$ git clone --recurse-submodule https://github.com/rticommunity/rticonnextdds-examples.git

The code for this module is located in the tutorials/application_design directory. See the README.md files for additional instructions.

1. Define the data model#

First, you need a data model as the basis of your domain. These are the data types that your applications will use in their communication, and the QoS associated with the flow of data.

In this module, you’ll use the data model defined in the Data Modeling module. You can use System Designer as explained in that module, or import the XML data model from that module into System Designer as shown below:

Recap

The data model consists of two types, VehicleMetrics and VehicleTransit, and two QoS profiles, VehicleMetrics_Profile and VehicleTransit_Profile.

VehicleModeling.xml#
<dds>
    <types>
        <const name="VIN_LENGTH" type="uint8" value="17"/>
        <typedef name="VIN" type="string" stringMaxLength="VIN_LENGTH"/>

        <struct name="Coord" extensibility="final" nested="true">
            <member name="lat" type="float64"/>
            <member name="lon" type="float64"/>
        </struct>
        <struct name="VehicleTransit" extensibility="appendable">
            <member name="vehicle_vin"
                    type="nonBasic"
                    nonBasicTypeName="VIN"
                    key="true" />
            <member name="current_position"
                    type="nonBasic"
                    nonBasicTypeName="Coord" />
            <member name="current_route"
                    type="nonBasic"
                    nonBasicTypeName="Coord"
                    sequenceMaxLength="-1"
                    optional="true" /> <!-- 'no route' == standby -->
        </struct>

        <typedef name="Percentage" type="float64" min="0.0" max="100.0"/>
        <struct name="VehicleMetrics" extensibility="appendable">
            <member name="vehicle_vin"
                    type="nonBasic"
                    nonBasicTypeName="VIN"
                    key="true"/>
            <member name="fuel_level"
                    type="nonBasic"
                    nonBasicTypeName="Percentage"/>
        </struct>
    </types>

    <qos_library name="VehicleModeling_Library">
        <qos_profile name="VehicleMetrics_Profile">
            <datawriter_qos base_name="BuiltinQosLib::Generic.BestEffort">
                <deadline>
                    <period>
                        <sec>10</sec>
                        <nanosec>0</nanosec>
                    </period>
                </deadline>
            </datawriter_qos>
            <datareader_qos base_name="BuiltinQosLib::Generic.BestEffort">
                <deadline>
                    <period>
                        <sec>15</sec>
                        <nanosec>0</nanosec>
                    </period>
                </deadline>
            </datareader_qos>
        </qos_profile>
        <qos_profile name="VehicleTransit_Profile">
            <datawriter_qos base_name="BuiltinQosLib::Generic.StrictReliable">
                <durability>
                    <kind>TRANSIENT_LOCAL_DURABILITY_QOS</kind>
                </durability>
            </datawriter_qos>
            <datareader_qos base_name="BuiltinQosLib::Generic.KeepLastReliable">
                <durability>
                    <kind>TRANSIENT_LOCAL_DURABILITY_QOS</kind>
                </durability>
            </datareader_qos>
        </qos_profile>
    </qos_library>
</dds>

Create a copy of this file with the same name (VehicleModeling.xml).

Create a new System Designer project to import this XML file. Also call your project VehicleModeling.

Import VehicleModeling.xml into System Designer by clicking import in the project.

Learn more on how to open or use System Designer in the Data Modeling module, in 1. Define the data types.

2. Define the domain entities#

With your data model open in System Designer, start defining the entities that will be part of your system. These include DomainParticipants, Topics, DataReaders, and DataWriters.

First, define a domain and its Topics.

Create a new Domain Library in your System Designer project. (In the View menu, click Domain then “Add.”)

Then, create a new Domain in your Domain Library.

Register the VehicleMetrics and VehicleTransit types and create two Topics, one for each type.

In the end, your Domain Library should look like this:

<dds>
    <domain_library name="DomainLibrary">
        <domain name="VehicleDomain" domain_id="0">
            <topic name="VehicleMetricsTopic" register_type_ref="VehicleMetrics" />
            <topic name="VehicleTransitTopic" register_type_ref="VehicleTransit" />
        </domain>
    </domain_library>

    <domain_participant_library name="ParticipantLibrary">
        <!-- Participants will be defined here -->
    </domain_participant_library>
</dds>

Next, define two DomainParticipants, one per application.

The first application simulates a vehicle that publishes its metrics and transit data. The simulation will navigate through a route at a fixed pace while the fuel is consumed over time.

Create a new DomainParticipant Library in your System Designer project. (In the Participant view, click “Add.”)

Then, create a new DomainParticipant called PublisherApp.

Create a Publisher called Publisher and two DataWriters, one for each Topic. A Publisher is a group of DataWriters.

In the end, your DomainParticipant should look like this:

<domain_participant name="PublisherApp" domain_ref="DomainLibrary::VehicleDomain">
    <data_writer name="MetricsWriter"
                topic_ref="VehicleMetricsTopic">
        <datawriter_qos base_name="VehicleModeling_Library::VehicleMetrics_Profile" />
    </data_writer>

    <data_writer name="TransitWriter"
                topic_ref="VehicleTransitTopic">
        <datawriter_qos base_name="VehicleModeling_Library::VehicleTransit_Profile" />
    </data_writer>
</domain_participant>

The second application subscribes to these two Topics to implement a dashboard to monitor vehicles that may be in the system. The subscriber will receive updates about the vehicle’s metrics and transit data, and either display them or produce statistics from them.

Create a new DomainParticipant called SubscriberApp. Then, create a Subscriber called Subscriber and two DataReaders, one for each Topic. A Subscriber is a group of DataReaders.

In the end, your DomainParticipant should look like this:

<domain_participant name="SubscriberApp" domain_ref="DomainLibrary::VehicleDomain">
    <data_reader name="MetricsReader"
                topic_ref="VehicleMetricsTopic">
        <datareader_qos base_name="VehicleModeling_Library::VehicleMetrics_Profile" />
    </data_reader>
    <data_reader name="TransitReader"
                topic_ref="VehicleTransitTopic">
        <datareader_qos base_name="VehicleModeling_Library::VehicleTransit_Profile" />
    </data_reader>
</domain_participant>

3. Load the domain entities#

In the previous step, you defined your entities in System Designer. Now, you’ll create two skeleton applications (publisher and subscriber) to load your XML configuration and create the domain entities.

Create a new directory and copy VehicleModeling.xml into it. You’ll use this directory to create the publisher and subscriber applications.

For the publisher application, create a new DomainParticipant using the ParticipantLibrary::PublisherApp definition, which contains two DataWriters.

Create a publisher.py file with the following content:

import rti.connextdds as dds

from VehicleModeling import VehicleMetrics, VehicleTransit, Coord

class PublisherSimulation:
    def __init__(self, metrics_writer: dds.DataWriter, transit_writer: dds.DataWriter):
        pass

    def run():
        pass

def main():

    dds.DomainParticipant.register_idl_type(VehicleMetrics, "VehicleMetrics")
    dds.DomainParticipant.register_idl_type(VehicleTransit, "VehicleTransit")

    qos_provider = dds.QosProvider("VehicleModeling.xml")

    with qos_provider.create_participant_from_config(
        "ParticipantLibrary::PublisherApp"
    ) as participant:
        metrics_writer = dds.DataWriter(
            participant.find_datawriter("Publisher::MetricsWriter")
        )
        transit_writer = dds.DataWriter(
            participant.find_datawriter("Publisher::TransitWriter")
        )

        simulation = PublisherSimulation(metrics_writer=metrics_writer, transit_writer=transit_writer)
        print(f"Running simulation: {simulation=}")

        simulation.run()

The PublisherSimulation class encapsulates the state of your publishing application, including which DataWriters it will use. Once the with block is exited, the DomainParticipant will be gracefully closed.

Find the full file (publisher.py) in GitHub.

Create a publisher.cxx file with the following content:

#include <iostream>
#include <sstream>
#include <thread>

#include <rti/rti.hpp>
#include "VehicleModeling.hpp"
#include "common.hpp"

class PublisherSimulation {
public:
    explicit PublisherSimulation(
            dds::pub::DataWriter<VehicleMetrics> metrics_writer,
            dds::pub::DataWriter<VehicleTransit> transit_writer)
            : metrics_writer_(metrics_writer),
              transit_writer_(transit_writer)
            // ...
    {
    }

    void run();

    friend std::string to_string(const PublisherSimulation &dashboard);

private:
    dds::pub::DataWriter<VehicleMetrics> metrics_writer_;
    dds::pub::DataWriter<VehicleTransit> transit_writer_;

    // ...
};

int main(int argc, char **argv)
{
    utils::set_random_seed(std::time(nullptr));

    rti::domain::register_type<VehicleMetrics>();
    rti::domain::register_type<VehicleTransit>();

    dds::core::QosProvider qos_provider("VehicleModeling.xml");

    auto participant = qos_provider.extensions().create_participant_from_config(
            "ParticipantLibrary::PublisherApp");

    using MetricsWriter = dds::pub::DataWriter<VehicleMetrics>;
    auto metrics_writer = rti::pub::find_datawriter_by_name<MetricsWriter>(
            participant,
            "Publisher::MetricsWriter");

    using TransitWriter = dds::pub::DataWriter<VehicleTransit>;
    auto transit_writer = rti::pub::find_datawriter_by_name<TransitWriter>(
            participant,
            "Publisher::TransitWriter");

    PublisherSimulation simulation(metrics_writer, transit_writer);
    std::cout << "Running simulation " << to_string(simulation)
            << std::endl;
    simulation.run();
}

The PublisherSimulation class encapsulates the state of your publishing application, including which DataWriters it will use. Once the participant goes out of scope, the DomainParticipant will be gracefully closed.

Find the full file (publisher.cxx) in GitHub; find the full utility file (common.hpp) also in Github.

The above code shows three important steps:

  • Type registration: The VehicleMetrics and VehicleTransit types are registered with the DomainParticipant. This is necessary for the DomainParticipant to be able to serialize and deserialize the data using the code generated by Code Generator (rtiddsgen).

    Note

    If you haven’t already, generate the code in your target programming language for the types defined in VehicleModeling.xml, as explained in the Data Modeling module, in 2. Generate example applications. You won’t need the example files that rtiddsgen generates, but you’ll need the type files.

  • Participant creation: The DomainParticipant is created using the XML configuration ParticipantLibrary::PublisherApp. This configuration contains the QoS settings for the DomainParticipant and its entities.

  • Endpoint look-up: The DataWriter (endpoint) instances are retrieved by looking them up by their names.

For the suscriber application, create a new DomainParticipant using the ParticipantLibrary::SubscriberApp definition, which contains two DataReaders.

Create a subscriber.py file with the following content:

import asyncio

import rti.asyncio
import rti.connextdds as dds

from VehicleModeling import Coord, VehicleMetrics, VehicleTransit

class SubscriberDashboard:
    def __init__(
        self,
        metrics_reader: "dds.DataReader",
        transit_reader: "dds.DataReader",
    ):
        pass

    async def run(self):
        pass

def main():
    dds.DomainParticipant.register_idl_type(VehicleMetrics, "VehicleMetrics")
    dds.DomainParticipant.register_idl_type(VehicleTransit, "VehicleTransit")

    qos_provider = dds.QosProvider("VehicleModeling.xml")

    with qos_provider.create_participant_from_config(
        "ParticipantLibrary::SubscriberApp"
    ) as participant:
        transit_reader = dds.DataReader(
            participant.find_datareader("Subscriber::TransitReader")
        )
        metrics_reader = dds.DataReader(
            participant.find_datareader("Subscriber::MetricsReader")
        )

        dashboard = SubscriberDashboard(
            transit_reader=transit_reader, metrics_reader=metrics_reader
        )

        print(f"Running dashboard: {dashboard=}")
        asyncio.run(dashboard.run())

SubscriberDashboard is a class that encapsulates your subscriber application, including which DataReaders it will use. Once the with block is exited, the DomainParticipant will be gracefully closed.

Find the full file (subscriber.py) in GitHub.

Create a subscriber.cxx file with the following content:

#include <iostream>
#include <sstream>
#include <thread>
#include <unordered_map>

#include <rti/rti.hpp>
#include "VehicleModeling.hpp"
#include "common.hpp"

class SubscriberDashboard {
public:
    explicit SubscriberDashboard(
            dds::sub::DataReader<VehicleMetrics> metrics_reader,
            dds::sub::DataReader<VehicleTransit> transit_reader)
            : metrics_reader_(metrics_reader), transit_reader_(transit_reader)
    {
    }

    // ...

    void run();

    friend std::string to_string(const SubscriberDashboard &dashboard);

private:
    dds::sub::DataReader<VehicleMetrics> metrics_reader_;
    dds::sub::DataReader<VehicleTransit> transit_reader_;

    // ..

};

int main(int argc, char **argv)
{
    utils::set_random_seed(std::time(nullptr));

    rti::domain::register_type<VehicleMetrics>();
    rti::domain::register_type<VehicleTransit>();

    dds::core::QosProvider qos_provider("VehicleModeling.xml");

    auto participant = qos_provider.extensions().create_participant_from_config(
            "ParticipantLibrary::SubscriberApp");

    using MetricsReader = dds::sub::DataReader<VehicleMetrics>;
    auto metrics_reader = rti::sub::find_datareader_by_name<MetricsReader>(
            participant,
            "Subscriber::MetricsReader");

    using TransitReader = dds::sub::DataReader<VehicleTransit>;
    auto transit_reader = rti::sub::find_datareader_by_name<TransitReader>(
            participant,
            "Subscriber::TransitReader");

    SubscriberDashboard dashboard(metrics_reader, transit_reader);
    std::cout << "Running dashboard " << to_string(dashboard)
            << std::endl;
    dashboard.run();
}

SubscriberDashboard is a class that encapsulates your subscriber application, including which DataReaders it will use. Once the participant goes out of scope, the DomainParticipant will be gracefully closed.

Find the full file (subscriber.cxx) in GitHub; find the full utility file (common.hpp) also in Github.

The subscriber application follows the same approach as the publisher application. It registers the types, creates a DomainParticipant, and looks up the DataReaders by their names.

In this section, you created a skeleton application focused on how to create the participants and their entities from an XML definition. Next, you’ll complete the logic of your applications.

4. Write the application logic#

For the publisher application, we’ll create a simulation that periodically publishes updates to the Topics for VehicleMetrics and VehicleTransit.

This code will be executed in the main function previously defined, using the DataWriters passed in the simulation initialization. Once the DomainParticipant is closed, the DataWriters will be closed as well.

class PublisherSimulation:
    # ...
    def run(self):
        while not self.has_ended:
            self._metrics_writer.write(
                VehicleMetrics(
                    self._vehicle_vin,
                    self._vehicle_fuel,
                )
            )

            self._transit_writer.write(
                VehicleTransit(
                    self._vehicle_vin,
                    current_route=self._vehicle_route,
                    current_position=self._vehicle_position,
                )
            )

            time.sleep(1)

            if self._is_on_standby:
                print(
                    f"Vehicle '{self._vehicle_vin}' has reached its destination, now moving to a new location..."
                )
                self._vehicle_route = new_route(start=self._vehicle_position)

            self._vehicle_position = self._vehicle_route.pop(0)
            self._vehicle_fuel -= 10 * random.random()

            if self._is_out_of_fuel:
                self._vehicle_fuel = 0.0

                print(f"Vehicle '{self._vehicle_vin}' ran out of fuel!")

Find the full file (publisher.py) in GitHub.

This code will be executed in the main function previously defined, using the DataWriters passed in the simulation initialization. Once the DomainParticipant is closed, the DataWriters will be closed as well.

void PublisherSimulation::run()
{
    while (!has_ended()) {
        metrics_writer_.write(
                VehicleMetrics { vehicle_vin_, vehicle_fuel_ });

        transit_writer_.write(VehicleTransit { vehicle_vin_,
                                                vehicle_position_,
                                                vehicle_route_ });

        std::this_thread::sleep_for(std::chrono::seconds(1));

        if (is_on_standby()) {
            std::cout << "Vehicle '" << vehicle_vin_
                        << "' has reached its destination, now moving to a "
                            "new location..."
                        << std::endl;
            vehicle_route_ = utils::new_route();
            vehicle_route_[0] = vehicle_position_;
        }

        vehicle_fuel_ -= 10 * utils::random_stduniform();
        vehicle_position_ = vehicle_route_.front();
        vehicle_route_.erase(vehicle_route_.begin());

        if (is_out_of_fuel()) {
            vehicle_fuel_ = 0.0;
            std::cout << "Vehicle '" << vehicle_vin_ << "' ran out of fuel!"
                        << std::endl;
        }
    }
}

Find the full file (publisher.cxx) in GitHub; find the full utility file (common.hpp) also in Github.

The metrics DataWriter will periodically publish the vehicle’s statistics (fuel level). The fuel level is decremented with every iteration, so that the simulation eventually ends.

The transit DataWriter will periodically publish updates about the vehicle’s position and route. It will also create a new route once the vehicle is on standby.

For the subscriber application, we’ll create a dashboard that displays information directly obtained or derived from updates to the topics for VehicleMetrics and VehicleTransit.

This code will be asynchronously executed in the main function previously defined, using the DataReaders passed in the simulation initialization. Once the DomainParticipant is closed, the DataReaders will be closed as well.

class SubscriberDashboard:
    # ...
    async def run(self):
        await asyncio.gather(
            self._display_app(),
            self._metrics_app(),
            self._transit_app(),
        )

You’ll find one “job” function for each responsibility of the dashboard—that is, for either displaying or subscribing to a topic.

class SubscriberDashboard:
    # ...
    async def _display_app(self):
        while True:
            print(f"[[ DASHBOARD: {datetime.now()} ]]")
            print(f"Online vehicles: {len(self.online_vehicles)}")
            for data in self.online_vehicles.values():
                print(f"- Vehicle {data.vin}:")
                print(f"  Fuel updates: {len(data.fuel_history)}")
                print(f"  Last known destination: {data.current_destination}")
                print(f"  Last known fuel level: {data.fuel_history[-1]}")
            print(f"Offline vehicles: {len(self.offline_vehicles.keys())}")
            for data in self.offline_vehicles.values():
                mean_full_consumption = statistics.mean(
                    data.fuel_history
                ) / len(data.fuel_history)

                print(f"- Vehicle {data.vin}:")
                print(f"  Mean fuel consumption: {mean_full_consumption}")
                print(
                    f"  Known reached destinations: {len(data.reached_destinations)}"
                )
                for coord in data.reached_destinations:
                    print(f"    - {coord}")
            print()
            await asyncio.sleep(0.5)
class SubscriberDashboard:
    # ...
    async def _metrics_app(self):
        async for sample, info in self._metrics_reader.take_async():
            if info.instance_handle not in self._dashboard_data:
                if sample is None:
                    continue
                self._dashboard_data[info.instance_handle] = DashboardItem(
                    sample.vehicle_vin
                )

            instance_data = self._dashboard_data[info.instance_handle]
            instance_data.is_historical = (
                info.state.instance_state != dds.InstanceState.ALIVE
            )

            if not sample or instance_data.is_historical:
                continue

            instance_data.fuel_history.append(sample.fuel_level)

        print("metrics ended")
class SubscriberDashboard:
    # ...
    async def _transit_app(self):
        async for sample, info in self._transit_reader.take_async():
            if info.instance_handle not in self._dashboard_data:
                if sample is None:
                    continue
                self._dashboard_data[info.instance_handle] = DashboardItem(
                    sample.vehicle_vin
                )

            instance_data = self._dashboard_data[info.instance_handle]
            instance_data.is_historical = (
                info.state.instance_state != dds.InstanceState.ALIVE
            )

            if not sample or instance_data.is_historical:
                continue

            if len(sample.current_route):
                # Vehicle is on their way to a destination
                instance_data.current_destination = sample.current_route[-1]
            else:
                # Vehicle has finished its route
                instance_data.current_destination = None
                instance_data.reached_destinations.append(
                    sample.current_position
                )

        print("transit ended")

Find the full file (subscriber.py) in GitHub.

This code will be executed in the main function previously defined, using the DataReaders passed in the simulation initialization. Once the DomainParticipant is closed, the DataReaders will be closed as well.

void SubscriberDashboard::run()
{
    std::mutex mutex;

    dds::sub::cond::ReadCondition metrics_condition(
            metrics_reader_,
            dds::sub::status::DataState::any(),
            [this]() { metrics_app(); });

    dds::sub::cond::ReadCondition transit_condition(
            transit_reader_,
            dds::sub::status::DataState::any(),
            [this]() { transit_app(); });

    dds::core::cond::GuardCondition display_condition;
    display_condition.extensions().handler([this]() { display_app(); });

    std::thread display_thread([&display_condition, &mutex]() {
        for (;;) {
            std::this_thread::sleep_for(std::chrono::milliseconds(500));
            std::lock_guard<std::mutex> lock(mutex);
            display_condition.trigger_value(true);
        }
    });

    waitset.attach_condition(metrics_condition);
    waitset.attach_condition(transit_condition);
    waitset.attach_condition(display_condition);

    for (;;) {
        waitset.dispatch();
        std::lock_guard<std::mutex> lock(mutex);
        display_condition.trigger_value(false);
    }
}

You’ll find one “job” function for each responsibility of the dashboard—that is, for either displaying or subscribing to a topic.

void SubscriberDashboard::display_app()
{
    std::stringstream ss;
    auto now = std::chrono::system_clock::now();
    ss << "[[ DASHBOARD: " << now.time_since_epoch().count() << " ]]\n";
    {
        auto online = online_vehicles();
        ss << "Online vehicles: " << online.size() << "\n";
        for (auto &item : online) {
            ss << "- Vehicle " << item.vin << ":\n";
            ss << "  Fuel updates: " << item.fuel_history.size() << "\n";
            ss << "  Last known destination: "
                << (item.current_destination
                            ? to_string(*item.current_destination)
                            : "None")
                << "\n";
            ss << "  Last known fuel level: " << (item.fuel_history.empty()
                    ? "None"
                    : to_string(item.fuel_history.back()))
                << "\n";
        }
    }
    {
        auto offline = offline_vehicles();
        ss << "Offline vehicles: " << offline.size() << "\n";
        for (auto &item : offline) {
            ss << "- Vehicle " << item.vin << ":\n";
            ss << "  Mean fuel consumption: "
                << std::accumulate(
                            item.fuel_history.begin(),
                            item.fuel_history.end(),
                            0.0)
                            / item.fuel_history.size()
                << "\n";
            ss << "  Known reached destinations: "
                << item.reached_destinations.size() << "\n";
            for (auto &destination : item.reached_destinations) {
                ss << "    - " << to_string(destination) << "\n";
            }
        }
    }

    std::cout << ss.str() << std::endl;
}
void SubscriberDashboard::metrics_app()
{
    for (const auto &sample : metrics_reader_.take()) {
        auto it = dashboard_data_.find(sample.info().instance_handle());
        // If not a tracked vehicle, track it.
        if (it == dashboard_data_.end()) {
            if (!sample.info().valid())
                continue;

            auto new_handle = sample.info().instance_handle();
            auto new_data = DashboardItem { sample.data().vehicle_vin() };
            it = dashboard_data_.emplace(new_handle, new_data).first;
        }

        auto &item = it->second;
        item.is_historical = sample.info().state().instance_state()
                != dds::sub::status::InstanceState::alive();

        if (!sample.info().valid() && item.is_historical) {
            continue;
        }

        item.fuel_history.push_back(sample.data().fuel_level());
    }
}
void SubscriberDashboard::transit_app()
{
    for (const auto &sample : transit_reader_.take()) {
        auto it = dashboard_data_.find(sample.info().instance_handle());
        // If not a tracked vehicle, track it.
        if (it == dashboard_data_.end()) {
            if (!sample.info().valid())
                continue;

            auto new_handle = sample.info().instance_handle();
            auto new_data = DashboardItem { sample.data().vehicle_vin() };
            it = dashboard_data_.emplace(new_handle, new_data).first;
        }

        auto &item = it->second;
        item.is_historical = sample.info().state().instance_state()
                != dds::sub::status::InstanceState::alive();

        if (!sample.info().valid() && item.is_historical) {
            continue;
        }

        auto &current_route = sample.data().current_route();
        if (current_route->size() > 0) {
            item.current_destination = current_route->back();
        } else {
            item.reached_destinations.push_back(*item.current_destination);
            item.current_destination.reset();
            item.completed_routes++;
        }
    }
}

Find the full file (subscriber.cxx) in GitHub; find the full utility file (common.hpp) also in Github.

The metrics DataReader will update the fuel history of each vehicle. From these, you can derive information like the fuel consumption of each vehicle.

The transit DataReader can track the current destination of each vehicle and store completed trips.

Learn More#

You have learned how to define your applications’ entities using System Designer, including which types and QoS will they use. You’ve also learned how to use XML configuration to create and use these entities in your applications.

Next Steps

Full code examples:

  • Example repository - The Git repository which contains the code referenced in this module, as well as build and run instructions.

Reference documentation:

Back to Learn

Was this page helpful?

Back to Learn