kopia lustrzana https://github.com/meshtastic/firmware
Introduce contention window (CW) (#1500)
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>pull/1499/head
rodzic
553b35d0ad
commit
a1b07ed6aa
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
Ładowanie…
Reference in New Issue