#include #include #include #include #include #include #include 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>& 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::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 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(&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 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 evse_id, const types::reservation::Reservation& reservation) { std::lock_guard 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> 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 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 lk(this->event_mutex); if (connector_state == ConnectorState::AVAILABLE) { // Nothing to cancel. return; } if (this->evses.count(static_cast(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 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 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 lk(this->event_mutex); if (this->evse_reservations.count(evse_id) > 0) { return true; } return false; } std::pair> ReservationHandler::cancel_reservation(const int reservation_id, const bool execute_callback, const types::reservation::ReservationEndReason reason) { std::lock_guard lk(this->event_mutex); std::pair> 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 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& 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 lk(this->event_mutex); const std::pair> 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 ReservationHandler::matches_reserved_identifier(const std::string& id_token, const std::optional evse_id, std::optional parent_id_token) { std::lock_guard 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 evse_id) { std::lock_guard 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 reservation_lock(this->event_mutex); std::set 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(evse.first), this->evse_reservations) == types::reservation::ReservationResult::Accepted && get_connector_availability_reservation_result(static_cast(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 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& 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& 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> ReservationHandler::get_all_possible_orders( const std::vector& connectors) const { std::vector input_next = connectors; std::vector input_prev = connectors; std::vector> 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& used_evse_ids, const std::vector& next_car_arrival_order, const std::map& 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 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 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 global_reservation_type, const std::vector& reservations_no_evse, const std::map& evse_specific_reservations) { std::vector 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> 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 evse_id) { std::lock_guard lk(this->event_mutex); this->reservation_id_to_reservation_timeout_timer_map[reservation.reservation_id] = std::make_unique(&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 ReservationHandler::get_all_evses_with_connector_type( const types::evse_manager::ConnectorTypeEnum connector_type) const { std::vector 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 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 reservations_to_cancel; std::map evse_specific_reservations; std::vector 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& currently_available_evses, const std::set& reserved_evses) { ReservationEvseStatus evse_status_to_send; std::set 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 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