44 KiB
44 KiB
libiec61850-1.5.3 MMS 服务端 API 开发手册
本文档基于 libiec61850 v1.5.3 源码,覆盖 IEC 61850 服务端全部公开 C API,达到开发者脱离源码即可进行项目开发的标准。
目录
- 架构概述
- 数据模型创建(动态模型)
- 服务器配置(IedServerConfig)
- 服务器生命周期管理
- 数据值读取与更新
- 控制模型回调
- 报告控制块(RCB)事件处理
- 读写访问控制
- 定值组(SGCB)处理
- GOOSE 发布
- SV 控制块
- 连接管理与认证
- 日志服务
- 运行模式:线程模式 vs 非线程模式
- 与 RTU 项目对照
- 附录:公共数据类型速查
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 完整创建流程
// 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)
// 在 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)
// 创建数据集
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 设置数据属性默认值
// 在创建 IedServer 之前可以设置默认值
MmsValue* defaultVal = MmsValue_newFloat(0.0f);
DataAttribute_setValue(stVal, defaultVal);
MmsValue_delete(defaultVal);
2.6 DA 属性查询
DataAttributeType DataAttribute_getType(DataAttribute* self);
FunctionalConstraint DataAttribute_getFC(DataAttribute* self);
uint8_t DataAttribute_getTrgOps(DataAttribute* self);
2.7 创建其他控制块
// 定值组控制块
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 模型销毁
// 销毁动态创建的数据模型
// 注意:一定要在 IedServer_destroy 之后调用,否则会导致资源泄漏
IedModel_destroy(model);
2.9 数据属性类型枚举(DataAttributeType)
创建 DA 时使用的类型常量:
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 配置结构体字段
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
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);
报告设置可写性常量:
#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 查询配置
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 创建服务器
// 简单创建(仅需数据模型)
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 附加访问点
// 为服务器添加额外的监听地址(可在 start 前多次添加)
// 返回 true 成功,false 失败
IedServer_addAccessPoint(server, "192.168.2.1", 102, NULL);
4.3 设置服务器属性
// 设置监听地址(默认监听所有接口)
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 启动/停止(线程模式)
// 启动(线程模式)—— 内部创建后台线程
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 启动/停止(非线程模式)
// 启动(非线程模式)—— 用户驱动消息循环
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 底层访问
// 获取数据模型
IedModel* model = IedServer_getDataModel(server);
// 获取底层 MmsServer(谨慎使用:直接操作可能干扰 IedServer)
MmsServer mmsServer = IedServer_getMmsServer(server);
5. 数据值读取与更新
5.1 读取属性值
// 通用读取(返回 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)
// 通用更新
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 批量更新(加锁)
// 更新多个值时加锁以提高效率
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 更新控制模型
// 更新可控数据对象的控制模型
IedServer_updateCtlModel(server, ctlObject, CONTROL_MODEL_SBO_NORMAL);
// 注意:对应的控制结构必须在数据模型中存在
6. 控制模型回调
服务端通过三个层级的回调实现 IEC 61850 的遥控机制。
6.1 控制操作完整流程
客户端发送控制命令
↓
PerformCheck 回调(静态测试:联锁、权限等)
↓ (通过)
WaitForExecution 回调(动态测试:同步检查等)
↓ (通过)
Control 回调(实际执行:操作继电器等)
↓
CommandTermination 响应返回客户端
6.2 回调返回值
// 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 上下文信息
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 注册控制回调
// 实际执行回调(必须注册)
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 状态变化回调
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 完整枚举
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 事件类型
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 事件回调
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 写访问控制(单个数据属性)
// 写访问回调
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 全局写访问策略
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 读访问控制(全局)
// 全局读访问回调(对每个读请求调用)
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 内部切换定值组
// 内部事件导致活跃定值组变化
IedServer_changeActiveSettingGroup(server, sgcb, 3); // 切换到第3组
// 获取当前活跃定值组号
uint8_t activeSG = IedServer_getActiveSettingGroup(server, sgcb);
9.2 定值组变化回调
// 活跃定值组切换前回调(可拒绝切换)
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
// 批量启用所有 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 事件回调
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 信息查询
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 控制块
// 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 连接认证
// ACSE 认证回调
bool myAuthenticator(void* parameter, AcseAuthenticationParameter* authParameter) {
// 从 authParameter 中提取认证信息
// 返回 true 接受连接,false 拒绝连接
return true;
}
IedServer_setAuthenticator(server, myAuthenticator, authParam);
12.2 连接状态变化回调
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
const char* ClientConnection_getPeerAddress(ClientConnection self); // 客户端IP
const char* ClientConnection_getLocalAddress(ClientConnection self); // 服务端本地IP
void* ClientConnection_getSecurityToken(ClientConnection self); // 认证令牌
13. 日志服务
// 设置日志存储(将日志控制块关联到日志存储实例)
IedServer_setLogStorage(server, "TEMPLATE/LLN0$MyLog", logStorage);
14. 运行模式:线程模式 vs 非线程模式
线程模式(默认,推荐)
IedServer server = IedServer_createWithConfig(model, NULL, config);
IedServer_start(server, 102);
// 库内部创建线程处理所有客户端连接和周期性任务
// 用户只需调用 update 系列函数更新数据
IedServer_updateFloatAttributeValue(server, da, value); // 安全,随时可调用
非线程模式(嵌入式/特殊场景)
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 调用)
// 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 后台线程的特殊处理
// 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 操作
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 操作
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(双点位置)
typedef enum {
DBPOS_INTERMEDIATE_STATE = 0, // 中间态
DBPOS_OFF = 1, // 分
DBPOS_ON = 2, // 合
DBPOS_BAD_STATE = 3 // 坏态
} Dbpos;
16.4 触发选项
#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 报告选项
#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 控制模型
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 基础构造(服务端常用)
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 数据访问错误
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。