Files
ESP32_Serial_Swiss_Army_Knife/src/serial_config.c
Commander1024 535c27350d Add Phase 1 UART service foundation
Add versioned NVS-backed configuration, buffered UART1 I/O, modem
monitoring, counters, and serial console controls. Coordinate UART1
ownership with Phase 0 diagnostics and document loopback verification.
2026-08-22 23:23:13 +02:00

408 lines
10 KiB
C

#include "serial_config.h"
#include <stddef.h>
#include <string.h>
#include "nvs.h"
#include "nvs_flash.h"
void serial_config_defaults(serial_config_t *config)
{
if (config == NULL) {
return;
}
*config = (serial_config_t) {
.version = SERIAL_CONFIG_VERSION,
.baud_rate = 115200,
.data_bits = SERIAL_CONFIG_DATA_BITS_8,
.parity = SERIAL_CONFIG_PARITY_NONE,
.stop_bits = SERIAL_CONFIG_STOP_BITS_1,
.flow_control = SERIAL_CONFIG_FLOW_CONTROL_NONE,
.dtr_behavior = SERIAL_CONFIG_DTR_INACTIVE,
.rts_threshold = SERIAL_CONFIG_DEFAULT_RTS_THRESHOLD,
};
}
esp_err_t serial_config_validate(const serial_config_t *config)
{
if (config == NULL ||
config->version != SERIAL_CONFIG_VERSION ||
config->baud_rate < SERIAL_CONFIG_MIN_BAUD_RATE ||
config->baud_rate > SERIAL_CONFIG_MAX_BAUD_RATE ||
config->rts_threshold == 0 ||
config->rts_threshold > SERIAL_CONFIG_MAX_RTS_THRESHOLD) {
return ESP_ERR_INVALID_ARG;
}
switch (config->data_bits) {
case SERIAL_CONFIG_DATA_BITS_7:
case SERIAL_CONFIG_DATA_BITS_8:
break;
default:
return ESP_ERR_INVALID_ARG;
}
switch (config->parity) {
case SERIAL_CONFIG_PARITY_NONE:
case SERIAL_CONFIG_PARITY_EVEN:
case SERIAL_CONFIG_PARITY_ODD:
break;
default:
return ESP_ERR_INVALID_ARG;
}
switch (config->stop_bits) {
case SERIAL_CONFIG_STOP_BITS_1:
case SERIAL_CONFIG_STOP_BITS_2:
break;
default:
return ESP_ERR_INVALID_ARG;
}
switch (config->flow_control) {
case SERIAL_CONFIG_FLOW_CONTROL_NONE:
case SERIAL_CONFIG_FLOW_CONTROL_RTS_CTS:
break;
default:
return ESP_ERR_INVALID_ARG;
}
switch (config->dtr_behavior) {
case SERIAL_CONFIG_DTR_INACTIVE:
case SERIAL_CONFIG_DTR_ACTIVE:
case SERIAL_CONFIG_DTR_ON_CONNECT:
break;
default:
return ESP_ERR_INVALID_ARG;
}
return ESP_OK;
}
esp_err_t serial_config_to_uart_config(const serial_config_t *config, uart_config_t *uart_config)
{
if (uart_config == NULL) {
return ESP_ERR_INVALID_ARG;
}
esp_err_t err = serial_config_validate(config);
if (err != ESP_OK) {
return err;
}
uart_word_length_t data_bits;
switch (config->data_bits) {
case SERIAL_CONFIG_DATA_BITS_7:
data_bits = UART_DATA_7_BITS;
break;
case SERIAL_CONFIG_DATA_BITS_8:
data_bits = UART_DATA_8_BITS;
break;
default:
return ESP_ERR_INVALID_ARG;
}
uart_parity_t parity;
switch (config->parity) {
case SERIAL_CONFIG_PARITY_NONE:
parity = UART_PARITY_DISABLE;
break;
case SERIAL_CONFIG_PARITY_EVEN:
parity = UART_PARITY_EVEN;
break;
case SERIAL_CONFIG_PARITY_ODD:
parity = UART_PARITY_ODD;
break;
default:
return ESP_ERR_INVALID_ARG;
}
uart_stop_bits_t stop_bits;
switch (config->stop_bits) {
case SERIAL_CONFIG_STOP_BITS_1:
stop_bits = UART_STOP_BITS_1;
break;
case SERIAL_CONFIG_STOP_BITS_2:
stop_bits = UART_STOP_BITS_2;
break;
default:
return ESP_ERR_INVALID_ARG;
}
uart_hw_flowcontrol_t flow_control;
switch (config->flow_control) {
case SERIAL_CONFIG_FLOW_CONTROL_NONE:
flow_control = UART_HW_FLOWCTRL_DISABLE;
break;
case SERIAL_CONFIG_FLOW_CONTROL_RTS_CTS:
flow_control = UART_HW_FLOWCTRL_CTS_RTS;
break;
default:
return ESP_ERR_INVALID_ARG;
}
*uart_config = (uart_config_t) {
.baud_rate = (int)config->baud_rate,
.data_bits = data_bits,
.parity = parity,
.stop_bits = stop_bits,
.flow_ctrl = flow_control,
.rx_flow_ctrl_thresh = (uint8_t)config->rts_threshold,
.source_clk = UART_SCLK_DEFAULT,
};
return ESP_OK;
}
bool serial_config_parse_data_bits(const char *text, serial_config_data_bits_t *value)
{
if (text == NULL || value == NULL) {
return false;
}
if (strcmp(text, "7") == 0) {
*value = SERIAL_CONFIG_DATA_BITS_7;
return true;
}
if (strcmp(text, "8") == 0) {
*value = SERIAL_CONFIG_DATA_BITS_8;
return true;
}
return false;
}
const char *serial_config_data_bits_to_string(serial_config_data_bits_t value)
{
switch (value) {
case SERIAL_CONFIG_DATA_BITS_7:
return "7";
case SERIAL_CONFIG_DATA_BITS_8:
return "8";
default:
return NULL;
}
}
bool serial_config_parse_parity(const char *text, serial_config_parity_t *value)
{
if (text == NULL || value == NULL) {
return false;
}
if (strcmp(text, "none") == 0) {
*value = SERIAL_CONFIG_PARITY_NONE;
return true;
}
if (strcmp(text, "even") == 0) {
*value = SERIAL_CONFIG_PARITY_EVEN;
return true;
}
if (strcmp(text, "odd") == 0) {
*value = SERIAL_CONFIG_PARITY_ODD;
return true;
}
return false;
}
const char *serial_config_parity_to_string(serial_config_parity_t value)
{
switch (value) {
case SERIAL_CONFIG_PARITY_NONE:
return "none";
case SERIAL_CONFIG_PARITY_EVEN:
return "even";
case SERIAL_CONFIG_PARITY_ODD:
return "odd";
default:
return NULL;
}
}
bool serial_config_parse_stop_bits(const char *text, serial_config_stop_bits_t *value)
{
if (text == NULL || value == NULL) {
return false;
}
if (strcmp(text, "1") == 0) {
*value = SERIAL_CONFIG_STOP_BITS_1;
return true;
}
if (strcmp(text, "2") == 0) {
*value = SERIAL_CONFIG_STOP_BITS_2;
return true;
}
return false;
}
const char *serial_config_stop_bits_to_string(serial_config_stop_bits_t value)
{
switch (value) {
case SERIAL_CONFIG_STOP_BITS_1:
return "1";
case SERIAL_CONFIG_STOP_BITS_2:
return "2";
default:
return NULL;
}
}
bool serial_config_parse_flow_control(const char *text, serial_config_flow_control_t *value)
{
if (text == NULL || value == NULL) {
return false;
}
if (strcmp(text, "none") == 0) {
*value = SERIAL_CONFIG_FLOW_CONTROL_NONE;
return true;
}
if (strcmp(text, "rts-cts") == 0) {
*value = SERIAL_CONFIG_FLOW_CONTROL_RTS_CTS;
return true;
}
return false;
}
const char *serial_config_flow_control_to_string(serial_config_flow_control_t value)
{
switch (value) {
case SERIAL_CONFIG_FLOW_CONTROL_NONE:
return "none";
case SERIAL_CONFIG_FLOW_CONTROL_RTS_CTS:
return "rts-cts";
default:
return NULL;
}
}
bool serial_config_parse_dtr_behavior(const char *text, serial_config_dtr_behavior_t *value)
{
if (text == NULL || value == NULL) {
return false;
}
if (strcmp(text, "inactive") == 0) {
*value = SERIAL_CONFIG_DTR_INACTIVE;
return true;
}
if (strcmp(text, "active") == 0) {
*value = SERIAL_CONFIG_DTR_ACTIVE;
return true;
}
if (strcmp(text, "on-connect") == 0) {
*value = SERIAL_CONFIG_DTR_ON_CONNECT;
return true;
}
return false;
}
const char *serial_config_dtr_behavior_to_string(serial_config_dtr_behavior_t value)
{
switch (value) {
case SERIAL_CONFIG_DTR_INACTIVE:
return "inactive";
case SERIAL_CONFIG_DTR_ACTIVE:
return "active";
case SERIAL_CONFIG_DTR_ON_CONNECT:
return "on-connect";
default:
return NULL;
}
}
esp_err_t serial_config_storage_init(void)
{
/*
* Never erase the shared default NVS partition automatically. Future Wi-Fi,
* certificates, and provisioning data will live there too; destructive
* recovery belongs behind an explicit factory-reset operation.
*/
return nvs_flash_init();
}
esp_err_t serial_config_load(serial_config_t *config, bool *used_stored_config)
{
if (config == NULL || used_stored_config == NULL) {
return ESP_ERR_INVALID_ARG;
}
/* Callers always receive a usable configuration when storage is absent or stale. */
serial_config_defaults(config);
*used_stored_config = false;
esp_err_t err = serial_config_storage_init();
if (err != ESP_OK) {
return err;
}
nvs_handle_t handle;
err = nvs_open(SERIAL_CONFIG_NVS_NAMESPACE, NVS_READONLY, &handle);
if (err == ESP_ERR_NVS_NOT_FOUND) {
return ESP_OK;
}
if (err != ESP_OK) {
return err;
}
size_t stored_size = 0;
err = nvs_get_blob(handle, SERIAL_CONFIG_NVS_BLOB_KEY, NULL, &stored_size);
if (err == ESP_ERR_NVS_NOT_FOUND || err == ESP_ERR_NVS_TYPE_MISMATCH) {
nvs_close(handle);
return ESP_OK;
}
if (err != ESP_OK) {
nvs_close(handle);
return err;
}
if (stored_size != sizeof(serial_config_t)) {
nvs_close(handle);
return ESP_OK;
}
serial_config_t stored_config;
err = nvs_get_blob(handle, SERIAL_CONFIG_NVS_BLOB_KEY, &stored_config, &stored_size);
nvs_close(handle);
if (err == ESP_ERR_NVS_INVALID_LENGTH) {
return ESP_OK;
}
if (err != ESP_OK) {
return err;
}
if (stored_size != sizeof(stored_config) || serial_config_validate(&stored_config) != ESP_OK) {
return ESP_OK;
}
*config = stored_config;
*used_stored_config = true;
return ESP_OK;
}
esp_err_t serial_config_save(const serial_config_t *config)
{
esp_err_t err = serial_config_validate(config);
if (err != ESP_OK) {
return err;
}
err = serial_config_storage_init();
if (err != ESP_OK) {
return err;
}
nvs_handle_t handle;
err = nvs_open(SERIAL_CONFIG_NVS_NAMESPACE, NVS_READWRITE, &handle);
if (err != ESP_OK) {
return err;
}
err = nvs_set_blob(handle, SERIAL_CONFIG_NVS_BLOB_KEY, config, sizeof(*config));
if (err == ESP_OK) {
err = nvs_commit(handle);
}
nvs_close(handle);
return err;
}
esp_err_t serial_config_reset_storage(void)
{
serial_config_t defaults;
serial_config_defaults(&defaults);
return serial_config_save(&defaults);
}