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,36 @@
add_library(everest_api_module_helpers OBJECT)
add_library(everest::everest_api_module_helpers ALIAS everest_api_module_helpers)
ev_register_library_target(everest_api_module_helpers)
target_compile_options(everest_api_module_helpers
PUBLIC -Wall -Wextra -pedantic -Werror=switch)
target_include_directories(everest_api_module_helpers
PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
"$<TARGET_PROPERTY:generate_cpp_files,EVEREST_GENERATED_INCLUDE_DIR>"
)
target_sources(everest_api_module_helpers
PRIVATE
src/ApiHelper.cpp
src/ValidatingMqttProxy.cpp
)
add_dependencies(everest_api_module_helpers generate_cpp_files)
target_link_libraries(everest_api_module_helpers
PUBLIC
everest::everest_api_types
everest::framework
)
install(TARGETS everest_api_module_helpers
EXPORT everest_api_module_helpers-targets
LIBRARY
)
install(
DIRECTORY include/everest_api_module_helpers
DESTINATION include
)

View File

@@ -0,0 +1,95 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2026 Pionix GmbH and Contributors to EVerest
#pragma once
#include <atomic>
#include <cstddef>
#include <functional>
#include <map>
#include <memory>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
#include <everest/logging.hpp>
#include <utils/config_service.hpp>
#include <everest_api_module_helpers/ValidatingMqttProxy.hpp>
#include <everest_api_types/entrypoint/API.hpp>
#include <everest_api_types/generic/codec.hpp>
#include <everest_api_types/utilities/CommCheckHandler.hpp>
#include <everest_api_types/utilities/MqttProviderInterface.hpp>
#include <everest_api_types/utilities/Topics.hpp>
#include <everest_api_types/utilities/codec.hpp>
namespace everest::lib::API {
inline constexpr std::string_view bridge_connection_lost_message{"Bridge to implementation connection lost"};
class ApiHelper {
public:
using ParseAndPublishFtor = std::function<bool(std::string const&)>;
ApiHelper(const ModuleInfo& info_, Mqtt::MqttProviderInterface& mqtt_provider_,
std::map<std::string, size_t> implemented_apis_,
std::shared_ptr<Everest::config::ConfigServiceClient> config_service_client_) :
info(info_),
mqtt(mqtt_provider_),
implemented_apis(std::move(implemented_apis_)),
config_service_client(std::move(config_service_client_)) {
}
const ModuleInfo& info;
Mqtt::MqttProviderInterface& mqtt;
const Topics& get_topics() const;
// Initialization helpers (called from module::init())
void init(V1_0::types::entrypoint::CommunicationParameters const& comm_parameters);
void publish_ready_beacon();
// Topic subscription helpers
void subscribe_api_topic(std::string const& topic, ParseAndPublishFtor parse_and_publish);
void subscribe_entrypoint_var(std::string const& var, ParseAndPublishFtor parse_and_publish);
template <typename CommCheckT> void generate_api_var_communication_check(CommCheckT* comm_check) {
subscribe_api_topic("communication_check", [comm_check](std::string const& data) {
bool val = false;
if (deserialize(data, val)) {
comm_check->set_value(val);
return true;
}
return false;
});
}
template <typename CommCheckT> void setup_heartbeat_generator(CommCheckT* comm_check, int interval_ms) {
auto topic = topics.everest_to_extern("heartbeat");
auto action = [this, topic]() {
using V1_0::types::generic::serialize;
mqtt.publish(topic, serialize(hb_id++));
return true;
};
comm_check->heartbeat(interval_ms, action);
}
private:
void subscribe_mqtt_topic(std::string const& topic, ParseAndPublishFtor parse_and_publish);
void init_entrypoint_API(V1_0::types::entrypoint::CommunicationParameters const& comm_parameters);
void init_topics();
void generate_api_entrypoint_cmd_discover();
void generate_api_entrypoint_cmd_query_module();
Topics topics;
std::atomic<size_t> hb_id{0};
V1_0::types::entrypoint::ApiDiscoverResponse discover_response{};
std::map<std::string, V1_0::types::entrypoint::ApiDiscoverResponse> module_query_response{};
std::map<std::string, size_t> implemented_apis;
std::atomic<bool> responsible_for_sending_ready_beacon{false};
std::shared_ptr<Everest::config::ConfigServiceClient> config_service_client;
};
} // namespace everest::lib::API

View File

@@ -0,0 +1,31 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2026 Pionix GmbH and Contributors to EVerest
#pragma once
#include <functional>
#include <string>
#include <framework/ModuleAdapter.hpp>
#include <everest_api_types/utilities/MqttProviderInterface.hpp>
namespace everest::lib::API::Mqtt {
class ValidatingMqttProxy : public MqttProviderInterface {
public:
explicit ValidatingMqttProxy(Everest::MqttProvider& provider_);
void publish(const std::string& topic, const std::string& data) override;
void publish(const std::string& topic, bool data) override;
Everest::UnsubscribeToken subscribe(const std::string& topic,
std::function<void(std::string)> handler) const override;
private:
Everest::MqttProvider& provider;
[[nodiscard]] static bool is_topic_valid(std::string_view topic);
};
} // namespace everest::lib::API::Mqtt

View File

@@ -0,0 +1,139 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2026 Pionix GmbH and Contributors to EVerest
#include <everest_api_module_helpers/ApiHelper.hpp>
#include <algorithm>
#include <exception>
#include <sstream>
#include <string_view>
#include <thread>
#include <everest_api_types/entrypoint/codec.hpp>
#include <everest_api_types/generic/string.hpp>
#include <everest/logging.hpp>
#include <generated/version_information.hpp>
namespace {
inline bool ends_with(std::string_view str, std::string_view suffix) {
return str.size() >= suffix.size() && str.substr(str.size() - suffix.size()) == suffix;
}
} // namespace
namespace everest::lib::API {
void ApiHelper::init(V1_0::types::entrypoint::CommunicationParameters const& comm_parameters) {
init_entrypoint_API(comm_parameters);
init_topics();
generate_api_entrypoint_cmd_query_module();
generate_api_entrypoint_cmd_discover();
}
void ApiHelper::init_entrypoint_API(V1_0::types::entrypoint::CommunicationParameters const& comm_parameters) {
const auto& module_configs = config_service_client->get_module_configs();
const std::string api_module_type_ending = "_API";
std::vector<std::string> active_api_modules;
for (const auto& [id, _] : module_configs) {
// Heuristics for figuring out if this module is an API module: type ends with "_API"
if (ends_with(id.module_type, "_API")) {
active_api_modules.push_back(id.module_id);
}
}
std::sort(active_api_modules.begin(), active_api_modules.end());
if (active_api_modules.size() > 0 && info.id == active_api_modules[0]) {
// each module will figure out if it happens to be the one with the
// lowest-ordering alphabetical id if so, it shall be the one which sends
// out a ready beacon
responsible_for_sending_ready_beacon = true;
}
for (const auto& [api_type, version] : implemented_apis) {
V1_0::types::entrypoint::ApiParameter api_params;
api_params.type = api_type;
api_params.module_id = info.id;
api_params.version = version;
api_params.communication_monitoring = comm_parameters;
discover_response.apis.push_back(api_params);
if (module_query_response.count(api_type) == 0) {
module_query_response[api_type] = V1_0::types::entrypoint::ApiDiscoverResponse{};
}
module_query_response[api_type].apis.push_back(std::move(api_params));
}
}
void ApiHelper::init_topics() {
for (const auto& [api_type, version] : implemented_apis) {
topics.setup(info.id, api_type, version);
// Extend here to support multiple api implementations per module
break;
}
}
void ApiHelper::generate_api_entrypoint_cmd_discover() {
subscribe_entrypoint_var("discover", [this](std::string const& data) {
V1_0::types::generic::RequestReply msg;
if (deserialize(data, msg)) {
mqtt.publish(msg.replyTo, V1_0::types::entrypoint::serialize(discover_response));
return true;
}
return false;
});
}
void ApiHelper::generate_api_entrypoint_cmd_query_module() {
for (const auto& [api_type, _] : implemented_apis) {
std::stringstream topic;
topic << "query-modules/" << api_type;
subscribe_entrypoint_var(topic.str(), [api_type = api_type, this](std::string const& data) {
V1_0::types::generic::RequestReply msg;
if (deserialize(data, msg)) {
if (module_query_response.count(api_type) > 0) {
mqtt.publish(msg.replyTo, V1_0::types::entrypoint::serialize(module_query_response[api_type]));
}
return true;
}
return false;
});
}
}
void ApiHelper::publish_ready_beacon() {
if (responsible_for_sending_ready_beacon) {
mqtt.publish(topics.entrypoint("ready_beacon"), std::string{"{}"});
}
}
void ApiHelper::subscribe_entrypoint_var(std::string const& var, ParseAndPublishFtor parse_and_publish) {
subscribe_mqtt_topic(topics.entrypoint(var), std::move(parse_and_publish));
}
void ApiHelper::subscribe_api_topic(std::string const& topic, ParseAndPublishFtor parse_and_publish) {
subscribe_mqtt_topic(topics.extern_to_everest(topic), std::move(parse_and_publish));
}
void ApiHelper::subscribe_mqtt_topic(std::string const& topic, ParseAndPublishFtor parse_and_publish) {
mqtt.subscribe(topic, [topic = topic, f = std::move(parse_and_publish)](std::string const& data) {
try {
if (not f(data)) {
EVLOG_warning << "Invalid data: Deserialization failed.\n" << topic << "\n" << data;
}
} catch (const std::exception& e) {
EVLOG_warning << "Topic: '" << topic << "' failed with -> " << e.what() << "\n => " << data;
} catch (...) {
EVLOG_warning << "Invalid data: Failed to parse JSON or to get data from it.\n" << topic;
}
});
}
const Topics& ApiHelper::get_topics() const {
return topics;
}
} // namespace everest::lib::API

View File

@@ -0,0 +1,49 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2026 Pionix GmbH and Contributors to EVerest
#include <everest_api_module_helpers/ValidatingMqttProxy.hpp>
#include <cstddef>
#include <string_view>
#include <utility>
#include <everest/logging.hpp>
namespace everest::lib::API::Mqtt {
ValidatingMqttProxy::ValidatingMqttProxy(Everest::MqttProvider& provider_) : provider(provider_) {
}
void ValidatingMqttProxy::publish(const std::string& topic, const std::string& data) {
if (is_topic_valid(topic)) {
provider.publish(topic, data);
} else {
EVLOG_warning << "ValidatingMqttProxy: Droped '" << data << "' as topic '" << topic << "' invalid";
}
}
void ValidatingMqttProxy::publish(const std::string& topic, bool data) {
if (is_topic_valid(topic)) {
provider.publish(topic, data);
} else {
EVLOG_warning << "ValidatingMqttProxy: Droped '" << data << "' as topic '" << topic << "' invalid";
}
}
Everest::UnsubscribeToken ValidatingMqttProxy::subscribe(const std::string& topic,
std::function<void(std::string)> handler) const {
return provider.subscribe(topic, std::move(handler));
}
bool ValidatingMqttProxy::is_topic_valid(std::string_view topic) {
static constexpr std::size_t max_topic_length = 65535;
if (topic.empty() || topic.length() > max_topic_length) {
return false;
}
if (topic.find('\0') != std::string_view::npos || topic.find_first_of("+#") != std::string_view::npos) {
return false;
}
return true;
}
} // namespace everest::lib::API::Mqtt