Repeated preamble/header activity can keep a requested primary or TempRadio change busy indefinitely. Bound unfinished-RX admission for accepted LR2021 changes while preserving completed packets, TX, BUSY and exclusive owners. Ordinary scans and CAD keep their existing admission rules. Expose the failing stage and native status through get radio.apply. Add saved radio.xtrim capacitor calibration and measured radio.freqerror in signed Hz from the last valid LoRa packet. Preserve native trim by default, restore explicit calibration after resets/Morse, verify saves before applying, wait two seconds for the acknowledgement, and roll back failed changes. Calibration motivation and prior upstream work: https://github.com/meshcore-dev/MeshCore/issues/3365 https://github.com/recrof/MeshCore/tree/lr2021-trim Packet-status format follows Semtech's LR20xx driver and RadioLib staging. Tests execute production activity/admission and transaction paths with noisy RX, completion races, protected owners, failed setters, RX restart and save rollback. Physical W12 retuning and RF calibration still require confirmation.
507 lines
18 KiB
C++
507 lines
18 KiB
C++
#include <helpers/RegulatorSettings.h>
|
|
#if defined(USE_LR2021)
|
|
#include <helpers/radiolib/LR2021CalibrationSettings.h>
|
|
#endif
|
|
#include <helpers/LedSettings.h>
|
|
#include "SensorMesh.h"
|
|
#include <math.h>
|
|
#include <helpers/IdentityGeneration.h>
|
|
#include <helpers/ui/StartupScreen.h>
|
|
#include <helpers/ui/DisplayPowerSettings.h>
|
|
#if defined(HELTEC_V4_DISPLAY_UNIFIED) && HELTEC_V4_DISPLAY_UNIFIED && defined(DISPLAY_CLASS)
|
|
#include <helpers/ui/HeltecV4DisplaySettings.h>
|
|
#endif
|
|
#if defined(OLED_AUTO_DETECT) && OLED_AUTO_DETECT
|
|
#include <helpers/ui/OLEDControllerSettings.h>
|
|
#endif
|
|
#if defined(NRF52_PLATFORM)
|
|
#include <helpers/nrf52/InternalPrimaryFsBoot.h>
|
|
#include <helpers/nrf52/RamFallbackFileSystem.h>
|
|
#endif
|
|
#include <helpers/UsbLogging.h>
|
|
#include <helpers/UsbLoggingWatchdog.h>
|
|
#include <helpers/UsbLoggingClientActivity.h>
|
|
#include <helpers/MorseLoopPolicy.h>
|
|
#include <helpers/PowerManagementUtils.h>
|
|
#if defined(NRF52_PLATFORM) && MESH_ENABLE_SENSOR_TRACKER
|
|
#include <utility/SoftwareTimer.h>
|
|
static SoftwareTimer tracker_wake_timer;
|
|
static void trackerWakeCallback(TimerHandle_t) { }
|
|
|
|
// SYSTEMON event sleep has no built-in deadline. Arm a real timer before
|
|
// leaving the loop; allocation/queue failure keeps polling instead of hanging.
|
|
static bool armTrackerSleepWake(uint32_t seconds) {
|
|
if (seconds == 0 || seconds > 30) return false;
|
|
if (!tracker_wake_timer.getHandle()) {
|
|
tracker_wake_timer.begin(1000, trackerWakeCallback, nullptr, false);
|
|
}
|
|
const TimerHandle_t handle = tracker_wake_timer.getHandle();
|
|
if (!handle) return false;
|
|
const TickType_t requested_ticks = pdMS_TO_TICKS(seconds * 1000UL);
|
|
if (requested_ticks == 0) return false;
|
|
|
|
// Reposting an unchanged timer command wakes the higher-priority timer task
|
|
// and leaves a scheduler event immediately before every CPU sleep attempt.
|
|
// An existing earlier wake is sufficient; do not keep postponing it.
|
|
bool wake_ready = false;
|
|
taskENTER_CRITICAL();
|
|
if (xTimerIsTimerActive(handle) != pdFALSE) {
|
|
// The compound snapshot must not race the one-shot becoming dormant:
|
|
// xTimerGetExpiryTime() is defined only while the timer is active.
|
|
const TickType_t expiry = xTimerGetExpiryTime(handle);
|
|
const TickType_t now = xTaskGetTickCount();
|
|
const TickType_t remaining = static_cast<TickType_t>(expiry - now);
|
|
wake_ready = remaining != 0 && remaining <= requested_ticks;
|
|
}
|
|
taskEXIT_CRITICAL();
|
|
if (wake_ready) return true;
|
|
|
|
// Dormant/expired timers need a fresh command even when the period matches.
|
|
// A failed queue send cannot promise an earlier deadline; keep polling.
|
|
return xTimerChangePeriod(handle, requested_ticks, 0) == pdPASS;
|
|
}
|
|
#endif
|
|
|
|
#if defined(ESP32_PLATFORM)
|
|
#include <helpers/ESP32TrueRandom.h>
|
|
#include <helpers/esp32/ProfileIdleSleep.h>
|
|
#endif
|
|
|
|
#ifdef DISPLAY_CLASS
|
|
#include "UITask.h"
|
|
static UITask ui_task(display);
|
|
static bool display_ready = false;
|
|
#endif
|
|
|
|
class MyMesh : public SensorMesh {
|
|
public:
|
|
MyMesh(mesh::MainBoard& board, mesh::Radio& radio, mesh::MillisecondClock& ms, mesh::RNG& rng, mesh::RTCClock& rtc, mesh::MeshTables& tables)
|
|
: SensorMesh(board, radio, ms, rng, rtc, tables),
|
|
battery_data(12*24, 5*60) // 24 hours worth of battery data, every 5 minutes
|
|
{
|
|
}
|
|
|
|
protected:
|
|
/* ========================== custom logic here ========================== */
|
|
Trigger low_batt, critical_batt;
|
|
TimeSeriesData battery_data;
|
|
|
|
void onSensorDataRead() override {
|
|
float batt_voltage = getVoltage(TELEM_CHANNEL_SELF);
|
|
// Boards without battery measurement return zero; do not alarm on it.
|
|
const bool battery_available = batt_voltage > 0.0f && isfinite(batt_voltage);
|
|
|
|
battery_data.recordData(getRTCClock(), batt_voltage); // record battery
|
|
alertIf(battery_available && batt_voltage < 3.4f, critical_batt, HIGH_PRI_ALERT, "Battery is critical!");
|
|
alertIf(battery_available && batt_voltage < 3.6f, low_batt, LOW_PRI_ALERT, "Battery is low");
|
|
}
|
|
|
|
int querySeriesData(uint32_t start_secs_ago, uint32_t end_secs_ago, MinMaxAvg dest[], int max_num) override {
|
|
battery_data.calcMinMaxAvg(getRTCClock(), start_secs_ago, end_secs_ago, &dest[0], TELEM_CHANNEL_SELF, LPP_VOLTAGE);
|
|
return 1;
|
|
}
|
|
|
|
bool handleCustomCommand(uint32_t sender_timestamp, char* command, char* reply) override {
|
|
if (strcmp(command, "magic") == 0) { // example 'custom' command handling
|
|
strcpy(reply, "**Magic now done**");
|
|
return true; // handled
|
|
}
|
|
return false; // not handled
|
|
}
|
|
/* ======================================================================= */
|
|
};
|
|
|
|
StdRNG fast_rng;
|
|
SimpleMeshTables tables;
|
|
|
|
MyMesh the_mesh(board, radio_driver, *new ArduinoMillis(), fast_rng, rtc_clock, tables);
|
|
|
|
SensorMesh& activeSensorMesh() {
|
|
return the_mesh;
|
|
}
|
|
|
|
void halt() {
|
|
while (1) ;
|
|
}
|
|
|
|
static char command[160];
|
|
static const unsigned long POWERSAVING_FIRST_SLEEP_SECS = 120;
|
|
|
|
void setup() {
|
|
mesh::prepareUsbLoggingPort();
|
|
Serial.begin(115200);
|
|
#if MESH_USB_CONSOLE_COOPERATIVE
|
|
mesh::beginUsbLoggingPort();
|
|
#endif
|
|
board.begin();
|
|
|
|
#ifdef HAS_EXTERNAL_WATCHDOG
|
|
external_watchdog.begin();
|
|
#endif
|
|
|
|
FILESYSTEM* fs;
|
|
#if defined(NRF52_PLATFORM)
|
|
bool volatile_primary_fs = false;
|
|
mesh::storage::RamFallbackFileSystem* ram_primary_fs = nullptr;
|
|
const auto primary_fs_boot =
|
|
mesh::storage::beginInternalPrimaryFilesystemSafely(InternalFS);
|
|
if (mesh::storage::internalPrimaryFilesystemReady(primary_fs_boot)) {
|
|
fs = &InternalFS;
|
|
} else {
|
|
ram_primary_fs = mesh::storage::createRamFallbackFileSystem();
|
|
if (ram_primary_fs == nullptr) {
|
|
MESH_DEBUG_PRINTLN("InternalFS and RAM fallback initialization failed; rebooting");
|
|
board.reboot();
|
|
return;
|
|
}
|
|
fs = &ram_primary_fs->filesystem();
|
|
volatile_primary_fs = true;
|
|
}
|
|
IdentityStore store(*fs, "");
|
|
#elif defined(STM32_PLATFORM)
|
|
InternalFS.begin();
|
|
fs = &InternalFS;
|
|
IdentityStore store(InternalFS, "");
|
|
#elif defined(ESP32)
|
|
SPIFFS.begin(true);
|
|
fs = &SPIFFS;
|
|
IdentityStore store(SPIFFS, "/identity");
|
|
#elif defined(RP2040_PLATFORM)
|
|
LittleFS.begin();
|
|
fs = &LittleFS;
|
|
IdentityStore store(LittleFS, "/identity");
|
|
store.begin();
|
|
#else
|
|
#error "need to define filesystem"
|
|
#endif
|
|
FILESYSTEM* regulator_prefs_fs = fs;
|
|
#if defined(NRF52_PLATFORM)
|
|
if (volatile_primary_fs) regulator_prefs_fs = nullptr;
|
|
#endif
|
|
mesh::leds::beginSettings(board, regulator_prefs_fs);
|
|
if (!mesh::power::beginRegulatorSettings(board, radio_driver, regulator_prefs_fs)) {
|
|
MESH_DEBUG_PRINTLN("Regulator selection could not be applied");
|
|
}
|
|
mesh::RadioProfileCLI::configureBootTuning(regulator_prefs_fs, radio_driver);
|
|
#if defined(USE_LR2021)
|
|
mesh::lr2021::loadLR2021CalibrationSettings(regulator_prefs_fs, radio_driver);
|
|
#endif
|
|
#ifdef DISPLAY_CLASS
|
|
mesh::ui::loadDisplayPowerSettings(fs, false);
|
|
#if defined(HELTEC_V4_DISPLAY_UNIFIED) && HELTEC_V4_DISPLAY_UNIFIED
|
|
mesh::ui::loadHeltecV4DisplaySettings(regulator_prefs_fs, display);
|
|
#endif
|
|
#if defined(OLED_AUTO_DETECT) && OLED_AUTO_DETECT
|
|
mesh::ui::loadOLEDControllerSettings(regulator_prefs_fs);
|
|
#endif
|
|
mesh::ui::StartupScreen startup_screen;
|
|
display_ready = display.begin();
|
|
if (display_ready) {
|
|
startup_screen.begin(&display,
|
|
board.isExternalPowered() || board.isUsbHostConnected());
|
|
}
|
|
#endif
|
|
|
|
// Let serial settle after the display is already showing startup.
|
|
delay(1000);
|
|
|
|
#if defined(MESH_DEBUG) && defined(NRF52_PLATFORM)
|
|
// Allow the USB console to settle before printing boot diagnostics.
|
|
delay(5000);
|
|
#endif
|
|
|
|
int radioinit_attempts = 0;
|
|
while (!radio_init()) {
|
|
++radioinit_attempts;
|
|
MESH_DEBUG_PRINTLN("Radio init failed! (attempt %d)", radioinit_attempts);
|
|
if (radioinit_attempts >= 3) {
|
|
MESH_DEBUG_PRINTLN("Radio init failed 3x - rebooting");
|
|
board.reboot();
|
|
}
|
|
delay(500);
|
|
}
|
|
|
|
fast_rng.begin(radio_driver.getRngSeed());
|
|
|
|
#if defined(NRF52_PLATFORM)
|
|
IdentityLoadResult identity_load = volatile_primary_fs
|
|
? store.loadResult("_main", the_mesh.self_id)
|
|
: mesh::storage::loadIdentityWithPrimaryRecovery(
|
|
InternalFS,
|
|
[&store]() { return store.loadResult("_main", the_mesh.self_id); });
|
|
if (identity_load == IdentityLoadResult::Unreadable) {
|
|
ram_primary_fs = mesh::storage::createRamFallbackFileSystem();
|
|
if (ram_primary_fs != nullptr) {
|
|
fs = &ram_primary_fs->filesystem();
|
|
store.useFileSystem(*fs);
|
|
volatile_primary_fs = true;
|
|
identity_load = IdentityLoadResult::Missing;
|
|
}
|
|
}
|
|
#else
|
|
const IdentityLoadResult identity_load =
|
|
store.loadResult("_main", the_mesh.self_id);
|
|
#endif
|
|
const bool needs_identity = identity_load == IdentityLoadResult::Missing
|
|
|| (identity_load == IdentityLoadResult::Loaded
|
|
&& mesh::hasReservedIdentityPrefix(the_mesh.self_id));
|
|
bool identity_ready = identity_load != IdentityLoadResult::Unreadable;
|
|
if (needs_identity) {
|
|
#ifdef DISPLAY_CLASS
|
|
startup_screen.generatingKey();
|
|
mesh::ScopedIdentityGenerationProgress progress(
|
|
mesh::ui::StartupScreen::progress, &startup_screen);
|
|
#endif
|
|
MESH_DEBUG_PRINTLN("Generating new keypair");
|
|
identity_ready = mesh::generateUsableLocalIdentity(the_mesh.self_id, radio_new_identity);
|
|
#ifdef DISPLAY_CLASS
|
|
if (identity_ready) startup_screen.savingIdentity();
|
|
else startup_screen.starting();
|
|
#endif
|
|
if (identity_ready) identity_ready = store.saveWithRetry("_main", the_mesh.self_id);
|
|
#ifdef DISPLAY_CLASS
|
|
startup_screen.starting();
|
|
#endif
|
|
}
|
|
|
|
#if defined(ESP32_PLATFORM)
|
|
mesh::discardESP32TrueRandom();
|
|
#endif
|
|
if (!identity_ready) {
|
|
MESH_DEBUG_PRINTLN("Identity generation or persistence failed after retries; rebooting");
|
|
board.reboot();
|
|
return;
|
|
}
|
|
|
|
Stream& console = mesh::usbConsolePort();
|
|
console.print("Sensor ID: ");
|
|
mesh::Utils::printHex(console, the_mesh.self_id.pub_key, PUB_KEY_SIZE);
|
|
console.println();
|
|
|
|
command[0] = 0;
|
|
|
|
sensors.begin();
|
|
|
|
#if defined(NRF52_PLATFORM)
|
|
the_mesh.setTrackerStorageAvailable(!volatile_primary_fs);
|
|
#endif
|
|
the_mesh.begin(fs);
|
|
|
|
#if defined(NRF52_PLATFORM)
|
|
mesh::loadUsbLoggingWatchdog(fs, !volatile_primary_fs,
|
|
[]() -> uint32_t { return rtc_clock.getCurrentTime(); });
|
|
#else
|
|
mesh::loadUsbLoggingWatchdog(fs, true,
|
|
[]() -> uint32_t { return rtc_clock.getCurrentTime(); });
|
|
#endif
|
|
|
|
#if defined(NRF52_PLATFORM)
|
|
if (volatile_primary_fs) {
|
|
strncpy(the_mesh.getNodePrefs()->node_name,
|
|
mesh::storage::BAD_FILESYSTEM_NODE_NAME,
|
|
sizeof(the_mesh.getNodePrefs()->node_name));
|
|
the_mesh.getNodePrefs()->node_name[
|
|
sizeof(the_mesh.getNodePrefs()->node_name) - 1] = 0;
|
|
}
|
|
#endif
|
|
|
|
#ifdef DISPLAY_CLASS
|
|
#if defined(HELTEC_V4_DISPLAY_UNIFIED) && HELTEC_V4_DISPLAY_UNIFIED
|
|
// Initialize the UI dependency even when auto detection found no screen.
|
|
// A later saved display.type change can enable it without restarting.
|
|
display_ready = true;
|
|
#endif
|
|
if (display_ready) {
|
|
ui_task.begin(the_mesh.getNodePrefs(), FIRMWARE_BUILD_DATE, FIRMWARE_VERSION);
|
|
}
|
|
#endif
|
|
|
|
// send out initial zero hop Advertisement to the mesh
|
|
#if ENABLE_ADVERT_ON_BOOT == 1
|
|
the_mesh.sendSelfAdvertisement(16000, false);
|
|
#endif
|
|
|
|
board.onBootComplete();
|
|
}
|
|
|
|
static bool usbLoggingRecoverySafe(void*) {
|
|
if (board.isOTAUpdateRunning() || board.isRadioTestActive()
|
|
|| radio_driver.isWatchdogObserving() || radio_driver.isCalibratingNoiseFloor()
|
|
|| !the_mesh.canRecoverUsbLogging()) return false;
|
|
const auto usb = mesh::usbLoggingStatus();
|
|
return !usb.reader_connected || usb.stalled || !command[0];
|
|
}
|
|
|
|
void loop() {
|
|
#if defined(NRF52_PLATFORM)
|
|
board.prepareSleepCycle();
|
|
#endif
|
|
mesh::serviceUsbLoggingPort();
|
|
#if defined(NRF52_PLATFORM)
|
|
board.feedWatchdog(the_mesh.getNodePrefs()->system_watchdog_enabled != 0);
|
|
#endif
|
|
bool usb_ready = true;
|
|
#if MESH_USB_CONSOLE_COOPERATIVE
|
|
mesh::serviceUsbLoggingPort();
|
|
mesh::serviceUsbTerminalPort();
|
|
if (mesh::takeUsbTerminalSessionReset()) command[0] = 0;
|
|
usb_ready = mesh::tryCompleteUsbTerminalSessionReset()
|
|
&& mesh::canAcceptUsbConsoleCommand();
|
|
#endif
|
|
Stream& console = mesh::usbConsolePort();
|
|
int len = strlen(command);
|
|
// `command` must stay NUL-terminated within its bounds. If it ever isn't,
|
|
// strlen() above can return >= sizeof(command) and the loop below would then
|
|
// index past the buffer, so clamp defensively.
|
|
if (len >= (int)sizeof(command)) {
|
|
command[0] = 0;
|
|
len = 0;
|
|
}
|
|
while (usb_ready && console.available() && len < sizeof(command)-1) {
|
|
char c = console.read();
|
|
if (c != '\n') {
|
|
command[len++] = c;
|
|
command[len] = 0;
|
|
}
|
|
console.print(c);
|
|
}
|
|
if (len == sizeof(command)-1) { // buffer full: treat as a completed line
|
|
command[sizeof(command)-2] = '\r'; // place end-of-line marker inside the buffer
|
|
command[sizeof(command)-1] = 0; // keep the buffer NUL-terminated
|
|
}
|
|
|
|
if (len > 0 && command[len - 1] == '\r') { // received complete line
|
|
command[len - 1] = 0; // replace newline with C string null terminator
|
|
char reply[160];
|
|
reply[0] = 0;
|
|
if (len < static_cast<int>(sizeof(command) - 1)
|
|
&& strlen(command) == static_cast<size_t>(len - 1))
|
|
mesh::noteUsbLoggingStatsCommand(command);
|
|
the_mesh.handleCommand(0, command, reply); // NOTE: there is no sender_timestamp via serial!
|
|
if (reply[0]) {
|
|
console.print(" -> ");
|
|
console.println(reply);
|
|
}
|
|
|
|
command[0] = 0; // reset command buffer
|
|
}
|
|
|
|
mesh::power::serviceRegulatorSettings();
|
|
board.loop(); // let the board feed its watchdog, run periodic housekeeping
|
|
|
|
the_mesh.loop();
|
|
const bool morse_timing = mesh::morseTimingActive(&radio_driver);
|
|
#if defined(USE_LR2021)
|
|
if (!morse_timing) mesh::lr2021::loopLR2021CalibrationSettings(radio_driver);
|
|
#endif
|
|
if (!morse_timing) sensors.loop();
|
|
#ifdef DISPLAY_CLASS
|
|
#if defined(HELTEC_V4_DISPLAY_UNIFIED) && HELTEC_V4_DISPLAY_UNIFIED
|
|
if (display.selectedType() != mesh::ui::HeltecV4DisplayType::None) ui_task.loop();
|
|
#else
|
|
if (display_ready) ui_task.loop();
|
|
#endif
|
|
#endif
|
|
rtc_clock.tick();
|
|
if (mesh::serviceUsbLoggingWatchdog(usbLoggingRecoverySafe)) board.reboot();
|
|
#if MESH_USB_CONSOLE_COOPERATIVE
|
|
mesh::serviceUsbTerminalPort();
|
|
#endif
|
|
#ifdef HAS_EXTERNAL_WATCHDOG
|
|
external_watchdog.loop();
|
|
#endif
|
|
#if defined(NRF52_PLATFORM)
|
|
if ((the_mesh.getNodePrefs()->powersaving_enabled || the_mesh.isTrackerModeEnabled())
|
|
&& the_mesh.isDualRadioActive() && !morse_timing) board.prepareProfileIdleCycle();
|
|
#endif
|
|
|
|
#if defined(ESP32_PLATFORM)
|
|
board.servicePowerTestControls();
|
|
mesh::esp32::serviceCpuFrequencyPolicy(board, the_mesh.getNodePrefs()->cpu_freq_mhz,
|
|
the_mesh.getNodePrefs()->powersaving_enabled != 0, millis(), false, the_mesh.isDualRadioActive());
|
|
#endif
|
|
const auto sleep_guard = [](bool allowed, mesh::powerdiag::Reason reason) {
|
|
if (!allowed && mesh::powerdiag::enabled())
|
|
mesh::powerdiag::decision(mesh::powerdiag::mask(reason), millis());
|
|
return allowed;
|
|
};
|
|
bool can_power_save = sleep_guard(the_mesh.getNodePrefs()->powersaving_enabled
|
|
|| the_mesh.isTrackerModeEnabled(), mesh::powerdiag::Reason::PolicyOff)
|
|
&& sleep_guard(!morse_timing, mesh::powerdiag::Reason::Morse)
|
|
&& sleep_guard(!board.isUsbDataConnected(), mesh::powerdiag::Reason::UsbSession)
|
|
&& sleep_guard(!mesh::isUsbLoggingWatchdogArmed(), mesh::powerdiag::Reason::UsbRecovery);
|
|
#if defined(MOMENTARY_BUTTON_WAKE_FROM_SLEEP) \
|
|
&& MOMENTARY_BUTTON_WAKE_FROM_SLEEP \
|
|
&& defined(PIN_USER_BTN) && defined(DISPLAY_CLASS)
|
|
can_power_save = can_power_save && sleep_guard(!user_btn.needsPolling(), mesh::powerdiag::Reason::Button);
|
|
#endif
|
|
if (can_power_save) {
|
|
#if defined(ESP32_PLATFORM)
|
|
const uint32_t sleep_budget_started_us = static_cast<uint32_t>(esp_timer_get_time());
|
|
#endif
|
|
uint32_t sleep_ms = the_mesh.getPowerSaveSleepMilliseconds(30000);
|
|
uint32_t noise_floor_delay_ms = 0;
|
|
const bool noise_floor_wait = the_mesh.getNextNoiseFloorWakeDelay(noise_floor_delay_ms);
|
|
if (noise_floor_wait && noise_floor_delay_ms < sleep_ms) sleep_ms = noise_floor_delay_ms;
|
|
if (noise_floor_wait && noise_floor_delay_ms == 0) {
|
|
if (mesh::powerdiag::enabled())
|
|
mesh::powerdiag::decision(mesh::powerdiag::mask(mesh::powerdiag::Reason::Calibration), millis());
|
|
sleep_ms = 0;
|
|
}
|
|
#ifdef HAS_EXTERNAL_WATCHDOG
|
|
if (sleep_ms > 0) external_watchdog.feed();
|
|
#endif
|
|
#if defined(NRF52_PLATFORM)
|
|
if (sleep_ms > 0 && the_mesh.canIdleDualRadio()) {
|
|
#if MESH_ENABLE_SENSOR_TRACKER
|
|
if (!the_mesh.isTrackerModeEnabled() || armTrackerSleepWake(mesh::power::powerSaveSleepSeconds(sleep_ms))) {
|
|
// Timer setup can schedule another task or consume the scan guard.
|
|
if (the_mesh.canIdleDualRadio()) {
|
|
if (mesh::powerdiag::enabled()) mesh::powerdiag::decision(0, millis());
|
|
if (noise_floor_wait) board.sleepMilliseconds(sleep_ms);
|
|
else board.sleep(0);
|
|
}
|
|
}
|
|
else {
|
|
if (mesh::powerdiag::enabled())
|
|
mesh::powerdiag::boardVeto(mesh::powerdiag::Reason::NoWake, millis());
|
|
delay(1);
|
|
}
|
|
#else
|
|
if (mesh::powerdiag::enabled()) mesh::powerdiag::decision(0, millis());
|
|
if (noise_floor_wait) board.sleepMilliseconds(sleep_ms);
|
|
else board.sleep(0);
|
|
#endif
|
|
}
|
|
#else
|
|
if (sleep_ms > 0
|
|
&& the_mesh.millisHasNowPassed(
|
|
POWERSAVING_FIRST_SLEEP_SECS * 1000UL)) {
|
|
if (mesh::powerdiag::enabled()) mesh::powerdiag::decision(0, millis());
|
|
#if defined(ESP32_PLATFORM)
|
|
if (the_mesh.isDualRadioActive()) {
|
|
if (!mesh::esp32::tryProfileIdleSleep(the_mesh, board, sleep_budget_started_us, sleep_ms)
|
|
&& mesh::powerdiag::enabled())
|
|
mesh::powerdiag::decision(mesh::powerdiag::mask(mesh::powerdiag::Reason::DualRadio), millis());
|
|
} else
|
|
#endif
|
|
board.sleepMilliseconds(sleep_ms);
|
|
} else if (sleep_ms > 0 && mesh::powerdiag::enabled()) {
|
|
mesh::powerdiag::decision(mesh::powerdiag::mask(mesh::powerdiag::Reason::Startup), millis());
|
|
}
|
|
#endif
|
|
}
|
|
#if defined(ESP32_PLATFORM)
|
|
if (!morse_timing && !can_power_save && !the_mesh.isDualRadioActive()
|
|
&& (the_mesh.getNodePrefs()->powersaving_enabled
|
|
|| the_mesh.isTrackerModeEnabled())) {
|
|
// Live radio2 needs the scanner's zero-delay polling cadence.
|
|
delay(1); // Keep USB and the button wake interval serviced while idle.
|
|
}
|
|
#endif
|
|
if (the_mesh.getNodePrefs()->reboot_interval > 0 &&
|
|
the_mesh.millisHasNowPassed(the_mesh.getNodePrefs()->reboot_interval * 3600000UL)) {
|
|
board.reboot();
|
|
}
|
|
}
|