Server - Buffered Reporting

Dieses Beispiel zeigt die Buffered-Reporting-Implementierung in einem TwinCAT IEC61850 Serverprojekt. Die für das Reporting benötigten BRCBs (buffered report control blocks) und Datasets können im TwinCAT Telecontrol Configurator konfiguriert oder aus einer vorhandenen SCL-Datei (z. B. ICD-Datei) importiert werden. Die Instanzen der BRCBs und der Datasets werden während der Codegenerierung automatisch angelegt (normalerweise unterhalb von LLN0). Die Initialwerte der BRCB-Attribute können bereits im TwinCAT Telecontrol Configurator konfiguriert werden.

Download TwinCAT XAE Project (*.zip): Sample28.zip

Das hier beschriebene Beispiel nutzt die Statemachine, welche in dem Kapitel „Allgemeine Server - Projektstruktur“ beschrieben ist. Die States: 0,1,10 und 100 sind identisch zu der dort beschriebenen Statemachine. Andere States wurden für das Beispiel modifiziert oder auch neue States hinzugefügt.

Bei der Implementierung der Buffered-Reporting-Funktionalität in einem TwinCAT IEC 61850 Serverprojekt handelt es sich praktisch um eine „Blackbox“. D.h. die komplette Funktionalität ist in einem Baustein bereits gekapselt und die Applikation muss lediglich diesen Baustein aufrufen. Jeder BRCB besitzt ein solches Baustein-Unterelement mit dem Namen: „Server“. Die SPS-Applikation muss nur die Methode: „Execute“ an dem „Server“-Baustein zyklisch aufrufen. Diese Methode ist für das Versenden der Reports und die Aktualisierung der Statusinformationen in dem dazugehörigen BRCB verantwortlich (siehe Code unten).

Die Konfiguration der Reports kann entweder auf der Serverseite direkt am BRCB oder seitens Clients via Client-Server-Dienste (GetBRCBDataValues und SetBRCBDataValues) vorgenommen werden. Die via Reporting zu kommunizierenden IED-Daten werden mit Hilfe der Datasets festgelegt. In einem Dataset können mehrere Datenreferenzen (Dataset-Member) konfiguriert werden.

Reports, die nicht versendet werden können, werden im Buffer gespeichert. Ist die maximale Anzahl an Einträgen im Buffer erreicht, wird der älteste Report gelöscht. Bei der Deklaration eines BRCBs kann die maximale Anzahl an Einträgen über die Eigenschaft „nMaxBufferEntries“ eingestellt werden. Im Beispiel ist dies bei der Deklaration von „brcb101“ zu sehen:

brcb101: FB_ScsmBrCBImplClass := (Server:=(nMaxBufferEntries:=15), RptID:=(sValue:='IEDLD1/LLN0.brcb101'), DatSet:=(sValue:='IEDLD1/LLN0.DS1'), ConfRev:=(nValue:=1), OptFlds:=(SequenceNumber:=TRUE, ReportTimeStamp:=TRUE, ReasonForInclusion:=TRUE, DataSetName:=TRUE, DataReference:=TRUE, BufferOverflow:=TRUE, EntryID:=TRUE, ConfRevision:=TRUE), TrgOps:=(DataChange:=TRUE, QualityChange:=TRUE, DataUpdate:=TRUE, Integrity:=TRUE, GeneralInterrogation:=TRUE), IntgPd:=(nValue:=5000), bLinkResult:=THIS^.AddBufferedReportControlBlockToContainer(ipBufferedReportControlBlock:=brcb101));

Das Beispiel enthält eine einfache Simulation einiger Dataset-Member-Werte. Wenn die Variable „bSimulation“ auf TRUE gesetzt ist, dann werden die Werte alle 2 Sekunden modifiziert.
Wie erkennt die BRCB-Server-Implementierung, dass ein Wert modifiziert wurde und ein Report generiert werden soll? Dies geschieht durch den Schreibzugriff auf den Attributwert (z.  B.: bValue-, tValue-, iValue-, eValue-, nValue-Eigenschaft). Es wird aber nicht sofort bei jedem einzelnen Schreibzugriff auf einen Attributwert ein neuer Report generiert. Dieses wäre unerwünscht. Die „Server“-Implementierung markiert intern die geänderten Dataset-Member als modifiziert. Erst beim Aufruf der „Execute“-Methode wird ein Report generiert und versendet. Die in dem Report versendeten Daten entsprechen den Daten zum Zeitpunkt der Wertänderung des Dataset-Members. Die Werte werden bei Änderung zwischengespeichert und nach Ablauf der im Attribut „BufTm“ konfigurierten Zeit in einen Report kodiert und verschickt. Wenn innerhalb der Bufferzeit ein erneuter Schreibzugriff vorkommt, werden die zwischengespeicherten Daten überschrieben. Dieser Ablauf ist in der folgenden Abbildung dargestellt:

Server - Buffered Reporting 1:

Für Daten der funktionalen Gruppe „ST“ ist in der IEC 61850-7-2 ein abweichendes Verhalten für das Zwischenspeichern definiert. Im Falle, dass vor Ablaufen von „BufTm“ der Wert erneut geschrieben wird, werden die zwischengespeicherten Daten sofort zu einem Report kodiert und verschickt. Daraufhin werden die neuen Daten in den Zwischenspeicher geschrieben und die Überwachung der Bufferzeit wird neu gestartet. In der folgenden Grafik ist das beschriebene Verhalten abgebildet:

Server - Buffered Reporting 2:

Für Daten der funktionalen Gruppe „MX“ kann ausgewählt werden, ob der BRCB diese gesondert, wie Daten der funktionalen Gruppe „ST“, behandeln soll. Dies kann über die Eigenschaft „bReplaceBufferedMX“ eingestellt werden. Im Beispiel wird die Sonderbehandlung bei „brcb201“ aktiviert:

brcb201: FB_ScsmBrCBImplClass := (Server:=(bReplaceBufferedMX:=TRUE), RptID:=(sValue:='IEDLD1/LLN0.brcb201'), DatSet:=(sValue:='IEDLD1/LLN0.DS2'), ConfRev:=(nValue:=1), OptFlds:=(SequenceNumber:=TRUE, ReportTimeStamp:=TRUE, ReasonForInclusion:=TRUE, DataSetName:=TRUE, DataReference:=TRUE, BufferOverflow:=TRUE, EntryID:=TRUE, ConfRevision:=TRUE), BufTm:=(nValue:=5000), TrgOps:=(DataChange:=TRUE, QualityChange:=TRUE, DataUpdate:=TRUE, Integrity:=TRUE, GeneralInterrogation:=TRUE), IntgPd:=(nValue:=5000), bLinkResult:=THIS^.AddBufferedReportControlBlockToContainer(ipBufferedReportControlBlock:=brcb201));

Wie „MX“ Daten je nach Einstellung von „bReplaceBufferedMX“ behandelt werden ist in der folgen Abbildung zu sehen:

Server - Buffered Reporting 3:

Das sofortige Versenden von Reports bei wiederholter Änderung eines Werts des zugeordneten Datasets kann dazu führen, dass mehr Reports als gewünscht generiert werden. Dies z. B. ist der Fall, wenn ein Datenobjekt der funktionalen Gruppe „ST“ in einem Dataset referenziert ist und dann mehrere Attribute des Datenobjekts einzeln geschrieben werden. Da jede Wertänderung eines Datenattributs innerhalb eines Datenobjekts auch eine Wertänderung des Datenobjekts darstellt, werden mehrere Reports generiert. Wird die Eigenschaft „bOverwriteSameCycleChanges“ auf TRUE gesetzt, werden mehrfache Wertänderungen innerhalb eines SPS Zyklus als eine einzelne Wertänderung behandelt. Die Erkennung, welche Werte im aktuellen Zyklus geschrieben wurden, wird mit Aufruf der „Execute“-Methode zurückgesetzt. Eine Wertänderung vor und nach dem Aufruf werden daher als nicht als Änderung im selben Zyklus interpretiert.

Server - Buffered Reporting 4:

Beachten Sie, dass beim Schreiben oder Forcen der Werte im TwinCAT Online-Mode keine Reports generiert werden. Die „Server“-Implementierung kann auf diese Weise nicht erkennen, wenn ein Wert modifiziert wurde.

Integrity-Reports und General-Interrogation-Reports werden automatisch entsprechend der Konfiguration des BRCBs generiert und versendet. Das Versenden dieser Reports muss in der SPS-Applikation nicht auf eine besondere Weise getriggert werden. Es wird ebenfalls von dem zyklischen Aufruf der Server-„Execute“-Methode übernommen.

PROGRAM MAIN
VAR
    bSimulation                   : BOOL:=TRUE; (* Enables/disables simulation of data update/change *)
    tSimulation                   : TIME:=T#2S; (* Cycle time of simulated data update/change *)
    fbTimer                       : TON;
    tT                            : T_UtcTime;
    
    bReplaceBufferedMX            : BOOL; (* Toggles bReplaceBufferedMX property of brcb201 *)
    bOverwriteSameCycleChanges    : BOOL; (* Toggles bOverwriteSameCycleChanges property of brcb301 *)
END_VAR
P_IEC61850MAIN();

fbIED.IEDLD1.LLN0.brcb201.Server.bReplaceBufferedMX:= bReplaceBufferedMX;
fbIED.IEDLD1.LLN0.brcb301.Server.bOverwriteSameCycleChanges:= bOverwriteSameCycleChanges;

fbTimer(IN:=bSimulation, PT:=tSimulation);
IF fbTimer.Q THEN(* Simulate server data update *)
    fbTimer(IN:=FALSE);
    fbTimer(IN:=bSimulation);
    
    fbIEDServer.GetSystemTime(ipAA:=0, tT=>tT);
    
    (* Simulate "IEDLD1/LLN0.DS1" member value change *)
    fbIED.IEDLD1.LEDGGIO1.SPCSO1.t.tValue:= tT;
    fbIED.IEDLD1.LEDGGIO1.SPCSO1.stVal.bValue:= NOT fbIED.IEDLD1.LEDGGIO1.SPCSO1.stVal.bValue;
    fbIED.IEDLD1.LEDGGIO2.SPCSO1.t.tValue:= tT;
    fbIED.IEDLD1.LEDGGIO2.SPCSO1.stVal.bValue:= NOT fbIED.IEDLD1.LEDGGIO2.SPCSO1.stVal.bValue;
    fbIED.IEDLD1.LEDGGIO3.SPCSO1.t.tValue:= tT;
    fbIED.IEDLD1.LEDGGIO3.SPCSO1.stVal.bValue:= NOT fbIED.IEDLD1.LEDGGIO3.SPCSO1.stVal.bValue;
    fbIED.IEDLD1.LEDGGIO4.SPCSO1.t.tValue:= tT;
    fbIED.IEDLD1.LEDGGIO4.SPCSO1.stVal.bValue:= NOT fbIED.IEDLD1.LEDGGIO4.SPCSO1.stVal.bValue;
    fbIED.IEDLD1.LEDGGIO5.SPCSO1.t.tValue:= tT;
    fbIED.IEDLD1.LEDGGIO5.SPCSO1.stVal.bValue:= NOT fbIED.IEDLD1.LEDGGIO5.SPCSO1.stVal.bValue;
    fbIED.IEDLD1.LEDGGIO6.SPCSO1.t.tValue:= tT;
    fbIED.IEDLD1.LEDGGIO6.SPCSO1.stVal.bValue:= NOT fbIED.IEDLD1.LEDGGIO6.SPCSO1.stVal.bValue;
    fbIED.IEDLD1.LEDGGIO7.SPCSO1.t.tValue:= tT;
    fbIED.IEDLD1.LEDGGIO7.SPCSO1.stVal.bValue:= NOT fbIED.IEDLD1.LEDGGIO7.SPCSO1.stVal.bValue;
    fbIED.IEDLD1.LEDGGIO8.SPCSO1.t.tValue:= tT;
    fbIED.IEDLD1.LEDGGIO8.SPCSO1.stVal.bValue:= NOT fbIED.IEDLD1.LEDGGIO8.SPCSO1.stVal.bValue;

    (* Simulate "IEDLD1/LLN0.DS2" member value change *)
    fbIED.IEDLD1.MMXU1.TotW.t.tValue:= tT;
    fbIED.IEDLD1.MMXU1.TotW.mag.f.fValue:= fbIED.IEDLD1.MMXU1.TotW.mag.f.fValue + 0.1;
    
    (* Simulate "IEDLD1/LLN0.DS3" member value change *)
    fbIED.IEDLD1.XCBR1.Pos.t.tValue:= tT;
    IF fbIED.IEDLD1.XCBR1.Pos.stVal.eValue = E_AcsiDbpos.On THEN
        fbIED.IEDLD1.XCBR1.Pos.stVal.eValue:= E_AcsiDbpos.Off;
    ELSE
        fbIED.IEDLD1.XCBR1.Pos.stVal.eValue:= E_AcsiDbpos.On;
    END_IF
    fbIED.IEDLD1.XCBR1.Pos.q.OldData:= NOT fbIED.IEDLD1.XCBR1.Pos.q.OldData;
    fbIED.IEDLD1.XCBR1.Pos.q.eValidity:= SEL(fbIED.IEDLD1.XCBR1.Pos.q.OldData, E_AcsiQualityValidity.Good, E_AcsiQualityValidity.Questionable);
END_IF
FUNCTION_BLOCK FB_IEDServerSession IMPLEMENTS I_ScsmAbortIndEventSink, I_ScsmAssociateIndEventSink, I_ScsmReleaseIndEventSink
VAR_INPUT
    fbConnection     : FB_iec61850ConnectionClass := (ipAbortInd:=THIS^, ipAssociateInd:=THIS^, ipReleaseInd:=THIS^);
END_VAR
VAR
    _bAbort          : BOOL; (* Rising edge activates AbortReq() command execution *)
    _bDisconnect     : BOOL; (* Rising edge activates ReleaseReq() command execution *)

    state            : BYTE;
    eState           : E_AsyncEnvironmentState; (* Environment state *)
    bBusy            : BOOL; (* If TRUE => command execution is busy *)
    bSuccess         : BOOL;
    ipResult         : I_AsyncServiceResultClass;
    sLastErrorResult : T_MaxString;
    fbAbortReason    : FB_ServiceErrorClass := (stError:=SUCCESS_EVENT);
    sLastAbortReason : T_MaxString;
END_VAR
fbConnection.Execute();
eState:= fbConnection.eState;

(* Execute server control block implementations *)
fbIED.IEDLD1.LLN0.brcb101.Server.Execute();
fbIED.IEDLD1.LLN0.brcb201.Server.Execute();
fbIED.IEDLD1.LLN0.brcb301.Server.Execute();

CASE state OF
     0: (* Initial state *)
          IF _bAbort THEN (* Abort connection => execute AbortReq() command *)
               _bAbort:= FALSE;
               bSuccess:= fbConnection.AbortReq(ipReason:=fbAbortReason, ipSink:=0, ipResult=>ipResult);
               state:= SEL(bSuccess, 100, 1);
          ELSIF eState = E_AsyncEnvironmentState.Established AND _bDisconnect THEN (* Close/release connection => execute ReleaseReq() command *)
               _bDisconnect:= FALSE;
               bSuccess:= fbConnection.ReleaseReq(ipSink:=0, ipResult=>ipResult);
               state:= SEL(bSuccess, 100, 1);
          ELSIF eState = E_AsyncEnvironmentState.Established THEN (* Connection established => exchange IED data *)
               state:= 10;
          END_IF
          _bDisconnect:= FALSE;

     1: (* Wait for AbortReq() or ReleaseReq() command completion *)
          IF ipResult <> 0 THEN
               ipResult.Execute();
               IF NOT (bBusy:=ipResult.IsBusy()) THEN
                    state:= SEL(ipResult.IsCompleted(), 100(* failed or aborted *), 0(* succeeded *));
               END_IF
          END_IF

     10: (* connection established *)
          state:= 0;

     100: (* Error state *)
          state:= 0;
          IF ipResult <> 0 THEN
               sLastErrorResult:= ipResult.Dump();
          END_IF
END_CASE