# libiec61850-1.5.3 MMS 客户端 API 开发手册 > 本文档基于 libiec61850 v1.5.3 源码,覆盖 MMS 客户端全部公开 C API,达到开发者脱离源码即可进行项目开发的标准。 --- ## 目录 1. [架构概述](#1-架构概述) 2. [核心数据类型](#2-核心数据类型) 3. [连接管理](#3-连接管理) 4. [数据读写](#4-数据读写) 5. [报告服务(Report)](#5-报告服务report) 6. [控制服务(Control)](#6-控制服务control) 7. [数据集服务(DataSet)](#7-数据集服务dataset) 8. [文件服务(File)](#8-文件服务file) 9. [日志服务(Log)](#9-日志服务log) 10. [模型发现服务](#10-模型发现服务) 11. [SV/GOOSE 控制块处理](#11-svgoose-控制块处理) 12. [MSO 传输层访问](#12-mms低层传输层访问) 13. [错误码完整参考](#13-错误码完整参考) 14. [运行模式:线程模式 vs 非线程模式](#14-运行模式线程模式-vs-非线程模式) 15. [与 RTU 项目对照](#15-与-rtu-项目对照) --- ## 1. 架构概述 libiec61850 客户端 API 提供两个抽象层级: | 层级 | API 头文件 | 描述 | |------|-----------|------| | **高层 IEC 61850** | `iec61850_client.h` | 封装 IEC 61850 语义(FC, LD/LN/DO/DA),推荐使用 | | **底层 MMS** | `mms_client_connection.h` | 直接操作 MMS 协议 domain/item,一般不需要直接使用 | 客户端核心不透明句柄为 `IedConnection`,所有操作围绕它展开。 ### 协议栈层次 ``` ┌─────────────────────────────────────────────────────┐ │ 高层 IEC 61850 API (iec61850_client.h) │ │ IedConnection / ClientReport / ControlObjectClient │ ├─────────────────────────────────────────────────────┤ │ 底层 MMS API (mms_client_connection.h) │ │ MmsConnection / MmsValue │ ├─────────────────────────────────────────────────────┤ │ ISO 传输层 (ACSE + Presentation + Session) │ ├─────────────────────────────────────────────────────┤ │ ASN.1 BER 编解码 + TCP/IP │ └─────────────────────────────────────────────────────┘ ``` ### 两类运行模式 | 模式 | 创建方式 | 消息处理 | 适用场景 | |------|---------|---------|---------| | **线程模式**(默认) | `IedConnection_create()` | 库内部后台线程自动处理 | 大多数场景 | | **非线程模式** | `IedConnection_createEx(tlsConfig, false)` | 用户周期性调用 `IedConnection_tick()` | 需要精确控制线程的嵌入式系统 | 在非线程模式下,禁止使用同步(阻塞)API,必须使用 `*Async` 异步版本。 --- ## 2. 核心数据类型 ### 2.1 主要不透明句柄 ```c typedef struct sIedConnection* IedConnection; // 客户端连接 typedef struct sClientReportControlBlock* ClientReportControlBlock; // RCB 本地对象 typedef struct sClientReport* ClientReport; // 接收到的报告 typedef struct sClientDataSet* ClientDataSet; // 数据集本地对象 typedef struct sControlObjectClient* ControlObjectClient; // 控制对象 typedef struct sClientSVControlBlock* ClientSVControlBlock; // SV控制块 typedef struct sClientGooseControlBlock* ClientGooseControlBlock; // GOOSE控制块 ``` ### 2.2 连接状态 ```c typedef enum { IED_STATE_CLOSED = 0, // 空闲/关闭 IED_STATE_CONNECTING = 1, // 连接中 IED_STATE_CONNECTED = 2, // 已连接 IED_STATE_CLOSING = 3 // 关闭中 } IedConnectionState; ``` ### 2.3 功能约束(FunctionalConstraint) 客户端读写数据时,**必须指定 FC**: | FC 枚举 | 值 | 含义 | 典型使用 | |---------|----|------|---------| | `IEC61850_FC_ST` | 0 | 状态信息 | 读遥信状态 | | `IEC61850_FC_MX` | 1 | 测量值 | 读遥测值 | | `IEC61850_FC_SP` | 2 | 设定值 | 读设定点 | | `IEC61850_FC_SV` | 3 | 替代值 | 读替代值 | | `IEC61850_FC_CF` | 4 | 配置 | 读配置 | | `IEC61850_FC_DC` | 5 | 描述 | 读描述 | | `IEC61850_FC_SG` | 6 | 定值组 | 读定值组活跃值 | | `IEC61850_FC_SE` | 7 | 可编辑定值组 | 读/写编辑定值组 | | `IEC61850_FC_CO` | 12 | 控制 | 写控制命令 | | `IEC61850_FC_RP` | 15 | 非缓存报告 | RCB 操作 | | `IEC61850_FC_BR` | 16 | 缓存报告 | BRCB 操作 | **引用格式**:`"LD名/LN名.DO名.DA名"`,如 `"simpleIOGenericIO/GGIO1.ST.Ind1.stVal"`。 ### 2.4 控制模型 ```c typedef enum { CONTROL_MODEL_STATUS_ONLY = 0, // 只读,不支持控制 CONTROL_MODEL_DIRECT_NORMAL = 1, // 直控-普通安全 CONTROL_MODEL_SBO_NORMAL = 2, // 选控-普通安全(需Select再Operate) CONTROL_MODEL_DIRECT_ENHANCED = 3,// 直控-增强安全(含CommandTermination) CONTROL_MODEL_SBO_ENHANCED = 4 // 选控-增强安全(SelectWithValue再Operate) } ControlModel; ``` ### 2.5 Quality 质量 ```c typedef uint16_t Quality; // 有效性(低2位) #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_BAD_REFERENCE 16 // 坏基准值 #define QUALITY_DETAIL_OSCILLATORY 32 // 振荡 #define QUALITY_DETAIL_FAILURE 64 // 故障 #define QUALITY_DETAIL_OLD_DATA 128 // 旧数据 #define QUALITY_DETAIL_INCONSISTENT 256 // 不一致 #define QUALITY_DETAIL_INACCURATE 512 // 不精确 #define QUALITY_SOURCE_SUBSTITUTED 1024 // 替代值 #define QUALITY_TEST 2048 // 测试 #define QUALITY_OPERATOR_BLOCKED 4096 // 操作员闭锁 // Quality 操作函数 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); Quality Quality_fromMmsValue(const MmsValue* mmsValue); MmsValue* Quality_toMmsValue(Quality* self, MmsValue* mmsValue); ``` ### 2.6 Timestamp ```c typedef union { uint8_t val[8]; } Timestamp; Timestamp* Timestamp_create(void); Timestamp* Timestamp_createFromByteArray(const uint8_t* byteArray); void Timestamp_destroy(Timestamp* self); void Timestamp_clearFlags(Timestamp* self); uint32_t Timestamp_getTimeInSeconds(Timestamp* self); // 秒级时间戳 uint64_t Timestamp_getTimeInMs(Timestamp* self); // 毫秒级时间戳 uint64_t Timestamp_getTimeInNs(Timestamp* self); // 纳秒级时间戳 bool Timestamp_isLeapSecondKnown(Timestamp* self); void Timestamp_setLeapSecondKnown(Timestamp* self, bool value); bool Timestamp_hasClockFailure(Timestamp* self); void Timestamp_setClockFailure(Timestamp* self, bool value); bool Timestamp_isClockNotSynchronized(Timestamp* self); void Timestamp_setClockNotSynchronized(Timestamp* self, bool value); int Timestamp_getSubsecondPrecision(Timestamp* self); void Timestamp_setSubsecondPrecision(Timestamp* self, int subsecondPrecision); void Timestamp_setTimeInSeconds(Timestamp* self, uint32_t secondsSinceEpoch); void Timestamp_setTimeInMilliseconds(Timestamp* self, uint64_t msTime); void Timestamp_setTimeInNanoseconds(Timestamp* self, uint64_t nsTime); void Timestamp_setByMmsUtcTime(Timestamp* self, const MmsValue* mmsValue); MmsValue* Timestamp_toMmsValue(Timestamp* self, MmsValue* mmsValue); Timestamp* Timestamp_fromMmsValue(Timestamp* self, MmsValue* mmsValue); ``` ### 2.7 回调函数类型速查 ```c // 通用服务回调(写操作完成等) typedef void (*IedConnection_GenericServiceHandler)(uint32_t invokeId, void* parameter, IedClientError err); // 连接状态变化回调 typedef void (*IedConnection_StateChangedHandler)(void* parameter, IedConnection connection, IedConnectionState newState); // 读对象回调 typedef void (*IedConnection_ReadObjectHandler)(uint32_t invokeId, void* parameter, IedClientError err, MmsValue* value); // 报告接收回调 typedef void (*ReportCallbackFunction)(void* parameter, ClientReport report); // 控制操作完成回调 typedef void (*ControlObjectClient_ControlActionHandler)(uint32_t invokeId, void* parameter, IedClientError err, ControlActionType type, bool success); // 命令终止回调 typedef void (*CommandTerminationHandler)(void* parameter, ControlObjectClient controlClient); // 文件下载数据回调 typedef bool (*IedClientGetFileHandler)(void* parameter, uint8_t* buffer, uint32_t bytesRead); ``` ### 2.8 MmsValue 基础操作 `MmsValue` 是所有客户端数据交换的统一容器类型。每个 `MmsValue` 有一个 `MmsType`: ```c typedef enum { MMS_ARRAY = 0, // 数组 MMS_STRUCTURE = 1, // 结构体 MMS_BOOLEAN = 2, // 布尔 MMS_BIT_STRING = 3, // 位串 MMS_INTEGER = 4, // 有符号整数 MMS_UNSIGNED = 5, // 无符号整数 MMS_FLOAT = 6, // 浮点数 MMS_OCTET_STRING = 7, // 字节串 MMS_VISIBLE_STRING = 8, // 可见字符串 MMS_STRING = 9, // MMS 字符串 MMS_UTC_TIME = 10, // UTC 时间 MMS_DATA_ACCESS_ERROR = 11, } MmsType; ``` **构造**(仅在需要主动创建值时使用,读取操作由库自动创建): ```c MmsValue* MmsValue_newBoolean(bool boolean); MmsValue* MmsValue_newFloat(float value); MmsValue* MmsValue_newDouble(double value); MmsValue* MmsValue_newIntegerFromInt32(int32_t integer); MmsValue* MmsValue_newIntegerFromInt64(int64_t integer); MmsValue* MmsValue_newUnsignedFromUint32(uint32_t integer); MmsValue* MmsValue_newVisibleString(const char* string); MmsValue* MmsValue_newUtcTime(uint32_t timeval); // 秒级时间戳 MmsValue* MmsValue_newUtcTimeByMsTime(uint64_t timeval); // 毫秒级时间戳 MmsValue* MmsValue_newOctetString(int size, int maxSize); MmsValue* MmsValue_newBitString(int bitSize); MmsValue* MmsValue_newStructure(const MmsVariableSpecification* typeSpec); MmsValue* MmsValue_createEmptyStructure(int size); MmsValue* MmsValue_createEmptyArray(int size); ``` **取值**: ```c bool MmsValue_getBoolean(const MmsValue* value); float MmsValue_toFloat(const MmsValue* self); double MmsValue_toDouble(const MmsValue* self); int32_t MmsValue_toInt32(const MmsValue* value); int64_t MmsValue_toInt64(const MmsValue* self); uint32_t MmsValue_toUint32(const MmsValue* value); uint64_t MmsValue_getUtcTimeInMs(const MmsValue* value); uint64_t MmsValue_getUtcTimeInMsWithUs(const MmsValue* self, uint32_t* usec); uint32_t MmsValue_toUnixTimestamp(const MmsValue* self); const char* MmsValue_toString(MmsValue* self); // 对 VISIBLE_STRING/STRING 类型 // 数组/结构体 MmsValue* MmsValue_getElement(const MmsValue* array, int index); uint32_t MmsValue_getArraySize(const MmsValue* self); // 位串 bool MmsValue_getBitStringBit(const MmsValue* self, int bitPos); int MmsValue_getBitStringSize(const MmsValue* self); uint32_t MmsValue_getBitStringAsInteger(const MmsValue* self); uint32_t MmsValue_getBitStringAsIntegerBigEndian(const MmsValue* self); // 字节串 uint16_t MmsValue_getOctetStringSize(const MmsValue* self); uint8_t* MmsValue_getOctetStringBuffer(MmsValue* self); uint8_t MmsValue_getOctetStringOctet(MmsValue* self, int octetPos); ``` **赋值**: ```c void MmsValue_setBoolean(MmsValue* value, bool boolValue); void MmsValue_setFloat(MmsValue* self, float newFloatValue); void MmsValue_setDouble(MmsValue* self, double newFloatValue); void MmsValue_setInt32(MmsValue* self, int32_t integer); void MmsValue_setInt64(MmsValue* value, int64_t integer); void MmsValue_setUint32(MmsValue* value, uint32_t integer); void MmsValue_setVisibleString(MmsValue* self, const char* string); MmsValue* MmsValue_setUtcTime(MmsValue* self, uint32_t timeval); // 秒 MmsValue* MmsValue_setUtcTimeMs(MmsValue* self, uint64_t timeval); // 毫秒 void MmsValue_setOctetString(MmsValue* self, const uint8_t* buf, int size); void MmsValue_setBitStringBit(MmsValue* self, int bitPos, bool value); void MmsValue_setElement(MmsValue* complexValue, int index, MmsValue* elementValue); ``` **生命周期**: ```c MmsValue* MmsValue_clone(const MmsValue* self); // 深拷贝,调用者负责释放 void MmsValue_delete(MmsValue* self); // 递归删除所有子元素 bool MmsValue_update(MmsValue* self, const MmsValue* source); bool MmsValue_equals(const MmsValue* self, const MmsValue* otherValue); MmsType MmsValue_getType(const MmsValue* self); ``` --- ## 3. 连接管理 ### 3.1 完整连接生命周期 ```c // 步骤1:创建连接 IedConnection conn = IedConnection_create(); // 或高级版本:IedConnection_createEx(tlsConfig, useThreads); // 步骤2(可选):设置参数 IedConnection_setLocalAddress(conn, "0.0.0.0", 0); // 本地绑定地址和端口,0=自动分配 IedConnection_setConnectTimeout(conn, 5000); // 连接超时(ms),须在connect前调用 IedConnection_setRequestTimeout(conn, 3000); // 请求超时(ms),可随时调用 IedConnection_setTimeQuality(conn, true, false, false, 10); // 时间品质 // 步骤3:连接服务器(阻塞版本) IedClientError err; IedConnection_connect(conn, &err, "192.168.1.100", 102); if (err != IED_ERROR_OK) { /* 处理错误 */ } // 步骤3':连接服务器(异步版本) IedConnection_connectAsync(conn, &err, "192.168.1.100", 102); // 轮询 IedConnection_getState(conn) 或安装状态回调来等待连接完成 // 步骤4:安装连接状态回调(可选) void stateHandler(void* param, IedConnection connection, IedConnectionState newState) { switch (newState) { case IED_STATE_CONNECTED: /* 连接成功 */ break; case IED_STATE_CLOSED: /* 连接关闭 */ break; // ... } } IedConnection_installStateChangedHandler(conn, stateHandler, NULL); // 步骤5:进行业务操作... // 步骤6:关闭连接(三选一) IedConnection_close(conn); // 直接关闭 TCP(最常用) IedConnection_abort(conn, &err); // 发送 ACSE Abort 后关闭 IedConnection_release(conn, &err); // 发送 MMS Conclude(优雅关闭) // 步骤7:释放资源 IedConnection_destroy(conn); ``` ### 3.2 连接状态查询 ```c IedConnectionState IedConnection_getState(IedConnection self); // 状态流转: // CLOSED → connect() → CONNECTING → 连接成功 → CONNECTED // CONNECTED → close()/abort() → CLOSING → TCP断开 → CLOSED ``` ### 3.3 连接参数 | API | 说明 | |-----|------| | `IedConnection_setLocalAddress(conn, ip, port)` | 绑定本地地址(可选,不调用由OS自动分配) | | `IedConnection_setConnectTimeout(conn, ms)` | 连接超时,须在connect前调用 | | `IedConnection_setRequestTimeout(conn, ms)` | 请求超时,可随时调整 | | `IedConnection_getRequestTimeout(conn)` | 获取当前请求超时值 | | `IedConnection_setTimeQuality(conn, a,b,c,d)` | 设置本连接生成的所有时间戳的品质 | ### 3.4 获取底层连接 ```c MmsConnection IedConnection_getMmsConnection(IedConnection self); ``` 返回底层 `MmsConnection` 句柄,可用于直接调用低层 MMS API。 --- ## 4. 数据读写 ### 4.1 通用读写(通过 FCDA 引用 + FC) ```c // 同步读:返回 MmsValue*,失败返回 NULL MmsValue* val = IedConnection_readObject(conn, &err, "simpleIOGenericIO/GGIO1.ST.Ind1.stVal", IEC61850_FC_ST); if (val) { float f = MmsValue_toFloat(val); // 注意:不要手动 delete val,生命周期由库管理 } // 同步写 MmsValue* writeVal = MmsValue_newBoolean(true); IedConnection_writeObject(conn, &err, "simpleIOGenericIO/GGIO1.SP.Pos1.ctlVal", IEC61850_FC_CO, writeVal); MmsValue_delete(writeVal); // 异步读 void readHandler(uint32_t invokeId, void* param, IedClientError err, MmsValue* value) { if (err == IED_ERROR_OK) { /* 使用 value */ } } uint32_t id = IedConnection_readObjectAsync(conn, &err, "ref", IEC61850_FC_ST, readHandler, myParam); // 异步写 uint32_t id = IedConnection_writeObjectAsync(conn, &err, "ref", IEC61850_FC_CO, value, genericHandler, myParam); ``` ### 4.2 便捷类型读写(推荐用于简单类型) **读**: ```c bool v = IedConnection_readBooleanValue(conn, &err, "ref", IEC61850_FC_ST); float v = IedConnection_readFloatValue(conn, &err, "ref", IEC61850_FC_MX); int32_t v = IedConnection_readInt32Value(conn, &err, "ref", IEC61850_FC_ST); int64_t v = IedConnection_readInt64Value(conn, &err, "ref", IEC61850_FC_ST); uint32_t v = IedConnection_readUnsigned32Value(conn, &err, "ref", IEC61850_FC_ST); char* v = IedConnection_readStringValue(conn, &err, "ref", IEC61850_FC_CF); // 需手动 free! Quality v = IedConnection_readQualityValue(conn, &err, "ref", IEC61850_FC_ST); Timestamp* v = IedConnection_readTimestampValue(conn, &err, "ref", IEC61850_FC_ST, NULL); // 需手动 free! ``` **注意**:`readStringValue` 返回的 `char*` 由库动态分配,调用者必须手动 `free()`。 `readTimestampValue` 如果传入 `NULL` 则库分配新 Timestamp 对象,调用者负责 `Timestamp_destroy()`;如果传入已有的 Timestamp 指针则复用。 **写**: ```c IedConnection_writeBooleanValue(conn, &err, "ref", IEC61850_FC_CO, true); IedConnection_writeFloatValue(conn, &err, "ref", IEC61850_FC_CO, 1.5f); IedConnection_writeInt32Value(conn, &err, "ref", IEC61850_FC_CO, 42); IedConnection_writeUnsigned32Value(conn, &err, "ref", IEC61850_FC_CO, 100); IedConnection_writeVisibleStringValue(conn, &err, "ref", IEC61850_FC_CF, "hello"); IedConnection_writeOctetString(conn, &err, "ref", IEC61850_FC_CF, data, len); ``` --- ## 5. 报告服务(Report) 报告是 IEC 61850 客户端接收服务端主动上送数据的机制。 ### 5.1 RCB 类型 | 类型 | 引用名特征 | 行为 | |------|-----------|------| | **URCB** (Unbuffered) | 含 `RP`(如 `LLN0.RP.EventsRCB01`) | 不缓存,断连后丢失 | | **BRCB** (Buffered) | 含 `BR`(如 `LLN0.BR.EventsBRCB01`) | 缓存报告,断连后可重传 | ### 5.2 RCB 元素掩码(setRCBValues 时指定要写入哪些字段) ```c #define RCB_ELEMENT_RPT_ID 1 // 报告ID #define RCB_ELEMENT_RPT_ENA 2 // 报告使能 #define RCB_ELEMENT_RESV 4 // 预留(仅URCB) #define RCB_ELEMENT_DATSET 8 // 数据集 #define RCB_ELEMENT_CONF_REV 16 // 配置版本 #define RCB_ELEMENT_OPT_FLDS 32 // 选项字段 #define RCB_ELEMENT_BUF_TM 64 // 缓冲时间 #define RCB_ELEMENT_SQ_NUM 128 // 序列号 #define RCB_ELEMENT_TRG_OPS 256 // 触发选项 #define RCB_ELEMENT_INTG_PD 512 // 完整性周期 #define RCB_ELEMENT_GI 1024 // 总召 #define RCB_ELEMENT_PURGE_BUF 2048 // 清除缓冲区(仅BRCB) #define RCB_ELEMENT_ENTRY_ID 4096 // 条目ID(仅BRCB) #define RCB_ELEMENT_TIME_OF_ENTRY 8192 // 条目时间(仅BRCB) #define RCB_ELEMENT_RESV_TMS 16384 // 预留时间(仅BRCB) #define RCB_ELEMENT_OWNER 32768 // 所有者 ``` ### 5.3 触发选项(TrgOps) ```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 // 仅上升沿触发(瞬态) ``` ### 5.4 报告选项字段(OptFlds)— 决定报告包含哪些信息 ```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 // 条目ID #define RPT_OPT_CONF_REV 128 // 配置版本 ``` ### 5.5 完整报告配置与接收流程 ```c // 步骤1:获取 RCB(读取服务端当前RCB值) ClientReportControlBlock rcb = IedConnection_getRCBValues(conn, &err, "simpleIOGenericIO/LLN0.RP.EventsRCB01", NULL); if (!rcb || err != IED_ERROR_OK) { /* 错误处理 */ } // 或者异步获取 uint32_t id = IedConnection_getRCBValuesAsync(conn, &err, rcbRef, NULL, handler, param); // 步骤2:配置 RCB 参数 ClientReportControlBlock_setRptEna(rcb, true); // 使能报告 ClientReportControlBlock_setDataSetReference(rcb, "simpleIOGenericIO/LLN0$Events"); // 设置数据集 ClientReportControlBlock_setOptFlds(rcb, RPT_OPT_SEQ_NUM | RPT_OPT_TIME_STAMP | RPT_OPT_REASON_FOR_INCLUSION | RPT_OPT_DATA_SET | RPT_OPT_DATA_REFERENCE | RPT_OPT_BUF_OVERFLOW | RPT_OPT_CONF_REV); ClientReportControlBlock_setTrgOps(rcb, TRG_OPT_DATA_CHANGED | TRG_OPT_INTEGRITY | TRG_OPT_GI); ClientReportControlBlock_setBufTm(rcb, 100); // 缓冲时间 100ms ClientReportControlBlock_setIntgPd(rcb, 60000); // 完整性周期 60s // 步骤3:将配置写入服务端 IedConnection_setRCBValues(conn, &err, rcb, RCB_ELEMENT_RPT_ENA | RCB_ELEMENT_OPT_FLDS | RCB_ELEMENT_TRG_OPS | RCB_ELEMENT_DATSET | RCB_ELEMENT_BUF_TM | RCB_ELEMENT_INTG_PD, false); // false=多变量合并单次MMS写请求(通常建议false) // 异步版本 IedConnection_setRCBValuesAsync(conn, &err, rcb, mask, false, handler, param); // 步骤4:安装报告回调 IedConnection_installReportHandler(conn, "simpleIOGenericIO/LLN0.RP.EventsRCB01", // RCB引用 "EventsRpt", // 报告标识符(RptID) rcbHandler, // 回调函数 myParam); // 用户参数 // 步骤5:报告回调处理 void rcbHandler(void* parameter, ClientReport report) { // 获取数据集值(MmsValue 数组) MmsValue* values = ClientReport_getDataSetValues(report); int size = MmsValue_getArraySize(values); // 遍历数据集成员 for (int i = 0; i < size; i++) { ReasonForInclusion reason = ClientReport_getReasonForInclusion(report, i); const char* ref = ClientReport_getDataReference(report, i); MmsValue* element = MmsValue_getElement(values, i); // 根据类型处理值... } // 报告元数据 if (ClientReport_hasTimestamp(report)) uint64_t ts = ClientReport_getTimestamp(report); if (ClientReport_hasSeqNum(report)) uint16_t seq = ClientReport_getSeqNum(report); if (ClientReport_hasBufOvfl(report)) bool ovfl = ClientReport_getBufOvfl(report); if (ClientReport_hasConfRev(report)) uint32_t cr = ClientReport_getConfRev(report); if (ClientReport_hasSubSeqNum(report)) { // 分段报告 uint16_t subSeq = ClientReport_getSubSeqNum(report); bool more = ClientReport_getMoreSegmentsFollow(report); } } // 步骤6:触发总召(可选) ClientReportControlBlock_setGI(rcb, true); IedConnection_setRCBValues(conn, &err, rcb, RCB_ELEMENT_GI, false); // 步骤7:注销报告 IedConnection_uninstallReportHandler(conn, "simpleIOGenericIO/LLN0.RP.EventsRCB01"); // 步骤8:释放 RCB 对象 ClientReportControlBlock_destroy(rcb); ``` ### 5.6 包含原因枚举 ```c typedef int ReasonForInclusion; #define IEC61850_REASON_NOT_INCLUDED 0 // 未包含 #define IEC61850_REASON_DATA_CHANGE 1 // 数据变化 #define IEC61850_REASON_QUALITY_CHANGE 2 // 品质变化 #define IEC61850_REASON_DATA_UPDATE 4 // 数据更新 #define IEC61850_REASON_INTEGRITY 8 // 周期性上送 #define IEC61850_REASON_GI 16 // 总召 #define IEC61850_REASON_UNKNOWN 32 // 原因未知 ``` ### 5.7 ClientReport API 完整列表 ```c const char* ClientReport_getDataSetName(ClientReport self); MmsValue* ClientReport_getDataSetValues(ClientReport self); // 仅回调内有效 char* ClientReport_getRcbReference(ClientReport self); char* ClientReport_getRptId(ClientReport self); ReasonForInclusion ClientReport_getReasonForInclusion(ClientReport self, int elementIndex); MmsValue* ClientReport_getEntryId(ClientReport self); bool ClientReport_hasTimestamp(ClientReport self); uint64_t ClientReport_getTimestamp(ClientReport self); bool ClientReport_hasSeqNum(ClientReport self); uint16_t ClientReport_getSeqNum(ClientReport self); bool ClientReport_hasDataSetName(ClientReport self); bool ClientReport_hasReasonForInclusion(ClientReport self); bool ClientReport_hasConfRev(ClientReport self); uint32_t ClientReport_getConfRev(ClientReport self); bool ClientReport_hasBufOvfl(ClientReport self); bool ClientReport_getBufOvfl(ClientReport self); bool ClientReport_hasDataReference(ClientReport self); const char* ClientReport_getDataReference(ClientReport self, int elementIndex); bool ClientReport_hasSubSeqNum(ClientReport self); // 分段报告 uint16_t ClientReport_getSubSeqNum(ClientReport self); bool ClientReport_getMoreSegmentsFollow(ClientReport self); char* ReasonForInclusion_getValueAsString(ReasonForInclusion reasonCode); ``` ### 5.8 ClientReportControlBlock API 完整列表 ```c ClientReportControlBlock ClientReportControlBlock_create(const char* rcbReference); void ClientReportControlBlock_destroy(ClientReportControlBlock self); char* ClientReportControlBlock_getObjectReference(ClientReportControlBlock self); bool ClientReportControlBlock_isBuffered(ClientReportControlBlock self); // Getter/Setter const char* ClientReportControlBlock_getRptId(ClientReportControlBlock self); void ClientReportControlBlock_setRptId(ClientReportControlBlock self, const char* rptId); bool ClientReportControlBlock_getRptEna(ClientReportControlBlock self); void ClientReportControlBlock_setRptEna(ClientReportControlBlock self, bool rptEna); bool ClientReportControlBlock_getResv(ClientReportControlBlock self); void ClientReportControlBlock_setResv(ClientReportControlBlock self, bool resv); const char* ClientReportControlBlock_getDataSetReference(ClientReportControlBlock self); void ClientReportControlBlock_setDataSetReference(ClientReportControlBlock self, const char* dataSetReference); uint32_t ClientReportControlBlock_getConfRev(ClientReportControlBlock self); int ClientReportControlBlock_getOptFlds(ClientReportControlBlock self); void ClientReportControlBlock_setOptFlds(ClientReportControlBlock self, int optFlds); uint32_t ClientReportControlBlock_getBufTm(ClientReportControlBlock self); void ClientReportControlBlock_setBufTm(ClientReportControlBlock self, uint32_t bufTm); uint16_t ClientReportControlBlock_getSqNum(ClientReportControlBlock self); int ClientReportControlBlock_getTrgOps(ClientReportControlBlock self); void ClientReportControlBlock_setTrgOps(ClientReportControlBlock self, int trgOps); uint32_t ClientReportControlBlock_getIntgPd(ClientReportControlBlock self); void ClientReportControlBlock_setIntgPd(ClientReportControlBlock self, uint32_t intgPd); bool ClientReportControlBlock_getGI(ClientReportControlBlock self); void ClientReportControlBlock_setGI(ClientReportControlBlock self, bool gi); bool ClientReportControlBlock_getPurgeBuf(ClientReportControlBlock self); void ClientReportControlBlock_setPurgeBuf(ClientReportControlBlock self, bool purgeBuf); bool ClientReportControlBlock_hasResvTms(ClientReportControlBlock self); int16_t ClientReportControlBlock_getResvTms(ClientReportControlBlock self); void ClientReportControlBlock_setResvTms(ClientReportControlBlock self, int16_t resvTms); MmsValue* ClientReportControlBlock_getEntryId(ClientReportControlBlock self); void ClientReportControlBlock_setEntryId(ClientReportControlBlock self, MmsValue* entryId); uint64_t ClientReportControlBlock_getEntryTime(ClientReportControlBlock self); MmsValue* ClientReportControlBlock_getOwner(ClientReportControlBlock self); ``` --- ## 6. 控制服务(Control) 客户端控制功能用于向服务端发送遥控命令。支持直控(Direct)和选控(SBO,Select Before Operate)。 ### 6.1 控制对象生命周期 ```c // 创建(同步版本:会请求服务端获取 ctlModel 等信息,可能阻塞) ControlObjectClient ctl = ControlObjectClient_create( "simpleIOGenericIO/GGIO1.SP.Pos1", conn); // 创建(扩展版本:不阻塞,需要已知 ctlModel 和 controlObjectSpec) ControlObjectClient ctl = ControlObjectClient_createEx( objRef, conn, CONTROL_MODEL_SBO_NORMAL, controlObjectSpec); // 销毁 ControlObjectClient_destroy(ctl); ``` ### 6.2 同步控制操作 ```c // === 直控-普通安全 (DIRECT_NORMAL) === // 直接发送 operate MmsValue* ctlVal = MmsValue_newBoolean(true); bool success = ControlObjectClient_operate(ctl, ctlVal, 0); // operTime=0 立即执行 MmsValue_delete(ctlVal); // === 选控-普通安全 (SBO_NORMAL) === // 第一步:select bool ok = ControlObjectClient_select(ctl); // 第二步:operate bool ok = ControlObjectClient_operate(ctl, ctlVal, 0); // === 选控-增强安全 (SBO_ENHANCED) === // 第一步:select with value bool ok = ControlObjectClient_selectWithValue(ctl, ctlVal); // 第二步:operate bool ok = ControlObjectClient_operate(ctl, ctlVal, 0); // === 取消操作 === bool ok = ControlObjectClient_cancel(ctl); ``` ### 6.3 异步控制操作 ```c void actionHandler(uint32_t invokeId, void* param, IedClientError err, ControlActionType type, bool success) { switch (type) { case CONTROL_ACTION_TYPE_SELECT: /* select 结果 */ break; case CONTROL_ACTION_TYPE_OPERATE: /* operate 结果 */ break; case CONTROL_ACTION_TYPE_CANCEL: /* cancel 结果 */ break; } } uint32_t id; id = ControlObjectClient_operateAsync(ctl, &err, ctlVal, 0, actionHandler, myParam); id = ControlObjectClient_selectAsync(ctl, &err, actionHandler, myParam); id = ControlObjectClient_selectWithValueAsync(ctl, &err, ctlVal, actionHandler, myParam); id = ControlObjectClient_cancelAsync(ctl, &err, actionHandler, myParam); ``` ### 6.4 命令终止回调(增强安全模式) ```c void terminationHandler(void* parameter, ControlObjectClient controlClient) { LastApplError lastErr = ControlObjectClient_getLastApplError(controlClient); if (lastErr.error == CONTROL_ERROR_NO_ERROR) { // CommandTermination+ (成功) } else { // CommandTermination- (失败) } } ControlObjectClient_setCommandTerminationHandler(ctl, terminationHandler, param); ``` ### 6.5 控制对象其他 API ```c const char* ControlObjectClient_getObjectReference(ControlObjectClient self); ControlModel ControlObjectClient_getControlModel(ControlObjectClient self); void ControlObjectClient_setControlModel(ControlObjectClient self, ControlModel ctlModel); void ControlObjectClient_changeServerControlModel(ControlObjectClient self, ControlModel ctlModel); MmsType ControlObjectClient_getCtlValType(ControlObjectClient self); IedClientError ControlObjectClient_getLastError(ControlObjectClient self); LastApplError ControlObjectClient_getLastApplError(ControlObjectClient self); // 控制参数设置 void ControlObjectClient_setTestMode(ControlObjectClient self, bool value); void ControlObjectClient_setOrigin(ControlObjectClient self, const char* orIdent, int orCat); void ControlObjectClient_setInterlockCheck(ControlObjectClient self, bool value); void ControlObjectClient_setSynchroCheck(ControlObjectClient self, bool value); void ControlObjectClient_useConstantT(ControlObjectClient self, bool useConstantT); ``` **Originator 类别(orCat)**: ```c #define CONTROL_ORCAT_NOT_SUPPORTED 0 #define CONTROL_ORCAT_BAY_CONTROL 1 // 间隔层操作员 #define CONTROL_ORCAT_STATION_CONTROL 2 // 站控层操作员 #define CONTROL_ORCAT_REMOTE_CONTROL 3 // 远方控制 #define CONTROL_ORCAT_AUTOMATIC_BAY 4 // 间隔层自动 #define CONTROL_ORCAT_AUTOMATIC_STATION 5 // 站控层自动 #define CONTROL_ORCAT_AUTOMATIC_REMOTE 6 // 远方自动 #define CONTROL_ORCAT_MAINTENANCE 7 // 维护工具 #define CONTROL_ORCAT_PROCESS 8 // 过程层 ``` --- ## 7. 数据集服务(DataSet) 数据集是一组 FCD/FCDA 引用的集合,用于报告、日志等。 ### 7.1 读取数据集 ```c // 同步 ClientDataSet ds = IedConnection_readDataSetValues(conn, &err, "simpleIOGenericIO/LLN0$Events", NULL); // NULL=创建新实例 if (ds) { MmsValue* values = ClientDataSet_getValues(ds); // MMS_ARRAY int size = ClientDataSet_getDataSetSize(ds); char* ref = ClientDataSet_getReference(ds); ClientDataSet_destroy(ds); } // 异步 IedConnection_readDataSetValuesAsync(conn, &err, dsRef, NULL, handler, param); // 回调: void handler(uint32_t invokeId, void* param, IedClientError err, ClientDataSet dataSet) ``` ### 7.2 创建/删除数据集 ```c // 创建 LinkedList members = LinkedList_create(); LinkedList_add(members, strdup("simpleIOGenericIO/GGIO1.ST.Ind1.stVal[ST]")); LinkedList_add(members, strdup("simpleIOGenericIO/GGIO1.ST.Ind2.stVal[ST]")); IedConnection_createDataSet(conn, &err, "simpleIOGenericIO/LLN0.MyDS", members); LinkedList_destroyDeep(members, free); // 异步创建 IedConnection_createDataSetAsync(conn, &err, ref, members, handler, param); // 删除 bool deleted = IedConnection_deleteDataSet(conn, &err, "simpleIOGenericIO/LLN0.MyDS"); // 异步删除 IedConnection_deleteDataSetAsync(conn, &err, ref, handler, param); ``` ### 7.3 获取数据集目录 ```c // 同步:返回 LinkedList 所有成员引用 bool isDeletable; LinkedList elements = IedConnection_getDataSetDirectory(conn, &err, ref, &isDeletable); // 异步 IedConnection_getDataSetDirectoryAsync(conn, &err, ref, handler, param); // 回调: void handler(uint32_t id, void* param, IedClientError err, LinkedList dir, bool isDeletable) ``` ### 7.4 写入数据集 ```c LinkedList values = LinkedList_create(); LinkedList_add(values, MmsValue_newFloat(1.5f)); LinkedList_add(values, MmsValue_newBoolean(true)); LinkedList accessResults = NULL; IedConnection_writeDataSetValues(conn, &err, ref, values, &accessResults); // 异步 IedConnection_writeDataSetValuesAsync(conn, &err, ref, values, handler, param); // 回调: void handler(uint32_t id, void* param, IedClientError err, LinkedList accessResults) ``` ### 7.5 ClientDataSet API ```c void ClientDataSet_destroy(ClientDataSet self); MmsValue* ClientDataSet_getValues(ClientDataSet self); // MMS_ARRAY char* ClientDataSet_getReference(ClientDataSet self); int ClientDataSet_getDataSetSize(ClientDataSet self); // 成员数 ``` --- ## 8. 文件服务(File) ### 8.1 获取文件目录 ```c // 获取根目录 LinkedList /**/ dir = IedConnection_getFileDirectory(conn, &err, NULL); if (dir) { // 遍历 LinkedList element = LinkedList_getNext(dir); while (element) { FileDirectoryEntry entry = (FileDirectoryEntry) element->data; const char* name = FileDirectoryEntry_getFileName(entry); uint32_t size = FileDirectoryEntry_getFileSize(entry); uint64_t mtime = FileDirectoryEntry_getLastModified(entry); element = LinkedList_getNext(element); } LinkedList_destroyDeep(dir, (LinkedListValueDeleteFunction)FileDirectoryEntry_destroy); } // 扩展版本(支持分页) bool moreFollows; LinkedList dir = IedConnection_getFileDirectoryEx(conn, &err, NULL, NULL, &moreFollows); // 如果 moreFollows=true,用最后一个文件名做 continueAfter 继续获取 // 异步版本 IedConnection_getFileDirectoryAsyncEx(conn, &err, dirName, continueAfter, handler, param); ``` ### 8.2 下载文件(GetFile) ```c // 同步 bool fileHandler(void* parameter, uint8_t* buffer, uint32_t bytesRead) { // 将 buffer 中的数据写入本地文件 fwrite(buffer, 1, bytesRead, (FILE*)parameter); return true; // 返回 true 继续下载 } uint32_t totalBytes = IedConnection_getFile(conn, &err, "remoteFile.txt", fileHandler, fp); // 异步 bool asyncFileHandler(uint32_t invokeId, void* parameter, IedClientError err, uint32_t originalInvokeId, uint8_t* buffer, uint32_t bytesRead, bool moreFollows) { if (err != IED_ERROR_OK) { /* 错误处理 */ return false; } fwrite(buffer, 1, bytesRead, (FILE*)parameter); return moreFollows; // 还有更多数据 } uint32_t id = IedConnection_getFileAsync(conn, &err, "remoteFile.txt", asyncFileHandler, fp); ``` ### 8.3 上传文件(SetFile)/ 删除文件 ```c // 上传 IedConnection_setFile(conn, &err, "localFile.txt", "remoteFile.txt"); // 异步 IedConnection_setFileAsync(conn, &err, src, dst, handler, param); // 删除 IedConnection_deleteFile(conn, &err, "remoteFile.txt"); // 异步 IedConnection_deleteFileAsync(conn, &err, filename, handler, param); ``` --- ## 9. 日志服务(Log) ```c // 按时间范围查询日志 bool moreFollows; LinkedList /* */ entries = IedConnection_queryLogByTime( conn, &err, "LDName/LNName$LogName", startTime, endTime, &moreFollows); // 按条目ID查询后续日志 LinkedList entries = IedConnection_queryLogAfter( conn, &err, "LDName/LNName$LogName", entryID, timeStamp, &moreFollows); // 异步版本 IedConnection_queryLogByTimeAsync(conn, &err, ref, start, end, handler, param); IedConnection_queryLogAfterAsync(conn, &err, ref, entryID, ts, handler, param); ``` --- ## 10. 模型发现服务 用于动态发现服务器端设备模型结构。 ### 10.1 获取完整设备模型 ```c IedConnection_getDeviceModelFromServer(conn, &err); // 此调用会缓存模型,后续的查询基于缓存,不再产生网络请求 ``` ### 10.2 目录查询 ```c // 获取逻辑设备列表 LinkedList /* */ lds = IedConnection_getLogicalDeviceList(conn, &err); // 或:IedConnection_getServerDirectory(conn, &err, false); // 获取逻辑节点列表 LinkedList lns = IedConnection_getLogicalDeviceDirectory(conn, &err, "LDName"); // 获取逻辑节点目录(按ACSI类别过滤) LinkedList items = IedConnection_getLogicalNodeDirectory(conn, &err, "LDName/LLN0", ACSI_CLASS_DATA_SET); // 获取数据对象(DO)的子元素 LinkedList das = IedConnection_getDataDirectory(conn, &err, "LDName/GGIO1.ST.Ind1"); LinkedList das = IedConnection_getDataDirectoryFC(conn, &err, "LDName/GGIO1.ST.Ind1"); // 带 FC 后缀 LinkedList das = IedConnection_getDataDirectoryByFC(conn, &err, "LDName/GGIO1.ST", IEC61850_FC_ST); // 获取变量规格 MmsVariableSpecification* spec = IedConnection_getVariableSpecification(conn, &err, ref, fc); // 获取逻辑设备所有MMS变量名 LinkedList vars = IedConnection_getLogicalDeviceVariables(conn, &err, "LDName"); // 获取逻辑设备所有数据集名 LinkedList dss = IedConnection_getLogicalDeviceDataSets(conn, &err, "LDName"); ``` ### 10.3 ACSI 类别枚举 ```c typedef enum { ACSI_CLASS_DATA_OBJECT, ACSI_CLASS_DATA_SET, ACSI_CLASS_BRCB, ACSI_CLASS_URCB, ACSI_CLASS_LCB, ACSI_CLASS_LOG, ACSI_CLASS_SGCB, ACSI_CLASS_GoCB, ACSI_CLASS_GsCB, ACSI_CLASS_MSVCB, ACSI_CLASS_USVCB } ACSIClass; ``` ### 10.4 异步模型发现 ```c IedConnection_getServerDirectoryAsync(conn, &err, NULL, NULL, handler, param); IedConnection_getLogicalDeviceVariablesAsync(conn, &err, ld, NULL, NULL, handler, param); IedConnection_getLogicalDeviceDataSetsAsync(conn, &err, ld, NULL, NULL, handler, param); IedConnection_getVariableSpecificationAsync(conn, &err, ref, fc, handler, param); ``` --- ## 11. SV/GOOSE 控制块处理 ### 11.1 SV 控制块 ```c ClientSVControlBlock svcb = ClientSVControlBlock_create(conn, "ref"); bool ena = ClientSVControlBlock_getSvEna(svcb); ClientSVControlBlock_setSvEna(svcb, true); char* msvID = ClientSVControlBlock_getMsvID(svcb); char* ds = ClientSVControlBlock_getDatSet(svcb); // 需手动 free uint32_t confRev = ClientSVControlBlock_getConfRev(svcb); uint16_t smpRate = ClientSVControlBlock_getSmpRate(svcb); uint8_t smpMod = ClientSVControlBlock_getSmpMod(svcb); int noASDU = ClientSVControlBlock_getNoASDU(svcb); PhyComAddress addr = ClientSVControlBlock_getDstAddress(svcb); int optFlds = ClientSVControlBlock_getOptFlds(svcb); bool mcast = ClientSVControlBlock_isMulticast(svcb); IedClientError err = ClientSVControlBlock_getLastComError(svcb); ClientSVControlBlock_destroy(svcb); ``` ### 11.2 GOOSE 控制块 ```c ClientGooseControlBlock gcb = ClientGooseControlBlock_create("ref"); // 读取服务端GoCB IedConnection_getGoCBValues(conn, &err, "simpleIOGenericIO/LLN0.gcbEvents", gcb); // 或传入 NULL 创建新实例:gcb = IedConnection_getGoCBValues(conn, &err, ref, NULL); // 读写属性 bool ena = ClientGooseControlBlock_getGoEna(gcb); ClientGooseControlBlock_setGoEna(gcb, true); const char* goID = ClientGooseControlBlock_getGoID(gcb); ClientGooseControlBlock_setGoID(gcb, "newID"); const char* ds = ClientGooseControlBlock_getDatSet(gcb); ClientGooseControlBlock_setDatSet(gcb, "LD/LLN0$ds1"); uint32_t confRev = ClientGooseControlBlock_getConfRev(gcb); bool ndsComm = ClientGooseControlBlock_getNdsComm(gcb); uint32_t minTime = ClientGooseControlBlock_getMinTime(gcb); uint32_t maxTime = ClientGooseControlBlock_getMaxTime(gcb); bool fixedOffs = ClientGooseControlBlock_getFixedOffs(gcb); PhyComAddress addr = ClientGooseControlBlock_getDstAddress(gcb); ClientGooseControlBlock_setDstAddress(gcb, value); // 写入服务端 IedConnection_setGoCBValues(conn, &err, gcb, GOCB_ELEMENT_GO_ENA | GOCB_ELEMENT_DATSET, false); ClientGooseControlBlock_destroy(gcb); ``` **GoCB 元素掩码**: ```c #define GOCB_ELEMENT_GO_ENA 1 #define GOCB_ELEMENT_GO_ID 2 #define GOCB_ELEMENT_DATSET 4 #define GOCB_ELEMENT_CONF_REV 8 #define GOCB_ELEMENT_NDS_COMM 16 #define GOCB_ELEMENT_DST_ADDRESS 32 #define GOCB_ELEMENT_MIN_TIME 64 #define GOCB_ELEMENT_MAX_TIME 128 #define GOCB_ELEMENT_FIXED_OFFS 256 #define GOCB_ELEMENT_ALL 511 ``` --- ## 12. MMS低层传输层访问 当高层 API 不够用时,可通过 `IedConnection_getMmsConnection()` 获取底层 `MmsConnection`,直接使用 `mms_client_connection.h` 中的低层 API: ```c // 获取底层连接 MmsConnection mms = IedConnection_getMmsConnection(conn); // 低层API包含: // - 直接域名(domain)和变量名(item)的读写 // - 命名变量列表操作 // - 文件服务(底层的FileOpen/Read/Close等) // - 日志/Journal读取 ``` 一般开发不需要使用低层 API,只有在高层 API 不支持的特殊场景(如非标准服务器)才需要。 --- ## 13. 错误码完整参考 ```c typedef enum { IED_ERROR_OK = 0, // 成功 IED_ERROR_NOT_CONNECTED = 1, // 未连接 IED_ERROR_ALREADY_CONNECTED = 2, // 已连接(重复连接) IED_ERROR_CONNECTION_LOST = 3, // 连接丢失 IED_ERROR_SERVICE_NOT_SUPPORTED = 4, // 服务不支持 IED_ERROR_CONNECTION_REJECTED = 5, // 连接被拒绝 IED_ERROR_OUTSTANDING_CALL_LIMIT_REACHED = 6, // 未完成调用达上限 IED_ERROR_USER_PROVIDED_INVALID_ARGUMENT = 10,// 无效参数 IED_ERROR_ENABLE_REPORT_FAILED_DATASET_MISMATCH = 11, // 报告使能失败(数据集不匹配) IED_ERROR_OBJECT_REFERENCE_INVALID = 12, // 对象引用无效 IED_ERROR_UNEXPECTED_VALUE_RECEIVED = 13, // 收到意外类型值 IED_ERROR_TIMEOUT = 20, // 超时 IED_ERROR_ACCESS_DENIED = 21, // 访问被拒绝 IED_ERROR_OBJECT_DOES_NOT_EXIST = 22, // 对象不存在 IED_ERROR_OBJECT_EXISTS = 23, // 对象已存在 IED_ERROR_OBJECT_ACCESS_UNSUPPORTED = 24, // 访问方式不支持 IED_ERROR_TYPE_INCONSISTENT = 25, // 类型不一致 IED_ERROR_TEMPORARILY_UNAVAILABLE = 26, // 临时不可用 IED_ERROR_OBJECT_UNDEFINED = 27, // 对象未定义 IED_ERROR_INVALID_ADDRESS = 28, // 无效地址 IED_ERROR_HARDWARE_FAULT = 29, // 硬件故障 IED_ERROR_TYPE_UNSUPPORTED = 30, // 类型不支持 IED_ERROR_OBJECT_ATTRIBUTE_INCONSISTENT = 31, // 属性不一致 IED_ERROR_OBJECT_VALUE_INVALID = 32, // 对象值无效 IED_ERROR_OBJECT_INVALIDATED = 33, // 对象已失效 IED_ERROR_MALFORMED_MESSAGE = 34, // 畸形消息 IED_ERROR_SERVICE_NOT_IMPLEMENTED = 98, // 服务未实现 IED_ERROR_UNKNOWN = 99 // 未知错误 } IedClientError; ``` --- ## 14. 运行模式:线程模式 vs 非线程模式 ### 线程模式(默认) ```c IedConnection conn = IedConnection_create(); // 库内部创建后台线程自动处理 MMS 消息 // 可以使用所有同步/异步 API IedConnection_connect(conn, &err, host, port); IedConnection_destroy(conn); ``` ### 非线程模式 ```c IedConnection conn = IedConnection_createEx(NULL, false); // useThreads=false // 必须使用异步 API IedConnection_connectAsync(conn, &err, host, port); // 周期性轮询 while (running) { bool canPause = IedConnection_tick(conn); // canPause=true: 当前无活跃请求,调用线程可以挂起 // canPause=false: 忙,应尽快再次调用 tick if (canPause) { usleep(10000); // 10ms } } // 警告:不要使用同步 API!它会永远阻塞(因为没有后台线程驱动) ``` --- ## 15. 与 RTU 项目对照 RTU 项目中,`src/protocol/libmms_m/` 封装了客户端 API,核心数据结构为 `stru_mms_m_obj`: | RTU 封装层 | libiec61850 API | |-----------|----------------| | `stru_mms_m_obj.run.con` | `IedConnection` | | `stru_mms_m_obj.run.con_state` | `IedConnection_getState()` | | `stru_ln_rpt.rcb` | `ClientReportControlBlock` | | `stru_ld_dataset.ln_datasets[]` | `IedConnection_readDataSetValues()` | | `stru_mms_m_obj.run.out_status_cb` | `IedConnection_installStateChangedHandler()` | | `mms_m_sg.cpp` (定值组读写) | `IedConnection_readObject`/`writeObject` with FC=SG/SE | | `mms_m_file.cpp` (文件传输) | `IedConnection_getFile`/`setFile` | **线程模型**:RTU 客户端使用独立线程(`pthread_task`),通过 `sem` 信号量进行同步。运行间隔为 `MMS_M_THREAD_RUN_TM = 300ms`。 **关键使用模式**(参考 RTU 代码): ```cpp // RTU 中的典型初始化流程 stru_mms_m_obj* obj = new stru_mms_m_obj; obj->run.con = IedConnection_create(); IedConnection_setConnectTimeout(obj->run.con, obj->connectionTimeout); // 连接 IedClientError err; IedConnection_connect(obj->run.con, &err, obj->run.ip.c_str(), obj->run.port); obj->run.con_state = IedConnection_getState(obj->run.con); // 安装报告回调 IedConnection_installReportHandler(obj->run.con, rcbRef.c_str(), rptId.c_str(), reportCallback, obj); ``` --- > 本文档基于 `libiec61850-1.5.3/src/iec61850/inc/iec61850_client.h` (3091行) 和 `src/mms/inc/mms_value.h` (1062行) 完整归纳,覆盖所有公开 C API。