284 lines
5.8 KiB
C
284 lines
5.8 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,
|
|
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,
|
|
},
|
|
};
|
|
|
|
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);
|
|
}
|