From be369fa260b07d178f18bb5ff6c2c24329738ecf Mon Sep 17 00:00:00 2001 From: clement Date: Thu, 18 Jun 2026 17:41:53 +0200 Subject: [PATCH] feat(plip): stage files onto SD via /game/file /game/file only wrote SPIFFS (960 KB, too small for a voice pack). A path prefixed sd/ (or /sdcard/) now routes to the microSD: the card is mounted on demand (audio_ensure_sd, independent of hook state) and the parent dir is created. MAX_BODY 256->512 KB so full say() hints fit (streamed in 2 KB chunks, RAM-safe). Enable FATFS LFN_HEAP so >8.3 filenames work (greet_0142738200.wav etc). Validated: 40-sample FR voice pack staged to /sdcard/voice on the bench PLIP (960 MB card). --- plip_voice/main/audio.c | 6 +++++ plip_voice/main/audio.h | 6 +++++ plip_voice/main/net.c | 41 +++++++++++++++++++++++++++++------ plip_voice/sdkconfig.defaults | 4 ++++ 4 files changed, 50 insertions(+), 7 deletions(-) diff --git a/plip_voice/main/audio.c b/plip_voice/main/audio.c index 62cbb9b..ea289e8 100644 --- a/plip_voice/main/audio.c +++ b/plip_voice/main/audio.c @@ -137,6 +137,12 @@ static void ensure_sd_mounted(void) } } +bool audio_ensure_sd(void) +{ + ensure_sd_mounted(); + return s_sd_mounted; +} + /* ── WAV parser ──────────────────────────────────────────────────────────── */ static esp_err_t parse_wav_header(const uint8_t *buf, size_t len, wav_info_t *out) diff --git a/plip_voice/main/audio.h b/plip_voice/main/audio.h index cc41fdb..0c62be9 100644 --- a/plip_voice/main/audio.h +++ b/plip_voice/main/audio.h @@ -39,6 +39,12 @@ esp_err_t audio_play_async(const char *path); /* Stop current playback immediately. */ esp_err_t audio_stop(void); +/* Mount the microSD card at /sdcard on demand (best-effort, idempotent) and + * report whether it is mounted. Unlike playback, this does NOT depend on the + * hook state — it lets the REST layer stage a voice pack onto the SD while the + * handset is down. Returns true if /sdcard is usable. */ +bool audio_ensure_sd(void); + /* True while the audio worker is playing a clip (tone/WAV). The conversation * LISTEN loop polls this to stay half-duplex: never capture while playing * (avoids the earpiece→mic feedback that saturated the line). */ diff --git a/plip_voice/main/net.c b/plip_voice/main/net.c index 10fe2e3..5847f33 100644 --- a/plip_voice/main/net.c +++ b/plip_voice/main/net.c @@ -22,6 +22,7 @@ #include #include #include +#include #include "audio.h" #include "tones.h" @@ -53,7 +54,8 @@ #define SPIFFS_LABEL "storage" #define SPIFFS_BASE "/spiffs" -#define MAX_BODY (256 * 1024) +#define MAX_BODY (512 * 1024) /* streamed in 2 KB chunks → RAM-safe; raised + so full-length say() hint WAVs fit on SD */ static volatile bool s_connected = false; static httpd_handle_t s_httpd = NULL; @@ -209,14 +211,39 @@ static esp_err_t handle_file_post(httpd_req_t *req) if (req->content_len <= 0 || req->content_len > MAX_BODY) return send_json(req, "413 Payload Too Large", "{\"error\":\"too large\"}"); - ensure_spiffs(); + /* Route to the microSD when the path is sd-prefixed ("sd/…", "sdcard/…" + * or "/sdcard/…"); otherwise stage in SPIFFS (default). The SD is mounted + * on demand here so a voice pack can be staged without an off-hook play. */ + const char *base; + const char *rel = fpath; + bool to_sd = false; + if (strncmp(fpath, "sd/", 3) == 0) { rel = fpath + 3; to_sd = true; } + else if (strncmp(fpath, "sdcard/", 7) == 0) { rel = fpath + 7; to_sd = true; } + else if (strncmp(fpath, "/sdcard/", 8) == 0) { rel = fpath + 8; to_sd = true; } - /* Build full SPIFFS path. */ - char full[160]; - if (fpath[0] == '/') { - snprintf(full, sizeof(full), "%s%s", SPIFFS_BASE, fpath); + if (to_sd) { + if (!audio_ensure_sd()) + return send_json(req, "503 Service Unavailable", "{\"error\":\"no sd card\"}"); + base = PLIP_SD_MOUNT; /* "/sdcard" */ } else { - snprintf(full, sizeof(full), "%s/%s", SPIFFS_BASE, fpath); + ensure_spiffs(); + base = SPIFFS_BASE; + } + while (*rel == '/') rel++; /* avoid a double slash when joining */ + + char full[192]; + snprintf(full, sizeof(full), "%s/%s", base, rel); + + /* Create the immediate parent dir (one level) so a pack can live in a + * subfolder like /sdcard/voice/.wav — fopen won't create it. FAT + * supports mkdir; SPIFFS is flat so we only do this for the SD path. */ + if (to_sd) { + char *slash = strrchr(full, '/'); + if (slash && slash != full) { + *slash = '\0'; + mkdir(full, 0775); /* best-effort; ignore EEXIST */ + *slash = '/'; + } } FILE *f = fopen(full, "wb"); diff --git a/plip_voice/sdkconfig.defaults b/plip_voice/sdkconfig.defaults index b6f354d..cf808bc 100644 --- a/plip_voice/sdkconfig.defaults +++ b/plip_voice/sdkconfig.defaults @@ -52,3 +52,7 @@ CONFIG_PLIP_HOOK_ACTIVE_HIGH=y # Example: CONFIG_PLIP_GATEWAY_URL="http://192.168.0.175:8401" # CONFIG_PLIP_GATEWAY_TOKEN="testtoken" # Default (Kconfig): http://192.168.0.50:8401 with empty token. + +# Long file names on the SD (voice sample pack uses >8.3 names) +CONFIG_FATFS_LFN_HEAP=y +CONFIG_FATFS_MAX_LFN=255 -- 2.52.0