diff --git a/libiec61850/libiec61850手册/libiec61850_客户端开发手册.md b/libiec61850/libiec61850手册/libiec61850_客户端开发手册.md new file mode 100644 index 0000000..feaf82f --- /dev/null +++ b/libiec61850/libiec61850手册/libiec61850_客户端开发手册.md @@ -0,0 +1,1884 @@ +# libiec61850 客户端开发手册 + +> 基于 libiec61850 v1.5.3 完整 API 参考。本文档旨在做到脱离代码即可完成客户端开发。 + +--- + +## 目录 + +1. [概述与连接生命周期](#1-概述与连接生命周期) +2. [IedConnection 连接管理](#2-iedconnection-连接管理) +3. [数据读写操作](#3-数据读写操作) +4. [数据集 (DataSet) 操作](#4-数据集-dataset-操作) +5. [报告 (Report) 控制](#5-报告-report-控制) +6. [控制操作 (Control)](#6-控制操作-control) +7. [服务器目录浏览与发现](#7-服务器目录浏览与发现) +8. [文件服务](#8-文件服务) +9. [日志服务 (Journal)](#9-日志服务-journal) +10. [GOOSE 订阅](#10-goose-订阅) +11. [GOOSE 发布 (独立)](#11-goose-发布-独立) +12. [Sampled Values 客户端](#12-sampled-values-客户端) +13. [SV 控制块 (SVCB)](#13-sv-控制块-svcb) +14. [MmsValue 完整操作手册](#14-mmsvalue-完整操作手册) +15. [MmsVariableSpecification](#15-mmsvariablespecification) +16. [TLS 安全连接](#16-tls-安全连接) +17. [单线程模式 (非线程模式)](#17-单线程模式-非线程模式) +18. [MmsConnection 底层接口](#18-mmsconnection-底层接口) +19. [完整代码示例](#19-完整代码示例) + +--- + +## 1. 概述与连接生命周期 + +``` +IedConnection_create() // 创建连接实例 + ↓ +[可选] 设置本地地址/超时 + ↓ +IedConnection_connect() // 连接到服务器 (阻塞) + ↓ +读写操作 / 报告订阅 / 控制操作 + ↓ +IedConnection_close() // 关闭连接 (推荐) + or IedConnection_abort() // 中断连接 + or IedConnection_release() // 正常释放 (MMS Conclude) + ↓ +IedConnection_destroy() // 销毁并释放所有资源 +``` + +### 1.1 连接状态枚举 + +```c +typedef enum { + IED_STATE_CLOSED = 0, // 关闭/空闲 + IED_STATE_CONNECTING, // 正在连接 + IED_STATE_CONNECTED, // 已连接 + IED_STATE_CLOSING // 正在关闭 +} IedConnectionState; +``` + +### 1.2 错误码枚举 (IedClientError) + +| 错误码 | 值 | 含义 | +|--------|-----|------| +| `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 | 未知错误 | + +--- + +## 2. IedConnection 连接管理 + +### 2.1 创建连接实例 + +```c +// 基本创建 (多线程模式, 非TLS) +IedConnection IedConnection_create(void); + +// 扩展创建 +IedConnection IedConnection_createEx( + TLSConfiguration tlsConfig, // TLS 配置, NULL 表示非TLS + bool useThreads); // true=多线程, false=单线程 +// useThreads=false 时需要使用异步API + IedConnection_tick() + +// 创建 TLS 连接 (已废弃, 推荐使用 createEx) +IedConnection IedConnection_createWithTlsSupport(TLSConfiguration tlsConfig); + +// 销毁连接 (自动关闭已连接) +void IedConnection_destroy(IedConnection self); +``` + +### 2.2 连接配置 + +```c +// 设置本地地址/端口 +void IedConnection_setLocalAddress(IedConnection self, + const char* localIpAddress, int localPort); +// localPort < 1 时由OS自动选择端口 +// 必须在 connect 前调用 + +// 设置连接超时 (ms) +void IedConnection_setConnectTimeout(IedConnection self, uint32_t timeoutInMs); +// 必须在 connect 前调用 + +// 设置请求超时 (ms) - 可随时调用 +void IedConnection_setRequestTimeout(IedConnection self, uint32_t timeoutInMs); + +// 获取请求超时 +uint32_t IedConnection_getRequestTimeout(IedConnection self); + +// 设置客户端生成的所有时间戳的时间品质 +void IedConnection_setTimeQuality(IedConnection self, + bool leapSecondKnown, + bool clockFailure, + bool clockNotSynchronized, + int subsecondPrecision); +``` + +### 2.3 连接与断开 + +```c +// === 同步连接 (阻塞直到连接建立或超时) === +void IedConnection_connect(IedConnection self, + IedClientError* error, // 输出: 错误码 + const char* hostname, // 主机名或IP地址 + int tcpPort); // TCP端口 + +// === 异步连接 (立即返回) === +void IedConnection_connectAsync(IedConnection self, + IedClientError* error, + const char* hostname, + int tcpPort); +// 需要通过状态变化处理器或轮询 IedConnection_getState() 来跟踪状态 + +// === 获取连接状态 === +IedConnectionState IedConnection_getState(IedConnection self); + +// === 获取最后一次应用错误 === +LastApplError IedConnection_getLastApplError(IedConnection self); +// LastApplError 结构: +// int ctlNum; +// ControlLastApplError error; +// ControlAddCause addCause; + +// === 连接断开方法 === + +// 关闭连接 (简单关闭TCP, 无握手) - 推荐 +void IedConnection_close(IedConnection self); + +// Abort (发送 ACSE abort 消息后关闭) +void IedConnection_abort(IedConnection self, IedClientError* error); + +// 异步 Abort +void IedConnection_abortAsync(IedConnection self, IedClientError* error); + +// Release (发送 MMS conclude 消息 - 正常释放) +void IedConnection_release(IedConnection self, IedClientError* error); +// 注意: 服务器可能拒绝, 此时连接仍保持 + +// 异步 Release +void IedConnection_releaseAsync(IedConnection self, IedClientError* error); +``` + +### 2.4 连接状态回调 + +```c +// 状态变化处理器 (推荐) +typedef void (*IedConnection_StateChangedHandler)( + void* parameter, + IedConnection connection, + IedConnectionState newState); + +void IedConnection_installStateChangedHandler(IedConnection self, + IedConnection_StateChangedHandler handler, void* parameter); + +// 旧版连接关闭处理器 (已废弃) +typedef void (*IedConnectionClosedHandler)( + void* parameter, IedConnection connection); + +void IedConnection_installConnectionClosedHandler(IedConnection self, + IedConnectionClosedHandler handler, void* parameter); +``` + +### 2.5 底层连接访问 + +```c +// 获取底层 MmsConnection (用于直接调用 MMS 级服务) +MmsConnection IedConnection_getMmsConnection(IedConnection self); + +// 单线程模式: 周期性调用以处理连接事件 +bool IedConnection_tick(IedConnection self); +// 返回: true = 连接等待中可挂起, false = 忙需尽快再调用 +``` + +--- + +## 3. 数据读写操作 + +### 3.1 同步读取 + +```c +// 读取单个对象 +MmsValue* IedConnection_readObject(IedConnection self, + IedClientError* error, + const char* objectReference, // 对象引用路径, 如 "simpleIOGenericIO/GGIO1.AnIn1.mag.f" + FunctionalConstraint fc); // 功能约束, 如 IEC61850_FC_MX +// 返回: MmsValue* 或 NULL (失败) +// 注意: 即使 mmsError=OK, 返回值也可能为 NULL (服务器返回空结果列表) + +// 读取数组元素 (支持索引语法) +// "testComplexArray/MHAI1.HA.phsAHar(2).cVal.mag.f" // 数组第2个元素的子属性 +``` + +### 3.2 异步读取 + +```c +// 回调类型 +typedef void (*IedConnection_ReadObjectHandler)( + uint32_t invokeId, // 请求调用ID + void* parameter, // 用户参数 + IedClientError err, // 错误码 + MmsValue* value); // 读取的值 (需由用户 MmsValue_delete 释放!) + +// 异步读取 +void IedConnection_readObjectAsync(IedConnection self, + IedClientError* error, + const char* objectReference, + FunctionalConstraint fc, + IedConnection_ReadObjectHandler handler, + void* parameter); +``` + +### 3.3 同步写入 + +```c +// 写入单个对象 +void IedConnection_writeObject(IedConnection self, + IedClientError* error, + const char* objectReference, + FunctionalConstraint fc, + MmsValue* value); + +// 示例: 写 Visible String +MmsValue* val = MmsValue_newVisibleString("libiec61850.com"); +IedConnection_writeObject(con, &error, + "simpleIOGenericIO/GGIO1.NamPlt.vendor", IEC61850_FC_DC, val); +MmsValue_delete(val); +``` + +### 3.4 异步写入 + +```c +// 通用回调 +typedef void (*IedConnection_GenericServiceHandler)( + uint32_t invokeId, void* parameter, IedClientError err); + +void IedConnection_writeObjectAsync(IedConnection self, + IedClientError* error, + const char* objectReference, + FunctionalConstraint fc, + MmsValue* value, + IedConnection_GenericServiceHandler handler, + void* parameter); +``` + +--- + +## 4. 数据集 (DataSet) 操作 + +### 4.1 ClientDataSet 类型 + +```c +typedef struct sClientDataSet* ClientDataSet; + +// 读取数据集值 +ClientDataSet IedConnection_readDataSetValues(IedConnection self, + IedClientError* error, + const char* dataSetReference, // 如 "simpleIOGenericIO/LLN0.Events" + ClientDataSet dataSet); // 可复用已有实例或传 NULL +// 返回: ClientDataSet 实例或 NULL + +// 异步版本 +void IedConnection_readDataSetValuesAsync(IedConnection self, + IedClientError* error, + const char* dataSetReference, + ClientDataSet dataSet, + IedConnection_ReadDataSetHandler handler, void* parameter); + +// 获取数据集大小 +int ClientDataSet_getDataSetSize(ClientDataSet self); + +// 获取数据集值 (返回 MMS_ARRAY 类型的 MmsValue*) +MmsValue* ClientDataSet_getValues(ClientDataSet self); + +// 获取数据集目录 (返回 LinkedList 元素名称列表) +LinkedList IedConnection_getDataSetDirectory(IedConnection self, + IedClientError* error, + const char* dataSetReference, + ClientDataSet dataSet); + +// 写入数据集值 +void IedConnection_writeDataSetValuesAsync(IedConnection self, + IedClientError* error, + const char* dataSetReference, + LinkedList /* */ values, + IedConnection_WriteDataSetHandler handler, void* parameter); + +// 销毁数据集 +void ClientDataSet_destroy(ClientDataSet self); +``` + +--- + +## 5. 报告 (Report) 控制 + +### 5.1 报告回调 + +```c +// 报告回调函数类型 +typedef void (*ReportCallbackFunction)( + void* parameter, // 用户参数 + ClientReport report); // 报告对象 + +// 安装报告处理器 +void IedConnection_installReportHandler(IedConnection self, + const char* rcbReference, // RCB引用, 如 "simpleIOGenericIO/LLN0.RP.EventsRCB01" + const char* rptId, // 报告ID (从 ClientReportControlBlock_getRptId 获取) + ReportCallbackFunction handler, + void* parameter); +``` + +### 5.2 ClientReport 读取 + +```c +// 获取报告中的数据集值 +MmsValue* ClientReport_getDataSetValues(ClientReport report); + +// 获取 RCB 引用 +const char* ClientReport_getRcbReference(ClientReport report); + +// 获取报告ID +const char* ClientReport_getRptId(ClientReport report); + +// 检查报告是否包含时间戳 +bool ClientReport_hasTimestamp(ClientReport report); + +// 获取报告时间戳 (Unix 毫秒时间戳) +uint64_t ClientReport_getTimestamp(ClientReport report); + +// 获取某个数据成员的包含原因 +ReasonForInclusion ClientReport_getReasonForInclusion(ClientReport report, int index); + +// 包含原因枚举值: +// IEC61850_REASON_NOT_INCLUDED - 未包含 +// IEC61850_REASON_DATA_CHANGE - 数据变化 +// IEC61850_REASON_QUALITY_CHANGE - 品质变化 +// IEC61850_REASON_DATA_UPDATE - 数据更新 +// IEC61850_REASON_INTEGRITY - 完整性 +// IEC61850_REASON_GI - 总召唤 +// IEC61850_REASON_APPLICATION_TRIGGER - 应用触发 +``` + +### 5.3 ClientReportControlBlock 操作 + +```c +// RCB 元素标志 (用于 setRCBValues) +#define RCB_ELEMENT_RPT_ENA 1 +#define RCB_ELEMENT_RESV 2 +#define RCB_ELEMENT_DATSET 4 +#define RCB_ELEMENT_TRG_OPS 8 +#define RCB_ELEMENT_OPT_FLDS 16 +#define RCB_ELEMENT_BUF_TIME 32 +#define RCB_ELEMENT_INTG_PD 64 +#define RCB_ELEMENT_GI 128 +#define RCB_ELEMENT_PURGE_BUF 256 +#define RCB_ELEMENT_ENTRY_ID 512 + +// 创建 RCB 客户端对象 +ClientReportControlBlock ClientReportControlBlock_create(const char* objectReference); + +// 读取 RCB 值 (从服务器) +ClientReportControlBlock IedConnection_getRCBValues(IedConnection self, + IedClientError* error, + const char* rcbReference, + ClientReportControlBlock rcb); // 已有对象或 NULL + +// 异步读取 RCB +void IedConnection_getRCBValuesAsync(IedConnection self, + IedClientError* error, + const char* rcbReference, + ClientReportControlBlock rcb, + IedConnection_GetRCBValuesHandler handler, void* parameter); + +// 设置 RCB 值 (写入服务器) +void IedConnection_setRCBValues(IedConnection self, + IedClientError* error, + ClientReportControlBlock rcb, + uint32_t parameterMask, // 要写入的参数掩码 (RCB_ELEMENT_*) + bool singleRequest); // true=单请求, false=分多请求 + +// 异步设置 RCB +void IedConnection_setRCBValuesAsync(IedConnection self, + IedClientError* error, + ClientReportControlBlock rcb, + uint32_t parameterMask, + bool singleRequest, + IedConnection_GenericServiceHandler handler, void* parameter); + +// === ClientReportControlBlock getters/setters === +const char* ClientReportControlBlock_getObjectReference(ClientReportControlBlock self); +const char* ClientReportControlBlock_getRptId(ClientReportControlBlock self); +bool ClientReportControlBlock_getRptEna(ClientReportControlBlock self); + +void ClientReportControlBlock_setRptEna(ClientReportControlBlock self, bool value); +void ClientReportControlBlock_setRptId(ClientReportControlBlock self, const char* rptId); +void ClientReportControlBlock_setDataSetReference(ClientReportControlBlock self, const char* dataSetRef); +void ClientReportControlBlock_setTrgOps(ClientReportControlBlock self, uint8_t trgOps); +void ClientReportControlBlock_setOptFlds(ClientReportControlBlock self, uint8_t optFlds); +void ClientReportControlBlock_setBufTm(ClientReportControlBlock self, uint32_t bufTm); +void ClientReportControlBlock_setIntgPd(ClientReportControlBlock self, uint32_t intgPd); +void ClientReportControlBlock_setGI(ClientReportControlBlock self, bool value); +void ClientReportControlBlock_setResv(ClientReportControlBlock self, bool value); +void ClientReportControlBlock_setPurgeBuf(ClientReportControlBlock self, bool value); +void ClientReportControlBlock_setEntryId(ClientReportControlBlock self, uint8_t* entryId); + +// 销毁 +void ClientReportControlBlock_destroy(ClientReportControlBlock self); +``` + +### 5.4 报告配置典型流程 + +```c +// 1. 获取当前 RCB 值 +ClientReportControlBlock rcb = + IedConnection_getRCBValues(con, &error, "simpleIOGenericIO/LLN0.RP.EventsRCB01", NULL); + +// 2. 获取 rptId (用于报告回调匹配) +const char* rptId = ClientReportControlBlock_getRptId(rcb); + +// 3. 安装报告回调 +IedConnection_installReportHandler(con, + "simpleIOGenericIO/LLN0.RP.EventsRCB01", + rptId, reportCallback, NULL); + +// 4. 配置 RCB 参数 +ClientReportControlBlock_setTrgOps(rcb, + TRG_OPT_DATA_UPDATE | TRG_OPT_INTEGRITY | TRG_OPT_GI); +ClientReportControlBlock_setRptEna(rcb, true); +ClientReportControlBlock_setIntgPd(rcb, 5000); // 5秒完整性周期 + +// 5. 写入服务器 +IedConnection_setRCBValues(con, &error, rcb, + RCB_ELEMENT_RPT_ENA | RCB_ELEMENT_TRG_OPS | RCB_ELEMENT_INTG_PD, + true); + +// 6. 触发总召唤 (GI) +Thread_sleep(1000); +ClientReportControlBlock_setGI(rcb, true); +IedConnection_setRCBValues(con, &error, rcb, RCB_ELEMENT_GI, true); + +// 7. 一段时间后禁用报告 +ClientReportControlBlock_setRptEna(rcb, false); +IedConnection_setRCBValues(con, &error, rcb, RCB_ELEMENT_RPT_ENA, true); + +// 8. 清理 +ClientReportControlBlock_destroy(rcb); +``` + +--- + +## 6. 控制操作 (Control) + +### 6.1 ControlObjectClient 创建与销毁 + +```c +// 创建控制对象客户端 (多线程模式) +ControlObjectClient ControlObjectClient_create( + const char* objectReference, // 如 "simpleIOGenericIO/GGIO1.SPCSO1" + IedConnection connection); + +// 创建控制对象客户端 (非线程模式 - 需提供 varSpec) +ControlObjectClient ControlObjectClient_createEx( + const char* objectReference, + IedConnection connection, + ControlModel controlModel, // 控制模型 + MmsVariableSpecification* varSpec); // 变量类型规格 + +// 销毁控制对象 +void ControlObjectClient_destroy(ControlObjectClient self); +``` + +### 6.2 控制操作函数 + +```c +// === 直接操作 (Direct Operate) === +bool ControlObjectClient_operate(ControlObjectClient self, + MmsValue* ctlVal, // 控制值 + uint64_t timeStamp); // 定时操作的时间戳, 0=立即执行 +// 返回: true=成功, false=失败 + +// === 选择 (Select) === +bool ControlObjectClient_select(ControlObjectClient self); +// 返回: true=选择成功 + +// === 带值选择 (Select With Value) === +bool ControlObjectClient_selectWithValue(ControlObjectClient self, + MmsValue* ctlVal); + +// === 取消 (Cancel) === +bool ControlObjectClient_cancel(ControlObjectClient self); + +// === 异步操作 === +void ControlObjectClient_operateAsync(ControlObjectClient self, + IedClientError* error, + MmsValue* ctlVal, + uint64_t timeStamp, + ControlAsyncHandler handler, void* parameter); + +void ControlObjectClient_selectAsync(ControlObjectClient self, + IedClientError* error, + ControlAsyncHandler handler, void* parameter); + +void ControlObjectClient_selectWithValueAsync(ControlObjectClient self, + IedClientError* error, + MmsValue* ctlVal, + ControlAsyncHandler handler, void* parameter); + +// 异步回调类型 +typedef void (*ControlAsyncHandler)( + uint32_t invokeId, void* parameter, + IedClientError err, + ControlActionType type, // select/operate/cancel + bool success); +``` + +### 6.3 CommandTermination (增强安全) + +```c +// 命令终止处理器 +typedef void (*CommandTerminationHandler)( + void* parameter, + ControlObjectClient connection); + +void ControlObjectClient_setCommandTerminationHandler( + ControlObjectClient self, + CommandTerminationHandler handler, void* parameter); + +// 获取上次应用错误 +LastApplError ControlObjectClient_getLastApplError(ControlObjectClient self); +// LastApplError 包含: .ctlNum, .error, .addCause +// 当 error != 0 表示 CommandTermination- +// 当 error == 0 表示 CommandTermination+ +``` + +### 6.4 来源设置 + +```c +// 设置控制来源 +void ControlObjectClient_setOrigin(ControlObjectClient self, + const char* orIdent, // 来源标识字符串 + int orCat); // 来源类别 (orCat) + +// 来源类别常量 (orCat): +// CONTROL_ORCAT_NOT_SUPPORTED = 0 +// CONTROL_ORCAT_BAY_CONTROL = 1 +// CONTROL_ORCAT_STATION_CONTROL = 2 +// CONTROL_ORCAT_REMOTE_CONTROL = 3 +// CONTROL_ORCAT_AUTOMATIC_BAY = 4 +// CONTROL_ORCAT_AUTOMATIC_STATION = 5 +// CONTROL_ORCAT_AUTOMATIC_REMOTE = 6 +// CONTROL_ORCAT_MAINTENANCE = 7 +// CONTROL_ORCAT_PROCESS = 8 +``` + +### 6.5 四种控制方式示例 + +```c +// 1. 直接控制 (Direct Normal) +ControlObjectClient ctl = ControlObjectClient_create(ref, con); +MmsValue* val = MmsValue_newBoolean(true); +ControlObjectClient_setOrigin(ctl, NULL, 3); +ControlObjectClient_operate(ctl, val, 0); +MmsValue_delete(val); +ControlObjectClient_destroy(ctl); + +// 2. 选择前操作 (SBO Normal) +ControlObjectClient ctl = ControlObjectClient_create(ref, con); +if (ControlObjectClient_select(ctl)) { + MmsValue* val = MmsValue_newBoolean(true); + ControlObjectClient_operate(ctl, val, 0); + MmsValue_delete(val); +} +ControlObjectClient_destroy(ctl); + +// 3. 直接控制-增强安全 (Direct Enhanced) +ControlObjectClient ctl = ControlObjectClient_create(ref, con); +ControlObjectClient_setCommandTerminationHandler(ctl, termHandler, NULL); +MmsValue* val = MmsValue_newBoolean(true); +ControlObjectClient_operate(ctl, val, 0); +// 等待 CommandTermination 回调 +Thread_sleep(1000); +MmsValue_delete(val); +ControlObjectClient_destroy(ctl); + +// 4. 选择前操作-增强安全 (SBO Enhanced) +ControlObjectClient ctl = ControlObjectClient_create(ref, con); +ControlObjectClient_setCommandTerminationHandler(ctl, termHandler, NULL); +MmsValue* val = MmsValue_newBoolean(true); +if (ControlObjectClient_selectWithValue(ctl, val)) { + ControlObjectClient_operate(ctl, val, 0); + Thread_sleep(1000); +} +MmsValue_delete(val); +ControlObjectClient_destroy(ctl); +``` + +--- + +## 7. 服务器目录浏览与发现 + +### 7.1 同步函数 + +```c +// 获取服务器目录 (逻辑设备列表) +LinkedList IedConnection_getServerDirectory(IedConnection self, + IedClientError* error, + const char* continueAfter, // 续传点, 首次调用传 NULL + LinkedList result); // 存储结果, 传 NULL 创建新列表 + +// 获取逻辑设备变量列表 +LinkedList IedConnection_getLogicalDeviceVariables(IedConnection self, + IedClientError* error, + const char* ldName, // 逻辑设备名称 + const char* continueAfter, + LinkedList result); + +// 获取逻辑设备数据集列表 +LinkedList IedConnection_getLogicalDeviceDataSets(IedConnection self, + IedClientError* error, + const char* ldName, + const char* continueAfter, + LinkedList result); + +// 获取变量类型规格 (用于非线程模式预获取 varSpec) +MmsVariableSpecification* IedConnection_getVariableSpecification( + IedConnection self, + IedClientError* error, + const char* objectReference, + FunctionalConstraint fc); +``` + +### 7.2 异步函数 + +```c +// NameList 回调类型 +typedef void (*IedConnection_GetNameListHandler)( + uint32_t invokeId, void* parameter, + IedClientError err, + LinkedList nameList, // 名称列表 (LinkedList) + bool moreFollows); // 是否有更多数据 + +void IedConnection_getServerDirectoryAsync(IedConnection self, + IedClientError* error, + const char* continueAfter, + LinkedList result, + IedConnection_GetNameListHandler handler, void* parameter); + +void IedConnection_getLogicalDeviceVariablesAsync(IedConnection self, + IedClientError* error, + const char* ldName, + const char* continueAfter, + LinkedList result, + IedConnection_GetNameListHandler handler, void* parameter); + +void IedConnection_getLogicalDeviceDataSetsAsync(IedConnection self, + IedClientError* error, + const char* ldName, + const char* continueAfter, + LinkedList result, + IedConnection_GetNameListHandler handler, void* parameter); + +// 变量规格回调 +typedef void (*IedConnection_GetVariableSpecificationHandler)( + uint32_t invokeId, void* parameter, + IedClientError err, + MmsVariableSpecification* spec); + +void IedConnection_getVariableSpecificationAsync(IedConnection self, + IedClientError* error, + const char* objectReference, + FunctionalConstraint fc, + IedConnection_GetVariableSpecificationHandler handler, void* parameter); +``` + +--- + +## 8. 文件服务 + +```c +// 获取文件目录 +bool IedConnection_getFileDirectory(IedConnection self, + IedClientError* error, + const char* fileSpecification, // 文件匹配模式 (NULL=根目录) + const char* continueAfter, // 续传点 (NULL=首次) + IedConnection_FileDirectoryHandler handler, void* parameter); + +// 打开文件 (返回 FRSM ID) +int32_t IedConnection_fileOpen(IedConnection self, + IedClientError* error, + const char* filename, + uint32_t initialPosition, // 初始位置 + uint32_t* fileSize, // 输出: 文件大小 + uint64_t* lastModified); // 输出: 最后修改时间 + +// 读取文件数据块 +bool IedConnection_fileRead(IedConnection self, + IedClientError* error, + int32_t frsmId, // FRSM ID + IedConnection_FileReadHandler handler, void* parameter); +// 返回: true=还有更多数据, false=已读完 + +// 关闭文件 +void IedConnection_fileClose(IedConnection self, + IedClientError* error, int32_t frsmId); + +// 删除文件 +void IedConnection_fileDelete(IedConnection self, + IedClientError* error, const char* filename); + +// 重命名文件 +void IedConnection_fileRename(IedConnection self, + IedClientError* error, + const char* currentFileName, const char* newFileName); + +// 下载文件 (ObtainFile) +void IedConnection_obtainFile(IedConnection self, + IedClientError* error, + const char* sourceFile, // 客户端源文件 + const char* destinationFile); // 服务器目标文件 + +// 设置文件服务本地存储路径 +void IedConnection_setFilestoreBasepath(IedConnection self, const char* basepath); +``` + +--- + +## 9. 日志服务 (Journal) + +```c +// 按时间范围读取日志 +LinkedList IedConnection_readJournalTimeRange(IedConnection self, + IedClientError* error, + const char* ldName, // 逻辑设备名 (domainId) + const char* journalRef, // 日志引用 + MmsValue* startTime, // 开始时间 (MmsValue of type BINARY_TIME) + MmsValue* endTime, // 结束时间 + bool* moreFollows); // 输出: 是否还有更多 + +// 按条目读取日志 +LinkedList IedConnection_readJournalStartAfter(IedConnection self, + IedClientError* error, + const char* ldName, + const char* journalRef, + MmsValue* timeSpecification, // 时间起点 + MmsValue* entrySpecification, // 条目起点 + bool* moreFollows); + +// 日志条目访问: +uint64_t IedConnection_JournalEntry_getEntryID(JournalEntry self); +uint64_t IedConnection_JournalEntry_getTimestamp(JournalEntry self); +LinkedList IedConnection_JournalEntry_getVariables(JournalEntry self); +``` + +--- + +## 10. GOOSE 订阅 + +### 10.1 GOOSE 订阅者 API + +```c +#include "goose_receiver.h" +#include "goose_subscriber.h" + +// 创建 GOOSE 接收器 +GooseReceiver GooseReceiver_create(void); + +// 设置网络接口 +void GooseReceiver_setInterfaceId(GooseReceiver self, const char* interfaceId); + +// 创建 GOOSE 订阅者 +GooseSubscriber GooseSubscriber_create( + char* goCbRef, // GoCB 引用, 如 "simpleIOGenericIO/LLN0$GO$gcbAnalogValues" + MmsValue* dataSetValues); // 数据集值容器或 NULL +// 若为 NULL, 收到第一个匹配消息后自动创建 + +// 设置订阅者过滤器 +void GooseSubscriber_setDstMac(GooseSubscriber self, uint8_t dstMac[6]); +// 设置后只接收目标MAC匹配的消息 + +void GooseSubscriber_setAppId(GooseSubscriber self, uint16_t appId); +// 设置后只接收 APPID 匹配的消息 + +// 设置为观察者模式 (接收所有GOOSE消息) +void GooseSubscriber_setObserver(GooseSubscriber self); + +// 设置监听回调 +typedef void (*GooseListener)(GooseSubscriber subscriber, void* parameter); + +void GooseSubscriber_setListener(GooseSubscriber self, + GooseListener listener, void* parameter); + +// 添加订阅者到接收器 +void GooseReceiver_addSubscriber(GooseReceiver self, GooseSubscriber subscriber); + +// 启动接收 (创建后台线程) +void GooseReceiver_start(GooseReceiver self); + +// 检查运行状态 +bool GooseReceiver_isRunning(GooseReceiver self); + +// 停止接收 +void GooseReceiver_stop(GooseReceiver self); + +// 销毁接收器 (同时销毁所有订阅者) +void GooseReceiver_destroy(GooseReceiver self); +``` + +### 10.2 GooseSubscriber 信息获取 + +```c +// 消息有效性 +bool GooseSubscriber_isValid(GooseSubscriber self); +GooseParseError GooseSubscriber_getParseError(GooseSubscriber self); + +// 消息元数据 +char* GooseSubscriber_getGoId(GooseSubscriber self); +char* GooseSubscriber_getGoCbRef(GooseSubscriber self); +char* GooseSubscriber_getDataSet(GooseSubscriber self); + +uint32_t GooseSubscriber_getStNum(GooseSubscriber self); +uint32_t GooseSubscriber_getSqNum(GooseSubscriber self); +uint64_t GooseSubscriber_getTimestamp(GooseSubscriber self); // ms 时间戳 +uint32_t GooseSubscriber_getTimeAllowedToLive(GooseSubscriber self); +uint32_t GooseSubscriber_getConfRev(GooseSubscriber self); + +bool GooseSubscriber_isTest(GooseSubscriber self); +bool GooseSubscriber_needsCommission(GooseSubscriber self); + +// MAC 地址 +void GooseSubscriber_getSrcMac(GooseSubscriber self, uint8_t* buffer); +void GooseSubscriber_getDstMac(GooseSubscriber self, uint8_t* buffer); + +// VLAN +bool GooseSubscriber_isVlanSet(GooseSubscriber self); +uint16_t GooseSubscriber_getVlanId(GooseSubscriber self); +uint8_t GooseSubscriber_getVlanPrio(GooseSubscriber self); + +// 数据集值 +MmsValue* GooseSubscriber_getDataSetValues(GooseSubscriber self); +// 注意: 如果接收器运行在独立线程, 此 MmsValue 仅应在回调函数内使用! + +// 获取 APPID +int32_t GooseSubscriber_getAppId(GooseSubscriber self); +``` + +### 10.3 GOOSE 回调示例 + +```c +static void +gooseListener(GooseSubscriber subscriber, void* parameter) +{ + printf("GOOSE: stNum=%u sqNum=%u\n", + GooseSubscriber_getStNum(subscriber), + GooseSubscriber_getSqNum(subscriber)); + + MmsValue* values = GooseSubscriber_getDataSetValues(subscriber); + + char buf[1024]; + MmsValue_printToBuffer(values, buf, 1024); + printf(" allData: %s\n", buf); +} +``` + +--- + +## 11. GOOSE 发布 (独立) + +(与服务端手册第11.2节相同) + +```c +#include "goose_publisher.h" + +// 通信参数 +typedef struct sCommParameters { + uint8_t vlanPriority; + uint16_t vlanId; + uint16_t appId; + uint8_t dstAddress[6]; +} CommParameters; + +// 创建 +GoosePublisher GoosePublisher_create(CommParameters* params, const char* interfaceID); +GoosePublisher GoosePublisher_createEx(CommParameters* params, const char* interfaceID, bool useVlanTag); + +// 配置 +void GoosePublisher_setGoID(GoosePublisher self, char* goID); +void GoosePublisher_setGoCbRef(GoosePublisher self, char* goCbRef); +void GoosePublisher_setDataSetRef(GoosePublisher self, char* dataSetRef); +void GoosePublisher_setConfRev(GoosePublisher self, uint32_t confRev); +void GoosePublisher_setTimeAllowedToLive(GoosePublisher self, uint32_t ttl); +void GoosePublisher_setSimulation(GoosePublisher self, bool simulation); +void GoosePublisher_setNeedsCommission(GoosePublisher self, bool ndsCom); + +// 发布 +int GoosePublisher_publish(GoosePublisher self, LinkedList dataSet); +// dataSet 是 MmsValue* 链表 + +// 状态数管理 +uint64_t GoosePublisher_increaseStNum(GoosePublisher self); // 数据变化时调用 +void GoosePublisher_reset(GoosePublisher self); // 重置 stNum/sqNum +void GoosePublisher_setStNum(GoosePublisher self, uint32_t stNum); // 测试用 +void GoosePublisher_setSqNum(GoosePublisher self, uint32_t sqNum); // 测试用 + +// 销毁 +void GoosePublisher_destroy(GoosePublisher self); +``` + +--- + +## 12. Sampled Values 客户端 + +### 12.1 SV 发布者 (独立) + +```c +#include "sv_publisher.h" + +// 创建 +SVPublisher SVPublisher_create(CommParameters* params, const char* interfaceId); +SVPublisher SVPublisher_createEx(CommParameters* params, const char* interfaceId, bool useVlanTag); + +// 添加 ASDU +SVPublisher_ASDU SVPublisher_addASDU(SVPublisher self, + const char* svId, // SV ID 字符串 + const char* datset, // 数据集引用 + uint32_t confRev); // 配置版本 + +// 向 ASDU 添加数据点 +int SVPublisher_ASDU_addFLOAT(SVPublisher_ASDU asdu); +int SVPublisher_ASDU_addINT32(SVPublisher_ASDU asdu); +int SVPublisher_ASDU_addINT64(SVPublisher_ASDU asdu); +int SVPublisher_ASDU_addTimestamp(SVPublisher_ASDU asdu); +int SVPublisher_ASDU_addQuality(SVPublisher_ASDU asdu); + +// 完成配置 (锁定, 不能再添加数据点) +void SVPublisher_setupComplete(SVPublisher self); + +// 设置数据点值 +void SVPublisher_ASDU_setFLOAT(SVPublisher_ASDU asdu, int index, float value); +void SVPublisher_ASDU_setINT32(SVPublisher_ASDU asdu, int index, int32_t value); +void SVPublisher_ASDU_setINT64(SVPublisher_ASDU asdu, int index, int64_t value); +void SVPublisher_ASDU_setTimestamp(SVPublisher_ASDU asdu, int index, Timestamp value); +void SVPublisher_ASDU_setQuality(SVPublisher_ASDU asdu, int index, Quality value); + +// 递增采样计数 +void SVPublisher_ASDU_increaseSmpCnt(SVPublisher_ASDU asdu); + +// 发送 SV 消息 +void SVPublisher_publish(SVPublisher self); + +// 销毁 +void SVPublisher_destroy(SVPublisher self); +``` + +### 12.2 SV 订阅者 + +```c +#include "sv_subscriber.h" + +// 创建接收器 +SVReceiver SVReceiver_create(void); +void SVReceiver_setInterfaceId(SVReceiver self, const char* interfaceId); + +// 创建订阅者 +SVSubscriber SVSubscriber_create(const char* svId, uint16_t appId); +// svId 为 NULL 时不按 SV ID 过滤 + +// 设置监听回调 +typedef void (*SVUpdateListener)( + SVSubscriber subscriber, void* parameter, SVSubscriber_ASDU asdu); + +void SVSubscriber_setListener(SVSubscriber self, + SVUpdateListener listener, void* parameter); + +// 添加订阅者到接收器 +void SVReceiver_addSubscriber(SVReceiver self, SVSubscriber subscriber); + +// 启动/停止 +void SVReceiver_start(SVReceiver self); +bool SVReceiver_isRunning(SVReceiver self); +void SVReceiver_stop(SVReceiver self); +void SVReceiver_destroy(SVReceiver self); + +// ASDU 信息获取 +const char* SVSubscriber_ASDU_getSvId(SVSubscriber_ASDU asdu); +uint32_t SVSubscriber_ASDU_getSmpCnt(SVSubscriber_ASDU asdu); +uint32_t SVSubscriber_ASDU_getConfRev(SVSubscriber_ASDU asdu); +uint32_t SVSubscriber_ASDU_getDataSize(SVSubscriber_ASDU asdu); + +// 获取数据 (按字节偏移量) +float SVSubscriber_ASDU_getFLOAT32(SVSubscriber_ASDU asdu, uint32_t byteOffset); +int32_t SVSubscriber_ASDU_getINT32(SVSubscriber_ASDU asdu, uint32_t byteOffset); +int64_t SVSubscriber_ASDU_getINT64(SVSubscriber_ASDU asdu, uint32_t byteOffset); +Timestamp SVSubscriber_ASDU_getTimestamp(SVSubscriber_ASDU asdu, uint32_t byteOffset); +Quality SVSubscriber_ASDU_getQuality(SVSubscriber_ASDU asdu, uint32_t byteOffset); +// 注意: 需要先验知识了解数据集结构! +``` + +--- + +## 13. SV 控制块 (SVCB) + +```c +// 创建 SVCB 客户端对象 +ClientSVControlBlock ClientSVControlBlock_create( + IedConnection connection, const char* reference); + +void ClientSVControlBlock_destroy(ClientSVControlBlock self); + +// 判断是否为组播 +bool ClientSVControlBlock_isMulticast(ClientSVControlBlock self); + +// 获取最后一次通讯错误 +IedClientError ClientSVControlBlock_getLastComError(ClientSVControlBlock self); + +// 启用/禁用 SV +bool ClientSVControlBlock_setSvEna(ClientSVControlBlock self, bool value); +bool ClientSVControlBlock_getSvEna(ClientSVControlBlock self); + +// 预留 +bool ClientSVControlBlock_setResv(ClientSVControlBlock self, bool value); +bool ClientSVControlBlock_getResv(ClientSVControlBlock self); + +// MSV/Usv ID +char* ClientSVControlBlock_getMsvID(ClientSVControlBlock self); +``` + +--- + +## 14. MmsValue 完整操作手册 + +### 14.1 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_GENERALIZED_TIME = 9, + MMS_BINARY_TIME = 10, MMS_BCD = 11, + MMS_BOOLEAN_ARRAY = 12, MMS_OBJ_ID = 13, + MMS_STRING = 14, MMS_UTC_TIME = 15, + MMS_DATA_ACCESS_ERROR = 16, + MMS_INTEGER_64 = 17 +} MmsType; +``` + +### 14.2 创建 MmsValue + +```c +// === 基本类型创建 === +MmsValue* MmsValue_newBoolean(bool value); +MmsValue* MmsValue_newFloat(float value); +MmsValue* MmsValue_newDouble(double value); +MmsValue* MmsValue_newIntegerFromInt8(int8_t value); +MmsValue* MmsValue_newIntegerFromInt16(int16_t value); +MmsValue* MmsValue_newIntegerFromInt32(int32_t value); +MmsValue* MmsValue_newIntegerFromInt64(int64_t value); +MmsValue* MmsValue_newUnsignedFromUint32(uint32_t value); + +// === 字符串 === +MmsValue* MmsValue_newVisibleString(const char* string); +MmsValue* MmsValue_newVisibleStringWithSize(int size); +MmsValue* MmsValue_newString(const char* string); +MmsValue* MmsValue_newStringWithSize(int size); + +// === 时间 === +MmsValue* MmsValue_newUtcTimeByMsTime(uint64_t msTime); +MmsValue* MmsValue_newUtcTime(uint32_t timeInSecWithQuality); +MmsValue* MmsValue_newBinaryTime(bool timeOfDay); + +// === 位串 === +MmsValue* MmsValue_newBitString(int bitSize); + +// === 字节串 === +MmsValue* MmsValue_newOctetString(int size, int maxSize); + +// === 数组/结构体 === +MmsValue* MmsValue_createArray(const MmsVariableSpecification* elementType, int size); +MmsValue* MmsValue_createEmptyArray(int size); +MmsValue* MmsValue_createEmptyStructure(int size); +``` + +### 14.3 读取 MmsValue + +```c +// === 获取类型 === +MmsType MmsValue_getType(const MmsValue* self); + +// === 类型转换读取 === +int64_t MmsValue_toInt64(const MmsValue* self); +int32_t MmsValue_toInt32(const MmsValue* self); +uint32_t MmsValue_toUint32(const MmsValue* self); +double MmsValue_toDouble(const MmsValue* self); +float MmsValue_toFloat(const MmsValue* self); +bool MmsValue_getBoolean(const MmsValue* self); + +// === 字符串 === +const char* MmsValue_toString(MmsValue* self); +int MmsValue_getStringSize(MmsValue* self); + +// === 时间 === +uint32_t MmsValue_toUnixTimestamp(const MmsValue* self); + +// === 数组/结构体 === +uint32_t MmsValue_getArraySize(const MmsValue* self); +MmsValue* MmsValue_getElement(const MmsValue* array, int index); + +// === 位串 === +bool MmsValue_getBitStringBit(const MmsValue* self, int bitPos); +int MmsValue_getBitStringSize(const MmsValue* self); +int MmsValue_getBitStringByteSize(const MmsValue* self); + +// === 数据访问错误 === +MmsDataAccessError MmsValue_getDataAccessError(const MmsValue* self); +``` + +### 14.4 修改 MmsValue + +```c +// === 设置值 === +void MmsValue_setFloat(MmsValue* self, float newValue); +void MmsValue_setDouble(MmsValue* self, double newValue); +void MmsValue_setBoolean(MmsValue* self, bool value); +void MmsValue_setInt8(MmsValue* self, int8_t value); +void MmsValue_setInt16(MmsValue* self, int16_t value); +void MmsValue_setInt32(MmsValue* self, int32_t value); +void MmsValue_setInt64(MmsValue* self, int64_t value); +void MmsValue_setUint8(MmsValue* self, uint8_t value); +void MmsValue_setUint16(MmsValue* self, uint16_t value); +void MmsValue_setUint32(MmsValue* self, uint32_t value); + +// === 字符串 === +void MmsValue_setVisibleString(MmsValue* self, const char* string); +void MmsValue_setString(MmsValue* self, const char* string); + +// === 时间 === +void MmsValue_setUtcTimeMs(MmsValue* self, uint64_t msTime); +void MmsValue_setUtcTimeByBuffer(MmsValue* self, const uint8_t* buf); +void MmsValue_setBinaryTime(MmsValue* self, bool timeOfDay); + +// === 位串 === +void MmsValue_setBitStringBit(MmsValue* self, int bitPos, bool value); +void MmsValue_deleteAllBitStringBits(MmsValue* self); +void MmsValue_setBitStringFromInteger(MmsValue* self, uint32_t intValue); + +// === 数组/结构体元素 === +void MmsValue_setElement(MmsValue* complexValue, int index, MmsValue* elementValue); +// 替换现有元素,调用者负责释放被替换的值 +``` + +### 14.5 序列化与打印 + +```c +// 打印到缓冲区 +void MmsValue_printToBuffer(const MmsValue* self, char* buffer, int bufferSize); + +// MMS 数据编解码 +int MmsValue_encodeMmsData(const MmsValue* self, + uint8_t* buffer, int bufOffset, bool berEncoding); + +MmsValue* MmsValue_decodeMmsData(const uint8_t* buffer, int bufOffset, int bufLength); +``` + +### 14.6 内存管理 + +```c +// 删除单个 MmsValue (只删除自身,不删除子元素) +void MmsValue_delete(MmsValue* value); + +// 深度删除 MmsValue (递归删除所有子元素) +void MmsValue_deleteDeep(MmsValue* value); +``` + +--- + +## 15. MmsVariableSpecification + +用于描述 MMS 变量的类型规格,在获取变量规格或创建数据集时使用。 + +```c +// 销毁 +void MmsVariableSpecification_destroy(MmsVariableSpecification* self); + +// 获取类型 +MmsType MmsVariableSpecification_getType(MmsVariableSpecification* self); + +// 获取名称 +const char* MmsVariableSpecification_getName(MmsVariableSpecification* self); + +// 获取大小 (结构体/数组的元素数, 或整数/字符串的位/字节大小) +int MmsVariableSpecification_getSize(MmsVariableSpecification* self); + +// 检查值是否匹配此类型 +bool MmsVariableSpecification_isValueOfType( + MmsVariableSpecification* self, const MmsValue* value); + +// 获取子规格 +MmsVariableSpecification* MmsVariableSpecification_getChildSpecificationByIndex( + MmsVariableSpecification* self, int index); +MmsVariableSpecification* MmsVariableSpecification_getChildSpecificationByName( + MmsVariableSpecification* self, const char* name, int* index); +MmsVariableSpecification* MmsVariableSpecification_getArrayElementSpecification( + MmsVariableSpecification* self); + +// 递归查找命名变量 +MmsVariableSpecification* MmsVariableSpecification_getNamedVariableRecursive( + MmsVariableSpecification* self, const char* nameId); + +// 获取子值 +MmsValue* MmsVariableSpecification_getChildValue( + MmsVariableSpecification* self, MmsValue* value, const char* childId); + +// 获取结构体元素名列表 +LinkedList MmsVariableSpecification_getStructureElements( + MmsVariableSpecification* self); + +// 浮点数格式信息 +int MmsVariableSpecification_getExponentWidth(MmsVariableSpecification* self); +``` + +### 15.1 MmsVariableAccessSpecification (数据集条目规格) + +```c +// 创建简单条目 +MmsVariableAccessSpecification* MmsVariableAccessSpecification_create( + char* domainId, char* itemId); + +// 创建带可选访问的条目 (数组元素或数组元素组件) +MmsVariableAccessSpecification* MmsVariableAccessSpecification_createAlternateAccess( + char* domainId, char* itemId, + int32_t index, // 数组索引 + char* componentName); // 组件名 (NULL 表示整个数组元素) + +// 销毁 +void MmsVariableAccessSpecification_destroy(MmsVariableAccessSpecification* self); +``` + +--- + +## 16. TLS 安全连接 + +```c +#include "tls_config.h" + +// 创建 TLS 配置 +TLSConfiguration tlsConfig = TLSConfiguration_create(); + +// 设置为客户端模式 +void TLSConfiguration_setClientMode(TLSConfiguration self); + +// 加载 CA 证书 +bool TLSConfiguration_addCACertificateFromFile(TLSConfiguration self, + const char* certFilename); + +// 加载客户端私钥 +bool TLSConfiguration_setOwnKeyFromFile(TLSConfiguration self, + const char* keyFilename, const char* password); + +// 加载客户端证书 +bool TLSConfiguration_setOwnCertificateFromFile(TLSConfiguration self, + const char* certFilename); + +// 证书链验证 +void TLSConfiguration_setChainValidation(TLSConfiguration self, bool enable); + +// 仅允许已知证书 +void TLSConfiguration_setAllowOnlyKnownCertificates(TLSConfiguration self, bool enable); + +// 安全事件处理器 +void TLSConfiguration_setEventHandler(TLSConfiguration self, + TLSEventHandler handler, void* parameter); + +// 创建 TLS 连接 +IedConnection con = IedConnection_createWithTlsSupport(tlsConfig); +// 或 IedConnection con = IedConnection_createEx(tlsConfig, true); + +// 连接后销毁配置 +TLSConfiguration_destroy(tlsConfig); +``` + +### TLS 事件级别 + +```c +typedef enum { + TLS_SEC_EVT_INFO = 0, // 信息 + TLS_SEC_EVT_WARNING = 1, // 警告 + TLS_SEC_EVT_INCIDENT = 2 // 事件 +} TLSEventLevel; + +// 事件代码: +// TLS_EVENT_CODE_ALM_ALGO_NOT_SUPPORTED 1 +// TLS_EVENT_CODE_ALM_UNSECURE_COMMUNICATION 2 +// TLS_EVENT_CODE_ALM_CERT_UNAVAILABLE 3 +``` + +--- + +## 17. 单线程模式 (非线程模式) + +不使用后台线程,适合嵌入式环境。 + +```c +// 1. 创建非线程连接 +IedConnection con = IedConnection_createEx(NULL, false); + +// 2. 安装状态变化处理器 (跟踪连接状态) +IedConnection_installStateChangedHandler(con, stateChangedHandler, NULL); + +// 3. 异步连接 +IedConnection_connectAsync(con, &error, hostname, port); + +// 4. 主循环: 调用 IedConnection_tick() 直到连接建立 +while (IedConnection_getState(con) != IED_STATE_CONNECTED) { + if (IedConnection_getState(con) == IED_STATE_CLOSED) { + // 连接失败 + break; + } + if (IedConnection_tick(con) == true) + Thread_sleep(10); +} + +// 5. 使用所有异步API (*Async) 变体 +IedConnection_readObjectAsync(con, &error, ref, fc, readHandler, param); + +// 6. 周期性调用 tick 处理消息 +uint64_t start = Hal_getTimeInMs(); +while (Hal_getTimeInMs() < start + 1000) { + if (IedConnection_tick(con) == true) + Thread_sleep(10); +} + +// 7. 异步释放 +IedConnection_releaseAsync(con, &error); +while (IedConnection_getState(con) != IED_STATE_CLOSED) { + if (IedConnection_tick(con) == true) + Thread_sleep(10); +} + +// 8. 销毁 +IedConnection_destroy(con); +``` + +### 异步回调通用模式 + +```c +// 读取回调 +static void readObjectHandler(uint32_t invokeId, void* parameter, + IedClientError err, MmsValue* value) +{ + if (err == IED_ERROR_OK) { + printf("Value: %f\n", MmsValue_toFloat(value)); + MmsValue_delete(value); // 用户负责释放! + } +} + +// 通用服务回调 +static void genericHandler(uint32_t invokeId, void* parameter, + IedClientError err) +{ + printf("Operation result: %i\n", err); +} +``` + +--- + +## 18. MmsConnection 底层接口 + +通过 `IedConnection_getMmsConnection()` 获取,提供 MMS 级精细控制。 + +### 18.1 连接管理 + +```c +// 创建 +MmsConnection MmsConnection_create(void); +MmsConnection MmsConnection_createSecure(TLSConfiguration tlsConfig); +MmsConnection MmsConnection_createNonThreaded(TLSConfiguration tlsConfig); + +// 连接 +bool MmsConnection_connect(MmsConnection self, MmsError* error, + const char* serverName, int serverPort); + +void MmsConnection_connectAsync(MmsConnection self, MmsError* error, + const char* serverName, int serverPort); + +// 非线程模式: 周期性调用 +bool MmsConnection_tick(MmsConnection self); + +// 关闭/Abort/Conclude +void MmsConnection_close(MmsConnection self); +void MmsConnection_abort(MmsConnection self, MmsError* error); +void MmsConnection_abortAsync(MmsConnection self, MmsError* error); +void MmsConnection_conclude(MmsConnection self, MmsError* error); +void MmsConnection_concludeAsync(MmsConnection self, MmsError* error, + MmsConnection_ConcludeAbortHandler handler, void* parameter); + +// 销毁 +void MmsConnection_destroy(MmsConnection self); +``` + +### 18.2 配置 + +```c +// 设置请求超时 +void MmsConnection_setRequestTimeout(MmsConnection self, uint32_t timeoutInMs); +uint32_t MmsConnection_getRequestTimeout(MmsConnection self); + +// 设置连接超时 +void MmsConnection_setConnectTimeout(MmsConnection self, uint32_t timeoutInMs); + +// 设置 MMS PDU 最大尺寸 (localDetail) +void MmsConnection_setLocalDetail(MmsConnection self, int32_t localDetail); +int32_t MmsConnection_getLocalDetail(MmsConnection self); + +// 设置文件存储路径 +void MmsConnection_setFilestoreBasepath(MmsConnection self, const char* basepath); + +// 获取 ISO/MMS 连接参数 +IsoConnectionParameters MmsConnection_getIsoConnectionParameters(MmsConnection self); +MmsConnectionParameters MmsConnection_getMmsConnectionParameters(MmsConnection self); +void MmsConnection_setIsoConnectionParameters(MmsConnection self, + IsoConnectionParameters* params); + +// 安装连接状态变化处理器 +void MmsConnection_setConnectionStateChangedHandler(MmsConnection self, + MmsConnectionStateChangedHandler handler, void* parameter); + +// 安装连接丢失处理器 +typedef void (*MmsConnectionLostHandler)(MmsConnection connection, void* parameter); +void MmsConnection_setConnectionLostHandler(MmsConnection self, + MmsConnectionLostHandler handler, void* parameter); + +// 安装 InformationReport 处理器 (接收服务器主动发送的未请求消息) +typedef void (*MmsInformationReportHandler)(void* parameter, + char* domainName, char* variableListName, + MmsValue* value, bool isVariableListName); +void MmsConnection_setInformationReportHandler(MmsConnection self, + MmsInformationReportHandler handler, void* parameter); + +// 安装原始消息处理器 (调试用) +typedef void (*MmsRawMessageHandler)(void* parameter, + uint8_t* message, int messageLength, bool received); +void MmsConnection_setRawMessageHandler(MmsConnection self, + MmsRawMessageHandler handler, void* parameter); +// 需要 CONFIG_MMS_RAW_MESSAGE_LOGGING +``` + +### 18.3 MMS 读写操作 + +```c +// 读取变量 +MmsValue* MmsConnection_readVariable(MmsConnection self, MmsError* error, + const char* domainId, const char* itemId); + +void MmsConnection_readVariableAsync(MmsConnection self, + uint32_t* usedInvokeId, MmsError* error, + const char* domainId, const char* itemId, + MmsConnection_ReadVariableHandler handler, void* parameter); + +// 读取变量组件 +MmsValue* MmsConnection_readVariableComponent(MmsConnection self, MmsError* error, + const char* domainId, const char* itemId, const char* componentId); + +// 读取数组元素 +MmsValue* MmsConnection_readArrayElements(MmsConnection self, MmsError* error, + const char* domainId, const char* itemId, + uint32_t startIndex, uint32_t numberOfElements); + +// 读取单个数组元素及组件 +MmsValue* MmsConnection_readSingleArrayElementWithComponent( + MmsConnection self, MmsError* error, + const char* domainId, const char* itemId, + uint32_t index, const char* componentId); + +// 读取多个变量 +MmsValue* MmsConnection_readMultipleVariables(MmsConnection self, MmsError* error, + const char* domainId, LinkedList /**/ items); + +// 写入变量 +MmsDataAccessError MmsConnection_writeVariable(MmsConnection self, MmsError* error, + const char* domainId, const char* itemId, MmsValue* value); + +// 写入变量组件 +MmsDataAccessError MmsConnection_writeVariableComponent(MmsConnection self, MmsError* error, + const char* domainId, const char* itemId, + const char* componentId, MmsValue* value); + +// 写入数组元素 +MmsDataAccessError MmsConnection_writeArrayElements(MmsConnection self, MmsError* error, + const char* domainId, const char* itemId, + int index, int numberOfElements, MmsValue* value); + +// 写入单个数组元素组件 +MmsDataAccessError MmsConnection_writeSingleArrayElementWithComponent( + MmsConnection self, MmsError* error, + const char* domainId, const char* itemId, + uint32_t arrayIndex, const char* componentId, MmsValue* value); + +// 写入多个变量 +void MmsConnection_writeMultipleVariables(MmsConnection self, MmsError* error, + const char* domainId, + LinkedList /**/ items, + LinkedList /**/ values, + LinkedList* /**/ accessResults); +``` + +### 18.4 命名变量列表 (Named Variable List) 操作 + +```c +// 读取命名变量列表 +MmsValue* MmsConnection_readNamedVariableListValues(MmsConnection self, MmsError* error, + const char* domainId, const char* listName, bool specWithResult); + +MmsValue* MmsConnection_readNamedVariableListValuesAssociationSpecific( + MmsConnection self, MmsError* error, + const char* listName, bool specWithResult); + +// 写入命名变量列表 +void MmsConnection_writeNamedVariableList(MmsConnection self, MmsError* error, + bool isAssociationSpecific, + const char* domainId, const char* itemId, + LinkedList /**/ values, + LinkedList* /**/ accessResults); + +// 定义命名变量列表 (创建数据集) +void MmsConnection_defineNamedVariableList(MmsConnection self, MmsError* error, + const char* domainId, const char* listName, + LinkedList /**/ variableSpecs); + +void MmsConnection_defineNamedVariableListAssociationSpecific( + MmsConnection self, MmsError* error, + const char* listName, LinkedList variableSpecs); + +// 读取命名变量列表目录 +LinkedList MmsConnection_readNamedVariableListDirectory(MmsConnection self, MmsError* error, + const char* domainId, const char* listName, bool* deletable); + +LinkedList MmsConnection_readNamedVariableListDirectoryAssociationSpecific( + MmsConnection self, MmsError* error, + const char* listName, bool* deletable); + +// 删除命名变量列表 +bool MmsConnection_deleteNamedVariableList(MmsConnection self, MmsError* error, + const char* domainId, const char* listName); + +bool MmsConnection_deleteAssociationSpecificNamedVariableList( + MmsConnection self, MmsError* error, const char* listName); +``` + +### 18.5 目录浏览 + +```c +// 获取 VMD 变量名列表 +LinkedList MmsConnection_getVMDVariableNames(MmsConnection self, MmsError* error); + +// 获取域列表 +LinkedList MmsConnection_getDomainNames(MmsConnection self, MmsError* error); + +// 获取域内变量名列表 +LinkedList MmsConnection_getDomainVariableNames(MmsConnection self, MmsError* error, + const char* domainId); + +// 获取域内数据集名列表 +LinkedList MmsConnection_getDomainVariableListNames(MmsConnection self, MmsError* error, + const char* domainId); + +// 获取域内日志名列表 +LinkedList MmsConnection_getDomainJournals(MmsConnection self, MmsError* error, + const char* domainId); + +// 获取关联特定数据集名列表 +LinkedList MmsConnection_getVariableListNamesAssociationSpecific( + MmsConnection self, MmsError* error); + +// 获取变量类型规格 +MmsVariableSpecification* MmsConnection_getVariableAccessAttributes( + MmsConnection self, MmsError* error, + const char* domainId, const char* itemId); +``` + +### 18.6 服务器状态与身份 + +```c +// 获取服务器身份 +MmsServerIdentity* MmsConnection_identify(MmsConnection self, MmsError* error); +// MmsServerIdentity: { char* vendorName; char* modelName; char* revision; } +void MmsServerIdentity_destroy(MmsServerIdentity* self); + +// 获取服务器状态 +void MmsConnection_getServerStatus(MmsConnection self, MmsError* error, + int* vmdLogicalStatus, int* vmdPhysicalStatus, + bool extendedDerivation); +// vmdLogicalStatus: 0-3 (STATE_CHANGES_ALLOWED/NO_CHANGES/LIMITED/SUPPORT) +// vmdPhysicalStatus: 0-3 (OPERATIONAL/PARTIALLY/INOPERATIONAL/NEEDS_COMMISSIONING) +``` + +### 18.7 MMS 文件服务 + +```c +// 获取文件目录 +bool MmsConnection_getFileDirectory(MmsConnection self, MmsError* error, + const char* fileSpecification, const char* continueAfter, + MmsFileDirectoryHandler handler, void* handlerParameter); + +// 打开文件 +int32_t MmsConnection_fileOpen(MmsConnection self, MmsError* error, + const char* filename, uint32_t initialPosition, + uint32_t* fileSize, uint64_t* lastModified); + +// 读取文件块 +bool MmsConnection_fileRead(MmsConnection self, MmsError* error, + int32_t frsmId, MmsFileReadHandler handler, void* handlerParameter); + +// 关闭文件 +void MmsConnection_fileClose(MmsConnection self, MmsError* error, int32_t frsmId); + +// 删除文件 +void MmsConnection_fileDelete(MmsConnection self, MmsError* error, + const char* fileName); + +// 重命名文件 +void MmsConnection_fileRename(MmsConnection self, MmsError* error, + const char* currentFileName, const char* newFileName); + +// 下载文件 (ObtainFile) +void MmsConnection_obtainFile(MmsConnection self, MmsError* error, + const char* sourceFile, const char* destinationFile); +``` + +### 18.8 MMS 日志服务 + +```c +// 按时间范围读取 +LinkedList MmsConnection_readJournalTimeRange(MmsConnection self, MmsError* error, + const char* domainId, const char* itemId, + MmsValue* startTime, MmsValue* endTime, bool* moreFollows); + +// 按条目读取 +LinkedList MmsConnection_readJournalStartAfter(MmsConnection self, MmsError* error, + const char* domainId, const char* itemId, + MmsValue* timeSpecification, MmsValue* entrySpecification, bool* moreFollows); + +// 日志条目结构 +struct sMmsJournalEntry { + MmsValue* entryID; // MMS_OCTET_STRING + MmsValue* occurenceTime; // MMS_BINARY_TIME + LinkedList journalVariables;// LinkedList +}; + +struct sMmsJournalVariable { + char* tag; + MmsValue* value; +}; + +// 访问日志条目 +MmsValue* MmsJournalEntry_getEntryID(MmsJournalEntry self); +MmsValue* MmsJournalEntry_getOccurenceTime(MmsJournalEntry self); +LinkedList MmsJournalEntry_getJournalVariables(MmsJournalEntry self); +const char* MmsJournalVariable_getTag(MmsJournalVariable self); +MmsValue* MmsJournalVariable_getValue(MmsJournalVariable self); +void MmsJournalEntry_destroy(MmsJournalEntry self); +``` + +--- + +## 19. 完整代码示例 + +### 19.1 基本客户端 (同步模式) + +```c +#include "iec61850_client.h" +#include "hal_thread.h" +#include +#include + +void reportCallback(void* parameter, ClientReport report) +{ + MmsValue* values = ClientReport_getDataSetValues(report); + printf("Report received for %s\n", ClientReport_getRcbReference(report)); + + for (int i = 0; i < 4; i++) { + ReasonForInclusion reason = ClientReport_getReasonForInclusion(report, i); + if (reason != IEC61850_REASON_NOT_INCLUDED) { + printf(" [%i] = %i (reason=%i)\n", i, + MmsValue_getBoolean(MmsValue_getElement(values, i)), reason); + } + } +} + +int main(int argc, char** argv) +{ + const char* host = (argc > 1) ? argv[1] : "localhost"; + int port = (argc > 2) ? atoi(argv[2]) : 102; + + IedClientError error; + IedConnection con = IedConnection_create(); + IedConnection_connect(con, &error, host, port); + + if (error != IED_ERROR_OK) { + printf("Connect failed: %i\n", error); + IedConnection_destroy(con); + return -1; + } + + // --- 读取模拟量 --- + MmsValue* val = IedConnection_readObject(con, &error, + "simpleIOGenericIO/GGIO1.AnIn1.mag.f", IEC61850_FC_MX); + + if (val) { + printf("AnIn1.mag.f = %f\n", MmsValue_toFloat(val)); + MmsValue_delete(val); + } + + // --- 写入 --- + MmsValue* strVal = MmsValue_newVisibleString("libiec61850.com"); + IedConnection_writeObject(con, &error, + "simpleIOGenericIO/GGIO1.NamPlt.vendor", IEC61850_FC_DC, strVal); + MmsValue_delete(strVal); + + // --- 读取数据集 --- + ClientDataSet ds = IedConnection_readDataSetValues(con, &error, + "simpleIOGenericIO/LLN0.Events", NULL); + if (ds) { + printf("DataSet has %d entries\n", ClientDataSet_getDataSetSize(ds)); + ClientDataSet_destroy(ds); + } + + // --- 报告 --- + ClientReportControlBlock rcb = IedConnection_getRCBValues(con, &error, + "simpleIOGenericIO/LLN0.RP.EventsRCB01", NULL); + if (rcb) { + IedConnection_installReportHandler(con, + "simpleIOGenericIO/LLN0.RP.EventsRCB01", + ClientReportControlBlock_getRptId(rcb), + reportCallback, NULL); + + ClientReportControlBlock_setTrgOps(rcb, + TRG_OPT_DATA_UPDATE | TRG_OPT_INTEGRITY | TRG_OPT_GI); + ClientReportControlBlock_setRptEna(rcb, true); + ClientReportControlBlock_setIntgPd(rcb, 5000); + IedConnection_setRCBValues(con, &error, rcb, + RCB_ELEMENT_RPT_ENA | RCB_ELEMENT_TRG_OPS | RCB_ELEMENT_INTG_PD, true); + + if (error != IED_ERROR_OK) + printf("Report activation failed: %i\n", error); + + Thread_sleep(1000); + + // 触发 GI + ClientReportControlBlock_setGI(rcb, true); + IedConnection_setRCBValues(con, &error, rcb, RCB_ELEMENT_GI, true); + + ClientReportControlBlock_destroy(rcb); + } + + // --- 控制 --- + ControlObjectClient ctl = ControlObjectClient_create( + "simpleIOGenericIO/GGIO1.SPCSO1", con); + if (ctl) { + MmsValue* ctlVal = MmsValue_newBoolean(true); + ControlObjectClient_setOrigin(ctl, NULL, CONTROL_ORCAT_REMOTE_CONTROL); + + if (ControlObjectClient_operate(ctl, ctlVal, 0)) + printf("Control OK\n"); + else + printf("Control FAILED\n"); + + // 读取状态确认 + MmsValue* stVal = IedConnection_readObject(con, &error, + "simpleIOGenericIO/GGIO1.SPCSO1.stVal", IEC61850_FC_ST); + if (stVal) { + printf("Status = %i\n", MmsValue_getBoolean(stVal)); + MmsValue_delete(stVal); + } + + MmsValue_delete(ctlVal); + ControlObjectClient_destroy(ctl); + } + + IedConnection_close(con); + IedConnection_destroy(con); + return 0; +} +``` + +### 19.2 GOOSE 订阅者完整示例 + +```c +#include "goose_receiver.h" +#include "goose_subscriber.h" +#include "hal_thread.h" +#include +#include + +static int running = 1; +void sigint_handler(int sig) { running = 0; } + +static void gooseListener(GooseSubscriber sub, void* param) +{ + printf("GOOSE: stNum=%u sqNum=%u TTL=%u %s\n", + GooseSubscriber_getStNum(sub), + GooseSubscriber_getSqNum(sub), + GooseSubscriber_getTimeAllowedToLive(sub), + GooseSubscriber_isValid(sub) ? "VALID" : "INVALID"); + + MmsValue* vals = GooseSubscriber_getDataSetValues(sub); + char buf[1024]; + MmsValue_printToBuffer(vals, buf, 1024); + printf(" Data: %s\n", buf); +} + +int main(int argc, char** argv) +{ + GooseReceiver rx = GooseReceiver_create(); + GooseReceiver_setInterfaceId(rx, (argc > 1) ? argv[1] : "eth0"); + + uint8_t dstMac[6] = {0x01, 0x0c, 0xcd, 0x01, 0x00, 0x01}; + GooseSubscriber sub = GooseSubscriber_create( + "simpleIOGenericIO/LLN0$GO$gcbAnalogValues", NULL); + GooseSubscriber_setDstMac(sub, dstMac); + GooseSubscriber_setAppId(sub, 1000); + GooseSubscriber_setListener(sub, gooseListener, NULL); + + GooseReceiver_addSubscriber(rx, sub); + GooseReceiver_start(rx); + + if (!GooseReceiver_isRunning(rx)) { + printf("Failed to start receiver\n"); + GooseReceiver_destroy(rx); + return -1; + } + + signal(SIGINT, sigint_handler); + while (running) Thread_sleep(100); + + GooseReceiver_stop(rx); + GooseReceiver_destroy(rx); + return 0; +} +``` diff --git a/libiec61850/libiec61850手册/libiec61850_服务端开发手册.md b/libiec61850/libiec61850手册/libiec61850_服务端开发手册.md new file mode 100644 index 0000000..e571677 --- /dev/null +++ b/libiec61850/libiec61850手册/libiec61850_服务端开发手册.md @@ -0,0 +1,1496 @@ +# libiec61850 服务端开发手册 + +> 基于 libiec61850 v1.5.3 完整 API 参考。本文档旨在做到脱离代码即可完成服务端开发。 + +--- + +## 目录 + +1. [架构概述](#1-架构概述) +2. [数据模型](#2-数据模型) +3. [服务器生命周期](#3-服务器生命周期) +4. [IedServerConfig 配置详解](#4-iedserverconfig-配置详解) +5. [服务器创建与销毁](#5-服务器创建与销毁) +6. [数据模型锁定与更新](#6-数据模型锁定与更新) +7. [数据读取函数](#7-数据读取函数) +8. [连接管理与认证](#8-连接管理与认证) +9. [控制模型 (Control)](#9-控制模型-control) +10. [报告控制块 (RCB)](#10-报告控制块-rcb) +11. [GOOSE 发布](#11-goose-发布) +12. [GOOSE 控制块 (GoCB)](#12-goose-控制块-gocb) +13. [Sampled Values 控制块 (SVCB)](#13-sampled-values-控制块-svcb) +14. [数据访问控制 (Access Control)](#14-数据访问控制-access-control) +15. [设置组 (Setting Groups)](#15-设置组-setting-groups) +16. [文件服务](#16-文件服务) +17. [日志服务 (Log Service)](#17-日志服务-log-service) +18. [TLS 安全通信](#18-tls-安全通信) +19. [单线程模式 (Threadless)](#19-单线程模式-threadless) +20. [MmsServer 底层接口](#20-mmsserver-底层接口) +21. [完整示例](#21-完整示例) + +--- + +## 1. 架构概述 + +服务端核心职责: + +``` +应用程序数据 --> [MmsValue] --> IedServer --> TCP/MMS --> 客户端 + | + +--> GOOSE 发布 (L2 以太网) + +--> SV 发布 (L2 以太网) +``` + +**关键类型**: +- `IedServer` — 服务器实例句柄(不透明指针) +- `IedServerConfig` — 服务器配置对象 +- `IedModel` — 数据模型根节点 +- `LogicalDevice` → `LogicalNode` → `DataObject` → `DataAttribute` — 数据模型层级 + +--- + +## 2. 数据模型 + +### 2.1 模型节点类型枚举 + +```c +typedef enum { + LogicalDeviceModelType, // 逻辑设备 + LogicalNodeModelType, // 逻辑节点 + DataObjectModelType, // 数据对象 + DataAttributeModelType // 数据属性 +} ModelNodeType; +``` + +### 2.2 创建数据模型的三种方式 + +#### 方式一: 静态模型生成器(推荐) + +使用 Java 工具 `genmodel.jar` 从 SCL/ICD 文件自动生成: +- 生成 `static_model.c` 和 `static_model.h` +- 所有节点作为全局变量,编译时确定,效率最高 +- 生成宏定义方便访问,如 `IEDMODEL_Device1_LLN0_Mod_stVal` + +```c +#include "static_model.h" + +// 直接使用生成的全局模型 +IedServer iedServer = IedServer_create(&iedModel); +``` + +#### 方式二: API 动态创建 + +通过 `iec61850_dynamic_model.h` API 在运行时构建模型: + +```c +#include "iec61850_dynamic_model.h" + +// 1. 创建根模型 +IedModel* model = IedModel_create("myIED"); + +// 2. 创建逻辑设备 +LogicalDevice* ld = LogicalDevice_create("SENSORS", model); + +// 3. 创建逻辑节点 +LogicalNode* lln0 = LogicalNode_create("LLN0", ld); +LogicalNode* ttmp1 = LogicalNode_create("TTMP1", ld); + +// 4. 使用 CDC 快捷函数创建数据对象 +DataObject* mod = CDC_ENS_create("Mod", (ModelNode*)lln0, 0); +DataObject* health = CDC_ENS_create("Health", (ModelNode*)lln0, 0); +DataObject* tmpSv = CDC_SAV_create("TmpSv", (ModelNode*)ttmp1, 0, false); + +// 5. 手动创建数据属性 +DataAttribute* da = DataAttribute_create("stVal", parent, + IEC61850_INT32, IEC61850_FC_ST, + TRG_OPT_DATA_CHANGED, 0, 0); + +// 6. 创建数据集 +DataSet* ds = DataSet_create("events", lln0); +DataSetEntry_create(ds, "TTMP1$MX$TmpSv$instMag$f", -1, NULL); + +// 7. 创建报告控制块 +ReportControlBlock_create("events01", lln0, "events01", false, NULL, 1, + TRG_OPT_DATA_CHANGED, RPT_OPT_SEQ_NUM | RPT_OPT_TIME_STAMP, 50, 0); + +// 8. 创建 GOOSE 控制块 +GSEControlBlock_create("gse01", lln0, "events01", "events", 1, false, 200, 3000); + +// 9. 创建设置组控制块 +SettingGroupControlBlock_create(lln0, 1, 1); + +// --- 使用完毕后 --- +IedServer_destroy(iedServer); +IedModel_destroy(model); // 仅动态模型需要 +``` + +#### 方式三: 配置文件解析 + +使用 `genconfig.jar` 工具从 SCL 生成 `model.cfg` 配置文件,然后运行时加载: + +```c +#include "iec61850_config_file_parser.h" + +IedModel* model = ConfigFileParser_createModelFromConfigFileEx("model.cfg"); + +// 通过对象引用路径查找 +DataAttribute* attr = (DataAttribute*) + IedModel_getModelNodeByObjectReference(model, "simpleIOGenericIO/GGIO1.AnIn1.mag.f"); + +// 通过短地址查找 +DataAttribute* attr2 = (DataAttribute*) + IedModel_getModelNodeByShortAddress(model, 101); +``` + +### 2.3 数据属性类型枚举 (DataAttributeType) + +| 枚举值 | 含义 | 枚举值 | 含义 | +|--------|------|--------|------| +| `IEC61850_BOOLEAN` | 布尔 | `IEC61850_INT8` | 8位有符号整数 | +| `IEC61850_INT16` | 16位有符号 | `IEC61850_INT32` | 32位有符号 | +| `IEC61850_INT64` | 64位有符号 | `IEC61850_INT8U` | 8位无符号 | +| `IEC61850_INT16U` | 16位无符号 | `IEC61850_INT32U` | 32位无符号 | +| `IEC61850_FLOAT32` | 32位浮点 | `IEC61850_FLOAT64` | 64位浮点 | +| `IEC61850_ENUMERATED` | 枚举 | `IEC61850_TIMESTAMP` | 时间戳 | +| `IEC61850_QUALITY` | 品质 | `IEC61850_CONSTRUCTED` | 构造类型(子属性) | +| `IEC61850_VISIBLE_STRING_32` | 可见字符串(32) | `IEC61850_VISIBLE_STRING_64` | 可见字符串(64) | +| `IEC61850_VISIBLE_STRING_255` | 可见字符串(255) | `IEC61850_UNICODE_STRING_255` | Unicode字符串 | +| `IEC61850_CHECK` | 检查位 | `IEC61850_CODEDENUM` | 编码枚举 | +| `IEC61850_OCTET_STRING_64` | 字节串(64) | `IEC61850_GENERIC_BITSTRING` | 位串 | + +### 2.4 功能约束枚举 (FunctionalConstraint) + +```c +typedef enum eFunctionalConstraint { + IEC61850_FC_ST = 0, // 状态信息(Status) + IEC61850_FC_MX = 1, // 测量值(Measurands) + IEC61850_FC_SP = 2, // 设定值(Setpoint) + IEC61850_FC_SV = 3, // 替代(Substitution) + IEC61850_FC_CF = 4, // 配置(Configuration) + IEC61850_FC_DC = 5, // 描述(Description) + IEC61850_FC_SG = 6, // 设置组(Setting Group) - 活动值 + IEC61850_FC_SE = 7, // 设置组可编辑(Setting Group Editable) - 编辑值 + IEC61850_FC_SR = 8, // 服务追踪(Service Response) + IEC61850_FC_OR = 9, // 操作接收(Operate Received) + IEC61850_FC_BL = 10, // 闭锁(Blocking) + IEC61850_FC_EX = 11, // 扩展定义(Extended) + IEC61850_FC_CO = 12, // 控制(Control) + IEC61850_FC_US = 13, // 单播SV + IEC61850_FC_MS = 14, // 组播SV + IEC61850_FC_RP = 15, // 非缓冲报告 + IEC61850_FC_BR = 16, // 缓冲报告 + IEC61850_FC_LG = 17, // 日志控制块 + IEC61850_FC_GO = 18, // GOOSE控制块 + IEC61850_FC_ALL = 99, // 所有FC(通配符) + IEC61850_FC_NONE = -1 // 无FC +} FunctionalConstraint; +``` + +### 2.5 品质位定义 (Quality) + +```c +typedef uint16_t Quality; + +// 有效性 +#define QUALITY_VALIDITY_GOOD 0 // 好 +#define QUALITY_VALIDITY_INVALID 2 // 无效 +#define QUALITY_VALIDITY_RESERVED 1 // 保留 +#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 +``` + +### 2.6 触发器选项 (Trigger Options) + +```c +#define TRG_OPT_DATA_CHANGED 1 // 数据变化(dchg) +#define TRG_OPT_QUALITY_CHANGED 2 // 品质变化(qchg) +#define TRG_OPT_DATA_UPDATE 4 // 数据更新(dupd) +#define TRG_OPT_INTEGRITY 8 // 完整性周期 +#define TRG_OPT_GI 16 // 总召唤(General Interrogation) +#define TRG_OPT_TRANSIENT 128 // 瞬变 +``` + +### 2.7 报告选项 (Report Options) + +```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 // 配置版本 +``` + +--- + +## 3. 服务器生命周期 + +```c +// 1. 创建配置 +IedServerConfig config = IedServerConfig_create(); +// ... 配置 config ... + +// 2. 创建服务器 +IedServer iedServer = IedServer_createWithConfig(&iedModel, tlsConfig, config); +IedServerConfig_destroy(config); // 配置对象不再需要 + +// 3. 设置各种处理器 +IedServer_setControlHandler(...); +IedServer_setConnectionIndicationHandler(...); +// ... + +// 4. 启动服务器 +IedServer_start(iedServer, 102); // TCP端口102 + +// 5. 主循环 - 更新数据 +while (running) { + IedServer_lockDataModel(iedServer); + IedServer_updateFloatAttributeValue(iedServer, attr, value); + IedServer_unlockDataModel(iedServer); + Thread_sleep(100); +} + +// 6. 停止 +IedServer_stop(iedServer); + +// 7. 销毁 +IedServer_destroy(iedServer); +// IedModel_destroy(model); // 仅动态模型 +``` + +--- + +## 4. IedServerConfig 配置详解 + +```c +typedef struct sIedServerConfig { + int reportBufferSize; // 缓冲报告缓冲区大小(字节) + int reportBufferSizeURCBs; // 非缓冲报告缓冲区大小 + char* fileServiceBasepath; // 文件服务根路径 + bool enableFileService; // 启用文件服务(默认true) + bool enableDynamicDataSetService; // 启 用动态数据集服务(默认true) + int maxAssociationSpecificDataSets; // 每连接最多关联数据集 + int maxDomainSpecificDataSets; // 最多域数据集 + int maxDataSetEntries; // 动态数据集最大条目数 + bool enableLogService; // 启用日志服务(默认true) + bool useIntegratedGoosePublisher; // 使用集成GOOSE发布者(默认true) + uint8_t edition; // IEC 61850版本(0=Ed1,1=Ed2,2=Ed2.1) + int maxMmsConnections; // 最大MMS连接数 + bool enableEditSG; // 启用EditSG服务(默认true) + bool enableResvTmsForSGCB; // SGCB.ResvTms可见 + bool enableResvTmsForBRCB; // BRCB.ResvTms可见 + bool enableOwnerForRCB; // RCB.Owner可见 + bool syncIntegrityReportTimes; // 同步完整性报告时间 + uint8_t reportSettingsWritable; // 报告设置可写标志 +} IedServerConfig; +``` + +### 4.1 配置函数清单 + +```c +// 创建与销毁 +IedServerConfig IedServerConfig_create(void); +void IedServerConfig_destroy(IedServerConfig self); + +// 版本设置 +void IedServerConfig_setEdition(IedServerConfig self, uint8_t edition); +uint8_t IedServerConfig_getEdition(IedServerConfig self); +// edition 可选值: IEC_61850_EDITION_1 (0), IEC_61850_EDITION_2 (1), IEC_61850_EDITION_2_1 (2) + +// 报告缓冲区 +void IedServerConfig_setReportBufferSize(IedServerConfig self, int reportBufferSize); +int IedServerConfig_getReportBufferSize(IedServerConfig self); +void IedServerConfig_setReportBufferSizeForURCBs(IedServerConfig self, int reportBufferSize); +int IedServerConfig_getReportBufferSizeForURCBs(IedServerConfig self); +// 默认值: 65536 字节 (由 CONFIG_REPORTING_DEFAULT_REPORT_BUFFER_SIZE 控制) + +// MMS连接数 +void IedServerConfig_setMaxMmsConnections(IedServerConfig self, int maxConnections); +int IedServerConfig_getMaxMmsConnections(IedServerConfig self); +// 此值必须小于 CONFIG_MAXIMUM_TCP_CLIENT_CONNECTIONS (若 != -1) + +// 同步完整性报告时间 +void IedServerConfig_setSyncIntegrityReportTimes(IedServerConfig self, bool enable); +bool IedServerConfig_getSyncIntegrityReportTimes(IedServerConfig self); +// 启用后完整性报告时间对齐UTC纪元 + +// 文件服务 +void IedServerConfig_setFileServiceBasePath(IedServerConfig self, const char* basepath); +const char* IedServerConfig_getFileServiceBasePath(IedServerConfig self); +void IedServerConfig_enableFileService(IedServerConfig self, bool enable); +bool IedServerConfig_isFileServiceEnabled(IedServerConfig self); + +// 动态数据集服务 +void IedServerConfig_enableDynamicDataSetService(IedServerConfig self, bool enable); +bool IedServerConfig_isDynamicDataSetServiceEnabled(IedServerConfig self); + +// 动态数据集限制 +void IedServerConfig_setMaxAssociationSpecificDataSets(IedServerConfig self, int maxDataSets); +int IedServerConfig_getMaxAssociationSpecificDataSets(IedServerConfig self); +void IedServerConfig_setMaxDomainSpecificDataSets(IedServerConfig self, int maxDataSets); +int IedServerConfig_getMaxDomainSpecificDataSets(IedServerConfig self); +void IedServerConfig_setMaxDataSetEntries(IedServerConfig self, int maxDataSetEntries); +int IedServerConfig_getMaxDatasSetEntries(IedServerConfig self); + +// 日志服务 +void IedServerConfig_enableLogService(IedServerConfig self, bool enable); +bool IedServerConfig_isLogServiceEnabled(IedServerConfig self); + +// EditSG 设置组编辑 +void IedServerConfig_enableEditSG(IedServerConfig self, bool enable); +// 注意: 禁用后 SGCB.ResvTms 不可见 + +// ResvTms 可见性 +void IedServerConfig_enableResvTmsForSGCB(IedServerConfig self, bool enable); +void IedServerConfig_enableResvTmsForBRCB(IedServerConfig self, bool enable); +bool IedServerConfig_isResvTmsForBRCBEnabled(IedServerConfig self); + +// RCB Owner 属性 +void IedServerConfig_enableOwnerForRCB(IedServerConfig self, bool enable); +bool IedServerConfig_isOwnerForRCBEnabled(IedServerConfig self); + +// GOOSE 发布控制 +void IedServerConfig_useIntegratedGoosePublisher(IedServerConfig self, bool enable); +// 默认启用; 如需使用独立的 GoosePublisher 可禁用 + +// 报告设置可写性 +void IedServerConfig_setReportSetting(IedServerConfig self, uint8_t setting, bool isDyn); +bool IedServerConfig_getReportSetting(IedServerConfig self, uint8_t setting); +// setting 取值: IEC61850_REPORTSETTINGS_RPT_ID, _BUF_TIME, _DATSET, _TRG_OPS, _OPT_FIELDS, _INTG_PD +``` + +--- + +## 5. 服务器创建与销毁 + +```c +// 创建服务器实例 +IedServer IedServer_create(IedModel* dataModel); +// 参数: dataModel - 指向 IedModel 数据结构的指针 +// 返回: 新的 IedServer 实例 + +// 创建带 TLS 支持的服务器 +IedServer IedServer_createWithTlsSupport(IedModel* dataModel, TLSConfiguration tlsConfiguration); +// 参数: tlsConfiguration - TLS 配置对象,或 NULL 表示不使用 TLS + +// 创建带完整配置的服务器 +IedServer IedServer_createWithConfig(IedModel* dataModel, + TLSConfiguration tlsConfiguration, IedServerConfig serverConfiguration); +// 参数: serverConfiguration - 高级配置对象 + +// 销毁服务器实例 (释放所有资源: 内存、TCP socket) +void IedServer_destroy(IedServer self); + +// 添加额外的本地接入点 (多网卡监听) +bool IedServer_addAccessPoint(IedServer self, + const char* ipAddr, int tcpPort, TLSConfiguration tlsConfiguration); +// 返回: 成功 true,失败 false + +// 设置本地监听 IP 地址 +void IedServer_setLocalIpAddress(IedServer self, const char* localIpAddress); +// 内部会创建 IP 字符串的副本 + +// 设置服务器身份 (MMS identify 服务) +void IedServer_setServerIdentity(IedServer self, + const char* vendor, const char* model, const char* revision); +// 条件编译: CONFIG_IEC61850_SUPPORT_SERVER_IDENTITY + +// 获取底层 MmsServer 实例 (用于高级操作) +MmsServer IedServer_getMmsServer(IedServer self); + +// 获取数据模型指针 +IedModel* IedServer_getDataModel(IedServer self); +``` + +### 启动与停止 + +```c +// === 多线程模式 === + +// 启动服务器 (创建后台线程) +void IedServer_start(IedServer self, int tcpPort); +// tcpPort: TCP 端口号,-1 使用默认 (102 for MMS, 3872 for TLS) + +// 检查服务器是否在运行 +bool IedServer_isRunning(IedServer self); + +// 获取当前连接数 +int IedServer_getNumberOfOpenConnections(IedServer self); + +// 停止服务器 +void IedServer_stop(IedServer self); + +// === 单线程模式 === +// (详见第19节) +void IedServer_startThreadless(IedServer self, int tcpPort); +int IedServer_waitReady(IedServer self, unsigned int timeoutMs); +void IedServer_processIncomingData(IedServer self); +void IedServer_performPeriodicTasks(IedServer self); +void IedServer_stopThreadless(IedServer self); +``` + +### 时间品质设置 + +```c +void IedServer_setTimeQuality(IedServer self, + bool leapSecondKnown, // 闰秒已知 + bool clockFailure, // 时钟故障 + bool clockNotSynchronized, // 时钟未同步 + int subsecondPrecision); // 亚秒精度 (fractionOfSecond 的有效位数) +``` + +--- + +## 6. 数据模型锁定与更新 + +### 6.1 线程安全 + +**重要规则**: 在回调函数内部(ControlHandler、WriteAccessHandler 等),数据模型已经被锁住!不要在回调函数内部再调用 `lockDataModel`,否则会导致死锁。 + +```c +// 锁定数据模型 (暂停处理客户端请求) +void IedServer_lockDataModel(IedServer self); + +// 解锁数据模型 (处理暂停的客户端请求) +void IedServer_unlockDataModel(IedServer self); +``` + +### 6.2 更新数据属性值 + +所有 `IedServer_update*` 函数都会自动: +- 检查触发条件是否满足 (dchg/qchg/dupd) +- 如果满足触发条件,通知相应的 RCB/GCB +- 自动更新内部缓存 + +```c +// === 通用更新函数 === +void IedServer_updateAttributeValue(IedServer self, + DataAttribute* dataAttribute, MmsValue* value); +// 最通用的更新函数,接受 MmsValue 对象 + +// === 类型特定的便捷更新函数 === +void IedServer_updateFloatAttributeValue(IedServer self, + DataAttribute* dataAttribute, float value); + +void IedServer_updateInt32AttributeValue(IedServer self, + DataAttribute* dataAttribute, int32_t value); + +void IedServer_updateInt64AttributeValue(IedServer self, + DataAttribute* dataAttribute, int64_t value); + +void IedServer_updateUnsignedAttributeValue(IedServer self, + DataAttribute* dataAttribute, uint32_t value); + +void IedServer_updateBooleanAttributeValue(IedServer self, + DataAttribute* dataAttribute, bool value); + +void IedServer_updateBitStringAttributeValue(IedServer self, + DataAttribute* dataAttribute, uint32_t value); + +void IedServer_updateVisibleStringAttributeValue(IedServer self, + DataAttribute* dataAttribute, char* value); + +void IedServer_updateDbposValue(IedServer self, + DataAttribute* dataAttribute, Dbpos value); +// Dbpos 是双点位置 (Double Point Position) + +// === 时间戳更新 === +void IedServer_updateUTCTimeAttributeValue(IedServer self, + DataAttribute* dataAttribute, uint64_t value); +// value: 毫秒时间戳 (自Epoch以来的毫秒数, 如 Hal_getTimeInMs()) + +void IedServer_updateTimestampAttributeValue(IedServer self, + DataAttribute* dataAttribute, Timestamp* timestamp); +// timestamp: 完整 Timestamp 结构体 (含品质标志) + +// === 品质更新 === +void IedServer_updateQuality(IedServer self, + DataAttribute* dataAttribute, Quality quality); +``` + +--- + +## 7. 数据读取函数 + +```c +// 获取数据属性底层 MmsValue 对象 (谨慎使用, 直接修改不会触发报告) +MmsValue* IedServer_getAttributeValue(IedServer self, + DataAttribute* dataAttribute); + +// 获取功能约束数据 (FCD) 的 MmsValue +MmsValue* IedServer_getFunctionalConstrainedData(IedServer self, + DataObject* dataObject, FunctionalConstraint fc); +// 直接获取 DataObject 指定 FC 下的全部 MmsValue +// 警告: 直接修改此值不会触发报告机制! + +// === 类型特定读取函数 === +bool IedServer_getBooleanAttributeValue(IedServer self, + const DataAttribute* dataAttribute); + +int32_t IedServer_getInt32AttributeValue(IedServer self, + const DataAttribute* dataAttribute); + +int64_t IedServer_getInt64AttributeValue(IedServer self, + const DataAttribute* dataAttribute); + +uint32_t IedServer_getUInt32AttributeValue(IedServer self, + const DataAttribute* dataAttribute); + +float IedServer_getFloatAttributeValue(IedServer self, + const DataAttribute* dataAttribute); + +uint64_t IedServer_getUTCTimeAttributeValue(IedServer self, + const DataAttribute* dataAttribute); + +uint32_t IedServer_getBitStringAttributeValue(IedServer self, + const DataAttribute* dataAttribute); + +const char* IedServer_getStringAttributeValue(IedServer self, + const DataAttribute* dataAttribute); +``` + +### 7.1 数据更新典型模式 + +```c +while (running) { + uint64_t timestamp = Hal_getTimeInMs(); + + // 构建 IEC 61850 时间戳 + Timestamp iecTimestamp; + Timestamp_clearFlags(&iecTimestamp); + Timestamp_setTimeInMilliseconds(&iecTimestamp, timestamp); + Timestamp_setLeapSecondKnown(&iecTimestamp, true); + + IedServer_lockDataModel(iedServer); + + // 更新测量值 + IedServer_updateFloatAttributeValue(iedServer, + IEDMODEL_GenericIO_GGIO1_AnIn1_mag_f, sinf(t)); + + // 更新品质 + IedServer_updateQuality(iedServer, + IEDMODEL_GenericIO_GGIO1_AnIn1_q, QUALITY_VALIDITY_GOOD); + + // 更新时间戳 + IedServer_updateTimestampAttributeValue(iedServer, + IEDMODEL_GenericIO_GGIO1_AnIn1_t, &iecTimestamp); + + IedServer_unlockDataModel(iedServer); + + Thread_sleep(100); +} +``` + +--- + +## 8. 连接管理与认证 + +### 8.1 连接指示处理器 + +```c +// 回调函数类型 +typedef void (*IedConnectionIndicationHandler)( + IedServer self, // 服务器实例 + ClientConnection connection, // 连接对象 + bool connected, // true=新连接, false=连接关闭 + void* parameter); // 用户参数 + +// 设置连接事件回调 +void IedServer_setConnectionIndicationHandler(IedServer self, + IedConnectionIndicationHandler handler, void* parameter); +``` + +### 8.2 ClientConnection 操作 + +```c +// 获取客户端对端地址 (IP:Port) +const char* ClientConnection_getPeerAddress(ClientConnection self); +// 注意: 返回的字符串仅在连接存在期间有效 + +// 获取本地地址 (IP:Port) +const char* ClientConnection_getLocalAddress(ClientConnection self); + +// 获取安全令牌 (认证阶段关联的) +void* ClientConnection_getSecurityToken(ClientConnection self); +// 如果没有认证器则返回 NULL +``` + +### 8.3 认证器 (Authenticator) + +```c +// 设置认证回调 +void IedServer_setAuthenticator(IedServer self, + AcseAuthenticator authenticator, void* authenticatorParameter); +// 每次客户端连接尝试时调用, 根据返回值决定接受或拒绝连接 +// 如果不设置, 默认接受所有连接 + +// 认证机制枚举 +typedef enum { + ACSE_AUTH_NONE = 0, // 无认证 + ACSE_AUTH_PASSWORD = 1, // ACSE 密码 + ACSE_AUTH_CERTIFICATE = 2, // ACSE 证书 + ACSE_AUTH_TLS = 3 // TLS 证书 +} AcseAuthenticationMechanism; +``` + +--- + +## 9. 控制模型 (Control) + +### 9.1 控制模型枚举 (ControlModel) + +```c +typedef enum { + CONTROL_MODEL_STATUS_ONLY = 0, // 仅状态 (不可控) + CONTROL_MODEL_DIRECT_NORMAL = 1, // 直接控制-普通安全 + CONTROL_MODEL_SBO_NORMAL = 2, // 选择前操作-普通安全 + CONTROL_MODEL_DIRECT_ENHANCED = 3, // 直接控制-增强安全 (含CommandTermination) + CONTROL_MODEL_SBO_ENHANCED = 4 // 选择前操作-增强安全 +} ControlModel; +``` + +### 9.2 控制处理器回调类型 + +```c +// 1. 操作检查处理器 (PerformCheckHandler) - 静态测试 (可选) +typedef CheckHandlerResult (*ControlPerformCheckHandler)( + ControlAction action, // 控制动作上下文 + void* parameter, // 用户参数 + MmsValue* ctlVal, // 控制值 + bool test, // 是否为测试命令 + bool interlockCheck); // 是否请求联锁检查 + +// 返回值: +// CONTROL_ACCEPTED = -1 检查通过 +// CONTROL_WAITING_FOR_SELECT = 0 等待选择 +// CONTROL_HARDWARE_FAULT = 1 硬件故障 +// CONTROL_TEMPORARILY_UNAVAILABLE = 2 暂时不可用(已被其他客户端控制) +// CONTROL_OBJECT_ACCESS_DENIED = 3 访问拒绝 +// CONTROL_OBJECT_UNDEFINED = 4 对象未定义 +// CONTROL_VALUE_INVALID = 11 值超出范围 + + +// 2. 等待执行处理器 (WaitForExecutionHandler) - 动态测试 (可选) +typedef ControlHandlerResult (*ControlWaitForExecutionHandler)( + ControlAction action, + void* parameter, + MmsValue* ctlVal, + bool test, + bool synchroCheck); // 是否请求同步检查 + +// 返回值: +// CONTROL_RESULT_FAILED = 0 失败 +// CONTROL_RESULT_OK = 1 成功 +// CONTROL_RESULT_WAITING = 2 等待中 (稍后再次调用) + + +// 3. 控制执行处理器 (ControlHandler) - 实际执行 (必需) +typedef ControlHandlerResult (*ControlHandler)( + ControlAction action, // 控制动作上下文 + void* parameter, // 用户参数 (通常是控制对象的 DataObject* 指针) + MmsValue* ctlVal, // 控制值 + bool test); // 是否为测试命令 + +// 返回值: 同 WaitForExecutionHandler + + +// 4. 选择状态变化处理器 (v1.5 新增) +typedef void (*ControlSelectStateChangedHandler)( + ControlAction action, + void* parameter, + bool isSelected, // true=被选择, false=取消选择 + SelectStateChangedReason reason); // 原因 + +typedef enum { + SELECT_STATE_REASON_SELECTED, + SELECT_STATE_REASON_CANCELED, + SELECT_STATE_REASON_TIMEOUT, + SELECT_STATE_REASON_OPERATED, + SELECT_STATE_REASON_OPERATE_FAILED, + SELECT_STATE_REASON_DISCONNECTED +} SelectStateChangedReason; +``` + +### 9.3 设置控制处理器 + +```c +// 设置控制处理器 (必需 - 在有控制模型中) +void IedServer_setControlHandler(IedServer self, + DataObject* node, // 可控数据对象 + ControlHandler handler, // 控制回调函数 + void* parameter); // 用户参数 + +// 设置操作检查处理器 (可选) +void IedServer_setPerformCheckHandler(IedServer self, + DataObject* node, + ControlPerformCheckHandler handler, + void* parameter); + +// 设置等待执行处理器 (可选) +void IedServer_setWaitForExecutionHandler(IedServer self, + DataObject* node, + ControlWaitForExecutionHandler handler, + void* parameter); + +// 设置选择状态变化处理器 (可选) +void IedServer_setSelectStateChangedHandler(IedServer self, + DataObject* node, + ControlSelectStateChangedHandler handler, + void* parameter); +``` + +### 9.4 ControlAction 上下文查询函数 + +在控制处理器内部,可以通过 ControlAction 获取更多上下文信息: + +```c +// 获取客户端连接对象 +ClientConnection ControlAction_getClientConnection(ControlAction self); + +// 获取控制对象 (DataObject*) +DataObject* ControlAction_getControlObject(ControlAction self); + +// 判断是否为 Select 命令 (vs Operate) +bool ControlAction_isSelect(ControlAction self); + +// 获取控制序号 +int ControlAction_getCtlNum(ControlAction self); + +// 获取来源类别 (orCat) +int ControlAction_getOrCat(ControlAction self); + +// 获取来源标识 +uint8_t* ControlAction_getOrIdent(ControlAction self, int* orIdentSize); + +// 获取同步检查位 +bool ControlAction_getSynchroCheck(ControlAction self); + +// 获取联锁检查位 +bool ControlAction_getInterlockCheck(ControlAction self); + +// 获取控制时间 (TimeActivatedControl), 非定时控制返回 0 +uint64_t ControlAction_getControlTime(ControlAction self); + +// 设置下一次 CommandTermination 的错误码 +void ControlAction_setError(ControlAction self, ControlLastApplError error); + +// 设置下一次 CommandTermination 的附加原因 +void ControlAction_setAddCause(ControlAction self, ControlAddCause addCause); +``` + +### 9.5 更新控制模型 + +```c +void IedServer_updateCtlModel(IedServer self, + DataObject* ctlObject, ControlModel value); +// 同时更新 "ctlModel" 属性值和内部控制结构 +// 注意: 对应的控制模型数据结构必须在数据模型中存在! +``` + +### 9.6 控制处理器典型实现 + +```c +static ControlHandlerResult +controlHandlerForBinaryOutput(ControlAction action, void* parameter, + MmsValue* value, bool test) +{ + if (test) + return CONTROL_RESULT_FAILED; // 拒绝测试命令 + + if (MmsValue_getType(value) != MMS_BOOLEAN) + return CONTROL_RESULT_FAILED; + + uint64_t timeStamp = Hal_getTimeInMs(); + bool newState = MmsValue_getBoolean(value); + + if (parameter == IEDMODEL_GenericIO_GGIO1_SPCSO1) { + // 执行实际 I/O 操作 + setHardwareOutput(1, newState); + + // 更新服务器状态 + IedServer_updateUTCTimeAttributeValue(iedServer, + IEDMODEL_GenericIO_GGIO1_SPCSO1_t, timeStamp); + IedServer_updateAttributeValue(iedServer, + IEDMODEL_GenericIO_GGIO1_SPCSO1_stVal, value); + } + + return CONTROL_RESULT_OK; +} + +// 安装 +IedServer_setControlHandler(iedServer, + IEDMODEL_GenericIO_GGIO1_SPCSO1, + (ControlHandler) controlHandlerForBinaryOutput, + IEDMODEL_GenericIO_GGIO1_SPCSO1); +``` + +--- + +## 10. 报告控制块 (RCB) + +### 10.1 RCB 事件类型 + +```c +typedef enum { + RCB_EVENT_GET_PARAMETER, // 客户端读取参数 + RCB_EVENT_SET_PARAMETER, // 客户端设置参数 + RCB_EVENT_UNRESERVED, // RCB 取消预留 + RCB_EVENT_RESERVED, // RCB 被预留 + RCB_EVENT_ENABLE, // RCB 启用 + RCB_EVENT_DISABLE, // RCB 禁用 + RCB_EVENT_GI, // 总召唤触发 + RCB_EVENT_PURGEBUF, // 清除缓冲区 + RCB_EVENT_OVERFLOW, // 报告缓冲区溢出 + RCB_EVENT_REPORT_CREATED // 新报告创建并写入缓冲区 +} IedServer_RCBEventType; +``` + +### 10.2 RCB 事件处理器 + +```c +// 回调类型 +typedef void (*IedServer_RCBEventHandler)( + void* parameter, // 用户参数 + ReportControlBlock* rcb, // 受影响的 RCB + ClientConnection connection, // 相关客户端连接 + IedServer_RCBEventType event, // 事件类型 + const char* parameterName, // 参数名 (仅用于 SET_PARAMETER 事件) + MmsDataAccessError serviceError); // 服务错误 (仅用于 SET_PARAMETER 事件) + +// 安装处理器 +void IedServer_setRCBEventHandler(IedServer self, + IedServer_RCBEventHandler handler, void* parameter); +``` + +--- + +## 11. GOOSE 发布 + +### 11.1 GOOSE 发布控制 + +```c +// 启用所有 GOOSE 控制块 (设置 GoEna=true) +void IedServer_enableGoosePublishing(IedServer self); +// 在启动或重置时调用,否则 GCB 默认为非活跃 + +// 禁用所有 GOOSE 控制块 (设置 GoEna=false) +void IedServer_disableGoosePublishing(IedServer self); + +// 设置 GOOSE 以太网接口 +void IedServer_setGooseInterfaceId(IedServer self, const char* interfaceId); +// interfaceId: OS 相关, Linux 下如 "eth0" +// 如果未调用或 interfaceId=NULL,使用 stack_config.h 中的默认值 + +// 为特定 GCB 设置接口 +void IedServer_setGooseInterfaceIdEx(IedServer self, + LogicalNode* ln, // 包含 GCB 的逻辑节点, NULL 表示为所有 GCB + const char* gcbName, // GCB 名称 (非对象引用!) + const char* interfaceId); + +// 控制 GOOSE VLAN 标签 +void IedServer_useGooseVlanTag(IedServer self, + LogicalNode* ln, // NULL 表示所有 GCB + const char* gcbName, + bool useVlanTag); +``` + +### 11.2 独立 GOOSE 发布者 (不依赖服务器模型) + +当 `useIntegratedGoosePublisher` 配置为 false,或者需要独立控制时: + +```c +#include "goose_publisher.h" + +// 通信参数 +typedef struct sCommParameters { + uint8_t vlanPriority; + uint16_t vlanId; + uint16_t appId; + uint8_t dstAddress[6]; // 目标 MAC 地址 +} CommParameters; + +// 创建发布者 +GoosePublisher GoosePublisher_create(CommParameters* parameters, + const char* interfaceID); + +// 扩展版: 可禁用 VLAN 标签 +GoosePublisher GoosePublisher_createEx(CommParameters* parameters, + const char* interfaceID, bool useVlanTag); + +// 销毁 +void GoosePublisher_destroy(GoosePublisher self); + +// 发布 GOOSE 消息 +int GoosePublisher_publish(GoosePublisher self, LinkedList dataSet); +// dataSet: MmsValue* 链表 +// 返回值: -1 表示发送错误 +// 自动递增 sqNum + +// 发布并保存到缓冲区 (调试用) +int GoosePublisher_publishAndDump(GoosePublisher self, LinkedList dataSet, + char* msgBuf, int32_t* msgLen, int32_t bufSize); + +// 设置 GOOSE 消息字段 +void GoosePublisher_setGoID(GoosePublisher self, char* goID); +void GoosePublisher_setGoCbRef(GoosePublisher self, char* goCbRef); +void GoosePublisher_setTimeAllowedToLive(GoosePublisher self, uint32_t timeAllowedToLive); +void GoosePublisher_setDataSetRef(GoosePublisher self, char* dataSetRef); +void GoosePublisher_setConfRev(GoosePublisher self, uint32_t confRev); +void GoosePublisher_setSimulation(GoosePublisher self, bool simulation); +void GoosePublisher_setNeedsCommission(GoosePublisher self, bool ndsCom); + +// 状态管理 +uint64_t GoosePublisher_increaseStNum(GoosePublisher self); +// 当数据集成员变化时调用, 重置 sqNum +void GoosePublisher_reset(GoosePublisher self); +// 重置 stNum=1, sqNum=0 + +// 仅用于测试 +void GoosePublisher_setStNum(GoosePublisher self, uint32_t stNum); +void GoosePublisher_setSqNum(GoosePublisher self, uint32_t sqNum); +``` + +--- + +## 12. GOOSE 控制块 (GoCB) + +```c +// GoCB 事件回调 +typedef void (*GoCBEventHandler)( + MmsGooseControlBlock goCb, // GoCB 实例 + int event, // IEC61850_GOCB_EVENT_ENABLE(1) 或 _DISABLE(0) + void* parameter); + +// 设置 GoCB 事件处理器 +void IedServer_setGoCBHandler(IedServer self, + GoCBEventHandler handler, void* parameter); + +// GoCB 信息查询 +char* MmsGooseControlBlock_getName(MmsGooseControlBlock self); +LogicalNode* MmsGooseControlBlock_getLogicalNode(MmsGooseControlBlock self); +DataSet* MmsGooseControlBlock_getDataSet(MmsGooseControlBlock self); +bool MmsGooseControlBlock_getGoEna(MmsGooseControlBlock self); +int MmsGooseControlBlock_getMinTime(MmsGooseControlBlock self); +int MmsGooseControlBlock_getMaxTime(MmsGooseControlBlock self); +bool MmsGooseControlBlock_getFixedOffs(MmsGooseControlBlock self); +bool MmsGooseControlBlock_getNdsCom(MmsGooseControlBlock self); +``` + +--- + +## 13. Sampled Values 控制块 (SVCB) + +```c +// SVCB 事件回调 +typedef void (*SVCBEventHandler)( + SVControlBlock* svcb, // SVCB 实例 + int event, // IEC61850_SVCB_EVENT_ENABLE(1) 或 _DISABLE(0) + void* parameter); + +// 设置 SVCB 事件处理器 +void IedServer_setSVCBHandler(IedServer self, + SVControlBlock* svcb, + SVCBEventHandler handler, void* parameter); +``` + +--- + +## 14. 数据访问控制 (Access Control) + +### 14.1 写访问处理器 (WriteAccessHandler) + +```c +// 回调类型 +typedef MmsDataAccessError (*WriteAccessHandler)( + DataAttribute* dataAttribute, // 被写入的数据属性 + MmsValue* value, // 客户端想要写入的值 + ClientConnection connection, // 发起操作的客户端连接 + void* parameter); // 用户参数 + +// 返回值: +// DATA_ACCESS_ERROR_SUCCESS - 接受写入 (栈自动更新值) +// DATA_ACCESS_ERROR_SUCCESS_NO_UPDATE - 接受但不自动更新 (用户自行更新) +// DATA_ACCESS_ERROR_OBJECT_ACCESS_DENIED - 拒绝写入 + +// 安装写访问处理器 (仅指定属性) +void IedServer_handleWriteAccess(IedServer self, + DataAttribute* dataAttribute, + WriteAccessHandler handler, void* parameter); +// 注意: 如果 dataAttribute 有子属性, 此处理器不会传播到子属性 + +// 安装写访问处理器 (包含所有子属性) +void IedServer_handleWriteAccessForComplexAttribute(IedServer self, + DataAttribute* dataAttribute, + WriteAccessHandler handler, void* parameter); +``` + +### 14.2 FC 级写策略 + +```c +typedef enum { + ACCESS_POLICY_ALLOW, // 允许 + ACCESS_POLICY_DENY // 拒绝 +} AccessPolicy; + +void IedServer_setWriteAccessPolicy(IedServer self, + FunctionalConstraint fc, AccessPolicy policy); +// 默认可对 FC=DC 和 FC=CF 的写访问都是拒绝的 +// 如需允许客户端修改描述信息: IedServer_setWriteAccessPolicy(server, IEC61850_FC_DC, ACCESS_POLICY_ALLOW); +``` + +### 14.3 读访问处理器 (ReadAccessHandler) + +```c +typedef MmsDataAccessError (*ReadAccessHandler)( + LogicalDevice* ld, // 被访问的逻辑设备 + LogicalNode* ln, // 被访问的逻辑节点 + DataObject* dataObject, // 被访问的数据对象 + FunctionalConstraint fc, // 功能约束 + ClientConnection connection, // 客户端连接 + void* parameter); // 用户参数 + +void IedServer_setReadAccessHandler(IedServer self, + ReadAccessHandler handler, void* parameter); +// 全局读访问处理器: 每次客户端读取前调用 +// 可用于实现基于角色的访问控制 (RBAC) +``` + +--- + +## 15. 设置组 (Setting Groups) + +### 15.1 结构体 + +```c +struct sSettingGroupControlBlock { + LogicalNode* parent; + uint8_t actSG; // 当前活动设置组号 + uint8_t numOfSGs; // 设置组总数 + uint8_t editSG; // 当前编辑设置组号 (上电为0) + bool cnfEdit; // 编辑确认标志 (上电为false) + uint64_t timestamp; + uint16_t resvTms; + SettingGroupControlBlock* sibling; // 链表中下一个 +}; +``` + +### 15.2 活动设置组操作 + +```c +// 服务器内部主动切换活动设置组 +void IedServer_changeActiveSettingGroup(IedServer self, + SettingGroupControlBlock* sgcb, uint8_t newActiveSg); +// 调用前用户应先更新 FC=SG 的数据属性 + +// 获取当前活动设置组号 +uint8_t IedServer_getActiveSettingGroup(IedServer self, + SettingGroupControlBlock* sgcb); +``` + +### 15.3 设置组回调处理器 + +```c +// 活动设置组变化处理器 +typedef bool (*ActiveSettingGroupChangedHandler)( + void* parameter, + SettingGroupControlBlock* sgcb, + uint8_t newActSg, // 新的活动设置组 + ClientConnection connection); // 请求变更的客户端 +// 返回 true 接受变更, false 拒绝 + +void IedServer_setActiveSettingGroupChangedHandler(IedServer self, + SettingGroupControlBlock* sgcb, + ActiveSettingGroupChangedHandler handler, void* parameter); + +// 编辑设置组变化处理器 +typedef bool (*EditSettingGroupChangedHandler)( + void* parameter, + SettingGroupControlBlock* sgcb, + uint8_t newEditSg, // 新的编辑设置组 + ClientConnection connection); +// 应在此回调中更新 FC=SE 的数据属性 +// 返回 true 接受变更, false 拒绝 + +void IedServer_setEditSettingGroupChangedHandler(IedServer self, + SettingGroupControlBlock* sgcb, + EditSettingGroupChangedHandler handler, void* parameter); + +// 编辑设置组确认处理器 +typedef void (*EditSettingGroupConfirmationHandler)( + void* parameter, + SettingGroupControlBlock* sgcb, + uint8_t editSg); // 被确认的编辑设置组 +// 客户端确认编辑后将 SE 值写入对应的 SG 值 + +void IedServer_setEditSettingGroupConfirmationHandler(IedServer self, + SettingGroupControlBlock* sgcb, + EditSettingGroupConfirmationHandler handler, void* parameter); +``` + +--- + +## 16. 文件服务 + +### 16.1 基础配置 + +```c +// 设置文件服务根目录 +void IedServer_setFilestoreBasepath(IedServer self, const char* basepath); +// 条件编译: CONFIG_SET_FILESTORE_BASEPATH_AT_RUNTIME + +// 安装文件访问处理器 (控制/监控文件访问) +void MmsServer_installFileAccessHandler(MmsServer self, + MmsFileAccessHandler handler, void* parameter); +// 通过 IedServer_getMmsServer() 获取 MmsServer +``` + +### 16.2 文件访问处理器 + +```c +typedef enum { + MMS_FILE_ACCESS_TYPE_READ_DIRECTORY, // 读取目录 + MMS_FILE_ACCESS_TYPE_OPEN, // 打开文件 + MMS_FILE_ACCESS_TYPE_OBTAIN, // 上传文件(ObtainFile) + MMS_FILE_ACCESS_TYPE_DELETE, // 删除文件 + MMS_FILE_ACCESS_TYPE_RENAME // 重命名文件 +} MmsFileServiceType; + +typedef MmsError (*MmsFileAccessHandler)( + void* parameter, // 用户参数 + MmsServerConnection connection, // 客户端连接 + MmsFileServiceType service, // 服务类型 + const char* localFilename, // 服务器端文件名 + const char* otherFilename); // 另一个文件名参数 +// 返回 MMS_ERROR_NONE 表示接受, 否则用适当的错误码 +``` + +--- + +## 17. 日志服务 (Log Service) + +```c +#include "logging_api.h" + +// 关联日志存储到服务器日志对象 +void IedServer_setLogStorage(IedServer self, + const char* logRef, // 日志对象引用 (如 "GenericIO/LLN0$EventLog") + LogStorage logStorage); // 日志存储实例 + +// SQLite 日志存储 (需包含 src/logging/drivers/sqlite/log_storage_sqlite.c) +extern LogStorage SqliteLogStorage_createInstance(const char* filename); + +// 日志存储 API +void LogStorage_setMaxLogEntries(LogStorage self, int maxEntries); +uint64_t LogStorage_addEntry(LogStorage self, uint64_t timestamp); +void LogStorage_addEntryData(LogStorage self, uint64_t entryID, + const char* dataRef, const uint8_t* data, int dataSize, uint8_t reasonCode); +void LogStorage_getEntries(LogStorage self, uint64_t fromTime, uint64_t toTime, + LogEntryCallback entryCallback, LogEntryDataCallback entryDataCallback, void* parameter); +void LogStorage_destroy(LogStorage self); +``` + +--- + +## 18. TLS 安全通信 + +```c +#include "tls_config.h" + +// 创建 TLS 配置 +TLSConfiguration tlsConfig = TLSConfiguration_create(); + +// 加载服务器私钥 +bool TLSConfiguration_setOwnKeyFromFile(TLSConfiguration self, + const char* keyFilename, const char* password); +// password: 私钥密码, 无密码时传 NULL + +// 加载服务器证书 +bool TLSConfiguration_setOwnCertificateFromFile(TLSConfiguration self, + const char* certFilename); + +// 加载 CA 证书 +bool TLSConfiguration_addCACertificateFromFile(TLSConfiguration self, + const char* certFilename); + +// 加载允许的客户端证书 (白名单) +bool TLSConfiguration_addAllowedCertificateFromFile(TLSConfiguration self, + const char* certFilename); + +// 证书链验证 +void TLSConfiguration_setChainValidation(TLSConfiguration self, bool enable); + +// 仅允许已知证书 +void TLSConfiguration_setAllowOnlyKnownCertificates(TLSConfiguration self, bool enable); + +// 安全事件处理器 +void TLSConfiguration_setEventHandler(TLSConfiguration self, + TLSEventHandler handler, void* parameter); + +// 使用 TLS 创建服务器 +IedServer iedServer = IedServer_createWithTlsSupport(&iedModel, tlsConfig); + +// 销毁 TLS 配置 (在 IedServer_destroy 之后) +TLSConfiguration_destroy(tlsConfig); +``` + +--- + +## 19. 单线程模式 (Threadless) + +适用于资源受限的嵌入式环境,所有操作在单个线程中完成。 + +```c +// 启动 (不创建后台线程) +void IedServer_startThreadless(IedServer self, int tcpPort); + +// 等待连接就绪 (带超时) - 可选 +int IedServer_waitReady(IedServer self, unsigned int timeoutMs); +// 返回: 0 = 无连接就绪, !=0 = 至少一个连接就绪 +// 等价于 Linux select() + +// 处理收到的数据 - 必须周期性调用 +void IedServer_processIncomingData(IedServer self); + +// 执行周期后台任务 - 必须周期性调用 +void IedServer_performPeriodicTasks(IedServer self); + +// 停止 +void IedServer_stopThreadless(IedServer self); + +// 单线程模式主循环示例: +IedServer_startThreadless(iedServer, 102); +while (running) { + uint64_t now = Hal_getTimeInMs(); + + // 定期更新数据 + if (now - lastUpdateTime >= 100) { + IedServer_lockDataModel(iedServer); + IedServer_updateFloatAttributeValue(iedServer, attr, value); + IedServer_unlockDataModel(iedServer); + lastUpdateTime = now; + } + + // 处理网络事件 + IedServer_processIncomingData(iedServer); + + // 处理周期任务 (超时、报告等) + IedServer_performPeriodicTasks(iedServer); + + Thread_sleep(1); +} +IedServer_stopThreadless(iedServer); +``` + +--- + +## 20. MmsServer 底层接口 + +通过 `IedServer_getMmsServer()` 获取后可用: + +```c +// 设置最大连接数 +void MmsServer_setMaxConnections(MmsServer self, int maxConnections); + +// 文件服务 +void MmsServer_setFilestoreBasepath(MmsServer self, const char* basepath); +void MmsServer_enableFileService(MmsServer self, bool enable); + +// 待命名变量列表 (数据集) 服务 +void MmsServer_enableDynamicNamedVariableListService(MmsServer self, bool enable); +void MmsServer_setMaxAssociationSpecificDataSets(MmsServer self, int maxDataSets); +void MmsServer_setMaxDomainSpecificDataSets(MmsServer self, int maxDataSets); +void MmsServer_setMaxDataSetEntries(MmsServer self, int maxDataSetEntries); + +// 日志服务 +void MmsServer_enableJournalService(MmsServer self, bool enable); + +// 服务器身份 +void MmsServer_setServerIdentity(MmsServer self, + char* vendorName, char* modelName, char* revision); + +// VMD 状态 +void MmsServer_setVMDStatus(MmsServer self, + int vmdLogicalStatus, int vmdPhysicalStatus); +// LOGICAL: 0=STATE_CHANGES_ALLOWED, 1=NO_CHANGES, 2=LIMITED_SERVICES, 3=SUPPORT_SERVICES +// PHYSICAL: 0=OPERATIONAL, 1=PARTIALLY, 2=INOPERATIONAL, 3=NEEDS_COMMISSIONING +``` + +--- + +## 21. 完整示例 + +### 21.1 最简单服务器 + +```c +#include "iec61850_server.h" +#include "hal_thread.h" +#include +#include "static_model.h" + +static int running = 0; + +void sigint_handler(int sig) { running = 0; } + +int main(int argc, char** argv) +{ + IedServer iedServer = IedServer_create(&iedModel); + IedServer_start(iedServer, 102); + + if (!IedServer_isRunning(iedServer)) { + IedServer_destroy(iedServer); + return -1; + } + + running = 1; + signal(SIGINT, sigint_handler); + + while (running) + Thread_sleep(1); + + IedServer_stop(iedServer); + IedServer_destroy(iedServer); + return 0; +} +``` + +### 21.2 带控制、报告和 GOOSE 的完整服务器 + +```c +#include "iec61850_server.h" +#include "hal_thread.h" +#include +#include +#include "static_model.h" + +static int running = 0; +static IedServer iedServer = NULL; + +// --- 控制处理器 --- +static ControlHandlerResult +controlHandler(ControlAction action, void* parameter, MmsValue* value, bool test) +{ + if (test) return CONTROL_RESULT_FAILED; + if (MmsValue_getType(value) != MMS_BOOLEAN) return CONTROL_RESULT_FAILED; + + uint64_t ts = Hal_getTimeInMs(); + IedServer_updateUTCTimeAttributeValue(iedServer, + IEDMODEL_GenericIO_GGIO1_SPCSO1_t, ts); + IedServer_updateAttributeValue(iedServer, + IEDMODEL_GenericIO_GGIO1_SPCSO1_stVal, value); + + return CONTROL_RESULT_OK; +} + +// --- 连接处理器 --- +static void +connectionHandler(IedServer self, ClientConnection conn, bool connected, void* param) +{ + printf("Client %s %s\n", + ClientConnection_getPeerAddress(conn), + connected ? "connected" : "disconnected"); +} + +// --- RCB 事件处理器 --- +static void +rcbHandler(void* param, ReportControlBlock* rcb, ClientConnection conn, + IedServer_RCBEventType event, const char* paramName, + MmsDataAccessError err) +{ + printf("RCB %s event: %i\n", ReportControlBlock_getName(rcb), event); +} + +// --- GoCB 事件处理器 --- +static void +goCbHandler(MmsGooseControlBlock goCb, int event, void* param) +{ + printf("GoCB %s: %s\n", + MmsGooseControlBlock_getName(goCb), + event == IEC61850_GOCB_EVENT_ENABLE ? "ENABLED" : "DISABLED"); +} + +int main(int argc, char** argv) +{ + int port = (argc > 1) ? atoi(argv[1]) : 102; + + IedServerConfig cfg = IedServerConfig_create(); + IedServerConfig_setReportBufferSize(cfg, 200000); + IedServerConfig_setMaxMmsConnections(cfg, 5); + + iedServer = IedServer_createWithConfig(&iedModel, NULL, cfg); + IedServerConfig_destroy(cfg); + + // 安装处理器 + IedServer_setControlHandler(iedServer, + IEDMODEL_GenericIO_GGIO1_SPCSO1, + (ControlHandler) controlHandler, + IEDMODEL_GenericIO_GGIO1_SPCSO1); + + IedServer_setConnectionIndicationHandler(iedServer, + (IedConnectionIndicationHandler) connectionHandler, NULL); + + IedServer_setRCBEventHandler(iedServer, rcbHandler, NULL); + IedServer_setGoCBHandler(iedServer, goCbHandler, NULL); + + // 允许客户端写 DC/CF + IedServer_setWriteAccessPolicy(iedServer, IEC61850_FC_DC, ACCESS_POLICY_ALLOW); + + // 设置 GOOSE 接口并启动发布 + IedServer_setGooseInterfaceId(iedServer, "eth0"); + + IedServer_start(iedServer, port); + + if (!IedServer_isRunning(iedServer)) { + printf("Start failed!\n"); + IedServer_destroy(iedServer); + return -1; + } + + IedServer_enableGoosePublishing(iedServer); + + running = 1; + signal(SIGINT, sigint_handler); + + float t = 0.f; + while (running) { + uint64_t ts = Hal_getTimeInMs(); + + // 构建时间戳 + Timestamp iecTs; + Timestamp_clearFlags(&iecTs); + Timestamp_setTimeInMilliseconds(&iecTs, ts); + Timestamp_setLeapSecondKnown(&iecTs, true); + + IedServer_lockDataModel(iedServer); + + IedServer_updateFloatAttributeValue(iedServer, + IEDMODEL_GenericIO_GGIO1_AnIn1_mag_f, sinf(t)); + IedServer_updateTimestampAttributeValue(iedServer, + IEDMODEL_GenericIO_GGIO1_AnIn1_t, &iecTs); + IedServer_updateQuality(iedServer, + IEDMODEL_GenericIO_GGIO1_AnIn1_q, QUALITY_VALIDITY_GOOD); + + IedServer_unlockDataModel(iedServer); + + t += 0.1f; + Thread_sleep(100); + } + + IedServer_stop(iedServer); + IedServer_destroy(iedServer); + return 0; +} +``` diff --git a/release/inc/myDatacenter.h b/release/inc/myDatacenter.h index d518893..cdc337c 100644 --- a/release/inc/myDatacenter.h +++ b/release/inc/myDatacenter.h @@ -66,7 +66,7 @@ int dc_signal_in(const std::string &saddr, const std::string &desc, const std::s int dc_signal_in_with_callback(const std::string &saddr, const std::string &desc, const std::string &link_saddr, void **p_data, out_signal_change_cb cb); -int dc_signal_ao(const std::string &saddr, const std::string &desc, uint8_t data_type, const stru_signal_param ¶m, uint8_t ctrl_type, void *p_data, void *p_default_data, signal_change_cb cb); +int dc_signal_ao(const std::string &saddr, const std::string &desc, uint8_t data_type, uint8_t ctrl_type, void *p_data, void *p_default_data, signal_change_cb cb); int dc_signal_ao_link_with_callback(const std::string &saddr, void **p_data, signal_change_cb cb); @@ -75,7 +75,7 @@ int dc_signal_ao_set_val(const std::string &saddr, SIGNAL_CTRL_STEP step, stru_s int dc_signal_ao_set_val_without_check(const std::string &saddr, uint8_t data_type, void *p_data); // 数据中心参数信号注册接口 -int dc_signal_param(const std::string &saddr, const std::string &desc, uint8_t data_type, const stru_signal_param ¶m, uint8_t ctrl_type, void **p_data, void **p_default_data, int num, signal_change_cb cb); +int dc_signal_param(const std::string &saddr, const std::string &desc, uint8_t data_type, uint8_t ctrl_type, void **p_data, void **p_default_data, int num, signal_change_cb cb); // 数据中心参数链接信号接口 int dc_signal_param_link_with_callback(const std::string &saddr, void **p_data, int num, signal_change_cb cb); diff --git a/src/system/libdatacenter/inc/dc_param.h b/src/system/libdatacenter/inc/dc_param.h index 7456be2..2bf1471 100644 --- a/src/system/libdatacenter/inc/dc_param.h +++ b/src/system/libdatacenter/inc/dc_param.h @@ -4,6 +4,14 @@ #include "dc_signal.h" #include "myDatacenter.h" +#include + #define DC_PARAM_PATH "/mnt/RTU/test/file/PARAM/param.xml" -void dc_param_cfg_parse(); \ No newline at end of file +void dc_param_cfg_parse(); + +// 洢Ԫݵdc_param_cfg_parse ڴźǰã +void dc_param_metadata_store(const std::string &saddr, const stru_signal_param ¶m); + +// saddr ҲԪݣҵ true򷵻 false +bool dc_param_metadata_lookup(const std::string &saddr, stru_signal_param &out_param); diff --git a/src/system/libdatacenter/src/dc_param.cpp b/src/system/libdatacenter/src/dc_param.cpp index 0b24a02..d136889 100644 --- a/src/system/libdatacenter/src/dc_param.cpp +++ b/src/system/libdatacenter/src/dc_param.cpp @@ -1,6 +1,25 @@ #include "dc_param.h" #include "tinyxml2.h" +// 中央元数据表(以 saddr 为键,初始化时写入,运行时只读) +static std::unordered_map g_param_metadata; + +void dc_param_metadata_store(const std::string &saddr, const stru_signal_param ¶m) +{ + g_param_metadata[saddr] = param; +} + +bool dc_param_metadata_lookup(const std::string &saddr, stru_signal_param &out_param) +{ + auto it = g_param_metadata.find(saddr); + if (it != g_param_metadata.end()) + { + out_param = it->second; + return true; + } + return false; +} + void dc_param_cfg_parse() { using namespace tinyxml2; @@ -75,7 +94,9 @@ void dc_param_cfg_parse() param.step = step; param.unit = unit ? std::string(unit) : std::string(); - dc_signal_ao(saddr, desc, data_type, param, + // 先存入中央元数据表,再注册信号 + dc_param_metadata_store(saddr, param); + dc_signal_ao(saddr, desc, data_type, SIGNAL_CTRL_TYPE::SBO_NORMAL, p_data, p_default_data, nullptr); } @@ -155,7 +176,9 @@ void dc_param_cfg_parse() param.step = step; param.unit = unit ? std::string(unit) : std::string(); - dc_signal_param(saddr, desc, data_type, param, + // 先存入中央元数据表,再注册信号 + dc_param_metadata_store(saddr, param); + dc_signal_param(saddr, desc, data_type, SIGNAL_CTRL_TYPE::SBO_NORMAL, vec_p_data.data(), vec_p_default_data.data(), num, nullptr); } diff --git a/src/system/libdatacenter/src/dc_signal.cpp b/src/system/libdatacenter/src/dc_signal.cpp index 9ca08d7..80fdc81 100644 --- a/src/system/libdatacenter/src/dc_signal.cpp +++ b/src/system/libdatacenter/src/dc_signal.cpp @@ -371,7 +371,25 @@ LOCAL int dc_data_compare(uint8_t data_type, void *p_data, void *p_data2) return -1; } -LOCAL bool dc_signal_ao_add_check(stru_signal *p_signal, const std::string &desc, uint8_t data_type, const stru_signal_param ¶m, uint8_t ctrl_type, void *p_default_data) + +// 从中央元数据表查找并设置 signal.param +LOCAL void dc_signal_apply_metadata(stru_signal &signal) +{ + stru_signal_param param; + if (dc_param_metadata_lookup(signal.saddr, param)) + { + signal.param = param; + } + else + { + signal.param.min = 0.0f; + signal.param.max = 0.0f; + signal.param.step = 0.0f; + signal.param.unit.clear(); + } +} + +LOCAL bool dc_signal_ao_add_check(stru_signal *p_signal, const std::string &desc, uint8_t data_type, uint8_t ctrl_type, void *p_default_data) { if(p_signal == nullptr || p_default_data == nullptr) { @@ -393,10 +411,6 @@ LOCAL bool dc_signal_ao_add_check(stru_signal *p_signal, const std::string &desc change = true; } - if(p_signal->param.min != param.min || p_signal->param.max != param.max || p_signal->param.step != param.step || p_signal->param.unit != param.unit) - { - p_signal->param = param; - change = true; } if(p_signal->ctrl_type != ctrl_type) @@ -413,7 +427,7 @@ LOCAL bool dc_signal_ao_add_check(stru_signal *p_signal, const std::string &desc return change; } -LOCAL bool dc_signal_param_add_check(stru_signal *p_signal, const std::string &desc, uint8_t data_type, const stru_signal_param ¶m, uint8_t ctrl_type, void **p_default_data, uint8_t num) +LOCAL bool dc_signal_param_add_check(stru_signal *p_signal, const std::string &desc, uint8_t data_type, uint8_t ctrl_type, void **p_default_data, uint8_t num) { bool change = false; @@ -429,10 +443,6 @@ LOCAL bool dc_signal_param_add_check(stru_signal *p_signal, const std::string &d change = true; } - if(p_signal->param.min != param.min || p_signal->param.max != param.max || p_signal->param.step != param.step || p_signal->param.unit != param.unit) - { - p_signal->param = param; - change = true; } if(p_signal->ctrl_type != ctrl_type) @@ -1083,7 +1093,7 @@ int dc_signal_in_with_callback(const std::string &saddr, const std::string &desc return dc_signal_add_to_map(signal, g_datacenter.signal_in); } -int dc_signal_ao(const std::string &saddr, const std::string &desc, uint8_t data_type, const stru_signal_param ¶m, uint8_t ctrl_type, void *p_data, void *p_default_data, signal_change_cb cb) +int dc_signal_ao(const std::string &saddr, const std::string &desc, uint8_t data_type, uint8_t ctrl_type, void *p_data, void *p_default_data, signal_change_cb cb) { if(p_data == nullptr || p_default_data == nullptr) { @@ -1095,7 +1105,7 @@ int dc_signal_ao(const std::string &saddr, const std::string &desc, uint8_t data stru_signal *p_signal = dc_find_signal(saddr, g_datacenter.signal_ao); if(p_signal != nullptr && !p_signal->vec_p_data.empty() && !p_signal->vec_p_default_data.empty() && nullptr != p_signal->vec_p_data[0] && nullptr != p_signal->vec_p_default_data[0]) { - if(true == dc_signal_ao_add_check(p_signal, desc, data_type, param, ctrl_type, p_default_data)) + if(true == dc_signal_ao_add_check(p_signal, desc, data_type, ctrl_type, p_default_data)) { dc_set_param_cfg_change(true); } @@ -1119,7 +1129,7 @@ int dc_signal_ao(const std::string &saddr, const std::string &desc, uint8_t data signal.saddr.assign(saddr); signal.desc.assign(desc); signal.data_type = data_type; - signal.param = param; + dc_signal_apply_metadata(signal); signal.ctrl_type = ctrl_type; dc_signal_add_to_map(signal, g_datacenter.signal_ao); @@ -1263,7 +1273,7 @@ int dc_signal_ao_set_val_without_check(const std::string &saddr, uint8_t data_ty return -1; } -int dc_signal_param(const std::string &saddr, const std::string &desc, uint8_t data_type, const stru_signal_param ¶m, uint8_t ctrl_type, void **p_data, void **p_default_data, int data_num, signal_change_cb cb) +int dc_signal_param(const std::string &saddr, const std::string &desc, uint8_t data_type, uint8_t ctrl_type, void **p_data, void **p_default_data, int data_num, signal_change_cb cb) { // 先从参数表里解析,调用参数注册 // 程序注册时,从参数表里去查找,同一个信息,使用参数表中的保存的数据值,在线程初始化时,通知所有链接此信号的信号 @@ -1289,7 +1299,7 @@ int dc_signal_param(const std::string &saddr, const std::string &desc, uint8_t d if(p_signal != nullptr) { // dc_set_signal_val(p_data, data_type, p_signal->p_data); - if(true == dc_signal_param_add_check(p_signal, desc, data_type, param, ctrl_type, p_default_data, data_num)) + if(true == dc_signal_param_add_check(p_signal, desc, data_type, ctrl_type, p_default_data, data_num)) { dc_set_param_cfg_change(true); } @@ -1329,7 +1339,7 @@ int dc_signal_param(const std::string &saddr, const std::string &desc, uint8_t d signal.saddr.assign(saddr); signal.desc.assign(desc); signal.data_type = data_type; - signal.param = param; + dc_signal_apply_metadata(signal); signal.ctrl_type = ctrl_type; dc_signal_add_to_map(signal, g_datacenter.signal_param); diff --git a/src/system/libiec61850m/src/iec61850m.cpp b/src/system/libiec61850m/src/iec61850m.cpp index ef7cede..760feb1 100644 --- a/src/system/libiec61850m/src/iec61850m.cpp +++ b/src/system/libiec61850m/src/iec61850m.cpp @@ -417,9 +417,8 @@ LOCAL int iec61850m_ao_signal_init(int num, stru_point_item *p_item) uint8_t local_type = g_mms_m_type_to_local_type[p->type]; uint8_t ctrl_type = SIGNAL_CTRL_TYPE::DIRECT_NORMAL; - stru_signal_param param = {0}; - if(0 != dc_signal_ao(p->saddr, p->desc, local_type, param, ctrl_type, p->value.p_val[0], p->value.p_default[0], iec61850m_signal_ao_change_callback)) + if(0 != dc_signal_ao(p->saddr, p->desc, local_type, ctrl_type, p->value.p_val[0], p->value.p_default[0], iec61850m_signal_ao_change_callback)) { MY_LOG_E("dc_signal_ao failed, saddr %s, desc %s, type %d", p->saddr, p->desc, p->type); return -1; @@ -445,9 +444,8 @@ LOCAL int iec61850m_param_signal_init(int num, stru_point_item *p_item) uint8_t local_type = g_mms_m_type_to_local_type[p->type]; uint8_t ctrl_type = SIGNAL_CTRL_TYPE::DIRECT_NORMAL; - stru_signal_param param = {0}; - if(0 != dc_signal_param(p->saddr, p->desc, local_type, param, ctrl_type, p->value.p_val, p->value.p_default, MMS_M_MAX_VAL_NUM, iec61850m_signal_param_change_callback)) + if(0 != dc_signal_param(p->saddr, p->desc, local_type, ctrl_type, p->value.p_val, p->value.p_default, MMS_M_MAX_VAL_NUM, iec61850m_signal_param_change_callback)) { MY_LOG_E("dc_signal_param failed, saddr %s, desc %s, type %d", p->saddr, p->desc, p->type); return -1; diff --git a/src/system/libself_ptl/src/self_ptl.cpp b/src/system/libself_ptl/src/self_ptl.cpp index f1dce40..f4be853 100644 --- a/src/system/libself_ptl/src/self_ptl.cpp +++ b/src/system/libself_ptl/src/self_ptl.cpp @@ -897,8 +897,9 @@ int self_ptl_do_signal_out(stru_app *p_app) param.max = safeStringToFloat(p_ao->p_param->max); param.step = safeStringToFloat(p_ao->p_param->step); param.unit = p_ao->p_param->unit; + dc_param_metadata_store(p_ao->p_param->base.saddr, param); - ret |= dc_signal_ao(p_ao->p_param->base.saddr, p_ao->p_param->base.desc, p_ao->p_param->type, param, SIGNAL_CTRL_TYPE::SBO_NORMAL, p_ao->vec_p_data[0], p_ao->vec_p_default_data[0], self_ptl_signal_change_callback); + ret |= dc_signal_ao(p_ao->p_param->base.saddr, p_ao->p_param->base.desc, p_ao->p_param->type, SIGNAL_CTRL_TYPE::SBO_NORMAL, p_ao->vec_p_data[0], p_ao->vec_p_default_data[0], self_ptl_signal_change_callback); } } @@ -917,8 +918,9 @@ int self_ptl_do_signal_out(stru_app *p_app) param.max = safeStringToFloat(p_param->p_param->max); param.step = safeStringToFloat(p_param->p_param->step); param.unit = p_param->p_param->unit; + dc_param_metadata_store(p_param->p_param->base.saddr, param); - ret |= dc_signal_param(p_param->p_param->base.saddr, p_param->p_param->base.desc, p_param->p_param->type, param, + ret |= dc_signal_param(p_param->p_param->base.saddr, p_param->p_param->base.desc, p_param->p_param->type, SIGNAL_CTRL_TYPE::SBO_NORMAL, p_param->vec_p_data.data(), p_param->vec_p_default_data.data(), p_param->p_param->num, self_ptl_signal_change_callback); } }