diff --git a/src/heaplog.h b/src/heaplog.h new file mode 100644 index 0000000..87f25d4 --- /dev/null +++ b/src/heaplog.h @@ -0,0 +1,17 @@ +#pragma once + +#include +#include + +// WiFi and the BLE controller need DMA-capable internal RAM, which the default +// heap figures do not track - a healthy largest-free-block can sit in a region +// esp_wifi_init() cannot use, which reads as NO_MEM with plenty "free". +inline void logHeap(const char *tag) { + Serial.printf("[heap] %-10s def=%u/%u internal=%u/%u dma=%u/%u\n", tag, + (unsigned)heap_caps_get_free_size(MALLOC_CAP_DEFAULT), + (unsigned)heap_caps_get_largest_free_block(MALLOC_CAP_DEFAULT), + (unsigned)heap_caps_get_free_size(MALLOC_CAP_INTERNAL), + (unsigned)heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL), + (unsigned)heap_caps_get_free_size(MALLOC_CAP_DMA), + (unsigned)heap_caps_get_largest_free_block(MALLOC_CAP_DMA)); +} diff --git a/src/host/providers_net.cpp b/src/host/providers_net.cpp index ffc3b8b..3ba4e7e 100644 --- a/src/host/providers_net.cpp +++ b/src/host/providers_net.cpp @@ -1,6 +1,8 @@ #include #include #include + +#include "../heaplog.h" #include #include #include @@ -201,40 +203,52 @@ Status Wifi::connect(const std::string *ssid, const std::string *password) { if (settings.wifiSsid.isEmpty()) return Status::failure("no saved network"); + bleShutdown(); + logHeap("pre-connect"); WiFi.mode(WIFI_STA); - WiFi.begin(settings.wifiSsid.c_str(), settings.wifiPassword.c_str()); + if (WiFi.begin(settings.wifiSsid.c_str(), settings.wifiPassword.c_str()) == + WL_CONNECT_FAILED) + return Status::failure("wifi radio unavailable, reboot to use wifi"); return Status::success(); } esp32lua::WifiStatus Wifi::status() const { esp32lua::WifiStatus status; - status.ssid = WiFi.SSID().c_str(); - status.ip = WiFi.localIP().toString().c_str(); - status.rssi = WiFi.RSSI(); - switch (WiFi.status()) { - case WL_CONNECTED: - status.state = "connected"; - break; - case WL_IDLE_STATUS: - case WL_DISCONNECTED: - status.state = settings.wifiSsid.isEmpty() ? "disconnected" : "connecting"; - break; - case WL_NO_SSID_AVAIL: - status.state = "not_found"; - break; - default: - status.state = "failed"; - break; + if (WiFi.getMode() == WIFI_MODE_NULL) { + // Touching SSID/IP/RSSI here would re-init the powered-off driver. + status.ssid = settings.wifiSsid.c_str(); + status.state = "disconnected"; + } else { + status.ssid = WiFi.SSID().c_str(); + if (status.ssid.empty()) + status.ssid = settings.wifiSsid.c_str(); + status.ip = WiFi.localIP().toString().c_str(); + status.rssi = WiFi.RSSI(); + switch (WiFi.status()) { + case WL_CONNECTED: + status.state = "connected"; + break; + case WL_IDLE_STATUS: + case WL_DISCONNECTED: + status.state = settings.wifiSsid.isEmpty() ? "disconnected" : "connecting"; + break; + case WL_NO_SSID_AVAIL: + status.state = "not_found"; + break; + default: + status.state = "failed"; + break; + } } if (status.state != "connected") status.ip = "0.0.0.0"; return status; } -void Wifi::disconnect() { WiFi.disconnect(); } +void Wifi::disconnect() { WiFi.disconnect(false); } Status Wifi::forget() { - WiFi.disconnect(true); + WiFi.disconnect(false, true); settings.wifiSsid = ""; settings.wifiPassword = ""; return settings.save() ? Status::success() diff --git a/src/main.cpp b/src/main.cpp index 82d5673..58c3b87 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -7,6 +7,9 @@ #include #include +#include + +#include "heaplog.h" #include "host/lua_host.h" #include "net.h" #include "settings.h" @@ -42,6 +45,16 @@ static void fallbackScreen(const char *message) { void setup() { Serial.begin(115200); + + // A failed new otherwise unwinds to terminate() and a bare abort backtrace. + // Nothing can be freed at that point, so this only buys a legible cause. + std::set_new_handler([]() { + logHeap("oom"); + Serial.println("[fatal] out of memory"); + Serial.flush(); + ESP.restart(); + }); + tft.begin(); tft.setRotation(0); tft.fillScreen(TFT_WHITE); @@ -58,6 +71,7 @@ void setup() { net::begin(); if (!host.begin()) fallbackScreen("home failed to start"); + net::startWifi(); } void loop() { diff --git a/src/net.cpp b/src/net.cpp index 23d8259..7c49245 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -4,12 +4,14 @@ #include #include +#include "heaplog.h" #include "settings.h" namespace { bool synced = false; bool wasConnected = false; +volatile bool wifiShutdownPending = false; // The device cannot be running before its own firmware was compiled, so the // build timestamp is a safe floor. Certificate validity checks fail against @@ -33,7 +35,9 @@ time_t buildTime() { // Latched, because sntp_get_sync_status() reports COMPLETED only briefly before // resetting to wait for the next cycle; polling it would flap. void onTimeSync(struct timeval *) { + const bool first = !synced; synced = true; + if (first) wifiShutdownPending = true; Serial.printf("[net] clock synced: %lu\n", (unsigned long)time(nullptr)); } @@ -55,13 +59,33 @@ void net::begin() { sntp_set_time_sync_notification_cb(onTimeSync); WiFi.persistent(false); - if (settings.wifiSsid.length()) { - WiFi.mode(WIFI_STA); - WiFi.begin(settings.wifiSsid.c_str(), settings.wifiPassword.c_str()); - } +} + +// Deferred until the Lua runtime has allocated, so the driver's first init +// faces the same heap shape as every later one. Starting it against a pristine +// boot heap wins blocks that cannot be reassembled once Lua is resident. +void net::startWifi() { + if (settings.wifiSsid.isEmpty()) + return; + logHeap("pre-wifi"); + WiFi.mode(WIFI_STA); + WiFi.begin(settings.wifiSsid.c_str(), settings.wifiPassword.c_str()); + logHeap("wifi-start"); } void net::loop() { + if (wifiShutdownPending) { + wifiShutdownPending = false; + esp_sntp_stop(); + WiFi.mode(WIFI_OFF); + wasConnected = false; + logHeap("wifi-off"); + return; + } + + if (WiFi.getMode() == WIFI_MODE_NULL) + return; + bool connected = WiFi.status() == WL_CONNECTED; if (connected == wasConnected) return; diff --git a/src/net.h b/src/net.h index c96ea5b..85a9e78 100644 --- a/src/net.h +++ b/src/net.h @@ -7,6 +7,7 @@ namespace net { void begin(); +void startWifi(); void loop(); // Pushes settings.timezone into libc, so os.date() in Lua reports local time.