Skip to content

Commit

Permalink
Add 3 tier model mapping support (#198)
Browse files Browse the repository at this point in the history
* Add 3 tier model mapping support

Add module level evse and connector mappings

Add 3 tier model mappings to errors

---------

Signed-off-by: Kai-Uwe Hermann <[email protected]>
  • Loading branch information
hikinggrass authored Jul 25, 2024
1 parent 929ceab commit dadfc93
Show file tree
Hide file tree
Showing 14 changed files with 250 additions and 19 deletions.
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.15.2
VERSION 0.16.0
DESCRIPTION "The open operating system for e-mobility charging stations"
LANGUAGES CXX C
)
Expand Down
5 changes: 3 additions & 2 deletions everestpy/src/everest/everestpy.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,10 @@ PYBIND11_MODULE(everestpy, m) {
.export_values();

py::class_<ImplementationIdentifier>(error_submodule, "ImplementationIdentifier")
.def(py::init<const std::string&, const std::string&>())
.def(py::init<const std::string&, const std::string&, std::optional<Mapping>>())
.def_readwrite("module_id", &ImplementationIdentifier::module_id)
.def_readwrite("implementation_id", &ImplementationIdentifier::implementation_id);
.def_readwrite("implementation_id", &ImplementationIdentifier::implementation_id)
.def_readwrite("mapping", &ImplementationIdentifier::mapping);

py::class_<Everest::error::UUID>(error_submodule, "UUID")
.def(py::init<>())
Expand Down
20 changes: 18 additions & 2 deletions everestpy/src/everest/framework/error/__init__.pyi
Original file line number Diff line number Diff line change
@@ -1,20 +1,36 @@
from enum import Enum
from typing import Callable, overload
from typing import Callable, overload, Optional

class Severity(Enum):
Low = "Low",
Medium = "Medium",
High = "High"

class Mapping:
@overload
def __init__(self, evse: int) -> None ...

@overload
def __init__(self, evse: int, connector: int) -> None ...

@property
def evse(self) -> int ...

@property
def connector(self) -> Optional[int] ...

class ImplementationIdentifier:
def __init__(self, module_id: str, implementation_id: str) -> None: ...
def __init__(self, module_id: str, implementation_id: str, mapping: Optional[Mapping]) -> None: ...

@property
def module_id(self) -> str: ...

@property
def implementation_id(self) -> str: ...

@property
def mapping(self) -> Optional[Mapping]: ...

class UUID:
@overload
def __init__(self) -> None: ...
Expand Down
1 change: 1 addition & 0 deletions include/framework/everest.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,7 @@ class Everest {
std::string telemetry_prefix;
std::optional<TelemetryConfig> telemetry_config;
bool telemetry_enabled;
std::optional<ModuleTierMappings> module_tier_mappings;

void handle_ready(json data);

Expand Down
26 changes: 26 additions & 0 deletions include/utils/config.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ class Config {
json errors;
schemas _schemas;

std::unordered_map<std::string, ModuleTierMappings> tier_mappings;
std::unordered_map<std::string, std::optional<TelemetryConfig>> telemetry_configs;

///
Expand Down Expand Up @@ -102,6 +103,19 @@ class Config {
json extract_implementation_info(const std::string& module_id, const std::string& impl_id) const;
void resolve_all_requirements();

///
/// \brief Parses the 3 tier model mappings in the config
/// 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:
/// mapping:
/// 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.
void parse_3_tier_model_mapping();

// experimental caches
std::unordered_map<std::string, std::string> module_names;
std::unordered_map<std::string, ConfigCache> module_config_cache;
Expand Down Expand Up @@ -178,6 +192,18 @@ class Config {
/// \returns a json object that contains the interface definition
json get_interface_definition(const std::string& interface_name);

///
/// \returns the 3 tier model mappings
std::unordered_map<std::string, ModuleTierMappings> get_3_tier_model_mappings();

//
/// \returns the 3 tier model mappings for the given \p module_id
std::optional<ModuleTierMappings> get_3_tier_model_mappings(const std::string& module_id);

//
/// \returns the 3 tier model mapping for the given \p module_id and \p impl_id
std::optional<Mapping> get_3_tier_model_mapping(const std::string& module_id, const std::string& impl_id);

///
/// \brief turns then given \p module_id into a printable identifier
///
Expand Down
11 changes: 6 additions & 5 deletions include/utils/error.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,12 @@
#define UTILS_ERROR_DEFAULTS_DESCRIPTION "no description provided"
#define UTILS_ERROR_DEFAULTS_MESSAGE "no message provided"
#define UTILS_ERROR_DEFAULTS_SEVERITY Everest::error::Severity::Low
#define UTILS_ERROR_DEFAULTS_ORIGIN ImplementationIdentifier("no-module-id-provided", "no-implementation-id-provided")
#define UTILS_ERROR_DEFAULTS_TIMESTAMP date::utc_clock::now()
#define UTILS_ERROR_DEFAULTS_UUID UUID()
#define UTILS_ERROR_DEFAULTS_STATE Everest::error::State::Active
#define UTILS_ERROR_DEFAULTS_VENDOR_ID "everest"
#define UTILS_ERROR_DEFAULTS_ORIGIN \
ImplementationIdentifier("no-module-id-provided", "no-implementation-id-provided", std::nullopt)
#define UTILS_ERROR_DEFAULTS_TIMESTAMP date::utc_clock::now()
#define UTILS_ERROR_DEFAULTS_UUID UUID()
#define UTILS_ERROR_DEFAULTS_STATE Everest::error::State::Active
#define UTILS_ERROR_DEFAULTS_VENDOR_ID "everest"

namespace Everest {
namespace error {
Expand Down
9 changes: 8 additions & 1 deletion include/utils/error/error_json.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,20 @@ template <> struct adl_serializer<Everest::error::Error> {
{"state", Everest::error::state_to_string(e.state)},
{"sub_type", e.sub_type},
{"vendor_id", e.vendor_id}};
if (e.origin.mapping.has_value()) {
j["origin"]["mapping"] = e.origin.mapping.value();
}
}
static Everest::error::Error from_json(const json& j) {
Everest::error::ErrorType type = j.at("type");
std::string message = j.at("message");
std::string description = j.at("description");
std::optional<Mapping> mapping;
if (j.at("origin").contains("mapping")) {
mapping = j.at("origin").at("mapping");
}
ImplementationIdentifier origin =
ImplementationIdentifier(j.at("origin").at("module_id"), j.at("origin").at("implementation_id"));
ImplementationIdentifier(j.at("origin").at("module_id"), j.at("origin").at("implementation_id"), mapping);
Everest::error::Severity severity = Everest::error::string_to_severity(j.at("severity"));
Everest::error::Error::time_point timestamp = Everest::Date::from_rfc3339(j.at("timestamp"));
Everest::error::UUID uuid(j.at("uuid"));
Expand Down
42 changes: 41 additions & 1 deletion include/utils/types.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,26 @@ 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<int> connector; ///< An optional Connector id

Mapping(int evse) : evse(evse) {
}

Mapping(int evse, int connector) : evse(evse), connector(connector) {
}
};

/// \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
///< absent the module is assumed to be mapped to the whole charging station
std::unordered_map<std::string, std::optional<Mapping>>
implementations; ///< Mappings for the individual implementations of the module
};

struct Requirement {
Requirement(const std::string& requirement_id_, size_t index_);
bool operator<(const Requirement& rhs) const;
Expand All @@ -92,14 +112,34 @@ struct Requirement {
};

struct ImplementationIdentifier {
ImplementationIdentifier(const std::string& module_id_, const std::string& implementation_id_);
ImplementationIdentifier(const std::string& module_id_, const std::string& implementation_id_,
std::optional<Mapping> mapping_ = std::nullopt);
std::string to_string() const;
std::string module_id;
std::string implementation_id;
std::optional<Mapping> mapping;
};
bool operator==(const ImplementationIdentifier& lhs, const ImplementationIdentifier& rhs);
bool operator!=(const ImplementationIdentifier& lhs, const ImplementationIdentifier& rhs);

NLOHMANN_JSON_NAMESPACE_BEGIN
template <> struct adl_serializer<Mapping> {
static void to_json(json& j, const Mapping& m) {
j = {{"evse", m.evse}};
if (m.connector.has_value()) {
j["connector"] = m.connector.value();
}
}
static Mapping from_json(const json& j) {
auto m = Mapping(j.at("evse").get<int>());
if (j.contains("connector")) {
m.connector = j.at("connector").get<int>();
}
return m;
}
};
NLOHMANN_JSON_NAMESPACE_END

#define EVCALLBACK(function) [](auto&& PH1) { function(std::forward<decltype(PH1)>(PH1)); }

#endif // UTILS_TYPES_HPP
65 changes: 65 additions & 0 deletions lib/config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -548,6 +548,7 @@ Config::Config(std::shared_ptr<RuntimeSettings> rs, bool manager) : rs(rs), mana
}

resolve_all_requirements();
parse_3_tier_model_mapping();
}

error::ErrorTypeMap Config::get_error_map() const {
Expand Down Expand Up @@ -804,6 +805,29 @@ json Config::get_interface_definition(const std::string& interface_name) {
return this->interface_definitions.value(interface_name, json());
}

std::unordered_map<std::string, ModuleTierMappings> Config::get_3_tier_model_mappings() {
return this->tier_mappings;
}

std::optional<ModuleTierMappings> Config::get_3_tier_model_mappings(const std::string& module_id) {
if (this->tier_mappings.find(module_id) == this->tier_mappings.end()) {
return std::nullopt;
}
return this->tier_mappings.at(module_id);
}

std::optional<Mapping> 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);
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()) {
return std::nullopt;
}
return mapping.implementations.at(impl_id);
}

json Config::load_schema(const fs::path& path) {
BOOST_LOG_FUNCTION();

Expand Down Expand Up @@ -1144,4 +1168,45 @@ void Config::resolve_all_requirements() {
}
EVLOG_debug << "All module requirements resolved successfully...";
}

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");

ModuleTierMappings module_tier_mappings;
auto& module_config = element.value();
if (module_config.contains("evse")) {
auto mapping = Mapping(module_config.at("evse").get<int>());
if (module_config.contains("connector")) {
mapping.connector = module_config.at("connector").get<int>();
}
module_tier_mappings.module = mapping;
}
auto& 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<int>(), tier_mapping_value.at("connector").get<int>());
} else {
module_tier_mappings.implementations[impl_id] =
Mapping(tier_mapping_value.at("evse").get<int>());
}
} 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));
}
}
}
this->tier_mappings[module_id] = module_tier_mappings;
}
}

} // namespace Everest
4 changes: 2 additions & 2 deletions lib/error/error.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,8 @@ Error::Error(const ErrorType& type_, const ErrorSubType& sub_type_, const std::s
Error::Error(const ErrorType& type_, const ErrorSubType& sub_type_, const std::string& message_,
const std::string& description_, const std::string& origin_module_,
const std::string& origin_implementation_, const Severity& severity_) :
Error(type_, sub_type_, message_, description_, ImplementationIdentifier(origin_module_, origin_implementation_),
severity_) {
Error(type_, sub_type_, message_, description_,
ImplementationIdentifier(origin_module_, origin_implementation_, std::nullopt), severity_) {
}

Error::Error() :
Expand Down
3 changes: 2 additions & 1 deletion lib/error/error_filter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,8 @@ OriginFilter ErrorFilter::get_origin_filter() const {
EVLOG_error << "Filter type is not OriginFilter. Defaulting to "
"'OriginFilter::ImplementationIdentifier(\"no-module-id-provided\", "
"\"no-implementation-id-provided\")'.";
return OriginFilter(ImplementationIdentifier("no-module-id-provided", "no-implementation-id-provided"));
return OriginFilter(
ImplementationIdentifier("no-module-id-provided", "no-implementation-id-provided", std::nullopt));
}
return std::get<OriginFilter>(filter);
}
Expand Down
45 changes: 44 additions & 1 deletion lib/everest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ 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);

// 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"))) {
// setup shared database
Expand Down Expand Up @@ -108,8 +110,49 @@ Everest::Everest(std::string module_id_, const Config& config_, bool validate_da
// setup error state monitor
this->impl_error_state_monitors[impl] = std::make_shared<error::ErrorStateMonitor>(error_database);

std::optional<Mapping> mapping;
if (this->module_tier_mappings.has_value()) {
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);
if (impl_mapping.has_value()) {
if (mapping.has_value()) {
auto& mapping_value = mapping.value();
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.",
this->module_id, this->module_name, mapping_value.evse, impl,
impl_mapping_value.evse, impl_mapping_value.evse);
mapping_value.evse = impl_mapping_value.evse;
}

if (not mapping_value.connector.has_value() and impl_mapping_value.connector.has_value()) {
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();
if (mapping_value_connector_value != impl_mapping_value_connector_value) {
EVLOG_warning
<< fmt::format("Mapping value mismatch. {} ({}) connector ({}) != {} mapping connector "
"({}). Setting connector={}, please fix this in the config.",
this->module_id, this->module_name, mapping_value_connector_value, impl,
impl_mapping_value_connector_value, impl_mapping_value_connector_value);
}
mapping_value.connector = impl_mapping_value_connector_value;
}

} else {
EVLOG_info << "No module mapping, so using impl mapping here";
mapping = impl_mapping;
}
}
}

// setup error factory
ImplementationIdentifier default_origin(this->module_id, impl);
ImplementationIdentifier default_origin(this->module_id, impl, mapping);
this->error_factories[impl] = std::make_shared<error::ErrorFactory>(
std::make_shared<error::ErrorTypeMap>(this->config.get_error_map()), default_origin);
}
Expand Down
6 changes: 3 additions & 3 deletions lib/types.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@ bool Requirement::operator<(const Requirement& rhs) const {
}
}

ImplementationIdentifier::ImplementationIdentifier(const std::string& module_id_,
const std::string& implementation_id_) :
module_id(module_id_), implementation_id(implementation_id_) {
ImplementationIdentifier::ImplementationIdentifier(const std::string& module_id_, const std::string& implementation_id_,
std::optional<Mapping> mapping_) :
module_id(module_id_), implementation_id(implementation_id_), mapping(mapping_) {
}

std::string ImplementationIdentifier::to_string() const {
Expand Down
Loading

0 comments on commit dadfc93

Please sign in to comment.