#include "config.h" #include #include #include #include #include "FreeRTOS.h" #include "task.h" #include "queue.h" #include "flash_param.h" #include "usart.h" #include "route_msg.h" #include "app_runtime.h" #include "debug_log.h" #include "uart_trans.h" #define CONFIG_RX_BUFFER_SIZE 160u #define CONFIG_TX_BUFFER_SIZE 512u #define CONFIG_CMD_MAX_LEN 160u static device_config_t g_config; static volatile bool g_reset_requested; static volatile bool g_uart1_tx_busy; static volatile uint32_t g_config_rx_route_fail_count; static volatile route_send_result_t g_config_rx_route_fail_reason; static uint8_t g_uart1_rx_buffer[CONFIG_RX_BUFFER_SIZE]; static char g_config_cmd_buffer[CONFIG_CMD_MAX_LEN]; static char g_config_response_buffer[CONFIG_TX_BUFFER_SIZE]; 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; 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 (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 = strlen(name); if (!prefix_equals_ignore_case(cmd, name) || cmd[name_len] != '=') { return false; } *value = skip_whitespace(cmd + name_len + 1u); return true; } static char *config_next_token(char **cursor) { char *start = *cursor; char *end; 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 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]; uint32_t i; 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 (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, (unsigned long)g_config.uart_baudrate[0], (unsigned long)g_config.uart_baudrate[1]); 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; } 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.reconnect_interval_ms = 3000u; 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]; const char *value; const char *p; strncpy(cmd_copy, cmd, sizeof(cmd_copy) - 1u); cmd_copy[sizeof(cmd_copy) - 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; 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?")) { 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; } if (parse_command_with_value(p, "NET", &value)) { char value_copy[96]; char *cursor; char *token; 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; } 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?")) { char rip_str[CONFIG_LINK_COUNT][16]; uint32_t i; for (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\nOK\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; } 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) { char rip_str[16]; 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; } 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; } 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; 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]; int i; 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 (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 = CONFIG_RX_BUFFER_SIZE - dma_counter; BaseType_t xHigherPriorityTaskWoken = pdFALSE; HAL_StatusTypeDef hal_status; route_send_result_t route_result; if (g_uart1_tx_busy) { return; } if (len > 0u && xConfigQueue != NULL) { route_result = route_send_from_isr(xConfigQueue, 0u, 0u, ROUTE_CONN_UART1, g_uart1_rx_buffer, len, &xHigherPriorityTaskWoken); if (route_result != ROUTE_SEND_OK) { g_config_rx_route_fail_reason = route_result; g_config_rx_route_fail_count += 1u; } } hal_status = HAL_UART_DMAStop(&huart1); if (hal_status != HAL_OK) { portYIELD_FROM_ISR(xHigherPriorityTaskWoken); return; } hal_status = HAL_UART_Receive_DMA(&huart1, g_uart1_rx_buffer, CONFIG_RX_BUFFER_SIZE); if (hal_status != HAL_OK) { __HAL_UART_DISABLE_IT(&huart1, UART_IT_IDLE); portYIELD_FROM_ISR(xHigherPriorityTaskWoken); return; } portYIELD_FROM_ISR(xHigherPriorityTaskWoken); } void config_start_reception(void) { debug_log_write("[CFG] rx-start enter\r\n"); __HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE); if (HAL_UART_Receive_DMA(&huart1, g_uart1_rx_buffer, CONFIG_RX_BUFFER_SIZE) != HAL_OK) { debug_log_write("[CFG] rx-start fail\r\n"); Debug_TrapWithRttHint("cfg-rx-start-fail"); return; } debug_log_write("[CFG] rx-start exit\r\n"); } static void config_respond_to_uart(route_msg_t *msg, const char *response) { if (msg->conn_type == ROUTE_CONN_UART1) { g_uart1_tx_busy = true; __HAL_UART_DISABLE_IT(&huart1, UART_IT_IDLE); (void)HAL_UART_Transmit(&huart1, (const uint8_t *)response, (uint16_t)strlen(response), 200u); __HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE); g_uart1_tx_busy = false; } else if (msg->src_id == ENDPOINT_UART2 || msg->src_id == ENDPOINT_UART3) { uart_channel_t channel = (msg->src_id == ENDPOINT_UART3) ? UART_CHANNEL_U1 : UART_CHANNEL_U0; uint8_t frame[ROUTE_MSG_MAX_PAYLOAD + 6u]; uint16_t frame_len = 0u; uart_trans_send_result_t uart_result; if (uart_mux_encode_frame(msg->src_id, 0u, (const uint8_t *)response, (uint16_t)strlen(response), frame, &frame_len, sizeof(frame))) { uart_result = uart_trans_send_buffer(channel, frame, frame_len); if (uart_result != UART_TRANS_SEND_OK) { debug_log_printf("[CFG] resp-tx-fail ch=%u rc=%s len=%u\r\n", (unsigned int)channel, uart_trans_send_result_to_str(uart_result), (unsigned int)frame_len); } } else { debug_log_printf("[CFG] resp-enc-fail src=0x%02X len=%u\r\n", (unsigned int)msg->src_id, (unsigned int)strlen(response)); } } } static void config_report_route_failures(uint32_t *reported_route_fail_count) { uint32_t fail_count; route_send_result_t fail_reason; if (reported_route_fail_count == NULL) { return; } fail_count = g_config_rx_route_fail_count; fail_reason = g_config_rx_route_fail_reason; if (fail_count != *reported_route_fail_count) { *reported_route_fail_count = fail_count; debug_log_printf("[CFG] rx-route-fail rc=%s cnt=%lu\r\n", route_send_result_to_str(fail_reason), (unsigned long)fail_count); } } void ConfigTask(void *argument) { route_msg_t *msg; at_result_t result; uint32_t reported_route_fail_count = 0u; (void)argument; debug_log_write("[CFG] task-entry\r\n"); config_start_reception(); debug_log_write("[CFG] task-ready\r\n"); for (;;) { config_report_route_failures(&reported_route_fail_count); if (xQueueReceive(xConfigQueue, &msg, pdMS_TO_TICKS(50)) != pdPASS) { continue; } config_report_route_failures(&reported_route_fail_count); if (msg->len >= sizeof(g_config_cmd_buffer)) { msg->len = sizeof(g_config_cmd_buffer) - 1u; } memcpy(g_config_cmd_buffer, msg->data, msg->len); g_config_cmd_buffer[msg->len] = '\0'; result = config_process_at_cmd(g_config_cmd_buffer, g_config_response_buffer, sizeof(g_config_response_buffer)); config_respond_to_uart(msg, g_config_response_buffer); if (result == AT_NEED_REBOOT) { config_respond_to_uart(msg, "Note: Use AT+SAVE then AT+RESET to apply changes\r\n"); } route_msg_free(msg); if (g_reset_requested) { g_reset_requested = false; vTaskDelay(pdMS_TO_TICKS(100)); NVIC_SystemReset(); } } } 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; }