diff --git a/examples/companion_radio/UITask.cpp b/examples/companion_radio/UITask.cpp index f4e3fb0b..675972dc 100644 --- a/examples/companion_radio/UITask.cpp +++ b/examples/companion_radio/UITask.cpp @@ -266,7 +266,7 @@ void UITask::buttonHandler() { digitalWrite(PIN_STATUS_LED, LOW); delay(10); #endif - _board->powerOff(); + shutdown(); // without restart } } btn_state_change_time = millis(); @@ -278,6 +278,30 @@ void UITask::buttonHandler() { #endif } + +/* hardware-agnostic pre-shutdown activity should be done here +*/ +void UITask::shutdown(bool restart){ + + #ifdef PIN_BUZZER + /* note: we have a choice here - + we can do a blocking buzzer.loop() with non-deterministic consequences + or we can set a flag and delay the shutdown for a couple of seconds + while a non-blocking buzzer.loop() plays out in UITask::loop() + */ + buzzer.shutdown(); + uint32_t buzzer_timer = millis(); // fail-safe shutdown + while (buzzer.isPlaying() && (millis() - 2500) < buzzer_timer) + buzzer.loop(); + + #endif // PIN_BUZZER + + if (restart) + _board->reboot(); + else + _board->powerOff(); +} + void UITask::loop() { buttonHandler(); userLedHandler(); diff --git a/examples/companion_radio/UITask.h b/examples/companion_radio/UITask.h index 134b5a16..d774e54c 100644 --- a/examples/companion_radio/UITask.h +++ b/examples/companion_radio/UITask.h @@ -39,7 +39,6 @@ class UITask { void buttonHandler(); void userLedHandler(); void renderBatteryIndicator(uint16_t batteryMilliVolts); - public: @@ -55,5 +54,6 @@ public: void msgRead(int msgcount); void newMsg(uint8_t path_len, const char* from_name, const char* text, int msgcount); void soundBuzzer(UIEventType bet = UIEventType::none); + void shutdown(bool restart = false); void loop(); }; diff --git a/examples/companion_radio/main.cpp b/examples/companion_radio/main.cpp index 13db88c0..30a1c9cc 100644 --- a/examples/companion_radio/main.cpp +++ b/examples/companion_radio/main.cpp @@ -57,6 +57,7 @@ #define FLOOD_SEND_TIMEOUT_FACTOR 16.0f #define DIRECT_SEND_PERHOP_FACTOR 6.0f #define DIRECT_SEND_PERHOP_EXTRA_MILLIS 250 +#define LAZY_CONTACTS_WRITE_DELAY 5000 #define PUBLIC_GROUP_PSK "izOH6cXN6mrJ5e26oRXNcg==" @@ -198,6 +199,7 @@ class MyMesh : public BaseChatMesh { uint8_t app_target_ver; uint8_t* sign_data; uint32_t sign_data_len; + unsigned long dirty_contacts_expiry; uint8_t cmd_frame[MAX_FRAME_SIZE+1]; uint8_t out_frame[MAX_FRAME_SIZE+1]; CayenneLPP telemetry; @@ -488,6 +490,10 @@ protected: return _prefs.airtime_factor; } + int getInterferenceThreshold() const override { + return 14; // hard-coded for now + } + int calcRxDelay(float score, uint32_t air_time) const override { if (_prefs.rx_delay_base <= 0.0f) return 0; return (int) ((pow(_prefs.rx_delay_base, 0.85f - score) - 1.0) * air_time); @@ -524,7 +530,7 @@ protected: #endif } - saveContacts(); + dirty_contacts_expiry = futureMillis(LAZY_CONTACTS_WRITE_DELAY); } void onContactPathUpdated(const ContactInfo& contact) override { @@ -532,7 +538,7 @@ protected: memcpy(&out_frame[1], contact.id.pub_key, PUB_KEY_SIZE); _serial->writeFrame(out_frame, 1 + PUB_KEY_SIZE); // NOTE: app may not be connected - saveContacts(); + dirty_contacts_expiry = futureMillis(LAZY_CONTACTS_WRITE_DELAY); } bool processAck(const uint8_t *data) override { @@ -603,7 +609,8 @@ protected: void onSignedMessageRecv(const ContactInfo& from, mesh::Packet* pkt, uint32_t sender_timestamp, const uint8_t *sender_prefix, const char *text) override { markConnectionActive(from); - saveContacts(); // from.sync_since change needs to be persisted + // from.sync_since change needs to be persisted + dirty_contacts_expiry = futureMillis(LAZY_CONTACTS_WRITE_DELAY); queueMessage(from, TXT_TYPE_SIGNED_PLAIN, pkt, sender_timestamp, sender_prefix, 4, text); } @@ -695,6 +702,7 @@ protected: if (memcmp(&data[4], "OK", 2) == 0) { // legacy Repeater login OK response out_frame[i++] = PUSH_CODE_LOGIN_SUCCESS; out_frame[i++] = 0; // legacy: is_admin = false + memcpy(&out_frame[i], contact.id.pub_key, 6); i += 6; // pub_key_prefix } else if (data[4] == RESP_SERVER_LOGIN_OK) { // new login response uint16_t keep_alive_secs = ((uint16_t)data[5]) * 16; if (keep_alive_secs > 0) { @@ -702,11 +710,13 @@ protected: } out_frame[i++] = PUSH_CODE_LOGIN_SUCCESS; out_frame[i++] = data[6]; // permissions (eg. is_admin) + memcpy(&out_frame[i], contact.id.pub_key, 6); i += 6; // pub_key_prefix + memcpy(&out_frame[i], &tag, 4); i += 4; // NEW: include server timestamp } else { out_frame[i++] = PUSH_CODE_LOGIN_FAIL; out_frame[i++] = 0; // reserved + memcpy(&out_frame[i], contact.id.pub_key, 6); i += 6; // pub_key_prefix } - memcpy(&out_frame[i], contact.id.pub_key, 6); i += 6; // pub_key_prefix _serial->writeFrame(out_frame, i); } else if (len > 4 && // check for status response pending_status && memcmp(&pending_status, contact.id.pub_key, 4) == 0 // legacy matching scheme @@ -794,6 +804,7 @@ public: pending_login = pending_status = pending_telemetry = 0; next_ack_idx = 0; sign_data = NULL; + dirty_contacts_expiry = 0; // defaults memset(&_prefs, 0, sizeof(_prefs)); @@ -1145,7 +1156,7 @@ public: if (recipient) { recipient->out_path_len = -1; //recipient->lastmod = ?? shouldn't be needed, app already has this version of contact - saveContacts(); + dirty_contacts_expiry = futureMillis(LAZY_CONTACTS_WRITE_DELAY); writeOKFrame(); } else { writeErrFrame(ERR_CODE_NOT_FOUND); // unknown contact @@ -1156,7 +1167,7 @@ public: if (recipient) { updateContactFromFrame(*recipient, cmd_frame, len); //recipient->lastmod = ?? shouldn't be needed, app already has this version of contact - saveContacts(); + dirty_contacts_expiry = futureMillis(LAZY_CONTACTS_WRITE_DELAY); writeOKFrame(); } else { ContactInfo contact; @@ -1164,7 +1175,7 @@ public: contact.lastmod = getRTCClock()->getCurrentTime(); contact.sync_since = 0; if (addContact(contact)) { - saveContacts(); + dirty_contacts_expiry = futureMillis(LAZY_CONTACTS_WRITE_DELAY); writeOKFrame(); } else { writeErrFrame(ERR_CODE_TABLE_FULL); @@ -1174,7 +1185,7 @@ public: uint8_t* pub_key = &cmd_frame[1]; ContactInfo* recipient = lookupContactByPubKey(pub_key, PUB_KEY_SIZE); if (recipient && removeContact(*recipient)) { - saveContacts(); + dirty_contacts_expiry = futureMillis(LAZY_CONTACTS_WRITE_DELAY); writeOKFrame(); } else { writeErrFrame(ERR_CODE_NOT_FOUND); // not found, or unable to remove @@ -1293,6 +1304,9 @@ public: savePrefs(); writeOKFrame(); } else if (cmd_frame[0] == CMD_REBOOT && memcmp(&cmd_frame[1], "reboot", 6) == 0) { + if (dirty_contacts_expiry) { // is there are pending dirty contacts write needed? + saveContacts(); + } board.reboot(); } else if (cmd_frame[0] == CMD_GET_BATTERY_VOLTAGE) { uint8_t reply[3]; @@ -1563,6 +1577,12 @@ public: checkConnections(); } + // is there are pending dirty contacts write needed? + if (dirty_contacts_expiry && millisHasNowPassed(dirty_contacts_expiry)) { + saveContacts(); + dirty_contacts_expiry = 0; + } + #ifdef DISPLAY_CLASS ui_task.setHasConnection(_serial->isConnected()); ui_task.loop(); 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..5ba6cbca 100644 --- a/examples/simple_room_server/main.cpp +++ b/examples/simple_room_server/main.cpp @@ -126,7 +126,7 @@ struct PostInfo { struct ServerStats { 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; @@ -287,7 +287,7 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { ServerStats 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(); @@ -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; } diff --git a/src/helpers/TBeamBoard.h b/src/helpers/TBeamBoard.h index fc52e712..8eba6933 100644 --- a/src/helpers/TBeamBoard.h +++ b/src/helpers/TBeamBoard.h @@ -7,6 +7,9 @@ // Defined using AXP2102 #define XPOWERS_CHIP_AXP2101 +#define PIN_BOARD_SDA1 42 //SDA for PMU and PFC8563 (RTC) +#define PIN_BOARD_SCL1 41 //SCL for PMU and PFC8563 (RTC) +#define PIN_PMU_IRQ 40 //IRQ pin for PMU // LoRa radio module pins for TBeam #define P_LORA_DIO_0 26 @@ -28,15 +31,13 @@ #include class TBeamBoard : public ESP32Board { - XPowersAXP2101 power; - + XPowersLibInterface *PMU = NULL; public: + bool power_init(); + void printPMU(); + void begin() { ESP32Board::begin(); - - power.setALDO2Voltage(3300); - power.enableALDO2(); - pinMode(38, INPUT_PULLUP); esp_reset_reason_t reason = esp_reset_reason(); @@ -49,6 +50,7 @@ public: rtc_gpio_hold_dis((gpio_num_t)P_LORA_NSS); rtc_gpio_deinit((gpio_num_t)P_LORA_DIO_1); } + power_init(); } void enterDeepSleep(uint32_t secs, int pin_wake_btn = -1) { @@ -75,7 +77,8 @@ public: } uint16_t getBattMilliVolts() override { - return power.getBattVoltage(); + if(PMU) return PMU->getBattVoltage(); + else return 0; } const char* getManufacturerName() const override { diff --git a/src/helpers/TBeamS3SupremeBoard.h b/src/helpers/TBeamS3SupremeBoard.h index ccb8e24c..47160787 100644 --- a/src/helpers/TBeamS3SupremeBoard.h +++ b/src/helpers/TBeamS3SupremeBoard.h @@ -75,6 +75,7 @@ public: rtc_gpio_hold_dis((gpio_num_t)P_LORA_NSS); rtc_gpio_deinit((gpio_num_t)P_LORA_DIO_1); } + power_init(); } void enterDeepSleep(uint32_t secs, int pin_wake_btn = -1) { diff --git a/variants/lilygo_tbeam/target.cpp b/variants/lilygo_tbeam/target.cpp index 116088d6..69a980fc 100644 --- a/variants/lilygo_tbeam/target.cpp +++ b/variants/lilygo_tbeam/target.cpp @@ -3,6 +3,14 @@ TBeamBoard board; +// Using PMU AXP2102 +#define PMU_WIRE_PORT Wire + +bool pmuIntFlag = false; +static void setPMUIntFlag(){ + pmuIntFlag = true; +} + #if defined(P_LORA_SCLK) static SPIClass spi; RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_0, P_LORA_RESET, P_LORA_DIO_1, spi); @@ -24,6 +32,112 @@ SensorManager sensors; #define LORA_CR 5 #endif +bool TBeamBoard::power_init() +{ + if (!PMU) + { + PMU = new XPowersAXP2101(PMU_WIRE_PORT); + if (!PMU->init()) + { + // Serial.println("Warning: Failed to find AXP2101 power management"); + delete PMU; + PMU = NULL; + } + else + { + // Serial.println("AXP2101 PMU init succeeded, using AXP2101 PMU"); + } + } + if (!PMU) + { + PMU = new XPowersAXP192(PMU_WIRE_PORT); + if (!PMU->init()) + { + // Serial.println("Warning: Failed to find AXP192 power management"); + delete PMU; + PMU = NULL; + } + else + { + // Serial.println("AXP192 PMU init succeeded, using AXP192 PMU"); + } + } + + if (!PMU) + { + MESH_DEBUG_PRINTLN("PMU init failed."); + return false; + } + + // Serial.printf("PMU ID:0x%x\n", PMU->getChipID()); + // printPMU(); + if (PMU->getChipModel() == XPOWERS_AXP192) + { + // lora radio power channel + PMU->setPowerChannelVoltage(XPOWERS_LDO2, 3300); + PMU->enablePowerOutput(XPOWERS_LDO2); + // oled module power channel, + // disable it will cause abnormal communication between boot and AXP power supply, + // do not turn it off + PMU->setPowerChannelVoltage(XPOWERS_DCDC1, 3300); + // enable oled power + PMU->enablePowerOutput(XPOWERS_DCDC1); + // gnss module power channel + PMU->setPowerChannelVoltage(XPOWERS_LDO3, 3300); + // power->enablePowerOutput(XPOWERS_LDO3); + // protected oled power source + PMU->setProtectedChannel(XPOWERS_DCDC1); + // protected esp32 power source + PMU->setProtectedChannel(XPOWERS_DCDC3); + // disable not use channel + PMU->disablePowerOutput(XPOWERS_DCDC2); + // disable all axp chip interrupt + PMU->disableIRQ(XPOWERS_AXP192_ALL_IRQ); + PMU->setChargerConstantCurr(XPOWERS_AXP192_CHG_CUR_550MA); + } + else if (PMU->getChipModel() == XPOWERS_AXP2101) + { + // gnss module power channel + PMU->setPowerChannelVoltage(XPOWERS_ALDO4, 3300); + PMU->enablePowerOutput(XPOWERS_ALDO4); + // lora radio power channel + PMU->setPowerChannelVoltage(XPOWERS_ALDO3, 3300); + PMU->enablePowerOutput(XPOWERS_ALDO3); + // m.2 interface + PMU->setPowerChannelVoltage(XPOWERS_DCDC3, 3300); + PMU->enablePowerOutput(XPOWERS_DCDC3); + // power->setPowerChannelVoltage(XPOWERS_DCDC4, 3300); + // power->enablePowerOutput(XPOWERS_DCDC4); + // not use channel + PMU->disablePowerOutput(XPOWERS_DCDC2); // not elicited + PMU->disablePowerOutput(XPOWERS_DCDC5); // not elicited + PMU->disablePowerOutput(XPOWERS_DLDO1); // Invalid power channel, it does not exist + PMU->disablePowerOutput(XPOWERS_DLDO2); // Invalid power channel, it does not exist + PMU->disablePowerOutput(XPOWERS_VBACKUP); + // disable all axp chip interrupt + PMU->disableIRQ(XPOWERS_AXP2101_ALL_IRQ); + PMU->setChargerConstantCurr(XPOWERS_AXP2101_CHG_CUR_500MA); + + // Set up PMU interrupts + // Serial.println("Setting up PMU interrupts"); + pinMode(PIN_PMU_IRQ, INPUT_PULLUP); + attachInterrupt(PIN_PMU_IRQ, setPMUIntFlag, FALLING); + + // Reset and re-enable PMU interrupts + // Serial.println("Re-enable interrupts"); + PMU->disableIRQ(XPOWERS_AXP2101_ALL_IRQ); + PMU->clearIrqStatus(); + PMU->enableIRQ( + XPOWERS_AXP2101_BAT_INSERT_IRQ | XPOWERS_AXP2101_BAT_REMOVE_IRQ | // Battery interrupts + XPOWERS_AXP2101_VBUS_INSERT_IRQ | XPOWERS_AXP2101_VBUS_REMOVE_IRQ | // VBUS interrupts + XPOWERS_AXP2101_PKEY_SHORT_IRQ | XPOWERS_AXP2101_PKEY_LONG_IRQ | // Power Key interrupts + XPOWERS_AXP2101_BAT_CHG_DONE_IRQ | XPOWERS_AXP2101_BAT_CHG_START_IRQ // Charging interrupts + ); + } + + return true; +} + bool radio_init() { fallback_clock.begin(); rtc_clock.begin(Wire); @@ -66,3 +180,24 @@ mesh::LocalIdentity radio_new_identity() { RadioNoiseListener rng(radio); return mesh::LocalIdentity(&rng); // create new random identity } + +#ifdef MESH_DEBUG +void TBeamBoard::printPMU() +{ + Serial.print("isCharging:"); Serial.println(PMU->isCharging() ? "YES" : "NO"); + Serial.print("isDischarge:"); Serial.println(PMU->isDischarge() ? "YES" : "NO"); + Serial.print("isVbusIn:"); Serial.println(PMU->isVbusIn() ? "YES" : "NO"); + Serial.print("getBattVoltage:"); Serial.print(PMU->getBattVoltage()); Serial.println("mV"); + Serial.print("getVbusVoltage:"); Serial.print(PMU->getVbusVoltage()); Serial.println("mV"); + Serial.print("getSystemVoltage:"); Serial.print(PMU->getSystemVoltage()); Serial.println("mV"); + + // The battery percentage may be inaccurate at first use, the PMU will automatically + // learn the battery curve and will automatically calibrate the battery percentage + // after a charge and discharge cycle + if (PMU->isBatteryConnect()) { + Serial.print("getBatteryPercent:"); Serial.print(PMU->getBatteryPercent()); Serial.println("%"); + } + + Serial.println(); +} +#endif \ No newline at end of file