W&T verbindet
Interfaces für TCP/IP, Ethernet, RS-232, RS-485, USB, 20mA, Glas- und Kunststoff-LWL, http, SNMP, OPC, Modbus TCP, I/O digital, I/O analog, ISA, PCI

Tutorial zum Web-IO Digital:

Web-IO Digital mit C# über
Binary-Sockets ansprechen


Für das Erstellen von Windows-Anwendungen war Visual C++ bis vor kurzem eine der meistgenutzten Entwicklungsplattformen. Inzwischen arbeiten immer mehr Programmierer mit dem .Net Framework und erstellen ihre Anwendungen in C# (C Sharp).

Mit C Sharp Web-IO steuern

Mit dem folgenden C#-Programmbeispiel können Sie Ihr Web-IO Digital mit seinen Inputs und Outputs in einer Windows-Anwendung über den Binary-Socket-Modus ansprechen.


Vorbereitungen


Zusammenstellen der verschiedenen Bedienelemente und Anzeigeobjekte im VB.net-Form

Beienelemente Visual Basic

Bei der Benennung der einzelnen Objekte ist es hilfreich, sinngebende Namen zu verwenden. In diesem Beipiel beschreibt der erste Teil des Namens die Art des Objektes und der zweite Teil die Funktion.

Importieren von Ressourcen und Deklaration von Membervariablen

Als erstes werden alle für die Netzwerkverbindung und die GUI(Graphical User Interface) benötigten Klassen importiert.


using System;
using System.Windows.Forms;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Runtime.InteropServices;
            

Anschließend werden die Komponenten der Applikation und wichtige Variablen für eine TCP-Verbindung als Membervariablen der Klasse deklariert und somit den Methoden der Klasse zugänglich gemacht.


  public class mainWindow : System.Windows.Forms.Form
  {	// All elements of the application
    private CheckBox cb_Output0;
    private CheckBox cb_Output1;
    private CheckBox cb_Input0;
    private CheckBox cb_Input1;
    private CheckBox cb_Polling_Counter;
    private CheckBox cb_Polling_Outputs;
    private CheckBox cb_Polling_Inputs;
    private Button bt_Readall_Outputs;
    private Button bt_Readall_Inputs;
    private Button bt_Clear_Counter0;
    private Button bt_Clear_Counter1;
    private Button bt_Clearall_Counter;
    private Button bt_Read_Counter0;
    private Button bt_Read_Counter1;
    private Button bt_Readall_Counter;
    private Label lb_Counter0;
    private Label lb_Counter1;
    private Label lb_Intervall;
    private TextBox tb_Counter0;
    private TextBox tb_Counter1;
    private TextBox tb_Intervall;
    private Button bt_Connect;
    private Button bt_Disconnect;
    private TextBox tb_Password;
    private TextBox tb_Port;
    private TextBox tb_IP;
    private GroupBox gb_ioControlBox;
    private GroupBox gb_conControlBox;
    private StatusBar statusBar;
    private Label label2;
    private Label label1;
    private Label label3;
    private System.Windows.Forms.Timer counter;
    private System.Windows.Forms.Timer outputs;
    private System.Windows.Forms.Timer inputs;

    private Socket client;
    private int intervall;
    private byte[] receivebuffer = new byte[256];
    private structIOState IOState;
    delegate void delegateSub();
						

Die Binär-Strukturen

Für den Binary-Zugriff müssen die benötigten Binär-Strukturen definiert werden, über die mit dem Web-IO kommuniziert werden soll. Eine detaillierte Beschreibung dieser Strukturen finden Sie in der Binary-Kurzübersicht oder im Programmierhandbuch zum Web-IO.

Die Struktur IOState

Die Struktur IOState gehört nicht zu den Binary-Strukturen und wird nicht unmittelbar für die Kommunikation zwischen Anwendung und Web-IO benötigt. Sie dient zum Austausch der IO-Zustände zwischen Empfangs-Thread und den Programmteilen, die für die Aktualisierung der Anzeigeelemente verantwortlich sind.

Die Struktur EADriver

Mit ihren vier 16-Bit Variablen ist EADriver die Grundstruktur, die auch Bestandteil aller weiteren Binary-Strukturen ist.


public struct structIOState
{
  public UInt16 InputState;
  public UInt16 OutputState;
  public UInt32 CounterValue0;
  public UInt32 CounterValue1;
}

public struct structEADriver
{
  public UInt16 Start_1;
  public UInt16 Start_2;
  public UInt16 StructType;
  public UInt16 StructLength;
}

public struct structOptions
{
  public structEADriver EADriver;
  public UInt32 Version;
  public UInt32 Options;
}

public struct structWriteRegister
{
  public structEADriver EADriver;
  public UInt16 Amount;
  public UInt16 Value;
}

public struct structSetBit
{
  public structEADriver EADriver;
  public UInt16 Mask;
  public UInt16 Value;
}

public struct structRegisterState
{
  public structEADriver EADriver;
  public UInt16 DriverID;
  public UInt16 InputValue;
  public UInt16 OutputValue;
}

public struct structReadCounter
{
  public structEADriver EADriver;
  public UInt16 CounterIndex;
}

[StructLayout(LayoutKind.Explicit)]
public struct structCounter
{
  [FieldOffset(0)]public structEADriver EADriver;
  [FieldOffset(8)] public UInt16 CounterIndex;
  [FieldOffset(10)] public UInt32 CounterValue;
}

[StructLayout(LayoutKind.Explicit)]
public struct structAllCounter
{
  [FieldOffset(0)] public structEADriver EADriver;
  [FieldOffset(8)] public UInt16 CounterNoOf;
  [FieldOffset(10)] public UInt32 CounterValue0;
  [FieldOffset(14)] public UInt32 CounterValue1;
}
						

Bei den Strukturen ist es wichtig, dass die Einzelvariablen bündig im Speicher hintereinander liegen. Das handhabt C# nicht automatisch so, insbesondere dann, wenn in einer Struktur Variablen unterschiedlicher Größe zusammengefasst sind. Um dennoch eine bündige Speicherbelegung zu gewährleisten, wird über [StructLayout(LayoutKind.Explicit)] festgelegt, dass jeder Einzelvariablen über [FieldOffset(14)] ein fester Offset zur ersten Speicherstelle der Struktur zugewiesen wird.

Programmstart

Einrichten der Bedienelemente

Die Gruppe mit den Bedienelementen für das Web-IO wird zunächst für die Bedienung gesperrt. Sobald eine Verbindung zustande kommt, werden alle Elemente freigeschaltet, welche eine sinnvolle Ausführung besitzen.

Der Name des jeweiligen Bedienelementes ist dem Kontext nach vom Element selbst abgeleitet. Die beiden ersten Zeichen im Namen stehen für den Typ des Elements (cb -> Checkbox, bt -> Button, gb -> Groupbox und tb -> TextBox).


public mainWindow()
{
  InitializeComponent();
  gb_ioControlBox.Enabled = false;
  bt_Disconnect.Enabled = false;
  cb_Input0.Enabled = false;
  cb_Input1.Enabled = false;
  tb_Counter0.Enabled = false;
  tb_Counter1.Enabled = false;
  IOState.InputState = 0;
  IOState.OutputState = 0;
  IOState.CounterValue0 = 0;
  IOState.CounterValue1 = 0;
}
						

Verbindungskontrolle

Einleiten der Verbindung

Durch Eingabe der IP-Adresse des Web-IO in das Textfeld ed_ip und Klick auf den Button bt_connect, wird der Verbindungsaufbau gestartet.


private void bt_Connect_Click(object sender, System.EventArgs e)
{
  try
  {
    if ((tb_IP.Text != "") && (tb_Port.Text != ""))
    {
      client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
      IPEndPoint ipep = new IPEndPoint(IPAddress.Parse(tb_IP.Text), int.Parse(tb_Port.Text));
      client.BeginConnect(ipep, new AsyncCallback(connectCallback), client);
    }
    else
      statusBar.Text = "IP and Port needed!";
  }
  catch (SocketException)
  {
    statusBar.Text = "Connection not possible!";
  }
}
						

Verbindungsaufbau

Für die Abwicklung des TCP/IP-Handlings wird zunächst ein IPEndPoint aus IP-Adresse und TCP-Port definiert und damit der TCP_client-Socket initialisiert. Im Zuge der Verbindungsanforderung wird ein Verweis auf eine Callback-Prozedur geschaffen.

Verbindung kommt zustande

Sobald das Web-IO die Verbindung annimmt, wird die Callback-Prozedur ausgeführt. Darüber hinaus wird ein Verweis auf eine Callback-Routine für den Datenempfang erstellt.

Über Senden der Struktur Options an das Web-IO wird dieses angewiesen, beim Setzen eines Outputs den veränderten Schaltzustand über die Struktur RegisterState zu übermitteln.


private void connectCallback(IAsyncResult ar)
{
  try
  {
    client = (Socket)ar.AsyncState;
    client.EndConnect(ar);
    connectupdatedisplay();
    intervall = 1000;
    client.BeginReceive(receivebuffer, 0, 255, SocketFlags.None, new AsyncCallback(receiveCallback), client);
  }
  catch (Exception)
  {
    disconnect();
  }
  if (client.Connected)
  {
    IntPtr BufPtr;
    structOptions Options;
    Options.EADriver.Start_1 = 0;
    Options.EADriver.Start_2 = 0;
    Options.EADriver.StructType = 0x1F0;
    Options.EADriver.StructLength = 16;
    Options.Version = 0;
    Options.Options = 1;
    BufPtr = Marshal.AllocHGlobal(Marshal.SizeOf(Options));
    Marshal.StructureToPtr(Options, BufPtr, true);
    sendstructure(BufPtr, (short)Options.EADriver.StructLength);
  }
}
						

Außerdem werden über Aufruf der Prozedur connectupdatedisplay() alle benötigten Bedienelemente der Applikation freigeschaltet und der Connect-Button deaktiviert.


 private void connectupdatedisplay()
{
  if (InvokeRequired)
  {
    BeginInvoke(new delegateSub(connectupdatedisplay), new object[] { });
    return;
  }
  statusBar.Text = "Connected!";
  gb_ioControlBox.Enabled = true;
  bt_Disconnect.Enabled = true;
  bt_Connect.Enabled = false;
}
						

In der aktuellen Version von C# ist es nicht mehr erlaubt, von einem Thread (hier die Callback-Funktion) auf grafische Elemente eines anderen Threads (Form) zuzugreifen.

Eine Lösung bietet hier die Benutzung von Delegaten und Invoke.

Trennen der Verbindung

Die Verbindung bleibt solange bestehen, bis sie vom Benutzer durch Klick auf den Disconnect-Button beendet wird oder das Web-IO die Verbindung beendet.


private void bt_Disconnect_Click(object sender, System.EventArgs e)
{
  try
  {
    client.Shutdown(SocketShutdown.Both);
    client.Close();
    disconnectupdatedisplay();
  }
  catch (Exception)
  {
    statusBar.Text = "Not able to disconnect!";
  }
}
            

In diesem Fall wird eine entsprechende Prozedur aufgerufen.


private void disconnect()
{
  try
  {
    client.Shutdown(SocketShutdown.Both);
    client.Close();
    disconnectupdatedisplay();
  }
  catch (Exception)
  {
    statusBar.Text = "Not able to disconnect!";
  }
}
            

Für einen Thread-sicheren Zugriff wurde das Zurücksetzen der Formular-Elemente in die Prozedur disconnectupdatedisplay() ausgelagert.


private void disconnectupdatedisplay()
{
  if (InvokeRequired)
  {
    BeginInvoke(new delegateSub(disconnectupdatedisplay), new object[] { });
    return;
  }
  statusBar.Text = "Disconnected!";
  gb_ioControlBox.Enabled = false;
  bt_Disconnect.Enabled = false;
  bt_Connect.Enabled = true;
}
            

Verbindungsfehler

Alle die TCP/IP-Kommunikation betreffenden Aktionen werden innerhalb der Try-Anweisung ausgeführt. Treten Fehler auf, wird die disconnect();-Methode des client-Objekts aufgerufen.

Bedienung und Kommunikation von Client-Seite

Senden von Binär-Strukturen

Sobald eine Verbindung mit dem Web-IO zustande gekommen ist, kann der Anwender durch Bedienung der entsprechenden Programmelemente Binär-Strukturen an das Web-IO senden.


private void sendstructure(IntPtr BufPtr, Int16 BufSize)
{
  byte[] senddata;
  senddata = new byte[BufSize];
  Marshal.Copy(BufPtr, senddata, 0, BufSize);
  client.BeginSend(senddata, 0, senddata.Length, 0, new AsyncCallback(sendCallback), client);
}

private void sendCallback(IAsyncResult ar)
{
  try
  {
    Socket tmp_client = (Socket) ar.AsyncState;
    int bytessend = tmp_client.EndSend(ar);
  }
  catch(Exception)
  {
    statusBar.Text = "Error by sending";
  }
}
            

C# sieht keine Methode zum direkten Versenden von Binärstrukturen vor. Binäre Daten können nur als ein Byte-Array gesendet werden. Deshalb wird beim Aufruf der sendstructure-Funktion ein Pointer (Zeiger) übergeben. Dieser Pointer verweist auf die Speicherstelle, ab der der Inhalt der zu versendenden Struktur zu finden ist. Hier wird deutlich, warum die Strukturvariablen Byte-bündig im Speicher liegen müssen. Als zweiter Parameter wird die Länge der Struktur übergeben.

Über Marshal.Copy(BufPtr, senddata, 0, Bufsize) werden die Strukturdaten dann in ein Byte-Array kopiert. Dieses Byte-Array wird dann an das Web-IO versendet.

Setzen der Outputs

Das Setzen der Outputs wird dem Anwender über zwei Checkboxen cb_outputx ermöglicht. Das Programm nutzt dazu das MouseUP-Ereignis dieses Objektes. Wird ein MouseUp, also ein Loslassen der Output-Checkbox, registriert, führt das Programm die entsprechende Prozedur aus und gibt - je nachdem, ob die Checkbox gesetzt ist oder nicht - die entsprechend ausgefüllte SetBit-Struktur an das Web-IO weiter.


private void cb_Output0_MouseUp(object sender, MouseEventArgs e)
{
  IntPtr BufPtr;
  structSetBit SetBit;
  SetBit.EADriver.Start_1 = 0;
  SetBit.EADriver.Start_2 = 0;
  SetBit.EADriver.StructType = 0x9;
  SetBit.EADriver.StructLength = 0xC;
  SetBit.Mask = 1;
  if (cb_Output0.Checked)
    SetBit.Value = 1;
  else
    SetBit.Value = 0;
  BufPtr = Marshal.AllocHGlobal(Marshal.SizeOf(SetBit));
  Marshal.StructureToPtr(SetBit, BufPtr, true);
  sendstructure(BufPtr, (short)SetBit.EADriver.StructLength);
}

private void cb_Output1_MouseUp(object sender, MouseEventArgs e)
{
  IntPtr BufPtr;
  structSetBit SetBit;
  SetBit.EADriver.Start_1 = 0;
  SetBit.EADriver.Start_2 = 0;
  SetBit.EADriver.StructType = 0x9;
  SetBit.EADriver.StructLength = 0xC;
  SetBit.Mask = 2;
  if (cb_Output1.Checked)
    SetBit.Value = 2;
  else
    SetBit.Value = 0;
  BufPtr = Marshal.AllocHGlobal(Marshal.SizeOf(SetBit));
  Marshal.StructureToPtr(SetBit, BufPtr, true);
  sendstructure(BufPtr, (short)SetBit.EADriver.StructLength);
}
            

Output/Input-Statusabfragen

Den Status der Outputs und Inputs kann der Anwender durch Anklicken des zugehörigen Buttons anfordern.


private void bt_Readall_Outputs_Click(object sender, System.EventArgs e)
{
  IntPtr BufPtr;
  structEADriver RegisterRequest;
  RegisterRequest.Start_1 = 0;
  RegisterRequest.Start_2 = 0;
  RegisterRequest.StructType = 0x21;
  RegisterRequest.StructLength = 8;
  BufPtr = Marshal.AllocHGlobal(Marshal.SizeOf(RegisterRequest));
  Marshal.StructureToPtr(RegisterRequest, BufPtr, true);
  sendstructure(BufPtr, (short)RegisterRequest.StructLength);
}
            

Über Senden der Struktur RegisterRequest werden die Schaltzustände von Inputs und Outputs angefordert. Das Web-IO beantwortet diese Anfrage mit der Struktur RegisterState.

Sollen nur die Inputs abgefragt werden, wir das durch Klicken auf den bt_inputs-Button ausgelöst. Hierzu wird die Struktur ReadRegister versendet, was das Web-IO mit der Struktur WriteRegister beantwortet.


private void bt_Readall_Inputs_Click(object sender, System.EventArgs e)
{
  IntPtr BufPtr;
  structEADriver ReadRegister;
  ReadRegister.Start_1 = 0;
  ReadRegister.Start_2 = 0;
  ReadRegister.StructType = 1;
  ReadRegister.StructLength = 8;
  BufPtr = Marshal.AllocHGlobal(Marshal.SizeOf(ReadRegister));
  Marshal.StructureToPtr(ReadRegister, BufPtr, true);
  sendstructure(BufPtr, (short)ReadRegister.StructLength);
}
            

Counter abfragen/löschen

Auch die Zählerstände der Input-Counter lassen sich abfragen bzw. löschen. Dazu wird die Struktur ReadCounter bzw. ReadClearCounter versendet, wobei mit CounterIndex die Nummer des Counters übergeben wird. Das Web-IO antwortet mit der Struktur Counter.


private void bt_Read_Counter0_Click(object sender, System.EventArgs e)
{
  IntPtr BufPtr;
  structReadCounter ReadCounter;
  ReadCounter.EADriver.Start_1 = 0;
  ReadCounter.EADriver.Start_2 = 0;
  ReadCounter.EADriver.StructType = 0xB0;
  ReadCounter.EADriver.StructLength = 0xA;
  ReadCounter.CounterIndex = 0;
  BufPtr = Marshal.AllocHGlobal(Marshal.SizeOf(ReadCounter));
  Marshal.StructureToPtr(ReadCounter, BufPtr, true);
  sendstructure(BufPtr, (short)ReadCounter.EADriver.StructLength);
}

private void bt_Read_Counter1_Click(object sender, System.EventArgs e)
{
  IntPtr BufPtr;
  structReadCounter ReadCounter;
  ReadCounter.EADriver.Start_1 = 0;
  ReadCounter.EADriver.Start_2 = 0;
  ReadCounter.EADriver.StructType = 0xB0;
  ReadCounter.EADriver.StructLength = 0xA;
  ReadCounter.CounterIndex = 1;
  BufPtr = Marshal.AllocHGlobal(Marshal.SizeOf(ReadCounter));
  Marshal.StructureToPtr(ReadCounter, BufPtr, true);
  sendstructure(BufPtr, (short)ReadCounter.EADriver.StructLength);
}

private void bt_Clear_Counter0_Click(object sender, System.EventArgs e)
{
  IntPtr BufPtr;
  structReadCounter ReadCounter;
  ReadCounter.EADriver.Start_1 = 0;
  ReadCounter.EADriver.Start_2 = 0;
  ReadCounter.EADriver.StructType = 0xC0;
  ReadCounter.EADriver.StructLength = 0xA;
  ReadCounter.CounterIndex = 0;
  BufPtr = Marshal.AllocHGlobal(Marshal.SizeOf(ReadCounter));
  Marshal.StructureToPtr(ReadCounter, BufPtr, true);
  sendstructure(BufPtr, (short)ReadCounter.EADriver.StructLength);
}

private void bt_Clear_Counter1_Click(object sender, System.EventArgs e)
{
  IntPtr BufPtr;
  structReadCounter ReadCounter;
  ReadCounter.EADriver.Start_1 = 0;
  ReadCounter.EADriver.Start_2 = 0;
  ReadCounter.EADriver.StructType = 0xC0;
  ReadCounter.EADriver.StructLength = 0xA;
  ReadCounter.CounterIndex = 1;
  BufPtr = Marshal.AllocHGlobal(Marshal.SizeOf(ReadCounter));
  Marshal.StructureToPtr(ReadCounter, BufPtr, true);
  sendstructure(BufPtr, (short)ReadCounter.EADriver.StructLength);
}
						

Über die Struktur ReadAllCounter bzw. ReadClearAllCounter lassen sich auch alle Counter zeitgleich lesen bzw. löschen. Das Web-IO antwortet mit der Struktur AllCounter.


private void bt_Readall_Counter_Click(object sender, System.EventArgs e)
{
  IntPtr BufPtr;
  structEADriver ReadAllCounter;
  ReadAllCounter.Start_1 = 0;
  ReadAllCounter.Start_2 = 0;
  ReadAllCounter.StructType = 0xB1;
  ReadAllCounter.StructLength = 8;
  BufPtr = Marshal.AllocHGlobal(Marshal.SizeOf(ReadAllCounter));
  Marshal.StructureToPtr(ReadAllCounter, BufPtr, true);
  sendstructure(BufPtr, (short)ReadAllCounter.StructLength);
}

private void bt_Clearall_Counter_Click(object sender, System.EventArgs e)
{
  IntPtr BufPtr;
  structEADriver ReadClearAllCounter;
  ReadClearAllCounter.Start_1 = 0;
  ReadClearAllCounter.Start_2 = 0;
  ReadClearAllCounter.StructType = 0xC1;
  ReadClearAllCounter.StructLength = 8;
  BufPtr = Marshal.AllocHGlobal(Marshal.SizeOf(ReadClearAllCounter));
  Marshal.StructureToPtr(ReadClearAllCounter, BufPtr, true);
  sendstructure(BufPtr, (short)ReadClearAllCounter.StructLength);
}
						

Datenempfang vom Web-IO

Auswerten und Anzeigen der empfangenen Daten

Das Web-IO sendet je nach Anfrage oder auslösendem Ereignis die passende Struktur zurück. Bei Datenempfang wird die entsprechende Callback-Prozedur aufgerufen. Zur Auswertung befüllen die ersten 8 Byte des empfangenen Byte-Array über eine Zeigeroperation zunächst eine EADriver-Struktur. Um welchen Strukturtyp es sich handelt, erkennt die Anwendung über die Variable EADriver.StructType.

  • EADriver.StructType = 8
    Struktur WriteRegister für den Status der Inputs

  • EADriver.StructType = 31 (hex.)
    Struktur RegisterState für den Status der Inputs und Outputs

  • EADriver.StructType = B4 (hex.)
    Struktur Counter für den Wert einzelner Counter

  • EADriver.StructType = B5 (hex.)
    Struktur AllCounter für den Wert aller Counter

Zur Auswertung befüllt das empfangene Byte-Array über eine Zeigeroperation nun die passende Struktur.

Die so übergebenen Werte werden dann ausgewertet und angezeigt. Für die Inputs und Outputs wird über WriteRegister.Value, RegisterStae.InputValue und RegisterStae.outputValue das Bit-Muster aller Inputs bzw. Outputs übergeben.


private void receiveCallback(IAsyncResult ar)
{
  int BytesRead;
  string rcv = string.Empty;
  try
  {
    BytesRead = client.EndReceive(ar);
    client.BeginReceive(receivebuffer, 0, 255, SocketFlags.None, new AsyncCallback(receiveCallback), client);
  }
  catch (Exception)
  {
    BytesRead = 0;
  }
  if (BytesRead == 0)
  {
    if (client.Connected) { disconnect(); }
  }
  else
  {
    GCHandle MyGC = GCHandle.Alloc(receivebuffer, GCHandleType.Pinned);
    structEADriver EADriver = (structEADriver)Marshal.PtrToStructure(MyGC.AddrOfPinnedObject(), typeof(structEADriver));
    switch (EADriver.StructType)
    {
      case 8:
        structWriteRegister WriteRegister = (structWriteRegister)Marshal.PtrToStructure(MyGC.AddrOfPinnedObject(), typeof(structWriteRegister));
        IOState.InputState = WriteRegister.Value;
      break;
      case 0x31:
        structRegisterState RegisterState = (structRegisterState)Marshal.PtrToStructure(MyGC.AddrOfPinnedObject(), typeof(structRegisterState));
        IOState.InputState = RegisterState.InputValue;
        IOState.OutputState = RegisterState.OutputValue;
      break;
      case 0xB4:
        structCounter Counter = (structCounter)Marshal.PtrToStructure(MyGC.AddrOfPinnedObject(), typeof(structCounter));
        if (Counter.CounterIndex == 0) { IOState.CounterValue0 = Counter.CounterValue; }
        if (Counter.CounterIndex == 1) { IOState.CounterValue1 = Counter.CounterValue; }
      break;
      case 0xB5:
        structAllCounter AllCounter = (structAllCounter)Marshal.PtrToStructure(MyGC.AddrOfPinnedObject(), typeof(structAllCounter));
        IOState.CounterValue0 = AllCounter.CounterValue0;
        IOState.CounterValue1 = AllCounter.CounterValue1;
      break;
    }
    IOupdatedisplay();
    MyGC.Free();
  }
}
						

Für eine Thread-sichere Aktualisierung der Anzeige wird das Prozessabbild der IOs über die Strukur IOState an die Prozedur IOupdatedisplay übergeben. Über Invoke und Delegate-Bildung werden die betreffenden Anzeigeelemente an den IO-Status angepasst.


private void IOupdatedisplay()
{
  if (InvokeRequired)
  {
    BeginInvoke(new delegateSub(IOupdatedisplay), new object[] { });
    return;
  }
  if ((IOState.OutputState & 1) == 1)
    cb_Output0.Checked = true;
  else
    cb_Output0.Checked = false;
  if ((IOState.OutputState & 2) == 2)
    cb_Output1.Checked = true;
  else
    cb_Output1.Checked = false;
  if ((IOState.InputState & 1) == 1)
    cb_Input0.Checked = true;
  else
    cb_Input0.Checked = false;
  if ((IOState.InputState & 2) == 2)
    cb_Input1.Checked = true;
  else
    cb_Input1.Checked = false;
    tb_Counter0.Text = IOState.CounterValue0.ToString();
    tb_Counter1.Text = IOState.CounterValue1.ToString();
}
            

Polling

Zyklisches Abfragen bestimmter Werte

Um auch eine automatische Aktualisierung der Anzeige zu ermöglichen, wird ein Timer benutzt.

In Abhängigkeit der Checkboxen für Output-, Input- und Counter-Polling werden die entsprechenden Informationen im eingestellten Intervall vom Web-IO abgerufen.


private void timer_handler(object sender, System.EventArgs e)
{
  if (sender == counter) bt_Readall_Counter_Click(sender, e);
  if (sender == outputs) bt_Readall_Outputs_Click(sender, e);
  if (sender == inputs) bt_Readall_Inputs_Click(sender, e);
}

private void cb_Polling_Counter_CheckedChanged(object sender, System.EventArgs e)
{
  if(cb_Polling_Counter.Checked)
  {
    counter = new System.Windows.Forms.Timer();
    counter.Interval = intervall;
    counter.Start();
    counter.Tick += new EventHandler(timer_handler);
  }
 else
   counter.Stop();
}

private void cb_Polling_Outputs_CheckedChanged(object sender, System.EventArgs e)
{
  if(cb_Polling_Outputs.Checked)
  {
    outputs = new System.Windows.Forms.Timer();
    outputs.Interval = intervall;
    outputs.Start();
    outputs.Tick += new EventHandler(timer_handler);
  }
  else
    outputs.Stop();
}

private void cb_Polling_Inputs_CheckedChanged(object sender, System.EventArgs e)
{
  if(cb_Polling_Inputs.Checked)
  {
    inputs = new System.Windows.Forms.Timer();
    inputs.Interval = intervall;
    inputs.Start();
    inputs.Tick += new EventHandler(timer_handler);
  }
  else
    inputs.Stop();
}
            

Das gewünschte Intervall kann in das entsprechende Textfeld eingegeben werden. Bei Änderung wird das Timer-Intervall dann automatisch angepasst.


private void tb_Intervall_TextChanged(object sender, System.EventArgs e)
{
  try
  {
    if(Convert.ToInt32(tb_Intervall.Text) > 0)
    {
      intervall = Convert.ToInt32(tb_Intervall.Text);
      statusBar.Text = "New range: " + intervall.ToString() + " ms!";
    }
    else
      statusBar.Text = "Only positiv Integer allowed!";
  }
  catch(Exception)
  {
    statusBar.Text = "Only positiv Integer allowed!";
  }
}
						

Das Beispielprogramm unterstützt alle gängigen Funktionen des Web-IO im Binary-Socket-Modus, optimiert für das Web-IO 2x Digital Input, 2x Digital Output. Für die anderen Web-IO-Modelle müssen ggf. Anpassungen am Programm vorgenommen werden. Eine detaillierte Beschreibung zu den Binär-Strukturen finden Sie in der Binary-Kurzübersicht oder im Programmierhandbuch zum Web-IO.

Programmbeispiel herunterladen


Produkte