Reading and writing of PLC variables of any type

From TwinCAT.Ads.NET version >= 1.0.0.15

Task

Read and Write variables of any type with the help of the ReadAny and WriteAny methods.

Description

ReadAny

In the event method btnRead_Click the method  TcAdsClient.ReadAny is used to read a variable by handle:

TwinCAT.Ads.TcAdsClient.ReadAny(System.Int32, System.Type)
TwinCAT.Ads.TcAdsClient.ReadAny(System.Int32, System.Type, System.Int32[])

The type of the variable is passed to the method in the parameter type. In case the method was successfully, the read data will be returned as an object. The type of the object is equal to the type passed in the parameter type. Because some data types (arrays and strings) need additional information, an overload of the method ReadAny exists, that takes an additional parameter args. Full list of supported types can be found in the documentation of the overloaded method.

 

Example:

A global PLC variable  of the type ARRAY[0..3] OF DINT should be read:

Dim hArr AsInteger
Dim arr(0 To 3) AsInteger

hArr = adsClient.CreateVariableHandle(".arr")
arr = adsClient.ReadAny(hArr, GetType(Integer()), NewInteger() {4})

...
adsClient.DeleteVariableHandle(hArr)

 

WriteAny

In the event method btnWrite_Click the method TcAdsClient.WriteAny is used to write to a variable by handle:

TwinCAT.Ads.TcAdsClient.WriteAny(System.Int32, System.Object)
TwinCAT.Ads.TcAdsClient.WriteAny(System.Int32, System.Object, System.Int32[])

The parameter value is a reference to the object, that should be written to the PLC variable.  Full list of supported types of the object value can be found in the documentation of the overloaded method.

 

Example:

A global PLC variable  of the type ARRAY[0..3] OF DINT should be written:

Dim hArr AsInteger
Dim arr AsInteger() = NewInteger() {1, 2, 3, 4}

hArr = adsClient.CreateVariableHandle(".arr")
adsClient.WriteAny(hArr, arr)

...
adsClient.DeleteVariableHandle(hArr)

 

Reading and writing of structures (not possible with the Compact Framework, CE):

To be able to read or write PLC structures the memory layout of the structure or class in .NET must be the same as in the PLC. The layout of a structure or class can be specified with the attribute StructLayoutAttribute. The LayoutKind must be set to LayoutKind.Sequential and the pack must be set to 1. Therefore the class SimpleStruct is defined as followed:

<StructLayout(LayoutKind.Sequential, Pack:=1)> _
Public Class SimpleStruct
    Public lrealVal As Double
    Public dintVal1 As Integer
End Class


If arrays, strings or boolean values are define the class, one must specify how these fields should be marshalled. This is accomplished with help of the MarshalAs attribute. Because arrays and strings do not have a fixed length in .NET, the property SizeConst is necessary for arrays and strings. It is not possible to marshal multidimensional arrays or arrays of structures with the :NET Framework 1.1. Multidimensional arrays in the PLC must be mapped to one dimensional arrays in .NET.

In the example the MarshalAsAttribute is used in the class ComplexStruct:

<StructLayout(LayoutKind.Sequential, Pack:=1)> _
Public Class ComplexStruct
    Public intVal As Short
    'specifies how .NET should marshal the array
    'SizeConst specifies the number of elements the array has.
    <MarshalAs(UnmanagedType.ByValArray, SizeConst:=4)> _
    Public dintArr(4) As Integer
    <MarshalAs(UnmanagedType.I1)> _
    Public boolVal As Boolean
    Public byteVal As Byte
    'specifies how .NET should marshal the string
    'SizeConst specifies the number of characters the string has.
    '(inclusive the terminating null ). 
    <MarshalAs(UnmanagedType.ByValTStr, SizeConst:=6)> _
    Public stringVal As String = ""
    Public simpleStruct1 As SimpleStruct = New SimpleStruct
End Class

 

Register ADS notifcations

In the event method btnAddNotifications_Click the method AddDeviceNotificationEx is used to register notifications for a PLC variable. If the value of a variable changes the event AdsNotificationEx is fired. The difference to the event AdsNotification, is that the value of the variable is stored in an object instead of in an AdsStream. Therefore one has to pass the type of the object to the method AddDeviceNotificationEx:

notificationHandles.Add(adsClient.AddDeviceNotificationEx("MAIN.dint1", AdsTransMode.OnChange, 100, 0, tbDint1, GetType(Integer)))

 

As user object the textbox that should display the value is passed. If  the event is fired, the event method adsClient_AdsNotificationEx is called.

 

 

Visual Basic (for .NET framework) program

Imports TwinCAT.Ads
Imports System.Runtime.InteropServices

Public Class Form1
    Inherits System.Windows.Forms.Form

    'PLC variable handles
    Private hdint1 As Integer
    Private hbool1 As Integer    
    Private husint1 As Integer
    Private hlreal1 As Integer
    Private hstr1 As Integer
    Private hstr2 As Integer
    Private hcomplexStruct As Integer
    Private notificationHandles As ArrayList

#Region " Windows Form Designer generated code "

...

#End Region
    Private WithEvents adsClient As TcAdsClient

    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
    adsClient = New TcAdsClient
    notificationHandles = New ArrayList
    Try
        adsClient.Connect(801)
        btnDeleteNotifications.Enabled = False
        'create handles for the PLC variables
        hbool1 = adsClient.CreateVariableHandle("MAIN.bool1")
        hdint1 = adsClient.CreateVariableHandle("MAIN.dint1")
        husint1 = adsClient.CreateVariableHandle("MAIN.usint1")
        hlreal1 = adsClient.CreateVariableHandle("MAIN.lreal1")
        hstr1 = adsClient.CreateVariableHandle("MAIN.str1")
        hstr2 = adsClient.CreateVariableHandle("MAIN.str2")
        hcomplexStruct = adsClient.CreateVariableHandle("MAIN.ComplexStruct1")
    Catch ex As Exception
        MessageBox.Show(ex.Message)
    End Try
    End Sub

    Private Sub Form1_Closing(ByVal sender As Object, ByVal e As System.ComponentModel.CancelEventArgs) Handles MyBase.Closing
    adsClient.Dispose()
    End Sub

    Private Sub btnRead_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnRead.Click
    Try
        'read by handle
        'the second parameter specifies the type of the variable
        tbDint1.Text = adsClient.ReadAny(hdint1, GetType(Integer)).ToString()
        tbUsint1.Text = adsClient.ReadAny(husint1, GetType(Byte)).ToString()
        tbBool1.Text = adsClient.ReadAny(hbool1, GetType(Boolean)).ToString()
        tblreal1.Text = adsClient.ReadAny(hlreal1, GetType(Double)).ToString()
        'with strings one has to additionally pass the number of characters
        'specified in the PLC project(default 80). 
        'This value is passed is an Integer array.         
        tbStr1.Text = adsClient.ReadAny(hstr1, GetType(String), New Integer() {80}).ToString()
        tbStr2.Text = adsClient.ReadAny(hstr2, GetType(String), New Integer() {5}).ToString()
        FillStructControls(adsClient.ReadAny(hcomplexStruct, GetType(ComplexStruct)))
    Catch ex As Exception
        MessageBox.Show(ex.Message)
    End Try
    End Sub

    Private Sub btnWrite_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnWrite.Click
    Try
        'write by handle
        'the second parameter is the object to be written to the PLC variable
        adsClient.WriteAny(hdint1, Integer.Parse(tbDint1.Text))
        adsClient.WriteAny(husint1, Byte.Parse(tbUsint1.Text))
        adsClient.WriteAny(hbool1, Boolean.Parse(tbBool1.Text))
        adsClient.WriteAny(hlreal1, Double.Parse(tblreal1.Text))
        'with strings one has to additionally pass the number of characters
        'the variable has in the PLC(default 80).        
        adsClient.WriteAny(hstr1, tbStr1.Text, New Integer() {80})
        adsClient.WriteAny(hstr2, tbStr2.Text, New Integer() {5})
        adsClient.WriteAny(hcomplexStruct, GetStructFromControls())
    Catch ex As Exception
        MessageBox.Show(ex.Message)
    End Try
    End Sub

    Private Sub btnAddNotifications_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnAddNotifications.Click
    notificationHandles.Clear()
    Try
        'register notification        
        notificationHandles.Add(adsClient.AddDeviceNotificationEx("MAIN.dint1", AdsTransMode.OnChange, 100, 0, tbDint1, GetType(Integer)))
        notificationHandles.Add(adsClient.AddDeviceNotificationEx("MAIN.usint1", AdsTransMode.OnChange, 100, 0, tbUsint1, GetType(Byte)))
        notificationHandles.Add(adsClient.AddDeviceNotificationEx("MAIN.bool1", AdsTransMode.OnChange, 100, 0, tbBool1, GetType(Boolean)))
        notificationHandles.Add(adsClient.AddDeviceNotificationEx("MAIN.lreal1", AdsTransMode.OnChange, 100, 0, tblreal1, GetType(Double)))
        notificationHandles.Add(adsClient.AddDeviceNotificationEx("MAIN.str1", AdsTransMode.OnChange, 100, 0, tbStr1, GetType(String), New Integer() {80}))
        notificationHandles.Add(adsClient.AddDeviceNotificationEx("MAIN.str2", AdsTransMode.OnChange, 100, 0, tbStr2, GetType(String), New Integer() {5}))
        notificationHandles.Add(adsClient.AddDeviceNotificationEx("MAIN.complexStruct1", AdsTransMode.OnChange, 100, 0, tbDint1, GetType(ComplexStruct)))
    Catch ex As Exception
        MessageBox.Show(ex.Message)
    End Try
    btnDeleteNotifications.Enabled = True
    btnAddNotifications.Enabled = False
    End Sub

    Private Sub btnDeleteNotifications_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnDeleteNotifications.Click
    'delete registered notifications.
    Try
        For Each handle As Integer In notificationHandles
        adsClient.DeleteDeviceNotification(handle)
        Next
    Catch ex As Exception
        MessageBox.Show(ex.Message)
    End Try
    notificationHandles.Clear()
    btnAddNotifications.Enabled = True
    btnDeleteNotifications.Enabled = False
    End Sub

    Private Sub adsClient_AdsNotificationEx(ByVal sender As Object, ByVal e As TwinCAT.Ads.AdsNotificationExEventArgs) Handles adsClient.AdsNotificationEx
    Dim txtBox As TextBox = e.UserData
    Dim type As Type = e.Value.GetType()
    If (type Is GetType(String) Or type.IsPrimitive()) Then
        txtBox.Text = e.Value
    ElseIf (type Is GetType(ComplexStruct)) Then
        FillStructControls(DirectCast(e.Value, ComplexStruct))
    End If
    End Sub

    Public Sub FillStructControls(ByVal struct As ComplexStruct)
    tbComplexStruct_IntVal.Text = struct.intVal
    tbComplexStruct_dintArr.Text = String.Format("{0}, {1}, {2}, {3}", struct.dintArr(0), struct.dintArr(1), struct.dintArr(2), struct.dintArr(3))
    tbComplexStruct_boolVal.Text = struct.boolVal
    tbComplexStruct_ByteVal.Text = struct.byteVal
    tbComplexStruct_stringVal.Text = struct.stringVal
    tbComplexStruct_SimpleStruct1_lrealVal.Text = struct.simpleStruct1.lrealVal
    tbComplexStruct_SimpleStruct_dintVal.Text = struct.simpleStruct1.dintVal1
    End Sub

    Public Function GetStructFromControls() As ComplexStruct
    Dim struct As ComplexStruct = New ComplexStruct
    Dim stringArr() As String = tbComplexStruct_dintArr.Text.Split(New Char() {","})

    struct.intVal = Short.Parse(tbComplexStruct_IntVal.Text)
    For i As Integer = 0 To stringArr.Length - 1
        struct.dintArr(i) = Integer.Parse(stringArr(i))
    Next
    struct.boolVal = Boolean.Parse(tbComplexStruct_boolVal.Text)
    struct.byteVal = Byte.Parse(tbComplexStruct_ByteVal.Text)
    struct.stringVal = tbComplexStruct_stringVal.Text
    struct.simpleStruct1.dintVal1 = Integer.Parse(tbComplexStruct_SimpleStruct_dintVal.Text)
    struct.simpleStruct1.lrealVal = Double.Parse(tbComplexStruct_SimpleStruct1_lrealVal.Text)
    Return struct
    End Function
End Class

<StructLayout(LayoutKind.Sequential, Pack:=1)> _
Public Class SimpleStruct
    Public lrealVal As Double
    Public dintVal1 As Integer
End Class

<StructLayout(LayoutKind.Sequential, Pack:=1)> _
Public Class ComplexStruct
    Public intVal As Short
    'specifies how .NET should marshal the array
    'SizeConst specifies the number of elements the array has.
    <MarshalAs(UnmanagedType.ByValArray, SizeConst:=4)> _
    Public dintArr(4) As Integer
    <MarshalAs(UnmanagedType.I1)> _
    Public boolVal As Boolean
    Public byteVal As Byte
    'specifies how .NET should marshal the string
    'SizeConst specifies the number of characters the string has.
    '(inclusive the terminating null ). 
    <MarshalAs(UnmanagedType.ByValTStr, SizeConst:=6)> _
    Public stringVal As String = ""
    Public simpleStruct1 As SimpleStruct = New SimpleStruct
End Class

PLC program

TYPE TSimpleStruct :
STRUCT
    lrealVal : LREAL := 1.23;
    dintVal : DINT := 120000;
END_STRUCT
END_TYPE

TYPE TComplexStruct :
STRUCT
    intVal : INT :=1200;
    dintArr : ARRAY[0..3] OF DINT:= 1,2,3,4;
    boolVal : BOOL := FALSE;
    byteVal : BYTE :=10;
    stringVal : STRING(5) := 'hallo';
    simpleStruct1 : TSimpleStruct;
END_STRUCT
END_TYPE

PROGRAM MAIN
VAR
    (*primitive Types*)
    bool1       :BOOL   := FALSE;
    int1        :INT    := 30000;
    dint1       :DINT   :=125000;
    usint1      :USINT  :=200;
    real1       :REAL   := 1.2;
    lreal1      :LREAL  :=3.5;
    (*string Types*)
    str1        :STRING     := 'this is a test string';
    str2        :STRING(5):= 'hallo';
    (*struct Types*)
    complexStruct1  : TComplexStruct;
END_VAR
;

 

Download

Language / IDE

Unpack the sample program

Visual Basic (for .NET framework)

Sample07.exe