重构MySMS

三年前, 我搞了一个短信猫玩, 所以就写了个控制短信猫的程序, 详见这里

说是重构, 几乎就是除了界面部分都重写了一遍。

原来的设计, 使用了类似于同步的一种方式:

向串口发送命令, 然后读取并处理返回的信息, 就这么简单。

但是, 这个方式显然不能处理突发情况, 也就是短信猫主动发送数据的情况, 比如收到了新短信或者是有电话打进来。而是必须要等到下一次发送命令的时候, 才有可能处理得到。

“有可能”是因为, 我处理返回信息实际上就是判断里面有没有”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 条评论。

发表评论

注意 - 你可以用以下 HTML tags and attributes:
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

:wink: :twisted: :roll: :oops: :mrgreen: :lol: :idea: :evil: :cry: :arrow: :?: :-| :-x :-o :-P :-D :-? :) :( :!: 8-O 8)

Trackbacks and Pingbacks:

本文链接:https://twd2.me/archives/6264QrCode