网络中这方面的资源还挺多的,特别是发短信的源码。利用AT(attention)命令接口控制SIM卡的活动,虽然我们也许不会去写嵌入式系统,但仍然建议基于串口/USB口通过Modem/手机编写AT高级语言编程的朋友们先找本较新版本的《ATCommandsInterface》手册读读。对AT命令接口有个认识之后,再去开发你的解决方案。另外,通过串口连接Modem,还需要对串口的基础知识有一些了解,比如基本的常识:端口名称(COM1,COM2...)、波特率(正确的波特率才能与设备正常通信,在终端看到正确的字符编码,否则会是乱码)、校验位(这里通常为0)、数据位(这里通常为8)、停止位(这里通常为1)、读写超时(比如100ms)、串口缓冲区、握手协议(Handshake,通常不设定,由设备自己控制)、RTS是否启用(这里应该启用)、DTR是否启用等等。
.NETFromework2.0之后,我们有两种方法解决与串口的通信问题,第一种方式,利用.NET框架中提供的SerialPort类,它可以很好的通过事件的方式监听串口数据,对缓冲区控制也不错,但是要注意,接收事件是在另一个线程进行的,你可能需要维护发送与接收串口数据的同步。第二种方式,使用网络上流传较多的JustinIO类,很简单的类,实现了读写串口的API方法(Read和Write),比较简单易用,但是功能有限,也许需要二次开发。还要明白串口的收发数据的长度需要从缓冲区里读写,也没有读完了或者写完了的说法,只是有数据就提供,需要由自己编写代码来判断数据的长短,是否读取完整。
关于AT命令接口的问题,你需要了解,每条AT命令以CR结束(我也查到过CRLF结束的),命令的返回信息则是被CRLF首尾括起的字符串。什么时候返回什么样的数据,成功(如<CRLF>OK<CRLF>)或者失败(如<CRLF>ERROR<CRLF>但不仅限于它)的提示都需要有一些了解。对得到的回应数据进行分析取舍,来电时或者来短信时,会自动返回的数据。推荐编程之前使用PCommTerminal软件(在国内的大型软件下载站内能够搜索下载到),向Modem测试AT指令。
当我们收到短信时,可能会收到这样的数据:
+CMTI:"SM",1
+CMTI:"SM",2
然后通过AT+CMGR=1或者AT+CMGR=2读取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-SCTS:09/10/2316:06:44 32(+32时区)
02 - 短信内容字节长度(此处是16进制数,若是7-bit表示解码后的字节长度)
6D4B - 短信内容(0x6D4B表示汉字“测”)
--------------------------------------------------------------------------------
2009年11月3日补充:关于收到超长短信的问题
上面代码中并没有处理超长短信,但是处理方法很简单,请阅读下面的内容。
一条短信的长度是有限的,不能超过140个Octs,因此,当我们发送超长短信的时候,需要被分割成若干条。那么,在接收PDU串的解码中,会占用掉前6个Octs用于记录超长短信的报文标志——所以你会发现,即使用手机发送超长短信的时候,分割后的汉字字符是按70-3=67个来计算的,即每67个汉字会被分成一条消息(不必奇怪,手机提示比较人性化,第1页提示最多输入70个汉字,第2页提示最多输入64个汉字,以后每页提示最多输入67个汉字)。总之,最终要保证一条短信长度不超过140个Octs。
这6个Octs的超长短信报文标志会放在短信内容的最前6个Octs当中,也就是前面“手机接收的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