Merge branch 'main' into feat/tdeck-pro-target

# Conflicts:
#	deploy/apps/lang/bg.lang
#	deploy/apps/lang/de.lang
#	deploy/apps/lang/el.lang
#	deploy/apps/lang/es.lang
#	deploy/apps/lang/fr.lang
#	deploy/apps/lang/hu.lang
#	deploy/apps/lang/it.lang
#	deploy/apps/lang/nl.lang
#	deploy/apps/lang/pt-br.lang
#	deploy/apps/lang/ro.lang
#	deploy/apps/lang/ru.lang
#	deploy/apps/lang/sr.lang
#	deploy/apps/lang/uk.lang
#	src/ui-touch/UITask.cpp
This commit is contained in:
Michael A. Cojocari committed 2026-09-08 15:41:45 -04:00
commit f3136ea951
45 files changed
+4048 -526

No files matched your search

+42 -21
View File
@@ -1435,6 +1435,36 @@ void setup() {
* contiguous DMA block, so let it claim memory before BLE. (Association
* happens later in loop(); this just inits the stack.) */
if (want_wifi) {
// Register every Wi-Fi event handler BEFORE the stack is started, never
// after. WiFi.onEvent() push_backs into a std::vector that the Arduino core
// iterates from its own event task, with no lock on either side: a
// registration that reallocates the vector while an event is being
// dispatched frees the buffer the event task is walking, and it then calls
// through a dangling function pointer. That is a real crash we have a dump
// of, on a Heltec V4-R8 on beta_77: the event task was inside
// _eventCallback copying a WiFiEventCbList entry while setup() was inside
// _M_realloc_insert growing that same vector, and it jumped to a garbage PC.
//
// The bug is in the Arduino core rather than here, and we cannot lock its
// vector, but we can make the race impossible: with nothing registered
// after the stack can emit events, the vector is never mutated while it is
// being read. Both handlers used to be added afterwards, one of them after
// WiFi.begin(), which is where the window came from.
//
// Records WHY every STA disconnect happened, surfaced by the UI's Wi-Fi
// status string as "auth failed (rNN)", so failures are diagnosable on a
// device with no serial console.
WiFi.onEvent([](WiFiEvent_t, WiFiEventInfo_t info){
g_wifi_last_disc_reason = info.wifi_sta_disconnected.reason;
}, ARDUINO_EVENT_WIFI_STA_DISCONNECTED);
// Without the WIFI_DEBUG_PRINTLN tracing the old copy had: that macro comes
// from SerialWifiInterface.h, which is not in scope this early in setup().
// The prints were debug-only and compile to nothing in a release build; the
// flag is the handler's actual job and is unchanged.
WiFi.onEvent([](WiFiEvent_t event, WiFiEventInfo_t){
if (event == ARDUINO_EVENT_WIFI_STA_DISCONNECTED) wifi_needs_reconnect = true;
else if (event == ARDUINO_EVENT_WIFI_STA_GOT_IP) wifi_needs_reconnect = false;
});
const bool wifi_mode_ready = WiFi.mode(WIFI_STA);
#if defined(TLORA_PAGER)
if (!wifi_mode_ready) {
@@ -1448,12 +1478,6 @@ void setup() {
// exists so a later WPA re-auth cannot bypass Wi-Fi-first ordering
#endif
WiFi.persistent(false);
// Record WHY every STA disconnect happened — surfaced by the UI's Wi-Fi
// status string as "auth failed (rNN)" so failures are diagnosable on a
// device with no serial console. Additive: multiple onEvent handlers coexist.
WiFi.onEvent([](WiFiEvent_t, WiFiEventInfo_t info){
g_wifi_last_disc_reason = info.wifi_sta_disconnected.reason;
}, ARDUINO_EVENT_WIFI_STA_DISCONNECTED);
// NOTE: do NOT enable modem-sleep here. On a fresh, *unassociated* STA (the
// setup wizard, no creds yet) DTIM modem-sleep naps the radio through the
// scan dwell, so WiFi.scanNetworks() comes back empty ("no networks found").
@@ -1562,16 +1586,6 @@ void setup() {
board.setInhibitSleep(true); // prevent sleep when WiFi is active
WiFi.setAutoReconnect(true);
WiFi.onEvent([](WiFiEvent_t event, WiFiEventInfo_t info){
if (event == ARDUINO_EVENT_WIFI_STA_DISCONNECTED) {
WIFI_DEBUG_PRINTLN("WiFi disconnected. Flagging for reconnect...");
wifi_needs_reconnect = true;
} else if (event == ARDUINO_EVENT_WIFI_STA_GOT_IP) {
WIFI_DEBUG_PRINTLN("WiFi connected successfully!");
wifi_needs_reconnect = false;
}
});
if (wifiConfigHasRuntime()) {
char ssid[WIFI_CONFIG_SSID_MAX];
char pwd[WIFI_CONFIG_PWD_MAX];
@@ -1782,11 +1796,18 @@ void loop() {
// Bluetooth request can never cold-start NimBLE over a hidden retry.
if (wifi_radio_en) WiFi.setAutoReconnect(false);
#endif
/* Only touch WiFi state if it was actually started this session. When
* BLE is the active transport (no creds saved), WiFi was never inited
* and calling WiFi.disconnect()/mode(WIFI_OFF) would trigger esp_wifi_init
* under low heap → crash. Setting wifi_started=false here is harmless. */
if (wifi_started) {
/* Only touch WiFi state if the driver is actually up. When BLE is the active
* transport (no creds saved), WiFi was never inited and calling
* WiFi.disconnect()/mode(WIFI_OFF) would trigger esp_wifi_init under low
* heap → crash. Setting wifi_started=false here is harmless.
*
* wifi_started alone under-approximates that: a scan or the version check can
* bring STA up without going through this state machine, and then disabling
* Wi-Fi skipped the power-down entirely and left the modem running (#450 — the
* idle power-saver then blocks on "Wi-Fi on" forever). getMode() != WIFI_OFF
* PROVES the driver is inited, which is precisely what the guard wants to know,
* so it is safe to add and it closes that leak. */
if (wifi_started || WiFi.getMode() != WIFI_OFF) {
if (!wifi_radio_en) {
WiFi.disconnect(true);
delay(50);