Reading and writing of PLC variables of any type (ReadAny, WriteAny)

Download

Language / IDE

Unpack the example program

Visual C#

Sample07.zip

Visual Basic (for .NET Framework)

Sample07.exe

Delphi Prism (Embarcadero Prism XE2, Oxygene for .NET)

Sample07.exe

Delphi for .NET (Borland Developer Studio 2006)

Sample07.exe

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:

public object ReadAny(int variableHandle, Type type)
public object ReadAny(int variableHandle, Type type, int[] args)

The type of the variable is passed to the method in the parameter type. In case the method was successfull, the read data will be returned as a 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. E.g., with strings one must pass an integer array of the length 1. Full list of supported types can be found in the documentation of the overloaded method.

 

Example:

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

int hArr;
int[] arr;
 
hArr = adsClient.CreateVariableHandle(".arr");
arr = (int[]) adsClient.ReadAny(hArr, typeof(int[]), new int[] {4});

...
adsClient.DeleteVariableHandle(hArr);

 

WriteAny

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

public void WriteAny(int variableHandle, object value)
public void WriteAny(int variableHandle, object value, int[] args)

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 PLC variable  of the type ARRAY[0..3] OF DINT should be written:

int hArr;
int[] arr = new int[]{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 double lrealVal;
    public int dintVal1;
}

 

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 short intVal;
    //specifies how .NET should marshal the array
    //SizeConst specifies the number of elements the array has.
    [MarshalAs(UnmanagedType.ByValArray, SizeConst=4)]
    public int[] dintArr = new int[4];
    [MarshalAs(UnmanagedType.I1)]
    public bool boolVal;
    public byte byteVal;
    //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 string stringVal = "";
    public SimpleStruct simpleStruct1 =new SimpleStruct();
}

 

 

Register ADS notifications

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 must pass the type of the object to the method AddDeviceNotificationEx:

notificationHandles.Add(adsClient.AddDeviceNotificationEx("MAIN.dint1", AdsTransMode.OnChange, 100, 0, tbDint1, typeof(int)));

 

As user object the textbox that should display the value is passed. If the event is fired, the event method adsClient_AdsNotificationEx is called. For this the event must be registered in the Form_Load method.

adsClient.AdsNotificationEx+=new AdsNotificationExEventHandler(adsClient_AdsNotificationEx);

 

 

C# program

using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
using System.Runtime.InteropServices;
using TwinCAT.Ads;


namespace Sample07
{
    /// <summary>
    /// Summary description for Form1.
    /// </summary>
    public class Form1 : System.Windows.Forms.Form
    {
        internal System.Windows.Forms.Button btnDeleteNotifications;
        internal System.Windows.Forms.Button btnAddNotifications;
        internal System.Windows.Forms.Button btnWrite;
        internal System.Windows.Forms.Button btnRead;
        internal System.Windows.Forms.GroupBox GroupBox3;
        internal System.Windows.Forms.TextBox tbComplexStruct_dintArr;
        internal System.Windows.Forms.Label Label14;
        internal System.Windows.Forms.TextBox tbComplexStruct_ByteVal;
        internal System.Windows.Forms.Label Label13;
        internal System.Windows.Forms.TextBox tbComplexStruct_SimpleStruct1_lrealVal;
        internal System.Windows.Forms.Label Label12;
        internal System.Windows.Forms.TextBox tbComplexStruct_SimpleStruct_dintVal;
        internal System.Windows.Forms.Label Label11;
        internal System.Windows.Forms.Label Label5;
        internal System.Windows.Forms.TextBox tbComplexStruct_stringVal;
        internal System.Windows.Forms.Label Label3;
        internal System.Windows.Forms.TextBox tbComplexStruct_boolVal;
        internal System.Windows.Forms.Label Label9;
        internal System.Windows.Forms.TextBox tbComplexStruct_IntVal;
        internal System.Windows.Forms.Label Label10;
        internal System.Windows.Forms.GroupBox GroupBox2;
        internal System.Windows.Forms.TextBox tbStr2;
        internal System.Windows.Forms.Label Label7;
        internal System.Windows.Forms.TextBox tbStr1;
        internal System.Windows.Forms.Label Label8;
        internal System.Windows.Forms.GroupBox GroupBox1;
        internal System.Windows.Forms.TextBox tblreal1;
        internal System.Windows.Forms.Label Label6;
        internal System.Windows.Forms.TextBox tbUsint1;
        internal System.Windows.Forms.Label Label4;
        internal System.Windows.Forms.TextBox tbDint1;
        internal System.Windows.Forms.Label Label2;
        internal System.Windows.Forms.TextBox tbBool1;
        internal System.Windows.Forms.Label Label1;
        
        /// <summary>
        /// Required designer variable.
        /// </summary>
        private System.ComponentModel.Container components = null;
        
        //PLC variable handles
        private int hdint1;
        private int hbool1;
        private int husint1;
        private int hlreal1;
        private int hstr1;
        private int hstr2;
        private int hcomplexStruct;
        private ArrayList notificationHandles;
        
        private TcAdsClient adsClient;
        
        public Form1()
        {
            //
            // Required for Windows Form Designer support
            //
            InitializeComponent();          
        }
        
        /// <summary>
        /// Clean up any resources being used.
        /// </summary>
        protected override void Dispose( bool disposing )
        {
            if( disposing )
            {
                if (components != null)
                {
                    components.Dispose();
                }
            }
            base.Dispose( disposing );
        }
        
        #region Windows Form Designer generated code
        ..
        #endregion
        
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main()
        {
          Application.Run(new Form1());
        }
        
        private void Form1_Load(object sender, System.EventArgs e)
        {
            adsClient = new TcAdsClient();
            notificationHandles = new ArrayList();
            try
            {
                // Connect to local PLC - Runtime 1 - TwinCAT2 Port=801, TwinCAT3 Port=851
                adsClient.Connect(801);
                adsClient.AdsNotificationEx+=new AdsNotificationExEventHandler(adsClient_AdsNotificationEx);
                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(Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }
        
        private void Form1_Closing(object sender, System.ComponentModel.CancelEventArgs e)
        {
        adsClient.Dispose();
        }
        
        private void btnRead_Click(object sender, System.EventArgs e)
        {
            try
            {
                //read by handle
                //the second parameter specifies the type of the variable
                tbDint1.Text = adsClient.ReadAny(hdint1, typeof(int)).ToString();
                tbUsint1.Text = adsClient.ReadAny(husint1, typeof(Byte)).ToString();
                tbBool1.Text = adsClient.ReadAny(hbool1, typeof(Boolean)).ToString();
                tblreal1.Text = adsClient.ReadAny(hlreal1, typeof(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 int array.
                tbStr1.Text = adsClient.ReadAny(hstr1, typeof(String), new int[] {80}).ToString();
                tbStr2.Text = adsClient.ReadAny(hstr2, typeof(String), new int[] {5}).ToString();
                FillStructControls((ComplexStruct)adsClient.ReadAny(hcomplexStruct, typeof(ComplexStruct)));
            }
            catch(Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }
        
        private void btnWrite_Click(object sender, System.EventArgs e)
        {
            try
            {
                //write by handle
                //the second parameter is the object to be written to the PLC variable
                adsClient.WriteAny(hdint1, int.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 int[] {80});
                adsClient.WriteAny(hstr2, tbStr2.Text, new int[] {5});
                adsClient.WriteAny(hcomplexStruct, GetStructFromControls()) ;
            }
            catch(Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }
        
        private void btnAddNotifications_Click(object sender, System.EventArgs e)
        {
            notificationHandles.Clear();
            try
            {
                //register notification
                notificationHandles.Add(adsClient.AddDeviceNotificationEx("MAIN.dint1", AdsTransMode.OnChange, 100, 0, tbDint1, typeof(int)));
                notificationHandles.Add(adsClient.AddDeviceNotificationEx("MAIN.usint1", AdsTransMode.OnChange, 100, 0, tbUsint1, typeof(Byte)));
                notificationHandles.Add(adsClient.AddDeviceNotificationEx("MAIN.bool1", AdsTransMode.OnChange, 100, 0, tbBool1, typeof(Boolean)));
                notificationHandles.Add(adsClient.AddDeviceNotificationEx("MAIN.lreal1", AdsTransMode.OnChange, 100, 0, tblreal1, typeof(Double)));
                notificationHandles.Add(adsClient.AddDeviceNotificationEx("MAIN.str1", AdsTransMode.OnChange, 100, 0, tbStr1, typeof(String), new int[] {80}));
                notificationHandles.Add(adsClient.AddDeviceNotificationEx("MAIN.str2", AdsTransMode.OnChange, 100, 0, tbStr2, typeof(String), new int[] {5}));
                notificationHandles.Add(adsClient.AddDeviceNotificationEx("MAIN.complexStruct1", AdsTransMode.OnChange, 100, 0, tbDint1, typeof(ComplexStruct)));
            }
            catch(Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
            btnDeleteNotifications.Enabled = true;
            btnAddNotifications.Enabled = false;
        }
        
        private void btnDeleteNotifications_Click(object sender, System.EventArgs e)
        {
            //delete registered notifications.
            try
            {
                foreach(int handle in notificationHandles)
                adsClient.DeleteDeviceNotification(handle);
            }
            catch(Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
            notificationHandles.Clear();
            btnAddNotifications.Enabled = true;
            btnDeleteNotifications.Enabled = false;
        }
        
        private void adsClient_AdsNotificationEx(object sender, AdsNotificationExEventArgs e)
        {
            TextBox textBox = (TextBox)e.UserData;
            Type type = e.Value.GetType();
            if(type == typeof(string) || type.IsPrimitive)
                textBox.Text = e.Value.ToString();
            else if(type == typeof(ComplexStruct))
                FillStructControls((ComplexStruct)e.Value);
        }
        
        private void FillStructControls(ComplexStruct structure)
        {
            tbComplexStruct_IntVal.Text = structure.intVal.ToString();
            tbComplexStruct_dintArr.Text = String.Format("{0:d}, {1:d}, {2:d}, {3:d}", structure.dintArr[0],
            structure.dintArr[1], structure.dintArr[2], structure.dintArr[3]);
            tbComplexStruct_boolVal.Text = structure.boolVal.ToString();
            tbComplexStruct_ByteVal.Text = structure.byteVal.ToString();
            tbComplexStruct_stringVal.Text = structure.stringVal;
            tbComplexStruct_SimpleStruct1_lrealVal.Text = structure.simpleStruct1.lrealVal.ToString();
            tbComplexStruct_SimpleStruct_dintVal.Text = structure.simpleStruct1.dintVal1.ToString();
        }
        
        private ComplexStruct GetStructFromControls()
        {
            ComplexStruct structure = new ComplexStruct();
            String[] stringArr = tbComplexStruct_dintArr.Text.Split(new char[] {','});
            structure.intVal = short.Parse(tbComplexStruct_IntVal.Text);
            for(int i=0; i<stringArr.Length; i++)
            structure.dintArr[i] = int.Parse(stringArr[i]);
            
            structure.boolVal = Boolean.Parse(tbComplexStruct_boolVal.Text);
            structure.byteVal = Byte.Parse(tbComplexStruct_ByteVal.Text);
            structure.stringVal = tbComplexStruct_stringVal.Text;
            structure.simpleStruct1.dintVal1 = int.Parse(tbComplexStruct_SimpleStruct_dintVal.Text);
            structure.simpleStruct1.lrealVal = double.Parse(tbComplexStruct_SimpleStruct1_lrealVal.Text);
            return structure;
        }
    }
    
    // TwinCAT2 Pack = 1, TwinCAT3 Pack = 0
    [StructLayout(LayoutKind.Sequential, Pack=1)]
    public class SimpleStruct
    {
        public double lrealVal;
        public int dintVal1;
    }
    
    // TwinCAT2 Pack = 1, TwinCAT3 Pack = 0
    [StructLayout(LayoutKind.Sequential, Pack=1)]
    public class ComplexStruct
    {
        public short intVal;
        //specifies how .NET should marshal the array
        //SizeConst specifies the number of elements the array has.
        [MarshalAs(UnmanagedType.ByValArray, SizeConst=4)]
        public int[] dintArr = new int[4];
        [MarshalAs(UnmanagedType.I1)]
        public bool boolVal;
        public byte byteVal;
        //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 string stringVal = "";
        public SimpleStruct simpleStruct1 =new SimpleStruct();
    }
}

PLC program

TYPE TSimpleStruct :
STRUCT
    lrealVal: LREAL := 1.23;
    dintVal1: 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