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:
Eric F
2026-06-08 00:38:27 -04:00
parent 468cfeaa50
commit d398a6ced2
7326 changed files with 1177561 additions and 7 deletions

View File

@@ -0,0 +1,407 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#ifndef UTILS_CONFIG_HPP
#define UTILS_CONFIG_HPP
#include <filesystem>
#include <list>
#include <optional>
#include <set>
#include <string>
#include <string_view>
#include <tuple>
#include <unordered_map>
#include <nlohmann/json-schema.hpp>
#include <utils/config/mqtt_settings.hpp>
#include <utils/config/settings.hpp>
#include <utils/config/storage_userconfig.hpp>
#include <utils/config_cache.hpp>
#include <utils/error.hpp>
#include <utils/error/error_type_map.hpp>
#include <utils/module_config.hpp>
#include <utils/types.hpp>
namespace Everest {
namespace fs = std::filesystem;
using everest::config::ModuleConfig;
using everest::config::ModuleConfigurations;
struct ManagerSettings;
struct RuntimeSettings;
///
/// \brief A structure that contains all available schemas
///
struct Schemas {
nlohmann::json config; ///< The config schema
nlohmann::json manifest; ///< The manifest scheme
nlohmann::json interface; ///< The interface schema
nlohmann::json type; ///< The type schema
nlohmann::json error_declaration_list; ///< The error-declaration-list schema
};
struct Validators {
nlohmann::json_schema::json_validator config;
nlohmann::json_schema::json_validator manifest;
nlohmann::json_schema::json_validator type;
nlohmann::json_schema::json_validator interface;
nlohmann::json_schema::json_validator error_declaration_list;
};
struct SchemaValidation {
Schemas schemas;
Validators validators;
};
struct ImplementationInfo {
std::string module_id;
std::string module_name;
std::string impl_id;
std::string impl_intf;
};
///
/// \brief A simple json schema loader that uses the builtin draft7 schema of
/// the json schema validator when it encounters it, throws an exception
/// otherwise
void loader(const nlohmann::json_uri& uri, nlohmann::json& schema);
///
/// \brief An extension to the default format checker of the json schema
/// validator supporting uris
void format_checker(const std::string& format, const std::string& value);
///
/// \brief loads and validates a json schema at the provided \p path
///
/// \returns the loaded json schema as a json object as well as a related schema validator
std::tuple<nlohmann::json, nlohmann::json_schema::json_validator> load_schema(const fs::path& path);
///
/// \brief loads the config.json and manifest.json in the schemes subfolder of
/// the provided \p schemas_dir
///
/// \returns the loaded configs and related validators
SchemaValidation load_schemas(const fs::path& schemas_dir);
/// \brief Serializes the given \p module_configuration and related data to JSON.
///
/// Includes the module's config, mappings (own and connected), and telemetry if present.
///
/// \param module_id ID of the module to serialize.
/// \param module_configurations Map of all module configurations.
/// \return JSON object with the serialized module configuration.
json get_serialized_module_config(std::string_view module_id, const ModuleConfigurations& module_configurations);
///
/// \brief Base class for configs
///
class ConfigBase {
protected:
ModuleConfigurations module_configs;
nlohmann::json settings;
nlohmann::json manifests;
nlohmann::json interfaces;
nlohmann::json interface_definitions;
nlohmann::json types;
Schemas schemas;
// experimental caches
std::map<std::string, std::string, std::less<>> module_names;
error::ErrorTypeMap error_map;
const MQTTSettings mqtt_settings;
public:
///
/// \brief Create a ConfigBase with the provided \p mqtt_settings
explicit ConfigBase(const MQTTSettings& mqtt_settings) : mqtt_settings(mqtt_settings){};
///
/// \brief turns then given \p module_id into a printable identifier
///
/// \returns a string with the printable identifier
std::string printable_identifier(std::string_view module_id) const;
///
/// \brief turns then given \p module_id and \p impl_id into a printable identifier
///
/// \returns a string with the printable identifier
std::string printable_identifier(std::string_view module_id, std::string_view impl_id) const;
///
/// \returns the module name matching the provided \p module_id
std::string get_module_name(std::string_view module_id) const;
///
/// \brief turns the given \p module_id and \p impl_id into a mqtt prefix
///
std::string mqtt_prefix(std::string_view module_id, std::string_view impl_id);
///
/// \brief turns the given \p module_id into a mqtt prefix
///
std::string mqtt_module_prefix(std::string_view module_id) const;
///
/// \returns a json object that contains the main config
const ModuleConfigurations& get_module_configurations() const;
///
/// \brief checks if the config contains the given \p module_id
bool contains(std::string_view module_id) const;
///
/// \returns a json object that contains the manifests
const nlohmann::json& get_manifests() const;
///
/// \returns a json object that contains the interface definitions
const nlohmann::json& get_interface_definitions() const;
///
/// \returns a json object that contains the available interfaces
const nlohmann::json& get_interfaces() const;
///
/// \returns a json object that contains the settings
const nlohmann::json& get_settings() const;
///
/// \returns an object that contains the schemas
const Schemas& get_schemas() const;
///
/// \returns an object that contains the error types
const error::ErrorTypes& get_error_types() const;
///
/// \returns a json object that contains the types
const nlohmann::json& get_types() const;
///
/// \return the cached mapping of module ids to module names
const std::map<std::string, std::string, std::less<>>& get_module_names() const;
///
/// \brief checks if the given \p module_id provides the requirement given in \p requirement_id
///
/// \returns a json object that contains the requirement
std::vector<Fulfillment> resolve_requirement(std::string_view module_id, std::string_view requirement_id) const;
///
/// \brief resolves all Requirements of the given \p module_id to their Fulfillments
///
/// \returns a map indexed by Requirements
std::map<Requirement, Fulfillment> resolve_requirements(std::string_view module_id) const;
///
/// \returns a list of Requirements for \p module_id
std::list<Requirement> get_requirements(std::string_view module_id) const;
///
/// \brief A Fulfillment is a combination of a Requirement and the module and implementation ids where this is
/// implemented
/// \returns a map of Fulfillments for \p module_id
std::map<std::string, std::vector<Fulfillment>> get_fulfillments(std::string_view module_id) const;
};
///
/// \brief Config intended to be created by the manager for validation and serialization. Contains config and
/// manifest parsing
///
class ManagerConfig : public ConfigBase {
private:
const ManagerSettings& ms;
Validators validators;
std::unique_ptr<nlohmann::json_schema::json_validator> draft7_validator;
std::unique_ptr<everest::config::UserConfigStorage> user_config_storage;
std::map<everest::config::ConfigurationParameterIdentifier, everest::config::GetConfigurationParameterResponse>
database_get_config_parameter_response_cache;
nlohmann::json apply_user_config_and_defaults();
///
/// \brief loads and validates the manifest of the \p module_config
void load_and_validate_manifest(ModuleConfig& module_config);
///
/// \brief loads and validates the given file \p file_path with the schema \p schema
///
/// \returns the loaded json and how long the validation took in ms
std::tuple<nlohmann::json, int64_t> load_and_validate_with_schema(const fs::path& file_path,
const nlohmann::json& schema);
///
/// \brief resolves inheritance tree of json interface \p intf_name, throws an exception if variables or
/// commands would be overwritten
///
/// \returns the resulting interface definition
nlohmann::json resolve_interface(std::string_view intf_name);
///
/// \brief loads the contents of the interface file referenced by the give \p intf_name from disk and validates
/// its contents
///
/// \returns a json object containing the interface definition
nlohmann::json load_interface_file(std::string_view intf_name);
///
/// \brief loads the contents of an error or an error list referenced by the given \p reference.
///
/// \returns a list of json objects containing the error definitions
std::list<nlohmann::json> resolve_error_ref(std::string_view reference);
///
/// \brief replaces all error references in the given \p interface_json with the actual error definitions
///
/// \returns the interface_json with replaced error references
nlohmann::json replace_error_refs(nlohmann::json& interface_json);
///
/// \brief resolves all requirements (connections) of the modules in the main config
void resolve_all_requirements();
///
/// \brief parses the provided \p config resolving types, errors, manifests, requirements and 3 tier module
/// mappings
void parse(ModuleConfigurations& module_configs);
///
/// \brief Parses the 3 tier model mappings in the config
/// A "mapping" can be specified in the following way:
/// You can set a EVSE id called "evse" and Connector id called "connector" for the whole module.
/// Alternatively you can set individual mappings for implementations.
/// mapping:
/// module:
/// evse: 1
/// connector: 1
/// implementations:
/// implementation_id:
/// evse: 1
/// connector: 1
/// If no mappings are found it will be assumed that the module is mapped to the charging station.
/// If only a module mapping is defined alle implementations are mapped to this module mapping.
/// Implementations can have overwritten mappings.
void parse_3_tier_model_mapping();
public:
///
/// \brief Create a ManagerConfig from the provided ManagerSettings \p ms
explicit ManagerConfig(const ManagerSettings& ms);
/// \brief Sets the config \p value associated with the \p identifier
/// \returns if the setting of the value was successful or not
everest::config::SetConfigStatus
set_config_value(const everest::config::ConfigurationParameterIdentifier& identifier,
const everest::config::ConfigEntry& value);
/// \brief Gets the configuration parameter associated with the \p identifier
/// \returns a result containing the configuration item or an error
everest::config::GetConfigurationParameterResponse
get_config_value(const everest::config::ConfigurationParameterIdentifier& identifier);
};
///
/// \brief Contains intended to be used by modules using a pre-parsed and validated config json serialized from
/// ManagerConfig
///
class Config : public ConfigBase {
private:
ModuleConfig module_config;
std::map<std::string, ModuleTierMappings, std::less<>> tier_mappings;
std::optional<TelemetryConfig> telemetry_config;
std::map<std::string, ConfigCache, std::less<>> module_config_cache;
void populate_module_config_cache();
void populate_error_map();
public:
///
/// \brief creates a new Config object form the given \p mqtt_settings and \p config
explicit Config(const MQTTSettings& mqtt_settings, const nlohmann::json& config);
///
/// \returns object that contains the module config options
ModuleConfig get_module_config() const;
///
/// \returns the ErrorTypeMap
error::ErrorTypeMap get_error_map() const;
///
/// \returns true if the module \p module_name provides the implementation \p impl_id
bool module_provides(std::string_view module_name, std::string_view impl_id);
///
/// \returns the commands that the modules \p module_name implements from the given implementation \p impl_id
const nlohmann::json& get_module_cmds(std::string_view module_name, std::string_view impl_id);
///
/// \brief A RequirementInitialization contains everything needed to initialize a requirement in user code. This
/// includes the Requirement, its Fulfillment and an optional Mapping
/// \returns a RequirementInitialization
RequirementInitialization get_requirement_initialization(std::string_view module_id) const;
///
/// \returns a map of module config options
ModuleConfigs get_module_configs(std::string_view module_id) const;
//
/// \returns the 3 tier model mappings for the given \p module_id
std::optional<ModuleTierMappings> get_module_3_tier_model_mappings(std::string_view module_id) const;
//
/// \returns the 3 tier model mapping for the given \p module_id and \p impl_id
std::optional<Mapping> get_3_tier_model_mapping(std::string_view module_id, std::string_view impl_id) const;
///
/// \brief assemble basic information about the module (id, name,
/// authors, license)
///
/// \returns a ModuleInfo object
ModuleInfo get_module_info(std::string_view module_id) const;
///
/// \returns a TelemetryConfig if this has been configured
std::optional<TelemetryConfig> get_telemetry_config();
///
/// \returns a json object that contains the interface definition
nlohmann::json get_interface_definition(std::string_view interface_name) const;
///
/// \brief A json schema loader that can handle type refs and otherwise uses the builtin draft7 schema of
/// the json schema validator when it encounters it. Throws an exception
/// otherwise
void ref_loader(const nlohmann::json_uri& uri, nlohmann::json& schema);
///
/// \brief loads all module manifests relative to the \p main_dir
///
/// \returns all module manifests as a json object
static nlohmann::json load_all_manifests(std::string_view modules_dir, std::string_view schemas_dir);
///
/// \brief Extracts the keys of the provided json \p object
///
/// \returns a set of object keys
static everest::config::Keys keys(const nlohmann::json& object);
};
} // namespace Everest
NLOHMANN_JSON_NAMESPACE_BEGIN
template <> struct adl_serializer<Everest::Schemas> {
static void to_json(nlohmann::json& j, const Everest::Schemas& s);
static void from_json(const nlohmann::json& j, Everest::Schemas& s);
};
NLOHMANN_JSON_NAMESPACE_END
#endif // UTILS_CONFIG_HPP

View File

@@ -0,0 +1,48 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#ifndef UTILS_CONFIG_MQTT_SETTINGS_HPP
#define UTILS_CONFIG_MQTT_SETTINGS_HPP
#include <cstdint>
#include <string>
namespace Everest {
/// \brief minimal MQTT connection settings needed for an initial connection of a module to the manager
struct MQTTSettings {
std::string broker_socket_path; ///< A path to a socket the MQTT broker uses in socket mode. If this is set
///< broker_host and broker_port are ignored
std::string broker_host; ///< The hostname of the MQTT broker
std::uint16_t broker_port = 0; ///< The port the MQTT broker listens on
std::string everest_prefix; ///< MQTT topic prefix for the "everest" topic
std::string external_prefix; ///< MQTT topic prefix for external topics
/// \brief Indicates if a Unix Domain Socket is used for connection to the MQTT broker
/// \returns true is a UDS is used, false if a connection via host and port is used
bool uses_socket() const;
};
/// \brief Creates MQTTSettings with a Unix Domain Socket with the provided \p mqtt_broker_socket_path
/// using the \p mqtt_everest_prefix and \p mqtt_external_prefix
MQTTSettings create_mqtt_settings(const std::string& mqtt_broker_socket_path, const std::string& mqtt_everest_prefix,
const std::string& mqtt_external_prefix);
/// \brief Creates MQTTSettings for IP based connections with the provided \p mqtt_broker_host
/// and \p mqtt_broker_port using the \p mqtt_everest_prefix and \p mqtt_external_prefix
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);
/// \brief Populates the given MQTTSettings \p mqtt_settings with a Unix Domain Socket with the provided \p
/// mqtt_broker_socket_path using the \p mqtt_everest_prefix and \p mqtt_external_prefix
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);
/// \brief Populates the given MQTTSettings \p mqtt_settings for IP based connections with the provided \p
/// mqtt_broker_host and \p mqtt_broker_port using the \p mqtt_everest_prefix and \p 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);
} // namespace Everest
#endif // UTILS_CONFIG_MQTT_SETTINGS_HPP

View File

@@ -0,0 +1,107 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
#pragma once
#include <filesystem>
#include <string>
#include <nlohmann/json.hpp>
#include <utils/config/mqtt_settings.hpp>
#include <utils/config/storage_sqlite.hpp>
#include <utils/config/types.hpp>
namespace Everest {
namespace fs = std::filesystem;
enum class ConfigBootMode {
YamlFile = 1, // configuration is loaded from a YAML file
Database = 2, // configuration is loaded from a database
DatabaseInit = 3 // configuration is preferably loaded from a database, but if no valid config is found, it falls
// back to a YAML file and initializes the database
};
/// \brief EVerest framework runtime settings needed to successfully run modules
struct RuntimeSettings {
fs::path prefix; ///< Prefix for EVerest installation
fs::path etc_dir; ///< Directory that contains configs, certificates
fs::path data_dir; ///< Directory for general data, definitions for EVerest interfaces, types, errors an schemas
fs::path modules_dir; ///< Directory that contains EVerest modules
fs::path logging_config_file; ///< Path to the logging configuration file
std::string telemetry_prefix; ///< MQTT prefix for telemetry
bool telemetry_enabled = false; ///< If telemetry is enabled
bool validate_schema = false; ///< If schema validation for all var publishes and cmd calls is enabled
bool forward_exceptions = false; ///< If exceptions in cmd handlers should be caught and forwarded to the caller
};
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);
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);
struct DatabaseTag {};
/// \brief Settings needed by the manager to load and validate a config
struct ManagerSettings {
fs::path configs_dir; ///< Directory that contains EVerest configs
fs::path db_dir; ///< Directory that contains the database
fs::path schemas_dir; ///< Directory that contains schemas for config, manifest, interfaces, etc.
fs::path interfaces_dir; ///< Directory that contains interface definitions
fs::path types_dir; ///< Directory that contains type definitions
fs::path errors_dir; ///< Directory that contains error definitions
fs::path config_file; ///< Path to the loaded config file
fs::path www_dir; ///< Directory that contains the everest-admin-panel
int controller_port = 0; ///< Websocket port of the controller
int controller_rpc_timeout_ms = 0; ///< RPC timeout for controller commands
std::string run_as_user; ///< Username under which EVerest should run
std::string version_information; ///< Version information string reported on startup of the manager
nlohmann::json config; ///< Parsed json of the config_file
MQTTSettings mqtt_settings; ///< MQTT connection settings
RuntimeSettings runtime_settings; ///< Runtime settings needed to successfully run modules
ConfigBootMode boot_mode =
ConfigBootMode::YamlFile; ///< Source of the config, can be YamlFile, Database or DatabaseInit
std::unique_ptr<everest::config::SqliteStorage> storage; ///< Sqlite Storage for settings and module configs
ManagerSettings() = default;
/// \brief Constructor that initializes the ManagerSettings with the given prefix and config file. Boot source is
/// set to YamlFile.
ManagerSettings(const std::string& prefix, const std::string& config);
/// \brief Constructor that initializes the ManagerSettings with the given database path. Boot source is set to
/// Database.
ManagerSettings(const std::string& prefix, const std::string& db, DatabaseTag);
/// \brief Constructor that initializes the ManagerSettings with the given prefix, config file and database path.
/// Boot Source is set to DatabaseInit.
ManagerSettings(const std::string& prefix, const std::string& config, const std::string& db);
/// \brief Initializes the ManagerSettings with the given settings and prefix.
void init_settings(const everest::config::Settings& settings);
/// \brief Initializes the ManagerSettings based on the user provided \p config file or fallback options
void init_config_file(const std::string& config);
/// \brief Initializes the ManagerSettings prefix and data_dir base on user provided \p prefix or the default
/// prefix.
void init_prefix_and_data_dir(const std::string& prefix);
};
} // namespace Everest
NLOHMANN_JSON_NAMESPACE_BEGIN
template <> struct adl_serializer<Everest::RuntimeSettings> {
static void to_json(nlohmann::json& j, const Everest::RuntimeSettings& r);
static void from_json(const nlohmann::json& j, Everest::RuntimeSettings& r);
};
NLOHMANN_JSON_NAMESPACE_END

View File

@@ -0,0 +1,76 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
#pragma once
#include <map>
#include <string>
#include <nlohmann/json.hpp>
#include <utils/config/storage_types.hpp>
namespace Everest {
struct ManagerSettings;
}
namespace everest::config {
/// \brief EVerest Storage Interface providing read and write access to configurations
class StorageInterface {
public:
virtual ~StorageInterface() = default;
/// \brief Writes given EVerest \p module_configs to persistent storage
/// \param config EVerest config
/// \return
virtual GenericResponseStatus write_module_configs(const ModuleConfigurations& module_configs) = 0;
/// \brief Writes EVerest config \p settings to persistent storage
/// \param settings EVerest settings configuration
/// \return
virtual GenericResponseStatus write_settings(const Everest::ManagerSettings& manager_settings) = 0;
/// \brief Wipes all configuration entries from persistent storage
virtual GenericResponseStatus wipe() = 0;
/// \brief Gets EVerest config from persistent storage
/// \return Response with status of operation and config. config is only set if status is OK. Config contains all
/// module configurations and manager settings
virtual GetModuleConfigsResponse get_module_configs() = 0;
/// \brief Gets EVerest manager settings from persistent storage
/// \return
virtual GetSettingsResponse get_settings() = 0;
/// \brief Gets EVerest config from persistent storage for a single module
/// \param module_id
/// \return Response with status of operation and module config. config is only set if status is OK
virtual GetModuleConfigurationResponse get_module_config(const std::string& module_id) = 0;
/// \brief Gets single configuration parameter from persistent storage
/// \param identifier Identifies configuration parameter
/// \return Response with status of operation and configuration parameter. configuration parameter is only set if
/// status is OK.
virtual GetConfigurationParameterResponse
get_configuration_parameter(const ConfigurationParameterIdentifier& identifier) = 0;
/// \brief Writes single configuration parameter to persistent storage
/// \param identifier Identifies configuration parameter
/// \param value
/// \return Response with status of operation
virtual GetSetResponseStatus update_configuration_parameter(const ConfigurationParameterIdentifier& identifier,
const std::string& value) = 0;
/// \brief Writes single configuration parameter including characteristics to persistent storage
/// \param identifier Identifies configuration parameter
/// \param characteristics of the configuration parameter
/// \param value
/// \return Response with status of operation
virtual GetSetResponseStatus
write_configuration_parameter(const ConfigurationParameterIdentifier& identifier,
const ConfigurationParameterCharacteristics characteristics,
const std::string& value) = 0;
};
} // namespace everest::config

View File

@@ -0,0 +1,64 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
#pragma once
#include <everest/database/sqlite/connection.hpp>
#include <utils/config/storage.hpp>
namespace everest::config {
/// \brief Implements StorageInterface with SQLite
class SqliteStorage : public StorageInterface {
public:
/// \brief Constructor
/// \param db_path Path to SQLite database file
/// \param migration_files_path Path to SQL migration files
/// \throws MigrationException if migration fails
/// \throws std::runtime_error if database cannot be opened
SqliteStorage(const fs::path& db_path, const std::filesystem::path& migration_files_path);
GenericResponseStatus write_module_configs(const ModuleConfigurations& module_configs) override;
GenericResponseStatus write_settings(const Everest::ManagerSettings& manager_settings) override;
GenericResponseStatus wipe() override;
GetModuleConfigsResponse get_module_configs() override;
GetSettingsResponse get_settings() override;
GetModuleConfigurationResponse get_module_config(const std::string& module_id) override;
GetConfigurationParameterResponse
get_configuration_parameter(const ConfigurationParameterIdentifier& identifier) override;
GetSetResponseStatus update_configuration_parameter(const ConfigurationParameterIdentifier& identifier,
const std::string& value) override;
GetSetResponseStatus write_configuration_parameter(const ConfigurationParameterIdentifier& identifier,
const ConfigurationParameterCharacteristics characteristics,
const std::string& value) override;
/// \brief Checks if the database contains a valid configuration
bool contains_valid_config();
/// \brief Marks the current configuration as valid
/// \param is_valid True if the configuration is valid, false otherwise
/// \param config_dump JSON dump of the config file that was used to create the configuration
/// \param yaml_file_path Path to the config file that was used to create the configuration
void mark_valid(const bool is_valid, const std::string& config_dump,
const std::optional<fs::path>& config_file_path);
private:
std::unique_ptr<everest::db::sqlite::ConnectionInterface> db;
GenericResponseStatus write_module_data(const ModuleData& module_info);
GenericResponseStatus write_module_fulfillment(const std::string& module_id, const Fulfillment& fulfillment);
GenericResponseStatus 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);
GenericResponseStatus write_access(const std::string& module_id, const Access& access);
GenericResponseStatus write_config_access(const std::string& module_id, const ConfigAccess& config_access);
GenericResponseStatus write_module_config_access(const std::string& module_id, const std::string& other_module_id,
const ModuleConfigAccess& module_config_access);
GenericResponseStatus write_setting(const std::string& setting_name, const std::string& value);
GetModuleFulfillmentsResponse get_module_fulfillments(const std::string& module_id);
GetModuleDataResponse get_module_data(const std::string& module_id);
GetModuleTierMappingsResponse get_module_tier_mappings(const std::string& module_id);
GetModuleConfigAccessResponse get_module_config_access(const std::string& module_id);
GetConfigAccessResponse get_config_access(const std::string& module_id);
};
} // namespace everest::config

View File

@@ -0,0 +1,87 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
#pragma once
#include <optional>
#include <string>
#include <vector>
#include <nlohmann/json_fwd.hpp>
#include <utils/config/types.hpp>
namespace everest::config {
enum class GenericResponseStatus {
OK,
Failed
};
enum class GetSetResponseStatus {
OK,
Failed,
NotFound
};
struct ModuleData {
std::string module_id;
std::string module_name;
bool standalone = false;
std::optional<std::vector<std::string>> capabilities;
};
struct GetConfigurationParameterResponse {
GetSetResponseStatus status = GetSetResponseStatus::Failed;
std::optional<ConfigurationParameter> configuration_parameter;
};
struct GetModuleConfigsResponse {
GenericResponseStatus status = GenericResponseStatus::Failed;
ModuleConfigurations module_configs;
};
struct GetSettingsResponse {
GenericResponseStatus status = GenericResponseStatus::Failed;
std::optional<Settings> settings;
};
struct GetModuleFulfillmentsResponse {
GenericResponseStatus status = GenericResponseStatus::Failed;
std::vector<Fulfillment> module_fulfillments;
};
struct GetModuleTierMappingsResponse {
GenericResponseStatus status = GenericResponseStatus::Failed;
ModuleTierMappings module_tier_mappings;
};
struct GetConfigAccessResponse {
GenericResponseStatus status = GenericResponseStatus::Failed;
std::optional<ConfigAccess> config_access;
};
struct GetModuleConfigAccessResponse {
GenericResponseStatus status = GenericResponseStatus::Failed;
std::map<std::string, everest::config::ModuleConfigAccess> module_config_access;
};
struct GetModuleConfigurationResponse {
GenericResponseStatus status = GenericResponseStatus::Failed;
std::optional<ModuleConfig> config;
};
struct GetModuleDataResponse {
GenericResponseStatus status = GenericResponseStatus::Failed;
std::optional<ModuleData> module_data;
};
struct ConfigurationParameterIdentifier {
std::string module_id;
std::string configuration_parameter_name;
std::optional<std::string> module_implementation_id;
bool operator<(const ConfigurationParameterIdentifier& rhs) const;
};
} // namespace everest::config

View File

@@ -0,0 +1,41 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#pragma once
#include <nlohmann/json.hpp>
#include <utils/config/storage.hpp>
namespace everest::config {
/// \brief Implements StorageInterface with YAML user-config
class UserConfigStorage : public StorageInterface {
public:
/// \brief Constructor
/// \param user_config_path Path to user-config file
UserConfigStorage(const fs::path& user_config_path);
GenericResponseStatus write_module_configs(const ModuleConfigurations& module_configs) override;
GenericResponseStatus write_settings(const Everest::ManagerSettings& manager_settings) override;
GenericResponseStatus wipe() override;
GetModuleConfigsResponse get_module_configs() override;
GetSettingsResponse get_settings() override;
GetModuleConfigurationResponse get_module_config(const std::string& module_id) override;
GetConfigurationParameterResponse
get_configuration_parameter(const ConfigurationParameterIdentifier& identifier) override;
GetSetResponseStatus update_configuration_parameter(const ConfigurationParameterIdentifier& identifier,
const std::string& value) override;
GetSetResponseStatus write_configuration_parameter(const ConfigurationParameterIdentifier& identifier,
const ConfigurationParameterCharacteristics characteristics,
const std::string& value) override;
const nlohmann::json& get_user_config() const;
private:
fs::path user_config_path;
nlohmann::json user_config;
};
} // namespace everest::config

View File

@@ -0,0 +1,281 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
#pragma once
#include <cstdint>
#include <filesystem>
#include <map>
#include <nlohmann/json.hpp>
#include <optional>
#include <set>
#include <string>
#include <variant>
#include <vector>
class ConfigParseException : public std::exception {
public:
enum ParseErrorType {
MISSING_ENTRY,
SCHEMA
};
ConfigParseException(ParseErrorType err_t, const std::string& entry, const std::string& what = "") :
err_t(err_t), entry(entry), what(what){};
const ParseErrorType err_t;
const std::string entry;
const std::string what;
};
/// \brief A Mapping that can be used to map a module or implementation to a specific EVSE or optionally to a Connector
struct Mapping {
int evse; ///< The EVSE id
std::optional<int> connector; ///< An optional Connector id
Mapping(int evse) : evse(evse) {
}
Mapping(int evse, int connector) : evse(evse), connector(connector) {
}
};
/// \brief Writes the string representation of the given Mapping \p mapping to the given output stream \p os
/// \returns an output stream with the Mapping written to
inline std::ostream& operator<<(std::ostream& os, const Mapping& mapping) {
os << "Mapping(evse: " << mapping.evse;
if (mapping.connector.has_value()) {
os << ", connector: " << mapping.connector.value();
}
os << ")";
return os;
}
/// \brief Writes the string representation of the given Mapping \p mapping to the given output stream \p os
/// \returns an output stream with the Mapping written to
inline std::ostream& operator<<(std::ostream& os, const std::optional<Mapping>& mapping) {
if (mapping.has_value()) {
os << mapping.value();
} else {
os << "Mapping(charging station)";
}
return os;
}
/// \brief A 3 tier mapping for a module and its individual implementations
struct ModuleTierMappings {
std::optional<Mapping> module; ///< Mapping of the whole module to an EVSE id and optional Connector id. If this is
///< absent the module is assumed to be mapped to the whole charging station
std::map<std::string, std::optional<Mapping>, std::less<>>
implementations; ///< Mappings for the individual implementations of the module
};
/// \brief A Requirement of an EVerest module
struct Requirement {
std::string id;
size_t index = 0;
};
bool operator<(const Requirement& lhs, const Requirement& rhs);
/// \brief A Fulfillment relates a Requirement to its connected implementation, identified via its module and
/// implementation id.
struct Fulfillment {
std::string module_id; // the id of the module that fulfills the requirement
std::string implementation_id; // the id of the implementation that fulfills the requirement
Requirement requirement; // the requirement of the module that is fulfilled
};
struct TelemetryConfig {
int id;
explicit TelemetryConfig(int id) : id(id) {
}
};
namespace everest::config {
namespace fs = std::filesystem;
struct ConfigurationParameter;
struct ModuleConfig;
using ModuleId = std::string;
using RequirementId = std::string;
using ConfigEntry = std::variant<std::string, bool, int, double>;
using ImplementationIdentifier = std::string;
using ModuleConnections = std::map<RequirementId, std::vector<Fulfillment>, std::less<>>;
using ModuleConfigurations = std::map<ModuleId, ModuleConfig, std::less<>>;
using ModuleConfigurationParameters = std::map<ImplementationIdentifier, std::vector<ConfigurationParameter>>;
using Keys = std::set<std::string, std::less<>>;
struct VisitConfigEntry {
std::string operator()(const std::string& value) const {
return value;
};
std::string operator()(bool value) const {
return value ? "true" : "false";
};
std::string operator()(int value) const {
return std::to_string(value);
};
std::string operator()(double value) const {
return std::to_string(value);
};
};
std::string config_entry_to_string(const everest::config::ConfigEntry& entry);
enum class Mutability {
ReadOnly,
ReadWrite,
WriteOnly
};
enum class Datatype {
Unknown,
String,
Decimal,
Integer,
Boolean
};
struct Settings {
std::optional<fs::path> prefix;
std::optional<fs::path> config_file;
std::optional<fs::path> configs_dir;
std::optional<fs::path> schemas_dir;
std::optional<fs::path> modules_dir;
std::optional<fs::path> interfaces_dir;
std::optional<fs::path> types_dir;
std::optional<fs::path> errors_dir;
std::optional<fs::path> www_dir;
std::optional<fs::path> logging_config_file;
std::optional<int> controller_port;
std::optional<int> controller_rpc_timeout_ms;
std::optional<std::string> mqtt_broker_socket_path;
std::optional<std::string> mqtt_broker_host;
std::optional<std::uint16_t> mqtt_broker_port;
std::optional<std::string> mqtt_everest_prefix;
std::optional<std::string> mqtt_external_prefix;
std::optional<std::string> telemetry_prefix;
std::optional<bool> telemetry_enabled;
std::optional<bool> validate_schema;
std::optional<std::string> run_as_user;
std::optional<bool> forward_exceptions;
};
/// \brief Struct that contains the characteristics of a configuration parameter including its datatype, mutability and
/// unit
struct ConfigurationParameterCharacteristics {
Datatype datatype = Datatype::Unknown;
Mutability mutability = Mutability::ReadOnly;
std::optional<std::string> unit;
};
/// \brief Struct that contains the name, value and characteristics of a configuration parameter
struct ConfigurationParameter {
std::string name;
ConfigEntry value;
ConfigurationParameterCharacteristics characteristics;
bool validate_type() const;
};
/// \brief Access control information to an individual module config
struct ModuleConfigAccess {
bool allow_read = false; ///< Allow read access to config items
bool allow_write = false; ///< Allow write access to config items
bool allow_set_read_only = false; ///< If ReadOnly config items can be treated as ReadWrite (this typically requires
///< a reboot to have an effect)
};
/// \brief
struct ConfigAccess {
bool allow_global_read = false; ///< Allow this module to read the config items of all other modules
bool allow_global_write = false; ///< Allow this module to write the config items of all other modules
bool allow_set_read_only = false; ///< If ReadOnly config items can be treated as ReadWrite (this typically requires
///< a reboot to have an effect)
std::map<std::string, everest::config::ModuleConfigAccess>
modules; ///< Individual access to other modules config. The key represents the other modules module_id
///< and the value the associated access rights
};
/// \brief Access control information for a particular module
struct Access {
std::optional<ConfigAccess> config; ///< Access control to other modules configuration items
};
/// \brief Struct that contains the configuration of an EVerest module
struct ModuleConfig {
bool standalone = false;
std::string module_name;
std::string module_id;
std::optional<std::vector<std::string>> capabilities;
ModuleConfigurationParameters configuration_parameters; // contains: config_module and config_implementations
// as well as the upcoming "config" key
bool telemetry_enabled = false;
std::optional<TelemetryConfig> telemetry_config;
ModuleConnections connections;
ModuleTierMappings mapping;
Access access;
};
enum class SetConfigStatus {
Accepted,
Rejected,
RebootRequired
};
ConfigEntry parse_config_value(Datatype datatype, const std::string& value_str);
ModuleConfigurations parse_module_configs(const nlohmann::json& config);
Settings parse_settings(const nlohmann::json& settings_json);
Datatype string_to_datatype(const std::string& str);
std::string datatype_to_string(const Datatype datatype);
Mutability string_to_mutability(const std::string& str);
std::string mutability_to_string(const Mutability mutability);
ModuleTierMappings parse_mapping(const nlohmann::json& mapping_json);
} // namespace everest::config
NLOHMANN_JSON_NAMESPACE_BEGIN
template <> struct adl_serializer<everest::config::ModuleConfig> {
static void to_json(nlohmann::json& j, const everest::config::ModuleConfig& m);
static void from_json(const nlohmann::json& j, everest::config::ModuleConfig& m);
};
template <> struct adl_serializer<everest::config::ConfigurationParameterCharacteristics> {
static void to_json(nlohmann::json& j, const everest::config::ConfigurationParameterCharacteristics& c);
static void from_json(const nlohmann::json& j, everest::config::ConfigurationParameterCharacteristics& c);
};
template <> struct adl_serializer<everest::config::ConfigEntry> {
static void to_json(nlohmann::json& j, const everest::config::ConfigEntry& entry);
static void from_json(const nlohmann::json& j, everest::config::ConfigEntry& entry);
};
template <> struct adl_serializer<everest::config::ConfigurationParameter> {
static void to_json(nlohmann::json& j, const everest::config::ConfigurationParameter& p);
static void from_json(const nlohmann::json& j, everest::config::ConfigurationParameter& p);
};
template <> struct adl_serializer<everest::config::ModuleConfigAccess> {
static void to_json(nlohmann::json& j, const everest::config::ModuleConfigAccess& m);
static void from_json(const nlohmann::json& j, everest::config::ModuleConfigAccess& m);
};
template <> struct adl_serializer<everest::config::ConfigAccess> {
static void to_json(nlohmann::json& j, const everest::config::ConfigAccess& c);
static void from_json(const nlohmann::json& j, everest::config::ConfigAccess& c);
};
template <> struct adl_serializer<everest::config::Access> {
static void to_json(nlohmann::json& j, const everest::config::Access& c);
static void from_json(const nlohmann::json& j, everest::config::Access& c);
};
NLOHMANN_JSON_NAMESPACE_END

View File

@@ -0,0 +1,27 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#ifndef UTILS_CONFIG_CACHE_HPP
#define UTILS_CONFIG_CACHE_HPP
#include <map>
#include <set>
#include <string>
#include <utils/types.hpp>
namespace Everest {
struct ConfigCache {
std::set<std::string, std::less<>> provides_impl;
std::map<std::string, nlohmann::json, std::less<>> cmds;
};
} // namespace Everest
NLOHMANN_JSON_NAMESPACE_BEGIN
template <> struct adl_serializer<Everest::ConfigCache> {
static void to_json(nlohmann::json& j, const Everest::ConfigCache& c);
static void from_json(const nlohmann::json& j, Everest::ConfigCache& c);
};
NLOHMANN_JSON_NAMESPACE_END
#endif // UTILS_CONFIG_CACHE_HPP

View File

@@ -0,0 +1,215 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#pragma once
#include <cstddef>
#include <utils/config.hpp>
#include <utils/mqtt_abstraction.hpp>
namespace Everest {
namespace config {
constexpr auto MODULE_IMPLEMENTATION_ID = "!module";
inline constexpr std::size_t mqtt_get_config_retries = 1;
/// \brief The type of request or response
enum class Type {
Get, ///< Identifies a get request or response
Set, ///< Identifies a set request or response
Unknown ///< Used for unknown requests that could not be parsed
};
/// \brief Possible get request and response sub-types
enum class GetType {
All, ///< All module configurations that the requesting module has access to
Module, ///< The module configuration for the requesting module
Value, ///< A specific configuration value identified by a ConfigurationParameterIdentifier
AllMappings, ///< All module mappings that the requesting module has access to
Unknown ///< Used for unknown requests that could not be parsed
// TODO: Potential additions in the future:
// Delta, // This would need tracking of when the last Request was made
};
/// \brief Represents a get request
struct GetRequest {
GetType type = GetType::Unknown; ///< The type of get request
std::optional<everest::config::ConfigurationParameterIdentifier> identifier; ///< Used for GetType::Value
// TODO: Potential additions in the future:
// optional timestamp for GetType::Delta?
// a list of requested modules?
};
/// \brief Represents a response to a get request
struct GetResponse {
GetType type = GetType::Unknown; ///< The type of get response, the same as in the get request
nlohmann::json data; ///< Data associated with this reponse.
// FIXME: use proper type(s) for data?
};
/// \brief Represents a set request
struct SetRequest {
everest::config::ConfigurationParameterIdentifier
identifier; ///< An identifier for the configuration parameter to be set
std::string value; ///< The string representation of the configuration value to be set
// TODO: should value be a ConfigEntry variant type?
};
/// \brief Possible response status values
enum class ResponseStatus {
Ok, ///< Everything worked
Error, ///< There was an error during handling of the request
AccessDenied ///< There was an access error during handling of the request
};
/// \brief Possible set response status values
enum class SetResponseStatus {
Accepted, ///< Configuration value was set successfully
Rejected, ///< Configuration value could not be set
RebootRequired ///< Configuration value was set successfully but a reboot is required for modules to actually use
///< this value
};
/// \brief Represents a response to a set request
struct SetResponse {
SetResponseStatus status = SetResponseStatus::Rejected; ///< Status of the set response
std::string status_info; ///< Can contain additional status information
};
/// \brief Represents a container for various requests that can be made to the ConfigService
struct Request {
Type type = Type::Unknown; ///< The type of request
std::variant<std::monostate, GetRequest, SetRequest> request; ///< The request itself
std::string origin; ///< The origin of the request, the module id of the requesting module
};
/// \brief Represents a container for various responses to requests made to the ConfigService
struct Response {
ResponseStatus status = ResponseStatus::Error; ///< Status of the response
std::string status_info; ///< Can contain additional status information
std::optional<Type> type; ///< The type of the response, identical to the request, missing when status is Error
std::variant<std::monostate, GetResponse, SetResponse> response; ///< The response itself
};
/// \brief Represents a container for getting a configuration parameter
struct GetConfigResult {
ResponseStatus status = ResponseStatus::Error; ///< Status of the result
std::string status_info; ///< Can contain additional status information
everest::config::ConfigurationParameter configuration_parameter; ///< The requested configuration parameter
};
/// \brief Represents a container for the result of setting a configuration parameter
struct SetConfigResult {
ResponseStatus status = ResponseStatus::Error; ///< Status of the result
std::string status_info; ///< Can contain additional status information
everest::config::SetConfigStatus set_status =
everest::config::SetConfigStatus::Rejected; ///< Specific status for the resut of setting the config parameter
};
/// \brief Represents a compound type to identify a specific module instance and its type
struct ModuleIdType {
std::string module_id; ///< The module id
std::string module_type; ///< The associated module type
bool operator<(const ModuleIdType& rhs) const;
};
class ConfigServiceClient {
public:
/// \brief ConfigService client using the provided \p mqtt_abstraction for the module identified by \p module_id
/// \p module_names is a mapping of all module ids to module names/types for usage in get_module_configs()
ConfigServiceClient(std::shared_ptr<MQTTAbstraction> mqtt_abstraction, const std::string& module_id,
const std::map<std::string, std::string, std::less<>>& module_names);
/// \brief Compiles and \returns all module configs that this module has access to
std::map<ModuleIdType, everest::config::ModuleConfigurationParameters> get_module_configs();
/// \brief Compiles and \returns all mappings of modules that this module has access to
std::map<std::string, ModuleTierMappings> get_mappings();
/// \brief Sets the config \p value associated with the \p identifier
/// \returns a result containing status and potential error information
SetConfigResult set_config_value(const everest::config::ConfigurationParameterIdentifier& identifier,
const std::string& value);
/// \brief Gets the config value associated with the \p identifier
/// \returns a result containing the configuration item or an error
GetConfigResult get_config_value(const everest::config::ConfigurationParameterIdentifier& identifier);
private:
std::shared_ptr<MQTTAbstraction> mqtt_abstraction;
std::string origin;
std::map<std::string, std::string, std::less<>> module_names;
};
class ConfigService {
public:
/// \brief ConfigService using the provided \p mqtt_abstraction to distribute relevant parts of the given \p config
/// when another module requests them and has appropriate access rights to them
ConfigService(MQTTAbstraction& mqtt_abstraction, std::shared_ptr<ManagerConfig> config);
private:
MQTTAbstraction& mqtt_abstraction;
std::shared_ptr<TypedHandler> get_config_token;
std::shared_ptr<ManagerConfig> config;
};
namespace conversions {
std::string type_to_string(Type type);
Type string_to_type(const std::string& type);
std::string get_type_to_string(GetType type);
GetType string_to_get_type(const std::string& type);
std::string response_status_to_string(ResponseStatus status);
ResponseStatus string_to_response_status(const std::string& status);
std::string set_response_status_to_string(SetResponseStatus status);
SetResponseStatus string_to_set_response_status(const std::string& status);
everest::config::SetConfigStatus set_response_status_to_set_config_status(SetResponseStatus status);
SetResponseStatus set_config_status_to_set_response_status(everest::config::SetConfigStatus status);
} // namespace conversions
std::ostream& operator<<(std::ostream& os, const GetType& t);
void to_json(nlohmann::json& j, const GetRequest& r);
void from_json(const nlohmann::json& j, GetRequest& r);
void to_json(nlohmann::json& j, const SetRequest& r);
void from_json(const nlohmann::json& j, SetRequest& r);
void to_json(nlohmann::json& j, const GetResponse& r);
void from_json(const nlohmann::json& j, GetResponse& r);
void to_json(nlohmann::json& j, const SetResponse& r);
void from_json(const nlohmann::json& j, SetResponse& r);
void to_json(nlohmann::json& j, const Request& r);
void from_json(const nlohmann::json& j, Request& r);
void to_json(nlohmann::json& j, const Response& r);
void from_json(const nlohmann::json& j, Response& r);
} // namespace config
} // namespace Everest
NLOHMANN_JSON_NAMESPACE_BEGIN
template <> struct adl_serializer<everest::config::ConfigurationParameterIdentifier> {
static void to_json(nlohmann::json& j, const everest::config::ConfigurationParameterIdentifier& c);
static void from_json(const nlohmann::json& j, everest::config::ConfigurationParameterIdentifier& c);
};
NLOHMANN_JSON_NAMESPACE_END

View File

@@ -0,0 +1,103 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#ifndef UTILS_CONVERSIONS_HPP
#define UTILS_CONVERSIONS_HPP
#include <variant>
#include <nlohmann/json.hpp>
#include <utils/types.hpp>
namespace Everest {
using json = nlohmann::json;
namespace detail {
template <typename FundamentalType> constexpr bool is_type_compatible(nlohmann::json::value_t json_type);
template <> constexpr inline bool is_type_compatible<std::nullptr_t>(nlohmann::json::value_t json_type) {
return json_type == nlohmann::json::value_t::null;
}
template <> constexpr inline bool is_type_compatible<bool>(nlohmann::json::value_t json_type) {
return json_type == nlohmann::json::value_t::boolean;
}
template <> constexpr inline bool is_type_compatible<int>(nlohmann::json::value_t json_type) {
return json_type == nlohmann::json::value_t::number_integer ||
json_type == nlohmann::json::value_t::number_unsigned;
}
template <> constexpr inline bool is_type_compatible<double>(nlohmann::json::value_t json_type) {
return json_type == nlohmann::json::value_t::number_float;
}
template <> constexpr inline bool is_type_compatible<std::string>(nlohmann::json::value_t json_type) {
return json_type == nlohmann::json::value_t::string;
}
template <> constexpr inline bool is_type_compatible<nlohmann::json::array_t>(nlohmann::json::value_t json_type) {
return json_type == nlohmann::json::value_t::array;
}
template <> constexpr inline bool is_type_compatible<nlohmann::json::object_t>(nlohmann::json::value_t json_type) {
return json_type == nlohmann::json::value_t::object;
}
template <typename T> bool json_to_variant_impl(T& /*to*/, const nlohmann::json& /*from*/) noexcept {
return false;
}
template <typename VariantType, typename CurrentType, typename... Rest>
bool json_to_variant_impl(VariantType& to, const nlohmann::json& from) noexcept {
if (is_type_compatible<CurrentType>(from.type())) {
to = from.get<CurrentType>();
return true;
}
return json_to_variant_impl<VariantType, Rest...>(to, from);
}
} // namespace detail
template <typename... Ts> static std::variant<Ts...> json_to_variant(const nlohmann::json& j) {
std::variant<Ts...> var;
if (detail::json_to_variant_impl<std::variant<Ts...>, Ts...>(var, j)) {
return var;
}
throw std::runtime_error("The given json object doesn't contain any type, the std::variant is aware of");
}
template <typename T> nlohmann::json variant_to_json(T variant) {
return std::visit(
[](auto&& arg) -> nlohmann::json {
using U = std::decay_t<decltype(arg)>;
if constexpr (std::is_same_v<std::monostate, U>) { // FIXME: do we really want this?
return nlohmann::json(nullptr);
} else {
return arg;
}
},
variant);
}
namespace conversions {
/// The json tag for the error type.
constexpr auto ERROR_TYPE = "__everest__error_type";
/// The json tag for the error message.
constexpr auto ERROR_MSG = "__everest__error_msg";
std::string cmd_error_type_to_string(CmdErrorType cmd_error_type);
CmdErrorType string_to_cmd_error_type(const std::string& cmd_error_string);
} // namespace conversions
void to_json(nlohmann::json& j, const CmdResultError& e);
void from_json(const nlohmann::json& j, CmdResultError& e);
} // namespace Everest
#endif // UTILS_CONVERSIONS_HPP

View File

@@ -0,0 +1,21 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2022 Pionix GmbH and Contributors to EVerest
#ifndef UTILS_DATE_HPP
#define UTILS_DATE_HPP
#include <date/date.h>
#include <date/tz.h>
namespace Everest {
namespace Date {
std::string to_rfc3339(const std::chrono::time_point<date::utc_clock>& t);
std::chrono::time_point<date::utc_clock> from_rfc3339_slow(const std::string& t);
std::chrono::time_point<date::utc_clock> from_rfc3339(const std::string& t);
} // namespace Date
} // namespace Everest
#endif // UTILS_CONFIG_HPP

View File

@@ -0,0 +1,92 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#ifndef UTILS_ERROR_HPP
#define UTILS_ERROR_HPP
#include <string>
#include <utils/date.hpp>
#include <utils/types.hpp>
#define UTILS_ERROR_DEFAULTS_TYPE "NotValidType"
#define UTILS_ERROR_DEFAULTS_SUB_TYPE ""
#define UTILS_ERROR_DEFAULTS_DESCRIPTION "no description provided"
#define UTILS_ERROR_DEFAULTS_MESSAGE "no message provided"
#define UTILS_ERROR_DEFAULTS_SEVERITY Everest::error::Severity::Low
#define UTILS_ERROR_DEFAULTS_ORIGIN \
ImplementationIdentifier("no-module-id-provided", "no-implementation-id-provided", std::nullopt)
#define UTILS_ERROR_DEFAULTS_TIMESTAMP date::utc_clock::now()
#define UTILS_ERROR_DEFAULTS_UUID UUID()
#define UTILS_ERROR_DEFAULTS_STATE Everest::error::State::Active
#define UTILS_ERROR_DEFAULTS_VENDOR_ID "everest"
namespace Everest {
namespace error {
enum class Severity {
Low,
Medium,
High
};
std::string severity_to_string(const Severity& s);
Severity string_to_severity(const std::string& s);
struct UUID {
UUID();
explicit UUID(const std::string& uuid);
bool operator<(const UUID& other) const;
bool operator==(const UUID& other) const;
bool operator!=(const UUID& other) const;
std::string to_string() const;
std::string uuid;
};
using ErrorType = std::string;
using ErrorSubType = std::string;
enum class State {
Active,
ClearedByModule,
ClearedByReboot
};
std::string state_to_string(const State& s);
State string_to_state(const std::string& s);
///
/// \brief The Error struct represents an error object
///
struct Error {
using time_point = date::utc_clock::time_point;
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 = UTILS_ERROR_DEFAULTS_STATE);
Error(const ErrorType& type, const ErrorSubType& sub_type, const std::string& message,
const std::string& description, const ImplementationIdentifier& origin,
const Severity& severity = UTILS_ERROR_DEFAULTS_SEVERITY);
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 = UTILS_ERROR_DEFAULTS_SEVERITY);
Error();
ErrorType type;
ErrorSubType sub_type;
std::string message;
std::string description;
ImplementationIdentifier origin;
std::string vendor_id;
Severity severity;
time_point timestamp;
UUID uuid;
State state;
};
using ErrorHandle = UUID;
using ErrorPtr = std::shared_ptr<Error>;
using ErrorCallback = std::function<void(Error)>;
using ErrorTypes = std::map<ErrorType, std::string>;
} // namespace error
} // namespace Everest
#endif // UTILS_ERROR_HPP

View File

@@ -0,0 +1,31 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#ifndef UTILS_ERROR_DATABASE_HPP
#define UTILS_ERROR_DATABASE_HPP
#include <list>
#include <memory>
#include <utils/error/error_filter.hpp>
namespace Everest {
namespace error {
class ErrorDatabase {
public:
using EditErrorFunc = std::function<void(ErrorPtr)>;
ErrorDatabase() = default;
virtual ~ErrorDatabase() = default;
virtual void add_error(ErrorPtr error) = 0;
virtual std::list<ErrorPtr> get_errors(const std::list<ErrorFilter>& filters) const = 0;
virtual std::list<ErrorPtr> edit_errors(const std::list<ErrorFilter>& filters, EditErrorFunc edit_func) = 0;
virtual std::list<ErrorPtr> remove_errors(const std::list<ErrorFilter>& filters) = 0;
};
} // namespace error
} // namespace Everest
#endif // UTILS_ERROR_DATABASE_HPP

View File

@@ -0,0 +1,32 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#ifndef ERROR_DATABASE_MAP_HPP
#define ERROR_DATABASE_MAP_HPP
#include <list>
#include <utils/error.hpp>
#include <utils/error/error_database.hpp>
namespace Everest {
namespace error {
class ErrorDatabaseMap : public ErrorDatabase {
public:
ErrorDatabaseMap() = default;
void add_error(ErrorPtr error) override;
std::list<ErrorPtr> get_errors(const std::list<ErrorFilter>& filters) const override;
std::list<ErrorPtr> edit_errors(const std::list<ErrorFilter>& filters, EditErrorFunc edit_func) override;
std::list<ErrorPtr> remove_errors(const std::list<ErrorFilter>& filters) override;
private:
std::list<ErrorPtr> get_errors_no_mutex(const std::list<ErrorFilter>& filters) const;
std::map<ErrorHandle, ErrorPtr> errors;
mutable std::mutex errors_mutex;
};
} // namespace error
} // namespace Everest
#endif // ERROR_DATABASE_MAP_HPP

View File

@@ -0,0 +1,63 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#ifndef UTILS_ERROR_FACTORY_HPP
#define UTILS_ERROR_FACTORY_HPP
#include <memory>
#include <optional>
#include <string>
#include <utils/error.hpp>
namespace Everest {
namespace error {
struct ErrorTypeMap;
class ErrorFactory {
public:
explicit ErrorFactory(std::shared_ptr<ErrorTypeMap> error_type_map);
ErrorFactory(std::shared_ptr<ErrorTypeMap> error_type_map, ImplementationIdentifier default_origin);
ErrorFactory(std::shared_ptr<ErrorTypeMap> error_type_map, ImplementationIdentifier default_origin,
Severity default_severity);
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 create_error() const;
Error create_error(const ErrorType& type, const ErrorSubType& sub_type, const std::string& message) const;
Error create_error(const ErrorType& type, const ErrorSubType& sub_type, const std::string& message,
const Severity severity) const;
Error create_error(const ErrorType& type, const ErrorSubType& sub_type, const std::string& message,
const State state) const;
Error create_error(const ErrorType& type, const ErrorSubType& sub_type, const std::string& message,
const Severity severity, const State state) const;
void set_default_origin(const ImplementationIdentifier& origin);
void set_default_severity(Severity severity);
void set_default_state(State state);
void set_default_type(const ErrorType& type);
void set_default_sub_type(const ErrorSubType& sub_type);
void set_default_message(const std::string& message);
void set_default_vendor_id(const std::string& vendor_id);
private:
const 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;
void set_description(Error& error) const;
};
} // namespace error
} // namespace Everest
#endif // UTILS_ERROR_FACTORY_HPP

View File

@@ -0,0 +1,114 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#ifndef UTILS_ERROR_FILTER_HPP
#define UTILS_ERROR_FILTER_HPP
#include <utils/error.hpp>
#include <utils/types.hpp>
#include <variant>
namespace Everest {
namespace error {
///
/// \brief This filter is used to filter errors by their state.
///
using StateFilter = State;
std::string state_filter_to_string(const StateFilter& s);
StateFilter string_to_state_filter(const std::string& s);
///
/// \brief This filter is used to filter errors by their origin.
///
using OriginFilter = ImplementationIdentifier;
///
/// \brief This filter is used to filter errors by their type.
///
struct TypeFilter {
explicit TypeFilter(const ErrorType& value);
ErrorType value;
};
///
/// \brief This filter is used to filter errors by their severity.
///
enum class SeverityFilter {
LOW_GE, ///< greater or equal LOW
MEDIUM_GE, ///< greater or equal MEDIUM
HIGH_GE ///< greater or equal HIGHS
};
std::string severity_filter_to_string(const SeverityFilter& s);
SeverityFilter string_to_severity_filter(const std::string& s);
///
/// \brief This filter is used to filter errors by their time of occurrence.
///
struct TimePeriodFilter {
Error::time_point from; // time point from which the filter should be applied
Error::time_point to; // time point to which the filter should be applied
};
///
/// \brief This filter is used to filter errors by their handle.
/// The handle is the error code.
using HandleFilter = ErrorHandle;
///
/// \brief This filter is used to filter errors by their sub type.
///
struct SubTypeFilter {
explicit SubTypeFilter(const ErrorSubType& value);
ErrorSubType value;
};
struct VendorIdFilter {
explicit VendorIdFilter(const std::string& value);
std::string value;
};
///
/// \brief This enum is used to identify the different filter types.
///
enum class FilterType {
State = 1,
Origin = 2,
Type = 3,
Severity = 4,
TimePeriod = 5,
Handle = 6,
SubType = 7,
VendorId = 8
};
std::string filter_type_to_string(const FilterType& f);
FilterType string_to_filter_type(const std::string& s);
class ErrorFilter {
public:
using FilterVariant = std::variant<std::monostate, StateFilter, OriginFilter, TypeFilter, SeverityFilter,
TimePeriodFilter, HandleFilter, SubTypeFilter, VendorIdFilter>;
ErrorFilter();
explicit ErrorFilter(const FilterVariant& filter_);
FilterType get_filter_type() const;
StateFilter get_state_filter() const;
OriginFilter get_origin_filter() const;
TypeFilter get_type_filter() const;
SeverityFilter get_severity_filter() const;
TimePeriodFilter get_time_period_filter() const;
HandleFilter get_handle_filter() const;
SubTypeFilter get_sub_type_filter() const;
VendorIdFilter get_vendor_id_filter() const;
private:
FilterVariant filter;
};
} // namespace error
} // namespace Everest
#endif // UTILS_ERROR_FILTER_HPP

View File

@@ -0,0 +1,48 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#ifndef UTILS_ERROR_JSON_HPP
#define UTILS_ERROR_JSON_HPP
#include <nlohmann/json.hpp>
#include <utils/error.hpp>
NLOHMANN_JSON_NAMESPACE_BEGIN
template <> struct adl_serializer<Everest::error::Error> {
static void to_json(json& j, const Everest::error::Error& e) {
j = {{"type", e.type},
{"description", e.description},
{"message", e.message},
{"origin", {{"module_id", e.origin.module_id}, {"implementation_id", e.origin.implementation_id}}},
{"severity", Everest::error::severity_to_string(e.severity)},
{"timestamp", Everest::Date::to_rfc3339(e.timestamp)},
{"uuid", e.uuid.uuid},
{"state", Everest::error::state_to_string(e.state)},
{"sub_type", e.sub_type},
{"vendor_id", e.vendor_id}};
if (e.origin.mapping.has_value()) {
j["origin"]["mapping"] = e.origin.mapping.value();
}
}
static Everest::error::Error from_json(const json& j) {
const Everest::error::ErrorType type = j.at("type");
const std::string message = j.at("message");
const std::string description = j.at("description");
std::optional<Mapping> mapping;
if (j.at("origin").contains("mapping")) {
mapping = j.at("origin").at("mapping");
}
const ImplementationIdentifier origin =
ImplementationIdentifier(j.at("origin").at("module_id"), j.at("origin").at("implementation_id"), mapping);
const Everest::error::Severity severity = Everest::error::string_to_severity(j.at("severity"));
const Everest::error::Error::time_point timestamp = Everest::Date::from_rfc3339(j.at("timestamp"));
const Everest::error::UUID uuid(j.at("uuid"));
const Everest::error::State state = Everest::error::string_to_state(j.at("state"));
const Everest::error::ErrorSubType sub_type(j.at("sub_type"));
const std::string vendor_id = j.at("vendor_id");
return {type, sub_type, message, description, origin, vendor_id, severity, timestamp, uuid, state};
}
};
NLOHMANN_JSON_NAMESPACE_END
#endif // UTILS_ERROR_JSON_HPP

View File

@@ -0,0 +1,83 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#ifndef UTILS_ERROR_MANAGER_IMPL_HPP
#define UTILS_ERROR_MANAGER_IMPL_HPP
#include <list>
#include <memory>
#include <mutex>
#include <utils/error.hpp>
namespace Everest {
namespace error {
struct ErrorDatabase;
struct ErrorTypeMap;
class ErrorManagerImpl {
public:
using PublishErrorFunc = std::function<void(const error::Error&)>;
ErrorManagerImpl(std::shared_ptr<ErrorTypeMap> error_type_map, std::shared_ptr<ErrorDatabase> error_database,
std::list<ErrorType> allowed_error_types, PublishErrorFunc publish_raised_error,
PublishErrorFunc publish_cleared_error, const bool validate_error_types = true);
///
/// \brief raise_error raises an error
/// \param error The error to raise
/// \details This function raises an error. The error is added to the error database if it has a valid error type
/// and is allowed to be raised.
///
void raise_error(const Error& error);
///
/// \brief clear_error clears an error
/// \param type The error type to clear
/// \details This function clears an error if there are not multiples sub types active.
///
std::list<ErrorPtr> clear_error(const ErrorType& type);
///
/// \brief clear_error clears an error
/// \param type The error type to clear
/// \param sub_type The error sub type to clear
/// \details This function clears a specific subtype of an error type.
///
std::list<ErrorPtr> clear_error(const ErrorType& type, const ErrorSubType& sub_type);
///
/// \brief clear_all_errors clears all errors
/// \details This function clears all errors that are currently active.
///
std::list<ErrorPtr> clear_all_errors();
///
/// \brief clear_all_errors clears all errors of a specific ErrorType
/// \param error_type The error type to clear
/// \details This function clears all errors of a specific ErrorType that are currently active.
///
std::list<ErrorPtr> clear_all_errors(const ErrorType& error_type);
private:
bool can_be_raised(const ErrorType& type, const ErrorSubType& sub_type) const;
bool can_be_cleared(const ErrorType& type, const ErrorSubType& sub_type) const;
bool can_be_cleared(const ErrorType& type) const;
std::shared_ptr<ErrorTypeMap> error_type_map;
std::shared_ptr<ErrorDatabase> database;
std::list<ErrorType> allowed_error_types;
std::mutex mutex;
PublishErrorFunc publish_raised_error;
PublishErrorFunc publish_cleared_error;
const bool validate_error_types;
};
} // namespace error
} // namespace Everest
#endif // UTILS_ERROR_MANAGER_IMPL_HPP

View File

@@ -0,0 +1,53 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#ifndef UTILS_ERROR_MANAGER_REQ_HPP
#define UTILS_ERROR_MANAGER_REQ_HPP
#include <utils/error.hpp>
#include <list>
#include <map>
#include <memory>
namespace Everest {
namespace error {
struct ErrorDatabase;
struct ErrorTypeMap;
class ErrorManagerReq {
public:
using SubscribeErrorFunc = std::function<void(const ErrorType&, const ErrorCallback&, const ErrorCallback&)>;
ErrorManagerReq(std::shared_ptr<ErrorTypeMap> error_type_map, std::shared_ptr<ErrorDatabase> error_database,
std::list<ErrorType> allowed_error_types, SubscribeErrorFunc subscribe_error_func);
void subscribe_error(const ErrorType& type, const ErrorCallback& callback, const ErrorCallback& clear_callback);
void subscribe_all_errors(const ErrorCallback& callback, const ErrorCallback& clear_callback);
private:
struct Subscription {
Subscription(const ErrorType& type, const ErrorCallback& callback, const ErrorCallback& clear_callback);
ErrorType type;
ErrorCallback callback;
ErrorCallback clear_callback;
};
std::map<ErrorType, std::list<Subscription>> error_subscriptions;
void on_error_raised(const Error& error);
void on_error_cleared(const Error& error);
void on_error(const Error& error, const bool raised) const;
std::shared_ptr<ErrorTypeMap> error_type_map;
std::shared_ptr<ErrorDatabase> database;
std::list<ErrorType> allowed_error_types;
SubscribeErrorFunc subscribe_error_func;
};
} // namespace error
} // namespace Everest
#endif // UTILS_ERROR_MANAGER_REQ_HPP

View File

@@ -0,0 +1,49 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#ifndef UTILS_ERROR_MANAGER_REQ_GLOBAL_HPP
#define UTILS_ERROR_MANAGER_REQ_GLOBAL_HPP
#include <utils/error.hpp>
#include <list>
#include <map>
#include <memory>
namespace Everest {
namespace error {
struct ErrorDatabase;
struct ErrorTypeMap;
class ErrorManagerReqGlobal {
public:
using SubscribeGlobalAllErrorsFunc = std::function<void(const ErrorCallback&, const ErrorCallback&)>;
ErrorManagerReqGlobal(std::shared_ptr<ErrorTypeMap> error_type_map, std::shared_ptr<ErrorDatabase> error_database,
SubscribeGlobalAllErrorsFunc subscribe_global_all_errors_func);
void subscribe_global_all_errors(const ErrorCallback& callback, const ErrorCallback& clear_callback);
private:
struct Subscription {
Subscription(const ErrorCallback& callback, const ErrorCallback& clear_callback);
ErrorCallback callback;
ErrorCallback clear_callback;
};
void on_error_raised(const Error& error);
void on_error_cleared(const Error& error);
std::shared_ptr<ErrorTypeMap> error_type_map;
std::shared_ptr<ErrorDatabase> database;
SubscribeGlobalAllErrorsFunc subscribe_global_all_errors_func;
std::list<Subscription> subscriptions;
};
} // namespace error
} // namespace Everest
#endif // UTILS_ERROR_MANAGER_REQ_GLOBAL_HPP

View File

@@ -0,0 +1,74 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#ifndef UTILS_ERROR_STATE_MONITOR_HPP
#define UTILS_ERROR_STATE_MONITOR_HPP
#include <list>
#include <utils/error.hpp>
namespace Everest {
namespace error {
struct ErrorDatabase;
///
/// \brief The StateMonitor class is used to monitor the state of multiple error types
/// \details The StateMonitor class is used to monitor the state of multiple error types. It can be used to check if a
/// certain error type is active or if a certain condition is satisfied.
///
class ErrorStateMonitor {
public:
///
/// \brief The StateCondition struct represents a single condition that can be checked by the StateMonitor
///
struct StateCondition {
StateCondition(ErrorType type, ErrorSubType sub_type, bool active);
ErrorType type;
ErrorSubType sub_type;
bool active;
};
///
/// \brief StateMonitor constructor
/// \param error_database The error database to monitor
///
explicit ErrorStateMonitor(std::shared_ptr<ErrorDatabase> error_database);
///
/// \brief is_error_active checks if a certain combination of error type and sub_type is active
/// \param type The error type to check
/// \param sub_type The error sub type to check
/// \return True if the error type is active, false otherwise
///
bool is_error_active(const ErrorType& type, const ErrorSubType& sub_type) const;
///
/// \brief get_active_errors returns the list of active errors for this error state monitor
/// \return List of active errors
///
std::list<ErrorPtr> get_active_errors() const;
///
/// \brief is_condition_satisfied checks if a certain condition is satisfied
/// \param condition The condition to check
/// \return True if the condition is satisfied, false otherwise
///
bool is_condition_satisfied(const StateCondition& condition) const;
///
/// \brief is_condition_satisfied checks if a certain list of conditions is satisfied
/// \param condition The list of conditions to check
/// \return True if all conditions are satisfied, false otherwise
///
bool is_condition_satisfied(const std::list<StateCondition>& condition) const;
private:
std::shared_ptr<ErrorDatabase> database;
};
} // namespace error
} // namespace Everest
#endif // UTILS_ERROR_STATE_MONITOR_HPP

View File

@@ -0,0 +1,72 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#ifndef UTILS_ERROR_TYPE_MAP_HPP
#define UTILS_ERROR_TYPE_MAP_HPP
#include <filesystem>
#include <utils/error.hpp>
namespace Everest {
namespace error {
///
/// \brief A map of error types to their descriptions.
/// This class is used to load error types from a directory
/// and to get the description of an error type.
///
class ErrorTypeMap {
public:
///
/// \brief Default constructor.
/// Creates an empty map.
///
ErrorTypeMap() = default;
///
/// \brief Constructor that loads error types from a directory.
/// \param error_types_dir The directory to load the error types from.
///
explicit ErrorTypeMap(const std::filesystem::path& error_types_dir);
///
/// \brief Loads error types from a directory.
/// \param error_types_dir The directory to load the error types from.
///
void load_error_types(const std::filesystem::path& error_types_dir);
///
/// \brief Loads error types from a given map
/// \param error_types_dir The map to load the error types from.
///
void load_error_types_map(std::map<ErrorType, std::string> error_types_map);
///
/// \brief Gets the description of an error type.
/// \param error_type The error type to get the description of.
/// \return The description of the error type.
///
std::string get_description(const ErrorType& error_type) const;
///
/// \brief Checks if an error type exists.
/// \param error_type The error type to check.
/// \return True if the error type exists, false otherwise.
///
bool has(const ErrorType& error_type) const;
///
/// \brief Returns the contained ErrorType map
/// \return The error types map
///
const ErrorTypes& get_error_types() const;
private:
ErrorTypes error_types;
};
} // namespace error
} // namespace Everest
#endif // UTILS_ERROR_TYPE_MAP_HPP

View File

@@ -0,0 +1,47 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#pragma once
#include <stdexcept>
namespace Everest {
struct BootException : public std::runtime_error {
using std::runtime_error::runtime_error;
};
class CmdError : public std::runtime_error {
public:
using std::runtime_error::runtime_error;
};
class MessageParsingError : public CmdError {
public:
using CmdError::CmdError;
};
class SchemaValidationError : public CmdError {
public:
using CmdError::CmdError;
};
class HandlerException : public CmdError {
public:
using CmdError::CmdError;
};
class CmdTimeout : public CmdError {
public:
using CmdError::CmdError;
};
class Shutdown : public CmdError {
public:
using CmdError::CmdError;
};
class NotReady : public CmdError {
public:
using CmdError::CmdError;
};
} // namespace Everest

View File

@@ -0,0 +1,30 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#ifndef UTILS_FILESYSTEM_HPP
#define UTILS_FILESYSTEM_HPP
#include <nlohmann/json.hpp>
#include <utils/types.hpp>
namespace Everest {
namespace fs = std::filesystem;
/// \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 = "The");
/// \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 = "The");
/// \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);
/// \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);
} // namespace Everest
#endif // UTILS_FILESYSTEM_HPP

View File

@@ -0,0 +1,72 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2023 Pionix GmbH and Contributors to EVerest
#ifndef UTILS_FORMATTER_HPP
#define UTILS_FORMATTER_HPP
#include <atomic>
#include <fmt/core.h>
#include <nlohmann/json.hpp>
#include <string>
namespace everest::formatting {
// NOTE (aw): this function is only here to hide the implementation of
// fmt::format_error in <fmt/format.h>
// in fmt version < 10, the function throw_format_error is not exposed
// in the public api
// NOTE (aw): this is only for backward compatibility, once finally
// switching to version 10, this function could be removed again
void throw_format_error(const char* message);
} // namespace everest::formatting
template <> struct fmt::formatter<nlohmann::json> {
constexpr auto parse(fmt::format_parse_context& ctx) -> fmt::format_parse_context::iterator {
auto it = ctx.begin(), end = ctx.end();
if (it != end && *it != '}') {
everest::formatting::throw_format_error("Invalid format");
}
return it;
}
auto format(const nlohmann::json& j, fmt::format_context& ctx) const -> fmt::format_context::iterator {
return fmt::format_to(ctx.out(), "{}", j.dump());
}
};
template <> struct fmt::formatter<std::atomic_bool> {
constexpr auto parse(fmt::format_parse_context& ctx) -> fmt::format_parse_context::iterator {
auto it = ctx.begin(), end = ctx.end();
if (it != end && *it != '}') {
everest::formatting::throw_format_error("Invalid format");
}
return it;
}
auto format(const std::atomic_bool& a, fmt::format_context& ctx) const -> fmt::format_context::iterator {
if (a) {
return fmt::format_to(ctx.out(), "true");
}
return fmt::format_to(ctx.out(), "false");
}
};
template <typename T> struct fmt::formatter<std::atomic<T>> {
constexpr auto parse(fmt::format_parse_context& ctx) -> fmt::format_parse_context::iterator {
auto it = ctx.begin(), end = ctx.end();
if (it != end && *it != '}') {
everest::formatting::throw_format_error("Invalid format");
}
return it;
}
auto format(const std::atomic<T>& a, fmt::format_context& ctx) const -> fmt::format_context::iterator {
return fmt::format_to(ctx.out(), "{}", a.load());
}
};
#endif // UTILS_FORMATTER_HPP

View File

@@ -0,0 +1,14 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#pragma once
#include <limits>
#include <utils/types.hpp>
namespace Everest::helpers {
template <typename T, typename U> T constexpr clamp_to(U len) {
return (len <= std::numeric_limits<T>::max()) ? static_cast<T>(len) : std::numeric_limits<T>::max();
}
} // namespace Everest::helpers

View File

@@ -0,0 +1,129 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#pragma once
#include <atomic>
#include <chrono>
#include <map>
#include <string>
#include <thread>
#include <unordered_map>
#include <unordered_set>
#include <vector>
#include <everest/util/async/monitor.hpp>
#include <everest/util/async/thread_pool_scaling.hpp>
#include <everest/util/queue/thread_safe_queue.hpp>
#include <utils/message_handler_scaling_policy.hpp>
#include <utils/message_queue.hpp>
#include <utils/types.hpp>
using MqttTopic = std::string;
using CmdId = std::string;
namespace Everest {
constexpr std::size_t THREAD_POOL_SCALING_LATENCY_THRESHOLD_MS =
detail::MESSAGE_HANDLER_THREAD_POOL_SCALING_LATENCY_THRESHOLD_MS;
constexpr std::chrono::seconds THREAD_POOL_SCALING_IDLE_TIMEOUT{2};
constexpr std::size_t THREAD_POOL_SCALING_MIN_THREAD_COUNT = 1;
constexpr std::size_t MAX_PENDING_MESSAGES_PER_TOPIC = 100;
/// \brief Handles message dispatching and thread-safe queuing of different message types.
///
/// Messages are routed to one of four channels based on their type:
/// - operation_message_queue → operation_dispatcher_thread → operation_thread_pool
/// (vars, cmds, errors, GetConfig, ModuleReady — parallel across topics, serial per topic)
/// - result_message_queue → result_worker_thread (cmd results, GetConfig responses — serial)
/// - external_mqtt_message_queue → external_mqtt_worker_thread (external MQTT — serial)
/// - GlobalReady → ready_thread (one-shot, spawned per message)
class MessageHandler {
public:
MessageHandler();
~MessageHandler();
/// \brief Adds given \p message to the message queue for processing
void add(const ParsedMessage& message);
/// \brief Stops all threads started by this handler
void stop();
/// \brief Registers a \p handler for a specific \p topic
void register_handler(const std::string& topic, std::shared_ptr<TypedHandler> handler);
using SharedTypedHandler = std::shared_ptr<TypedHandler>;
using SingleHandlerMap = std::map<MqttTopic, SharedTypedHandler>;
using MultiHandlerMap = std::map<MqttTopic, std::vector<SharedTypedHandler>>;
private:
struct OperationTopics {
std::unordered_set<std::string> in_flight;
std::unordered_map<std::string, everest::lib::util::simple_queue<ParsedMessage>> pending_messages;
};
struct ResponseHandlers {
std::map<CmdId, std::shared_ptr<TypedHandler>> cmd; // cmd result handlers of module
std::shared_ptr<TypedHandler> config; // get module config response handler of module
};
struct GenericHandlers {
MultiHandlerMap var; // var handlers of module
SingleHandlerMap cmd; // cmd handlers of module
MultiHandlerMap error; // error handlers with wildcard support
SingleHandlerMap get_module_config; // get module config handler of manager
SharedTypedHandler global_ready; // global ready handler of module
SingleHandlerMap module_ready; // module ready handlers of manager
MultiHandlerMap external_var; // external MQTT handlers of module
};
void run_operation_dispatcher();
void run_result_message_worker();
void run_external_mqtt_worker();
void dispatch_operation_message(ParsedMessage&& message);
void schedule_operation_message(ParsedMessage&& message);
void on_operation_message_done(const std::string& topic);
void handle_operation_message(const std::string& topic, const json& payload);
void handle_result_message(const std::string& topic, const json& payload);
// Individual message handler methods
void handle_var_message(const std::string& topic, const json& data);
void handle_cmd_message(const std::string& topic, const json& data);
void handle_external_mqtt_message(const std::string& topic, const json& data);
void handle_error_message(const std::string& topic, const json& data);
void handle_get_config_message(const std::string& topic, const json& data);
void handle_module_ready_message(const std::string& topic, const json& data);
void handle_cmd_result(const std::string& topic, const json& payload);
void handle_get_config_response(const std::string& topic, const json& payload);
// Threads
std::thread operation_dispatcher_thread; // processes vars, commands, external MQTT, errors, GetConfig and
// ModuleReady messages
std::thread result_worker_thread; // processes cmd results and GetConfig responses
std::thread external_mqtt_worker_thread; // processes external MQTT messages
// Wrapped in a monitor so that concurrent GlobalReady arrivals in add() are safe:
// the steal-then-join pattern moves the previous thread out under the lock and joins
// outside the lock, preventing concurrent join/assignment races on the raw std::thread.
everest::lib::util::monitor<std::thread> ready;
using ThreadPool = everest::lib::util::thread_pool_scaling<detail::MessageHandlerScalingPolicy,
everest::lib::util::RethrowExceptions>;
std::unique_ptr<ThreadPool> operation_thread_pool;
using MessageQueue = everest::lib::util::thread_safe_queue<ParsedMessage>;
MessageQueue operation_message_queue;
MessageQueue result_message_queue;
MessageQueue external_mqtt_message_queue;
everest::lib::util::monitor<OperationTopics> operations;
everest::lib::util::monitor<ResponseHandlers> responses;
everest::lib::util::monitor<GenericHandlers> handlers;
std::atomic<bool> running = true;
};
} // namespace Everest

View File

@@ -0,0 +1,36 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#pragma once
#include <everest/compile_time_settings.hpp>
#include <everest/util/async/thread_pool_scaling.hpp>
namespace Everest::detail {
constexpr std::size_t MESSAGE_HANDLER_THREAD_POOL_SCALING_LATENCY_THRESHOLD_MS =
EVEREST_FRAMEWORK_THREAD_POOL_SCALING_LATENCY_THRESHOLD_MS;
constexpr std::size_t MESSAGE_HANDLER_THREAD_POOL_SCALING_LATENCY_TICK_MS =
EVEREST_FRAMEWORK_THREAD_POOL_SCALING_LATENCY_TICK_MS;
constexpr std::size_t MESSAGE_HANDLER_THREAD_POOL_SCALING_FIXED_SIZE_THRESHOLD =
EVEREST_FRAMEWORK_THREAD_POOL_SCALING_FIXED_SIZE_THRESHOLD;
using MessageHandlerLatencyScaling =
everest::lib::util::LatencyScaling<MESSAGE_HANDLER_THREAD_POOL_SCALING_LATENCY_THRESHOLD_MS,
MESSAGE_HANDLER_THREAD_POOL_SCALING_LATENCY_TICK_MS>;
using MessageHandlerFixedSizeScaling =
everest::lib::util::FixedSizeScaling<MESSAGE_HANDLER_THREAD_POOL_SCALING_FIXED_SIZE_THRESHOLD>;
#if EVEREST_FRAMEWORK_THREAD_POOL_SCALING_POLICY == EVEREST_FRAMEWORK_THREAD_POOL_SCALING_POLICY_CUSTOM
using MessageHandlerScalingPolicy = EVEREST_FRAMEWORK_THREAD_POOL_SCALING_POLICY_CUSTOM_TYPE;
#elif EVEREST_FRAMEWORK_THREAD_POOL_SCALING_POLICY == EVEREST_FRAMEWORK_THREAD_POOL_SCALING_POLICY_GREEDY
using MessageHandlerScalingPolicy = everest::lib::util::GreedyScaling;
#elif EVEREST_FRAMEWORK_THREAD_POOL_SCALING_POLICY == EVEREST_FRAMEWORK_THREAD_POOL_SCALING_POLICY_CONSERVATIVE
using MessageHandlerScalingPolicy = everest::lib::util::ConservativeScaling;
#elif EVEREST_FRAMEWORK_THREAD_POOL_SCALING_POLICY == EVEREST_FRAMEWORK_THREAD_POOL_SCALING_POLICY_FIXED_SIZE
using MessageHandlerScalingPolicy = MessageHandlerFixedSizeScaling;
#else
using MessageHandlerScalingPolicy = MessageHandlerLatencyScaling;
#endif
} // namespace Everest::detail

View File

@@ -0,0 +1,40 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#ifndef UTILS_MESSAGE_QUEUE_HPP
#define UTILS_MESSAGE_QUEUE_HPP
#include <condition_variable>
#include <cstddef>
#include <functional>
#include <map>
#include <memory>
#include <mutex>
#include <queue>
#include <string_view>
#include <thread>
#include <unordered_set>
#include <nlohmann/json.hpp>
#include <utils/types.hpp>
namespace Everest {
/// \brief Contains a payload and the topic it was received on
struct Message {
std::string topic; ///< The MQTT topic where this message originated from
std::string payload; ///< The message payload
Message(std::string_view topic_param, std::string_view payload_param) : topic(topic_param), payload(payload_param) {
}
};
struct ParsedMessage {
std::string topic;
nlohmann::json data;
};
using MessageCallback = std::function<void(const Message&)>;
} // namespace Everest
#endif // UTILS_MESSAGE_QUEUE_HPP

View File

@@ -0,0 +1,551 @@
/**
* Macros for metaprogramming
* ExtendedC
*
* Copyright (C) 2012 Justin Spahr-Summers
* Released under the MIT license
*/
#ifndef EXTC_METAMACROS_H
#define EXTC_METAMACROS_H
/**
* Executes one or more expressions (which may have a void type, such as a call
* to a function that returns no value) and always returns true.
*/
#define metamacro_exprify(...) ((__VA_ARGS__), true)
/**
* Returns a string representation of VALUE after full macro expansion.
*/
#define metamacro_stringify(VALUE) metamacro_stringify_(VALUE)
/**
* Returns A and B concatenated after full macro expansion.
*/
#define metamacro_concat(A, B) metamacro_concat_(A, B)
/**
* Returns the Nth variadic argument (starting from zero). At least
* N + 1 variadic arguments must be given. N must be between zero and twenty,
* inclusive.
*/
#define metamacro_at(N, ...) metamacro_concat(metamacro_at, N)(__VA_ARGS__)
/**
* Returns the number of arguments (up to twenty) provided to the macro. At
* least one argument must be provided.
*
* Inspired by P99: http://p99.gforge.inria.fr
*/
#define metamacro_argcount(...) \
metamacro_at(20, ##__VA_ARGS__, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)
/**
* Identical to #metamacro_foreach_cxt, except that no CONTEXT argument is
* given. Only the index and current argument will thus be passed to MACRO.
*/
#define metamacro_foreach(MACRO, SEP, ...) metamacro_foreach_cxt(metamacro_foreach_iter, SEP, MACRO, __VA_ARGS__)
/**
* For each consecutive variadic argument (up to twenty), MACRO is passed the
* zero-based index of the current argument, CONTEXT, and then the argument
* itself. The results of adjoining invocations of MACRO are then separated by
* SEP.
*
* Inspired by P99: http://p99.gforge.inria.fr
*/
#define metamacro_foreach_cxt(MACRO, SEP, CONTEXT, ...) \
metamacro_concat(metamacro_foreach_cxt, metamacro_argcount(__VA_ARGS__))(MACRO, SEP, CONTEXT, __VA_ARGS__)
/**
* Identical to #metamacro_foreach_cxt. This can be used when the former would
* fail due to recursive macro expansion.
*/
#define metamacro_foreach_cxt_recursive(MACRO, SEP, CONTEXT, ...) \
metamacro_concat(metamacro_foreach_cxt_recursive, metamacro_argcount(__VA_ARGS__))(MACRO, SEP, CONTEXT, __VA_ARGS__)
/**
* In consecutive order, appends each variadic argument (up to twenty) onto
* BASE. The resulting concatenations are then separated by SEP.
*
* This is primarily useful to manipulate a list of macro invocations into instead
* invoking a different, possibly related macro.
*/
#define metamacro_foreach_concat(BASE, SEP, ...) \
metamacro_foreach_cxt(metamacro_foreach_concat_iter, SEP, BASE, __VA_ARGS__)
/**
* Iterates COUNT times, each time invoking MACRO with the current index
* (starting at zero) and CONTEXT. The results of adjoining invocations of MACRO
* are then separated by SEP.
*
* COUNT must be an integer between zero and twenty, inclusive.
*/
#define metamacro_for_cxt(COUNT, MACRO, SEP, CONTEXT) metamacro_concat(metamacro_for_cxt, COUNT)(MACRO, SEP, CONTEXT)
/**
* Returns the first argument given. At least one argument must be provided.
*
* This is useful when implementing a variadic macro, where you may have only
* one variadic argument, but no way to retrieve it (for example, because \c ...
* always needs to match at least one argument).
*
* @code
#define varmacro(...) \
metamacro_head(__VA_ARGS__)
* @endcode
*/
#define metamacro_head(...) metamacro_head_(__VA_ARGS__, 0)
/**
* Returns every argument except the first. At least two arguments must be
* provided.
*/
#define metamacro_tail(...) metamacro_tail_(__VA_ARGS__)
/**
* Returns the first N (up to twenty) variadic arguments as a new argument list.
* At least N variadic arguments must be provided.
*/
#define metamacro_take(N, ...) metamacro_concat(metamacro_take, N)(__VA_ARGS__)
/**
* Removes the first N (up to twenty) variadic arguments from the given argument
* list. At least N variadic arguments must be provided.
*/
#define metamacro_drop(N, ...) metamacro_concat(metamacro_drop, N)(__VA_ARGS__)
/**
* Decrements VAL, which must be a number between zero and twenty, inclusive.
*
* This is primarily useful when dealing with indexes and counts in
* metaprogramming.
*/
#define metamacro_dec(VAL) metamacro_at(VAL, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19)
/**
* Increments VAL, which must be a number between zero and twenty, inclusive.
*
* This is primarily useful when dealing with indexes and counts in
* metaprogramming.
*/
#define metamacro_inc(VAL) metamacro_at(VAL, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21)
/**
* If A is equal to B, the next argument list is expanded; otherwise, the
* argument list after that is expanded. A and B must be numbers between zero
* and twenty, inclusive. Additionally, B must be greater than or equal to A.
*
* @code
// expands to true
metamacro_if_eq(0, 0)(true)(false)
// expands to false
metamacro_if_eq(0, 1)(true)(false)
* @endcode
*
* This is primarily useful when dealing with indexes and counts in
* metaprogramming.
*/
#define metamacro_if_eq(A, B) metamacro_concat(metamacro_if_eq, A)(B)
/**
* Identical to #metamacro_if_eq. This can be used when the former would fail
* due to recursive macro expansion.
*/
#define metamacro_if_eq_recursive(A, B) metamacro_concat(metamacro_if_eq_recursive, A)(B)
/**
* Returns 1 if N is an even number, or 0 otherwise. N must be between zero and
* twenty, inclusive.
*
* For the purposes of this test, zero is considered even.
*/
#define metamacro_is_even(N) metamacro_at(N, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1)
/**
* Returns the logical NOT of B, which must be the number zero or one.
*/
#define metamacro_not(B) metamacro_at(B, 1, 0)
// IMPLEMENTATION DETAILS FOLLOW!
// Do not write code that depends on anything below this line.
#define metamacro_stringify_(VALUE) #VALUE
#define metamacro_concat_(A, B) A##B
#define metamacro_foreach_iter(INDEX, MACRO, ARG) MACRO(INDEX, ARG)
#define metamacro_head_(FIRST, ...) FIRST
#define metamacro_tail_(FIRST, ...) __VA_ARGS__
#define metamacro_consume_(...)
#define metamacro_expand_(...) __VA_ARGS__
// implemented from scratch so that metamacro_concat() doesn't end up nesting
#define metamacro_foreach_concat_iter(INDEX, BASE, ARG) metamacro_foreach_concat_iter_(BASE, ARG)
#define metamacro_foreach_concat_iter_(BASE, ARG) BASE##ARG
// metamacro_at expansions
#define metamacro_at0(...) metamacro_head(__VA_ARGS__)
#define metamacro_at1(_0, ...) metamacro_head(__VA_ARGS__)
#define metamacro_at2(_0, _1, ...) metamacro_head(__VA_ARGS__)
#define metamacro_at3(_0, _1, _2, ...) metamacro_head(__VA_ARGS__)
#define metamacro_at4(_0, _1, _2, _3, ...) metamacro_head(__VA_ARGS__)
#define metamacro_at5(_0, _1, _2, _3, _4, ...) metamacro_head(__VA_ARGS__)
#define metamacro_at6(_0, _1, _2, _3, _4, _5, ...) metamacro_head(__VA_ARGS__)
#define metamacro_at7(_0, _1, _2, _3, _4, _5, _6, ...) metamacro_head(__VA_ARGS__)
#define metamacro_at8(_0, _1, _2, _3, _4, _5, _6, _7, ...) metamacro_head(__VA_ARGS__)
#define metamacro_at9(_0, _1, _2, _3, _4, _5, _6, _7, _8, ...) metamacro_head(__VA_ARGS__)
#define metamacro_at10(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, ...) metamacro_head(__VA_ARGS__)
#define metamacro_at11(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, ...) metamacro_head(__VA_ARGS__)
#define metamacro_at12(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, ...) metamacro_head(__VA_ARGS__)
#define metamacro_at13(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, ...) metamacro_head(__VA_ARGS__)
#define metamacro_at14(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, ...) metamacro_head(__VA_ARGS__)
#define metamacro_at15(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, ...) metamacro_head(__VA_ARGS__)
#define metamacro_at16(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, ...) \
metamacro_head(__VA_ARGS__)
#define metamacro_at17(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, ...) \
metamacro_head(__VA_ARGS__)
#define metamacro_at18(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, ...) \
metamacro_head(__VA_ARGS__)
#define metamacro_at19(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, ...) \
metamacro_head(__VA_ARGS__)
#define metamacro_at20(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, ...) \
metamacro_head(__VA_ARGS__)
// metamacro_foreach_cxt expansions
#define metamacro_foreach_cxt0(MACRO, SEP, CONTEXT)
#define metamacro_foreach_cxt1(MACRO, SEP, CONTEXT, _0) MACRO(0, CONTEXT, _0)
#define metamacro_foreach_cxt2(MACRO, SEP, CONTEXT, _0, _1) \
metamacro_foreach_cxt1(MACRO, SEP, CONTEXT, _0) SEP MACRO(1, CONTEXT, _1)
#define metamacro_foreach_cxt3(MACRO, SEP, CONTEXT, _0, _1, _2) \
metamacro_foreach_cxt2(MACRO, SEP, CONTEXT, _0, _1) SEP MACRO(2, CONTEXT, _2)
#define metamacro_foreach_cxt4(MACRO, SEP, CONTEXT, _0, _1, _2, _3) \
metamacro_foreach_cxt3(MACRO, SEP, CONTEXT, _0, _1, _2) SEP MACRO(3, CONTEXT, _3)
#define metamacro_foreach_cxt5(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4) \
metamacro_foreach_cxt4(MACRO, SEP, CONTEXT, _0, _1, _2, _3) SEP MACRO(4, CONTEXT, _4)
#define metamacro_foreach_cxt6(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5) \
metamacro_foreach_cxt5(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4) SEP MACRO(5, CONTEXT, _5)
#define metamacro_foreach_cxt7(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6) \
metamacro_foreach_cxt6(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5) SEP MACRO(6, CONTEXT, _6)
#define metamacro_foreach_cxt8(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7) \
metamacro_foreach_cxt7(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6) SEP MACRO(7, CONTEXT, _7)
#define metamacro_foreach_cxt9(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8) \
metamacro_foreach_cxt8(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7) SEP MACRO(8, CONTEXT, _8)
#define metamacro_foreach_cxt10(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9) \
metamacro_foreach_cxt9(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8) SEP MACRO(9, CONTEXT, _9)
#define metamacro_foreach_cxt11(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10) \
metamacro_foreach_cxt10(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9) SEP MACRO(10, CONTEXT, _10)
#define metamacro_foreach_cxt12(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11) \
metamacro_foreach_cxt11(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10) \
SEP MACRO(11, CONTEXT, _11)
#define metamacro_foreach_cxt13(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12) \
metamacro_foreach_cxt12(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11) \
SEP MACRO(12, CONTEXT, _12)
#define metamacro_foreach_cxt14(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13) \
metamacro_foreach_cxt13(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12) \
SEP MACRO(13, CONTEXT, _13)
#define metamacro_foreach_cxt15(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14) \
metamacro_foreach_cxt14(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13) \
SEP MACRO(14, CONTEXT, _14)
#define metamacro_foreach_cxt16(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, \
_15) \
metamacro_foreach_cxt15(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14) \
SEP MACRO(15, CONTEXT, _15)
#define metamacro_foreach_cxt17(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, \
_15, _16) \
metamacro_foreach_cxt16(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15) \
SEP MACRO(16, CONTEXT, _16)
#define metamacro_foreach_cxt18(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, \
_15, _16, _17) \
metamacro_foreach_cxt17(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, \
_16) SEP MACRO(17, CONTEXT, _17)
#define metamacro_foreach_cxt19(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, \
_15, _16, _17, _18) \
metamacro_foreach_cxt18(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, \
_16, _17) SEP MACRO(18, CONTEXT, _18)
#define metamacro_foreach_cxt20(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, \
_15, _16, _17, _18, _19) \
metamacro_foreach_cxt19(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, \
_16, _17, _18) SEP MACRO(19, CONTEXT, _19)
// metamacro_foreach_cxt_recursive expansions
#define metamacro_foreach_cxt_recursive0(MACRO, SEP, CONTEXT)
#define metamacro_foreach_cxt_recursive1(MACRO, SEP, CONTEXT, _0) MACRO(0, CONTEXT, _0)
#define metamacro_foreach_cxt_recursive2(MACRO, SEP, CONTEXT, _0, _1) \
metamacro_foreach_cxt_recursive1(MACRO, SEP, CONTEXT, _0) SEP MACRO(1, CONTEXT, _1)
#define metamacro_foreach_cxt_recursive3(MACRO, SEP, CONTEXT, _0, _1, _2) \
metamacro_foreach_cxt_recursive2(MACRO, SEP, CONTEXT, _0, _1) SEP MACRO(2, CONTEXT, _2)
#define metamacro_foreach_cxt_recursive4(MACRO, SEP, CONTEXT, _0, _1, _2, _3) \
metamacro_foreach_cxt_recursive3(MACRO, SEP, CONTEXT, _0, _1, _2) SEP MACRO(3, CONTEXT, _3)
#define metamacro_foreach_cxt_recursive5(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4) \
metamacro_foreach_cxt_recursive4(MACRO, SEP, CONTEXT, _0, _1, _2, _3) SEP MACRO(4, CONTEXT, _4)
#define metamacro_foreach_cxt_recursive6(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5) \
metamacro_foreach_cxt_recursive5(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4) SEP MACRO(5, CONTEXT, _5)
#define metamacro_foreach_cxt_recursive7(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6) \
metamacro_foreach_cxt_recursive6(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5) SEP MACRO(6, CONTEXT, _6)
#define metamacro_foreach_cxt_recursive8(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7) \
metamacro_foreach_cxt_recursive7(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6) SEP MACRO(7, CONTEXT, _7)
#define metamacro_foreach_cxt_recursive9(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8) \
metamacro_foreach_cxt_recursive8(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7) SEP MACRO(8, CONTEXT, _8)
#define metamacro_foreach_cxt_recursive10(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9) \
metamacro_foreach_cxt_recursive9(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8) SEP MACRO(9, CONTEXT, _9)
#define metamacro_foreach_cxt_recursive11(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10) \
metamacro_foreach_cxt_recursive10(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9) \
SEP MACRO(10, CONTEXT, _10)
#define metamacro_foreach_cxt_recursive12(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11) \
metamacro_foreach_cxt_recursive11(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10) \
SEP MACRO(11, CONTEXT, _11)
#define metamacro_foreach_cxt_recursive13(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12) \
metamacro_foreach_cxt_recursive12(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11) \
SEP MACRO(12, CONTEXT, _12)
#define metamacro_foreach_cxt_recursive14(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, \
_13) \
metamacro_foreach_cxt_recursive13(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12) \
SEP MACRO(13, CONTEXT, _13)
#define metamacro_foreach_cxt_recursive15(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, \
_13, _14) \
metamacro_foreach_cxt_recursive14(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13) \
SEP MACRO(14, CONTEXT, _14)
#define metamacro_foreach_cxt_recursive16(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, \
_13, _14, _15) \
metamacro_foreach_cxt_recursive15(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, \
_14) SEP MACRO(15, CONTEXT, _15)
#define metamacro_foreach_cxt_recursive17(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, \
_13, _14, _15, _16) \
metamacro_foreach_cxt_recursive16(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, \
_14, _15) SEP MACRO(16, CONTEXT, _16)
#define metamacro_foreach_cxt_recursive18(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, \
_13, _14, _15, _16, _17) \
metamacro_foreach_cxt_recursive17(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, \
_14, _15, _16) SEP MACRO(17, CONTEXT, _17)
#define metamacro_foreach_cxt_recursive19(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, \
_13, _14, _15, _16, _17, _18) \
metamacro_foreach_cxt_recursive18(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, \
_14, _15, _16, _17) SEP MACRO(18, CONTEXT, _18)
#define metamacro_foreach_cxt_recursive20(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, \
_13, _14, _15, _16, _17, _18, _19) \
metamacro_foreach_cxt_recursive19(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, \
_14, _15, _16, _17, _18) SEP MACRO(19, CONTEXT, _19)
// metamacro_for_cxt expansions
#define metamacro_for_cxt0(MACRO, SEP, CONTEXT)
#define metamacro_for_cxt1(MACRO, SEP, CONTEXT) MACRO(0, CONTEXT)
#define metamacro_for_cxt2(MACRO, SEP, CONTEXT) metamacro_for_cxt1(MACRO, SEP, CONTEXT) SEP MACRO(1, CONTEXT)
#define metamacro_for_cxt3(MACRO, SEP, CONTEXT) metamacro_for_cxt2(MACRO, SEP, CONTEXT) SEP MACRO(2, CONTEXT)
#define metamacro_for_cxt4(MACRO, SEP, CONTEXT) metamacro_for_cxt3(MACRO, SEP, CONTEXT) SEP MACRO(3, CONTEXT)
#define metamacro_for_cxt5(MACRO, SEP, CONTEXT) metamacro_for_cxt4(MACRO, SEP, CONTEXT) SEP MACRO(4, CONTEXT)
#define metamacro_for_cxt6(MACRO, SEP, CONTEXT) metamacro_for_cxt5(MACRO, SEP, CONTEXT) SEP MACRO(5, CONTEXT)
#define metamacro_for_cxt7(MACRO, SEP, CONTEXT) metamacro_for_cxt6(MACRO, SEP, CONTEXT) SEP MACRO(6, CONTEXT)
#define metamacro_for_cxt8(MACRO, SEP, CONTEXT) metamacro_for_cxt7(MACRO, SEP, CONTEXT) SEP MACRO(7, CONTEXT)
#define metamacro_for_cxt9(MACRO, SEP, CONTEXT) metamacro_for_cxt8(MACRO, SEP, CONTEXT) SEP MACRO(8, CONTEXT)
#define metamacro_for_cxt10(MACRO, SEP, CONTEXT) metamacro_for_cxt9(MACRO, SEP, CONTEXT) SEP MACRO(9, CONTEXT)
#define metamacro_for_cxt11(MACRO, SEP, CONTEXT) metamacro_for_cxt10(MACRO, SEP, CONTEXT) SEP MACRO(10, CONTEXT)
#define metamacro_for_cxt12(MACRO, SEP, CONTEXT) metamacro_for_cxt11(MACRO, SEP, CONTEXT) SEP MACRO(11, CONTEXT)
#define metamacro_for_cxt13(MACRO, SEP, CONTEXT) metamacro_for_cxt12(MACRO, SEP, CONTEXT) SEP MACRO(12, CONTEXT)
#define metamacro_for_cxt14(MACRO, SEP, CONTEXT) metamacro_for_cxt13(MACRO, SEP, CONTEXT) SEP MACRO(13, CONTEXT)
#define metamacro_for_cxt15(MACRO, SEP, CONTEXT) metamacro_for_cxt14(MACRO, SEP, CONTEXT) SEP MACRO(14, CONTEXT)
#define metamacro_for_cxt16(MACRO, SEP, CONTEXT) metamacro_for_cxt15(MACRO, SEP, CONTEXT) SEP MACRO(15, CONTEXT)
#define metamacro_for_cxt17(MACRO, SEP, CONTEXT) metamacro_for_cxt16(MACRO, SEP, CONTEXT) SEP MACRO(16, CONTEXT)
#define metamacro_for_cxt18(MACRO, SEP, CONTEXT) metamacro_for_cxt17(MACRO, SEP, CONTEXT) SEP MACRO(17, CONTEXT)
#define metamacro_for_cxt19(MACRO, SEP, CONTEXT) metamacro_for_cxt18(MACRO, SEP, CONTEXT) SEP MACRO(18, CONTEXT)
#define metamacro_for_cxt20(MACRO, SEP, CONTEXT) metamacro_for_cxt19(MACRO, SEP, CONTEXT) SEP MACRO(19, CONTEXT)
// metamacro_if_eq expansions
#define metamacro_if_eq0(VALUE) metamacro_concat(metamacro_if_eq0_, VALUE)
#define metamacro_if_eq0_0(...) __VA_ARGS__ metamacro_consume_
#define metamacro_if_eq0_1(...) metamacro_expand_
#define metamacro_if_eq0_2(...) metamacro_expand_
#define metamacro_if_eq0_3(...) metamacro_expand_
#define metamacro_if_eq0_4(...) metamacro_expand_
#define metamacro_if_eq0_5(...) metamacro_expand_
#define metamacro_if_eq0_6(...) metamacro_expand_
#define metamacro_if_eq0_7(...) metamacro_expand_
#define metamacro_if_eq0_8(...) metamacro_expand_
#define metamacro_if_eq0_9(...) metamacro_expand_
#define metamacro_if_eq0_10(...) metamacro_expand_
#define metamacro_if_eq0_11(...) metamacro_expand_
#define metamacro_if_eq0_12(...) metamacro_expand_
#define metamacro_if_eq0_13(...) metamacro_expand_
#define metamacro_if_eq0_14(...) metamacro_expand_
#define metamacro_if_eq0_15(...) metamacro_expand_
#define metamacro_if_eq0_16(...) metamacro_expand_
#define metamacro_if_eq0_17(...) metamacro_expand_
#define metamacro_if_eq0_18(...) metamacro_expand_
#define metamacro_if_eq0_19(...) metamacro_expand_
#define metamacro_if_eq0_20(...) metamacro_expand_
#define metamacro_if_eq1(VALUE) metamacro_if_eq0(metamacro_dec(VALUE))
#define metamacro_if_eq2(VALUE) metamacro_if_eq1(metamacro_dec(VALUE))
#define metamacro_if_eq3(VALUE) metamacro_if_eq2(metamacro_dec(VALUE))
#define metamacro_if_eq4(VALUE) metamacro_if_eq3(metamacro_dec(VALUE))
#define metamacro_if_eq5(VALUE) metamacro_if_eq4(metamacro_dec(VALUE))
#define metamacro_if_eq6(VALUE) metamacro_if_eq5(metamacro_dec(VALUE))
#define metamacro_if_eq7(VALUE) metamacro_if_eq6(metamacro_dec(VALUE))
#define metamacro_if_eq8(VALUE) metamacro_if_eq7(metamacro_dec(VALUE))
#define metamacro_if_eq9(VALUE) metamacro_if_eq8(metamacro_dec(VALUE))
#define metamacro_if_eq10(VALUE) metamacro_if_eq9(metamacro_dec(VALUE))
#define metamacro_if_eq11(VALUE) metamacro_if_eq10(metamacro_dec(VALUE))
#define metamacro_if_eq12(VALUE) metamacro_if_eq11(metamacro_dec(VALUE))
#define metamacro_if_eq13(VALUE) metamacro_if_eq12(metamacro_dec(VALUE))
#define metamacro_if_eq14(VALUE) metamacro_if_eq13(metamacro_dec(VALUE))
#define metamacro_if_eq15(VALUE) metamacro_if_eq14(metamacro_dec(VALUE))
#define metamacro_if_eq16(VALUE) metamacro_if_eq15(metamacro_dec(VALUE))
#define metamacro_if_eq17(VALUE) metamacro_if_eq16(metamacro_dec(VALUE))
#define metamacro_if_eq18(VALUE) metamacro_if_eq17(metamacro_dec(VALUE))
#define metamacro_if_eq19(VALUE) metamacro_if_eq18(metamacro_dec(VALUE))
#define metamacro_if_eq20(VALUE) metamacro_if_eq19(metamacro_dec(VALUE))
// metamacro_if_eq_recursive expansions
#define metamacro_if_eq_recursive0(VALUE) metamacro_concat(metamacro_if_eq_recursive0_, VALUE)
#define metamacro_if_eq_recursive0_0(...) __VA_ARGS__ metamacro_consume_
#define metamacro_if_eq_recursive0_1(...) metamacro_expand_
#define metamacro_if_eq_recursive0_2(...) metamacro_expand_
#define metamacro_if_eq_recursive0_3(...) metamacro_expand_
#define metamacro_if_eq_recursive0_4(...) metamacro_expand_
#define metamacro_if_eq_recursive0_5(...) metamacro_expand_
#define metamacro_if_eq_recursive0_6(...) metamacro_expand_
#define metamacro_if_eq_recursive0_7(...) metamacro_expand_
#define metamacro_if_eq_recursive0_8(...) metamacro_expand_
#define metamacro_if_eq_recursive0_9(...) metamacro_expand_
#define metamacro_if_eq_recursive0_10(...) metamacro_expand_
#define metamacro_if_eq_recursive0_11(...) metamacro_expand_
#define metamacro_if_eq_recursive0_12(...) metamacro_expand_
#define metamacro_if_eq_recursive0_13(...) metamacro_expand_
#define metamacro_if_eq_recursive0_14(...) metamacro_expand_
#define metamacro_if_eq_recursive0_15(...) metamacro_expand_
#define metamacro_if_eq_recursive0_16(...) metamacro_expand_
#define metamacro_if_eq_recursive0_17(...) metamacro_expand_
#define metamacro_if_eq_recursive0_18(...) metamacro_expand_
#define metamacro_if_eq_recursive0_19(...) metamacro_expand_
#define metamacro_if_eq_recursive0_20(...) metamacro_expand_
#define metamacro_if_eq_recursive1(VALUE) metamacro_if_eq_recursive0(metamacro_dec(VALUE))
#define metamacro_if_eq_recursive2(VALUE) metamacro_if_eq_recursive1(metamacro_dec(VALUE))
#define metamacro_if_eq_recursive3(VALUE) metamacro_if_eq_recursive2(metamacro_dec(VALUE))
#define metamacro_if_eq_recursive4(VALUE) metamacro_if_eq_recursive3(metamacro_dec(VALUE))
#define metamacro_if_eq_recursive5(VALUE) metamacro_if_eq_recursive4(metamacro_dec(VALUE))
#define metamacro_if_eq_recursive6(VALUE) metamacro_if_eq_recursive5(metamacro_dec(VALUE))
#define metamacro_if_eq_recursive7(VALUE) metamacro_if_eq_recursive6(metamacro_dec(VALUE))
#define metamacro_if_eq_recursive8(VALUE) metamacro_if_eq_recursive7(metamacro_dec(VALUE))
#define metamacro_if_eq_recursive9(VALUE) metamacro_if_eq_recursive8(metamacro_dec(VALUE))
#define metamacro_if_eq_recursive10(VALUE) metamacro_if_eq_recursive9(metamacro_dec(VALUE))
#define metamacro_if_eq_recursive11(VALUE) metamacro_if_eq_recursive10(metamacro_dec(VALUE))
#define metamacro_if_eq_recursive12(VALUE) metamacro_if_eq_recursive11(metamacro_dec(VALUE))
#define metamacro_if_eq_recursive13(VALUE) metamacro_if_eq_recursive12(metamacro_dec(VALUE))
#define metamacro_if_eq_recursive14(VALUE) metamacro_if_eq_recursive13(metamacro_dec(VALUE))
#define metamacro_if_eq_recursive15(VALUE) metamacro_if_eq_recursive14(metamacro_dec(VALUE))
#define metamacro_if_eq_recursive16(VALUE) metamacro_if_eq_recursive15(metamacro_dec(VALUE))
#define metamacro_if_eq_recursive17(VALUE) metamacro_if_eq_recursive16(metamacro_dec(VALUE))
#define metamacro_if_eq_recursive18(VALUE) metamacro_if_eq_recursive17(metamacro_dec(VALUE))
#define metamacro_if_eq_recursive19(VALUE) metamacro_if_eq_recursive18(metamacro_dec(VALUE))
#define metamacro_if_eq_recursive20(VALUE) metamacro_if_eq_recursive19(metamacro_dec(VALUE))
// metamacro_take expansions
#define metamacro_take0(...)
#define metamacro_take1(...) metamacro_head(__VA_ARGS__)
#define metamacro_take2(...) metamacro_head(__VA_ARGS__), metamacro_take1(metamacro_tail(__VA_ARGS__))
#define metamacro_take3(...) metamacro_head(__VA_ARGS__), metamacro_take2(metamacro_tail(__VA_ARGS__))
#define metamacro_take4(...) metamacro_head(__VA_ARGS__), metamacro_take3(metamacro_tail(__VA_ARGS__))
#define metamacro_take5(...) metamacro_head(__VA_ARGS__), metamacro_take4(metamacro_tail(__VA_ARGS__))
#define metamacro_take6(...) metamacro_head(__VA_ARGS__), metamacro_take5(metamacro_tail(__VA_ARGS__))
#define metamacro_take7(...) metamacro_head(__VA_ARGS__), metamacro_take6(metamacro_tail(__VA_ARGS__))
#define metamacro_take8(...) metamacro_head(__VA_ARGS__), metamacro_take7(metamacro_tail(__VA_ARGS__))
#define metamacro_take9(...) metamacro_head(__VA_ARGS__), metamacro_take8(metamacro_tail(__VA_ARGS__))
#define metamacro_take10(...) metamacro_head(__VA_ARGS__), metamacro_take9(metamacro_tail(__VA_ARGS__))
#define metamacro_take11(...) metamacro_head(__VA_ARGS__), metamacro_take10(metamacro_tail(__VA_ARGS__))
#define metamacro_take12(...) metamacro_head(__VA_ARGS__), metamacro_take11(metamacro_tail(__VA_ARGS__))
#define metamacro_take13(...) metamacro_head(__VA_ARGS__), metamacro_take12(metamacro_tail(__VA_ARGS__))
#define metamacro_take14(...) metamacro_head(__VA_ARGS__), metamacro_take13(metamacro_tail(__VA_ARGS__))
#define metamacro_take15(...) metamacro_head(__VA_ARGS__), metamacro_take14(metamacro_tail(__VA_ARGS__))
#define metamacro_take16(...) metamacro_head(__VA_ARGS__), metamacro_take15(metamacro_tail(__VA_ARGS__))
#define metamacro_take17(...) metamacro_head(__VA_ARGS__), metamacro_take16(metamacro_tail(__VA_ARGS__))
#define metamacro_take18(...) metamacro_head(__VA_ARGS__), metamacro_take17(metamacro_tail(__VA_ARGS__))
#define metamacro_take19(...) metamacro_head(__VA_ARGS__), metamacro_take18(metamacro_tail(__VA_ARGS__))
#define metamacro_take20(...) metamacro_head(__VA_ARGS__), metamacro_take19(metamacro_tail(__VA_ARGS__))
// metamacro_drop expansions
#define metamacro_drop0(...) __VA_ARGS__
#define metamacro_drop1(...) metamacro_tail(__VA_ARGS__)
#define metamacro_drop2(...) metamacro_drop1(metamacro_tail(__VA_ARGS__))
#define metamacro_drop3(...) metamacro_drop2(metamacro_tail(__VA_ARGS__))
#define metamacro_drop4(...) metamacro_drop3(metamacro_tail(__VA_ARGS__))
#define metamacro_drop5(...) metamacro_drop4(metamacro_tail(__VA_ARGS__))
#define metamacro_drop6(...) metamacro_drop5(metamacro_tail(__VA_ARGS__))
#define metamacro_drop7(...) metamacro_drop6(metamacro_tail(__VA_ARGS__))
#define metamacro_drop8(...) metamacro_drop7(metamacro_tail(__VA_ARGS__))
#define metamacro_drop9(...) metamacro_drop8(metamacro_tail(__VA_ARGS__))
#define metamacro_drop10(...) metamacro_drop9(metamacro_tail(__VA_ARGS__))
#define metamacro_drop11(...) metamacro_drop10(metamacro_tail(__VA_ARGS__))
#define metamacro_drop12(...) metamacro_drop11(metamacro_tail(__VA_ARGS__))
#define metamacro_drop13(...) metamacro_drop12(metamacro_tail(__VA_ARGS__))
#define metamacro_drop14(...) metamacro_drop13(metamacro_tail(__VA_ARGS__))
#define metamacro_drop15(...) metamacro_drop14(metamacro_tail(__VA_ARGS__))
#define metamacro_drop16(...) metamacro_drop15(metamacro_tail(__VA_ARGS__))
#define metamacro_drop17(...) metamacro_drop16(metamacro_tail(__VA_ARGS__))
#define metamacro_drop18(...) metamacro_drop17(metamacro_tail(__VA_ARGS__))
#define metamacro_drop19(...) metamacro_drop18(metamacro_tail(__VA_ARGS__))
#define metamacro_drop20(...) metamacro_drop19(metamacro_tail(__VA_ARGS__))
#endif

View File

@@ -0,0 +1,15 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#ifndef UTILS_MODULE_CONFIG_HPP
#define UTILS_MODULE_CONFIG_HPP
#include <nlohmann/json.hpp>
#include <utils/mqtt_abstraction.hpp>
namespace Everest {
/// \brief get config from manager via mqtt
nlohmann::json get_module_config(std::shared_ptr<MQTTAbstraction> mqtt, const std::string& module_id);
} // namespace Everest
#endif // UTILS_MODULE_CONFIG_HPP

View File

@@ -0,0 +1,100 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#ifndef UTILS_MQTT_ABSTRACTION_HPP
#define UTILS_MQTT_ABSTRACTION_HPP
#include <future>
#include <nlohmann/json.hpp>
#include <utils/config/mqtt_settings.hpp>
#include <utils/types.hpp>
namespace Everest {
///
/// \brief Pure virtual interface for MQTT communication in EVerest modules.
///
/// Use MQTTAbstractionImpl for the real MQTT connection.
///
class MQTTAbstraction {
public:
// forbid copy assignment and copy construction
MQTTAbstraction(MQTTAbstraction const&) = delete;
void operator=(MQTTAbstraction const&) = delete;
virtual ~MQTTAbstraction() = default;
/// \brief connects to the mqtt broker
virtual bool connect() = 0;
/// \brief disconnects from the mqtt broker
virtual void disconnect() = 0;
/// \brief publishes the given \p json on the given \p topic with QOS level 0
virtual void publish(const std::string& topic, const nlohmann::json& json) = 0;
/// \brief publishes the given \p json on the given \p topic with the given \p qos
virtual void publish(const std::string& topic, const nlohmann::json& json, QOS qos, bool retain = false) = 0;
/// \brief publishes the given \p data on the given \p topic with QOS level 0
virtual void publish(const std::string& topic, const std::string& data) = 0;
/// \brief publishes the given \p data on the given \p topic with the given \p qos
virtual void publish(const std::string& topic, const std::string& data, QOS qos, bool retain = false) = 0;
/// \brief subscribes to the given \p topic with QOS level 0
virtual void subscribe(const std::string& topic) = 0;
/// \brief subscribes to the given \p topic with the given \p qos
virtual void subscribe(const std::string& topic, QOS qos) = 0;
/// \brief unsubscribes from the given \p topic
virtual void unsubscribe(const std::string& topic) = 0;
/// \brief clears any previously published topics that had the retain flag set
virtual void clear_retained_topics() = 0;
/// \brief Sends a get request on \p topic and waits for a JSON response, with the given \p qos and \p retries
virtual nlohmann::json get(const std::string& topic, QOS qos, std::size_t retries = 0) = 0;
/// \brief Sends an MQTT request and waits for a JSON response.
///
/// Registers a temporary handler for the response topic in \p request, publishes the request
/// message, and waits for the corresponding response. Throws EverestTimeoutError on timeout.
///
/// \param request The MQTT request containing the response topic, request topic, payload, QoS, and timeout.
/// \param retries How often the get should be retried on timeout, defaults to 0.
/// \return The JSON response received.
virtual nlohmann::json get(const MQTTRequest& request, std::size_t retries = 0) = 0;
/// \brief Get MQTT topic prefix for the "everest" topic
virtual const std::string& get_everest_prefix() const = 0;
/// \brief Get MQTT topic prefix for external topics
virtual const std::string& get_external_prefix() const = 0;
/// \brief Spawn a thread running the mqtt main loop
/// \returns a future, which will be fulfilled on thread termination
virtual std::shared_future<void> spawn_main_loop_thread() = 0;
/// \returns the main loop future, which will be fulfilled on thread termination
virtual std::shared_future<void> get_main_loop_future() = 0;
/// \brief subscribes to \p topic and registers a \p handler called when a message arrives, with the given \p qos
virtual void register_handler(const std::string& topic, std::shared_ptr<TypedHandler> handler, QOS qos) = 0;
/// \brief unsubscribes a handler identified by its \p token from the given \p topic
virtual void unregister_handler(const std::string& topic, const Token& token) = 0;
protected:
MQTTAbstraction() = default;
};
/// \brief Create a real MQTTAbstraction backed by the given \p mqtt_settings.
/// Use this instead of constructing MQTTAbstractionImpl directly.
std::unique_ptr<MQTTAbstraction> make_mqtt_abstraction(const MQTTSettings& mqtt_settings);
} // namespace Everest
#endif // UTILS_MQTT_ABSTRACTION_HPP

View File

@@ -0,0 +1,117 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#ifndef UTILS_MQTT_ABSTRACTION_IMPL_HPP
#define UTILS_MQTT_ABSTRACTION_IMPL_HPP
#include <cstddef>
#include <functional>
#include <future>
#include <map>
#include <memory>
#include <mutex>
#include <thread>
#include <vector>
#include <nlohmann/json.hpp>
#include <everest/io/event/fd_event_handler.hpp>
#include <everest/io/event/timer_fd.hpp>
#include <everest/io/mqtt/mqtt_client.hpp>
#include <everest/util/async/monitor.hpp>
#include <everest/util/queue/simple_queue.hpp>
#include <utils/config/mqtt_settings.hpp>
#include <utils/message_handler.hpp>
#include <utils/message_queue.hpp>
#include <utils/mqtt_abstraction.hpp>
#include <utils/thread.hpp>
#include <utils/types.hpp>
namespace Everest {
/// \brief Contains a payload and the topic it was received on with additional QOS
struct MessageWithQOS : Message {
QOS qos; ///< The Quality of Service level
bool retain; ///< If the retain flag should be set on publishing this message
MessageWithQOS(const std::string& topic, const std::string& payload, QOS qos, bool retain);
};
///
/// \brief Contains a C++ abstraction of MQTT-C and some convenience functionality for using MQTT in EVerest modules
///
class MQTTAbstractionImpl : public MQTTAbstraction {
public:
MQTTAbstractionImpl(const MQTTSettings& mqtt_settings);
~MQTTAbstractionImpl() override;
MQTTAbstractionImpl(MQTTAbstractionImpl const&) = delete;
void operator=(MQTTAbstractionImpl const&) = delete;
bool connect() override;
void disconnect() override;
void publish(const std::string& topic, const nlohmann::json& json) override;
void publish(const std::string& topic, const nlohmann::json& json, QOS qos, bool retain = false) override;
void publish(const std::string& topic, const std::string& data) override;
void publish(const std::string& topic, const std::string& data, QOS qos, bool retain = false) override;
void subscribe(const std::string& topic) override;
void subscribe(const std::string& topic, QOS qos) override;
void unsubscribe(const std::string& topic) override;
void clear_retained_topics() override;
nlohmann::json get(const MQTTRequest& request, std::size_t retries = 0) override;
nlohmann::json get(const std::string& topic, QOS qos, std::size_t retries = 0) override;
const std::string& get_everest_prefix() const override;
const std::string& get_external_prefix() const override;
std::shared_future<void> spawn_main_loop_thread() override;
std::shared_future<void> get_main_loop_future() override;
void register_handler(const std::string& topic, std::shared_ptr<TypedHandler> handler, QOS qos) override;
void unregister_handler(const std::string& topic, const Token& token) override;
///
/// \brief checks if the given \p full_topic matches the given \p wildcard_topic that can contain "+" and "#"
/// wildcards
///
/// \returns true if the topic matches, false otherwise
static bool check_topic_matches(const std::string& full_topic, const std::string& wildcard_topic);
private:
template <class T> using monitor = everest::lib::util::monitor<T>;
using shared_messages = std::vector<std::shared_ptr<MessageWithQOS>>;
struct Topics {
std::vector<std::string> retained_topics;
std::unordered_set<std::string> subscribed_topics;
};
std::atomic_bool mqtt_is_connected;
std::atomic_bool running;
MessageHandler message_handler;
everest::lib::util::simple_queue<Message> message_queue;
monitor<shared_messages> messages_before_connected;
monitor<Topics> managed_topics;
std::shared_future<void> main_loop_future;
std::string mqtt_server_socket_path;
std::string mqtt_server_address;
std::uint16_t mqtt_server_port = 0;
std::string mqtt_everest_prefix;
std::string mqtt_external_prefix;
std::unique_ptr<everest::lib::io::mqtt::mqtt_client> mqtt_client;
everest::lib::io::event::event_fd disconnect_event;
everest::lib::io::event::event_fd new_message_event;
everest::lib::io::event::fd_event_handler ev_handler;
// This must be destroyed first.
Thread mqtt_mainloop_thread;
void on_mqtt_message();
void on_mqtt_connect();
void handle_mqtt_message(const Message& message);
static void on_mqtt_disconnect();
nlohmann::json get_internal(const MQTTRequest& request);
};
} // namespace Everest
#endif // UTILS_MQTT_ABSTRACTION_IMPL_HPP

View File

@@ -0,0 +1,49 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2022 Pionix GmbH and Contributors to EVerest
#ifndef UTILS_SERIAL_HPP
#define UTILS_SERIAL_HPP
#include <cstddef>
#include <cstdio>
#include <stdint.h>
#include <string>
#include <termios.h>
#include <vector>
namespace Everest {
class Serial {
public:
Serial();
virtual ~Serial();
bool openDevice(const char* device, int baud);
virtual void run() = 0;
virtual std::string hexdump(const std::uint8_t* const msg, int msg_len) const;
virtual std::string hexdump(const std::vector<std::uint8_t>& msg) const;
protected:
int fd{-1};
int baud{0};
private:
// Serial interface
bool setSerialAttributes();
// COBS de-/encoder
void cobsDecodeReset();
void handlePacket(uint8_t* buf, int len);
void cobsDecode(uint8_t* buf, int len);
void cobsDecodeByte(uint8_t byte);
std::size_t cobsEncode(const void* data, std::size_t length, uint8_t* buffer);
uint8_t msg[2048];
uint8_t code;
uint8_t block;
uint8_t* decode;
uint32_t crc32(uint8_t* buf, int len);
};
} // namespace Everest
#endif // UTILS_CONFIG_HPP

View File

@@ -0,0 +1,37 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2023 Pionix GmbH and Contributors to EVerest
#ifndef STATUS_FIFO_HPP
#define STATUS_FIFO_HPP
#include <string>
namespace Everest {
class StatusFifo {
public:
// defined messages
static constexpr auto ALL_MODULES_STARTED = "ALL_MODULES_STARTED\n";
static constexpr auto WAITING_FOR_STANDALONE_MODULES = "WAITING_FOR_STANDALONE_MODULES\n";
static StatusFifo create_from_path(const std::string&);
void update(const std::string&);
StatusFifo(StatusFifo const&) = delete;
StatusFifo& operator=(StatusFifo const&) = delete;
// NOTE (aw): the move constructor could be implementented, but we don't need it for now
StatusFifo(StatusFifo&&) = delete;
StatusFifo& operator=(StatusFifo&&) = delete;
~StatusFifo();
private:
StatusFifo() = default;
explicit StatusFifo(int fd_) : fd(fd_), disabled(false), opened(true){};
int fd{-1};
bool disabled{true};
bool opened{false};
};
} // namespace Everest
#endif // STATUS_FIFO_HPP

View File

@@ -0,0 +1,26 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2021 Pionix GmbH and Contributors to EVerest
#ifndef UTILS_THREAD_HPP
#define UTILS_THREAD_HPP
#include <chrono>
#include <future>
#include <thread>
namespace Everest {
class Thread {
public:
~Thread();
void stop();
bool shouldExit();
Thread& operator=(std::thread&&);
private:
std::thread handle;
std::atomic_bool exit_signal{false};
};
} // namespace Everest
#endif // UTILS_THREAD_HPP

View File

@@ -0,0 +1,204 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#ifndef UTILS_TYPES_HPP
#define UTILS_TYPES_HPP
#include <cstddef>
#include <filesystem>
#include <fmt/core.h>
#include <map>
#include <optional>
#include <string>
#include <variant>
#include <vector>
#include <nlohmann/json.hpp>
#include <utils/config/types.hpp>
using json = nlohmann::json;
using Value = json;
using Parameters = json;
using Result = std::optional<json>;
using JsonCommand = std::function<json(json)>;
using Command = std::function<Result(const Parameters&)>;
using ArgumentType = std::vector<std::string>;
using Arguments = std::map<std::string, ArgumentType>;
using ReturnType = std::vector<std::string>;
using JsonCallback = std::function<void(json)>;
using ValueCallback = std::function<void(Value)>;
using ConfigMap = std::map<std::string, everest::config::ConfigEntry>;
using ModuleConfigs = std::map<std::string, ConfigMap>;
using Array = json::array_t;
using Object = json::object_t;
// TODO (aw): can we pass the handler arguments by const ref?
using Handler = std::function<void(const std::string&, json)>;
using StringHandler = std::function<void(std::string)>;
using StringPairHandler = std::function<void(const std::string& topic, const std::string& data)>;
enum class HandlerType {
Call,
Result,
SubscribeVar,
SubscribeError,
GetConfig,
GetConfigResponse,
ConfigRequest,
ModuleReady,
GlobalReady,
ExternalMQTT,
Unknown
};
struct TypedHandler {
std::string name;
std::string id;
HandlerType type;
std::shared_ptr<Handler> handler;
TypedHandler(const std::string& name_, const std::string& id_, HandlerType type_,
std::shared_ptr<Handler> handler_);
TypedHandler(const std::string& name_, HandlerType type_, std::shared_ptr<Handler> handler_);
TypedHandler(HandlerType type_, std::shared_ptr<Handler> handler_);
};
using Token = std::shared_ptr<TypedHandler>;
struct ModuleInfo {
struct Paths {
std::filesystem::path etc;
std::filesystem::path libexec;
std::filesystem::path share;
};
std::string name;
std::vector<std::string> authors;
std::string license;
std::string id;
Paths paths;
bool telemetry_enabled = false;
bool global_errors_enabled = false;
std::optional<Mapping> mapping;
};
enum class MqttMessageType {
Var, ///< Variable message
Cmd, ///< Command message
CmdResult, ///< Command result message
ExternalMQTT, ///< External MQTT message
RaiseError, ///< Raise error message
ClearError, ///< Clear error message
GetConfig, ///< Get config request
GetConfigResponse, ///< Get config response
Telemetry, ///< Telemetry message
Heartbeat, ///< Heartbeat message
ModuleReady, ///< Module ready message
GlobalReady ///< Global ready message
};
std::string mqtt_message_type_to_string(MqttMessageType type);
MqttMessageType string_to_mqtt_message_type(std::string_view str);
struct MqttMessagePayload {
MqttMessageType type; ///< The type of the MQTT message
json data; ///< The data of the MQTT message
MqttMessagePayload() = delete;
template <typename T, typename = typename std::enable_if<std::is_convertible<T, json>::value>::type>
MqttMessagePayload(MqttMessageType type_, T&& data_) : type(type_), data(std::forward<T>(data_)) {
}
};
/// \brief Contains everything that's needed to initialize a requirement in user code
struct RequirementInitializer {
Requirement requirement;
Fulfillment fulfillment;
std::optional<Mapping> mapping;
};
using RequirementInitialization = std::map<std::string, std::vector<RequirementInitializer>>;
struct ImplementationIdentifier {
ImplementationIdentifier(const std::string& module_id_, const std::string& implementation_id_,
std::optional<Mapping> mapping_ = std::nullopt);
std::string to_string() const;
std::string module_id;
std::string implementation_id;
std::optional<Mapping> mapping;
};
bool operator==(const ImplementationIdentifier& lhs, const ImplementationIdentifier& rhs);
bool operator!=(const ImplementationIdentifier& lhs, const ImplementationIdentifier& rhs);
NLOHMANN_JSON_NAMESPACE_BEGIN
template <> struct adl_serializer<Mapping> {
static void to_json(json& j, const Mapping& m);
static Mapping from_json(const json& j);
};
template <> struct adl_serializer<TelemetryConfig> {
static void to_json(json& j, const TelemetryConfig& t);
static TelemetryConfig from_json(const json& j);
};
template <> struct adl_serializer<ModuleTierMappings> {
static void to_json(json& j, const ModuleTierMappings& m);
static ModuleTierMappings from_json(const json& j);
};
template <> struct adl_serializer<Requirement> {
static void to_json(json& j, const Requirement& r);
static Requirement from_json(const json& j);
};
template <> struct adl_serializer<Fulfillment> {
static void to_json(json& j, const Fulfillment& f);
static Fulfillment from_json(const json& j);
};
template <> struct adl_serializer<MqttMessagePayload> {
static void to_json(json& j, const MqttMessagePayload& m);
static MqttMessagePayload from_json(const json& j);
};
NLOHMANN_JSON_NAMESPACE_END
#define EVCALLBACK(function) [](auto&& PH1) { function(std::forward<decltype(PH1)>(PH1)); }
namespace Everest {
inline constexpr int mqtt_get_config_timeout_ms = 5000;
inline constexpr std::size_t mqtt_get_config_retries = 1;
/// \brief Errors than can happen related to commands
enum class CmdErrorType {
MessageParsingError, ///< Parsing of the message to the command handler has failed
SchemaValidationError, ///< Schema validation of arguments or result has failed
HandlerException, ///< An exception was thrown during handling of the command in user code
CmdTimeout, ///< A timeout happened when calling the command (eg. when the callee doesn't respond to the caller)
Shutdown, ///< EVerest is shutting down during a command call
NotReady ///< EVerest / the callee is not yet ready but received a command call already from another module
};
struct CmdResultError {
CmdErrorType event;
std::string msg;
std::exception_ptr ex;
};
struct CmdResult {
std::optional<json> result;
std::optional<CmdResultError> error;
};
/// \brief MQTT Quality of service
enum class QOS {
QOS0, ///< At most once delivery
QOS1, ///< At least once delivery
QOS2 ///< Exactly once delivery
};
struct MQTTRequest {
std::string response_topic;
QOS qos = QOS::QOS2;
std::chrono::milliseconds timeout = std::chrono::milliseconds(mqtt_get_config_timeout_ms);
std::optional<std::string> request_topic;
std::optional<std::string> request_data;
};
} // namespace Everest
#endif // UTILS_TYPES_HPP