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 ede6ed9c..5c07df27 100644 --- a/include/utils/config.hpp +++ b/include/utils/config.hpp @@ -103,12 +103,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. @@ -144,11 +149,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 /// @@ -196,11 +219,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 660150e6..9c059b1d 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 @@ -703,25 +704,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; } @@ -807,21 +847,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); } @@ -1170,36 +1212,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 3c1c4a47..91784c3c 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/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) { 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