#include "publicdata/publicdata.h" #if EC200A_4G_EN #include #include #include "plat_comm/4g_module/ec200a_4g.h" #include "plat_comm/plat_comm_task.h" #include "mylog/mylog.h" #include "usart.h" #include "app_init/app_init.h" #if(MY_SHELL_EN) //Shell 使能 #include "letter_shell/shell_port.h" #endif /** 本文件 FTP/升级/日志 AT 调试:不走 Plat_Comm 的 Plat_Comm_LOG,统一 MYLOG_MSG(0xFF) 直出 */ #define EC200A_4G_LOG(fmt, ...) MYLOG_MSG(0xFF, fmt, ##__VA_ARGS__) S_4G_CTRL_DATA s_4g_ctrl; #if 1 //串口收发接口 /** * ************************************************************************ * @brief - 数据发送接口函数 发送 * * @param[in] u8_sendBuf - 发送数据地址 * @param[in] u16_len - 发送数据长度 * * * @author jiankalka * @date 2025-08-07 * ************************************************************************ */ void v_4g_send_data(U8_T u8_id, U8_T *u8_sendBuf , U16_T u16_len) { u16_usart_send(COM_PORT_BS,u8_sendBuf, u16_len); #if TEST_BS_PRINTF_FLAG //调试打印 printf("//4g send,len is [%d]\r\n", u16_len); printf_char("---",u8_sendBuf,u16_len); #endif } /** * ************************************************************************ * @brief - 数据接收接口函数 接收 * * @param[in] u8_recvBuf - 接收数据地址 * @param[in] u16_len - 预期接收数据长度 * * @return U16_T - 已接收数据长度 * * @author jiankalka * @date 2025-08-07 * ************************************************************************ */ U16_T u16_4g_recv_data(U8_T *u8_recvBuf ,U16_T u16_len) { U16_T recv_len; recv_len = u16_usart_recv(COM_PORT_BS,u8_recvBuf,u16_len); #if TEST_BS_PRINTF_FLAG //调试打印 if(recv_len == 0) return 0; printf("\\4g recv,len is [%d]\r\n", recv_len); printf_char("---",u8_recvBuf,recv_len); #endif return recv_len; } /** 排空模组口 RX 环形缓冲(丢弃)。FTP / QISEND 等共用 UART 去粘连 */ void v_4g_uart_rx_discard_fifo(void) { U8_T scrap[128]; U16_T n; U32_T total = 0; while (total < 8192U) { n = u16_usart_recv(COM_PORT_BS, scrap, sizeof(scrap)); if (n == 0U) { break; } total += n; } } #endif #if 1 //公共接口函数 /** * ************************************************************************ * @brief -在接收缓存中,查找是否收到期望应答 * * @param[in] src -缓存数据 * @param[in] des -目标值 * @param[out] s8_findResult - 目标值下标 * * @author LQ * @date 2025-08-22 * ************************************************************************ */ S8_T s8_FindAck(U8_T *src, U8_T *des) { S8_T s8_findResult = -1; // 查询结果 char *p_strstr = NULL; p_strstr = strstr((const char *)src, (const char *)des); if (p_strstr != NULL) { s8_findResult = p_strstr - (char *)src; // des所在下标 } #if TEST_BS_PRINTF_FLAG //调试打印 printf("s8_FindAck start \r\n - res(%d) , %s -> %s\r\ns8_FindAck end \r\n",s8_findResult,des,src); #endif return s8_findResult; } /** * @brief 在二进制数据中查找目标序列 * @param src: 源数据缓冲区 * @param srcLen: 源数据长度(字节数) * @param des: 目标序列 * @param desLen: 目标序列长度(字节数) * @retval 找到返回目标序列在源数据中的起始索引,未找到返回-1 */ S16_T s16_FindAck_Ex(U8_T *src, U16_T srcLen, U8_T *des, U16_T desLen) { S16_T s16_findResult = -1; U16_T i, j; // 参数检查 if (src == NULL || des == NULL || srcLen == 0 || desLen == 0) { //printf("\r\n is NULL (%d)(%d)(%d)(%d)\r\n",(src == NULL),(des == NULL),(srcLen==0),(desLen == 0)); return -1; } // 如果目标序列比源数据还长,肯定找不到 if (desLen > srcLen) { //printf("\r\ndesLen[%d] > srcLen[%d]\r\n",desLen,srcLen); return -1; } // 遍历源数据,查找目标序列 for (i = 0; i <= srcLen - desLen; i++) { // 比较当前位置开始的desLen个字节是否与目标序列匹配 for (j = 0; j < desLen; j++) { if (src[i + j] != des[j]) { break; // 发现不匹配,跳出内层循环 } } // 如果内层循环完整执行,说明找到了匹配 if (j == desLen) { s16_findResult = i; break; } } return s16_findResult; } /** * ************************************************************************ * @brief -AT指令设置命令 * * @param[in] sendbuf -发送数据指针 * @param[in] sendLen -发送数据长度 * @param[in] ack -期望应答有效数据 NULL是期待回复值为OK * @param[in] resendCnt -重发次数计数 * @param[in] delayTimeMs -延时时间 * @param[out] ackIndex - 期望应答有效数据 -1 未接收到 》=0 成功接收到期望应答数据 * * @author LQ * @date 2025-08-22 * ************************************************************************ */ S8_T s8_4g_Send_AT_Cmd(U8_T *sendBuf, U8_T sendLen, U8_T *ack, U8_T resendCnt, U16_T delayTimeMs) { U8_T count = 0; U16_T rcvLen = 0; S8_T ackIndex = -1; U8_T ackOK[] = "OK"; U8_T rcvBuf[BS_RECV_BUF_LEN] = {0}; // com_write(COM_PORT_BS, sendBuf, sendLen, 0); v_4g_send_data(ID_4G, sendBuf, sendLen); do { mSleep(delayTimeMs); memset(rcvBuf, 0, BS_RECV_BUF_LEN); // rcvLen = com_read(COM_PORT_BS, rcvBuf, 100, 0); rcvLen = u16_4g_recv_data(rcvBuf, BS_RECV_BUF_LEN); if (rcvLen > 0) { if (s8_FindAck(rcvBuf, ackOK) >= 0) // 收到OK,如果后面有数据,说明数据也接收完全 { if (ack != NULL) { ackIndex = s8_FindAck(rcvBuf, ack); // 找到期望应答有效数据 if ((ackIndex >= 0)) return ackIndex; } else return 1; } } count++; } while (count < resendCnt); // 如果没收到期望应答,重新发送 return -1; } /** * ************************************************************************ * @brief - 具有返回值的 AT指令发送、接收函数 * * @param[in] sendBuf - 发送数据指针 * @param[in] sendLen - 发送数据长度 * @param[in] rcvBuf - 接收的数据内容 外部传入,接收赋值后传出 方便外部直接使用 * @param[in] recvLen - 最大可支持数据内容,防止数组越界 * @param[in] ack - 期望应答有效数据 NULL是期待回复值为OK 非NULL时,期待回复值为期待值 * @param[in] resendCnt - 重发次数计数 * @param[in] delayTimeMs - 单次接收等待延时时间 * * @return S8_T - 期望应答有效数据 -1 未接收到 》=0 成功接收到期望应答数据 并标志期待值的下标位置 * * @author jiankalka * @date 2025-11-24 * ************************************************************************ */ S8_T s8_4g_Send_Recv_Cmd(U8_T *sendBuf, U8_T sendLen,U8_T *rcvBuf,U16_T recvLen, U8_T *ack, U8_T resendCnt, U16_T delayTimeMs) { U8_T count = 0; U16_T rcvLen = 0; S16_T ackIndex = -1; U8_T ackOK[] = "OK"; // com_write(COM_PORT_BS, sendBuf, sendLen, 0); v_4g_send_data(ID_4G, sendBuf, sendLen); memset(rcvBuf, 0, recvLen); do { mSleep(delayTimeMs); // rcvLen = com_read(COM_PORT_BS, rcvBuf, 100, 0); if(rcvLen < recvLen) //已接收数据满了 不再接收新数据 rcvLen += u16_4g_recv_data(&rcvBuf[rcvLen], recvLen-rcvLen); if (rcvLen > 0) { if(ack == NULL) //预期为空时 找OK { ackIndex = s16_FindAck_Ex(rcvBuf,rcvLen+2, ackOK ,sizeof(ackOK)-1); //+2 if ((ackIndex >= 0)) return ackIndex; } else // 预期不为空时 找预期内容 { ackIndex = s16_FindAck_Ex(rcvBuf,rcvLen, ack,strlen((char *)ack)); if ((ackIndex >= 0)) return ackIndex; } } count++; } while (count < resendCnt); // 如果没收到期望应答,重新发送 return -1; } void v_4g_log_data_send_data(U8_T *u8_sendBuf , U16_T u16_len) { if (u8_sendBuf != NULL && u16_len > 0U) { v_4g_send_data(ID_4G, u8_sendBuf, u16_len); } #if TEST_BS_PRINTF_FLAG //调试打印 printf("//4g log send,len is [%d]\r\n", u16_len); printf_hex("---",u8_sendBuf,u16_len); #endif } #endif #if 1 //内部逻辑控制 /** * ************************************************************************ * @brief - 查询信号强度 * * * @return S8_T - -1 查询失败 >0 查询成功 * * @author jiankalka * @date 2025-11-22 * ************************************************************************ */ static S8_T s8_ec200a_csq() { U8_T u8_sendBuf[] = "AT+CSQ\r\n"; U8_T u8_ack[] = "+CSQ: "; U8_T rcvBuf[BS_RECV_BUF_LEN] = {0}; S8_T s8_ret; S8_T ackIndex = -1; int a, b; s8_ret = s8_4g_Send_Recv_Cmd(u8_sendBuf, sizeof(u8_sendBuf) - 1,rcvBuf,BS_RECV_BUF_LEN, u8_ack, 2, 50); if(s8_ret >= 0) { if (sscanf((char *)&rcvBuf[s8_ret], "+CSQ: %d,%d", &a, &b) == 2) { s_4g_ctrl.u8_csq = a; #if TEST_BS_PRINTF_FLAG printf("信号强度= %d\r\n",s_4g_ctrl.u8_csq); #endif } else s8_ret = -1; } return s8_ret; } /** * ************************************************************************ * @brief - 查询sim卡号 ccid号 * * * @return S8_T - -1 查询失败 >0 查询成功 * * @author jiankalka * @date 2025-11-22 * ************************************************************************ */ static S8_T s8_ec200a_qccid() { U8_T u8_sendBuf[] = "AT+QCCID\r\n"; U8_T u8_ack[] = "+QCCID:"; S8_T ackIndex = -1; U8_T rcvBuf[BS_RECV_BUF_LEN] = {0}; S8_T s8_ret; s8_ret = s8_4g_Send_Recv_Cmd(u8_sendBuf, sizeof(u8_sendBuf) - 1,rcvBuf,BS_RECV_BUF_LEN, NULL, 2, 50); if(s8_ret >= 0) { ackIndex = s8_FindAck(rcvBuf, u8_ack); if ((ackIndex >= 0)) { s8_ret = 1; memset(s_4g_ctrl.u8_Sim_Card, 0, 20); strncpy((char *)s_4g_ctrl.u8_Sim_Card, (char *)&rcvBuf[ackIndex + 8],20); printf("*************** QCCID recv = %s\r\n", s_4g_ctrl.u8_Sim_Card); } else { s8_ret = -1; } } return s8_ret; } static S8_T s8_ec200a_gmr(void) { U8_T u8_sendBuf[] = "AT+GMR\r\n"; return s8_4g_Send_AT_Cmd(u8_sendBuf, sizeof(u8_sendBuf) - 1, NULL, 2, 50); } ///////////////////////////////// /** * ************************************************************************ * @brief - 查询 PS 域网络注册状态 * 1. 当 AT+CGREG?/AT+CEREG? 结果中的等于 1 或者5,表示模块已在UMTS/LTE网络注册上PS业务 * 2. 等待60 s后,不管是否注册上PS业务,都进入下一个流程 * * * @return S8_T - -1 查询失败 >0 查询成功 * * @author jiankalka * @date 2025-11-22 * ************************************************************************ */ S8_T s8_ec200a_set_cgreg() { U8_T u8_sendBuf[] = "AT+CGREG=2\r\n"; return s8_4g_Send_AT_Cmd(u8_sendBuf, sizeof(u8_sendBuf) - 1, NULL, 2, 50); } S8_T s8_ec200a_cgreg() { U8_T u8_sendBuf[] = "AT+CGREG=2\r\n"; U8_T u8_sendBuf2[] = "AT+CGREG?\r\n"; U8_T u8_ack[] = "+CGREG:"; U8_T rcvBuf[BS_RECV_BUF_LEN] = {0}; S8_T s8_ret; int n, stat; // s8_ret = s8_4g_Send_AT_Cmd(u8_sendBuf, sizeof(u8_sendBuf) - 1, NULL, 2, 50); // if(s8_ret == -1) // return s8_ret; s8_ret = s8_4g_Send_Recv_Cmd(u8_sendBuf2, sizeof(u8_sendBuf2) - 1,rcvBuf,BS_RECV_BUF_LEN, u8_ack, 2, 50); if(s8_ret >= 0) { // printf("CGREG recv = %s\n", rcvBuf); // 使用sscanf直接提取两个数字 if (sscanf((char *)&rcvBuf[s8_ret], "+CGREG: %d,%d", &n, &stat) == 2) { //printf("CGREG 成功提取: n=%d, stat=%d\r\n", n, stat); // 判断stat 是否为1或者5 已注册,其余为未注册 if(stat != 1 && stat != 5) s8_ret = -1; else if(stat == 3) EC200A_4G_LOG("REG reject\r\n"); } else s8_ret = -1; } // U8_T u8_test[] = "AT+COPS?\r\n"; // s8_4g_Send_AT_Cmd(u8_test, sizeof(u8_test) - 1, NULL, 2, 50); return s8_ret; } /** * ************************************************************************ * @brief - 查询(U)SIM卡状态 * * * @return S8_T - -1 未成功 >0成功 * * @author jiankalka * @date 2025-11-21 * ************************************************************************ */ static S8_T s8_ec200a_cpin() { U8_T u8_sendBuf[] = "AT+CPIN?\r\n"; return s8_4g_Send_AT_Cmd(u8_sendBuf, sizeof(u8_sendBuf) - 1, NULL, 2, 50); } /** * ************************************************************************ * @brief - AT+QICSGP 配置 TCP/IP 场景参数 * AT+QICSGP=[,,[,,)[,[,]]]] * AT+QICSGP=1,1,"UNINET","","",1 //配置场景 1,APN 配置为"UNINET"(中国联通)。 * * 整型。场景 ID。范围:1~15。 * 整型。协议类型。 * 1 IPv4 * 2 IPv6 * 3 IPv4v6 * 字符串类型。接入点名称。 * 字符串类型。用户名。最大长度:127 字节。 * 字符串类型。密码。最大长度:127 字节。 * 整型。APN 鉴权方式。 * 0 None * 1 PAP * 2 CHAP * 3 PAP 或 CHAP * 整型。是否在 CDMA 网络下保存。 * 0 不保存 * 1 保存 * @return S8_T - -1 未成功 >0成功 * * @author jiankalka * @date 2025-11-22 * ************************************************************************ */ S8_T s8_ec200a_qicsgp_ctx(U8_T context_id) { if(s_4g_ctrl.s_4g_para.u8_Sim_Type == 0) //公网卡 跳过设置APN { if(s_4g_ctrl.s_4g_para.apn_str == NULL || strlen((char *)s_4g_ctrl.s_4g_para.apn_str) == 0) //无apn配置 { U8_T u8_sendBuf[48]; int n = snprintf((char *)u8_sendBuf, sizeof(u8_sendBuf), "AT+QICSGP=%u,3,\"\",\"\",\"\",1\r\n", (unsigned)context_id); if (n <= 0 || n >= (int)sizeof(u8_sendBuf)) { return -1; } return s8_4g_Send_AT_Cmd(u8_sendBuf, (U16_T)n, NULL, 2, 50); } else { U8_T u8_sendBuf[72] = {0}; int n = snprintf((char *)u8_sendBuf, sizeof(u8_sendBuf), "AT+QICSGP=%u,3,\"%s\",\"\",\"\",1\r\n", (unsigned)context_id, s_4g_ctrl.s_4g_para.apn_str); if (n <= 0 || n >= (int)sizeof(u8_sendBuf)) { return -1; } printf("apn 2 = %s\r\n", u8_sendBuf); return s8_4g_Send_AT_Cmd(u8_sendBuf, (U16_T)n, NULL, 2, 50); } } else if(s_4g_ctrl.s_4g_para.u8_Sim_Type == 1) { U8_T u8_sendBuf[56]; int n = snprintf((char *)u8_sendBuf, sizeof(u8_sendBuf), "AT+QICSGP=%u,3,\"M2MPLNAMR\",\"\",\"\",1\r\n", (unsigned)context_id); if (n <= 0 || n >= (int)sizeof(u8_sendBuf)) { return -1; } printf("apn 3 = %s\r\n", u8_sendBuf); return s8_4g_Send_AT_Cmd(u8_sendBuf, (U16_T)n, NULL, 2, 50); } return 0; } static S8_T s8_ec200a_qicsgp(void) { return s8_ec200a_qicsgp_ctx((U8_T)EC200A_TCP_PDP_CTX_ID); } /** * ************************************************************************ * @brief - AT+CGQMIN 服务质量参数(最低可接受的) * 该命令允许 TE 指定最低可接受的配置文件参数,当激活 PDP 上下文时,由 MT 根据协商的参数检查 * 该最低可接受的配置文件。设置命令指定上下文标识符标识的上下文的参数。 * 设置命令的一种特殊形式 AT+CGQMIN=会使上下文标识符的最低可接受的配置文件处于 * 未定义状态,此时将不会对协商的配置文件进行检查。查询命令返回每个已定义 PDP 上下文的当前配置。 * 详情请参考 3GPP TS 23.107。 * * @return S8_T - -1 未成功 >0成功 * * @author jiankalka * @date 2025-11-22 * ************************************************************************ */ static S8_T s8_ec200a_cgqmin() { U8_T u8_sendBuf[] = "AT+CGQMIN?\r\n"; return s8_4g_Send_AT_Cmd(u8_sendBuf, sizeof(u8_sendBuf) - 1, NULL, 2, 50); } /** * ************************************************************************ * @brief - AT+CGQREQ 服务质量参数(请求的) * 在 MT 激活 PDP 上下文时,该命令允许 MT 指定服务质量配置文件。 * 该设置命令指定上下文的配置文件。设置命令的一种特殊形式 AT+CGQREQ=会使上下文 * 标识符请求的参数处于未定义状态。查询命令返回每一个已定义的上下文的当前配置。详细信息请 * 参考 3GPP TS 23.107。 * * @return S8_T - -1 未成功 >0成功 * * @author jiankalka * @date 2025-11-22 * ************************************************************************ */ static S8_T s8_ec200a_cgqreq() { U8_T u8_sendBuf[] = "AT+CGQREQ?\r\n"; return s8_4g_Send_AT_Cmd(u8_sendBuf, sizeof(u8_sendBuf) - 1, NULL, 2, 50); } /** * ************************************************************************ * @brief - AT+QIACT 激活 PDP 场景 * 在用AT+QIACT激活PDP场景前,需要使用AT+QICSGP配置场景。场景激活后,可以通过AT+QIACT?查询 IP 地址。 * 虽然的范围为 1~15,但是模块最多仅可同时激活 3 路 PDP 场景。受网络状态影响,执行 * AT+QIACT 后,等待返回结果 OK 或者 ERROR 的最大时间为 150 秒,在结果尚未返回之前,无法执行任何 AT 命令。 * * * @return S8_T - -1 未成功 >0成功 * * @author jiankalka * @date 2025-11-22 * ************************************************************************ */ S8_T s8_ec200a_qiact_ctx(U8_T context_id) { U8_T u8_sendBuf[24]; int n = snprintf((char *)u8_sendBuf, sizeof(u8_sendBuf), "AT+QIACT=%u\r\n", (unsigned)context_id); if (n <= 0 || n >= (int)sizeof(u8_sendBuf)) { return -1; } return s8_4g_Send_AT_Cmd(u8_sendBuf, (U16_T)n, NULL, 20, 1000); } S8_T s8_ec200a_qiact(void) { return s8_ec200a_qiact_ctx((U8_T)EC200A_TCP_PDP_CTX_ID); } S8_T s8_ec200a_qideact_ctx(U8_T context_id) { U8_T u8_sendBuf[24]; int n = snprintf((char *)u8_sendBuf, sizeof(u8_sendBuf), "AT+QIDEACT=%u\r\n", (unsigned)context_id); if (n <= 0 || n >= (int)sizeof(u8_sendBuf)) { return -1; } return s8_4g_Send_AT_Cmd(u8_sendBuf, (U16_T)n, NULL, 5, 100); } /** * ************************************************************************ * @brief - TLS 相关参数配置(使用模块内置 SSL 功能) * 该函数示例性地通过 QSSLCFG 配置 SSL 上下文,具体参数需根据 * EC200A SSL AT 命令手册进行调整与完善。 * * @return S8_T - -1 未成功 >0成功 * ************************************************************************ */ static S8_T s8_ec200a_ssl_cfg(void) { S8_T s8_ret; U8_T u8_buf[64]; /* 如未启用 TLS,直接视为成功 */ if (s_4g_ctrl.s_4g_para.use_tls == 0U) { return 1; } /* * 按《LTE Standard(A)系列 SSL 应用指导》配置常用 SSL 参数: * 1) TLS 版本设为 TLS1.2:AT+QSSLCFG="sslversion",,4 * 2) 身份验证模式设为 0(不验证服务器证书,便于联调):AT+QSSLCFG="seclevel",,0 * 3) 忽略本地时间对证书有效性的影响:AT+QSSLCFG="ignorelocaltime",,1 * 4) 启用 SNI:AT+QSSLCFG="sni",,1 * 如需严格验证服务器证书,可将 seclevel 改为 1/2,并配置 CA 证书路径等。 */ /* 1) TLS 版本:TLS1.2 */ snprintf((char *)u8_buf, sizeof(u8_buf), "AT+QSSLCFG=\"sslversion\",%d,4\r\n", EC200A_SSL_CTX_ID); s8_ret = s8_4g_Send_AT_Cmd(u8_buf, (U8_T)strlen((char *)u8_buf), NULL, 2, 200); if (s8_ret < 0) { return s8_ret; } /* 2) 身份验证模式:0=不验证服务器证书(联调阶段),后续可改为 1/2 */ snprintf((char *)u8_buf, sizeof(u8_buf), "AT+QSSLCFG=\"seclevel\",%d,0\r\n", EC200A_SSL_CTX_ID); s8_ret = s8_4g_Send_AT_Cmd(u8_buf, (U8_T)strlen((char *)u8_buf), NULL, 2, 200); if (s8_ret < 0) { return s8_ret; } /* 3) 忽略证书有效期对验证结果的影响(模块本地时间可能尚未与网络时间同步) */ snprintf((char *)u8_buf, sizeof(u8_buf), "AT+QSSLCFG=\"ignorelocaltime\",%d,1\r\n", EC200A_SSL_CTX_ID); s8_ret = s8_4g_Send_AT_Cmd(u8_buf, (U8_T)strlen((char *)u8_buf), NULL, 2, 200); if (s8_ret < 0) { return s8_ret; } /* 4) 启用 SNI,便于服务器根据主机名匹配证书 */ snprintf((char *)u8_buf, sizeof(u8_buf), "AT+QSSLCFG=\"sni\",%d,1\r\n", EC200A_SSL_CTX_ID); s8_ret = s8_4g_Send_AT_Cmd(u8_buf, (U8_T)strlen((char *)u8_buf), NULL, 2, 200); if (s8_ret < 0) { return s8_ret; } return 1; } #if !EC200A_TCP_USE_TRANSPARENT_MODE /** memchr-style:在 buf[0..len) 中查找 needle(二进制安全,不要求 '\0') */ static int ec200a_buf_contains(const U8_T *buf, U16_T len, const char *needle) { U16_T nl; U16_T i; if (buf == NULL || needle == NULL) { return 0; } nl = (U16_T)strlen(needle); if (nl == 0U || len < nl) { return 0; } for (i = 0; i + nl <= len; i++) { if (memcmp(buf + i, needle, nl) == 0) { return 1; } } return 0; } /** 缓冲 TCP:经 `v_4g_send_data` 与 AT 路径共用模组 UART(`COM_PORT_BS`) */ static U16_T u16_4g_uart_rx_modem_raw(U8_T *buf, U16_T len) { if (buf == NULL || len == 0U) { return 0; } return u16_usart_recv(COM_PORT_BS, buf, len); } static U16_T u16_4g_uart_rx_modem(U8_T *buf, U16_T len) { U16_T n; n = u16_4g_uart_rx_modem_raw(buf, len); #if TEST_BS_PRINTF_FLAG if (n > 0U) { printf("\\4g buf recv,len is [%d]\r\n", n); printf_char("---", buf, n); } #endif return n; } /** 非透传:发送 AT 后累计 UART,直到解析到 +QIOPEN / +QSSLOPEN 成功 URC */ static S8_T s8_ec200a_qiopen_accumulate(const U8_T *cmd, U16_T cmd_len) { U8_T acc[EC200A_QIOPEN_ACCUM_SZ]; U16_T acc_len = 0; U16_T wait_i; memset(acc, 0, sizeof(acc)); v_4g_send_data(ID_4G, (U8_T *)(void *)cmd, cmd_len); for (wait_i = 0; wait_i < 200U; wait_i++) { unsigned cid = 0, err = 1; char *p; mSleep(100); if (acc_len + 1U < sizeof(acc)) { acc_len += u16_4g_uart_rx_modem(acc + acc_len, (U16_T)(sizeof(acc) - 1U - acc_len)); acc[acc_len < sizeof(acc) ? acc_len : sizeof(acc) - 1U] = '\0'; } p = strstr((char *)acc, "+QIOPEN:"); if (p == NULL) { p = strstr((char *)acc, "+QSSLOPEN:"); } if (p != NULL) { int nfield = 0; if (strncmp(p, "+QIOPEN:", 8) == 0) { nfield = sscanf(p, "+QIOPEN: %u,%u", &cid, &err); } else if (strncmp(p, "+QSSLOPEN:", 10) == 0) { nfield = sscanf(p, "+QSSLOPEN: %u,%u", &cid, &err); } if (nfield >= 2 && err == 0U) { s_4g_ctrl.u8_tcp_socket_id = s_4g_ctrl.s_4g_para.use_tls ? EC200A_SSL_CLIENT_ID : 0U; (void)cid; return 1; } } if (strstr((char *)acc, "+CME ERROR:") != NULL || strstr((char *)acc, "\r\nERROR\r\n") != NULL) { break; } } EC200A_4G_LOG("4G QIOPEN buffered: timeout or error\r\n"); return -1; } /** AT+QISEND / AT+QSSLSEND:, 后等待 '>',再发二进制,再等 SEND OK */ static S8_T s8_ec200a_sock_send_once(U8_T sock_id, const U8_T *data, U16_T chunk_len, U8_T use_tls) { U8_T u8_cmd[56]; U8_T rb[EC200A_QISEND_WAIT_RB_SZ]; U16_T rb_len = 0; U16_T i; U8_T attempt; const char *at_tag = use_tls ? "QSSLSEND" : "QISEND"; if (data == NULL || chunk_len == 0U) { return -1; } snprintf((char *)u8_cmd, sizeof(u8_cmd), "AT+%s=%u,%u\r\n", at_tag, (unsigned)sock_id, (unsigned)chunk_len); /* FTP 与 QISEND/QSSLSEND 共用 UART:先排空 RX,必要时重发一遍 AT */ for (attempt = 0; attempt < 2U; attempt++) { v_4g_uart_rx_discard_fifo(); if (attempt > 0U) { mSleep(50); } v_4g_send_data(ID_4G, u8_cmd, (U16_T)strlen((char *)u8_cmd)); memset(rb, 0, sizeof(rb)); rb_len = 0; for (i = 0; i < 300U; i++) { mSleep(10); if (rb_len + 1U < sizeof(rb)) { rb_len += u16_4g_uart_rx_modem(rb + rb_len, (U16_T)(sizeof(rb) - 1U - rb_len)); rb[rb_len < sizeof(rb) ? rb_len : sizeof(rb) - 1U] = '\0'; } if (memchr(rb, '>', (size_t)rb_len) != NULL) { goto sock_send_got_prompt; } if (ec200a_buf_contains(rb, rb_len, "\r\nERROR\r\n") || ec200a_buf_contains(rb, rb_len, "\nERROR\r\n")) { EC200A_4G_LOG("4G %s: AT ERROR (no >)\r\n", at_tag); return -1; } if (rb_len + 64U >= sizeof(rb)) { EC200A_4G_LOG("4G %s: rx buf full no >\r\n", at_tag); return -1; } } } EC200A_4G_LOG("4G %s: no prompt >\r\n", at_tag); return -1; sock_send_got_prompt: v_4g_send_data(ID_4G, (U8_T *)(void *)data, chunk_len); memset(rb, 0, sizeof(rb)); rb_len = 0; for (i = 0; i < 600U; i++) { mSleep(10); if (rb_len + 1U < sizeof(rb)) { rb_len += u16_4g_uart_rx_modem(rb + rb_len, (U16_T)(sizeof(rb) - 1U - rb_len)); rb[rb_len < sizeof(rb) ? rb_len : sizeof(rb) - 1U] = '\0'; } if (rb_len + 32U >= sizeof(rb) && rb_len > EC200A_QISEND_RB_TAIL_KEEP) { U16_T drop = (U16_T)(rb_len - EC200A_QISEND_RB_TAIL_KEEP); memmove(rb, rb + drop, (size_t)(rb_len - drop)); rb_len = (U16_T)(rb_len - drop); rb[rb_len < sizeof(rb) ? rb_len : sizeof(rb) - 1U] = '\0'; } if (ec200a_buf_contains(rb, rb_len, "SEND OK")) { return 1; } if (ec200a_buf_contains(rb, rb_len, "\r\nERROR\r\n") || ec200a_buf_contains(rb, rb_len, "\nERROR\r\n") || ec200a_buf_contains(rb, rb_len, "SEND FAIL")) { EC200A_4G_LOG("4G %s: fail\r\n", at_tag); return -1; } } EC200A_4G_LOG("4G %s: no SEND OK\r\n", at_tag); return -1; } /** 从 +QIRD: / +QSSLRECV: 应答行提取负载(格式相同,仅前缀不同) */ static U16_T ec200a_parse_sock_recv_payload(const char *tag, const U8_T *rb, U16_T rb_len, U8_T *out, U16_T out_max) { const U16_T tag_len = (U16_T)strlen(tag); U16_T i, j; unsigned datalen = 0; int had_len_digits = 0; if (rb == NULL || out == NULL || rb_len == 0U || out_max == 0U) { return 0; } /* 多次应答拼在同一缓冲时,取最后一次 tag 行 */ i = rb_len; for (j = 0; j + tag_len <= rb_len; j++) { if (memcmp(rb + j, tag, tag_len) == 0) { i = j; } } if (i + tag_len > rb_len) { return 0; } j = (U16_T)(i + tag_len); while (j < rb_len && (rb[j] == ' ' || rb[j] == '\t')) { j++; } /* 支持 +TAG: 或 +TAG: , */ datalen = 0; while (j < rb_len && rb[j] >= '0' && rb[j] <= '9') { had_len_digits = 1; datalen = datalen * 10U + (unsigned)(rb[j] - '0'); j++; } if (j < rb_len && rb[j] == ',') { j++; datalen = 0; had_len_digits = 0; while (j < rb_len && rb[j] >= '0' && rb[j] <= '9') { had_len_digits = 1; datalen = datalen * 10U + (unsigned)(rb[j] - '0'); j++; } } if (!had_len_digits) { return 0; } while (j < rb_len && rb[j] != '\n') { j++; } if (j >= rb_len) { return 0; } j++; /* 跳过 \n(允许上一字节为 \r) */ if (datalen == 0U) { return 0; } if ((U32_T)j + datalen > rb_len) { return 0; } if (datalen > out_max) { datalen = out_max; } memcpy(out, rb + j, datalen); return (U16_T)datalen; } static U16_T ec200a_parse_qird_payload(const U8_T *rb, U16_T rb_len, U8_T *out, U16_T out_max) { return ec200a_parse_sock_recv_payload("+QIRD:", rb, rb_len, out, out_max); } static U16_T ec200a_parse_qsslrecv_payload(const U8_T *rb, U16_T rb_len, U8_T *out, U16_T out_max) { return ec200a_parse_sock_recv_payload("+QSSLRECV:", rb, rb_len, out, out_max); } /** 判定 UART 缓冲内「最后一次 +TAG:」应答是否已收全 */ static int ec200a_sock_recv_rx_complete(const char *tag, const U8_T *rb, U16_T rb_len) { const U16_T tag_len = (U16_T)strlen(tag); U16_T i, j; unsigned datalen = 0; int had_len_digits = 0; if (rb == NULL || rb_len < tag_len) { return 0; } i = rb_len; for (j = 0; j + tag_len <= rb_len; j++) { if (memcmp(rb + j, tag, tag_len) == 0) { i = j; } } if (i + tag_len > rb_len) { return 0; } j = (U16_T)(i + tag_len); while (j < rb_len && (rb[j] == ' ' || rb[j] == '\t')) { j++; } datalen = 0; while (j < rb_len && rb[j] >= '0' && rb[j] <= '9') { had_len_digits = 1; datalen = datalen * 10U + (unsigned)(rb[j] - '0'); j++; } if (j < rb_len && rb[j] == ',') { j++; datalen = 0; had_len_digits = 0; while (j < rb_len && rb[j] >= '0' && rb[j] <= '9') { had_len_digits = 1; datalen = datalen * 10U + (unsigned)(rb[j] - '0'); j++; } } while (j < rb_len && rb[j] != '\n') { j++; } if (j >= rb_len) { return 0; } j++; if (!had_len_digits) { return 0; } if (datalen == 0U) { return 1; } return ((U32_T)j + datalen <= rb_len) ? 1 : 0; } static int ec200a_qird_rx_complete(const U8_T *rb, U16_T rb_len) { return ec200a_sock_recv_rx_complete("+QIRD:", rb, rb_len); } static int ec200a_qsslrecv_rx_complete(const U8_T *rb, U16_T rb_len) { return ec200a_sock_recv_rx_complete("+QSSLRECV:", rb, rb_len); } /** * @brief 非透传模式下发送 TCP/TLS 应用层数据(经模组缓冲 Socket)。 * * 明文 TCP 使用 `AT+QISEND`;`QSSLOPEN` 建立的 wss 通道须用 `AT+QSSLSEND`(对 SSL socket 发 QISEND 会直接 ERROR)。 * 按 `EC200A_TCP_APP_RW_MAX` 分包,UART 与 FTP/其它 AT 共用。 * 任一分包失败则停止后续发送并打日志。 * * @param[in] u8_id 预留与平台抽象一致,当前未使用。 * @param[in] u8_sendBuf 待发数据缓冲。 * @param[in] u16_len 待发总长度(字节)。 * * @note 仅在 `EC200A_TCP_USE_TRANSPARENT_MODE == 0` 时编译;透传路径请用 `v_4g_send_data`。 */ void v_ec200a_tcp_app_send(U8_T u8_id, U8_T *u8_sendBuf, U16_T u16_len) { U8_T sock_id = s_4g_ctrl.u8_tcp_socket_id; U8_T use_tls = s_4g_ctrl.s_4g_para.use_tls; U16_T off; (void)u8_id; if (u8_sendBuf == NULL || u16_len == 0U) { return; } { U16_T chunk_cap = EC200A_TCP_APP_RW_MAX; if (chunk_cap > EC200A_QISEND_AT_LEN_MAX) { chunk_cap = EC200A_QISEND_AT_LEN_MAX; } for (off = 0; off < u16_len; off += chunk_cap) { U16_T chunk = (U16_T)(u16_len - off); if (chunk > chunk_cap) { chunk = chunk_cap; } if (s8_ec200a_sock_send_once(sock_id, u8_sendBuf + off, chunk, use_tls) < 0) { EC200A_4G_LOG("4G tcp buffered send break off=%u (%s)\r\n", (unsigned)off, use_tls ? "QSSLSEND" : "QISEND"); v_4g_uart_rx_discard_fifo(); break; } } } } /** * @brief 非透传模式下从模组 Socket 接收缓冲区读取一段数据(AT+QIRD 或 AT+QSSLRECV)。 * * wss 使用 `AT+QSSLRECV=,want`;明文 TCP 使用 `AT+QIRD=...`。轮询 UART 直至应答收齐, * 再从最后一次 `+QSSLRECV:` / `+QIRD:` 行拷贝负载。 * 单次调用最多返回 `min(u16_len, EC200A_TCP_APP_RW_MAX, EC200A_QIRD_AT_READ_MAX)` 字节;更大负载需上层循环多次调用。 * * @param[out] u8_recvBuf 接收缓冲。 * @param[in] u16_len 期望拷贝的最大字节数(同时限制单次 QIRD 请求长度)。 * * @return 本次实际拷贝的应用负载字节数;无数据或解析失败返回 0。 * * @note 仅在 `EC200A_TCP_USE_TRANSPARENT_MODE == 0` 时编译。 */ U16_T u16_ec200a_tcp_app_recv(U8_T *u8_recvBuf, U16_T u16_len) { U8_T sock_id = s_4g_ctrl.u8_tcp_socket_id; U8_T use_tls = s_4g_ctrl.s_4g_para.use_tls; U8_T cmd[48]; U8_T rb[EC200A_QIRD_RSP_MAX]; U16_T want; U16_T rb_len = 0; U16_T i; U16_T paylen; const char *recv_at = use_tls ? "QSSLRECV" : "QIRD"; const char *empty_tag = use_tls ? "+QSSLRECV: 0\r\n" : "+QIRD: 0\r\n"; if (u8_recvBuf == NULL || u16_len == 0U) { return 0; } want = u16_len; if (want > EC200A_TCP_APP_RW_MAX) { want = EC200A_TCP_APP_RW_MAX; } if (want > EC200A_QIRD_AT_READ_MAX) { want = EC200A_QIRD_AT_READ_MAX; } snprintf((char *)cmd, sizeof(cmd), "AT+%s=%u,%u\r\n", recv_at, (unsigned)sock_id, (unsigned)want); memset(rb, 0, sizeof(rb)); v_4g_send_data(ID_4G, cmd, (U16_T)strlen((char *)cmd)); for (i = 0; i < 500U; i++) { mSleep(5); if (rb_len + 2U < sizeof(rb)) { rb_len += u16_4g_uart_rx_modem(rb + rb_len, (U16_T)(sizeof(rb) - 2U - rb_len)); rb[rb_len < sizeof(rb) ? rb_len : sizeof(rb) - 1U] = '\0'; } if (ec200a_buf_contains(rb, rb_len, "+CME ERROR:") || ec200a_buf_contains(rb, rb_len, "\r\nERROR\r\n")) { break; } if (use_tls) { if (ec200a_qsslrecv_rx_complete(rb, rb_len)) { break; } } else if (ec200a_qird_rx_complete(rb, rb_len)) { break; } if (rb_len + 32U >= sizeof(rb)) { EC200A_4G_LOG("4G %s: rsp buf overflow risk len=%u\r\n", recv_at, (unsigned)rb_len); break; } } if (use_tls) { paylen = ec200a_parse_qsslrecv_payload(rb, rb_len, u8_recvBuf, u16_len); } else { paylen = ec200a_parse_qird_payload(rb, rb_len, u8_recvBuf, u16_len); } if (paylen == 0U && EC200A_QIRD_EMPTY_POLL_DELAY_MS > 0U && (use_tls ? ec200a_qsslrecv_rx_complete(rb, rb_len) : ec200a_qird_rx_complete(rb, rb_len)) && !ec200a_buf_contains(rb, rb_len, "+CME ERROR:") && !ec200a_buf_contains(rb, rb_len, "\r\nERROR\r\n") && ec200a_buf_contains(rb, rb_len, empty_tag)) { mSleep(EC200A_QIRD_EMPTY_POLL_DELAY_MS); } if (paylen == 0U) { int complete = use_tls ? ec200a_qsslrecv_rx_complete(rb, rb_len) : ec200a_qird_rx_complete(rb, rb_len); int err = ec200a_buf_contains(rb, rb_len, "+CME ERROR:") || ec200a_buf_contains(rb, rb_len, "\r\nERROR\r\n"); if (!complete || err) { v_4g_uart_rx_discard_fifo(); } } return paylen; } /** Quectel TCP/IP:AT+QISDE 控制 QISEND 数据是否在 UART 回显;失败不阻断建链(旧固件或无此命令时可能 ERROR) */ static S8_T s8_ec200a_qisde_apply(void) { U8_T u8_buf[24]; int n; n = snprintf((char *)u8_buf, sizeof(u8_buf), "AT+QISDE=%u\r\n", (unsigned)EC200A_QISDE_ECHO); if (n <= 0 || n >= (int)sizeof(u8_buf)) { return -1; } return s8_4g_Send_AT_Cmd(u8_buf, (U16_T)n, NULL, 3, 200); } #endif /* !EC200A_TCP_USE_TRANSPARENT_MODE */ S8_T s8_ec200a_tcp_socket_close(void); /** * ************************************************************************ * @brief - AT+QIOPEN 打开 Socket 服务 * 该命令用于打开 Socket 服务。服务类型可通过指定,数据访问模式(缓存模式,直吐 * 模式和透传模式)可通过配置,URC +QIOPEN 将指示 Socket 服务是否成功打开。 * AT+QIOPEN=1,0,"TCP","220.180.239.212",8009,0,2 //场景为 1,为 0。执行 AT+QIOPEN * 之前,Host 需使用 AT+QIACT 来激活场景。 * CONNECT //连接成功,等待 URC 响应结果 CONNECT 建议 * 等待 150 秒。若 150 秒内 URC 无响应,Host 可通过 AT+QICLOSE 断开 Socket 连接。 * * @return S8_T - -1 未成功 >0成功 * * @author jiankalka * @date 2025-11-22 * ************************************************************************ */ static S8_T s8_ec200a_qiopen() { U8_T u8_sendBuf[64] = {0}; U8_T u8_ack[] = "CONNECT"; U8_T rcvBuf[BS_RECV_BUF_LEN] = {0}; int cmd_n; if(s_4g_ctrl.s_4g_para.socketA_IP == NULL) return -1; /* 建链前先关残留 Socket(异常复位后模组侧可能仍占着 SSL 连接,导致平台 404 拒绝 WS 升级) */ (void)s8_ec200a_tcp_socket_close(); /* 根据配置选择普通 TCP 还是模块内置 TLS 通道; * TLS:AT+QSSLOPEN=,,,"host",port, * TCP:AT+QIOPEN=1,0,"TCP","host",port,0, */ if (s_4g_ctrl.s_4g_para.use_tls) { cmd_n = snprintf((char *)u8_sendBuf, sizeof(u8_sendBuf), "AT+QSSLOPEN=%d,%d,%d,\"%s\",%d,%d\r\n", EC200A_SSL_PDP_CTX_ID, EC200A_SSL_CTX_ID, EC200A_SSL_CLIENT_ID, s_4g_ctrl.s_4g_para.socketA_IP, s_4g_ctrl.s_4g_para.socketA_port, EC200A_TCP_QIOPEN_ACCESS_MODE); } else { cmd_n = snprintf((char *)u8_sendBuf, sizeof(u8_sendBuf), "AT+QIOPEN=%u,0,\"TCP\",\"%s\",%d,0,%d\r\n", (unsigned)EC200A_TCP_PDP_CTX_ID, s_4g_ctrl.s_4g_para.socketA_IP,s_4g_ctrl.s_4g_para.socketA_port, EC200A_TCP_QIOPEN_ACCESS_MODE); } if (cmd_n <= 0 || cmd_n >= (int)sizeof(u8_sendBuf)) { return -1; } #if EC200A_TCP_USE_TRANSPARENT_MODE return s8_4g_Send_Recv_Cmd(u8_sendBuf, (U16_T)cmd_n, rcvBuf, BS_RECV_BUF_LEN, u8_ack, 10, 1000); #else return s8_ec200a_qiopen_accumulate(u8_sendBuf, (U16_T)cmd_n); #endif } /** * 关闭业务 TCP/SSL Socket(不拆 PDP)。QICLOSE 失败(如已关)不视为致命错误。 */ S8_T s8_ec200a_tcp_socket_close(void) { U8_T cmd[48]; int n; if (s_4g_ctrl.s_4g_para.socketA_IP == NULL) { return -1; } if (s_4g_ctrl.s_4g_para.use_tls) { n = snprintf((char *)cmd, sizeof(cmd), "AT+QSSLCLOSE=%u\r\n", (unsigned)s_4g_ctrl.u8_tcp_socket_id); } else { n = snprintf((char *)cmd, sizeof(cmd), "AT+QICLOSE=%u\r\n", (unsigned)s_4g_ctrl.u8_tcp_socket_id); } if (n <= 0 || n >= (int)sizeof(cmd)) { return -1; } (void)s8_4g_Send_AT_Cmd(cmd, (U16_T)n, NULL, 3, 400); mSleep(120); v_4g_uart_rx_discard_fifo(); return 0; } /** * 业务 PDP 已就绪且 e_4g_init_step==STEP_INIT_OK:仅 QICLOSE/QSSLCLOSE 再 QIOPEN/QSSLOPEN,不重跑驻网/PDP。 * QICLOSE 失败(例如已关闭)不阻断后续 QIOPEN。 */ S8_T s8_ec200a_tcp_socket_soft_reopen(void) { S8_T r; if (s_4g_ctrl.e_4g_init_step != STEP_INIT_OK) { return -1; } if (s_4g_ctrl.s_4g_para.socketA_IP == NULL) { return -1; } (void)s8_ec200a_tcp_socket_close(); (void)s8_ec200a_qisde_apply(); r = s8_ec200a_qiopen(); return r; } static S8_T s8_ec200a_qpowd() { U8_T u8_sendBuf[] = "AT+QPOWD=1\r\n"; S8_T s8_ret = s8_4g_Send_AT_Cmd(u8_sendBuf, sizeof(u8_sendBuf) - 1, NULL, 2, 50); if(s8_ret >= 0) mSleep(12*1000); return s8_ret; } static S8_T s8_ec200a_cfun() { U8_T u8_sendBuf[] = "AT+CFUN=0\r\n"; U8_T u8_sendBuf2[] = "AT+CFUN=1\r\n"; S8_T s8_ret; // s8_ret = s8_4g_Send_AT_Cmd(u8_sendBuf, sizeof(u8_sendBuf) - 1, NULL, 5, 500); // if(s8_ret >= 0) { s8_ret = s8_4g_Send_AT_Cmd(u8_sendBuf2, sizeof(u8_sendBuf2) - 1, NULL, 5, 500); } return s8_ret; } #endif U8_T u8_ec200a_modem_tcp_stack_ok(void) { return (s_4g_ctrl.e_4g_init_step == STEP_INIT_OK) ? 1U : 0U; } #if 1 //切换到指令模式 /** * ************************************************************************ * @brief -切换到指令模式 * * @param[in] * @param[out] - * * @author LQ * @date 2025-08-22 * ************************************************************************ */ S8_T s8_work_mode_cmd() // 切换到指令模式 { U8_T u8_sendAT[] = "AT\r\n"; U8_T u8_sendBuf[] = "+++"; s8_4g_Send_AT_Cmd(u8_sendBuf, sizeof(u8_sendBuf) - 1, NULL, 3, 500); return s8_4g_Send_AT_Cmd(u8_sendAT, sizeof(u8_sendAT) - 1, NULL, 2, 50); } /** * ************************************************************************ * @brief - 重启4G模块 发送关机--开机 延时足够时间 * * @param[in] u8_flag - 0 仅开机 1先关机 再开机 * * @return S8_T - * * @author jiankalka * @date 2025-11-25 * ************************************************************************ */ S8_T s8_cat_reboot(U8_T u8_flag) { S8_T s8_at_res,s8_ret = -1; U8_T u8_cnt = 20; //printf("cat 4g reboot %d",u8_flag); if(u8_flag) { while (u8_cnt) { s8_at_res = s8_work_mode_cmd(); if(s8_at_res >= 0) { s8_at_res = s8_ec200a_qpowd(); if(s8_at_res >= 0) { s8_ret = 1; break; } } PWR_4G(0); //执行开机 mSleep(2000); PWR_4G(1); u8_cnt --; } } else { //s8_ret = s8_ec200a_qpowd(); s8_ret = 1; PWR_4G(0); mSleep(3000); PWR_4G(1); } return s8_ret; } #endif #if 1 //对外接口函数 /** * ************************************************************************ * @brief -4G 模块初始化函数 包括设置4G联网参数 * * @param[in] s_4g_para - 4G联网参数配置 * * * @author jiankalka * @date 2025-11-22 * ************************************************************************ */ void v_4g_data_init(S_4G_CONFIG_PARA *s_4g_para) { memset(&s_4g_ctrl , 0 ,sizeof(S_4G_CTRL_DATA)); memcpy(&s_4g_ctrl.s_4g_para ,s_4g_para,sizeof(S_4G_CONFIG_PARA)); s8_cat_reboot(0); } /** * ************************************************************************ * @brief - 4G 模块初始化函数 * * * @return S8_T - 0配网成功 非0 配网中(1-15配网步骤) * * @author jiankalka * @date 2025-11-21 * ************************************************************************ */ S8_T s8_ec200a_4g_init() { S8_T s8_ret = 1,s8_at_res; // static U16_T u16_cnt = 0; // if(u16_cnt++ < 10) // return s8_ret; // u16_cnt = 0; // char testmag[] = "AT+QIOPEN=1,0,\"TCP\",\"182.92.157.86\",8088,0,2\r\nCONNECT"; // U8_T u8_ack[] = "CONNECT"; // printf("test %d\r\n",s8_FindAck(testmag, u8_ack)); // return s8_ret; //printf("4g config\r\n"); /* 使用 TCP/IP AT 命令的流程 通过 TCP/IP AT 命令,Host 可以配置 PDP 场景、激活/去激活 PDP 场景、打开/关闭 Socket 服务,并 通过 Socket 服务发送/接收数据。TCP/IP AT 命令的使用流程如下图所示: (1)建议开关机流程: 1 .关机:发送AT+QPOWD,等待12 s以上,直接断电。 2. 开机:先使Power Key按键处于低电平状态,等待2 s后,再拉高Power Ke y (2)查询(U)SIM卡状态: 执行AT+CPIN,如果2 0 s后命令"AT+CPIN"无法识别(U)SIM卡状态,建议重启模块 (3)PS 业务: 1. 当 AT+CGREG?/AT+CEREG? 结果中的等于 1 或者5,表示模块已在UMTS/LTE网络注册上PS业务 2. 等待60 s后,不管是否注册上PS业务,都进入下一个流程 AT+CGREG PS 域网络注册状态 AT+CEREG EPS 网络注册状态 (4)配置PDP Context激活参数: 1 . 通过 AT+QICSGP 命令配置APN,U ser Name,Password 和Auth Type 2. 通过 AT+CGQMIN/AT+CGEQMIN/ AT+CGQREQ/AT+CGEQREQ 配置 QoS 参数 AT+QICSGP=[,,[,,)[,[,]]]] AT+QICSGP=1,1,"UNINET","","",1 //配置场景 1,APN 配置为"UNINET"(中国联通)。 AT+CGQMIN 服务质量参数(最低可接受的) AT+CGEQMIN 3G 服务质量参数(最低可接受) AT+CGQREQ 服务质量参数(请求的) AT+CGEQREQ 3G 服务质量参数(请求的) (5)激活PDP Context: 1. 通过 AT+QIACT=激活PDP Context 2 . 通过 AT+QIACT? 查询PDP Context IP地址 注意事项: a) 以上命令必须按顺序执行 b) 如果等待150 s后,AT+QIACT没有响应,建议重启模块. c) 如果连续3次反激活PDP Context失败,建议重启模块 3. 反激活PDP Context:如果等待40s后AT+QIDEACT没有响应,建议重启模块 AT+QIACT 激活 PDP 场景 在用AT+QIACT激活PDP场景前,需要使用AT+QICSGP配置场景。场景激活后,可以通过AT+QIACT?查询 IP 地址。 虽然的范围为 1~15,但是模块最多仅可同时激活 3 路 PDP 场景。受网络状态影响,执行 AT+QIACT 后,等待返回结果 OK 或者 ERROR 的最大时间为 150 秒,在结果尚未返回之前,无法执行任何 AT 命令。 AT+QIDEACT 去激活 PDP 场景 该命令用于去激活特定场景,断开在这个场景内建立的所有 TCP/IP 连接。受网络状态影响,执行 AT+QIDEACT 后,等待返回结果 OK 或者 ERROR 的最大时间为 40 秒。在结果尚未返回之前,无法执行任何 AT 命令 (6)打开连接: 1. 如果等待150 s,没有收到AT+QIOPEN的响应,请关闭连接。 2 .如果连续5次打开连接失败,先反激活PDP Context,然后重新激活PDP Context并再次打开连接。 (7)TCP连接的维护和检测: 1. 通过 AT+QISEND=[,] 发送数据。建议定期向远程端发送小数据包以维护和检测TCP连接。 2. 通过 AT+QISEND=,0 查询ACK的发送。如果在2分钟后(每5 s查询一次,一共查询2 4次),对方没有收到数据, 说明TCP连接可能出现异常,请执行AT+QICLOSE命令关闭这个TCP连接,然后通过AT+QIOPEN重新建立TCP连接。 */ switch (s_4g_ctrl.e_4g_init_step) { case STEP_SWCHTO_CMD_MODE: //切换到指令模式 { s8_at_res = s8_work_mode_cmd(); if(s8_at_res >= 0){ s_4g_ctrl.e_4g_init_step = STEP_QUERY_SIM_STATE; //状态跳转 s_4g_ctrl.u8_out_cnt = 0; //超时计数清空 } break; } case STEP_QUERY_SIM_STATE: //查询SIM卡状态 { s8_at_res = s8_ec200a_cpin(); //查询SIM卡状态 if(s8_at_res >= 0){ s_4g_ctrl.e_4g_init_step = STEP_QUERY_CCID; //状态跳转 s_4g_ctrl.u8_out_cnt = 0; //超时计数清空 } break; } case STEP_QUERY_CCID: //查询sim卡号 { s8_at_res = s8_ec200a_qccid(); //获取SIM卡号 if(s8_at_res >= 0){ s_4g_ctrl.e_4g_init_step = STEP_SET_CFUN; //状态跳转 s_4g_ctrl.u8_out_cnt = 0; //超时计数清空 } break; } case STEP_SET_CFUN: //设置功能模式 { s8_at_res = s8_ec200a_cfun(); //设置功能模式 if(s8_at_res >= 0){ s_4g_ctrl.e_4g_init_step = STEP_SET_CGREG; //状态跳转 s_4g_ctrl.u8_out_cnt = 0; //超时计数清空 } } case STEP_SET_CGREG: //设置PS域 网络注册状态 { s8_at_res = s8_ec200a_set_cgreg(); // if(s8_at_res >= 0){ s_4g_ctrl.e_4g_init_step = STEP_QUERY_CGREG; //状态跳转 s_4g_ctrl.u8_out_cnt = 0; //超时计数清空 } } case STEP_QUERY_CGREG: //查询PS域 网络注册状态 { s8_at_res = s8_ec200a_cgreg(); //查询PS域 网络注册状态 if(s8_at_res >= 0){ s_4g_ctrl.e_4g_init_step = STEP_SET_QICSGP_APN; //状态跳转 s_4g_ctrl.u8_out_cnt = 0; //超时计数清空 } break; } case STEP_SET_QICSGP_APN: //设置TCP场景参数 设置APN { s8_at_res = s8_ec200a_qicsgp(); //设置TCP场景参数 设置APN if(s8_at_res >= 0){ s_4g_ctrl.e_4g_init_step = STEP_SET_CGQMIN; //状态跳转 s_4g_ctrl.u8_out_cnt = 0; //超时计数清空 } break; } case STEP_SET_CGQMIN: //设置服务质量参数(最低可接受的) { s8_at_res = s8_ec200a_cgqmin(); //设置服务质量参数(最低可接受的) if(s8_at_res >= 0){ s_4g_ctrl.e_4g_init_step = STEP_SET_CGQREQ; //状态跳转 s_4g_ctrl.u8_out_cnt = 0; //超时计数清空 } break; } case STEP_SET_CGQREQ: //设置服务质量参数(请求的) { s8_at_res = s8_ec200a_cgqreq(); //设置服务质量参数(请求的) if(s8_at_res >= 0){ s_4g_ctrl.e_4g_init_step = STEP_QUERY_CSQ; //状态跳转 s_4g_ctrl.u8_out_cnt = 0; //超时计数清空 } break; } case STEP_QUERY_CSQ: //查询信号强度 { s8_at_res = s8_ec200a_csq(); //查询信号强度 if(s8_at_res >= 0){ s_4g_ctrl.e_4g_init_step = STEP_SET_QIACT; //状态跳转 s_4g_ctrl.u8_out_cnt = 0; //超时计数清空 } break; } case STEP_SET_QIACT: //激活PDP场景 { s8_at_res = s8_ec200a_qiact(); //激活PDP场景 if(s8_at_res >= 0){ /* 若启用 TLS,则在激活 PDP 后先做 SSL 配置,否则直接进入打开 socket 步骤 */ if (s_4g_ctrl.s_4g_para.use_tls) { s_4g_ctrl.e_4g_init_step = STEP_SET_SSL_CFG; } else { s_4g_ctrl.e_4g_init_step = STEP_SET_SOCKET_AND_OPEN; } s_4g_ctrl.u8_out_cnt = 0; //超时计数清空 } break; } case STEP_SET_SSL_CFG: //TLS 配置 { s8_at_res = s8_ec200a_ssl_cfg(); if (s8_at_res >= 0) { s_4g_ctrl.e_4g_init_step = STEP_SET_SOCKET_AND_OPEN; //TLS 配置完成,进入打开 socket s_4g_ctrl.u8_out_cnt = 0; } break; } case STEP_SET_SOCKET_AND_OPEN: //设置socket 参数并打开 { #if !EC200A_TCP_USE_TRANSPARENT_MODE (void)s8_ec200a_qisde_apply(); /* AT+QISDE:关闭 QISEND 数据 UART 回显(失败仍尝试建链) */ #endif s8_at_res = s8_ec200a_qiopen(); //设置socket 参数并打开 if(s8_at_res >= 0){ s_4g_ctrl.e_4g_init_step = STEP_SAVE; //状态跳转 s_4g_ctrl.u8_out_cnt = 0; //超时计数清空 } break; } case STEP_SAVE: //保存当前设置并重启 case STEP_INIT_OK: //设置完成 { s_4g_ctrl.e_4g_init_step = STEP_INIT_OK; s8_ret = 0; return s8_ret; break; } case STEP_INIT_DEBUG: //调试模式 等待shell命令配置 { mSleep(1000); return s8_ret; } default: break; } mSleep(50); //4G模块配置超时 重新初始化 if(s_4g_ctrl.u8_out_cnt == 0) { s_4g_ctrl.u32_outtime = get_current_seconds(); //超时计数清空 s_4g_ctrl.u8_out_cnt++; } else { s_4g_ctrl.u8_out_cnt = u32_safe_seconds_since(s_4g_ctrl.u32_outtime); #if TEST_BS_PRINTF_FLAG //调试打印 printf("test out time (%d)[%d]\r\n",s_4g_ctrl.e_4g_init_step,s_4g_ctrl.u8_out_cnt); #endif s8_ec200a_csq(); s8_ec200a_gmr(); } if(s_4g_ctrl.u8_out_cnt > 150) { EC200A_4G_LOG("4G cfg TO, retry state=%d\r\n",s_4g_ctrl.e_4g_init_step); s_4g_ctrl.u8_out_cnt = 0; s_4g_ctrl.e_4g_init_step = STEP_SWCHTO_CMD_MODE; s8_cat_reboot(1); } else if(s_4g_ctrl.e_4g_init_step == STEP_QUERY_CGREG && s_4g_ctrl.u8_out_cnt > 60) { EC200A_4G_LOG("4G PS reg TO, force jump\r\n"); s_4g_ctrl.u8_out_cnt = 0; s_4g_ctrl.e_4g_init_step = STEP_SET_QICSGP_APN; } return s8_ret; } /** * ************************************************************************ * @brief - 获取sim卡号 * * @param[in] u8_data - 卡号数组 * @param[in] buf_len - 数组缓存区大小 * * @return U8_T - 0失败 1成功 * * @author jiankalka * @date 2025-11-26 * ************************************************************************ */ U8_T u8_get_sim_card(U8_T *u8_data ,U8_T buf_len) { memset(u8_data , 0 , buf_len); strncpy((char *)u8_data ,(char *)s_4g_ctrl.u8_Sim_Card, buf_len ); return 1; } U8_T u8_UAT_check_signal_quality() { return s_4g_ctrl.u8_csq; } #endif #if(MY_SHELL_EN) //Shell 使能 /** * ************************************************************************ * @brief - 遥控测试函数 * * * * @author jiankalka * @date 2025-08-04 * ************************************************************************ */ // 命令 type 0 is fan1; 1 is fan2; 2 is acyk1; 3 is acyk2; void cat1Func(int type , int on,char *mag) { v_shell_print("cat1Func %d %d\r\n",type,on); if(type == 0) { if(on) s_4g_ctrl.e_4g_init_step = STEP_INIT_DEBUG; else s_4g_ctrl.e_4g_init_step = STEP_SWCHTO_CMD_MODE; } else { if(mag == NULL) v_shell_print("cat1Func mag is null\r\n"); else { char test_mag[64] = {0}; snprintf(test_mag,64,"%s\r\n",mag); s8_4g_Send_AT_Cmd((U8_T *)test_mag,strlen(test_mag), NULL, 2, 100); v_shell_print("cat1Func mag is send :(%d) %s\r\n",strlen(test_mag),test_mag); } } } // 注册 SHELL_EXPORT_CMD(SHELL_CMD_PERMISSION(0)|SHELL_CMD_TYPE(SHELL_TYPE_CMD_FUNC), catCtrl,cat1Func, myshlle-- 4 g cat test); #endif #endif /* EC200A_4G_EN */