三年前, 我搞了一个短信猫玩, 所以就写了个控制短信猫的程序, 详见这里。
说是重构, 几乎就是除了界面部分都重写了一遍。
原来的设计, 使用了类似于同步的一种方式:
向串口发送命令, 然后读取并处理返回的信息, 就这么简单。
但是, 这个方式显然不能处理突发情况, 也就是短信猫主动发送数据的情况, 比如收到了新短信或者是有电话打进来。而是必须要等到下一次发送命令的时候, 才有可能处理得到。
“有可能”是因为, 我处理返回信息实际上就是判断里面有没有”OK”或者”ERROR”字样, 显然不会处理别的东西。
于是就这么用了几年。
现在的设计, 多开了个线程来读取串口发来的所有数据:
串口接收到的数据以行为单位, 一行就是一条数据。
使用了ManualResetEvent。当串口发来OK或者ERROR的时候, 保存这个OK或者ERROR的消息并设置这个event为非阻塞状态, 允许发送线程继续; 当串口发来其他数据, 比如收到了新短信或者是有电话打进来的时候, 就调用相应的处理程序。
串口不能同时读写, 并且读取操作是阻塞的, 于是在读取的时候就不能写入了。为了解决这个问题, 我设置了读取的超时时间, 并使用了同步锁。
同时, 顺便解决了接收短信有时候用TEXT模式不正常的问题, 使用了PDU格式。
下面是解析PDU的代码。
Imports System.Text
Public Class PDUHelper
Public Shared Function ToPDU(sms As ShortMessage) As String
'TODO: To PDU
Throw New NotImplementedException()
End Function
Public Shared Function Parse(raw As String) As ShortMessage
Dim offset = 0
Dim SMSCLength = HexToByte(raw.Substring(offset, 2))
offset += 2
Dim SMSC = DecodeAddress(raw.Substring(offset, SMSCLength * 2))
offset += SMSCLength * 2
Dim Unknown = HexToByte(raw.Substring(offset, 2))
offset += 2
Dim RemoteAddressLength = HexToByte(raw.Substring(offset, 2))
offset += 2
'转换成字节数
If RemoteAddressLength Mod 2 = 1 Then
RemoteAddressLength += 1
End If
RemoteAddressLength = RemoteAddressLength / 2 + 1
Dim RemoteAddress = DecodeAddress(raw.Substring(offset, RemoteAddressLength * 2))
offset += RemoteAddressLength * 2
Dim PID = HexToByte(raw.Substring(offset, 2))
offset += 2
Dim DCS = HexToByte(raw.Substring(offset, 2))
offset += 2
Dim SendTime = DecodeTime(raw.Substring(offset, 14))
offset += 14
Dim UDL = HexToByte(raw.Substring(offset, 2))
offset += 2
If DCS = 0 Then '7bit时, UDL存的是真实字符的个数, 要转换为字节数
UDL = Math.Ceiling(UDL * 7 / 8 )
End If
Dim UD = HexToBytes(raw.Substring(offset, UDL * 2))
offset += UDL * 2
Dim UD_string = ""
If DCS = 0 Then '7bit
UD_string = Decode7bit(Math.Floor(UDL * 8 / 7), UD)
ElseIf DCS = 8 Then 'UCS2
UD_string = DecodeUCS2(UD)
Else
UD_string = BytesToHex(UD)
Utils.dbg("Warning: Unknown DCS, returning raw data")
End If
Dim sms As New ShortMessage
sms.Message = UD_string
sms.MessageCentre = SMSC
sms.Time = SendTime
sms.RemoteAddress = RemoteAddress
Return sms
End Function
Public Shared Function DecodeTime(raw As String) As DateTime
Dim year = Now.Year.ToString().Substring(0, 2) + raw(1) + raw(0)
Dim month = raw(3) + raw(2)
Dim day = raw(5) + raw(4)
Dim hour = raw(7) + raw(6)
Dim minute = raw(9) + raw(8)
Dim second = raw(11) + raw(10)
Dim offset = HexToByte(raw(13) + raw(12))
Return New DateTime(year, month, day, hour, minute, second)
End Function
Public Shared Function EncodeAddress(addr As String) As Byte()
Dim _91 = addr(0) = "+"
If _91 Then
addr = addr.Substring(1)
End If
If addr.Length Mod 2 = 1 Then
addr += "F"
End If
Dim len = addr.Length / 2
If _91 Then
len += 1
End If
Dim raw(len - 1) As Byte
Dim offset = 0
If _91 Then
raw(0) = &H91
offset = 1
End If
For i = 0 To addr.Length - 1 Step 2
raw(offset + i / 2) = Convert.ToByte(addr(i), 16) + (Convert.ToByte(addr(i + 1), 16) << 4)
Next
Return raw
End Function
Public Shared Function DecodeAddress(raw As String) As String
Dim sb As New StringBuilder()
For i = 0 To raw.Length - 1 Step 2
Dim currblock = raw.Substring(i, 2)
If i = 0 AndAlso currblock = "91" Then
sb.Append("+")
Else
sb.Append(currblock(1))
If currblock(0).ToString().ToUpper() <> "F" Then
sb.Append(currblock(0))
End If
End If
Next
Return sb.ToString()
End Function
Public Shared Function DecodeAddress(raw As Byte()) As String
Return DecodeAddress(BytesToHex(raw))
End Function
Public Shared Function DecodeUCS2(raw As Byte()) As String
Return Encoding.BigEndianUnicode.GetString(raw)
End Function
Public Shared Function EncodeUCS2(data As String) As Byte()
Return Encoding.BigEndianUnicode.GetBytes(data)
End Function
Public Shared Function Encode7bit(data As String) As Byte()
Dim raw = Encoding.Default.GetBytes(data)
Dim rawlen = raw.Length
Dim result(Math.Ceiling(rawlen * 7 / 8 )) As Byte
Dim lastData As Byte = 0 '上一字节残余的数据
Dim i = 0, counter = 0
'8个字节一组
Do While i < rawlen
Dim idInGroup = i Mod 8 '当前正在处理的组内字节的序号,范围是0-7
If idInGroup = 0 Then
'组内第一个字节,只是保存起来,待处理下一个字节时使用
lastData = raw(i)
Else
'组内其它字节,将其右边部分与残余数据相加,得到一个目标编码字节
result(counter) = raw(i) << (8 - idInGroup) Or lastData
lastData = raw(i) >> idInGroup
counter += 1
End If
i += 1
Loop
If i Mod 8 > 0 Then
result(counter) = lastData
counter += 1
End If
ReDim Preserve result(counter - 1)
Return result
End Function
Public Shared Function Decode7bit(count As Integer, raw As Byte()) As String
'Dim raw = HexToByte(data)
Dim rawlen = raw.Length
Dim result(Math.Ceiling(rawlen * 8 / 7)) As Byte
Dim lastData As Byte = 0 '上一字节残余的数据
Dim i = 0, counter = 0
'7个字节一组
Do While i < rawlen
Dim idInGroup = i Mod 7 '当前正在处理的组内字节的序号,范围是0-6
result(counter) = (raw(i) << idInGroup Or lastData) And &H7F
lastData = raw(i) >> (7 - idInGroup)
counter += 1
i += 1
'到了一组的最后一个字节
If i Mod 7 = 0 Then
'额外得到一个目标解码字节
result(counter) = lastData
lastData = 0
counter += 1
End If
Loop
ReDim Preserve result(counter - 1)
Return Encoding.Default.GetString(result)
End Function
Public Shared Function HexToBytes(data As String) As Byte()
Dim bin(data.Length / 2 - 1) As Byte
For i = 0 To data.Length - 1 Step 2
bin(i / 2) = Convert.ToByte(data.Substring(i, 2), 16)
Next
Return bin
End Function
Public Shared Function BytesToHex(data As Byte()) As String
Dim sb As New StringBuilder()
For i = 0 To data.Length - 1
sb.Append(data(i).ToString("X2"))
Next
Return sb.ToString()
End Function
Public Shared Function HexToByte(data As String) As Byte
Return Convert.ToByte(data, 16)
End Function
Public Shared Function BytesToHex(data As Byte) As String
Return data.ToString("X2")
End Function
End Class
重构后, 可维护性指数由80成功降为77!

1 条评论。