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