Introduce contention window (CW) (#1500)

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
pull/1499/head
GUVWAF 2022-06-13 15:21:18 +02:00 zatwierdzone przez GitHub
rodzic 553b35d0ad
commit a1b07ed6aa
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
3 zmienionych plików z 38 dodań i 52 usunięć

Wyświetl plik

@ -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);
}
/**

Wyświetl plik

@ -52,8 +52,6 @@ class RadioInterface
CallbackObserver<RadioInterface, void *> notifyDeepSleepObserver =
CallbackObserver<RadioInterface, void *>(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);

Wyświetl plik

@ -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)
{