diff --git a/examples/companion_radio/MyMesh.h b/examples/companion_radio/MyMesh.h index 47e1317..6b9e0e2 100644 --- a/examples/companion_radio/MyMesh.h +++ b/examples/companion_radio/MyMesh.h @@ -8,11 +8,11 @@ #define FIRMWARE_VER_CODE 10 #ifndef FIRMWARE_BUILD_DATE -#define FIRMWARE_BUILD_DATE "15 March 2026" +#define FIRMWARE_BUILD_DATE "17 March 2026" #endif #ifndef FIRMWARE_VERSION -#define FIRMWARE_VERSION "Meck v1.1" +#define FIRMWARE_VERSION "Meck v1.2" #endif #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) diff --git a/examples/companion_radio/main.cpp b/examples/companion_radio/main.cpp index 446839b..c7c261d 100644 --- a/examples/companion_radio/main.cpp +++ b/examples/companion_radio/main.cpp @@ -1122,9 +1122,10 @@ void setup() { if (ssid.length() > 0) { MESH_DEBUG_PRINTLN("setup() - WiFi: connecting to '%s'", ssid.c_str()); WiFi.begin(ssid.c_str(), pass.c_str()); - unsigned long timeout = millis() + 8000; + unsigned long timeout = millis() + 3000; // 3s max — non-critical, Settings can retry while (WiFi.status() != WL_CONNECTED && millis() < timeout) { - delay(100); + yield(); // Feed WDT during wait + delay(50); } if (WiFi.status() == WL_CONNECTED) { Serial.printf("WiFi companion: connected to %s, IP: %s\n", @@ -1415,20 +1416,20 @@ void loop() { sensors.loop(); - // GPS diagnostic — print sentence count every 30s so we can tell if Serial2 is receiving data - #if HAS_GPS - { - static unsigned long lastGpsDiag = 0; - if (millis() - lastGpsDiag >= 30000) { - lastGpsDiag = millis(); - uint32_t sentences = gpsStream.getSentenceCount(); - uint16_t perSec = gpsStream.getSentencesPerSec(); - Serial.printf("GPS diag: %lu sentences total, %u/sec, Serial2.available=%d, lat=%.6f lon=%.6f\n", - (unsigned long)sentences, perSec, Serial2.available(), - sensors.node_lat, sensors.node_lon); - } - } - #endif + // GPS diagnostic — disabled to reduce serial noise (uncomment for debugging) + // #if HAS_GPS + // { + // static unsigned long lastGpsDiag = 0; + // if (millis() - lastGpsDiag >= 30000) { + // lastGpsDiag = millis(); + // uint32_t sentences = gpsStream.getSentenceCount(); + // uint16_t perSec = gpsStream.getSentencesPerSec(); + // Serial.printf("GPS diag: %lu sentences total, %u/sec, Serial2.available=%d, lat=%.6f lon=%.6f\n", + // (unsigned long)sentences, perSec, Serial2.available(), + // sensors.node_lat, sensors.node_lon); + // } + // } + // #endif // Map screen: periodically update own GPS position and contact markers #if HAS_GPS @@ -2492,6 +2493,7 @@ void handleKeyboardInput() { ui_task.injectKey('g'); // Re-center on GPS } else { Serial.println("Opening map"); + cpuPower.setBoost(); // Map render is CPU-intensive (PNG decode + SD reads) { MapScreen* ms = (MapScreen*)ui_task.getMapScreen(); if (ms) { @@ -2509,7 +2511,7 @@ void handleKeyboardInput() { double lon = ((double)ci.gps_lon) / 1000000.0; ms->addMarker(lat, lon, ci.name, ci.type); markerCount++; - Serial.printf(" marker: %s @ %.4f,%.4f (type=%d)\n", ci.name, lat, lon, ci.type); + // Serial.printf(" marker: %s @ %.4f,%.4f (type=%d)\n", ci.name, lat, lon, ci.type); } } Serial.printf("MapScreen: %d contacts with GPS position\n", markerCount); diff --git a/examples/companion_radio/ui-new/Mapscreen.h b/examples/companion_radio/ui-new/Mapscreen.h index 5eb0716..0b1b3cb 100644 --- a/examples/companion_radio/ui-new/Mapscreen.h +++ b/examples/companion_radio/ui-new/Mapscreen.h @@ -137,22 +137,18 @@ public: _zoomMin(MAP_MIN_ZOOM), _zoomMax(MAP_MAX_ZOOM), _pngBuf(nullptr), + _lineBuf(nullptr), _tileFound(false) { - // Allocate marker array in PSRAM at construction (~20KB) - // so addMarker() works before enter() is called - _markers = (MapMarker*)ps_calloc(MAP_MAX_MARKERS, sizeof(MapMarker)); - if (_markers) { - Serial.printf("MapScreen: markers allocated (%d × %d = %d bytes PSRAM)\n", - MAP_MAX_MARKERS, (int)sizeof(MapMarker), - MAP_MAX_MARKERS * (int)sizeof(MapMarker)); - } else { - Serial.println("MapScreen: marker PSRAM alloc FAILED"); - } + // Marker array and PNG buffers are deferred to enter() to avoid + // consuming 20KB+ PSRAM at boot when the map may never be opened. + _markers = nullptr; + _numMarkers = 0; } ~MapScreen() { if (_pngBuf) { free(_pngBuf); _pngBuf = nullptr; } + if (_lineBuf) { free(_lineBuf); _lineBuf = nullptr; } if (_markers) { free(_markers); _markers = nullptr; } } @@ -184,7 +180,12 @@ public: // Add a location marker (call once per contact before entering map) void clearMarkers() { _numMarkers = 0; } void addMarker(double lat, double lon, const char* name = "", uint8_t type = 0) { - if (!_markers || _numMarkers >= MAP_MAX_MARKERS) return; + // Lazy-allocate markers on first use (deferred from constructor) + if (!_markers) { + _markers = (MapMarker*)ps_calloc(MAP_MAX_MARKERS, sizeof(MapMarker)); + if (!_markers) return; // Alloc failed — skip silently + } + if (_numMarkers >= MAP_MAX_MARKERS) return; if (lat == 0.0 && lon == 0.0) return; // Skip no-location contacts _markers[_numMarkers].lat = lat; _markers[_numMarkers].lon = lon; @@ -203,6 +204,18 @@ public: _einkDisplay = static_cast(&display); _needsRedraw = true; + // Allocate marker array in PSRAM on first use (~20KB) + if (!_markers) { + _markers = (MapMarker*)ps_calloc(MAP_MAX_MARKERS, sizeof(MapMarker)); + if (_markers) { + Serial.printf("MapScreen: markers allocated (%d × %d = %d bytes PSRAM)\n", + MAP_MAX_MARKERS, (int)sizeof(MapMarker), + MAP_MAX_MARKERS * (int)sizeof(MapMarker)); + } else { + Serial.println("MapScreen: marker PSRAM alloc FAILED"); + } + } + // Allocate PNG read buffer in PSRAM on first use if (!_pngBuf) { _pngBuf = (uint8_t*)ps_malloc(MAP_PNG_BUF_SIZE); @@ -217,6 +230,20 @@ public: } } + // Allocate scanline decode buffer in PSRAM (512 bytes — avoids stack + // allocation inside the PNGdec callback which is called 256× per tile) + if (!_lineBuf) { + _lineBuf = (uint16_t*)ps_malloc(MAP_TILE_SIZE * sizeof(uint16_t)); + if (!_lineBuf) { + _lineBuf = (uint16_t*)malloc(MAP_TILE_SIZE * sizeof(uint16_t)); + } + if (_lineBuf) { + Serial.println("MapScreen: lineBuf allocated"); + } else { + Serial.println("MapScreen: lineBuf alloc FAILED"); + } + } + // Detect available zoom levels from SD card directories detectZoomRange(); } @@ -356,6 +383,7 @@ private: // PNG decode buffer (PSRAM) uint8_t* _pngBuf; + uint16_t* _lineBuf; // Scanline RGB565 buffer for PNG decode (PSRAM) bool _tileFound; // Did last tile load succeed? // PNGdec instance @@ -381,6 +409,7 @@ private: int offsetY; // Screen Y offset for this tile int viewportY; // Top of viewport (MAP_VIEWPORT_Y) int viewportH; // Height of viewport (MAP_VIEWPORT_H) + uint16_t* lineBuf; // Scanline decode buffer (PSRAM-allocated, avoids 512B stack usage per callback) }; DrawContext _drawCtx; @@ -487,7 +516,7 @@ private: // Load a PNG tile from SD and decode it directly to the display // screenX, screenY = top-left corner on display where this tile goes bool loadAndRenderTile(int tileX, int tileY, int screenX, int screenY) { - if (!_pngBuf || !_einkDisplay) return false; + if (!_pngBuf || !_lineBuf || !_einkDisplay) return false; char path[64]; buildTilePath(path, sizeof(path), _zoom, tileX, tileY); @@ -521,6 +550,7 @@ private: _drawCtx.offsetY = screenY; _drawCtx.viewportY = MAP_VIEWPORT_Y; _drawCtx.viewportH = MAP_VIEWPORT_H; + _drawCtx.lineBuf = _lineBuf; // Open PNG from memory buffer int rc = _png.openRAM(_pngBuf, fileSize, pngDrawCallback); @@ -547,7 +577,7 @@ private: // Uses getLineAsRGB565 with correct (little) endianness for ESP32. static int pngDrawCallback(PNGDRAW* pDraw) { DrawContext* ctx = (DrawContext*)pDraw->pUser; - if (!ctx || !ctx->display || !ctx->png) return 0; + if (!ctx || !ctx->display || !ctx->png || !ctx->lineBuf) return 0; int screenY = ctx->offsetY + pDraw->y; @@ -564,9 +594,8 @@ private: } uint16_t lineWidth = pDraw->iWidth; - uint16_t lineBuf[MAP_TILE_SIZE]; if (lineWidth > MAP_TILE_SIZE) lineWidth = MAP_TILE_SIZE; - ctx->png->getLineAsRGB565(pDraw, lineBuf, PNG_RGB565_LITTLE_ENDIAN, 0xFFFFFFFF); + ctx->png->getLineAsRGB565(pDraw, ctx->lineBuf, PNG_RGB565_LITTLE_ENDIAN, 0xFFFFFFFF); for (int x = 0; x < lineWidth; x++) { int screenX = ctx->offsetX + x; @@ -574,7 +603,7 @@ private: // RGB565 little-endian on ESP32: standard bit layout // R[15:11] G[10:5] B[4:0] - uint16_t pixel = lineBuf[x]; + uint16_t pixel = ctx->lineBuf[x]; // For B&W tiles this is 0x0000 (black) or 0xFFFF (white) // Simple threshold on full 16-bit value handles both cleanly @@ -639,6 +668,7 @@ private: } else { missing++; } + yield(); // Feed WDT between tiles — each tile can take 1-2s at 80MHz } }