#include "ec20ftp.h" #include #include "syslib.h" //#define STR2(R) STR1(R) /******************************************************************************** * @file ec20ftp.c * @author 江苏九比特信息系统有限公司 Mr.Wang * @version V1.0.0 * @date 11-Dec-2018 * @brief 提供Quectel模块EC20关于FTP硬件驱动程序 ****************************************************************************** * @attention * 约定基本名词如下: * contextID:链路ID * EC20模块链路ID范围1~16,每一个链路ID都会对应一个本地IP;。 * 本驱动强制规定,FTP协议默认只用一个链路ID(即contextID=3)用于ftp链路。 *******************************************************************************/ /***************************************** *内部使用的常变量定义 ****************************************/ uint8_t ftpLocalIp[MAX_IP_LEN] ; //FTP链路本地IPP #define FTP_CMDPACK_LEN 256 //FTP命令的最大长度 /******************************************************** ec20模块FTP相关AT指令处理 *********************************************************/ enum eFtpCmdNum { SETCONTEXTID =0, SETACCOUNT =1, SETFILETYPE =2, SETTRANSMODE =3, SETRSTIMEOUT =4, FTPLOGIN =5, GETLOGINSTATE =6, SETDIRECTORY =7, GETDIRECTORY =8, LISTFILENAME =9, GETFILESIZE =10, DOWNFILE =11, GETHASDOWNSIZE =12, RENAMEFILE =13, FTPLOGOUT = 14 } ;//枚举ec20模块FTP相关指令枚举 volatile EC20_CMD_DATA_s sFtpCmd[15]= { // cmdNum cmdStr, timeout(100ms), trueStr, trueOffset falseStr revResult rtyNum {SETCONTEXTID, "AT+QFTPCFG=\"contextid\",%d\r\n", 10, "OK", -1, "ERROR", TIMEOUT, 2 }, //手册回码等待150S {SETACCOUNT, "AT+QFTPCFG=\"account\",\"%s\",\"%s\"\r\n",10, "OK" , -1, "ERROR", TIMEOUT, 2 }, //账户、密码 {SETFILETYPE, "AT+QFTPCFG=\"filetype\",%d\r\n" , 10, "OK" , -1, "ERROR", TIMEOUT, 2 }, //文件类型 {SETTRANSMODE, "AT+QFTPCFG=\"transmode\",%d\r\n" , 10, "OK" , -1, "ERROR", TIMEOUT, 2 }, //传输模式 {SETRSTIMEOUT, "AT+QFTPCFG=\"rsptimeout\",%d\r\n" , 10, "OK" , -1, "ERROR", TIMEOUT, 2 }, //返回超时时间 {FTPLOGIN, "AT+QFTPOPEN=\"%s\",%d\r\n", (20*10), "OK", -1, "ERROR", TIMEOUT, 2 }, //FTP登录 {GETLOGINSTATE, "AT+QFTPSTAT\r\n", 10, "+QFTPSTAT: 0", -1, "ERROR", TIMEOUT, 2 }, //(0:Opening 1:idle 2:Transferring 3:Closing 4:Closed) {SETDIRECTORY, "AT+QFTPCWD=\"%s\"\r\n" , 10, "+QFTPCWD: 0,0", -1, "ERROR", TIMEOUT, 2 }, //ftp目录路径 {GETDIRECTORY, "AT+QFTPPWD\r\n" , 10, "+QFTPPWD: 0", -1, "ERROR", TIMEOUT, 2 }, //传输模式 {LISTFILENAME, "AT+QFTPNLST=\"%s\"\r\n" , 10, "+QFTPNLST: 0", -1, "ERROR", TIMEOUT, 2 }, //ftp目录路径 列出FTP目录中所有文件的文件名字 {GETFILESIZE, "AT+QFTPSIZE=\"%s\"\r\n" , 10, "+QFTPSIZE: 0", -1, "ERROR", TIMEOUT, 2 }, //文件名 获取文件名文件大小:+QFTPSIZE: 0,36048 {DOWNFILE, "AT+QFTPGET=\"%s\",\"COM:\",%d,%d\r\n" , 40, "\r\nOK\r\n\r\n+QFTPGET: 0", -1, "ERROR", TIMEOUT, 1 }, //文件名+起始偏移量+下载字节数 下载文件 {GETHASDOWNSIZE, "AT+QFTPLEN\r\n" , 10, "+OK", -1, "ERROR", TIMEOUT, 2 }, //ftp目录路径 列出FTP目录中所有文件的文件名字 {RENAMEFILE, "AT+QFTPRENAME=\"%s\",\"%s\"\r\n" , 20, "+QFTPRENAME: 0", -1, "ERROR", TIMEOUT, 2 }, //旧文件名+新文件名 {FTPLOGOUT, "AT+QFTPCLOSE\r\n", (2*10), "+QFTPCLOSE", -1, "ERROR", TIMEOUT, 2 }, //FTP退出登录 } ; //EC20模块module相关指令的EC20_CMD_DATA_s结构体类型参数 /************************************************************************************************** * 名 称: static const char *FtpCmdNumToString(enum eFtpCmdNum result) * 功能说明: 输出枚举成员名的字符串指针。 * 入口参数: eFtpCmdNum类型的枚举 * 出口参数: 为枚举的成员名字符串指针 **************************************************************************************************/ static inline const char *FtpCmdNumToString(enum eFtpCmdNum result) { switch (result) { ENUM_CHIP_TYPE_CASE(SETCONTEXTID) ENUM_CHIP_TYPE_CASE(SETACCOUNT) ENUM_CHIP_TYPE_CASE(SETFILETYPE) ENUM_CHIP_TYPE_CASE(SETTRANSMODE) ENUM_CHIP_TYPE_CASE(SETRSTIMEOUT) ENUM_CHIP_TYPE_CASE(FTPLOGIN) ENUM_CHIP_TYPE_CASE(GETLOGINSTATE) ENUM_CHIP_TYPE_CASE(SETDIRECTORY) ENUM_CHIP_TYPE_CASE(GETDIRECTORY) ENUM_CHIP_TYPE_CASE(LISTFILENAME) ENUM_CHIP_TYPE_CASE(GETFILESIZE) ENUM_CHIP_TYPE_CASE(DOWNFILE) ENUM_CHIP_TYPE_CASE(GETHASDOWNSIZE) ENUM_CHIP_TYPE_CASE(RENAMEFILE) ENUM_CHIP_TYPE_CASE(FTPLOGOUT) } return "无此命令"; } /************************************************************************************************** * 名 称: RunResult Ftp_Config( void ) * 功能说明: 对FTP相关参数进行配置 * 入口参数: * @param1 *psFtp FtpP_s结构体变量指针 * 出口参数: * @param1 runResult RunResult枚举类型变量,返回函数运行结果 **************************************************************************************************/ RunResult Ftp_Config( FtpP_s *psFtp ) { RunResult runResult = TIMEOUT ; runResult = EC20_SendFtpCmd(SETCONTEXTID, NULL, psFtp->contextId ) ; if( runResult != RUNOK ) return runResult ; runResult = EC20_SendFtpCmd(SETACCOUNT, NULL, psFtp->userName, psFtp->password ) ; AppLogPrintf("配置FTP服务器Username:%s, Password:%s .", psFtp->userName, psFtp->password ) ; if( runResult != RUNOK ) return runResult ; EC20_SendFtpCmd(SETFILETYPE, NULL, psFtp->eFiletype ) ; EC20_SendFtpCmd(SETTRANSMODE, NULL, psFtp->eTransmode ) ; EC20_SendFtpCmd(SETRSTIMEOUT, NULL, psFtp->rsptimeout ) ; return runResult ; } /************************************************************************************************** * 名 称: RunResult Ftp_PDP_Init( FtpP_s *psFtp ) * 功能说明: 初始化FTP链路 * 出口参数: * @param1 runResult RunResult枚举类型变量,返回函数运行结果 **************************************************************************************************/ RunResult Ftp_PDP_Init( FtpP_s *psFtp ) { RunResult runResult = TIMEOUT ; uint8_t *ftpLocalIp = portMalloc(MAX_IP_LEN) ; runResult = Query_Context( psFtp->contextId, ftpLocalIp ) ; //查询psFtp->contextId是否激活 if( RUNOK == runResult ) /*psFtp->contextId已激活*/ //去激活->再次激活 { // runResult = Deact_Context(HTTP_CONTEXTID) ; // if( RUNOK != runResult ) /*HTTP_CONTEXTID去激活失败*/ //直接返回错误 // { // return RUNERR ; // } return RUNOK ; } runResult = ActivePDP(psFtp->contextId, ftpLocalIp) ; if( RUNOK == runResult ) { AppLogPrintf("FTP本地IP:%s", ftpLocalIp) ; } portFree(ftpLocalIp) ; return(runResult) ; } /************************************************************************************************** * 名 称: Ftp_Login( FtpP_s *psFtp ) * 功能说明: 登录FTP服务器 * 入口参数: * @param1 *psFtp FtpP_s结构体变量指针 * 出口参数: * @param1 runResult RunResult枚举类型变量,返回函数运行结果 **************************************************************************************************/ RunResult Ftp_Login(FtpP_s *psFtp) { RunResult runResult = TIMEOUT ; runResult = EC20_SendFtpCmd(FTPLOGIN, NULL, psFtp->ftpServerIP, psFtp->ftpServerPort ) ; if( runResult != RUNOK ) return runResult ; int checkCsTimes = 10 ; //10*2S等待FTP进入idle状态 while( checkCsTimes-- ) { Wait_For_Nms(30) ; runResult = EC20_SendFtpCmd(GETLOGINSTATE, NULL ) ; if( RUNOK == runResult ) { if( (sFtpCmd[GETLOGINSTATE].trueOffset > 0)&& (ec20AtBuf[sFtpCmd[GETLOGINSTATE].trueOffset+13] == 0x31)) { runResult =RUNOK ; break; } else { runResult =RUNERR ; } } } return (runResult) ; } /************************************************************************************************** * 名 称: Ftp_Logout( FtpP_s *psFtp ) * 功能说明: 登出FTP服务器 * 入口参数: * @param1 *psFtp FtpP_s结构体变量指针 * 出口参数: * @param1 runResult RunResult枚举类型变量,返回函数运行结果 **************************************************************************************************/ RunResult Ftp_Logout(FtpP_s *psFtp) { RunResult runResult = TIMEOUT ; runResult = EC20_SendFtpCmd(FTPLOGOUT, NULL ) ; return runResult ; } /************************************************************************************************** * 名 称: Ftp_Set_Dir( FtpP_s *psFtp ) * 功能说明: 设置Ftp操作目录 * 入口参数: * @param1 *psFtp FtpP_s结构体变量指针 * 出口参数: * @param1 runResult RunResult枚举类型变量,返回函数运行结果 **************************************************************************************************/ RunResult Ftp_Set_Dir( FtpP_s *psFtp ) { RunResult runResult = TIMEOUT ; runResult = EC20_SendFtpCmd(SETDIRECTORY, NULL, psFtp->ftpDirectory ) ; //设置用户目录 if( runResult != RUNOK ) return runResult ; runResult = EC20_SendFtpCmd(GETDIRECTORY, NULL ) ; if( RUNOK == runResult ) { if( (sFtpCmd[GETDIRECTORY].trueOffset > 0)&& (kmp(ec20AtBuf, (const char*)psFtp->ftpDirectory) > 0)) //确认查询到的目录为用户设置的目录 { runResult =RUNOK ; } else { runResult =RUNERR ; } } return (runResult) ; } /************************************************************************************************** * 名 称: RunResult Ftp_Find_File( uint8_t *dir, uint8_t *fileName ) * 功能说明: 在dir目录中查找名为fileName的文件 * 入口参数: * @param1 *dir 查找的目录 * @param2 *fileName 所查找的文件文件名 * 出口参数: * @param1 RUNOK 在目录中能找到文件 * @param2 RUNTIMEOUT 在目录中不能找到文件 * @param3 RUNERR 列出dir目录中文件名出错 * 说 明: ec20AtBuf[EC20_ATBUF_LEN] 256字节, 所以当dir中文件名总字节数大于(EC20_ATBUF_LEN-50)时也会出现查找失败。 **************************************************************************************************/ RunResult Ftp_Find_File( uint8_t *dir, uint8_t *fileName ) { if( RUNOK != EC20_SendFtpCmd(LISTFILENAME, NULL, dir )) return RUNERR ; if( kmp(ec20AtBuf, (const char*)fileName) > 0 ) return RUNOK ; else return TIMEOUT ; } /************************************************************************************************** * 名 称: u32 Ftp_Get_FileSize( uint8_t *fileName ) * 功能说明: 查询*fileName 文件的大小 * 入口参数: * @param1 *fileName 索要查询文件大小的文件名 * 出口参数: * @param1 u32 返回文件大小(bytes) **************************************************************************************************/ u32 Ftp_Get_FileSize( uint8_t *fileName ) { RunResult runResult = TIMEOUT ; u32 fileSize = 0 ; uint8_t fileSizeBuf[7] = {0} ; runResult = EC20_SendFtpCmd(GETFILESIZE, NULL, fileName ) ; //设置用户目录 if( runResult != RUNOK ) return 0 ; CopyValues(fileSizeBuf, (uint8_t*)&ec20AtBuf[ sFtpCmd[GETFILESIZE].trueOffset+13], 0x0D, 6) ; fileSize = atoi((const char*)fileSizeBuf) ; return (fileSize) ; } /************************************************************************************************** * 名 称: RunResult Ftp_File_Rename( uint8_t *oldName, uint8_t *newName ) * 功能说明: 文件重命名 * 入口参数: * @param1 *oldName 旧文件名 * @param2 *newName 新文件名 * 出口参数: * @param1 runResult RunResult枚举类型数据 **************************************************************************************************/ RunResult Ftp_File_Rename( uint8_t *oldName, uint8_t *newName ) { RunResult runResult = TIMEOUT ; runResult = EC20_SendFtpCmd(RENAMEFILE, NULL, oldName, newName ) ; // return (runResult) ; } /************************************************************************************************** * 名 称: int Ftp_Down_File( uint8_t *fileName, uint32_t startPos, uint16_t transLen) * 功能说明: 从名为fileName的文件startPos位置开始下载transLen长度的数据 * 入口参数: * @param1 *fileName 下载的文件文件名 * @param2 startPos 开始下载的位置 * @param3 transLen 下载的字节数 * 出口参数: * @param1 headPos 如果校验正确的话返回"CONNECT"在ec20FtpBuf内的偏移地址, 错误返回-1 * 注 意:下载文件时,EC20串口模式切换到FTP_MODE,届时接收数据将会存储在ec20FtpBuf缓冲区中 * ec20FtpBuf数据校验方法是判断CONNECT和+QFTPGET: 0,2048之间的字节数是否为我们请求的字节数, * “CONNECT 0x0D 0x0A 数据 0x0D 0x0A OK 0x0D 0x0A 0x0D 0x0A +QFTPGET: 0,2048” **************************************************************************************************/ int Ftp_Down_File( uint8_t *fileName, uint32_t startPos, uint16_t transLen) { int headPos = -1 ; uint8_t revTimes = 0 ; ftpDataMode = true ; //EC20串口接收模式切换到FTP模式 Ec20FtpBufReset() ; //FTP接收缓冲区初始化 UARTx_SendString(EC20_UART, (uint8_t *)sFtpCmd[DOWNFILE].cmdStr, fileName, startPos, transLen ) ; while( revTimes++ < sFtpCmd[DOWNFILE].timeout*2 ) { Wait_For_Nms(15) ; headPos = kmp(ec20FtpBuf, "CONNECT")+9 ; //下载的文件数据开始的地方相对用户数据开始处 在ec20FtpBuf中的偏移量 sFtpCmd[DOWNFILE].trueOffset = kmp(&ec20FtpBuf[transLen+headPos], sFtpCmd[DOWNFILE].trueStr) ; //+QFTPGET: 0,2048在相对用户数据结尾处 在ec20FtpBuf中的偏移量 if( sFtpCmd[DOWNFILE].trueOffset == 0) { if( headPos >= 0 ) //CONNECT和+QFTPGET: 0,2048之间的字节数 是否正常 { break ; } else { headPos = -1 ; break ; } } else if( kmp(ec20FtpBuf, sFtpCmd[DOWNFILE].falseStr) >= 0) { headPos = -1 ; break ; } } ftpDataMode = false ; //EC20串口接收模式切出FTP模式 return (headPos) ; } /************************************************************************************************** * 名 称: RunResult EC20_SendFtpCmd( uint8_t cmdNum, char *format,... ) * 功能说明: MCU串口向EC20发送Tcp相关命令 * 入口参数: * @param1 cmdNum EC20_CMD_DATA_s中cmdNum成员命令编号 * @param2 char *format,... 可变参变量 * 出口参数: * @param1 status RunResult枚举类型变量,返回函数运行结果 **************************************************************************************************/ RunResult EC20_SendFtpCmd( uint8_t cmdNum, char *format,... ) { uint8_t revTimes = 0 ; RunResult status = TIMEOUT ; uint8_t retryTimes = sFtpCmd[cmdNum].rtyNum ; char *cmdPack = NULL ; format = sFtpCmd[cmdNum].cmdStr ; cmdPack = portMalloc(FTP_CMDPACK_LEN*sizeof(uint8_t)) ; if( cmdPack == NULL ) { ErrorLogPrintf("EC20 FtpCmdPack 内存分配失败!") ; portFree(cmdPack) ; return RUNERR ; } va_list ap; va_start (ap, format); int outLen = vsnprintf(cmdPack, FTP_CMDPACK_LEN, (const char*)format, ap); //vsprintf (temp, cmd, ap); //到此为止,所有的参数情况已经汇总到temp了 if((outLen<=0)||( outLen > FTP_CMDPACK_LEN)) { ErrorLogPrintf("FTP cmdPack 溢出!--增加FTP_CMDPACK_LEN数值。") ; status = RUNERR ; goto tcpCmdOut ; } while(retryTimes--) { Ec20AtBufReset() ; revTimes = 0 ; UARTx_SendData(EC20_UART, cmdPack, outLen ) ; //DMA发送 while( revTimes++ < sFtpCmd[cmdNum].timeout ) { Wait_For_Nms(100) ; sFtpCmd[cmdNum].trueOffset = kmp(ec20AtBuf, sFtpCmd[cmdNum].trueStr) ; if( sFtpCmd[cmdNum].trueOffset >= 0) { status = RUNOK ; goto tcpCmdOut ; } else if( kmp(ec20AtBuf, sFtpCmd[cmdNum].falseStr) >= 0) { status = RUNERR ; goto tcpCmdOut ; } } Wait_For_Nms( 1000 ) ; } tcpCmdOut:portFree(cmdPack) ; va_end(ap) ; DebugLogPrintf("%s %s\r\n[%s]", FtpCmdNumToString((enum eFtpCmdNum)cmdNum), RunResultToString(status), ec20AtBuf ) ; return (status) ; }