手机接收的PDU串的分析(包含7-bit和UCS2解码,超长短信解释)

网络中这方面的资源还挺多的,特别是发短信的源码。利用AT(attention)命令接口控制SIM卡的活动,虽然我们也许不会去写嵌入式系统,但仍然建议基于串口/USB口通过Modem/手机编写AT高级语言编程的朋友们先找本较新版本的《ATCommandsInterface》手册读读。对AT命令接口有个认识之后,再去开发你的解决方案。另外,通过串口连接Modem,还需要对串口的基础知识有一些了解,比如基本的常识:端口名称(COM1,COM2...)、波特率(正确的波特率才能与设备正常通信,在终端看到正确的字符编码,否则会是乱码)、校验位(这里通常为0)、数据位(这里通常为8)、停止位(这里通常为1)、读写超时(比如100ms)、串口缓冲区、握手协议(Handshake,通常不设定,由设备自己控制)RTS是否启用(这里应该启用)DTR是否启用等等。


.NETFromework2.0之后,我们有两种方法解决与串口的通信问题,第一种方式,利用.NET框架中提供的SerialPort类,它可以很好的通过事件的方式监听串口数据,对缓冲区控制也不错,但是要注意,接收事件是在另一个线程进行的,你可能需要维护发送与接收串口数据的同步。第二种方式,使用网络上流传较多的JustinIO类,很简单的类,实现了读写串口的API方法(ReadWrite),比较简单易用,但是功能有限,也许需要二次开发。还要明白串口的收发数据的长度需要从缓冲区里读写,也没有读完了或者写完了的说法,只是有数据就提供,需要由自己编写代码来判断数据的长短,是否读取完整。


关于AT命令接口的问题,你需要了解,每条AT命令以CR结束(我也查到过CRLF结束的),命令的返回信息则是被CRLF首尾括起的字符串。什么时候返回什么样的数据,成功(<CRLF>OK<CRLF>)或者失败(<CRLF>ERROR<CRLF>但不仅限于它)的提示都需要有一些了解。对得到的回应数据进行分析取舍,来电时或者来短信时,会自动返回的数据。推荐编程之前使用PCommTerminal软件(在国内的大型软件下载站内能够搜索下载到),向Modem测试AT指令。


当我们收到短信时,可能会收到这样的数据:


+CMTI:"SM",1


+CMTI:"SM",2


然后通过AT+CMGR=1或者AT+CMGR2读取SIM卡中的短信内容,如果设置了PDU格式,那么会得到PDU数据。如果在之前设定过AT+CNMI=2,1,0,0,0,那么短信息会跳过SIM卡的存储,直接显示出收到的数据。将短信模式设置为PDU模式时(at+cmgf=0),在PDU解码时需要注意编码方式,通常有汉字信息的采用UCS2编码(最多70字符),纯英文信息采用7-bit编码(7位编码算法,可以将ASCII的数字、大小写字母转换成该编码,但是标点符号就无能为力了,最多160字符),图像和铃声采用8-bit编码(最多140字节)。在7位编码的时候建议用文本模式(at+cmgf=1)去读取短信,就不必进行7-bit解码,而且能够显示正确的标点。

下面是设备收到PDU信息时得到的PDU字串的解码分析,仅供参考:


usingSystem;

usingSystem.Text;

usingSystem.Globalization;

classTest

{

staticvoid Main()

{

string[]pdus =

{

"0891683108401105F0240D91685149910183F0000890013261604423026D4B",

"0891683108401105F0040D91683105706027F50008900162311142230A6D4B8BD5003100320033",

"0891683108401105F0040D91683105706027F500009001728033652304D4E2940A",

"0891683108401105F0040D91683105706027F50000900172907074230928D58612D9505429",

};

for(int i = 0; i < pdus.Length; i++)

{

stringpdu = pdus[i];

stringnumber, message;

DateTimetimestamp;

ParseReceivedSms(pdu,out number, out timestamp, out message);

Console.WriteLine("TEST.NO.{0}",i + 1);

Console.WriteLine("number:{0}\ntimestamp: {1}\nmessage: {2}", number, timestamp, message);

Console.WriteLine("messagelength: " + message.Length + "\n");

}

Console.Write("Pressany key to EXIT...");

Console.ReadKey(true);

}

publicstatic void ParseReceivedSms(string pduEncoded, out string number,out DateTime timestamp, out string message)

{

//分析电话号码

char[]numberChars = new char[12];

for(int i = 26, j = 0; i < 26 + 12; i += 2, j += 2)

{

numberChars[j]= pduEncoded[i + 1];

numberChars[j+ 1] = pduEncoded[i];

}

//numberChars[11]='\0';

number= new string(numberChars, 0, 11);

//分析接收时间

stringtimestampString = string.Format("{0}{1}/{2}{3}/{4}{5}{6}{7}:{8}{9}:{10}{11}",

pduEncoded[43],pduEncoded[42],

pduEncoded[45],pduEncoded[44],

pduEncoded[47],pduEncoded[46],

pduEncoded[49],pduEncoded[48],

pduEncoded[51],pduEncoded[50],

pduEncoded[53], pduEncoded[52]

);

timestamp= Convert.ToDateTime(timestampString);

//分析短信

//intmsgCountByByte=(pduEncoded.Length-58)/2;

intmsgCountByByte = Byte.Parse(pduEncoded.Substring(56, 2),NumberStyles.HexNumber);

if(msgCountByByte <= 0)

{

message= string.Empty;

}

else

{

byte[]bytes = new byte[msgCountByByte];

intmsgType = Byte.Parse(pduEncoded.Substring(40, 2),NumberStyles.HexNumber); //取编码方式

switch(msgType)

{

case0: //bit7

message= Gsm7bitDecoding(pduEncoded.Substring(58));

break;

case8: //UCS2

for(int i = 58, j = 0; j < msgCountByByte; i += 2, j++)

{

bytes[j]= Byte.Parse(pduEncoded.Substring(i, 2), NumberStyles.HexNumber);

}

message= Encoding.BigEndianUnicode.GetString(bytes);

break;

case4: //bit8

default:

for(int i = 58, j = 0; j < msgCountByByte; i += 2, j++)

{

bytes[j]= Byte.Parse(pduEncoded.Substring(i, 2), NumberStyles.HexNumber);

}

message= Encoding.ASCII.GetString(bytes);

break;

}

}

}

publicstatic string Gsm7bitEncoding(string text)

{

byte[]textBytes = Encoding.ASCII.GetBytes(text);

//intgsm7bitEncodingLength = textBytes.Length * 7;

//gsm7bitEncodingLength= gsm7bitEncodingLength % 8 != 0 ? gsm7bitEncodingLength / 8 + 1 :gsm7bitEncodingLength / 8;

inttextBytesLength = textBytes.Length;

intgsm7bitEncodingLength = (int)Math.Ceiling((textBytesLength * 7) /8.0);

byte[]gsm7bitEncodingBytes = new byte[gsm7bitEncodingLength];

inta, b, k;

for(a = 0, b = 0; b < textBytesLength; a++, b++)

{

k= b % 8;

if(k == 7)

{

a--;

if(b < (textBytesLength - 1))

{

gsm7bitEncodingBytes[a] |=(byte)((textBytes[b] & 0x7f) << 1);

}

}

else

{

if(b < (textBytesLength - 1))

{

gsm7bitEncodingBytes[a] =(byte)(((textBytes[b] & 0x7f) >> k) | ((textBytes[b + 1] &0x7f) << (7 - k)));

}

else

{

gsm7bitEncodingBytes[a] =(byte)(((textBytes[b] & 0x7f) >> k));

}

}

}

returnBytesToHexString(gsm7bitEncodingBytes);

}

publicstatic string Gsm7bitDecoding(string textEncoded)

{

byte[]src = HexStringToBytes(textEncoded);

if(src.Length == 0)

{

returnstring.Empty;

}

intsrcLength = src.Length;

intdstLength = srcLength * 8 / 7;

byte[]dst = new byte[dstLength];

inta, b, k;

for(a = 0, b = 0; b < srcLength ; a++, b++)

{

k= a % 8;

if(a > 0)

{

dst[a]= (byte)(((src[b] << k) & 0x7f) | (src[b - 1] >> 8 -k));

}

else

{

dst[a]= (byte)(src[b] & 0x7f);

}

if(k == 7 && a > 0)

{

dst[++a]= (byte)(src[b] & 0x7f);

}

}

returnEncoding.ASCII.GetString(dst);

}

staticstring BytesToHexString(byte[] data)

{

StringBuildersb = new StringBuilder();

for(int i = 0; i < data.Length; i++)

{

sb.Append(data[i].ToString("X2"));

}

returnsb.ToString();

}

staticbyte[] HexStringToBytes(string hexString)

{

inthexStringLength = hexString.Length;

if(hexStringLength < 2 || hexStringLength % 2 != 0)

{

returnnew byte[0];

}

byte[]data = new byte[hexStringLength / 2];

for(int i = 0, j = 0; i < hexStringLength; i += 2, j++)

{

data[j]= Byte.Parse(hexString.Substring(i, 2), NumberStyles.HexNumber);

}

returndata;

}

}

直接用CSC编译,编码的分析看代码最后的注释部分,测试结果如下:



TEST.NO.1

number:15941910380

timestamp:2009-10-23 16:06:44

message:

messagelength: 1


TEST.NO.2

number:13500706725

timestamp:2009-10-26 13:11:24

message:测试123

messagelength: 5


TEST.NO.3

number:13500706725

timestamp:2009-10-27 8:33:56

message:TEST

messagelength: 4


TEST.NO.4

number:13500706725

timestamp:2009-10-27 9:07:47

message:(* *)

messagelength: 9


Pressany key to EXIT...为了便于对齐,接收PDU串分析注释的等宽字体如下(您可能需要Firefox浏览器查看)


+CMGL:1,1,,22

0891683108401105F0240D91685149910183F0000890013261604423026D4B

+CMGL:3,1,,30

0891683108401105F0040D91683105706027F50008900162311142230A6D4B8BD5003100320033

+CMGL:6,1,,24

0891683108401105F0040D91683105706027F500009001728033652304D4E2940A

+CMGL:7,1,,28

0891683108401105F0040D91683105706027F50000900172907074230928D58612D9505429


OK


手机接收的PDU串分析

0891683108401105F0240D91685149910183F0000890013261604423026D4B

08+8613800411500F 0D +8615941910380F 09102316064432 6D4B

91 24 91 00 02

08

08 - 短信中心号码/地址长度(指后面有8位字节)

91 - 国际格式号码(在前面加"+")

+8613800411500F- 短信中心号码/地址:13800411500

24 - SMS_DELIVER的第一个8:参考http://www.dreamfabric.com/sms/submit_fo.html

0D - 发送方号码/地址长度(指后面的号码是13位,与前面的短信中心号码表示方法不同)

91 - 国际格式号码(在前面加"+")

+8615941910380F- 发送方号码/地址:15941910380

00 - 协议标识TP-PID:普通GSM,点到点方式

08 - 编码方式TP-DCS:三种:00表示7-bit编码(英文)04表示8-bit编码(图片和铃声)08表示UCS2编码(汉字)

09102316064423 - 时间戳TP-SCTS09/10/2316:06:44 32(+32时区)

02 - 短信内容字节长度(此处是16进制数,若是7-bit表示解码后的字节长度)

6D4B - 短信内容(0x6D4B表示汉字“测”)


--------------------------------------------------------------------------------


2009113日补充:关于收到超长短信的问题

上面代码中并没有处理超长短信,但是处理方法很简单,请阅读下面的内容。

一条短信的长度是有限的,不能超过140Octs,因此,当我们发送超长短信的时候,需要被分割成若干条。那么,在接收PDU串的解码中,会占用掉前6Octs用于记录超长短信的报文标志——所以你会发现,即使用手机发送超长短信的时候,分割后的汉字字符是按70-367个来计算的,即每67个汉字会被分成一条消息(不必奇怪,手机提示比较人性化,第1页提示最多输入70个汉字,第2页提示最多输入64个汉字,以后每页提示最多输入67个汉字)。总之,最终要保证一条短信长度不超过140Octs


6Octs的超长短信报文标志会放在短信内容的最前6Octs当中,也就是前面“手机接收的PDU串分析”中紧随“短信内容字节长度”之后。这6位代表的意义如下:(参见这里的1.4超长短信)


假设短信内容前6位是:

0500037E0201

05 03 02

00 7E 01


05 -协议长度(后面占5位)

00 -表示拆分短信

03 -拆分数据的长度(后面的3位)

7E -唯一标识(用于把多条短信合并)

02 -共被拆分2条短信

01 -序号,这是其中的第1条短信


那么,第2条短信的头6位数据就应该是:

0500037E0202



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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值