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,8 @@
__pycache__/
*.so
*.egg-info/
htmlcov/
.coverage

View File

@@ -0,0 +1,17 @@
ev_create_pip_install_targets(
PACKAGE_NAME
everestpy
DIST_DEPENDS
everestpy_ln_dist
LOCAL_DEPENDS
everestpy_ln_local
)
ev_create_python_wheel_targets(
PACKAGE_NAME
everestpy
DEPENDS
everestpy_ln_dist
)
add_subdirectory(src/everest)

View File

@@ -0,0 +1,9 @@
[build-system]
requires = [
"setuptools>=42",
"wheel"
]
build-backend = "setuptools.build_meta"
[tool.autopep8]
max_line_length = 120

View File

@@ -0,0 +1,31 @@
[metadata]
name = everestpy
version = attr: everest.framework.__version__
author = 'Kai-Uwe Hermann'
author_email = kh@pionix.de
description = everest framework python binding
long_description = file: README.rst
long_description_content_type = text/x-rst
url = https://github.com/EVerest/everest-framework
classifiers =
Programming Language :: Python :: 3
License :: OSI Approved :: Apache Software License
Operating System :: OS Independent
[options]
package_dir =
= src
# packages = find:
python_requires = >=3.7
[options.packages.find]
exclude =
tests*
[options.package_data]
* = *.pyi, *.so
[options.extras_require]
test =
coverage

View File

@@ -0,0 +1,25 @@
load("@pybind11_bazel//:build_defs.bzl", "pybind_extension")
load("@rules_python//python:py_library.bzl", "py_library")
load("//third-party/bazel/toolchains:defs.bzl", "CROSS_PYTHON_INCOMPATIBLE")
pybind_extension(
name = "everestpy",
srcs = glob(["everest/**/*.cpp", "everest/**/*.hpp"]),
deps = ["//lib/everest/framework:framework", "@pybind11_json//:pybind11_json"],
includes = ["everest"],
target_compatible_with = CROSS_PYTHON_INCOMPATIBLE,
visibility = [
"//visibility:public",
],
)
py_library(
name = "framework",
data = [":everestpy.so"],
srcs = glob(["everest/framework/**/*.py",]),
target_compatible_with = CROSS_PYTHON_INCOMPATIBLE,
visibility = [
"//visibility:public",
],
imports = ["."],
)

View File

@@ -0,0 +1,71 @@
find_package(Python3
REQUIRED
COMPONENTS
Development
)
if (DISABLE_EDM)
find_package(pybind11 REQUIRED)
find_package(pybind11_json REQUIRED)
endif()
# support overriding where the include files are to be found.
# Used for Yocto where the pybind11 libraries may have come from a cache and the
# full path is no longer the same
if (PYBIND11_INTERFACE_INCLUDE_DIRECTORIES)
set_target_properties(pybind11::pybind11 PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES ${PYBIND11_INTERFACE_INCLUDE_DIRECTORIES}
)
set_target_properties(pybind11_json PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES ${PYBIND11_INTERFACE_INCLUDE_DIRECTORIES}
)
endif()
pybind11_add_module(everestpy misc.cpp module.cpp everestpy.cpp)
target_compile_options(everestpy PRIVATE ${COMPILER_WARNING_OPTIONS})
target_link_libraries(everestpy
PRIVATE
everest::framework
everest::log
pybind11_json
fmt::fmt
)
set(EVERESTPY_LIB_INSTALL_DIR ${CMAKE_INSTALL_LIBDIR}/everest/everestpy/everest/framework)
set(EVERESTPY_DIST_INSTALL_PATH ${CMAKE_INSTALL_PREFIX}/${EVERESTPY_LIB_INSTALL_DIR}/$<TARGET_FILE_NAME:everestpy>)
add_custom_target(everestpy_ln_dist
COMMAND
test -f $$DESTDIR${EVERESTPY_DIST_INSTALL_PATH} || (echo "everestpy library not found, did you run the install target?" && false)
COMMAND
${CMAKE_COMMAND} -E create_symlink $$DESTDIR${EVERESTPY_DIST_INSTALL_PATH} $<TARGET_FILE_NAME:everestpy>
WORKING_DIRECTORY
${CMAKE_CURRENT_SOURCE_DIR}/framework
COMMENT
"Creating symlink to distributed everestpy"
)
add_custom_target(everestpy_ln_local
COMMAND
${CMAKE_COMMAND} -E create_symlink $<TARGET_FILE:everestpy> $<TARGET_FILE_NAME:everestpy>
WORKING_DIRECTORY
${CMAKE_CURRENT_SOURCE_DIR}/framework
DEPENDS
everestpy
COMMENT
"Creating symlink to build everestpy"
)
install(
TARGETS everestpy
LIBRARY
DESTINATION ${EVERESTPY_LIB_INSTALL_DIR}
)
install(
FILES
framework/__init__.py
DESTINATION ${EVERESTPY_LIB_INSTALL_DIR}
)

View File

@@ -0,0 +1,173 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2023 Pionix GmbH and Contributors to EVerest
#include <filesystem>
#include <fstream>
#include <cstdlib>
#include <pybind11/functional.h>
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
#include <pybind11/stl/filesystem.h>
#include <pybind11_json/pybind11_json.hpp>
#include "misc.hpp"
#include "module.hpp"
#include <utils/error.hpp>
#include <utils/error/error_factory.hpp>
#include <utils/error/error_state_monitor.hpp>
namespace py = pybind11;
PYBIND11_MODULE(everestpy, m) {
// FIXME (aw): add m.doc?
py::class_<RuntimeSession>(m, "RuntimeSession")
.def(py::init<>())
.def(py::init<const std::string&, const std::string&>());
py::class_<ModuleInfo::Paths>(m, "ModuleInfoPaths")
.def_readonly("etc", &ModuleInfo::Paths::etc)
.def_readonly("libexec", &ModuleInfo::Paths::libexec)
.def_readonly("share", &ModuleInfo::Paths::share);
py::class_<ModuleInfo>(m, "ModuleInfo")
.def_readonly("name", &ModuleInfo::name)
.def_readonly("authors", &ModuleInfo::authors)
.def_readonly("license", &ModuleInfo::license)
.def_readonly("id", &ModuleInfo::id)
.def_readonly("paths", &ModuleInfo::paths)
.def_readonly("telemetry_enabled", &ModuleInfo::telemetry_enabled);
auto error_submodule = m.def_submodule("error");
py::enum_<Everest::error::Severity>(error_submodule, "Severity")
.value("Low", Everest::error::Severity::Low)
.value("Medium", Everest::error::Severity::Medium)
.value("High", Everest::error::Severity::High)
.export_values();
py::class_<ImplementationIdentifier>(error_submodule, "ImplementationIdentifier")
.def(py::init<const std::string&, const std::string&, std::optional<Mapping>>())
.def_readwrite("module_id", &ImplementationIdentifier::module_id)
.def_readwrite("implementation_id", &ImplementationIdentifier::implementation_id)
.def_readwrite("mapping", &ImplementationIdentifier::mapping);
py::class_<Everest::error::UUID>(error_submodule, "UUID")
.def(py::init<>())
.def(py::init<const std::string&>())
.def_readwrite("uuid", &Everest::error::UUID::uuid);
py::enum_<Everest::error::State>(error_submodule, "State")
.value("Active", Everest::error::State::Active)
.value("ClearedByModule", Everest::error::State::ClearedByModule)
.value("ClearedByReboot", Everest::error::State::ClearedByReboot)
.export_values();
py::class_<Everest::error::Error>(error_submodule, "Error")
.def(py::init<>())
.def_readwrite("type", &Everest::error::Error::type)
.def_readwrite("sub_type", &Everest::error::Error::sub_type)
.def_readwrite("description", &Everest::error::Error::description)
.def_readwrite("message", &Everest::error::Error::message)
.def_readwrite("severity", &Everest::error::Error::severity)
.def_readwrite("origin", &Everest::error::Error::origin)
.def_readwrite("timestamp", &Everest::error::Error::timestamp)
.def_readwrite("uuid", &Everest::error::Error::uuid)
.def_readwrite("state", &Everest::error::Error::state);
py::class_<Everest::error::ErrorStateMonitor::StateCondition>(error_submodule, "ErrorStateCondition")
.def(py::init<std::string, std::string, bool>())
.def_readwrite("type", &Everest::error::ErrorStateMonitor::StateCondition::type)
.def_readwrite("sub_type", &Everest::error::ErrorStateMonitor::StateCondition::sub_type)
.def_readwrite("active", &Everest::error::ErrorStateMonitor::StateCondition::active);
py::class_<Everest::error::ErrorStateMonitor, std::shared_ptr<Everest::error::ErrorStateMonitor>>(
error_submodule, "ErrorStateMonitor")
.def("is_error_active", &Everest::error::ErrorStateMonitor::is_error_active)
.def("is_condition_satisfied", py::overload_cast<const Everest::error::ErrorStateMonitor::StateCondition&>(
&Everest::error::ErrorStateMonitor::is_condition_satisfied, py::const_))
.def("is_condition_satisfied",
py::overload_cast<const std::list<Everest::error::ErrorStateMonitor::StateCondition>&>(
&Everest::error::ErrorStateMonitor::is_condition_satisfied, py::const_));
py::class_<Everest::error::ErrorFactory, std::shared_ptr<Everest::error::ErrorFactory>>(error_submodule,
"ErrorFactory")
.def("create_error", py::overload_cast<>(&Everest::error::ErrorFactory::create_error, py::const_))
.def("create_error",
py::overload_cast<const Everest::error::ErrorType&, const Everest::error::ErrorSubType&,
const std::string&>(&Everest::error::ErrorFactory::create_error, py::const_))
.def("create_error", py::overload_cast<const Everest::error::ErrorType&, const Everest::error::ErrorSubType&,
const std::string&, Everest::error::Severity>(
&Everest::error::ErrorFactory::create_error, py::const_))
.def("create_error", py::overload_cast<const Everest::error::ErrorType&, const Everest::error::ErrorSubType&,
const std::string&, Everest::error::State>(
&Everest::error::ErrorFactory::create_error, py::const_))
.def("create_error", py::overload_cast<const Everest::error::ErrorType&, const Everest::error::ErrorSubType&,
const std::string&, Everest::error::Severity, Everest::error::State>(
&Everest::error::ErrorFactory::create_error, py::const_));
py::class_<Interface>(m, "Interface")
.def_readonly("variables", &Interface::variables)
.def_readonly("commands", &Interface::commands)
.def_readonly("errors", &Interface::errors);
py::class_<Fulfillment>(m, "Fulfillment")
.def_readonly("module_id", &Fulfillment::module_id)
.def_readonly("implementation_id", &Fulfillment::implementation_id);
py::class_<ModuleSetup::Configurations>(m, "ModuleSetupConfigurations")
.def_readonly("implementations", &ModuleSetup::Configurations::implementations)
.def_readonly("module", &ModuleSetup::Configurations::module);
py::class_<ModuleSetup>(m, "ModuleSetup")
.def_readonly("configs", &ModuleSetup::configs)
.def_readonly("connections", &ModuleSetup::connections);
py::class_<Module>(m, "Module")
.def(py::init<const RuntimeSession&>())
.def(py::init<const std::string&, const RuntimeSession&>())
.def("say_hello", &Module::say_hello)
.def("init_done", py::overload_cast<>(&Module::init_done))
.def("init_done", py::overload_cast<const std::function<void()>&>(&Module::init_done))
.def("call_command", &Module::call_command)
.def("publish_variable", &Module::publish_variable)
.def("implement_command", &Module::implement_command)
.def("subscribe_variable", &Module::subscribe_variable)
.def("raise_error", &Module::raise_error)
.def("clear_error",
py::overload_cast<const std::string&, const Everest::error::ErrorType&>(&Module::clear_error),
py::arg("impl_id"), py::arg("type"))
.def("clear_error",
py::overload_cast<const std::string&, const Everest::error::ErrorType&,
const Everest::error::ErrorSubType&>(&Module::clear_error),
py::arg("impl_id"), py::arg("type"), py::arg("sub_type"))
.def("clear_all_errors_of_impl", py::overload_cast<const std::string&>(&Module::clear_all_errors_of_impl),
py::arg("impl_id"))
.def("clear_all_errors_of_impl",
py::overload_cast<const std::string&, const Everest::error::ErrorType&>(&Module::clear_all_errors_of_impl),
py::arg("impl_id"), py::arg("type"))
.def("get_error_state_monitor_impl", &Module::get_error_state_monitor_impl)
.def("get_error_factory", &Module::get_error_factory)
.def("subscribe_error", &Module::subscribe_error)
.def("subscribe_all_errors", &Module::subscribe_all_errors)
.def("get_error_state_monitor_req", &Module::get_error_state_monitor_req)
.def_property_readonly("fulfillments", &Module::get_fulfillments)
.def_property_readonly("implementations", &Module::get_implementations)
.def_property_readonly("requirements", &Module::get_requirements)
.def_property_readonly("info", &Module::get_info);
auto log_submodule = m.def_submodule("log");
log_submodule.def("update_process_name",
[](const std::string& process_name) { Everest::Logging::update_process_name(process_name); });
log_submodule.def("verbose", [](const std::string& message) { EVLOG_verbose << message; });
log_submodule.def("debug", [](const std::string& message) { EVLOG_debug << message; });
log_submodule.def("info", [](const std::string& message) { EVLOG_info << message; });
log_submodule.def("warning", [](const std::string& message) { EVLOG_warning << message; });
log_submodule.def("error", [](const std::string& message) { EVLOG_error << message; });
log_submodule.def("critical", [](const std::string& message) { EVLOG_critical << message; });
m.attr("__version__") = "0.25.0";
}

View File

@@ -0,0 +1,6 @@
__version__ = '0.25.0'
try:
from .everestpy import *
except ImportError:
from everestpy import *

View File

@@ -0,0 +1,127 @@
from pathlib import Path
from typing import Callable, overload
from . import error
class ModuleInfoPaths:
@property
def etc(self) -> Path: ...
@property
def libexec(self) -> Path: ...
@property
def share(self) -> Path: ...
class Interface:
@property
def commands(self) -> list[str]: ...
@property
def variables(self) -> list[str]: ...
class ModuleInfo:
@property
def authors(self) -> list[str]: ...
@property
def license(self) -> str: ...
@property
def id(self) -> str: ...
@property
def name(self) -> str: ...
@property
def paths(self) -> ModuleInfoPaths: ...
class ModuleSetupConfigurations:
@property
def implementations(self) -> dict[str, dict]: ...
@property
def module(self) -> dict: ...
class ModuleSetup:
@property
def configs(self) -> ModuleSetupConfigurations: ...
@property
def connections(self) -> dict[str, list[Fulfillment]]: ...
class Fulfillment:
@property
def module_id(self) -> str: ...
@property
def implementation_id(self) -> str: ...
class RuntimeSession:
@overload
def __init__(self) -> None: ...
@overload
def __init__(self, prefix: str, config_file_path: str) -> None: ...
class Module:
@overload
def __init__(self, session: RuntimeSession) -> None: ...
@overload
def __init__(self, module_id: str, session: RuntimeSession) -> None: ...
def say_hello(self) -> ModuleSetup: ...
@overload
def init_done(self) -> None: ...
@overload
def init_done(self, on_ready_handler: Callable[[], None]) -> None: ...
def call_command(self, fulfillment: Fulfillment,
command_name: str, args: dict) -> None: ...
def publish_variable(self, implementation_id: str,
variable_name: str, value: dict) -> None: ...
def implement_command(self, implementation_id: str, command_name: str,
handler: Callable[[dict], dict]) -> None: ...
def subscribe_variable(self, fulfillment: Fulfillment,
variable_name: str, callback: Callable[[dict], None]) -> None: ...
def raise_error(self, implementation_id: str, error: error.Error) -> None: ...
def clear_error(self, implementation_id: str, type: str) -> None: ...
def clear_error(self, implementation_id: str, type: str, sub_type: str) -> None: ...
def clear_all_errors_of_impl(self, implementation_id: str) -> None: ...
def clear_all_errors_of_impl(self, implementation_id: str, type: str) -> None: ...
def get_error_state_monitor_impl(self, implementation_id: str) -> error.ErrorStateMonitor: ...
def get_error_factory(self, implementation_id: str) -> error.ErrorFactory: ...
def subscribe_error(
self,
fulfillment: Fulfillment,
error_type: str,
callback: error.ErrorCallback,
clear_callback: error.ErrorCallback
) -> None: ...
def subscribe_all_errors(
self,
fulfillment: Fulfillment,
callback: error.ErrorCallback,
clear_callback: error.ErrorCallback
) -> None: ...
def get_error_state_monitor_req(self, fulfillment: Fulfillment) -> error.ErrorStateMonitor: ...
@property
def requirements(self) -> dict[str, Interface]: ...
@property
def implementations(self) -> dict[str, Interface]: ...
@property
def info(self) -> ModuleInfo: ...

View File

@@ -0,0 +1,113 @@
from enum import Enum
from typing import Callable, overload, Optional
class Severity(Enum):
Low = "Low",
Medium = "Medium",
High = "High"
class Mapping:
@overload
def __init__(self, evse: int) -> None ...
@overload
def __init__(self, evse: int, connector: int) -> None ...
@property
def evse(self) -> int ...
@property
def connector(self) -> Optional[int] ...
class ImplementationIdentifier:
def __init__(self, module_id: str, implementation_id: str, mapping: Optional[Mapping]) -> None: ...
@property
def module_id(self) -> str: ...
@property
def implementation_id(self) -> str: ...
@property
def mapping(self) -> Optional[Mapping]: ...
class UUID:
@overload
def __init__(self) -> None: ...
@overload
def __init__(self, uuid: str) -> None: ...
class State(Enum):
Active = "Active",
ClearedByModule = "ClearedByModule",
ClearedByReboot = "ClearedByReboot"
class Error:
def __init__(self) -> None: ...
@property
def type(self) -> str: ...
@property
def sub_type(self) -> str: ...
@property
def description(self) -> str: ...
@property
def message(self) -> str: ...
@property
def severity(self) -> Severity: ...
@property
def origin(self) -> ImplementationIdentifier: ...
@property
def timestamp(self) -> int: ...
@property
def uuid(self) -> UUID: ...
@property
def state(self) -> State: ...
ErrorCallback = Callable[[Error], None]
class ErrorStateCondition:
def __init__(self, type: str, sub_type: str, active: bool) -> None: ...
@property
def type(self) -> str: ...
@property
def sub_type(self) -> str: ...
@property
def active(self) -> bool: ...
class ErrorStateMonitor:
def is_error_active(self, type: str, sub_type: str) -> bool: ...
@overload
def is_condition_satisfied(self, condition: ErrorStateCondition) -> bool: ...
@overload
def is_condition_satisfied(self, condition: list[ErrorStateCondition]) -> bool: ...
class ErrorFactory:
@overload
def create_error(self) -> Error: ...
@overload
def create_error(self, type: str, sub_type: str, message: str) -> Error: ...
@overload
def create_error(self, type: str, sub_type: str, message: str, severity: Severity) -> Error: ...
@overload
def create_error(self, type: str, sub_type: str, message: str, state: State) -> Error: ...
@overload
def create_error(self, type: str, sub_type: str, message: str, severity: Severity, state: State) -> Error: ...

View File

@@ -0,0 +1,9 @@
def verbose(message: str) -> None: ...
def debug(message: str) -> None: ...
def info(message: str) -> None: ...
def warning(message: str) -> None: ...
def error(message: str) -> None: ...
def critical(message: str) -> None: ...
def update_process_name(name: str) -> None: ...

View File

@@ -0,0 +1,164 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#include "misc.hpp"
#include <cstddef>
#include <cstdlib>
#include <stdexcept>
#include <utils/filesystem.hpp>
const std::string get_variable_from_env(const std::string& variable) {
const auto value = std::getenv(variable.c_str());
if (value == nullptr) {
throw std::runtime_error(variable + " needed for everestpy");
}
return value;
}
const std::string get_variable_from_env(const std::string& variable, const std::string& default_value) {
const auto value = std::getenv(variable.c_str());
if (value == nullptr) {
return default_value;
}
return value;
}
namespace {
Everest::MQTTSettings get_mqtt_settings_from_env() {
const auto mqtt_everest_prefix =
get_variable_from_env(Everest::EV_MQTT_EVEREST_PREFIX, Everest::defaults::MQTT_EVEREST_PREFIX);
const auto mqtt_external_prefix =
get_variable_from_env(Everest::EV_MQTT_EXTERNAL_PREFIX, Everest::defaults::MQTT_EXTERNAL_PREFIX);
const auto mqtt_broker_socket_path = std::getenv(Everest::EV_MQTT_BROKER_SOCKET_PATH);
const auto mqtt_broker_host = std::getenv(Everest::EV_MQTT_BROKER_HOST);
const auto mqtt_broker_port = std::getenv(Everest::EV_MQTT_BROKER_PORT);
if (mqtt_broker_socket_path == nullptr) {
if (mqtt_broker_host == nullptr or mqtt_broker_port == nullptr) {
throw std::runtime_error("If EV_MQTT_BROKER_SOCKET_PATH is not set EV_MQTT_BROKER_HOST and "
"EV_MQTT_BROKER_PORT are needed for everestpy");
}
auto mqtt_broker_port_ = Everest::defaults::MQTT_BROKER_PORT;
try {
mqtt_broker_port_ = std::stoul(mqtt_broker_port);
} catch (...) {
EVLOG_warning << "Could not parse MQTT broker port, using default: " << mqtt_broker_port_;
}
return Everest::create_mqtt_settings(mqtt_broker_host, mqtt_broker_port_, mqtt_everest_prefix,
mqtt_external_prefix);
} else {
return Everest::create_mqtt_settings(mqtt_broker_socket_path, mqtt_everest_prefix, mqtt_external_prefix);
}
}
} // namespace
/// This is just kept for compatibility
RuntimeSession::RuntimeSession(const std::string& prefix, const std::string& config_file) {
EVLOG_warning
<< "everestpy: Usage of the old RuntimeSession ctor detected, config should be loaded via MQTT not via "
"the provided config_file. For this please set the appropriate environment variables and call "
"RuntimeSession()";
// We extract the settings from the config file so everest-testing doesn't break
const auto ms = Everest::ManagerSettings(prefix, config_file);
Everest::Logging::init(ms.runtime_settings.logging_config_file.string());
this->mqtt_settings = ms.mqtt_settings;
}
RuntimeSession::RuntimeSession() {
const auto module_id = get_variable_from_env("EV_MODULE");
namespace fs = std::filesystem;
const fs::path logging_config_file =
Everest::assert_file(get_variable_from_env("EV_LOG_CONF_FILE"), "Default logging config");
Everest::Logging::init(logging_config_file.string(), module_id);
this->mqtt_settings = get_mqtt_settings_from_env();
}
ModuleSetup create_setup_from_config(const std::string& module_id, Everest::Config& config) {
ModuleSetup setup;
const std::string& module_name = config.get_module_name(module_id);
const auto& module_manifest = config.get_manifests().at(module_name);
// setup connections
for (const auto& requirement : module_manifest.at("requires").items()) {
const auto& requirement_id = requirement.key();
const auto fulfillments = config.resolve_requirement(module_id, requirement_id);
// Make sure we store the index information in our Fulfillment structures
std::vector<Fulfillment> indexed_fulfillments;
indexed_fulfillments.reserve(fulfillments.size());
for (size_t ii = 0; ii != fulfillments.size(); ++ii) {
Fulfillment indexed_fulfillment = fulfillments.at(ii);
indexed_fulfillment.requirement.index = ii;
EVLOG_verbose << "Setting up " << ii << " implementation_id=" << indexed_fulfillment.implementation_id
<< ", module_id=" << indexed_fulfillment.module_id;
indexed_fulfillments.emplace_back(indexed_fulfillment);
}
setup.connections[requirement_id] = indexed_fulfillments;
}
const auto& module_config = config.get_module_config();
for (const auto& [impl_id, configuration_parameters] : module_config.configuration_parameters) {
json json_config_map;
for (const auto& config_param : configuration_parameters) {
json_config_map[config_param.name] = config_param.value; // implicit conversion to json
}
if (impl_id == "!module") {
setup.configs.module = json_config_map;
continue;
}
setup.configs.implementations.emplace(impl_id, json_config_map);
}
return setup;
}
Interface create_everest_interface_from_definition(const json& def) {
Interface intf;
if (def.contains("cmds")) {
const auto& cmds = def.at("cmds");
intf.commands.reserve(cmds.size());
for (const auto& cmd : cmds.items()) {
intf.commands.push_back(cmd.key());
}
}
if (def.contains("vars")) {
const auto& vars = def.at("vars");
intf.variables.reserve(vars.size());
for (const auto& var : vars.items()) {
intf.variables.push_back(var.key());
}
}
if (def.contains("errors")) {
const auto& errors = def.at("errors");
std::size_t errors_size = 0;
for (const auto& error_namespace_it : errors.items()) {
errors_size += error_namespace_it.value().size();
}
intf.errors.reserve(errors_size);
for (const auto& error_namespace_it : errors.items()) {
for (const auto& error_name_it : error_namespace_it.value().items()) {
intf.errors.push_back(error_namespace_it.key() + "/" + error_name_it.key());
}
}
}
return intf;
}

View File

@@ -0,0 +1,51 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2023 Pionix GmbH and Contributors to EVerest
#ifndef EVERESTPY_MISC_HPP
#define EVERESTPY_MISC_HPP
#include <string>
#include <framework/runtime.hpp>
#include <utils/config/mqtt_settings.hpp>
#include <utils/config/types.hpp>
#include <utils/types.hpp>
const std::string get_variable_from_env(const std::string& variable);
const std::string get_variable_from_env(const std::string& variable, const std::string& default_value);
class RuntimeSession {
public:
RuntimeSession(const std::string& prefix, const std::string& config_file);
RuntimeSession();
const Everest::MQTTSettings& get_mqtt_settings() const {
return mqtt_settings;
}
private:
Everest::MQTTSettings mqtt_settings;
};
struct Interface {
std::vector<std::string> variables;
std::vector<std::string> commands;
std::vector<std::string> errors;
};
Interface create_everest_interface_from_definition(const json& def);
struct ModuleSetup {
struct Configurations {
std::map<std::string, json> implementations;
json module;
};
Configurations configs;
std::map<std::string, std::vector<Fulfillment>> connections;
};
ModuleSetup create_setup_from_config(const std::string& module_id, Everest::Config& config);
#endif // EVERESTPY_MISC_HPP

View File

@@ -0,0 +1,145 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2023 Pionix GmbH and Contributors to EVerest
#include "module.hpp"
#include <pybind11/pybind11.h>
#include <utils/error/error_factory.hpp>
#include <utils/error/error_manager_impl.hpp>
#include <utils/error/error_manager_req.hpp>
#include <utils/error/error_state_monitor.hpp>
std::unique_ptr<Everest::Everest>
Module::create_everest_instance(const std::string& module_id, const Everest::Config& config,
const Everest::RuntimeSettings& rs,
std::shared_ptr<Everest::MQTTAbstraction> mqtt_abstraction) {
return std::make_unique<Everest::Everest>(module_id, config, rs.validate_schema, mqtt_abstraction,
rs.telemetry_prefix, rs.telemetry_enabled, rs.forward_exceptions);
}
Module::Module(const RuntimeSession& session) : Module(get_variable_from_env("EV_MODULE"), session) {
}
Module::Module(const std::string& module_id_, const RuntimeSession& session_) :
module_id(module_id_), session(session_), start_time(std::chrono::steady_clock::now()) {
this->mqtt_abstraction =
std::shared_ptr<Everest::MQTTAbstraction>(Everest::make_mqtt_abstraction(session.get_mqtt_settings()));
this->mqtt_abstraction->connect();
this->mqtt_abstraction->spawn_main_loop_thread();
const auto result = Everest::get_module_config(this->mqtt_abstraction, module_id);
Everest::RuntimeSettings result_settings = result.at("settings");
this->rs = std::make_unique<Everest::RuntimeSettings>(std::move(result_settings));
this->config_ = std::make_unique<Everest::Config>(session.get_mqtt_settings(), result);
const auto& config = get_config();
this->handle = create_everest_instance(module_id, config, *this->rs, this->mqtt_abstraction);
// determine the fulfillments for our requirements
const auto& module_name = config.get_module_name(this->module_id);
const auto module_manifest = config.get_manifests().at(module_name);
// setup module info
module_info = config.get_module_info(module_id);
populate_module_info_path_from_runtime_settings(module_info, *this->rs);
// setup implementations
for (const auto& implementation : module_manifest.at("provides").items()) {
const auto& implementation_id = implementation.key();
const std::string interface_name = implementation.value().at("interface");
const auto& interface_def = config.get_interface_definition(interface_name);
implementations.emplace(implementation_id, create_everest_interface_from_definition(interface_def));
}
// setup requirements
for (const auto& requirement : module_manifest.at("requires").items()) {
const auto& requirement_id = requirement.key();
const std::string interface_name = requirement.value().at("interface");
const auto& interface_def = config.get_interface_definition(interface_name);
requirements.emplace(requirement_id, create_everest_interface_from_definition(interface_def));
}
}
ModuleSetup Module::say_hello() {
handle->connect();
handle->spawn_main_loop_thread();
return create_setup_from_config(module_id, get_config());
}
json Module::call_command(const Fulfillment& fulfillment, const std::string& cmd_name, json args) {
// FIXME (aw): we're releasing the GIL here, because the mqtt thread will want to aquire it when calling the
// callbacks
const pybind11::gil_scoped_release release;
const auto& result = handle->call_cmd(fulfillment.requirement, cmd_name, std::move(args));
return result;
}
void Module::publish_variable(const std::string& impl_id, const std::string& var_name, json value) {
// NOTE (aw): publish_var just sends output directly via mqtt, so we don't need to release here as opposed to
// call_command
handle->publish_var(impl_id, var_name, std::move(value));
}
void Module::implement_command(const std::string& impl_id, const std::string& cmd_name,
std::function<json(json)> command_handler) {
auto& handler = command_handlers.emplace_back(std::move(command_handler));
handle->provide_cmd(impl_id, cmd_name, [&handler](json args) { return handler(std::move(args)); });
}
void Module::subscribe_variable(const Fulfillment& fulfillment, const std::string& var_name,
std::function<void(json)> subscription_callback) {
auto& callback = subscription_callbacks.emplace_back(std::move(subscription_callback));
handle->subscribe_var(fulfillment.requirement, var_name, [&callback](json args) { callback(std::move(args)); });
}
void Module::raise_error(const std::string& impl_id, const Everest::error::Error& error) {
handle->get_error_manager_impl(impl_id)->raise_error(error);
}
void Module::clear_error(const std::string& impl_id, const Everest::error::ErrorType& type) {
handle->get_error_manager_impl(impl_id)->clear_error(type);
}
void Module::clear_error(const std::string& impl_id, const Everest::error::ErrorType& type,
const Everest::error::ErrorSubType& sub_type) {
handle->get_error_manager_impl(impl_id)->clear_error(type, sub_type);
}
void Module::clear_all_errors_of_impl(const std::string& impl_id) {
handle->get_error_manager_impl(impl_id)->clear_all_errors();
}
void Module::clear_all_errors_of_impl(const std::string& impl_id, const Everest::error::ErrorType& type) {
handle->get_error_manager_impl(impl_id)->clear_all_errors(type);
}
std::shared_ptr<Everest::error::ErrorStateMonitor>
Module::get_error_state_monitor_impl(const std::string& impl_id) const {
return handle->get_error_state_monitor_impl(impl_id);
}
std::shared_ptr<Everest::error::ErrorFactory> Module::get_error_factory(const std::string& impl_id) const {
return handle->get_error_factory(impl_id);
}
void Module::subscribe_error(const Fulfillment& fulfillment, const Everest::error::ErrorType& type,
const Everest::error::ErrorCallback& callback,
const Everest::error::ErrorCallback& clear_callback) {
handle->get_error_manager_req(fulfillment.requirement)->subscribe_error(type, callback, clear_callback);
}
void Module::subscribe_all_errors(const Fulfillment& fulfillment, const Everest::error::ErrorCallback& callback,
const Everest::error::ErrorCallback& clear_callback) {
handle->get_error_manager_req(fulfillment.requirement)->subscribe_all_errors(callback, clear_callback);
}
std::shared_ptr<Everest::error::ErrorStateMonitor>
Module::get_error_state_monitor_req(const Fulfillment& fulfillment) const {
return handle->get_error_state_monitor_req(fulfillment.requirement);
}

View File

@@ -0,0 +1,113 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2023 Pionix GmbH and Contributors to EVerest
#ifndef EVERESTPY_MODULE_HPP
#define EVERESTPY_MODULE_HPP
#include <chrono>
#include <deque>
#include <functional>
#include <map>
#include <memory>
#include <string>
#include <vector>
#include <framework/everest.hpp>
#include "misc.hpp"
class Module {
public:
Module(const RuntimeSession&);
Module(const std::string&, const RuntimeSession&);
ModuleSetup say_hello();
void init_done(const std::function<void()>& on_ready_handler) {
this->handle->check_code();
if (on_ready_handler) {
handle->register_on_ready_handler(on_ready_handler);
}
const auto end_time = std::chrono::steady_clock::now();
EVLOG_info << "Module " << fmt::format(Everest::TERMINAL_STYLE_BLUE, "{}", this->module_id) << " initialized ["
<< std::chrono::duration_cast<std::chrono::milliseconds>(end_time - this->start_time).count()
<< "ms]";
handle->signal_ready();
}
void init_done() {
init_done(nullptr);
}
Everest::Config& get_config() {
return *config_;
}
json call_command(const Fulfillment& fulfillment, const std::string& cmd_name, json args);
void publish_variable(const std::string& impl_id, const std::string& var_name, json value);
void implement_command(const std::string& impl_id, const std::string& cmd_name, std::function<json(json)> handler);
void subscribe_variable(const Fulfillment& fulfillment, const std::string& var_name,
std::function<void(json)> callback);
void raise_error(const std::string& impl_id, const Everest::error::Error& error);
void clear_error(const std::string& impl_id, const Everest::error::ErrorType& type);
void clear_error(const std::string& impl_id, const Everest::error::ErrorType& type,
const Everest::error::ErrorSubType& sub_type);
void clear_all_errors_of_impl(const std::string& impl_id);
void clear_all_errors_of_impl(const std::string& impl_id, const Everest::error::ErrorType& type);
std::shared_ptr<Everest::error::ErrorStateMonitor> get_error_state_monitor_impl(const std::string& impl_id) const;
std::shared_ptr<Everest::error::ErrorFactory> get_error_factory(const std::string& impl_id) const;
void subscribe_error(const Fulfillment& fulfillment, const Everest::error::ErrorType& type,
const Everest::error::ErrorCallback& callback,
const Everest::error::ErrorCallback& clear_callback);
void subscribe_all_errors(const Fulfillment& fulfillment, const Everest::error::ErrorCallback& callback,
const Everest::error::ErrorCallback& clear_callback);
std::shared_ptr<Everest::error::ErrorStateMonitor>
get_error_state_monitor_req(const Fulfillment& fulfillment) const;
const auto& get_fulfillments() const {
return fulfillments;
}
const auto& get_info() const {
return module_info;
}
const auto& get_requirements() const {
return requirements;
}
const auto& get_implementations() const {
return implementations;
}
private:
const std::string module_id;
const RuntimeSession& session;
const std::chrono::time_point<std::chrono::steady_clock> start_time;
std::unique_ptr<Everest::RuntimeSettings> rs;
std::shared_ptr<Everest::MQTTAbstraction> mqtt_abstraction;
std::unique_ptr<Everest::Config> config_;
std::unique_ptr<Everest::Everest> handle;
// NOTE (aw): we're keeping the handlers local to the module instance and don't pass them by copy-construction
// to "external" c/c++ code, so no GIL related problems should appear
std::deque<std::function<json(json)>> command_handlers{};
std::deque<std::function<void(json)>> subscription_callbacks{};
std::deque<std::function<void(json)>> err_susbcription_callbacks{};
std::deque<std::function<void(json)>> err_cleared_susbcription_callbacks{};
static std::unique_ptr<Everest::Everest>
create_everest_instance(const std::string& module_id, const Everest::Config& config,
const Everest::RuntimeSettings& rs,
std::shared_ptr<Everest::MQTTAbstraction> mqtt_abstraction);
ModuleInfo module_info{};
std::map<std::string, Interface> requirements;
std::map<std::string, Interface> implementations;
std::map<std::string, std::vector<Fulfillment>> fulfillments;
};
#endif // EVERESTPY_MODULE_HPP