- 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
1010 lines
43 KiB
C++
1010 lines
43 KiB
C++
// SPDX-License-Identifier: Apache-2.0
|
|
// Copyright 2020 - 2023 Pionix GmbH and Contributors to EVerest
|
|
#include <everest/exceptions.hpp>
|
|
#include <everest/logging.hpp>
|
|
|
|
#include <framework/everest.hpp>
|
|
#include <framework/runtime.hpp>
|
|
|
|
#include <napi.h>
|
|
|
|
#include <sys/prctl.h>
|
|
|
|
#include <chrono>
|
|
#include <cstddef>
|
|
#include <future>
|
|
#include <iostream>
|
|
#include <map>
|
|
#include <optional>
|
|
#include <thread>
|
|
#include <vector>
|
|
|
|
#include "conversions.hpp"
|
|
#include "js_exec_ctx.hpp"
|
|
#include "utils.hpp"
|
|
|
|
#include <utils/config/mqtt_settings.hpp>
|
|
#include <utils/config/settings.hpp>
|
|
#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>
|
|
#include <utils/filesystem.hpp>
|
|
#include <utils/module_config.hpp>
|
|
|
|
namespace EverestJs {
|
|
|
|
struct EvModCtx {
|
|
EvModCtx(std::unique_ptr<Everest::Everest> everest_, const Everest::json& module_manifest, const Napi::Env& env) :
|
|
everest(std::move(everest_)),
|
|
module_manifest(module_manifest),
|
|
framework_ready_deferred(Napi::Promise::Deferred::New(env)),
|
|
framework_ready_flag{false} {
|
|
framework_ready_promise = Napi::Persistent(framework_ready_deferred.Promise());
|
|
};
|
|
std::unique_ptr<Everest::Everest> everest;
|
|
const Everest::json module_manifest;
|
|
|
|
const Napi::Promise::Deferred framework_ready_deferred;
|
|
Napi::Reference<Napi::Promise> framework_ready_promise;
|
|
bool framework_ready_flag;
|
|
Napi::ObjectReference js_module_ref;
|
|
|
|
std::unique_ptr<JsExecCtx> js_cb;
|
|
|
|
std::map<std::pair<Requirement, std::string>, Napi::FunctionReference> var_subscriptions;
|
|
struct CallbackPair {
|
|
Napi::FunctionReference callback;
|
|
Napi::FunctionReference clear_callback;
|
|
};
|
|
std::map<std::pair<Requirement, std::string>, CallbackPair> error_subscriptions;
|
|
bool all_subscribed = false;
|
|
CallbackPair cb_pair_subscribe_all;
|
|
std::map<std::pair<std::string, std::string>, Napi::FunctionReference> cmd_handlers;
|
|
|
|
std::map<std::string, Napi::FunctionReference> mqtt_subscriptions;
|
|
};
|
|
|
|
// FIXME (aw): this could go into a class with singleton pattern
|
|
EvModCtx* ctx;
|
|
|
|
static Napi::Value publish_var(const std::string& impl_id, const std::string& var_name,
|
|
const Napi::CallbackInfo& info) {
|
|
BOOST_LOG_FUNCTION();
|
|
const auto& env = info.Env();
|
|
|
|
try {
|
|
ctx->everest->publish_var(impl_id, var_name, convertToJson(info[0]));
|
|
} catch (std::exception& e) {
|
|
EVLOG_AND_RETHROW(env);
|
|
}
|
|
|
|
return info.Env().Undefined();
|
|
}
|
|
|
|
static Napi::Value setup_cmd_handler(const std::string& impl_id, const std::string& cmd_name,
|
|
const Napi::CallbackInfo& info) {
|
|
BOOST_LOG_FUNCTION();
|
|
|
|
const auto& env = info.Env();
|
|
|
|
try {
|
|
const auto& handler = info[0].As<Napi::Function>();
|
|
|
|
auto cmd_key = std::make_pair(impl_id, cmd_name);
|
|
auto& cmd_handlers = ctx->cmd_handlers;
|
|
|
|
if (cmd_handlers.find(cmd_key) != cmd_handlers.end()) {
|
|
EVTHROW(EVEXCEPTION(Everest::EverestApiError, "Attaching more than one handler to ", impl_id, "->",
|
|
cmd_name, " is not yet supported!"));
|
|
}
|
|
|
|
cmd_handlers.insert({cmd_key, Napi::Persistent(handler)});
|
|
// FIXME (aw): in principle we could also pass this reference down to js_cb
|
|
|
|
ctx->everest->provide_cmd(impl_id, cmd_name, [cmd_key](Everest::json input) -> Everest::json {
|
|
Everest::json result;
|
|
|
|
ctx->js_cb->exec(
|
|
[&input, &cmd_key](Napi::Env& env) {
|
|
const auto& arg = convertToNapiValue(env, input);
|
|
std::vector<napi_value> args{ctx->cmd_handlers[cmd_key].Value(), ctx->js_module_ref.Value(), arg};
|
|
return args;
|
|
},
|
|
[&result, &cmd_key](const Napi::CallbackInfo& info, bool err) {
|
|
if (err) {
|
|
const std::string error_msg =
|
|
"Call into " + cmd_key.first + "->" + cmd_key.second + " got rejected";
|
|
throw Napi::Error::New(info.Env(), error_msg);
|
|
}
|
|
|
|
result = convertToJson(info[0]);
|
|
});
|
|
|
|
return result;
|
|
});
|
|
} catch (std::exception& e) {
|
|
EVLOG_AND_RETHROW(env);
|
|
}
|
|
|
|
return env.Undefined();
|
|
}
|
|
|
|
static Napi::Value set_var_subscription_handler(const Requirement& req, const std::string& var_name,
|
|
const Napi::CallbackInfo& info) {
|
|
BOOST_LOG_FUNCTION();
|
|
const auto& env = info.Env();
|
|
|
|
try {
|
|
const auto& handler = info[0].As<Napi::Function>();
|
|
|
|
auto sub_key = std::make_pair(req, var_name);
|
|
auto& var_subs = ctx->var_subscriptions;
|
|
|
|
if (var_subs.find(sub_key) != var_subs.end()) {
|
|
// FIXME (aw): error handling, if var is already subscribed
|
|
EVTHROW(EVEXCEPTION(Everest::EverestApiError, "Subscribing to ", req.id, "->", var_name,
|
|
" more than once is not yet supported!"));
|
|
}
|
|
var_subs.insert({sub_key, Napi::Persistent(handler)});
|
|
|
|
// FIXME (aw): in principle we could also pass this reference down to js_cb
|
|
ctx->everest->subscribe_var(req, var_name, [sub_key](Everest::json input) {
|
|
ctx->js_cb->exec(
|
|
[&input, &sub_key](Napi::Env& env) {
|
|
const auto& arg = convertToNapiValue(env, input);
|
|
std::vector<napi_value> args{ctx->var_subscriptions[sub_key].Value(), ctx->js_module_ref.Value(),
|
|
arg};
|
|
return args;
|
|
},
|
|
nullptr);
|
|
});
|
|
} catch (std::exception& e) {
|
|
EVLOG_AND_RETHROW(env);
|
|
}
|
|
|
|
return env.Undefined();
|
|
}
|
|
|
|
static Napi::Value signal_ready(const Napi::CallbackInfo& info) {
|
|
BOOST_LOG_FUNCTION();
|
|
|
|
const auto& env = info.Env();
|
|
Napi::Value retval;
|
|
|
|
try {
|
|
ctx->everest->signal_ready();
|
|
retval = ctx->framework_ready_promise.Value();
|
|
} catch (std::exception& e) {
|
|
EVLOG_AND_RETHROW(env);
|
|
}
|
|
|
|
return retval;
|
|
}
|
|
|
|
void framework_ready_handler() {
|
|
BOOST_LOG_FUNCTION();
|
|
// resolving the promise must be done inside the main js context!
|
|
auto handle_ready_js = [](const Napi::CallbackInfo& info) -> Napi::Value {
|
|
ctx->framework_ready_flag = true;
|
|
ctx->framework_ready_deferred.Resolve(ctx->js_module_ref.Value());
|
|
return info.Env().Undefined();
|
|
};
|
|
|
|
ctx->js_cb->exec(
|
|
[handle_ready_js](Napi::Env& env) {
|
|
std::vector<napi_value> args{Napi::Function::New(env, handle_ready_js)};
|
|
return args;
|
|
},
|
|
nullptr);
|
|
}
|
|
|
|
static Napi::Value mqtt_publish(const Napi::CallbackInfo& info) {
|
|
BOOST_LOG_FUNCTION();
|
|
|
|
const auto& env = info.Env();
|
|
try {
|
|
const auto& topic_alias = info[0].ToString().Utf8Value();
|
|
const auto& data = info[1].ToString().Utf8Value();
|
|
|
|
ctx->everest->external_mqtt_publish(topic_alias, data);
|
|
} catch (std::exception& e) {
|
|
EVLOG_AND_RETHROW(env);
|
|
}
|
|
|
|
return env.Undefined();
|
|
}
|
|
|
|
static Napi::Value mqtt_subscribe(const Napi::CallbackInfo& info) {
|
|
BOOST_LOG_FUNCTION();
|
|
|
|
const auto& env = info.Env();
|
|
|
|
try {
|
|
const auto& topic_alias = info[0].ToString().Utf8Value();
|
|
const auto& handler = info[1].As<Napi::Function>();
|
|
|
|
auto& mqtt_subs = ctx->mqtt_subscriptions;
|
|
|
|
if (mqtt_subs.find(topic_alias) != mqtt_subs.end()) {
|
|
EVTHROW(EVEXCEPTION(Everest::EverestApiError, "Subscribing to external mqtt topic alias '", topic_alias,
|
|
"' more than once is not yet supported!"));
|
|
}
|
|
|
|
ctx->mqtt_subscriptions.insert({topic_alias, Napi::Persistent(handler)});
|
|
|
|
ctx->everest->provide_external_mqtt_handler(topic_alias, [topic_alias](std::string data) {
|
|
ctx->js_cb->exec(
|
|
[&topic_alias, &data](Napi::Env& env) {
|
|
// in case we're not ready, the mod argument of the subscribe handler will be undefined, so no
|
|
// module related functions can be used
|
|
Napi::Value module_ref = (ctx->framework_ready_flag) ? ctx->js_module_ref.Value() : env.Undefined();
|
|
std::vector<napi_value> args = {ctx->mqtt_subscriptions[topic_alias].Value(), module_ref,
|
|
Napi::String::New(env, data)};
|
|
return args;
|
|
},
|
|
nullptr);
|
|
});
|
|
} catch (std::exception& e) {
|
|
EVLOG_AND_RETHROW(env);
|
|
}
|
|
|
|
return env.Undefined();
|
|
}
|
|
|
|
static Napi::Value telemetry_publish(const Napi::CallbackInfo& info) {
|
|
BOOST_LOG_FUNCTION();
|
|
|
|
const auto& env = info.Env();
|
|
try {
|
|
auto length = info.Length();
|
|
if (length == 3 || length == 4) {
|
|
const auto& category = info[0].ToString().Utf8Value();
|
|
const auto& subcategory = info[1].ToString().Utf8Value();
|
|
std::string type = subcategory;
|
|
if (length == 3) {
|
|
// assume it's category, subcategory, telemetry
|
|
auto telemetry = convertToTelemetryMap(info[2].As<Napi::Object>());
|
|
ctx->everest->telemetry_publish(category, subcategory, subcategory, telemetry);
|
|
} else if (length == 4) {
|
|
// assume it's category, subcategory, type, telemetry
|
|
auto telemetry = convertToTelemetryMap(info[3].As<Napi::Object>());
|
|
ctx->everest->telemetry_publish(category, subcategory, type, telemetry);
|
|
}
|
|
}
|
|
} catch (std::exception& e) {
|
|
EVLOG_AND_RETHROW(env);
|
|
}
|
|
|
|
return env.Undefined();
|
|
}
|
|
|
|
static Napi::Value call_cmd(const Requirement& req, const std::string& cmd_name, const Napi::CallbackInfo& info) {
|
|
BOOST_LOG_FUNCTION();
|
|
|
|
const auto& env = info.Env();
|
|
Napi::Value cmd_result;
|
|
|
|
try {
|
|
const auto& argument = convertToJson(info[0]);
|
|
const auto& retval = ctx->everest->call_cmd(req, cmd_name, argument);
|
|
|
|
cmd_result = convertToNapiValue(info.Env(), retval);
|
|
} catch (std::exception& e) {
|
|
EVLOG_AND_RETHROW(env);
|
|
}
|
|
|
|
return cmd_result;
|
|
}
|
|
|
|
static Napi::Value raise_error(const std::string& impl_id, const Napi::CallbackInfo& info) {
|
|
BOOST_LOG_FUNCTION();
|
|
|
|
const Napi::Env& env = info.Env();
|
|
|
|
try {
|
|
ctx->everest->get_error_manager_impl(impl_id)->raise_error(convertToError(info[0]));
|
|
} catch (std::exception& e) {
|
|
EVLOG_AND_RETHROW(env);
|
|
}
|
|
|
|
return env.Undefined();
|
|
}
|
|
|
|
static Napi::Value clear_error(const std::string& impl_id, const Napi::CallbackInfo& info) {
|
|
BOOST_LOG_FUNCTION();
|
|
|
|
const Napi::Env& env = info.Env();
|
|
try {
|
|
const int info_length = info.Length();
|
|
if (info_length == 1) {
|
|
ctx->everest->get_error_manager_impl(impl_id)->clear_error(convertToErrorType(info[0]));
|
|
} else if (info_length == 2) {
|
|
if (info[1].IsBoolean()) {
|
|
if (info[1].As<Napi::Boolean>()) {
|
|
ctx->everest->get_error_manager_impl(impl_id)->clear_all_errors(convertToErrorType(info[0]));
|
|
} else {
|
|
ctx->everest->get_error_manager_impl(impl_id)->clear_error(convertToErrorType(info[0]));
|
|
}
|
|
|
|
} else {
|
|
ctx->everest->get_error_manager_impl(impl_id)->clear_error(convertToErrorType(info[0]),
|
|
convertToErrorSubType(info[1]));
|
|
}
|
|
} else {
|
|
throw Everest::EverestApiError("There is no handler for " + std::to_string(info_length) + " arguments");
|
|
}
|
|
} catch (std::exception& e) {
|
|
EVLOG_AND_RETHROW(env);
|
|
}
|
|
|
|
return env.Undefined();
|
|
}
|
|
|
|
static Napi::Value clear_all_errors_of_impl(const std::string& impl_id, const Napi::CallbackInfo& info) {
|
|
BOOST_LOG_FUNCTION();
|
|
|
|
const Napi::Env& env = info.Env();
|
|
|
|
try {
|
|
ctx->everest->get_error_manager_impl(impl_id)->clear_all_errors();
|
|
} catch (std::exception& e) {
|
|
EVLOG_AND_RETHROW(env);
|
|
}
|
|
|
|
return env.Undefined();
|
|
}
|
|
|
|
static Napi::Value is_error_active_impl(const std::string& impl_id, const Napi::CallbackInfo& info) {
|
|
BOOST_LOG_FUNCTION();
|
|
|
|
const Napi::Env& env = info.Env();
|
|
|
|
bool result = false;
|
|
|
|
try {
|
|
result = ctx->everest->get_error_state_monitor_impl(impl_id)->is_error_active(convertToErrorType(info[0]),
|
|
convertToErrorSubType(info[1]));
|
|
} catch (std::exception& e) {
|
|
EVLOG_AND_RETHROW(env);
|
|
}
|
|
|
|
return Napi::Boolean::New(env, result);
|
|
}
|
|
|
|
static Napi::Value is_condition_satisfied_impl(const std::string& impl_id, const Napi::CallbackInfo& info) {
|
|
BOOST_LOG_FUNCTION();
|
|
|
|
const Napi::Env& env = info.Env();
|
|
|
|
bool result = false;
|
|
|
|
try {
|
|
if (isSingleErrorStateCondition(info[0])) {
|
|
result = ctx->everest->get_error_state_monitor_impl(impl_id)->is_condition_satisfied(
|
|
convertToErrorStateCondition(info[0]));
|
|
} else {
|
|
result = ctx->everest->get_error_state_monitor_impl(impl_id)->is_condition_satisfied(
|
|
convertToErrorStateConditionList(info[0]));
|
|
}
|
|
} catch (std::exception& e) {
|
|
EVLOG_AND_RETHROW(env);
|
|
}
|
|
|
|
return Napi::Boolean::New(env, result);
|
|
}
|
|
|
|
static Napi::Value create_error(const std::string& impl_id, const Napi::CallbackInfo& info) {
|
|
BOOST_LOG_FUNCTION();
|
|
|
|
const Napi::Env& env = info.Env();
|
|
|
|
try {
|
|
if (info.Length() == 0) {
|
|
return convertToNapiValue(env, ctx->everest->get_error_factory(impl_id)->create_error());
|
|
} else if (info.Length() == 3) {
|
|
return convertToNapiValue(
|
|
env, ctx->everest->get_error_factory(impl_id)->create_error(
|
|
convertToErrorType(info[0]), convertToErrorSubType(info[1]), info[2].ToString().Utf8Value()));
|
|
} else if (info.Length() == 4) {
|
|
return convertToNapiValue(env, ctx->everest->get_error_factory(impl_id)->create_error(
|
|
convertToErrorType(info[0]), convertToErrorSubType(info[1]),
|
|
info[2].ToString().Utf8Value(), convertToErrorSeverity(info[3])));
|
|
} else if (info.Length() == 5) {
|
|
return convertToNapiValue(env, ctx->everest->get_error_factory(impl_id)->create_error(
|
|
convertToErrorType(info[0]), convertToErrorSubType(info[1]),
|
|
info[2].ToString().Utf8Value(), convertToErrorSeverity(info[3]),
|
|
convertToErrorState(info[4])));
|
|
}
|
|
} catch (std::exception& e) {
|
|
EVLOG_AND_RETHROW(env);
|
|
}
|
|
|
|
return env.Undefined();
|
|
}
|
|
|
|
static Napi::Value subscribe_error(const Requirement& req, const Napi::CallbackInfo& info) {
|
|
BOOST_LOG_FUNCTION();
|
|
|
|
const Napi::Env& env = info.Env();
|
|
|
|
try {
|
|
std::string type = info[0].ToString().Utf8Value();
|
|
|
|
Napi::Function callback = info[1].As<Napi::Function>();
|
|
Napi::Function clear_callback = info[2].As<Napi::Function>();
|
|
|
|
auto& error_subs = ctx->error_subscriptions;
|
|
std::pair<Requirement, std::string> sub_key = std::make_pair(req, type);
|
|
if (ctx->all_subscribed || error_subs.find(sub_key) != error_subs.end()) {
|
|
EVTHROW(EVEXCEPTION(Everest::EverestApiError, "Subscribing to error ", req.id, "->", type,
|
|
" more than once is not yet supported!"));
|
|
}
|
|
|
|
error_subs[sub_key] = EvModCtx::CallbackPair();
|
|
error_subs[sub_key].callback = Napi::Persistent(callback);
|
|
error_subs[sub_key].clear_callback = Napi::Persistent(clear_callback);
|
|
|
|
ctx->everest->get_error_manager_req(req)->subscribe_error(
|
|
type,
|
|
[sub_key](const Everest::error::Error& error) {
|
|
ctx->js_cb->exec(
|
|
[&error, &sub_key](Napi::Env& env) {
|
|
const auto& arg = convertToNapiValue(env, error);
|
|
std::vector<napi_value> args{ctx->error_subscriptions[sub_key].callback.Value(),
|
|
ctx->js_module_ref.Value(), arg};
|
|
return args;
|
|
},
|
|
nullptr);
|
|
},
|
|
[sub_key](const Everest::error::Error& error) {
|
|
ctx->js_cb->exec(
|
|
[&error, &sub_key](Napi::Env& env) {
|
|
const auto& arg = convertToNapiValue(env, error);
|
|
std::vector<napi_value> args{ctx->error_subscriptions[sub_key].clear_callback.Value(),
|
|
ctx->js_module_ref.Value(), arg};
|
|
return args;
|
|
},
|
|
nullptr);
|
|
});
|
|
} catch (std::exception& e) {
|
|
EVLOG_AND_RETHROW(env);
|
|
}
|
|
|
|
return env.Undefined();
|
|
}
|
|
|
|
static Napi::Value subscribe_all_errors(const Requirement& req, const Napi::CallbackInfo& info) {
|
|
BOOST_LOG_FUNCTION();
|
|
|
|
const Napi::Env& env = info.Env();
|
|
|
|
try {
|
|
auto& error_subs = ctx->error_subscriptions;
|
|
|
|
if (ctx->all_subscribed || error_subs.size() > 0) {
|
|
EVTHROW(EVEXCEPTION(Everest::EverestApiError, "Subscribing to an error ", req.id,
|
|
" more than once is not yet supported!"));
|
|
}
|
|
ctx->all_subscribed = true;
|
|
|
|
ctx->cb_pair_subscribe_all.callback = Napi::Persistent(info[0].As<Napi::Function>());
|
|
ctx->cb_pair_subscribe_all.clear_callback = Napi::Persistent(info[1].As<Napi::Function>());
|
|
|
|
ctx->everest->get_error_manager_req(req)->subscribe_all_errors(
|
|
[](Everest::error::Error error) {
|
|
ctx->js_cb->exec(
|
|
[&error](Napi::Env& env) {
|
|
const auto& arg = convertToNapiValue(env, error);
|
|
std::vector<napi_value> args{ctx->cb_pair_subscribe_all.callback.Value(),
|
|
ctx->js_module_ref.Value(), arg};
|
|
return args;
|
|
},
|
|
nullptr);
|
|
},
|
|
[](Everest::error::Error error) {
|
|
ctx->js_cb->exec(
|
|
[&error](Napi::Env& env) {
|
|
const auto& arg = convertToNapiValue(env, error);
|
|
std::vector<napi_value> args{ctx->cb_pair_subscribe_all.clear_callback.Value(),
|
|
ctx->js_module_ref.Value(), arg};
|
|
return args;
|
|
},
|
|
nullptr);
|
|
});
|
|
} catch (std::exception& e) {
|
|
EVLOG_AND_RETHROW(env);
|
|
}
|
|
|
|
return env.Undefined();
|
|
}
|
|
|
|
static Napi::Value is_error_active_req(const Requirement& req, const Napi::CallbackInfo& info) {
|
|
BOOST_LOG_FUNCTION();
|
|
|
|
const Napi::Env& env = info.Env();
|
|
|
|
bool result = false;
|
|
|
|
try {
|
|
result = ctx->everest->get_error_state_monitor_req(req)->is_error_active(convertToErrorType(info[0]),
|
|
convertToErrorSubType(info[1]));
|
|
} catch (std::exception& e) {
|
|
EVLOG_AND_RETHROW(env);
|
|
}
|
|
|
|
return Napi::Boolean::New(env, result);
|
|
}
|
|
|
|
static Napi::Value is_condition_satisfied_req(const Requirement& req, const Napi::CallbackInfo& info) {
|
|
BOOST_LOG_FUNCTION();
|
|
|
|
const Napi::Env& env = info.Env();
|
|
|
|
bool result = false;
|
|
|
|
try {
|
|
if (isSingleErrorStateCondition(info[0])) {
|
|
result = ctx->everest->get_error_state_monitor_req(req)->is_condition_satisfied(
|
|
convertToErrorStateCondition(info[0]));
|
|
} else {
|
|
result = ctx->everest->get_error_state_monitor_req(req)->is_condition_satisfied(
|
|
convertToErrorStateConditionList(info[0]));
|
|
}
|
|
} catch (std::exception& e) {
|
|
EVLOG_AND_RETHROW(env);
|
|
}
|
|
|
|
return Napi::Boolean::New(env, result);
|
|
}
|
|
|
|
static Napi::Value boot_module(const Napi::CallbackInfo& info) {
|
|
BOOST_LOG_FUNCTION();
|
|
|
|
const auto start_time = std::chrono::steady_clock::now();
|
|
|
|
auto env = info.Env();
|
|
|
|
auto available_handlers_prop = Napi::Object::New(env);
|
|
|
|
try {
|
|
|
|
auto module_this = info.This().ToObject();
|
|
const Napi::Object& settings = info[0].ToObject();
|
|
const Napi::Function& callback_wrapper = info[1].As<Napi::Function>();
|
|
|
|
const auto& module_id = settings.Get("module").ToString().Utf8Value();
|
|
const auto& prefix = settings.Get("prefix").ToString().Utf8Value();
|
|
const auto& mqtt_everest_prefix = settings.Get("mqtt_everest_prefix").ToString().Utf8Value();
|
|
const auto& mqtt_external_prefix = settings.Get("mqtt_external_prefix").ToString().Utf8Value();
|
|
const auto& mqtt_broker_socket_path = settings.Get("mqtt_broker_socket_path").ToString().Utf8Value();
|
|
const auto& mqtt_server_address = settings.Get("mqtt_server_address").ToString().Utf8Value();
|
|
const auto& mqtt_server_port = settings.Get("mqtt_server_port").ToString().Utf8Value();
|
|
const bool validate_schema = settings.Get("validate_schema").ToBoolean().Value();
|
|
|
|
namespace fs = std::filesystem;
|
|
fs::path logging_config_file =
|
|
Everest::assert_file(settings.Get("logging_config_file").ToString().Utf8Value(), "Default logging config");
|
|
// initialize logging as early as possible
|
|
Everest::Logging::init(logging_config_file.string(), module_id);
|
|
std::shared_ptr<Everest::MQTTAbstraction> mqtt;
|
|
Everest::MQTTSettings mqtt_settings{};
|
|
if (mqtt_broker_socket_path.empty()) {
|
|
auto mqtt_broker_port = Everest::defaults::MQTT_BROKER_PORT;
|
|
try {
|
|
mqtt_broker_port = std::stoul(mqtt_server_port);
|
|
} catch (...) {
|
|
EVLOG_warning << "Could not parse MQTT broker port, using default: " << mqtt_broker_port;
|
|
}
|
|
|
|
Everest::populate_mqtt_settings(mqtt_settings, mqtt_server_address, mqtt_broker_port, mqtt_everest_prefix,
|
|
mqtt_external_prefix);
|
|
} else {
|
|
Everest::populate_mqtt_settings(mqtt_settings, mqtt_broker_socket_path, mqtt_everest_prefix,
|
|
mqtt_external_prefix);
|
|
}
|
|
|
|
mqtt = std::shared_ptr<Everest::MQTTAbstraction>(Everest::make_mqtt_abstraction(mqtt_settings));
|
|
mqtt->connect();
|
|
mqtt->spawn_main_loop_thread();
|
|
|
|
const auto result = Everest::get_module_config(mqtt, module_id);
|
|
|
|
Everest::RuntimeSettings result_settings = result.at("settings");
|
|
auto rs = std::make_unique<Everest::RuntimeSettings>(std::move(result_settings));
|
|
|
|
auto config = std::make_unique<Everest::Config>(mqtt_settings, result);
|
|
|
|
if (!config->contains(module_id)) {
|
|
EVTHROW(EVEXCEPTION(Everest::EverestConfigError,
|
|
"Module with identifier '" << module_id << "' not found in config!"));
|
|
}
|
|
|
|
const std::string& module_name = config->get_module_name(module_id);
|
|
const auto& module_manifest = config->get_manifests().at(module_name);
|
|
// FIXME (aw): get_classes should be called get_units and should contain the type of class for each unit
|
|
const auto& module_impls = config->get_interfaces().at(module_name);
|
|
|
|
// initialize everest framework
|
|
const auto& module_identifier = config->printable_identifier(module_id);
|
|
EVLOG_debug << "Initializing framework for module " << module_identifier << "...";
|
|
EVLOG_debug << "Trying to set process name to: '" << module_identifier << "'...";
|
|
if (prctl(PR_SET_NAME, module_identifier.c_str())) {
|
|
EVLOG_warning << "Could not set process name to '" << module_identifier << "'";
|
|
}
|
|
|
|
Everest::Logging::update_process_name(module_identifier);
|
|
|
|
//
|
|
// fill in everything we know about the module
|
|
//
|
|
|
|
// provides property: iterate over every implementation that this modules provides
|
|
auto provided_impls_prop = Napi::Object::New(env);
|
|
auto provided_cmds_prop = Napi::Object::New(env);
|
|
const auto& interface_definitions = config->get_interface_definitions();
|
|
for (const auto& impl_definition : module_impls.items()) {
|
|
const auto& impl_id = impl_definition.key();
|
|
const auto& interface_name = module_impls.at(impl_id).get<std::string>();
|
|
const auto& impl_intf = interface_definitions.at(interface_name);
|
|
|
|
auto impl_prop = Napi::Object::New(env);
|
|
|
|
// iterator over every variable, that the interface of this implementation provides
|
|
auto impl_vars_prop = Napi::Object::New(env);
|
|
if (impl_intf.contains("vars")) {
|
|
for (const auto& var_entry : impl_intf["vars"].items()) {
|
|
const auto& var_name = var_entry.key();
|
|
|
|
impl_vars_prop.DefineProperty(Napi::PropertyDescriptor::Value(
|
|
var_name,
|
|
Napi::Function::New(env,
|
|
[impl_id, var_name](const Napi::CallbackInfo& info) {
|
|
return publish_var(impl_id, var_name, info);
|
|
}),
|
|
napi_enumerable));
|
|
}
|
|
}
|
|
|
|
if (impl_vars_prop.GetPropertyNames().Length()) {
|
|
impl_prop.DefineProperty(Napi::PropertyDescriptor::Value("publish", impl_vars_prop, napi_enumerable));
|
|
}
|
|
|
|
auto cmd_register_prop = Napi::Object::New(env);
|
|
// iterate over every command, that the interface offers
|
|
if (impl_intf.contains("cmds")) {
|
|
for (const auto& cmd_entry : impl_intf["cmds"].items()) {
|
|
const auto& cmd_name = cmd_entry.key();
|
|
cmd_register_prop.DefineProperty(Napi::PropertyDescriptor::Value(
|
|
cmd_name,
|
|
Napi::Function::New(env,
|
|
[impl_id, cmd_name](const Napi::CallbackInfo& info) {
|
|
return setup_cmd_handler(impl_id, cmd_name, info);
|
|
}),
|
|
napi_enumerable));
|
|
}
|
|
}
|
|
|
|
if (cmd_register_prop.GetPropertyNames().Length()) {
|
|
auto impl_cmds_prop = Napi::Object::New(env);
|
|
impl_cmds_prop.DefineProperty(
|
|
Napi::PropertyDescriptor::Value("register", cmd_register_prop, napi_enumerable));
|
|
provided_cmds_prop.DefineProperty(
|
|
Napi::PropertyDescriptor::Value(impl_id, impl_cmds_prop, napi_enumerable));
|
|
}
|
|
|
|
impl_prop.DefineProperty(Napi::PropertyDescriptor::Value(
|
|
"raise_error",
|
|
Napi::Function::New(env,
|
|
[impl_id](const Napi::CallbackInfo& info) { return raise_error(impl_id, info); }),
|
|
napi_enumerable));
|
|
impl_prop.DefineProperty(Napi::PropertyDescriptor::Value(
|
|
"clear_error",
|
|
Napi::Function::New(env,
|
|
[impl_id](const Napi::CallbackInfo& info) { return clear_error(impl_id, info); }),
|
|
napi_enumerable));
|
|
impl_prop.DefineProperty(Napi::PropertyDescriptor::Value(
|
|
"clear_all_errors_of_impl",
|
|
Napi::Function::New(
|
|
env, [impl_id](const Napi::CallbackInfo& info) { return clear_all_errors_of_impl(impl_id, info); }),
|
|
napi_enumerable));
|
|
Napi::Object error_state_monitor_prop = Napi::Object::New(env);
|
|
error_state_monitor_prop.DefineProperty(Napi::PropertyDescriptor::Value(
|
|
"is_error_active",
|
|
Napi::Function::New(
|
|
env, [impl_id](const Napi::CallbackInfo& info) { return is_error_active_impl(impl_id, info); }),
|
|
napi_enumerable));
|
|
error_state_monitor_prop.DefineProperty(Napi::PropertyDescriptor::Value(
|
|
"is_condition_satisfied",
|
|
Napi::Function::New(
|
|
env,
|
|
[impl_id](const Napi::CallbackInfo& info) { return is_condition_satisfied_impl(impl_id, info); }),
|
|
napi_enumerable));
|
|
impl_prop.DefineProperty(
|
|
Napi::PropertyDescriptor::Value("error_state_monitor", error_state_monitor_prop, napi_enumerable));
|
|
Napi::Object error_factory_prop = Napi::Object::New(env);
|
|
error_factory_prop.DefineProperty(Napi::PropertyDescriptor::Value(
|
|
"create_error",
|
|
Napi::Function::New(env,
|
|
[impl_id](const Napi::CallbackInfo& info) { return create_error(impl_id, info); }),
|
|
napi_enumerable));
|
|
impl_prop.DefineProperty(
|
|
Napi::PropertyDescriptor::Value("error_factory", error_factory_prop, napi_enumerable));
|
|
provided_impls_prop.DefineProperty(Napi::PropertyDescriptor::Value(impl_id, impl_prop, napi_enumerable));
|
|
}
|
|
module_this.DefineProperty(Napi::PropertyDescriptor::Value("provides", provided_impls_prop, napi_enumerable));
|
|
available_handlers_prop.DefineProperty(
|
|
Napi::PropertyDescriptor::Value("provides", provided_cmds_prop, napi_enumerable));
|
|
|
|
// uses[_optional] property
|
|
auto uses_reqs_prop = Napi::Object::New(env);
|
|
auto uses_list_reqs_prop = Napi::Object::New(env);
|
|
auto uses_cmds_prop = Napi::Object::New(env);
|
|
auto uses_list_cmds_prop = Napi::Object::New(env);
|
|
for (const auto& requirement : module_manifest.at("requires").items()) {
|
|
auto req_prop = Napi::Object::New(env);
|
|
const auto& requirement_id = requirement.key();
|
|
const auto fulfillments = config->resolve_requirement(module_id, requirement_id);
|
|
auto req_array_prop = Napi::Array::New(env);
|
|
auto req_mod_cmds_array = Napi::Array::New(env);
|
|
for (std::size_t i = 0; i < fulfillments.size(); i++) {
|
|
const auto& fulfillment = fulfillments.at(i);
|
|
const std::string& requirement_module_id = fulfillment.module_id;
|
|
const std::string& requirement_impl_id = fulfillment.implementation_id;
|
|
// FIXME (aw): why is const auto& not possible for the following line?
|
|
// we only want cmds/vars from the required interface to be usable, not from it's child interfaces
|
|
const std::string& interface_name = requirement.value().at("interface");
|
|
const auto& requirement_impl_intf = config->get_interface_definition(interface_name);
|
|
auto requirement_vars = Everest::Config::keys(requirement_impl_intf["vars"]);
|
|
auto requirement_cmds = Everest::Config::keys(requirement_impl_intf["cmds"]);
|
|
|
|
auto var_subscribe_prop = Napi::Object::New(env);
|
|
for (auto const& var_name : requirement_vars) {
|
|
var_subscribe_prop.DefineProperty(Napi::PropertyDescriptor::Value(
|
|
var_name,
|
|
Napi::Function::New(
|
|
env,
|
|
[requirement_id, i, var_name](const Napi::CallbackInfo& info) {
|
|
return set_var_subscription_handler({requirement_id, i}, var_name, info);
|
|
}),
|
|
napi_enumerable));
|
|
}
|
|
|
|
if (requirement_vars.size()) {
|
|
req_prop.DefineProperty(
|
|
Napi::PropertyDescriptor::Value("subscribe", var_subscribe_prop, napi_enumerable));
|
|
}
|
|
|
|
auto cmd_prop = Napi::Object::New(env);
|
|
for (auto const& cmd_name : requirement_cmds) {
|
|
cmd_prop.DefineProperty(Napi::PropertyDescriptor::Value(
|
|
cmd_name,
|
|
Napi::Function::New(env,
|
|
[requirement_id, i, cmd_name](const Napi::CallbackInfo& info) {
|
|
return call_cmd({requirement_id, i}, cmd_name, info);
|
|
}),
|
|
napi_enumerable));
|
|
}
|
|
|
|
auto req_mod_cmds_prop = Napi::Object::New(env);
|
|
req_mod_cmds_prop.DefineProperty(Napi::PropertyDescriptor::Value("call", cmd_prop, napi_enumerable));
|
|
|
|
if (requirement_cmds.size()) {
|
|
req_mod_cmds_array.Set(i, req_mod_cmds_prop);
|
|
}
|
|
|
|
req_prop.DefineProperty(Napi::PropertyDescriptor::Value(
|
|
"subscribe_error",
|
|
Napi::Function::New(env,
|
|
[requirement_id, i](const Napi::CallbackInfo& info) {
|
|
return subscribe_error({requirement_id, i}, info);
|
|
}),
|
|
napi_enumerable));
|
|
req_prop.DefineProperty(Napi::PropertyDescriptor::Value(
|
|
"subscribe_all_errors",
|
|
Napi::Function::New(env,
|
|
[requirement_id, i](const Napi::CallbackInfo& info) {
|
|
return subscribe_all_errors({requirement_id, i}, info);
|
|
}),
|
|
napi_enumerable));
|
|
Napi::Object error_state_monitor_prop = Napi::Object::New(env);
|
|
error_state_monitor_prop.DefineProperty(Napi::PropertyDescriptor::Value(
|
|
"is_error_active",
|
|
Napi::Function::New(env,
|
|
[requirement_id, i](const Napi::CallbackInfo& info) {
|
|
return is_error_active_req({requirement_id, i}, info);
|
|
}),
|
|
napi_enumerable));
|
|
error_state_monitor_prop.DefineProperty(Napi::PropertyDescriptor::Value(
|
|
"is_condition_satisfied",
|
|
Napi::Function::New(env,
|
|
[requirement_id, i](const Napi::CallbackInfo& info) {
|
|
return is_condition_satisfied_req({requirement_id, i}, info);
|
|
}),
|
|
napi_enumerable));
|
|
req_prop.DefineProperty(
|
|
Napi::PropertyDescriptor::Value("error_state_monitor", error_state_monitor_prop, napi_enumerable));
|
|
|
|
req_array_prop.Set(i, req_prop);
|
|
}
|
|
uses_list_reqs_prop.DefineProperty(
|
|
Napi::PropertyDescriptor::Value(requirement_id, req_array_prop, napi_enumerable));
|
|
uses_list_cmds_prop.DefineProperty(
|
|
Napi::PropertyDescriptor::Value(requirement_id, req_mod_cmds_array, napi_enumerable));
|
|
}
|
|
|
|
if (uses_reqs_prop.GetPropertyNames().Length())
|
|
available_handlers_prop.DefineProperty(
|
|
Napi::PropertyDescriptor::Value("uses", uses_reqs_prop, napi_enumerable));
|
|
if (uses_list_reqs_prop.GetPropertyNames().Length())
|
|
available_handlers_prop.DefineProperty(
|
|
Napi::PropertyDescriptor::Value("uses_list", uses_list_reqs_prop, napi_enumerable));
|
|
|
|
if (uses_cmds_prop.GetPropertyNames().Length())
|
|
module_this.DefineProperty(Napi::PropertyDescriptor::Value("uses", uses_cmds_prop, napi_enumerable));
|
|
if (uses_list_cmds_prop.GetPropertyNames().Length())
|
|
module_this.DefineProperty(
|
|
Napi::PropertyDescriptor::Value("uses_list", uses_list_cmds_prop, napi_enumerable));
|
|
|
|
// external mqtt property
|
|
if (module_manifest.contains("enable_external_mqtt") && module_manifest["enable_external_mqtt"] == true) {
|
|
auto mqtt_prop = Napi::Object::New(env);
|
|
mqtt_prop.DefineProperty(
|
|
Napi::PropertyDescriptor::Value("publish", Napi::Function::New(env, mqtt_publish), napi_enumerable));
|
|
|
|
mqtt_prop.DefineProperty(Napi::PropertyDescriptor::Value(
|
|
"subscribe", Napi::Function::New(env, mqtt_subscribe), napi_enumerable));
|
|
|
|
module_this.DefineProperty(Napi::PropertyDescriptor::Value("mqtt", mqtt_prop, napi_enumerable));
|
|
}
|
|
|
|
// telemetry property
|
|
if (module_manifest.contains("enable_telemetry") && module_manifest["enable_telemetry"] == true) {
|
|
auto telemetry_prop = Napi::Object::New(env);
|
|
telemetry_prop.DefineProperty(Napi::PropertyDescriptor::Value(
|
|
"publish", Napi::Function::New(env, telemetry_publish), napi_enumerable));
|
|
|
|
module_this.DefineProperty(Napi::PropertyDescriptor::Value("telemetry", telemetry_prop, napi_enumerable));
|
|
}
|
|
|
|
// config property
|
|
const auto module_config = config->get_module_config();
|
|
|
|
auto module_info = config->get_module_info(module_id);
|
|
populate_module_info_path_from_runtime_settings(module_info, *rs);
|
|
|
|
auto module_config_prop = Napi::Object::New(env);
|
|
auto module_config_impl_prop = Napi::Object::New(env);
|
|
|
|
for (const auto& [impl_id, parameters] : module_config.configuration_parameters) {
|
|
json config_map;
|
|
for (const auto& param : parameters) {
|
|
config_map[param.name] = param.value;
|
|
}
|
|
|
|
if (impl_id == "!module") {
|
|
module_config_prop.DefineProperty(
|
|
Napi::PropertyDescriptor::Value("module", convertToNapiValue(env, config_map), napi_enumerable));
|
|
continue;
|
|
}
|
|
module_config_impl_prop.DefineProperty(
|
|
Napi::PropertyDescriptor::Value(impl_id, convertToNapiValue(env, config_map), napi_enumerable));
|
|
}
|
|
module_config_prop.DefineProperty(
|
|
Napi::PropertyDescriptor::Value("impl", module_config_impl_prop, napi_enumerable));
|
|
|
|
module_this.DefineProperty(Napi::PropertyDescriptor::Value("config", module_config_prop, napi_enumerable));
|
|
|
|
auto module_info_prop = Napi::Object::New(env);
|
|
// set moduleName property
|
|
module_info_prop.DefineProperty(
|
|
Napi::PropertyDescriptor::Value("name", Napi::String::New(env, module_info.name), napi_enumerable));
|
|
|
|
// set moduleId property
|
|
module_info_prop.DefineProperty(
|
|
Napi::PropertyDescriptor::Value("id", Napi::String::New(env, module_info.id), napi_enumerable));
|
|
|
|
// set printable identifier
|
|
module_info_prop.DefineProperty(Napi::PropertyDescriptor::Value(
|
|
"printable_identifier", Napi::String::New(env, module_identifier), napi_enumerable));
|
|
|
|
auto module_info_paths_prop = Napi::Object::New(env);
|
|
module_info_paths_prop.DefineProperty(Napi::PropertyDescriptor::Value(
|
|
"etc", Napi::String::New(env, module_info.paths.etc.string()), napi_enumerable));
|
|
|
|
module_info_paths_prop.DefineProperty(Napi::PropertyDescriptor::Value(
|
|
"libexec", Napi::String::New(env, module_info.paths.libexec.string()), napi_enumerable));
|
|
|
|
module_info_paths_prop.DefineProperty(Napi::PropertyDescriptor::Value(
|
|
"share", Napi::String::New(env, module_info.paths.share.string()), napi_enumerable));
|
|
|
|
// add path info property
|
|
module_info_prop.DefineProperty(
|
|
Napi::PropertyDescriptor::Value("paths", module_info_paths_prop, napi_enumerable));
|
|
|
|
// set telemetry_enabled
|
|
module_info_prop.DefineProperty(Napi::PropertyDescriptor::Value(
|
|
"telemetry_enabled", Napi::Boolean::New(env, rs->telemetry_enabled), napi_enumerable));
|
|
|
|
module_this.DefineProperty(Napi::PropertyDescriptor::Value("info", module_info_prop, napi_enumerable));
|
|
|
|
// connect to mqtt server and start mqtt mainloop thread
|
|
auto everest_handle =
|
|
std::make_unique<Everest::Everest>(module_id, *config, validate_schema, mqtt, rs->telemetry_prefix,
|
|
rs->telemetry_enabled, rs->forward_exceptions);
|
|
|
|
ctx = new EvModCtx(std::move(everest_handle), module_manifest, env);
|
|
|
|
// FIXME (aw): passing the handle away and then still accessing it is bad design
|
|
// all of this should be solved, if we would have a module instance on the js side
|
|
ctx->everest->connect();
|
|
|
|
ctx->js_module_ref = Napi::Persistent(module_this);
|
|
ctx->js_cb = std::make_unique<JsExecCtx>(env, callback_wrapper);
|
|
ctx->everest->register_on_ready_handler(framework_ready_handler);
|
|
|
|
const auto end_time = std::chrono::steady_clock::now();
|
|
EVLOG_info << "Module " << fmt::format(Everest::TERMINAL_STYLE_BLUE, "{}", module_id) << " initialized ["
|
|
<< std::chrono::duration_cast<std::chrono::milliseconds>(end_time - start_time).count() << "ms]";
|
|
|
|
ctx->everest->spawn_main_loop_thread();
|
|
|
|
} catch (std::exception& e) {
|
|
EVLOG_AND_RETHROW(env);
|
|
}
|
|
|
|
return available_handlers_prop;
|
|
}
|
|
|
|
const std::string extract_logstring(const Napi::CallbackInfo& info) {
|
|
// TODO (aw): check input
|
|
return info[0].ToString().Utf8Value();
|
|
}
|
|
|
|
static Napi::Object Init(Napi::Env env, Napi::Object exports) {
|
|
BOOST_LOG_FUNCTION();
|
|
|
|
//
|
|
// setup logging functions
|
|
//
|
|
Napi::Object log = Napi::Object::New(env);
|
|
|
|
log.DefineProperty(Napi::PropertyDescriptor::Value(
|
|
"debug",
|
|
Napi::Function::New(env, [](const Napi::CallbackInfo& info) { EVLOG_debug << extract_logstring(info); }),
|
|
napi_enumerable));
|
|
|
|
log.DefineProperty(Napi::PropertyDescriptor::Value(
|
|
"info", Napi::Function::New(env, [](const Napi::CallbackInfo& info) { EVLOG_info << extract_logstring(info); }),
|
|
napi_enumerable));
|
|
|
|
log.DefineProperty(Napi::PropertyDescriptor::Value(
|
|
"warning",
|
|
Napi::Function::New(env, [](const Napi::CallbackInfo& info) { EVLOG_warning << extract_logstring(info); }),
|
|
napi_enumerable));
|
|
|
|
log.DefineProperty(Napi::PropertyDescriptor::Value(
|
|
"error",
|
|
Napi::Function::New(env, [](const Napi::CallbackInfo& info) { EVLOG_error << extract_logstring(info); }),
|
|
napi_enumerable));
|
|
|
|
log.DefineProperty(Napi::PropertyDescriptor::Value(
|
|
"critical",
|
|
Napi::Function::New(env, [](const Napi::CallbackInfo& info) { EVLOG_critical << extract_logstring(info); }),
|
|
napi_enumerable));
|
|
|
|
exports.DefineProperty(Napi::PropertyDescriptor::Value("log", log, napi_enumerable));
|
|
|
|
exports.DefineProperty(
|
|
Napi::PropertyDescriptor::Value("signal_ready", Napi::Function::New(env, signal_ready), napi_enumerable));
|
|
|
|
exports.DefineProperty(
|
|
Napi::PropertyDescriptor::Value("boot_module", Napi::Function::New(env, boot_module), napi_enumerable));
|
|
|
|
return exports;
|
|
}
|
|
|
|
NODE_API_MODULE(everestjs, Init)
|
|
|
|
} // namespace EverestJs
|