#include "settings.h" #include "ui.h" #include "sdkconfig.h" #include "esp_log.h" #include "nvs.h" #include "nvs_flash.h" #include "esp_heap_caps.h" #include #include #include #include static const char *TAG = "settings"; static nvs_handle_t nvs; settings_t settings; 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); } void settings_init(void) { esp_err_t err = nvs_flash_init(); if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) { ESP_ERROR_CHECK(nvs_flash_erase()); err = nvs_flash_init(); } ESP_ERROR_CHECK(err); ESP_ERROR_CHECK(nvs_open("widget", NVS_READWRITE, &nvs)); settings = (settings_t){ .interval_s = CONFIG_WIDGET_INTERVAL_S, .lcd_brightness = CONFIG_WIDGET_LCD_BRIGHTNESS, .led_brightness = CONFIG_WIDGET_LED_BRIGHTNESS, #if CONFIG_WIDGET_LISTING_RATING .listing = LISTING_RATING, #elif CONFIG_WIDGET_LISTING_NEW .listing = LISTING_NEW, #else .listing = LISTING_MIXED, #endif .source = SOURCE_RANDOM, .caption = 1, .provider = PROVIDER_PREF_MIXED, .version = SETTINGS_VERSION, }; // Blobs written by older builds may be shorter: copy what is there over the defaults. settings_t stored = settings; size_t len = sizeof stored; if (nvs_get_blob(nvs, "settings", &stored, &len) == ESP_OK && len >= 7 && len <= sizeof stored) { memcpy(&settings, &stored, len); if (len <= offsetof(settings_t, provider)) settings.provider = PROVIDER_PREF_MIXED; // pre-provider blob if (settings.version < 3) settings.flip = 0; // field lived in padding before v3 settings.flip = settings.flip ? 1 : 0; settings.version = SETTINGS_VERSION; if (settings.interval_s < 5 || settings.interval_s > 300) settings.interval_s = CONFIG_WIDGET_INTERVAL_S; if (settings.lcd_brightness > 100) settings.lcd_brightness = 100; if (settings.listing > LISTING_NEW) settings.listing = LISTING_MIXED; if (settings.source > SOURCE_POPULAR) settings.source = SOURCE_RANDOM; if (settings.theme >= ui_theme_count()) settings.theme = 0; 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); 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; } ESP_LOGI(TAG, "interval %us, lcd %u%%, ring %u, listing %u, source %u, provider %u, %d favourites", settings.interval_s, settings.lcd_brightness, settings.led_brightness, settings.listing, settings.source, settings.provider, fav_count()); } void settings_save(void) { if (nvs_set_blob(nvs, "settings", &settings, sizeof settings) != ESP_OK || nvs_commit(nvs) != ESP_OK) ESP_LOGW(TAG, "save failed"); } void settings_request_portal(void) { nvs_set_u8(nvs, "portal", 1); nvs_commit(nvs); } bool settings_take_portal_request(void) { uint8_t v = 0; if (nvs_get_u8(nvs, "portal", &v) != ESP_OK || !v) return false; nvs_erase_key(nvs, "portal"); nvs_commit(nvs); return true; } const cam_info_t *fav_get(int slot) { return (slot >= 0 && slot < FAV_SLOTS && fav_used[slot]) ? &favs[slot] : NULL; } void fav_set(int slot, const cam_info_t *cam) { if (slot < 0 || slot >= FAV_SLOTS) return; char key[8]; slot_key(slot, key); favs[slot] = *cam; fav_used[slot] = true; if (nvs_set_blob(nvs, key, &favs[slot], sizeof favs[slot]) != ESP_OK || nvs_commit(nvs) != ESP_OK) ESP_LOGW(TAG, "favourite %d not saved", slot); } void fav_clear(int slot) { if (slot < 0 || slot >= FAV_SLOTS) return; char key[8]; slot_key(slot, key); fav_used[slot] = false; nvs_erase_key(nvs, key); nvs_commit(nvs); } int fav_find(const cam_info_t *cam) { for (int i = 0; i < FAV_SLOTS; i++) if (fav_used[i] && favs[i].id == cam->id && favs[i].provider == cam->provider) return i; return -1; } int fav_count(void) { int n = 0; for (int i = 0; i < FAV_SLOTS; i++) n += fav_used[i]; return n; }