'*************************************************************************
'
'      Author       : Esben Laursen (hyber@hyber.dk)
'      
'      Webpage      : http://www.hyber.dk or http://netsmsgw.sf.net
'
'      Notes        :
'
'      License      : This program is free software; you can redistribute
'                     it and/or modify it under the terms of the GNU
'                     General Public License as published by the Free
'                     Software Foundation version 2 of the License.
'
'*************************************************************************


Imports System.IO.Ports

Public Class smsd

    Dim mstrPortname As String = "COM1"
    Dim mintPortRate As Integer = 115200
    Dim mporParity As System.IO.Ports.Parity = IO.Ports.Parity.None
    Dim mintDataBits As Integer = 8
    Dim mporStopBits As System.IO.Ports.StopBits = StopBits.One
    Dim mporHandShake As Handshake = Handshake.RequestToSend
    Dim mbolDTR As Boolean = True
    Dim mbolRTS As Boolean = True
    Dim mintWait As Integer = 5000
    Dim mintATWait As Integer = 200
    Dim mintWaitRead As Integer = 0
    Dim mintWaitWrite As Integer = 0
    Dim mdatStartTime As DateTime = "00:00:00"
    Dim mdatEndTime As DateTime = "23:59:59"
    Dim mbolIgnoreHighPrio As Boolean = True
    Dim mbolRestrict As Boolean = False
    Dim WithEvents mtmrQueue As New Timers.Timer
    Shared mbolFakeIt As Boolean = False
    Shared mcolQueue As New Collection
    Shared mbolRun As Boolean = False
    Shared mbolAbortQueue As Boolean = False
    Shared mbolDoneReading As Boolean
    Shared mstrReadData As String

    Dim WithEvents mserPort As New System.IO.Ports.SerialPort
    Event Log(ByVal pstrLog As String, ByVal penuLogLevel As LogLevels)
    Event GotData(ByVal pstrData As String)
    Event smsSent(ByVal sms As smsMsg, ByVal Success As Boolean)


    Public Enum LogLevels
        None = 0
        Critical = 1
        Warnings = 2
        Notice = 3
        Info = 4
        Debug = 5
    End Enum

    Public Structure PhoneInfo
        Dim Manufacturer As String
        Dim Model As String
        Dim Version As String
    End Structure



    'Private Structure smsMsg
    '    Dim strTlf As String
    '    Dim strMsg As String
    '    Dim enuPriority As smsUtils.smsPriority
    'End Structure

#Region "properties"

    Public Property IgnoreTimeRestrictOnHighPriority() As Boolean
        Get
            Return mbolIgnoreHighPrio
        End Get
        Set(ByVal value As Boolean)
            mbolIgnoreHighPrio = value
        End Set
    End Property


    Public ReadOnly Property IsQueueRunning() As Boolean
        Get
            Return mbolRun
        End Get
    End Property

    Public Property RescrictTime() As Boolean
        Get
            Return mbolRestrict
        End Get
        Set(ByVal value As Boolean)
            mbolRestrict = value
        End Set
    End Property

    Public Property StartTime() As DateTime
        Get
            Return mdatStartTime
        End Get
        Set(ByVal value As DateTime)
            mdatStartTime = value
        End Set
    End Property

    Public Property EndTime() As DateTime
        Get
            Return mdatEndTime
        End Get
        Set(ByVal value As DateTime)
            mdatEndTime = value
        End Set
    End Property

    Public Property TimeOutReadMs() As Integer
        Get
            Return mintWaitRead
        End Get
        Set(ByVal value As Integer)
            mintWaitRead = value
        End Set
    End Property

    Public Property TimeOutWriteMs() As Integer
        Get
            Return mintWaitWrite
        End Get
        Set(ByVal value As Integer)
            mintWaitWrite = value
        End Set
    End Property

    Public ReadOnly Property QueueCount() As Integer
        Get
            Return mcolQueue.Count
        End Get
    End Property


    Public Property DataBits() As Integer
        Get
            Return mintDataBits
        End Get
        Set(ByVal value As Integer)
            mintDataBits = value
        End Set
    End Property


    Public Property HandShake() As System.IO.Ports.Handshake
        Get
            Return mporHandShake
        End Get
        Set(ByVal value As System.IO.Ports.Handshake)
            mporHandShake = value
        End Set
    End Property

    Public Property WaitBetweenSMS() As Integer
        Get
            'mintwait is in ms, we return sec.
            Return mintWait / 1000
        End Get
        Set(ByVal value As Integer)
            'mintwait is in ms, we get sec.
            mintWait = value * 1000
        End Set
    End Property

    Public Property ComPortName() As String
        Get
            Return mstrPortname
        End Get
        Set(ByVal value As String)
            mstrPortname = value
        End Set
    End Property

    Public Property BaudRate() As Integer
        Get
            Return mintPortRate
        End Get
        Set(ByVal value As Integer)
            mintPortRate = value
        End Set
    End Property

    Public Property Parity() As System.IO.Ports.Parity
        Get
            Return mporParity
        End Get
        Set(ByVal value As System.IO.Ports.Parity)
            mporParity = value
        End Set
    End Property


    Public Property DTR() As Boolean
        Get
            Return mbolDTR
        End Get
        Set(ByVal value As Boolean)
            mbolDTR = value
        End Set
    End Property

    Public Property RTS() As Boolean
        Get
            Return mbolRTS
        End Get
        Set(ByVal value As Boolean)
            mbolRTS = value
        End Set
    End Property

    Public Property StopBits() As System.IO.Ports.StopBits
        Get
            Return mporStopBits
        End Get
        Set(ByVal value As System.IO.Ports.StopBits)
            mporStopBits = value
        End Set
    End Property

    Public ReadOnly Property IsOpen() As Boolean
        Get
            Return mserPort.IsOpen
        End Get
    End Property

    Public Property AT_Wait() As Integer
        Get
            Return mintATWait
        End Get
        Set(ByVal pintms As Integer)
            mintATWait = pintms
        End Set
    End Property

    Public Property FakeIt() As Boolean
        Get
            Return mbolFakeIt
        End Get
        Set(ByVal value As Boolean)
            mbolFakeIt = value
        End Set
    End Property

#End Region

#Region "Subs and functions"

    Public Sub AddToQueue(ByVal psmsMsg As smsMsg)
        Try
            mcolQueue.Add(psmsMsg, psmsMsg.ID)

        Catch ex As Exception
            RaiseEvent Log(gstrCriticalErr & " - AddToQueue(msg) - " & ex.Message.ToString, LogLevels.Critical)
        End Try
    End Sub

    Public Sub AddToQueue(ByVal pcolsmsMsg As Collection)
        Try
            Dim msg As smsMsg
            For Each msg In pcolsmsMsg
                mcolQueue.Add(msg, msg.ID)
            Next

        Catch ex As Exception
            RaiseEvent Log(gstrCriticalErr & " - AddToQueue(col) - " & ex.Message.ToString, LogLevels.Critical)
        End Try
    End Sub

    Public Sub StartQueue()
        Try

            mbolAbortQueue = False
            mtmrQueue.Interval = 1000
            mtmrQueue.Enabled = True
            mtmrQueue.Start()

        Catch ex As Exception
            RaiseEvent Log(gstrCriticalErr & " - StartQueue - " & ex.Message.ToString, LogLevels.Critical)
        End Try
    End Sub

    Private Sub DoQueue()

        Try

            Dim msg As smsMsg

            If mbolRun Then Exit Sub

            mbolRun = True

            Call OpenPort()

            For Each msg In mcolQueue
                Select Case msg.Priority
                    Case smsUtils.smsPriority.High
                        'if we stop queue, then abort
                        If mbolAbortQueue Then GoTo Abort
                        SendSMS(msg)
                    Case smsUtils.smsPriority.Normal, smsUtils.smsPriority.Low
                        'ToDo: Add low priority :-)
                        If mbolRestrict Then 'time span when we may send sms's are restricted or not
                            If Now.TimeOfDay < mdatEndTime.TimeOfDay And Now.ToLocalTime.TimeOfDay > mdatStartTime.TimeOfDay Then
                                'if we stop queue, then abort
                                If mbolAbortQueue Then GoTo Abort
                                'if we are within the start/end time, we can send the sms.
                                SendSMS(msg)
                            End If
                        Else
                            'if we stop queue, then abort
                            If mbolAbortQueue Then GoTo Abort
                            SendSMS(msg)
                        End If

                End Select
            Next

Abort:

            Call ClosePort()

            mbolRun = False
            mbolAbortQueue = False

        Catch ex As Exception
            RaiseEvent Log(gstrCriticalErr & " - DoQueue - " & ex.Message, LogLevels.Critical)
            Call ClosePort()
            mbolRun = False
            mbolAbortQueue = False
        End Try

    End Sub

    Public Sub StopQueue()
        Try
            If mtmrQueue.Enabled Then
                mbolAbortQueue = True
                mtmrQueue.Enabled = False
                RaiseEvent Log(gstrStop, LogLevels.Info)
            End If

        Catch ex As Exception
            RaiseEvent Log(gstrCriticalErr & " - StopQueue - " & ex.Message.ToString, LogLevels.Critical)
        End Try
    End Sub

    Public Sub StopQueue(ByVal AbortAndClose As Boolean)
        Try
            mbolAbortQueue = AbortAndClose
            mtmrQueue.Enabled = False
            RaiseEvent Log(gstrStop, LogLevels.Info)

        Catch ex As Exception
            RaiseEvent Log(gstrCriticalErr & " - StopQueue(" & AbortAndClose & ") - " & ex.Message.ToString, LogLevels.Critical)
        End Try
    End Sub


    Public Sub New(ByVal pstrComPort As String)
        Try
            mstrPortname = pstrComPort
        Catch ex As Exception
            RaiseEvent Log(gstrCriticalErr & " - New - " & ex.Message.ToString, LogLevels.Critical)
        End Try
    End Sub

    Private Sub OpenPort(Optional ByVal pbolFakeit As Boolean = False)
        Try


            If mserPort.IsOpen Then
                mserPort.Close()
                Threading.Thread.Sleep(1000)
            End If

            RaiseEvent Log(gstrSMSSetup, LogLevels.Debug)
            With mserPort
                .PortName = mstrPortname
                .BaudRate = mintPortRate
                .Parity = mporParity
                .DataBits = mintDataBits
                .StopBits = mporStopBits
                .Handshake = mporHandShake
                .DtrEnable = mbolDTR
                .RtsEnable = mbolRTS
                .NewLine = vbCrLf

                If Not mintWaitRead = 0 Then .ReadTimeout = mintWaitRead
                If Not mintWaitWrite = 0 Then .WriteTimeout = mintWaitWrite



                'open port
                RaiseEvent Log(gstrSMSOpenCOM, LogLevels.Debug)
                If Not pbolFakeit Then
                    .Open()
                    Dim thread As New Threading.Thread(AddressOf ReadPort)
                    thread.Start()
                End If

            End With

        Catch ex As Exception
            RaiseEvent Log(gstrCriticalErr & " - OpenPort - " & ex.Message.ToString, LogLevels.Critical)
        End Try

    End Sub

    Private Sub ClosePort()
        Try
            If mserPort.IsOpen Then mserPort.Close()
        Catch ex As Exception
            RaiseEvent Log(gstrCriticalErr & " - ClosePort - " & ex.Message.ToString, LogLevels.Critical)
        End Try
    End Sub

    Private Sub SendSMS(ByVal sms As smsMsg)

        Try

            Dim res As ComResponse

            mcolQueue.Remove(sms.ID) 'remove message from queue :-)
            RaiseEvent Log(gstrDelSMSQueue & sms.ID, LogLevels.Debug)

            If Me.FakeIt Then
                GoTo Quit
                RaiseEvent smsSent(sms, True)
                RaiseEvent Log(gstrFaked, LogLevels.Info)
            End If

            res = SendData("AT")
            'expect OK from phone, if we do not get a okay from port there is a 
            'communication problem, so lets close the port.
            If Not res.OK Then RaiseEvent Log(gstrCommProblem, LogLevels.Warnings)
            If Not res.OK Then Call ClosePort()
            If res.OK Then
                res = SendData("AT+CMGF=1")

                'exepct OK from phone
                If res.OK Then
                    res = SendData("AT+CMGS=" & Chr(34) & sms.Number & Chr(34))

                    'expect > from phone
                    If Left(res.ResponseTextCollection.Item(1), 1) = ">" Then
                        res = SendData(Microsoft.VisualBasic.Left(sms.Message, 158) & vbCrLf & Chr(26))

                        'expect OK from phone
                        If res.OK Then
                            RaiseEvent Log(pstrSentSMS & sms.Message, LogLevels.Notice)
                            RaiseEvent smsSent(sms, True)
                            GoTo Quit
                        End If

                        If Not InStr(res.ResponseTextCollection.Item(1), "+CMS ERROR: 500") Then
                            'if its not a error 500 from phone, it might be a communication
                            'problem with the phone, so just to be safe let's close the port
                            'and it will be opened at next sms.
                            Call ClosePort()
                        End If
                    End If
                End If
            End If

            RaiseEvent Log(gstrErrSend, LogLevels.Warnings)
            RaiseEvent Log(gstrExpect & "OK " & gstrGot & res.ResponseTextCollection.Item(1), LogLevels.Warnings)
            RaiseEvent smsSent(sms, False)

Quit:

            'It seems like if we send to many sms's at one time it will not get delivered?
            'so lets wait 5 secs between (default) :-)
            System.Threading.Thread.Sleep(mintWait)

        Catch ex As Exception
            RaiseEvent Log(gstrCriticalErr & " - SendSMS - " & ex.Message.ToString, LogLevels.Critical)
        End Try
    End Sub

    'Old outdated version of sendsms
    'Public Function SendSMS(ByVal pstrPhoneNo As String, ByVal pstrText As String, Optional ByVal pbolFakeIt As Boolean = False) As Boolean
    '    Try
    '        'open the COM port
    '        Call OpenPort(pbolFakeIt)

    '        If Not pbolFakeIt Then mserPort.WriteLine("AT+CMGF=1" & vbCr)
    '        RaiseEvent Log(gstrSMSAT1, LogLevels.Debug)
    '        System.Threading.Thread.Sleep(mintATWait)
    '        If Not pbolFakeIt Then mserPort.WriteLine("AT+CMGS=" & Chr(34) & pstrPhoneNo & Chr(34) & vbCr)
    '        RaiseEvent Log(gstrSMSAT2 & Chr(34) & pstrPhoneNo & Chr(34), LogLevels.Debug)
    '        System.Threading.Thread.Sleep(mintATWait)
    '        If Not pbolFakeIt Then mserPort.WriteLine(Microsoft.VisualBasic.Left(pstrText, 158) & vbCrLf & Chr(26))

    '        RaiseEvent Log(pstrSentSMS & pstrPhoneNo, LogLevels.Notice)
    '        'It seems like if we send to many sms's at one time it will not get delivered?
    '        'so lets wait 5 secs between (default) :-)
    '        System.Threading.Thread.Sleep(mintWait)

    '        'close the COM port again after use :)
    '        Call ClosePort()

    '        Return True

    '    Catch ex As Exception
    '        RaiseEvent Log(gstrCriticalErr & ex.Message.ToString, LogLevels.Critical)
    '    End Try
    'End Function

    Public Function GetPhoneInfo() As PhoneInfo

        Dim inf As New PhoneInfo
        Dim comRes As ComResponse

        Call OpenPort()

        comRes = SendData("ATI")
        If comRes.OK Then inf.Manufacturer = comRes.ResponseTextCollection.Item(1) Else inf.Manufacturer = "Unknown"

        comRes = SendData("ATI3")

        If comRes.OK Then inf.Model = comRes.ResponseTextCollection.Item(1) Else inf.Model = "Unknown"

        comRes = SendData("ATI2")

        If comRes.OK Then inf.Version = comRes.ResponseTextCollection.Item(1) Else inf.Version = "Unknown"

        Call ClosePort()

        Return inf

    End Function



    Public Function TestPhoneConnection() As String
        Try
            Dim str As String = ""
            Dim res As ComResponse
            Dim bolWasRunning As Boolean

            'if the queue is running we stop it and test phone connection
StopQueue:
            If Me.IsQueueRunning Then
                Call StopQueue(True)
                bolWasRunning = True
                Threading.Thread.Sleep(500)
                RaiseEvent Log(gstrPause, LogLevels.Debug)
            End If

            'make sure the queue is not running, if it is. Try stopping it again.

            'If Me.IsQueueRunning Then GoTo StopQueue

            Call OpenPort()

            res = SendData("ATI3")
            If res.OK Then str = res.ResponseTextCollection.Item(1)

            res = SendData("ATI2")
            If res.OK Then str = str & vbCrLf & res.ResponseTextCollection.Item(1)


            Call ClosePort()

            'lets resume the queue after the test.
            If bolWasRunning Then
                RaiseEvent Log(gstrResume, LogLevels.Debug)
                Call StartQueue()
            End If
            
            Return str

        Catch ex As Exception
            RaiseEvent Log(gstrCriticalErr & ex.Message.ToString, LogLevels.Critical)
            Return ex.Message.ToString
        End Try
    End Function

    Private Function SendData(ByVal pstrCMD As String) As ComResponse
        Try

            Dim i As Integer
            Dim str(30) As String
            Dim comRes As ComResponse

            If Not mserPort.IsOpen Then Call OpenPort()

            mbolDoneReading = False
            mserPort.WriteLine(pstrCMD)
            RaiseEvent Log(gstrCOMSend & pstrCMD, LogLevels.Debug)

            Threading.Thread.Sleep(3000)

            'i = 0
            'Do While mbolDoneReading = False
            '    System.Threading.Thread.Sleep(1000)
            '    i += 1 'just to do something.
            '    If i = 5 Then Exit Do
            'Loop

            RaiseEvent Log(gstrCOMResponse & mstrReadData, LogLevels.Debug)

            str = Split(mstrReadData, vbCrLf & vbCrLf, 30)

            comRes = New ComResponse


            For i = 0 To str.Length - 1
                If Microsoft.VisualBasic.Left(str(i), 2) = vbCrLf Then str(i) = str(i).Remove(0, 2)
            Next

            For i = 0 To str.Length - 1

                If LCase(str(i)) = LCase(pstrCMD) Then
                    comRes.cmdSent = pstrCMD
                    GoTo NextItem
                End If

                If LCase(Microsoft.VisualBasic.Left(str(i), 2)) = "ok" Then
                    comRes.OK = True
                    If comRes.ResponseTextCollection.Count = 0 Then comRes.ResponseTextCollection.Add("OK")
                    Exit For
                End If


                comRes.ResponseTextCollection.Add(Trim(str(i)))
NextItem:
            Next


            'If LCase(str(0)) = LCase(pstrCMD) Then
            '    strctRes.strSent = str(0)
            '    strctRes.strResponseText = str(1)
            '    If LCase(Microsoft.VisualBasic.Left(str(2), 2)) = "ok" Then strctRes.bolOK = True Else strctRes.bolOK = False

            'Else
            '    strctRes.strSent = pstrCMD
            '    strctRes.strResponseText = str(0)
            '    If LCase(Microsoft.VisualBasic.Left(str(1), 2)) = "ok" Then strctRes.bolOK = True Else strctRes.bolOK = False

            'End If

            'RaiseEvent Log(gstrCOMResponse & strctRes.strResponseText, LogLevels.Debug)
            Return comRes

        Catch ex As Exception
            RaiseEvent Log(gstrCriticalErr & ex.Message.ToString, LogLevels.Critical)
            Dim comResp As New ComResponse
            comResp.OK = False
            Return comResp
        End Try

    End Function


    Private Sub ReadPort()
        Try

            Dim strRead As String = Nothing
            Dim rxBuffer(mserPort.ReadBufferSize) As Byte
            Dim SMSMessage As String = Nothing
            Dim Strpos As Integer = 0
            Dim TmpStr As String = Nothing
            While mserPort.IsOpen = True
                If (mserPort.BytesToRead <> 0) And (mserPort.IsOpen = True) Then
                    While mserPort.BytesToRead <> 0
                        mserPort.Read(rxBuffer, 0, mserPort.ReadBufferSize)
                        'convert to string..


                        'MsgBox(System.Text.Encoding.ASCII.GetString(rxBuffer))
                        'strRead = strRead & System.Text.Encoding.ASCII.GetString(rxBuffer)
                        strRead = System.Text.Encoding.ASCII.GetString(rxBuffer)

                        System.Threading.Thread.Sleep(200)


                    End While
                    RaiseEvent GotData(strRead)
                    mstrReadData = strRead
                    mbolDoneReading = True
                    strRead = String.Empty
                    ReDim rxBuffer(mserPort.ReadBufferSize)
                End If
            End While

        Catch ex As Exception
            RaiseEvent Log(gstrCriticalErr & ex.Message.ToString, LogLevels.Critical)
        End Try

    End Sub




#End Region

    Private Sub tmrQueue_Elapsed(ByVal sender As Object, ByVal e As System.Timers.ElapsedEventArgs) Handles mtmrQueue.Elapsed
        If Not mcolQueue.Count = 0 Or mbolRun = True Then
            Call DoQueue()
        End If

    End Sub
End Class

Public Class ComResponse
    Public cmdSent As String
    Public ResponseTextCollection As New Collection
    Public OK As Boolean
End Class
