文章目录
前言
上一遍博客已经讲述了PDU编码的实现,接下来就要说如何实现发送信息了,其实发送短信的过程就是在串口下发送AT指令的过程,为了提高代码的可移植性以及看起来更简洁,所以我们先来学习一下AT指令的封装吧!
1. AT指令的封装
1.1 atcmd.h
atmcmd.c 的头文件,声明了接下来的一些函数定义以及宏、枚举等信息。
代码:
#ifndef _ATCMD_H_
#define _ATCMD_H_
#include <stdlib.h>
#include "myserial.h"
#define SEND_BUF 128
typedef struct atcmd_ctx_s
{
char send_at_buf[516]; // send AT command
char succe_at_buf[128]; // expect receive respond
char use_buf[512]; // need store the contents of the response in the use_buf
int use_len; // length of use_buf
int resp_timeout; // respond time timeout
}atcmd_ctx_t;
typedef struct gsm_ctx_s
{
int status;
int sim_signal;
}gsm_ctx_t;
enum
{
GSM_STATUS_INIT = 0, //SIM card and serial port initial status.
GSM_STATUS_SERIAL_READY, //Serial port ready.
GSM_STATUS_SIM_EXIST, //SIM card exist.
GSM_STATUS_SIM_REG, //SIM card already register.
GSM_STATUS_SIM_SIG, //SIM card have signal/
GSM_STATUS_READY = GSM_STATUS_SIM_SIG, //SIM card and serial port all ready.
};
/*-----------------------------------------------------------------------
| funtion:AT command send and receive respond.
|
| argument: serial: serial port property struct.
| 2、atcmd: send at command relevant parameters struct.
|
| return: rv: success: == 0 ; error: < 0
|-----------------------------------------------------------------------*/
int send_recv_atcmd(serial_ctx_t *serial,atcmd_ctx_t *atcmd);
/*-----------------------------------------------------------------------
| funtion:Check sim card and serial port whether already ready.
|
| argument: serial: serial port property struct.
|
| return: rv: success: == 0 ; error: < 0
|-----------------------------------------------------------------------*/
int check_sim_serial(serial_ctx_t *serial,gsm_ctx_t *gsm_ctx);
/*-----------------------------------------------------------------------
| funtion:Check serial port whether already ready.
|
| argument: serial_fd: serial port fd.
|
| return: rv: success: == 0 ; error: < 0
|-----------------------------------------------------------------------*/
int check_serial_ready(serial_ctx_t *serial);
/*-----------------------------------------------------------------------
| funtion:Check sim cart whether already exist.
|
| argument: serial_fd: serial port fd.
|
| return: rv: success: == 0 ; error: < 0
|-----------------------------------------------------------------------*/
int check_sim_exist(serial_ctx_t *serial);
/*-----------------------------------------------------------------------
| funtion:Check sim cart whether already register.
|
| argument: serial_fd: serial port fd.
|
| return: rv: success: == 0 ; error: < 0
|-----------------------------------------------------------------------*/
int check_sim_register(serial_ctx_t *serial);
/*--------------------------------------------------------------------------
| funtion:Check sim cart whether already signal.
|
| argument: serial_fd: serial port fd.
|
| return: rv: success: == 0 ; error: < 0
|---------------------------------------------------------------------------*/
int check_sim_signal(serial_ctx_t *serial,gsm_ctx_t *gsm_ctx);
/*--------------------------------------------------------------------------
| funtion:Check sms amount of sim cart.
|
| argument: serial_fd: serial port fd.
|
| return: rv: success: == 0 ; error: < 0
|---------------------------------------------------------------------------*/
int check_sms_amount(serial_ctx_t *serial,int *sms_sum);
#endif /* ----- #ifndef _ATCMD_H_ ----- */
1.2 send_recv_atcmd()
该函数把前面说到的实现AT指令的收发的文章中的serial_send() 和 serial_receive() 函数封装了起来,后面只需调用这个函数就能实现AT指令的发送与接收。
代码;
/*-----------------------------------------------------------------------
| funtion:AT command send and receive respond.
|
| argument: 1、serial_fd: serial port fd.
| 2、atcmd: send at command relevant parameters struct.
|
| return: rv: success: == 0 ; error: < 0
|-----------------------------------------------------------------------*/
int send_recv_atcmd(serial_ctx_t *serial,atcmd_ctx_t *atcmd)
{
int rv = 0;
char recv_tranfer[512] = {0};
if(!atcmd || !serial)
{
printf("%s,Invalid input arguments\n",__func__);
return -1;
}
rv = serial_send(serial,atcmd->send_at_buf,strlen(atcmd->send_at_buf));
if(rv < 0)
{
printf("serial port send AT cmd failure and rv: %d\n",rv);
return -2;
}
usleep(10000);
rv = serial_receive(serial,recv_tranfer,sizeof(recv_tranfer),atcmd->resp_timeout);
if(rv < 0)
{
printf("serial port receive AT response failure and rv: %d\n",rv);
return -3;
}
if(!strstr(recv_tranfer,atcmd->succe_at_buf))
{
printf("recv_tranfer: %s",recv_tranfer);
printf("recive AT response have not expect data and [%s] not in recive AT response\n",atcmd->succe_at_buf);
return -4;
}
if(atcmd->use_len)
{
strncpy(atcmd->use_buf,recv_tranfer,atcmd->use_len);
}
return 0;
}
2. check系列函数
在我们使用串口发送AT指令前必须检查串口是否已经准备好了,以及SIM卡是否存在4G模块的卡槽里,是否已经注册、是否有信号等,接下来逐一进行封装。
2.1 check_serial_ready()
函数功能:检查串口是否已经就绪。
原理:在串口下我们发送AT指令后,如果串口就绪,就会返回OK,否则就返回ERROR。我们通过返回的字符串(use_buf)是否含有OK来判断是否已经就绪。
代码:
/*-----------------------------------------------------------------------
| funtion:Check serial port whether already ready.
|
| argument: serial_fd: serial port fd.
|
| return: rv: success: == 0 ; error: < 0
|-----------------------------------------------------------------------*/
int check_serial_ready(serial_ctx_t *serial)
{
int rv = 0;
atcmd_ctx_t atcmd;
if(!serial)
{
printf("%s,Invalid input arguments\n",__func__);
return -1;
}
memset(&atcmd,0,sizeof(atcmd_ctx_t));
strncpy(atcmd.send_at_buf,"AT\r",sizeof(atcmd.send_at_buf));
strncpy(atcmd.succe_at_buf,"OK",sizeof(atcmd.succe_at_buf));
atcmd.resp_timeout = 2;
rv = send_recv_atcmd(serial,&atcmd);
if(rv < 0)
{
printf("send_resp_serial() of %s failure and rv: %d\n",__func__,rv);
return -2;
}
return 0;
}
2.2 check_sim_exist()
函数功能:检查SIM卡就是否存在
原理:发送”AT+CPIN?”,如果存在则返回“READY”,否则则返回“ERROR”,同样通过判断返回的字符串(use_buf)是否含有“READY”来判断SIM卡是否存在。
代码:
/*-----------------------------------------------------------------------
| funtion:Check sim cart whether already exist.
|
| argument: serial_fd: serial port fd.
|
| return: rv: success: == 0 ; error: < 0
|-----------------------------------------------------------------------*/
int check_sim_exist(serial_ctx_t *serial)
{
int rv = 0;
atcmd_ctx_t atcmd;
if(!serial)
{
printf("%s,Invalid input arguments\n",__func__);
return -1;
}
memset(&atcmd,0,sizeof(atcmd_ctx_t));
strncpy(atcmd.send_at_buf,"AT+CPIN?\r",sizeof(atcmd.send_at_buf));
strncpy(atcmd.succe_at_buf,"READY",sizeof(atcmd.succe_at_buf));
atcmd.resp_timeout = 2;
rv = send_recv_atcmd(serial,&atcmd);
if(rv < 0)
{
printf("send_resp_serial() of %s failure and rv: %d\n",__func__,rv);
return -2;
}
return 0;
}
2.3 check_sim_register()
函数功能:判断SIM卡是否已经注册上。
原理:发送”AT+CREG?”,如果已经注册上则返回“0,1”,否则则返回“ERROR”,同样通过判断返回的字符串(use_buf)是否含有“0,1”来判断SIM卡是否已经存在。
代码:
/*-----------------------------------------------------------------------
| funtion:Check sim cart whether already register.
|
| argument: serial_fd: serial port fd.
|
| return: rv: success: == 0 ; error: < 0
|-----------------------------------------------------------------------*/
int check_sim_register(serial_ctx_t *serial)
{
int rv = 0;
atcmd_ctx_t atcmd;
if(!serial)
{
printf("%s,Invalid input arguments\n",__func__);
return -1;
}
memset(&atcmd,0,sizeof(atcmd_ctx_t));
strncpy(atcmd.send_at_buf,"AT+CREG?\r",sizeof(atcmd.send_at_buf));
strncpy(atcmd.succe_at_buf,"0,1",sizeof(atcmd.succe_at_buf));
atcmd.resp_timeout = 2;
rv = send_recv_atcmd(serial,&atcmd);
if(rv < 0)
{
printf("send_resp_serial() of %s failure and rv: %d\n",__func__,rv);
return -2;
}
return 0;
}
2.4 check_sim_signal()
函数功能:检查SIM卡是否有信号或者说信号是否符号发送要求。
原理: SIM的信号强度也需要检测,太低会影响短信的发送,99,99表示无信号,发送”AT+CSQ”,通过判断返回的字符串(use_buf)中的值来判断来判断SIM卡的信号强度是否符合。第一个值表示强度:在7~31表示正常,越高越好,不在这个范围表示信号太强或者太弱;第二个值通常为99
代码:
/*--------------------------------------------------------------------------
| funtion:Check sim cart whether already signal.
|
| argument: serial_fd: serial port fd.
|
| return: rv: success: == 0 ; error: < 0
|---------------------------------------------------------------------------*/
int check_sim_signal(serial_ctx_t *serial,gsm_ctx_t *gsm_ctx)
{
int i = 0;
int rv = 0;
atcmd_ctx_t atcmd;
char judge_sig[8] = {0};
int judge_sig_int = 0;
if(!serial || !gsm_ctx)
{
printf("%s,Invalid input arguments\n",__func__);
return -1;
}
memset(&atcmd,0,sizeof(atcmd_ctx_t));
strncpy(atcmd.send_at_buf,"AT+CSQ\r",sizeof(atcmd.send_at_buf));
strncpy(atcmd.succe_at_buf,"+CSQ",sizeof(atcmd.succe_at_buf));
atcmd.use_len = sizeof(atcmd.use_buf);
atcmd.resp_timeout = 2;
rv = send_recv_atcmd(serial,&atcmd);
if(rv < 0)
{
printf("send_resp_serial() of %s failure and rv: %d\n",__func__,rv);
return -2;
}
if(!atcmd.use_buf)
{
printf("%s not copy AT response to use_buf\n",__func__);
return -3;
}
for(i=0; i < atcmd.use_len; i++)
{
if(',' == atcmd.use_buf[i])
{
if(' ' == atcmd.use_buf[i-2])
strncpy(judge_sig,&atcmd.use_buf[i-1],1); // the signal is single-signal, eg: 6,99
else
strncpy(judge_sig,&atcmd.use_buf[i-2],2); // the signal is double-signal, eg: 21,99
break;
}
}
judge_sig_int = atoi(judge_sig);
if(judge_sig_int < 7 || judge_sig_int > 31)
{
printf("sim card signal strength is too low or no signal and signal strength: %d\n",judge_sig_int);
return -4;
}
gsm_ctx->sim_signal = judge_sig_int;
return 0;
}
2.5 check_sim_serial()
函数功能:把上面的几个检查函数,封装为一个函数,在之后直接调用这个函数即可。
代码:
/*-----------------------------------------------------------------------
| funtion:Check sim card and serial port whether already ready.
|
| argument: serial: serial port property struct.
|
| return: rv: success: == 0 ; error: < 0
|-----------------------------------------------------------------------*/
int check_sim_serial(serial_ctx_t *serial,gsm_ctx_t *gsm_ctx)
{
int rv = 0;
if(!serial || !gsm_ctx)
{
printf("%s,Invalid input arguments\n",__func__);
return -1;
}
switch(gsm_ctx->status)
{
case GSM_STATUS_INIT:
if((rv = check_serial_ready(serial)) < 0)
{
printf("check serial port failure or [not ready] and rv: %d\n",rv);
break;
}
//printf("serial port alread ready\n");
gsm_ctx->status ++;
case GSM_STATUS_SERIAL_READY:
if((rv = check_sim_exist(serial)) < 0)
{
printf("check sim card failure or [not exist] and rv: %d\n",rv);
break;
}
//printf("sim card alread exist\n");
gsm_ctx->status ++;
case GSM_STATUS_SIM_EXIST:
if((rv = check_sim_register(serial)) < 0)
{
printf("check sim card failure or [not register] and rv: %d\n",rv);
break;
}
//printf("sim card alread register\n");
gsm_ctx->status ++;
case GSM_STATUS_SIM_REG:
if((rv = check_sim_signal(serial,gsm_ctx)) < 0)
{
printf("check sim card failure or [not signal] and rv: %d\n",rv);
break;
}
//printf("sim card have signal\n");
gsm_ctx->status ++;
default: break;
}
return gsm_ctx->status;
}
2.5.1 状态机的使用
状态机是有限状态自动机的简称,是现实事物运行规则抽象而成的一个数学模型。详情柯参考:状态机
先来解释什么是“状态”( State )。现实事物是有不同状态的,例如一个LED等,就有 亮 和 灭两种状态。我们通常所说的状态机是有限状态机,也就是被描述的事物的状态的数量是有限个,例如LED灯的状态就是两个 亮和 灭。
上述的2.5 的check_sim_serial()函数中就是使用了状态机来这个机制,利用Switch语句,来逐一判断串口就绪、SIM卡存在、注册、有信号这四个状态。因为这些状态的判断并不是在初始化的时候,只进行一次检查就可以了的,可能程序运行过程中SIM卡移动了,但是还继续进行下去就会出现错误,所以要把这些检查放到while循环里面,但是每次都要全部判断太麻烦了,而且会降低效率,这时候就可以时候状态机来判断记录状态了。
switch(gsm_ctx->status)
{
case GSM_STATUS_INIT:
if((rv = check_serial_ready(serial)) < 0)
{
printf("check serial port failure or [not ready] and rv: %d\n",rv);
break;
}
//printf("serial port alread ready\n");
gsm_ctx->status ++;
case GSM_STATUS_SERIAL_READY:
if((rv = check_sim_exist(serial)) < 0)
{
printf("check sim card failure or [not exist] and rv: %d\n",rv);
break;
}
//printf("sim card alread exist\n");
gsm_ctx->status ++;
case GSM_STATUS_SIM_EXIST:
if((rv = check_sim_register(serial)) < 0)
{
printf("check sim card failure or [not register] and rv: %d\n",rv);
break;
}
//printf("sim card alread register\n");
gsm_ctx->status ++;
case GSM_STATUS_SIM_REG:
if((rv = check_sim_signal(serial,gsm_ctx)) < 0)
{
printf("check sim card failure or [not signal] and rv: %d\n",rv);
break;
}
//printf("sim card have signal\n");
gsm_ctx->status ++;
default: break;
}
enum
{
GSM_STATUS_INIT = 0, //SIM card and serial port initial status.
GSM_STATUS_SERIAL_READY, //Serial port ready.
GSM_STATUS_SIM_EXIST, //SIM card exist.
GSM_STATUS_SIM_REG, //SIM card already register.
GSM_STATUS_SIM_SIG, //SIM card have signal/
GSM_STATUS_READY = GSM_STATUS_SIM_SIG, //SIM card and serial port all ready.
};
总结
本篇博客实现了AT指令的封装、check系列函数的实现以及状态机的利用。