VC++下应用AT命令对短信息的编程

来源:互联网 发布:log4j如何打印执行SQL 编辑:程序博客网 时间:2024/05/22 13:25

 

  前面数次连载我们以较长的篇幅讲解了串口通信的硬件原理、DOS平台控制以及基于WIN32 API、控件和第三方类的串口编程。作为本系列文章的最后一次连载,本章将给出一个典型的应用实例:西门子短信服务模块TC35的串口控制。

  1.短信控制终端

  作为短信 (Short Message ServiceSMS)一族,想必你有这样的体会:用手机编辑短信息十分不便、容易出错,而且修改费时,若能用计算机来收发短信则方便许多。注意,本文所说的用计算机收发短信并不是说通过"网易短信王"等方式在Internet上收发短信,而是直接用计算机控制运行了GSM通信系统的短信终端进行收发,因而其收发短信的原理与手机是本质相同的。

  实际上,一大堆的垃圾短信也是采用这种短信终端发出来的!

  我们来介绍一款GSM模块,它就是西门子公司的TC35,它由GSM基带处理器、电源专用集成电路、射频电路和闪速存储器等部分组成,负责处理GSM蜂窝设备中的音频、数据和信号,内嵌的软件部分执行应用接口和所有GSM协议栈的功能。TC35支持中文短信息,工作在EGSM900GSM1800双频段,电源范围为3.3~5.5V,可传输语音和数据信号,消耗功率在EGSM900(4)GSM1800(1)分别为2W1W,通过接口连接器和天线连接器分别连接SIM卡读卡器和天线。TC35的数据接口(CMOS电平)通过AT命令可双向传输指令和数据,可选波特率为300bit/s~115kbit/s,自动波特率为1.2k~115kbit/s。它支持文本和PDU格式的,可通过AT命令或关断信号实现重启和故障恢复。

  我们需要利用以TC35模块为主的硬件组成一个TC35终端设备,并与电脑通过RS-232C串口相连,并自行编制在PC上运行的短信息收发软件,就可以组成一个短信收发系统。TC35终端电路如下图所示:

 

 

TC35的控制主要包含如下几类指令:

  (1)初始化指令

  设置短消息发送格式AT+CMGF=1<CR>,设置1代表PDU模式,<CR>是回车符号,也就是0x0d,指令正确则模块返回<CRLF>OK<CRLF><CRLF>是回车换行符号。

  (2)设置/读取短消息中心

  短消息中心号码由移动运营商提供。

  设置短消息中心的指令格式为:

  AT+CSCA=″+8613800531500″(短消息中心)<CR>

  设置正确则模块返回<CRLF>OK<CRLF>

  读取短消息服务中心则使用命令:

  AT+CSCA=?<CR>

  TC35模块应该返回:

  <CRLF>+CSCA:″8613800531500″<CRLF>

  (3)设置短消息到达自动提示

  设置短消息到达自动提示的指令格式为:

  AT+CNMI=1,1,0,0,1<CR>

  设置正确则TC35模块返回:

  <CRLF>OK<CRLF>

  设置此命令可使模块在短消息到达后向串口发送指令:

  <CRLF>+CMTI:″SM″,INDEX(信息存储位置)<CRLF>

  通过TC35发送短消息的方法为:

  PC上的控制软件按照PDU的格式发送和接收数据,短消息的内容可以是中文或者其他字符。在PDU模式,如果发送短消息,则首先发送短消息数据的长度:

  AT+CMGS=<length><CR>

  等待TC35模块返回ASCII字符">",则可以将PDU数据输入,PDU数据以<Z>(也就是0x1a)作为结束符。短消息发送成功,模块返回:

  <CRLF>OK<CRLF>

 

通过TC35接收短消息的方法为:

  短消息到来后,串口上会接收到指令

  <CRLF>+CMTI:″SM″,INDEX(信息存储位置)<CRLF>

  PC上的控制软件通过读取PDU数据的AT命令

  AT+CMGR=INDEX<CRLF>

  将TC35模块中PDU格式的短消息内容读出。如果用+CMGL代替+CMGR,则可一次性读出全部短消息。

  通过TC35删除短消息的方法为:

  PC上的控制软件收到一条短消息并处理后,需要将其在SIM卡上删除,以防止SIM卡饱和。删除短消息的指令为:

  AT+CMGD=INDEX<CR>

  删除后模块返回

  <CRLF>OK<CRLF>

  2.程序实例

  由于本文的宗旨在于讲解串口通信,因此,我们屏蔽图形用户界面的细节,制作一个简单的短信收发软件,它包含了控制短信终端的所有串口通信内容。实际上,一个理想的短信收发软件的界面应类似于OutlookFoxmail,包含收件箱、发件箱、已发送短信箱等内容,但是这些东西都与我们要介绍的串口通信无关,因此,下面的软件界面虽"败絮其外",但仍可称得上"金玉其中"

  关于界面上控件的描述如下:

BEGIN
 EDITTEXT IDC_SMSCONTENT_EDIT,39,61,242,38,ES_AUTOHSCROLL
 PUSHBUTTON "发送",IDC_SEND_BUTTON,316,80,45,18
 GROUPBOX "接收短消息",IDC_STATIC,28,124,361,167
 LTEXT "对方手机号",IDC_STATIC,41,35,42,11
 EDITTEXT IDC_PHONENUM_EDIT,88,30,192,17,ES_AUTOHSCROLL
 PUSHBUTTON "清除",IDC_CLEAR_BUTTON,316,30,45,18
 GROUPBOX "发送短消息",IDC_STATIC,29,19,361,95
 LISTBOX IDC_RECVSMS_LIST,43,137,331,127,LBS_SORT |
 LBS_NOINTEGRALHEIGHT | WS_VSCROLL | WS_TABSTOP
 PUSHBUTTON "接收",IDC_RECV_BUTTON,77,269,55,16
 PUSHBUTTON "清空",IDC_DELETEALL_BUTTON,273,268,45,14
END

对话框类的消息映射为:

BEGIN_MESSAGE_MAP(CSMSControlDlg, CDialog)
//{{AFX_MSG_MAP(CSMSControlDlg)
 ON_WM_SYSCOMMAND()
 ON_WM_PAINT()
 ON_WM_QUERYDRAGICON()
 ON_BN_CLICKED(IDC_CLEAR_BUTTON, OnClearButton)
 ON_BN_CLICKED(IDC_SEND_BUTTON, OnSendButton)
 ON_BN_CLICKED(IDC_RECV_BUTTON, OnRecvButton)
 ON_BN_CLICKED(IDC_DELETEALL_BUTTON, OnDeleteallButton)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()

  感谢《通过串口收发短消息》一文的作者bhw98,他为我们编写了数个独立于操作系统平台的C函数,使得我们可以在应用程序中直接对这些函数进行调用。在本控制软件中,也对这些函数进行了充分利用。

  下面是对本例程软件的主要数据结构和核心函数的介绍:

  数据结构

// 用户信息编码方式
#define GSM_7BIT 0
#define GSM_8BIT 4
#define GSM_UCS2 8
//
短消息参数结构,编码/解码共用
//
其中,字符串以0结尾
typedef struct
{
 char SCA[16]; //短消息服务中心号码(SMSC地址)
 char TPA[16]; //目标号码或回复号码(TP-DATP-RA)
 char TP_PID; //用户信息协议标识(TP-PID)
 char TP_DCS; //用户信息编码方式(TP-DCS)
 char TP_SCTS[16]; //服务时间戳字符串(TP_SCTS),接收时用到
 char TP_UD[161]; //原始用户信息(编码前或解码后的TP-UD)
 char index; //短消息序号,在读取时用到
} SM_PARAM;

  发送短消息

  发送按钮对应的函数为CSMSControlDlg::OnSendButton,它读取用户输出并根据目标电话号码和短信息内容形成SM_PARAM(PDU参数)的内容,接着进行发送:

 

void CSMSControlDlg::OnSendButton()
{
 // TODO: Add your control notification handler code here
 //获得用户输入
 CString desPhoneNum;
 CString smsContent;
 GetDlgItemText(IDC_PHONENUM_EDIT,desPhoneNum);
 GetDlgItemText(IDC_SMSCONTENT_EDIT,smsContent);
 //填充SM_PARAM结构体内容
 SM_PARAM smParam;
 smParam = CreateSMPARAMStruct(desPhoneNum,smsContent);
 //发送短信息
 gsmSendMessage(smParam);
}

  其中调用的gsmSendMessage函数体现了串口通信的核心内容,它按照第1节阐述的GSM模块发送短消息的串口控制流程进行短信的发送:

BOOL gsmSendMessage(const SM_PARAM *pSrc // pSrc:PDU参数指针)
{
 int nPduLength; // PDU串长度
 unsigned char nSmscLength; // SMSC串长度
 int nLength; //串口收到的数据长度
 char cmd[16]; //命令串
 char pdu[512]; // PDU
                                                       char ans[128]; //应答串
 nPduLength = gsmEncodePdu(pSrc, pdu); //根据PDU参数,编码PDU
 strcat(pdu, "x01a"); //Ctrl-Z结束
 gsmString2Bytes(pdu, &nSmscLength, 2); //PDU串中的SMSC信息长度
 nSmscLength++; //加上长度字节本身
 //命令中的长度,不包括SMSC信息长度,以数据字节计
 sprintf(cmd, "AT+CMGS=%d
", nPduLength / 2-nSmscLength); //
生成命令
 WriteComm(cmd, strlen(cmd)); //先输出命令串
 nLength = ReadComm(ans, 128); //读应答数据
 
 //根据能否找到"
> "
决定成功与否
 if (nLength == 4 && strncmp(ans, "
> ", 4) == 0)
 {
  WriteComm(pdu, strlen(pdu)); //得到肯定回答,继续输出PDU
  nLength = ReadComm(ans, 128); //读应答数据
                                                                                     
  //根据能否找到"+CMS ERROR"决定成功与否
  if (nLength > 0 && strncmp(ans, "+CMS ERROR", 10) != 0)
  {
   return TRUE;
  }
 }
 return FALSE;
}

 

读取短消息

  点击"接收"按钮会通过gsmReadMessage函数的调用获得所有短消息,最后在列表控件中显示所有短信:

void CSMSControlDlg::OnRecvButton()
{
 // TODO: Add your control notification handler code here
 SM_PARAM smParam[100];//短信缓冲区
 int smsNum;//短信条数
 smsNum = gsmReadMessage(smParam);//读取短信
 //显示短信
 for(int i=0;i<smsNum;i++)
 {
  m_recvlist.AddString(CString(smsNum[i].TPA)+smsNum[i].TP_UD);
 }
}

  其中调用的gsmReadMessage函数完成最核心的短信接收功能,它按照第1节阐述的GSM模块接收短消息的串口控制流程进行短信的接收:

// 参数:pMsg短消息缓冲区,必须足够大
//
返回:短消息条数
int gsmReadMessage(SM_PARAM* pMsg)
{
 int nLength; //串口收到的数据长度
 int nMsg; //短消息计数值
 char* ptr; //内部用的数据指针
 char cmd[16]; //命令串
 char ans[1024]; //应答串
 nMsg = 0;
 ptr = ans;
 sprintf(cmd, "AT+CMGL
"); //
生成命令,用+CMGL可一次性读出全部短消息
                  
 WriteComm(cmd, strlen(cmd)); //输出命令串
 nLength = ReadComm(ans, 1024); //读应答数据
 //根据能否找到"+CMS ERROR"决定成功与否
 if(nLength > 0 && strncmp(ans, "+CMS ERROR", 10) != 0)
 {
  //循环读取每一条短消息,"+CMGL:"开头
  while((ptr = strstr(ptr, "+CMGL:")) != NULL)
  {
   ptr += 6; //跳过"+CMGL:"
   sscanf(ptr, "%d", &pMsg->index); //读取序号
                   
   ptr = strstr(ptr, "
"); //
找下一行
   ptr += 2; //跳过"
"
   gsmDecodePdu(ptr, pMsg); // PDU串解码
   pMsg++; //准备读下一条短消息
   nMsg++; //短消息计数加1
  }
 }
  return nMsg;
}

删除短消息

  我们可以在读取完所有短信息后调用gsmDeleteMessage函数在GSM模块上删除那些已经被接收到PC上的短信息,它按照第1节阐述的GSM模块删除短消息的串口控制流程进行短信的删除:

// index: 短消息序号,从1开始
BOOL gsmDeleteMessage(const int index)
{
 int nLength; //串口收到的数据长度
 char cmd[16]; //命令串
 char ans[128]; //应答串
 sprintf(cmd, "AT+CMGD=%d
", index); //
生成命令
 //输出命令串
 WriteComm(cmd, strlen(cmd));
 //读应答数据
 nLength = ReadComm(ans, 128);
 //根据能否找到"+CMS ERROR"决定成功与否
 if (nLength > 0 && strncmp(ans, "+CMS ERROR", 10) != 0)
 {
  return TRUE;
 }
 return FALSE;
}

  在PC控制软件的短信列表框中删除所有短消息的"清空"按钮函数为:

void CSMSControlDlg::OnDeleteallButton()
{
 // TODO: Add your control notification handler code here
 m_recvlist.ResetContent();
}

  设置//写串口

  在应用程序启动与退出及gsmSendMessagegsmReadMessagegsmDeleteMessage函数中广泛使用的串口相关函数用WIN32 API实现:

// 串口设备句柄
HANDLE hComm;
//
打开串口
// pPort:
串口名称或设备路径,可用"COM1""\.COM1"两种方式,建议用后者
// nBaudRate:
波特率
// nParity:
奇偶校验
// nByteSize:
数据字节宽度
// nStopBits:
停止位
BOOL OpenComm(const char *pPort, int nBaudRate, int nParity, int nByteSize, int
nStopBits)
{
 DCB dcb; //串口控制块
 COMMTIMEOUTS timeouts =
 {
  //串口超时控制参数
  100, //读字符间隔超时时间: 100 ms
  1, //读操作时每字符的时间: 1 ms (n个字符总共为n ms)
  500, //基本的(额外的)读超时时间: 500 ms
  1, //写操作时每字符的时间: 1 ms (n个字符总共为n ms)
  100
 }; //基本的(额外的)写超时时间: 100 ms
 hComm = CreateFile(pPort, //串口名称或设备路径
  GENERIC_READ | GENERIC_WRITE, //读写方式
  0, //共享方式:独占
  NULL, //默认的安全描述符
  OPEN_EXISTING, //创建方式
  0, //不需设置文件属性
  NULL); //不需参照模板文件
 if (hComm == INVALID_HANDLE_VALUE)
  return FALSE;
 //打开串口失败
 GetCommState(hComm, &dcb); //DCB
 dcb.BaudRate = nBaudRate;
 dcb.ByteSize = nByteSize;
 dcb.Parity = nParity;
 dcb.StopBits = nStopBits;
 SetCommState(hComm, &dcb); //设置DCB
 
 SetupComm(hComm, 4096, 1024); //设置输入输出缓冲区大小
 SetCommTimeouts(hComm, &timeouts); //设置超时
 return TRUE;
}
//
关闭串口
BOOL CloseComm()
{
 return CloseHandle(hComm);
}
//
写串口
// pData:
待写的数据缓冲区指针
// nLength:
待写的数据长度
void WriteComm(void *pData, int nLength)
{
 DWORD dwNumWrite; //串口发出的数据长度
 WriteFile(hComm, pData, (DWORD)nLength, &dwNumWrite, NULL);
}
//
读串口
// pData:
待读的数据缓冲区指针
// nLength:
待读的最大数据长度
//
返回:实际读入的数据长度
int ReadComm(void *pData, int nLength)
{
 DWORD dwNumRead; //串口收到的数据长度
 ReadFile(hComm, pData, (DWORD)nLength, &dwNumRead, NULL);
 return (int)dwNumRead;
}

  编/解码GSM短消息

  陷于本文的篇幅,这里只给出编解码函数的原型,具体请参看GSM标准及《通过串口收发短消息》一文。

// UCS2编码返回:目标编码串长度
int gsmEncodeUcs2(const char *pSrc, //
源字符串指针
 unsigned char *pDst, // pDst:目标编码串指针
 int nSrcLength // nSrcLength:源字符串长度
);
// UCS2
解码返回:目标字符串长度
int gsmDecodeUcs2(const unsigned char *pSrc, //
源编码串指针
char *pDst, // pDst:
目标字符串指针
int nSrcLength // nSrcLength:
源编码串长度
);
//
可打印字符串转换为字节数据返回:目标数据长度
//
如:"C8329BFD0E01" --> {0xC8, 0x32, 0x9B, 0xFD, 0x0E, 0x01}
int gsmString2Bytes(const char *pSrc, // pSrc:
源字符串指针
unsigned char *pDst, // pDst:
目标数据指针
int nSrcLength // nSrcLength:
源字符串长度
);
//
字节数据转换为可打印字符串返回:目标字符串长度
//
如:{0xC8, 0x32, 0x9B, 0xFD, 0x0E, 0x01} --> "C8329BFD0E01"
int gsmBytes2String(const unsigned char *pSrc, // pSrc:
源数据指针
char *pDst, // pDst:
目标字符串指针
int nSrcLength // nSrcLength:
源数据长度
);

  3.总结

  串口编程的核心在于串口通信方式(发送、接收和握手)的控制,而具体的应用领域反而是次要的。掌握了根本的原理,就可以灵活地将其应用于任意领域,综合实例中的例子"短信控制终端"只是冰山一角。

 

原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 wow dk老拉嘲讽怎么办 4k屏幕字体太小怎么办 车被冰雹打了怎么办 玩3d游戏头晕怎么办 dnf黑龙大会秒杀怎么办 黑曼巴c挂不上弦怎么办 费效比超过100%怎么办 入职体检不报销怎么办 入职体检过不了怎么办 入职体检血糖高怎么办 入职体检没过怎么办 入职体检血压高怎么办 看病没带医保卡怎么办 孕妇8个月血压高怎么办 怀孕3个月血压高怎么办 怀孕3个月血压低怎么办 38周孕妇血压高怎么办 孕前检查后怀孕怎么办 精子a加b成活率底怎么办 怀孕五周孕酮低怎么办 怀孕38天孕酮低怎么办 刚怀孕了孕酮低怎么办 怀孕2个月孕酮低怎么办 怀孕七周孕酮低怎么办 孕前检查孕酮低怎么办 甘油三酯偏高7.2怎么办 入职体检来例假怎么办 6激素检查喝水了怎么办 抽血前吃了东西怎么办 怀孕一个月孕酮低怎么办 怀孕了老是有痰怎么办 孕妇有很多白痰怎么办 血糖高怎么办吃什么好 5年糖尿病血糖高怎么办 血糖高引起的视力模糊怎么办 谷丙转氨酶和谷草转氨酶高怎么办 产检血糖有点高怎么办 孕期餐后血糖高怎么办 怀孕12周血糖高怎么办 怀孕2个月血糖高怎么办 怀孕7个月血糖高怎么办