Calculate airtime of transmitted and received packets separately (#8205)

This commit is contained in:
GUVWAF
2025-10-04 12:29:25 +02:00
committed by GitHub
parent 0e38fef5bf
commit e8296914a5
11 changed files with 110 additions and 59 deletions

View File

@@ -65,5 +65,7 @@ template <class T> class LR11x0Interface : public RadioLibInterface
virtual void addReceiveMetadata(meshtastic_MeshPacket *mp) override; virtual void addReceiveMetadata(meshtastic_MeshPacket *mp) override;
virtual void setStandby() override; virtual void setStandby() override;
uint32_t getPacketTime(uint32_t pl, bool received) override { return computePacketTime(lora, pl, received); }
}; };
#endif #endif

View File

@@ -65,8 +65,10 @@ class RF95Interface : public RadioLibInterface
*/ */
virtual void configHardwareForSend() override; virtual void configHardwareForSend() override;
uint32_t getPacketTime(uint32_t pl, bool received) override { return computePacketTime(*lora, pl, received); }
private: private:
/** Some boards require GPIO control of tx vs rx paths */ /** Some boards require GPIO control of tx vs rx paths */
void setTransmitEnable(bool txon); void setTransmitEnable(bool txon);
}; };
#endif #endif

View File

@@ -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. separated by 2.16 MHz with respect to the adjacent channels. Channel zero starts at 903.08 MHz center frequency.
*/ */
/** uint32_t RadioInterface::getPacketTime(const meshtastic_MeshPacket *p, bool received)
* 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 pl = 0; uint32_t pl = 0;
if (p->which_payload_variant == meshtastic_MeshPacket_encrypted_tag) { 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); size_t numbytes = pb_encode_to_bytes(bytes, sizeof(bytes), &meshtastic_Data_msg, &p->decoded);
pl = numbytes + sizeof(PacketHeader); pl = numbytes + sizeof(PacketHeader);
} }
return getPacketTime(pl); return getPacketTime(pl, received);
} }
/** The delay to use for retransmitting dropped packets */ /** The delay to use for retransmitting dropped packets */
@@ -624,8 +598,7 @@ void RadioInterface::applyModemConfig()
saveFreq(freq + loraConfig.frequency_offset); saveFreq(freq + loraConfig.frequency_offset);
slotTimeMsec = computeSlotTimeMsec(); slotTimeMsec = computeSlotTimeMsec();
preambleTimeMsec = getPacketTime((uint32_t)0); preambleTimeMsec = preambleLength * (pow_of_2(sf) / bw);
maxPacketTimeMsec = getPacketTime(meshtastic_Constants_DATA_PAYLOAD_LEN + sizeof(PacketHeader));
LOG_INFO("Radio freq=%.3f, config.lora.frequency_offset=%.3f", freq, loraConfig.frequency_offset); 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, 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("numChannels: %d x %.3fkHz", numChannels, bw);
LOG_INFO("channel_num: %d", channel_num + 1); LOG_INFO("channel_num: %d", channel_num + 1);
LOG_INFO("frequency: %f", getFreq()); 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: /** Slottime is the time to detect a transmission has started, consisting of:

View File

@@ -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 = 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 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(); uint32_t slotTimeMsec = computeSlotTimeMsec();
uint16_t preambleLength = 16; // 8 is default, but we use longer to increase the amount of sleep time when receiving 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 preambleTimeMsec = 165; // calculated on startup, this is the default for LongFast
uint32_t maxPacketTimeMsec = 3246; // calculated on startup, this is the default for LongFast
const uint32_t PROCESSING_TIME_MSEC = const uint32_t PROCESSING_TIME_MSEC =
4500; // time to construct, process and construct a packet again (empirically determined) 4500; // time to construct, process and construct a packet again (empirically determined)
const uint8_t CWmin = 3; // minimum CWsize const uint8_t CWmin = 3; // minimum CWsize
@@ -202,8 +201,8 @@ class RadioInterface
* *
* @return num msecs for the packet * @return num msecs for the packet
*/ */
uint32_t getPacketTime(const meshtastic_MeshPacket *p); uint32_t getPacketTime(const meshtastic_MeshPacket *p, bool received = false);
uint32_t getPacketTime(uint32_t totalPacketLen); virtual uint32_t getPacketTime(uint32_t totalPacketLen, bool received = false) = 0;
/** /**
* Get the channel we saved. * Get the channel we saved.

View File

@@ -116,16 +116,21 @@ bool RadioLibInterface::receiveDetected(uint16_t irq, ulong syncWordHeaderValidF
if (detected) { if (detected) {
if (!activeReceiveStart) { if (!activeReceiveStart) {
activeReceiveStart = millis(); activeReceiveStart = millis();
} else if (!Throttle::isWithinTimespanMs(activeReceiveStart, 2 * preambleTimeMsec) && !(irq & syncWordHeaderValidFlag)) { } else if (!Throttle::isWithinTimespanMs(activeReceiveStart, 2 * preambleTimeMsec)) {
// The HEADER_VALID flag should be set by now if it was really a packet, so ignore PREAMBLE_DETECTED flag if (!(irq & syncWordHeaderValidFlag)) {
activeReceiveStart = 0; // The HEADER_VALID flag should be set by now if it was really a packet, so ignore PREAMBLE_DETECTED flag
LOG_DEBUG("Ignore false preamble detection"); activeReceiveStart = 0;
return false; LOG_DEBUG("Ignore false preamble detection");
} else if (!Throttle::isWithinTimespanMs(activeReceiveStart, maxPacketTimeMsec)) { return false;
// We should have gotten an RX_DONE IRQ by now if it was really a packet, so ignore HEADER_VALID flag } else {
activeReceiveStart = 0; uint32_t maxPacketTimeMsec = getPacketTime(meshtastic_Constants_DATA_PAYLOAD_LEN + sizeof(PacketHeader));
LOG_DEBUG("Ignore false header detection"); if (!Throttle::isWithinTimespanMs(activeReceiveStart, maxPacketTimeMsec)) {
return false; // 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; return detected;
@@ -411,8 +416,6 @@ void RadioLibInterface::completeSending()
void RadioLibInterface::handleReceiveInterrupt() 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 // when this is called, we should be in receive mode - if we are not, just jump out instead of bombing. Possible Race
// Condition? // Condition?
if (!isReceiving) { if (!isReceiving) {
@@ -425,12 +428,12 @@ void RadioLibInterface::handleReceiveInterrupt()
// read the number of actually received bytes // read the number of actually received bytes
size_t length = iface->getPacketLength(); size_t length = iface->getPacketLength();
xmitMsec = getPacketTime(length); uint32_t rxMsec = getPacketTime(length, true);
#ifndef DISABLE_WELCOME_UNSET #ifndef DISABLE_WELCOME_UNSET
if (config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_UNSET) { if (config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_UNSET) {
LOG_WARN("lora rx disabled: Region unset"); LOG_WARN("lora rx disabled: Region unset");
airTime->logAirtime(RX_ALL_LOG, xmitMsec); airTime->logAirtime(RX_ALL_LOG, rxMsec);
return; return;
} }
#endif #endif
@@ -446,7 +449,7 @@ void RadioLibInterface::handleReceiveInterrupt()
radioBuffer.header.to, radioBuffer.header.from, radioBuffer.header.flags); radioBuffer.header.to, radioBuffer.header.from, radioBuffer.header.flags);
rxBad++; rxBad++;
airTime->logAirtime(RX_ALL_LOG, xmitMsec); airTime->logAirtime(RX_ALL_LOG, rxMsec);
} else { } else {
// Skip the 4 headers that are at the beginning of the rxBuf // Skip the 4 headers that are at the beginning of the rxBuf
@@ -456,7 +459,7 @@ void RadioLibInterface::handleReceiveInterrupt()
if (payloadLen < 0) { if (payloadLen < 0) {
LOG_WARN("Ignore received packet too short"); LOG_WARN("Ignore received packet too short");
rxBad++; rxBad++;
airTime->logAirtime(RX_ALL_LOG, xmitMsec); airTime->logAirtime(RX_ALL_LOG, rxMsec);
} else { } else {
rxGood++; rxGood++;
// altered packet with "from == 0" can do Remote Node Administration without permission // altered packet with "from == 0" can do Remote Node Administration without permission
@@ -494,7 +497,7 @@ void RadioLibInterface::handleReceiveInterrupt()
printPacket("Lora RX", mp); printPacket("Lora RX", mp);
airTime->logAirtime(RX_LOG, xmitMsec); airTime->logAirtime(RX_LOG, rxMsec);
deliverToReceiver(mp); deliverToReceiver(mp);
} }

View File

@@ -61,6 +61,17 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified
MeshPacketQueue txQueue = MeshPacketQueue(MAX_TX_QUEUE); MeshPacketQueue txQueue = MeshPacketQueue(MAX_TX_QUEUE);
protected: 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 * 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 * 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(); virtual void setStandby();
/**
* Derive packet time either for a received (using header info) or a transmitted packet
*/
template <typename T> 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="; const char *radioLibErr = "RadioLib err=";
/** /**

View File

@@ -76,7 +76,7 @@ bool ReliableRouter::shouldFilterReceived(const meshtastic_MeshPacket *p)
If we don't add this, we will likely retransmit too early. If we don't add this, we will likely retransmit too early.
*/ */
for (auto i = pending.begin(); i != pending.end(); i++) { 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); return isBroadcast(p->to) ? FloodingRouter::shouldFilterReceived(p) : NextHopRouter::shouldFilterReceived(p);

View File

@@ -72,6 +72,8 @@ template <class T> class SX126xInterface : public RadioLibInterface
virtual void setStandby() override; virtual void setStandby() override;
uint32_t getPacketTime(uint32_t pl, bool received) override { return computePacketTime(lora, pl, received); }
private: private:
/** Some boards require GPIO control of tx vs rx paths */ /** Some boards require GPIO control of tx vs rx paths */
void setTransmitEnable(bool txon); void setTransmitEnable(bool txon);

View File

@@ -67,4 +67,6 @@ template <class T> class SX128xInterface : public RadioLibInterface
virtual void addReceiveMetadata(meshtastic_MeshPacket *mp) override; virtual void addReceiveMetadata(meshtastic_MeshPacket *mp) override;
virtual void setStandby() override; virtual void setStandby() override;
uint32_t getPacketTime(uint32_t pl, bool received) override { return computePacketTime(lora, pl, received); }
}; };

View File

@@ -182,7 +182,7 @@ void SimRadio::onNotify(uint32_t notification)
assert(txp); assert(txp);
startSend(txp); startSend(txp);
// Packet has been sent, count it toward our TX airtime utilization. // 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); airTime->logAirtime(TX_LOG, xmitMsec);
notifyLater(xmitMsec, ISR_TX, false); // Model the time it is busy sending notifyLater(xmitMsec, ISR_TX, false); // Model the time it is busy sending
@@ -252,7 +252,7 @@ void SimRadio::startReceive(meshtastic_MeshPacket *p)
if (isActivelyReceiving()) { if (isActivelyReceiving()) {
LOG_WARN("Collision detected, dropping current and previous packet!"); LOG_WARN("Collision detected, dropping current and previous packet!");
rxBad++; rxBad++;
airTime->logAirtime(RX_ALL_LOG, getPacketTime(receivingPacket)); airTime->logAirtime(RX_ALL_LOG, getPacketTime(receivingPacket, true));
packetPool.release(receivingPacket); packetPool.release(receivingPacket);
receivingPacket = nullptr; receivingPacket = nullptr;
return; return;
@@ -270,7 +270,7 @@ void SimRadio::startReceive(meshtastic_MeshPacket *p)
} }
isReceiving = true; isReceiving = true;
receivingPacket = packetPool.allocCopy(*p); 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 notifyLater(airtimeMsec, ISR_RX, false); // Model the time it is busy receiving
#else #else
isReceiving = true; isReceiving = true;
@@ -311,7 +311,7 @@ void SimRadio::handleReceiveInterrupt()
printPacket("Lora RX", mp); printPacket("Lora RX", mp);
airTime->logAirtime(RX_LOG, getPacketTime(mp)); airTime->logAirtime(RX_LOG, RadioInterface::getPacketTime(mp, true));
deliverToReceiver(mp); deliverToReceiver(mp);
} }
@@ -332,4 +332,29 @@ int16_t SimRadio::readData(uint8_t *data, size_t len)
} }
return state; 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;
} }

View File

@@ -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 */ * If a send was in progress finish it and return the buffer to the pool */
void completeSending(); void completeSending();
virtual uint32_t getPacketTime(uint32_t pl, bool received = false) override;
}; };
extern SimRadio *simRadio; extern SimRadio *simRadio;