diff --git a/examples/connext_dds/remote_procedure_call/py/Inventory.py b/examples/connext_dds/remote_procedure_call/py/Inventory.py index e2f9e9675..cec44ceb2 100644 --- a/examples/connext_dds/remote_procedure_call/py/Inventory.py +++ b/examples/connext_dds/remote_procedure_call/py/Inventory.py @@ -9,6 +9,7 @@ # damages arising out of the use or inability to use the software. # +from abc import ABC from dataclasses import field from typing import Sequence import rti.idl as idl @@ -32,7 +33,7 @@ class UnknownItemError(Exception): @rpc.service -class InventoryService: +class InventoryService(ABC): @rpc.operation def get_inventory(self) -> InventoryContents: """Get the current inventory inventory""" diff --git a/tutorials/application_design/VehicleModeling.xml b/tutorials/application_design/VehicleModeling.xml index 27e87be1b..0e99496c4 100644 --- a/tutorials/application_design/VehicleModeling.xml +++ b/tutorials/application_design/VehicleModeling.xml @@ -1,76 +1,65 @@ - - + - - - + + - - + + + - - - + + + - - + + - - + + - + - 10 + 15 0 - - + + - 15 + 10 0 - + - + TRANSIENT_LOCAL_DURABILITY_QOS - - + + TRANSIENT_LOCAL_DURABILITY_QOS - + + + @@ -80,24 +69,26 @@ - + + - - - - + + + + - - + + - + + - + diff --git a/tutorials/application_design/c++11/CMakeLists.txt b/tutorials/application_design/c++11/CMakeLists.txt new file mode 100644 index 000000000..9c80da8f0 --- /dev/null +++ b/tutorials/application_design/c++11/CMakeLists.txt @@ -0,0 +1,52 @@ +# +# (c) 2024 Copyright, Real-Time Innovations, Inc. All rights reserved. +# +# RTI grants Licensee a license to use, modify, compile, and create derivative +# works of the Software. Licensee has the right to distribute object form +# only for use with RTI products. The Software is provided "as is", with no +# warranty of any type, including any warranty for fitness for any purpose. +# RTI is under no obligation to maintain or support the Software. RTI shall +# not be liable for any incidental or consequential damages arising out of the +# use or inability to use the software. +# + +cmake_minimum_required(VERSION 3.11) +project(connext-tutorial-application-design) +list(APPEND CMAKE_MODULE_PATH + "${CMAKE_CURRENT_SOURCE_DIR}/../../../resources/cmake/Modules" +) +include(ConnextDdsConfigureCmakeUtils) +connextdds_configure_cmake_utils() + +# Include ConnextDdsAddExample.cmake from resources/cmake +include(ConnextDdsAddExample) + +connextdds_call_codegen( + TYPE_PATH "../VehicleModeling.xml" + LANG "C++11" + PREFIX "VehicleModeling" +) + +connextdds_add_application( + TARGET VehicleModeling_publisher + LANG "C++11" + OUTPUT_NAME "publisher" + SOURCES + $ + "${CMAKE_CURRENT_SOURCE_DIR}/publisher.cxx" + DEPENDENCIES + NO_REQUIRE_QOS + ${_CONNEXT_DEPENDENCIES} +) + +connextdds_add_application( + TARGET VehicleModeling_subscriber + LANG "C++11" + OUTPUT_NAME "subscriber" + SOURCES + $ + "${CMAKE_CURRENT_SOURCE_DIR}/subscriber.cxx" + DEPENDENCIES + NO_REQUIRE_QOS + ${_CONNEXT_DEPENDENCIES} +) diff --git a/tutorials/application_design/c++11/README.md b/tutorials/application_design/c++11/README.md new file mode 100644 index 000000000..65414477c --- /dev/null +++ b/tutorials/application_design/c++11/README.md @@ -0,0 +1,48 @@ +# Tutorial: Application Design + +This code is part of the Connext Application Design tutorial. + +## Building the Example :wrench: + +You can build the example following the instructions in the tutorial, or you can +build it using CMake as follows. + +1. Generate the build files: + +```sh +mkdir build +cd build +cmake .. +``` + +This command will try to find the location of your Connext installation. If it +can't find it, specify it with the ``-DCONNEXTDDS_DIR`` option, for example: + +```sh +cmake -DCONNEXTDDS_DIR=/home/rti/rti_connext_dds-x.y.z .. +``` + +If you are compiling for windows you may also need to specify the +[generator](https://cmake.org/cmake/help/latest/manual/cmake-generators.7.html), +and platform. + +```sh +cmake .. -G "Visual Studio 17 2022" -A x64 +``` + +2. Build the applications: + +```sh +cmake --build . +``` + +If you are using a multi-configuration generator, such as Visual Studio +solutions, you can specify the configuration mode to build as follows: + +```sh +cmake --build . --config Release|Debug +``` + +## Running the Example :rocket: + +See the tutorial for instructions. diff --git a/tutorials/application_design/c++11/common.hpp b/tutorials/application_design/c++11/common.hpp new file mode 100644 index 000000000..e244dc00f --- /dev/null +++ b/tutorials/application_design/c++11/common.hpp @@ -0,0 +1,103 @@ +// +// (c) 2024 Copyright, Real-Time Innovations, Inc. All rights reserved. +// +// RTI grants Licensee a license to use, modify, compile, and create derivative +// works of the Software. Licensee has the right to distribute object form +// only for use with RTI products. The Software is provided "as is", with no +// warranty of any type, including any warranty for fitness for any purpose. +// RTI is under no obligation to maintain or support the Software. RTI shall +// not be liable for any incidental or consequential damages arising out of the +// use or inability to use the software. + +#ifndef COMMON_HPP +#define COMMON_HPP + +#include +#include +#include + +#include "VehicleModeling.hpp" + +using CoordSequence = rti::core::bounded_sequence; + +namespace { // Coord namespace + +std::string to_string(const Coord &coord) +{ + std::ostringstream ss; + ss << "Coord(lat: " << coord.lat() << ", lon: " << coord.lon() << ")"; + return ss.str(); +} + +} // namespace + +namespace rti::core { // bounded_sequence namespace + +std::string to_string(const CoordSequence &route) +{ + using ::to_string; + + std::ostringstream ss; + ss << "Route("; + for (auto it = route.begin(); it != route.end();) { + ss << to_string(*it); + if (++it != route.end()) { + ss << ", "; + } + } + ss << ")"; + return ss.str(); +} + +} // namespace rti::core + +namespace utils { + +namespace details { + +static std::random_device rd; +static std::mt19937 gen { rd() }; + +}; // namespace details + +void set_random_seed(unsigned seed) +{ + details::gen.seed(seed); +} + +double random_range(double min, double max) +{ + return std::uniform_real_distribution<>(min, max)(details::gen); +} + +double random_stduniform() +{ + return random_range(0.0, 1.0); +} + +std::string new_vin() +{ + static const std::string choices("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"); + + std::ostringstream ss; + for (int i = 0; i < VIN_LENGTH; ++i) { + ss << choices[random_range(0, choices.size())]; + } + return ss.str(); +} + + +CoordSequence new_route(int n = 5) +{ + CoordSequence route; + for (int i = 0; i < n; ++i) { + double r1 = random_stduniform() * 100.0; + double r2 = random_stduniform() * 100.0; + route.push_back(Coord { r1, r2 }); + } + return route; +} + +} // namespace utils + +#endif diff --git a/tutorials/application_design/c++11/publisher.cxx b/tutorials/application_design/c++11/publisher.cxx new file mode 100644 index 000000000..7555c650e --- /dev/null +++ b/tutorials/application_design/c++11/publisher.cxx @@ -0,0 +1,131 @@ +// +// (c) 2024 Copyright, Real-Time Innovations, Inc. All rights reserved. +// +// RTI grants Licensee a license to use, modify, compile, and create derivative +// works of the Software. Licensee has the right to distribute object form +// only for use with RTI products. The Software is provided "as is", with no +// warranty of any type, including any warranty for fitness for any purpose. +// RTI is under no obligation to maintain or support the Software. RTI shall +// not be liable for any incidental or consequential damages arising out of the +// use or inability to use the software. +// + +#include +#include +#include + +#include +#include "VehicleModeling.hpp" +#include "common.hpp" + +class PublisherSimulation { +public: + explicit PublisherSimulation( + dds::pub::DataWriter metrics_writer, + dds::pub::DataWriter transit_writer) + : metrics_writer_(metrics_writer), + transit_writer_(transit_writer), + vehicle_vin_(utils::new_vin()), + vehicle_fuel_(100.0), + vehicle_route_(utils::new_route()), + vehicle_position_(vehicle_route_[0]) + { + } + + bool has_ended() const + { + return is_out_of_fuel(); + } + + void run(); + + friend std::string to_string(const PublisherSimulation &sim); + +private: + dds::pub::DataWriter metrics_writer_; + dds::pub::DataWriter transit_writer_; + + std::string vehicle_vin_; + double vehicle_fuel_; + CoordSequence vehicle_route_; + Coord vehicle_position_; + + bool is_out_of_fuel() const + { + return vehicle_fuel_ <= 0.0; + } + + bool is_on_standby() const + { + return vehicle_route_.empty(); + } +}; + +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; + } + } +} + +std::string to_string(const PublisherSimulation &sim) +{ + std::ostringstream ss; + ss << "PublisherSimulation(vehicle_vin: " << sim.vehicle_vin_; + ss << ", vehicle_fuel: " << sim.vehicle_fuel_; + ss << ", vehicle_route: " << to_string(sim.vehicle_route_); + ss << ", vehicle_position: " << to_string(sim.vehicle_position_) << ")"; + return ss.str(); +} + +int main(int argc, char **argv) +{ + utils::set_random_seed(std::time(nullptr)); + + rti::domain::register_type(); + rti::domain::register_type(); + + dds::core::QosProvider qos_provider("../VehicleModeling.xml"); + + auto participant = qos_provider.extensions().create_participant_from_config( + "ParticipantLibrary::PublisherApp"); + + using MetricsWriter = dds::pub::DataWriter; + auto metrics_writer = rti::pub::find_datawriter_by_name( + participant, + "Publisher::MetricsWriter"); + + using TransitWriter = dds::pub::DataWriter; + auto transit_writer = rti::pub::find_datawriter_by_name( + participant, + "Publisher::TransitWriter"); + + PublisherSimulation simulation(metrics_writer, transit_writer); + std::cout << "Running simulation " << to_string(simulation) << std::endl; + simulation.run(); +} diff --git a/tutorials/application_design/c++11/subscriber.cxx b/tutorials/application_design/c++11/subscriber.cxx new file mode 100644 index 000000000..900202f09 --- /dev/null +++ b/tutorials/application_design/c++11/subscriber.cxx @@ -0,0 +1,254 @@ +// +// (c) 2024 Copyright, Real-Time Innovations, Inc. All rights reserved. +// +// RTI grants Licensee a license to use, modify, compile, and create derivative +// works of the Software. Licensee has the right to distribute object form +// only for use with RTI products. The Software is provided "as is", with no +// warranty of any type, including any warranty for fitness for any purpose. +// RTI is under no obligation to maintain or support the Software. RTI shall +// not be liable for any incidental or consequential damages arising out of the +// use or inability to use the software. +// + +#include +#include +#include +#include + +#include +#include "VehicleModeling.hpp" +#include "common.hpp" + +struct DashboardItem { + std::string vin; + bool is_historical; + std::vector fuel_history; + int completed_routes; + rti::core::optional current_destination; + std::vector reached_destinations; +}; + +class SubscriberDashboard { +public: + explicit SubscriberDashboard( + dds::sub::DataReader metrics_reader, + dds::sub::DataReader transit_reader) + : metrics_reader_(metrics_reader), transit_reader_(transit_reader) + { + } + + void run(); + + friend std::string to_string(const SubscriberDashboard &dashboard); + +private: + dds::sub::DataReader metrics_reader_; + dds::sub::DataReader transit_reader_; + dds::core::cond::WaitSet waitset; + std::unordered_map + dashboard_data_; + + void display_app(); + void metrics_app(); + void transit_app(); + + std::vector online_vehicles() const + { + std::vector online; + for (const auto &item : dashboard_data_) { + if (!item.second.is_historical) { + online.push_back(item.second); + } + } + return online; + } + + std::vector offline_vehicles() const + { + std::vector offline; + for (const auto &item : dashboard_data_) { + if (item.second.is_historical) { + offline.push_back(item.second); + } + } + return offline; + } +}; + +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 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 lock(mutex); + display_condition.trigger_value(false); + } +} + +void SubscriberDashboard::display_app() +{ + using ::to_string; + using std::to_string; + + 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 ¤t_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++; + } + } +} + +std::string to_string(const SubscriberDashboard &dashboard) +{ + std::ostringstream ss; + ss << "Dashboard()"; + return ss.str(); +} + +int main(int argc, char **argv) +{ + utils::set_random_seed(std::time(nullptr)); + + rti::domain::register_type(); + rti::domain::register_type(); + + dds::core::QosProvider qos_provider("../VehicleModeling.xml"); + + auto participant = qos_provider.extensions().create_participant_from_config( + "ParticipantLibrary::SubscriberApp"); + + using MetricsReader = dds::sub::DataReader; + auto metrics_reader = rti::sub::find_datareader_by_name( + participant, + "Subscriber::MetricsReader"); + + using TransitReader = dds::sub::DataReader; + auto transit_reader = rti::sub::find_datareader_by_name( + participant, + "Subscriber::TransitReader"); + + SubscriberDashboard dashboard(metrics_reader, transit_reader); + std::cout << "Running dashboard " << to_string(dashboard) << std::endl; + dashboard.run(); +} diff --git a/tutorials/application_design/py/README.md b/tutorials/application_design/py/README.md index 62888abc2..383c00aaa 100644 --- a/tutorials/application_design/py/README.md +++ b/tutorials/application_design/py/README.md @@ -1,4 +1,4 @@ -# Tutorial: Data Persistence +# Tutorial: Application Design This code is part of the Connext Application Design tutorial and is included here in full for convenience. diff --git a/tutorials/data_persistence/c++11/USER_QOS_PROFILES.xml b/tutorials/data_persistence/c++11/USER_QOS_PROFILES.xml index 3df796002..dbebb31c4 100644 --- a/tutorials/data_persistence/c++11/USER_QOS_PROFILES.xml +++ b/tutorials/data_persistence/c++11/USER_QOS_PROFILES.xml @@ -14,9 +14,21 @@ BuiltinQosSnippetLib::QosPolicy.Durability.Persistent - BuiltinQosSnippetLib::QosPolicy.History.KeepAll BuiltinQosSnippetLib::QosPolicy.Reliability.Reliable + + + + KEEP_LAST_HISTORY_QOS + 10 + + + + + KEEP_LAST_HISTORY_QOS + 10 + + diff --git a/tutorials/data_persistence/py/USER_QOS_PROFILES.xml b/tutorials/data_persistence/py/USER_QOS_PROFILES.xml index 3df796002..dbebb31c4 100644 --- a/tutorials/data_persistence/py/USER_QOS_PROFILES.xml +++ b/tutorials/data_persistence/py/USER_QOS_PROFILES.xml @@ -14,9 +14,21 @@ BuiltinQosSnippetLib::QosPolicy.Durability.Persistent - BuiltinQosSnippetLib::QosPolicy.History.KeepAll BuiltinQosSnippetLib::QosPolicy.Reliability.Reliable + + + + KEEP_LAST_HISTORY_QOS + 10 + + + + + KEEP_LAST_HISTORY_QOS + 10 + + diff --git a/tutorials/last_value_cache/c++11/USER_QOS_PROFILES.xml b/tutorials/last_value_cache/c++11/USER_QOS_PROFILES.xml index 79068323a..9e2e7cde1 100644 --- a/tutorials/last_value_cache/c++11/USER_QOS_PROFILES.xml +++ b/tutorials/last_value_cache/c++11/USER_QOS_PROFILES.xml @@ -13,22 +13,26 @@ - BuiltinQosLib::Generic.KeepLastReliable.TransientLocal + BuiltinQosSnippetLib::QosPolicy.Durability.TransientLocal + BuiltinQosSnippetLib::QosPolicy.Reliability.Reliable + BuiltinQosSnippetLib::QosPolicy.History.KeepLast_1 - BuiltinQosLib::Generic.KeepLastReliable.TransientLocal + BuiltinQosSnippetLib::QosPolicy.Durability.TransientLocal + BuiltinQosSnippetLib::QosPolicy.Reliability.Reliable - + + KEEP_LAST_HISTORY_QOS 10 + KEEP_LAST_HISTORY_QOS 10 diff --git a/tutorials/last_value_cache/py/USER_QOS_PROFILES.xml b/tutorials/last_value_cache/py/USER_QOS_PROFILES.xml index 79068323a..9e2e7cde1 100644 --- a/tutorials/last_value_cache/py/USER_QOS_PROFILES.xml +++ b/tutorials/last_value_cache/py/USER_QOS_PROFILES.xml @@ -13,22 +13,26 @@ - BuiltinQosLib::Generic.KeepLastReliable.TransientLocal + BuiltinQosSnippetLib::QosPolicy.Durability.TransientLocal + BuiltinQosSnippetLib::QosPolicy.Reliability.Reliable + BuiltinQosSnippetLib::QosPolicy.History.KeepLast_1 - BuiltinQosLib::Generic.KeepLastReliable.TransientLocal + BuiltinQosSnippetLib::QosPolicy.Durability.TransientLocal + BuiltinQosSnippetLib::QosPolicy.Reliability.Reliable - + + KEEP_LAST_HISTORY_QOS 10 + KEEP_LAST_HISTORY_QOS 10