diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index b66e19522..73a5bf32e 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -541,10 +541,22 @@ int MyMesh::calcRxDelay(float score, uint32_t air_time) const { } uint32_t MyMesh::getRetransmitDelay(const mesh::Packet *packet) { + if (_radio->isAS923_1_JP()) { + // JP LBT: suppress txdelay to jitter-scale to avoid adding unnecessary + // latency on top of LBT backoff. A window equal to jitter_max gives + // ~33% collision reduction vs zero, scales naturally with airtime as + // CR changes, and keeps average added delay to ~56ms at SF12/BW125. + uint32_t jitter_max = _radio->getEstAirtimeFor(MAX_TRANS_UNIT) / RadioLibWrapper::JP_LBT_JITTER_DIVISOR; + return getRNG()->nextInt(0, jitter_max + 1); + } uint32_t t = (_radio->getEstAirtimeFor(packet->getPathByteLen() + packet->payload_len + 2) * _prefs.tx_delay_factor); return getRNG()->nextInt(0, 5*t + 1); } uint32_t MyMesh::getDirectRetransmitDelay(const mesh::Packet *packet) { + if (_radio->isAS923_1_JP()) { + uint32_t jitter_max = _radio->getEstAirtimeFor(MAX_TRANS_UNIT) / RadioLibWrapper::JP_LBT_JITTER_DIVISOR; + return getRNG()->nextInt(0, jitter_max + 1); + } uint32_t t = (_radio->getEstAirtimeFor(packet->getPathByteLen() + packet->payload_len + 2) * _prefs.direct_tx_delay_factor); return getRNG()->nextInt(0, 5*t + 1); } diff --git a/examples/simple_room_server/MyMesh.cpp b/examples/simple_room_server/MyMesh.cpp index 36978e808..4c272d312 100644 --- a/examples/simple_room_server/MyMesh.cpp +++ b/examples/simple_room_server/MyMesh.cpp @@ -272,10 +272,22 @@ const char *MyMesh::getLogDateTime() { } uint32_t MyMesh::getRetransmitDelay(const mesh::Packet *packet) { + if (_radio->isAS923_1_JP()) { + // JP LBT: suppress txdelay to jitter-scale to avoid adding unnecessary + // latency on top of LBT backoff. A window equal to jitter_max gives + // ~33% collision reduction vs zero, scales naturally with airtime as + // CR changes, and keeps average added delay to ~56ms at SF12/BW125. + uint32_t jitter_max = _radio->getEstAirtimeFor(MAX_TRANS_UNIT) / RadioLibWrapper::JP_LBT_JITTER_DIVISOR; + return getRNG()->nextInt(0, jitter_max + 1); + } uint32_t t = (_radio->getEstAirtimeFor(packet->getPathByteLen() + packet->payload_len + 2) * _prefs.tx_delay_factor); return getRNG()->nextInt(0, 5*t + 1); } uint32_t MyMesh::getDirectRetransmitDelay(const mesh::Packet *packet) { + if (_radio->isAS923_1_JP()) { + uint32_t jitter_max = _radio->getEstAirtimeFor(MAX_TRANS_UNIT) / RadioLibWrapper::JP_LBT_JITTER_DIVISOR; + return getRNG()->nextInt(0, jitter_max + 1); + } uint32_t t = (_radio->getEstAirtimeFor(packet->getPathByteLen() + packet->payload_len + 2) * _prefs.direct_tx_delay_factor); return getRNG()->nextInt(0, 5*t + 1); } diff --git a/examples/simple_sensor/SensorMesh.cpp b/examples/simple_sensor/SensorMesh.cpp index 59c9aa090..066b2e054 100644 --- a/examples/simple_sensor/SensorMesh.cpp +++ b/examples/simple_sensor/SensorMesh.cpp @@ -313,12 +313,24 @@ int SensorMesh::calcRxDelay(float score, uint32_t air_time) const { } uint32_t SensorMesh::getRetransmitDelay(const mesh::Packet* packet) { + if (_radio->isAS923_1_JP()) { + // JP LBT: suppress txdelay to jitter-scale to avoid adding unnecessary + // latency on top of LBT backoff. A window equal to jitter_max gives + // ~33% collision reduction vs zero, scales naturally with airtime as + // CR changes, and keeps average added delay to ~56ms at SF12/BW125. + uint32_t jitter_max = _radio->getEstAirtimeFor(MAX_TRANS_UNIT) / RadioLibWrapper::JP_LBT_JITTER_DIVISOR; + return getRNG()->nextInt(0, jitter_max + 1); + } uint32_t t = (_radio->getEstAirtimeFor(packet->getPathByteLen() + packet->payload_len + 2) * _prefs.tx_delay_factor); - return getRNG()->nextInt(0, 6)*t; + return getRNG()->nextInt(0, 5*t + 1); } uint32_t SensorMesh::getDirectRetransmitDelay(const mesh::Packet* packet) { + if (_radio->isAS923_1_JP()) { + uint32_t jitter_max = _radio->getEstAirtimeFor(MAX_TRANS_UNIT) / RadioLibWrapper::JP_LBT_JITTER_DIVISOR; + return getRNG()->nextInt(0, jitter_max + 1); + } uint32_t t = (_radio->getEstAirtimeFor(packet->getPathByteLen() + packet->payload_len + 2) * _prefs.direct_tx_delay_factor); - return getRNG()->nextInt(0, 6)*t; + return getRNG()->nextInt(0, 5*t + 1); } int SensorMesh::getInterferenceThreshold() const { return _prefs.interference_threshold; diff --git a/src/helpers/radiolib/RadioLibWrappers.cpp b/src/helpers/radiolib/RadioLibWrappers.cpp index f640a8469..f6afbaaff 100644 --- a/src/helpers/radiolib/RadioLibWrappers.cpp +++ b/src/helpers/radiolib/RadioLibWrappers.cpp @@ -209,8 +209,14 @@ bool RadioLibWrapper::isChannelActive() { } YIELD_TASK(); } + // Channel free: reset busy counter and add airtime-scaled jitter. + // JP_LBT_JITTER_DIVISOR controls jitter upper bound: + // /8 -> SF12/BW125 ~975ms, SF7/BW62.5 ~50ms + // /16 -> SF12/BW125 ~490ms, SF7/BW62.5 ~25ms + // /32 -> SF12/BW125 ~245ms, SF7/BW62.5 ~12ms (default) _busy_count = 0; - uint32_t jitter_until = millis() + random(0, 500); + uint32_t airtime_ms = getEstAirtimeFor(MAX_TRANS_UNIT); + uint32_t jitter_until = millis() + random(0, airtime_ms / JP_LBT_JITTER_DIVISOR); while (millis() < jitter_until) { YIELD_TASK(); } diff --git a/src/helpers/radiolib/RadioLibWrappers.h b/src/helpers/radiolib/RadioLibWrappers.h index ef04e158e..699b31223 100644 --- a/src/helpers/radiolib/RadioLibWrappers.h +++ b/src/helpers/radiolib/RadioLibWrappers.h @@ -57,6 +57,8 @@ public: virtual uint8_t getCodingRate() const { return 8; } // default CR4/8, override in subclass virtual float getFreqMHz() const { return 0.0f; } // default unknown, override in subclass + static constexpr uint8_t JP_LBT_JITTER_DIVISOR = 32; + bool isAS923_1_JP() const override { float freq = getFreqMHz(); return (fabsf(freq - 920.800f) < 0.05f ||