Add extracted tools: CitrineOS, OpenOCPP, ShapeShifter
- CitrineOS core extracted (CSMS OCPP 2.0.1) - OpenOCPP extracted (firmware OCPP 1.6J/2.0.1) - ShapeShifter library installed (pip install -e) - ShapeShifter specification extracted - EVerest extracted TODO updated with progress
This commit is contained in:
112
tools/EVerest-main/lib/everest/framework/lib/CMakeLists.txt
Normal file
112
tools/EVerest-main/lib/everest/framework/lib/CMakeLists.txt
Normal file
@@ -0,0 +1,112 @@
|
||||
set(INCLUDE_DIR "${PROJECT_SOURCE_DIR}/include")
|
||||
|
||||
# EVerest framework
|
||||
# by default build as static
|
||||
set(DEFAULT_LINKAGE SHARED)
|
||||
# FIXME (aw): the following does not play well with CPM, because it get set by other dependencies ...
|
||||
#if (DEFINED BUILD_SHARED_LIBS AND NOT BUILD_SHARED_LIBS)
|
||||
# message(FATAL_ERROR "everest-framework library does not support static build yet")
|
||||
#endif()
|
||||
add_library(framework ${DEFAULT_LINKAGE})
|
||||
add_library(everest::framework ALIAS framework)
|
||||
|
||||
set_target_properties(framework PROPERTIES SOVERSION ${PROJECT_VERSION})
|
||||
|
||||
target_sources(framework
|
||||
PRIVATE
|
||||
config.cpp
|
||||
config/mqtt_settings.cpp
|
||||
config/settings.cpp
|
||||
config/sqlite_storage.cpp
|
||||
config/storage_types.cpp
|
||||
config/userconfig_storage.cpp
|
||||
config/types.cpp
|
||||
config_cache.cpp
|
||||
config_service.cpp
|
||||
conversions.cpp
|
||||
error/error.cpp
|
||||
error/error_database_map.cpp
|
||||
error/error_filter.cpp
|
||||
error/error_manager_impl.cpp
|
||||
error/error_manager_req.cpp
|
||||
error/error_manager_req_global.cpp
|
||||
error/error_type_map.cpp
|
||||
error/error_state_monitor.cpp
|
||||
error/error_factory.cpp
|
||||
everest.cpp
|
||||
formatter.cpp
|
||||
filesystem.cpp
|
||||
module_adapter.cpp
|
||||
module_config.cpp
|
||||
mqtt_abstraction_impl.cpp
|
||||
thread.cpp
|
||||
types.cpp
|
||||
serial.cpp
|
||||
status_fifo.cpp
|
||||
date.cpp
|
||||
runtime.cpp
|
||||
message_handler.cpp
|
||||
)
|
||||
|
||||
# FIXME (aw): c++17 doesn't need necessarily to be public, but our
|
||||
# headers are messed up
|
||||
target_compile_features(framework PUBLIC cxx_std_17)
|
||||
|
||||
target_compile_options(framework PRIVATE ${COMPILER_WARNING_OPTIONS})
|
||||
|
||||
target_include_directories(framework
|
||||
PUBLIC
|
||||
$<BUILD_INTERFACE:${EVEREST_FRAMEWORK_GENERATED_INC_DIR}>
|
||||
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>
|
||||
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/everest>
|
||||
)
|
||||
|
||||
if(EVEREST_FRAMEWORK_THREAD_POOL_SCALING_POLICY STREQUAL "custom" AND
|
||||
EVEREST_FRAMEWORK_THREAD_POOL_SCALING_POLICY_CUSTOM_INCLUDE_DIR)
|
||||
target_include_directories(framework
|
||||
PUBLIC
|
||||
$<BUILD_INTERFACE:${EVEREST_FRAMEWORK_THREAD_POOL_SCALING_POLICY_CUSTOM_INCLUDE_DIR}>
|
||||
)
|
||||
endif()
|
||||
|
||||
if(Boost_VERSION_STRING VERSION_LESS "1.69.0")
|
||||
set(EVEREST_FRAMEWORK_BOOST_SYSTEM_LINK_LIBRARY Boost::system)
|
||||
endif()
|
||||
|
||||
target_link_libraries(framework
|
||||
PUBLIC
|
||||
date::date-tz
|
||||
nlohmann_json::nlohmann_json
|
||||
nlohmann_json_schema_validator
|
||||
fmt::fmt
|
||||
|
||||
everest::log
|
||||
everest::sqlite
|
||||
everest::yaml
|
||||
$<BUILD_INTERFACE:everest_helpers>
|
||||
$<INSTALL_INTERFACE:everest::helpers>
|
||||
$<BUILD_INTERFACE:everest_util>
|
||||
$<INSTALL_INTERFACE:everest::util>
|
||||
${STD_FILESYSTEM_COMPAT_LIB}
|
||||
PRIVATE
|
||||
${EVEREST_FRAMEWORK_BOOST_SYSTEM_LINK_LIBRARY}
|
||||
Boost::thread
|
||||
Boost::program_options
|
||||
everest::io
|
||||
${ATOMIC_LIBS}
|
||||
)
|
||||
|
||||
collect_migration_files(
|
||||
LOCATION ${PROJECT_SOURCE_DIR}/schemas/migrations
|
||||
INSTALL_DESTINATION "${CMAKE_INSTALL_DATADIR}/everest/migrations"
|
||||
)
|
||||
|
||||
target_compile_definitions(framework
|
||||
PRIVATE
|
||||
TARGET_MIGRATION_FILE_VERSION=${TARGET_MIGRATION_FILE_VERSION}
|
||||
)
|
||||
|
||||
set_target_properties(framework
|
||||
PROPERTIES
|
||||
POSITION_INDEPENDENT_CODE ON
|
||||
)
|
||||
1612
tools/EVerest-main/lib/everest/framework/lib/config.cpp
Normal file
1612
tools/EVerest-main/lib/everest/framework/lib/config.cpp
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,45 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
#include <utils/config/mqtt_settings.hpp>
|
||||
|
||||
namespace Everest {
|
||||
|
||||
bool MQTTSettings::uses_socket() const {
|
||||
if (not broker_socket_path.empty()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
MQTTSettings create_mqtt_settings(const std::string& mqtt_broker_socket_path, const std::string& mqtt_everest_prefix,
|
||||
const std::string& mqtt_external_prefix) {
|
||||
MQTTSettings mqtt_settings;
|
||||
populate_mqtt_settings(mqtt_settings, mqtt_broker_socket_path, mqtt_everest_prefix, mqtt_external_prefix);
|
||||
return mqtt_settings;
|
||||
}
|
||||
|
||||
MQTTSettings create_mqtt_settings(const std::string& mqtt_broker_host, std::uint16_t mqtt_broker_port,
|
||||
const std::string& mqtt_everest_prefix, const std::string& mqtt_external_prefix) {
|
||||
MQTTSettings mqtt_settings;
|
||||
populate_mqtt_settings(mqtt_settings, mqtt_broker_host, mqtt_broker_port, mqtt_everest_prefix,
|
||||
mqtt_external_prefix);
|
||||
return mqtt_settings;
|
||||
}
|
||||
|
||||
void populate_mqtt_settings(MQTTSettings& mqtt_settings, const std::string& mqtt_broker_socket_path,
|
||||
const std::string& mqtt_everest_prefix, const std::string& mqtt_external_prefix) {
|
||||
mqtt_settings.broker_socket_path = mqtt_broker_socket_path;
|
||||
mqtt_settings.everest_prefix = mqtt_everest_prefix;
|
||||
mqtt_settings.external_prefix = mqtt_external_prefix;
|
||||
}
|
||||
|
||||
void populate_mqtt_settings(MQTTSettings& mqtt_settings, const std::string& mqtt_broker_host,
|
||||
std::uint16_t mqtt_broker_port, const std::string& mqtt_everest_prefix,
|
||||
const std::string& mqtt_external_prefix) {
|
||||
mqtt_settings.broker_host = mqtt_broker_host;
|
||||
mqtt_settings.broker_port = mqtt_broker_port;
|
||||
mqtt_settings.everest_prefix = mqtt_everest_prefix;
|
||||
mqtt_settings.external_prefix = mqtt_external_prefix;
|
||||
}
|
||||
|
||||
} // namespace Everest
|
||||
@@ -0,0 +1,64 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include <utils/config/settings.hpp>
|
||||
|
||||
namespace Everest {
|
||||
|
||||
RuntimeSettings create_runtime_settings(const fs::path& prefix, const fs::path& etc_dir, const fs::path& data_dir,
|
||||
const fs::path& modules_dir, const fs::path& logging_config_file,
|
||||
const std::string& telemetry_prefix, bool telemetry_enabled,
|
||||
bool validate_schema, bool forward_exceptions) {
|
||||
RuntimeSettings runtime_settings;
|
||||
runtime_settings.prefix = prefix;
|
||||
runtime_settings.etc_dir = etc_dir;
|
||||
runtime_settings.data_dir = data_dir;
|
||||
runtime_settings.modules_dir = modules_dir;
|
||||
runtime_settings.logging_config_file = logging_config_file;
|
||||
runtime_settings.telemetry_prefix = telemetry_prefix;
|
||||
runtime_settings.telemetry_enabled = telemetry_enabled;
|
||||
runtime_settings.validate_schema = validate_schema;
|
||||
runtime_settings.forward_exceptions = forward_exceptions;
|
||||
return runtime_settings;
|
||||
}
|
||||
|
||||
void populate_runtime_settings(RuntimeSettings& runtime_settings, const fs::path& prefix, const fs::path& etc_dir,
|
||||
const fs::path& data_dir, const fs::path& modules_dir,
|
||||
const fs::path& logging_config_file, const std::string& telemetry_prefix,
|
||||
bool telemetry_enabled, bool validate_schema, bool forward_exceptions) {
|
||||
runtime_settings.prefix = prefix;
|
||||
runtime_settings.etc_dir = etc_dir;
|
||||
runtime_settings.data_dir = data_dir;
|
||||
runtime_settings.modules_dir = modules_dir;
|
||||
runtime_settings.logging_config_file = logging_config_file;
|
||||
runtime_settings.telemetry_prefix = telemetry_prefix;
|
||||
runtime_settings.telemetry_enabled = telemetry_enabled;
|
||||
runtime_settings.validate_schema = validate_schema;
|
||||
runtime_settings.forward_exceptions = forward_exceptions;
|
||||
}
|
||||
|
||||
} // namespace Everest
|
||||
|
||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||
void adl_serializer<Everest::RuntimeSettings>::to_json(nlohmann::json& j, const Everest::RuntimeSettings& r) {
|
||||
j = {{"prefix", r.prefix},
|
||||
{"etc_dir", r.etc_dir},
|
||||
{"data_dir", r.data_dir},
|
||||
{"modules_dir", r.modules_dir},
|
||||
{"telemetry_prefix", r.telemetry_prefix},
|
||||
{"telemetry_enabled", r.telemetry_enabled},
|
||||
{"validate_schema", r.validate_schema},
|
||||
{"forward_exceptions", r.forward_exceptions}};
|
||||
}
|
||||
|
||||
void adl_serializer<Everest::RuntimeSettings>::from_json(const nlohmann::json& j, Everest::RuntimeSettings& r) {
|
||||
r.prefix = j.at("prefix").get<std::string>();
|
||||
r.etc_dir = j.at("etc_dir").get<std::string>();
|
||||
r.data_dir = j.at("data_dir").get<std::string>();
|
||||
r.modules_dir = j.at("modules_dir").get<std::string>();
|
||||
r.telemetry_prefix = j.at("telemetry_prefix").get<std::string>();
|
||||
r.telemetry_enabled = j.at("telemetry_enabled").get<bool>();
|
||||
r.validate_schema = j.at("validate_schema").get<bool>();
|
||||
r.forward_exceptions = j.at("forward_exceptions").get<bool>();
|
||||
}
|
||||
NLOHMANN_JSON_NAMESPACE_END
|
||||
@@ -0,0 +1,898 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
#include "utils/config/storage.hpp"
|
||||
#include "utils/config/types.hpp"
|
||||
#include <everest/compile_time_settings.hpp>
|
||||
#include <everest/database/exceptions.hpp>
|
||||
#include <everest/database/sqlite/schema_updater.hpp>
|
||||
#include <everest/logging.hpp>
|
||||
|
||||
#include <utils/config/settings.hpp>
|
||||
#include <utils/config/storage_sqlite.hpp>
|
||||
#include <utils/date.hpp>
|
||||
|
||||
using json = nlohmann::json;
|
||||
using namespace everest::db;
|
||||
using namespace everest::db::sqlite;
|
||||
|
||||
namespace everest::config {
|
||||
namespace {
|
||||
const std::string& default_module_implementation_id() {
|
||||
static const std::string DEFAULT_MODULE_IMPLEMENTATION_ID = "!module";
|
||||
return DEFAULT_MODULE_IMPLEMENTATION_ID;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
/// \brief Helper for accessing the column indices of the SETTING table
|
||||
enum class SettingColumnIndex : int {
|
||||
COL_ID = 0,
|
||||
COL_PREFIX,
|
||||
COL_CONFIG_FILE,
|
||||
COL_CONFIGS_DIR,
|
||||
COL_SCHEMAS_DIR,
|
||||
COL_MODULES_DIR,
|
||||
COL_INTERFACES_DIR,
|
||||
COL_TYPES_DIR,
|
||||
COL_ERRORS_DIR,
|
||||
COL_WWW_DIR,
|
||||
COL_LOGGING_CONFIG_FILE,
|
||||
COL_CONTROLLER_PORT,
|
||||
COL_CONTROLLER_RPC_TIMEOUT_MS,
|
||||
COL_MQTT_BROKER_SOCKET_PATH,
|
||||
COL_MQTT_BROKER_HOST,
|
||||
COL_MQTT_BROKER_PORT,
|
||||
COL_MQTT_EVEREST_PREFIX,
|
||||
COL_MQTT_EXTERNAL_PREFIX,
|
||||
COL_TELEMETRY_PREFIX,
|
||||
COL_TELEMETRY_ENABLED,
|
||||
COL_VALIDATE_SCHEMA,
|
||||
COL_RUN_AS_USER,
|
||||
COL_FORWARD_EXCEPTIONS,
|
||||
};
|
||||
|
||||
/// \brief Helper for accessing the column indices of the CONFIGURATION table
|
||||
enum class ConfigurationColumnIndex {
|
||||
COL_MODULE_ID = 1,
|
||||
COL_PARAMETER_NAME,
|
||||
COL_VALUE,
|
||||
COL_MUTABILITY_ID,
|
||||
COL_DATATYPE_ID,
|
||||
COL_UNIT,
|
||||
COL_MODULE_IMPLEMENTATION_ID,
|
||||
};
|
||||
|
||||
/// \brief Helper for accessing the column indices of the CONFIGURATION table of a specific MODULE_ID
|
||||
enum class ConfigurationColumnModuleIdIndex {
|
||||
COL_PARAMETER_NAME = 0,
|
||||
COL_VALUE,
|
||||
COL_MODULE_IMPLEMENTATION_ID,
|
||||
COL_MUTABILITY_ID,
|
||||
COL_DATATYPE_ID,
|
||||
COL_UNIT,
|
||||
};
|
||||
|
||||
namespace {
|
||||
int to_int(SettingColumnIndex setting_column_index) {
|
||||
return static_cast<int>(setting_column_index);
|
||||
}
|
||||
|
||||
int to_int(ConfigurationColumnIndex configuration_column_index) {
|
||||
return static_cast<int>(configuration_column_index);
|
||||
}
|
||||
|
||||
int to_int(ConfigurationColumnModuleIdIndex configuration_column_module_id_index) {
|
||||
return static_cast<int>(configuration_column_module_id_index);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
SqliteStorage::SqliteStorage(const fs::path& db_path, const std::filesystem::path& migration_files_path) {
|
||||
db = std::make_unique<Connection>(db_path);
|
||||
|
||||
SchemaUpdater updater{db.get()};
|
||||
|
||||
if (!updater.apply_migration_files(migration_files_path, TARGET_MIGRATION_FILE_VERSION)) {
|
||||
if (db_path.parent_path().empty()) {
|
||||
EVLOG_error
|
||||
<< "Could not apply migrations for database at provided path: \"" << db_path.string()
|
||||
<< "\" likely because the database path is just a filename. You MUST provide a full path to the "
|
||||
"database.";
|
||||
}
|
||||
throw MigrationException("SQL migration failed");
|
||||
}
|
||||
|
||||
if (!db->open_connection()) {
|
||||
throw std::runtime_error("Could not open database at provided path: " + db_path.string());
|
||||
} else {
|
||||
EVLOG_info << "Established connection to database successfully: " << db_path;
|
||||
}
|
||||
}
|
||||
|
||||
GenericResponseStatus SqliteStorage::write_module_configs(const ModuleConfigurations& module_configs) {
|
||||
try {
|
||||
auto transaction = this->db->begin_transaction();
|
||||
|
||||
for (const auto& [module_id, module] : module_configs) {
|
||||
ModuleData module_data;
|
||||
module_data.module_id = module_id;
|
||||
module_data.module_name = module.module_name;
|
||||
module_data.standalone = module.standalone;
|
||||
module_data.capabilities = module.capabilities;
|
||||
|
||||
if (this->write_module_data(module_data) != GenericResponseStatus::OK) {
|
||||
EVLOG_error << "Failed to write module info for module: " << module_id;
|
||||
return GenericResponseStatus::Failed;
|
||||
}
|
||||
|
||||
for (const auto& [requirement_id, connections] : module.connections) {
|
||||
for (const auto& connection : connections) {
|
||||
Fulfillment fulfillment;
|
||||
fulfillment.module_id = connection.module_id;
|
||||
fulfillment.implementation_id = connection.implementation_id;
|
||||
fulfillment.requirement = {requirement_id};
|
||||
|
||||
if (this->write_module_fulfillment(module_id, fulfillment) != GenericResponseStatus::OK) {
|
||||
EVLOG_error << "Failed to write module fulfillment for module: " << module_id
|
||||
<< " and requirement: " << requirement_id;
|
||||
return GenericResponseStatus::Failed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (module.mapping.module.has_value()) {
|
||||
const auto& map = module.mapping.module.value();
|
||||
if (this->write_module_tier_mapping(module_id, default_module_implementation_id(), map.evse,
|
||||
map.connector) != GenericResponseStatus::OK) {
|
||||
EVLOG_error << "Failed to write module tier mapping for module: " << module_id;
|
||||
return GenericResponseStatus::Failed;
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& [impl_id, mapping] : module.mapping.implementations) {
|
||||
if (mapping.has_value()) {
|
||||
const auto& map = mapping.value();
|
||||
if (this->write_module_tier_mapping(module_id, impl_id, map.evse, map.connector) !=
|
||||
GenericResponseStatus::OK) {
|
||||
EVLOG_error << "Failed to write module tier mapping for module: " << module_id
|
||||
<< " and implementation id: " << impl_id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& [impl_id, params] : module.configuration_parameters) {
|
||||
for (const auto& param : params) {
|
||||
ConfigurationParameterIdentifier identifier;
|
||||
identifier.module_id = module_id;
|
||||
identifier.module_implementation_id = impl_id;
|
||||
identifier.configuration_parameter_name = param.name;
|
||||
|
||||
std::string value;
|
||||
if (std::holds_alternative<std::string>(param.value)) {
|
||||
value = std::get<std::string>(param.value);
|
||||
} else {
|
||||
const nlohmann::json temp = param.value;
|
||||
value = temp.dump();
|
||||
}
|
||||
if (this->write_configuration_parameter(identifier, param.characteristics, value) !=
|
||||
GetSetResponseStatus::OK) {
|
||||
EVLOG_error << "Failed to write configuration parameter for module: " << module_id
|
||||
<< ", param: " << identifier.configuration_parameter_name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (this->write_access(module_id, module.access) != GenericResponseStatus::OK) {
|
||||
EVLOG_error << "Failed to write module access for module: " << module_id;
|
||||
}
|
||||
}
|
||||
|
||||
transaction->commit();
|
||||
return GenericResponseStatus::OK;
|
||||
} catch (const std::exception& e) {
|
||||
EVLOG_error << "Failed writing config to database: " << e.what();
|
||||
return GenericResponseStatus::Failed;
|
||||
}
|
||||
}
|
||||
|
||||
GenericResponseStatus SqliteStorage::wipe() {
|
||||
const std::string sql = "PRAGMA FOREIGN_KEYS = ON; DELETE FROM MODULE; PRAGMA FOREIGN_KEYS = OFF;";
|
||||
try {
|
||||
if (this->db->execute_statement(sql)) {
|
||||
return GenericResponseStatus::OK;
|
||||
}
|
||||
return GenericResponseStatus::Failed;
|
||||
} catch (const std::exception& e) {
|
||||
EVLOG_error << "Failed to wipe database: " << e.what();
|
||||
return GenericResponseStatus::Failed;
|
||||
}
|
||||
}
|
||||
|
||||
GetModuleConfigsResponse SqliteStorage::get_module_configs() {
|
||||
GetModuleConfigsResponse response;
|
||||
|
||||
try {
|
||||
const std::string sql = "SELECT ID FROM MODULE";
|
||||
|
||||
auto stmt = this->db->new_statement(sql);
|
||||
|
||||
while (stmt->step() == SQLITE_ROW) {
|
||||
auto module_config_response = this->get_module_config(stmt->column_text(0));
|
||||
if (module_config_response.status == GenericResponseStatus::OK and
|
||||
module_config_response.config.has_value()) {
|
||||
response.module_configs[module_config_response.config.value().module_id] =
|
||||
module_config_response.config.value();
|
||||
} else {
|
||||
response.status = GenericResponseStatus::Failed;
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
response.status = GenericResponseStatus::OK;
|
||||
return response;
|
||||
} catch (const std::exception& e) {
|
||||
EVLOG_error << "Failed to get EVerest config: " << e.what();
|
||||
response.status = GenericResponseStatus::Failed;
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
GetSettingsResponse SqliteStorage::get_settings() {
|
||||
const std::string sql = "SELECT * FROM SETTING WHERE ID = 0";
|
||||
auto stmt = this->db->new_statement(sql);
|
||||
|
||||
if (stmt->step() != SQLITE_ROW) {
|
||||
return GetSettingsResponse{GenericResponseStatus::Failed, std::nullopt};
|
||||
}
|
||||
|
||||
Settings settings;
|
||||
[[maybe_unused]] const auto id =
|
||||
stmt->column_int(to_int(SettingColumnIndex::COL_ID)); // ID is required and always present
|
||||
|
||||
// text
|
||||
settings.prefix = stmt->column_text(to_int(SettingColumnIndex::COL_PREFIX));
|
||||
settings.config_file = stmt->column_text(to_int(SettingColumnIndex::COL_CONFIG_FILE));
|
||||
settings.configs_dir = stmt->column_text(to_int(SettingColumnIndex::COL_CONFIGS_DIR));
|
||||
settings.schemas_dir = stmt->column_text(to_int(SettingColumnIndex::COL_SCHEMAS_DIR));
|
||||
settings.modules_dir = stmt->column_text(to_int(SettingColumnIndex::COL_MODULES_DIR));
|
||||
settings.interfaces_dir = stmt->column_text(to_int(SettingColumnIndex::COL_INTERFACES_DIR));
|
||||
settings.types_dir = stmt->column_text(to_int(SettingColumnIndex::COL_TYPES_DIR));
|
||||
settings.errors_dir = stmt->column_text(to_int(SettingColumnIndex::COL_ERRORS_DIR));
|
||||
settings.www_dir = stmt->column_text(to_int(SettingColumnIndex::COL_WWW_DIR));
|
||||
settings.logging_config_file = stmt->column_text(to_int(SettingColumnIndex::COL_LOGGING_CONFIG_FILE));
|
||||
settings.mqtt_broker_socket_path = stmt->column_text(to_int(SettingColumnIndex::COL_MQTT_BROKER_SOCKET_PATH));
|
||||
settings.mqtt_broker_host = stmt->column_text(to_int(SettingColumnIndex::COL_MQTT_BROKER_HOST));
|
||||
settings.mqtt_everest_prefix = stmt->column_text(to_int(SettingColumnIndex::COL_MQTT_EVEREST_PREFIX));
|
||||
settings.mqtt_external_prefix = stmt->column_text(to_int(SettingColumnIndex::COL_MQTT_EXTERNAL_PREFIX));
|
||||
settings.telemetry_prefix = stmt->column_text(to_int(SettingColumnIndex::COL_TELEMETRY_PREFIX));
|
||||
settings.run_as_user = stmt->column_text(to_int(SettingColumnIndex::COL_RUN_AS_USER));
|
||||
|
||||
// integer
|
||||
settings.controller_port = stmt->column_int(to_int(SettingColumnIndex::COL_CONTROLLER_PORT));
|
||||
settings.controller_rpc_timeout_ms = stmt->column_int(to_int(SettingColumnIndex::COL_CONTROLLER_RPC_TIMEOUT_MS));
|
||||
settings.mqtt_broker_port = stmt->column_int(to_int(SettingColumnIndex::COL_MQTT_BROKER_PORT));
|
||||
|
||||
// boolean
|
||||
settings.telemetry_enabled = stmt->column_int(to_int(SettingColumnIndex::COL_TELEMETRY_ENABLED)) != 0;
|
||||
settings.validate_schema = stmt->column_int(to_int(SettingColumnIndex::COL_VALIDATE_SCHEMA)) != 0;
|
||||
settings.forward_exceptions = stmt->column_int(to_int(SettingColumnIndex::COL_FORWARD_EXCEPTIONS)) != 0;
|
||||
|
||||
return GetSettingsResponse{GenericResponseStatus::OK, settings};
|
||||
}
|
||||
|
||||
GetModuleConfigurationResponse SqliteStorage::get_module_config(const std::string& module_id) {
|
||||
|
||||
GetModuleConfigurationResponse response;
|
||||
|
||||
const auto module_data_response = this->get_module_data(module_id);
|
||||
if (module_data_response.status == GenericResponseStatus::Failed or !module_data_response.module_data.has_value()) {
|
||||
response.status = GenericResponseStatus::Failed;
|
||||
return response;
|
||||
}
|
||||
|
||||
const auto module_data = module_data_response.module_data.value();
|
||||
|
||||
const auto module_fulfillments_response = this->get_module_fulfillments(module_id);
|
||||
if (module_fulfillments_response.status == GenericResponseStatus::Failed) {
|
||||
response.status = GenericResponseStatus::Failed;
|
||||
return response;
|
||||
}
|
||||
const auto module_fulfillments = module_fulfillments_response.module_fulfillments;
|
||||
|
||||
const auto module_tier_mappings_response = this->get_module_tier_mappings(module_id);
|
||||
if (module_tier_mappings_response.status == GenericResponseStatus::Failed) {
|
||||
response.status = GenericResponseStatus::Failed;
|
||||
return response;
|
||||
}
|
||||
const auto module_tier_mappings = module_tier_mappings_response.module_tier_mappings;
|
||||
|
||||
const auto config_access_response = this->get_config_access(module_id);
|
||||
if (config_access_response.status == GenericResponseStatus::Failed) {
|
||||
response.status = GenericResponseStatus::Failed;
|
||||
return response;
|
||||
}
|
||||
const auto config_access = config_access_response.config_access;
|
||||
|
||||
try {
|
||||
|
||||
const std::string sql =
|
||||
"SELECT PARAMETER_NAME, VALUE, MODULE_IMPLEMENTATION_ID, MUTABILITY_ID, DATATYPE_ID, UNIT "
|
||||
"FROM CONFIGURATION WHERE MODULE_ID = "
|
||||
"@module_id";
|
||||
auto stmt = this->db->new_statement(sql);
|
||||
|
||||
stmt->bind_text(1, module_id);
|
||||
|
||||
ModuleConfig module_config;
|
||||
module_config.capabilities = module_data.capabilities;
|
||||
module_config.module_id = module_data.module_id;
|
||||
module_config.module_name = module_data.module_name;
|
||||
module_config.standalone = module_data.standalone;
|
||||
for (const auto& fulfillment : module_fulfillments) {
|
||||
module_config.connections[fulfillment.requirement.id].push_back(fulfillment);
|
||||
}
|
||||
module_config.mapping = module_tier_mappings;
|
||||
module_config.access.config = config_access;
|
||||
|
||||
while (stmt->step() == SQLITE_ROW) {
|
||||
ConfigurationParameter configuration_parameter;
|
||||
configuration_parameter.name =
|
||||
stmt->column_text(to_int(ConfigurationColumnModuleIdIndex::COL_PARAMETER_NAME));
|
||||
const auto value_str = stmt->column_text(to_int(ConfigurationColumnModuleIdIndex::COL_VALUE));
|
||||
auto implementation_id =
|
||||
stmt->column_text(to_int(ConfigurationColumnModuleIdIndex::COL_MODULE_IMPLEMENTATION_ID));
|
||||
ConfigurationParameterCharacteristics characteristics;
|
||||
characteristics.mutability =
|
||||
static_cast<Mutability>(stmt->column_int(to_int(ConfigurationColumnModuleIdIndex::COL_MUTABILITY_ID)));
|
||||
characteristics.datatype =
|
||||
static_cast<Datatype>(stmt->column_int(to_int(ConfigurationColumnModuleIdIndex::COL_DATATYPE_ID)));
|
||||
characteristics.unit = stmt->column_text_nullable(to_int(ConfigurationColumnModuleIdIndex::COL_UNIT));
|
||||
configuration_parameter.characteristics = characteristics;
|
||||
configuration_parameter.value = parse_config_value(characteristics.datatype, value_str);
|
||||
|
||||
module_config.configuration_parameters[implementation_id].push_back(configuration_parameter);
|
||||
}
|
||||
response.status = GenericResponseStatus::OK;
|
||||
response.config = module_config;
|
||||
return response;
|
||||
} catch (const std::exception& e) {
|
||||
EVLOG_error << "Failed to get module config with module_id: " << module_id;
|
||||
response.status = GenericResponseStatus::Failed;
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
GetConfigurationParameterResponse
|
||||
SqliteStorage::get_configuration_parameter(const ConfigurationParameterIdentifier& identifier) {
|
||||
|
||||
GetConfigurationParameterResponse response;
|
||||
|
||||
try {
|
||||
const std::string sql = "SELECT VALUE, MUTABILITY_ID, DATATYPE_ID, UNIT FROM "
|
||||
"CONFIGURATION WHERE MODULE_ID = "
|
||||
"@module_id AND PARAMETER_NAME = @config_param_name AND MODULE_IMPLEMENTATION_ID = "
|
||||
"@module_implementation_id";
|
||||
auto stmt = this->db->new_statement(sql);
|
||||
|
||||
stmt->bind_text("@module_id", identifier.module_id);
|
||||
stmt->bind_text("@config_param_name", identifier.configuration_parameter_name);
|
||||
stmt->bind_text("@module_implementation_id", identifier.module_implementation_id.has_value()
|
||||
? identifier.module_implementation_id.value()
|
||||
: default_module_implementation_id());
|
||||
|
||||
const auto status = stmt->step();
|
||||
|
||||
if (status == SQLITE_DONE) {
|
||||
response.status = GetSetResponseStatus::NotFound;
|
||||
return response;
|
||||
}
|
||||
|
||||
if (status == SQLITE_ROW) {
|
||||
response.status = GetSetResponseStatus::OK;
|
||||
|
||||
ConfigurationParameter configuration_parameter;
|
||||
configuration_parameter.name = identifier.configuration_parameter_name;
|
||||
const auto value_str = stmt->column_text(0);
|
||||
ConfigurationParameterCharacteristics characteristics;
|
||||
characteristics.mutability = static_cast<Mutability>(stmt->column_int(1));
|
||||
characteristics.datatype = static_cast<Datatype>(stmt->column_int(2));
|
||||
characteristics.unit = stmt->column_text_nullable(3);
|
||||
configuration_parameter.characteristics = characteristics;
|
||||
configuration_parameter.value = parse_config_value(characteristics.datatype, value_str);
|
||||
response.configuration_parameter = configuration_parameter;
|
||||
return response;
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
EVLOG_error << "Failed to get config value with module_id: " << identifier.module_id
|
||||
<< " and config_parameter_name: " << identifier.configuration_parameter_name;
|
||||
response.status = GetSetResponseStatus::Failed;
|
||||
return response;
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
GetSetResponseStatus
|
||||
SqliteStorage::write_configuration_parameter(const ConfigurationParameterIdentifier& identifier,
|
||||
const ConfigurationParameterCharacteristics characteristics,
|
||||
const std::string& value) {
|
||||
try {
|
||||
const std::string insert_query = "INSERT OR REPLACE INTO CONFIGURATION (MODULE_ID, PARAMETER_NAME, "
|
||||
"VALUE, "
|
||||
"MUTABILITY_ID, DATATYPE_ID, UNIT, MODULE_IMPLEMENTATION_ID) VALUES "
|
||||
"(?, ?, ?, ?, ?, ?, ?);";
|
||||
|
||||
auto stmt = this->db->new_statement(insert_query);
|
||||
stmt->bind_text(to_int(ConfigurationColumnIndex::COL_MODULE_ID), identifier.module_id);
|
||||
stmt->bind_text(to_int(ConfigurationColumnIndex::COL_PARAMETER_NAME), identifier.configuration_parameter_name);
|
||||
stmt->bind_text(to_int(ConfigurationColumnIndex::COL_VALUE), value);
|
||||
stmt->bind_int(to_int(ConfigurationColumnIndex::COL_MUTABILITY_ID),
|
||||
static_cast<int>(characteristics.mutability));
|
||||
stmt->bind_int(to_int(ConfigurationColumnIndex::COL_DATATYPE_ID), static_cast<int>(characteristics.datatype));
|
||||
if (characteristics.unit.has_value()) {
|
||||
stmt->bind_text(to_int(ConfigurationColumnIndex::COL_UNIT), characteristics.unit.value());
|
||||
} else {
|
||||
stmt->bind_null(to_int(ConfigurationColumnIndex::COL_UNIT));
|
||||
}
|
||||
|
||||
if (identifier.module_implementation_id.has_value()) {
|
||||
stmt->bind_text(to_int(ConfigurationColumnIndex::COL_MODULE_IMPLEMENTATION_ID),
|
||||
identifier.module_implementation_id.value());
|
||||
} else {
|
||||
stmt->bind_null(to_int(ConfigurationColumnIndex::COL_MODULE_IMPLEMENTATION_ID));
|
||||
}
|
||||
|
||||
if (stmt->step() != SQLITE_DONE) {
|
||||
return GetSetResponseStatus::NotFound;
|
||||
}
|
||||
|
||||
return GetSetResponseStatus::OK;
|
||||
} catch (const std::exception& e) {
|
||||
EVLOG_error << "Failed to set config value with module_id: " << identifier.module_id
|
||||
<< " and config_parameter_name: " << identifier.configuration_parameter_name;
|
||||
return GetSetResponseStatus::Failed;
|
||||
}
|
||||
}
|
||||
|
||||
bool SqliteStorage::contains_valid_config() {
|
||||
const std::string sql = "SELECT VALID FROM CONFIG_META WHERE ID = 0";
|
||||
auto stmt = this->db->new_statement(sql);
|
||||
if (stmt->step() != SQLITE_ROW) {
|
||||
return false;
|
||||
}
|
||||
const auto is_valid = stmt->column_int(0);
|
||||
return is_valid == 1;
|
||||
}
|
||||
|
||||
void SqliteStorage::mark_valid(const bool is_valid, const std::string& config_dump,
|
||||
const std::optional<fs::path>& config_file_path) {
|
||||
const std::string sql =
|
||||
"INSERT OR REPLACE INTO CONFIG_META (ID, LAST_UPDATED, VALID, CONFIG_DUMP, CONFIG_FILE_PATH) VALUES (0, "
|
||||
"@last_updated, @is_valid, @config_dump, @config_file_path);";
|
||||
auto stmt = this->db->new_statement(sql);
|
||||
|
||||
const auto last_updated = Everest::Date::to_rfc3339(date::utc_clock::now());
|
||||
stmt->bind_text("@last_updated", last_updated);
|
||||
stmt->bind_int("@is_valid", is_valid ? 1 : 0);
|
||||
stmt->bind_text("@config_dump", config_dump, SQLiteString::Transient);
|
||||
if (config_file_path.has_value()) {
|
||||
stmt->bind_text("@config_file_path", config_file_path.value().string(), SQLiteString::Transient);
|
||||
} else {
|
||||
stmt->bind_null("@config_file_path");
|
||||
}
|
||||
if (stmt->step() != SQLITE_DONE) {
|
||||
EVLOG_error << "Failed to mark config as valid";
|
||||
} else {
|
||||
EVLOG_debug << "Marked config as valid";
|
||||
}
|
||||
}
|
||||
|
||||
GetSetResponseStatus SqliteStorage::update_configuration_parameter(const ConfigurationParameterIdentifier& identifier,
|
||||
const std::string& value) {
|
||||
|
||||
try {
|
||||
const std::string update_query = "UPDATE CONFIGURATION SET VALUE = @value "
|
||||
"WHERE MODULE_ID = @module_id AND "
|
||||
"PARAMETER_NAME = @parameter_name AND "
|
||||
"MODULE_IMPLEMENTATION_ID = @module_implementation_id;";
|
||||
|
||||
auto stmt = this->db->new_statement(update_query);
|
||||
stmt->bind_text("@value", value);
|
||||
stmt->bind_text("@module_id", identifier.module_id);
|
||||
stmt->bind_text("@parameter_name", identifier.configuration_parameter_name);
|
||||
const std::string impl_id = identifier.module_implementation_id.value_or(default_module_implementation_id());
|
||||
stmt->bind_text("@module_implementation_id", impl_id);
|
||||
|
||||
if (stmt->step() != SQLITE_DONE) {
|
||||
return GetSetResponseStatus::Failed;
|
||||
}
|
||||
|
||||
if (stmt->changes() == 0) {
|
||||
return GetSetResponseStatus::NotFound;
|
||||
}
|
||||
|
||||
return GetSetResponseStatus::OK;
|
||||
} catch (const std::exception& e) {
|
||||
EVLOG_error << "Failed to set config value with module_id: " << identifier.module_id
|
||||
<< " and config_parameter_name: " << identifier.configuration_parameter_name;
|
||||
return GetSetResponseStatus::Failed;
|
||||
}
|
||||
}
|
||||
|
||||
GenericResponseStatus SqliteStorage::write_module_data(const ModuleData& module_data) {
|
||||
|
||||
const std::string insert_query =
|
||||
"INSERT OR REPLACE INTO MODULE (ID, NAME, STANDALONE, CAPABILITIES) VALUES (?, ?, ?, ?);";
|
||||
|
||||
auto stmt = this->db->new_statement(insert_query);
|
||||
|
||||
stmt->bind_text(1, module_data.module_id);
|
||||
stmt->bind_text(2, module_data.module_name);
|
||||
stmt->bind_int(3, module_data.standalone);
|
||||
|
||||
if (module_data.capabilities.has_value()) {
|
||||
stmt->bind_text(4, json(module_data.capabilities.value()).dump(), SQLiteString::Transient);
|
||||
} else {
|
||||
stmt->bind_null(4);
|
||||
}
|
||||
|
||||
if (stmt->step() != SQLITE_DONE) {
|
||||
return GenericResponseStatus::Failed;
|
||||
}
|
||||
|
||||
return GenericResponseStatus::OK;
|
||||
}
|
||||
|
||||
GenericResponseStatus SqliteStorage::write_module_fulfillment(const std::string& module_id,
|
||||
const Fulfillment& fulfillment) {
|
||||
const std::string sql =
|
||||
"INSERT OR REPLACE INTO MODULE_FULFILLMENT (MODULE_ID, REQUIREMENT_NAME, IMPLEMENTATION_ID, "
|
||||
"IMPLEMENTATION_MODULE_ID) VALUES (?,?,?,?)";
|
||||
|
||||
auto stmt = this->db->new_statement(sql);
|
||||
|
||||
stmt->bind_text(1, module_id);
|
||||
stmt->bind_text(2, fulfillment.requirement.id);
|
||||
stmt->bind_text(3, fulfillment.implementation_id);
|
||||
stmt->bind_text(4, fulfillment.module_id);
|
||||
|
||||
if (stmt->step() != SQLITE_DONE) {
|
||||
return GenericResponseStatus::Failed;
|
||||
}
|
||||
|
||||
return GenericResponseStatus::OK;
|
||||
}
|
||||
|
||||
GenericResponseStatus SqliteStorage::write_module_tier_mapping(const std::string& module_id,
|
||||
const std::string& implementation_id,
|
||||
const int32_t evse_id,
|
||||
const std::optional<int32_t> connector_id) {
|
||||
const std::string sql = "INSERT OR REPLACE INTO MODULE_TIER_MAPPING (MODULE_ID, IMPLEMENTATION_ID, "
|
||||
"EVSE_ID, CONNECTOR_ID) VALUES (?,?,?,?)";
|
||||
|
||||
auto stmt = this->db->new_statement(sql);
|
||||
|
||||
stmt->bind_text(1, module_id);
|
||||
stmt->bind_text(2, implementation_id);
|
||||
stmt->bind_int(3, evse_id);
|
||||
|
||||
if (connector_id.has_value()) {
|
||||
stmt->bind_int(4, connector_id.value());
|
||||
} else {
|
||||
stmt->bind_null(4);
|
||||
}
|
||||
|
||||
if (stmt->step() != SQLITE_DONE) {
|
||||
return GenericResponseStatus::Failed;
|
||||
}
|
||||
|
||||
return GenericResponseStatus::OK;
|
||||
}
|
||||
|
||||
GenericResponseStatus SqliteStorage::write_access(const std::string& module_id, const Access& access) {
|
||||
if (access.config.has_value()) {
|
||||
return write_config_access(module_id, access.config.value());
|
||||
}
|
||||
return GenericResponseStatus::OK;
|
||||
}
|
||||
GenericResponseStatus SqliteStorage::write_config_access(const std::string& module_id,
|
||||
const ConfigAccess& config_access) {
|
||||
// write global config access to db
|
||||
const std::string sql = "INSERT OR REPLACE INTO CONFIG_ACCESS (MODULE_ID, ALLOW_GLOBAL_READ, ALLOW_GLOBAL_WRITE, "
|
||||
"ALLOW_SET_READ_ONLY) VALUES (?,?,?,?)";
|
||||
|
||||
auto stmt = this->db->new_statement(sql);
|
||||
|
||||
stmt->bind_text(1, module_id);
|
||||
stmt->bind_int(2, config_access.allow_global_read ? 1 : 0);
|
||||
stmt->bind_int(3, config_access.allow_global_write ? 1 : 0);
|
||||
stmt->bind_int(4, config_access.allow_set_read_only ? 1 : 0);
|
||||
|
||||
if (stmt->step() != SQLITE_DONE) {
|
||||
return GenericResponseStatus::Failed;
|
||||
}
|
||||
|
||||
// write individual module config access to db
|
||||
for (const auto& [other_module_id, module_config_access] : config_access.modules) {
|
||||
const auto result = write_module_config_access(module_id, other_module_id, module_config_access);
|
||||
if (result != GenericResponseStatus::OK) {
|
||||
return GenericResponseStatus::Failed;
|
||||
}
|
||||
}
|
||||
|
||||
return GenericResponseStatus::OK;
|
||||
}
|
||||
GenericResponseStatus SqliteStorage::write_module_config_access(const std::string& module_id,
|
||||
const std::string& other_module_id,
|
||||
const ModuleConfigAccess& module_config_access) {
|
||||
const std::string sql = "INSERT OR REPLACE INTO MODULE_CONFIG_ACCESS (MODULE_ID, OTHER_MODULE_ID, "
|
||||
"ALLOW_READ, ALLOW_WRITE, ALLOW_SET_READ_ONLY) VALUES (?,?,?,?,?)";
|
||||
|
||||
auto stmt = this->db->new_statement(sql);
|
||||
|
||||
stmt->bind_text(1, module_id);
|
||||
stmt->bind_text(2, other_module_id);
|
||||
stmt->bind_int(3, module_config_access.allow_read ? 1 : 0);
|
||||
stmt->bind_int(4, module_config_access.allow_write ? 1 : 0);
|
||||
stmt->bind_int(5, module_config_access.allow_set_read_only ? 1 : 0);
|
||||
|
||||
if (stmt->step() != SQLITE_DONE) {
|
||||
return GenericResponseStatus::Failed;
|
||||
}
|
||||
return GenericResponseStatus::OK;
|
||||
}
|
||||
|
||||
GenericResponseStatus SqliteStorage::write_settings(const Everest::ManagerSettings& manager_settings) {
|
||||
auto transaction = this->db->begin_transaction();
|
||||
|
||||
std::vector<std::string> keys = {"ID",
|
||||
"PREFIX",
|
||||
"CONFIG_FILE",
|
||||
"CONFIGS_DIR",
|
||||
"SCHEMAS_DIR",
|
||||
"MODULES_DIR",
|
||||
"INTERFACES_DIR",
|
||||
"TYPES_DIR",
|
||||
"ERRORS_DIR",
|
||||
"WWW_DIR",
|
||||
"LOGGING_CONFIG_FILE",
|
||||
"CONTROLLER_PORT",
|
||||
"CONTROLLER_RPC_TIMEOUT_MS",
|
||||
"MQTT_BROKER_SOCKET_PATH",
|
||||
"MQTT_BROKER_HOST",
|
||||
"MQTT_BROKER_PORT",
|
||||
"MQTT_EVEREST_PREFIX",
|
||||
"MQTT_EXTERNAL_PREFIX",
|
||||
"TELEMETRY_PREFIX",
|
||||
"TELEMETRY_ENABLED",
|
||||
"VALIDATE_SCHEMA",
|
||||
"RUN_AS_USER",
|
||||
"FORWARD_EXCEPTIONS"};
|
||||
|
||||
std::string sql = "INSERT INTO SETTING (";
|
||||
for (size_t i = 0; i < keys.size(); ++i) {
|
||||
sql += keys.at(i);
|
||||
if (i < keys.size() - 1) {
|
||||
sql += ", ";
|
||||
}
|
||||
}
|
||||
sql += ") VALUES (";
|
||||
for (size_t i = 0; i < keys.size(); ++i) {
|
||||
sql += (i == 0 ? "?" : ", ?");
|
||||
}
|
||||
sql += ") ON CONFLICT(ID) DO UPDATE SET ";
|
||||
for (size_t i = 1; i < keys.size(); ++i) {
|
||||
sql += keys.at(i) + " = excluded." + keys.at(i);
|
||||
if (i < keys.size() - 1) {
|
||||
sql += ", ";
|
||||
}
|
||||
}
|
||||
sql += ";";
|
||||
|
||||
auto stmt = this->db->new_statement(sql);
|
||||
|
||||
// ID is always 0
|
||||
stmt->bind_int(to_int(SettingColumnIndex::COL_ID) + 1, 0);
|
||||
|
||||
auto bind_text_opt = [&](SettingColumnIndex index, int offset, const std::optional<std::string>& opt) {
|
||||
if (opt.has_value()) {
|
||||
stmt->bind_text(to_int(index) + offset, opt.value(), SQLiteString::Transient);
|
||||
} else {
|
||||
stmt->bind_null(to_int(index) + offset);
|
||||
}
|
||||
};
|
||||
|
||||
auto bind_path_opt = [&](SettingColumnIndex index, int offset, const std::optional<fs::path>& opt) {
|
||||
if (opt.has_value()) {
|
||||
stmt->bind_text(to_int(index) + offset, opt.value().string(), SQLiteString::Transient);
|
||||
} else {
|
||||
stmt->bind_null(to_int(index) + offset);
|
||||
}
|
||||
};
|
||||
|
||||
auto bind_int_opt = [&](SettingColumnIndex index, int offset, const std::optional<int>& opt) {
|
||||
if (opt.has_value()) {
|
||||
stmt->bind_int(to_int(index) + offset, opt.value());
|
||||
} else {
|
||||
stmt->bind_null(to_int(index) + offset);
|
||||
}
|
||||
};
|
||||
|
||||
auto bind_bool_opt = [&](SettingColumnIndex index, int offset, const std::optional<bool>& opt) {
|
||||
if (opt) {
|
||||
stmt->bind_int(to_int(index) + offset, opt.value() ? 1 : 0);
|
||||
} else {
|
||||
stmt->bind_null(to_int(index) + offset);
|
||||
}
|
||||
};
|
||||
|
||||
bind_path_opt(SettingColumnIndex::COL_PREFIX, 1, manager_settings.runtime_settings.prefix);
|
||||
bind_path_opt(SettingColumnIndex::COL_CONFIG_FILE, 1, manager_settings.config_file);
|
||||
bind_path_opt(SettingColumnIndex::COL_CONFIGS_DIR, 1, manager_settings.configs_dir);
|
||||
bind_path_opt(SettingColumnIndex::COL_SCHEMAS_DIR, 1, manager_settings.schemas_dir);
|
||||
bind_path_opt(SettingColumnIndex::COL_MODULES_DIR, 1, manager_settings.runtime_settings.modules_dir);
|
||||
bind_path_opt(SettingColumnIndex::COL_INTERFACES_DIR, 1, manager_settings.interfaces_dir);
|
||||
bind_path_opt(SettingColumnIndex::COL_TYPES_DIR, 1, manager_settings.types_dir);
|
||||
bind_path_opt(SettingColumnIndex::COL_ERRORS_DIR, 1, manager_settings.errors_dir);
|
||||
bind_path_opt(SettingColumnIndex::COL_WWW_DIR, 1, manager_settings.www_dir);
|
||||
bind_path_opt(SettingColumnIndex::COL_LOGGING_CONFIG_FILE, 1,
|
||||
manager_settings.runtime_settings.logging_config_file);
|
||||
|
||||
bind_int_opt(SettingColumnIndex::COL_CONTROLLER_PORT, 1, manager_settings.controller_port);
|
||||
bind_int_opt(SettingColumnIndex::COL_CONTROLLER_RPC_TIMEOUT_MS, 1, manager_settings.controller_rpc_timeout_ms);
|
||||
|
||||
bind_text_opt(SettingColumnIndex::COL_MQTT_BROKER_SOCKET_PATH, 1,
|
||||
manager_settings.mqtt_settings.broker_socket_path);
|
||||
bind_text_opt(SettingColumnIndex::COL_MQTT_BROKER_HOST, 1, manager_settings.mqtt_settings.broker_host);
|
||||
bind_int_opt(SettingColumnIndex::COL_MQTT_BROKER_PORT, 1, manager_settings.mqtt_settings.broker_port);
|
||||
bind_text_opt(SettingColumnIndex::COL_MQTT_EVEREST_PREFIX, 1, manager_settings.mqtt_settings.everest_prefix);
|
||||
bind_text_opt(SettingColumnIndex::COL_MQTT_EXTERNAL_PREFIX, 1, manager_settings.mqtt_settings.external_prefix);
|
||||
bind_text_opt(SettingColumnIndex::COL_TELEMETRY_PREFIX, 1, manager_settings.runtime_settings.telemetry_prefix);
|
||||
|
||||
bind_bool_opt(SettingColumnIndex::COL_TELEMETRY_ENABLED, 1, manager_settings.runtime_settings.telemetry_enabled);
|
||||
bind_bool_opt(SettingColumnIndex::COL_VALIDATE_SCHEMA, 1, manager_settings.runtime_settings.validate_schema);
|
||||
bind_text_opt(SettingColumnIndex::COL_RUN_AS_USER, 1, manager_settings.run_as_user);
|
||||
bind_bool_opt(SettingColumnIndex::COL_FORWARD_EXCEPTIONS, 1, manager_settings.runtime_settings.forward_exceptions);
|
||||
|
||||
if (stmt->step() != SQLITE_DONE) {
|
||||
return GenericResponseStatus::Failed;
|
||||
}
|
||||
|
||||
transaction->commit();
|
||||
return GenericResponseStatus::OK;
|
||||
}
|
||||
|
||||
GetModuleDataResponse SqliteStorage::get_module_data(const std::string& module_id) {
|
||||
|
||||
GetModuleDataResponse response;
|
||||
|
||||
const std::string sql = "SELECT NAME, STANDALONE, CAPABILITIES FROM MODULE WHERE ID = @module_id";
|
||||
|
||||
auto stmt = this->db->new_statement(sql);
|
||||
stmt->bind_text("@module_id", module_id);
|
||||
const auto status = stmt->step();
|
||||
|
||||
if (status == SQLITE_DONE) {
|
||||
response.status = GenericResponseStatus::Failed;
|
||||
return response;
|
||||
}
|
||||
|
||||
if (status == SQLITE_ROW) {
|
||||
ModuleData module_data;
|
||||
module_data.module_id = module_id;
|
||||
module_data.module_name = stmt->column_text(0);
|
||||
module_data.standalone = stmt->column_int(1);
|
||||
const auto capabilities_str = stmt->column_text_nullable(2);
|
||||
if (capabilities_str.has_value()) {
|
||||
module_data.capabilities = json::parse(capabilities_str.value()).get<std::vector<std::string>>();
|
||||
}
|
||||
response.module_data = module_data;
|
||||
response.status = GenericResponseStatus::OK;
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
GetModuleFulfillmentsResponse SqliteStorage::get_module_fulfillments(const std::string& module_id) {
|
||||
GetModuleFulfillmentsResponse response;
|
||||
|
||||
const std::string sql =
|
||||
"SELECT REQUIREMENT_NAME, IMPLEMENTATION_ID, IMPLEMENTATION_MODULE_ID FROM MODULE_FULFILLMENT "
|
||||
"WHERE MODULE_ID = @module_id";
|
||||
|
||||
auto stmt = this->db->new_statement(sql);
|
||||
stmt->bind_text("@module_id", module_id);
|
||||
|
||||
size_t index = 0;
|
||||
while (stmt->step() == SQLITE_ROW) {
|
||||
Fulfillment fulfillment;
|
||||
fulfillment.requirement = {stmt->column_text(0), index};
|
||||
fulfillment.implementation_id = stmt->column_text(1);
|
||||
fulfillment.module_id = stmt->column_text(2);
|
||||
response.module_fulfillments.push_back(fulfillment);
|
||||
index++;
|
||||
}
|
||||
|
||||
response.status = GenericResponseStatus::OK; // FIXME: when to return failed?
|
||||
return response;
|
||||
}
|
||||
|
||||
GetModuleTierMappingsResponse SqliteStorage::get_module_tier_mappings(const std::string& module_id) {
|
||||
GetModuleTierMappingsResponse response;
|
||||
|
||||
const std::string sql = "SELECT IMPLEMENTATION_ID, EVSE_ID, CONNECTOR_ID FROM MODULE_TIER_MAPPING "
|
||||
"WHERE MODULE_ID = @module_id";
|
||||
|
||||
auto stmt = this->db->new_statement(sql);
|
||||
stmt->bind_text("@module_id", module_id);
|
||||
|
||||
ModuleTierMappings module_tier_mappings;
|
||||
|
||||
while (stmt->step() == SQLITE_ROW) {
|
||||
auto implementation_id = stmt->column_text(0);
|
||||
Mapping mapping = {stmt->column_int(1)};
|
||||
if (stmt->column_type(2) != SQLITE_NULL) {
|
||||
mapping.connector = stmt->column_int(2);
|
||||
}
|
||||
if (implementation_id == default_module_implementation_id()) {
|
||||
module_tier_mappings.module = mapping;
|
||||
} else {
|
||||
module_tier_mappings.implementations[implementation_id] = mapping;
|
||||
}
|
||||
}
|
||||
|
||||
response.module_tier_mappings = module_tier_mappings;
|
||||
response.status = GenericResponseStatus::OK;
|
||||
return response;
|
||||
}
|
||||
|
||||
GetModuleConfigAccessResponse SqliteStorage::get_module_config_access(const std::string& module_id) {
|
||||
GetModuleConfigAccessResponse response;
|
||||
const std::string sql = "SELECT OTHER_MODULE_ID, ALLOW_SET_READ_ONLY FROM MODULE_CONFIG_ACCESS "
|
||||
"WHERE MODULE_ID = @module_id";
|
||||
|
||||
auto stmt = this->db->new_statement(sql);
|
||||
stmt->bind_text("@module_id", module_id);
|
||||
|
||||
std::map<std::string, everest::config::ModuleConfigAccess> module_config_access;
|
||||
while (stmt->step() == SQLITE_ROW) {
|
||||
const auto other_module_id = stmt->column_text(0);
|
||||
const auto allow_set_read_only = stmt->column_int(1) != 0;
|
||||
module_config_access[other_module_id].allow_set_read_only = allow_set_read_only;
|
||||
}
|
||||
|
||||
response.module_config_access = module_config_access;
|
||||
response.status = GenericResponseStatus::OK;
|
||||
return response;
|
||||
}
|
||||
|
||||
GetConfigAccessResponse SqliteStorage::get_config_access(const std::string& module_id) {
|
||||
GetConfigAccessResponse response;
|
||||
|
||||
const std::string sql =
|
||||
"SELECT ALLOW_GLOBAL_READ, ALLOW_SET_READ_ONLY FROM CONFIG_ACCESS WHERE MODULE_ID = @module_id";
|
||||
|
||||
auto stmt = this->db->new_statement(sql);
|
||||
stmt->bind_text("@module_id", module_id);
|
||||
|
||||
const auto status = stmt->step();
|
||||
|
||||
if (status == SQLITE_DONE) {
|
||||
response.status = GenericResponseStatus::OK;
|
||||
return response;
|
||||
}
|
||||
|
||||
if (status == SQLITE_ROW) {
|
||||
ConfigAccess config_access;
|
||||
config_access.allow_global_read = stmt->column_int(0) != 0;
|
||||
config_access.allow_set_read_only = stmt->column_int(1) != 0;
|
||||
const auto module_config_access = get_module_config_access(module_id);
|
||||
if (module_config_access.status == GenericResponseStatus::OK) {
|
||||
config_access.modules = module_config_access.module_config_access;
|
||||
}
|
||||
response.config_access = config_access;
|
||||
response.status = GenericResponseStatus::OK;
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
} // namespace everest::config
|
||||
@@ -0,0 +1,15 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include <utils/config/storage_types.hpp>
|
||||
|
||||
namespace everest::config {
|
||||
bool ConfigurationParameterIdentifier::operator<(const ConfigurationParameterIdentifier& rhs) const {
|
||||
return (
|
||||
this->module_id < rhs.module_id ||
|
||||
(this->module_id == rhs.module_id && this->configuration_parameter_name < rhs.configuration_parameter_name) ||
|
||||
(this->module_id == rhs.module_id && this->configuration_parameter_name == rhs.configuration_parameter_name &&
|
||||
this->module_implementation_id < rhs.module_implementation_id));
|
||||
}
|
||||
|
||||
} // namespace everest::config
|
||||
483
tools/EVerest-main/lib/everest/framework/lib/config/types.cpp
Normal file
483
tools/EVerest-main/lib/everest/framework/lib/config/types.cpp
Normal file
@@ -0,0 +1,483 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <utils/config/types.hpp>
|
||||
#include <utils/types.hpp>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
bool operator<(const Requirement& lhs, const Requirement& rhs) {
|
||||
if (lhs.id < rhs.id) {
|
||||
return true;
|
||||
} else if (lhs.id == rhs.id) {
|
||||
return (lhs.index < rhs.index);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
namespace everest::config {
|
||||
std::string config_entry_to_string(const everest::config::ConfigEntry& entry) {
|
||||
return std::visit(VisitConfigEntry{}, entry);
|
||||
}
|
||||
|
||||
bool ConfigurationParameter::validate_type() const {
|
||||
return std::visit(
|
||||
[&](auto&& arg) -> bool {
|
||||
using T = std::decay_t<decltype(arg)>;
|
||||
switch (characteristics.datatype) {
|
||||
case Datatype::String:
|
||||
return std::is_same_v<T, std::string>;
|
||||
case Datatype::Boolean:
|
||||
return std::is_same_v<T, bool>;
|
||||
case Datatype::Integer:
|
||||
return std::is_same_v<T, int>;
|
||||
case Datatype::Decimal:
|
||||
return std::is_same_v<T, double> || std::is_same_v<T, int>;
|
||||
// allow integers where decimal is expected
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
},
|
||||
value);
|
||||
}
|
||||
|
||||
Settings parse_settings(const json& settings_json) {
|
||||
Settings settings;
|
||||
|
||||
if (auto it = settings_json.find("prefix"); it != settings_json.end()) {
|
||||
settings.prefix = it->get<std::string>();
|
||||
}
|
||||
if (auto it = settings_json.find("config_file"); it != settings_json.end()) {
|
||||
settings.config_file = it->get<std::string>();
|
||||
}
|
||||
if (auto it = settings_json.find("configs_dir"); it != settings_json.end()) {
|
||||
settings.configs_dir = it->get<std::string>();
|
||||
}
|
||||
if (auto it = settings_json.find("schemas_dir"); it != settings_json.end()) {
|
||||
settings.schemas_dir = it->get<std::string>();
|
||||
}
|
||||
if (auto it = settings_json.find("modules_dir"); it != settings_json.end()) {
|
||||
settings.modules_dir = it->get<std::string>();
|
||||
}
|
||||
if (auto it = settings_json.find("interfaces_dir"); it != settings_json.end()) {
|
||||
settings.interfaces_dir = it->get<std::string>();
|
||||
}
|
||||
if (auto it = settings_json.find("types_dir"); it != settings_json.end()) {
|
||||
settings.types_dir = it->get<std::string>();
|
||||
}
|
||||
if (auto it = settings_json.find("errors_dir"); it != settings_json.end()) {
|
||||
settings.errors_dir = it->get<std::string>();
|
||||
}
|
||||
if (auto it = settings_json.find("www_dir"); it != settings_json.end()) {
|
||||
settings.www_dir = it->get<std::string>();
|
||||
}
|
||||
if (auto it = settings_json.find("logging_config_file"); it != settings_json.end()) {
|
||||
settings.logging_config_file = it->get<std::string>();
|
||||
}
|
||||
if (auto it = settings_json.find("controller_port"); it != settings_json.end()) {
|
||||
settings.controller_port = it->get<int>();
|
||||
}
|
||||
if (auto it = settings_json.find("controller_rpc_timeout_ms"); it != settings_json.end()) {
|
||||
settings.controller_rpc_timeout_ms = it->get<int>();
|
||||
}
|
||||
if (auto it = settings_json.find("mqtt_broker_socket_path"); it != settings_json.end()) {
|
||||
settings.mqtt_broker_socket_path = it->get<std::string>();
|
||||
}
|
||||
if (auto it = settings_json.find("mqtt_broker_host"); it != settings_json.end()) {
|
||||
settings.mqtt_broker_host = it->get<std::string>();
|
||||
}
|
||||
if (auto it = settings_json.find("mqtt_broker_port"); it != settings_json.end()) {
|
||||
settings.mqtt_broker_port = it->get<int>();
|
||||
}
|
||||
if (auto it = settings_json.find("mqtt_everest_prefix"); it != settings_json.end()) {
|
||||
settings.mqtt_everest_prefix = it->get<std::string>();
|
||||
}
|
||||
if (auto it = settings_json.find("mqtt_external_prefix"); it != settings_json.end()) {
|
||||
settings.mqtt_external_prefix = it->get<std::string>();
|
||||
}
|
||||
if (auto it = settings_json.find("telemetry_prefix"); it != settings_json.end()) {
|
||||
settings.telemetry_prefix = it->get<std::string>();
|
||||
}
|
||||
if (auto it = settings_json.find("telemetry_enabled"); it != settings_json.end()) {
|
||||
settings.telemetry_enabled = it->get<bool>();
|
||||
}
|
||||
if (auto it = settings_json.find("validate_schema"); it != settings_json.end()) {
|
||||
settings.validate_schema = it->get<bool>();
|
||||
}
|
||||
if (auto it = settings_json.find("run_as_user"); it != settings_json.end()) {
|
||||
settings.run_as_user = it->get<std::string>();
|
||||
}
|
||||
if (auto it = settings_json.find("forward_exceptions"); it != settings_json.end()) {
|
||||
settings.forward_exceptions = it->get<bool>();
|
||||
}
|
||||
return settings;
|
||||
}
|
||||
|
||||
namespace {
|
||||
ModuleConfigurationParameters parse_config_parameters(const json& config_json) {
|
||||
ModuleConfigurationParameters config_maps;
|
||||
|
||||
auto parse_entry = [](const std::string& name, const json& jval) -> ConfigurationParameter {
|
||||
ConfigurationParameter param;
|
||||
param.name = name;
|
||||
param.characteristics.mutability = Mutability::ReadOnly;
|
||||
|
||||
if (jval.is_string()) {
|
||||
param.value = jval.get<std::string>();
|
||||
param.characteristics.datatype = Datatype::String;
|
||||
} else if (jval.is_boolean()) {
|
||||
param.value = jval.get<bool>();
|
||||
param.characteristics.datatype = Datatype::Boolean;
|
||||
} else if (jval.is_number_integer()) {
|
||||
param.value = jval.get<int>();
|
||||
param.characteristics.datatype = Datatype::Integer;
|
||||
} else if (jval.is_number_float()) {
|
||||
param.value = jval.get<double>();
|
||||
param.characteristics.datatype = Datatype::Decimal;
|
||||
} else {
|
||||
throw std::runtime_error("Unsupported JSON type for config parameter: " + name);
|
||||
}
|
||||
|
||||
return param;
|
||||
};
|
||||
|
||||
if (config_json.contains("config_module")) {
|
||||
const auto& config_module = config_json.at("config_module");
|
||||
for (auto config_entry = config_module.begin(); config_entry != config_module.end(); ++config_entry) {
|
||||
config_maps["!module"].push_back(parse_entry(config_entry.key(), config_entry.value()));
|
||||
}
|
||||
}
|
||||
|
||||
if (config_json.contains("config_implementation")) {
|
||||
const auto& config_implementations = config_json.at("config_implementation");
|
||||
for (auto impl = config_implementations.begin(); impl != config_implementations.end(); ++impl) {
|
||||
for (auto config_entry = impl.value().begin(); config_entry != impl.value().end(); ++config_entry) {
|
||||
config_maps[impl.key()].push_back(parse_entry(config_entry.key(), config_entry.value()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return config_maps;
|
||||
}
|
||||
|
||||
ModuleConnections parse_connections(const json& connections_json) {
|
||||
ModuleConnections connections;
|
||||
|
||||
for (auto requirement = connections_json.begin(); requirement != connections_json.end(); ++requirement) {
|
||||
for (const auto& connection : requirement.value()) {
|
||||
if (!connection.contains("module_id")) {
|
||||
throw ConfigParseException(ConfigParseException::MISSING_ENTRY, "module_id",
|
||||
"Missing 'module_id' in connection");
|
||||
}
|
||||
if (!connection.contains("implementation_id")) {
|
||||
throw ConfigParseException(ConfigParseException::MISSING_ENTRY, "implementation_id",
|
||||
"Missing 'implementation_id' in connection");
|
||||
}
|
||||
Fulfillment fulfillment;
|
||||
fulfillment.module_id = connection.at("module_id");
|
||||
fulfillment.implementation_id = connection.at("implementation_id");
|
||||
fulfillment.requirement = {requirement.key()};
|
||||
connections[requirement.key()].push_back(fulfillment);
|
||||
}
|
||||
}
|
||||
|
||||
return connections;
|
||||
}
|
||||
|
||||
ModuleConfig parse_module_config(const std::string& module_id, const json& module_json) {
|
||||
|
||||
if (!module_json.contains("module")) {
|
||||
throw ConfigParseException(ConfigParseException::MISSING_ENTRY, "module", "Missing 'module' in config");
|
||||
}
|
||||
|
||||
ModuleConfig module_config;
|
||||
module_config.module_id = module_id;
|
||||
module_config.module_name = module_json.at("module").get<std::string>();
|
||||
module_config.standalone = module_json.value("standalone", false);
|
||||
|
||||
if (module_json.contains("capabilities")) {
|
||||
module_config.capabilities = module_json["capabilities"].get<std::vector<std::string>>();
|
||||
}
|
||||
|
||||
if (module_json.contains("connections")) {
|
||||
module_config.connections = parse_connections(module_json.at("connections"));
|
||||
}
|
||||
|
||||
if (module_json.contains("mapping")) {
|
||||
module_config.mapping = parse_mapping(module_json.at("mapping"));
|
||||
}
|
||||
|
||||
if (module_json.contains("telemetry")) {
|
||||
module_config.telemetry_config = module_json.at("telemetry").get<TelemetryConfig>();
|
||||
}
|
||||
|
||||
if (module_json.contains("access")) {
|
||||
module_config.access = module_json.at("access").get<Access>();
|
||||
}
|
||||
|
||||
module_config.configuration_parameters = parse_config_parameters(module_json);
|
||||
|
||||
return module_config;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
ModuleTierMappings parse_mapping(const json& mapping_json) {
|
||||
ModuleTierMappings mapping_config;
|
||||
|
||||
if (mapping_json.contains("module") && mapping_json["module"].contains("evse")) {
|
||||
Mapping module_mapping(mapping_json["module"]["evse"].get<int32_t>());
|
||||
if (mapping_json["module"].contains("connector")) {
|
||||
module_mapping.connector = mapping_json["module"]["connector"].get<int32_t>();
|
||||
}
|
||||
mapping_config.module = module_mapping;
|
||||
}
|
||||
|
||||
if (mapping_json.contains("implementations")) {
|
||||
for (auto impl = mapping_json["implementations"].begin(); impl != mapping_json["implementations"].end();
|
||||
++impl) {
|
||||
Mapping impl_mapping(impl.value().at("evse").get<int32_t>());
|
||||
if (impl.value().contains("connector")) {
|
||||
impl_mapping.connector = impl.value().at("connector").get<int32_t>();
|
||||
}
|
||||
mapping_config.implementations[impl.key()] = impl_mapping;
|
||||
}
|
||||
}
|
||||
|
||||
return mapping_config;
|
||||
}
|
||||
|
||||
ConfigEntry parse_config_value(Datatype datatype, const std::string& value_str) {
|
||||
try {
|
||||
switch (datatype) {
|
||||
case Datatype::String:
|
||||
return value_str;
|
||||
case Datatype::Decimal:
|
||||
return std::stod(value_str);
|
||||
case Datatype::Integer:
|
||||
return std::stoi(value_str);
|
||||
case Datatype::Boolean:
|
||||
return value_str == "true" || value_str == "1";
|
||||
default:
|
||||
throw std::out_of_range("Unsupported datatype: " + datatype_to_string(datatype));
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
throw std::runtime_error("Failed to parse config value '" + value_str + "' as type " +
|
||||
datatype_to_string(datatype) + ": " + e.what());
|
||||
}
|
||||
}
|
||||
|
||||
ModuleConfigurations parse_module_configs(const json& active_modules_json) {
|
||||
ModuleConfigurations module_configs;
|
||||
|
||||
for (auto module = active_modules_json.begin(); module != active_modules_json.end(); ++module) {
|
||||
module_configs.insert({module.key(), parse_module_config(module.key(), module.value())});
|
||||
}
|
||||
|
||||
return module_configs;
|
||||
}
|
||||
|
||||
Datatype string_to_datatype(const std::string& str) {
|
||||
if (str == "string") {
|
||||
return Datatype::String;
|
||||
} else if (str == "number") {
|
||||
return Datatype::Decimal;
|
||||
} else if (str == "integer") {
|
||||
return Datatype::Integer;
|
||||
} else if (str == "boolean" or "bool") {
|
||||
return Datatype::Boolean;
|
||||
} else if (str == "unknown") {
|
||||
return Datatype::Unknown;
|
||||
}
|
||||
throw std::out_of_range("Could not convert: " + str + " to Datatype");
|
||||
}
|
||||
|
||||
std::string datatype_to_string(const Datatype datatype) {
|
||||
switch (datatype) {
|
||||
case Datatype::String:
|
||||
return "string";
|
||||
case Datatype::Decimal:
|
||||
return "number";
|
||||
case Datatype::Integer:
|
||||
return "integer";
|
||||
case Datatype::Boolean:
|
||||
return "bool";
|
||||
case Datatype::Unknown:
|
||||
return "unknown";
|
||||
}
|
||||
throw std::out_of_range("Could not convert Datatype to string");
|
||||
}
|
||||
|
||||
Mutability string_to_mutability(const std::string& str) {
|
||||
if (str == "ReadOnly") {
|
||||
return Mutability::ReadOnly;
|
||||
} else if (str == "ReadWrite") {
|
||||
return Mutability::ReadWrite;
|
||||
} else if (str == "WriteOnly") {
|
||||
return Mutability::WriteOnly;
|
||||
}
|
||||
throw std::out_of_range("Could not convert: " + str + " to Mutability");
|
||||
}
|
||||
|
||||
std::string mutability_to_string(const Mutability mutability) {
|
||||
switch (mutability) {
|
||||
case Mutability::ReadOnly:
|
||||
return "ReadOnly";
|
||||
case Mutability::ReadWrite:
|
||||
return "ReadWrite";
|
||||
case Mutability::WriteOnly:
|
||||
return "WriteOnly";
|
||||
}
|
||||
throw std::out_of_range("Could not convert Mutability to string");
|
||||
}
|
||||
|
||||
} // namespace everest::config
|
||||
|
||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||
|
||||
void adl_serializer<everest::config::ModuleConfigAccess>::to_json(nlohmann::json& j,
|
||||
const everest::config::ModuleConfigAccess& m) {
|
||||
j["allow_read"] = m.allow_read;
|
||||
j["allow_write"] = m.allow_write;
|
||||
j["allow_set_read_only"] = m.allow_set_read_only;
|
||||
}
|
||||
|
||||
void adl_serializer<everest::config::ModuleConfigAccess>::from_json(const nlohmann::json& j,
|
||||
everest::config::ModuleConfigAccess& m) {
|
||||
if (j.contains("allow_read")) {
|
||||
m.allow_read = j.at("allow_read").get<bool>();
|
||||
}
|
||||
if (j.contains("allow_write")) {
|
||||
m.allow_write = j.at("allow_write").get<bool>();
|
||||
}
|
||||
if (j.contains("allow_set_read_only")) {
|
||||
m.allow_set_read_only = j.at("allow_set_read_only").get<bool>();
|
||||
}
|
||||
}
|
||||
|
||||
void adl_serializer<everest::config::ConfigAccess>::to_json(nlohmann::json& j, const everest::config::ConfigAccess& c) {
|
||||
j["allow_global_read"] = c.allow_global_read;
|
||||
j["allow_global_write"] = c.allow_global_write;
|
||||
j["allow_set_read_only"] = c.allow_set_read_only;
|
||||
j["modules"] = c.modules;
|
||||
}
|
||||
|
||||
void adl_serializer<everest::config::ConfigAccess>::from_json(const nlohmann::json& j,
|
||||
everest::config::ConfigAccess& c) {
|
||||
if (j.contains("allow_global_read")) {
|
||||
c.allow_global_read = j.at("allow_global_read").get<bool>();
|
||||
}
|
||||
if (j.contains("allow_global_write")) {
|
||||
c.allow_global_write = j.at("allow_global_write").get<bool>();
|
||||
}
|
||||
if (j.contains("allow_set_read_only")) {
|
||||
c.allow_set_read_only = j.at("allow_set_read_only").get<bool>();
|
||||
}
|
||||
if (j.contains("modules")) {
|
||||
c.modules = j.at("modules").get<std::map<std::string, everest::config::ModuleConfigAccess>>();
|
||||
}
|
||||
}
|
||||
|
||||
void adl_serializer<everest::config::Access>::to_json(nlohmann::json& j, const everest::config::Access& a) {
|
||||
if (a.config.has_value()) {
|
||||
j["config"] = a.config.value();
|
||||
}
|
||||
}
|
||||
|
||||
void adl_serializer<everest::config::Access>::from_json(const nlohmann::json& j, everest::config::Access& a) {
|
||||
if (j.contains("config")) {
|
||||
a.config = j.at("config").get<everest::config::ConfigAccess>();
|
||||
}
|
||||
}
|
||||
|
||||
void adl_serializer<everest::config::ConfigurationParameterCharacteristics>::to_json(
|
||||
nlohmann::json& j, const everest::config::ConfigurationParameterCharacteristics& c) {
|
||||
j["datatype"] = datatype_to_string(c.datatype);
|
||||
j["mutability"] = mutability_to_string(c.mutability);
|
||||
if (c.unit.has_value()) {
|
||||
j["unit"] = c.unit.value();
|
||||
}
|
||||
}
|
||||
|
||||
void adl_serializer<everest::config::ConfigurationParameterCharacteristics>::from_json(
|
||||
const nlohmann::json& j, everest::config::ConfigurationParameterCharacteristics& c) {
|
||||
c.datatype = everest::config::string_to_datatype(j.at("datatype").get<std::string>());
|
||||
c.mutability = everest::config::string_to_mutability(j.at("mutability").get<std::string>());
|
||||
if (j.contains("unit")) {
|
||||
c.unit = j.at("unit").get<std::string>();
|
||||
}
|
||||
}
|
||||
|
||||
void adl_serializer<everest::config::ConfigEntry>::to_json(nlohmann::json& j,
|
||||
const everest::config::ConfigEntry& entry) {
|
||||
std::visit([&j](auto&& arg) { j = arg; }, entry);
|
||||
}
|
||||
|
||||
void adl_serializer<everest::config::ConfigEntry>::from_json(const nlohmann::json& j,
|
||||
everest::config::ConfigEntry& entry) {
|
||||
if (j.is_boolean()) {
|
||||
entry = j.get<bool>();
|
||||
} else if (j.is_number_integer()) {
|
||||
entry = j.get<int>();
|
||||
} else if (j.is_number_float()) {
|
||||
entry = j.get<double>();
|
||||
} else if (j.is_string()) {
|
||||
entry = j.get<std::string>();
|
||||
} else {
|
||||
throw std::runtime_error("Unsupported JSON type for ConfigEntry");
|
||||
}
|
||||
}
|
||||
|
||||
void adl_serializer<everest::config::ConfigurationParameter>::to_json(
|
||||
nlohmann::json& j, const everest::config::ConfigurationParameter& p) {
|
||||
j["name"] = p.name;
|
||||
j["value"] = p.value;
|
||||
j["characteristics"] = p.characteristics;
|
||||
}
|
||||
|
||||
void adl_serializer<everest::config::ConfigurationParameter>::from_json(const nlohmann::json& j,
|
||||
everest::config::ConfigurationParameter& p) {
|
||||
p.name = j.at("name").get<std::string>();
|
||||
p.value = j.at("value").get<everest::config::ConfigEntry>();
|
||||
p.characteristics = j.at("characteristics").get<everest::config::ConfigurationParameterCharacteristics>();
|
||||
}
|
||||
|
||||
void adl_serializer<everest::config::ModuleConfig>::to_json(nlohmann::json& j, const everest::config::ModuleConfig& m) {
|
||||
j["standalone"] = m.standalone;
|
||||
j["module_name"] = m.module_name;
|
||||
j["module_id"] = m.module_id;
|
||||
if (m.capabilities.has_value()) {
|
||||
j["capabilities"] = m.capabilities.value();
|
||||
}
|
||||
if (m.telemetry_config.has_value()) {
|
||||
j["telemetry_config"] = m.telemetry_config.value();
|
||||
}
|
||||
j["configuration_parameters"] = m.configuration_parameters;
|
||||
j["telemetry_enabled"] = m.telemetry_enabled;
|
||||
j["connections"] = m.connections;
|
||||
j["mapping"] = m.mapping;
|
||||
j["access"] = m.access;
|
||||
}
|
||||
|
||||
void adl_serializer<everest::config::ModuleConfig>::from_json(const nlohmann::json& j,
|
||||
everest::config::ModuleConfig& m) {
|
||||
m.standalone = j.at("standalone").get<bool>();
|
||||
m.module_name = j.at("module_name").get<std::string>();
|
||||
m.module_id = j.at("module_id").get<std::string>();
|
||||
if (j.contains("capabilities")) {
|
||||
m.capabilities = j.at("capabilities").get<std::vector<std::string>>();
|
||||
}
|
||||
if (j.contains("telemetry_config")) {
|
||||
m.telemetry_config = j.at("telemetry_config").get<TelemetryConfig>();
|
||||
}
|
||||
m.configuration_parameters = j.at("configuration_parameters").get<everest::config::ModuleConfigurationParameters>();
|
||||
m.telemetry_enabled = j.at("telemetry_enabled").get<bool>();
|
||||
m.connections = j.at("connections").get<everest::config::ModuleConnections>();
|
||||
m.mapping = j.at("mapping").get<ModuleTierMappings>();
|
||||
if (j.contains("access")) {
|
||||
m.access = j.at("access").get<everest::config::Access>();
|
||||
}
|
||||
}
|
||||
|
||||
NLOHMANN_JSON_NAMESPACE_END
|
||||
@@ -0,0 +1,109 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
#include <exception>
|
||||
|
||||
#include <everest/logging.hpp>
|
||||
|
||||
#include <utils/config/storage.hpp>
|
||||
#include <utils/config/storage_userconfig.hpp>
|
||||
#include <utils/conversions.hpp>
|
||||
#include <utils/yaml_loader.hpp>
|
||||
|
||||
namespace everest::config {
|
||||
|
||||
UserConfigStorage::UserConfigStorage(const fs::path& user_config_path) : user_config_path(user_config_path) {
|
||||
try {
|
||||
if (fs::exists(user_config_path)) {
|
||||
this->user_config = Everest::load_yaml(user_config_path);
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
EVLOG_error << "Could not load user-config at " << user_config_path.string() << ": " << e.what();
|
||||
}
|
||||
}
|
||||
|
||||
GenericResponseStatus UserConfigStorage::write_module_configs(const ModuleConfigurations& /*module_configs*/) {
|
||||
return GenericResponseStatus::Failed;
|
||||
}
|
||||
GenericResponseStatus UserConfigStorage::write_settings(const Everest::ManagerSettings& /*manager_settings*/) {
|
||||
return GenericResponseStatus::Failed;
|
||||
}
|
||||
GenericResponseStatus UserConfigStorage::wipe() {
|
||||
try {
|
||||
this->user_config = nlohmann::json::object();
|
||||
Everest::save_yaml(this->user_config, this->user_config_path);
|
||||
} catch (const std::exception& e) {
|
||||
EVLOG_error << "Could not save user-config to " << this->user_config_path.string() << ": " << e.what();
|
||||
return GenericResponseStatus::Failed;
|
||||
}
|
||||
return GenericResponseStatus::OK;
|
||||
}
|
||||
GetModuleConfigsResponse UserConfigStorage::get_module_configs() {
|
||||
GetModuleConfigsResponse response;
|
||||
response.status = GenericResponseStatus::Failed;
|
||||
return response;
|
||||
}
|
||||
GetSettingsResponse UserConfigStorage::get_settings() {
|
||||
GetSettingsResponse response;
|
||||
response.status = GenericResponseStatus::Failed;
|
||||
return response;
|
||||
}
|
||||
GetModuleConfigurationResponse UserConfigStorage::get_module_config(const std::string& /*module_id*/) {
|
||||
GetModuleConfigurationResponse response;
|
||||
response.status = GenericResponseStatus::Failed;
|
||||
return response;
|
||||
}
|
||||
GetConfigurationParameterResponse
|
||||
UserConfigStorage::get_configuration_parameter(const ConfigurationParameterIdentifier& /*identifier*/) {
|
||||
GetConfigurationParameterResponse response;
|
||||
response.status = GetSetResponseStatus::Failed;
|
||||
return response;
|
||||
}
|
||||
|
||||
GetSetResponseStatus
|
||||
UserConfigStorage::update_configuration_parameter(const ConfigurationParameterIdentifier& /*identifier*/,
|
||||
const std::string& /*value*/) {
|
||||
return GetSetResponseStatus::Failed;
|
||||
}
|
||||
|
||||
GetSetResponseStatus
|
||||
UserConfigStorage::write_configuration_parameter(const ConfigurationParameterIdentifier& identifier,
|
||||
const ConfigurationParameterCharacteristics characteristics,
|
||||
const std::string& value) {
|
||||
// TODO: expect module_id etc. to be valid already
|
||||
if (not this->user_config.contains("active_modules")) {
|
||||
this->user_config["active_modules"] = nlohmann::json::object();
|
||||
}
|
||||
auto& active_modules = this->user_config.at("active_modules");
|
||||
if (not active_modules.contains(identifier.module_id)) {
|
||||
active_modules[identifier.module_id] = json::object();
|
||||
}
|
||||
auto& module_cfg = active_modules[identifier.module_id];
|
||||
const auto impl_id = identifier.module_implementation_id.value_or("!module");
|
||||
if (impl_id == "!module") {
|
||||
if (not module_cfg.contains("config_module")) {
|
||||
module_cfg["config_module"] = json::object();
|
||||
}
|
||||
auto& config_module = module_cfg["config_module"];
|
||||
config_module[identifier.configuration_parameter_name] =
|
||||
everest::config::parse_config_value(characteristics.datatype, value);
|
||||
} else {
|
||||
if (not module_cfg.contains("config_implementation")) {
|
||||
module_cfg["config_implementation"] = json::object();
|
||||
}
|
||||
auto& config_implementation = module_cfg["config_implementation"];
|
||||
if (not config_implementation.contains(impl_id)) {
|
||||
config_implementation[impl_id] = json::object();
|
||||
}
|
||||
auto& config_implementation_impl_id = config_implementation[impl_id];
|
||||
config_implementation_impl_id[identifier.configuration_parameter_name] =
|
||||
everest::config::parse_config_value(characteristics.datatype, value);
|
||||
}
|
||||
|
||||
Everest::save_yaml(this->user_config, this->user_config_path);
|
||||
return GetSetResponseStatus::OK;
|
||||
}
|
||||
|
||||
const nlohmann::json& UserConfigStorage::get_user_config() const {
|
||||
return this->user_config;
|
||||
}
|
||||
} // namespace everest::config
|
||||
@@ -0,0 +1,14 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
#include <utils/config_cache.hpp>
|
||||
|
||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||
void adl_serializer<Everest::ConfigCache>::to_json(nlohmann::json& j, const Everest::ConfigCache& c) {
|
||||
j = {{"provides_impl", c.provides_impl}, {"cmds", c.cmds}};
|
||||
}
|
||||
|
||||
void adl_serializer<Everest::ConfigCache>::from_json(const nlohmann::json& j, Everest::ConfigCache& c) {
|
||||
c.provides_impl = j.at("provides_impl");
|
||||
c.cmds = j.at("cmds");
|
||||
}
|
||||
NLOHMANN_JSON_NAMESPACE_END
|
||||
645
tools/EVerest-main/lib/everest/framework/lib/config_service.cpp
Normal file
645
tools/EVerest-main/lib/everest/framework/lib/config_service.cpp
Normal file
@@ -0,0 +1,645 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include <exception>
|
||||
|
||||
#include <everest/logging.hpp>
|
||||
|
||||
#include <utils/config/storage.hpp>
|
||||
#include <utils/config/types.hpp>
|
||||
#include <utils/config_service.hpp>
|
||||
#include <utils/conversions.hpp>
|
||||
|
||||
namespace Everest {
|
||||
namespace config {
|
||||
|
||||
bool ModuleIdType::operator<(const ModuleIdType& rhs) const {
|
||||
return (this->module_id < rhs.module_id ||
|
||||
(this->module_id == rhs.module_id && this->module_type < rhs.module_type));
|
||||
}
|
||||
|
||||
enum class AccessMethod {
|
||||
Read,
|
||||
Write
|
||||
};
|
||||
|
||||
namespace {
|
||||
bool access_allowed(const everest::config::Access& access, const std::string& origin, const std::string& target,
|
||||
AccessMethod method) {
|
||||
if (origin == target) {
|
||||
// a module can always read and write its own config
|
||||
return true;
|
||||
}
|
||||
if (not access.config.has_value()) {
|
||||
return false;
|
||||
}
|
||||
const auto& config_access = access.config.value();
|
||||
const auto& config_access_modules_it = config_access.modules.find(target);
|
||||
switch (method) {
|
||||
case AccessMethod::Read:
|
||||
if (config_access.allow_global_read) {
|
||||
return true;
|
||||
}
|
||||
if (config_access_modules_it != config_access.modules.end()) {
|
||||
if (config_access_modules_it->second.allow_read) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case AccessMethod::Write:
|
||||
if (config_access.allow_global_write) {
|
||||
return true;
|
||||
}
|
||||
if (config_access_modules_it != config_access.modules.end()) {
|
||||
if (config_access_modules_it->second.allow_write) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
nlohmann::json get_module_config(const std::string& module_id, const ManagerConfig& config) {
|
||||
const auto& module_configurations = config.get_module_configurations();
|
||||
return get_serialized_module_config(module_id, module_configurations);
|
||||
}
|
||||
|
||||
everest::config::ModuleConfigurationParameters
|
||||
update_mutability(const everest::config::ModuleConfigurationParameters& configuration_parameters,
|
||||
bool allow_set_read_only = false) {
|
||||
if (not allow_set_read_only) {
|
||||
return configuration_parameters;
|
||||
}
|
||||
everest::config::ModuleConfigurationParameters updated_configuration_parameters = configuration_parameters;
|
||||
for (auto& [impl_id, config_params] : updated_configuration_parameters) {
|
||||
for (auto& config_param : config_params) {
|
||||
auto& mutability = config_param.characteristics.mutability;
|
||||
if (mutability == everest::config::Mutability::ReadOnly) {
|
||||
config_param.characteristics.mutability = everest::config::Mutability::ReadWrite;
|
||||
}
|
||||
}
|
||||
}
|
||||
return updated_configuration_parameters;
|
||||
}
|
||||
|
||||
GetResponse handle_get_module_config(const std::string& origin, const ManagerConfig& config) {
|
||||
GetResponse get_response;
|
||||
get_response.type = GetType::Module;
|
||||
get_response.data = get_module_config(origin, config);
|
||||
return get_response;
|
||||
}
|
||||
|
||||
Response handle_get_config_value(const GetRequest& get_request, const std::string& origin, ManagerConfig& config) {
|
||||
Response response;
|
||||
response.type = Type::Get;
|
||||
GetResponse get_response;
|
||||
get_response.type = GetType::Value;
|
||||
|
||||
if (not get_request.identifier.has_value()) {
|
||||
response.status = ResponseStatus::Error; // no identifier is always an error in this case
|
||||
response.status_info = "No identifier provided";
|
||||
} else {
|
||||
const auto identifier = get_request.identifier.value();
|
||||
const auto& module_configs = config.get_module_configurations();
|
||||
const everest::config::Access access = module_configs.at(origin).access;
|
||||
if (not access_allowed(access, origin, identifier.module_id, AccessMethod::Read)) {
|
||||
response.status = ResponseStatus::AccessDenied;
|
||||
response.status_info =
|
||||
fmt::format("Access to config item denied: {} cannot access {}", origin, identifier.module_id);
|
||||
} else {
|
||||
const auto get_config_value_response = config.get_config_value(identifier);
|
||||
if (get_config_value_response.status == everest::config::GetSetResponseStatus::OK) {
|
||||
if (get_config_value_response.configuration_parameter.has_value()) {
|
||||
get_response.data = get_config_value_response.configuration_parameter.value();
|
||||
response.status = ResponseStatus::Ok;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
response.response = get_response;
|
||||
return response;
|
||||
}
|
||||
|
||||
GetResponse handle_get_all_configs(const std::string& origin, const ManagerConfig& config) {
|
||||
// FIXME: we might run into size limits when the config is really large, split this up in the
|
||||
// future!
|
||||
GetResponse get_response;
|
||||
get_response.type = GetType::All;
|
||||
json all_configs = json::object();
|
||||
const auto& module_configs = config.get_module_configurations();
|
||||
everest::config::Access access = module_configs.at(origin).access;
|
||||
for (const auto& [module_id, module_name] : config.get_module_names()) {
|
||||
if (not access_allowed(access, origin, module_id, AccessMethod::Read)) {
|
||||
// request.origin has no access to module_id.config
|
||||
continue;
|
||||
}
|
||||
auto allow_set_read_only = false;
|
||||
if (access.config.has_value()) {
|
||||
const auto config_access = access.config.value();
|
||||
allow_set_read_only = config_access.allow_set_read_only;
|
||||
|
||||
if (not allow_set_read_only) {
|
||||
// check if allow_set_read_only is set for specific modules
|
||||
const auto module_config_access_it = config_access.modules.find(module_id);
|
||||
if (module_config_access_it != config_access.modules.end()) {
|
||||
allow_set_read_only = module_config_access_it->second.allow_set_read_only;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
all_configs[module_id] =
|
||||
update_mutability(module_configs.at(module_id).configuration_parameters, allow_set_read_only);
|
||||
}
|
||||
get_response.data = all_configs;
|
||||
return get_response;
|
||||
}
|
||||
|
||||
GetResponse handle_get_all_mappings(const std::string& origin, const ManagerConfig& config) {
|
||||
GetResponse get_response;
|
||||
get_response.type = GetType::AllMappings;
|
||||
json all_mappings = json::object();
|
||||
const auto& module_configs = config.get_module_configurations();
|
||||
const everest::config::Access access = module_configs.at(origin).access;
|
||||
for (const auto& [module_id, module_name] : config.get_module_names()) {
|
||||
if (not access_allowed(access, origin, module_id, AccessMethod::Read)) {
|
||||
// request.origin has no access to module_id.mappings
|
||||
continue;
|
||||
}
|
||||
all_mappings[module_id] = module_configs.at(module_id).mapping;
|
||||
}
|
||||
|
||||
get_response.data = all_mappings;
|
||||
return get_response;
|
||||
}
|
||||
|
||||
Response handle_set_request(const SetRequest& set_request, const std::string& origin, ManagerConfig& config) {
|
||||
Response response;
|
||||
response.type = Type::Set;
|
||||
SetResponse set_response;
|
||||
set_response.status = SetResponseStatus::Rejected;
|
||||
|
||||
const auto& module_configs = config.get_module_configurations();
|
||||
const everest::config::Access access = module_configs.at(origin).access;
|
||||
if (not access_allowed(access, origin, set_request.identifier.module_id, AccessMethod::Write)) {
|
||||
set_response.status = SetResponseStatus::Rejected;
|
||||
response.status = ResponseStatus::AccessDenied;
|
||||
response.status_info =
|
||||
fmt::format("Access to config item denied: {} cannot access {}", origin, set_request.identifier.module_id);
|
||||
} else {
|
||||
// TODO: explicit input validation
|
||||
const auto& target_module_config = module_configs.at(set_request.identifier.module_id);
|
||||
everest::config::SetConfigStatus status = everest::config::SetConfigStatus::Rejected;
|
||||
const auto impl_id = set_request.identifier.module_implementation_id.value_or(MODULE_IMPLEMENTATION_ID);
|
||||
for (const auto& config_entry : target_module_config.configuration_parameters.at(impl_id)) {
|
||||
if (config_entry.name == set_request.identifier.configuration_parameter_name) {
|
||||
try {
|
||||
const auto value = parse_config_value(config_entry.characteristics.datatype, set_request.value);
|
||||
status = config.set_config_value(set_request.identifier, value);
|
||||
response.status = ResponseStatus::Ok;
|
||||
} catch (const std::exception& e) {
|
||||
response.status = ResponseStatus::Error;
|
||||
response.status_info = fmt::format("Could not set config entry {} of module {}: {}",
|
||||
set_request.identifier.configuration_parameter_name,
|
||||
set_request.identifier.module_id, e.what());
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
set_response.status = conversions::set_config_status_to_set_response_status(status);
|
||||
}
|
||||
response.response = set_response;
|
||||
return response;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
ConfigServiceClient::ConfigServiceClient(std::shared_ptr<MQTTAbstraction> mqtt_abstraction,
|
||||
const std::string& module_id,
|
||||
const std::map<std::string, std::string, std::less<>>& module_names) :
|
||||
mqtt_abstraction(mqtt_abstraction), origin(module_id), module_names(module_names) {
|
||||
}
|
||||
|
||||
std::map<ModuleIdType, everest::config::ModuleConfigurationParameters> ConfigServiceClient::get_module_configs() {
|
||||
Request get_request;
|
||||
get_request.type = Type::Get;
|
||||
get_request.request = GetRequest{GetType::All};
|
||||
get_request.origin = this->origin;
|
||||
|
||||
MQTTRequest mqtt_request;
|
||||
mqtt_request.response_topic =
|
||||
fmt::format("{}modules/{}/response", mqtt_abstraction->get_everest_prefix(), get_request.origin);
|
||||
mqtt_request.request_topic = fmt::format("{}config/request", mqtt_abstraction->get_everest_prefix());
|
||||
mqtt_request.request_data = json(get_request).dump();
|
||||
|
||||
try {
|
||||
Response response = mqtt_abstraction->get(mqtt_request, mqtt_get_config_retries);
|
||||
if (response.status != ResponseStatus::Ok) {
|
||||
EVLOG_error << "Could not get module configs via MQTT";
|
||||
return {};
|
||||
}
|
||||
|
||||
GetResponse get_response = std::get<GetResponse>(response.response);
|
||||
|
||||
std::map<ModuleIdType, everest::config::ModuleConfigurationParameters> module_configs;
|
||||
for (const auto& [module_id, config_maps] : get_response.data.items()) {
|
||||
ModuleIdType module_id_type;
|
||||
module_id_type.module_id = module_id;
|
||||
module_id_type.module_type = module_names.at(module_id);
|
||||
module_configs[module_id_type] = config_maps;
|
||||
}
|
||||
|
||||
return module_configs;
|
||||
} catch (const std::exception& e) {
|
||||
EVLOG_error << "Could not get module configs via MQTT: " << e.what();
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
std::map<std::string, ModuleTierMappings> ConfigServiceClient::get_mappings() {
|
||||
Request get_request;
|
||||
get_request.type = Type::Get;
|
||||
get_request.request = GetRequest{GetType::AllMappings};
|
||||
get_request.origin = this->origin;
|
||||
|
||||
MQTTRequest mqtt_request;
|
||||
mqtt_request.response_topic =
|
||||
fmt::format("{}modules/{}/response", mqtt_abstraction->get_everest_prefix(), get_request.origin);
|
||||
mqtt_request.request_topic = fmt::format("{}config/request", mqtt_abstraction->get_everest_prefix());
|
||||
mqtt_request.request_data = json(get_request).dump();
|
||||
|
||||
try {
|
||||
Response response = mqtt_abstraction->get(mqtt_request, mqtt_get_config_retries);
|
||||
if (response.status != ResponseStatus::Ok) {
|
||||
EVLOG_error << "Could not get mappings configs via MQTT";
|
||||
return {};
|
||||
}
|
||||
|
||||
GetResponse get_response = std::get<GetResponse>(response.response);
|
||||
|
||||
std::map<std::string, ModuleTierMappings> mappings;
|
||||
for (const auto& [module_id, mapping] : get_response.data.items()) {
|
||||
mappings[module_id] = everest::config::parse_mapping(mapping);
|
||||
}
|
||||
|
||||
return mappings;
|
||||
} catch (const std::exception& e) {
|
||||
EVLOG_error << "Could not get mappings configs via MQTT: " << e.what();
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
SetConfigResult
|
||||
ConfigServiceClient::set_config_value(const everest::config::ConfigurationParameterIdentifier& identifier,
|
||||
const std::string& value) {
|
||||
SetConfigResult result;
|
||||
Request request;
|
||||
request.type = Type::Set;
|
||||
request.origin = this->origin;
|
||||
SetRequest set_request;
|
||||
set_request.identifier = identifier;
|
||||
set_request.value = value;
|
||||
request.request = set_request;
|
||||
|
||||
try {
|
||||
MQTTRequest mqtt_request;
|
||||
mqtt_request.response_topic =
|
||||
fmt::format("{}modules/{}/response", mqtt_abstraction->get_everest_prefix(), request.origin);
|
||||
mqtt_request.request_topic = fmt::format("{}config/request", mqtt_abstraction->get_everest_prefix());
|
||||
mqtt_request.request_data = json(request).dump();
|
||||
|
||||
const Response response = mqtt_abstraction->get(mqtt_request, mqtt_get_config_retries);
|
||||
result.status = response.status;
|
||||
result.status_info = response.status_info;
|
||||
if (response.status == ResponseStatus::Ok) {
|
||||
if (response.type.has_value() and response.type.value() == Type::Set) {
|
||||
const SetResponse set_response = std::get<SetResponse>(response.response);
|
||||
result.set_status = conversions::set_response_status_to_set_config_status(set_response.status);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
} catch (const std::exception& e) {
|
||||
EVLOG_info << "Could not set config value: " << identifier.module_id << ": "
|
||||
<< identifier.module_implementation_id.value_or(MODULE_IMPLEMENTATION_ID) << ": "
|
||||
<< identifier.configuration_parameter_name << ": " << e.what();
|
||||
result.status = ResponseStatus::Error;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
GetConfigResult
|
||||
ConfigServiceClient::get_config_value(const everest::config::ConfigurationParameterIdentifier& identifier) {
|
||||
GetConfigResult result;
|
||||
Request request;
|
||||
request.type = Type::Get;
|
||||
request.origin = this->origin;
|
||||
GetRequest get_request;
|
||||
get_request.type = GetType::Value;
|
||||
get_request.identifier = identifier;
|
||||
request.request = get_request;
|
||||
|
||||
try {
|
||||
MQTTRequest mqtt_request;
|
||||
mqtt_request.response_topic =
|
||||
fmt::format("{}modules/{}/response", mqtt_abstraction->get_everest_prefix(), request.origin);
|
||||
mqtt_request.request_topic = fmt::format("{}config/request", mqtt_abstraction->get_everest_prefix());
|
||||
mqtt_request.request_data = json(request).dump();
|
||||
const Response response = mqtt_abstraction->get(mqtt_request, mqtt_get_config_retries);
|
||||
result.status = response.status;
|
||||
result.status_info = response.status_info;
|
||||
if (response.status == ResponseStatus::Ok) {
|
||||
if (response.type.has_value() and response.type.value() == Type::Get) {
|
||||
const GetResponse get_response = std::get<GetResponse>(response.response);
|
||||
result.configuration_parameter = get_response.data;
|
||||
}
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
EVLOG_info << "Could not get config value: " << identifier.module_id << ": "
|
||||
<< identifier.module_implementation_id.value_or(MODULE_IMPLEMENTATION_ID) << ": "
|
||||
<< identifier.configuration_parameter_name << ": " << e.what();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
ConfigService::ConfigService(MQTTAbstraction& mqtt_abstraction, std::shared_ptr<ManagerConfig> config) :
|
||||
mqtt_abstraction(mqtt_abstraction), config(config) {
|
||||
|
||||
// TODO: thread-safe?
|
||||
|
||||
const Handler global_config_request_handler = [&mqtt_abstraction, config](const std::string& /*topic*/,
|
||||
const nlohmann::json& data) {
|
||||
Response response;
|
||||
response.status = ResponseStatus::Error;
|
||||
try {
|
||||
Request request = data;
|
||||
response.type = request.type;
|
||||
const auto response_topic =
|
||||
fmt::format("{}modules/{}/response", mqtt_abstraction.get_everest_prefix(), request.origin);
|
||||
|
||||
if (request.type == Type::Get) {
|
||||
const GetRequest get_request = std::get<GetRequest>(request.request);
|
||||
if (get_request.type == GetType::Module) {
|
||||
response.response = handle_get_module_config(request.origin, *config);
|
||||
response.status = ResponseStatus::Ok;
|
||||
} else if (get_request.type == GetType::Value) {
|
||||
response = handle_get_config_value(get_request, request.origin, *config);
|
||||
} else if (get_request.type == GetType::All) {
|
||||
response.response = handle_get_all_configs(request.origin, *config);
|
||||
response.status = ResponseStatus::Ok;
|
||||
} else if (get_request.type == GetType::AllMappings) {
|
||||
response.response = handle_get_all_mappings(request.origin, *config);
|
||||
response.status = ResponseStatus::Ok;
|
||||
}
|
||||
} else if (request.type == Type::Set) {
|
||||
response = handle_set_request(std::get<SetRequest>(request.request), request.origin, *config);
|
||||
}
|
||||
|
||||
MqttMessagePayload payload{MqttMessageType::GetConfigResponse, response};
|
||||
mqtt_abstraction.publish(response_topic, payload, QOS::QOS2);
|
||||
|
||||
} catch (const std::exception& e) {
|
||||
EVLOG_error << "Exception during handling of request: " << e.what();
|
||||
} catch (...) {
|
||||
EVLOG_error << "Could not parse request: " << data.dump();
|
||||
}
|
||||
};
|
||||
|
||||
const std::string global_config_request_topic =
|
||||
fmt::format("{}config/request", mqtt_abstraction.get_everest_prefix());
|
||||
this->get_config_token = std::make_shared<TypedHandler>(HandlerType::GetConfig,
|
||||
std::make_shared<Handler>(global_config_request_handler));
|
||||
mqtt_abstraction.register_handler(global_config_request_topic, this->get_config_token, QOS::QOS2);
|
||||
}
|
||||
|
||||
namespace conversions {
|
||||
std::string type_to_string(Type type) {
|
||||
switch (type) {
|
||||
case Type::Get:
|
||||
return "Get";
|
||||
case Type::Set:
|
||||
return "Set";
|
||||
case Type::Unknown:
|
||||
return "Unknown";
|
||||
}
|
||||
throw std::out_of_range("Could not convert Type to string");
|
||||
}
|
||||
|
||||
Type string_to_type(const std::string& type) {
|
||||
if (type == "Get") {
|
||||
return Type::Get;
|
||||
} else if (type == "Set") {
|
||||
return Type::Set;
|
||||
} else if (type == "Unknown") {
|
||||
return Type::Unknown;
|
||||
}
|
||||
throw std::out_of_range("Could not convert " + type + " to Type");
|
||||
}
|
||||
|
||||
std::string get_type_to_string(GetType type) {
|
||||
switch (type) {
|
||||
case GetType::All:
|
||||
return "All";
|
||||
case GetType::Module:
|
||||
return "Module";
|
||||
case GetType::Value:
|
||||
return "Value";
|
||||
case GetType::AllMappings:
|
||||
return "AllMappings";
|
||||
case GetType::Unknown:
|
||||
return "Unknown";
|
||||
}
|
||||
throw std::out_of_range("Could not convert GetType to string");
|
||||
}
|
||||
|
||||
GetType string_to_get_type(const std::string& type) {
|
||||
if (type == "All") {
|
||||
return GetType::All;
|
||||
} else if (type == "Module") {
|
||||
return GetType::Module;
|
||||
} else if (type == "Value") {
|
||||
return GetType::Value;
|
||||
} else if (type == "AllMappings") {
|
||||
return GetType::AllMappings;
|
||||
} else if (type == "Unknown") {
|
||||
return GetType::Unknown;
|
||||
}
|
||||
throw std::out_of_range("Could not convert " + type + " to GetType");
|
||||
}
|
||||
|
||||
std::string response_status_to_string(ResponseStatus status) {
|
||||
switch (status) {
|
||||
case ResponseStatus::Ok:
|
||||
return "Ok";
|
||||
case ResponseStatus::Error:
|
||||
return "Error";
|
||||
case ResponseStatus::AccessDenied:
|
||||
return "AccessDenied";
|
||||
}
|
||||
throw std::out_of_range("Could not convert ResponseStatus to string");
|
||||
}
|
||||
|
||||
ResponseStatus string_to_response_status(const std::string& status) {
|
||||
if (status == "Ok") {
|
||||
return ResponseStatus::Ok;
|
||||
} else if (status == "Error") {
|
||||
return ResponseStatus::Error;
|
||||
} else if (status == "AccessDenied") {
|
||||
return ResponseStatus::AccessDenied;
|
||||
}
|
||||
throw std::out_of_range("Could not convert " + status + " to ResponseStatus");
|
||||
}
|
||||
|
||||
std::string set_response_status_to_string(SetResponseStatus status) {
|
||||
switch (status) {
|
||||
case SetResponseStatus::Accepted:
|
||||
return "Accepted";
|
||||
case SetResponseStatus::Rejected:
|
||||
return "Rejected";
|
||||
case SetResponseStatus::RebootRequired:
|
||||
return "RebootRequired";
|
||||
}
|
||||
throw std::out_of_range("Could not convert SetResponseStatus to string");
|
||||
}
|
||||
|
||||
SetResponseStatus string_to_set_response_status(const std::string& status) {
|
||||
if (status == "Accepted") {
|
||||
return SetResponseStatus::Accepted;
|
||||
} else if (status == "Rejected") {
|
||||
return SetResponseStatus::Rejected;
|
||||
} else if (status == "RebootRequired") {
|
||||
return SetResponseStatus::RebootRequired;
|
||||
}
|
||||
throw std::out_of_range("Could not convert " + status + " to SetResponseStatus");
|
||||
}
|
||||
|
||||
everest::config::SetConfigStatus set_response_status_to_set_config_status(SetResponseStatus status) {
|
||||
switch (status) {
|
||||
case SetResponseStatus::Accepted:
|
||||
return everest::config::SetConfigStatus::Accepted;
|
||||
case SetResponseStatus::Rejected:
|
||||
return everest::config::SetConfigStatus::Rejected;
|
||||
case SetResponseStatus::RebootRequired:
|
||||
return everest::config::SetConfigStatus::RebootRequired;
|
||||
}
|
||||
throw std::out_of_range("Could not convert SetResponseStatus to SetConfigStatus");
|
||||
}
|
||||
|
||||
SetResponseStatus set_config_status_to_set_response_status(everest::config::SetConfigStatus status) {
|
||||
switch (status) {
|
||||
case everest::config::SetConfigStatus::Accepted:
|
||||
return SetResponseStatus::Accepted;
|
||||
case everest::config::SetConfigStatus::Rejected:
|
||||
return SetResponseStatus::Rejected;
|
||||
case everest::config::SetConfigStatus::RebootRequired:
|
||||
return SetResponseStatus::RebootRequired;
|
||||
}
|
||||
throw std::out_of_range("Could not convert SetConfigStatus to SetResponseStatus");
|
||||
}
|
||||
} // namespace conversions
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const GetType& t) {
|
||||
os << conversions::get_type_to_string(t);
|
||||
return os;
|
||||
}
|
||||
|
||||
void to_json(nlohmann::json& j, const GetRequest& r) {
|
||||
j = {{"type", conversions::get_type_to_string(r.type)}};
|
||||
if (r.identifier.has_value()) {
|
||||
j["identifier"] = r.identifier.value();
|
||||
}
|
||||
}
|
||||
|
||||
void from_json(const nlohmann::json& j, GetRequest& r) {
|
||||
r.type = conversions::string_to_get_type(j.at("type"));
|
||||
if (j.contains("identifier")) {
|
||||
r.identifier = j.at("identifier");
|
||||
}
|
||||
}
|
||||
|
||||
void to_json(nlohmann::json& j, const SetRequest& r) {
|
||||
j = {{"value", r.value}};
|
||||
j["identifier"] = nlohmann::json(r.identifier);
|
||||
}
|
||||
|
||||
void from_json(const nlohmann::json& j, SetRequest& r) {
|
||||
r.identifier = j.at("identifier");
|
||||
r.value = j.at("value");
|
||||
}
|
||||
|
||||
void to_json(nlohmann::json& j, const GetResponse& r) {
|
||||
j = {{"type", conversions::get_type_to_string(r.type)}, {"data", r.data}};
|
||||
}
|
||||
|
||||
void from_json(const nlohmann::json& j, GetResponse& r) {
|
||||
r.type = conversions::string_to_get_type(j.at("type"));
|
||||
r.data = j.at("data");
|
||||
}
|
||||
|
||||
void to_json(nlohmann::json& j, const SetResponse& r) {
|
||||
j = {{"status", conversions::set_response_status_to_string(r.status)}};
|
||||
}
|
||||
|
||||
void from_json(const nlohmann::json& j, SetResponse& r) {
|
||||
r.status = conversions::string_to_set_response_status(j.at("status"));
|
||||
}
|
||||
|
||||
void to_json(nlohmann::json& j, const Request& r) {
|
||||
j = {{"type", conversions::type_to_string(r.type)}, {"origin", r.origin}};
|
||||
j["request"] = Everest::variant_to_json(r.request);
|
||||
}
|
||||
|
||||
void from_json(const nlohmann::json& j, Request& r) {
|
||||
r.type = conversions::string_to_type(j.at("type"));
|
||||
if (r.type == Type::Get) {
|
||||
r.request = j.at("request").get<GetRequest>();
|
||||
} else if (r.type == Type::Set) {
|
||||
r.request = j.at("request").get<SetRequest>();
|
||||
}
|
||||
r.origin = j.at("origin");
|
||||
}
|
||||
|
||||
void to_json(nlohmann::json& j, const Response& r) {
|
||||
j = {{"status", conversions::response_status_to_string(r.status)}, {"status_info", r.status_info}};
|
||||
if (r.type.has_value()) {
|
||||
j["type"] = conversions::type_to_string(r.type.value());
|
||||
}
|
||||
j["response"] = Everest::variant_to_json(r.response);
|
||||
}
|
||||
|
||||
void from_json(const nlohmann::json& j, Response& r) {
|
||||
r.status = conversions::string_to_response_status(j.at("status"));
|
||||
r.status_info = j.at("status_info");
|
||||
if (j.contains("type")) {
|
||||
Type type = conversions::string_to_type(j.at("type"));
|
||||
r.type = type;
|
||||
|
||||
if (type == Type::Get) {
|
||||
r.response = j.at("response").get<GetResponse>();
|
||||
} else if (type == Type::Set) {
|
||||
r.response = j.at("response").get<SetResponse>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace config
|
||||
} // namespace Everest
|
||||
|
||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||
void adl_serializer<everest::config::ConfigurationParameterIdentifier>::to_json(
|
||||
nlohmann::json& j, const everest::config::ConfigurationParameterIdentifier& c) {
|
||||
j = {{"module_id", c.module_id}, {"configuration_parameter_name", c.configuration_parameter_name}};
|
||||
if (c.module_implementation_id.has_value()) {
|
||||
j["module_implementation_id"] = c.module_implementation_id.value();
|
||||
}
|
||||
}
|
||||
|
||||
void adl_serializer<everest::config::ConfigurationParameterIdentifier>::from_json(
|
||||
const nlohmann::json& j, everest::config::ConfigurationParameterIdentifier& c) {
|
||||
c.module_id = j.at("module_id");
|
||||
c.configuration_parameter_name = j.at("configuration_parameter_name");
|
||||
if (j.contains("module_implementation_id")) {
|
||||
c.module_implementation_id = j.at("module_implementation_id");
|
||||
}
|
||||
}
|
||||
NLOHMANN_JSON_NAMESPACE_END
|
||||
61
tools/EVerest-main/lib/everest/framework/lib/conversions.cpp
Normal file
61
tools/EVerest-main/lib/everest/framework/lib/conversions.cpp
Normal file
@@ -0,0 +1,61 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
#include <utils/conversions.hpp>
|
||||
|
||||
namespace Everest {
|
||||
namespace conversions {
|
||||
constexpr auto CMD_ERROR_TYPE_MESSAGE_PARSING_ERROR = "MessageParsingError";
|
||||
constexpr auto CMD_ERROR_TYPE_SCHEMA_VALIDATION_ERROR = "SchemaValidationError";
|
||||
constexpr auto CMD_ERROR_TYPE_HANDLER_EXCEPTION = "HandlerException";
|
||||
constexpr auto CMD_ERROR_TYPE_CMD_TIMEOUT = "CmdTimeout";
|
||||
constexpr auto CMD_ERROR_TYPE_SHUTDOWN = "Shutdown";
|
||||
constexpr auto CMD_ERROR_TYPE_NOT_READY = "NotReady";
|
||||
|
||||
std::string cmd_error_type_to_string(CmdErrorType cmd_error) {
|
||||
switch (cmd_error) {
|
||||
case CmdErrorType::MessageParsingError:
|
||||
return CMD_ERROR_TYPE_MESSAGE_PARSING_ERROR;
|
||||
case CmdErrorType::SchemaValidationError:
|
||||
return CMD_ERROR_TYPE_SCHEMA_VALIDATION_ERROR;
|
||||
case CmdErrorType::HandlerException:
|
||||
return CMD_ERROR_TYPE_HANDLER_EXCEPTION;
|
||||
case CmdErrorType::CmdTimeout:
|
||||
return CMD_ERROR_TYPE_CMD_TIMEOUT;
|
||||
case CmdErrorType::Shutdown:
|
||||
return CMD_ERROR_TYPE_SHUTDOWN;
|
||||
case CmdErrorType::NotReady:
|
||||
return CMD_ERROR_TYPE_NOT_READY;
|
||||
}
|
||||
|
||||
throw std::runtime_error("Unknown CmdError");
|
||||
}
|
||||
|
||||
CmdErrorType string_to_cmd_error_type(const std::string& cmd_error_string) {
|
||||
if (cmd_error_string == CMD_ERROR_TYPE_MESSAGE_PARSING_ERROR) {
|
||||
return CmdErrorType::MessageParsingError;
|
||||
} else if (cmd_error_string == CMD_ERROR_TYPE_SCHEMA_VALIDATION_ERROR) {
|
||||
return CmdErrorType::SchemaValidationError;
|
||||
} else if (cmd_error_string == CMD_ERROR_TYPE_HANDLER_EXCEPTION) {
|
||||
return CmdErrorType::HandlerException;
|
||||
} else if (cmd_error_string == CMD_ERROR_TYPE_CMD_TIMEOUT) {
|
||||
return CmdErrorType::CmdTimeout;
|
||||
} else if (cmd_error_string == CMD_ERROR_TYPE_SHUTDOWN) {
|
||||
return CmdErrorType::Shutdown;
|
||||
} else if (cmd_error_string == CMD_ERROR_TYPE_NOT_READY) {
|
||||
return CmdErrorType::NotReady;
|
||||
}
|
||||
|
||||
throw std::runtime_error("Unknown CmdError");
|
||||
}
|
||||
} // namespace conversions
|
||||
|
||||
void to_json(nlohmann::json& j, const CmdResultError& e) {
|
||||
j = {{conversions::ERROR_TYPE, conversions::cmd_error_type_to_string(e.event)}, {conversions::ERROR_MSG, e.msg}};
|
||||
}
|
||||
|
||||
void from_json(const nlohmann::json& j, CmdResultError& e) {
|
||||
e.event = conversions::string_to_cmd_error_type(j.at(conversions::ERROR_TYPE));
|
||||
e.msg = j.at(conversions::ERROR_MSG);
|
||||
}
|
||||
|
||||
} // namespace Everest
|
||||
101
tools/EVerest-main/lib/everest/framework/lib/date.cpp
Normal file
101
tools/EVerest-main/lib/everest/framework/lib/date.cpp
Normal file
@@ -0,0 +1,101 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2026 Pionix GmbH and Contributors to EVerest
|
||||
#include <charconv>
|
||||
#include <string_view>
|
||||
|
||||
#include <utils/date.hpp>
|
||||
|
||||
namespace Everest {
|
||||
namespace Date {
|
||||
|
||||
std::string to_rfc3339(const std::chrono::time_point<date::utc_clock>& t) {
|
||||
return date::format("%FT%TZ", std::chrono::time_point_cast<std::chrono::milliseconds>(t));
|
||||
}
|
||||
|
||||
std::chrono::time_point<date::utc_clock> from_rfc3339_slow(const std::string& t) {
|
||||
std::istringstream infile{t};
|
||||
std::chrono::time_point<date::utc_clock> tp;
|
||||
infile >> date::parse("%FT%T", tp);
|
||||
return tp;
|
||||
}
|
||||
|
||||
constexpr std::size_t typical_len = 24;
|
||||
|
||||
std::chrono::time_point<date::utc_clock> from_rfc3339(const std::string& t) {
|
||||
std::string_view input(t);
|
||||
// attempt a slow parse if input size doesn't match our expectations
|
||||
if (input.size() < typical_len) {
|
||||
return from_rfc3339_slow(t);
|
||||
}
|
||||
std::string_view y_str = input.substr(0, 4);
|
||||
std::string_view m_str = input.substr(5, 2);
|
||||
std::string_view d_str = input.substr(8, 2);
|
||||
std::string_view h_str = input.substr(11, 2);
|
||||
std::string_view M_str = input.substr(14, 2);
|
||||
std::string_view s_str = input.substr(17, 2);
|
||||
std::string_view ms_str = input.substr(20, 3);
|
||||
|
||||
int y;
|
||||
{
|
||||
auto [ptr, ec] = std::from_chars(y_str.data(), y_str.data() + y_str.size(), y);
|
||||
if (ec != std::errc{}) {
|
||||
return from_rfc3339_slow(t);
|
||||
}
|
||||
}
|
||||
|
||||
int m;
|
||||
{
|
||||
auto [ptr, ec] = std::from_chars(m_str.data(), m_str.data() + m_str.size(), m);
|
||||
if (ec != std::errc{}) {
|
||||
return from_rfc3339_slow(t);
|
||||
}
|
||||
}
|
||||
|
||||
int d;
|
||||
{
|
||||
auto [ptr, ec] = std::from_chars(d_str.data(), d_str.data() + d_str.size(), d);
|
||||
if (ec != std::errc{}) {
|
||||
return from_rfc3339_slow(t);
|
||||
}
|
||||
}
|
||||
|
||||
int h;
|
||||
|
||||
{
|
||||
auto [ptr, ec] = std::from_chars(h_str.data(), h_str.data() + h_str.size(), h);
|
||||
if (ec != std::errc{}) {
|
||||
return from_rfc3339_slow(t);
|
||||
}
|
||||
}
|
||||
|
||||
int M;
|
||||
{
|
||||
auto [ptr, ec] = std::from_chars(M_str.data(), M_str.data() + M_str.size(), M);
|
||||
if (ec != std::errc{}) {
|
||||
return from_rfc3339_slow(t);
|
||||
}
|
||||
}
|
||||
|
||||
int s;
|
||||
{
|
||||
auto [ptr, ec] = std::from_chars(s_str.data(), s_str.data() + s_str.size(), s);
|
||||
if (ec != std::errc{}) {
|
||||
return from_rfc3339_slow(t);
|
||||
}
|
||||
}
|
||||
|
||||
int ms;
|
||||
{
|
||||
auto [ptr, ec] = std::from_chars(ms_str.data(), ms_str.data() + ms_str.size(), ms);
|
||||
if (ec != std::errc{}) {
|
||||
return from_rfc3339_slow(t);
|
||||
}
|
||||
}
|
||||
|
||||
using namespace date;
|
||||
using namespace std::chrono;
|
||||
return utc_clock::from_sys(sys_days{year{y} / m / d} + hours{h} + minutes{M} + seconds{s} + milliseconds{ms});
|
||||
}
|
||||
|
||||
} // namespace Date
|
||||
} // namespace Everest
|
||||
119
tools/EVerest-main/lib/everest/framework/lib/error/error.cpp
Normal file
119
tools/EVerest-main/lib/everest/framework/lib/error/error.cpp
Normal file
@@ -0,0 +1,119 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
#include <utils/error.hpp>
|
||||
|
||||
#include <everest/helpers/helpers.hpp>
|
||||
|
||||
#include <everest/logging.hpp>
|
||||
|
||||
namespace Everest {
|
||||
namespace error {
|
||||
|
||||
UUID::UUID() {
|
||||
uuid = everest::helpers::get_uuid();
|
||||
}
|
||||
|
||||
UUID::UUID(const std::string& uuid_) : uuid(uuid_) {
|
||||
}
|
||||
|
||||
bool UUID::operator<(const UUID& other) const {
|
||||
return uuid < other.uuid;
|
||||
}
|
||||
|
||||
bool UUID::operator==(const UUID& other) const {
|
||||
return this->uuid == other.uuid;
|
||||
}
|
||||
|
||||
bool UUID::operator!=(const UUID& other) const {
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
std::string UUID::to_string() const {
|
||||
return uuid;
|
||||
}
|
||||
|
||||
Error::Error(const ErrorType& type_, const ErrorSubType& sub_type_, const std::string& message_,
|
||||
const std::string& description_, const ImplementationIdentifier& origin_, const std::string& vendor_id_,
|
||||
const Severity& severity_, const time_point& timestamp_, const UUID& uuid_, const State& state_) :
|
||||
type(type_),
|
||||
sub_type(sub_type_),
|
||||
message(message_),
|
||||
description(description_),
|
||||
origin(origin_),
|
||||
vendor_id(vendor_id_),
|
||||
severity(severity_),
|
||||
timestamp(timestamp_),
|
||||
uuid(uuid_),
|
||||
state(state_) {
|
||||
}
|
||||
|
||||
Error::Error(const ErrorType& type_, const ErrorSubType& sub_type_, const std::string& message_,
|
||||
const std::string& description_, const ImplementationIdentifier& origin_, const Severity& severity_) :
|
||||
Error(type_, sub_type_, message_, description_, origin_, UTILS_ERROR_DEFAULTS_VENDOR_ID, severity_,
|
||||
UTILS_ERROR_DEFAULTS_TIMESTAMP, UTILS_ERROR_DEFAULTS_UUID) {
|
||||
}
|
||||
|
||||
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_, std::nullopt), severity_) {
|
||||
}
|
||||
|
||||
Error::Error() :
|
||||
Error(UTILS_ERROR_DEFAULTS_TYPE, UTILS_ERROR_DEFAULTS_SUB_TYPE, UTILS_ERROR_DEFAULTS_MESSAGE,
|
||||
UTILS_ERROR_DEFAULTS_DESCRIPTION, UTILS_ERROR_DEFAULTS_ORIGIN, UTILS_ERROR_DEFAULTS_SEVERITY) {
|
||||
}
|
||||
|
||||
std::string severity_to_string(const Severity& s) {
|
||||
switch (s) {
|
||||
case Severity::Low:
|
||||
return "Low";
|
||||
case Severity::Medium:
|
||||
return "Medium";
|
||||
case Severity::High:
|
||||
return "High";
|
||||
}
|
||||
EVLOG_error << "No known string conversion for provided enum of type Severity. Defaulting to High.";
|
||||
return "High";
|
||||
}
|
||||
|
||||
Severity string_to_severity(const std::string& s) {
|
||||
if (s == "Low") {
|
||||
return Severity::Low;
|
||||
} else if (s == "Medium") {
|
||||
return Severity::Medium;
|
||||
} else if (s == "High") {
|
||||
return Severity::High;
|
||||
}
|
||||
EVLOG_error << "Provided string " << s << " could not be converted to enum of type Severity. Defaulting to High.";
|
||||
return Severity::High;
|
||||
}
|
||||
|
||||
std::string state_to_string(const State& s) {
|
||||
switch (s) {
|
||||
case State::Active:
|
||||
return "Active";
|
||||
case State::ClearedByModule:
|
||||
return "ClearedByModule";
|
||||
case State::ClearedByReboot:
|
||||
return "ClearedByReboot";
|
||||
}
|
||||
EVLOG_error << "No known string conversion for provided enum of type State. Defaulting to Active.";
|
||||
return "Active";
|
||||
}
|
||||
|
||||
State string_to_state(const std::string& s) {
|
||||
if (s == "Active") {
|
||||
return State::Active;
|
||||
} else if (s == "ClearedByModule") {
|
||||
return State::ClearedByModule;
|
||||
} else if (s == "ClearedByReboot") {
|
||||
return State::ClearedByReboot;
|
||||
}
|
||||
EVLOG_error << "Provided string " << s << " could not be converted to enum of type State. Defaulting to Active.";
|
||||
return State::Active;
|
||||
}
|
||||
|
||||
} // namespace error
|
||||
} // namespace Everest
|
||||
@@ -0,0 +1,110 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include <utils/error/error_database_map.hpp>
|
||||
|
||||
#include <everest/logging.hpp>
|
||||
#include <utils/error.hpp>
|
||||
#include <utils/error/error_json.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace Everest {
|
||||
namespace error {
|
||||
|
||||
void ErrorDatabaseMap::add_error(ErrorPtr error) {
|
||||
const std::lock_guard<std::mutex> lock(this->errors_mutex);
|
||||
if (this->errors.find(error->uuid) != this->errors.end()) {
|
||||
std::stringstream ss;
|
||||
ss << "Error with handle " << error->uuid.to_string() << " already exists in ErrorDatabaseMap." << std::endl;
|
||||
ss << "Error object: " << nlohmann::json(*error).dump(2);
|
||||
EVLOG_error << ss.str();
|
||||
return;
|
||||
}
|
||||
this->errors[error->uuid] = error;
|
||||
}
|
||||
|
||||
std::list<ErrorPtr> ErrorDatabaseMap::get_errors(const std::list<ErrorFilter>& filters) const {
|
||||
const std::lock_guard<std::mutex> lock(this->errors_mutex);
|
||||
return this->get_errors_no_mutex(filters);
|
||||
}
|
||||
|
||||
std::list<ErrorPtr> ErrorDatabaseMap::get_errors_no_mutex(const std::list<ErrorFilter>& filters) const {
|
||||
BOOST_LOG_FUNCTION();
|
||||
|
||||
std::list<ErrorPtr> result;
|
||||
std::transform(this->errors.begin(), this->errors.end(), std::back_inserter(result),
|
||||
[](const std::pair<ErrorHandle, ErrorPtr>& entry) { return entry.second; });
|
||||
|
||||
for (const ErrorFilter& filter : filters) {
|
||||
std::function<bool(const ErrorPtr&)> pred;
|
||||
switch (filter.get_filter_type()) {
|
||||
case FilterType::State: {
|
||||
pred = []([[maybe_unused]] const ErrorPtr& error) { return false; };
|
||||
EVLOG_error << "ErrorDatabaseMap does not support StateFilter. Ignoring.";
|
||||
} break;
|
||||
case FilterType::Origin: {
|
||||
pred = [&filter](const ErrorPtr& error) { return error->origin != filter.get_origin_filter(); };
|
||||
} break;
|
||||
case FilterType::Type: {
|
||||
pred = [&filter](const ErrorPtr& error) { return error->type != filter.get_type_filter().value; };
|
||||
} break;
|
||||
case FilterType::Severity: {
|
||||
pred = [&filter](const ErrorPtr& error) {
|
||||
switch (filter.get_severity_filter()) {
|
||||
case SeverityFilter::LOW_GE: {
|
||||
return error->severity < Severity::Low;
|
||||
} break;
|
||||
case SeverityFilter::MEDIUM_GE: {
|
||||
return error->severity < Severity::Medium;
|
||||
} break;
|
||||
case SeverityFilter::HIGH_GE: {
|
||||
return error->severity < Severity::High;
|
||||
} break;
|
||||
}
|
||||
EVLOG_error << "No known condition for provided enum of type SeverityFilter.";
|
||||
return false;
|
||||
};
|
||||
} break;
|
||||
case FilterType::TimePeriod: {
|
||||
pred = [&filter](const ErrorPtr& error) {
|
||||
return error->timestamp < filter.get_time_period_filter().from ||
|
||||
error->timestamp > filter.get_time_period_filter().to;
|
||||
};
|
||||
} break;
|
||||
case FilterType::Handle: {
|
||||
pred = [&filter](const ErrorPtr& error) { return error->uuid != filter.get_handle_filter(); };
|
||||
} break;
|
||||
case FilterType::SubType: {
|
||||
pred = [&filter](const ErrorPtr& error) { return error->sub_type != filter.get_sub_type_filter().value; };
|
||||
} break;
|
||||
case FilterType::VendorId: {
|
||||
pred = [&filter](const ErrorPtr& error) { return error->vendor_id != filter.get_vendor_id_filter().value; };
|
||||
} break;
|
||||
default:
|
||||
EVLOG_error << "No known pred for provided enum of type FilterType. Ignoring.";
|
||||
return result;
|
||||
}
|
||||
result.remove_if(pred);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::list<ErrorPtr> ErrorDatabaseMap::edit_errors(const std::list<ErrorFilter>& filters, EditErrorFunc edit_func) {
|
||||
const std::lock_guard<std::mutex> lock(this->errors_mutex);
|
||||
std::list<ErrorPtr> result = this->get_errors_no_mutex(filters);
|
||||
for (const ErrorPtr& error : result) {
|
||||
edit_func(error);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::list<ErrorPtr> ErrorDatabaseMap::remove_errors(const std::list<ErrorFilter>& filters) {
|
||||
BOOST_LOG_FUNCTION();
|
||||
const EditErrorFunc remove_func = [this](const ErrorPtr& error) { this->errors.erase(error->uuid); };
|
||||
return this->edit_errors(filters, remove_func);
|
||||
}
|
||||
|
||||
} // namespace error
|
||||
} // namespace Everest
|
||||
@@ -0,0 +1,185 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
#include <utility>
|
||||
|
||||
#include <utils/error/error_factory.hpp>
|
||||
#include <utils/error/error_type_map.hpp>
|
||||
|
||||
namespace Everest {
|
||||
namespace error {
|
||||
|
||||
ErrorFactory::ErrorFactory(std::shared_ptr<ErrorTypeMap> error_type_map_) :
|
||||
ErrorFactory(error_type_map_, std::nullopt, std::nullopt, std::nullopt, std::nullopt, std::nullopt, std::nullopt,
|
||||
std::nullopt) {
|
||||
}
|
||||
|
||||
ErrorFactory::ErrorFactory(std::shared_ptr<ErrorTypeMap> error_type_map_, ImplementationIdentifier default_origin_) :
|
||||
ErrorFactory(error_type_map_, default_origin_, std::nullopt, std::nullopt, std::nullopt, std::nullopt, std::nullopt,
|
||||
std::nullopt) {
|
||||
}
|
||||
|
||||
ErrorFactory::ErrorFactory(std::shared_ptr<ErrorTypeMap> error_type_map_, ImplementationIdentifier default_origin_,
|
||||
Severity default_severity_) :
|
||||
ErrorFactory(error_type_map_, default_origin_, default_severity_, std::nullopt, std::nullopt, std::nullopt,
|
||||
std::nullopt, std::nullopt) {
|
||||
}
|
||||
|
||||
ErrorFactory::ErrorFactory(std::shared_ptr<ErrorTypeMap> error_type_map_,
|
||||
std::optional<ImplementationIdentifier> default_origin_,
|
||||
std::optional<Severity> default_severity_, std::optional<State> default_state_,
|
||||
std::optional<ErrorType> default_type_, std::optional<ErrorSubType> default_sub_type_,
|
||||
std::optional<std::string> default_message_, std::optional<std::string> default_vendor_id_) :
|
||||
error_type_map(error_type_map_),
|
||||
default_origin(std::move(default_origin_)),
|
||||
default_severity(default_severity_),
|
||||
default_state(default_state_),
|
||||
default_type(std::move(default_type_)),
|
||||
default_sub_type(std::move(default_sub_type_)),
|
||||
default_message(std::move(default_message_)),
|
||||
default_vendor_id(std::move(default_vendor_id_)) {
|
||||
}
|
||||
|
||||
Error ErrorFactory::create_error() const {
|
||||
Error error;
|
||||
if (default_origin.has_value()) {
|
||||
error.origin = default_origin.value();
|
||||
}
|
||||
if (default_severity.has_value()) {
|
||||
error.severity = default_severity.value();
|
||||
}
|
||||
if (default_state.has_value()) {
|
||||
error.state = default_state.value();
|
||||
}
|
||||
if (default_type.has_value()) {
|
||||
error.type = default_type.value();
|
||||
}
|
||||
if (default_sub_type.has_value()) {
|
||||
error.sub_type = default_sub_type.value();
|
||||
}
|
||||
if (default_message.has_value()) {
|
||||
error.message = default_message.value();
|
||||
}
|
||||
if (default_vendor_id.has_value()) {
|
||||
error.vendor_id = default_vendor_id.value();
|
||||
}
|
||||
set_description(error);
|
||||
return error;
|
||||
}
|
||||
|
||||
Error ErrorFactory::create_error(const ErrorType& type, const ErrorSubType& sub_type,
|
||||
const std::string& message) const {
|
||||
Error error;
|
||||
error.type = type;
|
||||
error.sub_type = sub_type;
|
||||
error.message = message;
|
||||
if (default_origin.has_value()) {
|
||||
error.origin = default_origin.value();
|
||||
}
|
||||
if (default_severity.has_value()) {
|
||||
error.severity = default_severity.value();
|
||||
}
|
||||
if (default_state.has_value()) {
|
||||
error.state = default_state.value();
|
||||
}
|
||||
if (default_vendor_id.has_value()) {
|
||||
error.vendor_id = default_vendor_id.value();
|
||||
}
|
||||
set_description(error);
|
||||
return error;
|
||||
}
|
||||
|
||||
Error ErrorFactory::create_error(const ErrorType& type, const ErrorSubType& sub_type, const std::string& message,
|
||||
const Severity severity) const {
|
||||
Error error;
|
||||
error.type = type;
|
||||
error.sub_type = sub_type;
|
||||
error.message = message;
|
||||
error.severity = severity;
|
||||
if (default_origin.has_value()) {
|
||||
error.origin = default_origin.value();
|
||||
}
|
||||
if (default_state.has_value()) {
|
||||
error.state = default_state.value();
|
||||
}
|
||||
if (default_vendor_id.has_value()) {
|
||||
error.vendor_id = default_vendor_id.value();
|
||||
}
|
||||
set_description(error);
|
||||
return error;
|
||||
}
|
||||
|
||||
Error ErrorFactory::create_error(const ErrorType& type, const ErrorSubType& sub_type, const std::string& message,
|
||||
const State state) const {
|
||||
Error error;
|
||||
error.type = type;
|
||||
error.sub_type = sub_type;
|
||||
error.message = message;
|
||||
error.state = state;
|
||||
if (default_origin.has_value()) {
|
||||
error.origin = default_origin.value();
|
||||
}
|
||||
if (default_severity.has_value()) {
|
||||
error.severity = default_severity.value();
|
||||
}
|
||||
if (default_vendor_id.has_value()) {
|
||||
error.vendor_id = default_vendor_id.value();
|
||||
}
|
||||
set_description(error);
|
||||
return error;
|
||||
}
|
||||
|
||||
Error ErrorFactory::create_error(const ErrorType& type, const ErrorSubType& sub_type, const std::string& message,
|
||||
const Severity severity, const State state) const {
|
||||
Error error;
|
||||
error.type = type;
|
||||
error.sub_type = sub_type;
|
||||
error.message = message;
|
||||
error.severity = severity;
|
||||
error.state = state;
|
||||
if (default_origin.has_value()) {
|
||||
error.origin = default_origin.value();
|
||||
}
|
||||
if (default_vendor_id.has_value()) {
|
||||
error.vendor_id = default_vendor_id.value();
|
||||
}
|
||||
set_description(error);
|
||||
return error;
|
||||
}
|
||||
|
||||
void ErrorFactory::set_default_origin(const ImplementationIdentifier& origin) {
|
||||
default_origin = origin;
|
||||
}
|
||||
|
||||
void ErrorFactory::set_default_severity(Severity severity) {
|
||||
default_severity = severity;
|
||||
}
|
||||
|
||||
void ErrorFactory::set_default_state(State state) {
|
||||
default_state = state;
|
||||
}
|
||||
|
||||
void ErrorFactory::set_default_type(const ErrorType& type) {
|
||||
default_type = type;
|
||||
}
|
||||
|
||||
void ErrorFactory::set_default_sub_type(const ErrorSubType& sub_type) {
|
||||
default_sub_type = sub_type;
|
||||
}
|
||||
|
||||
void ErrorFactory::set_default_message(const std::string& message) {
|
||||
default_message = message;
|
||||
}
|
||||
|
||||
void ErrorFactory::set_default_vendor_id(const std::string& vendor_id) {
|
||||
default_vendor_id = vendor_id;
|
||||
}
|
||||
|
||||
void ErrorFactory::set_description(Error& error) const {
|
||||
if (error.type == "") {
|
||||
return;
|
||||
}
|
||||
error.description = error_type_map->get_description(error.type);
|
||||
}
|
||||
|
||||
} // namespace error
|
||||
} // namespace Everest
|
||||
@@ -0,0 +1,181 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include <utils/error/error_filter.hpp>
|
||||
|
||||
#include <everest/exceptions.hpp>
|
||||
#include <everest/logging.hpp>
|
||||
|
||||
namespace Everest {
|
||||
namespace error {
|
||||
|
||||
std::string state_filter_to_string(const StateFilter& f) {
|
||||
return state_to_string(f);
|
||||
}
|
||||
|
||||
StateFilter string_to_state_filter(const std::string& s) {
|
||||
return string_to_state(s);
|
||||
}
|
||||
|
||||
std::string severity_filter_to_string(const SeverityFilter& f) {
|
||||
switch (f) {
|
||||
case SeverityFilter::LOW_GE:
|
||||
return "LOW_GE";
|
||||
case SeverityFilter::MEDIUM_GE:
|
||||
return "MEDIUM_GE";
|
||||
case SeverityFilter::HIGH_GE:
|
||||
return "HIGH_GE";
|
||||
}
|
||||
EVLOG_error << "No known string conversion for provided enum of type SeverityFilter. Defaulting to HIGH_GE";
|
||||
return "HIGH_GE";
|
||||
}
|
||||
|
||||
SeverityFilter string_to_severity_filter(const std::string& s) {
|
||||
if (s == "LOW_GE") {
|
||||
return SeverityFilter::LOW_GE;
|
||||
} else if (s == "MEDIUM_GE") {
|
||||
return SeverityFilter::MEDIUM_GE;
|
||||
} else if (s == "HIGH_GE") {
|
||||
return SeverityFilter::HIGH_GE;
|
||||
}
|
||||
EVLOG_error << "Provided string " << s
|
||||
<< " could not be converted to enum of type SeverityFilter. Defaulting to HIGH_GE";
|
||||
return SeverityFilter::HIGH_GE;
|
||||
}
|
||||
|
||||
TypeFilter::TypeFilter(const ErrorType& value_) : value(value_) {
|
||||
}
|
||||
|
||||
SubTypeFilter::SubTypeFilter(const ErrorSubType& value_) : value(value_) {
|
||||
}
|
||||
|
||||
VendorIdFilter::VendorIdFilter(const std::string& value_) : value(value_) {
|
||||
}
|
||||
|
||||
std::string filter_type_to_string(const FilterType& f) {
|
||||
switch (f) {
|
||||
case FilterType::State:
|
||||
return "State";
|
||||
case FilterType::Origin:
|
||||
return "Origin";
|
||||
case FilterType::Type:
|
||||
return "Type";
|
||||
case FilterType::Severity:
|
||||
return "Severity";
|
||||
case FilterType::TimePeriod:
|
||||
return "TimePeriod";
|
||||
case FilterType::Handle:
|
||||
return "Handle";
|
||||
case FilterType::SubType:
|
||||
return "SubType";
|
||||
case FilterType::VendorId:
|
||||
return "VendorId";
|
||||
}
|
||||
EVLOG_error << "No known string conversion for provided enum of type FilterType. Defaulting to Type";
|
||||
return "Type";
|
||||
}
|
||||
|
||||
FilterType string_to_filter_type(const std::string& s) {
|
||||
if (s == "State") {
|
||||
return FilterType::State;
|
||||
} else if (s == "Origin") {
|
||||
return FilterType::Origin;
|
||||
} else if (s == "Type") {
|
||||
return FilterType::Type;
|
||||
} else if (s == "Severity") {
|
||||
return FilterType::Severity;
|
||||
} else if (s == "TimePeriod") {
|
||||
return FilterType::TimePeriod;
|
||||
} else if (s == "Handle") {
|
||||
return FilterType::Handle;
|
||||
} else if (s == "SubType") {
|
||||
return FilterType::SubType;
|
||||
} else if (s == "VendorId") {
|
||||
return FilterType::VendorId;
|
||||
}
|
||||
EVLOG_error << "Provided string " << s << " could not be converted to enum of type FilterType. Deafulting to Type.";
|
||||
return FilterType::Type;
|
||||
}
|
||||
|
||||
ErrorFilter::ErrorFilter() = default;
|
||||
|
||||
ErrorFilter::ErrorFilter(const FilterVariant& filter_) : filter(filter_) {
|
||||
}
|
||||
|
||||
FilterType ErrorFilter::get_filter_type() const {
|
||||
if (filter.index() == 0) {
|
||||
EVLOG_error << "Filter type is not set. Defaulting to 'FilterType::State'.";
|
||||
return FilterType::State;
|
||||
}
|
||||
return static_cast<FilterType>(filter.index());
|
||||
}
|
||||
|
||||
StateFilter ErrorFilter::get_state_filter() const {
|
||||
if (this->get_filter_type() != FilterType::State) {
|
||||
EVLOG_error << "Filter type is not StateFilter. Defaulting to 'StateFilter::Active'.";
|
||||
return StateFilter::Active;
|
||||
}
|
||||
return std::get<StateFilter>(filter);
|
||||
}
|
||||
|
||||
OriginFilter ErrorFilter::get_origin_filter() const {
|
||||
if (this->get_filter_type() != FilterType::Origin) {
|
||||
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", std::nullopt));
|
||||
}
|
||||
return std::get<OriginFilter>(filter);
|
||||
}
|
||||
|
||||
TypeFilter ErrorFilter::get_type_filter() const {
|
||||
if (this->get_filter_type() != FilterType::Type) {
|
||||
EVLOG_error << "Filter type is not TypeFilter. Defaulting to 'TypeFilter(\"no-type-provided\")'.";
|
||||
return TypeFilter("no-type-provided");
|
||||
}
|
||||
return std::get<TypeFilter>(filter);
|
||||
}
|
||||
|
||||
SeverityFilter ErrorFilter::get_severity_filter() const {
|
||||
if (this->get_filter_type() != FilterType::Severity) {
|
||||
EVLOG_error << "Filter type is not SeverityFilter. Defaulting to 'SeverityFilter::HIGH_GE'.";
|
||||
return SeverityFilter::HIGH_GE;
|
||||
}
|
||||
return std::get<SeverityFilter>(filter);
|
||||
}
|
||||
|
||||
TimePeriodFilter ErrorFilter::get_time_period_filter() const {
|
||||
if (this->get_filter_type() != FilterType::TimePeriod) {
|
||||
EVLOG_error << "Filter type is not TimePeriodFilter. Defaulting to 'TimePeriodFilter{Error::time_point(), "
|
||||
"Error::time_point()}'.";
|
||||
return TimePeriodFilter{Error::time_point(), Error::time_point()};
|
||||
}
|
||||
return std::get<TimePeriodFilter>(filter);
|
||||
}
|
||||
|
||||
HandleFilter ErrorFilter::get_handle_filter() const {
|
||||
if (this->get_filter_type() != FilterType::Handle) {
|
||||
EVLOG_error << "Filter type is not HandleFilter. Defaulting to 'HandleFilter()'.";
|
||||
return HandleFilter();
|
||||
}
|
||||
return std::get<HandleFilter>(filter);
|
||||
}
|
||||
|
||||
SubTypeFilter ErrorFilter::get_sub_type_filter() const {
|
||||
if (this->get_filter_type() != FilterType::SubType) {
|
||||
EVLOG_error << "Filter type is not SubTypeFilter. Defaulting to 'SubTypeFilter(\"no-sub-type-provided\")'.";
|
||||
return SubTypeFilter("no-sub-type-provided");
|
||||
}
|
||||
return std::get<SubTypeFilter>(filter);
|
||||
}
|
||||
|
||||
VendorIdFilter ErrorFilter::get_vendor_id_filter() const {
|
||||
if (this->get_filter_type() != FilterType::VendorId) {
|
||||
throw EverestBaseLogicError("Filter type is not VendorIdFilter.");
|
||||
}
|
||||
return std::get<VendorIdFilter>(filter);
|
||||
}
|
||||
|
||||
} // namespace error
|
||||
} // namespace Everest
|
||||
@@ -0,0 +1,151 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include <utility>
|
||||
#include <utils/error/error_manager_impl.hpp>
|
||||
|
||||
#include <utils/error.hpp>
|
||||
#include <utils/error/error_database.hpp>
|
||||
#include <utils/error/error_json.hpp>
|
||||
#include <utils/error/error_type_map.hpp>
|
||||
|
||||
#include <everest/logging.hpp>
|
||||
|
||||
#include <list>
|
||||
#include <memory>
|
||||
|
||||
namespace Everest {
|
||||
namespace error {
|
||||
|
||||
ErrorManagerImpl::ErrorManagerImpl(std::shared_ptr<ErrorTypeMap> error_type_map_,
|
||||
std::shared_ptr<ErrorDatabase> error_database_,
|
||||
std::list<ErrorType> allowed_error_types_,
|
||||
ErrorManagerImpl::PublishErrorFunc publish_raised_error_,
|
||||
ErrorManagerImpl::PublishErrorFunc publish_cleared_error_,
|
||||
const bool validate_error_types_) :
|
||||
error_type_map(error_type_map_),
|
||||
database(error_database_),
|
||||
allowed_error_types(std::move(allowed_error_types_)),
|
||||
publish_raised_error(std::move(publish_raised_error_)),
|
||||
publish_cleared_error(std::move(publish_cleared_error_)),
|
||||
validate_error_types(validate_error_types_) {
|
||||
if (validate_error_types) {
|
||||
for (const ErrorType& type : allowed_error_types) {
|
||||
if (!error_type_map->has(type)) {
|
||||
EVLOG_error << "Error type '" << type << "' in allowed_error_types is not defined, ignored.";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ErrorManagerImpl::raise_error(const Error& error) {
|
||||
if (validate_error_types) {
|
||||
if (std::find(allowed_error_types.begin(), allowed_error_types.end(), error.type) ==
|
||||
allowed_error_types.end()) {
|
||||
std::stringstream ss;
|
||||
ss << "Error type " << error.type << " is not allowed to be raised. Ignoring.";
|
||||
ss << std::endl << "Error object: " << nlohmann::json(error).dump(2);
|
||||
EVLOG_error << ss.str();
|
||||
return;
|
||||
}
|
||||
}
|
||||
const std::lock_guard<std::mutex> lock(mutex);
|
||||
if (!can_be_raised(error.type, error.sub_type)) {
|
||||
std::stringstream ss;
|
||||
ss << "Error can't be raised, because type " << error.type << ", sub_type " << error.sub_type
|
||||
<< " is already active.";
|
||||
ss << std::endl << "Error object: " << nlohmann::json(error).dump(2);
|
||||
EVLOG_debug << ss.str();
|
||||
return;
|
||||
}
|
||||
database->add_error(std::make_shared<Error>(error));
|
||||
this->publish_raised_error(error);
|
||||
EVLOG_error << "Error raised, type: " << error.type << ", sub_type: " << error.sub_type
|
||||
<< ", message: " << error.message;
|
||||
}
|
||||
|
||||
std::list<ErrorPtr> ErrorManagerImpl::clear_error(const ErrorType& type) {
|
||||
const ErrorSubType sub_type("");
|
||||
return clear_error(type, sub_type);
|
||||
}
|
||||
|
||||
std::list<ErrorPtr> ErrorManagerImpl::clear_error(const ErrorType& type, const ErrorSubType& sub_type) {
|
||||
const std::lock_guard<std::mutex> lock(mutex);
|
||||
if (!can_be_cleared(type, sub_type)) {
|
||||
EVLOG_debug << "Error can't be cleared, because type " << type << ", sub_type " << sub_type
|
||||
<< " is not active.";
|
||||
return {};
|
||||
}
|
||||
const std::list<ErrorFilter> filters = {ErrorFilter(TypeFilter(type)), ErrorFilter(SubTypeFilter(sub_type))};
|
||||
std::list<ErrorPtr> res = database->remove_errors(filters);
|
||||
if (res.size() > 1) {
|
||||
std::stringstream ss;
|
||||
ss << "There are more than one matching error, this is not valid:" << std::endl;
|
||||
for (const ErrorPtr& error : res) {
|
||||
ss << nlohmann::json(*error).dump(2) << std::endl;
|
||||
}
|
||||
EVLOG_error << ss.str();
|
||||
return {};
|
||||
}
|
||||
const ErrorPtr error = res.front();
|
||||
error->state = State::ClearedByModule;
|
||||
this->publish_cleared_error(*error);
|
||||
EVLOG_info << "Cleared error of type " << type << " with sub_type " << sub_type;
|
||||
return res;
|
||||
}
|
||||
|
||||
std::list<ErrorPtr> ErrorManagerImpl::clear_all_errors() {
|
||||
const std::lock_guard<std::mutex> lock(mutex);
|
||||
const std::list<ErrorFilter> filters = {};
|
||||
std::list<ErrorPtr> res = database->remove_errors(filters);
|
||||
if (res.empty()) {
|
||||
EVLOG_debug << "No errors can be cleared, because no errors are active";
|
||||
return res;
|
||||
}
|
||||
std::stringstream ss;
|
||||
ss << "Cleared " << res.size() << " errors:" << std::endl;
|
||||
for (const ErrorPtr& error : res) {
|
||||
error->state = State::ClearedByModule;
|
||||
this->publish_cleared_error(*error);
|
||||
ss << " - type: " << error->type << ", sub_type: " << error->sub_type << std::endl;
|
||||
}
|
||||
EVLOG_info << ss.str();
|
||||
return res;
|
||||
}
|
||||
|
||||
std::list<ErrorPtr> ErrorManagerImpl::clear_all_errors(const ErrorType& error_type) {
|
||||
const std::lock_guard<std::mutex> lock(mutex);
|
||||
if (!can_be_cleared(error_type)) {
|
||||
EVLOG_debug << "Errors can't be cleared, because type " << error_type << " is not active.";
|
||||
return {};
|
||||
}
|
||||
const std::list<ErrorFilter> filters = {ErrorFilter(TypeFilter(error_type))};
|
||||
std::list<ErrorPtr> res = database->remove_errors(filters);
|
||||
std::stringstream ss;
|
||||
ss << "Cleared " << res.size() << " errors of type " << error_type << " with sub_types:" << std::endl;
|
||||
for (const ErrorPtr& error : res) {
|
||||
error->state = State::ClearedByModule;
|
||||
this->publish_cleared_error(*error);
|
||||
ss << " - " << error->sub_type << std::endl;
|
||||
}
|
||||
EVLOG_info << ss.str();
|
||||
return res;
|
||||
}
|
||||
|
||||
bool ErrorManagerImpl::can_be_raised(const ErrorType& type, const ErrorSubType& sub_type) const {
|
||||
const std::list<ErrorFilter> filters = {ErrorFilter(TypeFilter(type)), ErrorFilter(SubTypeFilter(sub_type))};
|
||||
return database->get_errors(filters).empty();
|
||||
}
|
||||
|
||||
bool ErrorManagerImpl::can_be_cleared(const ErrorType& type, const ErrorSubType& sub_type) const {
|
||||
const std::list<ErrorFilter> filters = {ErrorFilter(TypeFilter(type)), ErrorFilter(SubTypeFilter(sub_type))};
|
||||
return !database->get_errors(filters).empty();
|
||||
}
|
||||
|
||||
bool ErrorManagerImpl::can_be_cleared(const ErrorType& type) const {
|
||||
const std::list<ErrorFilter> filters = {ErrorFilter(TypeFilter(type))};
|
||||
return !database->get_errors(filters).empty();
|
||||
}
|
||||
|
||||
} // namespace error
|
||||
} // namespace Everest
|
||||
@@ -0,0 +1,125 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include <utility>
|
||||
#include <utils/error/error_manager_req.hpp>
|
||||
|
||||
#include <utils/error.hpp>
|
||||
#include <utils/error/error_database.hpp>
|
||||
#include <utils/error/error_filter.hpp>
|
||||
#include <utils/error/error_json.hpp>
|
||||
#include <utils/error/error_type_map.hpp>
|
||||
|
||||
#include <everest/logging.hpp>
|
||||
|
||||
namespace Everest {
|
||||
namespace error {
|
||||
|
||||
ErrorManagerReq::ErrorManagerReq(std::shared_ptr<ErrorTypeMap> error_type_map_,
|
||||
std::shared_ptr<ErrorDatabase> error_database_,
|
||||
std::list<ErrorType> allowed_error_types_, SubscribeErrorFunc subscribe_error_func_) :
|
||||
error_type_map(error_type_map_),
|
||||
database(error_database_),
|
||||
allowed_error_types(std::move(allowed_error_types_)),
|
||||
subscribe_error_func(std::move(subscribe_error_func_)) {
|
||||
|
||||
for (const ErrorType& type : allowed_error_types) {
|
||||
if (!error_type_map->has(type)) {
|
||||
EVLOG_error << "Error type '" << type << "' in allowed_error_types is not defined, ignored.";
|
||||
}
|
||||
}
|
||||
const ErrorCallback on_raise = [this](const Error& error) { this->on_error_raised(error); };
|
||||
const ErrorCallback on_clear = [this](const Error& error) { this->on_error_cleared(error); };
|
||||
for (const ErrorType& type : allowed_error_types) {
|
||||
subscribe_error_func(type, on_raise, on_clear);
|
||||
error_subscriptions[type] = {};
|
||||
}
|
||||
}
|
||||
|
||||
ErrorManagerReq::Subscription::Subscription(const ErrorType& type_, const ErrorCallback& callback_,
|
||||
const ErrorCallback& clear_callback_) :
|
||||
type(type_), callback(callback_), clear_callback(clear_callback_) {
|
||||
}
|
||||
|
||||
void ErrorManagerReq::subscribe_error(const ErrorType& type, const ErrorCallback& callback,
|
||||
const ErrorCallback& clear_callback) {
|
||||
if (error_subscriptions.count(type) != 1) {
|
||||
EVLOG_error << "Tpye " << type << " is not known, ignore subscription";
|
||||
return;
|
||||
}
|
||||
const Subscription sub(type, callback, clear_callback);
|
||||
error_subscriptions.at(type).push_back(sub);
|
||||
}
|
||||
|
||||
void ErrorManagerReq::subscribe_all_errors(const ErrorCallback& callback, const ErrorCallback& clear_callback) {
|
||||
for (const ErrorType& type : allowed_error_types) {
|
||||
const Subscription sub(type, callback, clear_callback);
|
||||
error_subscriptions.at(type).push_back(sub);
|
||||
}
|
||||
}
|
||||
|
||||
void ErrorManagerReq::on_error_raised(const Error& error) {
|
||||
if (std::find(allowed_error_types.begin(), allowed_error_types.end(), error.type) == allowed_error_types.end()) {
|
||||
std::stringstream ss;
|
||||
ss << "Error can't be raised by module_id " << error.origin.module_id << " and implemenetation_id "
|
||||
<< error.origin.implementation_id << ", ignored.";
|
||||
ss << std::endl << "Error object: " << nlohmann::json(error).dump(2);
|
||||
EVLOG_error << ss.str();
|
||||
return;
|
||||
}
|
||||
if (!error_type_map->has(error.type)) {
|
||||
std::stringstream ss;
|
||||
ss << "Error type '" << error.type << "' is not defined, ignored.";
|
||||
ss << std::endl << "Error object: " << nlohmann::json(error).dump(2);
|
||||
EVLOG_error << ss.str();
|
||||
return;
|
||||
}
|
||||
std::list<ErrorPtr> errors =
|
||||
database->get_errors({ErrorFilter(TypeFilter(error.type)), ErrorFilter(SubTypeFilter(error.sub_type)),
|
||||
ErrorFilter(OriginFilter(error.origin))});
|
||||
if (!errors.empty()) {
|
||||
// Error is already raised, ignoring identical new error
|
||||
// FIXME: can we prevent this from happening in the first place?
|
||||
return;
|
||||
}
|
||||
database->add_error(std::make_shared<Error>(error));
|
||||
on_error(error, true);
|
||||
}
|
||||
|
||||
void ErrorManagerReq::on_error_cleared(const Error& error) {
|
||||
const std::list<ErrorFilter> filters = {ErrorFilter(TypeFilter(error.type)),
|
||||
ErrorFilter(SubTypeFilter(error.sub_type))};
|
||||
const std::list<ErrorPtr> res = database->remove_errors(filters);
|
||||
if (res.size() < 1) {
|
||||
std::stringstream ss;
|
||||
ss << "Error wasn't raised, type: " << error.type << ", sub_type: " << error.sub_type << ", ignored.";
|
||||
ss << std::endl << "Error object: " << nlohmann::json(error).dump(2);
|
||||
EVLOG_error << ss.str();
|
||||
return;
|
||||
}
|
||||
if (res.size() > 1) {
|
||||
std::stringstream ss;
|
||||
ss << "More than one error is cleared, type: " << error.type << ", sub_type: " << error.sub_type
|
||||
<< ", ignored.";
|
||||
for (const ErrorPtr& error : res) {
|
||||
ss << std::endl << "Error object: " << nlohmann::json(*error).dump(2);
|
||||
}
|
||||
EVLOG_error << ss.str();
|
||||
return;
|
||||
}
|
||||
|
||||
on_error(error, false);
|
||||
}
|
||||
|
||||
void ErrorManagerReq::on_error(const Error& error, const bool raised) const {
|
||||
for (const Subscription& sub : error_subscriptions.at(error.type)) {
|
||||
if (raised) {
|
||||
sub.callback(error);
|
||||
} else {
|
||||
sub.clear_callback(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace error
|
||||
} // namespace Everest
|
||||
@@ -0,0 +1,112 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include <utility>
|
||||
#include <utils/error/error_manager_req_global.hpp>
|
||||
|
||||
#include <everest/logging.hpp>
|
||||
#include <utils/error/error_database.hpp>
|
||||
#include <utils/error/error_json.hpp>
|
||||
#include <utils/error/error_type_map.hpp>
|
||||
|
||||
namespace Everest {
|
||||
namespace error {
|
||||
|
||||
ErrorManagerReqGlobal::ErrorManagerReqGlobal(std::shared_ptr<ErrorTypeMap> error_type_map_,
|
||||
std::shared_ptr<ErrorDatabase> error_database_,
|
||||
SubscribeGlobalAllErrorsFunc subscribe_global_all_errors_func_) :
|
||||
error_type_map(error_type_map_),
|
||||
database(error_database_),
|
||||
subscribe_global_all_errors_func(std::move(subscribe_global_all_errors_func_)),
|
||||
subscriptions({}) {
|
||||
this->subscribe_global_all_errors_func([this](const Error& error) { this->on_error_raised(error); },
|
||||
[this](const Error& error) { this->on_error_cleared(error); });
|
||||
}
|
||||
|
||||
void ErrorManagerReqGlobal::subscribe_global_all_errors(const ErrorCallback& callback,
|
||||
const ErrorCallback& clear_callback) {
|
||||
const Subscription sub(callback, clear_callback);
|
||||
subscriptions.push_back(sub);
|
||||
}
|
||||
|
||||
ErrorManagerReqGlobal::Subscription::Subscription(const ErrorCallback& callback_,
|
||||
const ErrorCallback& clear_callback_) :
|
||||
callback(callback_), clear_callback(clear_callback_) {
|
||||
}
|
||||
|
||||
void ErrorManagerReqGlobal::on_error_raised(const Error& error) {
|
||||
if (!error_type_map->has(error.type)) {
|
||||
std::stringstream ss;
|
||||
ss << "Error type '" << error.type << "' is not defined, ignoring error";
|
||||
ss << std::endl << "Error object: " << nlohmann::json(error).dump(2);
|
||||
EVLOG_error << ss.str();
|
||||
return;
|
||||
}
|
||||
std::list<ErrorPtr> errors =
|
||||
database->get_errors({ErrorFilter(TypeFilter(error.type)), ErrorFilter(SubTypeFilter(error.sub_type)),
|
||||
ErrorFilter(OriginFilter(error.origin))});
|
||||
if (!errors.empty()) {
|
||||
std::stringstream ss;
|
||||
ss << "Error of type '" << error.type << "' and sub type '" << error.sub_type
|
||||
<< "' is already raised, ignoring new error";
|
||||
ss << std::endl << "Error object: " << nlohmann::json(error).dump(2);
|
||||
EVLOG_error << ss.str();
|
||||
return;
|
||||
}
|
||||
database->add_error(std::make_shared<Error>(error));
|
||||
errors = database->get_errors({ErrorFilter(TypeFilter(error.type)), ErrorFilter(SubTypeFilter(error.sub_type)),
|
||||
ErrorFilter(OriginFilter(error.origin))});
|
||||
if (errors.size() != 1) {
|
||||
EVLOG_error << "Error wasn't added, type: " << error.type << ", sub type: " << error.sub_type;
|
||||
return;
|
||||
}
|
||||
for (const Subscription& sub : subscriptions) {
|
||||
sub.callback(error);
|
||||
}
|
||||
}
|
||||
|
||||
void ErrorManagerReqGlobal::on_error_cleared(const Error& error) {
|
||||
if (!error_type_map->has(error.type)) {
|
||||
std::stringstream ss;
|
||||
ss << "Error type '" << error.type << "' is not defined, ignoring error";
|
||||
ss << std::endl << "Error object: " << nlohmann::json(error).dump(2);
|
||||
EVLOG_error << ss.str();
|
||||
return;
|
||||
}
|
||||
const std::list<ErrorPtr> errors =
|
||||
database->get_errors({ErrorFilter(TypeFilter(error.type)), ErrorFilter(SubTypeFilter(error.sub_type)),
|
||||
ErrorFilter(OriginFilter(error.origin))});
|
||||
if (errors.empty()) {
|
||||
std::stringstream ss;
|
||||
ss << "Error of type '" << error.type << "' and sub type '" << error.sub_type
|
||||
<< "' is not raised, ignoring clear error";
|
||||
ss << std::endl << "Error object: " << nlohmann::json(error).dump(2);
|
||||
EVLOG_error << ss.str();
|
||||
return;
|
||||
}
|
||||
const std::list<ErrorPtr> res =
|
||||
database->remove_errors({ErrorFilter(TypeFilter(error.type)), ErrorFilter(SubTypeFilter(error.sub_type)),
|
||||
ErrorFilter(OriginFilter(error.origin))});
|
||||
if (res.size() > 1) {
|
||||
std::stringstream ss;
|
||||
ss << "More than one error is cleared, type: " << error.type << ", sub type: " << error.sub_type;
|
||||
for (const ErrorPtr& error : res) {
|
||||
ss << std::endl << "Error object: " << nlohmann::json(*error).dump(2);
|
||||
}
|
||||
EVLOG_error << ss.str();
|
||||
return;
|
||||
}
|
||||
if (res.empty()) {
|
||||
std::stringstream ss;
|
||||
ss << "Error wasn't removed, type: " << error.type << ", sub type: " << error.sub_type;
|
||||
ss << std::endl << "Error object: " << nlohmann::json(error).dump(2);
|
||||
EVLOG_error << ss.str();
|
||||
return;
|
||||
}
|
||||
for (const Subscription& sub : subscriptions) {
|
||||
sub.clear_callback(error);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace error
|
||||
} // namespace Everest
|
||||
@@ -0,0 +1,46 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include <utility>
|
||||
#include <utils/error/error_state_monitor.hpp>
|
||||
|
||||
#include <utils/error/error_database.hpp>
|
||||
#include <utils/error/error_filter.hpp>
|
||||
|
||||
#include <everest/logging.hpp>
|
||||
|
||||
namespace Everest {
|
||||
namespace error {
|
||||
|
||||
ErrorStateMonitor::StateCondition::StateCondition(ErrorType type_, ErrorSubType sub_type_, bool active_) :
|
||||
type(std::move(type_)), sub_type(std::move(sub_type_)), active(active_) {
|
||||
}
|
||||
|
||||
ErrorStateMonitor::ErrorStateMonitor(std::shared_ptr<ErrorDatabase> error_database_) : database(error_database_) {
|
||||
}
|
||||
|
||||
bool ErrorStateMonitor::is_error_active(const ErrorType& type, const ErrorSubType& sub_type) const {
|
||||
const std::list<ErrorFilter> filters = {ErrorFilter(TypeFilter(type)), ErrorFilter(SubTypeFilter(sub_type))};
|
||||
return database->get_errors(filters).size() > 0;
|
||||
}
|
||||
|
||||
std::list<ErrorPtr> ErrorStateMonitor::get_active_errors() const {
|
||||
return database->get_errors({});
|
||||
}
|
||||
|
||||
bool ErrorStateMonitor::is_condition_satisfied(const StateCondition& condition) const {
|
||||
const bool res = is_error_active(condition.type, condition.sub_type);
|
||||
return res == condition.active;
|
||||
}
|
||||
|
||||
bool ErrorStateMonitor::is_condition_satisfied(const std::list<StateCondition>& condition) const {
|
||||
for (const auto& cond : condition) {
|
||||
if (!is_condition_satisfied(cond)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace error
|
||||
} // namespace Everest
|
||||
@@ -0,0 +1,94 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include <utility>
|
||||
#include <utils/error/error_type_map.hpp>
|
||||
|
||||
#include <utils/error.hpp>
|
||||
#include <utils/yaml_loader.hpp>
|
||||
|
||||
#include <everest/logging.hpp>
|
||||
|
||||
namespace Everest {
|
||||
namespace error {
|
||||
|
||||
ErrorTypeMap::ErrorTypeMap(const std::filesystem::path& error_types_dir) {
|
||||
load_error_types(error_types_dir);
|
||||
}
|
||||
|
||||
void ErrorTypeMap::load_error_types(const std::filesystem::path& error_types_dir) {
|
||||
BOOST_LOG_FUNCTION();
|
||||
|
||||
if (!std::filesystem::is_directory(error_types_dir) || !std::filesystem::exists(error_types_dir)) {
|
||||
EVLOG_error << "Error types directory '" << error_types_dir.string()
|
||||
<< "' does not exist, error types not loaded.";
|
||||
return;
|
||||
}
|
||||
for (const auto& entry : std::filesystem::directory_iterator(error_types_dir)) {
|
||||
if (!entry.is_regular_file()) {
|
||||
continue;
|
||||
}
|
||||
if (entry.path().extension() != ".yaml") {
|
||||
continue;
|
||||
}
|
||||
const std::string prefix = entry.path().stem().string();
|
||||
json error_type_file = Everest::load_yaml(entry.path());
|
||||
if (!error_type_file.contains("errors")) {
|
||||
EVLOG_warning << "Error type file '" << entry.path().string() << "' does not contain 'errors' key.";
|
||||
continue;
|
||||
}
|
||||
if (!error_type_file.at("errors").is_array()) {
|
||||
EVLOG_error << "Error type file '" << entry.path().string()
|
||||
<< "' does not contain an array with key 'errors', skipped.";
|
||||
continue;
|
||||
}
|
||||
for (const auto& error : error_type_file["errors"]) {
|
||||
if (!error.contains("name")) {
|
||||
EVLOG_error << "Error type file '" << entry.path().string()
|
||||
<< "' contains an error without a 'name' key, skipped.";
|
||||
continue;
|
||||
}
|
||||
std::string description;
|
||||
if (!error.contains("description")) {
|
||||
EVLOG_error << "Error type file '" << entry.path().string()
|
||||
<< "' contains an error without a 'description' key, using default description";
|
||||
description = "No description found";
|
||||
} else {
|
||||
description = error.at("description").get<std::string>();
|
||||
}
|
||||
ErrorType complete_name = prefix + "/" + error.at("name").get<std::string>();
|
||||
if (this->has(complete_name)) {
|
||||
EVLOG_error << "Error type file '" << entry.path().string() << "' contains an error with the name '"
|
||||
<< complete_name << "' which is already defined, skipped.";
|
||||
continue;
|
||||
}
|
||||
error_types[complete_name] = description;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ErrorTypeMap::load_error_types_map(std::map<ErrorType, std::string> error_types_map) {
|
||||
this->error_types = std::move(error_types_map);
|
||||
}
|
||||
|
||||
std::string ErrorTypeMap::get_description(const ErrorType& error_type) const {
|
||||
std::string description;
|
||||
try {
|
||||
description = error_types.at(error_type);
|
||||
} catch (...) {
|
||||
EVLOG_error << "Error type '" << error_type << "' is not defined, returning default description.";
|
||||
description = "No description found";
|
||||
}
|
||||
return description;
|
||||
}
|
||||
|
||||
bool ErrorTypeMap::has(const ErrorType& error_type) const {
|
||||
return error_types.find(error_type) != error_types.end();
|
||||
}
|
||||
|
||||
const ErrorTypes& ErrorTypeMap::get_error_types() const {
|
||||
return error_types;
|
||||
}
|
||||
|
||||
} // namespace error
|
||||
} // namespace Everest
|
||||
1200
tools/EVerest-main/lib/everest/framework/lib/everest.cpp
Normal file
1200
tools/EVerest-main/lib/everest/framework/lib/everest.cpp
Normal file
File diff suppressed because it is too large
Load Diff
71
tools/EVerest-main/lib/everest/framework/lib/filesystem.cpp
Normal file
71
tools/EVerest-main/lib/everest/framework/lib/filesystem.cpp
Normal file
@@ -0,0 +1,71 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include <fmt/core.h>
|
||||
|
||||
#include <everest/exceptions.hpp>
|
||||
#include <everest/logging.hpp>
|
||||
|
||||
#include <utils/exceptions.hpp>
|
||||
#include <utils/filesystem.hpp>
|
||||
|
||||
namespace Everest {
|
||||
|
||||
/// \brief Check if the provided \p path is a directory
|
||||
/// \returns The canonical version of the provided path
|
||||
/// \throws BootException if the path doesn't exist or isn't a directory
|
||||
fs::path assert_dir(const std::string& path, const std::string& path_alias) {
|
||||
auto fs_path = fs::path(path);
|
||||
|
||||
if (!fs::exists(fs_path)) {
|
||||
throw BootException(fmt::format("{} path '{}' does not exist", path_alias, path));
|
||||
}
|
||||
|
||||
fs_path = fs::canonical(fs_path);
|
||||
|
||||
if (!fs::is_directory(fs_path)) {
|
||||
throw BootException(fmt::format("{} path '{}' is not a directory", path_alias, path));
|
||||
}
|
||||
|
||||
return fs_path;
|
||||
}
|
||||
|
||||
/// \brief Check if the provided \p path is a file
|
||||
/// \returns The canonical version of the provided path
|
||||
/// \throws BootException if the path doesn't exist or isn't a regular file
|
||||
fs::path assert_file(const std::string& path, const std::string& file_alias) {
|
||||
auto fs_file = fs::path(path);
|
||||
|
||||
if (!fs::exists(fs_file)) {
|
||||
throw BootException(fmt::format("{} file '{}' does not exist", file_alias, path));
|
||||
}
|
||||
|
||||
fs_file = fs::canonical(fs_file);
|
||||
|
||||
if (!fs::is_regular_file(fs_file)) {
|
||||
throw BootException(fmt::format("{} file '{}' is not a regular file", file_alias, path));
|
||||
}
|
||||
|
||||
return fs_file;
|
||||
}
|
||||
|
||||
/// \returns true if the file at the provided \p path has an extensions \p ext
|
||||
bool has_extension(const std::string& path, const std::string& ext) {
|
||||
auto path_ext = fs::path(path).extension().string();
|
||||
|
||||
// lowercase the string
|
||||
std::transform(path_ext.begin(), path_ext.end(), path_ext.begin(), [](unsigned char c) { return std::tolower(c); });
|
||||
|
||||
return path_ext == ext;
|
||||
}
|
||||
|
||||
/// \returns a path that has been prefixed by \p prefix from the provided json \p value
|
||||
std::string get_prefixed_path_from_json(const nlohmann::json& value, const fs::path& prefix) {
|
||||
auto settings_configs_dir = value.get<std::string>();
|
||||
if (fs::path(settings_configs_dir).is_relative()) {
|
||||
settings_configs_dir = (prefix / settings_configs_dir).string();
|
||||
}
|
||||
return settings_configs_dir;
|
||||
}
|
||||
|
||||
} // namespace Everest
|
||||
11
tools/EVerest-main/lib/everest/framework/lib/formatter.cpp
Normal file
11
tools/EVerest-main/lib/everest/framework/lib/formatter.cpp
Normal file
@@ -0,0 +1,11 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2024 Pionix GmbH and Contributors to EVerest
|
||||
#include <utils/formatter.hpp>
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
namespace everest::formatting {
|
||||
void throw_format_error(const char* message) {
|
||||
throw fmt::format_error(message);
|
||||
}
|
||||
} // namespace everest::formatting
|
||||
500
tools/EVerest-main/lib/everest/framework/lib/message_handler.cpp
Normal file
500
tools/EVerest-main/lib/everest/framework/lib/message_handler.cpp
Normal file
@@ -0,0 +1,500 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
#include "everest/util/misc/bind.hpp"
|
||||
#include "everest/util/misc/container.hpp"
|
||||
#include <utils/message_handler.hpp>
|
||||
|
||||
#include <everest/logging.hpp>
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include <optional>
|
||||
|
||||
namespace Everest {
|
||||
|
||||
namespace {
|
||||
bool check_topic_matches(std::string_view full_topic, std::string_view wildcard_topic) {
|
||||
// Verbatim match
|
||||
if (full_topic == wildcard_topic) {
|
||||
return true;
|
||||
}
|
||||
|
||||
std::size_t full_topic_pos = 0;
|
||||
std::size_t wildcard_topic_pos = 0;
|
||||
|
||||
while (wildcard_topic_pos < wildcard_topic.size()) {
|
||||
// Always match on the first multi-level wildcard found
|
||||
if (wildcard_topic[wildcard_topic_pos] == '#') {
|
||||
return true;
|
||||
}
|
||||
|
||||
std::size_t wildcard_topic_next = wildcard_topic.find('/', wildcard_topic_pos);
|
||||
std::size_t wildcard_topic_substr_len = (wildcard_topic_next == std::string_view::npos)
|
||||
? std::string_view::npos
|
||||
: wildcard_topic_next - wildcard_topic_pos;
|
||||
std::string_view wildcard_topic_substr = wildcard_topic.substr(wildcard_topic_pos, wildcard_topic_substr_len);
|
||||
|
||||
std::size_t full_topic_next = full_topic.find('/', full_topic_pos);
|
||||
std::size_t full_topic_substr_len =
|
||||
(full_topic_next == std::string_view::npos) ? std::string_view::npos : full_topic_next - full_topic_pos;
|
||||
std::string_view full_topic_substr = full_topic.substr(full_topic_pos, full_topic_substr_len);
|
||||
|
||||
if (wildcard_topic_substr != "+" && wildcard_topic_substr != full_topic_substr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (wildcard_topic_next == std::string_view::npos) {
|
||||
return full_topic_next == std::string_view::npos;
|
||||
}
|
||||
|
||||
if (full_topic_next == std::string_view::npos) {
|
||||
return wildcard_topic.substr(wildcard_topic_next + 1) == "#";
|
||||
}
|
||||
|
||||
wildcard_topic_pos = wildcard_topic_next + 1;
|
||||
full_topic_pos = full_topic_next + 1;
|
||||
}
|
||||
|
||||
return full_topic_pos >= full_topic.size();
|
||||
}
|
||||
|
||||
// Pure function: collects all handlers whose registered topic (with MQTT wildcard support)
|
||||
// matches the incoming topic. Kept outside MessageHandler to signal it has no dependency
|
||||
// on class state; callers must hold the handler map lock before passing data.
|
||||
std::vector<MessageHandler::SharedTypedHandler>
|
||||
copy_shared_handler_wildcard(std::map<std::string, std::vector<MessageHandler::SharedTypedHandler>> const& data,
|
||||
std::string const& topic) {
|
||||
std::vector<MessageHandler::SharedTypedHandler> handler_copy;
|
||||
for (const auto& [wildcard_topic, handlers_vec] : data) {
|
||||
if (check_topic_matches(topic, wildcard_topic)) {
|
||||
handler_copy.insert(handler_copy.end(), handlers_vec.begin(), handlers_vec.end());
|
||||
}
|
||||
}
|
||||
return handler_copy;
|
||||
}
|
||||
|
||||
std::vector<MessageHandler::SharedTypedHandler> copy_shared_handler(MessageHandler::MultiHandlerMap const& data,
|
||||
std::string const& topic) {
|
||||
std::vector<MessageHandler::SharedTypedHandler> handler_copy;
|
||||
auto const* ptr = everest::lib::util::find_ptr(data, topic);
|
||||
if (ptr != nullptr) {
|
||||
handler_copy = ptr->second;
|
||||
}
|
||||
return handler_copy;
|
||||
}
|
||||
|
||||
MessageHandler::SharedTypedHandler copy_shared_handler(MessageHandler::SingleHandlerMap const& data,
|
||||
std::string const& topic) {
|
||||
MessageHandler::SharedTypedHandler handler_copy;
|
||||
auto const* ptr = everest::lib::util::find_ptr(data, topic);
|
||||
if (ptr != nullptr) {
|
||||
handler_copy = ptr->second;
|
||||
}
|
||||
return handler_copy;
|
||||
}
|
||||
|
||||
void warn_on_high_queue_size(everest::lib::util::simple_queue<ParsedMessage> const& queue, std::string const& topic) {
|
||||
if (queue.size() >= MAX_PENDING_MESSAGES_PER_TOPIC) {
|
||||
EVLOG_warning << "Pending message queue for topic '" << topic << "' has reached the limit ("
|
||||
<< MAX_PENDING_MESSAGES_PER_TOPIC << "). Handler may be stuck or too slow.";
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
using everest::lib::util::bind_obj;
|
||||
|
||||
MessageHandler::MessageHandler() {
|
||||
operation_thread_pool = std::make_unique<ThreadPool>(
|
||||
THREAD_POOL_SCALING_MIN_THREAD_COUNT, std::thread::hardware_concurrency(), THREAD_POOL_SCALING_IDLE_TIMEOUT);
|
||||
operation_dispatcher_thread = std::thread([this] { run_operation_dispatcher(); });
|
||||
result_worker_thread = std::thread([this] { run_result_message_worker(); });
|
||||
external_mqtt_worker_thread = std::thread([this] { run_external_mqtt_worker(); });
|
||||
}
|
||||
|
||||
MessageHandler::~MessageHandler() {
|
||||
stop();
|
||||
}
|
||||
|
||||
void MessageHandler::add(const ParsedMessage& message) {
|
||||
EVLOG_verbose << "Adding message to queue: " << message.topic << " with data: " << message.data;
|
||||
|
||||
MqttMessageType msg_type = MqttMessageType::ExternalMQTT; // Default to ExternalMQTT if msg_type is not present
|
||||
|
||||
if (message.data.is_object()) {
|
||||
auto msg_type_it = message.data.find("msg_type");
|
||||
if (msg_type_it != message.data.end() && msg_type_it->is_string()) {
|
||||
msg_type = string_to_mqtt_message_type(msg_type_it->get<std::string>());
|
||||
}
|
||||
}
|
||||
|
||||
if (msg_type == MqttMessageType::CmdResult || msg_type == MqttMessageType::GetConfigResponse) {
|
||||
EVLOG_verbose << "Pushing cmd_result message to queue: " << message.data;
|
||||
result_message_queue.push(message);
|
||||
} else if (msg_type == MqttMessageType::GlobalReady) {
|
||||
const auto topic_copy = message.topic;
|
||||
const auto data_copy = message.data.at("data");
|
||||
|
||||
// Steal the previous ready thread under the monitor lock, then join it outside.
|
||||
// Using steal-then-join avoids holding the lock during join(), which could block
|
||||
// other add() calls for an arbitrary duration while the handler runs.
|
||||
std::thread old_ready;
|
||||
{
|
||||
auto handle = ready.handle();
|
||||
old_ready = std::move(*handle);
|
||||
*handle = std::thread([this, topic_copy, data_copy] {
|
||||
SharedTypedHandler action;
|
||||
{
|
||||
auto handle = handlers.handle();
|
||||
action = handle->global_ready;
|
||||
}
|
||||
if (action) {
|
||||
(*action->handler)(topic_copy, data_copy);
|
||||
}
|
||||
});
|
||||
} // release ready monitor lock before joining
|
||||
if (old_ready.joinable()) {
|
||||
old_ready.join();
|
||||
}
|
||||
} else if (msg_type == MqttMessageType::ExternalMQTT) {
|
||||
external_mqtt_message_queue.push(message);
|
||||
} else {
|
||||
operation_message_queue.push(message);
|
||||
}
|
||||
}
|
||||
|
||||
void MessageHandler::stop() {
|
||||
if (!running.exchange(false)) {
|
||||
return; // Already stopped
|
||||
}
|
||||
|
||||
operation_message_queue.stop();
|
||||
result_message_queue.stop();
|
||||
external_mqtt_message_queue.stop();
|
||||
|
||||
// Join the dispatcher first: it must not be able to call schedule_operation_message()
|
||||
// (which dereferences operation_thread_pool) after the pool is destroyed.
|
||||
if (operation_dispatcher_thread.joinable()) {
|
||||
operation_dispatcher_thread.join();
|
||||
}
|
||||
// The thread_pool destructor handles stopping and joining its workers.
|
||||
operation_thread_pool.reset();
|
||||
if (result_worker_thread.joinable()) {
|
||||
result_worker_thread.join();
|
||||
}
|
||||
if (external_mqtt_worker_thread.joinable()) {
|
||||
external_mqtt_worker_thread.join();
|
||||
}
|
||||
std::thread ready_to_join;
|
||||
{
|
||||
auto handle = ready.handle();
|
||||
ready_to_join = std::move(*handle);
|
||||
}
|
||||
if (ready_to_join.joinable()) {
|
||||
ready_to_join.join();
|
||||
}
|
||||
}
|
||||
|
||||
void MessageHandler::run_operation_dispatcher() {
|
||||
while (auto message = operation_message_queue.wait_and_pop()) {
|
||||
dispatch_operation_message(std::move(message.value()));
|
||||
}
|
||||
|
||||
EVLOG_debug << "Operation dispatcher thread stopped";
|
||||
}
|
||||
|
||||
void MessageHandler::dispatch_operation_message(ParsedMessage&& message) {
|
||||
{
|
||||
auto handle = operations.handle();
|
||||
if (everest::lib::util::exists(handle->in_flight, message.topic)) {
|
||||
auto& pending_queue = handle->pending_messages[message.topic];
|
||||
warn_on_high_queue_size(pending_queue, message.topic);
|
||||
pending_queue.push(std::move(message));
|
||||
return;
|
||||
}
|
||||
handle->in_flight.insert(message.topic);
|
||||
}
|
||||
|
||||
schedule_operation_message(std::move(message));
|
||||
}
|
||||
|
||||
void MessageHandler::schedule_operation_message(ParsedMessage&& message) {
|
||||
auto handle_operation_message_ftor = bind_obj(&MessageHandler::handle_operation_message, this);
|
||||
auto on_operation_message_done_ftor = bind_obj(&MessageHandler::on_operation_message_done, this);
|
||||
auto operation = [handle = std::move(handle_operation_message_ftor),
|
||||
done = std::move(on_operation_message_done_ftor), message = std::move(message)]() {
|
||||
try {
|
||||
handle(message.topic, message.data);
|
||||
} catch (...) {
|
||||
done(message.topic);
|
||||
throw;
|
||||
}
|
||||
done(message.topic);
|
||||
};
|
||||
|
||||
if (operation_thread_pool) {
|
||||
operation_thread_pool->run(std::move(operation));
|
||||
}
|
||||
}
|
||||
|
||||
void MessageHandler::on_operation_message_done(const std::string& topic) {
|
||||
std::optional<ParsedMessage> next_message;
|
||||
{
|
||||
auto handle = operations.handle();
|
||||
if (!running) {
|
||||
// Shutting down: stop scheduling and release the in-flight slot.
|
||||
handle->in_flight.erase(topic);
|
||||
return;
|
||||
}
|
||||
|
||||
auto opt_pending_it = everest::lib::util::find_optional(handle->pending_messages, topic);
|
||||
if (not opt_pending_it.has_value()) {
|
||||
handle->in_flight.erase(topic);
|
||||
return;
|
||||
}
|
||||
auto& pending_it = opt_pending_it.value();
|
||||
auto& pending_messages = pending_it->second;
|
||||
next_message = pending_messages.pop();
|
||||
if (pending_messages.empty()) {
|
||||
handle->pending_messages.erase(pending_it);
|
||||
}
|
||||
}
|
||||
|
||||
if (!next_message.has_value()) {
|
||||
EVLOG_error << "Internal error: pending_messages queue for topic '" << topic
|
||||
<< "' was empty when expected to contain a message";
|
||||
auto handle = operations.handle();
|
||||
handle->in_flight.erase(topic);
|
||||
return;
|
||||
}
|
||||
schedule_operation_message(std::move(*next_message));
|
||||
}
|
||||
|
||||
void MessageHandler::run_result_message_worker() {
|
||||
while (auto message = result_message_queue.wait_and_pop()) {
|
||||
handle_result_message(message->topic, message->data);
|
||||
}
|
||||
EVLOG_debug << "Cmd result worker thread stopped";
|
||||
}
|
||||
|
||||
void MessageHandler::run_external_mqtt_worker() {
|
||||
while (auto message = external_mqtt_message_queue.wait_and_pop()) {
|
||||
handle_external_mqtt_message(message->topic, message->data);
|
||||
}
|
||||
EVLOG_debug << "External MQTT worker thread stopped";
|
||||
}
|
||||
|
||||
void MessageHandler::handle_operation_message(const std::string& topic, const json& payload) {
|
||||
MqttMessageType msg_type = MqttMessageType::ExternalMQTT;
|
||||
|
||||
// Determine message type
|
||||
auto msg_type_it = payload.find("msg_type");
|
||||
if (msg_type_it != payload.end() && msg_type_it->is_string()) {
|
||||
msg_type = string_to_mqtt_message_type(msg_type_it->get_ref<const std::string&>());
|
||||
}
|
||||
|
||||
auto data_it = payload.find("data");
|
||||
const json& data = (data_it != payload.end()) ? *data_it : payload;
|
||||
|
||||
switch (msg_type) {
|
||||
case MqttMessageType::Var:
|
||||
handle_var_message(topic, data);
|
||||
break;
|
||||
case MqttMessageType::Cmd:
|
||||
handle_cmd_message(topic, data);
|
||||
break;
|
||||
case MqttMessageType::ExternalMQTT:
|
||||
handle_external_mqtt_message(topic, data);
|
||||
break;
|
||||
case MqttMessageType::RaiseError:
|
||||
case MqttMessageType::ClearError:
|
||||
handle_error_message(topic, data);
|
||||
break;
|
||||
case MqttMessageType::GetConfig:
|
||||
handle_get_config_message(topic, data);
|
||||
break;
|
||||
case MqttMessageType::ModuleReady:
|
||||
handle_module_ready_message(topic, data);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void MessageHandler::handle_result_message(const std::string& topic, const json& payload) {
|
||||
auto msg_type_it = payload.find("msg_type");
|
||||
if (msg_type_it == payload.end()) {
|
||||
EVLOG_warning << "Received cmd_result message without msg_type: " << payload;
|
||||
return;
|
||||
}
|
||||
|
||||
const auto msg_type = string_to_mqtt_message_type(msg_type_it->get<std::string>());
|
||||
|
||||
if (msg_type == MqttMessageType::CmdResult) {
|
||||
handle_cmd_result(topic, payload);
|
||||
} else if (msg_type == MqttMessageType::GetConfigResponse) {
|
||||
handle_get_config_response(topic, payload);
|
||||
} else {
|
||||
EVLOG_warning << "Received invalid cmd_result message: " << payload;
|
||||
}
|
||||
}
|
||||
|
||||
void MessageHandler::register_handler(const std::string& topic, std::shared_ptr<TypedHandler> handler) {
|
||||
switch (handler->type) {
|
||||
case HandlerType::Call: {
|
||||
auto lock = handlers.handle();
|
||||
lock->cmd[topic] = handler;
|
||||
break;
|
||||
}
|
||||
case HandlerType::Result: {
|
||||
auto lock = responses.handle();
|
||||
lock->cmd[handler->id] = handler;
|
||||
break;
|
||||
}
|
||||
case HandlerType::SubscribeVar: {
|
||||
auto lock = handlers.handle();
|
||||
lock->var[topic].push_back(handler);
|
||||
break;
|
||||
}
|
||||
case HandlerType::SubscribeError: {
|
||||
auto lock = handlers.handle();
|
||||
lock->error[topic].push_back(handler);
|
||||
break;
|
||||
}
|
||||
case HandlerType::ExternalMQTT: {
|
||||
auto lock = handlers.handle();
|
||||
lock->external_var[topic].push_back(handler);
|
||||
break;
|
||||
}
|
||||
case HandlerType::GetConfig: {
|
||||
auto lock = handlers.handle();
|
||||
lock->get_module_config[topic] = handler;
|
||||
break;
|
||||
}
|
||||
case HandlerType::GetConfigResponse: {
|
||||
auto lock = responses.handle();
|
||||
lock->config = handler;
|
||||
break;
|
||||
}
|
||||
case HandlerType::ModuleReady: {
|
||||
auto lock = handlers.handle();
|
||||
lock->module_ready[topic] = handler;
|
||||
break;
|
||||
}
|
||||
case HandlerType::GlobalReady: {
|
||||
auto lock = handlers.handle();
|
||||
lock->global_ready = handler;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
EVLOG_warning << "Unknown handler type for topic: " << topic;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Private message handler methods
|
||||
void MessageHandler::handle_var_message(const std::string& topic, const json& data) {
|
||||
std::vector<SharedTypedHandler> handler_copy;
|
||||
{
|
||||
auto handle = handlers.handle();
|
||||
handler_copy = copy_shared_handler(handle->var, topic);
|
||||
}
|
||||
|
||||
const auto& json_data = data.at("data");
|
||||
for (const auto& handler : handler_copy) {
|
||||
(*handler->handler)(topic, json_data);
|
||||
}
|
||||
}
|
||||
|
||||
void MessageHandler::handle_cmd_message(const std::string& topic, const json& data) {
|
||||
SharedTypedHandler handler_copy;
|
||||
{
|
||||
auto handle = handlers.handle();
|
||||
handler_copy = copy_shared_handler(handle->cmd, topic);
|
||||
}
|
||||
|
||||
if (handler_copy) {
|
||||
(*handler_copy->handler)(topic, data);
|
||||
}
|
||||
}
|
||||
|
||||
void MessageHandler::handle_external_mqtt_message(const std::string& topic, const json& data) {
|
||||
std::vector<SharedTypedHandler> handler_copy;
|
||||
{
|
||||
auto handle = handlers.handle();
|
||||
handler_copy = copy_shared_handler_wildcard(handle->external_var, topic);
|
||||
}
|
||||
|
||||
for (const auto& handler : handler_copy) {
|
||||
(*handler->handler)(topic, data);
|
||||
}
|
||||
}
|
||||
|
||||
void MessageHandler::handle_error_message(const std::string& topic, const json& data) {
|
||||
std::vector<SharedTypedHandler> handler_copy;
|
||||
{
|
||||
auto handle = handlers.handle();
|
||||
handler_copy = copy_shared_handler_wildcard(handle->error, topic);
|
||||
}
|
||||
|
||||
for (const auto& handler : handler_copy) {
|
||||
(*handler->handler)(topic, data);
|
||||
}
|
||||
}
|
||||
|
||||
void MessageHandler::handle_get_config_message(const std::string& topic, const json& data) {
|
||||
SharedTypedHandler handler_copy;
|
||||
{
|
||||
auto handle = handlers.handle();
|
||||
handler_copy = copy_shared_handler(handle->get_module_config, topic);
|
||||
}
|
||||
|
||||
if (handler_copy) {
|
||||
(*handler_copy->handler)(topic, data);
|
||||
}
|
||||
}
|
||||
|
||||
void MessageHandler::handle_module_ready_message(const std::string& topic, const json& data) {
|
||||
SharedTypedHandler handler_copy;
|
||||
{
|
||||
auto handle = handlers.handle();
|
||||
handler_copy = copy_shared_handler(handle->module_ready, topic);
|
||||
}
|
||||
|
||||
if (handler_copy) {
|
||||
(*handler_copy->handler)(topic, data);
|
||||
}
|
||||
}
|
||||
|
||||
void MessageHandler::handle_cmd_result(const std::string& topic, const json& payload) {
|
||||
const auto& data = payload.at("data").at("data");
|
||||
const auto& id = data.at("id").get<std::string>();
|
||||
|
||||
std::shared_ptr<TypedHandler> handler_copy;
|
||||
{
|
||||
auto handle = responses.handle();
|
||||
auto it = handle->cmd.find(id);
|
||||
if (it != handle->cmd.end()) {
|
||||
handler_copy = it->second;
|
||||
handle->cmd.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
if (handler_copy) {
|
||||
(*handler_copy->handler)(topic, data);
|
||||
}
|
||||
}
|
||||
|
||||
void MessageHandler::handle_get_config_response(const std::string& topic, const json& payload) {
|
||||
std::shared_ptr<TypedHandler> handler_copy;
|
||||
{
|
||||
auto handle = responses.handle();
|
||||
if (handle->config) {
|
||||
handler_copy = handle->config;
|
||||
}
|
||||
}
|
||||
if (handler_copy) {
|
||||
(*handler_copy->handler)(topic, payload.at("data"));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Everest
|
||||
@@ -0,0 +1,78 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
#include <framework/ModuleAdapter.hpp>
|
||||
|
||||
namespace Everest {
|
||||
constexpr auto mqtt_provider_default_precision = 5;
|
||||
|
||||
ModuleBase::ModuleBase(const ModuleInfo& info) : info(info){};
|
||||
|
||||
void ModuleBase::invoke_init(ImplementationBase& impl) {
|
||||
impl.init();
|
||||
}
|
||||
|
||||
void ModuleBase::invoke_ready(ImplementationBase& impl) {
|
||||
impl.ready();
|
||||
}
|
||||
|
||||
void ModuleAdapter::check_complete() {
|
||||
// FIXME (aw): I should throw if some of my handlers are not set
|
||||
return;
|
||||
}
|
||||
|
||||
void ModuleAdapter::gather_cmds(ImplementationBase& impl) {
|
||||
impl._gather_cmds(registered_commands);
|
||||
}
|
||||
|
||||
MqttProvider::MqttProvider(ModuleAdapter& ev) : ev(ev){};
|
||||
|
||||
void MqttProvider::publish(const std::string& topic, const std::string& data, bool retain) {
|
||||
ev.ext_mqtt_publish(topic, data, retain);
|
||||
}
|
||||
|
||||
void MqttProvider::publish(const std::string& topic, const char* data, bool retain) {
|
||||
ev.ext_mqtt_publish(topic, std::string(data), retain);
|
||||
}
|
||||
|
||||
void MqttProvider::publish(const std::string& topic, bool data, bool retain) {
|
||||
if (data) {
|
||||
ev.ext_mqtt_publish(topic, "true", retain);
|
||||
} else {
|
||||
ev.ext_mqtt_publish(topic, "false", retain);
|
||||
}
|
||||
}
|
||||
|
||||
void MqttProvider::publish(const std::string& topic, int data, bool retain) {
|
||||
ev.ext_mqtt_publish(topic, std::to_string(data), retain);
|
||||
}
|
||||
|
||||
void MqttProvider::publish(const std::string& topic, double data, int precision, bool retain) {
|
||||
std::stringstream stream;
|
||||
stream << std::fixed << std::setprecision(precision) << data;
|
||||
ev.ext_mqtt_publish(topic, stream.str(), retain);
|
||||
}
|
||||
|
||||
void MqttProvider::publish(const std::string& topic, double data, bool retain) {
|
||||
this->publish(topic, data, mqtt_provider_default_precision, retain);
|
||||
}
|
||||
|
||||
UnsubscribeToken MqttProvider::subscribe(const std::string& topic, StringHandler handler) const {
|
||||
return ev.ext_mqtt_subscribe(topic, std::move(handler));
|
||||
}
|
||||
|
||||
UnsubscribeToken MqttProvider::subscribe(const std::string& topic, StringPairHandler handler) const {
|
||||
return ev.ext_mqtt_subscribe_pair(topic, std::move(handler));
|
||||
}
|
||||
|
||||
TelemetryProvider::TelemetryProvider(ModuleAdapter& ev) : ev(ev){};
|
||||
|
||||
void TelemetryProvider::publish(const std::string& category, const std::string& subcategory, const std::string& type,
|
||||
const TelemetryMap& telemetry) {
|
||||
ev.telemetry_publish(category, subcategory, type, telemetry);
|
||||
}
|
||||
|
||||
void TelemetryProvider::publish(const std::string& category, const std::string& subcategory,
|
||||
const TelemetryMap& telemetry) {
|
||||
publish(category, subcategory, subcategory, telemetry);
|
||||
}
|
||||
} // namespace Everest
|
||||
105
tools/EVerest-main/lib/everest/framework/lib/module_config.cpp
Normal file
105
tools/EVerest-main/lib/everest/framework/lib/module_config.cpp
Normal file
@@ -0,0 +1,105 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include <future>
|
||||
|
||||
#include <fmt/core.h>
|
||||
|
||||
#include <everest/exceptions.hpp>
|
||||
#include <everest/logging.hpp>
|
||||
|
||||
#include <utils/config_service.hpp>
|
||||
#include <utils/module_config.hpp>
|
||||
#include <utils/types.hpp>
|
||||
|
||||
namespace Everest {
|
||||
using json = nlohmann::json;
|
||||
|
||||
namespace {
|
||||
// TODO: make this use the ConfigServiceClient as well?
|
||||
// TODO: needs changes in the bindings to support a new get_module_config call
|
||||
json get_definitions(std::shared_ptr<MQTTAbstraction> mqtt) {
|
||||
const auto& everest_prefix = mqtt->get_everest_prefix();
|
||||
|
||||
json result;
|
||||
const auto interface_names_topic = fmt::format("{}interfaces", everest_prefix);
|
||||
const auto interface_names = mqtt->get(interface_names_topic, QOS::QOS2, config::mqtt_get_config_retries);
|
||||
auto interface_definitions = json::object();
|
||||
for (const auto& interface : interface_names) {
|
||||
auto interface_topic = fmt::format("{}interface_definitions/{}", everest_prefix, interface.get<std::string>());
|
||||
auto interface_definition = mqtt->get(interface_topic, QOS::QOS2, config::mqtt_get_config_retries);
|
||||
interface_definitions[interface] = interface_definition;
|
||||
}
|
||||
|
||||
result["interface_definitions"] = interface_definitions;
|
||||
|
||||
const auto type_names_topic = fmt::format("{}types", everest_prefix);
|
||||
const auto type_names = mqtt->get(type_names_topic, QOS::QOS2, config::mqtt_get_config_retries);
|
||||
auto type_definitions = json::object();
|
||||
for (const auto& type_name : type_names) {
|
||||
// type_definition keys already start with a / so omit it in the topic name
|
||||
auto type_topic = fmt::format("{}type_definitions{}", everest_prefix, type_name.get<std::string>());
|
||||
auto type_definition = mqtt->get(type_topic, QOS::QOS2, config::mqtt_get_config_retries);
|
||||
type_definitions[type_name] = type_definition;
|
||||
}
|
||||
|
||||
result["types"] = type_definitions;
|
||||
|
||||
const auto settings_topic = fmt::format("{}settings", everest_prefix);
|
||||
const auto settings = mqtt->get(settings_topic, QOS::QOS2, config::mqtt_get_config_retries);
|
||||
result["settings"] = settings;
|
||||
|
||||
const auto validate_schema = settings.value("validate_schema", json(false)).get<bool>();
|
||||
if (validate_schema) {
|
||||
const auto schemas_topic = fmt::format("{}schemas", everest_prefix);
|
||||
const auto schemas = mqtt->get(schemas_topic, QOS::QOS2, config::mqtt_get_config_retries);
|
||||
result["schemas"] = schemas;
|
||||
}
|
||||
|
||||
const auto module_names_topic = fmt::format("{}module_names", everest_prefix);
|
||||
const auto module_names = mqtt->get(module_names_topic, QOS::QOS2, config::mqtt_get_config_retries);
|
||||
result["module_names"] = module_names;
|
||||
|
||||
auto manifests = json::object();
|
||||
for (const auto& module_name : module_names) {
|
||||
auto manifest_topic = fmt::format("{}manifests/{}", everest_prefix, module_name.get<std::string>());
|
||||
auto manifest = mqtt->get(manifest_topic, QOS::QOS2, config::mqtt_get_config_retries);
|
||||
manifests[module_name] = manifest;
|
||||
}
|
||||
|
||||
result["manifests"] = manifests;
|
||||
|
||||
return result;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
json get_module_config(std::shared_ptr<MQTTAbstraction> mqtt, const std::string& module_id) {
|
||||
const auto& everest_prefix = mqtt->get_everest_prefix();
|
||||
|
||||
config::GetRequest get_request;
|
||||
get_request.type = config::GetType::Module;
|
||||
config::Request request;
|
||||
request.request = get_request;
|
||||
request.origin = module_id;
|
||||
request.type = config::Type::Get;
|
||||
|
||||
MQTTRequest mqtt_request;
|
||||
mqtt_request.response_topic = fmt::format("{}modules/{}/response", everest_prefix, module_id);
|
||||
mqtt_request.request_topic = fmt::format("{}config/request", everest_prefix);
|
||||
mqtt_request.request_data = json(request).dump();
|
||||
|
||||
json result;
|
||||
|
||||
config::Response response = mqtt->get(mqtt_request, config::mqtt_get_config_retries);
|
||||
EVLOG_verbose << fmt::format("Incoming config for {}", module_id);
|
||||
if (response.status == config::ResponseStatus::Ok and response.type.has_value() and
|
||||
response.type.value() == config::Type::Get) {
|
||||
auto get_response = std::get<config::GetResponse>(response.response);
|
||||
result = get_response.data;
|
||||
}
|
||||
result.update(get_definitions(mqtt));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace Everest
|
||||
@@ -0,0 +1,445 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <cstddef>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <thread>
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <netdb.h>
|
||||
#include <netinet/tcp.h>
|
||||
#include <poll.h>
|
||||
#include <sys/eventfd.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
|
||||
#include <boost/algorithm/string.hpp>
|
||||
#include <boost/algorithm/string/split.hpp>
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include <everest/exceptions.hpp>
|
||||
#include <everest/io/mqtt/mosquitto_cpp.hpp>
|
||||
#include <everest/io/mqtt/mqtt_client.hpp>
|
||||
#include <everest/logging.hpp>
|
||||
|
||||
#include <utils/mqtt_abstraction_impl.hpp>
|
||||
|
||||
namespace Everest {
|
||||
constexpr auto mqtt_keep_alive = 20;
|
||||
constexpr auto mqtt_get_timeout_ms = 5000; ///< Timeout for MQTT get in milliseconds
|
||||
constexpr auto mqtt_reconnect_timeout_ms = 2000; ///< MQTT reconnect timeout
|
||||
|
||||
namespace {
|
||||
everest::lib::io::mqtt::mqtt_client::QoS to_io_qos(Everest::QOS qos,
|
||||
everest::lib::io::mqtt::mqtt_client::QoS default_qos) {
|
||||
switch (qos) {
|
||||
case QOS::QOS0:
|
||||
return everest::lib::io::mqtt::mqtt_client::QoS::at_most_once;
|
||||
break;
|
||||
case QOS::QOS1:
|
||||
return everest::lib::io::mqtt::mqtt_client::QoS::at_least_once;
|
||||
break;
|
||||
case QOS::QOS2:
|
||||
return everest::lib::io::mqtt::mqtt_client::QoS::exactly_once;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return default_qos;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
MessageWithQOS::MessageWithQOS(const std::string& topic, const std::string& payload, QOS qos, bool retain) :
|
||||
Message{topic, payload}, qos(qos), retain(retain) {
|
||||
}
|
||||
|
||||
MQTTAbstractionImpl::MQTTAbstractionImpl(const MQTTSettings& mqtt_settings) :
|
||||
mqtt_everest_prefix(mqtt_settings.everest_prefix),
|
||||
mqtt_external_prefix(mqtt_settings.external_prefix),
|
||||
running(true) {
|
||||
BOOST_LOG_FUNCTION();
|
||||
|
||||
EVLOG_debug << "Initializing MQTT abstraction layer...";
|
||||
|
||||
if (mqtt_settings.uses_socket()) {
|
||||
this->mqtt_server_socket_path = mqtt_settings.broker_socket_path;
|
||||
} else {
|
||||
this->mqtt_server_address = mqtt_settings.broker_host;
|
||||
this->mqtt_server_port = mqtt_settings.broker_port;
|
||||
}
|
||||
|
||||
this->mqtt_is_connected = false;
|
||||
|
||||
this->mqtt_client = std::make_unique<everest::lib::io::mqtt::mqtt_client>(mqtt_reconnect_timeout_ms);
|
||||
this->mqtt_client->set_error_handler([](int error, std::string const& msg) {
|
||||
if (error) {
|
||||
EVLOG_error << fmt::format("MQTT error: {}", msg);
|
||||
}
|
||||
});
|
||||
this->mqtt_client->set_callback_connect([this](auto& mqtt, auto, auto, auto const&) { this->on_mqtt_connect(); });
|
||||
this->mqtt_client->set_callback_disconnect([this](auto& mqtt, auto, auto const&) { this->on_mqtt_disconnect(); });
|
||||
}
|
||||
|
||||
MQTTAbstractionImpl::~MQTTAbstractionImpl() {
|
||||
if (this->running.load()) {
|
||||
this->disconnect();
|
||||
}
|
||||
// this->mqtt_mainloop_thread.join();
|
||||
}
|
||||
|
||||
bool MQTTAbstractionImpl::connect() {
|
||||
BOOST_LOG_FUNCTION();
|
||||
|
||||
if (this->mqtt_is_connected) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!this->mqtt_server_socket_path.empty()) {
|
||||
EVLOG_debug << fmt::format("Connecting to MQTT broker: {}", this->mqtt_server_socket_path);
|
||||
const auto result = this->mqtt_client->connect(this->mqtt_server_socket_path, mqtt_keep_alive);
|
||||
return (result == everest::lib::io::mqtt::ErrorCode::Success);
|
||||
} else {
|
||||
EVLOG_debug << fmt::format("Connecting to MQTT broker: {}:{}", this->mqtt_server_address,
|
||||
this->mqtt_server_port);
|
||||
try {
|
||||
const auto result =
|
||||
this->mqtt_client->connect("", this->mqtt_server_address, this->mqtt_server_port, mqtt_keep_alive);
|
||||
return (result == everest::lib::io::mqtt::ErrorCode::Success);
|
||||
} catch (std::exception& e) {
|
||||
EVLOG_critical << fmt::format("Could not connect to MQTT broker: {}", e.what());
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void MQTTAbstractionImpl::disconnect() {
|
||||
BOOST_LOG_FUNCTION();
|
||||
|
||||
this->disconnect_event.notify();
|
||||
|
||||
// FIXME(kai): always set connected to false for the moment
|
||||
this->mqtt_is_connected = false;
|
||||
}
|
||||
|
||||
void MQTTAbstractionImpl::publish(const std::string& topic, const json& json) {
|
||||
BOOST_LOG_FUNCTION();
|
||||
|
||||
publish(topic, json, QOS::QOS2);
|
||||
}
|
||||
|
||||
void MQTTAbstractionImpl::publish(const std::string& topic, const json& json, QOS qos, bool retain) {
|
||||
BOOST_LOG_FUNCTION();
|
||||
|
||||
publish(topic, json.dump(), qos, retain);
|
||||
}
|
||||
|
||||
void MQTTAbstractionImpl::publish(const std::string& topic, const std::string& data) {
|
||||
BOOST_LOG_FUNCTION();
|
||||
|
||||
publish(topic, data, QOS::QOS0);
|
||||
}
|
||||
|
||||
void MQTTAbstractionImpl::publish(const std::string& topic, const std::string& data, QOS qos, bool retain) {
|
||||
BOOST_LOG_FUNCTION();
|
||||
|
||||
if (topic.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto mqtt_qos = to_io_qos(qos, everest::lib::io::mqtt::mqtt_client::QoS::at_most_once);
|
||||
|
||||
if (retain) {
|
||||
if (not(data.empty() and qos == QOS::QOS0)) {
|
||||
// topic should be retained, so save the topic in retained_topics
|
||||
// do not save the topic when the payload is empty and QOS is set to 0 which means a retained topic is to be
|
||||
// cleared
|
||||
auto handle = this->managed_topics.handle();
|
||||
handle->retained_topics.push_back(topic);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
auto handle = messages_before_connected.handle();
|
||||
if (!this->mqtt_is_connected) {
|
||||
handle->push_back(std::make_shared<MessageWithQOS>(topic, data, qos, retain));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const auto error = this->mqtt_client->publish(topic, data, mqtt_qos, retain, {});
|
||||
if (error != everest::lib::io::mqtt::ErrorCode::Success) {
|
||||
EVLOG_error << "MQTT error during publishing";
|
||||
}
|
||||
|
||||
EVLOG_verbose << fmt::format("publishing to topic: {} with payload: {} and qos: {} and retain: {}", topic, data,
|
||||
static_cast<int>(qos), static_cast<int>(retain));
|
||||
}
|
||||
|
||||
void MQTTAbstractionImpl::subscribe(const std::string& topic) {
|
||||
BOOST_LOG_FUNCTION();
|
||||
|
||||
subscribe(topic, QOS::QOS2);
|
||||
}
|
||||
|
||||
void MQTTAbstractionImpl::subscribe(const std::string& topic, QOS qos) {
|
||||
BOOST_LOG_FUNCTION();
|
||||
auto max_qos_level = to_io_qos(qos, everest::lib::io::mqtt::mqtt_client::QoS::at_most_once);
|
||||
|
||||
auto handle = this->managed_topics.handle();
|
||||
handle->subscribed_topics.insert(topic);
|
||||
|
||||
this->ev_handler.add_action([this, topic, max_qos_level]() {
|
||||
const auto result = this->mqtt_client->subscribe(
|
||||
topic,
|
||||
[this, topic]([[maybe_unused]] everest::lib::io::mqtt::mosquitto_cpp& client,
|
||||
everest::lib::io::mqtt::mosquitto_cpp::message const& message) {
|
||||
this->message_queue.emplace(topic, message.payload);
|
||||
this->new_message_event.notify();
|
||||
},
|
||||
max_qos_level);
|
||||
});
|
||||
}
|
||||
|
||||
void MQTTAbstractionImpl::unsubscribe(const std::string& topic) {
|
||||
BOOST_LOG_FUNCTION();
|
||||
auto handle = this->managed_topics.handle();
|
||||
|
||||
if (handle->subscribed_topics.find(topic) == handle->subscribed_topics.end()) {
|
||||
EVLOG_warning << fmt::format("Tried to unsubscribe from topic {} but it was not subscribed", topic);
|
||||
return;
|
||||
}
|
||||
|
||||
EVLOG_verbose << fmt::format("Unsubscribing from topic: {}", topic);
|
||||
|
||||
handle->subscribed_topics.erase(topic);
|
||||
this->ev_handler.add_action([this, topic]() { this->mqtt_client->unsubscribe(topic, {}); });
|
||||
}
|
||||
|
||||
void MQTTAbstractionImpl::clear_retained_topics() {
|
||||
BOOST_LOG_FUNCTION();
|
||||
auto handle = this->managed_topics.handle();
|
||||
|
||||
for (const auto& retained_topic : handle->retained_topics) {
|
||||
this->publish(retained_topic, std::string(), QOS::QOS0, true);
|
||||
EVLOG_verbose << "Cleared retained topic: " << retained_topic;
|
||||
}
|
||||
|
||||
handle->retained_topics.clear();
|
||||
}
|
||||
|
||||
json MQTTAbstractionImpl::get(const MQTTRequest& request, std::size_t retries) {
|
||||
BOOST_LOG_FUNCTION();
|
||||
std::size_t attempt = 0;
|
||||
while (attempt <= retries) {
|
||||
try {
|
||||
return this->get_internal(request);
|
||||
} catch (const EverestTimeoutError& error) {
|
||||
if (attempt < retries) {
|
||||
attempt += 1;
|
||||
} else {
|
||||
std::rethrow_exception(std::current_exception());
|
||||
}
|
||||
}
|
||||
}
|
||||
EVLOG_AND_THROW(
|
||||
EverestInternalError(fmt::format("Unknown error while waiting for result of get({})", request.response_topic)));
|
||||
}
|
||||
|
||||
std::unique_ptr<MQTTAbstraction> make_mqtt_abstraction(const MQTTSettings& mqtt_settings) {
|
||||
return std::make_unique<MQTTAbstractionImpl>(mqtt_settings);
|
||||
}
|
||||
|
||||
json MQTTAbstractionImpl::get(const std::string& topic, QOS qos, std::size_t retries) {
|
||||
BOOST_LOG_FUNCTION();
|
||||
const MQTTRequest request = {topic, qos};
|
||||
return this->get(request, retries);
|
||||
}
|
||||
|
||||
const std::string& MQTTAbstractionImpl::get_everest_prefix() const {
|
||||
BOOST_LOG_FUNCTION();
|
||||
return mqtt_everest_prefix;
|
||||
}
|
||||
|
||||
const std::string& MQTTAbstractionImpl::get_external_prefix() const {
|
||||
BOOST_LOG_FUNCTION();
|
||||
return mqtt_external_prefix;
|
||||
}
|
||||
|
||||
nlohmann::json MQTTAbstractionImpl::get_internal(const MQTTRequest& request) {
|
||||
BOOST_LOG_FUNCTION();
|
||||
std::promise<json> res_promise;
|
||||
std::future<json> res_future = res_promise.get_future();
|
||||
|
||||
// Why repsonse by value, wouldn't a reference do?
|
||||
const auto res_handler = [&res_promise](const std::string& /*topic*/, json response) {
|
||||
res_promise.set_value(std::move(response));
|
||||
};
|
||||
|
||||
// FIXME: use configurable HandlerType?
|
||||
const auto res_token =
|
||||
std::make_shared<TypedHandler>(HandlerType::GetConfigResponse, std::make_shared<Handler>(res_handler));
|
||||
this->register_handler(request.response_topic, res_token, request.qos);
|
||||
if (request.request_topic.has_value()) {
|
||||
if (request.request_data.has_value()) {
|
||||
MqttMessagePayload payload{MqttMessageType::GetConfig, json::parse(request.request_data.value())};
|
||||
this->publish(request.request_topic.value(), payload, request.qos);
|
||||
} else {
|
||||
MqttMessagePayload payload{MqttMessageType::GetConfig, json{}};
|
||||
this->publish(request.request_topic.value(), payload, request.qos);
|
||||
}
|
||||
}
|
||||
// wait for result future
|
||||
auto res_future_status = res_future.wait_for(request.timeout);
|
||||
|
||||
if (res_future_status == std::future_status::timeout) {
|
||||
this->unregister_handler(request.response_topic, res_token);
|
||||
EVLOG_AND_THROW(
|
||||
EverestTimeoutError(fmt::format("Timeout while waiting for result of get({})", request.response_topic)));
|
||||
}
|
||||
if (res_future_status == std::future_status::ready) {
|
||||
this->unregister_handler(request.response_topic, res_token);
|
||||
return res_future.get();
|
||||
}
|
||||
|
||||
return json{};
|
||||
}
|
||||
|
||||
std::shared_future<void> MQTTAbstractionImpl::spawn_main_loop_thread() {
|
||||
BOOST_LOG_FUNCTION();
|
||||
|
||||
std::packaged_task<void(void)> task([this]() {
|
||||
try {
|
||||
this->ev_handler.register_event_handler(this->mqtt_client.get());
|
||||
this->ev_handler.register_event_handler(&this->disconnect_event,
|
||||
[this](const auto&) { this->running = false; });
|
||||
this->ev_handler.register_event_handler(&this->new_message_event,
|
||||
[this](const auto&) { on_mqtt_message(); });
|
||||
|
||||
this->ev_handler.run(this->running);
|
||||
} catch (boost::exception& e) {
|
||||
EVLOG_critical << fmt::format("Caught MQTT mainloop boost::exception:\n{}",
|
||||
boost::diagnostic_information(e, true));
|
||||
exit(1);
|
||||
} catch (std::exception& e) {
|
||||
EVLOG_critical << fmt::format("Caught MQTT mainloop std::exception:\n{}",
|
||||
boost::diagnostic_information(e, true));
|
||||
exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
this->main_loop_future = task.get_future();
|
||||
this->mqtt_mainloop_thread = std::thread(std::move(task));
|
||||
return this->main_loop_future;
|
||||
}
|
||||
|
||||
std::shared_future<void> MQTTAbstractionImpl::get_main_loop_future() {
|
||||
BOOST_LOG_FUNCTION();
|
||||
return this->main_loop_future;
|
||||
}
|
||||
|
||||
void MQTTAbstractionImpl::on_mqtt_message() {
|
||||
while (true) {
|
||||
auto msg = message_queue.pop();
|
||||
if (not msg.has_value()) {
|
||||
break;
|
||||
}
|
||||
handle_mqtt_message(msg.value());
|
||||
}
|
||||
}
|
||||
|
||||
void MQTTAbstractionImpl::handle_mqtt_message(const Message& message) {
|
||||
BOOST_LOG_FUNCTION();
|
||||
|
||||
EVLOG_verbose << "Incoming MQTT message. topic: " << message.topic << " payload: " << message.payload;
|
||||
|
||||
const auto& topic = message.topic;
|
||||
const auto& payload = message.payload;
|
||||
|
||||
try {
|
||||
std::string_view topic_view = topic;
|
||||
std::string_view mqtt_everest_prefix_view = mqtt_everest_prefix;
|
||||
if (topic_view.size() >= mqtt_everest_prefix_view.size() &&
|
||||
topic_view.compare(0, mqtt_everest_prefix_view.size(), mqtt_everest_prefix_view) == 0) {
|
||||
EVLOG_verbose << fmt::format("topic {} starts with {}", topic, mqtt_everest_prefix);
|
||||
try {
|
||||
this->message_handler.add(ParsedMessage{std::move(topic), json::parse(payload.begin(), payload.end())});
|
||||
} catch (nlohmann::detail::parse_error& e) {
|
||||
EVLOG_warning << fmt::format("Could not decode json for incoming topic '{}': {}", topic, payload);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
EVLOG_debug << fmt::format("Message parsing for topic '{}' not implemented. Wrapping in json object.",
|
||||
topic);
|
||||
this->message_handler.add(ParsedMessage{std::move(topic), std::move(payload)});
|
||||
}
|
||||
} catch (const boost::exception& e) {
|
||||
EVLOG_critical << fmt::format("Caught MQTT on_message boost::exception:\n{}",
|
||||
boost::diagnostic_information(e, true));
|
||||
exit(1);
|
||||
} catch (const std::exception& e) {
|
||||
EVLOG_critical << fmt::format("Caught MQTT on_message std::exception:\n{}",
|
||||
boost::diagnostic_information(e, true));
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
void MQTTAbstractionImpl::on_mqtt_connect() {
|
||||
BOOST_LOG_FUNCTION();
|
||||
|
||||
EVLOG_debug << "Connected to MQTT broker";
|
||||
|
||||
// Drain the messages_before_connected under the lock, then publish outside it.
|
||||
// publish() itself acquires the same monitor lock, so calling it
|
||||
// while holding the handle would deadlock
|
||||
shared_messages to_publish;
|
||||
{
|
||||
auto handle = messages_before_connected.handle();
|
||||
this->mqtt_is_connected = true;
|
||||
to_publish = std::move(*handle);
|
||||
}
|
||||
for (auto& message : to_publish) {
|
||||
this->publish(message->topic, message->payload, message->qos, message->retain);
|
||||
}
|
||||
}
|
||||
|
||||
void MQTTAbstractionImpl::on_mqtt_disconnect() {
|
||||
BOOST_LOG_FUNCTION();
|
||||
|
||||
EVLOG_AND_THROW(EverestInternalError("Lost connection to MQTT broker"));
|
||||
}
|
||||
|
||||
void MQTTAbstractionImpl::register_handler(const std::string& topic, std::shared_ptr<TypedHandler> handler, QOS qos) {
|
||||
BOOST_LOG_FUNCTION();
|
||||
|
||||
auto subscription_required = [this](const std::string& topic) {
|
||||
auto handle = this->managed_topics.handle();
|
||||
return std::find(handle->subscribed_topics.begin(), handle->subscribed_topics.end(), topic) ==
|
||||
handle->subscribed_topics.end();
|
||||
};
|
||||
|
||||
this->message_handler.register_handler(topic, handler);
|
||||
|
||||
if (subscription_required(topic)) {
|
||||
EVLOG_verbose << fmt::format("Subscribing to {}", topic);
|
||||
this->subscribe(topic, qos);
|
||||
}
|
||||
}
|
||||
|
||||
void MQTTAbstractionImpl::unregister_handler(const std::string& topic, const Token& token) {
|
||||
BOOST_LOG_FUNCTION();
|
||||
|
||||
EVLOG_verbose << fmt::format("Unregistering handler {} for {}", fmt::ptr(&token), topic);
|
||||
|
||||
if (this->mqtt_is_connected) {
|
||||
this->unsubscribe(topic);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Everest
|
||||
796
tools/EVerest-main/lib/everest/framework/lib/runtime.cpp
Normal file
796
tools/EVerest-main/lib/everest/framework/lib/runtime.cpp
Normal file
@@ -0,0 +1,796 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include <framework/runtime.hpp>
|
||||
#include <utils/config/storage_sqlite.hpp>
|
||||
#include <utils/error.hpp>
|
||||
#include <utils/error/error_factory.hpp>
|
||||
#include <utils/error/error_json.hpp>
|
||||
#include <utils/error/error_manager_impl.hpp>
|
||||
#include <utils/error/error_manager_req.hpp>
|
||||
#include <utils/error/error_state_monitor.hpp>
|
||||
#include <utils/filesystem.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdlib>
|
||||
#include <fstream>
|
||||
|
||||
#include <boost/program_options.hpp>
|
||||
|
||||
namespace Everest {
|
||||
|
||||
namespace po = boost::program_options;
|
||||
|
||||
std::string parse_string_option(const po::variables_map& vm, const char* option) {
|
||||
if (vm.count(option) == 0) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return vm[option].as<std::string>();
|
||||
}
|
||||
|
||||
void populate_module_info_path_from_runtime_settings(ModuleInfo& mi, const RuntimeSettings& rs) {
|
||||
mi.paths.etc = rs.etc_dir;
|
||||
mi.paths.libexec = rs.modules_dir / mi.name;
|
||||
mi.paths.share = rs.data_dir / defaults::MODULES_DIR / mi.name;
|
||||
}
|
||||
|
||||
ManagerSettings::ManagerSettings(const std::string& prefix_, const std::string& config_) :
|
||||
boot_mode(ConfigBootMode::YamlFile) { // NOLINT(cppcoreguidelines-use-default-member-init): already default
|
||||
// initialized, but repeated for clarity
|
||||
init_prefix_and_data_dir(prefix_);
|
||||
init_config_file(config_);
|
||||
const auto settings = everest::config::parse_settings(config.value("settings", json::object()));
|
||||
if (settings.prefix.has_value()) {
|
||||
EVLOG_warning << "Setting the prefix in the config file is deprecated. Please use the --prefix command line "
|
||||
"option instead.";
|
||||
}
|
||||
init_settings(settings);
|
||||
}
|
||||
|
||||
ManagerSettings::ManagerSettings(const std::string& prefix_, const std::string& db_, DatabaseTag) :
|
||||
boot_mode(ConfigBootMode::Database) {
|
||||
|
||||
init_prefix_and_data_dir(prefix_);
|
||||
|
||||
db_dir = assert_file(db_, "User provided database");
|
||||
|
||||
const auto migrations_dir = this->runtime_settings.data_dir / "migrations";
|
||||
this->storage = std::make_unique<everest::config::SqliteStorage>(db_dir, migrations_dir);
|
||||
|
||||
if (!this->storage->contains_valid_config()) {
|
||||
throw BootException("Database not initialized or valid");
|
||||
}
|
||||
|
||||
EVLOG_info << "Booting and parsing configuration from database: " << db_dir;
|
||||
const auto settings_response = this->storage->get_settings();
|
||||
if (settings_response.status != everest::config::GenericResponseStatus::OK or
|
||||
!settings_response.settings.has_value()) {
|
||||
throw BootException("Failed to load settings from database");
|
||||
}
|
||||
const auto settings = settings_response.settings.value();
|
||||
init_settings(settings);
|
||||
this->storage->write_settings(*this);
|
||||
}
|
||||
|
||||
ManagerSettings::ManagerSettings(const std::string& prefix_, const std::string& config_, const std::string& db_) :
|
||||
boot_mode(ConfigBootMode::DatabaseInit) {
|
||||
|
||||
init_prefix_and_data_dir(prefix_);
|
||||
init_config_file(config_);
|
||||
|
||||
const auto migrations_dir = this->runtime_settings.data_dir / "migrations";
|
||||
this->storage = std::make_unique<everest::config::SqliteStorage>(fs::path(db_), migrations_dir);
|
||||
|
||||
everest::config::Settings settings;
|
||||
if (this->storage->contains_valid_config()) {
|
||||
EVLOG_info << "Booting and parsing configuration from database: " << db_;
|
||||
const auto settings_response = this->storage->get_settings();
|
||||
if (settings_response.status != everest::config::GenericResponseStatus::OK or
|
||||
!settings_response.settings.has_value()) {
|
||||
throw BootException("Failed to load settings from database");
|
||||
}
|
||||
settings = settings_response.settings.value();
|
||||
} else {
|
||||
EVLOG_info << "Database not initialized or valid, falling back to YAML config file: " << config_;
|
||||
this->storage->wipe();
|
||||
settings = everest::config::parse_settings(config.value("settings", json::object()));
|
||||
}
|
||||
|
||||
init_settings(settings);
|
||||
this->storage->write_settings(*this);
|
||||
}
|
||||
|
||||
void ManagerSettings::init_settings(const everest::config::Settings& settings) {
|
||||
if (this->runtime_settings.prefix.empty()) {
|
||||
throw std::runtime_error(
|
||||
"Prefix must be set before initializing the settings. Please call init_prefix_and_data_dir() first.");
|
||||
}
|
||||
if (this->runtime_settings.data_dir.empty()) {
|
||||
throw std::runtime_error("Data directory must be set before initializing the settings. Please call "
|
||||
"init_prefix_and_data_dir() first.");
|
||||
}
|
||||
|
||||
const auto prefix = this->runtime_settings.prefix;
|
||||
const auto data_dir = this->runtime_settings.data_dir;
|
||||
fs::path etc_dir;
|
||||
{
|
||||
// etc directory
|
||||
const auto default_etc_dir = (fs::path(defaults::SYSCONF_DIR) / defaults::NAMESPACE).relative_path();
|
||||
if (prefix.string() == "/usr") {
|
||||
etc_dir = fs::path("/") / default_etc_dir;
|
||||
} else if (prefix.filename() == "usr") {
|
||||
etc_dir = prefix.parent_path() / default_etc_dir;
|
||||
} else {
|
||||
etc_dir = prefix / default_etc_dir;
|
||||
}
|
||||
etc_dir = assert_dir(etc_dir.string(), "Default etc directory");
|
||||
}
|
||||
|
||||
if (settings.configs_dir.has_value()) {
|
||||
auto settings_configs_dir = get_prefixed_path_from_json(settings.configs_dir.value().string(), prefix);
|
||||
configs_dir = assert_dir(settings_configs_dir, "Config provided configs directory");
|
||||
} else {
|
||||
configs_dir = assert_dir(etc_dir.string(), "Default configs directory");
|
||||
}
|
||||
|
||||
if (settings.schemas_dir.has_value()) {
|
||||
const auto settings_schemas_dir = get_prefixed_path_from_json(settings.schemas_dir.value().string(), prefix);
|
||||
schemas_dir = assert_dir(settings_schemas_dir, "Config provided schema directory");
|
||||
} else {
|
||||
const auto default_schemas_dir = data_dir / defaults::SCHEMAS_DIR;
|
||||
schemas_dir = assert_dir(default_schemas_dir.string(), "Default schema directory");
|
||||
}
|
||||
|
||||
if (settings.interfaces_dir.has_value()) {
|
||||
const auto settings_interfaces_dir =
|
||||
get_prefixed_path_from_json(settings.interfaces_dir.value().string(), prefix);
|
||||
interfaces_dir = assert_dir(settings_interfaces_dir, "Config provided interface directory");
|
||||
} else {
|
||||
const auto default_interfaces_dir = data_dir / defaults::INTERFACES_DIR;
|
||||
interfaces_dir = assert_dir(default_interfaces_dir, "Default interface directory");
|
||||
}
|
||||
|
||||
fs::path modules_dir;
|
||||
if (settings.modules_dir.has_value()) {
|
||||
const auto settings_modules_dir = get_prefixed_path_from_json(settings.modules_dir.value().string(), prefix);
|
||||
modules_dir = assert_dir(settings_modules_dir, "Config provided module directory");
|
||||
} else {
|
||||
const auto default_modules_dir = prefix / defaults::LIBEXEC_DIR / defaults::NAMESPACE / defaults::MODULES_DIR;
|
||||
modules_dir = assert_dir(default_modules_dir, "Default module directory");
|
||||
}
|
||||
|
||||
if (settings.types_dir.has_value()) {
|
||||
const auto settings_types_dir = get_prefixed_path_from_json(settings.types_dir.value().string(), prefix);
|
||||
types_dir = assert_dir(settings_types_dir, "Config provided type directory");
|
||||
} else {
|
||||
const auto default_types_dir = data_dir / defaults::TYPES_DIR;
|
||||
types_dir = assert_dir(default_types_dir, "Default type directory");
|
||||
}
|
||||
|
||||
if (settings.errors_dir.has_value()) {
|
||||
const auto settings_errors_dir = get_prefixed_path_from_json(settings.errors_dir.value().string(), prefix);
|
||||
errors_dir = assert_dir(settings_errors_dir, "Config provided error directory");
|
||||
} else {
|
||||
const auto default_errors_dir = data_dir / defaults::ERRORS_DIR;
|
||||
errors_dir = assert_dir(default_errors_dir, "Default error directory");
|
||||
}
|
||||
|
||||
if (settings.www_dir.has_value()) {
|
||||
const auto settings_www_dir = get_prefixed_path_from_json(settings.www_dir.value().string(), prefix);
|
||||
www_dir = assert_dir(settings_www_dir, "Config provided www directory");
|
||||
} else {
|
||||
const auto default_www_dir = data_dir / defaults::WWW_DIR;
|
||||
www_dir = assert_dir(default_www_dir, "Default www directory");
|
||||
}
|
||||
|
||||
fs::path logging_config_file;
|
||||
if (settings.logging_config_file.has_value()) {
|
||||
const auto settings_logging_config_file =
|
||||
get_prefixed_path_from_json(settings.logging_config_file.value().string(), prefix);
|
||||
logging_config_file = assert_file(settings_logging_config_file, "Config provided logging config");
|
||||
} else {
|
||||
auto default_logging_config_file =
|
||||
fs::path(defaults::SYSCONF_DIR) / defaults::NAMESPACE / defaults::LOGGING_CONFIG_NAME;
|
||||
if (prefix.string() != "/usr") {
|
||||
default_logging_config_file = prefix / default_logging_config_file;
|
||||
} else {
|
||||
default_logging_config_file = fs::path("/") / default_logging_config_file;
|
||||
}
|
||||
|
||||
logging_config_file = assert_file(default_logging_config_file, "Default logging config");
|
||||
}
|
||||
|
||||
if (settings.controller_port.has_value()) {
|
||||
controller_port = settings.controller_port.value();
|
||||
} else {
|
||||
controller_port = defaults::CONTROLLER_PORT;
|
||||
}
|
||||
|
||||
if (settings.controller_rpc_timeout_ms.has_value()) {
|
||||
controller_rpc_timeout_ms = settings.controller_rpc_timeout_ms.value();
|
||||
} else {
|
||||
controller_rpc_timeout_ms = defaults::CONTROLLER_RPC_TIMEOUT_MS;
|
||||
}
|
||||
|
||||
std::string mqtt_broker_socket_path;
|
||||
std::string mqtt_broker_host;
|
||||
std::uint16_t mqtt_broker_port = 0;
|
||||
std::string mqtt_everest_prefix;
|
||||
std::string mqtt_external_prefix;
|
||||
|
||||
// Unix Domain Socket configuration MUST be set in the configuration,
|
||||
// doesn't have a default value if not provided thus it takes precedence
|
||||
// over default values - this is to have backward compatiblity in term of configuration
|
||||
// in case both UDS (Unix Domain Sockets) and IDS (Internet Domain Sockets) are set in config, raise exception
|
||||
if (settings.mqtt_broker_socket_path.has_value()) {
|
||||
mqtt_broker_socket_path = settings.mqtt_broker_socket_path.value();
|
||||
}
|
||||
|
||||
if (settings.mqtt_broker_host.has_value()) {
|
||||
mqtt_broker_host = settings.mqtt_broker_host.value();
|
||||
if (!mqtt_broker_socket_path.empty()) {
|
||||
// invalid configuration, can't have both UDS and IDS
|
||||
throw BootException(
|
||||
fmt::format("Setting both the Unix Domain Socket {} and Internet Domain Socket {} in config is invalid",
|
||||
mqtt_broker_socket_path, mqtt_broker_host));
|
||||
}
|
||||
} else {
|
||||
mqtt_broker_host = defaults::MQTT_BROKER_HOST;
|
||||
}
|
||||
|
||||
// overwrite mqtt broker host with environment variable
|
||||
// NOLINTNEXTLINE(concurrency-mt-unsafe): not problematic that this function is not threadsafe here
|
||||
const char* mqtt_server_address = std::getenv("MQTT_SERVER_ADDRESS");
|
||||
if (mqtt_server_address != nullptr) {
|
||||
mqtt_broker_host = mqtt_server_address;
|
||||
if (!mqtt_broker_socket_path.empty()) {
|
||||
// invalid configuration, can't have both UDS and IDS
|
||||
throw BootException(
|
||||
fmt::format("Setting both the Unix Domain Socket {} and Internet Domain Socket {} in "
|
||||
"config and as environment variable respectivelly (as MQTT_SERVER_ADDRESS) is not allowed",
|
||||
mqtt_broker_socket_path, mqtt_broker_host));
|
||||
}
|
||||
}
|
||||
|
||||
if (settings.mqtt_broker_port.has_value()) {
|
||||
mqtt_broker_port = settings.mqtt_broker_port.value();
|
||||
} else {
|
||||
mqtt_broker_port = defaults::MQTT_BROKER_PORT;
|
||||
}
|
||||
|
||||
// overwrite mqtt broker port with environment variable
|
||||
// NOLINTNEXTLINE(concurrency-mt-unsafe): not problematic that this function is not threadsafe here
|
||||
const char* mqtt_server_port = std::getenv("MQTT_SERVER_PORT");
|
||||
|
||||
if (mqtt_server_port != nullptr) {
|
||||
try {
|
||||
mqtt_broker_port = std::stoul(mqtt_server_port);
|
||||
} catch (...) {
|
||||
EVLOG_warning << "Environment variable MQTT_SERVER_PORT set, but not set to an integer. Ignoring.";
|
||||
}
|
||||
}
|
||||
|
||||
if (settings.mqtt_everest_prefix.has_value()) {
|
||||
mqtt_everest_prefix = settings.mqtt_everest_prefix.value();
|
||||
} else {
|
||||
mqtt_everest_prefix = defaults::MQTT_EVEREST_PREFIX;
|
||||
}
|
||||
|
||||
// always make sure the everest mqtt prefix ends with '/'
|
||||
if (mqtt_everest_prefix.length() > 0 && mqtt_everest_prefix.back() != '/') {
|
||||
mqtt_everest_prefix = mqtt_everest_prefix += "/";
|
||||
}
|
||||
|
||||
if (settings.mqtt_external_prefix.has_value()) {
|
||||
mqtt_external_prefix = settings.mqtt_external_prefix.value();
|
||||
} else {
|
||||
mqtt_external_prefix = defaults::MQTT_EXTERNAL_PREFIX;
|
||||
}
|
||||
|
||||
if (mqtt_everest_prefix == mqtt_external_prefix) {
|
||||
throw BootException(fmt::format("mqtt_everest_prefix '{}' cannot be equal to mqtt_external_prefix '{}'!",
|
||||
mqtt_everest_prefix, mqtt_external_prefix));
|
||||
}
|
||||
|
||||
if (not mqtt_broker_socket_path.empty()) {
|
||||
populate_mqtt_settings(this->mqtt_settings, mqtt_broker_socket_path, mqtt_everest_prefix, mqtt_external_prefix);
|
||||
} else {
|
||||
populate_mqtt_settings(this->mqtt_settings, mqtt_broker_host, mqtt_broker_port, mqtt_everest_prefix,
|
||||
mqtt_external_prefix);
|
||||
}
|
||||
|
||||
run_as_user = settings.run_as_user.value_or("");
|
||||
|
||||
auto version_information_path = data_dir / VERSION_INFORMATION_FILE;
|
||||
if (fs::exists(version_information_path)) {
|
||||
std::ifstream ifs(version_information_path.string());
|
||||
version_information = std::string(std::istreambuf_iterator<char>(ifs), std::istreambuf_iterator<char>());
|
||||
} else {
|
||||
version_information = "unknown";
|
||||
}
|
||||
|
||||
std::string telemetry_prefix;
|
||||
if (settings.telemetry_prefix.has_value()) {
|
||||
telemetry_prefix = settings.telemetry_prefix.value();
|
||||
} else {
|
||||
telemetry_prefix = defaults::TELEMETRY_PREFIX;
|
||||
}
|
||||
|
||||
// always make sure the telemetry mqtt prefix ends with '/'
|
||||
if (telemetry_prefix.length() > 0 && telemetry_prefix.back() != '/') {
|
||||
telemetry_prefix = telemetry_prefix += "/";
|
||||
}
|
||||
|
||||
bool telemetry_enabled = defaults::TELEMETRY_ENABLED;
|
||||
if (settings.telemetry_enabled.has_value()) {
|
||||
telemetry_enabled = settings.telemetry_enabled.value();
|
||||
} else {
|
||||
telemetry_enabled = defaults::TELEMETRY_ENABLED;
|
||||
}
|
||||
|
||||
bool validate_schema = defaults::VALIDATE_SCHEMA;
|
||||
if (settings.validate_schema.has_value()) {
|
||||
validate_schema = settings.validate_schema.value();
|
||||
} else {
|
||||
validate_schema = defaults::VALIDATE_SCHEMA;
|
||||
}
|
||||
|
||||
bool forward_exceptions = defaults::FORWARD_EXCEPTIONS;
|
||||
if (settings.forward_exceptions.has_value()) {
|
||||
forward_exceptions = settings.forward_exceptions.value();
|
||||
} else {
|
||||
forward_exceptions = defaults::FORWARD_EXCEPTIONS;
|
||||
}
|
||||
|
||||
populate_runtime_settings(this->runtime_settings, prefix, etc_dir, data_dir, modules_dir, logging_config_file,
|
||||
telemetry_prefix, telemetry_enabled, validate_schema, forward_exceptions);
|
||||
}
|
||||
|
||||
void ManagerSettings::init_prefix_and_data_dir(const std::string& prefix_) {
|
||||
fs::path prefix;
|
||||
if (prefix_.length() != 0) {
|
||||
// user provided
|
||||
prefix = assert_dir(prefix_, "User provided prefix");
|
||||
}
|
||||
if (prefix.empty()) {
|
||||
prefix = assert_dir(defaults::PREFIX, "Default prefix");
|
||||
}
|
||||
runtime_settings.data_dir =
|
||||
assert_dir((prefix / defaults::DATAROOT_DIR / defaults::NAMESPACE).string(), "Default share directory");
|
||||
runtime_settings.prefix = prefix;
|
||||
}
|
||||
|
||||
void ManagerSettings::init_config_file(const std::string& config_) {
|
||||
if (this->runtime_settings.prefix.empty()) {
|
||||
throw std::runtime_error(
|
||||
"Prefix must be set before initializing the config file. Please call init_prefix_and_data_dir() first.");
|
||||
}
|
||||
|
||||
if (config_.length() != 0) {
|
||||
try {
|
||||
config_file = assert_file(config_, "User profided config");
|
||||
} catch (const BootException& e) {
|
||||
if (has_extension(config_file, ".yaml")) {
|
||||
throw;
|
||||
}
|
||||
|
||||
// otherwise, we propbably got a simple config file name
|
||||
}
|
||||
}
|
||||
|
||||
if (config_file.empty()) {
|
||||
auto config_file_prefix = this->runtime_settings.prefix;
|
||||
if (config_file_prefix.empty()) {
|
||||
config_file_prefix = assert_dir(defaults::PREFIX, "Default prefix");
|
||||
}
|
||||
|
||||
if (config_file_prefix.string() == "/usr") {
|
||||
// we're going to look in /etc, which isn't prefixed by /usr
|
||||
config_file_prefix = "/";
|
||||
}
|
||||
|
||||
if (config_.length() != 0) {
|
||||
// user provided short form
|
||||
|
||||
const auto user_config_file =
|
||||
config_file_prefix / defaults::SYSCONF_DIR / defaults::NAMESPACE / fmt::format("{}.yaml", config_);
|
||||
|
||||
const auto short_form_alias = fmt::format("User provided (by using short form: '{}')", config_);
|
||||
|
||||
config_file = assert_file(user_config_file, short_form_alias);
|
||||
} else {
|
||||
// default
|
||||
config_file =
|
||||
assert_file(config_file_prefix / defaults::SYSCONF_DIR / defaults::NAMESPACE / defaults::CONFIG_NAME,
|
||||
"Default config");
|
||||
}
|
||||
}
|
||||
|
||||
// now the config file should have been found
|
||||
if (config_file.empty()) {
|
||||
throw std::runtime_error("Assertion for found config file failed");
|
||||
}
|
||||
|
||||
config = load_yaml(config_file);
|
||||
if (config == nullptr) {
|
||||
EVLOG_info << "Config file is null, treating it as empty";
|
||||
config = json::object();
|
||||
} else if (!config.is_object()) {
|
||||
throw BootException(fmt::format("Config file '{}' is not an object", config_file.string()));
|
||||
}
|
||||
}
|
||||
|
||||
ModuleCallbacks::ModuleCallbacks(
|
||||
const std::function<void(ModuleAdapter module_adapter)>& register_module_adapter,
|
||||
const std::function<std::vector<cmd>(const RequirementInitialization& requirement_init)>& 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) {
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays): pass-through of argc and argv from main()
|
||||
ModuleLoader::ModuleLoader(int argc, char* argv[], ModuleCallbacks callbacks, VersionInformation version_information) :
|
||||
runtime_settings(nullptr), callbacks(std::move(callbacks)), version_information(std::move(version_information)) {
|
||||
try {
|
||||
if (!this->parse_command_line(argc, argv)) {
|
||||
this->should_exit = true;
|
||||
}
|
||||
} catch (std::exception& e) {
|
||||
std::cout << "Error during command line parsing: " << e.what() << "\n";
|
||||
this->should_exit = true;
|
||||
}
|
||||
}
|
||||
|
||||
int ModuleLoader::initialize() {
|
||||
if (this->should_exit) {
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
Logging::init(this->logging_config_file.string(), this->module_id);
|
||||
|
||||
const auto start_time = std::chrono::steady_clock::now();
|
||||
|
||||
this->mqtt = std::shared_ptr<MQTTAbstraction>(make_mqtt_abstraction(this->mqtt_settings));
|
||||
this->mqtt->connect();
|
||||
this->mqtt->spawn_main_loop_thread();
|
||||
|
||||
const auto result = get_module_config(this->mqtt, this->module_id);
|
||||
const auto get_config_time = std::chrono::steady_clock::now();
|
||||
EVLOG_debug << "Module " << fmt::format(TERMINAL_STYLE_OK, "{}", module_id) << " get_config() ["
|
||||
<< std::chrono::duration_cast<std::chrono::milliseconds>(get_config_time - start_time).count() << "ms]";
|
||||
|
||||
RuntimeSettings result_settings = result.at("settings");
|
||||
this->runtime_settings = std::make_unique<RuntimeSettings>(std::move(result_settings));
|
||||
|
||||
if (!this->runtime_settings) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const auto& rs = this->runtime_settings;
|
||||
const auto shutdown_mqtt = [this]() {
|
||||
if (this->mqtt) {
|
||||
try {
|
||||
this->mqtt->disconnect();
|
||||
} catch (const std::exception& e) {
|
||||
EVLOG_critical << fmt::format("MQTT disconnect in exception path failed: {}", e.what());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
const auto config = Config(this->mqtt_settings, result);
|
||||
const auto config_instantiation_time = std::chrono::steady_clock::now();
|
||||
EVLOG_debug
|
||||
<< "Module " << fmt::format(TERMINAL_STYLE_OK, "{}", module_id) << " after Config() instantiation ["
|
||||
<< std::chrono::duration_cast<std::chrono::milliseconds>(config_instantiation_time - start_time).count()
|
||||
<< "ms]";
|
||||
|
||||
if (!config.contains(this->module_id)) {
|
||||
EVLOG_error << fmt::format("Module id '{}' not found in config!", this->module_id);
|
||||
return 2;
|
||||
}
|
||||
|
||||
const std::string module_identifier = config.printable_identifier(this->module_id);
|
||||
const auto module_name = config.get_module_name(this->module_id);
|
||||
if ((this->application_name != module_name) and (this->application_name != module_identifier)) {
|
||||
EVLOG_error << fmt::format(
|
||||
"Module id '{}': Expected a '{}' module, but it looks like you started a '{}' module.", this->module_id,
|
||||
module_name, this->application_name);
|
||||
}
|
||||
EVLOG_debug << fmt::format("Initializing framework for module {}...", module_identifier);
|
||||
EVLOG_verbose << fmt::format("Setting process name to: '{}'...", module_identifier);
|
||||
const int prctl_return = prctl(PR_SET_NAME, module_identifier.c_str());
|
||||
if (prctl_return == 1) {
|
||||
EVLOG_warning << fmt::format("Could not set process name to '{}', it remains '{}'", module_identifier,
|
||||
this->original_process_name);
|
||||
}
|
||||
Logging::update_process_name(module_identifier);
|
||||
|
||||
auto everest = Everest(this->module_id, config, rs->validate_schema, this->mqtt, rs->telemetry_prefix,
|
||||
rs->telemetry_enabled, rs->forward_exceptions);
|
||||
|
||||
// module import
|
||||
EVLOG_debug << fmt::format("Initializing module {}...", module_identifier);
|
||||
|
||||
if (!everest.connect()) {
|
||||
if (this->mqtt_settings.broker_socket_path.empty()) {
|
||||
EVLOG_error << fmt::format("Cannot connect to MQTT broker at {}:{}", this->mqtt_settings.broker_host,
|
||||
this->mqtt_settings.broker_port);
|
||||
} else {
|
||||
EVLOG_error << fmt::format("Cannot connect to MQTT broker socket at {}",
|
||||
this->mqtt_settings.broker_socket_path);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
ModuleAdapter module_adapter;
|
||||
|
||||
module_adapter.call = [&everest](const Requirement& req, const std::string& cmd_name, const Parameters& args) {
|
||||
return everest.call_cmd(req, cmd_name, args);
|
||||
};
|
||||
|
||||
module_adapter.publish = [&everest](const std::string& req, const std::string& var_name, const Value& value) {
|
||||
return everest.publish_var(req, var_name, value);
|
||||
};
|
||||
|
||||
module_adapter.subscribe = [&everest](const Requirement& req, const std::string& var_name,
|
||||
const ValueCallback& callback) {
|
||||
return everest.subscribe_var(req, var_name, callback);
|
||||
};
|
||||
|
||||
module_adapter.get_error_manager_impl = [&everest](const std::string& impl_id) {
|
||||
return everest.get_error_manager_impl(impl_id);
|
||||
};
|
||||
|
||||
module_adapter.get_error_state_monitor_impl = [&everest](const std::string& impl_id) {
|
||||
return everest.get_error_state_monitor_impl(impl_id);
|
||||
};
|
||||
|
||||
module_adapter.get_error_factory = [&everest](const std::string& impl_id) {
|
||||
return everest.get_error_factory(impl_id);
|
||||
};
|
||||
|
||||
module_adapter.get_error_manager_req = [&everest](const Requirement& req) {
|
||||
return everest.get_error_manager_req(req);
|
||||
};
|
||||
|
||||
module_adapter.get_error_state_monitor_req = [&everest](const Requirement& req) {
|
||||
return everest.get_error_state_monitor_req(req);
|
||||
};
|
||||
|
||||
module_adapter.get_global_error_manager = [&everest]() { return everest.get_global_error_manager(); };
|
||||
|
||||
module_adapter.get_global_error_state_monitor = [&everest]() {
|
||||
return everest.get_global_error_state_monitor();
|
||||
};
|
||||
|
||||
module_adapter.get_config_service_client = [&everest]() { return everest.get_config_service_client(); };
|
||||
|
||||
// NOLINTNEXTLINE(modernize-avoid-bind): prefer bind here for readability
|
||||
module_adapter.ext_mqtt_publish =
|
||||
std::bind(&Everest::Everest::external_mqtt_publish, &everest, std::placeholders::_1, std::placeholders::_2,
|
||||
std::placeholders::_3);
|
||||
|
||||
module_adapter.ext_mqtt_subscribe = [&everest](const std::string& topic, const StringHandler& handler) {
|
||||
return everest.provide_external_mqtt_handler(topic, handler);
|
||||
};
|
||||
|
||||
module_adapter.ext_mqtt_subscribe_pair = [&everest](const std::string& topic,
|
||||
const StringPairHandler& handler) {
|
||||
return everest.provide_external_mqtt_handler(topic, handler);
|
||||
};
|
||||
|
||||
module_adapter.telemetry_publish = [&everest](const std::string& category, const std::string& subcategory,
|
||||
const std::string& type, const TelemetryMap& telemetry) {
|
||||
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
|
||||
const std::vector<cmd> cmds =
|
||||
this->callbacks.everest_register(config.get_requirement_initialization(this->module_id));
|
||||
|
||||
for (const auto& command : cmds) {
|
||||
everest.provide_cmd(command);
|
||||
}
|
||||
|
||||
const auto module_configs = config.get_module_configs(this->module_id);
|
||||
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();
|
||||
const 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);
|
||||
|
||||
everest.spawn_main_loop_thread();
|
||||
|
||||
// register the modules ready handler with the framework
|
||||
// this handler gets called when the global ready signal is received
|
||||
everest.register_on_ready_handler(this->callbacks.ready);
|
||||
|
||||
// the module should now be ready
|
||||
everest.signal_ready();
|
||||
|
||||
const auto end_time = std::chrono::steady_clock::now();
|
||||
EVLOG_info << "Module " << fmt::format(TERMINAL_STYLE_BLUE, "{}", module_id) << " initialized ["
|
||||
<< std::chrono::duration_cast<std::chrono::milliseconds>(end_time - start_time).count() << "ms]";
|
||||
|
||||
everest.wait_for_main_loop_end();
|
||||
|
||||
EVLOG_info << "Exiting...";
|
||||
} catch (boost::exception& e) {
|
||||
EVLOG_critical << fmt::format("Caught top level boost::exception:\n{}", boost::diagnostic_information(e, true));
|
||||
shutdown_mqtt();
|
||||
return EXIT_FAILURE;
|
||||
} catch (std::exception& e) {
|
||||
EVLOG_critical << fmt::format("Caught top level std::exception:\n{}", boost::diagnostic_information(e, true));
|
||||
shutdown_mqtt();
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays): pass-through of argc and argv from main()
|
||||
bool ModuleLoader::parse_command_line(int argc, char* argv[]) {
|
||||
po::options_description desc("EVerest");
|
||||
desc.add_options()("version", "Print version and exit");
|
||||
desc.add_options()("help,h", "produce help message");
|
||||
desc.add_options()("prefix", po::value<std::string>(), "Set main EVerest directory");
|
||||
desc.add_options()("module,m", po::value<std::string>(),
|
||||
"Which module should be executed (module id from config file)");
|
||||
desc.add_options()("dontvalidateschema", "Don't validate json schema on every message");
|
||||
desc.add_options()("config", po::value<std::string>(), "Just kept for compatibility, not used anymore.");
|
||||
desc.add_options()("log_config", po::value<std::string>(), "The path to a custom logging config");
|
||||
desc.add_options()("mqtt_broker_socket_path", po::value<std::string>(), "The MQTT broker socket path");
|
||||
desc.add_options()("mqtt_broker_host", po::value<std::string>(), "The MQTT broker hostname");
|
||||
desc.add_options()("mqtt_broker_port", po::value<int>(), "The MQTT broker port");
|
||||
desc.add_options()("mqtt_everest_prefix", po::value<std::string>(), "The MQTT everest prefix");
|
||||
desc.add_options()("mqtt_external_prefix", po::value<std::string>(), "The external MQTT prefix");
|
||||
|
||||
po::variables_map vm;
|
||||
po::store(po::parse_command_line(argc, argv, desc), vm);
|
||||
po::notify(vm);
|
||||
std::string argv0;
|
||||
if (argc > 0) {
|
||||
argv0 = *argv;
|
||||
if (not argv0.empty()) {
|
||||
this->application_name = fs::path(argv0).stem().string();
|
||||
}
|
||||
}
|
||||
|
||||
if (vm.count("help") != 0) {
|
||||
std::cout << desc << "\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (vm.count("version") != 0) {
|
||||
std::cout << argv0 << " (" << this->version_information.project_name << " "
|
||||
<< this->version_information.project_version << " " << this->version_information.git_version << ")"
|
||||
<< std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (vm.count("config") != 0) {
|
||||
std::cout
|
||||
<< "--config is not used anymore, modules request their config automatically via MQTT"
|
||||
<< "\n"
|
||||
<< "If you want to influence this config loading behavior you can specify the appropriate --mqtt_* flags"
|
||||
<< "\n";
|
||||
}
|
||||
|
||||
std::string mqtt_broker_socket_path;
|
||||
if (vm.count("mqtt_broker_socket_path") != 0) {
|
||||
mqtt_broker_socket_path = vm["mqtt_broker_socket_path"].as<std::string>();
|
||||
}
|
||||
|
||||
std::string mqtt_broker_host;
|
||||
if (vm.count("mqtt_broker_host") != 0) {
|
||||
mqtt_broker_host = vm["mqtt_broker_host"].as<std::string>();
|
||||
if (!mqtt_broker_socket_path.empty()) {
|
||||
// invalid configuration, can't have both UDS and IDS
|
||||
throw BootException(
|
||||
fmt::format("Setting both the Unix Domain Socket {} and Internet Domain Socket {} in config is invalid",
|
||||
mqtt_broker_socket_path, mqtt_broker_host));
|
||||
}
|
||||
} else {
|
||||
mqtt_broker_host = defaults::MQTT_BROKER_HOST;
|
||||
}
|
||||
|
||||
// overwrite mqtt broker host with environment variable
|
||||
// NOLINTNEXTLINE(concurrency-mt-unsafe): not problematic that this function is not threadsafe here
|
||||
const char* mqtt_server_address = std::getenv("MQTT_SERVER_ADDRESS");
|
||||
if (mqtt_server_address != nullptr) {
|
||||
mqtt_broker_host = mqtt_server_address;
|
||||
|
||||
if (!mqtt_broker_socket_path.empty()) {
|
||||
// invalid configuration, can't have both UDS and IDS
|
||||
throw BootException(
|
||||
fmt::format("Setting both the Unix Domain Socket {} and Internet Domain Socket {} in "
|
||||
"config and as environment variable respectivelly (as MQTT_SERVER_ADDRESS) is not allowed",
|
||||
mqtt_broker_socket_path, mqtt_broker_host));
|
||||
}
|
||||
}
|
||||
|
||||
std::uint16_t mqtt_broker_port = defaults::MQTT_BROKER_PORT;
|
||||
if (vm.count("mqtt_broker_port") != 0) {
|
||||
const auto mqtt_broker_port_int = vm["mqtt_broker_port"].as<int>();
|
||||
if (mqtt_broker_port_int > std::numeric_limits<std::uint16_t>::max()) {
|
||||
throw BootException(fmt::format("MQTT broker port {} is out of range.", mqtt_broker_port_int));
|
||||
}
|
||||
mqtt_broker_port = static_cast<std::uint16_t>(mqtt_broker_port_int);
|
||||
}
|
||||
|
||||
// overwrite mqtt broker port with environment variable
|
||||
// NOLINTNEXTLINE(concurrency-mt-unsafe): not problematic that this function is not threadsafe here
|
||||
const char* mqtt_server_port = std::getenv("MQTT_SERVER_PORT");
|
||||
|
||||
if (mqtt_server_port != nullptr) {
|
||||
try {
|
||||
mqtt_broker_port = std::stoul(mqtt_server_port);
|
||||
} catch (...) {
|
||||
EVLOG_warning << "Environment variable MQTT_SERVER_PORT set, but not set to an integer. Ignoring.";
|
||||
}
|
||||
}
|
||||
|
||||
std::string mqtt_everest_prefix;
|
||||
if (vm.count("mqtt_everest_prefix") != 0) {
|
||||
mqtt_everest_prefix = vm["mqtt_everest_prefix"].as<std::string>();
|
||||
} else {
|
||||
mqtt_everest_prefix = defaults::MQTT_EVEREST_PREFIX;
|
||||
}
|
||||
|
||||
// always make sure the everest mqtt prefix ends with '/'
|
||||
if (mqtt_everest_prefix.length() > 0 && mqtt_everest_prefix.back() != '/') {
|
||||
mqtt_everest_prefix = mqtt_everest_prefix += "/";
|
||||
}
|
||||
|
||||
std::string mqtt_external_prefix;
|
||||
if (vm.count("mqtt_external_prefix") != 0) {
|
||||
mqtt_external_prefix = vm["mqtt_external_prefix"].as<std::string>();
|
||||
} else {
|
||||
mqtt_external_prefix = "";
|
||||
}
|
||||
|
||||
if (not mqtt_broker_socket_path.empty()) {
|
||||
populate_mqtt_settings(this->mqtt_settings, mqtt_broker_socket_path, mqtt_everest_prefix, mqtt_external_prefix);
|
||||
} else {
|
||||
populate_mqtt_settings(this->mqtt_settings, mqtt_broker_host, mqtt_broker_port, mqtt_everest_prefix,
|
||||
mqtt_external_prefix);
|
||||
}
|
||||
|
||||
if (vm.count("log_config") != 0) {
|
||||
auto command_line_logging_config_file = vm["log_config"].as<std::string>();
|
||||
this->logging_config_file =
|
||||
assert_file(command_line_logging_config_file, "Command line provided logging config");
|
||||
|
||||
} else {
|
||||
auto default_logging_config_file =
|
||||
fs::path(defaults::SYSCONF_DIR) / defaults::NAMESPACE / defaults::LOGGING_CONFIG_NAME;
|
||||
|
||||
if (std::strcmp(defaults::PREFIX, "/usr") != 0) {
|
||||
default_logging_config_file = defaults::PREFIX / default_logging_config_file;
|
||||
} else {
|
||||
default_logging_config_file = fs::path("/") / default_logging_config_file;
|
||||
}
|
||||
this->logging_config_file = assert_file(default_logging_config_file, "Default logging config");
|
||||
}
|
||||
|
||||
this->original_process_name = argv0;
|
||||
|
||||
if (vm.count("module") != 0) {
|
||||
this->module_id = vm["module"].as<std::string>();
|
||||
} else {
|
||||
EVTHROW(EVEXCEPTION(EverestApiError, "--module parameter is required"));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace Everest
|
||||
179
tools/EVerest-main/lib/everest/framework/lib/serial.cpp
Normal file
179
tools/EVerest-main/lib/everest/framework/lib/serial.cpp
Normal file
@@ -0,0 +1,179 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
#include <utils/helpers.hpp>
|
||||
#include <utils/serial.hpp>
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
namespace Everest {
|
||||
Serial::Serial() {
|
||||
cobsDecodeReset();
|
||||
}
|
||||
|
||||
Serial::~Serial() {
|
||||
if (fd != -1) {
|
||||
close(fd);
|
||||
}
|
||||
}
|
||||
|
||||
bool Serial::openDevice(const char* device, int _baud) {
|
||||
|
||||
fd = open(device, O_RDWR | O_NOCTTY | O_SYNC);
|
||||
if (fd < 0) {
|
||||
printf("Serial: error %d opening %s: %s\n", errno, device, strerror(errno));
|
||||
return false;
|
||||
} // else printf ("Serial: opened %s as %i\n", device, fd);
|
||||
cobsDecodeReset();
|
||||
|
||||
switch (_baud) {
|
||||
case 9600:
|
||||
baud = B9600;
|
||||
break;
|
||||
case 19200:
|
||||
baud = B19200;
|
||||
break;
|
||||
case 38400:
|
||||
baud = B38400;
|
||||
break;
|
||||
case 57600:
|
||||
baud = B57600;
|
||||
break;
|
||||
case 115200:
|
||||
baud = B115200;
|
||||
break;
|
||||
case 230400:
|
||||
baud = B230400;
|
||||
break;
|
||||
default:
|
||||
baud = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
return setSerialAttributes();
|
||||
}
|
||||
|
||||
bool Serial::setSerialAttributes() {
|
||||
struct termios tty;
|
||||
if (tcgetattr(fd, &tty) != 0) {
|
||||
printf("Serial: error %d from tcgetattr\n", errno);
|
||||
return false;
|
||||
}
|
||||
|
||||
cfsetospeed(&tty, baud);
|
||||
cfsetispeed(&tty, baud);
|
||||
|
||||
tty.c_cflag = (tty.c_cflag & ~CSIZE) | CS8; // 8-bit chars
|
||||
// disable IGNBRK for mismatched speed tests; otherwise receive break
|
||||
// as \000 chars
|
||||
tty.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON | IXOFF | IXANY);
|
||||
tty.c_lflag = 0; // no signaling chars, no echo,
|
||||
// no canonical processing
|
||||
tty.c_oflag = 0; // no remapping, no delays
|
||||
tty.c_cc[VMIN] = 0; // read blocks
|
||||
tty.c_cc[VTIME] = 5; // 0.5 seconds read timeout
|
||||
|
||||
tty.c_cflag |= (CLOCAL | CREAD); // ignore modem controls,
|
||||
// enable reading
|
||||
tty.c_cflag &= ~(PARENB | PARODD); // shut off parity
|
||||
tty.c_cflag &= ~CSTOPB;
|
||||
tty.c_cflag &= ~CRTSCTS;
|
||||
|
||||
if (tcsetattr(fd, TCSANOW, &tty) != 0) {
|
||||
printf("Serial: error %d from tcsetattr\n", errno);
|
||||
return false;
|
||||
}
|
||||
// printf ("Success setting tcsetattr\n");
|
||||
return true;
|
||||
}
|
||||
|
||||
void Serial::cobsDecodeReset() {
|
||||
code = 0xff;
|
||||
block = 0;
|
||||
decode = msg;
|
||||
}
|
||||
|
||||
uint32_t Serial::crc32(uint8_t* buf, int len) {
|
||||
int i, j;
|
||||
uint32_t b, crc, msk;
|
||||
|
||||
i = 0;
|
||||
crc = 0xFFFFFFFF;
|
||||
while (i < len) {
|
||||
b = buf[i];
|
||||
crc = crc ^ b;
|
||||
for (j = 7; j >= 0; j--) {
|
||||
msk = -(crc & 1);
|
||||
crc = (crc >> 1) ^ (0xEDB88320 & msk);
|
||||
}
|
||||
i = i + 1;
|
||||
}
|
||||
// printf("%X",crc);
|
||||
return crc;
|
||||
}
|
||||
|
||||
void Serial::handlePacket(uint8_t* buf, int len) {
|
||||
// FIXME (aw): why is there no implementation here?
|
||||
}
|
||||
|
||||
void Serial::cobsDecode(uint8_t* buf, int len) {
|
||||
for (int i = 0; i < len; i++)
|
||||
cobsDecodeByte(buf[i]);
|
||||
}
|
||||
|
||||
void Serial::cobsDecodeByte(uint8_t byte) {
|
||||
// check max length
|
||||
if ((decode - msg == 2048 - 1) && byte != 0x00) {
|
||||
printf("cobsDecode: Buffer overflow\n");
|
||||
cobsDecodeReset();
|
||||
}
|
||||
|
||||
if (block) {
|
||||
// we're currently decoding and should not get a 0
|
||||
if (byte == 0x00) {
|
||||
// probably found some garbage -> reset
|
||||
printf("cobsDecode: Garbage detected\n");
|
||||
cobsDecodeReset();
|
||||
return;
|
||||
}
|
||||
*decode++ = byte;
|
||||
} else {
|
||||
if (code != 0xff) {
|
||||
*decode++ = 0;
|
||||
}
|
||||
block = code = byte;
|
||||
if (code == 0x00) {
|
||||
// we're finished, reset everything and commit
|
||||
if (decode == msg) {
|
||||
// we received nothing, just a 0x00
|
||||
printf("cobsDecode: Received nothing\n");
|
||||
} else {
|
||||
// set back decode with one, as it gets post-incremented
|
||||
handlePacket(msg, helpers::clamp_to<int>(decode - 1 - msg));
|
||||
}
|
||||
cobsDecodeReset();
|
||||
return; // need to return here, because of block--
|
||||
}
|
||||
}
|
||||
block--;
|
||||
}
|
||||
|
||||
std::string Serial::hexdump(const std::uint8_t* const msg, int msg_len) const {
|
||||
std::stringstream ss;
|
||||
for (int i = 0; i < msg_len; i++) {
|
||||
if (i > 0) {
|
||||
ss << " ";
|
||||
}
|
||||
ss << "0x" << std::hex << std::setw(2) << std::setfill('0') << static_cast<int>(msg[i]);
|
||||
}
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
std::string Serial::hexdump(const std::vector<std::uint8_t>& msg) const {
|
||||
return hexdump(msg.data(), msg.size());
|
||||
}
|
||||
|
||||
} // namespace Everest
|
||||
57
tools/EVerest-main/lib/everest/framework/lib/status_fifo.cpp
Normal file
57
tools/EVerest-main/lib/everest/framework/lib/status_fifo.cpp
Normal file
@@ -0,0 +1,57 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2023 Pionix GmbH and Contributors to EVerest
|
||||
#include <utils/status_fifo.hpp>
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
namespace Everest {
|
||||
|
||||
StatusFifo StatusFifo::create_from_path(const std::string& fifo_path) {
|
||||
if (fifo_path.length() == 0) {
|
||||
return StatusFifo();
|
||||
}
|
||||
|
||||
// try to open the file
|
||||
auto fd = open(fifo_path.c_str(), O_WRONLY | O_NONBLOCK);
|
||||
|
||||
// note: for fifo files, opening for write only in non-blocking mode will fail with ENXIO if the other end hasn't
|
||||
// opened the file yet
|
||||
if (fd == -1) {
|
||||
if (errno == ENXIO) {
|
||||
auto msg = std::string("Failed to open status fifo at ") + fifo_path + " (fifo not opened for read?)";
|
||||
throw std::runtime_error(msg);
|
||||
} else {
|
||||
auto msg =
|
||||
std::string("Failed to open status fifo at ") + fifo_path + " (fifo file not created with mkfifo?)";
|
||||
throw std::runtime_error(msg);
|
||||
}
|
||||
}
|
||||
|
||||
return StatusFifo(fd);
|
||||
}
|
||||
|
||||
void StatusFifo::update(const std::string& message) {
|
||||
if (disabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto ret = write(fd, message.c_str(), message.length());
|
||||
if (ret == -1) {
|
||||
// NOTE (aw): if we fail to write, we might assume, that the reader of the fifo is not interested in us anymore
|
||||
// so we won't send any further messages
|
||||
disabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
StatusFifo::~StatusFifo() {
|
||||
if (opened) {
|
||||
close(fd);
|
||||
}
|
||||
}
|
||||
} // namespace Everest
|
||||
29
tools/EVerest-main/lib/everest/framework/lib/thread.cpp
Normal file
29
tools/EVerest-main/lib/everest/framework/lib/thread.cpp
Normal file
@@ -0,0 +1,29 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2021 Pionix GmbH and Contributors to EVerest
|
||||
#include <utils/thread.hpp>
|
||||
|
||||
namespace Everest {
|
||||
|
||||
Thread::~Thread() {
|
||||
stop();
|
||||
}
|
||||
|
||||
void Thread::stop() {
|
||||
if (handle.joinable()) {
|
||||
exit_signal = true;
|
||||
handle.join();
|
||||
}
|
||||
exit_signal = false;
|
||||
}
|
||||
|
||||
bool Thread::shouldExit() {
|
||||
return exit_signal;
|
||||
}
|
||||
|
||||
Thread& Thread::operator=(std::thread&& t) {
|
||||
stop();
|
||||
handle = std::move(t);
|
||||
return *this;
|
||||
}
|
||||
|
||||
} // namespace Everest
|
||||
186
tools/EVerest-main/lib/everest/framework/lib/types.cpp
Normal file
186
tools/EVerest-main/lib/everest/framework/lib/types.cpp
Normal file
@@ -0,0 +1,186 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
#include <utils/types.hpp>
|
||||
|
||||
TypedHandler::TypedHandler(const std::string& name_, const std::string& id_, HandlerType type_,
|
||||
std::shared_ptr<Handler> handler_) :
|
||||
name(name_), id(id_), type(type_), handler(std::move(handler_)) {
|
||||
}
|
||||
|
||||
TypedHandler::TypedHandler(const std::string& name_, HandlerType type_, std::shared_ptr<Handler> handler_) :
|
||||
TypedHandler(name_, "", type_, std::move(handler_)) {
|
||||
}
|
||||
|
||||
TypedHandler::TypedHandler(HandlerType type_, std::shared_ptr<Handler> handler_) :
|
||||
TypedHandler("", "", type_, std::move(handler_)) {
|
||||
}
|
||||
|
||||
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 {
|
||||
return this->module_id + "->" + this->implementation_id;
|
||||
}
|
||||
|
||||
bool operator==(const ImplementationIdentifier& lhs, const ImplementationIdentifier& rhs) {
|
||||
return lhs.module_id == rhs.module_id && lhs.implementation_id == rhs.implementation_id;
|
||||
}
|
||||
|
||||
bool operator!=(const ImplementationIdentifier& lhs, const ImplementationIdentifier& rhs) {
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
std::string mqtt_message_type_to_string(MqttMessageType type) {
|
||||
switch (type) {
|
||||
case MqttMessageType::Var:
|
||||
return "Var";
|
||||
case MqttMessageType::Cmd:
|
||||
return "Cmd";
|
||||
case MqttMessageType::CmdResult:
|
||||
return "CmdResult";
|
||||
case MqttMessageType::ExternalMQTT:
|
||||
return "ExternalMQTT";
|
||||
case MqttMessageType::RaiseError:
|
||||
return "RaiseError";
|
||||
case MqttMessageType::ClearError:
|
||||
return "ClearError";
|
||||
case MqttMessageType::GetConfig:
|
||||
return "GetConfig";
|
||||
case MqttMessageType::GetConfigResponse:
|
||||
return "GetConfigResponse";
|
||||
case MqttMessageType::Heartbeat:
|
||||
return "Heartbeat";
|
||||
case MqttMessageType::ModuleReady:
|
||||
return "ModuleReady";
|
||||
case MqttMessageType::GlobalReady:
|
||||
return "GlobalReady";
|
||||
default:
|
||||
throw std::runtime_error("Unknown MQTT message type");
|
||||
}
|
||||
}
|
||||
|
||||
MqttMessageType string_to_mqtt_message_type(std::string_view str) {
|
||||
if (str == "Var") {
|
||||
return MqttMessageType::Var;
|
||||
} else if (str == "Cmd") {
|
||||
return MqttMessageType::Cmd;
|
||||
} else if (str == "CmdResult") {
|
||||
return MqttMessageType::CmdResult;
|
||||
} else if (str == "ExternalMQTT") {
|
||||
return MqttMessageType::ExternalMQTT;
|
||||
} else if (str == "RaiseError") {
|
||||
return MqttMessageType::RaiseError;
|
||||
} else if (str == "ClearError") {
|
||||
return MqttMessageType::ClearError;
|
||||
} else if (str == "GetConfig") {
|
||||
return MqttMessageType::GetConfig;
|
||||
} else if (str == "GetConfigResponse") {
|
||||
return MqttMessageType::GetConfigResponse;
|
||||
} else if (str == "Heartbeat") {
|
||||
return MqttMessageType::Heartbeat;
|
||||
} else if (str == "ModuleReady") {
|
||||
return MqttMessageType::ModuleReady;
|
||||
} else if (str == "GlobalReady") {
|
||||
return MqttMessageType::GlobalReady;
|
||||
}
|
||||
|
||||
throw std::runtime_error(fmt::format("Unknown MQTT message type string: {}", str));
|
||||
}
|
||||
|
||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||
void adl_serializer<Mapping>::to_json(json& j, const Mapping& m) {
|
||||
j = {{"evse", m.evse}};
|
||||
if (m.connector.has_value()) {
|
||||
j["connector"] = m.connector.value();
|
||||
}
|
||||
}
|
||||
|
||||
Mapping adl_serializer<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;
|
||||
}
|
||||
|
||||
void adl_serializer<TelemetryConfig>::to_json(json& j, const TelemetryConfig& t) {
|
||||
j = {{"id", t.id}};
|
||||
}
|
||||
|
||||
TelemetryConfig adl_serializer<TelemetryConfig>::from_json(const json& j) {
|
||||
auto t = TelemetryConfig(j.at("id").get<int>());
|
||||
return t;
|
||||
}
|
||||
|
||||
void adl_serializer<ModuleTierMappings>::to_json(json& j, const ModuleTierMappings& m) {
|
||||
if (m.module.has_value()) {
|
||||
j = {{"module", m.module.value()}};
|
||||
}
|
||||
if (m.implementations.size() > 0) {
|
||||
j["implementations"] = json::object();
|
||||
for (auto& impl_mapping : m.implementations) {
|
||||
if (impl_mapping.second.has_value()) {
|
||||
j["implementations"][impl_mapping.first] = impl_mapping.second.value();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ModuleTierMappings adl_serializer<ModuleTierMappings>::from_json(const json& j) {
|
||||
ModuleTierMappings m;
|
||||
if (!j.is_null()) {
|
||||
if (j.contains("module")) {
|
||||
m.module = j.at("module");
|
||||
}
|
||||
if (j.contains("implementations")) {
|
||||
for (auto& impl : j.at("implementations").items()) {
|
||||
m.implementations[impl.key()] = impl.value();
|
||||
}
|
||||
}
|
||||
}
|
||||
return m;
|
||||
}
|
||||
|
||||
void adl_serializer<Requirement>::to_json(json& j, const Requirement& r) {
|
||||
j = {{"id", r.id}};
|
||||
if (r.index != 0) {
|
||||
j["index"] = r.index;
|
||||
}
|
||||
}
|
||||
|
||||
Requirement adl_serializer<Requirement>::from_json(const json& j) {
|
||||
Requirement r;
|
||||
r.id = j.at("id").get<std::string>();
|
||||
if (j.contains("index")) {
|
||||
r.index = j.at("index").get<size_t>();
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
void adl_serializer<Fulfillment>::to_json(json& j, const Fulfillment& f) {
|
||||
j = {{"module_id", f.module_id}, {"implementation_id", f.implementation_id}, {"requirement", f.requirement}};
|
||||
}
|
||||
|
||||
Fulfillment adl_serializer<Fulfillment>::from_json(const json& j) {
|
||||
Fulfillment f;
|
||||
f.module_id = j.at("module_id").get<std::string>();
|
||||
f.implementation_id = j.at("implementation_id").get<std::string>();
|
||||
f.requirement = j.at("requirement").get<Requirement>();
|
||||
return f;
|
||||
}
|
||||
|
||||
void adl_serializer<MqttMessagePayload>::to_json(json& j, const MqttMessagePayload& m) {
|
||||
j = {{"msg_type", mqtt_message_type_to_string(m.type)}, {"data", m.data}};
|
||||
}
|
||||
|
||||
MqttMessagePayload adl_serializer<MqttMessagePayload>::from_json(const json& j) {
|
||||
return MqttMessagePayload{string_to_mqtt_message_type(j.at("msg_type").get<std::string>()),
|
||||
j.at("data").get<json>()};
|
||||
}
|
||||
|
||||
NLOHMANN_JSON_NAMESPACE_END
|
||||
Reference in New Issue
Block a user