431 lines
12 KiB
C
431 lines
12 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>
|
|
|
|
#define CONFIG_RX_BUFFER_SIZE 128u
|
|
#define CONFIG_TX_BUFFER_SIZE 256u
|
|
#define CONFIG_CMD_MAX_LEN 128u
|
|
|
|
static device_config_t g_config;
|
|
static uint8_t g_rx_buffer[CONFIG_RX_BUFFER_SIZE];
|
|
static volatile uint16_t g_rx_index;
|
|
static volatile bool g_rx_complete;
|
|
static volatile bool g_reset_requested;
|
|
|
|
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 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) {
|
|
g_config.uart2_baudrate = (uint32_t)atoi(value);
|
|
snprintf(response, max_len, "OK\r\n");
|
|
return AT_NEED_REBOOT;
|
|
}
|
|
if (equals_ignore_case(cmd_name, "BAUD2") && value != NULL) {
|
|
g_config.uart3_baudrate = (uint32_t)atoi(value);
|
|
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_uart_idle_handler(void)
|
|
{
|
|
uint16_t dma_counter = __HAL_DMA_GET_COUNTER(huart1.hdmarx);
|
|
uint16_t len = (uint16_t)(CONFIG_RX_BUFFER_SIZE - dma_counter);
|
|
|
|
if (len > 0u) {
|
|
g_rx_index = len;
|
|
g_rx_complete = true;
|
|
}
|
|
|
|
HAL_UART_DMAStop(&huart1);
|
|
HAL_UART_Receive_DMA(&huart1, g_rx_buffer, CONFIG_RX_BUFFER_SIZE);
|
|
}
|
|
|
|
void config_start_reception(void)
|
|
{
|
|
__HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE);
|
|
HAL_UART_Receive_DMA(&huart1, g_rx_buffer, CONFIG_RX_BUFFER_SIZE);
|
|
}
|
|
|
|
void config_poll(void)
|
|
{
|
|
char response[CONFIG_TX_BUFFER_SIZE];
|
|
char cmd_buffer[CONFIG_CMD_MAX_LEN];
|
|
at_result_t result;
|
|
uint16_t len;
|
|
|
|
if (!g_rx_complete) {
|
|
return;
|
|
}
|
|
|
|
len = g_rx_index;
|
|
if (len >= CONFIG_CMD_MAX_LEN) {
|
|
len = CONFIG_CMD_MAX_LEN - 1u;
|
|
}
|
|
|
|
memcpy(cmd_buffer, g_rx_buffer, len);
|
|
cmd_buffer[len] = '\0';
|
|
g_rx_complete = false;
|
|
g_rx_index = 0u;
|
|
|
|
result = config_process_at_cmd(cmd_buffer, response, sizeof(response));
|
|
HAL_UART_Transmit(&huart1, (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";
|
|
HAL_UART_Transmit(&huart1, (uint8_t *)hint, sizeof(hint) - 1u, 1000u);
|
|
}
|
|
}
|
|
|
|
bool config_is_reset_requested(void)
|
|
{
|
|
return g_reset_requested;
|
|
}
|
|
|
|
void config_clear_reset_requested(void)
|
|
{
|
|
g_reset_requested = false;
|
|
}
|