/** * @file ml307_tcp.c * @brief ML307 TCP Connection Implementation * @details TCP/SSL connection management for ML307 4G module * Reference: c_version/ml307_tcp.c */ #include "ml307_tcp.h" #include "ml307_modem.h" #include "at_uart.h" #include #include #include #include #include #define TAG "ml307_tcp" #include "lisa_log.h" #include "FreeRTOS.h" #include "event_groups.h" #include "semphr.h" #include "task.h" /* Include ring buffer for efficient TCP receive buffering */ #include "ring_buffer.h" /* Maximum packet size for TCP send (half of 1460 for hex encoding) */ #define MAX_PACKET_SIZE (1024) /* Maximum number of TCP connections (shared with UDP, managed by modem) */ #define MAX_TCP_CONNECTIONS 5 /* Receive buffer size */ #define TCP_RECV_BUFFER_SIZE 1024 * 6 /* Convert connection ID (1-5) to array index (0-4) */ #define TCP_ID_TO_INDEX(tcp_id) ((tcp_id) - 1) /* TCP prefetch task configuration */ #define TCP_PREFETCH_TASK_PRIORITY 5 #define TCP_PREFETCH_TASK_STACK_SIZE 2048 #define TCP_PREFETCH_INTERVAL_MS 3 /**< Prefetch interval in milliseconds */ /** * @brief ML307 TCP instance structure */ struct ml307_tcp { int tcp_id; /**< TCP connection ID */ bool is_ssl; /**< SSL enabled flag */ bool connected; /**< Connection status */ bool instance_active; /**< Instance active flag */ bool initialized; /**< Instance initialized flag */ int last_error; /**< Last error code */ int last_sent_bytes; /**< Last sent bytes count from MIPSEND */ EventGroupHandle_t event_group; /**< Event group for synchronization */ at_urc_callback_node_t *urc_node; /**< URC callback node */ /* User callbacks */ tcp_stream_callback_t stream_callback; /**< Data stream callback */ void *stream_user_data; /**< Stream callback user data */ tcp_disconnect_callback_t disc_callback;/**< Disconnect callback */ void *disc_user_data; /**< Disconnect callback user data */ /* Receive buffer for ml307_tcp_recv() - using ring buffer */ struct ring_buf ring_buf; /**< Ring buffer instance */ uint8_t recv_buffer_data[TCP_RECV_BUFFER_SIZE]; /**< Ring buffer backing data */ size_t available_data_len; /**< Available data length from URC notification */ EventGroupHandle_t recv_event; /**< Event for data available */ }; /* Global TCP connections array */ static struct ml307_tcp tcp_connections[MAX_TCP_CONNECTIONS] __attribute__((section(".psram.data"))) = {0}; /* TCP prefetch task handle */ static TaskHandle_t tcp_prefetch_task_handle = NULL; static volatile bool tcp_prefetch_task_running = false; /* Forward declarations */ static char *hex_encode(const char *data, size_t length, size_t *out_len); static char *hex_decode(const char *hex_str, size_t hex_len, size_t *out_len); static void ml307_tcp_internal_recv_handler(const char *data, size_t len, void *user_data); static void ml307_tcp_prefetch_task(void *pvParameters); /** * @brief URC callback handler */ static void ml307_tcp_urc_handler(const char *command, at_arg_value_t *arguments, size_t arg_count, void *user_data) { // LISA_LOGI(TAG, "%s -----", __func__); ml307_tcp_t *tcp = (ml307_tcp_t *)user_data; if (!tcp || !tcp->initialized || !command) return; // LISA_LOGI(TAG, "ml307_tcp_urc_handler---arg_count: %d, %s", arg_count, command); /* +MIPOPEN: , */ if (strcmp(command, "MIPOPEN") == 0 && arg_count >= 2) { if (arguments[0].type == AT_ARG_TYPE_INT && arguments[0].data.int_val == tcp->tcp_id) { int result = (arguments[1].type == AT_ARG_TYPE_INT) ? arguments[1].data.int_val : -1; tcp->connected = (result == 0); if (tcp->connected) { tcp->instance_active = true; xEventGroupClearBits(tcp->event_group, ML307_TCP_DISCONNECTED | ML307_TCP_ERROR); xEventGroupSetBits(tcp->event_group, ML307_TCP_CONNECTED); LISA_LOGI(TAG, "TCP %d connected", tcp->tcp_id); } else { tcp->last_error = result; xEventGroupSetBits(tcp->event_group, ML307_TCP_ERROR); LISA_LOGE(TAG, "TCP %d connection failed: %d", tcp->tcp_id, result); } } } else if (strcmp(command, "MIPCLOSE") == 0 && arg_count >= 1) { if (arguments[0].type == AT_ARG_TYPE_INT && arguments[0].data.int_val == tcp->tcp_id) { tcp->instance_active = false; xEventGroupSetBits(tcp->event_group, ML307_TCP_DISCONNECTED); LISA_LOGI(TAG, "TCP %d closed", tcp->tcp_id); } } else if (strcmp(command, "MIPSEND") == 0 && arg_count >= 2) { if (arguments[0].type == AT_ARG_TYPE_INT && arguments[0].data.int_val == tcp->tcp_id) { /* Save the number of bytes sent */ if (arguments[1].type == AT_ARG_TYPE_INT) { tcp->last_sent_bytes = arguments[1].data.int_val; // LISA_LOGI(TAG, "TCP %d sent %d bytes", tcp->tcp_id, tcp->last_sent_bytes); } xEventGroupSetBits(tcp->event_group, ML307_TCP_SEND_COMPLETE); } } else if (strcmp(command, "MIPURC") == 0 && arg_count >= 3) { // LISA_LOGI(TAG, "MIPURC: %d , %d, %s-----", arguments[1].type, arguments[1].data.int_val, arguments[0].data.string_val.value); if (arguments[1].type == AT_ARG_TYPE_INT && arguments[1].data.int_val == tcp->tcp_id) { if (arguments[0].type == AT_ARG_TYPE_STRING) { const char *urc_type = arguments[0].data.string_val.value; /* Received TCP data */ if (strcmp(urc_type, "rtcp") == 0) { if (tcp->connected) { if (arg_count >= 4) { /* Check if this is cache mode (arguments[3] is int) or normal mode (arguments[3] is string) */ if (arguments[3].type == AT_ARG_TYPE_STRING) { /* Normal mode: arg[3] contains hex data */ size_t decoded_len = 0; char *decoded = hex_decode( arguments[3].data.string_val.value, arguments[3].data.string_val.len, &decoded_len ); if (decoded) { ml307_tcp_internal_recv_handler(decoded, decoded_len, tcp); free(decoded); } } else if (arguments[3].type == AT_ARG_TYPE_INT) { /* Cache mode: arg[2]=data_length, arg[3]=total_buffer_length */ int data_len = (arguments[2].type == AT_ARG_TYPE_INT) ? arguments[2].data.int_val : 0; int total_buf = arguments[3].data.int_val; // LISA_LOGI(TAG, "TCP %d in CACHE mode - data notification: %d bytes (total buffer: %d bytes)", // tcp->tcp_id, data_len, total_buf); /* 保存可用数据长度,供 ml307_tcp_recv() 使用 */ tcp->available_data_len = total_buf; } } } } else if (strcmp(urc_type, "disconn") == 0) { if (tcp->connected) { tcp->connected = false; LISA_LOGW(TAG, "TCP %d disconnected by remote", tcp->tcp_id); if (tcp->disc_callback) { tcp->disc_callback(tcp->disc_user_data); } } tcp->instance_active = false; xEventGroupSetBits(tcp->event_group, ML307_TCP_DISCONNECTED); } } } } else if (strcmp(command, "MIPSTATE") == 0) { // LISA_LOGI(TAG, "MIPSTATE: %d , %d, %d-----", arg_count, arguments[0].type, arguments[4].type); // LISA_LOGI(TAG, "TCP %d state: %s", tcp->tcp_id, arguments[4].data.string_val.value); /* 检查参数数量,防止越界访问 */ if (arg_count >= 5 && arguments[0].type == AT_ARG_TYPE_INT && arguments[0].data.int_val == tcp->tcp_id && arguments[4].type == AT_ARG_TYPE_STRING) { const char *state = arguments[4].data.string_val.value; if (state) { /* 根据不同状态设置标志 */ if (strcmp(state, "CONNECTED") == 0) { tcp->connected = true; tcp->instance_active = true; xEventGroupClearBits(tcp->event_group, ML307_TCP_DISCONNECTED | ML307_TCP_ERROR); xEventGroupSetBits(tcp->event_group, ML307_TCP_CONNECTED); LISA_LOGI(TAG, "TCP %d state: CONNECTED", tcp->tcp_id); } else if (strcmp(state, "INITIAL") == 0) { tcp->connected = false; tcp->instance_active = true; /* INITIAL状态表示实例已分配但未连接 */ xEventGroupSetBits(tcp->event_group, ML307_TCP_INITIALIZED); LISA_LOGI(TAG, "TCP %d state: INITIAL", tcp->tcp_id); } else { /* 其他状态(如CLOSING, CLOSED等)*/ tcp->connected = false; tcp->instance_active = false; LISA_LOGI(TAG, "TCP %d state: %s", tcp->tcp_id, state); } } } } else if (strcmp(command, "MIPRD") == 0 && arg_count >= 3) { // LISA_LOGI(TAG, "MIPRD received with %zu arguments", arg_count); if (arguments[0].type == AT_ARG_TYPE_INT && arguments[0].data.int_val == tcp->tcp_id) { /* 根据参数数量判断格式 */ int hex_data_index = 2; /* 默认:arg[2] 是 HEX 数据 */ /* 如果有 4 个参数,可能是:,,, */ if (arg_count >= 4 && arguments[2].type == AT_ARG_TYPE_INT) { hex_data_index = 3; /* arg[3] 是 HEX 数据 */ } if (arguments[hex_data_index].type == AT_ARG_TYPE_STRING) { /* Decode hex data from AT+MIPRD response */ size_t decoded_len = 0; char *decoded = hex_decode( arguments[hex_data_index].data.string_val.value, arguments[hex_data_index].data.string_val.len, &decoded_len ); if (decoded) { /* Store in receive buffer */ ml307_tcp_internal_recv_handler(decoded, decoded_len, tcp); free(decoded); // LISA_LOGI(TAG, "TCP %d read %zu bytes via AT+MIPRD, remaining: %zu", // tcp->tcp_id, decoded_len, tcp->available_data_len); } } } } else if (strcmp(command, "FIFO_OVERFLOW") == 0) { LISA_LOGE(TAG, "FIFO overflow, disconnecting TCP %d", tcp->tcp_id); xEventGroupSetBits(tcp->event_group, ML307_TCP_ERROR); ml307_tcp_disconnect(tcp->tcp_id); } } /** * @brief Hex encode helper */ static char *hex_encode(const char *data, size_t length, size_t *out_len) { if (!data || length == 0 || !out_len) return NULL; char *hex = (char *)malloc(length * 2 + 1); if (!hex) { LISA_LOGE(TAG, "Failed to allocate hex buffer"); return NULL; } for (size_t i = 0; i < length; i++) { sprintf(hex + i * 2, "%02X", (uint8_t)data[i]); } hex[length * 2] = '\0'; *out_len = length * 2; return hex; } /** * @brief Hex decode helper */ static char *hex_decode(const char *hex_str, size_t hex_len, size_t *out_len) { if (!hex_str || hex_len == 0 || !out_len) return NULL; if (hex_len % 2 != 0) { LISA_LOGE(TAG, "Invalid hex string length: %zu", hex_len); return NULL; } size_t data_len = hex_len / 2; char *data = (char *)malloc(data_len + 1); if (!data) { LISA_LOGE(TAG, "Failed to allocate decode buffer"); return NULL; } for (size_t i = 0; i < data_len; i++) { char byte_str[3] = {hex_str[i * 2], hex_str[i * 2 + 1], '\0'}; char *endptr; long val = strtol(byte_str, &endptr, 16); if (endptr != byte_str + 2) { LISA_LOGE(TAG, "Invalid hex character at position %zu", i * 2); free(data); return NULL; } /* 检查值是否在有效字节范围内 */ if (val < 0 || val > 255) { LISA_LOGE(TAG, "Hex value out of range at position %zu: 0x%lx", i * 2, val); free(data); return NULL; } data[i] = (char)val; } data[data_len] = '\0'; *out_len = data_len; return data; } /** * @brief Internal receive handler to store data in recv buffer */ static void ml307_tcp_internal_recv_handler(const char *data, size_t len, void *user_data) { ml307_tcp_t *tcp = (ml307_tcp_t *)user_data; if (!tcp || !data || len == 0) return; /* Update modem buffer tracking */ if (tcp->available_data_len >= len) { tcp->available_data_len -= len; } else { tcp->available_data_len = 0; } // LISA_LOGI(TAG, "ring_buf_put-----: %d", tcp->tcp_id, len); /* Write data to ring buffer */ uint32_t written = ring_buf_put(&tcp->ring_buf, (const uint8_t *)data, (uint32_t)len); if (written < len) { LISA_LOGI(TAG, "TCP %d recv buffer overflow: dropped %zu bytes", tcp->tcp_id, len - written); } /* Always set PREFETCH_AVAILABLE to unblock prefetch task, even if buffer is full */ if (tcp->recv_event) { xEventGroupSetBits(tcp->recv_event, ML307_TCP_PREFETCH_AVAILABLE); } } /** * @brief Initialize ML307 TCP connection */ int ml307_tcp_init(bool is_ssl) { /* Allocate a free connection ID (returns 1-5) */ int tcp_id = ml307_modem_alloc_connect_id(); if (tcp_id < 0) { LISA_LOGE(TAG, "No free connection ID available"); return -1; } ml307_tcp_t *tcp = &tcp_connections[TCP_ID_TO_INDEX(tcp_id)]; if (tcp->initialized) { LISA_LOGW(TAG, "TCP %d already initialized", tcp_id); return -1; } memset(tcp, 0, sizeof(ml307_tcp_t)); tcp->tcp_id = tcp_id; tcp->is_ssl = is_ssl; /* Initialize ring buffer for receiving data */ ring_buf_init(&tcp->ring_buf, TCP_RECV_BUFFER_SIZE, tcp->recv_buffer_data); tcp->event_group = xEventGroupCreate(); if (!tcp->event_group) { LISA_LOGE(TAG, "Failed to create event group for TCP %d", tcp_id); return -1; } tcp->recv_event = xEventGroupCreate(); if (!tcp->recv_event) { LISA_LOGE(TAG, "Failed to create recv event group for TCP %d", tcp_id); vEventGroupDelete(tcp->event_group); tcp->event_group = NULL; return -1; } tcp->urc_node = at_uart_register_urc_callback(ml307_tcp_urc_handler, tcp); if (!tcp->urc_node) { LISA_LOGE(TAG, "Failed to register URC callback for TCP %d", tcp_id); vEventGroupDelete(tcp->recv_event); vEventGroupDelete(tcp->event_group); tcp->event_group = NULL; tcp->recv_event = NULL; return -1; } tcp->initialized = true; LISA_LOGI(TAG, "Initialized TCP %d (SSL: %s)", tcp_id, is_ssl ? "enabled" : "disabled"); return tcp_id; } /** * @brief Deinitialize TCP connection */ void ml307_tcp_deinit(int tcp_id) { if (tcp_id < 1 || tcp_id > MAX_TCP_CONNECTIONS) { LISA_LOGE(TAG, "Invalid TCP ID: %d (valid range: 1-%d)", tcp_id, MAX_TCP_CONNECTIONS); return; } ml307_tcp_t *tcp = &tcp_connections[TCP_ID_TO_INDEX(tcp_id)]; if (!tcp->initialized) { return; } LISA_LOGI(TAG, "Deinitializing TCP %d", tcp_id); if (tcp->urc_node) { at_uart_unregister_urc_callback(tcp->urc_node); tcp->urc_node = NULL; } if (tcp->recv_event) { vEventGroupDelete(tcp->recv_event); tcp->recv_event = NULL; } if (tcp->event_group) { vEventGroupDelete(tcp->event_group); tcp->event_group = NULL; } /* Free the connection ID */ ml307_modem_free_connect_id(tcp_id); memset(tcp, 0, sizeof(ml307_tcp_t)); } /** * @brief Connect to remote server */ bool ml307_tcp_connect(int tcp_id, const char *host, int port, bool is_ssl) { if (tcp_id < 1 || tcp_id > MAX_TCP_CONNECTIONS) { LISA_LOGE(TAG, "Invalid TCP ID: %d (valid range: 1-%d)", tcp_id, MAX_TCP_CONNECTIONS); return false; } ml307_tcp_t *tcp = &tcp_connections[TCP_ID_TO_INDEX(tcp_id)]; /* Refresh pointer after init */ if (tcp->connected) return true; if (!host) { LISA_LOGE(TAG, "Invalid parameters: host is NULL"); return false; } LISA_LOGI(TAG, "Connecting TCP %d to %s:%d", tcp_id, host, port); xEventGroupClearBits(tcp->event_group, ML307_TCP_CONNECTED | ML307_TCP_DISCONNECTED | ML307_TCP_ERROR); char command[128]; EventBits_t bits; snprintf(command, sizeof(command), "AT+MIPSTATE=%d", tcp_id); if (!at_uart_send_command(command, 1000, true)) { LISA_LOGE(TAG, "Failed to query TCP state"); ml307_tcp_deinit(tcp_id); return -1; } bits = xEventGroupWaitBits(tcp->event_group, ML307_TCP_INITIALIZED | ML307_TCP_CONNECTED, pdTRUE, pdFALSE, pdMS_TO_TICKS(TCP_CONNECT_TIMEOUT_MS)); if (!(bits & ML307_TCP_INITIALIZED) && !(bits & ML307_TCP_CONNECTED)) { LISA_LOGE(TAG, "Failed to initialize TCP connection"); ml307_tcp_deinit(tcp_id); return -1; } if (tcp->instance_active || tcp->connected) { LISA_LOGI(TAG, "TCP %d already active, closing...", tcp_id); snprintf(command, sizeof(command), "AT+MIPCLOSE=%d", tcp_id); if (at_uart_send_command(command, 1000, true)) { xEventGroupWaitBits(tcp->event_group, ML307_TCP_DISCONNECTED, pdTRUE, pdFALSE, pdMS_TO_TICKS(TCP_CONNECT_TIMEOUT_MS)); } } // 配置 SSL if (tcp->is_ssl) { snprintf(command, sizeof(command), "AT+MIPCFG=\"ssl\",%d,1,0", tcp->tcp_id); } else { snprintf(command, sizeof(command), "AT+MIPCFG=\"ssl\",%d,0,0", tcp->tcp_id); } if (!at_uart_send_command(command, 1000, true)) { LISA_LOGE(TAG, "Failed to configure SSL"); ml307_tcp_deinit(tcp_id); return false; } snprintf(command, sizeof(command), "AT+MIPCFG=\"encoding\",%d,1,1", tcp_id); if (!at_uart_send_command(command, 1000, true)) { LISA_LOGE(TAG, "Failed to set HEX encoding"); ml307_tcp_deinit(tcp_id); return false; } snprintf(command, sizeof(command), "AT+MIPOPEN=%d,\"TCP\",\"%s\",%d,60,2,0", tcp_id, host, port); if (!at_uart_send_command(command, TCP_CONNECT_TIMEOUT_MS, true)) { tcp->last_error = at_uart_get_cme_error_code(); LISA_LOGE(TAG, "Failed to open TCP connection, error=%d", tcp->last_error); ml307_tcp_deinit(tcp_id); return false; } bits = xEventGroupWaitBits(tcp->event_group, ML307_TCP_CONNECTED | ML307_TCP_ERROR, pdTRUE, pdFALSE, pdMS_TO_TICKS(TCP_CONNECT_TIMEOUT_MS)); if (bits & ML307_TCP_ERROR) { LISA_LOGE(TAG, "Failed to connect to %s:%d, error=%d", host, port, tcp->last_error); ml307_tcp_deinit(tcp_id); return false; } if (!(bits & ML307_TCP_CONNECTED)) { LISA_LOGE(TAG, "Connection timeout to %s:%d", host, port); ml307_tcp_deinit(tcp_id); return false; } LISA_LOGI(TAG, "TCP %d connected successfully", tcp_id); return true; } /** * @brief Disconnect from remote server */ int ml307_tcp_disconnect(int tcp_id) { if (tcp_id < 1 || tcp_id > MAX_TCP_CONNECTIONS) { return -1; } ml307_tcp_t *tcp = &tcp_connections[TCP_ID_TO_INDEX(tcp_id)]; if (!tcp->initialized) { return -1; } if (tcp->instance_active) { LISA_LOGI(TAG, "Disconnecting TCP %d", tcp_id); char command[128]; snprintf(command, sizeof(command), "AT+MIPCLOSE=%d", tcp_id); if (at_uart_send_command(command, 1000, true)) { xEventGroupWaitBits(tcp->event_group, ML307_TCP_DISCONNECTED, pdTRUE, pdFALSE, pdMS_TO_TICKS(TCP_CONNECT_TIMEOUT_MS)); } if (tcp->connected) { tcp->connected = false; if (tcp->disc_callback) { tcp->disc_callback(tcp->disc_user_data); } } } /* Auto-deinitialize after disconnect */ ml307_tcp_deinit(tcp_id); return 0; } /** * @brief Send data to remote server */ int ml307_tcp_send(int tcp_id, const char *data, size_t length) { if (tcp_id < 1 || tcp_id > MAX_TCP_CONNECTIONS || !data || length == 0) { LISA_LOGE(TAG, "Invalid parameters--tcp_id: %d, length: %zu, data: %p", tcp_id, length, data); return -1; } ml307_tcp_t *tcp = &tcp_connections[TCP_ID_TO_INDEX(tcp_id)]; if (!tcp->initialized) { LISA_LOGE(TAG, "TCP %d not initialized", tcp_id); return -1; } if (!tcp->connected) { LISA_LOGE(TAG, "TCP %d not connected", tcp_id); return -1; } size_t total_sent = 0; while (total_sent < length) { size_t chunk_size = (length - total_sent > MAX_PACKET_SIZE) ? MAX_PACKET_SIZE : (length - total_sent); size_t hex_len = 0; char *hex_data = hex_encode(data + total_sent, chunk_size, &hex_len); if (!hex_data) { LISA_LOGE(TAG, "Failed to encode hex data"); return -1; } /* 计算命令头实际长度: "AT+MIPSEND=XX,XXXX," 最多64字节 */ char cmd_header[64]; int cmd_len = snprintf(cmd_header, sizeof(cmd_header), "AT+MIPSEND=%d,%zu,", tcp_id, chunk_size); /* 检查命令头是否被截断 */ if (cmd_len < 0 || cmd_len >= (int)sizeof(cmd_header)) { LISA_LOGE(TAG, "Command header too long"); free(hex_data); return -1; } /* 检查整数溢出:防止 hex_len 过大导致 total_cmd_len 溢出 */ if (hex_len > SIZE_MAX - (size_t)cmd_len - 3) { LISA_LOGE(TAG, "Command size overflow: hex_len=%zu, cmd_len=%d", hex_len, cmd_len); free(hex_data); return -1; } /* 分配完整命令缓冲区: 命令头 + hex数据 + "\r\n" + '\0' */ size_t total_cmd_len = cmd_len + hex_len + 3; char *command = (char *)malloc(total_cmd_len); if (!command) { LISA_LOGE(TAG, "Failed to allocate command buffer"); free(hex_data); return -1; } /* 安全地构造完整命令 */ memcpy(command, cmd_header, cmd_len); memcpy(command + cmd_len, hex_data, hex_len); command[cmd_len + hex_len] = '\r'; command[cmd_len + hex_len + 1] = '\n'; command[cmd_len + hex_len + 2] = '\0'; free(hex_data); // LISA_LOGI(TAG, "Sending %zu bytes (chunk %zu)", chunk_size, total_sent / MAX_PACKET_SIZE + 1); xEventGroupClearBits(tcp->event_group, ML307_TCP_SEND_COMPLETE); tcp->last_sent_bytes = 0; if (!at_uart_send_command(command, 1500, false)) { LISA_LOGE(TAG, "Failed to send data chunk"); free(command); ml307_tcp_disconnect(tcp_id); return -1; } free(command); EventBits_t bits = xEventGroupWaitBits(tcp->event_group, ML307_TCP_SEND_COMPLETE, pdTRUE, pdFALSE, pdMS_TO_TICKS(TCP_SEND_TIMEOUT_MS)); if (!(bits & ML307_TCP_SEND_COMPLETE)) { LISA_LOGE(TAG, "No send confirmation received"); return -1; } if (tcp->last_sent_bytes > 0 && tcp->last_sent_bytes != (int)chunk_size) { LISA_LOGW(TAG, "TCP %d partial send: requested %zu, sent %d", tcp_id, chunk_size, tcp->last_sent_bytes); total_sent += tcp->last_sent_bytes; } else { total_sent += chunk_size; } } // LISA_LOGI(TAG, "TCP %d sent %zu bytes", tcp_id, total_sent); return (int)total_sent; } /** * @brief Receive data from remote server using AT+MIPRD command */ int ml307_tcp_recv(int tcp_id, char *buffer, size_t length, uint32_t timeout_ms) { // LISA_LOGI(TAG, "%s-----------", __func__); if (!length) { LISA_LOGE(TAG, "recv length is 0"); errno = EAGAIN; return -1; } if (tcp_id < 1 || tcp_id > MAX_TCP_CONNECTIONS || !buffer) { LISA_LOGE(TAG, "Invalid parameters-----: tcp_id: %d, %d", tcp_id, length); return -1; } ml307_tcp_t *tcp = &tcp_connections[TCP_ID_TO_INDEX(tcp_id)]; if (!tcp->initialized) { LISA_LOGE(TAG, "TCP %d not initialized", tcp_id); return -1; } if (!tcp->connected) { LISA_LOGE(TAG, "TCP %d not connected", tcp_id); return -1; } /* Check if we have enough data in ring buffer */ uint32_t available = ring_buf_size_get(&tcp->ring_buf); if (available < length) { // if (length != 2) // LISA_LOGI(TAG, "length----: %d, ring_buf_available: %u, modem_available: %zu\r\n", length, available, tcp->available_data_len); /* Polling-based approach: check buffer length directly without event waiting */ TickType_t start_tick = xTaskGetTickCount(); TickType_t timeout_ticks = pdMS_TO_TICKS(timeout_ms > 0 ? timeout_ms : 3); do { /* Check timeout condition first */ TickType_t elapsed_ticks = xTaskGetTickCount() - start_tick; if (elapsed_ticks >= timeout_ticks) { LISA_LOGD(TAG, "TCP %d recv timeout after %u ms", tcp_id, (unsigned int)(elapsed_ticks * portTICK_PERIOD_MS)); break; } /* Check if we have enough data now */ uint32_t current_available = ring_buf_size_get(&tcp->ring_buf); if (current_available >= length) { break; } vTaskDelay(pdMS_TO_TICKS(3)); } while (1); } /* Check if ring buffer has data */ uint32_t buf_available = ring_buf_size_get(&tcp->ring_buf); if (buf_available == 0) { /* No data available */ errno = EAGAIN; /* 设置errno表示暂时没有数据 */ return -1; } /* Read data from ring buffer */ uint32_t to_read = (length > buf_available) ? buf_available : length; uint32_t bytes_read = ring_buf_get(&tcp->ring_buf, (uint8_t *)buffer, to_read); if (bytes_read > 0) { // LISA_LOGI(TAG, "TCP %d received %u bytes via ring buffer", tcp_id, bytes_read); return (int)bytes_read; } return 0; } /** * @brief Register data stream callback */ void ml307_tcp_on_stream(int tcp_id, tcp_stream_callback_t callback, void *user_data) { if (tcp_id < 1 || tcp_id > MAX_TCP_CONNECTIONS) { return; } ml307_tcp_t *tcp = &tcp_connections[TCP_ID_TO_INDEX(tcp_id)]; if (!tcp->initialized) { return; } tcp->stream_callback = callback; tcp->stream_user_data = user_data; LISA_LOGI(TAG, "Stream callback registered for TCP %d", tcp_id); } /** * @brief Register disconnect callback */ void ml307_tcp_on_disconnected(int tcp_id, tcp_disconnect_callback_t callback, void *user_data) { if (tcp_id < 1 || tcp_id > MAX_TCP_CONNECTIONS) { return; } ml307_tcp_t *tcp = &tcp_connections[TCP_ID_TO_INDEX(tcp_id)]; if (!tcp->initialized) { return; } tcp->disc_callback = callback; tcp->disc_user_data = user_data; LISA_LOGI(TAG, "Disconnect callback registered for TCP %d", tcp_id); } /** * @brief Check if TCP is connected */ bool ml307_tcp_connected(int tcp_id) { if (tcp_id < 1 || tcp_id > MAX_TCP_CONNECTIONS) { return false; } ml307_tcp_t *tcp = &tcp_connections[TCP_ID_TO_INDEX(tcp_id)]; return tcp->initialized && tcp->connected; } /** * @brief Try to prefetch TCP data from modem to recv buffer for all active connections * * @return Total number of bytes prefetched across all connections, 0 if no prefetch needed */ static int ml307_tcp_data_prefetch(void) { /* Iterate through all TCP connection slots */ for (int i = 0; i < MAX_TCP_CONNECTIONS; i++) { ml307_tcp_t *tcp = &tcp_connections[i]; int tcp_id = i + 1; /* Connection IDs are 1-5 */ /* Skip uninitialized or disconnected connections */ if (!tcp->initialized || !tcp->connected) { continue; } /* Get available space in ring buffer and calculate read length */ uint32_t max_space = ring_buf_space_get(&tcp->ring_buf); size_t read_len = tcp->available_data_len < max_space ? tcp->available_data_len : max_space; /* Check if there's data to read and space available */ if (read_len == 0) { vTaskDelay(pdMS_TO_TICKS(5)); continue; } if (read_len > 512) { read_len = 512; } /* Clear the prefetch event before issuing new command */ xEventGroupClearBits(tcp->recv_event, ML307_TCP_PREFETCH_AVAILABLE); /* Issue AT+MIPRD command to read data from modem (支持重试) */ char command[128]; snprintf(command, sizeof(command), "AT+MIPRD=%d,%zu", tcp_id, read_len); // LISA_LOGI(TAG, "command---: %s-----------", command); int retry_count = 0; const int max_retries = 2; /* 最多重试2次 */ bool cmd_success = false; while (retry_count <= max_retries && !cmd_success) { if (!at_uart_send_command(command, 1000, true)) { retry_count++; LISA_LOGE(TAG, "TCP %d failed to send MIPRD command (attempt %d/%d)", tcp_id, retry_count, max_retries + 1); if (retry_count <= max_retries) { vTaskDelay(pdMS_TO_TICKS(50)); /* 短暂延迟后重试 */ continue; } else { break; } } cmd_success = true; } if (!cmd_success) { LISA_LOGE(TAG, "TCP %d MIPRD command failed after %d retries, skipping", tcp_id, max_retries + 1); continue; } /* Wait for data to be received into recv_buffer (via URC handler) */ EventBits_t bits = xEventGroupWaitBits(tcp->recv_event, ML307_TCP_PREFETCH_AVAILABLE, pdTRUE, /* Clear bit after wait */ pdFALSE, pdMS_TO_TICKS(1000)); if ((bits & ML307_TCP_PREFETCH_AVAILABLE) == 0) { LISA_LOGW(TAG, "TCP %d prefetch timeout after MIPRD", tcp_id); continue; /* Try next connection */ } } return 0; } /** * @brief TCP prefetch task * * This task runs periodically to prefetch TCP data from the modem * for all active connections, reducing latency. */ static void ml307_tcp_prefetch_task(void *pvParameters) { (void)pvParameters; LISA_LOGI(TAG, "TCP prefetch task started (interval: %d ms)", TCP_PREFETCH_INTERVAL_MS); while (tcp_prefetch_task_running) { /* Call prefetch function for all active connections */ ml307_tcp_data_prefetch(); /* Sleep for the configured interval */ vTaskDelay(pdMS_TO_TICKS(TCP_PREFETCH_INTERVAL_MS)); } LISA_LOGI(TAG, "TCP prefetch task stopped"); tcp_prefetch_task_handle = NULL; vTaskDelete(NULL); } int ml307_tcp_start_prefetch_task(void) { if (tcp_prefetch_task_handle != NULL) { LISA_LOGW(TAG, "TCP prefetch task already running"); return -1; } tcp_prefetch_task_running = true; BaseType_t ret = xTaskCreate( ml307_tcp_prefetch_task, "tcp_prefetch", TCP_PREFETCH_TASK_STACK_SIZE, NULL, TCP_PREFETCH_TASK_PRIORITY, &tcp_prefetch_task_handle ); if (ret != pdPASS) { LISA_LOGE(TAG, "Failed to create TCP prefetch task"); tcp_prefetch_task_running = false; tcp_prefetch_task_handle = NULL; return -1; } LISA_LOGI(TAG, "TCP prefetch task created successfully"); return 0; } /** * @brief Stop TCP prefetch task * * Stops the background TCP prefetch task. */ void ml307_tcp_stop_prefetch_task(void) { if (tcp_prefetch_task_handle == NULL) { LISA_LOGW(TAG, "TCP prefetch task not running"); return; } LISA_LOGI(TAG, "Stopping TCP prefetch task..."); tcp_prefetch_task_running = false; /* Wait for task to finish (max 1 second) */ for (int i = 0; i < 10 && tcp_prefetch_task_handle != NULL; i++) { vTaskDelay(pdMS_TO_TICKS(100)); } if (tcp_prefetch_task_handle != NULL) { LISA_LOGW(TAG, "TCP prefetch task did not stop gracefully"); } }