Add extracted tools: CitrineOS, OpenOCPP, ShapeShifter

- CitrineOS core extracted (CSMS OCPP 2.0.1)
- OpenOCPP extracted (firmware OCPP 1.6J/2.0.1)
- ShapeShifter library installed (pip install -e)
- ShapeShifter specification extracted
- EVerest extracted

TODO updated with progress
This commit is contained in:
Eric F
2026-06-08 00:38:27 -04:00
parent 468cfeaa50
commit d398a6ced2
7326 changed files with 1177561 additions and 7 deletions

View File

@@ -0,0 +1 @@
tests.txt

View 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

View 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

View 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,
)

View 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

View File

@@ -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="&lt;mxfile&gt;&lt;diagram id=&quot;4SK5cN6X-azEmNZbr7fQ&quot; name=&quot;Page-1&quot;&gt;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==&lt;/diagram&gt;&lt;/mxfile&gt;">
<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

View 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.

View File

@@ -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="&lt;mxfile&gt;&lt;diagram id=&quot;-amA90pGGpgJD1hGHcMB&quot; name=&quot;Page-1&quot;&gt;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=&lt;/diagram&gt;&lt;/mxfile&gt;">
<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

View 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>

View File

@@ -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="&lt;mxfile&gt;&lt;diagram id=&quot;o8SZXadFU9JfIY1HxVTB&quot; name=&quot;Page-1&quot;&gt;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/&lt;/diagram&gt;&lt;/mxfile&gt;">
<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

View 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_

View 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_

View File

@@ -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_

View File

@@ -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

File diff suppressed because it is too large Load Diff

View 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()

View 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

View File

@@ -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

View 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

View 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

View 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

View 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'

View File

@@ -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

View File

@@ -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

View 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})

View File

@@ -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;
}
};
};

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -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

View 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)

View 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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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;

View File

@@ -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;
};

View 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

View 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

View File

@@ -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

View File

@@ -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

View 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

View 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",
],
),
)

View 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

View File

@@ -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

View File

@@ -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

File diff suppressed because it is too large Load Diff

View 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_

View 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

View 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_

View 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

File diff suppressed because it is too large Load Diff

View 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

View 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

View 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_

View File

@@ -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

View File

@@ -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_

View 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/`

View 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 + "&gt;" + 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, "<", "&lt;");
boost::replace_all(out, ">", "&gt;");
return out;
}
} // namespace module

View 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

View 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

View 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

View 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

View 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

View File

@@ -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

View File

@@ -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

View 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)

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View 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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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})

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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);
}

View File

@@ -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));
}

View File

@@ -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

View File

@@ -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

View File

@@ -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

View 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

View 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

View 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

View File

@@ -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

View File

@@ -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

View 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,
)

View 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

View File

@@ -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

View File

@@ -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

View 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.

View File

@@ -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