/** * @file config.c * @brief Bare-metal final AT configuration module implementation. */ #include "config.h" #include "flash_param.h" #include "../Core/Inc/usart.h" #include #include #include #include #define CONFIG_TX_BUFFER_SIZE 512u #define CONFIG_CMD_MAX_LEN 160u #define CONFIG_UART_HANDLE huart1 typedef struct { uint32_t magic; uint16_t version; uint16_t reserved; uint8_t mac[6]; uint8_t dhcp_enable; uint8_t reserved2; uint8_t ip[4]; uint8_t mask[4]; uint8_t gw[4]; uint16_t server_port; uint16_t reserved3; uint8_t remote_ip[4]; uint16_t remote_port; uint16_t reconnect_interval; uint32_t uart2_baudrate; uint32_t uart3_baudrate; uint8_t uart2_databits; uint8_t uart2_stopbits; uint8_t uart2_parity; uint8_t uart3_databits; uint8_t uart3_stopbits; uint8_t uart3_parity; uint16_t reserved4; uint32_t crc; } legacy_device_config_v2_t; static device_config_t g_config; static volatile bool g_reset_requested; static uint8_t g_uart_cmd_buffer[CONFIG_CMD_MAX_LEN]; static uint16_t g_uart_cmd_len; static bool g_uart_rx_seen_cr; 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 char g_at_response_buffer[CONFIG_TX_BUFFER_SIZE]; static char g_cmd_parse_buffer[CONFIG_CMD_MAX_LEN]; static char g_cmd_work_buffer[CONFIG_CMD_MAX_LEN]; 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 prefix_equals_ignore_case(const char *str, const char *prefix) { while (*prefix != '\0') { char c1 = *str++; char c2 = *prefix++; if (c1 >= 'a' && c1 <= 'z') { c1 -= 32; } if (c2 >= 'a' && c2 <= 'z') { c2 -= 32; } if (c1 != c2) { return 0; } } return 1; } static int parse_u32_value(const char *value, uint32_t min_value, uint32_t max_value, uint32_t *parsed_value) { char *endptr; unsigned long parsed; if (value == NULL || parsed_value == NULL) { return -1; } parsed = strtoul(value, &endptr, 10); if (endptr == value || *skip_whitespace(endptr) != '\0') { return -1; } if (parsed < min_value || parsed > max_value) { return -1; } *parsed_value = (uint32_t)parsed; return 0; } static int parse_link_uart(const char *value, uint8_t *uart) { if (equals_ignore_case(value, "U0")) { *uart = LINK_UART_U0; return 0; } if (equals_ignore_case(value, "U1")) { *uart = LINK_UART_U1; return 0; } return -1; } static const char *link_uart_to_str(uint8_t uart) { return (uart == LINK_UART_U1) ? "U1" : "U0"; } static const char *link_index_to_name(uint32_t index) { switch (index) { case CONFIG_LINK_S1: return "S1"; case CONFIG_LINK_S2: return "S2"; case CONFIG_LINK_C1: return "C1"; case CONFIG_LINK_C2: return "C2"; default: return "?"; } } static int parse_link_name(const char *value, uint32_t *index) { if (value == NULL || index == NULL) { return -1; } if (equals_ignore_case(value, "S1")) { *index = CONFIG_LINK_S1; return 0; } if (equals_ignore_case(value, "S2")) { *index = CONFIG_LINK_S2; return 0; } if (equals_ignore_case(value, "C1")) { *index = CONFIG_LINK_C1; return 0; } if (equals_ignore_case(value, "C2")) { *index = CONFIG_LINK_C2; return 0; } return -1; } static bool parse_command_with_value(const char *cmd, const char *name, const char **value) { size_t name_len; if (cmd == NULL || name == NULL || value == NULL) { return false; } name_len = strlen(name); if (!prefix_equals_ignore_case(cmd, name)) { return false; } if (cmd[name_len] != '=') { return false; } *value = skip_whitespace(cmd + name_len + 1u); return true; } static char *config_next_token(char **cursor) { char *start; char *end; if (cursor == NULL || *cursor == NULL) { return NULL; } start = *cursor; while (*start == ' ' || *start == '\t') { ++start; } if (*start == '\0') { *cursor = NULL; return NULL; } end = start; while (*end != '\0' && *end != ',') { ++end; } if (*end == ',') { *end = '\0'; *cursor = end + 1; } else { *cursor = NULL; } trim_trailing(start); return start; } static void set_link_defaults(void) { static const uint8_t zero_ip[4] = {0u, 0u, 0u, 0u}; static const uint8_t c1_ip[4] = {192u, 168u, 1u, 200u}; static const uint8_t c2_ip[4] = {192u, 168u, 1u, 201u}; memset(g_config.links, 0, sizeof(g_config.links)); g_config.links[CONFIG_LINK_S1].enabled = 1u; g_config.links[CONFIG_LINK_S1].uart = LINK_UART_U0; g_config.links[CONFIG_LINK_S1].local_port = 8080u; memcpy(g_config.links[CONFIG_LINK_S1].remote_ip, zero_ip, sizeof(zero_ip)); g_config.links[CONFIG_LINK_S2].enabled = 0u; g_config.links[CONFIG_LINK_S2].uart = LINK_UART_U1; g_config.links[CONFIG_LINK_S2].local_port = 8081u; memcpy(g_config.links[CONFIG_LINK_S2].remote_ip, zero_ip, sizeof(zero_ip)); g_config.links[CONFIG_LINK_C1].enabled = 1u; g_config.links[CONFIG_LINK_C1].uart = LINK_UART_U1; g_config.links[CONFIG_LINK_C1].local_port = 9001u; memcpy(g_config.links[CONFIG_LINK_C1].remote_ip, c1_ip, sizeof(c1_ip)); g_config.links[CONFIG_LINK_C1].remote_port = 9000u; g_config.links[CONFIG_LINK_C2].enabled = 0u; g_config.links[CONFIG_LINK_C2].uart = LINK_UART_U0; g_config.links[CONFIG_LINK_C2].local_port = 9002u; memcpy(g_config.links[CONFIG_LINK_C2].remote_ip, c2_ip, sizeof(c2_ip)); g_config.links[CONFIG_LINK_C2].remote_port = 9001u; } static void migrate_legacy_config(const legacy_device_config_v2_t *legacy) { config_set_defaults(); memcpy(g_config.net.mac, legacy->mac, sizeof(g_config.net.mac)); memcpy(g_config.net.ip, legacy->ip, sizeof(g_config.net.ip)); memcpy(g_config.net.mask, legacy->mask, sizeof(g_config.net.mask)); memcpy(g_config.net.gw, legacy->gw, sizeof(g_config.net.gw)); g_config.uart_baudrate[0] = legacy->uart2_baudrate; g_config.uart_baudrate[1] = legacy->uart3_baudrate; g_config.mux_mode = MUX_MODE_RAW; g_config.links[CONFIG_LINK_S1].enabled = (legacy->server_port != 0u) ? 1u : 0u; g_config.links[CONFIG_LINK_S1].uart = LINK_UART_U0; g_config.links[CONFIG_LINK_S1].local_port = legacy->server_port; memset(g_config.links[CONFIG_LINK_S1].remote_ip, 0, sizeof(g_config.links[CONFIG_LINK_S1].remote_ip)); g_config.links[CONFIG_LINK_S1].remote_port = 0u; g_config.links[CONFIG_LINK_S2].enabled = 0u; g_config.links[CONFIG_LINK_C1].enabled = (legacy->remote_port != 0u) ? 1u : 0u; g_config.links[CONFIG_LINK_C1].uart = LINK_UART_U1; g_config.links[CONFIG_LINK_C1].local_port = 8081u; memcpy(g_config.links[CONFIG_LINK_C1].remote_ip, legacy->remote_ip, sizeof(g_config.links[CONFIG_LINK_C1].remote_ip)); g_config.links[CONFIG_LINK_C1].remote_port = legacy->remote_port; g_config.links[CONFIG_LINK_C2].enabled = 0u; g_config.crc = config_calc_crc(&g_config); } static bool try_load_legacy_config(void) { legacy_device_config_v2_t legacy; uint32_t expected_crc; if (flash_param_read(&legacy, sizeof(legacy)) != 0) { return false; } expected_crc = flash_param_crc32(&legacy, offsetof(legacy_device_config_v2_t, crc)); if (legacy.magic != CONFIG_MAGIC || legacy.version != 0x0002u || legacy.crc != expected_crc) { return false; } migrate_legacy_config(&legacy); return true; } static at_result_t handle_summary_query(char *response, uint16_t max_len) { char ip_str[16]; char mask_str[16]; char gw_str[16]; char mac_str[18]; char rip_str[CONFIG_LINK_COUNT][16]; config_ip_to_str(g_config.net.ip, ip_str); config_ip_to_str(g_config.net.mask, mask_str); config_ip_to_str(g_config.net.gw, gw_str); config_mac_to_str(g_config.net.mac, mac_str); for (uint32_t i = 0; i < CONFIG_LINK_COUNT; ++i) { config_ip_to_str(g_config.links[i].remote_ip, rip_str[i]); } snprintf(response, max_len, "+NET:IP=%s,MASK=%s,GW=%s,MAC=%s\r\n" "+LINK:S1,EN=%u,LPORT=%u,RIP=%s,RPORT=%u,UART=%s\r\n" "+LINK:S2,EN=%u,LPORT=%u,RIP=%s,RPORT=%u,UART=%s\r\n" "+LINK:C1,EN=%u,LPORT=%u,RIP=%s,RPORT=%u,UART=%s\r\n" "+LINK:C2,EN=%u,LPORT=%u,RIP=%s,RPORT=%u,UART=%s\r\n" "+MUX:%u\r\n" "+MAP:UART2=0x04,UART3=0x08,C1=0x01,C2=0x02,S1=0x10,S2=0x20\r\n" "+BAUD:U0=%lu,U1=%lu\r\n" "OK\r\n", ip_str, mask_str, gw_str, mac_str, g_config.links[0].enabled, g_config.links[0].local_port, rip_str[0], g_config.links[0].remote_port, link_uart_to_str(g_config.links[0].uart), g_config.links[1].enabled, g_config.links[1].local_port, rip_str[1], g_config.links[1].remote_port, link_uart_to_str(g_config.links[1].uart), g_config.links[2].enabled, g_config.links[2].local_port, rip_str[2], g_config.links[2].remote_port, link_uart_to_str(g_config.links[2].uart), g_config.links[3].enabled, g_config.links[3].local_port, rip_str[3], g_config.links[3].remote_port, link_uart_to_str(g_config.links[3].uart), g_config.mux_mode, g_config.uart_baudrate[0], g_config.uart_baudrate[1]); return AT_OK; } static at_result_t handle_net_query(char *response, uint16_t max_len) { char ip_str[16]; char mask_str[16]; char gw_str[16]; char mac_str[18]; config_ip_to_str(g_config.net.ip, ip_str); config_ip_to_str(g_config.net.mask, mask_str); config_ip_to_str(g_config.net.gw, gw_str); config_mac_to_str(g_config.net.mac, mac_str); snprintf(response, max_len, "+NET:IP=%s,MASK=%s,GW=%s,MAC=%s\r\nOK\r\n", ip_str, mask_str, gw_str, mac_str); return AT_OK; } static at_result_t handle_link_query(uint32_t index, char *response, uint16_t max_len) { char rip_str[16]; if (index >= CONFIG_LINK_COUNT) { snprintf(response, max_len, "ERROR: Invalid route field\r\n"); return AT_INVALID_PARAM; } config_ip_to_str(g_config.links[index].remote_ip, rip_str); snprintf(response, max_len, "+LINK:%s,EN=%u,LPORT=%u,RIP=%s,RPORT=%u,UART=%s\r\nOK\r\n", link_index_to_name(index), g_config.links[index].enabled, g_config.links[index].local_port, rip_str, g_config.links[index].remote_port, link_uart_to_str(g_config.links[index].uart)); return AT_OK; } static at_result_t handle_all_link_query(char *response, uint16_t max_len) { char rip_str[CONFIG_LINK_COUNT][16]; for (uint32_t i = 0; i < CONFIG_LINK_COUNT; ++i) { config_ip_to_str(g_config.links[i].remote_ip, rip_str[i]); } snprintf(response, max_len, "+LINK:S1,EN=%u,LPORT=%u,RIP=%s,RPORT=%u,UART=%s\r\n" "+LINK:S2,EN=%u,LPORT=%u,RIP=%s,RPORT=%u,UART=%s\r\n" "+LINK:C1,EN=%u,LPORT=%u,RIP=%s,RPORT=%u,UART=%s\r\n" "+LINK:C2,EN=%u,LPORT=%u,RIP=%s,RPORT=%u,UART=%s\r\n" "OK\r\n", g_config.links[0].enabled, g_config.links[0].local_port, rip_str[0], g_config.links[0].remote_port, link_uart_to_str(g_config.links[0].uart), g_config.links[1].enabled, g_config.links[1].local_port, rip_str[1], g_config.links[1].remote_port, link_uart_to_str(g_config.links[1].uart), g_config.links[2].enabled, g_config.links[2].local_port, rip_str[2], g_config.links[2].remote_port, link_uart_to_str(g_config.links[2].uart), g_config.links[3].enabled, g_config.links[3].local_port, rip_str[3], g_config.links[3].remote_port, link_uart_to_str(g_config.links[3].uart)); 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)) { return 0; } if (try_load_legacy_config()) { return 0; } config_set_defaults(); return -1; } 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_NET_IP; const uint8_t default_mask[] = DEFAULT_NET_MASK; const uint8_t default_gw[] = DEFAULT_NET_GW; const uint8_t default_mac[] = DEFAULT_NET_MAC; memset(&g_config, 0, sizeof(g_config)); g_config.magic = CONFIG_MAGIC; g_config.version = CONFIG_VERSION; g_config.mux_mode = MUX_MODE_RAW; memcpy(g_config.net.ip, default_ip, sizeof(g_config.net.ip)); memcpy(g_config.net.mask, default_mask, sizeof(g_config.net.mask)); memcpy(g_config.net.gw, default_gw, sizeof(g_config.net.gw)); memcpy(g_config.net.mac, default_mac, sizeof(g_config.net.mac)); set_link_defaults(); g_config.uart_baudrate[0] = DEFAULT_UART_BAUDRATE; g_config.uart_baudrate[1] = DEFAULT_UART_BAUDRATE; 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) { const char *value; const char *p; if (cmd == NULL || response == NULL || max_len == 0u) { return AT_ERROR; } strncpy(g_cmd_work_buffer, cmd, sizeof(g_cmd_work_buffer) - 1u); g_cmd_work_buffer[sizeof(g_cmd_work_buffer) - 1u] = '\0'; trim_trailing(g_cmd_work_buffer); p = skip_whitespace(g_cmd_work_buffer); 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; if ((equals_ignore_case(p, "?") || equals_ignore_case(p, "QUERY"))) { return handle_summary_query(response, max_len); } if (equals_ignore_case(p, "SAVE")) { 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(p, "RESET")) { g_reset_requested = true; snprintf(response, max_len, "OK: Resetting...\r\n"); return AT_OK; } if (equals_ignore_case(p, "DEFAULT")) { config_set_defaults(); snprintf(response, max_len, "OK: Defaults restored\r\n"); return AT_OK; } if (equals_ignore_case(p, "MUX?")) { snprintf(response, max_len, "+MUX:%u\r\nOK\r\n", g_config.mux_mode); return AT_OK; } if (parse_command_with_value(p, "MUX", &value)) { uint32_t mux_value; if (parse_u32_value(value, 0u, 1u, &mux_value) != 0) { snprintf(response, max_len, "ERROR: Invalid value\r\n"); return AT_INVALID_PARAM; } g_config.mux_mode = (uint8_t)mux_value; snprintf(response, max_len, "OK\r\n"); return AT_NEED_REBOOT; } if (equals_ignore_case(p, "NET?")) { return handle_net_query(response, max_len); } if (parse_command_with_value(p, "NET", &value)) { char value_copy[96]; char *token; char *cursor; uint8_t ip[4]; uint8_t mask[4]; uint8_t gw[4]; uint8_t mac[6]; strncpy(value_copy, value, sizeof(value_copy) - 1u); value_copy[sizeof(value_copy) - 1u] = '\0'; cursor = value_copy; token = config_next_token(&cursor); if (token == NULL || config_str_to_ip(token, ip) != 0) { snprintf(response, max_len, "ERROR: Invalid IP format\r\n"); return AT_INVALID_PARAM; } token = config_next_token(&cursor); if (token == NULL || config_str_to_ip(token, mask) != 0) { snprintf(response, max_len, "ERROR: Invalid mask format\r\n"); return AT_INVALID_PARAM; } token = config_next_token(&cursor); if (token == NULL || config_str_to_ip(token, gw) != 0) { snprintf(response, max_len, "ERROR: Invalid gateway format\r\n"); return AT_INVALID_PARAM; } token = config_next_token(&cursor); if (token == NULL || config_str_to_mac(token, mac) != 0) { snprintf(response, max_len, "ERROR: Invalid MAC format\r\n"); return AT_INVALID_PARAM; } if (config_next_token(&cursor) != NULL) { snprintf(response, max_len, "ERROR: Invalid value\r\n"); return AT_INVALID_PARAM; } memcpy(g_config.net.ip, ip, sizeof(ip)); memcpy(g_config.net.mask, mask, sizeof(mask)); memcpy(g_config.net.gw, gw, sizeof(gw)); memcpy(g_config.net.mac, mac, sizeof(mac)); snprintf(response, max_len, "OK\r\n"); return AT_NEED_REBOOT; } if (equals_ignore_case(p, "LINK?")) { return handle_all_link_query(response, max_len); } if (parse_command_with_value(p, "LINK", &value)) { char value_copy[96]; char *cursor; char *token; uint32_t index; uint32_t enabled; uint32_t local_port; uint32_t remote_port; uint8_t rip[4]; uint8_t uart; strncpy(value_copy, value, sizeof(value_copy) - 1u); value_copy[sizeof(value_copy) - 1u] = '\0'; cursor = value_copy; token = config_next_token(&cursor); if (token == NULL || parse_link_name(token, &index) != 0) { snprintf(response, max_len, "ERROR: Invalid route field\r\n"); return AT_INVALID_PARAM; } token = config_next_token(&cursor); if (token == NULL) { return handle_link_query(index, response, max_len); } if (parse_u32_value(token, 0u, 1u, &enabled) != 0) { snprintf(response, max_len, "ERROR: Invalid value\r\n"); return AT_INVALID_PARAM; } token = config_next_token(&cursor); if (token == NULL || parse_u32_value(token, 1u, 65535u, &local_port) != 0) { snprintf(response, max_len, "ERROR: Invalid port\r\n"); return AT_INVALID_PARAM; } token = config_next_token(&cursor); if (token == NULL || config_str_to_ip(token, rip) != 0) { snprintf(response, max_len, "ERROR: Invalid remote IP format\r\n"); return AT_INVALID_PARAM; } token = config_next_token(&cursor); if (token == NULL || parse_u32_value(token, 0u, 65535u, &remote_port) != 0) { snprintf(response, max_len, "ERROR: Invalid port\r\n"); return AT_INVALID_PARAM; } token = config_next_token(&cursor); if (token == NULL || parse_link_uart(token, &uart) != 0) { snprintf(response, max_len, "ERROR: Invalid route field\r\n"); return AT_INVALID_PARAM; } if (config_next_token(&cursor) != NULL) { snprintf(response, max_len, "ERROR: Invalid value\r\n"); return AT_INVALID_PARAM; } g_config.links[index].enabled = (uint8_t)enabled; g_config.links[index].local_port = (uint16_t)local_port; memcpy(g_config.links[index].remote_ip, rip, sizeof(rip)); g_config.links[index].remote_port = (uint16_t)remote_port; g_config.links[index].uart = uart; if (handle_link_query(index, response, max_len) != AT_OK) { snprintf(response, max_len, "ERROR: Invalid route field\r\n"); return AT_INVALID_PARAM; } 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; int b; int c; int 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') { g_uart_rx_seen_cr = true; return; } if (byte == '\n') { if (g_uart_rx_seen_cr && 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; } g_uart_rx_seen_cr = false; return; } if (g_uart_rx_seen_cr) { g_uart_cmd_len = 0u; g_uart_rx_seen_cr = false; } if (g_uart_cmd_len < (CONFIG_CMD_MAX_LEN - 1u)) { g_uart_cmd_buffer[g_uart_cmd_len++] = byte; g_uart_cmd_buffer[g_uart_cmd_len] = '\0'; } else { g_uart_cmd_len = 0u; } } bool config_build_response_frame(const uint8_t *data, uint16_t len, char *response, uint16_t max_len, at_result_t *result) { if (data == NULL || response == NULL || len < 2u || max_len == 0u) { return false; } if (len >= CONFIG_CMD_MAX_LEN) { return false; } memcpy(g_cmd_parse_buffer, data, len); g_cmd_parse_buffer[len] = '\0'; if (((g_cmd_parse_buffer[0] != 'A') && (g_cmd_parse_buffer[0] != 'a')) || ((g_cmd_parse_buffer[1] != 'T') && (g_cmd_parse_buffer[1] != 't'))) { return false; } *result = config_process_at_cmd(g_cmd_parse_buffer, response, max_len); return true; } bool config_try_process_frame(const uint8_t *data, uint16_t len) { at_result_t result = AT_ERROR; if (!config_build_response_frame(data, len, g_at_response_buffer, sizeof(g_at_response_buffer), &result)) { return false; } if (HAL_UART_Transmit(&CONFIG_UART_HANDLE, (uint8_t *)g_at_response_buffer, (uint16_t)strlen(g_at_response_buffer), 1000u) != HAL_OK) { return false; } if (result == AT_NEED_REBOOT) { static const char hint[] = "Note: Use AT+SAVE then AT+RESET to apply changes\r\n"; if (HAL_UART_Transmit(&CONFIG_UART_HANDLE, (uint8_t *)hint, sizeof(hint) - 1u, 1000u) != HAL_OK) { return false; } } return true; } bool config_is_reset_requested(void) { return g_reset_requested; } void config_clear_reset_requested(void) { g_reset_requested = false; } uint8_t config_link_index_to_endpoint(uint8_t index) { switch (index) { case CONFIG_LINK_S1: return ENDPOINT_S1; case CONFIG_LINK_S2: return ENDPOINT_S2; case CONFIG_LINK_C1: return ENDPOINT_C1; case CONFIG_LINK_C2: return ENDPOINT_C2; default: return 0u; } } uint8_t config_uart_index_to_endpoint(uint8_t uart_index) { return (uart_index == LINK_UART_U1) ? ENDPOINT_UART3 : ENDPOINT_UART2; } bool config_endpoint_is_single(uint8_t endpoint) { return endpoint == ENDPOINT_C1 || endpoint == ENDPOINT_C2 || endpoint == ENDPOINT_UART2 || endpoint == ENDPOINT_UART3 || endpoint == ENDPOINT_S1 || endpoint == ENDPOINT_S2; }