-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Expand file tree
/
Copy pathRadioLibWrappers.cpp
More file actions
295 lines (254 loc) · 9.43 KB
/
Copy pathRadioLibWrappers.cpp
File metadata and controls
295 lines (254 loc) · 9.43 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
#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
// ambient CAD auto-calibration: probe CAD periodically while idle, and raise/lower detPeak
// so ambient activity (sub-decode-threshold distant traffic, interference) stops tripping LBT
#define CAD_PROBE_INTERVAL 4000 // millis between ambient CAD probes
#define CAD_PROBE_WINDOW 32 // probes per evaluation window
#define CAD_PROBE_RAISE_HITS 10 // >= ~30% ambient detect rate -> desensitize
#define CAD_PROBE_LOWER_HITS 3 // <= ~10% ambient detect rate -> re-sensitize
#define CAD_PEAK_OFFSET_MAX 10
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;
_cad_enabled = false;
_cad_peak_offset = 0;
_cad_probe_count = _cad_probe_hits = 0;
_cad_last_count = _cad_last_hits = 0;
_last_cad_probe = 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) {
_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() {
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);
}
if (_cad_enabled && state == STATE_RX && getCADDetPeakBase() > 0
&& millis() - _last_cad_probe >= CAD_PROBE_INTERVAL) {
_last_cad_probe = millis();
if (!isReceivingPacket()) {
int16_t result = performChannelScan();
// CAD-done DIO interrupt sets STATE_INT_READY via setFlag() ISR. Clear it
// before restarting RX so recvRaw() doesn't try to read a non-existent packet.
state = STATE_IDLE;
startRecv();
if (result == RADIOLIB_CHANNEL_FREE) {
_cad_probe_count++;
} else if (result == RADIOLIB_LORA_DETECTED || result == RADIOLIB_PREAMBLE_DETECTED) {
_cad_probe_count++;
_cad_probe_hits++;
}
// else: scan error, don't count the sample
if (_cad_probe_hits >= CAD_PROBE_RAISE_HITS) { // ambient rate too high, don't wait out the window
if (_cad_peak_offset < CAD_PEAK_OFFSET_MAX) {
_cad_peak_offset++;
MESH_DEBUG_PRINTLN("RadioLibWrapper: CAD ambient %d/%d, detPeak offset -> %d",
(int)_cad_probe_hits, (int)_cad_probe_count, (int)_cad_peak_offset);
}
_cad_last_hits = _cad_probe_hits; _cad_last_count = _cad_probe_count;
_cad_probe_count = _cad_probe_hits = 0;
} else if (_cad_probe_count >= CAD_PROBE_WINDOW) {
if (_cad_probe_hits <= CAD_PROBE_LOWER_HITS && _cad_peak_offset > 0) {
_cad_peak_offset--;
MESH_DEBUG_PRINTLN("RadioLibWrapper: CAD ambient %d/%d, detPeak offset -> %d",
(int)_cad_probe_hits, (int)_cad_probe_count, (int)_cad_peak_offset);
}
_cad_last_hits = _cad_probe_hits; _cad_last_count = _cad_probe_count;
_cad_probe_count = _cad_probe_hits = 0;
}
}
}
}
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;
}
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) {
int err = _radio->startReceive();
if (err == RADIOLIB_ERR_NONE) {
state = STATE_RX;
} else {
MESH_DEBUG_PRINTLN("RadioLibWrapper: error: startReceive(%d)", err);
}
}
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;
}
int16_t RadioLibWrapper::performChannelScan() {
uint8_t peak_base = getCADDetPeakBase();
if (_cad_peak_offset > 0 && peak_base > 0) {
ChannelScanConfig_t cfg = {
.cad = {
.symNum = 0xFF, // 0xFF -> radio-specific default (RADIOLIB_*_CAD_PARAM_DEFAULT)
.detPeak = (uint8_t)(peak_base + _cad_peak_offset),
.detMin = 0xFF,
.exitMode = 0xFF,
.timeout = 0,
.irqFlags = RADIOLIB_IRQ_CAD_DEFAULT_FLAGS,
.irqMask = RADIOLIB_IRQ_CAD_DEFAULT_MASK,
},
};
return _radio->scanChannel(cfg);
}
return _radio->scanChannel();
}
bool RadioLibWrapper::isChannelActive() {
// int.thresh: RSSI-based interference detection (relative to noise floor)
if (_threshold != 0 && getCurrentRSSI() > _noise_floor + _threshold) return true;
// cad: hardware channel activity detection
if (_cad_enabled) {
int16_t result = performChannelScan();
// scanChannel() triggers DIO interrupt (CAD done) which sets STATE_INT_READY
// via setFlag() ISR. Clear it before restarting RX so recvRaw() doesn't
// try to read a non-existent packet and count a spurious recv error.
state = STATE_IDLE;
startRecv();
if (result != RADIOLIB_CHANNEL_FREE) return true;
}
return false;
}
float RadioLibWrapper::getLastRSSI() const {
return _radio->getRSSI();
}
float RadioLibWrapper::getLastSNR() const {
return _radio->getSNR();
}
// 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));
}