Lesen und Schreiben von SPS Variablen eines beliebigen Typs (ReadAny, WriteAny)

Download

Voraussetzungen

Sprache / IDE

Beispielprogram auspacken

C# / Visual Studio

Sample07.zip

Aufgabe

Auslesen bzw. Beschreiben von Variablen eines beliebigen Typs mit Hilfe der Methode ReadAny bzw. WriteAny.

Beschreibung

ReadAny

In der Event-Methode btnRead_Click wird die Methode TcAdsClient.ReadAny verwendet um die Variablen per Handle auszulesen:


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

ReadAnyReadAny

Der Methode wird im Parameter type der Typ der zu lesenden Variable übergeben. Wenn die Methode erfolgreich gewesen ist, werden die gelesenen Daten als Objekt zurückgeliefert. Der Typ des Objektes entspricht dem Parameter type. Da bei einigen Datentypen(Arrays und Strings) zusätzliche Informationen notwendig sind gibt es eine Überladung der Methode ReadAny, die den zusätzlichen Parameter args enthält. Bei Strings z.B. müsste ein Integer-Array der Länge 1 übergeben werden. Eine vollständige Liste der unterstützten Typen finden Sie in der Dokumentation der überladenen Methode.

Beispiel:

Eine SPS-Variable vom Type ARRAY[0..3] OF DINT soll gelesen werden:


int hArr;
int[] arr;

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

...
adsClient.DeleteVariableHandle(hArr)

WriteAny

In der Event-Methode btnWrite_Click wird die Methode TcAdsClient.WriteAny verwendet um die Variablen per Handle zu beschreiben:


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

WriteAnyWriteAny

Der Parameter value enthält das Objekt, das in die SPS geschrieben werden soll. Eine vollständige Liste der unterstützten Typen von value finden Sie in der Dokumentation der überladenen Methode.

Beispiel:

Eine SPS-Variable vom Typ ARRAY[0..3] OF DINT soll beschrieben werden:


int hArr;
int[] arr = new int[]{1,2,3,4}

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

...
adsClient.DeleteVariableHandle(hArr)

Lesen und Schreiben von Strukturen:
( nicht möglich mit dem Compact Framework)

Beim Lesen und beschreiben von Strukturen und Klassen, muss darauf geachtet werden, dass das Speicherlayout der Struktur dem Layout in der SPS entspricht. Das Layout kann mit dem Attribut StructLayoutAttribute festgelegt werden. Dabei muss als LayoutKind LayoutKind.Sequential ausgewählt werden und Pack auf 1 gesetzt werden. Die Klasse SimpleStruct ist deshalb wie folgt definiert worden:

[StructLayout(LayoutKind.Sequential, Pack=0)] // TwinCAT2->Pack=1 TwinCAT3->Pack=0
public class SimpleStruct
{
public double lrealVal;
public int dintVal1;
}

Wenn Arrays, Strings oder boolsche Werte in einer Struktur/Klasse definiert sind, muss man festlegen, wie diese gemarshallt werden. Dies geschieht mit Hilfe des MarshalAs Attributs. Da Arrays und Strings unter .Net normalerweise keine feste Länge haben, muss mit dem Property SizeConst die Grösse des Arrays bzw. des Strings festgelegt werden. Das Marshallen von Mehrdimensionale Arrays oder Arrays von Strukturen ist mit dem Marhaler des .NET Frameworks 1.1 nicht möglich. Mehrdimensionale Arrays müssten auf eindimensionale Arrays abgebildet werden. Arrays von Strukturen müssten in der .NET Struktur aufgelöst werden.

In dem Beispiel wird das MarshalAsAttribute in der Klasse ComplexStruct verwendet:

[StructLayout(LayoutKind.Sequential, Pack=0)] // TwinCAT2->Pack=1 TwinCAT3->Pack=0
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();
}

Anmelden von ADS Notifications

In der Event-Methode btnAddNotifications_Click wird die Methode AddDeviceNotificationEx dazu verwendet, Notifications für die SPS Variable anzumelden. Bei Änderung des Wertes der Variable wird das Event AdsNotificationEx ausgelöst. Der Unterschied zu dem Event AdsNotifcation, besteht darin das der Wert einer Variablen in einem Objekt gespeichert ist, anstatt in einem AdsStream. Der Methode AddNotificationEx muss dazu der Typ dieses Objektes übergeben werden:


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

Als User-Objekt wurde hier die Textbox, die den Wert anzeigen soll, übergeben. Wenn das Event ausgelöst wird, wird die Event-Methode adsClient_AdsNotificationEx aufgerufen. Dazu wird das Event in Form_Load angemeldet:


adsClient.AdsNotificationEx+=new AdsNotificationExEventHandler(adsClient_AdsNotificationEx);

Lesen und Schreiben von SPS Variablen eines beliebigen Typs (ReadAny, WriteAny) 1:

C# Programm


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 - TwinCAT 3 Port=851
adsClient.Connect(851);
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, TwinCAT 3 Pack = 0
[StructLayout(LayoutKind.Sequential, Pack=0)]
public class SimpleStruct
{
public double lrealVal;
public int dintVal1;
}

// TwinCAT2 Pack = 1, TwinCAT3 Pack = 0
[StructLayout(LayoutKind.Sequential, Pack=0)]
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 Programm


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