Detect OLED or the original stock TFT touch assembly in ordinary V4.2/V4.3 images. Add saved live display.type auto/oled/tft/off selection and report the active selection in board output. Keep sensors on primary I2C, preserve shared power, and isolate TFT FSPI from radio HSPI through sleep and changes. Consolidate future picker/build choices only with application capability proof. Retain exact legacy OTA identities and separate R8 hardware images. Document controls and physical qualification limits. Validation: five serialized Full builds with capability/size/RAM checks; focused host tests, existing R8/T-Deck/OLED regressions, build-profile suite, and picker/provider tests. V4.3 OLED live changes and off/auto reboot persistence passed on Mercerwood. Physical TFT qualification remains pending.
522 lines
18 KiB
C++
522 lines
18 KiB
C++
#include <helpers/RegulatorSettings.h>
|
|
#include <helpers/sensors/SolarXiaoPower.h>
|
|
#include <helpers/LedSettings.h>
|
|
#include <Arduino.h> // needed for PlatformIO
|
|
#include <Mesh.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
|
|
#include <helpers/UsbLoggingWatchdog.h>
|
|
#include <helpers/UsbLoggingClientActivity.h>
|
|
#include <helpers/MorseLoopPolicy.h>
|
|
#include <helpers/PowerManagementUtils.h>
|
|
#if MESH_PACKET_LOGGING
|
|
#include <helpers/SerialPacketLog.h>
|
|
#endif
|
|
|
|
#include "MyMesh.h"
|
|
#if defined(OTA_BOOTLOADER_MIGRATION)
|
|
#include <helpers/ota/BootloaderMigration.h>
|
|
#endif
|
|
|
|
#if defined(ESP32_PLATFORM)
|
|
#include <helpers/ESP32TrueRandom.h>
|
|
#include <helpers/esp32/ProfileIdleSleep.h>
|
|
#include <helpers/esp32/BootFileSystem.h>
|
|
#endif
|
|
#if defined(NRF52_PLATFORM)
|
|
#include <helpers/nrf52/InternalPrimaryFsBoot.h>
|
|
#include <helpers/nrf52/RamFallbackFileSystem.h>
|
|
#endif
|
|
|
|
#ifdef ETHERNET_ENABLED
|
|
#define ETHERNET_CLI_BANNER "MeshCore Room Server CLI"
|
|
#include <helpers/nrf52/EthernetCLI.h>
|
|
#endif
|
|
|
|
#ifdef DISPLAY_CLASS
|
|
#include "UITask.h"
|
|
#ifdef DISPLAY_TOUCH_TOGGLE
|
|
static UITask ui_task(board, display);
|
|
#else
|
|
static UITask ui_task(display);
|
|
#endif
|
|
static bool display_ready = false;
|
|
#endif
|
|
|
|
StdRNG fast_rng;
|
|
SimpleMeshTables tables;
|
|
MyMesh the_mesh(board, radio_driver, *new ArduinoMillis(), fast_rng, rtc_clock, tables);
|
|
#include "../InfrastructureWireless.h"
|
|
|
|
#if defined(ETHERNET_ENABLED) && defined(OTA_RAK15002_SD_STORE)
|
|
#include <helpers/ota/OtaContext.h>
|
|
// The RAK15002 and RAK13800 occupy the same IO-slot chip select. Ethernet is
|
|
// opt-in, and only the mesh thread may release cached sources/staging owners
|
|
// and reserve the bus before its asynchronous DHCP worker starts.
|
|
static bool prepareSdEthernet(void*, char* error, size_t capacity) {
|
|
auto* context = mesh::ota::ota_context_if_active();
|
|
if (!context) {
|
|
snprintf(error, capacity, "Error: OTA storage is not ready");
|
|
return false;
|
|
}
|
|
if (mesh::ota::ota_rak15002_sd_card_present()) {
|
|
snprintf(error, capacity, "Error: RAK15002 SD card fitted; Ethernet shares its IO slot");
|
|
return false;
|
|
}
|
|
if (!context->prepareRakSdForEthernet() ||
|
|
!mesh::ota::rak4631_try_acquire_spi(mesh::ota::RakSharedSpiOwner::Ethernet)) {
|
|
snprintf(error, capacity, "Error: Ethernet bus is locked by OTA activity or storage");
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool canReleaseSdEthernet(void*) {
|
|
auto* context = mesh::ota::ota_context_if_active();
|
|
return context && context->canReleaseRakSdForEthernet();
|
|
}
|
|
|
|
static bool releaseSdEthernet(void*) {
|
|
auto* context = mesh::ota::ota_context_if_active();
|
|
if (!context || !context->releasedRakSdEthernet()) return false;
|
|
mesh::ota::rak4631_release_spi(mesh::ota::RakSharedSpiOwner::Ethernet);
|
|
return true;
|
|
}
|
|
#endif
|
|
|
|
void halt() {
|
|
while (1) ;
|
|
}
|
|
|
|
static char command[MAX_POST_TEXT_LEN+1];
|
|
#ifdef ETHERNET_ENABLED
|
|
static char ethernet_command[MAX_POST_TEXT_LEN+1];
|
|
#endif
|
|
|
|
// For power saving
|
|
unsigned long POWERSAVING_FIRSTSLEEP_SECS = 120; // The first sleep (if enabled) from boot
|
|
|
|
void setup() {
|
|
mesh::wireless::control().begin(infrastructure_wireless);
|
|
mesh::prepareUsbLoggingPort();
|
|
Serial.begin(115200);
|
|
#if MESH_USB_CONSOLE_COOPERATIVE
|
|
mesh::beginUsbLoggingPort();
|
|
#endif
|
|
#if MESH_PACKET_LOGGING
|
|
mesh::serialLogBegin();
|
|
#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(RP2040_PLATFORM)
|
|
LittleFS.begin();
|
|
fs = &LittleFS;
|
|
IdentityStore store(LittleFS, "/identity");
|
|
store.begin();
|
|
#elif defined(ESP32)
|
|
SPIFFS.begin(true);
|
|
fs = &SPIFFS;
|
|
IdentityStore store(SPIFFS, "/identity");
|
|
#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 defined(MESH_SOLARXIAO_POWER) && MESH_SOLARXIAO_POWER
|
|
mesh::solar::beginSettings(board, regulator_prefs_fs, MESH_ENABLE_TELEMETRY_HISTORY);
|
|
#endif
|
|
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);
|
|
#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);
|
|
|
|
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
|
|
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;
|
|
}
|
|
|
|
#if defined(ESP32_PLATFORM)
|
|
// Identity writes have finished; this FS view remains alive after setup.
|
|
static mesh::Esp32BootFileSystem boot_fs(*fs);
|
|
fs = &boot_fs;
|
|
boot_fs.beginInventory();
|
|
#endif
|
|
|
|
Stream& console = mesh::usbConsolePort();
|
|
console.print("Room ID: ");
|
|
mesh::Utils::printHex(console, the_mesh.self_id.pub_key, PUB_KEY_SIZE); console.println();
|
|
|
|
command[0] = 0;
|
|
#ifdef ETHERNET_ENABLED
|
|
ethernet_command[0] = 0;
|
|
#endif
|
|
|
|
sensors.begin();
|
|
|
|
the_mesh.begin(fs);
|
|
#if defined(OTA_BOOTLOADER_MIGRATION)
|
|
mesh::ota::bootloader_migration_begin(volatile_primary_fs ? nullptr : fs);
|
|
#endif
|
|
|
|
#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) {
|
|
#ifdef WITH_MQTT_BRIDGE
|
|
ui_task.setObserverPrefs(the_mesh.getObserverPrefs());
|
|
#endif
|
|
#ifdef DISPLAY_ACTIVITY_DASHBOARD
|
|
ui_task.setActivityWindow(the_mesh.getActivityWindow());
|
|
#endif
|
|
ui_task.begin(the_mesh.getNodePrefs(), FIRMWARE_BUILD_DATE, FIRMWARE_VERSION);
|
|
}
|
|
#endif
|
|
|
|
#ifdef ETHERNET_ENABLED
|
|
#if defined(OTA_RAK15002_SD_STORE)
|
|
EthernetCliHooks hooks;
|
|
hooks.prepare = prepareSdEthernet;
|
|
hooks.can_release = canReleaseSdEthernet;
|
|
hooks.released = releaseSdEthernet;
|
|
hooks.storage_conflict = [](void*) { return mesh::ota::ota_rak15002_sd_card_present(); };
|
|
ethernet_configure(hooks); // Shared IO slot defaults OFF on every restart.
|
|
#endif
|
|
ethernet_start_task();
|
|
#endif
|
|
|
|
// send out initial zero hop Advertisement to the mesh
|
|
#if ENABLE_ADVERT_ON_BOOT == 1
|
|
the_mesh.sendSelfAdvertisement(16000, false);
|
|
#endif
|
|
|
|
#if defined(ESP32_PLATFORM)
|
|
mesh::endEsp32BootFileInventory();
|
|
#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]
|
|
#if MESH_USB_CONSOLE_COOPERATIVE
|
|
&& !the_mesh.hasPendingSerialOutput()
|
|
#endif
|
|
);
|
|
}
|
|
|
|
void loop() {
|
|
#if defined(NRF52_PLATFORM)
|
|
board.prepareSleepCycle();
|
|
#endif
|
|
mesh::serviceUsbLoggingPort();
|
|
mesh::wireless::control().service(millis());
|
|
#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;
|
|
the_mesh.cancelPendingSerialOutput();
|
|
}
|
|
usb_ready = mesh::tryCompleteUsbTerminalSessionReset();
|
|
usb_ready = usb_ready && !the_mesh.hasPendingSerialOutput()
|
|
&& 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);
|
|
#ifdef ETHERNET_ENABLED
|
|
if (!ethernet_handle_command(command, reply)) {
|
|
the_mesh.handleUsbCommand(command, reply);
|
|
}
|
|
#else
|
|
the_mesh.handleUsbCommand(command, reply);
|
|
#endif
|
|
if (reply[0]) {
|
|
console.printf(" -> %s\r\n", reply);
|
|
}
|
|
|
|
command[0] = 0; // reset command buffer
|
|
}
|
|
|
|
#ifdef ETHERNET_ENABLED
|
|
ethernet_loop_maintain();
|
|
#if defined(OTA_RAK15002_SD_STORE)
|
|
if (ethernet_take_session_reset() || !ethernet_get_enabled()) {
|
|
#else
|
|
if (ethernet_take_session_reset() || !ethernet_client.connected()) {
|
|
#endif
|
|
the_mesh.cancelLocalOutput(ethernet_client);
|
|
ethernet_command[0] = 0;
|
|
}
|
|
if (!the_mesh.hasPendingLocalOutput() && ethernet_read_line(ethernet_command, sizeof(ethernet_command))) {
|
|
char reply[160];
|
|
reply[0] = 0;
|
|
if (!ethernet_handle_command(ethernet_command, reply)) {
|
|
the_mesh.handleLocalCommand(ethernet_command, reply, ethernet_client);
|
|
}
|
|
ethernet_send_reply(reply);
|
|
ethernet_command[0] = 0;
|
|
}
|
|
#endif
|
|
|
|
the_mesh.loop();
|
|
const bool morse_timing = mesh::morseTimingActive(&radio_driver);
|
|
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();
|
|
mesh::power::serviceRegulatorSettings();
|
|
#if defined(MESH_SOLARXIAO_POWER) && MESH_SOLARXIAO_POWER
|
|
mesh::solar::service();
|
|
#endif
|
|
board.loop();
|
|
if (mesh::serviceUsbLoggingWatchdog(usbLoggingRecoverySafe)) board.reboot();
|
|
#if MESH_USB_CONSOLE_COOPERATIVE
|
|
mesh::serviceUsbTerminalPort();
|
|
#endif
|
|
#ifdef TBEAM_1W
|
|
board.updateFanControl();
|
|
#endif
|
|
#ifdef HAS_EXTERNAL_WATCHDOG
|
|
external_watchdog.loop();
|
|
#endif
|
|
#if defined(NRF52_PLATFORM)
|
|
if (the_mesh.getNodePrefs()->powersaving_enabled && 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,
|
|
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)
|
|
&& sleep_guard(!mesh::wireless::control().pending(), mesh::powerdiag::Reason::Wireless);
|
|
#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::powerdiag::enabled()) mesh::powerdiag::decision(0, millis());
|
|
if (noise_floor_wait) board.sleepMilliseconds(sleep_ms);
|
|
else board.sleep(0); // nrf ignores seconds param, sleeps whenever possible
|
|
}
|
|
#else
|
|
if (sleep_ms > 0 && the_mesh.millisHasNowPassed(POWERSAVING_FIRSTSLEEP_SECS * 1000)) { // To check if it is time to sleep
|
|
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); // Wake for scheduled jobs or a LoRa packet.
|
|
} 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) {
|
|
// Live radio2 needs the scanner's zero-delay polling cadence.
|
|
delay(1); // Keep USB and the button wake interval serviced while idle.
|
|
}
|
|
#endif
|
|
}
|