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
;