'**********************************************************************************************
'* OPC Comm
'*
'* Archie Jacobs
'* Manufacturing Automation, LLC
'* ajacobs@mfgcontrol.com
'* 20-MAY-11
'*
'*
'* Copyright 2011 Archie Jacobs
'*
'*
'*******************************************************************************************************
Imports System.ComponentModel.Design

Public Class OPCComm
    Inherits System.ComponentModel.Component
    Implements ICommComponent

    '* Create a common instance to share so multiple driver instances can be used in a project
    Private DLL As Opc.Da.Server
    Private fact As New OpcCom.Factory

    Private SubscribedCollection As New List(Of PolledAddressInfo)

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

    '*********************************************************************************
    '* 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 OPCItem As Opc.Da.Item
        Dim dlgCallBack As ICommComponent.ReturnValues
        Dim PollRate As Integer
        Dim ID As Integer
        Dim ElementsToRead As Integer
    End Structure

    Private CurrentID As Integer = 1
    Private TransactionID As Integer

    Private PolledAddressList As New List(Of PolledAddressInfo)

#Region "Constructor/Destructor"
    Private components As System.ComponentModel.IContainer
    Public Sub New(ByVal container As System.ComponentModel.IContainer)
        MyClass.New()

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

    Public Sub New()
        MyBase.New()
    End Sub


    '* Component overrides dispose to clean up the component list.
    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
        'RemoveHandler DLL(MyDLLInstance).DataReceived, Dr
        If DLL IsNot Nothing Then
            DLL.Disconnect()
            DLL.Dispose()
        End If

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


    '***************************************************************
    '* Create the Data Link Layer Instances
    '* if the IP Address is the same, then resuse a common instance
    '***************************************************************
    Private Sub CreateDLLInstance()
        If DLL Is Nothing Then
            DLL = New Opc.Da.Server(fact, Nothing)
            DLL.Url = New Opc.URL("opcda://localhost/" & _OPCServer)
            'DLL.Connect(DLL.Url, New Opc.ConnectData(New System.Net.NetworkCredential()))
            DLL.Connect()
        End If
    End Sub

#Region "Properties"
    Private _OPCServer As String = "RSLinx OPC Server"
    Public Property OPCServer() As String
        Get
            Return _OPCServer
        End Get
        Set(ByVal value As String)
            _OPCServer = value
        End Set
    End Property

    Private _OPCGroup As String = "MyGroup"
    Public Property OPCGroup() As String
        Get
            Return _OPCGroup
        End Get
        Set(ByVal value As String)
            _OPCGroup = value
        End Set
    End Property

    Private _OPCTopic As String
    Public Property OPCTopic() As String
        Get
            Return _OPCTopic
        End Get
        Set(ByVal value As String)
            _OPCTopic = value
        End Set
    End Property

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

    '**************************************************************
    '* Stop the polling of subscribed data
    '**************************************************************
    Private m_DisableSubscriptions
    Public Property DisableSubscriptions() As Boolean Implements ICommComponent.DisableSubscriptions
        Get
            Return m_DisableSubscriptions
        End Get
        Set(ByVal value As Boolean)
            m_DisableSubscriptions = value
        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

        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 "Read/Write Interface to Driver"
    Private ReadSubscription As Opc.Da.Subscription
    Private ReadSubscriptionState As Opc.Da.SubscriptionState

    Public Function ReadAny(ByVal startAddress As String, ByVal numberOfElements As Integer) As String() Implements ICommComponent.ReadAny
        If DLL Is Nothing Then CreateDLLInstance()

        If Not AsyncMode Then
            Dim items(0) As Opc.Da.Item
            items(0) = New Opc.Da.Item
            items(0).ItemName = "[" & _OPCTopic & "]" & startAddress & ",L" & numberOfElements
            items(0).SamplingRate = 50
            items(0).Active = True

            Dim values() As Opc.Da.ItemValue
            values = DLL.Read(items)


            Dim ArraySize As Integer = 0
            If TypeOf (values(0).Value) Is System.Array Then
                ArraySize = values(0).Value.length - 1
            End If

            Dim ReturnValues(ArraySize) As String
            If ArraySize > 0 Then
                For i As Integer = 0 To values(0).Value.length - 1
                    ReturnValues(i) = CStr(values(0).Value(i))
                Next
            Else
                ReturnValues(0) = CStr(values(0).Value)
            End If

            Return ReturnValues
        Else
            '*********************************************************************
            '* If Async Mode, then return immediately and return value on event
            '*********************************************************************
            If TransactionID < 32767 Then
                TransactionID += 1
            Else
                TransactionID = 0
            End If


            If ReadSubscriptionState Is Nothing Then
                ReadSubscriptionState = New Opc.Da.SubscriptionState
                ReadSubscriptionState.Name = "AsyncReadGroup"

                ReadSubscription = DLL.CreateSubscription(ReadSubscriptionState)
            End If


            Dim Items(0) As Opc.Da.Item
            Items(0) = New Opc.Da.Item
            Items(0).ItemName = "[" & _OPCTopic & "]" & startAddress & ",L" & numberOfElements
            Items(0).SamplingRate = 250
            Items(0).ClientHandle = TransactionID

            Dim ItemRes() As Opc.Da.ItemResult
            ItemRes = ReadSubscription.AddItems(Items)

            For i As Integer = 0 To ItemRes.Length - 1
                Items(i).ServerHandle = ItemRes(i).ServerHandle
            Next


            'Dim tmp(0) As Opc.Da.Item
            'tmp(0) = New Opc.Da.Item
            'If _OPCTopic <> "" Then
            '    tmp(0).ItemName = "[" & _OPCTopic & "]"
            'Else
            '    tmp(0).ItemName = ""
            'End If
            'tmp(0).ItemName &= startAddress  '*& ",L" & numberOfElements
            'tmp(0).SamplingRate = 250



            Dim req As Opc.IRequest = Nothing
            ReadSubscription.Read(Items, TransactionID, New Opc.Da.ReadCompleteEventHandler(AddressOf AsyncReadCompleteCallback), req)

            Dim r() As String = {TransactionID}
            Return r
        End If
    End Function


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

    '*************************************
    '* Return values from an Async Read
    '*************************************
    Private Sub AsyncReadCompleteCallback(ByVal clientHandle As Object, ByVal values() As Opc.Da.ItemValueResult)
        Dim ArraySize As Integer = 0
        If TypeOf (values(0).Value) Is System.Array Then
            ArraySize = values(0).Value.length - 1
        End If

        Dim ReturnValues(ArraySize) As String
        If ArraySize > 0 Then
            For i As Integer = 0 To values(0).Value.length - 1
                ReturnValues(i) = CStr(values(0).Value(i))
            Next
        Else
            ReturnValues(0) = CStr(values(0).Value)
        End If

        Dim x As New MfgControl.AdvancedHMI.Drivers.PLCCommEventArgs(ReturnValues, values(0).ItemName, clientHandle)
        If m_SynchronizingObject IsNot Nothing Then
            Dim Parameters() As Object = {Me, x}
            m_SynchronizingObject.BeginInvoke(drsd, Parameters)
        Else
            RaiseEvent DataReceived(Me, x)
        End If

        ReadSubscription.SetEnabled(False)
    End Sub

#Region "AsyncReturn"
    '* This is needed so the handler can be removed
    'Private Dr As EventHandler = AddressOf DataLinkLayer_DataReceived

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

    '*****************************************************************
    '* Write Section
    '*
    '* Address is in the form of <file type><file Number>:<offset>
    '* examples  N7:0, B3:0,
    '******************************************************************
    Public Function WriteData(ByVal startAddress As String, ByVal numberOfElements As Integer, ByVal dataToWrite() As String) As Integer
        Dim items(0) As Opc.Da.Item
        items(0) = New Opc.Da.Item
        items(0).ItemName = "[" & _OPCTopic & "]" & startAddress & ",L" & numberOfElements


        Dim Values(numberOfElements - 1) As Opc.Da.ItemValue
        For i As Integer = 0 To numberOfElements - 1
            Values(i) = New Opc.Da.ItemValue((items(i)))
            Values(i).Value = CStr(dataToWrite(i))
        Next

        DLL.Write(Values)
    End Function

    Public Function WriteData(ByVal startAddress As String, ByVal dataToWrite As String) As String Implements ICommComponent.WriteData
        Dim temp(1) As String
        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
#End Region



#Region "Subscribing"
    '*********************************************************************
    Private Subscription As Opc.Da.Subscription
    Private SubscriptionState As New Opc.Da.SubscriptionState
    Private SubscribedItems() As Opc.Da.Item


    'Public Function Subscribe(ByVal PLCAddress As String, ByVal numberOfElements As Integer, ByVal CallBack As ICommComponent.ReturnValues) As Integer Implements ICommComponent.Subscribe
    Public Function Subscribe(ByVal PLCAddress As String, ByVal numberOfElements As Integer, ByVal CallBack As ICommComponent.ReturnValues, ByVal FullDataTypeBP As String, ByVal DataLengthBP As Integer) As Integer Implements ICommComponent.Subscribe
        Dim tmpPA As New PolledAddressInfo

        Dim tmp As New Opc.Da.Item
        If _OPCTopic <> "" Then
            tmp.ItemName = "[" & _OPCTopic & "]"
        Else
            tmp.ItemName = ""
        End If
        tmp.ItemName &= PLCAddress  '*& ",L" & numberOfElements

        tmpPA.OPCItem = tmp
        tmpPA.dlgCallBack = CallBack

        SubscribedCollection.Add(tmpPA)


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


        UpdateSubscribedItems()

        'ReadCompleted = False
        Dim req As Opc.IRequest = Nothing
        Subscription.Read(SubscribedItems, SubscribedCollection.Count - 1, New Opc.Da.ReadCompleteEventHandler(AddressOf ReadCompleteCallback), req)
        'Subscription.Read(SubscribedItems)


        Return tmpPA.ID
    End Function

    'Private ReadCompleted As Boolean
    Private Sub ReadCompleteCallback(ByVal clientHandle As Object, ByVal values() As Opc.Da.ItemValueResult)
        'ReadCompleted = True
    End Sub
    Private Sub DataChangedCallBack(ByVal clientHandle As Object, ByVal requestHandle As Object, ByVal values() As Opc.Da.ItemValueResult)
        '        Dim x = values(0).Value
        For i As Integer = 0 To values.Length - 1
            Dim ReturnedValues() As String = {values(i).Value}

            If values(i).ClientHandle < SubscribedCollection.Count Then
                m_SynchronizingObject.BeginInvoke(SubscribedCollection(CInt(values(i).ClientHandle)).dlgCallBack, CObj(ReturnedValues))
            End If
        Next
    End Sub

    Private ItemResult() As Opc.Da.ItemResult

    Private Sub UpdateSubscribedItems()
        '****************************************************************************
        If DLL Is Nothing Then CreateDLLInstance()

        If DLL.IsConnected Then
            If Subscription Is Nothing Then
                'SubscriptionState.Name = _OPCGroup
                SubscriptionState.Name = "SubscribedGroup"

                Subscription = DLL.CreateSubscription(SubscriptionState)
                AddHandler Subscription.DataChanged, AddressOf DataChangedCallBack
            End If


            If SubscribedItems IsNot Nothing And Subscription.Items.Length > 0 Then Subscription.RemoveItems(SubscribedItems)

            ReDim SubscribedItems(SubscribedCollection.Count - 1)
            For i As Integer = 0 To SubscribedCollection.Count - 1
                SubscribedItems(i) = New Opc.Da.Item
                SubscribedItems(i).ItemName = SubscribedCollection(i).OPCItem.ItemName
                SubscribedItems(i).SamplingRate = 250
                'SubscribedItems(i).Active = True
                SubscribedItems(i).ClientHandle = i
            Next
            ItemResult = Subscription.AddItems(SubscribedItems)

            For i As Integer = 0 To ItemResult.Length - 1
                SubscribedItems(i).ServerHandle = ItemResult(i).ServerHandle
            Next
        End If
    End Sub

    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)
        End If

        UpdateSubscribedItems()
    End Function

#End Region




End Class






