ec200u.c 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425
  1. #include "ec200u.h"
  2. #include "../swapi/subjects/serial/serial.h"
  3. // 模块名称
  4. static const char MODULE_NAME[] = "EC200U";
  5. #define MAX_CMD_CHARS 65535 // 单条指令最大字符数
  6. #define MAX_ACK_CHARS 65535 // 指令应答最大字符数
  7. #define WAIT_ACK_TIMEOUT 9000 // 等待应答超时时间ms
  8. #define R_SILENT_TIMEOUT 50 // 应答静默超时时间ms
  9. // 关闭命令回显模式
  10. static const char *AT_ECHO_OFF = "ATE0" ;
  11. // 获取4G模块的型号
  12. static const char *AT_GET_CGMM = "AT+CGMM" ;
  13. // 获取模块的IMEI号
  14. static const char *AT_GET_IMEI = "AT+CGSN" ;
  15. // 查看网络注册状态
  16. static const char *AT_GET_CREG = "AT+CREG?";
  17. // 获取当前信号强度
  18. static const char *AT_GET_CSQ = "AT+CSQ" ;
  19. // 获取SIM-PIN状态
  20. static const char *AT_GET_PINSTA = "AT+CPIN?";
  21. // 获取SIM-ICCID号
  22. static const char *AT_GET_ICCID = "AT+QCCID";
  23. // 激活USB网络模式
  24. static const char *AT_QCFG_CMD1 = "AT+QCFG=\"usbnet\",1";
  25. static const char *AT_QCFG_CMD2 = "AT+qnetdevctl=1,1,1" ;
  26. // AT指令请求和应答
  27. typedef struct
  28. {
  29. // 指令
  30. struct
  31. {
  32. char data[MAX_CMD_CHARS+1]; // 指令数据(字符串)
  33. int len; // 指令长度
  34. } cmd;
  35. // 等待执行结束的超时ms
  36. int timeout;
  37. // 等待执行结束的信号量
  38. void *hEndSgl;
  39. // 应答
  40. struct
  41. {
  42. int ret; // 返回值: >0时执行成功, 表明应答数据的长度; <0时出错(-1超时)
  43. char data[MAX_ACK_CHARS+1]; // 应答数据(字符串)
  44. } ack;
  45. } SATCmdTrans;
  46. // 与模块的串口通讯
  47. typedef struct
  48. {
  49. void *h; // 打开的串口句柄
  50. void *do_cmd_mutex; // 指令执行互斥量, 同一时刻正在执行的指令只能有一条
  51. SATCmdTrans *pTrans; // 执行的指令及其应答结果: 空闲时为空, 工作时不为空
  52. } SEC200UCom;
  53. static SEC200UCom s_myCom;
  54. // 定义AT指令格式符
  55. #define CR '\r' // 回车
  56. #define LF '\n' // 换行
  57. #define CRLF "\r\n" // 回车换行
  58. #define ATOK "\r\nOK\r\n" // 应答成功
  59. #define ATER "ERROR" // 应答失败
  60. // 执行一条指令, 返回: 大于0成功; 小于0失败(-1命令超时)
  61. static int comio_doCmd(SATCmdTrans *pTrans/*要执行的指令(IN&OUT)*/, int timeout/*等待应答的超时时间, 单位是毫秒*/)
  62. {
  63. SEC200UCom *pComIO = &s_myCom; const char *log_prefix = serial_get_log_prefix(pComIO->h);
  64. unsigned long stime, wtime, etime; char *pAtCmdStr; int atCmdLen, ret;
  65. xgettickcount(&stime);
  66. ret = sw_mutex_lock(pComIO->do_cmd_mutex, timeout);
  67. if(ret != 0) { pTrans->ack.ret = -1; goto end_p; } // 互斥量上锁超时
  68. xgettickcount(&etime); wtime = etime - stime;
  69. pTrans->hEndSgl = sw_signal_create();
  70. if(!pTrans->hEndSgl) { pTrans->ack.ret = -2; goto end_p; }
  71. pAtCmdStr = (char *)sw_heap_malloc(pTrans->cmd.len+2);
  72. if(!pAtCmdStr) { pTrans->ack.ret = -3; goto end_p; }
  73. memcpy(pAtCmdStr, pTrans->cmd.data, pTrans->cmd.len);
  74. pAtCmdStr[pTrans->cmd.len] = CR; pAtCmdStr[pTrans->cmd.len+1] = '\0';
  75. atCmdLen = strlen((const char *)pAtCmdStr);
  76. serial_recvThrd_pause(pComIO->h); sw_thrd_delay(THRDWAIT_DELAY); // "pComIO->pTrans"的控制权转移前, 先暂停串口数据的接收
  77. ret = serial_send_data(pComIO->h, (unsigned char *)pAtCmdStr, atCmdLen); // 发送一条指令
  78. sw_heap_free(pAtCmdStr); // 释放AT指令字符串内存
  79. if(ret == atCmdLen)
  80. {
  81. sw_log_trace("[%s] %s send a cmd(%d bytes): %s", MODULE_NAME, log_prefix, pTrans->cmd.len, pTrans->cmd.data);
  82. }
  83. else
  84. {
  85. 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);
  86. serial_recvThrd_resume(pComIO->h); pTrans->ack.ret = -4; goto end_p;
  87. }
  88. pTrans->timeout = (wtime > timeout ? 0 : (timeout - wtime));
  89. pComIO->pTrans = pTrans; serial_recvThrd_resume(pComIO->h); // 恢复串口数据接收, "pComIO->pTrans"的控制权转移给接收线程.
  90. sw_signal_wait(pTrans->hEndSgl, WAIT_FOREVER); // 等待指令执行结束, "pComIO->pTrans"的控制权收回
  91. end_p:
  92. if(pTrans->hEndSgl) sw_signal_destroy(pTrans->hEndSgl);
  93. pComIO->pTrans = NULL;
  94. sw_mutex_unlock(pComIO->do_cmd_mutex);
  95. return pTrans->ack.ret;
  96. }
  97. // 接收、处理来自EC200U模块的数据报文帧, 串口-线程回调
  98. static int comio_data_recv_proc(unsigned long wParam/*传递打开的串口句柄*/, unsigned long lParam/*保留暂未使用*/)
  99. {
  100. SEC200UCom *pComIO = &s_myCom; void *pSerial = pComIO->h; SATCmdTrans *pTrans = pComIO->pTrans;
  101. const unsigned char *pRecvBuf = serial_get_recv_buffer(pSerial); int nRecvBytes = serial_get_recv_buffer_bytes(pSerial);
  102. const char *log_prefix = serial_get_log_prefix(pSerial); bool bPrintRecvBuf = true;
  103. static unsigned long enteredWaitAckTime = 0, now; static unsigned long enteredSilentTime = 0; static int nSilentRecvBytes = 0;
  104. if(!pTrans) { goto ret_p2; } // 无主动指令时
  105. // 1, 计算命令应答超时
  106. if(enteredWaitAckTime == 0) xgettickcount(&enteredWaitAckTime);
  107. xgettickcount(&now);
  108. // 2, 分析应答回复内容
  109. if(strstr((const char *)pRecvBuf, ATOK) != NULL || strstr((const char *)pRecvBuf, ATER) != NULL)
  110. { // 已应答
  111. if(nRecvBytes > nSilentRecvBytes) { enteredSilentTime = now; nSilentRecvBytes = nRecvBytes; }
  112. if((now-enteredSilentTime) < R_SILENT_TIMEOUT) { goto ret_p3; } // 等待静默期结束(延期多接收一会数据, 避免"内容"在"ATOK"或"ATER"之后)
  113. memcpy(pTrans->ack.data, pRecvBuf, nRecvBytes);
  114. pTrans->ack.ret = nRecvBytes; pTrans->ack.data[pTrans->ack.ret] = '\0'; bPrintRecvBuf = false;
  115. sw_log_trace("[%s] %s received a cmd ack(%d bytes): %s", MODULE_NAME, log_prefix, pTrans->ack.ret, pTrans->ack.data);
  116. goto ret_p1;
  117. }
  118. else if((now-enteredWaitAckTime) >= pTrans->timeout)
  119. { // 已超时
  120. sw_log_error("[%s] %s timeout occurred while waiting for the \"%s\" command's ack!!", MODULE_NAME, log_prefix, pTrans->cmd.data);
  121. pTrans->ack.ret = -1; pTrans->ack.data[0] = '\0';
  122. goto ret_p1;
  123. }
  124. else
  125. { // 继续收
  126. goto ret_p3;
  127. }
  128. // 3, 缓存数据接收控制
  129. ret_p1: // 点亮信号量, 表明本指令执行结束
  130. sw_signal_give(pComIO->pTrans->hEndSgl); enteredWaitAckTime = 0; enteredSilentTime = 0; nSilentRecvBytes = 0;
  131. goto ret_p2;
  132. ret_p2: // 清接收缓存, 重新开始一轮新指令
  133. if(nRecvBytes > 0 && bPrintRecvBuf)
  134. {
  135. serial_printf_recv_buffer(pSerial, LEVEL_TRACE); // 十六进制打印当前串口接收数据缓存
  136. sw_log_warn("[%s] %s discarded %d bytes of meaningless data!", MODULE_NAME, log_prefix, nRecvBytes);
  137. }
  138. if(nRecvBytes > 0) serial_clear_recv_buffer(pSerial);
  139. goto ret_p3;
  140. ret_p3: return 1; // 持续累加的接收新数据
  141. }
  142. // 打开与模块的通讯, 返回: 0成功, <0时失败
  143. int EC200U_ComInit()
  144. {
  145. s_myCom.do_cmd_mutex = sw_mutex_create();
  146. if(!s_myCom.do_cmd_mutex) { sw_log_error("[%s] error to create a mutex!!", MODULE_NAME); return -1; }
  147. s_myCom.pTrans = NULL;
  148. const char *serialName = "/dev/ttyUSB0"; int baudrate = 115200;
  149. const char *parityCheck = "none"; // 无校检
  150. s_myCom.h = serial_open(serialName, baudrate, parityCheck, \
  151. comio_data_recv_proc, comio_data_recv_proc, NULL);
  152. if(!s_myCom.h)
  153. {
  154. sw_log_error("[%s] failed to open the \"%s:%d(%s parity)\" device!!", \
  155. MODULE_NAME, serialName, baudrate, parityCheck);
  156. EC200U_ComExit(); return -1;
  157. }
  158. SATCmdTrans trans; memset(&trans, 0, sizeof(SATCmdTrans));
  159. strcpy(trans.cmd.data, AT_ECHO_OFF); trans.cmd.len = strlen(trans.cmd.data);
  160. int ret = comio_doCmd(&trans, WAIT_ACK_TIMEOUT);
  161. if(!(ret > 0 && strstr(trans.ack.data, ATOK)))
  162. {
  163. sw_log_error("[%s] failed to close echo mode!!", MODULE_NAME);
  164. EC200U_ComExit(); return -2+ret;
  165. }
  166. memset(&trans, 0, sizeof(SATCmdTrans));
  167. strcpy(trans.cmd.data, AT_GET_CGMM); trans.cmd.len = strlen(trans.cmd.data);
  168. ret = comio_doCmd(&trans, WAIT_ACK_TIMEOUT);
  169. if(!(ret > 0 && strstr(trans.ack.data, ATOK) && xstrcasestr(trans.ack.data, "left", MODULE_NAME)))
  170. {
  171. if(ret < 0) sw_log_error("[%s] failed to get module model!!", MODULE_NAME);
  172. EC200U_ComExit(); return -99+ret;
  173. }
  174. memset(&trans, 0, sizeof(SATCmdTrans));
  175. strcpy(trans.cmd.data, AT_QCFG_CMD1); trans.cmd.len = strlen(trans.cmd.data);
  176. ret = comio_doCmd(&trans, WAIT_ACK_TIMEOUT);
  177. if(!(ret > 0 && strstr(trans.ack.data, ATOK)))
  178. {
  179. sw_log_error("[%s] failed to configure USB network mode!!", MODULE_NAME);
  180. EC200U_ComExit(); return -3+ret;
  181. }
  182. memset(&trans, 0, sizeof(SATCmdTrans));
  183. strcpy(trans.cmd.data, AT_QCFG_CMD2); trans.cmd.len = strlen(trans.cmd.data);
  184. ret = comio_doCmd(&trans, WAIT_ACK_TIMEOUT);
  185. if(!(ret > 0 && strstr(trans.ack.data, ATOK)))
  186. {
  187. sw_log_error("[%s] failed to activate USB network mode!!", MODULE_NAME);
  188. EC200U_ComExit(); return -4+ret;
  189. }
  190. return 0;
  191. }
  192. // 关闭与模块的通讯, 返回: 0成功, <0时失败
  193. int EC200U_ComExit()
  194. {
  195. if(s_myCom.h) serial_close(s_myCom.h, WAITTHRD_SAFEEXIT_TIMEOUT);
  196. if(s_myCom.do_cmd_mutex) sw_mutex_destroy(s_myCom.do_cmd_mutex);
  197. memset(&s_myCom, 0, sizeof(s_myCom));
  198. return 0;
  199. }
  200. // 改写sysfs虚拟文件, 返回: 0成功, <0失败
  201. static int sysfs_write(const char *path, const char *value)
  202. {
  203. int fd = open(path, O_WRONLY);
  204. if (fd < 0) return -1;
  205. ssize_t len = write(fd, value, strlen(value));
  206. if(len != strlen(value)) { close(fd); return -2; }
  207. close(fd); return 0;
  208. }
  209. // 控制模块断电重启, 返回: 0成功, <0时失败
  210. // 138脚 -- gpio4.IO[20] (4-1)*32+20 = 116 -- 4G_POWER
  211. int EC200U_Repower()
  212. {
  213. sysfs_write("/sys/class/gpio/unexport", "116"); // 撤销原导出
  214. if(sysfs_write("/sys/class/gpio/export", "116") < 0) return -2; // 重设新导出
  215. if(sysfs_write("/sys/class/gpio/gpio116/direction", "out") < 0) return -3; // 设置为输出
  216. if(sysfs_write("/sys/class/gpio/gpio116/value", "1") < 0) return -4; // 输出高电平-断电
  217. sw_thrd_delay(5000); // 等一段时间-毫秒
  218. if(sysfs_write("/sys/class/gpio/gpio116/value", "0") < 0) return -5; // 输出低电平-上电
  219. return 0;
  220. }
  221. // 获取模块的标识号, 返回: =15成功, <0失败(-1命令超时)
  222. int EC200U_GetIMEI(char buf[16]/*固定长15个十进制数字组成*/)
  223. {
  224. SATCmdTrans trans; memset(&trans, 0, sizeof(SATCmdTrans));
  225. strcpy(trans.cmd.data, AT_GET_IMEI);
  226. trans.cmd.len = strlen(trans.cmd.data);
  227. int ret = comio_doCmd(&trans, WAIT_ACK_TIMEOUT);
  228. if(ret < 0) return ret; // 执行超时或出错
  229. else if(strstr(trans.ack.data, ATOK) == NULL) goto end_p; // 未收到成功应答
  230. const char *p = trans.ack.data, *pLineS, *pLineE; int lineLen, crlfLen = strlen(CRLF);
  231. lsa_p: // 行扫描, 逐行分析应答结果
  232. if((*p) == '\0') goto end_p;
  233. pLineS = strstr(p, CRLF); pLineE = NULL; lineLen = 0;
  234. if(pLineS) { pLineS += crlfLen; pLineE = strstr(pLineS, CRLF); }
  235. if(pLineE) { lineLen = pLineE - pLineS; }
  236. if(lineLen == 0)
  237. { // 连续两个CRLF, 上面的逻辑保证了"lineLen"只可能大于或等于0, 不会是小于0
  238. if(pLineE) { p = pLineE; goto lsa_p; } // 跳第一个CRLF, 继续分析下一行
  239. else goto end_p;
  240. }
  241. //// 示例: "359759002514931"
  242. if(lineLen == 15)
  243. {
  244. bool isHex = true;
  245. for(int i = 0; i < lineLen; i++) if(!xisxdigit(pLineS[i])) { isHex = false; break; }
  246. if(isHex) { memcpy(buf, pLineS, lineLen); buf[lineLen] = '\0'; return lineLen; } // 成功读到模块的IMEI号
  247. }
  248. p = pLineE + crlfLen;
  249. goto lsa_p; // 继续分析下一行
  250. end_p:
  251. return -5;
  252. }
  253. // 获取网络注册状态, 返回: >=0成功, <0失败(-1命令超时)
  254. int EC200U_GetCregState()
  255. {
  256. SATCmdTrans trans; memset(&trans, 0, sizeof(SATCmdTrans));
  257. strcpy(trans.cmd.data, AT_GET_CREG);
  258. trans.cmd.len = strlen(trans.cmd.data);
  259. int ret = comio_doCmd(&trans, WAIT_ACK_TIMEOUT);
  260. if(ret < 0) return ret; // 执行超时或出错
  261. else if(strstr(trans.ack.data, ATOK) == NULL) goto end_p; // 未收到成功应答
  262. const char *p = trans.ack.data, *pLineS, *pLineE; int lineLen, crlfLen = strlen(CRLF);
  263. const char *p1, *p2; char statBuf[3]; int statLen;
  264. lsa_p: // 行扫描, 逐行分析应答结果
  265. if((*p) == '\0') goto end_p;
  266. pLineS = strstr(p, CRLF); pLineE = NULL; lineLen = 0;
  267. if(pLineS) { pLineS += crlfLen; pLineE = strstr(pLineS, CRLF); }
  268. if(pLineE) { lineLen = pLineE - pLineS; }
  269. if(lineLen == 0)
  270. { // 连续两个CRLF, 上面的逻辑保证了"lineLen"只可能大于或等于0, 不会是小于0
  271. if(pLineE) { p = pLineE; goto lsa_p; } // 跳第一个CRLF, 继续分析下一行
  272. else goto end_p;
  273. }
  274. //// 示例: "+CREG: <n>,<stat>"
  275. //// 说明: <n>注册状态报告模式, <stat>当前网络注册状态
  276. p1 = strstr(pLineS, "+CREG:"); p2 = NULL;
  277. if(p1 && p1 < pLineE) { p1 += 6; p2 = strchr(p1, ','); }
  278. if(p2 && p2 < pLineE)
  279. {
  280. p2++; while(*p2 == 0x20 && p2 < pLineE) { p2++; } // 跳过空格
  281. statLen = (pLineE - p2); if(statLen != 1 && statLen != 2) return -5; // 状态值范围: 1或2个数字
  282. memcpy(statBuf, p2, statLen); statBuf[statLen] = '\0';
  283. bool isDigit = true;
  284. for(int i = 0; i < strlen(statBuf); i++) if(!xisdigit(statBuf[i])) { isDigit = false; break; }
  285. if(isDigit) return atoi(statBuf); // 成功读到模块的网络注册状态值
  286. else return -6;
  287. }
  288. p = pLineE + crlfLen;
  289. goto lsa_p; // 继续分析下一行
  290. end_p:
  291. return -7;
  292. }
  293. // 获取当前信号强度, 返回: >=0成功, <0失败(-1命令超时)
  294. int EC200U_GetRSSIFromCSQ()
  295. {
  296. SATCmdTrans trans; memset(&trans, 0, sizeof(SATCmdTrans));
  297. strcpy(trans.cmd.data, AT_GET_CSQ);
  298. trans.cmd.len = strlen(trans.cmd.data);
  299. int ret = comio_doCmd(&trans, WAIT_ACK_TIMEOUT);
  300. if(ret < 0) return ret; // 执行超时或出错
  301. else if(strstr(trans.ack.data, ATOK) == NULL) goto end_p; // 未收到成功应答
  302. const char *p = trans.ack.data, *pLineS, *pLineE; int lineLen, crlfLen = strlen(CRLF);
  303. const char *p1, *p2; char rssiBuf[3]; int rssiLen;
  304. lsa_p: // 行扫描, 逐行分析应答结果
  305. if((*p) == '\0') goto end_p;
  306. pLineS = strstr(p, CRLF); pLineE = NULL; lineLen = 0;
  307. if(pLineS) { pLineS += crlfLen; pLineE = strstr(pLineS, CRLF); }
  308. if(pLineE) { lineLen = pLineE - pLineS; }
  309. if(lineLen == 0)
  310. { // 连续两个CRLF, 上面的逻辑保证了"lineLen"只可能大于或等于0, 不会是小于0
  311. if(pLineE) { p = pLineE; goto lsa_p; } // 跳第一个CRLF, 继续分析下一行
  312. else goto end_p;
  313. }
  314. //// 示例: "+CSQ: 15,99"
  315. //// 说明: 逗号前第一个数值是RSSI的值
  316. p1 = strstr(pLineS, "+CSQ:"); p2 = NULL;
  317. if(p1 && p1 < pLineE) { p1 += 5; p2 = strchr(p1, ','); }
  318. if(p2 && p2 < pLineE)
  319. {
  320. while(*p1 == 0x20 && p1 < p2) { p1++; } // 跳过空格
  321. rssiLen = (p2 - p1); if(rssiLen != 1 && rssiLen != 2) return -5; // 强度值范围: 0-99, 1或2个数字
  322. memcpy(rssiBuf, p1, rssiLen); rssiBuf[rssiLen] = '\0';
  323. bool isDigit = true;
  324. for(int i = 0; i < strlen(rssiBuf); i++) if(!xisdigit(rssiBuf[i])) { isDigit = false; break; }
  325. if(isDigit) return atoi(rssiBuf); // 成功读到模块的接收信号强度值
  326. else return -6;
  327. }
  328. p = pLineE + crlfLen;
  329. goto lsa_p; // 继续分析下一行
  330. end_p:
  331. return -7;
  332. }
  333. // 返回电话卡的状态, 返回: 1已可用, <0失败(-1命令超时)
  334. int EC200U_IsSimCardReady()
  335. {
  336. SATCmdTrans trans; memset(&trans, 0, sizeof(SATCmdTrans));
  337. strcpy(trans.cmd.data, AT_GET_PINSTA);
  338. trans.cmd.len = strlen(trans.cmd.data);
  339. int ret = comio_doCmd(&trans, WAIT_ACK_TIMEOUT);
  340. if(ret < 0) return ret; // 执行超时或出错
  341. else if(strstr(trans.ack.data, ATOK) && strstr(trans.ack.data, "+CPIN: READY")) return 1;
  342. else return -5;
  343. }
  344. // 获取电话卡标识号, 返回: =20成功, <0失败(-1命令超时)
  345. int EC200U_GetSimICCID(char buf[21]/*一般由20个十进制数字组成*/)
  346. {
  347. SATCmdTrans trans; memset(&trans, 0, sizeof(SATCmdTrans));
  348. strcpy(trans.cmd.data, AT_GET_ICCID);
  349. trans.cmd.len = strlen(trans.cmd.data);
  350. int ret = comio_doCmd(&trans, WAIT_ACK_TIMEOUT);
  351. if(ret < 0) return ret; // 执行超时或出错
  352. else if(strstr(trans.ack.data, ATOK) == NULL) goto end_p; // 未收到成功应答
  353. const char *p = trans.ack.data, *pLineS, *pLineE; int lineLen, crlfLen = strlen(CRLF);
  354. const char *p1, *p2; int iccidLen;
  355. lsa_p: // 行扫描, 逐行分析应答结果
  356. if((*p) == '\0') goto end_p;
  357. pLineS = strstr(p, CRLF); pLineE = NULL; lineLen = 0;
  358. if(pLineS) { pLineS += crlfLen; pLineE = strstr(pLineS, CRLF); }
  359. if(pLineE) { lineLen = pLineE - pLineS; }
  360. if(lineLen == 0)
  361. { // 连续两个CRLF, 上面的逻辑保证了"lineLen"只可能大于或等于0, 不会是小于0
  362. if(pLineE) { p = pLineE; goto lsa_p; } // 跳第一个CRLF, 继续分析下一行
  363. else goto end_p;
  364. }
  365. //// 示例: "+QCCID: 89860436102480057245"
  366. p1 = strstr(pLineS, "+QCCID:"); p2 = NULL;
  367. if(p1 && p1 < pLineE) { p1 += 7; p2 = pLineE; }
  368. if(p2)
  369. {
  370. while(*p1 == 0x20 && p1 < p2) { p1++; } // 跳过空格
  371. iccidLen = (p2 - p1); if(iccidLen != 20) return -5; // 长度不对
  372. bool isHex = true;
  373. for(int i = 0; i < iccidLen; i++) if(!xisxdigit(p1[i])) { isHex = false; break; }
  374. if(isHex) { memcpy(buf, p1, iccidLen); buf[iccidLen] = '\0'; return iccidLen; } // 成功读到SIM卡的ICCID号
  375. else return -6;
  376. }
  377. p = pLineE + crlfLen;
  378. goto lsa_p; // 继续分析下一行
  379. end_p:
  380. return -7;
  381. }