短信开发系列(二):GSM手机短信开发之短信解码
短信开发系列目录:
短信开发系列(一):GSM手机短信开发初探
短信开发系列(二):GSM手机短信开发之短信解码
短信开发系列(三):短信接收引擎
昨天写了短信的发送,今天我们在来谈谈如果读取SIM卡中的短信息,然后将SIM中经过编码的内容进行解码。
首先,我们熟悉几个读取短信的AT命令:
2 {
3 WriteTimeout = 500,
4 ReadTimeout = 5000,
5 RtsEnable = true,
6 DtrEnable = true
7 };
8 serialPort.Open();
2 Thread.Sleep(500);
3 var result = ReadBuffer(serialPort);
4 Console.WriteLine(result);
2 Thread.Sleep(500);
3 Console.WriteLine(ReadBuffer(serialPort));
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 }
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卡中的索引],[是否已读取],,[返回串的字节长度]
第二行为实际的返回串,包括了信息发送服务中心的号码,发送的时间戳,发送者的号码,发送内容等。具体的解码参考下面的代码
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 == 0) return 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 == 0) return 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 }
下面是上述解码函数中设计的操作方法:
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(0, 2), 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(0, 12)), 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 * 2, 2), 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 * 7, 7), 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
最后是信息内容的封装类:
2 {
3 /// <summary>
4 /// 服务中心的号码
5 /// </summary>
6 /// <value>The service center no.</value>
7 public string ServiceCenterNo { get; set; }
8 /// <summary>
9 /// 服务中心发送的时间戳
10 /// </summary>
11 /// <value>The service center time stamp.</value>
12 public DateTime ServiceCenterTimeStamp { get; set; }
13 /// <summary>
14 /// 发送者的电话号码
15 /// </summary>
16 /// <value>The sender no.</value>
17 public string SenderNo { get; set; }
18 public bool ReplyPathExists { get; set; }
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 { get; set; }
26 public bool StatusReportIndication { get; set; }
27 public TimeSpan ValidityPeriod { get; set; }
28 /// <summary>
29 /// 时间有效性格式
30 /// </summary>
31 /// <value>The validity period format.</value>
32 public ValidityPeriodFormat ValidityPeriodFormat { get; set; }
33 /// <summary>
34 /// 当前报文是提交发送的还是接收到的
35 /// </summary>
36 /// <value>The direction.</value>
37 public SMSDirection Direction { get; set; }
38
39 public byte MessageReference { get; set; }
40 public byte ProtocolIdentifier { get; set; }
41
42 /// <summary>
43 /// 用户数据编码格式
44 /// </summary>
45 /// <value>The data coding scheme.</value>
46 public byte DataCodingScheme { get; set; }
47 /// <summary>
48 /// 用户数据头
49 /// </summary>
50 /// <value>The user data header.</value>
51 public byte[] UserDataHeader { get; set; }
52 /// <summary>
53 /// 用户数据(未解码)
54 /// </summary>
55 /// <value>The user data.</value>
56 public string UserData { get; set; }
57 /// <summary>
58 /// 用户短信的内容
59 /// </summary>
60 /// <value>The message.</value>
61 public string Message { get; set; }
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) * 7, 0, 0, 0);
71 else if (v > 167) ValidityPeriod = new TimeSpan((v - 166), 0, 0, 0);
72 else if (v > 143) ValidityPeriod = new TimeSpan(12, (v - 143) * 30, 0);
73 else ValidityPeriod = new TimeSpan(0, (v + 1) * 5, 0);
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 }