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

1208 lines
47 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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和选控SBOSelect 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<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 写入数据集
```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 /*<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
```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 /* <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 获取完整设备模型
```c
IedConnection_getDeviceModelFromServer(conn, &err);
// 此调用会缓存模型,后续的查询基于缓存,不再产生网络请求
```
### 10.2 目录查询
```c
// 获取逻辑设备列表
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 类别枚举
```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。