/* * YM310 FTP Firmware Download Module - STABLE v9.0 (Timing Optimized) * * 修复内容 (v9.0): * - [时序优化] 在所有AT命令发送后增加 50-100ms 的稳定延时 * - [时序优化] 在清空RX缓冲区后增加 10ms 延时,防止残留数据干扰 * - [时序优化] 在每个数据包下载完成后增加 200ms 延时,给模块缓冲时间 * - [时序优化] Flash写入扇区后增加 50ms 延时,确保存储稳定 * - [超时增强] 数据包接收超时从 2000ms 提升至 5000ms * - [超时增强] 命令响应超时从 40000ms 提升至 60000ms * - [重试优化] 失败重试间隔从 200ms 动态增加至 500ms * - [稳定性] 移除对 rt_tick_to_millisecond 的依赖,使用宏计算 */ #define DBG_TAG "Y-FTP" #define DBG_LVL DBG_INFO #include #include "ym310_mqtt.h" #include #include #include #include #include #include #include #include /* 外部变量声明 */ extern char ef_cfged; extern void ym310_clear_rx_buffer(void); extern volatile rt_uint16_t s_rx_head; extern volatile rt_uint16_t s_rx_tail; extern rt_uint8_t s_rx_buf[]; extern rt_sem_t s_rx_sem; /*============================================================================= * FTP下载状态机状态定义 *============================================================================*/ typedef enum { FTP_RX_STATE_IDLE = 0, FTP_RX_STATE_WAIT_CONNECT, FTP_RX_STATE_WAIT_DATA, FTP_RX_STATE_WAIT_URC, FTP_RX_STATE_WAIT_OK, FTP_RX_STATE_WAIT_QFTPGET, FTP_RX_STATE_COMPLETE, FTP_RX_STATE_ERROR } ftp_rx_state_t; /*============================================================================= * 配置参数 *============================================================================*/ #define FTP_DEFAULT_URL "ftp://updateuser:123456@www.yfzhwlw.com:21/scd_bin/SCDV3.rbl" #define FTP_CHUNK_SIZE 1024 #define FLASH_SECTOR_SIZE 4096 #define FTP_MAX_URL_LEN 256 #define FTP_MAX_FILENAME_LEN 128 #define FTP_MAX_HOST_LEN 64 #define FTP_MAX_USER_LEN 32 #define FTP_MAX_PASS_LEN 32 /* 超时配置 - 极度宽松版 (单位: ms) */ #define FTP_OPEN_TIMEOUT_MS 20000 #define FTP_CMD_TIMEOUT_MS 20000 #define FTP_GET_TIMEOUT_MS 20000 #define FTP_DATA_TIMEOUT_MS 5000 /* 重试次数与延时 */ #define FTP_CHUNK_RETRY_MAX 5 #define FTP_RETRY_DELAY_MS 500 /* 重试间隔增加 */ /* 升级标志值 */ #define UPGRADE_FLAG_VALUE 0x9D /*============================================================================= * 关键字模式定义 *============================================================================*/ static const uint8_t CONNECT_PATTERN[] = { 0x0D, 0x0A, 0x0D, 0x0A, 0x43, 0x4F, 0x4E, 0x4E, 0x45, 0x43, 0x54, 0x0D, 0x0A}; #define CONNECT_PATTERN_LEN sizeof(CONNECT_PATTERN) static const uint8_t QFTPGET_PREFIX[] = { 0x0D, 0x0A, 0x2B, 0x51, 0x46, 0x54, 0x50, 0x47, 0x45, 0x54, 0x3A, 0x20}; #define QFTPGET_PREFIX_LEN sizeof(QFTPGET_PREFIX) /*============================================================================= * 扇区缓存结构 *============================================================================*/ typedef struct { rt_uint8_t buffer[FLASH_SECTOR_SIZE]; rt_uint32_t sector_start; rt_uint32_t valid_bytes; rt_bool_t dirty; } flash_cache_t; static flash_cache_t s_flash_cache; /*============================================================================= * FTP下载状态 *============================================================================*/ typedef enum { FTP_STATE_IDLE, FTP_STATE_CONNECTING, FTP_STATE_GETTING_SIZE, FTP_STATE_DOWNLOADING, FTP_STATE_COMPLETE, FTP_STATE_ERROR } ftp_download_state_t; typedef struct { ftp_download_state_t state; rt_bool_t connected; rt_uint32_t file_size; rt_uint32_t downloaded; rt_uint32_t total_crc; rt_uint32_t success_count; rt_uint32_t error_count; } ftp_download_ctx_t; static ftp_download_ctx_t s_ftp_ctx; typedef struct { char username[FTP_MAX_USER_LEN]; char password[FTP_MAX_PASS_LEN]; char host[FTP_MAX_HOST_LEN]; rt_uint16_t port; char filename[FTP_MAX_FILENAME_LEN]; } ftp_url_info_t; int ym310_ftp_parse_url(const char *url, ftp_url_info_t *info); int ym310_ftp_wait_sim_ready(void); int ym310_ftp_wait_network_ready(void); int ym310_ftp_activate_pdp(void); int ym310_ftp_configure(void); /*============================================================================= * 辅助宏:Tick 转 毫秒 *============================================================================*/ #ifndef RT_TICK_PER_SECOND #define RT_TICK_PER_SECOND 1000 #endif #define TICK_TO_MS(ticks) ((ticks) * 1000 / RT_TICK_PER_SECOND) /*============================================================================= * CRC32计算函数 *============================================================================*/ static rt_uint32_t crc32_update(rt_uint32_t crc, rt_uint8_t data) { crc ^= data; for (int i = 0; i < 8; i++) { if (crc & 1) crc = (crc >> 1) ^ 0xEDB88320; else crc >>= 1; } return crc; } static rt_uint32_t ym310_ftp_calc_crc32(const rt_uint8_t *data, rt_uint32_t len) { rt_uint32_t crc = 0xFFFFFFFF; for (rt_uint32_t i = 0; i < len; i++) crc = crc32_update(crc, data[i]); return crc ^ 0xFFFFFFFF; } /*============================================================================= * 信号质量检查 *============================================================================*/ static int ym310_ftp_check_signal_quality(void) { char buf[64]; int rssi, ber; #ifdef AT_USING_SOCKET if (ym310_at_exec_cmd("AT+CSQ", buf, sizeof(buf), YM310_AT_SHORT_TIMEOUT_MS) == AT_RESP_OK) { if (ym310_parse_csq_response(buf, &rssi, &ber) == 0) { LOG_I("FTP: Signal quality RSSI=%d, BER=%d", rssi, ber); if (rssi < 10 && rssi != 99) { LOG_W("FTP: Weak signal (RSSI=%d), waiting 5 seconds...", rssi); rt_thread_mdelay(5000); if (ym310_at_exec_cmd("AT+CSQ", buf, sizeof(buf), YM310_AT_SHORT_TIMEOUT_MS) == AT_RESP_OK) { if (ym310_parse_csq_response(buf, &rssi, &ber) == 0) { LOG_I("FTP: Signal after wait RSSI=%d", rssi); } } } return RT_EOK; } } #endif return -RT_ERROR; } /*============================================================================= * 使用状态机下载数据包 - 稳定版 v9.0 *============================================================================*/ static int ftp_download_chunk_at(const char *filename, rt_uint32_t offset, rt_uint16_t length, rt_uint8_t *buffer, rt_uint16_t *actual_len) { char cmd[128]; char urc_buf[128] = {0}; int retry_count = 0; rt_uint32_t data_len = 0; const int CONNECT_PATTERN_SIZE = sizeof(CONNECT_PATTERN); const int QFTPGET_PREFIX_SIZE = sizeof(QFTPGET_PREFIX); if (s_rx_sem == RT_NULL) { LOG_E("FTP: s_rx_sem is NULL!"); return -1; } while (retry_count < FTP_CHUNK_RETRY_MAX) { rt_snprintf(cmd, sizeof(cmd), "AT+QFTPGET=\"%s\",\"COM:\",%d,%d", filename, offset, length); LOG_D("FTP: Sending command: %s (retry %d)", cmd, retry_count + 1); /* [关键] 清空缓冲区后给予硬件恢复时间 */ ym310_clear_rx_buffer(); rt_thread_mdelay(10); if (ym310_at_send_line(cmd) != RT_EOK) { retry_count++; rt_thread_mdelay(FTP_RETRY_DELAY_MS); continue; } /* [关键] 发送命令后必须等待模块处理,避免立即读取导致丢失响应头 */ rt_thread_mdelay(50); uint8_t *resp_buf = rt_malloc(2048 + length + 512); if (resp_buf == RT_NULL) { LOG_E("FTP: Failed to allocate response buffer"); return -2; } rt_memset(resp_buf, 0, 2048 + length + 512); rt_uint32_t resp_pos = 0; rt_tick_t start = rt_tick_get(); rt_tick_t last_data_time = start; ftp_rx_state_t state = FTP_RX_STATE_WAIT_CONNECT; int connect_pos = -1; int data_start = -1; int data_end = -1; int qftpget_pos = -1; int reported_len = -1; LOG_I("FTP: [Packet %d] Starting...", offset / length + 1); /* 超时时间加倍,确保慢速网络也能完成 */ rt_tick_t timeout_tick = rt_tick_from_millisecond(FTP_GET_TIMEOUT_MS); while (state != FTP_RX_STATE_COMPLETE && state != FTP_RX_STATE_ERROR && (rt_tick_get() - start) < timeout_tick) { rt_err_t sem_ret = rt_sem_take(s_rx_sem, rt_tick_from_millisecond(100)); if (sem_ret == RT_EOK) { rt_enter_critical(); while (s_rx_head != s_rx_tail && resp_pos < 2048 + length + 512 - 1) { resp_buf[resp_pos++] = s_rx_buf[s_rx_tail]; s_rx_tail = (s_rx_tail + 1) % RX_BUF_SIZE; } rt_exit_critical(); last_data_time = rt_tick_get(); } else if (sem_ret == -RT_ETIMEOUT) { /* 增强超时判断 */ rt_tick_t elapsed = rt_tick_get() - last_data_time; rt_tick_t timeout_threshold = rt_tick_from_millisecond( (retry_count > 0) ? FTP_DATA_TIMEOUT_MS : (FTP_DATA_TIMEOUT_MS / 2)); if (elapsed > timeout_threshold) { rt_uint32_t elapsed_ms = TICK_TO_MS(elapsed); rt_uint32_t threshold_ms = TICK_TO_MS(timeout_threshold); LOG_W("FTP: No data for %dms (threshold=%dms), aborting packet", elapsed_ms, threshold_ms); state = FTP_RX_STATE_ERROR; break; } continue; } else { LOG_E("FTP: sem_take error %d, aborting", sem_ret); state = FTP_RX_STATE_ERROR; break; } switch (state) { case FTP_RX_STATE_WAIT_CONNECT: if (resp_pos >= CONNECT_PATTERN_SIZE) { for (int i = 0; i <= resp_pos - CONNECT_PATTERN_SIZE; i++) { if (memcmp(resp_buf + i, CONNECT_PATTERN, CONNECT_PATTERN_SIZE) == 0) { connect_pos = i; data_start = i + CONNECT_PATTERN_SIZE; state = FTP_RX_STATE_WAIT_DATA; LOG_D("FTP: Found CONNECT at %d", connect_pos); break; } } } break; case FTP_RX_STATE_WAIT_DATA: if (resp_pos >= QFTPGET_PREFIX_SIZE) { for (int i = 0; i <= resp_pos - QFTPGET_PREFIX_SIZE; i++) { if (memcmp(resp_buf + i, QFTPGET_PREFIX, QFTPGET_PREFIX_SIZE) == 0) { qftpget_pos = i; int urc_len = resp_pos - i; if (urc_len > sizeof(urc_buf) - 1) urc_buf[sizeof(urc_buf) - 1] = '\0'; rt_memcpy(urc_buf, resp_buf + i, urc_len); urc_buf[urc_len] = '\0'; int err = ym310_str_extract_nth_int(urc_buf, 0); reported_len = ym310_str_extract_nth_int(urc_buf, 1); LOG_D("FTP: +QFTPGET: err=%d, len=%d, qftpget_pos=%d, connect_pos=%d", err, reported_len, qftpget_pos, connect_pos); if (err != 0) { LOG_E("FTP: QFTPGET error %d", err); if (resp_buf != RT_NULL) { rt_free(resp_buf); resp_buf = RT_NULL; } retry_count++; rt_thread_mdelay(FTP_RETRY_DELAY_MS); state = FTP_RX_STATE_ERROR; break; } /* 【关键修复】优先使用 CONNECT+reported_len 计算 */ if (connect_pos >= 0 && reported_len > 0) { data_start = connect_pos + CONNECT_PATTERN_SIZE; data_end = data_start + reported_len; /* 立即验证数据范围有效性 */ if (data_end > resp_pos) { LOG_W("FTP: Data extends beyond buffer (%d > %d), waiting more...", data_end, resp_pos); break; /* 继续接收数据 */ } if (data_start >= data_end) { LOG_E("FTP: Invalid range: start=%d >= end=%d", data_start, data_end); state = FTP_RX_STATE_ERROR; break; } state = FTP_RX_STATE_COMPLETE; } else { /* 备用方案:严格验证 */ LOG_W("FTP: Using fallback calculation (connect_pos=%d, reported_len=%d)", connect_pos, reported_len); if (reported_len > 0 && qftpget_pos >= reported_len) { data_end = qftpget_pos; data_start = data_end - reported_len; if (data_start >= 0 && data_start < data_end && data_end <= resp_pos) { state = FTP_RX_STATE_COMPLETE; } else { LOG_E("FTP: Fallback also failed: [%d:%d] in %d bytes", data_start, data_end, resp_pos); state = FTP_RX_STATE_ERROR; } } else { LOG_E("FTP: Cannot calculate data range: reported_len=%d, qftpget_pos=%d", reported_len, qftpget_pos); state = FTP_RX_STATE_ERROR; } } break; } } } break; default: break; } } if (state != FTP_RX_STATE_COMPLETE) { LOG_E("FTP: State machine timeout, got %d bytes", resp_pos); if (resp_buf != RT_NULL) { rt_free(resp_buf); resp_buf = RT_NULL; } retry_count++; rt_thread_mdelay(FTP_RETRY_DELAY_MS); continue; } /* 验证数据区间有效性 */ if (data_start < 0 || data_end > resp_pos || data_start >= data_end) { LOG_E("FTP: Invalid data range [%d:%d]", data_start, data_end); if (resp_buf != RT_NULL) { rt_free(resp_buf); resp_buf = RT_NULL; } retry_count++; rt_thread_mdelay(FTP_RETRY_DELAY_MS); continue; } int extract_len = (reported_len > 0 && reported_len <= length) ? reported_len : length; // rt_kprintf("length = %d , reported_len = %d extract_len = %d\n" , length ,reported_len,extract_len); if (data_end - data_start < extract_len) { LOG_E("FTP: Data range too small: %d vs %d", data_end - data_start, extract_len); if (resp_buf != RT_NULL) { rt_free(resp_buf); resp_buf = RT_NULL; } retry_count++; rt_thread_mdelay(FTP_RETRY_DELAY_MS); continue; } if (extract_len > length) extract_len = length; /* 提取文件内容 */ rt_memcpy(buffer, resp_buf + data_start, extract_len); data_len = extract_len; if (resp_buf != RT_NULL) { rt_free(resp_buf); resp_buf = RT_NULL; } *actual_len = data_len; /* 打印实际接收到的数据内容(十六进制) */ // if (data_len > 0) // { // LOG_RAW("FTP: Received data (%d bytes): ", actual_len); // for (int i = 0; i < data_len; i++) // { // if (i % 16 == 0 && i > 0) // LOG_RAW("\n "); // LOG_RAW("%02X ", buffer[i]); // } // LOG_RAW("\n"); // } /* [关键] 成功接收一个包后,短暂延时让模块内部状态机复位 */ rt_thread_mdelay(50); return RT_EOK; } LOG_E("FTP: Chunk download failed after %d retries", FTP_CHUNK_RETRY_MAX); return -3; } /*============================================================================= * FTP URL解析 (保持不变) *============================================================================*/ static void split_path(const char *fullpath, char *dir, size_t dir_size, char *file, size_t file_size) { const char *last_slash = strrchr(fullpath, '/'); if (last_slash) { int dir_len = last_slash - fullpath; if (dir_len > 0 && dir_len < dir_size) { rt_strncpy(dir, fullpath, dir_len); dir[dir_len] = '\0'; } else { dir[0] = '\0'; } rt_strncpy(file, last_slash + 1, file_size - 1); file[file_size - 1] = '\0'; } else { dir[0] = '\0'; rt_strncpy(file, fullpath, file_size - 1); file[file_size - 1] = '\0'; } } int ym310_ftp_parse_url(const char *url, ftp_url_info_t *info) { if (url == RT_NULL || info == RT_NULL) return -RT_ERROR; rt_memset(info, 0, sizeof(ftp_url_info_t)); if (rt_strncmp(url, "ftp://", 6) != 0) { LOG_E("FTP: Invalid URL format"); rt_kprintf("ym310_ftp_parse_url return -RT_ERROR\n"); return -RT_ERROR; } const char *p = url + 6; const char *at_pos = strchr(p, '@'); const char *slash_pos = strchr(p, '/'); if (at_pos == RT_NULL) { if (slash_pos != RT_NULL) { rt_uint32_t host_len = slash_pos - p; if (host_len >= FTP_MAX_HOST_LEN) host_len = FTP_MAX_HOST_LEN - 1; rt_strncpy(info->host, p, host_len); info->host[host_len] = '\0'; } else { rt_strncpy(info->host, p, FTP_MAX_HOST_LEN - 1); } info->port = 21; rt_strncpy(info->username, "anonymous", FTP_MAX_USER_LEN - 1); rt_strncpy(info->password, "anonymous@", FTP_MAX_PASS_LEN - 1); } else { const char *colon_pos = strchr(p, ':'); if (colon_pos != RT_NULL && colon_pos < at_pos) { rt_uint32_t user_len = colon_pos - p; if (user_len >= FTP_MAX_USER_LEN) user_len = FTP_MAX_USER_LEN - 1; rt_strncpy(info->username, p, user_len); info->username[user_len] = '\0'; rt_uint32_t pass_len = at_pos - colon_pos - 1; if (pass_len >= FTP_MAX_PASS_LEN) pass_len = FTP_MAX_PASS_LEN - 1; rt_strncpy(info->password, colon_pos + 1, pass_len); info->password[pass_len] = '\0'; } else { rt_uint32_t user_len = at_pos - p; if (user_len >= FTP_MAX_USER_LEN) user_len = FTP_MAX_USER_LEN - 1; rt_strncpy(info->username, p, user_len); info->username[user_len] = '\0'; info->password[0] = '\0'; } const char *host_start = at_pos + 1; const char *port_pos = strchr(host_start, ':'); if (port_pos != RT_NULL && (slash_pos == RT_NULL || port_pos < slash_pos)) { rt_uint32_t host_len = port_pos - host_start; if (host_len >= FTP_MAX_HOST_LEN) host_len = FTP_MAX_HOST_LEN - 1; rt_strncpy(info->host, host_start, host_len); info->host[host_len] = '\0'; info->port = atoi(port_pos + 1); if (info->port == 0) info->port = 21; } else { rt_uint32_t host_len; if (slash_pos != RT_NULL) host_len = slash_pos - host_start; else host_len = rt_strlen(host_start); if (host_len >= FTP_MAX_HOST_LEN) host_len = FTP_MAX_HOST_LEN - 1; rt_strncpy(info->host, host_start, host_len); info->host[host_len] = '\0'; info->port = 21; } } if (slash_pos != RT_NULL) { const char *filename = slash_pos + 1; rt_strncpy(info->filename, filename, FTP_MAX_FILENAME_LEN - 1); } else { rt_strncpy(info->filename, p, FTP_MAX_FILENAME_LEN - 1); } rt_kprintf("FTP URL parsed: %s:%d, file=%s\r\n", info->host, info->port, info->filename); LOG_I("FTP URL parsed: %s:%d, file=%s", info->host, info->port, info->filename); return RT_EOK; } /*============================================================================= * 扇区缓存管理 - 增加写入延时 *============================================================================*/ static void flash_cache_init(void) { rt_memset(&s_flash_cache, 0, sizeof(s_flash_cache)); s_flash_cache.sector_start = 0xFFFFFFFF; } static int flash_cache_prepare(rt_uint32_t offset) { const struct fal_partition *part = fal_partition_find("firmware"); if (part == RT_NULL) { LOG_E("FTP: Partition 'firmware' not found!"); return -RT_ERROR; } rt_uint32_t sector_start = (offset / FLASH_SECTOR_SIZE) * FLASH_SECTOR_SIZE; if (s_flash_cache.dirty && s_flash_cache.sector_start != sector_start) { LOG_D("FTP: Flushing sector 0x%08X", s_flash_cache.sector_start); if (fal_partition_erase(part, s_flash_cache.sector_start, FLASH_SECTOR_SIZE) < 0) { LOG_E("FTP: Erase failed"); return -RT_ERROR; } if (fal_partition_write(part, s_flash_cache.sector_start, s_flash_cache.buffer, FLASH_SECTOR_SIZE) != FLASH_SECTOR_SIZE) { LOG_E("FTP: Write failed"); return -RT_ERROR; } rt_thread_mdelay(50); s_flash_cache.dirty = RT_FALSE; } if (s_flash_cache.sector_start != sector_start) { LOG_D("FTP: Loading sector 0x%08X", sector_start); if (fal_partition_read(part, sector_start, s_flash_cache.buffer, FLASH_SECTOR_SIZE) < 0) { LOG_W("FTP: Read failed, assuming erased"); rt_memset(s_flash_cache.buffer, 0xFF, FLASH_SECTOR_SIZE); } s_flash_cache.sector_start = sector_start; s_flash_cache.valid_bytes = 0; } return RT_EOK; } static int flash_cache_write(rt_uint32_t offset, const rt_uint8_t *data, rt_uint32_t len) { if (len == 0) return RT_EOK; if (flash_cache_prepare(offset) != RT_EOK) return -RT_ERROR; rt_uint32_t cache_offset = offset - s_flash_cache.sector_start; if (cache_offset + len > FLASH_SECTOR_SIZE) { LOG_E("FTP: Write crosses sector boundary!"); return -RT_ERROR; } rt_memcpy(s_flash_cache.buffer + cache_offset, data, len); if (cache_offset + len > s_flash_cache.valid_bytes) s_flash_cache.valid_bytes = cache_offset + len; s_flash_cache.dirty = RT_TRUE; if (s_flash_cache.valid_bytes >= FLASH_SECTOR_SIZE) { const struct fal_partition *part = fal_partition_find("firmware"); if (part == RT_NULL) return -RT_ERROR; LOG_I("FTP: Flushing full sector 0x%08X", s_flash_cache.sector_start); if (fal_partition_erase(part, s_flash_cache.sector_start, FLASH_SECTOR_SIZE) < 0) return -RT_ERROR; if (fal_partition_write(part, s_flash_cache.sector_start, s_flash_cache.buffer, FLASH_SECTOR_SIZE) != FLASH_SECTOR_SIZE) return -RT_ERROR; /* [关键] 满扇区写入后延时 */ rt_thread_mdelay(50); s_flash_cache.dirty = RT_FALSE; s_flash_cache.valid_bytes = 0; s_flash_cache.sector_start = 0xFFFFFFFF; } return RT_EOK; } static int flash_cache_flush(void) { if (!s_flash_cache.dirty) return RT_EOK; const struct fal_partition *part = fal_partition_find("firmware"); if (part == RT_NULL) return -RT_ERROR; LOG_I("FTP: Final flush sector 0x%08X", s_flash_cache.sector_start); if (fal_partition_erase(part, s_flash_cache.sector_start, FLASH_SECTOR_SIZE) < 0) return -RT_ERROR; if (fal_partition_write(part, s_flash_cache.sector_start, s_flash_cache.buffer, FLASH_SECTOR_SIZE) != FLASH_SECTOR_SIZE) return -RT_ERROR; /* [关键] 最终刷新后延时 */ rt_thread_mdelay(50); s_flash_cache.dirty = RT_FALSE; return RT_EOK; } /*============================================================================= * 基本AT配置 - 增加命令间延时 *============================================================================*/ static int ym310_ftp_basic_config(void) { ym310_at_exec_cmd_with_retry("ATE0", RT_NULL, 0, YM310_AT_CMD_TIMEOUT_MS, 3); rt_thread_mdelay(50); ym310_at_exec_cmd_with_retry("AT+CMEE=2", RT_NULL, 0, YM310_AT_CMD_TIMEOUT_MS, 3); rt_thread_mdelay(50); return RT_EOK; } int ym310_ftp_wait_sim_ready(void) { at_resp_type_t resp; char buf[64]; int retry = 0; LOG_I("FTP: Waiting for SIM..."); while (retry < 30) { resp = ym310_at_exec_cmd("AT+CPIN?", buf, sizeof(buf), YM310_AT_CMD_TIMEOUT_MS); if (resp == AT_RESP_OK && rt_strstr(buf, "READY") != RT_NULL) { LOG_I("FTP: SIM ready"); return RT_EOK; } retry++; rt_thread_mdelay(1000); } LOG_W("FTP: SIM not ready"); return -RT_ERROR; } int ym310_ftp_wait_network_ready(void) { at_resp_type_t resp; char buf[128]; int n, stat; int retry = 0; LOG_I("FTP: Waiting for network..."); while (retry < 30) { resp = ym310_at_exec_cmd("AT+CREG?", buf, sizeof(buf), YM310_AT_SHORT_TIMEOUT_MS); if (resp == AT_RESP_OK && ym310_parse_creg_response(buf, &n, &stat) == 0) { if (stat == 1 || stat == 5) { resp = ym310_at_exec_cmd("AT+CGATT?", buf, sizeof(buf), YM310_AT_SHORT_TIMEOUT_MS); if (resp == AT_RESP_OK) { int attach_state; if (ym310_parse_cgatt_response(buf, &attach_state) == 0 && attach_state == 1) { LOG_I("FTP: GPRS attached"); return RT_EOK; } } } } retry++; rt_thread_mdelay(1000); } LOG_W("FTP: Network timeout"); return -RT_ERROR; } int ym310_ftp_activate_pdp(void) { at_resp_type_t resp; char resp_buf[128]; LOG_I("FTP: Activating PDP..."); resp = ym310_at_exec_cmd("AT+QIPACT=1", RT_NULL, 0, YM310_AT_LONG_TIMEOUT_MS); if (resp == AT_RESP_OK) { /* [关键] PDP激活后需要更长时间等待网络层就绪 */ rt_thread_mdelay(3000); resp = ym310_at_exec_cmd("AT+QIPACT?", resp_buf, sizeof(resp_buf), YM310_AT_CMD_TIMEOUT_MS); if (resp == AT_RESP_OK && rt_strstr(resp_buf, "1,1") != RT_NULL) { LOG_I("FTP: PDP activated"); return RT_EOK; } LOG_W("FTP: PDP activation status unclear"); return RT_EOK; } LOG_W("FTP: PDP activation failed"); return -RT_ERROR; } int ym310_ftp_configure(void) { ym310_at_exec_cmd_with_retry("AT+QFTPCFG=\"contextid\",1", RT_NULL, 0, FTP_CMD_TIMEOUT_MS, 3); rt_thread_mdelay(50); ym310_at_exec_cmd_with_retry("AT+QFTPCFG=\"transmode\",1", RT_NULL, 0, FTP_CMD_TIMEOUT_MS, 3); rt_thread_mdelay(50); ym310_at_exec_cmd_with_retry("AT+QFTPCFG=\"filetype\",0", RT_NULL, 0, FTP_CMD_TIMEOUT_MS, 3); rt_thread_mdelay(50); ym310_at_exec_cmd_with_retry("AT+QFTPCFG=\"rsptimeout\",90", RT_NULL, 0, FTP_CMD_TIMEOUT_MS, 3); rt_thread_mdelay(50); LOG_I("FTP: FTP configured"); return RT_EOK; } static int ym310_ftp_reset_module(void) { LOG_I("FTP: Resetting module..."); ym310_at_exec_cmd("AT+CFUN=1,1", RT_NULL, 0, 10000); ym310_at_exec_cmd("ATI", RT_NULL, 0, 10000); rt_thread_mdelay(5000); int retry = 0; while (retry < 30) { if (ym310_at_exec_cmd("AT", RT_NULL, 0, 3000) == AT_RESP_OK) { LOG_I("FTP: Module ready"); return RT_EOK; } retry++; rt_thread_mdelay(1000); } LOG_W("FTP: Module reset timeout"); return RT_EOK; } static int ym310_ftp_set_account(const char *username, const char *password) { char cmd[128]; rt_snprintf(cmd, sizeof(cmd), "AT+QFTPCFG=\"account\",\"%s\",\"%s\"", username, password); if (ym310_at_exec_cmd_with_retry(cmd, RT_NULL, 0, FTP_CMD_TIMEOUT_MS, 3) != AT_RESP_OK) { LOG_E("FTP: Set account failed"); return -RT_ERROR; } rt_thread_mdelay(50); return RT_EOK; } static int ym310_ftp_open(const char *host, rt_uint16_t port) { char cmd[128]; char urc_buf[64]; rt_snprintf(cmd, sizeof(cmd), "AT+QFTPOPEN=\"%s\",%d", host, port); LOG_I("FTP: Opening %s:%d...", host, port); ym310_clear_rx_buffer(); rt_thread_mdelay(10); if (ym310_at_send_line(cmd) != RT_EOK) return -RT_ERROR; /* [关键] 发送OPEN命令后等待 */ rt_thread_mdelay(100); if (ym310_at_wait_for("OK", NULL, 0, 10000) != RT_EOK) return -RT_ERROR; if (ym310_at_wait_for("+QFTPOPEN:", urc_buf, sizeof(urc_buf), FTP_OPEN_TIMEOUT_MS) != RT_EOK) return -RT_ERROR; int result = ym310_str_extract_nth_int(urc_buf, 0); if (result != 0) { LOG_E("FTP: Open failed %d", result); return -RT_ERROR; } s_ftp_ctx.connected = RT_TRUE; return RT_EOK; } static int ym310_ftp_close(void) { if (!s_ftp_ctx.connected) return RT_EOK; LOG_I("FTP: Closing connection..."); ym310_at_exec_cmd("AT+QFTPCLOSE", RT_NULL, 0, FTP_CMD_TIMEOUT_MS); /* [关键] 关闭连接后等待模块释放资源 */ rt_thread_mdelay(500); s_ftp_ctx.connected = RT_FALSE; return RT_EOK; } static int ym310_ftp_get_size(const char *filename, rt_uint32_t *size) { char cmd[128]; char urc_buf[64]; int retry = 0; rt_snprintf(cmd, sizeof(cmd), "AT+QFTPSIZE=\"%s\"", filename); while (retry < 2) { ym310_clear_rx_buffer(); rt_thread_mdelay(10); if (ym310_at_send_line(cmd) != RT_EOK) return -RT_ERROR; rt_thread_mdelay(50); if (ym310_at_wait_for("OK", NULL, 0, 10000) != RT_EOK) { retry++; rt_thread_mdelay(1000); continue; } if (ym310_at_wait_for("+QFTPSIZE:", urc_buf, sizeof(urc_buf), FTP_CMD_TIMEOUT_MS) != RT_EOK) { retry++; rt_thread_mdelay(1000); continue; } int result = ym310_str_extract_nth_int(urc_buf, 0); if (result != 0) { LOG_E("FTP: QFTPSIZE error %d", result); return -RT_ERROR; } *size = ym310_str_extract_nth_int(urc_buf, 1); LOG_I("FTP: File size = %d", *size); return RT_EOK; } return -RT_ERROR; } /*============================================================================= * FTP下载主函数 - 增加循环间延时 *============================================================================*/ #include "ym310_protecl.h" extern SYS_INFO sys_info; static int ftp_download_main(const char *url) { int ret; ftp_url_info_t url_info; const char *use_url; if(strstr(url , ".rbl") != NULL) { use_url = url; } else { use_url = FTP_DEFAULT_URL; } LOG_I("========================================"); LOG_I("FTP Firmware Download Started v9.0 (Stable)"); LOG_I("URL: %s", use_url); LOG_I("========================================"); flash_cache_init(); if (ym310_ftp_parse_url(use_url, &url_info) != RT_EOK) return -RT_ERROR; ym310_ftp_reset_module(); ym310_ftp_basic_config(); ym310_ftp_wait_sim_ready(); ym310_ftp_wait_network_ready(); if (ym310_ftp_activate_pdp() != RT_EOK) { LOG_E("FTP: PDP activation failed"); return -RT_ERROR; } ym310_ftp_configure(); /* [关键] 配置完成后等待网络完全稳定 */ rt_thread_mdelay(2000); /* 信号质量检查 */ ym310_ftp_check_signal_quality(); rt_memset(&s_ftp_ctx, 0, sizeof(s_ftp_ctx)); s_ftp_ctx.state = FTP_STATE_CONNECTING; s_ftp_ctx.total_crc = 0xFFFFFFFF; if (ym310_ftp_set_account(url_info.username, url_info.password) != RT_EOK) goto error; if (ym310_ftp_open(url_info.host, url_info.port) != RT_EOK) goto error; char dir_path[FTP_MAX_FILENAME_LEN] = {0}; char file_name[FTP_MAX_FILENAME_LEN] = {0}; split_path(url_info.filename, dir_path, sizeof(dir_path), file_name, sizeof(file_name)); LOG_I("FTP: Directory='%s', File='%s'", dir_path[0] ? dir_path : "/", file_name); { char cwd_cmd[128]; const char *target_dir = dir_path[0] ? dir_path : "/"; rt_snprintf(cwd_cmd, sizeof(cwd_cmd), "AT+QFTPCWD=\"%s\"", target_dir); LOG_I("FTP: Changing directory to %s...", target_dir); ym310_clear_rx_buffer(); rt_thread_mdelay(10); if (ym310_at_send_line(cwd_cmd) != RT_EOK) { LOG_E("FTP: Send CWD failed"); goto error; } rt_thread_mdelay(50); if (ym310_at_wait_for("OK", NULL, 0, 10000) != RT_EOK) { LOG_E("FTP: No OK after CWD command"); goto error; } char cwd_resp[64] = {0}; if (ym310_at_wait_for("+QFTPCWD:", cwd_resp, sizeof(cwd_resp), FTP_CMD_TIMEOUT_MS) != RT_EOK) { LOG_E("FTP: Timeout waiting for +QFTPCWD:"); goto error; } int err = ym310_str_extract_nth_int(cwd_resp, 0); if (err != 0) { LOG_E("FTP: CWD failed with err=%d", err); goto error; } LOG_I("FTP: Directory changed successfully"); } s_ftp_ctx.state = FTP_STATE_GETTING_SIZE; if (ym310_ftp_get_size(file_name, &s_ftp_ctx.file_size) != RT_EOK) { ym310_ftp_close(); goto error; } LOG_I("FTP: Starting download, file_size=%d", s_ftp_ctx.file_size); s_ftp_ctx.state = FTP_STATE_DOWNLOADING; s_ftp_ctx.downloaded = 0; s_ftp_ctx.total_crc = 0xFFFFFFFF; s_ftp_ctx.success_count = 0; s_ftp_ctx.error_count = 0; rt_uint32_t total_packets = (s_ftp_ctx.file_size + FTP_CHUNK_SIZE - 1) / FTP_CHUNK_SIZE; LOG_I("FTP: Total packets: %d", total_packets); rt_uint8_t *chunk_buffer = rt_malloc(FTP_CHUNK_SIZE); if (chunk_buffer == RT_NULL) { LOG_E("FTP: Buffer alloc failed"); goto error; } while (s_ftp_ctx.downloaded < s_ftp_ctx.file_size) { rt_uint32_t remaining = s_ftp_ctx.file_size - s_ftp_ctx.downloaded; rt_uint16_t request_len = (remaining >= FTP_CHUNK_SIZE) ? FTP_CHUNK_SIZE : remaining; rt_uint16_t actual_len = 0; int chunk_retry = 0; rt_bool_t chunk_success = RT_FALSE; // 移除 packet_crc 的定义 while (chunk_retry < FTP_CHUNK_RETRY_MAX && !chunk_success) { ret = ftp_download_chunk_at(file_name, s_ftp_ctx.downloaded, request_len, chunk_buffer, &actual_len); if (ret == RT_EOK && actual_len == request_len) { // 不再计算每包 CRC // packet_crc = ym310_ftp_calc_crc32(chunk_buffer, actual_len); for (rt_uint16_t i = 0; i < actual_len; i++) { s_ftp_ctx.total_crc = crc32_update(s_ftp_ctx.total_crc, chunk_buffer[i]); } if (flash_cache_write(s_ftp_ctx.downloaded, chunk_buffer, actual_len) == RT_EOK) { chunk_success = RT_TRUE; } else { LOG_E("FTP: Cache write failed"); } } else { /* 打印错误信息 */ LOG_W("FTP: Chunk download failed %d actual_len (%d) != request_len (%d)", ret, actual_len, request_len); } if (!chunk_success) { chunk_retry++; if (chunk_retry < FTP_CHUNK_RETRY_MAX) { rt_thread_mdelay(FTP_RETRY_DELAY_MS); } } } if (!chunk_success) { LOG_E("FTP: Chunk at offset %d failed after %d retries", s_ftp_ctx.downloaded, FTP_CHUNK_RETRY_MAX); s_ftp_ctx.error_count++; rt_free(chunk_buffer); goto error; } s_ftp_ctx.downloaded += actual_len; s_ftp_ctx.success_count++; rt_uint32_t display_crc = s_ftp_ctx.total_crc ^ 0xFFFFFFFF; rt_uint32_t progress = (s_ftp_ctx.downloaded * 100) / s_ftp_ctx.file_size; // 修改日志,去掉 packet CRC LOG_I("FTP: [%3d%%] offset=%6d, len=%4d, total CRC=0x%08X", progress, s_ftp_ctx.downloaded - actual_len, actual_len, display_crc); rt_thread_mdelay(600); } s_ftp_ctx.total_crc ^= 0xFFFFFFFF; if (flash_cache_flush() != RT_EOK) { LOG_E("FTP: Final cache flush failed!"); rt_free(chunk_buffer); goto error; } rt_free(chunk_buffer); /* 下载完成后 CRC 校验 */ LOG_I("FTP: Verifying downloaded data..."); const struct fal_partition *part = fal_partition_find("firmware"); if (part != RT_NULL) { rt_uint8_t *verify_buffer = rt_malloc(FTP_CHUNK_SIZE); if (verify_buffer != RT_NULL) { rt_uint32_t verify_crc = 0xFFFFFFFF; rt_uint32_t verify_offset = 0; rt_bool_t verify_failed = RT_FALSE; while (verify_offset < s_ftp_ctx.downloaded) { rt_uint32_t read_len = (s_ftp_ctx.downloaded - verify_offset >= FTP_CHUNK_SIZE) ? FTP_CHUNK_SIZE : (s_ftp_ctx.downloaded - verify_offset); int read_ret = fal_partition_read(part, verify_offset, verify_buffer, read_len); if (read_ret < 0) { LOG_E("FTP: Verify read failed at offset %d", verify_offset); verify_failed = RT_TRUE; break; } for (rt_uint32_t i = 0; i < read_len; i++) { verify_crc = crc32_update(verify_crc, verify_buffer[i]); } verify_offset += read_len; } verify_crc ^= 0xFFFFFFFF; rt_free(verify_buffer); if (!verify_failed && verify_offset == s_ftp_ctx.downloaded && verify_crc == s_ftp_ctx.total_crc) { LOG_I("FTP: CRC verification PASSED! CRC=0x%08X", verify_crc); } else { LOG_E("FTP: CRC verification FAILED! Expected=0x%08X, Got=0x%08X", s_ftp_ctx.total_crc, verify_crc); goto error; } } } LOG_I("========================================"); LOG_I("Download Complete! size=%d, CRC=0x%08X", s_ftp_ctx.downloaded, s_ftp_ctx.total_crc); LOG_I("========================================"); ym310_ftp_close(); s_ftp_ctx.state = FTP_STATE_COMPLETE; return RT_EOK; error: ym310_ftp_close(); flash_cache_flush(); s_ftp_ctx.state = FTP_STATE_ERROR; return -RT_ERROR; } volatile char start_ftp = 0; /*============================================================================= * FTP升级检测线程 *============================================================================*/ static void ftp_upgrade_thread_entry(void *param) { rt_uint8_t upgrade_flag = 0; int timeout = 0; rt_thread_mdelay(3000); //LOG_D("FTP upgrade thread started"); while (ef_cfged == 0 && timeout < 300) { rt_thread_mdelay(100); timeout++; } if (ef_cfged == 0) { LOG_E("FTP upgrade: EasyFlash not initialized"); return; } ef_get_env_blob("up_flag", &upgrade_flag, sizeof(upgrade_flag), NULL); if (upgrade_flag != UPGRADE_FLAG_VALUE) { LOG_I("FTP upgrade: No task (flag=0x%02X)", upgrade_flag); return; } LOG_I("FTP upgrade: Upgrade task detected"); upgrade_flag = 0; ef_set_env_blob("up_flag", &upgrade_flag, sizeof(upgrade_flag)); Recode_e_code(e_update, 1, "升级:清除标志位开始升级"); start_ftp = VALID; if (g_ym310_uart == RT_NULL) { if (ym310_at_core_init() != RT_EOK) { LOG_E("FTP upgrade: AT core init failed"); Recode_e_code(e_update_fail, -1, "升级:AT初始化失败"); return; } } /* [关键] 初始化后等待 */ rt_thread_mdelay(1000); int ret = ftp_download_main(sys_info.update_url); if (ret == RT_EOK) { char info_buf[64]; rt_snprintf(info_buf, sizeof(info_buf), "升级:下载完成 size=%d CRC=0x%08X", s_ftp_ctx.downloaded, s_ftp_ctx.total_crc); LOG_I("FTP upgrade completed! %s", info_buf); upgrade_flag = 0; ef_set_env_blob("up_flag", &upgrade_flag, sizeof(upgrade_flag)); Recode_e_code(e_update_ok, 0, info_buf); rt_thread_mdelay(500); rt_hw_cpu_reset(); } else { LOG_E("FTP upgrade failed: %d", ret); Recode_e_code(e_update_fail, ret, "升级:下载失败"); char update_res = 0x9A; ef_set_env_blob("update_res", &update_res, sizeof(update_res)); rt_thread_mdelay(500); rt_hw_cpu_reset(); } } /*============================================================================= * 启动FTP升级检测线程 *============================================================================*/ int ym310_ftp_start_upgrade_thread(void) { static rt_bool_t started = RT_FALSE; rt_thread_t tid; if (started) return RT_EOK; tid = rt_thread_create("ftp_upgrade", ftp_upgrade_thread_entry, RT_NULL, 8192, 20, 10); if (tid != RT_NULL) { rt_thread_startup(tid); started = RT_TRUE; //LOG_I("FTP upgrade thread created"); return RT_EOK; } LOG_E("Failed to create FTP upgrade thread"); return -RT_ERROR; } INIT_APP_EXPORT(ym310_ftp_start_upgrade_thread); /*============================================================================= * MSH命令(手动触发下载) *============================================================================*/ // static void ftp_download(int argc, char **argv) // { // const char *url = RT_NULL; // if (g_ym310_uart == RT_NULL) // { // LOG_I("AT core not initialized, initializing..."); // if (ym310_at_core_init() != RT_EOK) // { // LOG_E("AT core init failed"); // return; // } // } // if (argc >= 2) // url = argv[1]; // LOG_I("Starting FTP download..."); // int ret = ftp_download_main(url); // if (ret == RT_EOK) // LOG_I("FTP download completed!"); // else // LOG_E("FTP download failed: %d", ret); // } // MSH_CMD_EXPORT(ftp_download, Download firmware via FTP : ftp_download[url]); /*============================================================================= * 获取下载进度 *============================================================================*/ void ym310_ftp_get_progress(rt_uint32_t *downloaded, rt_uint32_t *total, rt_uint32_t *crc) { if (downloaded) *downloaded = s_ftp_ctx.downloaded; if (total) *total = s_ftp_ctx.file_size; if (crc) *crc = s_ftp_ctx.total_crc; }