#define RADIOLIB_STATIC_ONLY 1 #include "RadioLibWrappers.h" #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 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 _preamble_sf = getSpreadingFactor(); _radio->setPreambleLength(preambleLengthForSF(_preamble_sf)); // longer preamble for lower SF improves reliability state = STATE_IDLE; if (_board->getStartupReason() == BD_STARTUP_RX_PACKET) { // received a LoRa packet (while in deep sleep) setFlag(); // LoRa packet is already received } _noise_floor = 0; _threshold = 0; // start average out some samples _num_floor_samples = 0; _floor_sample_sum = 0; } uint32_t RadioLibWrapper::getRngSeed() { return _radio->random(0x7FFFFFFF); } void RadioLibWrapper::setTxPower(int8_t dbm) { _tx_dbm = dbm; _radio->setOutputPower(dbm); } 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 _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() { // Power-save toggled vs the currently-armed RX mode: re-arm into the other mode // once the radio is idle (don't interrupt an in-flight TX or an unread RX-done). if (_power_save != _ps_active) { if (state != STATE_TX_WAIT && !(state & STATE_INT_READY) && !isReceivingPacket()) armRecv(); return; } // In power-save the SX126x hardware duty-cycles RX on its own — nothing to poll // here, and noise-floor sampling (used only by the disabled interference check) // would read a chip that is asleep most of the time. if (_power_save) return; if (state == STATE_RX && _num_floor_samples < NUM_NOISE_FLOOR_SAMPLES) { if (!isReceivingPacket()) { int rssi = getCurrentRSSI(); if (rssi < _noise_floor + SAMPLING_THRESHOLD) { // only consider samples below current floor + sampling THRESHOLD _num_floor_samples++; _floor_sample_sum += rssi; } } } 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() { armRecv(); } // Arm the receiver. In power-save mode this starts the SX126x hardware RX // duty-cycle (the chip cycles RX↔sleep and latches a preamble on its own); // otherwise a continuous RX. Falls back to continuous RX if the modem doesn't // support duty-cycle (base startPowerSaveRecv() returns UNSUPPORTED). void RadioLibWrapper::armRecv() { if (_power_save) { int16_t e = startPowerSaveRecv(); if (e == RADIOLIB_ERR_NONE) { state = STATE_RX; _ps_active = true; return; } MESH_DEBUG_PRINTLN("RadioLibWrapper: RX duty-cycle unsupported (%d) — power-save off", (int)e); _power_save = false; } _ps_active = false; 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; } int RadioLibWrapper::recvRaw(uint8_t* bytes, int sz) { int len = 0; if (state & STATE_INT_READY) { len = _radio->getPacketLength(); 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); len = 0; n_recv_errors++; } else { // Serial.print(" readData() -> "); Serial.println(len); n_recv++; } } state = STATE_IDLE; // need another startReceive() } if (state != STATE_RX) { armRecv(); // continuous RX, or re-arm the duty-cycle in power-save mode } return len; } uint32_t RadioLibWrapper::getEstAirtimeFor(int len_bytes) { return _radio->getTimeOnAir(len_bytes) / 1000; } bool RadioLibWrapper::startSendRaw(const uint8_t* bytes, int len) { _board->onBeforeTransmit(); int err = _radio->startTransmit((uint8_t *) bytes, len); if (err == RADIOLIB_ERR_NONE) { state = STATE_TX_WAIT; return true; } MESH_DEBUG_PRINTLN("RadioLibWrapper: error: startTransmit(%d)", err); idle(); // trigger another startRecv() _board->onAfterTransmit(); return false; } bool RadioLibWrapper::isSendComplete() { if (state & STATE_INT_READY) { state = STATE_IDLE; n_sent++; return true; } return false; } void RadioLibWrapper::onSendFinished() { _radio->finishTransmit(); _board->onAfterTransmit(); state = STATE_IDLE; } bool RadioLibWrapper::isChannelActive() { return _threshold == 0 ? false // interference check is disabled : getCurrentRSSI() > _noise_floor + _threshold; } float RadioLibWrapper::getLastRSSI() const { return _radio->getRSSI(); } float RadioLibWrapper::getLastSNR() const { return _radio->getSNR(); } float RadioLibWrapper::packetScoreInt(float snr, int sf, int packet_len) { if (sf < 7) return 0.0f; float floor = snrFloorForSF(sf); // min SNR for a chance of success if (snr < floor) return 0.0f; // below the demod floor → no chance auto success_rate_based_on_snr = (snr - floor) / 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)); }