Linux串口编程短信篇(三) ——— AT指令封装实现Check系列函数及SMS函数封装

一. 很重要的函数: send_at_cmd()

 标题并未提到这个send_at_cmd()函数,但可以这么说,该程序实现最终功能的最为核心的一个函数就是
send_at_cmd(),也是一个非常好用的函数… 那么,他到底重要在什么地方呢?又为什么说它好用呢?

1.1 设计思路

 刚开始学习串口编程时,写了这么两个函数,分别是 comport_send() 和 comport_recv(),这两个函数分别封装了 write() 和 read() 系统调用,主要用来实现向串口发送AT指令和接收串口回发的数据,后来,开始写发送中英文短信的函数,还有一些检查串口状态,检查SIM卡状态的函数,是这样实现的:

  1. 调用 comport_send 函数,向串口发送指定的AT指令:

      例如: comport_send(comport,“AT+CREG?\r”,len);

  1. 接下来,调用comport_recv() 函数读取串口发来的信息:

      comport_recv(comport,rbuf,buf_len,0); // 0表示不使用select阻塞

  1. 最后,要么打印rbuf,要么使用strstr() 函数来查找OK或者 ’ > ’ ( > 表示短信输入),来确定某些功能是否可用

      if(strstr(rbuf,“OK”)
        printf(" Received OK ! \n");

问题,因为很多地方都要用到这一系列的方法,所以,我将这三步进行了封装,将要发送的AT命令,期望收到的内容等信息通过参数传进函数中,如果AT指令发送成功且收到了我期望的字符,则函数成功,返回0,否则返回-1.

函数还有四个参数,分别是用来存储读取到的信息的buf,该参数可以为NULL,表示不关心串口返回的数据具体是什么,只需要知道有没有找到期望的字符,当然如果传了buf则保存至buf中,下一个参数是该参数的长度,最后一个参数用来指定read()时是否使用select() 进行计时阻塞,还有就是函数的第一个参数,必须是串口呀!

1.2 send_at_cmd()

/************************************************************************************ 
 *
 *     Function:  int send_at_cmd(ComportAttr *comport,char *atcmd,char *expected_recv,char *rmsg,int msgsize,int timeout)
 *
 *    Parameter:  ComportAttr *comport   -   Serial port for communication
 *              
 *                char *arcmt            -   Commands to be sent to the serial port
 *
 *                char *expected_recv    -   Expected information
 *
 *                char *rmsg             -   Used to save the received information, can be NULL 
 *
 *                int msgsize            -   The size of rmsg  
 *
 *                int timeout            -   If this option is not 0, the program will call select to block read
 *
 *  Description:  Send the command in "atcmd" to the serial port, read the serial port, if you read the common-
 *                value with the lieutenant colonel expected_recv, the function returns 0, otherwise  returns -1
 *
 * Return Value:  0                      -   AT command sent successfully and received the expected value
 *                
 *                negative number        -   AT command failed to send or did not receive the expected value
 *
 ************************************************************************************/
int send_at_cmd(ComportAttr *comport,char *atcmd,char *expected_recv,char *rmsg,int msgsize,int timeout)
{
    char   temp_msg[512] = {0};

    if(!atcmd || !expected_recv)
    {
        printf("Unable to send AT commond,Invalid parameter.\n");
        return -1;
    }

    if(comport_send(comport,atcmd,strlen(atcmd)) < 0)
    {
        printf("Send AT commond failed:%s\n",strerror(errno));
        return -2;
    }

    usleep(10000);

    if(comport_recv(comport,temp_msg,sizeof(temp_msg),timeout) <= 0)
    {
        printf("Recving message failed:%s\n",strerror(errno));
        return -3;
    }

    /* Search expectations */
    if(!strstr(temp_msg,expected_recv))
    {
        printf("Can't find what you expect to receive,\'expected_recv\' is not in \'rmsg\'\n");
        return -4;
    }
    if(rmsg)
    {
        strncpy(rmsg,temp_msg,msgsize);
    }
     
    return 0;
}

//7月25日 修改

正如上文所说,发送atcmd,如果读取的数据中找到了expected_recv ,则函数返回0.
关于comport_send() , comport_recv() 的实现请参考之前的博客.

二. Check系列函数

有了上面的send_at_cmd() 函数,Check系列函数的实现就简单了很多,下面来看,发短信之前,用来检查是否可以发短信的函数吧~

2.1 check_comport_ready()

设计原因: 发送短信前,必须先确保串口是可用的,通过发送 AT 并收到 OK 实现确认,因为不需要提取更多信息,所以第四个参数传NULL.
发送指令: AT
期望接收: OK
失败接收: ERROR
实际测试:
在这里插入图片描述
代码实现:

/* Check if the serial port can communicate */
int check_comport_ready(ComportAttr *comport)
{
    int    retval;

    retval = send_at_cmd(comport,"AT\r","OK",NULL,0,2);  //发送AT期望收到OK

    if(retval != 0)
    {
        printf("Serial port cannot be used\n");
        return retval;
    }

    return retval;
}

成功返回 0 .

2.2 check_if_there_is_sim()

设计原因: 串口能够通信,并不代表模块可以检测出SIM,有可能SIM卡安装不当,或者没有SIM,都需要检查
发送指令: AT+CPIN?
期望接收: READY
失败接收: ERROR
实际测试:
在这里插入图片描述
代码实现:

/* Check if the module recognizes the SIM card */
int check_if_there_is_sim(ComportAttr *comport)
{
    int    retval;

    retval = send_at_cmd(comport,"AT+CPIN?\r","READY",NULL,0,2);

    if(retval != 0)
    {
        printf("No SIM detected\n");
        return retval;
    }

    return retval;
}

成功返回 0 .

2.3 check_sim_login()

设计原因: 模块能检测出SIM卡,并不代表SIM已经注册上了,之前就是因为这个问题搞了很久
发送指令: AT+CREG?
期望接收: 0,1 or 0,3
失败接收: 不为0,1 或 0,3均不可用
实际测试:
在这里插入图片描述

代码实现:

/* Check the SIM card registration," 0,1 " , " 0,3 "  means available */
int check_sim_login(ComportAttr *comport)
{
    int    retval1;
    int    retval2;

    retval1 = send_at_cmd(comport,"AT+CREG?\r","0,1",NULL,0,2);
    retval2 = send_at_cmd(comport,"AT+CREG?\r","0,3",NULL,0,2);

    if(retval1 && retval2)
    {
        printf("SIM Card is not registered\n");
        return -1;
    }

    return 0;
}

成功返回 0 .

2.4 check_sim_signal()

设计原因: SIM的信号强度也需要检测,太低会影响短信的发送,99,99表示无信号
发送指令: AT+CSQ
期望接收: +CSQ: **,## —— **在8~31之间越高越好,##通常是99
失败接收: 不为0,1 或 0,3均不可用
实际测试:
在这里插入图片描述
ps:因为信号可能是个位数,也有可能是十位数,所以需要进行判断,因为这里需要用到读取的内容,所以传入msg保存.

代码实现:

/* Check the SIM card signal strength ,The signal strength is too low or 99 is unavailable */
int check_sim_signal(ComportAttr *comport)
{
    int    i;
    int    retval;
    int    signal_strength;
    char   str_signal[10] = {0};
    char   msg[128] = {0};

    retval = send_at_cmd(comport,"AT+CSQ\r","+CSQ",msg,sizeof(msg),2);

/* IF Sucess:
 *
 * AT+CSQ
 *
 * +CSQ: 9,99
 *
 * OK
 *
 * * * * * * * 
 * AT+CSQ
 *
 * +CSQ: 23,99
 *
 * OK
 *
 * */

    if(retval != 0)
    {
        printf("Can not check signal\n");
        return -1;
    }

    for(i = 0; i < sizeof(msg); i++)
    {
        if(msg[i] == ',')  //Locate ' , '
        {
            if(msg[i-2] == ' ')
                strncpy(str_signal,&msg[i-1],1);  //The signal strength is in single digits, for example: 8,99
            else
                strncpy(str_signal,&msg[i-2],2);  //The signal strength is ten digits, for example: 28,99
        }
    }

    signal_strength = atoi(str_signal);

    if(signal_strength < 7 || signal_strength == 99)  //Signal is too low or no signal
    {
        printf("Signal Strengh is too low or no signal\n");
        return -2;
    }

    return 0;
}

2.5 check_sim_allready()

封装了上述所有的check函数,全部满足则返回0,否则,返回 -1,-2…

/* Call 'check series' of functions, return 0 if all pass, otherwise return -1,-2.... */
int check_sim_allready(ComportAttr *comport)
{

    if(check_comport_ready(comport) < 0)
        return -1;
    printf("Serial port ready!\n");


    if(check_if_there_is_sim(comport) < 0)
        return -2;
    printf("SIM card can be detected!\n");


    if(check_sim_login(comport) < 0)
        return -3;
    printf("SIM card is registered!\n");


    if(check_sim_signal(comport) < 0)
        return -4;
    printf("Signal strength meets requirements!\n");

    printf("\nSIM cards are all ready!\n");

    return 0;
}

三. 获取短信中心号码

使用命令

AT+CSCA?

获取到保存了中心号码的buf后,操作buf获取中心号码
在这里插入图片描述

代码实现:

/* 
 * Obtain the SMS center number of the SIM card
 * Different operators and different regions will result in different SMS center numbers
 * China Telecom can not use.
 * */
int get_sms_center_number(ComportAttr *comport,char *center_number)
{
    int      retval;
    char    *ptr;
    char     rbuf[128] = {0};

    retval = send_at_cmd(comport,"AT+CSCA?\r","CSCA",rbuf,sizeof(rbuf),2);

/* IF Success:
 *
 * AT+CSCA?
 *
 * +CSCA: "+8613010788500",145
 *
 * OK
 *
 * */

    if(retval < 0)
    {
        printf("Can not receive CSCA\n");
        return -1;
    }

    ptr = strstr(rbuf,"CSCA");
    ptr += 7;

    strncpy(center_number,ptr,CENTER_NUM_LEN);

    return 0;
}

四. TEXT / PDU SMS SEND

 在写好PDU编码,send_at_cmd() 等功能模块的函数后,再来写发送短信的函数就会简单很多,只要搞清楚AT发短信的流程就很轻松的写出来了. 关于AT发送短信的步骤需要搞懂,可参考:AT指令发送中英文短信详细流程

4.1 TEXT SMS SEND

因为ASCII编码足以表示所有英文字符以及英文符号,不需要用到PDU编码,所以才把TEXT格式的短信单独列出来,发送短信的步骤也简单很多

在这里插入图片描述
代码:

/* Send SMS in TEXT format */
int text_sms_send(ComportAttr *comport,char *sms_buf,char *phone_number)
{
    int    retval;
    char   sbuf[30] = {0};

    if(!sms_buf || !phone_number)
    {
        printf("Invalid parameter\n");
        return -1;
    }

    retval = send_at_cmd(comport,"AT+CMGF=1\r","OK",NULL,0,2);  //"AT+CMGF=1" means TEXT SMS
    if(retval < 0)
    {
        printf("Send \"AT+CMGF=1\" failed or can not receive \"OK\"\n");
        return -2;
    }

    /* Phone Number */
    sprintf(sbuf,"AT+CMGS=\"%s\"\r",phone_number);

    /* Expect to receive ' > ' */
    retval = send_at_cmd(comport,sbuf,">",NULL,0,2);
    if(retval < 0)
    {
        printf("Send AT+CMGS failed or can not receive \'>\'\n");
        return -3;
    }

    strcat(sms_buf,"\x1a");

    /* Send SMS,'\x1a'is the end of SMS sending Peugeot */
    retval = send_at_cmd(comport,sms_buf,"OK",NULL,0,10);
    if(retval < 0)
    {
        printf("SMS send failed\n");
        return -4;
    }

    printf("SMS sent successfully!\n");

    return 0;
}

4.2 PDU SMS SEND

 写到这里,就会发现之前的努力都是值得的,正是因为前面将所有需要用到的功能模块都进行的封装,PDU编码只需要传入几个参数便可完成,所以,再来写PDU SMS SEND 函数时,只需要清楚发送PDU格式短信的步骤,同样可以很轻松的写出函数啦!

在这里插入图片描述

代码:

/* Send PDU SMS , Will call the PDU encoded function */
int pdu_sms_send(ComportAttr *comport,char *sms_buf,char *phone_number)
{
    int     value_cmgs;
    char    pdu_buf[512] = {0};
    char    sbuf[32] = {0};
    char    center_number[128] = {0};

    if(!sms_buf || !phone_number)
    {
        printf("Invalid parameter\n");
        return -1;
    }

    if(get_sms_center_number(comport,center_number) < 0)  //Obtain SMS Center Number
    {
        printf("Can not get SMS center number\n");
        return -2;
    }

    if(pdu_encod(sms_buf,center_number,phone_number,pdu_buf,&value_cmgs) < 0)  //PDU encoding
    {
        printf("Failed to construct PDU code\n");
        return -3;
    }

    if(send_at_cmd(comport,"AT+CMGF=0\r","OK",NULL,0,2) < 0)  //"AT+CMGF=0" means PDU SMS
    {
        printf("Send \"AT+CMGF=0\" failed\n");
        return -4;
    }

    /* Splice the processed phone number with the processed SMS into a string
     * value_cmgs represents one-half the length of the string, in decimal.
     * */
    sprintf(sbuf,"AT+CMGS=%d\r",value_cmgs);

    /*  Expect to receive ' > ' */
    if(send_at_cmd(comport,sbuf,">",NULL,0,2) < 0)  //Most of the failures are due to PDU encoding failure
    {
        printf("Send \"AT+CMGS=%d\"failed,or can not receive \'>\'\n",value_cmgs);
        return -5;
    }

    strcat(pdu_buf,"\x1a");

    /* If it can't receive OK within 10s,return */
    if(send_at_cmd(comport,pdu_buf,"OK",NULL,0,10) < 0)
    {
        printf("PDU SMS send failed\n");
        return -6;
    }

    printf("SMS sent successfully!\n");

    return 0;
}

五. 头文件 sms.h

/********************************************************************************
 *      Copyright:  (C) 2020 LuXiaoyang<920916829@qq.com>
 *                  All rights reserved.
 *
 *       Filename:  comport.h
 *    Description:  This head file of sms.c
 *
 *        Version:  1.0.0(09/07/20)
 *         Author:  LuXiaoyang <920916829@qq.com>
 *      ChangeLog:  1, Release initial version on "09/07/20 08:58:33"
 *                 
 ********************************************************************************/
#ifndef  _SMS_H_
#define  _SMS_H_

#include "comport.h"
#include "PDU.h"

#define CENTER_NUM_LEN 14




/* Send AT Commond(comport_send plus) */
int send_at_cmd(ComportAttr *comport,char *atcmd,char *expected_recv,char *rmsg,int msgsize,int timeout);

/* Check if the serial port can communicate */
int check_comport_ready(ComportAttr *comport);

/* Check if the module can recognize the SIM card */
int check_if_there_is_sim(ComportAttr *comport);

/* Check the SIM card registration */
int check_sim_login(ComportAttr *comport);

/* Check the SIM card signal strength */
int check_sim_signal(ComportAttr *comport);

/* Use the above function to check whether the SIM card is ready to be used */
int check_sim_allready(ComportAttr *comport);

/* Get SMS Center Number of the SIM Card */
int get_sms_center_number(ComportAttr *comport,char *center_number);

/* Send TEXT Message */
int text_sms_send(ComportAttr *comport,char *sms_buf,char *phone_number);

/* Send PDU message */
int pdu_sms_send(ComportAttr *comport,char *sms_buf,char *phone_number);


#endif   /*   ----- #ifndef _COMPORT_H_  ----- */


本系列函数属于较上层的功能实现函数,封装了send_at_cmd() 函数,从而轻松的实现其他功能函数,通过该函数,设计了检查串口和SIM卡相关状态的一系列函数,在通过check_sim_allready() 将check系列函数进行封装,从而一个函数就能实现发送短信前的检测工作;
在发送短信时,也只是停留在传入参数即可实现所有功能,短信中心号,电话号码,UTF-8等数据的处理,统统交由PDU.c中的pdu_encod()函数实现,函数要做的仅仅是与发送指令而已,像这样使用一层一层的封装,调用,使得函数的移植性得到了很大的提升,这样,在真正的main程序中,只需要简单的调用几个函数即可实现全部功能…

  • 6
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值