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:
1
tools/EVerest-main/modules/EVSE/Auth/.gitignore
vendored
Normal file
1
tools/EVerest-main/modules/EVSE/Auth/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
tests.txt
|
||||
235
tools/EVerest-main/modules/EVSE/Auth/Auth.cpp
Normal file
235
tools/EVerest-main/modules/EVSE/Auth/Auth.cpp
Normal file
@@ -0,0 +1,235 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
#include <everest/helpers/helpers.hpp>
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "Auth.hpp"
|
||||
#include <everest/logging.hpp>
|
||||
|
||||
namespace module {
|
||||
|
||||
void Auth::init() {
|
||||
invoke_init(*p_main);
|
||||
invoke_init(*p_reservation);
|
||||
|
||||
this->auth_handler = std::make_unique<AuthHandler>(
|
||||
string_to_selection_algorithm(this->config.selection_algorithm), this->config.connection_timeout,
|
||||
this->config.plug_in_timeout_enabled, this->config.prioritize_authorization_over_stopping_transaction,
|
||||
this->config.ignore_connector_faults, this->info.id,
|
||||
(!this->r_kvs.empty() ? this->r_kvs.at(0).get() : nullptr));
|
||||
|
||||
for (const auto& token_provider : this->r_token_provider) {
|
||||
token_provider->subscribe_provided_token([this](ProvidedIdToken provided_token) {
|
||||
{
|
||||
auto state = this->event_state.handle();
|
||||
if (!state->started) {
|
||||
EVLOG_warning << "Auth not fully initialized. Discarding provided token";
|
||||
return;
|
||||
}
|
||||
}
|
||||
std::thread t([this, provided_token]() { this->auth_handler->on_token(provided_token); });
|
||||
t.detach();
|
||||
});
|
||||
}
|
||||
for (const auto& token_validator : this->r_token_validator) {
|
||||
token_validator->subscribe_validate_result_update([this](ValidationResultUpdate validation_result_update) {
|
||||
{
|
||||
auto state = this->event_state.handle();
|
||||
if (!state->started) {
|
||||
EVLOG_warning << "Auth not fully initialized. Discarding validation result update";
|
||||
return;
|
||||
}
|
||||
}
|
||||
this->auth_handler->handle_token_validation_result_update(validation_result_update);
|
||||
});
|
||||
}
|
||||
|
||||
// Subscribe to session events and errors in init() so we don't miss any events
|
||||
// Events received before ready() are queued.
|
||||
int32_t evse_index = 0;
|
||||
for (const auto& evse_manager : this->r_evse_manager) {
|
||||
const int32_t evse_idx = evse_index;
|
||||
|
||||
evse_manager->subscribe_session_event([this, evse_idx](SessionEvent session_event) {
|
||||
{
|
||||
auto state = this->event_state.handle();
|
||||
if (!state->started) {
|
||||
EVLOG_debug << "Auth not fully initialized, but received a session event on evse_index: "
|
||||
<< evse_idx << " that will be queued up: " << session_event.event;
|
||||
state->event_queue.emplace(evse_idx, session_event);
|
||||
return;
|
||||
}
|
||||
}
|
||||
this->auth_handler->handle_session_event(this->auth_handler->get_evse_id_by_index(evse_idx), session_event);
|
||||
});
|
||||
|
||||
evse_manager->subscribe_error(
|
||||
"evse_manager/Inoperative",
|
||||
[this, evse_idx](const Everest::error::Error& error) {
|
||||
{
|
||||
auto state = this->event_state.handle();
|
||||
if (!state->started) {
|
||||
EVLOG_debug << "Auth not fully initialized, queuing permanent fault raised for evse_index: "
|
||||
<< evse_idx;
|
||||
state->event_queue.emplace(evse_idx, PermanentFaultRaised{1});
|
||||
return;
|
||||
}
|
||||
}
|
||||
this->auth_handler->handle_permanent_fault_raised(this->auth_handler->get_evse_id_by_index(evse_idx),
|
||||
1);
|
||||
},
|
||||
[this, evse_idx](const Everest::error::Error& error) {
|
||||
{
|
||||
auto state = this->event_state.handle();
|
||||
if (!state->started) {
|
||||
EVLOG_debug << "Auth not fully initialized, queuing permanent fault cleared for evse_index: "
|
||||
<< evse_idx;
|
||||
state->event_queue.emplace(evse_idx, PermanentFaultCleared{1});
|
||||
return;
|
||||
}
|
||||
}
|
||||
this->auth_handler->handle_permanent_fault_cleared(this->auth_handler->get_evse_id_by_index(evse_idx),
|
||||
1);
|
||||
});
|
||||
|
||||
evse_index++;
|
||||
}
|
||||
}
|
||||
|
||||
void Auth::ready() {
|
||||
invoke_ready(*p_main);
|
||||
invoke_ready(*p_reservation);
|
||||
|
||||
int32_t evse_index = 0;
|
||||
for (const auto& evse_manager : this->r_evse_manager) {
|
||||
const int32_t evse_id = evse_manager->call_get_evse().id;
|
||||
std::vector<Connector> connectors;
|
||||
for (const auto& connector : evse_manager->call_get_evse().connectors) {
|
||||
connectors.push_back(
|
||||
Connector(connector.id, connector.type.value_or(types::evse_manager::ConnectorTypeEnum::Unknown)));
|
||||
}
|
||||
|
||||
this->auth_handler->init_evse(evse_id, evse_index, connectors);
|
||||
|
||||
evse_index++;
|
||||
}
|
||||
|
||||
this->auth_handler->register_publish_token_validation_status_callback(
|
||||
[this](const ProvidedIdToken& token, TokenValidationStatus status,
|
||||
const std::vector<MessageContent>& tariff_messages) {
|
||||
this->p_main->publish_token_validation_status({token, status, tariff_messages});
|
||||
});
|
||||
|
||||
this->auth_handler->register_notify_evse_callback(
|
||||
[this](const int evse_index, const ProvidedIdToken& provided_token, const ValidationResult& validation_result) {
|
||||
this->r_evse_manager.at(evse_index)->call_authorize_response(provided_token, validation_result);
|
||||
});
|
||||
this->auth_handler->register_withdraw_authorization_callback(
|
||||
[this](const int32_t evse_index) { this->r_evse_manager.at(evse_index)->call_withdraw_authorization(); });
|
||||
this->auth_handler->register_validate_token_callback([this](const ProvidedIdToken& provided_token) {
|
||||
std::vector<ValidationResult> validation_results;
|
||||
for (const auto& token_validator : this->r_token_validator) {
|
||||
try {
|
||||
const auto result = token_validator->call_validate_token(provided_token);
|
||||
validation_results.push_back(result);
|
||||
// TODO: This is very broad catch, make it more narrow when the everest-framework error handling will be
|
||||
// established
|
||||
} catch (const std::exception& e) {
|
||||
EVLOG_warning << "Exception during validating token: " << e.what();
|
||||
ValidationResult validation_result;
|
||||
validation_result.authorization_status = AuthorizationStatus::Unknown;
|
||||
validation_results.push_back(validation_result);
|
||||
}
|
||||
}
|
||||
return validation_results;
|
||||
});
|
||||
this->auth_handler->register_stop_transaction_callback(
|
||||
[this](const int32_t evse_index, const StopTransactionRequest& request) {
|
||||
this->r_evse_manager.at(evse_index)->call_stop_transaction(request);
|
||||
});
|
||||
this->auth_handler->register_reserved_callback(
|
||||
[this](const std::optional<int32_t> evse_id, const int32_t& reservation_id) {
|
||||
// Only call the evse manager to store the reservation if it is done for a specific evse.
|
||||
if (evse_id.has_value()) {
|
||||
EVLOG_info << "Call reserved callback for evse id " << evse_id.value();
|
||||
|
||||
if (!this->r_evse_manager.at(evse_id.value() - 1)->call_reserve(reservation_id)) {
|
||||
EVLOG_warning << "EVSE manager does not allow placing a reservation for evse id " << evse_id.value()
|
||||
<< ": cancelling reservation.";
|
||||
this->auth_handler->handle_cancel_reservation(reservation_id);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
ReservationUpdateStatus status;
|
||||
status.reservation_id = reservation_id;
|
||||
status.reservation_status = Reservation_status::Placed;
|
||||
this->p_reservation->publish_reservation_update(status);
|
||||
return true;
|
||||
});
|
||||
this->auth_handler->register_reservation_cancelled_callback(
|
||||
[this](const std::optional<int32_t> evse_id, const int32_t reservation_id, const ReservationEndReason reason,
|
||||
const bool send_reservation_update) {
|
||||
// Only call the evse manager to cancel the reservation if it was for a specific evse
|
||||
if (evse_id.has_value() && evse_id.value() > 0) {
|
||||
EVLOG_debug << "Call evse manager to cancel the reservation with evse id " << evse_id.value();
|
||||
this->r_evse_manager.at(evse_id.value() - 1)->call_cancel_reservation();
|
||||
}
|
||||
|
||||
if (send_reservation_update) {
|
||||
ReservationUpdateStatus status;
|
||||
status.reservation_id = reservation_id;
|
||||
if (reason == ReservationEndReason::Expired) {
|
||||
status.reservation_status = Reservation_status::Expired;
|
||||
} else if (reason == ReservationEndReason::Cancelled) {
|
||||
status.reservation_status = Reservation_status::Removed;
|
||||
} else {
|
||||
// On reservation used: do not publish a reservation update!!
|
||||
return;
|
||||
}
|
||||
this->p_reservation->publish_reservation_update(status);
|
||||
}
|
||||
});
|
||||
|
||||
// Process any events that were queued during init before we were ready
|
||||
{
|
||||
auto state = this->event_state.handle();
|
||||
while (!state->event_queue.empty()) {
|
||||
auto queued_event = state->event_queue.front();
|
||||
state->event_queue.pop();
|
||||
const int32_t evse_id = this->auth_handler->get_evse_id_by_index(queued_event.evse_index);
|
||||
if (std::holds_alternative<SessionEvent>(queued_event.data)) {
|
||||
const auto& session_event = std::get<SessionEvent>(queued_event.data);
|
||||
EVLOG_debug << "Processing queued session event for evse_id: " << evse_id
|
||||
<< ", event: " << session_event.event;
|
||||
this->auth_handler->handle_session_event(evse_id, session_event);
|
||||
} else if (std::holds_alternative<PermanentFaultRaised>(queued_event.data)) {
|
||||
const auto& fault = std::get<PermanentFaultRaised>(queued_event.data);
|
||||
EVLOG_debug << "Processing queued permanent fault raised for evse_id: " << evse_id;
|
||||
this->auth_handler->handle_permanent_fault_raised(evse_id, fault.connector_id);
|
||||
} else if (std::holds_alternative<PermanentFaultCleared>(queued_event.data)) {
|
||||
const auto& fault = std::get<PermanentFaultCleared>(queued_event.data);
|
||||
EVLOG_debug << "Processing queued permanent fault cleared for evse_id: " << evse_id;
|
||||
this->auth_handler->handle_permanent_fault_cleared(evse_id, fault.connector_id);
|
||||
}
|
||||
}
|
||||
state->started = true;
|
||||
}
|
||||
|
||||
this->auth_handler->initialize();
|
||||
}
|
||||
|
||||
void Auth::set_connection_timeout(int& connection_timeout) {
|
||||
this->auth_handler->set_connection_timeout(connection_timeout);
|
||||
}
|
||||
|
||||
void Auth::set_master_pass_group_id(const std::string& master_pass_group_id) {
|
||||
this->auth_handler->set_master_pass_group_id(master_pass_group_id);
|
||||
}
|
||||
|
||||
WithdrawAuthorizationResult Auth::handle_withdraw_authorization(const WithdrawAuthorizationRequest& request) {
|
||||
return this->auth_handler->handle_withdraw_authorization(request);
|
||||
}
|
||||
|
||||
} // namespace module
|
||||
138
tools/EVerest-main/modules/EVSE/Auth/Auth.hpp
Normal file
138
tools/EVerest-main/modules/EVSE/Auth/Auth.hpp
Normal file
@@ -0,0 +1,138 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
#ifndef AUTH_HPP
|
||||
#define AUTH_HPP
|
||||
|
||||
//
|
||||
// AUTO GENERATED - MARKED REGIONS WILL BE KEPT
|
||||
// template version 2
|
||||
//
|
||||
|
||||
#include "ld-ev.hpp"
|
||||
|
||||
// headers for provided interface implementations
|
||||
#include <generated/interfaces/auth/Implementation.hpp>
|
||||
#include <generated/interfaces/reservation/Implementation.hpp>
|
||||
|
||||
// headers for required interface implementations
|
||||
#include <generated/interfaces/auth_token_provider/Interface.hpp>
|
||||
#include <generated/interfaces/auth_token_validator/Interface.hpp>
|
||||
#include <generated/interfaces/evse_manager/Interface.hpp>
|
||||
#include <generated/interfaces/kvs/Interface.hpp>
|
||||
|
||||
// ev@4bf81b14-a215-475c-a1d3-0a484ae48918:v1
|
||||
// insert your custom include headers here
|
||||
#include <AuthHandler.hpp>
|
||||
#include <everest/util/async/monitor.hpp>
|
||||
#include <memory>
|
||||
#include <queue>
|
||||
#include <variant>
|
||||
|
||||
using namespace types::evse_manager;
|
||||
using namespace types::authorization;
|
||||
|
||||
struct PermanentFaultRaised {
|
||||
int32_t connector_id;
|
||||
};
|
||||
|
||||
struct PermanentFaultCleared {
|
||||
int32_t connector_id;
|
||||
};
|
||||
|
||||
using AuthEvent = std::variant<SessionEvent, PermanentFaultRaised, PermanentFaultCleared>;
|
||||
|
||||
struct EvseEvent {
|
||||
int32_t evse_index;
|
||||
AuthEvent data;
|
||||
|
||||
EvseEvent(int32_t evse_index_, AuthEvent data_) : evse_index(evse_index_), data(std::move(data_)) {
|
||||
}
|
||||
};
|
||||
|
||||
struct EventQueueState {
|
||||
bool started{false};
|
||||
std::queue<EvseEvent> event_queue;
|
||||
};
|
||||
// ev@4bf81b14-a215-475c-a1d3-0a484ae48918:v1
|
||||
|
||||
namespace module {
|
||||
|
||||
struct Conf {
|
||||
std::string selection_algorithm;
|
||||
int connection_timeout;
|
||||
std::string master_pass_group_id;
|
||||
bool prioritize_authorization_over_stopping_transaction;
|
||||
bool ignore_connector_faults;
|
||||
bool plug_in_timeout_enabled;
|
||||
};
|
||||
|
||||
class Auth : public Everest::ModuleBase {
|
||||
public:
|
||||
Auth() = delete;
|
||||
Auth(const ModuleInfo& info, std::unique_ptr<authImplBase> p_main,
|
||||
std::unique_ptr<reservationImplBase> p_reservation,
|
||||
std::vector<std::unique_ptr<auth_token_providerIntf>> r_token_provider,
|
||||
std::vector<std::unique_ptr<auth_token_validatorIntf>> r_token_validator,
|
||||
std::vector<std::unique_ptr<evse_managerIntf>> r_evse_manager, std::vector<std::unique_ptr<kvsIntf>> r_kvs,
|
||||
Conf& config) :
|
||||
ModuleBase(info),
|
||||
p_main(std::move(p_main)),
|
||||
p_reservation(std::move(p_reservation)),
|
||||
r_token_provider(std::move(r_token_provider)),
|
||||
r_token_validator(std::move(r_token_validator)),
|
||||
r_evse_manager(std::move(r_evse_manager)),
|
||||
r_kvs(std::move(r_kvs)),
|
||||
config(config){};
|
||||
|
||||
const std::unique_ptr<authImplBase> p_main;
|
||||
const std::unique_ptr<reservationImplBase> p_reservation;
|
||||
const std::vector<std::unique_ptr<auth_token_providerIntf>> r_token_provider;
|
||||
const std::vector<std::unique_ptr<auth_token_validatorIntf>> r_token_validator;
|
||||
const std::vector<std::unique_ptr<evse_managerIntf>> r_evse_manager;
|
||||
const std::vector<std::unique_ptr<kvsIntf>> r_kvs;
|
||||
const Conf& config;
|
||||
|
||||
// ev@1fce4c5e-0ab8-41bb-90f7-14277703d2ac:v1
|
||||
// insert your public definitions here
|
||||
std::unique_ptr<AuthHandler> auth_handler;
|
||||
|
||||
/**
|
||||
* @brief Set the connection timeout for the auth handler
|
||||
*
|
||||
* @param connection_timeout timeout in seconds
|
||||
*/
|
||||
void set_connection_timeout(int& connection_timeout);
|
||||
|
||||
/**
|
||||
* @brief Set the master pass group id for the auth handler
|
||||
*
|
||||
* @param master_pass_group_id master pass group id
|
||||
*/
|
||||
void set_master_pass_group_id(const std::string& master_pass_group_id);
|
||||
|
||||
WithdrawAuthorizationResult handle_withdraw_authorization(const WithdrawAuthorizationRequest& request);
|
||||
// ev@1fce4c5e-0ab8-41bb-90f7-14277703d2ac:v1
|
||||
|
||||
protected:
|
||||
// ev@4714b2ab-a24f-4b95-ab81-36439e1478de:v1
|
||||
// insert your protected definitions here
|
||||
// ev@4714b2ab-a24f-4b95-ab81-36439e1478de:v1
|
||||
|
||||
private:
|
||||
friend class LdEverest;
|
||||
void init();
|
||||
void ready();
|
||||
|
||||
// ev@211cfdbe-f69a-4cd6-a4ec-f8aaa3d1b6c8:v1
|
||||
// insert your private definitions here
|
||||
everest::lib::util::monitor<EventQueueState> event_state;
|
||||
// ev@211cfdbe-f69a-4cd6-a4ec-f8aaa3d1b6c8:v1
|
||||
};
|
||||
|
||||
// ev@087e516b-124c-48df-94fb-109508c7cda9:v1
|
||||
// insert other definitions here
|
||||
// ev@087e516b-124c-48df-94fb-109508c7cda9:v1
|
||||
|
||||
} // namespace module
|
||||
|
||||
#endif // AUTH_HPP
|
||||
42
tools/EVerest-main/modules/EVSE/Auth/BUILD.bazel
Normal file
42
tools/EVerest-main/modules/EVSE/Auth/BUILD.bazel
Normal file
@@ -0,0 +1,42 @@
|
||||
load("@rules_cc//cc:defs.bzl", "cc_library")
|
||||
load("//modules:module.bzl", "cc_everest_module")
|
||||
|
||||
cc_library(
|
||||
name = "auth_handler",
|
||||
srcs = glob(["lib/*.cpp"]),
|
||||
hdrs = glob(["include/*.hpp"]),
|
||||
strip_include_prefix = "include",
|
||||
deps = [
|
||||
"//lib/everest/timer:libtimer",
|
||||
"@boost.asio",
|
||||
"//lib/everest/framework:framework",
|
||||
"@com_github_HowardHinnant_date//:date",
|
||||
"//types:types_lib",
|
||||
"//interfaces:interfaces_lib",
|
||||
"//lib/everest/helpers",
|
||||
"//lib/everest/util",
|
||||
],
|
||||
# See https://github.com/HowardHinnant/date/issues/324
|
||||
local_defines = [
|
||||
"BUILD_TZ_LIB=ON",
|
||||
"USE_SYSTEM_TZ_DB=ON",
|
||||
"USE_OS_TZDB=1",
|
||||
"USE_AUTOLOAD=0",
|
||||
"HAS_REMOTE_API=0",
|
||||
],
|
||||
copts = ["-std=c++17"],
|
||||
)
|
||||
|
||||
IMPLS = [
|
||||
"main",
|
||||
"reservation",
|
||||
]
|
||||
|
||||
cc_everest_module(
|
||||
name = "Auth",
|
||||
deps = [
|
||||
"//lib/everest/timer:libtimer",
|
||||
":auth_handler",
|
||||
],
|
||||
impls = IMPLS,
|
||||
)
|
||||
40
tools/EVerest-main/modules/EVSE/Auth/CMakeLists.txt
Normal file
40
tools/EVerest-main/modules/EVSE/Auth/CMakeLists.txt
Normal file
@@ -0,0 +1,40 @@
|
||||
#
|
||||
# AUTO GENERATED - MARKED REGIONS WILL BE KEPT
|
||||
# template version 3
|
||||
#
|
||||
|
||||
# module setup:
|
||||
# - ${MODULE_NAME}: module name
|
||||
ev_setup_cpp_module()
|
||||
|
||||
# ev@bcc62523-e22b-41d7-ba2f-825b493a3c97:v1
|
||||
# insert your custom targets and additional config variables here
|
||||
option(BUILD_TESTING "Run unit tests" OFF)
|
||||
|
||||
add_subdirectory(lib)
|
||||
|
||||
target_include_directories(${MODULE_NAME} PRIVATE "include")
|
||||
|
||||
target_link_libraries(${MODULE_NAME}
|
||||
PRIVATE
|
||||
auth_handler
|
||||
date::date
|
||||
date::date-tz
|
||||
everest::timer
|
||||
everest::helpers
|
||||
everest::util
|
||||
)
|
||||
# ev@bcc62523-e22b-41d7-ba2f-825b493a3c97:v1
|
||||
|
||||
target_sources(${MODULE_NAME}
|
||||
PRIVATE
|
||||
"main/authImpl.cpp"
|
||||
"reservation/reservationImpl.cpp"
|
||||
)
|
||||
|
||||
# ev@c55432ab-152c-45a9-9d2e-7281d50c69c3:v1
|
||||
# insert other things like install cmds etc here
|
||||
if(EVEREST_CORE_BUILD_TESTING)
|
||||
add_subdirectory(tests)
|
||||
endif()
|
||||
# ev@c55432ab-152c-45a9-9d2e-7281d50c69c3:v1
|
||||
@@ -0,0 +1,249 @@
|
||||
<svg host="65bd71144e" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="701px" height="376px" viewBox="-0.5 -0.5 701 376" content="<mxfile><diagram id="4SK5cN6X-azEmNZbr7fQ" name="Page-1">7Vrbdto4FP0a1pp5KMu2sIFHHJJMQtPQZpJp5iVL2DJ2KiwqywT60G+fI1sCX4CQadKQCw8J2jq6nq3tc2Qa6GAyP+Z4Gp4xn9CGZfjzBuo3LMvptOGvBBY5gOxuDox55OeQuQIuoh9EgYZC08gnSclQMEZFNC2DHotj4okShjlnd2WzgNHyqFM8JjXgwsO0jv4T+SLM0Y5trPC/SDQO9cimoWomWBsrIAmxz+4KEDpsoAPOmMi/TeYHhMq90/uStzvaULucGCexWNPgMiH8fHQr98QyKB6BWzKjv9k3EgM05GwGW8vzbmjqRb4XYi7yNidqlPSmN7B634UxP+/nlnp8a7mE5dQSsdDbFoqJHM9sIJfNCA9otvIRZd43gLL/F1PsRfFYWd2FkSASkh3cAZUAC1gsFCNMp9lWyAGjjGejIIRbZgsDjmk0jgHzYC9gSchNlp13oQQzEBH4tKfMJpHvy5m6ieCwHet6zGvOZTdCUlf6FcaPKK1inKWxT3y1DjxKGE0F6XFPT12iy1JX9VIYM7ACFNjLMTXNjGYHsLqXlePlmsi8AClfHBM2IYIvwETVIkVAdQKtLsrLdys+W7adY2GByy2FYXWExsueVyyDL8r9uljg3VYebued3+wNqBlPrTBIy7xr7cq7WyLEQvugswPF0DpCOMj2g2AjIda5rEKIFRFg3i7xQV7UfBkXIRuzGNPDFQpdwm70pHSBScxiiZHY14g+QwAdRVQvlswj8TWbga1K17KmaRgdVR4SHoH75OnoG1l7cGWhiSzKNh8M3Ugi1VYb6ZiwlHukIA2g05iPibJyckiufitlC5xcKmmRk0uQE4pFNCur9DqmZmPA5uFFwWDKolgkNSIvJ7Ijt2uirE7MDNNUz6wJZaW5VpmrcHyhH7dAWU6S6AceZQZyt7Pj4mLv2zjjlKalTwKcUrFO9zYqXUZ0Nba5RkmzT/VANNFal7d2VqCdvbRBTzaJRy8FllgGRBsprGirkghQksVk8dFtX/bKSuK8P8Fe5hOsZXf24QmmI6kr8J2PBbsnlDoDIh4Y9s+z4MQrE7H9TsSXSUTHegGhlAG8+3z24Vy0To7LvOv83lCKdExUqNn/UAqS1YcHU6pRKZxq/1I45dTDqfbbCKe6tXDKauZlKbjkRkgF/iPy8y9/vp4Iq/PsEdb9mjI/obfuZYTKmmIajX0SFR8n4VI/gmjeh7IqyaohFuBV6UZYRna690+F2oZTUKFVerZdhlQrLUPmL6tQ+80mdaYOE1YyhKQMfSGJ1IhXIzr6yvUZVedwlhCZ1+EYj+XVpGHeE1Njd8B+dK5QaOKKDr3fT76UoLplV9I7w96HqLrGxXueiV+Ai7OfJ6f/fpm6FS6idy6+UC7a1l5wcTvzmpJ5X8mQ9x1eYd5vvi1/YSmejIp0+bpRvD3fGFll8ZdZDK0K9+ePld6Z6I1EVvX7cpyClzlQ4DWmdebz35xvV5LrE3fw6ah3OhjOFxUl2fm2/E29d9t4wvf36E7mQ4kVnnk63tI/0TArz7JcrVSrLR3ZbWN7R7nG1Tp6JDFp18TkCvPkFamHs+fq0QT1aN+E8XfWGVbU4/2q+c2oRzUS/t/qUe3oadWjftd8MPFfk3o8/53yBaGARUy+NPXJFM4C7K+RFYtVlI0jT1YEctj8Tf9khzf96NQduP1P6Gh25ZTlx9r5UvpxEnBjh/SbkkCUkm+UyVCVC4JN18hYPb9+nOw6+zxddo2Myk0PqmfXprVGzlrGg0n4gOxaZxoN1MuIwaVDZF6GMk+KxZRsqFK/OmUQZmQGWOlnRt1Mp5amU070eys/Nx4xRgsG28nNzeHg6ONw1j9j3Qq5zacndxJiuQd9n3npJPO/O8rJ9nGkey8cgE4TPc0J2P326eFn4Yk4b3Yrz0enW+e8seZGyXYeg/MArX5inD8qV7/TRof/AQ==</diagram></mxfile>">
|
||||
<defs/>
|
||||
<g>
|
||||
<rect x="25" y="0" width="255" height="45" rx="4.5" ry="4.5" fill="#f2f3f5" stroke="#3a414a" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 239px; height: 1px; padding-top: 23px; margin-left: 33px;">
|
||||
<div data-drawio-colors="color: #3a414a; " style="box-sizing: border-box; font-size: 0px; text-align: center; width: 237px; max-height: 27px;">
|
||||
<div style="display: inline-block; font-size: 16.7px; font-family: Helvetica; color: rgb(58, 65, 74); line-height: 1.2; pointer-events: all; width: 100%; white-space: normal; overflow-wrap: normal;">
|
||||
Token Provider
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="153" y="28" fill="#3a414a" font-family="Helvetica" font-size="17px" text-anchor="middle">
|
||||
Token Provider
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<path d="M 152.5 45.36 L 152.5 156.95" fill="none" stroke="#635dff" stroke-miterlimit="10" pointer-events="stroke"/>
|
||||
<path d="M 152.5 163.75 L 149.1 156.95 L 155.9 156.95 Z" fill="#635dff" stroke="#635dff" stroke-miterlimit="10" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 106px; margin-left: 153px;">
|
||||
<div data-drawio-colors="color: #333333; background-color: rgb(255, 255, 255); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
|
||||
<div style="display: inline-block; font-size: 13.3px; font-family: Helvetica; color: rgb(51, 51, 51); line-height: 1.2; pointer-events: all; font-weight: bold; background-color: rgb(255, 255, 255); white-space: nowrap;">
|
||||
1. Token
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="153" y="110" fill="#333333" font-family="Helvetica" font-size="13px" text-anchor="middle" font-weight="bold">
|
||||
1. Token
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<rect x="25" y="165" width="255" height="45" rx="4.5" ry="4.5" fill="#f2f3f5" stroke="#3a414a" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 239px; height: 1px; padding-top: 188px; margin-left: 33px;">
|
||||
<div data-drawio-colors="color: #3a414a; " style="box-sizing: border-box; font-size: 0px; text-align: center; width: 237px; max-height: 27px;">
|
||||
<div style="display: inline-block; font-size: 16.7px; font-family: Helvetica; color: rgb(58, 65, 74); line-height: 1.2; pointer-events: all; width: 100%; white-space: normal; overflow-wrap: normal;">
|
||||
Auth Module
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="153" y="193" fill="#3a414a" font-family="Helvetica" font-size="17px" text-anchor="middle">
|
||||
Auth Module
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<rect x="25" y="330" width="255" height="45" rx="4.5" ry="4.5" fill="#f2f3f5" stroke="#3a414a" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 239px; height: 1px; padding-top: 353px; margin-left: 33px;">
|
||||
<div data-drawio-colors="color: #3a414a; " style="box-sizing: border-box; font-size: 0px; text-align: center; width: 237px; max-height: 27px;">
|
||||
<div style="display: inline-block; font-size: 16.7px; font-family: Helvetica; color: rgb(58, 65, 74); line-height: 1.2; pointer-events: all; width: 100%; white-space: normal; overflow-wrap: normal;">
|
||||
Token Validator
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="153" y="358" fill="#3a414a" font-family="Helvetica" font-size="17px" text-anchor="middle">
|
||||
Token Validator
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<path d="M 84.92 210.36 L 84.92 321.99" fill="none" stroke="#e81313" stroke-miterlimit="10" pointer-events="stroke"/>
|
||||
<path d="M 84.92 328.79 L 81.52 321.99 L 88.32 321.99 Z" fill="#e81313" stroke="#e81313" stroke-miterlimit="10" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 271px; margin-left: 85px;">
|
||||
<div data-drawio-colors="color: #333333; background-color: rgb(255, 255, 255); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
|
||||
<div style="display: inline-block; font-size: 13.3px; font-family: Helvetica; color: rgb(51, 51, 51); line-height: 1.2; pointer-events: all; font-weight: bold; background-color: rgb(255, 255, 255); white-space: nowrap;">
|
||||
2. validate_token(id_token)
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="85" y="275" fill="#333333" font-family="Helvetica" font-size="13px" text-anchor="middle" font-weight="bold">
|
||||
2. validate_token(id_token)
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<path d="M 205.03 329.64 L 205.03 218.01" fill="none" stroke="#e81313" stroke-miterlimit="10" stroke-dasharray="3 8" pointer-events="stroke"/>
|
||||
<path d="M 205.03 211.21 L 208.43 218.01 L 201.63 218.01 Z" fill="#e81313" stroke="#e81313" stroke-miterlimit="10" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 270px; margin-left: 206px;">
|
||||
<div data-drawio-colors="color: #333333; background-color: rgb(255, 255, 255); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
|
||||
<div style="display: inline-block; font-size: 13.3px; font-family: Helvetica; color: rgb(51, 51, 51); line-height: 1.2; pointer-events: all; font-weight: bold; background-color: rgb(255, 255, 255); white-space: nowrap;">
|
||||
3. Result
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="206" y="274" fill="#333333" font-family="Helvetica" font-size="13px" text-anchor="middle" font-weight="bold">
|
||||
3. Result
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<rect x="445" y="112" width="255" height="45" rx="4.5" ry="4.5" fill="#f2f3f5" stroke="#3a414a" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 239px; height: 1px; padding-top: 135px; margin-left: 453px;">
|
||||
<div data-drawio-colors="color: #3a414a; " style="box-sizing: border-box; font-size: 0px; text-align: center; width: 237px; max-height: 27px;">
|
||||
<div style="display: inline-block; font-size: 16.7px; font-family: Helvetica; color: rgb(58, 65, 74); line-height: 1.2; pointer-events: all; width: 100%; white-space: normal; overflow-wrap: normal;">
|
||||
Evse Manager 1
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="573" y="140" fill="#3a414a" font-family="Helvetica" font-size="17px" text-anchor="middle">
|
||||
Evse Manager 1
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<rect x="445" y="232" width="255" height="45" rx="4.5" ry="4.5" fill="#f2f3f5" stroke="#3a414a" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 239px; height: 1px; padding-top: 255px; margin-left: 453px;">
|
||||
<div data-drawio-colors="color: #3a414a; " style="box-sizing: border-box; font-size: 0px; text-align: center; width: 237px; max-height: 27px;">
|
||||
<div style="display: inline-block; font-size: 16.7px; font-family: Helvetica; color: rgb(58, 65, 74); line-height: 1.2; pointer-events: all; width: 100%; white-space: normal; overflow-wrap: normal;">
|
||||
Evse Manager 2
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="573" y="260" fill="#3a414a" font-family="Helvetica" font-size="17px" text-anchor="middle">
|
||||
Evse Manager 2
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<path d="M 280.25 187.5 L 356.5 187.5 Q 362.5 187.5 362.5 193.5 L 362.5 248.5 Q 362.5 254.5 368.5 254.5 L 437.05 254.5" fill="none" stroke="#e81313" stroke-miterlimit="10" pointer-events="stroke"/>
|
||||
<path d="M 443.85 254.5 L 437.05 257.9 L 437.05 251.1 Z" fill="#e81313" stroke="#e81313" stroke-miterlimit="10" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 222px; margin-left: 363px;">
|
||||
<div data-drawio-colors="color: #333333; background-color: rgb(255, 255, 255); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
|
||||
<div style="display: inline-block; font-size: 13.3px; font-family: Helvetica; color: rgb(51, 51, 51); line-height: 1.2; pointer-events: all; font-weight: bold; background-color: rgb(255, 255, 255); white-space: nowrap;">
|
||||
authorize(id_token)
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="363" y="226" fill="#333333" font-family="Helvetica" font-size="13px" text-anchor="middle" font-weight="bold">
|
||||
authorize(id_token)
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<path d="M 400 22 L 557.31 22" fill="none" stroke="#635dff" stroke-miterlimit="10" pointer-events="stroke"/>
|
||||
<path d="M 564.11 22 L 557.31 25.4 L 557.31 18.6 Z" fill="#635dff" stroke="#635dff" stroke-miterlimit="10" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 23px; margin-left: 484px;">
|
||||
<div data-drawio-colors="color: #333333; background-color: rgb(255, 255, 255); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
|
||||
<div style="display: inline-block; font-size: 13.3px; font-family: Helvetica; color: rgb(51, 51, 51); line-height: 1.2; pointer-events: all; font-weight: bold; background-color: rgb(255, 255, 255); white-space: nowrap;">
|
||||
Vars
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="484" y="26" fill="#333333" font-family="Helvetica" font-size="13px" text-anchor="middle" font-weight="bold">
|
||||
Vars
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<path d="M 400 52 L 557.31 52" fill="none" stroke="#e81313" stroke-miterlimit="10" pointer-events="stroke"/>
|
||||
<path d="M 564.11 52 L 557.31 55.4 L 557.31 48.6 Z" fill="#e81313" stroke="#e81313" stroke-miterlimit="10" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 53px; margin-left: 484px;">
|
||||
<div data-drawio-colors="color: #333333; background-color: rgb(255, 255, 255); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
|
||||
<div style="display: inline-block; font-size: 13.3px; font-family: Helvetica; color: rgb(51, 51, 51); line-height: 1.2; pointer-events: all; font-weight: bold; background-color: rgb(255, 255, 255); white-space: nowrap;">
|
||||
Cmds
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="484" y="56" fill="#333333" font-family="Helvetica" font-size="13px" text-anchor="middle" font-weight="bold">
|
||||
Cmds
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<rect x="295" y="142" width="120" height="40" rx="4.5" ry="4.5" fill-opacity="0" fill="#ffffff" stroke="rgb(0, 0, 0)" stroke-opacity="0" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe flex-start; justify-content: unsafe flex-start; width: 116px; height: 1px; padding-top: 145px; margin-left: 298px;">
|
||||
<div data-drawio-colors="color: #3a414a; " style="box-sizing: border-box; font-size: 0px; text-align: left; width: 114px;">
|
||||
<div style="display: inline-block; font-size: 10px; font-family: Helvetica; color: rgb(58, 65, 74); line-height: 1.2; pointer-events: all; width: 100%; white-space: normal; overflow-wrap: normal;">
|
||||
Selection depends on Selection logic of auth module
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="298" y="155" fill="#3a414a" font-family="Helvetica" font-size="10px">
|
||||
Selection depends on Se...
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<path d="M 190 76 L 295 76 L 295 123.6 Q 268.75 108.48 242.5 123.6 Q 216.25 138.72 190 123.6 L 190 84.4 Z" fill="rgb(255, 255, 255)" stroke="#3a414a" stroke-miterlimit="10" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe flex-start; justify-content: unsafe flex-start; width: 101px; height: 1px; padding-top: 79px; margin-left: 193px;">
|
||||
<div data-drawio-colors="color: #3a414a; " style="box-sizing: border-box; font-size: 0px; text-align: left; width: 99px;">
|
||||
<div style="display: inline-block; font-size: 8.3px; font-family: Helvetica; color: rgb(58, 65, 74); line-height: 1.2; pointer-events: all; width: 100%; white-space: normal; overflow-wrap: normal;">
|
||||
id_token: string
|
||||
<br/>
|
||||
type: string
|
||||
<br/>
|
||||
connectors: array of int
|
||||
<br/>
|
||||
prevalidated: bool
|
||||
<div>
|
||||
<br/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="193" y="87" fill="#3a414a" font-family="Helvetica" font-size="8px">
|
||||
id_token: string...
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
</g>
|
||||
<switch>
|
||||
<g requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"/>
|
||||
<a transform="translate(0,-5)" xlink:href="https://www.diagrams.net/doc/faq/svg-export-text-problems" target="_blank">
|
||||
<text text-anchor="middle" font-size="10px" x="50%" y="100%">
|
||||
Viewer does not support full SVG 1.1
|
||||
</text>
|
||||
</a>
|
||||
</switch>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 22 KiB |
87
tools/EVerest-main/modules/EVSE/Auth/docs/index.rst
Normal file
87
tools/EVerest-main/modules/EVSE/Auth/docs/index.rst
Normal file
@@ -0,0 +1,87 @@
|
||||
.. _everest_modules_handwritten_Auth:
|
||||
|
||||
.. ===========
|
||||
.. Auth Module
|
||||
.. ===========
|
||||
|
||||
This module handles incoming authorization and reservation requests.
|
||||
|
||||
The task of the module is to receive tokens from token providers, validate them and assign them to EvseManagers.
|
||||
It is responsible for providing authorization to EvseManagers and for stopping transactions at the EvseManagers if a token
|
||||
or parent id token is presented to stop a transaction. In addition, the module is responsible for managing all
|
||||
reservations and matching them with incoming tokens.
|
||||
|
||||
The module contains the logic to select a connector for incoming tokens (e.g. by waiting for a car plug in, user
|
||||
interface, random selection, etc.). Currently two selection algorithms are implemented and described in
|
||||
`Selection Algorithm`_.
|
||||
|
||||
The following flow diagram describes how an incoming token is handled by the module.
|
||||
|
||||
.. image:: token_handling.drawio.svg
|
||||
:alt: TokenHandling
|
||||
|
||||
.. note::
|
||||
|
||||
The processing of each authorization request and the respective validation runs in an individual thread. This
|
||||
allows the parallel processing of authorization requests.
|
||||
|
||||
Integration in EVerest
|
||||
======================
|
||||
|
||||
This module provides implementations for the `reservation` and the `auth` interfaces.
|
||||
|
||||
It requires connections to module(s) implementing the `token_provider`, `token_validator` and `evse_manager` interfaces.
|
||||
|
||||
The following diagram shows how it integrates with other EVerest modules.
|
||||
|
||||
.. image:: everest_integration.drawio.svg
|
||||
:alt: Integration
|
||||
|
||||
The module connections of the evse_manager requirement must be connected in the correct order in the EVerest config
|
||||
file, i.e. the module representing the EVSE with evse id 1 must listed first, EVSE with evse id 2 second and so on.
|
||||
|
||||
Selection Algorithm
|
||||
===================
|
||||
|
||||
The selection algorithm contains the logic to select one connector for an incoming token. The algorithm can be
|
||||
specified within the module config using the key `selection_algorithm`. In case the charging station has only
|
||||
one connector, the selection of a connector is pretty straight-forward because there is only one that is
|
||||
available. The selection algorithm becomes relevant in case the Auth module manages authorization requests
|
||||
for multiple connectors.
|
||||
|
||||
Three options for the selection are available:
|
||||
|
||||
* PlugEvents
|
||||
* FindFirst
|
||||
* UserInput
|
||||
|
||||
PlugEvents
|
||||
----------
|
||||
|
||||
The following flow chart describes how a connector is selected using the `PlugEvents` algorithm.
|
||||
|
||||
.. image:: plug_events_selection_algorithm.drawio.svg
|
||||
:alt: SelectionAlgorithm
|
||||
|
||||
.. note::
|
||||
|
||||
In case a user authorizes first and no EV is connected to the charger to initiate a SessionStarted event the
|
||||
processing thread waits for a SessionStarted event to select the respective connector. A Plug-In timeout may
|
||||
occur, which will require a subsequent initiation of authorization to start a charging session.
|
||||
|
||||
FindFirst
|
||||
---------
|
||||
|
||||
The `FindFirst` selection method simply selects the first available connector that has no active transaction.
|
||||
This method attempts to select a connector immediately.
|
||||
|
||||
UserInput
|
||||
---------
|
||||
|
||||
Not yet implemented.
|
||||
|
||||
Plug&Charge Authorization
|
||||
=========================
|
||||
|
||||
Please see the :doc:`Plug&Charge configuration guide </how-to-guides/configure-pnc>`
|
||||
for further information about the Plug&Charge integration in EVerest.
|
||||
@@ -0,0 +1,248 @@
|
||||
<svg host="65bd71144e" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="729px" height="417px" viewBox="-0.5 -0.5 729 417" content="<mxfile><diagram id="-amA90pGGpgJD1hGHcMB" name="Page-1">7VtZc9o6FP41PJbxgo15DFsWbsq9Q5I2fekILGMlxqK22PrQ394jWTK2MYRLS2hJMpMEHWs5kj59Z5GpmK3J8jJCU/+WujioGJq7rJjtimHYTh3+csEqEZhWIxGMI+ImIn0tGJDvWAo1KZ0RF8e5iozSgJFpXjiiYYhHLCdDUUQX+WoeDfKjTtEYbwgGIxRsSj8Rl/mJ1LG0tfwKk7GvRtY1+WSCVGUpiH3k0kVGZHYqZiuilCWfJssWDvjaqXVJ2nW3PE0Vi3DIShrcxzjqD5/4mhhagIawLaLStQvlO/qMQ/i/IKAk6DrjCwpTTteRRnz94piMwwnvX4wVzEbEHfkoYknH11IV60u/d60vv6FQf0pqKiXNdJ6p/jFbqbX12YQrpVfMJp3jyAvE8gwDOnoGkfg/mKIRCcey1sInDHMR72ABeANZRGehi11ZA0UjiSHYIbPp0ZDJsq5X61LSogHMj6tgmqim1xDI43Qgs+rwMotgjUpriid9Xp9xUPMdV1IFEU30sblDctNgsgwvMyK5RJeYTjCLVlBFPoXFTZqo49NwkvJiDUbdlHX8DBBrEnVI4n+cdr2GCHyQ26KKGdDsBFE/DLiSNOSQoV6KFexmIRRz+RwRaMi3vLsbRU2t37u4vifd9qfbPIpqr4Ain06Gs7gAGe2IgCngdhjTYMbwRYrfPJobR4SYaeYgVnNKIKZILAuxxlEhthssXQDL5eKmPbnBj3mwWPuC5QkztlKL7WzBRRYN5m+ihW2MpYPeTeyC+ZH60oj5dExDFHTWUugSVuOCmzaoEvIjCK1CV0kU8EHUJYGaLF4S9lloYMnSI39S1bSGLP+LIwLbh/m8NNEetjLThBcfRVFLFOWCYqOtcIzpLBrhjFEAM46iMVZslYj45HdCNgtJrQSSqTDCAWJknjfiZUAVY8DaoVWmwpSSkMUbOE4V+UVoD3CQyJhP4rzN3YX5mt7vPYwd2xm2f+Qxbx+fIA+xpI2/mRZrivIkLVrKI33R8tqno8XI6Pda3fqURO12HiL149Fi1SxBg/hRNeVA+lkQaMOpZSh0TahbCfQDUKZm5jk0pdSDGLS2yaD2WTLoRtQjXZM5CmZSs0ccywVawxmOOHTQzKA6wjH5nrihYpnFiWqi0fNYgEnh0cUegliIoyoAdxYkI9gHvjlNzhwE4sIL+WBCXFfAqYjwnSchd2Y297q+N0vtvT1bKGcbv3RCdzfFtIFinlaLwZ11h/MU03gFN/2sgz3byHvilm7uaXJO6InfAh4e3YepMbLtPB50bV9AvElXXFdW4ZSWxN60JI2ztCSHgfszgNuho49jtKQFcOvHA/cbc6is3CFoOM6rR6Ql/pR+niHpZhrZ2PCoPtLzcaj0/Y3wsTyqi0JWXYT8JBThP0+fohmcqQgWkxHKpd9meIaToTlKhUx8Tqp35jG+RSEaY95ZDIdKpFnhd4DjGLoY8NMoErF4LlL3GuI+nRbSksF80fMK82pDLO4EprC1fI/cKhRKlY/wBFw43qcX0cmLE1n4ol/CVPtY5D9EB8NV2jyRilGSobz0kdQg6ZbB+aYz3hleQqCr3P9tFH5h9nvuRHuuGn23QOGvcDuxbzZZnY4Ae6zEQS2eFUanJQQvBgCGzUv+f0aF95FR0hM/x3N7zXyixTZLEi1OKbPWjun2trKwD7fi+6Vrjbt6v9cKwvvetFm41tDf7zVe817DNv6Ie40Dk786wCj6cuU+Ln5MCzDa+8bjPft7cPbXtv6C7K8DGJl2boh+w4wCRva+IXiPVv789K9eFq9YbyReUSnSM80A6/bJA5bdJFMFkml/vR0+uD+K/rRzPJI5A+o4IN9XSHSk1/WHM4e1yRxvK9/3CYk41BNufchZoiRw7sxffOcNOf3eB6trenf3k8IxeIV7kDfnjxXfg6vb+74Hd0J/zAeMPFRnhvGP5eUxYhzxauSN+WO/mj0W7pnz+/2x86TVDX8sPcrnmT9WYeAJ3bEDEwZLIJ/mcNH+uuoOC+RjvBuooycM/goD1QGM3PznXTNjXEgq7f/m/rsvXzlVGqCxaXbUjp+Z2TkM3lcAb7AOXuv+Q+El8f2/UvAO7/3grVXrTuW3xqpqd88/VgXR+mtgSQ/r79KZnZ8=</diagram></mxfile>">
|
||||
<defs/>
|
||||
<g>
|
||||
<rect x="195" y="0" width="135" height="40" rx="20" ry="20" fill="rgb(255, 255, 255)" stroke="#3a414a" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 131px; height: 1px; padding-top: 20px; margin-left: 197px;">
|
||||
<div data-drawio-colors="color: #3a414a; " style="box-sizing: border-box; font-size: 0px; text-align: center; width: 129px; max-height: 34px;">
|
||||
<div style="display: inline-block; font-size: 11.7px; font-family: Helvetica; color: rgb(58, 65, 74); line-height: 1.2; pointer-events: all; width: 100%; white-space: normal; overflow-wrap: normal;">
|
||||
Id Token with multiple connector assignment
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="263" y="24" fill="#3a414a" font-family="Helvetica" font-size="12px" text-anchor="middle">
|
||||
Id Token with multiple...
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<path d="M 233 112.5 L 259.4 92.7 Q 263 90 266.6 92.7 L 319.4 132.3 Q 323 135 319.4 137.7 L 266.6 177.3 Q 263 180 259.4 177.3 L 206.6 137.7 Q 203 135 206.6 132.3 Z" fill="rgb(255, 255, 255)" stroke="#3a414a" stroke-miterlimit="10" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 116px; height: 1px; padding-top: 135px; margin-left: 205px;">
|
||||
<div data-drawio-colors="color: #3a414a; " style="box-sizing: border-box; font-size: 0px; text-align: center; width: 114px; max-height: 84px;">
|
||||
<div style="display: inline-block; font-size: 10px; font-family: Helvetica; color: rgb(58, 65, 74); line-height: 1.2; pointer-events: all; width: 100%; white-space: normal; overflow-wrap: normal;">
|
||||
Only one of assigned connectors available?
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="263" y="138" fill="#3a414a" font-family="Helvetica" font-size="10px" text-anchor="middle">
|
||||
Only one of assigned co...
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<path d="M 262.5 40.36 L 262.5 59 Q 262.5 65 262.62 71 L 262.85 83.39" fill="none" stroke="#3a414a" stroke-miterlimit="10" pointer-events="stroke"/>
|
||||
<path d="M 262.98 90.19 L 259.45 83.45 L 266.25 83.32 Z" fill="#3a414a" stroke="#3a414a" stroke-miterlimit="10" pointer-events="all"/>
|
||||
<rect x="390" y="112" width="135" height="46" rx="4.5" ry="4.5" fill="rgb(255, 255, 255)" stroke="#3a414a" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 119px; height: 1px; padding-top: 135px; margin-left: 398px;">
|
||||
<div data-drawio-colors="color: #3a414a; " style="box-sizing: border-box; font-size: 0px; text-align: center; width: 117px; max-height: 28px;">
|
||||
<div style="display: inline-block; font-size: 11.7px; font-family: Helvetica; color: rgb(58, 65, 74); line-height: 1.2; pointer-events: all; width: 100%; white-space: normal; overflow-wrap: normal;">
|
||||
Select this connector
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="458" y="139" fill="#3a414a" font-family="Helvetica" font-size="12px" text-anchor="middle">
|
||||
Select this connector
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<path d="M 321.08 135 L 381.9 135" fill="none" stroke="#3a414a" stroke-miterlimit="10" pointer-events="stroke"/>
|
||||
<path d="M 388.7 135 L 381.9 138.4 L 381.9 131.6 Z" fill="#3a414a" stroke="#3a414a" stroke-miterlimit="10" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 136px; margin-left: 356px;">
|
||||
<div data-drawio-colors="color: #333333; background-color: rgb(255, 255, 255); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
|
||||
<div style="display: inline-block; font-size: 13.3px; font-family: Helvetica; color: rgb(51, 51, 51); line-height: 1.2; pointer-events: all; font-weight: bold; background-color: rgb(255, 255, 255); white-space: nowrap;">
|
||||
Yes
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="356" y="139" fill="#333333" font-family="Helvetica" font-size="13px" text-anchor="middle" font-weight="bold">
|
||||
Yes
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<rect x="593" y="115" width="135" height="40" rx="20" ry="20" fill="rgb(255, 255, 255)" stroke="#3a414a" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 131px; height: 1px; padding-top: 135px; margin-left: 595px;">
|
||||
<div data-drawio-colors="color: #3a414a; " style="box-sizing: border-box; font-size: 0px; text-align: center; width: 129px; max-height: 34px;">
|
||||
<div style="display: inline-block; font-size: 11.7px; font-family: Helvetica; color: rgb(58, 65, 74); line-height: 1.2; pointer-events: all; width: 100%; white-space: normal; overflow-wrap: normal;">
|
||||
End
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="661" y="139" fill="#3a414a" font-family="Helvetica" font-size="12px" text-anchor="middle">
|
||||
End
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<path d="M 525.4 135 L 584.9 135" fill="none" stroke="#3a414a" stroke-miterlimit="10" pointer-events="stroke"/>
|
||||
<path d="M 591.7 135 L 584.9 138.4 L 584.9 131.6 Z" fill="#3a414a" stroke="#3a414a" stroke-miterlimit="10" pointer-events="all"/>
|
||||
<path d="M 263 178.92 L 263 223.39" fill="none" stroke="#3a414a" stroke-miterlimit="10" pointer-events="stroke"/>
|
||||
<path d="M 263 230.19 L 259.6 223.39 L 266.4 223.39 Z" fill="#3a414a" stroke="#3a414a" stroke-miterlimit="10" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 205px; margin-left: 264px;">
|
||||
<div data-drawio-colors="color: #333333; background-color: rgb(255, 255, 255); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
|
||||
<div style="display: inline-block; font-size: 13.3px; font-family: Helvetica; color: rgb(51, 51, 51); line-height: 1.2; pointer-events: all; font-weight: bold; background-color: rgb(255, 255, 255); white-space: nowrap;">
|
||||
No
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="264" y="209" fill="#333333" font-family="Helvetica" font-size="13px" text-anchor="middle" font-weight="bold">
|
||||
No
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<rect x="0" y="232" width="180" height="104" rx="4.5" ry="4.5" fill-opacity="0" fill="#ffffff" stroke="rgb(0, 0, 0)" stroke-opacity="0" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe flex-start; justify-content: unsafe flex-start; width: 176px; height: 1px; padding-top: 235px; margin-left: 3px;">
|
||||
<div data-drawio-colors="color: #3a414a; " style="box-sizing: border-box; font-size: 0px; text-align: left; width: 174px;">
|
||||
<div style="display: inline-block; font-size: 10px; font-family: Helvetica; color: rgb(58, 65, 74); line-height: 1.2; pointer-events: all; width: 100%; white-space: normal; overflow-wrap: normal;">
|
||||
A connector is in the authorization queue as soon as the EvseManager sends a SessionStarted event and no authorization has yet been presented. A connector is removed from the authorization queue when it is selected by the selector or if the connection timeout expires
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="3" y="245" fill="#3a414a" font-family="Helvetica" font-size="10px">
|
||||
A connector is in the authorization...
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<path d="M 233 252.5 L 259.4 232.7 Q 263 230 266.6 232.7 L 319.4 272.3 Q 323 275 319.4 277.7 L 266.6 317.3 Q 263 320 259.4 317.3 L 206.6 277.7 Q 203 275 206.6 272.3 Z" fill="rgb(255, 255, 255)" stroke="#3a414a" stroke-miterlimit="10" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 116px; height: 1px; padding-top: 275px; margin-left: 205px;">
|
||||
<div data-drawio-colors="color: #3a414a; " style="box-sizing: border-box; font-size: 0px; text-align: center; width: 114px; max-height: 84px;">
|
||||
<div style="display: inline-block; font-size: 10px; font-family: Helvetica; color: rgb(58, 65, 74); line-height: 1.2; pointer-events: all; width: 100%; white-space: normal; overflow-wrap: normal;">
|
||||
Connector in authorization queue?
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="263" y="278" fill="#3a414a" font-family="Helvetica" font-size="10px" text-anchor="middle">
|
||||
Connector in authorizat...
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<rect x="390" y="252" width="135" height="46" rx="4.5" ry="4.5" fill="rgb(255, 255, 255)" stroke="#3a414a" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 119px; height: 1px; padding-top: 275px; margin-left: 398px;">
|
||||
<div data-drawio-colors="color: #3a414a; " style="box-sizing: border-box; font-size: 0px; text-align: center; width: 117px; max-height: 28px;">
|
||||
<div style="display: inline-block; font-size: 11.7px; font-family: Helvetica; color: rgb(58, 65, 74); line-height: 1.2; pointer-events: all; width: 100%; white-space: normal; overflow-wrap: normal;">
|
||||
Select this connector
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="458" y="279" fill="#3a414a" font-family="Helvetica" font-size="12px" text-anchor="middle">
|
||||
Select this connector
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<path d="M 321.08 275 L 381.9 275" fill="none" stroke="#3a414a" stroke-miterlimit="10" pointer-events="stroke"/>
|
||||
<path d="M 388.7 275 L 381.9 278.4 L 381.9 271.6 Z" fill="#3a414a" stroke="#3a414a" stroke-miterlimit="10" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 276px; margin-left: 356px;">
|
||||
<div data-drawio-colors="color: #333333; background-color: rgb(255, 255, 255); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
|
||||
<div style="display: inline-block; font-size: 13.3px; font-family: Helvetica; color: rgb(51, 51, 51); line-height: 1.2; pointer-events: all; font-weight: bold; background-color: rgb(255, 255, 255); white-space: nowrap;">
|
||||
Yes
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="356" y="279" fill="#333333" font-family="Helvetica" font-size="13px" text-anchor="middle" font-weight="bold">
|
||||
Yes
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<path d="M 525.4 275 L 654.5 275 Q 660.5 275 660.5 269 L 660.5 163.05" fill="none" stroke="#3a414a" stroke-miterlimit="10" pointer-events="stroke"/>
|
||||
<path d="M 660.5 156.25 L 663.9 163.05 L 657.1 163.05 Z" fill="#3a414a" stroke="#3a414a" stroke-miterlimit="10" pointer-events="all"/>
|
||||
<rect x="195" y="370" width="135" height="46" rx="4.5" ry="4.5" fill="rgb(255, 255, 255)" stroke="#3a414a" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 119px; height: 1px; padding-top: 393px; margin-left: 203px;">
|
||||
<div data-drawio-colors="color: #3a414a; " style="box-sizing: border-box; font-size: 0px; text-align: center; width: 117px; max-height: 28px;">
|
||||
<div style="display: inline-block; font-size: 11.7px; font-family: Helvetica; color: rgb(58, 65, 74); line-height: 1.2; pointer-events: all; width: 100%; white-space: normal; overflow-wrap: normal;">
|
||||
Wait for next SessionStarted Event
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="263" y="397" fill="#3a414a" font-family="Helvetica" font-size="12px" text-anchor="middle">
|
||||
Wait for next Sessio...
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<path d="M 263 318.92 L 263 339 Q 263 345 262.88 351 L 262.66 361.94" fill="none" stroke="#3a414a" stroke-miterlimit="10" pointer-events="stroke"/>
|
||||
<path d="M 262.52 368.74 L 259.26 361.87 L 266.06 362.01 Z" fill="#3a414a" stroke="#3a414a" stroke-miterlimit="10" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 346px; margin-left: 264px;">
|
||||
<div data-drawio-colors="color: #333333; background-color: rgb(255, 255, 255); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
|
||||
<div style="display: inline-block; font-size: 13.3px; font-family: Helvetica; color: rgb(51, 51, 51); line-height: 1.2; pointer-events: all; font-weight: bold; background-color: rgb(255, 255, 255); white-space: nowrap;">
|
||||
No
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="264" y="349" fill="#333333" font-family="Helvetica" font-size="13px" text-anchor="middle" font-weight="bold">
|
||||
No
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<rect x="390" y="370" width="135" height="46" rx="4.5" ry="4.5" fill="rgb(255, 255, 255)" stroke="#3a414a" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 119px; height: 1px; padding-top: 393px; margin-left: 398px;">
|
||||
<div data-drawio-colors="color: #3a414a; " style="box-sizing: border-box; font-size: 0px; text-align: center; width: 117px; max-height: 28px;">
|
||||
<div style="display: inline-block; font-size: 11.7px; font-family: Helvetica; color: rgb(58, 65, 74); line-height: 1.2; pointer-events: all; width: 100%; white-space: normal; overflow-wrap: normal;">
|
||||
Select this connector
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="458" y="397" fill="#3a414a" font-family="Helvetica" font-size="12px" text-anchor="middle">
|
||||
Select this connector
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<path d="M 330.4 393 L 381.9 393" fill="none" stroke="#3a414a" stroke-miterlimit="10" pointer-events="stroke"/>
|
||||
<path d="M 388.7 393 L 381.9 396.4 L 381.9 389.6 Z" fill="#3a414a" stroke="#3a414a" stroke-miterlimit="10" pointer-events="all"/>
|
||||
<path d="M 525.4 393 L 692.3 393 Q 698.3 393 698.3 387 L 698.3 163.05" fill="none" stroke="#3a414a" stroke-miterlimit="10" pointer-events="stroke"/>
|
||||
<path d="M 698.3 156.25 L 701.7 163.05 L 694.9 163.05 Z" fill="#3a414a" stroke="#3a414a" stroke-miterlimit="10" pointer-events="all"/>
|
||||
</g>
|
||||
<switch>
|
||||
<g requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"/>
|
||||
<a transform="translate(0,-5)" xlink:href="https://www.diagrams.net/doc/faq/svg-export-text-problems" target="_blank">
|
||||
<text text-anchor="middle" font-size="10px" x="50%" y="100%">
|
||||
Viewer does not support full SVG 1.1
|
||||
</text>
|
||||
</a>
|
||||
</switch>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 23 KiB |
191
tools/EVerest-main/modules/EVSE/Auth/docs/state_chart.drawio
Normal file
191
tools/EVerest-main/modules/EVSE/Auth/docs/state_chart.drawio
Normal file
@@ -0,0 +1,191 @@
|
||||
<mxfile host="65bd71144e">
|
||||
<diagram id="enTwcsW3kJKdFbbsoOIB" name="Page-1">
|
||||
<mxGraphModel dx="910" dy="576" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="850" pageHeight="1100" math="0" shadow="0">
|
||||
<root>
|
||||
<mxCell id="0"/>
|
||||
<mxCell id="1" parent="0"/>
|
||||
<mxCell id="50" style="edgeStyle=none;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" parent="1" target="49" edge="1">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="1120" y="524" as="sourcePoint"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="75" style="edgeStyle=none;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;entryX=0;entryY=0.25;entryDx=0;entryDy=0;" edge="1" parent="1" source="7" target="73">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points">
|
||||
<mxPoint x="180" y="250"/>
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="76" value="TransactionStarted" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="75">
|
||||
<mxGeometry x="-0.3167" y="-4" relative="1" as="geometry">
|
||||
<mxPoint x="29" y="-45" as="offset"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="77" style="edgeStyle=none;html=1;exitX=1;exitY=0.25;exitDx=0;exitDy=0;entryX=0;entryY=0.25;entryDx=0;entryDy=0;" edge="1" parent="1" source="7" target="71">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="78" value="Faulted" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="77">
|
||||
<mxGeometry x="-0.0286" relative="1" as="geometry">
|
||||
<mxPoint x="1" y="-10" as="offset"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="79" style="edgeStyle=none;html=1;exitX=0.75;exitY=1;exitDx=0;exitDy=0;entryX=0;entryY=0.25;entryDx=0;entryDy=0;" edge="1" parent="1" source="7" target="11">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points">
|
||||
<mxPoint x="210" y="490"/>
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="80" value="Disable" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="79">
|
||||
<mxGeometry x="0.3063" y="2" relative="1" as="geometry">
|
||||
<mxPoint x="11" y="-8" as="offset"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="7" value="Available" style="rounded=1;whiteSpace=wrap;html=1;arcSize=40;fontColor=#000000;fillColor=#ffffc0;strokeColor=#ff0000;" parent="1" vertex="1">
|
||||
<mxGeometry x="120" y="360" width="120" height="40" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="9" value="" style="ellipse;html=1;shape=startState;fillColor=#000000;strokeColor=#ff0000;" parent="1" vertex="1">
|
||||
<mxGeometry x="70" y="365" width="30" height="30" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="10" value="" style="edgeStyle=orthogonalEdgeStyle;html=1;verticalAlign=bottom;endArrow=open;endSize=8;strokeColor=#ff0000;entryX=0;entryY=0.5;entryDx=0;entryDy=0;exitX=0.867;exitY=0.5;exitDx=0;exitDy=0;exitPerimeter=0;" parent="1" source="9" target="7" edge="1">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="210" y="330" as="targetPoint"/>
|
||||
<mxPoint x="110" y="390" as="sourcePoint"/>
|
||||
<Array as="points"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="89" style="edgeStyle=none;html=1;exitX=0;exitY=0.75;exitDx=0;exitDy=0;entryX=0.5;entryY=1;entryDx=0;entryDy=0;" edge="1" parent="1" source="11" target="7">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points">
|
||||
<mxPoint x="180" y="510"/>
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="90" value="Enable" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="89">
|
||||
<mxGeometry x="-0.2428" y="1" relative="1" as="geometry">
|
||||
<mxPoint x="35" y="9" as="offset"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="91" style="edgeStyle=none;html=1;exitX=1;exitY=0.25;exitDx=0;exitDy=0;entryX=0.25;entryY=1;entryDx=0;entryDy=0;" edge="1" parent="1" source="11" target="34">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points">
|
||||
<mxPoint x="610" y="490"/>
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="92" value="Faulted" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="91">
|
||||
<mxGeometry x="-0.1389" y="-1" relative="1" as="geometry">
|
||||
<mxPoint x="-23" y="-11" as="offset"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="11" value="Unavailable" style="rounded=1;whiteSpace=wrap;html=1;arcSize=40;fontColor=#000000;fillColor=#ffffc0;strokeColor=#ff0000;" parent="1" vertex="1">
|
||||
<mxGeometry x="320" y="480" width="120" height="40" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="93" style="edgeStyle=none;html=1;exitX=0;exitY=0.75;exitDx=0;exitDy=0;entryX=1;entryY=0.75;entryDx=0;entryDy=0;" edge="1" parent="1" source="34" target="71">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="94" value="Enable" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="93">
|
||||
<mxGeometry x="0.1894" y="1" relative="1" as="geometry">
|
||||
<mxPoint x="13" y="9" as="offset"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="95" style="edgeStyle=none;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=1;entryY=0.75;entryDx=0;entryDy=0;" edge="1" parent="1" source="34" target="11">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points">
|
||||
<mxPoint x="640" y="510"/>
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="96" value="ErrorCleared" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="95">
|
||||
<mxGeometry x="0.1272" y="3" relative="1" as="geometry">
|
||||
<mxPoint x="-35" y="7" as="offset"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="34" value="UnavailableFaulted" style="rounded=1;whiteSpace=wrap;html=1;arcSize=40;fontColor=#000000;fillColor=#ffffc0;strokeColor=#ff0000;" parent="1" vertex="1">
|
||||
<mxGeometry x="580" y="360" width="120" height="40" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="49" value="Matched" style="rounded=1;whiteSpace=wrap;html=1;arcSize=40;fontColor=#000000;fillColor=#ffffc0;strokeColor=#ff0000;" parent="1" vertex="1">
|
||||
<mxGeometry x="1320" y="400" width="140" height="40" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="85" style="edgeStyle=none;html=1;exitX=0;exitY=0.75;exitDx=0;exitDy=0;entryX=1;entryY=0.75;entryDx=0;entryDy=0;" edge="1" parent="1" source="71" target="7">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="86" value="ErrorCleared" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="85">
|
||||
<mxGeometry x="-0.1888" y="-2" relative="1" as="geometry">
|
||||
<mxPoint x="-7" y="12" as="offset"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="87" style="edgeStyle=none;html=1;exitX=1;exitY=0.25;exitDx=0;exitDy=0;entryX=0;entryY=0.25;entryDx=0;entryDy=0;" edge="1" parent="1" source="71" target="34">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="580" y="370" as="targetPoint"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="88" value="Disable" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="87">
|
||||
<mxGeometry x="-0.1806" y="4" relative="1" as="geometry">
|
||||
<mxPoint x="11" y="-6" as="offset"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="71" value="Faulted" style="rounded=1;whiteSpace=wrap;html=1;arcSize=40;fontColor=#000000;fillColor=#ffffc0;strokeColor=#ff0000;" vertex="1" parent="1">
|
||||
<mxGeometry x="320" y="360" width="120" height="40" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="81" style="edgeStyle=none;html=1;exitX=0;exitY=0.75;exitDx=0;exitDy=0;entryX=0.75;entryY=0;entryDx=0;entryDy=0;" edge="1" parent="1" source="73" target="7">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points">
|
||||
<mxPoint x="210" y="270"/>
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="82" value="SessionFinished" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="81">
|
||||
<mxGeometry x="0.1526" relative="1" as="geometry">
|
||||
<mxPoint x="30" y="-8" as="offset"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="83" style="edgeStyle=none;html=1;entryX=0;entryY=0.25;entryDx=0;entryDy=0;exitX=1;exitY=0.25;exitDx=0;exitDy=0;" edge="1" parent="1" source="73" target="74">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="410" y="310" as="sourcePoint"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="84" value="Faulted" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="83">
|
||||
<mxGeometry x="-0.3833" y="4" relative="1" as="geometry">
|
||||
<mxPoint x="23" y="-5" as="offset"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="73" value="Occupied" style="rounded=1;whiteSpace=wrap;html=1;arcSize=40;fontColor=#000000;fillColor=#ffffc0;strokeColor=#ff0000;" vertex="1" parent="1">
|
||||
<mxGeometry x="290" y="240" width="120" height="40" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="97" style="edgeStyle=none;html=1;exitX=0.25;exitY=1;exitDx=0;exitDy=0;entryX=0.75;entryY=0;entryDx=0;entryDy=0;" edge="1" parent="1" source="74" target="71">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="558.2799999999997" y="279.9999999999999" as="sourcePoint"/>
|
||||
<mxPoint x="410" y="357.32000000000016" as="targetPoint"/>
|
||||
<Array as="points">
|
||||
<mxPoint x="550" y="320"/>
|
||||
<mxPoint x="410" y="320"/>
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="98" value="SessionFinished" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="97">
|
||||
<mxGeometry x="0.4476" y="1" relative="1" as="geometry">
|
||||
<mxPoint x="49" y="-1" as="offset"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="99" style="edgeStyle=none;html=1;exitX=0;exitY=0.75;exitDx=0;exitDy=0;entryX=1;entryY=0.75;entryDx=0;entryDy=0;" edge="1" parent="1" source="74" target="73">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="100" value="ErrorCleared" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="99">
|
||||
<mxGeometry x="0.3106" y="-4" relative="1" as="geometry">
|
||||
<mxPoint x="16" y="14" as="offset"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="74" value="FaultedOccupied" style="rounded=1;whiteSpace=wrap;html=1;arcSize=40;fontColor=#000000;fillColor=#ffffc0;strokeColor=#ff0000;" vertex="1" parent="1">
|
||||
<mxGeometry x="520" y="240" width="120" height="40" as="geometry"/>
|
||||
</mxCell>
|
||||
</root>
|
||||
</mxGraphModel>
|
||||
</diagram>
|
||||
</mxfile>
|
||||
@@ -0,0 +1,430 @@
|
||||
<svg host="65bd71144e" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="541px" height="946px" viewBox="-0.5 -0.5 541 946" content="<mxfile><diagram id="o8SZXadFU9JfIY1HxVTB" name="Page-1">7Vxbe6I6FP01Pk4/IKjwKF56odqZ0vvLfBGi0iJpAa8P/e0ngaACShkrrYfSh7ZsAoRk7bWzV0IqoDmenzrwddTFBrIqAmfMK6BVEYSaVCe/qWERGEBVDgxDxzQCE782aOYSMSPHrBPTQG6koIex5ZmvUaOObRvpXsQGHQfPosUG2Io+9RUOUcKg6dBKWu9NwxsFVqnKre1nyByOwifzHDszhmFhZnBH0MCzDRNoV0DTwdgL/hvPm8iibRe2S3BdZ8fZVcUcZHtbLrh1kXPVf6ZtInAW7JNu8QudG+T4Br8gO7iBNdFNQx9BxwtKn4dPHyrqTK29CZdns6Bk+GRhVflVpVxvETbYyBvTJ/EVoOApcgaW/859C+svxOT/1V6hbtpDVmo2Mj1ETfQGMwIiYnPwxDaQwUpAR2fAIM0OlAG2PXbM8yd1ZmliCzt+FQCAIi9CYndXDwInEj32HPLiW0v6Z65oeY8ilXZjaA37nfPvkWx21hPkZT003zCxJjpFeIw8Z0GKsLOSUA0uYT5RFYPD2RpgPGBFRhvgEhmSIMP0cHXndbeTf1ivhIcbQEgFRnPi0HeyaEUnLml8oUZaiOf8dyaO41DXc6DtQt0zsV0BnXQAwZGiSnBoDqv6SRRA4AsANMLj/sT9UrTEQNt3sTXxUGMF3iiU5RzxBUAEX7y4BWAhLW0CTM4VYOlw6RO4dN8Hj9Xn+0kULmJWuDwjz1uEjS3tQMYmHsCBOGEXXfGk3goySEBh9cWON8JDbEOrvbaSW5LWaNBgRYrY2KY2ZBuhJYQ+MXVMK3xZNDe9B78GVXb0SM+ccJzMjn8jxyTdh+h7cf71pCs3LqGHj/4hF1SUGuIX7YSjiyeOjjYiAmEH6AyRt+HjAkdfPhWym5DktkByZXSQBT1zGg3L24DqP4O0HVxsFHjFpu25CRyvKvJZ7oQMlvj17yZHkgrQkmjqIn9IYJPhgpPuBwKlzepTyxnPHqJ+UM2fNvchS/n/TJU8J4hRrqxVMwbj2vdx5Z2pqLBvNHo3vZcoRmr5ceUJ2AIH/ycsyR7EF4JVZUnc4NU1y+5k1V+ERzkQJdYVz+5FqyBJq9VC0moiuWFJ4xRaE1azR+SyBlrDmfg4uYGygWoHueYS9v0CtJl9j1Kg/jL0wRTi0UADOLHotdAyh2Qo29JJP9DOUSh1mCT9a7ATY9MwfDjFEZ7qCRGfSfZ1LTNNZe6eHZSzi1+u0ZhEidiIPpVznGdF/XXfOPPm3UWUc6QyLh0+LgmAi8Wl2vHHpQuCkduG0r4CXj2KEbkcw6dEGz6MHJ+LNqIkfyreVJPxRipkvNmF7oYPTCYlYoc8iMO6Pnk1KS4+kDv6L4p6ecfdvd3Mfkexz3NZwV/qHQfRO0SunuTKr9c72raRDpmRpah2tVG7bvbkGGT4L4BMoTXWRAAFnJwxgOYKipbp6tAhwOhcI3Y2qyJ/T+CyvF9OlT/wMgaXL5Dkf94QLC4NiEJWBH3jEKw/VtTzB+se/BXmMZBklt3LMdj+Y7DPZfz8FiWV53/UGKyDnZlPkBFe5Dwc6AGmAYORWZoTTIgTjFBj+f68MGNOkHkyoWTKveczs4faXIlSQ1Zga4YD+gqd2aQNLnDByUCjt/DQ1NMR5b4qKu5yz7Z70o0hqpTl80dUTeaPAVGbQLpGpNA0Q2Jovynq03tbmtafbmLQyazWl4nhQRLDurSFmL4+MQwCG6Q0NIaePvoQQu8EQud/LtqWdi7EIFQvIZRzFlCvHSOG2IwznJCBsENbYY+p5jNHUTnpqTdt19oxWJWafv5BTQZHEdTSMaJ5inpa5c5u+rP3GEZy1PTLueaPVvBwZPBwyMSTQSySeDIVtWCJZ2KyOSTvos4283JmpsprujmdY54Ixwxq7nAhLm6jHCNk1sFLjvmHVYKUcaSPOSayStAXu6TDk0wxZxiTJCMkSKaHi8Mx/zAa+h6OmU4U9f7SaS5uBtUYx+Soi/9ojmGritM5RgIRAZ2q8PXPkUx9C8kIP4RkxGKTDDhykukSknkThhezt0EsWcq+eLskmUMPZPiDz9Jto5ijHcewKvymhg1diwdRXUtmX6Gt/SG45SHpKVz3WtBEK9R3jpaf7gg/9Re9W2SosW9LhMw68o9cHJD8yOpf06fVh1n7s460hXWOdm3AdtaR5OiEjMzm9Tq7WEqQpbQLDsBS+3kSmJN0YjC6sG4uWzFPyiydl560lyf5QsTnXOl/5Um5rLJJR7fXaWq/+KveS9fTY+guRf8CCXJbPjDjxUL6QWIsCpKif6FS5WPX/E9Pm5rRfZ6pb9exte+g1PwL9A1ruI/PT5TjQFLzL1S+C75f9L+jK4IpvNgyYTiFphW03AdrqpTzlnbTuV12eliLEVC5P03lS5flVcWjWFKVjpdrgpelgTTtYhn7sAKUG9Tkm/F9foOacFQbiUJcIaPQfui+vGhptdMzgx/UY0sBQTlzcSwZ3wH8YMtoDBRzq6bkaCw5+1CkjA8c++TDH0IxsHvxoNR7pzGKyXHyoaSYLxeVtqlKP2TfIiAVPOMLN2Y6VpK5JSQjT2pXnBr/Tg+UyvU3LldPzIXmwDE/RFUSi61cg2NXrltqS9M8zVjU3mL7Gos5KteFII4jEAK27HEVLksvGHEQ03pz+eAO6x36Qfs/</diagram></mxfile>">
|
||||
<defs/>
|
||||
<g>
|
||||
<rect x="0" y="0" width="135" height="40" rx="20" ry="20" fill="rgb(255, 255, 255)" stroke="#3a414a" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 131px; height: 1px; padding-top: 20px; margin-left: 2px;">
|
||||
<div data-drawio-colors="color: #3a414a; " style="box-sizing: border-box; font-size: 0px; text-align: center; width: 129px; max-height: 34px;">
|
||||
<div style="display: inline-block; font-size: 11.7px; font-family: Helvetica; color: rgb(58, 65, 74); line-height: 1.2; pointer-events: all; width: 100%; white-space: normal; overflow-wrap: normal;">
|
||||
Id Token
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="68" y="24" fill="#3a414a" font-family="Helvetica" font-size="12px" text-anchor="middle">
|
||||
Id Token
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<path d="M 38 112.5 L 64.4 92.7 Q 68 90 71.6 92.7 L 124.4 132.3 Q 128 135 124.4 137.7 L 71.6 177.3 Q 68 180 64.4 177.3 L 11.6 137.7 Q 8 135 11.6 132.3 Z" fill="rgb(255, 255, 255)" stroke="#3a414a" stroke-miterlimit="10" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 116px; height: 1px; padding-top: 135px; margin-left: 10px;">
|
||||
<div data-drawio-colors="color: #3a414a; " style="box-sizing: border-box; font-size: 0px; text-align: center; width: 114px; max-height: 84px;">
|
||||
<div style="display: inline-block; font-size: 11.7px; font-family: Helvetica; color: rgb(58, 65, 74); line-height: 1.2; pointer-events: all; width: 100%; white-space: normal; overflow-wrap: normal;">
|
||||
Currently used
|
||||
<br/>
|
||||
for transaction?
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="68" y="139" fill="#3a414a" font-family="Helvetica" font-size="12px" text-anchor="middle">
|
||||
Currently used...
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<path d="M 67.5 40.36 L 67.5 59 Q 67.5 65 67.62 71 L 67.85 83.39" fill="none" stroke="#3a414a" stroke-miterlimit="10" pointer-events="stroke"/>
|
||||
<path d="M 67.98 90.19 L 64.45 83.45 L 71.25 83.32 Z" fill="#3a414a" stroke="#3a414a" stroke-miterlimit="10" pointer-events="all"/>
|
||||
<rect x="199" y="111" width="135" height="46" rx="4.5" ry="4.5" fill="rgb(255, 255, 255)" stroke="#3a414a" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 119px; height: 1px; padding-top: 134px; margin-left: 207px;">
|
||||
<div data-drawio-colors="color: #3a414a; " style="box-sizing: border-box; font-size: 0px; text-align: center; width: 117px; max-height: 28px;">
|
||||
<div style="display: inline-block; font-size: 11.7px; font-family: Helvetica; color: rgb(58, 65, 74); line-height: 1.2; pointer-events: all; width: 100%; white-space: normal; overflow-wrap: normal;">
|
||||
Call stop_transaction at evse manager
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="267" y="138" fill="#3a414a" font-family="Helvetica" font-size="12px" text-anchor="middle">
|
||||
Call stop_transactio...
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<path d="M 126.08 135 L 157.5 135 Q 163.5 135 163.5 134.5 L 163.5 134.25 Q 163.5 134 169.5 134 L 190.9 134" fill="none" stroke="#3a414a" stroke-miterlimit="10" pointer-events="stroke"/>
|
||||
<path d="M 197.7 134 L 190.9 137.4 L 190.9 130.6 Z" fill="#3a414a" stroke="#3a414a" stroke-miterlimit="10" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 136px; margin-left: 164px;">
|
||||
<div data-drawio-colors="color: #333333; background-color: rgb(255, 255, 255); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
|
||||
<div style="display: inline-block; font-size: 13.3px; font-family: Helvetica; color: rgb(51, 51, 51); line-height: 1.2; pointer-events: all; font-weight: bold; background-color: rgb(255, 255, 255); white-space: nowrap;">
|
||||
Yes
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="164" y="140" fill="#333333" font-family="Helvetica" font-size="13px" text-anchor="middle" font-weight="bold">
|
||||
Yes
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<rect x="405" y="112" width="135" height="46" rx="4.5" ry="4.5" fill="rgb(255, 255, 255)" stroke="#3a414a" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 119px; height: 1px; padding-top: 135px; margin-left: 413px;">
|
||||
<div data-drawio-colors="color: #3a414a; " style="box-sizing: border-box; font-size: 0px; text-align: center; width: 117px; max-height: 28px;">
|
||||
<div style="display: inline-block; font-size: 11.7px; font-family: Helvetica; color: rgb(58, 65, 74); line-height: 1.2; pointer-events: all; width: 100%; white-space: normal; overflow-wrap: normal;">
|
||||
Remove transaction
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="473" y="139" fill="#3a414a" font-family="Helvetica" font-size="12px" text-anchor="middle">
|
||||
Remove transaction
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<path d="M 334.4 134 L 363.5 134 Q 369.5 134 375.5 134.08 L 396.9 134.39" fill="none" stroke="#3a414a" stroke-miterlimit="10" pointer-events="stroke"/>
|
||||
<path d="M 403.7 134.48 L 396.85 137.79 L 396.95 130.99 Z" fill="#3a414a" stroke="#3a414a" stroke-miterlimit="10" pointer-events="all"/>
|
||||
<path d="M 38 375.5 L 64.4 355.7 Q 68 353 71.6 355.7 L 124.4 395.3 Q 128 398 124.4 400.7 L 71.6 440.3 Q 68 443 64.4 440.3 L 11.6 400.7 Q 8 398 11.6 395.3 Z" fill="rgb(255, 255, 255)" stroke="#3a414a" stroke-miterlimit="10" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 116px; height: 1px; padding-top: 398px; margin-left: 10px;">
|
||||
<div data-drawio-colors="color: #3a414a; " style="box-sizing: border-box; font-size: 0px; text-align: center; width: 114px; max-height: 84px;">
|
||||
<div style="display: inline-block; font-size: 11.7px; font-family: Helvetica; color: rgb(58, 65, 74); line-height: 1.2; pointer-events: all; width: 100%; white-space: normal; overflow-wrap: normal;">
|
||||
All connectors occupied?
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="68" y="402" fill="#3a414a" font-family="Helvetica" font-size="12px" text-anchor="middle">
|
||||
All connectors occup...
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<rect x="405" y="255" width="135" height="40" rx="20" ry="20" fill="rgb(255, 255, 255)" stroke="#3a414a" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 131px; height: 1px; padding-top: 275px; margin-left: 407px;">
|
||||
<div data-drawio-colors="color: #3a414a; " style="box-sizing: border-box; font-size: 0px; text-align: center; width: 129px; max-height: 34px;">
|
||||
<div style="display: inline-block; font-size: 11.7px; font-family: Helvetica; color: rgb(58, 65, 74); line-height: 1.2; pointer-events: all; width: 100%; white-space: normal; overflow-wrap: normal;">
|
||||
End
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="473" y="279" fill="#3a414a" font-family="Helvetica" font-size="12px" text-anchor="middle">
|
||||
End
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<rect x="199" y="375" width="135" height="46" rx="4.5" ry="4.5" fill="rgb(255, 255, 255)" stroke="#3a414a" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 119px; height: 1px; padding-top: 398px; margin-left: 207px;">
|
||||
<div data-drawio-colors="color: #3a414a; " style="box-sizing: border-box; font-size: 0px; text-align: center; width: 117px; max-height: 28px;">
|
||||
<div style="display: inline-block; font-size: 11.7px; font-family: Helvetica; color: rgb(58, 65, 74); line-height: 1.2; pointer-events: all; width: 100%; white-space: normal; overflow-wrap: normal;">
|
||||
Discard/Reject Id Token
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="267" y="402" fill="#3a414a" font-family="Helvetica" font-size="12px" text-anchor="middle">
|
||||
Discard/Reject Id To...
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<path d="M 334.4 398 L 363.5 398 Q 369.5 398 369.5 392 L 369.5 281 Q 369.5 275 375.5 275 L 396.9 275" fill="none" stroke="#3a414a" stroke-miterlimit="10" pointer-events="stroke"/>
|
||||
<path d="M 403.7 275 L 396.9 278.4 L 396.9 271.6 Z" fill="#3a414a" stroke="#3a414a" stroke-miterlimit="10" pointer-events="all"/>
|
||||
<rect x="0" y="255" width="135" height="46" rx="4.5" ry="4.5" fill="rgb(255, 255, 255)" stroke="#3a414a" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 119px; height: 1px; padding-top: 278px; margin-left: 8px;">
|
||||
<div data-drawio-colors="color: #3a414a; " style="box-sizing: border-box; font-size: 0px; text-align: center; width: 117px; max-height: 28px;">
|
||||
<div style="display: inline-block; font-size: 11.7px; font-family: Helvetica; color: rgb(58, 65, 74); line-height: 1.2; pointer-events: all; width: 100%; white-space: normal; overflow-wrap: normal;">
|
||||
Forward Id Token to validators
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="68" y="282" fill="#3a414a" font-family="Helvetica" font-size="12px" text-anchor="middle">
|
||||
Forward Id Token to...
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<rect x="0" y="637" width="135" height="46" rx="4.5" ry="4.5" fill="rgb(255, 255, 255)" stroke="#3a414a" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 119px; height: 1px; padding-top: 660px; margin-left: 8px;">
|
||||
<div data-drawio-colors="color: #3a414a; " style="box-sizing: border-box; font-size: 0px; text-align: center; width: 117px; max-height: 28px;">
|
||||
<div style="display: inline-block; font-size: 11.7px; font-family: Helvetica; color: rgb(58, 65, 74); line-height: 1.2; pointer-events: all; width: 100%; white-space: normal; overflow-wrap: normal;">
|
||||
Select Connector using Selection logic
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="68" y="664" fill="#3a414a" font-family="Helvetica" font-size="12px" text-anchor="middle">
|
||||
Select Connector usi...
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<path d="M 38 757.5 L 64.4 737.7 Q 68 735 71.6 737.7 L 124.4 777.3 Q 128 780 124.4 782.7 L 71.6 822.3 Q 68 825 64.4 822.3 L 11.6 782.7 Q 8 780 11.6 777.3 Z" fill="rgb(255, 255, 255)" stroke="#3a414a" stroke-miterlimit="10" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 116px; height: 1px; padding-top: 780px; margin-left: 10px;">
|
||||
<div data-drawio-colors="color: #3a414a; " style="box-sizing: border-box; font-size: 0px; text-align: center; width: 114px; max-height: 84px;">
|
||||
<div style="display: inline-block; font-size: 11.7px; font-family: Helvetica; color: rgb(58, 65, 74); line-height: 1.2; pointer-events: all; width: 100%; white-space: normal; overflow-wrap: normal;">
|
||||
Connector Reserved?
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="68" y="784" fill="#3a414a" font-family="Helvetica" font-size="12px" text-anchor="middle">
|
||||
Connector Reserved?
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<path d="M 281 757.5 L 307.4 737.7 Q 311 735 314.6 737.7 L 367.4 777.3 Q 371 780 367.4 782.7 L 314.6 822.3 Q 311 825 307.4 822.3 L 254.6 782.7 Q 251 780 254.6 777.3 Z" fill="rgb(255, 255, 255)" stroke="#3a414a" stroke-miterlimit="10" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 116px; height: 1px; padding-top: 780px; margin-left: 253px;">
|
||||
<div data-drawio-colors="color: #3a414a; " style="box-sizing: border-box; font-size: 0px; text-align: center; width: 114px; max-height: 84px;">
|
||||
<div style="display: inline-block; font-size: 11.7px; font-family: Helvetica; color: rgb(58, 65, 74); line-height: 1.2; pointer-events: all; width: 100%; white-space: normal; overflow-wrap: normal;">
|
||||
Id Tag match?
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="311" y="784" fill="#3a414a" font-family="Helvetica" font-size="12px" text-anchor="middle">
|
||||
Id Tag match?
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<rect x="0" y="877" width="135" height="46" rx="4.5" ry="4.5" fill="rgb(255, 255, 255)" stroke="#3a414a" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 119px; height: 1px; padding-top: 900px; margin-left: 8px;">
|
||||
<div data-drawio-colors="color: #3a414a; " style="box-sizing: border-box; font-size: 0px; text-align: center; width: 117px; max-height: 28px;">
|
||||
<div style="display: inline-block; font-size: 11.7px; font-family: Helvetica; color: rgb(58, 65, 74); line-height: 1.2; pointer-events: all; width: 100%; white-space: normal; overflow-wrap: normal;">
|
||||
Call authorize at evse manager
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="68" y="904" fill="#3a414a" font-family="Helvetica" font-size="12px" text-anchor="middle">
|
||||
Call authorize at ev...
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<path d="M 126.08 780 L 245.23 780" fill="none" stroke="#3a414a" stroke-miterlimit="10" pointer-events="stroke"/>
|
||||
<path d="M 252.03 780 L 245.23 783.4 L 245.23 776.6 Z" fill="#3a414a" stroke="#3a414a" stroke-miterlimit="10" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 781px; margin-left: 190px;">
|
||||
<div data-drawio-colors="color: #333333; background-color: rgb(255, 255, 255); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
|
||||
<div style="display: inline-block; font-size: 13.3px; font-family: Helvetica; color: rgb(51, 51, 51); line-height: 1.2; pointer-events: all; font-weight: bold; background-color: rgb(255, 255, 255); white-space: nowrap;">
|
||||
Yes
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="190" y="784" fill="#333333" font-family="Helvetica" font-size="13px" text-anchor="middle" font-weight="bold">
|
||||
Yes
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<path d="M 68 823.92 L 68 845 Q 68 851 67.88 857 L 67.65 868.94" fill="none" stroke="#3a414a" stroke-miterlimit="10" pointer-events="stroke"/>
|
||||
<path d="M 67.52 875.74 L 64.25 868.87 L 71.05 869.01 Z" fill="#3a414a" stroke="#3a414a" stroke-miterlimit="10" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 852px; margin-left: 69px;">
|
||||
<div data-drawio-colors="color: #333333; background-color: rgb(255, 255, 255); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
|
||||
<div style="display: inline-block; font-size: 13.3px; font-family: Helvetica; color: rgb(51, 51, 51); line-height: 1.2; pointer-events: all; font-weight: bold; background-color: rgb(255, 255, 255); white-space: nowrap;">
|
||||
No
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="69" y="855" fill="#333333" font-family="Helvetica" font-size="13px" text-anchor="middle" font-weight="bold">
|
||||
No
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<path d="M 311 736.08 L 311 584 Q 311 578 311.02 572 L 311.43 429.02" fill="none" stroke="#3a414a" stroke-miterlimit="10" pointer-events="stroke"/>
|
||||
<path d="M 311.45 422.22 L 314.83 429.03 L 308.03 429.01 Z" fill="#3a414a" stroke="#3a414a" stroke-miterlimit="10" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 579px; margin-left: 311px;">
|
||||
<div data-drawio-colors="color: #333333; background-color: rgb(255, 255, 255); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
|
||||
<div style="display: inline-block; font-size: 13.3px; font-family: Helvetica; color: rgb(51, 51, 51); line-height: 1.2; pointer-events: all; font-weight: bold; background-color: rgb(255, 255, 255); white-space: nowrap;">
|
||||
No
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="311" y="582" fill="#333333" font-family="Helvetica" font-size="13px" text-anchor="middle" font-weight="bold">
|
||||
No
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<path d="M 311 823.92 L 311 894 Q 311 900 305 900 L 143.1 900" fill="none" stroke="#3a414a" stroke-miterlimit="10" pointer-events="stroke"/>
|
||||
<path d="M 136.3 900 L 143.1 896.6 L 143.1 903.4 Z" fill="#3a414a" stroke="#3a414a" stroke-miterlimit="10" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 901px; margin-left: 262px;">
|
||||
<div data-drawio-colors="color: #333333; background-color: rgb(255, 255, 255); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
|
||||
<div style="display: inline-block; font-size: 13.3px; font-family: Helvetica; color: rgb(51, 51, 51); line-height: 1.2; pointer-events: all; font-weight: bold; background-color: rgb(255, 255, 255); white-space: nowrap;">
|
||||
Yes
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="262" y="904" fill="#333333" font-family="Helvetica" font-size="13px" text-anchor="middle" font-weight="bold">
|
||||
Yes
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<path d="M 67.5 923.37 L 67.5 931 Q 67.5 937 73.5 937 L 466.5 937 Q 472.5 937 472.5 931 L 472.5 303.05" fill="none" stroke="#3a414a" stroke-miterlimit="10" pointer-events="stroke"/>
|
||||
<path d="M 472.5 296.25 L 475.9 303.05 L 469.1 303.05 Z" fill="#3a414a" stroke="#3a414a" stroke-miterlimit="10" pointer-events="all"/>
|
||||
<path d="M 472.5 158.37 L 472.5 246.95" fill="none" stroke="#3a414a" stroke-miterlimit="10" pointer-events="stroke"/>
|
||||
<path d="M 472.5 253.75 L 469.1 246.95 L 475.9 246.95 Z" fill="#3a414a" stroke="#3a414a" stroke-miterlimit="10" pointer-events="all"/>
|
||||
<path d="M 68 178.92 L 68 211.5 Q 68 217.5 67.92 223.5 L 67.6 246.94" fill="none" stroke="#3a414a" stroke-miterlimit="10" pointer-events="stroke"/>
|
||||
<path d="M 67.51 253.74 L 64.2 246.89 L 71 246.98 Z" fill="#3a414a" stroke="#3a414a" stroke-miterlimit="10" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 217px; margin-left: 69px;">
|
||||
<div data-drawio-colors="color: #333333; background-color: rgb(255, 255, 255); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
|
||||
<div style="display: inline-block; font-size: 13.3px; font-family: Helvetica; color: rgb(51, 51, 51); line-height: 1.2; pointer-events: all; font-weight: bold; background-color: rgb(255, 255, 255); white-space: nowrap;">
|
||||
No
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="69" y="221" fill="#333333" font-family="Helvetica" font-size="13px" text-anchor="middle" font-weight="bold">
|
||||
No
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<path d="M 126.08 398 L 190.9 398" fill="none" stroke="#3a414a" stroke-miterlimit="10" pointer-events="stroke"/>
|
||||
<path d="M 197.7 398 L 190.9 401.4 L 190.9 394.6 Z" fill="#3a414a" stroke="#3a414a" stroke-miterlimit="10" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 399px; margin-left: 163px;">
|
||||
<div data-drawio-colors="color: #333333; background-color: rgb(255, 255, 255); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
|
||||
<div style="display: inline-block; font-size: 13.3px; font-family: Helvetica; color: rgb(51, 51, 51); line-height: 1.2; pointer-events: all; font-weight: bold; background-color: rgb(255, 255, 255); white-space: nowrap;">
|
||||
Yes
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="163" y="402" fill="#333333" font-family="Helvetica" font-size="13px" text-anchor="middle" font-weight="bold">
|
||||
Yes
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<path d="M 38 517.5 L 64.4 497.7 Q 68 495 71.6 497.7 L 124.4 537.3 Q 128 540 124.4 542.7 L 71.6 582.3 Q 68 585 64.4 582.3 L 11.6 542.7 Q 8 540 11.6 537.3 Z" fill="rgb(255, 255, 255)" stroke="#3a414a" stroke-miterlimit="10" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 116px; height: 1px; padding-top: 540px; margin-left: 10px;">
|
||||
<div data-drawio-colors="color: #3a414a; " style="box-sizing: border-box; font-size: 0px; text-align: center; width: 114px; max-height: 84px;">
|
||||
<div style="display: inline-block; font-size: 11.7px; font-family: Helvetica; color: rgb(58, 65, 74); line-height: 1.2; pointer-events: all; width: 100%; white-space: normal; overflow-wrap: normal;">
|
||||
Valid token available?
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="68" y="544" fill="#3a414a" font-family="Helvetica" font-size="12px" text-anchor="middle">
|
||||
Valid token availabl...
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<path d="M 67.5 301.37 L 67.5 321 Q 67.5 327 67.61 333 L 67.86 346.39" fill="none" stroke="#3a414a" stroke-miterlimit="10" pointer-events="stroke"/>
|
||||
<path d="M 67.98 353.19 L 64.46 346.45 L 71.26 346.32 Z" fill="#3a414a" stroke="#3a414a" stroke-miterlimit="10" pointer-events="all"/>
|
||||
<path d="M 68 441.92 L 68 488.39" fill="none" stroke="#3a414a" stroke-miterlimit="10" pointer-events="stroke"/>
|
||||
<path d="M 68 495.19 L 64.6 488.39 L 71.4 488.39 Z" fill="#3a414a" stroke="#3a414a" stroke-miterlimit="10" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 469px; margin-left: 69px;">
|
||||
<div data-drawio-colors="color: #333333; background-color: rgb(255, 255, 255); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
|
||||
<div style="display: inline-block; font-size: 13.3px; font-family: Helvetica; color: rgb(51, 51, 51); line-height: 1.2; pointer-events: all; font-weight: bold; background-color: rgb(255, 255, 255); white-space: nowrap;">
|
||||
No
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="69" y="473" fill="#333333" font-family="Helvetica" font-size="13px" text-anchor="middle" font-weight="bold">
|
||||
No
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<path d="M 68 583.92 L 68 605 Q 68 611 67.88 617 L 67.65 628.94" fill="none" stroke="#3a414a" stroke-miterlimit="10" pointer-events="stroke"/>
|
||||
<path d="M 67.52 635.74 L 64.25 628.87 L 71.05 629.01 Z" fill="#3a414a" stroke="#3a414a" stroke-miterlimit="10" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 612px; margin-left: 69px;">
|
||||
<div data-drawio-colors="color: #333333; background-color: rgb(255, 255, 255); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
|
||||
<div style="display: inline-block; font-size: 13.3px; font-family: Helvetica; color: rgb(51, 51, 51); line-height: 1.2; pointer-events: all; font-weight: bold; background-color: rgb(255, 255, 255); white-space: nowrap;">
|
||||
Yes
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="69" y="615" fill="#333333" font-family="Helvetica" font-size="13px" text-anchor="middle" font-weight="bold">
|
||||
Yes
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<path d="M 126.08 540 L 260.5 540 Q 266.5 540 266.5 534 L 266.5 429.06" fill="none" stroke="#3a414a" stroke-miterlimit="10" pointer-events="stroke"/>
|
||||
<path d="M 266.5 422.26 L 269.9 429.06 L 263.1 429.06 Z" fill="#3a414a" stroke="#3a414a" stroke-miterlimit="10" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 541px; margin-left: 257px;">
|
||||
<div data-drawio-colors="color: #333333; background-color: rgb(255, 255, 255); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
|
||||
<div style="display: inline-block; font-size: 13.3px; font-family: Helvetica; color: rgb(51, 51, 51); line-height: 1.2; pointer-events: all; font-weight: bold; background-color: rgb(255, 255, 255); white-space: nowrap;">
|
||||
No
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="257" y="544" fill="#333333" font-family="Helvetica" font-size="13px" text-anchor="middle" font-weight="bold">
|
||||
No
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<path d="M 67.5 683.37 L 67.5 703 Q 67.5 709 67.61 715 L 67.86 728.39" fill="none" stroke="#3a414a" stroke-miterlimit="10" pointer-events="stroke"/>
|
||||
<path d="M 67.98 735.19 L 64.46 728.45 L 71.26 728.32 Z" fill="#3a414a" stroke="#3a414a" stroke-miterlimit="10" pointer-events="all"/>
|
||||
</g>
|
||||
<switch>
|
||||
<g requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"/>
|
||||
<a transform="translate(0,-5)" xlink:href="https://www.diagrams.net/doc/faq/svg-export-text-problems" target="_blank">
|
||||
<text text-anchor="middle" font-size="10px" x="50%" y="100%">
|
||||
Viewer does not support full SVG 1.1
|
||||
</text>
|
||||
</a>
|
||||
</switch>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 39 KiB |
349
tools/EVerest-main/modules/EVSE/Auth/include/AuthHandler.hpp
Normal file
349
tools/EVerest-main/modules/EVSE/Auth/include/AuthHandler.hpp
Normal file
@@ -0,0 +1,349 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#ifndef _AUTH_HANDLER_HPP_
|
||||
#define _AUTH_HANDLER_HPP_
|
||||
|
||||
#include <condition_variable>
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <set>
|
||||
|
||||
#include <utils/types.hpp>
|
||||
|
||||
#include <generated/types/authorization.hpp>
|
||||
#include <generated/types/evse_manager.hpp>
|
||||
#include <generated/types/reservation.hpp>
|
||||
#include <generated/types/text_message.hpp>
|
||||
|
||||
#include <Connector.hpp>
|
||||
#include <ReservationHandler.hpp>
|
||||
|
||||
using namespace types::evse_manager;
|
||||
using namespace types::authorization;
|
||||
using namespace types::reservation;
|
||||
using namespace types::text_message;
|
||||
|
||||
namespace types {
|
||||
namespace authorization {
|
||||
|
||||
inline bool operator<(const IdToken& lhs, const IdToken& rhs) {
|
||||
return lhs.value < rhs.value;
|
||||
}
|
||||
|
||||
inline bool operator<(const ProvidedIdToken& lhs, const ProvidedIdToken& rhs) {
|
||||
return lhs.id_token < rhs.id_token;
|
||||
}
|
||||
|
||||
} // namespace authorization
|
||||
} // namespace types
|
||||
|
||||
namespace module {
|
||||
|
||||
enum class TokenHandlingResult {
|
||||
ALREADY_IN_PROCESS,
|
||||
REJECTED,
|
||||
USED_TO_START_TRANSACTION,
|
||||
USED_TO_STOP_TRANSACTION,
|
||||
TIMEOUT,
|
||||
NO_CONNECTOR_AVAILABLE,
|
||||
WITHDRAWN
|
||||
};
|
||||
|
||||
namespace conversions {
|
||||
std::string token_handling_result_to_string(const TokenHandlingResult& result);
|
||||
} // namespace conversions
|
||||
|
||||
/**
|
||||
* @brief This class handles authorization and reservation requests. It keeps track of the state of each connector and
|
||||
* validates incoming token and reservation requests accordingly.
|
||||
*
|
||||
*/
|
||||
class AuthHandler {
|
||||
|
||||
public:
|
||||
AuthHandler(const SelectionAlgorithm& selection_algorithm, const int connection_timeout,
|
||||
bool plug_in_timeout_enabled, bool prioritize_authorization_over_stopping_transaction,
|
||||
bool ignore_connector_faults, const std::string& id, kvsIntf* store);
|
||||
virtual ~AuthHandler();
|
||||
|
||||
/**
|
||||
* @brief Initializes the evse with the given \p connectors and the given \p evse_id . It instantiates new
|
||||
* connector objects and fills data sturctures of the class.
|
||||
*
|
||||
* @param evse_id
|
||||
* @param evse_index
|
||||
* @param connectors The connectors.
|
||||
*/
|
||||
void init_evse(const int evse_id, const int evse_index, const std::vector<Connector>& connectors);
|
||||
|
||||
/**
|
||||
* @brief Returns the evse_id for the given \p evse_index .
|
||||
*
|
||||
* @param evse_index
|
||||
* @return int32_t evse_id
|
||||
*/
|
||||
int32_t get_evse_id_by_index(const int evse_index);
|
||||
|
||||
/**
|
||||
* @brief Call when everything is initialized. This will call 'init' of the reservation handler.
|
||||
*/
|
||||
void initialize();
|
||||
|
||||
/**
|
||||
* @brief Handler for a new incoming \p provided_token
|
||||
*
|
||||
* @param provided_token
|
||||
*/
|
||||
TokenHandlingResult on_token(const ProvidedIdToken& provided_token);
|
||||
|
||||
/**
|
||||
* @brief Handler for an update to a token validation result. This is mainly used to update if we have a parent id.
|
||||
*
|
||||
* @param validation_result_update
|
||||
*/
|
||||
void handle_token_validation_result_update(const ValidationResultUpdate& validation_result_update);
|
||||
|
||||
/**
|
||||
* @brief Handler for new incoming \p reservation for the given \p connector . Places the reservation if possible.
|
||||
*
|
||||
* @param reservation
|
||||
* @return types::reservation::ReservationResult
|
||||
*/
|
||||
types::reservation::ReservationResult handle_reservation(const Reservation& reservation);
|
||||
|
||||
/**
|
||||
* @brief Handler for incoming cancel reservation request for the given \p reservation_id .
|
||||
*
|
||||
* @param reservation_id
|
||||
* @return return value first returns false if the reservation could not been cancelled. Return value second is the
|
||||
* evse id or nullopt if the reservation was a 'global' reservation without evse id.
|
||||
*/
|
||||
std::pair<bool, std::optional<int32_t>> handle_cancel_reservation(const int32_t reservation_id);
|
||||
|
||||
/**
|
||||
* @brief Callback to check if there is a reservation for the given token (on the given evse id).
|
||||
* @param id_token The token to check.
|
||||
* @param evse_id The evse to check the reservation for.
|
||||
* @param group_id_token The group id token to check.
|
||||
* @return The reservation check status
|
||||
*/
|
||||
ReservationCheckStatus handle_reservation_exists(std::string& id_token, const std::optional<int>& evse_id,
|
||||
std::optional<std::string>& group_id_token);
|
||||
|
||||
/**
|
||||
* @brief Callback to signal EvseManager that the given \p connector_id has been reserved with the given \p
|
||||
* reservation_id .
|
||||
*
|
||||
* @param evse_id
|
||||
* @param reservation_id
|
||||
*
|
||||
* @return true of EvseManager accepted the reservation.
|
||||
*/
|
||||
bool call_reserved(const int reservation_id, const std::optional<int>& evse_id);
|
||||
|
||||
/**
|
||||
* @brief Callback to signal EvseManager that the reservation for the given \p evse_id has been cancelled.
|
||||
*
|
||||
* @param reservation_id The id of the cancelled reservation.
|
||||
* @param reason The reason the reservation was cancelled.
|
||||
* @param evse_id Evse id if reservation was for a specific evse.
|
||||
* @param send_reservation_update True to send a reservation update. This should not be sent if OCPP cancels
|
||||
* the reservation.
|
||||
*/
|
||||
void call_reservation_cancelled(const int32_t reservation_id, const ReservationEndReason reason,
|
||||
const std::optional<int>& evse_id, const bool send_reservation_update);
|
||||
|
||||
/**
|
||||
* @brief Handler for the given \p events at the given \p connector . Submits events to the state machine of the
|
||||
* handler.
|
||||
*
|
||||
* @param evse_id
|
||||
* @param events
|
||||
*/
|
||||
void handle_session_event(const int evse_id, const SessionEvent& events);
|
||||
|
||||
/**
|
||||
* @brief Handler for permanent faults from evsemanager that prevents charging
|
||||
*/
|
||||
void handle_permanent_fault_cleared(const int evse_id, const int32_t connector_id);
|
||||
void handle_permanent_fault_raised(const int evse_id, const int32_t connector_id);
|
||||
|
||||
/**
|
||||
* @brief Set the connection timeout of the handler.
|
||||
*
|
||||
* @param connection_timeout
|
||||
*/
|
||||
void set_connection_timeout(const int connection_timeout);
|
||||
|
||||
/**
|
||||
* @brief Set the plug in timeout enabled flag of the handler.
|
||||
*
|
||||
* @param plug_in_timeout_enabled
|
||||
*/
|
||||
void set_plug_in_timeout_enabled(bool plug_in_timeout_enabled);
|
||||
|
||||
/**
|
||||
* @brief Set the master pass group id of the handler.
|
||||
*
|
||||
* @param master_pass_group_id
|
||||
*/
|
||||
void set_master_pass_group_id(const std::string& master_pass_group_id);
|
||||
|
||||
/**
|
||||
* @brief Set the prioritize authorization over stopping transaction flag of the handler.
|
||||
*
|
||||
* @param b
|
||||
*/
|
||||
void set_prioritize_authorization_over_stopping_transaction(bool b);
|
||||
|
||||
/**
|
||||
* @brief Registers the given \p callback to notify the evse about the processed authorization request.
|
||||
*
|
||||
* @param callback
|
||||
*/
|
||||
void
|
||||
register_notify_evse_callback(const std::function<void(const int evse_index, const ProvidedIdToken& provided_token,
|
||||
const ValidationResult& validation_result)>& callback);
|
||||
/**
|
||||
* @brief Registers the given \p callback to withdraw authorization.
|
||||
*
|
||||
* @param callback
|
||||
*/
|
||||
void register_withdraw_authorization_callback(const std::function<void(const int evse_index)>& callback);
|
||||
|
||||
/**
|
||||
* @brief Registers the given \p callback to validate a token.
|
||||
*
|
||||
* @param callback
|
||||
*/
|
||||
void register_validate_token_callback(
|
||||
const std::function<std::vector<ValidationResult>(const ProvidedIdToken& provided_token)>& callback);
|
||||
|
||||
/**
|
||||
* @brief Registers the given \p callback to stop a transaction at an EvseManager.
|
||||
*
|
||||
* @param callback
|
||||
*/
|
||||
void register_stop_transaction_callback(
|
||||
const std::function<void(const int evse_index, const StopTransactionRequest& request)>& callback);
|
||||
|
||||
/**
|
||||
* @brief Registers the given \p callback to signal a reservation to an EvseManager.
|
||||
*
|
||||
* @param callback
|
||||
*/
|
||||
void register_reserved_callback(
|
||||
const std::function<bool(const std::optional<int>& evse_id, const int& reservation_id)>& callback);
|
||||
|
||||
/**
|
||||
* @brief Registers the given \p callback to signal a reservation has been cancelled to the EvseManager.
|
||||
*
|
||||
* @param callback
|
||||
*/
|
||||
void register_reservation_cancelled_callback(
|
||||
const std::function<void(const std::optional<int32_t>& evse_id, const int32_t reservation_id,
|
||||
const ReservationEndReason reason, const bool send_reservation_update)>& callback);
|
||||
|
||||
/**
|
||||
* @brief Registers the given \p callback to publish the intermediate token validation status.
|
||||
*
|
||||
* @param callback
|
||||
*/
|
||||
void register_publish_token_validation_status_callback(
|
||||
const std::function<void(const ProvidedIdToken&, TokenValidationStatus, const std::vector<MessageContent>&)>&
|
||||
callback);
|
||||
|
||||
WithdrawAuthorizationResult handle_withdraw_authorization(const WithdrawAuthorizationRequest& request);
|
||||
|
||||
private:
|
||||
enum class SelectEvseReturnStatus {
|
||||
EvseSelected,
|
||||
Interrupted,
|
||||
TimeOut
|
||||
};
|
||||
|
||||
struct SelectEvseResult {
|
||||
std::optional<int> evse_id;
|
||||
SelectEvseReturnStatus status;
|
||||
};
|
||||
|
||||
SelectionAlgorithm selection_algorithm;
|
||||
int connection_timeout;
|
||||
bool plug_in_timeout_enabled;
|
||||
std::optional<std::string> master_pass_group_id;
|
||||
bool prioritize_authorization_over_stopping_transaction;
|
||||
bool ignore_faults;
|
||||
ReservationHandler reservation_handler;
|
||||
|
||||
std::map<int, std::unique_ptr<EVSEContext>> evses;
|
||||
|
||||
std::list<int> plug_in_queue;
|
||||
std::set<ProvidedIdToken> tokens_in_process;
|
||||
std::condition_variable cv;
|
||||
std::condition_variable processing_finished_cv;
|
||||
std::mutex event_mutex;
|
||||
std::mutex withdraw_mutex;
|
||||
std::unique_ptr<WithdrawAuthorizationRequest> withdraw_request;
|
||||
|
||||
// callbacks
|
||||
std::function<void(const int evse_index, const ProvidedIdToken& provided_token,
|
||||
const ValidationResult& validation_result)>
|
||||
notify_evse_callback;
|
||||
std::function<void(const int evse_index)> withdraw_authorization_callback;
|
||||
std::function<std::vector<ValidationResult>(const ProvidedIdToken& provided_token)> validate_token_callback;
|
||||
std::function<void(const int evse_index, const StopTransactionRequest& request)> stop_transaction_callback;
|
||||
std::function<void(const Array& reservations)> reservation_update_callback;
|
||||
std::function<bool(const std::optional<int>& evse_index, const int& reservation_id)> reserved_callback;
|
||||
std::function<void(const std::optional<int>& evse_index, const int32_t reservation_id,
|
||||
const types::reservation::ReservationEndReason reason, const bool send_reservation_update)>
|
||||
reservation_cancelled_callback;
|
||||
std::function<void(const ProvidedIdToken& token, TokenValidationStatus status,
|
||||
const std::vector<MessageContent>& tariff_messages)>
|
||||
publish_token_validation_status_callback;
|
||||
|
||||
void publish_token_validation_status(const ProvidedIdToken& token, TokenValidationStatus status,
|
||||
const std::vector<MessageContent>& tariff_messages = {});
|
||||
|
||||
std::vector<int> get_referenced_evses(const ProvidedIdToken& provided_token);
|
||||
int used_for_transaction(const std::vector<int>& evse_ids, const std::string& id_token);
|
||||
bool is_token_already_in_process(const ProvidedIdToken& provided_id_token,
|
||||
const std::vector<int>& referenced_evses);
|
||||
bool any_evse_available(const std::vector<int>& evse_ids);
|
||||
bool any_parent_id_present(const std::vector<int>& evse_ids);
|
||||
bool equals_master_pass_group_id(const std::optional<types::authorization::IdToken> parent_id_token);
|
||||
|
||||
TokenHandlingResult handle_token(ProvidedIdToken& provided_token, std::unique_lock<std::mutex>& lk);
|
||||
|
||||
/**
|
||||
* @brief Method selects an evse based on the configured selection algorithm. It might block until an event
|
||||
* occurs that can be used to determine an evse.
|
||||
*
|
||||
* @param selected_evses
|
||||
* @param id_token The id token of the request.
|
||||
* @return The status and optional evse id if an evse was selected.
|
||||
*/
|
||||
SelectEvseResult select_evse(const std::vector<int>& selected_evses, const IdToken& id_token,
|
||||
std::unique_lock<std::mutex>& lk);
|
||||
bool is_authorization_withdrawn(const std::vector<int>& selected_evses, const IdToken& id_token);
|
||||
|
||||
int get_latest_plugin(const std::vector<int>& evse_ids);
|
||||
void notify_evse(int evse_id, const ProvidedIdToken& provided_token, const ValidationResult& validation_result,
|
||||
std::unique_lock<std::mutex>& lk);
|
||||
Identifier get_identifier(const ValidationResult& validation_result, const std::string& id_token,
|
||||
const AuthorizationType& type);
|
||||
void submit_event_for_connector(const int32_t evse_id, const int32_t connector_id,
|
||||
const ConnectorEvent connector_event);
|
||||
/**
|
||||
* @brief Check reservations: if there are as many reservations as evse's, all should be set to reserved.
|
||||
*
|
||||
* This will check the reservation status of the evse's and send the statusses to the evse manager.
|
||||
*/
|
||||
void check_evse_reserved_and_send_updates();
|
||||
};
|
||||
|
||||
} // namespace module
|
||||
|
||||
#endif //_AUTH_HANDLER_HPP_
|
||||
100
tools/EVerest-main/modules/EVSE/Auth/include/Connector.hpp
Normal file
100
tools/EVerest-main/modules/EVSE/Auth/include/Connector.hpp
Normal file
@@ -0,0 +1,100 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#ifndef _CONNECTOR_HPP_
|
||||
#define _CONNECTOR_HPP_
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include <everest/timer.hpp>
|
||||
|
||||
#include <utils/types.hpp>
|
||||
|
||||
#include <ConnectorStateMachine.hpp>
|
||||
#include <generated/types/authorization.hpp>
|
||||
#include <generated/types/evse_manager.hpp>
|
||||
|
||||
namespace module {
|
||||
|
||||
/// \brief Validated Identifier struct. Used to keep track of active Identifiers
|
||||
struct Identifier {
|
||||
types::authorization::IdToken id_token; ///< IdToken of the identifier
|
||||
types::authorization::AuthorizationType type; ///< Type of the provider of the identifier
|
||||
std::optional<types::authorization::AuthorizationStatus> authorization_status;
|
||||
std::optional<std::string> expiry_time; ///< Absolute UTC time point when reservation expires in RFC3339 format
|
||||
std::optional<types::authorization::IdToken> parent_id_token; ///< Parent id token of the identifier
|
||||
};
|
||||
|
||||
struct Connector {
|
||||
explicit Connector(
|
||||
int id, const types::evse_manager::ConnectorTypeEnum type = types::evse_manager::ConnectorTypeEnum::Unknown) :
|
||||
id(id), transaction_active(false), state_machine(ConnectorState::AVAILABLE), type(type) {
|
||||
}
|
||||
|
||||
int id;
|
||||
|
||||
bool transaction_active;
|
||||
ConnectorStateMachine state_machine;
|
||||
types::evse_manager::ConnectorTypeEnum type;
|
||||
|
||||
/**
|
||||
* @brief Submits the given \p event to the state machine
|
||||
*
|
||||
* @param event
|
||||
*/
|
||||
void submit_event(ConnectorEvent event);
|
||||
|
||||
/**
|
||||
* @brief Returns true if connector is in state UNAVAILABLE or UNAVAILABLE_FAULTED
|
||||
*
|
||||
* @return true
|
||||
* @return false
|
||||
*/
|
||||
bool is_unavailable() const;
|
||||
|
||||
ConnectorState get_state() const;
|
||||
};
|
||||
|
||||
struct EVSEContext {
|
||||
|
||||
EVSEContext(
|
||||
int evse_id, int evse_index, int connector_id,
|
||||
const types::evse_manager::ConnectorTypeEnum connector_type = types::evse_manager::ConnectorTypeEnum::Unknown) :
|
||||
evse_id(evse_id), evse_index(evse_index), transaction_active(false), plugged_in(false) {
|
||||
Connector c(connector_id, connector_type);
|
||||
connectors.push_back(c);
|
||||
}
|
||||
|
||||
EVSEContext(int evse_id, int evse_index, const std::vector<Connector>& connectors) :
|
||||
evse_id(evse_id),
|
||||
evse_index(evse_index),
|
||||
transaction_active(false),
|
||||
connectors(connectors),
|
||||
plugged_in(false),
|
||||
plug_in_timeout(false) {
|
||||
}
|
||||
|
||||
int32_t evse_id;
|
||||
int32_t evse_index;
|
||||
bool transaction_active;
|
||||
|
||||
// identifier is set when transaction is running and none if not
|
||||
std::optional<Identifier> identifier = std::nullopt;
|
||||
std::vector<Connector> connectors;
|
||||
Everest::SteadyTimer timeout_timer;
|
||||
std::atomic<bool> timeout_in_progress{false};
|
||||
bool plugged_in;
|
||||
bool plug_in_timeout; // indicates no authorization received within connection_timeout. Replug is required for this
|
||||
// EVSE to get authorization and start a transaction
|
||||
|
||||
bool is_available();
|
||||
bool is_unavailable();
|
||||
};
|
||||
|
||||
namespace conversions {
|
||||
std::string connector_state_to_string(const ConnectorState& state);
|
||||
} // namespace conversions
|
||||
|
||||
} // namespace module
|
||||
|
||||
#endif //_CONNECTOR_HPP_
|
||||
@@ -0,0 +1,45 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#ifndef _CONNECTOR_STATE_MACHINE_HPP_
|
||||
#define _CONNECTOR_STATE_MACHINE_HPP_
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
namespace module {
|
||||
|
||||
enum class ConnectorEvent {
|
||||
ENABLE,
|
||||
DISABLE,
|
||||
ERROR_CLEARED,
|
||||
FAULTED,
|
||||
TRANSACTION_STARTED,
|
||||
SESSION_FINISHED
|
||||
};
|
||||
|
||||
/// @warning Do not change the order of ConnectorState, or if you do it, fix the code in ReservationHandler.
|
||||
enum class ConnectorState {
|
||||
AVAILABLE,
|
||||
UNAVAILABLE,
|
||||
FAULTED,
|
||||
OCCUPIED,
|
||||
UNAVAILABLE_FAULTED,
|
||||
FAULTED_OCCUPIED
|
||||
};
|
||||
|
||||
class ConnectorStateMachine {
|
||||
public:
|
||||
explicit ConnectorStateMachine(ConnectorState initial_state);
|
||||
|
||||
bool handle_event(ConnectorEvent event);
|
||||
|
||||
ConnectorState get_state() const;
|
||||
|
||||
private:
|
||||
ConnectorState state;
|
||||
};
|
||||
|
||||
} // namespace module
|
||||
|
||||
#endif //_CONNECTOR_STATE_MACHINE_HPP_
|
||||
@@ -0,0 +1,357 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
|
||||
#include <boost/shared_ptr.hpp>
|
||||
|
||||
#include <Connector.hpp>
|
||||
#include <everest/timer.hpp>
|
||||
#include <generated/types/evse_manager.hpp>
|
||||
#include <generated/types/reservation.hpp>
|
||||
|
||||
class kvsIntf;
|
||||
|
||||
namespace module {
|
||||
|
||||
struct ReservationEvseStatus {
|
||||
std::set<int32_t> reserved;
|
||||
std::set<int32_t> available;
|
||||
};
|
||||
|
||||
class ReservationHandler {
|
||||
private: // Members
|
||||
/// \brief Map of EVSE's, with EVSE id as key and the EVSE struct as value.
|
||||
std::map<int, std::unique_ptr<module::EVSEContext>>& evses;
|
||||
/// \brief Key value store id.
|
||||
const std::string kvs_store_key_id;
|
||||
/// \brief Key value store for storing reservations.
|
||||
kvsIntf* store;
|
||||
/// \brief Map of EVSE specific reservations, with EVSE id as key and the Reservation type as value.
|
||||
std::map<uint32_t, types::reservation::Reservation> evse_reservations;
|
||||
/// \brief All reservations not bound to a specific EVSE.
|
||||
std::vector<types::reservation::Reservation> global_reservations;
|
||||
/// \brief event mutex, for all timer bound locks (for `reservation_id_to_reservation_timeout_timer_map`)
|
||||
mutable std::recursive_mutex event_mutex;
|
||||
/// \brief Map with reservations and their timer.
|
||||
///
|
||||
/// Every reservation has a specific end time, which is stored in this map. Key is the reservation id. When the
|
||||
/// timer expires, it is removed from the map and the reservation is removed from the `evse_reservations` or
|
||||
/// `global_reservations`.
|
||||
std::map<int, std::unique_ptr<Everest::SteadyTimer>> reservation_id_to_reservation_timeout_timer_map;
|
||||
|
||||
/// \brief The callback that is called when a reservation is cancelled.
|
||||
std::function<void(const std::optional<uint32_t>& evse_id, const int32_t reservation_id,
|
||||
const types::reservation::ReservationEndReason reason, const bool send_reservation_update)>
|
||||
reservation_cancelled_callback;
|
||||
|
||||
std::set<int32_t> last_reserved_status;
|
||||
|
||||
/// \brief worker for the timers.
|
||||
boost::shared_ptr<boost::asio::executor_work_guard<boost::asio::io_context::executor_type>> work;
|
||||
/// \brief io_context for the worker for the timers.
|
||||
boost::asio::io_context io_context;
|
||||
/// \brief io context thread for the timers.
|
||||
std::thread io_context_thread;
|
||||
|
||||
public:
|
||||
///
|
||||
/// \brief Constructor.
|
||||
///
|
||||
ReservationHandler(std::map<int, std::unique_ptr<module::EVSEContext>>& evses, const std::string& id,
|
||||
kvsIntf* store);
|
||||
|
||||
///
|
||||
/// \brief Destructor.
|
||||
///
|
||||
~ReservationHandler();
|
||||
|
||||
///
|
||||
/// \brief Load reservations from key value store.
|
||||
///
|
||||
void load_reservations();
|
||||
|
||||
///
|
||||
/// \brief Try to make a reservation.
|
||||
/// \param evse_id Optional, the evse id. If omitted, a 'global' reservation will be made.
|
||||
/// \param reservation The reservation to make.
|
||||
/// \return The result of the reservation (`Accepted` if the reservation could be made).
|
||||
///
|
||||
types::reservation::ReservationResult make_reservation(const std::optional<uint32_t> evse_id,
|
||||
const types::reservation::Reservation& reservation);
|
||||
|
||||
///
|
||||
/// \brief Change a specific connector state.
|
||||
///
|
||||
/// This is important for the reservation handler, to know which connector is in which state, to know if a
|
||||
/// reservation can be made or not.
|
||||
///
|
||||
/// \param connector_state The state of the connector.
|
||||
/// \param evse_id The EVSE id the connector belongs to.
|
||||
/// \param connector_id The connector id.
|
||||
///
|
||||
void on_connector_state_changed(const ConnectorState connector_state, const uint32_t evse_id,
|
||||
const uint32_t connector_id);
|
||||
|
||||
///
|
||||
/// \brief Check if charging is possible on a given EVSE.
|
||||
///
|
||||
/// If there are multiple global reservations, while a charging station might look available, it is possible that
|
||||
/// charging is not possible because then cars that made reservations can not charge anymore.
|
||||
///
|
||||
/// Only use this function to check if a car can charge without having a reservation id.
|
||||
///
|
||||
/// \param evse_id The evse on which a car wants to charge.
|
||||
/// \return True if charging is possible.
|
||||
///
|
||||
bool is_charging_possible(const uint32_t evse_id);
|
||||
|
||||
///
|
||||
/// \brief Check is an EVSE is reserved.
|
||||
///
|
||||
/// This only looks at EVSE specific reservations.
|
||||
///
|
||||
/// \param evse_id The evse id to check.
|
||||
/// \return True if EVSE is reserved.
|
||||
///
|
||||
bool is_evse_reserved(const uint32_t evse_id);
|
||||
|
||||
///
|
||||
/// \brief Cancel a reservation.
|
||||
/// \param reservation_id The id of the reservation to cancel.
|
||||
/// \param execute_callback True if the `reservation_cancelled_callback` must be called.
|
||||
/// \param reason The cancel reason.
|
||||
/// \return First: true if reservation could be cancelled.
|
||||
/// Second: The evse id if the reservation to cancel was made for a specific EVSE.
|
||||
///
|
||||
std::pair<bool, std::optional<uint32_t>> cancel_reservation(const int reservation_id, const bool execute_callback,
|
||||
const types::reservation::ReservationEndReason reason);
|
||||
|
||||
///
|
||||
/// \brief Cancel a reservation.
|
||||
/// \param evse_id The evse id to cancel the reservation for.
|
||||
/// \param execute_callback True if the `reservation_cancelled_callback` must be called.
|
||||
/// \return True if the reservation could be cancelled.
|
||||
///
|
||||
bool cancel_reservation(const uint32_t evse_id, const bool execute_callback);
|
||||
|
||||
///
|
||||
/// \brief Register reservation cancelled callback.
|
||||
/// \param callback The callback that should be called when a reservation is cancelled.
|
||||
///
|
||||
void register_reservation_cancelled_callback(
|
||||
const std::function<void(const std::optional<uint32_t>& evse_id, const int32_t reservation_id,
|
||||
const types::reservation::ReservationEndReason reason,
|
||||
const bool send_reservation_update)>& callback);
|
||||
|
||||
///
|
||||
/// \brief Called when a reservation is used, will remove it from the reservation list.
|
||||
/// \param reservation_id The if of the reservation that is used.
|
||||
///
|
||||
/// \note This will not set the EVSE or Connector to 'available'. That must be done separately (because we don't
|
||||
/// know here when the connector is not connected anymore).
|
||||
///
|
||||
void on_reservation_used(const int32_t reservation_id);
|
||||
|
||||
///
|
||||
/// @brief Function checks if the given \p id_token or \p parent_id_token matches the reserved token of the given \p
|
||||
/// evse_id
|
||||
///
|
||||
/// @param id_token Id token
|
||||
/// @param evse_id Evse id
|
||||
/// @param parent_id_token Parent id token
|
||||
/// @return The reservation id when there is a matching identifier, otherwise std::nullopt.
|
||||
///
|
||||
std::optional<int32_t> matches_reserved_identifier(const std::string& id_token,
|
||||
const std::optional<uint32_t> evse_id,
|
||||
std::optional<std::string> parent_id_token);
|
||||
|
||||
///
|
||||
/// @brief Functions check if reservation at the given \p evse_id contains a parent_id
|
||||
/// @param evse_id Evse id
|
||||
/// @return true if reservation for \p evse_id exists and reservation contains a parent_id
|
||||
///
|
||||
bool has_reservation_parent_id(const std::optional<uint32_t> evse_id);
|
||||
|
||||
///
|
||||
/// \brief Check if the number of global reservations match the number of available evse's.
|
||||
/// \return The new reservation status of the evse's.
|
||||
///
|
||||
/// \note The return value has the new reserved and new available statusses (so the ones that were already reserved
|
||||
/// are not added to those lists).
|
||||
///
|
||||
ReservationEvseStatus check_number_global_reservations_match_number_available_evses();
|
||||
|
||||
private: // Functions
|
||||
///
|
||||
/// \brief Check if there is a specific connector type in the vector.
|
||||
/// \param evse_connectors The vector to check for the type.
|
||||
/// \param connector_type The connector type to find.
|
||||
/// \return True if the connector type is in the vector.
|
||||
///
|
||||
bool has_evse_connector_type(const std::vector<Connector>& evse_connectors,
|
||||
const types::evse_manager::ConnectorTypeEnum connector_type) const;
|
||||
|
||||
///
|
||||
/// \brief Check if there is at least one EVSE with the given connector type.
|
||||
/// \param connector_type The connector type to check.
|
||||
/// \return True if at least one EVSE has this connector type.
|
||||
///
|
||||
bool does_evse_connector_type_exist(const types::evse_manager::ConnectorTypeEnum connector_type) const;
|
||||
|
||||
///
|
||||
/// \brief Helper function to get a reservation result from the current EVSE state and connector state, and if
|
||||
/// there is a specific reservation for this EVSE.
|
||||
/// \param evse_id The evse id to get the state from.
|
||||
/// \param evse_specific_reservations The evse specific reservations list to look in.
|
||||
/// \return The `ReservationResult` to return for this specific EVSE.
|
||||
///
|
||||
types::reservation::ReservationResult get_evse_connector_state_reservation_result(
|
||||
const uint32_t evse_id, const std::map<uint32_t, types::reservation::Reservation>& evse_specific_reservations);
|
||||
|
||||
///
|
||||
/// \brief Helper function to check if the connector of a specific EVSE is available.
|
||||
/// \param evse_id The evse id the connector belongs to.
|
||||
/// \param connector_type The connector type to check.
|
||||
/// \return The `ReservationResult` to return for his specific connector.
|
||||
///
|
||||
types::reservation::ReservationResult
|
||||
get_connector_availability_reservation_result(const uint32_t evse_id,
|
||||
const types::evse_manager::ConnectorTypeEnum connector_type);
|
||||
|
||||
///
|
||||
/// \brief Get all possible orders of connector types given a vector of connector types.
|
||||
///
|
||||
/// For the reservations, there must be checked if all combinations of arriving cars with specific connector types
|
||||
/// are possible. For that, we want to have a list of all different combinations of arriving.
|
||||
///
|
||||
/// So for example for connector type A, B and C, the different combinations are:
|
||||
/// - A, B, C
|
||||
/// - A, C, B
|
||||
/// - B, A, C
|
||||
/// - B, C, A
|
||||
/// - C, A, B
|
||||
/// - C, B, A
|
||||
///
|
||||
/// And for connector types A, A and B, the combinations are:
|
||||
/// - A, A, B
|
||||
/// - A, B, A
|
||||
/// - B, A, A
|
||||
///
|
||||
/// \param connectors The connector types to get all orders from.
|
||||
/// \return A vector of all orders of the connector types.
|
||||
///
|
||||
std::vector<std::vector<types::evse_manager::ConnectorTypeEnum>>
|
||||
get_all_possible_orders(const std::vector<types::evse_manager::ConnectorTypeEnum>& connectors) const;
|
||||
|
||||
///
|
||||
/// \brief Helper function: For a specific order of arrival of cars, check if there is still an EVSE available for
|
||||
/// each car.
|
||||
///
|
||||
/// This function is called recursively, until no 'virtual cars' are left.
|
||||
///
|
||||
/// \param used_evse_ids The evse id's we have used in previous checks. This will be empty when the
|
||||
/// function is first called and will be filled every time an evse id is
|
||||
/// 'used'.
|
||||
/// \param next_car_arrival_order The order in which the cars arrive. This is for example 'A, B, C' and as
|
||||
/// soon as the first is handled, it is removed from the list before
|
||||
/// recursively calling the function again.
|
||||
/// \param evse_specific_reservations EVSE specific reservations, to see if an EVSE is already reserved.
|
||||
/// \return True if this combination of car arrival orders is possible.
|
||||
///
|
||||
bool can_virtual_car_arrive(const std::vector<uint32_t>& used_evse_ids,
|
||||
const std::vector<types::evse_manager::ConnectorTypeEnum>& next_car_arrival_order,
|
||||
const std::map<uint32_t, types::reservation::Reservation>& evse_specific_reservations);
|
||||
|
||||
///
|
||||
/// \brief Check if it is possible to make a new reservation.
|
||||
/// \param global_reservation_type If it is a global reservation: the reservation type.
|
||||
/// \param reservations_no_evse The list of global reservations.
|
||||
/// \param evse_specific_reservations The list of evse specific reservations.
|
||||
/// \return True if a reservation is possible, otherwise false.
|
||||
///
|
||||
bool is_reservation_possible(const std::optional<types::evse_manager::ConnectorTypeEnum> global_reservation_type,
|
||||
const std::vector<types::reservation::Reservation>& reservations_no_evse,
|
||||
const std::map<uint32_t, types::reservation::Reservation>& evse_specific_reservations);
|
||||
|
||||
///
|
||||
/// \brief If a reservation is made, add the reservation to the `reservation_id_to_reservation_timeout_timer_map`.
|
||||
/// \param reservation The reservation.
|
||||
/// \param evse_id The evse id.
|
||||
///
|
||||
void set_reservation_timer(const types::reservation::Reservation& reservation,
|
||||
const std::optional<uint32_t> evse_id);
|
||||
|
||||
///
|
||||
/// \brief Get all evses that have a specific connector type.
|
||||
/// \param connector_type The connector type.
|
||||
/// \return Vector with evse's.
|
||||
///
|
||||
std::vector<EVSEContext*>
|
||||
get_all_evses_with_connector_type(const types::evse_manager::ConnectorTypeEnum connector_type) const;
|
||||
|
||||
///
|
||||
/// \brief For H01.FR.11, H01.FR.12 and H01.FR.13, the correct state must be returned.
|
||||
///
|
||||
/// Also see @see module::ReservationHandler::get_reservation_evse_connector_state. This is a helper function to
|
||||
/// return the 'more important' state (Occupied is 'more important' than Unavailable).
|
||||
///
|
||||
/// \param currrent_state The current connector state.
|
||||
/// \param new_state The new state.
|
||||
/// \return The connector state.
|
||||
///
|
||||
ConnectorState get_new_connector_state(ConnectorState currrent_state, const ConnectorState new_state) const;
|
||||
|
||||
///
|
||||
/// \brief For H01.FR.11, H01.FR.12 and H01.FR.13, the correct state must be returned: if (all) evses are Occupied
|
||||
/// or reserved, occupied must be returned, if (all) evses are Faulted, faulted must be returned, if (all)
|
||||
/// evses are unavailable, unavailable must be returned. This function helps returning the correct state.
|
||||
///
|
||||
/// If at least one of the EVSE's is Occupied, it will return occupied, then it will look to faulted and then to
|
||||
/// unavailable. So if one is occupied and one faulted, it will still return occupied.
|
||||
///
|
||||
/// \param connector_type The connector type to check.
|
||||
/// \return The reservation result that can be returned on the reserve now request.
|
||||
///
|
||||
types::reservation::ReservationResult
|
||||
get_reservation_evse_connector_state(const types::evse_manager::ConnectorTypeEnum connector_type) const;
|
||||
|
||||
///
|
||||
/// \brief After a connector or evse is set to unavailable, faulted or occupied, this function can be called to
|
||||
/// check the reservations and cancel reservations that are not possible now anymore.
|
||||
///
|
||||
void check_reservations_and_cancel_if_not_possible();
|
||||
|
||||
///
|
||||
/// \brief Store reservations to key value store.
|
||||
///
|
||||
void store_reservations();
|
||||
|
||||
///
|
||||
/// \brief Get new reserved / available status for evse's and store it.
|
||||
/// \param currently_available_evses Current available evse's.
|
||||
/// \param reserved_evses Current reserved evse's.
|
||||
/// \return A struct with changed reservation statuses compared with the last time this function was called.
|
||||
///
|
||||
/// When an evse is reserved and it was available before, it will be added to the set in the struct (return value).
|
||||
/// But when an evse is reserved and last time it was already reserved, it is not added.
|
||||
///
|
||||
ReservationEvseStatus
|
||||
get_evse_global_reserved_status_and_set_new_status(const std::set<int32_t>& currently_available_evses,
|
||||
const std::set<int32_t>& reserved_evses);
|
||||
|
||||
///
|
||||
/// \brief Helper function to print information about reservations and evses, to find out why a reservation has
|
||||
/// failed.
|
||||
/// \param reservation The reservation.
|
||||
/// \param evse_id The evse id.
|
||||
///
|
||||
void print_reservations_debug_info(const types::reservation::Reservation& reservation,
|
||||
const std::optional<uint32_t> evse_id, const bool reservation_failed);
|
||||
};
|
||||
|
||||
} // namespace module
|
||||
1099
tools/EVerest-main/modules/EVSE/Auth/lib/AuthHandler.cpp
Normal file
1099
tools/EVerest-main/modules/EVSE/Auth/lib/AuthHandler.cpp
Normal file
File diff suppressed because it is too large
Load Diff
39
tools/EVerest-main/modules/EVSE/Auth/lib/CMakeLists.txt
Normal file
39
tools/EVerest-main/modules/EVSE/Auth/lib/CMakeLists.txt
Normal file
@@ -0,0 +1,39 @@
|
||||
set(MODULE_DIR "${PROJECT_SOURCE_DIR}/modules/EVSE/Auth")
|
||||
set(INCLUDE_DIR "${MODULE_DIR}/include")
|
||||
|
||||
add_library(auth_handler STATIC)
|
||||
ev_register_library_target(auth_handler)
|
||||
|
||||
target_sources(auth_handler PRIVATE
|
||||
AuthHandler.cpp
|
||||
Connector.cpp
|
||||
ReservationHandler.cpp
|
||||
ConnectorStateMachine.cpp
|
||||
)
|
||||
|
||||
get_target_property(GENERATED_INCLUDE_DIR generate_cpp_files EVEREST_GENERATED_INCLUDE_DIR)
|
||||
|
||||
target_include_directories(auth_handler PRIVATE
|
||||
${INCLUDE_DIR}
|
||||
${GENERATED_INCLUDE_DIR}
|
||||
)
|
||||
|
||||
add_dependencies(auth_handler generate_cpp_files)
|
||||
|
||||
target_link_libraries(auth_handler
|
||||
PRIVATE
|
||||
everest::timer
|
||||
date::date
|
||||
date::date-tz
|
||||
everest::framework
|
||||
everest::helpers
|
||||
)
|
||||
|
||||
# needs c++ 14
|
||||
target_compile_features(auth_handler PRIVATE cxx_std_14)
|
||||
|
||||
if(EVEREST_ENABLE_COMPILE_WARNINGS)
|
||||
target_compile_options(auth_handler
|
||||
PRIVATE ${EVEREST_COMPILE_OPTIONS}
|
||||
)
|
||||
endif()
|
||||
85
tools/EVerest-main/modules/EVSE/Auth/lib/Connector.cpp
Normal file
85
tools/EVerest-main/modules/EVSE/Auth/lib/Connector.cpp
Normal file
@@ -0,0 +1,85 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include <Connector.hpp>
|
||||
|
||||
namespace module {
|
||||
|
||||
void Connector::submit_event(ConnectorEvent event) {
|
||||
state_machine.handle_event(event);
|
||||
}
|
||||
|
||||
ConnectorState Connector::get_state() const {
|
||||
return this->state_machine.get_state();
|
||||
}
|
||||
|
||||
bool Connector::is_unavailable() const {
|
||||
return this->get_state() == ConnectorState::UNAVAILABLE || this->get_state() == ConnectorState::UNAVAILABLE_FAULTED;
|
||||
}
|
||||
|
||||
namespace conversions {
|
||||
std::string connector_state_to_string(const ConnectorState& state) {
|
||||
switch (state) {
|
||||
case ConnectorState::AVAILABLE:
|
||||
return "AVAILABLE";
|
||||
case ConnectorState::OCCUPIED:
|
||||
return "OCCUPIED";
|
||||
case ConnectorState::UNAVAILABLE:
|
||||
return "UNAVAILABLE";
|
||||
case ConnectorState::FAULTED:
|
||||
return "FAULTED";
|
||||
case ConnectorState::FAULTED_OCCUPIED:
|
||||
return "FAULTED_OCCUPIED";
|
||||
case ConnectorState::UNAVAILABLE_FAULTED:
|
||||
return "UNAVAILABLE_FAULTED";
|
||||
default:
|
||||
throw std::runtime_error("No known conversion for the given connector state");
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace conversions
|
||||
|
||||
bool EVSEContext::is_available() {
|
||||
if (this->plug_in_timeout) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// if an identifier is present, an EVSE is not considered available
|
||||
if (this->identifier.has_value()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool occupied = false;
|
||||
bool available = false;
|
||||
for (const auto& connector : this->connectors) {
|
||||
if (connector.get_state() == ConnectorState::OCCUPIED ||
|
||||
connector.get_state() == ConnectorState::FAULTED_OCCUPIED) {
|
||||
occupied = true;
|
||||
}
|
||||
if (connector.get_state() != ConnectorState::UNAVAILABLE && connector.get_state() != ConnectorState::FAULTED) {
|
||||
available = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (occupied) {
|
||||
// When at least one connector is occupied, they are both not available.
|
||||
return false;
|
||||
}
|
||||
|
||||
return available;
|
||||
}
|
||||
|
||||
bool EVSEContext::is_unavailable() {
|
||||
for (const auto& connector : this->connectors) {
|
||||
if (!connector.is_unavailable()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// namespace conversions
|
||||
|
||||
// namespace conversions
|
||||
} // namespace module
|
||||
@@ -0,0 +1,98 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include <ConnectorStateMachine.hpp>
|
||||
|
||||
namespace module {
|
||||
|
||||
ConnectorStateMachine::ConnectorStateMachine(ConnectorState initial_state) : state(initial_state) {
|
||||
}
|
||||
|
||||
bool ConnectorStateMachine::handle_event(ConnectorEvent event) {
|
||||
switch (state) {
|
||||
case ConnectorState::AVAILABLE:
|
||||
switch (event) {
|
||||
case ConnectorEvent::TRANSACTION_STARTED:
|
||||
state = ConnectorState::OCCUPIED;
|
||||
return true;
|
||||
case ConnectorEvent::DISABLE:
|
||||
state = ConnectorState::UNAVAILABLE;
|
||||
return true;
|
||||
case ConnectorEvent::FAULTED:
|
||||
state = ConnectorState::FAULTED;
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
case ConnectorState::UNAVAILABLE:
|
||||
switch (event) {
|
||||
case ConnectorEvent::ENABLE:
|
||||
state = ConnectorState::AVAILABLE;
|
||||
return true;
|
||||
case ConnectorEvent::FAULTED:
|
||||
state = ConnectorState::UNAVAILABLE_FAULTED;
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
case ConnectorState::FAULTED:
|
||||
switch (event) {
|
||||
case ConnectorEvent::ERROR_CLEARED:
|
||||
state = ConnectorState::AVAILABLE;
|
||||
return true;
|
||||
case ConnectorEvent::DISABLE:
|
||||
state = ConnectorState::UNAVAILABLE_FAULTED;
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
case ConnectorState::OCCUPIED:
|
||||
switch (event) {
|
||||
case ConnectorEvent::SESSION_FINISHED:
|
||||
state = ConnectorState::AVAILABLE;
|
||||
return true;
|
||||
case ConnectorEvent::FAULTED:
|
||||
state = ConnectorState::FAULTED_OCCUPIED;
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
case ConnectorState::FAULTED_OCCUPIED:
|
||||
switch (event) {
|
||||
case ConnectorEvent::ERROR_CLEARED:
|
||||
state = ConnectorState::OCCUPIED;
|
||||
return true;
|
||||
case ConnectorEvent::SESSION_FINISHED:
|
||||
state = ConnectorState::FAULTED;
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
case ConnectorState::UNAVAILABLE_FAULTED:
|
||||
switch (event) {
|
||||
case ConnectorEvent::ENABLE:
|
||||
state = ConnectorState::FAULTED;
|
||||
return true;
|
||||
case ConnectorEvent::ERROR_CLEARED:
|
||||
state = ConnectorState::UNAVAILABLE;
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
default:
|
||||
// could/should not happen!
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
ConnectorState ConnectorStateMachine::get_state() const {
|
||||
return this->state;
|
||||
}
|
||||
|
||||
} // namespace module
|
||||
878
tools/EVerest-main/modules/EVSE/Auth/lib/ReservationHandler.cpp
Normal file
878
tools/EVerest-main/modules/EVSE/Auth/lib/ReservationHandler.cpp
Normal file
@@ -0,0 +1,878 @@
|
||||
#include <ReservationHandler.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include <Connector.hpp>
|
||||
|
||||
#include <everest/helpers/helpers.hpp>
|
||||
#include <everest/logging.hpp>
|
||||
#include <generated/interfaces/kvs/Interface.hpp>
|
||||
#include <utils/date.hpp>
|
||||
|
||||
using everest::helpers::is_equal_case_insensitive;
|
||||
|
||||
namespace module {
|
||||
|
||||
static types::reservation::ReservationResult
|
||||
connector_state_to_reservation_result(const ConnectorState connector_state);
|
||||
|
||||
ReservationHandler::ReservationHandler(std::map<int, std::unique_ptr<module::EVSEContext>>& evses,
|
||||
const std::string& id, kvsIntf* store) :
|
||||
evses(evses), kvs_store_key_id("reservation_" + id), store(store) {
|
||||
// Create this worker thread and io service etc here for the timer.
|
||||
this->work = boost::make_shared<boost::asio::executor_work_guard<boost::asio::io_context::executor_type>>(
|
||||
boost::asio::make_work_guard(this->io_context));
|
||||
this->io_context_thread = std::thread([this]() { this->io_context.run(); });
|
||||
}
|
||||
|
||||
ReservationHandler::~ReservationHandler() {
|
||||
this->reservation_id_to_reservation_timeout_timer_map.clear();
|
||||
work->get_executor().context().stop();
|
||||
(*work).reset(); // explicitly call underlying reset method, not the smart pointer reset
|
||||
io_context.stop();
|
||||
io_context_thread.join();
|
||||
}
|
||||
|
||||
void ReservationHandler::load_reservations() {
|
||||
std::lock_guard<std::recursive_mutex> lk(this->event_mutex);
|
||||
if (this->store == nullptr) {
|
||||
EVLOG_warning << "Can not load reservations because no storage was configured.";
|
||||
return;
|
||||
}
|
||||
|
||||
const auto stored_reservations = store->call_load(this->kvs_store_key_id);
|
||||
const Array* reservations_json = std::get_if<Array>(&stored_reservations);
|
||||
if (reservations_json == nullptr) {
|
||||
EVLOG_warning << "Can not load reservations: reservations is not a json array.";
|
||||
return;
|
||||
}
|
||||
|
||||
for (const auto& reservation : *reservations_json) {
|
||||
types::reservation::Reservation r;
|
||||
try {
|
||||
r = reservation.at("reservation");
|
||||
} catch (const json::exception& e) {
|
||||
EVLOG_error << "Could not get reservation from store: " << e.what();
|
||||
continue;
|
||||
}
|
||||
|
||||
std::optional<uint32_t> evse_id;
|
||||
if (reservation.contains("evse_id")) {
|
||||
evse_id = reservation.at("evse_id");
|
||||
}
|
||||
|
||||
types::reservation::ReservationResult reservation_result = this->make_reservation(evse_id, r);
|
||||
if (reservation_result != types::reservation::ReservationResult::Accepted) {
|
||||
EVLOG_warning << "Load reservations: Could not make reservation with id " << r.reservation_id
|
||||
<< ": reservation cancelled.";
|
||||
this->reservation_cancelled_callback(evse_id, r.reservation_id,
|
||||
types::reservation::ReservationEndReason::Cancelled, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
types::reservation::ReservationResult
|
||||
ReservationHandler::make_reservation(const std::optional<uint32_t> evse_id,
|
||||
const types::reservation::Reservation& reservation) {
|
||||
std::lock_guard<std::recursive_mutex> lk(this->event_mutex);
|
||||
if (date::utc_clock::now() > Everest::Date::from_rfc3339(reservation.expiry_time)) {
|
||||
EVLOG_info << "Rejecting reservation because expire time is in the past.";
|
||||
return types::reservation::ReservationResult::Rejected;
|
||||
}
|
||||
|
||||
// If a reservation was made with an existing reservation id, the existing reservation must be replaced (H01.FR.01).
|
||||
// We cancel the reservation here because of that. That also means that if the reservation can not be made, the old
|
||||
// reservation is cancelled anyway.
|
||||
std::pair<bool, std::optional<uint32_t>> reservation_cancelled = this->cancel_reservation(
|
||||
reservation.reservation_id, false, types::reservation::ReservationEndReason::Cancelled);
|
||||
if (reservation_cancelled.first && reservation_cancelled.second.has_value()) {
|
||||
EVLOG_debug << "Cancelled reservation with id " << reservation.reservation_id << " for evse id "
|
||||
<< reservation_cancelled.second.value() << " because a reservation with the same id was made";
|
||||
}
|
||||
|
||||
if (evse_id.has_value()) {
|
||||
if (this->evse_reservations.count(evse_id.value()) > 0) {
|
||||
// There already is a reservation for this evse.
|
||||
EVLOG_debug << "Rejected reservation because there already is a reservation for this evse.";
|
||||
return types::reservation::ReservationResult::Occupied;
|
||||
}
|
||||
|
||||
if (this->evses.count(evse_id.value()) == 0) {
|
||||
// There is no evse with this evse id.
|
||||
EVLOG_warning << "Rejected reservation because there is no evse with this evse id: " << evse_id.value();
|
||||
return types::reservation::ReservationResult::Rejected;
|
||||
}
|
||||
const types::evse_manager::ConnectorTypeEnum connector_type =
|
||||
reservation.connector_type.value_or(types::evse_manager::ConnectorTypeEnum::Unknown);
|
||||
|
||||
// We have to return a valid state here.
|
||||
// So if one or all connectors are occupied or reserved, return occupied. (H01.FR.11)
|
||||
// If one or all are faulted, return faulted. (H01.FR.12)
|
||||
// If one or all are unavailable, return unavailable. (H01.FR.13)
|
||||
// It is not clear what to return if one is faulted, one occupied and one available so in that case the first
|
||||
// in row is returned, which is occupied.
|
||||
const types::reservation::ReservationResult evse_state =
|
||||
this->get_evse_connector_state_reservation_result(evse_id.value(), this->evse_reservations);
|
||||
const types::reservation::ReservationResult connector_state =
|
||||
this->get_connector_availability_reservation_result(evse_id.value(), connector_type);
|
||||
|
||||
if (!has_evse_connector_type(this->evses[evse_id.value()]->connectors, connector_type)) {
|
||||
EVLOG_debug << "Rejected reservation because this evse (id: " << evse_id.value()
|
||||
<< ") does not have the requested connector type ("
|
||||
<< types::evse_manager::connector_type_enum_to_string(connector_type) << ")";
|
||||
return types::reservation::ReservationResult::Rejected;
|
||||
} else if (evse_state != types::reservation::ReservationResult::Accepted) {
|
||||
print_reservations_debug_info(reservation, evse_id, true);
|
||||
EVLOG_debug << "Rejecting reservation because connector is not available";
|
||||
return evse_state;
|
||||
} else if (connector_state != types::reservation::ReservationResult::Accepted) {
|
||||
print_reservations_debug_info(reservation, evse_id, true);
|
||||
return connector_state;
|
||||
} else {
|
||||
// Everything fine, continue.
|
||||
if (global_reservations.empty()) {
|
||||
set_reservation_timer(reservation, evse_id);
|
||||
this->evse_reservations[evse_id.value()] = reservation;
|
||||
EVLOG_info << "Created reservation for evse id " << evse_id.value() << ", connector type "
|
||||
<< types::evse_manager::connector_type_enum_to_string(
|
||||
reservation.connector_type.value_or(types::evse_manager::ConnectorTypeEnum::Unknown));
|
||||
return types::reservation::ReservationResult::Accepted;
|
||||
}
|
||||
|
||||
// Make a copy of the evse specific reservations map so we can add this reservation to test if the
|
||||
// reservation is possible. Only if it is, we add it to the 'member' map.
|
||||
std::map<uint32_t, types::reservation::Reservation> evse_specific_reservations = this->evse_reservations;
|
||||
evse_specific_reservations[evse_id.value()] = reservation;
|
||||
|
||||
// Check if the reservations are possible with the added evse specific reservation.
|
||||
if (!is_reservation_possible(std::nullopt, this->global_reservations, evse_specific_reservations)) {
|
||||
print_reservations_debug_info(reservation, evse_id, true);
|
||||
return get_reservation_evse_connector_state(connector_type);
|
||||
}
|
||||
|
||||
// Reservation is possible, add to evse specific reservations.
|
||||
this->evse_reservations[evse_id.value()] = reservation;
|
||||
EVLOG_info << "Created reservation for evse id " << evse_id.value() << ", connector type "
|
||||
<< types::evse_manager::connector_type_enum_to_string(
|
||||
reservation.connector_type.value_or(types::evse_manager::ConnectorTypeEnum::Unknown));
|
||||
}
|
||||
} else {
|
||||
if (reservation.connector_type.has_value() &&
|
||||
!does_evse_connector_type_exist(reservation.connector_type.value())) {
|
||||
EVLOG_info << "Can not make reservation because the connector type does not exist.";
|
||||
return types::reservation::ReservationResult::Rejected;
|
||||
}
|
||||
|
||||
const types::evse_manager::ConnectorTypeEnum connector_type =
|
||||
reservation.connector_type.value_or(types::evse_manager::ConnectorTypeEnum::Unknown);
|
||||
if (!is_reservation_possible(connector_type, this->global_reservations, this->evse_reservations)) {
|
||||
print_reservations_debug_info(reservation, evse_id, true);
|
||||
return get_reservation_evse_connector_state(connector_type);
|
||||
}
|
||||
|
||||
EVLOG_info << "Created reservation for connector type "
|
||||
<< types::evse_manager::connector_type_enum_to_string(
|
||||
reservation.connector_type.value_or(types::evse_manager::ConnectorTypeEnum::Unknown));
|
||||
global_reservations.push_back(reservation);
|
||||
store_reservations();
|
||||
}
|
||||
|
||||
set_reservation_timer(reservation, evse_id);
|
||||
|
||||
return types::reservation::ReservationResult::Accepted;
|
||||
}
|
||||
|
||||
void ReservationHandler::on_connector_state_changed(const ConnectorState connector_state, const uint32_t evse_id,
|
||||
const uint32_t connector_id) {
|
||||
std::lock_guard<std::recursive_mutex> lk(this->event_mutex);
|
||||
if (connector_state == ConnectorState::AVAILABLE) {
|
||||
// Nothing to cancel.
|
||||
return;
|
||||
}
|
||||
|
||||
if (this->evses.count(static_cast<int32_t>(evse_id)) == 0) {
|
||||
EVLOG_warning << "on_connector_state_changed: evse " << evse_id << " does not exist. This should not happen.";
|
||||
return;
|
||||
}
|
||||
|
||||
auto& connectors = this->evses[evse_id]->connectors;
|
||||
auto connector_it = std::find_if(connectors.begin(), connectors.end(),
|
||||
[connector_id](const auto& connector) { return connector_id == connector.id; });
|
||||
|
||||
if (connector_it == connectors.end()) {
|
||||
// Connector with specific connector id not found
|
||||
EVLOG_warning << "Could not change connector state for connector id " << connector_id << " of evse " << evse_id
|
||||
<< ": connector id does not exist. This should not happen.";
|
||||
return;
|
||||
}
|
||||
|
||||
const bool reservation_exists = evse_reservations.count(evse_id) != 0;
|
||||
|
||||
if (reservation_exists && evse_reservations[evse_id].connector_type.has_value() &&
|
||||
(connector_it->type == evse_reservations[evse_id].connector_type.value() ||
|
||||
connector_it->type == types::evse_manager::ConnectorTypeEnum::Unknown ||
|
||||
evse_reservations[evse_id].connector_type.value() == types::evse_manager::ConnectorTypeEnum::Unknown)) {
|
||||
cancel_reservation(evse_reservations[evse_id].reservation_id, true,
|
||||
types::reservation::ReservationEndReason::Cancelled);
|
||||
return;
|
||||
}
|
||||
|
||||
// Now we might have one connector less, let's check if all reservations are still possible now and if not, cancel
|
||||
// the one(s) that can not be done anymore.
|
||||
check_reservations_and_cancel_if_not_possible();
|
||||
}
|
||||
|
||||
bool ReservationHandler::is_charging_possible(const uint32_t evse_id) {
|
||||
std::lock_guard<std::recursive_mutex> lk(this->event_mutex);
|
||||
if (this->evse_reservations.count(evse_id) > 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this->evses.count(evse_id) == 0) {
|
||||
// Not existing evse id
|
||||
return false;
|
||||
}
|
||||
|
||||
std::map<uint32_t, types::reservation::Reservation> reservations = this->evse_reservations;
|
||||
// We want to test if charging is possible on this evse id with the current reservations. For that, we do like it
|
||||
// is a new reservation and check if that reservation is possible. If it is, we can charge on that evse.
|
||||
types::reservation::Reservation r;
|
||||
// It is a dummy reservation so the details are not important.
|
||||
reservations[evse_id] = r;
|
||||
return is_reservation_possible(std::nullopt, this->global_reservations, reservations);
|
||||
}
|
||||
|
||||
bool ReservationHandler::is_evse_reserved(const uint32_t evse_id) {
|
||||
std::lock_guard<std::recursive_mutex> lk(this->event_mutex);
|
||||
if (this->evse_reservations.count(evse_id) > 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
std::pair<bool, std::optional<uint32_t>>
|
||||
ReservationHandler::cancel_reservation(const int reservation_id, const bool execute_callback,
|
||||
const types::reservation::ReservationEndReason reason) {
|
||||
std::lock_guard<std::recursive_mutex> lk(this->event_mutex);
|
||||
std::pair<bool, std::optional<uint32_t>> result;
|
||||
|
||||
bool reservation_cancelled = false;
|
||||
|
||||
auto reservation_id_timer_it = this->reservation_id_to_reservation_timeout_timer_map.find(reservation_id);
|
||||
if (reservation_id_timer_it != this->reservation_id_to_reservation_timeout_timer_map.end()) {
|
||||
reservation_id_timer_it->second->stop();
|
||||
this->reservation_id_to_reservation_timeout_timer_map.erase(reservation_id_timer_it);
|
||||
reservation_cancelled = true;
|
||||
result.first = true;
|
||||
}
|
||||
|
||||
if (!reservation_cancelled) {
|
||||
result.first = false;
|
||||
return result;
|
||||
}
|
||||
|
||||
EVLOG_info << "Cancel reservation with reservation id " << reservation_id;
|
||||
|
||||
std::optional<uint32_t> evse_id;
|
||||
for (const auto& reservation : this->evse_reservations) {
|
||||
if (reservation.second.reservation_id == reservation_id) {
|
||||
evse_id = reservation.first;
|
||||
}
|
||||
}
|
||||
|
||||
if (evse_id.has_value()) {
|
||||
auto it = this->evse_reservations.find(evse_id.value());
|
||||
if (it != this->evse_reservations.end()) {
|
||||
this->evse_reservations.erase(it);
|
||||
} else {
|
||||
EVLOG_warning << "Could not remove reservation with evse id " << evse_id.value()
|
||||
<< ": this should not happen";
|
||||
}
|
||||
|
||||
} else {
|
||||
// No evse, search in global reservations
|
||||
const auto& it = std::find_if(this->global_reservations.begin(), this->global_reservations.end(),
|
||||
[reservation_id](const types::reservation::Reservation& reservation) {
|
||||
return reservation.reservation_id == reservation_id;
|
||||
});
|
||||
|
||||
if (it != this->global_reservations.end()) {
|
||||
this->global_reservations.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
this->store_reservations();
|
||||
|
||||
if (execute_callback && this->reservation_cancelled_callback != nullptr) {
|
||||
this->reservation_cancelled_callback(evse_id, reservation_id, reason, execute_callback);
|
||||
}
|
||||
|
||||
result.second = evse_id;
|
||||
return result;
|
||||
}
|
||||
|
||||
bool ReservationHandler::cancel_reservation(const uint32_t evse_id, const bool execute_callback) {
|
||||
auto it = this->evse_reservations.find(evse_id);
|
||||
if (it != this->evse_reservations.end()) {
|
||||
int reservation_id = it->second.reservation_id;
|
||||
return this
|
||||
->cancel_reservation(reservation_id, execute_callback, types::reservation::ReservationEndReason::Cancelled)
|
||||
.first;
|
||||
} else {
|
||||
EVLOG_warning << "Could not cancel reservation with evse id " << evse_id;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void ReservationHandler::register_reservation_cancelled_callback(
|
||||
const std::function<void(const std::optional<uint32_t>& evse_id, const int32_t reservation_id,
|
||||
const types::reservation::ReservationEndReason reason,
|
||||
const bool send_reservation_update)>& callback) {
|
||||
this->reservation_cancelled_callback = callback;
|
||||
}
|
||||
|
||||
void ReservationHandler::on_reservation_used(const int32_t reservation_id) {
|
||||
std::lock_guard<std::recursive_mutex> lk(this->event_mutex);
|
||||
const std::pair<bool, std::optional<uint32_t>> cancelled =
|
||||
this->cancel_reservation(reservation_id, false, types::reservation::ReservationEndReason::UsedToStartCharging);
|
||||
if (cancelled.first) {
|
||||
if (cancelled.second.has_value()) {
|
||||
EVLOG_info << "Reservation (" << reservation_id << ") for evse#" << cancelled.second.value()
|
||||
<< " used and cancelled";
|
||||
} else {
|
||||
EVLOG_info << "Reservation (" << reservation_id << ") without evse id used and cancelled";
|
||||
}
|
||||
} else {
|
||||
EVLOG_info << "Could not cancel reservation with reservation id " << reservation_id;
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<int32_t> ReservationHandler::matches_reserved_identifier(const std::string& id_token,
|
||||
const std::optional<uint32_t> evse_id,
|
||||
std::optional<std::string> parent_id_token) {
|
||||
std::lock_guard<std::recursive_mutex> lk(this->event_mutex);
|
||||
EVLOG_debug << "Matches reserved identifier for evse id " << (evse_id.has_value() ? evse_id.value() : 9999)
|
||||
<< " and id token " << everest::helpers::redact(id_token) << " and parent id token "
|
||||
<< (parent_id_token.has_value() ? everest::helpers::redact(parent_id_token.value()) : "-");
|
||||
|
||||
// Return true if id tokens match or parent id tokens exists and match.
|
||||
if (evse_id.has_value()) {
|
||||
if (this->evse_reservations.count(evse_id.value())) {
|
||||
const types::reservation::Reservation& reservation = this->evse_reservations[evse_id.value()];
|
||||
if (is_equal_case_insensitive(reservation.id_token, id_token) ||
|
||||
(parent_id_token.has_value() && reservation.parent_id_token.has_value() &&
|
||||
is_equal_case_insensitive(parent_id_token.value(), reservation.parent_id_token.value()))) {
|
||||
EVLOG_debug << "There is a reservation (" << reservation.reservation_id << ") for evse "
|
||||
<< evse_id.value() << " and the token matches";
|
||||
return reservation.reservation_id;
|
||||
} else {
|
||||
EVLOG_debug << "There is a reservation for evse id " << evse_id.value() << ", but token does not match";
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If evse_id == 0 or there is no reservation found with the given evse id, search globally for reservation with
|
||||
// this token.
|
||||
for (const auto& reservation : global_reservations) {
|
||||
if (is_equal_case_insensitive(reservation.id_token, id_token) ||
|
||||
(parent_id_token.has_value() && reservation.parent_id_token.has_value() &&
|
||||
is_equal_case_insensitive(parent_id_token.value(), reservation.parent_id_token.value()))) {
|
||||
EVLOG_debug << "There is a reservation for the token, reservation id: " << reservation.reservation_id;
|
||||
return reservation.reservation_id;
|
||||
}
|
||||
}
|
||||
|
||||
EVLOG_debug << "No reservation found which matches the reserved identifier";
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
bool ReservationHandler::has_reservation_parent_id(const std::optional<uint32_t> evse_id) {
|
||||
std::lock_guard<std::recursive_mutex> lk(this->event_mutex);
|
||||
if (evse_id.has_value()) {
|
||||
if (this->evses.count(evse_id.value()) == 0) {
|
||||
// EVSE id does not exist.
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this->evse_reservations.count(evse_id.value())) {
|
||||
return this->evse_reservations[evse_id.value()].parent_id_token.has_value();
|
||||
}
|
||||
}
|
||||
|
||||
// Check if one of the global reservations has a parent id.
|
||||
for (const auto& reservation : this->global_reservations) {
|
||||
if (reservation.parent_id_token.has_value()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
ReservationEvseStatus ReservationHandler::check_number_global_reservations_match_number_available_evses() {
|
||||
std::unique_lock<std::recursive_mutex> reservation_lock(this->event_mutex);
|
||||
std::set<int32_t> available_evses;
|
||||
// Get all evse's that are not reserved or used.
|
||||
for (const auto& evse : this->evses) {
|
||||
if (get_evse_connector_state_reservation_result(static_cast<uint32_t>(evse.first), this->evse_reservations) ==
|
||||
types::reservation::ReservationResult::Accepted &&
|
||||
get_connector_availability_reservation_result(static_cast<uint32_t>(evse.first),
|
||||
types::evse_manager::ConnectorTypeEnum::Unknown) ==
|
||||
types::reservation::ReservationResult::Accepted) {
|
||||
available_evses.insert(evse.first);
|
||||
}
|
||||
}
|
||||
if (available_evses.size() == this->global_reservations.size()) {
|
||||
// There are as many evses available as 'global' reservations, so all evse's are reserved. Set all available
|
||||
// evse's to reserved.
|
||||
return get_evse_global_reserved_status_and_set_new_status(available_evses, available_evses);
|
||||
}
|
||||
|
||||
// There are not as many global reservations as available evse's, but we have to check for specific connector types
|
||||
// as well.
|
||||
std::set<int32_t> reserved_evses_with_specific_connector_type;
|
||||
for (const auto& global_reservation : this->global_reservations) {
|
||||
if (!is_reservation_possible(global_reservation.connector_type, this->global_reservations,
|
||||
this->evse_reservations)) {
|
||||
// A new reservation with this type is not possible (so also arrival of an extra car is not), so all evse's
|
||||
// with this connector type should be set to reserved.
|
||||
for (const auto& evse : this->evses) {
|
||||
if (available_evses.find(evse.first) != available_evses.end() &&
|
||||
this->has_evse_connector_type(
|
||||
evse.second->connectors,
|
||||
global_reservation.connector_type.value_or(types::evse_manager::ConnectorTypeEnum::Unknown))) {
|
||||
// This evse is available and has a specific connector type. So it should be set to unavailable.
|
||||
reserved_evses_with_specific_connector_type.insert(evse.first);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return get_evse_global_reserved_status_and_set_new_status(available_evses,
|
||||
reserved_evses_with_specific_connector_type);
|
||||
}
|
||||
|
||||
bool ReservationHandler::has_evse_connector_type(const std::vector<Connector>& evse_connectors,
|
||||
const types::evse_manager::ConnectorTypeEnum connector_type) const {
|
||||
if (connector_type == types::evse_manager::ConnectorTypeEnum::Unknown) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (const auto& connector : evse_connectors) {
|
||||
if (connector.type == types::evse_manager::ConnectorTypeEnum::Unknown || connector.type == connector_type) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ReservationHandler::does_evse_connector_type_exist(
|
||||
const types::evse_manager::ConnectorTypeEnum connector_type) const {
|
||||
for (const auto& [evse_id, evse] : evses) {
|
||||
if (has_evse_connector_type(evse->connectors, connector_type)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
types::reservation::ReservationResult ReservationHandler::get_evse_connector_state_reservation_result(
|
||||
const uint32_t evse_id, const std::map<uint32_t, types::reservation::Reservation>& evse_specific_reservations) {
|
||||
if (evses.count(evse_id) == 0) {
|
||||
EVLOG_warning << "Get evse state for evse " << evse_id
|
||||
<< " not possible: evse id does not exists. This should not happen.";
|
||||
return types::reservation::ReservationResult::Rejected;
|
||||
}
|
||||
|
||||
// Check if evse is available.
|
||||
if (evses[evse_id]->plugged_in) {
|
||||
return connector_state_to_reservation_result(ConnectorState::OCCUPIED);
|
||||
}
|
||||
|
||||
// If one connector is occupied, then the other connector can also not be used (one connector of an evse can be
|
||||
// used at the same time).
|
||||
for (const auto& connector : evses[evse_id]->connectors) {
|
||||
if (connector.get_state() == ConnectorState::OCCUPIED ||
|
||||
connector.get_state() == ConnectorState::FAULTED_OCCUPIED) {
|
||||
return connector_state_to_reservation_result(connector.get_state());
|
||||
}
|
||||
}
|
||||
|
||||
// If evse is reserved, it is not available.
|
||||
if (evse_specific_reservations.count(evse_id) > 0) {
|
||||
return types::reservation::ReservationResult::Occupied;
|
||||
}
|
||||
|
||||
return types::reservation::ReservationResult::Accepted;
|
||||
}
|
||||
|
||||
types::reservation::ReservationResult ReservationHandler::get_connector_availability_reservation_result(
|
||||
const uint32_t evse_id, const types::evse_manager::ConnectorTypeEnum connector_type) {
|
||||
if (evses.count(evse_id) == 0) {
|
||||
EVLOG_warning << "Request if connector is available for evse id " << evse_id
|
||||
<< ", but evse id does not exist. This should not happen.";
|
||||
return types::reservation::ReservationResult::Rejected;
|
||||
}
|
||||
|
||||
ConnectorState connector_state = ConnectorState::UNAVAILABLE;
|
||||
|
||||
for (const auto& connector : evses[evse_id]->connectors) {
|
||||
if ((connector.type == connector_type || connector.type == types::evse_manager::ConnectorTypeEnum::Unknown ||
|
||||
connector_type == types::evse_manager::ConnectorTypeEnum::Unknown)) {
|
||||
if (connector.get_state() == ConnectorState::AVAILABLE) {
|
||||
return types::reservation::ReservationResult::Accepted;
|
||||
} else {
|
||||
connector_state = get_new_connector_state(connector_state, connector.get_state());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return connector_state_to_reservation_result(connector_state);
|
||||
}
|
||||
|
||||
std::vector<std::vector<types::evse_manager::ConnectorTypeEnum>> ReservationHandler::get_all_possible_orders(
|
||||
const std::vector<types::evse_manager::ConnectorTypeEnum>& connectors) const {
|
||||
|
||||
std::vector<types::evse_manager::ConnectorTypeEnum> input_next = connectors;
|
||||
std::vector<types::evse_manager::ConnectorTypeEnum> input_prev = connectors;
|
||||
std::vector<std::vector<types::evse_manager::ConnectorTypeEnum>> output;
|
||||
|
||||
if (connectors.empty()) {
|
||||
return output;
|
||||
}
|
||||
|
||||
// For next_permutation, the input must be ordered or it will stop halfway. So if it stops halafway,
|
||||
// prev_permutation will find the others.
|
||||
do {
|
||||
output.push_back(input_next);
|
||||
} while (std::next_permutation(input_next.begin(), input_next.end()));
|
||||
|
||||
while (std::prev_permutation(input_prev.begin(), input_prev.end())) {
|
||||
output.push_back(input_prev);
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
bool ReservationHandler::can_virtual_car_arrive(
|
||||
const std::vector<uint32_t>& used_evse_ids,
|
||||
const std::vector<types::evse_manager::ConnectorTypeEnum>& next_car_arrival_order,
|
||||
const std::map<uint32_t, types::reservation::Reservation>& evse_specific_reservations) {
|
||||
|
||||
bool is_possible = false;
|
||||
|
||||
for (const auto& [evse_id, evse] : evses) {
|
||||
// Check if there is a car already at this evse id.
|
||||
if (std::find(used_evse_ids.begin(), used_evse_ids.end(), evse_id) != used_evse_ids.end()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (get_evse_connector_state_reservation_result(evse_id, evse_specific_reservations) ==
|
||||
types::reservation::ReservationResult::Accepted &&
|
||||
has_evse_connector_type(evse->connectors, next_car_arrival_order.at(0)) &&
|
||||
get_connector_availability_reservation_result(evse_id, next_car_arrival_order.at(0)) ==
|
||||
types::reservation::ReservationResult::Accepted) {
|
||||
is_possible = true;
|
||||
|
||||
std::vector<uint32_t> next_used_evse_ids = used_evse_ids;
|
||||
// Add evse id to list when we call the function recursively.
|
||||
next_used_evse_ids.push_back(evse_id);
|
||||
|
||||
// Check if this is the last.
|
||||
if (next_car_arrival_order.size() == 1) {
|
||||
// If this is the last and a car can arrive, then this combination is possible.
|
||||
return true;
|
||||
}
|
||||
|
||||
// Call next level recursively.
|
||||
// Remove connector type ('car') from list when we call the function recursively.
|
||||
const std::vector<types::evse_manager::ConnectorTypeEnum> next_arrival_order(
|
||||
next_car_arrival_order.begin() + 1, next_car_arrival_order.end());
|
||||
|
||||
if (!this->can_virtual_car_arrive(next_used_evse_ids, next_arrival_order, evse_specific_reservations)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return is_possible;
|
||||
}
|
||||
|
||||
bool ReservationHandler::is_reservation_possible(
|
||||
const std::optional<types::evse_manager::ConnectorTypeEnum> global_reservation_type,
|
||||
const std::vector<types::reservation::Reservation>& reservations_no_evse,
|
||||
const std::map<uint32_t, types::reservation::Reservation>& evse_specific_reservations) {
|
||||
|
||||
std::vector<types::evse_manager::ConnectorTypeEnum> types;
|
||||
for (const auto& global_reservation : reservations_no_evse) {
|
||||
types.push_back(global_reservation.connector_type.value_or(types::evse_manager::ConnectorTypeEnum::Unknown));
|
||||
}
|
||||
|
||||
if (global_reservation_type.has_value()) {
|
||||
types.push_back(global_reservation_type.value());
|
||||
}
|
||||
|
||||
// Check if the total amount of reservations is not more than the total amount of evse's.
|
||||
if (types.size() + evse_specific_reservations.size() > this->evses.size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::vector<std::vector<types::evse_manager::ConnectorTypeEnum>> orders = get_all_possible_orders(types);
|
||||
|
||||
for (const auto& o : orders) {
|
||||
if (!this->can_virtual_car_arrive({}, o, evse_specific_reservations)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ReservationHandler::set_reservation_timer(const types::reservation::Reservation& reservation,
|
||||
const std::optional<uint32_t> evse_id) {
|
||||
std::lock_guard<std::recursive_mutex> lk(this->event_mutex);
|
||||
this->reservation_id_to_reservation_timeout_timer_map[reservation.reservation_id] =
|
||||
std::make_unique<Everest::SteadyTimer>(&this->io_context);
|
||||
|
||||
this->reservation_id_to_reservation_timeout_timer_map[reservation.reservation_id]->at(
|
||||
[this, reservation, evse_id]() {
|
||||
if (evse_id.has_value()) {
|
||||
EVLOG_info << "Reservation expired for evse #" << evse_id.value()
|
||||
<< " (reservation id: " << reservation.reservation_id << ")";
|
||||
} else {
|
||||
EVLOG_info << "Reservation expired for reservation id " << reservation.reservation_id;
|
||||
}
|
||||
|
||||
this->cancel_reservation(reservation.reservation_id, true,
|
||||
types::reservation::ReservationEndReason::Expired);
|
||||
},
|
||||
Everest::Date::from_rfc3339(reservation.expiry_time));
|
||||
}
|
||||
|
||||
std::vector<EVSEContext*> ReservationHandler::get_all_evses_with_connector_type(
|
||||
const types::evse_manager::ConnectorTypeEnum connector_type) const {
|
||||
std::vector<EVSEContext*> result;
|
||||
for (const auto& evse : this->evses) {
|
||||
if (this->has_evse_connector_type(evse.second->connectors, connector_type)) {
|
||||
result.push_back(evse.second.get());
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
ConnectorState ReservationHandler::get_new_connector_state(ConnectorState current_state,
|
||||
const ConnectorState new_state) const {
|
||||
if (new_state == ConnectorState::OCCUPIED) {
|
||||
return ConnectorState::OCCUPIED;
|
||||
}
|
||||
|
||||
if (new_state > current_state) {
|
||||
if (new_state > ConnectorState::OCCUPIED) {
|
||||
if (new_state == ConnectorState::FAULTED_OCCUPIED) {
|
||||
current_state = ConnectorState::OCCUPIED;
|
||||
} else if (new_state == ConnectorState::UNAVAILABLE_FAULTED) {
|
||||
if (current_state != ConnectorState::OCCUPIED) {
|
||||
current_state = ConnectorState::FAULTED;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
current_state = new_state;
|
||||
}
|
||||
}
|
||||
|
||||
return current_state;
|
||||
}
|
||||
|
||||
types::reservation::ReservationResult ReservationHandler::get_reservation_evse_connector_state(
|
||||
const types::evse_manager::ConnectorTypeEnum connector_type) const {
|
||||
// If at least one connector is occupied, return occupied.
|
||||
if (!global_reservations.empty() || !(evse_reservations.empty())) {
|
||||
return types::reservation::ReservationResult::Occupied;
|
||||
}
|
||||
|
||||
bool found_state = false;
|
||||
|
||||
ConnectorState state = ConnectorState::UNAVAILABLE;
|
||||
|
||||
for (const auto& [evse_id, evse] : evses) {
|
||||
if (evse->plugged_in) {
|
||||
// Overwrite state if we found a connector that was not available (if needed).
|
||||
state = get_new_connector_state(state, ConnectorState::OCCUPIED);
|
||||
found_state = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found_state) {
|
||||
const std::vector<EVSEContext*> evses_with_connector_type =
|
||||
this->get_all_evses_with_connector_type(connector_type);
|
||||
if (evses_with_connector_type.empty()) {
|
||||
// This should not happen because then it should have been rejected before already somewhere in the
|
||||
// code...
|
||||
return types::reservation::ReservationResult::Rejected;
|
||||
}
|
||||
|
||||
// Get all evse's with this specific connector type and check the connectors availability states.
|
||||
for (const auto& evse : evses_with_connector_type) {
|
||||
for (const auto& connector : evse->connectors) {
|
||||
if (connector.type != connector_type &&
|
||||
connector.type != types::evse_manager::ConnectorTypeEnum::Unknown &&
|
||||
connector_type != types::evse_manager::ConnectorTypeEnum::Unknown) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (connector.get_state() != ConnectorState::AVAILABLE) {
|
||||
state = get_new_connector_state(state, connector.get_state());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return connector_state_to_reservation_result(state);
|
||||
}
|
||||
|
||||
void ReservationHandler::check_reservations_and_cancel_if_not_possible() {
|
||||
|
||||
std::vector<int32_t> reservations_to_cancel;
|
||||
std::map<uint32_t, types::reservation::Reservation> evse_specific_reservations;
|
||||
std::vector<types::reservation::Reservation> reservations_no_evse;
|
||||
|
||||
for (const auto& [evse_id, reservation] : this->evse_reservations) {
|
||||
evse_specific_reservations[evse_id] = reservation;
|
||||
if (!is_reservation_possible(std::nullopt, reservations_no_evse, evse_specific_reservations)) {
|
||||
reservations_to_cancel.push_back(reservation.reservation_id);
|
||||
evse_specific_reservations.erase(evse_id);
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& reservation : this->global_reservations) {
|
||||
if (is_reservation_possible(reservation.connector_type, reservations_no_evse, evse_specific_reservations)) {
|
||||
reservations_no_evse.push_back(reservation);
|
||||
} else {
|
||||
reservations_to_cancel.push_back(reservation.reservation_id);
|
||||
}
|
||||
}
|
||||
|
||||
for (const int32_t reservation_id : reservations_to_cancel) {
|
||||
this->cancel_reservation(reservation_id, true, types::reservation::ReservationEndReason::Cancelled);
|
||||
}
|
||||
}
|
||||
|
||||
void ReservationHandler::store_reservations() {
|
||||
if (this->store == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
Array reservations = json::array();
|
||||
for (const auto& reservation : this->evse_reservations) {
|
||||
|
||||
json r = json::object({{"evse_id", reservation.first}, {"reservation", reservation.second}});
|
||||
reservations.push_back(r);
|
||||
}
|
||||
|
||||
for (const auto& reservation : this->global_reservations) {
|
||||
json r = json::object({{"reservation", reservation}});
|
||||
reservations.push_back(r);
|
||||
}
|
||||
|
||||
if (!reservations.empty()) {
|
||||
this->store->call_store(this->kvs_store_key_id, reservations);
|
||||
}
|
||||
}
|
||||
|
||||
ReservationEvseStatus ReservationHandler::get_evse_global_reserved_status_and_set_new_status(
|
||||
const std::set<int32_t>& currently_available_evses, const std::set<int32_t>& reserved_evses) {
|
||||
ReservationEvseStatus evse_status_to_send;
|
||||
std::set<int32_t> new_reserved_evses;
|
||||
|
||||
for (const auto evse_id : reserved_evses) {
|
||||
if (this->last_reserved_status.find(evse_id) != this->last_reserved_status.end()) {
|
||||
// Evse was already reserved, don't add it to the new status.
|
||||
} else {
|
||||
evse_status_to_send.reserved.insert(evse_id);
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto evse_id : currently_available_evses) {
|
||||
const bool is_reserved = reserved_evses.find(evse_id) != reserved_evses.end();
|
||||
const bool was_reserved = this->last_reserved_status.find(evse_id) != this->last_reserved_status.end();
|
||||
if (not is_reserved) {
|
||||
if (was_reserved) {
|
||||
evse_status_to_send.available.insert(evse_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
new_reserved_evses = reserved_evses;
|
||||
this->last_reserved_status = new_reserved_evses;
|
||||
|
||||
return evse_status_to_send;
|
||||
}
|
||||
|
||||
void ReservationHandler::print_reservations_debug_info(const types::reservation::Reservation& reservation,
|
||||
const std::optional<uint32_t> evse_id,
|
||||
const bool reservation_failed) {
|
||||
std::string reservation_information;
|
||||
if (reservation_failed) {
|
||||
reservation_information = "Reservation not possible";
|
||||
} else {
|
||||
reservation_information = "New reservation";
|
||||
}
|
||||
EVLOG_debug << reservation_information
|
||||
<< ". Evse id: " << (evse_id.has_value() ? std::to_string(evse_id.value()) : "no evse id")
|
||||
<< ", connector type: "
|
||||
<< (reservation.connector_type.has_value()
|
||||
? types::evse_manager::connector_type_enum_to_string(reservation.connector_type.value())
|
||||
: "no connector type given");
|
||||
std::string evse_info;
|
||||
for (const auto& evse : this->evses) {
|
||||
evse_info += "- " + std::to_string(evse.first) + ":\n";
|
||||
for (const auto& connector : evse.second->connectors) {
|
||||
evse_info += "--- " + std::to_string(connector.id) + " " +
|
||||
types::evse_manager::connector_type_enum_to_string(connector.type) +
|
||||
", available: " + (connector.get_state() == ConnectorState::AVAILABLE ? "yes" : "no") + "\n";
|
||||
}
|
||||
}
|
||||
std::string reservation_info;
|
||||
for (const auto& evse_reservation : this->evse_reservations) {
|
||||
reservation_info +=
|
||||
"- evse " + std::to_string(evse_reservation.first) + ": " +
|
||||
types::evse_manager::connector_type_enum_to_string(
|
||||
evse_reservation.second.connector_type.value_or(types::evse_manager::ConnectorTypeEnum::Unknown)) +
|
||||
"\n";
|
||||
}
|
||||
|
||||
for (const auto& reservation : this->global_reservations) {
|
||||
reservation_info += "- global : " +
|
||||
types::evse_manager::connector_type_enum_to_string(
|
||||
reservation.connector_type.value_or(types::evse_manager::ConnectorTypeEnum::Unknown)) +
|
||||
"\n";
|
||||
}
|
||||
|
||||
EVLOG_debug << "Current evse's and states: \n" << evse_info;
|
||||
EVLOG_debug << "Current reservations: \n" << reservation_info;
|
||||
}
|
||||
|
||||
static types::reservation::ReservationResult
|
||||
connector_state_to_reservation_result(const ConnectorState connector_state) {
|
||||
switch (connector_state) {
|
||||
case ConnectorState::AVAILABLE:
|
||||
return types::reservation::ReservationResult::Accepted;
|
||||
case ConnectorState::UNAVAILABLE:
|
||||
return types::reservation::ReservationResult::Unavailable;
|
||||
case ConnectorState::FAULTED:
|
||||
case ConnectorState::UNAVAILABLE_FAULTED:
|
||||
case ConnectorState::FAULTED_OCCUPIED:
|
||||
return types::reservation::ReservationResult::Faulted;
|
||||
case ConnectorState::OCCUPIED:
|
||||
return types::reservation::ReservationResult::Occupied;
|
||||
}
|
||||
|
||||
return types::reservation::ReservationResult::Rejected;
|
||||
}
|
||||
|
||||
} // namespace module
|
||||
32
tools/EVerest-main/modules/EVSE/Auth/main/authImpl.cpp
Normal file
32
tools/EVerest-main/modules/EVSE/Auth/main/authImpl.cpp
Normal file
@@ -0,0 +1,32 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include "authImpl.hpp"
|
||||
|
||||
#include <everest/logging.hpp>
|
||||
#include <utility>
|
||||
|
||||
namespace module {
|
||||
namespace main {
|
||||
|
||||
void authImpl::init() {
|
||||
}
|
||||
|
||||
void authImpl::ready() {
|
||||
}
|
||||
|
||||
void authImpl::handle_set_connection_timeout(int& connection_timeout) {
|
||||
this->mod->set_connection_timeout(connection_timeout);
|
||||
}
|
||||
|
||||
void authImpl::handle_set_master_pass_group_id(std::string& master_pass_group_id) {
|
||||
this->mod->set_master_pass_group_id(master_pass_group_id);
|
||||
}
|
||||
|
||||
types::authorization::WithdrawAuthorizationResult
|
||||
authImpl::handle_withdraw_authorization(WithdrawAuthorizationRequest& request) {
|
||||
return this->mod->handle_withdraw_authorization(request);
|
||||
}
|
||||
|
||||
} // namespace main
|
||||
} // namespace module
|
||||
64
tools/EVerest-main/modules/EVSE/Auth/main/authImpl.hpp
Normal file
64
tools/EVerest-main/modules/EVSE/Auth/main/authImpl.hpp
Normal file
@@ -0,0 +1,64 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
#ifndef MAIN_AUTH_IMPL_HPP
|
||||
#define MAIN_AUTH_IMPL_HPP
|
||||
|
||||
//
|
||||
// AUTO GENERATED - MARKED REGIONS WILL BE KEPT
|
||||
// template version 3
|
||||
//
|
||||
|
||||
#include <generated/interfaces/auth/Implementation.hpp>
|
||||
|
||||
#include "../Auth.hpp"
|
||||
|
||||
// ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1
|
||||
// insert your custom include headers here
|
||||
// ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1
|
||||
|
||||
namespace module {
|
||||
namespace main {
|
||||
|
||||
struct Conf {};
|
||||
|
||||
class authImpl : public authImplBase {
|
||||
public:
|
||||
authImpl() = delete;
|
||||
authImpl(Everest::ModuleAdapter* ev, const Everest::PtrContainer<Auth>& mod, Conf& config) :
|
||||
authImplBase(ev, "main"), mod(mod), config(config){};
|
||||
|
||||
// ev@8ea32d28-373f-4c90-ae5e-b4fcc74e2a61:v1
|
||||
// insert your public definitions here
|
||||
// ev@8ea32d28-373f-4c90-ae5e-b4fcc74e2a61:v1
|
||||
|
||||
protected:
|
||||
// command handler functions (virtual)
|
||||
virtual void handle_set_connection_timeout(int& connection_timeout) override;
|
||||
virtual void handle_set_master_pass_group_id(std::string& master_pass_group_id) override;
|
||||
virtual types::authorization::WithdrawAuthorizationResult
|
||||
handle_withdraw_authorization(types::authorization::WithdrawAuthorizationRequest& request) override;
|
||||
|
||||
// ev@d2d1847a-7b88-41dd-ad07-92785f06f5c4:v1
|
||||
// insert your protected definitions here
|
||||
// ev@d2d1847a-7b88-41dd-ad07-92785f06f5c4:v1
|
||||
|
||||
private:
|
||||
const Everest::PtrContainer<Auth>& mod;
|
||||
const Conf& config;
|
||||
|
||||
virtual void init() override;
|
||||
virtual void ready() override;
|
||||
|
||||
// ev@3370e4dd-95f4-47a9-aaec-ea76f34a66c9:v1
|
||||
// insert your private definitions here
|
||||
// ev@3370e4dd-95f4-47a9-aaec-ea76f34a66c9:v1
|
||||
};
|
||||
|
||||
// ev@3d7da0ad-02c2-493d-9920-0bbbd56b9876:v1
|
||||
// insert other definitions here
|
||||
// ev@3d7da0ad-02c2-493d-9920-0bbbd56b9876:v1
|
||||
|
||||
} // namespace main
|
||||
} // namespace module
|
||||
|
||||
#endif // MAIN_AUTH_IMPL_HPP
|
||||
99
tools/EVerest-main/modules/EVSE/Auth/manifest.yaml
Normal file
99
tools/EVerest-main/modules/EVSE/Auth/manifest.yaml
Normal file
@@ -0,0 +1,99 @@
|
||||
description: >-
|
||||
This module implements the authentication handling for the EVerest. It is responsible for providing authorization to
|
||||
the connected evse managers. In addition to that, it handles the reservation management.
|
||||
config:
|
||||
selection_algorithm:
|
||||
description: >-
|
||||
The selection algorithm contains the logic to select one connector for an incoming token. In case the charging station
|
||||
has only one connector, the selection of a connector is pretty straight-forward because there is only one that is
|
||||
available. The selection algorithm becomes relevant in case the Auth module manages authorization requests
|
||||
for multiple connectors. The following values can be set:
|
||||
PlugEvents: Selection of connector is based on EV Plug In events
|
||||
FindFirst: Simply selects the first available connector that has no active transaction
|
||||
UserInput: Placeholder, not yet implemented
|
||||
type: string
|
||||
default: FindFirst
|
||||
connection_timeout:
|
||||
description: >-
|
||||
Defines how many seconds an authorization is valid before it is discarded.
|
||||
Defines how many seconds a user can provide authorization after the plug in
|
||||
of a car
|
||||
type: integer
|
||||
master_pass_group_id:
|
||||
description: >-
|
||||
IdTokens that have this id as groupId belong to the Master Pass Group. Meaning they can stop any ongoing
|
||||
transaction, but cannot start transactions. This can, for example, be used by law enforcement personal to stop any
|
||||
ongoing transaction when an EV has to be towed away. If left empty, master_pass_group_id is not used.
|
||||
type: string
|
||||
default: ""
|
||||
prioritize_authorization_over_stopping_transaction:
|
||||
description: >-
|
||||
Boolean value to describe the handling of parent id tokens.
|
||||
|
||||
If true, a new token will be preferably used for authorization of a new connector if a connector is available. It
|
||||
will not be used to finish a transaction using its parent_id_token. parent_id_token will only be used to finish
|
||||
transaction if no connector is available for authorization anymore.
|
||||
|
||||
If false, a new token will be used to finish a transaction if the parent_id_token of a present transaction matches
|
||||
the parent_id_token of the provided_token. Authorization to available connectors will only be provided if no
|
||||
transaction can be stopped using the given parent_id_token
|
||||
type: boolean
|
||||
default: true
|
||||
ignore_connector_faults:
|
||||
description: >-
|
||||
Boolean value to describe the handling of faults on connectors.
|
||||
|
||||
If true, faults reported on connectors are ignored, i.e. they can still be authorized. This should be disabled in
|
||||
most use cases, but e.g. in free charging applications it may be useful to allow a charging session in the following case:
|
||||
A connector e.g. has an overtemperature fault that at some point will clear once it is cooled down. A car is plugged in before
|
||||
the error is cleared. The user would expect that the charging starts once it is cooled down. When this option is set to false,
|
||||
it will not be authorized on plug in as the connector is in fault state and it will never recover until the car is replugged.
|
||||
If it is set to true, the authorization happens on the faulty connector and charging will start once the fault is cleared.
|
||||
|
||||
If false, faulty connectors are treated as not available and will not be authorized. This is a good setting for e.g. public chargers.
|
||||
type: boolean
|
||||
default: false
|
||||
plug_in_timeout_enabled:
|
||||
description: >-
|
||||
This parameter is only used if the charging station has multiple EVSEs managed by this Auth module.
|
||||
|
||||
Controls the authorization-timeout behavior after a plug-in event. When enabled, an internal timer is started immediately after an EV
|
||||
is detected as plugged in. During the time defined by connection_timeout, the user must present a valid authorization token.
|
||||
|
||||
If no valid token is received within the specified connection_timeout, the authorization attempt is considered as timed out and a
|
||||
re-plug by the user is required to start a new authorization process.
|
||||
|
||||
This setting is particularly useful for charging stations with multiple EVSEs and a shared, non-EVSE-specific authorization interface
|
||||
(e.g., a single RFID reader). It prevents authorization tokens from being incorrectly associated with an EVSE where an EV is
|
||||
plugged in but has not been authorized, by ensuring that expired or ambiguous plug-ins are not considered for EVSE assignments
|
||||
for future authorization attempts.
|
||||
type: boolean
|
||||
default: false
|
||||
provides:
|
||||
main:
|
||||
description: This implements the auth interface for EVerest
|
||||
interface: auth
|
||||
reservation:
|
||||
description: This implements the reservation interface for EVerest.
|
||||
interface: reservation
|
||||
requires:
|
||||
token_provider:
|
||||
interface: auth_token_provider
|
||||
min_connections: 1
|
||||
max_connections: 128
|
||||
token_validator:
|
||||
interface: auth_token_validator
|
||||
min_connections: 1
|
||||
max_connections: 128
|
||||
evse_manager:
|
||||
interface: evse_manager
|
||||
min_connections: 1
|
||||
max_connections: 128
|
||||
kvs:
|
||||
interface: kvs
|
||||
min_connections: 0
|
||||
max_connections: 1
|
||||
metadata:
|
||||
license: https://opensource.org/licenses/Apache-2.0
|
||||
authors:
|
||||
- 'Piet Gömpel'
|
||||
@@ -0,0 +1,49 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include "reservationImpl.hpp"
|
||||
|
||||
namespace module {
|
||||
namespace reservation {
|
||||
|
||||
void reservationImpl::init() {
|
||||
}
|
||||
|
||||
void reservationImpl::ready() {
|
||||
}
|
||||
|
||||
types::reservation::ReservationResult reservationImpl::handle_reserve_now(types::reservation::Reservation& request) {
|
||||
// your code for cmd reserve_now goes here
|
||||
|
||||
EVLOG_debug << "Handle reservation for evse id " << (request.evse_id.has_value() ? request.evse_id.value() : -1);
|
||||
|
||||
const auto reservation_result = this->mod->auth_handler->handle_reservation(request);
|
||||
if (reservation_result == ReservationResult::Accepted) {
|
||||
if (!this->mod->auth_handler->call_reserved(request.reservation_id, request.evse_id)) {
|
||||
return ReservationResult::Rejected;
|
||||
}
|
||||
}
|
||||
return reservation_result;
|
||||
};
|
||||
|
||||
bool reservationImpl::handle_cancel_reservation(int& reservation_id) {
|
||||
const auto reservation_cancelled = this->mod->auth_handler->handle_cancel_reservation(reservation_id);
|
||||
if (reservation_cancelled.first) {
|
||||
// Call reservation cancelled. This comes from outside, so we don't send the status update (otherwise this is
|
||||
// sent to OCPP and that is not according to specification).
|
||||
this->mod->auth_handler->call_reservation_cancelled(reservation_id, ReservationEndReason::Cancelled,
|
||||
reservation_cancelled.second, false);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
types::reservation::ReservationCheckStatus
|
||||
reservationImpl::handle_exists_reservation(types::reservation::ReservationCheck& request) {
|
||||
return this->mod->auth_handler->handle_reservation_exists(request.id_token, request.evse_id,
|
||||
request.group_id_token);
|
||||
};
|
||||
|
||||
} // namespace reservation
|
||||
} // namespace module
|
||||
@@ -0,0 +1,64 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
#ifndef RESERVATION_RESERVATION_IMPL_HPP
|
||||
#define RESERVATION_RESERVATION_IMPL_HPP
|
||||
|
||||
//
|
||||
// AUTO GENERATED - MARKED REGIONS WILL BE KEPT
|
||||
// template version 3
|
||||
//
|
||||
|
||||
#include <generated/interfaces/reservation/Implementation.hpp>
|
||||
|
||||
#include "../Auth.hpp"
|
||||
|
||||
// ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1
|
||||
// insert your custom include headers here
|
||||
// ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1
|
||||
|
||||
namespace module {
|
||||
namespace reservation {
|
||||
|
||||
struct Conf {};
|
||||
|
||||
class reservationImpl : public reservationImplBase {
|
||||
public:
|
||||
reservationImpl() = delete;
|
||||
reservationImpl(Everest::ModuleAdapter* ev, const Everest::PtrContainer<Auth>& mod, Conf& config) :
|
||||
reservationImplBase(ev, "reservation"), mod(mod), config(config){};
|
||||
|
||||
// ev@8ea32d28-373f-4c90-ae5e-b4fcc74e2a61:v1
|
||||
// insert your public definitions here
|
||||
// ev@8ea32d28-373f-4c90-ae5e-b4fcc74e2a61:v1
|
||||
|
||||
protected:
|
||||
// command handler functions (virtual)
|
||||
virtual types::reservation::ReservationResult handle_reserve_now(types::reservation::Reservation& request) override;
|
||||
virtual bool handle_cancel_reservation(int& reservation_id) override;
|
||||
virtual types::reservation::ReservationCheckStatus
|
||||
handle_exists_reservation(types::reservation::ReservationCheck& request) override;
|
||||
|
||||
// ev@d2d1847a-7b88-41dd-ad07-92785f06f5c4:v1
|
||||
// insert your protected definitions here
|
||||
// ev@d2d1847a-7b88-41dd-ad07-92785f06f5c4:v1
|
||||
|
||||
private:
|
||||
const Everest::PtrContainer<Auth>& mod;
|
||||
const Conf& config;
|
||||
|
||||
virtual void init() override;
|
||||
virtual void ready() override;
|
||||
|
||||
// ev@3370e4dd-95f4-47a9-aaec-ea76f34a66c9:v1
|
||||
// insert your private definitions here
|
||||
// ev@3370e4dd-95f4-47a9-aaec-ea76f34a66c9:v1
|
||||
};
|
||||
|
||||
// ev@3d7da0ad-02c2-493d-9920-0bbbd56b9876:v1
|
||||
// insert other definitions here
|
||||
// ev@3d7da0ad-02c2-493d-9920-0bbbd56b9876:v1
|
||||
|
||||
} // namespace reservation
|
||||
} // namespace module
|
||||
|
||||
#endif // RESERVATION_RESERVATION_IMPL_HPP
|
||||
43
tools/EVerest-main/modules/EVSE/Auth/tests/CMakeLists.txt
Normal file
43
tools/EVerest-main/modules/EVSE/Auth/tests/CMakeLists.txt
Normal file
@@ -0,0 +1,43 @@
|
||||
set(TEST_TARGET_NAME ${PROJECT_NAME}_auth_tests)
|
||||
|
||||
set(MODULE_DIR
|
||||
"${PROJECT_SOURCE_DIR}/modules/EVSE/Auth")
|
||||
|
||||
set(TEST_SOURCES ${MODULE_DIR}/lib/ReservationHandler.cpp
|
||||
${MODULE_DIR}/lib/AuthHandler.cpp
|
||||
${MODULE_DIR}/lib/Connector.cpp
|
||||
${MODULE_DIR}/lib/ConnectorStateMachine.cpp)
|
||||
|
||||
add_executable(${TEST_TARGET_NAME} auth_tests.cpp reservation_tests.cpp ${TEST_SOURCES})
|
||||
|
||||
message("Current source dir: ${CMAKE_CURRENT_SOURCE_DIR}")
|
||||
|
||||
set(INCLUDE_DIR
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/stubs"
|
||||
"${MODULE_DIR}/include"
|
||||
"${MODULE_DIR}/tests"
|
||||
)
|
||||
|
||||
get_target_property(GENERATED_INCLUDE_DIR generate_cpp_files EVEREST_GENERATED_INCLUDE_DIR)
|
||||
|
||||
target_include_directories(${TEST_TARGET_NAME} PUBLIC
|
||||
${INCLUDE_DIR}
|
||||
${GENERATED_INCLUDE_DIR}
|
||||
)
|
||||
|
||||
target_link_libraries(${TEST_TARGET_NAME} PRIVATE
|
||||
GTest::gmock
|
||||
GTest::gtest_main
|
||||
everest::timer
|
||||
${CMAKE_DL_LIBS}
|
||||
everest::log
|
||||
everest::framework
|
||||
everest::helpers
|
||||
pthread
|
||||
nlohmann_json::nlohmann_json
|
||||
date::date
|
||||
date::date-tz
|
||||
)
|
||||
|
||||
add_test(${TEST_TARGET_NAME} ${TEST_TARGET_NAME})
|
||||
ev_register_test_target(${TEST_TARGET_NAME})
|
||||
@@ -0,0 +1,40 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include <map>
|
||||
|
||||
/**
|
||||
* @brief This implementation is only used for testing purposes. It is used to check which EVSE have received
|
||||
* authorization.
|
||||
*
|
||||
*/
|
||||
class FakeAuthReceiver {
|
||||
|
||||
private:
|
||||
std::map<int32_t, bool> evse_index_to_authorization_map;
|
||||
|
||||
public:
|
||||
FakeAuthReceiver(){};
|
||||
explicit FakeAuthReceiver(const std::vector<int32_t>& evse_indices) {
|
||||
for (const auto evse_index : evse_indices) {
|
||||
evse_index_to_authorization_map[evse_index] = false;
|
||||
}
|
||||
};
|
||||
void add_evse_index(const int32_t evse_index) {
|
||||
evse_index_to_authorization_map[evse_index] = false;
|
||||
};
|
||||
void authorize(const int32_t evse_index) {
|
||||
evse_index_to_authorization_map[evse_index] = true;
|
||||
};
|
||||
void deauthorize(const int32_t evse_index) {
|
||||
evse_index_to_authorization_map[evse_index] = false;
|
||||
};
|
||||
bool get_authorization(const int32_t evse_index) {
|
||||
return evse_index_to_authorization_map[evse_index];
|
||||
};
|
||||
void reset() {
|
||||
for (auto& e : evse_index_to_authorization_map) {
|
||||
e.second = false;
|
||||
}
|
||||
};
|
||||
};
|
||||
2251
tools/EVerest-main/modules/EVSE/Auth/tests/auth_tests.cpp
Normal file
2251
tools/EVerest-main/modules/EVSE/Auth/tests/auth_tests.cpp
Normal file
File diff suppressed because it is too large
Load Diff
1455
tools/EVerest-main/modules/EVSE/Auth/tests/reservation_tests.cpp
Normal file
1455
tools/EVerest-main/modules/EVSE/Auth/tests/reservation_tests.cpp
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,35 @@
|
||||
#ifndef KVS_INTERFACE_HPP
|
||||
#define KVS_INTERFACE_HPP
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <variant>
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
// #include <utils/types.hpp>
|
||||
|
||||
using nlohmann::json;
|
||||
|
||||
using Array = nlohmann::json::array_t;
|
||||
using Object = nlohmann::json::object_t;
|
||||
|
||||
class kvsIntf {
|
||||
private:
|
||||
std::variant<std::nullptr_t, Array, Object, bool, double, int, std::string> value;
|
||||
|
||||
public:
|
||||
kvsIntf() {
|
||||
}
|
||||
void call_store(std::string key,
|
||||
std::variant<std::nullptr_t, Array, Object, bool, double, int, std::string> value) {
|
||||
std::cout << "Store called!" << std::endl;
|
||||
this->value = value;
|
||||
}
|
||||
std::variant<std::nullptr_t, Array, Object, bool, double, int, std::string> call_load(std::string key) {
|
||||
std::cout << "Load called!" << std::endl;
|
||||
|
||||
return this->value;
|
||||
}
|
||||
};
|
||||
|
||||
#endif // KVS_INTERFACE_HPP
|
||||
0
tools/EVerest-main/modules/EVSE/BUILD.bazel
Normal file
0
tools/EVerest-main/modules/EVSE/BUILD.bazel
Normal file
11
tools/EVerest-main/modules/EVSE/CMakeLists.txt
Normal file
11
tools/EVerest-main/modules/EVSE/CMakeLists.txt
Normal file
@@ -0,0 +1,11 @@
|
||||
ev_add_module(Auth)
|
||||
ev_add_module(Evse15118D20)
|
||||
ev_add_module(EvseManager)
|
||||
ev_add_module(EvseSecurity)
|
||||
ev_add_module(EvseSlac)
|
||||
ev_add_module(EvseV2G)
|
||||
ev_add_module(IsoMux)
|
||||
ev_add_module(Iso15118InternetVas)
|
||||
ev_add_module(OCPP)
|
||||
ev_add_module(OCPP201)
|
||||
ev_add_module(StaticISO15118VASProvider)
|
||||
31
tools/EVerest-main/modules/EVSE/Evse15118D20/CMakeLists.txt
Normal file
31
tools/EVerest-main/modules/EVSE/Evse15118D20/CMakeLists.txt
Normal file
@@ -0,0 +1,31 @@
|
||||
#
|
||||
# AUTO GENERATED - MARKED REGIONS WILL BE KEPT
|
||||
# template version 3
|
||||
#
|
||||
|
||||
# module setup:
|
||||
# - ${MODULE_NAME}: module name
|
||||
ev_setup_cpp_module()
|
||||
|
||||
# ev@bcc62523-e22b-41d7-ba2f-825b493a3c97:v1
|
||||
target_sources(${MODULE_NAME}
|
||||
PRIVATE
|
||||
charger/session_logger.cpp
|
||||
charger/utils.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(${MODULE_NAME}
|
||||
PRIVATE
|
||||
iso15118::iso15118
|
||||
)
|
||||
# ev@bcc62523-e22b-41d7-ba2f-825b493a3c97:v1
|
||||
|
||||
target_sources(${MODULE_NAME}
|
||||
PRIVATE
|
||||
"charger/ISO15118_chargerImpl.cpp"
|
||||
"extensions/iso15118_extensionsImpl.cpp"
|
||||
)
|
||||
|
||||
# ev@c55432ab-152c-45a9-9d2e-7281d50c69c3:v1
|
||||
# insert other things like install cmds etc here
|
||||
# ev@c55432ab-152c-45a9-9d2e-7281d50c69c3:v1
|
||||
@@ -0,0 +1,17 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
#include "Evse15118D20.hpp"
|
||||
|
||||
namespace module {
|
||||
|
||||
void Evse15118D20::init() {
|
||||
invoke_init(*p_charger);
|
||||
invoke_init(*p_extensions);
|
||||
}
|
||||
|
||||
void Evse15118D20::ready() {
|
||||
invoke_ready(*p_charger);
|
||||
invoke_ready(*p_extensions);
|
||||
}
|
||||
|
||||
} // namespace module
|
||||
@@ -0,0 +1,88 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
#ifndef EVSE15118D20_HPP
|
||||
#define EVSE15118D20_HPP
|
||||
|
||||
//
|
||||
// AUTO GENERATED - MARKED REGIONS WILL BE KEPT
|
||||
// template version 2
|
||||
//
|
||||
|
||||
#include "ld-ev.hpp"
|
||||
|
||||
// headers for provided interface implementations
|
||||
#include <generated/interfaces/ISO15118_charger/Implementation.hpp>
|
||||
#include <generated/interfaces/iso15118_extensions/Implementation.hpp>
|
||||
|
||||
// headers for required interface implementations
|
||||
#include <generated/interfaces/ISO15118_vas/Interface.hpp>
|
||||
#include <generated/interfaces/evse_security/Interface.hpp>
|
||||
|
||||
// ev@4bf81b14-a215-475c-a1d3-0a484ae48918:v1
|
||||
// insert your custom include headers here
|
||||
// ev@4bf81b14-a215-475c-a1d3-0a484ae48918:v1
|
||||
|
||||
namespace module {
|
||||
|
||||
struct Conf {
|
||||
std::string device;
|
||||
std::string logging_path;
|
||||
std::string tls_negotiation_strategy;
|
||||
bool enforce_tls_1_3;
|
||||
bool enable_ssl_logging;
|
||||
bool enable_tls_key_logging;
|
||||
std::string tls_key_logging_path;
|
||||
bool enable_sdp_server;
|
||||
bool supported_dynamic_mode;
|
||||
bool supported_mobility_needs_mode_provided_by_secc;
|
||||
bool supported_scheduled_mode;
|
||||
std::string custom_protocol_namespace;
|
||||
bool negative_bidirectional_limits;
|
||||
};
|
||||
|
||||
class Evse15118D20 : public Everest::ModuleBase {
|
||||
public:
|
||||
Evse15118D20() = delete;
|
||||
Evse15118D20(const ModuleInfo& info, std::unique_ptr<ISO15118_chargerImplBase> p_charger,
|
||||
std::unique_ptr<iso15118_extensionsImplBase> p_extensions,
|
||||
std::unique_ptr<evse_securityIntf> r_security,
|
||||
std::vector<std::unique_ptr<ISO15118_vasIntf>> r_iso15118_vas, Conf& config) :
|
||||
ModuleBase(info),
|
||||
p_charger(std::move(p_charger)),
|
||||
p_extensions(std::move(p_extensions)),
|
||||
r_security(std::move(r_security)),
|
||||
r_iso15118_vas(std::move(r_iso15118_vas)),
|
||||
config(config){};
|
||||
|
||||
const std::unique_ptr<ISO15118_chargerImplBase> p_charger;
|
||||
const std::unique_ptr<iso15118_extensionsImplBase> p_extensions;
|
||||
const std::unique_ptr<evse_securityIntf> r_security;
|
||||
const std::vector<std::unique_ptr<ISO15118_vasIntf>> r_iso15118_vas;
|
||||
const Conf& config;
|
||||
|
||||
// ev@1fce4c5e-0ab8-41bb-90f7-14277703d2ac:v1
|
||||
// insert your public definitions here
|
||||
// ev@1fce4c5e-0ab8-41bb-90f7-14277703d2ac:v1
|
||||
|
||||
protected:
|
||||
// ev@4714b2ab-a24f-4b95-ab81-36439e1478de:v1
|
||||
// insert your protected definitions here
|
||||
// ev@4714b2ab-a24f-4b95-ab81-36439e1478de:v1
|
||||
|
||||
private:
|
||||
friend class LdEverest;
|
||||
void init();
|
||||
void ready();
|
||||
|
||||
// ev@211cfdbe-f69a-4cd6-a4ec-f8aaa3d1b6c8:v1
|
||||
// insert your private definitions here
|
||||
// ev@211cfdbe-f69a-4cd6-a4ec-f8aaa3d1b6c8:v1
|
||||
};
|
||||
|
||||
// ev@087e516b-124c-48df-94fb-109508c7cda9:v1
|
||||
// insert other definitions here
|
||||
// ev@087e516b-124c-48df-94fb-109508c7cda9:v1
|
||||
|
||||
} // namespace module
|
||||
|
||||
#endif // EVSE15118D20_HPP
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,113 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
#ifndef CHARGER_ISO15118_CHARGER_IMPL_HPP
|
||||
#define CHARGER_ISO15118_CHARGER_IMPL_HPP
|
||||
|
||||
//
|
||||
// AUTO GENERATED - MARKED REGIONS WILL BE KEPT
|
||||
// template version 3
|
||||
//
|
||||
|
||||
#include <generated/interfaces/ISO15118_charger/Implementation.hpp>
|
||||
|
||||
#include "../Evse15118D20.hpp"
|
||||
|
||||
// ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1
|
||||
#include <bitset>
|
||||
#include <mutex>
|
||||
|
||||
#include "utils.hpp"
|
||||
|
||||
#include <iso15118/d20/config.hpp>
|
||||
#include <iso15118/session/feedback.hpp>
|
||||
#include <iso15118/tbd_controller.hpp>
|
||||
// ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1
|
||||
|
||||
namespace module {
|
||||
namespace charger {
|
||||
|
||||
struct Conf {};
|
||||
|
||||
class ISO15118_chargerImpl : public ISO15118_chargerImplBase {
|
||||
public:
|
||||
ISO15118_chargerImpl() = delete;
|
||||
ISO15118_chargerImpl(Everest::ModuleAdapter* ev, const Everest::PtrContainer<Evse15118D20>& mod, Conf& config) :
|
||||
ISO15118_chargerImplBase(ev, "charger"), mod(mod), config(config){};
|
||||
|
||||
// ev@8ea32d28-373f-4c90-ae5e-b4fcc74e2a61:v1
|
||||
// insert your public definitions here
|
||||
// ev@8ea32d28-373f-4c90-ae5e-b4fcc74e2a61:v1
|
||||
|
||||
protected:
|
||||
// command handler functions (virtual)
|
||||
virtual void handle_setup(types::iso15118::EVSEID& evse_id, types::iso15118::SaeJ2847BidiMode& sae_j2847_mode,
|
||||
bool& debug_mode) override;
|
||||
virtual void handle_set_charging_parameters(types::iso15118::SetupPhysicalValues& physical_values) override;
|
||||
virtual void handle_session_setup(std::vector<types::iso15118::PaymentOption>& payment_options,
|
||||
bool& supported_certificate_service,
|
||||
bool& central_contract_validation_allowed) override;
|
||||
virtual void handle_bpt_setup(types::iso15118::BptSetup& bpt_config) override;
|
||||
virtual void handle_set_powersupply_capabilities(types::power_supply_DC::Capabilities& capabilities) override;
|
||||
virtual void handle_authorization_response(types::authorization::AuthorizationStatus& authorization_status,
|
||||
types::authorization::CertificateStatus& certificate_status) override;
|
||||
virtual void handle_ac_contactor_closed(bool& status) override;
|
||||
virtual void handle_dlink_ready(bool& value) override;
|
||||
virtual void handle_cable_check_finished(bool& status) override;
|
||||
virtual void handle_receipt_is_required(bool& receipt_required) override;
|
||||
virtual void handle_stop_charging(bool& stop) override;
|
||||
virtual void handle_pause_charging(bool& pause) override;
|
||||
virtual void handle_no_energy_pause_charging(types::iso15118::NoEnergyPauseMode& mode) override;
|
||||
virtual bool
|
||||
handle_update_supported_app_protocols(types::iso15118::SupportedAppProtocols& supported_app_protocols) override;
|
||||
virtual void handle_update_energy_transfer_modes(
|
||||
std::vector<types::iso15118::EnergyTransferMode>& supported_energy_transfer_modes) override;
|
||||
virtual void handle_update_ac_max_current(double& max_current) override;
|
||||
virtual void handle_update_ac_parameters(types::iso15118::AcParameters& ac_parameters) override;
|
||||
virtual void handle_update_ac_maximum_limits(types::iso15118::AcEvseMaximumPower& maximum_limits) override;
|
||||
virtual void handle_update_ac_minimum_limits(types::iso15118::AcEvseMinimumPower& minimum_limits) override;
|
||||
virtual void handle_update_ac_target_values(types::iso15118::AcTargetValues& target_values) override;
|
||||
virtual void handle_update_ac_present_power(types::units::Power& present_power) override;
|
||||
virtual void handle_update_dc_maximum_limits(types::iso15118::DcEvseMaximumLimits& maximum_limits) override;
|
||||
virtual void handle_update_dc_minimum_limits(types::iso15118::DcEvseMinimumLimits& minimum_limits) override;
|
||||
virtual void handle_update_isolation_status(types::iso15118::IsolationStatus& isolation_status) override;
|
||||
virtual void
|
||||
handle_update_dc_present_values(types::iso15118::DcEvsePresentVoltageCurrent& present_voltage_current) override;
|
||||
virtual void handle_update_meter_info(types::powermeter::Powermeter& powermeter) override;
|
||||
virtual void handle_send_error(types::iso15118::EvseError& error) override;
|
||||
virtual void handle_reset_error() override;
|
||||
|
||||
// ev@d2d1847a-7b88-41dd-ad07-92785f06f5c4:v1
|
||||
// insert your protected definitions here
|
||||
// ev@d2d1847a-7b88-41dd-ad07-92785f06f5c4:v1
|
||||
|
||||
private:
|
||||
const Everest::PtrContainer<Evse15118D20>& mod;
|
||||
const Conf& config;
|
||||
|
||||
virtual void init() override;
|
||||
virtual void ready() override;
|
||||
|
||||
// ev@3370e4dd-95f4-47a9-aaec-ea76f34a66c9:v1
|
||||
iso15118::session::feedback::Callbacks create_callbacks();
|
||||
|
||||
std::unique_ptr<iso15118::TbdController> controller;
|
||||
|
||||
iso15118::d20::EvseSetupConfig setup_config;
|
||||
std::bitset<NUMBER_OF_SETUP_STEPS> setup_steps_done{0};
|
||||
|
||||
std::vector<iso15118::d20::SupportedVASs> supported_vas_services_per_provider;
|
||||
std::mutex vas_mutex;
|
||||
|
||||
void update_supported_vas_services();
|
||||
std::optional<size_t> get_vas_provider_index(uint16_t service_id);
|
||||
// ev@3370e4dd-95f4-47a9-aaec-ea76f34a66c9:v1
|
||||
};
|
||||
|
||||
// ev@3d7da0ad-02c2-493d-9920-0bbbd56b9876:v1
|
||||
// insert other definitions here
|
||||
// ev@3d7da0ad-02c2-493d-9920-0bbbd56b9876:v1
|
||||
|
||||
} // namespace charger
|
||||
} // namespace module
|
||||
|
||||
#endif // CHARGER_ISO15118_CHARGER_IMPL_HPP
|
||||
@@ -0,0 +1,141 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
#include "session_logger.hpp"
|
||||
|
||||
#include <chrono>
|
||||
#include <ctime>
|
||||
#include <fstream>
|
||||
#include <iomanip>
|
||||
#include <stdexcept>
|
||||
|
||||
#include <date/date.h>
|
||||
|
||||
#include <iso15118/session/logger.hpp>
|
||||
|
||||
#include <everest/logging.hpp>
|
||||
|
||||
using LogEvent = iso15118::session::logging::Event;
|
||||
|
||||
std::string get_filename_for_current_time() {
|
||||
const auto now = std::chrono::system_clock::now();
|
||||
const auto now_t = std::chrono::system_clock::to_time_t(now);
|
||||
|
||||
std::tm now_tm;
|
||||
gmtime_r(&now_t, &now_tm);
|
||||
|
||||
char buffer[64];
|
||||
strftime(buffer, sizeof(buffer), "%y%m%d_%H-%M-%S.yaml", &now_tm);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
// static auto timepoint_to_string(const iso15118::session::logging::TimePoint& timepoint) {
|
||||
// using namespace date;
|
||||
// return static_cast<std::string>(timepoint);
|
||||
// }
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const iso15118::session::logging::ExiMessageDirection& direction) {
|
||||
using Direction = iso15118::session::logging::ExiMessageDirection;
|
||||
switch (direction) {
|
||||
case Direction::FROM_EV:
|
||||
return os << "FROM_EV";
|
||||
case Direction::TO_EV:
|
||||
return os << "TO_EV";
|
||||
}
|
||||
|
||||
return os;
|
||||
}
|
||||
|
||||
class SessionLog {
|
||||
public:
|
||||
SessionLog(const std::string& file_name) : file(file_name.c_str(), std::ios::out) {
|
||||
if (not file.good()) {
|
||||
throw std::runtime_error("Failed to open file " + file_name + " for writing iso15118 session log");
|
||||
}
|
||||
|
||||
EVLOG_info << "Created logfile at: " << file_name;
|
||||
}
|
||||
void operator()(const iso15118::session::logging::SimpleEvent& event) {
|
||||
file << "- type: INFO\n";
|
||||
add_timestamp(event.time_point);
|
||||
file << " info: \"" << event.info << "\"\n";
|
||||
}
|
||||
|
||||
void operator()(const iso15118::session::logging::ExiMessageEvent& event) {
|
||||
file << "- type: EXI\n";
|
||||
add_timestamp(event.time_point);
|
||||
file << " direction: " << event.direction << "\n";
|
||||
file << " sdp_payload_type: " << event.payload_type << "\n";
|
||||
add_hex_encoded_data(event.data, event.len);
|
||||
}
|
||||
|
||||
void flush() {
|
||||
file.flush();
|
||||
}
|
||||
|
||||
private:
|
||||
std::fstream file;
|
||||
|
||||
void add_timestamp(const iso15118::session::logging::TimePoint& timestamp) {
|
||||
if (not timestamp_initialized) {
|
||||
last_timestamp = timestamp;
|
||||
timestamp_initialized = true;
|
||||
}
|
||||
|
||||
const auto offset_ms = std::chrono::duration_cast<std::chrono::milliseconds>(timestamp - last_timestamp);
|
||||
file << " timestamp_offset: " << offset_ms.count() << "\n";
|
||||
|
||||
const auto dp = date::floor<date::days>(timestamp);
|
||||
const auto time = date::make_time(timestamp - dp);
|
||||
const auto milliseconds = std::chrono::duration_cast<std::chrono::milliseconds>(time.subseconds());
|
||||
file << " timestamp: \"";
|
||||
file << std::setfill('0') << std::setw(2) << time.hours().count() << ":";
|
||||
file << std::setfill('0') << std::setw(2) << time.minutes().count() << ":";
|
||||
file << std::setfill('0') << std::setw(2) << time.seconds().count() << ".";
|
||||
file << std::setfill('0') << std::setw(4) << milliseconds.count();
|
||||
file << "\"\n";
|
||||
|
||||
last_timestamp = timestamp;
|
||||
}
|
||||
|
||||
void add_hex_encoded_data(const uint8_t* data, size_t len) {
|
||||
file << " data: \"";
|
||||
|
||||
const auto flags = file.flags();
|
||||
|
||||
file << std::hex;
|
||||
|
||||
for (int i = 0; i < len; ++i) {
|
||||
file << std::setfill('0') << std::setw(2) << static_cast<int>(data[i]);
|
||||
}
|
||||
|
||||
file.flags(flags);
|
||||
file << "\"\n";
|
||||
}
|
||||
|
||||
iso15118::session::logging::TimePoint last_timestamp;
|
||||
bool timestamp_initialized{false};
|
||||
};
|
||||
|
||||
SessionLogger::SessionLogger(std::filesystem::path output_dir_) : output_dir(std::filesystem::absolute(output_dir_)) {
|
||||
// FIXME (aw): this is quite brute force ...
|
||||
if (not std::filesystem::exists(output_dir)) {
|
||||
std::filesystem::create_directory(output_dir);
|
||||
}
|
||||
|
||||
iso15118::session::logging::set_session_log_callback([this](std::uintptr_t id, const LogEvent& event) {
|
||||
auto log_it = logs.find(id);
|
||||
if (log_it == logs.end()) {
|
||||
const auto log_file_name = output_dir / get_filename_for_current_time();
|
||||
const auto emplaced = logs.emplace(id, std::make_unique<SessionLog>(log_file_name.string()));
|
||||
|
||||
log_it = emplaced.first;
|
||||
}
|
||||
|
||||
auto& log = *log_it->second;
|
||||
|
||||
std::visit(log, event);
|
||||
log.flush();
|
||||
});
|
||||
}
|
||||
|
||||
SessionLogger::~SessionLogger() = default;
|
||||
@@ -0,0 +1,21 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <filesystem>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
|
||||
// forward declare
|
||||
class SessionLog;
|
||||
|
||||
class SessionLogger {
|
||||
public:
|
||||
SessionLogger(std::filesystem::path output_dir);
|
||||
~SessionLogger();
|
||||
|
||||
private:
|
||||
std::filesystem::path output_dir;
|
||||
std::map<std::uintptr_t, std::unique_ptr<SessionLog>> logs;
|
||||
};
|
||||
508
tools/EVerest-main/modules/EVSE/Evse15118D20/charger/utils.cpp
Normal file
508
tools/EVerest-main/modules/EVSE/Evse15118D20/charger/utils.cpp
Normal file
@@ -0,0 +1,508 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
#include "utils.hpp"
|
||||
|
||||
#include <everest/logging.hpp>
|
||||
|
||||
#include <iso15118/d20/limits.hpp>
|
||||
#include <iso15118/message/common_types.hpp>
|
||||
#include <iso15118/message/dc_charge_parameter_discovery.hpp>
|
||||
#include <iso15118/message/schedule_exchange.hpp>
|
||||
#include <iso15118/message/service_detail.hpp>
|
||||
|
||||
namespace module::charger {
|
||||
|
||||
namespace dt = iso15118::message_20::datatypes;
|
||||
|
||||
namespace {
|
||||
dt::Parameter convert_parameter(const types::iso15118_vas::Parameter& parameter) {
|
||||
dt::Parameter out;
|
||||
out.name = parameter.name;
|
||||
|
||||
if (parameter.value.bool_value.has_value()) {
|
||||
out.value = parameter.value.bool_value.value();
|
||||
} else if (parameter.value.int_value.has_value()) {
|
||||
out.value = static_cast<int32_t>(parameter.value.int_value.value());
|
||||
} else if (parameter.value.short_value.has_value()) {
|
||||
out.value = static_cast<int16_t>(parameter.value.short_value.value());
|
||||
} else if (parameter.value.byte_value.has_value()) {
|
||||
out.value = static_cast<int8_t>(parameter.value.byte_value.value());
|
||||
} else if (parameter.value.rational_number.has_value()) {
|
||||
out.value = dt::from_float(parameter.value.rational_number.value());
|
||||
} else if (parameter.value.finite_string.has_value()) {
|
||||
out.value = parameter.value.finite_string.value();
|
||||
} else {
|
||||
throw std::invalid_argument("Invalid ParameterValue in convert_parameter: " + parameter.name +
|
||||
" has no value set");
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
dt::ParameterSet convert_parameter_set(const types::iso15118_vas::ParameterSet& parameter_set) {
|
||||
dt::ParameterSet out;
|
||||
out.id = parameter_set.set_id;
|
||||
|
||||
for (const auto& parameter : parameter_set.parameters) {
|
||||
const auto* ptr = out.parameter.try_emplace_back(convert_parameter(parameter));
|
||||
|
||||
if (ptr == nullptr) {
|
||||
EVLOG_warning << "VAS parameter set is bigger then 32";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
std::optional<float> convert_from_optional(const std::optional<dt::RationalNumber>& in) {
|
||||
return (in.has_value()) ? std::make_optional(dt::from_RationalNumber(*in)) : std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<dt::RationalNumber> convert_from_optional(const std::optional<float>& in) {
|
||||
return (in.has_value()) ? std::make_optional(dt::from_float(*in)) : std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<float> convert_from_optional(const std::optional<uint32_t>& in) {
|
||||
return (in.has_value()) ? std::make_optional(static_cast<float>(*in)) : std::nullopt;
|
||||
}
|
||||
|
||||
types::iso15118::AppProtocol convert_app_protocol(const iso15118::message_20::SupportedAppProtocol& app_protocol) {
|
||||
types::iso15118::AppProtocol result;
|
||||
result.protocol_namespace = app_protocol.protocol_namespace;
|
||||
result.priority = app_protocol.priority;
|
||||
result.schema_id = app_protocol.schema_id;
|
||||
result.version_number_major = static_cast<int32_t>(app_protocol.version_number_major);
|
||||
result.version_number_minor = static_cast<int32_t>(app_protocol.version_number_minor);
|
||||
return result;
|
||||
}
|
||||
|
||||
types::iso15118::EvInformation convert_ev_info(const iso15118::d20::EVInformation& ev_info) {
|
||||
types::iso15118::EvInformation result;
|
||||
result.evcc_id = ev_info.evcc_id;
|
||||
result.selected_protocol = convert_app_protocol(ev_info.selected_app_protocol);
|
||||
result.supported_protocols.Protocols.reserve(ev_info.ev_supported_app_protocols.size());
|
||||
for (const auto& supported_app : ev_info.ev_supported_app_protocols) {
|
||||
result.supported_protocols.Protocols.push_back(convert_app_protocol(supported_app));
|
||||
}
|
||||
result.tls_leaf_certificate = ev_info.ev_tls_leaf_cert;
|
||||
result.tls_sub_ca_1_certificate = ev_info.ev_tls_sub_ca_1_cert;
|
||||
result.tls_sub_ca_2_certificate = ev_info.ev_tls_sub_ca_2_cert;
|
||||
result.tls_root_certificate = ev_info.ev_tls_root_cert;
|
||||
return result;
|
||||
}
|
||||
|
||||
types::iso15118::DcChargeDynamicModeValues convert_dynamic_values(const dt::Dynamic_DC_CLReqControlMode& in) {
|
||||
return {dt::from_RationalNumber(in.target_energy_request),
|
||||
dt::from_RationalNumber(in.max_energy_request),
|
||||
dt::from_RationalNumber(in.min_energy_request),
|
||||
dt::from_RationalNumber(in.max_charge_power),
|
||||
dt::from_RationalNumber(in.min_charge_power),
|
||||
dt::from_RationalNumber(in.max_charge_current),
|
||||
dt::from_RationalNumber(in.max_voltage),
|
||||
dt::from_RationalNumber(in.min_voltage),
|
||||
convert_from_optional(in.departure_time),
|
||||
std::nullopt,
|
||||
std::nullopt,
|
||||
std::nullopt,
|
||||
std::nullopt,
|
||||
std::nullopt};
|
||||
}
|
||||
|
||||
types::iso15118::DcChargeDynamicModeValues convert_dynamic_values(const dt::BPT_Dynamic_DC_CLReqControlMode& in) {
|
||||
return {dt::from_RationalNumber(in.target_energy_request), dt::from_RationalNumber(in.max_energy_request),
|
||||
dt::from_RationalNumber(in.min_energy_request), dt::from_RationalNumber(in.max_charge_power),
|
||||
dt::from_RationalNumber(in.min_charge_power), dt::from_RationalNumber(in.max_charge_current),
|
||||
dt::from_RationalNumber(in.max_voltage), dt::from_RationalNumber(in.min_voltage),
|
||||
convert_from_optional(in.departure_time), dt::from_RationalNumber(in.max_discharge_power),
|
||||
dt::from_RationalNumber(in.min_discharge_power), dt::from_RationalNumber(in.max_discharge_current),
|
||||
convert_from_optional(in.max_v2x_energy_request), convert_from_optional(in.min_v2x_energy_request)};
|
||||
}
|
||||
|
||||
template <>
|
||||
void fill_v2x_charging_parameters(types::iso15118::V2XChargingParameters& out_params,
|
||||
const iso15118::d20::DcTransferLimits& evse_limits,
|
||||
const dt::DC_CPDReqEnergyTransferMode& ev_limits) {
|
||||
// As per the OCPP 2.1 spec (2.109) we should use the MIN/MAX function between EV and EVSE
|
||||
out_params.min_charge_power = std::max(dt::from_RationalNumber(evse_limits.charge_limits.power.min),
|
||||
dt::from_RationalNumber(ev_limits.min_charge_power));
|
||||
out_params.max_charge_power = std::min(dt::from_RationalNumber(evse_limits.charge_limits.power.max),
|
||||
dt::from_RationalNumber(ev_limits.max_charge_power));
|
||||
|
||||
out_params.min_charge_current = std::max(dt::from_RationalNumber(evse_limits.charge_limits.current.min),
|
||||
dt::from_RationalNumber(ev_limits.min_charge_current));
|
||||
out_params.max_charge_current = std::min(dt::from_RationalNumber(evse_limits.charge_limits.current.max),
|
||||
dt::from_RationalNumber(ev_limits.max_charge_current));
|
||||
|
||||
out_params.min_voltage =
|
||||
std::max(dt::from_RationalNumber(evse_limits.voltage.min), dt::from_RationalNumber(ev_limits.min_voltage));
|
||||
out_params.max_voltage =
|
||||
std::min(dt::from_RationalNumber(evse_limits.voltage.max), dt::from_RationalNumber(ev_limits.max_voltage));
|
||||
|
||||
out_params.target_soc = ev_limits.target_soc;
|
||||
}
|
||||
|
||||
template <>
|
||||
void fill_v2x_charging_parameters(types::iso15118::V2XChargingParameters& out_params,
|
||||
const iso15118::d20::DcTransferLimits& evse_limits,
|
||||
const dt::BPT_DC_CPDReqEnergyTransferMode& ev_limits) {
|
||||
// Fill in the common data
|
||||
fill_v2x_charging_parameters<iso15118::d20::DcTransferLimits, dt::DC_CPDReqEnergyTransferMode>(
|
||||
out_params, evse_limits, static_cast<const dt::DC_CPDReqEnergyTransferMode&>(ev_limits));
|
||||
|
||||
// Fill in the bidi data
|
||||
if (evse_limits.discharge_limits.has_value()) {
|
||||
const auto& evse_discharge_limits = evse_limits.discharge_limits.value();
|
||||
|
||||
out_params.min_discharge_power = std::max(dt::from_RationalNumber(evse_discharge_limits.power.min),
|
||||
dt::from_RationalNumber(ev_limits.min_discharge_power));
|
||||
out_params.max_discharge_power = std::min(dt::from_RationalNumber(evse_discharge_limits.power.max),
|
||||
dt::from_RationalNumber(ev_limits.max_discharge_power));
|
||||
|
||||
out_params.min_discharge_current = std::max(dt::from_RationalNumber(evse_discharge_limits.current.min),
|
||||
dt::from_RationalNumber(ev_limits.min_discharge_current));
|
||||
out_params.max_discharge_current = std::min(dt::from_RationalNumber(evse_discharge_limits.current.max),
|
||||
dt::from_RationalNumber(ev_limits.max_discharge_current));
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void fill_v2x_charging_parameters(types::iso15118::V2XChargingParameters& out_params,
|
||||
const iso15118::d20::AcTransferLimits& evse_limits,
|
||||
const dt::AC_CPDReqEnergyTransferMode& ev_limits) {
|
||||
// As per the OCPP 2.1 spec (2.109) we should use the MIN/MAX function between EV and EVSE
|
||||
out_params.min_charge_power = std::max(dt::from_RationalNumber(evse_limits.charge_power.min),
|
||||
dt::from_RationalNumber(ev_limits.min_charge_power));
|
||||
out_params.max_charge_power = std::min(dt::from_RationalNumber(evse_limits.charge_power.max),
|
||||
dt::from_RationalNumber(ev_limits.max_charge_power));
|
||||
|
||||
if (evse_limits.charge_power_L2.has_value() and ev_limits.max_charge_power_L2.has_value() and
|
||||
ev_limits.min_charge_power_L2.has_value()) {
|
||||
out_params.min_charge_power_l2 = std::max(dt::from_RationalNumber(evse_limits.charge_power_L2.value().min),
|
||||
dt::from_RationalNumber(ev_limits.min_charge_power_L2.value()));
|
||||
out_params.max_charge_power_l2 = std::min(dt::from_RationalNumber(evse_limits.charge_power_L2.value().max),
|
||||
dt::from_RationalNumber(ev_limits.max_charge_power_L2.value()));
|
||||
}
|
||||
|
||||
if (evse_limits.charge_power_L3.has_value() and ev_limits.max_charge_power_L3.has_value() and
|
||||
ev_limits.min_charge_power_L3.has_value()) {
|
||||
out_params.min_charge_power_l3 = std::max(dt::from_RationalNumber(evse_limits.charge_power_L3.value().min),
|
||||
dt::from_RationalNumber(ev_limits.min_charge_power_L3.value()));
|
||||
out_params.max_charge_power_l3 = std::min(dt::from_RationalNumber(evse_limits.charge_power_L3.value().max),
|
||||
dt::from_RationalNumber(ev_limits.max_charge_power_L3.value()));
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void fill_v2x_charging_parameters(types::iso15118::V2XChargingParameters& out_params,
|
||||
const iso15118::d20::AcTransferLimits& evse_limits,
|
||||
const dt::BPT_AC_CPDReqEnergyTransferMode& ev_limits) {
|
||||
// Fill in the common data
|
||||
fill_v2x_charging_parameters<iso15118::d20::AcTransferLimits, dt::AC_CPDReqEnergyTransferMode>(
|
||||
out_params, evse_limits, static_cast<const dt::AC_CPDReqEnergyTransferMode&>(ev_limits));
|
||||
|
||||
if (evse_limits.discharge_power.has_value()) {
|
||||
const auto& evse_discharge_limits = evse_limits.discharge_power.value();
|
||||
|
||||
out_params.min_discharge_power = std::max(dt::from_RationalNumber(evse_discharge_limits.min),
|
||||
dt::from_RationalNumber(ev_limits.min_discharge_power));
|
||||
out_params.max_discharge_power = std::min(dt::from_RationalNumber(evse_discharge_limits.max),
|
||||
dt::from_RationalNumber(ev_limits.max_discharge_power));
|
||||
}
|
||||
|
||||
if (evse_limits.discharge_power_L2.has_value() && ev_limits.min_discharge_power_L2.has_value() &&
|
||||
ev_limits.max_discharge_power_L2.has_value()) {
|
||||
const auto& evse_discharge_limits = evse_limits.discharge_power_L2.value();
|
||||
|
||||
out_params.min_discharge_power_l2 = std::max(dt::from_RationalNumber(evse_discharge_limits.min),
|
||||
dt::from_RationalNumber(ev_limits.min_discharge_power_L2.value()));
|
||||
out_params.max_discharge_power_l2 = std::min(dt::from_RationalNumber(evse_discharge_limits.max),
|
||||
dt::from_RationalNumber(ev_limits.max_discharge_power_L2.value()));
|
||||
}
|
||||
|
||||
if (evse_limits.discharge_power_L3.has_value() && ev_limits.min_discharge_power_L3.has_value() &&
|
||||
ev_limits.max_discharge_power_L3.has_value()) {
|
||||
const auto& evse_discharge_limits = evse_limits.discharge_power_L3.value();
|
||||
|
||||
out_params.min_discharge_power_l3 = std::max(dt::from_RationalNumber(evse_discharge_limits.min),
|
||||
dt::from_RationalNumber(ev_limits.min_discharge_power_L3.value()));
|
||||
out_params.max_discharge_power_l3 = std::min(dt::from_RationalNumber(evse_discharge_limits.max),
|
||||
dt::from_RationalNumber(ev_limits.max_discharge_power_L3.value()));
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void fill_v2x_charging_parameters(types::iso15118::V2XChargingParameters& out_params,
|
||||
const dt::Scheduled_SEReqControlMode& ev_control_mode) {
|
||||
out_params.ev_target_energy_request = convert_from_optional(ev_control_mode.target_energy);
|
||||
out_params.ev_min_energy_request = convert_from_optional(ev_control_mode.min_energy);
|
||||
out_params.ev_max_energy_request = convert_from_optional(ev_control_mode.max_energy);
|
||||
}
|
||||
|
||||
template <>
|
||||
void fill_v2x_charging_parameters(types::iso15118::V2XChargingParameters& out_params,
|
||||
const dt::Dynamic_SEReqControlMode& ev_control_mode) {
|
||||
out_params.ev_target_energy_request = dt::from_RationalNumber(ev_control_mode.target_energy);
|
||||
out_params.ev_min_energy_request = dt::from_RationalNumber(ev_control_mode.min_energy);
|
||||
out_params.ev_max_energy_request = dt::from_RationalNumber(ev_control_mode.max_energy);
|
||||
|
||||
out_params.ev_min_v2xenergy_request = convert_from_optional(ev_control_mode.min_v2x_energy);
|
||||
out_params.ev_max_v2xenergy_request = convert_from_optional(ev_control_mode.max_v2x_energy);
|
||||
}
|
||||
|
||||
everest::lib::util::fixed_vector<dt::ParameterSet, 32>
|
||||
convert_parameter_set_list(const std::vector<types::iso15118_vas::ParameterSet>& parameter_set_list) {
|
||||
everest::lib::util::fixed_vector<dt::ParameterSet, 32> out;
|
||||
for (const auto& parameter_set : parameter_set_list) {
|
||||
const auto* ptr = out.try_emplace_back(convert_parameter_set(parameter_set));
|
||||
|
||||
if (ptr == nullptr) {
|
||||
EVLOG_warning << "VAS parameter set list is bigger then 32";
|
||||
break;
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
types::iso15118::AcEvPowerLimits fill_ac_ev_power_limits(const dt::AC_CPDReqEnergyTransferMode& mode) {
|
||||
const auto max_charge_L1 = dt::from_RationalNumber(mode.max_charge_power);
|
||||
const auto max_charge_L2 = convert_from_optional(mode.max_charge_power_L2);
|
||||
const auto max_charge_L3 = convert_from_optional(mode.max_charge_power_L3);
|
||||
const auto max_charge_total = max_charge_L1 + max_charge_L2.value_or(0.0) + max_charge_L3.value_or(0.0);
|
||||
|
||||
const auto min_charge_L1 = dt::from_RationalNumber(mode.min_charge_power);
|
||||
const auto min_charge_L2 = convert_from_optional(mode.min_charge_power_L2);
|
||||
const auto min_charge_L3 = convert_from_optional(mode.min_charge_power_L3);
|
||||
const auto min_charge_total = min_charge_L1 + min_charge_L2.value_or(0.0) + min_charge_L3.value_or(0.0);
|
||||
|
||||
types::iso15118::AcEvPowerLimits ac_ev_power_limits;
|
||||
|
||||
ac_ev_power_limits.max_charge_power = {max_charge_total, std::make_optional<float>(max_charge_L1), max_charge_L2,
|
||||
max_charge_L3};
|
||||
ac_ev_power_limits.min_charge_power = {min_charge_total, std::make_optional<float>(min_charge_L1), min_charge_L2,
|
||||
min_charge_L3};
|
||||
|
||||
return ac_ev_power_limits;
|
||||
}
|
||||
|
||||
types::iso15118::AcEvPowerLimits fill_ac_ev_power_limits(const dt::BPT_AC_CPDReqEnergyTransferMode& mode) {
|
||||
const auto max_charge_L1 = dt::from_RationalNumber(mode.max_charge_power);
|
||||
const auto max_charge_L2 = convert_from_optional(mode.max_charge_power_L2);
|
||||
const auto max_charge_L3 = convert_from_optional(mode.max_charge_power_L3);
|
||||
const auto max_charge_total = max_charge_L1 + max_charge_L2.value_or(0.0) + max_charge_L3.value_or(0.0);
|
||||
|
||||
const auto min_charge_L1 = dt::from_RationalNumber(mode.min_charge_power);
|
||||
const auto min_charge_L2 = convert_from_optional(mode.min_charge_power_L2);
|
||||
const auto min_charge_L3 = convert_from_optional(mode.min_charge_power_L3);
|
||||
const auto min_charge_total = min_charge_L1 + min_charge_L2.value_or(0.0) + min_charge_L3.value_or(0.0);
|
||||
|
||||
const auto max_discharge_L1 = dt::from_RationalNumber(mode.max_discharge_power);
|
||||
const auto max_discharge_L2 = convert_from_optional(mode.max_discharge_power_L2);
|
||||
const auto max_discharge_L3 = convert_from_optional(mode.max_discharge_power_L3);
|
||||
const auto max_discharge_total = max_discharge_L1 + max_discharge_L2.value_or(0.0) + max_discharge_L3.value_or(0.0);
|
||||
|
||||
const auto min_discharge_L1 = dt::from_RationalNumber(mode.min_discharge_power);
|
||||
const auto min_discharge_L2 = convert_from_optional(mode.min_discharge_power_L2);
|
||||
const auto min_discharge_L3 = convert_from_optional(mode.min_discharge_power_L3);
|
||||
const auto min_discharge_total = min_discharge_L1 + min_discharge_L2.value_or(0.0) + min_discharge_L3.value_or(0.0);
|
||||
|
||||
types::iso15118::AcEvPowerLimits ac_ev_power_limits;
|
||||
|
||||
ac_ev_power_limits.max_charge_power = {max_charge_total, std::make_optional<float>(max_charge_L1), max_charge_L2,
|
||||
max_charge_L3};
|
||||
ac_ev_power_limits.min_charge_power = {min_charge_total, std::make_optional<float>(min_charge_L1), min_charge_L2,
|
||||
min_charge_L3};
|
||||
ac_ev_power_limits.max_discharge_power = {max_discharge_total, std::make_optional<float>(max_discharge_L1),
|
||||
max_discharge_L2, max_discharge_L3};
|
||||
ac_ev_power_limits.min_discharge_power = {min_discharge_total, std::make_optional<float>(min_discharge_L1),
|
||||
min_discharge_L2, min_discharge_L3};
|
||||
|
||||
return ac_ev_power_limits;
|
||||
}
|
||||
|
||||
types::iso15118::AcEvPowerLimits fill_ac_ev_power_limits(const dt::Dynamic_AC_CLReqControlMode& mode) {
|
||||
const auto max_charge_L1 = dt::from_RationalNumber(mode.max_charge_power);
|
||||
const auto max_charge_L2 = convert_from_optional(mode.max_charge_power_L2);
|
||||
const auto max_charge_L3 = convert_from_optional(mode.max_charge_power_L3);
|
||||
const auto max_charge_total = max_charge_L1 + max_charge_L2.value_or(0.0) + max_charge_L3.value_or(0.0);
|
||||
|
||||
const auto min_charge_L1 = dt::from_RationalNumber(mode.min_charge_power);
|
||||
const auto min_charge_L2 = convert_from_optional(mode.min_charge_power_L2);
|
||||
const auto min_charge_L3 = convert_from_optional(mode.min_charge_power_L3);
|
||||
const auto min_charge_total = min_charge_L1 + min_charge_L2.value_or(0.0) + min_charge_L3.value_or(0.0);
|
||||
|
||||
types::iso15118::AcEvPowerLimits ac_ev_power_limits;
|
||||
|
||||
ac_ev_power_limits.max_charge_power = {max_charge_total, std::make_optional<float>(max_charge_L1), max_charge_L2,
|
||||
max_charge_L3};
|
||||
ac_ev_power_limits.min_charge_power = {min_charge_total, std::make_optional<float>(min_charge_L1), min_charge_L2,
|
||||
min_charge_L3};
|
||||
|
||||
return ac_ev_power_limits;
|
||||
}
|
||||
|
||||
types::iso15118::AcEvPowerLimits fill_ac_ev_power_limits(const dt::BPT_Dynamic_AC_CLReqControlMode& mode) {
|
||||
const auto max_charge_L1 = dt::from_RationalNumber(mode.max_charge_power);
|
||||
const auto max_charge_L2 = convert_from_optional(mode.max_charge_power_L2);
|
||||
const auto max_charge_L3 = convert_from_optional(mode.max_charge_power_L3);
|
||||
const auto max_charge_total = max_charge_L1 + max_charge_L2.value_or(0.0) + max_charge_L3.value_or(0.0);
|
||||
|
||||
const auto min_charge_L1 = dt::from_RationalNumber(mode.min_charge_power);
|
||||
const auto min_charge_L2 = convert_from_optional(mode.min_charge_power_L2);
|
||||
const auto min_charge_L3 = convert_from_optional(mode.min_charge_power_L3);
|
||||
const auto min_charge_total = min_charge_L1 + min_charge_L2.value_or(0.0) + min_charge_L3.value_or(0.0);
|
||||
|
||||
const auto max_discharge_L1 = dt::from_RationalNumber(mode.max_discharge_power);
|
||||
const auto max_discharge_L2 = convert_from_optional(mode.max_discharge_power_L2);
|
||||
const auto max_discharge_L3 = convert_from_optional(mode.max_discharge_power_L3);
|
||||
const auto max_discharge_total = max_discharge_L1 + max_discharge_L2.value_or(0.0) + max_discharge_L3.value_or(0.0);
|
||||
|
||||
const auto min_discharge_L1 = dt::from_RationalNumber(mode.min_discharge_power);
|
||||
const auto min_discharge_L2 = convert_from_optional(mode.min_discharge_power_L2);
|
||||
const auto min_discharge_L3 = convert_from_optional(mode.min_discharge_power_L3);
|
||||
const auto min_discharge_total = min_discharge_L1 + min_discharge_L2.value_or(0.0) + min_discharge_L3.value_or(0.0);
|
||||
|
||||
types::iso15118::AcEvPowerLimits ac_ev_power_limits;
|
||||
|
||||
ac_ev_power_limits.max_charge_power = {max_charge_total, std::make_optional<float>(max_charge_L1), max_charge_L2,
|
||||
max_charge_L3};
|
||||
ac_ev_power_limits.min_charge_power = {min_charge_total, std::make_optional<float>(min_charge_L1), min_charge_L2,
|
||||
min_charge_L3};
|
||||
ac_ev_power_limits.max_discharge_power = {max_discharge_total, std::make_optional<float>(max_discharge_L1),
|
||||
max_discharge_L2, max_discharge_L3};
|
||||
ac_ev_power_limits.min_discharge_power = {min_discharge_total, std::make_optional<float>(min_discharge_L1),
|
||||
min_discharge_L2, min_discharge_L3};
|
||||
|
||||
return ac_ev_power_limits;
|
||||
}
|
||||
|
||||
types::iso15118::AcEvPowerLimits fill_ac_ev_power_limits(const dt::Scheduled_AC_CLReqControlMode& mode) {
|
||||
const auto max_charge_L1 = convert_from_optional(mode.max_charge_power);
|
||||
const auto max_charge_L2 = convert_from_optional(mode.max_charge_power_L2);
|
||||
const auto max_charge_L3 = convert_from_optional(mode.max_charge_power_L3);
|
||||
const auto max_charge_total =
|
||||
max_charge_L1.value_or(0.0) + max_charge_L2.value_or(0.0) + max_charge_L3.value_or(0.0);
|
||||
|
||||
const auto min_charge_L1 = convert_from_optional(mode.min_charge_power);
|
||||
const auto min_charge_L2 = convert_from_optional(mode.min_charge_power_L2);
|
||||
const auto min_charge_L3 = convert_from_optional(mode.min_charge_power_L3);
|
||||
const auto min_charge_total =
|
||||
min_charge_L1.value_or(0.0) + min_charge_L2.value_or(0.0) + min_charge_L3.value_or(0.0);
|
||||
|
||||
types::iso15118::AcEvPowerLimits ac_ev_power_limits;
|
||||
|
||||
ac_ev_power_limits.max_charge_power =
|
||||
(max_charge_L1.has_value() or max_charge_L2.has_value() or max_charge_L3.has_value())
|
||||
? std::make_optional<types::units::Power>({max_charge_total, max_charge_L1, max_charge_L2, max_charge_L3})
|
||||
: std::nullopt;
|
||||
ac_ev_power_limits.min_charge_power =
|
||||
(max_charge_L1.has_value() or max_charge_L2.has_value() or max_charge_L3.has_value())
|
||||
? std::make_optional<types::units::Power>({min_charge_total, min_charge_L1, min_charge_L2, min_charge_L3})
|
||||
: std::nullopt;
|
||||
|
||||
return ac_ev_power_limits;
|
||||
}
|
||||
|
||||
types::iso15118::AcEvPowerLimits fill_ac_ev_power_limits(const dt::BPT_Scheduled_AC_CLReqControlMode& mode) {
|
||||
const auto max_charge_L1 = convert_from_optional(mode.max_charge_power);
|
||||
const auto max_charge_L2 = convert_from_optional(mode.max_charge_power_L2);
|
||||
const auto max_charge_L3 = convert_from_optional(mode.max_charge_power_L3);
|
||||
const auto max_charge_total =
|
||||
max_charge_L1.value_or(0.0) + max_charge_L2.value_or(0.0) + max_charge_L3.value_or(0.0);
|
||||
|
||||
const auto min_charge_L1 = convert_from_optional(mode.min_charge_power);
|
||||
const auto min_charge_L2 = convert_from_optional(mode.min_charge_power_L2);
|
||||
const auto min_charge_L3 = convert_from_optional(mode.min_charge_power_L3);
|
||||
const auto min_charge_total =
|
||||
min_charge_L1.value_or(0.0) + min_charge_L2.value_or(0.0) + min_charge_L3.value_or(0.0);
|
||||
|
||||
const auto max_discharge_L1 = convert_from_optional(mode.max_discharge_power);
|
||||
const auto max_discharge_L2 = convert_from_optional(mode.max_discharge_power_L2);
|
||||
const auto max_discharge_L3 = convert_from_optional(mode.max_discharge_power_L3);
|
||||
const auto max_discharge_total =
|
||||
max_discharge_L1.value_or(0.0) + max_discharge_L2.value_or(0.0) + max_discharge_L3.value_or(0.0);
|
||||
|
||||
const auto min_discharge_L1 = convert_from_optional(mode.min_discharge_power);
|
||||
const auto min_discharge_L2 = convert_from_optional(mode.min_discharge_power_L2);
|
||||
const auto min_discharge_L3 = convert_from_optional(mode.min_discharge_power_L3);
|
||||
const auto min_discharge_total =
|
||||
min_discharge_L1.value_or(0.0) + min_discharge_L2.value_or(0.0) + min_discharge_L3.value_or(0.0);
|
||||
|
||||
types::iso15118::AcEvPowerLimits ac_ev_power_limits;
|
||||
|
||||
ac_ev_power_limits.max_charge_power =
|
||||
(max_charge_L1.has_value() or max_charge_L2.has_value() or max_charge_L3.has_value())
|
||||
? std::make_optional<types::units::Power>({max_charge_total, max_charge_L1, max_charge_L2, max_charge_L3})
|
||||
: std::nullopt;
|
||||
ac_ev_power_limits.min_charge_power =
|
||||
(min_charge_L1.has_value() or min_charge_L2.has_value() or min_charge_L3.has_value())
|
||||
? std::make_optional<types::units::Power>({min_charge_total, min_charge_L1, min_charge_L2, min_charge_L3})
|
||||
: std::nullopt;
|
||||
ac_ev_power_limits.max_discharge_power =
|
||||
(max_discharge_L1.has_value() or max_discharge_L2.has_value() or max_discharge_L3.has_value())
|
||||
? std::make_optional<types::units::Power>(
|
||||
{max_discharge_total, max_discharge_L1, max_discharge_L2, max_discharge_L3})
|
||||
: std::nullopt;
|
||||
ac_ev_power_limits.min_discharge_power =
|
||||
(min_discharge_L1.has_value() or min_discharge_L2.has_value() or min_discharge_L3.has_value())
|
||||
? std::make_optional<types::units::Power>(
|
||||
{min_discharge_total, min_discharge_L1, min_discharge_L2, min_discharge_L3})
|
||||
: std::nullopt;
|
||||
return ac_ev_power_limits;
|
||||
}
|
||||
|
||||
types::iso15118::AcEvPresentPowerValues fill_ac_ev_present_power_values(const dt::Dynamic_AC_CLReqControlMode& mode) {
|
||||
types::iso15118::AcEvPresentPowerValues present_values{};
|
||||
|
||||
const auto present_active_power_L1 = dt::from_RationalNumber(mode.present_active_power);
|
||||
const auto present_active_power_L2 = convert_from_optional(mode.present_active_power_L2);
|
||||
const auto present_active_power_L3 = convert_from_optional(mode.present_active_power_L3);
|
||||
const auto present_active_power_total =
|
||||
present_active_power_L1 + present_active_power_L2.value_or(0.0) + present_active_power_L3.value_or(0.0);
|
||||
|
||||
const auto present_reactive_power_L1 = dt::from_RationalNumber(mode.present_reactive_power);
|
||||
const auto present_reactive_power_L2 = convert_from_optional(mode.present_reactive_power_L2);
|
||||
const auto present_reactive_power_L3 = convert_from_optional(mode.present_reactive_power_L3);
|
||||
const auto present_reactive_power_total =
|
||||
present_reactive_power_L1 + present_reactive_power_L2.value_or(0.0) + present_reactive_power_L3.value_or(0.0);
|
||||
|
||||
present_values.present_active_power = {present_active_power_total, present_active_power_L1, present_active_power_L2,
|
||||
present_active_power_L3};
|
||||
|
||||
present_values.present_reactive_power = {present_reactive_power_total, present_reactive_power_L1,
|
||||
present_reactive_power_L2, present_reactive_power_L3};
|
||||
|
||||
return present_values;
|
||||
}
|
||||
|
||||
types::iso15118::AcEvPresentPowerValues fill_ac_ev_present_power_values(const dt::Scheduled_AC_CLReqControlMode& mode) {
|
||||
types::iso15118::AcEvPresentPowerValues present_values{};
|
||||
|
||||
const auto present_active_power_L1 = dt::from_RationalNumber(mode.present_active_power);
|
||||
const auto present_active_power_L2 = convert_from_optional(mode.present_active_power_L2);
|
||||
const auto present_active_power_L3 = convert_from_optional(mode.present_active_power_L3);
|
||||
const auto present_active_power_total =
|
||||
present_active_power_L1 + present_active_power_L2.value_or(0.0) + present_active_power_L3.value_or(0.0);
|
||||
|
||||
const auto present_reactive_power_L1 = convert_from_optional(mode.present_reactive_power);
|
||||
const auto present_reactive_power_L2 = convert_from_optional(mode.present_reactive_power_L2);
|
||||
const auto present_reactive_power_L3 = convert_from_optional(mode.present_reactive_power_L3);
|
||||
const auto present_reactive_power_total = present_reactive_power_L1.value_or(0.0) +
|
||||
present_reactive_power_L2.value_or(0.0) +
|
||||
present_reactive_power_L3.value_or(0.0);
|
||||
|
||||
present_values.present_active_power = {present_active_power_total, present_active_power_L1, present_active_power_L2,
|
||||
present_active_power_L3};
|
||||
|
||||
present_values.present_reactive_power =
|
||||
(present_reactive_power_L1.has_value() or present_reactive_power_L2.has_value() or
|
||||
present_reactive_power_L3.has_value())
|
||||
? std::make_optional<types::units::Power>({present_reactive_power_total, present_reactive_power_L1,
|
||||
present_reactive_power_L2, present_reactive_power_L3})
|
||||
: std::nullopt;
|
||||
|
||||
return present_values;
|
||||
}
|
||||
|
||||
} // namespace module::charger
|
||||
152
tools/EVerest-main/modules/EVSE/Evse15118D20/charger/utils.hpp
Normal file
152
tools/EVerest-main/modules/EVSE/Evse15118D20/charger/utils.hpp
Normal file
@@ -0,0 +1,152 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
#include <generated/types/iso15118.hpp>
|
||||
#include <generated/types/iso15118_vas.hpp>
|
||||
|
||||
#include <iso15118/d20/ev_information.hpp>
|
||||
#include <iso15118/message/ac_charge_loop.hpp>
|
||||
#include <iso15118/message/ac_charge_parameter_discovery.hpp>
|
||||
#include <iso15118/message/dc_charge_loop.hpp>
|
||||
#include <iso15118/message/service_detail.hpp>
|
||||
#include <iso15118/message/type.hpp>
|
||||
|
||||
#include <everest/util/vector/fixed_vector.hpp>
|
||||
|
||||
static constexpr auto NUMBER_OF_SETUP_STEPS = 5;
|
||||
|
||||
namespace module::charger {
|
||||
|
||||
namespace dt = iso15118::message_20::datatypes;
|
||||
|
||||
enum class SetupStep : std::uint8_t {
|
||||
SETUP,
|
||||
ENERGY_SERVICE,
|
||||
AUTH_SETUP,
|
||||
MAX_LIMITS,
|
||||
MIN_LIMITS,
|
||||
};
|
||||
|
||||
template <typename T> constexpr auto to_underlying_value(T t) {
|
||||
return static_cast<std::underlying_type_t<T>>(t);
|
||||
}
|
||||
|
||||
static_assert(NUMBER_OF_SETUP_STEPS == to_underlying_value(SetupStep::MIN_LIMITS) + 1,
|
||||
"NUMBER_OF_SETUP_STEPS should be in sync with the SetupStep enum definition");
|
||||
|
||||
constexpr types::iso15118::V2gMessageId convert_v2g_message_type(iso15118::message_20::Type type) {
|
||||
|
||||
using Type = iso15118::message_20::Type;
|
||||
using Id = types::iso15118::V2gMessageId;
|
||||
|
||||
switch (type) {
|
||||
case Type::None:
|
||||
return Id::UnknownMessage;
|
||||
case Type::SupportedAppProtocolReq:
|
||||
return Id::SupportedAppProtocolReq;
|
||||
case Type::SupportedAppProtocolRes:
|
||||
return Id::SupportedAppProtocolRes;
|
||||
case Type::SessionSetupReq:
|
||||
return Id::SessionSetupReq;
|
||||
case Type::SessionSetupRes:
|
||||
return Id::SessionSetupRes;
|
||||
case Type::AuthorizationSetupReq:
|
||||
return Id::AuthorizationSetupReq;
|
||||
case Type::AuthorizationSetupRes:
|
||||
return Id::AuthorizationSetupRes;
|
||||
case Type::AuthorizationReq:
|
||||
return Id::AuthorizationReq;
|
||||
case Type::AuthorizationRes:
|
||||
return Id::AuthorizationRes;
|
||||
case Type::ServiceDiscoveryReq:
|
||||
return Id::ServiceDiscoveryReq;
|
||||
case Type::ServiceDiscoveryRes:
|
||||
return Id::ServiceDiscoveryRes;
|
||||
case Type::ServiceDetailReq:
|
||||
return Id::ServiceDetailReq;
|
||||
case Type::ServiceDetailRes:
|
||||
return Id::ServiceDetailRes;
|
||||
case Type::ServiceSelectionReq:
|
||||
return Id::ServiceSelectionReq;
|
||||
case Type::ServiceSelectionRes:
|
||||
return Id::ServiceSelectionRes;
|
||||
case Type::DC_ChargeParameterDiscoveryReq:
|
||||
return Id::DcChargeParameterDiscoveryReq;
|
||||
case Type::DC_ChargeParameterDiscoveryRes:
|
||||
return Id::DcChargeParameterDiscoveryRes;
|
||||
case Type::ScheduleExchangeReq:
|
||||
return Id::ScheduleExchangeReq;
|
||||
case Type::ScheduleExchangeRes:
|
||||
return Id::ScheduleExchangeRes;
|
||||
case Type::DC_CableCheckReq:
|
||||
return Id::DcCableCheckReq;
|
||||
case Type::DC_CableCheckRes:
|
||||
return Id::DcCableCheckRes;
|
||||
case Type::DC_PreChargeReq:
|
||||
return Id::DcPreChargeReq;
|
||||
case Type::DC_PreChargeRes:
|
||||
return Id::DcPreChargeRes;
|
||||
case Type::PowerDeliveryReq:
|
||||
return Id::PowerDeliveryReq;
|
||||
case Type::PowerDeliveryRes:
|
||||
return Id::PowerDeliveryRes;
|
||||
case Type::DC_ChargeLoopReq:
|
||||
return Id::DcChargeLoopReq;
|
||||
case Type::DC_ChargeLoopRes:
|
||||
return Id::DcChargeLoopRes;
|
||||
case Type::DC_WeldingDetectionReq:
|
||||
return Id::DcWeldingDetectionReq;
|
||||
case Type::DC_WeldingDetectionRes:
|
||||
return Id::DcWeldingDetectionRes;
|
||||
case Type::SessionStopReq:
|
||||
return Id::SessionStopReq;
|
||||
case Type::SessionStopRes:
|
||||
return Id::SessionStopRes;
|
||||
case Type::AC_ChargeParameterDiscoveryReq:
|
||||
return Id::AcChargeParameterDiscoveryReq;
|
||||
case Type::AC_ChargeParameterDiscoveryRes:
|
||||
return Id::AcChargeParameterDiscoveryRes;
|
||||
case Type::AC_ChargeLoopReq:
|
||||
return Id::AcChargeLoopReq;
|
||||
case Type::AC_ChargeLoopRes:
|
||||
return Id::AcChargeLoopRes;
|
||||
}
|
||||
|
||||
return Id::UnknownMessage;
|
||||
}
|
||||
|
||||
std::optional<float> convert_from_optional(const std::optional<dt::RationalNumber>& in);
|
||||
std::optional<dt::RationalNumber> convert_from_optional(const std::optional<float>& in);
|
||||
std::optional<float> convert_from_optional(const std::optional<uint32_t>& in);
|
||||
|
||||
types::iso15118::AppProtocol convert_app_protocol(const iso15118::message_20::SupportedAppProtocol& app_protocol);
|
||||
types::iso15118::EvInformation convert_ev_info(const iso15118::d20::EVInformation& ev_info);
|
||||
|
||||
types::iso15118::DcChargeDynamicModeValues convert_dynamic_values(const dt::Dynamic_DC_CLReqControlMode& in);
|
||||
types::iso15118::DcChargeDynamicModeValues convert_dynamic_values(const dt::BPT_Dynamic_DC_CLReqControlMode& in);
|
||||
|
||||
template <typename EVSE, typename EV>
|
||||
void fill_v2x_charging_parameters(types::iso15118::V2XChargingParameters& out_params, const EVSE& evse_limits,
|
||||
const EV& ev_limits);
|
||||
template <typename In>
|
||||
void fill_v2x_charging_parameters(types::iso15118::V2XChargingParameters& out_params, const In& data);
|
||||
|
||||
everest::lib::util::fixed_vector<dt::ParameterSet, 32>
|
||||
convert_parameter_set_list(const std::vector<types::iso15118_vas::ParameterSet>& parameter_set_list);
|
||||
|
||||
types::iso15118::AcEvPowerLimits fill_ac_ev_power_limits(const dt::AC_CPDReqEnergyTransferMode& mode);
|
||||
types::iso15118::AcEvPowerLimits fill_ac_ev_power_limits(const dt::BPT_AC_CPDReqEnergyTransferMode& mode);
|
||||
types::iso15118::AcEvPowerLimits fill_ac_ev_power_limits(const dt::Dynamic_AC_CLReqControlMode& mode);
|
||||
types::iso15118::AcEvPowerLimits fill_ac_ev_power_limits(const dt::BPT_Dynamic_AC_CLReqControlMode& mode);
|
||||
types::iso15118::AcEvPowerLimits fill_ac_ev_power_limits(const dt::Scheduled_AC_CLReqControlMode& mode);
|
||||
types::iso15118::AcEvPowerLimits fill_ac_ev_power_limits(const dt::BPT_Scheduled_AC_CLReqControlMode& mode);
|
||||
|
||||
types::iso15118::AcEvPresentPowerValues fill_ac_ev_present_power_values(const dt::Dynamic_AC_CLReqControlMode& mode);
|
||||
types::iso15118::AcEvPresentPowerValues fill_ac_ev_present_power_values(const dt::Scheduled_AC_CLReqControlMode& mode);
|
||||
|
||||
} // namespace module::charger
|
||||
@@ -0,0 +1,21 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include "iso15118_extensionsImpl.hpp"
|
||||
|
||||
namespace module {
|
||||
namespace extensions {
|
||||
|
||||
void iso15118_extensionsImpl::init() {
|
||||
}
|
||||
|
||||
void iso15118_extensionsImpl::ready() {
|
||||
}
|
||||
|
||||
void iso15118_extensionsImpl::handle_set_get_certificate_response(
|
||||
types::iso15118::ResponseExiStreamStatus& certificate_response) {
|
||||
// your code for cmd set_get_certificate_response goes here
|
||||
}
|
||||
|
||||
} // namespace extensions
|
||||
} // namespace module
|
||||
@@ -0,0 +1,62 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
#ifndef EXTENSIONS_ISO15118_EXTENSIONS_IMPL_HPP
|
||||
#define EXTENSIONS_ISO15118_EXTENSIONS_IMPL_HPP
|
||||
|
||||
//
|
||||
// AUTO GENERATED - MARKED REGIONS WILL BE KEPT
|
||||
// template version 3
|
||||
//
|
||||
|
||||
#include <generated/interfaces/iso15118_extensions/Implementation.hpp>
|
||||
|
||||
#include "../Evse15118D20.hpp"
|
||||
|
||||
// ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1
|
||||
// insert your custom include headers here
|
||||
// ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1
|
||||
|
||||
namespace module {
|
||||
namespace extensions {
|
||||
|
||||
struct Conf {};
|
||||
|
||||
class iso15118_extensionsImpl : public iso15118_extensionsImplBase {
|
||||
public:
|
||||
iso15118_extensionsImpl() = delete;
|
||||
iso15118_extensionsImpl(Everest::ModuleAdapter* ev, const Everest::PtrContainer<Evse15118D20>& mod, Conf& config) :
|
||||
iso15118_extensionsImplBase(ev, "extensions"), mod(mod), config(config){};
|
||||
|
||||
// ev@8ea32d28-373f-4c90-ae5e-b4fcc74e2a61:v1
|
||||
// insert your public definitions here
|
||||
// ev@8ea32d28-373f-4c90-ae5e-b4fcc74e2a61:v1
|
||||
|
||||
protected:
|
||||
// command handler functions (virtual)
|
||||
virtual void
|
||||
handle_set_get_certificate_response(types::iso15118::ResponseExiStreamStatus& certificate_response) override;
|
||||
|
||||
// ev@d2d1847a-7b88-41dd-ad07-92785f06f5c4:v1
|
||||
// insert your protected definitions here
|
||||
// ev@d2d1847a-7b88-41dd-ad07-92785f06f5c4:v1
|
||||
|
||||
private:
|
||||
const Everest::PtrContainer<Evse15118D20>& mod;
|
||||
const Conf& config;
|
||||
|
||||
virtual void init() override;
|
||||
virtual void ready() override;
|
||||
|
||||
// ev@3370e4dd-95f4-47a9-aaec-ea76f34a66c9:v1
|
||||
// insert your private definitions here
|
||||
// ev@3370e4dd-95f4-47a9-aaec-ea76f34a66c9:v1
|
||||
};
|
||||
|
||||
// ev@3d7da0ad-02c2-493d-9920-0bbbd56b9876:v1
|
||||
// insert other definitions here
|
||||
// ev@3d7da0ad-02c2-493d-9920-0bbbd56b9876:v1
|
||||
|
||||
} // namespace extensions
|
||||
} // namespace module
|
||||
|
||||
#endif // EXTENSIONS_ISO15118_EXTENSIONS_IMPL_HPP
|
||||
93
tools/EVerest-main/modules/EVSE/Evse15118D20/manifest.yaml
Normal file
93
tools/EVerest-main/modules/EVSE/Evse15118D20/manifest.yaml
Normal file
@@ -0,0 +1,93 @@
|
||||
description: >-
|
||||
This module is a draft implementation of iso15118-20 for the EVSE side
|
||||
config:
|
||||
device:
|
||||
description: >-
|
||||
Ethernet device used for HLC. Any local interface that has an ipv6
|
||||
link-local and a MAC addr will work
|
||||
type: string
|
||||
default: eth0
|
||||
logging_path:
|
||||
description: Path to logging directory (will be created if non existent)
|
||||
type: string
|
||||
default: "."
|
||||
tls_negotiation_strategy:
|
||||
description: Select strategy on how to negotiate connection encryption
|
||||
type: string
|
||||
enum:
|
||||
- ACCEPT_CLIENT_OFFER
|
||||
- ENFORCE_TLS
|
||||
- ENFORCE_NO_TLS
|
||||
default: ACCEPT_CLIENT_OFFER
|
||||
enforce_tls_1_3:
|
||||
description: Enforcing tls version 1.3. Only applies if tls_negotiation_strategy is ENFORCE_TLS.
|
||||
type: boolean
|
||||
default: false
|
||||
enable_ssl_logging:
|
||||
description: Verbosely log the ssl/tls connection
|
||||
type: boolean
|
||||
default: false
|
||||
enable_tls_key_logging:
|
||||
description: >-
|
||||
Enable/Disable the export of TLS session keys (pre-master-secret)
|
||||
during a TLS handshake. Note that this option is for testing and
|
||||
simulation purpose only
|
||||
type: boolean
|
||||
default: false
|
||||
tls_key_logging_path:
|
||||
description: >-
|
||||
Output directory for the TLS key log file
|
||||
type: string
|
||||
default: /tmp
|
||||
enable_sdp_server:
|
||||
description: >-
|
||||
Enable the built-in SDP server
|
||||
type: boolean
|
||||
default: true
|
||||
supported_dynamic_mode:
|
||||
description: The EVSE should support dynamic mode
|
||||
type: boolean
|
||||
default: true
|
||||
supported_mobility_needs_mode_provided_by_secc:
|
||||
description: >-
|
||||
The EVSE should support the mobility needs mode provided by the SECC.
|
||||
Mobility needs mode provided by the EVCC is always provided.
|
||||
type: boolean
|
||||
default: false
|
||||
supported_scheduled_mode:
|
||||
description: The EVSE should support scheduled mode
|
||||
type: boolean
|
||||
default: false
|
||||
custom_protocol_namespace:
|
||||
description: Providing a custom protocol namespace.
|
||||
type: string
|
||||
default: ""
|
||||
negative_bidirectional_limits:
|
||||
description: >-
|
||||
Some cars send or expect negative discharge limits. However, it is not clear from the standard
|
||||
whether the discharge limits should be negative. Until this is clarified, the positive limits
|
||||
can be converted using this option.
|
||||
type: boolean
|
||||
default: false
|
||||
provides:
|
||||
charger:
|
||||
interface: ISO15118_charger
|
||||
description: >-
|
||||
This interface provides limited access to iso15118-20
|
||||
extensions:
|
||||
interface: iso15118_extensions
|
||||
description: >-
|
||||
This interface is used to share data between ISO15118 and OCPP modules
|
||||
to support the requirements of the OCPP protocol
|
||||
requires:
|
||||
security:
|
||||
interface: evse_security
|
||||
iso15118_vas:
|
||||
interface: ISO15118_vas
|
||||
min_connections: 0
|
||||
max_connections: 128
|
||||
metadata:
|
||||
license: https://opensource.org/licenses/Apache-2.0
|
||||
authors:
|
||||
- aw@pionix.de
|
||||
- Sebastian Lukas
|
||||
28
tools/EVerest-main/modules/EVSE/EvseManager/BUILD.bazel
Normal file
28
tools/EVerest-main/modules/EVSE/EvseManager/BUILD.bazel
Normal file
@@ -0,0 +1,28 @@
|
||||
load("//modules:module.bzl", "cc_everest_module")
|
||||
|
||||
IMPLS = [
|
||||
"energy_grid",
|
||||
"evse",
|
||||
"token_provider",
|
||||
"random_delay",
|
||||
"dc_external_derate",
|
||||
"over_voltage",
|
||||
"voltage_plausibility"
|
||||
]
|
||||
|
||||
cc_everest_module(
|
||||
name = "EvseManager",
|
||||
deps = [
|
||||
"@pugixml//:pugixml",
|
||||
"@sigslot//:sigslot",
|
||||
"//lib/everest/helpers",
|
||||
"//lib/everest/util",
|
||||
],
|
||||
impls = IMPLS,
|
||||
srcs = glob(
|
||||
[
|
||||
"*.cpp",
|
||||
"*.hpp",
|
||||
],
|
||||
),
|
||||
)
|
||||
69
tools/EVerest-main/modules/EVSE/EvseManager/CMakeLists.txt
Normal file
69
tools/EVerest-main/modules/EVSE/EvseManager/CMakeLists.txt
Normal file
@@ -0,0 +1,69 @@
|
||||
#
|
||||
# AUTO GENERATED - MARKED REGIONS WILL BE KEPT
|
||||
# template version 3
|
||||
#
|
||||
|
||||
# module setup:
|
||||
# - ${MODULE_NAME}: module name
|
||||
ev_setup_cpp_module()
|
||||
|
||||
# ev@bcc62523-e22b-41d7-ba2f-825b493a3c97:v1
|
||||
# insert your custom targets and additional config variables here
|
||||
target_sources(${MODULE_NAME}
|
||||
PRIVATE
|
||||
Charger.cpp
|
||||
SessionLog.cpp
|
||||
v2gMessage.cpp
|
||||
CarManufacturer.cpp
|
||||
IECStateMachine.cpp
|
||||
ErrorHandling.cpp
|
||||
backtrace.cpp
|
||||
PersistentStore.cpp
|
||||
over_voltage/OverVoltageMonitor.cpp
|
||||
voltage_plausibility/VoltagePlausibilityMonitor.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(${MODULE_NAME}
|
||||
PRIVATE
|
||||
everest::helpers
|
||||
everest::util
|
||||
Pal::Sigslot
|
||||
pugixml::pugixml
|
||||
)
|
||||
|
||||
if (CMAKE_COMPILER_IS_GNUCC AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 9)
|
||||
target_link_libraries(${MODULE_NAME}
|
||||
PRIVATE
|
||||
stdc++fs
|
||||
)
|
||||
endif()
|
||||
|
||||
target_compile_options(${MODULE_NAME}
|
||||
PRIVATE
|
||||
"-rdynamic"
|
||||
)
|
||||
|
||||
target_link_options(${MODULE_NAME}
|
||||
PRIVATE
|
||||
"-rdynamic"
|
||||
)
|
||||
|
||||
# needed for std::filesystem
|
||||
target_compile_features(${MODULE_NAME} PUBLIC cxx_std_17)
|
||||
# ev@bcc62523-e22b-41d7-ba2f-825b493a3c97:v1
|
||||
|
||||
target_sources(${MODULE_NAME}
|
||||
PRIVATE
|
||||
"evse/evse_managerImpl.cpp"
|
||||
"energy_grid/energyImpl.cpp"
|
||||
"token_provider/auth_token_providerImpl.cpp"
|
||||
"random_delay/uk_random_delayImpl.cpp"
|
||||
"dc_external_derate/dc_external_derateImpl.cpp"
|
||||
)
|
||||
|
||||
# ev@c55432ab-152c-45a9-9d2e-7281d50c69c3:v1
|
||||
# insert other things like install cmds etc here
|
||||
if(EVEREST_CORE_BUILD_TESTING)
|
||||
add_subdirectory(tests)
|
||||
endif()
|
||||
# ev@c55432ab-152c-45a9-9d2e-7281d50c69c3:v1
|
||||
@@ -0,0 +1,38 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2022 Pionix GmbH and Contributors to EVerest
|
||||
#include "CarManufacturer.hpp"
|
||||
#include <set>
|
||||
|
||||
namespace module {
|
||||
|
||||
types::evse_manager::CarManufacturer get_manufacturer_from_mac(const std::string& mac) {
|
||||
// Tesla OUIs according to http://standards-oui.ieee.org/oui.txt
|
||||
const std::set<std::string> tesla = {
|
||||
"0C:29:8F",
|
||||
"4C:FC:AA",
|
||||
"54:F8:F0",
|
||||
"98:ED:5C",
|
||||
};
|
||||
|
||||
if (mac.size() < 8) {
|
||||
return types::evse_manager::CarManufacturer::Unknown;
|
||||
}
|
||||
|
||||
if (mac.substr(0, 8) == "00:7D:FA") {
|
||||
return types::evse_manager::CarManufacturer::VolkswagenGroup;
|
||||
}
|
||||
|
||||
if (tesla.count(mac.substr(0, 8)) > 0) {
|
||||
return types::evse_manager::CarManufacturer::Tesla;
|
||||
}
|
||||
|
||||
// Tesla also acquired a /28 sub-range, let's have a dedicated check for this
|
||||
// https://mac.lc/address/DC:44:27
|
||||
if (mac.substr(0, 10) == "DC:44:27:1") {
|
||||
return types::evse_manager::CarManufacturer::Tesla;
|
||||
}
|
||||
|
||||
return types::evse_manager::CarManufacturer::Unknown;
|
||||
}
|
||||
|
||||
} // namespace module
|
||||
@@ -0,0 +1,36 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
#ifndef CAR_MANUFACTURER_HPP
|
||||
#define CAR_MANUFACTURER_HPP
|
||||
|
||||
#include <generated/interfaces/evse_manager/Implementation.hpp>
|
||||
#include <string>
|
||||
|
||||
/*
|
||||
Not all manufacturers can be determined from the MAC addresses since they are using MAC addresses from the OBC
|
||||
manufacturer, so many will be Unknown.
|
||||
|
||||
used for mapping:
|
||||
|
||||
Tesla: 0C:29:8F, 4C:FC:AA, 54:F8:F0, 98:ED:5C,
|
||||
DC:44:27:1x:xx:xx/28
|
||||
VW: 00:7D:FA (used for all brands such as Skoda as well)
|
||||
|
||||
not used here since they might be in use in multiple cars:
|
||||
|
||||
Opel Ampera (GM): 04:4E:AF (LG Innotek, OBC manufacturer)
|
||||
Mercedes EQC: CC:88:26 (LG Innotek, OBC manufacturer)
|
||||
BMW: 00:01:A9 (seems not to be used by OBCs of BMW)
|
||||
BMW iX: 00:30:AB 22kW OBC (Delta Networks, Inc.)
|
||||
BMW i4: EC:65:CC 11kW OBC (Panasonic Automotive Systems Company of America)
|
||||
Polestar 2: 48:C5:8D Lear Corporation GmbH
|
||||
*/
|
||||
|
||||
namespace module {
|
||||
|
||||
// Helper function to determine the car manufacturer from MAC address
|
||||
types::evse_manager::CarManufacturer get_manufacturer_from_mac(const std::string& mac);
|
||||
|
||||
} // namespace module
|
||||
|
||||
#endif // CAR_MANUFACTURER_HPP
|
||||
2231
tools/EVerest-main/modules/EVSE/EvseManager/Charger.cpp
Normal file
2231
tools/EVerest-main/modules/EVSE/EvseManager/Charger.cpp
Normal file
File diff suppressed because it is too large
Load Diff
487
tools/EVerest-main/modules/EVSE/EvseManager/Charger.hpp
Normal file
487
tools/EVerest-main/modules/EVSE/EvseManager/Charger.hpp
Normal file
@@ -0,0 +1,487 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2021 Pionix GmbH and Contributors to EVerest
|
||||
/*
|
||||
* Charger.h
|
||||
*
|
||||
* Created on: 08.03.2021
|
||||
* Author: cornelius
|
||||
*
|
||||
* IEC 61851-1 compliant AC/DC high level charging logic
|
||||
*
|
||||
* This class provides:
|
||||
* 1) Hi level state machine that is controlled by a) events from evse_board_support interface
|
||||
* and b) by external commands from higher levels
|
||||
*
|
||||
* The state machine runs in its own (big) thread. After plugin,
|
||||
* The charger waits in state WaitingForAuthentication forever. Send
|
||||
* Authenticate()
|
||||
* from hi level to start charging. After car is unplugged, it waits in
|
||||
* ChargingFinished forever (or in an error state if an error happens during
|
||||
* charging).
|
||||
*/
|
||||
|
||||
#ifndef SRC_EVDRIVERS_CHARGER_H_
|
||||
#define SRC_EVDRIVERS_CHARGER_H_
|
||||
|
||||
#include "SessionLog.hpp"
|
||||
#include "ld-ev.hpp"
|
||||
#include "utils/thread.hpp"
|
||||
#include <chrono>
|
||||
#include <date/date.h>
|
||||
#include <date/tz.h>
|
||||
#include <generated/interfaces/ISO15118_charger/Interface.hpp>
|
||||
#include <generated/interfaces/powermeter/Interface.hpp>
|
||||
#include <generated/types/authorization.hpp>
|
||||
#include <generated/types/evse_manager.hpp>
|
||||
#include <generated/types/units_signed.hpp>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <queue>
|
||||
#include <sigslot/signal.hpp>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "ErrorHandling.hpp"
|
||||
#include "EventQueue.hpp"
|
||||
#include "IECStateMachine.hpp"
|
||||
#include "PersistentStore.hpp"
|
||||
#include "scoped_lock_timeout.hpp"
|
||||
#include "utils.hpp"
|
||||
|
||||
namespace module {
|
||||
|
||||
const std::string IEC62196Type2Cable = "IEC62196Type2Cable";
|
||||
const std::string IEC62196Type2Socket = "IEC62196Type2Socket";
|
||||
|
||||
class Charger {
|
||||
public:
|
||||
Charger(const std::unique_ptr<IECStateMachine>& bsp, const std::unique_ptr<ErrorHandling>& error_handling,
|
||||
const std::vector<std::unique_ptr<powermeterIntf>>& r_powermeter_billing,
|
||||
const std::unique_ptr<PersistentStore>& store,
|
||||
const types::evse_board_support::Connector_type& connector_type, const std::string& evse_id);
|
||||
~Charger();
|
||||
|
||||
enum class ChargeMode {
|
||||
AC,
|
||||
DC
|
||||
};
|
||||
|
||||
enum class EvseState {
|
||||
Disabled,
|
||||
Idle,
|
||||
WaitingForAuthentication,
|
||||
PrepareCharging,
|
||||
Charging,
|
||||
ChargingPausedEV,
|
||||
ChargingPausedEVSE,
|
||||
StoppingCharging,
|
||||
Finished,
|
||||
T_step_EF,
|
||||
T_step_X1,
|
||||
SwitchPhases
|
||||
};
|
||||
|
||||
enum class HlcTerminatePause {
|
||||
Unknown,
|
||||
Terminate,
|
||||
Pause
|
||||
};
|
||||
|
||||
// Public interface to configure Charger
|
||||
//
|
||||
// Call anytime also during charging, but call setters in this block at
|
||||
// least initially once.
|
||||
//
|
||||
|
||||
// external input to charger: update max_current and new validUntil
|
||||
bool set_max_current(float ampere, std::chrono::time_point<std::chrono::steady_clock> validUntil);
|
||||
float get_max_current();
|
||||
|
||||
sigslot::signal<float> signal_max_current;
|
||||
|
||||
void setup(bool has_ventilation, const ChargeMode charge_mode, bool ac_hlc_enabled, bool ac_hlc_use_5percent,
|
||||
bool ac_enforce_hlc, bool ac_with_soc_timeout, float soft_over_current_tolerance_percent,
|
||||
float soft_over_current_measurement_noise_A, const int switch_3ph1ph_delay_s,
|
||||
const std::string switch_3ph1ph_cp_state, const int soft_over_current_timeout_ms,
|
||||
const int _state_F_after_fault_ms, const bool fail_on_powermeter_errors, const bool raise_mrec9,
|
||||
const int sleep_before_enabling_pwm_hlc_mode_ms, const utils::SessionIdType session_id_type,
|
||||
const int hlc_charge_loop_without_energy_timeout_s);
|
||||
|
||||
void enable_disable_initial_state_publish();
|
||||
bool enable_disable(int connector_id, const types::evse_manager::EnableDisableSource& source);
|
||||
|
||||
// Public interface during charging
|
||||
//
|
||||
// Call anytime, but mostly used during charging session
|
||||
//
|
||||
|
||||
// Returns active session_uuid. Returns empty string if not session is active
|
||||
std::string get_session_id() const;
|
||||
|
||||
// call when in state WaitingForAuthentication
|
||||
void authorize(bool a, const types::authorization::ProvidedIdToken& token,
|
||||
const types::authorization::ValidationResult& result);
|
||||
bool deauthorize();
|
||||
|
||||
bool get_authorized_pnc();
|
||||
bool get_authorized_eim();
|
||||
|
||||
// this indicates the charger is done with all of its t_step_XX routines and HLC can now also start charging
|
||||
bool get_authorized_eim_ready_for_hlc();
|
||||
bool get_authorized_pnc_ready_for_hlc();
|
||||
|
||||
// trigger replug sequence while charging to switch number of phases
|
||||
bool switch_three_phases_while_charging(bool n);
|
||||
|
||||
bool pause_charging();
|
||||
bool resume_charging();
|
||||
|
||||
bool cancel_transaction(const types::evse_manager::StopTransactionRequest&
|
||||
request); // cancel transaction ahead of time when car is still plugged
|
||||
|
||||
void set_current_drawn_by_vehicle(float l1, float l2, float l3);
|
||||
|
||||
// Signal for EvseEvents
|
||||
sigslot::signal<types::evse_manager::SessionEventEnum> signal_simple_event;
|
||||
sigslot::signal<std::string> signal_session_resumed_event;
|
||||
sigslot::signal<types::evse_manager::StartSessionReason, std::optional<types::authorization::ProvidedIdToken>>
|
||||
signal_session_started_event;
|
||||
sigslot::signal<types::authorization::ProvidedIdToken> signal_transaction_started_event;
|
||||
sigslot::signal<types::evse_manager::StopTransactionReason, std::optional<types::authorization::ProvidedIdToken>>
|
||||
signal_transaction_finished_event;
|
||||
sigslot::signal<types::evse_manager::ChargingPausedEVSEReasons> signal_charging_paused_evse_event;
|
||||
|
||||
sigslot::signal<> signal_ac_with_soc_timeout;
|
||||
|
||||
sigslot::signal<> signal_dc_supply_off;
|
||||
sigslot::signal<types::iso15118::DcEvseMaximumLimits> signal_dc_enforce_target_limits;
|
||||
sigslot::signal<> signal_slac_reset;
|
||||
sigslot::signal<> signal_slac_start;
|
||||
|
||||
sigslot::signal<> signal_hlc_stop_charging;
|
||||
sigslot::signal<> signal_hlc_pause_charging;
|
||||
sigslot::signal<types::iso15118::EvseError> signal_hlc_error;
|
||||
sigslot::signal<> signal_hlc_plug_in_timeout;
|
||||
|
||||
sigslot::signal<> signal_hlc_no_energy_available;
|
||||
|
||||
void run();
|
||||
|
||||
void request_error_sequence();
|
||||
|
||||
void set_matching_started(bool m);
|
||||
|
||||
void notify_currentdemand_started();
|
||||
void reset_dc_enforce_target_limits_timer();
|
||||
|
||||
std::string evse_state_to_string(EvseState s);
|
||||
|
||||
EvseState get_current_state();
|
||||
sigslot::signal<EvseState> signal_state;
|
||||
|
||||
void inform_new_evse_max_hlc_limits(const types::iso15118::DcEvseMaximumLimits& l);
|
||||
types::iso15118::DcEvseMaximumLimits get_evse_max_hlc_limits();
|
||||
|
||||
void inform_new_evse_min_hlc_limits(const types::iso15118::DcEvseMinimumLimits& limits);
|
||||
types::iso15118::DcEvseMinimumLimits get_evse_min_hlc_limits();
|
||||
|
||||
void dlink_pause();
|
||||
void dlink_error();
|
||||
void dlink_terminate();
|
||||
|
||||
void set_hlc_charging_active();
|
||||
void set_hlc_allow_close_contactor(bool on);
|
||||
|
||||
void set_hlc_d20_active();
|
||||
|
||||
bool stop_charging_on_fatal_error();
|
||||
bool entered_fatal_error_state();
|
||||
int time_in_fatal_error_state_ms();
|
||||
|
||||
/// @brief Returns the OCMF start data.
|
||||
///
|
||||
/// The data is generated when starting the transaction. The call resets the
|
||||
/// internal variable and is thus not idempotent.
|
||||
std::optional<types::units_signed::SignedMeterValue> get_start_signed_meter_value();
|
||||
|
||||
/// @brief Returns the OCMF stop data.
|
||||
///
|
||||
/// The data is generated when stopping the transaction. The call resets the
|
||||
/// internal variable and is thus not idempotent.
|
||||
std::optional<types::units_signed::SignedMeterValue> get_stop_signed_meter_value();
|
||||
|
||||
types::evse_manager::EnableDisableSource get_last_enable_disable_source();
|
||||
|
||||
utils::Stopwatch& get_stopwatch() {
|
||||
return stopwatch;
|
||||
}
|
||||
|
||||
void set_connector_type(types::evse_board_support::Connector_type t) {
|
||||
connector_type = t;
|
||||
}
|
||||
|
||||
std::optional<types::evse_manager::StopTransactionReason> get_last_stop_transaction_reason();
|
||||
|
||||
void cleanup_transactions_on_startup();
|
||||
EventQueue<CPEvent> bsp_event_queue;
|
||||
|
||||
private:
|
||||
utils::Stopwatch stopwatch;
|
||||
|
||||
std::optional<types::units_signed::SignedMeterValue>
|
||||
take_signed_meter_data(std::optional<types::units_signed::SignedMeterValue>& data);
|
||||
|
||||
bool stop_charging_on_fatal_error_internal();
|
||||
float get_max_current_internal();
|
||||
float get_max_current_signalled_to_ev_internal();
|
||||
bool deauthorize_internal();
|
||||
bool pause_charging_wait_for_power_internal();
|
||||
|
||||
void bcb_toggle_reset();
|
||||
void bcb_toggle_detect_start_pulse();
|
||||
void bcb_toggle_detect_stop_pulse();
|
||||
bool bcb_toggle_detected();
|
||||
|
||||
void clear_errors_on_unplug();
|
||||
|
||||
void update_pwm_now(float duty_cycle);
|
||||
void update_pwm_now_if_changed(float duty_cycle);
|
||||
void update_pwm_now_if_changed_ampere(float duty_cycle);
|
||||
void update_pwm_max_every_5seconds_ampere(float duty_cycle);
|
||||
void cp_state_X1();
|
||||
void cp_state_F();
|
||||
|
||||
void process_cp_events_independent(CPEvent cp_event);
|
||||
void process_cp_events_state(CPEvent cp_event);
|
||||
|
||||
void main_thread();
|
||||
void error_thread();
|
||||
|
||||
void emergency_shutdown();
|
||||
void error_shutdown();
|
||||
|
||||
float ampere_to_duty_cycle(float ampere);
|
||||
|
||||
void check_soft_over_current();
|
||||
|
||||
bool power_available();
|
||||
|
||||
void start_session(bool authfirst);
|
||||
void stop_session();
|
||||
|
||||
bool start_transaction();
|
||||
void stop_transaction();
|
||||
|
||||
void process_event(CPEvent event);
|
||||
|
||||
void set_state(EvseState s);
|
||||
|
||||
// This mutex locks all variables related to the state machine
|
||||
Everest::timed_mutex_traceable state_machine_mutex;
|
||||
|
||||
// used by different threads, complete main loop must be locked for write access
|
||||
struct SharedContext {
|
||||
// As per IEC61851-1 A.5.3
|
||||
bool legacy_wakeup_done{false};
|
||||
bool hlc_allow_close_contactor{false};
|
||||
bool iec_allow_close_contactor{false};
|
||||
bool contactor_open{true};
|
||||
bool hlc_charging_active{false};
|
||||
HlcTerminatePause hlc_charging_terminate_pause;
|
||||
types::iso15118::DcEvseMaximumLimits current_evse_max_limits;
|
||||
types::iso15118::DcEvseMinimumLimits current_evse_min_limits;
|
||||
bool pwm_running{false};
|
||||
std::optional<types::authorization::ProvidedIdToken>
|
||||
stop_transaction_id_token; // only set in case transaction was stopped locally
|
||||
types::authorization::ProvidedIdToken id_token;
|
||||
types::authorization::ValidationResult validation_result;
|
||||
std::atomic_bool flag_authorized{false};
|
||||
std::atomic_bool flag_externally_cancelled{false};
|
||||
std::atomic_bool flag_paused_by_evse{false};
|
||||
std::atomic_bool flag_ev_plugged_in{false};
|
||||
// set to true if auth is from PnC, otherwise to false (EIM)
|
||||
bool authorized_pnc;
|
||||
bool matching_started;
|
||||
float max_current;
|
||||
std::chrono::time_point<std::chrono::steady_clock> max_current_valid_until;
|
||||
std::optional<double> max_current_cable;
|
||||
std::atomic_bool flag_transaction_active;
|
||||
bool session_active;
|
||||
std::string session_uuid;
|
||||
bool connector_enabled;
|
||||
// Set when disable is requested while a session/transaction is active.
|
||||
// Tells the state machine to transition to Disabled once the session is
|
||||
// properly terminated instead of returning to Idle.
|
||||
bool flag_disable_requested{false};
|
||||
EvseState current_state;
|
||||
std::optional<types::evse_manager::StopTransactionReason> last_stop_transaction_reason;
|
||||
types::evse_manager::StartSessionReason last_start_session_reason;
|
||||
float current_drawn_by_vehicle[3];
|
||||
ShutdownType shutdown_type{ShutdownType::None};
|
||||
ShutdownType last_shutdown_type{ShutdownType::None};
|
||||
int ac_with_soc_timer;
|
||||
// non standard compliant option: time out after a while and switch back to DC to get SoC update
|
||||
bool ac_with_soc_timeout;
|
||||
bool contactor_welded{false};
|
||||
bool switch_3ph1ph_threephase{false};
|
||||
bool switch_3ph1ph_threephase_ongoing{false};
|
||||
|
||||
std::optional<types::units_signed::SignedMeterValue> stop_signed_meter_value;
|
||||
std::optional<types::units_signed::SignedMeterValue> start_signed_meter_value;
|
||||
|
||||
std::atomic_bool hlc_d20_active{false};
|
||||
} shared_context;
|
||||
|
||||
struct ConfigContext {
|
||||
// non standard compliant option to enforce HLC in AC mode
|
||||
bool ac_enforce_hlc;
|
||||
// Config option to use 5 percent PWM in HLC AC mode
|
||||
bool ac_hlc_use_5percent;
|
||||
// Config option to enable HLC in AC mode
|
||||
bool ac_hlc_enabled;
|
||||
// AC or DC
|
||||
ChargeMode charge_mode{0};
|
||||
// Delay when switching from 1ph to 3ph or 3ph to 1ph
|
||||
int switch_3ph1ph_delay_s{10};
|
||||
// Use state F if true, otherwise use X1
|
||||
bool switch_3ph1ph_cp_state_F{false};
|
||||
// Tolerate soft over current for given time
|
||||
int soft_over_current_timeout_ms{7000};
|
||||
// Switch to F for configured ms after a fatal error
|
||||
int state_F_after_fault_ms{300};
|
||||
// Fail on powermeter errors
|
||||
bool fail_on_powermeter_errors;
|
||||
// Raise MREC9 authorization timeout error
|
||||
bool raise_mrec9;
|
||||
// sleep before enabling pwm in hlc mode
|
||||
int sleep_before_enabling_pwm_hlc_mode_ms{1000};
|
||||
// type used to generate session ids
|
||||
utils::SessionIdType session_id_type{utils::SessionIdType::UUID};
|
||||
// Timeout in seconds that defines for how long the EVSE allows the ISO charge loop (AC: ChargingStatus, DC:
|
||||
// CurrentDemand)
|
||||
int hlc_charge_loop_without_energy_timeout_s{300};
|
||||
} config_context;
|
||||
|
||||
// Used by different threads, but requires no complete state machine locking
|
||||
std::atomic<float> soft_over_current_tolerance_percent{10.};
|
||||
std::atomic<float> soft_over_current_measurement_noise_A{0.5};
|
||||
// HLC uses 5 percent signalling. Used both for AC and DC modes.
|
||||
std::atomic_bool hlc_use_5percent_current_session;
|
||||
// HLC enabled in current AC session. This can change during the session if e.g. HLC fails.
|
||||
std::atomic_bool ac_hlc_enabled_current_session;
|
||||
|
||||
// This struct is only used from main loop thread
|
||||
struct InternalContext {
|
||||
bool hlc_bcb_sequence_started{false};
|
||||
int hlc_ev_pause_bcb_count{0};
|
||||
std::chrono::time_point<std::chrono::steady_clock> hlc_ev_pause_start_of_bcb;
|
||||
std::chrono::time_point<std::chrono::steady_clock> hlc_ev_pause_start_of_bcb_sequence;
|
||||
float update_pwm_last_duty_cycle;
|
||||
std::chrono::time_point<std::chrono::steady_clock> last_pwm_update;
|
||||
|
||||
EvseState t_step_EF_return_state;
|
||||
float t_step_EF_return_pwm;
|
||||
float t_step_EF_return_ampere;
|
||||
|
||||
EvseState switching_phases_return_state;
|
||||
|
||||
EvseState t_step_X1_return_state;
|
||||
float t_step_X1_return_pwm;
|
||||
std::chrono::time_point<std::chrono::steady_clock> last_over_current_event;
|
||||
bool over_current{false};
|
||||
|
||||
ShutdownType last_shutdown_type{ShutdownType::None};
|
||||
std::chrono::system_clock::time_point current_state_started;
|
||||
EvseState last_state_detect_state_change;
|
||||
EvseState last_state;
|
||||
|
||||
bool pp_warning_printed{false};
|
||||
bool no_energy_warning_printed{false};
|
||||
float pwm_set_last_ampere{0};
|
||||
bool t_step_ef_x1_pause{false};
|
||||
bool cp_state_F_active{false};
|
||||
|
||||
bool ac_x1_fallback_nominal_timeout_running{false};
|
||||
std::chrono::time_point<std::chrono::steady_clock> ac_x1_fallback_nominal_timeout_started;
|
||||
bool auth_received_printed{false};
|
||||
|
||||
bool hlc_charge_loop_no_energy_timeout_running{false};
|
||||
std::chrono::time_point<std::chrono::steady_clock> iso_charge_loop_no_energy_timeout_started;
|
||||
|
||||
std::chrono::time_point<std::chrono::steady_clock> fatal_error_became_active;
|
||||
bool fatal_error_timer_running{false};
|
||||
bool dc_statistics_printed{false};
|
||||
|
||||
types::evse_manager::ChargingPausedEVSEReasons last_charging_paused_evse_reasons;
|
||||
} internal_context;
|
||||
|
||||
// main Charger thread
|
||||
Everest::Thread main_thread_handle;
|
||||
Everest::Thread error_thread_handle;
|
||||
|
||||
std::atomic<std::chrono::steady_clock::time_point> last_dc_enforce_target_limits{};
|
||||
|
||||
const std::unique_ptr<IECStateMachine>& bsp;
|
||||
const std::unique_ptr<ErrorHandling>& error_handling;
|
||||
const std::vector<std::unique_ptr<powermeterIntf>>& r_powermeter_billing;
|
||||
const std::unique_ptr<PersistentStore>& store;
|
||||
std::atomic<types::evse_board_support::Connector_type> connector_type{
|
||||
types::evse_board_support::Connector_type::IEC62196Type2Cable};
|
||||
const std::string evse_id;
|
||||
|
||||
EventQueue<ErrorHandlingEvents> error_handling_event_queue;
|
||||
|
||||
// constants
|
||||
constexpr static int LEGACY_WAKEUP_TIMEOUT{30000};
|
||||
constexpr static int PREPARING_TIMEOUT_PAUSED_BY_EV{10000};
|
||||
// valid Length of BCB toggles
|
||||
static constexpr auto TP_EV_VALD_STATE_DURATION_MIN =
|
||||
std::chrono::milliseconds(200 - 50); // We give 50 msecs tolerance to the norm values (table 3 ISO15118-3)
|
||||
static constexpr auto TP_EV_VALD_STATE_DURATION_MAX =
|
||||
std::chrono::milliseconds(400 + 50); // We give 50 msecs tolerance to the norm values (table 3 ISO15118-3)
|
||||
// Maximum duration of a BCB toggle sequence of 1-3 BCB toggles
|
||||
static constexpr auto TT_EVSE_VALD_TOGGLE =
|
||||
std::chrono::milliseconds(3500 + 200); // We give 200 msecs tolerance to the norm values (table 3 ISO15118-3)
|
||||
static constexpr auto MAINLOOP_UPDATE_RATE = std::chrono::milliseconds(100);
|
||||
static constexpr float PWM_5_PERCENT = 0.05;
|
||||
static constexpr int T_REPLUG_MS = 4000;
|
||||
// 3 seconds according to IEC61851-1
|
||||
static constexpr int T_STEP_X1 = 3000;
|
||||
// 4 seconds according to table 3 of ISO15118-3
|
||||
static constexpr int T_STEP_EF = 4000;
|
||||
static constexpr int IEC_PWM_MAX_UPDATE_INTERVAL = 5000;
|
||||
// EV READY certification requires a small pause of 500-1000 ms in X1 after a t_step_EF sequence before going to X2-
|
||||
// This is not required by IEC61851-1, but it is allowed by the IEC. It helps some older EVs to start charging
|
||||
// after the wake-up sequence.
|
||||
static constexpr int STAY_IN_X1_AFTER_TSTEP_EF_MS = 750;
|
||||
static constexpr int WAIT_FOR_ENERGY_IN_AUTHLOOP_TIMEOUT_MS = 5000;
|
||||
static constexpr int AC_X1_FALLBACK_TO_NOMINAL_TIMEOUT_MS = 10000;
|
||||
static constexpr int STOPPING_CHARGING_TIMEOUT_MS = 20000;
|
||||
// Ensures apply_new_target_voltage_current() is called at least every DC_ENFORCE_TARGET_LIMITS_INTERVAL_MS
|
||||
// during DC charging. This re-applies EVSE limits to the power supply even when the EV does not send
|
||||
// new target values or ignores updated limits from energy management.
|
||||
// The timer is reset whenever apply_new_target_voltage_current() is called from any source
|
||||
// (subscribe callback, energy manager, or this periodic signal).
|
||||
static constexpr int DC_ENFORCE_TARGET_LIMITS_INTERVAL_MS = 5000;
|
||||
|
||||
types::evse_manager::EnableDisableSource active_enable_disable_source{
|
||||
types::evse_manager::Enable_source::Unspecified, types::evse_manager::Enable_state::Unassigned, 10000};
|
||||
std::vector<types::evse_manager::EnableDisableSource> enable_disable_source_table;
|
||||
bool parse_enable_disable_source_table();
|
||||
void enable_disable_source_table_update(const types::evse_manager::EnableDisableSource& source);
|
||||
|
||||
protected:
|
||||
// provide access for unit tests
|
||||
void run_state_machine();
|
||||
constexpr auto& get_shared_context() {
|
||||
return shared_context;
|
||||
}
|
||||
constexpr const auto& get_enable_disable_source_table() const {
|
||||
return enable_disable_source_table;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace module
|
||||
|
||||
#endif // SRC_EVDRIVERS_CHARGER_H_
|
||||
372
tools/EVerest-main/modules/EVSE/EvseManager/ErrorHandling.cpp
Normal file
372
tools/EVerest-main/modules/EVSE/EvseManager/ErrorHandling.cpp
Normal file
@@ -0,0 +1,372 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2023 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include "ErrorHandling.hpp"
|
||||
|
||||
namespace {
|
||||
std::string generate_description(const Everest::error::Error& error) {
|
||||
std::string result;
|
||||
if (auto n = error.type.find('/'); n != std::string::npos) {
|
||||
// found slash '/'
|
||||
if ((n + 1) == error.type.size()) {
|
||||
// slash is the last character
|
||||
result = error.type.substr(0, n);
|
||||
} else {
|
||||
// remove upto and including the first /
|
||||
result = error.type.substr(++n);
|
||||
// remove slash at the end
|
||||
if (result.back() == '/') {
|
||||
result.pop_back();
|
||||
}
|
||||
}
|
||||
if (!error.sub_type.empty()) {
|
||||
result += '/' + error.sub_type;
|
||||
}
|
||||
} else {
|
||||
// return the original description
|
||||
result = error.description;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace module {
|
||||
|
||||
using ErrorList = std::list<Everest::error::ErrorType>;
|
||||
static const struct IgnoreErrors {
|
||||
// p_evse. We need to ignore Inoperative here as this is the result of this check.
|
||||
ErrorList evse{"evse_manager/Inoperative"};
|
||||
ErrorList bsp{"evse_board_support/MREC3HighTemperature", "evse_board_support/MREC18CableOverTempDerate",
|
||||
"evse_board_support/VendorWarning"};
|
||||
ErrorList connector_lock{"connector_lock/VendorWarning"};
|
||||
ErrorList ac_rcd{"ac_rcd/VendorWarning"};
|
||||
ErrorList imd{"isolation_monitor/VendorWarning"};
|
||||
ErrorList powersupply{"power_supply_DC/VendorWarning"};
|
||||
ErrorList powermeter{"generic/VendorWarning"};
|
||||
ErrorList over_voltage_monitor{"over_voltage_monitor/VendorWarning"};
|
||||
} ignore_errors;
|
||||
|
||||
ErrorHandling::ErrorHandling(const std::unique_ptr<evse_board_supportIntf>& _r_bsp,
|
||||
const std::vector<std::unique_ptr<ISO15118_chargerIntf>>& _r_hlc,
|
||||
const std::vector<std::unique_ptr<connector_lockIntf>>& _r_connector_lock,
|
||||
const std::vector<std::unique_ptr<ac_rcdIntf>>& _r_ac_rcd,
|
||||
const std::unique_ptr<evse_managerImplBase>& _p_evse,
|
||||
const std::vector<std::unique_ptr<isolation_monitorIntf>>& _r_imd,
|
||||
const std::vector<std::unique_ptr<power_supply_DCIntf>>& _r_powersupply,
|
||||
const std::vector<std::unique_ptr<powermeterIntf>>& _r_powermeter,
|
||||
const std::vector<std::unique_ptr<over_voltage_monitorIntf>>& _r_over_voltage_monitor,
|
||||
bool _inoperative_error_use_vendor_id) :
|
||||
r_bsp(_r_bsp),
|
||||
r_hlc(_r_hlc),
|
||||
r_connector_lock(_r_connector_lock),
|
||||
r_ac_rcd(_r_ac_rcd),
|
||||
p_evse(_p_evse),
|
||||
r_imd(_r_imd),
|
||||
r_powersupply(_r_powersupply),
|
||||
r_powermeter(_r_powermeter),
|
||||
r_over_voltage_monitor(_r_over_voltage_monitor),
|
||||
inoperative_error_use_vendor_id(_inoperative_error_use_vendor_id) {
|
||||
|
||||
// Subscribe to bsp driver to receive Errors from the bsp hardware
|
||||
r_bsp->subscribe_all_errors([this](const Everest::error::Error& error) { process_error(); },
|
||||
[this](const Everest::error::Error& error) { process_error(); });
|
||||
|
||||
// Subscribe to connector lock to receive errors from connector lock hardware
|
||||
if (r_connector_lock.size() > 0) {
|
||||
r_connector_lock[0]->subscribe_all_errors([this](const Everest::error::Error& error) { process_error(); },
|
||||
[this](const Everest::error::Error& error) { process_error(); });
|
||||
}
|
||||
|
||||
// Subscribe to ac_rcd to receive errors from AC RCD hardware
|
||||
if (r_ac_rcd.size() > 0) {
|
||||
r_ac_rcd[0]->subscribe_all_errors([this](const Everest::error::Error& error) { process_error(); },
|
||||
[this](const Everest::error::Error& error) { process_error(); });
|
||||
}
|
||||
|
||||
// Subscribe to ac_rcd to receive errors from IMD hardware
|
||||
if (r_imd.size() > 0) {
|
||||
r_imd[0]->subscribe_all_errors([this](const Everest::error::Error& error) { process_error(); },
|
||||
[this](const Everest::error::Error& error) { process_error(); });
|
||||
}
|
||||
|
||||
// Subscribe to powersupply to receive errors from DC powersupply hardware
|
||||
if (r_powersupply.size() > 0) {
|
||||
r_powersupply[0]->subscribe_all_errors([this](const Everest::error::Error& error) { process_error(); },
|
||||
[this](const Everest::error::Error& error) { process_error(); });
|
||||
}
|
||||
|
||||
// Subscribe to powermeter to receive errors from powermeter hardware
|
||||
if (r_powermeter.size() > 0) {
|
||||
r_powermeter[0]->subscribe_all_errors([this](const Everest::error::Error& error) { process_error(); },
|
||||
[this](const Everest::error::Error& error) { process_error(); });
|
||||
}
|
||||
|
||||
// Subscribe to over_voltage_monitor to receive errors from over voltage monitor hardware
|
||||
if (r_over_voltage_monitor.size() > 0) {
|
||||
r_over_voltage_monitor[0]->subscribe_all_errors(
|
||||
[this](const Everest::error::Error& error) { process_error(); },
|
||||
[this](const Everest::error::Error& error) { process_error(); });
|
||||
}
|
||||
}
|
||||
|
||||
void ErrorHandling::raise_overcurrent_error(const std::string& description) {
|
||||
// raise externally
|
||||
// Emergency shutdown according to IEC61851-23 Table CC.10 --> Severity::High
|
||||
Everest::error::Error error_object = p_evse->error_factory->create_error(
|
||||
"evse_manager/MREC4OverCurrentFailure", "", description, Everest::error::Severity::High);
|
||||
p_evse->raise_error(error_object);
|
||||
process_error();
|
||||
}
|
||||
|
||||
void ErrorHandling::clear_overcurrent_error() {
|
||||
// clear externally
|
||||
if (p_evse->error_state_monitor->is_error_active("evse_manager/MREC4OverCurrentFailure", "")) {
|
||||
p_evse->clear_error("evse_manager/MREC4OverCurrentFailure", "");
|
||||
}
|
||||
process_error();
|
||||
}
|
||||
|
||||
void ErrorHandling::raise_over_voltage_error(Everest::error::Severity severity, const std::string& description) {
|
||||
Everest::error::Error error_object =
|
||||
p_evse->error_factory->create_error("evse_manager/MREC5OverVoltage", "", description, severity);
|
||||
p_evse->raise_error(error_object);
|
||||
process_error();
|
||||
}
|
||||
|
||||
void ErrorHandling::clear_over_voltage_error() {
|
||||
if (p_evse->error_state_monitor->is_error_active("evse_manager/MREC5OverVoltage", "")) {
|
||||
p_evse->clear_error("evse_manager/MREC5OverVoltage", "");
|
||||
}
|
||||
process_error();
|
||||
}
|
||||
|
||||
// Find out if the current error set is fatal to charging or not
|
||||
void ErrorHandling::process_error() {
|
||||
const auto fatal = errors_prevent_charging();
|
||||
if (fatal) {
|
||||
// signal to charger a new error has been set that prevents charging
|
||||
raise_inoperative_error(*fatal);
|
||||
} else {
|
||||
// signal an error that does not prevent charging
|
||||
clear_inoperative_error();
|
||||
}
|
||||
|
||||
// All errors cleared signal is for OCPP 1.6. It is triggered when there are no errors anymore,
|
||||
// even those that did not block charging.
|
||||
|
||||
auto number_of_active_errors = [](const auto& impl) {
|
||||
if (impl.size() > 0) {
|
||||
return static_cast<int>(impl[0]->error_state_monitor->get_active_errors().size());
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
const int error_count = p_evse->error_state_monitor->get_active_errors().size() +
|
||||
r_bsp->error_state_monitor->get_active_errors().size() +
|
||||
number_of_active_errors(r_connector_lock) + number_of_active_errors(r_ac_rcd) +
|
||||
number_of_active_errors(r_imd) + number_of_active_errors(r_powersupply) +
|
||||
number_of_active_errors(r_powermeter);
|
||||
|
||||
if (error_count == 0) {
|
||||
signal_all_errors_cleared();
|
||||
}
|
||||
}
|
||||
|
||||
// Check all errors from p_evse and all requirements to see if they block charging
|
||||
std::optional<Everest::error::Error> ErrorHandling::errors_prevent_charging() {
|
||||
|
||||
auto is_fatal = [](auto errors, auto ignore_list) -> std::optional<Everest::error::Error> {
|
||||
for (const auto& e : errors) {
|
||||
if (std::none_of(ignore_list.begin(), ignore_list.end(), [e](const auto& ign) { return e->type == ign; })) {
|
||||
return *e;
|
||||
}
|
||||
}
|
||||
return std::nullopt;
|
||||
};
|
||||
|
||||
auto fatal = is_fatal(p_evse->error_state_monitor->get_active_errors(), ignore_errors.evse);
|
||||
if (fatal) {
|
||||
return fatal;
|
||||
}
|
||||
|
||||
fatal = is_fatal(r_bsp->error_state_monitor->get_active_errors(), ignore_errors.bsp);
|
||||
if (fatal) {
|
||||
return fatal;
|
||||
}
|
||||
|
||||
if (r_connector_lock.size() > 0) {
|
||||
fatal = is_fatal(r_connector_lock[0]->error_state_monitor->get_active_errors(), ignore_errors.connector_lock);
|
||||
if (fatal) {
|
||||
return fatal;
|
||||
}
|
||||
}
|
||||
|
||||
if (r_ac_rcd.size() > 0) {
|
||||
fatal = is_fatal(r_ac_rcd[0]->error_state_monitor->get_active_errors(), ignore_errors.ac_rcd);
|
||||
if (fatal) {
|
||||
return fatal;
|
||||
}
|
||||
}
|
||||
|
||||
if (r_imd.size() > 0) {
|
||||
fatal = is_fatal(r_imd[0]->error_state_monitor->get_active_errors(), ignore_errors.imd);
|
||||
if (fatal) {
|
||||
return fatal;
|
||||
}
|
||||
}
|
||||
|
||||
if (r_powersupply.size() > 0) {
|
||||
fatal = is_fatal(r_powersupply[0]->error_state_monitor->get_active_errors(), ignore_errors.powersupply);
|
||||
if (fatal) {
|
||||
return fatal;
|
||||
}
|
||||
}
|
||||
|
||||
if (r_powermeter.size() > 0) {
|
||||
fatal = is_fatal(r_powermeter[0]->error_state_monitor->get_active_errors(), ignore_errors.powermeter);
|
||||
if (fatal) {
|
||||
return fatal;
|
||||
}
|
||||
}
|
||||
|
||||
if (r_over_voltage_monitor.size() > 0) {
|
||||
fatal = is_fatal(r_over_voltage_monitor[0]->error_state_monitor->get_active_errors(),
|
||||
ignore_errors.over_voltage_monitor);
|
||||
if (fatal) {
|
||||
return fatal;
|
||||
}
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
void ErrorHandling::raise_inoperative_error(const Everest::error::Error& caused_by) {
|
||||
if (p_evse->error_state_monitor->is_error_active("evse_manager/Inoperative", "")) {
|
||||
// dont raise if already raised
|
||||
return;
|
||||
}
|
||||
|
||||
// raise externally
|
||||
Everest::error::Error error_object = p_evse->error_factory->create_error(
|
||||
"evse_manager/Inoperative", "", caused_by.type, Everest::error::Severity::High);
|
||||
error_object.description = generate_description(caused_by);
|
||||
if (inoperative_error_use_vendor_id && !caused_by.vendor_id.empty()) {
|
||||
error_object.vendor_id = caused_by.vendor_id;
|
||||
} else {
|
||||
error_object.vendor_id = "EVerest";
|
||||
}
|
||||
p_evse->raise_error(error_object);
|
||||
|
||||
// shutdown based on severity
|
||||
if (caused_by.severity == Everest::error::Severity::High) {
|
||||
signal_error(ErrorHandlingEvents::ForceEmergencyShutdown);
|
||||
} else {
|
||||
signal_error(ErrorHandlingEvents::ForceErrorShutdown);
|
||||
}
|
||||
}
|
||||
|
||||
void ErrorHandling::clear_inoperative_error() {
|
||||
// clear externally
|
||||
if (p_evse->error_state_monitor->is_error_active("evse_manager/Inoperative", "")) {
|
||||
p_evse->clear_error("evse_manager/Inoperative");
|
||||
signal_error(ErrorHandlingEvents::AllErrorsPreventingChargingCleared);
|
||||
}
|
||||
}
|
||||
|
||||
void ErrorHandling::raise_internal_error(const std::string& description) {
|
||||
// raise externally
|
||||
Everest::error::Error error_object =
|
||||
p_evse->error_factory->create_error("evse_manager/Internal", "", description, Everest::error::Severity::High);
|
||||
p_evse->raise_error(error_object);
|
||||
process_error();
|
||||
}
|
||||
|
||||
void ErrorHandling::clear_internal_error() {
|
||||
// clear externally
|
||||
if (p_evse->error_state_monitor->is_error_active("evse_manager/Internal", "")) {
|
||||
p_evse->clear_error("evse_manager/Internal");
|
||||
process_error();
|
||||
}
|
||||
}
|
||||
|
||||
void ErrorHandling::raise_authorization_timeout_error(const std::string& description) {
|
||||
// raise externally
|
||||
Everest::error::Error error_object = p_evse->error_factory->create_error(
|
||||
"evse_manager/MREC9AuthorizationTimeout", "", description, Everest::error::Severity::Medium);
|
||||
p_evse->raise_error(error_object);
|
||||
process_error();
|
||||
}
|
||||
|
||||
void ErrorHandling::clear_authorization_timeout_error() {
|
||||
// clear externally
|
||||
if (p_evse->error_state_monitor->is_error_active("evse_manager/MREC9AuthorizationTimeout", "")) {
|
||||
p_evse->clear_error("evse_manager/MREC9AuthorizationTimeout");
|
||||
process_error();
|
||||
}
|
||||
}
|
||||
|
||||
void ErrorHandling::raise_powermeter_transaction_start_failed_error(const std::string& description) {
|
||||
// raise externally
|
||||
Everest::error::Error error_object = p_evse->error_factory->create_error(
|
||||
"evse_manager/PowermeterTransactionStartFailed", "", description, Everest::error::Severity::Medium);
|
||||
p_evse->raise_error(error_object);
|
||||
process_error();
|
||||
}
|
||||
|
||||
void ErrorHandling::clear_powermeter_transaction_start_failed_error() {
|
||||
// clear externally
|
||||
if (p_evse->error_state_monitor->is_error_active("evse_manager/PowermeterTransactionStartFailed", "")) {
|
||||
p_evse->clear_error("evse_manager/PowermeterTransactionStartFailed");
|
||||
process_error();
|
||||
}
|
||||
}
|
||||
|
||||
void ErrorHandling::raise_isolation_resistance_fault(const std::string& description, const std::string& sub_type) {
|
||||
// Error shutdown according to IEC61851-23 Table CC.10 --> Severity::Medium
|
||||
Everest::error::Error error_object = p_evse->error_factory->create_error(
|
||||
"evse_manager/MREC22ResistanceFault", sub_type, description, Everest::error::Severity::Medium);
|
||||
p_evse->raise_error(error_object);
|
||||
process_error();
|
||||
}
|
||||
|
||||
void ErrorHandling::clear_isolation_resistance_fault(const std::string& sub_type) {
|
||||
if (p_evse->error_state_monitor->is_error_active("evse_manager/MREC22ResistanceFault", sub_type)) {
|
||||
p_evse->clear_error("evse_manager/MREC22ResistanceFault", sub_type);
|
||||
process_error();
|
||||
}
|
||||
}
|
||||
|
||||
void ErrorHandling::raise_cable_check_fault(const std::string& description) {
|
||||
// Error shutdown according to IEC61851-23 Table CC.10 --> Severity::Medium
|
||||
Everest::error::Error error_object = p_evse->error_factory->create_error(
|
||||
"evse_manager/MREC11CableCheckFault", "Self test failed", description, Everest::error::Severity::Medium);
|
||||
p_evse->raise_error(error_object);
|
||||
process_error();
|
||||
}
|
||||
|
||||
void ErrorHandling::clear_cable_check_fault() {
|
||||
if (p_evse->error_state_monitor->is_error_active("evse_manager/MREC11CableCheckFault", "Self test failed")) {
|
||||
p_evse->clear_error("evse_manager/MREC11CableCheckFault", "Self test failed");
|
||||
process_error();
|
||||
}
|
||||
}
|
||||
|
||||
void ErrorHandling::raise_voltage_plausibility_fault(const std::string& description) {
|
||||
// raise externally
|
||||
// High severity as this indicates a serious measurement inconsistency
|
||||
Everest::error::Error error_object = p_evse->error_factory->create_error(
|
||||
"evse_manager/VoltagePlausibilityFault", "", description, Everest::error::Severity::High);
|
||||
p_evse->raise_error(error_object);
|
||||
process_error();
|
||||
}
|
||||
|
||||
void ErrorHandling::clear_voltage_plausibility_fault() {
|
||||
// clear externally
|
||||
if (p_evse->error_state_monitor->is_error_active("evse_manager/VoltagePlausibilityFault", "")) {
|
||||
p_evse->clear_error("evse_manager/VoltagePlausibilityFault");
|
||||
process_error();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace module
|
||||
125
tools/EVerest-main/modules/EVSE/EvseManager/ErrorHandling.hpp
Normal file
125
tools/EVerest-main/modules/EVSE/EvseManager/ErrorHandling.hpp
Normal file
@@ -0,0 +1,125 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2021 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
/*
|
||||
The ErrorHandling class handles all errors from BSP/ConnectorLock etc
|
||||
and classifies them for charging:
|
||||
|
||||
If we can continue charging despite the error, we treat them as warnings and do not track them here. If e.g. the BSP
|
||||
raises a high temperature error it needs to limit the output power by itself.
|
||||
|
||||
The decision whether an error requires a replug or not to clear depends on the reporting module. It will need to clear
|
||||
them at the appropriate time.
|
||||
*/
|
||||
|
||||
#ifndef SRC_ERROR_HANDLING_H_
|
||||
#define SRC_ERROR_HANDLING_H_
|
||||
|
||||
#include "ld-ev.hpp"
|
||||
|
||||
#include <chrono>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <queue>
|
||||
|
||||
#include <generated/interfaces/ISO15118_charger/Interface.hpp>
|
||||
#include <generated/interfaces/ac_rcd/Interface.hpp>
|
||||
#include <generated/interfaces/connector_lock/Interface.hpp>
|
||||
#include <generated/interfaces/evse_board_support/Interface.hpp>
|
||||
#include <generated/interfaces/evse_manager/Implementation.hpp>
|
||||
#include <generated/interfaces/evse_manager/Interface.hpp>
|
||||
#include <generated/interfaces/isolation_monitor/Interface.hpp>
|
||||
#include <generated/interfaces/over_voltage_monitor/Interface.hpp>
|
||||
#include <generated/interfaces/power_supply_DC/Interface.hpp>
|
||||
#include <generated/interfaces/powermeter/Interface.hpp>
|
||||
#include <sigslot/signal.hpp>
|
||||
|
||||
#include "Timeout.hpp"
|
||||
#include "utils/thread.hpp"
|
||||
|
||||
namespace module {
|
||||
|
||||
class EvseManager;
|
||||
|
||||
// ErrorHandling events
|
||||
enum class ErrorHandlingEvents : std::uint8_t {
|
||||
ForceErrorShutdown,
|
||||
ForceEmergencyShutdown,
|
||||
AllErrorsPreventingChargingCleared,
|
||||
AllErrorCleared
|
||||
};
|
||||
|
||||
enum class ShutdownType {
|
||||
None,
|
||||
ErrorShutdown,
|
||||
EmergencyShutdown
|
||||
};
|
||||
|
||||
class ErrorHandling {
|
||||
|
||||
public:
|
||||
// We need the r_bsp reference to be able to talk to the bsp driver module
|
||||
explicit ErrorHandling(const std::unique_ptr<evse_board_supportIntf>& r_bsp,
|
||||
const std::vector<std::unique_ptr<ISO15118_chargerIntf>>& r_hlc,
|
||||
const std::vector<std::unique_ptr<connector_lockIntf>>& r_connector_lock,
|
||||
const std::vector<std::unique_ptr<ac_rcdIntf>>& r_ac_rcd,
|
||||
const std::unique_ptr<evse_managerImplBase>& _p_evse,
|
||||
const std::vector<std::unique_ptr<isolation_monitorIntf>>& _r_imd,
|
||||
const std::vector<std::unique_ptr<power_supply_DCIntf>>& _r_powersupply,
|
||||
const std::vector<std::unique_ptr<powermeterIntf>>& _r_powermeter,
|
||||
const std::vector<std::unique_ptr<over_voltage_monitorIntf>>& _r_over_voltage_monitor,
|
||||
bool _inoperative_error_use_vendor_id);
|
||||
|
||||
// Signal that error set has changed. Bool argument is true if it is preventing charging at the moment and false if
|
||||
// charging can continue.
|
||||
sigslot::signal<ErrorHandlingEvents> signal_error;
|
||||
// Signal that all errors are cleared (both those preventing charging and not)
|
||||
sigslot::signal<> signal_all_errors_cleared;
|
||||
|
||||
void raise_overcurrent_error(const std::string& description);
|
||||
void clear_overcurrent_error();
|
||||
|
||||
void raise_over_voltage_error(Everest::error::Severity severity, const std::string& description);
|
||||
void clear_over_voltage_error();
|
||||
|
||||
void raise_internal_error(const std::string& description);
|
||||
void clear_internal_error();
|
||||
|
||||
void raise_authorization_timeout_error(const std::string& description);
|
||||
void clear_authorization_timeout_error();
|
||||
|
||||
void raise_powermeter_transaction_start_failed_error(const std::string& description);
|
||||
void clear_powermeter_transaction_start_failed_error();
|
||||
|
||||
void raise_isolation_resistance_fault(const std::string& description, const std::string& sub_type);
|
||||
void clear_isolation_resistance_fault(const std::string& sub_type);
|
||||
|
||||
void raise_cable_check_fault(const std::string& description);
|
||||
void clear_cable_check_fault();
|
||||
|
||||
void raise_voltage_plausibility_fault(const std::string& description);
|
||||
void clear_voltage_plausibility_fault();
|
||||
|
||||
protected:
|
||||
void raise_inoperative_error(const Everest::error::Error& caused_by);
|
||||
|
||||
private:
|
||||
void process_error();
|
||||
void clear_inoperative_error();
|
||||
std::optional<Everest::error::Error> errors_prevent_charging();
|
||||
|
||||
const std::unique_ptr<evse_board_supportIntf>& r_bsp;
|
||||
const std::vector<std::unique_ptr<ISO15118_chargerIntf>>& r_hlc;
|
||||
const std::vector<std::unique_ptr<connector_lockIntf>>& r_connector_lock;
|
||||
const std::vector<std::unique_ptr<ac_rcdIntf>>& r_ac_rcd;
|
||||
const std::unique_ptr<evse_managerImplBase>& p_evse;
|
||||
const std::vector<std::unique_ptr<isolation_monitorIntf>>& r_imd;
|
||||
const std::vector<std::unique_ptr<power_supply_DCIntf>>& r_powersupply;
|
||||
const std::vector<std::unique_ptr<powermeterIntf>>& r_powermeter;
|
||||
const std::vector<std::unique_ptr<over_voltage_monitorIntf>>& r_over_voltage_monitor;
|
||||
const bool inoperative_error_use_vendor_id;
|
||||
};
|
||||
|
||||
} // namespace module
|
||||
|
||||
#endif // SRC_BSP_STATE_MACHINE_H_
|
||||
60
tools/EVerest-main/modules/EVSE/EvseManager/EventQueue.hpp
Normal file
60
tools/EVerest-main/modules/EVSE/EvseManager/EventQueue.hpp
Normal file
@@ -0,0 +1,60 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
#ifndef EVENTQUEUE_HPP
|
||||
#define EVENTQUEUE_HPP
|
||||
|
||||
#include <chrono>
|
||||
#include <condition_variable>
|
||||
#include <cstdint>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
|
||||
namespace module {
|
||||
|
||||
template <typename E> class EventQueue {
|
||||
public:
|
||||
using events_t = std::vector<E>;
|
||||
|
||||
private:
|
||||
events_t pending;
|
||||
std::mutex mux;
|
||||
std::condition_variable cv;
|
||||
|
||||
public:
|
||||
void push(const E& event) {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mux);
|
||||
pending.push_back(event);
|
||||
}
|
||||
cv.notify_all();
|
||||
}
|
||||
|
||||
events_t get_events() {
|
||||
std::lock_guard<std::mutex> lock(mux);
|
||||
events_t active;
|
||||
pending.swap(active);
|
||||
return active;
|
||||
}
|
||||
|
||||
events_t wait() {
|
||||
std::unique_lock<std::mutex> ul(mux);
|
||||
cv.wait(ul, [this]() { return !pending.empty(); });
|
||||
events_t active;
|
||||
pending.swap(active);
|
||||
ul.unlock();
|
||||
return active;
|
||||
}
|
||||
|
||||
template <class Rep, class Period> events_t wait_for(const std::chrono::duration<Rep, Period>& rel_time) {
|
||||
std::unique_lock<std::mutex> ul(mux);
|
||||
if (!cv.wait_for(ul, rel_time, [this]() { return !pending.empty(); })) {
|
||||
return {};
|
||||
}
|
||||
events_t active;
|
||||
pending.swap(active);
|
||||
return active;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace module
|
||||
#endif
|
||||
2707
tools/EVerest-main/modules/EVSE/EvseManager/EvseManager.cpp
Normal file
2707
tools/EVerest-main/modules/EVSE/EvseManager/EvseManager.cpp
Normal file
File diff suppressed because it is too large
Load Diff
440
tools/EVerest-main/modules/EVSE/EvseManager/EvseManager.hpp
Normal file
440
tools/EVerest-main/modules/EVSE/EvseManager/EvseManager.hpp
Normal file
@@ -0,0 +1,440 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
#ifndef EVSE_MANAGER_HPP
|
||||
#define EVSE_MANAGER_HPP
|
||||
|
||||
//
|
||||
// AUTO GENERATED - MARKED REGIONS WILL BE KEPT
|
||||
// template version 2
|
||||
//
|
||||
|
||||
#include "ld-ev.hpp"
|
||||
|
||||
// headers for provided interface implementations
|
||||
#include <generated/interfaces/auth_token_provider/Implementation.hpp>
|
||||
#include <generated/interfaces/dc_external_derate/Implementation.hpp>
|
||||
#include <generated/interfaces/energy/Implementation.hpp>
|
||||
#include <generated/interfaces/evse_manager/Implementation.hpp>
|
||||
#include <generated/interfaces/uk_random_delay/Implementation.hpp>
|
||||
|
||||
// headers for required interface implementations
|
||||
#include <generated/interfaces/ISO15118_charger/Interface.hpp>
|
||||
#include <generated/interfaces/ac_rcd/Interface.hpp>
|
||||
#include <generated/interfaces/connector_lock/Interface.hpp>
|
||||
#include <generated/interfaces/evse_board_support/Interface.hpp>
|
||||
#include <generated/interfaces/isolation_monitor/Interface.hpp>
|
||||
#include <generated/interfaces/kvs/Interface.hpp>
|
||||
#include <generated/interfaces/over_voltage_monitor/Interface.hpp>
|
||||
#include <generated/interfaces/power_supply_DC/Interface.hpp>
|
||||
#include <generated/interfaces/powermeter/Interface.hpp>
|
||||
#include <generated/interfaces/slac/Interface.hpp>
|
||||
|
||||
// ev@4bf81b14-a215-475c-a1d3-0a484ae48918:v1
|
||||
// insert your custom include headers here
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <condition_variable>
|
||||
#include <ctime>
|
||||
#include <date/date.h>
|
||||
#include <date/tz.h>
|
||||
#include <future>
|
||||
#include <iostream>
|
||||
#include <optional>
|
||||
|
||||
#include "CarManufacturer.hpp"
|
||||
#include "Charger.hpp"
|
||||
#include "ErrorHandling.hpp"
|
||||
#include "PersistentStore.hpp"
|
||||
#include "SessionLog.hpp"
|
||||
#include "VarContainer.hpp"
|
||||
#include "over_voltage/OverVoltageMonitor.hpp"
|
||||
#include "scoped_lock_timeout.hpp"
|
||||
#include "voltage_plausibility/VoltagePlausibilityMonitor.hpp"
|
||||
#include <everest/util/async/monitor.hpp>
|
||||
// ev@4bf81b14-a215-475c-a1d3-0a484ae48918:v1
|
||||
|
||||
namespace module {
|
||||
|
||||
struct Conf {
|
||||
int connector_id;
|
||||
std::string connector_type;
|
||||
std::string evse_id;
|
||||
std::string evse_id_din;
|
||||
bool payment_enable_eim;
|
||||
bool payment_enable_contract;
|
||||
double ac_nominal_voltage;
|
||||
bool ev_receipt_required;
|
||||
bool session_logging;
|
||||
std::string session_logging_path;
|
||||
bool session_logging_xml;
|
||||
bool has_ventilation;
|
||||
std::string charge_mode;
|
||||
bool supported_iso_ac_bpt;
|
||||
bool ac_hlc_enabled;
|
||||
bool ac_hlc_use_5percent;
|
||||
bool ac_enforce_hlc;
|
||||
bool ac_with_soc;
|
||||
int internal_over_voltage_duration_ms;
|
||||
bool dbg_hlc_auth_after_tstep;
|
||||
int dc_isolation_voltage_V;
|
||||
int cable_check_wait_number_of_imd_measurements;
|
||||
bool cable_check_enable_imd_self_test;
|
||||
bool cable_check_enable_imd_self_test_relays_open;
|
||||
int cable_check_relays_open_voltage_V;
|
||||
int cable_check_relays_closed_timeout_s;
|
||||
bool cable_check_wait_below_60V_before_finish;
|
||||
bool hack_skoda_enyaq;
|
||||
int hack_present_current_offset;
|
||||
bool hack_pause_imd_during_precharge;
|
||||
bool hack_allow_bpt_with_iso2;
|
||||
bool hack_simplified_mode_limit_10A;
|
||||
bool autocharge_use_slac_instead_of_hlc;
|
||||
bool enable_autocharge;
|
||||
std::string logfile_suffix;
|
||||
double soft_over_current_tolerance_percent;
|
||||
double soft_over_current_measurement_noise_A;
|
||||
bool hack_fix_hlc_integer_current_requests;
|
||||
bool disable_authentication;
|
||||
bool sae_j2847_2_bpt_enabled;
|
||||
std::string sae_j2847_2_bpt_mode;
|
||||
bool request_zero_power_in_idle;
|
||||
bool external_ready_to_start_charging;
|
||||
bool uk_smartcharging_random_delay_enable;
|
||||
int uk_smartcharging_random_delay_max_duration;
|
||||
bool uk_smartcharging_random_delay_at_any_change;
|
||||
int initial_meter_value_timeout_ms;
|
||||
int switch_3ph1ph_delay_s;
|
||||
std::string switch_3ph1ph_cp_state;
|
||||
int soft_over_current_timeout_ms;
|
||||
bool lock_connector_in_state_b;
|
||||
int state_F_after_fault_ms;
|
||||
bool fail_on_powermeter_errors;
|
||||
bool raise_mrec9;
|
||||
int sleep_before_enabling_pwm_hlc_mode_ms;
|
||||
bool central_contract_validation_allowed;
|
||||
bool contract_certificate_installation_enabled;
|
||||
bool inoperative_error_use_vendor_id;
|
||||
double voltage_plausibility_max_spread_threshold_V;
|
||||
int voltage_plausibility_fault_duration_ms;
|
||||
std::string session_id_type;
|
||||
bool zero_power_ignore_pause;
|
||||
bool zero_power_allow_ev_to_ignore_pause;
|
||||
std::string bpt_channel;
|
||||
std::string bpt_generator_mode;
|
||||
std::string bpt_grid_code_island_method;
|
||||
int hlc_charge_loop_without_energy_timeout_s;
|
||||
int dc_ramp_ampere_per_second;
|
||||
};
|
||||
|
||||
class EvseManager : public Everest::ModuleBase {
|
||||
public:
|
||||
EvseManager() = delete;
|
||||
EvseManager(const ModuleInfo& info, Everest::MqttProvider& mqtt_provider, Everest::TelemetryProvider& telemetry,
|
||||
std::unique_ptr<evse_managerImplBase> p_evse, std::unique_ptr<energyImplBase> p_energy_grid,
|
||||
std::unique_ptr<auth_token_providerImplBase> p_token_provider,
|
||||
std::unique_ptr<uk_random_delayImplBase> p_random_delay,
|
||||
std::unique_ptr<dc_external_derateImplBase> p_dc_external_derate,
|
||||
std::unique_ptr<evse_board_supportIntf> r_bsp, std::vector<std::unique_ptr<ac_rcdIntf>> r_ac_rcd,
|
||||
std::vector<std::unique_ptr<connector_lockIntf>> r_connector_lock,
|
||||
std::vector<std::unique_ptr<powermeterIntf>> r_powermeter_grid_side,
|
||||
std::vector<std::unique_ptr<powermeterIntf>> r_powermeter_car_side,
|
||||
std::vector<std::unique_ptr<slacIntf>> r_slac, std::vector<std::unique_ptr<ISO15118_chargerIntf>> r_hlc,
|
||||
std::vector<std::unique_ptr<isolation_monitorIntf>> r_imd,
|
||||
std::vector<std::unique_ptr<over_voltage_monitorIntf>> r_over_voltage_monitor,
|
||||
std::vector<std::unique_ptr<power_supply_DCIntf>> r_powersupply_DC,
|
||||
std::vector<std::unique_ptr<kvsIntf>> r_store, Conf& config) :
|
||||
ModuleBase(info),
|
||||
mqtt(mqtt_provider),
|
||||
telemetry(telemetry),
|
||||
p_evse(std::move(p_evse)),
|
||||
p_energy_grid(std::move(p_energy_grid)),
|
||||
p_token_provider(std::move(p_token_provider)),
|
||||
p_random_delay(std::move(p_random_delay)),
|
||||
p_dc_external_derate(std::move(p_dc_external_derate)),
|
||||
r_bsp(std::move(r_bsp)),
|
||||
r_ac_rcd(std::move(r_ac_rcd)),
|
||||
r_connector_lock(std::move(r_connector_lock)),
|
||||
r_powermeter_grid_side(std::move(r_powermeter_grid_side)),
|
||||
r_powermeter_car_side(std::move(r_powermeter_car_side)),
|
||||
r_slac(std::move(r_slac)),
|
||||
r_hlc(std::move(r_hlc)),
|
||||
r_imd(std::move(r_imd)),
|
||||
r_over_voltage_monitor(std::move(r_over_voltage_monitor)),
|
||||
r_powersupply_DC(std::move(r_powersupply_DC)),
|
||||
r_store(std::move(r_store)),
|
||||
config(config){};
|
||||
|
||||
Everest::MqttProvider& mqtt;
|
||||
Everest::TelemetryProvider& telemetry;
|
||||
const std::unique_ptr<evse_managerImplBase> p_evse;
|
||||
const std::unique_ptr<energyImplBase> p_energy_grid;
|
||||
const std::unique_ptr<auth_token_providerImplBase> p_token_provider;
|
||||
const std::unique_ptr<uk_random_delayImplBase> p_random_delay;
|
||||
const std::unique_ptr<dc_external_derateImplBase> p_dc_external_derate;
|
||||
const std::unique_ptr<evse_board_supportIntf> r_bsp;
|
||||
const std::vector<std::unique_ptr<ac_rcdIntf>> r_ac_rcd;
|
||||
const std::vector<std::unique_ptr<connector_lockIntf>> r_connector_lock;
|
||||
const std::vector<std::unique_ptr<powermeterIntf>> r_powermeter_grid_side;
|
||||
const std::vector<std::unique_ptr<powermeterIntf>> r_powermeter_car_side;
|
||||
const std::vector<std::unique_ptr<slacIntf>> r_slac;
|
||||
const std::vector<std::unique_ptr<ISO15118_chargerIntf>> r_hlc;
|
||||
const std::vector<std::unique_ptr<isolation_monitorIntf>> r_imd;
|
||||
const std::vector<std::unique_ptr<over_voltage_monitorIntf>> r_over_voltage_monitor;
|
||||
const std::vector<std::unique_ptr<power_supply_DCIntf>> r_powersupply_DC;
|
||||
const std::vector<std::unique_ptr<kvsIntf>> r_store;
|
||||
const Conf& config;
|
||||
|
||||
// ev@1fce4c5e-0ab8-41bb-90f7-14277703d2ac:v1
|
||||
// insert your public definitions here
|
||||
std::unique_ptr<Charger> charger;
|
||||
sigslot::signal<int> signalNrOfPhasesAvailable;
|
||||
types::powermeter::Powermeter get_latest_powermeter_data_billing();
|
||||
types::evse_board_support::HardwareCapabilities get_hw_capabilities();
|
||||
std::atomic<bool> ready_for_capabilities{false};
|
||||
|
||||
std::mutex external_local_limits_mutex;
|
||||
bool update_max_current_limit(types::energy::ExternalLimits& limits, float max_current_import,
|
||||
float max_current_export);
|
||||
bool update_max_watt_limit(types::energy::ExternalLimits& limits, float max_watt_export,
|
||||
std::optional<float> max_watt_import);
|
||||
void update_to_zero_discharge_limit(types::energy::ExternalLimits& limits);
|
||||
bool update_local_energy_limit(types::energy::ExternalLimits l);
|
||||
types::energy::ExternalLimits get_local_energy_limits();
|
||||
|
||||
void cancel_reservation(bool signal_event);
|
||||
bool is_reserved();
|
||||
|
||||
std::optional<types::evse_manager::ConnectorTypeEnum> connector_type;
|
||||
|
||||
///
|
||||
/// \brief Reserve this evse.
|
||||
/// \param id The reservation id.
|
||||
/// \param signal_reservation_event True when other modules must be signalled about a new reservation (session
|
||||
/// event).
|
||||
/// \return True on success.
|
||||
///
|
||||
bool reserve(int32_t id, const bool signal_reservation_event = true);
|
||||
int32_t get_reservation_id();
|
||||
|
||||
bool get_hlc_waiting_for_auth_pnc();
|
||||
void set_pnc_enabled(const bool pnc_enabled);
|
||||
void set_central_contract_validation_allowed(const bool central_contract_validation_allowed);
|
||||
void set_contract_certificate_installation_enabled(const bool contract_certificate_installation_enabled);
|
||||
|
||||
sigslot::signal<types::evse_manager::SessionEvent> signalReservationEvent;
|
||||
|
||||
void charger_was_authorized();
|
||||
|
||||
const std::vector<std::unique_ptr<powermeterIntf>>& r_powermeter_billing();
|
||||
|
||||
// FIXME: this will be removed with proper integration of BPT on ISO-20
|
||||
// on DIN SPEC and -2 we claim a positive charging current on ISO protocol,
|
||||
// but the power supply switches to discharge if this flag is set.
|
||||
std::atomic_bool is_actually_exporting_to_grid{false};
|
||||
|
||||
types::evse_manager::EVInfo get_ev_info();
|
||||
void apply_new_target_voltage_current();
|
||||
void process_dc_ev_target_voltage_current(const types::iso15118::DcEvseMaximumLimits& hlc_limits);
|
||||
|
||||
std::string selected_protocol = "Unknown";
|
||||
|
||||
std::atomic_bool sae_bidi_active{false};
|
||||
|
||||
void ready_to_start_charging();
|
||||
|
||||
std::unique_ptr<IECStateMachine> bsp;
|
||||
std::unique_ptr<ErrorHandling> error_handling;
|
||||
std::unique_ptr<PersistentStore> store;
|
||||
|
||||
std::atomic_bool random_delay_enabled{false};
|
||||
std::atomic_bool random_delay_running{false};
|
||||
std::chrono::time_point<std::chrono::steady_clock> random_delay_end_time;
|
||||
std::chrono::time_point<date::utc_clock> random_delay_start_time;
|
||||
std::atomic<std::chrono::seconds> random_delay_max_duration;
|
||||
std::atomic<std::chrono::time_point<std::chrono::steady_clock>> timepoint_ready_for_charging;
|
||||
|
||||
bool session_is_iso_d20_ac_bpt();
|
||||
bool session_is_iso_d20_dc_bpt();
|
||||
|
||||
types::power_supply_DC::Capabilities get_powersupply_capabilities();
|
||||
void set_external_derating(types::dc_external_derate::ExternalDerating d);
|
||||
|
||||
void update_powersupply_capabilities(types::power_supply_DC::Capabilities caps) {
|
||||
std::scoped_lock lock(powersupply_capabilities_mutex);
|
||||
|
||||
if (caps != powersupply_capabilities) {
|
||||
r_hlc[0]->call_set_powersupply_capabilities(caps);
|
||||
}
|
||||
|
||||
powersupply_capabilities = caps;
|
||||
|
||||
// Inform HLC layer about update of physical values
|
||||
types::iso15118::SetupPhysicalValues setup_physical_values;
|
||||
setup_physical_values.dc_current_regulation_tolerance = powersupply_capabilities.current_regulation_tolerance_A;
|
||||
setup_physical_values.dc_peak_current_ripple = powersupply_capabilities.peak_current_ripple_A;
|
||||
setup_physical_values.dc_energy_to_be_delivered = 10000;
|
||||
r_hlc[0]->call_set_charging_parameters(setup_physical_values);
|
||||
|
||||
types::iso15118::DcEvseMinimumLimits evse_min_limits;
|
||||
evse_min_limits.evse_minimum_current_limit = powersupply_capabilities.min_export_current_A;
|
||||
evse_min_limits.evse_minimum_voltage_limit = powersupply_capabilities.min_export_voltage_V;
|
||||
evse_min_limits.evse_minimum_power_limit =
|
||||
evse_min_limits.evse_minimum_current_limit * evse_min_limits.evse_minimum_voltage_limit;
|
||||
r_hlc[0]->call_update_dc_minimum_limits(evse_min_limits);
|
||||
|
||||
// HLC layer will also get new maximum current/voltage/watt limits etc, but those will need to run through
|
||||
// energy management first. Those limits will be applied in energy_grid implementation when requesting
|
||||
// energy, so it is enough to set the powersupply_capabilities here.
|
||||
// FIXME: this is not implemented yet: enforce_limits uses the enforced limits to tell HLC, but capabilities
|
||||
// limits are not yet included in request.
|
||||
}
|
||||
std::atomic_int ac_nr_phases_active{0};
|
||||
// ev@1fce4c5e-0ab8-41bb-90f7-14277703d2ac:v1
|
||||
|
||||
protected:
|
||||
// ev@4714b2ab-a24f-4b95-ab81-36439e1478de:v1
|
||||
// insert your protected definitions here
|
||||
// ev@4714b2ab-a24f-4b95-ab81-36439e1478de:v1
|
||||
|
||||
private:
|
||||
friend class LdEverest;
|
||||
void init();
|
||||
void ready();
|
||||
|
||||
// ev@211cfdbe-f69a-4cd6-a4ec-f8aaa3d1b6c8:v1
|
||||
// insert your private definitions here
|
||||
std::mutex powersupply_capabilities_mutex;
|
||||
types::power_supply_DC::Capabilities powersupply_capabilities;
|
||||
std::mutex dc_external_derate_mutex;
|
||||
types::dc_external_derate::ExternalDerating dc_external_derate;
|
||||
|
||||
Everest::timed_mutex_traceable power_mutex;
|
||||
types::powermeter::Powermeter latest_powermeter_data_billing;
|
||||
|
||||
Everest::Thread energyThreadHandle;
|
||||
everest::lib::util::monitor<types::evse_board_support::HardwareCapabilities> hw_capabilities;
|
||||
|
||||
types::energy::ExternalLimits external_local_energy_limits;
|
||||
const float EVSE_ABSOLUTE_MAX_CURRENT = 80.0;
|
||||
bool slac_enabled;
|
||||
|
||||
std::atomic_bool contactor_open{true};
|
||||
|
||||
Everest::timed_mutex_traceable charger_ready_mutex;
|
||||
bool charger_ready{false};
|
||||
std::atomic_bool hlc_enabled;
|
||||
|
||||
std::atomic_bool hlc_waiting_for_auth_eim;
|
||||
std::atomic_bool hlc_waiting_for_auth_pnc;
|
||||
|
||||
std::atomic_bool pnc_enabled{false};
|
||||
std::atomic_bool central_contract_validation_allowed{false};
|
||||
std::atomic_bool contract_certificate_installation_enabled{false};
|
||||
|
||||
VarContainer<types::isolation_monitor::IsolationMeasurement> isolation_measurement;
|
||||
VarContainer<types::power_supply_DC::VoltageCurrent> powersupply_measurement;
|
||||
VarContainer<bool> selftest_result;
|
||||
std::unique_ptr<OverVoltageMonitor> internal_over_voltage_monitor;
|
||||
|
||||
// Track voltage to earth failures for debouncing
|
||||
int voltage_to_earth_failure_count{0};
|
||||
std::chrono::steady_clock::time_point first_voltage_to_earth_failure_time{};
|
||||
static constexpr std::chrono::seconds MIN_TIME_BETWEEN_FIRST_AND_LAST_FAILURE{2};
|
||||
static constexpr int REQUIRED_CONSECUTIVE_FAILURES{2};
|
||||
|
||||
std::atomic<double> latest_target_current_low_pass{0.};
|
||||
std::atomic<std::chrono::steady_clock::time_point> latest_target_current_low_pass_last_update{};
|
||||
std::atomic<double> latest_target_voltage{0.};
|
||||
std::atomic<double> latest_target_current{0.};
|
||||
std::atomic<double> last_power_supply_voltage{0.};
|
||||
std::atomic<double> last_power_supply_current{0.};
|
||||
|
||||
// Raw EV target values as received from ISO15118 stack
|
||||
std::atomic<double> raw_ev_target_voltage{0.};
|
||||
std::atomic<double> raw_ev_target_current{0.};
|
||||
|
||||
types::authorization::ProvidedIdToken autocharge_token;
|
||||
|
||||
void log_v2g_message(types::iso15118::V2gMessages const& v2g_messages);
|
||||
|
||||
// Reservations
|
||||
bool reserved;
|
||||
int32_t reservation_id;
|
||||
Everest::timed_mutex_traceable reservation_mutex;
|
||||
|
||||
// Voltage plausibility monitor
|
||||
std::unique_ptr<VoltagePlausibilityMonitor> voltage_plausibility_monitor;
|
||||
|
||||
void setup_AC_mode();
|
||||
void setup_fake_DC_mode();
|
||||
|
||||
// special funtion to switch mode while session is active
|
||||
void switch_AC_mode();
|
||||
void switch_DC_mode();
|
||||
|
||||
// DC handlers
|
||||
void cable_check();
|
||||
|
||||
void powersupply_DC_on();
|
||||
std::atomic_bool powersupply_dc_is_on{false};
|
||||
bool powersupply_DC_set(double voltage, double current);
|
||||
void powersupply_DC_off();
|
||||
bool wait_powersupply_DC_voltage_reached(double target_voltage);
|
||||
bool wait_powersupply_DC_below_voltage(double target_voltage);
|
||||
|
||||
bool cable_check_should_exit();
|
||||
|
||||
double get_emergency_over_voltage_threshold();
|
||||
double get_error_over_voltage_threshold();
|
||||
|
||||
// EV information
|
||||
Everest::timed_mutex_traceable ev_info_mutex;
|
||||
types::evse_manager::EVInfo ev_info;
|
||||
types::evse_manager::CarManufacturer car_manufacturer{types::evse_manager::CarManufacturer::Unknown};
|
||||
|
||||
void imd_stop();
|
||||
void imd_start();
|
||||
Everest::Thread telemetryThreadHandle;
|
||||
|
||||
void fail_cable_check(const std::string& reason);
|
||||
|
||||
// setup sae j2847/2 v2h mode
|
||||
void setup_v2h_mode();
|
||||
|
||||
bool check_isolation_resistance_in_range(double resistance);
|
||||
bool check_voltage_to_protective_earth_in_range(types::isolation_monitor::IsolationMeasurement m);
|
||||
|
||||
static constexpr double CABLECHECK_CURRENT_LIMIT{2};
|
||||
static constexpr double CABLECHECK_INSULATION_FAULT_RESISTANCE_OHM{100000.};
|
||||
static constexpr double CABLECHECK_SAFE_VOLTAGE{60.};
|
||||
static constexpr int CABLECHECK_SELFTEST_TIMEOUT{30};
|
||||
|
||||
std::atomic_bool current_demand_active{false};
|
||||
std::atomic_bool slac_unmatched{false};
|
||||
std::mutex powermeter_mutex;
|
||||
std::condition_variable powermeter_cv;
|
||||
bool initial_powermeter_value_received{false};
|
||||
|
||||
std::optional<types::iso15118::ServiceCategory> selected_d20_energy_service{std::nullopt};
|
||||
|
||||
std::atomic<types::power_supply_DC::ChargingPhase> power_supply_DC_charging_phase{
|
||||
types::power_supply_DC::ChargingPhase::Other};
|
||||
|
||||
types::power_supply_DC::ChargingPhase last_power_supply_DC_charging_phase{
|
||||
types::power_supply_DC::ChargingPhase::Other};
|
||||
everest::lib::util::monitor<std::vector<types::iso15118::EnergyTransferMode>> supported_energy_transfers;
|
||||
void publish_and_update_supported_energy_transfers();
|
||||
bool update_supported_energy_transfers(const std::vector<types::iso15118::EnergyTransferMode>& energy_transfers);
|
||||
bool update_supported_energy_transfers(const types::iso15118::EnergyTransferMode& energy_transfer);
|
||||
std::mutex hlc_ac_parameters_mutex;
|
||||
void update_hlc_ac_parameters();
|
||||
// ev@211cfdbe-f69a-4cd6-a4ec-f8aaa3d1b6c8:v1
|
||||
};
|
||||
|
||||
// ev@087e516b-124c-48df-94fb-109508c7cda9:v1
|
||||
// insert other definitions here
|
||||
// ev@087e516b-124c-48df-94fb-109508c7cda9:v1
|
||||
|
||||
} // namespace module
|
||||
|
||||
#endif // EVSE_MANAGER_HPP
|
||||
528
tools/EVerest-main/modules/EVSE/EvseManager/IECStateMachine.cpp
Normal file
528
tools/EVerest-main/modules/EVSE/EvseManager/IECStateMachine.cpp
Normal file
@@ -0,0 +1,528 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2023 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include "IECStateMachine.hpp"
|
||||
#include "everest/logging.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <math.h>
|
||||
#include <optional>
|
||||
#include <string.h>
|
||||
|
||||
namespace module {
|
||||
|
||||
// helper type for visitor
|
||||
template <class... Ts> struct overloaded : Ts... {
|
||||
using Ts::operator()...;
|
||||
};
|
||||
template <class... Ts> overloaded(Ts...) -> overloaded<Ts...>;
|
||||
|
||||
enum class TimerControl : std::uint8_t {
|
||||
do_nothing,
|
||||
start,
|
||||
stop,
|
||||
};
|
||||
|
||||
static std::variant<RawCPState, CPEvent> from_bsp_event(types::board_support_common::Event e) {
|
||||
switch (e) {
|
||||
case types::board_support_common::Event::A:
|
||||
return RawCPState::A;
|
||||
case types::board_support_common::Event::B:
|
||||
return RawCPState::B;
|
||||
case types::board_support_common::Event::C:
|
||||
return RawCPState::C;
|
||||
case types::board_support_common::Event::D:
|
||||
return RawCPState::D;
|
||||
case types::board_support_common::Event::E:
|
||||
return RawCPState::E;
|
||||
case types::board_support_common::Event::F:
|
||||
return RawCPState::F;
|
||||
case types::board_support_common::Event::PowerOn:
|
||||
return CPEvent::PowerOn;
|
||||
case types::board_support_common::Event::PowerOff:
|
||||
return CPEvent::PowerOff;
|
||||
default:
|
||||
return RawCPState::Disabled;
|
||||
}
|
||||
}
|
||||
|
||||
/// \brief Converts the given Event \p e to human readable string
|
||||
/// \returns a string representation of the Event
|
||||
const std::string cpevent_to_string(CPEvent e) {
|
||||
switch (e) {
|
||||
case CPEvent::CarPluggedIn:
|
||||
return "CarPluggedIn";
|
||||
case CPEvent::CarRequestedPower:
|
||||
return "CarRequestedPower";
|
||||
case CPEvent::PowerOn:
|
||||
return "PowerOn";
|
||||
case CPEvent::PowerOff:
|
||||
return "PowerOff";
|
||||
case CPEvent::CarRequestedStopPower:
|
||||
return "CarRequestedStopPower";
|
||||
case CPEvent::CarUnplugged:
|
||||
return "CarUnplugged";
|
||||
case CPEvent::EFtoBCD:
|
||||
return "EFtoBCD";
|
||||
case CPEvent::BCDtoEF:
|
||||
return "BCDtoEF";
|
||||
case CPEvent::BCDtoE:
|
||||
return "BCDtoE";
|
||||
}
|
||||
throw std::out_of_range("No known string conversion for provided enum of type CPEvent");
|
||||
}
|
||||
|
||||
IECStateMachine::IECStateMachine(const std::unique_ptr<evse_board_supportIntf>& r_bsp_,
|
||||
bool lock_connector_in_state_b_) :
|
||||
r_bsp(r_bsp_), lock_connector_in_state_b(lock_connector_in_state_b_) {
|
||||
// feed the state machine whenever the timer expires
|
||||
timeout_state_c1.signal_reached.connect([this]() { feed_state_machine(std::nullopt); });
|
||||
timeout_unlock_state_F.signal_reached.connect([this]() { feed_state_machine(std::nullopt); });
|
||||
|
||||
// Subscribe to bsp driver to receive BspEvents from the hardware
|
||||
r_bsp->subscribe_event([this](types::board_support_common::BspEvent const& event) {
|
||||
if (enabled) {
|
||||
// feed into state machine
|
||||
process_bsp_event(event);
|
||||
} else {
|
||||
EVLOG_info << "Ignoring BSP Event, BSP is not enabled yet.";
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void IECStateMachine::process_bsp_event(types::board_support_common::BspEvent const& bsp_event) {
|
||||
auto event = from_bsp_event(bsp_event.event);
|
||||
std::visit(overloaded{[this](const RawCPState& raw_state) {
|
||||
// If it is a raw CP state, run it through the state machine
|
||||
feed_state_machine(raw_state);
|
||||
},
|
||||
// If it is another CP event, pass through
|
||||
[this](CPEvent& event) {
|
||||
// track relais state as confirmed by BSP
|
||||
if (event == CPEvent::PowerOn) {
|
||||
relais_on = true;
|
||||
} else if (event == CPEvent::PowerOff) {
|
||||
relais_on = false;
|
||||
}
|
||||
check_connector_lock();
|
||||
|
||||
signal_event(event);
|
||||
}},
|
||||
event);
|
||||
}
|
||||
|
||||
void IECStateMachine::feed_state_machine(std::optional<RawCPState> const& cp_state_opt) {
|
||||
auto events = state_machine(cp_state_opt);
|
||||
|
||||
// Process all events
|
||||
while (not events.empty()) {
|
||||
EVLOG_debug << "CPEvent " << static_cast<int>(events.front());
|
||||
signal_event(events.front());
|
||||
events.pop();
|
||||
}
|
||||
}
|
||||
|
||||
// Main IEC state machine. Needs to be called whenever:
|
||||
// - CP state changes (both events from hardware as well as duty cycle changes)
|
||||
// - Allow power on changes
|
||||
// - The C1 6s timer expires
|
||||
std::queue<CPEvent> IECStateMachine::state_machine(std::optional<RawCPState> const& cp_state_opt) {
|
||||
|
||||
if (cp_state_opt) {
|
||||
EVLOG_debug << "RawCPState " << static_cast<int>(cp_state_opt.value());
|
||||
} else {
|
||||
EVLOG_debug << "RawCPState not set";
|
||||
}
|
||||
std::queue<CPEvent> events;
|
||||
auto timer_state_C1 = TimerControl::do_nothing;
|
||||
auto timer_unlock_state_F = TimerControl::do_nothing;
|
||||
|
||||
{
|
||||
// mutex protected section
|
||||
Everest::scoped_lock_timeout lock(state_machine_mutex, Everest::MutexDescription::IEC_state_machine);
|
||||
|
||||
const auto cp_state = cp_state_opt.value_or(last_cp_state);
|
||||
|
||||
if (cp_state not_eq RawCPState::F and last_cp_state == RawCPState::F) {
|
||||
timer_unlock_state_F = TimerControl::stop;
|
||||
}
|
||||
|
||||
switch (cp_state) {
|
||||
|
||||
case RawCPState::Disabled:
|
||||
if (last_cp_state != RawCPState::Disabled) {
|
||||
pwm_running = false;
|
||||
r_bsp->call_cp_state_X1();
|
||||
ev_simplified_mode = false;
|
||||
timer_state_C1 = TimerControl::stop;
|
||||
call_allow_power_on_bsp(false);
|
||||
connector_unlock();
|
||||
}
|
||||
break;
|
||||
|
||||
case RawCPState::A:
|
||||
if (last_cp_state != RawCPState::A) {
|
||||
pwm_running = false;
|
||||
r_bsp->call_cp_state_X1();
|
||||
ev_simplified_mode = false;
|
||||
car_plugged_in = false;
|
||||
call_allow_power_on_bsp(false);
|
||||
timer_state_C1 = TimerControl::stop;
|
||||
connector_unlock();
|
||||
}
|
||||
|
||||
// Table A.6: Sequence 2.1 Unplug at state Bx (or any other
|
||||
// state) Table A.6: Sequence 2.2 Unplug at state Cx, Dx
|
||||
if (last_cp_state != RawCPState::A && last_cp_state != RawCPState::Disabled) {
|
||||
events.push(CPEvent::CarUnplugged);
|
||||
}
|
||||
break;
|
||||
|
||||
case RawCPState::B:
|
||||
// Table A.6: Sequence 7 EV stops charging
|
||||
// Table A.6: Sequence 8.2 EV supply equipment
|
||||
// responds to EV opens S2 (w/o PWM)
|
||||
if (lock_connector_in_state_b) {
|
||||
connector_lock();
|
||||
} else {
|
||||
connector_unlock();
|
||||
}
|
||||
|
||||
if (last_cp_state != RawCPState::A && last_cp_state != RawCPState::B) {
|
||||
|
||||
events.push(CPEvent::CarRequestedStopPower);
|
||||
// Need to switch off according to Table A.6 Sequence 8.1
|
||||
// within 100ms
|
||||
call_allow_power_on_bsp(false);
|
||||
timer_state_C1 = TimerControl::stop;
|
||||
}
|
||||
|
||||
// Table A.6: Sequence 1.1 Plug-in
|
||||
if (last_cp_state == RawCPState::A || last_cp_state == RawCPState::Disabled ||
|
||||
(!car_plugged_in && last_cp_state == RawCPState::F)) {
|
||||
events.push(CPEvent::CarPluggedIn);
|
||||
car_plugged_in = true;
|
||||
ev_simplified_mode = false;
|
||||
}
|
||||
|
||||
if (last_cp_state == RawCPState::E || last_cp_state == RawCPState::F) {
|
||||
// Triggers SLAC start
|
||||
events.push(CPEvent::EFtoBCD);
|
||||
}
|
||||
break;
|
||||
|
||||
case RawCPState::D:
|
||||
connector_lock();
|
||||
// If state D is not supported switch off.
|
||||
if (not has_ventilation) {
|
||||
call_allow_power_on_bsp(false);
|
||||
timer_state_C1 = TimerControl::stop;
|
||||
break;
|
||||
}
|
||||
// no break, intended fall through: If we support state D it is handled the same way as state C
|
||||
[[fallthrough]];
|
||||
|
||||
case RawCPState::C:
|
||||
connector_lock();
|
||||
// Table A.6: Sequence 1.2 Plug-in
|
||||
if (last_cp_state == RawCPState::A || last_cp_state == RawCPState::Disabled ||
|
||||
(!car_plugged_in && last_cp_state == RawCPState::F)) {
|
||||
events.push(CPEvent::CarPluggedIn);
|
||||
car_plugged_in = true;
|
||||
EVLOG_info << "Detected simplified mode.";
|
||||
ev_simplified_mode = true;
|
||||
} else if (last_cp_state == RawCPState::B) {
|
||||
events.push(CPEvent::CarRequestedPower);
|
||||
}
|
||||
|
||||
if (!pwm_running && last_pwm_running) { // X2->C1
|
||||
// Table A.6 Sequence 10.2: EV does not stop drawing power
|
||||
// even if PWM stops. Stop within 6 seconds (E.g. Kona1!)
|
||||
timer_state_C1 = TimerControl::start;
|
||||
}
|
||||
|
||||
// PWM switches on while in state C
|
||||
if (pwm_running && !last_pwm_running) {
|
||||
// when resuming after a pause before the EV goes to state B, stop the timer.
|
||||
timer_state_C1 = TimerControl::stop;
|
||||
|
||||
// If we resume charging and the EV never left state C during pause we allow non-compliant EVs to switch
|
||||
// on again.
|
||||
if (power_on_allowed) {
|
||||
call_allow_power_on_bsp(true);
|
||||
}
|
||||
}
|
||||
|
||||
if (timeout_state_c1.reached()) {
|
||||
EVLOG_warning
|
||||
<< "Timeout of 6 seconds reached, EV did not go back to state B after PWM was switched off. "
|
||||
"Powering off under load.";
|
||||
// We are still in state C, but the 6 seconds timeout has been reached. Now force power off under load.
|
||||
call_allow_power_on_bsp(false);
|
||||
}
|
||||
|
||||
if (pwm_running) { // C2
|
||||
// 1) When we come from state B: switch on if we are allowed to
|
||||
// 2) When we are in C2 for a while now and finally get a delayed power_on_allowed: also switch on
|
||||
|
||||
if (power_on_allowed && (!last_power_on_allowed || last_cp_state == RawCPState::B)) {
|
||||
// Table A.6: Sequence 4 EV ready to charge.
|
||||
// Must enable power within 3 seconds.
|
||||
call_allow_power_on_bsp(true);
|
||||
}
|
||||
|
||||
// Simulate Request power Event here for simplified mode
|
||||
// to ensure that this mode behaves similar for higher
|
||||
// layers. Note this does not work with 5% mode
|
||||
// correctly, but simplified mode does not support HLC
|
||||
// anyway.
|
||||
if (!last_pwm_running && ev_simplified_mode) {
|
||||
events.push(CPEvent::CarRequestedPower);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case RawCPState::E:
|
||||
connector_unlock();
|
||||
if (last_cp_state != RawCPState::E) {
|
||||
timer_state_C1 = TimerControl::stop;
|
||||
call_allow_power_on_bsp(false);
|
||||
pwm_running = false;
|
||||
r_bsp->call_cp_state_X1();
|
||||
if (last_cp_state == RawCPState::B || last_cp_state == RawCPState::C ||
|
||||
last_cp_state == RawCPState::D) {
|
||||
events.push(CPEvent::BCDtoEF);
|
||||
events.push(CPEvent::BCDtoE);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case RawCPState::F:
|
||||
timer_state_C1 = TimerControl::stop;
|
||||
call_allow_power_on_bsp(false);
|
||||
if (last_cp_state not_eq RawCPState::F) {
|
||||
timer_unlock_state_F = TimerControl::start;
|
||||
pwm_running = false;
|
||||
}
|
||||
if (last_cp_state == RawCPState::B || last_cp_state == RawCPState::C || last_cp_state == RawCPState::D) {
|
||||
events.push(CPEvent::BCDtoEF);
|
||||
}
|
||||
|
||||
if (timeout_unlock_state_F.reached()) {
|
||||
connector_unlock();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
check_connector_lock();
|
||||
|
||||
last_cp_state = cp_state;
|
||||
last_pwm_running = pwm_running;
|
||||
last_power_on_allowed = power_on_allowed;
|
||||
// end of mutex protected section
|
||||
}
|
||||
|
||||
// stopping the timer could lead to a deadlock when called from the
|
||||
// mutex protected section
|
||||
switch (timer_state_C1) {
|
||||
case TimerControl::start:
|
||||
timeout_state_c1.start(power_off_under_load_in_c1_timeout);
|
||||
break;
|
||||
case TimerControl::stop:
|
||||
timeout_state_c1.stop();
|
||||
break;
|
||||
case TimerControl::do_nothing:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
switch (timer_unlock_state_F) {
|
||||
case TimerControl::start:
|
||||
timeout_unlock_state_F.start(unlock_in_state_f_timeout);
|
||||
break;
|
||||
case TimerControl::stop:
|
||||
timeout_unlock_state_F.stop();
|
||||
break;
|
||||
case TimerControl::do_nothing:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return events;
|
||||
}
|
||||
|
||||
// High level state machine sets PWM duty cycle
|
||||
void IECStateMachine::set_pwm(double value) {
|
||||
{
|
||||
Everest::scoped_lock_timeout lock(state_machine_mutex, Everest::MutexDescription::IEC_set_pwm);
|
||||
if (value > 0 && value < 1) {
|
||||
pwm_running = true;
|
||||
} else {
|
||||
pwm_running = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (ev_simplified_mode_evse_limit and ev_simplified_mode and value > ev_simplified_mode_evse_limit_pwm) {
|
||||
EVLOG_warning
|
||||
<< "Simplified mode: Limiting output PWM to 10A due to config option \"hack_simplified_mode_limit_10A\"";
|
||||
value = ev_simplified_mode_evse_limit_pwm;
|
||||
}
|
||||
|
||||
r_bsp->call_pwm_on(value * 100);
|
||||
|
||||
feed_state_machine(std::nullopt);
|
||||
}
|
||||
|
||||
// High level state machine sets state X1
|
||||
void IECStateMachine::set_cp_state_X1() {
|
||||
{
|
||||
Everest::scoped_lock_timeout lock(state_machine_mutex, Everest::MutexDescription::IEC_set_cp_state_X1);
|
||||
pwm_running = false;
|
||||
}
|
||||
r_bsp->call_cp_state_X1();
|
||||
// Don't run the state machine in the callers context
|
||||
feed_state_machine(std::nullopt);
|
||||
}
|
||||
|
||||
// High level state machine sets state F
|
||||
void IECStateMachine::set_cp_state_F() {
|
||||
{
|
||||
Everest::scoped_lock_timeout lock(state_machine_mutex, Everest::MutexDescription::IEC_set_cp_state_F);
|
||||
pwm_running = false;
|
||||
}
|
||||
r_bsp->call_cp_state_F();
|
||||
// Don't run the state machine in the callers context
|
||||
feed_state_machine(std::nullopt);
|
||||
}
|
||||
|
||||
// The higher level state machine in Charger.cpp calls this to indicate it allows contactors to be switched on
|
||||
void IECStateMachine::allow_power_on(bool value, types::evse_board_support::Reason reason) {
|
||||
{
|
||||
Everest::scoped_lock_timeout lock(state_machine_mutex, Everest::MutexDescription::IEC_allow_power_on);
|
||||
// Only set the flags here in case of power on.
|
||||
power_on_allowed = value;
|
||||
power_on_reason = reason;
|
||||
// In case of power off, we can directly forward this to the BSP driver here
|
||||
if (not power_on_allowed) {
|
||||
call_allow_power_on_bsp(false);
|
||||
}
|
||||
}
|
||||
// The actual power on will be handled in the state machine to verify it is in the correct CP state etc.
|
||||
// Don't run the state machine in the callers context
|
||||
feed_state_machine(std::nullopt);
|
||||
}
|
||||
|
||||
// Private member function used to actually call the BSP driver's allow_power_on
|
||||
// No need to lock mutex as this will be called from state machine or locked context only
|
||||
void IECStateMachine::call_allow_power_on_bsp(bool value) {
|
||||
if (not value) {
|
||||
power_on_allowed = false;
|
||||
power_on_reason = types::evse_board_support::Reason::PowerOff;
|
||||
}
|
||||
r_bsp->call_allow_power_on({value, power_on_reason});
|
||||
}
|
||||
|
||||
void IECStateMachine::set_pp_ampacity(types::board_support_common::ProximityPilot const& pp) {
|
||||
switch (pp.ampacity) {
|
||||
case types::board_support_common::Ampacity::A_13:
|
||||
pp_ampacity = 13.;
|
||||
break;
|
||||
case types::board_support_common::Ampacity::A_20:
|
||||
pp_ampacity = 20.;
|
||||
break;
|
||||
case types::board_support_common::Ampacity::A_32:
|
||||
pp_ampacity = 32.;
|
||||
break;
|
||||
case types::board_support_common::Ampacity::A_63_3ph_70_1ph:
|
||||
if (max_phases == AcPhases::SinglePhase) {
|
||||
pp_ampacity = 70.;
|
||||
} else {
|
||||
pp_ampacity = 63.;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
pp_ampacity = 0.;
|
||||
}
|
||||
}
|
||||
|
||||
// High level state machine requests reading PP ampacity value.
|
||||
// The high level state machine will never call this if it is not used
|
||||
// (e.g. in DC or AC tethered charging)
|
||||
std::optional<double> IECStateMachine::read_pp_ampacity() {
|
||||
const double tmp = pp_ampacity;
|
||||
if (tmp == 0.0) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return tmp;
|
||||
}
|
||||
|
||||
// Forward special request to switch the number of phases during charging. BSP will need to implement a special
|
||||
// sequence to not destroy cars.
|
||||
void IECStateMachine::switch_three_phases_while_charging(bool n) {
|
||||
r_bsp->call_ac_switch_three_phases_while_charging(n);
|
||||
}
|
||||
|
||||
// Forwards config parameters from EvseManager module config to BSP
|
||||
void IECStateMachine::setup(bool has_ventilation) {
|
||||
this->has_ventilation = has_ventilation;
|
||||
}
|
||||
|
||||
// enable/disable the charging port and CP signal
|
||||
void IECStateMachine::enable(bool en) {
|
||||
enabled = en;
|
||||
r_bsp->call_enable(en);
|
||||
}
|
||||
|
||||
// Forward the over current detection limit to the BSP. Many BSP MCUs monitor the charge current and trigger a fault
|
||||
// in case of over current. This sets the target charging current value to be used in OC detection. It cannot be
|
||||
// derived from the PWM duty cycle, use this value instead.
|
||||
void IECStateMachine::set_overcurrent_limit(double amps) {
|
||||
if (amps != last_amps) {
|
||||
r_bsp->call_ac_set_overcurrent_limit_A(amps);
|
||||
last_amps = amps;
|
||||
}
|
||||
}
|
||||
|
||||
void IECStateMachine::connector_lock() {
|
||||
should_be_locked = true;
|
||||
}
|
||||
|
||||
void IECStateMachine::connector_unlock() {
|
||||
should_be_locked = false;
|
||||
force_unlocked = false;
|
||||
}
|
||||
|
||||
void IECStateMachine::connector_force_unlock() {
|
||||
RawCPState cp;
|
||||
|
||||
{
|
||||
Everest::scoped_lock_timeout lock(state_machine_mutex, Everest::MutexDescription::IEC_force_unlock);
|
||||
cp = last_cp_state;
|
||||
}
|
||||
|
||||
if (not relais_on) {
|
||||
// Unconditionally try to unlock, as `is_locked` might not always reflect the physical state of the lock.
|
||||
// This can occur for example in case of a failed unlock due to a hardware issue.
|
||||
signal_unlock();
|
||||
is_locked = false;
|
||||
}
|
||||
|
||||
if (cp == RawCPState::B or cp == RawCPState::C) {
|
||||
force_unlocked = true;
|
||||
check_connector_lock();
|
||||
}
|
||||
}
|
||||
|
||||
void IECStateMachine::check_connector_lock() {
|
||||
bool should_be_locked_considering_relais_and_force = relais_on or (should_be_locked and not force_unlocked);
|
||||
|
||||
if (not is_locked and should_be_locked_considering_relais_and_force) {
|
||||
signal_lock();
|
||||
is_locked = true;
|
||||
} else if (is_locked and not should_be_locked_considering_relais_and_force) {
|
||||
signal_unlock();
|
||||
is_locked = false;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace module
|
||||
158
tools/EVerest-main/modules/EVSE/EvseManager/IECStateMachine.hpp
Normal file
158
tools/EVerest-main/modules/EVSE/EvseManager/IECStateMachine.hpp
Normal file
@@ -0,0 +1,158 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2023 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
/*
|
||||
The IECStateMachine class provides an adapter between the board support package driver (in a seperate module) and the
|
||||
high level state machine in Charger.cpp.
|
||||
|
||||
Typically the CP signal generation/reading and control of contactors/RCD etc is handled by a dedicated MCU. This MCU
|
||||
and/or the HW is responsible for the basic electrical safety of the system (such as safely shut down in case of RCD
|
||||
trigger or Linux crashing). The BSP driver is just a simple HW abstraction layer that translates the commands for
|
||||
setting PWM duty cycle/allow contactors on as well as the CP signal readings/error conditions into the everest world.
|
||||
It should not need to implement any logic or understanding of the IEC61851-1 or any higher protocol.
|
||||
|
||||
This IECStateMachine is the low level state machine translating the IEC61851-1 CP states ABCDEF into more useful
|
||||
abstract events such as "CarPluggedIn/CarRequestedPower" etc. These events drive the high level state machine in
|
||||
Charger.cpp which handles the actual charging session and coordinates IEC/ISO/SLAC.
|
||||
|
||||
*/
|
||||
|
||||
#ifndef SRC_BSP_STATE_MACHINE_H_
|
||||
#define SRC_BSP_STATE_MACHINE_H_
|
||||
|
||||
#include "ld-ev.hpp"
|
||||
|
||||
#include <chrono>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <queue>
|
||||
|
||||
#include <generated/interfaces/evse_board_support/Interface.hpp>
|
||||
#include <sigslot/signal.hpp>
|
||||
|
||||
#include "Timeout.hpp"
|
||||
#include "utils/thread.hpp"
|
||||
|
||||
#include "scoped_lock_timeout.hpp"
|
||||
|
||||
namespace module {
|
||||
|
||||
// Abstract events that drive the higher level state machine in Charger.cpp
|
||||
enum class CPEvent {
|
||||
CarPluggedIn,
|
||||
CarRequestedPower,
|
||||
PowerOn,
|
||||
PowerOff,
|
||||
CarRequestedStopPower,
|
||||
CarUnplugged,
|
||||
EFtoBCD,
|
||||
BCDtoEF,
|
||||
BCDtoE
|
||||
};
|
||||
|
||||
// Just a helper for log printing
|
||||
const std::string cpevent_to_string(CPEvent e);
|
||||
|
||||
// Raw (valid) CP states for the IECStateMachine
|
||||
enum class RawCPState {
|
||||
Disabled,
|
||||
A,
|
||||
B,
|
||||
C,
|
||||
D,
|
||||
E,
|
||||
F
|
||||
};
|
||||
|
||||
// Number of active phases for AC charging
|
||||
enum class AcPhases {
|
||||
SinglePhase,
|
||||
TwoPhases,
|
||||
ThreePhases
|
||||
};
|
||||
|
||||
class IECStateMachine {
|
||||
public:
|
||||
// We need the r_bsp reference to be able to talk to the bsp driver module
|
||||
IECStateMachine(const std::unique_ptr<evse_board_supportIntf>& r_bsp_, bool lock_connector_in_state_b_);
|
||||
// Call when new events from BSP requirement come in. Will signal internal events
|
||||
void process_bsp_event(types::board_support_common::BspEvent const& bsp_event);
|
||||
// Allow power on from Charger state machine
|
||||
void allow_power_on(bool value, types::evse_board_support::Reason reason);
|
||||
|
||||
void set_pp_ampacity(types::board_support_common::ProximityPilot const& pp);
|
||||
std::optional<double> read_pp_ampacity();
|
||||
void switch_three_phases_while_charging(bool n);
|
||||
void setup(bool has_ventilation);
|
||||
|
||||
void set_overcurrent_limit(double amps);
|
||||
|
||||
void set_pwm(double value);
|
||||
void set_cp_state_X1();
|
||||
void set_cp_state_F();
|
||||
|
||||
void set_max_phases(AcPhases phases) {
|
||||
max_phases = phases;
|
||||
}
|
||||
|
||||
void enable(bool en);
|
||||
|
||||
void connector_force_unlock();
|
||||
|
||||
void set_ev_simplified_mode_evse_limit(bool l) {
|
||||
ev_simplified_mode_evse_limit = l;
|
||||
}
|
||||
|
||||
// Signal for internal events type
|
||||
sigslot::signal<CPEvent> signal_event;
|
||||
sigslot::signal<> signal_lock;
|
||||
sigslot::signal<> signal_unlock;
|
||||
|
||||
private:
|
||||
void connector_lock();
|
||||
void connector_unlock();
|
||||
void check_connector_lock();
|
||||
const std::unique_ptr<evse_board_supportIntf>& r_bsp;
|
||||
bool lock_connector_in_state_b{true};
|
||||
|
||||
bool pwm_running{false};
|
||||
bool last_pwm_running{false};
|
||||
|
||||
static constexpr float ev_simplified_mode_evse_limit_pwm{10 / 0.6 / 100.}; // Fixed 10A limit
|
||||
// If set to true, EVSE will limit to 10A in case of simplified charging
|
||||
bool ev_simplified_mode_evse_limit{false};
|
||||
bool ev_simplified_mode{false};
|
||||
bool has_ventilation{false};
|
||||
bool power_on_allowed{false};
|
||||
bool last_power_on_allowed{false};
|
||||
std::atomic<double> pp_ampacity{0.0};
|
||||
std::atomic<double> last_amps{-1};
|
||||
std::atomic<AcPhases> max_phases{AcPhases::ThreePhases};
|
||||
|
||||
bool car_plugged_in{false};
|
||||
|
||||
RawCPState last_cp_state{RawCPState::Disabled};
|
||||
AsyncTimeout timeout_state_c1;
|
||||
AsyncTimeout timeout_unlock_state_F;
|
||||
|
||||
Everest::timed_mutex_traceable state_machine_mutex;
|
||||
void feed_state_machine(std::optional<RawCPState> const& cp_state_opt);
|
||||
std::queue<CPEvent> state_machine(std::optional<RawCPState> const& cp_state_opt);
|
||||
|
||||
types::evse_board_support::Reason power_on_reason{types::evse_board_support::Reason::PowerOff};
|
||||
void call_allow_power_on_bsp(bool value);
|
||||
|
||||
std::atomic_bool is_locked{false};
|
||||
std::atomic_bool should_be_locked{false};
|
||||
std::atomic_bool force_unlocked{false};
|
||||
|
||||
std::atomic_bool enabled{false};
|
||||
std::atomic_bool relais_on{false};
|
||||
|
||||
static constexpr std::chrono::seconds power_off_under_load_in_c1_timeout{6};
|
||||
static constexpr std::chrono::seconds unlock_in_state_f_timeout{5};
|
||||
};
|
||||
|
||||
} // namespace module
|
||||
|
||||
#endif // SRC_BSP_STATE_MACHINE_H_
|
||||
@@ -0,0 +1,44 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2023 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include "PersistentStore.hpp"
|
||||
|
||||
namespace module {
|
||||
|
||||
PersistentStore::PersistentStore(const std::vector<std::unique_ptr<kvsIntf>>& _r_store, const std::string module_id) :
|
||||
r_store(_r_store) {
|
||||
|
||||
if (r_store.size() > 0) {
|
||||
active = true;
|
||||
}
|
||||
|
||||
session_key = module_id + "_session";
|
||||
}
|
||||
|
||||
void PersistentStore::store_session(const std::string& session_uuid) {
|
||||
if (active) {
|
||||
r_store[0]->call_store(session_key, session_uuid);
|
||||
}
|
||||
}
|
||||
|
||||
void PersistentStore::clear_session() {
|
||||
if (active) {
|
||||
r_store[0]->call_store(session_key, "");
|
||||
}
|
||||
}
|
||||
|
||||
std::string PersistentStore::get_session() {
|
||||
if (active) {
|
||||
auto r = r_store[0]->call_load(session_key);
|
||||
try {
|
||||
if (std::holds_alternative<std::string>(r)) {
|
||||
return std::get<std::string>(r);
|
||||
}
|
||||
} catch (...) {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
} // namespace module
|
||||
@@ -0,0 +1,33 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2021 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
/*
|
||||
The Persistent Store class is an abstraction layer to store any persistent information
|
||||
(such as sessions) for the EvseManager.
|
||||
*/
|
||||
|
||||
#ifndef EVSE_MANAGER_PERSISTENT_STORE_H_
|
||||
#define EVSE_MANAGER_PERSISTENT_STORE_H_
|
||||
|
||||
#include <generated/interfaces/kvs/Interface.hpp>
|
||||
|
||||
namespace module {
|
||||
|
||||
class PersistentStore {
|
||||
public:
|
||||
// We need the r_bsp reference to be able to talk to the bsp driver module
|
||||
explicit PersistentStore(const std::vector<std::unique_ptr<kvsIntf>>& r_store, const std::string module_id);
|
||||
|
||||
void store_session(const std::string& session_uuid);
|
||||
void clear_session();
|
||||
std::string get_session();
|
||||
|
||||
private:
|
||||
const std::vector<std::unique_ptr<kvsIntf>>& r_store;
|
||||
std::string session_key;
|
||||
bool active{false};
|
||||
};
|
||||
|
||||
} // namespace module
|
||||
|
||||
#endif // EVSE_MANAGER_PERSISTENT_STORE_H_
|
||||
14
tools/EVerest-main/modules/EVSE/EvseManager/README.md
Normal file
14
tools/EVerest-main/modules/EVSE/EvseManager/README.md
Normal file
@@ -0,0 +1,14 @@
|
||||
# EvseManager documentation
|
||||
|
||||
see `doc.rst`
|
||||
|
||||
## Tests
|
||||
|
||||
```bash
|
||||
cd EVerest
|
||||
mkdir build && cd build
|
||||
cmake -DBUILD_TESTING=ON -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX=./dist ..
|
||||
make -j$(nproc) install
|
||||
```
|
||||
|
||||
Tests are installed in `./modules/EvseManager/tests/`
|
||||
250
tools/EVerest-main/modules/EVSE/EvseManager/SessionLog.cpp
Normal file
250
tools/EVerest-main/modules/EVSE/EvseManager/SessionLog.cpp
Normal file
@@ -0,0 +1,250 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
#include "SessionLog.hpp"
|
||||
#include "everest/logging.hpp"
|
||||
#include "v2gMessage.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <date/date.h>
|
||||
#include <date/tz.h>
|
||||
#include <everest/helpers/helpers.hpp>
|
||||
#include <filesystem>
|
||||
#include <optional>
|
||||
#include <utils/date.hpp>
|
||||
|
||||
#include <boost/algorithm/string.hpp>
|
||||
#include <fmt/core.h>
|
||||
|
||||
namespace module {
|
||||
|
||||
SessionLog session_log;
|
||||
|
||||
SessionLog::SessionLog() {
|
||||
session_active = false;
|
||||
enabled = false;
|
||||
xmloutput = true;
|
||||
}
|
||||
|
||||
SessionLog::~SessionLog() {
|
||||
if (logfile_csv.is_open()) {
|
||||
logfile_csv.close();
|
||||
}
|
||||
}
|
||||
|
||||
void SessionLog::setPath(const std::string& path) {
|
||||
logpath_root = std::filesystem::weakly_canonical(std::filesystem::path(path));
|
||||
}
|
||||
|
||||
void SessionLog::setMqtt(mqtt_publish_ftor const& mqtt_provider) {
|
||||
mqtt = mqtt_provider;
|
||||
}
|
||||
|
||||
void SessionLog::enable() {
|
||||
enabled = true;
|
||||
}
|
||||
|
||||
std::optional<std::filesystem::path> SessionLog::startSession(const std::string& suffix_string) {
|
||||
if (enabled) {
|
||||
if (session_active) {
|
||||
stopSession();
|
||||
}
|
||||
|
||||
// create general log directory if it does not exist
|
||||
if (!std::filesystem::exists(logpath_root)) {
|
||||
try {
|
||||
std::filesystem::create_directories(logpath_root);
|
||||
} catch (std::filesystem::filesystem_error& e) {
|
||||
EVLOG_error << fmt::format("Cannot create logpath {}: {}", logpath_root.string(), e.what());
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
std::string ts = Everest::Date::to_rfc3339(date::utc_clock::now());
|
||||
auto ts_suffix_string = fmt::format("{}-{}", ts, suffix_string);
|
||||
auto logpath_with_suffix = std::filesystem::weakly_canonical(logpath_root / ts_suffix_string);
|
||||
|
||||
const auto [root_it, suffix_ix] = std::mismatch(logpath_root.begin(), logpath_root.end(),
|
||||
logpath_with_suffix.begin(), logpath_with_suffix.end());
|
||||
if (root_it != logpath_root.end()) {
|
||||
EVLOG_error << fmt::format("Logpath with suffix ({}) is not a subdirectory of logpath root {}",
|
||||
logpath_with_suffix.string(), logpath_root.string());
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
logpath = logpath_with_suffix;
|
||||
|
||||
// create sessionlog directory if it does not exist
|
||||
if (!std::filesystem::exists(logpath)) {
|
||||
std::filesystem::create_directories(logpath);
|
||||
}
|
||||
|
||||
// open new file
|
||||
fn = logpath / "incomplete-eventlog.csv";
|
||||
fnhtml = logpath / "incomplete-eventlog.html";
|
||||
fn_complete = logpath / "eventlog.csv";
|
||||
fnhtml_complete = logpath / "eventlog.html";
|
||||
|
||||
try {
|
||||
logfile_csv.open(fn);
|
||||
logfile_html.open(fnhtml);
|
||||
session_active = true;
|
||||
} catch (const std::ofstream::failure& e) {
|
||||
EVLOG_error << fmt::format("Cannot open {} of {} for writing", fn.string(), fnhtml.string());
|
||||
session_active = false;
|
||||
}
|
||||
logfile_html << fmt::format("<html><head><title>EVerest log session {}</title>\n",
|
||||
everest::helpers::escape_html(suffix_string));
|
||||
logfile_html << "<style>"
|
||||
".log {"
|
||||
" font-family: Arial, Helvetica, sans-serif;"
|
||||
" border-collapse: collapse;"
|
||||
" width: 100%;"
|
||||
"}"
|
||||
".log td, .log th {"
|
||||
" border: 1px solid #ddd;"
|
||||
" padding: 8px;"
|
||||
" vertical-align: top;"
|
||||
"}"
|
||||
".log tr.CAR{background-color: #E4E6F2;}"
|
||||
".log tr.EVSE{background-color: #F2F0E4;}"
|
||||
".log tr.SYS{background-color: white;}"
|
||||
".log th {"
|
||||
" padding-top: 12px;"
|
||||
" padding-bottom: 12px;"
|
||||
" text-align: left;"
|
||||
" vertical-align: top;"
|
||||
" background-color: #04AA6D;"
|
||||
" color: white;"
|
||||
"}"
|
||||
"</style>";
|
||||
logfile_html << "</head><body><table class=\"log\">\n";
|
||||
sys("Session logging started.");
|
||||
return logpath;
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
void SessionLog::stopSession() {
|
||||
if (enabled) {
|
||||
sys("Session logging stopped.");
|
||||
|
||||
logfile_html << "</table></body></html>\n";
|
||||
|
||||
if (logfile_csv.is_open()) {
|
||||
logfile_csv.close();
|
||||
}
|
||||
if (logfile_html.is_open()) {
|
||||
logfile_html.close();
|
||||
}
|
||||
|
||||
// rename files to indicate they are finished now
|
||||
try {
|
||||
std::filesystem::rename(fn, fn_complete);
|
||||
} catch (const std::filesystem::filesystem_error& fs_err) {
|
||||
EVLOG_error << "Could not rename " << fn << ": " << fs_err.what();
|
||||
}
|
||||
|
||||
try {
|
||||
std::filesystem::rename(fnhtml, fnhtml_complete);
|
||||
} catch (const std::filesystem::filesystem_error& fs_err) {
|
||||
EVLOG_error << "Could not rename " << fnhtml << ": " << fs_err.what();
|
||||
}
|
||||
|
||||
session_active = false;
|
||||
}
|
||||
}
|
||||
|
||||
void SessionLog::evse(bool iso15118, const std::string& msg) {
|
||||
evse(iso15118, msg, "", "", "", "");
|
||||
}
|
||||
|
||||
void SessionLog::car(bool iso15118, const std::string& msg) {
|
||||
car(iso15118, msg, "", "", "", "");
|
||||
}
|
||||
|
||||
void SessionLog::evse(bool iso15118, const std::string& msg, const std::string& xml, const std::string& xml_hex,
|
||||
const std::string& xml_base64, const std::string& json_str) {
|
||||
output(0, iso15118, msg, xml, xml_hex, xml_base64, json_str);
|
||||
}
|
||||
|
||||
void SessionLog::car(bool iso15118, const std::string& msg, const std::string& xml, const std::string& xml_hex,
|
||||
const std::string& xml_base64, const std::string& json_str) {
|
||||
output(1, iso15118, msg, xml, xml_hex, xml_base64, json_str);
|
||||
}
|
||||
|
||||
void SessionLog::output(unsigned int typ, bool iso15118, const std::string& msg, const std::string& xml,
|
||||
const std::string& xml_hex, const std::string& xml_base64, const std::string& json_str) {
|
||||
if (enabled && session_active) {
|
||||
std::string ts = Everest::Date::to_rfc3339(date::utc_clock::now());
|
||||
|
||||
std::string xml_pretty;
|
||||
v2g_message v2g;
|
||||
if (!xml.empty()) {
|
||||
v2g.from_xml(xml);
|
||||
xml_pretty = v2g.to_xml();
|
||||
} else if (!json_str.empty()) {
|
||||
v2g.from_json(json_str);
|
||||
xml_pretty = v2g.to_json();
|
||||
}
|
||||
|
||||
// output to EVerest log
|
||||
std::string log = msg;
|
||||
std::string origin, target;
|
||||
if (xmloutput) {
|
||||
log += xml_pretty;
|
||||
}
|
||||
if (typ == 0) {
|
||||
origin = "EVSE";
|
||||
target = "CAR";
|
||||
EVLOG_info << "\033[1;34mEVSE " << (iso15118 ? "ISO" : "IEC") << " " << log << "\033[1;0m";
|
||||
} else if (typ == 1) {
|
||||
origin = "CAR";
|
||||
target = "EVSE";
|
||||
EVLOG_info << " \033[1;33mCAR " << (iso15118 ? "ISO" : "IEC") << " "
|
||||
<< log << "\033[1;0m";
|
||||
} else {
|
||||
origin = "SYS";
|
||||
target = "";
|
||||
EVLOG_info << "SYS " << msg;
|
||||
}
|
||||
|
||||
// output to session log file
|
||||
logfile_csv << fmt::format("\"{}\",\"{}\",\"{}\",\"{}\"\n", ts, origin, msg, xml_pretty);
|
||||
logfile_csv.flush();
|
||||
|
||||
// output to session html file
|
||||
logfile_html << fmt::format("<tr class=\"{}\"> <td>{}</td> <td>{}</td> <td><b>{}</b></td><td><b>{}</b></td> "
|
||||
"<td><pre lang=\"xml\">{}</pre></td> <td><pre lang=\"xml\">{}</pre></td> <td><pre "
|
||||
"lang=\"xml\">{}</pre></td> </tr>\n",
|
||||
origin, ts, origin + ">" + target, (typ == 0 || typ == 2 ? msg : ""),
|
||||
(typ == 1 ? msg : ""), html_encode(xml_pretty), xml_hex, xml_base64);
|
||||
logfile_html.flush();
|
||||
|
||||
// output to api
|
||||
nlohmann::json data;
|
||||
data["origin"] = origin;
|
||||
data["target"] = target;
|
||||
data["iso15118"] = iso15118;
|
||||
data["msg"] = msg;
|
||||
this->mqtt(data);
|
||||
}
|
||||
}
|
||||
|
||||
void SessionLog::xmlOutput(bool e) {
|
||||
xmloutput = e;
|
||||
}
|
||||
|
||||
void SessionLog::sys(const std::string& msg) {
|
||||
output(2, false, msg, "", "", "", "");
|
||||
}
|
||||
|
||||
std::string SessionLog::html_encode(const std::string& msg) {
|
||||
std::string out = msg;
|
||||
boost::replace_all(out, "<", "<");
|
||||
boost::replace_all(out, ">", ">");
|
||||
return out;
|
||||
}
|
||||
|
||||
} // namespace module
|
||||
62
tools/EVerest-main/modules/EVSE/EvseManager/SessionLog.hpp
Normal file
62
tools/EVerest-main/modules/EVSE/EvseManager/SessionLog.hpp
Normal file
@@ -0,0 +1,62 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
#ifndef SESSION_LOG_HPP
|
||||
#define SESSION_LOG_HPP
|
||||
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <functional>
|
||||
#include <nlohmann/json_fwd.hpp>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
namespace module {
|
||||
/*
|
||||
Simple session logger that outputs to one file per session and EVLOG
|
||||
*/
|
||||
|
||||
class SessionLog {
|
||||
public:
|
||||
using mqtt_publish_ftor = std::function<void(nlohmann::json const&)>;
|
||||
SessionLog();
|
||||
~SessionLog();
|
||||
|
||||
void setPath(const std::string& path);
|
||||
void setMqtt(mqtt_publish_ftor const& mqtt_provider);
|
||||
void enable();
|
||||
std::optional<std::filesystem::path> startSession(const std::string& suffix_string);
|
||||
void stopSession();
|
||||
|
||||
void car(bool iso15118, const std::string& msg);
|
||||
void car(bool iso15118, const std::string& msg, const std::string& xml, const std::string& xml_hex,
|
||||
const std::string& xml_base64, const std::string& json_str);
|
||||
|
||||
void evse(bool iso15118, const std::string& msg);
|
||||
void evse(bool iso15118, const std::string& msg, const std::string& xml, const std::string& xml_hex,
|
||||
const std::string& xml_base64, const std::string& json_str);
|
||||
|
||||
void xmlOutput(bool e);
|
||||
|
||||
void sys(const std::string& msg);
|
||||
|
||||
private:
|
||||
void output(unsigned int evse, bool iso15118, const std::string& msg, const std::string& xml,
|
||||
const std::string& xml_hex, const std::string& xml_base64, const std::string& json_str);
|
||||
std::string html_encode(const std::string& msg);
|
||||
bool xmloutput;
|
||||
bool session_active;
|
||||
bool enabled;
|
||||
std::filesystem::path logpath_root;
|
||||
std::filesystem::path logpath;
|
||||
std::filesystem::path fn, fnhtml, fn_complete, fnhtml_complete;
|
||||
|
||||
std::ofstream logfile_csv;
|
||||
std::ofstream logfile_html;
|
||||
mqtt_publish_ftor mqtt;
|
||||
};
|
||||
|
||||
extern SessionLog session_log;
|
||||
|
||||
} // namespace module
|
||||
|
||||
#endif // SESSION_LOG_HPP
|
||||
122
tools/EVerest-main/modules/EVSE/EvseManager/Timeout.hpp
Normal file
122
tools/EVerest-main/modules/EVSE/EvseManager/Timeout.hpp
Normal file
@@ -0,0 +1,122 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
#ifndef TIMEOUT_HPP
|
||||
#define TIMEOUT_HPP
|
||||
|
||||
#include <chrono>
|
||||
#include <sigslot/signal.hpp>
|
||||
|
||||
#include "utils/thread.hpp"
|
||||
|
||||
using namespace std::chrono;
|
||||
|
||||
/*
|
||||
Simple helper class for a polling timeout
|
||||
*/
|
||||
|
||||
class Timeout {
|
||||
public:
|
||||
void start(milliseconds _t) {
|
||||
t = _t;
|
||||
start_time = steady_clock::now();
|
||||
running = true;
|
||||
}
|
||||
|
||||
void stop() {
|
||||
running = false;
|
||||
}
|
||||
|
||||
bool is_running() {
|
||||
return running;
|
||||
}
|
||||
|
||||
bool reached() {
|
||||
if (!running)
|
||||
return false;
|
||||
if ((steady_clock::now() - start_time) > t) {
|
||||
running = false;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
milliseconds t;
|
||||
time_point<steady_clock> start_time;
|
||||
bool running{false};
|
||||
};
|
||||
|
||||
/* Simple helper class for a timeout with internal thread */
|
||||
class AsyncTimeout {
|
||||
public:
|
||||
void start(milliseconds _t) {
|
||||
std::scoped_lock lock(mutex);
|
||||
|
||||
if (running) {
|
||||
wait_thread.stop();
|
||||
running = false;
|
||||
}
|
||||
|
||||
t = _t;
|
||||
start_time = steady_clock::now();
|
||||
|
||||
// start waiting thread
|
||||
wait_thread = std::thread([this]() {
|
||||
while (not wait_thread.shouldExit()) {
|
||||
std::this_thread::sleep_for(resolution);
|
||||
if (reached_nolock()) {
|
||||
// Note the order is important here.
|
||||
// We first signal reached which will call all callbacks.
|
||||
// The timer is still running in this in those callbacks, so they can also call reached() and
|
||||
// get a true as return value.
|
||||
signal_reached();
|
||||
// After all signal handlers are called, we stop the timer.
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
running = true;
|
||||
}
|
||||
|
||||
// Note that stopping the timer may take up to "resolution" amount of time to return
|
||||
void stop() {
|
||||
std::scoped_lock lock(mutex);
|
||||
if (running) {
|
||||
wait_thread.stop();
|
||||
running = false;
|
||||
}
|
||||
}
|
||||
|
||||
bool is_running() {
|
||||
std::scoped_lock lock(mutex);
|
||||
return running;
|
||||
}
|
||||
|
||||
bool reached() {
|
||||
std::scoped_lock lock(mutex);
|
||||
return reached_nolock();
|
||||
}
|
||||
|
||||
sigslot::signal<> signal_reached;
|
||||
|
||||
private:
|
||||
bool reached_nolock() {
|
||||
if (!running) {
|
||||
return false;
|
||||
} else if ((steady_clock::now() - start_time) > t) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
constexpr static auto resolution = 500ms;
|
||||
milliseconds t;
|
||||
time_point<steady_clock> start_time;
|
||||
bool running{false};
|
||||
std::mutex mutex;
|
||||
Everest::Thread wait_thread;
|
||||
};
|
||||
|
||||
#endif
|
||||
52
tools/EVerest-main/modules/EVSE/EvseManager/VarContainer.hpp
Normal file
52
tools/EVerest-main/modules/EVSE/EvseManager/VarContainer.hpp
Normal file
@@ -0,0 +1,52 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
#ifndef VARCONTAINER_HPP
|
||||
#define VARCONTAINER_HPP
|
||||
|
||||
#include <chrono>
|
||||
#include <condition_variable>
|
||||
#include <mutex>
|
||||
|
||||
/*
|
||||
Simple helper class for a thread safe single producer / single consumer pattern
|
||||
with a queue size of one.
|
||||
*/
|
||||
|
||||
template <class T> class VarContainer {
|
||||
public:
|
||||
T& operator=(T d) {
|
||||
{
|
||||
std::scoped_lock lock(data_mutex);
|
||||
data = d;
|
||||
unread_data = true;
|
||||
}
|
||||
condvar.notify_one();
|
||||
return data;
|
||||
};
|
||||
|
||||
void clear() {
|
||||
std::scoped_lock lock(data_mutex);
|
||||
unread_data = false;
|
||||
};
|
||||
|
||||
bool wait_for(T& d, std::chrono::milliseconds timeout) {
|
||||
std::unique_lock<std::mutex> lock(data_mutex);
|
||||
|
||||
if (condvar.wait_for(lock, timeout, [this] { return unread_data; })) {
|
||||
unread_data = false;
|
||||
d = data;
|
||||
return true;
|
||||
} else {
|
||||
// Timeout occurred in wait_for
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
private:
|
||||
T data;
|
||||
std::condition_variable condvar;
|
||||
std::mutex data_mutex;
|
||||
bool unread_data{false};
|
||||
};
|
||||
|
||||
#endif
|
||||
47
tools/EVerest-main/modules/EVSE/EvseManager/backtrace.cpp
Normal file
47
tools/EVerest-main/modules/EVSE/EvseManager/backtrace.cpp
Normal file
@@ -0,0 +1,47 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include "backtrace.hpp"
|
||||
|
||||
#ifdef EVEREST_USE_BACKTRACES
|
||||
|
||||
#include <cstring>
|
||||
#include <execinfo.h>
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
|
||||
/*
|
||||
Simple backtrace signal handler
|
||||
*/
|
||||
namespace Everest {
|
||||
|
||||
void signal_handler(int signo) {
|
||||
void* array[10];
|
||||
size_t size;
|
||||
|
||||
// get void*'s for all entries on the stack
|
||||
size = backtrace(array, 10);
|
||||
|
||||
// print out all the frames to stderr
|
||||
printf("\n---------------------------------------------------------------------------------------------------\n");
|
||||
backtrace_symbols_fd(array, size, STDOUT_FILENO);
|
||||
printf("---------------------------------------------------------------------------------------------------\n\n");
|
||||
}
|
||||
|
||||
void install_backtrace_handler() {
|
||||
struct sigaction bt_handler;
|
||||
memset(&bt_handler, 0, sizeof(bt_handler));
|
||||
|
||||
bt_handler.sa_handler = signal_handler;
|
||||
|
||||
if (sigaction(SIGUSR1, &bt_handler, NULL) < 0) {
|
||||
perror("sigaction");
|
||||
}
|
||||
}
|
||||
|
||||
void request_backtrace(pthread_t id) {
|
||||
pthread_kill(id, SIGUSR1);
|
||||
}
|
||||
|
||||
} // namespace Everest
|
||||
#endif
|
||||
22
tools/EVerest-main/modules/EVSE/EvseManager/backtrace.hpp
Normal file
22
tools/EVerest-main/modules/EVSE/EvseManager/backtrace.hpp
Normal file
@@ -0,0 +1,22 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
#ifndef EVEREST_BACKTRACE
|
||||
#define EVEREST_BACKTRACE
|
||||
#ifdef __linux__
|
||||
#include <signal.h>
|
||||
|
||||
#include <features.h>
|
||||
#ifdef __USE_GNU
|
||||
#include "backtrace.hpp"
|
||||
#define EVEREST_USE_BACKTRACES
|
||||
/*
|
||||
Simple backtrace signal handler
|
||||
*/
|
||||
namespace Everest {
|
||||
void signal_handler(int signo);
|
||||
void install_backtrace_handler();
|
||||
void request_backtrace(pthread_t id);
|
||||
} // namespace Everest
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
@@ -0,0 +1,20 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include "dc_external_derateImpl.hpp"
|
||||
|
||||
namespace module {
|
||||
namespace dc_external_derate {
|
||||
|
||||
void dc_external_derateImpl::init() {
|
||||
}
|
||||
|
||||
void dc_external_derateImpl::ready() {
|
||||
}
|
||||
|
||||
void dc_external_derateImpl::handle_set_external_derating(types::dc_external_derate::ExternalDerating& derate) {
|
||||
mod->set_external_derating(derate);
|
||||
}
|
||||
|
||||
} // namespace dc_external_derate
|
||||
} // namespace module
|
||||
@@ -0,0 +1,61 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
#ifndef DC_EXTERNAL_DERATE_DC_EXTERNAL_DERATE_IMPL_HPP
|
||||
#define DC_EXTERNAL_DERATE_DC_EXTERNAL_DERATE_IMPL_HPP
|
||||
|
||||
//
|
||||
// AUTO GENERATED - MARKED REGIONS WILL BE KEPT
|
||||
// template version 3
|
||||
//
|
||||
|
||||
#include <generated/interfaces/dc_external_derate/Implementation.hpp>
|
||||
|
||||
#include "../EvseManager.hpp"
|
||||
|
||||
// ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1
|
||||
// insert your custom include headers here
|
||||
// ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1
|
||||
|
||||
namespace module {
|
||||
namespace dc_external_derate {
|
||||
|
||||
struct Conf {};
|
||||
|
||||
class dc_external_derateImpl : public dc_external_derateImplBase {
|
||||
public:
|
||||
dc_external_derateImpl() = delete;
|
||||
dc_external_derateImpl(Everest::ModuleAdapter* ev, const Everest::PtrContainer<EvseManager>& mod, Conf& config) :
|
||||
dc_external_derateImplBase(ev, "dc_external_derate"), mod(mod), config(config){};
|
||||
|
||||
// ev@8ea32d28-373f-4c90-ae5e-b4fcc74e2a61:v1
|
||||
// insert your public definitions here
|
||||
// ev@8ea32d28-373f-4c90-ae5e-b4fcc74e2a61:v1
|
||||
|
||||
protected:
|
||||
// command handler functions (virtual)
|
||||
virtual void handle_set_external_derating(types::dc_external_derate::ExternalDerating& derate) override;
|
||||
|
||||
// ev@d2d1847a-7b88-41dd-ad07-92785f06f5c4:v1
|
||||
// insert your protected definitions here
|
||||
// ev@d2d1847a-7b88-41dd-ad07-92785f06f5c4:v1
|
||||
|
||||
private:
|
||||
const Everest::PtrContainer<EvseManager>& mod;
|
||||
const Conf& config;
|
||||
|
||||
virtual void init() override;
|
||||
virtual void ready() override;
|
||||
|
||||
// ev@3370e4dd-95f4-47a9-aaec-ea76f34a66c9:v1
|
||||
// insert your private definitions here
|
||||
// ev@3370e4dd-95f4-47a9-aaec-ea76f34a66c9:v1
|
||||
};
|
||||
|
||||
// ev@3d7da0ad-02c2-493d-9920-0bbbd56b9876:v1
|
||||
// insert other definitions here
|
||||
// ev@3d7da0ad-02c2-493d-9920-0bbbd56b9876:v1
|
||||
|
||||
} // namespace dc_external_derate
|
||||
} // namespace module
|
||||
|
||||
#endif // DC_EXTERNAL_DERATE_DC_EXTERNAL_DERATE_IMPL_HPP
|
||||
441
tools/EVerest-main/modules/EVSE/EvseManager/docs/index.rst
Normal file
441
tools/EVerest-main/modules/EVSE/EvseManager/docs/index.rst
Normal file
@@ -0,0 +1,441 @@
|
||||
.. _everest_modules_handwritten_EvseManager:
|
||||
|
||||
.. ************************
|
||||
.. EvseManager
|
||||
.. ************************
|
||||
|
||||
The module ``EvseManager`` is a central module that manages one EVSE
|
||||
(i.e. one connector to charge a car).
|
||||
It may control multiple physical connectors if they are not usable at the same
|
||||
time and share one connector id,
|
||||
but one EvseManager always shows as one connector in OCPP for example. So in
|
||||
general each connector should have a dedicated EvseManager module loaded.
|
||||
|
||||
The EvseManager contains the high level charging logic (Basic charging and
|
||||
HLC/SLAC interaction), collects all relevant data for the charging session
|
||||
(e.g. energy delivered during this charging session) and provides control over
|
||||
the charging port/session. For HLC it uses two helper protocol modules that it
|
||||
controls (SLAC and ISO15118).
|
||||
|
||||
Protocol modules such as OCPP or other APIs use EvseManagers to control the
|
||||
charging session and get all relevant data.
|
||||
|
||||
The following charge modes are supported:
|
||||
|
||||
* AC charging: Basic Charging according to IEC61851/SAE J1772 and HLC according
|
||||
to ISO15118-2
|
||||
* DC charging: ISO15118-2 and DIN SPEC 70121
|
||||
|
||||
Additional features:
|
||||
|
||||
* Autocharge support (PnC coming soon)
|
||||
* Seamlessly integrates into EVerest Energy Management
|
||||
* The lowest level IEC61851 state machine can be run on a dedicated
|
||||
microcontroller for improved electrical safety
|
||||
* Support for seperate AC and DC side metering in DC application
|
||||
|
||||
Typical connections
|
||||
===================
|
||||
|
||||
TODO: AC and DC module graphs and description
|
||||
|
||||
AC Configuration
|
||||
----------------
|
||||
|
||||
DC Configuration
|
||||
----------------
|
||||
|
||||
In DC applications, the EvseManager still has an AC side that behaves similar
|
||||
to a normal AC charger. The board_support module therefore still has to report
|
||||
AC capabilities which refer to the AC input of the AC/DC power supply. If an AC
|
||||
side RCD is used it also belongs to the board_support driver.
|
||||
An AC side power meter can be connected and it will be used for Energy
|
||||
management.
|
||||
|
||||
In addition, on the DC side the following hardware modules can be connected:
|
||||
|
||||
* A DC powermeter: This will be used for billing purposes if present.
|
||||
If not connected, billing will fall back to the AC side power meter.
|
||||
* Isolation monitoring: This will be used to monitor isolation during
|
||||
CableCheck, PreCharge and CurrentDemand steps.
|
||||
* DC power supply: This is the AC/DC converter that actually charges the car.
|
||||
|
||||
Software over-voltage supervision is always active during DC charging. The configuration option
|
||||
``internal_over_voltage_duration_ms`` defines for how long the measured DC voltage
|
||||
must exceed the negotiated limit before EvseManager raises ``MREC5OverVoltage``.
|
||||
Set it to ``0`` to trigger immediately once the threshold is crossed.
|
||||
|
||||
Software over-voltage supervision is always active during DC charging. The configuration option
|
||||
``internal_over_voltage_duration_ms`` defines for how long the measured DC voltage
|
||||
must exceed the negotiated limit before EvseManager raises ``MREC5OverVoltage``.
|
||||
Set it to ``0`` to trigger immediately once the threshold is crossed.
|
||||
|
||||
Published variables
|
||||
===================
|
||||
|
||||
session_events
|
||||
--------------
|
||||
|
||||
EvseManager publishes the session_events variable whenever an event happens.
|
||||
It does not publish its internal state but merely events that happen that can
|
||||
be used to drive an state machine within another module.
|
||||
|
||||
Example: Write a simple module that lights up an LED if the evse is reserved.
|
||||
This module requires an EvseManager and subscribes to the session_events
|
||||
variable. Internally it has only two states: Reserved (LED on), NotReserved
|
||||
(LED off).
|
||||
|
||||
The state machine transitions are driven by the two events from EvseManager:
|
||||
ReservationStart and ReservationEnd.
|
||||
|
||||
All other events are ignored in this module as they are not needed.
|
||||
|
||||
powermeter
|
||||
----------
|
||||
|
||||
EvseManager republishes the power meter struct that if it has a powermeter
|
||||
connected. This struct should be used for OCPP and display purposes. It comes
|
||||
from the power meter that can be used for billing (DC side on DC, AC side on
|
||||
AC). If no powermeter is connected EvseManager will never publish this
|
||||
variable.
|
||||
|
||||
|
||||
Charging State Machine
|
||||
======================
|
||||
|
||||
.. mermaid::
|
||||
|
||||
stateDiagram-v2
|
||||
direction TB
|
||||
|
||||
[*] --> Idle
|
||||
|
||||
%% Enable / Disable
|
||||
Idle --> Disabled : disable
|
||||
Disabled --> Idle : enable
|
||||
|
||||
%% Happy path
|
||||
Idle --> WaitingForAuthentication : EV plugged in
|
||||
WaitingForAuthentication --> PrepareCharging : Authorized
|
||||
PrepareCharging --> Charging : Contactor close allowed
|
||||
Charging --> StoppingCharging : Stop condition
|
||||
StoppingCharging --> Finished : Transaction end
|
||||
Finished --> Idle : EV unplugged
|
||||
|
||||
%% Early exit / Errors
|
||||
WaitingForAuthentication --> Finished : Fatal error or EV unplugged
|
||||
PrepareCharging --> StoppingCharging : Fatal error, deauth, or EV unplugged
|
||||
|
||||
%% Pauses
|
||||
Charging --> ChargingPausedEV : EV moves to state B
|
||||
ChargingPausedEV --> PrepareCharging : Power requested (BCB/Car)
|
||||
ChargingPausedEV --> ChargingPausedEVSE : No power (AC BASIC)
|
||||
ChargingPausedEV --> StoppingCharging : Deauth or unplugged
|
||||
|
||||
ChargingPausedEVSE --> PrepareCharging : Power available / Errors cleared
|
||||
ChargingPausedEVSE --> StoppingCharging : Deauth or unplugged
|
||||
|
||||
%% Post-Stop logic
|
||||
StoppingCharging --> ChargingPausedEV : EV-initiated pause
|
||||
|
||||
State Transitions
|
||||
-----------------
|
||||
|
||||
**Basic Flow**
|
||||
|
||||
* ``Idle`` -> ``WaitingForAuthentication``: EV plugged in.
|
||||
* ``WaitingForAuthentication`` -> ``PrepareCharging``: Authorized by EIM or PnC.
|
||||
* ``PrepareCharging`` -> ``Charging``: Contactor close allowed.
|
||||
* ``Charging`` -> ``StoppingCharging``: Triggered by any **Stop Condition** (see below).
|
||||
* ``StoppingCharging`` -> ``Finished``: No transaction, EV unplugged, or not authorized.
|
||||
* ``Finished`` -> ``Idle``: EV unplugged.
|
||||
|
||||
**Pause & Resume**
|
||||
|
||||
* ``ChargingPausedEV`` -> ``PrepareCharging``: BCB toggle or ``CarRequestedPower``.
|
||||
* ``ChargingPausedEV`` -> ``ChargingPausedEVSE``: No power available (AC BASIC only).
|
||||
* ``ChargingPausedEVSE`` -> ``PrepareCharging``: Power available, no EVSE pause and errors cleared.
|
||||
* ``StoppingCharging`` -> ``ChargingPausedEV``: EV-initiated pause after stop sequence.
|
||||
|
||||
**Stop Conditions**
|
||||
|
||||
The transition ``Charging`` -> ``StoppingCharging`` occurs if:
|
||||
* Fatal error
|
||||
* Deauthorization
|
||||
* EVSE pause requested
|
||||
* EV unplugged
|
||||
* IEC contactor opened
|
||||
* No power available (Immediate for AC BASIC; timeout for HLC).
|
||||
|
||||
.. note::
|
||||
|
||||
Transient helper states (``T_step_EF``, ``T_step_X1``, and ``SwitchPhases``) are omitted
|
||||
for readability. Transitions passing through these are shown as direct arrows.
|
||||
|
||||
Session Events
|
||||
--------------
|
||||
|
||||
These are the ``SessionEvent`` values published by the EvseManager with respect to the state machine
|
||||
states and transitions.
|
||||
|
||||
.. list-table::
|
||||
:header-rows: 1
|
||||
:widths: 35 30 35
|
||||
|
||||
* - State / Transition
|
||||
- Event
|
||||
- Trigger
|
||||
* - ``Disabled``
|
||||
- ``Disabled``
|
||||
- On entry
|
||||
* - ``Idle``
|
||||
- ``Enabled``
|
||||
- When EVSE is enabled via ``enable_disable()``
|
||||
* - ``WaitingForAuthentication``
|
||||
- ``SessionStarted``
|
||||
- On entry (new session)
|
||||
* - ``WaitingForAuthentication``
|
||||
- ``AuthRequired``
|
||||
- On entry
|
||||
* - ``WaitingForAuthentication``
|
||||
- ``Authorized``
|
||||
- When ``authorize()`` is called externally
|
||||
* - ``WaitingForAuthentication``
|
||||
- ``TransactionStarted``
|
||||
- After auth is accepted, just before leaving state
|
||||
* - ``PrepareCharging``
|
||||
- ``PrepareCharging``
|
||||
- On entry
|
||||
* - ``Charging``
|
||||
- ``ChargingStarted``
|
||||
- On entry
|
||||
* - ``ChargingPausedEV``
|
||||
- ``ChargingPausedEV``
|
||||
- On entry
|
||||
* - ``ChargingPausedEVSE``
|
||||
- ``ChargingPausedEVSE``
|
||||
- When the set of pause reasons changes
|
||||
* - ``StoppingCharging``
|
||||
- ``StoppingCharging``
|
||||
- On entry
|
||||
* - ``Finished``
|
||||
- ``ChargingFinished``
|
||||
- On entry (if transaction was active)
|
||||
* - ``Finished``
|
||||
- ``TransactionFinished``
|
||||
- On entry (if transaction was active)
|
||||
* - ``Finished`` -> ``Idle``
|
||||
- ``SessionFinished``
|
||||
- When EV unplugs and ``stop_session()`` is called
|
||||
|
||||
Authentication
|
||||
==============
|
||||
|
||||
The Auth modules validates tokens and assignes tokens to EvseManagers, see Auth
|
||||
documentation. It will call ``Authorize(id_tag, pnc)`` on EvseManager to
|
||||
indicated that the EvseManager may start the charging session.
|
||||
Auth module may revoke authorization (``withdraw_authorization`` command) if
|
||||
the charging session has not begun yet (typically due to timeout), but not once
|
||||
charging has started.
|
||||
|
||||
|
||||
Autocharge / PnC
|
||||
----------------
|
||||
|
||||
Autocharge is fully supported, PnC support is coming soon and will use the same
|
||||
logic. The car itself is a token provider that can provide an auth token to be
|
||||
validated by the Auth system (see Auth documentation for more details).
|
||||
EvseManager provides a ``token_provider`` interface for that purpose.
|
||||
|
||||
If external identification (EIM) is used in HLC (no PnC) then Autocharge is
|
||||
enabled by connecting the ``token_provider`` interface to Auth module. When the
|
||||
car sends its EVCCID in the HLC protocol it is converted to Autocharge format
|
||||
and published as Auth token. It is based on the following specification:
|
||||
|
||||
https://github.com/openfastchargingalliance/openfastchargingalliance/blob/master/autocharge-final.pdf
|
||||
|
||||
To enable PnC the config option ``payment_enable_contract`` must be set to
|
||||
true. If the car selects Contract instead of EIM PnC will be used instead of
|
||||
Autocharge.
|
||||
|
||||
Reservation
|
||||
-----------
|
||||
|
||||
Reservation handling logic is implemented in the Auth module. If the Auth
|
||||
module wants to reserve a specific EvseManager (or cancel the reservation) it
|
||||
needs to call the reserve/cancel_reservation commands. EvseManager does not
|
||||
check reservation id against the token id when it should start charging, this
|
||||
must be handled in Auth module. EvseManager only needs to know whether it is
|
||||
reserved or not to emit an ReservatonStart/ReservationEnd event to notify other
|
||||
modules such as OCPP and API or e.g. switch on a specific LED signal on the
|
||||
charging port.
|
||||
|
||||
Energy Management
|
||||
=================
|
||||
|
||||
EvseManager seamlessly intergrates into the EVerest Energy Management.
|
||||
For further details refer to the documentation of the EnergyManager module.
|
||||
|
||||
EvseManager has a grid facing Energy interface which the energy tree uses to
|
||||
provide energy for the charging sessions. New energy needs to be provided on
|
||||
regular intervals (with a timeout).
|
||||
|
||||
If the supplied energy limits time out, EvseManager will stop charging.
|
||||
This prevents e.g. overload conditions when the network connection drops
|
||||
between the energy tree and EvseManager.
|
||||
|
||||
EvseManager will send out its wishes at regular intervals: It sends a
|
||||
requested energy schedule into the energy tree that is merged from hardware
|
||||
capabilities (as reported by board_support module), EvseManager module
|
||||
configuration settings
|
||||
(max_current, three_phases) and external limts (via ``set_external_limits``
|
||||
command) e.g. set by OCPP module.
|
||||
|
||||
Note that the ``set_external_limits`` should not be used by multiple modules,
|
||||
as the last one always wins. If you have multiple sources of exernal limits
|
||||
that you want to combine, add extra EnergyNode modules in the chain and
|
||||
feed in limits via those.
|
||||
|
||||
The combined schedule sent to the energy tree is the minimum of all energy
|
||||
limits.
|
||||
|
||||
After traversing the energy tree the EnergyManager will use this information
|
||||
to assign limits (and a schedule)
|
||||
for this EvseManager and will call enforce_limits on the energy interface.
|
||||
These values will then be used
|
||||
to configure PWM/DC power supplies to actually charge the car and must not
|
||||
be confused with the original wishes that
|
||||
were sent to the energy tree.
|
||||
|
||||
The EvseManager will never assign energy to itself, it always requests energy
|
||||
from the energy manager and only charges
|
||||
if the energy manager responds with an assignment.
|
||||
|
||||
Limits in the energy object can be specified in ampere (per phase) and/or watt.
|
||||
Currently watt limits are unsupported, but it should behave according to that
|
||||
logic:
|
||||
|
||||
If both are specified also both limits will be applied, whichever is lower.
|
||||
With DC charging, ampere limits apply
|
||||
to the AC side and watt limits apply to both AC and DC side.
|
||||
|
||||
Energy Management: 1ph/3ph switching
|
||||
====================================
|
||||
|
||||
EVerest has support for switching between 1ph and 3ph configurations during AC
|
||||
charging (e.g. for solar charging when sometimes charging with less then 4.2kW (6A*230V*3ph)
|
||||
if desired).
|
||||
|
||||
Be warned: Some vehicles (such as first generation of Renault Zoe) may be permanently
|
||||
damaged when switching from 1ph to 3ph during charging. Use at your own risk!
|
||||
|
||||
To use this feature several things need to be enabled:
|
||||
|
||||
- In EvseManager, adjust two config options to your needs: ``switch_3ph1ph_delay_s``, ``switch_3ph1ph_cp_state``
|
||||
- In the BSP driver, set ``supports_changing_phases_during_charging`` to true in the reported capabilities.
|
||||
If your bsp hardware detects e.g. the Zoe, you can set that flag to false and publish updated capabilities any time.
|
||||
- BSP driver capabilities: Also make sure that minimum phases are set to one and maximum phases to 3
|
||||
- BSP driver: make sure the ``ac_switch_three_phases_while_charging`` command is correctly implemented
|
||||
- EnergyManager: Adjust ``switch_3ph1ph_while_charging_mode``, ``switch_3ph1ph_max_nr_of_switches_per_session``,
|
||||
``switch_3ph1ph_switch_limit_stickyness``, ``switch_3ph1ph_power_hysteresis_W``, ``switch_3ph1ph_time_hysteresis_s``
|
||||
config options to your needs
|
||||
|
||||
If all of this is properly set up, the EnergyManager will drive the 1ph/3ph switching. In order to do so,
|
||||
it needs an (external) limit to be set. There are two options: The external limit can be in Watt (not in Ampere),
|
||||
even though we are AC charging. This is the preferred option as it gives the freedom to the EnergyManager to
|
||||
decide when to switch. The limit can come from OCPP schedule or e.g. via an additional EnergyNode.
|
||||
|
||||
The second option is to set a limit in Ampere and set a limitation on the number of phases (e.g. min_phase=1, max_phase=1).
|
||||
This will enforce switching and can be used to decide the switching time externally. EnergyManager does not have the
|
||||
freedom to make the choice in this case.
|
||||
|
||||
Take care especially with the power(watt) and time based hysteresis settings. They should be adjusted to the
|
||||
actual use case to avoid relays wearing due too a lot of switching cycles. Consider also to limit the maximum
|
||||
number of switching cycles per charging session.
|
||||
|
||||
Error Handling
|
||||
==============
|
||||
|
||||
The control flow of this module can be influenced by the error implementation of its requirements. This section documents
|
||||
the side effects that can be caused by errors raised by a requirement.
|
||||
|
||||
This module subscribes to all errors of the following requirements:
|
||||
|
||||
* evse_manager
|
||||
* evse_board_support
|
||||
* connector_lock
|
||||
* ac_rcd
|
||||
* isolation_monitor
|
||||
* power_supply_DC
|
||||
* powermeter (if the config option fail_on_powermeter_errors is set true)
|
||||
|
||||
A raised error can cause the EvseManager to stop the charging session and become inoperative. Charging is not possible until the error is cleared.
|
||||
If no charging session is currently running, it will prevent sessions from being started. If a charging session is currently running and an error is raised
|
||||
this will interrupt the charging session.
|
||||
|
||||
Almost all errors that are reported from the requirements of this module cause the EvseManager to become Inoperative until the error is cleared.
|
||||
The following sections provide an overview of the errors that do **not** cause the EvseManager to become Inoperative.
|
||||
|
||||
evse_manager
|
||||
-------------
|
||||
|
||||
* evse_manager/Inoperative
|
||||
* evse_manager/MREC11CableCheckFault
|
||||
|
||||
|
||||
evse_board_support
|
||||
------------------
|
||||
|
||||
* evse_board_support/MREC3HighTemperature
|
||||
* evse_board_support/MREC18CableOverTempDerate
|
||||
* evse_board_support/VendorWarning
|
||||
|
||||
connector_lock
|
||||
--------------
|
||||
|
||||
* connector_lock/VendorWarning
|
||||
|
||||
ac_rcd
|
||||
------
|
||||
|
||||
* ac_rcd/VendorWarning
|
||||
|
||||
isolation_monitor
|
||||
-----------------
|
||||
|
||||
* isolation_monitor/VendorWarning
|
||||
|
||||
power_supply_DC
|
||||
---------------
|
||||
|
||||
* power_supply_DC/VendorWarning
|
||||
|
||||
powermeter
|
||||
----------
|
||||
|
||||
Powermeter errors cause the EvseManager to become Inoperative, if fail_on_powermeter_errors is configured to true. If it is configured to false, errors from the powermeter will not cause the EvseManager to become Inoperative.
|
||||
|
||||
* powermeter/CommunicationFault
|
||||
* powermeter/VendorError
|
||||
|
||||
Note that ``powermeter/VendorWarning`` is explicitly ignored by the EvseManager's inoperative logic (similar to other ``VendorWarning`` errors)
|
||||
and will not block charging even if ``fail_on_powermeter_errors`` is set to true. It should be used to signal non-fatal conditions such as
|
||||
high temperature warnings from the powermeter.
|
||||
|
||||
When a charging session is stopped because of an error, the EvseManager differentiates between **Emergency Shutdowns** and **Error Shutdowns**. The severity of the
|
||||
error influences the type of the shudown. Emergency shutdowns are caused by errors with `Severity::High` and error shutdowns are caused by errors with `Severity::Medium` or `Severity::Low`.
|
||||
|
||||
In case of an **Emergency Shutdown** the EvseManager will immediately:
|
||||
|
||||
* Signal PWM state F if HLC is not active
|
||||
* Turn off the PWM
|
||||
* Turn off the DC power supply in case of DC
|
||||
* Signal to open the contactor
|
||||
|
||||
In case of an **Error Shutdown** the EvseManager will:
|
||||
|
||||
* Signal PWM state F if HLC is not active
|
||||
* Keep the PWM on if HLC is active
|
||||
* Turn off the DC power supply in case of DC
|
||||
* Signal to open the contactor
|
||||
* Report the error via HLC to the EV (if HLC active)
|
||||
@@ -0,0 +1,676 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include "energyImpl.hpp"
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <date/date.h>
|
||||
#include <date/tz.h>
|
||||
#include <fmt/core.h>
|
||||
#include <string>
|
||||
#include <utils/date.hpp>
|
||||
#include <utils/formatter.hpp>
|
||||
|
||||
namespace module {
|
||||
namespace energy_grid {
|
||||
|
||||
// helper to find out if voltage changed (more then noise)
|
||||
static bool voltage_changed(float a, float b) {
|
||||
constexpr float noise_voltage = 1;
|
||||
return (fabs(a - b) > noise_voltage);
|
||||
}
|
||||
|
||||
void energyImpl::init() {
|
||||
|
||||
charger_state = Charger::EvseState::Disabled;
|
||||
|
||||
source_base = mod->info.id;
|
||||
source_bsp_caps = source_base + "/evse_board_support_caps";
|
||||
source_psu_caps = source_base + "/powersupply_dc_caps";
|
||||
|
||||
std::srand((unsigned)time(0));
|
||||
|
||||
// UUID must be unique also beyond this charging station -> will be handled on framework level and above later
|
||||
energy_flow_request.uuid = mod->info.id;
|
||||
energy_flow_request.node_type = types::energy::NodeType::Evse;
|
||||
|
||||
if (mod->r_powermeter_car_side.size()) {
|
||||
mod->r_powermeter_car_side[0]->subscribe_powermeter([this](types::powermeter::Powermeter p) {
|
||||
// Received new power meter values, update our energy object.
|
||||
std::lock_guard<std::mutex> lock(this->energy_mutex);
|
||||
energy_flow_request.energy_usage_leaves = p;
|
||||
});
|
||||
}
|
||||
|
||||
if (mod->r_powermeter_grid_side.size()) {
|
||||
mod->r_powermeter_grid_side[0]->subscribe_powermeter([this](types::powermeter::Powermeter p) {
|
||||
// Received new power meter values, update our energy object.
|
||||
std::lock_guard<std::mutex> lock(this->energy_mutex);
|
||||
energy_flow_request.energy_usage_root = p;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void energyImpl::clear_import_request_schedule() {
|
||||
types::energy::ScheduleReqEntry entry_import;
|
||||
const auto tpnow = date::utc_clock::now();
|
||||
const auto tp =
|
||||
Everest::Date::to_rfc3339(date::floor<std::chrono::hours>(tpnow) + date::get_leap_second_info(tpnow).elapsed);
|
||||
|
||||
entry_import.timestamp = tp;
|
||||
entry_import.limits_to_root.ac_max_phase_count = {hw_caps.max_phase_count_import};
|
||||
entry_import.limits_to_root.ac_min_phase_count = {hw_caps.min_phase_count_import};
|
||||
entry_import.limits_to_root.ac_max_current_A = {hw_caps.max_current_A_import};
|
||||
entry_import.limits_to_root.ac_min_current_A = {hw_caps.min_current_A_import};
|
||||
entry_import.limits_to_root.ac_supports_changing_phases_during_charging =
|
||||
hw_caps.supports_changing_phases_during_charging;
|
||||
entry_import.limits_to_root.ac_number_of_active_phases = mod->ac_nr_phases_active;
|
||||
|
||||
if (mod->config.charge_mode == "DC") {
|
||||
// For DC, apply our power supply capabilities as limit on leaves side
|
||||
const auto caps = mod->get_powersupply_capabilities();
|
||||
entry_import.limits_to_leaves.total_power_W = {caps.max_export_power_W,
|
||||
source_base + "/clear_import_request_schedule"};
|
||||
// Note both sides are optionals
|
||||
entry_import.conversion_efficiency = caps.conversion_efficiency_export;
|
||||
}
|
||||
|
||||
energy_flow_request.schedule_import = std::vector<types::energy::ScheduleReqEntry>({entry_import});
|
||||
}
|
||||
|
||||
void energyImpl::clear_export_request_schedule() {
|
||||
types::energy::ScheduleReqEntry entry_export;
|
||||
const auto tpnow = date::utc_clock::now();
|
||||
const auto tp =
|
||||
Everest::Date::to_rfc3339(date::floor<std::chrono::hours>(tpnow) + date::get_leap_second_info(tpnow).elapsed);
|
||||
|
||||
entry_export.timestamp = tp;
|
||||
entry_export.limits_to_root.ac_max_phase_count = {hw_caps.max_phase_count_export, source_bsp_caps};
|
||||
entry_export.limits_to_root.ac_min_phase_count = {hw_caps.min_phase_count_export, source_bsp_caps};
|
||||
entry_export.limits_to_root.ac_max_current_A = {hw_caps.max_current_A_export, source_bsp_caps};
|
||||
entry_export.limits_to_root.ac_min_current_A = {hw_caps.min_current_A_export, source_bsp_caps};
|
||||
entry_export.limits_to_root.ac_supports_changing_phases_during_charging =
|
||||
hw_caps.supports_changing_phases_during_charging;
|
||||
entry_export.limits_to_root.ac_number_of_active_phases = mod->ac_nr_phases_active;
|
||||
|
||||
if (mod->config.charge_mode == "DC") {
|
||||
// For DC, apply our power supply capabilities as limit on leaves side
|
||||
const auto caps = mod->get_powersupply_capabilities();
|
||||
entry_export.limits_to_leaves.total_power_W = {caps.max_import_power_W.value_or(0), source_psu_caps};
|
||||
// Note that both sides are optionals
|
||||
entry_export.conversion_efficiency = caps.conversion_efficiency_import;
|
||||
}
|
||||
|
||||
energy_flow_request.schedule_export = std::vector<types::energy::ScheduleReqEntry>({entry_export});
|
||||
}
|
||||
|
||||
void energyImpl::clear_request_schedules() {
|
||||
this->clear_import_request_schedule();
|
||||
this->clear_export_request_schedule();
|
||||
}
|
||||
|
||||
void energyImpl::ready() {
|
||||
hw_caps = mod->get_hw_capabilities();
|
||||
last_powersupply_capabilities = mod->get_powersupply_capabilities();
|
||||
clear_request_schedules();
|
||||
|
||||
// request energy now
|
||||
request_energy_from_energy_manager(true);
|
||||
|
||||
// request energy every second
|
||||
std::thread([this] {
|
||||
while (true) {
|
||||
hw_caps = mod->get_hw_capabilities();
|
||||
request_energy_from_energy_manager(false);
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
}
|
||||
}).detach();
|
||||
|
||||
// request energy at the start and end of a charging session
|
||||
mod->charger->signal_state.connect([this](Charger::EvseState s) {
|
||||
charger_state = s;
|
||||
if (s == Charger::EvseState::WaitingForAuthentication || s == Charger::EvseState::Finished) {
|
||||
std::thread request_energy_thread([this]() { request_energy_from_energy_manager(true); });
|
||||
request_energy_thread.detach();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
types::energy::EvseState to_energy_evse_state(const Charger::EvseState charger_state) {
|
||||
switch (charger_state) {
|
||||
case Charger::EvseState::Disabled:
|
||||
return types::energy::EvseState::Disabled;
|
||||
break;
|
||||
case Charger::EvseState::Idle:
|
||||
return types::energy::EvseState::Unplugged;
|
||||
break;
|
||||
case Charger::EvseState::WaitingForAuthentication:
|
||||
return types::energy::EvseState::WaitForAuth;
|
||||
break;
|
||||
case Charger::EvseState::PrepareCharging:
|
||||
return types::energy::EvseState::PrepareCharging;
|
||||
break;
|
||||
case Charger::EvseState::Charging:
|
||||
return types::energy::EvseState::Charging;
|
||||
break;
|
||||
case Charger::EvseState::ChargingPausedEV:
|
||||
return types::energy::EvseState::PausedEV;
|
||||
break;
|
||||
case Charger::EvseState::ChargingPausedEVSE:
|
||||
return types::energy::EvseState::PausedEVSE;
|
||||
break;
|
||||
case Charger::EvseState::StoppingCharging:
|
||||
return types::energy::EvseState::Finished;
|
||||
break;
|
||||
case Charger::EvseState::Finished:
|
||||
return types::energy::EvseState::Finished;
|
||||
break;
|
||||
case Charger::EvseState::T_step_EF:
|
||||
return types::energy::EvseState::PrepareCharging;
|
||||
break;
|
||||
case Charger::EvseState::T_step_X1:
|
||||
return types::energy::EvseState::PrepareCharging;
|
||||
break;
|
||||
case Charger::EvseState::SwitchPhases:
|
||||
return types::energy::EvseState::Charging;
|
||||
break;
|
||||
}
|
||||
return types::energy::EvseState::Disabled;
|
||||
}
|
||||
|
||||
void energyImpl::request_energy_from_energy_manager(bool priority_request) {
|
||||
std::lock_guard<std::mutex> lock(this->energy_mutex);
|
||||
clear_import_request_schedule();
|
||||
clear_export_request_schedule();
|
||||
|
||||
// If we need energy, copy local limit schedules to energy_flow_request.
|
||||
if (charger_state == Charger::EvseState::Charging || charger_state == Charger::EvseState::PrepareCharging ||
|
||||
charger_state == Charger::EvseState::WaitingForAuthentication ||
|
||||
charger_state == Charger::EvseState::ChargingPausedEV || !mod->config.request_zero_power_in_idle) {
|
||||
|
||||
// copy complete external limit schedules for import
|
||||
if (not mod->get_local_energy_limits().schedule_import.empty()) {
|
||||
energy_flow_request.schedule_import = mod->get_local_energy_limits().schedule_import;
|
||||
|
||||
if (mod->config.charge_mode == "DC") {
|
||||
// For DC, apply our power supply capabilities as an additional limit on leaves side
|
||||
const auto caps = mod->get_powersupply_capabilities();
|
||||
for (auto& entry : energy_flow_request.schedule_import) {
|
||||
// Apply caps limit if we request a leaves side watt value and it is larger than the capabilities
|
||||
if (entry.limits_to_leaves.total_power_W.has_value() and
|
||||
entry.limits_to_leaves.total_power_W.value().value > caps.max_export_power_W) {
|
||||
entry.limits_to_leaves.total_power_W = {caps.max_export_power_W, source_psu_caps};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// apply our local hardware limits on root side
|
||||
for (auto& e : energy_flow_request.schedule_import) {
|
||||
if (!e.limits_to_root.ac_max_current_A.has_value() ||
|
||||
e.limits_to_root.ac_max_current_A.value().value > hw_caps.max_current_A_import) {
|
||||
e.limits_to_root.ac_max_current_A = {hw_caps.max_current_A_import, source_bsp_caps};
|
||||
|
||||
// are we in EV pause mode? -> Reduce requested current to minimum just to see when car
|
||||
// wants to start charging again. The energy manager may pause us externally to reduce to
|
||||
// zero
|
||||
if (charger_state == Charger::EvseState::ChargingPausedEV && mod->config.request_zero_power_in_idle) {
|
||||
e.limits_to_root.ac_max_current_A = {hw_caps.min_current_A_import, source_bsp_caps};
|
||||
}
|
||||
}
|
||||
|
||||
if (!e.limits_to_root.ac_max_phase_count.has_value() ||
|
||||
e.limits_to_root.ac_max_phase_count.value().value > hw_caps.max_phase_count_import)
|
||||
e.limits_to_root.ac_max_phase_count = {hw_caps.max_phase_count_import, source_bsp_caps};
|
||||
|
||||
// copy remaining hw limits on root side
|
||||
e.limits_to_root.ac_min_phase_count = {hw_caps.min_phase_count_import, source_bsp_caps};
|
||||
e.limits_to_root.ac_min_current_A = {hw_caps.min_current_A_import, source_bsp_caps};
|
||||
e.limits_to_root.ac_supports_changing_phases_during_charging =
|
||||
hw_caps.supports_changing_phases_during_charging;
|
||||
e.limits_to_root.ac_number_of_active_phases = mod->ac_nr_phases_active;
|
||||
}
|
||||
|
||||
// copy complete external limit schedules for export
|
||||
if (not mod->get_local_energy_limits().schedule_export.empty()) {
|
||||
energy_flow_request.schedule_export = mod->get_local_energy_limits().schedule_export;
|
||||
|
||||
if (mod->config.charge_mode == "DC") {
|
||||
// For DC, apply our power supply capabilities as an additional limit on leaves side
|
||||
const auto caps = mod->get_powersupply_capabilities();
|
||||
for (auto& entry : energy_flow_request.schedule_export) {
|
||||
if (entry.limits_to_leaves.total_power_W.has_value() and caps.max_import_power_W.has_value() and
|
||||
entry.limits_to_leaves.total_power_W.value().value > caps.max_import_power_W.value()) {
|
||||
entry.limits_to_leaves.total_power_W = {caps.max_import_power_W.value(), source_bsp_caps};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// apply our local hardware limits on root side
|
||||
for (auto& e : energy_flow_request.schedule_export) {
|
||||
if (!e.limits_to_root.ac_max_current_A.has_value() ||
|
||||
e.limits_to_root.ac_max_current_A.value().value > hw_caps.max_current_A_export) {
|
||||
e.limits_to_root.ac_max_current_A = {hw_caps.max_current_A_export, source_bsp_caps};
|
||||
|
||||
// are we in EV pause mode? -> Reduce requested current to minimum just to see when car
|
||||
// wants to start discharging again. The energy manager may pause us externally to reduce to
|
||||
// zero
|
||||
if (charger_state == Charger::EvseState::ChargingPausedEV) {
|
||||
e.limits_to_root.ac_max_current_A = {hw_caps.min_current_A_export, source_bsp_caps + "_pause"};
|
||||
}
|
||||
}
|
||||
|
||||
if (!e.limits_to_root.ac_max_phase_count.has_value() ||
|
||||
e.limits_to_root.ac_max_phase_count.value().value > hw_caps.max_phase_count_export)
|
||||
e.limits_to_root.ac_max_phase_count = {hw_caps.max_phase_count_export, source_bsp_caps};
|
||||
|
||||
// copy remaining hw limits on root side
|
||||
e.limits_to_root.ac_min_phase_count = {hw_caps.min_phase_count_export, source_bsp_caps};
|
||||
e.limits_to_root.ac_min_current_A = {hw_caps.min_current_A_export, source_bsp_caps};
|
||||
e.limits_to_root.ac_supports_changing_phases_during_charging =
|
||||
hw_caps.supports_changing_phases_during_charging;
|
||||
e.limits_to_root.ac_number_of_active_phases = mod->ac_nr_phases_active;
|
||||
}
|
||||
|
||||
// Cap by PP cable rating so the EnergyManager cannot allocate more than the cable allows
|
||||
if (mod->config.charge_mode == "AC") {
|
||||
const auto pp_rating = mod->bsp->read_pp_ampacity();
|
||||
if (pp_rating) {
|
||||
const std::string source_pp = source_base + "/pp_ampacity";
|
||||
for (auto& e : energy_flow_request.schedule_import) {
|
||||
if (e.limits_to_root.ac_max_current_A.has_value() &&
|
||||
e.limits_to_root.ac_max_current_A.value().value > pp_rating.value()) {
|
||||
e.limits_to_root.ac_max_current_A = {static_cast<float>(pp_rating.value()), source_pp};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (mod->config.charge_mode == "DC") {
|
||||
// For DC mode remove amp limit on leave side if any
|
||||
for (auto& e : energy_flow_request.schedule_import) {
|
||||
e.limits_to_leaves.ac_max_current_A.reset();
|
||||
}
|
||||
for (auto& e : energy_flow_request.schedule_export) {
|
||||
e.limits_to_leaves.ac_max_current_A.reset();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (mod->config.charge_mode == "DC") {
|
||||
// we dont need power at the moment
|
||||
energy_flow_request.schedule_import[0].limits_to_leaves.total_power_W = {0., "Idle"};
|
||||
energy_flow_request.schedule_export[0].limits_to_leaves.total_power_W = {0., "Idle"};
|
||||
} else {
|
||||
energy_flow_request.schedule_import[0].limits_to_leaves.ac_max_current_A = {0., "Idle"};
|
||||
energy_flow_request.schedule_export[0].limits_to_leaves.ac_max_current_A = {0., "Idle"};
|
||||
}
|
||||
}
|
||||
|
||||
if (priority_request) {
|
||||
energy_flow_request.priority_request = true;
|
||||
} else {
|
||||
energy_flow_request.priority_request = false;
|
||||
}
|
||||
|
||||
// Attach our state
|
||||
energy_flow_request.evse_state = to_energy_evse_state(charger_state);
|
||||
|
||||
publish_energy_flow_request(energy_flow_request);
|
||||
// EVLOG_info << "Outgoing request " << energy_flow_request;
|
||||
}
|
||||
|
||||
static bool almost_eq(float a, float b) {
|
||||
return a > b - 0.1 and a < b + 0.1;
|
||||
}
|
||||
|
||||
static bool almost_eq(std::optional<float> const& a, std::optional<float> const& b) {
|
||||
if (a.has_value() and b.has_value()) {
|
||||
return almost_eq(a.value(), b.value());
|
||||
}
|
||||
if (not a.has_value() and not b.has_value()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool almost_eq(types::power_supply_DC::Capabilities const& lhs,
|
||||
types::power_supply_DC::Capabilities const& rhs) {
|
||||
bool result = lhs.bidirectional == rhs.bidirectional and
|
||||
almost_eq(lhs.current_regulation_tolerance_A, rhs.current_regulation_tolerance_A) and
|
||||
almost_eq(lhs.peak_current_ripple_A, rhs.peak_current_ripple_A) and
|
||||
almost_eq(lhs.max_export_voltage_V, rhs.max_export_voltage_V) and
|
||||
almost_eq(lhs.min_export_voltage_V, rhs.min_export_voltage_V) and
|
||||
almost_eq(lhs.max_export_current_A, rhs.max_export_current_A) and
|
||||
almost_eq(lhs.min_export_current_A, rhs.min_export_current_A) and
|
||||
almost_eq(lhs.max_export_power_W, rhs.max_export_power_W) and
|
||||
almost_eq(lhs.max_import_voltage_V, rhs.max_import_voltage_V) and
|
||||
almost_eq(lhs.min_import_voltage_V, rhs.min_import_voltage_V) and
|
||||
almost_eq(lhs.max_import_current_A, rhs.max_import_current_A) and
|
||||
almost_eq(lhs.min_import_current_A, rhs.min_import_current_A) and
|
||||
almost_eq(lhs.max_import_power_W, rhs.max_import_power_W) and
|
||||
almost_eq(lhs.conversion_efficiency_import, rhs.conversion_efficiency_import) and
|
||||
almost_eq(lhs.conversion_efficiency_export, rhs.conversion_efficiency_export);
|
||||
return result;
|
||||
}
|
||||
|
||||
// This is the decision logic when limits are changing.
|
||||
bool energyImpl::random_delay_needed(float last_limit, float limit) {
|
||||
|
||||
if (mod->config.uk_smartcharging_random_delay_at_any_change) {
|
||||
if (not almost_eq(last_limit, limit)) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
if (almost_eq(last_limit, 0.) and limit > 0.1) {
|
||||
return true;
|
||||
} else if (last_limit > 0.1 and almost_eq(limit, 0.)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Are we starting up with a car attached? This will need a random delay as well
|
||||
if ((charger_state == Charger::EvseState::PrepareCharging or charger_state == Charger::EvseState::Charging or
|
||||
charger_state == Charger::EvseState::WaitingForAuthentication) and
|
||||
std::chrono::steady_clock::now() - mod->timepoint_ready_for_charging.load() <
|
||||
detect_startup_with_ev_attached_duration) {
|
||||
last_enforced_limit = 0.;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void energyImpl::handle_enforce_limits(types::energy::EnforcedLimits& value) {
|
||||
if (value.uuid == energy_flow_request.uuid) {
|
||||
// EVLOG_info << "Incoming enforce limits" << value;
|
||||
|
||||
// set hardware limit
|
||||
float limit = 0.;
|
||||
int active_phasecount = mod->ac_nr_phases_active;
|
||||
|
||||
// apply enforced limits
|
||||
|
||||
// set enforced AC current limit
|
||||
if (value.limits_root_side.ac_max_current_A.has_value()) {
|
||||
limit = value.limits_root_side.ac_max_current_A.value().value;
|
||||
}
|
||||
|
||||
// apply number of phase limit
|
||||
if (value.limits_root_side.ac_max_phase_count.has_value() &&
|
||||
value.limits_root_side.ac_max_phase_count.value().value not_eq active_phasecount) {
|
||||
if (mod->get_hw_capabilities().supports_changing_phases_during_charging) {
|
||||
if (mod->charger->switch_three_phases_while_charging(
|
||||
value.limits_root_side.ac_max_phase_count.value().value == 3)) {
|
||||
mod->ac_nr_phases_active = value.limits_root_side.ac_max_phase_count.value().value;
|
||||
EVLOG_info << fmt::format("3ph/1ph: Switching #ph from {} to {}", active_phasecount,
|
||||
value.limits_root_side.ac_max_phase_count.value().value);
|
||||
} else {
|
||||
EVLOG_warning << fmt::format(
|
||||
"3ph/1ph: Energymanager requests switching #ph from {} to {}, ignored.", active_phasecount,
|
||||
value.limits_root_side.ac_max_phase_count.value().value);
|
||||
}
|
||||
} else {
|
||||
EVLOG_error << fmt::format(
|
||||
"Energy manager requests switching #ph from {} to {}, but switching phases during "
|
||||
"charging is not supported by HW.",
|
||||
active_phasecount, value.limits_root_side.ac_max_phase_count.value().value);
|
||||
}
|
||||
}
|
||||
|
||||
// apply watt limit
|
||||
if (value.limits_root_side.total_power_W.has_value()) {
|
||||
mod->mqtt.publish(fmt::format("everest_external/nodered/{}/state/max_watt", mod->config.connector_id),
|
||||
value.limits_root_side.total_power_W.value().value);
|
||||
// watt limit converted to current limit
|
||||
const float current_limit_power = value.limits_root_side.total_power_W.value().value /
|
||||
mod->config.ac_nominal_voltage / mod->ac_nr_phases_active;
|
||||
if ((limit >= 0 and limit > current_limit_power) or (limit < 0 and limit < current_limit_power)) {
|
||||
limit = current_limit_power;
|
||||
}
|
||||
}
|
||||
|
||||
const auto enforced_limit = limit;
|
||||
|
||||
// check if we need to add a random delay for UK smart charging regs
|
||||
if (mod->random_delay_enabled) {
|
||||
|
||||
// Are we in a state where a random delay makes sense?
|
||||
if (not(charger_state == Charger::EvseState::PrepareCharging or
|
||||
charger_state == Charger::EvseState::Charging or
|
||||
charger_state == Charger::EvseState::WaitingForAuthentication)) {
|
||||
mod->random_delay_running = false;
|
||||
}
|
||||
|
||||
// Do we need to start a new random delay?
|
||||
// Ignore changes of less then 0.1 amps
|
||||
if (not mod->random_delay_running and random_delay_needed(last_enforced_limit, limit)) {
|
||||
mod->random_delay_running = true;
|
||||
mod->random_delay_start_time = date::utc_clock::now();
|
||||
auto random_delay_s = std::rand() % mod->random_delay_max_duration.load().count();
|
||||
mod->random_delay_end_time = std::chrono::steady_clock::now() + std::chrono::seconds(random_delay_s);
|
||||
EVLOG_info << "UK Smart Charging regulations: Starting random delay of " << random_delay_s << "s";
|
||||
limit_when_random_delay_started = last_enforced_limit;
|
||||
}
|
||||
|
||||
// If a delay is running, replace the current limit with the stored value
|
||||
if (mod->random_delay_running) {
|
||||
// use limit from the time point when the random delay started
|
||||
limit = limit_when_random_delay_started;
|
||||
// publish the current random delay timer
|
||||
auto seconds_left = std::chrono::duration_cast<std::chrono::seconds>(mod->random_delay_end_time -
|
||||
std::chrono::steady_clock::now())
|
||||
.count();
|
||||
types::uk_random_delay::CountDown c;
|
||||
c.current_limit_after_delay_A = enforced_limit;
|
||||
c.current_limit_during_delay_A = limit_when_random_delay_started;
|
||||
if (seconds_left <= 0) {
|
||||
EVLOG_info << "UK Smart Charging regulations: Random delay elapsed.";
|
||||
c.countdown_s = 0;
|
||||
mod->random_delay_running = false;
|
||||
} else {
|
||||
EVLOG_debug << "Random delay running, " << seconds_left
|
||||
<< "s left. Applying the limit before the random delay ("
|
||||
<< limit_when_random_delay_started << "A) instead of requested limit ("
|
||||
<< enforced_limit << "A)";
|
||||
c.countdown_s = seconds_left;
|
||||
c.start_time = Everest::Date::to_rfc3339(mod->random_delay_start_time);
|
||||
}
|
||||
mod->p_random_delay->publish_countdown(c);
|
||||
} else {
|
||||
types::uk_random_delay::CountDown c;
|
||||
c.countdown_s = 0;
|
||||
c.current_limit_after_delay_A = enforced_limit;
|
||||
c.current_limit_during_delay_A = limit_when_random_delay_started;
|
||||
mod->p_random_delay->publish_countdown(c);
|
||||
}
|
||||
}
|
||||
|
||||
last_enforced_limit = enforced_limit;
|
||||
|
||||
// publish for e.g. OCPP module with the updated limit
|
||||
if (value.limits_root_side.ac_max_current_A) {
|
||||
// update based on the PP cable rating
|
||||
const auto pp_rating = mod->bsp->read_pp_ampacity();
|
||||
if (pp_rating) {
|
||||
types::energy::NumberWithSource nws_updated{std::min(limit, static_cast<float>(pp_rating.value())),
|
||||
value.limits_root_side.ac_max_current_A.value().source};
|
||||
value.limits_root_side.ac_max_current_A = std::move(nws_updated);
|
||||
}
|
||||
}
|
||||
mod->p_evse->publish_enforced_limits(value);
|
||||
|
||||
// update limit at the charger
|
||||
const auto valid_until = steady_clock::now() + seconds(value.valid_for);
|
||||
if (limit >= 0) {
|
||||
// import
|
||||
mod->charger->set_max_current(limit, valid_until);
|
||||
} else {
|
||||
// export
|
||||
if (mod->session_is_iso_d20_ac_bpt()) {
|
||||
mod->charger->set_max_current(limit, valid_until);
|
||||
} else {
|
||||
// FIXME: we cannot discharge on PWM charging or with -2, so we fake a charging current here.
|
||||
mod->charger->set_max_current(0, valid_until);
|
||||
}
|
||||
}
|
||||
|
||||
mod->signalNrOfPhasesAvailable(mod->ac_nr_phases_active);
|
||||
|
||||
if (mod->config.charge_mode == "DC") {
|
||||
// DC mode apply limit at the leave side, we get root side limits here from EnergyManager on ACDC!
|
||||
// FIXME: multiply by conversion_efficiency here!
|
||||
if (value.limits_root_side.total_power_W.has_value() and
|
||||
value.limits_root_side.ac_max_current_A.has_value()) {
|
||||
float watt_leave_side = value.limits_root_side.total_power_W.value().value;
|
||||
float ampere_root_side = value.limits_root_side.ac_max_current_A.value().value;
|
||||
|
||||
auto ev_info = mod->get_ev_info();
|
||||
float target_voltage = ev_info.target_voltage.value_or(0.);
|
||||
float actual_voltage = ev_info.present_voltage.value_or(0.);
|
||||
|
||||
bool values_changed = true;
|
||||
auto powersupply_capabilities = mod->get_powersupply_capabilities();
|
||||
|
||||
// did the values change since the last call?
|
||||
if (almost_eq(last_enforced_limits_watt, watt_leave_side) and
|
||||
almost_eq(last_enforced_limits_ampere, ampere_root_side) and
|
||||
almost_eq(target_voltage, last_target_voltage) and
|
||||
not voltage_changed(actual_voltage, last_actual_voltage) and
|
||||
almost_eq(powersupply_capabilities, last_powersupply_capabilities)) {
|
||||
values_changed = false;
|
||||
}
|
||||
|
||||
if (values_changed) {
|
||||
last_enforced_limits_ampere = ampere_root_side;
|
||||
last_enforced_limits_watt = watt_leave_side;
|
||||
last_target_voltage = target_voltage;
|
||||
last_actual_voltage = actual_voltage;
|
||||
last_powersupply_capabilities = powersupply_capabilities;
|
||||
|
||||
// tell car our new limits
|
||||
types::iso15118::DcEvseMaximumLimits evse_max_limits;
|
||||
types::iso15118::DcEvseMinimumLimits evse_min_limits;
|
||||
|
||||
// Current Limits (min & max)
|
||||
evse_max_limits.evse_maximum_current_limit = powersupply_capabilities.max_export_current_A;
|
||||
evse_max_limits.evse_maximum_discharge_current_limit =
|
||||
powersupply_capabilities.max_import_current_A;
|
||||
|
||||
evse_min_limits.evse_minimum_current_limit = powersupply_capabilities.min_export_current_A;
|
||||
evse_min_limits.evse_minimum_discharge_current_limit =
|
||||
powersupply_capabilities.min_import_current_A;
|
||||
|
||||
float total_current{0.0};
|
||||
|
||||
if (target_voltage > 10) {
|
||||
// we use target_voltage here to calculate current limit.
|
||||
// If target_voltage is a lot higher then the actual voltage the
|
||||
// current limit is too low, i.e. charging will not reach the actual watt value.
|
||||
// FIXME: we could use some magic here that involves actual measured voltage as well.
|
||||
if (actual_voltage > 10) {
|
||||
total_current = watt_leave_side / actual_voltage;
|
||||
} else {
|
||||
total_current = watt_leave_side / target_voltage;
|
||||
}
|
||||
} else {
|
||||
total_current = powersupply_capabilities.max_export_current_A;
|
||||
}
|
||||
|
||||
if (total_current >= 0.0) {
|
||||
evse_max_limits.evse_maximum_current_limit =
|
||||
std::min(total_current, powersupply_capabilities.max_export_current_A);
|
||||
} else {
|
||||
evse_max_limits.evse_maximum_discharge_current_limit.emplace(std::fabs(total_current));
|
||||
if (powersupply_capabilities.max_import_current_A.has_value() and
|
||||
std::fabs(total_current) > powersupply_capabilities.max_import_current_A.value()) {
|
||||
evse_max_limits.evse_maximum_discharge_current_limit =
|
||||
powersupply_capabilities.max_import_current_A;
|
||||
}
|
||||
}
|
||||
|
||||
// Power limits (min & max)
|
||||
evse_max_limits.evse_maximum_power_limit = powersupply_capabilities.max_export_power_W;
|
||||
evse_max_limits.evse_maximum_discharge_power_limit = powersupply_capabilities.max_import_power_W;
|
||||
|
||||
evse_min_limits.evse_minimum_power_limit =
|
||||
powersupply_capabilities.min_export_voltage_V * powersupply_capabilities.min_export_current_A;
|
||||
evse_min_limits.evse_minimum_discharge_power_limit =
|
||||
powersupply_capabilities.min_import_voltage_V.value_or(0.0) *
|
||||
powersupply_capabilities.min_import_current_A.value_or(0.0);
|
||||
|
||||
if (watt_leave_side >= 0) {
|
||||
evse_max_limits.evse_maximum_power_limit =
|
||||
std::min(watt_leave_side, powersupply_capabilities.max_export_power_W);
|
||||
} else {
|
||||
evse_max_limits.evse_maximum_discharge_power_limit.emplace(std::fabs(watt_leave_side));
|
||||
if (powersupply_capabilities.max_import_power_W.has_value() and
|
||||
std::fabs(watt_leave_side) > powersupply_capabilities.max_import_power_W.value()) {
|
||||
evse_max_limits.evse_maximum_discharge_power_limit =
|
||||
powersupply_capabilities.max_import_power_W;
|
||||
}
|
||||
}
|
||||
|
||||
// Voltage limits (min & max)
|
||||
evse_max_limits.evse_maximum_voltage_limit = powersupply_capabilities.max_export_voltage_V;
|
||||
evse_min_limits.evse_minimum_voltage_limit = powersupply_capabilities.min_export_voltage_V;
|
||||
|
||||
// FIXME: we tell the ISO stack positive numbers for DIN spec and ISO-2 here in case of exporting to
|
||||
// grid. This needs to be fixed in the transition to -20 for BPT.
|
||||
|
||||
mod->is_actually_exporting_to_grid = false;
|
||||
if (watt_leave_side < 0 and total_current < 0 and
|
||||
evse_max_limits.evse_maximum_discharge_power_limit.has_value() and
|
||||
evse_max_limits.evse_maximum_discharge_current_limit.has_value()) {
|
||||
|
||||
// we are exporting power back to the grid
|
||||
if (mod->config.hack_allow_bpt_with_iso2) {
|
||||
evse_max_limits.evse_maximum_power_limit =
|
||||
evse_max_limits.evse_maximum_discharge_power_limit.value();
|
||||
evse_max_limits.evse_maximum_current_limit =
|
||||
evse_max_limits.evse_maximum_discharge_current_limit.value();
|
||||
mod->is_actually_exporting_to_grid = true;
|
||||
} else if (mod->sae_bidi_active) {
|
||||
evse_max_limits.evse_maximum_power_limit =
|
||||
-evse_max_limits.evse_maximum_discharge_power_limit.value();
|
||||
evse_max_limits.evse_maximum_current_limit =
|
||||
-evse_max_limits.evse_maximum_discharge_current_limit.value();
|
||||
mod->is_actually_exporting_to_grid = true;
|
||||
} else if (mod->session_is_iso_d20_dc_bpt()) {
|
||||
mod->is_actually_exporting_to_grid = true;
|
||||
} else {
|
||||
EVLOG_error << "Bidirectional export back to grid requested, but not supported.";
|
||||
evse_max_limits.evse_maximum_power_limit = 0.;
|
||||
evse_max_limits.evse_maximum_current_limit = 0.;
|
||||
evse_max_limits.evse_maximum_discharge_power_limit = 0.;
|
||||
evse_max_limits.evse_maximum_discharge_current_limit = 0.;
|
||||
}
|
||||
}
|
||||
|
||||
session_log.evse(
|
||||
true, fmt::format(
|
||||
"Change HLC Limits: {}W/{}A, target_voltage {}, actual_voltage {}, bpt_active {}",
|
||||
evse_max_limits.evse_maximum_power_limit, evse_max_limits.evse_maximum_current_limit,
|
||||
target_voltage, actual_voltage, mod->is_actually_exporting_to_grid));
|
||||
mod->r_hlc[0]->call_update_dc_maximum_limits(evse_max_limits);
|
||||
mod->r_hlc[0]->call_update_dc_minimum_limits(evse_min_limits);
|
||||
mod->charger->inform_new_evse_max_hlc_limits(evse_max_limits);
|
||||
mod->charger->inform_new_evse_min_hlc_limits(evse_min_limits);
|
||||
|
||||
// This is just neccessary to switch between charging and discharging
|
||||
if (target_voltage > 0) {
|
||||
mod->apply_new_target_voltage_current();
|
||||
}
|
||||
|
||||
// Note: If the limits are lower then before, we could tell the DC power supply to
|
||||
// ramp down already here instead of waiting for the car to request less power.
|
||||
// Some cars may not like it, so we wait for the car to request less for now.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace energy_grid
|
||||
} // namespace module
|
||||
@@ -0,0 +1,83 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
#ifndef ENERGY_GRID_ENERGY_IMPL_HPP
|
||||
#define ENERGY_GRID_ENERGY_IMPL_HPP
|
||||
|
||||
//
|
||||
// AUTO GENERATED - MARKED REGIONS WILL BE KEPT
|
||||
// template version 3
|
||||
//
|
||||
|
||||
#include <generated/interfaces/energy/Implementation.hpp>
|
||||
|
||||
#include "../EvseManager.hpp"
|
||||
|
||||
// ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1
|
||||
#include "utils/thread.hpp"
|
||||
// ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1
|
||||
|
||||
namespace module {
|
||||
namespace energy_grid {
|
||||
|
||||
struct Conf {};
|
||||
|
||||
class energyImpl : public energyImplBase {
|
||||
public:
|
||||
energyImpl() = delete;
|
||||
energyImpl(Everest::ModuleAdapter* ev, const Everest::PtrContainer<EvseManager>& mod, Conf& config) :
|
||||
energyImplBase(ev, "energy_grid"), mod(mod), config(config){};
|
||||
|
||||
// ev@8ea32d28-373f-4c90-ae5e-b4fcc74e2a61:v1
|
||||
// insert your public definitions here
|
||||
// ev@8ea32d28-373f-4c90-ae5e-b4fcc74e2a61:v1
|
||||
|
||||
protected:
|
||||
// command handler functions (virtual)
|
||||
virtual void handle_enforce_limits(types::energy::EnforcedLimits& value) override;
|
||||
|
||||
// ev@d2d1847a-7b88-41dd-ad07-92785f06f5c4:v1
|
||||
// insert your protected definitions here
|
||||
// ev@d2d1847a-7b88-41dd-ad07-92785f06f5c4:v1
|
||||
|
||||
private:
|
||||
const Everest::PtrContainer<EvseManager>& mod;
|
||||
const Conf& config;
|
||||
|
||||
virtual void init() override;
|
||||
virtual void ready() override;
|
||||
|
||||
// ev@3370e4dd-95f4-47a9-aaec-ea76f34a66c9:v1
|
||||
std::mutex energy_mutex;
|
||||
bool random_delay_needed(float last_limit, float limit);
|
||||
// types::energy_price_information::PricePerkWh price_limit;
|
||||
// types::energy::OptimizerTarget optimizer_target;
|
||||
types::energy::EnergyFlowRequest energy_flow_request;
|
||||
float last_enforced_limits_ampere{-9999};
|
||||
float last_enforced_limits_watt{-9999};
|
||||
float last_target_voltage{-9999};
|
||||
float last_actual_voltage{-9999};
|
||||
types::power_supply_DC::Capabilities last_powersupply_capabilities;
|
||||
void clear_import_request_schedule();
|
||||
void clear_export_request_schedule();
|
||||
void clear_request_schedules();
|
||||
void request_energy_from_energy_manager(bool priority_request);
|
||||
types::evse_board_support::HardwareCapabilities hw_caps;
|
||||
float last_enforced_limit{0.};
|
||||
float limit_when_random_delay_started{0.};
|
||||
std::atomic<Charger::EvseState> charger_state;
|
||||
static constexpr std::chrono::seconds detect_startup_with_ev_attached_duration{5};
|
||||
|
||||
std::string source_base;
|
||||
std::string source_bsp_caps;
|
||||
std::string source_psu_caps;
|
||||
// ev@3370e4dd-95f4-47a9-aaec-ea76f34a66c9:v1
|
||||
};
|
||||
|
||||
// ev@3d7da0ad-02c2-493d-9920-0bbbd56b9876:v1
|
||||
// insert other definitions here
|
||||
// ev@3d7da0ad-02c2-493d-9920-0bbbd56b9876:v1
|
||||
|
||||
} // namespace energy_grid
|
||||
} // namespace module
|
||||
|
||||
#endif // ENERGY_GRID_ENERGY_IMPL_HPP
|
||||
@@ -0,0 +1,501 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
#include "evse_managerImpl.hpp"
|
||||
#include <utils/date.hpp>
|
||||
|
||||
#include <date/date.h>
|
||||
#include <date/tz.h>
|
||||
#include <utils/date.hpp>
|
||||
|
||||
#include <fmt/core.h>
|
||||
|
||||
#include "SessionLog.hpp"
|
||||
|
||||
namespace module {
|
||||
|
||||
namespace evse {
|
||||
|
||||
bool str_to_bool(const std::string& data) {
|
||||
if (data == "true") {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void evse_managerImpl::init() {
|
||||
limits.nr_of_phases_available = 1;
|
||||
limits.max_current = 0.;
|
||||
|
||||
mod->signalNrOfPhasesAvailable.connect([this](const int n) {
|
||||
if (n >= 1 && n <= 3) {
|
||||
limits.nr_of_phases_available = n;
|
||||
publish_limits(limits);
|
||||
}
|
||||
});
|
||||
|
||||
// Interface to Node-RED debug UI
|
||||
|
||||
mod->mqtt.subscribe(fmt::format("everest_external/nodered/{}/cmd/enable", mod->config.connector_id),
|
||||
[&charger = mod->charger](const std::string& data) {
|
||||
charger->enable_disable(0, {types::evse_manager::Enable_source::LocalAPI,
|
||||
types::evse_manager::Enable_state::Enable, 100});
|
||||
});
|
||||
|
||||
mod->mqtt.subscribe(fmt::format("everest_external/nodered/{}/cmd/disable", mod->config.connector_id),
|
||||
[&charger = mod->charger](const std::string& data) {
|
||||
charger->enable_disable(0, {types::evse_manager::Enable_source::LocalAPI,
|
||||
types::evse_manager::Enable_state::Disable, 100});
|
||||
});
|
||||
|
||||
mod->mqtt.subscribe(
|
||||
fmt::format("everest_external/nodered/{}/cmd/switch_three_phases_while_charging", mod->config.connector_id),
|
||||
[&charger = mod->charger](const std::string& data) {
|
||||
charger->switch_three_phases_while_charging(str_to_bool(data));
|
||||
});
|
||||
|
||||
mod->mqtt.subscribe(fmt::format("everest_external/nodered/{}/cmd/pause_charging", mod->config.connector_id),
|
||||
[&charger = mod->charger](const std::string& data) { charger->pause_charging(); });
|
||||
|
||||
mod->mqtt.subscribe(fmt::format("everest_external/nodered/{}/cmd/resume_charging", mod->config.connector_id),
|
||||
[&charger = mod->charger](const std::string& data) { charger->resume_charging(); });
|
||||
|
||||
// /Interface to Node-RED debug UI
|
||||
|
||||
if (mod->r_powermeter_billing().size() > 0) {
|
||||
mod->r_powermeter_billing()[0]->subscribe_powermeter([this](const types::powermeter::Powermeter& p) {
|
||||
// Republish data on proxy powermeter struct
|
||||
publish_powermeter(p);
|
||||
});
|
||||
mod->r_powermeter_billing()[0]->subscribe_public_key_ocmf([this](const std::string& public_key_ocmf) {
|
||||
// Republish data on proxy powermeter public_key_ocmf
|
||||
publish_powermeter_public_key_ocmf(public_key_ocmf);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void evse_managerImpl::ready() {
|
||||
|
||||
// publish evse id at least once
|
||||
publish_evse_id(mod->config.evse_id);
|
||||
|
||||
mod->r_bsp->subscribe_telemetry([this](types::evse_board_support::Telemetry telemetry) {
|
||||
// external Nodered interface
|
||||
mod->mqtt.publish(fmt::format("everest_external/nodered/{}/state/temperature", mod->config.connector_id),
|
||||
telemetry.evse_temperature_C);
|
||||
// external Nodered interface
|
||||
publish_telemetry(telemetry);
|
||||
});
|
||||
|
||||
// The module code generates the reservation events and we merely publish them here
|
||||
mod->signalReservationEvent.connect([this](types::evse_manager::SessionEvent j) {
|
||||
j.uuid.clear(); // Reservation is not part of a session
|
||||
publish_session_event(j);
|
||||
});
|
||||
|
||||
mod->charger->signal_session_started_event.connect(
|
||||
[this](const types::evse_manager::StartSessionReason& start_reason,
|
||||
const std::optional<types::authorization::ProvidedIdToken>& provided_id_token) {
|
||||
types::evse_manager::SessionEvent se;
|
||||
se.event = types::evse_manager::SessionEventEnum::SessionStarted;
|
||||
this->mod->selected_protocol = "IEC61851-1";
|
||||
types::evse_manager::SessionStarted session_started;
|
||||
|
||||
se.timestamp = Everest::Date::to_rfc3339(date::utc_clock::now());
|
||||
session_started.meter_value = mod->get_latest_powermeter_data_billing();
|
||||
|
||||
if (mod->config.disable_authentication &&
|
||||
start_reason == types::evse_manager::StartSessionReason::EVConnected) {
|
||||
|
||||
// Free service, authorize immediately in a separate thread (to avoid dead lock with charger state
|
||||
// machine, this signal handler runs in state machine context)
|
||||
std::thread authorize_thread([this]() {
|
||||
types::authorization::ProvidedIdToken provided_token;
|
||||
provided_token.authorization_type = types::authorization::AuthorizationType::RFID;
|
||||
provided_token.id_token = {"FREESERVICE", types::authorization::IdTokenType::Local};
|
||||
provided_token.prevalidated = true;
|
||||
mod->charger->authorize(true, provided_token,
|
||||
{types::authorization::AuthorizationStatus::Accepted});
|
||||
mod->charger_was_authorized();
|
||||
});
|
||||
authorize_thread.detach();
|
||||
}
|
||||
|
||||
session_started.reason = start_reason;
|
||||
const auto session_uuid = this->mod->charger->get_session_id();
|
||||
session_started.meter_value = mod->get_latest_powermeter_data_billing();
|
||||
session_started.id_tag = provided_id_token;
|
||||
if (mod->is_reserved()) {
|
||||
session_started.reservation_id = mod->get_reservation_id();
|
||||
if (start_reason == types::evse_manager::StartSessionReason::Authorized) {
|
||||
this->mod->cancel_reservation(false);
|
||||
}
|
||||
}
|
||||
|
||||
const auto logging_path = session_log.startSession(
|
||||
mod->config.logfile_suffix == "session_uuid" ? session_uuid : mod->config.logfile_suffix);
|
||||
|
||||
if (logging_path.has_value()) {
|
||||
session_started.logging_path = logging_path.value().string();
|
||||
}
|
||||
session_log.evse(false, fmt::format("Session Started: {}",
|
||||
types::evse_manager::start_session_reason_to_string(start_reason)));
|
||||
|
||||
mod->telemetry.publish("session", "events",
|
||||
{
|
||||
{"timestamp", Everest::Date::to_rfc3339(date::utc_clock::now())},
|
||||
{"type", "session_started"},
|
||||
{"session_id", session_uuid},
|
||||
{"reason", types::evse_manager::start_session_reason_to_string(start_reason)},
|
||||
});
|
||||
|
||||
se.session_started = session_started;
|
||||
se.uuid = session_uuid;
|
||||
publish_session_event(se);
|
||||
});
|
||||
|
||||
mod->charger->signal_transaction_started_event.connect([this](
|
||||
const types::authorization::ProvidedIdToken& id_token) {
|
||||
types::evse_manager::SessionEvent se;
|
||||
se.event = types::evse_manager::SessionEventEnum::TransactionStarted;
|
||||
types::evse_manager::TransactionStarted transaction_started;
|
||||
se.timestamp = Everest::Date::to_rfc3339(date::utc_clock::now());
|
||||
|
||||
transaction_started.meter_value = mod->get_latest_powermeter_data_billing();
|
||||
if (mod->is_reserved()) {
|
||||
transaction_started.reservation_id.emplace(mod->get_reservation_id());
|
||||
mod->cancel_reservation(false); // this allows OCPP1.6 to not move back to available.
|
||||
}
|
||||
|
||||
transaction_started.id_tag = id_token;
|
||||
|
||||
double energy_import = transaction_started.meter_value.energy_Wh_import.total;
|
||||
|
||||
session_log.evse(false, fmt::format("Transaction Started ({} kWh)", energy_import / 1000.));
|
||||
const auto session_uuid = this->mod->charger->get_session_id();
|
||||
|
||||
Everest::TelemetryMap telemetry_data = {
|
||||
{"timestamp", Everest::Date::to_rfc3339(date::utc_clock::now())},
|
||||
{"type", "transaction_started"},
|
||||
{"session_id", session_uuid},
|
||||
{"energy_counter_import_wh", transaction_started.meter_value.energy_Wh_import.total},
|
||||
{"id_tag", transaction_started.id_tag.id_token.value}};
|
||||
|
||||
if (transaction_started.meter_value.energy_Wh_export.has_value()) {
|
||||
telemetry_data["energy_counter_export_wh"] = transaction_started.meter_value.energy_Wh_export.value().total;
|
||||
}
|
||||
mod->telemetry.publish("session", "events", telemetry_data);
|
||||
|
||||
se.transaction_started.emplace(transaction_started);
|
||||
se.uuid = session_uuid;
|
||||
publish_session_event(se);
|
||||
});
|
||||
|
||||
mod->charger->signal_transaction_finished_event.connect(
|
||||
[this](const types::evse_manager::StopTransactionReason& finished_reason,
|
||||
std::optional<types::authorization::ProvidedIdToken> finish_token) {
|
||||
types::evse_manager::SessionEvent se;
|
||||
|
||||
se.event = types::evse_manager::SessionEventEnum::TransactionFinished;
|
||||
this->mod->selected_protocol = "Unknown";
|
||||
types::evse_manager::TransactionFinished transaction_finished;
|
||||
|
||||
se.timestamp = Everest::Date::to_rfc3339(date::utc_clock::now());
|
||||
|
||||
transaction_finished.meter_value = mod->get_latest_powermeter_data_billing();
|
||||
|
||||
transaction_finished.reason.emplace(finished_reason);
|
||||
transaction_finished.id_tag = finish_token;
|
||||
|
||||
double energy_import = transaction_finished.meter_value.energy_Wh_import.total;
|
||||
|
||||
session_log.evse(false, fmt::format("Transaction Finished: {} ({} kWh)",
|
||||
types::evse_manager::stop_transaction_reason_to_string(finished_reason),
|
||||
energy_import / 1000.));
|
||||
const auto session_uuid = this->mod->charger->get_session_id();
|
||||
Everest::TelemetryMap telemetry_data = {
|
||||
{"timestamp", Everest::Date::to_rfc3339(date::utc_clock::now())},
|
||||
{"type", "transaction_finished"},
|
||||
{"session_id", session_uuid},
|
||||
{"energy_counter_import_wh", energy_import},
|
||||
{"reason", types::evse_manager::stop_transaction_reason_to_string(finished_reason)}};
|
||||
|
||||
if (transaction_finished.meter_value.energy_Wh_export.has_value()) {
|
||||
telemetry_data["energy_counter_export_wh"] =
|
||||
transaction_finished.meter_value.energy_Wh_export.value().total;
|
||||
}
|
||||
|
||||
transaction_finished.start_signed_meter_value = mod->charger->get_start_signed_meter_value();
|
||||
transaction_finished.signed_meter_value = mod->charger->get_stop_signed_meter_value();
|
||||
mod->telemetry.publish("session", "events", telemetry_data);
|
||||
|
||||
se.transaction_finished.emplace(transaction_finished);
|
||||
se.uuid = session_uuid;
|
||||
|
||||
publish_session_event(se);
|
||||
});
|
||||
|
||||
mod->charger->signal_charging_paused_evse_event.connect(
|
||||
[this](types::evse_manager::ChargingPausedEVSEReasons reason) {
|
||||
types::evse_manager::SessionEvent se;
|
||||
|
||||
se.event = types::evse_manager::SessionEventEnum::ChargingPausedEVSE;
|
||||
se.timestamp = Everest::Date::to_rfc3339(date::utc_clock::now());
|
||||
|
||||
const auto session_uuid = this->mod->charger->get_session_id();
|
||||
|
||||
types::evse_manager::ChargingStateChangedEvent charging_state_changed_event;
|
||||
charging_state_changed_event.meter_value = mod->get_latest_powermeter_data_billing();
|
||||
se.charging_state_changed_event = charging_state_changed_event;
|
||||
|
||||
se.uuid = session_uuid;
|
||||
|
||||
se.charging_paused_evse = reason;
|
||||
|
||||
publish_session_event(se);
|
||||
});
|
||||
|
||||
mod->charger->signal_simple_event.connect([this](const types::evse_manager::SessionEventEnum& e) {
|
||||
types::evse_manager::SessionEvent se;
|
||||
|
||||
se.event = e;
|
||||
se.timestamp = Everest::Date::to_rfc3339(date::utc_clock::now());
|
||||
|
||||
const auto session_uuid = this->mod->charger->get_session_id();
|
||||
if (e == types::evse_manager::SessionEventEnum::SessionFinished) {
|
||||
types::evse_manager::SessionFinished session_finished;
|
||||
session_finished.meter_value = mod->get_latest_powermeter_data_billing();
|
||||
se.session_finished = session_finished;
|
||||
session_log.evse(false, fmt::format("Session Finished"));
|
||||
session_log.stopSession();
|
||||
mod->telemetry.publish("session", "events",
|
||||
{{"timestamp", Everest::Date::to_rfc3339(date::utc_clock::now())},
|
||||
{"type", "session_finished"},
|
||||
{"session_id", session_uuid}});
|
||||
} else if (e == types::evse_manager::SessionEventEnum::Enabled or
|
||||
e == types::evse_manager::SessionEventEnum::Disabled) {
|
||||
if (connector_status_changed) {
|
||||
se.connector_id = 1;
|
||||
}
|
||||
|
||||
// Add source information (Who initiated this state change)
|
||||
se.source = mod->charger->get_last_enable_disable_source();
|
||||
} else if (e == types::evse_manager::SessionEventEnum::ChargingPausedEV or
|
||||
e == types::evse_manager::SessionEventEnum::ChargingPausedEVSE or
|
||||
e == types::evse_manager::SessionEventEnum::ChargingStarted) {
|
||||
types::evse_manager::ChargingStateChangedEvent charging_state_changed_event;
|
||||
charging_state_changed_event.meter_value = mod->get_latest_powermeter_data_billing();
|
||||
se.charging_state_changed_event = charging_state_changed_event;
|
||||
} else if (e == types::evse_manager::SessionEventEnum::Authorized or
|
||||
e == types::evse_manager::SessionEventEnum::Deauthorized) {
|
||||
types::evse_manager::AuthorizationEvent authorization_event;
|
||||
authorization_event.meter_value = mod->get_latest_powermeter_data_billing();
|
||||
se.authorization_event = authorization_event;
|
||||
}
|
||||
|
||||
se.uuid = session_uuid;
|
||||
|
||||
publish_session_event(se);
|
||||
|
||||
if (e == types::evse_manager::SessionEventEnum::SessionFinished) {
|
||||
this->mod->selected_protocol = "Unknown";
|
||||
}
|
||||
|
||||
// Cancel reservations if charger is disabled
|
||||
if (mod->is_reserved() and e == types::evse_manager::SessionEventEnum::Disabled) {
|
||||
mod->cancel_reservation(true);
|
||||
}
|
||||
|
||||
publish_selected_protocol(this->mod->selected_protocol);
|
||||
});
|
||||
|
||||
mod->charger->signal_session_resumed_event.connect([this](const std::string& session_id) {
|
||||
types::evse_manager::SessionEvent session_event;
|
||||
session_event.uuid = session_id;
|
||||
session_event.timestamp = Everest::Date::to_rfc3339(date::utc_clock::now());
|
||||
session_event.event = types::evse_manager::SessionEventEnum::SessionResumed;
|
||||
publish_session_event(session_event);
|
||||
});
|
||||
|
||||
// Note: Deprecated. Only kept for Node red compatibility, will be removed in the future
|
||||
// Legacy external mqtt pubs
|
||||
mod->charger->signal_max_current.connect([this](float c) {
|
||||
mod->mqtt.publish(fmt::format("everest_external/nodered/{}/state/max_current", mod->config.connector_id), c);
|
||||
|
||||
limits.uuid = mod->info.id;
|
||||
limits.max_current = c;
|
||||
publish_limits(limits);
|
||||
});
|
||||
|
||||
mod->charger->signal_state.connect([this](Charger::EvseState s) {
|
||||
mod->mqtt.publish(fmt::format("everest_external/nodered/{}/state/state_string", mod->config.connector_id),
|
||||
mod->charger->evse_state_to_string(s));
|
||||
mod->mqtt.publish(fmt::format("everest_external/nodered/{}/state/state", mod->config.connector_id),
|
||||
static_cast<int>(s));
|
||||
});
|
||||
}
|
||||
|
||||
types::evse_manager::Evse evse_managerImpl::handle_get_evse() {
|
||||
types::evse_manager::Evse evse;
|
||||
evse.id = this->mod->config.connector_id;
|
||||
|
||||
std::vector<types::evse_manager::Connector> connectors;
|
||||
types::evse_manager::Connector connector;
|
||||
// EvseManager currently only supports a single connector with id: 1;
|
||||
connector.id = 1;
|
||||
connector.type = mod->connector_type;
|
||||
|
||||
connectors.push_back(connector);
|
||||
evse.connectors = connectors;
|
||||
return evse;
|
||||
}
|
||||
|
||||
bool evse_managerImpl::handle_enable_disable(int& connector_id, types::evse_manager::EnableDisableSource& cmd_source) {
|
||||
connector_status_changed = connector_id != 0;
|
||||
return mod->charger->enable_disable(connector_id, cmd_source);
|
||||
};
|
||||
|
||||
void evse_managerImpl::handle_authorize_response(types::authorization::ProvidedIdToken& provided_token,
|
||||
types::authorization::ValidationResult& validation_result) {
|
||||
const auto pnc = provided_token.authorization_type == types::authorization::AuthorizationType::PlugAndCharge;
|
||||
|
||||
if (validation_result.authorization_status == types::authorization::AuthorizationStatus::Accepted) {
|
||||
|
||||
if (this->mod->get_hlc_waiting_for_auth_pnc() && !pnc) {
|
||||
EVLOG_info
|
||||
<< "EvseManager received Authorization other than PnC while waiting for PnC. This has no effect.";
|
||||
return;
|
||||
}
|
||||
|
||||
this->mod->charger->authorize(true, provided_token, validation_result);
|
||||
mod->charger_was_authorized();
|
||||
if (validation_result.reservation_id.has_value()) {
|
||||
EVLOG_debug << "Reserve evse manager reservation id for id " << validation_result.reservation_id.value();
|
||||
// The validation result returns a reservation id. If this was a reservation for a specific evse, the
|
||||
// evse manager probably already stored the reservation id (and this call is not really necessary). But if
|
||||
// the reservation was not for a specific evse, the evse manager still has to send the reservation id in the
|
||||
// transaction event request. So that is why we call 'reserve' here, so the evse manager knows the
|
||||
// reservation id that belongs to this specific session and can send it accordingly.
|
||||
// As this is not a new reservation but an existing one, we don't signal a reservation event for this.
|
||||
mod->reserve(validation_result.reservation_id.value(), false);
|
||||
}
|
||||
} else if (pnc) {
|
||||
// we only send authorization responses to the HLC for PnC rejections. In case of EIM we could
|
||||
// still receive a successfull authorization later and therefore we don't inform the HLC
|
||||
this->mod->r_hlc[0]->call_authorization_response(
|
||||
validation_result.authorization_status,
|
||||
validation_result.certificate_status.value_or(types::authorization::CertificateStatus::Accepted));
|
||||
}
|
||||
};
|
||||
|
||||
void evse_managerImpl::handle_withdraw_authorization() {
|
||||
// reservation_id might have been stored when reserved id token has been authorized, but timed out, so
|
||||
// we can consider the reservation as consumed
|
||||
if (mod->charger->get_authorized_eim() and mod->is_reserved()) {
|
||||
mod->cancel_reservation(true);
|
||||
}
|
||||
this->mod->charger->deauthorize();
|
||||
};
|
||||
|
||||
bool evse_managerImpl::handle_reserve(int& reservation_id) {
|
||||
return mod->reserve(reservation_id, true);
|
||||
};
|
||||
|
||||
void evse_managerImpl::handle_cancel_reservation() {
|
||||
mod->cancel_reservation(true);
|
||||
};
|
||||
|
||||
bool evse_managerImpl::handle_pause_charging() {
|
||||
return mod->charger->pause_charging();
|
||||
};
|
||||
|
||||
bool evse_managerImpl::handle_resume_charging() {
|
||||
return mod->charger->resume_charging();
|
||||
};
|
||||
|
||||
bool evse_managerImpl::handle_stop_transaction(types::evse_manager::StopTransactionRequest& request) {
|
||||
return mod->charger->cancel_transaction(request);
|
||||
};
|
||||
|
||||
bool evse_managerImpl::handle_external_ready_to_start_charging() {
|
||||
if (mod->config.external_ready_to_start_charging) {
|
||||
EVLOG_info << "Received external ready to start charging command.";
|
||||
mod->ready_to_start_charging();
|
||||
return true;
|
||||
} else {
|
||||
EVLOG_warning
|
||||
<< "Ignoring external ready to start charging command, this could be a configuration issue. Please check "
|
||||
"if 'external_ready_to_start_charging' is set to true if you want to use this feature.";
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool evse_managerImpl::handle_force_unlock(int& connector_id) {
|
||||
if (not mod->r_connector_lock.empty()) {
|
||||
types::evse_manager::StopTransactionRequest request;
|
||||
request.reason = types::evse_manager::StopTransactionReason::UnlockCommand;
|
||||
mod->charger->cancel_transaction(request);
|
||||
mod->bsp->connector_force_unlock();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
void evse_managerImpl::handle_set_plug_and_charge_configuration(
|
||||
types::evse_manager::PlugAndChargeConfiguration& plug_and_charge_configuration) {
|
||||
if (plug_and_charge_configuration.pnc_enabled.has_value()) {
|
||||
mod->set_pnc_enabled(plug_and_charge_configuration.pnc_enabled.value());
|
||||
}
|
||||
if (plug_and_charge_configuration.central_contract_validation_allowed.has_value()) {
|
||||
mod->set_central_contract_validation_allowed(
|
||||
plug_and_charge_configuration.central_contract_validation_allowed.value());
|
||||
}
|
||||
if (plug_and_charge_configuration.contract_certificate_installation_enabled.has_value()) {
|
||||
mod->set_contract_certificate_installation_enabled(
|
||||
plug_and_charge_configuration.contract_certificate_installation_enabled.value());
|
||||
}
|
||||
}
|
||||
|
||||
types::evse_manager::UpdateAllowedEnergyTransferModesResult
|
||||
evse_managerImpl::handle_update_allowed_energy_transfer_modes(
|
||||
std::vector<types::iso15118::EnergyTransferMode>& allowed_energy_transfer_modes) {
|
||||
std::vector<types::iso15118::EnergyTransferMode> filtered_energy_transfer_modes;
|
||||
|
||||
if (mod->r_hlc.empty() or !mod->r_hlc[0]) {
|
||||
return types::evse_manager::UpdateAllowedEnergyTransferModesResult::NoHlc;
|
||||
}
|
||||
|
||||
filtered_energy_transfer_modes.reserve(allowed_energy_transfer_modes.size());
|
||||
|
||||
// TODO(mlitre): Add check for incompatible type(s), for now we just transform DC stuff
|
||||
// in case of MCS and only if a connector type was configured at all;
|
||||
// also TODO: for DC we can check whether BPT can be supported in case DC supply supports it
|
||||
std::transform(allowed_energy_transfer_modes.begin(), allowed_energy_transfer_modes.end(),
|
||||
filtered_energy_transfer_modes.begin(), [&](types::iso15118::EnergyTransferMode m) {
|
||||
// for MCS we have to replace DC types with MCS types
|
||||
if (mod->connector_type.has_value() and
|
||||
mod->connector_type == types::evse_manager::ConnectorTypeEnum::cMCS) {
|
||||
|
||||
if (m == types::iso15118::EnergyTransferMode::DC) {
|
||||
return types::iso15118::EnergyTransferMode::MCS;
|
||||
}
|
||||
if (m == types::iso15118::EnergyTransferMode::DC_BPT) {
|
||||
return types::iso15118::EnergyTransferMode::MCS_BPT;
|
||||
}
|
||||
}
|
||||
|
||||
// everything else pass untouched
|
||||
return m;
|
||||
});
|
||||
|
||||
// check whether at least one mode has survived our filtering
|
||||
if (!filtered_energy_transfer_modes.size()) {
|
||||
return types::evse_manager::UpdateAllowedEnergyTransferModesResult::IncompatibleEnergyTransfer;
|
||||
}
|
||||
|
||||
mod->r_hlc[0]->call_update_energy_transfer_modes(filtered_energy_transfer_modes);
|
||||
return types::evse_manager::UpdateAllowedEnergyTransferModesResult::Accepted;
|
||||
}
|
||||
|
||||
} // namespace evse
|
||||
} // namespace module
|
||||
@@ -0,0 +1,80 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
#ifndef EVSE_EVSE_MANAGER_IMPL_HPP
|
||||
#define EVSE_EVSE_MANAGER_IMPL_HPP
|
||||
|
||||
//
|
||||
// AUTO GENERATED - MARKED REGIONS WILL BE KEPT
|
||||
// template version 3
|
||||
//
|
||||
|
||||
#include <generated/interfaces/evse_manager/Implementation.hpp>
|
||||
|
||||
#include "../EvseManager.hpp"
|
||||
|
||||
// ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1
|
||||
// insert your custom include headers here
|
||||
// ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1
|
||||
|
||||
namespace module {
|
||||
namespace evse {
|
||||
|
||||
struct Conf {};
|
||||
|
||||
class evse_managerImpl : public evse_managerImplBase {
|
||||
public:
|
||||
evse_managerImpl() = delete;
|
||||
evse_managerImpl(Everest::ModuleAdapter* ev, const Everest::PtrContainer<EvseManager>& mod, Conf& config) :
|
||||
evse_managerImplBase(ev, "evse"), mod(mod), config(config){};
|
||||
|
||||
// ev@8ea32d28-373f-4c90-ae5e-b4fcc74e2a61:v1
|
||||
// insert your public definitions here
|
||||
void set_nr_of_phases_available(int n);
|
||||
// ev@8ea32d28-373f-4c90-ae5e-b4fcc74e2a61:v1
|
||||
|
||||
protected:
|
||||
// command handler functions (virtual)
|
||||
virtual types::evse_manager::Evse handle_get_evse() override;
|
||||
virtual bool handle_enable_disable(int& connector_id,
|
||||
types::evse_manager::EnableDisableSource& cmd_source) override;
|
||||
virtual void handle_authorize_response(types::authorization::ProvidedIdToken& provided_token,
|
||||
types::authorization::ValidationResult& validation_result) override;
|
||||
virtual void handle_withdraw_authorization() override;
|
||||
virtual bool handle_reserve(int& reservation_id) override;
|
||||
virtual void handle_cancel_reservation() override;
|
||||
virtual bool handle_pause_charging() override;
|
||||
virtual bool handle_resume_charging() override;
|
||||
virtual bool handle_stop_transaction(types::evse_manager::StopTransactionRequest& request) override;
|
||||
virtual bool handle_force_unlock(int& connector_id) override;
|
||||
virtual bool handle_external_ready_to_start_charging() override;
|
||||
virtual void handle_set_plug_and_charge_configuration(
|
||||
types::evse_manager::PlugAndChargeConfiguration& plug_and_charge_configuration) override;
|
||||
virtual types::evse_manager::UpdateAllowedEnergyTransferModesResult handle_update_allowed_energy_transfer_modes(
|
||||
std::vector<types::iso15118::EnergyTransferMode>& allowed_energy_transfer_modes) override;
|
||||
|
||||
// ev@d2d1847a-7b88-41dd-ad07-92785f06f5c4:v1
|
||||
// insert your protected definitions here
|
||||
// ev@d2d1847a-7b88-41dd-ad07-92785f06f5c4:v1
|
||||
|
||||
private:
|
||||
const Everest::PtrContainer<EvseManager>& mod;
|
||||
const Conf& config;
|
||||
|
||||
virtual void init() override;
|
||||
virtual void ready() override;
|
||||
|
||||
// ev@3370e4dd-95f4-47a9-aaec-ea76f34a66c9:v1
|
||||
// insert your private definitions here
|
||||
std::atomic_bool connector_status_changed{false};
|
||||
types::evse_manager::Limits limits;
|
||||
// ev@3370e4dd-95f4-47a9-aaec-ea76f34a66c9:v1
|
||||
};
|
||||
|
||||
// ev@3d7da0ad-02c2-493d-9920-0bbbd56b9876:v1
|
||||
// insert other definitions here
|
||||
// ev@3d7da0ad-02c2-493d-9920-0bbbd56b9876:v1
|
||||
|
||||
} // namespace evse
|
||||
} // namespace module
|
||||
|
||||
#endif // EVSE_EVSE_MANAGER_IMPL_HPP
|
||||
498
tools/EVerest-main/modules/EVSE/EvseManager/manifest.yaml
Normal file
498
tools/EVerest-main/modules/EVSE/EvseManager/manifest.yaml
Normal file
@@ -0,0 +1,498 @@
|
||||
description: >-
|
||||
EVSE Manager. Grid side power meter: Will be used for energy management.
|
||||
Will also be used for billing if no car side power meter connected. Car side powermeter:
|
||||
Will be used for billing if present.
|
||||
config:
|
||||
connector_id:
|
||||
description: Connector id of this evse manager
|
||||
type: integer
|
||||
connector_type:
|
||||
description: The connector type of this evse manager (/evse_manager#/ConnectorTypeEnum)
|
||||
type: string
|
||||
default: "Unknown"
|
||||
evse_id:
|
||||
description: EVSE ID
|
||||
type: string
|
||||
default: DE*PNX*E1234567*1
|
||||
evse_id_din:
|
||||
description: EVSE ID DIN after DIN SPEC 91286
|
||||
type: string
|
||||
default: 49A80737A45678
|
||||
payment_enable_eim:
|
||||
description: Set to true to enable EIM (e.g. RFID card or mobile app) authorization
|
||||
type: boolean
|
||||
default: true
|
||||
payment_enable_contract:
|
||||
description: Set to true to enable contract (aka plug and charge) authorization
|
||||
type: boolean
|
||||
default: true
|
||||
ac_nominal_voltage:
|
||||
description: Nominal AC voltage between phase and neutral in Volt
|
||||
type: number
|
||||
default: 230
|
||||
ev_receipt_required:
|
||||
description: "Unsupported: request receipt from EV with HLC"
|
||||
type: boolean
|
||||
default: false
|
||||
session_logging:
|
||||
description: Enable/Disable session log file output
|
||||
type: boolean
|
||||
default: false
|
||||
session_logging_path:
|
||||
description: Output directory for session log files
|
||||
type: string
|
||||
default: /tmp
|
||||
session_logging_xml:
|
||||
description: Log full XML messages for HLC
|
||||
type: boolean
|
||||
default: true
|
||||
has_ventilation:
|
||||
description: Allow ventilated charging or not
|
||||
type: boolean
|
||||
default: true
|
||||
charge_mode:
|
||||
description: Select charging mode
|
||||
type: string
|
||||
enum:
|
||||
- AC
|
||||
- DC
|
||||
default: AC
|
||||
supported_iso_ac_bpt:
|
||||
description: Set to true if ISO15118-20 AC BPT is supported and should be offered to the EV
|
||||
type: boolean
|
||||
default: false
|
||||
ac_hlc_enabled:
|
||||
description: Enable or disable HLC (aka ISO15118) for AC mode
|
||||
type: boolean
|
||||
default: false
|
||||
ac_hlc_use_5percent:
|
||||
description: >-
|
||||
If set to true, start with 5% PWM if HLC is enabled on AC. If set to false, start with X1. Note that
|
||||
almost no EV will perform SLAC/HLC on X1. Recommendation is to set this to true.
|
||||
type: boolean
|
||||
default: true
|
||||
ac_enforce_hlc:
|
||||
description: >-
|
||||
Combine with 5percent option to really enforce HLC even with EIM. ISO15118-3 forbids the usage of 5% if Auth happens before plugin,
|
||||
but most EVs will not perform HLC with nominal PWM. For the home use case (always authorized or auth before plugin) it may still be desirable
|
||||
to use HLC e.g. for better energy management.
|
||||
In this case you can use this option, but be aware that EVs that do not support HLC may take a long time to start (or never start) charging.
|
||||
This mode is not ISO15118-2/-3 compliant. Do not use for public charging stations.
|
||||
type: boolean
|
||||
default: false
|
||||
ac_with_soc:
|
||||
description: >-
|
||||
Special mode that switches between AC and DC charging to get SoC percentage with AC charging
|
||||
type: boolean
|
||||
default: false
|
||||
internal_over_voltage_duration_ms:
|
||||
description: >-
|
||||
Time in milliseconds the internal software over voltage monitor waits before raising MREC5 once the measured
|
||||
voltage exceeds the negotiated limit.
|
||||
type: integer
|
||||
minimum: 0
|
||||
default: 400
|
||||
dbg_hlc_auth_after_tstep:
|
||||
description: >-
|
||||
Special mode: send HLC auth ok only after t_step_XX is finished (true) or directly when available (false)
|
||||
type: boolean
|
||||
default: false
|
||||
dc_isolation_voltage_V:
|
||||
description: Override DC voltage used to test isolation in CableCheck.
|
||||
Default is 0, which means the voltage will be determined according to IEC 61851-23 (2023) CC.4.1.2
|
||||
type: integer
|
||||
default: 0
|
||||
cable_check_wait_number_of_imd_measurements:
|
||||
description: >-
|
||||
Amount of isolation measurement samples to collect before the value can be trusted. This does not average,
|
||||
it will evaluate the last measurement. Some IMDs (e.g. from Bender) need to measure for 10s to really get a trustable result.
|
||||
In this case, at 1 Hz sample rate, specify 10 samples here. If the self test includes the isolation resistance checking
|
||||
you may also set this to 0.
|
||||
type: integer
|
||||
default: 1
|
||||
cable_check_enable_imd_self_test:
|
||||
description: >-
|
||||
Enable self testing of IMD in cable check with the voltage calculated from ev_max_voltage and with relays closed.
|
||||
This is required e.g. for Bender IMDs which also perform an isolation measurement during the self test.
|
||||
A self test is required for IEC 61851-23 (2023) compliance.
|
||||
type: boolean
|
||||
default: true
|
||||
cable_check_enable_imd_self_test_relays_open:
|
||||
description: >-
|
||||
Enable self testing of IMD at the beginning of cable check where the relays may still be open.
|
||||
The voltage is set by the cable_check_relays_open_voltage_V config option and may differ from the
|
||||
voltage that is later used to perform the isolation resistance sample.
|
||||
cable_check_wait_number_of_imd_measurements needs to be >=1 in this case as no isolation resistance checking
|
||||
can be performed at this stage.
|
||||
A self test is required for IEC 61851-23 (2023) compliance.
|
||||
type: boolean
|
||||
default: false
|
||||
cable_check_relays_open_voltage_V:
|
||||
description: >-
|
||||
Voltage to set for the self test using cable_check_enable_imd_self_test_relays_open option.
|
||||
type: integer
|
||||
default: 48
|
||||
cable_check_relays_closed_timeout_s:
|
||||
description: >-
|
||||
Timeout in seconds to wait for the relays to close at the beginning of cablecheck.
|
||||
Normally they should be closed right away, but when cable_check_enable_imd_self_test_relays_open option,
|
||||
the relays may close may be delayed by the self test.
|
||||
type: integer
|
||||
default: 5
|
||||
cable_check_wait_below_60V_before_finish:
|
||||
description: >-
|
||||
Switch off power supply and wait until output voltage drops below 60V before cable check is finished.
|
||||
Note: There are different versions of IEC 61851-23:2023 in the wild with the same version number but slightly different content.
|
||||
The IEC was correcting mistakes _after_ releasing the document initially without tagging a new version number.
|
||||
Some early versions require to wait for the output voltage to drop below 60V in CC.4.1.2 (last sentence).
|
||||
Later versions do not have that requirement. The later versions are correct and should be used according to IEC.
|
||||
Both settings (true and false) are compliant with the correct version of IEC 61851-23:2023.
|
||||
Set to true when:
|
||||
- the power supply has no active discharge, and lowering the voltage with no load takes a very long time. In this case
|
||||
this option usually helps to ramp the voltage down quickly by switching it off. It will be switched on again in precharge.
|
||||
Also, some EVs switch their internal relay on at a too high voltage when the voltage is ramped down directly from cablecheck voltage to precharge voltage,
|
||||
so true is the recommended default.
|
||||
Set to false when:
|
||||
- the power supply has active discharge and can ramp down quickly without a switch off (by just setting a lower target voltage).
|
||||
This may save a few seconds as it avoids unnecessary voltage down and up ramping.
|
||||
type: boolean
|
||||
default: true
|
||||
hack_skoda_enyaq:
|
||||
description: >-
|
||||
Skoda Enyaq requests DC charging voltages below its battery level or even below 0 initially.
|
||||
Set to true to enable dirty workaround.
|
||||
type: boolean
|
||||
default: false
|
||||
hack_present_current_offset:
|
||||
description: >-
|
||||
Adds an offset [A] to the present current reported to the car on HLC.
|
||||
Set to 0 unless you really know what you are doing.
|
||||
type: integer
|
||||
default: 0
|
||||
hack_pause_imd_during_precharge:
|
||||
description: >-
|
||||
Disable IMD at the end of CableCheck and re-enable when current is flowing in CurrentDemand.
|
||||
Some DCDC power supplies do not start current flow when insulation measurement is active.
|
||||
Set to true to enable dirty workaround for some IMD hardware.
|
||||
type: boolean
|
||||
default: false
|
||||
hack_allow_bpt_with_iso2:
|
||||
description:
|
||||
Hack. Allow bidirectional power transfer with DIN spec and ISO-2. Currents communicated on HLC will
|
||||
always be positive but power supply may actually discharge the car.
|
||||
type: boolean
|
||||
default: false
|
||||
hack_simplified_mode_limit_10A:
|
||||
description: >-
|
||||
Limit PWM to 10A if EV uses simplified charging mode. Set to false to be compliant with IEC61851-1:2019 section A.2.3.
|
||||
It is the responsibility of the EV to limit to 10A according to the norm. Enable this option to deviate from the norm
|
||||
and limit from the EVSE side.
|
||||
type: boolean
|
||||
default: false
|
||||
autocharge_use_slac_instead_of_hlc:
|
||||
description: Use slac ev mac address for autocharge instead of EVCCID from HLC
|
||||
type: boolean
|
||||
default: false
|
||||
enable_autocharge:
|
||||
description: >-
|
||||
Enables Autocharge (i.e. Mac address for authorization). Disabled by default as PnC should be used instead.
|
||||
type: boolean
|
||||
default: false
|
||||
logfile_suffix:
|
||||
description: Use the string given for the log folder name. Special string session_uuid will be replaced with session uuid.
|
||||
type: string
|
||||
default: session_uuid
|
||||
soft_over_current_tolerance_percent:
|
||||
description: Allow for N percent over current in soft over current checking during AC charging.
|
||||
type: number
|
||||
default: 10.0
|
||||
soft_over_current_measurement_noise_A:
|
||||
description: Set current measurement noise. Added to limit as an offset to avoid false triggers.
|
||||
type: number
|
||||
default: 0.5
|
||||
hack_fix_hlc_integer_current_requests:
|
||||
description: >-
|
||||
Some cars request only integer ampere values during DC charging. For low power DC charging that
|
||||
means that they charge a few hundred watts slower then needed. If enabled, this will charge at full power if the difference
|
||||
between EV requested current (integer) and HLC current limit is less then 1.0
|
||||
type: boolean
|
||||
default: false
|
||||
disable_authentication:
|
||||
description: >-
|
||||
Do not wait for authorization from Auth module, offer a free service. Start charging immediately after plug in.
|
||||
Do not use with Auth manager or OCPP, this option is only to allow charging with a standalone EvseManager that is not connected to an Auth manager.
|
||||
Use DummyTokenProvider/Validator when testing with Auth module and/or OCPP.
|
||||
type: boolean
|
||||
default: false
|
||||
sae_j2847_2_bpt_enabled:
|
||||
description: Enable SAE J2847 2 V2G or V2H mode
|
||||
type: boolean
|
||||
default: false
|
||||
sae_j2847_2_bpt_mode:
|
||||
description: SAE J2847 2 BPT mode
|
||||
type: string
|
||||
enum:
|
||||
- V2H
|
||||
- V2G
|
||||
default: V2G
|
||||
request_zero_power_in_idle:
|
||||
description: >-
|
||||
"true: In Idle mode (no car connected), request 0A from energy management. In installations with many charging stations this should be set"
|
||||
"to allow the power to be distributed to the chargers that are connected to a car."
|
||||
"false: Request the normal current even if no car is connected. This speeds up the start of charging on AC BASIC charging as"
|
||||
"EvseManager does not need to wait for energy from the energy manager after plug in."
|
||||
type: boolean
|
||||
default: false
|
||||
external_ready_to_start_charging:
|
||||
description: Enable the external ready to start charging signal that delays charging ready until it has been received
|
||||
type: boolean
|
||||
default: false
|
||||
uk_smartcharging_random_delay_enable:
|
||||
description: >-
|
||||
"true: enable random_delays on start up, false: disable random delays on startup."
|
||||
"They can also be enabled/disabled during runtime on the random_delay implementation."
|
||||
type: boolean
|
||||
default: false
|
||||
uk_smartcharging_random_delay_max_duration:
|
||||
description: >-
|
||||
"Start up value for the maximum duration of a random delay."
|
||||
"Can be modified during runtime on the random_delay implementation."
|
||||
type: integer
|
||||
default: 600
|
||||
uk_smartcharging_random_delay_at_any_change:
|
||||
description: >-
|
||||
"True: use random delays on any current change during charging. False: Only use if current is reduced to zero or increased from zero."
|
||||
type: boolean
|
||||
default: true
|
||||
initial_meter_value_timeout_ms:
|
||||
description: >-
|
||||
This timeout in ms defines for how long the EvseManager waits for an initial meter value from a powermeter before it becomes ready to start charging.
|
||||
If configured to 0, the EvseManager will not wait for an initial meter value before it becomes ready to start charging.
|
||||
type: integer
|
||||
default: 5000
|
||||
switch_3ph1ph_delay_s:
|
||||
description: >-
|
||||
Wait for n seconds when switching between 3ph/1ph mode.
|
||||
type: integer
|
||||
default: 10
|
||||
switch_3ph1ph_cp_state:
|
||||
description: >-
|
||||
CP state to use for switching.
|
||||
WARNING: Some EVs may be permanently destroyed when switching from 1ph to 3ph.
|
||||
It is the responsibiltiy of the evse_board_support implementation to ensure the EV is capable of performing
|
||||
the switch. If it is not, the capabilities must set the supports_changing_phases_during_charging to false.
|
||||
Phase switching is only possible in basic charging mode.
|
||||
type: string
|
||||
enum:
|
||||
- X1
|
||||
- F
|
||||
default: X1
|
||||
soft_over_current_timeout_ms:
|
||||
description: >-
|
||||
Allow for over current to be present for N ms in soft over current checking during AC charging.
|
||||
type: integer
|
||||
minimum: 6000
|
||||
default: 7000
|
||||
lock_connector_in_state_b:
|
||||
description: >-
|
||||
Indicates if the connector lock should be locked in state B. If set to false, connector will remain unlocked in CP state B
|
||||
and this violates IEC61851-1:2019 D.6.5 Table D.9 line 4 and should not be used in public environments!
|
||||
type: boolean
|
||||
default: true
|
||||
state_F_after_fault_ms:
|
||||
description: >-
|
||||
Set state F after any fault that stops charging for the specified time in ms while in Charging mode (CX->F(300ms)->C1/B1).
|
||||
When a fault occurs in state B2, no state F is added (B2->B1 on fault).
|
||||
Some (especially older hybrid vehicles) may go into a permanent fault mode once they detect state F,
|
||||
in this case EVerest cannot recover the charging session if the fault is cleared.
|
||||
In this case you can set this parameter to 0, which will avoid to use state F in case of a fault
|
||||
and only disables PWM (C2->C1) while switching off power. This will violate IEC 61851-1:2017 however.
|
||||
The default is 300ms as the minimum suggested by IEC 61851-1:2017 Table A.5 (description) to be compliant.
|
||||
This setting is only active in BASIC charging mode.
|
||||
type: integer
|
||||
default: 300
|
||||
fail_on_powermeter_errors:
|
||||
description: >-
|
||||
Set the EVSE Manager to an inoperative state if the powermeter requirement is configured and has reported errors
|
||||
type: boolean
|
||||
default: true
|
||||
raise_mrec9:
|
||||
description: >-
|
||||
Allows to configure if an MREC9 authorization timeout error shall be raised by this module in case an authorization
|
||||
timeout occurs. It is recommended to disable it if OCPP1.6 is used.
|
||||
type: boolean
|
||||
default: false
|
||||
sleep_before_enabling_pwm_hlc_mode_ms:
|
||||
description: >-
|
||||
Sleep before the PWM signal is updated in HLC mode. Teslas are really fast with sending the first slac packet after
|
||||
enabling PWM, so the sleep allows SLAC to be ready for it. Some EV testers have issues with a value >= 1000ms,
|
||||
although ISO15118 or IEC61851 does not specify a timeout.
|
||||
type: integer
|
||||
default: 500
|
||||
central_contract_validation_allowed:
|
||||
description: >-
|
||||
Used for ISO15118 plug and charge:
|
||||
If false, contract shall not be present in PaymentOptionList.
|
||||
If true, contract may be present in PaymentOptionList if TLS is used.
|
||||
type: boolean
|
||||
default: false
|
||||
contract_certificate_installation_enabled:
|
||||
description: >-
|
||||
Used for ISO15118 plug and charge: Indicates if the charger supports contract CertificateInstall and CertificateUpdate
|
||||
type: boolean
|
||||
default: true
|
||||
inoperative_error_use_vendor_id:
|
||||
description: >-
|
||||
When raising evse_manager/Inoperative use the vendor ID from the original cause
|
||||
type: boolean
|
||||
default: false
|
||||
voltage_plausibility_max_spread_threshold_V:
|
||||
description: >-
|
||||
Maximum allowed spread (max-min) in volts between voltage measurements from different
|
||||
sources (power supply, powermeter, isolation monitor, over voltage monitor) before a
|
||||
voltage plausibility fault is triggered. During DC charging, if the spread
|
||||
exceeds this threshold for the configured duration, a fault is raised.
|
||||
type: number
|
||||
default: 50.0
|
||||
voltage_plausibility_fault_duration_ms:
|
||||
description: >-
|
||||
Duration in milliseconds for which the spread must exceed the threshold
|
||||
before a voltage plausibility fault is raised. A duration of 0 ms means that a fault
|
||||
will be raised immediately when the threshold is exceeded.
|
||||
type: integer
|
||||
default: 30000
|
||||
session_id_type:
|
||||
description: >-
|
||||
Type to use for generation of session ids.
|
||||
UUID: 36 characters UUIDs
|
||||
UUID_BASE64: 22 characters base64 encoded uuids
|
||||
SHORT_BASE64: 16 characters base64 encoded ids
|
||||
type: string
|
||||
enum:
|
||||
- UUID
|
||||
- UUID_BASE64
|
||||
- SHORT_BASE64
|
||||
default: UUID
|
||||
zero_power_ignore_pause:
|
||||
description: >-
|
||||
If no energy is available the charger can signal a pause to the car before cable check.
|
||||
With this option the charger ignores that and continues as if energy were available.
|
||||
This is not standard compliant!
|
||||
As of today most EVs do not handle the pause signal correctly causing the session to be
|
||||
terminated and not allowing to start charging again when energy is available.
|
||||
The default of this value is true, allowing the session to proceed until the charge loop.
|
||||
Terminating the session in the charge loop when no energy is available is handled more
|
||||
gracefully by most EVs and allows to start charging again when energy is available without
|
||||
the need of unplugging and plugging again.
|
||||
type: boolean
|
||||
default: true
|
||||
zero_power_allow_ev_to_ignore_pause:
|
||||
description: >-
|
||||
If no energy is available the charger can signal a pause to the car before cable check.
|
||||
With this option, the charger ignores that the ev goes to the energy phase instead of pausing
|
||||
the session. This is not standard compliant!
|
||||
type: boolean
|
||||
default: false
|
||||
bpt_channel:
|
||||
description: Type of installed/uninstalled power transfer channel
|
||||
type: string
|
||||
enum:
|
||||
- None
|
||||
- Unified
|
||||
- Separated
|
||||
default: None
|
||||
bpt_generator_mode:
|
||||
description: Power converter behavior
|
||||
type: string
|
||||
enum:
|
||||
- None
|
||||
- GridFollowing
|
||||
- GridForming
|
||||
default: None
|
||||
bpt_grid_code_island_method:
|
||||
description: Active/inactive islanding detection method. Only for AC.
|
||||
type: string
|
||||
enum:
|
||||
- None
|
||||
- Active
|
||||
- Passive
|
||||
default: None
|
||||
hlc_charge_loop_without_energy_timeout_s:
|
||||
description: >-
|
||||
Timeout (s) to allow HLC charge loop to continue without available energy;
|
||||
when exceeded, the HLC session is stopped and PWM is disabled until energy returns.
|
||||
If set too long, some EVs stop the session on their own. Then it cannot be resumed
|
||||
when energy is available again.
|
||||
type: integer
|
||||
default: 5
|
||||
dc_ramp_ampere_per_second:
|
||||
description: >-
|
||||
Maximum ampere per second limit for up/down ramping of current in charging loop.
|
||||
type: integer
|
||||
default: 25
|
||||
provides:
|
||||
evse:
|
||||
interface: evse_manager
|
||||
description: This is the main evsemanager interface
|
||||
energy_grid:
|
||||
description: This is the tree leaf interface to build the energy supply tree
|
||||
interface: energy
|
||||
token_provider:
|
||||
description: Provides authtokens for autocharge or plug and charge
|
||||
interface: auth_token_provider
|
||||
random_delay:
|
||||
description: Provides control over UK smart charging regulation random delay feature
|
||||
interface: uk_random_delay
|
||||
dc_external_derate:
|
||||
description: Provides DC external derating capabilities
|
||||
interface: dc_external_derate
|
||||
requires:
|
||||
bsp:
|
||||
interface: evse_board_support
|
||||
ac_rcd:
|
||||
interface: ac_rcd
|
||||
min_connections: 0
|
||||
max_connections: 1
|
||||
connector_lock:
|
||||
interface: connector_lock
|
||||
min_connections: 0
|
||||
max_connections: 1
|
||||
powermeter_grid_side:
|
||||
interface: powermeter
|
||||
min_connections: 0
|
||||
max_connections: 1
|
||||
powermeter_car_side:
|
||||
interface: powermeter
|
||||
min_connections: 0
|
||||
max_connections: 1
|
||||
slac:
|
||||
interface: slac
|
||||
min_connections: 0
|
||||
max_connections: 1
|
||||
hlc:
|
||||
interface: ISO15118_charger
|
||||
min_connections: 0
|
||||
max_connections: 1
|
||||
imd:
|
||||
interface: isolation_monitor
|
||||
min_connections: 0
|
||||
max_connections: 1
|
||||
over_voltage_monitor:
|
||||
interface: over_voltage_monitor
|
||||
min_connections: 0
|
||||
max_connections: 1
|
||||
powersupply_DC:
|
||||
interface: power_supply_DC
|
||||
min_connections: 0
|
||||
max_connections: 1
|
||||
store:
|
||||
interface: kvs
|
||||
min_connections: 0
|
||||
max_connections: 1
|
||||
enable_external_mqtt: true
|
||||
enable_telemetry: true
|
||||
metadata:
|
||||
license: https://opensource.org/licenses/Apache-2.0
|
||||
authors:
|
||||
- Cornelius Claussen
|
||||
- Anton Woellert
|
||||
@@ -0,0 +1,154 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include "over_voltage/OverVoltageMonitor.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <condition_variable>
|
||||
#include <fmt/core.h>
|
||||
#include <thread>
|
||||
|
||||
namespace module {
|
||||
|
||||
OverVoltageMonitor::OverVoltageMonitor(ErrorCallback callback, std::chrono::milliseconds duration) :
|
||||
error_callback_(std::move(callback)), duration_(duration) {
|
||||
timer_thread_ = std::thread(&OverVoltageMonitor::timer_thread_func, this);
|
||||
}
|
||||
|
||||
OverVoltageMonitor::~OverVoltageMonitor() {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(timer_mutex_);
|
||||
timer_thread_exit_ = true;
|
||||
timer_armed_ = false;
|
||||
}
|
||||
timer_cv_.notify_one();
|
||||
if (timer_thread_.joinable()) {
|
||||
timer_thread_.join();
|
||||
}
|
||||
}
|
||||
|
||||
void OverVoltageMonitor::set_limits(double emergency_limit, double error_limit) {
|
||||
emergency_limit_ = emergency_limit;
|
||||
error_limit_ = error_limit;
|
||||
limits_valid_ = true;
|
||||
}
|
||||
|
||||
void OverVoltageMonitor::start_monitor() {
|
||||
fault_latched_ = false;
|
||||
cancel_error_timer();
|
||||
running_ = true;
|
||||
}
|
||||
|
||||
void OverVoltageMonitor::stop_monitor() {
|
||||
running_ = false;
|
||||
cancel_error_timer();
|
||||
}
|
||||
|
||||
void OverVoltageMonitor::reset() {
|
||||
fault_latched_ = false;
|
||||
cancel_error_timer();
|
||||
}
|
||||
|
||||
void OverVoltageMonitor::update_voltage(double voltage_v) {
|
||||
if (!running_ || fault_latched_ || !limits_valid_) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (voltage_v >= emergency_limit_) {
|
||||
cancel_error_timer();
|
||||
trigger_fault(FaultType::Emergency,
|
||||
fmt::format("Voltage {:.2f} V exceeded emergency limit {:.2f} V.", voltage_v, emergency_limit_));
|
||||
return;
|
||||
}
|
||||
|
||||
if (voltage_v >= error_limit_) {
|
||||
arm_error_timer(voltage_v);
|
||||
} else {
|
||||
cancel_error_timer();
|
||||
}
|
||||
}
|
||||
|
||||
void OverVoltageMonitor::trigger_fault(FaultType type, const std::string& reason) {
|
||||
fault_latched_ = true;
|
||||
running_ = false;
|
||||
cancel_error_timer();
|
||||
if (error_callback_) {
|
||||
error_callback_(type, reason);
|
||||
}
|
||||
}
|
||||
|
||||
void OverVoltageMonitor::arm_error_timer(double voltage_v) {
|
||||
if (duration_.count() == 0) {
|
||||
trigger_fault(FaultType::Error, fmt::format("Voltage {:.2f} V exceeded limit {:.2f} V for at least {} ms.",
|
||||
voltage_v, error_limit_, duration_.count()));
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(timer_mutex_);
|
||||
if (timer_armed_) {
|
||||
timer_voltage_snapshot_ = std::max(timer_voltage_snapshot_, voltage_v);
|
||||
return;
|
||||
}
|
||||
timer_armed_ = true;
|
||||
timer_voltage_snapshot_ = voltage_v;
|
||||
timer_deadline_ = std::chrono::steady_clock::now() + duration_;
|
||||
}
|
||||
timer_cv_.notify_one();
|
||||
}
|
||||
|
||||
void OverVoltageMonitor::cancel_error_timer() {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(timer_mutex_);
|
||||
if (!timer_armed_) {
|
||||
return;
|
||||
}
|
||||
timer_armed_ = false;
|
||||
}
|
||||
timer_cv_.notify_one();
|
||||
}
|
||||
|
||||
void OverVoltageMonitor::timer_thread_func() {
|
||||
std::unique_lock<std::mutex> lock(timer_mutex_);
|
||||
|
||||
while (!timer_thread_exit_) {
|
||||
// Wait until a timer is armed or exit is requested
|
||||
timer_cv_.wait(lock, [this] { return timer_thread_exit_ || timer_armed_; });
|
||||
if (timer_thread_exit_) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Capture the current deadline and wait until it expires or is cancelled/updated
|
||||
auto deadline = timer_deadline_;
|
||||
while (!timer_thread_exit_ && timer_armed_) {
|
||||
if (timer_cv_.wait_until(lock, deadline) == std::cv_status::timeout) {
|
||||
break;
|
||||
}
|
||||
// Woken up: check for exit, cancellation or re-arming with a new deadline
|
||||
if (timer_thread_exit_ || !timer_armed_ || timer_deadline_ != deadline) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (timer_thread_exit_) {
|
||||
break;
|
||||
}
|
||||
if (!timer_armed_ || timer_deadline_ != deadline) {
|
||||
// Timer was cancelled or re-armed; go back to waiting
|
||||
continue;
|
||||
}
|
||||
|
||||
// Timer expired with this deadline and is still armed
|
||||
const double voltage = timer_voltage_snapshot_;
|
||||
timer_armed_ = false;
|
||||
|
||||
// Release the lock while invoking the callback path
|
||||
lock.unlock();
|
||||
trigger_fault(FaultType::Error, fmt::format("Voltage {:.2f} V exceeded limit {:.2f} V for at least {} ms.",
|
||||
voltage, error_limit_, duration_.count()));
|
||||
lock.lock();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace module
|
||||
@@ -0,0 +1,126 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
#include <condition_variable>
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
namespace module {
|
||||
|
||||
/// \brief Simple software over-voltage watchdog used by EvseManager.
|
||||
///
|
||||
/// The monitor observes a DC voltage and compares it against two thresholds:
|
||||
///
|
||||
/// - \ref FaultType::Emergency: if the measured voltage is greater than or equal to the
|
||||
/// configured emergency limit, an emergency fault is raised immediately.
|
||||
/// - \ref FaultType::Error: if the measured voltage is greater than or equal to the configured
|
||||
/// error limit continuously for at least the configured duration, an error fault is raised.
|
||||
///
|
||||
/// After a fault has been raised, it is latched until \ref reset() is called and monitoring
|
||||
/// is started again via \ref start_monitor().
|
||||
///
|
||||
/// Thread-safety:
|
||||
/// - Public APIs are intended to be called from EvseManager threads and from callbacks of the
|
||||
/// external over_voltage_monitor interface.
|
||||
/// - Internally, a dedicated background thread waits on an error timer condition and synchronizes
|
||||
/// access to timer-related state via an internal mutex and condition variable.
|
||||
class OverVoltageMonitor {
|
||||
public:
|
||||
/// \brief Type of over-voltage fault.
|
||||
///
|
||||
/// - Error: voltage above the error limit for at least the configured duration.
|
||||
/// - Emergency: voltage above the emergency limit, triggers immediately.
|
||||
enum class FaultType {
|
||||
Error,
|
||||
Emergency
|
||||
};
|
||||
|
||||
/// \brief Callback type used to report detected faults.
|
||||
///
|
||||
/// The callback is invoked from an internal monitoring context when a fault is detected.
|
||||
/// It receives the fault type and a human-readable description.
|
||||
using ErrorCallback = std::function<void(FaultType, const std::string&)>;
|
||||
|
||||
/// \brief Construct a new OverVoltageMonitor.
|
||||
///
|
||||
/// \param callback Function that will be called whenever a fault is detected.
|
||||
/// \param duration Duration for which the voltage must stay above the error limit before
|
||||
/// an \ref FaultType::Error is raised. A duration of 0 ms means that an
|
||||
/// error fault will be raised immediately when the error limit is exceeded.
|
||||
OverVoltageMonitor(ErrorCallback callback, std::chrono::milliseconds duration);
|
||||
|
||||
/// \brief Destructor joins the internal timer thread before destroying the object.
|
||||
~OverVoltageMonitor();
|
||||
|
||||
/// \brief Configure the error and emergency voltage limits.
|
||||
///
|
||||
/// This must be called before monitoring can become active. Calling this function marks
|
||||
/// the limits as valid and enables evaluation in \ref update_voltage().
|
||||
///
|
||||
/// \param emergency_limit Emergency limit in volts; exceeding this immediately triggers an
|
||||
/// \ref FaultType::Emergency.
|
||||
/// \param error_limit Error limit in volts; exceeding this for at least the configured
|
||||
/// duration triggers an \ref FaultType::Error.
|
||||
void set_limits(double emergency_limit, double error_limit);
|
||||
|
||||
/// \brief Start monitoring of incoming voltage samples.
|
||||
///
|
||||
/// Clears any latched fault state and cancels a pending error timer, then enables
|
||||
/// evaluation in \ref update_voltage().
|
||||
void start_monitor();
|
||||
|
||||
/// \brief Stop monitoring of incoming voltage samples.
|
||||
///
|
||||
/// Monitoring is disabled and any pending error timer is cancelled. Existing latched
|
||||
/// faults remain active until \ref reset() is called.
|
||||
void stop_monitor();
|
||||
|
||||
/// \brief Feed a new voltage sample to the monitor.
|
||||
///
|
||||
/// If monitoring is active and limits have been configured, this function evaluates the
|
||||
/// sample against the configured error and emergency limits and may:
|
||||
///
|
||||
/// - Trigger an immediate emergency fault.
|
||||
/// - Arm or update an error timer.
|
||||
/// - Cancel an active error timer if the voltage falls back below the error limit.
|
||||
///
|
||||
/// \param voltage_v Measured DC voltage in volts.
|
||||
void update_voltage(double voltage_v);
|
||||
|
||||
/// \brief Reset the internal fault latch and cancel timers.
|
||||
///
|
||||
/// This clears any previously raised fault and stops the internal error timer. Monitoring
|
||||
/// remains disabled until \ref start_monitor() is called again.
|
||||
void reset();
|
||||
|
||||
private:
|
||||
void timer_thread_func();
|
||||
|
||||
void trigger_fault(FaultType type, const std::string& reason);
|
||||
void arm_error_timer(double voltage_v);
|
||||
void cancel_error_timer();
|
||||
|
||||
ErrorCallback error_callback_;
|
||||
std::chrono::milliseconds duration_;
|
||||
bool running_{false};
|
||||
bool limits_valid_{false};
|
||||
bool fault_latched_{false};
|
||||
double emergency_limit_{std::numeric_limits<double>::infinity()};
|
||||
double error_limit_{std::numeric_limits<double>::infinity()};
|
||||
|
||||
std::mutex timer_mutex_;
|
||||
double timer_voltage_snapshot_{0.0};
|
||||
std::chrono::steady_clock::time_point timer_deadline_{};
|
||||
bool timer_armed_{false};
|
||||
bool timer_thread_exit_{false};
|
||||
std::condition_variable timer_cv_;
|
||||
std::thread timer_thread_;
|
||||
};
|
||||
|
||||
} // namespace module
|
||||
@@ -0,0 +1,35 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include "uk_random_delayImpl.hpp"
|
||||
|
||||
namespace module {
|
||||
namespace random_delay {
|
||||
|
||||
void uk_random_delayImpl::init() {
|
||||
}
|
||||
|
||||
void uk_random_delayImpl::ready() {
|
||||
}
|
||||
|
||||
void uk_random_delayImpl::handle_enable() {
|
||||
mod->random_delay_running = false;
|
||||
mod->random_delay_enabled = true;
|
||||
}
|
||||
|
||||
void uk_random_delayImpl::handle_disable() {
|
||||
mod->random_delay_running = false;
|
||||
mod->random_delay_enabled = false;
|
||||
}
|
||||
|
||||
void uk_random_delayImpl::handle_cancel() {
|
||||
mod->random_delay_running = false;
|
||||
}
|
||||
|
||||
void uk_random_delayImpl::handle_set_duration_s(int& value) {
|
||||
mod->random_delay_max_duration = std::chrono::seconds(value);
|
||||
}
|
||||
|
||||
} // namespace random_delay
|
||||
|
||||
} // namespace module
|
||||
@@ -0,0 +1,64 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
#ifndef RANDOM_DELAY_UK_RANDOM_DELAY_IMPL_HPP
|
||||
#define RANDOM_DELAY_UK_RANDOM_DELAY_IMPL_HPP
|
||||
|
||||
//
|
||||
// AUTO GENERATED - MARKED REGIONS WILL BE KEPT
|
||||
// template version 3
|
||||
//
|
||||
|
||||
#include <generated/interfaces/uk_random_delay/Implementation.hpp>
|
||||
|
||||
#include "../EvseManager.hpp"
|
||||
|
||||
// ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1
|
||||
// insert your custom include headers here
|
||||
// ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1
|
||||
|
||||
namespace module {
|
||||
namespace random_delay {
|
||||
|
||||
struct Conf {};
|
||||
|
||||
class uk_random_delayImpl : public uk_random_delayImplBase {
|
||||
public:
|
||||
uk_random_delayImpl() = delete;
|
||||
uk_random_delayImpl(Everest::ModuleAdapter* ev, const Everest::PtrContainer<EvseManager>& mod, Conf& config) :
|
||||
uk_random_delayImplBase(ev, "random_delay"), mod(mod), config(config){};
|
||||
|
||||
// ev@8ea32d28-373f-4c90-ae5e-b4fcc74e2a61:v1
|
||||
// insert your public definitions here
|
||||
// ev@8ea32d28-373f-4c90-ae5e-b4fcc74e2a61:v1
|
||||
|
||||
protected:
|
||||
// command handler functions (virtual)
|
||||
virtual void handle_enable() override;
|
||||
virtual void handle_disable() override;
|
||||
virtual void handle_cancel() override;
|
||||
virtual void handle_set_duration_s(int& value) override;
|
||||
|
||||
// ev@d2d1847a-7b88-41dd-ad07-92785f06f5c4:v1
|
||||
// insert your protected definitions here
|
||||
// ev@d2d1847a-7b88-41dd-ad07-92785f06f5c4:v1
|
||||
|
||||
private:
|
||||
const Everest::PtrContainer<EvseManager>& mod;
|
||||
const Conf& config;
|
||||
|
||||
virtual void init() override;
|
||||
virtual void ready() override;
|
||||
|
||||
// ev@3370e4dd-95f4-47a9-aaec-ea76f34a66c9:v1
|
||||
// insert your private definitions here
|
||||
// ev@3370e4dd-95f4-47a9-aaec-ea76f34a66c9:v1
|
||||
};
|
||||
|
||||
// ev@3d7da0ad-02c2-493d-9920-0bbbd56b9876:v1
|
||||
// insert other definitions here
|
||||
// ev@3d7da0ad-02c2-493d-9920-0bbbd56b9876:v1
|
||||
|
||||
} // namespace random_delay
|
||||
} // namespace module
|
||||
|
||||
#endif // RANDOM_DELAY_UK_RANDOM_DELAY_IMPL_HPP
|
||||
@@ -0,0 +1,295 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
#ifndef SCOPED_LOCK_TIMEOUT
|
||||
#define SCOPED_LOCK_TIMEOUT
|
||||
|
||||
#include "everest/exceptions.hpp"
|
||||
#include "everest/logging.hpp"
|
||||
#include <mutex>
|
||||
#include <signal.h>
|
||||
|
||||
#include "backtrace.hpp"
|
||||
|
||||
/*
|
||||
Simple helper class for scoped lock with timeout
|
||||
*/
|
||||
namespace Everest {
|
||||
|
||||
enum class MutexDescription {
|
||||
Undefined,
|
||||
Charger_signal_loop,
|
||||
Charger_signal_error,
|
||||
Charger_signal_error_cleared,
|
||||
Charger_mainloop,
|
||||
Charger_process_event,
|
||||
Charger_waiting_for_power,
|
||||
Charger_cancel_transaction,
|
||||
Charger_setup,
|
||||
Charger_get_current_state,
|
||||
Charger_get_authorized_pnc,
|
||||
Charger_get_authorized_eim,
|
||||
Charger_get_authorized_pnc_ready_for_hlc,
|
||||
Charger_get_authorized_eim_ready_for_hlc,
|
||||
Charger_authorize,
|
||||
Charger_deauthorize,
|
||||
Charger_disable,
|
||||
Charger_enable,
|
||||
Charger_set_faulted,
|
||||
Charger_get_max_current,
|
||||
Charger_set_current_drawn_by_vehicle,
|
||||
Charger_request_error_sequence,
|
||||
Charger_set_matching_started,
|
||||
Charger_notify_currentdemand_started,
|
||||
Charger_inform_new_evse_max_hlc_limits,
|
||||
Charger_get_evse_max_hlc_limits,
|
||||
Charger_inform_new_evse_min_hlc_limits,
|
||||
Charger_get_evse_min_hlc_limits,
|
||||
Charger_dlink_pause,
|
||||
Charger_dlink_terminate,
|
||||
Charger_dlink_error,
|
||||
Charger_set_hlc_charging_active,
|
||||
Charger_set_hlc_allow_close_contactor,
|
||||
Charger_errors_prevent_charging,
|
||||
Charger_set_max_current,
|
||||
Charger_switch_three_phases_while_charging,
|
||||
Charger_get_last_stop_transaction,
|
||||
Charger_set_hlc_d20_active,
|
||||
IEC_process_bsp_event,
|
||||
IEC_state_machine,
|
||||
IEC_set_pwm,
|
||||
IEC_set_cp_state_X1,
|
||||
IEC_set_cp_state_F,
|
||||
IEC_allow_power_on,
|
||||
IEC_force_unlock,
|
||||
EVSE_charger_ready,
|
||||
EVSE_set_ev_info,
|
||||
EVSE_publish_ev_info,
|
||||
EVSE_subscribe_dc_ev_maximum_limits,
|
||||
EVSE_subscribe_departure_time,
|
||||
EVSE_subscribe_ac_eamount,
|
||||
EVSE_subscribe_ac_ev_max_voltage,
|
||||
EVSE_subscribe_ac_ev_max_current,
|
||||
EVSE_subscribe_ac_ev_min_current,
|
||||
EVSE_subscribe_ac_ev_power_limits,
|
||||
EVSE_subscribe_ac_ev_present_powers,
|
||||
EVSE_subscribe_ac_ev_dynamic_control_mode,
|
||||
EVSE_subscribe_dc_ev_energy_capacity,
|
||||
EVSE_subscribe_dc_ev_energy_request,
|
||||
EVSE_subscribe_dc_full_soc,
|
||||
EVSE_subscribe_dc_bulk_soc,
|
||||
EVSE_subscribe_dc_ev_remaining_time,
|
||||
EVSE_subscribe_dc_ev_status,
|
||||
EVSE_subscribe_evcc_id,
|
||||
EVSE_subscribe_powermeter,
|
||||
EVSE_get_latest_powermeter_data_billing,
|
||||
EVSE_get_reservation_id,
|
||||
EVSE_reserve,
|
||||
EVSE_cancel_reservation,
|
||||
EVSE_is_reserved,
|
||||
EVSE_get_ev_info
|
||||
};
|
||||
|
||||
static std::string to_string(MutexDescription d) {
|
||||
switch (d) {
|
||||
case MutexDescription::Undefined:
|
||||
return "Undefined";
|
||||
case MutexDescription::Charger_signal_loop:
|
||||
return "Charger.cpp: error_handling->signal_loop";
|
||||
case MutexDescription::Charger_signal_error:
|
||||
return "Charger.cpp: error_handling->signal_error";
|
||||
case MutexDescription::Charger_signal_error_cleared:
|
||||
return "Charger.cpp: error_handling->signal_all_errors_cleared";
|
||||
case MutexDescription::Charger_mainloop:
|
||||
return "Charger.cpp: mainloop";
|
||||
case MutexDescription::Charger_process_event:
|
||||
return "Charger.cpp: process_event";
|
||||
case MutexDescription::Charger_waiting_for_power:
|
||||
return "Charger.cpp: pause_charging_wait_for_power";
|
||||
case MutexDescription::Charger_cancel_transaction:
|
||||
return "Charger.cpp: cancel_transaction";
|
||||
case MutexDescription::Charger_setup:
|
||||
return "Charger.cpp: setup";
|
||||
case MutexDescription::Charger_get_current_state:
|
||||
return "Charger.cpp: get_current_state";
|
||||
case MutexDescription::Charger_get_authorized_pnc:
|
||||
return "Charger.cpp: get_authorized_pnc";
|
||||
case MutexDescription::Charger_get_authorized_eim:
|
||||
return "Charger.cpp: get_authorized_eim";
|
||||
case MutexDescription::Charger_get_authorized_pnc_ready_for_hlc:
|
||||
return "Charger.cpp: get_authorized_pnc_ready_for_hlc";
|
||||
case MutexDescription::Charger_get_authorized_eim_ready_for_hlc:
|
||||
return "Charger.cpp: get_authorized_eim_ready_for_hlc";
|
||||
case MutexDescription::Charger_authorize:
|
||||
return "Charger.cpp: authorize";
|
||||
case MutexDescription::Charger_deauthorize:
|
||||
return "Charger.cpp: deauthorize";
|
||||
case MutexDescription::Charger_disable:
|
||||
return "Charger.cpp: disable";
|
||||
case MutexDescription::Charger_enable:
|
||||
return "Charger.cpp: enable";
|
||||
case MutexDescription::Charger_set_faulted:
|
||||
return "Charger.cpp: set_faulted";
|
||||
case MutexDescription::Charger_get_max_current:
|
||||
return "Charger.cpp: get_max_current";
|
||||
case MutexDescription::Charger_set_current_drawn_by_vehicle:
|
||||
return "Charger.cpp: set_current_drawn_by_vehicle";
|
||||
case MutexDescription::Charger_request_error_sequence:
|
||||
return "Charger.cpp: request_error_sequence";
|
||||
case MutexDescription::Charger_set_matching_started:
|
||||
return "Charger.cpp: set_matching_started";
|
||||
case MutexDescription::Charger_notify_currentdemand_started:
|
||||
return "Charger.cpp: notify_currentdemand_started";
|
||||
case MutexDescription::Charger_inform_new_evse_max_hlc_limits:
|
||||
return "Charger.cpp: inform_new_evse_max_hlc_limits";
|
||||
case MutexDescription::Charger_get_evse_max_hlc_limits:
|
||||
return "Charger.cpp: get_evse_max_hlc_limits";
|
||||
case MutexDescription::Charger_inform_new_evse_min_hlc_limits:
|
||||
return "Charger.cpp: inform_new_evse_min_hlc_limits";
|
||||
case MutexDescription::Charger_get_evse_min_hlc_limits:
|
||||
return "Charger.cpp: get_evse_min_hlc_limits";
|
||||
case MutexDescription::Charger_dlink_pause:
|
||||
return "Charger.cpp: dlink_pause";
|
||||
case MutexDescription::Charger_dlink_terminate:
|
||||
return "Charger.cpp: dlink_dlink_terminate";
|
||||
case MutexDescription::Charger_dlink_error:
|
||||
return "Charger.cpp: dlink_error";
|
||||
case MutexDescription::Charger_set_hlc_charging_active:
|
||||
return "Charger.cpp: set_hlc_charging_active";
|
||||
case MutexDescription::Charger_set_hlc_allow_close_contactor:
|
||||
return "Charger.cpp: set_hlc_allow_close_contactor";
|
||||
case MutexDescription::Charger_errors_prevent_charging:
|
||||
return "Charger.cpp: errors_prevent_charging";
|
||||
case MutexDescription::Charger_set_max_current:
|
||||
return "Charger.cpp: set max current";
|
||||
case MutexDescription::Charger_switch_three_phases_while_charging:
|
||||
return "Charger.cpp switch_three_phases_while_charging";
|
||||
case MutexDescription::Charger_get_last_stop_transaction:
|
||||
return "Charger.cpp get_last_stop_transaction";
|
||||
case MutexDescription::Charger_set_hlc_d20_active:
|
||||
return "Charger.cpp set_hlc_d20_active";
|
||||
case MutexDescription::IEC_process_bsp_event:
|
||||
return "IECStateMachine::process_bsp_event";
|
||||
case MutexDescription::IEC_state_machine:
|
||||
return "IECStateMachine::state_machine";
|
||||
case MutexDescription::IEC_set_pwm:
|
||||
return "IECStateMachine::set_pwm";
|
||||
case MutexDescription::IEC_set_cp_state_X1:
|
||||
return "IECStateMachine::set_cp_state_X1";
|
||||
case MutexDescription::IEC_set_cp_state_F:
|
||||
return "IECStateMachine::set_cp_state_F";
|
||||
case MutexDescription::IEC_allow_power_on:
|
||||
return "IECStateMachine::allow_power_on";
|
||||
case MutexDescription::IEC_force_unlock:
|
||||
return "IECStateMachine::force_unlock";
|
||||
case MutexDescription::EVSE_charger_ready:
|
||||
return "EvseManager.cpp: charger_ready";
|
||||
case MutexDescription::EVSE_set_ev_info:
|
||||
return "EvseManager.cpp: set ev_info present_voltage/current";
|
||||
case MutexDescription::EVSE_publish_ev_info:
|
||||
return "EvseManager.cpp: publish_ev_info";
|
||||
case MutexDescription::EVSE_subscribe_dc_ev_maximum_limits:
|
||||
return "EvseManager.cpp: subscribe_dc_ev_maximum_limits";
|
||||
case MutexDescription::EVSE_subscribe_departure_time:
|
||||
return "EvseManager.cpp: subscribe_departure_time";
|
||||
case MutexDescription::EVSE_subscribe_ac_eamount:
|
||||
return "EvseManager.cpp: subscribe_ac_eamount";
|
||||
case MutexDescription::EVSE_subscribe_ac_ev_max_voltage:
|
||||
return "EvseManager.cpp: subscribe_ac_ev_max_voltage";
|
||||
case MutexDescription::EVSE_subscribe_ac_ev_max_current:
|
||||
return "EvseManager.cpp: subscribe_ac_ev_max_current";
|
||||
case MutexDescription::EVSE_subscribe_ac_ev_min_current:
|
||||
return "EvseManager.cpp: subscribe_ac_ev_min_current";
|
||||
case MutexDescription::EVSE_subscribe_ac_ev_power_limits:
|
||||
return "EvseManager.cpp: subscribe_ac_ev_power_limits";
|
||||
case MutexDescription::EVSE_subscribe_ac_ev_present_powers:
|
||||
return "EvseManager.cpp: subscribe_ac_ev_present_powers";
|
||||
case MutexDescription::EVSE_subscribe_ac_ev_dynamic_control_mode:
|
||||
return "EvseManager.cpp: subscribe_ac_ev_dynamic_control_mode";
|
||||
case MutexDescription::EVSE_subscribe_dc_ev_energy_capacity:
|
||||
return "EvseManager.cpp: subscribe_dc_ev_energy_capacity";
|
||||
case MutexDescription::EVSE_subscribe_dc_ev_energy_request:
|
||||
return "EvseManager.cpp: subscribe_dc_ev_energy_request";
|
||||
case MutexDescription::EVSE_subscribe_dc_full_soc:
|
||||
return "EvseManager.cpp: subscribe_dc_full_soc";
|
||||
case MutexDescription::EVSE_subscribe_dc_bulk_soc:
|
||||
return "EvseManager.cpp: subscribe_dc_bulk_soc";
|
||||
case MutexDescription::EVSE_subscribe_dc_ev_remaining_time:
|
||||
return "EvseManager.cpp: subscribe_dc_ev_remaining_time";
|
||||
case MutexDescription::EVSE_subscribe_dc_ev_status:
|
||||
return "EvseManager.cpp subscribe_dc_ev_status";
|
||||
case MutexDescription::EVSE_subscribe_evcc_id:
|
||||
return "EvseManager.cpp: subscribe_evcc_id";
|
||||
case MutexDescription::EVSE_subscribe_powermeter:
|
||||
return "EvseManager.cpp: subscribe_powermeter";
|
||||
case MutexDescription::EVSE_get_latest_powermeter_data_billing:
|
||||
return "EvseManager.cpp: get_latest_powermeter_data_billing";
|
||||
case MutexDescription::EVSE_get_reservation_id:
|
||||
return "EvseManager.cpp: get_reservation_id";
|
||||
case MutexDescription::EVSE_reserve:
|
||||
return "EvseManager.cpp: reserve";
|
||||
case MutexDescription::EVSE_cancel_reservation:
|
||||
return "EvseManager.cpp: cancel_reservation";
|
||||
case MutexDescription::EVSE_is_reserved:
|
||||
return "EvseManager.cpp: is_reserved";
|
||||
case MutexDescription::EVSE_get_ev_info:
|
||||
return "EvseManager.cpp: get_ev_info";
|
||||
}
|
||||
return "Undefined";
|
||||
}
|
||||
|
||||
class timed_mutex_traceable : public std::timed_mutex {
|
||||
#ifdef EVEREST_USE_BACKTRACES
|
||||
public:
|
||||
MutexDescription description;
|
||||
pthread_t p_id;
|
||||
#endif
|
||||
};
|
||||
|
||||
template <typename mutex_type> class scoped_lock_timeout {
|
||||
public:
|
||||
explicit scoped_lock_timeout(mutex_type& __m, MutexDescription description) : mutex(__m) {
|
||||
if (not mutex.try_lock_for(deadlock_timeout)) {
|
||||
#ifdef EVEREST_USE_BACKTRACES
|
||||
request_backtrace(pthread_self());
|
||||
request_backtrace(mutex.p_id);
|
||||
// Give some time for other timeouts to report their state and backtraces
|
||||
std::this_thread::sleep_for(std::chrono::seconds(10));
|
||||
|
||||
std::string different_thread;
|
||||
if (mutex.p_id not_eq pthread_self()) {
|
||||
different_thread = " from a different thread.";
|
||||
} else {
|
||||
different_thread = " from the same thread";
|
||||
}
|
||||
|
||||
EVLOG_AND_THROW(EverestTimeoutError("Mutex deadlock detected: Failed to lock " + to_string(description) +
|
||||
", mutex held by " + to_string(mutex.description) + different_thread));
|
||||
#endif
|
||||
} else {
|
||||
locked = true;
|
||||
#ifdef EVEREST_USE_BACKTRACES
|
||||
mutex.description = description;
|
||||
mutex.p_id = pthread_self();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
~scoped_lock_timeout() {
|
||||
if (locked) {
|
||||
mutex.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
scoped_lock_timeout(const scoped_lock_timeout&) = delete;
|
||||
scoped_lock_timeout& operator=(const scoped_lock_timeout&) = delete;
|
||||
|
||||
private:
|
||||
bool locked{false};
|
||||
mutex_type& mutex;
|
||||
|
||||
// This should be lower then command timeouts from framework (by default 300s)
|
||||
static constexpr auto deadlock_timeout = std::chrono::seconds(120);
|
||||
};
|
||||
} // namespace Everest
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,75 @@
|
||||
set(TEST_TARGET_NAME ${PROJECT_NAME}_EvseManager_tests)
|
||||
|
||||
set(TESTS_INCLUDE_DIR "${PROJECT_SOURCE_DIR}/tests/include")
|
||||
|
||||
add_executable(${TEST_TARGET_NAME})
|
||||
|
||||
add_dependencies(${TEST_TARGET_NAME} ${MODULE_NAME})
|
||||
|
||||
get_target_property(GENERATED_INCLUDE_DIR generate_cpp_files EVEREST_GENERATED_INCLUDE_DIR)
|
||||
|
||||
target_include_directories(${TEST_TARGET_NAME} PRIVATE
|
||||
.. ${TESTS_INCLUDE_DIR}
|
||||
${GENERATED_INCLUDE_DIR}
|
||||
${CMAKE_BINARY_DIR}/generated/modules/${MODULE_NAME}
|
||||
)
|
||||
|
||||
target_sources(${TEST_TARGET_NAME} PRIVATE
|
||||
EventQueueTest.cpp
|
||||
ErrorHandlingTest.cpp
|
||||
IECStateMachineTest.cpp
|
||||
OverVoltageMonitorTest.cpp
|
||||
VoltagePlausibilityMonitorTest.cpp
|
||||
../ErrorHandling.cpp
|
||||
../IECStateMachine.cpp
|
||||
../backtrace.cpp
|
||||
../over_voltage/OverVoltageMonitor.cpp
|
||||
../voltage_plausibility/VoltagePlausibilityMonitor.cpp
|
||||
)
|
||||
|
||||
target_compile_definitions(${TEST_TARGET_NAME} PRIVATE
|
||||
BUILD_TESTING_MODULE_EVSE_MANAGER
|
||||
)
|
||||
|
||||
target_link_libraries(${TEST_TARGET_NAME} PRIVATE
|
||||
GTest::gmock
|
||||
GTest::gtest_main
|
||||
everest::log
|
||||
everest::framework
|
||||
everest::helpers
|
||||
sigslot
|
||||
)
|
||||
|
||||
add_test(${TEST_TARGET_NAME} ${TEST_TARGET_NAME})
|
||||
ev_register_test_target(${TEST_TARGET_NAME})
|
||||
|
||||
set(CHARGER_TEST_TARGET_NAME ${PROJECT_NAME}_EvseManagerCharger_tests)
|
||||
add_executable(${CHARGER_TEST_TARGET_NAME})
|
||||
|
||||
add_dependencies(${CHARGER_TEST_TARGET_NAME} ${MODULE_NAME})
|
||||
|
||||
target_include_directories(${CHARGER_TEST_TARGET_NAME} PRIVATE
|
||||
.. ${TESTS_INCLUDE_DIR}
|
||||
${GENERATED_INCLUDE_DIR}
|
||||
${CMAKE_BINARY_DIR}/generated/modules/${MODULE_NAME}
|
||||
)
|
||||
|
||||
target_sources(${CHARGER_TEST_TARGET_NAME} PRIVATE
|
||||
ChargerTest.cpp
|
||||
../Charger.cpp
|
||||
)
|
||||
|
||||
target_compile_definitions(${CHARGER_TEST_TARGET_NAME} PRIVATE
|
||||
BUILD_TESTING_MODULE_EVSE_MANAGER
|
||||
)
|
||||
|
||||
target_link_libraries(${CHARGER_TEST_TARGET_NAME} PRIVATE
|
||||
GTest::gmock
|
||||
GTest::gtest_main
|
||||
everest::framework
|
||||
everest::helpers
|
||||
sigslot
|
||||
)
|
||||
|
||||
add_test(${CHARGER_TEST_TARGET_NAME} ${CHARGER_TEST_TARGET_NAME})
|
||||
ev_register_test_target(${CHARGER_TEST_TARGET_NAME})
|
||||
@@ -0,0 +1,955 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include "generated/types/evse_manager.hpp"
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <Charger.hpp>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
|
||||
namespace {
|
||||
using namespace module;
|
||||
using namespace types::evse_manager;
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// test classes
|
||||
|
||||
// class that provides access to internal state from the Charger class
|
||||
struct ChargerDerived : public Charger {
|
||||
using Charger::Charger;
|
||||
using Charger::get_enable_disable_source_table;
|
||||
using Charger::get_shared_context;
|
||||
using Charger::run_state_machine;
|
||||
|
||||
// updated when a non-zero connector is used to enable_disable()
|
||||
constexpr const auto& connector_enabled() {
|
||||
return get_shared_context().connector_enabled;
|
||||
}
|
||||
|
||||
constexpr const auto& current_state() {
|
||||
return get_shared_context().current_state;
|
||||
}
|
||||
|
||||
constexpr void current_state(EvseState state) {
|
||||
get_shared_context().current_state = state;
|
||||
}
|
||||
|
||||
constexpr const auto& flag_disable_requested() {
|
||||
return get_shared_context().flag_disable_requested;
|
||||
}
|
||||
};
|
||||
|
||||
// class that creates a consistent starting state for tests
|
||||
struct ChargerTest : public testing::Test {
|
||||
// charger requirements
|
||||
std::unique_ptr<IECStateMachine> charger_bsp;
|
||||
std::unique_ptr<ErrorHandling> charger_error_handling;
|
||||
std::vector<std::unique_ptr<powermeterIntf>> charger_powermeter_billing;
|
||||
std::unique_ptr<PersistentStore> charger_store;
|
||||
|
||||
// error handling requirements
|
||||
std::unique_ptr<evse_board_supportIntf> error_handler_bsp;
|
||||
std::vector<std::unique_ptr<ISO15118_chargerIntf>> error_handler_hlc;
|
||||
std::vector<std::unique_ptr<connector_lockIntf>> error_handler_connector_lock;
|
||||
std::vector<std::unique_ptr<ac_rcdIntf>> error_handler_ac_rcd;
|
||||
std::unique_ptr<evse_managerImplBase> error_handler_evse;
|
||||
std::vector<std::unique_ptr<isolation_monitorIntf>> error_handler_imd;
|
||||
std::vector<std::unique_ptr<power_supply_DCIntf>> error_handler_powersupply;
|
||||
std::vector<std::unique_ptr<powermeterIntf>> error_handler_powermeter;
|
||||
std::vector<std::unique_ptr<over_voltage_monitorIntf>> error_handler_over_voltage_monitor;
|
||||
|
||||
std::unique_ptr<ChargerDerived> charger;
|
||||
|
||||
ChargerTest() :
|
||||
charger_error_handling(std::make_unique<ErrorHandling>(
|
||||
error_handler_bsp, error_handler_hlc, error_handler_connector_lock, error_handler_ac_rcd,
|
||||
error_handler_evse, error_handler_imd, error_handler_powersupply, error_handler_powermeter,
|
||||
error_handler_over_voltage_monitor, false)) {
|
||||
}
|
||||
|
||||
void SetUp() override {
|
||||
reset_last_event();
|
||||
charger = std::make_unique<ChargerDerived>(
|
||||
charger_bsp, charger_error_handling, charger_powermeter_billing, charger_store,
|
||||
types::evse_board_support::Connector_type::IEC62196Type2Socket, "EVSETEST");
|
||||
charger->signal_simple_event.connect(&ChargerTest::session_event, this);
|
||||
}
|
||||
|
||||
void TearDown() override {
|
||||
charger.reset(nullptr);
|
||||
}
|
||||
|
||||
void session_event(SessionEventEnum event) {
|
||||
last_event = event;
|
||||
}
|
||||
|
||||
static constexpr SessionEventEnum default_event{SessionEventEnum::SessionFinished};
|
||||
static constexpr EnableDisableSource default_source{Enable_source::Unspecified, Enable_state::Unassigned, 10000};
|
||||
|
||||
SessionEventEnum last_event{default_event};
|
||||
|
||||
constexpr void reset_last_event() {
|
||||
last_event = default_event;
|
||||
}
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// tests for enable_disable()
|
||||
// interesting variables:
|
||||
// - enable_disable_source_table (not directly available)
|
||||
// - shared_context.connector_enabled - charger->connector_enabled()
|
||||
// - shared_context.current_state - charger->current_state()
|
||||
// - signal_simple_event - last_event
|
||||
|
||||
TEST_F(ChargerTest, EnableDisableSourceInit) {
|
||||
// check the default values on startup
|
||||
// this is the starting point for all tests
|
||||
const auto last_source = charger->get_last_enable_disable_source();
|
||||
EXPECT_EQ(last_source, default_source);
|
||||
EXPECT_EQ(last_event, default_event);
|
||||
EXPECT_TRUE(charger->connector_enabled());
|
||||
EXPECT_EQ(charger->current_state(), Charger::EvseState::Idle);
|
||||
}
|
||||
|
||||
TEST_F(ChargerTest, EnableDisableSourceInitPlusStateEnabled) {
|
||||
charger->current_state(Charger::EvseState::Idle);
|
||||
|
||||
// check the default values on startup
|
||||
// this is the starting point for all tests
|
||||
auto last_source = charger->get_last_enable_disable_source();
|
||||
EXPECT_EQ(last_source, default_source);
|
||||
EXPECT_EQ(last_event, default_event);
|
||||
EXPECT_TRUE(charger->connector_enabled());
|
||||
EXPECT_EQ(charger->current_state(), Charger::EvseState::Idle);
|
||||
|
||||
reset_last_event();
|
||||
constexpr EnableDisableSource enable_default{Enable_source::Unspecified, Enable_state::Enable, 10000};
|
||||
charger->enable_disable_initial_state_publish();
|
||||
last_source = charger->get_last_enable_disable_source();
|
||||
EXPECT_EQ(last_source, enable_default);
|
||||
EXPECT_EQ(last_event, SessionEventEnum::Enabled);
|
||||
EXPECT_TRUE(charger->connector_enabled());
|
||||
EXPECT_EQ(charger->current_state(), Charger::EvseState::Idle);
|
||||
|
||||
// already enabled so no event
|
||||
reset_last_event();
|
||||
constexpr EnableDisableSource enable_source{Enable_source::CSMS, Enable_state::Enable, 100};
|
||||
EXPECT_TRUE(charger->enable_disable(0, enable_source));
|
||||
last_source = charger->get_last_enable_disable_source();
|
||||
EXPECT_EQ(last_source, enable_source);
|
||||
EXPECT_EQ(last_event, default_event);
|
||||
}
|
||||
|
||||
TEST_F(ChargerTest, EnableDisableSourceInitPlusStateDisabled) {
|
||||
charger->current_state(Charger::EvseState::Disabled);
|
||||
|
||||
// check the default values on startup
|
||||
// this is the starting point for all tests
|
||||
auto last_source = charger->get_last_enable_disable_source();
|
||||
EXPECT_EQ(last_source, default_source);
|
||||
EXPECT_EQ(last_event, default_event);
|
||||
EXPECT_TRUE(charger->connector_enabled());
|
||||
EXPECT_EQ(charger->current_state(), Charger::EvseState::Disabled);
|
||||
|
||||
reset_last_event();
|
||||
constexpr EnableDisableSource disable_default{Enable_source::Unspecified, Enable_state::Disable, 10000};
|
||||
charger->enable_disable_initial_state_publish();
|
||||
last_source = charger->get_last_enable_disable_source();
|
||||
EXPECT_EQ(last_source, disable_default);
|
||||
EXPECT_EQ(last_event, SessionEventEnum::Disabled);
|
||||
EXPECT_TRUE(charger->connector_enabled());
|
||||
EXPECT_EQ(charger->current_state(), Charger::EvseState::Disabled);
|
||||
|
||||
// already disabled so no event
|
||||
reset_last_event();
|
||||
constexpr EnableDisableSource disable_source{Enable_source::CSMS, Enable_state::Disable, 100};
|
||||
EXPECT_FALSE(charger->enable_disable(0, disable_source));
|
||||
last_source = charger->get_last_enable_disable_source();
|
||||
EXPECT_EQ(last_source, disable_source);
|
||||
EXPECT_EQ(last_event, default_event);
|
||||
}
|
||||
|
||||
TEST_F(ChargerTest, EnableDisableSourceConnectorEnabled0) {
|
||||
// connector_enabled must only change when a non-zero connector ID is used
|
||||
ASSERT_TRUE(charger->connector_enabled());
|
||||
|
||||
constexpr EnableDisableSource enable_source{Enable_source::CSMS, Enable_state::Enable, 100};
|
||||
constexpr EnableDisableSource disable_source{Enable_source::CSMS, Enable_state::Disable, 100};
|
||||
|
||||
// test with connector ID 0: connector_enabled must not change
|
||||
EXPECT_FALSE(charger->enable_disable(0, disable_source));
|
||||
EXPECT_TRUE(charger->connector_enabled());
|
||||
EXPECT_EQ(charger->current_state(), Charger::EvseState::Disabled);
|
||||
EXPECT_TRUE(charger->enable_disable(0, enable_source));
|
||||
EXPECT_TRUE(charger->connector_enabled());
|
||||
// tricky case, connector_enabled is true but the change was to connector 0
|
||||
// this is what the original code does ...
|
||||
EXPECT_EQ(charger->current_state(), Charger::EvseState::Idle);
|
||||
|
||||
// force connector_enabled false
|
||||
EXPECT_FALSE(charger->enable_disable(1, disable_source));
|
||||
EXPECT_FALSE(charger->connector_enabled());
|
||||
|
||||
// test with connector ID 0: connector_enabled must not change
|
||||
EXPECT_FALSE(charger->enable_disable(0, disable_source));
|
||||
EXPECT_FALSE(charger->connector_enabled());
|
||||
EXPECT_EQ(charger->current_state(), Charger::EvseState::Disabled);
|
||||
EXPECT_TRUE(charger->enable_disable(0, enable_source));
|
||||
EXPECT_FALSE(charger->connector_enabled());
|
||||
// enable on connector 0 does not change state to Idle
|
||||
EXPECT_EQ(charger->current_state(), Charger::EvseState::Disabled);
|
||||
}
|
||||
|
||||
TEST_F(ChargerTest, EnableDisableSourceConnectorEnabled1) {
|
||||
// connector_enabled must only change when a non-zero connector ID is used
|
||||
ASSERT_TRUE(charger->connector_enabled());
|
||||
|
||||
constexpr EnableDisableSource enable_source{Enable_source::CSMS, Enable_state::Enable, 100};
|
||||
constexpr EnableDisableSource disable_source{Enable_source::CSMS, Enable_state::Disable, 100};
|
||||
|
||||
// test with connector ID 1: connector_enabled must change
|
||||
EXPECT_FALSE(charger->enable_disable(1, disable_source));
|
||||
EXPECT_FALSE(charger->connector_enabled());
|
||||
EXPECT_EQ(charger->current_state(), Charger::EvseState::Disabled);
|
||||
EXPECT_TRUE(charger->enable_disable(1, enable_source));
|
||||
EXPECT_TRUE(charger->connector_enabled());
|
||||
EXPECT_EQ(charger->current_state(), Charger::EvseState::Idle);
|
||||
EXPECT_FALSE(charger->enable_disable(1, disable_source));
|
||||
EXPECT_FALSE(charger->connector_enabled());
|
||||
EXPECT_EQ(charger->current_state(), Charger::EvseState::Disabled);
|
||||
}
|
||||
|
||||
constexpr EnableDisableSource enable_source_10{Enable_source::CSMS, Enable_state::Enable, 10};
|
||||
constexpr EnableDisableSource enable_source_100{Enable_source::CSMS, Enable_state::Enable, 100};
|
||||
constexpr EnableDisableSource enable_source_1000{Enable_source::CSMS, Enable_state::Enable, 1000};
|
||||
constexpr EnableDisableSource disable_source_10{Enable_source::CSMS, Enable_state::Disable, 10};
|
||||
constexpr EnableDisableSource disable_source_100{Enable_source::CSMS, Enable_state::Disable, 100};
|
||||
constexpr EnableDisableSource disable_source_1000{Enable_source::CSMS, Enable_state::Disable, 1000};
|
||||
constexpr EnableDisableSource unassigned_source_10{Enable_source::CSMS, Enable_state::Unassigned, 10};
|
||||
constexpr EnableDisableSource unassigned_source_100{Enable_source::CSMS, Enable_state::Unassigned, 100};
|
||||
constexpr EnableDisableSource unassigned_source_1000{Enable_source::CSMS, Enable_state::Unassigned, 1000};
|
||||
|
||||
TEST_F(ChargerTest, EnableDisableTableSingleSource) {
|
||||
// enable_disable settings are added to a table
|
||||
// parse_enable_disable_source_table() processes the table and updates
|
||||
// active_enable_disable_source which is available via get_last_enable_disable_source()
|
||||
// enable_disable() updates the table and calls parse_enable_disable_source_table()
|
||||
|
||||
// EnableDisableTable
|
||||
// Evaluation will be done based on priorities. 0 is the highest priority, 10000 the lowest
|
||||
// If all sources are unassigned, the connector is enabled
|
||||
// If two sources have the same priority, "disabled" has priority over "enabled"
|
||||
|
||||
const int connector_id = 1; // use a consistent value
|
||||
|
||||
const auto& enable_disable_source_table = charger->get_enable_disable_source_table();
|
||||
|
||||
EXPECT_TRUE(enable_disable_source_table.empty());
|
||||
EXPECT_EQ(enable_disable_source_table.size(), 0);
|
||||
auto last_source = charger->get_last_enable_disable_source();
|
||||
EXPECT_EQ(last_source, default_source);
|
||||
|
||||
// check source change of state
|
||||
|
||||
EXPECT_TRUE(charger->enable_disable(connector_id, enable_source_100));
|
||||
EXPECT_EQ(enable_disable_source_table.size(), 1);
|
||||
last_source = charger->get_last_enable_disable_source();
|
||||
EXPECT_EQ(last_source, enable_source_100);
|
||||
|
||||
EXPECT_FALSE(charger->enable_disable(connector_id, disable_source_100));
|
||||
EXPECT_EQ(enable_disable_source_table.size(), 1);
|
||||
last_source = charger->get_last_enable_disable_source();
|
||||
EXPECT_EQ(last_source, disable_source_100);
|
||||
|
||||
EXPECT_TRUE(charger->enable_disable(connector_id, unassigned_source_100));
|
||||
EXPECT_EQ(enable_disable_source_table.size(), 1);
|
||||
last_source = charger->get_last_enable_disable_source();
|
||||
// unexpected
|
||||
EXPECT_EQ(last_source, default_source);
|
||||
|
||||
// check source change of priority
|
||||
|
||||
EXPECT_TRUE(charger->enable_disable(connector_id, enable_source_1000));
|
||||
EXPECT_EQ(enable_disable_source_table.size(), 1);
|
||||
last_source = charger->get_last_enable_disable_source();
|
||||
EXPECT_EQ(last_source, enable_source_1000);
|
||||
|
||||
EXPECT_TRUE(charger->enable_disable(connector_id, enable_source_100));
|
||||
EXPECT_EQ(enable_disable_source_table.size(), 1);
|
||||
last_source = charger->get_last_enable_disable_source();
|
||||
EXPECT_EQ(last_source, enable_source_100);
|
||||
|
||||
EXPECT_TRUE(charger->enable_disable(connector_id, enable_source_10));
|
||||
EXPECT_EQ(enable_disable_source_table.size(), 1);
|
||||
last_source = charger->get_last_enable_disable_source();
|
||||
EXPECT_EQ(last_source, enable_source_10);
|
||||
|
||||
EXPECT_FALSE(charger->enable_disable(connector_id, disable_source_1000));
|
||||
EXPECT_EQ(enable_disable_source_table.size(), 1);
|
||||
last_source = charger->get_last_enable_disable_source();
|
||||
EXPECT_EQ(last_source, disable_source_1000);
|
||||
|
||||
EXPECT_FALSE(charger->enable_disable(connector_id, disable_source_100));
|
||||
EXPECT_EQ(enable_disable_source_table.size(), 1);
|
||||
last_source = charger->get_last_enable_disable_source();
|
||||
EXPECT_EQ(last_source, disable_source_100);
|
||||
|
||||
EXPECT_FALSE(charger->enable_disable(connector_id, disable_source_10));
|
||||
EXPECT_EQ(enable_disable_source_table.size(), 1);
|
||||
last_source = charger->get_last_enable_disable_source();
|
||||
EXPECT_EQ(last_source, disable_source_10);
|
||||
|
||||
// last_source is always the default since Unassigned
|
||||
// entries are ignored
|
||||
|
||||
EXPECT_TRUE(charger->enable_disable(connector_id, unassigned_source_1000));
|
||||
EXPECT_EQ(enable_disable_source_table.size(), 1);
|
||||
last_source = charger->get_last_enable_disable_source();
|
||||
EXPECT_EQ(last_source, default_source);
|
||||
|
||||
EXPECT_TRUE(charger->enable_disable(connector_id, unassigned_source_100));
|
||||
EXPECT_EQ(enable_disable_source_table.size(), 1);
|
||||
last_source = charger->get_last_enable_disable_source();
|
||||
EXPECT_EQ(last_source, default_source);
|
||||
|
||||
EXPECT_TRUE(charger->enable_disable(connector_id, unassigned_source_10));
|
||||
EXPECT_EQ(enable_disable_source_table.size(), 1);
|
||||
last_source = charger->get_last_enable_disable_source();
|
||||
EXPECT_EQ(last_source, default_source);
|
||||
}
|
||||
|
||||
TEST_F(ChargerTest, EnableDisableTableSourceMulti) {
|
||||
// enable_disable settings are added to a table
|
||||
// parse_enable_disable_source_table() processes the table and updates
|
||||
// active_enable_disable_source which is available via get_last_enable_disable_source()
|
||||
// enable_disable() updates the table and calls parse_enable_disable_source_table()
|
||||
|
||||
// EnableDisableTable
|
||||
// Evaluation will be done based on priorities. 0 is the highest priority, 10000 the lowest
|
||||
// If all sources are unassigned, the connector is enabled
|
||||
// If two sources have the same priority, "disabled" has priority over "enabled"
|
||||
|
||||
const int connector_id = 1; // use a consistent value
|
||||
|
||||
const auto& enable_disable_source_table = charger->get_enable_disable_source_table();
|
||||
|
||||
EXPECT_TRUE(enable_disable_source_table.empty());
|
||||
EXPECT_EQ(enable_disable_source_table.size(), 0);
|
||||
auto last_source = charger->get_last_enable_disable_source();
|
||||
EXPECT_EQ(last_source, default_source);
|
||||
|
||||
// check multiple sources are added to the table
|
||||
|
||||
EXPECT_TRUE(charger->enable_disable(connector_id, enable_source_10));
|
||||
EXPECT_EQ(enable_disable_source_table.size(), 1);
|
||||
last_source = charger->get_last_enable_disable_source();
|
||||
EXPECT_EQ(last_source, enable_source_10);
|
||||
|
||||
EXPECT_TRUE(charger->enable_disable(connector_id, {Enable_source::FirmwareUpdate, Enable_state::Enable, 11}));
|
||||
EXPECT_EQ(enable_disable_source_table.size(), 2);
|
||||
last_source = charger->get_last_enable_disable_source();
|
||||
EXPECT_EQ(last_source, enable_source_10);
|
||||
|
||||
EXPECT_TRUE(charger->enable_disable(connector_id, {Enable_source::LocalAPI, Enable_state::Enable, 11}));
|
||||
EXPECT_EQ(enable_disable_source_table.size(), 3);
|
||||
last_source = charger->get_last_enable_disable_source();
|
||||
EXPECT_EQ(last_source, enable_source_10);
|
||||
|
||||
EXPECT_TRUE(charger->enable_disable(connector_id, {Enable_source::LocalKeyLock, Enable_state::Enable, 11}));
|
||||
EXPECT_EQ(enable_disable_source_table.size(), 4);
|
||||
last_source = charger->get_last_enable_disable_source();
|
||||
EXPECT_EQ(last_source, enable_source_10);
|
||||
|
||||
EXPECT_TRUE(charger->enable_disable(connector_id, {Enable_source::MobileApp, Enable_state::Enable, 11}));
|
||||
EXPECT_EQ(enable_disable_source_table.size(), 5);
|
||||
last_source = charger->get_last_enable_disable_source();
|
||||
EXPECT_EQ(last_source, enable_source_10);
|
||||
|
||||
EXPECT_TRUE(charger->enable_disable(connector_id, {Enable_source::RemoteKeyLock, Enable_state::Enable, 11}));
|
||||
EXPECT_EQ(enable_disable_source_table.size(), 6);
|
||||
last_source = charger->get_last_enable_disable_source();
|
||||
EXPECT_EQ(last_source, enable_source_10);
|
||||
|
||||
const EnableDisableSource disable_source{Enable_source::ServiceTechnician, Enable_state::Disable, 11};
|
||||
EXPECT_TRUE(charger->enable_disable(connector_id, disable_source));
|
||||
EXPECT_EQ(enable_disable_source_table.size(), 7);
|
||||
last_source = charger->get_last_enable_disable_source();
|
||||
EXPECT_EQ(last_source, enable_source_10);
|
||||
|
||||
EXPECT_TRUE(charger->enable_disable(connector_id, {Enable_source::Unspecified, Enable_state::Enable, 11}));
|
||||
EXPECT_EQ(enable_disable_source_table.size(), 8);
|
||||
last_source = charger->get_last_enable_disable_source();
|
||||
EXPECT_EQ(last_source, enable_source_10);
|
||||
|
||||
// update CSMS - next highest expected - disable_source
|
||||
EXPECT_FALSE(charger->enable_disable(connector_id, enable_source_100));
|
||||
EXPECT_EQ(enable_disable_source_table.size(), 8);
|
||||
last_source = charger->get_last_enable_disable_source();
|
||||
EXPECT_EQ(last_source, disable_source);
|
||||
}
|
||||
|
||||
TEST_F(ChargerTest, EnableDisableTablePriority) {
|
||||
// enable_disable settings are added to a table
|
||||
// parse_enable_disable_source_table() processes the table and updates
|
||||
// active_enable_disable_source which is available via get_last_enable_disable_source()
|
||||
// enable_disable() updates the table and calls parse_enable_disable_source_table()
|
||||
|
||||
// EnableDisableTable
|
||||
// Evaluation will be done based on priorities. 0 is the highest priority, 10000 the lowest
|
||||
// If all sources are unassigned, the connector is enabled
|
||||
// If two sources have the same priority, "disabled" has priority over "enabled"
|
||||
|
||||
const int connector_id = 1; // use a consistent value
|
||||
|
||||
const auto& enable_disable_source_table = charger->get_enable_disable_source_table();
|
||||
|
||||
EXPECT_TRUE(enable_disable_source_table.empty());
|
||||
EXPECT_EQ(enable_disable_source_table.size(), 0);
|
||||
auto last_source = charger->get_last_enable_disable_source();
|
||||
EXPECT_EQ(last_source, default_source);
|
||||
|
||||
// check priority is being respected (via different sources)
|
||||
// base priority is 10 - higher values must be ignored
|
||||
|
||||
EXPECT_FALSE(charger->enable_disable(connector_id, disable_source_10));
|
||||
EXPECT_EQ(enable_disable_source_table.size(), 1);
|
||||
last_source = charger->get_last_enable_disable_source();
|
||||
EXPECT_EQ(last_source, disable_source_10);
|
||||
|
||||
// ignored because disable has higher priority over enabled
|
||||
const EnableDisableSource next_expected{Enable_source::FirmwareUpdate, Enable_state::Enable, 10};
|
||||
EXPECT_FALSE(charger->enable_disable(connector_id, next_expected));
|
||||
EXPECT_EQ(enable_disable_source_table.size(), 2);
|
||||
last_source = charger->get_last_enable_disable_source();
|
||||
EXPECT_EQ(last_source, disable_source_10);
|
||||
|
||||
// ignored because unassigned is ignored
|
||||
EXPECT_FALSE(charger->enable_disable(connector_id, {Enable_source::LocalAPI, Enable_state::Unassigned, 9}));
|
||||
EXPECT_EQ(enable_disable_source_table.size(), 3);
|
||||
last_source = charger->get_last_enable_disable_source();
|
||||
EXPECT_EQ(last_source, disable_source_10);
|
||||
|
||||
// ignored because priority is lower
|
||||
EXPECT_FALSE(charger->enable_disable(connector_id, {Enable_source::LocalAPI, Enable_state::Disable, 12}));
|
||||
EXPECT_EQ(enable_disable_source_table.size(), 3);
|
||||
last_source = charger->get_last_enable_disable_source();
|
||||
EXPECT_EQ(last_source, disable_source_10);
|
||||
|
||||
// ignored because priority is even lower
|
||||
EXPECT_FALSE(charger->enable_disable(connector_id, {Enable_source::LocalAPI, Enable_state::Enable, 200}));
|
||||
EXPECT_EQ(enable_disable_source_table.size(), 3);
|
||||
last_source = charger->get_last_enable_disable_source();
|
||||
EXPECT_EQ(last_source, disable_source_10);
|
||||
|
||||
// overrides CSMS so it is ignored - expected is
|
||||
EXPECT_TRUE(charger->enable_disable(connector_id, unassigned_source_10));
|
||||
EXPECT_EQ(enable_disable_source_table.size(), 3);
|
||||
last_source = charger->get_last_enable_disable_source();
|
||||
EXPECT_EQ(last_source, next_expected);
|
||||
|
||||
// disabled takes priority
|
||||
const EnableDisableSource last_expected{Enable_source::LocalAPI, Enable_state::Disable, 10};
|
||||
EXPECT_FALSE(charger->enable_disable(connector_id, last_expected));
|
||||
EXPECT_EQ(enable_disable_source_table.size(), 3);
|
||||
last_source = charger->get_last_enable_disable_source();
|
||||
EXPECT_EQ(last_source, last_expected);
|
||||
|
||||
// higher priority
|
||||
const EnableDisableSource higher_expected{Enable_source::FirmwareUpdate, Enable_state::Enable, 5};
|
||||
EXPECT_TRUE(charger->enable_disable(connector_id, higher_expected));
|
||||
EXPECT_EQ(enable_disable_source_table.size(), 3);
|
||||
last_source = charger->get_last_enable_disable_source();
|
||||
EXPECT_EQ(last_source, higher_expected);
|
||||
}
|
||||
|
||||
TEST_F(ChargerTest, EnableDisableSourceEnable0) {
|
||||
constexpr EnableDisableSource sourceA{Enable_source::CSMS, Enable_state::Unassigned, 100};
|
||||
constexpr EnableDisableSource sourceB{Enable_source::FirmwareUpdate, Enable_state::Enable, 90};
|
||||
|
||||
// enable from default state
|
||||
|
||||
EXPECT_TRUE(charger->enable_disable(0, sourceA));
|
||||
auto last_source = charger->get_last_enable_disable_source();
|
||||
// Unassigned updates do not change the result from get_last_enable_disable_source()
|
||||
EXPECT_EQ(last_source, default_source);
|
||||
// There should not be an update event - default state is enabled
|
||||
EXPECT_EQ(last_event, default_event);
|
||||
EXPECT_EQ(charger->current_state(), Charger::EvseState::Idle);
|
||||
EXPECT_TRUE(charger->connector_enabled()); // default state
|
||||
|
||||
reset_last_event();
|
||||
EXPECT_TRUE(charger->enable_disable(0, sourceB));
|
||||
last_source = charger->get_last_enable_disable_source();
|
||||
// Unassigned updates do not change the result from get_last_enable_disable_source()
|
||||
EXPECT_EQ(last_source, sourceB);
|
||||
// There should not be an update event - enabled -> enabled
|
||||
EXPECT_EQ(last_event, default_event);
|
||||
EXPECT_EQ(charger->current_state(), Charger::EvseState::Idle);
|
||||
EXPECT_TRUE(charger->connector_enabled()); // default state
|
||||
}
|
||||
|
||||
TEST_F(ChargerTest, EnableDisableSourceEnable1A) {
|
||||
constexpr EnableDisableSource sourceA{Enable_source::CSMS, Enable_state::Disable, 100};
|
||||
constexpr EnableDisableSource sourceB{Enable_source::FirmwareUpdate, Enable_state::Enable, 90};
|
||||
|
||||
// force a complete disable state
|
||||
EXPECT_FALSE(charger->enable_disable(1, sourceA));
|
||||
auto last_source = charger->get_last_enable_disable_source();
|
||||
// Unassigned updates do not change the result from get_last_enable_disable_source()
|
||||
EXPECT_EQ(last_source, sourceA);
|
||||
EXPECT_EQ(last_event, SessionEventEnum::Disabled);
|
||||
EXPECT_EQ(charger->current_state(), Charger::EvseState::Disabled);
|
||||
EXPECT_FALSE(charger->connector_enabled());
|
||||
|
||||
// enable on connector 1
|
||||
reset_last_event();
|
||||
EXPECT_TRUE(charger->enable_disable(1, sourceB));
|
||||
last_source = charger->get_last_enable_disable_source();
|
||||
EXPECT_EQ(last_source, sourceB);
|
||||
EXPECT_EQ(last_event, SessionEventEnum::Enabled);
|
||||
EXPECT_EQ(charger->current_state(), Charger::EvseState::Idle);
|
||||
EXPECT_TRUE(charger->connector_enabled());
|
||||
}
|
||||
|
||||
TEST_F(ChargerTest, EnableDisableSourceEnable1B) {
|
||||
constexpr EnableDisableSource sourceA{Enable_source::CSMS, Enable_state::Disable, 100};
|
||||
constexpr EnableDisableSource sourceB{Enable_source::FirmwareUpdate, Enable_state::Enable, 90};
|
||||
|
||||
// force a complete disable state
|
||||
EXPECT_FALSE(charger->enable_disable(1, sourceA));
|
||||
auto last_source = charger->get_last_enable_disable_source();
|
||||
// Unassigned updates do not change the result from get_last_enable_disable_source()
|
||||
EXPECT_EQ(last_source, sourceA);
|
||||
EXPECT_EQ(last_event, SessionEventEnum::Disabled);
|
||||
EXPECT_EQ(charger->current_state(), Charger::EvseState::Disabled);
|
||||
EXPECT_FALSE(charger->connector_enabled());
|
||||
|
||||
// enable on connector 0
|
||||
reset_last_event();
|
||||
EXPECT_TRUE(charger->enable_disable(0, sourceB));
|
||||
last_source = charger->get_last_enable_disable_source();
|
||||
EXPECT_EQ(last_source, sourceB);
|
||||
EXPECT_EQ(last_event, SessionEventEnum::Enabled);
|
||||
EXPECT_EQ(charger->current_state(), Charger::EvseState::Disabled);
|
||||
EXPECT_FALSE(charger->connector_enabled());
|
||||
|
||||
// enable on connector 1
|
||||
reset_last_event();
|
||||
EXPECT_TRUE(charger->enable_disable(1, sourceB));
|
||||
last_source = charger->get_last_enable_disable_source();
|
||||
EXPECT_EQ(last_source, sourceB);
|
||||
// enable -> enable so no event
|
||||
EXPECT_EQ(last_event, default_event);
|
||||
EXPECT_EQ(charger->current_state(), Charger::EvseState::Idle);
|
||||
EXPECT_TRUE(charger->connector_enabled());
|
||||
}
|
||||
|
||||
TEST_F(ChargerTest, EnableDisableSourceDisable0) {
|
||||
constexpr EnableDisableSource sourceA{Enable_source::CSMS, Enable_state::Unassigned, 100};
|
||||
constexpr EnableDisableSource sourceB{Enable_source::FirmwareUpdate, Enable_state::Disable, 100};
|
||||
|
||||
// disable from default state
|
||||
|
||||
EXPECT_TRUE(charger->enable_disable(0, sourceA));
|
||||
auto last_source = charger->get_last_enable_disable_source();
|
||||
// Unassigned updates do not change the result from get_last_enable_disable_source()
|
||||
EXPECT_EQ(last_source, default_source);
|
||||
// This is possibly an error
|
||||
EXPECT_EQ(last_event, default_event);
|
||||
EXPECT_EQ(charger->current_state(), Charger::EvseState::Idle);
|
||||
|
||||
reset_last_event();
|
||||
EXPECT_FALSE(charger->enable_disable(0, sourceB));
|
||||
last_source = charger->get_last_enable_disable_source();
|
||||
EXPECT_EQ(last_source, sourceB);
|
||||
EXPECT_EQ(last_event, SessionEventEnum::Disabled);
|
||||
EXPECT_EQ(charger->current_state(), Charger::EvseState::Disabled);
|
||||
}
|
||||
|
||||
TEST_F(ChargerTest, EnableDisableSourceDisable1) {
|
||||
constexpr EnableDisableSource sourceA{Enable_source::CSMS, Enable_state::Unassigned, 100};
|
||||
constexpr EnableDisableSource sourceB{Enable_source::FirmwareUpdate, Enable_state::Disable, 100};
|
||||
|
||||
// disable from default state
|
||||
|
||||
EXPECT_TRUE(charger->enable_disable(0, sourceA));
|
||||
auto last_source = charger->get_last_enable_disable_source();
|
||||
// Unassigned updates do not change the result from get_last_enable_disable_source()
|
||||
EXPECT_EQ(last_source, default_source);
|
||||
// This is possibly an error
|
||||
EXPECT_EQ(last_event, default_event);
|
||||
EXPECT_EQ(charger->current_state(), Charger::EvseState::Idle);
|
||||
|
||||
reset_last_event();
|
||||
EXPECT_FALSE(charger->enable_disable(0, sourceB));
|
||||
last_source = charger->get_last_enable_disable_source();
|
||||
EXPECT_EQ(last_source, sourceB);
|
||||
EXPECT_EQ(last_event, SessionEventEnum::Disabled);
|
||||
EXPECT_EQ(charger->current_state(), Charger::EvseState::Disabled);
|
||||
|
||||
reset_last_event();
|
||||
EXPECT_FALSE(charger->enable_disable(1, sourceB));
|
||||
last_source = charger->get_last_enable_disable_source();
|
||||
EXPECT_EQ(last_source, sourceB);
|
||||
// disable -> disable so no event
|
||||
EXPECT_EQ(last_event, default_event);
|
||||
EXPECT_EQ(charger->current_state(), Charger::EvseState::Disabled);
|
||||
}
|
||||
|
||||
TEST_F(ChargerTest, EnableDisableSourceDisableEnable) {
|
||||
constexpr EnableDisableSource sourceA{Enable_source::CSMS, Enable_state::Unassigned, 100};
|
||||
constexpr EnableDisableSource sourceB{Enable_source::FirmwareUpdate, Enable_state::Disable, 100};
|
||||
constexpr EnableDisableSource sourceC{Enable_source::LocalAPI, Enable_state::Enable, 90};
|
||||
|
||||
// default state
|
||||
EXPECT_TRUE(charger->enable_disable(0, sourceA));
|
||||
auto last_source = charger->get_last_enable_disable_source();
|
||||
// Unassigned updates do not change the result from get_last_enable_disable_source()
|
||||
EXPECT_EQ(last_source, default_source);
|
||||
// This is possibly an error
|
||||
EXPECT_EQ(last_event, default_event);
|
||||
EXPECT_EQ(charger->current_state(), Charger::EvseState::Idle);
|
||||
|
||||
// disable 0
|
||||
reset_last_event();
|
||||
EXPECT_FALSE(charger->enable_disable(0, sourceB));
|
||||
last_source = charger->get_last_enable_disable_source();
|
||||
EXPECT_EQ(last_source, sourceB);
|
||||
EXPECT_EQ(last_event, SessionEventEnum::Disabled);
|
||||
EXPECT_EQ(charger->current_state(), Charger::EvseState::Disabled);
|
||||
|
||||
// disable 1
|
||||
reset_last_event();
|
||||
EXPECT_FALSE(charger->enable_disable(1, sourceB));
|
||||
last_source = charger->get_last_enable_disable_source();
|
||||
EXPECT_EQ(last_source, sourceB);
|
||||
// no event: disable -> disable
|
||||
EXPECT_EQ(last_event, default_event);
|
||||
EXPECT_EQ(charger->current_state(), Charger::EvseState::Disabled);
|
||||
|
||||
// enable 0
|
||||
reset_last_event();
|
||||
EXPECT_TRUE(charger->enable_disable(0, sourceC));
|
||||
last_source = charger->get_last_enable_disable_source();
|
||||
EXPECT_EQ(last_source, sourceC);
|
||||
EXPECT_EQ(last_event, SessionEventEnum::Enabled);
|
||||
// remains disabled because the enable was on connector 0
|
||||
EXPECT_EQ(charger->current_state(), Charger::EvseState::Disabled);
|
||||
|
||||
// enable 1
|
||||
reset_last_event();
|
||||
EXPECT_TRUE(charger->enable_disable(1, sourceC));
|
||||
last_source = charger->get_last_enable_disable_source();
|
||||
EXPECT_EQ(last_source, sourceC);
|
||||
// enable->enable hence no event
|
||||
EXPECT_EQ(last_event, default_event);
|
||||
// updated because not connector 0
|
||||
EXPECT_EQ(charger->current_state(), Charger::EvseState::Idle);
|
||||
}
|
||||
|
||||
// tests for cancel_transaction() / authorize() interaction
|
||||
TEST_F(ChargerTest, DelayedAuthorizeAfterCancelTransactionIsIgnored) {
|
||||
auto& ctx = charger->get_shared_context();
|
||||
|
||||
// Simulate an active charging session
|
||||
ctx.flag_transaction_active = true;
|
||||
ctx.session_active = true;
|
||||
ctx.flag_authorized = true;
|
||||
|
||||
// External cancellation (e.g. OCPP RemoteStop)
|
||||
types::evse_manager::StopTransactionRequest stop_request;
|
||||
stop_request.reason = types::evse_manager::StopTransactionReason::Remote;
|
||||
EXPECT_TRUE(charger->cancel_transaction(stop_request));
|
||||
|
||||
EXPECT_FALSE(ctx.flag_authorized);
|
||||
EXPECT_TRUE(ctx.flag_externally_cancelled);
|
||||
|
||||
// Delayed authorization response arrives after the cancellation
|
||||
types::authorization::ProvidedIdToken token;
|
||||
token.id_token.value = "DELAYED_TOKEN";
|
||||
token.id_token.type = types::authorization::IdTokenType::Central;
|
||||
token.authorization_type = types::authorization::AuthorizationType::OCPP;
|
||||
|
||||
types::authorization::ValidationResult validation_result;
|
||||
validation_result.authorization_status = types::authorization::AuthorizationStatus::Accepted;
|
||||
|
||||
charger->authorize(true, token, validation_result);
|
||||
|
||||
// The delayed response must not restore authorization
|
||||
EXPECT_FALSE(ctx.flag_authorized);
|
||||
EXPECT_TRUE(ctx.flag_externally_cancelled);
|
||||
}
|
||||
|
||||
// Test that disabling while a transaction is active goes through the proper
|
||||
// StoppingCharging->Finished->Disabled sequence instead of jumping directly.
|
||||
TEST_F(ChargerTest, DisableDuringActiveTransaction) {
|
||||
constexpr EnableDisableSource disable_source{Enable_source::CSMS, Enable_state::Disable, 100};
|
||||
|
||||
auto& ctx = charger->get_shared_context();
|
||||
|
||||
// Simulate an active charging session with contactors closed
|
||||
ctx.current_state = Charger::EvseState::Charging;
|
||||
ctx.flag_transaction_active = true;
|
||||
ctx.session_active = true;
|
||||
ctx.flag_authorized = true;
|
||||
ctx.contactor_open = false;
|
||||
|
||||
reset_last_event();
|
||||
EXPECT_FALSE(charger->enable_disable(1, disable_source));
|
||||
|
||||
// enable_disable calls run_state_machine synchronously: Charging->StoppingCharging.
|
||||
// Must NOT immediately jump to Disabled — session needs proper teardown.
|
||||
EXPECT_EQ(ctx.current_state, Charger::EvseState::StoppingCharging);
|
||||
EXPECT_TRUE(charger->flag_disable_requested());
|
||||
EXPECT_EQ(ctx.last_stop_transaction_reason, StopTransactionReason::EVSEDisabled);
|
||||
|
||||
// Simulate relay opening and transaction already stopped
|
||||
ctx.contactor_open = true;
|
||||
ctx.flag_transaction_active = false;
|
||||
|
||||
// State machine: StoppingCharging->Finished->Disabled (all in one loop)
|
||||
reset_last_event();
|
||||
charger->run_state_machine();
|
||||
EXPECT_EQ(ctx.current_state, Charger::EvseState::Disabled);
|
||||
EXPECT_EQ(last_event, SessionEventEnum::Disabled);
|
||||
}
|
||||
|
||||
// Test that disabling while in WaitingForAuthentication (no transaction yet)
|
||||
// ends the session and transitions to Disabled without going through StoppingCharging.
|
||||
TEST_F(ChargerTest, DisableDuringWaitingForAuthentication) {
|
||||
constexpr EnableDisableSource disable_source{Enable_source::CSMS, Enable_state::Disable, 100};
|
||||
|
||||
auto& ctx = charger->get_shared_context();
|
||||
|
||||
// Simulate EV plugged in, waiting for auth — no transaction started yet
|
||||
ctx.current_state = Charger::EvseState::WaitingForAuthentication;
|
||||
ctx.session_active = true;
|
||||
ctx.flag_ev_plugged_in = true;
|
||||
ctx.flag_transaction_active = false;
|
||||
ctx.flag_authorized = false;
|
||||
|
||||
reset_last_event();
|
||||
EXPECT_FALSE(charger->enable_disable(1, disable_source));
|
||||
|
||||
// run_state_machine is called synchronously inside enable_disable, so by
|
||||
// the time enable_disable returns the state machine has already driven
|
||||
// WaitingForAuthentication -> Finished -> Disabled.
|
||||
EXPECT_EQ(ctx.current_state, Charger::EvseState::Disabled);
|
||||
EXPECT_EQ(last_event, SessionEventEnum::Disabled);
|
||||
}
|
||||
|
||||
// Test that disabling while in Idle (no session) immediately transitions to Disabled
|
||||
TEST_F(ChargerTest, DisableDuringIdle) {
|
||||
constexpr EnableDisableSource disable_source{Enable_source::CSMS, Enable_state::Disable, 100};
|
||||
|
||||
auto& ctx = charger->get_shared_context();
|
||||
|
||||
// Simulate EV plugged in, no session active
|
||||
ctx.current_state = Charger::EvseState::Idle;
|
||||
ctx.session_active = false;
|
||||
ctx.flag_ev_plugged_in = true;
|
||||
ctx.flag_transaction_active = false;
|
||||
ctx.flag_authorized = false;
|
||||
|
||||
reset_last_event();
|
||||
EXPECT_FALSE(charger->enable_disable(1, disable_source));
|
||||
|
||||
// Must immediately transition to Disabled
|
||||
EXPECT_EQ(ctx.current_state, Charger::EvseState::Disabled);
|
||||
EXPECT_EQ(last_event, SessionEventEnum::Disabled);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// the following code is stubs to enable testing Charger in isolation.
|
||||
// If these stubs are needed elsewhere then they could go into separate files
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// backtrace stub
|
||||
namespace Everest {
|
||||
void signal_handler(int signo) {
|
||||
}
|
||||
void install_backtrace_handler() {
|
||||
}
|
||||
void request_backtrace(pthread_t id) {
|
||||
}
|
||||
} // namespace Everest
|
||||
|
||||
namespace module {
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// IECStateMachine stub
|
||||
IECStateMachine::IECStateMachine(const std::unique_ptr<evse_board_supportIntf>& r_bsp_,
|
||||
bool lock_connector_in_state_b_) :
|
||||
r_bsp(r_bsp_) {
|
||||
}
|
||||
void IECStateMachine::process_bsp_event(const types::board_support_common::BspEvent& bsp_event) {
|
||||
}
|
||||
void IECStateMachine::allow_power_on(bool value, types::evse_board_support::Reason reason) {
|
||||
}
|
||||
|
||||
std::optional<double> IECStateMachine::read_pp_ampacity() {
|
||||
return std::nullopt;
|
||||
}
|
||||
void IECStateMachine::switch_three_phases_while_charging(bool n) {
|
||||
}
|
||||
void IECStateMachine::setup(bool has_ventilation) {
|
||||
}
|
||||
|
||||
void IECStateMachine::set_overcurrent_limit(double amps) {
|
||||
}
|
||||
|
||||
void IECStateMachine::set_pwm(double value) {
|
||||
}
|
||||
void IECStateMachine::set_cp_state_X1() {
|
||||
}
|
||||
void IECStateMachine::set_cp_state_F() {
|
||||
}
|
||||
|
||||
void IECStateMachine::enable(bool en) {
|
||||
}
|
||||
|
||||
void IECStateMachine::connector_force_unlock() {
|
||||
}
|
||||
|
||||
const std::string cpevent_to_string(CPEvent e) {
|
||||
switch (e) {
|
||||
case CPEvent::CarPluggedIn:
|
||||
return "CarPluggedIn";
|
||||
case CPEvent::CarRequestedPower:
|
||||
return "CarRequestedPower";
|
||||
case CPEvent::PowerOn:
|
||||
return "PowerOn";
|
||||
case CPEvent::PowerOff:
|
||||
return "PowerOff";
|
||||
case CPEvent::CarRequestedStopPower:
|
||||
return "CarRequestedStopPower";
|
||||
case CPEvent::CarUnplugged:
|
||||
return "CarUnplugged";
|
||||
case CPEvent::EFtoBCD:
|
||||
return "EFtoBCD";
|
||||
case CPEvent::BCDtoEF:
|
||||
return "BCDtoEF";
|
||||
case CPEvent::BCDtoE:
|
||||
return "BCDtoE";
|
||||
}
|
||||
throw std::out_of_range("No known string conversion for provided enum of type CPEvent");
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// ErrorHandling stub
|
||||
ErrorHandling::ErrorHandling(const std::unique_ptr<evse_board_supportIntf>& r_bsp,
|
||||
const std::vector<std::unique_ptr<ISO15118_chargerIntf>>& r_hlc,
|
||||
const std::vector<std::unique_ptr<connector_lockIntf>>& r_connector_lock,
|
||||
const std::vector<std::unique_ptr<ac_rcdIntf>>& r_ac_rcd,
|
||||
const std::unique_ptr<evse_managerImplBase>& _p_evse,
|
||||
const std::vector<std::unique_ptr<isolation_monitorIntf>>& _r_imd,
|
||||
const std::vector<std::unique_ptr<power_supply_DCIntf>>& _r_powersupply,
|
||||
const std::vector<std::unique_ptr<powermeterIntf>>& _r_powermeter,
|
||||
const std::vector<std::unique_ptr<over_voltage_monitorIntf>>& _r_over_voltage_monitor,
|
||||
bool _inoperative_error_use_vendor_id) :
|
||||
r_bsp(r_bsp),
|
||||
r_hlc(r_hlc),
|
||||
r_connector_lock(r_connector_lock),
|
||||
r_ac_rcd(r_ac_rcd),
|
||||
p_evse(p_evse),
|
||||
r_imd(_r_imd),
|
||||
r_powersupply(r_powersupply),
|
||||
r_powermeter(_r_powermeter),
|
||||
r_over_voltage_monitor(_r_over_voltage_monitor),
|
||||
inoperative_error_use_vendor_id(_inoperative_error_use_vendor_id) {
|
||||
}
|
||||
|
||||
void ErrorHandling::raise_overcurrent_error(const std::string& description) {
|
||||
}
|
||||
void ErrorHandling::clear_overcurrent_error() {
|
||||
}
|
||||
|
||||
void ErrorHandling::raise_over_voltage_error(Everest::error::Severity severity, const std::string& description) {
|
||||
}
|
||||
void ErrorHandling::clear_over_voltage_error() {
|
||||
}
|
||||
|
||||
void ErrorHandling::raise_internal_error(const std::string& description) {
|
||||
}
|
||||
void ErrorHandling::clear_internal_error() {
|
||||
}
|
||||
|
||||
void ErrorHandling::raise_authorization_timeout_error(const std::string& description) {
|
||||
}
|
||||
void ErrorHandling::clear_authorization_timeout_error() {
|
||||
}
|
||||
|
||||
void ErrorHandling::raise_powermeter_transaction_start_failed_error(const std::string& description) {
|
||||
}
|
||||
void ErrorHandling::clear_powermeter_transaction_start_failed_error() {
|
||||
}
|
||||
|
||||
void ErrorHandling::raise_isolation_resistance_fault(const std::string& description, const std::string& sub_type) {
|
||||
}
|
||||
void ErrorHandling::clear_isolation_resistance_fault(const std::string& sub_type) {
|
||||
}
|
||||
|
||||
void ErrorHandling::raise_cable_check_fault(const std::string& description) {
|
||||
}
|
||||
void ErrorHandling::clear_cable_check_fault() {
|
||||
}
|
||||
void ErrorHandling::clear_voltage_plausibility_fault() {
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// SessionLog stub
|
||||
SessionLog::SessionLog() {
|
||||
}
|
||||
SessionLog::~SessionLog() {
|
||||
}
|
||||
|
||||
void SessionLog::setPath(const std::string& path) {
|
||||
}
|
||||
void SessionLog::setMqtt(const std::function<void(const nlohmann::json& data)>& mqtt_provider) {
|
||||
}
|
||||
void SessionLog::enable() {
|
||||
}
|
||||
std::optional<std::filesystem::path> SessionLog::startSession(const std::string& suffix_string) {
|
||||
return {};
|
||||
}
|
||||
void SessionLog::stopSession() {
|
||||
}
|
||||
|
||||
void SessionLog::car(bool iso15118, const std::string& msg) {
|
||||
}
|
||||
void SessionLog::car(bool iso15118, const std::string& msg, const std::string& xml, const std::string& xml_hex,
|
||||
const std::string& xml_base64, const std::string& json_str) {
|
||||
}
|
||||
|
||||
void SessionLog::evse(bool iso15118, const std::string& msg) {
|
||||
}
|
||||
void SessionLog::evse(bool iso15118, const std::string& msg, const std::string& xml, const std::string& xml_hex,
|
||||
const std::string& xml_base64, const std::string& json_str) {
|
||||
}
|
||||
|
||||
void SessionLog::xmlOutput(bool e) {
|
||||
}
|
||||
|
||||
void SessionLog::sys(const std::string& msg) {
|
||||
}
|
||||
|
||||
SessionLog session_log;
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// PersistentStore stub
|
||||
PersistentStore::PersistentStore(const std::vector<std::unique_ptr<kvsIntf>>& r_store, const std::string module_id) :
|
||||
r_store(r_store) {
|
||||
}
|
||||
|
||||
void PersistentStore::store_session(const std::string& session_uuid) {
|
||||
}
|
||||
void PersistentStore::clear_session() {
|
||||
}
|
||||
std::string PersistentStore::get_session() {
|
||||
return {};
|
||||
}
|
||||
|
||||
} // namespace module
|
||||
@@ -0,0 +1,352 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include "ErrorHandling.hpp"
|
||||
#include "EvseManagerStub.hpp"
|
||||
#include "evse_board_supportIntfStub.hpp"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <memory>
|
||||
#include <set>
|
||||
|
||||
namespace Everest::error {
|
||||
bool operator<(const Error& lhs, const Error& rhs) {
|
||||
return lhs.type < rhs.type;
|
||||
}
|
||||
bool operator==(const Error& lhs, const Error& rhs) {
|
||||
return (lhs.type == rhs.type) && (lhs.sub_type == rhs.sub_type);
|
||||
}
|
||||
} // namespace Everest::error
|
||||
|
||||
namespace {
|
||||
|
||||
struct ErrorHandlingTest : public module::ErrorHandling {
|
||||
using module::ErrorHandling::ErrorHandling;
|
||||
using module::ErrorHandling::raise_inoperative_error;
|
||||
};
|
||||
|
||||
struct ErrorDatabaseStub : public Everest::error::ErrorDatabase {
|
||||
using Error = Everest::error::Error;
|
||||
using ErrorPtr = Everest::error::ErrorPtr;
|
||||
using ErrorFilter = Everest::error::ErrorFilter;
|
||||
|
||||
std::list<Error>& active_errors;
|
||||
|
||||
ErrorDatabaseStub(std::list<Error>& active) : Everest::error::ErrorDatabase(), active_errors(active) {
|
||||
}
|
||||
|
||||
virtual void add_error(ErrorPtr error) {
|
||||
}
|
||||
virtual std::list<ErrorPtr> get_errors(const std::list<ErrorFilter>& filters) const {
|
||||
std::list<ErrorPtr> result;
|
||||
for (const auto& error : active_errors) {
|
||||
result.push_back(std::make_shared<Error>(error));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
virtual std::list<ErrorPtr> edit_errors(const std::list<ErrorFilter>& filters, EditErrorFunc edit_func) {
|
||||
return {};
|
||||
}
|
||||
virtual std::list<ErrorPtr> remove_errors(const std::list<ErrorFilter>& filters) {
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
struct EvseManagerModuleAdapterStub : public module::stub::EvseManagerModuleAdapter {
|
||||
std::shared_ptr<Everest::error::ErrorTypeMap> error_type_map;
|
||||
std::shared_ptr<ErrorDatabaseStub> error_database;
|
||||
std::list<Everest::error::ErrorType> error_list;
|
||||
std::list<Everest::error::Error> active_errors;
|
||||
|
||||
EvseManagerModuleAdapterStub() :
|
||||
module::stub::EvseManagerModuleAdapter(),
|
||||
error_type_map(std::make_shared<Everest::error::ErrorTypeMap>()),
|
||||
error_database(std::make_shared<ErrorDatabaseStub>(active_errors)),
|
||||
error_list{} {
|
||||
}
|
||||
|
||||
virtual std::shared_ptr<Everest::error::ErrorManagerImpl> get_error_manager_impl_fn(const std::string& str) {
|
||||
return std::make_shared<Everest::error::ErrorManagerImpl>(
|
||||
error_type_map, std::make_shared<Everest::error::ErrorDatabaseMap>(), error_list,
|
||||
[this](const Everest::error::Error& error) {
|
||||
if (error_raise.find(error.type) == error_raise.end()) {
|
||||
throw std::runtime_error("Error type " + error.type + " not found");
|
||||
}
|
||||
error_raise[error.type](error);
|
||||
active_errors.push_back(error);
|
||||
},
|
||||
[this](const Everest::error::Error& error) {
|
||||
if (error_raise.find(error.type) == error_raise.end()) {
|
||||
throw std::runtime_error("Error type " + error.type + " not found");
|
||||
}
|
||||
error_clear[error.type](error);
|
||||
active_errors.remove(error);
|
||||
},
|
||||
false);
|
||||
}
|
||||
|
||||
virtual std::shared_ptr<Everest::error::ErrorManagerReq> get_error_manager_req_fn(const Requirement& req) {
|
||||
return std::make_shared<Everest::error::ErrorManagerReq>(
|
||||
error_type_map, std::make_shared<Everest::error::ErrorDatabaseMap>(), error_list,
|
||||
[this](const Everest::error::ErrorType& error_type, const Everest::error::ErrorCallback& callback,
|
||||
const Everest::error::ErrorCallback& clear_callback) {
|
||||
error_raise[error_type] = callback;
|
||||
error_clear[error_type] = clear_callback;
|
||||
});
|
||||
}
|
||||
|
||||
virtual std::shared_ptr<Everest::error::ErrorFactory> get_error_factory_fn(const std::string&) {
|
||||
return std::make_shared<Everest::error::ErrorFactory>(error_type_map);
|
||||
}
|
||||
|
||||
virtual std::shared_ptr<Everest::error::ErrorStateMonitor> get_error_state_monitor_impl_fn(const std::string&) {
|
||||
return std::make_shared<Everest::error::ErrorStateMonitor>(error_database);
|
||||
}
|
||||
};
|
||||
|
||||
struct ErrorHandlingTesting : public testing::Test {
|
||||
EvseManagerModuleAdapterStub adapter;
|
||||
std::map<Everest::error::ErrorType, std::string> error_types_map;
|
||||
|
||||
std::unique_ptr<evse_board_supportIntf> r_bsp{};
|
||||
const std::vector<std::unique_ptr<ISO15118_chargerIntf>> r_hlc{};
|
||||
const std::vector<std::unique_ptr<connector_lockIntf>> r_connector_lock{};
|
||||
const std::vector<std::unique_ptr<ac_rcdIntf>> r_ac_rcd{};
|
||||
std::unique_ptr<evse_managerImplBase> p_evse{};
|
||||
const std::vector<std::unique_ptr<isolation_monitorIntf>> _r_imd{};
|
||||
const std::vector<std::unique_ptr<power_supply_DCIntf>> _r_powersupply{};
|
||||
const std::vector<std::unique_ptr<powermeterIntf>> _r_powermeter{};
|
||||
const std::vector<std::unique_ptr<over_voltage_monitorIntf>> _r_over_voltage_monitor{};
|
||||
|
||||
std::unique_ptr<ErrorHandlingTest> error_handler;
|
||||
|
||||
void construct(bool _inoperative_error_use_vendor_id) {
|
||||
error_types_map = {{"evse_board_support/VendorWarning", "warning"},
|
||||
{"evse_manager/Inoperative", "inoperative"}};
|
||||
adapter.error_type_map->load_error_types_map(error_types_map);
|
||||
adapter.active_errors.clear();
|
||||
for (const auto& [error, description] : error_types_map) {
|
||||
adapter.error_list.push_back(error);
|
||||
}
|
||||
r_bsp = std::make_unique<module::stub::evse_board_supportIntfStub>(adapter);
|
||||
p_evse = std::make_unique<module::stub::evse_managerImplStub>(&adapter, "manager");
|
||||
error_handler = std::make_unique<ErrorHandlingTest>(r_bsp, r_hlc, r_connector_lock, r_ac_rcd, p_evse, _r_imd,
|
||||
_r_powersupply, _r_powermeter, _r_over_voltage_monitor,
|
||||
_inoperative_error_use_vendor_id);
|
||||
}
|
||||
|
||||
static constexpr std::string_view default_description{"description"};
|
||||
static constexpr std::string_view default_no_vendor_id{"EVerest"};
|
||||
static constexpr std::string_view default_vendor_id{"vendor_id"};
|
||||
|
||||
Everest::error::Error test_description(const std::string& type, const std::string& subtype) {
|
||||
Everest::error::Error error{};
|
||||
error.type = type;
|
||||
error.sub_type = subtype;
|
||||
error.message = "message";
|
||||
error.description = default_description;
|
||||
error.vendor_id = default_vendor_id;
|
||||
error_handler->raise_inoperative_error(error);
|
||||
|
||||
EXPECT_EQ(adapter.active_errors.size(), 1);
|
||||
auto& active = adapter.active_errors.front();
|
||||
EXPECT_EQ(active.type, "evse_manager/Inoperative");
|
||||
EXPECT_EQ(active.sub_type, "");
|
||||
EXPECT_EQ(active.message, error.type);
|
||||
return active;
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(ErrorHandlingTesting, NoType) {
|
||||
construct(true);
|
||||
auto error = test_description("", "sub-type");
|
||||
EXPECT_EQ(error.description, default_description);
|
||||
EXPECT_EQ(error.vendor_id, default_vendor_id);
|
||||
construct(false);
|
||||
error = test_description("", "sub-type");
|
||||
EXPECT_EQ(error.description, default_description);
|
||||
EXPECT_EQ(error.vendor_id, default_no_vendor_id);
|
||||
}
|
||||
|
||||
TEST_F(ErrorHandlingTesting, TypeNoSlashA) {
|
||||
construct(true);
|
||||
auto error = test_description("type", "sub-type");
|
||||
EXPECT_EQ(error.description, default_description);
|
||||
EXPECT_EQ(error.vendor_id, default_vendor_id);
|
||||
construct(false);
|
||||
error = test_description("type", "sub-type");
|
||||
EXPECT_EQ(error.description, default_description);
|
||||
EXPECT_EQ(error.vendor_id, default_no_vendor_id);
|
||||
}
|
||||
|
||||
TEST_F(ErrorHandlingTesting, TypeNoSlashB) {
|
||||
construct(true);
|
||||
auto error = test_description("type", "");
|
||||
EXPECT_EQ(error.description, default_description);
|
||||
EXPECT_EQ(error.vendor_id, default_vendor_id);
|
||||
construct(false);
|
||||
error = test_description("type", "");
|
||||
EXPECT_EQ(error.description, default_description);
|
||||
EXPECT_EQ(error.vendor_id, default_no_vendor_id);
|
||||
}
|
||||
|
||||
TEST_F(ErrorHandlingTesting, TypeSlashEndA) {
|
||||
construct(true);
|
||||
auto error = test_description("type/", "sub-type");
|
||||
EXPECT_EQ(error.description, "type/sub-type");
|
||||
EXPECT_EQ(error.vendor_id, default_vendor_id);
|
||||
construct(false);
|
||||
error = test_description("type/", "sub-type");
|
||||
EXPECT_EQ(error.description, "type/sub-type");
|
||||
EXPECT_EQ(error.vendor_id, default_no_vendor_id);
|
||||
}
|
||||
|
||||
TEST_F(ErrorHandlingTesting, TypeSlashEndB) {
|
||||
construct(true);
|
||||
auto error = test_description("type/", "");
|
||||
EXPECT_EQ(error.description, "type");
|
||||
EXPECT_EQ(error.vendor_id, default_vendor_id);
|
||||
construct(false);
|
||||
error = test_description("type/", "");
|
||||
EXPECT_EQ(error.description, "type");
|
||||
EXPECT_EQ(error.vendor_id, default_no_vendor_id);
|
||||
}
|
||||
|
||||
TEST_F(ErrorHandlingTesting, TypeSlashBeginA) {
|
||||
construct(true);
|
||||
auto error = test_description("/type", "sub-type");
|
||||
EXPECT_EQ(error.description, "type/sub-type");
|
||||
EXPECT_EQ(error.vendor_id, default_vendor_id);
|
||||
construct(false);
|
||||
error = test_description("/type", "sub-type");
|
||||
EXPECT_EQ(error.description, "type/sub-type");
|
||||
EXPECT_EQ(error.vendor_id, default_no_vendor_id);
|
||||
}
|
||||
|
||||
TEST_F(ErrorHandlingTesting, TypeSlashBeginB) {
|
||||
construct(true);
|
||||
auto error = test_description("/type", "");
|
||||
EXPECT_EQ(error.description, "type");
|
||||
EXPECT_EQ(error.vendor_id, default_vendor_id);
|
||||
construct(false);
|
||||
error = test_description("/type", "");
|
||||
EXPECT_EQ(error.description, "type");
|
||||
EXPECT_EQ(error.vendor_id, default_no_vendor_id);
|
||||
}
|
||||
|
||||
TEST_F(ErrorHandlingTesting, TypeFullA) {
|
||||
construct(true);
|
||||
auto error = test_description("module/type", "sub-type");
|
||||
EXPECT_EQ(error.description, "type/sub-type");
|
||||
EXPECT_EQ(error.vendor_id, default_vendor_id);
|
||||
construct(false);
|
||||
error = test_description("module/type", "sub-type");
|
||||
EXPECT_EQ(error.description, "type/sub-type");
|
||||
EXPECT_EQ(error.vendor_id, default_no_vendor_id);
|
||||
}
|
||||
|
||||
TEST_F(ErrorHandlingTesting, TypeFullB) {
|
||||
construct(true);
|
||||
auto error = test_description("module/type", "");
|
||||
EXPECT_EQ(error.description, "type");
|
||||
EXPECT_EQ(error.vendor_id, default_vendor_id);
|
||||
construct(false);
|
||||
error = test_description("module/type", "");
|
||||
EXPECT_EQ(error.description, "type");
|
||||
EXPECT_EQ(error.vendor_id, default_no_vendor_id);
|
||||
}
|
||||
|
||||
TEST_F(ErrorHandlingTesting, TypeFullC) {
|
||||
construct(true);
|
||||
auto error = test_description("module/type/", "sub-type");
|
||||
EXPECT_EQ(error.description, "type/sub-type");
|
||||
EXPECT_EQ(error.vendor_id, default_vendor_id);
|
||||
construct(false);
|
||||
error = test_description("module/type/", "sub-type");
|
||||
EXPECT_EQ(error.description, "type/sub-type");
|
||||
EXPECT_EQ(error.vendor_id, default_no_vendor_id);
|
||||
}
|
||||
|
||||
TEST_F(ErrorHandlingTesting, TypeFullD) {
|
||||
construct(true);
|
||||
auto error = test_description("module/type/", "");
|
||||
EXPECT_EQ(error.description, "type");
|
||||
EXPECT_EQ(error.vendor_id, default_vendor_id);
|
||||
construct(false);
|
||||
error = test_description("module/type/", "");
|
||||
EXPECT_EQ(error.description, "type");
|
||||
EXPECT_EQ(error.vendor_id, default_no_vendor_id);
|
||||
}
|
||||
|
||||
TEST_F(ErrorHandlingTesting, NoVendorId) {
|
||||
Everest::error::Error raised_error{};
|
||||
raised_error.type = "module/type";
|
||||
raised_error.sub_type = "sub-type";
|
||||
raised_error.message = "message";
|
||||
raised_error.description = default_description;
|
||||
raised_error.vendor_id = "";
|
||||
|
||||
construct(true);
|
||||
error_handler->raise_inoperative_error(raised_error);
|
||||
EXPECT_EQ(adapter.active_errors.size(), 1);
|
||||
auto& error = adapter.active_errors.front();
|
||||
EXPECT_EQ(error.type, "evse_manager/Inoperative");
|
||||
EXPECT_EQ(error.sub_type, "");
|
||||
EXPECT_EQ(error.message, raised_error.type);
|
||||
EXPECT_EQ(error.description, "type/sub-type");
|
||||
EXPECT_EQ(error.vendor_id, default_no_vendor_id);
|
||||
|
||||
construct(false);
|
||||
error_handler->raise_inoperative_error(raised_error);
|
||||
EXPECT_EQ(adapter.active_errors.size(), 1);
|
||||
error = adapter.active_errors.front();
|
||||
EXPECT_EQ(error.type, "evse_manager/Inoperative");
|
||||
EXPECT_EQ(error.sub_type, "");
|
||||
EXPECT_EQ(error.message, raised_error.type);
|
||||
EXPECT_EQ(error.description, "type/sub-type");
|
||||
EXPECT_EQ(error.vendor_id, default_no_vendor_id);
|
||||
}
|
||||
|
||||
TEST_F(ErrorHandlingTesting, IsActive) {
|
||||
Everest::error::Error first_error{};
|
||||
first_error.type = "module/type";
|
||||
first_error.sub_type = "sub-type";
|
||||
first_error.message = "message";
|
||||
first_error.description = "description";
|
||||
first_error.vendor_id = "vendor_id";
|
||||
|
||||
construct(true);
|
||||
EXPECT_FALSE(p_evse->error_state_monitor->is_error_active("evse_manager/Inoperative", ""));
|
||||
|
||||
error_handler->raise_inoperative_error(first_error);
|
||||
EXPECT_EQ(adapter.active_errors.size(), 1);
|
||||
auto error = adapter.active_errors.front();
|
||||
EXPECT_EQ(error.type, "evse_manager/Inoperative");
|
||||
EXPECT_EQ(error.sub_type, "");
|
||||
EXPECT_EQ(error.message, first_error.type);
|
||||
EXPECT_EQ(error.description, "type/sub-type");
|
||||
EXPECT_EQ(error.vendor_id, first_error.vendor_id);
|
||||
|
||||
EXPECT_TRUE(p_evse->error_state_monitor->is_error_active("evse_manager/Inoperative", ""));
|
||||
|
||||
Everest::error::Error raised_error{};
|
||||
raised_error.type = "module/new-type";
|
||||
raised_error.sub_type = "new-sub-type";
|
||||
raised_error.message = "new-message";
|
||||
raised_error.description = "new-description";
|
||||
raised_error.vendor_id = "new-vendor_id";
|
||||
|
||||
// should not be a new error
|
||||
error_handler->raise_inoperative_error(raised_error);
|
||||
EXPECT_EQ(adapter.active_errors.size(), 1);
|
||||
error = adapter.active_errors.front();
|
||||
// should be first error
|
||||
EXPECT_EQ(error.type, "evse_manager/Inoperative");
|
||||
EXPECT_EQ(error.sub_type, "");
|
||||
EXPECT_EQ(error.message, first_error.type);
|
||||
EXPECT_EQ(error.description, "type/sub-type");
|
||||
EXPECT_EQ(error.vendor_id, first_error.vendor_id);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -0,0 +1,94 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
#include <EventQueue.hpp>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <condition_variable>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
|
||||
namespace {
|
||||
|
||||
enum class ErrorHandlingEvents : std::uint8_t {
|
||||
PreventCharging,
|
||||
PreventChargingWelded,
|
||||
AllErrorsCleared
|
||||
};
|
||||
|
||||
TEST(EventQueue, init) {
|
||||
module::EventQueue<ErrorHandlingEvents> queue;
|
||||
auto events = queue.get_events();
|
||||
EXPECT_EQ(events.size(), 0);
|
||||
}
|
||||
|
||||
TEST(EventQueue, one) {
|
||||
module::EventQueue<ErrorHandlingEvents> queue;
|
||||
auto events = queue.get_events();
|
||||
EXPECT_EQ(events.size(), 0);
|
||||
|
||||
queue.push(ErrorHandlingEvents::PreventCharging);
|
||||
events = queue.get_events();
|
||||
ASSERT_EQ(events.size(), 1);
|
||||
EXPECT_EQ(events[0], ErrorHandlingEvents::PreventCharging);
|
||||
|
||||
events = queue.get_events();
|
||||
EXPECT_EQ(events.size(), 0);
|
||||
}
|
||||
|
||||
TEST(EventQueue, two) {
|
||||
module::EventQueue<ErrorHandlingEvents> queue;
|
||||
auto events = queue.get_events();
|
||||
EXPECT_EQ(events.size(), 0);
|
||||
|
||||
queue.push(ErrorHandlingEvents::PreventCharging);
|
||||
queue.push(ErrorHandlingEvents::PreventChargingWelded);
|
||||
events = queue.get_events();
|
||||
ASSERT_EQ(events.size(), 2);
|
||||
EXPECT_EQ(events[0], ErrorHandlingEvents::PreventCharging);
|
||||
EXPECT_EQ(events[1], ErrorHandlingEvents::PreventChargingWelded);
|
||||
|
||||
events = queue.get_events();
|
||||
EXPECT_EQ(events.size(), 0);
|
||||
}
|
||||
|
||||
TEST(EventQueue, wait) {
|
||||
module::EventQueue<ErrorHandlingEvents> queue;
|
||||
auto events = queue.get_events();
|
||||
EXPECT_EQ(events.size(), 0);
|
||||
|
||||
std::size_t count = 0;
|
||||
std::condition_variable cv;
|
||||
std::mutex mux;
|
||||
bool ready{false};
|
||||
|
||||
std::thread wait_thread([&cv, &count, &queue, &ready, &mux]() {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mux);
|
||||
ready = true;
|
||||
}
|
||||
cv.notify_one();
|
||||
auto events = queue.wait();
|
||||
count = events.size();
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mux);
|
||||
ready = false;
|
||||
}
|
||||
cv.notify_one();
|
||||
});
|
||||
|
||||
std::unique_lock<std::mutex> ul(mux);
|
||||
cv.wait(ul, [&ready] { return ready; });
|
||||
ASSERT_EQ(count, 0U);
|
||||
|
||||
queue.push(ErrorHandlingEvents::PreventCharging);
|
||||
|
||||
cv.wait(ul, [&ready] { return !ready; });
|
||||
ASSERT_EQ(count, 1U);
|
||||
|
||||
events = queue.get_events();
|
||||
EXPECT_EQ(events.size(), 0);
|
||||
|
||||
wait_thread.join();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -0,0 +1,117 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#ifndef EVSEMANAGERSTUB_H_
|
||||
#define EVSEMANAGERSTUB_H_
|
||||
|
||||
#include <ErrorHandling.hpp>
|
||||
#include <ModuleAdapterStub.hpp>
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
namespace module::stub {
|
||||
|
||||
struct evse_managerImplStub : public evse_managerImplBase {
|
||||
evse_managerImplStub(Everest::ModuleAdapter* ev, const std::string& name) : evse_managerImplBase(ev, name) {
|
||||
}
|
||||
evse_managerImplStub() : evse_managerImplBase(nullptr, "manager") {
|
||||
}
|
||||
virtual void init() {
|
||||
}
|
||||
virtual void ready() {
|
||||
}
|
||||
virtual types::evse_manager::Evse handle_get_evse() {
|
||||
return types::evse_manager::Evse();
|
||||
}
|
||||
virtual bool handle_enable(int& connector_id) {
|
||||
return true;
|
||||
}
|
||||
virtual bool handle_disable(int& connector_id) {
|
||||
return true;
|
||||
}
|
||||
virtual bool handle_enable_disable(int& connector_id, types::evse_manager::EnableDisableSource& cmd_source) {
|
||||
return true;
|
||||
}
|
||||
virtual void handle_authorize_response(types::authorization::ProvidedIdToken& provided_token,
|
||||
types::authorization::ValidationResult& validation_result) {
|
||||
}
|
||||
virtual void handle_withdraw_authorization() {
|
||||
}
|
||||
virtual bool handle_reserve(int& reservation_id) {
|
||||
return true;
|
||||
}
|
||||
virtual void handle_cancel_reservation() {
|
||||
}
|
||||
virtual bool handle_pause_charging() {
|
||||
return true;
|
||||
}
|
||||
virtual bool handle_resume_charging() {
|
||||
return true;
|
||||
}
|
||||
virtual bool handle_stop_transaction(types::evse_manager::StopTransactionRequest& request) {
|
||||
return true;
|
||||
}
|
||||
virtual bool handle_force_unlock(int& connector_id) {
|
||||
return true;
|
||||
}
|
||||
virtual void handle_set_external_limits(types::energy::ExternalLimits& value) {
|
||||
}
|
||||
virtual types::evse_manager::SwitchThreePhasesWhileChargingResult
|
||||
handle_switch_three_phases_while_charging(bool& three_phases) {
|
||||
return types::evse_manager::SwitchThreePhasesWhileChargingResult::Success;
|
||||
}
|
||||
virtual bool handle_external_ready_to_start_charging() {
|
||||
return true;
|
||||
}
|
||||
virtual void handle_set_plug_and_charge_configuration(
|
||||
types::evse_manager::PlugAndChargeConfiguration& plug_and_charge_configuration) {
|
||||
}
|
||||
virtual types::evse_manager::UpdateAllowedEnergyTransferModesResult handle_update_allowed_energy_transfer_modes(
|
||||
std::vector<types::iso15118::EnergyTransferMode>& allowed_energy_transfer_modes) {
|
||||
return types::evse_manager::UpdateAllowedEnergyTransferModesResult::Accepted;
|
||||
}
|
||||
};
|
||||
|
||||
struct EvseManagerModuleAdapter : public ModuleAdapterStub {
|
||||
EvseManagerModuleAdapter() : id("evse_manager", "main") {
|
||||
}
|
||||
|
||||
ImplementationIdentifier id;
|
||||
std::map<std::string, Everest::error::ErrorCallback> error_raise;
|
||||
std::map<std::string, Everest::error::ErrorCallback> error_clear;
|
||||
|
||||
virtual std::shared_ptr<Everest::error::ErrorManagerReq> get_error_manager_req_fn(const Requirement& req) {
|
||||
return std::make_shared<Everest::error::ErrorManagerReq>(
|
||||
std::make_shared<Everest::error::ErrorTypeMap>(), std::make_shared<Everest::error::ErrorDatabaseMap>(),
|
||||
std::list<Everest::error::ErrorType>({Everest::error::ErrorType("evse_board_support/VendorWarning")}),
|
||||
[this](const Everest::error::ErrorType& error_type, const Everest::error::ErrorCallback& callback,
|
||||
const Everest::error::ErrorCallback& clear_callback) {
|
||||
error_raise[error_type] = callback;
|
||||
error_clear[error_type] = clear_callback;
|
||||
});
|
||||
}
|
||||
|
||||
virtual std::shared_ptr<Everest::error::ErrorManagerImpl> get_error_manager_impl_fn(const std::string& str) {
|
||||
return std::make_shared<Everest::error::ErrorManagerImpl>(
|
||||
std::make_shared<Everest::error::ErrorTypeMap>(), std::make_shared<Everest::error::ErrorDatabaseMap>(),
|
||||
std::list<Everest::error::ErrorType>(),
|
||||
[this](const Everest::error::Error& error) {
|
||||
std::printf("publish_raised_error\n");
|
||||
if (error_raise.find(error.type) == error_raise.end()) {
|
||||
throw std::runtime_error("Error type " + error.type + " not found");
|
||||
}
|
||||
error_raise[error.type](error);
|
||||
},
|
||||
[this](const Everest::error::Error& error) {
|
||||
std::printf("publish_cleared_error\n");
|
||||
if (error_raise.find(error.type) == error_raise.end()) {
|
||||
throw std::runtime_error("Error type " + error.type + " not found");
|
||||
}
|
||||
error_clear[error.type](error);
|
||||
},
|
||||
false);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace module::stub
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,337 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
#include "evse_board_supportIntfStub.hpp"
|
||||
#include <EventQueue.hpp>
|
||||
#include <IECStateMachine.hpp>
|
||||
#include <backtrace.hpp>
|
||||
#include <chrono>
|
||||
#include <gtest/gtest.h>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
namespace {
|
||||
|
||||
using BspEvent = types::board_support_common::BspEvent;
|
||||
using Event = types::board_support_common::Event;
|
||||
using Reason = types::evse_board_support::Reason;
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
struct BspStub : public module::stub::ModuleAdapterStub {
|
||||
typedef Result (BspStub::*bsp_fn)(Parameters p);
|
||||
ValueCallback event_cb;
|
||||
std::map<std::string, bsp_fn> _bsp;
|
||||
|
||||
BspStub() : module::stub::ModuleAdapterStub() {
|
||||
_bsp["allow_power_on"] = &BspStub::call_allow_power_on;
|
||||
_bsp["enable"] = &BspStub::call_enable;
|
||||
_bsp["cp_state_X1"] = &BspStub::call_cp_state_X1;
|
||||
_bsp["pwm_on"] = &BspStub::call_pwm_on;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// test interface (calls from BSP)
|
||||
|
||||
void raise_event(Event event) {
|
||||
if (event_cb != nullptr) {
|
||||
BspEvent bsp_event;
|
||||
bsp_event.event = event;
|
||||
event_cb(bsp_event);
|
||||
}
|
||||
};
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// BSP calls
|
||||
|
||||
virtual Result call_allow_power_on(Parameters p) {
|
||||
std::cout << "call_allow_power_on(" << p << ")" << std::endl;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
virtual Result call_enable(Parameters p) {
|
||||
std::cout << "call_enable(" << p << ")" << std::endl;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
virtual Result call_cp_state_X1(Parameters p) {
|
||||
std::cout << "call_cp_state_X1(" << p << ")" << std::endl;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
virtual Result call_pwm_on(Parameters p) {
|
||||
std::cout << "call_pwm_on(" << p << ")" << std::endl;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// internal interface
|
||||
virtual void subscribe_fn(const Requirement&, const std::string& fn, ValueCallback cb) override {
|
||||
std::printf("subscribe_fn(%s)\n", fn.c_str());
|
||||
event_cb = cb;
|
||||
}
|
||||
|
||||
virtual Result call_fn(const Requirement& req, const std::string& fn, Parameters p) override {
|
||||
if (auto itt = _bsp.find(fn); itt == _bsp.end()) {
|
||||
std::cout << "<missing> call_fn(" << fn << "," << p << ")" << std::endl;
|
||||
return std::nullopt;
|
||||
} else {
|
||||
return std::invoke(itt->second, this, p);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
TEST(IECStateMachine, init) {
|
||||
module::stub::ModuleAdapterStub module_adapter = module::stub::ModuleAdapterStub();
|
||||
std::unique_ptr<evse_board_supportIntf> bsp_if =
|
||||
std::make_unique<module::stub::evse_board_supportIntfStub>(module_adapter);
|
||||
module::IECStateMachine state_machine(std::move(bsp_if), true);
|
||||
}
|
||||
|
||||
#if 0
|
||||
// test to demonstrate the output from backtrace
|
||||
|
||||
struct BspStubTimeout : public BspStub {
|
||||
Result call_allow_power_on(Parameters p) {
|
||||
std::cout << "call_allow_power_on(" << p << ")" << std::endl;
|
||||
std::cout << "call_allow_power_on: sleep (" << std::this_thread::get_id() << ")" << std::endl;
|
||||
std::this_thread::sleep_for(200s);
|
||||
std::cout << "call_allow_power_on: finished" << std::endl;
|
||||
return std::nullopt;
|
||||
}
|
||||
};
|
||||
|
||||
TEST(IECStateMachine, init_subscribe) {
|
||||
#ifdef EVEREST_USE_BACKTRACES
|
||||
Everest::install_backtrace_handler();
|
||||
#endif
|
||||
|
||||
BspStubTimeout bsp;
|
||||
std::unique_ptr<evse_board_supportIntf> bsp_if = std::make_unique<module::stub::evse_board_supportIntfStub>(bsp);
|
||||
module::IECStateMachine state_machine(std::move(bsp_if), true);
|
||||
|
||||
state_machine.enable(true);
|
||||
state_machine.allow_power_on(true, Reason::FullPowerCharging);
|
||||
bsp.raise_event(Event::A);
|
||||
bsp.raise_event(Event::B);
|
||||
bsp.raise_event(Event::PowerOn);
|
||||
bsp.raise_event(Event::C);
|
||||
std::cout << "main: sleep (" << std::this_thread::get_id() << ")" << std::endl;
|
||||
std::this_thread::sleep_for(300s);
|
||||
}
|
||||
#endif
|
||||
|
||||
struct BspStubDeadlock : public BspStub {
|
||||
std::vector<std::chrono::time_point<std::chrono::steady_clock>> call_cp_state_X1_times;
|
||||
std::vector<std::chrono::time_point<std::chrono::steady_clock>> call_allow_power_on_times;
|
||||
int count = 0;
|
||||
enum class events_t : std::uint8_t {
|
||||
call_allow_power_on,
|
||||
call_enable,
|
||||
call_cp_state_X1,
|
||||
call_pwm_on,
|
||||
signal_lock,
|
||||
sleeping,
|
||||
};
|
||||
module::EventQueue<events_t> events;
|
||||
|
||||
std::string to_string(module::EventQueue<events_t>::events_t e) {
|
||||
std::string result;
|
||||
for (const auto& i : e) {
|
||||
result += std::to_string(static_cast<std::uint8_t>(i));
|
||||
result += " ";
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool contains(module::EventQueue<events_t>::events_t e, events_t event) {
|
||||
for (const auto& i : e) {
|
||||
if (i == event) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
auto wait() {
|
||||
return events.wait();
|
||||
}
|
||||
|
||||
virtual Result call_allow_power_on(Parameters p) {
|
||||
std::cout << "call_allow_power_on(" << p << ")" << std::endl;
|
||||
call_allow_power_on_times.push_back(std::chrono::steady_clock::now());
|
||||
events.push(events_t::call_allow_power_on);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
virtual Result call_enable(Parameters p) {
|
||||
std::cout << "call_enable(" << p << ")" << std::endl;
|
||||
events.push(events_t::call_enable);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
virtual Result call_cp_state_X1(Parameters p) {
|
||||
std::cout << "call_cp_state_X1(" << p << ")" << std::endl;
|
||||
call_cp_state_X1_times.push_back(std::chrono::steady_clock::now());
|
||||
if (count == 2) {
|
||||
std::cout << "sleeping ..." << std::endl;
|
||||
events.push(events_t::sleeping);
|
||||
std::this_thread::sleep_for(7s);
|
||||
}
|
||||
count++;
|
||||
events.push(events_t::call_cp_state_X1);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
virtual Result call_pwm_on(Parameters p) {
|
||||
std::cout << "call_pwm_on(" << p << ")" << std::endl;
|
||||
events.push(events_t::call_pwm_on);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
auto elapsed() const {
|
||||
auto last_call_cp_state_X1_time = call_cp_state_X1_times.back();
|
||||
auto last_call_allow_power_on_time = call_allow_power_on_times.back();
|
||||
return std::chrono::duration_cast<std::chrono::milliseconds>(last_call_allow_power_on_time -
|
||||
last_call_cp_state_X1_time)
|
||||
.count();
|
||||
}
|
||||
};
|
||||
|
||||
TEST(IECStateMachine, deadlock_test) {
|
||||
GTEST_SKIP() << "relies on thread timing so occasionally fails";
|
||||
|
||||
/*
|
||||
* a deadlock was caused by timeout_state_c1 timing out and
|
||||
* trying to raise an event while state_machine() was running
|
||||
* and wanting to stop the timer
|
||||
* 61851-1 state C starts the timer (from x2)
|
||||
*
|
||||
* check the timer runs for 6 seconds
|
||||
*/
|
||||
#ifdef EVEREST_USE_BACKTRACES
|
||||
Everest::install_backtrace_handler();
|
||||
#endif
|
||||
|
||||
BspStubDeadlock bsp;
|
||||
std::unique_ptr<evse_board_supportIntf> bsp_if = std::make_unique<module::stub::evse_board_supportIntfStub>(bsp);
|
||||
module::IECStateMachine state_machine(std::move(bsp_if), true);
|
||||
|
||||
std::uint8_t signal_lock_count = 0;
|
||||
|
||||
state_machine.signal_lock.connect([&signal_lock_count, &bsp]() {
|
||||
signal_lock_count++;
|
||||
std::cout << "signal_lock" << std::endl;
|
||||
// bsp.events.push(BspStubDeadlock::events_t::signal_lock);
|
||||
});
|
||||
|
||||
state_machine.enable(true);
|
||||
auto actions = bsp.wait();
|
||||
ASSERT_EQ(actions.size(), 1);
|
||||
ASSERT_EQ(actions[0], BspStubDeadlock::events_t::call_enable);
|
||||
|
||||
bsp.raise_event(Event::A);
|
||||
actions = bsp.wait();
|
||||
ASSERT_TRUE(bsp.contains(actions, BspStubDeadlock::events_t::call_cp_state_X1));
|
||||
|
||||
bsp.raise_event(Event::B);
|
||||
actions = bsp.wait();
|
||||
// std::cout << bsp.to_string(actions) << std::endl;
|
||||
// call_allow_power_on and/or signal_lock
|
||||
// ASSERT_TRUE(bsp.contains(actions, BspStubDeadlock::events_t::call_allow_power_on));
|
||||
|
||||
state_machine.set_pwm(0.5);
|
||||
actions = bsp.wait();
|
||||
// std::cout << bsp.to_string(actions) << std::endl;
|
||||
ASSERT_TRUE(bsp.contains(actions, BspStubDeadlock::events_t::call_pwm_on));
|
||||
|
||||
state_machine.allow_power_on(true, types::evse_board_support::Reason::FullPowerCharging);
|
||||
std::this_thread::sleep_for(1s);
|
||||
// actions = bsp.wait();
|
||||
// ASSERT_TRUE(bsp.contains(actions, BspStubDeadlock::events_t::signal_lock));
|
||||
|
||||
bsp.raise_event(Event::C);
|
||||
actions = bsp.wait();
|
||||
// std::cout << bsp.to_string(actions) << std::endl;
|
||||
ASSERT_TRUE(bsp.contains(actions, BspStubDeadlock::events_t::call_allow_power_on));
|
||||
|
||||
state_machine.set_cp_state_X1();
|
||||
// expect state_machine to run once and then again when the
|
||||
// timer expires
|
||||
|
||||
std::this_thread::sleep_for(8s);
|
||||
auto elapsed = bsp.elapsed();
|
||||
std::cout << "Elapsed: " << elapsed << std::endl;
|
||||
EXPECT_GT(elapsed, 6000);
|
||||
EXPECT_LT(elapsed, 6500);
|
||||
}
|
||||
|
||||
TEST(IECStateMachine, deadlock_fix) {
|
||||
GTEST_SKIP() << "relies on thread timing so occasionally fails";
|
||||
|
||||
/*
|
||||
* a deadlock was caused by timeout_state_c1 timing out and
|
||||
* trying to raise an event while state_machine() was running
|
||||
* and wanting to stop the timer
|
||||
* 61851-1 state C starts the timer (from X2)
|
||||
*/
|
||||
#ifdef EVEREST_USE_BACKTRACES
|
||||
Everest::install_backtrace_handler();
|
||||
#endif
|
||||
|
||||
BspStubDeadlock bsp;
|
||||
std::unique_ptr<evse_board_supportIntf> bsp_if = std::make_unique<module::stub::evse_board_supportIntfStub>(bsp);
|
||||
module::IECStateMachine state_machine(std::move(bsp_if), true);
|
||||
|
||||
std::uint8_t signal_lock_count = 0;
|
||||
|
||||
state_machine.signal_lock.connect([&signal_lock_count, &bsp]() {
|
||||
signal_lock_count++;
|
||||
std::cout << "signal_lock" << std::endl;
|
||||
// bsp.events.push(BspStubDeadlock::events_t::signal_lock);
|
||||
});
|
||||
|
||||
state_machine.enable(true);
|
||||
auto actions = bsp.wait();
|
||||
ASSERT_EQ(actions.size(), 1);
|
||||
ASSERT_EQ(actions[0], BspStubDeadlock::events_t::call_enable);
|
||||
|
||||
bsp.raise_event(Event::A);
|
||||
actions = bsp.wait();
|
||||
// std::cout << bsp.to_string(actions) << std::endl;
|
||||
ASSERT_TRUE(bsp.contains(actions, BspStubDeadlock::events_t::call_cp_state_X1));
|
||||
|
||||
bsp.raise_event(Event::B);
|
||||
actions = bsp.wait();
|
||||
// call_allow_power_on and/or signal_lock
|
||||
// ASSERT_TRUE(bsp.contains(actions, BspStubDeadlock::events_t::call_allow_power_on));
|
||||
|
||||
state_machine.set_pwm(0.5);
|
||||
actions = bsp.wait();
|
||||
ASSERT_TRUE(bsp.contains(actions, BspStubDeadlock::events_t::call_pwm_on));
|
||||
|
||||
state_machine.allow_power_on(true, types::evse_board_support::Reason::FullPowerCharging);
|
||||
std::this_thread::sleep_for(1s);
|
||||
|
||||
bsp.raise_event(Event::C);
|
||||
actions = bsp.wait();
|
||||
ASSERT_TRUE(bsp.contains(actions, BspStubDeadlock::events_t::call_allow_power_on));
|
||||
|
||||
state_machine.set_cp_state_X1();
|
||||
// expect state_machine to run once and then again when the
|
||||
// timer expires
|
||||
|
||||
actions = bsp.wait();
|
||||
ASSERT_TRUE(bsp.contains(actions, BspStubDeadlock::events_t::call_cp_state_X1));
|
||||
|
||||
// this would previously trigger the deadlock
|
||||
bsp.raise_event(Event::A);
|
||||
actions = bsp.wait();
|
||||
// std::cout << bsp.to_string(actions) << std::endl;
|
||||
ASSERT_TRUE(bsp.contains(actions, BspStubDeadlock::events_t::sleeping));
|
||||
|
||||
std::this_thread::sleep_for(10s);
|
||||
// if there is a deadlock the test won't finish
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -0,0 +1,190 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "over_voltage/OverVoltageMonitor.hpp"
|
||||
|
||||
#include <chrono>
|
||||
#include <condition_variable>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
|
||||
using namespace module;
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
class OverVoltageMonitorTest : public ::testing::Test {
|
||||
protected:
|
||||
struct CallbackState {
|
||||
std::mutex mtx;
|
||||
std::condition_variable cv;
|
||||
bool called{false};
|
||||
OverVoltageMonitor::FaultType type;
|
||||
std::string reason;
|
||||
};
|
||||
|
||||
static OverVoltageMonitor make_monitor(CallbackState& state, std::chrono::milliseconds duration) {
|
||||
return OverVoltageMonitor(
|
||||
[&state](OverVoltageMonitor::FaultType type, const std::string& reason) {
|
||||
std::lock_guard<std::mutex> lock(state.mtx);
|
||||
state.called = true;
|
||||
state.type = type;
|
||||
state.reason = reason;
|
||||
state.cv.notify_all();
|
||||
},
|
||||
duration);
|
||||
}
|
||||
|
||||
static bool wait_for_callback(CallbackState& state, std::chrono::milliseconds timeout = 500ms) {
|
||||
std::unique_lock<std::mutex> lock(state.mtx);
|
||||
return state.cv.wait_for(lock, timeout, [&state] { return state.called; });
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(OverVoltageMonitorTest, no_fault_below_limits) {
|
||||
CallbackState state;
|
||||
auto monitor = make_monitor(state, 100ms);
|
||||
|
||||
monitor.set_limits(450.0, 420.0);
|
||||
monitor.start_monitor();
|
||||
|
||||
monitor.update_voltage(400.0);
|
||||
monitor.update_voltage(410.0);
|
||||
|
||||
EXPECT_FALSE(wait_for_callback(state));
|
||||
}
|
||||
|
||||
TEST_F(OverVoltageMonitorTest, emergency_fault_triggers_immediately) {
|
||||
CallbackState state;
|
||||
auto monitor = make_monitor(state, 200ms);
|
||||
|
||||
monitor.set_limits(450.0, 420.0);
|
||||
monitor.start_monitor();
|
||||
|
||||
monitor.update_voltage(460.0);
|
||||
|
||||
ASSERT_TRUE(wait_for_callback(state));
|
||||
EXPECT_EQ(state.type, OverVoltageMonitor::FaultType::Emergency);
|
||||
}
|
||||
|
||||
TEST_F(OverVoltageMonitorTest, error_fault_triggers_after_duration) {
|
||||
CallbackState state;
|
||||
auto monitor = make_monitor(state, 100ms);
|
||||
|
||||
monitor.set_limits(450.0, 420.0);
|
||||
monitor.start_monitor();
|
||||
|
||||
monitor.update_voltage(430.0);
|
||||
|
||||
ASSERT_TRUE(wait_for_callback(state, 300ms));
|
||||
EXPECT_EQ(state.type, OverVoltageMonitor::FaultType::Error);
|
||||
}
|
||||
|
||||
TEST_F(OverVoltageMonitorTest, voltage_drop_cancels_error_timer) {
|
||||
CallbackState state;
|
||||
auto monitor = make_monitor(state, 150ms);
|
||||
|
||||
monitor.set_limits(450.0, 420.0);
|
||||
monitor.start_monitor();
|
||||
|
||||
monitor.update_voltage(430.0); // above error limit
|
||||
std::this_thread::sleep_for(50ms);
|
||||
monitor.update_voltage(410.0); // below error limit
|
||||
|
||||
EXPECT_FALSE(wait_for_callback(state, 300ms));
|
||||
}
|
||||
|
||||
TEST_F(OverVoltageMonitorTest, zero_duration_triggers_immediately) {
|
||||
CallbackState state;
|
||||
auto monitor = make_monitor(state, 0ms);
|
||||
|
||||
monitor.set_limits(450.0, 420.0);
|
||||
monitor.start_monitor();
|
||||
|
||||
monitor.update_voltage(425.0);
|
||||
|
||||
ASSERT_TRUE(wait_for_callback(state));
|
||||
EXPECT_EQ(state.type, OverVoltageMonitor::FaultType::Error);
|
||||
}
|
||||
|
||||
TEST_F(OverVoltageMonitorTest, fault_is_latched) {
|
||||
CallbackState state;
|
||||
auto monitor = make_monitor(state, 50ms);
|
||||
|
||||
monitor.set_limits(450.0, 420.0);
|
||||
monitor.start_monitor();
|
||||
|
||||
monitor.update_voltage(430.0);
|
||||
ASSERT_TRUE(wait_for_callback(state));
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(state.mtx);
|
||||
state.called = false;
|
||||
}
|
||||
|
||||
// Further voltage updates should not retrigger
|
||||
monitor.update_voltage(460.0);
|
||||
EXPECT_FALSE(wait_for_callback(state, 200ms));
|
||||
}
|
||||
|
||||
TEST_F(OverVoltageMonitorTest, stop_monitor_suppresses_fault) {
|
||||
CallbackState state;
|
||||
auto monitor = make_monitor(state, 100ms);
|
||||
|
||||
monitor.set_limits(450.0, 420.0);
|
||||
monitor.start_monitor();
|
||||
|
||||
monitor.update_voltage(430.0);
|
||||
monitor.stop_monitor();
|
||||
|
||||
EXPECT_FALSE(wait_for_callback(state, 300ms));
|
||||
}
|
||||
|
||||
TEST_F(OverVoltageMonitorTest, reset_clears_latched_fault_and_allows_retrigger) {
|
||||
CallbackState state;
|
||||
auto monitor = make_monitor(state, 50ms);
|
||||
|
||||
monitor.set_limits(450.0, 420.0);
|
||||
monitor.start_monitor();
|
||||
|
||||
// First fault
|
||||
monitor.update_voltage(430.0);
|
||||
ASSERT_TRUE(wait_for_callback(state, 300ms));
|
||||
EXPECT_EQ(state.type, OverVoltageMonitor::FaultType::Error);
|
||||
|
||||
// Clear callback state
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(state.mtx);
|
||||
state.called = false;
|
||||
}
|
||||
|
||||
// Reset should clear latch and timers
|
||||
monitor.reset();
|
||||
|
||||
// Must restart monitoring explicitly
|
||||
monitor.start_monitor();
|
||||
|
||||
// Second fault should be possible again
|
||||
monitor.update_voltage(430.0);
|
||||
ASSERT_TRUE(wait_for_callback(state, 300ms));
|
||||
EXPECT_EQ(state.type, OverVoltageMonitor::FaultType::Error);
|
||||
}
|
||||
|
||||
TEST_F(OverVoltageMonitorTest, multiple_voltage_updates_update_timer_snapshot) {
|
||||
CallbackState state;
|
||||
auto monitor = make_monitor(state, 100ms);
|
||||
|
||||
monitor.set_limits(500.0, 420.0);
|
||||
monitor.start_monitor();
|
||||
|
||||
// First update arms the timer
|
||||
monitor.update_voltage(430.0);
|
||||
|
||||
// Subsequent updates while timer is active
|
||||
monitor.update_voltage(435.0);
|
||||
monitor.update_voltage(432.0);
|
||||
monitor.update_voltage(440.0); // highest value
|
||||
|
||||
ASSERT_TRUE(wait_for_callback(state, 300ms));
|
||||
EXPECT_EQ(state.type, OverVoltageMonitor::FaultType::Error);
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "voltage_plausibility/VoltagePlausibilityMonitor.hpp"
|
||||
|
||||
#include <chrono>
|
||||
#include <condition_variable>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
using namespace module;
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
class VoltagePlausibilityMonitorTest : public ::testing::Test {
|
||||
protected:
|
||||
struct CallbackState {
|
||||
std::mutex mtx;
|
||||
std::condition_variable cv;
|
||||
bool called{false};
|
||||
std::string reason;
|
||||
};
|
||||
|
||||
static VoltagePlausibilityMonitor make_monitor(CallbackState& state, double max_spread_threshold_V,
|
||||
std::chrono::milliseconds fault_duration) {
|
||||
return VoltagePlausibilityMonitor(
|
||||
[&state](const std::string& reason) {
|
||||
std::lock_guard<std::mutex> lock(state.mtx);
|
||||
state.called = true;
|
||||
state.reason = reason;
|
||||
state.cv.notify_all();
|
||||
},
|
||||
max_spread_threshold_V, fault_duration);
|
||||
}
|
||||
|
||||
static bool wait_for_callback(CallbackState& state, std::chrono::milliseconds timeout = 500ms) {
|
||||
std::unique_lock<std::mutex> lock(state.mtx);
|
||||
return state.cv.wait_for(lock, timeout, [&state] { return state.called; });
|
||||
}
|
||||
|
||||
static void clear_callback_state(CallbackState& state) {
|
||||
std::lock_guard<std::mutex> lock(state.mtx);
|
||||
state.called = false;
|
||||
state.reason.clear();
|
||||
}
|
||||
|
||||
static void set_two_sources(VoltagePlausibilityMonitor& monitor, double power_supply_V, double powermeter_V) {
|
||||
monitor.update_power_supply_voltage(power_supply_V);
|
||||
monitor.update_powermeter_voltage(powermeter_V);
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(VoltagePlausibilityMonitorTest, no_fault_below_threshold) {
|
||||
CallbackState state;
|
||||
auto monitor = make_monitor(state, /*max_spread_threshold_V=*/10.0, /*fault_duration=*/50ms);
|
||||
|
||||
monitor.start_monitor();
|
||||
|
||||
set_two_sources(monitor, 400.0, 405.0); // spread 5 V <= 10 V
|
||||
|
||||
EXPECT_FALSE(wait_for_callback(state, 200ms));
|
||||
|
||||
monitor.stop_monitor();
|
||||
}
|
||||
|
||||
TEST_F(VoltagePlausibilityMonitorTest, requires_at_least_two_sources) {
|
||||
CallbackState state;
|
||||
auto monitor = make_monitor(state, /*max_spread_threshold_V=*/1.0, /*fault_duration=*/50ms);
|
||||
|
||||
monitor.start_monitor();
|
||||
|
||||
monitor.update_power_supply_voltage(400.0); // only one source available
|
||||
|
||||
EXPECT_FALSE(wait_for_callback(state, 200ms));
|
||||
|
||||
monitor.stop_monitor();
|
||||
}
|
||||
|
||||
TEST_F(VoltagePlausibilityMonitorTest, zero_duration_triggers_immediately) {
|
||||
CallbackState state;
|
||||
auto monitor = make_monitor(state, /*max_spread_threshold_V=*/5.0, /*fault_duration=*/0ms);
|
||||
|
||||
monitor.start_monitor();
|
||||
|
||||
set_two_sources(monitor, 400.0, 420.0); // spread 20 V > 5 V
|
||||
|
||||
ASSERT_TRUE(wait_for_callback(state, 200ms));
|
||||
EXPECT_NE(state.reason.find("fault immediately"), std::string::npos);
|
||||
}
|
||||
|
||||
TEST_F(VoltagePlausibilityMonitorTest, spread_above_threshold_triggers_after_duration) {
|
||||
CallbackState state;
|
||||
auto monitor = make_monitor(state, /*max_spread_threshold_V=*/5.0, /*fault_duration=*/50ms);
|
||||
|
||||
monitor.start_monitor();
|
||||
|
||||
set_two_sources(monitor, 400.0, 420.0); // arms timer
|
||||
|
||||
ASSERT_TRUE(wait_for_callback(state, 300ms));
|
||||
EXPECT_NE(state.reason.find("for at least"), std::string::npos);
|
||||
EXPECT_NE(state.reason.find("exceeded threshold"), std::string::npos);
|
||||
}
|
||||
|
||||
TEST_F(VoltagePlausibilityMonitorTest, returning_within_threshold_cancels_timer) {
|
||||
CallbackState state;
|
||||
auto monitor = make_monitor(state, /*max_spread_threshold_V=*/5.0, /*fault_duration=*/120ms);
|
||||
|
||||
monitor.start_monitor();
|
||||
|
||||
set_two_sources(monitor, 400.0, 420.0); // spread 20 V -> arm timer
|
||||
|
||||
std::this_thread::sleep_for(40ms);
|
||||
|
||||
// Bring spread back within the threshold before the timer expires
|
||||
monitor.update_powermeter_voltage(402.0); // spread now 2 V -> cancel timer
|
||||
|
||||
EXPECT_FALSE(wait_for_callback(state, 250ms));
|
||||
}
|
||||
|
||||
TEST_F(VoltagePlausibilityMonitorTest, fault_is_latched_and_not_retriggered) {
|
||||
CallbackState state;
|
||||
auto monitor = make_monitor(state, /*max_spread_threshold_V=*/1.0, /*fault_duration=*/0ms);
|
||||
|
||||
monitor.start_monitor();
|
||||
|
||||
set_two_sources(monitor, 400.0, 405.0); // immediate fault
|
||||
|
||||
ASSERT_TRUE(wait_for_callback(state, 200ms));
|
||||
|
||||
clear_callback_state(state);
|
||||
|
||||
// After a fault the monitor stops and latches the fault; further updates must not retrigger.
|
||||
set_two_sources(monitor, 410.0, 430.0);
|
||||
EXPECT_FALSE(wait_for_callback(state, 150ms));
|
||||
}
|
||||
|
||||
TEST_F(VoltagePlausibilityMonitorTest, stop_monitor_suppresses_fault) {
|
||||
CallbackState state;
|
||||
auto monitor = make_monitor(state, /*max_spread_threshold_V=*/5.0, /*fault_duration=*/80ms);
|
||||
|
||||
monitor.start_monitor();
|
||||
|
||||
set_two_sources(monitor, 400.0, 420.0); // arm timer
|
||||
monitor.stop_monitor(); // cancels timer
|
||||
|
||||
EXPECT_FALSE(wait_for_callback(state, 250ms));
|
||||
}
|
||||
|
||||
TEST_F(VoltagePlausibilityMonitorTest, reset_clears_samples_so_old_values_are_not_used) {
|
||||
CallbackState state;
|
||||
auto monitor = make_monitor(state, /*max_spread_threshold_V=*/5.0, /*fault_duration=*/80ms);
|
||||
|
||||
monitor.start_monitor();
|
||||
|
||||
// Prime both sources with a large spread, then stop and reset.
|
||||
set_two_sources(monitor, 400.0, 420.0);
|
||||
monitor.stop_monitor();
|
||||
monitor.reset();
|
||||
|
||||
// Restart monitoring. Provide only a single new source sample.
|
||||
// If reset() didn't clear the old powermeter sample timestamp, we'd still have 2 sources and might arm/fault.
|
||||
monitor.start_monitor();
|
||||
monitor.update_power_supply_voltage(400.0);
|
||||
|
||||
EXPECT_FALSE(wait_for_callback(state, 250ms));
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#ifndef EVSE_BOARD_SUPPORTINTFSTUB_H_
|
||||
#define EVSE_BOARD_SUPPORTINTFSTUB_H_
|
||||
|
||||
#include "ModuleAdapterStub.hpp"
|
||||
#include <generated/interfaces/evse_board_support/Interface.hpp>
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
namespace module::stub {
|
||||
|
||||
struct evse_board_supportIntfStub : public evse_board_supportIntf {
|
||||
explicit evse_board_supportIntfStub(ModuleAdapterStub& adapter) :
|
||||
evse_board_supportIntf(&adapter, Requirement{"requirement", 1}, "EvseManager", std::nullopt) {
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace module::stub
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,16 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include "auth_token_providerImpl.hpp"
|
||||
|
||||
namespace module {
|
||||
namespace token_provider {
|
||||
|
||||
void auth_token_providerImpl::init() {
|
||||
}
|
||||
|
||||
void auth_token_providerImpl::ready() {
|
||||
}
|
||||
|
||||
} // namespace token_provider
|
||||
} // namespace module
|
||||
@@ -0,0 +1,60 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
#ifndef TOKEN_PROVIDER_AUTH_TOKEN_PROVIDER_IMPL_HPP
|
||||
#define TOKEN_PROVIDER_AUTH_TOKEN_PROVIDER_IMPL_HPP
|
||||
|
||||
//
|
||||
// AUTO GENERATED - MARKED REGIONS WILL BE KEPT
|
||||
// template version 3
|
||||
//
|
||||
|
||||
#include <generated/interfaces/auth_token_provider/Implementation.hpp>
|
||||
|
||||
#include "../EvseManager.hpp"
|
||||
|
||||
// ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1
|
||||
// insert your custom include headers here
|
||||
// ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1
|
||||
|
||||
namespace module {
|
||||
namespace token_provider {
|
||||
|
||||
struct Conf {};
|
||||
|
||||
class auth_token_providerImpl : public auth_token_providerImplBase {
|
||||
public:
|
||||
auth_token_providerImpl() = delete;
|
||||
auth_token_providerImpl(Everest::ModuleAdapter* ev, const Everest::PtrContainer<EvseManager>& mod, Conf& config) :
|
||||
auth_token_providerImplBase(ev, "token_provider"), mod(mod), config(config){};
|
||||
|
||||
// ev@8ea32d28-373f-4c90-ae5e-b4fcc74e2a61:v1
|
||||
// insert your public definitions here
|
||||
// ev@8ea32d28-373f-4c90-ae5e-b4fcc74e2a61:v1
|
||||
|
||||
protected:
|
||||
// no commands defined for this interface
|
||||
|
||||
// ev@d2d1847a-7b88-41dd-ad07-92785f06f5c4:v1
|
||||
// insert your protected definitions here
|
||||
// ev@d2d1847a-7b88-41dd-ad07-92785f06f5c4:v1
|
||||
|
||||
private:
|
||||
const Everest::PtrContainer<EvseManager>& mod;
|
||||
const Conf& config;
|
||||
|
||||
virtual void init() override;
|
||||
virtual void ready() override;
|
||||
|
||||
// ev@3370e4dd-95f4-47a9-aaec-ea76f34a66c9:v1
|
||||
// insert your private definitions here
|
||||
// ev@3370e4dd-95f4-47a9-aaec-ea76f34a66c9:v1
|
||||
};
|
||||
|
||||
// ev@3d7da0ad-02c2-493d-9920-0bbbd56b9876:v1
|
||||
// insert other definitions here
|
||||
// ev@3d7da0ad-02c2-493d-9920-0bbbd56b9876:v1
|
||||
|
||||
} // namespace token_provider
|
||||
} // namespace module
|
||||
|
||||
#endif // TOKEN_PROVIDER_AUTH_TOKEN_PROVIDER_IMPL_HPP
|
||||
121
tools/EVerest-main/modules/EVSE/EvseManager/utils.hpp
Normal file
121
tools/EVerest-main/modules/EVSE/EvseManager/utils.hpp
Normal file
@@ -0,0 +1,121 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#ifndef _EVSEMANAGER_UTILS_H_
|
||||
#define _EVSEMANAGER_UTILS_H_
|
||||
|
||||
#include <everest/helpers/helpers.hpp>
|
||||
#include <generated/types/evse_manager.hpp>
|
||||
#include <generated/types/powermeter.hpp>
|
||||
#include <vector>
|
||||
|
||||
namespace module {
|
||||
namespace utils {
|
||||
|
||||
enum class SessionIdType {
|
||||
UUID,
|
||||
UUID_BASE64,
|
||||
SHORT_BASE64
|
||||
};
|
||||
|
||||
inline SessionIdType get_session_id_type_from_string(const std::string& type) {
|
||||
if (type == "UUID_BASE64") {
|
||||
return SessionIdType::UUID_BASE64;
|
||||
} else if (type == "SHORT_BASE64") {
|
||||
return SessionIdType::SHORT_BASE64;
|
||||
}
|
||||
// Default to UUID if the type is unknown
|
||||
return SessionIdType::UUID;
|
||||
}
|
||||
|
||||
inline std::string generate_session_id(SessionIdType type = SessionIdType::UUID) {
|
||||
if (type == SessionIdType::SHORT_BASE64) {
|
||||
return everest::helpers::get_base64_id();
|
||||
} else if (type == SessionIdType::UUID_BASE64) {
|
||||
return everest::helpers::get_base64_uuid();
|
||||
}
|
||||
return everest::helpers::get_uuid();
|
||||
}
|
||||
|
||||
inline types::powermeter::OCMFIdentificationType
|
||||
convert_to_ocmf_identification_type(const types::authorization::IdTokenType& id_token_type) {
|
||||
switch (id_token_type) {
|
||||
case types::authorization::IdTokenType::Central:
|
||||
return types::powermeter::OCMFIdentificationType::CENTRAL;
|
||||
case types::authorization::IdTokenType::eMAID:
|
||||
return types::powermeter::OCMFIdentificationType::EMAID;
|
||||
case types::authorization::IdTokenType::ISO14443:
|
||||
return types::powermeter::OCMFIdentificationType::ISO14443;
|
||||
case types::authorization::IdTokenType::ISO15693:
|
||||
return types::powermeter::OCMFIdentificationType::ISO15693;
|
||||
case types::authorization::IdTokenType::KeyCode:
|
||||
return types::powermeter::OCMFIdentificationType::KEY_CODE;
|
||||
case types::authorization::IdTokenType::Local:
|
||||
return types::powermeter::OCMFIdentificationType::LOCAL;
|
||||
case types::authorization::IdTokenType::NoAuthorization:
|
||||
return types::powermeter::OCMFIdentificationType::NONE;
|
||||
case types::authorization::IdTokenType::MacAddress:
|
||||
return types::powermeter::OCMFIdentificationType::UNDEFINED;
|
||||
}
|
||||
return types::powermeter::OCMFIdentificationType::UNDEFINED;
|
||||
}
|
||||
|
||||
// Simple helper class to measure time in ms
|
||||
class Stopwatch {
|
||||
public:
|
||||
Stopwatch() {
|
||||
reset();
|
||||
}
|
||||
|
||||
void reset() {
|
||||
start_time = std::chrono::steady_clock::now();
|
||||
phase_start_time = std::chrono::steady_clock::now();
|
||||
intitial_start_time = std::chrono::steady_clock::now();
|
||||
rep.clear();
|
||||
current_rep.clear();
|
||||
}
|
||||
|
||||
int mark_phase(const std::string& s) {
|
||||
int ms =
|
||||
std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - phase_start_time)
|
||||
.count();
|
||||
phase_start_time = start_time = std::chrono::steady_clock::now();
|
||||
current_rep = s;
|
||||
return ms;
|
||||
}
|
||||
|
||||
int mark(const std::string& s) {
|
||||
int ms = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - start_time)
|
||||
.count();
|
||||
start_time = std::chrono::steady_clock::now();
|
||||
current_rep += fmt::format(" | {}: {} ms", s, ms);
|
||||
return ms;
|
||||
}
|
||||
|
||||
std::string report_phase() {
|
||||
int ms =
|
||||
std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - phase_start_time)
|
||||
.count();
|
||||
current_rep += fmt::format(" | Total: {} ms", ms);
|
||||
rep.push_back(current_rep);
|
||||
return current_rep;
|
||||
}
|
||||
|
||||
std::vector<std::string> report_all_phases() {
|
||||
int ms = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() -
|
||||
intitial_start_time)
|
||||
.count();
|
||||
rep.push_back(fmt::format("Total duration all phases: {} ms", ms));
|
||||
return rep;
|
||||
}
|
||||
|
||||
private:
|
||||
std::chrono::time_point<std::chrono::steady_clock> start_time, intitial_start_time, phase_start_time;
|
||||
std::string current_rep;
|
||||
std::vector<std::string> rep;
|
||||
};
|
||||
|
||||
} // namespace utils
|
||||
} // namespace module
|
||||
|
||||
#endif
|
||||
34
tools/EVerest-main/modules/EVSE/EvseManager/v2gMessage.cpp
Normal file
34
tools/EVerest-main/modules/EVSE/EvseManager/v2gMessage.cpp
Normal file
@@ -0,0 +1,34 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2022 Pionix GmbH and Contributors to EVerest
|
||||
#include "v2gMessage.hpp"
|
||||
|
||||
#include <chrono>
|
||||
#include <date/date.h>
|
||||
#include <date/tz.h>
|
||||
|
||||
#include <fmt/core.h>
|
||||
|
||||
namespace module {
|
||||
|
||||
v2g_message::v2g_message() {
|
||||
}
|
||||
|
||||
bool v2g_message::from_xml(const std::string& xml) {
|
||||
return doc.load_string(xml.c_str());
|
||||
}
|
||||
|
||||
std::string v2g_message::to_xml() {
|
||||
std::stringstream ss;
|
||||
doc.save(ss);
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
void v2g_message::from_json(const std::string& json_str) {
|
||||
j = nlohmann::json::parse(json_str);
|
||||
}
|
||||
|
||||
std::string v2g_message::to_json() {
|
||||
return j.dump(4);
|
||||
}
|
||||
|
||||
} // namespace module
|
||||
30
tools/EVerest-main/modules/EVSE/EvseManager/v2gMessage.hpp
Normal file
30
tools/EVerest-main/modules/EVSE/EvseManager/v2gMessage.hpp
Normal file
@@ -0,0 +1,30 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
#ifndef V2G_MESSAGE_HPP
|
||||
#define V2G_MESSAGE_HPP
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <pugixml.hpp>
|
||||
#include <string>
|
||||
|
||||
namespace module {
|
||||
/*
|
||||
Simple V2G XML message helper
|
||||
*/
|
||||
|
||||
class v2g_message {
|
||||
public:
|
||||
v2g_message();
|
||||
bool from_xml(const std::string& xml);
|
||||
std::string to_xml();
|
||||
void from_json(const std::string& json_str);
|
||||
std::string to_json();
|
||||
|
||||
private:
|
||||
pugi::xml_document doc;
|
||||
nlohmann::json j;
|
||||
};
|
||||
|
||||
} // namespace module
|
||||
|
||||
#endif // V2G_MESSAGE_HPP
|
||||
@@ -0,0 +1,281 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include "voltage_plausibility/VoltagePlausibilityMonitor.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <condition_variable>
|
||||
#include <everest/logging.hpp>
|
||||
#include <fmt/core.h>
|
||||
#include <thread>
|
||||
|
||||
namespace module {
|
||||
|
||||
VoltagePlausibilityMonitor::VoltagePlausibilityMonitor(ErrorCallback callback, double max_spread_threshold_V,
|
||||
std::chrono::milliseconds fault_duration) :
|
||||
error_callback_(std::move(callback)),
|
||||
max_spread_threshold_V_(max_spread_threshold_V),
|
||||
fault_duration_(fault_duration) {
|
||||
timer_thread_ = std::thread(&VoltagePlausibilityMonitor::timer_thread_func, this);
|
||||
}
|
||||
|
||||
VoltagePlausibilityMonitor::~VoltagePlausibilityMonitor() {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(timer_mutex_);
|
||||
timer_thread_exit_ = true;
|
||||
timer_armed_ = false;
|
||||
}
|
||||
timer_cv_.notify_one();
|
||||
if (timer_thread_.joinable()) {
|
||||
timer_thread_.join();
|
||||
}
|
||||
}
|
||||
|
||||
void VoltagePlausibilityMonitor::start_monitor() {
|
||||
if (!running_.load()) {
|
||||
EVLOG_info << "VoltagePlausibilityMonitor, start monitoring";
|
||||
}
|
||||
fault_latched_.store(false);
|
||||
cancel_fault_timer();
|
||||
running_.store(true);
|
||||
}
|
||||
|
||||
void VoltagePlausibilityMonitor::stop_monitor() {
|
||||
if (running_.load()) {
|
||||
EVLOG_info << "VoltagePlausibilityMonitor, stop monitoring";
|
||||
}
|
||||
running_.store(false);
|
||||
cancel_fault_timer();
|
||||
}
|
||||
|
||||
void VoltagePlausibilityMonitor::reset() {
|
||||
fault_latched_.store(false);
|
||||
{
|
||||
// Reset all voltage samples to zero time - protected by data_mutex_
|
||||
std::lock_guard<std::mutex> lock(data_mutex_);
|
||||
power_supply_sample_.timestamp = std::chrono::steady_clock::time_point{};
|
||||
powermeter_sample_.timestamp = std::chrono::steady_clock::time_point{};
|
||||
isolation_monitor_sample_.timestamp = std::chrono::steady_clock::time_point{};
|
||||
over_voltage_monitor_sample_.timestamp = std::chrono::steady_clock::time_point{};
|
||||
}
|
||||
cancel_fault_timer();
|
||||
}
|
||||
|
||||
void VoltagePlausibilityMonitor::update_power_supply_voltage(double voltage_V) {
|
||||
std::vector<double> valid_voltages;
|
||||
if (running_.load() && !fault_latched_.load()) {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(data_mutex_);
|
||||
power_supply_sample_.voltage_V = voltage_V;
|
||||
power_supply_sample_.timestamp = std::chrono::steady_clock::now();
|
||||
valid_voltages = get_valid_voltages();
|
||||
}
|
||||
evaluate_voltages(valid_voltages);
|
||||
}
|
||||
}
|
||||
|
||||
void VoltagePlausibilityMonitor::update_powermeter_voltage(double voltage_V) {
|
||||
std::vector<double> valid_voltages;
|
||||
if (running_.load() && !fault_latched_.load()) {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(data_mutex_);
|
||||
powermeter_sample_.voltage_V = voltage_V;
|
||||
powermeter_sample_.timestamp = std::chrono::steady_clock::now();
|
||||
valid_voltages = get_valid_voltages();
|
||||
}
|
||||
evaluate_voltages(valid_voltages);
|
||||
}
|
||||
}
|
||||
|
||||
void VoltagePlausibilityMonitor::update_isolation_monitor_voltage(double voltage_V) {
|
||||
std::vector<double> valid_voltages;
|
||||
if (running_.load() && !fault_latched_.load()) {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(data_mutex_);
|
||||
isolation_monitor_sample_.voltage_V = voltage_V;
|
||||
isolation_monitor_sample_.timestamp = std::chrono::steady_clock::now();
|
||||
valid_voltages = get_valid_voltages();
|
||||
}
|
||||
evaluate_voltages(valid_voltages);
|
||||
}
|
||||
}
|
||||
|
||||
void VoltagePlausibilityMonitor::update_over_voltage_monitor_voltage(double voltage_V) {
|
||||
std::vector<double> valid_voltages;
|
||||
if (running_.load() && !fault_latched_.load()) {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(data_mutex_);
|
||||
over_voltage_monitor_sample_.voltage_V = voltage_V;
|
||||
over_voltage_monitor_sample_.timestamp = std::chrono::steady_clock::now();
|
||||
valid_voltages = get_valid_voltages();
|
||||
}
|
||||
evaluate_voltages(valid_voltages);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<double> VoltagePlausibilityMonitor::get_valid_voltages() const {
|
||||
std::vector<double> valid_voltages;
|
||||
const auto zero_time = std::chrono::steady_clock::time_point{};
|
||||
|
||||
// Collect voltage samples that were updated since the last reset/start.
|
||||
// A timestamp of zero (default-initialized) means we've never received a value (or it was cleared on reset()).
|
||||
if (power_supply_sample_.timestamp != zero_time) {
|
||||
valid_voltages.push_back(power_supply_sample_.voltage_V);
|
||||
}
|
||||
|
||||
if (powermeter_sample_.timestamp != zero_time) {
|
||||
valid_voltages.push_back(powermeter_sample_.voltage_V);
|
||||
}
|
||||
|
||||
if (isolation_monitor_sample_.timestamp != zero_time) {
|
||||
valid_voltages.push_back(isolation_monitor_sample_.voltage_V);
|
||||
}
|
||||
|
||||
if (over_voltage_monitor_sample_.timestamp != zero_time) {
|
||||
valid_voltages.push_back(over_voltage_monitor_sample_.voltage_V);
|
||||
}
|
||||
return valid_voltages;
|
||||
}
|
||||
|
||||
void VoltagePlausibilityMonitor::evaluate_voltages(const std::vector<double>& valid_voltages) {
|
||||
// Need at least 2 valid sources to compute spread
|
||||
if (valid_voltages.size() < 2) {
|
||||
const bool cancelled = cancel_fault_timer();
|
||||
if (cancelled) {
|
||||
EVLOG_info << "VoltagePlausibilityMonitor, using less than 2 valid voltage sources -> cancelling timer";
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Order values and compute spread (max-min)
|
||||
const double spread_V = calculate_spread(valid_voltages);
|
||||
// Check against threshold
|
||||
if (spread_V > max_spread_threshold_V_) {
|
||||
const bool armed = arm_fault_timer(spread_V);
|
||||
if (armed) {
|
||||
EVLOG_info << "VoltagePlausibilityMonitor, spread: " << spread_V
|
||||
<< " V > threshold: " << max_spread_threshold_V_ << " V -> arming timer";
|
||||
}
|
||||
} else {
|
||||
const bool cancelled = cancel_fault_timer();
|
||||
if (cancelled) {
|
||||
EVLOG_info << "VoltagePlausibilityMonitor, spread: " << spread_V
|
||||
<< " V <= threshold: " << max_spread_threshold_V_ << " V -> cancelling timer";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
double VoltagePlausibilityMonitor::calculate_spread(std::vector<double> valid_voltages) {
|
||||
if (valid_voltages.size() < 2) {
|
||||
return 0.0;
|
||||
}
|
||||
std::sort(valid_voltages.begin(), valid_voltages.end());
|
||||
return valid_voltages.back() - valid_voltages.front();
|
||||
}
|
||||
|
||||
void VoltagePlausibilityMonitor::trigger_fault(const std::string& reason) {
|
||||
fault_latched_.store(true);
|
||||
running_.store(false);
|
||||
cancel_fault_timer();
|
||||
if (error_callback_) {
|
||||
error_callback_(reason);
|
||||
}
|
||||
}
|
||||
|
||||
bool VoltagePlausibilityMonitor::arm_fault_timer(double spread_V) {
|
||||
if (fault_duration_.count() == 0) {
|
||||
trigger_fault(fmt::format(
|
||||
"Voltage spread {:.2f} V exceeded threshold {:.2f} V and fault duration is 0 ms -> fault immediately",
|
||||
spread_V, max_spread_threshold_V_));
|
||||
return false;
|
||||
}
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(timer_mutex_);
|
||||
if (timer_armed_) {
|
||||
return false;
|
||||
}
|
||||
timer_armed_ = true;
|
||||
timer_deadline_ = std::chrono::steady_clock::now() + fault_duration_;
|
||||
}
|
||||
timer_cv_.notify_one();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool VoltagePlausibilityMonitor::cancel_fault_timer() {
|
||||
bool cancelled_now = false;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(timer_mutex_);
|
||||
if (!timer_armed_) {
|
||||
return false;
|
||||
}
|
||||
timer_armed_ = false;
|
||||
cancelled_now = true;
|
||||
}
|
||||
timer_cv_.notify_one();
|
||||
return cancelled_now;
|
||||
}
|
||||
|
||||
void VoltagePlausibilityMonitor::timer_thread_func() {
|
||||
std::unique_lock<std::mutex> lock(timer_mutex_);
|
||||
|
||||
while (!timer_thread_exit_) {
|
||||
// Wait until a timer is armed or exit is requested
|
||||
timer_cv_.wait(lock, [this] { return timer_thread_exit_ || timer_armed_; });
|
||||
if (timer_thread_exit_) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Capture the current deadline and wait until it expires or is cancelled/updated
|
||||
auto deadline = timer_deadline_;
|
||||
while (!timer_thread_exit_ && timer_armed_) {
|
||||
if (timer_cv_.wait_until(lock, deadline) == std::cv_status::timeout) {
|
||||
break;
|
||||
}
|
||||
// Woken up: check for exit, cancellation or re-arming with a new deadline
|
||||
if (timer_thread_exit_ || !timer_armed_ || timer_deadline_ != deadline) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (timer_thread_exit_) {
|
||||
break;
|
||||
}
|
||||
if (!timer_armed_ || timer_deadline_ != deadline) {
|
||||
// Timer was cancelled or re-armed; go back to waiting
|
||||
continue;
|
||||
}
|
||||
|
||||
// Timer expired with this deadline and is still armed.
|
||||
// Re-check the fault condition using the *current* set of valid voltages. This avoids triggering a fault if
|
||||
// updates stopped and we no longer have enough sources (or monitoring stopped) while the timer was armed.
|
||||
timer_armed_ = false;
|
||||
|
||||
// Release the lock while invoking the callback path
|
||||
lock.unlock();
|
||||
bool should_trigger = false;
|
||||
double current_spread_V = 0.0;
|
||||
if (running_.load() && !fault_latched_.load()) {
|
||||
std::vector<double> valid_voltages;
|
||||
{
|
||||
std::lock_guard<std::mutex> data_lock(data_mutex_);
|
||||
valid_voltages = get_valid_voltages();
|
||||
}
|
||||
if (valid_voltages.size() >= 2) {
|
||||
current_spread_V = calculate_spread(std::move(valid_voltages));
|
||||
should_trigger = current_spread_V > max_spread_threshold_V_;
|
||||
}
|
||||
}
|
||||
|
||||
if (should_trigger) {
|
||||
trigger_fault(fmt::format("Voltage spread {:.2f} V exceeded threshold {:.2f} V for at least {} ms.",
|
||||
current_spread_V, max_spread_threshold_V_, fault_duration_.count()));
|
||||
} else {
|
||||
EVLOG_info << "VoltagePlausibilityMonitor, timer expired but fault condition no longer true -> no fault";
|
||||
}
|
||||
lock.lock();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace module
|
||||
@@ -0,0 +1,128 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <condition_variable>
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
namespace module {
|
||||
|
||||
/// \brief Voltage plausibility monitor used by EvseManager during DC charging.
|
||||
///
|
||||
/// The monitor compares voltage measurements from multiple sources (power supply, powermeter,
|
||||
/// isolation monitor, over voltage monitor) and checks that they report similar values. If the
|
||||
/// spread (max-min) between available sources exceeds a configured threshold for a given
|
||||
/// duration, a fault is raised.
|
||||
///
|
||||
/// Thread-safety:
|
||||
/// - Public APIs are intended to be called from EvseManager threads and from callbacks of the
|
||||
/// external voltage source interfaces.
|
||||
/// - Internally, a dedicated background thread waits on a fault timer condition and synchronizes
|
||||
/// access to timer-related state via an internal mutex and condition variable.
|
||||
class VoltagePlausibilityMonitor {
|
||||
public:
|
||||
/// \brief Callback type used to report detected faults.
|
||||
///
|
||||
/// The callback is invoked from an internal monitoring context when a fault is detected.
|
||||
/// It receives a human-readable description of the fault.
|
||||
using ErrorCallback = std::function<void(const std::string&)>;
|
||||
|
||||
/// \brief Construct a new VoltagePlausibilityMonitor.
|
||||
///
|
||||
/// \param callback Function that will be called whenever a fault is detected.
|
||||
/// \param max_spread_threshold_V Maximum allowed spread (max-min) in volts between
|
||||
/// voltage sources before a fault is triggered.
|
||||
/// \param fault_duration Duration for which the max-min spread must exceed the threshold
|
||||
/// before a fault is raised. A duration of 0 ms means that a fault will
|
||||
/// be raised immediately when the threshold is exceeded.
|
||||
VoltagePlausibilityMonitor(ErrorCallback callback, double max_spread_threshold_V,
|
||||
std::chrono::milliseconds fault_duration);
|
||||
|
||||
/// \brief Destructor joins the internal timer thread before destroying the object.
|
||||
~VoltagePlausibilityMonitor();
|
||||
|
||||
VoltagePlausibilityMonitor(const VoltagePlausibilityMonitor&) = delete;
|
||||
VoltagePlausibilityMonitor& operator=(const VoltagePlausibilityMonitor&) = delete;
|
||||
VoltagePlausibilityMonitor(VoltagePlausibilityMonitor&&) = delete;
|
||||
VoltagePlausibilityMonitor& operator=(VoltagePlausibilityMonitor&&) = delete;
|
||||
|
||||
/// \brief Start monitoring of incoming voltage samples.
|
||||
///
|
||||
/// Clears any latched fault state and cancels a pending fault timer, then enables
|
||||
/// evaluation of voltage samples.
|
||||
void start_monitor();
|
||||
|
||||
/// \brief Stop monitoring of incoming voltage samples.
|
||||
///
|
||||
/// Monitoring is disabled and any pending fault timer is cancelled. Existing latched
|
||||
/// faults remain active until \ref reset() is called.
|
||||
void stop_monitor();
|
||||
|
||||
/// \brief Feed a new voltage sample from the power supply.
|
||||
///
|
||||
/// \param voltage_V Measured DC voltage in volts.
|
||||
void update_power_supply_voltage(double voltage_V);
|
||||
|
||||
/// \brief Feed a new voltage sample from the powermeter.
|
||||
///
|
||||
/// \param voltage_V Measured DC voltage in volts.
|
||||
void update_powermeter_voltage(double voltage_V);
|
||||
|
||||
/// \brief Feed a new voltage sample from the isolation monitor.
|
||||
///
|
||||
/// \param voltage_V Measured DC voltage in volts.
|
||||
void update_isolation_monitor_voltage(double voltage_V);
|
||||
|
||||
/// \brief Feed a new voltage sample from the over voltage monitor.
|
||||
///
|
||||
/// \param voltage_V Measured DC voltage in volts.
|
||||
void update_over_voltage_monitor_voltage(double voltage_V);
|
||||
|
||||
/// \brief Reset the internal fault latch and cancel timers.
|
||||
///
|
||||
/// This clears any previously raised fault and stops the internal fault timer. Monitoring
|
||||
/// remains disabled until \ref start_monitor() is called again.
|
||||
void reset();
|
||||
|
||||
private:
|
||||
struct VoltageSample {
|
||||
std::chrono::steady_clock::time_point timestamp{};
|
||||
double voltage_V{0.0};
|
||||
};
|
||||
|
||||
void timer_thread_func();
|
||||
void trigger_fault(const std::string& reason);
|
||||
bool arm_fault_timer(double spread_V);
|
||||
bool cancel_fault_timer();
|
||||
std::vector<double> get_valid_voltages() const;
|
||||
void evaluate_voltages(const std::vector<double>& valid_voltages);
|
||||
static double calculate_spread(std::vector<double> valid_voltages);
|
||||
|
||||
ErrorCallback error_callback_;
|
||||
double max_spread_threshold_V_;
|
||||
std::chrono::milliseconds fault_duration_;
|
||||
std::atomic_bool running_{false};
|
||||
std::atomic_bool fault_latched_{false};
|
||||
|
||||
std::mutex data_mutex_;
|
||||
VoltageSample power_supply_sample_;
|
||||
VoltageSample powermeter_sample_;
|
||||
VoltageSample isolation_monitor_sample_;
|
||||
VoltageSample over_voltage_monitor_sample_;
|
||||
|
||||
std::mutex timer_mutex_;
|
||||
std::chrono::steady_clock::time_point timer_deadline_;
|
||||
bool timer_armed_{false};
|
||||
bool timer_thread_exit_{false};
|
||||
std::condition_variable timer_cv_;
|
||||
std::thread timer_thread_;
|
||||
};
|
||||
|
||||
} // namespace module
|
||||
15
tools/EVerest-main/modules/EVSE/EvseSecurity/BUILD.bazel
Normal file
15
tools/EVerest-main/modules/EVSE/EvseSecurity/BUILD.bazel
Normal file
@@ -0,0 +1,15 @@
|
||||
load("//modules:module.bzl", "cc_everest_module")
|
||||
|
||||
IMPLS = [
|
||||
"main",
|
||||
]
|
||||
|
||||
cc_everest_module(
|
||||
name = "EvseSecurity",
|
||||
deps = [
|
||||
"//lib/everest/evse_security:libevse-security",
|
||||
"//lib:evse_security_conversions",
|
||||
],
|
||||
impls = IMPLS,
|
||||
)
|
||||
|
||||
27
tools/EVerest-main/modules/EVSE/EvseSecurity/CMakeLists.txt
Normal file
27
tools/EVerest-main/modules/EVSE/EvseSecurity/CMakeLists.txt
Normal file
@@ -0,0 +1,27 @@
|
||||
#
|
||||
# AUTO GENERATED - MARKED REGIONS WILL BE KEPT
|
||||
# template version 3
|
||||
#
|
||||
|
||||
# module setup:
|
||||
# - ${MODULE_NAME}: module name
|
||||
ev_setup_cpp_module()
|
||||
|
||||
# ev@bcc62523-e22b-41d7-ba2f-825b493a3c97:v1
|
||||
# insert your custom targets and additional config variables here
|
||||
|
||||
target_link_libraries(${MODULE_NAME}
|
||||
PRIVATE
|
||||
everest::evse_security
|
||||
everest::evse_security_conversions
|
||||
)
|
||||
# ev@bcc62523-e22b-41d7-ba2f-825b493a3c97:v1
|
||||
|
||||
target_sources(${MODULE_NAME}
|
||||
PRIVATE
|
||||
"main/evse_securityImpl.cpp"
|
||||
)
|
||||
|
||||
# ev@c55432ab-152c-45a9-9d2e-7281d50c69c3:v1
|
||||
# insert other things like install cmds etc here
|
||||
# ev@c55432ab-152c-45a9-9d2e-7281d50c69c3:v1
|
||||
@@ -0,0 +1,15 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
#include "EvseSecurity.hpp"
|
||||
|
||||
namespace module {
|
||||
|
||||
void EvseSecurity::init() {
|
||||
invoke_init(*p_main);
|
||||
}
|
||||
|
||||
void EvseSecurity::ready() {
|
||||
invoke_ready(*p_main);
|
||||
}
|
||||
|
||||
} // namespace module
|
||||
@@ -0,0 +1,68 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
#ifndef EVSE_SECURITY_HPP
|
||||
#define EVSE_SECURITY_HPP
|
||||
|
||||
//
|
||||
// AUTO GENERATED - MARKED REGIONS WILL BE KEPT
|
||||
// template version 2
|
||||
//
|
||||
|
||||
#include "ld-ev.hpp"
|
||||
|
||||
// headers for provided interface implementations
|
||||
#include <generated/interfaces/evse_security/Implementation.hpp>
|
||||
|
||||
// ev@4bf81b14-a215-475c-a1d3-0a484ae48918:v1
|
||||
// insert your custom include headers here
|
||||
// ev@4bf81b14-a215-475c-a1d3-0a484ae48918:v1
|
||||
|
||||
namespace module {
|
||||
|
||||
struct Conf {
|
||||
std::string csms_ca_bundle;
|
||||
std::string mf_ca_bundle;
|
||||
std::string mo_ca_bundle;
|
||||
std::string v2g_ca_bundle;
|
||||
std::string csms_leaf_cert_directory;
|
||||
std::string csms_leaf_key_directory;
|
||||
std::string secc_leaf_cert_directory;
|
||||
std::string secc_leaf_key_directory;
|
||||
std::string private_key_password;
|
||||
};
|
||||
|
||||
class EvseSecurity : public Everest::ModuleBase {
|
||||
public:
|
||||
EvseSecurity() = delete;
|
||||
EvseSecurity(const ModuleInfo& info, std::unique_ptr<evse_securityImplBase> p_main, Conf& config) :
|
||||
ModuleBase(info), p_main(std::move(p_main)), config(config){};
|
||||
|
||||
const std::unique_ptr<evse_securityImplBase> p_main;
|
||||
const Conf& config;
|
||||
|
||||
// ev@1fce4c5e-0ab8-41bb-90f7-14277703d2ac:v1
|
||||
// insert your public definitions here
|
||||
// ev@1fce4c5e-0ab8-41bb-90f7-14277703d2ac:v1
|
||||
|
||||
protected:
|
||||
// ev@4714b2ab-a24f-4b95-ab81-36439e1478de:v1
|
||||
// insert your protected definitions here
|
||||
// ev@4714b2ab-a24f-4b95-ab81-36439e1478de:v1
|
||||
|
||||
private:
|
||||
friend class LdEverest;
|
||||
void init();
|
||||
void ready();
|
||||
|
||||
// ev@211cfdbe-f69a-4cd6-a4ec-f8aaa3d1b6c8:v1
|
||||
// insert your private definitions here
|
||||
// ev@211cfdbe-f69a-4cd6-a4ec-f8aaa3d1b6c8:v1
|
||||
};
|
||||
|
||||
// ev@087e516b-124c-48df-94fb-109508c7cda9:v1
|
||||
// insert other definitions here
|
||||
// ev@087e516b-124c-48df-94fb-109508c7cda9:v1
|
||||
|
||||
} // namespace module
|
||||
|
||||
#endif // EVSE_SECURITY_HPP
|
||||
126
tools/EVerest-main/modules/EVSE/EvseSecurity/docs/index.rst
Normal file
126
tools/EVerest-main/modules/EVSE/EvseSecurity/docs/index.rst
Normal file
@@ -0,0 +1,126 @@
|
||||
.. _everest_modules_handwritten_EvseSecurity:
|
||||
|
||||
.. ============
|
||||
.. EvseSecurity
|
||||
.. ============
|
||||
|
||||
This module implements the
|
||||
:ref:`evse_security interface <everest_interfaces_evse_security>`.
|
||||
It wraps the functionality of the
|
||||
`libevse-security <https://github.com/EVerest/libevse-security>`_
|
||||
to provide access to security-related operations to other Everest modules such
|
||||
as OCPP and ISO15118. These modules require similar security-related
|
||||
operations and shared access to certificates and keys, which this module
|
||||
facilitates.
|
||||
|
||||
For detailed information about the provided functionality, please refer to the
|
||||
README within the
|
||||
`libevse-security <https://github.com/EVerest/libevse-security>`_.
|
||||
|
||||
CA Certificate Domains
|
||||
======================
|
||||
|
||||
The combination of ISO 15118 and OCPP defines several CA certificate domains
|
||||
for charging stations, addressed and covered by this module:
|
||||
|
||||
* V2G root: Trust anchor for ISO 15118 TLS communication between the charging
|
||||
station and the electric vehicle.
|
||||
* CSMS root: Trust anchor for TLS communication between the charging station
|
||||
and OCPP CSMS.
|
||||
* MF root: Trust anchor of the manufacturer to verify firmware updates.
|
||||
* MO root: Trust anchor of the Mobility Operator domain to verify contract
|
||||
certificates.
|
||||
|
||||
Module Configuration
|
||||
====================
|
||||
|
||||
The following instructions describe how to configure the module parameters
|
||||
mainly for two domains: OCPP communication and ISO 15118 communication.
|
||||
|
||||
A lot of configuration parameters specify directory or file paths from which
|
||||
certificates and private keys are loaded or new certificates and keys are
|
||||
written to. Please make sure that these paths are writeable for EVerest.
|
||||
|
||||
Configuration for OCPP
|
||||
----------------------
|
||||
|
||||
In OCPP and OCPP security profiles, the security level of the connection is
|
||||
specified as follows:
|
||||
|
||||
* SecurityProfile 0: Unsecured transport without basic authentication
|
||||
* SecurityProfile 1: Unsecured transport with basic authentication
|
||||
* SecurityProfile 2: TLS with basic authentication
|
||||
* SecurityProfile 3: TLS with client-side certificates
|
||||
|
||||
Only when security profiles 2 or 3 are used, the configuration of this module
|
||||
is relevant for the OCPP communication. In this case, the charging station
|
||||
acts as a TLS client.
|
||||
|
||||
The ``csms_ca_bundle`` config parameter specifies a path to a file containing
|
||||
trusted CSMS root certificates. The server certificate presented by the CSMS
|
||||
server during the TLS handshake must be signed by one of the trusted root
|
||||
certificates specified in this file.
|
||||
|
||||
If new root certificates are installed using the ``install_ca_certificate``
|
||||
command with the CSMS domain specified, the new CA certificate is installed
|
||||
into the specified bundle and used for further validations.
|
||||
|
||||
Note: The OCPP modules in EVerest can be configured to also trust the
|
||||
operating system's default verify paths. The parameter controlling this
|
||||
behavior is ``UseSslDefaultVerifyPaths``. If configured to ``true``, the
|
||||
``csms_ca_bundle`` need not necessarily be configured.
|
||||
|
||||
If security profile 3 is used, a mutual TLS (mTLS) connection shall be
|
||||
established, so the CSMS server also verifies the client certificate. The
|
||||
``csms_leaf_cert_directory`` and ``csms_leaf_key_directory`` need to be
|
||||
configured for this. These parameters specify the directory of the client
|
||||
certificate and key for the mTLS connection.
|
||||
|
||||
New CSMS client certificates can be installed using the
|
||||
``update_leaf_certificate`` command with the CSMS domain specified. In OCPP,
|
||||
new client certificates are installed by using the ``CertificateSigned.req``
|
||||
message.
|
||||
|
||||
Configuration for ISO 15118
|
||||
---------------------------
|
||||
|
||||
For ISO 15118 communication, the charging station provides a server endpoint
|
||||
to which the electric vehicle connects. The communication may be secured using
|
||||
TLS. TLS is mandatory for Plug&Charge. If TLS is used, the correct
|
||||
configuration of the ``secc_leaf_cert_directory`` and
|
||||
``secc_leaf_key_directory`` is required. These directories are used to locate
|
||||
the server certificate and key for the ISO 15118 TLS server provided by the
|
||||
charging station.
|
||||
|
||||
New V2G client certificates can be installed using the
|
||||
``update_leaf_certificate`` command with the V2G domain specified. In OCPP,
|
||||
new client certificates are installed by using the ``CertificateSigned.req``
|
||||
message.
|
||||
|
||||
Private Key Password
|
||||
--------------------
|
||||
|
||||
If private keys are generated in the process of generating a certificate
|
||||
signing request (CSR), the private keys are not encrypted with a password.
|
||||
Therefore, no password needs to be configured if all certificates are
|
||||
installed using the ``generate_certificate_signing_request`` and
|
||||
``update_leaf_certificate`` commands.
|
||||
|
||||
If existing certificates and private keys are to be installed, the
|
||||
``private_key_password`` parameter specifies the password for encrypted
|
||||
private keys. Please note that only one value can be configured for possibly
|
||||
multiple encrypted private keys.
|
||||
|
||||
More about CSMS, V2G, MO and MF Bundles
|
||||
---------------------------------------
|
||||
|
||||
* The ``v2g_ca_bundle`` is used to verify the installation of SECC leaf
|
||||
certificates using the ``update_leaf_certificate`` command.
|
||||
* The ``csms_ca_bundle`` is used to verify the installation of CSMS leaf
|
||||
certificates using the ``update_leaf_certificate`` command.
|
||||
* The ``mo_ca_bundle`` is used to verify contract certificates provided by the
|
||||
electric vehicle as part of the ISO 15118 Plug & Charge process.
|
||||
* The ``mf_ca_bundle`` is used to verify firmware update files.
|
||||
|
||||
New root certificates can be installed in the specified domain using the
|
||||
``install_ca_certificate`` command.
|
||||
@@ -0,0 +1,290 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include "evse_securityImpl.hpp"
|
||||
#include <everest/conversions/evse_security/conversions.hpp>
|
||||
|
||||
namespace module {
|
||||
namespace main {
|
||||
|
||||
void evse_securityImpl::init() {
|
||||
|
||||
const auto certs_path = this->mod->info.paths.etc / "certs";
|
||||
evse_security::FilePaths file_paths = {certs_path / this->mod->config.csms_ca_bundle,
|
||||
certs_path / this->mod->config.mf_ca_bundle,
|
||||
certs_path / this->mod->config.mo_ca_bundle,
|
||||
certs_path / this->mod->config.v2g_ca_bundle,
|
||||
certs_path / this->mod->config.csms_leaf_cert_directory,
|
||||
certs_path / this->mod->config.csms_leaf_key_directory,
|
||||
certs_path / this->mod->config.secc_leaf_cert_directory,
|
||||
certs_path / this->mod->config.secc_leaf_key_directory};
|
||||
|
||||
std::optional<std::string> private_key_password = std::nullopt;
|
||||
if (!this->mod->config.private_key_password.empty()) {
|
||||
private_key_password = this->mod->config.private_key_password;
|
||||
}
|
||||
|
||||
this->evse_security = std::make_unique<evse_security::EvseSecurity>(file_paths, private_key_password);
|
||||
}
|
||||
|
||||
void evse_securityImpl::ready() {
|
||||
}
|
||||
|
||||
types::evse_security::InstallCertificateResult
|
||||
evse_securityImpl::handle_install_ca_certificate(std::string& certificate,
|
||||
types::evse_security::CaCertificateType& certificate_type) {
|
||||
try {
|
||||
const auto response = conversions::to_everest(
|
||||
this->evse_security->install_ca_certificate(certificate, conversions::from_everest(certificate_type)));
|
||||
if (response == types::evse_security::InstallCertificateResult::Accepted) {
|
||||
types::evse_security::CertificateStoreUpdate update;
|
||||
update.operation = types::evse_security::CertificateStoreUpdateOperation::Installed;
|
||||
update.ca_certificate_type = certificate_type;
|
||||
this->publish_certificate_store_update(update);
|
||||
}
|
||||
return response;
|
||||
} catch (const std::out_of_range& e) {
|
||||
EVLOG_warning << e.what();
|
||||
return types::evse_security::InstallCertificateResult::WriteError;
|
||||
}
|
||||
}
|
||||
|
||||
types::evse_security::DeleteCertificateResult
|
||||
evse_securityImpl::handle_delete_certificate(types::evse_security::CertificateHashData& certificate_hash_data) {
|
||||
try {
|
||||
const auto response = this->evse_security->delete_certificate(conversions::from_everest(certificate_hash_data));
|
||||
const auto result = conversions::to_everest(response.result);
|
||||
|
||||
if (result == types::evse_security::DeleteCertificateResult::Accepted) {
|
||||
types::evse_security::CertificateStoreUpdate update;
|
||||
|
||||
update.operation = types::evse_security::CertificateStoreUpdateOperation::Deleted;
|
||||
|
||||
if (response.ca_certificate_type.has_value()) {
|
||||
update.ca_certificate_type = conversions::to_everest(response.ca_certificate_type.value());
|
||||
}
|
||||
if (response.leaf_certificate_type.has_value()) {
|
||||
update.leaf_certificate_type = conversions::to_everest(response.leaf_certificate_type.value());
|
||||
}
|
||||
|
||||
this->publish_certificate_store_update(update);
|
||||
}
|
||||
|
||||
return result;
|
||||
} catch (const std::out_of_range& e) {
|
||||
EVLOG_warning << e.what();
|
||||
return types::evse_security::DeleteCertificateResult::Failed;
|
||||
}
|
||||
}
|
||||
|
||||
types::evse_security::InstallCertificateResult
|
||||
evse_securityImpl::handle_update_leaf_certificate(std::string& certificate_chain,
|
||||
types::evse_security::LeafCertificateType& certificate_type) {
|
||||
try {
|
||||
const auto response = conversions::to_everest(this->evse_security->update_leaf_certificate(
|
||||
certificate_chain, conversions::from_everest(certificate_type)));
|
||||
if (response == types::evse_security::InstallCertificateResult::Accepted) {
|
||||
types::evse_security::CertificateStoreUpdate update;
|
||||
update.operation = types::evse_security::CertificateStoreUpdateOperation::Installed;
|
||||
update.leaf_certificate_type = certificate_type;
|
||||
this->publish_certificate_store_update(update);
|
||||
}
|
||||
return response;
|
||||
} catch (const std::out_of_range& e) {
|
||||
EVLOG_warning << e.what();
|
||||
return types::evse_security::InstallCertificateResult::WriteError;
|
||||
}
|
||||
}
|
||||
|
||||
types::evse_security::CertificateValidationResult evse_securityImpl::handle_verify_certificate(
|
||||
std::string& certificate_chain, std::vector<types::evse_security::LeafCertificateType>& certificate_types) {
|
||||
|
||||
std::vector<evse_security::LeafCertificateType> _certificate_types;
|
||||
|
||||
for (const auto& certificate_type : certificate_types) {
|
||||
try {
|
||||
_certificate_types.push_back(conversions::from_everest(certificate_type));
|
||||
} catch (const std::out_of_range& e) {
|
||||
EVLOG_warning << e.what();
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
return conversions::to_everest(this->evse_security->verify_certificate(certificate_chain, _certificate_types));
|
||||
} catch (const std::out_of_range& e) {
|
||||
EVLOG_warning << e.what();
|
||||
return types::evse_security::CertificateValidationResult::Unknown;
|
||||
}
|
||||
}
|
||||
|
||||
types::evse_security::GetInstalledCertificatesResult evse_securityImpl::handle_get_installed_certificates(
|
||||
std::vector<types::evse_security::CertificateType>& certificate_types) {
|
||||
std::vector<evse_security::CertificateType> _certificate_types;
|
||||
|
||||
for (const auto& certificate_type : certificate_types) {
|
||||
try {
|
||||
_certificate_types.push_back(conversions::from_everest(certificate_type));
|
||||
} catch (const std::out_of_range& e) {
|
||||
EVLOG_warning << e.what();
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
return conversions::to_everest(this->evse_security->get_installed_certificates(_certificate_types));
|
||||
} catch (const std::out_of_range& e) {
|
||||
EVLOG_warning << e.what();
|
||||
return {types::evse_security::GetInstalledCertificatesStatus::NotFound, {}};
|
||||
}
|
||||
}
|
||||
|
||||
types::evse_security::OCSPRequestDataList evse_securityImpl::handle_get_v2g_ocsp_request_data() {
|
||||
try {
|
||||
return conversions::to_everest(this->evse_security->get_v2g_ocsp_request_data());
|
||||
} catch (const std::out_of_range& e) {
|
||||
EVLOG_warning << e.what();
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
types::evse_security::OCSPRequestDataList
|
||||
evse_securityImpl::handle_get_mo_ocsp_request_data(std::string& certificate_chain) {
|
||||
try {
|
||||
return conversions::to_everest(this->evse_security->get_mo_ocsp_request_data(certificate_chain));
|
||||
} catch (const std::out_of_range& e) {
|
||||
EVLOG_warning << e.what();
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
void evse_securityImpl::handle_update_ocsp_cache(types::evse_security::CertificateHashData& certificate_hash_data,
|
||||
std::string& ocsp_response) {
|
||||
try {
|
||||
this->evse_security->update_ocsp_cache(conversions::from_everest(certificate_hash_data), ocsp_response);
|
||||
} catch (const std::out_of_range& e) {
|
||||
EVLOG_warning << e.what();
|
||||
}
|
||||
}
|
||||
|
||||
bool evse_securityImpl::handle_is_ca_certificate_installed(types::evse_security::CaCertificateType& certificate_type) {
|
||||
try {
|
||||
return this->evse_security->is_ca_certificate_installed(conversions::from_everest(certificate_type));
|
||||
} catch (const std::out_of_range& e) {
|
||||
EVLOG_warning << e.what();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
types::evse_security::GetCertificateSignRequestResult evse_securityImpl::handle_generate_certificate_signing_request(
|
||||
types::evse_security::LeafCertificateType& certificate_type, std::string& country, std::string& organization,
|
||||
std::string& common, bool& use_tpm) {
|
||||
types::evse_security::GetCertificateSignRequestResult response;
|
||||
|
||||
try {
|
||||
auto csr_response = this->evse_security->generate_certificate_signing_request(
|
||||
conversions::from_everest(certificate_type), country, organization, common, use_tpm);
|
||||
|
||||
response.status = conversions::to_everest(csr_response.status);
|
||||
|
||||
if (csr_response.status == evse_security::GetCertificateSignRequestStatus::Accepted &&
|
||||
csr_response.csr.has_value()) {
|
||||
response.csr = csr_response.csr;
|
||||
}
|
||||
|
||||
return response;
|
||||
} catch (const std::out_of_range& e) {
|
||||
EVLOG_warning << e.what();
|
||||
response.status = types::evse_security::GetCertificateSignRequestStatus::GenerationError;
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
types::evse_security::GetCertificateInfoResult
|
||||
evse_securityImpl::handle_get_leaf_certificate_info(types::evse_security::LeafCertificateType& certificate_type,
|
||||
types::evse_security::EncodingFormat& encoding,
|
||||
bool& include_ocsp) {
|
||||
types::evse_security::GetCertificateInfoResult response;
|
||||
|
||||
try {
|
||||
const auto leaf_info = this->evse_security->get_leaf_certificate_info(
|
||||
conversions::from_everest(certificate_type), conversions::from_everest(encoding), include_ocsp);
|
||||
|
||||
response.status = conversions::to_everest(leaf_info.status);
|
||||
|
||||
if (leaf_info.status == evse_security::GetCertificateInfoStatus::Accepted && leaf_info.info.has_value()) {
|
||||
response.info = conversions::to_everest(leaf_info.info.value());
|
||||
}
|
||||
|
||||
return response;
|
||||
} catch (const std::out_of_range& e) {
|
||||
EVLOG_warning << e.what();
|
||||
response.status = types::evse_security::GetCertificateInfoStatus::Rejected;
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
types::evse_security::GetCertificateFullInfoResult
|
||||
evse_securityImpl::handle_get_all_valid_certificates_info(types::evse_security::LeafCertificateType& certificate_type,
|
||||
types::evse_security::EncodingFormat& encoding,
|
||||
bool& include_ocsp) {
|
||||
types::evse_security::GetCertificateFullInfoResult response;
|
||||
|
||||
try {
|
||||
const auto full_leaf_info = this->evse_security->get_all_valid_certificates_info(
|
||||
conversions::from_everest(certificate_type), conversions::from_everest(encoding), include_ocsp);
|
||||
|
||||
response.status = conversions::to_everest(full_leaf_info.status);
|
||||
|
||||
if (full_leaf_info.status == evse_security::GetCertificateInfoStatus::Accepted) {
|
||||
for (const auto& info : full_leaf_info.info) {
|
||||
response.info.push_back(conversions::to_everest(info));
|
||||
}
|
||||
}
|
||||
|
||||
return response;
|
||||
} catch (const std::out_of_range& e) {
|
||||
EVLOG_warning << e.what();
|
||||
response.status = types::evse_security::GetCertificateInfoStatus::Rejected;
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
std::string evse_securityImpl::handle_get_verify_file(types::evse_security::CaCertificateType& certificate_type) {
|
||||
try {
|
||||
return this->evse_security->get_verify_file(conversions::from_everest(certificate_type));
|
||||
} catch (const std::out_of_range& e) {
|
||||
EVLOG_warning << e.what();
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
std::string evse_securityImpl::handle_get_verify_location(types::evse_security::CaCertificateType& certificate_type) {
|
||||
try {
|
||||
return this->evse_security->get_verify_location(conversions::from_everest(certificate_type));
|
||||
} catch (const std::out_of_range& e) {
|
||||
EVLOG_warning << e.what();
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
int evse_securityImpl::handle_get_leaf_expiry_days_count(types::evse_security::LeafCertificateType& certificate_type) {
|
||||
try {
|
||||
return this->evse_security->get_leaf_expiry_days_count(conversions::from_everest(certificate_type));
|
||||
} catch (const std::out_of_range& e) {
|
||||
EVLOG_warning << e.what();
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
bool evse_securityImpl::handle_verify_file_signature(std::string& file_path, std::string& signing_certificate,
|
||||
std::string& signature) {
|
||||
try {
|
||||
return evse_security::EvseSecurity::verify_file_signature(std::filesystem::path(file_path), signing_certificate,
|
||||
signature);
|
||||
} catch (const std::out_of_range& e) {
|
||||
EVLOG_warning << e.what();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace main
|
||||
} // namespace module
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user