Skip to content
Open
12 changes: 12 additions & 0 deletions examples/simple_repeater/MyMesh.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
12 changes: 12 additions & 0 deletions examples/simple_room_server/MyMesh.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
16 changes: 14 additions & 2 deletions examples/simple_sensor/SensorMesh.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions src/Dispatcher.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ uint32_t Dispatcher::getCADFailRetryDelay() const {
return 200;
}
uint32_t Dispatcher::getCADFailMaxDuration() const {
if (_radio->isAS923_1_JP()) return UINT32_MAX; // ARIB STD-T108: never force TX during LBT
return 4000; // 4 seconds
}

Expand Down
5 changes: 5 additions & 0 deletions src/Dispatcher.h
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,11 @@ class Radio {

virtual float getLastRSSI() const { return 0; }
virtual float getLastSNR() const { return 0; }

virtual bool isAS923_1_JP() const { return false; }

virtual int getMaxTextLen() const { return 10 * 16; } // default 160 bytes
virtual int getMaxGroupTextLen() const { return 10 * 16; } // default 160 bytes
};

/**
Expand Down
11 changes: 7 additions & 4 deletions src/helpers/BaseChatMesh.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -419,8 +419,9 @@ void BaseChatMesh::onGroupDataRecv(mesh::Packet* packet, uint8_t type, const mes

mesh::Packet* BaseChatMesh::composeMsgPacket(const ContactInfo& recipient, uint32_t timestamp, uint8_t attempt, const char *text, uint32_t& expected_ack) {
int text_len = strlen(text);
if (text_len > MAX_TEXT_LEN) return NULL;
if (attempt > 3 && text_len > MAX_TEXT_LEN-2) return NULL;
int max_len = _radio->getMaxTextLen();
if (text_len > max_len) return NULL;
if (attempt > 3 && text_len > max_len - 2) return NULL;

uint8_t temp[5+MAX_TEXT_LEN+1];
memcpy(temp, &timestamp, 4); // mostly an extra blob to help make packet_hash unique
Expand Down Expand Up @@ -460,7 +461,8 @@ int BaseChatMesh::sendMessage(const ContactInfo& recipient, uint32_t timestamp,

int BaseChatMesh::sendCommandData(const ContactInfo& recipient, uint32_t timestamp, uint8_t attempt, const char* text, uint32_t& est_timeout) {
int text_len = strlen(text);
if (text_len > MAX_TEXT_LEN) return MSG_SEND_FAILED;
int max_len = _radio->getMaxTextLen();
if (text_len > max_len) return MSG_SEND_FAILED;

uint8_t temp[5+MAX_TEXT_LEN+1];
memcpy(temp, &timestamp, 4); // mostly an extra blob to help make packet_hash unique
Expand Down Expand Up @@ -493,7 +495,8 @@ bool BaseChatMesh::sendGroupMessage(uint32_t timestamp, mesh::GroupChannel& chan
char *ep = strchr((char *) &temp[5], 0);
int prefix_len = ep - (char *) &temp[5];

if (text_len + prefix_len > MAX_TEXT_LEN) text_len = MAX_TEXT_LEN - prefix_len;
int max_len = _radio->getMaxGroupTextLen();
if (text_len + prefix_len > max_len) text_len = max_len - prefix_len;
memcpy(ep, text, text_len);
ep[text_len] = 0; // null terminator

Expand Down
1 change: 1 addition & 0 deletions src/helpers/radiolib/CustomLR1110.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,5 @@ class CustomLR1110 : public LR1110 {
}

uint8_t getSpreadingFactor() const { return spreadingFactor; }
uint8_t getCodingRate() const { return this->codingRate + 4; } // RadioLib stores 1-4, return 5-8
};
2 changes: 2 additions & 0 deletions src/helpers/radiolib/CustomLR1110Wrapper.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ class CustomLR1110Wrapper : public RadioLibWrapper {
float getLastSNR() const override { return ((CustomLR1110 *)_radio)->getSNR(); }

uint8_t getSpreadingFactor() const override { return ((CustomLR1110 *)_radio)->getSpreadingFactor(); }
uint8_t getCodingRate() const override { return ((CustomLR1110 *)_radio)->getCodingRate(); }
float getFreqMHz() const override { return ((CustomLR1110 *)_radio)->getFreqMHz(); }

void setRxBoostedGainMode(bool en) override {
((CustomLR1110 *)_radio)->setRxBoostedGainMode(en);
Expand Down
2 changes: 2 additions & 0 deletions src/helpers/radiolib/CustomSX1262Wrapper.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ class CustomSX1262Wrapper : public RadioLibWrapper {
return packetScoreInt(snr, sf, packet_len);
}
uint8_t getSpreadingFactor() const override { return ((CustomSX1262 *)_radio)->spreadingFactor; }
uint8_t getCodingRate() const override { return ((CustomSX1262 *)_radio)->codingRate + 4; } // RadioLib stores 1-4, return 5-8
float getFreqMHz() const override { return ((CustomSX1262 *)_radio)->freqMHz; }
virtual void powerOff() override {
((CustomSX1262 *)_radio)->sleep(false);
}
Expand Down
45 changes: 42 additions & 3 deletions src/helpers/radiolib/RadioLibWrappers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@
#define RADIOLIB_STATIC_ONLY 1
#include "RadioLibWrappers.h"

#ifdef NRF52_PLATFORM
#define YIELD_TASK() vTaskDelay(1)
#else
#define YIELD_TASK() delay(1)
#endif

#define STATE_IDLE 0
#define STATE_RX 1
#define STATE_TX_WAIT 3
Expand Down Expand Up @@ -176,6 +182,9 @@ bool RadioLibWrapper::isSendComplete() {
void RadioLibWrapper::onSendFinished() {
_radio->finishTransmit();
_board->onAfterTransmit();
if (isAS923_1_JP()) {
delay(50); // ARIB STD-T108 §3.4.1: >= 50ms between transmissions
}
state = STATE_IDLE;
}

Expand All @@ -184,10 +193,40 @@ int16_t RadioLibWrapper::performChannelScan() {
}

bool RadioLibWrapper::isChannelActive() {
// int.thresh: RSSI-based interference detection (relative to noise floor)
if (_threshold != 0 && getCurrentRSSI() > _noise_floor + _threshold) return true;
if (isAS923_1_JP()) {
// ARIB STD-T108: 5ms continuous RSSI sensing, -80dBm absolute threshold
uint32_t sense_start = millis();
while (millis() - sense_start < 5) {
if (getCurrentRSSI() > -80.0f) {
_busy_count++;
uint32_t base_ms = 2000;
uint32_t max_backoff = min(base_ms * (1u << _busy_count), (uint32_t)16000);
uint32_t backoff_until = millis() + random(max_backoff / 2, max_backoff);
while (millis() < backoff_until) {
YIELD_TASK();
}
return true;
}
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 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();
}
// JP RSSI sensing passed; fall through to CAD if enabled
} else {
// Non-JP: RSSI-based interference detection (relative to noise floor)
if (_threshold != 0 && getCurrentRSSI() > _noise_floor + _threshold) return true;
}

// cad: hardware channel activity detection
// hardware channel activity detection (JP and non-JP)
if (_cad_enabled) {
int16_t result = performChannelScan();
// scanChannel() triggers DIO interrupt (CAD done) which sets STATE_INT_READY
Expand Down
33 changes: 32 additions & 1 deletion src/helpers/radiolib/RadioLibWrappers.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ class RadioLibWrapper : public mesh::Radio {
uint32_t n_recv, n_sent, n_recv_errors;
int16_t _noise_floor, _threshold;
bool _cad_enabled;
uint8_t _busy_count;
uint16_t _num_floor_samples;
int32_t _floor_sample_sum;
uint8_t _preamble_sf;
Expand All @@ -21,7 +22,7 @@ class RadioLibWrapper : public mesh::Radio {
virtual void doResetAGC();

public:
RadioLibWrapper(PhysicalLayer& radio, mesh::MainBoard& board) : _radio(&radio), _board(&board), _preamble_sf(0) { n_recv = n_sent = 0; }
RadioLibWrapper(PhysicalLayer& radio, mesh::MainBoard& board) : _radio(&radio), _board(&board), _busy_count(0), _preamble_sf(0) { n_recv = n_sent = 0; }

void begin() override;
virtual void powerOff() { _radio->sleep(); }
Expand All @@ -47,6 +48,36 @@ class RadioLibWrapper : public mesh::Radio {
virtual uint8_t getSpreadingFactor() const { return LORA_SF; }
static uint16_t preambleLengthForSF(uint8_t sf) { return sf <= 8 ? 32 : 16; }
void updatePreamble(uint8_t sf) { _preamble_sf = sf; _radio->setPreambleLength(preambleLengthForSF(sf)); }
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 ||
fabsf(freq - 921.000f) < 0.05f ||
fabsf(freq - 921.200f) < 0.05f);
}

int getMaxTextLen() const override {
if (!isAS923_1_JP()) return 10 * 16; // default 160 bytes
uint8_t cr = getCodingRate();
if (cr <= 5) return 64; // 3874ms @ SF12/BW125/CR4-5
if (cr == 6) return 48; // 3874ms @ SF12/BW125/CR4-6
if (cr == 7) return 32; // 3678ms @ SF12/BW125/CR4-7
return 24; // 3547ms @ SF12/BW125/CR4-8
}

int getMaxGroupTextLen() const override {
if (!isAS923_1_JP()) return 10 * 16; // default 160 bytes
uint8_t cr = getCodingRate();
if (cr <= 5) return 64; // 3710ms @ SF12/BW125/CR4-5
if (cr == 6) return 48; // 3678ms @ SF12/BW125/CR4-6
if (cr == 7) return 39; // 3907ms @ SF12/BW125/CR4-7
return 29; // 3809ms @ SF12/BW125/CR4-8
}

virtual int16_t performChannelScan();

int getNoiseFloor() const override { return _noise_floor; }
Expand Down