/* 连接到FTP服务器 ,从FTP服务器中下载文件,下载逻辑 */ /* includes ----------------------------------------------------------*/ #include "EC800_FTP_OTA.h" /* typedef -----------------------------------------------------------*/ typedef struct{ const char *account; // 用户名 const char *passWord; // 密码 uint8_t fileType; // 文件类型 1;ASCII uint8_t transmode; // 传输模式 1:被动 uint8_t rsptimeout; // 最大响应时间 90 const char * ftpAddr; // ftp服务器地址 uint16_t ftpPort; // ftp服务器端口 const char * textDirectory; // 下载文件的文件所在目录 const char *textName; // 下载文件的文件名称 uint32_t filesize; // 下载的文件大小 uint32_t startAddr; // 下载文件的起始字节 uint32_t byteNum; // 一次获取的字节个数 }ftpInfo; ftpInfo s_ftpInfo = { .account = "cdzupdate", .passWord = "cdz2021!Z", .fileType = 1, .transmode = 1, .rsptimeout = 90, .ftpAddr = "39.98.211.244", .ftpPort = 21, .textDirectory = "/data/cdz", .textName = "centralCtrSys.bin", }; /* define ------------------------------------------------------------*/ // 登陆到FTP服务器 // 第一步:配置和激活 PDP 上下文 #define AT_QIACT_1 "AT+QIACT=1\r\n" // 激活 PDP 上下文 1 #define AT_QIACT_query "AT+QIACT?\r\n" // 查询 PDP 上下文状态 #define AT_QFTPCFG_ID_1 "AT+QFTPCFG=\"contextid\",1\r\n" // 查询 PDP 上下文状态 // 第二步: 配置用户账号和传输设置 #define AT_QFTPCFG_ACCOUNT "AT+QFTPCFG=\"account\",\"%s\",\"%s\"\r\n" // 设置用户名和密码。 #define AT_QFTPCFG_FILE(fileType) "AT+QFTPCFG=\"filetype\"," #fileType "\r\n" // 设置文件类型为ASCII AT+QFTPCFG="filetype",1 #define AT_QFTPCFG_TRANS(mode) "AT+QFTPCFG=\"transmode\"," #mode "\r\n" // 设置为被动传输方式AT+QFTPCFG="transmode",1 #define AT_QFTPCFG_TIMEOUT(time) "AT+QFTPCFG=\"rsptimeout\"," #time "\r\n" // 设置最大响应时间(默认为90秒) //第三步:登录FTP服务器。 #define AT_QFTPOPEN(addr,port) "AT+QFTPOPEN=\"" #addr "\"," #port "\r\n" // 登录FTP服务器。 // 从FTP服务器下载文件 本项目文件较小,选择直接通过COM口输出下载数据 #define AT_QFTPCWD(directory) "AT+QFTPCWD=\"" #directory "\"\r\n" // 设置当前目录。 #define AT_QFTPSIZE(textName) "AT+QFTPSIZE=\"" #textName "\"\r\n" // 查询 FTP(S)服务器 test_my1.txt 文件大小 #define AT_QFTPGET(textName, startByte, downloadNum) \ "AT+QFTPGET=\"" #textName "\",\"COM:\"," #startByte "," #downloadNum "\r\n" // 下载文件, 通过COM口输出特定字段的数据 // AT指令响应超时时间定义 #define REC_TIMEOUT (10000) // 1ms /* macro -------------------------------------------------------------*/ /* variables ---------------------------------------------------------*/ /* function prototypes -----------------------------------------------*/ int findSubstring(const char* haystack, const char* needle) { // 如果needle是空字符串,我们定义它总是在haystack中找到,位置为0 if (!needle[0]) { return 0; } for (int i = 0; haystack[i] != '\0'; ++i) { // 当发现第一个字符匹配时,开始查找整个needle if (haystack[i] == needle[0]) { int j = 0; // 检查整个needle是否匹配 while (needle[j] != '\0' && haystack[i + j] == needle[j]) { j++; } // 如果needle到达了字符串的末尾,我们找到了完整的匹配 if (needle[j] == '\0') { return i; // 返回needle在haystack中开始的位置 } // 否则,继续从haystack的下一个字符开始查找 } } // 如果未找到匹配,返回-1 return -1; } uint8_t Accept_and_Compare_Str(const char* needle){ uint8_t temp = 0; static uint16_t timeOutCnt = 0; // 超时计数 char buff[USART3_REC_LEN] = {0}; while(!temp) { memcpy(buff, g_usart3_rx_buf, USART3_REC_LEN); HAL_UART_Receive_DMA(&huart3, (uint8_t*)g_usart3_rx_buf, USART3_REC_LEN); //设置接收缓冲区 __HAL_UART_ENABLE_IT(&huart3, UART_IT_IDLE); if(strstr(g_usart3_rx_buf, needle)){ temp = 1; } if(strstr(g_usart3_rx_buf, "ERROR")){ temp = 2; } if(timeOutCnt < REC_TIMEOUT){ timeOutCnt++; }else{ timeOutCnt = 0; temp = 3; } osDelay(1); }; g_usart3_rx_sta = 0; HAL_UART_DMAStop(&huart3); __HAL_UART_DISABLE_IT(&huart3, UART_IT_IDLE); return temp; } /* 1.登陆到FTP服务器--------------------------------------------------------*/ static uint8_t logIn_step = 0; /** * @brief 通过EC800登录FTP * @param NONE * @note NONE * @retval 无 */ void EC800_LogIn_FTP(void){ char* found = NULL; uint8_t errorCnt = 0; // 错误计数 ftpInfo *p_ftpInfo = &s_ftpInfo; char atCmd[100] = {0}; // 静态缓冲区用于存放AT命令 p_ftpInfo->fileType = p_ftpInfo->fileType; // 避免报警告 switch(logIn_step){ case 0: // 配置和激活 PDP 上下文 // EC800M_SendCommand(AT_QIACT_1); // // found = EC800M_RecRespond(g_usart3_rx_buf, AT_RESP_OK); // // if (found != NULL) { // printf("Activation successful\r\n"); // }else{ // errorCnt++; // printf("Activation unsuccessful\r\n"); // } EC800M_SendCommand(AT_QIACT_query); found = EC800M_RecRespond(g_usart3_rx_buf, AT_RESP_OK); if (found != NULL) { printf("PDP Context Status OK\r\n"); }else{ errorCnt++; printf("PDP Context Status error\r\n"); } EC800M_SendCommand(AT_QFTPCFG_ID_1); found = EC800M_RecRespond(g_usart3_rx_buf, AT_RESP_OK); if (found != NULL) { printf("Set PDP context to 1.\r\n"); }else{ errorCnt++; printf("ERROR! Set PDP context to 1.\r\n"); } if(errorCnt == 0){ logIn_step = 1; } break; case 1: // 配置用户账号和传输设置 snprintf(atCmd, sizeof(atCmd), "AT+QFTPCFG=\"account\",\"%s\",\"%s\"\r\n", p_ftpInfo->account, p_ftpInfo->passWord); EC800M_SendCommand(atCmd); found = EC800M_RecRespond(g_usart3_rx_buf, AT_RESP_OK); if (found != NULL) { printf("FINISH! Set username and password.\r\n"); }else{ errorCnt++; printf("ERROR! Set username and password.\r\n"); } snprintf(atCmd, sizeof(atCmd), "AT+QFTPCFG=\"filetype\",%d\r\n", p_ftpInfo->fileType); EC800M_SendCommand(atCmd); found = EC800M_RecRespond(g_usart3_rx_buf, AT_RESP_OK); if (found != NULL) { printf("FINISH! Set username and password.\r\n"); }else{ errorCnt++; printf("ERROR! Set username and password.\r\n"); } snprintf(atCmd, sizeof(atCmd), "AT+QFTPCFG=\"transmode\",%d\r\n", p_ftpInfo->transmode); EC800M_SendCommand(atCmd); found = EC800M_RecRespond(g_usart3_rx_buf, AT_RESP_OK); if (found != NULL) { printf("FINISH! Set to passive transfer mode.\r\n"); }else{ errorCnt++; printf("ERROR! Set to passive transfer mode.\r\n"); } snprintf(atCmd, sizeof(atCmd), "AT+QFTPCFG=\"rsptimeout\",%d\r\n", p_ftpInfo->rsptimeout); EC800M_SendCommand(atCmd); found = EC800M_RecRespond(g_usart3_rx_buf, AT_RESP_OK); if (found != NULL) { printf("FINISH! Set username and password.\r\n"); }else{ errorCnt++; printf("ERROR! Set username and password.\r\n"); } if(errorCnt == 0){ logIn_step = 2; } break; case 2: // 登录FTP服务器。 snprintf(atCmd, sizeof(atCmd), "AT+QFTPOPEN=\"%s\",%d\r\n", p_ftpInfo->ftpAddr, p_ftpInfo->ftpPort); EC800M_SendCommand(atCmd); // EC800_recEnable(); // 开始接收数据 errorCnt = Accept_and_Compare_Str("+QFTPOPEN: 0,0"); if(errorCnt == 1){ errorCnt = 0; printf("FINISH! Log in to the FTP server.\r\n"); }else{ printf("ERROR! Log in to the FTP server.\r\n"); } if(errorCnt == 0){ logIn_step = 3; } break; default : break; } } /* 2.从FTP服务器上下载文件--------------------------------------------------------*/ //int extract_data(const char* input, char* output, size_t output_size) { // const char* start_marker = "CONNECT\r\n"; // const char* end_marker = "\r\nOK"; // const char* start; // const char* end; // // // 寻找开始标记 // start = strstr(input, start_marker); // if (start == NULL) { // return HAL_ERROR; // } // // // 移动指针越过开始标记 // start += strlen(start_marker); // // // 寻找结束标记 // end = strstr(start, end_marker); // if (end == NULL) { // return HAL_ERROR; // } // // // 计算要提取数据的长度 // size_t data_length = end - start; // if (data_length >= output_size) { // 确保输出缓冲区能够存放提取的数据 // return HAL_ERROR; // } // // // 提取数据 // memcpy(output, start, data_length); // // // 在输出字符串最后添加一个null终止符 // output[data_length] = '\0'; // // return HAL_OK; //} /** * @brief 剔除除去固件的其他的数据 * @param NONE * @note NONE * @retval 无 */ int extract_data_as_uint32(const char* input, uint32_t* output, size_t output_size) { const char* start_marker = "CONNECT\r\n"; const char* end_marker = "\r\nOK"; const char* start; const char* end; // 寻找开始标记 // start = strstr(input, start_marker); start = input; if (start == NULL) { return HAL_ERROR; } // 移动指针越过开始标记 // start += strlen(start_marker); // 寻找结束标记 end = strstr(start, end_marker); if (end == NULL) { return HAL_ERROR; } // 计算要提取数据的长度 size_t data_length = end - start; // 确保提取的数据长度为4的倍数,适用于 uint32_t if (data_length % sizeof(uint32_t) != 0) { return HAL_ERROR; } // 计算 uint32_t 的数量 size_t data_length_uint32 = data_length / sizeof(uint32_t); if (data_length_uint32 > output_size) { // 确保输出缓冲区能够存放提取的数据 return HAL_ERROR; } // 提取数据并转换为 uint32_t 数组 memcpy(output, start, data_length_uint32 * sizeof(uint32_t)); return HAL_OK; } uint32_t totalBytesReceived = 0; uint32_t currentFlashAddress = APP1_ADDRESS; /** * @brief 更新固件 * @param NONE * @note NONE * @retval 无 */ void UpdateFirmware(void) { ftpInfo *p_ftpInfo = &s_ftpInfo; uint32_t buffer[FLASH_PAGE_SIZE / sizeof(uint32_t)]; // 2KB缓冲区 uint8_t returnTemp = 0; // 用于接收返回值的临时变量 char atCmd[100] = {0}; // 静态缓冲区用于存放AT命令 if (totalBytesReceived < (p_ftpInfo->filesize)) { // 计算剩余要接收的字节数 uint32_t remaining = (p_ftpInfo->filesize) - totalBytesReceived; uint32_t bytesToRead = remaining < 100 ? remaining : 100; p_ftpInfo->startAddr = totalBytesReceived; p_ftpInfo->byteNum = bytesToRead; // // 发送AT指令以开始接收 snprintf(atCmd, sizeof(atCmd), "AT+QFTPGET=\"%s\",\"COM:\",%u,%u\r\n", p_ftpInfo->textName, p_ftpInfo->startAddr, p_ftpInfo->byteNum); EC800M_SendCommand(atCmd); returnTemp = Accept_and_Compare_Str("+QFTPGET: 0,"); // 开始接收数据 if((returnTemp == 1) || (returnTemp == 3)){ // 接收成功 // 接收数据处理到buffer 剔除除去固件的其他的数据 returnTemp = extract_data_as_uint32(g_usart3_rx_buf, buffer, FLASH_PAGE_SIZE / sizeof(uint32_t)); if(returnTemp != HAL_OK){ printf("ERROR! UpdateFirmware_extract_data\n"); return; } }else{ return; } // 将接收到的数据写入Flash // if (FLASH_Write(currentFlashAddress, buffer, FLASH_PAGE_SIZE / sizeof(uint32_t)) != HAL_OK) { // // 错误处理 // printf("ERROR! UpdateFirmware_FLASH_Write\n"); // return; // } // 更新地址和接收统计 currentFlashAddress += FLASH_PAGE_SIZE; totalBytesReceived += bytesToRead; } // 根据实际情况实现接收完成后的操作 } uint8_t downloadStep = 0; /** * @brief FTP服务器上下载文件 * @param NONE * @note 每一个任务周期过来下载2k的数据,以保证其他任务的正常运行 * @retval 无 */ void EC800_FTP_DownloadText(void){ uint8_t errorCnt = 0; // 错误计数 ftpInfo *p_ftpInfo = &s_ftpInfo; char atCmd[100] = {0}; // 静态缓冲区用于存放AT命令 switch(downloadStep){ case 0: // 设置文件目录 snprintf(atCmd, sizeof(atCmd), "AT+QFTPCWD=\"%s\"\r\n", p_ftpInfo->textDirectory); EC800M_SendCommand(atCmd); errorCnt = Accept_and_Compare_Str("+QFTPCWD: 0,0"); if (errorCnt == 1) { errorCnt = 0; printf("FINISH! Set the file directory.\r\n"); }else{ printf("ERROR! Set the file directory.\r\n"); } if(errorCnt == 0){ downloadStep = 1; } break; case 1: // 获取文件长度 snprintf(atCmd, sizeof(atCmd), "AT+QFTPSIZE=\"%s\"\r\n", p_ftpInfo->textName); EC800M_SendCommand(atCmd); errorCnt = Accept_and_Compare_Str("+QFTPSIZE: 0,"); if (errorCnt == 1) { errorCnt = 0; printf("FINISH! Get the length of the file.\r\n"); // 使用sscanf从字符串中解析整数 if(sscanf(g_usart3_rx_buf, "\r\n+QFTPSIZE: %*d,%u", &(p_ftpInfo->filesize)) == 1) { // 解析成功,filesize变量现在包含值1000 printf("The filesize is: %u\n", p_ftpInfo->filesize); } else { // 解析失败 errorCnt++; printf("Failed to parse the filesize.\n"); } }else{ printf("ERROR! Get the length of the file.\r\n"); } if(errorCnt == 0){ downloadStep = 2; } break; case 2: UpdateFirmware(); break; default : break; } } /* 3. OTA升级从FTP流程--------------------------------------------------------*/ uint8_t upgradeStep = 0; /** * @brief OTA升级从FTP * @param NONE * @note NONE * @retval 无 */ void EC800_FTP_OTA_Upgrade(void){ switch (upgradeStep){ case 0: // 登陆到FTP服务器 EC800_LogIn_FTP(); if(logIn_step == 3){ upgradeStep = 1; } break; case 1: // 从FTP服务器上下载文件 EC800_FTP_DownloadText(); break; case 2: // 复位升级 HAL_NVIC_SystemReset(); break; default: break; } }