diff --git a/CHANGELOG.md b/CHANGELOG.md index ff0a8467..d512b27d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ - Support for TransactionMessageAttempts/-RetryInterval ([#345](https://github.com/matth-x/MicroOcpp/pull/345)) - Heap profiler and custom allocator support ([#350](https://github.com/matth-x/MicroOcpp/pull/350)) - Migration of persistent storage ([#355](https://github.com/matth-x/MicroOcpp/pull/355)) +- Build flag `MO_TX_ATTEMPT_TIMEOUT` to ignore offline attempts ([#398](https://github.com/matth-x/MicroOcpp/pull/398)) ### Removed diff --git a/src/MicroOcpp/Model/ConnectorBase/Connector.cpp b/src/MicroOcpp/Model/ConnectorBase/Connector.cpp index c0911858..f44c8da3 100644 --- a/src/MicroOcpp/Model/ConnectorBase/Connector.cpp +++ b/src/MicroOcpp/Model/ConnectorBase/Connector.cpp @@ -117,8 +117,30 @@ Connector::Connector(Context& context, std::shared_ptr filesy txNrFront = txNrBegin; if (model.getTransactionStore()) { - unsigned int txNrLatest = (txNrEnd + MAX_TX_CNT - 1) % MAX_TX_CNT; //txNr of the most recent tx on flash - transaction = model.getTransactionStore()->getTransaction(connectorId, txNrLatest); //returns nullptr if txNrLatest does not exist on flash + + //find and preload front transaction + unsigned int txSize = (txNrEnd + MAX_TX_CNT - txNrFront) % MAX_TX_CNT; + for (unsigned int i = 0; i < txSize; i++) { + + transactionFront = model.getTransactionStore()->getTransaction(connectorId, txNrFront); + + if (!transactionFront || (transactionFront->isAborted() || transactionFront->isCompleted() || transactionFront->isSilent())) { + //advance front + transactionFront = nullptr; + txNrFront = (txNrFront + 1) % MAX_TX_CNT; + MO_DBG_VERBOSE("txNrBegin=%u, txNrFront=%u, txNrEnd=%u", txNrBegin, txNrFront, txNrEnd); + } else { + //front is accurate. Done here + break; + } + } + + //preload back transaction + if (txNrEnd != txNrBegin) { + //there exists at least 1 transaction. Take most recent + unsigned int txNrLatest = (txNrEnd + MAX_TX_CNT - 1) % MAX_TX_CNT; //txNr of the most recent tx on flash + transaction = model.getTransactionStore()->getTransaction(connectorId, txNrLatest); //returns nullptr if txNrLatest does not exist on flash + } } else { MO_DBG_ERR("must initialize TxStore before Connector"); } @@ -249,9 +271,11 @@ void Connector::loop() { } } - if (transaction && ((transaction->isAborted() && MO_TX_CLEAN_ABORTED) || (transaction->isSilent() && transaction->getStopSync().isRequested()))) { + if (transaction && ((transaction->isAborted() && MO_TX_CLEAN_ABORTED) || (transaction->isSilent() && transaction->getStopSync().isRequested())) && + transaction->getTxNr() != txNrFront) { //If the transaction is aborted (invalidated before started) or is silent and has stopped. Delete all artifacts from flash //This is an optimization. The memory management will attempt to remove those files again later + //Don't do this if tx is at front. Then, `getFrontRequestOpNr()` has responsibility for object lifecycle bool removed = true; if (auto mService = model.getMeteringService()) { mService->abortTxMeterData(connectorId); @@ -263,9 +287,6 @@ void Connector::loop() { } if (removed) { - if (txNrFront == txNrEnd) { - txNrFront = transaction->getTxNr(); - } txNrEnd = transaction->getTxNr(); //roll back creation of last tx entry } @@ -517,7 +538,6 @@ void Connector::loop() { makeRequest( new Ocpp16::StatusNotification(connectorId, reportedStatus, reportedTimestamp, errorData)); - statusNotification->setTimeout(0); context.initiateRequest(std::move(statusNotification)); return; } @@ -549,10 +569,10 @@ std::shared_ptr Connector::allocateTransaction() { std::shared_ptr tx; - //clean possible aborted tx + //clean possible aborted tx. Go from one after front to end (all unsent messages, but skip front) unsigned int txr = txNrEnd; - unsigned int txSize = (txNrEnd + MAX_TX_CNT - txNrBegin) % MAX_TX_CNT; - for (unsigned int i = 0; i < txSize; i++) { + unsigned int txSize = (txNrEnd + MAX_TX_CNT - txNrFront) % MAX_TX_CNT; + for (unsigned int i = 0; i + 1 < txSize; i++) { txr = (txr + MAX_TX_CNT - 1) % MAX_TX_CNT; //decrement by 1 auto tx = model.getTransactionStore()->getTransaction(connectorId, txr); @@ -567,9 +587,6 @@ std::shared_ptr Connector::allocateTransaction() { removed &= model.getTransactionStore()->remove(connectorId, txr); } if (removed) { - if (txNrFront == txNrEnd) { - txNrFront = txr; - } txNrEnd = txr; MO_DBG_WARN("deleted dangling silent or aborted tx for new transaction"); } else { @@ -593,7 +610,7 @@ std::shared_ptr Connector::allocateTransaction() { //could not create transaction - now, try to replace tx history entry unsigned int txl = txNrBegin; - txSize = (txNrEnd + MAX_TX_CNT - txNrBegin) % MAX_TX_CNT; + txSize = (txNrFront + MAX_TX_CNT - txNrBegin) % MAX_TX_CNT; //range from oldest history tx to one tx before front for (unsigned int i = 0; i < txSize; i++) { @@ -617,9 +634,6 @@ std::shared_ptr Connector::allocateTransaction() { } if (removed) { txNrBegin = (txl + 1) % MAX_TX_CNT; - if (txNrFront == txl) { - txNrFront = txNrBegin; - } MO_DBG_DEBUG("deleted tx history entry for new transaction"); MO_DBG_VERBOSE("txNrBegin=%u, txNrFront=%u, txNrEnd=%u", txNrBegin, txNrFront, txNrEnd); @@ -1085,13 +1099,6 @@ unsigned int Connector::getFrontRequestOpNr() { unsigned int txSize = (txNrEnd + MAX_TX_CNT - txNrFront) % MAX_TX_CNT; - if (transactionFront && txSize == 0) { - //catch edge case where txBack has been rolled back and txFront was equal to txBack - MO_DBG_DEBUG("collect front transaction %u-%u after tx rollback", connectorId, transactionFront->getTxNr()); - MO_DBG_VERBOSE("txNrBegin=%u, txNrFront=%u, txNrEnd=%u", txNrBegin, txNrFront, txNrEnd); - transactionFront = nullptr; - } - for (unsigned int i = 0; i < txSize; i++) { if (!transactionFront) { @@ -1105,9 +1112,14 @@ unsigned int Connector::getFrontRequestOpNr() { #endif } - if (transactionFront && (transactionFront->isAborted() || transactionFront->isCompleted() || transactionFront->isSilent())) { + if (transactionFront && transactionFront == transaction) { + // Don't advance front tx beyond back tx + break; + } + + if (!transactionFront || (transactionFront->isAborted() || transactionFront->isCompleted() || transactionFront->isSilent())) { //advance front - MO_DBG_DEBUG("collect front transaction %u-%u", connectorId, transactionFront->getTxNr()); + MO_DBG_DEBUG("collect front transaction %u-%u", connectorId, txNrFront); transactionFront = nullptr; txNrFront = (txNrFront + 1) % MAX_TX_CNT; MO_DBG_VERBOSE("txNrBegin=%u, txNrFront=%u, txNrEnd=%u", txNrBegin, txNrFront, txNrEnd); @@ -1172,6 +1184,7 @@ std::unique_ptr Connector::fetchFrontRequest() { return nullptr; } + const auto attemptNr_capture = transactionFront->getStartSync().getAttemptNr(); transactionFront->getStartSync().advanceAttemptNr(); transactionFront->getStartSync().setAttemptTime(model.getClock().now()); transactionFront->commit(); @@ -1203,6 +1216,19 @@ std::unique_ptr Connector::fetchFrontRequest() { } }); + #if MO_TX_ATTEMPT_TIMEOUT == 0 + //a timeout should not increase the attemptNr. Roll back to previous attemptNr + startTx->setOnTimeoutListener([transactionFront_capture, attemptNr_capture] () { + MO_DBG_DEBUG("StartTx timeout -> roll back attemptNr and try again"); + if (transactionFront_capture) { + transactionFront_capture->getStartSync().setAttemptNr(attemptNr_capture); + transactionFront_capture->commit(); + } + }); + #else + (void)attemptNr_capture; + #endif + return startTx; } @@ -1229,6 +1255,7 @@ std::unique_ptr Connector::fetchFrontRequest() { return nullptr; } + const auto attemptNr_capture = transactionFront->getStopSync().getAttemptNr(); transactionFront->getStopSync().advanceAttemptNr(); transactionFront->getStopSync().setAttemptTime(model.getClock().now()); transactionFront->commit(); @@ -1264,6 +1291,19 @@ std::unique_ptr Connector::fetchFrontRequest() { } }); + #if MO_TX_ATTEMPT_TIMEOUT == 0 + //a timeout should not increase the attemptNr. Roll back to previous attemptNr + stopTx->setOnTimeoutListener([transactionFront_capture, attemptNr_capture] () { + MO_DBG_DEBUG("StopTx timeout -> roll back attemptNr and try again"); + if (transactionFront_capture) { + transactionFront_capture->getStopSync().setAttemptNr(attemptNr_capture); + transactionFront_capture->commit(); + } + }); + #else + (void)attemptNr_capture; + #endif + return stopTx; } } diff --git a/src/MicroOcpp/Model/ConnectorBase/Connector.h b/src/MicroOcpp/Model/ConnectorBase/Connector.h index 541c1f4c..183c4ac9 100644 --- a/src/MicroOcpp/Model/ConnectorBase/Connector.h +++ b/src/MicroOcpp/Model/ConnectorBase/Connector.h @@ -28,6 +28,10 @@ #define MO_REPORT_NOERROR 0 #endif +#ifndef MO_TX_ATTEMPT_TIMEOUT +#define MO_TX_ATTEMPT_TIMEOUT 1 //if the timeout of a tx-related msg should increase its attempt counter +#endif + namespace MicroOcpp { class Context;