From dfb964bff8e8d6fe0e48646f14050eab65dbaa4c Mon Sep 17 00:00:00 2001 From: "Maaike Zijderveld, iolar" Date: Mon, 16 Sep 2024 18:40:31 +0200 Subject: [PATCH 01/24] Start reservation implementation. Signed-off-by: Maaike Zijderveld, iolar --- include/ocpp/v201/charge_point.hpp | 33 ++++ lib/ocpp/v201/charge_point.cpp | 240 ++++++++++++++++++++++++++++- 2 files changed, 270 insertions(+), 3 deletions(-) diff --git a/include/ocpp/v201/charge_point.hpp b/include/ocpp/v201/charge_point.hpp index 28027e714..e7d9dfe16 100644 --- a/include/ocpp/v201/charge_point.hpp +++ b/include/ocpp/v201/charge_point.hpp @@ -27,6 +27,7 @@ #include "ocpp/v201/messages/Get15118EVCertificate.hpp" #include #include +#include #include #include #include @@ -55,6 +56,7 @@ #include #include #include +#include #include #include #include @@ -196,6 +198,12 @@ struct Callbacks { /// \brief Callback function is called when the websocket connection status changes std::optional> connection_state_changed_callback; + + /// \brief Callback function is called when a reservation request is received from the CSMS + std::optional> reserve_now_callback; + /// \brief Callback function is called when a cancel reservation request is received from the CSMS + std::optional> + cancel_reservation_callback; }; /// \brief Combines ChangeAvailabilityRequest with persist flag for scheduled Availability changes @@ -629,6 +637,27 @@ class ChargePoint : public ChargePointInterface, private ocpp::ChargingStationBa /// void set_evse_connectors_unavailable(EvseInterface& evse, bool persist); + /// + /// \brief Get connector type of Connector + /// \param evse_id EVSE id + /// \param connector_id Connector id + /// \return The connector type. If evse or connector id is not correct: std::nullopt. + /// + std::optional get_evse_connector_type(const uint32_t evse_id, const uint32_t connector_id); + + /// + /// \brief Get connector status. + /// + /// This will search if there is a connector on this evse with status 'Available'. It will search through all + /// connectors and return on the first connector that is 'Available'. + /// + /// \param evse_id The evse id. + /// \param connector_type The connector type. + /// \return std::nullopt if connector type is given, but does not exist. Otherwise the connector status. + /// + std::optional get_available_connector_or_status(const uint32_t evse_id, + std::optional connector_type); + /// \brief Get the value optional offline flag /// \return true if the charge point is offline. std::nullopt if it is online; bool is_offline(); @@ -742,6 +771,10 @@ class ChargePoint : public ChargePointInterface, private ocpp::ChargingStationBa void handle_change_availability_req(Call call); void handle_heartbeat_response(CallResult call); + // Function Block H: Reservations + void handle_reserve_now_request(Call call); + void handle_cancel_reservation_callback(Call call); + // Functional Block K: Smart Charging void handle_set_charging_profile_req(Call call); void handle_clear_charging_profile_req(Call call); diff --git a/lib/ocpp/v201/charge_point.cpp b/lib/ocpp/v201/charge_point.cpp index ef7972fe5..87d58c433 100644 --- a/lib/ocpp/v201/charge_point.cpp +++ b/lib/ocpp/v201/charge_point.cpp @@ -1164,6 +1164,12 @@ void ChargePoint::handle_message(const EnhancedMessage& messa case MessageType::HeartbeatResponse: 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); + break; case MessageType::SendLocalList: this->handle_send_local_authorization_list_req(json_message); break; @@ -1809,6 +1815,88 @@ void ChargePoint::set_evse_connectors_unavailable(EvseInterface& evse, bool pers } } +std::optional ChargePoint::get_evse_connector_type(const uint32_t evse_id, const uint32_t connector_id) { + // TODO mz add logging??? + EvseInterface* evse = nullptr; + try { + evse = &this->evse_manager->get_evse(static_cast(evse_id)); + } catch (EvseOutOfRangeException&) { + return std::nullopt; + } + + auto connector = evse->get_connector(static_cast(connector_id)); + if (connector == nullptr) { + return std::nullopt; + } + + ComponentVariable connector_cv = + ConnectorComponentVariables::get_component_variable(evse_id, connector_id, ConnectorComponentVariables::Type); + + const std::optional connector_type = + this->device_model->get_optional_value(connector_cv, AttributeEnum::Actual); + if (!connector_type.has_value()) { + return std::nullopt; + } + + return conversions::string_to_connector_enum(connector_type.value()); +} + +std::optional +ChargePoint::get_available_connector_or_status(const uint32_t evse_id, std::optional connector_type) { + EvseInterface* evse; + try { + evse = &evse_manager->get_evse(static_cast(evse_id)); + } catch (const EvseOutOfRangeException&) { + EVLOG_error << "Evse id " << evse_id << " is not a valid evse id."; + return std::nullopt; + } + + bool type_found = false; + ConnectorStatusEnum found_status = ConnectorStatusEnum::Unavailable; + const uint32_t number_of_connectors = evse->get_number_of_connectors(); + if (number_of_connectors == 0) { + return std::nullopt; + } + + for (uint32_t i = 1; i <= number_of_connectors; ++i) { + Connector* connector; + try { + connector = evse->get_connector(static_cast(i)); + } catch (const std::logic_error&) { + EVLOG_error << "Reserve now request: Connector with id " << i << " does not exist"; + } + + if (connector == nullptr) { + EVLOG_error << "Reserve now request: Connector with id " << i << " does not exist"; + continue; + } + + ConnectorStatusEnum connector_status = connector->get_effective_connector_status(); + + std::optional evse_connector_type = + this->get_evse_connector_type(static_cast(evse_id), i); + if (!connector_type.has_value() || (evse_connector_type.value() == connector_type.value())) { + type_found = true; + // We found an available connector, also store the status. + found_status = connector_status; + if (found_status == ConnectorStatusEnum::Available) { + // There is an available connector with the correct type and status: we don't have to search + // any further. + return found_status; + } + + // If status is not available, we keep on searching. If no connector is available, the status of + // (at least one of) the connectors is stored to return that later if no available connector is found. + } + } + + if (!type_found) { + return std::nullopt; + } + + return found_status; +} + bool ChargePoint::is_offline() { return !this->connectivity_manager->is_websocket_connected(); } @@ -3080,6 +3168,151 @@ void ChargePoint::handle_heartbeat_response(CallResult call) } } +void ChargePoint::handle_reserve_now_request(Call call) { + // TODO mz add FR's from specification + + ReserveNowResponse response; + response.status = ReserveNowStatusEnum::Rejected; + if (!this->callbacks.reserve_now_callback.has_value() || + !this->device_model->get_optional_value(ControllerComponentVariables::ReservationCtrlrAvailable) + .value_or(false) || + !this->device_model->get_optional_value(ControllerComponentVariables::ReservationCtrlrEnabled) + .value_or(false)) { + // Reservation not available / implemented, return 'Rejected'. + // H01.FR.01 + EVLOG_info << "Receiving a reservation request, but reservation is not implemented."; + const ocpp::CallResult call_result(response, call.uniqueId); + this->send(call_result); + 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(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."; + const ocpp::CallResult call_result(response, call.uniqueId); + this->send(call_result); + return; + } + + const std::optional evse_id = request.evseId.value(); + + ConnectorStatusEnum connector_status = ConnectorStatusEnum::Unavailable; + + if (evse_id.has_value()) { + if (evse_id.value() < 0 || !evse_manager->does_evse_exist(evse_id.value())) { + EVLOG_error << "Trying to make a reservation, but evse " << evse_id << " is not a valid evse id."; + this->send(ocpp::CallResult(response, call.uniqueId)); + return; + } + + // Check if there is a connector available for this evse id. + std::optional status = + get_available_connector_or_status(static_cast(evse_id.value()), request.connectorType); + + if (!status.has_value()) { + EVLOG_info << "Trying to make a reservation for connector type " + << conversions::connector_enum_to_string(request.connectorType.value()) << " for evse " + << evse_id << ", but this connector type does not exist."; + this->send(ocpp::CallResult(response, call.uniqueId)); + return; + } else { + connector_status = status.value(); + } + } 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) { + // TODO mz send 'rejected' and log error!!!! + EVLOG_error << "Trying to make a reservation, but number of evse's is 0"; + this->send(ocpp::CallResult(response, call.uniqueId)); + return; + } + + bool status_found = false; + for (uint64_t i = 1; i <= number_of_evses; i++) { + std::optional status = get_available_connector_or_status(i, request.connectorType); + if (status.has_value()) { + status_found = true; + connector_status = status.value(); + if (connector_status == ConnectorStatusEnum::Available) { + // There is at least one connector available! + break; + } + } + } + + if (!status_found) { + // TODO mz what to do here? All 'get_available_connector_or_status' returned nothing... + EVLOG_info << "Trying to make a reservation but could not get connector status."; + this->send(ocpp::CallResult(response, call.uniqueId)); + return; + } + } + + if (connector_status != ConnectorStatusEnum::Available) { + switch (connector_status) { + case ConnectorStatusEnum::Occupied: + case ConnectorStatusEnum::Reserved: + // H01.FR.11 + response.status = ReserveNowStatusEnum::Occupied; + break; + case ConnectorStatusEnum::Unavailable: + // H01.FR.14 + response.status = ReserveNowStatusEnum::Unavailable; + break; + case ConnectorStatusEnum::Faulted: + // H01.FR.12 + response.status = ReserveNowStatusEnum::Faulted; + break; + case ConnectorStatusEnum::Available: + // This can not happen, see check above, but this will silence compiler warnings. + break; + } + + this->send(ocpp::CallResult(response, call.uniqueId)); + return; + } + + // Connector status is available!! + + // Call reserve now callback and wait for the response. + response = this->callbacks.reserve_now_callback.value()(call.msg); + + // Reply with the response fromt he callback. + const ocpp::CallResult call_result(response, call.uniqueId); + this->send(call_result); + + if (response.status == ReserveNowStatusEnum::Accepted) { + // TODO mz send status notification request with connector status `Reserved`??? + // TODO mz set a status of the connector somewhere??? + } +} + +void ChargePoint::handle_cancel_reservation_callback(Call call) { + + CancelReservationResponse response; + if (!this->callbacks.cancel_reservation_callback.has_value() || + !this->device_model->get_optional_value(ControllerComponentVariables::ReservationCtrlrAvailable) + .value_or(false) || + !this->device_model->get_optional_value(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 = this->callbacks.cancel_reservation_callback.value()(call.msg); + } + + const ocpp::CallResult call_result(response, call.uniqueId); + this->send(call_result); +} + void ChargePoint::handle_set_charging_profile_req(Call call) { EVLOG_debug << "Received SetChargingProfileRequest: " << call.msg << "\nwith messageId: " << call.uniqueId; auto msg = call.msg; @@ -3170,9 +3403,10 @@ void ChargePoint::handle_get_charging_profiles_req(Call evse_ids; // will contain all evse_ids of the profiles std::set sources; // will contain all sources of the profiles From 92c75ac2c912d2fd59322adf750e591fc91086ee Mon Sep 17 00:00:00 2001 From: "Maaike Zijderveld, iolar" Date: Tue, 17 Sep 2024 18:18:08 +0200 Subject: [PATCH 02/24] Some changes to the reserve now callback. Signed-off-by: Maaike Zijderveld, iolar --- include/ocpp/v201/charge_point.hpp | 9 ++++++--- lib/ocpp/v201/charge_point.cpp | 13 +++++++++---- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/include/ocpp/v201/charge_point.hpp b/include/ocpp/v201/charge_point.hpp index e7d9dfe16..4187f1c0e 100644 --- a/include/ocpp/v201/charge_point.hpp +++ b/include/ocpp/v201/charge_point.hpp @@ -200,10 +200,13 @@ struct Callbacks { std::optional> connection_state_changed_callback; /// \brief Callback function is called when a reservation request is received from the CSMS - std::optional> reserve_now_callback; + std::optional connector_type, const std::optional evse_id, + const std::optional& group_id_token)>> + reserve_now_callback; /// \brief Callback function is called when a cancel reservation request is received from the CSMS - std::optional> - cancel_reservation_callback; + std::optional> cancel_reservation_callback; }; /// \brief Combines ChangeAvailabilityRequest with persist flag for scheduled Availability changes diff --git a/lib/ocpp/v201/charge_point.cpp b/lib/ocpp/v201/charge_point.cpp index 87d58c433..38b860ebc 100644 --- a/lib/ocpp/v201/charge_point.cpp +++ b/lib/ocpp/v201/charge_point.cpp @@ -3205,7 +3205,7 @@ void ChargePoint::handle_reserve_now_request(Call call) { if (evse_id.has_value()) { if (evse_id.value() < 0 || !evse_manager->does_evse_exist(evse_id.value())) { - EVLOG_error << "Trying to make a reservation, but evse " << evse_id << " is not a valid evse id."; + EVLOG_error << "Trying to make a reservation, but evse " << evse_id.value() << " is not a valid evse id."; this->send(ocpp::CallResult(response, call.uniqueId)); return; } @@ -3217,7 +3217,7 @@ void ChargePoint::handle_reserve_now_request(Call call) { if (!status.has_value()) { EVLOG_info << "Trying to make a reservation for connector type " << conversions::connector_enum_to_string(request.connectorType.value()) << " for evse " - << evse_id << ", but this connector type does not exist."; + << evse_id.value() << ", but this connector type does not exist."; this->send(ocpp::CallResult(response, call.uniqueId)); return; } else { @@ -3281,7 +3281,10 @@ void ChargePoint::handle_reserve_now_request(Call call) { // Connector status is available!! // Call reserve now callback and wait for the response. - response = this->callbacks.reserve_now_callback.value()(call.msg); + ReserveNowRequest reservation_request = call.msg; + response.status = this->callbacks.reserve_now_callback.value()( + reservation_request.id, reservation_request.expiryDateTime, reservation_request.idToken, + reservation_request.connectorType, reservation_request.evseId, reservation_request.groupIdToken); // Reply with the response fromt he callback. const ocpp::CallResult call_result(response, call.uniqueId); @@ -3306,7 +3309,9 @@ void ChargePoint::handle_cancel_reservation_callback(Callcallbacks.cancel_reservation_callback.value()(call.msg); + response.status = (this->callbacks.cancel_reservation_callback.value()(call.msg.reservationId) + ? CancelReservationStatusEnum::Accepted + : CancelReservationStatusEnum::Rejected); } const ocpp::CallResult call_result(response, call.uniqueId); From 218a1d58ea699394414799d081f1a1c4b567aec4 Mon Sep 17 00:00:00 2001 From: "Maaike Zijderveld, iolar" Date: Mon, 23 Sep 2024 18:26:40 +0200 Subject: [PATCH 03/24] Add todo's Signed-off-by: Maaike Zijderveld, iolar --- lib/ocpp/v201/charge_point.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/ocpp/v201/charge_point.cpp b/lib/ocpp/v201/charge_point.cpp index 38b860ebc..1728ce28b 100644 --- a/lib/ocpp/v201/charge_point.cpp +++ b/lib/ocpp/v201/charge_point.cpp @@ -1782,6 +1782,9 @@ bool ChargePoint::is_evse_reserved_for_other(EvseInterface& evse, const IdToken& } } } + + // TODO mz also check here if there is a reservation for a token and no evse id. + return false; } @@ -3169,8 +3172,6 @@ void ChargePoint::handle_heartbeat_response(CallResult call) } void ChargePoint::handle_reserve_now_request(Call call) { - // TODO mz add FR's from specification - ReserveNowResponse response; response.status = ReserveNowStatusEnum::Rejected; if (!this->callbacks.reserve_now_callback.has_value() || @@ -3227,7 +3228,6 @@ void ChargePoint::handle_reserve_now_request(Call call) { // 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) { - // TODO mz send 'rejected' and log error!!!! EVLOG_error << "Trying to make a reservation, but number of evse's is 0"; this->send(ocpp::CallResult(response, call.uniqueId)); return; @@ -3278,6 +3278,9 @@ void ChargePoint::handle_reserve_now_request(Call call) { return; } + // TODO mz also when connector is not available, check if reservation id exists and if it does, it should overwrite + // the reservation + // Connector status is available!! // Call reserve now callback and wait for the response. From d47b00953d5b06a0de1e59fdbbf98745870f4326 Mon Sep 17 00:00:00 2001 From: "Maaike Zijderveld, iolar" Date: Thu, 26 Sep 2024 17:58:25 +0200 Subject: [PATCH 04/24] Add todo. Signed-off-by: Maaike Zijderveld, iolar --- lib/ocpp/v201/charge_point.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/ocpp/v201/charge_point.cpp b/lib/ocpp/v201/charge_point.cpp index 6853e4994..6e1e4891a 100644 --- a/lib/ocpp/v201/charge_point.cpp +++ b/lib/ocpp/v201/charge_point.cpp @@ -1948,6 +1948,7 @@ bool ChargePoint::is_evse_reserved_for_other(EvseInterface& evse, const IdToken& } } + // TODO mz also check if connector status is not reserved, as there can be a reservation for evse id 0. // TODO mz also check here if there is a reservation for a token and no evse id. return false; From d310f0223ce849715ba27183744f36bf1c651f63 Mon Sep 17 00:00:00 2001 From: "Maaike Zijderveld, iolar" Date: Wed, 30 Oct 2024 19:11:58 +0100 Subject: [PATCH 05/24] Add reservation callbacks and handlers and fix some bugs. Signed-off-by: Maaike Zijderveld, iolar --- .../standardized/ReservationCtrlr.json | 3 +- include/ocpp/v201/charge_point.hpp | 10 ++++ lib/ocpp/v201/charge_point.cpp | 53 ++++++++----------- 3 files changed, 34 insertions(+), 32 deletions(-) diff --git a/config/v201/component_config/standardized/ReservationCtrlr.json b/config/v201/component_config/standardized/ReservationCtrlr.json index 940b2d36c..b887d8545 100644 --- a/config/v201/component_config/standardized/ReservationCtrlr.json +++ b/config/v201/component_config/standardized/ReservationCtrlr.json @@ -45,7 +45,8 @@ "attributes": [ { "type": "Actual", - "mutability": "ReadOnly" + "mutability": "ReadOnly", + "value": true } ], "description": "If this configuration variable is present and set to true: Charging Station supports Reservation where EVSE id is not specified.", diff --git a/include/ocpp/v201/charge_point.hpp b/include/ocpp/v201/charge_point.hpp index 24bc15bdc..f54f38ae2 100644 --- a/include/ocpp/v201/charge_point.hpp +++ b/include/ocpp/v201/charge_point.hpp @@ -197,6 +197,11 @@ class ChargePointInterface { /// \param connector_id Reserved connector id virtual void on_reserved(const int32_t evse_id, const int32_t connector_id) = 0; + /// \brief Event handler that should be called when the reservation of the connector is cleared. + /// \param evse_id Cleared EVSE id + /// \param connector_id Cleared connector id. + virtual void on_reservation_cleared(const int32_t evse_id, const int32_t connector_id) = 0; + /// \brief Event handler that will update the charging state internally when it has been changed. /// \param evse_id The evse id of which the charging state has changed. /// \param charging_state The new charging state. @@ -251,6 +256,11 @@ class ChargePointInterface { /// virtual void on_variable_changed(const SetVariableData& set_variable_data) = 0; + /// \brief Event handler that will send a ReservationStatusUpdate request. + /// \param reservation_id The reservation id. + /// \param status The status. + virtual void on_reservation_status(const int32_t reservation_id, const ReservationUpdateStatusEnum status) = 0; + /// \brief Data transfer mechanism initiated by charger /// \param vendorId /// \param messageId diff --git a/lib/ocpp/v201/charge_point.cpp b/lib/ocpp/v201/charge_point.cpp index 4173bdc63..69b641849 100644 --- a/lib/ocpp/v201/charge_point.cpp +++ b/lib/ocpp/v201/charge_point.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -763,6 +764,10 @@ 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); } +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); +} + bool ChargePoint::on_charging_state_changed(const uint32_t evse_id, const ChargingStateEnum charging_state, const TriggerReasonEnum trigger_reason) { auto& evse = this->evse_manager->get_evse(evse_id); @@ -1075,6 +1080,15 @@ void ChargePoint::on_variable_changed(const SetVariableData& set_variable_data) this->handle_variable_changed(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 call(req, this->message_queue->createMessageId()); + this->send(call); +} + bool ChargePoint::send(CallError call_error) { this->message_queue->push(call_error); return true; @@ -2040,7 +2054,8 @@ ChargePoint::get_available_connector_or_status(const uint32_t evse_id, std::opti std::optional evse_connector_type = this->get_evse_connector_type(static_cast(evse_id), i); - if (!connector_type.has_value() || (evse_connector_type.value() == connector_type.value())) { + if (!connector_type.has_value() || + (!evse_connector_type.has_value() || evse_connector_type.value() == connector_type.value())) { type_found = true; // We found an available connector, also store the status. found_status = connector_status; @@ -3405,7 +3420,7 @@ void ChargePoint::handle_reserve_now_request(Call call) { return; } - const std::optional evse_id = request.evseId.value(); + const std::optional evse_id = request.evseId; ConnectorStatusEnum connector_status = ConnectorStatusEnum::Unavailable; @@ -3422,8 +3437,8 @@ void ChargePoint::handle_reserve_now_request(Call call) { if (!status.has_value()) { EVLOG_info << "Trying to make a reservation for connector type " - << conversions::connector_enum_to_string(request.connectorType.value()) << " for evse " - << evse_id.value() << ", but this connector type does not exist."; + << conversions::connector_enum_to_string(request.connectorType.value_or(ConnectorEnum::Unknown)) + << " for evse " << evse_id.value() << ", but this connector type does not exist."; this->send(ocpp::CallResult(response, call.uniqueId)); return; } else { @@ -3457,36 +3472,12 @@ void ChargePoint::handle_reserve_now_request(Call call) { this->send(ocpp::CallResult(response, call.uniqueId)); return; } - } - if (connector_status != ConnectorStatusEnum::Available) { - switch (connector_status) { - case ConnectorStatusEnum::Occupied: - case ConnectorStatusEnum::Reserved: - // H01.FR.11 - response.status = ReserveNowStatusEnum::Occupied; - break; - case ConnectorStatusEnum::Unavailable: - // H01.FR.14 - response.status = ReserveNowStatusEnum::Unavailable; - break; - case ConnectorStatusEnum::Faulted: - // H01.FR.12 - response.status = ReserveNowStatusEnum::Faulted; - break; - case ConnectorStatusEnum::Available: - // This can not happen, see check above, but this will silence compiler warnings. - break; - } - - this->send(ocpp::CallResult(response, call.uniqueId)); - return; + EVLOG_info << "Handle reserve now request 7"; } - // TODO mz also when connector is not available, check if reservation id exists and if it does, it should overwrite - // the reservation - - // Connector status is available!! + // 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. ReserveNowRequest reservation_request = call.msg; From 706f54cd3f3fdc6796b064a5bd8dd0fa2959cc18 Mon Sep 17 00:00:00 2001 From: "Maaike Zijderveld, iolar" Date: Thu, 31 Oct 2024 17:13:21 +0100 Subject: [PATCH 06/24] Remove unnecessary todo's. Remove unnecessary loop to check for existing reservations. Signed-off-by: Maaike Zijderveld, iolar --- include/ocpp/v201/charge_point.hpp | 4 ++++ lib/ocpp/v201/charge_point.cpp | 30 ++++++++++-------------------- 2 files changed, 14 insertions(+), 20 deletions(-) diff --git a/include/ocpp/v201/charge_point.hpp b/include/ocpp/v201/charge_point.hpp index f54f38ae2..662af9348 100644 --- a/include/ocpp/v201/charge_point.hpp +++ b/include/ocpp/v201/charge_point.hpp @@ -874,6 +874,8 @@ class ChargePoint : public ChargePointInterface, private ocpp::ChargingStationBa void on_reserved(const int32_t evse_id, const int32_t connector_id) override; + void on_reservation_cleared(const int32_t evse_id, const int32_t connector_id) override; + bool on_charging_state_changed( const uint32_t evse_id, const ChargingStateEnum charging_state, const TriggerReasonEnum trigger_reason = TriggerReasonEnum::ChargingStateChanged) override; @@ -893,6 +895,8 @@ class ChargePoint : public ChargePointInterface, private ocpp::ChargingStationBa void on_variable_changed(const SetVariableData& set_variable_data) override; + void on_reservation_status(const int32_t reservation_id, const ReservationUpdateStatusEnum status) override; + std::optional data_transfer_req(const CiString<255>& vendorId, const std::optional>& messageId, const std::optional& data) override; diff --git a/lib/ocpp/v201/charge_point.cpp b/lib/ocpp/v201/charge_point.cpp index 69b641849..d0f0c8856 100644 --- a/lib/ocpp/v201/charge_point.cpp +++ b/lib/ocpp/v201/charge_point.cpp @@ -1945,22 +1945,13 @@ std::optional ChargePoint::get_transaction_evseid(const CiString<36>& t bool ChargePoint::is_evse_reserved_for_other(EvseInterface& evse, const IdToken& id_token, const std::optional& group_id_token) const { const uint32_t connectors = evse.get_number_of_connectors(); - for (uint32_t i = 1; i <= connectors; ++i) { - const ConnectorStatusEnum status = - evse.get_connector(static_cast(i))->get_effective_connector_status(); - if (status == ConnectorStatusEnum::Reserved) { - const std::optional> groupIdToken = - group_id_token.has_value() ? group_id_token.value().idToken : std::optional>{}; + const std::optional> groupIdToken = + group_id_token.has_value() ? group_id_token.value().idToken : std::optional>{}; - if (!callbacks.is_reservation_for_token_callback(evse.get_id(), id_token.idToken, groupIdToken)) { - return true; - } - } + if (!callbacks.is_reservation_for_token_callback(evse.get_id(), id_token.idToken, groupIdToken)) { + return true; } - // TODO mz also check if connector status is not reserved, as there can be a reservation for evse id 0. - // TODO mz also check here if there is a reservation for a token and no evse id. - return false; } @@ -1995,7 +1986,6 @@ void ChargePoint::set_evse_connectors_unavailable(EvseInterface& evse, bool pers } std::optional ChargePoint::get_evse_connector_type(const uint32_t evse_id, const uint32_t connector_id) { - // TODO mz add logging??? EvseInterface* evse = nullptr; try { evse = &this->evse_manager->get_evse(static_cast(evse_id)); @@ -3467,13 +3457,10 @@ void ChargePoint::handle_reserve_now_request(Call call) { } if (!status_found) { - // TODO mz what to do here? All 'get_available_connector_or_status' returned nothing... EVLOG_info << "Trying to make a reservation but could not get connector status."; this->send(ocpp::CallResult(response, call.uniqueId)); return; } - - EVLOG_info << "Handle reserve now request 7"; } // Connector exists and might or might not be available, but if the reservation id is already existing, reservation @@ -3485,13 +3472,16 @@ void ChargePoint::handle_reserve_now_request(Call call) { reservation_request.id, reservation_request.expiryDateTime, reservation_request.idToken, reservation_request.connectorType, reservation_request.evseId, reservation_request.groupIdToken); - // Reply with the response fromt he callback. + // Reply with the response from the callback. const ocpp::CallResult call_result(response, call.uniqueId); this->send(call_result); if (response.status == ReserveNowStatusEnum::Accepted) { - // TODO mz send status notification request with connector status `Reserved`??? - // TODO mz set a status of the connector somewhere??? + 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"; } } From f454ce7eefd65de9b5d687c669cf1eb455e23205 Mon Sep 17 00:00:00 2001 From: "Maaike Zijderveld, iolar" Date: Mon, 4 Nov 2024 13:10:48 +0100 Subject: [PATCH 07/24] Fix conversion of bool for connector zero support. Signed-off-by: Maaike Zijderveld, iolar --- lib/ocpp/v16/charge_point_configuration.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ocpp/v16/charge_point_configuration.cpp b/lib/ocpp/v16/charge_point_configuration.cpp index 7de14c0cd..cdae6bb00 100644 --- a/lib/ocpp/v16/charge_point_configuration.cpp +++ b/lib/ocpp/v16/charge_point_configuration.cpp @@ -1447,7 +1447,7 @@ std::optional ChargePointConfiguration::getReserveConnectorZeroSupport KeyValue kv; kv.key = "ReserveConnectorZeroSupported"; kv.readonly = true; - kv.value.emplace(std::to_string(reserve_connector_zero_supported.value())); + kv.value.emplace(ocpp::conversions::bool_to_string(reserve_connector_zero_supported.value())); reserve_connector_zero_supported_kv.emplace(kv); } return reserve_connector_zero_supported_kv; From 0eb884795f5cc70dd52bc9abd8662eca625bb7a4 Mon Sep 17 00:00:00 2001 From: "Maaike Zijderveld, iolar" Date: Mon, 4 Nov 2024 13:54:13 +0100 Subject: [PATCH 08/24] Add reservation status. Signed-off-by: Maaike Zijderveld, iolar --- doc/ocpp_201_status.md | 58 ++++++++++++++++++++++-------------------- 1 file changed, 30 insertions(+), 28 deletions(-) diff --git a/doc/ocpp_201_status.md b/doc/ocpp_201_status.md index 43e4afb7e..2217caf3b 100644 --- a/doc/ocpp_201_status.md +++ b/doc/ocpp_201_status.md @@ -1074,21 +1074,21 @@ This document contains the status of which OCPP 2.0.1 numbered functional requir | ID | Status | Remark | |-----------|--------|--------| -| H01.FR.01 | | | -| H01.FR.02 | | | -| H01.FR.03 | | | -| H01.FR.04 | | | -| H01.FR.06 | | | -| H01.FR.07 | | | -| H01.FR.09 | | | -| H01.FR.11 | | | -| H01.FR.12 | | | -| H01.FR.14 | | | -| H01.FR.15 | | | -| H01.FR.16 | | | -| H01.FR.17 | | | -| H01.FR.18 | | | -| H01.FR.19 | | | +| H01.FR.01 | ✅ | | +| H01.FR.02 | ✅ | | +| H01.FR.03 | ✅ | | +| H01.FR.04 | ✅ | | +| H01.FR.06 | ✅ | | +| H01.FR.07 | ✅ | | +| H01.FR.09 | ✅ | | +| H01.FR.11 | ✅ | | +| H01.FR.12 | ✅ | | +| H01.FR.14 | ✅ | | +| H01.FR.15 | ✅ | | +| H01.FR.16 | ✅ | | +| H01.FR.17 | ✅ | | +| H01.FR.18 | ✅ | | +| H01.FR.19 | ✅ | | | H01.FR.20 | | | | H01.FR.23 | | | | H01.FR.24 | | | @@ -1097,29 +1097,31 @@ This document contains the status of which OCPP 2.0.1 numbered functional requir | ID | Status | Remark | |-----------|--------|--------| -| H02.FR.01 | | | -| H02.FR.02 | | | +| H02.FR.01 | ✅ | | +| H02.FR.02 | ✅ | | ## Reservation - Use a reserved EVSE | ID | Status | Remark | |-----------|--------|--------| -| H03.FR.01 | | | -| H03.FR.02 | | | -| H03.FR.03 | | | -| H03.FR.04 | | | -| H03.FR.05 | | | -| H03.FR.06 | | | -| H03.FR.07 | | | -| H03.FR.08 | | | +| H03.FR.01 | ✅ | | +| H03.FR.02 | ✅ | | +| H03.FR.03 | ✅ | | +| H03.FR.04 | ✅ | | +| H03.FR.05 | ✅ | | +| H03.FR.06 | ✅ | | +| H03.FR.07 | ⛽️ | | +| H03.FR.08 | ⛽️ | | +| H03.FR.09 | ✅ | | +| H03.FR.10 | ✅ | | ## Reservation - Reservation Ended, not used | ID | Status | Remark | |-----------|--------|--------| -| H04.FR.01 | | | -| H04.FR.02 | | | -| H04.FR.03 | | | +| H04.FR.01 | ✅ | | +| H04.FR.02 | ✅ | | +| H04.FR.03 | | Not all the connectors maybe? | ## TariffAndCost - Show EV Driver-specific Tariff Information From 3823b1c9f16b6a90dd207c8a3f1bbbb4915c25bf Mon Sep 17 00:00:00 2001 From: "Maaike Zijderveld, iolar" Date: Tue, 5 Nov 2024 16:50:43 +0100 Subject: [PATCH 09/24] Review comments. Signed-off-by: Maaike Zijderveld, iolar --- include/ocpp/v201/charge_point.hpp | 20 ++-- include/ocpp/v201/evse.hpp | 24 +++++ include/ocpp/v201/evse_manager.hpp | 4 +- lib/ocpp/v201/charge_point.cpp | 135 ++++++++---------------- lib/ocpp/v201/evse.cpp | 69 ++++++++++++ lib/ocpp/v201/evse_manager.cpp | 10 +- tests/lib/ocpp/v201/mocks/evse_mock.hpp | 2 + 7 files changed, 152 insertions(+), 112 deletions(-) diff --git a/include/ocpp/v201/charge_point.hpp b/include/ocpp/v201/charge_point.hpp index 2e5c65405..bd154f288 100644 --- a/include/ocpp/v201/charge_point.hpp +++ b/include/ocpp/v201/charge_point.hpp @@ -579,26 +579,19 @@ class ChargePoint : public ChargePointInterface, private ocpp::ChargingStationBa /// void set_evse_connectors_unavailable(EvseInterface& evse, bool persist); - /// - /// \brief Get connector type of Connector - /// \param evse_id EVSE id - /// \param connector_id Connector id - /// \return The connector type. If evse or connector id is not correct: std::nullopt. - /// - std::optional get_evse_connector_type(const uint32_t evse_id, const uint32_t connector_id); - /// /// \brief Get connector status. /// /// This will search if there is a connector on this evse with status 'Available'. It will search through all - /// connectors and return on the first connector that is 'Available'. + /// connectors, optionally filtering by connector type, and return on the first connector that is 'Available'. If + /// there is no 'Available' connector, it will return the status of one of the connectors. /// /// \param evse_id The evse id. - /// \param connector_type The connector type. - /// \return std::nullopt if connector type is given, but does not exist. Otherwise the connector status. + /// \param connector_type The connector type to filter on (optional). + /// \return Connector status. If connector type is given and does not exist, std::nullopt. /// - std::optional get_available_connector_or_status(const uint32_t evse_id, - std::optional connector_type); + std::optional get_connector_status(const uint32_t evse_id, + std::optional connector_type); /// \brief Get the value optional offline flag /// \return true if the charge point is offline. std::nullopt if it is online; @@ -747,6 +740,7 @@ class ChargePoint : public ChargePointInterface, private ocpp::ChargingStationBa // Function Block H: Reservations void handle_reserve_now_request(Call call); void handle_cancel_reservation_callback(Call 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 call); diff --git a/include/ocpp/v201/evse.hpp b/include/ocpp/v201/evse.hpp index f53cf7d19..15dcfc520 100644 --- a/include/ocpp/v201/evse.hpp +++ b/include/ocpp/v201/evse.hpp @@ -36,6 +36,20 @@ class EvseInterface { /// \return virtual uint32_t get_number_of_connectors() const = 0; + /// + /// \brief Get connector status. + /// + /// This will search if there is a connector on this evse with status 'Available'. It will search through all + /// connectors, optionally filtering by connector type, and return on the first connector that is 'Available'. If + /// there is no 'Available' connector, it will return the status of one of the connectors. + /// + /// \param evse_id The evse id. + /// \param connector_type The connector type to filter on (optional). + /// \return Connector status. If connector type is given and does not exist, std::nullopt. + /// + virtual std::optional get_connector_status(const uint32_t evse_id, + std::optional connector_type) = 0; + /// \brief Opens a new transaction /// \param transaction_id id of the transaction /// \param connector_id id of the connector @@ -198,6 +212,14 @@ class Evse : public EvseInterface { /// \brief Component responsible for maintaining and persisting the operational status of CS, EVSEs, and connectors. std::shared_ptr component_state_manager; + /// + /// \brief Get connector type of Connector + /// \param evse_id EVSE id + /// \param connector_id Connector id + /// \return The connector type. If evse or connector id is not correct: std::nullopt. + /// + std::optional get_evse_connector_type(const uint32_t evse_id, const uint32_t connector_id); + public: /// \brief Construct a new Evse object /// \param evse_id id of the evse @@ -220,6 +242,8 @@ class Evse : public EvseInterface { int32_t get_id() const; uint32_t get_number_of_connectors() const; + std::optional get_connector_status(const uint32_t evse_id, + std::optional connector_type); void open_transaction(const std::string& transaction_id, const int32_t connector_id, const DateTime& timestamp, const MeterValue& meter_start, const std::optional& id_token, diff --git a/include/ocpp/v201/evse_manager.hpp b/include/ocpp/v201/evse_manager.hpp index f2da7ba88..9acee0556 100644 --- a/include/ocpp/v201/evse_manager.hpp +++ b/include/ocpp/v201/evse_manager.hpp @@ -50,9 +50,9 @@ class EvseManager : public EvseManagerInterface { const std::function& pause_charging_callback); EvseInterface& get_evse(int32_t id) override; - const EvseInterface& get_evse(int32_t id) const override; + const EvseInterface& get_evse(const int32_t id) const override; - bool does_evse_exist(int32_t id) const override; + bool does_evse_exist(const int32_t id) const override; size_t get_number_of_evses() const override; diff --git a/lib/ocpp/v201/charge_point.cpp b/lib/ocpp/v201/charge_point.cpp index a79d4c2c4..a5dcb51cf 100644 --- a/lib/ocpp/v201/charge_point.cpp +++ b/lib/ocpp/v201/charge_point.cpp @@ -1997,33 +1997,8 @@ void ChargePoint::set_evse_connectors_unavailable(EvseInterface& evse, bool pers } } -std::optional ChargePoint::get_evse_connector_type(const uint32_t evse_id, const uint32_t connector_id) { - EvseInterface* evse = nullptr; - try { - evse = &this->evse_manager->get_evse(static_cast(evse_id)); - } catch (EvseOutOfRangeException&) { - return std::nullopt; - } - - auto connector = evse->get_connector(static_cast(connector_id)); - if (connector == nullptr) { - return std::nullopt; - } - - ComponentVariable connector_cv = - ConnectorComponentVariables::get_component_variable(evse_id, connector_id, ConnectorComponentVariables::Type); - - const std::optional connector_type = - this->device_model->get_optional_value(connector_cv, AttributeEnum::Actual); - if (!connector_type.has_value()) { - return std::nullopt; - } - - return conversions::string_to_connector_enum(connector_type.value()); -} - -std::optional -ChargePoint::get_available_connector_or_status(const uint32_t evse_id, std::optional connector_type) { +std::optional ChargePoint::get_connector_status(const uint32_t evse_id, + std::optional connector_type) { EvseInterface* evse; try { evse = &evse_manager->get_evse(static_cast(evse_id)); @@ -2032,51 +2007,7 @@ ChargePoint::get_available_connector_or_status(const uint32_t evse_id, std::opti return std::nullopt; } - bool type_found = false; - ConnectorStatusEnum found_status = ConnectorStatusEnum::Unavailable; - const uint32_t number_of_connectors = evse->get_number_of_connectors(); - if (number_of_connectors == 0) { - return std::nullopt; - } - - for (uint32_t i = 1; i <= number_of_connectors; ++i) { - Connector* connector; - try { - connector = evse->get_connector(static_cast(i)); - } catch (const std::logic_error&) { - EVLOG_error << "Reserve now request: Connector with id " << i << " does not exist"; - } - - if (connector == nullptr) { - EVLOG_error << "Reserve now request: Connector with id " << i << " does not exist"; - continue; - } - - ConnectorStatusEnum connector_status = connector->get_effective_connector_status(); - - std::optional evse_connector_type = - this->get_evse_connector_type(static_cast(evse_id), i); - if (!connector_type.has_value() || - (!evse_connector_type.has_value() || evse_connector_type.value() == connector_type.value())) { - type_found = true; - // We found an available connector, also store the status. - found_status = connector_status; - if (found_status == ConnectorStatusEnum::Available) { - // There is an available connector with the correct type and status: we don't have to search - // any further. - return found_status; - } - - // If status is not available, we keep on searching. If no connector is available, the status of - // (at least one of) the connectors is stored to return that later if no available connector is found. - } - } - - if (!type_found) { - return std::nullopt; - } - - return found_status; + return evse->get_connector_status(evse_id, connector_type); } bool ChargePoint::is_offline() { @@ -3396,16 +3327,27 @@ void ChargePoint::handle_heartbeat_response(CallResult call) void ChargePoint::handle_reserve_now_request(Call call) { ReserveNowResponse response; response.status = ReserveNowStatusEnum::Rejected; - if (!this->callbacks.reserve_now_callback.has_value() || - !this->device_model->get_optional_value(ControllerComponentVariables::ReservationCtrlrAvailable) - .value_or(false) || - !this->device_model->get_optional_value(ControllerComponentVariables::ReservationCtrlrEnabled) - .value_or(false)) { + 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(ControllerComponentVariables::ReservationCtrlrAvailable) + .value_or(false)) { + status_info = "Reservation is not available"; + reservation_available = false; + } else if (!this->device_model->get_optional_value(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 implemented."; - const ocpp::CallResult call_result(response, call.uniqueId); - this->send(call_result); + EVLOG_info << "Receiving a reservation request, but reservation is not enabled or implemented."; + send_reserve_now_rejected_response(call.uniqueId, status_info); return; } @@ -3415,10 +3357,11 @@ void ChargePoint::handle_reserve_now_request(Call call) { !this->device_model->get_optional_value(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."; - const ocpp::CallResult call_result(response, call.uniqueId); - this->send(call_result); + 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; } @@ -3427,21 +3370,21 @@ void ChargePoint::handle_reserve_now_request(Call call) { ConnectorStatusEnum connector_status = ConnectorStatusEnum::Unavailable; if (evse_id.has_value()) { - if (evse_id.value() < 0 || !evse_manager->does_evse_exist(evse_id.value())) { + if (!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."; - this->send(ocpp::CallResult(response, call.uniqueId)); + send_reserve_now_rejected_response(call.uniqueId, "Evse id does not exist"); return; } // Check if there is a connector available for this evse id. std::optional status = - get_available_connector_or_status(static_cast(evse_id.value()), request.connectorType); + get_connector_status(static_cast(evse_id.value()), request.connectorType); if (!status.has_value()) { 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."; - this->send(ocpp::CallResult(response, call.uniqueId)); + send_reserve_now_rejected_response(call.uniqueId, "Connector type does not exist"); return; } else { connector_status = status.value(); @@ -3450,14 +3393,14 @@ void ChargePoint::handle_reserve_now_request(Call call) { // 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"; - this->send(ocpp::CallResult(response, call.uniqueId)); return; } bool status_found = false; for (uint64_t i = 1; i <= number_of_evses; i++) { - std::optional status = get_available_connector_or_status(i, request.connectorType); + std::optional status = get_connector_status(i, request.connectorType); if (status.has_value()) { status_found = true; connector_status = status.value(); @@ -3469,8 +3412,7 @@ void ChargePoint::handle_reserve_now_request(Call call) { } if (!status_found) { - EVLOG_info << "Trying to make a reservation but could not get connector status."; - this->send(ocpp::CallResult(response, call.uniqueId)); + send_reserve_now_rejected_response(call.uniqueId, "Could not get status info from connector"); return; } } @@ -3519,6 +3461,15 @@ void ChargePoint::handle_cancel_reservation_callback(Callsend(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 call_result(response, unique_id); + this->send(call_result); +} + void ChargePoint::handle_costupdated_req(const Call call) { CostUpdatedResponse response; ocpp::CallResult call_result(response, call.uniqueId); diff --git a/lib/ocpp/v201/evse.cpp b/lib/ocpp/v201/evse.cpp index 6260131dd..603a79daa 100644 --- a/lib/ocpp/v201/evse.cpp +++ b/lib/ocpp/v201/evse.cpp @@ -86,6 +86,56 @@ uint32_t Evse::get_number_of_connectors() const { return static_cast(this->id_connector_map.size()); } +std::optional Evse::get_connector_status(const uint32_t evse_id, + std::optional connector_type) { + bool type_found = false; + ConnectorStatusEnum found_status = ConnectorStatusEnum::Unavailable; + const uint32_t number_of_connectors = this->get_number_of_connectors(); + if (number_of_connectors == 0) { + return std::nullopt; + } + + for (uint32_t i = 1; i <= number_of_connectors; ++i) { + Connector* connector; + try { + connector = this->get_connector(static_cast(i)); + } catch (const std::logic_error&) { + EVLOG_error << "Connector with id " << i << " does not exist"; + continue; + } + + if (connector == nullptr) { + EVLOG_error << "Connector with id " << i << " does not exist"; + continue; + } + + const ConnectorStatusEnum connector_status = connector->get_effective_connector_status(); + + const std::optional evse_connector_type = + this->get_evse_connector_type(static_cast(evse_id), i); + if (!connector_type.has_value() || + (!evse_connector_type.has_value() || evse_connector_type.value() == connector_type.value())) { + type_found = true; + // We found an available connector, also store the status. + found_status = connector_status; + if (found_status == ConnectorStatusEnum::Available) { + // There is an available connector with the correct type and status: we don't have to search + // any further. + return found_status; + } + + // If status is not available, we keep on searching. If no connector is available, the status of + // (at least one of) the connectors is stored to return that later if no available connector is found. + } + } + + if (!type_found) { + return std::nullopt; + } + + return found_status; +} + void Evse::try_resume_transaction() { // Get an open transactions from the database and resume it if there is one auto transaction = this->database_handler->transaction_get(evse_id); @@ -121,6 +171,25 @@ void Evse::delete_database_transaction() { } } +std::optional Evse::get_evse_connector_type(const uint32_t evse_id, const uint32_t connector_id) { + + auto connector = this->get_connector(static_cast(connector_id)); + if (connector == nullptr) { + return std::nullopt; + } + + ComponentVariable connector_cv = + ConnectorComponentVariables::get_component_variable(evse_id, connector_id, ConnectorComponentVariables::Type); + + const std::optional connector_type = + this->device_model.get_optional_value(connector_cv, AttributeEnum::Actual); + if (!connector_type.has_value()) { + return std::nullopt; + } + + return conversions::string_to_connector_enum(connector_type.value()); +} + void Evse::open_transaction(const std::string& transaction_id, const int32_t connector_id, const DateTime& timestamp, const MeterValue& meter_start, const std::optional& id_token, const std::optional& group_id_token, const std::optional reservation_id, diff --git a/lib/ocpp/v201/evse_manager.cpp b/lib/ocpp/v201/evse_manager.cpp index 71b7714e8..216e2233f 100644 --- a/lib/ocpp/v201/evse_manager.cpp +++ b/lib/ocpp/v201/evse_manager.cpp @@ -30,21 +30,21 @@ EvseManager::EvseIterator EvseManager::end() { } EvseInterface& EvseManager::get_evse(int32_t id) { - if (id == 0 or id > this->evses.size()) { + if (id <= 0 or id > this->evses.size()) { throw EvseOutOfRangeException(id); } return *this->evses.at(id - 1); } -const EvseInterface& EvseManager::get_evse(int32_t id) const { - if (id == 0 or id > this->evses.size()) { +const EvseInterface& EvseManager::get_evse(const int32_t id) const { + if (id <= 0 or id > this->evses.size()) { throw EvseOutOfRangeException(id); } return *this->evses.at(id - 1); } -bool EvseManager::does_evse_exist(int32_t id) const { - return id <= this->evses.size(); +bool EvseManager::does_evse_exist(const int32_t id) const { + return id > 0 && static_cast(id) <= this->evses.size(); } size_t EvseManager::get_number_of_evses() const { diff --git a/tests/lib/ocpp/v201/mocks/evse_mock.hpp b/tests/lib/ocpp/v201/mocks/evse_mock.hpp index da05da0ac..804cbdd72 100644 --- a/tests/lib/ocpp/v201/mocks/evse_mock.hpp +++ b/tests/lib/ocpp/v201/mocks/evse_mock.hpp @@ -12,6 +12,8 @@ class EvseMock : public EvseInterface { public: MOCK_METHOD(int32_t, get_id, (), (const)); MOCK_METHOD(uint32_t, get_number_of_connectors, (), (const)); + MOCK_METHOD(std::optional, get_connector_status, + (const uint32_t evse_id, std::optional connector_type)); MOCK_METHOD(void, open_transaction, (const std::string& transaction_id, const int32_t connector_id, const DateTime& timestamp, const MeterValue& meter_start, const std::optional& id_token, From 27bcebb122def2a7f84804aa3c648eb00c640363 Mon Sep 17 00:00:00 2001 From: "Maaike Zijderveld, iolar" Date: Wed, 6 Nov 2024 10:36:49 +0100 Subject: [PATCH 10/24] Remove unused variable. Signed-off-by: Maaike Zijderveld, iolar --- lib/ocpp/v201/charge_point.cpp | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/lib/ocpp/v201/charge_point.cpp b/lib/ocpp/v201/charge_point.cpp index a5dcb51cf..eae580e08 100644 --- a/lib/ocpp/v201/charge_point.cpp +++ b/lib/ocpp/v201/charge_point.cpp @@ -3367,8 +3367,6 @@ void ChargePoint::handle_reserve_now_request(Call call) { const std::optional evse_id = request.evseId; - ConnectorStatusEnum connector_status = ConnectorStatusEnum::Unavailable; - if (evse_id.has_value()) { if (!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."; @@ -3386,8 +3384,6 @@ void ChargePoint::handle_reserve_now_request(Call call) { << " 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 { - connector_status = status.value(); } } else { // No evse id. Just search for all evse's if there is something available for reservation @@ -3403,8 +3399,7 @@ void ChargePoint::handle_reserve_now_request(Call call) { std::optional status = get_connector_status(i, request.connectorType); if (status.has_value()) { status_found = true; - connector_status = status.value(); - if (connector_status == ConnectorStatusEnum::Available) { + if (status.value() == ConnectorStatusEnum::Available) { // There is at least one connector available! break; } From 045bdaf0c8a8f58fa6811153f29814f9d4c5ddab Mon Sep 17 00:00:00 2001 From: "Maaike Zijderveld, iolar" Date: Fri, 8 Nov 2024 18:04:51 +0100 Subject: [PATCH 11/24] Review comment: change parameters of reserve now callback. Signed-off-by: Maaike Zijderveld, iolar --- include/ocpp/v201/charge_point.hpp | 2 +- include/ocpp/v201/charge_point_callbacks.hpp | 7 ++----- lib/ocpp/v201/charge_point.cpp | 8 +++----- 3 files changed, 6 insertions(+), 11 deletions(-) diff --git a/include/ocpp/v201/charge_point.hpp b/include/ocpp/v201/charge_point.hpp index bd154f288..c60c4392a 100644 --- a/include/ocpp/v201/charge_point.hpp +++ b/include/ocpp/v201/charge_point.hpp @@ -740,7 +740,7 @@ class ChargePoint : public ChargePointInterface, private ocpp::ChargingStationBa // Function Block H: Reservations void handle_reserve_now_request(Call call); void handle_cancel_reservation_callback(Call call); - void send_reserve_now_rejected_response(const MessageId& unique_id, const std::string status_info); + 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 call); diff --git a/include/ocpp/v201/charge_point_callbacks.hpp b/include/ocpp/v201/charge_point_callbacks.hpp index 6a25a99bd..5d1637ef6 100644 --- a/include/ocpp/v201/charge_point_callbacks.hpp +++ b/include/ocpp/v201/charge_point_callbacks.hpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -153,11 +154,7 @@ struct Callbacks { set_running_cost_callback; /// \brief Callback function is called when a reservation request is received from the CSMS - std::optional connector_type, const std::optional evse_id, - const std::optional& group_id_token)>> - reserve_now_callback; + std::optional> reserve_now_callback; /// \brief Callback function is called when a cancel reservation request is received from the CSMS std::optional> cancel_reservation_callback; }; diff --git a/lib/ocpp/v201/charge_point.cpp b/lib/ocpp/v201/charge_point.cpp index eae580e08..afd2bc175 100644 --- a/lib/ocpp/v201/charge_point.cpp +++ b/lib/ocpp/v201/charge_point.cpp @@ -3416,10 +3416,8 @@ void ChargePoint::handle_reserve_now_request(Call call) { // should be overwritten. // Call reserve now callback and wait for the response. - ReserveNowRequest reservation_request = call.msg; - response.status = this->callbacks.reserve_now_callback.value()( - reservation_request.id, reservation_request.expiryDateTime, reservation_request.idToken, - reservation_request.connectorType, reservation_request.evseId, reservation_request.groupIdToken); + 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 call_result(response, call.uniqueId); @@ -3456,7 +3454,7 @@ void ChargePoint::handle_cancel_reservation_callback(Callsend(call_result); } -void ChargePoint::send_reserve_now_rejected_response(const MessageId& unique_id, const std::string status_info) { +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(); From ae1d21afcde5d679d86f9de5e15efa7bb0594d01 Mon Sep 17 00:00:00 2001 From: "Maaike Zijderveld, iolar" Date: Wed, 13 Nov 2024 13:04:09 +0100 Subject: [PATCH 12/24] Fix bug for composite schedule Signed-off-by: Maaike Zijderveld, iolar --- lib/ocpp/v201/charge_point.cpp | 5 +++-- lib/ocpp/v201/evse_manager.cpp | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/ocpp/v201/charge_point.cpp b/lib/ocpp/v201/charge_point.cpp index c2dd67168..7fd23f7e7 100644 --- a/lib/ocpp/v201/charge_point.cpp +++ b/lib/ocpp/v201/charge_point.cpp @@ -3368,7 +3368,7 @@ void ChargePoint::handle_reserve_now_request(Call call) { const std::optional evse_id = request.evseId; if (evse_id.has_value()) { - if (!evse_manager->does_evse_exist(evse_id.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; @@ -4719,7 +4719,8 @@ std::vector ChargePoint::get_all_composite_schedules(const in composite_schedule_response.schedule.has_value()) { composite_schedules.push_back(composite_schedule_response.schedule.value()); } else { - EVLOG_warning << "Could not internally retrieve composite schedule: " << composite_schedule_response; + EVLOG_warning << "Could not internally retrieve composite schedule for evse id " << evse_id << ": " + << composite_schedule_response; } } diff --git a/lib/ocpp/v201/evse_manager.cpp b/lib/ocpp/v201/evse_manager.cpp index 216e2233f..fa795f615 100644 --- a/lib/ocpp/v201/evse_manager.cpp +++ b/lib/ocpp/v201/evse_manager.cpp @@ -44,7 +44,7 @@ const EvseInterface& EvseManager::get_evse(const int32_t id) const { } bool EvseManager::does_evse_exist(const int32_t id) const { - return id > 0 && static_cast(id) <= this->evses.size(); + return id >= 0 && static_cast(id) <= this->evses.size(); } size_t EvseManager::get_number_of_evses() const { From d7c779280584a5a82c13883cc2f35dced0913f16 Mon Sep 17 00:00:00 2001 From: "Maaike Zijderveld, iolar" Date: Wed, 13 Nov 2024 14:03:32 +0100 Subject: [PATCH 13/24] Review comments. Some changes in functions to make it more readable. Signed-off-by: Maaike Zijderveld, iolar --- include/ocpp/v201/charge_point.hpp | 20 ++++++------ include/ocpp/v201/evse.hpp | 18 +++++++---- lib/ocpp/v201/charge_point.cpp | 48 +++++++++++++++++---------- lib/ocpp/v201/evse.cpp | 52 ++++++++++++++++++++++++------ 4 files changed, 95 insertions(+), 43 deletions(-) diff --git a/include/ocpp/v201/charge_point.hpp b/include/ocpp/v201/charge_point.hpp index ca38e9e86..afcc22bb2 100644 --- a/include/ocpp/v201/charge_point.hpp +++ b/include/ocpp/v201/charge_point.hpp @@ -582,18 +582,20 @@ class ChargePoint : public ChargePointInterface, private ocpp::ChargingStationBa void set_evse_connectors_unavailable(EvseInterface& evse, bool persist); /// - /// \brief Get connector status. + /// \brief Check if there is a connector available with the given connector type. + /// \param evse_id The evse to check for. + /// \param connector_type The connector type. + /// \return True when a connector is available and the evse id exists. /// - /// This will search if there is a connector on this evse with status 'Available'. It will search through all - /// connectors, optionally filtering by connector type, and return on the first connector that is 'Available'. If - /// there is no 'Available' connector, it will return the status of one of the connectors. + bool is_connector_available(const uint32_t evse_id, std::optional connector_type); + /// - /// \param evse_id The evse id. - /// \param connector_type The connector type to filter on (optional). - /// \return Connector status. If connector type is given and does not exist, std::nullopt. + /// \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. /// - std::optional get_connector_status(const uint32_t evse_id, - std::optional connector_type); + bool does_connector_exist(const uint32_t evse_id, std::optional connector_type); /// \brief Get the value optional offline flag /// \return true if the charge point is offline. std::nullopt if it is online; diff --git a/include/ocpp/v201/evse.hpp b/include/ocpp/v201/evse.hpp index 15dcfc520..3ed88b7b6 100644 --- a/include/ocpp/v201/evse.hpp +++ b/include/ocpp/v201/evse.hpp @@ -36,6 +36,13 @@ class EvseInterface { /// \return virtual uint32_t get_number_of_connectors() const = 0; + /// + /// \brief Check if the given connector type exists on this evse. + /// \param connector_type The connector type to check. + /// \return True if connector type is unknown or this evse has the given connector type. + /// + virtual bool does_connector_exist(ConnectorEnum connector_type) = 0; + /// /// \brief Get connector status. /// @@ -43,12 +50,10 @@ class EvseInterface { /// connectors, optionally filtering by connector type, and return on the first connector that is 'Available'. If /// there is no 'Available' connector, it will return the status of one of the connectors. /// - /// \param evse_id The evse id. /// \param connector_type The connector type to filter on (optional). /// \return Connector status. If connector type is given and does not exist, std::nullopt. /// - virtual std::optional get_connector_status(const uint32_t evse_id, - std::optional connector_type) = 0; + virtual std::optional get_connector_status(std::optional connector_type) = 0; /// \brief Opens a new transaction /// \param transaction_id id of the transaction @@ -214,11 +219,10 @@ class Evse : public EvseInterface { /// /// \brief Get connector type of Connector - /// \param evse_id EVSE id /// \param connector_id Connector id /// \return The connector type. If evse or connector id is not correct: std::nullopt. /// - std::optional get_evse_connector_type(const uint32_t evse_id, const uint32_t connector_id); + std::optional get_evse_connector_type(const uint32_t connector_id); public: /// \brief Construct a new Evse object @@ -242,8 +246,8 @@ class Evse : public EvseInterface { int32_t get_id() const; uint32_t get_number_of_connectors() const; - std::optional get_connector_status(const uint32_t evse_id, - std::optional connector_type); + bool does_connector_exist(const ConnectorEnum connector_type) override; + std::optional get_connector_status(std::optional connector_type) override; void open_transaction(const std::string& transaction_id, const int32_t connector_id, const DateTime& timestamp, const MeterValue& meter_start, const std::optional& id_token, diff --git a/lib/ocpp/v201/charge_point.cpp b/lib/ocpp/v201/charge_point.cpp index 7fd23f7e7..d9e042c53 100644 --- a/lib/ocpp/v201/charge_point.cpp +++ b/lib/ocpp/v201/charge_point.cpp @@ -1997,17 +1997,34 @@ void ChargePoint::set_evse_connectors_unavailable(EvseInterface& evse, bool pers } } -std::optional ChargePoint::get_connector_status(const uint32_t evse_id, - std::optional connector_type) { +bool ChargePoint::is_connector_available(const uint32_t evse_id, std::optional connector_type) { EvseInterface* evse; try { evse = &evse_manager->get_evse(static_cast(evse_id)); } catch (const EvseOutOfRangeException&) { EVLOG_error << "Evse id " << evse_id << " is not a valid evse id."; - return std::nullopt; + return false; + } + + std::optional status = + evse->get_connector_status(connector_type.value_or(ConnectorEnum::Unknown)); + if (!status.has_value()) { + return false; } - return evse->get_connector_status(evse_id, connector_type); + return status.value() == ConnectorStatusEnum::Available; +} + +bool ChargePoint::does_connector_exist(const uint32_t evse_id, std::optional connector_type) { + EvseInterface* evse; + try { + evse = &evse_manager->get_evse(static_cast(evse_id)); + } catch (const EvseOutOfRangeException&) { + EVLOG_error << "Evse id " << evse_id << " is not a valid evse id."; + return false; + } + + return evse->does_connector_exist(connector_type.value_or(ConnectorEnum::Unknown)); } bool ChargePoint::is_offline() { @@ -3375,10 +3392,7 @@ void ChargePoint::handle_reserve_now_request(Call call) { } // Check if there is a connector available for this evse id. - std::optional status = - get_connector_status(static_cast(evse_id.value()), request.connectorType); - - if (!status.has_value()) { + if (!does_connector_exist(static_cast(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."; @@ -3394,19 +3408,19 @@ void ChargePoint::handle_reserve_now_request(Call call) { return; } - bool status_found = false; + bool connector_exists = false; for (uint64_t i = 1; i <= number_of_evses; i++) { - std::optional status = get_connector_status(i, request.connectorType); - if (status.has_value()) { - status_found = true; - if (status.value() == ConnectorStatusEnum::Available) { - // There is at least one connector available! - break; - } + 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 (!status_found) { + if (!connector_exists) { send_reserve_now_rejected_response(call.uniqueId, "Could not get status info from connector"); return; } diff --git a/lib/ocpp/v201/evse.cpp b/lib/ocpp/v201/evse.cpp index 603a79daa..ff07b499d 100644 --- a/lib/ocpp/v201/evse.cpp +++ b/lib/ocpp/v201/evse.cpp @@ -86,8 +86,40 @@ uint32_t Evse::get_number_of_connectors() const { return static_cast(this->id_connector_map.size()); } -std::optional Evse::get_connector_status(const uint32_t evse_id, - std::optional connector_type) { +bool Evse::does_connector_exist(const ConnectorEnum connector_type) { + const uint32_t number_of_connectors = this->get_number_of_connectors(); + if (number_of_connectors == 0) { + return false; + } + + if (connector_type == ConnectorEnum::Unknown) { + return true; + } + + for (uint32_t i = 1; i <= number_of_connectors; ++i) { + Connector* connector; + try { + connector = this->get_connector(static_cast(i)); + } catch (const std::logic_error&) { + EVLOG_error << "Connector with id " << i << " does not exist"; + continue; + } + + if (connector == nullptr) { + EVLOG_error << "Connector with id " << i << " does not exist"; + continue; + } + + ConnectorEnum type = this->get_evse_connector_type(i).value_or(ConnectorEnum::Unknown); + if (type == ConnectorEnum::Unknown || type == connector_type) { + return true; + } + } + + return false; +} + +std::optional Evse::get_connector_status(std::optional connector_type) { bool type_found = false; ConnectorStatusEnum found_status = ConnectorStatusEnum::Unavailable; const uint32_t number_of_connectors = this->get_number_of_connectors(); @@ -111,8 +143,7 @@ std::optional Evse::get_connector_status(const uint32_t evs const ConnectorStatusEnum connector_status = connector->get_effective_connector_status(); - const std::optional evse_connector_type = - this->get_evse_connector_type(static_cast(evse_id), i); + const std::optional evse_connector_type = this->get_evse_connector_type(i); if (!connector_type.has_value() || (!evse_connector_type.has_value() || evse_connector_type.value() == connector_type.value())) { type_found = true; @@ -171,15 +202,15 @@ void Evse::delete_database_transaction() { } } -std::optional Evse::get_evse_connector_type(const uint32_t evse_id, const uint32_t connector_id) { +std::optional Evse::get_evse_connector_type(const uint32_t connector_id) { auto connector = this->get_connector(static_cast(connector_id)); if (connector == nullptr) { return std::nullopt; } - ComponentVariable connector_cv = - ConnectorComponentVariables::get_component_variable(evse_id, connector_id, ConnectorComponentVariables::Type); + ComponentVariable connector_cv = ConnectorComponentVariables::get_component_variable( + this->evse_id, connector_id, ConnectorComponentVariables::Type); const std::optional connector_type = this->device_model.get_optional_value(connector_cv, AttributeEnum::Actual); @@ -226,7 +257,8 @@ void Evse::open_transaction(const std::string& transaction_id, const int32_t con try { this->database_handler->transaction_insert(*this->transaction.get(), this->evse_id); } catch (const QueryExecutionException& e) { - // Delete previous transactions that should not exist anyway and try again. Otherwise throw to higher level + // Delete previous transactions that should not exist anyway and try again. Otherwise throw to higher + // level this->delete_database_transaction(); this->database_handler->transaction_insert(*this->transaction.get(), this->evse_id); } @@ -561,8 +593,8 @@ void Evse::send_meter_value_on_pricing_trigger(const MeterValue& meter_value) { } } - // Check if there is a power kw trigger and if that is triggered. For the power kw trigger, we added hysterisis to - // prevent constant triggering. + // Check if there is a power kw trigger and if that is triggered. For the power kw trigger, we added hysterisis + // to prevent constant triggering. const std::optional active_power_meter_value = utils::get_total_power_active_import(meter_value); if (!this->trigger_metervalue_on_power_kw.has_value() or !active_power_meter_value.has_value()) { From c0191734ce649920a3cc89239b27bf52b8119e11 Mon Sep 17 00:00:00 2001 From: "Maaike Zijderveld, iolar" Date: Wed, 13 Nov 2024 15:51:39 +0100 Subject: [PATCH 14/24] Add new method to mock and change mock method function. Signed-off-by: Maaike Zijderveld, iolar --- tests/lib/ocpp/v201/mocks/evse_mock.hpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/lib/ocpp/v201/mocks/evse_mock.hpp b/tests/lib/ocpp/v201/mocks/evse_mock.hpp index 804cbdd72..0117a93e7 100644 --- a/tests/lib/ocpp/v201/mocks/evse_mock.hpp +++ b/tests/lib/ocpp/v201/mocks/evse_mock.hpp @@ -12,8 +12,9 @@ class EvseMock : public EvseInterface { public: MOCK_METHOD(int32_t, get_id, (), (const)); MOCK_METHOD(uint32_t, get_number_of_connectors, (), (const)); + MOCK_METHOD(bool, does_connector_exist, (ConnectorEnum connector_type)); MOCK_METHOD(std::optional, get_connector_status, - (const uint32_t evse_id, std::optional connector_type)); + (std::optional connector_type)); MOCK_METHOD(void, open_transaction, (const std::string& transaction_id, const int32_t connector_id, const DateTime& timestamp, const MeterValue& meter_start, const std::optional& id_token, From b6e8bb9e560372a8519f6b146a23b80d132e9278 Mon Sep 17 00:00:00 2001 From: "Maaike Zijderveld, iolar" Date: Thu, 14 Nov 2024 10:38:54 +0100 Subject: [PATCH 15/24] Bump version Signed-off-by: Maaike Zijderveld, iolar --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9f8181071..80f7145c1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.14) project(ocpp - VERSION 0.19.0 + VERSION 0.20.0 DESCRIPTION "A C++ implementation of the Open Charge Point Protocol" LANGUAGES CXX ) From 4a93e698864e29f56b3cf815a18637495309ddc2 Mon Sep 17 00:00:00 2001 From: "Maaike Zijderveld, iolar" Date: Thu, 14 Nov 2024 16:29:05 +0100 Subject: [PATCH 16/24] get_connector_status: When connector type is unknown, it should also return the connector status. Signed-off-by: Maaike Zijderveld, iolar --- lib/ocpp/v201/evse.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/ocpp/v201/evse.cpp b/lib/ocpp/v201/evse.cpp index ff07b499d..79207b942 100644 --- a/lib/ocpp/v201/evse.cpp +++ b/lib/ocpp/v201/evse.cpp @@ -143,9 +143,12 @@ std::optional Evse::get_connector_status(std::optionalget_effective_connector_status(); - const std::optional evse_connector_type = this->get_evse_connector_type(i); - if (!connector_type.has_value() || - (!evse_connector_type.has_value() || evse_connector_type.value() == connector_type.value())) { + const ConnectorEnum evse_connector_type = this->get_evse_connector_type(i).value_or(ConnectorEnum::Unknown); + const ConnectorEnum input_connector_type = connector_type.value_or(ConnectorEnum::Unknown); + const bool connector_type_unknown = + evse_connector_type == ConnectorEnum::Unknown || input_connector_type == ConnectorEnum::Unknown; + + if (connector_type_unknown || evse_connector_type == input_connector_type) { type_found = true; // We found an available connector, also store the status. found_status = connector_status; From 8568af1580212b4fc4ccd02f583460e5af9c965a Mon Sep 17 00:00:00 2001 From: "Maaike Zijderveld, iolar" Date: Fri, 15 Nov 2024 10:48:05 +0100 Subject: [PATCH 17/24] Change comment. Signed-off-by: Maaike Zijderveld, iolar --- include/ocpp/v201/evse.hpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/include/ocpp/v201/evse.hpp b/include/ocpp/v201/evse.hpp index 3ed88b7b6..babc05071 100644 --- a/include/ocpp/v201/evse.hpp +++ b/include/ocpp/v201/evse.hpp @@ -48,7 +48,8 @@ class EvseInterface { /// /// This will search if there is a connector on this evse with status 'Available'. It will search through all /// connectors, optionally filtering by connector type, and return on the first connector that is 'Available'. If - /// there is no 'Available' connector, it will return the status of one of the connectors. + /// there is no 'Available' connector, it will return the status of the last found connector with the given + /// connector type. /// /// \param connector_type The connector type to filter on (optional). /// \return Connector status. If connector type is given and does not exist, std::nullopt. From 6c3a3ec136f5f0a1f02a77101f4242a509070e74 Mon Sep 17 00:00:00 2001 From: "Maaike Zijderveld, iolar" Date: Mon, 18 Nov 2024 17:11:39 +0100 Subject: [PATCH 18/24] Fix build after merge. Signed-off-by: Maaike Zijderveld, iolar --- lib/ocpp/v201/charge_point.cpp | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/lib/ocpp/v201/charge_point.cpp b/lib/ocpp/v201/charge_point.cpp index 8e1c70d44..ce265dd05 100644 --- a/lib/ocpp/v201/charge_point.cpp +++ b/lib/ocpp/v201/charge_point.cpp @@ -1069,12 +1069,7 @@ void ChargePoint::on_reservation_status(const int32_t reservation_id, const Rese req.reservationUpdateStatus = status; ocpp::Call call(req, this->message_queue->createMessageId()); - this->send(call); -} - -bool ChargePoint::send(CallError call_error) { - this->message_queue->push(call_error); - return true; + this->message_dispatcher->dispatch_call(call); } void ChargePoint::initialize(const std::map& evse_connector_structure, @@ -3453,7 +3448,7 @@ void ChargePoint::handle_reserve_now_request(Call call) { // Reply with the response from the callback. const ocpp::CallResult call_result(response, call.uniqueId); - this->send(call_result); + this->message_dispatcher->dispatch_call_result(call_result); if (response.status == ReserveNowStatusEnum::Accepted) { EVLOG_debug << "Reservation with id " << reservation_request.id << " for " @@ -3483,7 +3478,7 @@ void ChargePoint::handle_cancel_reservation_callback(Call call_result(response, call.uniqueId); - this->send(call_result); + this->message_dispatcher->dispatch_call_result(call_result); } void ChargePoint::send_reserve_now_rejected_response(const MessageId& unique_id, const std::string& status_info) { @@ -3492,7 +3487,7 @@ void ChargePoint::send_reserve_now_rejected_response(const MessageId& unique_id, response.statusInfo = StatusInfo(); response.statusInfo->additionalInfo = status_info; const ocpp::CallResult call_result(response, unique_id); - this->send(call_result); + this->message_dispatcher->dispatch_call_result(call_result); } void ChargePoint::handle_costupdated_req(const Call call) { From 784013102dbf47814f1f9444e1667c051808b70a Mon Sep 17 00:00:00 2001 From: "Maaike Zijderveld, iolar" Date: Fri, 22 Nov 2024 16:08:48 +0100 Subject: [PATCH 19/24] Some changes to check if there is a reservation for an evse / id token (needed for remote start). Signed-off-by: Maaike Zijderveld, iolar --- include/ocpp/common/types.hpp | 9 +++++++++ include/ocpp/v16/charge_point.hpp | 2 +- include/ocpp/v16/charge_point_impl.hpp | 5 +++-- include/ocpp/v201/charge_point.hpp | 12 +++-------- include/ocpp/v201/charge_point_callbacks.hpp | 8 ++++---- lib/ocpp/v16/charge_point.cpp | 2 +- lib/ocpp/v16/charge_point_impl.cpp | 21 +++++++++++++------- lib/ocpp/v201/charge_point.cpp | 21 ++++++++++---------- tests/lib/ocpp/v201/test_charge_point.cpp | 4 ++-- 9 files changed, 48 insertions(+), 36 deletions(-) diff --git a/include/ocpp/common/types.hpp b/include/ocpp/common/types.hpp index 9c48d096a..0458517fe 100644 --- a/include/ocpp/common/types.hpp +++ b/include/ocpp/common/types.hpp @@ -834,6 +834,15 @@ struct CompositeScheduleDefaultLimits { int32_t number_phases; }; +/// \brief Status of a reservation check. +enum class ReservationCheckStatus { + NotReserved, ///< @brief No reservation of this evse and / or id token + ReservedForToken, ///< @brief Reservation for this token. + ReservedForOtherTokenAndParentToken, ///< @brief Reserved for other token and parent token. + ReservedForOtherTokenAndHasNoParentToken, ///< @brief Reserved for other token and reservation has no parent token. + ReservedForOtherTokenAndHasParentToken, ///< @brief Reserved for other token but reservation has a parent token. +}; + } // namespace ocpp #endif diff --git a/include/ocpp/v16/charge_point.hpp b/include/ocpp/v16/charge_point.hpp index 67746d726..7817159ac 100644 --- a/include/ocpp/v16/charge_point.hpp +++ b/include/ocpp/v16/charge_point.hpp @@ -563,7 +563,7 @@ class ChargePoint { /// \param callback /// \ingroup ocpp16_callbacks void register_is_token_reserved_for_connector_callback( - const std::function& callback); + const std::function& callback); /// \brief Registers a callback function for the session cost datatransfer message (California Pricing Requirements) /// \param session_cost_callback The callback. diff --git a/include/ocpp/v16/charge_point_impl.hpp b/include/ocpp/v16/charge_point_impl.hpp index 0f79a59d9..000c246fe 100644 --- a/include/ocpp/v16/charge_point_impl.hpp +++ b/include/ocpp/v16/charge_point_impl.hpp @@ -187,7 +187,8 @@ class ChargePointImpl : ocpp::ChargingStationBase { transaction_updated_callback; std::function transaction_stopped_callback; - std::function is_token_reserved_for_connector_callback; + std::function + is_token_reserved_for_connector_callback; // iso15118 callback std::function& callback); + const std::function& callback); void register_session_cost_callback( const std::function& diff --git a/include/ocpp/v201/charge_point.hpp b/include/ocpp/v201/charge_point.hpp index bc3309a86..984dc1932 100644 --- a/include/ocpp/v201/charge_point.hpp +++ b/include/ocpp/v201/charge_point.hpp @@ -568,16 +568,10 @@ class ChargePoint : public ChargePointInterface, private ocpp::ChargingStationBa /// \param evse The evse id that must be checked. Reservation will be checked for all connectors. /// \param id_token The id token to check if it is reserved for that token. /// \param group_id_token The group id token to check if it is reserved for that group id. - /// \return True when one of the EVSE connectors is reserved for another id token or group id token than the given - /// tokens. - /// If id_token is different than reserved id_token, but group_id_token is equal to reserved group_id_token, - /// returns true. - /// If both are different, returns true. - /// If id_token is equal to reserved id_token or group_id_token is equal, return false. - /// If there is no reservation, return false. + /// \return The status of the reservation for this evse, id token and group id token. /// - bool is_evse_reserved_for_other(EvseInterface& evse, const IdToken& id_token, - const std::optional& group_id_token) const; + ReservationCheckStatus is_evse_reserved_for_other(EvseInterface& evse, const IdToken& id_token, + const std::optional& group_id_token) const; /// /// \brief Check if one of the connectors of the evse is available (both connectors faulted or unavailable or on of diff --git a/include/ocpp/v201/charge_point_callbacks.hpp b/include/ocpp/v201/charge_point_callbacks.hpp index 9450fbc59..7da44d87d 100644 --- a/include/ocpp/v201/charge_point_callbacks.hpp +++ b/include/ocpp/v201/charge_point_callbacks.hpp @@ -79,13 +79,13 @@ struct Callbacks { std::function remote_start_transaction_callback; + /// /// \brief Check if the current reservation for the given evse id is made for the id token / group id token. - /// \return True if evse is reserved for the given id token / group id token, false if it is reserved for another - /// one. + /// \return The reservation check status of this evse / id token. /// - std::function idToken, - const std::optional> groupIdToken)> + std::function idToken, + const std::optional> groupIdToken)> is_reservation_for_token_callback; std::function update_firmware_request_callback; // callback to be called when a variable has been changed by the CSMS diff --git a/lib/ocpp/v16/charge_point.cpp b/lib/ocpp/v16/charge_point.cpp index 69cc94194..b73467f8c 100644 --- a/lib/ocpp/v16/charge_point.cpp +++ b/lib/ocpp/v16/charge_point.cpp @@ -328,7 +328,7 @@ void ChargePoint::register_security_event_callback( } void ChargePoint::register_is_token_reserved_for_connector_callback( - const std::function& callback) { + const std::function& callback) { this->charge_point->register_is_token_reserved_for_connector_callback(callback); } diff --git a/lib/ocpp/v16/charge_point_impl.cpp b/lib/ocpp/v16/charge_point_impl.cpp index 0c9501f69..93ffb71b2 100644 --- a/lib/ocpp/v16/charge_point_impl.cpp +++ b/lib/ocpp/v16/charge_point_impl.cpp @@ -1957,11 +1957,18 @@ void ChargePointImpl::handleRemoteStartTransactionRequest(ocpp::Callis_token_reserved_for_connector_callback != nullptr && - this->status->get_state(connector) == ChargePointStatus::Reserved && - !this->is_token_reserved_for_connector_callback(connector, call.msg.idTag.get())) { - obtainable = false; - continue; + if (this->is_token_reserved_for_connector_callback != nullptr) { + const ocpp::ReservationCheckStatus reservation_status = + is_token_reserved_for_connector_callback(connector, call.msg.idTag.get()); + + const bool is_reserved = + (reservation_status == ocpp::ReservationCheckStatus::ReservedForOtherTokenAndHasNoParentToken || + reservation_status == ocpp::ReservationCheckStatus::ReservedForOtherTokenAndParentToken); + + if (this->status->get_state(connector) == ChargePointStatus::Reserved && is_reserved) { + obtainable = false; + continue; + } } if (obtainable) { @@ -2018,7 +2025,7 @@ void ChargePointImpl::handleRemoteStartTransactionRequest(ocpp::Callprovide_token_callback(call.msg.idTag.get(), referenced_connectors, true); // prevalidated } - }; + } } bool ChargePointImpl::validate_against_cache_entries(CiString<20> id_tag) { @@ -4490,7 +4497,7 @@ void ChargePointImpl::register_security_event_callback( } void ChargePointImpl::register_is_token_reserved_for_connector_callback( - const std::function& callback) { + const std::function& callback) { this->is_token_reserved_for_connector_callback = callback; } diff --git a/lib/ocpp/v201/charge_point.cpp b/lib/ocpp/v201/charge_point.cpp index ce265dd05..a79445933 100644 --- a/lib/ocpp/v201/charge_point.cpp +++ b/lib/ocpp/v201/charge_point.cpp @@ -1968,17 +1968,13 @@ std::optional ChargePoint::get_transaction_evseid(const CiString<36>& t return std::nullopt; } -bool ChargePoint::is_evse_reserved_for_other(EvseInterface& evse, const IdToken& id_token, - const std::optional& group_id_token) const { - const uint32_t connectors = evse.get_number_of_connectors(); +ocpp::ReservationCheckStatus +ChargePoint::is_evse_reserved_for_other(EvseInterface& evse, const IdToken& id_token, + const std::optional& group_id_token) const { const std::optional> groupIdToken = group_id_token.has_value() ? group_id_token.value().idToken : std::optional>{}; - if (!callbacks.is_reservation_for_token_callback(evse.get_id(), id_token.idToken, groupIdToken)) { - return true; - } - - return false; + return callbacks.is_reservation_for_token_callback(evse.get_id(), id_token.idToken, groupIdToken); } bool ChargePoint::is_evse_connector_available(EvseInterface& evse) const { @@ -3184,9 +3180,14 @@ void ChargePoint::handle_remote_start_transaction_request(Call remote_start_transaction_callback_mock; - testing::MockFunction idToken, - const std::optional> groupIdToken)> + testing::MockFunction idToken, + const std::optional> groupIdToken)> is_reservation_for_token_callback_mock; testing::MockFunction update_firmware_request_callback_mock; From d9b337274cafb1d26a870b8d7f9fa920dbf11690 Mon Sep 17 00:00:00 2001 From: "Maaike Zijderveld, iolar" Date: Mon, 25 Nov 2024 12:03:10 +0100 Subject: [PATCH 20/24] Review comments. Signed-off-by: Maaike Zijderveld, iolar --- README.md | 2 +- lib/ocpp/v201/evse.cpp | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 831603012..74688b9ee 100644 --- a/README.md +++ b/README.md @@ -93,7 +93,7 @@ The following table shows the known CSMS with which this library was tested. | E. Transactions | ✅ yes | | F. RemoteControl | ✅ yes | | G. Availability | ✅ yes | -| H. Reservation | WIP | +| H. Reservation | ✅ yes | | I. TariffAndCost | ✅ yes | | J. MeterValues | ✅ yes | | K. SmartCharging | WIP | diff --git a/lib/ocpp/v201/evse.cpp b/lib/ocpp/v201/evse.cpp index 79207b942..41bdecb33 100644 --- a/lib/ocpp/v201/evse.cpp +++ b/lib/ocpp/v201/evse.cpp @@ -221,7 +221,13 @@ std::optional Evse::get_evse_connector_type(const uint32_t connec return std::nullopt; } - return conversions::string_to_connector_enum(connector_type.value()); + try { + return conversions::string_to_connector_enum(connector_type.value()); + } + catch (const StringToEnumException& e) { + EVLOG_warning << "Could not convert to ConnectorEnum: " << connector_type.value(); + return std::nullopt; + } } void Evse::open_transaction(const std::string& transaction_id, const int32_t connector_id, const DateTime& timestamp, From 0bc3c95c230edfd6537546dc1b1d379da45f9c69 Mon Sep 17 00:00:00 2001 From: "Maaike Zijderveld, iolar" Date: Mon, 25 Nov 2024 13:02:20 +0100 Subject: [PATCH 21/24] Fix build after merge. Signed-off-by: Maaike Zijderveld, iolar --- lib/ocpp/v201/charge_point.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ocpp/v201/charge_point.cpp b/lib/ocpp/v201/charge_point.cpp index 036203de2..4a4a71ab6 100644 --- a/lib/ocpp/v201/charge_point.cpp +++ b/lib/ocpp/v201/charge_point.cpp @@ -1067,7 +1067,7 @@ void ChargePoint::on_reservation_status(const int32_t reservation_id, const Rese req.reservationId = reservation_id; req.reservationUpdateStatus = status; - ocpp::Call call(req, this->message_queue->createMessageId()); + ocpp::Call call(req); this->message_dispatcher->dispatch_call(call); } From c682827aaa00ccf970d9a16792245f49b661ee63 Mon Sep 17 00:00:00 2001 From: "Maaike Zijderveld, iolar" Date: Mon, 25 Nov 2024 14:02:25 +0100 Subject: [PATCH 22/24] Review comments Signed-off-by: Maaike Zijderveld, iolar --- include/ocpp/common/types.hpp | 3 +-- lib/ocpp/v16/charge_point_impl.cpp | 3 +-- lib/ocpp/v201/charge_point.cpp | 3 +-- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/include/ocpp/common/types.hpp b/include/ocpp/common/types.hpp index 0458517fe..6fdcd1072 100644 --- a/include/ocpp/common/types.hpp +++ b/include/ocpp/common/types.hpp @@ -838,8 +838,7 @@ struct CompositeScheduleDefaultLimits { enum class ReservationCheckStatus { NotReserved, ///< @brief No reservation of this evse and / or id token ReservedForToken, ///< @brief Reservation for this token. - ReservedForOtherTokenAndParentToken, ///< @brief Reserved for other token and parent token. - ReservedForOtherTokenAndHasNoParentToken, ///< @brief Reserved for other token and reservation has no parent token. + ReservedForOtherToken, ///< @brief Reserved for other token and reservation has no parent token or parent token does not match. ReservedForOtherTokenAndHasParentToken, ///< @brief Reserved for other token but reservation has a parent token. }; diff --git a/lib/ocpp/v16/charge_point_impl.cpp b/lib/ocpp/v16/charge_point_impl.cpp index d1ef036fa..9cc684576 100644 --- a/lib/ocpp/v16/charge_point_impl.cpp +++ b/lib/ocpp/v16/charge_point_impl.cpp @@ -1962,8 +1962,7 @@ void ChargePointImpl::handleRemoteStartTransactionRequest(ocpp::Callstatus->get_state(connector) == ChargePointStatus::Reserved && is_reserved) { obtainable = false; diff --git a/lib/ocpp/v201/charge_point.cpp b/lib/ocpp/v201/charge_point.cpp index 4a4a71ab6..db85d9342 100644 --- a/lib/ocpp/v201/charge_point.cpp +++ b/lib/ocpp/v201/charge_point.cpp @@ -3193,8 +3193,7 @@ void ChargePoint::handle_remote_start_transaction_request(Call Date: Mon, 25 Nov 2024 16:55:33 +0100 Subject: [PATCH 23/24] Formatting Signed-off-by: Maaike Zijderveld, iolar --- include/ocpp/common/types.hpp | 9 +++++---- lib/ocpp/v16/charge_point_impl.cpp | 3 +-- lib/ocpp/v201/charge_point.cpp | 3 +-- lib/ocpp/v201/evse.cpp | 3 +-- 4 files changed, 8 insertions(+), 10 deletions(-) diff --git a/include/ocpp/common/types.hpp b/include/ocpp/common/types.hpp index 6fdcd1072..4f3932c19 100644 --- a/include/ocpp/common/types.hpp +++ b/include/ocpp/common/types.hpp @@ -836,10 +836,11 @@ struct CompositeScheduleDefaultLimits { /// \brief Status of a reservation check. enum class ReservationCheckStatus { - NotReserved, ///< @brief No reservation of this evse and / or id token - ReservedForToken, ///< @brief Reservation for this token. - ReservedForOtherToken, ///< @brief Reserved for other token and reservation has no parent token or parent token does not match. - ReservedForOtherTokenAndHasParentToken, ///< @brief Reserved for other token but reservation has a parent token. + NotReserved, ///< @brief No reservation of this evse and / or id token + ReservedForToken, ///< @brief Reservation for this token. + ReservedForOtherToken, ///< @brief Reserved for other token and reservation has no parent token or parent token does + ///< not match. + ReservedForOtherTokenAndHasParentToken, ///< @brief Reserved for other token but reservation has a parent token. }; } // namespace ocpp diff --git a/lib/ocpp/v16/charge_point_impl.cpp b/lib/ocpp/v16/charge_point_impl.cpp index 9cc684576..3f1399590 100644 --- a/lib/ocpp/v16/charge_point_impl.cpp +++ b/lib/ocpp/v16/charge_point_impl.cpp @@ -1961,8 +1961,7 @@ void ChargePointImpl::handleRemoteStartTransactionRequest(ocpp::Callstatus->get_state(connector) == ChargePointStatus::Reserved && is_reserved) { obtainable = false; diff --git a/lib/ocpp/v201/charge_point.cpp b/lib/ocpp/v201/charge_point.cpp index db85d9342..4e2759db7 100644 --- a/lib/ocpp/v201/charge_point.cpp +++ b/lib/ocpp/v201/charge_point.cpp @@ -3192,8 +3192,7 @@ void ChargePoint::handle_remote_start_transaction_request(Call Evse::get_evse_connector_type(const uint32_t connec try { return conversions::string_to_connector_enum(connector_type.value()); - } - catch (const StringToEnumException& e) { + } catch (const StringToEnumException& e) { EVLOG_warning << "Could not convert to ConnectorEnum: " << connector_type.value(); return std::nullopt; } From 8e9a5d751da001c7efe7806eef014c6a3ad2b85d Mon Sep 17 00:00:00 2001 From: "Maaike Zijderveld, iolar" Date: Tue, 26 Nov 2024 10:24:11 +0100 Subject: [PATCH 24/24] Bump version Signed-off-by: Maaike Zijderveld, iolar --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1dd244a7a..684ba2c24 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.14) project(ocpp - VERSION 0.20.0 + VERSION 0.21.0 DESCRIPTION "A C++ implementation of the Open Charge Point Protocol" LANGUAGES CXX )