当前位置:首页 >> 合同协议 >>

PDU

PDU 短信编程的问题大概有 10 年了吧,我在下面也粘一个,不过是用来对比的。 用 Comway 的短信猫,串口可以直接发送中英文短信,不需编码,例如: at^sms=13911694415 "这是一条直接发送短信的测试短信,This is a test message" 同样的功能按以前的论述需要好几页,如下: 通过串口收发短消息
关键字 短消息,串口,SMS,PDU,Unicode

Q 用串口连接 GSM 手机发送和接收短消息,在应用程序中如何编程实现?

Q 我们打算开发一个基于 GSM 短消息方式的 GPS 系统,如何利用 SMS 进行数据通信? A 首先,我们要对由 ESTI 制订的 SMS 规范有所了解。 与我们讨论的短消息收发有关的规范主要包括

GSM 03.38、GSM 03.40 和 GSM 07.05
。 前二者着重描述 SMS 的技术实现(含编码方式), 后者则规定了 SMS 的 DTE-DCE 接口标准(AT 命令集)。 一共有三种方式来发送和接收 SMS 信息:

Block Mode, Text Mode 和 PDU Mode。
1、Block Mode 已是昔日黄花,目前很少用了。 2、Text Mode 是纯文本方式,可使用不同的字符集,从技术上说也可用于发送中文短消息,但国内手机基 本上不支持,主要用于欧美地区。 3、 PDU Mode 被所有手机支持, 可以使用任何字符集, 这也是手机默认的编码方式。 Text Mode 比较简单, 而且不适合做自定义数据传输,我们就不讨论了。下面介绍的内容,是在 PDU Mode 下发送和接收短消息 的实现方法。 PDU 串表面上是一串 ASCII 码,由'0'-'9'、 'A'-'F'这些数字和字母组成。它们是 8 位字节的十六进制数,或 者 BCD 码十进制数。PDU 串不仅包含可显示的消息本身,还包含很多其它信息,如 SMS 服务中心号码、 目标号码、回复号码、编码方式和服务时间等。发送和接收的 PDU 串,结构是不完全相同的。我们先用两 个实际的例子说明 PDU 串的结构和编排方式。 例 1 发送:SMSC 号码是+8613800250500,对方号码是 13851872468,消息内容是"Hello!"。从手机发 出的 PDU 串可以是 08 91 68 31 08 20 05 05 F0 11 00 0D 91 68 31 58 81 27 64 F8 00 00 00 06 C8 32 9B FD 0E 01 对照规范,具体分析:

分段 含义 说明 08 SMSC 地址信息的长度 共 8 个八位字节(包括 91) 91 SMSC 地址格式(TON/NPI) 用国际格式号码(在前面加'+') 68 31 08 20 05 05 F0 SMSC 地址 8613800250500,补'F'凑成偶数个 11 基本参数(TP-MTI/VFP) 发送,TP-VP 用相对格式 00 消息基准值(TP-MR) 0 0D 目标地址数字个数 共 13 个十进制数(不包括 91 和'F') 91 目标地址格式(TON/NPI) 用国际格式号码(在前面加'+') 68 31 58 81 27 64 F8 目标地址(TP-DA) 8613851872468,补'F'凑成偶数个 00 协议标识(TP-PID) 是普通 GSM 类型,点到点方式 00 用户信息编码方式(TP-DCS) 7-bit 编码 00 有效期(TP-VP) 5 分钟 06 用户信息长度(TP-UDL) 实际长度 6 个字节 C8 32 9B FD 0E 01 用户信息(TP-UD) "Hello!"

例 2 接收:SMSC 号码是+8613800250500,对方号码是 13851872468,消息内容是"你好!"。手机接收到 的 PDU 串可以是 08 91 68 31 08 20 05 05 F0 84 0D 91 68 31 58 81 27 64 F8 00 08 30 30 21 80 63 54 80 06 4F 60 59 7D 00 21 对照规范,具体分析:

分段 含义 说明 08 地址信息的长度 个八位字节(包括 91) 91 SMSC 地址格式(TON/NPI) 用国际格式号码(在前面加'+') 68 31 08 20 05 05 F0 SMSC 地址 8613800250500,补'F'凑成偶数个 84 基本参数(TP-MTI/MMS/RP) 接收,无更多消息,有回复地址 0D 回复地址数字个数 共 13 个十进制数(不包括 91 和'F') 91 回复地址格式(TON/NPI) 用国际格式号码(在前面加'+') 68 31 58 81 27 64 F8 回复地址(TP-RA) 8613851872468,补'F'凑成偶数个 00 协议标识(TP-PID) 是普通 GSM 类型,点到点方式 08 用户信息编码方式(TP-DCS) UCS2 编码 30 30 21 80 63 54 80 时间戳(TP-SCTS) 2003-3-12 08:36:45 +8 时区 06 用户信息长度(TP-UDL) 实际长度 6 个字节 4F 60 59 7D 00 21 用户信息(TP-UD) "你好!" 若基本参数的最高位(TP-RP)为 0, 则没有回复地址的三个段。 从 Internet 上发出的短消息常常是这种情形。 注意号码和时间的表示方法,不是按正常顺序顺着来的,而且要以'F'将奇数补成偶数。

Q 上面两例中已经出现了 7-bit 和 UCS2 编码,请详细介绍一下这些编码方式?

A 在 PDU Mode 中,可以采用三种编码方式来对发送的内容进行编码,

它们是 7-bit、8-bit 和 UCS2 编码。
1、7-bit 编码用于发送普通的 ASCII 字符,它将一串 7-bit 的字符(最高位为 0)编码成 8-bit 的数据,每 8 个 字符可"压缩"成 7 个; 2、8-bit 编码通常用于发送数据消息,比如图片和铃声等;而 UCS2 编码用于发送 Unicode 字符。 3、PDU 串的用户信息(TP-UD)段最大容量是 140 字节,所以在这三种编码方式下,可以发送的短消息的 最大字符数分别是 160、140 和 70。这里,将一个英文字母、一个汉字和一个数据字节都视为一个字符。 需要注意的是,PDU 串的用户信息长度(TP-UDL),在各种编码方式下意义有所不同。7-bit 编码时,指原 始短消息的字符个数,而不是编码后的字节数。8-bit 编码时,就是字节数。UCS2 编码时,也是字节数, 等于原始短消息的字符数的两倍。 如果用户信息 (TP-UD) 中存在一个头 ( 基本参数的 TP-UDHI 为 1) ,在所有编码方式下,用户信息长度 (TP-UDL)都等于头长度与编码后字节数之和。 如果采用 GSM 03.42 所建议的压缩算法(TP-DCS 的高 3 位为 001),则该长度也是压缩编码后字节数或头 长度与压缩编码后字节数之和。 下面以一个具体的例子说明 7-bit 编码的过程。我们对英文短信"Hello!"进行编码: 将源串每 8 个字符分为一组(这个例子中不满 8 个)进行编码,在组内字符间压缩,但每组之间是没有什么 联系的。 用 C 实现 7-bit 编码和解码的算法如下: // 7-bit 编码 // pSrc: 源字符串指针 // pDst: 目标编码串指针 // nSrcLength: 源字符串长度 // 返回: 目标编码串长度 int gsmEncode7bit(const char* pSrc, unsigned char* pDst, int nSrcLength) { int nSrc; int nDst; int nChar; // 源字符串的计数值 // 目标编码串的计数值 // 当前正在处理的组内字符字节的序号,范围是 0-7 // 上一字节残余的数据

unsigned char nLeft; // 计数值初始化 nSrc = 0; nDst = 0;

// 将源串每 8 个字节分为一组,压缩成 7 个字节 // 循环该处理过程,直至源串被处理完

// 如果分组不到 8 字节,也能正确处理 while(nSrc<nSrcLength) { // 取源字符串的计数值的最低 3 位 nChar = nSrc & 7; // 处理源串的每个字节 if(nChar == 0) { // 组内第一个字节,只是保存起来,待处理下一个字节时使用 nLeft = *pSrc; } else { // 组内其它字节,将其右边部分与残余数据相加,得到一个目标编码字节 *pDst = (*pSrc << (8-nChar)) | nLeft; // 将该字节剩下的左边部分,作为残余数据保存起来 nLeft = *pSrc >> nChar; // 修改目标串的指针和计数值 pDst++; nDst++; } // 修改源串的指针和计数值 pSrc++; nSrc++; } // 返回目标串长度 return nDst; } // 7-bit 解码 // pSrc: 源编码串指针 // pDst: 目标字符串指针 // nSrcLength: 源编码串长度 // 返回: 目标字符串长度 int gsmDecode7bit(const unsigned char* pSrc, char* pDst, int nSrcLength) { int nSrc; int nDst; int nByte; // 源字符串的计数值 // 目标解码串的计数值 // 当前正在处理的组内字节的序号,范围是 0-6 // 上一字节残余的数据

unsigned char nLeft; // 计数值初始化

nSrc = 0; nDst = 0; // 组内字节序号和残余数据初始化 nByte = 0; nLeft = 0; // 将源数据每 7 个字节分为一组,解压缩成 8 个字节 // 循环该处理过程,直至源数据被处理完 // 如果分组不到 7 字节,也能正确处理 while(nSrc<nSrcLength) { // 将源字节右边部分与残余数据相加,去掉最高位,得到一个目标解码字节 *pDst = ((*pSrc << nByte) | nLeft) & 0x7f; // 将该字节剩下的左边部分,作为残余数据保存起来 nLeft = *pSrc >> (7-nByte); // 修改目标串的指针和计数值 pDst++; nDst++; // 修改字节计数值 nByte++; // 到了一组的最后一个字节 if(nByte == 7) { // 额外得到一个目标解码字节 *pDst = nLeft; // 修改目标串的指针和计数值 pDst++; nDst++; // 组内字节序号和残余数据初始化 nByte = 0; nLeft = 0; } // 修改源串的指针和计数值 pSrc++; nSrc++; }

*pDst = 0; // 返回目标串长度 return nDst; } 需要指出的是,7-bit 的字符集与 ANSI 标准字符集不完全一致,在 0x20 以下也排布了一些可打印字符, 但英文字母、阿拉伯数字和常用符号的位置两者是一样的。用上面介绍的算法收发纯英文短消息,一般情 况应该是够用了。如果是法语、德语、西班牙语等,含有 "?"、 "é"这一类字符,则要按上面编码的输出去 查表,请参阅 GSM 03.38 的规定。 8-bit 编码其实没有规定什么具体的算法,不需要介绍。 UCS2 编码是将每个字符(1-2 个字节)按照 ISO/IEC10646 的规定,转变为 16 位的 Unicode 宽字符。在 Windows 系统中,特别是在 2000/XP 中,可以简单地调用 API 函数实现编码和解码。如果没有系统的支 持,比如用单片机控制手机模块收发短消息,只好用查表法解决了。 Windows 环境下,用 C 实现 UCS2 编码和解码的算法如下: // UCS2 编码 // pSrc: 源字符串指针 // pDst: 目标编码串指针 // nSrcLength: 源字符串长度 // 返回: 目标编码串长度 int gsmEncodeUcs2(const char* pSrc, unsigned char* pDst, int nSrcLength) { int nDstLength; // UNICODE 宽字符数目 // UNICODE 串缓冲区

WCHAR wchar[128];

// 字符串-->UNICODE 串 nDstLength = ::MultiByteToWideChar(CP_ACP, 0, pSrc, nSrcLength, wchar, 128); // 高低字节对调,输出 for(int i=0; i<nDstLength; i++) { // 先输出高位字节 *pDst++ = wchar[i] >> 8; // 后输出低位字节 *pDst++ = wchar[i] & 0xff; } // 返回目标编码串长度 return nDstLength * 2; }

// UCS2 解码 // pSrc: 源编码串指针 // pDst: 目标字符串指针 // nSrcLength: 源编码串长度 // 返回: 目标字符串长度 int gsmDecodeUcs2(const unsigned char* pSrc, char* pDst, int nSrcLength) { int nDstLength; // UNICODE 宽字符数目 // UNICODE 串缓冲区

WCHAR wchar[128];

// 高低字节对调,拼成 UNICODE for(int i=0; i<nSrcLength/2; i++) { // 先高位字节 wchar[i] = *pSrc++ << 8; // 后低位字节 wchar[i] |= *pSrc++; } // UNICODE 串-->字符串 nDstLength = ::WideCharToMultiByte(CP_ACP, 0, wchar, nSrcLength/2, pDst, 160, NULL, NULL); // 输出字符串加个结束符 pDst[nDstLength] = '\0'; // 返回目标字符串长度 return nDstLength; } 用以上编码和解码模块,还不能将短消息字符串编码为 PDU 串需要的格式,也不能直接将 PDU 串中的用 户信息解码为短消息字符串,因为还差一个在可打印字符串和字节数据之间相互转换的环节。可以循环调 用 sscanf 和 sprintf 函数实现这种变换。下面提供不用这些函数的算法,它们也适用于单片机、DSP 编程 环境。 // 可打印字符串转换为字节数据 // 如:"C8329BFD0E01" --> {0xC8, 0x32, 0x9B, 0xFD, 0x0E, 0x01} // pSrc: 源字符串指针 // pDst: 目标数据指针 // nSrcLength: 源字符串长度 // 返回: 目标数据长度 int gsmString2Bytes(const char* pSrc, unsigned char* pDst, int nSrcLength) { for(int i=0; i<nSrcLength; i+=2) {

// 输出高 4 位 if(*pSrc>='0' && *pSrc<='9') { *pDst = (*pSrc - '0') << 4; } else { *pDst = (*pSrc - 'A' + 10) << 4; }

pSrc++; // 输出低 4 位 if(*pSrc>='0' && *pSrc<='9') { *pDst |= *pSrc - '0'; } else { *pDst |= *pSrc - 'A' + 10; } pSrc++; pDst++; } // 返回目标数据长度 returnnSrcLength / 2; } // 字节数据转换为可打印字符串 // 如:{0xC8, 0x32, 0x9B, 0xFD, 0x0E, 0x01} --> "C8329BFD0E01" // pSrc: 源数据指针 // pDst: 目标字符串指针 // nSrcLength: 源数据长度 // 返回: 目标字符串长度 int gsmBytes2String(const unsigned char* pSrc, char* pDst, int nSrcLength) { const char tab[]="0123456789ABCDEF"; // 0x0-0xf 的字符查找表

for(int i=0; i<nSrcLength; i++) { // 输出低 4 位 *pDst++ = tab[*pSrc >> 4];

// 输出高 4 位 *pDst++ = tab[*pSrc & 0x0f];

pSrc++; } // 输出字符串加个结束符 *pDst = '\0'; // 返回目标字符串长度 return nSrcLength * 2; } 关于 GSM 03.42 中的压缩算法,至今还没有发现哪里用过,这里我们就不讨论了。有兴趣的话,可深入研 究一下。

Q PDU 的核心编码方式已经清楚了,如何实现用 AT 命令收发短消息呢? A 在上篇中, 我们已经讨论了 7-bit, 8bit 和 UCS2 这几种 PDU 用户信息的编码方式, 并且给出了实现代码。 现在,重点描述 PDU 全串的编码和解码过程,以及 GSM 07.05 的 AT 命令实现方法。这些是底层的核心 代码,为了保证代码的可移植性,我们尽可能不用 MFC 的类,必要时用 ANSI C 标准库函数。 首先,定义如下常量和结构: // 用户信息编码方式 #define GSM_7BIT #define GSM_8BIT #define GSM_UCS2 0 4 8

// 短消息参数结构,编码/解码共用 // 其中,字符串以 0 结尾 typedef struct { char SCA[16]; char TPA[16]; char TP_PID; char TP_DCS; // 短消息服务中心号码(SMSC 地址) // 目标号码或回复号码(TP-DA 或 TP-RA) // 用户信息协议标识(TP-PID) // 用户信息编码方式(TP-DCS) // 原始用户信息(编码前或解码后的 TP-UD)

char TP_SCTS[16]; // 服务时间戳字符串(TP_SCTS), 接收时用到 char TP_UD[161]; char index; } SM_PARAM; 大家已经注意到 PDU 串中的号码和时间,都是两两颠倒的字符串。利用下面两个函数可进行正反变换: // 正常顺序的字符串转换为两两颠倒的字符串,若长度为奇数,补'F'凑成偶数 // 如:"8613851872468" --> "683158812764F8" // 短消息序号,在读取时用到

// pSrc: 源字符串指针 // pDst: 目标字符串指针 // nSrcLength: 源字符串长度 // 返回: 目标字符串长度 int gsmInvertNumbers(const char* pSrc, char* pDst, int nSrcLength) { int nDstLength; // 目标字符串长度 char ch; // 复制串长度 nDstLength = nSrcLength; // 两两颠倒 for(int i=0; i<nSrcLength;i+=2) { ch = *pSrc++; // 保存先出现的字符 // 复制先出现的字符 *pDst++ = *pSrc++; // 复制后出现的字符 *pDst++ = ch; } // 源串长度是奇数吗? if(nSrcLength & 1) { *(pDst-2) = 'F'; nDstLength++; } // 输出字符串加个结束符 *pDst = '\0'; // 返回目标字符串长度 return nDstLength; } // 两两颠倒的字符串转换为正常顺序的字符串 // 如:"683158812764F8" --> "8613851872468" // pSrc: 源字符串指针 // pDst: 目标字符串指针 // nSrcLength: 源字符串长度 // 返回: 目标字符串长度 int gsmSerializeNumbers(const char* pSrc, char* pDst, int nSrcLength) { int nDstLength; // 目标字符串长度 char ch; // 用于保存一个字符 // 补'F' // 目标串长度加 1 // 用于保存一个字符

// 复制串长度 nDstLength = nSrcLength; // 两两颠倒 for(int i=0; i<nSrcLength;i+=2) { ch = *pSrc++; // 保存先出现的字符 // 复制先出现的字符 *pDst++ = *pSrc++; // 复制后出现的字符 *pDst++ = ch; } // 最后的字符是'F'吗? if(*(pDst-1) == 'F') { pDst--; nDstLength--; } // 输出字符串加个结束符 *pDst = '\0'; // 返回目标字符串长度 return nDstLength; } 以下是 PDU 全串的编解码模块。为简化编程,有些字段用了固定值。 // PDU 编码,用于编制、发送短消息 // pSrc: 源 PDU 参数指针 // pDst: 目标 PDU 串指针 // 返回: 目标 PDU 串长度 int gsmEncodePdu(const SM_PARAM* pSrc, char* pDst) { int nLength; int nDstLength; // 内部用的串长度 // 目标 PDU 串长度 // 目标字符串长度减 1

unsigned char buf[256]; // 内部用的缓冲区 // SMSC 地址信息段 nLength = strlen(pSrc->SCA); // SMSC 地址字符串的长度 // SMSC 地址信息长度

buf[0] = (char)((nLength & 1) == 0 ? nLength : nLength + 1) / 2 + 1; buf[1] = 0x91; // 固定: 用国际格式号码

nDstLength = gsmBytes2String(buf, pDst, 2);

// 转换 2 个字节到目标 PDU 串 // 转换 SMSC 到目标

nDstLength += gsmInvertNumbers(pSrc->SCA, &pDst[nDstLength], nLength);

PDU 串 // TPDU 段基本参数、目标地址等 nLength = strlen(pSrc->TPA); buf[0] = 0x11; buf[1] = 0; // TP-DA 地址字符串的长度 // 是发送短信(TP-MTI=01),TP-VP 用相对格式(TP-VPF=10) // TP-MR=0 // 固定: 用国际格式号码

buf[2] = (char)nLength; // 目标地址数字个数(TP-DA 地址字符串真实长度) buf[3] = 0x91; nDstLength += gsmBytes2String(buf, &pDst[nDstLength], 4); // 转换 4 个字节到目标 PDU 串 nDstLength += gsmInvertNumbers(pSrc->TPA, &pDst[nDstLength], nLength); // 转 换 TP-DA 到 目 标 PDU 串 // TPDU 段协议标识、编码方式、用户信息等 nLength = strlen(pSrc->TP_UD); buf[0] = pSrc->TP_PID; buf[1] = pSrc->TP_DCS; buf[2] = 0; // 用户信息字符串的长度 // 协议标识(TP-PID) // 用户信息编码方式(TP-DCS)

// 有效期(TP-VP)为 5 分钟

if(pSrc->TP_DCS == GSM_7BIT) { // 7-bit 编码方式 buf[3] = nLength; // 编码前长度 // 转换 TP-DA 到目标 PDU 串

nLength = gsmEncode7bit(pSrc->TP_UD, &buf[4], nLength+1) + 4; } else if(pSrc->TP_DCS == GSM_UCS2) { // UCS2 编码方式 buf[3] = gsmEncodeUcs2(pSrc->TP_UD, &buf[4], nLength); nLength = buf[3] + 4; } else { // 8-bit 编码方式 buf[3] = gsmEncode8bit(pSrc->TP_UD, &buf[4], nLength); nLength = buf[3] + 4; } nDstLength += gsmBytes2String(buf, &pDst[nDstLength], nLength); 串 // 返回目标字符串长度 return nDstLength; } // PDU 解码,用于接收、阅读短消息 // pSrc: 源 PDU 串指针 // nLength 等于该段数据长度 // nLength 等于该段数据长度

// 转换 TP-DA 到目标 PDU 串

// 转换 TP-DA 到目标 PDU 串

// 转换该段数据到目标 PDU

// pDst: 目标 PDU 参数指针 // 返回: 用户信息串长度 int gsmDecodePdu(const char* pSrc, SM_PARAM* pDst) { int nDstLength; unsigned char tmp; // 目标 PDU 串长度 // 内部用的临时字节变量

unsigned char buf[256]; // 内部用的缓冲区 // SMSC 地址信息段 gsmString2Bytes(pSrc, &tmp, 2); tmp = (tmp - 1) * 2; pSrc += 4; // 指针后移 // 转换 SMSC 号码到目标 PDU 串 // 指针后移 // 取长度 // SMSC 号码串长度

gsmSerializeNumbers(pSrc, pDst->SCA, tmp); pSrc += tmp;

// TPDU 段基本参数、回复地址等 gsmString2Bytes(pSrc, &tmp, 2); pSrc += 2; if(tmp & 0x80) { // 包含回复地址,取回复地址信息 gsmString2Bytes(pSrc, &tmp, 2); if(tmp & 1) tmp += 1; pSrc += 4; // 指针后移 // 取 TP-RA 号码 // 指针后移 // 取长度 // 调整奇偶性 // 指针后移 // 取基本参数

gsmSerializeNumbers(pSrc, pDst->TPA, tmp); pSrc += tmp; } // TPDU 段协议标识、编码方式、用户信息等

gsmString2Bytes(pSrc, (unsigned char*)&pDst->TP_PID, 2); pSrc += 2; // 指针后移

// 取协议标识(TP-PID) // 取编码方式(TP-DCS)

gsmString2Bytes(pSrc, (unsigned char*)&pDst->TP_DCS, 2); pSrc += 2; // 指针后移

gsmSerializeNumbers(pSrc, pDst->TP_SCTS, 14); pSrc += 14; // 指针后移

// 服务时间戳字符串(TP_SCTS)

gsmString2Bytes(pSrc, &tmp, 2); pSrc += 2; // 指针后移

// 用户信息长度(TP-UDL)

if(pDst->TP_DCS == GSM_7BIT) { // 7-bit 解码 nDstLength = gsmString2Bytes(pSrc, buf, tmp & 7 ? (int)tmp * 7 / 4 + 2 : (int)tmp * 7 / 4); // 格 式 转 换 gsmDecode7bit(buf, pDst->TP_UD, nDstLength); nDstLength = tmp; // 转换到 TP-DU

} else if(pDst->TP_DCS == GSM_UCS2) { // UCS2 解码 nDstLength = gsmString2Bytes(pSrc, buf, tmp * 2); // 格式转换 // 转换到 TP-DU

nDstLength = gsmDecodeUcs2(buf, pDst->TP_UD, nDstLength); } else { // 8-bit 解码 nDstLength = gsmString2Bytes(pSrc, buf, tmp * 2); // 格式转换

nDstLength = gsmDecode8bit(buf, pDst->TP_UD, nDstLength); } // 返回目标字符串长度 return nDstLength; }

// 转换到 TP-DU

依照 GSM 07.05, 发送短消息用 AT+CMGS 命令, 阅读短消息用 AT+CMGR 命令, 列出短消息用 AT+CMGL 命令,删除短消息用 AT+CMGD 命令。但 AT+CMGL 命令能够读出所有的短消息,所以我们用它实现阅读 短消息功能,而没用 AT+CMGR。下面是发送、读取和删除短消息的实现代码: // 发送短消息 // pSrc: 源 PDU 参数指针 BOOL gsmSendMessage(const SM_PARAM* pSrc) { int nPduLength; // PDU 串长度 // SMSC 串长度 // 串口收到的数据长度 // 命令串 // PDU 串 // 应答串 // 根据 PDU 参数,编码 PDU 串

unsigned char nSmscLength; int nLength; char cmd[16]; char pdu[512]; char ans[128];

nPduLength = gsmEncodePdu(pSrc, pdu); strcat(pdu, "\x01a"); // 以 Ctrl-Z 结束

gsmString2Bytes(pdu, &nSmscLength, 2); nSmscLength++; // 加上长度字节本身

// 取 PDU 串中的 SMSC 信息长度

// 命令中的长度,不包括 SMSC 信息长度,以数据字节计 sprintf(cmd, "AT+CMGS=%d\r", nPduLength / 2 - nSmscLength); // 先输出命令串 // 生成命令

WriteComm(cmd, strlen(cmd));

nLength = ReadComm(ans, 128); // 读应答数据 // 根据能否找到"\r\n> "决定成功与否 if(nLength == 4 && strncmp(ans, "\r\n> ", 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; } // 读取短消息 // 用+CMGL 代替+CMGR,可一次性读出全部短消息 // 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\r");

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);

TRACE(" index=%d\n",pMsg->index); // 找下一行

ptr = strstr(ptr, "\r\n"); ptr += 2;

// 跳过"\r\n" // PDU 串解码

gsmDecodePdu(ptr, pMsg); pMsg++; nMsg++; } }

// 准备读下一条短消息 // 短消息计数加 1

return nMsg; } // 删除短消息 // index: 短消息序号,从 1 开始 BOOL gsmDeleteMessage(const int index) { int nLength; char cmd[16]; char ans[128]; // 串口收到的数据长度 // 命令串 // 应答串 // 生成命令

sprintf(cmd, "AT+CMGD=%d\r", index); // 输出命令串 WriteComm(cmd, strlen(cmd)); // 读应答数据 nLength = ReadComm(ans, 128);

// 根据能否找到"+CMS ERROR"决定成功与否 if(nLength > 0 && strncmp(ans, "+CMS ERROR", 10) != 0) { return TRUE; }

return FALSE; } 以上发送 AT 命令过程中用到了 WriteComm 和 ReadComm 函数,它们是用来读写串口的,依赖于具体的 操作系统。在 Windows 环境下,除了用 MSComm 控件,以及某些现成的串口通信类之外,也可以简单地 调用一些 Windows API 用实现。以下是利用 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; // 串口控制块 // 串口超时控制参数 // 读字符间隔超时时间: 100 ms // 读操作时每字符的时间: 1 ms (n 个字符总共为 n ms) // 基本的(额外的)读超时时间: 500 ms // 写操作时每字符的时间: 1 ms (n 个字符总共为 n ms) // 基本的(额外的)写超时时间: 100 ms // 串口名称或设备路径 // 读写方式

COMMTIMEOUTS timeouts = { 100, 1, 500, 1, 100};

hComm = CreateFile(pPort,

GENERIC_READ | GENERIC_WRITE, 0, NULL, // 共享方式:独占 // 默认的安全描述符 // 不需设置文件属性 // 不需参照模板文件

OPEN_EXISTING, // 创建方式 0, NULL);

if(hComm == INVALID_HANDLE_VALUE) return FALSE; // 取 DCB

// 打开串口失败

GetCommState(hComm, &dcb);

dcb.BaudRate = nBaudRate; dcb.ByteSize = nByteSize; dcb.Parity = nParity; dcb.StopBits = nStopBits; // 设置 DCB // 设置输入输出缓冲区大小 // 设置超时

SetCommState(hComm, &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; } Q 在用 AT 命令同手机通信时,需要注意哪些问题? A 任何一个 AT 命令发给手机,都可能返回成功或失败。例如,用 AT+CMGS 命令发送短消息时,如果此 时正好手机处于振铃或通话状态,就会返回一个"+CMS ERROR"。所以,应当在发送命令后,检测手机的 响应,失败后重发。而且,因为只有一个通信端口,发送和接收不可能同时进行。 如果串口通信用超时控制的同步(阻塞)模式,一般做法是专门将发送/接收处理封装在一个工作子线程内。 因为代码较多,这里就不详细介绍了。所附的 Demo 中,包含了完整的子线程和发送/接收应用程序界面的 源码。 Q 以上 AT 命令,是不是所有厂家的手机都支持? A ETSI GSM 07.05 规范直到 1998 年才形成最终 Release 版本(Ver 7.0.1),在这之前及之后一段时间内,

不排除各厂商在 DTE-DCE 的短消息 AT 命令有所不同的可能性。 我们用到的几个 PDU 模式下的 AT 命令, 是基本的命令,从原则上讲,各厂家的手机以及 GSM 模块应该都支持,但可能有细微差别。 Q 用户信息(TP-UD)内除了一般意义上的短消息,还可以是图片和声音数据。关于手机_铃声和图片格式方 面,有什么规范吗? A 为 统 一 手 机 _ 铃 声 、 图 片 格 式 , Motorola 和 Ericsson, Siemens, Alcatel 等 共 同 开 发 了 EMS(Enhanced Messaging Service)标准,并于 2002 年 2 月份公布。这些厂商格式相同。但另一手机巨 头 Nokia 未参加标准的制定,手机_铃声、图片格式与它们不同。所以没有形成统一的规范。EMS 其实并 没有超越 GSM 07.05,只是 TP-UD 数据部分包含一定格式而已。各厂家的手机_铃声、图片格式资料,可 以查阅相关网站。 Q 用户信息(TP-UD)其实可以是任何的自定义数据,是吗? A 是的,尽管手机上会显示乱码。这种情况下,编码方式已经没有任何意义。但注意仍然要遵守规范。比 如, 若指定 7-bit 编码方式, TP-UDL 应等于实际数据长度的 8/7(用进一法, 而不是四舍五入)。 在利用 SMS 进行点对点或多点对一点的数据通信的应用中,可以传输各种自定义数据,如 GPS 信息,环境监测信息, 加密的个人信息,等等。 如果在传输自定义数据的同时还要收发普通短消息,最简单的办法是在数据前面额外加个识别标志,比如 "FFFF",以区分自定义数据和普通短消息。


相关文章:
什么叫PDU.doc
什么叫PDU - PDU 是什么 目前常见的有两种: 1.一种通信数据单元(编码
PDU.doc
PDU - PDU 短信编程的问题大概有 10 年了吧,我在下面也粘一个,不过是
PDU安装说明.doc
PDU安装说明 - 关于电源分配单元(PDU)的安装说明... PDU安装说明_计算机软件及应用_IT/计算机_专业资料。关于电源分配单元(PDU)的安装说明 关于电源分配单元(PDU)的...
PDU接口类型介绍_图文.ppt
PDU接口类型介绍 - PDU接口类型介绍 PDU: Power Distrib
机房电源改造PDU.doc
机房电源改造PDU - 机房电源改造,集中在设备电源终端,采用IE13插头... 机房电源改造PDU_信息与通信_工程科技_专业资料。机房电源改造,集中在设备电源终端,采用IE13插...
【 PDU插座 】的技术参数.doc
PDU插座 】的技术参数 - 导电率优良,耐腐蚀抗氧化,机械性能好,正常插拔
PDU是什么.doc
PDU是什么 - PDU 是什么 目前常见的有两种: 1.一种通信数据单元(编码
SDU 和 PDU 的区别.doc
SDU 和 PDU 的区别 - SDU 和 PDU SDU(service Da
PDU成品出厂检验单_图文.doc
PDU成品出厂检验单 - PDU 成品出厂检验单 质量记录编号: 产品型号 生产
PDU接口电源介绍_图文.ppt
PDU接口电源介绍 - 对PDU接口电源方面做了详细介绍... PDU接口电源介绍_计算机硬件及网络_IT/计算机_专业资料。对PDU接口电源方面做了详细介绍 IT微环境之供电--PDU...
PDU机柜插座(技术参数 ).doc
PDU机柜插座(技术参数 ) - PDU 机柜专用电源插座相对于普通插座具有安全
PDU电源接口大全_图文.ppt
PDU电源接口大全 - IT微环境之供电--PDU 济南向亿信息技术有限公司 杭州华三通信技术有限公司. www.h3c.com.cn 名词解释 PDU: Power Distribu...
[干货] PDU核心电气件介绍与选型--高压继电器.doc
[干货] | PDU 核心电气件介绍与选型--高压 继电器 2017-11-26
积累PDU的几种方式.pdf
积累PDU的几种方式 - 为了加强PMP专业持续发展,维持PMP的国际证书信誉,PMI推出了一整套持续认证体系(CCR),该体系要求每位PMP每个持续认证周期内积累至少60个PDU以...
PMP积累PDU的几种方法.doc
PMP积累PDU的几种方法 - PMP 积累 PDU 的几种方法 PDU 专业发
PDU简介.doc
PDU简介 - 如何正确选择 PDU --纬诚严选,综合布线一站式采购平台 Power Distribution Unit(机柜电源插座):早期国外的 PDU 为主流,价格也比较贵。 前几年国...
PMP的PDU是什么.doc
PMP的PDU是什么 - PMP?PDU 是什么 项目管理 PMP?认证的火爆程
24口插座式交流PDU说明书.doc
24口插座式交流PDU说明书 - 24 口插座式交流 PDU 说明书 一、示意图
带防雷功能PDU插座产品介绍.doc
带防雷功能PDU插座产品介绍 - 带防雷功能PDU插座19英寸10A6位带开关防
PDU核心电气件介绍与选型--预充电阻和放电电阻,霍尔传....doc
[干货] | PDU 核心电气件介绍与选型--预充 电阻和放电电阻,霍尔传感器及
更多相关标签: