diff --git a/src/mesh/RadioLibInterface.cpp b/src/mesh/RadioLibInterface.cpp index c0a56a7f66..fcea9f5d8d 100644 --- a/src/mesh/RadioLibInterface.cpp +++ b/src/mesh/RadioLibInterface.cpp @@ -119,31 +119,50 @@ bool RadioLibInterface::canSendImmediately() return true; } +bool RadioLibInterface::preambleHoldActive() +{ + // Whatever sent the cleared preamble is off the air one max packet later. + if (preambleHoldStart && !Throttle::isWithinTimespanMs( + preambleHoldStart, getPacketTime(meshtastic_Constants_DATA_PAYLOAD_LEN + sizeof(PacketHeader)))) + preambleHoldStart = 0; + return preambleHoldStart != 0; +} + +void RadioLibInterface::holdOnPreamble() +{ + // During a hold a refire stays latched, so the first look after it sees an external source and holds again. + if (preambleHoldActive()) + return; + iface->clearIrq(1UL << RADIOLIB_IRQ_PREAMBLE_DETECTED); + preambleHoldStart = Time::skipZero(Time::getMillis()); + LOG_TRACE("Preamble seen, cleared, holding TX"); +} + bool RadioLibInterface::receiveDetected(uint16_t irq, unsigned long syncWordHeaderValidFlag, unsigned long preambleDetectedFlag) { - bool detected = (irq & (syncWordHeaderValidFlag | preambleDetectedFlag)); - // Handle false detections - if (detected) { + if (preambleHoldActive()) + return true; + + if (irq & syncWordHeaderValidFlag) { if (!activeReceiveStart) { activeReceiveStart = Time::skipZero(Time::getMillis()); - } else if (!Throttle::isWithinTimespanMs(activeReceiveStart, 2 * preambleTimeMsec)) { - if (!(irq & syncWordHeaderValidFlag)) { - // The HEADER_VALID flag should be set by now if it was really a packet, so ignore PREAMBLE_DETECTED flag - activeReceiveStart = 0; - LOG_TRACE("Ignore false preamble detection"); - return false; - } else { - uint32_t maxPacketTimeMsec = getPacketTime(meshtastic_Constants_DATA_PAYLOAD_LEN + sizeof(PacketHeader)); - if (!Throttle::isWithinTimespanMs(activeReceiveStart, maxPacketTimeMsec)) { - // We should have gotten an RX_DONE IRQ by now if it was really a packet, so ignore HEADER_VALID flag - activeReceiveStart = 0; - LOG_TRACE("Ignore false header detection"); - return false; - } - } + } else if (!Throttle::isWithinTimespanMs(activeReceiveStart, + getPacketTime(meshtastic_Constants_DATA_PAYLOAD_LEN + sizeof(PacketHeader)))) { + // We should have gotten an RX_DONE IRQ by now if it was really a packet, so ignore HEADER_VALID flag + activeReceiveStart = 0; + LOG_TRACE("Ignore false header detection"); + return false; } + return true; } - return detected; + + if (irq & preambleDetectedFlag) { + // Looks come once per CSMA backoff, too rarely to judge a preamble by symbol-time deadline (#11933). + // Clear it so the next look sees only a fresh one, and hold TX meanwhile; a clear never aborts RX. + holdOnPreamble(); + return true; + } + return false; } /// Send a packet (possibly by enquing in a private fifo). This routine will @@ -616,6 +635,7 @@ void RadioLibInterface::handleReceiveInterrupt() // Condition? const bool wasCadHandoff = cadHandoffRxStart != 0; cadHandoffRxStart = 0; // this RX ends the wait either way; the outcome is logged below + preambleHoldStart = 0; // likewise the reception a held preamble announced if (!isReceiving) { LOG_ERROR("handleReceiveInterrupt called while not in rx mode"); diff --git a/src/mesh/RadioLibInterface.h b/src/mesh/RadioLibInterface.h index b683a4e810..d1bd180bd9 100644 --- a/src/mesh/RadioLibInterface.h +++ b/src/mesh/RadioLibInterface.h @@ -352,6 +352,17 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified protected: uint32_t activeReceiveStart = 0; + // Time::getMillis() when a look cleared PREAMBLE_DETECTED and began holding TX, or 0 if no hold. + uint32_t preambleHoldStart = 0; + + /** True while a cleared preamble still holds TX; ends the hold once one max packet has passed. */ + bool preambleHoldActive(); + + /** Clear a bare PREAMBLE_DETECTED and hold TX one max packet, unless a hold is already running. */ + void holdOnPreamble(); + + /** Whether a packet is waiting to transmit; txQueue itself stays private. */ + bool hasQueuedTx() { return !txQueue.empty(); } bool receiveDetected(uint16_t irq, unsigned long syncWordHeaderValidFlag, unsigned long preambleDetectedFlag); diff --git a/src/mesh/SX126xInterface.cpp b/src/mesh/SX126xInterface.cpp index f526f905fa..a4efff1106 100644 --- a/src/mesh/SX126xInterface.cpp +++ b/src/mesh/SX126xInterface.cpp @@ -372,16 +372,11 @@ template void SX126xInterface::handleSoftwareLoraIrqPoll() RADIOLIB_SX126X_IRQ_RX_DONE | RADIOLIB_SX126X_IRQ_TIMEOUT | RADIOLIB_SX126X_IRQ_CRC_ERR | RADIOLIB_SX126X_IRQ_HEADER_ERR; const uint16_t noisyRxMask = RADIOLIB_SX126X_IRQ_PREAMBLE_DETECTED | RADIOLIB_SX126X_IRQ_HEADER_VALID; - // A bare PREAMBLE (no terminal RX event yet) means the chip is mid-reception. Do NOT treat it as a - // full RX event - that would repeatedly trigger readData(). The escape hatch (a noise preamble that - // never completes must not permanently block our own TX) runs only while a TX is queued, and clears - // ONLY the preamble. HEADER_VALID is evidence of a real inbound frame, so keep it latched: clearing - // it would make isActivelyReceiving() read idle mid-payload and let the queued TX stomp the frame. - // A stuck header can't block TX forever - receiveDetected() ages it out after maxPacketTimeMsec, - // and readData() clears everything once a real RX_DONE arrives. + // A bare PREAMBLE is mid-reception, not an RX event: readData() here would run on nothing. With a TX + // queued it goes through the same hold as the TX-path look; HEADER_VALID stays latched for readData(). const bool preambleOnly = (irq & RADIOLIB_SX126X_IRQ_PREAMBLE_DETECTED) && !(irq & RADIOLIB_SX126X_IRQ_HEADER_VALID); - if (!pollTxMode && !txQueue.empty() && preambleOnly && ((irq & ~noisyRxMask) == 0U)) { - lora.clearIrqFlags(RADIOLIB_SX126X_IRQ_PREAMBLE_DETECTED); + if (!pollTxMode && hasQueuedTx() && preambleOnly && ((irq & ~noisyRxMask) == 0U)) { + holdOnPreamble(); scheduleIrqPollTick(); return; }