This commit is contained in:
Jiro 2026-04-20 21:53:53 +10:00 committed by GitHub
commit 58eef2e792
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 143 additions and 17 deletions

View file

@ -258,7 +258,7 @@ float MyMesh::getAirtimeBudgetFactor() const {
}
int MyMesh::getInterferenceThreshold() const {
return 0; // disabled for now, until currentRSSI() problem is resolved
return 1; // non-zero enables hardware CAD (Channel Activity Detection) before TX
}
int MyMesh::calcRxDelay(float score, uint32_t air_time) const {

View file

@ -151,6 +151,10 @@ protected:
uint32_t calcFloodTimeoutMillisFor(uint32_t pkt_airtime_millis) const override;
uint32_t calcDirectTimeoutMillisFor(uint32_t pkt_airtime_millis, uint8_t path_len) const override;
uint32_t getCADFailMaxDuration() const override {
if (_radio->isJapanMode()) return UINT32_MAX; // JP LBT: no forced TX — channel must be free per ARIB STD-T108
return Dispatcher::getCADFailMaxDuration();
}
void onSendTimeout() override;
// DataStoreHost methods

View file

@ -886,7 +886,7 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc
_prefs.advert_interval = 1; // default to 2 minutes for NEW installs
_prefs.flood_advert_interval = 12; // 12 hours
_prefs.flood_max = 64;
_prefs.interference_threshold = 0; // disabled
_prefs.interference_threshold = 1; // non-zero enables hardware CAD before TX
// bridge defaults
_prefs.bridge_enabled = 1; // enabled

View file

@ -143,6 +143,10 @@ protected:
void logTx(mesh::Packet* pkt, int len) override;
void logTxFail(mesh::Packet* pkt, int len) override;
int calcRxDelay(float score, uint32_t air_time) const override;
uint32_t getCADFailMaxDuration() const override {
if (_radio->isJapanMode()) return UINT32_MAX; // JP LBT: no forced TX — channel must be free per ARIB STD-T108
return Dispatcher::getCADFailMaxDuration();
}
uint32_t getRetransmitDelay(const mesh::Packet* packet) override;
uint32_t getDirectRetransmitDelay(const mesh::Packet* packet) override;

View file

@ -643,7 +643,7 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc
_prefs.advert_interval = 1; // default to 2 minutes for NEW installs
_prefs.flood_advert_interval = 12; // 12 hours
_prefs.flood_max = 64;
_prefs.interference_threshold = 0; // disabled
_prefs.interference_threshold = 1; // non-zero enables hardware CAD before TX
#ifdef ROOM_PASSWORD
StrHelper::strncpy(_prefs.guest_password, ROOM_PASSWORD, sizeof(_prefs.guest_password));
#endif

View file

@ -137,6 +137,11 @@ protected:
void logTxFail(mesh::Packet* pkt, int len) override;
int calcRxDelay(float score, uint32_t air_time) const override;
uint32_t getCADFailMaxDuration() const override {
if (_radio->isJapanMode()) return UINT32_MAX; // JP LBT: no forced TX — channel must be free per ARIB STD-T108
return Dispatcher::getCADFailMaxDuration();
}
const char* getLogDateTime() override;
uint32_t getRetransmitDelay(const mesh::Packet* packet) override;
uint32_t getDirectRetransmitDelay(const mesh::Packet* packet) override;

View file

@ -725,7 +725,7 @@ SensorMesh::SensorMesh(mesh::MainBoard& board, mesh::Radio& radio, mesh::Millise
_prefs.flood_advert_interval = 0; // disabled
_prefs.disable_fwd = true;
_prefs.flood_max = 64;
_prefs.interference_threshold = 0; // disabled
_prefs.interference_threshold = 1; // non-zero enables hardware CAD before TX
// GPS defaults
_prefs.gps_enabled = 0;

View file

@ -117,6 +117,10 @@ protected:
float getAirtimeBudgetFactor() const override;
bool allowPacketForward(const mesh::Packet* packet) override;
int calcRxDelay(float score, uint32_t air_time) const override;
uint32_t getCADFailMaxDuration() const override {
if (_radio->isJapanMode()) return UINT32_MAX; // JP LBT: no forced TX — channel must be free per ARIB STD-T108
return Dispatcher::getCADFailMaxDuration();
}
uint32_t getRetransmitDelay(const mesh::Packet* packet) override;
uint32_t getDirectRetransmitDelay(const mesh::Packet* packet) override;
int getInterferenceThreshold() const override;

View file

@ -38,6 +38,10 @@ public:
virtual float packetScore(float snr, int packet_len) = 0;
virtual int getMaxTextLen() const { return 10 * 16; } // default: non-JP
virtual int getMaxGroupTextLen() const { return 10 * 16; } // default: non-JP
virtual bool isJapanMode() const { return false; } // default: non-JP
/**
* \brief starts the raw packet send. (no wait)
* \param bytes the raw packet data

View file

@ -395,9 +395,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
temp[4] = (attempt & 3);
@ -436,7 +436,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
@ -469,7 +470,9 @@ 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

View file

@ -5,7 +5,8 @@
#include <helpers/AdvertDataHelpers.h>
#include <helpers/TxtDataHelpers.h>
#define MAX_TEXT_LEN (10*CIPHER_BLOCK_SIZE) // must be LESS than (MAX_PACKET_PAYLOAD - 4 - CIPHER_MAC_SIZE - 1)
#define MAX_TEXT_LEN (10*CIPHER_BLOCK_SIZE)
// must be LESS than (MAX_PACKET_PAYLOAD - 4 - CIPHER_MAC_SIZE - 1)
#include "ContactInfo.h"

View file

@ -36,4 +36,8 @@ class CustomLR1110 : public LR1110 {
bool detected = ((irq & RADIOLIB_LR11X0_IRQ_SYNC_WORD_HEADER_VALID) || (irq & RADIOLIB_LR11X0_IRQ_PREAMBLE_DETECTED));
return detected;
}
};
uint8_t getCodingRate() const {
return this->codingRate + 4; // RadioLib stores 1-4, return 5-8
}
};

View file

@ -31,4 +31,11 @@ public:
bool getRxBoostedGainMode() const override {
return ((CustomLR1110 *)_radio)->getRxBoostedGainMode();
}
uint8_t getCodingRate() const override {
return ((CustomLR1110 *)_radio)->getCodingRate();
}
float getFreqMHz() const override {
return ((CustomLR1110 *)_radio)->getFreqMHz();
}
};

View file

@ -23,4 +23,11 @@ public:
}
void doResetAGC() override { sx126xResetAGC((SX126x *)_radio); }
uint8_t getCodingRate() const override {
return ((CustomSTM32WLx *)_radio)->codingRate + 4;
}
float getFreqMHz() const override {
return ((CustomSTM32WLx *)_radio)->freqMHz;
}
};

View file

@ -97,4 +97,4 @@ class CustomSX1262 : public SX1262 {
readRegister(RADIOLIB_SX126X_REG_RX_GAIN, &rxGain, 1);
return (rxGain == RADIOLIB_SX126X_RX_GAIN_BOOSTED);
}
};
};

View file

@ -36,4 +36,11 @@ public:
bool getRxBoostedGainMode() const override {
return ((CustomSX1262 *)_radio)->getRxBoostedGainMode();
}
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;
}
};

View file

@ -2,6 +2,13 @@
#define RADIOLIB_STATIC_ONLY 1
#include "RadioLibWrappers.h"
// Platform-safe yield for use in busy-wait loops
#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
@ -34,6 +41,7 @@ void RadioLibWrapper::begin() {
_noise_floor = 0;
_threshold = 0;
_busy_count = 0; // initialize exponential backoff counter
// start average out some samples
_num_floor_samples = 0;
@ -165,13 +173,51 @@ bool RadioLibWrapper::isSendComplete() {
void RadioLibWrapper::onSendFinished() {
_radio->finishTransmit();
_board->onAfterTransmit();
if (isJapanMode()) {
// ARIB STD-T108: wait >= 50ms after TX before next transmission
delay(50);
}
state = STATE_IDLE;
}
int16_t RadioLibWrapper::performChannelScan() {
return _radio->scanChannel();
}
bool RadioLibWrapper::isChannelActive() {
return _threshold == 0
? false // interference check is disabled
: getCurrentRSSI() > _noise_floor + _threshold;
if (_threshold == 0) return false; // interference check is disabled
// Activate JP_STRICT LBT on Japan 920MHz band 3 channels only
// CH25=920.800MHz, CH26=921.000MHz, CH27=921.200MHz (ARIB STD-T108)
if (isJapanMode()) {
// ARIB STD-T108 compliant LBT: continuous RSSI sensing for >= 5ms
// Energy-based sensing required; LoRa CAD not used
uint32_t sense_start = millis();
while (millis() - sense_start < 5) {
if (getCurrentRSSI() > -80.0f) {
// Channel busy: exponential backoff (tuned for JP 4s airtime)
_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 small jitter
_busy_count = 0;
uint32_t jitter_until = millis() + random(0, 500);
while (millis() < jitter_until) {
YIELD_TASK();
}
return false;
}
// Non-JP: original behavior (RSSI threshold only)
return getCurrentRSSI() > _noise_floor + _threshold;
}
float RadioLibWrapper::getLastRSSI() const {

View file

@ -9,6 +9,7 @@ protected:
mesh::MainBoard* _board;
uint32_t n_recv, n_sent, n_recv_errors;
int16_t _noise_floor, _threshold;
uint8_t _busy_count; // consecutive busy detections for exponential backoff
uint16_t _num_floor_samples;
int32_t _floor_sample_sum;
@ -31,14 +32,43 @@ public:
bool isInRecvMode() const override;
bool isChannelActive();
bool isReceiving() override {
bool isReceiving() override {
if (isReceivingPacket()) return true;
return isChannelActive();
}
virtual float getCurrentRSSI() =0;
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
//
bool isJapanMode() const {
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 {
if (!isJapanMode()) 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 {
if (!isJapanMode()) 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; }
void triggerNoiseFloorCalibrate(int threshold) override;
void resetAGC() override;

View file

@ -118,4 +118,4 @@ build_flags =
; -D MESH_PACKET_LOGGING=1
-D MESH_DEBUG=1
build_src_filter = ${rak4631.build_src_filter}
+<../examples/simple_sensor>
+<../examples/simple_sensor>