fix(l2): no remount while the card is a USB drive, vector packs locked across cores, time zone list, noise sweep restore

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Jakub
2026-10-12 19:30:46 +02:00
co-authored by Claude Opus 5.5
parent 57db27512d
commit 2b9e013071
10 changed files with 81 additions and 22 deletions
@@ -280,7 +280,9 @@ static void noiseSweep(Sweep& sw, const NodePrefs* p) {
sw.v[i] = (int16_t)lroundf(v[K / 2]);
}
radio.standby();
if (p) radio_driver.setParams(p->freq, p->bw, p->sf, p->cr);
// Back to what the mesh uses: the repeater's own profile while relaying on
// one, not always the chat settings.
if (p) the_mesh.applyRepeaterRadio();
radio.startReceive();
}
#endif
@@ -564,7 +564,8 @@ void UITask::buildHomeClock(lv_obj_t* box) {
lv_obj_set_style_pad_top(row, 12, 0);
char opts[200];
int o = 0;
for (int k = 0; k < telemetry::COUNT; k++) o += snprintf(opts + o, sizeof(opts) - o, "%s%s", k ? "\n" : "", telemetry::NAME[k]);
for (int k = 0; k < telemetry::COUNT && o < (int)sizeof(opts) - 24; k++) // 137 bytes today; the list only grows
o += snprintf(opts + o, sizeof(opts) - o, "%s%s", k ? "\n" : "", telemetry::NAME[k]);
for (int i = 0; i < FIELDS; i++) {
uint8_t f = _prefs ? _prefs->dashboard_fields[i] : telemetry::NONE;
if (f >= telemetry::COUNT) f = telemetry::NONE;
+8 -1
View File
@@ -287,7 +287,12 @@ static void backlightFadeIn() { display.fadeIn(); }
// is picked up.
static bool s_sd_mounted = false;
static uint32_t s_sd_last_try = 0;
// Lent to a computer as a USB drive (usbDriveStart()): its driver owns the
// card's host. Mounting it again here as well -- the map, the history and
// Settings > About all ask -- would put two drivers on one card.
static bool s_sd_lent = false;
static bool mountStorage() {
if (s_sd_lent) return false;
if (s_sd_mounted) return true;
if (s_sd_last_try && millis() - s_sd_last_try < 5000) return false;
s_sd_last_try = millis() | 1;
@@ -373,6 +378,7 @@ static bool usbDriveStart() {
if (s_usb_drive) return true;
SD_MMC.end();
s_sd_mounted = false;
s_sd_lent = true;
sdmmc_host_t host = SDMMC_HOST_DEFAULT();
host.flags = SDMMC_HOST_FLAG_1BIT;
sdmmc_slot_config_t slot = SDMMC_SLOT_CONFIG_DEFAULT();
@@ -386,7 +392,7 @@ static bool usbDriveStart() {
sdmmc_host_deinit();
ok = false;
}
if (!ok) { s_sd_last_try = 0; mountStorage(); return false; }
if (!ok) { s_sd_lent = false; s_sd_last_try = 0; mountStorage(); return false; }
s_msc.vendorID("MeshCore");
s_msc.productID("Wio L2 SD card");
s_msc.productRevision("1.0");
@@ -408,6 +414,7 @@ static void usbDriveStop() {
delay(100);
sdmmc_host_deinit();
s_usb_drive = false;
s_sd_lent = false; // the UI restarts next (restartScreen): the card is mounted afresh
}
#else
static bool usbHost() { return false; }
@@ -83,10 +83,9 @@ void UITask::mapRegionAction(uint8_t act) {
}
case A_DELETE: {
if (!tapConfirmed(s_del_lbl, "Delete?")) return;
char path[80];
snprintf(path, sizeof(path), "%s/%s", mapview::VECTOR_ROOT, p.file);
mapview::s_vpacks.closeOpen();
remove(path);
char file[sizeof(p.file)];
snprintf(file, sizeof(file), "%s", p.file); // p is the list's: read again by removePack()
mapview::s_vpacks.removePack(file);
mapview::s_vector.available(); // lists the packs again, forgets the data read
mapview::labels::flush();
mapview::s_cache.invalidate();
@@ -200,7 +200,7 @@ void UITask::scopeAction(uint8_t act) {
char t[40];
snprintf(t, sizeof(t), "Delete %s?", sl.name((uint8_t)s_scope));
confirmBody(navPopupPanel(t, false),
"Channels using it go back to no scope. If it is the default, * becomes the default.",
"Channels using it follow the default again. If it is the default, * becomes the default.",
LV_SYMBOL_TRASH " Delete", onScopeAction, S_DELETE_GO);
break;
}
@@ -487,7 +487,9 @@ void UITask::schemaRow(lv_obj_t* card, int i) {
lv_obj_add_event_cb(sw, onSchemaSwitch, LV_EVENT_VALUE_CHANGED, (void*)(uintptr_t)i);
return;
}
char opts[160];
// The longest list, the time zone, is 27 options and 167 bytes: at 160
// it lost UTC+8 .. +14, and a zone set from the app showed as UTC-12.
char opts[320];
int o = 0;
for (uint8_t k = 0; k < st.count && o < (int)sizeof(opts) - 16; k++) {
if (k) opts[o++] = '\n';
@@ -414,6 +414,7 @@ void UITask::storageFormatTap(bool go) {
lv_refr_now(NULL); // shown before the loop blocks
stopWalk();
if (mapview::s_dl.active()) mapDownloadStop();
mapview::s_vpacks.closeOpen();
bool ok = lvport::formatCard();
if (!ok) {
if (s_fmt_overlay) lv_obj_delete_async(s_fmt_overlay);
@@ -504,6 +505,7 @@ void UITask::usbTap(bool drive) {
if (!drive || locked()) return;
storeview::stopWalk();
if (mapview::s_dl.active()) mapDownloadStop();
mapview::s_vpacks.closeOpen(); // a pack file kept open between reads: not across the unmount
if (!lvport::usbDriveStart()) { showToast("No SD card to share", 3000); return; }
usbPanel("USB drive",
"The computer has the SD card now. Eject it there (or unplug the cable) when you're done - "
@@ -951,6 +951,7 @@ void UITask::fillStart(int n, int first, void (UITask::*row)(int)) {
void UITask::fillStep() {
FillJob& j = s_fill[0];
(this->*j.fn)(j.next++); // may queue more, behind
if (!s_fill_n) return; // the piece built a new screen: the queue went with the old one
if (j.next >= j.end) memmove(s_fill, s_fill + 1, --s_fill_n * sizeof(FillJob));
}
@@ -35,25 +35,75 @@ public:
// start), then done() -- a pack's file stays open for the next read.
struct Src { FILE* f = nullptr; uint32_t len = 0; bool owned = false; };
// The loop (labels, the region list, a scan) and the tile task on the other
// core both read packs: one shared FILE* and index. A recursive lock spans
// find() to done() and every change to the list, so one never closes or
// seeks the file under the other.
#if defined(ESP32)
VectorPacks() : _mx(xSemaphoreCreateRecursiveMutex()) {}
void lock() { if (_mx) xSemaphoreTakeRecursive(_mx, portMAX_DELAY); }
void unlock() { if (_mx) xSemaphoreGiveRecursive(_mx); }
#else
void lock() {}
void unlock() {}
#endif
// Lists the packs again (the map opening, a download finished).
void scan() {
lock();
closeOpen();
for (int i = 0; i < _n; i++) free(_p[i].index);
_n = 0;
DIR* d = opendir(VECTOR_ROOT);
if (!d) return;
if (!d) { unlock(); return; }
while (struct dirent* e = readdir(d)) {
size_t l = strlen(e->d_name);
if (_n >= MAX || l < 5 || l >= sizeof(_p[0].file) || strcasecmp(e->d_name + l - 4, ".vpk") != 0) continue;
if (open(_p[_n], e->d_name)) _n++;
}
closedir(d);
unlock();
}
int count() const { return _n; }
const Pack& at(int i) const { return _p[i]; }
// True: `s` is ready to read and the lock is held until done(s).
bool find(uint8_t kind, int dz, int x, int y, Src& s) {
lock();
if (findLocked(kind, dz, x, y, s)) return true;
unlock();
return false;
}
void done(Src& s) {
if (s.owned && s.f) fclose(s.f);
s.f = nullptr;
unlock();
}
// Before a pack file is replaced or deleted.
void closeOpen() {
lock();
if (_open_f) fclose(_open_f);
_open_f = nullptr;
_open_i = -1;
unlock();
}
// Deletes a pack: closed and removed under the lock (the tile task can't
// reopen it in between), then the list read again.
void removePack(const char* file) {
lock();
closeOpen();
char path[64];
snprintf(path, sizeof(path), "%s/%s", VECTOR_ROOT, file);
remove(path);
scan();
unlock();
}
private:
bool findLocked(uint8_t kind, int dz, int x, int y, Src& s) {
uint64_t key = keyOf(kind, dz, x, y);
for (int i = 0; i < _n; i++) {
Pack& p = _p[i];
@@ -75,23 +125,14 @@ public:
s.f = f; s.len = sz > 0 ? (uint32_t)sz : 0; s.owned = true;
return true;
}
void done(Src& s) {
if (s.owned && s.f) fclose(s.f);
s.f = nullptr;
}
// Before a pack file is replaced or deleted.
void closeOpen() {
if (_open_f) fclose(_open_f);
_open_f = nullptr;
_open_i = -1;
}
private:
Pack _p[MAX];
int _n = 0;
FILE* _open_f = nullptr; // the pack read last
int _open_i = -1;
#if defined(ESP32)
SemaphoreHandle_t _mx;
#endif
static const uint32_t INDEX_IN_RAM_MAX = 2u * 1024 * 1024;
static uint64_t keyOf(uint8_t kind, int dz, int x, int y) {