2025-01-13 14:07:48 +11:00
|
|
|
#pragma once
|
|
|
|
|
|
|
|
|
|
#include <Mesh.h>
|
|
|
|
|
#include <RadioLib.h>
|
|
|
|
|
|
|
|
|
|
class RadioLibWrapper : public mesh::Radio {
|
|
|
|
|
protected:
|
|
|
|
|
PhysicalLayer* _radio;
|
|
|
|
|
mesh::MainBoard* _board;
|
2026-01-24 20:06:29 -08:00
|
|
|
uint32_t n_recv, n_sent, n_recv_errors;
|
2025-05-26 17:18:49 +10:00
|
|
|
int16_t _noise_floor, _threshold;
|
2025-05-24 21:24:44 +10:00
|
|
|
uint16_t _num_floor_samples;
|
|
|
|
|
int32_t _floor_sample_sum;
|
2026-03-07 12:48:22 +11:00
|
|
|
uint8_t _preamble_sf;
|
2025-01-13 14:07:48 +11:00
|
|
|
|
2025-01-21 13:37:32 +11:00
|
|
|
void idle();
|
2025-02-27 04:05:50 +11:00
|
|
|
void startRecv();
|
2025-02-04 15:00:28 +11:00
|
|
|
float packetScoreInt(float snr, int sf, int packet_len);
|
2025-05-24 20:42:00 +10:00
|
|
|
virtual bool isReceivingPacket() =0;
|
2026-02-19 16:16:21 +01:00
|
|
|
virtual void doResetAGC();
|
2025-01-21 13:37:32 +11:00
|
|
|
|
2026-06-10 23:30:43 +02:00
|
|
|
// Power-save RX: hardware SX126x RX duty-cycle (SetRxDutyCycle). Instead of a
|
|
|
|
|
// continuous receive the chip itself cycles RX↔sleep, latches a preamble, then
|
|
|
|
|
// stays in RX to receive the packet (RX_DONE on DIO1) — no MCU state machine,
|
|
|
|
|
// average RX current cut several-fold. Driven from armRecv()/loop(); falls back
|
|
|
|
|
// to continuous RX if the modem doesn't support it.
|
|
|
|
|
bool _power_save = false;
|
|
|
|
|
bool _ps_active = false; // is the radio currently armed in duty-cycle mode
|
|
|
|
|
int8_t _tx_dbm = 0; // last TX power applied (tracks APC's live value)
|
|
|
|
|
void armRecv(); // arm RX: duty-cycle in power-save, else continuous
|
|
|
|
|
// Arm the hardware RX duty-cycle. Base returns UNSUPPORTED → armRecv() falls
|
|
|
|
|
// back to continuous RX; SX126x overrides with startReceiveDutyCycleAuto().
|
|
|
|
|
virtual int16_t startPowerSaveRecv() { return RADIOLIB_ERR_UNSUPPORTED; }
|
|
|
|
|
|
2025-01-13 14:07:48 +11:00
|
|
|
public:
|
2026-03-07 12:48:22 +11:00
|
|
|
RadioLibWrapper(PhysicalLayer& radio, mesh::MainBoard& board) : _radio(&radio), _board(&board), _preamble_sf(0) { n_recv = n_sent = 0; }
|
2025-01-13 14:07:48 +11:00
|
|
|
|
|
|
|
|
void begin() override;
|
2026-06-10 23:30:43 +02:00
|
|
|
// Enable/disable hardware duty-cycle RX. Takes effect on the next RX re-arm
|
|
|
|
|
// (loop() re-arms once the live mode differs from this request).
|
|
|
|
|
void setPowerSaving(bool en) { _power_save = en; }
|
|
|
|
|
bool getPowerSaving() const { return _power_save; }
|
2025-11-22 02:06:44 +01:00
|
|
|
virtual void powerOff() { _radio->sleep(); }
|
2025-01-13 14:07:48 +11:00
|
|
|
int recvRaw(uint8_t* bytes, int sz) override;
|
|
|
|
|
uint32_t getEstAirtimeFor(int len_bytes) override;
|
2025-05-12 17:26:44 +10:00
|
|
|
bool startSendRaw(const uint8_t* bytes, int len) override;
|
2025-01-13 14:07:48 +11:00
|
|
|
bool isSendComplete() override;
|
|
|
|
|
void onSendFinished() override;
|
2025-05-13 15:38:10 +10:00
|
|
|
bool isInRecvMode() const override;
|
2025-05-24 20:42:00 +10:00
|
|
|
bool isChannelActive();
|
|
|
|
|
|
2026-06-10 23:30:43 +02:00
|
|
|
bool isReceiving() override {
|
2025-05-24 20:42:00 +10:00
|
|
|
if (isReceivingPacket()) return true;
|
|
|
|
|
|
|
|
|
|
return isChannelActive();
|
|
|
|
|
}
|
2025-01-13 14:07:48 +11:00
|
|
|
|
2026-05-01 14:47:07 +10:00
|
|
|
virtual void setParams(float freq, float bw, uint8_t sf, uint8_t cr) = 0;
|
2026-06-20 09:07:00 +02:00
|
|
|
// RadioLib's own setFrequency() silently rejects values outside the chip's
|
|
|
|
|
// validated range and leaves the radio retuned to its previous frequency —
|
|
|
|
|
// setParams() above doesn't check that return code, so the UI clamps to this
|
|
|
|
|
// instead of letting NodePrefs drift out of sync with the actual radio.
|
|
|
|
|
// Default is the generic sanity bound the app's CMD_SET_RADIO_PARAMS already
|
|
|
|
|
// uses; chips with a narrower RadioLib-validated range override it.
|
|
|
|
|
virtual void getFreqBounds(float& min_mhz, float& max_mhz) const { min_mhz = 150.0f; max_mhz = 2500.0f; }
|
2026-05-01 14:47:07 +10:00
|
|
|
uint32_t getRngSeed();
|
|
|
|
|
void setTxPower(int8_t dbm);
|
2026-06-10 23:30:43 +02:00
|
|
|
int8_t getTxPower() const { return _tx_dbm; } // actual current power (reflects APC)
|
2026-05-01 14:47:07 +10:00
|
|
|
|
2025-05-25 21:44:15 +10:00
|
|
|
virtual float getCurrentRSSI() =0;
|
2026-03-05 22:14:22 +11:00
|
|
|
virtual uint8_t getSpreadingFactor() const { return LORA_SF; }
|
2026-03-08 05:37:49 +11:00
|
|
|
static uint16_t preambleLengthForSF(uint8_t sf) { return sf <= 8 ? 32 : 16; }
|
2026-06-10 23:30:43 +02:00
|
|
|
// Approx SNR demod floor per SF (Semtech): SF7 -7.5 dB … SF12 -20 dB, -2.5 dB/SF.
|
|
|
|
|
// Single source for both packetScore() and the APC link-margin target.
|
|
|
|
|
static float snrFloorForSF(uint8_t sf) {
|
|
|
|
|
if (sf < 7) sf = 7; else if (sf > 12) sf = 12;
|
|
|
|
|
return -7.5f - 2.5f * (float)(sf - 7);
|
|
|
|
|
}
|
2026-03-29 21:45:16 +11:00
|
|
|
void updatePreamble(uint8_t sf) { _preamble_sf = sf; _radio->setPreambleLength(preambleLengthForSF(sf)); }
|
2025-05-25 21:44:15 +10:00
|
|
|
|
|
|
|
|
int getNoiseFloor() const override { return _noise_floor; }
|
2025-05-26 17:18:49 +10:00
|
|
|
void triggerNoiseFloorCalibrate(int threshold) override;
|
2025-06-13 14:15:21 +10:00
|
|
|
void resetAGC() override;
|
2025-05-25 21:44:15 +10:00
|
|
|
|
2025-05-24 21:24:44 +10:00
|
|
|
void loop() override;
|
|
|
|
|
|
2025-01-13 14:07:48 +11:00
|
|
|
uint32_t getPacketsRecv() const { return n_recv; }
|
2026-01-24 20:06:29 -08:00
|
|
|
uint32_t getPacketsRecvErrors() const { return n_recv_errors; }
|
2025-01-13 14:07:48 +11:00
|
|
|
uint32_t getPacketsSent() const { return n_sent; }
|
2026-01-24 20:06:29 -08:00
|
|
|
void resetStats() { n_recv = n_sent = n_recv_errors = 0; }
|
2025-05-13 18:12:58 +10:00
|
|
|
|
2025-01-18 21:45:47 +11:00
|
|
|
virtual float getLastRSSI() const override;
|
|
|
|
|
virtual float getLastSNR() const override;
|
2025-02-04 12:35:53 +11:00
|
|
|
|
2025-02-04 15:00:28 +11:00
|
|
|
float packetScore(float snr, int packet_len) override { return packetScoreInt(snr, 10, packet_len); } // assume sf=10
|
2026-03-05 18:31:00 +00:00
|
|
|
|
|
|
|
|
virtual void setRxBoostedGainMode(bool) { }
|
|
|
|
|
virtual bool getRxBoostedGainMode() const { return false; }
|
2025-01-13 14:07:48 +11:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* \brief an RNG impl using the noise from the LoRa radio as entropy.
|
|
|
|
|
* NOTE: this is VERY SLOW! Use only for things like creating new LocalIdentity
|
|
|
|
|
*/
|
|
|
|
|
class RadioNoiseListener : public mesh::RNG {
|
|
|
|
|
PhysicalLayer* _radio;
|
|
|
|
|
public:
|
|
|
|
|
RadioNoiseListener(PhysicalLayer& radio): _radio(&radio) { }
|
|
|
|
|
|
|
|
|
|
void random(uint8_t* dest, size_t sz) override {
|
|
|
|
|
for (int i = 0; i < sz; i++) {
|
|
|
|
|
dest[i] = _radio->randomByte() ^ (::random(0, 256) & 0xFF);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|