'**********************************************************************************************
'* PCCC over Ethernet/IP
'*
'* Archie Jacobs
'* Manufacturing Automation, LLC
'* ajacobs@mfgcontrol.com
'* 01-DEC-09
'*
'* Copyright 2009, 2010 Archie Jacobs
'*
'* This class implements the two layers of the Allen Bradley DF1 protocol.
'* In terms of the AB documentation, the data link layer acts as the transmitter and receiver.
'* Communication commands in the format described in chapter 7, are passed to
'* the data link layer using the SendData method.
'*
'* Reference : Allen Bradley Publication 1770-6.5.16
'*
'* 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.
'*
'*
'* 22-MAR-07  Added floating point read/write capability
'* 23-MAR-07  Added string file read/write
'*              Handle reading of up to 256 elements in one call
'* 24-MAR-07  Corrected ReadAny to allow an array of strings to be read
'* 26-MAR-07  Changed error handling to throw exceptions to comply more with .NET standards
'* 29-MAR-07  When reading multiple sub elements of timers or counters, read all the same sub-element
'* 30-MAR-07  Added GetDataMemory, GetSlotCount, and GetIoConfig  - all were reverse engineered
'* 04-APR-07  Added GetMicroDataMemory
'* 07-APR-07  Reworked the Responded variable to try to fix a small issue during a lot of rapid reads
'* 12-APR-07  Fixed a problem with reading Timers & Counters  more than 39 at a time
'* 01-MAY-07  Fixed a problem with writing multiple elements using WriteData
'* 06-JUN-07  Add the assumption of file number 2 for S file (ParseAddress) e.g. S:1=S2:1
'* 30-AUG-07  Fixed a problem where the value 16 gets doubled, it would not check the last byte
'* 13-FEB-08  Added more errors codes in DecodeMessage, Added the EXT STS if STS=&hF0
'* 13-FEB-08  Added GetML1500DataMemory to work with the MicroLogix 1500
'* 14-FEB-08  Added Reading/Writing of Long data with help from Tony Cicchino
'* 14-FEB-08  Corrected problem when writing an array of Singles to an Integer table
'* 18-FEB-08  Corrected an error in SendData that would not allow it to retry
'* 23-FEB-08  Corrected a problem when reading elements with extended addressing
'* 26-FEB-08  Reconfigured ReadRawData & WriteRawData
'* 28-FEB-08  Completed Downloading & Uploading functions
'* 11-MAR-08  Added processor code &H6F
'* 05-APR-08  Set BytesToRead equal to SerialPort.Read in case less is read
'* 01-SEP-08  Separated Data Link Layer into its own class to prepare for HMI components
'* 23-SEP-08  Added AddVariableNotification to work with visual components (Gauge, Meter, etc)
'* 26-SEP-08  Bug Fix-Clear ReceivedDataPacketBuilder in the event of ACK, NAK, or ENQ
'* 28-SEP-08  Added AUTO baud rate option to auto detect on start up
'* 28-SEP-08  Bug Fix-Auto detect needed to also do an echo test because ENQ alone doesn't do checksum
'* 11-OCT-08  Bug fix- Various problems in ExtractData
'* 11-OCT-08  Modified WriteData(..., as string) to convert to data type according to address
'* 22-OCT-08  Fixed problem with variable notification of timers other than element 0
'* 24-OCT-08  Fixed problem with reading multiple timers using ReadAny
'* 27-OCT-08  Fixed a problem with InternalRequest being improperly triggered in received data
'*              This was cause when a routine went to PrefixAndSend instead of ReadRawData
'* 01-NOV-08  Renamed AddVariableNotification to Subscribe
'*              A subscription can now request more than one consectutive value
'* 01-DEC-09  Fixed problem with addressing IO at bit levels above 15
'* 01-DEC-09  Adapted to use Ethernet/IP as the data layer 
'*******************************************************************************************************
Imports System.ComponentModel.Design
Imports System.Text.RegularExpressions
Imports System.ComponentModel
Imports System
Imports System.Drawing
Imports System.Drawing.Design
Imports System.Reflection
Imports System.Windows.Forms
Imports System.Windows.Forms.Design

'<Assembly: system.Security.Permissions.SecurityPermissionAttribute(system.Security.Permissions.SecurityAction.RequestMinimum)> 
'<Assembly: CLSCompliant(True)> 
Public Class EthernetIPforSLCMicroComm
    Inherits System.ComponentModel.Component
    Implements ICommComponent

    '* Create a common instance to share so multiple DF1Comms can be used in a project
    'Private DLL As CIP
    Private Shared DLL(10) As MfgControl.AdvancedHMI.Drivers.CIP
    Private MyDLLInstance As Integer

    Private ProcessorType As Integer

    Private QueuedCommand As New System.Collections.ObjectModel.Collection(Of Byte)
    Private CommandInQueue As Boolean

    Private DisableEvent As Boolean
    Private rnd As New Random()
    '* create a random number as a TNS starting point
    Private TNS As UInt16 = (rnd.Next And &H7F) + 1
    Public DataPackets(255) As System.Collections.ObjectModel.Collection(Of Byte)

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

    Public Event UnsolictedMessageRcvd As EventHandler
    Public Event AutoDetectTry As EventHandler
    Public Event DownloadProgress As EventHandler
    Public Event UploadProgress As EventHandler

    Private Responded(255) As Boolean


    '*********************************************************************************
    '* 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 FileType As Long
        Dim FileNumber As Long
        Dim ElementNumber 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
    End Structure

    Private CurrentID As Integer = 1

    '* keep the original address by ref of low TNS byte so it can be returned to a linked polling address
    Private Shared PLCAddressByTNS(255) As ParsedDataAddress
    Private CurrentAddressToRead As String
    Private PolledAddressList As New List(Of PolledAddressInfo)
    Private tmrPollList As New List(Of Windows.Forms.Timer)

    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 CIP
        '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(MyDLLInstance) IsNot Nothing Then RemoveHandler DLL(MyDLLInstance).DataReceived, Dr

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

    '***************************************************************
    '* Create the Data Link Layer Instances
    '* if the IP Address is the same, then resuse a common instance
    '***************************************************************
    Private Sub CreateDLLInstance()
        If DLL(0) IsNot Nothing Then
            '* At least one DLL instance already exists,
            '* so check to see if it has the same IP address
            '* if so, reuse the instance, otherwise create a new one
            Dim i As Integer
            While DLL(i) IsNot Nothing AndAlso DLL(i).EIPEncap.IPAddress <> m_IPAddress AndAlso i < 11
                i += 1
            End While
            MyDLLInstance = i
        End If

        If DLL(MyDLLInstance) Is Nothing Then
            DLL(MyDLLInstance) = New MfgControl.AdvancedHMI.Drivers.CIP
            DLL(MyDLLInstance).EIPEncap.IPAddress = m_IPAddress
        End If
        AddHandler DLL(MyDLLInstance).DataReceived, Dr
    End Sub


#Region "Properties"
    Private m_MyNode As Integer
    <System.ComponentModel.Category("Communication Settings")> _
    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
    <System.ComponentModel.Category("Communication Settings")> _
    Public Property TargetNode() As Integer
        Get
            Return m_TargetNode
        End Get
        Set(ByVal value As Integer)
            m_TargetNode = value
        End Set
    End Property

    Private m_IPAddress As String = "192.168.0.10"
    <System.ComponentModel.Category("Communication Settings")> _
    Public Property IPAddress() As String
        Get
            'Return DLL(MyDLLInstance).EIPEncap.IPAddress
            Return m_IPAddress
        End Get
        Set(ByVal value As String)
            m_IPAddress = value

            '* If a new instance needs to be created, such as a different IP Address
            CreateDLLInstance()


            If DLL(MyDLLInstance) Is Nothing Then
            Else
                DLL(MyDLLInstance).EIPEncap.IPAddress = value
            End If
        End Set
    End Property

    '**************************************************************
    '* Determine whether to wait for a data read or raise an event
    '**************************************************************
    Private m_AsyncMode As Boolean
    <System.ComponentModel.Category("Communication Settings")> _
    Public Property AsyncMode() As Boolean
        Get
            Return m_AsyncMode
        End Get
        Set(ByVal value As Boolean)
            m_AsyncMode = value
        End Set
    End Property

    '**************************************************************
    '* Stop the polling of subscribed data
    '**************************************************************
    Private m_DisableSubscriptions
    <System.ComponentModel.Category("Communication Settings")> _
    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
            'If Me.Site.DesignMode Then

            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



            'Dim dh As IDesignerHost = DirectCast(Me.GetService(GetType(IDesignerHost)), IDesignerHost)
            'If dh IsNot Nothing Then
            '    Dim obj As Object = dh.RootComponent
            '    If obj IsNot Nothing Then
            '        m_ParentForm = DirectCast(obj, Form)
            '    End If
            'End If

            'Dim instance As IDesignerHost = Me.GetService(GetType(IDesignerHost))
            'm_SynchronizingObject = instance.RootComponent
            ''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 "Public Methods"


    '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.FileType <> 0 Then
            Dim tmpPA As New PolledAddressInfo
            tmpPA.PLCAddress = PLCAddress
            'tmpPA.PollRate = CycleTime
            tmpPA.PollRate = PollRate
            tmpPA.dlgCallBack = CallBack
            tmpPA.ID = CurrentID
            tmpPA.FileNumber = ParsedResult.FileNumber
            tmpPA.ElementNumber = ParsedResult.Element
            tmpPA.SubElement = ParsedResult.SubElement
            tmpPA.BitNumber = ParsedResult.BitNumber
            tmpPA.FileType = ParsedResult.FileType
            tmpPA.BytesPerElement = ParsedResult.BytesPerElements
            tmpPA.ElementsToRead = 1


            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 Windows.Forms.Timer
                'If CycleTime > 0 Then
                '    tmrTemp.Interval = CycleTime
                'Else
                '    tmrTemp.Interval = 10
                'End If
                tmrTemp.Interval = PollRate

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

                tmrTemp.Enabled = True
                'End If
            End If

            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.FileNumber = A2.FileNumber Then
            If A1.ElementNumber > A2.ElementNumber Then
                Return 1
            ElseIf A1.ElementNumber = A2.ElementNumber Then
                Return 0
            Else
                Return -1
            End If
        End If

        If A1.FileNumber > A2.FileNumber 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 no more items to be polled, remove all polling timers 28-NOV-10
            If PolledAddressList.Count = 0 Then
                For j As Integer = 0 To tmrPollList.Count - 1
                    tmrPollList(0).Enabled = False
                    tmrPollList.Remove(tmrPollList(0))
                Next
            End If
        End If
    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)

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

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

            '* Group into the same read if there is less than a 20 element gap
            '* Do not group IO addresses because they can exceed 16 bits which causes problems
            Dim ElementSpan As Integer = 20
            Dim HighestBit As Integer = PolledAddressList(i).BitNumber
            'If PolledAddressList(i).FileType <> &H8B And PolledAddressList(i).FileType <> &H8C Then
            While i < PolledAddressList.Count - 1 AndAlso (PolledAddressList(i).FileNumber = PolledAddressList(i + 1).FileNumber And _
                            ((PolledAddressList(i + 1).ElementNumber - PolledAddressList(i).ElementNumber < 20 And PolledAddressList(i).FileType <> &H8B And PolledAddressList(i).FileType <> &H8C) Or _
                                PolledAddressList(i + 1).ElementNumber = PolledAddressList(i).ElementNumber))
                NumberToReadCalc = PolledAddressList(i + 1).ElementNumber - PolledAddressList(FirstElement).ElementNumber + PolledAddressList(i + 1).ElementsToRead
                If NumberToReadCalc > NumberToRead Then NumberToRead = NumberToReadCalc

                '* This is used for IO addresses wher the bit can be above 15
                If PolledAddressList(i).BitNumber < 99 And PolledAddressList(i).BitNumber > HighestBit Then HighestBit = PolledAddressList(i).BitNumber

                i += 1
            End While


            '*****************************************************
            '* IO addresses can exceed bit 15 on the same element
            '*****************************************************
            If PolledAddressList(FirstElement).FileType = &H8B Or PolledAddressList(FirstElement).FileType = &H8C Then
                If PolledAddressList(FirstElement).FileType = PolledAddressList(i).FileType Then
                    If HighestBit > 15 And HighestBit < 99 Then
                        If ((HighestBit >> 4) + 1) > NumberToRead Then
                            NumberToRead = (HighestBit >> 4) + 1
                        End If
                    End If
                End If
            End If


            '* Get file type designation.
            '* Is it more than one character (e.g. "ST")
            If PolledAddressList(FirstElement).PLCAddress.Substring(1, 1) >= "A" And PolledAddressList(FirstElement).PLCAddress.Substring(1, 1) <= "Z" Then
                PLCAddress = PolledAddressList(FirstElement).PLCAddress.Substring(0, 2) & PolledAddressList(FirstElement).FileNumber & ":" & PolledAddressList(FirstElement).ElementNumber
            Else
                PLCAddress = PolledAddressList(FirstElement).PLCAddress.Substring(0, 1) & PolledAddressList(FirstElement).FileNumber & ":" & PolledAddressList(FirstElement).ElementNumber
            End If


            '*******************************************
            '* Read 3 bytes for each timer and counter
            '*******************************************
            If PolledAddressList(FirstElement).FileType = 134 Then
                'PLCAddress = "T" & PolledAddressList(FirstElement).FileNumber & ":" & PolledAddressList(FirstElement).ElementNumber
                NumberToRead *= 3
            End If

            If PolledAddressList(FirstElement).FileType = 135 Then
                'PLCAddress = "C" & PolledAddressList(FirstElement).FileNumber & ":" & PolledAddressList(FirstElement).ElementNumber
                NumberToRead *= 3
            End If


            '* This eliminates the error of late binding when porting to Win CE
            Dim tmr As Windows.Forms.Timer = sender

            If PolledAddressList(i).PollRate = tmr.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
                    Me.ReadAny(PLCAddress, NumberToRead)
                    'Me.ReadAny(PolledAddressList(FirstElement).PLCAddress, 1)
                    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}

                    m_SynchronizingObject.BeginInvoke(PolledAddressList(i).dlgCallBack, CObj(TempArray))
                    '* 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
        tmrPollList(intTimerIndex).Enabled = True
    End Sub

    '**************************************************************************************
    '* See DataLinkLayer_DataReceived
    '**************************************************************************************
    'Private Sub PolledValueReturned(ByVal address As String, ByVal values() As String)
    '    Dim k As Integer = 0
    '    While k < PolledAddressList.Count AndAlso address <> PolledAddressList(k).PLCAddress
    '        k += 1Fdatare

    '    End While


    '    '* Callback only if within array bounds
    '    If (k < PolledAddressList.Count) Then
    '        m_SynchronizingObject.BeginInvoke(PolledAddressList(k).dlgCallBack, values)

    '        'PolledAddressList(k).LastValue = values(0)
    '    End If
    'End Sub

    '***************************************
    '* COMMAND IMPLEMENTATION SECTION
    '***************************************
    Public Sub SetRunMode()
        '* Get the processor type by using a get status command
        Dim reply As Integer
        Dim rTNS As Integer
        Dim data(0) As Byte
        Dim Func As Integer

        If GetProcessorType() = &H58 Then  '* ML1000
            data(0) = 2
            Func = &H3A
        Else
            Func = &H80
            data(0) = 6
        End If

        reply = PrefixAndSend(&HF, Func, data, True, rTNS)

        If reply <> 0 Then Throw New MfgControl.AdvancedHMI.Drivers.PLCDriverException("Failed to change to Run mode, Check PLC Key switch - " & DecodeMessage(reply))
    End Sub

    Public Sub SetProgramMode()
        '* Get the processor type by using a get status command
        Dim reply As Integer
        Dim rTNS As Integer
        Dim data(0) As Byte
        Dim Func As Integer

        If GetProcessorType() = &H58 Then '* ML1000
            data(0) = 0
            Func = &H3A
        Else
            data(0) = 1
            Func = &H80
        End If

        reply = PrefixAndSend(&HF, Func, data, True, rTNS)

        If reply <> 0 Then Throw New MfgControl.AdvancedHMI.Drivers.PLCDriverException("Failed to change to Program mode, Check PLC Key switch - " & DecodeMessage(reply))
    End Sub


    'Public Sub DisableForces(ByVal targetNode As Integer)
    '    Dim rTNS As Integer
    '    Dim data() As Byte = {}
    '    Dim reply As Integer = PrefixAndSend(TargetNode, &HF, &H41, data, True, rTNS)
    'End Sub

    ''' <summary>
    ''' Retreives the processor code by using the get status command
    ''' </summary>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Function GetProcessorType() As Integer
        '* Get the processor type by using a get status command
        Dim rTNS As Integer
        Dim Data(0) As Byte
        If PrefixAndSend(6, 3, Data, True, rTNS) = 0 Then
            '* Returned data psoition 11 is the first character in the ASCII name of the processor
            '* Position 9 is the code for the processor
            '* &H58 = ML1000
            '* &H1A = Fixed SLC500
            '* &H18 = SLC 5/01
            '* &H25 = SLC 5/02
            '* &H49 = SLC 5/03
            '* &H5B = SLC 5/04
            '* &H6F = SLC 5/04 (L541)
            '* &H78 = SLC 5/05
            '* &H95 = CompactLogix L35E
            '* &H9C = ML1100
            '* &H88 = ML1200
            '* &H89 = ML1500 LSP
            '* &H8C = ML1500 LRP
            ProcessorType = DataPackets(rTNS)(9)
            Return DataPackets(rTNS)(9)
        End If
    End Function


    Public Structure DataFileDetails
        Dim FileType As String
        Dim FileNumber As Integer
        Dim NumberOfElements As Integer
    End Structure


    '*******************************************************************
    '* This is the start of reverse engineering to retreive data tables
    '*   Read 12 bytes File #0, Type 1, start at Element 21
    '*    Then extract the number of data and program files
    '*******************************************************************
    ''' <summary>
    ''' Retreives the list of data tables and number of elements in each
    ''' </summary>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Function GetDataMemory() As DataFileDetails()
        '**************************************************
        '* Read the File 0 (Program & data file directory
        '**************************************************
        Dim FileZeroData() As Byte = ReadFileDirectory()


        Dim NumberOfDataTables As Integer = FileZeroData(52) + FileZeroData(53) * 256
        Dim NumberOfProgramFiles As Integer = FileZeroData(46) + FileZeroData(47) * 256
        'Dim DataFiles(NumberOfDataTables - 1) As DataFileDetails
        Dim DataFiles As New System.Collections.ObjectModel.Collection(Of DataFileDetails)
        Dim FilePosition As Integer
        Dim BytesPerRow As Integer
        '*****************************************
        '* Process the data from the data table
        '*****************************************
        Select Case ProcessorType
            Case &H25, &H58 '*ML1000, SLC 5/02
                FilePosition = 93
                BytesPerRow = 8
            Case &H88 To &H9C   '* ML1100, ML1200, ML1500
                FilePosition = 103
                BytesPerRow = 10
            Case &H9F
                FilePosition = &H71
                BytesPerRow = 10
            Case Else               '* SLC 5/04, 5/05
                FilePosition = 79
                BytesPerRow = 10
        End Select


        '* Comb through data file 0 looking for data table definitions
        Dim i, k, BytesPerElement As Integer
        i = 0

        Dim DataFile As New DataFileDetails
        While k < NumberOfDataTables And FilePosition < FileZeroData.Length
            Select Case FileZeroData(FilePosition)
                Case &H82, &H8B : DataFile.FileType = "O"
                    BytesPerElement = 2
                Case &H83, &H8C : DataFile.FileType = "I"
                    BytesPerElement = 2
                Case &H84 : DataFile.FileType = "S"
                    BytesPerElement = 2
                Case &H85 : DataFile.FileType = "B"
                    BytesPerElement = 2
                Case &H86 : DataFile.FileType = "T"
                    BytesPerElement = 6
                Case &H87 : DataFile.FileType = "C"
                    BytesPerElement = 6
                Case &H88 : DataFile.FileType = "R"
                    BytesPerElement = 6
                Case &H89 : DataFile.FileType = "N"
                    BytesPerElement = 2
                Case &H8A : DataFile.FileType = "F"
                    BytesPerElement = 4
                Case &H8D : DataFile.FileType = "ST"
                    BytesPerElement = 84
                Case &H8E : DataFile.FileType = "A"
                    BytesPerElement = 2
                Case &H91 : DataFile.FileType = "L"   'Long Integer
                    BytesPerElement = 4
                Case &H92 : DataFile.FileType = "MG"   'Message Command 146
                    BytesPerElement = 50
                Case &H93 : DataFile.FileType = "PD"   'PID
                    BytesPerElement = 46
                Case &H94 : DataFile.FileType = "PLS"   'Programmable Limit Swith
                    BytesPerElement = 12

                Case Else : DataFile.FileType = "Undefined" '* 61h=Program File
                    BytesPerElement = 2
            End Select
            DataFile.NumberOfElements = (FileZeroData(FilePosition + 1) + FileZeroData(FilePosition + 2) * 256) / BytesPerElement
            DataFile.FileNumber = i

            '* Only return valid user data files
            If FileZeroData(FilePosition) > &H81 And FileZeroData(FilePosition) < &H9F Then
                DataFiles.Add(DataFile)
                'DataFile = New DataFileDetails
                k += 1
            End If

            '* Index file number once in the region of data files
            If k > 0 Then i += 1
            FilePosition += BytesPerRow
        End While

        '* Move to an array with a length of only good data files
        'Dim GoodDataFiles(k - 1) As DataFileDetails
        Dim GoodDataFiles(DataFiles.Count - 1) As DataFileDetails
        'For l As Integer = 0 To k - 1
        '    GoodDataFiles(l) = DataFiles(l)
        'Next

        DataFiles.CopyTo(GoodDataFiles, 0)

        Return GoodDataFiles
    End Function



    '*******************************************************************
    '*   Read the data file directory, File 0, Type 2
    '*    Then extract the number of data and program files
    '*******************************************************************
    Private Function GetML1500DataMemory() As DataFileDetails()
        Dim reply As Integer
        Dim PAddress As New ParsedDataAddress

        '* Get the length of File 0, Type 2. This is the program/data file directory
        PAddress.FileNumber = 0
        PAddress.FileType = 2
        PAddress.Element = &H2F
        Dim data() As Byte = ReadRawData(PAddress, 2, reply)


        If reply = 0 Then
            Dim FileZeroSize As Integer = data(0) + (data(1)) * 256

            PAddress.Element = 0
            PAddress.SubElement = 0
            '* Read all of File 0, Type 2
            Dim FileZeroData() As Byte = ReadRawData(PAddress, FileZeroSize, reply)

            '* Start Reading the data table configuration
            Dim DataFiles(256) As DataFileDetails

            Dim FilePosition As Integer
            Dim i As Integer


            '* Process the data from the data table
            If reply = 0 Then
                '* Comb through data file 0 looking for data table definitions
                Dim k, BytesPerElement As Integer
                i = 0
                FilePosition = 143
                While FilePosition < FileZeroData.Length
                    Select Case FileZeroData(FilePosition)
                        Case &H89 : DataFiles(k).FileType = "N"
                            BytesPerElement = 2
                        Case &H85 : DataFiles(k).FileType = "B"
                            BytesPerElement = 2
                        Case &H86 : DataFiles(k).FileType = "T"
                            BytesPerElement = 6
                        Case &H87 : DataFiles(k).FileType = "C"
                            BytesPerElement = 6
                        Case &H84 : DataFiles(k).FileType = "S"
                            BytesPerElement = 2
                        Case &H8A : DataFiles(k).FileType = "F"
                            BytesPerElement = 4
                        Case &H8D : DataFiles(k).FileType = "ST"
                            BytesPerElement = 84
                        Case &H8E : DataFiles(k).FileType = "A"
                            BytesPerElement = 2
                        Case &H88 : DataFiles(k).FileType = "R"
                            BytesPerElement = 6
                        Case &H82, &H8B : DataFiles(k).FileType = "O"
                            BytesPerElement = 2
                        Case &H83, &H8C : DataFiles(k).FileType = "I"
                            BytesPerElement = 2
                        Case &H91 : DataFiles(k).FileType = "L"   'Long Integer
                            BytesPerElement = 4
                        Case &H92 : DataFiles(k).FileType = "MG"   'Message Command 146
                            BytesPerElement = 50
                        Case &H93 : DataFiles(k).FileType = "PD"   'PID
                            BytesPerElement = 46
                        Case &H94 : DataFiles(k).FileType = "PLS"   'Programmable Limit Swith
                            BytesPerElement = 12

                        Case Else : DataFiles(k).FileType = "Undefined"  '* 61h=Program File
                            BytesPerElement = 2
                    End Select
                    DataFiles(k).NumberOfElements = (FileZeroData(FilePosition + 1) + FileZeroData(FilePosition + 2) * 256) / BytesPerElement
                    DataFiles(k).FileNumber = i

                    '* Only return valid user data files
                    If FileZeroData(FilePosition) > &H81 And FileZeroData(FilePosition) < &H95 Then k += 1

                    '* Index file number once in the region of data files
                    If k > 0 Then i += 1
                    FilePosition += 10
                End While

                '* Move to an array with a length of only good data files
                Dim GoodDataFiles(k - 1) As DataFileDetails
                For l As Integer = 0 To k - 1
                    GoodDataFiles(l) = DataFiles(l)
                Next

                Return GoodDataFiles
            Else
                Throw New MfgControl.AdvancedHMI.Drivers.PLCDriverException(DecodeMessage(reply) & " - Failed to get data table list")
            End If
        Else
            Throw New MfgControl.AdvancedHMI.Drivers.PLCDriverException(DecodeMessage(reply) & " - Failed to get data table list")
        End If
    End Function

    Private Function ReadFileDirectory() As Byte()
        GetProcessorType()

        '*****************************************************
        '* 1 & 2) Get the size of the File Directory
        '*****************************************************
        Dim PAddress As New ParsedDataAddress
        Select Case ProcessorType
            Case &H25, &H58  '* SLC 5/02 or ML1000
                PAddress.FileType = 0
                PAddress.Element = &H23
            Case &H88 To &H9C  '* ML1100, ML1200, ML1500
                PAddress.FileType = 2
                PAddress.Element = &H2F
            Case &H9F           '*ML1400
                PAddress.FileType = 3
                PAddress.Element = &H34
            Case Else           '* SLC 5/04, SLC 5/05
                PAddress.FileType = 1
                PAddress.Element = &H23
        End Select

        Dim reply As Integer

        Dim data() As Byte = ReadRawData(PAddress, 2, reply)
        If reply <> 0 Then Throw New MfgControl.AdvancedHMI.Drivers.PLCDriverException("Failed to Get Program Directory Size- " & DecodeMessage(reply))


        '*****************************************************
        '* 3) Read All of File 0 (File Directory)
        '*****************************************************
        PAddress.Element = 0
        Dim FileZeroSize As Integer = data(0) + data(1) * 256
        Dim FileZeroData() As Byte = ReadRawData(PAddress, FileZeroSize, reply)
        If reply <> 0 Then Throw New MfgControl.AdvancedHMI.Drivers.PLCDriverException("Failed to Get Program Directory - " & DecodeMessage(reply))

        Return FileZeroData
    End Function
    '********************************************************************
    '* Retreive the ladder files
    '* This was developed from a combination of Chapter 12
    '*  and reverse engineering
    '********************************************************************
    Public Structure PLCFileDetails
        Dim FileType As Integer
        Dim FileNumber As Integer
        Dim NumberOfBytes As Integer
        Dim data() As Byte
    End Structure
    Public Function UploadProgramData() As System.Collections.ObjectModel.Collection(Of PLCFileDetails)
        ''*****************************************************
        ''* 1,2 & 3) Read all of the directory File
        ''*****************************************************
        Dim FileZeroData() As Byte = ReadFileDirectory()

        Dim PAddress As New ParsedDataAddress
        Dim reply As Integer

        RaiseEvent UploadProgress(Me, System.EventArgs.Empty)

        '**************************************************
        '* 4) Parse the data from the File Directory data
        '**************************************************
        '*********************************************************************************
        '* Starting at corresponding File Position, each program is defined with 10 bytes
        '* 1st byte=File Type
        '* 2nd & 3rd bytes are program size
        '* 4th & 5th bytes are location with memory
        '*********************************************************************************
        Dim FilePosition As Integer
        Dim ProgramFile As New PLCFileDetails
        Dim ProgramFiles As New System.Collections.ObjectModel.Collection(Of PLCFileDetails)

        '*********************************************
        '* 4a) Add the directory information
        '*********************************************
        ProgramFile.FileNumber = 0
        ProgramFile.data = FileZeroData
        ProgramFile.FileType = PAddress.FileType
        ProgramFile.NumberOfBytes = FileZeroData.Length
        ProgramFiles.Add(ProgramFile)

        '**********************************************
        '* 5) Read the rest of the data tables
        '**********************************************
        Dim DataFileGroup, ForceFileGroup, SystemFileGroup, SystemLadderFileGroup As Integer
        Dim LadderFileGroup, Unknown1FileGroup, Unknown2FileGroup As Integer
        If reply = 0 Then
            Dim NumberOfProgramFiles As Integer = FileZeroData(46) + FileZeroData(47) * 256

            '* Comb through data file 0 and get the program file details
            Dim i As Integer
            '* The start of program file definitions
            Select Case ProcessorType
                Case &H25, &H58
                    FilePosition = 93
                Case &H88 To &H9C
                    FilePosition = 103
                Case &H9F   '* ML1400
                    FilePosition = &H71
                Case Else
                    FilePosition = 79
            End Select

            Do While FilePosition < FileZeroData.Length And reply = 0
                ProgramFile.FileType = FileZeroData(FilePosition)
                ProgramFile.NumberOfBytes = (FileZeroData(FilePosition + 1) + FileZeroData(FilePosition + 2) * 256)

                If ProgramFile.FileType >= &H40 AndAlso ProgramFile.FileType <= &H5F Then
                    ProgramFile.FileNumber = SystemFileGroup
                    SystemFileGroup += 1
                End If
                If (ProgramFile.FileType >= &H20 AndAlso ProgramFile.FileType <= &H3F) Then
                    ProgramFile.FileNumber = LadderFileGroup
                    LadderFileGroup += 1
                End If
                If (ProgramFile.FileType >= &H60 AndAlso ProgramFile.FileType <= &H7F) Then
                    ProgramFile.FileNumber = SystemLadderFileGroup
                    SystemLadderFileGroup += 1
                End If
                If ProgramFile.FileType >= &H80 AndAlso ProgramFile.FileType <= &H9F Then
                    ProgramFile.FileNumber = DataFileGroup
                    DataFileGroup += 1
                End If
                If ProgramFile.FileType >= &HA0 AndAlso ProgramFile.FileType <= &HBF Then
                    ProgramFile.FileNumber = ForceFileGroup
                    ForceFileGroup += 1
                End If
                If ProgramFile.FileType >= &HC0 AndAlso ProgramFile.FileType <= &HDF Then
                    ProgramFile.FileNumber = Unknown1FileGroup
                    Unknown1FileGroup += 1
                End If
                If ProgramFile.FileType >= &HE0 AndAlso ProgramFile.FileType <= &HFF Then
                    ProgramFile.FileNumber = Unknown2FileGroup
                    Unknown2FileGroup += 1
                End If

                PAddress.FileType = ProgramFile.FileType
                PAddress.FileNumber = ProgramFile.FileNumber
                PAddress.BitNumber = 99   '* Do not let the extract data try to interpret bit level

                If ProgramFile.NumberOfBytes > 0 Then
                    ProgramFile.data = ReadRawData(PAddress, ProgramFile.NumberOfBytes, reply)
                    If reply <> 0 Then Throw New MfgControl.AdvancedHMI.Drivers.PLCDriverException("Failed to Read Program File " & PAddress.FileNumber & ", Type " & PAddress.FileType & " - " & DecodeMessage(reply))
                Else
                    Dim ZeroLengthData(-1) As Byte
                    ProgramFile.data = ZeroLengthData
                End If


                ProgramFiles.Add(ProgramFile)
                RaiseEvent UploadProgress(Me, System.EventArgs.Empty)

                i += 1
                '* 10 elements are used to define each program file
                '* SLC 5/02 or ML1000
                If ProcessorType = &H25 OrElse ProcessorType = &H58 Then
                    FilePosition += 8
                Else
                    FilePosition += 10
                End If
            Loop

        End If

        Return ProgramFiles
    End Function

    '****************************************************************
    '* Download a group of files defined in the PLCFiles Collection
    '****************************************************************
    Public Sub DownloadProgramData(ByVal PLCFiles As System.Collections.ObjectModel.Collection(Of PLCFileDetails))
        '******************************
        '* 1 & 2) Change to program Mode
        '******************************
        SetProgramMode()
        RaiseEvent DownloadProgress(Me, System.EventArgs.Empty)

        '*************************************************************************
        '* 2) Initialize Memory & Put in Download mode using Execute Command List
        '*************************************************************************
        Dim DataLength As Integer
        Select Case ProcessorType
            Case &H5B, &H78, &H6F
                DataLength = 13
            Case &H88 To &H9C
                DataLength = 15
            Case &H9F
                DataLength = 15  '*** TODO
            Case Else
                DataLength = 15
        End Select

        Dim data(DataLength) As Byte
        '* 2 commands
        data(0) = &H2
        '* Number of bytes in 1st command
        data(1) = &HA
        '* Function &HAA
        data(2) = &HAA
        '* Write 4 bytes
        data(3) = 4
        data(4) = 0
        '* File type 63
        data(5) = &H63

        '* Lets go ahead and setup the file type for later use
        Dim PAddress As New ParsedDataAddress
        Dim reply As Integer

        '**********************************
        '* 2a) Search for File 0, Type 24
        '**********************************
        Dim i As Integer
        While i < PLCFiles.Count AndAlso (PLCFiles(i).FileNumber <> 0 OrElse PLCFiles(i).FileType <> &H24)
            i += 1
        End While

        '* Write bytes 02-07 from File 0, Type 24 to File 0, Type 63
        If i < PLCFiles.Count Then
            data(8) = PLCFiles(i).data(2)
            data(9) = PLCFiles(i).data(3)
            data(10) = PLCFiles(i).data(4)
            data(11) = PLCFiles(i).data(5)
            If DataLength > 14 Then
                data(12) = PLCFiles(i).data(6)
                data(13) = PLCFiles(i).data(7)
            End If
        End If


        Select Case ProcessorType
            Case &H78, &H5B, &H49, &H6F  '* SLC 5/05, 5/04, 5/03
                '* Read these 4 bytes to write back, File 0, Type 63
                PAddress.FileType = &H63
                PAddress.Element = 0
                PAddress.SubElement = 0
                Dim FourBytes() As Byte = ReadRawData(PAddress, 4, reply)
                If reply = 0 Then
                    Array.Copy(FourBytes, 0, data, 8, 4)
                    PAddress.FileType = 1
                    PAddress.Element = &H23
                Else
                    Throw New MfgControl.AdvancedHMI.Drivers.PLCDriverException("Failed to Read File 0, Type 63h - " & DecodeMessage(reply))
                End If

                '* Number of bytes in 1st command
                data(1) = &HA
                '* Number of bytes to write
                data(3) = 4
            Case &H88 To &H9C   '* ML1200, ML1500, ML1100
                '* Number of bytes in 1st command
                data(1) = &HC
                '* Number of bytes to write
                data(3) = 6
                PAddress.FileType = 2
                PAddress.Element = &H23
            Case &H9F   '* ML1400
                '* Number of bytes in 1st command
                data(1) = &HC       '* TODO
                '* Number of bytes to write
                data(3) = 6
                PAddress.FileType = 3
                PAddress.Element = &H28
            Case Else '* Fill in the gap for an unknown processor
                data(1) = &HA
                data(3) = 4
                PAddress.FileType = 1
                PAddress.Element = &H23
        End Select


        '* 1 byte in 2nd command - Start Download
        data(data.Length - 2) = 1
        data(data.Length - 1) = &H56

        Dim rTNS As Integer
        reply = PrefixAndSend(&HF, &H88, data, True, rTNS)
        If reply <> 0 Then Throw New MfgControl.AdvancedHMI.Drivers.PLCDriverException("Failed to Initialize for Download - " & DecodeMessage(reply))
        RaiseEvent DownloadProgress(Me, System.EventArgs.Empty)


        '*********************************
        '* 4) Secure Sole Access
        '*********************************
        Dim data2(-1) As Byte
        reply = PrefixAndSend(&HF, &H11, data2, True, rTNS)
        If reply <> 0 Then Throw New MfgControl.AdvancedHMI.Drivers.PLCDriverException("Failed to Secure Sole Access - " & DecodeMessage(reply))
        RaiseEvent DownloadProgress(Me, System.EventArgs.Empty)

        '*********************************
        '* 5) Write the directory length
        '*********************************
        PAddress.BitNumber = 16
        Dim data3(1) As Byte
        data3(0) = PLCFiles(0).data.Length And &HFF
        data3(1) = (PLCFiles(0).data.Length - data3(0)) / 256
        reply = WriteRawData(PAddress, 2, data3)
        If reply <> 0 Then Throw New MfgControl.AdvancedHMI.Drivers.PLCDriverException("Failed to Write Directory Length - " & DecodeMessage(reply))
        RaiseEvent DownloadProgress(Me, System.EventArgs.Empty)

        '*********************************
        '* 6) Write program directory
        '*********************************
        PAddress.Element = 0
        reply = WriteRawData(PAddress, PLCFiles(0).data.Length, PLCFiles(0).data)
        If reply <> 0 Then Throw New MfgControl.AdvancedHMI.Drivers.PLCDriverException("Failed to Write New Program Directory - " & DecodeMessage(reply))
        RaiseEvent DownloadProgress(Me, System.EventArgs.Empty)

        '*********************************
        '* 7) Write Program & Data Files
        '*********************************
        For i = 1 To PLCFiles.Count - 1
            PAddress.FileNumber = PLCFiles(i).FileNumber
            PAddress.FileType = PLCFiles(i).FileType
            PAddress.Element = 0
            PAddress.SubElement = 0
            PAddress.BitNumber = 16
            reply = WriteRawData(PAddress, PLCFiles(i).data.Length, PLCFiles(i).data)
            If reply <> 0 Then Throw New MfgControl.AdvancedHMI.Drivers.PLCDriverException("Failed when writing files to PLC - " & DecodeMessage(reply))
            RaiseEvent DownloadProgress(Me, System.EventArgs.Empty)
        Next

        '*********************************
        '* 8) Complete the Download
        '*********************************
        reply = PrefixAndSend(&HF, &H52, data2, True, rTNS)
        If reply <> 0 Then Throw New MfgControl.AdvancedHMI.Drivers.PLCDriverException("Failed to Indicate to PLC that Download is complete - " & DecodeMessage(reply))
        RaiseEvent DownloadProgress(Me, System.EventArgs.Empty)

        '*********************************
        '* 9) Release Sole Access
        '*********************************
        reply = PrefixAndSend(&HF, &H12, data2, True, rTNS)
        If reply <> 0 Then Throw New MfgControl.AdvancedHMI.Drivers.PLCDriverException("Failed to Release Sole Access - " & DecodeMessage(reply))
        RaiseEvent DownloadProgress(Me, System.EventArgs.Empty)
    End Sub


    ''' <summary>
    ''' Get the number of slots in the rack
    ''' </summary>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Function GetSlotCount() As Integer
        '* Get the header of the data table definition file
        Dim data(4) As Byte

        '* Number of bytes to read
        data(0) = 4
        '* Data File Number (0 is the system file)
        data(1) = 0
        '* File Type (&H60 must be a system type), this was pulled from reverse engineering
        data(2) = &H60
        '* Element Number
        data(3) = 0
        '* Sub Element Offset in words
        data(4) = 0


        Dim rTNS As Integer
        Dim reply As Integer = PrefixAndSend(&HF, &HA2, data, True, rTNS)

        If reply = 0 Then
            If DataPackets(rTNS)(6) > 0 Then
                Return DataPackets(rTNS)(6) - 1  '* if a rack based system, then subtract processor slot
            Else
                Return 0  '* micrologix reports 0 slots
            End If
        Else
            Throw New MfgControl.AdvancedHMI.Drivers.PLCDriverException("Failed to Release Sole Access - " & DecodeMessage(reply))
        End If
    End Function

    Public Structure IOConfig
        Dim InputBytes As Integer
        Dim OutputBytes As Integer
        Dim CardCode As Integer
    End Structure
    ''' <summary>
    ''' Get IO card list currently in rack
    ''' </summary>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Function GetIOConfig() As IOConfig()
        Dim ProcessorType As Integer = GetProcessorType()


        If ProcessorType = &H89 Or ProcessorType = &H8C Then  '* Is it a Micrologix 1500?
            Return GetML1500IOConfig()
        Else
            Return GetSLCIOConfig()
        End If
    End Function

    ''' <summary>
    ''' Get IO card list currently in rack of a SLC
    ''' </summary>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Function GetSLCIOConfig() As IOConfig()
        Dim slots As Integer = GetSlotCount()

        If slots > 0 Then
            '* Get the header of the data table definition file
            Dim data(4) As Byte

            '* Number of bytes to read
            data(0) = 4 + (slots + 1) * 6 + 2
            '* Data File Number (0 is the system file)
            data(1) = 0
            '* File Type (&H60 must be a system type), this was pulled from reverse engineering
            data(2) = &H60
            '* Element Number
            data(3) = 0
            '* Sub Element Offset in words
            data(4) = 0


            Dim rTNS As Integer
            Dim reply As Integer = PrefixAndSend(&HF, &HA2, data, True, rTNS)

            Dim BytesForConverting(1) As Byte
            Dim IOResult(slots) As IOConfig
            If reply = 0 Then
                '* Extract IO information
                For i As Integer = 0 To slots
                    IOResult(i).InputBytes = DataPackets(rTNS)(i * 6 + 10)
                    IOResult(i).OutputBytes = DataPackets(rTNS)(i * 6 + 12)
                    BytesForConverting(0) = DataPackets(rTNS)(i * 6 + 14)
                    BytesForConverting(1) = DataPackets(rTNS)(i * 6 + 15)
                    IOResult(i).CardCode = BitConverter.ToInt16(BytesForConverting, 0)
                Next
                Return IOResult
            Else
                Throw New MfgControl.AdvancedHMI.Drivers.PLCDriverException("Failed to get IO Config - " & DecodeMessage(reply))
            End If
        Else
            Throw New MfgControl.AdvancedHMI.Drivers.PLCDriverException("Failed to get Slot Count")
        End If
    End Function


    ''' <summary>
    ''' Get IO card list currently in rack of a ML1500
    ''' </summary>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Function GetML1500IOConfig() As IOConfig()
        '*************************************************************************
        '* Read the first 4 bytes of File 0, type 62 to get the total file length
        '**************************************************************************
        Dim data(4) As Byte
        Dim rTNS As Integer

        '* Number of bytes to read
        data(0) = 4
        '* Data File Number (0 is the system file)
        data(1) = 0
        '* File Type (&H62 must be a system type), this was pulled from reverse engineering
        data(2) = &H62
        '* Element Number
        data(3) = 0
        '* Sub Element Offset in words
        data(4) = 0

        Dim reply As Integer = PrefixAndSend(&HF, &HA2, data, True, rTNS)

        '******************************************
        '* Read all of File Zero, Type 62
        '******************************************
        If reply = 0 Then
            'TODO: Get this corrected
            Dim FileZeroSize As Integer = DataPackets(rTNS)(6) * 2
            Dim FileZeroData(FileZeroSize) As Byte
            Dim FilePosition As Integer
            Dim Subelement As Integer
            Dim i As Integer

            '* Number of bytes to read
            If FileZeroSize > &H50 Then
                data(0) = &H50
            Else
                data(0) = FileZeroSize
            End If

            '* Loop through reading all of file 0 in chunks of 80 bytes
            Do While FilePosition < FileZeroSize And reply = 0

                '* Read the file
                reply = PrefixAndSend(&HF, &HA2, data, True, rTNS)

                '* Transfer block of data read to the data table array
                i = 0
                Do While i < data(0)
                    FileZeroData(FilePosition) = DataPackets(rTNS)(i + 6)
                    i += 1
                    FilePosition += 1
                Loop


                '* point to the next element, by taking the last Start Element(in words) and adding it to the number of bytes read
                Subelement += data(0) / 2
                If Subelement < 255 Then
                    data(3) = Subelement
                Else
                    '* Use extended addressing
                    If data.Length < 6 Then ReDim Preserve data(5)
                    data(5) = Math.Floor(Subelement / 256)  '* 256+data(5)
                    data(4) = Subelement - (data(5) * 256) '*  calculate offset
                    data(3) = 255
                End If

                '* Set next length of data to read. Max of 80
                If FileZeroSize - FilePosition < 80 Then
                    data(0) = FileZeroSize - FilePosition
                Else
                    data(0) = 80
                End If
            Loop


            '**********************************
            '* Extract the data from the file
            '**********************************
            If reply = 0 Then
                Dim SlotCount As Integer = FileZeroData(2) - 2
                If SlotCount < 0 Then SlotCount = 0
                Dim SlotIndex As Integer = 1
                Dim IOResult(SlotCount) As IOConfig

                '*Start getting slot data
                i = 32 + SlotCount * 4
                Dim BytesForConverting(1) As Byte

                Do While SlotIndex <= SlotCount
                    IOResult(SlotIndex).InputBytes = FileZeroData(i + 2) * 2
                    IOResult(SlotIndex).OutputBytes = FileZeroData(i + 8) * 2
                    BytesForConverting(0) = FileZeroData(i + 18)
                    BytesForConverting(1) = FileZeroData(i + 19)
                    IOResult(SlotIndex).CardCode = BitConverter.ToInt16(BytesForConverting, 0)

                    i += 26
                    SlotIndex += 1
                Loop


                '****************************************
                '* Get the Slot 0(base unit) information
                '****************************************
                data(0) = 8
                '* Data File Number (0 is the system file)
                data(1) = 0
                '* File Type (&H60 must be a system type), this was pulled from reverse engineering
                data(2) = &H60
                '* Element Number
                data(3) = 0
                '* Sub Element Offset in words
                data(4) = 0


                '* Read File 0 to get the IO count on the base unit
                reply = PrefixAndSend(&HF, &HA2, data, True, rTNS)

                If reply = 0 Then
                    IOResult(0).InputBytes = DataPackets(rTNS)(10)
                    IOResult(0).OutputBytes = DataPackets(rTNS)(12)
                Else
                    Throw New MfgControl.AdvancedHMI.Drivers.PLCDriverException("Failed to get Base IO Config for Micrologix 1500- " & DecodeMessage(reply))
                End If


                Return IOResult
            End If
        End If

        Throw New MfgControl.AdvancedHMI.Drivers.PLCDriverException("Failed to get IO Config for Micrologix 1500- " & DecodeMessage(reply))
    End Function



    '******************************************
    '* Synchronous read of any data type
    '*  this function does not declare its return type because it dependent on the data type read
    '******************************************
    ''' <summary>
    ''' Synchronous read of any data type
    ''' this function returns results as an array of strings
    ''' </summary>
    ''' <param name="startAddress"></param>
    ''' <param name="numberOfElements"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Function ReadAny(ByVal startAddress As String, ByVal numberOfElements As Integer) As String() Implements ICommComponent.ReadAny
        '* Limit number of elements to one complete file or 256 elements
        '* NOT TRUE
        'If numberOfElements > 256 Then
        'Throw New ApplicationException("Can not read more than 256 elements")
        'numberOfElements = 256
        'End If

        Dim data(4) As Byte
        Dim ParsedResult As ParsedDataAddress = ParseAddress(startAddress)

        '* Store this here for use by extract data
        ParsedResult.NumberOfElements = numberOfElements

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

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

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

        '* If reading at bit level ,then convert number bits to read to number of words
        '* Fixed a problem when reading multiple bits that span over more than 1 word
        If ParsedResult.BitNumber < 99 Then
            'ArrayElements = Math.Floor(numberOfElements / 16)
            ArrayElements = Math.Floor(((numberOfElements + ParsedResult.BitNumber) - 1) / 16)

            'If ArrayElements Mod 16 > 0 Then data(0) += 1

            'ArrayElements += Math.Floor(ParsedResult.BitNumber / 16)
        End If


        '* Number of bytes to read
        Dim NumberOfBytes As Integer

        Select Case ParsedResult.FileType
            Case &H8D : NumberOfBytes = ((ArrayElements + 1)) * 84  '* String
            Case &H8A : NumberOfBytes = ((ArrayElements + 1)) * 4   '* Float
            Case &H91 : NumberOfBytes = ((ArrayElements + 1)) * 4   '* Long
            Case &H92 : NumberOfBytes = ((ArrayElements + 1)) * 50  '* Message
            Case &H86, &H87 : NumberOfBytes = ((ArrayElements + 1)) * 2   '* Timer
            Case Else : NumberOfBytes = ((ArrayElements + 1)) * 2
        End Select


        '* If it is a multiple read of sub-elements of timers and counter, then read an array of the same consectutive sub element
        '* FIX
        If ParsedResult.SubElement > 0 AndAlso ArrayElements > 0 AndAlso (ParsedResult.FileType = &H86 Or ParsedResult.FileType = &H87) Then
            'NumberOfBytes = (NumberOfBytes * 3) - 4  '* There are 3 words per sub element (6 bytes)
            NumberOfBytes = (NumberOfBytes * 3)   '* There are 3 words per sub element (6 bytes)
        End If


        Dim reply As Integer
        Dim ReturnedData(NumberOfBytes - 1) As Byte
        Dim ReturnedDataIndex As Integer

        Dim BytesToRead As Integer

        Dim Retries As Integer
        While reply = 0 AndAlso ReturnedDataIndex < NumberOfBytes
            BytesToRead = NumberOfBytes

            Dim ReturnedData2(BytesToRead) As Byte
            ReturnedData2 = ReadRawData(ParsedResult, BytesToRead, reply)

            '* Point to next set of bytes to read in block
            If reply = 0 Then
                '* Point to next element to begin reading
                ReturnedData2.CopyTo(ReturnedData, ReturnedDataIndex)
                ReturnedDataIndex += BytesToRead
            ElseIf Retries < 2 Then
                Retries += 1
                reply = 0
            Else
                '* An error was returned from the read operation
                Throw New MfgControl.AdvancedHMI.Drivers.PLCDriverException(DecodeMessage(reply))
            End If
        End While


        'If reply = 0 Then

        '* An error must have occurred if it made it this far, so throw exception
        'Throw New PLCDriverException(DecodeMessage(reply))

        If m_AsyncMode Then
            Dim x() As String = {reply}
            Return x
        Else
            Return ExtractData(ParsedResult, ReturnedData)
        End If
    End Function

    Private Shared Function ExtractData(ByVal ParsedResult As ParsedDataAddress, ByVal ReturnedData() As Byte) As String()
        '* Get the element size in bytes
        Dim ElementSize As Integer
        Select Case ParsedResult.FileType
            Case &H8A '* Floating point read (&H8A)
                ElementSize = 4  '* Was 5?
            Case &H8D ' * String
                ElementSize = 84
            Case &H86, &H87  '* Timer, counter
                ElementSize = 2
                '* FIX
                If ParsedResult.SubElement > 0 AndAlso ReturnedData.Length > 2 Then ElementSize *= 3
            Case &H91 '* Long Value read (&H91)
                ElementSize = 4
            Case &H92 '* MSG Value read (&H92)
                ElementSize = 50
            Case Else
                ElementSize = 2
        End Select

        '***************************************************
        '* Extract returned data into appropriate data type
        '***************************************************
        Dim result(Math.Floor(ReturnedData.Length / ElementSize) - 1) As String

        '* If requesting 0 elements, then default to 1
        Dim ArrayElements As Int16 = Math.Max(result.Length - 1 - 1, 0)

        '* If reading at bit level ,then convert number bits to read to number of words
        'If ParsedResult.BitNumber < 99 Then
        '    'ArrayElements = Math.Floor(result.Length - 1 / 16)
        '    Dim x As Integer = Math.Floor(result.Length - 1 / 16)
        'End If

        Dim StringLength As Integer
        Select Case ParsedResult.FileType
            Case &H8A '* Floating point read (&H8A)
                For i As Integer = 0 To result.Length - 1
                    result(i) = BitConverter.ToSingle(ReturnedData, (i * 4))
                Next
            Case &H8D ' * String
                For i As Integer = 0 To result.Length - 1
                    StringLength = BitConverter.ToInt16(ReturnedData, (i * 84))
                    '* The controller may falsely report the string length, so set to max allowed
                    If StringLength > 82 Then StringLength = 82

                    '* use a string builder for increased performance
                    Dim result2 As New System.Text.StringBuilder
                    Dim j As Integer = 2
                    '* Stop concatenation if a zero (NULL) is reached
                    While j < StringLength + 2 And ReturnedData((i * 84) + j + 1) > 0
                        result2.Append(Chr(ReturnedData((i * 84) + j + 1)))
                        '* Prevent an odd length string from getting a Null added on
                        If j < StringLength + 1 And (ReturnedData((i * 84) + j)) > 0 Then result2.Append(Chr(ReturnedData((i * 84) + j)))
                        j += 2
                    End While
                    result(i) = result2.ToString
                Next
            Case &H86, &H87  '* Timer, counter
                '* If a sub element is designated then read the same sub element for all timers
                Dim j As Integer
                For i As Integer = 0 To result.Length - 1
                    If ParsedResult.SubElement > 0 Then
                        j = i * 6  '+ ParsedResult.SubElement * 2
                    Else
                        j = i * 2
                    End If
                    result(i) = BitConverter.ToInt16(ReturnedData, (j))
                Next
            Case &H91 '* Long Value read (&H91)
                'Dim result(ArrayElements) As Single
                For i As Integer = 0 To result.Length - 1
                    result(i) = BitConverter.ToInt32(ReturnedData, (i * 4))
                Next
            Case &H92 '* MSG Value read (&H92)
                'Dim result(ArrayElements) As Single
                For i As Integer = 0 To result.Length - 1
                    result(i) = BitConverter.ToString(ReturnedData, (i * 50), 50)
                Next
            Case Else
                'Dim result(ArrayElements) As Int16
                For i As Integer = 0 To result.Length - 1
                    result(i) = BitConverter.ToInt16(ReturnedData, (i * 2))
                Next
        End Select
        'End If


        '******************************************************************************
        '* If the number of words to read is not specified, then return a single value
        '******************************************************************************
        '* Is it a bit level and N or B file?
        If ParsedResult.BitNumber >= 0 And ParsedResult.BitNumber < 99 Then
            Dim BitResult(ParsedResult.NumberOfElements - 1) As String
            Dim BitPos As Integer = ParsedResult.BitNumber
            Dim WordPos As Integer = 0
            'Dim Result(ArrayElements) As Boolean

            '* If a bit number is greater than 16, point to correct word
            '* This can happen on IO addresses (e.g. I:0/16)
            WordPos += BitPos >> 4
            BitPos = BitPos Mod 16

            '* Set array of consectutive bits
            For i As Integer = 0 To BitResult.Length - 1
                BitResult(i) = CBool(result(WordPos) And 2 ^ BitPos)
                BitPos += 1
                If BitPos > 15 Then
                    BitPos = 0
                    WordPos += 1
                End If
            Next
            Return BitResult
        End If

        Return result

    End Function
    '*************************************************************
    '* Overloaded method of ReadAny - that reads only one element
    '*************************************************************
    ''' <summary>
    ''' Synchronous read of any data type
    ''' this function returns results as a string
    ''' </summary>
    ''' <param name="startAddress"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Function ReadAny(ByVal startAddress As String) As String Implements ICommComponent.ReadAny
        Return ReadAny(startAddress, 1)(0)
    End Function

    ''' <summary>
    ''' Reads values and returns them as integers
    ''' </summary>
    ''' <param name="startAddress"></param>
    ''' <param name="numberOfBytes"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Function ReadInt(ByVal startAddress As String, ByVal numberOfBytes As Integer) As Integer()
        Dim result() As String
        result = ReadAny(startAddress, numberOfBytes)

        Dim Ints(result.Length) As Integer
        For i As Integer = 0 To result.Length - 1
            Ints(i) = CInt(result(i))
        Next

        Return Ints
    End Function

    '*********************************************************************************
    '* Read Raw File data and break up into chunks because of limits of DF1 protocol
    '*********************************************************************************
    Private Function ReadRawData(ByVal PAddress As ParsedDataAddress, ByVal numberOfBytes As Integer, ByRef result As Integer) As Byte()
        Dim NumberOfBytesToRead, FilePosition, rTNS As Integer
        Dim ResultData(numberOfBytes - 1) As Byte

        Do While FilePosition < numberOfBytes AndAlso result = 0
            '* Set next length of data to read. Max of 236 (slc 5/03 and up)
            '* This must limit to 82 for 5/02 and below
            If numberOfBytes - FilePosition < 236 Then
                NumberOfBytesToRead = numberOfBytes - FilePosition
            Else
                NumberOfBytesToRead = 236
            End If

            '* String is an exception
            If NumberOfBytesToRead > 168 AndAlso PAddress.FileType = &H8D Then
                '* Only two string elements can be read on each read (168 bytes)
                NumberOfBytesToRead = 168
            End If

            If NumberOfBytesToRead > 234 AndAlso (PAddress.FileType = &H86 OrElse PAddress.FileType = &H87) Then
                '* Timers & counters read in multiples of 6 bytes
                NumberOfBytesToRead = 234
            End If

            '* Data Monitor File is an exception
            If NumberOfBytesToRead > &H78 AndAlso PAddress.FileType = &HA4 Then
                '* Only two string elements can be read on each read (168 bytes)
                NumberOfBytesToRead = &H78
            End If

            '* The SLC 5/02 can only read &H50 bytes per read, possibly the ML1500
            'If NumberOfBytesToRead > &H50 AndAlso (ProcessorType = &H25 Or ProcessorType = &H89) Then
            If NumberOfBytesToRead > &H50 AndAlso (ProcessorType = &H25) Then
                NumberOfBytesToRead = &H50
            End If

            If NumberOfBytesToRead > 0 Then
                Dim DataSize, Func As Integer

                'If PAddress.SubElement = 0 Then
                '    DataSize = 3
                '    Func = &HA1
                'Else
                DataSize = 4
                Func = &HA2
                'End If

                '* Check if we need extended addressing
                If PAddress.Element >= 255 Then DataSize += 2
                If PAddress.SubElement >= 255 Then DataSize += 2

                Dim data(DataSize) As Byte


                '* Number of bytes to read - 
                data(0) = NumberOfBytesToRead

                '* File Number
                data(1) = PAddress.FileNumber

                '* File Type
                data(2) = PAddress.FileType

                '* Starting Element Number
                '* point to the next element (ref page 7-17)
                If PAddress.Element < 255 Then
                    data(3) = PAddress.Element
                Else
                    '* Use extended addressing
                    data(5) = Math.Floor(PAddress.Element / 256)  '* 256+data(5)
                    data(4) = PAddress.Element - (data(5) * 256) '*  calculate offset
                    data(3) = 255
                End If

                '* Sub Element (Are we using the subelement function of &HA2?)
                If Func = &HA2 Then
                    '* point to the next element (ref page 7-17)
                    If PAddress.SubElement < 255 Then
                        data(data.Length - 1) = PAddress.SubElement
                    Else
                        '* Use extended addressing
                        data(data.Length - 1) = Math.Floor(PAddress.SubElement / 256)
                        data(data.Length - 2) = PAddress.SubElement - (data(data.Length - 1) * 256)
                        data(data.Length - 3) = 255
                    End If
                End If

                '**********************************************************************
                '* Link the TNS to the original address for use by the linked polling
                '**********************************************************************
                PAddress.InternallyRequested = InternalRequest
                PLCAddressByTNS(TNS And 255) = PAddress

                result = PrefixAndSend(&HF, Func, data, False, rTNS)
                InternalRequest = False


                If result = 0 Then
                    If (m_AsyncMode = False Or FilePosition + NumberOfBytesToRead < numberOfBytes) Then
                        result = WaitForResponse(rTNS)

                        '* Return status byte that came from controller
                        If result = 0 Then
                            If DataPackets(rTNS) IsNot Nothing Then
                                If (DataPackets(rTNS).Count > 3) Then
                                    result = DataPackets(rTNS)(3)  '* STS position in DF1 message
                                    '* If its and EXT STS, page 8-4
                                    If result = &HF0 Then
                                        '* The EXT STS is the last byte in the packet
                                        'result = DataPackets(rTNS)(DataPackets(rTNS).Count - 2) + &H100
                                        result = DataPackets(rTNS)(DataPackets(rTNS).Count - 1) + &H100
                                    End If
                                End If
                            Else
                                result = -8 '* no response came back from PLC
                            End If
                        End If

                        '***************************************************
                        '* Extract returned data into appropriate data type
                        '* Transfer block of data read to the data table array
                        '***************************************************
                        '* TODO: Check array bounds
                        If result = 0 Then
                            For i As Integer = 0 To NumberOfBytesToRead - 1
                                ResultData(FilePosition + i) = DataPackets(rTNS)(i + 6)
                            Next
                        End If
                    End If
                Else
                    Throw New MfgControl.AdvancedHMI.Drivers.PLCDriverException(DecodeMessage(result))
                End If

                FilePosition += NumberOfBytesToRead

                '* point to the next element
                If PAddress.FileType = &HA4 Then
                    PAddress.Element += NumberOfBytesToRead / &H28
                Else
                    '* Use subelement because it works with all data file types
                    PAddress.SubElement += NumberOfBytesToRead / 2
                End If
            End If
        Loop

        Return ResultData
    End Function



    '*****************************************************************
    '* Write Section
    '*
    '* Address is in the form of <file type><file Number>:<offset>
    '* examples  N7:0, B3:0,
    '******************************************************************

    '* Handle one value of Integer type
    ''' <summary>
    ''' Write a single integer value to a PLC data table
    ''' The startAddress is in the common form of AB addressing (e.g. N7:0)
    ''' </summary>
    ''' <param name="startAddress"></param>
    ''' <param name="dataToWrite"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Function WriteData(ByVal startAddress As String, ByVal dataToWrite As Integer) As Integer
        Dim temp(1) As Integer
        temp(0) = dataToWrite
        Return WriteData(startAddress, 1, temp)
    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
        Return 0
    End Function


    '* Write an array of integers
    ''' <summary>
    ''' Write multiple consectutive integer values to a PLC data table
    ''' The startAddress is in the common form of AB addressing (e.g. N7:0)
    ''' </summary>
    ''' <param name="startAddress"></param>
    ''' <param name="numberOfElements"></param>
    ''' <param name="dataToWrite"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Function WriteData(ByVal startAddress As String, ByVal numberOfElements As Integer, ByVal dataToWrite() As Integer) As Integer
        Dim ParsedResult As ParsedDataAddress = ParseAddress(startAddress)

        Dim ConvertedData(numberOfElements * ParsedResult.BytesPerElements) As Byte

        Dim i As Integer
        If ParsedResult.FileType = &H91 Then
            '* Write to a Long integer file
            While i < numberOfElements
                '******* NOT Necesary to validate because dataToWrite keeps it in range for a long
                Dim b(3) As Byte
                b = BitConverter.GetBytes(dataToWrite(i))

                ConvertedData(i * 4) = b(0)
                ConvertedData(i * 4 + 1) = b(1)
                ConvertedData(i * 4 + 2) = b(2)
                ConvertedData(i * 4 + 3) = b(3)
                i += 1
            End While
        ElseIf ParsedResult.FileType <> 0 Then
            While i < numberOfElements
                '* Validate range
                If dataToWrite(i) > 32767 Or dataToWrite(i) < -32768 Then
                    Throw New MfgControl.AdvancedHMI.Drivers.PLCDriverException("Integer data out of range, must be between -32768 and 32767")
                End If

                ConvertedData(i * 2) = CByte(dataToWrite(i) And &HFF)
                ConvertedData(i * 2 + 1) = CByte((dataToWrite(i) >> 8) And &HFF)

                i += 1
            End While
        Else
            Throw New MfgControl.AdvancedHMI.Drivers.PLCDriverException("Invalid Address")
        End If

        Return WriteRawData(ParsedResult, numberOfElements * ParsedResult.BytesPerElements, ConvertedData)
    End Function

    '* Handle one value of Single type
    ''' <summary>
    ''' Write a single floating point value to a data table
    ''' The startAddress is in the common form of AB addressing (e.g. F8:0)
    ''' </summary>
    ''' <param name="startAddress"></param>
    ''' <param name="dataToWrite"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Function WriteData(ByVal startAddress As String, ByVal dataToWrite As Single) As Integer
        Dim temp(1) As Single
        temp(0) = dataToWrite
        Return WriteData(startAddress, 1, temp)
    End Function

    '* Write an array of Singles
    ''' <summary>
    ''' Write multiple consectutive floating point values to a PLC data table
    ''' The startAddress is in the common form of AB addressing (e.g. F8:0)
    ''' </summary>
    ''' <param name="startAddress"></param>
    ''' <param name="numberOfElements"></param>
    ''' <param name="dataToWrite"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Function WriteData(ByVal startAddress As String, ByVal numberOfElements As Integer, ByVal dataToWrite() As Single) As Integer
        Dim ParsedResult As ParsedDataAddress = ParseAddress(startAddress)

        Dim ConvertedData(numberOfElements * ParsedResult.BytesPerElements) As Byte

        Dim i As Integer
        If ParsedResult.FileType = &H8A Then
            '*Write to a floating point file
            Dim bytes(4) As Byte
            For i = 0 To numberOfElements - 1
                bytes = BitConverter.GetBytes(CSng(dataToWrite(i)))
                For j As Integer = 0 To 3
                    ConvertedData(i * 4 + j) = CByte(bytes(j))
                Next
            Next
        ElseIf ParsedResult.FileType = &H91 Then
            '* Write to a Long integer file
            While i < numberOfElements
                '* Validate range
                If dataToWrite(i) > 2147483647 Or dataToWrite(i) < -2147483648 Then
                    Throw New MfgControl.AdvancedHMI.Drivers.PLCDriverException("Integer data out of range, must be between -2147483648 and 2147483647")
                End If

                Dim b(3) As Byte
                b = BitConverter.GetBytes(CInt(dataToWrite(i)))

                ConvertedData(i * 4) = b(0)
                ConvertedData(i * 4 + 1) = b(1)
                ConvertedData(i * 4 + 2) = b(2)
                ConvertedData(i * 4 + 3) = b(3)
                i += 1
            End While
        Else
            '* Write to an integer file
            While i < numberOfElements
                '* Validate range
                If dataToWrite(i) > 32767 Or dataToWrite(i) < -32768 Then
                    Throw New MfgControl.AdvancedHMI.Drivers.PLCDriverException("Integer data out of range, must be between -32768 and 32767")
                End If

                ConvertedData(i * 2) = CByte(dataToWrite(i) And &HFF)
                ConvertedData(i * 2 + 1) = CByte((dataToWrite(i) >> 8) And &HFF)
                i += 1
            End While
        End If

        Return WriteRawData(ParsedResult, numberOfElements * ParsedResult.BytesPerElements, ConvertedData)
    End Function

    '* Write a String
    ''' <summary>
    ''' Write a string value to a string data table
    ''' The startAddress is in the common form of AB addressing (e.g. ST9:0)
    ''' </summary>
    ''' <param name="startAddress"></param>
    ''' <param name="dataToWrite"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    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

        Dim ParsedResult As ParsedDataAddress = ParseAddress(startAddress)

        If ParsedResult.FileType = 0 Then Throw New MfgControl.AdvancedHMI.Drivers.PLCDriverException("Invalid Address")

        If startAddress.IndexOf("ST", StringComparison.OrdinalIgnoreCase) >= 0 Then
            '* Add an extra character to compensate for characters written in pairs to integers
            Dim ConvertedData(dataToWrite.Length + 2 + 1) As Byte
            dataToWrite &= Chr(0)

            ConvertedData(0) = dataToWrite.Length - 1
            Dim i As Integer = 2
            While i <= dataToWrite.Length
                ConvertedData(i + 1) = Asc(dataToWrite.Substring(i - 2, 1))
                ConvertedData(i) = Asc(dataToWrite.Substring(i - 1, 1))
                i += 2
            End While
            'Array.Copy(System.Text.Encoding.Default.GetBytes(dataToWrite), 0, ConvertedData, 2, dataToWrite.Length)

            Return WriteRawData(ParsedResult, dataToWrite.Length + 2, ConvertedData)
        ElseIf startAddress.IndexOf("L", StringComparison.OrdinalIgnoreCase) >= 0 Then
            Return WriteData(startAddress, CInt(dataToWrite))
        ElseIf startAddress.IndexOf("F", StringComparison.OrdinalIgnoreCase) >= 0 Then
            Return WriteData(startAddress, CSng(dataToWrite))
        Else
            Return WriteData(startAddress, CInt(dataToWrite))
        End If
    End Function

    '**************************************************************
    '* Write to a PLC data file
    '*
    '**************************************************************
    Private Function WriteRawData(ByVal ParsedResult As ParsedDataAddress, ByVal numberOfBytes As Integer, ByVal dataToWrite() As Byte) As Integer
        'Dim dataC As New System.Collections.ObjectModel.Collection(Of Byte)

        '* Invalid address?
        If ParsedResult.FileType = 0 Then
            Return -5
        End If

        '**********************************************
        '* Use a bit level function if it is bit level
        '**********************************************
        Dim FunctionNumber As Byte

        Dim FilePosition, NumberOfBytesToWrite, DataStartPosition As Integer

        Dim reply As Integer
        Dim rTNS As Integer

        Do While FilePosition < numberOfBytes AndAlso reply = 0
            '* Set next length of data to read. Max of 236 (slc 5/03 and up)
            '* This must limit to 82 for 5/02 and below
            If numberOfBytes - FilePosition < 164 Then
                NumberOfBytesToWrite = numberOfBytes - FilePosition
            Else
                NumberOfBytesToWrite = 164
            End If

            '* These files seem to be a special case
            If ParsedResult.FileType >= &HA1 And NumberOfBytesToWrite > &H78 Then
                NumberOfBytesToWrite = &H78
            End If

            Dim DataSize As Integer = NumberOfBytesToWrite + 4

            '* For now we are only going to allow one bit to be set/reset per call
            If ParsedResult.BitNumber < 16 Then DataSize = 8

            If ParsedResult.Element >= 255 Then DataSize += 2
            If ParsedResult.SubElement >= 255 Then DataSize += 2

            Dim DataW(DataSize) As Byte

            '* Byte Size
            DataW(0) = ((NumberOfBytesToWrite And &HFF))
            '* File Number
            DataW(1) = (ParsedResult.FileNumber)
            '* File Type
            DataW(2) = (ParsedResult.FileType)
            '* Starting Element Number
            If ParsedResult.Element < 255 Then
                DataW(3) = (ParsedResult.Element)
            Else
                DataW(5) = Math.Floor(ParsedResult.Element / 256)
                DataW(4) = ParsedResult.Element - (DataW(5) * 256) '*  calculate offset
                DataW(3) = 255
            End If

            '* Sub Element
            If ParsedResult.SubElement < 255 Then
                DataW(DataW.Length - 1 - NumberOfBytesToWrite) = ParsedResult.SubElement
            Else
                '* Use extended addressing
                DataW(DataW.Length - 1 - NumberOfBytesToWrite) = Math.Floor(ParsedResult.SubElement / 256)  '* 256+data(5)
                DataW(DataW.Length - 2 - NumberOfBytesToWrite) = ParsedResult.SubElement - (DataW(DataW.Length - 1 - NumberOfBytesToWrite) * 256) '*  calculate offset
                DataW(DataW.Length - 3 - NumberOfBytesToWrite) = 255
            End If

            '* Are we changing a single bit?
            If ParsedResult.BitNumber < 16 Then
                FunctionNumber = &HAB  '* Ref http://www.iatips.com/pccc_tips.html#slc5_cmds
                '* Set the mask of which bit to change
                DataW(DataW.Length - 4) = ((2 ^ (ParsedResult.BitNumber)) And &HFF)
                DataW(DataW.Length - 3) = (2 ^ (ParsedResult.BitNumber - 8))

                If dataToWrite(0) <= 0 Then
                    '* Set bits to clear 
                    DataW(DataW.Length - 2) = 0
                    DataW(DataW.Length - 1) = 0
                Else
                    '* Bits to turn on
                    DataW(DataW.Length - 2) = ((2 ^ (ParsedResult.BitNumber)) And &HFF)
                    DataW(DataW.Length - 1) = (2 ^ (ParsedResult.BitNumber - 8))
                End If
            Else
                FunctionNumber = &HAA
                DataStartPosition = DataW.Length - NumberOfBytesToWrite

                '* Prevent index out of range when numberToWrite exceeds dataToWrite.Length
                Dim ValuesToMove As Integer = NumberOfBytesToWrite - 1
                If ValuesToMove + FilePosition > dataToWrite.Length - 1 Then
                    ValuesToMove = dataToWrite.Length - 1 - FilePosition
                End If

                For i As Integer = 0 To ValuesToMove
                    DataW(i + DataStartPosition) = dataToWrite(i + FilePosition)
                Next
            End If

            reply = PrefixAndSend(&HF, FunctionNumber, DataW, Not m_AsyncMode, rTNS)

            FilePosition += NumberOfBytesToWrite

            If ParsedResult.FileType <> &HA4 Then
                '* Use subelement because it works with all data file types
                ParsedResult.SubElement += NumberOfBytesToWrite / 2
            Else
                '* Special case file - 28h bytes per elements
                ParsedResult.Element += NumberOfBytesToWrite / &H28
            End If
        Loop

        If reply = 0 Then
            Return 0
        Else
            Throw New MfgControl.AdvancedHMI.Drivers.PLCDriverException(DecodeMessage(reply))
        End If
    End Function
    'End of Public Methods
#End Region

#Region "Shared Methods"
    '****************************************************************
    '* Convert an array of words into a string as AB PLC's represent
    '* Can be used when reading a string from an Integer file
    '****************************************************************
    ''' <summary>
    ''' Convert an array of integers to a string
    ''' This is used when storing strings in an integer data table
    ''' </summary>
    ''' <param name="words"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Shared Function WordsToString(ByVal words() As Int32) As String
        Dim WordCount As Integer = words.Length
        Return WordsToString(words, 0, WordCount)
    End Function

    ''' <summary>
    ''' Convert an array of integers to a string
    ''' This is used when storing strings in an integer data table
    ''' </summary>
    ''' <param name="words"></param>
    ''' <param name="index"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Shared Function WordsToString(ByVal words() As Int32, ByVal index As Integer) As String
        Dim WordCount As Integer = (words.Length - index)
        Return WordsToString(words, index, WordCount)
    End Function

    ''' <summary>
    ''' Convert an array of integers to a string
    ''' This is used when storing strings in an integer data table
    ''' </summary>
    ''' <param name="words"></param>
    ''' <param name="index"></param>
    ''' <param name="wordCount"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Shared Function WordsToString(ByVal words() As Int32, ByVal index As Integer, ByVal wordCount As Integer) As String
        Dim j As Integer = index
        Dim result2 As New System.Text.StringBuilder
        While j < wordCount
            result2.Append(Chr(words(j) / 256))
            '* Prevent an odd length string from getting a Null added on
            If CInt(words(j) And &HFF) > 0 Then
                result2.Append(Chr(words(j) And &HFF))
            End If
            j += 1
        End While

        Return result2.ToString
    End Function


    '**********************************************************
    '* Convert a string to an array of words
    '*  Can be used when writing a string to an Integer file
    '**********************************************************
    ''' <summary>
    ''' Convert a string to an array of words
    ''' Can be used when writing a string into an integer data table
    ''' </summary>
    ''' <param name="source"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Shared Function StringToWords(ByVal source As String) As Int32()
        If source Is Nothing Then
            Return Nothing
            ' Throw New ArgumentNullException("input")
        End If

        Dim ArraySize As Integer = CInt(Math.Ceiling(source.Length / 2)) - 1

        Dim ConvertedData(ArraySize) As Int32

        Dim i As Integer
        While i <= ArraySize
            ConvertedData(i) = Asc(source.Substring(i * 2, 1)) * 256
            '* Check if past last character of odd length string
            If (i * 2) + 1 < source.Length Then ConvertedData(i) += Asc(source.Substring((i * 2) + 1, 1))
            i += 1
        End While

        Return ConvertedData
    End Function

#End Region

#Region "Helper"
    Private Structure ParsedDataAddress
        Dim PLCAddress As String
        Dim FileType As Integer
        Dim FileNumber As Integer
        Dim Element 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
    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*(?<FileType>([SBCTRNFAIOL])|(ST)|(MG)|(PD)|(PLS))(?<FileNumber>\d{1,3}):(?<ElementNumber>\d{1,3})(/(?<BitNumber>\d{1,4}))?\s*$")
    Private RE2 As New Regex("(?i)^\s*(?<FileType>[SBN])(?<FileNumber>\d{1,3})(/(?<BitNumber>\d{1,4}))\s*$")
    Private RE3 As New Regex("(?i)^\s*(?<FileType>[CT])(?<FileNumber>\d{1,3}):(?<ElementNumber>\d{1,3})[/|.](?<SubElement>(ACC|PRE|EN|DN|TT|CU|CD|DN|OV|UN|UA))\s*$")
    '* IO variation without file number Type (Input : file 1, Output : file 0 )
    Private RE4 As New Regex("(?i)^\s*(?<FileType>([IOS])):(?<ElementNumber>\d{1,3})([.](?<SubElement>[0-7]))?(/(?<BitNumber>\d{1,4}))?\s*$")
    Private Function ParseAddress(ByVal DataAddress As String) As ParsedDataAddress
        Dim result As New ParsedDataAddress

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

        '*********************************
        '* Try all match patterns
        '*********************************
        Dim mc As MatchCollection = RE1.Matches(DataAddress)

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


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


        '*********************************************
        '* Get elements extracted from match patterns
        '*********************************************
        '* Is it an I,O, or S address without a file number Type?
        If mc.Item(0).Groups("FileNumber").Length = 0 Then
            ' Is it an input or Output file?
            If DataAddress.IndexOf("i") >= 0 Or DataAddress.IndexOf("I") >= 0 Then
                result.FileNumber = 1
            ElseIf DataAddress.IndexOf("o") >= 0 Or DataAddress.IndexOf("O") >= 0 Then
                result.FileNumber = 0
            Else
                result.FileNumber = 2
            End If
        Else
            result.FileNumber = mc.Item(0).Groups("FileNumber").ToString
        End If


        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("ElementNumber").Length > 0 Then
            result.Element = mc.Item(0).Groups("ElementNumber").ToString
        Else
            '* Only bit level specified (e.g. B3/16) so divide by 16 to get element
            result.Element = result.BitNumber >> 4
            result.BitNumber = result.BitNumber Mod 16
        End If

        If mc.Item(0).Groups("SubElement").Length > 0 Then
            Select Case mc.Item(0).Groups("SubElement").ToString.ToUpper(System.Globalization.CultureInfo.CurrentCulture)
                Case "PRE" : result.SubElement = 1
                Case "ACC" : result.SubElement = 2
                Case "EN" : result.SubElement = 15
                Case "TT" : result.SubElement = 14
                Case "DN" : result.SubElement = 13
                Case "CU" : result.SubElement = 15
                Case "CD" : result.SubElement = 14
                Case "OV" : result.SubElement = 12
                Case "UN" : result.SubElement = 11
                Case "UA" : result.SubElement = 10
                Case "0" : result.SubElement = 0
                Case "1" : result.SubElement = 1
                Case "2" : result.SubElement = 2
                Case "3" : result.SubElement = 3
                Case "4" : result.SubElement = 4
                Case "5" : result.SubElement = 5
                Case "6" : result.SubElement = 6
                Case "7" : result.SubElement = 7
                Case "8" : result.SubElement = 8
            End Select
        End If


        '* These subelements are bit level
        If result.SubElement > 8 Then
            result.BitNumber = result.SubElement
            result.SubElement = 0
        End If


        '***************************************
        '* Translate file type letter to number
        '***************************************
        If result.Element < 256 Then
            result.BytesPerElements = 2

            Dim FileType As String = mc.Item(0).Groups("FileType").ToString.ToUpper(System.Globalization.CultureInfo.CurrentCulture)
            Select Case FileType
                Case "N" : result.FileType = &H89
                Case "B" : result.FileType = &H85
                Case "T" : result.FileType = &H86
                Case "C" : result.FileType = &H87
                Case "F" : result.FileType = &H8A
                    result.BytesPerElements = 4
                Case "S" : result.FileType = &H84
                Case "ST" : result.FileType = &H8D
                    result.BytesPerElements = 76
                Case "A" : result.FileType = &H8E
                Case "R" : result.FileType = &H88
                Case "O" : result.FileType = &H8B
                Case "I" : result.FileType = &H8C
                Case "L" : result.FileType = &H91
                    result.BytesPerElements = 4
                Case "MG" : result.FileType = &H92   'Message Command 146
                    result.BytesPerElements = 50
                Case "PD" : result.FileType = &H93   'PID
                    result.BytesPerElements = 46
                Case "PLS" : result.FileType = &H94   'Programmable Limit Swith
                    result.BytesPerElements = 12
            End Select
        Else
            Throw New MfgControl.AdvancedHMI.Drivers.PLCDriverException("File Element can't be over 255")
        End If

        '**************************************************************************
        '* Was a bit number higher than 15 specified along with an element number?
        '* 1-DEC-09
        '**************************************************************************
        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.FileType <> &H8B And result.FileType <> &H8C Then
                result.Element += result.BitNumber >> 4
                result.BitNumber = result.BitNumber Mod 16
            End If
        End If

        Return result
    End Function

    '****************************************************
    '* Wait for a response from PLC before returning
    '****************************************************
    Dim MaxTicks As Integer = 85  '* 50 ticks per second
    Private Function WaitForResponse(ByVal rTNS As Integer) As Integer
        'Responded = False

        Dim Loops As Integer = 0
        While Not Responded(rTNS) And Loops < MaxTicks
            'Application.DoEvents()
            System.Threading.Thread.Sleep(20)
            Loops += 1
        End While

        If Loops >= MaxTicks Then
            Return -20
        Else
            Return 0
        End If
    End Function

    '**************************************************************
    '* This method implements the common application routine
    '* as discussed in the Software Layer section of the AB manual
    '**************************************************************
    Private Function PrefixAndSend(ByVal Command As Byte, ByVal Func As Byte, ByVal data() As Byte, ByVal Wait As Boolean, ByRef rTNS As Integer) As Integer
        Dim PacketSize As Integer
        PacketSize = data.Length + 6
        PacketSize = data.Length + 4 '* make this more generic for CIP Ethernet/IP encap


        Dim CommandPacke(PacketSize) As Byte
        Dim BytePos As Integer

        CommandPacke(0) = m_TargetNode
        CommandPacke(1) = m_MyNode
        BytePos = 2
        BytePos = 0

        CommandPacke(BytePos) = Command
        CommandPacke(BytePos + 1) = 0       '* STS (status, always 0)

        CommandPacke(BytePos + 2) = (TNS And 255)
        CommandPacke(BytePos + 3) = (TNS >> 8)

        '*Mark whether this was requested by a subscription or not
        '* FIX
        PLCAddressByTNS(TNS And 255).InternallyRequested = InternalRequest


        CommandPacke(BytePos + 4) = Func

        data.CopyTo(CommandPacke, BytePos + 5)

        rTNS = TNS And &HFF
        Responded(rTNS) = False
        Dim result As Integer
        result = SendData(CommandPacke, m_MyNode, m_TargetNode)


        If result = 0 And Wait Then
            result = WaitForResponse(rTNS)

            '* Return status byte that came from controller
            If result = 0 Then
                If DataPackets(rTNS) IsNot Nothing Then
                    If (DataPackets(rTNS).Count > 3) Then
                        result = DataPackets(rTNS)(3)  '* STS position in DF1 message
                        '* If its and EXT STS, page 8-4
                        If result = &HF0 Then
                            '* The EXT STS is the last byte in the packet
                            'result = DataPackets(rTNS)(DataPackets(rTNS).Count - 2) + &H100
                            result = DataPackets(rTNS)(DataPackets(rTNS).Count - 1) + &H100
                        End If
                    End If
                Else
                    result = -8 '* no response came back from PLC
                End If
            Else
                Dim DebugCheck As Integer = 0
            End If
        Else
            Dim DebugCheck As Integer = 0
        End If

        IncrementTNS()

        Return result
    End Function

    '**************************************************************
    '* This method Sends a response from an unsolicited msg
    '**************************************************************
    Private Function SendResponse(ByVal Command As Byte, ByVal rTNS As Integer) As Integer
        Dim PacketSize As Integer
        'PacketSize = Data.Length + 5
        PacketSize = 5
        PacketSize = 3    'Ethernet/IP Preparation


        Dim CommandPacke(PacketSize) As Byte
        Dim BytePos As Integer

        CommandPacke(1) = m_TargetNode
        CommandPacke(0) = m_MyNode
        BytePos = 2
        BytePos = 0

        CommandPacke(BytePos) = Command
        CommandPacke(BytePos + 1) = 0       '* STS (status, always 0)

        CommandPacke(BytePos + 2) = (rTNS And 255)
        CommandPacke(BytePos + 3) = (rTNS >> 8)


        Dim result As Integer
        result = SendData(CommandPacke, m_MyNode, m_TargetNode)
    End Function

    ' TODO : Put this in a New event
    'Private EventHandleAdded As Boolean
    '* This is needed so the handler can be removed
    Private Dr As EventHandler = AddressOf DataLinkLayer_DataReceived
    Private Function SendData(ByVal data() As Byte, ByVal MyNode As Byte, ByVal TargetNode As Byte) As Integer
        If DLL Is Nothing Then
            CreateDLLInstance()
        End If


        'If Not EventHandleAdded Then
        '    AddHandler DLL.DataReceived, AddressOf DataLinkLayer_DataReceived
        '    EventHandleAdded = True
        'End If

        Return DLL(MyDLLInstance).ExecutePCCC(data)
    End Function

    Private Sub IncrementTNS()
        '* Incement the TransactionNumber value
        If TNS < 65535 Then
            TNS += 1
        Else
            TNS = 1
        End If
    End Sub

    '************************************************
    '* Convert the message code number into a string
    '* Ref Page 8-3
    '************************************************
    Public Shared Function DecodeMessage(ByVal msgNumber As Integer) As String
        Select Case msgNumber
            Case 0
                DecodeMessage = ""
            Case -2
                Return "Not Acknowledged (NAK)"
            Case -3
                Return "No Reponse, Check COM Settings"
            Case -4
                Return "Unknown Message from DataLink Layer"
            Case -5
                Return "Invalid Address"
            Case -6
                Return "Could Not Open Com Port"
            Case -7
                Return "No data specified to data link layer"
            Case -8
                Return "No data returned from PLC"
            Case -20
                Return "No Data Returned"
            Case -21
                Return "Received Message NAKd from invalid checksum"

                '*** Errors coming from PLC
            Case 16
                Return "Illegal Command or Format, Address may not exist or not enough elements in data file"
            Case 32
                Return "PLC Has a Problem and Will Not Communicate"
            Case 48
                Return "Remote Node Host is Misssing, Disconnected, or Shut Down"
            Case 64
                Return "Host Could Not Complete Function Due To Hardware Fault"
            Case 80
                Return "Addressing problem or Memory Protect Rungs"
            Case 96
                Return "Function not allows due to command protection selection"
            Case 112
                Return "Processor is in Program mode"
            Case 128
                Return "Compatibility mode file missing or communication zone problem"
            Case 144
                Return "Remote node cannot buffer command"
            Case 240
                Return "Error code in EXT STS Byte"

                '* EXT STS Section - 256 is added to code to distinguish EXT codes
            Case 257
                Return "A field has an illegal value"
            Case 258
                Return "Less levels specified in address than minimum for any address"
            Case 259
                Return "More levels specified in address than system supports"
            Case 260
                Return "Symbol not found"
            Case 261
                Return "Symbol is of improper format"
            Case 262
                Return "Address doesn't point to something usable"
            Case 263
                Return "File is wrong size"
            Case 264
                Return "Cannot complete request, situation has changed since the start of the command"
            Case 265
                Return "Data or file is too large"
            Case 266
                Return "Transaction size plus word address is too large"
            Case 267
                Return "Access denied, improper priviledge"
            Case 268
                Return "Condition cannot be generated - resource is not available"
            Case 269
                Return "Condition already exists - resource is already available"
            Case 270
                Return "Command cannot be executed"

            Case Else
                Return "Unknown Message - " & msgNumber
        End Select
    End Function


    'Private Sub DF1DataLink1_DataReceived(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.DataReceivedDL
    Private Sub DataLinkLayer_DataReceived(ByVal sender As Object, ByVal e As MfgControl.AdvancedHMI.Drivers.PLCCommEventArgs)
        '* Should we only raise an event if we are in AsyncMode?
        '        If m_AsyncMode Then
        '**************************************************************************
        '* If the parent form property (Synchronizing Object) is set, then sync the event with its thread
        '**************************************************************************
        '* This was moved
        Dim SequenceNumber As UInt16 = e.SequenceNumber And 255
        If DLL(MyDLLInstance).DataPacket(SequenceNumber).Count < 5 Then Exit Sub
        Dim TNSReturned As Integer = DLL(MyDLLInstance).DataPacket(SequenceNumber)(4)
        DataPackets(TNSReturned) = DLL(MyDLLInstance).DataPacket(SequenceNumber)
        Responded(TNSReturned) = True


        '**************************************************************
        '* Only extract and send back if this response contained data
        '**************************************************************
        If DataPackets(TNSReturned).Count > 7 Then
            '***************************************************
            '* Extract returned data into appropriate data type
            '* Transfer block of data read to the data table array
            '***************************************************
            '* TODO: Check array bounds
            Dim ReturnedData(DataPackets(TNSReturned).Count - 7) As Byte
            For i As Integer = 0 To DLL(MyDLLInstance).DataPacket(SequenceNumber).Count - 7
                ReturnedData(i) = DataPackets(TNSReturned)(i + 6)
            Next
            Dim d() As String = ExtractData(PLCAddressByTNS(TNSReturned), ReturnedData)


            If Not PLCAddressByTNS(TNSReturned).InternallyRequested Then
                If Not DisableEvent Then
                    Dim x As New MfgControl.AdvancedHMI.Drivers.PLCCommEventArgs(d, PLCAddressByTNS(TNSReturned).PLCAddress, TNSReturned)
                    If m_SynchronizingObject IsNot Nothing Then
                        Dim Parameters() As Object = {Me, x}
                        m_SynchronizingObject.BeginInvoke(drsd, Parameters)
                    Else
                        'RaiseEvent DataReceived(Me, System.EventArgs.Empty)
                        RaiseEvent DataReceived(Me, x)
                    End If
                End If
            Else
                '*********************************************************
                '* Check to see if this is from the Polled variable list
                '*********************************************************
                Dim ParsedAddress As ParsedDataAddress = ParseAddress(PLCAddressByTNS(TNSReturned).PLCAddress)
                Dim EnoughElements As Boolean
                For i As Integer = 0 To PolledAddressList.Count - 1
                    EnoughElements = False                    '* Are there enought elements read for this request
                    If (PolledAddressList(i).ElementNumber - PLCAddressByTNS(TNSReturned).Element + PolledAddressList(i).ElementsToRead <= d.Length) And _
                        (PolledAddressList(i).FileType <> 134 And PolledAddressList(i).FileType <> 135 And PolledAddressList(i).FileType <> &H8B And PolledAddressList(i).FileType <> &H8C) Then
                        EnoughElements = True
                    End If
                    If (PolledAddressList(i).BitNumber < 16) And ((PolledAddressList(i).ElementNumber - PLCAddressByTNS(TNSReturned).Element + PolledAddressList(i).ElementsToRead) / 16 <= d.Length) Then
                        EnoughElements = True
                    End If
                    If (PolledAddressList(i).FileType = 134 Or PolledAddressList(i).FileType = 135) And (PolledAddressList(i).ElementNumber - PLCAddressByTNS(TNSReturned).Element + PolledAddressList(i).ElementsToRead) <= d.Length Then
                        EnoughElements = True
                    End If
                    '* IO addresses - be sure not to cross elements/card slots
                    If (PolledAddressList(i).FileType = &H8B Or PolledAddressList(i).FileType = &H8C And _
                            PolledAddressList(i).ElementNumber = PLCAddressByTNS(TNSReturned).Element) Then
                        Dim WordToUse As Integer = PolledAddressList(i).BitNumber >> 4
                        If (d.Length - 1) >= (PolledAddressList(i).ElementNumber - PLCAddressByTNS(TNSReturned).Element + (WordToUse)) Then
                            EnoughElements = True
                        End If
                    End If


                    If PolledAddressList(i).FileNumber = PLCAddressByTNS(TNSReturned).FileNumber And _
                        EnoughElements And _
                        PLCAddressByTNS(TNSReturned).Element <= PolledAddressList(i).ElementNumber Then ' And _
                        '((PLCAddressByTNS(TNSReturned).FileType <> &H8B And PLCAddressByTNS(TNSReturned).FileType <> &H8C) Or PLCAddressByTNS(TNSReturned).BitNumber = PolledAddressList(i).BitNumber) Then
                        'PolledAddressList(i).BitNumber = PLCAddressByTNS(TNSReturned).BitNumber Then
                        'PolledValueReturned(PLCAddressByTNS(TNSReturned).PLCAddress, d)

                        Dim BitResult(PolledAddressList(i).ElementsToRead - 1) As String
                        '* Handle timers and counters as exceptions because of the 3 subelements
                        If (PolledAddressList(i).FileType = 134 Or PolledAddressList(i).FileType = 135) Then
                            '* If this is a bit level address for a timer or counter, then handle appropriately
                            If PolledAddressList(i).BitNumber < 16 Then
                                Try
                                    '                                    If d((PolledAddressList(i).ElementNumber - PLCAddressByTNS(TNSReturned).Element) * 3) Then
                                    BitResult(0) = (d((PolledAddressList(i).ElementNumber - PLCAddressByTNS(TNSReturned).Element) * 3) And 2 ^ PolledAddressList(i).BitNumber) > 0
                                    'End If
                                Catch
                                    MsgBox("Error in returning data from datareceived")
                                End Try
                            Else
                                Try
                                    For k As Integer = 0 To PolledAddressList(i).ElementsToRead - 1
                                        BitResult(k) = d((PolledAddressList(i).ElementNumber - PLCAddressByTNS(TNSReturned).Element + k) * 3 + PolledAddressList(i).SubElement)
                                    Next
                                Catch
                                    MsgBox("Error in returning data from datareceived")
                                End Try
                            End If
                            m_SynchronizingObject.BeginInvoke(PolledAddressList(i).dlgCallBack, CObj(BitResult))
                        Else
                            '* If its bit level, then return the individual bit
                            If PolledAddressList(i).BitNumber < 99 Then
                                '*TODO : Make this handle a rquest for multiple bits
                                Try
                                    '* Test to see if bits or integers returned
                                    Dim x As Integer
                                    Try
                                        x = d(0)
                                        If PolledAddressList(i).BitNumber < 16 Then
                                            BitResult(0) = (d(PolledAddressList(i).ElementNumber - PLCAddressByTNS(TNSReturned).Element) And 2 ^ PolledAddressList(i).BitNumber) > 0
                                        Else
                                            Dim WordToUse As Integer = PolledAddressList(i).BitNumber >> 4
                                            Dim ModifiedBitToUse As Integer = PolledAddressList(i).BitNumber Mod 16
                                            BitResult(0) = (d(PolledAddressList(i).ElementNumber - PLCAddressByTNS(TNSReturned).Element + (WordToUse)) And 2 ^ ModifiedBitToUse) > 0
                                        End If
                                    Catch ex As Exception
                                        BitResult(0) = d(0)
                                    End Try
                                Catch ex As Exception
                                    MsgBox("Error in returning data from datareceived - " & ex.Message)
                                End Try
                                m_SynchronizingObject.BeginInvoke(PolledAddressList(i).dlgCallBack, CObj(BitResult))
                            Else
                                '* All other data types
                                For k As Integer = 0 To PolledAddressList(i).ElementsToRead - 1
                                    BitResult(k) = d((PolledAddressList(i).ElementNumber - PLCAddressByTNS(TNSReturned).Element + k))
                                Next

                                'm_SynchronizingObject.BeginInvoke(PolledAddressList(i).dlgCallBack, d(PolledAddressList(i).ElementNumber - PLCAddressByTNS(TNSReturned).Element))
                                m_SynchronizingObject.BeginInvoke(PolledAddressList(i).dlgCallBack, CObj(BitResult))
                            End If
                        End If

                        'PolledAddressList(k).LastValue = values(0)
                    End If
                Next
            End If
        End If
    End Sub

    '******************************************************************
    '* This is called when a message instruction was sent from the PLC
    '******************************************************************
    Private Sub DF1DataLink1_UnsolictedMessageRcvd()
        If m_SynchronizingObject IsNot Nothing Then
            Dim Parameters() As Object = {Me, EventArgs.Empty}
            m_SynchronizingObject.BeginInvoke(drsd, Parameters)
        Else
            RaiseEvent UnsolictedMessageRcvd(Me, System.EventArgs.Empty)
        End If
    End Sub


    '****************************************************************************
    '* 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
    Private Sub UnsolictedMessageRcvdSync(ByVal sender As Object, ByVal e As EventArgs)
        RaiseEvent UnsolictedMessageRcvd(sender, e)
    End Sub
#End Region

End Class






