昨天写了短信的发送,今天我们在来谈谈如果读取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
AT+CMGF=0\r
1 serialPort.Write(SMSUtil.GenSendFormatCmd());
2 Thread.Sleep(
500);
3
var result = ReadBuffer(serialPort);
4
3.读取全部的SMS信息:
AT+CMGL=4\r
1 serialPort.Write(SMSUtil.GenGetList(MessageListType.All));
2 Thread.Sleep(
500);
3
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 ==
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
下面是上述解码函数中设计的操作方法:
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(
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
最后是信息内容的封装类:
1
public
class SMSItem
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