fix: run FTP server in dedicated FreeRTOS task to unblock LVGL

Move ftpSrv.handleFTP() from Arduino loop() into a dedicated FreeRTOS
task pinned to core 0, leaving core 1 free for LVGL display updates.

FTP callbacks now only set volatile flags (ftp_ui_dirty, ftp_sd_dirty)
which the main loop polls to safely update UI and NeoPixel from core 1.
This eliminates the 20-second PASV data connection timeouts caused by
handleFTP() competing with display.update() for CPU time.

Also fix pre-existing BLACK constant build error in display_manager.cpp.
This commit is contained in:
Lago
2026-04-03 22:14:28 +02:00
parent 5290f8b4f0
commit 58494cd0a7
3 changed files with 55 additions and 18 deletions
+1
View File
@@ -1,5 +1,6 @@
{
"recommendations": [
"Jason2866.esp-decoder",
"pioarduino.pioarduino-ide"
],
"unwantedRecommendations": [
+1 -1
View File
@@ -51,7 +51,7 @@ bool DisplayManager::init() {
if (pct > 100) pct = 100;
gfx->setBrightness((uint8_t)(((uint16_t)pct * 255 + 50) / 100));
}
gfx->fillScreen(BLACK);
gfx->fillScreen((uint16_t)0x0000);
Serial.println("[DISP] Panel OK");
/* ── LVGL 9 init ───────────────────────────────────────────── */
+53 -17
View File
@@ -72,6 +72,12 @@ static bool pmic_ok = false;
static unsigned long last_time_update = 0;
static unsigned long last_batt_update = 0;
// FTP task → main loop cross-core signalling (set by FTP callbacks, consumed in loop)
enum FtpUiState : uint8_t { FTP_ST_IDLE, FTP_ST_CONNECTED, FTP_ST_TRANSFERRING };
static volatile FtpUiState ftp_ui_state = FTP_ST_IDLE;
static volatile bool ftp_ui_dirty = false; // UI label + LED need refresh
static volatile bool ftp_sd_dirty = false; // SD usage info needs refresh
// ---------------------------------------------------------------------------
// NeoPixel helpers
// ---------------------------------------------------------------------------
@@ -141,6 +147,7 @@ static void updateSdInfo() {
/**
* Called on FTP connect / disconnect events.
* NOTE: Runs on the FTP task (core 0) — only set flags here, never call LVGL/UI.
*/
void ftpCallback(FtpOperation ftpOperation, uint32_t freeSpace, uint32_t totalSpace) {
switch (ftpOperation) {
@@ -148,57 +155,62 @@ void ftpCallback(FtpOperation ftpOperation, uint32_t freeSpace, uint32_t totalSp
Serial.println("[FTP] Client connected");
ftp_client_on = true;
ftp_transfer = false;
ui_set_ftp("Connected");
ftp_ui_state = FTP_ST_CONNECTED;
ftp_ui_dirty = true;
break;
case FTP_DISCONNECT:
Serial.println("[FTP] Client disconnected");
ftp_client_on = false;
ftp_transfer = false;
ui_set_ftp("Idle");
ftp_ui_state = FTP_ST_IDLE;
ftp_ui_dirty = true;
break;
case FTP_FREE_SPACE_CHANGE:
Serial.printf("[FTP] Free space: %u / %u\n", freeSpace, totalSpace);
updateSdInfo();
ftp_sd_dirty = true;
break;
default:
break;
}
updateLed();
}
/**
* Called during file transfer operations.
* NOTE: Runs on the FTP task (core 0) — only set flags here, never call LVGL/UI.
*/
void ftpTransferCallback(FtpTransferOperation ftpOperation, const char *name, uint32_t transferredSize) {
switch (ftpOperation) {
case FTP_UPLOAD_START:
Serial.printf("[FTP] Upload start: %s\n", name);
ftp_transfer = true;
ui_set_ftp("Transferring");
ftp_transfer = true;
ftp_ui_state = FTP_ST_TRANSFERRING;
ftp_ui_dirty = true;
break;
case FTP_DOWNLOAD_START:
Serial.printf("[FTP] Download start: %s\n", name);
ftp_transfer = true;
ui_set_ftp("Transferring");
ftp_transfer = true;
ftp_ui_state = FTP_ST_TRANSFERRING;
ftp_ui_dirty = true;
break;
case FTP_UPLOAD:
case FTP_DOWNLOAD:
break; // Ongoing — silent to avoid serial flood
case FTP_TRANSFER_STOP:
Serial.printf("[FTP] Transfer done: %s (%u bytes)\n", name, transferredSize);
ftp_transfer = false;
ui_set_ftp(ftp_client_on ? "Connected" : "Idle");
updateSdInfo();
ftp_transfer = false;
ftp_ui_state = ftp_client_on ? FTP_ST_CONNECTED : FTP_ST_IDLE;
ftp_ui_dirty = true;
ftp_sd_dirty = true;
break;
case FTP_TRANSFER_ERROR:
Serial.printf("[FTP] Transfer error: %s\n", name);
ftp_transfer = false;
ui_set_ftp(ftp_client_on ? "Connected" : "Idle");
ftp_transfer = false;
ftp_ui_state = ftp_client_on ? FTP_ST_CONNECTED : FTP_ST_IDLE;
ftp_ui_dirty = true;
break;
default:
break;
}
updateLed();
}
// ---------------------------------------------------------------------------
@@ -438,7 +450,16 @@ static void initNtp() {
Serial.println("[NTP] Warning: time not yet synced (will update in background)");
}
/** Start FTP server with callbacks */
/** FTP task — runs handleFTP() in a dedicated loop on core 0 */
static void ftpTask(void *param) {
(void)param;
for (;;) {
ftpSrv.handleFTP();
vTaskDelay(pdMS_TO_TICKS(1)); // yield ~1 ms between iterations
}
}
/** Start FTP server with callbacks and launch dedicated task */
static void initFtp() {
Serial.println("[FTP] Starting FTP server (user: kode, port: 21)...");
ftpSrv.setCallback(ftpCallback);
@@ -446,7 +467,11 @@ static void initFtp() {
/* Set local IP so PASV mode sends the correct address to clients */
ftpSrv.setLocalIp(WiFi.localIP());
ftpSrv.begin("kode", "kode");
Serial.printf("[FTP] FTP server ready (PASV IP: %s, data port: 50009)\n",
/* Run FTP in its own task on core 0 so it never blocks LVGL on core 1 */
xTaskCreatePinnedToCore(ftpTask, "ftp", 8192, NULL, 1, NULL, 0);
Serial.printf("[FTP] FTP server ready on core 0 (PASV IP: %s, data port: 50009)\n",
WiFi.localIP().toString().c_str());
ui_set_ftp("Idle");
}
@@ -543,7 +568,18 @@ void setup() {
// ---------------------------------------------------------------------------
void loop() {
ftpSrv.handleFTP();
/* Process FTP status flags (set by callbacks on core 0, consumed here on core 1) */
if (ftp_ui_dirty) {
ftp_ui_dirty = false;
static const char *ftp_st_str[] = {"Idle", "Connected", "Transferring"};
ui_set_ftp(ftp_st_str[ftp_ui_state]);
updateLed();
}
if (ftp_sd_dirty) {
ftp_sd_dirty = false;
updateSdInfo();
}
display.update();
updateTimeDisplay();
updateBattery();