Files
TCP2UART/App/config.c
T

482 lines
14 KiB
C

/**
* @file config.c
* @brief Bare-metal AT command configuration module implementation.
*/
#include "config.h"
#include "flash_param.h"
#include "usart.h"
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "SEGGER_RTT.h"
#define CONFIG_TX_BUFFER_SIZE 256u
#define CONFIG_CMD_MAX_LEN 128u
#define CONFIG_UART_HANDLE huart1
static device_config_t g_config;
static volatile bool g_reset_requested;
static char g_uart_cmd_buffer[CONFIG_CMD_MAX_LEN];
static uint16_t g_uart_cmd_len;
static char g_pending_cmd_buffer[CONFIG_CMD_MAX_LEN];
static volatile uint16_t g_pending_cmd_len;
static volatile bool g_pending_cmd_ready;
static uint32_t config_calc_crc(const device_config_t *cfg)
{
return flash_param_crc32(cfg, offsetof(device_config_t, crc));
}
static const char *skip_whitespace(const char *str)
{
while (*str == ' ' || *str == '\t') {
++str;
}
return str;
}
static void trim_trailing(char *str)
{
int len = (int)strlen(str);
while (len > 0 && (str[len - 1] == ' ' || str[len - 1] == '\t' || str[len - 1] == '\r' || str[len - 1] == '\n')) {
str[--len] = '\0';
}
}
static bool equals_ignore_case(const char *a, const char *b)
{
while (*a != '\0' && *b != '\0') {
char c1 = *a++;
char c2 = *b++;
if (c1 >= 'a' && c1 <= 'z') c1 -= 32;
if (c2 >= 'a' && c2 <= 'z') c2 -= 32;
if (c1 != c2) {
return false;
}
}
return (*a == '\0' && *b == '\0');
}
static int parse_baudrate_value(const char *value, uint32_t *baudrate)
{
char *endptr;
unsigned long parsed;
if (value == NULL || baudrate == NULL) {
return -1;
}
parsed = strtoul(value, &endptr, 10);
if (endptr == value || *skip_whitespace(endptr) != '\0') {
return -1;
}
if (parsed < 1200ul || parsed > 921600ul) {
return -1;
}
*baudrate = (uint32_t)parsed;
return 0;
}
static at_result_t handle_query(char *response, uint16_t max_len)
{
char ip_str[16];
char mask_str[16];
char gw_str[16];
char rip_str[16];
char mac_str[18];
config_ip_to_str(g_config.ip, ip_str);
config_ip_to_str(g_config.mask, mask_str);
config_ip_to_str(g_config.gw, gw_str);
config_ip_to_str(g_config.remote_ip, rip_str);
config_mac_to_str(g_config.mac, mac_str);
snprintf(response, max_len,
"MAC: %s\r\n"
"DHCP: %u\r\n"
"IP: %s\r\n"
"MASK: %s\r\n"
"GW: %s\r\n"
"PORT: %u\r\n"
"RIP: %s\r\n"
"RPORT: %u\r\n"
"BAUD1: %lu\r\n"
"BAUD2: %lu\r\n",
mac_str,
g_config.dhcp_enable,
ip_str,
mask_str,
gw_str,
g_config.server_port,
rip_str,
g_config.remote_port,
g_config.uart2_baudrate,
g_config.uart3_baudrate);
return AT_OK;
}
int config_init(void)
{
flash_param_init();
return config_load();
}
int config_load(void)
{
if (flash_param_read(&g_config, sizeof(g_config)) != 0 ||
g_config.magic != CONFIG_MAGIC ||
g_config.version != CONFIG_VERSION ||
g_config.crc != config_calc_crc(&g_config)) {
config_set_defaults();
return -1;
}
return 0;
}
int config_save(void)
{
g_config.magic = CONFIG_MAGIC;
g_config.version = CONFIG_VERSION;
g_config.crc = config_calc_crc(&g_config);
return flash_param_write(&g_config, sizeof(g_config));
}
void config_set_defaults(void)
{
const uint8_t default_ip[] = DEFAULT_IP;
const uint8_t default_mask[] = DEFAULT_MASK;
const uint8_t default_gw[] = DEFAULT_GW;
const uint8_t default_mac[] = DEFAULT_MAC;
const uint8_t default_rip[] = DEFAULT_REMOTE_IP;
memset(&g_config, 0, sizeof(g_config));
g_config.magic = CONFIG_MAGIC;
g_config.version = CONFIG_VERSION;
memcpy(g_config.mac, default_mac, sizeof(g_config.mac));
memcpy(g_config.ip, default_ip, sizeof(g_config.ip));
memcpy(g_config.mask, default_mask, sizeof(g_config.mask));
memcpy(g_config.gw, default_gw, sizeof(g_config.gw));
memcpy(g_config.remote_ip, default_rip, sizeof(g_config.remote_ip));
g_config.dhcp_enable = DEFAULT_DHCP_ENABLE;
g_config.server_port = DEFAULT_SERVER_PORT;
g_config.remote_port = DEFAULT_REMOTE_PORT;
g_config.reconnect_interval = DEFAULT_RECONNECT_MS;
g_config.uart2_baudrate = DEFAULT_UART_BAUDRATE;
g_config.uart3_baudrate = DEFAULT_UART_BAUDRATE;
g_config.uart2_databits = DEFAULT_UART_DATABITS;
g_config.uart2_stopbits = DEFAULT_UART_STOPBITS;
g_config.uart2_parity = DEFAULT_UART_PARITY;
g_config.uart3_databits = DEFAULT_UART_DATABITS;
g_config.uart3_stopbits = DEFAULT_UART_STOPBITS;
g_config.uart3_parity = DEFAULT_UART_PARITY;
g_config.crc = config_calc_crc(&g_config);
}
const device_config_t *config_get(void)
{
return &g_config;
}
device_config_t *config_get_mutable(void)
{
return &g_config;
}
at_result_t config_process_at_cmd(const char *cmd, char *response, uint16_t max_len)
{
char cmd_copy[CONFIG_CMD_MAX_LEN];
char *cmd_name;
char *value;
const char *p;
if (cmd == NULL || response == NULL || max_len == 0u) {
return AT_ERROR;
}
strncpy(cmd_copy, cmd, CONFIG_CMD_MAX_LEN - 1u);
cmd_copy[CONFIG_CMD_MAX_LEN - 1u] = '\0';
trim_trailing(cmd_copy);
p = skip_whitespace(cmd_copy);
if ((p[0] != 'A' && p[0] != 'a') || (p[1] != 'T' && p[1] != 't')) {
snprintf(response, max_len, "ERROR: Unknown command\r\n");
return AT_UNKNOWN_CMD;
}
if (p[2] == '\0') {
snprintf(response, max_len, "OK\r\n");
return AT_OK;
}
if (p[2] != '+') {
snprintf(response, max_len, "ERROR: Unknown command\r\n");
return AT_UNKNOWN_CMD;
}
p += 3;
value = strchr((char *)p, '=');
if (value != NULL) {
*value = '\0';
++value;
value = (char *)skip_whitespace(value);
}
cmd_name = (char *)p;
trim_trailing(cmd_name);
if ((equals_ignore_case(cmd_name, "?") || equals_ignore_case(cmd_name, "QUERY")) && value == NULL) {
return handle_query(response, max_len);
}
if (equals_ignore_case(cmd_name, "SAVE") && value == NULL) {
if (config_save() != 0) {
snprintf(response, max_len, "ERROR: Save failed\r\n");
return AT_SAVE_FAILED;
}
snprintf(response, max_len, "OK: Configuration saved\r\n");
return AT_OK;
}
if (equals_ignore_case(cmd_name, "RESET") && value == NULL) {
g_reset_requested = true;
snprintf(response, max_len, "OK: Resetting...\r\n");
return AT_OK;
}
if (equals_ignore_case(cmd_name, "DEFAULT") && value == NULL) {
config_set_defaults();
snprintf(response, max_len, "OK: Defaults restored\r\n");
return AT_OK;
}
if (equals_ignore_case(cmd_name, "IP") && value != NULL) {
if (config_str_to_ip(value, g_config.ip) != 0) {
snprintf(response, max_len, "ERROR: Invalid IP format\r\n");
return AT_INVALID_PARAM;
}
snprintf(response, max_len, "OK\r\n");
return AT_NEED_REBOOT;
}
if (equals_ignore_case(cmd_name, "MASK") && value != NULL) {
if (config_str_to_ip(value, g_config.mask) != 0) {
snprintf(response, max_len, "ERROR: Invalid mask format\r\n");
return AT_INVALID_PARAM;
}
snprintf(response, max_len, "OK\r\n");
return AT_NEED_REBOOT;
}
if (equals_ignore_case(cmd_name, "GW") && value != NULL) {
if (config_str_to_ip(value, g_config.gw) != 0) {
snprintf(response, max_len, "ERROR: Invalid gateway format\r\n");
return AT_INVALID_PARAM;
}
snprintf(response, max_len, "OK\r\n");
return AT_NEED_REBOOT;
}
if (equals_ignore_case(cmd_name, "RIP") && value != NULL) {
if (config_str_to_ip(value, g_config.remote_ip) != 0) {
snprintf(response, max_len, "ERROR: Invalid remote IP format\r\n");
return AT_INVALID_PARAM;
}
snprintf(response, max_len, "OK\r\n");
return AT_NEED_REBOOT;
}
if (equals_ignore_case(cmd_name, "MAC") && value != NULL) {
if (config_str_to_mac(value, g_config.mac) != 0) {
snprintf(response, max_len, "ERROR: Invalid MAC format\r\n");
return AT_INVALID_PARAM;
}
snprintf(response, max_len, "OK\r\n");
return AT_NEED_REBOOT;
}
if (equals_ignore_case(cmd_name, "PORT") && value != NULL) {
int port = atoi(value);
if (port < 1 || port > 65535) {
snprintf(response, max_len, "ERROR: Invalid port\r\n");
return AT_INVALID_PARAM;
}
g_config.server_port = (uint16_t)port;
snprintf(response, max_len, "OK\r\n");
return AT_NEED_REBOOT;
}
if (equals_ignore_case(cmd_name, "RPORT") && value != NULL) {
int port = atoi(value);
if (port < 1 || port > 65535) {
snprintf(response, max_len, "ERROR: Invalid port\r\n");
return AT_INVALID_PARAM;
}
g_config.remote_port = (uint16_t)port;
snprintf(response, max_len, "OK\r\n");
return AT_NEED_REBOOT;
}
if (equals_ignore_case(cmd_name, "BAUD1") && value != NULL) {
if (parse_baudrate_value(value, &g_config.uart2_baudrate) != 0) {
snprintf(response, max_len, "ERROR: Invalid baudrate\r\n");
return AT_INVALID_PARAM;
}
snprintf(response, max_len, "OK\r\n");
return AT_NEED_REBOOT;
}
if (equals_ignore_case(cmd_name, "BAUD2") && value != NULL) {
if (parse_baudrate_value(value, &g_config.uart3_baudrate) != 0) {
snprintf(response, max_len, "ERROR: Invalid baudrate\r\n");
return AT_INVALID_PARAM;
}
snprintf(response, max_len, "OK\r\n");
return AT_NEED_REBOOT;
}
if (equals_ignore_case(cmd_name, "DHCP") && value != NULL) {
int dhcp = atoi(value);
if (dhcp != 0 && dhcp != 1) {
snprintf(response, max_len, "ERROR: Invalid value\r\n");
return AT_INVALID_PARAM;
}
if (dhcp != 0) {
snprintf(response, max_len, "ERROR: DHCP disabled in this build\r\n");
return AT_INVALID_PARAM;
}
g_config.dhcp_enable = (uint8_t)dhcp;
snprintf(response, max_len, "OK\r\n");
return AT_NEED_REBOOT;
}
snprintf(response, max_len, "ERROR: Unknown command\r\n");
return AT_UNKNOWN_CMD;
}
void config_ip_to_str(const uint8_t *ip, char *str)
{
sprintf(str, "%u.%u.%u.%u", ip[0], ip[1], ip[2], ip[3]);
}
int config_str_to_ip(const char *str, uint8_t *ip)
{
int a, b, c, d;
if (sscanf(str, "%d.%d.%d.%d", &a, &b, &c, &d) != 4) {
return -1;
}
if (a < 0 || a > 255 || b < 0 || b > 255 || c < 0 || c > 255 || d < 0 || d > 255) {
return -1;
}
ip[0] = (uint8_t)a;
ip[1] = (uint8_t)b;
ip[2] = (uint8_t)c;
ip[3] = (uint8_t)d;
return 0;
}
void config_mac_to_str(const uint8_t *mac, char *str)
{
sprintf(str, "%02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
}
int config_str_to_mac(const char *str, uint8_t *mac)
{
int a[6];
if (sscanf(str, "%x:%x:%x:%x:%x:%x", &a[0], &a[1], &a[2], &a[3], &a[4], &a[5]) != 6 &&
sscanf(str, "%x-%x-%x-%x-%x-%x", &a[0], &a[1], &a[2], &a[3], &a[4], &a[5]) != 6) {
return -1;
}
for (int i = 0; i < 6; ++i) {
if (a[i] < 0 || a[i] > 255) {
return -1;
}
mac[i] = (uint8_t)a[i];
}
return 0;
}
void config_poll(void)
{
if (g_pending_cmd_ready) {
uint16_t len = g_pending_cmd_len;
g_pending_cmd_ready = false;
g_pending_cmd_len = 0u;
(void)config_try_process_frame((const uint8_t *)g_pending_cmd_buffer, len);
}
}
void config_uart_rx_byte(uint8_t byte)
{
if (byte == '\r' || byte == '\n') {
if (g_uart_cmd_len > 0u) {
if (!g_pending_cmd_ready) {
memcpy(g_pending_cmd_buffer, g_uart_cmd_buffer, g_uart_cmd_len);
g_pending_cmd_buffer[g_uart_cmd_len] = '\0';
g_pending_cmd_len = g_uart_cmd_len;
g_pending_cmd_ready = true;
}
g_uart_cmd_len = 0u;
}
return;
}
if (g_uart_cmd_len < (CONFIG_CMD_MAX_LEN - 1u)) {
g_uart_cmd_buffer[g_uart_cmd_len++] = (char)byte;
g_uart_cmd_buffer[g_uart_cmd_len] = '\0';
} else {
g_uart_cmd_len = 0u;
}
}
bool config_try_process_frame(const uint8_t *data, uint16_t len)
{
char response[CONFIG_TX_BUFFER_SIZE];
char cmd_buffer[CONFIG_CMD_MAX_LEN];
at_result_t result;
HAL_StatusTypeDef tx_status;
if (data == NULL || len < 2u) {
return false;
}
if (len >= CONFIG_CMD_MAX_LEN) {
len = CONFIG_CMD_MAX_LEN - 1u;
}
memcpy(cmd_buffer, data, len);
cmd_buffer[len] = '\0';
if (((cmd_buffer[0] != 'A') && (cmd_buffer[0] != 'a')) ||
((cmd_buffer[1] != 'T') && (cmd_buffer[1] != 't'))) {
return false;
}
result = config_process_at_cmd(cmd_buffer, response, sizeof(response));
tx_status = HAL_UART_Transmit(&CONFIG_UART_HANDLE, (uint8_t *)response, (uint16_t)strlen(response), 1000u);
if (result == AT_NEED_REBOOT) {
static const char hint[] = "Note: Use AT+SAVE then AT+RESET to apply changes\r\n";
tx_status = HAL_UART_Transmit(&CONFIG_UART_HANDLE, (uint8_t *)hint, sizeof(hint) - 1u, 1000u);
}
return true;
}
bool config_is_reset_requested(void)
{
return g_reset_requested;
}
void config_clear_reset_requested(void)
{
g_reset_requested = false;
}