Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expose mappings of requirements, provides and modules to modules #206

Merged
merged 25 commits into from
Oct 30, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
630c48c
WIP: make mappings available outside of errors
hikinggrass Sep 27, 2024
0105d03
Add stream operators for (optional) Mapping for easier debugging
hikinggrass Oct 6, 2024
b67b7b1
Add module mapping to module info
hikinggrass Oct 6, 2024
9dd6f7f
Bump version to 0.18
hikinggrass Oct 6, 2024
ad32fd1
Merge remote-tracking branch 'origin/main' into feature/expose-mappings
hikinggrass Oct 15, 2024
557932c
clang-format
hikinggrass Oct 15, 2024
9edf536
Rename RequirementConnection to Fulfillment
hikinggrass Oct 16, 2024
e90e60f
clang-format
hikinggrass Oct 16, 2024
7d0b0e4
Remove get_imple_mapping from ModuleAdapter
hikinggrass Oct 17, 2024
acac5bd
Move mapping member from Requirement to Fulfilment
hikinggrass Oct 17, 2024
9877ce6
Rename function so it doesn't share a name with another one returning…
hikinggrass Oct 17, 2024
b130124
Make more mapping related variables&functions const
hikinggrass Oct 17, 2024
44baf44
Add reworded comment
hikinggrass Oct 17, 2024
501f01f
clang-format
hikinggrass Oct 17, 2024
8c3feca
Add resolve_requirements that is used as a basis for refactored get_r…
hikinggrass Oct 18, 2024
61b9281
Remove code that isn't used
hikinggrass Oct 18, 2024
5d542d3
Introduce RequirementInitializer and RequirementInitialization
hikinggrass Oct 21, 2024
26473f7
Remove redundant mapping from Fulfillment
hikinggrass Oct 21, 2024
000efd5
Turn operator< of Requirement into free function
hikinggrass Oct 21, 2024
cd20aab
Remove constructors of Requirement
hikinggrass Oct 21, 2024
6084d34
Remove ugly void cast
hikinggrass Oct 21, 2024
59644d3
More const auto(&) usage
hikinggrass Oct 21, 2024
2f45d45
Fix everestrs build after removal of Requirement constructor
hikinggrass Oct 21, 2024
aa53f89
Merge branch 'main' into feature/expose-mappings
hikinggrass Oct 29, 2024
87758ef
Restructure mappings in config schema.
hikinggrass Oct 29, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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
)
Expand Down
4 changes: 4 additions & 0 deletions include/framework/ModuleAdapter.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@ struct ModuleAdapter {
using ExtMqttSubscribeFunc = std::function<UnsubscribeToken(const std::string&, StringHandler)>;
using TelemetryPublishFunc =
std::function<void(const std::string&, const std::string&, const std::string&, const TelemetryMap&)>;
using GetMappingFunc = std::function<std::optional<ModuleTierMappings>()>;
using GetImplMappingFunc = std::function<std::optional<Mapping>(const std::string&)>;

CallFunc call;
PublishFunc publish;
Expand All @@ -107,6 +109,8 @@ struct ModuleAdapter {
ExtMqttSubscribeFunc ext_mqtt_subscribe;
std::vector<cmd> registered_commands;
TelemetryPublishFunc telemetry_publish;
GetMappingFunc get_mapping;
GetImplMappingFunc get_impl_mapping;
hikinggrass marked this conversation as resolved.
Show resolved Hide resolved

void check_complete() {
// FIXME (aw): I should throw if some of my handlers are not set
Expand Down
10 changes: 10 additions & 0 deletions include/framework/everest.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,16 @@ class Everest {
/// \returns true if telemetry is enabled
bool is_telemetry_enabled();

///
/// \returns the 3 tier model mappings for this module
///
std::optional<ModuleTierMappings> get_3_tier_model_mapping();

///
/// \returns the 3 tier model mapping for the given \p impl_id
///
std::optional<Mapping> get_3_tier_model_mapping(const std::string& impl_id);
hikinggrass marked this conversation as resolved.
Show resolved Hide resolved

///
/// \brief Chccks if all commands of a module that are listed in its manifest are available
///
Expand Down
8 changes: 6 additions & 2 deletions include/framework/runtime.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -134,14 +134,18 @@ void populate_module_info_path_from_runtime_settings(ModuleInfo&, std::shared_pt

struct ModuleCallbacks {
std::function<void(ModuleAdapter module_adapter)> register_module_adapter;
std::function<std::vector<cmd>(const json& connections)> everest_register;
std::function<std::vector<cmd>(
const std::map<std::string, std::vector<RequirementConnection>>& requirement_connections)>
everest_register;
std::function<void(ModuleConfigs module_configs, const ModuleInfo& info)> init;
std::function<void()> ready;

ModuleCallbacks() = default;

ModuleCallbacks(const std::function<void(ModuleAdapter module_adapter)>& register_module_adapter,
const std::function<std::vector<cmd>(const json& connections)>& everest_register,
const std::function<std::vector<cmd>(
const std::map<std::string, std::vector<RequirementConnection>>& requirement_connections)>&
everest_register,
const std::function<void(ModuleConfigs module_configs, const ModuleInfo& info)>& init,
const std::function<void()>& ready);
};
Expand Down
4 changes: 2 additions & 2 deletions include/utils/config.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -147,9 +147,9 @@ class Config {
json resolve_requirement(const std::string& module_id, const std::string& requirement_id) const;

///
/// \returns a list of Requirements for \p module_id
/// \returns a map of RequirementConnections for \p module_id
///
std::list<Requirement> get_requirements(const std::string& module_id) const;
std::map<std::string, std::vector<RequirementConnection>> get_requirement_connections(const std::string& module_id);

///
/// \brief checks if the config contains the given \p module_id
Expand Down
76 changes: 54 additions & 22 deletions include/utils/types.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -64,26 +64,6 @@ enum class QOS {
QOS2 ///< Exactly once delivery
};

struct ModuleInfo {
struct Paths {
std::filesystem::path etc;
std::filesystem::path libexec;
std::filesystem::path share;
};

std::string name;
std::vector<std::string> authors;
std::string license;
std::string id;
Paths paths;
bool telemetry_enabled;
bool global_errors_enabled;
};

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
Expand All @@ -96,6 +76,30 @@ struct Mapping {
}
};

/// \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>& mapping) {
hikinggrass marked this conversation as resolved.
Show resolved Hide resolved
if (mapping.has_value()) {
os << mapping.value();
} else {
os << "Mapping(charging station)";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this default representation make any sense?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The feature (at the moment at least) is inspired by the 3-tier mappings from OCPP. So if you have no explicit mapping it's assumed that the module maps to the whole charging station, getting more specific by a mapping to an evse and/or connector

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see. Still I'm unsure, whether the string Mapping(charging station) on a text console will help users to understand what is meant.

}

return os;
}

/// \brief A 3 tier mapping for a module and its individual implementations
struct ModuleTierMappings {
std::optional<Mapping> module; ///< Mapping of the whole module to an EVSE id and optional Connector id. If this is
Expand All @@ -104,11 +108,39 @@ struct ModuleTierMappings {
implementations; ///< Mappings for the individual implementations of the module
};

struct ModuleInfo {
struct Paths {
std::filesystem::path etc;
std::filesystem::path libexec;
std::filesystem::path share;
};

std::string name;
std::vector<std::string> authors;
std::string license;
std::string id;
Paths paths;
bool telemetry_enabled;
bool global_errors_enabled;
std::optional<Mapping> mapping;
};

struct TelemetryConfig {
int id;
};

struct Requirement {
Requirement(const std::string& requirement_id_, size_t index_);
Requirement() = default;
Requirement(const std::string& requirement_id_, size_t index_, std::optional<Mapping> mapping = std::nullopt);
bool operator<(const Requirement& rhs) const;
std::string id;
size_t index;
size_t index = 0;
std::optional<Mapping> mapping;
hikinggrass marked this conversation as resolved.
Show resolved Hide resolved
};
hikinggrass marked this conversation as resolved.
Show resolved Hide resolved

struct RequirementConnection {
hikinggrass marked this conversation as resolved.
Show resolved Hide resolved
Requirement requirement;
std::string requirement_module_id;
};

struct ImplementationIdentifier {
Expand Down
24 changes: 17 additions & 7 deletions lib/config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -705,23 +705,32 @@ json Config::resolve_requirement(const std::string& module_id, const std::string
return module_config["connections"][requirement_id];
}

std::list<Requirement> Config::get_requirements(const std::string& module_id) const {
std::map<std::string, std::vector<RequirementConnection>>
Config::get_requirement_connections(const std::string& module_id) {
BOOST_LOG_FUNCTION();

std::list<Requirement> res;
std::map<std::string, std::vector<RequirementConnection>> 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"))) {
std::vector<RequirementConnection> requirement_connections;
json resolved_req = this->resolve_requirement(module_id, req_id);
if (!resolved_req.is_array()) {
Requirement req(req_id, 0);
res.push_back(req);
auto resolved_module_id = resolved_req.at("module_id");
auto resolved_impl_id = resolved_req.at("implementation_id");
const auto mapping = this->get_3_tier_model_mapping(resolved_module_id, resolved_impl_id);
Requirement req(req_id, 0, mapping);
requirement_connections.push_back({req, resolved_module_id});
} else {
for (int i = 0; i < resolved_req.size(); i++) {
Requirement req(req_id, i);
res.push_back(req);
auto resolved_module_id = resolved_req.at(i).at("module_id");
auto resolved_impl_id = resolved_req.at(i).at("implementation_id");
const auto mapping = this->get_3_tier_model_mapping(resolved_module_id, resolved_impl_id);
Requirement req(req_id, i, mapping);
requirement_connections.push_back({req, resolved_module_id});
}
}
res[req_id] = requirement_connections;
}

return res;
Expand Down Expand Up @@ -823,7 +832,8 @@ std::optional<Mapping> Config::get_3_tier_model_mapping(const std::string& modul
}
auto& mapping = module_tier_mappings.value();
hikinggrass marked this conversation as resolved.
Show resolved Hide resolved
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);
Comment on lines 866 to 870
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This pattern happens quite often. It is usually better to use the iterator instead of doing the lookup twice:

const auto mapping_it = ...find(impl_id);
if (mapping_id == std::end(...)) {
  return default;
}
return *mapping_it;

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess something like this would be quite readable:

const auto& mappings = module_tier_mappings.value();
const auto mapping_it = mappings.implementations.find(impl_id);
if (mapping_it == mappings.implementations.end()) {
    return mappings.module;
}
const auto& [key, mapping] = (*mapping_it);
return mapping;

The alternative doesn't quite feel as readable:

...
return (*mapping_it).second;

Or what do you think?

}
Expand Down
63 changes: 41 additions & 22 deletions lib/everest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -158,30 +158,33 @@ Everest::Everest(std::string module_id_, const Config& config_, bool validate_da
}

// setup error_databases, error_managers and error_state_monitors for all requirements
for (const Requirement& req : config.get_requirements(module_id)) {
// setup shared database
std::shared_ptr<error::ErrorDatabaseMap> error_database = std::make_shared<error::ErrorDatabaseMap>();

// setup error manager
std::string interface_name = this->module_manifest.at("requires").at(req.id).at("interface");
json interface_def = this->config.get_interface_definition(interface_name);
std::list<std::string> allowed_error_types;
for (const auto& error_namespace_it : interface_def["errors"].items()) {
for (const auto& error_name_it : error_namespace_it.value().items()) {
allowed_error_types.push_back(error_namespace_it.key() + "/" + error_name_it.key());
for (const auto& requirement_connections : config.get_requirement_connections(module_id)) {
for (const RequirementConnection& req_con : requirement_connections.second) {
auto& req = req_con.requirement;
// setup shared database
std::shared_ptr<error::ErrorDatabaseMap> error_database = std::make_shared<error::ErrorDatabaseMap>();

// setup error manager
std::string interface_name = this->module_manifest.at("requires").at(req.id).at("interface");
json interface_def = this->config.get_interface_definition(interface_name);
std::list<std::string> allowed_error_types;
for (const auto& error_namespace_it : interface_def["errors"].items()) {
for (const auto& error_name_it : error_namespace_it.value().items()) {
allowed_error_types.push_back(error_namespace_it.key() + "/" + error_name_it.key());
}
}
error::ErrorManagerReq::SubscribeErrorFunc subscribe_error_func =
[this, req](const error::ErrorType& type, const error::ErrorCallback& callback,
const error::ErrorCallback& clear_callback) {
this->subscribe_error(req, type, callback, clear_callback);
};
this->req_error_managers[req] = std::make_shared<error::ErrorManagerReq>(
std::make_shared<error::ErrorTypeMap>(this->config.get_error_map()), error_database,
allowed_error_types, subscribe_error_func);

// setup error state monitor
this->req_error_state_monitors[req] = std::make_shared<error::ErrorStateMonitor>(error_database);
}
error::ErrorManagerReq::SubscribeErrorFunc subscribe_error_func =
[this, req](const error::ErrorType& type, const error::ErrorCallback& callback,
const error::ErrorCallback& clear_callback) {
this->subscribe_error(req, type, callback, clear_callback);
};
this->req_error_managers[req] = std::make_shared<error::ErrorManagerReq>(
std::make_shared<error::ErrorTypeMap>(this->config.get_error_map()), error_database, allowed_error_types,
subscribe_error_func);

// setup error state monitor
this->req_error_state_monitors[req] = std::make_shared<error::ErrorStateMonitor>(error_database);
}

// register handler for global ready signal
Expand Down Expand Up @@ -252,6 +255,22 @@ void Everest::register_on_ready_handler(const std::function<void()>& handler) {
this->on_ready = std::make_unique<std::function<void()>>(handler);
}

std::optional<ModuleTierMappings> Everest::get_3_tier_model_mapping() {
return this->module_tier_mappings;
}

std::optional<Mapping> Everest::get_3_tier_model_mapping(const std::string& impl_id) {
if (not this->module_tier_mappings.has_value()) {
return std::nullopt;
}
auto& mapping = this->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);
hikinggrass marked this conversation as resolved.
Show resolved Hide resolved
}

void Everest::check_code() {
BOOST_LOG_FUNCTION();

Expand Down
23 changes: 17 additions & 6 deletions lib/runtime.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -383,10 +383,12 @@ RuntimeSettings::RuntimeSettings(const std::string& prefix_, const std::string&
}
}

ModuleCallbacks::ModuleCallbacks(const std::function<void(ModuleAdapter module_adapter)>& register_module_adapter,
const std::function<std::vector<cmd>(const json& connections)>& everest_register,
const std::function<void(ModuleConfigs module_configs, const ModuleInfo& info)>& init,
const std::function<void()>& ready) :
ModuleCallbacks::ModuleCallbacks(
const std::function<void(ModuleAdapter module_adapter)>& register_module_adapter,
const std::function<std::vector<cmd>(
const std::map<std::string, std::vector<RequirementConnection>>& requirement_connections)>& everest_register,
const std::function<void(ModuleConfigs module_configs, const ModuleInfo& info)>& init,
const std::function<void()>& ready) :
register_module_adapter(register_module_adapter), everest_register(everest_register), init(init), ready(ready) {
}

Expand Down Expand Up @@ -495,11 +497,16 @@ int ModuleLoader::initialize() {
return everest.telemetry_publish(category, subcategory, type, telemetry);
};

module_adapter.get_mapping = [&everest]() { return everest.get_3_tier_model_mapping(); };

module_adapter.get_impl_mapping = [&everest](const std::string& impl_id) {
return everest.get_3_tier_model_mapping(impl_id);
};

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<cmd> cmds =
this->callbacks.everest_register(config.get_main_config()[this->module_id]["connections"]);
std::vector<cmd> cmds = this->callbacks.everest_register(config.get_requirement_connections(this->module_id));

for (auto const& command : cmds) {
everest.provide_cmd(command);
Expand All @@ -509,6 +516,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);

Expand Down
3 changes: 2 additions & 1 deletion lib/types.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ TypedHandler::TypedHandler(HandlerType type_, std::shared_ptr<Handler> handler_)
TypedHandler("", "", type_, handler_) {
}

Requirement::Requirement(const std::string& requirement_id_, size_t index_) : id(requirement_id_), index(index_) {
Requirement::Requirement(const std::string& requirement_id_, size_t index_, std::optional<Mapping> mapping) :
id(requirement_id_), index(index_), mapping(mapping) {
}

bool Requirement::operator<(const Requirement& rhs) const {
Expand Down
Loading