#include "publicdata/publicdata.h" #if EC200A_4G_EN #include #include #include "plat_comm/4g_module/ec200a_4g.h" #include "plat_comm/4g_module/ec200a_4g_ftp.h" #include "mylog/mylog.h" #include "usart.h" #include "app_init/app_init.h" #include "plat_comm/impl/bs_public_impl.h" #if(MY_SHELL_EN) //Shell 使能 #include "letter_shell/shell_port.h" #endif #include "plat_comm/plat_comm_task.h" #include "plat_comm/impl/bs_connect_impl.h" #include "bsp_include.h" #include "app_fatfs/app_fatfs.h" #include "app_fatfs/fatfs_init.h" /* CCU621_M stubs: upgrade impl not available */ static int upgrade_erase_program_area(void) { return 0; } static int upgrade_success_update_header(upgrade_header_t *h) { (void)h; return 0; } static int upgrade_write_packet(uint32_t i, const uint8_t *d, uint32_t l) { (void)i; (void)d; (void)l; return (int)l; } static U8_T fatfs_set_write_disable_flag(U8_T m, U8_T e) { (void)m; (void)e; return 0U; } /* CCU621_M fs stubs */ static FS_Status fs_file_exists(const char *p, bool *e) { (void)p; if(e)*e=false; return FS_STATUS_OK; } static FS_Status fs_get_file_size(const char *p, uint32_t *s) { (void)p; if(s)*s=0; return FS_STATUS_OK; } static FS_Status fs_read_file_content(const char *p, char *b, uint32_t bs, FS_ReadRange *r) { (void)p; (void)b; (void)bs; (void)r; return FS_STATUS_OK; } #if BS_OCPP_EN #include "plat_comm/ocpp/BS_ocpp_ctrl.h" /** OCPP:STEP_FTP_SAVE 先上送 Installing,再写头、FRAM 置位并复位;Installed 在 BootNotification 后上送 */ static U8_T s_u8_fw_save_ocpp_arm; #endif /** 本文件 FTP/升级/日志 AT 调试:不走 Plat_Comm 的 Plat_Comm_LOG,统一 MYLOG_MSG(0xFF) 直出 */ #define EC200A_FTP_LOG(fmt, ...) MYLOG_MSG(0xFF, fmt, ##__VA_ARGS__) /* QFREAD 应答(含 CONNECT 与负载)须 ≥ 单包读长 + AT/URC 余量;与 EC200A_FTP_UFS_RW_CHUNK_BYTES 对齐 */ #define FTP_REAC_DATA_BUF_LEN ((U16_T)((EC200A_FTP_UFS_RW_CHUNK_BYTES) + 512U)) #if ((EC200A_FTP_UFS_RW_CHUNK_BYTES) % (PACKET_SIZE)) != 0 #error EC200A_FTP_UFS_RW_CHUNK_BYTES must be a multiple of PACKET_SIZE (FTP upgrade QFREAD -> flash packets). #endif //引出 给ftp升级配置使用 extern S8_T s8_FindAck(U8_T *src, U8_T *des); extern S16_T s16_FindAck_Ex(U8_T *src, U16_T srcLen, U8_T *des, U16_T desLen); extern S8_T s8_4g_Send_AT_Cmd(U8_T *sendBuf, U8_T sendLen, U8_T *ack, U8_T resendCnt, U16_T delayTimeMs); extern S8_T s8_4g_Send_Recv_Cmd(U8_T *sendBuf, U8_T sendLen,U8_T *rcvBuf,U16_T recvLen, U8_T *ack, U8_T resendCnt, U16_T delayTimeMs); extern S8_T s8_cat_reboot(U8_T u8_flag); extern S8_T s8_work_mode_cmd(); // 切换到指令模式 extern S8_T s8_ec200a_set_cgreg(); extern S8_T s8_ec200a_cgreg(); typedef enum { STEP_FTP_SWCHTO_CMD_MODE = 0, //切换到指令模式 STEP_FTP_SET_CGREG, STEP_FTP_QUERY_CGREG, STEP_FTP_SET_QIDEACT, //去激活PDP场景 STEP_FTP_SET_QIACT, //激活PDP场景 STEP_FTP_FTP_CFG_contextid, //配置 FTP(S) PDP 上下文 ID STEP_FTP_FTP_CFG_account, //配置 FTP(S) 设置用户名和密码。 STEP_FTP_FTP_CFG_filetype, //配置 FTP(S) 设置文件类型为ASCII。 STEP_FTP_FTP_CFG_transmode, //配置 FTP(S) 设置为被动传输方式。 STEP_FTP_FTP_CFG_rsptimeout, //配置 FTP(S) 设置最大响应时间(默认为90秒)。 STEP_FTP_FTP_CFG_QFTPOPEN, //登录FTP服务器。 STEP_FTP_FTP_CFG_QFTPCWD, //切换目录 STEP_FTP_FTP_CFG_QFTPLIST, //获取当前目录的内容,并通过 STEP_FTP_FTP_GET_QFTPSIZE, //获取 文件大小 STEP_FTP_FTP_QFDEL, //获取 删除存储区保存的文件内容 STEP_FTP_FTP_GET_QFTPGET, //获取 文件内容 qflst STEP_FTP_FTP_GET_QFLST, //获取 UFS中文件内容 qflst STEP_FTP_FTP_GET_QFOPEN, //打开文件 STEP_FTP_FTP_GET_QFSEEK, //设置读取文件指针头地址 STEP_FTP_FTP_GET_QFREAD, //读取文件 STEP_FTP_FTP_GET_LOG_INFO, //获取日志信息 STEP_FTP_FTP_UPLOAD_LOG_UFS, /* 模组 UFS:QFOPEN 覆盖写日志文件,建立句柄 */ STEP_FTP_FTP_WRITE_LOG_UFS, //写入日志文件到 UFS STEP_FTP_FTP_UPLOAD_LOG_FTP, //上传日志文件到 FTP /** 日志上传成功后须关闭 FTP 会话,否则 +QFTP* URC 残留易干扰同 UART 上后续 QISEND(break off=0) */ STEP_FTP_FTP_QFTPCLOSE_AFTER_LOG, STEP_FTP_SAVE, //保存当前设置并重启 STEP_FTP_INIT_OK, //设置完成 STEP_FTP_INIT_DEBUG, // 调试 }E_EC200A_4G_FTP_INIT_STEP; E_EC200A_4G_FTP_INIT_STEP e_ec200a_ftp_init_step = STEP_FTP_SWCHTO_CMD_MODE; #if !EC200A_TCP_USE_TRANSPARENT_MODE /** 日志经 QFWRITE 写入 UFS 时已发送字节累计(QFSEEK 绝对偏移) */ static uint32_t s_log_ufs_qwrite_total; /** * AT+QFREAD:单次调用内发 AT→轮询至应答中出现 OK(与 QFWRITE 同思路,不跨 PlatComm 累加)。 * @param rsp_out 成功时为 pvPortMalloc 缓冲,调用方须 vPortFree;失败时为 NULL。 */ static S16_T s16_qfread_at_slice(const U8_T *sendBuf, U16_T sendLen, U8_T **rsp_out) { U8_T *acc; U16_T rcv_len = 0U; U16_T total_waits = 0U; S16_T ack; U8_T ackOK[] = "OK"; if (rsp_out == NULL || sendBuf == NULL || sendLen == 0U || sendLen > 72U) { return -1; } *rsp_out = NULL; acc = (U8_T *)pvPortMalloc((size_t)FTP_REAC_DATA_BUF_LEN); if (acc == NULL) { EC200A_FTP_LOG("[FTP] QFREAD malloc fail\r\n"); return -1; } memset(acc, 0, (size_t)FTP_REAC_DATA_BUF_LEN); v_4g_send_data((U8_T)0xFF, (U8_T *)(void *)sendBuf, sendLen); while (total_waits < (U16_T)EC200A_FTP_QFREAD_TOTAL_WAITS) { mSleep(EC200A_FTP_UART_SLICE_WAIT_MS); if (rcv_len + 1U < (U16_T)FTP_REAC_DATA_BUF_LEN) { rcv_len += u16_4g_recv_data( acc + rcv_len, (U16_T)((U16_T)FTP_REAC_DATA_BUF_LEN - 1U - rcv_len)); } else { EC200A_FTP_LOG("[FTP] QFREAD rsp overflow\r\n"); vPortFree(acc); return -1; } total_waits++; if (rcv_len > 0U) { if (strstr((char *)acc, "+CME ERROR") != NULL || strstr((char *)acc, "\r\nERROR\r\n") != NULL) { EC200A_FTP_LOG("[FTP] QFREAD modem ERROR\r\n"); vPortFree(acc); return -1; } ack = s16_FindAck_Ex(acc, rcv_len, ackOK, (U16_T)(sizeof(ackOK) - 1U)); if (ack != (S16_T)-1) { *rsp_out = acc; return (S16_T)rcv_len; } } } EC200A_FTP_LOG("[FTP] QFREAD wait total exceeded (single session)\r\n"); vPortFree(acc); return -1; } /** 发 QFTPGET 前排空模组 UART 残留,减轻与上一条 QIRD/URC 粘包导致帧头错位 */ static void v_qftpget_drain_modem_rx(void) { U8_T junk[128]; U16_T got; uint8_t r; for (r = 0U; r < 48U; r++) { got = u16_4g_recv_data(junk, (U16_T)sizeof(junk)); if (got == 0U) { break; } } } /** QFTPGET→COM:必须以行格式识别 CONNECT,避免固件二进制内出现子串 "CONNECT" 时负载起点错位 */ static int s32_qftpget_find_payload_start(const U8_T *raw, uint32_t raw_len, uint32_t *out_data_start) { static const uint8_t p_crlf[] = "\r\nCONNECT\r\n"; const uint32_t n_crlf = (uint32_t)(sizeof(p_crlf) - 1U); static const uint8_t p_lf[] = "\nCONNECT\n"; const uint32_t n_lf = (uint32_t)(sizeof(p_lf) - 1U); static const uint8_t p_tail[] = "CONNECT\r\n"; const uint32_t n_tail = (uint32_t)(sizeof(p_tail) - 1U); static const char atpfx[] = "AT+QFTPGET"; const uint32_t n_at = (uint32_t)(sizeof(atpfx) - 1U); uint32_t i; uint32_t atmin = 0U; uint8_t found_at = 0U; if (raw == NULL || out_data_start == NULL) { return -1; } for (i = 0U; i + n_crlf <= raw_len; i++) { if (memcmp(raw + i, p_crlf, (size_t)n_crlf) == 0) { *out_data_start = i + n_crlf; return 0; } } for (i = 0U; i + n_lf <= raw_len; i++) { if (memcmp(raw + i, p_lf, (size_t)n_lf) == 0) { *out_data_start = i + n_lf; return 0; } } { static const uint8_t p_lfcrlf[] = "\nCONNECT\r\n"; const uint32_t n_lfcrlf = (uint32_t)(sizeof(p_lfcrlf) - 1U); for (i = 0U; i + n_lfcrlf <= raw_len; i++) { if (memcmp(raw + i, p_lfcrlf, (size_t)n_lfcrlf) == 0) { *out_data_start = i + n_lfcrlf; return 0; } } } for (i = 0U; i + n_at <= raw_len; i++) { if (memcmp(raw + i, atpfx, (size_t)n_at) == 0) { atmin = i; found_at = 1U; break; } } if (found_at != 0U) { for (i = atmin; i + n_tail <= raw_len; i++) { if (memcmp(raw + i, p_tail, (size_t)n_tail) == 0) { *out_data_start = i + n_tail; return 0; } } } return -1; } /** 仅在 [0, scan_len) 内查 AT 失败串;CONNECT 之后为固件二进制,勿全缓冲 strstr(易误判) */ static int s32_qftpget_prefix_has_modem_fail(const U8_T *buf, uint32_t scan_len) { static const char k_cme[] = "+CME ERROR"; static const char k_err[] = "\r\nERROR\r\n"; const uint32_t L1 = (uint32_t)(sizeof(k_cme) - 1U); const uint32_t L2 = (uint32_t)(sizeof(k_err) - 1U); uint32_t n; if (buf == NULL || scan_len == 0U) { return 0; } if (scan_len >= L1) { for (n = 0U; n + L1 <= scan_len; n++) { if (memcmp(buf + n, k_cme, (size_t)L1) == 0) { return 1; } } } if (scan_len >= L2) { for (n = 0U; n + L2 <= scan_len; n++) { if (memcmp(buf + n, k_err, (size_t)L2) == 0) { return 1; } } } return 0; } /** * AT+QFTPGET="remote","COM:",off,len:非透传下与 QFREAD 同思路,拼 UART 直至 **CONNECT 后 expect_payload 字节 + \\r\\nOK**。 * 不得仅因缓冲区内出现「OK」(如 QIRD 的 \\r\\nOK)提前返回,否则会截断固件二进制。 * @param payload_off_out 成功时输出 CONNECT 负载首字节在 acc 内下标 */ static S16_T s16_qftpget_com_at_slice(const U8_T *sendBuf, U16_T sendLen, U8_T **rsp_out, U16_T expect_payload, uint32_t *payload_off_out) { U8_T *acc; U16_T rcv_len = 0U; U16_T total_waits = 0U; const U16_T max_waits = (U16_T)((EC200A_FTP_QFREAD_TOTAL_WAITS) * 12U); if (rsp_out == NULL || payload_off_out == NULL || sendBuf == NULL || sendLen == 0U || sendLen > 220U || expect_payload == 0U) { return -1; } *rsp_out = NULL; *payload_off_out = 0U; acc = (U8_T *)pvPortMalloc((size_t)FTP_REAC_DATA_BUF_LEN); if (acc == NULL) { EC200A_FTP_LOG("[FTP] QFTPGET(COM) malloc fail\r\n"); return -1; } memset(acc, 0, (size_t)FTP_REAC_DATA_BUF_LEN); v_qftpget_drain_modem_rx(); v_4g_send_data((U8_T)0xFF, (U8_T *)(void *)sendBuf, sendLen); while (total_waits < max_waits) { mSleep(EC200A_FTP_UART_SLICE_WAIT_MS); if (rcv_len + 1U < (U16_T)FTP_REAC_DATA_BUF_LEN) { rcv_len += u16_4g_recv_data( acc + rcv_len, (U16_T)((U16_T)FTP_REAC_DATA_BUF_LEN - 1U - rcv_len)); } else { EC200A_FTP_LOG("[FTP] QFTPGET(COM) rsp overflow\r\n"); vPortFree(acc); return -1; } total_waits++; if (rcv_len > 0U) { uint32_t ds = 0U; int pay_ok; uint32_t scan_top; pay_ok = s32_qftpget_find_payload_start(acc, (uint32_t)rcv_len, &ds); if (pay_ok == 0) { scan_top = ds; } else { scan_top = (uint32_t)rcv_len; if (scan_top > 256U) { scan_top = 256U; } } if (s32_qftpget_prefix_has_modem_fail(acc, scan_top)) { EC200A_FTP_LOG("[FTP] QFTPGET(COM) modem ERROR (prefix scan top=%u)\r\n", (unsigned)scan_top); vPortFree(acc); return -1; } if (pay_ok == 0) { uint32_t need = ds + (uint32_t)expect_payload + 4U; if (rcv_len >= need) { if (memcmp(acc + ds + expect_payload, "\r\nOK", 4U) == 0) { *rsp_out = acc; *payload_off_out = ds; return (S16_T)rcv_len; } } } } } EC200A_FTP_LOG("[FTP] QFTPGET(COM) wait total exceeded (expect_payload=%u)\r\n", (unsigned)expect_payload); vPortFree(acc); return -1; } static void s8_ec200a_qfclose_log_fd(void) { U8_T b[48]; int fd = (int)t_ftp_update_info.u32_file_fd; if (fd <= 0) { return; } (void)snprintf((char *)b, sizeof(b), "AT+QFCLOSE=%d\r\n", fd); (void)s8_4g_Send_AT_Cmd(b, (U16_T)strlen((char *)b), NULL, 5U, 200U); t_ftp_update_info.u32_file_fd = 0U; } /** 日志上传:QFOPEN 覆盖写 UFS 裸文件名(与 QFTPPUT 侧 UFS: 名一致) */ static S8_T s8_ec200a_qfopen_log_ufs(void) { U8_T u8_sendBuf[96] = {0}; U8_T rcvBuf[BS_RECV_BUF_LEN] = {0}; U8_T u8_ack[] = "+QFOPEN:"; char *last_slash = strrchr((char *)t_ftp_update_info.u8_file_name, '/'); S8_T s8_ret; int fdi = 0; if (last_slash == NULL) { return -1; } (void)snprintf((char *)u8_sendBuf, sizeof(u8_sendBuf), "AT+QFOPEN=\"%s\",1\r\n", last_slash + 1); s8_ret = s8_4g_Send_Recv_Cmd(u8_sendBuf, (U16_T)strlen((char *)u8_sendBuf), rcvBuf, BS_RECV_BUF_LEN, u8_ack, 5, 200); if (s8_ret < 0) { return -1; } if (sscanf((char *)&rcvBuf[s8_ret], "+QFOPEN: %d", &fdi) != 1 || fdi <= 0) { return -1; } t_ftp_update_info.u32_file_fd = (U32_T)fdi; s_log_ufs_qwrite_total = 0U; EC200A_FTP_LOG("[FTP-LOG] QFOPEN ok fd=%d file=%s\r\n", fdi, last_slash + 1); return 0; } static S8_T s8_ec200a_qfseek_log_abs(uint32_t abs_off) { U8_T u8_sendBuf[64] = {0}; int fd = (int)t_ftp_update_info.u32_file_fd; if (fd <= 0) { return -1; } (void)snprintf((char *)u8_sendBuf, sizeof(u8_sendBuf), "AT+QFSEEK=%d,%lu,0\r\n", fd, (unsigned long)abs_off); return s8_4g_Send_AT_Cmd(u8_sendBuf, (U16_T)strlen((char *)u8_sendBuf), NULL, 5, 150); } /** 单包 QFWRITE:本次调用内发 AT→轮询 CONNECT→发负载→等 OK;UART 应答仅在函数内局部 `acc` 拼接,不跨 PlatComm。 * 负载用 `pvPortMalloc(wlen)`,超过 `EC200A_FTP_QFWRITE_TOTAL_WAITS` 仍未完成则返回 -1。 */ static S8_T s8_ec200a_qfwrite_log_bin(const U8_T *data, U16_T wlen) { U8_T cmd[72] = {0}; U8_T acc[512]; U16_T acc_len = 0; U8_T phase = 0; U16_T total_waits = 0; U32_T tout = (U32_T)EC200A_FTP_QFUPL_AT_TIMEOUT_SEC; int fd = (int)t_ftp_update_info.u32_file_fd; const char *needle_ok = "\r\nOK"; U8_T *pay = NULL; if (data == NULL || wlen == 0U) { return (S8_T)-1; } if (wlen > (U16_T)EC200A_FTP_UFS_RW_CHUNK_BYTES) { return (S8_T)-1; } if (fd <= 0) { return (S8_T)-1; } if (tout < 5U) { tout = 5U; } if (tout > 65535U) { tout = 65535U; } pay = (U8_T *)pvPortMalloc((size_t)wlen); if (pay == NULL) { EC200A_FTP_LOG("[FTP-LOG] QFWRITE malloc pay fail\r\n"); return (S8_T)-1; } memcpy(pay, data, (size_t)wlen); memset(acc, 0, sizeof(acc)); (void)snprintf((char *)cmd, sizeof(cmd), "AT+QFWRITE=%d,%u,%lu\r\n", fd, (unsigned)wlen, (unsigned long)tout); v_4g_send_data((U8_T)0xFF, cmd, (U16_T)strlen((char *)cmd)); while (total_waits < (U16_T)EC200A_FTP_QFWRITE_TOTAL_WAITS) { mSleep(EC200A_FTP_UART_SLICE_WAIT_MS); if (acc_len + 32U < sizeof(acc)) { acc_len += u16_4g_recv_data(acc + acc_len, (U16_T)(sizeof(acc) - 1U - acc_len)); } else { if (acc_len > 160U) { memmove(acc, acc + 160U, (size_t)(acc_len - 160U)); acc_len = (U16_T)(acc_len - 160U); } else { acc_len = 0U; } acc_len += u16_4g_recv_data(acc + acc_len, (U16_T)(sizeof(acc) - 1U - acc_len)); } if (acc_len >= sizeof(acc)) { acc_len = (U16_T)(sizeof(acc) - 1U); } acc[acc_len] = '\0'; total_waits++; if (strstr((char *)acc, "+CME ERROR") != NULL || strstr((char *)acc, "\r\nERROR\r\n") != NULL) { EC200A_FTP_LOG("[FTP-LOG] QFWRITE modem ERROR\r\n"); vPortFree(pay); return (S8_T)-1; } if (phase == 0U) { if (strstr((char *)acc, "CONNECT") != NULL) { v_4g_send_data((U8_T)0xFF, pay, wlen); acc_len = 0; acc[0] = '\0'; phase = 1U; } } else { if (strstr((char *)acc, needle_ok) != NULL) { vPortFree(pay); return 0; } } } EC200A_FTP_LOG("[FTP-LOG] QFWRITE wait total exceeded (single session)\r\n"); vPortFree(pay); return (S8_T)-1; } #endif /* !EC200A_TCP_USE_TRANSPARENT_MODE */ #if 1 //ftp初始化配置 void v_ftp_ctrl_init() { #if !EC200A_TCP_USE_TRANSPARENT_MODE s_log_ufs_qwrite_total = 0U; #endif #if BS_OCPP_EN s_u8_fw_save_ocpp_arm = 0U; v_ocpp_clr_fw_installing_fsn_sent(); #endif e_ec200a_ftp_init_step = STEP_FTP_SWCHTO_CMD_MODE; } #endif #if 1 //数据操作 /** * @brief 解析4G模块FTP下载响应数据(简化版,只需验证\r\nOK) * @param raw_data: 原始接收数据 * @param raw_len: 原始数据长度 * @param file_len: 输出参数,文件数据长度 * @retval 成功返回文件数据起始地址,失败返回NULL */ const uint8_t* parse_4g_ftp_response(const uint8_t *raw_data, uint32_t raw_len, uint32_t *file_len) { // 改为查找 "CONNECT" 关键字 const uint8_t connect_keyword[] = "CONNECT"; const uint8_t ok_footer[] = "\r\nOK"; const uint8_t connect_keyword_len = sizeof(connect_keyword) - 1; // 7字节 const uint8_t ok_footer_len = sizeof(ok_footer) - 1; // 4字节 int32_t connect_pos = -1; int32_t ok_pos = -1; const uint8_t *file_start = NULL; if (raw_data == NULL || file_len == NULL) { return NULL; } *file_len = 0; // 1. 查找CONNECT关键字 for (uint32_t i = 0; i < raw_len - connect_keyword_len; i++) { if (memcmp(raw_data + i, connect_keyword, connect_keyword_len) == 0) { connect_pos = i; break; } } if (connect_pos < 0) { //EC200A_FTP_LOG("Error: CONNECT keyword not found\n"); return NULL; } // 2. 从CONNECT位置开始,找到下一个\r\n,文件数据从这里之后开始 uint32_t data_start = connect_pos; while (data_start < raw_len) { if (raw_data[data_start] == '\r' && raw_data[data_start + 1] == '\n') { data_start += 2; // 跳过\r\n break; } data_start++; } if (data_start >= raw_len) { //EC200A_FTP_LOG("Error: Cannot find data start after CONNECT\n"); return NULL; } // 3. 查找OK尾部(从后往前找,找到最后一个\r\nOK) ok_pos = -1; for (int32_t i = raw_len - ok_footer_len; i >= 0; i--) { if (memcmp(raw_data + i, ok_footer, ok_footer_len) == 0) { ok_pos = i; break; } } if (ok_pos < 0) { //EC200A_FTP_LOG("Error: \\r\\nOK footer not found\n"); return NULL; } // 4. 计算文件数据长度和起始地址 if (data_start >= (uint32_t)ok_pos) { //EC200A_FTP_LOG("Error: Invalid data positions (start=%u, ok=%d)\n", data_start, ok_pos); return NULL; } *file_len = ok_pos - data_start; file_start = raw_data + data_start; if (*file_len == 0) { //EC200A_FTP_LOG("Error: No file data found\n"); return NULL; } //EC200A_FTP_LOG("Success: Found file data at offset %u, length %u bytes\n", data_start, *file_len); return file_start; } /** * ************************************************************************ * @brief - FTP 获取文件数据时 自定义发送解析内容 * * @param[in] sendBuf - 发送数据指针 * @param[in] sendLen - 发送数据长度 * @param[in] rcvBuf - 接收的数据内容 外部传入,接收赋值后传出 方便外部直接使用 * @param[in] recvLen - 最大可支持数据内容,防止数组越界 * @param[in] resendCnt - 重发次数计数 * @param[in] delayTimeMs - 单次接收等待延时时间 * * @return S16_T - 期望应答有效数据 -1 未接收到 》=0 成功接收到期望应答数据 并返回接收数据长度 * * @author jiankalka * @date 2025-11-24 * ************************************************************************ */ S16_T s16_4g_Send_Recv_Ftp_Get(U8_T *sendBuf, U8_T sendLen,U8_T *rcvBuf,U16_T recvLen, U8_T resendCnt, U16_T delayTimeMs) { U8_T count = 0; U16_T rcvLen = 0; S16_T ackIndex = -1; U8_T ackOK[] = "OK"; // com_write(COM_PORT_BS, sendBuf, sendLen, 0); v_4g_send_data((U8_T)0xFF, sendBuf, sendLen); memset(rcvBuf, 0, recvLen); do { mSleep(delayTimeMs); // rcvLen = com_read(COM_PORT_BS, rcvBuf, 100, 0); if(rcvLen < recvLen) //已接收数据满了 不再接收新数据 rcvLen += u16_4g_recv_data(&rcvBuf[rcvLen], recvLen-rcvLen); if (rcvLen > 0) { //EC200A_FTP_LOG("recvLen = %d\r\n",rcvLen); ackIndex = s16_FindAck_Ex(rcvBuf,rcvLen, ackOK ,sizeof(ackOK)-1); if ((ackIndex != -1)) { return rcvLen; } } count++; } while (count < resendCnt); // 如果没收到期望应答,重新发送 //EC200A_FTP_LOG("sendBuf = %s is recv err [%d]\r\n",sendBuf,ackIndex); return -1; } #endif #if 1 //逻辑控制 /** * ************************************************************************ * @brief - 升级头部信息控制 * * @param[in] u8_flag - 0升级前 1升级后 * * * @author jiankalka * @date 2025-12-01 * ************************************************************************ */ static void v_upgrade_header_data(U8_T u8_flag) { upgrade_header_t header_data; Comm_Time time; GetCurrentTime(&time); memset(&header_data , 0, sizeof(upgrade_header_t)); header_data.file_size = t_ftp_update_info.u32_file_size; // 升级文件总大小 header_data.packet_count = (t_ftp_update_info.u32_file_size + (uint32_t)PACKET_SIZE - 1U) / (uint32_t)PACKET_SIZE; // 总包数(按 Flash 1KB 包计,与 QFREAD 块大小无关) header_data.crc32 = 0; // 文件CRC32校验和 header_data.timestamp = xDate2Seconds(&time); // 时间戳 //uint8_t version[16]; // 版本号 //uint8_t reserved[32]; // 保留字段 if(u8_flag == 0) { /* 升级开始前:先清空升级区域所有扇区,写入时就不用再擦除 */ if (upgrade_erase_program_area() != 0) { EC200A_FTP_LOG("upgrade_erase_program_area failed\r\n"); } header_data.upgrade_flag = UPGRADE_FLAG_IDLE; // 升级标志 无数据状态 } else { header_data.upgrade_flag = UPGRADE_FLAG_NEED_UP; // 升级标志 存在需要升级数据 } EC200A_FTP_LOG("upgrade_header flag(%d) fileSize[%d]",header_data.upgrade_flag,t_ftp_update_info.u32_file_size); upgrade_success_update_header(&header_data); } /** * ************************************************************************ * @brief - 发送一次AT并等待指定ACK(不重发) * * @param[in] sendBuf - 发送数据指针 * @param[in] sendLen - 发送数据长度 * @param[in/out] rcvBuf - 接收缓存 * @param[in] recvLen - 接收缓存长度 * @param[in] ack - 期望ACK字符串 * @param[in] ackLen - ACK长度 * @param[in] pollDelayMs - 轮询间隔 * @param[in] pollCount - 轮询次数 * * @return S16_T - ack在rcvBuf中的索引,失败返回-1 * ************************************************************************ */ static S16_T s16_4g_Send_Once_WaitAck(U8_T *sendBuf, U16_T sendLen, U8_T *rcvBuf, U16_T recvLen, const U8_T *ack, U16_T ackLen, U16_T pollDelayMs, U16_T pollCount) { U16_T rcvLen = 0; S16_T ackIndex = -1; if (sendBuf == NULL || rcvBuf == NULL || ack == NULL || recvLen == 0 || ackLen == 0) return -1; v_4g_send_data((U8_T)0xFF, sendBuf, sendLen); memset(rcvBuf, 0, recvLen); for (U16_T i = 0; i < pollCount; i++) { mSleep(pollDelayMs); if (rcvLen < recvLen) rcvLen += u16_4g_recv_data(&rcvBuf[rcvLen], recvLen - rcvLen); if (rcvLen > 0) { ackIndex = s16_FindAck_Ex(rcvBuf, rcvLen, (U8_T *)ack, ackLen); if (ackIndex != -1) return ackIndex; } } return -1; } /** * ************************************************************************ * @brief - 发送一次AT并等待ACK1或ACK2(不重发) * * @return >0 命中ack1; <0 命中ack2; 0/其它 失败 * ************************************************************************ */ static S16_T s16_4g_Send_Once_WaitAck2(U8_T *sendBuf, U16_T sendLen, U8_T *rcvBuf, U16_T recvLen, const U8_T *ack1, U16_T ack1Len, const U8_T *ack2, U16_T ack2Len, U16_T pollDelayMs, U16_T pollCount) { U16_T rcvLen = 0; S16_T idx1 = -1; S16_T idx2 = -1; if (sendBuf == NULL || rcvBuf == NULL || recvLen == 0) return 0; if (ack1 == NULL || ack1Len == 0 || ack2 == NULL || ack2Len == 0) return 0; v_4g_send_data((U8_T)0xFF, sendBuf, (U16_T)sendLen); memset(rcvBuf, 0, recvLen); for (U16_T i = 0; i < pollCount; i++) { mSleep(pollDelayMs); if (rcvLen < recvLen) rcvLen += u16_4g_recv_data(&rcvBuf[rcvLen], recvLen - rcvLen); if (rcvLen > 0) { idx1 = s16_FindAck_Ex(rcvBuf, rcvLen, (U8_T *)ack1, ack1Len); if (idx1 != -1) return idx1; // >0: ack1 idx2 = s16_FindAck_Ex(rcvBuf, rcvLen, (U8_T *)ack2, ack2Len); if (idx2 != -1) return (S16_T)(-idx2); // <0: ack2 } } return 0; } #endif #if 1 //AT指令 /** * ************************************************************************ * @brief - 配置 FTP(S) 使用的 PDP 上下文(恒为 EC200A_FTP_PDP_CTX_ID,与业务 TCP 分场景,见 ec200a_4g_cfg.h) * ************************************************************************ */ static S8_T s8_ec200a_qftpcfg_contextid(void) { U8_T u8_sendBuf[40]; unsigned ctx = (unsigned)EC200A_FTP_PDP_CTX_ID; int n = snprintf((char *)u8_sendBuf, sizeof(u8_sendBuf), "AT+QFTPCFG=\"contextid\",%u\r\n", ctx); if (n <= 0 || n >= (int)sizeof(u8_sendBuf)) { return -1; } return s8_4g_Send_AT_Cmd(u8_sendBuf, (U16_T)n, NULL, 3, 50); } /** * ************************************************************************ * @brief - 设置用户名和密码。 * * * @return S8_T - * * @author jiankalka * @date 2025-11-29 * ************************************************************************ */ static S8_T s8_ec200a_qftpcfg_account() { U8_T u8_sendBuf[64] = {0}; //AT+QFTPCFG="account","test","test" //设置用户名和密码。 snprintf((char *)u8_sendBuf,64,"AT+QFTPCFG=\"account\",\"%s\",\"%s\"\r\n", t_ftp_update_info.u8_user , t_ftp_update_info.u8_pwd); return s8_4g_Send_AT_Cmd(u8_sendBuf, sizeof(u8_sendBuf) - 1, NULL, 5, 100); } /** * ************************************************************************ * @brief - 设置文件类型为二进制 0 二进制 1 ASCII * * * @return S8_T - * * @author jiankalka * @date 2025-11-29 * ************************************************************************ */ static S8_T s8_ec200a_qftpcfg_filetype() { U8_T u8_sendBuf[] = "AT+QFTPCFG=\"filetype\",0\r\n"; return s8_4g_Send_AT_Cmd(u8_sendBuf, sizeof(u8_sendBuf) - 1, NULL, 5, 100); } /** * ************************************************************************ * @brief - 设置为被动传输方式。 * * * @return S8_T - * * @author jiankalka * @date 2025-11-29 * ************************************************************************ */ static S8_T s8_ec200a_qftpcfg_transmode() { U8_T u8_sendBuf[] = "AT+QFTPCFG=\"transmode\",1\r\n"; return s8_4g_Send_AT_Cmd(u8_sendBuf, sizeof(u8_sendBuf) - 1, NULL, 20, 1000); } /** * ************************************************************************ * @brief - 设置最大响应时间(默认为90秒)。 * * * @return S8_T - * * @author jiankalka * @date 2025-11-29 * ************************************************************************ */ static S8_T s8_ec200a_qftpcfg_rsptimeout() { U8_T u8_sendBuf[] = "AT+QFTPCFG=\"rsptimeout\",90\r\n"; return s8_4g_Send_AT_Cmd(u8_sendBuf, sizeof(u8_sendBuf) - 1, NULL, 5, 100); } /** * ************************************************************************ * @brief - 登录FTP服务器。 * * * @return S8_T - * * @author jiankalka * @date 2025-11-29 * ************************************************************************ */ static S8_T s8_ec200a_qftpopen() { U8_T u8_sendBuf[64] = {0}; //AT+QFTPOPEN="192.0.2.0",8309 登录FTP服务器 snprintf((char *)u8_sendBuf,64,"AT+QFTPOPEN=\"%s\",%d\r\n", t_ftp_update_info.u8_update_server_addr , t_ftp_update_info.u16_update_server_port); return s8_4g_Send_AT_Cmd(u8_sendBuf, sizeof(u8_sendBuf) - 1, NULL, 5, 100); } /** * ************************************************************************ * @brief - 设置目录 * * * @return S8_T - * * @author jiankalka * @date 2025-11-29 * ************************************************************************ */ static S8_T s8_ec200a_qftpcwd() { const char *p = (const char *)t_ftp_update_info.u8_path; U8_T u8_sendBuf[160] = {0}; S8_T r; size_t pl; int n; if (p == NULL || p[0] == '\0') { EC200A_FTP_LOG("[FTP-UPG] QFTPCWD skip (empty path)\r\n"); return 1; } pl = strlen(p); while (pl > 0U && p[pl - 1U] == '/') { pl--; } if (pl == 0U) { return 1; } n = snprintf((char *)u8_sendBuf, sizeof(u8_sendBuf), "AT+QFTPCWD=\"%.*s\"\r\n", (int)pl, p); if (n <= 0 || n >= (int)sizeof(u8_sendBuf)) { EC200A_FTP_LOG("[FTP-UPG] QFTPCWD cmd too long, skip (use full remote path)\r\n"); return 1; } r = s8_4g_Send_AT_Cmd(u8_sendBuf, (U16_T)n, NULL, 5, 100); if (r >= 0) { return r; } if (p[0] == '/') { n = snprintf((char *)u8_sendBuf, sizeof(u8_sendBuf), "AT+QFTPCWD=\"%.*s\"\r\n", (int)(pl - 1U), p + 1); if (n > 0 && n < (int)sizeof(u8_sendBuf)) { r = s8_4g_Send_AT_Cmd(u8_sendBuf, (U16_T)n, NULL, 5, 100); if (r >= 0) { return r; } } } EC200A_FTP_LOG("[FTP-UPG] QFTPCWD fail, continue (QFTPSIZE/QFTPGET use full remote path) ret=%d\r\n", (int)r); return 1; } /** * ************************************************************************ * @brief - 获取当前目录的内容,并通过 COM 口输出数据。 * * * @return S8_T - * * @author jiankalka * @date 2025-11-29 * ************************************************************************ */ static S8_T s8_ec200a_qftplist() { U8_T u8_sendBuf[] = "AT+QFTPLIST=\".\"\r\n"; return s8_4g_Send_AT_Cmd(u8_sendBuf, sizeof(u8_sendBuf) - 1, NULL, 5, 100); } /** 远端 path + file,供 QFTPSIZE / QFTPGET(COM)(减少对 QFTPCWD 的依赖) */ static U8_T s_ftp_remote_get_obj[200]; static int s32_ftp_fill_remote_get_object(void) { const char *p = (const char *)t_ftp_update_info.u8_path; const char *f = (const char *)t_ftp_update_info.u8_file_name; size_t pl; int n; if (f[0] == '\0') { return -1; } pl = (p != NULL) ? strlen(p) : 0U; while (pl > 0U && p[pl - 1U] == '/') { pl--; } if (pl == 0U) { n = snprintf((char *)s_ftp_remote_get_obj, sizeof(s_ftp_remote_get_obj), "%s", f); } else { n = snprintf((char *)s_ftp_remote_get_obj, sizeof(s_ftp_remote_get_obj), "%.*s/%s", (int)pl, p, f); } if (n <= 0 || (size_t)n >= sizeof(s_ftp_remote_get_obj)) { EC200A_FTP_LOG("[FTP-UPG] remote object path too long\r\n"); return -1; } return 0; } /** * ************************************************************************ * @brief - 获取文件大小 并计算包数量 * * * @return S8_T - * * @author jiankalka * @date 2025-11-29 * ************************************************************************ */ static S8_T s8_ec200a_qftpsize() { U8_T u8_sendBuf[256] = {0}; int n, k; enum { QFTPSIZE_ACCUM_LEN = 640 }; U8_T rcvBuf[QFTPSIZE_ACCUM_LEN]; int cmd_len; U16_T rcv_len = 0U; U16_T total_waits = 0U; /* +QFTPSIZE 常为异步 URC,可与业务 TCP 的 QIRD 应答交错;勿用 s8_4g_Send_Recv_Cmd 仅 2×50ms 等首包 */ const U16_T max_waits = (U16_T)((EC200A_FTP_QFREAD_TOTAL_WAITS) * 10U); if (s32_ftp_fill_remote_get_object() != 0) { return -1; } cmd_len = snprintf((char *)u8_sendBuf, sizeof(u8_sendBuf), "AT+QFTPSIZE=\"%s\"\r\n", (char *)s_ftp_remote_get_obj); if (cmd_len <= 0 || cmd_len >= (int)sizeof(u8_sendBuf)) { EC200A_FTP_LOG("[FTP-UPG] QFTPSIZE cmd too long (obj=%s,len=%d)\r\n", (char *)s_ftp_remote_get_obj, cmd_len); return -1; } memset(rcvBuf, 0, sizeof(rcvBuf)); v_4g_send_data((U8_T)0xFF, u8_sendBuf, (U16_T)cmd_len); while (total_waits < max_waits) { mSleep(EC200A_FTP_UART_SLICE_WAIT_MS); if (rcv_len + 1U < (U16_T)sizeof(rcvBuf)) { rcv_len += u16_4g_recv_data( rcvBuf + rcv_len, (U16_T)((U16_T)sizeof(rcvBuf) - 1U - rcv_len)); } else { EC200A_FTP_LOG("[FTP-UPG] QFTPSIZE accum overflow\r\n"); return -1; } total_waits++; if (rcv_len == 0U) { continue; } if (strstr((char *)rcvBuf, "+CME ERROR") != NULL || strstr((char *)rcvBuf, "\r\nERROR\r\n") != NULL) { EC200A_FTP_LOG("[FTP-UPG] QFTPSIZE modem ERROR in rsp\r\n"); return -1; } { char *p = strstr((char *)rcvBuf, "+QFTPSIZE:"); if (p != NULL && sscanf(p, "+QFTPSIZE: %d,%d", &n, &k) == 2) { if (k <= 0) { EC200A_FTP_LOG("[FTP-UPG] QFTPSIZE invalid size=%d\r\n", k); return -1; } t_ftp_update_info.u32_file_size = (uint32_t)k; EC200A_FTP_LOG("[FTP-UPG] QFTPSIZE obj=%s size=%lu (waits=%u)\r\n", (char *)s_ftp_remote_get_obj, (unsigned long)t_ftp_update_info.u32_file_size, (unsigned)total_waits); return 0; } } } EC200A_FTP_LOG("[FTP-UPG] QFTPSIZE timeout no +QFTPSIZE (obj=%s, rcv_len=%u)\r\n", (char *)s_ftp_remote_get_obj, (unsigned)rcv_len); return -1; } /** * ************************************************************************ * @brief - //删除 UFS 中所有文件(不删除目录)。 * * * @return S8_T - * * @author jiankalka * @date 2025-11-29 * ************************************************************************ */ static S8_T s8_ec200a_qfdel() { U8_T u8_sendBuf[] = "AT+QFDEL=\"*\"\r\n"; ////删除 UFS 中所有文件(不删除目录)。 return s8_4g_Send_AT_Cmd(u8_sendBuf, sizeof(u8_sendBuf) - 1, NULL, 5, 100); } /** 调试:打印本包负载前 10 / 后 10 字节(与 PC 上 bin 对拍);len≤10 只打一行 */ static void v_qftpget_log_pkt_data_edges(const U8_T *p, uint32_t len, uint32_t pkt_idx, int byte_off) { char hbuf[56]; char tbuf[56]; uint32_t hlen; uint32_t tstart; uint32_t ti; int pos; if (p == NULL || len == 0U) { return; } hlen = (len < 10U) ? len : 10U; pos = 0; for (ti = 0U; ti < hlen && pos < (int)sizeof(hbuf) - 4; ti++) { pos += snprintf(hbuf + (size_t)pos, sizeof(hbuf) - (size_t)pos, "%02X ", (unsigned)p[ti]); } hbuf[sizeof(hbuf) - 1U] = '\0'; if (len <= 10U) { EC200A_FTP_LOG("[FTP-UPG] pkt%u off=%d len=%lu hex: %s\r\n", (unsigned)pkt_idx, byte_off, (unsigned long)len, hbuf); return; } tstart = len - 10U; pos = 0; for (ti = tstart; ti < len && pos < (int)sizeof(tbuf) - 4; ti++) { pos += snprintf(tbuf + (size_t)pos, sizeof(tbuf) - (size_t)pos, "%02X ", (unsigned)p[ti]); } tbuf[sizeof(tbuf) - 1U] = '\0'; EC200A_FTP_LOG("[FTP-UPG] pkt%u off=%d len=%lu head10: %s tail10: %s\r\n", (unsigned)pkt_idx, byte_off, (unsigned long)len, hbuf, tbuf); } /** * ************************************************************************ * @brief - 周期获取文件内容 并存储 * * * @return S8_T - -1 获取失败 0获取中 1获取完成 * * @author jiankalka * @date 2025-11-29 * ************************************************************************ */ static S8_T s8_ec200a_qftpget() { U8_T u8_sendBuf[224] = {0}; S16_T s16_ret; U32_T file_len = 0U; const U8_T *file_data = NULL; uint32_t done; uint32_t f_sz; uint32_t rem; U16_T read_size; int packAddr; int sn; #if EC200A_TCP_USE_TRANSPARENT_MODE U8_T rcvBuf[FTP_REAC_DATA_BUF_LEN] = {0}; #else U8_T *qfget_rsp = NULL; #endif /* 分片经 COM 拉取并写 Flash(与 QFREAD 分包思路一致),不经 UFS 整文件 */ if (s32_ftp_fill_remote_get_object() != 0) { return -1; } f_sz = t_ftp_update_info.u32_file_size; done = (uint32_t)t_ftp_update_info.u16_packIndex * (uint32_t)t_ftp_update_info.u16_packSize; rem = (f_sz > done) ? (f_sz - done) : 0U; read_size = t_ftp_update_info.u16_packSize; if (rem == 0U) { return 1; } if (rem < (uint32_t)read_size) { read_size = (U16_T)rem; } packAddr = (int)((uint32_t)t_ftp_update_info.u16_packIndex * (uint32_t)t_ftp_update_info.u16_packSize); if (t_ftp_update_info.u16_packIndex == 0) { v_upgrade_header_data(0); } sn = snprintf((char *)u8_sendBuf, sizeof(u8_sendBuf), "AT+QFTPGET=\"%s\",\"COM:\",%d,%u\r\n", (char *)s_ftp_remote_get_obj, packAddr, (unsigned)read_size); if (sn <= 0 || sn >= (int)sizeof(u8_sendBuf)) { EC200A_FTP_LOG("[FTP-UPG] QFTPGET(COM) cmd too long\r\n"); return -1; } #if EC200A_TCP_USE_TRANSPARENT_MODE s16_ret = s16_4g_Send_Recv_Ftp_Get(u8_sendBuf, (U16_T)sn, rcvBuf, (U16_T)FTP_REAC_DATA_BUF_LEN, 12, 200); #else { uint32_t com_pay_off = 0U; s16_ret = s16_qftpget_com_at_slice(u8_sendBuf, (U16_T)sn, &qfget_rsp, read_size, &com_pay_off); if (s16_ret >= 0 && qfget_rsp != NULL) { uint32_t need = com_pay_off + (uint32_t)read_size + 4U; if ((uint32_t)s16_ret < need) { EC200A_FTP_LOG("[FTP-UPG] QFTPGET(COM) frame len err off=%lu need=%lu got=%d\r\n", (unsigned long)com_pay_off, (unsigned long)need, (int)s16_ret); vPortFree(qfget_rsp); qfget_rsp = NULL; s16_ret = -1; } else { file_data = qfget_rsp + com_pay_off; file_len = (U32_T)read_size; } } } #endif EC200A_FTP_LOG("[FTP-UPG] QFTPGET(COM) pkt %u/%u off=%d len=%u ret=%d\r\n", (unsigned)t_ftp_update_info.u16_packIndex, (unsigned)t_ftp_update_info.u16_packNum, packAddr, (unsigned)read_size, (int)s16_ret); if (s16_ret < 0) { #if !EC200A_TCP_USE_TRANSPARENT_MODE if (qfget_rsp != NULL) { vPortFree(qfget_rsp); } #endif return -1; } #if EC200A_TCP_USE_TRANSPARENT_MODE file_data = parse_4g_ftp_response(rcvBuf, (uint32_t)s16_ret, &file_len); #else /* 非透传:已由 s16_qftpget_com_at_slice 定界,不再 parse(避免尾缀混入 QIRD 时误用「最后一个 \\r\\nOK」) */ #endif if (file_data == NULL) { #if !EC200A_TCP_USE_TRANSPARENT_MODE if (qfget_rsp != NULL) { vPortFree(qfget_rsp); } #endif EC200A_FTP_LOG("[FTP-UPG] QFTPGET(COM) parse fail\r\n"); #if BS_OCPP_EN v_ocpp_set_fw_notify_pending((U8_T)OCPP_FW_NOTIFY_PENDING_DOWNLOAD_FAILED); #endif s_bs_task_ctrl.s_bs_ctrl[0].u8_updata_flag = 0U; s_bs_task_ctrl.s_bs_ctrl[1].u8_updata_flag = 0U; v_ftp_ctrl_init(); return -1; } v_qftpget_log_pkt_data_edges(file_data, (uint32_t)file_len, (uint32_t)t_ftp_update_info.u16_packIndex, packAddr); { uint32_t flash_base = (uint32_t)t_ftp_update_info.u16_packIndex * ((uint32_t)t_ftp_update_info.u16_packSize / (uint32_t)PACKET_SIZE); uint32_t o = 0U; while (o < file_len) { uint32_t sub = file_len - o; if (sub > (uint32_t)PACKET_SIZE) { sub = (uint32_t)PACKET_SIZE; } { int flash_write = upgrade_write_packet( flash_base + (uint32_t)(o / (uint32_t)PACKET_SIZE), file_data + o, sub); if (flash_write != 0) { EC200A_FTP_LOG("[FTP-UPG] flash wr fail sub_off=%lu ret=%d\r\n", (unsigned long)o, flash_write); #if BS_OCPP_EN v_ocpp_set_fw_notify_pending((U8_T)OCPP_FW_NOTIFY_PENDING_INSTALLATION_FAILED); #endif s_bs_task_ctrl.s_bs_ctrl[0].u8_updata_flag = 0U; s_bs_task_ctrl.s_bs_ctrl[1].u8_updata_flag = 0U; v_ftp_ctrl_init(); #if !EC200A_TCP_USE_TRANSPARENT_MODE if (qfget_rsp != NULL) { vPortFree(qfget_rsp); } #endif return -1; } } o += sub; } } #if !EC200A_TCP_USE_TRANSPARENT_MODE if (qfget_rsp != NULL) { vPortFree(qfget_rsp); } #endif t_ftp_update_info.u16_packIndex++; if (t_ftp_update_info.u16_packIndex >= t_ftp_update_info.u16_packNum) { return 1; } return 0; } /** * ************************************************************************ * @brief - 列出 UFS 中所有文件。 * * * @return S8_T - * * @author jiankalka * @date 2025-12-04 * ************************************************************************ */ static S8_T s8_ec200a_qflst() { U8_T u8_sendBuf[] = "AT+QFLST=\"*\"\r\n"; //列出 UFS 中所有文件。 S8_T s8_ret; int size; char fileName[NUM_64] = {0}; U8_T u8_ack[] = "+QFLST:"; U8_T rcvBuf[BS_RECV_BUF_LEN] = {0}; s8_ret = s8_4g_Send_Recv_Cmd(u8_sendBuf, sizeof(u8_sendBuf) - 1,rcvBuf,BS_RECV_BUF_LEN, u8_ack, 1, 1000*6); if(s8_ret >= 0) { /* +QFLST: "UFS:CCU601E.bin",584072 OK */ if (sscanf((char *)&rcvBuf[s8_ret], "+QFLST: \"UFS:%[^\"]\",%d", fileName, &size) == 2) { if(/*size == t_ftp_update_info.u32_file_size &&*/ strstr((char *)t_ftp_update_info.u8_file_name,fileName)) { EC200A_FTP_LOG("FTP file ok(%s)[%d], start\r\n",fileName,size); t_ftp_update_info.u16_packIndex = 0; //清空当前索引号 t_ftp_update_info.u32_file_size = (uint32_t)size; // UFS 文件大小 { uint32_t chunk = (uint32_t)EC200A_FTP_UFS_RW_CHUNK_BYTES; uint32_t fsz = (uint32_t)size; t_ftp_update_info.u16_packSize = (U16_T)chunk; t_ftp_update_info.u16_packNum = (U16_T)((fsz + chunk - 1U) / chunk); } EC200A_FTP_LOG("%s file size= (%d) ,packSize= (%u) ,packNum= (%d) \r\n", t_ftp_update_info.u8_file_name, size, (unsigned)EC200A_FTP_UFS_RW_CHUNK_BYTES, t_ftp_update_info.u16_packNum); if(t_ftp_update_info.u16_packNum == 0) //异常处理 s8_ret = -1; } else { EC200A_FTP_LOG("文件获取失败(%s)[%d] ,(%s)[%d] \r\n",fileName,size, t_ftp_update_info.u8_file_name,t_ftp_update_info.u32_file_size); s8_ret = -1; } } else s8_ret = -1; } return s8_ret; } /** * ************************************************************************ * @brief - 打开文件获取文件指针 * * * @return S8_T - * * @author jiankalka * @date 2025-12-04 * ************************************************************************ */ static S8_T s8_ec200a_qfopen() { U8_T u8_sendBuf[64] = {0}; U8_T rcvBuf[BS_RECV_BUF_LEN] = {0}; S8_T s8_ret; U8_T u8_ack[] = "+QFOPEN:"; //AT+QFOPEN="test.txt",2 //打开文件获取文件指针。 2:若文件存在,则作为只读文件直接打开;若文件不存在,则响应错误 snprintf((char *)u8_sendBuf,64,"AT+QFOPEN=\"%s\",2\r\n", t_ftp_update_info.u8_file_name); s8_ret = s8_4g_Send_Recv_Cmd(u8_sendBuf, sizeof(u8_sendBuf) - 1,rcvBuf,BS_RECV_BUF_LEN, u8_ack, 5, 200); if(s8_ret >= 0) { if (sscanf((char *)&rcvBuf[s8_ret], "+QFOPEN: %d", &t_ftp_update_info.u32_file_fd) == 1) { EC200A_FTP_LOG("FTP open ok[%d], start\r\n",t_ftp_update_info.u32_file_fd); v_upgrade_header_data(0); //升级前更新头部信息 } else s8_ret = -1; } return s8_ret; } /** * ************************************************************************ * @brief -设置文件指针为文件的初始位置。 * * * @return S8_T - * * @author jiankalka * @date 2025-12-04 * ************************************************************************ */ static S8_T s8_ec200a_qfseek() { U8_T u8_sendBuf[64] = {0}; //AT+QFSEEK=1,0,0 //设置文件指针为文件的初始位置。 snprintf((char *)u8_sendBuf,64,"AT+QFSEEK=%d,0,0\r\n", t_ftp_update_info.u32_file_fd); return s8_4g_Send_AT_Cmd(u8_sendBuf, sizeof(u8_sendBuf) - 1, NULL, 5, 100); } /** * ************************************************************************ * @brief - 读取文件数据 * * * @return S8_T - 0 尚有分包待读 1 已全部读完 -1 失败 * * @author jiankalka * @date 2025-12-04 * ************************************************************************ */ static S8_T s8_ec200a_qfread() { U8_T u8_sendBuf[64] = {0}; S16_T s16_ret; U32_T file_len; // 文件数据长度 const U8_T *file_data = NULL; // 文件数据起始地址 int sn; #if EC200A_TCP_USE_TRANSPARENT_MODE U8_T rcvBuf[FTP_REAC_DATA_BUF_LEN] = {0}; #else U8_T *qfread_rsp = NULL; #endif int packAddr = t_ftp_update_info.u16_packIndex*t_ftp_update_info.u16_packSize; //获取包地址 uint32_t done = (uint32_t)t_ftp_update_info.u16_packIndex * (uint32_t)t_ftp_update_info.u16_packSize; uint32_t f_sz = t_ftp_update_info.u32_file_size; uint32_t rem = (f_sz > done) ? (f_sz - done) : 0U; U16_T read_size = t_ftp_update_info.u16_packSize; if (rem == 0U) { return 1; } if (rem < (uint32_t)read_size) { read_size = (U16_T)rem; } snprintf((char *)u8_sendBuf, sizeof(u8_sendBuf), "AT+QFREAD=%d,%d\r\n", t_ftp_update_info.u32_file_fd ,read_size); sn = (int)strlen((char *)u8_sendBuf); if (sn <= 0 || sn >= (int)sizeof(u8_sendBuf)) { return -1; } #if EC200A_TCP_USE_TRANSPARENT_MODE s16_ret = s16_4g_Send_Recv_Ftp_Get(u8_sendBuf, (U16_T)sn, rcvBuf, FTP_REAC_DATA_BUF_LEN, 6, 200); #else s16_ret = s16_qfread_at_slice(u8_sendBuf, (U16_T)sn, &qfread_rsp); #endif EC200A_FTP_LOG("RX pkt(%d)/(%d), prog %02d%% [%d](%d)\r\n",t_ftp_update_info.u16_packIndex,t_ftp_update_info.u16_packNum, (packAddr*100/t_ftp_update_info.u32_file_size),packAddr,t_ftp_update_info.u32_file_size); if(s16_ret >= 0) { #if EC200A_TCP_USE_TRANSPARENT_MODE file_data = parse_4g_ftp_response(rcvBuf, (uint32_t)s16_ret, &file_len); #else file_data = parse_4g_ftp_response(qfread_rsp, (uint32_t)s16_ret, &file_len); #endif if (file_data != NULL) { EC200A_FTP_LOG("File data ok, len=%uB\r\n", file_len); { uint32_t flash_base = (uint32_t)t_ftp_update_info.u16_packIndex * ((uint32_t)t_ftp_update_info.u16_packSize / (uint32_t)PACKET_SIZE); uint32_t o = 0U; int flash_write = 0; while (o < file_len) { uint32_t sub = file_len - o; if (sub > (uint32_t)PACKET_SIZE) { sub = (uint32_t)PACKET_SIZE; } flash_write = upgrade_write_packet( flash_base + (uint32_t)(o / (uint32_t)PACKET_SIZE), file_data + o, sub); if (flash_write != 0) { EC200A_FTP_LOG("flash wr fail sub_off=%lu ret=%d\r\n", (unsigned long)o, flash_write); #if !EC200A_TCP_USE_TRANSPARENT_MODE if (qfread_rsp != NULL) { vPortFree(qfread_rsp); qfread_rsp = NULL; } #endif return -1; } o += sub; } EC200A_FTP_LOG("flash wr:%d (-1F 0OK) data[%02x %02x %02x %02x %02x %02x %02x %02x]\r\n", flash_write , file_data[0],file_data[1],file_data[2],file_data[3], file_data[4],file_data[5],file_data[6],file_data[7]); } t_ftp_update_info.u16_packIndex ++; } else { EC200A_FTP_LOG("FTP rsp parse fail\r\n"); } //退出条件判断 if(t_ftp_update_info.u16_packIndex >= t_ftp_update_info.u16_packNum) // 接收包足够 则退出 { s16_ret = 1; } else s16_ret = 0; } #if !EC200A_TCP_USE_TRANSPARENT_MODE if (qfread_rsp != NULL) { vPortFree(qfread_rsp); } #endif return (S8_T)s16_ret; } static S8_T s8_get_log_info() { fatfs_set_write_disable_flag(FATFS_WRITE_DISABLE_TCP_DEBUG, 1); /* v_app_fatfs_log_real_file_name -- CCU601E_D, disabled */ EC200A_FTP_LOG("LOG file = %s\r\n", t_ftp_update_info.u8_file_name); /* 检查文件是否存在 */ bool file_exists = false; FS_Status fs_status = fs_file_exists((char *)t_ftp_update_info.u8_file_name, &file_exists); if (fs_status != FS_STATUS_OK) { EC200A_FTP_LOG("[FileCtrl] Error checking file existence: %s, status=%d\r\n", t_ftp_update_info.u8_file_name, fs_status); /* 恢复文件写入禁用标志(清除TCP调试功能标志位) */ fatfs_set_write_disable_flag(FATFS_WRITE_DISABLE_TCP_DEBUG, 0); return -1; } if (!file_exists) { EC200A_FTP_LOG("[FileCtrl] File not found: %s\r\n", t_ftp_update_info.u8_file_name); /* 恢复文件写入禁用标志(清除TCP调试功能标志位) */ fatfs_set_write_disable_flag(FATFS_WRITE_DISABLE_TCP_DEBUG, 0); return -1; } /* 获取文件大小 */ uint32_t file_size = 0; fs_status = fs_get_file_size((char *)t_ftp_update_info.u8_file_name, &file_size); if (fs_status != FS_STATUS_OK) { EC200A_FTP_LOG("[FileCtrl] Failed to get file size: %s, status=%d\r\n", t_ftp_update_info.u8_file_name, fs_status); /* 恢复文件写入禁用标志(清除TCP调试功能标志位) */ fatfs_set_write_disable_flag(FATFS_WRITE_DISABLE_TCP_DEBUG, 0); return -1; } t_ftp_update_info.u32_file_size = file_size; if (EC200A_FTP_UFS_RW_CHUNK_BYTES > 65535U) { EC200A_FTP_LOG("[FileCtrl] EC200A_FTP_UFS_RW_CHUNK_BYTES too large for u16_packSize\r\n"); fatfs_set_write_disable_flag(FATFS_WRITE_DISABLE_TCP_DEBUG, 0); return -1; } t_ftp_update_info.u16_packSize = (U16_T)EC200A_FTP_UFS_RW_CHUNK_BYTES; /* 向上取整包数(与 u16_packSize 一致,便于 QFSEEK/QFWRITE 分包) */ t_ftp_update_info.u16_packNum = (U16_T)((file_size + (uint32_t)t_ftp_update_info.u16_packSize - 1U) / (uint32_t)t_ftp_update_info.u16_packSize); return 1; } #if !EC200A_TCP_USE_TRANSPARENT_MODE /** 当前 FAT 块 QFWRITE 完成后是否为日志文件末尾 */ static uint8_t s_log_tx_chunk_is_last; static S8_T s8_ec200a_write_log_data(void) { uint32_t chunk_size = t_ftp_update_info.u16_packSize; uint32_t offset = t_ftp_update_info.u16_packIndex * chunk_size; uint32_t file_size = t_ftp_update_info.u32_file_size; uint32_t remaining = file_size - offset; uint32_t read_size = (remaining < chunk_size) ? remaining : chunk_size; uint8_t is_last_chunk = (offset + read_size >= file_size) ? 1 : 0; uint8_t *read_buffer = NULL; char *text_data = NULL; uint32_t filtered_size = 0; uint32_t dest_index = 0; uint32_t i; S8_T wr; read_buffer = (uint8_t *)pvPortMalloc(read_size); if (read_buffer == NULL) { EC200A_FTP_LOG("[FileCtrl] Failed to allocate read buffer (%u bytes)\r\n", read_size); fatfs_set_write_disable_flag(FATFS_WRITE_DISABLE_TCP_DEBUG, 0); return -1; } { FS_Status fs_status; FS_ReadRange range = { .start_pos = offset, .end_pos = offset + read_size, .actual_read = 0 }; fs_status = fs_read_file_content((char *)t_ftp_update_info.u8_file_name, (char *)read_buffer, read_size, &range); if (fs_status != FS_STATUS_OK) { EC200A_FTP_LOG("[FileCtrl] Failed to read file content: %s, status=%d (offset=%u, size=%u)\r\n", (char *)t_ftp_update_info.u8_file_name, fs_status, offset, read_size); vPortFree(read_buffer); fatfs_set_write_disable_flag(FATFS_WRITE_DISABLE_TCP_DEBUG, 0); return -1; } if (range.actual_read == 0) { EC200A_FTP_LOG("[FileCtrl] Read failed: expected %u, got 0\r\n", read_size); vPortFree(read_buffer); fatfs_set_write_disable_flag(FATFS_WRITE_DISABLE_TCP_DEBUG, 0); return -1; } read_size = range.actual_read; is_last_chunk = (offset + read_size >= file_size - 20U) ? 1 : 0; } for (i = 0; i < read_size; i++) { if (read_buffer[i] != '\0') { filtered_size++; } } text_data = (char *)pvPortMalloc(filtered_size + 1U); if (text_data == NULL) { EC200A_FTP_LOG("[FileCtrl] Failed to allocate text buffer (%u bytes)\r\n", filtered_size + 1U); vPortFree(read_buffer); fatfs_set_write_disable_flag(FATFS_WRITE_DISABLE_TCP_DEBUG, 0); return -1; } dest_index = 0; for (i = 0; i < read_size; i++) { if (read_buffer[i] != '\0') { text_data[dest_index++] = read_buffer[i]; } } text_data[filtered_size] = '\0'; EC200A_FTP_LOG(" Progress: %.1f%% (%u/%u bytes)\r\n", (float)(offset + read_size) / (float)file_size * 100.0f, offset + read_size, file_size); vPortFree(read_buffer); read_buffer = NULL; if (filtered_size == 0U) { vPortFree(text_data); if (is_last_chunk != 0U) { s8_ec200a_qfclose_log_fd(); fatfs_set_write_disable_flag(FATFS_WRITE_DISABLE_TCP_DEBUG, 0); EC200A_FTP_LOG("[FileCtrl] File write disabled flag (TCP debug) disabled after final chunk download operation\r\n"); return 1; } EC200A_FTP_LOG("[FileCtrl] File write disabled flag (TCP debug) remains enabled for next chunk download\r\n"); t_ftp_update_info.u16_packIndex++; return 0; } if (s8_ec200a_qfseek_log_abs(s_log_ufs_qwrite_total) < 0) { EC200A_FTP_LOG("[FTP-LOG] QFSEEK fail off=%lu\r\n", (unsigned long)s_log_ufs_qwrite_total); s8_ec200a_qfclose_log_fd(); vPortFree(text_data); fatfs_set_write_disable_flag(FATFS_WRITE_DISABLE_TCP_DEBUG, 0); return -1; } s_log_tx_chunk_is_last = is_last_chunk; wr = s8_ec200a_qfwrite_log_bin((const U8_T *)text_data, (U16_T)filtered_size); vPortFree(text_data); if (wr < 0) { EC200A_FTP_LOG("[FTP-LOG] QFWRITE fail len=%u off=%lu\r\n", (unsigned)filtered_size, (unsigned long)s_log_ufs_qwrite_total); s8_ec200a_qfclose_log_fd(); fatfs_set_write_disable_flag(FATFS_WRITE_DISABLE_TCP_DEBUG, 0); return -1; } s_log_ufs_qwrite_total += filtered_size; if (is_last_chunk != 0U) { s8_ec200a_qfclose_log_fd(); fatfs_set_write_disable_flag(FATFS_WRITE_DISABLE_TCP_DEBUG, 0); EC200A_FTP_LOG("[FileCtrl] File write disabled flag (TCP debug) disabled after final chunk download operation\r\n"); return 1; } EC200A_FTP_LOG("[FileCtrl] File write disabled flag (TCP debug) remains enabled for next chunk download\r\n"); t_ftp_update_info.u16_packIndex++; return 0; } #else static S8_T s8_ec200a_write_log_data(void) { return -1; } #endif /* !EC200A_TCP_USE_TRANSPARENT_MODE */ /** * ************************************************************************ * @brief - 上传文件至FTP服务器 * * * @return S8_T - -1 获取失败 0获取中 1获取完成 * * @author jiankalka * @date 2025-11-29 * ************************************************************************ */ static S8_T s8_ec200a_qftpclose(void) { const U8_T u8_sendBuf[] = "AT+QFTPCLOSE\r\n"; return s8_4g_Send_AT_Cmd((U8_T *)(void *)u8_sendBuf, (U16_T)(sizeof(u8_sendBuf) - 1U), NULL, 5U, 400U); } static S8_T s8_ec200a_qftpput() { U8_T u8_sendBuf[128] = {0}; char *last_slash = strrchr((char *)t_ftp_update_info.u8_file_name, '/'); //带路径的 提取出文件名称 #if 1 //使用UFS接收文件 //AT+QFTPGET="test_my1.txt","UFS:test.txt" //下载文件并将其保存至 UFS,命名为 test.txt。 snprintf((char *)u8_sendBuf,sizeof(u8_sendBuf) - 1,"AT+QFTPPUT=\"%s\",\"UFS:%s\",0\r\n", last_slash+1 ,last_slash+1); return s8_4g_Send_AT_Cmd(u8_sendBuf, sizeof(u8_sendBuf) - 1, NULL, 10, 500); #endif } #endif U8_T u8_ec200a_4g_ftp_update() { S8_T s8_at_res; static U8_T u8_out_cnt = 0; static E_EC200A_4G_FTP_INIT_STEP s_last_step = (E_EC200A_4G_FTP_INIT_STEP)0xFF; // static U16_T u16_cnt = 0; // if(u16_cnt++ < 10) // return 0; // u16_cnt = 0; if (s_last_step != e_ec200a_ftp_init_step) { EC200A_FTP_LOG("[FTP-UPG] step=%d\r\n", (int)e_ec200a_ftp_init_step); s_last_step = e_ec200a_ftp_init_step; } switch (e_ec200a_ftp_init_step) { case STEP_FTP_SWCHTO_CMD_MODE: { s8_at_res = s8_work_mode_cmd(); EC200A_FTP_LOG("[FTP-UPG] step=%d ret=%d\r\n", (int)e_ec200a_ftp_init_step, s8_at_res); if(s8_at_res >= 0){ e_ec200a_ftp_init_step = STEP_FTP_SET_CGREG; //状态跳转 u8_out_cnt = 0; //超时计数清空 } break; } case STEP_FTP_SET_CGREG: //设置PS域 网络注册状态 { s8_at_res = s8_ec200a_set_cgreg(); // EC200A_FTP_LOG("[FTP-UPG] step=%d ret=%d\r\n", (int)e_ec200a_ftp_init_step, s8_at_res); if(s8_at_res >= 0){ e_ec200a_ftp_init_step = STEP_FTP_QUERY_CGREG; //状态跳转 u8_out_cnt = 0; //超时计数清空 } break; } case STEP_FTP_QUERY_CGREG: //查询PS域 网络注册状态 { s8_at_res = s8_ec200a_cgreg(); //查询PS域 网络注册状态 EC200A_FTP_LOG("[FTP-UPG] step=%d ret=%d\r\n", (int)e_ec200a_ftp_init_step, s8_at_res); if(s8_at_res >= 0){ e_ec200a_ftp_init_step = STEP_FTP_SET_QIDEACT; //状态跳转 u8_out_cnt = 0; //超时计数清空 } break; } case STEP_FTP_SET_QIDEACT: //去激活 FTP 专用 PDP(不碰业务 TCP 场景) { s8_at_res = s8_ec200a_qideact_ctx((U8_T)EC200A_FTP_PDP_CTX_ID); EC200A_FTP_LOG("[FTP-UPG] step=%d ret=%d\r\n", (int)e_ec200a_ftp_init_step, s8_at_res); if(s8_at_res >= 0){ e_ec200a_ftp_init_step = STEP_FTP_SET_QIACT; //状态跳转 u8_out_cnt = 0; //超时计数清空 } break; } case STEP_FTP_SET_QIACT: //配置并激活 FTP 专用 PDP { s8_at_res = s8_ec200a_qicsgp_ctx((U8_T)EC200A_FTP_PDP_CTX_ID); if (s8_at_res >= 0) { s8_at_res = s8_ec200a_qiact_ctx((U8_T)EC200A_FTP_PDP_CTX_ID); } EC200A_FTP_LOG("[FTP-UPG] step=%d ret=%d\r\n", (int)e_ec200a_ftp_init_step, s8_at_res); if(s8_at_res >= 0){ e_ec200a_ftp_init_step = STEP_FTP_FTP_CFG_contextid; //状态跳转 u8_out_cnt = 0; //超时计数清空 } break; } case STEP_FTP_FTP_CFG_contextid: //配置 FTP(S) PDP 上下文 ID { s8_at_res = s8_ec200a_qftpcfg_contextid(); EC200A_FTP_LOG("[FTP-UPG] step=%d ret=%d\r\n", (int)e_ec200a_ftp_init_step, s8_at_res); if(s8_at_res >= 0){ e_ec200a_ftp_init_step = STEP_FTP_FTP_CFG_account; //状态跳转 u8_out_cnt = 0; //超时计数清空 } break; } case STEP_FTP_FTP_CFG_account: //配置 FTP(S) 设置用户名和密码。 { s8_at_res = s8_ec200a_qftpcfg_account(); EC200A_FTP_LOG("[FTP-UPG] step=%d ret=%d (user=%s)\r\n", (int)e_ec200a_ftp_init_step, s8_at_res, t_ftp_update_info.u8_user); if(s8_at_res >= 0){ e_ec200a_ftp_init_step = STEP_FTP_FTP_CFG_filetype; //状态跳转 u8_out_cnt = 0; //超时计数清空 } break; } case STEP_FTP_FTP_CFG_filetype: //配置 FTP(S) 设置文件类型为ASCII。 { s8_at_res = s8_ec200a_qftpcfg_filetype(); EC200A_FTP_LOG("[FTP-UPG] step=%d ret=%d\r\n", (int)e_ec200a_ftp_init_step, s8_at_res); if(s8_at_res >= 0){ e_ec200a_ftp_init_step = STEP_FTP_FTP_CFG_transmode; //状态跳转 u8_out_cnt = 0; //超时计数清空 } break; } case STEP_FTP_FTP_CFG_transmode: //配置 FTP(S) 设置为被动传输方式。 { s8_at_res = s8_ec200a_qftpcfg_transmode(); EC200A_FTP_LOG("[FTP-UPG] step=%d ret=%d\r\n", (int)e_ec200a_ftp_init_step, s8_at_res); if(s8_at_res >= 0){ e_ec200a_ftp_init_step = STEP_FTP_FTP_CFG_rsptimeout; //状态跳转 u8_out_cnt = 0; //超时计数清空 } break; } case STEP_FTP_FTP_CFG_rsptimeout: //配置 FTP(S) 设置最大响应时间(默认为90秒 { s8_at_res = s8_ec200a_qftpcfg_rsptimeout(); EC200A_FTP_LOG("[FTP-UPG] step=%d ret=%d\r\n", (int)e_ec200a_ftp_init_step, s8_at_res); if(s8_at_res >= 0){ e_ec200a_ftp_init_step = STEP_FTP_FTP_CFG_QFTPOPEN; //状态跳转 u8_out_cnt = 0; //超时计数清空 } break; } case STEP_FTP_FTP_CFG_QFTPOPEN: //登录FTP服务器。 { s8_at_res = s8_ec200a_qftpopen(); EC200A_FTP_LOG("[FTP-UPG] step=%d ret=%d (host=%s:%d)\r\n", (int)e_ec200a_ftp_init_step, s8_at_res, t_ftp_update_info.u8_update_server_addr, t_ftp_update_info.u16_update_server_port); if(s8_at_res >= 0){ e_ec200a_ftp_init_step = STEP_FTP_FTP_CFG_QFTPCWD; //状态跳转 u8_out_cnt = 0; //超时计数清空 } break; } case STEP_FTP_FTP_CFG_QFTPCWD: //切换目录 { s8_at_res = s8_ec200a_qftpcwd(); EC200A_FTP_LOG("[FTP-UPG] step=%d ret=%d (path=%s)\r\n", (int)e_ec200a_ftp_init_step, s8_at_res, t_ftp_update_info.u8_path); if(s8_at_res >= 0){ e_ec200a_ftp_init_step = STEP_FTP_FTP_GET_QFTPSIZE; //状态跳转 u8_out_cnt = 0; //超时计数清空 } break; } case STEP_FTP_FTP_CFG_QFTPLIST: //获取当前目录的内容 -- 不使用 { s8_at_res = s8_ec200a_qftplist(); if(s8_at_res >= 0){ e_ec200a_ftp_init_step = STEP_FTP_SAVE; //状态跳转 u8_out_cnt = 0; //超时计数清空 } break; } case STEP_FTP_FTP_GET_QFTPSIZE: //获取 文件名称 { s8_at_res = s8_ec200a_qftpsize(); EC200A_FTP_LOG("[FTP-UPG] step=%d ret=%d (file=%s size=%lu)\r\n", (int)e_ec200a_ftp_init_step, s8_at_res, t_ftp_update_info.u8_file_name, (unsigned long)t_ftp_update_info.u32_file_size); if(s8_at_res >= 0){ { uint32_t chunk = (uint32_t)EC200A_FTP_UFS_RW_CHUNK_BYTES; uint32_t fsz = t_ftp_update_info.u32_file_size; t_ftp_update_info.u16_packSize = (U16_T)chunk; t_ftp_update_info.u16_packNum = (U16_T)((fsz + chunk - 1U) / chunk); t_ftp_update_info.u16_packIndex = 0; if (t_ftp_update_info.u16_packNum == 0U) { EC200A_FTP_LOG("[FTP-UPG] packNum=0, abort\r\n"); break; } EC200A_FTP_LOG("[FTP-UPG] COM chunk packSize=%u packNum=%u\r\n", (unsigned)t_ftp_update_info.u16_packSize, (unsigned)t_ftp_update_info.u16_packNum); } e_ec200a_ftp_init_step = STEP_FTP_FTP_QFDEL; //状态跳转 u8_out_cnt = 0; //超时计数清空 } break; } case STEP_FTP_FTP_QFDEL: //获取 删除存储区保存的文件内容 { s8_at_res = s8_ec200a_qfdel(); if(s8_at_res >= 0){ e_ec200a_ftp_init_step = STEP_FTP_FTP_GET_QFTPGET; //状态跳转 u8_out_cnt = 0; //超时计数清空 } break; } case STEP_FTP_FTP_GET_QFTPGET: //获取 文件内容 { s8_at_res = s8_ec200a_qftpget(); //返回值 等于1时 成功 EC200A_FTP_LOG("[FTP-UPG] step=%d ret=%d\r\n", (int)e_ec200a_ftp_init_step, s8_at_res); // 约定:-1 失败;0 正在处理/需要重试;1 完成成功 /* COM 分片已直接写入 Flash,跳过 QFLST/QFOPEN/QFSEEK/QFREAD(UFS) */ if(s8_at_res == 1){ #if BS_OCPP_EN v_ocpp_set_fw_notify_pending((U8_T)OCPP_FW_NOTIFY_PENDING_DOWNLOADED); #endif e_ec200a_ftp_init_step = STEP_FTP_SAVE; //状态跳转 u8_out_cnt = 0; //超时计数清空 } break; } case STEP_FTP_FTP_GET_QFLST: //获取 UFS中文件内容 qflst { s8_at_res = s8_ec200a_qflst(); //返回值 等于1时 成功 EC200A_FTP_LOG("[FTP-UPG] step=%d ret=%d\r\n", (int)e_ec200a_ftp_init_step, s8_at_res); if(s8_at_res >= 0){ e_ec200a_ftp_init_step = STEP_FTP_FTP_GET_QFOPEN; //状态跳转 u8_out_cnt = 0; //超时计数清空 } break; } case STEP_FTP_FTP_GET_QFOPEN: //打开文件 { s8_at_res = s8_ec200a_qfopen(); //返回值 等于1时 成功 if(s8_at_res >= 0){ e_ec200a_ftp_init_step = STEP_FTP_FTP_GET_QFSEEK; //状态跳转 u8_out_cnt = 0; //超时计数清空 } break; } case STEP_FTP_FTP_GET_QFSEEK: //设置读取文件指针头地址 { s8_at_res = s8_ec200a_qfseek(); //返回值 等于1时 成功 if(s8_at_res >= 0){ e_ec200a_ftp_init_step = STEP_FTP_FTP_GET_QFREAD; //状态跳转 u8_out_cnt = 0; //超时计数清空 } break; } case STEP_FTP_FTP_GET_QFREAD: //读取文件 { s8_at_res = s8_ec200a_qfread(); //返回值 等于1时 成功 if (s8_at_res == 1 || s8_at_res == 0) { u8_out_cnt = 0; //超时计数清空 if(s8_at_res == 1){ e_ec200a_ftp_init_step = STEP_FTP_SAVE; //状态跳转 } } break; } case STEP_FTP_SAVE: //保存当前设置并重启 case STEP_FTP_INIT_OK: //设置完成 { #if BS_OCPP_EN if (s_u8_fw_save_ocpp_arm == 0U) { /* 仅置 pending;实际上送在 Plat_Comm 联网半周期的 ocpp_send_mag_ctrl */ v_ocpp_set_fw_notify_pending((U8_T)OCPP_FW_NOTIFY_PENDING_INSTALLING); s_u8_fw_save_ocpp_arm = 1U; break; } if (u8_ocpp_fw_installing_fsn_sent() == 0U) { /* 须等 FSN(Installing) ws_send 成功;勿在未上送时写头/复位 */ break; } #endif v_upgrade_header_data(1); //升级后更新头部信息 #if BS_OCPP_EN v_ocpp_fw_eeprom_set_installed_after_boot(); s_u8_fw_save_ocpp_arm = 0U; v_ocpp_clr_fw_installing_fsn_sent(); v_sys_reboot(); /* Installing 已上送后再复位;Installed 在 BootNotification 后上送 */ #else v_sys_reboot(); //系统重启 默认此时未充电 #endif break; } case STEP_FTP_INIT_DEBUG: // 调试 { break; } default: break; } u8_out_cnt++ ; if(e_ec200a_ftp_init_step == STEP_FTP_QUERY_CGREG) //查询PS域 网络注册状态 { if(u8_out_cnt > 120){ e_ec200a_ftp_init_step = STEP_FTP_SET_QIDEACT; //强制跳转 u8_out_cnt = 0; } } else if(u8_out_cnt > 60*3) //停留在其中一个状态 大于3分钟 { #if BS_OCPP_EN if (s_bs_task_ctrl.ocpp_data.u8_log_file_upload) { v_ocpp_set_diag_upload_notify_pending(OCPP_DIAG_NOTIFY_PENDING_UPLOAD_FAILED); } else { v_ocpp_set_fw_notify_pending((U8_T)OCPP_FW_NOTIFY_PENDING_DOWNLOAD_FAILED); } #endif s_bs_task_ctrl.s_bs_ctrl[0].u8_updata_flag = 0; //停止 s_bs_task_ctrl.s_bs_ctrl[1].u8_updata_flag = 0; //停止 u8_out_cnt = 0; EC200A_FTP_LOG("FTP TO, exit upg\r\n"); } return 0; } U8_T u8_ec200a_4g_ftp_log_upload() { S8_T s8_at_res; static U8_T u8_out_cnt = 0; // static U16_T u16_cnt = 0; // if(u16_cnt++ < 10) // return 0; // u16_cnt = 0; EC200A_FTP_LOG("ftp test %d\r\n",e_ec200a_ftp_init_step); switch (e_ec200a_ftp_init_step) { case STEP_FTP_SWCHTO_CMD_MODE: { s8_at_res = s8_work_mode_cmd(); if(s8_at_res >= 0){ e_ec200a_ftp_init_step = STEP_FTP_FTP_QFDEL; //状态跳转 u8_out_cnt = 0; //超时计数清空 } break; } case STEP_FTP_FTP_QFDEL: //获取 删除存储区保存的文件内容 { s8_at_res = s8_ec200a_qfdel();// if(s8_at_res >= 0){ e_ec200a_ftp_init_step = STEP_FTP_FTP_GET_LOG_INFO; //状态跳转 u8_out_cnt = 0; //超时计数清空 } break; } case STEP_FTP_FTP_GET_LOG_INFO: { s8_at_res = s8_get_log_info(); if(s8_at_res >= 0){ e_ec200a_ftp_init_step = STEP_FTP_FTP_UPLOAD_LOG_UFS; //状态跳转 u8_out_cnt = 0; //超时计数清空 } break; } case STEP_FTP_FTP_UPLOAD_LOG_UFS: { #if !EC200A_TCP_USE_TRANSPARENT_MODE s8_at_res = s8_ec200a_qfopen_log_ufs(); #else s8_at_res = -1; #endif if(s8_at_res >= 0){ e_ec200a_ftp_init_step = STEP_FTP_FTP_WRITE_LOG_UFS; //状态跳转 u8_out_cnt = 0; //超时计数清空 } break; } case STEP_FTP_FTP_WRITE_LOG_UFS: //写入日志文件到 UFS { #if !EC200A_TCP_USE_TRANSPARENT_MODE s8_at_res = s8_ec200a_write_log_data(); //返回值 等于1时 成功 #else s8_at_res = -1; #endif // 约定:-1 失败;0 进行中;1 完成 if(s8_at_res == 0){ // 有进度则刷新超时计数,避免大文件上传过程中误判超时 u8_out_cnt = 0; }else if(s8_at_res == 1){ e_ec200a_ftp_init_step = STEP_FTP_FTP_GET_QFLST; //状态跳转 u8_out_cnt = 0; //超时计数清空 } break; } case STEP_FTP_FTP_GET_QFLST: //获取 UFS中文件内容 qflst { s8_at_res = s8_ec200a_qflst(); //返回值 等于1时 成功 if(s8_at_res >= 0){ e_ec200a_ftp_init_step = STEP_FTP_QUERY_CGREG; //状态跳转 u8_out_cnt = 0; //超时计数清空 } break; } case STEP_FTP_QUERY_CGREG: //查询PS域 网络注册状态 { s8_at_res = s8_ec200a_cgreg(); //查询PS域 网络注册状态 if(s8_at_res >= 0){ e_ec200a_ftp_init_step = STEP_FTP_SET_QIDEACT; //状态跳转 u8_out_cnt = 0; //超时计数清空 } break; } case STEP_FTP_SET_QIDEACT: //去激活 FTP 专用 PDP { s8_at_res = s8_ec200a_qideact_ctx((U8_T)EC200A_FTP_PDP_CTX_ID); if(s8_at_res >= 0){ e_ec200a_ftp_init_step = STEP_FTP_SET_QIACT; //状态跳转 u8_out_cnt = 0; //超时计数清空 } break; } case STEP_FTP_SET_QIACT: //配置并激活 FTP 专用 PDP { s8_at_res = s8_ec200a_qicsgp_ctx((U8_T)EC200A_FTP_PDP_CTX_ID); if (s8_at_res >= 0) { s8_at_res = s8_ec200a_qiact_ctx((U8_T)EC200A_FTP_PDP_CTX_ID); } if(s8_at_res >= 0){ e_ec200a_ftp_init_step = STEP_FTP_FTP_CFG_contextid; //状态跳转 u8_out_cnt = 0; //超时计数清空 } break; } case STEP_FTP_FTP_CFG_contextid: //配置 FTP(S) PDP 上下文 ID { s8_at_res = s8_ec200a_qftpcfg_contextid(); if(s8_at_res >= 0){ e_ec200a_ftp_init_step = STEP_FTP_FTP_CFG_account; //状态跳转 u8_out_cnt = 0; //超时计数清空 } break; } case STEP_FTP_FTP_CFG_account: //配置 FTP(S) 设置用户名和密码。 { s8_at_res = s8_ec200a_qftpcfg_account(); if(s8_at_res >= 0){ e_ec200a_ftp_init_step = STEP_FTP_FTP_CFG_filetype; //状态跳转 u8_out_cnt = 0; //超时计数清空 } break; } case STEP_FTP_FTP_CFG_filetype: //配置 FTP(S) 设置文件类型为ASCII。 { s8_at_res = s8_ec200a_qftpcfg_filetype(); if(s8_at_res >= 0){ e_ec200a_ftp_init_step = STEP_FTP_FTP_CFG_transmode; //状态跳转 u8_out_cnt = 0; //超时计数清空 } break; } case STEP_FTP_FTP_CFG_transmode: //配置 FTP(S) 设置为被动传输方式。 { s8_at_res = s8_ec200a_qftpcfg_transmode(); if(s8_at_res >= 0){ e_ec200a_ftp_init_step = STEP_FTP_FTP_CFG_rsptimeout; //状态跳转 u8_out_cnt = 0; //超时计数清空 } break; } case STEP_FTP_FTP_CFG_rsptimeout: //配置 FTP(S) 设置最大响应时间(默认为90秒 { s8_at_res = s8_ec200a_qftpcfg_rsptimeout(); if(s8_at_res >= 0){ e_ec200a_ftp_init_step = STEP_FTP_FTP_CFG_QFTPOPEN; //状态跳转 u8_out_cnt = 0; //超时计数清空 } break; } case STEP_FTP_FTP_CFG_QFTPOPEN: //登录FTP服务器。 { s8_at_res = s8_ec200a_qftpopen(); if(s8_at_res >= 0){ e_ec200a_ftp_init_step = STEP_FTP_FTP_CFG_QFTPCWD; //状态跳转 u8_out_cnt = 0; //超时计数清空 } break; } case STEP_FTP_FTP_CFG_QFTPCWD: //切换目录 { s8_at_res = s8_ec200a_qftpcwd(); if(s8_at_res >= 0){ e_ec200a_ftp_init_step = STEP_FTP_FTP_UPLOAD_LOG_FTP; //状态跳转 u8_out_cnt = 0; //超时计数清空 } break; } case STEP_FTP_FTP_UPLOAD_LOG_FTP: //上传日志文件到 FT { s8_at_res = s8_ec200a_qftpput(); if(s8_at_res >= 0){ e_ec200a_ftp_init_step = STEP_FTP_FTP_QFTPCLOSE_AFTER_LOG; u8_out_cnt = 0; //超时计数清空 } break; } case STEP_FTP_FTP_QFTPCLOSE_AFTER_LOG: { static U8_T u8_qclose_fail_streak; s8_at_res = s8_ec200a_qftpclose(); if (s8_at_res >= 0) { u8_qclose_fail_streak = 0; e_ec200a_ftp_init_step = STEP_FTP_INIT_OK; u8_out_cnt = 0; } else if (++u8_qclose_fail_streak >= 4U) { u8_qclose_fail_streak = 0; EC200A_FTP_LOG("FTP QFTPCLOSE fail ret=%d, continue\r\n", (int)s8_at_res); e_ec200a_ftp_init_step = STEP_FTP_INIT_OK; u8_out_cnt = 0; } break; } case STEP_FTP_INIT_OK: case STEP_FTP_INIT_DEBUG: // 调试 { if(u8_out_cnt < 20){ //等待 ftp 服务器 处理文件 break; } #if BS_OCPP_EN v_ocpp_set_diag_upload_notify_pending(OCPP_DIAG_NOTIFY_PENDING_UPLOADED); #endif EC200A_FTP_LOG("FTP log upload done, keep OCPP (no relink)\r\n"); s_bs_task_ctrl.s_bs_ctrl[0].u8_updata_flag = 0; s_bs_task_ctrl.s_bs_ctrl[1].u8_updata_flag = 0; #if BS_OCPP_EN s_bs_task_ctrl.ocpp_data.u8_log_file_upload = 0; s_bs_task_ctrl.ocpp_data.ulong_timer = 0; #endif t_ftp_update_info.u8_init_flag = 0; v_ftp_ctrl_init(); u8_out_cnt = 0; break; } default: break; } u8_out_cnt++ ; if(e_ec200a_ftp_init_step == STEP_FTP_QUERY_CGREG) //查询PS域 网络注册状态 { if(u8_out_cnt > 120){ e_ec200a_ftp_init_step = STEP_FTP_SET_QIDEACT; //强制跳转 u8_out_cnt = 0; } } else if(u8_out_cnt > 60*3) //停留在其中一个状态 大于3分钟 { #if BS_OCPP_EN if (s_bs_task_ctrl.ocpp_data.u8_log_file_upload) { v_ocpp_set_diag_upload_notify_pending(OCPP_DIAG_NOTIFY_PENDING_UPLOAD_FAILED); } #endif s_bs_task_ctrl.s_bs_ctrl[0].u8_updata_flag = 0; //停止 s_bs_task_ctrl.s_bs_ctrl[1].u8_updata_flag = 0; //停止 u8_out_cnt = 0; EC200A_FTP_LOG("FTP TO, exit upg\r\n"); } return 0; } /* LTE Standard(A)系列模块支持 FTP(S)协议,因此 FTP(S)服务器上的文件和目录可以通过 FTP(S)相 关 AT 命令进行操作。FTP(S)相关 AT 命令使用流程如下: 第一步:配置和激活 PDP 上下文。 1. 使用 AT+QICSGP 配置 PDP 上下文的和其他参数,详细 信息可参考文档 [1]。可通过 AT+CGQMIN、AT+CGEQMIN、AT+CGQREQ 和 AT+CGEQREQ 更新 QoS 设置,详细信息可参考文档 [2]; 2. 通过 AT+QIACT 激活 PDP 上下文,详细信息可参考文档 [1]; 3. 通过 AT+QFTPCFG="contextid",配置 FTP(S) PDP 上下文 ID。需提前激活 PDP 上 下文。 第二步:配置用户账号和 FTP(S)服务器。 1. 通过 AT+QFTPCFG="account",,配置账号信息; 2. 通过 AT+QFTPCFG="filetype",配置文件类型。在 FTP(S)服务器和客户端间传输的 文件类型可以是二进制数据或 ASCII 数据; 3. 通过 AT+QFTPCFG="transmode",配置传输模式,即在 FTP(S)服务器端口或客户 端 端 口 监 听 数 据 连 接 。 请 注 意 , 因 为 FTPS 当 前 不 支 持 主 动 模 式 , 必 须 设 置 AT+QFTPCFG="transmode",1 后才能进行 FTPS 操作; 4. 通过 AT+QFTPCFG="rsptimeout",配置命令的最大响应时间; 5. 若模块作为 FTPS 客户端,需执行如下命令: 1) 执行 AT+QFTPCFG="ssltype",1; 2) 执行 AT+QFTPCFG="SSL_ctxID",设置 SSL 上下文 ID; 3) AT+QSSLCFG 配置所指定的,详细信息可参考文档 [3]。 第三步:登录 FTP(S)服务器。 通 过 AT+QFTPOPEN=, 登录 FTP(S) 服务器 ,命令执行后, 若 响 应 URC +QFTPOPEN: 0,0,表示操作成功。注意 FTPS 和 FTP 端口不同。FTPS 服务器的端口取决于 FTPS 服务器提供商,通常为 990。 第四步:文件操作。 1. 通过 AT+QFTPCWD 设置当前目录; 2. 上传文件至 FTP(S)服务器; 1) 通过 AT+QFUPL 可以将文件上传至 UFS 或 SD 卡,再通过 AT+QFTPPUT 将文件上传至 FTP(S)服务器。文件上传成功后,可通过 AT+QFDEL 将其删除。详细信息可参考文档 [4]; 2) 在 COM 口通过 AT+QFTPPUT 将文件上传至 FTP(S)服务器。执行+++可结束文件上传。 3. 通过 AT+QFTPGET 从 FTP(S)服务器下载文件。文件将在 COM 口输出或被保存至 UFS 或 SD 卡。若 COM 口输出文件,模块将进入数据模式; 4. 通过 AT+QFTPSIZE 获取 FTP(S)服务器文件大小; 5. 通过 AT+QFTPLEN 获取 FTP(S)服务器和客户端之间传输的数据长度; 6. 通过 AT+QFTPDEL 删除 FTP(S)服务器文件; 7. 通过 AT+QFTPRENAME 重命名 FTP(S)服务器文件。 第五步:FTP(S)服务器目录操作。 1. 通过 AT+QFTPCWD 设置当前目录; 2. 通过 AT+QFTPMKDIR 创建文件夹; 3. 通过 AT+QFTPLIST 列出目录内容; 4. 通过 AT+QFTPNLST 列出目录文件名; 5. 通过 AT+QFTPRENAME 重命名文件或文件夹; 6. 通过 AT+QFTPRMDIR 删除文件夹; 7. 通过 AT+QFTPMLSD 列出标准文件和目录信息; 8. 通过 AT+QFTPMDTM 获取 FTP(S)服务器文件修改时间。 第六步:关闭和 FTP(S)服务器的连接。 通过 AT+QFTPCLOSE 关闭和 FTP(S)服务器的连接,命令执行后,若响应 URC +QFTPCLOSE: 0,0, 表明操作成功,即可重复上述第三步至第六步。 第七步:去激活 PDP 上下文。 通过 AT+QIDEACT=去激活 PDP 上下文,详细信息可参考文档 [1]。 */ // #if(MY_SHELL_EN) //Shell 使能 /** * ************************************************************************ * @brief - 遥控测试函数 * * * * @author jiankalka * @date 2025-08-04 * ************************************************************************ */ // 命令 type 0 is fan1; 1 is fan2; 2 is acyk1; 3 is acyk2; extern int parse_ftp_url(const char* url, T_FTP_UPDATE_INFO* info); void ftpFunc() { //ftp://cpadmin@39.104.96.26/ftpfile/firmareV3.61.rbl //ftp://cpadmin@39.104.96.26/AcTest.bin \addtogroup //ftp://cpadmin@39.104.96.26/CCU601E.bin const char *ftpUrl = "ftp://cpadmin:ieszt1234@39.104.96.26/CCU601E.bin"; v_shell_print("ftpTest %s\r\n",ftpUrl); if(parse_ftp_url(ftpUrl, &t_ftp_update_info) == 0) { v_shell_print("ftpTest is ok\r\n"); v_shell_print("用户名=%s\r\n",t_ftp_update_info.u8_user); v_shell_print("密码=%s\r\n",t_ftp_update_info.u8_pwd); v_shell_print("域名=%s\r\n",t_ftp_update_info.u8_update_server_addr); v_shell_print("文件路径=%s\r\n",t_ftp_update_info.u8_path); v_shell_print("文件名=%s\r\n",t_ftp_update_info.u8_file_name); t_ftp_update_info.u8_netType = 0; s_bs_task_ctrl.s_bs_ctrl[0].u8_updata_flag = 1; //开始升级 //e_ec200a_ftp_init_step = STEP_FTP_FTP_GET_QFLST; } else v_shell_print("ftpTest is err\r\n"); } // 注册 SHELL_EXPORT_CMD(SHELL_CMD_PERMISSION(0)|SHELL_CMD_TYPE(SHELL_TYPE_CMD_FUNC), ftpTest,ftpFunc, myshlle-- 4 g cat ftp test); // U8_T u8_flash_test[PACKET_SIZE]; // void flashFunc(int id) // { // uint32_t len = PACKET_SIZE; // upgrade_header_t header; // memset(u8_flash_test,0,PACKET_SIZE); // upgrade_read_packet(id,u8_flash_test,&len); // printf_hex("flash",u8_flash_test,len); // if (upgrade_read_header(&header) == 0) // { // EC200A_FTP_LOG("flag =[%d] , fileSize =[%d]\r\n",header.upgrade_flag,header.file_size); // } // else // EC200A_FTP_LOG("header err\r\n"); // v_shell_print("flashFunc \r\n"); // } // // 注册 // SHELL_EXPORT_CMD(SHELL_CMD_PERMISSION(0)|SHELL_CMD_TYPE(SHELL_TYPE_CMD_FUNC), flashTest,flashFunc, myshlle-- flashTest test); #endif #endif /* EC200A_4G_EN */