1295 lines
44 KiB
Markdown
1295 lines
44 KiB
Markdown
# libiec61850-1.5.3 MMS 服务端 API 开发手册
|
||
|
||
> 本文档基于 libiec61850 v1.5.3 源码,覆盖 IEC 61850 服务端全部公开 C API,达到开发者脱离源码即可进行项目开发的标准。
|
||
|
||
---
|
||
|
||
## 目录
|
||
|
||
1. [架构概述](#1-架构概述)
|
||
2. [数据模型创建(动态模型)](#2-数据模型创建动态模型)
|
||
3. [服务器配置(IedServerConfig)](#3-服务器配置iedserverconfig)
|
||
4. [服务器生命周期管理](#4-服务器生命周期管理)
|
||
5. [数据值读取与更新](#5-数据值读取与更新)
|
||
6. [控制模型回调](#6-控制模型回调)
|
||
7. [报告控制块(RCB)事件处理](#7-报告控制块rcb事件处理)
|
||
8. [读写访问控制](#8-读写访问控制)
|
||
9. [定值组(SGCB)处理](#9-定值组sgcb处理)
|
||
10. [GOOSE 发布](#10-goose-发布)
|
||
11. [SV 控制块](#11-sv-控制块)
|
||
12. [连接管理与认证](#12-连接管理与认证)
|
||
13. [日志服务](#13-日志服务)
|
||
14. [运行模式:线程模式 vs 非线程模式](#14-运行模式线程模式-vs-非线程模式)
|
||
15. [与 RTU 项目对照](#15-与-rtu-项目对照)
|
||
16. [附录:公共数据类型速查](#16-附录公共数据类型速查)
|
||
|
||
---
|
||
|
||
## 1. 架构概述
|
||
|
||
服务端 API 提供两个抽象层级:
|
||
|
||
| 层级 | API 头文件 | 描述 |
|
||
|------|-----------|------|
|
||
| **高层 IEC 61850** | `iec61850_server.h` | 封装 IEC 61850 语义(LD/LN/DO/DA/RCB/SGCB),推荐使用 |
|
||
| **底层 MMS** | `mms_server.h` | MMS 协议层服务器,一般不需要直接使用 |
|
||
|
||
核心不透明句柄为 `IedServer`,所有服务端操作围绕它展开。
|
||
|
||
### 关键头文件依赖
|
||
|
||
```
|
||
iec61850_server.h
|
||
├── iec61850_dynamic_model.h ← 动态创建数据模型(IedModel/LD/LN/DO/DA/RCB/DataSet)
|
||
├── iec61850_model.h ← 静态模型(自动生成)
|
||
├── iec61850_common.h ← FC/Quality/Timestamp/ControlModel
|
||
├── mms_server.h ← 底层 MMS 服务器
|
||
├── mms_value.h ← MmsValue 数据类型
|
||
└── iso_connection_parameters.h ← ISO 连接参数
|
||
```
|
||
|
||
### 服务端完整工作流
|
||
|
||
```
|
||
1. 创建数据模型 (IedModel → LD → LN → DO → DA → RCB → DataSet)
|
||
2. 创建配置 (IedServerConfig)
|
||
3. 创建服务器 (IedServer_createWithConfig)
|
||
4. 设置各种回调 (控制/RCB/写访问/读访问/连接认证)
|
||
5. 启动服务器 (IedServer_start) 或非线程模式启动 (IedServer_startThreadless)
|
||
6. 运行时更新数据值 (IedServer_update*AttributeValue)
|
||
7. 停止/销毁 (IedServer_stop + IedServer_destroy)
|
||
```
|
||
|
||
---
|
||
|
||
## 2. 数据模型创建(动态模型)
|
||
|
||
### 2.1 模型层级结构
|
||
|
||
```
|
||
IedModel (IED)
|
||
└─ LogicalDevice (LD) "TEMPLATE"
|
||
├─ LogicalNode (LN) "LLN0" ← 每个LD必须包含LLN0
|
||
│ ├─ DataObject (DO) "Mod" ← 模式
|
||
│ ├─ DataObject (DO) "Beh" ← 行为
|
||
│ ├─ DataObject (DO) "Health"
|
||
│ ├─ ReportControlBlock (RCB)
|
||
│ ├─ DataSet
|
||
│ └─ SettingGroupControlBlock (SGCB)
|
||
└─ LogicalNode (LN) "GGIO1" ← 通用IO
|
||
├─ DataObject (DO) "Ind1" (array=0)
|
||
│ ├─ DataAttribute (DA) "stVal" [FC=ST, type=FLOAT32, trgOps=dchg]
|
||
│ └─ DataAttribute (DA) "q" [FC=ST, type=QUALITY]
|
||
└─ DataObject (DO) "SPCSO1"
|
||
├─ DataAttribute (DA) "ctlVal" [FC=CO, type=BOOLEAN]
|
||
└─ DataAttribute (DA) "stVal" [FC=ST, type=BOOLEAN, trgOps=dchg]
|
||
```
|
||
|
||
### 2.2 完整创建流程
|
||
|
||
```c
|
||
// 1. 创建 IED 模型
|
||
IedModel* model = IedModel_create("MyIED");
|
||
IedModel_setIedNameForDynamicModel(model, "RTU"); // 必须在 IedServer_create 前调用
|
||
|
||
// 2. 创建逻辑设备
|
||
LogicalDevice* ld = LogicalDevice_create("TEMPLATE", model);
|
||
|
||
// 3. 创建 LLN0 逻辑节点(每个 LD 必须包含)
|
||
LogicalNode* ln0 = LogicalNode_create("LLN0", ld);
|
||
|
||
// 4. 创建 LLN0 下的常用数据对象
|
||
DataObject* mod = DataObject_create("Mod", ln0, 0);
|
||
DataObject* beh = DataObject_create("Beh", ln0, 0);
|
||
DataObject* health = DataObject_create("Health", ln0, 0);
|
||
|
||
// 5. 创建应用逻辑节点 GGIO1
|
||
LogicalNode* ggio1 = LogicalNode_create("GGIO1", ld);
|
||
|
||
// 6. 创建数据对象 Ind1(单点遥信)
|
||
DataObject* ind1 = DataObject_create("Ind1", ggio1, 0);
|
||
|
||
// 7. 创建数据属性 stVal(状态值)+ q(品质)+ t(时标)
|
||
// DataAttribute_create(名称, 父节点, 类型, FC, 触发选项, 数组大小, 短地址)
|
||
DataAttribute* stVal = DataAttribute_create("stVal", ind1,
|
||
IEC61850_FLOAT32, // 类型
|
||
IEC61850_FC_ST, // 功能约束
|
||
TRG_OPT_DATA_CHANGED, // 数据变化时触发报告
|
||
0, // 非数组
|
||
NULL); // 无短地址
|
||
|
||
DataAttribute* q = DataAttribute_create("q", ind1,
|
||
IEC61850_QUALITY, IEC61850_FC_ST, 0, 0, NULL);
|
||
|
||
DataAttribute* t = DataAttribute_create("t", ind1,
|
||
IEC61850_TIMESTAMP, IEC61850_FC_ST, 0, 0, NULL);
|
||
|
||
// 8. 创建可控数据对象(遥控)
|
||
DataObject* spcso1 = DataObject_create("SPCSO1", ggio1, 0);
|
||
DataAttribute* ctlVal = DataAttribute_create("ctlVal", spcso1,
|
||
IEC61850_BOOLEAN, IEC61850_FC_CO, 0, 0, NULL);
|
||
DataAttribute* stVal2 = DataAttribute_create("stVal", spcso1,
|
||
IEC61850_BOOLEAN, IEC61850_FC_ST, TRG_OPT_DATA_CHANGED, 0, NULL);
|
||
DataAttribute* q2 = DataAttribute_create("q", spcso1,
|
||
IEC61850_QUALITY, IEC61850_FC_ST, 0, 0, NULL);
|
||
DataAttribute* t2 = DataAttribute_create("t", spcso1,
|
||
IEC61850_TIMESTAMP, IEC61850_FC_ST, 0, 0, NULL);
|
||
```
|
||
|
||
### 2.3 创建报告控制块(RCB)
|
||
|
||
```c
|
||
// 在 LLN0 下创建 RCB
|
||
ReportControlBlock* rcb = ReportControlBlock_create(
|
||
"EventsRCB01", // 名称(会变成 LLN0.RP.EventsRCB01 或 .BR.)
|
||
ln0, // 父节点 LLN0
|
||
NULL, // rptId,NULL = 使用默认(对象引用)
|
||
false, // isBuffered: false=URCB, true=BRCB
|
||
"TEMPLATE/LLN0$Events", // 数据集引用
|
||
1, // confRef: 配置版本
|
||
TRG_OPT_DATA_CHANGED | TRG_OPT_INTEGRITY | TRG_OPT_GI, // trgOps
|
||
RPT_OPT_SEQ_NUM | RPT_OPT_TIME_STAMP |
|
||
RPT_OPT_REASON_FOR_INCLUSION | RPT_OPT_DATA_SET |
|
||
RPT_OPT_DATA_REFERENCE, // options (OptFlds)
|
||
100, // bufTm: 缓冲时间 100ms
|
||
60000 // intgPd: 完整性周期 60s
|
||
);
|
||
|
||
// 设置预配置客户端(可选,用于指定哪些客户端可以使用此RCB)
|
||
// uint8_t ipv4[4] = {192, 168, 1, 100};
|
||
// ReportControlBlock_setPreconfiguredClient(rcb, 4, ipv4);
|
||
|
||
// RCB 信息查询
|
||
const char* name = ReportControlBlock_getName(rcb);
|
||
bool isBuffered = ReportControlBlock_isBuffered(rcb); // true=BRCB, false=URCB
|
||
LogicalNode* parent = ReportControlBlock_getParent(rcb);
|
||
char* rptId = ReportControlBlock_getRptID(rcb); // 需手动 free
|
||
bool ena = ReportControlBlock_getRptEna(rcb); // 当前是否使能
|
||
char* ds = ReportControlBlock_getDataSet(rcb); // 需手动 free
|
||
uint32_t confRev = ReportControlBlock_getConfRev(rcb);
|
||
uint32_t optFlds = ReportControlBlock_getOptFlds(rcb);
|
||
uint32_t bufTm = ReportControlBlock_getBufTm(rcb);
|
||
uint16_t sqNum = ReportControlBlock_getSqNum(rcb); // 当前序列号
|
||
uint32_t trgOps = ReportControlBlock_getTrgOps(rcb);
|
||
uint32_t intgPd = ReportControlBlock_getIntgPd(rcb);
|
||
bool gi = ReportControlBlock_getGI(rcb);
|
||
bool purgeBuf = ReportControlBlock_getPurgeBuf(rcb);
|
||
MmsValue* entryId = ReportControlBlock_getEntryId(rcb);
|
||
uint64_t timeOfEntry = ReportControlBlock_getTimeofEntry(rcb);
|
||
int16_t resvTms = ReportControlBlock_getResvTms(rcb);
|
||
bool resv = ReportControlBlock_getResv(rcb);
|
||
MmsValue* owner = ReportControlBlock_getOwner(rcb);
|
||
```
|
||
|
||
### 2.4 创建数据集(DataSet)
|
||
|
||
```c
|
||
// 创建数据集
|
||
DataSet* ds = DataSet_create("Events", ln0); // 名称 + LLN0 父节点
|
||
|
||
// 添加数据集成员(FCDA 引用)
|
||
// DataSetEntry_create(数据集, 变量名, 索引, 组件名)
|
||
// 变量名格式: "LN名$FC$DO名$DA名"(用$而非.分隔,不要LD名前缀)
|
||
DataSetEntry_create(ds, "GGIO1$ST$Ind1$stVal", -1, NULL);
|
||
DataSetEntry_create(ds, "GGIO1$ST$Ind1$q", -1, NULL);
|
||
DataSetEntry_create(ds, "GGIO1$ST$Ind1$t", -1, NULL);
|
||
|
||
// 数据集查询
|
||
const char* dsName = DataSet_getName(ds);
|
||
int dsSize = DataSet_getSize(ds); // 成员数
|
||
DataSetEntry* first = DataSet_getFirstEntry(ds);
|
||
DataSetEntry* next = DataSetEntry_getNext(first);
|
||
```
|
||
|
||
### 2.5 设置数据属性默认值
|
||
|
||
```c
|
||
// 在创建 IedServer 之前可以设置默认值
|
||
MmsValue* defaultVal = MmsValue_newFloat(0.0f);
|
||
DataAttribute_setValue(stVal, defaultVal);
|
||
MmsValue_delete(defaultVal);
|
||
```
|
||
|
||
### 2.6 DA 属性查询
|
||
|
||
```c
|
||
DataAttributeType DataAttribute_getType(DataAttribute* self);
|
||
FunctionalConstraint DataAttribute_getFC(DataAttribute* self);
|
||
uint8_t DataAttribute_getTrgOps(DataAttribute* self);
|
||
```
|
||
|
||
### 2.7 创建其他控制块
|
||
|
||
```c
|
||
// 定值组控制块
|
||
SettingGroupControlBlock* sgcb = SettingGroupControlBlock_create(
|
||
ln0, // 父节点 LLN0
|
||
1, // actSG: 启动时活跃定值组
|
||
8); // numOfSGs: 定值组数量
|
||
|
||
// GOOSE 控制块
|
||
GSEControlBlock* gcb = GSEControlBlock_create(
|
||
"gcbEvents", ln0, "appId", "TEMPLATE/LLN0$GooseDS",
|
||
1, // confRev
|
||
false, // fixedOffs
|
||
-1, // minTime,-1=使用默认
|
||
-1); // maxTime,-1=使用默认
|
||
GSEControlBlock_addPhyComAddress(gcb, phyComAddress);
|
||
|
||
// Sampled Values 控制块
|
||
SVControlBlock* svcb = SVControlBlock_create(
|
||
"MSVCB01", ln0, "svID", "TEMPLATE/LLN0$SvDS",
|
||
1, // confRev
|
||
IEC61850_SV_SMPMOD_SAMPLES_PER_PERIOD, // smpMod
|
||
80, // smpRate (如 80 采样/周期)
|
||
IEC61850_SV_OPT_REFRESH_TIME | IEC61850_SV_OPT_SAMPLE_SYNC, // optFlds
|
||
false); // isUnicast: false=multicast
|
||
SVControlBlock_addPhyComAddress(svcb, phyComAddress);
|
||
|
||
// 日志控制块
|
||
LogControlBlock* lcb = LogControlBlock_create(
|
||
"LogCB01", ln0, "TEMPLATE/LLN0$Events",
|
||
"TEMPLATE/LLN0$MyLog", TRG_OPT_DATA_CHANGED, 60000, false, true);
|
||
|
||
// 日志对象
|
||
Log* log = Log_create("MyLog", ln0);
|
||
|
||
// PhyComAddress
|
||
uint8_t mac[6] = {0x01, 0x0C, 0xCD, 0x01, 0x00, 0x01};
|
||
PhyComAddress* addr = PhyComAddress_create(
|
||
4, // vlanPriority
|
||
100, // vlanId
|
||
0x4000, // appId
|
||
mac); // dstAddress
|
||
```
|
||
|
||
### 2.8 模型销毁
|
||
|
||
```c
|
||
// 销毁动态创建的数据模型
|
||
// 注意:一定要在 IedServer_destroy 之后调用,否则会导致资源泄漏
|
||
IedModel_destroy(model);
|
||
```
|
||
|
||
### 2.9 数据属性类型枚举(DataAttributeType)
|
||
|
||
创建 DA 时使用的类型常量:
|
||
|
||
```c
|
||
IEC61850_BOOLEAN // MMS_BOOLEAN
|
||
IEC61850_INT8 // MMS_INTEGER (8bit)
|
||
IEC61850_INT16 // MMS_INTEGER (16bit)
|
||
IEC61850_INT32 // MMS_INTEGER (32bit)
|
||
IEC61850_INT64 // MMS_INTEGER (64bit)
|
||
IEC61850_INT8U // MMS_UNSIGNED (8bit)
|
||
IEC61850_INT16U // MMS_UNSIGNED (16bit)
|
||
IEC61850_INT32U // MMS_UNSIGNED (32bit)
|
||
IEC61850_FLOAT32 // MMS_FLOAT (32bit)
|
||
IEC61850_FLOAT64 // MMS_FLOAT (64bit)
|
||
IEC61850_QUALITY // MMS_BIT_STRING (13bit, 品质)
|
||
IEC61850_TIMESTAMP // MMS_UTC_TIME
|
||
IEC61850_VISSTRING32 // MMS_VISIBLE_STRING (max 32)
|
||
IEC61850_VISSTRING64 // MMS_VISIBLE_STRING (max 64)
|
||
IEC61850_VISSTRING129 // MMS_VISIBLE_STRING (max 129)
|
||
IEC61850_VISSTRING255 // MMS_VISIBLE_STRING (max 255)
|
||
IEC61850_DBPOS // MMS_BIT_STRING (双点位置)
|
||
IEC61850_CONSTRUCTED // 复合类型(如 AnalogueValue)
|
||
```
|
||
|
||
---
|
||
|
||
## 3. 服务器配置(IedServerConfig)
|
||
|
||
### 3.1 配置结构体字段
|
||
|
||
```c
|
||
typedef struct sIedServerConfig {
|
||
int reportBufferSize; // BRCB 报告缓冲区大小
|
||
int reportBufferSizeURCBs; // URCB 报告缓冲区大小
|
||
char* fileServiceBasepath; // 文件服务根目录
|
||
bool enableFileService; // 是否启用文件服务
|
||
bool enableDynamicDataSetService; // 是否允许动态数据集
|
||
int maxAssociationSpecificDataSets; // 每连接最大关联数据集数
|
||
int maxDomainSpecificDataSets; // 最大域数据集数
|
||
int maxDataSetEntries; // 数据集最大条目数
|
||
bool enableLogService; // 是否启用日志服务
|
||
bool useIntegratedGoosePublisher; // 是否使用内置 GOOSE 发布器
|
||
uint8_t edition; // IEC 61850 版本:0=E1, 1=E2, 2=E2.1
|
||
int maxMmsConnections; // 最大 MMS 连接数
|
||
bool enableEditSG; // 是否允许 EditSG 服务
|
||
bool enableResvTmsForSGCB; // SGCB 是否可见 ResvTms
|
||
bool enableResvTmsForBRCB; // BRCB 是否可见 ResvTms
|
||
bool enableOwnerForRCB; // RCB 是否可见 owner 属性
|
||
bool syncIntegrityReportTimes; // 是否同步完整性报告时间
|
||
uint8_t reportSettingsWritable; // 哪些报告设置可写(位掩码)
|
||
} IedServerConfig;
|
||
```
|
||
|
||
### 3.2 配置 API
|
||
|
||
```c
|
||
IedServerConfig config = IedServerConfig_create();
|
||
|
||
// 标准配置
|
||
IedServerConfig_setReportBufferSize(config, 100); // BRCB 缓冲区
|
||
IedServerConfig_setReportBufferSizeForURCBs(config, 10); // URCB 缓冲区
|
||
IedServerConfig_setMaxMmsConnections(config, 5); // 最大连接数
|
||
IedServerConfig_setFileServiceBasePath(config, "./files");
|
||
IedServerConfig_enableFileService(config, true);
|
||
IedServerConfig_setEdition(config, IEC_61850_EDITION_2); // 版本
|
||
|
||
// 动态数据集
|
||
IedServerConfig_enableDynamicDataSetService(config, true);
|
||
IedServerConfig_setMaxAssociationSpecificDataSets(config, 10);
|
||
IedServerConfig_setMaxDomainSpecificDataSets(config, 5);
|
||
IedServerConfig_setMaxDataSetEntries(config, 128);
|
||
|
||
// 日志
|
||
IedServerConfig_enableLogService(config, false);
|
||
|
||
// 定值组
|
||
IedServerConfig_enableEditSG(config, true);
|
||
IedServerConfig_enableResvTmsForSGCB(config, true);
|
||
|
||
// RCB
|
||
IedServerConfig_enableResvTmsForBRCB(config, true);
|
||
IedServerConfig_enableOwnerForRCB(config, false);
|
||
|
||
// GOOSE
|
||
IedServerConfig_useIntegratedGoosePublisher(config, true);
|
||
|
||
// 完整性报告时间同步
|
||
IedServerConfig_setSyncIntegrityReportTimes(config, false);
|
||
|
||
// 报告设置可写性(IEC61850_REPORTSETTINGS_* 常量组合)
|
||
IedServerConfig_setReportSetting(config, IEC61850_REPORTSETTINGS_RPT_ID, true); // 允许客户端修改 RptID
|
||
IedServerConfig_setReportSetting(config, IEC61850_REPORTSETTINGS_DATSET, true); // 允许客户端修改数据集
|
||
|
||
// 销毁配置
|
||
IedServerConfig_destroy(config);
|
||
```
|
||
|
||
**报告设置可写性常量**:
|
||
|
||
```c
|
||
#define IEC61850_REPORTSETTINGS_RPT_ID 1
|
||
#define IEC61850_REPORTSETTINGS_BUF_TIME 2
|
||
#define IEC61850_REPORTSETTINGS_DATSET 4
|
||
#define IEC61850_REPORTSETTINGS_TRG_OPS 8
|
||
#define IEC61850_REPORTSETTINGS_OPT_FIELDS 16
|
||
#define IEC61850_REPORTSETTINGS_INTG_PD 32
|
||
```
|
||
|
||
### 3.3 查询配置
|
||
|
||
```c
|
||
uint8_t edition = IedServerConfig_getEdition(config);
|
||
int bufSize = IedServerConfig_getReportBufferSize(config);
|
||
int urcbBufSize = IedServerConfig_getReportBufferSizeForURCBs(config);
|
||
int maxConn = IedServerConfig_getMaxMmsConnections(config);
|
||
const char* path = IedServerConfig_getFileServiceBasePath(config);
|
||
bool fsEnabled = IedServerConfig_isFileServiceEnabled(config);
|
||
bool dsEnabled = IedServerConfig_isDynamicDataSetServiceEnabled(config);
|
||
int maxAssocDS = IedServerConfig_getMaxAssociationSpecificDataSets(config);
|
||
int maxDomainDS = IedServerConfig_getMaxDomainSpecificDataSets(config);
|
||
int maxDsEntries = IedServerConfig_getMaxDatasSetEntries(config);
|
||
bool logEnabled = IedServerConfig_isLogServiceEnabled(config);
|
||
bool syncRt = IedServerConfig_getSyncIntegrityReportTimes(config);
|
||
bool resvTmsBRCB = IedServerConfig_isResvTmsForBRCBEnabled(config);
|
||
bool ownerRCB = IedServerConfig_isOwnerForRCBEnabled(config);
|
||
bool reportSetting = IedServerConfig_getReportSetting(config, IEC61850_REPORTSETTINGS_TRG_OPS);
|
||
```
|
||
|
||
---
|
||
|
||
## 4. 服务器生命周期管理
|
||
|
||
### 4.1 创建服务器
|
||
|
||
```c
|
||
// 简单创建(仅需数据模型)
|
||
IedServer server = IedServer_create(model);
|
||
|
||
// TLS 支持
|
||
IedServer server = IedServer_createWithTlsSupport(model, tlsConfig);
|
||
|
||
// 完整配置创建(推荐)
|
||
IedServerConfig config = IedServerConfig_create();
|
||
// ... 配置 config ...
|
||
IedServer server = IedServer_createWithConfig(model, NULL, config); // NULL=无TLS
|
||
// IedServerConfig_destroy(config); // 创建后可销毁 config
|
||
```
|
||
|
||
### 4.2 附加访问点
|
||
|
||
```c
|
||
// 为服务器添加额外的监听地址(可在 start 前多次添加)
|
||
// 返回 true 成功,false 失败
|
||
IedServer_addAccessPoint(server, "192.168.2.1", 102, NULL);
|
||
```
|
||
|
||
### 4.3 设置服务器属性
|
||
|
||
```c
|
||
// 设置监听地址(默认监听所有接口)
|
||
IedServer_setLocalIpAddress(server, "0.0.0.0");
|
||
|
||
// 设置 MMS identify 服务响应(需要 CONFIG_IEC61850_SUPPORT_SERVER_IDENTITY)
|
||
IedServer_setServerIdentity(server, "VendorName", "ModelName", "1.0");
|
||
|
||
// 设置运行时文件服务根目录(需要 CONFIG_SET_FILESTORE_BASEPATH_AT_RUNTIME)
|
||
IedServer_setFilestoreBasepath(server, "/data/files");
|
||
|
||
// 设置时间品质(可在运行时更新)
|
||
IedServer_setTimeQuality(server,
|
||
true, // leapSecondKnown 位7: 闰秒已知
|
||
false, // clockFailure 位6: 时钟故障
|
||
false, // clockNotSynchronized 位5: 时钟未同步
|
||
10); // subsecondPrecision 位0-4: 亚秒精度(fractionOfSecond 有效位数)
|
||
```
|
||
|
||
### 4.4 启动/停止(线程模式)
|
||
|
||
```c
|
||
// 启动(线程模式)—— 内部创建后台线程
|
||
IedServer_start(server, 102); // 102 = MMS 默认端口,-1 = 使用默认端口
|
||
|
||
// 检查运行状态
|
||
bool running = IedServer_isRunning(server);
|
||
|
||
// 获取当前连接数
|
||
int connections = IedServer_getNumberOfOpenConnections(server);
|
||
|
||
// 停止
|
||
IedServer_stop(server);
|
||
|
||
// 销毁
|
||
IedServer_destroy(server);
|
||
// 注意:需要单独销毁数据模型 IedModel_destroy(model)
|
||
```
|
||
|
||
### 4.5 启动/停止(非线程模式)
|
||
|
||
```c
|
||
// 启动(非线程模式)—— 用户驱动消息循环
|
||
IedServer_startThreadless(server, 102);
|
||
|
||
// 主循环
|
||
while (running) {
|
||
// 等待连接就绪(可选,类似 select)
|
||
int ready = IedServer_waitReady(server, 100); // 超时 100ms
|
||
if (ready != 0) {
|
||
// 处理收到的数据
|
||
IedServer_processIncomingData(server);
|
||
}
|
||
// 执行周期性任务(报告生成、超时检查等)
|
||
IedServer_performPeriodicTasks(server);
|
||
}
|
||
|
||
// 停止
|
||
IedServer_stopThreadless(server);
|
||
|
||
// 销毁
|
||
IedServer_destroy(server);
|
||
```
|
||
|
||
### 4.6 底层访问
|
||
|
||
```c
|
||
// 获取数据模型
|
||
IedModel* model = IedServer_getDataModel(server);
|
||
|
||
// 获取底层 MmsServer(谨慎使用:直接操作可能干扰 IedServer)
|
||
MmsServer mmsServer = IedServer_getMmsServer(server);
|
||
```
|
||
|
||
---
|
||
|
||
## 5. 数据值读取与更新
|
||
|
||
### 5.1 读取属性值
|
||
|
||
```c
|
||
// 通用读取(返回 MmsValue*)
|
||
MmsValue* val = IedServer_getAttributeValue(server, stVal);
|
||
|
||
// 类型便捷读取
|
||
bool b = IedServer_getBooleanAttributeValue(server, da);
|
||
float f = IedServer_getFloatAttributeValue(server, da);
|
||
int32_t i32 = IedServer_getInt32AttributeValue(server, da);
|
||
int64_t i64 = IedServer_getInt64AttributeValue(server, da);
|
||
uint32_t u32 = IedServer_getUInt32AttributeValue(server, da);
|
||
uint64_t utc = IedServer_getUTCTimeAttributeValue(server, da); // ms
|
||
uint32_t bs = IedServer_getBitStringAttributeValue(server, da);
|
||
const char* s = IedServer_getStringAttributeValue(server, da); // VISIBLE_STRING/STRING
|
||
|
||
// 获取某个 FC 下的 FCD 对象(绕过报告通知机制,直接操作底层值)
|
||
MmsValue* fcd = IedServer_getFunctionalConstrainedData(server, dataObject, IEC61850_FC_ST);
|
||
// 警告:直接操作 FCD 不会触发报告,需谨慎使用
|
||
```
|
||
|
||
### 5.2 更新属性值(自动触发报告和 GOOSE)
|
||
|
||
```c
|
||
// 通用更新
|
||
IedServer_updateAttributeValue(server, da, mmsValue);
|
||
|
||
// 类型便捷更新 — 这些函数会自动检查触发条件(dchg/qchg/dupd)并触发报告
|
||
IedServer_updateFloatAttributeValue(server, da, 35.5f);
|
||
IedServer_updateInt32AttributeValue(server, da, 42);
|
||
IedServer_updateInt64AttributeValue(server, da, 12345678901234LL);
|
||
IedServer_updateUnsignedAttributeValue(server, da, 100);
|
||
IedServer_updateBooleanAttributeValue(server, da, true);
|
||
IedServer_updateVisibleStringAttributeValue(server, da, "Hello");
|
||
IedServer_updateBitStringAttributeValue(server, da, bitStringInt);
|
||
IedServer_updateUTCTimeAttributeValue(server, da, msTimestamp);
|
||
IedServer_updateTimestampAttributeValue(server, da, timestamp);
|
||
IedServer_updateDbposValue(server, da, DBPOS_ON); // 双点:ON/OFF/中间态/坏态
|
||
IedServer_updateQuality(server, da, quality); // 品质更新(触发 qchg)
|
||
```
|
||
|
||
### 5.3 批量更新(加锁)
|
||
|
||
```c
|
||
// 更新多个值时加锁以提高效率
|
||
IedServer_lockDataModel(server);
|
||
|
||
IedServer_updateFloatAttributeValue(server, da1, 1.5f);
|
||
IedServer_updateFloatAttributeValue(server, da2, 2.5f);
|
||
IedServer_updateFloatAttributeValue(server, da3, 3.5f);
|
||
|
||
IedServer_unlockDataModel(server); // 解锁后将触发一次通知
|
||
|
||
// 警告:绝对不要在库回调函数内部调用 lockDataModel!
|
||
// 库回调中数据模型已经锁定,再次锁定会导致死锁
|
||
```
|
||
|
||
### 5.4 更新控制模型
|
||
|
||
```c
|
||
// 更新可控数据对象的控制模型
|
||
IedServer_updateCtlModel(server, ctlObject, CONTROL_MODEL_SBO_NORMAL);
|
||
// 注意:对应的控制结构必须在数据模型中存在
|
||
```
|
||
|
||
---
|
||
|
||
## 6. 控制模型回调
|
||
|
||
服务端通过三个层级的回调实现 IEC 61850 的遥控机制。
|
||
|
||
### 6.1 控制操作完整流程
|
||
|
||
```
|
||
客户端发送控制命令
|
||
↓
|
||
PerformCheck 回调(静态测试:联锁、权限等)
|
||
↓ (通过)
|
||
WaitForExecution 回调(动态测试:同步检查等)
|
||
↓ (通过)
|
||
Control 回调(实际执行:操作继电器等)
|
||
↓
|
||
CommandTermination 响应返回客户端
|
||
```
|
||
|
||
### 6.2 回调返回值
|
||
|
||
```c
|
||
// PerformCheck 回调返回值
|
||
typedef enum {
|
||
CONTROL_ACCEPTED = -1, // 检查通过
|
||
CONTROL_WAITING_FOR_SELECT = 0, // 选择进行中(稍后重试)
|
||
CONTROL_HARDWARE_FAULT = 1, // 硬件故障
|
||
CONTROL_TEMPORARILY_UNAVAILABLE = 2,// 暂时不可用(已选中或正操作)
|
||
CONTROL_OBJECT_ACCESS_DENIED = 3, // 拒绝访问
|
||
CONTROL_OBJECT_UNDEFINED = 4, // 对象未定义
|
||
CONTROL_VALUE_INVALID = 11 // ctlVal 超出范围
|
||
} CheckHandlerResult;
|
||
|
||
// Control / WaitForExecution 回调返回值
|
||
typedef enum {
|
||
CONTROL_RESULT_FAILED = 0, // 失败
|
||
CONTROL_RESULT_OK = 1, // 成功
|
||
CONTROL_RESULT_WAITING = 2 // 等待中(异步执行,稍后再次调用)
|
||
} ControlHandlerResult;
|
||
```
|
||
|
||
### 6.3 ControlAction 上下文信息
|
||
|
||
```c
|
||
typedef void* ControlAction;
|
||
|
||
// 设置错误附加信息(在回调中使用)
|
||
void ControlAction_setError(ControlAction self, ControlLastApplError error);
|
||
void ControlAction_setAddCause(ControlAction self, ControlAddCause addCause);
|
||
|
||
// 获取客户端上下文
|
||
int ControlAction_getOrCat(ControlAction self); // 发起者类别
|
||
uint8_t* ControlAction_getOrIdent(ControlAction self, int* size); // 发起者标识
|
||
int ControlAction_getCtlNum(ControlAction self); // 控制序号
|
||
bool ControlAction_getSynchroCheck(ControlAction self); // 同步检查位
|
||
bool ControlAction_getInterlockCheck(ControlAction self); // 联锁检查位
|
||
bool ControlAction_isSelect(ControlAction self); // 是否是 Select 操作
|
||
ClientConnection ControlAction_getClientConnection(ControlAction self); // 客户端连接
|
||
DataObject* ControlAction_getControlObject(ControlAction self); // 控制对象
|
||
uint64_t ControlAction_getControlTime(ControlAction self); // 时间激活控制时间(0=立即)
|
||
```
|
||
|
||
### 6.4 注册控制回调
|
||
|
||
```c
|
||
// 实际执行回调(必须注册)
|
||
CheckHandlerResult myPerformCheck(ControlAction action, void* param,
|
||
MmsValue* ctlVal, bool test, bool interlockCheck) {
|
||
// 静态测试:检查联锁条件、访问权限等
|
||
if (!interlock_ok) {
|
||
ControlAction_setAddCause(action, ADD_CAUSE_BLOCKED_BY_INTERLOCKING);
|
||
return CONTROL_OBJECT_ACCESS_DENIED;
|
||
}
|
||
return CONTROL_ACCEPTED;
|
||
}
|
||
|
||
ControlHandlerResult myWaitForExecution(ControlAction action, void* param,
|
||
MmsValue* ctlVal, bool test, bool synchroCheck) {
|
||
// 动态测试:检查同步条件等
|
||
// 如果测试不能立即完成,返回 CONTROL_RESULT_WAITING,稍后会再次调用
|
||
if (needs_wait) return CONTROL_RESULT_WAITING;
|
||
return CONTROL_RESULT_OK;
|
||
}
|
||
|
||
ControlHandlerResult myControl(ControlAction action, void* param,
|
||
MmsValue* ctlVal, bool test) {
|
||
// 实际执行:控制继电器、输出信号等
|
||
bool val = MmsValue_getBoolean(ctlVal);
|
||
if (test) {
|
||
// 测试模式:不影响实际物理过程
|
||
return CONTROL_RESULT_OK;
|
||
}
|
||
set_output(val); // 控制硬件
|
||
return CONTROL_RESULT_OK;
|
||
}
|
||
|
||
// 注册三个层级的回调
|
||
IedServer_setControlHandler(server, spcso1, myControl, myParam);
|
||
IedServer_setPerformCheckHandler(server, spcso1, myPerformCheck, myParam);
|
||
IedServer_setWaitForExecutionHandler(server, spcso1, myWaitForExecution, myParam);
|
||
```
|
||
|
||
### 6.5 Select 状态变化回调
|
||
|
||
```c
|
||
typedef enum {
|
||
SELECT_STATE_REASON_SELECTED, // 被选中
|
||
SELECT_STATE_REASON_CANCELED, // 取消
|
||
SELECT_STATE_REASON_TIMEOUT, // 超时(sboTimeout)
|
||
SELECT_STATE_REASON_OPERATED, // 操作成功
|
||
SELECT_STATE_REASON_OPERATE_FAILED, // 操作失败
|
||
SELECT_STATE_REASON_DISCONNECTED // 选中客户端断开连接
|
||
} SelectStateChangedReason;
|
||
|
||
void mySelectStateChanged(ControlAction action, void* param,
|
||
bool isSelected, SelectStateChangedReason reason) {
|
||
if (isSelected) {
|
||
// 被客户端选中
|
||
} else {
|
||
// 取消选中(原因见 reason)
|
||
}
|
||
}
|
||
|
||
IedServer_setSelectStateChangedHandler(server, spcso1, mySelectStateChanged, myParam);
|
||
```
|
||
|
||
### 6.6 AddCause 完整枚举
|
||
|
||
```c
|
||
typedef enum {
|
||
ADD_CAUSE_UNKNOWN = 0,
|
||
ADD_CAUSE_NOT_SUPPORTED = 1,
|
||
ADD_CAUSE_BLOCKED_BY_SWITCHING_HIERARCHY = 2,
|
||
ADD_CAUSE_SELECT_FAILED = 3,
|
||
ADD_CAUSE_INVALID_POSITION = 4,
|
||
ADD_CAUSE_POSITION_REACHED = 5,
|
||
ADD_CAUSE_PARAMETER_CHANGE_IN_EXECUTION = 6,
|
||
ADD_CAUSE_STEP_LIMIT = 7,
|
||
ADD_CAUSE_BLOCKED_BY_MODE = 8,
|
||
ADD_CAUSE_BLOCKED_BY_PROCESS = 9,
|
||
ADD_CAUSE_BLOCKED_BY_INTERLOCKING = 10,
|
||
ADD_CAUSE_BLOCKED_BY_SYNCHROCHECK = 11,
|
||
ADD_CAUSE_COMMAND_ALREADY_IN_EXECUTION = 12,
|
||
ADD_CAUSE_BLOCKED_BY_HEALTH = 13,
|
||
ADD_CAUSE_1_OF_N_CONTROL = 14,
|
||
ADD_CAUSE_ABORTION_BY_CANCEL = 15,
|
||
ADD_CAUSE_TIME_LIMIT_OVER = 16,
|
||
ADD_CAUSE_ABORTION_BY_TRIP = 17,
|
||
ADD_CAUSE_OBJECT_NOT_SELECTED = 18,
|
||
ADD_CAUSE_OBJECT_ALREADY_SELECTED = 19,
|
||
ADD_CAUSE_NO_ACCESS_AUTHORITY = 20,
|
||
ADD_CAUSE_ENDED_WITH_OVERSHOOT = 21,
|
||
ADD_CAUSE_ABORTION_DUE_TO_DEVIATION = 22,
|
||
ADD_CAUSE_ABORTION_BY_COMMUNICATION_LOSS = 23,
|
||
ADD_CAUSE_ABORTION_BY_COMMAND = 24,
|
||
ADD_CAUSE_NONE = 25,
|
||
ADD_CAUSE_INCONSISTENT_PARAMETERS = 26,
|
||
ADD_CAUSE_LOCKED_BY_OTHER_CLIENT = 27
|
||
} ControlAddCause;
|
||
```
|
||
|
||
---
|
||
|
||
## 7. 报告控制块(RCB)事件处理
|
||
|
||
### 7.1 RCB 事件类型
|
||
|
||
```c
|
||
typedef enum {
|
||
RCB_EVENT_GET_PARAMETER, // 参数被读取(暂未实现)
|
||
RCB_EVENT_SET_PARAMETER, // 参数被客户端设置
|
||
RCB_EVENT_UNRESERVED, // 取消预留
|
||
RCB_EVENT_RESERVED, // 被预留
|
||
RCB_EVENT_ENABLE, // 被使能
|
||
RCB_EVENT_DISABLE, // 被停用
|
||
RCB_EVENT_GI, // 总召触发
|
||
RCB_EVENT_PURGEBUF, // 清除缓冲区
|
||
RCB_EVENT_OVERFLOW, // 报告缓冲区溢出
|
||
RCB_EVENT_REPORT_CREATED // 新报告创建并插入缓冲区
|
||
} IedServer_RCBEventType;
|
||
```
|
||
|
||
### 7.2 完整 RCB 事件回调
|
||
|
||
```c
|
||
void rcbEventHandler(void* parameter, ReportControlBlock* rcb,
|
||
ClientConnection connection, IedServer_RCBEventType event,
|
||
const char* parameterName, MmsDataAccessError serviceError) {
|
||
|
||
const char* rcbName = ReportControlBlock_getName(rcb);
|
||
char* rptId = ReportControlBlock_getRptID(rcb);
|
||
char* dataSet = ReportControlBlock_getDataSet(rcb);
|
||
|
||
switch (event) {
|
||
case RCB_EVENT_ENABLE:
|
||
// 客户端使能了 RCB → 可以开始上送数据
|
||
printf("RCB %s (RptID=%s) 已使能\n", rcbName, rptId);
|
||
break;
|
||
|
||
case RCB_EVENT_DISABLE:
|
||
// 客户端停用了 RCB → 暂停上送
|
||
break;
|
||
|
||
case RCB_EVENT_RESERVED:
|
||
// 客户端独占了 URCB
|
||
break;
|
||
|
||
case RCB_EVENT_UNRESERVED:
|
||
// 客户端释放了 URCB
|
||
break;
|
||
|
||
case RCB_EVENT_GI:
|
||
// 客户端触发了总召
|
||
// 库会自动执行总召,这里可以记录日志
|
||
break;
|
||
|
||
case RCB_EVENT_SET_PARAMETER:
|
||
if (serviceError != DATA_ACCESS_ERROR_SUCCESS) {
|
||
// 参数设置失败
|
||
printf("RCB 参数设置失败: param=%s, err=%d\n",
|
||
parameterName, serviceError);
|
||
} else {
|
||
// 参数设置成功
|
||
if (parameterName && strcmp(parameterName, "RptEna") == 0) {
|
||
bool ena = ReportControlBlock_getRptEna(rcb);
|
||
// ...
|
||
}
|
||
if (parameterName && strcmp(parameterName, "DatSet") == 0) {
|
||
// 客户端改变了数据集 → 需要更新本地 report 数据结构
|
||
// 重新调用 IedConnection_installReportHandler(客户端场景)
|
||
}
|
||
}
|
||
break;
|
||
|
||
case RCB_EVENT_OVERFLOW:
|
||
// 报告缓冲区溢出(仅 BRCB)
|
||
// 可能需要重新配置缓冲区大小或检查周期性积分周期
|
||
break;
|
||
|
||
case RCB_EVENT_REPORT_CREATED:
|
||
// 新报告已创建(可用于调试/监控)
|
||
break;
|
||
|
||
case RCB_EVENT_PURGEBUF:
|
||
// 客户端清除了缓冲区
|
||
break;
|
||
|
||
default:
|
||
break;
|
||
}
|
||
|
||
free(rptId);
|
||
free(dataSet);
|
||
}
|
||
|
||
// 注册
|
||
IedServer_setRCBEventHandler(server, rcbEventHandler, myParam);
|
||
```
|
||
|
||
---
|
||
|
||
## 8. 读写访问控制
|
||
|
||
### 8.1 写访问控制(单个数据属性)
|
||
|
||
```c
|
||
// 写访问回调
|
||
MmsDataAccessError myWriteAccessHandler(
|
||
DataAttribute* dataAttribute, MmsValue* value,
|
||
ClientConnection connection, void* parameter) {
|
||
|
||
// 检查客户端是否有写权限
|
||
if (!client_has_permission(connection)) {
|
||
return DATA_ACCESS_ERROR_OBJECT_ACCESS_DENIED;
|
||
}
|
||
|
||
// 检查值是否在允许范围内(可选)
|
||
if (MmsValue_toFloat(value) > 100.0f) {
|
||
return DATA_ACCESS_ERROR_OBJECT_VALUE_INVALID;
|
||
}
|
||
|
||
return DATA_ACCESS_ERROR_SUCCESS; // 接受,库自动更新值
|
||
// 或 return DATA_ACCESS_ERROR_SUCCESS_NO_UPDATE; // 接受但库不更新(自定义逻辑)
|
||
}
|
||
|
||
// 为单个数据属性注册写访问控制
|
||
IedServer_handleWriteAccess(server, stVal, myWriteAccessHandler, myParam);
|
||
|
||
// 为复合属性及其所有子属性注册写访问控制
|
||
IedServer_handleWriteAccessForComplexAttribute(server, complexDA, myWriteAccessHandler, myParam);
|
||
```
|
||
|
||
### 8.2 全局写访问策略
|
||
|
||
```c
|
||
typedef enum {
|
||
ACCESS_POLICY_ALLOW, // 允许
|
||
ACCESS_POLICY_DENY // 拒绝
|
||
} AccessPolicy;
|
||
|
||
// 设置某个 FC 的全局默认写访问策略
|
||
IedServer_setWriteAccessPolicy(server, IEC61850_FC_SP, ACCESS_POLICY_DENY);
|
||
IedServer_setWriteAccessPolicy(server, IEC61850_FC_SE, ACCESS_POLICY_ALLOW);
|
||
```
|
||
|
||
### 8.3 读访问控制(全局)
|
||
|
||
```c
|
||
// 全局读访问回调(对每个读请求调用)
|
||
MmsDataAccessError myReadAccessHandler(
|
||
LogicalDevice* ld, LogicalNode* ln, DataObject* dataObject,
|
||
FunctionalConstraint fc, ClientConnection connection, void* parameter) {
|
||
|
||
// 示例:禁止特定客户端读取 CO 数据
|
||
if (fc == IEC61850_FC_CO && is_restricted_client(connection)) {
|
||
return DATA_ACCESS_ERROR_OBJECT_ACCESS_DENIED;
|
||
}
|
||
|
||
return DATA_ACCESS_ERROR_SUCCESS;
|
||
}
|
||
|
||
IedServer_setReadAccessHandler(server, myReadAccessHandler, myParam);
|
||
```
|
||
|
||
---
|
||
|
||
## 9. 定值组(SGCB)处理
|
||
|
||
### 9.1 内部切换定值组
|
||
|
||
```c
|
||
// 内部事件导致活跃定值组变化
|
||
IedServer_changeActiveSettingGroup(server, sgcb, 3); // 切换到第3组
|
||
|
||
// 获取当前活跃定值组号
|
||
uint8_t activeSG = IedServer_getActiveSettingGroup(server, sgcb);
|
||
```
|
||
|
||
### 9.2 定值组变化回调
|
||
|
||
```c
|
||
// 活跃定值组切换前回调(可拒绝切换)
|
||
bool myActiveSGChanged(void* parameter, SettingGroupControlBlock* sgcb,
|
||
uint8_t newActSg, ClientConnection connection) {
|
||
if (newActSg > 8) return false; // 拒绝无效的定值组
|
||
// 更新 SG 相关数据属性(FC=SG)
|
||
return true; // 接受
|
||
}
|
||
IedServer_setActiveSettingGroupChangedHandler(server, sgcb, myActiveSGChanged, param);
|
||
|
||
// 编辑定值组切换前回调
|
||
bool myEditSGChanged(void* parameter, SettingGroupControlBlock* sgcb,
|
||
uint8_t newEditSg, ClientConnection connection) {
|
||
// 更新 SE 数据属性
|
||
return true;
|
||
}
|
||
IedServer_setEditSettingGroupChangedHandler(server, sgcb, myEditSGChanged, param);
|
||
|
||
// 编辑定值组确认回调
|
||
void myEditSGConfirmed(void* parameter, SettingGroupControlBlock* sgcb, uint8_t editSg) {
|
||
// 编辑组已确认,将 SG 数据复制到 SE 组
|
||
}
|
||
IedServer_setEditSettingGroupConfirmationHandler(server, sgcb, myEditSGConfirmed, param);
|
||
```
|
||
|
||
---
|
||
|
||
## 10. GOOSE 发布
|
||
|
||
### 10.1 启用/禁用 GOOSE
|
||
|
||
```c
|
||
// 批量启用所有 GoCB
|
||
IedServer_enableGoosePublishing(server);
|
||
|
||
// 批量禁用所有 GoCB
|
||
IedServer_disableGoosePublishing(server);
|
||
|
||
// 设置 GOOSE 网络接口(操作系统相关)
|
||
IedServer_setGooseInterfaceId(server, "eth0");
|
||
|
||
// 设置特定 GoCB 的网络接口
|
||
IedServer_setGooseInterfaceIdEx(server, someLN, "gcbEvents", "eth1");
|
||
|
||
// 启用/禁用 VLAN 标签(全局或特定 GoCB)
|
||
IedServer_useGooseVlanTag(server, NULL, NULL, true); // 全部启用
|
||
IedServer_useGooseVlanTag(server, someLN, "gcbEvents", false); // 特定 GoCB 禁用
|
||
```
|
||
|
||
### 10.2 GoCB 事件回调
|
||
|
||
```c
|
||
void goCBEventHandler(MmsGooseControlBlock goCb, int event, void* parameter) {
|
||
if (event == IEC61850_GOCB_EVENT_ENABLE) {
|
||
char* name = MmsGooseControlBlock_getName(goCb);
|
||
LogicalNode* ln = MmsGooseControlBlock_getLogicalNode(goCb);
|
||
DataSet* ds = MmsGooseControlBlock_getDataSet(goCb);
|
||
bool enabled = MmsGooseControlBlock_getGoEna(goCb);
|
||
int minTime = MmsGooseControlBlock_getMinTime(goCb);
|
||
int maxTime = MmsGooseControlBlock_getMaxTime(goCb);
|
||
bool fixedOffs = MmsGooseControlBlock_getFixedOffs(goCb);
|
||
bool ndsCom = MmsGooseControlBlock_getNdsCom(goCb);
|
||
free(name);
|
||
}
|
||
// event == IEC61850_GOCB_EVENT_DISABLE 停用
|
||
}
|
||
|
||
IedServer_setGoCBHandler(server, goCBEventHandler, myParam);
|
||
```
|
||
|
||
### 10.3 GoCB 信息查询
|
||
|
||
```c
|
||
char* MmsGooseControlBlock_getName(MmsGooseControlBlock self);
|
||
LogicalNode* MmsGooseControlBlock_getLogicalNode(MmsGooseControlBlock self);
|
||
DataSet* MmsGooseControlBlock_getDataSet(MmsGooseControlBlock self);
|
||
bool MmsGooseControlBlock_getGoEna(MmsGooseControlBlock self);
|
||
int MmsGooseControlBlock_getMinTime(MmsGooseControlBlock self);
|
||
int MmsGooseControlBlock_getMaxTime(MmsGooseControlBlock self);
|
||
bool MmsGooseControlBlock_getFixedOffs(MmsGooseControlBlock self);
|
||
bool MmsGooseControlBlock_getNdsCom(MmsGooseControlBlock self);
|
||
```
|
||
|
||
---
|
||
|
||
## 11. SV 控制块
|
||
|
||
```c
|
||
// SV 控制块回调
|
||
void svcBEventHandler(SVControlBlock* svcb, int event, void* parameter) {
|
||
if (event == IEC61850_SVCB_EVENT_ENABLE) {
|
||
// SV 被客户端使能
|
||
}
|
||
// event == IEC61850_SVCB_EVENT_DISABLE 停用
|
||
}
|
||
|
||
IedServer_setSVCBHandler(server, svcb, svcBEventHandler, myParam);
|
||
```
|
||
|
||
---
|
||
|
||
## 12. 连接管理与认证
|
||
|
||
### 12.1 连接认证
|
||
|
||
```c
|
||
// ACSE 认证回调
|
||
bool myAuthenticator(void* parameter, AcseAuthenticationParameter* authParameter) {
|
||
// 从 authParameter 中提取认证信息
|
||
// 返回 true 接受连接,false 拒绝连接
|
||
return true;
|
||
}
|
||
|
||
IedServer_setAuthenticator(server, myAuthenticator, authParam);
|
||
```
|
||
|
||
### 12.2 连接状态变化回调
|
||
|
||
```c
|
||
void connHandler(IedServer self, ClientConnection connection, bool connected, void* parameter) {
|
||
const char* peer = ClientConnection_getPeerAddress(connection);
|
||
const char* local = ClientConnection_getLocalAddress(connection);
|
||
void* token = ClientConnection_getSecurityToken(connection); // 认证令牌
|
||
|
||
if (connected) {
|
||
printf("新客户端连接: %s\n", peer);
|
||
} else {
|
||
printf("客户端断开: %s\n", peer);
|
||
}
|
||
}
|
||
|
||
IedServer_setConnectionIndicationHandler(server, connHandler, myParam);
|
||
```
|
||
|
||
### 12.3 ClientConnection API
|
||
|
||
```c
|
||
const char* ClientConnection_getPeerAddress(ClientConnection self); // 客户端IP
|
||
const char* ClientConnection_getLocalAddress(ClientConnection self); // 服务端本地IP
|
||
void* ClientConnection_getSecurityToken(ClientConnection self); // 认证令牌
|
||
```
|
||
|
||
---
|
||
|
||
## 13. 日志服务
|
||
|
||
```c
|
||
// 设置日志存储(将日志控制块关联到日志存储实例)
|
||
IedServer_setLogStorage(server, "TEMPLATE/LLN0$MyLog", logStorage);
|
||
```
|
||
|
||
---
|
||
|
||
## 14. 运行模式:线程模式 vs 非线程模式
|
||
|
||
### 线程模式(默认,推荐)
|
||
|
||
```c
|
||
IedServer server = IedServer_createWithConfig(model, NULL, config);
|
||
IedServer_start(server, 102);
|
||
// 库内部创建线程处理所有客户端连接和周期性任务
|
||
// 用户只需调用 update 系列函数更新数据
|
||
|
||
IedServer_updateFloatAttributeValue(server, da, value); // 安全,随时可调用
|
||
```
|
||
|
||
### 非线程模式(嵌入式/特殊场景)
|
||
|
||
```c
|
||
IedServer server = IedServer_createWithConfig(model, NULL, config);
|
||
IedServer_startThreadless(server, 102);
|
||
|
||
while (running) {
|
||
int ready = IedServer_waitReady(server, 100);
|
||
if (ready) {
|
||
IedServer_processIncomingData(server);
|
||
}
|
||
IedServer_performPeriodicTasks(server); // 报告、超时等
|
||
}
|
||
|
||
IedServer_stopThreadless(server);
|
||
```
|
||
|
||
**非线程模式的限制**:
|
||
- 必须周期性调用 `processIncomingData` 和 `performPeriodicTasks`
|
||
- 这些函数的调用频率直接影响响应速度和定时精度
|
||
- 报告周期精度取决于 `performPeriodicTasks` 的调用间隔
|
||
|
||
---
|
||
|
||
## 15. 与 RTU 项目对照
|
||
|
||
RTU 项目在 `src/protocol/libmms_s/` 中封装了服务端 API。
|
||
|
||
### 核心对应关系
|
||
|
||
| RTU 封装层 | libiec61850 API |
|
||
|-----------|----------------|
|
||
| `mms_s_init(icd_path, port)` | 完整初始化流程 |
|
||
| `mms_s_get_ied_server_ptr()` → `gp_iedServer` | `IedServer` 全局句柄 |
|
||
| `mms_s_model.cpp : model_init()` | `IedModel_create()` → `LogicalDevice_create()` → ... |
|
||
| `mms_s_value.cpp : mms_s_values_init()` | `IedServer` initializer 回调 → 遍历 DA 设置默认值 |
|
||
| `mms_s_control.cpp : control_init()` | `IedServer_setControlHandler()` |
|
||
| `mms_s_param.cpp : param_init()` | `IedServer_setActiveSettingGroupChangedHandler()` + EditSG |
|
||
| `mms_s_setting.cpp : setting_init()` | 初始化定值组数据 |
|
||
| `mms_s_file.cpp : file_init()` | `IedServerConfig_enableFileService()` |
|
||
| `rcbEventHandler()` (在 mms_s.cpp 中) | `IedServer_setRCBEventHandler()` |
|
||
| `mms_s_run_task()` 后台线程 | 线程模式下自动处理(RTU 仅用锁持有维持运行) |
|
||
| `mms_s_dbg_switch()` | `LOG_I` 条件输出开关 |
|
||
|
||
### RTU 初始化流程(对应 libiec61850 API 调用)
|
||
|
||
```cpp
|
||
// RTU 中的 mms_s_init() 流程
|
||
// 1. icd_parse(icd_path) → 解析 ICD XML 文件为 stru_icd 结构
|
||
// 2. model_init(*gp_icd) → IedModel_create() + 遍历 ICD 创建 LD/LN/DO/DA/RCB/DataSet
|
||
// 3. IedServerConfig_create() → IedServerConfig_enableResvTmsForSGCB(true)
|
||
// 4. IedServer_createWithConfig(iedModel, NULL, serverConfig)
|
||
// 5. IedServer_setTimeQuality(server, true, false, false, 10)
|
||
// 6. control_init() → 遍历可控 DO,安装 ControlHandler
|
||
// 7. param_init() → 安装 SGCB 回调
|
||
// 8. file_init() → 配置文件服务
|
||
// 9. IedServer_setRCBEventHandler(server, rcbEventHandler, NULL)
|
||
// 10. IedServer_start(server, port)
|
||
// 11. setting_init() → 初始化定值组数据
|
||
// 12. Thread_create(mms_s_run_task, iedModel, false) → 后台线程
|
||
```
|
||
|
||
### RTU 后台线程的特殊处理
|
||
|
||
```cpp
|
||
// RTU 使用线程模式,但创建了额外的后台线程来持有锁:
|
||
void* mms_s_run_task(void* parameter) {
|
||
while(g_running) {
|
||
IedServer_lockDataModel(gp_iedServer);
|
||
IedServer_unlockDataModel(gp_iedServer);
|
||
Thread_sleep(100);
|
||
}
|
||
IedServer_stop(gp_iedServer);
|
||
IedServer_destroy(gp_iedServer);
|
||
IedModel_destroy(iedModel); // 销毁数据模型
|
||
return NULL;
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 16. 附录:公共数据类型速查
|
||
|
||
### 16.1 Quality 操作
|
||
|
||
```c
|
||
typedef uint16_t Quality;
|
||
|
||
// 有效性
|
||
#define QUALITY_VALIDITY_GOOD 0
|
||
#define QUALITY_VALIDITY_INVALID 2
|
||
#define QUALITY_VALIDITY_QUESTIONABLE 3
|
||
|
||
// 详情标志
|
||
#define QUALITY_DETAIL_OVERFLOW 4
|
||
#define QUALITY_DETAIL_OUT_OF_RANGE 8
|
||
#define QUALITY_DETAIL_FAILURE 64
|
||
#define QUALITY_DETAIL_OLD_DATA 128
|
||
#define QUALITY_SOURCE_SUBSTITUTED 1024
|
||
#define QUALITY_TEST 2048
|
||
#define QUALITY_OPERATOR_BLOCKED 4096
|
||
|
||
Validity Quality_getValidity(Quality* self);
|
||
void Quality_setValidity(Quality* self, Validity validity);
|
||
void Quality_setFlag(Quality* self, int flag);
|
||
void Quality_unsetFlag(Quality* self, int flag);
|
||
bool Quality_isFlagSet(Quality* self, int flag);
|
||
```
|
||
|
||
### 16.2 Timestamp 操作
|
||
|
||
```c
|
||
typedef union { uint8_t val[8]; } Timestamp;
|
||
|
||
Timestamp* Timestamp_create(void);
|
||
void Timestamp_destroy(Timestamp* self);
|
||
uint32_t Timestamp_getTimeInSeconds(Timestamp* self);
|
||
uint64_t Timestamp_getTimeInMs(Timestamp* self);
|
||
void Timestamp_setTimeInMilliseconds(Timestamp* self, uint64_t msTime);
|
||
void Timestamp_setLeapSecondKnown(Timestamp* self, bool value);
|
||
void Timestamp_setClockFailure(Timestamp* self, bool value);
|
||
void Timestamp_setClockNotSynchronized(Timestamp* self, bool value);
|
||
void Timestamp_setSubsecondPrecision(Timestamp* self, int precision);
|
||
```
|
||
|
||
### 16.3 Dbpos(双点位置)
|
||
|
||
```c
|
||
typedef enum {
|
||
DBPOS_INTERMEDIATE_STATE = 0, // 中间态
|
||
DBPOS_OFF = 1, // 分
|
||
DBPOS_ON = 2, // 合
|
||
DBPOS_BAD_STATE = 3 // 坏态
|
||
} Dbpos;
|
||
```
|
||
|
||
### 16.4 触发选项
|
||
|
||
```c
|
||
#define TRG_OPT_DATA_CHANGED 1 // 数据变化触发
|
||
#define TRG_OPT_QUALITY_CHANGED 2 // 品质变化触发
|
||
#define TRG_OPT_DATA_UPDATE 4 // 数据更新触发
|
||
#define TRG_OPT_INTEGRITY 8 // 周期性触发
|
||
#define TRG_OPT_GI 16 // 总召触发
|
||
#define TRG_OPT_TRANSIENT 128 // 仅上升沿触发
|
||
```
|
||
|
||
### 16.5 报告选项
|
||
|
||
```c
|
||
#define RPT_OPT_SEQ_NUM 1
|
||
#define RPT_OPT_TIME_STAMP 2
|
||
#define RPT_OPT_REASON_FOR_INCLUSION 4
|
||
#define RPT_OPT_DATA_SET 8
|
||
#define RPT_OPT_DATA_REFERENCE 16
|
||
#define RPT_OPT_BUFFER_OVERFLOW 32
|
||
#define RPT_OPT_ENTRY_ID 64
|
||
#define RPT_OPT_CONF_REV 128
|
||
```
|
||
|
||
### 16.6 控制模型
|
||
|
||
```c
|
||
typedef enum {
|
||
CONTROL_MODEL_STATUS_ONLY = 0,
|
||
CONTROL_MODEL_DIRECT_NORMAL = 1,
|
||
CONTROL_MODEL_SBO_NORMAL = 2,
|
||
CONTROL_MODEL_DIRECT_ENHANCED = 3,
|
||
CONTROL_MODEL_SBO_ENHANCED = 4
|
||
} ControlModel;
|
||
```
|
||
|
||
### 16.7 MmsValue 基础构造(服务端常用)
|
||
|
||
```c
|
||
MmsValue* MmsValue_newBoolean(bool);
|
||
MmsValue* MmsValue_newFloat(float);
|
||
MmsValue* MmsValue_newDouble(double);
|
||
MmsValue* MmsValue_newIntegerFromInt32(int32_t);
|
||
MmsValue* MmsValue_newIntegerFromInt64(int64_t);
|
||
MmsValue* MmsValue_newUnsignedFromUint32(uint32_t);
|
||
MmsValue* MmsValue_newVisibleString(const char*);
|
||
MmsValue* MmsValue_newBitString(int bitSize);
|
||
MmsValue* MmsValue_newUtcTimeByMsTime(uint64_t msTime);
|
||
void MmsValue_delete(MmsValue*);
|
||
```
|
||
|
||
### 16.8 数据访问错误
|
||
|
||
```c
|
||
typedef enum {
|
||
DATA_ACCESS_ERROR_SUCCESS_NO_UPDATE = -3,
|
||
DATA_ACCESS_ERROR_NO_RESPONSE = -2,
|
||
DATA_ACCESS_ERROR_SUCCESS = -1,
|
||
DATA_ACCESS_ERROR_OBJECT_INVALIDATED = 0,
|
||
DATA_ACCESS_ERROR_HARDWARE_FAULT = 1,
|
||
DATA_ACCESS_ERROR_TEMPORARILY_UNAVAILABLE = 2,
|
||
DATA_ACCESS_ERROR_OBJECT_ACCESS_DENIED = 3,
|
||
DATA_ACCESS_ERROR_OBJECT_UNDEFINED = 4,
|
||
DATA_ACCESS_ERROR_INVALID_ADDRESS = 5,
|
||
DATA_ACCESS_ERROR_TYPE_UNSUPPORTED = 6,
|
||
DATA_ACCESS_ERROR_TYPE_INCONSISTENT = 7,
|
||
DATA_ACCESS_ERROR_OBJECT_ATTRIBUTE_INCONSISTENT = 8,
|
||
DATA_ACCESS_ERROR_OBJECT_ACCESS_UNSUPPORTED = 9,
|
||
DATA_ACCESS_ERROR_OBJECT_NONE_EXISTENT = 10,
|
||
DATA_ACCESS_ERROR_OBJECT_VALUE_INVALID = 11,
|
||
DATA_ACCESS_ERROR_UNKNOWN = 12
|
||
} MmsDataAccessError;
|
||
```
|
||
|
||
---
|
||
|
||
> 本文档基于 `libiec61850-1.5.3/src/iec61850/inc/iec61850_server.h` (1896行)、`iec61850_dynamic_model.h` (539行)、`iec61850_common.h` (548行) 和 `mms_value.h` (1062行) 完整归纳,覆盖所有公开 C API。
|