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.
408 lines
10 KiB
C
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);
|
|
}
|