/* * SPDX-License-Identifier: AGPL-3.0-only * Copyright (c) Camden Dixie O'Brien */ #include "config.h" #include "fatal.h" #include "esp_log.h" #include "nvs_flash.h" #include #include #define TAG "Config" #define NAMESPACE "config" #define MAX_CALLBACKS 8 typedef enum { HOSTNAME, SSID, PSK, TIMEZONE, ITEM_COUNT, } ItemIndex; typedef struct { const char *id; const char *default_value; char value[CONFIG_MAX_VALUE_SIZE]; struct { ConfigCallback funcs[MAX_CALLBACKS]; unsigned count; } callbacks; } Item; static Item state[ITEM_COUNT] = { [HOSTNAME] = { .id = "hostname", .default_value = CONFIG_DEFAULT_HOSTNAME, }, [SSID] = { .id = "ssid", .default_value = CONFIG_DEFAULT_SSID, }, [PSK] = { .id = "psk", .default_value = CONFIG_DEFAULT_PSK, }, [TIMEZONE] = { .id = "timezone", .default_value = CONFIG_DEFAULT_TIMEZONE, }, }; static bool load(ItemIndex item) { esp_err_t error; nvs_handle_t handle; error = nvs_open(NAMESPACE, NVS_READONLY, &handle); if (error == ESP_ERR_NVS_NOT_FOUND) return false; if (error != ESP_OK) { ESP_LOGE(TAG, "Error opening NVS: %04x", error); return false; } size_t size = CONFIG_MAX_VALUE_SIZE; error = nvs_get_str(handle, state[item].id, state[item].value, &size); if (error != ESP_OK) { ESP_LOGE( TAG, "Error loading %s from storage: %04x", state[item].id, error); nvs_close(handle); return false; } nvs_close(handle); return true; } static void save(ItemIndex item) { esp_err_t error; nvs_handle_t handle; error = nvs_open(NAMESPACE, NVS_READWRITE, &handle); if (error == ESP_ERR_NVS_NOT_FOUND) return; if (error != ESP_OK) { ESP_LOGE(TAG, "Error opening NVS: %04x", error); return; } error = nvs_set_str(handle, state[item].id, state[item].value); if (error != ESP_OK) { ESP_LOGE( TAG, "Error loading %s from storage: %04x", state[item].id, error); nvs_close(handle); return; } error = nvs_commit(handle); if (error != ESP_OK) ESP_LOGE(TAG, "Error commiting NVS update: %04x", error); nvs_close(handle); } static void set(ItemIndex item, const char *value) { if (value == NULL) { ESP_LOGW( TAG, "Attempt to set %s to null pointer; ignored", state[item].id); return; } size_t len = strlen(value); if (len >= CONFIG_MAX_VALUE_SIZE) { ESP_LOGW( TAG, "%s value \"%s\" exceeds maximum size; truncated", state[item].id, value); len = CONFIG_MAX_VALUE_SIZE - 1; } memcpy(state[item].value, value, len); state[item].value[len] = '\0'; save(item); for (unsigned i = 0; i < state[item].callbacks.count; ++i) state[item].callbacks.funcs[i](state[item].value); } static size_t get(ItemIndex item, char *buffer, size_t buffer_size) { size_t len = strlen(state[item].value); if (len < buffer_size) { memcpy(buffer, state[item].value, len); buffer[len] = '\0'; } return len; } static void add_callback(ItemIndex item, ConfigCallback callback) { if (callback == NULL) { ESP_LOGW( TAG, "Attempt to add null callback for %s; ignored", state[item].id); } else if (state[item].callbacks.count >= MAX_CALLBACKS) { ESP_LOGE( TAG, "Max callbacks exceeded for %s; callback discarded", state[item].id); } else { const unsigned pos = state[item].callbacks.count; state[item].callbacks.funcs[pos] = callback; ++state[item].callbacks.count; } } void config_init() { esp_err_t error; error = nvs_flash_init(); if (error == ESP_ERR_NVS_NO_FREE_PAGES || error == ESP_ERR_NVS_NEW_VERSION_FOUND) { ESP_LOGI(TAG, "NVS partition full or outdated; erasing"); (void)nvs_flash_erase(); error = nvs_flash_init(); } if (error != ESP_OK) { ESP_LOGE(TAG, "Error initializing NVS store: %04x", error); FATAL(); } for (ItemIndex item = (ItemIndex)0; item < ITEM_COUNT; ++item) { if (!load(item)) { set(item, state[item].default_value); save(item); } } } void config_set_hostname(const char *hostname) { set(HOSTNAME, hostname); } size_t config_get_hostname(char *buffer, size_t buffer_size) { return get(HOSTNAME, buffer, buffer_size); } void config_add_hostname_callback(ConfigCallback callback) { add_callback(HOSTNAME, callback); } void config_set_ssid(const char *ssid) { set(SSID, ssid); } size_t config_get_ssid(char *buffer, size_t buffer_size) { return get(SSID, buffer, buffer_size); } void config_add_ssid_callback(ConfigCallback callback) { add_callback(SSID, callback); } void config_set_psk(const char *psk) { set(PSK, psk); } size_t config_get_psk(char *buffer, size_t buffer_size) { return get(PSK, buffer, buffer_size); } void config_add_psk_callback(ConfigCallback callback) { add_callback(PSK, callback); } void config_set_timezone(const char *timezone) { set(TIMEZONE, timezone); } size_t config_get_timezone(char *buffer, size_t buffer_size) { return get(TIMEZONE, buffer, buffer_size); } void config_add_timezone_callback(ConfigCallback callback) { add_callback(TIMEZONE, callback); }