/* * 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 #include #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; } }