diff --git a/include/ocpp/common/message_queue.hpp b/include/ocpp/common/message_queue.hpp index acfe26412..fe6f97547 100644 --- a/include/ocpp/common/message_queue.hpp +++ b/include/ocpp/common/message_queue.hpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -610,6 +611,19 @@ template class MessageQueue { return false; } + bool contains_stop_transaction_message(const int32_t transaction_id) { + std::lock_guard lk(this->message_mutex); + for (const auto control_message : this->transaction_message_queue) { + if (control_message->messageType == v16::MessageType::StopTransaction) { + v16::StopTransactionRequest req = control_message->message.at(CALL_PAYLOAD); + if (req.transactionId == transaction_id) { + return true; + } + } + } + return false; + } + /// \brief Set transaction_message_attempts to given \p transaction_message_attempts void update_transaction_message_attempts(const int transaction_message_attempts) { this->transaction_message_attempts = transaction_message_attempts; diff --git a/include/ocpp/v16/charge_point_impl.hpp b/include/ocpp/v16/charge_point_impl.hpp index 15b2c37ab..1aa236b92 100644 --- a/include/ocpp/v16/charge_point_impl.hpp +++ b/include/ocpp/v16/charge_point_impl.hpp @@ -179,7 +179,7 @@ class ChargePointImpl : ocpp::ChargingStationBase { void init_state_machine(const std::map& connector_status_map); WebsocketConnectionOptions get_ws_connection_options(); void message_callback(const std::string& message); - void handle_message(const json& json_message, MessageType message_type); + void handle_message(const EnhancedMessage& message); bool allowed_to_send_message(json::array_t message_type); template bool send(Call call); template std::future> send_async(Call call); @@ -246,7 +246,7 @@ class ChargePointImpl : ocpp::ChargingStationBase { void handleRemoteStopTransactionRequest(Call call); void handleResetRequest(Call call); void handleStartTransactionResponse(CallResult call_result); - void handleStopTransactionResponse(CallResult call_result); + void handleStopTransactionResponse(const EnhancedMessage& message); void handleUnlockConnectorRequest(Call call); void handleHeartbeatResponse(CallResult call_result); diff --git a/include/ocpp/v16/database_handler.hpp b/include/ocpp/v16/database_handler.hpp index f55e04123..fc64755a8 100644 --- a/include/ocpp/v16/database_handler.hpp +++ b/include/ocpp/v16/database_handler.hpp @@ -72,8 +72,9 @@ class DatabaseHandler : public ocpp::common::DatabaseHandlerBase { void update_transaction(const std::string& session_id, int32_t meter_stop, const std::string& time_end, std::optional> id_tag_end, std::optional stop_reason); - /// \brief Updates the CSMS_ACK column for the transaction with the given \p session_id in the TRANSACTIONS table - void update_transaction_csms_ack(const std::string& session_id); + /// \brief Updates the CSMS_ACK column for the transaction with the given \p transaction_id in the TRANSACTIONS + /// table + void update_transaction_csms_ack(const int32_t transaction_id); /// \brief Updates the METER_LAST and METER_LAST_TIME column for the transaction with the given \p session_id in the /// TRANSACTIONS table diff --git a/lib/ocpp/v16/charge_point_impl.cpp b/lib/ocpp/v16/charge_point_impl.cpp index 44736528f..649c7dfad 100644 --- a/lib/ocpp/v16/charge_point_impl.cpp +++ b/lib/ocpp/v16/charge_point_impl.cpp @@ -308,10 +308,6 @@ void ChargePointImpl::update_clock_aligned_meter_values_interval() { void ChargePointImpl::stop_pending_transactions() { const auto transactions = this->database_handler->get_transactions(true); - if (!transactions.empty()) { - EVLOG_info << "Sending StopTransaction.req for " << transactions.size() - << " open transactions that haven't been acknowledged by CSMS."; - } for (const auto& transaction_entry : transactions) { std::shared_ptr transaction = std::make_shared( transaction_entry.connector, transaction_entry.session_id, CiString<20>(transaction_entry.id_tag_start), @@ -330,11 +326,17 @@ void ChargePointImpl::stop_pending_transactions() { const auto stop_energy_wh = std::make_shared(timestamp, meter_stop); transaction->add_stop_energy_wh(stop_energy_wh); transaction->set_transaction_id(transaction_entry.transaction_id); - this->transaction_handler->add_transaction(transaction); - this->stop_transaction(transaction_entry.connector, Reason::PowerLoss, std::nullopt); - this->database_handler->update_transaction(transaction_entry.session_id, meter_stop, timestamp.to_rfc3339(), - std::nullopt, Reason::PowerLoss); + // StopTransaction.req is not yet queued for the transaction in the database, so we add the transaction to the + // transaction_handler and initiate a StopTransaction.req + if (!this->message_queue->contains_stop_transaction_message(transaction_entry.transaction_id)) { + EVLOG_info << "Sending StopTransaction.req for transaction with id: " << transaction_entry.transaction_id + << " because it hasn't been acknowledged by CSMS."; + this->transaction_handler->add_transaction(transaction); + this->stop_transaction(transaction_entry.connector, Reason::PowerLoss, std::nullopt); + this->database_handler->update_transaction(transaction_entry.session_id, meter_stop, timestamp.to_rfc3339(), + std::nullopt, Reason::PowerLoss); + } } } @@ -935,13 +937,13 @@ void ChargePointImpl::message_callback(const std::string& message) { if (enhanced_message.messageType == MessageType::BootNotificationResponse) { this->handleBootNotificationResponse(json_message); } else { - this->handle_message(json_message, enhanced_message.messageType); + this->handle_message(enhanced_message); } } break; } case ChargePointConnectionState::Booted: { - this->handle_message(json_message, enhanced_message.messageType); + this->handle_message(enhanced_message); break; } @@ -959,9 +961,10 @@ void ChargePointImpl::message_callback(const std::string& message) { } } -void ChargePointImpl::handle_message(const json& json_message, MessageType message_type) { +void ChargePointImpl::handle_message(const EnhancedMessage& message) { + const auto& json_message = message.message; // lots of messages are allowed here - switch (message_type) { + switch (message.messageType) { case MessageType::AuthorizeResponse: // handled by authorize_id_tag future @@ -1012,7 +1015,7 @@ void ChargePointImpl::handle_message(const json& json_message, MessageType messa break; case MessageType::StopTransactionResponse: - this->handleStopTransactionResponse(json_message); + this->handleStopTransactionResponse(message); break; case MessageType::UnlockConnector: @@ -1122,7 +1125,6 @@ void ChargePointImpl::handleBootNotificationResponse(ocpp::CallResultstatus_notification(connector, ChargePointErrorCode::NoError, this->status->get_state(connector)); } - this->stop_pending_transactions(); this->message_queue->get_transaction_messages_from_db(); if (this->is_pnc_enabled()) { @@ -1139,6 +1141,8 @@ void ChargePointImpl::handleBootNotificationResponse(ocpp::CallResultocsp_request_timer->timeout(INITIAL_CERTIFICATE_REQUESTS_DELAY); } + this->stop_pending_transactions(); + break; } case RegistrationStatus::Pending: @@ -1620,76 +1624,90 @@ void ChargePointImpl::handleStartTransactionResponse(ocpp::CallResulttransaction_handler->get_transaction(call_result.uniqueId); - // this can happen when a chargepoint was offline during transaction and StopTransaction.req is already queued - if (transaction->is_finished()) { - this->message_queue->add_stopped_transaction_id(transaction->get_stop_transaction_message_id(), - start_transaction_response.transactionId); - } - this->message_queue->notify_start_transaction_handled(call_result.uniqueId.get(), - start_transaction_response.transactionId); - int32_t connector = transaction->get_connector(); - transaction->set_transaction_id(start_transaction_response.transactionId); + if (transaction != nullptr) { + // this can happen when a chargepoint was offline during transaction and StopTransaction.req is already queued + if (transaction->is_finished()) { + this->message_queue->add_stopped_transaction_id(transaction->get_stop_transaction_message_id(), + start_transaction_response.transactionId); + } + this->message_queue->notify_start_transaction_handled(call_result.uniqueId.get(), + start_transaction_response.transactionId); + int32_t connector = transaction->get_connector(); + transaction->set_transaction_id(start_transaction_response.transactionId); - this->database_handler->update_transaction(transaction->get_session_id(), start_transaction_response.transactionId, - call_result.msg.idTagInfo.parentIdTag); + this->database_handler->update_transaction(transaction->get_session_id(), + start_transaction_response.transactionId, + call_result.msg.idTagInfo.parentIdTag); - auto idTag = transaction->get_id_tag(); - this->database_handler->insert_or_update_authorization_cache_entry(idTag, start_transaction_response.idTagInfo); + auto idTag = transaction->get_id_tag(); + this->database_handler->insert_or_update_authorization_cache_entry(idTag, start_transaction_response.idTagInfo); - if (start_transaction_response.idTagInfo.status != AuthorizationStatus::Accepted) { - this->pause_charging_callback(connector); - if (this->configuration->getStopTransactionOnInvalidId()) { - this->stop_transaction_callback(connector, Reason::DeAuthorized); + if (start_transaction_response.idTagInfo.status != AuthorizationStatus::Accepted) { + this->pause_charging_callback(connector); + if (this->configuration->getStopTransactionOnInvalidId()) { + this->stop_transaction_callback(connector, Reason::DeAuthorized); + } + } else if (this->transaction_started_callback != nullptr) { + this->transaction_started_callback(connector, start_transaction_response.transactionId); } - } else if (this->transaction_started_callback != nullptr) { - this->transaction_started_callback(connector, start_transaction_response.transactionId); + } else { + EVLOG_warning << "Received StartTransaction.conf for transaction that is not known to transaction_handler"; } } -void ChargePointImpl::handleStopTransactionResponse(ocpp::CallResult call_result) { +void ChargePointImpl::handleStopTransactionResponse(const EnhancedMessage& message) { + + CallResult call_result = message.message; + const Call& original_call = message.call_message; StopTransactionResponse stop_transaction_response = call_result.msg; const auto transaction = this->transaction_handler->get_transaction(call_result.uniqueId); - int32_t connector = transaction->get_connector(); - if (stop_transaction_response.idTagInfo) { - auto id_tag = this->transaction_handler->get_authorized_id_tag(call_result.uniqueId.get()); - if (id_tag) { - this->database_handler->insert_or_update_authorization_cache_entry( - id_tag.value(), stop_transaction_response.idTagInfo.value()); + if (transaction != nullptr) { + int32_t connector = transaction->get_connector(); + + if (stop_transaction_response.idTagInfo) { + auto id_tag = this->transaction_handler->get_authorized_id_tag(call_result.uniqueId.get()); + if (id_tag) { + this->database_handler->insert_or_update_authorization_cache_entry( + id_tag.value(), stop_transaction_response.idTagInfo.value()); + } } - } - // perform a queued connector availability change - bool change_queued = false; - AvailabilityType connector_availability; - { - std::lock_guard change_availability_lock(change_availability_mutex); - change_queued = this->change_availability_queue.count(connector) != 0; - connector_availability = this->change_availability_queue[connector]; - this->change_availability_queue.erase(connector); - } + // perform a queued connector availability change + bool change_queued = false; + AvailabilityType connector_availability; + { + std::lock_guard change_availability_lock(change_availability_mutex); + change_queued = this->change_availability_queue.count(connector) != 0; + connector_availability = this->change_availability_queue[connector]; + this->change_availability_queue.erase(connector); + } - if (change_queued) { - this->database_handler->insert_or_update_connector_availability(connector, connector_availability); - EVLOG_debug << "Queued availability change of connector " << connector << " to " - << conversions::availability_type_to_string(connector_availability); + if (change_queued) { + this->database_handler->insert_or_update_connector_availability(connector, connector_availability); + EVLOG_debug << "Queued availability change of connector " << connector << " to " + << conversions::availability_type_to_string(connector_availability); - if (connector_availability == AvailabilityType::Operative) { - if (this->enable_evse_callback != nullptr) { - // TODO(kai): check return value - this->enable_evse_callback(connector); - } - this->status->submit_event(connector, FSMEvent::BecomeAvailable); - } else { - if (this->disable_evse_callback != nullptr) { - // TODO(kai): check return value - this->disable_evse_callback(connector); + if (connector_availability == AvailabilityType::Operative) { + if (this->enable_evse_callback != nullptr) { + // TODO(kai): check return value + this->enable_evse_callback(connector); + } + this->status->submit_event(connector, FSMEvent::BecomeAvailable); + } else { + if (this->disable_evse_callback != nullptr) { + // TODO(kai): check return value + this->disable_evse_callback(connector); + } + this->status->submit_event(connector, FSMEvent::ChangeAvailabilityToUnavailable); } - this->status->submit_event(connector, FSMEvent::ChangeAvailabilityToUnavailable); } + } else { + EVLOG_warning << "Received StopTransaction.conf for transaction that is not known to transaction_handler"; } - this->database_handler->update_transaction_csms_ack(transaction->get_session_id()); + this->database_handler->update_transaction_csms_ack(original_call.msg.transactionId); + this->transaction_handler->erase_stopped_transaction(call_result.uniqueId.get()); // when this transaction was stopped because of a Reset.req this signals that StopTransaction.conf has been received this->stop_transaction_cv.notify_one(); diff --git a/lib/ocpp/v16/database_handler.cpp b/lib/ocpp/v16/database_handler.cpp index 50e615aeb..bc68b8abb 100644 --- a/lib/ocpp/v16/database_handler.cpp +++ b/lib/ocpp/v16/database_handler.cpp @@ -167,12 +167,13 @@ void DatabaseHandler::update_transaction(const std::string& session_id, int32_t } } -void DatabaseHandler::update_transaction_csms_ack(const std::string& session_id) { - std::string sql = "UPDATE TRANSACTIONS SET CSMS_ACK=1, LAST_UPDATE=@last_update WHERE ID==@session_id"; +void DatabaseHandler::update_transaction_csms_ack(const int32_t transaction_id) { + std::string sql = + "UPDATE TRANSACTIONS SET CSMS_ACK=1, LAST_UPDATE=@last_update WHERE TRANSACTION_ID==@transaction_id"; SQLiteStatement stmt(this->db, sql); stmt.bind_text("@last_update", ocpp::DateTime().to_rfc3339(), SQLiteString::Transient); - stmt.bind_text("@session_id", session_id); + stmt.bind_int("@transaction_id", transaction_id); if (stmt.step() != SQLITE_DONE) { EVLOG_error << "Could not insert into table: " << sqlite3_errmsg(this->db) << std::endl;