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.
This commit is contained in:
@@ -0,0 +1,377 @@
|
||||
#include "serial_console.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <inttypes.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "esp_console.h"
|
||||
#include "esp_err.h"
|
||||
#include "rs232_port_owner.h"
|
||||
#include "serial_config.h"
|
||||
#include "serial_service.h"
|
||||
|
||||
static void print_usage(void)
|
||||
{
|
||||
printf("Usage:\n");
|
||||
printf(" serial status\n");
|
||||
printf(" serial start|stop\n");
|
||||
printf(" serial set <baud|data-bits|parity|stop-bits|flow|dtr|rts-threshold> <value>\n");
|
||||
printf(" serial save|load|defaults|reset\n");
|
||||
printf(" serial counters|clear-counters\n");
|
||||
printf(" serial send-hex <hex-bytes>\n");
|
||||
printf(" serial read [maximum-bytes]\n");
|
||||
}
|
||||
|
||||
static void print_config(const serial_config_t *config)
|
||||
{
|
||||
printf("Configuration v%lu: baud=%lu, data-bits=%s, parity=%s, stop-bits=%s, flow=%s, DTR=%s, RTS-threshold=%lu\n",
|
||||
(unsigned long)config->version,
|
||||
(unsigned long)config->baud_rate,
|
||||
serial_config_data_bits_to_string(config->data_bits),
|
||||
serial_config_parity_to_string(config->parity),
|
||||
serial_config_stop_bits_to_string(config->stop_bits),
|
||||
serial_config_flow_control_to_string(config->flow_control),
|
||||
serial_config_dtr_behavior_to_string(config->dtr_behavior),
|
||||
(unsigned long)config->rts_threshold);
|
||||
}
|
||||
|
||||
static bool parse_unsigned(const char *text, uint32_t minimum, uint32_t maximum, uint32_t *value)
|
||||
{
|
||||
char *end = NULL;
|
||||
errno = 0;
|
||||
unsigned long parsed = strtoul(text, &end, 10);
|
||||
if (errno != 0 || end == text || *end != '\0' ||
|
||||
parsed < minimum || parsed > maximum) {
|
||||
return false;
|
||||
}
|
||||
*value = (uint32_t)parsed;
|
||||
return true;
|
||||
}
|
||||
|
||||
static int show_status(void)
|
||||
{
|
||||
serial_config_t config;
|
||||
esp_err_t err = serial_service_get_config(&config);
|
||||
if (err != ESP_OK) {
|
||||
printf("Could not read serial configuration: %s\n", esp_err_to_name(err));
|
||||
return 1;
|
||||
}
|
||||
|
||||
bool running = serial_service_is_running();
|
||||
rs232_port_owner_t owner = rs232_port_get_owner();
|
||||
printf("UART service: %s\n", running ? "running" : "stopped");
|
||||
printf("RS-232 port owner: %s\n", rs232_port_owner_to_string(owner));
|
||||
print_config(&config);
|
||||
printf("Buffers: RX-available=%u TX-pending=%u\n",
|
||||
(unsigned int)serial_service_rx_available(),
|
||||
(unsigned int)serial_service_tx_pending());
|
||||
|
||||
if (running) {
|
||||
serial_modem_state_t modem;
|
||||
serial_service_get_modem_state(&modem);
|
||||
printf("Modem asserted: DCD=%d DSR=%d CTS=%d RI=%d; valid-voltage VLD=%d\n",
|
||||
modem.dcd, modem.dsr, modem.cts, modem.ri, modem.valid);
|
||||
} else if (owner == RS232_PORT_OWNER_NONE) {
|
||||
printf("Phase 0 hardware commands are available while the service is stopped.\n");
|
||||
} else {
|
||||
printf("Phase 0 commands are unavailable until the current owner releases the port.\n");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int show_counters(void)
|
||||
{
|
||||
serial_service_counters_t counters;
|
||||
serial_service_get_counters(&counters);
|
||||
|
||||
printf("Data: RX=%" PRIu64 " RX-dropped=%" PRIu64
|
||||
" TX-queued=%" PRIu64 " TX-to-UART=%" PRIu64
|
||||
" TX-dropped=%" PRIu64 "\n",
|
||||
counters.rx_bytes,
|
||||
counters.rx_dropped_bytes,
|
||||
counters.tx_queued_bytes,
|
||||
counters.tx_sent_to_uart_bytes,
|
||||
counters.tx_dropped_bytes);
|
||||
printf("UART errors: frame=%" PRIu64 " parity=%" PRIu64
|
||||
" FIFO-overflow=%" PRIu64 " buffer-full=%" PRIu64
|
||||
" break=%" PRIu64 "\n",
|
||||
counters.frame_errors,
|
||||
counters.parity_errors,
|
||||
counters.fifo_overflows,
|
||||
counters.buffer_full_events,
|
||||
counters.breaks);
|
||||
printf("Modem transitions: DCD=%" PRIu64 " DSR=%" PRIu64
|
||||
" CTS=%" PRIu64 " RI=%" PRIu64 " VLD=%" PRIu64 "\n",
|
||||
counters.dcd_transitions,
|
||||
counters.dsr_transitions,
|
||||
counters.cts_transitions,
|
||||
counters.ri_transitions,
|
||||
counters.valid_transitions);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int set_parameter(const char *parameter, const char *value)
|
||||
{
|
||||
serial_config_t candidate;
|
||||
esp_err_t err = serial_service_get_config(&candidate);
|
||||
if (err != ESP_OK) {
|
||||
printf("Could not read current configuration: %s\n", esp_err_to_name(err));
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (strcmp(parameter, "baud") == 0) {
|
||||
if (!parse_unsigned(
|
||||
value,
|
||||
SERIAL_CONFIG_MIN_BAUD_RATE,
|
||||
SERIAL_CONFIG_MAX_BAUD_RATE,
|
||||
&candidate.baud_rate)) {
|
||||
printf("Baud rate must be %u..%u.\n",
|
||||
SERIAL_CONFIG_MIN_BAUD_RATE,
|
||||
SERIAL_CONFIG_MAX_BAUD_RATE);
|
||||
return 1;
|
||||
}
|
||||
} else if (strcmp(parameter, "data-bits") == 0) {
|
||||
if (!serial_config_parse_data_bits(value, &candidate.data_bits)) {
|
||||
printf("Data bits must be 7 or 8.\n");
|
||||
return 1;
|
||||
}
|
||||
} else if (strcmp(parameter, "parity") == 0) {
|
||||
if (!serial_config_parse_parity(value, &candidate.parity)) {
|
||||
printf("Parity must be none, even, or odd.\n");
|
||||
return 1;
|
||||
}
|
||||
} else if (strcmp(parameter, "stop-bits") == 0) {
|
||||
if (!serial_config_parse_stop_bits(value, &candidate.stop_bits)) {
|
||||
printf("Stop bits must be 1 or 2.\n");
|
||||
return 1;
|
||||
}
|
||||
} else if (strcmp(parameter, "flow") == 0) {
|
||||
if (!serial_config_parse_flow_control(value, &candidate.flow_control)) {
|
||||
printf("Flow control must be none or rts-cts.\n");
|
||||
return 1;
|
||||
}
|
||||
} else if (strcmp(parameter, "dtr") == 0) {
|
||||
if (!serial_config_parse_dtr_behavior(value, &candidate.dtr_behavior)) {
|
||||
printf("DTR behavior must be inactive, active, or on-connect.\n");
|
||||
return 1;
|
||||
}
|
||||
} else if (strcmp(parameter, "rts-threshold") == 0) {
|
||||
if (!parse_unsigned(
|
||||
value,
|
||||
1,
|
||||
SERIAL_CONFIG_MAX_RTS_THRESHOLD,
|
||||
&candidate.rts_threshold)) {
|
||||
printf("RTS threshold must be 1..%u.\n", SERIAL_CONFIG_MAX_RTS_THRESHOLD);
|
||||
return 1;
|
||||
}
|
||||
} else {
|
||||
printf("Unknown serial parameter '%s'.\n", parameter);
|
||||
print_usage();
|
||||
return 1;
|
||||
}
|
||||
|
||||
err = serial_service_apply_config(&candidate);
|
||||
if (err != ESP_OK) {
|
||||
printf("Could not apply configuration: %s\n", esp_err_to_name(err));
|
||||
return 1;
|
||||
}
|
||||
|
||||
print_config(&candidate);
|
||||
printf("Applied in RAM%s; run 'serial save' to persist it.\n",
|
||||
serial_service_is_running() ? " after a controlled UART restart" : "");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int hexadecimal_value(char character)
|
||||
{
|
||||
if (character >= '0' && character <= '9') {
|
||||
return character - '0';
|
||||
}
|
||||
if (character >= 'a' && character <= 'f') {
|
||||
return character - 'a' + 10;
|
||||
}
|
||||
if (character >= 'A' && character <= 'F') {
|
||||
return character - 'A' + 10;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int send_hexadecimal(const char *text)
|
||||
{
|
||||
/* The 160-character REPL line comfortably carries at most 64 hex bytes. */
|
||||
uint8_t data[64];
|
||||
size_t text_length = strlen(text);
|
||||
if (text_length == 0 || (text_length % 2) != 0 || text_length > sizeof(data) * 2) {
|
||||
printf("Provide 1..64 bytes as an even number of hexadecimal digits without separators.\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
size_t data_length = text_length / 2;
|
||||
for (size_t index = 0; index < data_length; ++index) {
|
||||
int high = hexadecimal_value(text[index * 2]);
|
||||
int low = hexadecimal_value(text[index * 2 + 1]);
|
||||
if (high < 0 || low < 0) {
|
||||
size_t invalid_index = index * 2 + (high < 0 ? 0 : 1);
|
||||
printf("Invalid hexadecimal digit at character %u.\n", (unsigned int)invalid_index);
|
||||
return 1;
|
||||
}
|
||||
data[index] = (uint8_t)((high << 4) | low);
|
||||
}
|
||||
|
||||
size_t accepted = serial_service_write(data, data_length);
|
||||
printf("Queued %u of %u bytes.\n", (unsigned int)accepted, (unsigned int)data_length);
|
||||
return accepted == data_length ? 0 : 1;
|
||||
}
|
||||
|
||||
static int read_hexadecimal(int argc, char **argv)
|
||||
{
|
||||
uint32_t maximum = 512;
|
||||
if (argc == 3 && !parse_unsigned(argv[2], 1, 512, &maximum)) {
|
||||
printf("Read size must be 1..512 bytes.\n");
|
||||
return 1;
|
||||
}
|
||||
if (argc > 3) {
|
||||
print_usage();
|
||||
return 1;
|
||||
}
|
||||
|
||||
uint8_t data[512];
|
||||
size_t received = serial_service_read(data, maximum);
|
||||
printf("Read %u byte%s", (unsigned int)received, received == 1 ? "" : "s");
|
||||
if (received > 0) {
|
||||
printf(": ");
|
||||
for (size_t index = 0; index < received; ++index) {
|
||||
printf("%02x", data[index]);
|
||||
}
|
||||
}
|
||||
printf("\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int command_serial(int argc, char **argv)
|
||||
{
|
||||
if (argc == 1 || (argc == 2 && strcmp(argv[1], "status") == 0)) {
|
||||
return show_status();
|
||||
}
|
||||
if (argc == 2 && strcmp(argv[1], "start") == 0) {
|
||||
esp_err_t err = serial_service_start();
|
||||
if (err != ESP_OK) {
|
||||
printf("Could not start UART service: %s\n", esp_err_to_name(err));
|
||||
return 1;
|
||||
}
|
||||
return show_status();
|
||||
}
|
||||
if (argc == 2 && strcmp(argv[1], "stop") == 0) {
|
||||
esp_err_t err = serial_service_stop();
|
||||
if (err != ESP_OK) {
|
||||
printf("Could not stop UART service: %s\n", esp_err_to_name(err));
|
||||
return 1;
|
||||
}
|
||||
return show_status();
|
||||
}
|
||||
if (argc == 4 && strcmp(argv[1], "set") == 0) {
|
||||
return set_parameter(argv[2], argv[3]);
|
||||
}
|
||||
if (argc == 2 && strcmp(argv[1], "save") == 0) {
|
||||
serial_config_t config;
|
||||
esp_err_t err = serial_service_get_config(&config);
|
||||
if (err == ESP_OK) {
|
||||
err = serial_config_save(&config);
|
||||
}
|
||||
if (err != ESP_OK) {
|
||||
printf("Could not save configuration: %s\n", esp_err_to_name(err));
|
||||
return 1;
|
||||
}
|
||||
printf("Serial configuration saved to NVS.\n");
|
||||
return 0;
|
||||
}
|
||||
if (argc == 2 && strcmp(argv[1], "load") == 0) {
|
||||
serial_config_t config;
|
||||
bool used_stored_config;
|
||||
esp_err_t err = serial_config_load(&config, &used_stored_config);
|
||||
if (err == ESP_OK) {
|
||||
err = serial_service_apply_config(&config);
|
||||
}
|
||||
if (err != ESP_OK) {
|
||||
printf("Could not load configuration: %s\n", esp_err_to_name(err));
|
||||
return 1;
|
||||
}
|
||||
printf("Loaded %s configuration.\n", used_stored_config ? "stored" : "default");
|
||||
print_config(&config);
|
||||
return 0;
|
||||
}
|
||||
if (argc == 2 && strcmp(argv[1], "defaults") == 0) {
|
||||
serial_config_t config;
|
||||
serial_config_defaults(&config);
|
||||
esp_err_t err = serial_service_apply_config(&config);
|
||||
if (err != ESP_OK) {
|
||||
printf("Could not apply defaults: %s\n", esp_err_to_name(err));
|
||||
return 1;
|
||||
}
|
||||
printf("Defaults applied in RAM; run 'serial save' to persist them.\n");
|
||||
print_config(&config);
|
||||
return 0;
|
||||
}
|
||||
if (argc == 2 && strcmp(argv[1], "reset") == 0) {
|
||||
serial_config_t previous;
|
||||
serial_config_t defaults;
|
||||
serial_config_defaults(&defaults);
|
||||
esp_err_t err = serial_service_get_config(&previous);
|
||||
if (err == ESP_OK) {
|
||||
err = serial_service_apply_config(&defaults);
|
||||
}
|
||||
if (err == ESP_OK) {
|
||||
err = serial_config_reset_storage();
|
||||
if (err != ESP_OK) {
|
||||
/* Keep runtime and persisted behavior aligned if NVS cannot commit. */
|
||||
serial_service_apply_config(&previous);
|
||||
}
|
||||
}
|
||||
if (err != ESP_OK) {
|
||||
printf("Could not reset configuration: %s\n", esp_err_to_name(err));
|
||||
return 1;
|
||||
}
|
||||
printf("Defaults applied and saved to NVS.\n");
|
||||
print_config(&defaults);
|
||||
return 0;
|
||||
}
|
||||
if (argc == 2 && strcmp(argv[1], "counters") == 0) {
|
||||
return show_counters();
|
||||
}
|
||||
if (argc == 2 && strcmp(argv[1], "clear-counters") == 0) {
|
||||
serial_service_clear_counters();
|
||||
printf("Serial counters cleared.\n");
|
||||
return 0;
|
||||
}
|
||||
if (argc == 3 && strcmp(argv[1], "send-hex") == 0) {
|
||||
if (!serial_service_is_running()) {
|
||||
printf("UART service is stopped; run 'serial start' first.\n");
|
||||
return 1;
|
||||
}
|
||||
return send_hexadecimal(argv[2]);
|
||||
}
|
||||
if ((argc == 2 || argc == 3) && strcmp(argv[1], "read") == 0) {
|
||||
if (!serial_service_is_running()) {
|
||||
printf("UART service is stopped; run 'serial start' first.\n");
|
||||
return 1;
|
||||
}
|
||||
return read_hexadecimal(argc, argv);
|
||||
}
|
||||
|
||||
print_usage();
|
||||
return 1;
|
||||
}
|
||||
|
||||
esp_err_t serial_console_register_commands(void)
|
||||
{
|
||||
const esp_console_cmd_t command = {
|
||||
.command = "serial",
|
||||
.help = "Configure and control the Phase 1 UART service; use 'serial' for usage/status",
|
||||
.hint = NULL,
|
||||
.func = &command_serial,
|
||||
.argtable = NULL,
|
||||
};
|
||||
return esp_console_cmd_register(&command);
|
||||
}
|
||||
Reference in New Issue
Block a user