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

Feature/890 move reservations callbacks and handlers to new functional block #894

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
9 changes: 4 additions & 5 deletions include/ocpp/v201/charge_point.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

#include <ocpp/common/message_dispatcher.hpp>
#include <ocpp/v201/functional_blocks/data_transfer.hpp>
#include <ocpp/v201/functional_blocks/reservation.hpp>

#include <ocpp/common/charging_station_base.hpp>

Expand Down Expand Up @@ -390,6 +391,7 @@ class ChargePoint : public ChargePointInterface, private ocpp::ChargingStationBa

std::unique_ptr<MessageDispatcherInterface<MessageType>> message_dispatcher;
std::unique_ptr<DataTransferInterface> data_transfer;
std::unique_ptr<ReservationInterface> reservation;

// utility
std::shared_ptr<MessageQueue<v201::MessageType>> message_queue;
Expand Down Expand Up @@ -735,11 +737,6 @@ class ChargePoint : public ChargePointInterface, private ocpp::ChargingStationBa
void handle_change_availability_req(Call<ChangeAvailabilityRequest> call);
void handle_heartbeat_response(CallResult<HeartbeatResponse> call);

// Function Block H: Reservations
void handle_reserve_now_request(Call<ReserveNowRequest> call);
void handle_cancel_reservation_callback(Call<CancelReservationRequest> call);
void send_reserve_now_rejected_response(const MessageId& unique_id, const std::string& status_info);

// Functional Block I: TariffAndCost
void handle_costupdated_req(const Call<CostUpdatedRequest> call);

Expand Down Expand Up @@ -978,6 +975,8 @@ class ChargePoint : public ChargePointInterface, private ocpp::ChargingStationBa

const std::vector<int>& get_network_connection_slots() const override;

void send_not_implemented_error(const MessageId unique_message_id, const MessageTypeId message_type_id);

/// \brief Requests a value of a VariableAttribute specified by combination of \p component_id and \p variable_id
/// from the device model
/// \tparam T datatype of the value that is requested
Expand Down
78 changes: 78 additions & 0 deletions include/ocpp/v201/functional_blocks/reservation.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest

#include <ocpp/v201/message_dispatcher.hpp>
#include <ocpp/v201/message_handler.hpp>
#include <ocpp/v201/messages/CancelReservation.hpp>
#include <ocpp/v201/messages/ReservationStatusUpdate.hpp>
#include <ocpp/v201/messages/ReserveNow.hpp>

#pragma once

namespace ocpp::v201 {

class EvseInterface;
class EvseManagerInterface;

class ReservationInterface : public MessageHandlerInterface {
public:
virtual ~ReservationInterface(){};
virtual void on_reservation_status(const int32_t reservation_id, const ReservationUpdateStatusEnum status) = 0;
virtual ocpp::ReservationCheckStatus
is_evse_reserved_for_other(const EvseInterface& evse, const IdToken& id_token,
const std::optional<IdToken>& group_id_token) const = 0;
virtual void on_reserved(const int32_t evse_id, const int32_t connector_id) = 0;
virtual void on_reservation_cleared(const int32_t evse_id, const int32_t connector_id) = 0;
};

class Reservation : public ReservationInterface {
private: // Members
MessageDispatcherInterface<MessageType>& message_dispatcher;
std::shared_ptr<DeviceModel> device_model;
EvseManagerInterface& evse_manager;

/// \brief Callback function is called when a reservation request is received from the CSMS
std::function<ReserveNowStatusEnum(const ReserveNowRequest& request)> reserve_now_callback;
/// \brief Callback function is called when a cancel reservation request is received from the CSMS
std::function<bool(const int32_t reservationId)> cancel_reservation_callback;
///
/// \brief Check if the current reservation for the given evse id is made for the id token / group id token.
/// \return The reservation check status of this evse / id token.
///
const std::function<ocpp::ReservationCheckStatus(const int32_t evse_id, const CiString<36> idToken,
const std::optional<CiString<36>> groupIdToken)>
is_reservation_for_token_callback;

public:
Reservation(MessageDispatcherInterface<MessageType>& message_dispatcher, std::shared_ptr<DeviceModel> device_model,
EvseManagerInterface& evse_manager,
std::function<ReserveNowStatusEnum(const ReserveNowRequest& request)> reserve_now_callback,
std::function<bool(const int32_t reservationId)> cancel_reservation_callback,
const std::function<ocpp::ReservationCheckStatus(const int32_t evse_id, const CiString<36> idToken,
const std::optional<CiString<36>> groupIdToken)>
is_reservation_for_token_callback);
virtual void handle_message(const ocpp::EnhancedMessage<MessageType>& message) override;

virtual void on_reservation_status(const int32_t reservation_id, const ReservationUpdateStatusEnum status) override;
virtual ocpp::ReservationCheckStatus
is_evse_reserved_for_other(const EvseInterface& evse, const IdToken& id_token,
const std::optional<IdToken>& group_id_token) const override;
virtual void on_reserved(const int32_t evse_id, const int32_t connector_id) override;
virtual void on_reservation_cleared(const int32_t evse_id, const int32_t connector_id) override;

private: // Functions
void handle_reserve_now_request(Call<ReserveNowRequest> call);
void handle_cancel_reservation_callback(Call<CancelReservationRequest> call);

void send_reserve_now_rejected_response(const MessageId& unique_id, const std::string& status_info);

///
/// \brief Check if the connector exists on the given evse id.
/// \param evse_id The evse id to check for.
/// \param connector_type The connector type.
/// \return False if evse id does not exist or evse does not have the given connector type.
///
bool does_connector_exist(const uint32_t evse_id, std::optional<ConnectorEnum> connector_type);
};

} // namespace ocpp::v201
1 change: 1 addition & 0 deletions lib/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ if(LIBOCPP_ENABLE_V201)
ocpp/v201/connectivity_manager.cpp
ocpp/v201/message_dispatcher.cpp
ocpp/v201/functional_blocks/data_transfer.cpp
ocpp/v201/functional_blocks/reservation.cpp
)
add_subdirectory(ocpp/v201/messages)
endif()
Expand Down
192 changes: 34 additions & 158 deletions lib/ocpp/v201/charge_point.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -737,11 +737,15 @@ void ChargePoint::on_fault_cleared(const int32_t evse_id, const int32_t connecto
}

void ChargePoint::on_reserved(const int32_t evse_id, const int32_t connector_id) {
this->evse_manager->get_evse(evse_id).submit_event(connector_id, ConnectorEvent::Reserve);
if (this->reservation != nullptr) {
this->reservation->on_reserved(evse_id, connector_id);
}
}

void ChargePoint::on_reservation_cleared(const int32_t evse_id, const int32_t connector_id) {
this->evse_manager->get_evse(evse_id).submit_event(connector_id, ConnectorEvent::ReservationCleared);
if (this->reservation != nullptr) {
this->reservation->on_reservation_cleared(evse_id, connector_id);
}
}

bool ChargePoint::on_charging_state_changed(const uint32_t evse_id, const ChargingStateEnum charging_state,
Expand Down Expand Up @@ -1057,12 +1061,9 @@ void ChargePoint::on_variable_changed(const SetVariableData& set_variable_data)
}

void ChargePoint::on_reservation_status(const int32_t reservation_id, const ReservationUpdateStatusEnum status) {
ReservationStatusUpdateRequest req;
req.reservationId = reservation_id;
req.reservationUpdateStatus = status;

ocpp::Call<ReservationStatusUpdateRequest> call(req);
this->message_dispatcher->dispatch_call(call);
if (reservation != nullptr) {
this->reservation->on_reservation_status(reservation_id, status);
}
}

void ChargePoint::initialize(const std::map<int32_t, int32_t>& evse_connector_structure,
Expand Down Expand Up @@ -1185,6 +1186,13 @@ void ChargePoint::initialize(const std::map<int32_t, int32_t>& evse_connector_st
this->data_transfer = std::make_unique<DataTransfer>(
*this->message_dispatcher, this->callbacks.data_transfer_callback, DEFAULT_WAIT_FOR_FUTURE_TIMEOUT);

if (device_model->get_optional_value<bool>(ControllerComponentVariables::ReservationCtrlrAvailable)
.value_or(false)) {
this->reservation = std::make_unique<Reservation>(
*this->message_dispatcher, device_model, *this->evse_manager, this->callbacks.reserve_now_callback.value(),
this->callbacks.cancel_reservation_callback.value(), this->callbacks.is_reservation_for_token_callback);
}

if (this->callbacks.configure_network_connection_profile_callback.has_value()) {
this->connectivity_manager->set_configure_network_connection_profile_callback(
this->callbacks.configure_network_connection_profile_callback.value());
Expand Down Expand Up @@ -1281,10 +1289,12 @@ void ChargePoint::handle_message(const EnhancedMessage<v201::MessageType>& messa
this->handle_heartbeat_response(json_message);
break;
case MessageType::ReserveNow:
this->handle_reserve_now_request(json_message);
break;
case MessageType::CancelReservation:
this->handle_cancel_reservation_callback(json_message);
if (this->reservation != nullptr) {
this->reservation->handle_message(message);
} else {
send_not_implemented_error(message.uniqueId, message.messageTypeId);
}
break;
case MessageType::SendLocalList:
this->handle_send_local_authorization_list_req(json_message);
Expand Down Expand Up @@ -1350,18 +1360,12 @@ void ChargePoint::handle_message(const EnhancedMessage<v201::MessageType>& messa
this->handle_costupdated_req(json_message);
break;
default:
if (message.messageTypeId == MessageTypeId::CALL) {
const auto call_error = CallError(message.uniqueId, "NotImplemented", "", json({}));
this->message_dispatcher->dispatch_call_error(call_error);
}
send_not_implemented_error(message.uniqueId, message.messageTypeId);
break;
}
} catch (const MessageTypeNotImplementedException& e) {
EVLOG_warning << e.what();
if (message.messageTypeId == MessageTypeId::CALL) {
const auto call_error = CallError(message.uniqueId, "NotImplemented", "", json({}));
this->message_dispatcher->dispatch_call_error(call_error);
}
send_not_implemented_error(message.uniqueId, message.messageTypeId);
}
}

Expand Down Expand Up @@ -1930,10 +1934,11 @@ std::optional<int32_t> ChargePoint::get_transaction_evseid(const CiString<36>& t
ocpp::ReservationCheckStatus
ChargePoint::is_evse_reserved_for_other(EvseInterface& evse, const IdToken& id_token,
const std::optional<IdToken>& group_id_token) const {
const std::optional<CiString<36>> groupIdToken =
group_id_token.has_value() ? group_id_token.value().idToken : std::optional<CiString<36>>{};
if (this->reservation != nullptr) {
return this->reservation->is_evse_reserved_for_other(evse, id_token, group_id_token);
}

return callbacks.is_reservation_for_token_callback(evse.get_id(), id_token.idToken, groupIdToken);
return ReservationCheckStatus::NotReserved;
}

bool ChargePoint::is_evse_connector_available(EvseInterface& evse) const {
Expand Down Expand Up @@ -3312,142 +3317,6 @@ void ChargePoint::handle_heartbeat_response(CallResult<HeartbeatResponse> call)
}
}

void ChargePoint::handle_reserve_now_request(Call<ReserveNowRequest> call) {
ReserveNowResponse response;
response.status = ReserveNowStatusEnum::Rejected;
bool reservation_available = true;

std::string status_info;

if (!this->callbacks.reserve_now_callback.has_value()) {
reservation_available = false;
status_info = "Reservation is not implemented";
} else if (!this->device_model->get_optional_value<bool>(ControllerComponentVariables::ReservationCtrlrAvailable)
.value_or(false)) {
status_info = "Reservation is not available";
reservation_available = false;
} else if (!this->device_model->get_optional_value<bool>(ControllerComponentVariables::ReservationCtrlrEnabled)) {
reservation_available = false;
status_info = "Reservation is not enabled";
}

if (!reservation_available) {
// Reservation not available / implemented, return 'Rejected'.
// H01.FR.01
EVLOG_info << "Receiving a reservation request, but reservation is not enabled or implemented.";
send_reserve_now_rejected_response(call.uniqueId, status_info);
return;
}

// Check if we need a specific evse id during a reservation and if that is the case, if we recevied an evse id.
const ReserveNowRequest request = call.msg;
if (!request.evseId.has_value() &&
!this->device_model->get_optional_value<bool>(ControllerComponentVariables::ReservationCtrlrNonEvseSpecific)
.value_or(false)) {
// H01.FR.19
EVLOG_warning << "Trying to make a reservation, but no evse id was given while it should be sent in the "
"request when NonEvseSpecific is disabled.";
send_reserve_now_rejected_response(
call.uniqueId,
"No evse id was given while it should be sent in the request when NonEvseSpecific is disabled");
return;
}

const std::optional<int32_t> evse_id = request.evseId;

if (evse_id.has_value()) {
if (evse_id <= 0 || !evse_manager->does_evse_exist(evse_id.value())) {
EVLOG_error << "Trying to make a reservation, but evse " << evse_id.value() << " is not a valid evse id.";
send_reserve_now_rejected_response(call.uniqueId, "Evse id does not exist");
return;
}

// Check if there is a connector available for this evse id.
if (!does_connector_exist(static_cast<uint32_t>(evse_id.value()), request.connectorType)) {
EVLOG_info << "Trying to make a reservation for connector type "
<< conversions::connector_enum_to_string(request.connectorType.value_or(ConnectorEnum::Unknown))
<< " for evse " << evse_id.value() << ", but this connector type does not exist.";
send_reserve_now_rejected_response(call.uniqueId, "Connector type does not exist");
return;
}
} else {
// No evse id. Just search for all evse's if there is something available for reservation
const uint64_t number_of_evses = evse_manager->get_number_of_evses();
if (number_of_evses <= 0) {
send_reserve_now_rejected_response(call.uniqueId, "No evse's found in charging station");
EVLOG_error << "Trying to make a reservation, but number of evse's is 0";
return;
}

bool connector_exists = false;
for (uint64_t i = 1; i <= number_of_evses; i++) {
if (this->does_connector_exist(i, request.connectorType)) {
connector_exists = true;
}

if (this->is_connector_available(i, request.connectorType)) {
// There is at least one connector available!
break;
}
}

if (!connector_exists) {
send_reserve_now_rejected_response(call.uniqueId, "Could not get status info from connector");
return;
}
}

// Connector exists and might or might not be available, but if the reservation id is already existing, reservation
// should be overwritten.

// Call reserve now callback and wait for the response.
const ReserveNowRequest reservation_request = call.msg;
response.status = this->callbacks.reserve_now_callback.value()(reservation_request);

// Reply with the response from the callback.
const ocpp::CallResult<ReserveNowResponse> call_result(response, call.uniqueId);
this->message_dispatcher->dispatch_call_result(call_result);

if (response.status == ReserveNowStatusEnum::Accepted) {
EVLOG_debug << "Reservation with id " << reservation_request.id << " for "
<< (reservation_request.evseId.has_value()
? " evse_id: " + std::to_string(reservation_request.evseId.value())
: "")
<< " is accepted";
}
}

void ChargePoint::handle_cancel_reservation_callback(Call<CancelReservationRequest> call) {

CancelReservationResponse response;
if (!this->callbacks.cancel_reservation_callback.has_value() ||
!this->device_model->get_optional_value<bool>(ControllerComponentVariables::ReservationCtrlrAvailable)
.value_or(false) ||
!this->device_model->get_optional_value<bool>(ControllerComponentVariables::ReservationCtrlrEnabled)
.value_or(false)) {
// Reservation not available / implemented, return 'Rejected'.
// H01.FR.01
EVLOG_info << "Receiving a cancel reservation request, but reservation is not implemented.";
response.status = CancelReservationStatusEnum::Rejected;
} else {
response.status = (this->callbacks.cancel_reservation_callback.value()(call.msg.reservationId)
? CancelReservationStatusEnum::Accepted
: CancelReservationStatusEnum::Rejected);
}

const ocpp::CallResult<CancelReservationResponse> call_result(response, call.uniqueId);
this->message_dispatcher->dispatch_call_result(call_result);
}

void ChargePoint::send_reserve_now_rejected_response(const MessageId& unique_id, const std::string& status_info) {
ReserveNowResponse response;
response.status = ReserveNowStatusEnum::Rejected;
response.statusInfo = StatusInfo();
response.statusInfo->additionalInfo = status_info;
const ocpp::CallResult<ReserveNowResponse> call_result(response, unique_id);
this->message_dispatcher->dispatch_call_result(call_result);
}

void ChargePoint::handle_costupdated_req(const Call<CostUpdatedRequest> call) {
CostUpdatedResponse response;
ocpp::CallResult<CostUpdatedResponse> call_result(response, call.uniqueId);
Expand Down Expand Up @@ -4666,6 +4535,13 @@ const std::vector<int>& ChargePoint::get_network_connection_slots() const {
return this->connectivity_manager->get_network_connection_slots();
}

void ChargePoint::send_not_implemented_error(const MessageId unique_message_id, const MessageTypeId message_type_id) {
if (message_type_id == MessageTypeId::CALL) {
const auto call_error = CallError(unique_message_id, "NotImplemented", "", json({}));
this->message_dispatcher->dispatch_call_error(call_error);
}
}

// Static functions

///
Expand Down
Loading