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

10 KiB
Raw Blame History

libmms_s 模块分析

日期: 2026-06-12
基于源码: src/protocol/libmms_s/16个文件约7500行


1. 模块定位

libmms_s 是 RTU 的 IEC 61850 MMS 服务端库,位于协议层。它解析 ICDIED Capability DescriptionXML 文件构建完整的数据模型,创建 IedServer 对外提供 MMS 服务,实现控制执行、定值组管理、文件服务等功能。

与上下层关系:

  • 下层: 依赖 libiec61850 v1.5.x 的 IedServer/IedModel API
  • 上层: 被 iec61850s(系统层封装)调用初始化,通过回调机制将控制/定值变更通知上层
  • 数据方向: 接收远方客户端的 MMS 读写请求,通过回调将控制指令传递给 iec61850sdatacenter

2. ICD 解析流程(mms_s_icd.cpp

ICD 文件是 SCLSubstation 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(子 SDOmap_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(所有 DOvec_control_do(控制 DOvec_saddr(信号地址列表),map_saddr_pointsAddr→模型节点映射

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 下创建 SGSetting Group和 SESetting Group EditSE 是 SG 的镜像:

  • SG 的 sAddr = 原始 sAddrPROT/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

SPSet Point类 DAfc=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. 关键日志宏

#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__)

带颜色、时间戳、文件名和行号。