Früher hätte man jedem FB mit Nachtbetrieb eine entsprechende INPUT-Variable gegeben, anhand der Uhrzeit eine zentrale Variable "Nachtbetrieb" verwaltet und diese an die INPUT-Variablen der FBs übergeben. Wo ist nun der grosse Vorteil der Interfaces? Schliesslich ist es mit der Deklaration des Interface-Arrays ja nicht getan. Ich muss doch alle FBs statisch instanziieren und dann ihre Adressen den Interface-Variablen zuweisen.
Der grosse Vorteil ist, dass man das Array verwalten kann, ohne zu wissen was für Objekte sich darin befinden.
Beim traditionellen Ansatz bleibt einem nichts anderes übrig, als mit den konkreten Objekten zu arbeiten:
also in etwa so:
heizkessel1.Nachtbetrieb := global_nachtbetrieb;
heizkessel2.Nachtbetrieb := global_nachtbetrieb;
jalousie1.Nachtbetrieb :=
...
wenn man jetzt ein neues Objekt hinzufügt, muss man die Liste ändern. Das heisst zum Beispiel, dass man auch Zugriff auf diesen Code haben muss.
Bei meinem Beispiel oben kann man eine ganz allgemeine Verwaltung aufbauen, die kann in einer Bibliothek stecken, und muss nicht mehr
angefasst werden, nur weil man ein neues Objekt hinzufügt.
Und das gilt für alle Stellen an denen man das Objekt verwenden will.
Mit Hilfe von Interfaces kann man seinen Code einfacher modularisieren und einfacher erweitern.
Anderes Beispiel aus einem uralten Projekt aus den Anfangszeiten der V3. Ich habe das Projektarchiv im Anhang angehängt. Ich hoffe ihr könnt was damit anfangen.
Ich habe damals folgendes gemacht, ein Kollege von mir hat auf einem
CODESYS-Usergroup Treffen ein Projekt vorgestellt, in dem er gezeigt hat, wie er arbeitet, wie
er typischerweise seine Projekt aufbaut.
Das war noch aus Vor-OO Zeiten und der Kollege ist Applikateur und hatte mit Objektorientierung erstmal nichts am Hut.
Die Grundidee ist nicht schwierig, er hat eine Zustandsmaschine die er in SFC programmiert hat (bei mir ist das ein ST-Switch Case geworden) und die alle Einheiten einer Maschine verwaltet:
Code:
CASE state OF
Init:
// initialize all units
digPick(bEnable := TRUE, bHome := TRUE, bManual := FALSE);
digPlace(bEnable := TRUE, bHome := TRUE, bManual := FALSE);
axisPress(bEnable := TRUE, bHome := TRUE, bManual := FALSE);
axisPress2(bEnable := TRUE, bHome := TRUE, bManual := FALSE);
IF digPick.bDone AND digPlace.bDone AND axisPress.bDone THEN
IF bManualMode THEN
state := Manual;
ELSIF bAutoMode THEN
state := Auto;
END_IF
END_IF
Error:
// should never happen
// but if it does, all units are switched off
digPick(bEnable := FALSE);
digPlace(bEnable := FALSE);
axisPress(bEnable := FALSE);
axisPress2(bEnable := FALSE);
IF bQuitError THEN
state := Init;
END_IF
Manual:
// manual mode
digPick(bEnable := TRUE, bHome := FALSE, bManual := TRUE);
digPlace(bEnable := TRUE, bHome := FALSE, bManual := TRUE);
axisPress(bEnable := TRUE, bHome := FALSE, bManual := TRUE);
axisPress2(bEnable := TRUE, bHome := FALSE, bManual := TRUE);
IF bReset THEN
state := Init;
ELSIF bAutoMode THEN
state := Auto;
END_IF
Auto:
// auto mode
digPick(bEnable := TRUE, bHome := FALSE, bManual := FALSE);
digPlace(bEnable := TRUE, bHome := FALSE, bManual := FALSE);
axisPress(bEnable := TRUE, bHome := FALSE, bManual := FALSE, diSetPos := 1200);
axisPress2(bEnable := TRUE, bHome := FALSE, bManual := FALSE, diSetPos := 1200);
IF bReset THEN
state := Init;
ELSIF bManualMode THEN
state := Manual;
END_IF
ELSE
state := Error;
END_CASE
Und ich habe mir das jetzt vorgenommen, und nach meinen Vorstellungen umgebaut. Was man zunächst sieht: alle Units haben das gleiche Interface nach aussen,
und bieten Funktionen für jeden Zustand an.
Also mache ich daraus erstmal ein Interface IUnit, dass alle Objekte die in der Zustandsmaschine verwaltet werden wollen zu implementieren haben:
Code:
INTERFACE IUnit
Method Auto : BOOL;
Method Done : BOOL;
Method Enable : BOOL;
Method Home : BOOL;
Method Manual : BOOL;
Method Simulation : BOOL;
END_INTERFACE
Die neue Zustandsmaschine verwaltet jetzt ganz allgemein eine Liste von solchen Units:
Code:
FUNCTION_BLOCK Sequence
VAR
m_units : ARRAY [0..10] OF IUnit;
m_nNumUnits : INT := 0;
END_VAR
Und bietet eine Methode an um eine neue Unit zu registrieren. Die Zustandsmaschine sieht dann nicht so viel anders aus, nur iteriert man in jedem
Zustand über die Liste der Units und ruft die entsprechende Methode auf.
Was ist nun der Vorteil der OO-Lösung gegenüber der traditionellen Lösung? Man sieht das sofort wenn man an diesem Programm etwas ändern will.
Nehmen wir an, es kommt eine neue Einheit hinzu, dann genügt es diese in die Sequenz einzutragen und fertig:
seq.RegisterUnit(axisPress2);
Im alten Code muss man dagegen an vier Stellen editieren, und zwar direkt in der Zustandsmaschine.
Im OO-Code muss man die Zustandsmaschine nicht anfassen, die kann man auch in eine Bibliothek auslagern.
Noch ein grosser Vorteil: die Zustandsmaschine kann sogar im Laufenden Betrieb geändert werden
(wenn vom Programmierer vorgesehen), man könnte eine Funktion einbauen um eine Einheit aus der Zustandsmaschine zu entfernen und dann beispielsweise
unabhängig von den anderen Einheiten im Manualmodus betrieben zu werden.
Sowas ist mit dem herkömmlichen Verfahren überhaupt nicht möglich.
Was auch ein grosser Vorteil ist, durch das Interface wird eine Implementierung vorgegeben, man kann nicht einfach bei der Implementierung einer neuen Unit
einen Zustand vergessen.