短信开发系列(一):GSM手机短信开发初探

短信开发系列目录:

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

这两天需要实现一个短信的报警平台,便看了一下相关的资料。

其实短信的收发还是比较简单的。

 

首先,我们需要有个短信模块,也就是短信猫。

其次,需要熟悉一下AT命令,这个到google了解一下即可。

接着,需要了解串口编程。串口的编程,其实也是比较简单的一块。

最后,往串口中写入相关的命令即可。

过程是简单的,但是在实践中还是遇到一些麻烦,主要是调试硬件和调试AT命令,如果没有一定的经验,可能会不知所措。本文着重于介绍其功能的实现和协议的解析

 

下面是部分代码的解析

首先是打开串口

 

1 var serialPort = new SerialPort("COM6"115200, Parity.None, 8, StopBits.One)
2             {
3                 WriteTimeout = 500,
4                 ReadTimeout = 500,
5                 RtsEnable = true,
6                 DtrEnable = true
7             };
8 serialPort.Open();

 

其中波特率为115200,RtsEnable设置为true是为了能够在发送完命令后能够及时接收到返回值。每个AT命令发送后都应该会接收到相应的返回值。

刚开始的时候,没有设置这个属性,结果在读取接收缓冲区时,没能读取到任何信息。

 

然后就是发送一个设置发送格式的命令,确定发送的格式

 

1 /// <summary>
2         /// 生成设置信息发送格式的发送命令串
3         /// </summary>
4         /// <param name="format">The format.</param>
5         /// <returns></returns>
6         public static string GenSendFormatCmd(SendFormat format = SendFormat.Text)
7         {
8             return string.Format("AT+CMGF={0}\r", (int)format);
9         }

发送完之后,返回也会存在OK字样,通过该返回串可以判断是否发送成功。

 

最后,也是最主要的部分,就是构建发送内容了。

根据平常我们使用的情况,手机的信息发送,包括两部分,目的地址和信息内容。

实际的发送时,手机信息会被编码为PDU格式(当然还有其他格式,暂时没有研究),整个报文发出。

PDU格式的报文可以简单解析如下:

报文头+编码的手机号码+信息内容编码方案+编码的信息内容

具体的格式可以上网查找,下面我用代码来说明如果对手机号码和信息内容进行编码

 

 1 /// <summary>
 2         /// 生成短信息发送串
 3         /// </summary>
 4         /// <param name="phoneNo">The phone no.</param>
 5         /// <param name="message">The message.</param>
 6         /// <returns></returns>
 7         public static string GenSendCmd(string phoneNo, string message)
 8         {
 9             if (phoneNo.StartsWith("86")) phoneNo = "+" + phoneNo;
10             else if (!phoneNo.StartsWith("+86")) phoneNo = "+86" + phoneNo;
11 
12             phoneNo = EncodePhone(phoneNo);
13             message = EncodeMessage(message);
14 
15             return string.Format("AT+CMGS={0}\r{1}{2}{3}", (phoneNo.Length + message.Length) / 2 - 1, phoneNo, message, (char)26);
16         }

 

 1 /// <summary>
 2         /// 对电话号码进行编码
 3         /// </summary>
 4         /// <param name="phone">The phone.</param>
 5         /// <returns></returns>
 6         private static string EncodePhone(string phone)
 7         {
 8             var result = new StringBuilder();
 9 
10             /**构建协议头部**/
11             result.Append("00");//Length of SMSC information. Here the length is 0, which means that the SMSC stored in the phone should be used. Note: This octet is optional. On some phones this octet should be omitted! (Using the SMSC stored in phone is thus implicit)
12             result.Append("11");//PDU type (forst octet)文件头字节,一般为11或者01,10为乱码
13             result.Append("00");//信息类型(TP-Message-Reference),一般为00
14 
15             /**构建被叫号码地址(目的地址(TP-Destination-Address)**/
16             var isInternational = phone.StartsWith("+");
17             if (isInternational) phone = phone.Remove(01);//去除前面的+
18             var header = (phone.Length << 8) + 0x81 | (isInternational ? 0x10 : 0x20);
19             result.Append(Convert.ToString(header, 16).PadLeft(4'0'));//被叫号码长度+被叫号码类型
20 
21             if (phone.Length % 2 == 1) phone = phone + "F";//个数为奇数,则在后面补F凑成偶数
22             phone = SwapOddEven(phone);
23             result.Append(phone);//互换了奇偶位的电话号码
24 
25             /**构建协议尾部**/
26             result.Append("00");//协议标识TP-PID,这里一般为00 
27             result.Append("08");//数据编码方案TP-DCS(TP-Data-Coding-Scheme),采用前面说的USC2(16bit)数据编码 
28             result.Append("00");//有效期TP-VP(TP-Valid-Period)
29             //if (_validityPeriodFormat != ValidityPeriodFormat.FieldNotPresent)
30             //    result.Append("00");//有效期TP-VP(TP-Valid-Period)
31             //result.Append("A7");//?
32 
33             return result.ToString();
34         }

 

 1 /// <summary>
 2         /// 对信息内容进行编码
 3         /// </summary>
 4         /// <param name="message">The message.</param>
 5         /// <returns></returns>
 6         private static string EncodeMessage(string message)
 7         {
 8             var len = Encoding.BigEndianUnicode.GetByteCount(message);
 9 
10             var result = new StringBuilder();
11             //信息内容长度,一个字节两个16进制表示
12             //result.Append(Convert.ToString(len, 16).PadLeft(2, '0'));
13             result.AppendFormat("{0:X2}", len);
14             //Unicode 两个字节,4个16进制数表示
15             //foreach (byte b in messageBytes)
16             //    result.AppendFormat(Convert.ToString(b, 16).PadLeft(2, '0'));
17             foreach (var m in message)
18             {
19                 result.AppendFormat("{0:X4}", (int)m);
20             }
21 
22             return result.ToString();
23         }

 

 1 /// <summary>
 2         /// 互换奇偶位
 3         /// </summary>
 4         /// <param name="source">原字符串,如1234567890</param>
 5         /// <returns>返回互换了奇偶位的字符串,如:2143658709</returns>
 6         private static string SwapOddEven(string source)
 7         {
 8             var result = string.Empty;
 9 
10             for (var i = 0; i < source.Length; i++)
11                 result = result.Insert(i % 2 == 0 ? i : i - 1, source[i].ToString());
12 
13             return result;
14         }

构建好发送串之后,就可以通过串口将内容发送出去。

这里有一点要注意的,发送完每个AT命令之后,一定要睡眠一段时间,否则是不能发送出去的。这个可能是连续多条命令会被当成一条命令使用吧,求大神解答。

 

 1 buffer = Encoding.ASCII.GetBytes(SMSUtil.GenSendCmd(phone, message));
 2             serialPort.Write(buffer, 0, buffer.Length);
 3             Thread.Sleep(100);
 4             len = serialPort.BytesToRead;
 5             if (len > 0)
 6             {
 7                 buffer = new byte[len];
 8                 serialPort.Read(buffer, 0, len);
 9                 Console.WriteLine(Encoding.ASCII.GetString(buffer));
10             }

发送后,返回串会是如下的内容(返回报文头+经过编码的发送内容)

比如我发送的内容为:

string.Format("hello,marvin,now time is {0}", DateTime.Now)

 

AT+CMGS=99
00110
> 00d91683115509050F00008005400680065006C006C006F002C006D0061007200760069006E002C006E006F0077002000740069006D006500200069007300200032003000310032002F0037002F0034002000310031003A00320033003A00340035

因此根据返回内容是否包含\r\n>来判断是否已成功发送出去

 

完整的发送函数如下:

 

 1 private void Send(string phone, string message)
 2         {
 3             var serialPort = new SerialPort("COM6"115200, Parity.None, 8, StopBits.One)
 4             {
 5                 WriteTimeout = 500,
 6                 ReadTimeout = 500,
 7                 RtsEnable = true,
 8                 DtrEnable = true
 9             };
10             serialPort.Open();
11                 
12             var buffer = Encoding.ASCII.GetBytes(SMSUtil.GenSendFormatCmd());//设置发送格式
13             serialPort.Write(buffer, 0, buffer.Length);
14             Thread.Sleep(100);
15             var len = serialPort.BytesToRead;
16             if (len > 0)
17             {
18                 buffer = new byte[len];
19                 serialPort.Read(buffer, 0, len);
20                 Console.WriteLine(Encoding.ASCII.GetString(buffer));
21             }
22 
23             buffer = Encoding.ASCII.GetBytes(SMSUtil.GenSendCmd(phone, message));
24             serialPort.Write(buffer, 0, buffer.Length);
25             Thread.Sleep(100);
26             len = serialPort.BytesToRead;
27             if (len > 0)
28             {
29                 buffer = new byte[len];
30                 serialPort.Read(buffer, 0, len);
31                 Console.WriteLine(Encoding.ASCII.GetString(buffer));
32             }
33 
34             serialPort.Close();
35         }

 

posted @ 2012-07-04 14:54  马非码  阅读(4735)  评论(27编辑  收藏  举报