此次继续介绍控制服务的其他模型,让我们回顾一下,控制包括如下四种模型:
- 常规安全的直接控制(简称DOns);
- 常规安全的操作前选择控制(简称SBOns);
- 增强安全的直接控制(简称DOes);
- 增强安全的操作前选择控制(简称SBOes);
前面的两篇文章分别介绍了前两者,此次将介绍后两者,并以第四者增强安全的操作前选择控制的实例来让大家有一个更加直观的认识。
前面两篇文章重点介绍的是常规安全,此次是增强安全,那么常规和增强到底怎么区别呢?
我先做一下铺垫,先介绍一些每个模型采用的服务:
模型 | 采用的服务 |
DOns | Operate(操作)、 TimeActivatedOperate(时间激活操作) |
SBOns |
Select(选择) 、 Operate(操作)、 TimeActivatedOperate(时间激活 操作)和Cancel(取消) |
DOes |
Operate(操作)、 TimeActivatedOperate(时间激活操作)和 CommandTerminal(命令终止) |
SBOes |
SelectWithValue(带值的选择)、 cancel(取消)、 Operate(操作) 、 TimeActivatedOperate (时间激活操作)、 CommandTermination(命令终止)服 务 |
其次我们对比一下常规安全和增强安全的状态机区别(为了清晰描述暂时去除不常用的TimeActivatedOperate并适当简化,等大家理解之后感兴趣可以深入研究):
1、DOns状态机:
2、DOes状态机:
3、SBOns状态机:
4、SBOes状态机:
通过上述状态机的示意图可以看出,增强型比对应的常规型多了等待变化的状态,这个其实就是指对物理装置的变化,例如闸刀的开与合,结合上述我总结增强型和常规型的区别如下:
1、控制对象对控制结果是否监视,常规安全对控制的结果是不监视的,而增强安全是需要进行监视的;
2、增强型的增加了CmdTerm服务,叫命令终止,会携带控制结果报告给请求方。
3、对于操作前选择的方式,常规安全是选择操作(SBO),而增强安全是带值选择操作(SBOw)。
下面我们以增强安全的操作前选择控制的模型进行实例演示:
一、建模
我们以兼容逻辑节点类CSWI为例,其中的数据pos为可控双点DPC,根据规范8-1的附录E.3.2的扩展介绍,在增强安全的操作前选择控制模型下SBOw、Oper、Cancel为必选。
下面是使用ICD_Designer建模工具所建模型的截图:
由于此次采用增强安全的操作前选择控制,所以ctlModel的Val要设置如下:
<DAI name="ctlModel">
<Val>sbo-with-enhanced-security</Val>
</DAI>
为了方便对控制值的操作,为ctlVal设置DAID,如下:
<DAI name="ctlVal">
<Private type="SystemCorp_Generic">
<SystemCorp_Generic:GenericPrivateObject xmlns:SystemCorp_Generic="http://www.systemcorp.com.au/61850/SCL/Generic" Field1="1" Field2="1" Field3="0" Field4="0" Field5="0"/>
</Private>
</DAI>
二、服务端源码:
服务端主要完成选择和操作的回调
和之前一样选择的回调我们采用直接做成功应答,如下:
enum eCommandAddCause SelectCallbackHandler( void *ptUserData, struct IEC61850_DataAttributeID * ptControlID, struct IEC61850_DataAttributeData * ptSelectValue, struct IEC61850_CommandParameters* ptSelectParameters)
{
printf("===执行了Select回调函数===\r\n");
return IEC61850_COMMAND_ERROR_NONE;
}
那么操作的回调函数是否还同以前一样只做一个打印返回成功呢?这次真的不行了,因为是增强安全的模式,要监视控制的结果,怎样才算控制的成功呢?我来简单说说。
当客户端向服务端发起控制操作命令后,服务端要在控制值ctlVal变更后发动作指令给实际的物理装置,物理装置会根据指令进行实际的操作。该物理装置的状态是要被监视的,如果刚才的动作成功状态就会发生变化,监控程序就会将新的状态同步给61850的模型的stVal。无论是增强安全的还是常规安全的都应该是这样的过程,只不过常规安全的并不监视ctlVal变更后到stVal的变化的过程和结果,所以之前的例子也就偷偷懒没有认真的完成操作的回调。这次我们尽可能的让流程完整,正常来讲我们要在回调里向物理装置发送指令,但是毕竟是演示,我们可以直接在回调里更新stVal来使流程完整。
代码如下:
enum eCommandAddCause OperateCallbackHandler( void *ptUserData, struct IEC61850_DataAttributeID * ptControlID, struct IEC61850_DataAttributeData * ptOperateValue, struct IEC61850_CommandParameters* ptOperateParameters)
{
enum eCommandAddCause ErrorCode = IEC61850_COMMAND_ERROR_NONE; //Create return value and init to No Error
enum IEC61850_ErrorCodes eErrorCode = IEC61850_ERROR_NONE;
//定义DAID结构体指针,指向入参DAID
struct IEC61850_DataAttributeID_Generic *DaidGeneric = (struct IEC61850_DataAttributeID_Generic *)ptControlID;
struct IEC61850_DataAttributeID_Generic StValDAID = {0};
struct IEC61850_DataAttributeData StValDAData = {0}; // Always initialize the structure to 0 to avoid Garbage data value
IEC61850 myIEC61850Object = GetMyServerClient(); //Get the IEC61850 Structure
enum DbPosValues dbPosValue = DBPOS_OFF;
Boolean boolValue = FALSE;
//首先我们打印
printf("===执行了Operate回调函数===\r\n");
//先判断传入的DAID,对于此次demo可以不判断,但是实际装置上可能有很多控制对象需要加以区分
if( (DaidGeneric->uiField1 == 1) && (DaidGeneric->uiField2 == 1) && (DaidGeneric->uiField3 == 0) && (DaidGeneric->uiField4 == 0) && (DaidGeneric->uiField5 == 0) )
{/* CSWI */
//设置DAID,对应stVal的DAID,前面建模没有介绍,大家请补上
StValDAID.Generic_type = IEC61850_DAID_GENERIC;
StValDAID.uiField1 = 1;
StValDAID.uiField2 = 0;
StValDAID.uiField3 = 0;
StValDAID.uiField4 = 0;
StValDAID.uiField5 = 0;
//设置控制值
StValDAData.uiBitLength = IEC61850_DBPOS_BITSIZE;
StValDAData.ucType = IEC61850_DATATYPE_DBPOS;
StValDAData.pvData = &dbPosValue;
//根据ctlVal的新值来给stVal赋值
if( (*((Integer8*)ptOperateValue->pvData)) == FALSE )
{
dbPosValue = DBPOS_OFF;
}
else
{
dbPosValue = DBPOS_ON;
}
//执行PIS-10的标准函数IEC61850_Update来更新stVal值
eErrorCode = IEC61850_Update(myIEC61850Object, (struct IEC61850_DataAttributeID*)&StValDAID, &StValDAData, 1);
}
return ErrorCode;
}
三、客户端源码:
主函数主要做下列事情:
//1、由客户输入一个布尔值
boolVal = GetBooleanFromUser();
//2、做一个操作前选择
IEC61850Error = SelectCSWI(boolVal);
//3、操作前选择成功后执行操作
if(IEC61850Error == IEC61850_ERROR_NONE)
{
OperateCSWI(boolVal);
}
下面讲解一下SelectCSWI的内容:
enum IEC61850_ErrorCodes SelectCSWI(Boolean inCtrlValue)
{
enum IEC61850_ErrorCodes eErrorCode = IEC61850_ERROR_NONE;
//定义DAID结构体
struct IEC61850_DataAttributeID_Generic CtrlDAID = {0};
//定义数据属性,也就是带值选择所带的值
struct IEC61850_DataAttributeData CtrlDAData = {0};
//定义控制参数,比如check等,这里先定义为空
struct IEC61850_ControlParameters CtrlControlParams = {0};
//获取当前IEC61850对象
IEC61850 myIEC61850Object = GetMyServerClient();
//为DAID赋值,对应模型文件中的DAID
CtrlDAID.Generic_type = IEC61850_DAID_GENERIC;
CtrlDAID.uiField1 = 1;
CtrlDAID.uiField2 = 1;
CtrlDAID.uiField3 = 0;
CtrlDAID.uiField4 = 0;
CtrlDAID.uiField5 = 0;
//设置数据属性值
CtrlDAData.uiBitLength = sizeof(Boolean) * 8;
CtrlDAData.ucType = IEC61850_DATATYPE_BOOLEAN;
CtrlDAData.pvData = &inCtrlValue;
//执行PIS-10的标准函数IEC61850_ControlSelect进行操作前选择
eErrorCode = IEC61850_ControlSelect(myIEC61850Object, (struct IEC61850_DataAttributeID*)&CtrlDAID, &CtrlDAData, CtrlControlParams);
if(eErrorCode != IEC61850_ERROR_NONE)
{
//如果未成功
char strError[SIZE_OF_ERROR_STRING] = {0};
snprintf(strError, SIZE_OF_ERROR_STRING, "选择失败:(%i) %s\n", eErrorCode, IEC61850_ErrorString(eErrorCode));
SetErrorString(strError, SIZE_OF_ERROR_STRING);
}
else
{
//如果成功
char strError[SIZE_OF_ERROR_STRING] = {0};
SetErrorString(strError, SIZE_OF_ERROR_STRING);
}
return eErrorCode;
}
下面OperateCSWI的内容:
enum IEC61850_ErrorCodes OperateCSWI(Boolean inCtrlValue)
{
enum IEC61850_ErrorCodes eErrorCode = IEC61850_ERROR_NONE;
//定义DAID结构体
struct IEC61850_DataAttributeID_Generic CtrlDAID = {0};
//定义数据属性,也就是带值选择所带的值
struct IEC61850_DataAttributeData CtrlDAData = {0};
//定义控制参数,比如check等,这里先定义为空
struct IEC61850_ControlParameters CtrlControlParams = {0};
//获取当前IEC61850对象
IEC61850 myIEC61850Object = GetMyServerClient();
//为DAID赋值,对应模型文件中的DAID
CtrlDAID.Generic_type = IEC61850_DAID_GENERIC;
CtrlDAID.uiField1 = 1;
CtrlDAID.uiField2 = 1;
CtrlDAID.uiField3 = 0;
CtrlDAID.uiField4 = 0;
CtrlDAID.uiField5 = 0;
//设置数据属性值
CtrlDAData.uiBitLength = sizeof(Boolean) * 8;
CtrlDAData.ucType = IEC61850_DATATYPE_BOOLEAN;
CtrlDAData.pvData = &inCtrlValue;
//执行PIS-10的标准函数IEC61850_ControlOperate进行操作
eErrorCode = IEC61850_ControlOperate(myIEC61850Object, (struct IEC61850_DataAttributeID*)&CtrlDAID, &CtrlDAData, CtrlControlParams);
if(eErrorCode == IEC61850_ERROR_NONE)
{
//如果成功
char strError[SIZE_OF_ERROR_STRING] = {0};
SetErrorString(strError, SIZE_OF_ERROR_STRING);
}
else
{
//如果未成功
char strError[SIZE_OF_ERROR_STRING] = {0};
snprintf(strError, SIZE_OF_ERROR_STRING, "操作失败:(%i) %s\n", eErrorCode, IEC61850_ErrorString(eErrorCode));
SetErrorString(strError, SIZE_OF_ERROR_STRING);
}
return eErrorCode;
}
命令终止回调
前面提到了增强安全增加了CmdTerm服务,也就是状态机同步成功后服务端会发送一个CmdTerm给客户端,客户端接收到之后就会调用命令终止的回调。
这里我们无需做任何操作,返回成功即可,代码如下:
enum eCommandAddCause CommandTerminationCallback(void * ptUserData, struct IEC61850_DataAttributeID * ptControlID, struct IEC61850_DataAttributeData * ptCmdTermValue)
{
printf("\n==发生了CommandTerminationCallback==\n");
return IEC61850_COMMAND_ERROR_NONE;
};
四、效果展示
服务端:
客户端:
客户端输入T并回车(要与服务端当前的ctlVal不一致,否则选择失败)
从上图看出LastError是NONE,就是成功发送了选择和操作,并且打印了命令终止的回调信息,说明客户端接收到了服务端发来的CmdTerm。
我们再看服务端:
从上图看打印了两条信息,说明接收到了来自客户端的Select和Operate请求。
五、报文
下面我们通过抓包可以更加清楚交互的过程
图中有5条消息,下面做一一介绍;
1、由客户端发往服务端,是SelectWithValue,对应MMS的Write请求;
2、由服务端对SelectWithValue请求进行应答,自然对应MMS的Write应答;
3、由客户端发往服务端,是Operate,同样对应MMS的Write请求;
4、由服务端对Operate请求进行应答,自然对应MMS的Write应答;
5、由服务端向客户端发送CommandTermination,对应MMS的InformationReport请求,该请求是非确认,无需客户端应答,所以再没有后续的报文,至此增强安全的操作前选择模型就已经初步完成。
总结,本章重点讲到了控制服务的增强安全的控制模型。截止到本文已经对于控制围绕4大模型已经介绍完毕,其中很多细节没有详细的介绍,比如SBO的超时、取消服务的使用、按时间激活以及Test、Check等等,我们可以在具体的项目应用中去详细的了解,也欢迎加入QQ群:365273095进行交流。