On-device menu, persistent settings and 12 favourite slots
Hold the knob to open a six-row menu: save/remove the playing camera as a favourite (12 NVS slots), play or forget favourites, choose the source (random insecam or favourites), time per camera, listing, screen and ring brightness, caption, and reboot into Wi-Fi setup. Settings live in NVS and override the Kconfig first-boot defaults. Camera URLs are stored with their COUNTER placeholder so favourites get a fresh cache-buster on every visit.
This commit is contained in:
1 parent
eff9e5dfaa
commit
d92a2e53cc
15 files changed
+536
-39
No files matched your search
+47
-20
@@ -6,7 +6,9 @@
|
||||
#include "jpegdec.h"
|
||||
#include "jpegframe.h"
|
||||
#include "leds.h"
|
||||
#include "menu.h"
|
||||
#include "net.h"
|
||||
#include "settings.h"
|
||||
#include "ui.h"
|
||||
#include "sdkconfig.h"
|
||||
#include "esp_heap_caps.h"
|
||||
@@ -36,7 +38,7 @@ static const char *TAG = "slideshow";
|
||||
#define COL_PAUSED 0xffa000
|
||||
#define COL_ERROR 0xff2020
|
||||
|
||||
typedef enum { LEAVE_NEXT, LEAVE_PREV, LEAVE_ERROR } leave_t;
|
||||
typedef enum { LEAVE_NEXT, LEAVE_PREV, LEAVE_ERROR, LEAVE_MENU } leave_t;
|
||||
|
||||
static cam_info_t page_cams[PAGE_ITEMS];
|
||||
static int page_count, page_next;
|
||||
@@ -44,6 +46,7 @@ static int total_pages[2] = {287, 287};
|
||||
|
||||
static cam_info_t history[HISTORY_LEN];
|
||||
static int hist_len, hist_pos = -1;
|
||||
static int fav_cursor;
|
||||
|
||||
static uint8_t *jpeg_buf;
|
||||
static size_t jpeg_cap;
|
||||
@@ -55,13 +58,9 @@ static int64_t now_ms(void) { return esp_timer_get_time() / 1000; }
|
||||
|
||||
static insecam_listing_t pick_listing(void)
|
||||
{
|
||||
#if CONFIG_WIDGET_LISTING_RATING
|
||||
return INSECAM_BY_RATING;
|
||||
#elif CONFIG_WIDGET_LISTING_NEW
|
||||
return INSECAM_BY_NEW;
|
||||
#else
|
||||
if (settings.listing == LISTING_RATING) return INSECAM_BY_RATING;
|
||||
if (settings.listing == LISTING_NEW) return INSECAM_BY_NEW;
|
||||
return (esp_random() & 1) ? INSECAM_BY_RATING : INSECAM_BY_NEW;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Refills the page queue from a random listing page; blocks (with retries) until it has cameras.
|
||||
@@ -112,27 +111,42 @@ static const cam_info_t *next_camera(bool backwards)
|
||||
return &history[hist_pos];
|
||||
}
|
||||
if (hist_pos >= 0 && hist_pos < hist_len - 1) return &history[++hist_pos];
|
||||
if (settings.source == SOURCE_FAVORITES && fav_count() > 0) {
|
||||
for (int tries = 0; tries < FAV_SLOTS; tries++) {
|
||||
const cam_info_t *c = fav_get(fav_cursor);
|
||||
fav_cursor = (fav_cursor + 1) % FAV_SLOTS;
|
||||
if (c) { history_push(c); return &history[hist_pos]; }
|
||||
}
|
||||
}
|
||||
if (page_next >= page_count) refill_queue();
|
||||
history_push(&page_cams[page_next++]);
|
||||
return &history[hist_pos];
|
||||
}
|
||||
|
||||
static void set_paused(bool p, int64_t remaining_ms)
|
||||
static void set_paused(bool p)
|
||||
{
|
||||
paused = p;
|
||||
ui_set_badge(p ? "PAUSED" : "LIVE", p ? COL_PAUSED : COL_LIVE);
|
||||
leds_set_mode(p ? LED_SOLID : LED_COUNTDOWN, p ? COL_PAUSED : COL_LIVE);
|
||||
(void)remaining_ms;
|
||||
}
|
||||
|
||||
static void show_marker(const cam_info_t *cam)
|
||||
{
|
||||
int slot = fav_find(cam);
|
||||
char text[20];
|
||||
if (slot >= 0) snprintf(text, sizeof text, "FAV %d", slot + 1);
|
||||
ui_set_marker(slot >= 0 ? text : NULL);
|
||||
}
|
||||
|
||||
// Streams one camera until the interval elapses, the user intervenes, or it fails.
|
||||
static leave_t show_camera(const cam_info_t *cam)
|
||||
{
|
||||
const int64_t interval_ms = (int64_t)CONFIG_WIDGET_INTERVAL_S * 1000;
|
||||
char meta[96];
|
||||
const int64_t interval_ms = (int64_t)settings.interval_s * 1000;
|
||||
char meta[96], url[sizeof cam->url];
|
||||
snprintf(meta, sizeof meta, "%s " BULLET " connecting", cam->maker[0] ? cam->maker : "camera");
|
||||
ui_set_caption(cam->place, meta);
|
||||
ui_set_badge("...", 0x505860);
|
||||
show_marker(cam);
|
||||
ui_set_progress(-1);
|
||||
leds_set_mode(LED_SPIN, COL_CONNECT);
|
||||
ESP_LOGI(TAG, "#%lu %s (%s) %s", (unsigned long)cam->id, cam->place, cam->maker, cam->url);
|
||||
@@ -159,10 +173,14 @@ static leave_t show_camera(const cam_info_t *cam)
|
||||
switch (ev) {
|
||||
case INPUT_CW: case INPUT_KEY: leave = LEAVE_NEXT; goto done;
|
||||
case INPUT_CCW: leave = LEAVE_PREV; goto done;
|
||||
case INPUT_CLICK: if (frames) set_paused(!paused, interval_ms - shown_ms); break;
|
||||
case INPUT_DOUBLE: case INPUT_LONG:
|
||||
ui_set_overlay_visible(!ui_overlay_visible());
|
||||
if (frames) set_paused(paused, 0);
|
||||
case INPUT_CLICK: if (frames) set_paused(!paused); break;
|
||||
case INPUT_LONG: leave = LEAVE_MENU; goto done;
|
||||
case INPUT_DOUBLE:
|
||||
settings.caption = !ui_overlay_visible();
|
||||
settings_save();
|
||||
ui_set_overlay_visible(settings.caption);
|
||||
if (frames) set_paused(paused); else ui_set_badge("...", 0x505860);
|
||||
show_marker(cam);
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
@@ -180,7 +198,8 @@ static leave_t show_camera(const cam_info_t *cam)
|
||||
// (re)open
|
||||
if (!open) {
|
||||
if (ended_at && now - ended_at < REFRESH_STILL_MS) { vTaskDelay(pdMS_TO_TICKS(50)); continue; }
|
||||
esp_err_t err = httpstream_open(&s, cam->url, "Mozilla/5.0 (compatible; insecam-widget)", CONNECT_TIMEOUT_MS, RECV_SLICE_MS);
|
||||
insecam_effective_url(cam, url, sizeof url);
|
||||
esp_err_t err = httpstream_open(&s, url, "Mozilla/5.0 (compatible; insecam-widget)", CONNECT_TIMEOUT_MS, RECV_SLICE_MS);
|
||||
if (err != ESP_OK || s.status != 200) {
|
||||
if (err == ESP_OK) { ESP_LOGW(TAG, "HTTP %d", s.status); httpstream_close(&s); }
|
||||
else ESP_LOGW(TAG, "connect failed: %s", esp_err_to_name(err));
|
||||
@@ -244,7 +263,7 @@ static leave_t show_camera(const cam_info_t *cam)
|
||||
snprintf(meta, sizeof meta, "%s " BULLET " %dx%d " BULLET " #%lu",
|
||||
cam->maker[0] ? cam->maker : (is_stream ? "mjpeg" : "still"), w, h, (unsigned long)cam->id);
|
||||
ui_set_caption(cam->place, meta);
|
||||
set_paused(paused, interval_ms);
|
||||
set_paused(paused);
|
||||
last_tick = now_ms();
|
||||
}
|
||||
}
|
||||
@@ -256,8 +275,9 @@ done:
|
||||
|
||||
static void slideshow_task(void *arg)
|
||||
{
|
||||
bool backwards = false;
|
||||
bool backwards = false, replay = false;
|
||||
int consecutive_errors = 0;
|
||||
ui_set_overlay_visible(settings.caption);
|
||||
ui_show_camera_page();
|
||||
while (1) {
|
||||
if (!net_is_connected()) {
|
||||
@@ -265,9 +285,16 @@ static void slideshow_task(void *arg)
|
||||
leds_set_mode(LED_BREATHE, COL_ERROR);
|
||||
net_wait_connected(-1);
|
||||
}
|
||||
const cam_info_t *cam = next_camera(backwards);
|
||||
backwards = false;
|
||||
const cam_info_t *cam = replay ? &history[hist_pos] : next_camera(backwards);
|
||||
backwards = replay = false;
|
||||
leave_t why = show_camera(cam);
|
||||
if (why == LEAVE_MENU) {
|
||||
int slot = -1;
|
||||
if (menu_run(cam, &slot) == MENU_PLAY_FAV && fav_get(slot)) history_push(fav_get(slot));
|
||||
replay = true; // play the chosen favourite, or pick the same camera back up
|
||||
paused = false;
|
||||
continue;
|
||||
}
|
||||
if (why == LEAVE_ERROR) {
|
||||
consecutive_errors++;
|
||||
ui_toast("Camera offline, skipping", 900);
|
||||
|
||||
Reference in new issue
Block a user