Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -82,4 +82,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 @@ -43,6 +43,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(); }

bool setRxBoostedGainMode(bool en) override {
return ((CustomLR1110 *)_radio)->setRxBoostedGainMode(en) == RADIOLIB_ERR_NONE;
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 @@ -37,6 +37,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
55 changes: 52 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,50 @@ 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()) {
// Non-blocking backoff: if a prior busy detection armed a backoff wait,
// report busy and let Dispatcher::checkSend() re-poll on the next loop()
// pass instead of spin-waiting here (which used to block the Dispatcher
// loop for up to 16s).
if (_lbt_backoff_active) {
if ((int32_t)(millis() - _lbt_deadline) < 0) {
return true; // still waiting; re-checked next call
}
_lbt_backoff_active = false;
return isChannelActive(); // backoff elapsed -- re-sense (bounded, single-level recursion)
}

// 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);
_lbt_deadline = millis() + random(max_backoff / 2, max_backoff);
_lbt_backoff_active = true;
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
36 changes: 35 additions & 1 deletion src/helpers/radiolib/RadioLibWrappers.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,22 @@ 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;

bool _lbt_backoff_active; // non-blocking wait after RSSI busy detection (up to 16s)
uint32_t _lbt_deadline;

void idle();
void startRecv();
float packetScoreInt(float snr, int sf, int packet_len);
virtual bool isReceivingPacket() =0;
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), _lbt_backoff_active(false), _lbt_deadline(0) { n_recv = n_sent = 0; }

void begin() override;
virtual void powerOff() { _radio->sleep(); }
Expand All @@ -53,6 +57,36 @@ class RadioLibWrapper : public mesh::Radio {
static uint16_t preambleLengthForSF(uint8_t sf) { return sf <= 8 ? 32 : 16; }
void updatePreamble(uint8_t sf) { _preamble_sf = sf; _radio->setPreambleLength(preambleLengthForSF(sf)); }
PacketMillis calcMaxPacketMillis(uint8_t sf, float bw, uint8_t cr, uint8_t preambleSymbols);
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