Server - Unbuffered Reporting
This sample shows the Unbuffered Reporting implementation in a TwinCAT IEC61850 server project. The UrCBs (unbuffered report control blocks) and Datasets required for reporting can be configured in the TwinCAT Telecontrol Configurator or imported from an existing SCL file (e.g. ICD file). The instances of the UrCBs and the Datasets are created automatically during code generation (usually below LLN0). The initial values of the UrCB attributes can already be configured in the TwinCAT Telecontrol Configurator.
Download TwinCAT XAE Project (*.zip): Sample26.zip
The example described here uses the state machine that is described in the "General Server project structure" chapter. The States 0, 1, 10 and 100 are identical to the state machine described there. Other states were modified for the example or new states were also added.
The implementation of the unbuffered reporting functionality in a TwinCAT IEC 61850 server project is practically a "black box". This means that the complete functionality is already encapsulated in a function block and the application only has to call this function block. Each UrCB has such a function block subelement with the name: "Server". The PLC application only has to call the method: "Execute" at the "Server" block cyclically. This method is responsible for sending the reports and updating the status information in the associated UrCB (see code below).
The reports can be configured either on the server side directly on the UrCB or on the client side via client-server services (GetUrCBDataValues and SetUrCBDataValues). The IED data to be communicated via reporting are defined with the help of the datasets. Multiple data references (Dataset Members) can be configured in a dataset.
The sample contains a simple simulation of some Dataset member values. If the variable "bSimulation" is set to TRUE, then the values are modified every 2 seconds. How does the UrCB Server implementation recognize that a value has been modified and a report is to be generated? This is done through write access to the attribute value (for example: bValue, tValue, iValue, eValue, nValue properties). However, a new report is not generated immediately for each individual write access to an attribute value. This would be undesirable. The "Server" implementation marks the modified dataset members internally as modified. A report is not generated and sent until the "Execute" method is called.
Please note that no reports are generated when writing or forcing values in TwinCAT online mode. In this way, the "Server" implementation cannot detect when a value has been modified. Integrity reports and General Interrogation reports are automatically generated and sent in accordance with the configuration on the UrCB. The sending of these reports does not have to be triggered in a special way in the PLC application. The cyclic call of the server "Execute" method also handles the sending of these reports.
The time behavior of the report messages can be configured independently for each UrCB server instance via two properties "bReplaceBufferedMX" and "bOverwriteSameCycleChanges". For more information on how these properties work, see the description of the sample: 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