MeshCore/src/helpers/radiolib/RadioLibWrappers.cpp

250 lines
7.2 KiB
C++
Raw Normal View History

2025-01-13 14:07:48 +11:00
#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
2025-01-13 14:07:48 +11:00
#define STATE_IDLE 0
#define STATE_RX 1
#define STATE_TX_WAIT 3
#define STATE_TX_DONE 4
#define STATE_INT_READY 16
#define NUM_NOISE_FLOOR_SAMPLES 64
#define SAMPLING_THRESHOLD 14
2025-01-13 14:07:48 +11:00
static volatile uint8_t state = STATE_IDLE;
// this function is called when a complete packet
// is transmitted by the module
static
#if defined(ESP8266) || defined(ESP32)
ICACHE_RAM_ATTR
#endif
void setFlag(void) {
// we sent a packet, set the flag
state |= STATE_INT_READY;
}
void RadioLibWrapper::begin() {
_radio->setPacketReceivedAction(setFlag); // this is also SentComplete interrupt
state = STATE_IDLE;
if (_board->getStartupReason() == BD_STARTUP_RX_PACKET) { // received a LoRa packet (while in deep sleep)
setFlag(); // LoRa packet is already received
}
2025-05-25 21:44:15 +10:00
_noise_floor = 0;
_threshold = 0;
_busy_count = 0; // initialize exponential backoff counter
// start average out some samples
_num_floor_samples = 0;
_floor_sample_sum = 0;
2025-01-13 14:07:48 +11:00
}
void RadioLibWrapper::idle() {
_radio->standby();
state = STATE_IDLE; // need another startReceive()
}
void RadioLibWrapper::triggerNoiseFloorCalibrate(int threshold) {
_threshold = threshold;
if (_num_floor_samples >= NUM_NOISE_FLOOR_SAMPLES) { // ignore trigger if currently sampling
2025-05-25 21:44:15 +10:00
_num_floor_samples = 0;
_floor_sample_sum = 0;
}
}
void RadioLibWrapper::doResetAGC() {
_radio->sleep(); // warm sleep to reset analog frontend
}
void RadioLibWrapper::resetAGC() {
// make sure we're not mid-receive of packet!
if ((state & STATE_INT_READY) != 0 || isReceivingPacket()) return;
doResetAGC();
state = STATE_IDLE; // trigger a startReceive()
// Reset noise floor sampling so it reconverges from scratch.
// Without this, a stuck _noise_floor of -120 makes the sampling threshold
// too low (-106) to accept normal samples (~-105), self-reinforcing the
// stuck value even after the receiver has recovered.
_noise_floor = 0;
_num_floor_samples = 0;
_floor_sample_sum = 0;
}
void RadioLibWrapper::loop() {
if (state == STATE_RX && _num_floor_samples < NUM_NOISE_FLOOR_SAMPLES) {
if (!isReceivingPacket()) {
2025-05-25 21:44:15 +10:00
int rssi = getCurrentRSSI();
if (rssi < _noise_floor + SAMPLING_THRESHOLD) { // only consider samples below current floor + sampling THRESHOLD
2025-05-25 21:44:15 +10:00
_num_floor_samples++;
_floor_sample_sum += rssi;
}
}
2025-05-25 21:44:15 +10:00
} else if (_num_floor_samples >= NUM_NOISE_FLOOR_SAMPLES && _floor_sample_sum != 0) {
_noise_floor = _floor_sample_sum / NUM_NOISE_FLOOR_SAMPLES;
if (_noise_floor < -120) {
_noise_floor = -120; // clamp to lower bound of -120dBi
}
_floor_sample_sum = 0;
MESH_DEBUG_PRINTLN("RadioLibWrapper: noise_floor = %d", (int)_noise_floor);
}
}
void RadioLibWrapper::startRecv() {
int err = _radio->startReceive();
if (err == RADIOLIB_ERR_NONE) {
state = STATE_RX;
} else {
MESH_DEBUG_PRINTLN("RadioLibWrapper: error: startReceive(%d)", err);
}
}
bool RadioLibWrapper::isInRecvMode() const {
return (state & ~STATE_INT_READY) == STATE_RX;
}
2025-01-13 14:07:48 +11:00
int RadioLibWrapper::recvRaw(uint8_t* bytes, int sz) {
int len = 0;
2025-01-13 14:07:48 +11:00
if (state & STATE_INT_READY) {
len = _radio->getPacketLength();
2025-01-13 14:07:48 +11:00
if (len > 0) {
if (len > sz) { len = sz; }
int err = _radio->readData(bytes, len);
if (err != RADIOLIB_ERR_NONE) {
MESH_DEBUG_PRINTLN("RadioLibWrapper: error: readData(%d)", err);
2025-03-09 22:15:58 +11:00
len = 0;
n_recv_errors++;
2025-01-13 14:07:48 +11:00
} else {
// Serial.print(" readData() -> "); Serial.println(len);
2025-03-09 22:15:58 +11:00
n_recv++;
2025-01-13 14:07:48 +11:00
}
}
state = STATE_IDLE; // need another startReceive()
}
if (state != STATE_RX) {
int err = _radio->startReceive();
if (err == RADIOLIB_ERR_NONE) {
state = STATE_RX;
} else {
MESH_DEBUG_PRINTLN("RadioLibWrapper: error: startReceive(%d)", err);
2025-01-13 14:07:48 +11:00
}
}
return len;
2025-01-13 14:07:48 +11:00
}
uint32_t RadioLibWrapper::getEstAirtimeFor(int len_bytes) {
return _radio->getTimeOnAir(len_bytes) / 1000;
}
bool RadioLibWrapper::startSendRaw(const uint8_t* bytes, int len) {
2025-01-13 14:07:48 +11:00
_board->onBeforeTransmit();
int err = _radio->startTransmit((uint8_t *) bytes, len);
if (err == RADIOLIB_ERR_NONE) {
state = STATE_TX_WAIT;
return true;
2025-01-13 14:07:48 +11:00
}
MESH_DEBUG_PRINTLN("RadioLibWrapper: error: startTransmit(%d)", err);
idle(); // trigger another startRecv()
_board->onAfterTransmit();
return false;
2025-01-13 14:07:48 +11:00
}
bool RadioLibWrapper::isSendComplete() {
if (state & STATE_INT_READY) {
state = STATE_IDLE;
n_sent++;
return true;
}
return false;
}
void RadioLibWrapper::onSendFinished() {
_radio->finishTransmit();
_board->onAfterTransmit();
if (isJapanMode()) {
// ARIB STD-T108: wait >= 50ms after TX before next transmission
delay(50);
}
2025-01-13 14:07:48 +11:00
state = STATE_IDLE;
}
int16_t RadioLibWrapper::performChannelScan() {
return _radio->scanChannel();
}
bool RadioLibWrapper::isChannelActive() {
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;
}
2025-01-13 14:07:48 +11:00
float RadioLibWrapper::getLastRSSI() const {
return _radio->getRSSI();
}
float RadioLibWrapper::getLastSNR() const {
return _radio->getSNR();
}
2025-02-04 15:00:28 +11:00
// Approximate SNR threshold per SF for successful reception (based on Semtech datasheets)
static float snr_threshold[] = {
-7.5, // SF7 needs at least -7.5 dB SNR
-10, // SF8 needs at least -10 dB SNR
-12.5, // SF9 needs at least -12.5 dB SNR
-15, // SF10 needs at least -15 dB SNR
-17.5,// SF11 needs at least -17.5 dB SNR
-20 // SF12 needs at least -20 dB SNR
};
float RadioLibWrapper::packetScoreInt(float snr, int sf, int packet_len) {
if (sf < 7) return 0.0f;
if (snr < snr_threshold[sf - 7]) return 0.0f; // Below threshold, no chance of success
auto success_rate_based_on_snr = (snr - snr_threshold[sf - 7]) / 10.0;
auto collision_penalty = 1 - (packet_len / 256.0); // Assuming max packet of 256 bytes
return max(0.0, min(1.0, success_rate_based_on_snr * collision_penalty));
}