Fix tile download TLS memory and map rendering performance

- Route mbedtls allocations to PSRAM via mbedtls_platform_set_calloc_free()
  to fix TLS handshake failure (-32512 SSL alloc) with only ~36KB internal heap
- Use WiFiClientSecure with setInsecure() for HTTPS tile downloads from OSM
- Move tile downloads to background FreeRTOS task to avoid blocking UI thread
- Add incremental tile loading (one PNG decode per update cycle) to prevent
  LVGL mutex timeout from decoding 4 tiles synchronously
- Enable LVGL image cache (LV_IMG_CACHE_DEF_SIZE=8) so decoded PNGs stay
  in PSRAM and don't re-decode on every redraw
- Add touch drag panning for map navigation via LV_EVENT_PRESSING

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
torlando-tech
2026-03-05 19:08:28 -05:00
co-authored by Claude Opus 4.6
parent 1a89466b66
commit 0d3e1bae30
4 changed files with 146 additions and 13 deletions
+1 -1
View File
@@ -54,7 +54,7 @@
#define LV_DRAW_COMPLEX 1
#define LV_SHADOW_CACHE_SIZE 0
#define LV_CIRCLE_CACHE_SIZE 4
#define LV_IMG_CACHE_DEF_SIZE 0
#define LV_IMG_CACHE_DEF_SIZE 8
#define LV_GRADIENT_MAX_STOPS 2
#define LV_GRAD_CACHE_DEF_SIZE 0
#define LV_DITHER_GRADIENT 0
+37 -4
View File
@@ -8,8 +8,30 @@
#include "SDAccess.h"
#include <SD.h>
#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <HTTPClient.h>
#include <Log.h>
#include <mbedtls/platform.h>
#include <esp_heap_caps.h>
// ESP-IDF's default mbedtls allocator only uses internal SRAM (~36KB free),
// which is too small for TLS handshake (~40-50KB). Redirect to PSRAM.
// ESP32-S3's unified cache makes PSRAM safe for crypto operations.
static void* psram_calloc(size_t n, size_t size) {
void* ptr = heap_caps_calloc(n, size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
if (!ptr) {
ptr = calloc(n, size); // Fallback to default allocator
}
return ptr;
}
static bool _tls_allocator_set = false;
static void ensure_psram_tls() {
if (_tls_allocator_set) return;
mbedtls_platform_set_calloc_free(psram_calloc, free);
_tls_allocator_set = true;
}
namespace Hardware {
namespace TDeck {
@@ -61,26 +83,37 @@ bool TileDownloader::create_tile_dirs(int z, int x) {
}
bool TileDownloader::download_tile(int z, int x, int y) {
char log_buf[128];
if (WiFi.status() != WL_CONNECTED) {
WARNING("TileDownloader: WiFi not connected, skipping download");
return false;
}
if (!SDAccess::is_ready()) {
WARNING("TileDownloader: SD card not ready, skipping download");
return false;
}
String url = build_url(z, x, y);
String sd_path = build_sd_path(z, x, y);
char log_buf[128];
snprintf(log_buf, sizeof(log_buf), "TileDownloader: Fetching tile %d/%d/%d", z, x, y);
DEBUG(log_buf);
snprintf(log_buf, sizeof(log_buf), "TileDownloader: Downloading tile %d/%d/%d", z, x, y);
INFO(log_buf);
// Route mbedtls allocations to PSRAM (one-time, persists for all future TLS)
ensure_psram_tls();
WiFiClientSecure secureClient;
secureClient.setInsecure();
secureClient.setHandshakeTimeout(10);
HTTPClient http;
http.setUserAgent("Pyxis/1.0 (ESP32; LXMF messenger)");
http.setTimeout(10000);
http.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
if (!http.begin(url)) {
if (!http.begin(secureClient, url)) {
WARNING("TileDownloader: Failed to begin HTTP connection");
return false;
}
+84 -8
View File
@@ -30,6 +30,16 @@ MapScreen::MapScreen(lv_obj_t* parent)
memset(_peer_labels, 0, sizeof(_peer_labels));
memset(_loaded_tile_x, -1, sizeof(_loaded_tile_x));
memset(_loaded_tile_y, -1, sizeof(_loaded_tile_y));
memset(_pending_tiles, 0, sizeof(_pending_tiles));
_pending_count = 0;
_last_touch_x = 0;
_last_touch_y = 0;
// Create download queue and background task
_download_queue = xQueueCreate(8, sizeof(TileRequest));
_download_complete = false;
_download_task = nullptr;
xTaskCreatePinnedToCore(download_task_func, "tile_dl", 16384, this, 1, &_download_task, 0);
LVGL_LOCK();
@@ -150,6 +160,10 @@ void MapScreen::create_viewport() {
// Register key event on viewport for pan/zoom
lv_obj_add_flag(_viewport, LV_OBJ_FLAG_CLICKABLE);
lv_obj_add_event_cb(_viewport, on_key_event, LV_EVENT_KEY, this);
// Touch drag for panning
lv_obj_add_event_cb(_viewport, on_touch_event, LV_EVENT_PRESSED, this);
lv_obj_add_event_cb(_viewport, on_touch_event, LV_EVENT_PRESSING, this);
}
void MapScreen::show() {
@@ -186,6 +200,23 @@ void MapScreen::hide() {
}
void MapScreen::update_gps_position() {
// Reload tiles after background downloads complete
if (_download_complete) {
_download_complete = false;
_loaded_zoom = -1; // Force tile reload
update_tiles();
}
// Incremental tile loading: process one pending tile per cycle
// This prevents LVGL mutex timeout from decoding multiple PNGs at once
if (_pending_count > 0) {
PendingTile& pt = _pending_tiles[_pending_count - 1];
load_tile(pt.slot, pt.x, pt.y, pt.z);
_loaded_tile_x[pt.slot] = pt.x;
_loaded_tile_y[pt.slot] = pt.y;
_pending_count--;
}
if (!_gps) return;
if (_gps->location.isValid()) {
@@ -261,6 +292,8 @@ void MapScreen::update_tiles() {
{base_tile_x + 1, base_tile_y + 1}
};
_pending_count = 0;
for (int i = 0; i < 4; i++) {
int col = i % 2;
int row = i / 2;
@@ -272,9 +305,22 @@ void MapScreen::update_tiles() {
if (_loaded_zoom != _zoom ||
_loaded_tile_x[i] != tile_coords[i][0] ||
_loaded_tile_y[i] != tile_coords[i][1]) {
load_tile(i, tile_coords[i][0], tile_coords[i][1], _zoom);
_loaded_tile_x[i] = tile_coords[i][0];
_loaded_tile_y[i] = tile_coords[i][1];
int tx = tile_coords[i][0];
int ty = tile_coords[i][1];
if (Hardware::TDeck::TileDownloader::tile_exists(_zoom, tx, ty)) {
// Tile on SD — load immediately (fast with LVGL image cache)
load_tile(i, tx, ty, _zoom);
_loaded_tile_x[i] = tx;
_loaded_tile_y[i] = ty;
} else {
// Queue download + deferred load
_pending_tiles[_pending_count++] = {i, _zoom, tx, ty};
TileRequest req = {_zoom, tx, ty};
xQueueSend(_download_queue, &req, 0);
lv_img_set_src(_tile_imgs[i], "");
}
}
}
_loaded_zoom = _zoom;
@@ -341,16 +387,46 @@ void MapScreen::pan(int dx, int dy) {
}
void MapScreen::load_tile(int slot, int tile_x, int tile_y, int z) {
// Ensure tile is on SD (download if missing and WiFi available)
Hardware::TDeck::TileDownloader::ensure_tile(z, tile_x, tile_y);
// Build LVGL FS path: "S:tiles/{z}/{x}/{y}.png"
char path[64];
snprintf(path, sizeof(path), "S:tiles/%d/%d/%d.png", z, tile_x, tile_y);
lv_img_set_src(_tile_imgs[slot], path);
}
void MapScreen::download_task_func(void* param) {
MapScreen* self = (MapScreen*)param;
TileRequest req;
while (true) {
if (xQueueReceive(self->_download_queue, &req, portMAX_DELAY) == pdTRUE) {
if (Hardware::TDeck::TileDownloader::download_tile(req.z, req.x, req.y)) {
self->_download_complete = true;
}
}
}
}
void MapScreen::on_touch_event(lv_event_t* event) {
MapScreen* screen = (MapScreen*)lv_event_get_user_data(event);
lv_event_code_t code = lv_event_get_code(event);
lv_indev_t* indev = lv_indev_get_act();
if (!indev) return;
lv_point_t point;
lv_indev_get_point(indev, &point);
if (code == LV_EVENT_PRESSED) {
screen->_last_touch_x = point.x;
screen->_last_touch_y = point.y;
} else if (code == LV_EVENT_PRESSING) {
int dx = screen->_last_touch_x - point.x;
int dy = screen->_last_touch_y - point.y;
if (dx != 0 || dy != 0) {
screen->pan(dx, dy);
screen->_last_touch_x = point.x;
screen->_last_touch_y = point.y;
}
}
}
void MapScreen::on_back_clicked(lv_event_t* event) {
MapScreen* screen = (MapScreen*)lv_event_get_user_data(event);
if (screen->_back_callback) {
+24
View File
@@ -8,6 +8,8 @@
#include <Arduino.h>
#include <lvgl.h>
#include <functional>
#include <freertos/FreeRTOS.h>
#include <freertos/queue.h>
#include "Bytes.h"
class TinyGPSPlus;
@@ -107,11 +109,33 @@ private:
// Load a tile image into one of the 4 slots
void load_tile(int slot, int tile_x, int tile_y, int z);
// Touch drag state
int _last_touch_x;
int _last_touch_y;
// Incremental tile loading — one tile per update cycle to avoid LVGL mutex timeout
struct PendingTile {
int slot, z, x, y;
};
PendingTile _pending_tiles[4];
int _pending_count;
// Async tile downloading
struct TileRequest {
int z, x, y;
};
QueueHandle_t _download_queue;
TaskHandle_t _download_task;
volatile bool _download_complete;
static void download_task_func(void* param);
// Event handlers
static void on_back_clicked(lv_event_t* event);
static void on_zoom_in_clicked(lv_event_t* event);
static void on_zoom_out_clicked(lv_event_t* event);
static void on_key_event(lv_event_t* event);
static void on_touch_event(lv_event_t* event);
};
} // namespace LXMF