#include "air720u.h" #include "../swapi/subjects/serial/serial.h" // 模块名称 static const char MODULE_NAME[] = "Air720U"; #define MAX_CMD_CHARS 65535 // 单条指令最大字符数 #define MAX_ACK_CHARS 65535 // 指令应答最大字符数 #define WAIT_ACK_TIMEOUT 9000 // 等待应答超时时间ms #define R_SILENT_TIMEOUT 50 // 应答静默超时时间ms // 关闭命令回显模式 static const char *AT_ECHO_OFF = "ATE0" ; // 获取4G模块的型号 static const char *AT_GET_CGMM = "AT+CGMM" ; // 获取模块的IMEI号 static const char *AT_GET_IMEI = "AT+CGSN" ; // 查看网络注册状态 static const char *AT_GET_CREG = "AT+CREG?"; // 获取当前信号强度 static const char *AT_GET_CSQ = "AT+CSQ" ; // 获取SIM-PIN状态 static const char *AT_GET_PINSTA = "AT+CPIN?"; // 获取SIM-ICCID号 static const char *AT_GET_ICCID = "AT+ICCID"; // AT指令请求和应答 typedef struct { // 指令 struct { char data[MAX_CMD_CHARS+1]; // 指令数据(字符串) int len; // 指令长度 } cmd; // 等待执行结束的超时ms int timeout; // 等待执行结束的信号量 void *hEndSgl; // 应答 struct { int ret; // 返回值: >0时执行成功, 表明应答数据的长度; <0时出错(-1超时) char data[MAX_ACK_CHARS+1]; // 应答数据(字符串) } ack; } SATCmdTrans; // 与模块的串口通讯 typedef struct { void *h; // 打开的串口句柄 void *do_cmd_mutex; // 指令执行互斥量, 同一时刻正在执行的指令只能有一条 SATCmdTrans *pTrans; // 执行的指令及其应答结果: 空闲时为空, 工作时不为空 } SAir720UCom; static SAir720UCom s_myCom; // 定义AT指令格式符 #define CR '\r' // 回车 #define LF '\n' // 换行 #define CRLF "\r\n" // 回车换行 #define ATOK "\r\nOK\r\n" // 应答成功 #define ATER "ERROR" // 应答失败 // 执行一条指令, 返回: 大于0成功; 小于0失败(-1命令超时) static int comio_doCmd(SATCmdTrans *pTrans/*要执行的指令(IN&OUT)*/, int timeout/*等待应答的超时时间, 单位是毫秒*/) { SAir720UCom *pComIO = &s_myCom; const char *log_prefix = serial_get_log_prefix(pComIO->h); unsigned long stime, wtime, etime; char *pAtCmdStr; int atCmdLen, ret; xgettickcount(&stime); ret = sw_mutex_lock(pComIO->do_cmd_mutex, timeout); if(ret != 0) { pTrans->ack.ret = -1; goto end_p; } // 互斥量上锁超时 xgettickcount(&etime); wtime = etime - stime; pTrans->hEndSgl = sw_signal_create(); if(!pTrans->hEndSgl) { pTrans->ack.ret = -2; goto end_p; } pAtCmdStr = (char *)sw_heap_malloc(pTrans->cmd.len+2); if(!pAtCmdStr) { pTrans->ack.ret = -3; goto end_p; } memcpy(pAtCmdStr, pTrans->cmd.data, pTrans->cmd.len); pAtCmdStr[pTrans->cmd.len] = CR; pAtCmdStr[pTrans->cmd.len+1] = '\0'; atCmdLen = strlen((const char *)pAtCmdStr); serial_recvThrd_pause(pComIO->h); sw_thrd_delay(THRDWAIT_DELAY); // "pComIO->pTrans"的控制权转移前, 先暂停串口数据的接收 ret = serial_send_data(pComIO->h, (unsigned char *)pAtCmdStr, atCmdLen); // 发送一条指令 sw_heap_free(pAtCmdStr); // 释放AT指令字符串内存 if(ret == atCmdLen) { sw_log_trace("[%s] %s send a cmd(%d bytes): %s", MODULE_NAME, log_prefix, pTrans->cmd.len, pTrans->cmd.data); } else { sw_log_error("[%s] %s failed to send a cmd(%d bytes, ret=%d): %s!!", MODULE_NAME, log_prefix, pTrans->cmd.len, ret, pTrans->cmd.data); serial_recvThrd_resume(pComIO->h); pTrans->ack.ret = -4; goto end_p; } pTrans->timeout = (wtime > timeout ? 0 : (timeout - wtime)); pComIO->pTrans = pTrans; serial_recvThrd_resume(pComIO->h); // 恢复串口数据接收, "pComIO->pTrans"的控制权转移给接收线程. sw_signal_wait(pTrans->hEndSgl, WAIT_FOREVER); // 等待指令执行结束, "pComIO->pTrans"的控制权收回 end_p: if(pTrans->hEndSgl) sw_signal_destroy(pTrans->hEndSgl); pComIO->pTrans = NULL; sw_mutex_unlock(pComIO->do_cmd_mutex); return pTrans->ack.ret; } // 接收、处理来自Air720U模块的数据报文帧, 串口-线程回调 static int comio_data_recv_proc(unsigned long wParam/*传递打开的串口句柄*/, unsigned long lParam/*保留暂未使用*/) { SAir720UCom *pComIO = &s_myCom; void *pSerial = pComIO->h; SATCmdTrans *pTrans = pComIO->pTrans; const unsigned char *pRecvBuf = serial_get_recv_buffer(pSerial); int nRecvBytes = serial_get_recv_buffer_bytes(pSerial); const char *log_prefix = serial_get_log_prefix(pSerial); bool bPrintRecvBuf = true; static unsigned long enteredWaitAckTime = 0, now; static unsigned long enteredSilentTime = 0; static int nSilentRecvBytes = 0; if(!pTrans) { goto ret_p2; } // 无主动指令时 // 1, 计算命令应答超时 if(enteredWaitAckTime == 0) xgettickcount(&enteredWaitAckTime); xgettickcount(&now); // 2, 分析应答回复内容 if(strstr((const char *)pRecvBuf, ATOK) != NULL || strstr((const char *)pRecvBuf, ATER) != NULL) { // 已应答 if(nRecvBytes > nSilentRecvBytes) { enteredSilentTime = now; nSilentRecvBytes = nRecvBytes; } if((now-enteredSilentTime) < R_SILENT_TIMEOUT) { goto ret_p3; } // 等待静默期结束(延期多接收一会数据, 避免"内容"在"ATOK"或"ATER"之后) memcpy(pTrans->ack.data, pRecvBuf, nRecvBytes); pTrans->ack.ret = nRecvBytes; pTrans->ack.data[pTrans->ack.ret] = '\0'; bPrintRecvBuf = false; sw_log_trace("[%s] %s received a cmd ack(%d bytes): %s", MODULE_NAME, log_prefix, pTrans->ack.ret, pTrans->ack.data); goto ret_p1; } else if((now-enteredWaitAckTime) >= pTrans->timeout) { // 已超时 sw_log_error("[%s] %s timeout occurred while waiting for the \"%s\" command's ack!!", MODULE_NAME, log_prefix, pTrans->cmd.data); pTrans->ack.ret = -1; pTrans->ack.data[0] = '\0'; goto ret_p1; } else { // 继续收 goto ret_p3; } // 3, 缓存数据接收控制 ret_p1: // 点亮信号量, 表明本指令执行结束 sw_signal_give(pComIO->pTrans->hEndSgl); enteredWaitAckTime = 0; enteredSilentTime = 0; nSilentRecvBytes = 0; goto ret_p2; ret_p2: // 清接收缓存, 重新开始一轮新指令 if(nRecvBytes > 0 && bPrintRecvBuf) { serial_printf_recv_buffer(pSerial, LEVEL_TRACE); // 十六进制打印当前串口接收数据缓存 sw_log_warn("[%s] %s discarded %d bytes of meaningless data!", MODULE_NAME, log_prefix, nRecvBytes); } if(nRecvBytes > 0) serial_clear_recv_buffer(pSerial); goto ret_p3; ret_p3: return 1; // 持续累加的接收新数据 } // 打开与模块的通讯, 返回: 0成功, <0时失败 int Air720U_ComInit() { s_myCom.do_cmd_mutex = sw_mutex_create(); if(!s_myCom.do_cmd_mutex) { sw_log_error("[%s] error to create a mutex!!", MODULE_NAME); return -1; } s_myCom.pTrans = NULL; const char *serialName = "/dev/ttyUSB0"; int baudrate = 115200; const char *parityCheck = "none"; // 无校检 s_myCom.h = serial_open(serialName, baudrate, parityCheck, \ comio_data_recv_proc, comio_data_recv_proc, NULL); if(!s_myCom.h) { sw_log_error("[%s] failed to open the \"%s:%d(%s parity)\" device!!", \ MODULE_NAME, serialName, baudrate, parityCheck); Air720U_ComExit(); return -1; } SATCmdTrans trans; memset(&trans, 0, sizeof(SATCmdTrans)); strcpy(trans.cmd.data, AT_ECHO_OFF); trans.cmd.len = strlen(trans.cmd.data); int ret = comio_doCmd(&trans, WAIT_ACK_TIMEOUT); if(!(ret > 0 && strstr(trans.ack.data, ATOK))) { sw_log_error("[%s] failed to close echo mode!!", MODULE_NAME); Air720U_ComExit(); return -2+ret; } memset(&trans, 0, sizeof(SATCmdTrans)); strcpy(trans.cmd.data, AT_GET_CGMM); trans.cmd.len = strlen(trans.cmd.data); ret = comio_doCmd(&trans, WAIT_ACK_TIMEOUT); if(!(ret > 0 && strstr(trans.ack.data, ATOK) && xstrcasestr(trans.ack.data, "left", "Air72"))) { if(ret < 0) sw_log_error("[%s] failed to get module model(%d)!!", MODULE_NAME, ret); Air720U_ComExit(); return -99; } return 0; } // 关闭与模块的通讯, 返回: 0成功, <0时失败 int Air720U_ComExit() { if(s_myCom.h) serial_close(s_myCom.h, WAITTHRD_SAFEEXIT_TIMEOUT); if(s_myCom.do_cmd_mutex) sw_mutex_destroy(s_myCom.do_cmd_mutex); memset(&s_myCom, 0, sizeof(s_myCom)); return 0; } // 改写sysfs虚拟文件, 返回: 0成功, <0失败 static int sysfs_write(const char *path, const char *value) { int fd = open(path, O_WRONLY); if (fd < 0) return -1; ssize_t len = write(fd, value, strlen(value)); if(len != strlen(value)) { close(fd); return -2; } close(fd); return 0; } // 控制模块断电重启, 返回: 0成功, <0时失败 // 138脚 -- gpio4.IO[20] (4-1)*32+20 = 116 -- 4G_POWER int Air720U_Repower() { sysfs_write("/sys/class/gpio/unexport", "116"); // 撤销原导出 if(sysfs_write("/sys/class/gpio/export", "116") < 0) return -2; // 重设新导出 if(sysfs_write("/sys/class/gpio/gpio116/direction", "out") < 0) return -3; // 设置为输出 if(sysfs_write("/sys/class/gpio/gpio116/value", "1") < 0) return -4; // 输出高电平-断电 sw_thrd_delay(5000); // 等一段时间-毫秒 if(sysfs_write("/sys/class/gpio/gpio116/value", "0") < 0) return -5; // 输出低电平-上电 return 0; } // 获取模块的标识号, 返回: =15成功, <0失败(-1命令超时) int Air720U_GetIMEI(char buf[16]/*固定长15个十进制数字组成*/) { SATCmdTrans trans; memset(&trans, 0, sizeof(SATCmdTrans)); strcpy(trans.cmd.data, AT_GET_IMEI); trans.cmd.len = strlen(trans.cmd.data); int ret = comio_doCmd(&trans, WAIT_ACK_TIMEOUT); if(ret < 0) return ret; // 执行超时或出错 else if(strstr(trans.ack.data, ATOK) == NULL) goto end_p; // 未收到成功应答 const char *p = trans.ack.data, *pLineS, *pLineE; int lineLen, crlfLen = strlen(CRLF); lsa_p: // 行扫描, 逐行分析应答结果 if((*p) == '\0') goto end_p; pLineS = strstr(p, CRLF); pLineE = NULL; lineLen = 0; if(pLineS) { pLineS += crlfLen; pLineE = strstr(pLineS, CRLF); } if(pLineE) { lineLen = pLineE - pLineS; } if(lineLen == 0) { // 连续两个CRLF, 上面的逻辑保证了"lineLen"只可能大于或等于0, 不会是小于0 if(pLineE) { p = pLineE; goto lsa_p; } // 跳第一个CRLF, 继续分析下一行 else goto end_p; } //// 示例: "359759002514931" if(lineLen == 15) { bool isHex = true; for(int i = 0; i < lineLen; i++) if(!xisxdigit(pLineS[i])) { isHex = false; break; } if(isHex) { memcpy(buf, pLineS, lineLen); buf[lineLen] = '\0'; return lineLen; } // 成功读到模块的IMEI号 } p = pLineE + crlfLen; goto lsa_p; // 继续分析下一行 end_p: return -5; } // 获取网络注册状态, 返回: >=0成功, <0失败(-1命令超时) int Air720U_GetCregState() { SATCmdTrans trans; memset(&trans, 0, sizeof(SATCmdTrans)); strcpy(trans.cmd.data, AT_GET_CREG); trans.cmd.len = strlen(trans.cmd.data); int ret = comio_doCmd(&trans, WAIT_ACK_TIMEOUT); if(ret < 0) return ret; // 执行超时或出错 else if(strstr(trans.ack.data, ATOK) == NULL) goto end_p; // 未收到成功应答 const char *p = trans.ack.data, *pLineS, *pLineE; int lineLen, crlfLen = strlen(CRLF); const char *p1, *p2; char statBuf[3]; int statLen; lsa_p: // 行扫描, 逐行分析应答结果 if((*p) == '\0') goto end_p; pLineS = strstr(p, CRLF); pLineE = NULL; lineLen = 0; if(pLineS) { pLineS += crlfLen; pLineE = strstr(pLineS, CRLF); } if(pLineE) { lineLen = pLineE - pLineS; } if(lineLen == 0) { // 连续两个CRLF, 上面的逻辑保证了"lineLen"只可能大于或等于0, 不会是小于0 if(pLineE) { p = pLineE; goto lsa_p; } // 跳第一个CRLF, 继续分析下一行 else goto end_p; } //// 示例: "+CREG: ," //// 说明: 注册状态报告模式, 当前网络注册状态 p1 = strstr(pLineS, "+CREG:"); p2 = NULL; if(p1 && p1 < pLineE) { p1 += 6; p2 = strchr(p1, ','); } if(p2 && p2 < pLineE) { p2++; while(*p2 == 0x20 && p2 < pLineE) { p2++; } // 跳过空格 statLen = (pLineE - p2); if(statLen != 1 && statLen != 2) return -5; // 状态值范围: 1或2个数字 memcpy(statBuf, p2, statLen); statBuf[statLen] = '\0'; bool isDigit = true; for(int i = 0; i < strlen(statBuf); i++) if(!xisdigit(statBuf[i])) { isDigit = false; break; } if(isDigit) return atoi(statBuf); // 成功读到模块的网络注册状态值 else return -6; } p = pLineE + crlfLen; goto lsa_p; // 继续分析下一行 end_p: return -7; } // 获取当前信号强度, 返回: >=0成功, <0失败(-1命令超时) int Air720U_GetRSSIFromCSQ() { SATCmdTrans trans; memset(&trans, 0, sizeof(SATCmdTrans)); strcpy(trans.cmd.data, AT_GET_CSQ); trans.cmd.len = strlen(trans.cmd.data); int ret = comio_doCmd(&trans, WAIT_ACK_TIMEOUT); if(ret < 0) return ret; // 执行超时或出错 else if(strstr(trans.ack.data, ATOK) == NULL) goto end_p; // 未收到成功应答 const char *p = trans.ack.data, *pLineS, *pLineE; int lineLen, crlfLen = strlen(CRLF); const char *p1, *p2; char rssiBuf[3]; int rssiLen; lsa_p: // 行扫描, 逐行分析应答结果 if((*p) == '\0') goto end_p; pLineS = strstr(p, CRLF); pLineE = NULL; lineLen = 0; if(pLineS) { pLineS += crlfLen; pLineE = strstr(pLineS, CRLF); } if(pLineE) { lineLen = pLineE - pLineS; } if(lineLen == 0) { // 连续两个CRLF, 上面的逻辑保证了"lineLen"只可能大于或等于0, 不会是小于0 if(pLineE) { p = pLineE; goto lsa_p; } // 跳第一个CRLF, 继续分析下一行 else goto end_p; } //// 示例: "+CSQ: 15,99" //// 说明: 逗号前第一个数值是RSSI的值 p1 = strstr(pLineS, "+CSQ:"); p2 = NULL; if(p1 && p1 < pLineE) { p1 += 5; p2 = strchr(p1, ','); } if(p2 && p2 < pLineE) { while(*p1 == 0x20 && p1 < p2) { p1++; } // 跳过空格 rssiLen = (p2 - p1); if(rssiLen != 1 && rssiLen != 2) return -5; // 强度值范围: 0-99, 1或2个数字 memcpy(rssiBuf, p1, rssiLen); rssiBuf[rssiLen] = '\0'; bool isDigit = true; for(int i = 0; i < strlen(rssiBuf); i++) if(!xisdigit(rssiBuf[i])) { isDigit = false; break; } if(isDigit) return atoi(rssiBuf); // 成功读到模块的接收信号强度值 else return -6; } p = pLineE + crlfLen; goto lsa_p; // 继续分析下一行 end_p: return -7; } // 返回电话卡的状态, 返回: 1已可用, <0失败(-1命令超时) int Air720U_IsSimCardReady() { SATCmdTrans trans; memset(&trans, 0, sizeof(SATCmdTrans)); strcpy(trans.cmd.data, AT_GET_PINSTA); trans.cmd.len = strlen(trans.cmd.data); int ret = comio_doCmd(&trans, WAIT_ACK_TIMEOUT); if(ret < 0) return ret; // 执行超时或出错 else if(strstr(trans.ack.data, ATOK) && strstr(trans.ack.data, "+CPIN: READY")) return 1; else return -5; } // 获取电话卡标识号, 返回: =20成功, <0失败(-1命令超时) int Air720U_GetSimICCID(char buf[21]/*一般由20个十进制数字组成*/) { SATCmdTrans trans; memset(&trans, 0, sizeof(SATCmdTrans)); strcpy(trans.cmd.data, AT_GET_ICCID); trans.cmd.len = strlen(trans.cmd.data); int ret = comio_doCmd(&trans, WAIT_ACK_TIMEOUT); if(ret < 0) return ret; // 执行超时或出错 else if(strstr(trans.ack.data, ATOK) == NULL) goto end_p; // 未收到成功应答 const char *p = trans.ack.data, *pLineS, *pLineE; int lineLen, crlfLen = strlen(CRLF); const char *p1, *p2; int iccidLen; lsa_p: // 行扫描, 逐行分析应答结果 if((*p) == '\0') goto end_p; pLineS = strstr(p, CRLF); pLineE = NULL; lineLen = 0; if(pLineS) { pLineS += crlfLen; pLineE = strstr(pLineS, CRLF); } if(pLineE) { lineLen = pLineE - pLineS; } if(lineLen == 0) { // 连续两个CRLF, 上面的逻辑保证了"lineLen"只可能大于或等于0, 不会是小于0 if(pLineE) { p = pLineE; goto lsa_p; } // 跳第一个CRLF, 继续分析下一行 else goto end_p; } //// 示例: "+ICCID: 89860117831003134201" p1 = strstr(pLineS, "+ICCID:"); p2 = NULL; if(p1 && p1 < pLineE) { p1 += 7; p2 = pLineE; } if(p2) { while(*p1 == 0x20 && p1 < p2) { p1++; } // 跳过空格 iccidLen = (p2 - p1); if(iccidLen != 20) return -5; // 长度不对 bool isHex = true; for(int i = 0; i < iccidLen; i++) if(!xisxdigit(p1[i])) { isHex = false; break; } if(isHex) { memcpy(buf, p1, iccidLen); buf[iccidLen] = '\0'; return iccidLen; } // 成功读到SIM卡的ICCID号 else return -6; } p = pLineE + crlfLen; goto lsa_p; // 继续分析下一行 end_p: return -7; }