From 53ed32b9ff0f9905fe3eb1131937e109d3e45883 Mon Sep 17 00:00:00 2001 From: pietfried Date: Mon, 1 Apr 2024 12:47:11 +0200 Subject: [PATCH 1/4] Changes to be able to support configurable TxStart and TxStop points: * Now also handling TransactionEvent.conf for Updated (not only Started) * IdToken became optional for TransactionEvent(Started). To be able to support e.g. TxStartPoint EVConnected * Added on_authorized handler to report when an Evse has been authorized. This allows sending of TransactionEvent(Updated) with IdToken * Added trigger_reason as argument to on_charging_state_changed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: marcemmers <35759328+marcemmers@users.noreply.github.com> Signed-off-by: Piet Gömpel <37657534+Pietfried@users.noreply.github.com> Signed-off-by: pietfried --- include/ocpp/v201/charge_point.hpp | 11 ++- include/ocpp/v201/evse.hpp | 6 +- include/ocpp/v201/transaction.hpp | 2 +- lib/ocpp/v201/charge_point.cpp | 106 ++++++++++++++++++----------- lib/ocpp/v201/evse.cpp | 2 +- 5 files changed, 80 insertions(+), 47 deletions(-) diff --git a/include/ocpp/v201/charge_point.hpp b/include/ocpp/v201/charge_point.hpp index ef69ad9ae..0da124e73 100644 --- a/include/ocpp/v201/charge_point.hpp +++ b/include/ocpp/v201/charge_point.hpp @@ -458,7 +458,7 @@ class ChargePoint : ocpp::ChargingStationBase { void handle_get_local_authorization_list_version_req(Call call); // Functional Block E: Transaction - void handle_start_transaction_event_response(const EnhancedMessage& message); + void handle_transaction_event_response(const EnhancedMessage& message); void handle_get_transaction_status(const Call call); // Function Block F: Remote transaction control @@ -613,7 +613,7 @@ class ChargePoint : ocpp::ChargingStationBase { /// \param charging_state The new charging state void on_transaction_started(const int32_t evse_id, const int32_t connector_id, const std::string& session_id, const DateTime& timestamp, const ocpp::v201::TriggerReasonEnum trigger_reason, - const MeterValue& meter_start, const IdToken& id_token, + const MeterValue& meter_start, const std::optional& id_token, const std::optional& group_id_token, const std::optional& reservation_id, const std::optional& remote_start_id, const ChargingStateEnum charging_state); @@ -635,6 +635,9 @@ class ChargePoint : ocpp::ChargingStationBase { /// \param connector_id void on_session_finished(const int32_t evse_id, const int32_t connector_id); + /// \brief Event handler that should be called when the given \p id_token is authorized + void on_authorized(const int32_t evse_id, const int32_t connector_id, const IdToken& id_token); + /// \brief Event handler that should be called when a new meter value is present /// \param evse_id /// \param meter_value @@ -661,8 +664,10 @@ class ChargePoint : ocpp::ChargingStationBase { /// \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. + /// \param trigger_reason The trigger reason of the event. Defaults to ChargingStateChanged /// \return True on success. False if evse id does not exist. - bool on_charging_state_changed(const uint32_t evse_id, ChargingStateEnum charging_state); + bool on_charging_state_changed(const uint32_t evse_id, const ChargingStateEnum charging_state, + const TriggerReasonEnum trigger_reason = TriggerReasonEnum::ChargingStateChanged); /// \brief Validates provided \p id_token \p certificate and \p ocsp_request_data using CSMS, AuthCache or AuthList /// \param id_token diff --git a/include/ocpp/v201/evse.hpp b/include/ocpp/v201/evse.hpp index 8c51279cf..4e668d248 100644 --- a/include/ocpp/v201/evse.hpp +++ b/include/ocpp/v201/evse.hpp @@ -41,8 +41,8 @@ class EvseInterface { /// \param sampled_data_tx_updated_interval Interval between sampling of metering (or other) data, intended to /// be transmitted via TransactionEventRequest (eventType = Updated) messages virtual void open_transaction(const std::string& transaction_id, const int32_t connector_id, - const DateTime& timestamp, const MeterValue& meter_start, const IdToken& id_token, - const std::optional& group_id_token, + const DateTime& timestamp, const MeterValue& meter_start, + const std::optional& id_token, const std::optional& group_id_token, const std::optional reservation_id, const std::chrono::seconds sampled_data_tx_updated_interval, const std::chrono::seconds sampled_data_tx_ended_interval, @@ -170,7 +170,7 @@ class Evse : public EvseInterface { uint32_t get_number_of_connectors(); void open_transaction(const std::string& transaction_id, const int32_t connector_id, const DateTime& timestamp, - const MeterValue& meter_start, const IdToken& id_token, + const MeterValue& meter_start, const std::optional& id_token, const std::optional& group_id_token, const std::optional reservation_id, const std::chrono::seconds sampled_data_tx_updated_interval, const std::chrono::seconds sampled_data_tx_ended_interval, diff --git a/include/ocpp/v201/transaction.hpp b/include/ocpp/v201/transaction.hpp index adfcc8e11..d02030851 100644 --- a/include/ocpp/v201/transaction.hpp +++ b/include/ocpp/v201/transaction.hpp @@ -12,7 +12,7 @@ namespace v201 { /// \brief Struct that enhances the OCPP Transaction by some meta data and functionality struct EnhancedTransaction : public Transaction { - IdToken id_token; + std::optional id_token; std::optional group_id_token; std::optional reservation_id; int32_t connector_id; diff --git a/lib/ocpp/v201/charge_point.cpp b/lib/ocpp/v201/charge_point.cpp index 6ebb455aa..065cf91c0 100644 --- a/lib/ocpp/v201/charge_point.cpp +++ b/lib/ocpp/v201/charge_point.cpp @@ -293,11 +293,14 @@ ChargePoint::on_get_15118_ev_certificate_request(const Get15118EVCertificateRequ return call_result.msg; } -void ChargePoint::on_transaction_started( - const int32_t evse_id, const int32_t connector_id, const std::string& session_id, const DateTime& timestamp, - const ocpp::v201::TriggerReasonEnum trigger_reason, const MeterValue& meter_start, const IdToken& id_token, - const std::optional& group_id_token, const std::optional& reservation_id, - const std::optional& remote_start_id, const ChargingStateEnum charging_state) { +void ChargePoint::on_transaction_started(const int32_t evse_id, const int32_t connector_id, + const std::string& session_id, const DateTime& timestamp, + const ocpp::v201::TriggerReasonEnum trigger_reason, + const MeterValue& meter_start, const std::optional& id_token, + const std::optional& group_id_token, + const std::optional& reservation_id, + const std::optional& remote_start_id, + const ChargingStateEnum charging_state) { this->evses.at(evse_id)->open_transaction( session_id, connector_id, timestamp, meter_start, id_token, group_id_token, reservation_id, @@ -435,6 +438,27 @@ void ChargePoint::on_session_finished(const int32_t evse_id, const int32_t conne this->evses.at(evse_id)->submit_event(connector_id, ConnectorEvent::PlugOut); } +void ChargePoint::on_authorized(const int32_t evse_id, const int32_t connector_id, const IdToken& id_token) { + if (this->evses.at(evse_id)->get_transaction() == nullptr) { + // nothing to report in case transaction is not yet open + return; + } + + std::unique_ptr& transaction = + this->evses.at(static_cast(evse_id))->get_transaction(); + + if (transaction->id_token.has_value()) { + // if transactions id_token is already set, it is assumed it has already been reported + return; + } + + // set id_token of transaction and send TransactionEvent(Updated) with id_token + transaction->id_token = id_token; + this->transaction_event_req(TransactionEventEnum::Updated, ocpp::DateTime(), transaction->get_transaction(), + TriggerReasonEnum::Authorized, transaction->get_seq_no(), std::nullopt, std::nullopt, + id_token, std::nullopt, std::nullopt, this->is_offline(), std::nullopt); +} + void ChargePoint::on_meter_value(const int32_t evse_id, const MeterValue& meter_value) { if (evse_id == 0) { // if evseId = 0 then store in the chargepoint metervalues @@ -518,7 +542,8 @@ void ChargePoint::on_reserved(const int32_t evse_id, const int32_t connector_id) this->evses.at(evse_id)->submit_event(connector_id, ConnectorEvent::Reserve); } -bool ChargePoint::on_charging_state_changed(const uint32_t evse_id, ChargingStateEnum charging_state) { +bool ChargePoint::on_charging_state_changed(const uint32_t evse_id, const ChargingStateEnum charging_state, + const TriggerReasonEnum trigger_reason) { if (this->evses.find(static_cast(evse_id)) != this->evses.end()) { std::unique_ptr& transaction = this->evses.at(static_cast(evse_id))->get_transaction(); @@ -528,8 +553,7 @@ bool ChargePoint::on_charging_state_changed(const uint32_t evse_id, ChargingStat } else { transaction->chargingState = charging_state; this->transaction_event_req(TransactionEventEnum::Updated, DateTime(), transaction->get_transaction(), - TriggerReasonEnum::ChargingStateChanged, transaction->get_seq_no(), - std::nullopt, + trigger_reason, transaction->get_seq_no(), std::nullopt, this->evses.at(static_cast(evse_id))->get_evse_info(), std::nullopt, std::nullopt, std::nullopt, this->is_offline(), std::nullopt); } @@ -1080,7 +1104,7 @@ void ChargePoint::handle_message(const EnhancedMessage& messa this->handle_change_availability_req(json_message); break; case MessageType::TransactionEventResponse: - this->handle_start_transaction_event_response(message); + this->handle_transaction_event_response(message); break; case MessageType::RequestStartTransaction: this->handle_remote_start_transaction_request(json_message); @@ -1919,9 +1943,9 @@ void ChargePoint::transaction_event_req(const TransactionEventEnum& event_type, remote_start_id_per_evse.erase(it); } - if (event_type == TransactionEventEnum::Started and (!evse.has_value() or !id_token.has_value())) { - EVLOG_error << "Request to send TransactionEvent(Started) without given evse or id_token. These properties " - "are required for this eventType \"Started\"!"; + if (event_type == TransactionEventEnum::Started and (!evse.has_value())) { + EVLOG_error << "Request to send TransactionEvent(Started) without given evse. This property " + "is required for this eventType \"Started\"!"; return; } @@ -2449,7 +2473,7 @@ void ChargePoint::handle_clear_cache_req(Call call) { this->send(call_result); } -void ChargePoint::handle_start_transaction_event_response(const EnhancedMessage& message) { +void ChargePoint::handle_transaction_event_response(const EnhancedMessage& message) { CallResult call_result = message.message; const Call& original_call = message.call_message; const auto& original_msg = original_call.msg; @@ -2463,39 +2487,43 @@ void ChargePoint::handle_start_transaction_event_response(const EnhancedMessage< return; } + const int32_t evse_id = original_msg.evse.value().id; + + const auto msg = call_result.msg; + + if (!msg.idTokenInfo.has_value()) { + return; + } + if (!original_msg.idToken.has_value()) { - EVLOG_error << "Start transaction event sent without without idToken info"; + EVLOG_error + << "TransactionEvent.conf contains idTokenInfo when no idToken was part of the TransactionEvent.req"; return; } - const int32_t evse_id = original_msg.evse.value().id; const IdToken& id_token = original_msg.idToken.value(); - const auto msg = call_result.msg; - if (msg.idTokenInfo.has_value()) { - // C03.FR.0x and C05.FR.01: We SHALL NOT store central information in the Authorization Cache - // C10.FR.05 - if (id_token.type != IdTokenEnum::Central and - this->device_model->get_optional_value(ControllerComponentVariables::AuthCacheCtrlrEnabled) - .value_or(true)) { - auto id_token_info = msg.idTokenInfo.value(); - this->update_id_token_cache_lifetime(id_token_info); - this->database_handler->authorization_cache_insert_entry(utils::generate_token_hash(id_token), - id_token_info); - this->update_authorization_cache_size(); - } - if (msg.idTokenInfo.value().status != AuthorizationStatusEnum::Accepted) { - if (this->device_model->get_value(ControllerComponentVariables::StopTxOnInvalidId)) { - this->callbacks.stop_transaction_callback(evse_id, ReasonEnum::DeAuthorized); + // C03.FR.0x and C05.FR.01: We SHALL NOT store central information in the Authorization Cache + // C10.FR.05 + if (id_token.type != IdTokenEnum::Central and + this->device_model->get_optional_value(ControllerComponentVariables::AuthCacheCtrlrEnabled) + .value_or(true)) { + auto id_token_info = msg.idTokenInfo.value(); + this->update_id_token_cache_lifetime(id_token_info); + this->database_handler->authorization_cache_insert_entry(utils::generate_token_hash(id_token), id_token_info); + this->update_authorization_cache_size(); + } + if (msg.idTokenInfo.value().status != AuthorizationStatusEnum::Accepted) { + if (this->device_model->get_value(ControllerComponentVariables::StopTxOnInvalidId)) { + this->callbacks.stop_transaction_callback(evse_id, ReasonEnum::DeAuthorized); + } else { + if (this->device_model->get_optional_value(ControllerComponentVariables::MaxEnergyOnInvalidId) + .has_value()) { + // Energy delivery to the EV SHALL be allowed until the amount of energy specified in + // MaxEnergyOnInvalidId has been reached. + this->evses.at(evse_id)->start_checking_max_energy_on_invalid_id(); } else { - if (this->device_model->get_optional_value(ControllerComponentVariables::MaxEnergyOnInvalidId) - .has_value()) { - // Energy delivery to the EV SHALL be allowed until the amount of energy specified in - // MaxEnergyOnInvalidId has been reached. - this->evses.at(evse_id)->start_checking_max_energy_on_invalid_id(); - } else { - this->callbacks.pause_charging_callback(evse_id); - } + this->callbacks.pause_charging_callback(evse_id); } } } diff --git a/lib/ocpp/v201/evse.cpp b/lib/ocpp/v201/evse.cpp index 119ddbba6..d677710c3 100644 --- a/lib/ocpp/v201/evse.cpp +++ b/lib/ocpp/v201/evse.cpp @@ -70,7 +70,7 @@ uint32_t Evse::get_number_of_connectors() { } void Evse::open_transaction(const std::string& transaction_id, const int32_t connector_id, const DateTime& timestamp, - const MeterValue& meter_start, const IdToken& id_token, + const MeterValue& meter_start, const std::optional& id_token, const std::optional& group_id_token, const std::optional reservation_id, const std::chrono::seconds sampled_data_tx_updated_interval, const std::chrono::seconds sampled_data_tx_ended_interval, From a95e494d57cb361204b274dc52981eeb9d84dbc2 Mon Sep 17 00:00:00 2001 From: pietfried Date: Thu, 4 Apr 2024 16:34:45 +0200 Subject: [PATCH 2/4] make TxStartPoint and TxStopPoint RW and remove ParkingBayOccupancy from valuesList Signed-off-by: pietfried --- config/v201/component_schemas/standardized/TxCtrlr.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/config/v201/component_schemas/standardized/TxCtrlr.json b/config/v201/component_schemas/standardized/TxCtrlr.json index 511334385..ad8763cf7 100644 --- a/config/v201/component_schemas/standardized/TxCtrlr.json +++ b/config/v201/component_schemas/standardized/TxCtrlr.json @@ -117,14 +117,14 @@ "TxStartPoint": { "variable_name": "TxStartPoint", "characteristics": { - "valuesList": "ParkingBayOccupancy,EVConnected,Authorized,PowerPathClosed,EnergyTransfer,DataSigned", + "valuesList": "EVConnected,Authorized,PowerPathClosed,EnergyTransfer,DataSigned", "supportsMonitoring": true, "dataType": "MemberList" }, "attributes": [ { "type": "Actual", - "mutability": "ReadOnly" + "mutability": "ReadWrite" } ], "description": "Defines when the Charging Station starts a new transaction", @@ -134,14 +134,14 @@ "TxStopPoint": { "variable_name": "TxStopPoint", "characteristics": { - "valuesList": "ParkingBayOccupancy,EVConnected,Authorized,PowerPathClosed,EnergyTransfer", + "valuesList": "EVConnected,Authorized,PowerPathClosed,EnergyTransfer", "supportsMonitoring": true, "dataType": "MemberList" }, "attributes": [ { "type": "Actual", - "mutability": "ReadOnly" + "mutability": "ReadWrite" } ], "description": "Defines when the Charging Station ends a transaction", From 7677ddd29df4722458e65fcf26c99ccc272c363c Mon Sep 17 00:00:00 2001 From: pietfried Date: Mon, 8 Apr 2024 09:45:19 +0200 Subject: [PATCH 3/4] * Adressed comments * Stopping transactions if running on multiple evses in case status is not Accepted Signed-off-by: pietfried --- lib/ocpp/v201/charge_point.cpp | 40 +++++++++++++++++++++------------- 1 file changed, 25 insertions(+), 15 deletions(-) diff --git a/lib/ocpp/v201/charge_point.cpp b/lib/ocpp/v201/charge_point.cpp index 065cf91c0..d6278a8e6 100644 --- a/lib/ocpp/v201/charge_point.cpp +++ b/lib/ocpp/v201/charge_point.cpp @@ -1943,12 +1943,6 @@ void ChargePoint::transaction_event_req(const TransactionEventEnum& event_type, remote_start_id_per_evse.erase(it); } - if (event_type == TransactionEventEnum::Started and (!evse.has_value())) { - EVLOG_error << "Request to send TransactionEvent(Started) without given evse. This property " - "is required for this eventType \"Started\"!"; - return; - } - this->send(call); if (this->callbacks.transaction_event_callback.has_value()) { @@ -2478,20 +2472,15 @@ void ChargePoint::handle_transaction_event_response(const EnhancedMessage& original_call = message.call_message; const auto& original_msg = original_call.msg; - if (original_msg.eventType != TransactionEventEnum::Started) { + if (original_msg.eventType == TransactionEventEnum::Ended) { + // nothing to do for TransactionEventEnum::Ended return; } - if (!original_msg.evse.has_value()) { - EVLOG_error << "Start transaction event sent without without evse id"; - return; - } - - const int32_t evse_id = original_msg.evse.value().id; - const auto msg = call_result.msg; if (!msg.idTokenInfo.has_value()) { + // nothing to do when the response does not contain idTokenInfo return; } @@ -2513,7 +2502,28 @@ void ChargePoint::handle_transaction_event_response(const EnhancedMessagedatabase_handler->authorization_cache_insert_entry(utils::generate_token_hash(id_token), id_token_info); this->update_authorization_cache_size(); } - if (msg.idTokenInfo.value().status != AuthorizationStatusEnum::Accepted) { + + if (msg.idTokenInfo.value().status == AuthorizationStatusEnum::Accepted) { + // nothing to do in case status is accepted + return; + } + + std::vector evse_ids; // this is a vector because the id token could be active for multiple transactions + if (original_msg.evse.has_value()) { + evse_ids.push_back(original_msg.evse.value().id); + } else { + // add every evse_id with active transaction and given token + for (const auto& [evse_id, evse] : this->evses) { + if (evse->get_transaction() != nullptr and evse->get_transaction()->id_token.has_value()) { + if (evse->get_transaction()->id_token.value().idToken.get() == id_token.idToken.get()) { + evse_ids.push_back(evse_id); + } + } + } + } + + // post handling of transactions in case status is not Accepted + for (const auto evse_id : evse_ids) { if (this->device_model->get_value(ControllerComponentVariables::StopTxOnInvalidId)) { this->callbacks.stop_transaction_callback(evse_id, ReasonEnum::DeAuthorized); } else { From b419bc9ef9bcbf4002c963d4c6256f947cc74648 Mon Sep 17 00:00:00 2001 From: pietfried Date: Tue, 9 Apr 2024 16:02:25 +0200 Subject: [PATCH 4/4] assigning transaction to variable to not request it several times Signed-off-by: pietfried --- lib/ocpp/v201/charge_point.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/ocpp/v201/charge_point.cpp b/lib/ocpp/v201/charge_point.cpp index d6278a8e6..3490f3407 100644 --- a/lib/ocpp/v201/charge_point.cpp +++ b/lib/ocpp/v201/charge_point.cpp @@ -2514,8 +2514,9 @@ void ChargePoint::handle_transaction_event_response(const EnhancedMessageevses) { - if (evse->get_transaction() != nullptr and evse->get_transaction()->id_token.has_value()) { - if (evse->get_transaction()->id_token.value().idToken.get() == id_token.idToken.get()) { + const auto& transaction = evse->get_transaction(); + if (transaction != nullptr and transaction->id_token.has_value()) { + if (transaction->id_token.value().idToken.get() == id_token.idToken.get()) { evse_ids.push_back(evse_id); } }