304 lines
6.2 KiB
C

/*
* SPDX-License-Identifier: AGPL-3.0-only
* Copyright (c) Camden Dixie O'Brien
*/
#include "settings.h"
#include "console.h"
#include "fatal.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include <stdbool.h>
#include <string.h>
#define TAG "Settings"
#define NAMESPACE "settings"
#define MAX_CALLBACKS 8
typedef enum {
HOSTNAME,
SSID,
PSK,
TIMEZONE,
SNTP_SERVER,
ITEM_COUNT,
} ItemIndex;
typedef struct {
const char *id;
const char *default_value;
char value[SETTINGS_MAX_VALUE_SIZE];
struct {
SettingsCallback 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,
},
[SNTP_SERVER] = {
.id = "sntp-server",
.default_value = CONFIG_DEFAULT_SNTP_SERVER,
},
};
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 = SETTINGS_MAX_VALUE_SIZE;
error = nvs_get_str(handle, state[item].id, state[item].value, &size);
if (error == ESP_ERR_NVS_NOT_FOUND) {
nvs_close(handle);
return false;
} else 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 >= SETTINGS_MAX_VALUE_SIZE) {
ESP_LOGW(
TAG, "%s value \"%s\" exceeds maximum size; truncated",
state[item].id, value);
len = SETTINGS_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, SettingsCallback 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;
}
}
static int command_func(int argc, char **argv)
{
if (argc == 2) {
for (unsigned i = 0; i < ITEM_COUNT; ++i) {
if (strcmp(state[i].id, argv[1]) != 0)
continue;
char buffer[SETTINGS_MAX_VALUE_SIZE];
(void)get(i, buffer, SETTINGS_MAX_VALUE_SIZE);
printf("%s\n", buffer);
return 0;
}
printf("Setting not found\n");
return 1;
} else if (argc == 3) {
for (unsigned i = 0; i < ITEM_COUNT; ++i) {
if (strcmp(state[i].id, argv[1]) != 0)
continue;
set(i, argv[2]);
return 0;
}
printf("Setting not found\n");
return 1;
} else {
printf("Invalid number of arguments\n");
return 1;
}
}
void settings_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);
}
}
console_register(
"settings", "Get or set a setting",
"settings <id> OR settings <id> <value>", command_func);
}
void settings_set_hostname(const char *hostname)
{
set(HOSTNAME, hostname);
}
size_t settings_get_hostname(char *buffer, size_t buffer_size)
{
return get(HOSTNAME, buffer, buffer_size);
}
void settings_add_hostname_callback(SettingsCallback callback)
{
add_callback(HOSTNAME, callback);
}
void settings_set_ssid(const char *ssid)
{
set(SSID, ssid);
}
size_t settings_get_ssid(char *buffer, size_t buffer_size)
{
return get(SSID, buffer, buffer_size);
}
void settings_add_ssid_callback(SettingsCallback callback)
{
add_callback(SSID, callback);
}
void settings_set_psk(const char *psk)
{
set(PSK, psk);
}
size_t settings_get_psk(char *buffer, size_t buffer_size)
{
return get(PSK, buffer, buffer_size);
}
void settings_add_psk_callback(SettingsCallback callback)
{
add_callback(PSK, callback);
}
void settings_set_timezone(const char *timezone)
{
set(TIMEZONE, timezone);
}
size_t settings_get_timezone(char *buffer, size_t buffer_size)
{
return get(TIMEZONE, buffer, buffer_size);
}
void settings_add_timezone_callback(SettingsCallback callback)
{
add_callback(TIMEZONE, callback);
}
void settings_set_sntp_server(const char *sntp_server)
{
set(SNTP_SERVER, sntp_server);
}
size_t settings_get_sntp_server(char *buffer, size_t buffer_size)
{
return get(SNTP_SERVER, buffer, buffer_size);
}
void settings_add_sntp_server_callback(SettingsCallback callback)
{
add_callback(SNTP_SERVER, callback);
}