短信開發系列目錄:
短信開發系列(一):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 }