RTU/claude/工程/libiec61850_MMS客户端API开发手册.md

47 KiB
Raw Blame History

libiec61850-1.5.3 MMS 客户端 API 开发手册

本文档基于 libiec61850 v1.5.3 源码,覆盖 MMS 客户端全部公开 C API达到开发者脱离源码即可进行项目开发的标准。


目录

  1. 架构概述
  2. 核心数据类型
  3. 连接管理
  4. 数据读写
  5. 报告服务Report
  6. 控制服务Control
  7. 数据集服务DataSet
  8. 文件服务File
  9. 日志服务Log
  10. 模型发现服务
  11. SV/GOOSE 控制块处理
  12. MSO 传输层访问
  13. 错误码完整参考
  14. 运行模式:线程模式 vs 非线程模式
  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 主要不透明句柄

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 连接状态

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 控制模型

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 质量

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

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 回调函数类型速查

// 通用服务回调(写操作完成等)
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

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;

构造(仅在需要主动创建值时使用,读取操作由库自动创建):

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);

取值

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);

赋值

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);

生命周期

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 完整连接生命周期

// 步骤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 连接状态查询

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 获取底层连接

MmsConnection IedConnection_getMmsConnection(IedConnection self);

返回底层 MmsConnection 句柄,可用于直接调用低层 MMS API。


4. 数据读写

4.1 通用读写(通过 FCDA 引用 + FC

// 同步读:返回 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 便捷类型读写(推荐用于简单类型)

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 指针则复用。

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 时指定要写入哪些字段)

#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

#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— 决定报告包含哪些信息

#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 完整报告配置与接收流程

// 步骤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 包含原因枚举

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 完整列表

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 完整列表

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和选控SBOSelect Before Operate

6.1 控制对象生命周期

// 创建(同步版本:会请求服务端获取 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 同步控制操作

// === 直控-普通安全 (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 异步控制操作

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 命令终止回调(增强安全模式)

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

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

#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 读取数据集

// 同步
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 创建/删除数据集

// 创建
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 获取数据集目录

// 同步:返回 LinkedList<char*> 所有成员引用
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<char*> dir, bool isDeletable)

7.4 写入数据集

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

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 获取文件目录

// 获取根目录
LinkedList /*<FileDirectoryEntry>*/ 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

// 同步
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/ 删除文件

// 上传
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

// 按时间范围查询日志
bool moreFollows;
LinkedList /* <MmsJournalEntry> */ 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 获取完整设备模型

IedConnection_getDeviceModelFromServer(conn, &err);
// 此调用会缓存模型,后续的查询基于缓存,不再产生网络请求

10.2 目录查询

// 获取逻辑设备列表
LinkedList /* <char*> */ 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 类别枚举

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 异步模型发现

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 控制块

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 控制块

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 元素掩码

#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

// 获取底层连接
MmsConnection mms = IedConnection_getMmsConnection(conn);

// 低层API包含
// - 直接域名(domain)和变量名(item)的读写
// - 命名变量列表操作
// - 文件服务底层的FileOpen/Read/Close等
// - 日志/Journal读取

一般开发不需要使用低层 API只有在高层 API 不支持的特殊场景(如非标准服务器)才需要。


13. 错误码完整参考

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 非线程模式

线程模式(默认)

IedConnection conn = IedConnection_create();
// 库内部创建后台线程自动处理 MMS 消息
// 可以使用所有同步/异步 API
IedConnection_connect(conn, &err, host, port);
IedConnection_destroy(conn);

非线程模式

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 代码):

// 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。