248 lines
4.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,
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);
}