Add config component with hostname setting
This commit is contained in:
parent
ae1d470b4b
commit
10c77203ab
5
components/config/CMakeLists.txt
Normal file
5
components/config/CMakeLists.txt
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
idf_component_register(
|
||||||
|
SRCS "config.c"
|
||||||
|
INCLUDE_DIRS "."
|
||||||
|
REQUIRES fatal nvs_flash
|
||||||
|
)
|
159
components/config/config.c
Normal file
159
components/config/config.c
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
}
|
54
components/config/config.h
Normal file
54
components/config/config.h
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
* Copyright (c) Camden Dixie O'Brien
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef CONFIG_H
|
||||||
|
#define CONFIG_H
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
#define MAX_HOSTNAME_SIZE 32
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback type for consumers of string settings.
|
||||||
|
*/
|
||||||
|
typedef void (*ConfigStringCallback)(const char *value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the configuration module.
|
||||||
|
*
|
||||||
|
* If there is a saved configuration, it will be loaded. Otherwise,
|
||||||
|
* the default configuration will be loaded and saved.
|
||||||
|
*/
|
||||||
|
void config_init();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the device's hostname.
|
||||||
|
*
|
||||||
|
* The argument should be a null-terminated string. If the maximum
|
||||||
|
* length is exceeded, the value will still be used, but will be
|
||||||
|
* truncated.
|
||||||
|
*/
|
||||||
|
void config_set_hostname(const char *hostname);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write the device's hostname into the given buffer.
|
||||||
|
*
|
||||||
|
* The length of the hostname is returned. If the value's size exceeds
|
||||||
|
* the size of the buffer, nothing will be written to the buffer but
|
||||||
|
* the length is still returned.
|
||||||
|
*/
|
||||||
|
size_t config_get_hostname(char *buffer, size_t buffer_size);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a callback for hostname updates.
|
||||||
|
*
|
||||||
|
* The function specified in the argument will be invoked whenever a
|
||||||
|
* the hostname is updated, with the new value as its argument. The
|
||||||
|
* lifetime of the passed argument will be static, but the value may
|
||||||
|
* be modified once the callback returns.
|
||||||
|
*/
|
||||||
|
void config_add_hostname_callback(ConfigStringCallback callback);
|
||||||
|
|
||||||
|
#endif
|
@ -1,5 +1,5 @@
|
|||||||
idf_component_register(
|
idf_component_register(
|
||||||
SRCS "main.c"
|
SRCS "main.c"
|
||||||
INCLUDE_DIRS "."
|
INCLUDE_DIRS "."
|
||||||
REQUIRES fatal
|
REQUIRES config
|
||||||
)
|
)
|
||||||
|
@ -3,9 +3,9 @@
|
|||||||
* Copyright (c) Camden Dixie O'Brien
|
* Copyright (c) Camden Dixie O'Brien
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "fatal.h"
|
#include "config.h"
|
||||||
|
|
||||||
void app_main(void)
|
void app_main(void)
|
||||||
{
|
{
|
||||||
FATAL();
|
config_init();
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user