RTU/claude/工程/libmms_s模块分析.md

17 KiB
Raw Permalink Blame History

libmms_s 模块分析

日期2026-06-10


1. 模块概览

libmms_s 是 IEC 61850 MMS 服务端的封装库,基于 libiec61850 v1.5.3 的服务端 API提供 ICD 文件解析、动态数据模型创建、IedServer 生命周期管理,以及控制/定值/参数/文件等子系统的初始化。

目录结构

src/protocol/libmms_s/
├── inc/
│   ├── mms_s.h           # 核心头文件:日志宏、全局访问接口、类型转换
│   ├── mms_s_icd.h        # ICD 解析数据结构(完整 SCL 类型体系)
│   ├── mms_s_model.h      # 动态模型创建接口
│   ├── mms_s_value.h      # DA 初始值设定
│   ├── mms_s_control.h    # 控制功能接口
│   ├── mms_s_param.h      # 定值组参数接口
│   ├── mms_s_setting.h    # 定值SP接口
│   └── mms_s_file.h       # 文件传输服务接口
└── src/
    ├── mms_s.cpp          # 核心init/run/task/类型转换
    ├── mms_s_icd.cpp      # ICD XML 解析器tinyxml2
    ├── mms_s_model.cpp    # 动态 IedModel 创建
    ├── mms_s_control.cpp  # 控制SBO/直控)处理
    ├── mms_s_param.cpp    # 定值组SG/SE管理
    ├── mms_s_setting.cpp  # 定值SP管理
    ├── mms_s_value.cpp    # 数据属性初始值设定
    └── mms_s_file.cpp     # MMS 文件传输服务

2. 核心架构

2.1 单例全局状态

LOCAL IedServer gp_iedServer = NULL;   // 全局 IED 服务器(单例)
LOCAL stru_icd *gp_icd = NULL;         // 全局 ICD 数据(单例)
LOCAL int g_running = 0;               // 线程运行标志
LOCAL bool g_dbg_switch = false;       // 调试输出开关

整个库采用单例模式,全局只有一个 IedServer 和一个 ICD 数据结构。

2.2 模块分层

┌──────────────────────────────────────────┐
│              myMms_s.h                   │  ← 公共 API + 类型定义
├──────────────────────────────────────────┤
│  mms_s.cpp (init / run / 类型转换)        │  ← 库入口
├──────────────────────────────────────────┤
│  mms_s_icd.cpp    │  mms_s_model.cpp     │  ← ICD 解析 → 模型构建
├──────────────────────────────────────────┤
│  mms_s_value.cpp  (DA 初始值)             │  ← 模型回调
├──────────────────────────────────────────┤
│  mms_s_control.cpp │ mms_s_param.cpp     │  ← 功能模块
│  mms_s_setting.cpp │ mms_s_file.cpp      │
├──────────────────────────────────────────┤
│          libiec61850 (libiec61850.a)      │  ← 底层协议栈
└──────────────────────────────────────────┘

3. 完整初始化流程

mms_s_init() 的执行顺序(mms_s.cpp:261

1. icd_parse(icd_path)              → 解析 ICD 文件 → stru_icd
2. model_init(*gp_icd)              → 创建动态数据模型 → IedModel
3. IedServerConfig_create()         → 创建服务器配置
4. IedServer_createWithConfig()     → 创建 IedServer 实例
5. control_init()                   → 安装控制回调
6. param_init()                     → 安装定值组回调
7. file_init()                      → 安装文件服务
8. IedServer_setRCBEventHandler()   → 注册 RCB 事件监听
9. IedServer_start(port)            → 启动服务器(默认端口 102
10. setting_init()                  → 初始化定值
11. IedServer_isRunning()           → 检查运行状态
12. Thread_create(mms_s_run_task)   → 启动后台线程

4. ICD 解析子系统

4.1 数据结构体系(mms_s_icd.h

对应 IEC 61850-6 SCL 标准的完整类型体系:

数据类型模板层DataTypeTemplates

结构 说明 关键字段
stru_LNodeType 逻辑节点类型 lnClass, vec_do, map_do(key:name)
stru_DO 数据对象定义 desc, type→ DOType id
stru_DOType 数据对象类型 cdc, vec_da, map_da, vec_sdo, map_sdo
stru_DA 数据属性定义 bType, type, dchg, qchg, fc, text
stru_DAType 数据属性类型 vec_bda, map_bda(key:name)
stru_BDA 基本数据属性 bType, type, fc, dchg, qchg
stru_EnumType 枚举类型 vec_ord, map_enumVal(key:ord)

实例化模型层IED/Server

结构 说明
stru_ied IED 顶层模型(含 model, name, 各逻辑设备)
stru_LDevice 逻辑设备(含 Ldevice 指针, sgcb, ln0, vec_ln
stru_LN0 LLN0含 数据集, 报告控制, 定值控制)
stru_LN 普通逻辑节点
stru_DOI 数据对象实例(含 SDI, DAI
stru_SDI 子数据对象实例(可递归嵌套)
stru_DAI 数据属性实例(含 sAddr, val
stru_DataSet 数据集(含 FCDA 列表)
stru_FCDA 功能约束数据属性ldInst/lnClass/doName/daName/fc
stru_ReportControl 报告控制块(含 TrgOps, OptFields, RptEnabled_max
stru_SettingControl 定值控制块actSG, numOfSGs

运行时辅助结构

结构 说明
stru_all_DO DO 运行时树(含 libiec61850 DataObject 指针, map_all_sdo, map_all_da
stru_all_DA DA 运行时树(含 libiec61850 DataAttribute 指针, map_all_da 子节点)
stru_contorl_DO 控制 DO 定位信息ldevice_inst/ln_class/ln_inst/do_name/p_all_do
stru_saddr_point sAddr 到模型节点的映射node, da_t, da_q
stru_setting_info 定值信息DA 指针, 类型, 值)

4.2 解析流程(mms_s_icd.cpp

icd_parse(icd_file)
├── XMLDocument.LoadFile()
├── parse_ied()                → 解析 IED 实例
│   ├── parse_LDevice()        → 每个逻辑设备
│   │   ├── parse_LN0()        → LLN0数据集/报告控制/定值控制/DOI
│   │   │   ├── parse_DataSet()
│   │   │   ├── parse_ReportControl()
│   │   │   │   ├── parse_ReportControl_TrgOps()
│   │   │   │   ├── parse_ReportControl_OptFields()
│   │   │   │   └── parse_ReportControl_RptEnable()  → RptEnabled_max
│   │   │   ├── parse_DOI()    → parse_SDI() / parse_DAI()
│   │   │   └── parse_SettingControl()
│   │   └── parse_LN()         → 普通 LN + DOI
│   └── (沿 SCL > IED > AccessPoint > Server > LDevice 路径)
├── parse_dataTypeTemplates()  → 解析模板
│   ├── parse_LNodeType()
│   ├── parse_DOType()
│   ├── parse_DAType()
│   ├── parse_EnumType()
│   └── parse_BDA_fc() ×2      → 传播 FC/dchg/qchg 到嵌套 BDA
└── check_ied_dataTypeTemplates() → 一致性校验
    └── 递归验证每个实例节点都能在模板中找到定义

4.3 BDA 属性传播机制

关键设计:当 DA 的 type 指向 DATypeStruct 类型)时,parse_BDA_fc() 将父级 DA 的 fc/dchg/qchg 属性向下传播到 DAType 中所有 BDA确保嵌套 Struct 的底层 BDA 也能继承正确的 FC 约束。


5. 动态模型创建子系统

5.1 model_init() 主流程(mms_s_model.cpp:1319

1. IedModel_create(name)
2. model_ldevice_init()
   ├── LogicalDevice_create()
   ├── model_ln0_init()
   │   ├── LogicalNode_create("LLN0")
   │   ├── model_dataobject_init() → 创建所有 DO/SDO/DA
   │   ├── model_DataSet_init()    → 创建数据集+条目
   │   ├── model_ReportControlBlock_init() → 创建 RCB
   │   └── SettingGroupControlBlock_create()
   └── model_LNodes_init() → 创建普通 LN
3. model_sync_ied_default_values() → 同步 ICD DAI 中的 sAddr/val
4. model_search_control_DataObjects() → 搜索 ctlModel→vec_control_do
5. icd.ied.model->initializer = mms_s_values_init  → 注册回调

5.2 类型映射表

bType → DataAttributeTypemms_s_model.cpp:43 BOOLEAN → IEC61850_BOOLEAN, INT32 → IEC61850_INT32, FLOAT32 → IEC61850_FLOAT32, Struct → IEC61850_CONSTRUCTED, Quality → IEC61850_QUALITY, Timestamp → IEC61850_TIMESTAMP, Dbpos → IEC61850_GENERIC_BITSTRING 等,共约 25 种映射。

FC → FunctionalConstraintmms_s_model.cpp:86 ST/MX/SP/SV/CF/DC/SG/SE/SR/OR/BL/EX/CO/US/MS/RP/BR/LG/GO → 对应 IEC61850 枚举。

5.3 SG/SE 双数据属性设计

当 DA 的 FC=SG 时,除了创建 FC=SG 的 DA 外,还会额外创建一个 FC=SE 的同名 DAkey 加 _SE 后缀)。反之 FC=SE 同理。sAddr 映射时也会对应添加 _SG / _SE 后缀区分。

这是一个独创设计——在同一个 DO 下同时暴露 SG当前激活值和 SE编辑缓冲区值使得外部系统可以同时访问定值的"当前生效值"和"正在编辑中的值"。

5.4 sAddr 映射机制

ICD 文件中 DAI 元素的 sAddr 属性定义了该数据点与外部数据源的绑定关系。model_sync_ied_default_values() 将 sAddr 写入模型节点的 sAddr 字段,同时建立:

  • icd.ied.vec_saddr — sAddr 列表
  • icd.ied.map_saddr_point — sAddr → ModelNode 映射

对于 FC=SG 的点sAddr 后缀 _SGFC=SE 的点后缀 _SE


6. 控制子系统(mms_s_control.cpp

6.1 双回调模式

回调 阶段 职能
check_handler() Select / Interlock 权限校验,匹配合法 DO 则返回 CONTROL_ACCEPTED
control_handler() Operate 执行控制命令,更新 t时间戳+ stVal触发外部回调

6.2 控制执行流程

客户端 → Select Request → check_handler() → CONTROL_ACCEPTED
客户端 → Operate Request → check_handler() (interlock check) → control_handler()
  ├── IedServer_updateUTCTimeAttributeValue(t)
  ├── 匹配 sAddr → cb_control() → 通知应用层
  └── 根据 stVal 类型:
      ├── BOOLEAN → IedServer_updateAttributeValue()
      └── Dbpos   → IedServer_updateDbposValue()

6.3 控制 DO 发现

model_search_control_DataObjects() 遍历模型树,找到所有包含 ctlModel DA 的 DO存入 icd.ied.vec_control_docontrol_init() 遍历该列表,为每个安装 control_handlercheck_handler


7. 定值组子系统(mms_s_param.cpp

7.1 SG vs SE

FC 含义 mmsValue 加载时机
SG Setting Group — 当前激活定值区的实际值 激活定值组切换时加载
SE Setting Editable — 编辑缓冲区值 编辑定值组切换时加载

7.2 回调链

激活定值组切换  → param_active_sg_changed_handler() → param_load_active_sg_values()
编辑定值组切换  → param_edit_sg_changed_handler()   → param_load_edit_sg_values()
编辑确认       → edit_sg_confirmation_handler()     → 回读 SE 值 → cb_param()

7.3 值的加载与校验

  • 加载:根据 sAddr+"_SG"/"_SE" 在 map_saddr_point 中查找节点,按 MMS 类型分发更新函数
  • 回读:edit_sg_confirmation_handler() 读取 SE DA 当前值,做 min/max/step 校验,通过后触发外部回调 g_param_cb
  • 类型分发表:g_param_update_funcs[](写)和 g_param_get_funcs[](读),覆盖 BOOLEAN/INT/UINT/FLOAT/STRING

8. 定值子系统(mms_s_setting.cpp

8.1 与参数的差异

定值FC=SP不参与定值组切换是单一设置值。

8.2 写保护机制

  1. 全局访问策略:IedServer_setWriteAccessPolicy(IEC61850_FC_SP, ACCESS_POLICY_DENY)
  2. 精确放行:通过 IedServer_handleWriteAccess() 为已注册 sAddr 安装 writeAccessHandler
  3. writeAccessHandler 做值校验(范围+步长),通过后触发外部回调 cb_setting

8.3 校验流程

客户端写 SP 值 → writeAccessHandler()
├── 匹配 sAddr
├── 类型分发 (setting_get_BOOLEAN/INT/UINT/FLOAT/STRING)
│   └── setting_min_max_step_get()  → 读取同级 minVal/maxVal/stepSize
│       └── 范围校验 + 步长校验
├── cb_setting() → 通知应用层
└── return DATA_ACCESS_ERROR_SUCCESS → libiec61850 更新 mmsValue

9. 数据属性值初始化(mms_s_value.cpp

mms_s_values_init() 作为 IedModel.initializer 回调,在 IedServer 创建过程中由 libiec61850 自动调用。

初始化类型覆盖BOOLEAN, INT8U/32, FLOAT32, VisString*, Unicode*, Timestamp, Dbpos, Enum特殊按文本值或序号查找枚举值

Dbpos 特殊处理mms_s_da_value_init_Dbpos() 按 val 值映射到 DBPOS_INTERMEDIATE_STATE(0)/OFF(1)/ON(2)/BAD_STATE(>=3)。

9.1 值更新路径

mms_s_value_update() 是预留的值更新函数,通过 mms_s_value_update_register() 注册给外部。当数据中心信号变化时,上层调用此函数更新模型中的 DA 值,同时自动更新父 DO 的 t(时间戳)和 q(品质)。


10. 文件传输服务(mms_s_file.cpp

提供 MMS 文件传输基础能力:

  • 设置文件存储根目录 IedServer_setFilestoreBasepath()
  • 访问控制:禁止重命名、禁止删除 IEDSERVER.BIN
  • 连接事件日志

11. 后台运行线程

LOCAL void *mms_s_run_task(void *parameter)
{
    while(g_running) {
        IedServer_lockDataModel(gp_iedServer);
        IedServer_unlockDataModel(gp_iedServer);
        Thread_sleep(100);  // 100ms 周期
    }
    IedServer_stop(gp_iedServer);
    IedServer_destroy(gp_iedServer);
    IedModel_destroy(iedModel);
}

线程负责保护 IedServer 生命周期,通过 lock/unlock 持有模型锁保持服务活跃。收到 SIGINT 后 g_running=0,线程退出并销毁资源。


12. RCB 事件监听

rcbEventHandler() 监听客户端的报告控制块操作(mms_s.cpp:48

事件 含义
RCB_EVENT_ENABLE 客户端使能报告
RCB_EVENT_DISABLE 客户端关闭报告
RCB_EVENT_RESERVED 客户端预订 RCB
RCB_EVENT_UNRESERVED 客户端释放预订
RCB_EVENT_GI 总召触发
RCB_EVENT_SET_PARAMETER 客户端设置 RCB 参数(如 TrgOps 等)
RCB_EVENT_GET_PARAMETER 客户端获取 RCB 参数

特殊处理:当客户端设置 TrgOps 参数时,服务器强制追加 dchgrcb->trgOps |= 0x01),确保数据变化一定能触发报告上送。


13. 对外 API 汇总

API 文件 说明
mms_s_init(icd_path, port) mms_s.cpp 完整初始化 MMS 服务器
mms_s_dbg_switch(on) mms_s.cpp 调试开关
mms_s_get_icd_ptr() mms_s.cpp 获取 ICD 数据
mms_s_get_ied_server_ptr() mms_s.cpp 获取 IedServer
mms_s_control_register(...) mms_s_control.cpp 注册控制点
mms_s_setting_register(...) mms_s_setting.cpp 注册定值
mms_s_param_register(...) mms_s_param.cpp 注册参数(定值组)
mms_s_file_path_set(path) mms_s_file.cpp 设置文件根路径
mms_s_value_update_register(cb) mms_s_value.cpp 注册值更新回调
mms_s_get_string_by_mms_type(...) mms_s.cpp MMS 类型→字符串
mms_s_get_string_by_type(...) mms_s.cpp 自定义类型→字符串

14. 已知问题

14.1 check_handler 未调用 cb_interlock

mms_s_control.h 中声明了 mms_s_interlock_cbmms_s_set_interlock_cb(),但 check_handler() 中仅检查 ICD 中是否存在对应 DOcb_interlock 未被实际调用。外部注册的联锁检查回调不会生效。

14.2 param_min_max_step_get 实现不一致

mms_s_param.cppparam_min_max_step_get() 通过遍历 ModelNode 父子关系查找 minVal/maxVal/stepSize向上找到 DataObject 再遍历 firstChildmms_s_setting.cppsetting_min_max_step_get() 是通过 parent->firstChild 直接遍历。两者逻辑不同,前者更复杂(向上一级再到 DO可能是修正后的版本但后者仍保留了旧逻辑。

14.3 mms_s_value_update 未使用 g_iec61850s_value_init_cb_map

mms_s_value_update() 使用 g_iec61850s_value_update_cb_map(按 DataAttributeType 索引),但表中大部分类型回调为 NULL仅 BOOLEAN/INT32/INT32U/FLOAT32/Enum/VisString32/Unicode255/Timestamp 有实际实现。其他类型(如 INT8/INT16/INT64/FLOAT64 等)更新时会被跳过。