C#-Script (ab V2.0.0.0)

PLC-Lab bietet die Möglichkeit, innerhalb eines C#-Scripts auf Operanden der Symboltabelle lesend und schreibend zuzugreifen. Dies erweitert die Simulationsmöglichkeiten von PLC-Lab enorm, da man mit Hilfe des C#-Codes, Signale der virtuellen Anlage auswerten und in veränderter Form an andere Operanden weitergeben kann.

Möchte man das Verhalten einer realen Anlage in PLC-Lab nachahmen, dann kann man dies nun mit Hilfe der physikalischen Möglichkeiten von PLC-Lab erreichen und zusätzlich algorithmisch, mit Hilfe der mächtigen C#-Programmiersprache.

Einsatzbeispiele im professionellen Umfeld

  • Im C#-Script kann das Verhalten von externen intelligenten Geräten programmiert werden, welche Signale aus der Anlage vorverarbeiten bzw. auswerten und dann die Ergebnisse als Eingangsinformationen an die SPS liefern. Beispiel sind intelligente Messeinheiten, intelligente Sensoren usw.. (Siehe Beispiele 5 und 6)
  • Im C#-Script kann man sehr einfach zeitliche Verzögerungen realisieren. Diese müssen dann nicht mehr mechanisch in der virtuellen Anlage umgesetzt werden. (Siehe Beispiele 3 und 7)
  • Im C#-Script ist das Erzeugen von Zufallszahlen sehr einfach möglich. (Siehe Beispiel 4)
  • C# bietet umfangreiche mathematische Funktionen.
  • Das Beeinflussen von Eingängen in Abhängigkeit anderer Eingänge ist sehr einfach realisierbar. So können z.B. digitale Eingangssignale ausgewertet und als Folge binäre Eingänge gesetzt werden oder umgekehrt. (Siehe Beispiel 8)
  • Teile einer virtuellen Anlage können komplett in C# gesteuert werden.
  • uvm.

Einsatzbeispiele im schulischen Umfeld

  • PLC-Lab kann nun nicht nur im Lehr-Umfeld der Steuerungstechnik, sondern auch in der Informatik zum Einsatz kommen. So ist es nun auch möglich, das Steuerungsprogramm einer virtuellen Anlage ausschließlich in C# zu erstellen. Dabei ist nur PLC-Lab notwendig, eine zusätzliche Software wie z.B. Visual Studio wird nicht benötigt!
  • Alle Einsatzbeispiele aus dem professionellen Umfeld sind natürlich auch für die Lehre interessant.

Verwendung eines C#-Scripts in PLC-Lab

Der Einsatz eines C#-Scripts in PLC-Lab ist sehr einfach. Es sind nur wenige Schritte notwendig, um ein C#-Programm in die virtuelle Anlage von PLC-Lab einzubinden.

  1. Öffnen eines vorhandenen oder erzeugen eines neuen Projekts in PLC-Lab.

  2. Aktivieren des C#-Scripts im C#-Tab

  3. In der Symboltabelle die Symbole selektieren, auf welche lesend im C#-Script zugegriffen werden soll.

  4. In der Symboltabelle die Symbole selektieren, auf welche schreibend im C#-Script zugegriffen werden soll.

  5. Durch die Schritte 3 und 4 wurden die Lese- und Schreibfunktionen im C#-Script definiert. Diese können somit im C#-Programm verwendet werden:

  6. Damit kann das C#-Programm in der Loop-Funktion erstellt werden. Die Loop-Funktion wird zyklisch aufgerufen, sobald die Simulation gestartet wird. Diese stellt also ähnlich dem OB1 in STEP7-Programmen, die Wurzel des Programms dar.

Welche Operanden sollten im C#-Script gelesen bzw. beschrieben werden

Im C#-Script gelten die gleichen Gesetzmäßigkeiten was das Lesen und Beschreiben von Operanden angeht, wie in der virtuellen Anlage von PLC-Lab. Normalerweise werden Eingänge von PLC-Lab beschrieben und Ausgänge gelesen. Besitzt ein Device den Operandenbereich Merker oder DB, dann können diese sowohl gelesen als auch beschrieben werden.

Beschreiben von Ausgängen im C#-Script

Das Beschreiben von Ausgängen ist nur bei Ausgängen des Debug-Device sinnvoll, denn die Ausgänge werden bei den anderen Devices nicht auf das Gerät geschrieben. Dies bedeutet, ein beschriebener Ausgang wird beim nächsten Lesen der Ausgänge aus der SPS wieder überschrieben.

Im C#-Script sollte somit nur lesend auf Ausgänge zugegriffen werden.

Möchte man allerdings eine virtuelle Anlage ausschließlich mit Hilfe des C#-Scripts steuern, dann kann man die Operanden dem Debug-Device zuordnen und dann ist auch das Schreiben von Ausgängen im C#-Script sinnvoll.

Lesen und schreiben von Eingängen, Merkern und Datenbaustein-Daten

Wie schon erwähnt, kann sowohl der Lese- als auch der Schreibzugriff auf Eingänge im C#-Script sinnvoll sein. Denn das C#-Script ist ideal dafür, Signale zu verformen. So kann z.B. der Status eines Eingangsbits dazu verwendet werden, ein Eingangswort mit verschiedenen Zahlenwerten zu beschreiben. Dies wird auch in einigen nachfolgenden Beispielen gezeigt.

Gleiches gilt für Merker oder Daten aus Datenbausteine, falls diese im verwendeten Device vorhanden sind.

Sicherheitsaspekte bei der Verwendung eines C#-Scripts

C# ist eine mächtige Programmiersprache. Somit sind die Möglichkeiten innerhalb des C#-Scripts sehr groß, was auch negativ genutzt werden kann.

Aus diesem Grund wurden in PLC-Lab einige Mechanismen eingebaut, um das Risiko durch C#-Scripte in PLC-Lab zu minimieren.

Sicherheitsabfrage vor der Ausführung eines C#-Scripts

Ist in einem Anlagenprojekt von PLC-Lab ein Script enthalten, dann erfolgt beim ersten Start der Simulation (also bei Betätigung von Run) eine Sicherheitsabfrage.

Wird diese mit "Nein" beantwortet, dann wird das Script deaktiviert. Bei Betätigung des Ja-Buttons wird das Script ausgeführt.

Important

Sie sollten nur Scripte in Projekten ausführen lassen, deren Quelle Sie vertrauen.

Die Ausführung von C#-Scripten dauerhaft verbieten

Sollen auf einem PC generell keine C#-Scripte in PLC-Lab-Projekten ausgeführt werden, dann erreicht man dies mit Hilfe der Datei "csharpscriptlock.lck". Sobald diese Datei im Installationsverzeichnis von PLC-Lab vorhanden ist (der Inhalt ist irrelevant), wird die Ausführung des C#-Scripts verhindert. Beim Start der Simulation erscheint dann folgende Meldung:

Danach wird das Script deaktiviert und die Simulation ohne das C#-Script gestartet. Beim nächsten Start erscheint keine Meldung mehr, außer das C#-Script wurde wieder aktiviert.

Funktionelle Einschränkungen im C#-Script

Das C#-Script wurde aus Sicherheitsgründen in seinen Möglichkeiten eingeschränkt. Dies bedeutet, es wird der Zugriff auf potentiell gefährliche Funktionen unterbunden. Wird im Script eine solche Funktion verwendet, dann erscheint beim Prüfen des Codes oder beim Start der Simulation folgende Meldung:

Die genaue Ursache kann dann dem Output-Fenster entnommen werden.

Im Beispiel wurde eine Funktion aus dem Namespace "System.IO" verwendet. Dies ist im C#-Script von PLC-Lab nicht erlaubt.

Important

Trotz aller Vorsichtsmaßnahmen kann eine missbräuchliche Verwendung der C#-Scripte nicht zu 100% verhindert werden.

Bedienelemente im Tab "C# Script"

Im folgenden Bild ist das Tab "C# Script" dargestellt:

Nachfolgend werden die einzelnen Elemente erläutert.

Element Beschreibung
1 Script aktivieren: Nur wenn diese Option selektiert ist, wird das C#-Script ausgeführt und kann editiert werden. Ist die Option nicht selektiert, dann wird der Codebereich grau dargestellt und ist nicht editierbar.
2 Script überprüfen: Bei Betätigung des Buttons wird das Script überprüft und compiliert. Etwaige Meldungen und Fehler werden im Output-Fenster (8) dargestellt.
3 Script formatieren: Über diesen Button kann der Code ausgerichtet und formatiert werden.
4 Output löschen: Bei Betätigung werden die Texte im Output-Fenster gelöscht.
5 Hilfe: Der Button ruft die C#-Script-Hilfeseite im Wiki von PLC-Lab auf, sofern eine Internetverbindung vorhanden ist.
6 Codebereich: Hier kann der C#-Code programmiert werden. Der C#-Editor verfügt über Intellisense um den Code komfortable eingeben zu können.
7 Kontextmenü des C#-Editors: Über die rechte Maustaste kann das Kontextmenü des C#-Editors aufgerufen werden. Das Menü bietet verschiedene Funktionen, welche beim Editieren zur Verfügung stehen.
8 Output-Fenster: In diesem Fenster werden die Compiler-Meldungen, Fehler-Meldungen, Erfolgs-Meldungen und Debug-Meldungen ausgegeben. Das Löschen der Meldungen wird über den Button "Output löschen" (4) ausgelöst.
9 Quick find und Quick replace: Wird zum Suchen und zum Suchen/Ersetzen innerhalb des Codebereichs verwendet. Über "Find options" können zusätzliche Einstellungen für die Suche und das Ersetzen selektiert werden.

Lese- und Schreibfunktionen für Symbole im C#-Script definieren

Bevor man auf Symbole lesend oder schreibend im C#-Script zugreifen kann, müssen deren Lese- oder Schreibfunktionen im C#-Script definiert werden. Für ein Symbol kann sowohl eine Lese- als auch eine Schreibfunktion definiert werden, falls man beide Zugriffsarten benötigt.

Lesefunktionen für Symbole definieren

Am komfortabelsten können die Lesefunktionen aus der Symboltabelle heraus definiert werden. Voraussetzung ist, dass das C#-Script im Projekt aktiviert ist.

Ist die Aktivierung vorhanden, dann wechselt man zur Symboltabelle. Danach werden die Symbole in der Tabelle selektiert, für welche man jeweils eine Lesefunktion definieren möchte.

Hat man die gewünschten Symbole selektiert, dann wird der Button "Lesefunktionen für selektierte Symbole erzeugen" betätigt.

Anschließend erscheint eine Meldung in der Statuszeile, welche die Anzahl der erzeugten Funktionen benennt.

Im Beispiel wurden 9 Lesefunktionen erzeugt.

Wo werden die Lesefunktionen der Symbole im C#-Script definiert?

Innerhalb des C#-Scripts wurden die Funktionen hinter dem Schlüsselwort "//{{{PLC-Lab-Read}}}" eingefügt.

Important

Das Schlüsselwort "//{{{PLC-Lab-Read}}}" darf nicht entfernt werden!

Wie sind die Lesefunktionen der Symbole aufgebaut?

In der obigen Darstellung sind die zuvor erzeugten Lesefunktionen zu sehen. So hat die Funktion für das Symbol "S1ControlOn" folgendes Aussehen:

bool Get_PLCSimS1ControlOn() { return G.Read("PLCSim.S1ControlOn"); }

Der Rückgabewert der Funktion ist vom Typ "bool" da es sich um einen Bitoperanden handelt. Der Name der Funktion wurde mit "Get_PLCSimS1ControlOn" festgelegt. Dieser Name kann nach belieben geändert werden.

Nicht verändert werden darf die Angabe "{ return G.Read("PLCSim.S1ControlOn"); }"!

Denn "PLCSim.S1ControlOn" definiert das Symbol, von welchem der Rückgabewert abgefragt wird. Die Bezeichnung setzt sich aus dem Namen des Device und dem symbolischen Namen des Operanden zusammen. Wird der Symbolname innerhalb der Symboltabelle geändert, dann korrigiert PLC-Lab die Zugriffsbezeichnungen im C#-Script automatisch. Gleiches gilt, wenn das Device des Symbols geändert wird.

Für das Symbol "B4Cyl1AbsPos" wurde folgende Funktion erzeugt:

float Get_PLCSimB4Cyl1AbsPos() { return G.ReadDigital("PLCSim.B4Cyl1AbsPos"); }

Hier ist der Rückgabewert vom Typ "float", da es sich um einen digitalen Operanden handelt. Alle digitalen Operanden besitzen den gleichen Rückgabewert vom Typ "float". Im C#-Code kann dann über eine explizite Typenkonvertierung der gewünschte Datentyp erzeugt werden.

Beispiel:

Das Symbol "B4Cyl1AbsPos" besitzt den Datentyp UInt16. Deshalb soll der Rückgabewert der Lesefunktion einer C#-Variablen vom Typ UInt16 übergeben werden. Dazu wird eine explizite Typenkonvertierung durchgeführt. Der C#-Code hat folgendes Aussehen:

UInt16 cylPos = (UInt16)Get_PLCSimB4Cyl1AbsPos();

Der Rückgabewert der Lesefunktion wird explizit von float zu UInt16 gewandelt. Dies ist möglich, da sichergestellt ist, dass die Lesefunktion einen ganzzahligen Wert liefert, welcher in UInt16 gewandelt werden kann.

Schreibfunktionen für Symbole definieren

Am komfortabelsten können die Schreibfunktionen aus der Symboltabelle heraus definiert werden. Voraussetzung ist, dass das C#-Script im Projekt aktiviert ist.

Ist die Aktivierung vorhanden, dann wechselt man zur Symboltabelle. Danach werden die Symbole in der Tabelle selektiert, für welche man jeweils eine Schreibfunktion definieren möchte.

Hat man die gewünschten Symbole selektiert, dann wird der Button "Schreibfunktionen für selektierte Symbole erzeugen" betätigt.

Anschließend erscheint eine Meldung in der Statuszeile, welche die Anzahl der erzeugten Funktionen benennt.

Im Beispiel wurden 2 Schreibfunktionen erzeugt.

Wo werden die Schreibfunktionen der Symbole im C#-Script definiert?

Innerhalb des C#-Scripts wurden die Funktionen hinter dem Schlüsselwort "//{{{PLC-Lab-Write}}}" eingefügt.

Important

Das Schlüsselwort "//{{{PLC-Lab-Write}}}" darf nicht entfernt werden!

In der obigen Darstellung sind die zuvor erzeugten Schreibfunktionen zu sehen. So hat die Funktion für das Symbol "B4Cyl1LoadPos2" folgendes Aussehen:

void Set_PLCSimB4Cyl1LoadPos2(bool value) { G.Write("PLCSim.B4Cyl1LoadPos2", value); }

Der Übergabewert der Funktion ist vom Typ "bool" da es sich um einen Bitoperanden handelt. Der Name der Funktion wurde mit "Set_PLCSimB4Cyl1LoadPos2" festgelegt. Dieser Name kann nach belieben geändert werden.

Nicht verändert werden darf die Angabe " { G.Write("PLCSim.B4Cyl1LoadPos2", value); }"!

Denn "PLCSim.B4Cyl1LoadPos2" definiert das Symbol, welches beschrieben wird. Die Bezeichnung setzt sich aus dem Namen des Device und dem symbolischen Namen des Operanden zusammen. Wird der Symbolname innerhalb der Symboltabelle geändert, dann korrigiert PLC-Lab die Zugriffsbezeichnungen im C#-Script automatisch. Gleiches gilt, wenn das Device des Symbols geändert wird.

Wir nehmen an, dass zusätzlich ein Symbol mit der Bezeichnung "Cyl1Velocity" angelegt und eine Schreibfunktion definiert wurde. Der Datentyp des Symbols ist auf UInt16 eingestellt. Die für das Symbol definierte Schreibfunktion hat folgendes Aussehen:

void Set_PLCSimCyl1Velocity(float value) { G.WriteDigital("PLCSim.Cyl1Velocity", value); }

Hier ist der Übergabewert vom Typ "float", da es sich um einen digitalen Operanden handelt. Alle digitalen Operanden besitzen den gleichen Übergabewert vom Typ "float". Besitz ein Symbol einen anderen Datentyp, dann kann auch ein Wert bzw. eine Variable mit diesem Datentyp an die Funktion übergeben werden. Intern wird dann der float-Wert entsprechend gewandelt.

Beispiel:

Das Symbol "Cyl1Velocity" besitzt den Datentyp UInt16. Deshalb soll der Schreibfunktion ein Wert vom Typ UInt16 übergeben werden. Der C#-Code hat folgendes Aussehen:

Set_PLCSimCyl1Velocity(MyUInt16Value);

Die Variable "MyUInt16Value" besitzt den Datentyp UInt16 und kann direkt in der Funktion übergeben. Intern wird dann der UInt16-Wert an das Symbol übergeben.

Fehlersuche im C#-Script über Textausgabe

Das klassische Debuggen im Einzeschritt ist in PLC-Lab nicht möglich. Für die Fehlersuche muss man sich mit einer Textausgabe im Output-Fenster behelfen. Eine solche Textausgabe wird im C#-Script mit Hilfe der Funktion "G.DebugMessage" realisiert. Diese Funktion wird beim Erzeugen eines neuen Projekts bereits im C#-Script verwendet.

//write a message to the output window
if (G.LoopCount == 0) {
    G.DebugMessage("FIRST LOOP!");
}

Dabei wird beim ersten Zyklus der Loop-Funktion der Text "FIRST LOOP!" im Output-Fenster ausgegeben.

Beispiel zur Textausgabe im C#-Script

In einer virtuellen Anlage kann ein Tank manuell befüllt und entleert werden. Sobald der Füllstand des Tanks über 2000 Inkrementen liegt, soll im Output-Fenster des C#-Scripts eine Meldung erscheinen, wobei der momentane Füllstand ebenfalls auszugeben ist. Ändert sich der Füllstand abermals und befindet sich dieser weiterhin über 2000, dann sollen weitere Meldungen erfolgen.

Im C#-Script wird zunächst eine Klassenvariable angelegt, welche den Füllstand des letzten Zyklus speichern soll. Damit kann eine Änderung erfasst werden.

UInt16 LastLevel=0;

Nachfolgend der C#-Code des Beispiels innerhalb der Loop-Funktion:

//note the fill level as a UInt16 value
UInt16 level = (UInt16) Get_DebugLevel();
//is the level > 2000 and is there a change?
if (level > 2000 && LastLevel != level){
    G.DebugMessage(string.Format("Level > 2000, value: {0}", level));
    LastLevel=level;
}

Zunächst wird der aktuelle Füllstand über die Funktion "Get_DebugLevel()" gelesen und über eine explizite Typenkonvertierung an eine UInt16-Variable übergeben. Dann folgt eine if-Entscheidung, welche überprüft, ob der Füllstand über 2000 liegt und sich der Füllstand seit dem letzten Zyklus geändert hat. Ist dies der Fall, dann erfolgt die Textausgabe im Output-Fenster und der letzte angezeigte Füllstand wird in "LastLevel" abgespeichert.

Nach dem Start der Simulation und der Erhöhung des Füllstands über 2000, zeigt sich folgendes Bild:

Tipps und Regeln für das C# Script

  • Das Script besteht aus einer Klasse "PlcLabScript", die beliebig mit Methoden/Variablen/Klassen erweitert werden kann.
  • Das C#-Script besteht immer aus einer Datei.
  • Das C#-Skript startet immer mit der Methode "Loop()". Diese Methode wird zyklisch aufgerufen, sobald sich die PLC-Lab Anlage im RUN-Modus befindet. Sie können beliebige eigene Klassenmethoden (Funktionen) hinzufügen und diese dann aufrufen.
  • Programmieren Sie keine Schleifen, in denen Sie auf ein Ereignis warten. Dies würde zu einer Fehlermeldung führen. Da das Skript immer wieder zyklisch aufgerufen wird, ist dies nicht notwendig.
  • Programmieren Sie keine zeitliche Pausen im Script. Dies würde zu einer Fehlermeldung führen.
  • Wenn Sie im C#-Script eine Hi-Flanke für einen bestimmten Operanden erzeugen wollen, dann müssen Sie dies in mehreren Zyklen programmieren. Sonst kann PLC-Lab die Flanke nicht erkennen. Verwenden Sie z.B. "G.LoopCount". Dies ist eine Integer-Variable, die bei jedem Zyklus inkrementiert wird. Damit können Sie die nacheinander-folgenden Zyklen erkennen.

Der integrierte C# Editor

Das C#-Script wird im integrierten C#-Editor von PLC-Lab erstellt. Dieser Editor unterstützt den Programmierer auf vielfältige Weise bei der Programmeingabe.

Intellisense

Eine sehr hilfreiche Funktion ist das Intellisense, welches über die Tastenkombination [STRG] + [Space] aufgerufen werden kann. Nach Betätigung werden die an der momentanen Cursorstelle möglichen Eingaben vorgeschlagen. Nachfolgend ist eine Situation zu sehen, bei der die vorhandenen Funktionen und Felder der Klasse "DateTime" aufgelistet werden:

Man wählt nun aus der Liste die gewünschte Funktion aus und erhält darüber hinaus auch eine Hilfe zur momentanen Auswahl.

Hilfe zu Funktionen, Variablen usw.

Platziert man den Mauszeiger im C#-Editor über einer Klasse, einer Funktion oder einer Variablen, dann erscheint eine Hilfe zu dem Objekt. Nachfolgend ist eine solche Hilfemeldung zu sehen:

Besonderheit beim Beschreiben und Lesen von Symbolen

Wird ein Symbol über eine Schreibfunktion im C#-Script beschrieben, dann kann man nicht davon ausgehen, dass bei einer Abfrage des Symbols im gleichen Zyklus der geschriebene Wert schon enthalten ist. Es kann unter Umständen mehrere Zyklen dauern, bis der Wert im Symbol übernommen wurde.

Beispiel:

Set_DebugB1Sensor(true);
//
if (Get_DebugB1Sensor()){
    //do something
}

Für das obige Beispiel bedeutet dies, die if-Entscheidung ist unter Umständen erst in einer der folgenden Zyklen erfüllt.

Ist es für das obige Beispiel notwendig, dass die if-Entscheidung sofort erfüllt ist, dann sollte anstatt des Symbols eine Klassenvariable beschrieben werden. Folgender Code innerhalb der Klasse "PlcLabScript" wäre dafür geeignet:

//
bool B1Sensor=false;

/// <summary>
/// This method is called cyclically
/// </summary>
public void Loop() {
    //
    B1Sensor=true;
    //
    if (B1Sensor){
        //do something
    }
    //Write the status into the symbol
    Set_DebugB1Sensor(B1Sensor);
}

Die Klassenvariable "B1Sensor" wird im C#-Code als Stellvertreter für das Symbol verwendet. Am Ende der Loop-Funktion wird dann der Status der Klassenvariable in das Symbol geschrieben.

Im Beispiel 9 "Taktgenerator" kommt diese Möglichkeit zur Anwendung. Hier wird die Klassenvariable "PulseStatus" im C#-Code beschrieben bzw. gelesen und am Ende der Loop-Funktion wird der Status in das Symbol geschrieben ("Set_DebugPulse(PulseStatus);")

Beispiele zu C#-Scripten

Nachfolgend nun einige Beispiele, wie man das C#-Script innerhalb von PLC-Lab einsetzen kann.

Die Beispielanlagen befinden sich in den eigenen Dokumenten des Benutzers, im Verzeichnis "PlcLab-Editor\Examples\Mhj-Wiki-Examples".

Beispiel 1: Relais (ScriptHelpExample_01)

Es soll das Verhalten eines Relais mit drei Öffner- und drei Schließerkontakten im C#-Script programmiert werden.

Die Spule des Relais ist am Symbol "RelaisA1" angeschlossen. Die Öffner und Schließer sind mit Eingängen der SPS verbunden. Die Schließerkontakte haben die Symbolbezeichnungen "NoContact1" bis "NoContact3", die Öffnerkontakte sind mit "NcContact1" bis "NcContact3" bezeichnet.

Im ersten Schritt wird das C#-Script aktiviert.

Anschließend wechselt man zur Symboltabelle, um die Lese- und Schreibfunktionnen für die Symbole zu definieren.

Der Status des Symbols "RelaisA1" muss im Script gelesen werden. Deshalb wird das Symbol in der Symboltabelle selektiert und der Button zum Erzeugen einer Lesefunktion betätigt.

Die Symbole der Schließer- und Öffnerkontakte müssen alle beschrieben werden. Somit werden diese alle selektiert und der Button zum Erzeugen der Schreibfunktion betätigt.

Als Folge werden sechs Schreibfunktionen im C#-Script definiert.

Wechselt man zum C#-Script dann finden sich im unteren Bereich des Codes, die zuvor definierten Schreibfunktionen und die Lesefunktion.

Über diese kann nun auf die Symbole lesend bzw. schreiben zugegriffen werden.

Der C#-Code ist innerhalb der Loop-Funktion zu schreiben, diese wird zyklisch aufgerufen, vergleichbar mit dem OB1 in den S7-Steuerungen. Der Code für das Beispiel ist recht einfach, er besteht zunächst aus einer if-Entscheidung. Dabei wird der Status des Symbols "RelaisA1" über dessen Lesefunktion Get_DebugRelaisA1() abgefragt. Liefert diese Abfrage den Wert true, dann ist die if-Entscheidung erfüllt und es müssen alle Schließerkontakte auf den Status 1 (true) gesetzt werden. Die Öffnerkontakte erhalten den Status 0 (false).

//Relay has energized
if (Get_DebugRelaisA1()){
    Set_DebugNoContact1(true);
    Set_DebugNoContact2(true);
    Set_DebugNoContact3(true);
    Set_DebugNcContact1(false);
    Set_DebugNcContact2(false);
    Set_DebugNcContact3(false);
}

Danach folgt der else-Zweig, welcher beim Status 0 des "RelaisA1" bearbeitet wird. Hier ist das Ganze umgekehrt, dies bedeutet, die Öffnerkontakte werden auf true und die Schließerkontakte auf false gesetzt.

else {
    Set_DebugNoContact1(false);
    Set_DebugNoContact2(false);
    Set_DebugNoContact3(false);
    Set_DebugNcContact1(true);
    Set_DebugNcContact2(true);
    Set_DebugNcContact3(true);
}

Nachfolgend ist die gesamte Loop-Funktion zu sehen:

Damit ist das C#-Script komplett und die Simulation kann gestartet werden. Beim ersten Start der Simulation nach dem Öffnen des Projekts, folgt zunächst eine Sicherheitsafrage, ob das C#-Script bearbeitet werden soll. Diese Frage ist mit "Ja" zu beantworten. Danach startet die Simulation und das Script wird bearbeitet.

Die Spule des Relais kann über einen Schalter mit Spannung versorgt werden. Die Schließer- und Öffnerkontakte werden jeweils über Lampen symbolisiert. Befindet sich das Relais im Ruhezustand, dann sind die Öffnerkontakte (NC1 bis NC3) geschlossen und deren Lampen leuchten. Sobald der Schalter betätigt und somit das Relais mit Spannung versorgt wird, leuchten nur noch die Lampen der Schließerkontakte (NO 1 bis NO 3) und die Lampen der Öffnerkontakte sind dunkel.

Beispiel 2: Rotation steuern (ScriptHelpExample_02)

Ein Objekt ist drehbar gelagert. Die Drehung kann über eine pos. Flanke am Symbol "StartRotate" gestartet werden. Die Drehbewegung soll stoppen, sobald das Objekt um 90° rotiert wurde. Diese Funktionalität ist im C#-Script zu implementieren.

Im ersten Schritt wird das C#-Script aktiviert.

Anschließend wechselt man zur Symboltabelle, um die Lese- und Schreibfunktionnen für die Symbole zu definieren.

Im Script muss lesend auf die beiden Symbole "StartRotate" und "RotationPos" zugegriffen werden. Somit werden diese in der Symboltabelle selektiert und der Button zur Implementierung der Lesefunktionen betätigt.

Auf das Symbol "Rotate" ist schreibend zuzugreifen. Das Symbol wird selektiert und der Button zur Implementierung der Schreib-Funktion betätigt.

Im Script werden zunächst die beiden Klassenvariablen "LastValueStart" und "LastPos" definiert. Die Variable "LastValueStart" wird zum Erkennen der pos. Flanke des Start-Symbols benötigt. Über die Variablen "LastPos" wird erkannt, ob die Rotation momentan noch stattfindet.

bool LastValueStart=false;  
int LastPos=-1;

Das C#-Programm ist innerhalb der Loop-Funktion zu programmieren. Diese Funktion wird bei der Simulation zyklisch aufgerufen ist also ähnlich dem OB1 bei S7-Programmen.

Innerhalb der Loop-Funktion wird die aktuelle Position der Drehbewegung (0-360) in einer Integer-Variablen mit der Bezeichnung "pos" abgelegt. Dabei ist der float-Rückgabewert der Funktion Get_IMRotationPos() explizit in int zu wandeln. Dies ist möglich, da nur Werte zwischen 0 bis 360 geliefert werden können.

int pos = (int)Get_IMRotationPos();

In der folgenden if-Entscheidung wird geprüft ob eine Bewegung vorhanden ist (pos != LastPos) und sich das rotierende Objekt in einer Position befindet, welche ein Vielfaches von 90° ist. In diesem Fall ist die Rotation zu beenden.

//stop rotation?
if (LastPos >= 0 && pos != LastPos && pos % 90 == 0){
    Set_IMRotate(false);
}

Danach folgt die if-Entscheidung zum Start der Rotation. Hier wird die pos. Flanke des Start-Symbols erkannt und die Rotation gestartet.

//pos edge starting the rotation?
if (Get_IMStartRotate() && !LastValueStart){
    Set_IMRotate(true);
}

Zuletzt werden die Werte gespeichert um diese im nächsten Zyklus mit den neuen Werten vergleichen zu können.

//note the last values
LastValueStart=Get_IMStartRotate();
LastPos = pos;

Hier nun der gesamte Code der Loop-Funktion:

Damit ist das C#-Script komplett und die Simulation kann gestartet werden. Beim ersten Start der Simulation nach dem Öffnen des Projekts, folgt zunächst eine Sicherheitsafrage, ob das C#-Script bearbeitet werden soll. Diese Frage ist mit "Ja" zu beantworten. Danach startet die Simulation und das Script wird bearbeitet.

Wird der Taster "Start" betätigt, dann rotiert der Pfeil, bis dieser die nächste 90°-Marke erreicht hat. Ein erneutes Betätigen des Tasters startet den Vorgang erneut.

Beispiel 3: Einschalt- und Ausschaltverzögerung (ScriptHelpExample_03)

Nach dem Wechsel des Status eines Eingangs A auf 1, soll nach x Sekunden ein weiterer Eingang B auf 1 wechseln. Der Wechsel findet nicht statt, wenn Eingang A in dieser Zeit wieder auf 0 wechselt. Geht A nach der Einschaltverzögerung auf 0, dann soll B nach x Sekunden Verzögerung ebenfalls auf 0 gehen. Wechselt A in dieser Zeit wieder auf 1 dann wird die Ausschaltverzögerung gestoppt und B behält ebenfalls den Status 1. Die beiden Zeiten sollen von 1 bis 5 Sekunden einstellbar sein.

Die beschriebene Funktionalität ist über ein C#-Script zu realisieren.

Im ersten Schritt wird das C#-Script aktiviert.

Anschließend wechselt man zur Symboltabelle, um die Lese- und Schreibfunktionnen für die Symbole zu definieren.

Alle Symbole müssen lesend im C#-Script verarbeitet werden. Somit werden diese in der Symboltabelle selektiert und der Button zum Erzeugen der Lesefunktionen betätigt.

Das Symbol "InputB" ist darüber hinaus schreibend zu beeinflussen. Um die Schreibfunktion im Script einzufügen, wird das Symbol ausgewählt und der Button für das Erzeugen der Schreibfunktion betätigt.

Damit sind alle notwendigen Zugriffsfunktionen der Symbole vorhanden und es kann mit der Programmierung begonnen werden.

Im ersten Schritt werden die notwendigen Klassenvariablen erzeugt. Die Variable "LastValueInputA" wird zur Erkennung einer pos. Flanke des "InputA" benötigt. In dieser wird der Status aus dem letzten Zyklus abgelegt. Die beiden Variablen "OnDelayActive" und "OffDelayActive" sind true, sobald die Einschalt- bzw. die Ausschaltverzögerung gestartet wurde. Die jeweils eingestellten Zeiten für die Einschalt- und Ausschaltverzögerung sollen zu dem Zeitpunkt übernommen werden, wenn der Status des Symbols "InputA" eine pos. Flanke aufweist. Die Werte werden dann in den Variablen "OnDelaySelection" und "OffDelaySelection" gespeichert, damit spätere Änderungen keine Einfluss mehr haben. Zuletzt die beiden DateTime-Variablen mit den Start- und Stopzeiten des "InputA".

bool LastValueInputA=false;
bool OnDelayActive=false;
bool OffDelayActive=false;
int OnDelaySelection=0;
int OffDelaySelection=0;
DateTime StartInputA;
DateTime StopInputA;

Das eigentliche C#-Programm wird in der zyklisch aufgerufenen Loop-Funktion programmiert.

Die erste if-Entscheidung ist true, sobald eine pos. Flanke des "InputA" erkannt wird. Die Abfrage des Status von "InputA" erfolgt über die Lesefunktion "Get_DebugInputA()". Wurde die Flanke erkannt, dann wird der Zeitpunkt in "StartInputA" abgespeichert. Gleiches gilt für die eingestellten Zeiten für die Einschalt- und Ausschaltverzögerung, deren Wert ist in die dafür vorgesehenen Variablen zu schreiben. Zuletzt wird die Variable "OnDelayActive" auf true gesetzt.

//pos. edge InputA
if (Get_DebugInputA() && !LastValueInputA){
    StartInputA = DateTime.Now;
    OnDelaySelection = (int)Get_DebugSwitchOnDelay();
    OffDelaySelection = (int)Get_DebugSwitchOffDelay();
    OnDelayActive=true;
}

In der nachfolgenden if-Entscheidung wird geprüft, ob "InputA" während der laufenden Einschaltverzögerung wieder auf den Status 0 zurück geht. Ist dies der Fall, dann wird "OnDelayActive" rückgesetzt.

//InputA switch to 0 while on delay is active
if (!Get_DebugInputA() && OnDelayActive){
    OnDelayActive=false;
}

Nun folgt der Abschnitt, in dem überprüft wird, ob die Einschaltverzögerung abgelaufen ist. Diese Prüfung findet nur statt, wenn die Variable "OnDelayActive" den Wert true besitzt. Ist die Zeit abgelaufen, dann wird "InputB" mit Hilfe der Schreibfunktion "Set_DebugInputB" auf true gesetzt. Damit ist der Einschaltvorgang abgeschlossen und "OnDelayActive" wird rückgesetzt.

//on delay elapsed?
if (OnDelayActive){
    TimeSpan delay = DateTime.Now - StartInputA;
    if (delay.Seconds >= OnDelaySelection){
        Set_DebugInputB(true);
        OnDelayActive=false;
    }
}

Besitzen sowohl "InputA" als auch "InputB" den Status 1 und wechselt nun "InputA" auf den Status 0, dann wird dies in der nachfolgenden if-Entscheidung erkannt. Als Folge ist der Zeitpunkt in der Variablen "StopInputA" abzulegen und "OffDelayActive" auf true zu setzen.

//InputA switch to 0 while InputB is 1: off delay starts 
if (!OffDelayActive && Get_DebugInputB() && !Get_DebugInputA()){
    StopInputA = DateTime.Now;
    OffDelayActive=true;
}

Wechselt "InputA" auf 1, während die Ausschaltverzögerung läuft, dann soll der Vorgang gestoppt werden und "InputB" behält den Status 1.

//InputA switch to 1 while off delay
if (OffDelayActive && Get_DebugInputA()){
    OffDelayActive=false;
}

Jetzt folgt der Code-Abschnitt, in dem die Auschaltverzögerung überprüft wird. Ist die Zeit abgelaufen, dann ist "InputB" auf 0 zu setzen. Des Weiteren ist der Vorgang der Ausschaltverzögerung beendet und "OffDelayActive" erhält den Wert 0.

//off delay elapsed?
if (OffDelayActive){
    TimeSpan delay = DateTime.Now - StopInputA;
    if (delay.Seconds >= OffDelaySelection){
        Set_DebugInputB(false);
        OffDelayActive=false;
    }
}

Zuletzt wird der Status von "InputA" in "LastValueInputA" abgespeichert, damit die Flanken erkannt werden können.

//note the status
LastValueInputA = Get_DebugInputA();

Damit ist das C#-Programm komplett. Nachfolgend der gesamte Code in der Loop-Funktion:

Beim ersten Start der Simulation nach dem Öffnen des Projekts, folgt zunächst eine Sicherheitsafrage, ob das C#-Script bearbeitet werden soll. Diese Frage ist mit "Ja" zu beantworten. Danach startet die Simulation und das Script wird ausgeführt.

Die Einschalt- und Ausschaltverzögerung kann mit Hilfe von Slidern im Bereich von 1 bis 5 Sekunden selektiert werden. Betätigt man den Schalter "InputA" dann läuft die Einschaltverzögerung und nach x Sekunden leuchtet "InputB", als Zeichen dafür, dass dieser den Status 1 besitzt.

Wird nun der Status von "InputA" auf 0 geändert, dann beginnt die Ausschaltverzögerung zu laufen. Nach x Sekunden erhält auch "InputB" den Status 0.

Auf diese Weise können die einzelnen Bereiche der Aufgabenstellung getestet werden.

Beispiel 4: Über Zufallszahl verschiedene Objekte erzeugen (ScriptHelpExample_04)

Ein Band ist mit 6 verschiedenen Objekten zu versorgen. Das Erzeugen eines Objekts wird über einen Taster initiiert. Welches Objekt erzeugt wird, soll über eine Zufallszahl im Bereich 0 bis 5 entschieden werden. Diese Zufallszahl ist im C#-Script zu ermitteln.

In der virtuellen Anlage werden zunächst 6 verschiedene Objekte angelegt und zur Unterscheidung mit verschiedenen Farben versehen. Jedes Objekt erhält eine Mutter-Objekt-ID (von 1 bis 6), welche dann an dem jeweiligen Creator-Objekt anzugeben ist. Somit sind 6 Creator zu erzeugen mit den IDs 1 bis 6.

Damit die zu erzeugenden Objekte übereinander gelegt werden können, wird bei diesen Mutter-Objekten die Option "Neutral bei Kollision" ausgewählt. Die Kind-Objekte sollen diese Option allerdings nicht besitzen, aus diesem Grund wird ebenso die Option "Neutral bei Kollision nicht kopieren" selektiert.

Mit diesen Optionen können die Mutter-Objekte wie folgt angeordnet werden:

Diese liegen nun alle übereinander und versorgen das Förderband an der gleichen Position.

Im nächsten Schritt ist das C#-Script zu aktivieren.

In der Symboltabelle werden die Symbole "CreateObj1" bis "CreateObj6" angelegt, diese sind alle dem IM-Device zugeordnet und haben den Datentyp bool. Die Symbole werden an den Creator-Objekten 1 bis 6 als Create-Trigger Operand angegeben. Zuletzt wird das bool-Symbol "CreateObjX" benötigt, welches von einem Taster zu beeinflussen ist.

Nun sind die Lese- und Schreibfunktionnen für die Symbole zu definieren. Die Symbole "CreateObj1" bis "CreateObj6" müssen im C#-Script beschrieben werden, weshalb man diese selektiert und den Button zum Erzeugen der Schreibfunktionen betätigt.

Für das Symbol "CreateObjX" ist der Lesezugriff ausreichend, somit wird das Symbol selektiert und der Button für das Erzeugen einer Lesefunktion im C#-Script betätigt.

Nun wechselt man zum C# Script. Hier sind zunächst zwei Klassenvariablen zu definieren. Eine Variable um den letzten Status des Symbols "CreateObjX" zu speichern. Diese wird zum Erkennen der pos. Flanke benötigt. Die zweite Variable hält eine Instanz der Random-Klasse, welche zum Erzeugen der Zufallszahlen benötigt wird.

bool LastValueObjX=false;
Random Rnd = new Random();

Nun folgt das eigentliche C#-Programm in der zyklisch aufgerufenen Loop-Funktion. Zunächst werden alle Symbole "CreateObj1" bis "CreateObj6" auf den Wert 0 gesetzt, damit beim späteren Setzen auf jeden Fall eine pos. Flanke für den jeweiligen Creator erzeugt wird.

//set all to 0
Set_IMCreateObj1(false);
Set_IMCreateObj2(false);
Set_IMCreateObj3(false);
Set_IMCreateObj4(false);
Set_IMCreateObj5(false);
Set_IMCreateObj6(false);

Anschließend ist eine if-Entscheidung zu programmieren, in welcher die pos. Flanke von "CreateObjX" abgefragt wird. Wird eine Flanke erkannt, dann folgt ein switch-case, bei der die Random-Instanz eine Zahl von 0 bis 5 liefert. Je nach gelieferter Zahl, wird dann eines der Symbole "CreateObj1" bis "CreateObj6" auf den Status 1 gesetzt. Dies triggert dann den jeweiligen Creator innerhalb der virtuellen Anlage und ein bestimmtes Objekt wird erzeugt.

//pos edge?
if (Get_IMCreateObjX() && !LastValueObjX){
    switch (Rnd.Next(6)){
            case 0: Set_IMCreateObj1(true); break;
            case 1: Set_IMCreateObj2(true); break;
            case 2: Set_IMCreateObj3(true); break;
            case 3: Set_IMCreateObj4(true); break;
            case 4: Set_IMCreateObj5(true); break;
            case 5: Set_IMCreateObj6(true); break;
    }
}

Zuletzt noch die Zeile, mit welcher der Status von "CreateObjX" in der Variablen "LastValueObjX" abgelegt wird.

//note the status
LastValueObjX=Get_IMCreateObjX();

Hier nun der gesamte Code innerhalb der Loop-Funktion:

Beim ersten Start der Simulation nach dem Öffnen des Projekts, folgt zunächst eine Sicherheitsafrage, ob das C#-Script bearbeitet werden soll. Diese Frage ist mit "Ja" zu beantworten. Danach startet die Simulation und das Script wird ausgeführt.

Bei Betätigung des Tasters "CreateObjX" wird eine Zufallszahl im Bereich 0 bis 5 generiert und je nach Zahl eine pos. Flanke für einen Creator erzeugt. Somit wird das Band bei jedem Tastendruck mit zufälligen Kind-Objekten versorgt.

Beispiel 5: Schwellenwertschalter für Tankanlage (ScriptHelpExample_05)

Es soll der Schwellenwertschalter einer Tankanlage mit Hilfe des C#-Scripts realisiert werden. Der Schwellenwert liegt bei 3000 Inkrementen und die Hysterese ist im Bereich von 500 bis 1000 einstellbar.

Ist der Schwellenwert erreicht, dann wird dies der SPS über den Eingang mit dem Symbol "ThresholdValueSwitch" signalisiert.

Im ersten Schritt ist das C#-Script zu aktivieren.

Danach wechselt man zur Symboltabelle, um die Lese- und Schreibfunktionnen für die Symbole zu definieren.

Die Symbole "Hysteresis" und "Level" werden im C#-Script gelesen, somit sind die entsprechenden Lese-Funktionen zu implementieren. Dazu werden die Symbole in der Symboltabelle selektiert und der Button zum Implementieren der Lese-Funktionen betätigt.

Das Symbol "ThresholdValueSwitch" muss sowohl gelesen als auch beschrieben werden. Somit wird das Symbol ausgewählt und durch Betätigung der Buttons für die Lese- und Schreibfunktion die Funktionen im Script definiert.

Damit sind alle notwendigen Lese- und Schreibfunktionen definiert.

Das C#-Programm wird in der Loop-Funktion programmiert, diese Funktion wird während der Simulation zyklisch aufgerufen. Die Funktion ist somit vergleichbar mit dem OB1 bei S7-Programmen.

Der Füllstand und die Hysterese sind beides ganzzahlige Werte, somit können diese in Integer-Variablen abgelegt werden. Dazu wird der Wert der Lese-Funktionen explizit in int gewandelt.

int fillLevel=(int)Get_DebugLevel();
int hysteresis=(int)Get_DebugHysteresis();

Die gewünschte Funktionalität ist relativ einfach mit Hilfe von zwei if-Entscheidungen zu realisieren. Zunächst wird geprüft, ob der Schwellenwert erreicht ist, sofern der Schwellenwertschalter noch nicht eingeschalten ist.

if (!Get_DebugThresholdValueSwitch() && fillLevel >= 3000){
    Set_DebugThresholdValueSwitch(true);
}

Mit der zweiten if-Entscheidung wird bei gesetztem Schwellenwertschalter kontrolliert, ob der Wert abzüglich der Hysterese unterschritten ist.

if (Get_DebugThresholdValueSwitch() && fillLevel <= 3000 - hysteresis){
    Set_DebugThresholdValueSwitch(false);
}

Damit ist das C#-Programm komplett, anbei der gesamte Code der Loop-Funktion:

Beim ersten Start der Simulation nach dem Öffnen des Projekts, folgt zunächst eine Sicherheitsafrage, ob das C#-Script bearbeitet werden soll. Diese Frage ist mit "Ja" zu beantworten. Danach startet die Simulation und das Script wird ausgeführt.

Der Tank kann über den Taster "Supply" gefüllt werden. Hat der Füllstand den Level mit 3000 Inkrementen erreicht, dann reagiert der Schwellenwertschalter und die Lampe leuchtet. Die Hysterese ist auf 500 Inkremente eingestellt. Nun wird über den Taster "Drain" der Füllstand des Tanks verringert. Hat der Füllstand den Stand mit 2500 Inkrementen erreicht, dann wird die Lampe dunkel, da die untere Schwelle erreicht wurde. Anschließend wird die Hysterese auf 1000 eingestellt und der Vorgang wiederholt. Hier erfolgt die Abschaltung erst bei einem unteren Wert von 2000 Inkrementen.

Beispiel 6: Füllstandswarnung einer Tankanlage (ScriptHelpExample_06)

Der Füllstand eines Behälters wird über ein Zusatzgerät ausgewertet. Das Ergebnis der Auswertung wird mit Hilfe von zwei Eingängen an die SPS gemeldet.

Das Zusatzgerät hat folgende Funktion: Sobald der Füllstand des Behälters den Wert 3000 überschritten hat, wird dies am Eingang "ThresholdExceeded" gemeldet. Wird der Füllstand für 5 Sekunden oder länger überschritten, dann soll zusätzlich der Status des Eingangs "FillLevelWarning" auf 1 wechseln. Unterschreitet der Füllstand den Wert 3000, dann wechselt der Status der beiden Eingänge auf 0.

Die Funktion des Zusatzgeräts ist mit Hilfe eines C#-Scripts zu realisieren.

Im ersten Schritt ist das C#-Script zu aktivieren.

Danach wechselt man zur Symboltabelle, um die Lese- und Schreibfunktionnen für die Symbole zu definieren.

Die Symbole "Level" und "ThresholdExceeded" müssen im Script gelesen werden, weshalb von diesen Lesefunktionen zu implmentieren sind.

Beim Symbol "ThresholdExceeded" ist auch ein Schreibzugriff notwendig ebenso wie für das Symbol "FillLevelWarning". Von diesen beiden sind somit Schreibfunktionen einzufügen.

Im Script ist zunächst die Klassenvariable "dateTimeLevelExceed" anzulegen. Diese soll den Zeitpunkt speichern, sobald der Füllstand den Schwellenwert überschritten hat.

DateTime DateTimeLevelExceed;

Über eine if-Entscheidung wird das Überschreiten des Schwellenwerts erkannt, das Symbol "ThresholdExceeded" gesetzt und der Zeitpunkt in "DateTimeLevelExceed" abgelegt.

//Note the time if the level exceeds the threshold          
if (!Get_DebugThresholdExceeded() && fillLevel >= 3000){
    DateTimeLevelExceed=DateTime.Now;
    Set_DebugThresholdExceeded(true);
}

Solange der Schwellenwert überschritten ist, wird die Zeit des Überschreitens kontrolliert. Nach 5 Sekunden ist das Symbol "FillLevelWarning" zu setzen.

//Has the threshold been exceeded for 5 seconds?
if (Get_DebugThresholdExceeded()){
    TimeSpan timeSpan = DateTime.Now-DateTimeLevelExceed;
    if (timeSpan.Seconds >= 5){
        Set_DebugFillLevelWarning(true);    
    }
}

Das Rücksetzen der Symbole "ThresholdExceeded" und "FillLevelWarning" wird durchgeführt, sobald der Füllstand unter die Marke 3000 gefallen ist.

//Has the value fallen below the threshold?
if (fillLevel < 3000){
    Set_DebugThresholdExceeded(false);
    Set_DebugFillLevelWarning(false);   
}

Nachfolgend der gesamte Code in der Loop-Funktion:

Beim ersten Start der Simulation nach dem Öffnen des Projekts, folgt zunächst eine Sicherheitsafrage, ob das C#-Script bearbeitet werden soll. Diese Frage ist mit "Ja" zu beantworten. Danach startet die Simulation und das Script wird ausgeführt.

Über den Taster "Supply" wird der Zulauf des Tanks aktiviert und somit der Füllstand erhöht. Hat dieser den Stand von 3000 Inkrementen erreicht, dann schaltet sich "Threshold exceeded" ein und die Lampe leuchtet. Bleibt der Füllstand für mindestens 5 Sekunden über diesem Schwellenwert, dann leuchtet auch die Lampe "Fill level warning". Sinkt der Füllstand unter 3000, dann werden beide Lampen dunkel. Lässt man den Füllstand kurzzeitig über den Schwellenwert ansteigen und innerhalb von 5 Sekunden wieder absinken, dann bleibt "Fill level warning" dunkel.

Beispiel 7: Wert eines digitalen Operanden zeitgetriggert erhöhen oder verringern (ScriptHelpExample_07)

Nach Betätigung des Tasters "Increase" soll der Wert in der Wort-Variablen "Value" schrittweise um 100 Einheiten erhöht werden. Die Erhöhung soll dabei im 2 Sekunden-Takt erfolgen. Die Erhöhung ist zu stoppen, sobald der Stop-Taster oder der Taster "Decrease" betätigt werden oder aber der max. Wert 1000 erreicht ist.

Wird der Taster "Decrease" betätigt, dann ist der Wert in "Value" im 2 Sekunden-Takt zu verringern. Dieser Vorgang wird gestoppt, sobald der Stop-Taster oder der Taster "Increase" betätigt werden, bzw. der Wert 0 erreicht ist.

Im ersten Schritt ist das C#-Script zu aktivieren.

Danach wechselt man zur Symboltabelle, um die Lese- und Schreibfunktionnen für die Symbole zu definieren.

Die Taster "Increase", "Decrease" und "Stop" müssen im Script abgefragt werden, weshalb Lese-Funktionen der entsprechenden Symbole zu definieren sind.

Das Symbol "Value" ist sowohl zu lesen als auch schreibend zu beeinflussen, deshalb wird sowohl ein Lese- als auch ein Schreibzugriff definiert.

Im Script wird eine Klassenvariable vom Typ DateTime angelegt, welche den Zeitpunkt der letzten Erhöhung des Werts speichert. Des Weiteren ist eine Klassenvariable zum Erkennen der pos. Flanke des Increase-Tasters notwendig und eine Klassenvariable, welche den Start des Increase-Vorgangs speichert.

DateTime DateTimeIncrease;
bool LastStartIncreaseValue = false;
bool IncreaseStarted = false;

Die gleichen Klassenvariablen sind auch für den Decrease-Vorgang notwendig.

DateTime DateTimeDecrease;
bool LastStartDecreaseValue = false;
bool DecreaseStarted = false;

Nun das eigentliche C#-Programm innerhalb der beim Start der Simulation zyklisch aufgerufenen Loop-Funktion. Beim Erkennen der pos. Flanke des Increase-Tasters wird der Zeitpunkt gespeichert und die Variable "IncreaseStarted" auf true gesetzt. Gleichzeitig ist ein etwaiger decrease-Vorgang zu beenden, indem die Variable "DecreaseStarted" auf false gesetzt wird.

//Pos edge start increase
if (Get_DebugStartIncreaseValue() && !LastStartIncreaseValue) {
    DateTimeIncrease = DateTime.Now;
    IncreaseStarted = true;
    DecreaseStarted = false;
}

Ist der increase-Vorgang gestartet, dann wird die vergangene Zeit geprüft. Sind zwei Sekunden vergangen und ist der max. Wert noch nicht erreicht, dann wird "Value" um den Wert 100 erhöht und der neue Zeitpunkt abgespeichert. Sobald der max. Wert von größer 900 erreicht ist, wird der increase-Vorgang gestoppt.

//Increase: 2 seconds passed
if (IncreaseStarted) {
    TimeSpan timeSpan = DateTime.Now - DateTimeIncrease;
    if (timeSpan.Seconds >= 2 && (int) Get_DebugValue() <= 900) {
        Set_DebugValue((int) Get_DebugValue() + 100);
        DateTimeIncrease = DateTime.Now;
    }
    if ((int) Get_DebugValue() > 900){
        IncreaseStarted = false;
    }
}

In ähnlicher Weise ist auch der decrease-Vorgang zu implementiert. Hier ist entsprechend der minimale Wert zu prüfen.

//Pos edge start decrease
if (Get_DebugStartDecreaseValue() && !LastStartDecreaseValue) {
    DateTimeDecrease = DateTime.Now;
    DecreaseStarted = true;
    IncreaseStarted = false;
}
//Decrease: 2 seconds passed
if (DecreaseStarted) {
    TimeSpan timeSpan = DateTime.Now - DateTimeDecrease;
    if (timeSpan.Seconds >= 2 && (int) Get_DebugValue() >= 100) {
        Set_DebugValue((int) Get_DebugValue() - 100);
        DateTimeDecrease = DateTime.Now;
    }
    if ((int) Get_DebugValue() < 100){
        DecreaseStarted = false;
    }
}

Fehlt noch das Stoppen beider Vorgänge und das Abspeichern der Zustände für die Flankenerkennung.

//Stop?
if (Get_DebugStop()){
    IncreaseStarted = false;
    DecreaseStarted = false;
}
//Note the last status
LastStartIncreaseValue = Get_DebugStartIncreaseValue();
LastStartDecreaseValue = Get_DebugStartDecreaseValue();

Damit ist das C#-Programm vollständig. Beim ersten Start der Simulation nach dem Öffnen des Projekts, folgt zunächst eine Sicherheitsafrage, ob das C#-Script bearbeitet werden soll. Diese Frage ist mit "Ja" zu beantworten. Danach startet die Simulation und das Script wird ausgeführt.

Nach Betätigung des Tasters "Increase" wird der Wert im 2 Sekunden-Takt erhöht. Betätigt man danach den Taster "Decrease", dann verringert sich der Wert entsprechend. Der Taster "Stop" stoppt das Ganze und der momentane Wert wird beibehalten.

Beispiel 8: Position eines horizontalen Schlittens über Wahlschalter selektieren (ScriptHelpExample_08)

Über einen Wahlschalter ist die Position eines horizontalen Schlittens selektierbar. Der Wahlschalter besitzt die Positionen 0 bis 4. Über einen Start-Taster kann die Positionierung gestartet werden. Folgende Positionen (Inkremente) sind dabei anzufahren:

  • Schalterstellung 0: Pos. 10
  • Schalterstellung 1: Pos. 100
  • Schalterstellung 2: Pos. 210
  • Schalterstellung 3: Pos. 435
  • Schalterstellung 4: Pos. 190

Die Positionierung des Schlittens wird mit Hilfe der internen Positionierregelung von linearen Bewegungen in PLC-Lab durchgeführt.

Im ersten Schritt ist das C#-Script zu aktivieren.

Danach wechselt man zur Symboltabelle, um die Lese- und Schreibfunktionnen für die Symbole zu definieren.

Die Einstellung der anzufahrenden Positions-Inkremente wird im C#-Script vorgenommen. Da die Auswahl der neuen Position nur beim Betätigen des Start-Tasters erfolgen soll, ist ein Lesezugriff des Symbols "Start" notwendig. Ebenso muss die momentane Schalterstellung des Wahlschalters im Script abgefragt werden (Symbol "PosSelection").

Die anzufahrende Position wird in das Symbol "PositionSpecification" geschrieben, somit ist für das Symbol ein Schreibzugriff notwendig.

Im Script wird die Klassen-Variable "StartLastStatus" angelegt, diese speichert den letzten Status des Start-Tasters. Nur bei einer pos. Flanke soll der Wahlschalter ausgewertet und eine neue Ziel-Position übernommen werden.

bool StartLastStatus=false;

Wird eine pos. Flanke erkannt, dann erfolgt die Auswertung mit Hilfe von switch-case.

//Get the selected position if start was pressed
if (Get_DebugStart() && !StartLastStatus) {
    switch ((int)Get_DebugPosSelection()){
            case 0: Set_DebugPositionSpecification(10); break;
            case 1: Set_DebugPositionSpecification(100); break;
            case 2: Set_DebugPositionSpecification(210); break;
            case 4: Set_DebugPositionSpecification(435); break;
            case 8: Set_DebugPositionSpecification(190); break;
    }
}

Zuletzt ist der Status von "Start" in der Variablen "StartLastStatus" zu speichern.

StartLastStatus=Get_DebugStart();

Damit ist das C#-Programm vollständig. Beim ersten Start der Simulation nach dem Öffnen des Projekts, folgt zunächst eine Sicherheitsafrage, ob das C#-Script bearbeitet werden soll. Diese Frage ist mit "Ja" zu beantworten. Danach startet die Simulation und das Script wird ausgeführt.

Über den Wahlschalter kann die gewünschte Position selektiert werden. Anschließend betätigt man den Start-Taster um den Schlitten an diese Position positionieren zu lassen.

Beispiel 9: Taktgenerator für Step-Betrieb eines linearen Antriebs (ScriptHelpExample_09)

Im Beispiel soll ein Taktgenerator im C#-Script entwickelt werden. Die Impulsdauer des Taktgenerators ist im Bereich von 200 ms bis 2000 ms einstellbar. Des Weiteren kann die Anzahl der High-Impulse im Bereich 5 bis 20 selektiert werden. Gestartet wird der Taktgenerator über eine pos. Flanke am Symbol "StartPulse", diese wird mit Hilfe eines Tasters erzeugt. Die Takte am Symbol "Pulse" werden dazu genutzt, eine lineare Bewegung im Step-Betrieb zu betreiben. Dabei ist die Richtung und die Schrittweite einstellbar.

Im ersten Schritt ist das C#-Script zu aktivieren.

Danach wechselt man zur Symboltabelle, um die Lese- und Schreibfunktionnen für die Symbole zu definieren.

Zunächst die Symbole, welche lesend im C#-Script bearbeitet werden.

Für das Symbol "Pulse" ist auch eine Schreibfunktion notwendig , somit wird dieses selektiert und der entsprechende Button betätigt.

Im C#-Script sind einige Klassenvariablen notwendig. Nachfolgend sind diese zu sehen:

UInt16 ImpulsePauseTimeMs=0;
UInt16 PulseNumber=0;
DateTime StartPulseHigh;
DateTime StartPulseLow;
bool LastStartPulse;
UInt16 NumberHighPulses=0;
bool PulseGeneratorActive=false;
bool PulseStatus=false;

Wird der Taktgenerator gestartet, dann soll die eingestellte Anzahl der High-Impulse und die Impulsdauer übernommen werden. Läuft der Taktgenerator, dann soll eine Änderung dieser Parameter keine Auswirkungen mehr haben. Aus diesem Grund werden die Werte beim Start des Taktgenerators in den Klassenvariablen "ImpulsePauseTimeMs" und "PulseNumber" gespeichert.

Die weiteren Variablen werden benötigt, um den Startzeitpunkt des High-Impulses bzw. Low-Impulses zu speichern, die Anzahl der High-Impulse im laufenden Taktgenerator-Zyklus usw.. Diese kommen dann im weiteren Verlauf noch zum Einsatz.

Die erste if-Entscheidung prüft den Start des Taktgenerators. Dazu ist eine pos. Flanke des Symbols "StartPulse" notwendig. Diese wird erkannt, wenn das Symbol den Status 1 besitzt und der Status im letzten Zyklus noch 0 war. Des Weiteren darf der Taktgenerator noch nicht gestartet sein.

//Start pulse?
if (Get_DebugStartPulse() && !LastStartPulse && !PulseGeneratorActive){
    //note the settings
    ImpulsePauseTimeMs = (UInt16)Get_DebugImpulsePauseTimeMs();
    PulseNumber = (UInt16)Get_DebugPulseNumber();
    //
    PulseGeneratorActive=true;
    PulseStatus=true;
    StartPulseHigh = DateTime.Now;
    NumberHighPulses=0;
}

Ist die if-Entscheidung erfüllt, dann wird die eingestellte Dauer des High-Impuls und die Anzahl der Impulse in den Klassenvariablen abgespeichert. Danach wird der Status des Pulses auf 1 gesetzt und der Zeitpunkt des Starts abgespeichert.

Important

Der Status des Pulses wird nicht in das Symbol geschrieben, sondern in die Klassenvariable "PulseStatus". Dies ist notwendig, da im weiteren Verlauf der Loop-Funktion auf diesen Status zugegriffen wird und es wichtig ist, dass der korrekte Status zur Verfügung steht. Das Schreiben in ein Symbol kann mehrere Zyklen dauern, somit steht der geschriebene Wert in den nachfolgenden C#-Operationen noch nicht zur Verfügung. Das Schreiben in eine Klassenvariable umgeht dieses Problem, da diese sofort beschrieben wird und deren Zustand direkt danach gelesen werden kann. Siehe auch: Siehe auch: Besonderheit beim Beschreiben und Lesen von Symbolen

In der nachfolgenden if-Entscheidung wird geprüft, ob die Zeit für den High-Impuls bereits abgelaufen ist.

//high pulse time elapsed?
if (PulseGeneratorActive && PulseStatus){
    TimeSpan highTime = DateTime.Now - StartPulseHigh;
    int mSeconds = highTime.Seconds * 1000 + highTime.Milliseconds;
    if (mSeconds >= ImpulsePauseTimeMs){
        PulseStatus=false;
        StartPulseLow = DateTime.Now;
        NumberHighPulses+=1;
        //number of pulse reached
        if (NumberHighPulses >= PulseNumber){
            PulseGeneratorActive=false;
        }
    }
}

Die Überprüfung findet statt, sobald der Taktgenerator aktiv ist und der Status des Pulses den Wert 1 hat. Dabei wird auf die Klassenvariable "PulseStatus" zugegriffen, wegen der oben bereits benannten Problematik. Danach werden die bereits vergangenen Millisekunden ermittelt. Falls der High-Impuls zu beenden ist, wird die Phase des Low-Impulses gestartet. Sind bereits alle High-Impulse des Taktgenerator-Zyklus durchlaufen, wird der Zyklus beendet ("PulseGeneratorActive=false;").

Auf ähnliche Weise wird in der nachfolgenden if-Entscheidung der Low-Impuls überprüft und ein etwaiger High-Impuls gestartet.

//Low pulse time elapsed?
if (PulseGeneratorActive && !PulseStatus){
    TimeSpan LowTime = DateTime.Now - StartPulseLow;
    int mSeconds = LowTime.Seconds * 1000 + LowTime.Milliseconds;
    if (mSeconds >= ImpulsePauseTimeMs){
        PulseStatus=true;
        StartPulseHigh = DateTime.Now;
    }
}

Ist der Taktgenerator nicht aktiv, dann ist der Status des Pulses auf jeden Fall 0.

//
if (!PulseGeneratorActive){
    PulseStatus=false;
}

Anschließend folgen die letzten beiden Operationen der Loop-Funktion. Dabei wird zunächst der Wert der Klassenvariable "PulseStatus" an das Symbol weitergegeben. Danach wird der Wert des Symbols "StartPulse" in der Klassenvariable "LastStartPulse" abgelegt, um eine positive Flanke von "StartPulse" zu erkennen.

Damit ist das C#-Script komplett. Beim ersten Start der Simulation nach dem Öffnen des Projekts, folgt zunächst eine Sicherheitsafrage, ob das C#-Script bearbeitet werden soll. Diese Frage ist mit "Ja" zu beantworten. Danach startet die Simulation und das Script wird ausgeführt.

Für die Impulsdauer werden die voreingestellten 200 ms belassen. Die Anzahl der Impulse ist auf 5 eingestellt. Nach Betätigung des Tasters "StartPulse" startet der Taktgenerator und mit jedem positiven Impuls bewegt sich der horizontale Schlitte um einen Schritt nach rechts. Die Schrittweite ist dabei auf 5 Inkremente eingestellt. Anschließend werden die Einstellungen etwas variiert und abermals der Taktgenerator gestartet.