'**********************************************************************************************
'* Siemens Multi Protocol using libnodave
'*
'* Bryan Goose
'* DAVRON
'* 
'* 10-Jul-11
'*
'* This class allows communication via libnodave to various Siemens PLCs
'*
'* Reference : Siemens
'*
'* Copyright 2011 Bryan Goose
'*
'* Distributed under the GNU General Public License (www.gnu.org)
'*
'* This program is free software; you can redistribute it and/or
'* as published by the Free Software Foundation; either version 2
'* of the License, or (at your option) any later version.
'*
'* This program is distributed in the hope that it will be useful,
'* but WITHOUT ANY WARRANTY; without even the implied warranty of
'* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
'* GNU General Public License for more details.

'* You should have received a copy of the GNU General Public License
'* along with this program; if not, write to the Free Software
'* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
'*
'*
'**********************************************************************************************
Imports System.ComponentModel.Design
Imports System.Text.RegularExpressions
Imports System.ComponentModel
Imports System.Drawing '>>>BP
Imports System.Drawing.Design '>>>BP
Imports System.Windows.Forms '>>>BP
Imports System.Windows.Forms.Design '>>>BP

Public Class SiemensPLCsComm
    Inherits System.ComponentModel.Component
    Implements ICommComponent

    '<Assembly: system.Security.Permissions.SecurityPermissionAttribute(system.Security.Permissions.SecurityAction.RequestMinimum)> 
    Private CurrentAddressToRead As String
    Private rnd As New Random
    '* create a random number as a starting point

    Private PolledAddressList As New List(Of PolledAddressInfo)
    Private tmrPollList As New List(Of System.Timers.Timer)
    Private DisableEvent As Boolean

    'Variables for use by libnodave >>>BP
    Dim fds As libnodave.daveOSserialType '>>>BP
    Dim di As libnodave.daveInterface '>>>BP
    Dim dc As libnodave.daveConnection '>>>BP
    Dim buf(100) As Byte '>>>BP libnodave returns values to a buffer
    Dim res As Integer '>>>BP
    Dim result As Integer '>>>BP
    Dim ReturnedData() As String
    Dim PLCConnectedOK As Boolean = False
    Dim PLCConnectedFail As Boolean = False
    Dim ReadinProgress As Boolean = False
    Dim writeWaiting As Boolean = False
    Dim startAddressStored As String = "" ' used to store write data whilst read is in progress
    Dim dataToWriteStored As String = ""  ' used to store write data whilst read is in progress
    Dim FullDataTypeStored As String = "" ' used to store write data whilst read is in progress
    Dim DataLengthStored As Integer = 0   ' used to store write data whilst read is in progress
    Dim readWriteError As Boolean = False ' is set if there is a read or write error so that the buffer can be zeroed


    Public Delegate Sub PLCCommEventHandler(ByVal sender As Object, ByVal e As MfgControl.AdvancedHMI.Drivers.PLCCommEventArgs)

    Public Event DataReceived As PLCCommEventHandler

#Region "ComponentHandlers"

    Private components As System.ComponentModel.IContainer
    '<System.Diagnostics.DebuggerNonUserCode()> _
    Public Sub New(ByVal container As System.ComponentModel.IContainer)
        MyClass.New()

        'Required for Windows.Forms Class Composition Designer support
        container.Add(Me)
    End Sub

    '<System.Diagnostics.DebuggerNonUserCode()> _
    Public Sub New()
        MyBase.New()


        'If DLL Is Nothing Then
        '    DLL = New MfgControl.AdvancedHMI.Drivers.PPIDataLinkLayer
        '    DLL.BaudRate = m_SiemensBaudRate
        'End If
        'AddHandler DLL.DataReceived, Dr
    End Sub

    'Component overrides dispose to clean up the component list.
    '<System.Diagnostics.DebuggerNonUserCode()> _
    Protected Overrides Sub Dispose(ByVal disposing As Boolean)
        '* The handle linked to the DataLink Layer has to be removed, otherwise it causes a problem when a form is closed
        'If DLL IsNot Nothing Then RemoveHandler DLL.DataReceived, Dr

        'If disposing AndAlso components IsNot Nothing Then
        '    components.Dispose()
        'End If
        MyBase.Dispose(disposing)
    End Sub
#End Region


#Region "Properties"

    Private m_MyNode As Integer
    <System.ComponentModel.Category("Serial Comms")> _
    <System.ComponentModel.DisplayName("MPI/PPI Address - Computer")> _
        <System.ComponentModel.Description("Computer MPI/PPI Address")> _
    Public Property MyNode() As Integer
        Get
            Return m_MyNode
        End Get
        Set(ByVal value As Integer)
            m_MyNode = value
        End Set
    End Property


    Private m_TargetNode As Integer = 2
    <System.ComponentModel.Category("Serial Comms")> _
    <System.ComponentModel.DisplayName("MPI/PPI Address - PLC")> _
            <System.ComponentModel.Description("PLC MPI/PPI Address")> _
    Public Property TargetNode() As Integer
        Get
            Return m_TargetNode
        End Get
        Set(ByVal value As Integer)
            m_TargetNode = value
        End Set
    End Property

    '**************************************************************
    '* Determine whether to wait for a data read or raise an event
    '**************************************************************
    Private m_AsyncMode As Boolean
    Public Property AsyncMode() As Boolean
        Get
            Return m_AsyncMode
        End Get
        Set(ByVal value As Boolean)
            m_AsyncMode = value
        End Set
    End Property


    Private m_SiemensBaudRate As String = "9600"
    <System.ComponentModel.Category("Serial Comms")> _
    <System.ComponentModel.DisplayName("COM Port Baud Rate")> _
    <System.ComponentModel.Description("Computer RS232/485 port speed")> _
    <EditorAttribute(GetType(SiemensBaudRateEditor), GetType(System.Drawing.Design.UITypeEditor))> _
Public Property SiemensBaudRate() As String
        Get
            Return m_SiemensBaudRate
        End Get
        Set(ByVal value As String)
            m_SiemensBaudRate = value
        End Set
    End Property

    Private m_SiemensCommType As String = "PPI S7200"
    <EditorAttribute(GetType(SiemensCommTypeEditor), GetType(System.Drawing.Design.UITypeEditor))> _
Public Property SiemensCommType() As String
        Get
            Return m_SiemensCommType
        End Get
        Set(ByVal value As String)
            m_SiemensCommType = value
        End Set
    End Property


    Private m_SiemensCommPort As String = "COM1"
    <System.ComponentModel.Category("Serial Comms")> _
    <System.ComponentModel.DisplayName("COM Port")> _
    <System.ComponentModel.Description("Computer RS232/485 port")> _
    <EditorAttribute(GetType(SiemensCommPortEditor), GetType(System.Drawing.Design.UITypeEditor))> _
Public Property SiemensCommPort() As String
        Get
            Return m_SiemensCommPort
        End Get
        Set(ByVal value As String)
            m_SiemensCommPort = value
        End Set
    End Property


    Private m_SiemensInterfaceSpeed As String
    <System.ComponentModel.Category("Serial Comms")> _
    <System.ComponentModel.DisplayName("Interface speed")> _
    <System.ComponentModel.Description("PLC Interface speed")> _
    <EditorAttribute(GetType(SiemensInterfaceSpeedEditor), GetType(System.Drawing.Design.UITypeEditor))> _
Public Property SiemensInterfaceSpeed() As String
        Get
            Return m_SiemensInterfaceSpeed
        End Get
        Set(ByVal value As String)
            m_SiemensInterfaceSpeed = value
        End Set
    End Property


    Private m_TargetIPAddress As String
    <System.ComponentModel.Category("TCP/IP Comms")> _
    <System.ComponentModel.DisplayName("PLC IP Address")> _
    <System.ComponentModel.Description("IP Address format 192.168.0.10")> _
    Public Property TargetIPAddress() As String
        Get
            Return m_TargetIPAddress
        End Get
        Set(ByVal value As String)
            m_TargetIPAddress = value
        End Set
    End Property


    Private m_Rack As Integer
    <System.ComponentModel.Category("TCP/IP Comms")> _
    Public Property Rack() As Integer
        Get
            Return m_Rack
        End Get
        Set(ByVal value As Integer)
            m_Rack = value
        End Set
    End Property


    Private m_Slot As Integer
    <System.ComponentModel.Category("TCP/IP Comms")> _
    Public Property Slot() As Integer
        Get
            Return m_Slot
        End Get
        Set(ByVal value As Integer)
            m_Slot = value
        End Set
    End Property

    Private m_TimeOut As Int32 = 2000000
    <System.ComponentModel.Category("Serial Comms")> _
    <System.ComponentModel.DisplayName("Comms Timeout")> _
    <System.ComponentModel.Description("Increase for a slow connection. 2000000 is typical" & vbCrLf & "too small causes Read errors" & vbCrLf & "too large makes it slow to recover from errors")> _
    Public Property TimeOut() As Integer
        Get
            Return m_TimeOut
        End Get
        Set(ByVal value As Integer)
            m_TimeOut = value
        End Set
    End Property

    '**************************************************************
    '* Stop the polling of subscribed data
    '**************************************************************
    Private m_DisableSubscriptions
    Public Property DisableSubscriptions() As Boolean Implements ICommComponent.DisableSubscriptions
        Get
            Return m_DisableSubscriptions
        End Get
        Set(ByVal value As Boolean)
            m_DisableSubscriptions = value

            If value Then
                '* Stop the poll timers
                For i As Int16 = 0 To tmrPollList.Count - 1
                    tmrPollList(i).Enabled = False
                Next
            Else
                '* Start the poll timers
                For i As Int16 = 0 To tmrPollList.Count - 1
                    tmrPollList(i).Enabled = True
                Next
            End If
        End Set
    End Property


    '**************************************************
    '* Its purpose is to fetch
    '* the main form in order to synchronize the
    '* notification thread/event
    '**************************************************
    Private m_SynchronizingObject As Windows.Forms.Form
    '* do not let this property show up in the property window
    ' <System.ComponentModel.Browsable(False)> _
    Public Property SynchronizingObject() As Windows.Forms.Form
        Get
            Dim host1 As IDesignerHost
            Dim obj1 As Object
            If (m_SynchronizingObject Is Nothing) AndAlso MyBase.DesignMode Then
                host1 = CType(Me.GetService(GetType(IDesignerHost)), IDesignerHost)
                If host1 IsNot Nothing Then
                    obj1 = host1.RootComponent
                    m_SynchronizingObject = CType(obj1, System.ComponentModel.ISynchronizeInvoke)
                End If
            End If
            'End If
            Return m_SynchronizingObject
        End Get

        Set(ByVal Value As Windows.Forms.Form)
            If Not Value Is Nothing Then
                m_SynchronizingObject = Value
            End If
        End Set
    End Property

#End Region
#Region "Subscription Routines and Receieve Data"
    '*********************************************************************************
    '* This is used for linking a notification.
    '* An object can request a continuous poll and get a callback when value updated
    '*********************************************************************************
    Delegate Sub ReturnValues(ByVal Values As String)
    Private Structure PolledAddressInfo
        Dim PLCAddress As String
        Dim DBDataBase As Integer
        Dim DataArea As String
        Dim DataType As String
        Dim FullDataType As String
        Dim Offset As Integer
        Dim SubElement As Integer
        Dim BitNumber As Integer
        Dim BytesPerElement As Integer
        Dim dlgCallBack As ICommComponent.ReturnValues
        Dim PollRate As Integer
        Dim ID As Integer
        Dim LastValue As Object
        Dim ElementsToRead As Integer
        Dim TargetNode As Integer '27-APR-10
    End Structure
    Private CurrentID As Integer = 1


    'Public Function Subscribe(ByVal PLCAddress As String, ByVal PollRate As Integer, ByVal CallBack As ICommComponent.ReturnValues) As Integer Implements ICommComponent.Subscribe
    Public Function Subscribe(ByVal PLCAddress As String, ByVal PollRate As Integer, ByVal CallBack As ICommComponent.ReturnValues, ByVal FullDataTypeBP As String, ByVal DataLengthBP As Integer) As Integer Implements ICommComponent.Subscribe
        '*******************************************************************
        '*******************************************************************
        Dim ParsedResult As ParsedDataAddress = ParseAddress(PLCAddress)

        '* Valid address?
        If ParsedResult.DataArea <> "" Then
            Dim tmpPA As New PolledAddressInfo
            tmpPA.PLCAddress = PLCAddress
            'tmpPA.PollRate = CycleTime
            tmpPA.PollRate = PollRate
            tmpPA.dlgCallBack = CallBack
            tmpPA.ID = CurrentID
            tmpPA.DataArea = ParsedResult.DataArea
            tmpPA.Offset = ParsedResult.Offset
            tmpPA.SubElement = ParsedResult.SubElement
            tmpPA.BitNumber = ParsedResult.BitNumber
            tmpPA.DataType = ParsedResult.DataType
            tmpPA.FullDataType = FullDataTypeBP
            tmpPA.DBDataBase = ParsedResult.DBDataBase
            'tmpPA.BytesPerElement = ParsedResult.BytesPerElements
            tmpPA.ElementsToRead = 1
            tmpPA.TargetNode = m_TargetNode


            PolledAddressList.Add(tmpPA)

            PolledAddressList.Sort(AddressOf SortPolledAddresses)

            '* The ID is used as a reference for removing polled addresses
            CurrentID += 1


            '********************************************************************
            '* Check to see if there already exists a timer for this poll rate
            '********************************************************************
            If tmrPollList.Count < 1 Then
                Dim j As Integer = 0
                'While j < tmrPollList.Count AndAlso tmrPollList(j) IsNot Nothing AndAlso tmrPollList(j).Interval <> CycleTime
                '    j += 1
                'End While

                'If j >= tmrPollList.Count Then
                '* Add new timer
                Dim tmrTemp As New System.Timers.Timer
                'If CycleTime > 0 Then
                '    tmrTemp.Interval = CycleTime
                'Else
                '    tmrTemp.Interval = 10
                'End If
                If PollRate <= 0 Then PollRate = 250
                tmrTemp.Interval = PollRate

                tmrPollList.Add(tmrTemp)
                AddHandler tmrPollList(j).Elapsed, AddressOf PollUpdate

                tmrTemp.Enabled = True
                'End If
            End If
            'SetupPLCConnection()
            Return tmpPA.ID
        End If
        Return -1
    End Function

    '***************************************************************
    '* Used to sort polled addresses by File Number and element
    '* This helps in optimizing reading
    '**************************************************************
    Private Function SortPolledAddresses(ByVal A1 As PolledAddressInfo, ByVal A2 As PolledAddressInfo) As Integer
        If A1.DataArea = A2.DataArea Then
            If A1.Offset > A2.Offset Then
                Return 1
            ElseIf A1.Offset = A2.Offset Then
                Return 0
            Else
                Return -1
            End If
        End If

        If A1.DataArea > A2.DataArea Then
            Return 1
        Else
            Return -1
        End If
    End Function

    Public Function UnSubscribe(ByVal ID As Integer) As Integer Implements ICommComponent.UnSubscribe
        Dim i As Integer = 0
        While i < PolledAddressList.Count AndAlso PolledAddressList(i).ID <> ID
            i += 1
        End While

        If i < PolledAddressList.Count Then
            PolledAddressList.RemoveAt(i)
            If PolledAddressList.Count = 0 Then
                For j As Integer = 0 To tmrPollList.Count - 1
                    tmrPollList(j).Enabled = False
                    tmrPollList.Remove(tmrPollList(0))
                Next
            End If
        End If
        'ClosePLCConnection()
    End Function

    '**************************************************************
    '* Perform the reads for the variables added for notification
    '* Attempt to optimize by grouping reads
    '**************************************************************
    Private InternalRequest As Boolean '* This is used to dinstinquish when to send data back to notification request
    Private SavedPollRate As Integer
    Private Sub PollUpdate(ByVal sender As System.Object, ByVal e As System.EventArgs)
        If m_DisableSubscriptions Then Exit Sub

        Dim intTimerIndex As Integer = tmrPollList.IndexOf(sender)

        If writeWaiting = True Then
            ReadinProgress = False
            WriteData("OKtoWrite", "0", "String", 0)

        End If

        ReadinProgress = True

        ' Connet to the PLC on the first Poll
        If PLCConnectedOK = False And PLCConnectedFail = False Then
            SetupPLCConnection()
        End If

        '* Stop the poll timer
        tmrPollList(intTimerIndex).Enabled = False

        Dim i, NumberToRead, FirstElement As Integer
        Dim HighestBit As Integer = PolledAddressList(i).BitNumber
        While i < PolledAddressList.Count
            'Dim NumberToReadCalc As Integer
            NumberToRead = PolledAddressList(i).ElementsToRead
            FirstElement = i
            Dim PLCAddress As String = PolledAddressList(FirstElement).PLCAddress


            PLCAddress = PolledAddressList(FirstElement).PLCAddress


            If PolledAddressList(i).PollRate = sender.Interval Or SavedPollRate > 0 Then
                '* Make sure it does not wait for return value befoe coming back
                Dim tmp As Boolean = Me.AsyncMode
                Me.AsyncMode = True
                Try
                    InternalRequest = True

                    Dim ParsedResult As ParsedDataAddress = ParseAddress(PLCAddress)


                    Select Case PolledAddressList(i).FullDataType
                        Case "Unsigned Byte", "Signed Byte", "Hex Byte", "Bit"
                            ParsedResult.BytesPerElements = 1
                        Case "Unsigned Word", "Signed Word", "Hex Word"
                            ParsedResult.BytesPerElements = 2
                        Case "Unsigned Double Word", "Signed Double Word", "Float"
                            ParsedResult.BytesPerElements = 4
                        Case Else
                            Debug.Print("BytesPerElements - Full Data type not recognised")
                    End Select

                    '* Save this for being used by the polling link
                    CurrentAddressToRead = PLCAddress

                    '* If requesting 0 elements, then default to 1
                    Dim ArrayElements As Int16 = NumberToRead - 1
                    If ArrayElements < 0 Then
                        ArrayElements = 0
                    End If

                    ' If there was a Read or Write error then clear the receive buffer before the next Read
                    If readWriteError Then
                        ReDim buf(100)
                        readWriteError = False
                    End If

                    If PLCConnectedOK = True Then
                        Select Case PolledAddressList(i).DataArea
                            Case "I"
                                result = dc.readBytes(libnodave.daveInputs, 0, ParsedResult.Offset, ParsedResult.BytesPerElements, buf)
                            Case "Q"
                                result = dc.readBytes(libnodave.daveOutputs, 0, ParsedResult.Offset, ParsedResult.BytesPerElements, buf)
                            Case "M"
                                result = dc.readBytes(libnodave.daveFlags, 0, ParsedResult.Offset, ParsedResult.BytesPerElements, buf)
                            Case "V", "D"
                                result = dc.readBytes(libnodave.daveDB, ParsedResult.DBDataBase, ParsedResult.Offset, ParsedResult.BytesPerElements, buf)
                            Case "C"
                                If m_SiemensCommType = "PPI S7200" Then
                                    result = dc.readBytes(libnodave.daveCounter200, 0, ParsedResult.Offset, ParsedResult.BytesPerElements, buf)
                                Else
                                    result = dc.readBytes(libnodave.daveCounter, 0, ParsedResult.Offset, ParsedResult.BytesPerElements, buf)
                                End If
                            Case "T"
                                If m_SiemensCommType = "PPI S7200" Then
                                    result = dc.readBytes(libnodave.daveTimer200, 0, ParsedResult.Offset, ParsedResult.BytesPerElements, buf)
                                Else
                                    result = dc.readBytes(libnodave.daveTimer, 0, ParsedResult.Offset, ParsedResult.BytesPerElements, buf)
                                End If
                            Case Else
                                Debug.Print("PLC Read - Data type not recognised")
                        End Select

                        'Retrieve data from the buffer
                        ReDim ReturnedData(200)
                        Dim tempByte As Byte
                        Dim tempWord As Int16
                        Dim temp8bitsString As String
                        Dim temp8bitsString2 As String
                        If result = 0 Then
                            'TODO complete the data checking
                            Select Case PolledAddressList(i).FullDataType
                                Case "Unsigned Byte"
                                    If ReturnedData(0) >= 0 And ReturnedData(0) < 256 Then
                                        ReturnedData(0) = dc.getU8.ToString
                                    End If
                                Case "Signed Byte"
                                    If ReturnedData(0) >= -126 And ReturnedData(0) <= 127 Then
                                        ReturnedData(0) = dc.getS8.ToString
                                    End If
                                Case "Hex Byte"
                                    tempByte = dc.getU8
                                    ReturnedData(0) = tempByte.ToString("X")
                                Case "Unsigned Word"
                                    ReturnedData(0) = dc.getU16.ToString
                                Case "Signed Word"
                                    ReturnedData(0) = dc.getS16.ToString
                                Case "Hex Word"
                                    tempWord = dc.getU16
                                    ReturnedData(0) = tempWord.ToString("X")
                                Case "Unsigned Double Word"
                                    ReturnedData(0) = dc.getU32.ToString
                                Case "Signed Double Word"
                                    ReturnedData(0) = dc.getS32.ToString
                                Case "Float"
                                    ReturnedData(0) = dc.getFloat.ToString
                                Case "Bit"
                                    ' I converted resulting Byte to a string and then reversed the String
                                    ' I could have just converted and then got 8 - Substring position and avoided the Reverse
                                    temp8bitsString = Convert.ToString(dc.getU8, 2).PadLeft(8, "0"c)
                                    'Debug.Print(temp8bitsString)
                                    Dim arrChar() As Char = temp8bitsString.ToCharArray()
                                    Array.Reverse(arrChar)
                                    temp8bitsString2 = arrChar

                                    ReturnedData(0) = temp8bitsString2.Substring(PolledAddressList(i).BitNumber, 1)
                                    'Debug.Print(PolledAddressList(i).BitNumber, "    ", ReturnedData(0))
                                Case Else
                                    Debug.Print("Retrieve data from buffer - Full Data type not recognised")
                            End Select

                        Else
                            readWriteError = True
                        End If

                        'Debug.Print(ReturnedData(0))
                    End If

                    If PolledAddressList.Count > 0 Then
                        If i >= 0 And i <= PolledAddressList.Count Then
                            m_SynchronizingObject.Invoke(PolledAddressList(i).dlgCallBack, CObj(ReturnedData))
                        End If
                    End If

                    If SavedPollRate <> 0 Then
                        tmrPollList(0).Interval = SavedPollRate
                        SavedPollRate = 0
                    End If
                Catch ex As Exception
                    '* Send this message back to the requesting control
                    Dim TempArray() As String = {ex.Message}
                    If PolledAddressList.Count > 0 Then
                        If i >= 0 And i <= PolledAddressList.Count Then
                            m_SynchronizingObject.BeginInvoke(PolledAddressList(i).dlgCallBack, CObj(TempArray))
                        End If
                    End If


                    '* Slow down the poll rate to avoid app freezing
                    If SavedPollRate = 0 Then SavedPollRate = tmrPollList(intTimerIndex).Interval
                    tmrPollList(intTimerIndex).Interval = 5000
                End Try
                Me.AsyncMode = tmp
            End If
            i += 1
        End While


        '* Start the poll timer
        ReadinProgress = False

        'If writeWaiting = True Then
        '    WriteData("OKtoWrite", "0", "String", 0)

        'End If

        tmrPollList(intTimerIndex).Enabled = True

    End Sub

    Private Sub SetupPLCConnection()
        ' Set the libnodave communication type
        Select Case m_SiemensCommType
            Case "MPI S7300/400", "MPI2 S7300/400", "PPI S7200"
                'Set the Comm Port
                fds.rfd = libnodave.setPort(SiemensCommPort, SiemensBaudRate, AscW("E"))
            Case "ISOTCP", "ISOTCP S7200"
                ' Open the Socket connection
                fds.rfd = libnodave.openSocket(102, TargetIPAddress)
            Case Else
                '"MPI IBH"
                fds.rfd = libnodave.openSocket(1099, TargetIPAddress)
        End Select

        fds.wfd = fds.rfd 'Only needed for Ethernet interfaces but does no harm otherwise
        'If Port/Connection opens OK then Intialize the connection to the device

        If fds.rfd <= 0 Then
            PLCConnectedFail = True
            MsgBox("Couldn't open Serial Port " & SiemensCommPort & vbCrLf & "Check COM Port and Baud Rate")

            'End If

            'If fds.rfd > 0 Then
        Else
            Debug.Print("SiemensPLCs - Opened Port or Socket")

            di = New libnodave.daveInterface(fds, "IF1", 0, 0, 0)

            'TODO Add ability to use more than one Interface (IF1)
            'Got to be a prettier and moer consice way to do this
            Select Case m_SiemensCommType
                Case "MPI S7300/400"
                    Select Case m_SiemensInterfaceSpeed
                        Case "9K"
                            di = New libnodave.daveInterface(fds, "IF1", m_MyNode, libnodave.daveProtoMPI, libnodave.daveSpeed9k)
                        Case "19K"
                            di = New libnodave.daveInterface(fds, "IF1", m_MyNode, libnodave.daveProtoMPI, libnodave.daveSpeed19k)
                        Case "187K"
                            di = New libnodave.daveInterface(fds, "IF1", m_MyNode, libnodave.daveProtoMPI, libnodave.daveSpeed187k)
                        Case "500K"
                            di = New libnodave.daveInterface(fds, "IF1", m_MyNode, libnodave.daveProtoMPI, libnodave.daveSpeed500k)
                        Case "1500K"
                            di = New libnodave.daveInterface(fds, "IF1", m_MyNode, libnodave.daveProtoMPI, libnodave.daveSpeed1500k)
                        Case "45K"
                            di = New libnodave.daveInterface(fds, "IF1", m_MyNode, libnodave.daveProtoMPI, libnodave.daveSpeed45k)
                        Case "93K"
                            di = New libnodave.daveInterface(fds, "IF1", m_MyNode, libnodave.daveProtoMPI, libnodave.daveSpeed93k)
                        Case Else

                    End Select

                Case "MPI2 S7300/400"
                    Select Case m_SiemensInterfaceSpeed
                        Case "9K"
                            di = New libnodave.daveInterface(fds, "IF1", m_MyNode, libnodave.daveProtoMPI2, libnodave.daveSpeed9k)
                        Case "19K"
                            di = New libnodave.daveInterface(fds, "IF1", m_MyNode, libnodave.daveProtoMPI2, libnodave.daveSpeed19k)
                        Case "187K"
                            di = New libnodave.daveInterface(fds, "IF1", m_MyNode, libnodave.daveProtoMPI2, libnodave.daveSpeed187k)
                        Case "500K"
                            di = New libnodave.daveInterface(fds, "IF1", m_MyNode, libnodave.daveProtoMPI2, libnodave.daveSpeed500k)
                        Case "1500K"
                            di = New libnodave.daveInterface(fds, "IF1", m_MyNode, libnodave.daveProtoMPI2, libnodave.daveSpeed1500k)
                        Case "45K"
                            di = New libnodave.daveInterface(fds, "IF1", m_MyNode, libnodave.daveProtoMPI2, libnodave.daveSpeed45k)
                        Case "93K"
                            di = New libnodave.daveInterface(fds, "IF1", m_MyNode, libnodave.daveProtoMPI2, libnodave.daveSpeed93k)
                        Case Else

                    End Select
                Case "PPI S7200"
                    Select Case m_SiemensInterfaceSpeed
                        Case "9K"
                            di = New libnodave.daveInterface(fds, "IF1", m_MyNode, libnodave.daveProtoPPI, libnodave.daveSpeed9k)
                        Case "19K"
                            di = New libnodave.daveInterface(fds, "IF1", m_MyNode, libnodave.daveProtoPPI, libnodave.daveSpeed19k)
                        Case "187K"
                            di = New libnodave.daveInterface(fds, "IF1", m_MyNode, libnodave.daveProtoPPI, libnodave.daveSpeed187k)
                        Case "500K"
                            di = New libnodave.daveInterface(fds, "IF1", m_MyNode, libnodave.daveProtoPPI, libnodave.daveSpeed500k)
                        Case "1500K"
                            di = New libnodave.daveInterface(fds, "IF1", m_MyNode, libnodave.daveProtoPPI, libnodave.daveSpeed1500k)
                        Case "45K"
                            di = New libnodave.daveInterface(fds, "IF1", m_MyNode, libnodave.daveProtoPPI, libnodave.daveSpeed45k)
                        Case "93K"
                            di = New libnodave.daveInterface(fds, "IF1", m_MyNode, libnodave.daveProtoPPI, libnodave.daveSpeed93k)
                        Case Else

                    End Select

                Case "ISOTCP"
                    di = New libnodave.daveInterface(fds, "IF1", 0, libnodave.daveProtoISOTCP, libnodave.daveSpeed187k)
                Case "ISOTCP S7200"
                    di = New libnodave.daveInterface(fds, "IF1", 0, libnodave.daveProtoISOTCP243, libnodave.daveSpeed187k)
                Case Else
                    di = New libnodave.daveInterface(fds, "IF1", 0, libnodave.daveProtoMPI_IBH, libnodave.daveSpeed187k)
            End Select

            Debug.Print("SiemensPLCs - Set the Interface")


            di.setTimeout(TimeOut)  ' Make this longer if you have a very long response time
            res = di.initAdapter

            If res = 0 Then       ' init Adapter is ok
                Debug.Print("SiemensPLCs - Intialize the Adaptor")

                dc = New libnodave.daveConnection(di, m_TargetNode, Rack, Slot)
                ' rack amd slot don't matter in case of MPI/PPI, TargetNode doesn't matter for Ethernet


                res = dc.connectPLC()
                If res = 0 Then
                    Debug.Print("SiemensPLCs - Connected to PLC")
                    PLCConnectedOK = True
                Else : Debug.Print(res)
                End If

            End If
        End If
    End Sub

    ' Didn't now where to call this from and so it currently doesn't do anything
    Private Sub ClosePLCConnection()
        res = dc.disconnectPLC
        If res = 0 Then
            Debug.Print("Disconected from the PLC")
        End If
        res = di.disconnectAdapter
        If res = 0 Then
            Debug.Print("Disconected from the Adapter")
        End If
        Select Case m_SiemensCommType
            Case "MPI S7300/400", "MPI2 S7300/400", "PPI S7200"
                'Close the Comm Port
                res = libnodave.closePort(m_SiemensCommPort)
                If res = 0 Then
                    Debug.Print("Closed the Port")
                End If
            Case "ISOTCP", "ISOTCP S7200"
                ' Close the Socket connection
                res = libnodave.closeSocket(102)
                If res = 0 Then
                    Debug.Print("Closed Socket 102")
                End If
            Case Else
                '"MPI IBH"
                res = libnodave.closeSocket(1099)
                If res = 0 Then
                    Debug.Print("Closed Socket 1099")
                End If
        End Select

        If res = 0 Then
            Debug.Print("Disconected from the Adapter")
        End If
    End Sub


#End Region


    '*************************************************************
    '* Overloaded method of ReadAny - that reads only one element
    '*************************************************************
    Public Function ReadAny(ByVal startAddress As String) As String Implements ICommComponent.ReadAny
        Return ReadAny(startAddress, 1)(0)
    End Function


    '*********************************************************************************************
    Public Function ReadAny(ByVal startAddress As String, ByVal numberOfElements As Integer) As String() Implements ICommComponent.ReadAny

        Dim d() As String = {0}
        Return d

    End Function

    '*************************************************************
    '* Overloaded methods of WriteData
    '*************************************************************
    Public Function WriteData(ByVal startAddress As String, ByVal dataToWrite As String) As String Implements ICommComponent.WriteData
        If dataToWrite Is Nothing Then
            Return 0
        End If
        Return 0
    End Function

    'Overloaded version of WriteData to alow for FullDataType
    Public Function WriteData(ByVal startAddress As String, ByVal dataToWrite As String, ByVal FullDataTypeBP As String, ByVal DataLengthBP As Integer) As String Implements ICommComponent.WriteData
        If dataToWrite Is Nothing Then
            Return 0
        End If


        ' Handshake the Reads and Writes
        If ReadinProgress = True Then

            startAddressStored = startAddress
            dataToWriteStored = dataToWrite
            FullDataTypeStored = FullDataTypeBP
            DataLengthStored = DataLengthBP

            writeWaiting = True

            Return 0
        End If

        If startAddress = "OKtoWrite" Then
            startAddress = startAddressStored
            dataToWrite = dataToWriteStored
            FullDataTypeBP = FullDataTypeStored
            DataLengthBP = DataLengthStored
        End If

        Dim ParsedResult As ParsedDataAddress = ParseAddress(startAddress)

        '* Invalid address?
        If ParsedResult.DataArea = "" Then
            Throw New MfgControl.AdvancedHMI.Drivers.PLCDriverException("Invalid Address")
        End If


        Dim Writeresult As Integer
        Select Case ParsedResult.DataArea
            Case "Q"
                Select Case FullDataTypeBP
                    Case "Bit"
                        Writeresult = dc.writeBits(libnodave.daveOutputs, ParsedResult.Offset, ParsedResult.BitNumber, 1, BitConverter.GetBytes(libnodave.daveSwapIed_32(CBool(dataToWrite))))
                    Case "Unsigned Byte", "Signed Byte", "Hex Byte"
                        Writeresult = dc.writeBytes(libnodave.daveOutputs, 0, ParsedResult.Offset, 1, BitConverter.GetBytes(CInt(dataToWrite)))
                    Case "Unsigned Word", "Signed Word", "Hex Word"
                        Writeresult = dc.writeBytes(libnodave.daveOutputs, 0, ParsedResult.Offset, 2, BitConverter.GetBytes(libnodave.daveSwapIed_16(CInt(dataToWrite))))
                    Case "Unsigned Double Word", "Signed Double Word"
                        Writeresult = dc.writeBytes(libnodave.daveOutputs, 0, ParsedResult.Offset, 4, BitConverter.GetBytes(libnodave.daveSwapIed_32(CInt(dataToWrite))))
                    Case Else
                        Debug.Print("WriteData Q - Case data length - Full Data type not recognised")
                End Select
            Case "M"
                Select Case FullDataTypeBP
                    Case "Bit"
                        Writeresult = dc.writeBits(libnodave.daveFlags, ParsedResult.Offset, ParsedResult.BitNumber, 1, BitConverter.GetBytes(libnodave.daveSwapIed_32(CBool(dataToWrite))))
                    Case "Unsigned Byte", "Signed Byte", "Hex Byte"
                        Writeresult = dc.writeBytes(libnodave.daveFlags, 0, ParsedResult.Offset, 1, BitConverter.GetBytes(CInt(dataToWrite)))
                    Case "Unsigned Word", "Signed Word", "Hex Word"
                        Writeresult = dc.writeBytes(libnodave.daveFlags, 0, ParsedResult.Offset, 2, BitConverter.GetBytes(libnodave.daveSwapIed_16(CInt(dataToWrite))))
                    Case "Unsigned Double Word", "Signed Double Word"
                        Writeresult = dc.writeBytes(libnodave.daveFlags, 0, ParsedResult.Offset, 4, BitConverter.GetBytes(libnodave.daveSwapIed_32(CInt(dataToWrite))))
                    Case "Float"
                        Writeresult = dc.writeBytes(libnodave.daveFlags, 0, ParsedResult.Offset, 4, BitConverter.GetBytes(libnodave.toPLCfloat(CSng(dataToWrite))))
                    Case Else
                        Debug.Print("WriteData M - Get data length - Full Data type not recognised")
                End Select
            Case "V", "D" ' V is for S7-200, D id for the rest of the PLC range that use BataBase memory
                Select Case FullDataTypeBP
                    Case "Unsigned Byte", "Signed Byte", "Hex Byte"
                        Writeresult = dc.writeBytes(libnodave.daveDB, ParsedResult.DBDataBase, ParsedResult.Offset, 1, BitConverter.GetBytes(CInt(dataToWrite)))
                    Case "Unsigned Word", "Signed Word", "Hex Word"
                        Writeresult = dc.writeBytes(libnodave.daveDB, ParsedResult.DBDataBase, ParsedResult.Offset, 2, BitConverter.GetBytes(libnodave.daveSwapIed_16(CInt(dataToWrite))))
                    Case "Unsigned Double Word", "Signed Double Word"
                        Writeresult = dc.writeBytes(libnodave.daveDB, ParsedResult.DBDataBase, ParsedResult.Offset, 4, BitConverter.GetBytes(libnodave.daveSwapIed_32(CInt(dataToWrite))))
                    Case "Float"
                        Writeresult = dc.writeBytes(libnodave.daveDB, ParsedResult.DBDataBase, ParsedResult.Offset, 4, BitConverter.GetBytes(libnodave.toPLCfloat(CSng(dataToWrite))))
                    Case Else
                        Debug.Print("WriteData V/DB- Case data length - Full Data type not recognised")
                End Select
            Case "C" ' TODO
                If m_SiemensCommType = "PPI S7200" Then
                    Writeresult = dc.writeBytes(libnodave.daveCounter200, 0, ParsedResult.Offset, ParsedResult.BytesPerElements, buf)
                Else
                    Writeresult = dc.writeBytes(libnodave.daveCounter, 0, ParsedResult.Offset, ParsedResult.BytesPerElements, buf)
                End If
            Case "T" 'TODO
                If m_SiemensCommType = "PPI S7200" Then
                    Writeresult = dc.writeBytes(libnodave.daveTimer200, 0, ParsedResult.Offset, ParsedResult.BytesPerElements, buf)
                Else
                    Writeresult = dc.writeBytes(libnodave.daveTimer, 0, ParsedResult.Offset, ParsedResult.BytesPerElements, buf)
                End If
            Case "I"
                Debug.Print("Can't write to Inputs")
            Case Else
                Debug.Print("WriteData - Data Area type not recognised")
        End Select

        Debug.Print("Return from Write = {0:d}", result)

        writeWaiting = False

        Return 0
    End Function


    Private Structure ParsedDataAddress
        Dim PLCAddress As String
        Dim DBDataBase As Integer
        Dim DataArea As String
        Dim DataType As String
        Dim FullDataType As String
        Dim Offset As Integer
        Dim SubElement As Integer
        Dim BitNumber As Integer
        Dim BytesPerElements As Integer
        Dim TableSizeInBytes As Integer
        Dim NumberRead As Integer
        Dim InternallyRequested As Boolean
        Dim NumberOfElements As Integer
        Dim TargetNode As Integer
    End Structure

    '*********************************************************************************
    '* Parse the address string and validate, if invalid, Return 0 in FileType
    '* Convert the file type letter Type to the corresponding value
    '* Reference page 7-18
    '*********************************************************************************





    Private RE1 As New Regex("(?i)^\s*(?<DataArea>([IQMTCSV]))(?<DataType>([BWD]))?(?<Offset>\d{1,4}).?(?<BitNumber>\d{1,3})?\s*$")

    Private RE2 As New Regex("(?i)^\s*\w{2}?(?<DBDataBase>\d{1,4}).\w{2}(?<DataType>([BWD]))?(?<Offset>\d{1,4}).?(?<BitNumber>\d{1,3})?\s*$")
    'Private RE2 As New Regex("(?i)^\s*(?<FileType>[SBN])(?<FileNumber>\d{1,3})(/(?<BitNumber>\d{1,4}))\s*$")
    Private Function ParseAddress(ByVal DataAddress As String) As ParsedDataAddress

        Dim result As New ParsedDataAddress

        result.DataArea = 0  '* Let a 0 indicate an invalid address
        result.BitNumber = 99  '* Let a 99 indicate no bit level requested

        '*********************************
        '* Try all match patterns
        '*********************************
        Dim mc As MatchCollection

        If DataAddress.Chars(0) = "D" Then 'If address atarts with D then use the DB version of Match
            mc = RE2.Matches(DataAddress)
            result.DataArea = "D"
        Else
            mc = RE1.Matches(DataAddress)
            result.DBDataBase = 1
        End If


        If mc.Count <= 0 Then
            'mc = RE2.Matches(DataAddress)
            'If mc.Count <= 0 Then
            Return result
            '    End If
        End If

        '*******************************************************************
        '* Keep the original address with the parsed values for later use
        '*******************************************************************
        result.PLCAddress = DataAddress


        '*********************************************
        '* Get elements extracted from match patterns
        '*********************************************
        If mc.Item(0).Groups("BitNumber").Length > 0 Then
            result.BitNumber = mc.Item(0).Groups("BitNumber").ToString
        End If

        '* Was an element number specified? 
        If mc.Item(0).Groups("Offset").Length > 0 Then
            result.Offset = mc.Item(0).Groups("Offset").ToString
        End If

        '* Get DataArea 
        If mc.Item(0).Groups("DataArea").Length > 0 Then
            result.DataArea = mc.Item(0).Groups("DataArea").ToString
        End If

        '* Get DataType 
        If mc.Item(0).Groups("DataType").Length > 0 Then
            result.DataType = mc.Item(0).Groups("DataType").ToString
        End If

        '* Get DataBase
        If mc.Item(0).Groups("DBDataBase").Length > 0 Then
            result.DBDataBase = Convert.ToInt16(mc.Item(0).Groups("DBDataBase").ToString)
        End If


        '**************************************************************************
        '* Was a bit number higher than 15 specified along with an element number?
        '**************************************************************************
        If result.BitNumber > 15 And result.BitNumber < 99 Then
            '* IO points can use higher bit numbers, so make sure it is not IO
            If result.DataArea <> &H8B And result.DataArea <> &H8C Then
                result.Offset += result.BitNumber >> 3
                result.BitNumber = result.BitNumber Mod 8
            End If
        End If

        Return result
    End Function



    '****************************************************************************
    '* This is required to sync the event back to the parent form's main thread
    '****************************************************************************
    Dim drsd As EventHandler = AddressOf DataReceivedSync
    'Delegate Sub DataReceivedSyncDel(ByVal sender As Object, ByVal e As EventArgs)
    Private Sub DataReceivedSync(ByVal sender As Object, ByVal e As MfgControl.AdvancedHMI.Drivers.PLCCommEventArgs)
        RaiseEvent DataReceived(sender, e)
    End Sub

End Class

#Region "Design Time Drop Down Lists"
' This UITypeEditor can be associated with Int32, Double and Single
' properties to provide a design-mode angle selection interface.
<System.Security.Permissions.PermissionSetAttribute(System.Security.Permissions.SecurityAction.Demand, Name:="FullTrust")> _
Public Class SiemensBaudRateEditor
    Inherits System.Drawing.Design.UITypeEditor

    Public Sub New()
    End Sub

    ' Indicates whether the UITypeEditor provides a form-based (modal) dialog, 
    ' drop down dialog, or no UI outside of the properties window.
    Public Overloads Overrides Function GetEditStyle(ByVal context As System.ComponentModel.ITypeDescriptorContext) As System.Drawing.Design.UITypeEditorEditStyle
        Return UITypeEditorEditStyle.DropDown
    End Function

    ' Displays the UI for value selection.
    Dim edSvc As IWindowsFormsEditorService
    Private WithEvents lb As ListBox
    Public Overloads Overrides Function EditValue(ByVal context As System.ComponentModel.ITypeDescriptorContext, ByVal provider As System.IServiceProvider, ByVal value As Object) As Object
        ' Uses the IWindowsFormsEditorService to display a 
        ' drop-down UI in the Properties window.
        edSvc = CType(provider.GetService(GetType(IWindowsFormsEditorService)), IWindowsFormsEditorService)
        lb = New ListBox
        If (edSvc IsNot Nothing) Then
            lb.Items.Add("9600")
            lb.Items.Add("19200")
            lb.Items.Add("38400")
            lb.Items.Add("57600")
            lb.Items.Add("115200")
            lb.Items.Add("187500")
            AddHandler lb.SelectedIndexChanged, AddressOf ListItemSelected

            edSvc.DropDownControl(lb)
        End If
        Return lb.SelectedItem
    End Function

    Private Sub ListItemSelected(ByVal sender As Object, ByVal e As System.EventArgs)
        edSvc.CloseDropDown()
    End Sub



    ' Indicates whether the UITypeEditor supports painting a 
    ' representation of a property's value.
    Public Overloads Overrides Function GetPaintValueSupported(ByVal context As System.ComponentModel.ITypeDescriptorContext) As Boolean
        Return False
    End Function
End Class



' This UITypeEditor can be associated with Int32, Double and Single
' properties to provide a design-mode angle selection interface.
<System.Security.Permissions.PermissionSetAttribute(System.Security.Permissions.SecurityAction.Demand, Name:="FullTrust")> _
Public Class SiemensCommTypeEditor
    Inherits System.Drawing.Design.UITypeEditor

    Public Sub New()
    End Sub

    ' Indicates whether the UITypeEditor provides a form-based (modal) dialog, 
    ' drop down dialog, or no UI outside of the properties window.
    Public Overloads Overrides Function GetEditStyle(ByVal context As System.ComponentModel.ITypeDescriptorContext) As System.Drawing.Design.UITypeEditorEditStyle
        Return UITypeEditorEditStyle.DropDown
    End Function

    ' Displays the UI for value selection.
    Dim edSvc As IWindowsFormsEditorService
    Private WithEvents lb As ListBox
    Public Overloads Overrides Function EditValue(ByVal context As System.ComponentModel.ITypeDescriptorContext, ByVal provider As System.IServiceProvider, ByVal value As Object) As Object
        ' Uses the IWindowsFormsEditorService to display a 
        ' drop-down UI in the Properties window.
        edSvc = CType(provider.GetService(GetType(IWindowsFormsEditorService)), IWindowsFormsEditorService)
        lb = New ListBox
        If (edSvc IsNot Nothing) Then
            lb.Items.Add("MPI S7300/400")
            lb.Items.Add("MPI2 S7300/400")
            lb.Items.Add("PPI S7200")
            lb.Items.Add("ISOTCP")
            lb.Items.Add("ISOTCP S7200")
            lb.Items.Add("MPI IBH")
            AddHandler lb.SelectedIndexChanged, AddressOf ListItemSelected

            edSvc.DropDownControl(lb)
        End If
        Return lb.SelectedItem
    End Function

    Private Sub ListItemSelected(ByVal sender As Object, ByVal e As System.EventArgs)
        edSvc.CloseDropDown()
    End Sub



    ' Indicates whether the UITypeEditor supports painting a 
    ' representation of a property's value.
    Public Overloads Overrides Function GetPaintValueSupported(ByVal context As System.ComponentModel.ITypeDescriptorContext) As Boolean
        Return False
    End Function
End Class

' This UITypeEditor can be associated with Int32, Double and Single
' properties to provide a design-mode angle selection interface.
<System.Security.Permissions.PermissionSetAttribute(System.Security.Permissions.SecurityAction.Demand, Name:="FullTrust")> _
Public Class SiemensInterfaceSpeedEditor
    Inherits System.Drawing.Design.UITypeEditor

    Public Sub New()
    End Sub

    ' Indicates whether the UITypeEditor provides a form-based (modal) dialog, 
    ' drop down dialog, or no UI outside of the properties window.
    Public Overloads Overrides Function GetEditStyle(ByVal context As System.ComponentModel.ITypeDescriptorContext) As System.Drawing.Design.UITypeEditorEditStyle
        Return UITypeEditorEditStyle.DropDown
    End Function

    ' Displays the UI for value selection.
    Dim edSvc As IWindowsFormsEditorService
    Private WithEvents lb As ListBox
    Public Overloads Overrides Function EditValue(ByVal context As System.ComponentModel.ITypeDescriptorContext, ByVal provider As System.IServiceProvider, ByVal value As Object) As Object
        ' Uses the IWindowsFormsEditorService to display a 
        ' drop-down UI in the Properties window.
        edSvc = CType(provider.GetService(GetType(IWindowsFormsEditorService)), IWindowsFormsEditorService)
        lb = New ListBox
        If (edSvc IsNot Nothing) Then
            lb.Items.Add("9K")
            lb.Items.Add("19K")
            lb.Items.Add("187K")
            lb.Items.Add("500K")
            lb.Items.Add("1500K")
            lb.Items.Add("45K")
            lb.Items.Add("93K")
            AddHandler lb.SelectedIndexChanged, AddressOf ListItemSelected

            edSvc.DropDownControl(lb)
        End If
        Return lb.SelectedItem
    End Function

    Private Sub ListItemSelected(ByVal sender As Object, ByVal e As System.EventArgs)
        edSvc.CloseDropDown()
    End Sub



    ' Indicates whether the UITypeEditor supports painting a 
    ' representation of a property's value.
    Public Overloads Overrides Function GetPaintValueSupported(ByVal context As System.ComponentModel.ITypeDescriptorContext) As Boolean
        Return False
    End Function
End Class




' This UITypeEditor can be associated with Int32, Double and Single
' properties to provide a design-mode angle selection interface.
<System.Security.Permissions.PermissionSetAttribute(System.Security.Permissions.SecurityAction.Demand, Name:="FullTrust")> _
Public Class SiemensCommPortEditor
    Inherits System.Drawing.Design.UITypeEditor

    Public Sub New()
    End Sub

    ' Indicates whether the UITypeEditor provides a form-based (modal) dialog, 
    ' drop down dialog, or no UI outside of the properties window.
    Public Overloads Overrides Function GetEditStyle(ByVal context As System.ComponentModel.ITypeDescriptorContext) As System.Drawing.Design.UITypeEditorEditStyle
        Return UITypeEditorEditStyle.DropDown
    End Function

    ' Displays the UI for value selection.
    Dim edSvc As IWindowsFormsEditorService
    Private WithEvents lb As ListBox
    Public Overloads Overrides Function EditValue(ByVal context As System.ComponentModel.ITypeDescriptorContext, ByVal provider As System.IServiceProvider, ByVal value As Object) As Object
        ' Uses the IWindowsFormsEditorService to display a 
        ' drop-down UI in the Properties window.
        edSvc = CType(provider.GetService(GetType(IWindowsFormsEditorService)), IWindowsFormsEditorService)
        lb = New ListBox
        If (edSvc IsNot Nothing) Then
            lb.Items.Add("COM1")
            lb.Items.Add("COM2")
            lb.Items.Add("COM3")
            lb.Items.Add("COM4")
            lb.Items.Add("COM5")
            lb.Items.Add("COM6")
            lb.Items.Add("COM7")
            lb.Items.Add("COM8")
            lb.Items.Add("COM9")
            lb.Items.Add("COM10")
            AddHandler lb.SelectedIndexChanged, AddressOf ListItemSelected

            edSvc.DropDownControl(lb)
        End If
        Return lb.SelectedItem
    End Function

    Private Sub ListItemSelected(ByVal sender As Object, ByVal e As System.EventArgs)
        edSvc.CloseDropDown()
    End Sub



    ' Indicates whether the UITypeEditor supports painting a 
    ' representation of a property's value.
    Public Overloads Overrides Function GetPaintValueSupported(ByVal context As System.ComponentModel.ITypeDescriptorContext) As Boolean
        Return False
    End Function
End Class



#End Region