/** * @file config.c * @brief Bare-metal AT command configuration module implementation. */ #include "config.h" #include "flash_param.h" #include "usart.h" #include #include #include #include #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; }