/* * YM310 FTP ECO Log Upload Module - v1.1 (Chunked Upload) * * - 分块读取 ecode 记录,每块 ≤ 4KB,通过 QFTPPUT 续传 * - 文件名 = IMEI_YYYYMMDD.txt,上传目录为 /scd_txt * - 上传结束后清除 easyflash 标志并重启 */ #define DBG_TAG "Y-FUL" #define DBG_LVL DBG_INFO #include #include "ym310_mqtt.h" #include #include #include #include #include #include #include "ecode.h" #include "ym310_protecl.h" /*-----------------------------------------------------------------------------*/ typedef struct { char username[32]; char password[32]; char host[64]; uint16_t port; char filename[128]; } ftp_url_info_t; /*-----------------------------------------------------------------------------*/ extern void ym310_clear_rx_buffer(void); extern at_resp_type_t ym310_at_exec_cmd(const char *cmd, char *resp, rt_uint16_t resp_size, rt_int32_t timeout_ms); extern at_resp_type_t ym310_at_exec_cmd_with_retry(const char *cmd, char *resp, rt_uint16_t resp_size, rt_int32_t timeout_ms, rt_uint8_t retry_max); extern int ym310_at_send_line(const char *line); extern int ym310_at_send_raw(const char *data, rt_uint16_t len); extern int ym310_at_wait_for(const char *expected, char *buf, rt_uint16_t buf_size, rt_int32_t timeout_ms); extern rt_err_t ym310_at_mutex_lock(rt_int32_t timeout_ms); extern void ym310_at_mutex_unlock(void); extern int ym310_at_core_init(void); extern uint8_t ef_cfged; extern rt_device_t g_ym310_uart; extern char start_ftp; extern int ym310_ftp_parse_url(const char *url, ftp_url_info_t *info); extern int ym310_ftp_wait_sim_ready(void); extern int ym310_ftp_wait_network_ready(void); extern int ym310_ftp_activate_pdp(void); extern int ym310_ftp_configure(void); /*-----------------------------------------------------------------------------*/ #define UPLOAD_FLAG_VALUE 0x9D #define EF_UPLOAD_KEY "upload_ecode" #define FTP_DEFAULT_UPLOAD_URL "ftp://updateuser:123456@www.yfzhwlw.com:21/scd_log" // #define FTP_DEFAULT_UPLOAD_URL "ftp://ruofeng:MhRx3NJsiaAx@47.92.123.150:21/" #define FTP_CMD_TIMEOUT_MS 20000 #define FTP_OPEN_TIMEOUT_MS 20000 #define FTP_CHUNK_BUF_SIZE 4096 /* 每块最大字节数 */ /*-----------------------------------------------------------------------------*/ /** * @brief 发送一个数据块(通过 QFTPPUT 续传) * @param filename 上传文件名 * @param offset 文件的起始写入位置(字节) * @param data 数据缓冲区 * @param len 数据长度 * @param is_last 是否为最后一包 * @return 成功返回 RT_EOK */ static int ftp_put_chunk(const char *filename, uint32_t offset, const uint8_t *data, uint16_t len, rt_bool_t is_last) { char cmd[128]; int retry_count = 0; const int MAX_RETRY = 3; while (retry_count < MAX_RETRY) { rt_snprintf(cmd, sizeof(cmd), "AT+QFTPPUT=\"%s\",\"COM:\",%lu,%d,%d", filename, offset, len, is_last ? 1 : 0); LOG_D("FTP chunk: %s offset=%lu len=%d last=%d (retry %d/%d)", cmd, offset, len, is_last, retry_count + 1, MAX_RETRY); if (ym310_at_mutex_lock(60000) != RT_EOK) { LOG_E("FTP chunk: mutex lock failed"); return -RT_ERROR; } ym310_clear_rx_buffer(); rt_thread_mdelay(10); if (ym310_at_send_line(cmd) != RT_EOK) { LOG_E("FTP chunk: send command failed"); ym310_at_mutex_unlock(); retry_count++; rt_thread_mdelay(500); continue; } /* 等待 CONNECT */ if (ym310_at_wait_for("CONNECT", NULL, 0, 30000) != RT_EOK) { LOG_E("FTP chunk: no CONNECT (retry %d)", retry_count + 1); ym310_at_mutex_unlock(); retry_count++; rt_thread_mdelay(500); continue; } /* 发送数据 */ if (ym310_at_send_raw((const char *)data, len) < 0) { LOG_E("FTP chunk: send data failed"); ym310_at_mutex_unlock(); retry_count++; rt_thread_mdelay(500); continue; } /* 等待 +QFTPPUT: 结果 */ char urc[64] = {0}; if (ym310_at_wait_for("+QFTPPUT:", urc, sizeof(urc), 30000) != RT_EOK) { LOG_E("FTP chunk: no +QFTPPUT URC (retry %d)", retry_count + 1); ym310_at_mutex_unlock(); retry_count++; rt_thread_mdelay(500); continue; } int err = ym310_str_extract_nth_int(urc, 0); int transferred = ym310_str_extract_nth_int(urc, 1); if (err != 0 || transferred != len) { LOG_E("FTP chunk: transfer err=%d, got=%d, expected=%d (retry %d)", err, transferred, len, retry_count + 1); ym310_at_mutex_unlock(); retry_count++; rt_thread_mdelay(500); continue; } /* 消耗掉可能存在的 OK */ ym310_at_wait_for("OK", NULL, 0, 2000); ym310_at_mutex_unlock(); LOG_I("FTP chunk: success offset=%lu len=%d", offset, len); return RT_EOK; } LOG_E("FTP chunk: failed after %d retries at offset=%lu", MAX_RETRY, offset); return -RT_ERROR; } /*-----------------------------------------------------------------------------*/ /** * @brief 分块上传全部 ecode 记录到指定文件 * @param filename 上传的文件名 * @return RT_EOK 成功 */ static int ftp_upload_ecode_file(const char *filename) { uint32_t total_records = ecode_get_count(); if (total_records == 0) { LOG_W("FTP Upload: no ecode records"); return -RT_ERROR; } LOG_I("FTP Upload: %u records to upload", total_records); uint32_t sent_records = 0; uint32_t sent_bytes = 0; /* 文件已写入的字节数 (用于 startpos) */ uint8_t *chunk_buf = rt_malloc(FTP_CHUNK_BUF_SIZE); if (!chunk_buf) { LOG_E("FTP Upload: malloc chunk buffer failed"); return -RT_ERROR; } int ret = -RT_ERROR; while (sent_records < total_records) { uint16_t chunk_len = 0; uint16_t added = 0; /* 填充当前块,尽量填满 4KB */ while (sent_records + added < total_records && chunk_len < FTP_CHUNK_BUF_SIZE - 200) /* 留出余量 */ { ecode_record_t rec; if (ecode_read(sent_records + added, &rec) != 0) break; /* 读取失败终止 */ int wrote = rt_snprintf((char *)chunk_buf + chunk_len, FTP_CHUNK_BUF_SIZE - chunk_len, "%u - %02u/%02u/%02u %02u:%02u:%02u: (%5u-%-5u) %s\n", rec.num, rec.time_stamp.tm_year + 2000, rec.time_stamp.tm_mon, rec.time_stamp.tm_mday, rec.time_stamp.tm_hour, rec.time_stamp.tm_min, rec.time_stamp.tm_sec, rec.code, rec.para, rec.detail); if (wrote < 0 || (uint16_t)wrote >= (FTP_CHUNK_BUF_SIZE - chunk_len)) break; chunk_len += wrote; added++; } if (added == 0) { LOG_E("FTP Upload: failed to read any record at offset %u", sent_records); goto cleanup; } /* 判断是否是最后一包 */ rt_bool_t is_last = (sent_records + added >= total_records) ? RT_TRUE : RT_FALSE; LOG_D("FTP Upload: sending chunk records %u-%u, %u bytes", sent_records, sent_records + added - 1, chunk_len); if (ftp_put_chunk(filename, sent_bytes, chunk_buf, chunk_len, is_last) != RT_EOK) { LOG_E("FTP Upload: chunk send failed at record %u", sent_records); goto cleanup; } sent_records += added; sent_bytes += chunk_len; /* 防止过于频繁,给模块一点喘息 */ rt_thread_mdelay(210); } ret = RT_EOK; LOG_I("FTP Upload: all chunks sent, total bytes=%u", sent_bytes); cleanup: rt_free(chunk_buf); return ret; } /*-----------------------------------------------------------------------------*/ /** * @brief 上传主流程:解析URL -> 打开FTP -> CWD -> 上传文件 -> 关闭 */ static int ftp_upload_ecode_report(const char *upload_url) { ftp_url_info_t url_info; int ret; LOG_I("========================================"); LOG_I("FTP ECO Upload: Starting..."); LOG_I("Upload URL: %s", upload_url); /* 1. 解析 URL */ if (ym310_ftp_parse_url(upload_url, &url_info) != RT_EOK) { LOG_E("FTP Upload: URL parse failed"); return -RT_ERROR; } /* 2. 基本 AT 就绪 */ if (ym310_at_exec_cmd_with_retry("AT", NULL, 0, 3000, 3) != AT_RESP_OK) { LOG_E("FTP Upload: module unresponsive"); return -RT_ERROR; } /* === 新增:网络准备与 FTP 配置 === */ if (ym310_ftp_wait_sim_ready() != RT_EOK) { LOG_E("FTP Upload: SIM not ready"); return -RT_ERROR; } if (ym310_ftp_wait_network_ready() != RT_EOK) { LOG_E("FTP Upload: Network not ready"); return -RT_ERROR; } if (ym310_ftp_activate_pdp() != RT_EOK) { LOG_E("FTP Upload: PDP activation failed"); return -RT_ERROR; } if (ym310_ftp_configure() != RT_EOK) { LOG_E("FTP Upload: FTP configure failed"); return -RT_ERROR; } ym310_at_exec_cmd_with_retry("ATE0", NULL, 0, 3000, 2); ym310_at_exec_cmd_with_retry("AT+CMEE=2", NULL, 0, 3000, 2); /* 3. 设置 FTP 账户 */ char cmd[128]; rt_snprintf(cmd, sizeof(cmd), "AT+QFTPCFG=\"account\",\"%s\",\"%s\"", url_info.username, url_info.password); if (ym310_at_exec_cmd_with_retry(cmd, NULL, 0, FTP_CMD_TIMEOUT_MS, 3) != AT_RESP_OK) { LOG_E("FTP Upload: set account failed"); return -RT_ERROR; } /* 4. 打开 FTP 连接 */ rt_memset(cmd, 0, sizeof(cmd)); rt_snprintf(cmd, sizeof(cmd), "AT+QFTPOPEN=\"%s\",%d", url_info.host, url_info.port); LOG_I("FTP Upload: opening %s:%d", url_info.host, url_info.port); ym310_clear_rx_buffer(); rt_thread_mdelay(10); if (ym310_at_send_line(cmd) != RT_EOK) { LOG_E("FTP Upload: send OPEN failed"); return -RT_ERROR; } if (ym310_at_wait_for("OK", NULL, 0, 10000) != RT_EOK) { LOG_E("FTP Upload: no OK after OPEN"); return -RT_ERROR; } char open_urc[64] = {0}; if (ym310_at_wait_for("+QFTPOPEN:", open_urc, sizeof(open_urc), FTP_OPEN_TIMEOUT_MS) != RT_EOK) { LOG_E("FTP Upload: no +QFTPOPEN URC"); ym310_at_exec_cmd("AT+QFTPCLOSE", NULL, 0, 5000); return -RT_ERROR; } if (ym310_str_extract_nth_int(open_urc, 0) != 0) { LOG_E("FTP Upload: OPEN failed, err=%d", ym310_str_extract_nth_int(open_urc, 0)); ym310_at_exec_cmd("AT+QFTPCLOSE", NULL, 0, 5000); return -RT_ERROR; } LOG_I("FTP Upload: connection opened"); /* 5. 切换目录 */ const char *target_dir = url_info.filename; if (target_dir[0] == '\0') target_dir = "/"; rt_snprintf(cmd, sizeof(cmd), "AT+QFTPCWD=\"%s\"", target_dir); LOG_I("FTP Upload: CWD to %s", target_dir); ym310_clear_rx_buffer(); rt_thread_mdelay(10); if (ym310_at_send_line(cmd) != RT_EOK) { LOG_E("FTP Upload: send CWD failed"); ym310_at_exec_cmd("AT+QFTPCLOSE", NULL, 0, 5000); return -RT_ERROR; } if (ym310_at_wait_for("OK", NULL, 0, 10000) != RT_EOK) { LOG_E("FTP Upload: no OK after CWD"); ym310_at_exec_cmd("AT+QFTPCLOSE", NULL, 0, 5000); return -RT_ERROR; } char cwd_urc[64] = {0}; if (ym310_at_wait_for("+QFTPCWD:", cwd_urc, sizeof(cwd_urc), FTP_CMD_TIMEOUT_MS) != RT_EOK) { LOG_E("FTP Upload: no +QFTPCWD URC"); ym310_at_exec_cmd("AT+QFTPCLOSE", NULL, 0, 5000); return -RT_ERROR; } if (ym310_str_extract_nth_int(cwd_urc, 0) != 0) { LOG_E("FTP Upload: CWD failed"); ym310_at_exec_cmd("AT+QFTPCLOSE", NULL, 0, 5000); return -RT_ERROR; } LOG_I("FTP Upload: CWD success"); /* 6. 生成文件名 */ char filename[64]; rt_snprintf(filename, sizeof(filename), "%s_%04d%02d%02d.txt", sys_info.imei, time_now.year, time_now.month, time_now.date); /* 7. 分块上传 */ ret = ftp_upload_ecode_file(filename); /* 8. 关闭 FTP 连接 */ ym310_at_exec_cmd("AT+QFTPCLOSE", NULL, 0, 5000); LOG_I("FTP Upload: connection closed"); if (ret == RT_EOK) { LOG_I("========================================"); LOG_I("FTP ECO Upload completed successfully!"); LOG_I("========================================"); } return ret; } extern void_lte_power_on(void); extern void_lte_power_off(void); /*-----------------------------------------------------------------------------*/ /* 上传任务线程 */ static void ftp_upload_thread_entry(void *param) { uint8_t upload_flag = 0; rt_thread_mdelay(3000); int timeout = 0; while (!ef_cfged && timeout < 300) { rt_thread_mdelay(100); timeout++; } if (!ef_cfged) { LOG_E("FTP Upload: EasyFlash not ready"); return; } ef_get_env_blob(EF_UPLOAD_KEY, &upload_flag, sizeof(upload_flag), NULL); if (upload_flag != UPLOAD_FLAG_VALUE) { LOG_I("FTP Upload: no upload task"); return; } /* 清除标志 */ upload_flag = 0; ef_set_env_blob(EF_UPLOAD_KEY, &upload_flag, sizeof(upload_flag)); LOG_I("FTP Upload: task detected"); start_ftp = VALID; void_lte_power_off(); rt_thread_mdelay(1000); void_lte_power_on(); if (g_ym310_uart == RT_NULL) { if (ym310_at_core_init() != RT_EOK) { LOG_E("FTP Upload: AT core init failed"); return; } } rt_thread_mdelay(1000); int ret = ftp_upload_ecode_report(FTP_DEFAULT_UPLOAD_URL); if (ret == RT_EOK) { Recode_e_code(e_upload_ecode, 1, "EC日志上传成功"); LOG_I("FTP Upload: success, rebooting..."); char upload_res = VALID; ef_set_env_blob("upload_res", &upload_res, sizeof(upload_res)); } else { Recode_e_code(e_upload_ecode, 1, "EC日志上传失败"); LOG_E("FTP Upload: failed, rebooting..."); char upload_res = 0x9A; ef_set_env_blob("upload_res", &upload_res, sizeof(upload_res)); } void_lte_power_off(); rt_thread_mdelay(1000); void_lte_power_on(); rt_hw_cpu_reset(); } /*-----------------------------------------------------------------------------*/ static rt_bool_t upload_thread_started = RT_FALSE; int ym310_ftp_upload_start(void) { if (upload_thread_started) return RT_EOK; rt_thread_t tid = rt_thread_create("ftp_upload", ftp_upload_thread_entry, RT_NULL, 4096, 21, 10); if (tid == RT_NULL) { LOG_E("FTP Upload: thread create failed"); return -RT_ERROR; } rt_thread_startup(tid); upload_thread_started = RT_TRUE; return RT_EOK; } INIT_APP_EXPORT(ym310_ftp_upload_start);