From 83af30008c8778e52f96e4f5bd2f78bc773c3d67 Mon Sep 17 00:00:00 2001 From: barsnick Date: Mon, 28 Oct 2024 09:17:56 +0100 Subject: [PATCH 1/2] Fix: MQTTAbstractionImpl: flush/sync MQTT Connect message immediately (#208) * Fix: MQTTAbstractionImpl: flush/sync MQTT Connect message immediately Previously, a socket was opened, but the MQTT Connect command message was not flushed to the broker until init() was finished. This could be critical if a module's init() phase waits for some reason, as the MQTT broker's default socket timeout might disconnect the client. This behavior was observed with the mosquitto broker, where unregistered sockets were consistently disconnected after 94 seconds. Insert an additional mqtt_sync() directly after mqtt_connect(), while the mqtt_sync() thread is not yet started. Co-authored-by: Fabian Hartung Signed-off-by: Moritz Barsnick * Integrate early returns This commit integrates early returns in the mqtt_abstraction_impl.cpp file. This is done to improve the readability of the code and to reduce the nesting of the code. Signed-off-by: Fabian Hartung --------- Signed-off-by: Moritz Barsnick Signed-off-by: Fabian Hartung Co-authored-by: Fabian Hartung --- lib/mqtt_abstraction_impl.cpp | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/lib/mqtt_abstraction_impl.cpp b/lib/mqtt_abstraction_impl.cpp index 52f36d0c..c04bc04e 100644 --- a/lib/mqtt_abstraction_impl.cpp +++ b/lib/mqtt_abstraction_impl.cpp @@ -484,13 +484,18 @@ bool MQTTAbstractionImpl::connectBroker(std::string& socket_path) { uint8_t connect_flags = MQTT_CONNECT_CLEAN_SESSION; /* Send connection request to the broker. */ if (mqtt_connect(&this->mqtt_client, nullptr, nullptr, nullptr, 0, nullptr, nullptr, connect_flags, - mqtt_keep_alive) == MQTT_OK) { - // TODO(kai): async? - on_mqtt_connect(); - return true; + mqtt_keep_alive) != MQTT_OK) { + return false; + } + // TODO(kai): async? + const auto error = mqtt_sync(&this->mqtt_client); + if (error != MQTT_OK) { + EVLOG_error << fmt::format("Error during MQTT sync: {}", mqtt_error_str(error)); + return false; } - return false; + on_mqtt_connect(); + return true; } bool MQTTAbstractionImpl::connectBroker(const char* host, const char* port) { @@ -521,13 +526,18 @@ bool MQTTAbstractionImpl::connectBroker(const char* host, const char* port) { uint8_t connect_flags = MQTT_CONNECT_CLEAN_SESSION; /* Send connection request to the broker. */ if (mqtt_connect(&this->mqtt_client, nullptr, nullptr, nullptr, 0, nullptr, nullptr, connect_flags, - mqtt_keep_alive) == MQTT_OK) { - // TODO(kai): async? - on_mqtt_connect(); - return true; + mqtt_keep_alive) != MQTT_OK) { + return false; + } + // TODO(kai): async? + const auto error = mqtt_sync(&this->mqtt_client); + if (error != MQTT_OK) { + EVLOG_error << fmt::format("Error during MQTT sync: {}", mqtt_error_str(error)); + return false; } - return false; + on_mqtt_connect(); + return true; } int MQTTAbstractionImpl::open_nb_socket(const char* addr, const char* port) { From bb3d3a91bb50031d21aa3d43220801a9eb69a6bd Mon Sep 17 00:00:00 2001 From: Kai Hermann Date: Wed, 30 Oct 2024 13:53:50 +0100 Subject: [PATCH 2/2] Expose mappings of requirements, provides and modules to modules (#206) * Add stream operators for (optional) Mapping for easier debugging * Add module mapping to module info * Bump version to 0.18 * Make more mapping related variables&functions const * Add resolve_requirements that is used as a basis for refactored get_requirements and get_fulfillments * Remove code that isn't used * Introduce RequirementInitializer and RequirementInitialization These can be used in (generated) user code to initialize requirements with their fulfillments and optional mappings * Turn operator< of Requirement into free function * Remove constructors of Requirement * More const auto(&) usage * Fix everestrs build after removal of Requirement constructor * Restructure mappings in config schema. Now all mappings are defined under a "mapping" key that contains a "module" and/or "implementations" key which in turn can have mappings for individual implementations defined --------- Signed-off-by: Kai-Uwe Hermann --- CMakeLists.txt | 2 +- everestpy/src/everest/misc.hpp | 7 +- everestrs/everestrs/src/everestrs_sys.cpp | 4 +- include/framework/ModuleAdapter.hpp | 2 + include/framework/everest.hpp | 12 ++ include/framework/runtime.hpp | 11 +- include/utils/config.hpp | 31 ++++- include/utils/types.hpp | 82 +++++++++---- lib/config.cpp | 136 +++++++++++++++------- lib/everest.cpp | 29 ++++- lib/runtime.cpp | 17 ++- lib/types.cpp | 11 +- schemas/config.yaml | 40 ++++--- 13 files changed, 269 insertions(+), 115 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7ec86648..ed99ffca 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.14) project(everest-framework - VERSION 0.17.2 + VERSION 0.18.0 DESCRIPTION "The open operating system for e-mobility charging stations" LANGUAGES CXX C ) diff --git a/everestpy/src/everest/misc.hpp b/everestpy/src/everest/misc.hpp index aad3d2d7..366505b2 100644 --- a/everestpy/src/everest/misc.hpp +++ b/everestpy/src/everest/misc.hpp @@ -6,6 +6,7 @@ #include #include +#include class RuntimeSession { public: @@ -28,12 +29,6 @@ class RuntimeSession { static std::unique_ptr create_config_instance(std::shared_ptr rs); }; -struct Fulfillment { - std::string module_id; - std::string implementation_id; - Requirement requirement; -}; - struct Interface { std::vector variables; std::vector commands; diff --git a/everestrs/everestrs/src/everestrs_sys.cpp b/everestrs/everestrs/src/everestrs_sys.cpp index 08e7a65f..931a85fe 100644 --- a/everestrs/everestrs/src/everestrs_sys.cpp +++ b/everestrs/everestrs/src/everestrs_sys.cpp @@ -108,14 +108,14 @@ void Module::provide_command(const Runtime& rt, rust::String implementation_id, void Module::subscribe_variable(const Runtime& rt, rust::String implementation_id, size_t index, rust::String name) const { - const Requirement req(std::string(implementation_id), index); + const auto req = Requirement{std::string(implementation_id), index}; handle_->subscribe_var(req, std::string(name), [&rt, implementation_id, index, name](json args) { rt.handle_variable(implementation_id, index, name, json2blob(args)); }); } JsonBlob Module::call_command(rust::Str implementation_id, size_t index, rust::Str name, JsonBlob blob) const { - const Requirement req(std::string(implementation_id), index); + const auto req = Requirement{std::string(implementation_id), index}; json return_value = handle_->call_cmd(req, std::string(name), json::parse(blob.data.begin(), blob.data.end())); return json2blob(return_value); diff --git a/include/framework/ModuleAdapter.hpp b/include/framework/ModuleAdapter.hpp index b91c4c8c..e925a799 100644 --- a/include/framework/ModuleAdapter.hpp +++ b/include/framework/ModuleAdapter.hpp @@ -92,6 +92,7 @@ struct ModuleAdapter { using ExtMqttSubscribeFunc = std::function; using TelemetryPublishFunc = std::function; + using GetMappingFunc = std::function()>; CallFunc call; PublishFunc publish; @@ -107,6 +108,7 @@ struct ModuleAdapter { ExtMqttSubscribeFunc ext_mqtt_subscribe; std::vector registered_commands; TelemetryPublishFunc telemetry_publish; + GetMappingFunc get_mapping; void check_complete() { // FIXME (aw): I should throw if some of my handlers are not set diff --git a/include/framework/everest.hpp b/include/framework/everest.hpp index b0f7252f..d9af9fa1 100644 --- a/include/framework/everest.hpp +++ b/include/framework/everest.hpp @@ -142,6 +142,11 @@ class Everest { /// \returns true if telemetry is enabled bool is_telemetry_enabled(); + /// + /// \returns the 3 tier model mappings for this module + /// + std::optional get_3_tier_model_mapping(); + /// /// \brief Chccks if all commands of a module that are listed in its manifest are available /// @@ -239,6 +244,13 @@ class Everest { /// void subscribe_global_all_errors(const error::ErrorCallback& callback, const error::ErrorCallback& clear_callback); }; + +/// +/// \returns the 3 tier model mapping from a \p module_tier_mapping for the given \p impl_id +/// +std::optional get_impl_mapping(std::optional module_tier_mappings, + const std::string& impl_id); + } // namespace Everest #endif // FRAMEWORK_EVEREST_HPP diff --git a/include/framework/runtime.hpp b/include/framework/runtime.hpp index 918a9cd9..b0bb595f 100644 --- a/include/framework/runtime.hpp +++ b/include/framework/runtime.hpp @@ -134,16 +134,17 @@ void populate_module_info_path_from_runtime_settings(ModuleInfo&, std::shared_pt struct ModuleCallbacks { std::function register_module_adapter; - std::function(const json& connections)> everest_register; + std::function(const RequirementInitialization& requirement_init)> everest_register; std::function init; std::function ready; ModuleCallbacks() = default; - ModuleCallbacks(const std::function& register_module_adapter, - const std::function(const json& connections)>& everest_register, - const std::function& init, - const std::function& ready); + ModuleCallbacks( + const std::function& register_module_adapter, + const std::function(const RequirementInitialization& requirement_init)>& everest_register, + const std::function& init, + const std::function& ready); }; struct VersionInformation { diff --git a/include/utils/config.hpp b/include/utils/config.hpp index e5d0202e..7140af41 100644 --- a/include/utils/config.hpp +++ b/include/utils/config.hpp @@ -105,12 +105,17 @@ class Config { /// /// \brief Parses the 3 tier model mappings in the config + /// A "mapping" can be specified in the following way: /// You can set a EVSE id called "evse" and Connector id called "connector" for the whole module. - /// Additionally a "mapping" can be specified in the following way: + /// Alternatively you can set individual mappings for implementations. /// mapping: - /// implementation_id: + /// module: /// evse: 1 /// connector: 1 + /// implementations: + /// implementation_id: + /// evse: 1 + /// connector: 1 /// If no mappings are found it will be assumed that the module is mapped to the charging station. /// If only a module mapping is defined alle implementations are mapped to this module mapping. /// Implementations can have overwritten mappings. @@ -146,11 +151,29 @@ class Config { /// \returns a json object that contains the requirement json resolve_requirement(const std::string& module_id, const std::string& requirement_id) const; + /// + /// \brief resolves all Requirements of the given \p module_id to their Fulfillments + /// + /// \returns a map indexed by Requirements + std::map resolve_requirements(const std::string& module_id) const; + /// /// \returns a list of Requirements for \p module_id /// std::list get_requirements(const std::string& module_id) const; + /// + /// \brief A Fulfillment is a combination of a Requirement and the module and implementation ids where this is + /// implemented + /// \returns a map of Fulfillments for \p module_id + std::map> get_fulfillments(const std::string& module_id) const; + + /// + /// \brief A RequirementInitialization contains everything needed to initialize a requirement in user code. This + /// includes the Requirement, its Fulfillment and an optional Mapping + /// \returns a RequirementInitialization + RequirementInitialization get_requirement_initialization(const std::string& module_id) const; + /// /// \brief checks if the config contains the given \p module_id /// @@ -198,11 +221,11 @@ class Config { // /// \returns the 3 tier model mappings for the given \p module_id - std::optional get_3_tier_model_mappings(const std::string& module_id); + std::optional get_module_3_tier_model_mappings(const std::string& module_id) const; // /// \returns the 3 tier model mapping for the given \p module_id and \p impl_id - std::optional get_3_tier_model_mapping(const std::string& module_id, const std::string& impl_id); + std::optional get_3_tier_model_mapping(const std::string& module_id, const std::string& impl_id) const; /// /// \brief turns then given \p module_id into a printable identifier diff --git a/include/utils/types.hpp b/include/utils/types.hpp index e3d2afc4..08f24e47 100644 --- a/include/utils/types.hpp +++ b/include/utils/types.hpp @@ -64,6 +64,50 @@ enum class QOS { QOS2 ///< Exactly once delivery }; +/// \brief A Mapping that can be used to map a module or implementation to a specific EVSE or optionally to a Connector +struct Mapping { + int evse; ///< The EVSE id + std::optional connector; ///< An optional Connector id + + Mapping(int evse) : evse(evse) { + } + + Mapping(int evse, int connector) : evse(evse), connector(connector) { + } +}; + +/// \brief Writes the string representation of the given Mapping \p mapping to the given output stream \p os +/// \returns an output stream with the Mapping written to +inline std::ostream& operator<<(std::ostream& os, const Mapping& mapping) { + os << "Mapping(evse: " << mapping.evse; + if (mapping.connector.has_value()) { + os << ", connector: " << mapping.connector.value(); + } + os << ")"; + + return os; +} + +/// \brief Writes the string representation of the given Mapping \p mapping to the given output stream \p os +/// \returns an output stream with the Mapping written to +inline std::ostream& operator<<(std::ostream& os, const std::optional& mapping) { + if (mapping.has_value()) { + os << mapping.value(); + } else { + os << "Mapping(charging station)"; + } + + return os; +} + +/// \brief A 3 tier mapping for a module and its individual implementations +struct ModuleTierMappings { + std::optional module; ///< Mapping of the whole module to an EVSE id and optional Connector id. If this is + ///< absent the module is assumed to be mapped to the whole charging station + std::unordered_map> + implementations; ///< Mappings for the individual implementations of the module +}; + struct ModuleInfo { struct Paths { std::filesystem::path etc; @@ -78,38 +122,36 @@ struct ModuleInfo { Paths paths; bool telemetry_enabled; bool global_errors_enabled; + std::optional mapping; }; struct TelemetryConfig { int id; }; -/// \brief A Mapping that can be used to map a module or implementation to a specific EVSE or optionally to a Connector -struct Mapping { - int evse; ///< The EVSE id - std::optional connector; ///< An optional Connector id +struct Requirement { + std::string id; + size_t index = 0; +}; - Mapping(int evse) : evse(evse) { - } +bool operator<(const Requirement& lhs, const Requirement& rhs); - Mapping(int evse, int connector) : evse(evse), connector(connector) { - } +/// \brief A Fulfillment relates a Requirement to its connected implementation, identified via its module and +/// implementation id. +struct Fulfillment { + std::string module_id; + std::string implementation_id; + Requirement requirement; }; -/// \brief A 3 tier mapping for a module and its individual implementations -struct ModuleTierMappings { - std::optional module; ///< Mapping of the whole module to an EVSE id and optional Connector id. If this is - ///< absent the module is assumed to be mapped to the whole charging station - std::unordered_map> - implementations; ///< Mappings for the individual implementations of the module +/// \brief Contains everything that's needed to initialize a requirement in user code +struct RequirementInitializer { + Requirement requirement; + Fulfillment fulfillment; + std::optional mapping; }; -struct Requirement { - Requirement(const std::string& requirement_id_, size_t index_); - bool operator<(const Requirement& rhs) const; - std::string id; - size_t index; -}; +using RequirementInitialization = std::map>; struct ImplementationIdentifier { ImplementationIdentifier(const std::string& module_id_, const std::string& implementation_id_, diff --git a/lib/config.cpp b/lib/config.cpp index b455d662..cf51d169 100644 --- a/lib/config.cpp +++ b/lib/config.cpp @@ -1,6 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 -// Copyright 2020 - 2023 Pionix GmbH and Contributors to EVerest +// Copyright Pionix GmbH and Contributors to EVerest #include +#include #include #include #include @@ -705,25 +706,64 @@ json Config::resolve_requirement(const std::string& module_id, const std::string return module_config["connections"][requirement_id]; } -std::list Config::get_requirements(const std::string& module_id) const { - BOOST_LOG_FUNCTION(); +std::map Config::resolve_requirements(const std::string& module_id) const { + std::map requirements; - std::list res; - - std::string module_name = get_module_name(module_id); - for (const std::string& req_id : Config::keys(this->manifests.at(module_name).at("requires"))) { - json resolved_req = this->resolve_requirement(module_id, req_id); + const auto& module_name = get_module_name(module_id); + for (const auto& req_id : Config::keys(this->manifests.at(module_name).at("requires"))) { + const auto& resolved_req = this->resolve_requirement(module_id, req_id); if (!resolved_req.is_array()) { - Requirement req(req_id, 0); - res.push_back(req); + const auto& resolved_module_id = resolved_req.at("module_id"); + const auto& resolved_impl_id = resolved_req.at("implementation_id"); + const auto req = Requirement{req_id, 0}; + requirements[req] = {resolved_module_id, resolved_impl_id, req}; } else { - for (int i = 0; i < resolved_req.size(); i++) { - Requirement req(req_id, i); - res.push_back(req); + for (std::size_t i = 0; i < resolved_req.size(); i++) { + const auto& resolved_module_id = resolved_req.at(i).at("module_id"); + const auto& resolved_impl_id = resolved_req.at(i).at("implementation_id"); + const auto req = Requirement{req_id, i}; + requirements[req] = {resolved_module_id, resolved_impl_id, req}; } } } + return requirements; +} + +std::list Config::get_requirements(const std::string& module_id) const { + BOOST_LOG_FUNCTION(); + + std::list res; + + for (const auto& [requirement, fulfillment] : this->resolve_requirements(module_id)) { + res.push_back(requirement); + } + + return res; +} + +std::map> Config::get_fulfillments(const std::string& module_id) const { + BOOST_LOG_FUNCTION(); + + std::map> res; + + for (const auto& [requirement, fulfillment] : this->resolve_requirements(module_id)) { + res[requirement.id].push_back(fulfillment); + } + + return res; +} + +RequirementInitialization Config::get_requirement_initialization(const std::string& module_id) const { + BOOST_LOG_FUNCTION(); + + RequirementInitialization res; + + for (const auto& [requirement, fulfillment] : this->resolve_requirements(module_id)) { + const auto& mapping = this->get_3_tier_model_mapping(fulfillment.module_id, fulfillment.implementation_id); + res[requirement.id].push_back({requirement, fulfillment, mapping}); + } + return res; } @@ -809,21 +849,23 @@ std::unordered_map Config::get_3_tier_model_map return this->tier_mappings; } -std::optional Config::get_3_tier_model_mappings(const std::string& module_id) { +std::optional Config::get_module_3_tier_model_mappings(const std::string& module_id) const { if (this->tier_mappings.find(module_id) == this->tier_mappings.end()) { return std::nullopt; } return this->tier_mappings.at(module_id); } -std::optional Config::get_3_tier_model_mapping(const std::string& module_id, const std::string& impl_id) { - auto module_tier_mappings = this->get_3_tier_model_mappings(module_id); +std::optional Config::get_3_tier_model_mapping(const std::string& module_id, + const std::string& impl_id) const { + const auto module_tier_mappings = this->get_module_3_tier_model_mappings(module_id); if (not module_tier_mappings.has_value()) { return std::nullopt; } - auto& mapping = module_tier_mappings.value(); + const auto& mapping = module_tier_mappings.value(); if (mapping.implementations.find(impl_id) == mapping.implementations.end()) { - return std::nullopt; + // if no specific implementation mapping is given, use the module mapping + return mapping.module; } return mapping.implementations.at(impl_id); } @@ -1172,36 +1214,44 @@ void Config::resolve_all_requirements() { void Config::parse_3_tier_model_mapping() { for (auto& element : this->main.items()) { const auto& module_id = element.key(); - auto impl_info = this->extract_implementation_info(module_id); - auto provides = this->manifests.at(impl_info.at("module_name")).at("provides"); + const auto& impl_info = this->extract_implementation_info(module_id); + const auto& provides = this->manifests.at(impl_info.at("module_name")).at("provides"); ModuleTierMappings module_tier_mappings; - auto& module_config = element.value(); - if (module_config.contains("evse")) { - auto mapping = Mapping(module_config.at("evse").get()); - if (module_config.contains("connector")) { - mapping.connector = module_config.at("connector").get(); - } - module_tier_mappings.module = mapping; - } - auto& mapping = module_config.at("mapping"); + const auto& module_config = element.value(); + const auto& config_mapping = module_config.at("mapping"); // an empty mapping means it is mapped to the charging station and gets no specific mapping attached - if (not mapping.empty()) { - for (auto& tier_mapping : mapping.items()) { - auto impl_id = tier_mapping.key(); - auto tier_mapping_value = tier_mapping.value(); - if (provides.contains(impl_id)) { - if (tier_mapping_value.contains("connector")) { - module_tier_mappings.implementations[impl_id] = Mapping( - tier_mapping_value.at("evse").get(), tier_mapping_value.at("connector").get()); + if (not config_mapping.empty()) { + if (config_mapping.contains("module")) { + const auto& module_mapping = config_mapping.at("module"); + if (module_mapping.contains("evse")) { + auto mapping = Mapping(module_mapping.at("evse").get()); + if (module_mapping.contains("connector")) { + mapping.connector = module_mapping.at("connector").get(); + } + module_tier_mappings.module = mapping; + } + } + + if (config_mapping.contains("implementations")) { + const auto& implementations_mapping = config_mapping.at("implementations"); + for (auto& tier_mapping : implementations_mapping.items()) { + const auto& impl_id = tier_mapping.key(); + const auto& tier_mapping_value = tier_mapping.value(); + if (provides.contains(impl_id)) { + if (tier_mapping_value.contains("connector")) { + module_tier_mappings.implementations[impl_id] = + Mapping(tier_mapping_value.at("evse").get(), + tier_mapping_value.at("connector").get()); + } else { + module_tier_mappings.implementations[impl_id] = + Mapping(tier_mapping_value.at("evse").get()); + } } else { - module_tier_mappings.implementations[impl_id] = - Mapping(tier_mapping_value.at("evse").get()); + EVLOG_warning << fmt::format("Mapping {} of module {} in config refers to a provides that does " + "not exist, please fix this", + impl_id, printable_identifier(module_id)); } - } else { - EVLOG_warning << fmt::format( - "Mapping {} of module {} in config refers to a provides that does not exist, please fix this", - impl_id, printable_identifier(module_id)); } } } diff --git a/lib/everest.cpp b/lib/everest.cpp index 8d870dc6..f72545c7 100644 --- a/lib/everest.cpp +++ b/lib/everest.cpp @@ -81,7 +81,7 @@ Everest::Everest(std::string module_id_, const Config& config_, bool validate_da this->global_error_state_monitor = nullptr; } - this->module_tier_mappings = config.get_3_tier_model_mappings(this->module_id); + this->module_tier_mappings = config.get_module_3_tier_model_mappings(this->module_id); // setup error_managers, error_state_monitors, error_factories and error_databases for all implementations for (const std::string& impl : Config::keys(this->module_manifest.at("provides"))) { @@ -112,14 +112,14 @@ Everest::Everest(std::string module_id_, const Config& config_, bool validate_da std::optional mapping; if (this->module_tier_mappings.has_value()) { - auto& module_tier_mapping = this->module_tier_mappings.value(); + const auto& module_tier_mapping = this->module_tier_mappings.value(); // start with the module mapping and overwrite it (partially) with the implementation mapping mapping = module_tier_mapping.module; - auto impl_mapping = config.get_3_tier_model_mapping(this->module_id, impl); + const auto impl_mapping = config.get_3_tier_model_mapping(this->module_id, impl); if (impl_mapping.has_value()) { if (mapping.has_value()) { auto& mapping_value = mapping.value(); - auto& impl_mapping_value = impl_mapping.value(); + const auto& impl_mapping_value = impl_mapping.value(); if (mapping_value.evse != impl_mapping_value.evse) { EVLOG_warning << fmt::format("Mapping value mismatch. {} ({}) evse ({}) != {} mapping evse " "({}). Setting evse={}, please fix this in the config.", @@ -132,8 +132,8 @@ Everest::Everest(std::string module_id_, const Config& config_, bool validate_da mapping_value.connector = impl_mapping_value.connector; } if (mapping_value.connector.has_value() and impl_mapping_value.connector.has_value()) { - auto& mapping_value_connector_value = mapping_value.connector.value(); - auto& impl_mapping_value_connector_value = impl_mapping_value.connector.value(); + const auto& mapping_value_connector_value = mapping_value.connector.value(); + const auto& impl_mapping_value_connector_value = impl_mapping_value.connector.value(); if (mapping_value_connector_value != impl_mapping_value_connector_value) { EVLOG_warning << fmt::format("Mapping value mismatch. {} ({}) connector ({}) != {} mapping connector " @@ -252,6 +252,10 @@ void Everest::register_on_ready_handler(const std::function& handler) { this->on_ready = std::make_unique>(handler); } +std::optional Everest::get_3_tier_model_mapping() { + return this->module_tier_mappings; +} + void Everest::check_code() { BOOST_LOG_FUNCTION(); @@ -1076,4 +1080,17 @@ bool Everest::check_arg(ArgumentType arg_types, json manifest_arg) { } return true; } + +std::optional get_impl_mapping(std::optional module_tier_mappings, + const std::string& impl_id) { + if (not module_tier_mappings.has_value()) { + return std::nullopt; + } + auto& mapping = module_tier_mappings.value(); + if (mapping.implementations.find(impl_id) == mapping.implementations.end()) { + // if no specific implementation mapping is given, use the module mapping + return mapping.module; + } + return mapping.implementations.at(impl_id); +} } // namespace Everest diff --git a/lib/runtime.cpp b/lib/runtime.cpp index ee6ccbff..89b2dfce 100644 --- a/lib/runtime.cpp +++ b/lib/runtime.cpp @@ -383,10 +383,11 @@ RuntimeSettings::RuntimeSettings(const std::string& prefix_, const std::string& } } -ModuleCallbacks::ModuleCallbacks(const std::function& register_module_adapter, - const std::function(const json& connections)>& everest_register, - const std::function& init, - const std::function& ready) : +ModuleCallbacks::ModuleCallbacks( + const std::function& register_module_adapter, + const std::function(const RequirementInitialization& requirement_init)>& everest_register, + const std::function& init, + const std::function& ready) : register_module_adapter(register_module_adapter), everest_register(everest_register), init(init), ready(ready) { } @@ -495,11 +496,13 @@ int ModuleLoader::initialize() { return everest.telemetry_publish(category, subcategory, type, telemetry); }; + module_adapter.get_mapping = [&everest]() { return everest.get_3_tier_model_mapping(); }; + this->callbacks.register_module_adapter(module_adapter); // FIXME (aw): would be nice to move this config related thing toward the module_init function std::vector cmds = - this->callbacks.everest_register(config.get_main_config()[this->module_id]["connections"]); + this->callbacks.everest_register(config.get_requirement_initialization(this->module_id)); for (auto const& command : cmds) { everest.provide_cmd(command); @@ -509,6 +512,10 @@ int ModuleLoader::initialize() { auto module_info = config.get_module_info(this->module_id); populate_module_info_path_from_runtime_settings(module_info, rs); module_info.telemetry_enabled = everest.is_telemetry_enabled(); + auto module_mappings = everest.get_3_tier_model_mapping(); + if (module_mappings.has_value()) { + module_info.mapping = module_mappings.value().module; + } this->callbacks.init(module_configs, module_info); diff --git a/lib/types.cpp b/lib/types.cpp index a764eded..f4644667 100644 --- a/lib/types.cpp +++ b/lib/types.cpp @@ -13,14 +13,11 @@ TypedHandler::TypedHandler(HandlerType type_, std::shared_ptr handler_) TypedHandler("", "", type_, handler_) { } -Requirement::Requirement(const std::string& requirement_id_, size_t index_) : id(requirement_id_), index(index_) { -} - -bool Requirement::operator<(const Requirement& rhs) const { - if (id < rhs.id) { +bool operator<(const Requirement& lhs, const Requirement& rhs) { + if (lhs.id < rhs.id) { return true; - } else if (id == rhs.id) { - return (index < rhs.index); + } else if (lhs.id == rhs.id) { + return (lhs.index < rhs.index); } else { return false; } diff --git a/schemas/config.yaml b/schemas/config.yaml index 1fbd0549..eb020779 100644 --- a/schemas/config.yaml +++ b/schemas/config.yaml @@ -147,32 +147,40 @@ properties: default: {} # don't allow arbitrary additional properties additionalProperties: false - evse: - description: evse this module id maps to - type: integer - connector: - description: connector this module id maps to - type: integer mapping: description: >- - this configures a list of implementations this module provides and their mapping to the 3-tier-model of charging station, evse and connector + this configures a module mapping and a list of implementations this module provides and their mapping to the 3-tier-model of charging station, evse and connector if no mapping is provided by default this implementation is associated with the charging station type: object - patternProperties: - # implementation id - ^[a-zA-Z_][a-zA-Z0-9_.-]*$: + properties: + module: type: object - required: - - evse properties: evse: - description: evse this implementation id maps to + description: evse this module id maps to type: integer connector: - description: connector this implementation id maps to + description: connector this module id maps to type: integer - # don't allow arbitrary additional properties - additionalProperties: false + # allow arbitrary additional properties for future extensions (not used at the moment) + additionalProperties: true + implementations: + type: object + patternProperties: + # implementation id + ^[a-zA-Z_][a-zA-Z0-9_.-]*$: + type: object + required: + - evse + properties: + evse: + description: evse this implementation id maps to + type: integer + connector: + description: connector this implementation id maps to + type: integer + # allow arbitrary additional properties for future extensions (not used at the moment) + additionalProperties: true # add empty config if not already present default: {} # don't allow arbitrary additional properties