From 1354fcfc3c0e93d76e30b5ac81dd30cca7ff4f1a Mon Sep 17 00:00:00 2001 From: huangkongsheng Date: Mon, 19 Oct 2026 23:20:00 +0800 Subject: [PATCH] Discard blank transaction placeholders on recovery createTransaction() commits the empty tx object to flash before the caller fills it in. A device reset in that window leaves a tx file whose start/stop opNrs are still 0 and whose idTag is empty; on the next boot it loads back as active-but-never-started and blocks the connector forever (#455). allocateTransaction()'s cleanup now also treats such blank entries as dangling. A real tx that was authorized before the reset has an idTag and is unaffected. --- src/MicroOcpp/Model/ConnectorBase/Connector.cpp | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/MicroOcpp/Model/ConnectorBase/Connector.cpp b/src/MicroOcpp/Model/ConnectorBase/Connector.cpp index 11142dc9..3c676b8b 100644 --- a/src/MicroOcpp/Model/ConnectorBase/Connector.cpp +++ b/src/MicroOcpp/Model/ConnectorBase/Connector.cpp @@ -563,8 +563,17 @@ std::shared_ptr Connector::allocateTransaction() { txr = (txr + MAX_TX_CNT - 1) % MAX_TX_CNT; //decrement by 1 auto tx = model.getTransactionStore()->getTransaction(connectorId, txr); - //check if dangling silent tx, aborted tx, or corrupted entry (tx == null) - if (!tx || tx->isSilent() || (tx->isAborted() && MO_TX_CLEAN_ABORTED)) { + //check if dangling silent tx, aborted tx, corrupted entry (tx == null), + //or a blank placeholder: createTransaction() commits the empty tx object + //to flash before the caller fills it in (idTag, start opNr). A device + //reset in between leaves a tx that never starts and blocks the connector + //(issue #455). Such a tx has neither a start opNr nor an idTag; a real + //tx that was authorized before the reset does have an idTag. + bool isPlaceholder = tx + && !tx->isSilent() + && tx->getStartSync().getOpNr() == 0 + && tx->getIdTag()[0] == '\0'; + if (!tx || tx->isSilent() || (tx->isAborted() && MO_TX_CLEAN_ABORTED) || isPlaceholder) { //yes, remove bool removed = true; if (auto mService = model.getMeteringService()) {