/** * @file config.c * @brief AT command configuration module implementation */ #include "config.h" #include "flash_param.h" #include "usart.h" #include "FreeRTOS.h" #include "task.h" #include #include #include #include /*--------------------------------------------------------------------------- * Private Definitions *---------------------------------------------------------------------------*/ #define CONFIG_RX_BUFFER_SIZE 128 #define CONFIG_TX_BUFFER_SIZE 256 #define CONFIG_CMD_MAX_LEN 128 /* AT command prefixes */ #define AT_PREFIX "AT+" #define AT_QUERY "AT+?" /*--------------------------------------------------------------------------- * Private Variables *---------------------------------------------------------------------------*/ /* Current device configuration */ static device_config_t g_config; static uint32_t config_calc_crc(const device_config_t *cfg) { return flash_param_crc32(cfg, offsetof(device_config_t, crc)); } /* UART1 reception buffer */ static uint8_t g_rx_buffer[CONFIG_RX_BUFFER_SIZE]; static volatile uint16_t g_rx_index = 0; static volatile bool g_rx_complete = false; static volatile bool g_reset_requested = false; /*--------------------------------------------------------------------------- * Private Functions - String Utilities *---------------------------------------------------------------------------*/ /** * @brief Skip whitespace in string */ static const char *skip_whitespace(const char *str) { while (*str == ' ' || *str == '\t') { str++; } return str; } /** * @brief Trim trailing whitespace and newlines */ static void trim_trailing(char *str) { int len = strlen(str); while (len > 0 && (str[len-1] == ' ' || str[len-1] == '\t' || str[len-1] == '\r' || str[len-1] == '\n')) { str[--len] = '\0'; } } /** * @brief Compare string prefix (case-insensitive) */ static bool starts_with(const char *str, const char *prefix) { while (*prefix) { char c1 = *str++; char c2 = *prefix++; /* Convert to uppercase for comparison */ if (c1 >= 'a' && c1 <= 'z') c1 -= 32; if (c2 >= 'a' && c2 <= 'z') c2 -= 32; if (c1 != c2) { return false; } } return true; } 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'); } /*--------------------------------------------------------------------------- * Private Functions - AT Command Handlers *---------------------------------------------------------------------------*/ /** * @brief Handle AT+IP command */ static at_result_t handle_ip(const char *value, char *response, uint16_t max_len) { uint8_t ip[4]; if (config_str_to_ip(value, ip) != 0) { snprintf(response, max_len, "ERROR: Invalid IP format\r\n"); return AT_INVALID_PARAM; } memcpy(g_config.ip, ip, 4); snprintf(response, max_len, "OK\r\n"); return AT_NEED_REBOOT; } /** * @brief Handle AT+MASK command */ static at_result_t handle_mask(const char *value, char *response, uint16_t max_len) { uint8_t mask[4]; if (config_str_to_ip(value, mask) != 0) { snprintf(response, max_len, "ERROR: Invalid mask format\r\n"); return AT_INVALID_PARAM; } memcpy(g_config.mask, mask, 4); snprintf(response, max_len, "OK\r\n"); return AT_NEED_REBOOT; } /** * @brief Handle AT+GW command */ static at_result_t handle_gw(const char *value, char *response, uint16_t max_len) { uint8_t gw[4]; if (config_str_to_ip(value, gw) != 0) { snprintf(response, max_len, "ERROR: Invalid gateway format\r\n"); return AT_INVALID_PARAM; } memcpy(g_config.gw, gw, 4); snprintf(response, max_len, "OK\r\n"); return AT_NEED_REBOOT; } /** * @brief Handle AT+PORT command */ static at_result_t handle_port(const char *value, char *response, uint16_t max_len) { int port = atoi(value); if (port < 1 || port > 65535) { snprintf(response, max_len, "ERROR: Invalid port (1-65535)\r\n"); return AT_INVALID_PARAM; } g_config.server_port = (uint16_t)port; snprintf(response, max_len, "OK\r\n"); return AT_NEED_REBOOT; } /** * @brief Handle AT+RIP command */ static at_result_t handle_rip(const char *value, char *response, uint16_t max_len) { uint8_t ip[4]; if (config_str_to_ip(value, ip) != 0) { snprintf(response, max_len, "ERROR: Invalid remote IP format\r\n"); return AT_INVALID_PARAM; } memcpy(g_config.remote_ip, ip, 4); snprintf(response, max_len, "OK\r\n"); return AT_NEED_REBOOT; } /** * @brief Handle AT+RPORT command */ static at_result_t handle_rport(const char *value, char *response, uint16_t max_len) { int port = atoi(value); if (port < 1 || port > 65535) { snprintf(response, max_len, "ERROR: Invalid port (1-65535)\r\n"); return AT_INVALID_PARAM; } g_config.remote_port = (uint16_t)port; snprintf(response, max_len, "OK\r\n"); return AT_NEED_REBOOT; } /** * @brief Handle AT+BAUD1 command (UART2) */ static at_result_t handle_baud1(const char *value, char *response, uint16_t max_len) { int baud = atoi(value); /* Validate common baud rates */ if (baud != 9600 && baud != 19200 && baud != 38400 && baud != 57600 && baud != 115200 && baud != 230400 && baud != 460800 && baud != 921600) { snprintf(response, max_len, "ERROR: Invalid baud rate\r\n"); return AT_INVALID_PARAM; } g_config.uart2_baudrate = (uint32_t)baud; snprintf(response, max_len, "OK\r\n"); return AT_NEED_REBOOT; } /** * @brief Handle AT+BAUD2 command (UART3) */ static at_result_t handle_baud2(const char *value, char *response, uint16_t max_len) { int baud = atoi(value); /* Validate common baud rates */ if (baud != 9600 && baud != 19200 && baud != 38400 && baud != 57600 && baud != 115200 && baud != 230400 && baud != 460800 && baud != 921600) { snprintf(response, max_len, "ERROR: Invalid baud rate\r\n"); return AT_INVALID_PARAM; } g_config.uart3_baudrate = (uint32_t)baud; snprintf(response, max_len, "OK\r\n"); return AT_NEED_REBOOT; } /** * @brief Handle AT+MAC command */ static at_result_t handle_mac(const char *value, char *response, uint16_t max_len) { uint8_t mac[6]; if (config_str_to_mac(value, mac) != 0) { snprintf(response, max_len, "ERROR: Invalid MAC format\r\n"); return AT_INVALID_PARAM; } memcpy(g_config.mac, mac, 6); snprintf(response, max_len, "OK\r\n"); return AT_NEED_REBOOT; } /** * @brief Handle AT+DHCP command */ static at_result_t handle_dhcp(const char *value, char *response, uint16_t max_len) { int dhcp = atoi(value); if (dhcp != 0 && dhcp != 1) { snprintf(response, max_len, "ERROR: Invalid value (0 or 1)\r\n"); return AT_INVALID_PARAM; } g_config.dhcp_enable = (uint8_t)dhcp; snprintf(response, max_len, "OK\r\n"); return AT_NEED_REBOOT; } /** * @brief Handle AT+SAVE command */ static at_result_t handle_save(char *response, uint16_t max_len) { 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; } /** * @brief Handle AT+RESET command */ static at_result_t handle_reset(char *response, uint16_t max_len) { snprintf(response, max_len, "OK: Resetting...\r\n"); g_reset_requested = true; return AT_OK; } /** * @brief Handle AT+DEFAULT command */ static at_result_t handle_default(char *response, uint16_t max_len) { config_set_defaults(); snprintf(response, max_len, "OK: Defaults restored. Use AT+SAVE to save.\r\n"); return AT_OK; } /** * @brief Handle AT+? query command */ static at_result_t handle_query(char *response, uint16_t max_len) { char ip_str[16], mask_str[16], gw_str[16], rip_str[16], 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, "=== TCP2UART Configuration ===\r\n" "MAC: %s\r\n" "DHCP: %s\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" "==============================\r\n", mac_str, g_config.dhcp_enable ? "Enabled" : "Disabled", 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; } /*--------------------------------------------------------------------------- * Public Functions *---------------------------------------------------------------------------*/ /** * @brief Initialize configuration module */ int config_init(void) { /* Load configuration from Flash */ return config_load(); } /** * @brief Load configuration from Flash */ int config_load(void) { int ret; ret = flash_param_read(&g_config, sizeof(device_config_t)); if (ret != 0 || g_config.magic != CONFIG_MAGIC || g_config.version != CONFIG_VERSION || g_config.crc != config_calc_crc(&g_config)) { /* Invalid or corrupted configuration, use defaults */ config_set_defaults(); return -1; } return 0; } /** * @brief Save configuration to Flash */ int config_save(void) { /* Update magic and CRC before saving */ 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(device_config_t)); } /** * @brief Reset configuration to factory defaults */ void config_set_defaults(void) { uint8_t default_ip[] = DEFAULT_IP; uint8_t default_mask[] = DEFAULT_MASK; uint8_t default_gw[] = DEFAULT_GW; uint8_t default_mac[] = DEFAULT_MAC; uint8_t default_rip[] = DEFAULT_REMOTE_IP; memset(&g_config, 0, sizeof(device_config_t)); g_config.magic = CONFIG_MAGIC; g_config.version = CONFIG_VERSION; memcpy(g_config.mac, default_mac, 6); g_config.dhcp_enable = DEFAULT_DHCP_ENABLE; memcpy(g_config.ip, default_ip, 4); memcpy(g_config.mask, default_mask, 4); memcpy(g_config.gw, default_gw, 4); g_config.server_port = DEFAULT_SERVER_PORT; memcpy(g_config.remote_ip, default_rip, 4); 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); } /** * @brief Get current configuration (read-only) */ const device_config_t *config_get(void) { return &g_config; } /** * @brief Get mutable configuration */ device_config_t *config_get_mutable(void) { return &g_config; } /** * @brief Process AT command */ 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; at_result_t result = AT_UNKNOWN_CMD; if (cmd == NULL || response == NULL || max_len == 0) { return AT_ERROR; } /* Make a copy for modification */ strncpy(cmd_copy, cmd, CONFIG_CMD_MAX_LEN - 1); cmd_copy[CONFIG_CMD_MAX_LEN - 1] = '\0'; trim_trailing(cmd_copy); /* Skip leading whitespace */ const char *p = skip_whitespace(cmd_copy); /* Check for AT+ prefix */ if (!starts_with(p, "AT+")) { if (equals_ignore_case(p, "AT")) { snprintf(response, max_len, "OK\r\n"); return AT_OK; } snprintf(response, max_len, "ERROR: Unknown command\r\n"); return AT_UNKNOWN_CMD; } /* Move past AT+ */ p += 3; /* Find '=' separator if present */ value = strchr((char *)p, '='); if (value != NULL) { *value = '\0'; /* Terminate command part */ value++; /* Point to value */ value = (char *)skip_whitespace(value); } cmd_name = (char *)p; trim_trailing(cmd_name); /* Process commands */ if (equals_ignore_case(cmd_name, "IP") && value != NULL) { result = handle_ip(value, response, max_len); } else if (equals_ignore_case(cmd_name, "MASK") && value != NULL) { result = handle_mask(value, response, max_len); } else if (equals_ignore_case(cmd_name, "GW") && value != NULL) { result = handle_gw(value, response, max_len); } else if (equals_ignore_case(cmd_name, "PORT") && value != NULL) { result = handle_port(value, response, max_len); } else if (equals_ignore_case(cmd_name, "RIP") && value != NULL) { result = handle_rip(value, response, max_len); } else if (equals_ignore_case(cmd_name, "RPORT") && value != NULL) { result = handle_rport(value, response, max_len); } else if (equals_ignore_case(cmd_name, "BAUD1") && value != NULL) { result = handle_baud1(value, response, max_len); } else if (equals_ignore_case(cmd_name, "BAUD2") && value != NULL) { result = handle_baud2(value, response, max_len); } else if (equals_ignore_case(cmd_name, "MAC") && value != NULL) { result = handle_mac(value, response, max_len); } else if (equals_ignore_case(cmd_name, "DHCP") && value != NULL) { result = handle_dhcp(value, response, max_len); } else if (equals_ignore_case(cmd_name, "SAVE") && value == NULL) { result = handle_save(response, max_len); } else if (equals_ignore_case(cmd_name, "RESET") && value == NULL) { result = handle_reset(response, max_len); } else if (equals_ignore_case(cmd_name, "DEFAULT") && value == NULL) { result = handle_default(response, max_len); } else if ((equals_ignore_case(cmd_name, "?") || equals_ignore_case(cmd_name, "QUERY")) && value == NULL) { result = handle_query(response, max_len); } else { snprintf(response, max_len, "ERROR: Unknown command\r\n"); result = AT_UNKNOWN_CMD; } return result; } /** * @brief Format IP address to string */ void config_ip_to_str(const uint8_t *ip, char *str) { sprintf(str, "%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]); } /** * @brief Parse IP address from string */ 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; } /** * @brief Format MAC address to string */ 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]); } /** * @brief Parse MAC address from string */ 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) { /* Try alternate format with dashes */ if (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; } /** * @brief UART1 IDLE interrupt handler */ void config_uart_idle_handler(void) { uint16_t dma_counter = __HAL_DMA_GET_COUNTER(huart1.hdmarx); uint16_t len = CONFIG_RX_BUFFER_SIZE - dma_counter; if (len > 0) { g_rx_index = len; g_rx_complete = true; } /* Stop DMA and restart */ HAL_UART_DMAStop(&huart1); HAL_UART_Receive_DMA(&huart1, g_rx_buffer, CONFIG_RX_BUFFER_SIZE); } /** * @brief Start UART1 reception */ void config_start_reception(void) { /* Enable IDLE interrupt */ __HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE); /* Start DMA reception */ HAL_UART_Receive_DMA(&huart1, g_rx_buffer, CONFIG_RX_BUFFER_SIZE); } /** * @brief Configuration task */ void config_task(void *argument) { char response[CONFIG_TX_BUFFER_SIZE]; char cmd_buffer[CONFIG_CMD_MAX_LEN]; at_result_t result; (void)argument; /* Initialize configuration */ config_init(); /* Start UART1 reception */ config_start_reception(); /* Send startup message */ snprintf(response, sizeof(response), "\r\n=== TCP2UART v1.0 ===\r\n" "Type AT+? for configuration\r\n\r\n"); HAL_UART_Transmit(&huart1, (uint8_t *)response, strlen(response), 1000); while (1) { /* Wait for command */ if (g_rx_complete) { /* Copy command and null-terminate */ uint16_t len = g_rx_index; if (len >= CONFIG_CMD_MAX_LEN) { len = CONFIG_CMD_MAX_LEN - 1; } memcpy(cmd_buffer, g_rx_buffer, len); cmd_buffer[len] = '\0'; /* Reset reception state */ g_rx_complete = false; g_rx_index = 0; /* Process command */ result = config_process_at_cmd(cmd_buffer, response, sizeof(response)); /* Send response */ HAL_UART_Transmit(&huart1, (uint8_t *)response, strlen(response), 1000); if (g_reset_requested) { g_reset_requested = false; vTaskDelay(pdMS_TO_TICKS(100)); NVIC_SystemReset(); } /* Handle reboot needed */ if (result == AT_NEED_REBOOT) { HAL_UART_Transmit(&huart1, (uint8_t *)"Note: Use AT+SAVE then AT+RESET to apply changes\r\n", 51, 1000); } } vTaskDelay(pdMS_TO_TICKS(10)); } }