160 lines
3.4 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_CONSUMERS 8
#define DEFAULT_HOSTNAME "bedside-clock"
#define HOSTNAME_KEY "hostname"
typedef struct {
char hostname[MAX_HOSTNAME_SIZE];
} Config;
typedef struct {
ConfigStringCallback callbacks[MAX_CONSUMERS];
unsigned count;
} StringConsumers;
typedef struct {
Config config;
StringConsumers hostname_consumers;
} State;
static State state;
static bool load_hostname()
{
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 = MAX_HOSTNAME_SIZE;
error = nvs_get_str(handle, HOSTNAME_KEY, state.config.hostname, &size);
if (error != ESP_OK) {
ESP_LOGE(TAG, "Error loading hostname from storage: %04x", error);
nvs_close(handle);
return false;
}
nvs_close(handle);
return true;
}
static void save_hostname()
{
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, HOSTNAME_KEY, state.config.hostname);
if (error != ESP_OK) {
ESP_LOGE(TAG, "Error loading hostname from storage: %04x", 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);
}
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();
}
memset(&state, 0, sizeof(state));
if (!load_hostname()) {
config_set_hostname(DEFAULT_HOSTNAME);
save_hostname();
}
}
void config_set_hostname(const char *hostname)
{
if (hostname == NULL) {
ESP_LOGW(TAG, "Null pointer passed to %s(); ignored", __func__);
return;
}
size_t len = strlen(hostname);
if (len >= MAX_HOSTNAME_SIZE) {
ESP_LOGW(
TAG, "Hostname \"%s\" exceeds maximum size; truncated",
hostname);
len = MAX_HOSTNAME_SIZE - 1;
}
memcpy(state.config.hostname, hostname, len);
state.config.hostname[len] = '\0';
save_hostname();
for (unsigned i = 0; i < state.hostname_consumers.count; ++i)
state.hostname_consumers.callbacks[i](state.config.hostname);
}
size_t config_get_hostname(char *buffer, size_t buffer_size)
{
size_t len = strlen(state.config.hostname);
if (len < buffer_size) {
memcpy(buffer, state.config.hostname, len);
buffer[len] = '\0';
}
return len;
}
void config_add_hostname_consumer(ConfigStringCallback callback)
{
if (callback == NULL) {
} else if (state.hostname_consumers.count >= MAX_CONSUMERS) {
ESP_LOGE(
TAG, "Max consumers exceeded for hostname; callback discarded");
} else {
state.hostname_consumers.callbacks[state.hostname_consumers.count]
= callback;
++state.hostname_consumers.count;
}
}