Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reservation all connectors reserved (#958) #972

Merged
merged 20 commits into from
Dec 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
3635ad0
Initial commit.
maaikez Nov 15, 2024
53e59f6
Check reservation status for 'global' reservation and number of evse'…
maaikez Nov 18, 2024
b1dae5e
Check number of evse's and number of reservations and set evse's to r…
maaikez Nov 19, 2024
e744b25
Remove todo's and add mutex
maaikez Nov 20, 2024
1d62f22
Add python tests for reservations, for 1.6 and 2.0.1
maaikez Nov 26, 2024
6d30934
Remove changing connector state for connector id 0 in 1.6 when a rese…
maaikez Nov 27, 2024
d0036c9
Remove todo and change logging minor detail.
maaikez Nov 27, 2024
3392776
Some documentation and removing of logging to start the pull request.
maaikez Nov 27, 2024
45eb331
Avoid race conditions. Add test that evse is going to available again…
maaikez Nov 27, 2024
d06a7e4
Formatting...
maaikez Nov 27, 2024
a857b83
Fix bug where reserved callback was called without checking if it is …
maaikez Nov 28, 2024
a5f42bb
Fix some codacy issues, add documentation.
maaikez Nov 28, 2024
b2eb2b0
Fix test.
maaikez Nov 28, 2024
5a393a0
Set everest utils to correct version.
maaikez Nov 29, 2024
6f32be4
Fix tests.
maaikez Dec 10, 2024
b2a5665
Fix ocpp 16 test as well for raise_error
maaikez Dec 10, 2024
a2bd096
Review comments.
maaikez Dec 11, 2024
5572a16
Add test that covers (current existing) bug in evse manager. Skip tes…
maaikez Dec 12, 2024
6040177
Do not cancel the reservation when it is used from the reservation ha…
maaikez Dec 13, 2024
4314efc
Update libocpp dependency
maaikez Dec 16, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions dependencies.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ libevse-security:
# OCPP
libocpp:
git: https://github.com/EVerest/libocpp.git
git_tag: e7a37da3610e4cbf66dfbc58b9aa98fca2aa6cec
git_tag: e52ac969095804144af4896af1984cedaf45a3f8
cmake_condition: "EVEREST_DEPENDENCY_ENABLED_LIBOCPP"
# Josev
Josev:
Expand All @@ -86,7 +86,7 @@ ext-mbedtls:
# everest-testing and ev-dev-tools
everest-utils:
git: https://github.com/EVerest/everest-utils.git
git_tag: v0.4.3
git_tag: v0.4.4

# unit testing
gtest:
Expand Down
5 changes: 3 additions & 2 deletions interfaces/evse_manager.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,9 @@ cmds:
arguments:
reservation_id:
description: >-
The reservation id (should be added to the TransactionStarted
event)
The reservation id (should be added to the TransactionStarted event). Set this to a negative value if there is
no specific reservation id for this evse but the evse should still move to a Reserved state because of total
global reservations.
type: integer
result:
description: Returns true if the EVSE accepted the reservation, else false.
Expand Down
2 changes: 1 addition & 1 deletion modules/Auth/Auth.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ void Auth::ready() {
[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()) {
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();
}
Expand Down
6 changes: 6 additions & 0 deletions modules/Auth/include/AuthHandler.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,12 @@ class AuthHandler {
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
Expand Down
38 changes: 38 additions & 0 deletions modules/Auth/include/ReservationHandler.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include <map>
#include <mutex>
#include <optional>
#include <set>
#include <vector>

#include <Connector.hpp>
Expand All @@ -15,6 +16,11 @@ 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.
Expand Down Expand Up @@ -45,6 +51,8 @@ class ReservationHandler {
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::io_service::work> work;
/// \brief io_service for the worker for the timers.
Expand Down Expand Up @@ -125,6 +133,14 @@ class ReservationHandler {
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.
Expand Down Expand Up @@ -163,6 +179,15 @@ class ReservationHandler {
///
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.
Expand Down Expand Up @@ -308,6 +333,19 @@ class ReservationHandler {
///
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.
Expand Down
48 changes: 45 additions & 3 deletions modules/Auth/lib/AuthHandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ void AuthHandler::init_evse(const int evse_id, const int evse_index, const std::

void AuthHandler::initialize() {
this->reservation_handler.load_reservations();
check_evse_reserved_and_send_updates();
}

TokenHandlingResult AuthHandler::on_token(const ProvidedIdToken& provided_token) {
Expand Down Expand Up @@ -611,15 +612,20 @@ ReservationCheckStatus AuthHandler::handle_reservation_exists(std::string& id_to
}

bool AuthHandler::call_reserved(const int reservation_id, const std::optional<int>& evse_id) {
return this->reserved_callback(evse_id, reservation_id);
const bool reserved = this->reserved_callback(evse_id, reservation_id);
if (reserved) {
this->check_evse_reserved_and_send_updates();
}

return reserved;
}

void AuthHandler::call_reservation_cancelled(const int32_t reservation_id,
const types::reservation::ReservationEndReason reason,
const std::optional<int>& evse_id, const bool send_reservation_update) {
std::optional<int32_t> evse_index;
if (evse_id.has_value() && evse_id.value() > 0) {
EVLOG_info << "Cancel reservation for evse id" << evse_id.value();
EVLOG_info << "Cancel reservation for evse id " << evse_id.value();
}

this->reservation_cancelled_callback(evse_id, reservation_id, reason, send_reservation_update);
Expand Down Expand Up @@ -659,6 +665,7 @@ void AuthHandler::handle_session_event(const int evse_id, const SessionEvent& ev
std::lock_guard<std::mutex> lk(this->timer_mutex);
this->evses.at(evse_id)->event_mutex.lock();
const auto event_type = event.event;
bool check_reservations = false;

switch (event_type) {
case SessionEventEnum::SessionStarted: {
Expand Down Expand Up @@ -689,6 +696,7 @@ void AuthHandler::handle_session_event(const int evse_id, const SessionEvent& ev
this->evses.at(evse_id)->transaction_active = true;
this->submit_event_for_connector(evse_id, connector_id, ConnectorEvent::TRANSACTION_STARTED);
this->evses.at(evse_id)->timeout_timer.stop();
check_reservations = true;
break;
}
case SessionEventEnum::TransactionFinished:
Expand All @@ -704,18 +712,26 @@ void AuthHandler::handle_session_event(const int evse_id, const SessionEvent& ev
std::lock_guard<std::mutex> lk(this->plug_in_queue_mutex);
this->plug_in_queue.remove_if([evse_id](int value) { return value == evse_id; });
}

check_reservations = true;
break;
}
case SessionEventEnum::Disabled:
this->submit_event_for_connector(evse_id, connector_id, ConnectorEvent::DISABLE);
check_reservations = true;
break;
case SessionEventEnum::Enabled:
this->submit_event_for_connector(evse_id, connector_id, ConnectorEvent::ENABLE);
check_reservations = true;
break;
case SessionEventEnum::ReservationStart:
break;
case SessionEventEnum::ReservationEnd:
case SessionEventEnum::ReservationEnd: {
if (reservation_handler.is_evse_reserved(evse_id)) {
reservation_handler.cancel_reservation(evse_id, true);
}
break;
}
/// explicitly fall through all the SessionEventEnum values we are not handling
case SessionEventEnum::Authorized:
[[fallthrough]];
Expand Down Expand Up @@ -747,6 +763,12 @@ void AuthHandler::handle_session_event(const int evse_id, const SessionEvent& ev
break;
}
this->evses.at(evse_id)->event_mutex.unlock();

// When reservation is started or ended, check if the number of reservations match the number of evses and
// send 'reserved' notifications to the evse manager accordingly if needed.
if (check_reservations) {
check_evse_reserved_and_send_updates();
}
}

void AuthHandler::set_connection_timeout(const int connection_timeout) {
Expand Down Expand Up @@ -821,4 +843,24 @@ void AuthHandler::submit_event_for_connector(const int32_t evse_id, const int32_
}
}

void AuthHandler::check_evse_reserved_and_send_updates() {
ReservationEvseStatus reservation_status =
this->reservation_handler.check_number_global_reservations_match_number_available_evses();
for (const auto& available_evse : reservation_status.available) {
EVLOG_debug << "Evse " << available_evse << " is now available";
this->reservation_cancelled_callback(
available_evse, -1, types::reservation::ReservationEndReason::GlobalReservationRequirementDropped, false);
}

for (const auto& reserved_evse : reservation_status.reserved) {
EVLOG_debug << "Evse " << reserved_evse << " is now reserved";
if (this->reserved_callback != nullptr) {
const bool reserved = this->reserved_callback(reserved_evse, -1);
if (!reserved) {
EVLOG_warning << "Could not reserve " << reserved_evse << " for non evse specific reservations";
}
}
}
}

} // namespace module
90 changes: 88 additions & 2 deletions modules/Auth/lib/ReservationHandler.cpp
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
#include <ReservationHandler.hpp>

#include <algorithm>
#include <iostream>

#include <Connector.hpp>

Expand Down Expand Up @@ -313,6 +312,19 @@ ReservationHandler::cancel_reservation(const int reservation_id, const bool exec
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,
Expand All @@ -322,7 +334,7 @@ void ReservationHandler::register_reservation_cancelled_callback(

void ReservationHandler::on_reservation_used(const int32_t reservation_id) {
const std::pair<bool, std::optional<uint32_t>> cancelled =
this->cancel_reservation(reservation_id, true, types::reservation::ReservationEndReason::UsedToStartCharging);
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()
Expand Down Expand Up @@ -400,6 +412,51 @@ bool ReservationHandler::has_reservation_parent_id(const std::optional<uint32_t>
return false;
}

ReservationEvseStatus ReservationHandler::check_number_global_reservations_match_number_available_evses() {
std::set<int32_t> available_evses;
std::unique_lock<std::recursive_mutex> lock(this->evse_mutex);
// 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);
}
}

std::unique_lock<std::recursive_mutex> reservation_lock(this->reservation_mutex);
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) {
Expand Down Expand Up @@ -744,6 +801,35 @@ void ReservationHandler::store_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) {
Expand Down
Loading