短信开发系列(二):GSM手机短信开发之短信解码

短信开发系列目录:

短信开发系列(一):GSM手机短信开发初探
短信开发系列(二):GSM手机短信开发之短信解码
短信开发系列(三):短信接收引擎

昨天写了短信的发送,今天我们在来谈谈如果读取SIM卡中的短信息,然后将SIM中经过编码的内容进行解码。

首先,我们熟悉几个读取短信的AT命令:

AT+CMGL=0  读取电话上全部未读过的SMS消息
AT+CMGL=2  列出已有的短信息
AT+CMGL=4  读取全部SMS消息
AT+CMGR=X 读取第X条短消息
使用串口工具MCOM,可以调试以上几条命令,具体方法就不谈了。下面谈谈如果完整的读取完缓冲区中所有的短信和如何对短信进行解码
 
1.初始化串口,打开串口
1 var serialPort = new SerialPort("COM6"115200, Parity.None, 8, StopBits.One)
2             {
3                 WriteTimeout = 500,
4                 ReadTimeout = 5000,
5                 RtsEnable = true,
6                 DtrEnable = true
7             };
8             serialPort.Open();
2.设置发送命令格式: AT+CMGF=0\r
1 serialPort.Write(SMSUtil.GenSendFormatCmd());
2             Thread.Sleep(500);
3             var result = ReadBuffer(serialPort);
4             Console.WriteLine(result);
3.读取全部的SMS信息:AT+CMGL=4\r
 
1 serialPort.Write(SMSUtil.GenGetList(MessageListType.All));
2             Thread.Sleep(500);
3             Console.WriteLine(ReadBuffer(serialPort));
4.读取返回接收缓冲区的内容
这里需要介绍一下接收时的作法,因为之前我试的时候,一直没能接收完整。
 1 /// <summary>
 2         /// 读取缓冲区的内容
 3         /// </summary>
 4         /// <param name="serialPort">The serial port.</param>
 5         /// <returns></returns>
 6         private static string ReadBuffer(SerialPort serialPort)
 7         {
 8             var len = serialPort.BytesToRead;
 9             var result = new StringBuilder();
10             while (len > 0)
11             {
12                 var buffer = new byte[len];
13                 serialPort.Read(buffer, 0, len);
14                 result.Append(Encoding.ASCII.GetString(buffer));
15 
16                 Thread.Sleep(500);
17                 len = serialPort.BytesToRead;
18             }
19             return result.ToString();
20         }
上面这段代码,采用了循环读取缓冲区的作法,直到返回的字节数为0,则判断当前已经把缓冲区的所有短信读取完毕。这里要注意,在进行下一次读取时,一定要先休眠500毫秒或者更多,否则此时缓冲区中的字节数为0,嗯,这里没时间研究,不知道是什么原因,求大神指点。当然,应该不是缓冲区不足的原因。之前我没有休眠直接读取时,第一次的返回都只能读取一条短信,不超过200字节,然后重新打开串口,就能返回之后的所有短信内容。
当然,上面的代码是阻塞式的,效率肯定不高,但是还没想到怎么优化。
返回的内容如下:

AT+CMGF=0
OK
AT+CMGL=0
+CMGL: 8,0,,58
0891683108705505F0040D91683115509050F00000217040518530232B207499CD7EB3DA61B93DED66B9DF77103DDD2E83D273900C1693BD6E2F1A2816D3C56A3A180E
+CMGL: 9,0,,57
0891683108705505F0040D91683115509050F00000217040518511232AE8329BFD66B5C3727BDACD72BFEF207ABA5D06A5E720192C267BDD5E34502CA68BD574301C
+CMGL: 10,0,,57
0891683108705505F0040D91683115509050F00000217040518591232AE8329BFD66B5C3727BDACD72BFEF207ABA5D06A5E720192C267BDD5E34502CA68BD574301C

第一行的+CMGL:8,0,,58的格式如下:+CMGL:[当前信息在SIM卡中的索引],[是否已读取],,[返回串的字节长度]

第二行为实际的返回串,包括了信息发送服务中心的号码,发送的时间戳,发送者的号码,发送内容等。具体的解码参考下面的代码

 

 1 /// <summary>
 2         /// 将接收的ASCII码解析为SMSItem
 3         /// </summary>
 4         /// <param name="src">The SRC.</param>
 5         /// <returns></returns>
 6         public static SMSItem DecodeSrc(string src)
 7         {
 8             var item = new SMSItem();
 9             item.ServiceCenterNo = PopServiceCenterNo(ref src);//服务中心所在号码
10             //pdu类型
11             var pduType = PopByte(ref src);
12             var bits = new System.Collections.BitArray(new[] { pduType });
13             item.ReplyPathExists = bits[7];//?
14             item.UserDataStartsWithHeader = bits[6];//用户数据区是否具有头部
15             item.StatusReportIndication = bits[5];//?
16             item.ValidityPeriodFormat = (ValidityPeriodFormat)(pduType & 0x18);//时间有效性格式
17             item.Direction = (SMSDirection)(pduType & 1);//当前内容是提交发送的还是接收到的
18 
19             if (item.Direction == SMSDirection.Submited) item.MessageReference = PopByte(ref src);//如果是提交的,该字节为信息类型(TP-Message-Reference)
20             item.SenderNo = PopSenderNo(ref src);
21             item.ProtocolIdentifier = PopByte(ref src);//协议标识TP-PID
22             item.DataCodingScheme = PopByte(ref src);//数据编码方案TP-DCS(TP-Data-Coding-Scheme)
23 
24             if (item.Direction == SMSDirection.Submited)
25             {//如果是提交的信息,则该字节为数据的有效性
26                 item.SetValidityPeriod(PopByte(ref src));
27             }
28             else
29             {//如果是接收的,则表示信息中心发送短信的时间
30                 item.ServiceCenterTimeStamp = PopDate(ref src);
31             }
32 
33             item.UserData = src;//未解码的用户数据区
34             if (string.IsNullOrEmpty(src)) return item;
35 
36             int userDataLength = PopByte(ref src);//用户数据区长度
37             if (userDataLength == 0return item;
38 
39             if (item.UserDataStartsWithHeader)
40             {
41                 var userDataHeaderLength = PopByte(ref src);
42                 item.UserDataHeader = PopBytes(ref src, userDataHeaderLength);
43                 userDataLength -= userDataHeaderLength + 1;
44             }
45 
46             if (userDataLength == 0return item;
47 
48             switch ((SMSEncoding)item.DataCodingScheme & SMSEncoding.ReservedMask)
49             {//根据不同的编码方案进行解码
50                 case SMSEncoding._7Bit:
51                     item.Message = Decode7Bit(src, userDataLength);
52                     break;
53                 case SMSEncoding._8Bit:
54                     item.Message = Decode8Bit(src, userDataLength);
55                     break;
56                 case SMSEncoding.UCS2:
57                     item.Message = DecodeUCS2(src, userDataLength);
58                     break;
59             }
60 
61             return item;
62         }

下面是上述解码函数中设计的操作方法:

 

  1 #region 解析接收串
  2         /// <summary>
  3         /// 把Source中的第一个字节(16进制)移除,并转化为Byte类型
  4         /// </summary>
  5         /// <param name="source">The source.</param>
  6         /// <returns></returns>
  7         private static byte PopByte(ref string source)
  8         {
  9             var b = Convert.ToByte(source.Substring(02), 16);
 10             source = source.Substring(2);
 11 
 12             return b;
 13         }
 14 
 15         /// <summary>
 16         /// 把Source中的指定长度的字节(16进制)移除,并转化为Byte数组
 17         /// </summary>
 18         /// <param name="source">The source.</param>
 19         /// <param name="length">The length.</param>
 20         /// <returns></returns>
 21         public static byte[] PopBytes(ref string source, int length)
 22         {
 23             var bytes = source.Substring(0, length * 2);
 24             source = source.Substring(length * 2);
 25 
 26             return GetBytes(bytes, 16);
 27         }
 28 
 29         /// <summary>
 30         /// 把Source中的服务中心部分的字符串移除,正常的电话号码类型
 31         /// </summary>
 32         /// <param name="source">The source.</param>
 33         /// <returns></returns>
 34         private static string PopServiceCenterNo(ref string source)
 35         {
 36             var addrLen = PopByte(ref source);//地址的长度(指示字节个数)
 37             return addrLen == 0 ? string.Empty : PopPhoneNo(ref source, addrLen * 2);
 38         }
 39 
 40         /// <summary>
 41         /// 把Source中的发送者的号码部分的字符串移除,正常的电话号码类型
 42         /// </summary>
 43         /// <param name="source">The source.</param>
 44         /// <returns></returns>
 45         private static string PopSenderNo(ref string source)
 46         {
 47             int addrLen = PopByte(ref source);
 48 
 49             return (addrLen = addrLen + 2) == 2
 50                 ? string.Empty :
 51                 PopPhoneNo(ref source, addrLen + (addrLen % 2));
 52         }
 53 
 54         /// <summary>
 55         /// 把Source中的指定长度的字符串移除,并转化为正常的电话号码类型
 56         /// </summary>
 57         /// <param name="source">The source.</param>
 58         /// <param name="length">The length.</param>
 59         /// <returns></returns>
 60         private static string PopPhoneNo(ref string source, int length)
 61         {
 62             var address = source.Substring(0, length);
 63             source = source.Substring(address.Length);
 64 
 65             var addressType = PopByte(ref address);
 66             address = SwapOddEven(address).Trim('F');
 67 
 68             if (0x09 == addressType >> 4) address = "+" + address;
 69 
 70             return address;
 71         }
 72 
 73         /// <summary>
 74         /// 把Source中的前面表示时间的字符串移除,并转化为正常的时间格式
 75         /// </summary>
 76         /// <param name="source">The source.</param>
 77         /// <returns></returns>
 78         private static DateTime PopDate(ref string source)
 79         {
 80             var bytes = GetBytes(SwapOddEven(source.Substring(012)), 10);
 81 
 82             source = source.Substring(14);
 83 
 84             return new DateTime(2000 + bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5]);
 85         }
 86 
 87         /// <summary>
 88         /// 把某10进制或者16进制的字符串转换为byte数组
 89         /// </summary>
 90         /// <param name="source">字符串</param>
 91         /// <param name="fromBase">进制数,10或者16</param>
 92         /// <returns></returns>
 93         private static byte[] GetBytes(string source, int fromBase)
 94         {
 95             var bytes = new List<byte>();
 96 
 97             for (var i = 0; i < source.Length / 2; i++)
 98             {
 99                 bytes.Add(Convert.ToByte(source.Substring(i * 22), fromBase));
100             }
101 
102             return bytes.ToArray();
103         }
104 
105         /// <summary>
106         /// Decode8s the bit.
107         /// </summary>
108         /// <param name="source">The source.</param>
109         /// <param name="length">The length.</param>
110         /// <returns></returns>
111         private static string Decode8Bit(string source, int length)
112         {
113             //or ASCII?
114             return Encoding.UTF8.GetString(GetBytes(source.Substring(0, length * 2), 16));
115         }
116 
117         /// <summary>
118         /// Decodes the UCS2.
119         /// </summary>
120         /// <param name="source">The source.</param>
121         /// <param name="length">The length.</param>
122         /// <returns></returns>
123         private static string DecodeUCS2(string source, int length)
124         {
125             return Encoding.BigEndianUnicode.GetString(GetBytes(source.Substring(0, length * 2), 16));
126         }
127 
128         /// <summary>
129         /// Decode7s the bit.
130         /// </summary>
131         /// <param name="source">The source.</param>
132         /// <param name="length">The length.</param>
133         /// <returns></returns>
134         private static string Decode7Bit(string source, int length)
135         {
136             var bytes = GetInvertBytes(source);
137 
138             var temp = new StringBuilder();
139             foreach (var b in bytes) temp.Append(Convert.ToString(b, 2).PadLeft(8'0'));
140             var binary = temp.ToString().PadRight(length * 7'0');//转换为2进制,不足在右边补0
141 
142             temp.Clear();
143             for (var i = 1; i <= length; i++) temp.Append((char)Convert.ToByte(binary.Substring(binary.Length - i * 77), 2));
144 
145             return temp.ToString().Replace('\x0''\x40');
146         }
147 
148         /// <summary>
149         /// 把字符串(16进制)转换为byte数组,并反转
150         /// </summary>
151         /// <param name="source">The source.</param>
152         /// <returns></returns>
153         private static IEnumerable<byte> GetInvertBytes(string source)
154         {
155             var bytes = GetBytes(source, 16);
156             Array.Reverse(bytes);
157             return bytes;
158         }
159         #endregion

 

最后是信息内容的封装类:

 

  1 public class SMSItem
  2     {
  3         /// <summary>
  4         /// 服务中心的号码
  5         /// </summary>
  6         /// <value>The service center no.</value>
  7         public string ServiceCenterNo { getset; }
  8         /// <summary>
  9         /// 服务中心发送的时间戳
 10         /// </summary>
 11         /// <value>The service center time stamp.</value>
 12         public DateTime ServiceCenterTimeStamp { getset; }
 13         /// <summary>
 14         /// 发送者的电话号码
 15         /// </summary>
 16         /// <value>The sender no.</value>
 17         public string SenderNo { getset; }
 18         public bool ReplyPathExists { getset; }
 19         /// <summary>
 20         /// 用户数据区是否以报文头开始
 21         /// </summary>
 22         /// <value>
 23         ///     <c>true</c> if [user data starts with header]; otherwise, <c>false</c>.
 24         /// </value>
 25         public bool UserDataStartsWithHeader { getset; }
 26         public bool StatusReportIndication { getset; }
 27         public TimeSpan ValidityPeriod { getset; }
 28         /// <summary>
 29         /// 时间有效性格式
 30         /// </summary>
 31         /// <value>The validity period format.</value>
 32         public ValidityPeriodFormat ValidityPeriodFormat { getset; }
 33         /// <summary>
 34         /// 当前报文是提交发送的还是接收到的
 35         /// </summary>
 36         /// <value>The direction.</value>
 37         public SMSDirection Direction { getset; }
 38 
 39         public byte MessageReference { getset; }
 40         public byte ProtocolIdentifier { getset; }
 41 
 42         /// <summary>
 43         /// 用户数据编码格式
 44         /// </summary>
 45         /// <value>The data coding scheme.</value>
 46         public byte DataCodingScheme { getset; }
 47         /// <summary>
 48         /// 用户数据头
 49         /// </summary>
 50         /// <value>The user data header.</value>
 51         public byte[] UserDataHeader { getset; }
 52         /// <summary>
 53         /// 用户数据(未解码)
 54         /// </summary>
 55         /// <value>The user data.</value>
 56         public string UserData { getset; }
 57         /// <summary>
 58         /// 用户短信的内容
 59         /// </summary>
 60         /// <value>The message.</value>
 61         public string Message { getset; }
 62 
 63         #region Methods
 64         /// <summary>
 65         /// 设置时间验证格式
 66         /// </summary>
 67         /// <param name="v">The v.</param>
 68         public void SetValidityPeriod(byte v)
 69         {
 70             if (v > 196) ValidityPeriod = new TimeSpan((v - 192) * 7000);
 71             else if (v > 167) ValidityPeriod = new TimeSpan((v - 166), 000);
 72             else if (v > 143) ValidityPeriod = new TimeSpan(12, (v - 143) * 300);
 73             else ValidityPeriod = new TimeSpan(0, (v + 1) * 50);
 74         }
 75         /// <summary>
 76         /// 设置时间验证格式
 77         /// </summary>
 78         /// <param name="v">The v.</param>
 79         public void SetValidityPeriod(TimeSpan v)
 80         {
 81             if (v.Days > 441)
 82                 throw new ArgumentOutOfRangeException("TimeSpan.Days", v.Days, "Value must be not greater 441 days.");
 83 
 84             if (v.Days > 30//Up to 441 days
 85                 SetValidityPeriod((byte)(192 + v.Days / 7));
 86             else if (v.Days > 1//Up to 30 days
 87                 SetValidityPeriod((byte)(166 + v.Days));
 88             else if (v.Hours > 12//Up to 24 hours
 89                 SetValidityPeriod((byte)(143 + (v.Hours - 12) * 2 + v.Minutes / 30));
 90             else if (v.Hours > 1 || v.Minutes > 1//Up to 12 days
 91                 SetValidityPeriod((byte)(v.Hours * 12 + v.Minutes / 5 - 1));
 92             else
 93             {
 94                 ValidityPeriodFormat = ValidityPeriodFormat.FieldNotPresent;
 95                 return;
 96             }
 97 
 98             ValidityPeriodFormat = ValidityPeriodFormat.Relative;
 99         }
100         #endregion
101     }

 

posted @ 2012-07-05 12:06  马非码  阅读(3804)  评论(3编辑  收藏  举报