diff --git a/src/mesh/LR11x0Interface.h b/src/mesh/LR11x0Interface.h index 4829ddc1df..840184bbf6 100644 --- a/src/mesh/LR11x0Interface.h +++ b/src/mesh/LR11x0Interface.h @@ -65,5 +65,7 @@ template class LR11x0Interface : public RadioLibInterface virtual void addReceiveMetadata(meshtastic_MeshPacket *mp) override; virtual void setStandby() override; + + uint32_t getPacketTime(uint32_t pl, bool received) override { return computePacketTime(lora, pl, received); } }; #endif \ No newline at end of file diff --git a/src/mesh/RF95Interface.h b/src/mesh/RF95Interface.h index 327e579009..ffd8ae0082 100644 --- a/src/mesh/RF95Interface.h +++ b/src/mesh/RF95Interface.h @@ -65,8 +65,10 @@ class RF95Interface : public RadioLibInterface */ virtual void configHardwareForSend() override; + uint32_t getPacketTime(uint32_t pl, bool received) override { return computePacketTime(*lora, pl, received); } + private: /** Some boards require GPIO control of tx vs rx paths */ void setTransmitEnable(bool txon); }; -#endif \ No newline at end of file +#endif diff --git a/src/mesh/RadioInterface.cpp b/src/mesh/RadioInterface.cpp index 29f91060cf..88218e406a 100644 --- a/src/mesh/RadioInterface.cpp +++ b/src/mesh/RadioInterface.cpp @@ -230,33 +230,7 @@ The band is from 902 to 928 MHz. It mentions channel number and its respective c separated by 2.16 MHz with respect to the adjacent channels. Channel zero starts at 903.08 MHz center frequency. */ -/** - * Calculate airtime per - * https://www.rs-online.com/designspark/rel-assets/ds-assets/uploads/knowledge-items/application-notes-for-the-internet-of-things/LoRa%20Design%20Guide.pdf - * section 4 - * - * @return num msecs for the packet - */ -uint32_t RadioInterface::getPacketTime(uint32_t pl) -{ - float bandwidthHz = bw * 1000.0f; - bool headDisable = false; // we currently always use the header - float tSym = (1 << sf) / bandwidthHz; - - bool lowDataOptEn = tSym > 16e-3 ? true : false; // Needed if symbol time is >16ms - - float tPreamble = (preambleLength + 4.25f) * tSym; - float numPayloadSym = - 8 + max(ceilf(((8.0f * pl - 4 * sf + 28 + 16 - 20 * headDisable) / (4 * (sf - 2 * lowDataOptEn))) * cr), 0.0f); - float tPayload = numPayloadSym * tSym; - float tPacket = tPreamble + tPayload; - - uint32_t msecs = tPacket * 1000; - - return msecs; -} - -uint32_t RadioInterface::getPacketTime(const meshtastic_MeshPacket *p) +uint32_t RadioInterface::getPacketTime(const meshtastic_MeshPacket *p, bool received) { uint32_t pl = 0; if (p->which_payload_variant == meshtastic_MeshPacket_encrypted_tag) { @@ -265,7 +239,7 @@ uint32_t RadioInterface::getPacketTime(const meshtastic_MeshPacket *p) size_t numbytes = pb_encode_to_bytes(bytes, sizeof(bytes), &meshtastic_Data_msg, &p->decoded); pl = numbytes + sizeof(PacketHeader); } - return getPacketTime(pl); + return getPacketTime(pl, received); } /** The delay to use for retransmitting dropped packets */ @@ -624,8 +598,7 @@ void RadioInterface::applyModemConfig() saveFreq(freq + loraConfig.frequency_offset); slotTimeMsec = computeSlotTimeMsec(); - preambleTimeMsec = getPacketTime((uint32_t)0); - maxPacketTimeMsec = getPacketTime(meshtastic_Constants_DATA_PAYLOAD_LEN + sizeof(PacketHeader)); + preambleTimeMsec = preambleLength * (pow_of_2(sf) / bw); LOG_INFO("Radio freq=%.3f, config.lora.frequency_offset=%.3f", freq, loraConfig.frequency_offset); LOG_INFO("Set radio: region=%s, name=%s, config=%u, ch=%d, power=%d", myRegion->name, channelName, loraConfig.modem_preset, @@ -635,7 +608,7 @@ void RadioInterface::applyModemConfig() LOG_INFO("numChannels: %d x %.3fkHz", numChannels, bw); LOG_INFO("channel_num: %d", channel_num + 1); LOG_INFO("frequency: %f", getFreq()); - LOG_INFO("Slot time: %u msec", slotTimeMsec); + LOG_INFO("Slot time: %u msec, preamble time: %u msec", slotTimeMsec, preambleTimeMsec); } /** Slottime is the time to detect a transmission has started, consisting of: diff --git a/src/mesh/RadioInterface.h b/src/mesh/RadioInterface.h index 0c5b6cd1ae..6049a11cc2 100644 --- a/src/mesh/RadioInterface.h +++ b/src/mesh/RadioInterface.h @@ -87,9 +87,8 @@ class RadioInterface const uint8_t NUM_SYM_CAD = 2; // Number of symbols used for CAD, 2 is the default since RadioLib 6.3.0 as per AN1200.48 const uint8_t NUM_SYM_CAD_24GHZ = 4; // Number of symbols used for CAD in 2.4 GHz, 4 is recommended in AN1200.22 of SX1280 uint32_t slotTimeMsec = computeSlotTimeMsec(); - uint16_t preambleLength = 16; // 8 is default, but we use longer to increase the amount of sleep time when receiving - uint32_t preambleTimeMsec = 165; // calculated on startup, this is the default for LongFast - uint32_t maxPacketTimeMsec = 3246; // calculated on startup, this is the default for LongFast + uint16_t preambleLength = 16; // 8 is default, but we use longer to increase the amount of sleep time when receiving + uint32_t preambleTimeMsec = 165; // calculated on startup, this is the default for LongFast const uint32_t PROCESSING_TIME_MSEC = 4500; // time to construct, process and construct a packet again (empirically determined) const uint8_t CWmin = 3; // minimum CWsize @@ -202,8 +201,8 @@ class RadioInterface * * @return num msecs for the packet */ - uint32_t getPacketTime(const meshtastic_MeshPacket *p); - uint32_t getPacketTime(uint32_t totalPacketLen); + uint32_t getPacketTime(const meshtastic_MeshPacket *p, bool received = false); + virtual uint32_t getPacketTime(uint32_t totalPacketLen, bool received = false) = 0; /** * Get the channel we saved. diff --git a/src/mesh/RadioLibInterface.cpp b/src/mesh/RadioLibInterface.cpp index 3717e87809..4a18d4139b 100644 --- a/src/mesh/RadioLibInterface.cpp +++ b/src/mesh/RadioLibInterface.cpp @@ -116,16 +116,21 @@ bool RadioLibInterface::receiveDetected(uint16_t irq, ulong syncWordHeaderValidF if (detected) { if (!activeReceiveStart) { activeReceiveStart = millis(); - } else if (!Throttle::isWithinTimespanMs(activeReceiveStart, 2 * preambleTimeMsec) && !(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_DEBUG("Ignore false preamble detection"); - return false; - } else 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_DEBUG("Ignore false header detection"); - return false; + } 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_DEBUG("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_DEBUG("Ignore false header detection"); + return false; + } + } } } return detected; @@ -411,8 +416,6 @@ void RadioLibInterface::completeSending() void RadioLibInterface::handleReceiveInterrupt() { - uint32_t xmitMsec; - // when this is called, we should be in receive mode - if we are not, just jump out instead of bombing. Possible Race // Condition? if (!isReceiving) { @@ -425,12 +428,12 @@ void RadioLibInterface::handleReceiveInterrupt() // read the number of actually received bytes size_t length = iface->getPacketLength(); - xmitMsec = getPacketTime(length); + uint32_t rxMsec = getPacketTime(length, true); #ifndef DISABLE_WELCOME_UNSET if (config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_UNSET) { LOG_WARN("lora rx disabled: Region unset"); - airTime->logAirtime(RX_ALL_LOG, xmitMsec); + airTime->logAirtime(RX_ALL_LOG, rxMsec); return; } #endif @@ -446,7 +449,7 @@ void RadioLibInterface::handleReceiveInterrupt() radioBuffer.header.to, radioBuffer.header.from, radioBuffer.header.flags); rxBad++; - airTime->logAirtime(RX_ALL_LOG, xmitMsec); + airTime->logAirtime(RX_ALL_LOG, rxMsec); } else { // Skip the 4 headers that are at the beginning of the rxBuf @@ -456,7 +459,7 @@ void RadioLibInterface::handleReceiveInterrupt() if (payloadLen < 0) { LOG_WARN("Ignore received packet too short"); rxBad++; - airTime->logAirtime(RX_ALL_LOG, xmitMsec); + airTime->logAirtime(RX_ALL_LOG, rxMsec); } else { rxGood++; // altered packet with "from == 0" can do Remote Node Administration without permission @@ -494,7 +497,7 @@ void RadioLibInterface::handleReceiveInterrupt() printPacket("Lora RX", mp); - airTime->logAirtime(RX_LOG, xmitMsec); + airTime->logAirtime(RX_LOG, rxMsec); deliverToReceiver(mp); } diff --git a/src/mesh/RadioLibInterface.h b/src/mesh/RadioLibInterface.h index 3444b1a2c2..d8f38cad3a 100644 --- a/src/mesh/RadioLibInterface.h +++ b/src/mesh/RadioLibInterface.h @@ -61,6 +61,17 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified MeshPacketQueue txQueue = MeshPacketQueue(MAX_TX_QUEUE); protected: + ModemType_t modemType = RADIOLIB_MODEM_LORA; + DataRate_t getDataRate() const { return {.lora = {.spreadingFactor = sf, .bandwidth = bw, .codingRate = cr}}; } + PacketConfig_t getPacketConfig() const + { + return {.lora = {.preambleLength = preambleLength, + .implicitHeader = false, + .crcEnabled = true, + // We use auto LDRO, meaning it is enabled if the symbol time is >= 16msec + .ldrOptimize = (1 << sf) / bw >= 16}}; + } + /** * We use a meshtastic sync word, but hashed with the Channel name. For releases before 1.2 we used 0x12 (or for very old * loads 0x14) Note: do not use 0x34 - that is reserved for lorawan @@ -209,6 +220,36 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified */ virtual void setStandby(); + /** + * Derive packet time either for a received (using header info) or a transmitted packet + */ + template uint32_t computePacketTime(T &lora, uint32_t pl, bool received) + { + if (received) { + // First get the actual coding rate and CRC status from the received packet + uint8_t rxCR; + bool hasCRC; + lora.getLoRaRxHeaderInfo(&rxCR, &hasCRC); + // Go from raw header value to denominator + if (rxCR < 5) { + rxCR += 4; + } else if (rxCR == 7) { + rxCR = 8; + } + + // Received packet configuration must be the same as configured, except for coding rate and CRC + DataRate_t dr = getDataRate(); + dr.lora.codingRate = rxCR; + + PacketConfig_t pc = getPacketConfig(); + pc.lora.crcEnabled = hasCRC; + + return lora.calculateTimeOnAir(modemType, dr, pc, pl) / 1000; + } + + return lora.getTimeOnAir(pl) / 1000; + } + const char *radioLibErr = "RadioLib err="; /** diff --git a/src/mesh/ReliableRouter.cpp b/src/mesh/ReliableRouter.cpp index b31c352fef..00066a7a34 100644 --- a/src/mesh/ReliableRouter.cpp +++ b/src/mesh/ReliableRouter.cpp @@ -76,7 +76,7 @@ bool ReliableRouter::shouldFilterReceived(const meshtastic_MeshPacket *p) If we don't add this, we will likely retransmit too early. */ for (auto i = pending.begin(); i != pending.end(); i++) { - i->second.nextTxMsec += iface->getPacketTime(p); + i->second.nextTxMsec += iface->getPacketTime(p, true); } return isBroadcast(p->to) ? FloodingRouter::shouldFilterReceived(p) : NextHopRouter::shouldFilterReceived(p); diff --git a/src/mesh/SX126xInterface.h b/src/mesh/SX126xInterface.h index dc7024daa0..b8f16ac6da 100644 --- a/src/mesh/SX126xInterface.h +++ b/src/mesh/SX126xInterface.h @@ -72,6 +72,8 @@ template class SX126xInterface : public RadioLibInterface virtual void setStandby() override; + uint32_t getPacketTime(uint32_t pl, bool received) override { return computePacketTime(lora, pl, received); } + private: /** Some boards require GPIO control of tx vs rx paths */ void setTransmitEnable(bool txon); diff --git a/src/mesh/SX128xInterface.h b/src/mesh/SX128xInterface.h index bba31dab4b..acdcbbb27c 100644 --- a/src/mesh/SX128xInterface.h +++ b/src/mesh/SX128xInterface.h @@ -67,4 +67,6 @@ template class SX128xInterface : public RadioLibInterface virtual void addReceiveMetadata(meshtastic_MeshPacket *mp) override; virtual void setStandby() override; + + uint32_t getPacketTime(uint32_t pl, bool received) override { return computePacketTime(lora, pl, received); } }; diff --git a/src/platform/portduino/SimRadio.cpp b/src/platform/portduino/SimRadio.cpp index cea1eab3a2..0af2bf47fa 100644 --- a/src/platform/portduino/SimRadio.cpp +++ b/src/platform/portduino/SimRadio.cpp @@ -182,7 +182,7 @@ void SimRadio::onNotify(uint32_t notification) assert(txp); startSend(txp); // Packet has been sent, count it toward our TX airtime utilization. - uint32_t xmitMsec = getPacketTime(txp); + uint32_t xmitMsec = RadioInterface::getPacketTime(txp); airTime->logAirtime(TX_LOG, xmitMsec); notifyLater(xmitMsec, ISR_TX, false); // Model the time it is busy sending @@ -252,7 +252,7 @@ void SimRadio::startReceive(meshtastic_MeshPacket *p) if (isActivelyReceiving()) { LOG_WARN("Collision detected, dropping current and previous packet!"); rxBad++; - airTime->logAirtime(RX_ALL_LOG, getPacketTime(receivingPacket)); + airTime->logAirtime(RX_ALL_LOG, getPacketTime(receivingPacket, true)); packetPool.release(receivingPacket); receivingPacket = nullptr; return; @@ -270,7 +270,7 @@ void SimRadio::startReceive(meshtastic_MeshPacket *p) } isReceiving = true; receivingPacket = packetPool.allocCopy(*p); - uint32_t airtimeMsec = getPacketTime(p); + uint32_t airtimeMsec = getPacketTime(p, true); notifyLater(airtimeMsec, ISR_RX, false); // Model the time it is busy receiving #else isReceiving = true; @@ -311,7 +311,7 @@ void SimRadio::handleReceiveInterrupt() printPacket("Lora RX", mp); - airTime->logAirtime(RX_LOG, getPacketTime(mp)); + airTime->logAirtime(RX_LOG, RadioInterface::getPacketTime(mp, true)); deliverToReceiver(mp); } @@ -332,4 +332,29 @@ int16_t SimRadio::readData(uint8_t *data, size_t len) } return state; +} + +/** + * Calculate airtime per + * https://www.rs-online.com/designspark/rel-assets/ds-assets/uploads/knowledge-items/application-notes-for-the-internet-of-things/LoRa%20Design%20Guide.pdf + * section 4 + * + * @return num msecs for the packet + */ +uint32_t SimRadio::getPacketTime(uint32_t pl, bool received) +{ + float bandwidthHz = bw * 1000.0f; + bool headDisable = false; // we currently always use the header + float tSym = (1 << sf) / bandwidthHz; + + bool lowDataOptEn = tSym > 16e-3 ? true : false; // Needed if symbol time is >16ms + + float tPreamble = (preambleLength + 4.25f) * tSym; + float numPayloadSym = + 8 + max(ceilf(((8.0f * pl - 4 * sf + 28 + 16 - 20 * headDisable) / (4 * (sf - 2 * lowDataOptEn))) * cr), 0.0f); + float tPayload = numPayloadSym * tSym; + float tPacket = tPreamble + tPayload; + + uint32_t msecs = tPacket * 1000; + return msecs; } \ No newline at end of file diff --git a/src/platform/portduino/SimRadio.h b/src/platform/portduino/SimRadio.h index d8b53739f2..d87e6be758 100644 --- a/src/platform/portduino/SimRadio.h +++ b/src/platform/portduino/SimRadio.h @@ -88,6 +88,8 @@ class SimRadio : public RadioInterface, protected concurrency::NotifiedWorkerThr /** * If a send was in progress finish it and return the buffer to the pool */ void completeSending(); + + virtual uint32_t getPacketTime(uint32_t pl, bool received = false) override; }; extern SimRadio *simRadio; \ No newline at end of file