Server - Unbuffered Reporting

Dieses Beispiel zeigt die Unbuffered-Reporting-Implementierung in einem TwinCAT IEC61850 Serverprojekt. Die fürs Reporting benötigten UrCBs (unbuffered 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 UrCBs und der Datasets werden während der Codegenerierung automatisch angelegt (normalerweise unterhalb von LLN0). Die Initialwerte der UrCB-Attribute können bereits im TwinCAT Telecontrol Configurator konfiguriert werden.

Download TwinCAT XAE Project (*.zip): Sample26.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 Unbuffered-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 UrCB besitzt einen solchen 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 UrCB verantwortlich (siehe Code unten).

Die Konfiguration der Reports kann entweder auf der Serverseite direkt am UrCB oder seitens Clients via Client-Server-Dienste (GetUrCBDataValues und SetUrCBDataValues) 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.

Das Beispiel enthält eine einfache Simulation einiger Dataset-Memberwerte. Wenn die Variable „bSimulation“ auf TRUE gesetzt ist, dann werden die Werte alle 2 Sekunden modifiziert. Wie erkennt die UrCB-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. Dies 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.

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 am UrCB generiert und versendet. Das Versenden dieser Reports muss in der SPS-Applikation nicht auf eine besondere Weise getriggert werden. Das Versenden dieser Reports übernimmt ebenfalls der zyklische Aufruf der Server-„Execute“-Methode.

Das zeitliche Verhalten der Report-Meldungen kann unabhängig für jede UrCB-Server-Instanz über zwei Eigenschaften „bReplaceBufferedMX“ und „bOverwriteSameCycleChanges“ konfiguriert werden. Weitere Informationen zur Funktionsweise dieser Eigenschaften finden Sie in der Beschreibung des Beispiels: Server-Buffered Reporting.

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 urcb201 *)
    bOverwriteSameCycleChanges    : BOOL; (* Toggles bOverwriteSameCycleChanges property of urcb301 *)
END_VAR
P_IEC61850MAIN();

fbIED.IEDLD1.LLN0.urcb201.Server.bReplaceBufferedMX:= bReplaceBufferedMX;
fbIED.IEDLD1.LLN0.urcb301.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;
    _bDisconnect     : BOOL;
    state            : BYTE;
    eState           : E_AsyncEnvironmentState;
    bBusy            : BOOL;
    bSuccess         : BOOL;
    ipResult         : I_AsyncServiceResultClass;
    sLastErrorResult : T_MaxString;
    fbAbortReason    : FB_ServiceErrorClass := (stError:=SUCCESS_EVENT);
    sLastAbortReason : T_MaxString;
    bSimulation      : BOOL:=TRUE;
    tSimulation      : TIME:=T#2S;
    fbTimer          : TON;
    tT               : T_UtcTime;
END_VAR
fbConnection.Execute();
eState:= fbConnection.eState;

fbTimer(IN:=bSimulation, PT:=tSimulation);
IF fbTimer.Q THEN(* Simulate server data update *)
    fbTimer(IN:=FALSE);
    fbTimer(IN:=bSimulation);

    fbConnection.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;
END_IF

(* Execute server control block implementations *)
fbIED.IEDLD1.LLN0.urcb101.Server.Execute();
fbIED.IEDLD1.LLN0.urcb201.Server.Execute();
fbIED.IEDLD1.LLN0.urcb301.Server.Execute();
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