50 favourite slots; favourite lists show only used slots

Save-to-slot preselects the first empty slot; the favourites cache lives
in PSRAM.
This commit is contained in:
Juli committed 2026-09-17 03:00:19 +02:00
1 parent 89dfdf09a6
commit 1ea7b13bc7
4 files changed
+36 -20

No files matched your search

+3 -3
View File
@@ -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.
+27 -14
View File
@@ -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;
+5 -2
View File
@@ -3,13 +3,15 @@
#include "esp_log.h"
#include "nvs.h"
#include "nvs_flash.h"
#include "esp_heap_caps.h"
#include <assert.h>
#include <stdio.h>
#include <string.h>
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;
+1 -1
View File
@@ -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);