diff --git a/src/mesh/RadioInterface.cpp b/src/mesh/RadioInterface.cpp index d949c4626..635862956 100644 --- a/src/mesh/RadioInterface.cpp +++ b/src/mesh/RadioInterface.cpp @@ -159,61 +159,50 @@ uint32_t RadioInterface::getPacketTime(MeshPacket *p) /** The delay to use for retransmitting dropped packets */ uint32_t RadioInterface::getRetransmissionMsec(const MeshPacket *p) { - assert(shortPacketMsec); // Better be non zero + assert(slotTimeMsec); // Better be non zero static uint8_t bytes[MAX_RHPACKETLEN]; size_t numbytes = pb_encode_to_bytes(bytes, sizeof(bytes), Data_fields, &p->decoded); uint32_t packetAirtime = getPacketTime(numbytes + sizeof(PacketHeader)); - uint32_t tCADmsec = 2 * (1 << sf) / bw; // duration of CAD is roughly 2 symbols according to SX127x datasheet - /* Make sure enough time has elapsed for this packet to be sent and an ACK is received. - * Right now we have to wait until another node floods the same packet, as that is our implicit ACK. - * TODO: Revise when want_ack will be used (right now it is always set to 0 afterwards). - */ - return 2*packetAirtime + 2*MIN_TX_WAIT_MSEC + shortPacketMsec + shortPacketMsec*2 + PROCESSING_TIME_MSEC + 2*tCADmsec; + // Make sure enough time has elapsed for this packet to be sent and an ACK is received. + // DEBUG_MSG("Waiting for flooding message with airtime %d and slotTime is %d\n", packetAirtime, slotTimeMsec); + float channelUtil = airTime->channelUtilizationPercent(); + uint8_t CWsize = map(channelUtil, 0, 100, CWmin, CWmax); + // Assuming we pick max. of CWsize and there will be a receiver with SNR at half the range + return 2*packetAirtime + (pow(2, CWsize) + pow(2, int((CWmax+CWmin)/2))) * slotTimeMsec + PROCESSING_TIME_MSEC; } -/** The delay to use when we want to send something but the ether is busy */ +/** The delay to use when we want to send something */ uint32_t RadioInterface::getTxDelayMsec() { - /** At the low end we want to pick a delay large enough that anyone who just completed sending (some other node) - * has had enough time to switch their radio back into receive mode. - */ - const uint32_t MIN_TX_WAIT_MSEC = 100; - - /** - * At the high end, this value is used to spread node attempts across time so when they are replying to a packet - * they don't both check that the airwaves are clear at the same moment. As long as they are off by some amount - * one of the two will be first to start transmitting and the other will see that. I bet 500ms is more than enough - * to guarantee this. - */ - // const uint32_t MAX_TX_WAIT_MSEC = 2000; // stress test would still fail occasionally with 1000 - - return random((MIN_TX_WAIT_MSEC), (MIN_TX_WAIT_MSEC + shortPacketMsec)); + /** We wait a random multiple of 'slotTimes' (see definition in header file) in order to avoid collisions. + The pool to take a random multiple from is the contention window (CW), which size depends on the + current channel utilization. */ + float channelUtil = airTime->channelUtilizationPercent(); + uint8_t CWsize = map(channelUtil, 0, 100, CWmin, CWmax); + // DEBUG_MSG("Current channel utilization is %f so setting CWsize to %d\n", channelUtil, CWsize); + return random(0, pow(2, CWsize)) * slotTimeMsec; } -/** The delay to use when we want to send something but the ether is busy */ +/** The delay to use when we want to flood a message */ uint32_t RadioInterface::getTxDelayMsecWeighted(float snr) { - /** At the low end we want to pick a delay large enough that anyone who just completed sending (some other node) - * has had enough time to switch their radio back into receive mode. - */ - const uint32_t MIN_TX_WAIT_MSEC = 100; - // The minimum value for a LoRa SNR const uint32_t SNR_MIN = -20; // The maximum value for a LoRa SNR const uint32_t SNR_MAX = 15; - // high SNR = Long Delay - // low SNR = Short Delay + // high SNR = large CW size (Long Delay) + // low SNR = small CW size (Short Delay) uint32_t delay = 0; - + uint8_t CWsize = map(snr, SNR_MIN, SNR_MAX, CWmin, CWmax); + // DEBUG_MSG("rx_snr of %f so setting CWsize to:%d\n", snr, CWsize); if (config.device.role == Config_DeviceConfig_Role_Router || config.device.role == Config_DeviceConfig_Role_RouterClient) { - delay = map(snr, SNR_MIN, SNR_MAX, MIN_TX_WAIT_MSEC, (MIN_TX_WAIT_MSEC + (shortPacketMsec / 2))); + delay = random(0, 2*CWsize) * slotTimeMsec; DEBUG_MSG("rx_snr found in packet. As a router, setting tx delay:%d\n", delay); } else { - delay = map(snr, SNR_MIN, SNR_MAX, MIN_TX_WAIT_MSEC + (shortPacketMsec / 2), (MIN_TX_WAIT_MSEC + shortPacketMsec * 2)); + delay = random(0, pow(2, CWsize)) * slotTimeMsec; DEBUG_MSG("rx_snr found in packet. Setting tx delay:%d\n", delay); } @@ -411,7 +400,6 @@ void RadioInterface::applyModemConfig() } power = loraConfig.tx_power; - shortPacketMsec = getPacketTime(sizeof(PacketHeader)); assert(myRegion); // Should have been found in init // Calculate the number of channels @@ -431,7 +419,7 @@ void RadioInterface::applyModemConfig() DEBUG_MSG("Radio myRegion->numChannels: %d\n", numChannels); DEBUG_MSG("Radio channel_num: %d\n", channel_num); DEBUG_MSG("Radio frequency: %f\n", getFreq()); - DEBUG_MSG("Short packet time: %u msec\n", shortPacketMsec); + DEBUG_MSG("Slot time: %u msec\n", slotTimeMsec); } /** diff --git a/src/mesh/RadioInterface.h b/src/mesh/RadioInterface.h index c5ff642c9..898ef20e4 100644 --- a/src/mesh/RadioInterface.h +++ b/src/mesh/RadioInterface.h @@ -52,8 +52,6 @@ class RadioInterface CallbackObserver notifyDeepSleepObserver = CallbackObserver(this, &RadioInterface::notifyDeepSleepCb); - /// Number of msecs we expect our shortest actual packet to be over the wire (used in retry timeout calcs) - uint32_t shortPacketMsec; protected: bool disabled = false; @@ -61,10 +59,16 @@ class RadioInterface float bw = 125; uint8_t sf = 9; uint8_t cr = 7; - + /** Slottime is the minimum time to wait, consisting of: + - CAD duration (maximum of SX126x and SX127x); + - roundtrip air propagation time (assuming max. 30km between nodes); + - Tx/Rx turnaround time (maximum of SX126x and SX127x); + - MAC processing time (measured on T-beam) */ + uint32_t slotTimeMsec = 8.5 * pow(2, sf)/bw + 0.2 + 0.4 + 7; uint16_t preambleLength = 32; // 8 is default, but we use longer to increase the amount of sleep time when receiving - const uint32_t MIN_TX_WAIT_MSEC = 100; // minimum time to wait before transmitting after sensing the channel in ms const uint32_t PROCESSING_TIME_MSEC = 4500; // time to construct, process and construct a packet again (empirically determined) + const uint8_t CWmin = 2; // minimum CWsize + const uint8_t CWmax = 8; // maximum CWsize MeshPacket *sendingPacket = NULL; // The packet we are currently sending uint32_t lastTxStart = 0L; @@ -128,10 +132,10 @@ class RadioInterface /** The delay to use for retransmitting dropped packets */ uint32_t getRetransmissionMsec(const MeshPacket *p); - /** The delay to use when we want to send something but the ether is busy */ + /** The delay to use when we want to send something */ uint32_t getTxDelayMsec(); - /** The delay to use when we want to send something but the ether is busy. Use a weighted scale based on SNR */ + /** The delay to use when we want to flood a message. Use a weighted scale based on SNR */ uint32_t getTxDelayMsecWeighted(float snr); diff --git a/src/mesh/RadioLibInterface.cpp b/src/mesh/RadioLibInterface.cpp index 5d049a291..afe176656 100644 --- a/src/mesh/RadioLibInterface.cpp +++ b/src/mesh/RadioLibInterface.cpp @@ -170,17 +170,11 @@ ErrorCode RadioLibInterface::send(MeshPacket *p) } /** radio helper thread callback. - - We never immediately transmit after any operation (either rx or tx). Instead we should start receiving and - wait a random delay of 100ms to 100ms+shortPacketMsec to make sure we are not stomping on someone else. The 100ms delay - at the beginning ensures all possible listeners have had time to finish processing the previous packet and now have their - radio in RX state. The up to 100ms+shortPacketMsec random delay gives a chance for all possible senders to have high odds - of detecting that someone else started transmitting first and then they will wait until that packet finishes. - - NOTE: the large flood rebroadcast delay might still be needed even with this approach. Because we might not be able to - hear other transmitters that we are potentially stomping on. Requires further thought. - - FIXME, the MIN_TX_WAIT_MSEC and MAX_TX_WAIT_MSEC values should be tuned via logic analyzer later. + We never immediately transmit after any operation (either Rx or Tx). Instead we should wait a random multiple of + 'slotTimes' (see definition in RadioInterface.h) taken from a contention window (CW) to lower the chance of collision. + The CW size is determined by setTransmitDelay() and depends either on the current channel utilization or SNR in case + of a flooding message. After this, we perform channel activity detection (CAD) and reset the transmit delay if it is + currently active. */ void RadioLibInterface::onNotify(uint32_t notification) {