diff --git a/examples/simple_repeater/main.cpp b/examples/simple_repeater/main.cpp index 12c843b7..5db62ff1 100644 --- a/examples/simple_repeater/main.cpp +++ b/examples/simple_repeater/main.cpp @@ -79,7 +79,7 @@ struct RepeaterStats { uint16_t batt_milli_volts; uint16_t curr_tx_queue_len; - uint16_t curr_free_queue_len; + int16_t noise_floor; int16_t last_rssi; uint32_t n_packets_recv; uint32_t n_packets_sent; @@ -183,7 +183,7 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { RepeaterStats stats; stats.batt_milli_volts = board.getBattMilliVolts(); stats.curr_tx_queue_len = _mgr->getOutboundCount(0xFFFFFFFF); - stats.curr_free_queue_len = _mgr->getFreeCount(); + stats.noise_floor = (int16_t)_radio->getNoiseFloor(); stats.last_rssi = (int16_t) radio_driver.getLastRSSI(); stats.n_packets_recv = radio_driver.getPacketsRecv(); stats.n_packets_sent = radio_driver.getPacketsSent(); @@ -327,6 +327,9 @@ protected: uint32_t t = (_radio->getEstAirtimeFor(packet->path_len + packet->payload_len + 2) * _prefs.direct_tx_delay_factor); return getRNG()->nextInt(0, 6)*t; } + int getInterferenceThreshold() const override { + return _prefs.interference_threshold; + } void onAnonDataRecv(mesh::Packet* packet, uint8_t type, const mesh::Identity& sender, uint8_t* data, size_t len) override { if (type == PAYLOAD_TYPE_ANON_REQ) { // received an initial request by a possible admin client (unknown at this stage) @@ -565,6 +568,7 @@ public: _prefs.advert_interval = 1; // default to 2 minutes for NEW installs _prefs.flood_advert_interval = 3; // 3 hours _prefs.flood_max = 64; + _prefs.interference_threshold = 14; // DB } CommonCLI* getCLI() { return &_cli; } diff --git a/examples/simple_room_server/main.cpp b/examples/simple_room_server/main.cpp index dad7ce78..d46270ac 100644 --- a/examples/simple_room_server/main.cpp +++ b/examples/simple_room_server/main.cpp @@ -406,6 +406,9 @@ protected: uint32_t t = (_radio->getEstAirtimeFor(packet->path_len + packet->payload_len + 2) * _prefs.direct_tx_delay_factor); return getRNG()->nextInt(0, 6)*t; } + int getInterferenceThreshold() const override { + return _prefs.interference_threshold; + } bool allowPacketForward(const mesh::Packet* packet) override { if (_prefs.disable_fwd) return false; @@ -711,6 +714,7 @@ public: _prefs.advert_interval = 1; // default to 2 minutes for NEW installs _prefs.flood_advert_interval = 3; // 3 hours _prefs.flood_max = 64; + _prefs.interference_threshold = 14; // DB #ifdef ROOM_PASSWORD StrHelper::strncpy(_prefs.guest_password, ROOM_PASSWORD, sizeof(_prefs.guest_password)); #endif diff --git a/src/Dispatcher.cpp b/src/Dispatcher.cpp index 3d5b04fc..7ac5cbe3 100644 --- a/src/Dispatcher.cpp +++ b/src/Dispatcher.cpp @@ -10,6 +10,10 @@ namespace mesh { #define MAX_RX_DELAY_MILLIS 32000 // 32 seconds +#ifndef NOISE_FLOOR_CALIB_INTERVAL + #define NOISE_FLOOR_CALIB_INTERVAL 2000 // 2 seconds +#endif + void Dispatcher::begin() { n_sent_flood = n_sent_direct = 0; n_recv_flood = n_recv_direct = 0; @@ -36,6 +40,12 @@ uint32_t Dispatcher::getCADFailMaxDuration() const { } void Dispatcher::loop() { + if (millisHasNowPassed(next_floor_calib_time)) { + _radio->triggerNoiseFloorCalibrate(getInterferenceThreshold()); + next_floor_calib_time = futureMillis(NOISE_FLOOR_CALIB_INTERVAL); + } + _radio->loop(); + // check for radio 'stuck' in mode other than Rx bool is_recv = _radio->isInRecvMode(); if (is_recv != prev_isrecv_mode) { diff --git a/src/Dispatcher.h b/src/Dispatcher.h index d03c9f73..bce13b6b 100644 --- a/src/Dispatcher.h +++ b/src/Dispatcher.h @@ -56,6 +56,15 @@ public: */ virtual void onSendFinished() = 0; + /** + * \brief do any processing needed on each loop cycle + */ + virtual void loop() { } + + virtual int getNoiseFloor() const { return 0; } + + virtual void triggerNoiseFloorCalibrate(int threshold) { } + virtual bool isInRecvMode() const = 0; /** @@ -107,6 +116,7 @@ class Dispatcher { unsigned long next_tx_time; unsigned long cad_busy_start; unsigned long radio_nonrx_start; + unsigned long next_floor_calib_time; bool prev_isrecv_mode; uint32_t n_sent_flood, n_sent_direct; uint32_t n_recv_flood, n_recv_direct; @@ -124,6 +134,7 @@ protected: { outbound = NULL; total_air_time = 0; next_tx_time = 0; cad_busy_start = 0; + next_floor_calib_time = 0; _err_flags = 0; radio_nonrx_start = 0; prev_isrecv_mode = true; @@ -142,6 +153,7 @@ protected: virtual int calcRxDelay(float score, uint32_t air_time) const; virtual uint32_t getCADFailRetryDelay() const; virtual uint32_t getCADFailMaxDuration() const; + virtual int getInterferenceThreshold() const { return 0; } // disabled by default public: void begin(); diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index 8b8296f5..baad8f40 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -56,6 +56,7 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) { file.read(pad, 4); // 120 file.read((uint8_t *) &_prefs->flood_max, sizeof(_prefs->flood_max)); // 124 file.read((uint8_t *) &_prefs->flood_advert_interval, sizeof(_prefs->flood_advert_interval)); // 125 + file.read((uint8_t *) &_prefs->interference_threshold, sizeof(_prefs->interference_threshold)); // 126 // sanitise bad pref values _prefs->rx_delay_base = constrain(_prefs->rx_delay_base, 0, 20.0f); @@ -109,6 +110,7 @@ void CommonCLI::savePrefs(FILESYSTEM* fs) { file.write(pad, 4); // 120 file.write((uint8_t *) &_prefs->flood_max, sizeof(_prefs->flood_max)); // 124 file.write((uint8_t *) &_prefs->flood_advert_interval, sizeof(_prefs->flood_advert_interval)); // 125 + file.write((uint8_t *) &_prefs->interference_threshold, sizeof(_prefs->interference_threshold)); // 126 file.close(); } @@ -176,6 +178,8 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch const char* config = &command[4]; if (memcmp(config, "af", 2) == 0) { sprintf(reply, "> %s", StrHelper::ftoa(_prefs->airtime_factor)); + } else if (memcmp(config, "int.thresh", 10) == 0) { + sprintf(reply, "> %d", (uint32_t) _prefs->interference_threshold); } else if (memcmp(config, "allow.read.only", 15) == 0) { sprintf(reply, "> %s", _prefs->allow_read_only ? "on" : "off"); } else if (memcmp(config, "flood.advert.interval", 21) == 0) { @@ -223,6 +227,10 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch _prefs->airtime_factor = atof(&config[3]); savePrefs(); strcpy(reply, "OK"); + } else if (memcmp(config, "int.thresh ", 11) == 0) { + _prefs->interference_threshold = atoi(&config[11]); + savePrefs(); + strcpy(reply, "OK"); } else if (memcmp(config, "allow.read.only ", 16) == 0) { _prefs->allow_read_only = memcmp(&config[16], "on", 2) == 0; savePrefs(); diff --git a/src/helpers/CommonCLI.h b/src/helpers/CommonCLI.h index 0e88c266..37402c09 100644 --- a/src/helpers/CommonCLI.h +++ b/src/helpers/CommonCLI.h @@ -24,6 +24,7 @@ struct NodePrefs { // persisted to file uint8_t reserved2; float bw; uint8_t flood_max; + uint8_t interference_threshold; }; class CommonCLICallbacks { diff --git a/src/helpers/CustomLLCC68Wrapper.h b/src/helpers/CustomLLCC68Wrapper.h index c7d95c41..f7dd7a9f 100644 --- a/src/helpers/CustomLLCC68Wrapper.h +++ b/src/helpers/CustomLLCC68Wrapper.h @@ -6,18 +6,11 @@ class CustomLLCC68Wrapper : public RadioLibWrapper { public: CustomLLCC68Wrapper(CustomLLCC68& radio, mesh::MainBoard& board) : RadioLibWrapper(radio, board) { } - bool isReceiving() override { - if (((CustomLLCC68 *)_radio)->isReceiving()) return true; - - idle(); // put sx126x into standby - // do some basic CAD (blocks for ~12780 micros (on SF 10)!) - bool activity = (((CustomLLCC68 *)_radio)->scanChannel() == RADIOLIB_LORA_DETECTED); - if (activity) { - startRecv(); - } else { - idle(); - } - return activity; + bool isReceivingPacket() override { + return ((CustomLLCC68 *)_radio)->isReceiving(); + } + float getCurrentRSSI() override { + return ((CustomLLCC68 *)_radio)->getRSSI(false); } float getLastRSSI() const override { return ((CustomLLCC68 *)_radio)->getRSSI(); } float getLastSNR() const override { return ((CustomLLCC68 *)_radio)->getSNR(); } diff --git a/src/helpers/CustomLR1110Wrapper.h b/src/helpers/CustomLR1110Wrapper.h index 3a96d3c2..7e2ffa2d 100644 --- a/src/helpers/CustomLR1110Wrapper.h +++ b/src/helpers/CustomLR1110Wrapper.h @@ -6,18 +6,13 @@ class CustomLR1110Wrapper : public RadioLibWrapper { public: CustomLR1110Wrapper(CustomLR1110& radio, mesh::MainBoard& board) : RadioLibWrapper(radio, board) { } - bool isReceiving() override { - if (((CustomLR1110 *)_radio)->isReceiving()) return true; - - idle(); // put sx126x into standby - // do some basic CAD (blocks for ~12780 micros (on SF 10)!) - bool activity = (((CustomLR1110 *)_radio)->scanChannel() == RADIOLIB_LORA_DETECTED); - if (activity) { - startRecv(); - } else { - idle(); - } - return activity; + bool isReceivingPacket() override { + return ((CustomLR1110 *)_radio)->isReceiving(); + } + float getCurrentRSSI() override { + float rssi = -110; + ((CustomLR1110 *)_radio)->getRssiInst(&rssi); + return rssi; } void onSendFinished() override { diff --git a/src/helpers/CustomSTM32WLxWrapper.h b/src/helpers/CustomSTM32WLxWrapper.h index 84f78376..9e2d0441 100644 --- a/src/helpers/CustomSTM32WLxWrapper.h +++ b/src/helpers/CustomSTM32WLxWrapper.h @@ -7,18 +7,11 @@ class CustomSTM32WLxWrapper : public RadioLibWrapper { public: CustomSTM32WLxWrapper(CustomSTM32WLx& radio, mesh::MainBoard& board) : RadioLibWrapper(radio, board) { } - bool isReceiving() override { - if (((CustomSTM32WLx *)_radio)->isReceiving()) return true; - - idle(); // put sx126x into standby - // do some basic CAD (blocks for ~12780 micros (on SF 10)!) - bool activity = (((CustomSTM32WLx *)_radio)->scanChannel() == RADIOLIB_LORA_DETECTED); - if (activity) { - startRecv(); - } else { - idle(); - } - return activity; + bool isReceivingPacket() override { + return ((CustomSTM32WLx *)_radio)->isReceiving(); + } + float getCurrentRSSI() override { + return ((CustomSTM32WLx *)_radio)->getRSSI(false); } float getLastRSSI() const override { return ((CustomSTM32WLx *)_radio)->getRSSI(); } float getLastSNR() const override { return ((CustomSTM32WLx *)_radio)->getSNR(); } diff --git a/src/helpers/CustomSX1262Wrapper.h b/src/helpers/CustomSX1262Wrapper.h index ea2da5fe..119f6dce 100644 --- a/src/helpers/CustomSX1262Wrapper.h +++ b/src/helpers/CustomSX1262Wrapper.h @@ -2,23 +2,15 @@ #include "CustomSX1262.h" #include "RadioLibWrappers.h" -#include class CustomSX1262Wrapper : public RadioLibWrapper { public: CustomSX1262Wrapper(CustomSX1262& radio, mesh::MainBoard& board) : RadioLibWrapper(radio, board) { } - bool isReceiving() override { - if (((CustomSX1262 *)_radio)->isReceiving()) return true; - - idle(); // put sx126x into standby - // do some basic CAD (blocks for ~12780 micros (on SF 10)!) - bool activity = (((CustomSX1262 *)_radio)->scanChannel() == RADIOLIB_LORA_DETECTED); - if (activity) { - startRecv(); - } else { - idle(); - } - return activity; + bool isReceivingPacket() override { + return ((CustomSX1262 *)_radio)->isReceiving(); + } + float getCurrentRSSI() override { + return ((CustomSX1262 *)_radio)->getRSSI(false); } float getLastRSSI() const override { return ((CustomSX1262 *)_radio)->getRSSI(); } float getLastSNR() const override { return ((CustomSX1262 *)_radio)->getSNR(); } diff --git a/src/helpers/CustomSX1268Wrapper.h b/src/helpers/CustomSX1268Wrapper.h index f9eee447..5d7106b4 100644 --- a/src/helpers/CustomSX1268Wrapper.h +++ b/src/helpers/CustomSX1268Wrapper.h @@ -6,18 +6,11 @@ class CustomSX1268Wrapper : public RadioLibWrapper { public: CustomSX1268Wrapper(CustomSX1268& radio, mesh::MainBoard& board) : RadioLibWrapper(radio, board) { } - bool isReceiving() override { - if (((CustomSX1268 *)_radio)->isReceiving()) return true; - - idle(); // put sx126x into standby - // do some basic CAD (blocks for ~12780 micros (on SF 10)!) - bool activity = (((CustomSX1268 *)_radio)->scanChannel() == RADIOLIB_LORA_DETECTED); - if (activity) { - startRecv(); - } else { - idle(); - } - return activity; + bool isReceivingPacket() override { + return ((CustomSX1268 *)_radio)->isReceiving(); + } + float getCurrentRSSI() override { + return ((CustomSX1268 *)_radio)->getRSSI(false); } float getLastRSSI() const override { return ((CustomSX1268 *)_radio)->getRSSI(); } float getLastSNR() const override { return ((CustomSX1268 *)_radio)->getSNR(); } diff --git a/src/helpers/CustomSX1276Wrapper.h b/src/helpers/CustomSX1276Wrapper.h index f9900705..28257990 100644 --- a/src/helpers/CustomSX1276Wrapper.h +++ b/src/helpers/CustomSX1276Wrapper.h @@ -6,18 +6,11 @@ class CustomSX1276Wrapper : public RadioLibWrapper { public: CustomSX1276Wrapper(CustomSX1276& radio, mesh::MainBoard& board) : RadioLibWrapper(radio, board) { } - bool isReceiving() override { - if (((CustomSX1276 *)_radio)->isReceiving()) return true; - - idle(); // put into standby - // do some basic CAD (blocks for ~12780 micros (on SF 10)!) - bool activity = (((CustomSX1276 *)_radio)->tryScanChannel() == RADIOLIB_PREAMBLE_DETECTED); - if (activity) { - startRecv(); - } else { - idle(); - } - return activity; + bool isReceivingPacket() override { + return ((CustomSX1276 *)_radio)->isReceiving(); + } + float getCurrentRSSI() override { + return ((CustomSX1276 *)_radio)->getRSSI(false); } float getLastRSSI() const override { return ((CustomSX1276 *)_radio)->getRSSI(); } float getLastSNR() const override { return ((CustomSX1276 *)_radio)->getSNR(); } diff --git a/src/helpers/RadioLibWrappers.cpp b/src/helpers/RadioLibWrappers.cpp index 39fb340e..d37bc498 100644 --- a/src/helpers/RadioLibWrappers.cpp +++ b/src/helpers/RadioLibWrappers.cpp @@ -8,6 +8,8 @@ #define STATE_TX_DONE 4 #define STATE_INT_READY 16 +#define NUM_NOISE_FLOOR_SAMPLES 64 + static volatile uint8_t state = STATE_IDLE; // this function is called when a complete packet @@ -28,6 +30,13 @@ void RadioLibWrapper::begin() { 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; } void RadioLibWrapper::idle() { @@ -35,6 +44,31 @@ void RadioLibWrapper::idle() { state = STATE_IDLE; // need another startReceive() } +void RadioLibWrapper::triggerNoiseFloorCalibrate(int threshold) { + _threshold = threshold; + if (threshold > 0 && _num_floor_samples >= NUM_NOISE_FLOOR_SAMPLES) { // ignore trigger if currently sampling + _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 + _threshold) { // only consider samples below current floor+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; + _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) { @@ -108,6 +142,12 @@ void RadioLibWrapper::onSendFinished() { 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(); } diff --git a/src/helpers/RadioLibWrappers.h b/src/helpers/RadioLibWrappers.h index bdbadb19..bb308071 100644 --- a/src/helpers/RadioLibWrappers.h +++ b/src/helpers/RadioLibWrappers.h @@ -8,10 +8,14 @@ protected: PhysicalLayer* _radio; mesh::MainBoard* _board; uint32_t n_recv, n_sent; + int16_t _noise_floor, _threshold; + uint16_t _num_floor_samples; + int32_t _floor_sample_sum; void idle(); void startRecv(); float packetScoreInt(float snr, int sf, int packet_len); + virtual bool isReceivingPacket() =0; public: RadioLibWrapper(PhysicalLayer& radio, mesh::MainBoard& board) : _radio(&radio), _board(&board) { n_recv = n_sent = 0; } @@ -23,6 +27,20 @@ public: bool isSendComplete() override; void onSendFinished() override; bool isInRecvMode() const override; + bool isChannelActive(); + + bool isReceiving() override { + if (isReceivingPacket()) return true; + + return isChannelActive(); + } + + virtual float getCurrentRSSI() =0; + + int getNoiseFloor() const override { return _noise_floor; } + void triggerNoiseFloorCalibrate(int threshold) override; + + void loop() override; uint32_t getPacketsRecv() const { return n_recv; } uint32_t getPacketsSent() const { return n_sent; }