From 1ea7b13bc72180c6eec2debef72cc7163de8f11d Mon Sep 17 00:00:00 2001 From: Juli Date: Thu, 17 Sep 2026 03:00:19 +0200 Subject: [PATCH] 50 favourite slots; favourite lists show only used slots Save-to-slot preselects the first empty slot; the favourites cache lives in PSRAM. --- README.md | 6 +++--- main/menu.c | 41 +++++++++++++++++++++++++++-------------- main/settings.c | 7 +++++-- main/settings.h | 2 +- 4 files changed, 36 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 6a2040f..8154dff 100644 --- a/README.md +++ b/README.md @@ -60,8 +60,8 @@ click a number to edit it (turn changes it, click confirms), hold to go back. Entries: * **Save as favourite / Remove from favourites** — the camera that was playing; - saving asks for one of the 24 slots (overwrites it). -* **Favourites** — the 24 slots; click one to play it. A saved camera shows a + saving asks for one of the 50 slots (it jumps to the first empty one; picking a used slot overwrites it). +* **Favourites** — the saved cameras; click one to play it. A saved camera shows a yellow *FAV n* marker while it plays. * **Source** — *Random* draws from the sites, *Favourites* cycles the saved slots. * **Provider** — *insecam*, *opentopia* or *Mixed* (a random site per batch). @@ -120,7 +120,7 @@ it stays out of the way. The CC1101 board's BQ27220 gauge is not supported. elapses or the knob moves, skip dead cameras, keep history for "previous". * `ui.c` is LVGL 9 via `esp_lvgl_port`: a canvas for the frame, a translucent caption bar, a LIVE/PAUSED badge, a hairline progress bar and a six-row menu page. -* `settings.c` keeps the settings blob and the 24 favourite slots in NVS +* `settings.c` keeps the settings blob and the 50 favourite slots in NVS (namespace `widget`); `menu.c` is the knob-driven menu that edits them; `power.c` is the deep-sleep power-off with ext1 wake on the encoder pins. diff --git a/main/menu.c b/main/menu.c index e15fddd..ffd3da8 100644 --- a/main/menu.c +++ b/main/menu.c @@ -25,6 +25,7 @@ enum { ROW_FAV_TOGGLE, ROW_FAVS, ROW_SOURCE, ROW_PROVIDER, ROW_INTERVAL, ROW_LIS static ui_menu_row_t rows[TOP_ROWS > FAV_SLOTS + 1 ? TOP_ROWS : FAV_SLOTS + 1]; static char values[TOP_ROWS][20]; static char slot_labels[FAV_SLOTS][4]; +static int row_slot[FAV_SLOTS + 1]; // list row -> favourite slot (-1 = Back) static const char *listing_name(uint8_t l) { @@ -65,16 +66,26 @@ static int build_top(const cam_info_t *current, int fav_slot) return TOP_ROWS; } -// Slot list: "1 .. 12" with the place name, then "Back". -static int build_slots(void) +// Slot list with the place names, then "Back". filled_only hides empty slots. +static int build_slots(bool filled_only) { + int n = 0; for (int i = 0; i < FAV_SLOTS; i++) { const cam_info_t *c = fav_get(i); + if (filled_only && !c) continue; snprintf(slot_labels[i], sizeof slot_labels[i], "%d", i + 1); - rows[i] = (ui_menu_row_t){slot_labels[i], c ? c->place : "empty", c == NULL}; + rows[n] = (ui_menu_row_t){slot_labels[i], c ? c->place : "empty", c == NULL}; + row_slot[n++] = i; } - rows[FAV_SLOTS] = (ui_menu_row_t){"Back", NULL, false}; - return FAV_SLOTS + 1; + rows[n] = (ui_menu_row_t){"Back", NULL, false}; + row_slot[n++] = -1; + return n; +} + +static int first_empty_slot(void) +{ + for (int i = 0; i < FAV_SLOTS; i++) if (!fav_get(i)) return i; + return 0; } static int clampi(int v, int lo, int hi) { return v < lo ? lo : v > hi ? hi : v; } @@ -92,7 +103,7 @@ menu_result_t menu_run(const cam_info_t *current, int *fav_slot_out) const char *title = "insecam widget"; if (view == VIEW_TOP) count = build_top(current, fav_slot); else { - count = build_slots(); + count = build_slots(view != VIEW_FAV_SAVE); title = view == VIEW_FAV_PLAY ? "Favourites" : view == VIEW_FAV_SAVE ? "Save to slot" : "Forget favourite"; } sel = clampi(sel, 0, count - 1); @@ -134,26 +145,28 @@ menu_result_t menu_run(const cam_info_t *current, int *fav_slot_out) // --- click if (view != VIEW_TOP) { - if (sel == FAV_SLOTS) { view = VIEW_TOP; sel = top = 0; continue; } - const cam_info_t *c = fav_get(sel); + int slot = row_slot[sel]; + if (slot < 0) { view = VIEW_TOP; sel = top = 0; continue; } + const cam_info_t *c = fav_get(slot); char msg[48]; if (view == VIEW_FAV_SAVE) { if (current) { - fav_set(sel, current); - snprintf(msg, sizeof msg, "Saved to slot %d", sel + 1); + fav_set(slot, current); + snprintf(msg, sizeof msg, "Saved to slot %d", slot + 1); ui_toast(msg, 1500); } view = VIEW_TOP; sel = top = 0; } else if (view == VIEW_FAV_PLAY) { if (!c) { ui_toast("Empty slot", 1000); continue; } - *fav_slot_out = sel; + *fav_slot_out = slot; result = MENU_PLAY_FAV; goto out; } else { // VIEW_FAV_FORGET if (!c) { ui_toast("Empty slot", 1000); continue; } - fav_clear(sel); - snprintf(msg, sizeof msg, "Forgot slot %d", sel + 1); + fav_clear(slot); + snprintf(msg, sizeof msg, "Forgot slot %d", slot + 1); ui_toast(msg, 1500); + if (fav_count() == 0) { view = VIEW_TOP; sel = top = 0; } } continue; } @@ -161,7 +174,7 @@ menu_result_t menu_run(const cam_info_t *current, int *fav_slot_out) case ROW_FAV_TOGGLE: if (!current) break; if (fav_slot >= 0) { fav_clear(fav_slot); ui_toast("Removed from favourites", 1500); } - else { view = VIEW_FAV_SAVE; sel = top = 0; } + else { view = VIEW_FAV_SAVE; sel = first_empty_slot(); top = 0; } break; case ROW_FAVS: view = VIEW_FAV_PLAY; sel = top = 0; break; case ROW_FORGET: view = VIEW_FAV_FORGET; sel = top = 0; break; diff --git a/main/settings.c b/main/settings.c index 0c4dbe8..764656f 100644 --- a/main/settings.c +++ b/main/settings.c @@ -3,13 +3,15 @@ #include "esp_log.h" #include "nvs.h" #include "nvs_flash.h" +#include "esp_heap_caps.h" +#include #include #include static const char *TAG = "settings"; static nvs_handle_t nvs; settings_t settings; -static cam_info_t favs[FAV_SLOTS]; +static cam_info_t *favs; // FAV_SLOTS entries, in PSRAM static bool fav_used[FAV_SLOTS]; static void slot_key(int slot, char *key) { snprintf(key, 8, "fav%d", slot); } @@ -49,10 +51,11 @@ void settings_init(void) if (settings.source > SOURCE_FAVORITES) settings.source = SOURCE_RANDOM; if (settings.provider > PROVIDER_PREF_MIXED) settings.provider = PROVIDER_PREF_MIXED; } + favs = heap_caps_calloc(FAV_SLOTS, sizeof *favs, MALLOC_CAP_SPIRAM); + assert(favs); for (int i = 0; i < FAV_SLOTS; i++) { char key[8]; slot_key(i, key); - memset(&favs[i], 0, sizeof favs[i]); len = sizeof favs[i]; fav_used[i] = nvs_get_blob(nvs, key, &favs[i], &len) == ESP_OK && len <= sizeof favs[i] && favs[i].url[0]; if (fav_used[i] && favs[i].provider > PROVIDER_OPENTOPIA) favs[i].provider = PROVIDER_INSECAM; diff --git a/main/settings.h b/main/settings.h index b3578c8..22dd862 100644 --- a/main/settings.h +++ b/main/settings.h @@ -29,7 +29,7 @@ void settings_save(void); void settings_request_portal(void); bool settings_take_portal_request(void); -#define FAV_SLOTS 24 +#define FAV_SLOTS 50 const cam_info_t *fav_get(int slot); // NULL when empty void fav_set(int slot, const cam_info_t *cam); void fav_clear(int slot);