Files
CCU621M/app/plat_comm/4g_module/ec200a_4g.c
T

1695 lines
56 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#include "publicdata/publicdata.h"
#if EC200A_4G_EN
#include <string.h>
#include <stdio.h>
#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? 结果中的<stat>等于 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=<contextID>[,<context_type>,<APN>[,<username>,<password>)[,<authentication>[,<CDMA_pwd>]]]]
* AT+QICSGP=1,1,"UNINET","","",1 //配置场景 1APN 配置为"UNINET"(中国联通)。
*
* <contextID> 整型。场景 ID。范围:1~15。
* <context_type> 整型。协议类型。
* 1 IPv4
* 2 IPv6
* 3 IPv4v6
* <APN> 字符串类型。接入点名称。
* <username> 字符串类型。用户名。最大长度:127 字节。
* <password> 字符串类型。密码。最大长度:127 字节。
* <authentication> 整型。APN 鉴权方式。
* 0 None
* 1 PAP
* 2 CHAP
* 3 PAP 或 CHAP
* <CDMA_pwd> 整型。是否在 CDMA 网络下保存<username>和<password>。
* 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 根据协商的参数检查
* 该最低可接受的配置文件。设置命令指定上下文标识符<cid>标识的上下文的参数。
* 设置命令的一种特殊形式 AT+CGQMIN=<cid>会使上下文标识符<cid>的最低可接受的配置文件处于
* 未定义状态,此时将不会对协商的配置文件进行检查。查询命令返回每个已定义 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 指定服务质量配置文件。
* 该设置命令指定上下文<cid>的配置文件。设置命令的一种特殊形式 AT+CGQREQ=<cid>会使上下文
* 标识符<cid>请求的参数处于未定义状态。查询命令返回每一个已定义的上下文的当前配置。详细信息请
* 参考 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 地址。
* 虽然<contextID>的范围为 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.2AT+QSSLCFG="sslversion",<ctx>,4
* 2) 身份验证模式设为 0(不验证服务器证书,便于联调):AT+QSSLCFG="seclevel",<ctx>,0
* 3) 忽略本地时间对证书有效性的影响:AT+QSSLCFG="ignorelocaltime",<ctx>,1
* 4) 启用 SNIAT+QSSLCFG="sni",<ctx>,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<id>,<len> 后等待 '>',再发二进制,再等 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: <len> 或 +TAG: <mux>,<len> */
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=<sock_id>,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/IPAT+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 服务。服务类型可通过<service_type>指定,数据访问模式(缓存模式,直吐
* 模式和透传模式)可通过<access_mode>配置,URC +QIOPEN 将指示 Socket 服务是否成功打开。
* AT+QIOPEN=1,0,"TCP","220.180.239.212",8009,0,2 //场景为 1<connectID>为 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 通道;
* TLSAT+QSSLOPEN=<PDP>,<SSL_ctx>,<clientID>,"host",port,<access_mode>
* TCPAT+QIOPEN=1,0,"TCP","host",port,0,<access_mode>
*/
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? 结果中的<stat>等于 1 或者5,表示模块已在UMTS/LTE网络注册上PS业务
2. 等待60 s后,不管是否注册上PS业务,都进入下一个流程
AT+CGREG PS 域网络注册状态
AT+CEREG EPS 网络注册状态
(4)配置PDP Context激活参数:
1 . 通过 AT+QICSGP 命令配置APNU ser NamePassword 和Auth Type
2. 通过 AT+CGQMIN/AT+CGEQMIN/ AT+CGQREQ/AT+CGEQREQ 配置 QoS 参数
AT+QICSGP=<contextID>[,<context_type>,<APN>[,<username>,<password>)[,<authentication>[,<CDMA_pwd>]]]]
AT+QICSGP=1,1,"UNINET","","",1 //配置场景 1APN 配置为"UNINET"(中国联通)。
AT+CGQMIN 服务质量参数(最低可接受的)
AT+CGEQMIN 3G 服务质量参数(最低可接受)
AT+CGQREQ 服务质量参数(请求的)
AT+CGEQREQ 3G 服务质量参数(请求的)
(5)激活PDP Context
1. 通过 AT+QIACT=<contextID>激活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 地址。
虽然<contextID>的范围为 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=<connectID>[,<send_length>] 发送数据。建议定期向远程端发送小数据包以维护和检测TCP连接。
2. 通过 AT+QISEND=<connectID>,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 */