PDU编码
PDU编码是以金笛短信PDU编解码器编码规则来编码的,编码后可以在该网站上进行解码
编码后的字符串复制进方框内,点击convert后进行解码
AT指令收发短信主要有两种模式:Text模式和PDU(Protocol DataUnit,协议数据单元)模式。使用Text模式收发短信代码简单,很容易实现,最大缺点不支持中文短信。PDU模式不仅能发送中文短信,也能发送英文短信。 在PDU Mode中,可以采用三种编码方式来对发送的内容进行编码,它们是7-bit、8-bit和UCS2编码。
7-bit编码用于发送普通的ASCII字符,它将一串7-bit的字符(最高位为0)编码成8-bit的数据,每8个字符可“压缩”成7个;8-bit编码通常用于发送数据消息,比如图片和铃声等;而UCS2编码用于发送Unicode字符。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),则该长度也是压缩编码后字节数或头长度与压缩编码后字节数之和。
短信发送实例:
Text模式(向号码为15050850677的手机发送“TEST”):
1: AT //发送AT 返回OK 连接成功
2:
3: OK
4:
5: AT+CMGF=1 //设置为Text模式
6:
7: AT+CMGS="15050850677" //发送指令,双引号内改为对方手机号码
8:
9: > TEST(+^z,十六进制的1A)//返回字符串中有OK 发送成功 >号为设备返回字符
PDU模式(向号码为15050850677的手机发送“你好”):
1: AT //发送AT 返回OK 连接成功
2:
3: OK
4:
5: AT+CMGF=0 //设置为PDU模式
6:
7: AT+CMGS=19 //发送指令,更改为对应PDU编码的长度计算方法在后面
8:
9: >0011000D91685150800576F70008C4044F60597D(+^z,十六进制的1A)//返回字符串中有OK 发送成功
PDU编码例子
PDU编码协议简单说明
例1 发送:SMSC号码是 +8613800250500,对方号码是13693092030,消息内容是“Hello! ”。从手机发出的PDU串可以是
08 91 68 31 08 20 05 05 F0 11 00 0D 91 68 31 96 03 29 30 F0 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 96 03 29 30 F0 目标地址(TP-DA) 8613693092030,补‘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,对方号码是13693092030,消息内容是“你好! ”。手机接收到的PDU串可以是
08 91 68 31 08 20 05 05 F0 84 0D 91 68 31 96 03 29 30 F0 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 96 03 29 30 F0 回复地址(TP-RA) 8613693092030,补‘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’将奇数补成偶数。
7bit编码
需要指出的是,7-bit的字符集与ANSI标准字符集不完全一致,在0x20以下也排布了一些可打印字符,但英文字母、阿拉伯数字和常用符号的位 置两者是一样的。用上面介绍的算法收发纯英文短消息,一般情况应该是够用了。如果是法语、德语、西班牙语等,含有 “?”、 “é”这一类字符,则要按上面编码的输出去查表,请参阅GSM 03.38的规定。
/*******************************************************************************
* Function :int gsmEncode7bit(const char* pSrc ,unsigned char* pDst)
* Describe : 7bit编码
* Parameter:
* pSrc:目标字符串指针
* pDst:源编码串指针
*
* Return : 目标字符串长度
*
*******************************************************************************/
int gsmEncode7bit(const char* pSrc ,unsigned char* pDst)
{
int nSrc; // 源字符串的计数值
int nDst; // 目标编码串的计数值
int nChar; // 当前正在处理的组内字符字节的序号,范围是0-7
unsigned char nLeft = NULL; // 上一字节残余的数据
int nSrcLength=strlen(pSrc);
// 计数值初始化
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++;
//printf("%c",*pDst);
pDst++; nDst++;
}
// 修改源串的指针和计数值
pSrc++; nSrc++;
}
//Nleft还有剩余,需要一个自己保留。
nChar = nSrc & 7;
if(nChar != 0)
{
*pDst=nLeft;
nDst++;
pDst++;
}
*pDst='\0';
// 返回目标串长度
return nDst;
}
16bit/UCS32+8bit编码
UCS2编码是将每个字符(1-2个字节)按照ISO/IEC10646的规定,转变为16位的Unicode宽字符。在Windows系统中,特别是在2000/XP中,可以简单地调用API 函数实现编码和解码。如果没有系统的支持,比如用单片机控制手机模块收发短消息,只好用查表法解决了。
8-bit编码其实没有规定什么具体的算法,不需要介绍。可以直接将coding置为4即为8bit编码
/*******************************************************************************
* Function :void make_pdu(char* number, char* message, int messagelen, char* pdu)
* Describe : PDU编码
* Parameter:
* number:电话号码
* message:数据
* messagelen:数据长度
* pdu:编码后存储的地址
*
* Return : 无
*
*******************************************************************************/
void make_pdu(char* number, char* message_utf8, int messagelen, char* pdu)
{
int flags=17; // Validity field
int numberlength = 0;
int numberformat = 129; //电话号码前不加+
char tmp[50] = {0};
int proto = 0;
int coding = 8; // 16bit
int validity = 196;
char tmp2[500] = {0};
char message_ucs2[1024] = {0};
//将电话号码转存到数组tmp中
strcpy(tmp, number);
//得到电话号码长度
numberlength=strlen(tmp);
//如果电话号码位数为奇数,则在号码后加F
if (numberlength%2)
{
strcat(tmp,"F");
}
//将电话号码转化为PDU格式
swapchars(tmp);
//将消息转化为PDU格式
int ret = utf8_to_ucs2(message_utf8, messagelen, message_ucs2, 1024);
binary2pdu(message_ucs2,ret,tmp2);
//格式化
sprintf(pdu, "00%02X00%02X%02X%s%02X%02X%02X%02X", flags, numberlength, numberformat, tmp, proto, coding, validity, ret);
strcat(pdu,tmp2);
}
PDU解码
解码例子
0891683108200505F0040D91683119930093F6000880015141652123044F60597D
第一段——短信中心码段
0891683108200505F0
1、短信息中心地址长度段
【SMSC-Length–短信中心号码长度】
首先看前两位“ 08”08* 2=16,即表示“08”后面的16位字符串为短信中心号码信息;
2、短信息中心号码类型
【Type-of-SMSC–短信中心号码类型(国际)】
再看后两位“91”,此“91”为短信息中心号码类型,91是TON/NPI。TON/NPI遵守International/E.164标准,指在号码前需加‘+’号;此外还可有其他数值,但91最常用;
3、短信息中心号码,即所使用的服务中心地址
【SMSC-Value–短信中心号码值】
再 看“91”后面一段“683108200505F0”,将此段字符串的奇偶位换位得到“8613800250500F”此处“86”是国家代号,即中国国 家代号,后面“13800250500F”中的“13800250500”即为短信中心号,后面的“F”为补充位,使得成为偶数位
第二段——手机号码段
040D91683119930093F6
1、【First-Octet,固定格式】
“ 04”—SMS_DELIVER的第一个8 位;
2、【Address-Length–源号码长度(13位)】
“0D”—发送方号码长度;“0D”转换成十进制即为“13”表示“发送方号码类型”后面13位为“发送方号码”,
3、
“91”—发送方号码类型(同第一段的“2”)
4、【Address-Value –源号码值(8613913900396)[13位]】
“683119930093F6”—发送方号码;将此段奇偶位换位得:“8613913900396F”
第三段——协议标识(TP-PID)与用户信息编码方式(TP-DCS)
0008
1、为协议标识
前两字符“00”,为协议标识(TP-Protocol-Identifier)。“00” 是普通 GSM 类型,表示点到点方式。
2、用户信息编码方式
再后面两个字符“08”,为用户信息编码方式(TP-DCS)数据编码方案DCS (DataCoding-Scheme),有关数值含义如下:
00:表示7比特,最多160个字符
04:表示为8比特,最多发140个字符
08:表示16比特,最多发70个汉字;
第四段——短信息发送的日期、时间与时区类型(DateTime Zone Type)【TP-SCTS:】
80015141652123
1、短信发送的日期
前六位数字800151,奇偶位换位得到“081015”,即表示 08年10月15日。
2、短信发送时间
接着六位数字416521 ,奇偶位换位得到“145621”,即表示14时56分21秒;
3、时区类型
接下来两位数字23,这即为时区类型。
第五段——用户数据长度与用户数据
044F60597D
1、用户数据长度
前两位“04”为用十六进制表示的用户数据长度,“04”转换成十进制为4,即其后面的8(8由4*2=8所得)位数据即为用户数据。
2、用户数据【TP-UDL:TP-User-Data-Length】
后面的字符串“4F60597D”即为用户数据。经过反编译得出为“你好”
解码模块代码
含有7bit、8bit、16bit解码,这里只贴出一个整的解码模块。有判断短信编码方式来进行对应的解码方式,需要整个编解码工程的代码,请联系我的邮箱1621057679@qq.com
/*******************************************************************************
* Function :split_pdu(char* pdu, char* number, char* message)
* Describe : PDU解码
* Parameter:
* pdu:PDU码
* number:解码得到的电话号码的存储空间
* message:解码得到的数据的存储空间
*
*
* Return : 无
* Note : sms_phone的容量需>16
* sender_phone的容量需>16
* rev_times的容量需>32
* message的容量需>512
*******************************************************************************/
void split_pdu(char* pdu, char* sms_phone,char* sender_phone,
char *rev_times, char* message)
{
PDU_FORMAT pud_format;
int ret;
int sender_number_length;
int coding;
int data_length;
//pud数据首地址
pud_format.head = pdu;
//短线中心号码长度地址 -
pud_format.smsLen = pud_format.head + 22;
//printf("pud_format.smsLen:%c\r\n",*pud_format.smsLen);
//PDU数据校验
ret = pdu_data_check(*pud_format.smsLen);
if(ret < 0){
return ;
}
//获取短信中心号码
pud_format.sms = pud_format.head + 6; //短线中心号码地址
ret = get_sms_center_number(pud_format.sms, sms_phone);
if(ret < 0){
printf("Get SMS Center Number fail\r\n");
return ;
}
//printf("sms_phone:%s\r\n",sms_phone);
//获取发送者号码长度
pud_format.senderLen = pud_format.head + 21; //发送者号码长度地址
sender_number_length = acsii2hex(*pud_format.senderLen) + 1;
//printf("sender_number_length:%d\r\n",sender_number_length);
//获取发送者号码
pud_format.sender = pud_format.head + 24; //发送者号码地址
ret = get_sender_number(pud_format.sender, sender_number_length, sender_phone);
if(ret < 0){
printf("Get Sender Number fail\r\n");
return ;
}
//printf("sender_phone:%s\r\n",sender_phone);
//判断编码类型
pud_format.coding = pud_format.sender + sender_number_length; //编码类型地址
coding = coding_type_judgment(pud_format.coding);
//printf("coding:%d bit\r\n",coding);
//获取短信接收到的时间
pud_format.time = pud_format.coding + 4; //时间地址
ret = get_rev_time(pud_format.time,rev_times);
if(ret < 0){
printf("Get Rcv Time fail\r\n");
return ;
}
//printf("rev_times:%s\r\n",rev_times);
//获取pdu短信内容长度
pud_format.dataLen = pud_format.time + 14;
data_length = get_data_length(pud_format.dataLen);
//printf("data_length:%d\r\n",data_length);
//解析pdu短信内容
pud_format.data = pud_format.dataLen + 2;
data_parsing(pud_format.data, data_length,coding, message);
//printf("message:%s\r\n",message);
}