185 lines
3.9 KiB
C
185 lines
3.9 KiB
C
/*
|
|
* 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 <stdbool.h>
|
|
#include <string.h>
|
|
|
|
#define TAG "Config"
|
|
|
|
#define NAMESPACE "config"
|
|
#define MAX_CALLBACKS 8
|
|
|
|
typedef enum {
|
|
HOSTNAME,
|
|
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 = "bedside-clock" },
|
|
};
|
|
|
|
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);
|
|
}
|