diff --git a/examples/companion_radio/ui-lvgl/DiagScreen.h b/examples/companion_radio/ui-lvgl/DiagScreen.h index 3022a8fd..772e06bd 100644 --- a/examples/companion_radio/ui-lvgl/DiagScreen.h +++ b/examples/companion_radio/ui-lvgl/DiagScreen.h @@ -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 diff --git a/examples/companion_radio/ui-lvgl/HomeScreen.h b/examples/companion_radio/ui-lvgl/HomeScreen.h index cf3b0aec..0af542e2 100644 --- a/examples/companion_radio/ui-lvgl/HomeScreen.h +++ b/examples/companion_radio/ui-lvgl/HomeScreen.h @@ -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; diff --git a/examples/companion_radio/ui-lvgl/LvglPort.h b/examples/companion_radio/ui-lvgl/LvglPort.h index 4953cb5a..71847e67 100644 --- a/examples/companion_radio/ui-lvgl/LvglPort.h +++ b/examples/companion_radio/ui-lvgl/LvglPort.h @@ -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; } diff --git a/examples/companion_radio/ui-lvgl/MapRegions.h b/examples/companion_radio/ui-lvgl/MapRegions.h index 2655f1d9..1579e6f2 100644 --- a/examples/companion_radio/ui-lvgl/MapRegions.h +++ b/examples/companion_radio/ui-lvgl/MapRegions.h @@ -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(); diff --git a/examples/companion_radio/ui-lvgl/RadioExtras.h b/examples/companion_radio/ui-lvgl/RadioExtras.h index c01d7449..20ab3ce0 100644 --- a/examples/companion_radio/ui-lvgl/RadioExtras.h +++ b/examples/companion_radio/ui-lvgl/RadioExtras.h @@ -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; } diff --git a/examples/companion_radio/ui-lvgl/SettingsPages.h b/examples/companion_radio/ui-lvgl/SettingsPages.h index 36f5f3bd..6185e60d 100644 --- a/examples/companion_radio/ui-lvgl/SettingsPages.h +++ b/examples/companion_radio/ui-lvgl/SettingsPages.h @@ -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'; diff --git a/examples/companion_radio/ui-lvgl/StorageScreen.h b/examples/companion_radio/ui-lvgl/StorageScreen.h index 67db8557..8da53fc3 100644 --- a/examples/companion_radio/ui-lvgl/StorageScreen.h +++ b/examples/companion_radio/ui-lvgl/StorageScreen.h @@ -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 - " diff --git a/examples/companion_radio/ui-lvgl/UITask.cpp b/examples/companion_radio/ui-lvgl/UITask.cpp index 15d3d19d..9f30b699 100644 --- a/examples/companion_radio/ui-lvgl/UITask.cpp +++ b/examples/companion_radio/ui-lvgl/UITask.cpp @@ -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)); } diff --git a/examples/companion_radio/ui-lvgl/map/VectorPacks.h b/examples/companion_radio/ui-lvgl/map/VectorPacks.h index 02a6a932..7e86a06a 100644 --- a/examples/companion_radio/ui-lvgl/map/VectorPacks.h +++ b/examples/companion_radio/ui-lvgl/map/VectorPacks.h @@ -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) { diff --git a/src/helpers/ui/DisplayDriver.h b/src/helpers/ui/DisplayDriver.h index e2578ae6..dcfe4058 100644 --- a/src/helpers/ui/DisplayDriver.h +++ b/src/helpers/ui/DisplayDriver.h @@ -441,6 +441,10 @@ public: int ellipsis_width = getTextWidth(ellipsis); int str_len = strlen(temp_str); + // Room for the ellipsis whatever the trim below leaves (a near-full + // buffer of narrow glyphs on a wide panel could keep all but one byte). + const int room = (int)sizeof(temp_str) - 1 - (int)strlen(ellipsis); + if (str_len > room) temp_str[str_len = room] = 0; while (str_len > 0 && getTextWidth(temp_str) > max_width - ellipsis_width) { temp_str[--str_len] = 0;