# libmms_s 模块分析 **日期**: 2026-06-12 **基于源码**: `src/protocol/libmms_s/`(16个文件,约7500行) --- ## 1. 模块定位 `libmms_s` 是 RTU 的 IEC 61850 MMS 服务端库,位于协议层。它解析 ICD(IED Capability Description)XML 文件构建完整的数据模型,创建 `IedServer` 对外提供 MMS 服务,实现控制执行、定值组管理、文件服务等功能。 **与上下层关系**: - **下层**: 依赖 libiec61850 v1.5.x 的 IedServer/IedModel API - **上层**: 被 `iec61850s`(系统层封装)调用初始化,通过回调机制将控制/定值变更通知上层 - **数据方向**: 接收远方客户端的 MMS 读写请求,通过回调将控制指令传递给 `iec61850s` → `datacenter` ## 2. ICD 解析流程(`mms_s_icd.cpp`) ICD 文件是 SCL(Substation Configuration Language)格式的 XML。解析流程自顶向下: ``` XML 加载 (tinyxml2) → SCL 根元素 → DataTypeTemplates 区域: - 遍历 LNodeType(逻辑节点类型) - DO → 引用 DOType - 遍历 DOType(数据对象类型) - SDO → 递归引用 DOType - DA → 属性定义(bType, fc, dchg, qchg) - fc/dchg/qchg 从父级 BDA 向下传播 - 遍历 DAType(数据属性类型) - BDA → fc/dchg/qchg 从 BDA 向下传播到子 BDA - 遍历 EnumType(枚举类型) - EnumVal → 枚举值映射 → IED 区域: - AccessPoint → Server - Server → LDevice - LN0 (LLN0): - DOI/SDI/DAI → 运行时值覆盖 - DataSet → FCDA 成员 - ReportControl → TrgOps, OptFields - SettingControl → numOfSGs, actSG - LN(逻辑节点)→ DOI/SDI/DAI → 校验: - LN/LN0 的 lnType 在 DataTypeTemplates 中存在 - DataSet 的 FCDA 引用的 DO/SDO/DA 在模型中存在 ``` 核心数据结构: - `stru_all_DO`: 包含 `map_all_sdo`(子 SDO)和 `map_all_da`(子 DA),每个 DA 记录 `sAddr`, `bType`, `fc` - `stru_icd`: 顶层结构,包含 `ied` 对象和 `map_lnode_type`, `map_do_type`, `map_da_type`, `map_enum_type` - `stru_icd::ied`: 包含 `map_ldevice`(逻辑设备),`vec_all_DO`(所有 DO),`vec_control_do`(控制 DO),`vec_saddr`(信号地址列表),`map_saddr_point`(sAddr→模型节点映射) ## 3. 动态模型创建(`mms_s_model.cpp`) 将 ICD 解析的 SCL 结构映射为 libiec61850 的 `IedModel` 对象树。 ### 3.1 类型映射 | ICD bType 字符串 | libiec61850 DataAttributeType | |-----------------|------------------------------| | `BOOLEAN` | IEC61850_BOOLEAN | | `INT8` / `Struct` | IEC61850_INT8 | | `INT16` | IEC61850_INT16 | | `INT32` | IEC61850_INT32 | | `INT64` | IEC61850_INT64 | | `INT128` | IEC61850_INT128 | | `INT8U` | IEC61850_INT8U | | `INT16U` | IEC61850_INT16U | | `INT24U` | IEC61850_INT24U | | `INT32U` | IEC61850_INT32U | | `FLOAT32` | IEC61850_FLOAT32 | | `FLOAT64` | IEC61850_FLOAT64 | | `VisString64` 等 | IEC61850_VISIBLE_STRING64 | | `Unicode255` 等 | IEC61850_UNICODE_STRING255 | | `Timestamp` | IEC61850_TIMESTAMP | | `Dbpos` | IEC61850_DBPOS | | `Quality` | IEC61850_QUALITY | | `Check` | IEC61850_CHECK | | `Tcmd` | IEC61850_TCMD | | `OptFlds` | IEC61850_OPTFLDS | | `TrgOps` | IEC61850_TRGOPS | | `EntryID` | IEC61850_ENTRYID | | `EntryTime` | IEC61850_ENTRYTIME | | `ObjRef` | IEC61850_OBJREF | | `PhyComAddr` | IEC61850_PHYCOMADDR | | `Octet64` | IEC61850_OCTET_STRING_64 | | `Currency` | IEC61850_CURRENCY | | `AnalogueValue` | IEC61850_ANALOGUE_VALUE | | `Unit` | IEC61850_UNIT | | `Vector` | IEC61850_VECTOR | | `ValWithTrans` | IEC61850_VAL_WITH_TRANS | | `CUG` | IEC61850_CUG | ### 3.2 创建流程 ``` model_init(icd): IedModel_create(ied_name) // 创建根模型 → model_ldevice_init(model, icd): 遍历每个 LDevice: LogicalDevice_create(name, model) → model_init_lnodes(ld, icd): 遍历每个 LN/LN0: LogicalNode_create(name, ld) → model_init_do(ln, icd): 遍历 DO/SDO/DA 递归创建 DataObject 和 DataAttribute 记录 sAddr → ModelNode 映射 → 对 LN0: model_init_datasets → 创建 DataSet + FCDA model_init_rcb → 创建 URCB/BRCB + TrgOps/OptFields model_init_sgcb → 创建 SGCB + SG/SE 镜像 → model_sync_ied_default_values: ICD 默认值同步到模型节点 → model_search_control_DataObjects: 搜索所有含 ctlModel 的 DO → 设置 model.initializer = mms_s_values_init ``` ### 3.3 SG/SE 定值组镜像 每个 LN0 的 SGCB 下创建 SG(Setting Group)和 SE(Setting Group Edit),SE 是 SG 的镜像: - SG 的 sAddr = 原始 sAddr(如 `PROT/LLN0$SG$sg1$StrVal`) - SE 的 sAddr = `SE_` 前缀版本 - `mms_s_param.cpp` 只操作 SE,协议栈负责 SG↔SE 同步 ### 3.4 sAddr 映射 `sAddr` 是信号地址,IEC 61850 标准字段。解析时保存 `sAddr → (fc, node)` 映射到 `map_saddr_point`,供上层 `iec61850s` 按 sAddr 注册信号回调。 ## 4. MMS 服务器启动(`mms_s.cpp`) ``` mms_s_init(icd_path, stack_size): → 解析 ICD → 创建 IedModel → model_init → IedServerConfig 配置: - 设置 selectLlN0ForUnmatchedCommands = true - 设置 maxMmsConnections = default - 设置 reportBufferSize = 8 → IedServer_create(&model) // 创建服务器 → mms_s_control_init(server) // 安装控制回调 → mms_s_param_init(server) // 安装定值组回调 → mms_s_file_init(server) // 安装文件服务 → mms_s_setting_init(server) // 安装 SP 定值回调 → IedServer_start(server, port) → pthread_create → server_run_thread: while(1) IedServer_processEvent(server, timeout) ``` ## 5. 控制服务(`mms_s_control.cpp`) ### 5.1 控制模型 支持三种控制模型(由 ICD 中 DO 的 `ctlModel` 决定): - `status-only` (0): 仅状态,不参与控制 - `direct-with-normal-security` (1): 直接执行 - `sbo-with-normal-security` (2): 选择-执行(Select Before Operate) ### 5.2 回调链 ``` 远方客户端发送控制命令 → IedServer 触发 control_handler(control, ctlVal, oper, interlockCheck): - oper == OPERATE: 执行操作 stVal 从 ctlVal 中提取 若是 SBO: control.oper() → 更新 t, origin, ctlNum 若是 Direct: 直接更新 stVal + t 调用 p_control_callback(state) → 通知 iec61850s - oper == CANCEL: 取消操作 → 同时触发 check_handler(control, ctlVal, oper, interlockCheck): - interlockCheck == true: 检查连锁条件 - 返回 false 阻止操作执行 ``` ### 5.3 控制注册 ``` mms_s_control_register(controls, count, callback): 遍历所有控制 DO(由 model_search_control_DataObjects 在 ICD 解析阶段收集) → 通过 sAddr 在 map_saddr_point 中查找对应的 ModelNode → IedServer_setControlHandlerEx → 安装 control_handler + check_handler → 保存 callback 到全局列表 ``` ## 6. 定值组管理(`mms_s_param.cpp`) ### 6.1 SG/SE 编辑流程 ``` 远方客户端编辑定值: → SGCB.SelectEditSG(zone): 切换到编辑区 → 加载当前激活 SG 的值到 SE → 设置 selectEditSG_callback → 通知上层定值区切换 → SE 各 DA 写入新值: → writeAccessHandler: 校验 min/max/step → 通过才允许写入 → SGCB.ConfirmEditSG(zone): → confirmEditSG_callback: 读取 SE 所有值 → 校验范围/步长 → 调用 p_param_callback(zone, data) → 通知 iec61850s 返回 true 确认 / false 拒绝 ``` ### 6.2 值校验 每种数据类型对应独立的校验函数: - BOOLEAN: 直接通过 - INT8/16/32: `minVal/maxVal/stepSize` 从 min/max/stepSize DA 读取 - INT8U/16U/32U: 同上(无符号) - FLOAT32/64: 校验 + epsilon 容差(`fabs(val - min) > 0.000001`) - VisString/UnicodeString: 长度校验 - Enum: 枚举值范围校验 ### 6.3 定值区信号绑定 ``` mms_s_bind_param_zone_signal(saddr, callback): → 在 LN0 的 SGCB 中查找 ActSG DA → IedServer_handleWriteAccess: 安装 writeAccessHandler → 当 ActSG 被写入时: 读取新 actSG 值 调用 callback(saddr, new_act_sg) → iec61850s 通过 datacenter 更新相关信号 ``` ## 7. 固定定值管理(`mms_s_setting.cpp`) SP(Set Point)类 DA(fc=SP)的读写管理: ``` mms_s_setting_register(settings, count, callback): → 对每个 SP DA: - IedServer_setWriteAccessPolicy(deny) // 全局拒绝 SP 写入 - IedServer_handleWriteAccess 安装 writeAccessHandler: → 校验 min/max/step → 允许后调用 p_setting_callback(saddr, data) - sgcb 定值区变化时同步 SP 值 ``` ## 8. 文件服务(`mms_s_file.cpp`) ``` file_init(server): → IedServer_setFilestoreBasepath(path) → IedServer_setFileServiceHandler: - rename: 一律拒绝 - read/write/delete: 允许 - IEDSERVER.BIN 文件保护: 拒绝任何操作 → IedServer_installConnectionHandler(connectionHandler): 记录连接建立/断开日志 ``` ## 9. DA 值初始化(`mms_s_value.cpp`) ``` mms_s_values_init(param, model): // 作为 model->initializer 在 IedServer_create 时被调用 → 遍历 LD → LN → DO → SDO → DA 树 → 按 bType 初始化 MmsValue: BOOLEAN → MmsValue_newBoolean(false) INT32 → MmsValue_newInteger(0) INT32U → MmsValue_newUnsigned(0) FLOAT32 → MmsValue_newFloat(0.0) VisString* → MmsValue_newVisibleString("") Unicode* → MmsValue_newUnicodeString("") Timestamp → MmsValue_newUtcTime(0) Dbpos → MmsValue_newDbpos() Enum → 按 OrderedEnum 表现(有 ord → INTEGER, 无 → 第一枚举值) → MmsValue_setDeletable(value) // 标记为模型可管理生命周期 mms_s_value_update(saddr, value_str): → 按 bType 从字符串解析值 → 更新 MmsValue → IedServer_updateAttributeValue(server, node, value) // 触发 RCB 报告 → 自动更新 t(时标)和 q(品质)字段 ``` ## 10. 关键日志宏 ```cpp #define MMS_S_LOG_E(fmt, ...) LOG_E("[MMS_S][%s:%d]" fmt, __FILENAME__, __LINE__, ##__VA_ARGS__) #define MMS_S_LOG_W(fmt, ...) LOG_W("[MMS_S][%s:%d]" fmt, __FILENAME__, __LINE__, ##__VA_ARGS__) #define MMS_S_LOG_I(fmt, ...) LOG_I("[MMS_S][%s:%d]" fmt, __FILENAME__, __LINE__, ##__VA_ARGS__) ``` 带颜色、时间戳、文件名和行号。