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

8.7 KiB
Raw Blame History

libmms_m 模块分析

日期: 2026-06-12
基于源码: src/protocol/libmms_m/8个文件约3000行


1. 模块定位

libmms_m 是 RTU 的 IEC 61850 MMS 客户端库,位于协议层。它通过 libiec61850 库连接到远方 IED 装置,以事件驱动架构实现遥测/遥信读取All-Call、GI、遥控执行SBO/Direct、AO/参数读写、报告订阅等全部 MMS 客户端功能。

与上下层关系:

  • 下层: 依赖 libiec61850 v1.5.x 提供的 IedConnection API
  • 上层: 被 iec61850m(系统层封装)通过对外 API 调用
  • 并发: 每个 IED 连接一个独立线程 mms_m_run_thread,通过事件队列 + 信号量与其他线程通信

2. 核心数据结构

2.1 主对象 stru_mms_m_obj

每个 IED 连接创建一个 stru_mms_m_obj 实例,全局保存在 g_mms_m_obj_mapmap<int, obj*>)中,以 obj_fd 为句柄。

字段 用途
cfg_path 配置文件路径,用于错误日志和重复检测
ied_name IED 名称,从配置中提取
debug_print_flag 调试打印开关(MMS_M_DEBUG_PRINT_ON = 1
connectionTimeout 连接超时(毫秒)
p_cfg 指向 stru_cfg 的点表配置ST/MX/CO/AO/Param
ldevs LDevice 树LD→LN→DO→point_item 指针),用于 dataset member 匹配
ld_datasets 运行时发现的 LD→Dataset→Member→RCB 完整结构
rcb_numbers 可配置的 RCB 编号列表(如 {"01","02"}),空则默认 "01"
zone_saddr 绑定定值区信号的 saddr用于检测定值区切换
current_zone 当前定值区号
obj_fd 对象句柄,对外 API 用此标识
run 运行时状态(连接、定时器、事件队列、回调列表)

2.2 运行时结构 stru_mms_m_run

字段 用途
con IedConnection 句柄
con_state / old_con_state 连接状态(前后帧对比检测上线/离线)
ip / port 远方 IED 的 IP/端口
running_init ICD 初始化是否完成标志
event_queue 环形事件队列(容量 EVENT_QUEUE_SIZE
sem 信号量,保护事件队列的读写
timer[4] 四个定时器 T0-T3
pthread_task 工作线程句柄
out_cb_lists 数据回调函数列表,向上层推送读取到的值
out_status_cb 连接状态回调(上线/离线通知)

2.3 事件类型 _MMS_M_EVENT

_MMS_M_EVENT_ALL_CALL    // 全数据读取ST+MX
_MMS_M_EVENT_GI_CALL     // 总召
_MMS_M_EVENT_CO_SELECT   // 遥控选择
_MMS_M_EVENT_CO_DIRECT   // 遥控执行
_MMS_M_EVENT_CO_CANCEL   // 遥控取消
_MMS_M_EVENT_AO_READ     // 读取AO值
_MMS_M_EVENT_AO_WRITE    // 写入AO值
_MMS_M_EVENT_PARAM_READ  // 读取参数值
_MMS_M_EVENT_PARAM_WRITE // 写入参数值

2.4 定时器配置

定时器 间隔 触发动作
T0 120s 发送 All-Call → 全数据刷新
T1 60s 发送 GI → 总召
T2 30s 读取所有 AO + Param
T3 20s 预留(未使用)

定时器实现为倒计数(cnt--),在每次 mms_m_run 中检查。到零时触发回调并重置计数。

3. 核心流程

3.1 初始化流程

上层调用 mms_m_out_init(p_cfg, debug_flag, timeout)
  → 检查配置文件是否重复
  → new stru_mms_m_obj初始化基础字段
  → mms_m_ied_init(obj): 
      - 提取 ied_name, ip, port
      - 遍历 ST/MX/CO/AO/Param 点表
      - 调用 mms_m_add_point_to_ldevs 构建 LD→LN→DO→point 树形索引
      - 用于后续 dataset member 匹配
  → sem_init 信号量
  → pthread_create → mms_m_run_thread 启动工作线程
  → 返回 obj_fd

3.2 连接管理(mms_m_do_comm

mms_m_run_thread 主循环(每 MMS_M_THREAD_RUN_TM 毫秒):
  → mms_m_do_comm:
      比较 con_state 与 old_con_state:
      - 非连接状态: IedConnection_connectAsync 异步重连
      - 新上线 (old != CONNECTED, new == CONNECTED):
          mms_m_control_init() 重建 ControlObjectClient
          回调 out_status_cb(ON_LINE)
      - 新离线 (old == CONNECTED, new != CONNECTED):
          running_init = false  // RCB 需重新订阅
          回调 out_status_cb(OFF_LINE)
      old_con_state = con_state
  → if CONNECTED: mms_m_run(obj)

3.3 运行流程(mms_m_run

mms_m_run(obj):
  → mms_m_run_init(obj):  // 仅首次执行
      mms_m_icd_init(obj):
        - 遍历 LD → LN通过 IedConnection_getLogicalDeviceList/Directory
        - 对每个 LN: mms_m_icd_dataset_init获取所有 DataSet + Members
        - 对每个 LN: mms_m_icd_report_initURCB + BRCB按 rcb_numbers 过滤)
      mms_m_ld_dataset_match_point_init(obj):
        - 遍历所有 dataset member
        - 解析 ref 得到 LD/LN/DO 名称
        - 在 ldevs 树中查找匹配的 DO建立 member.p_do_vec 指针关系
        - 报告回调时通过此关系快速定位到具体信号点
      mms_m_rcb_init(obj):
        - 获取 RCB 值Resv→TrgOps→RptEna→GI
        - 安装 mms_m_report_callback
        - 设置 TrgOps = dchg | qchg | GI
  → mms_m_do_send(obj): 处理事件队列中的操作
  → mms_m_timer_running(obj): 检查四个定时器

3.4 All-Call 流程

T0 定时器到期 → mms_m_do_call_all:
  → 推送 _MMS_M_EVENT_ALL_CALL 事件
  → 重置 T0

mms_m_do_send → mms_m_send_call_all:
  遍历 ST 和 MX 点表:
    IedConnection_readObject(p_con, point.reference, fc)
    → mms_m_get_mmsValue: MMS 类型转换 (BOOLEAN→uint8, INTEGER→int32, UNSIGNED→uint32, FLOAT→float, BIT_STRING→quality)
    → 构造 out_val { name, desc, reference, time, value, quality, reason }
    → mms_m_put_value: 遍历 out_cb_lists 执行每个回调

3.5 遥控流程SBO/Direct

上层调用 mms_m_out_do_set_yk(event):
  → 按 app_fd 或 ied_name 定位 obj
  → mms_m_push_event

mms_m_do_send → mms_m_send_co:
  switch ctrl_type:
    _MMS_M_EVENT_CO_SELECT:  ControlObjectClient_select
    _MMS_M_EVENT_CO_DIRECT:  ControlObjectClient_operate
    _MMS_M_EVENT_CO_CANCEL:  ControlObjectClient_cancel
  → mms_m_send_set_callback: 设置操作完成回调

3.6 AO/Param 读写

AO 读取mms_m_send_read_ao
  → 按 saddr 或全表读取
  → IedConnection_readObject → 类型转换 → 回调推送

Param 写入mms_m_send_param_write
  → 检测定值区是否切换(对比 current_zone
  → 定值区变化: 
      读取 SG 信息 → select EditSG → 写入 SE 值 → confirm CnfEdit
  → 定值区不变:
      直接写入 SE 值 → confirm
  → 操作完成回调

3.7 报告回调(mms_m_report_callback

IedConnection 收到 report → mms_m_report_callback:
  → 遍历 dataset members
  → 检查 ReasonForInclusion非 NOT_INCLUDED 才处理)
  → mms_m_get_MmsValue 解析数据值和时标
  → 通过 member.p_do_vec 找到所有绑定的信号点
  → 构造 out_val → 遍历 out_cb_lists 推送

4. 对外 API

API 说明
mms_m_out_init(p_cfg, dbg, timeout) 创建 IED 连接,返回 obj_fd
mms_m_out_get_connect_status(fd, cb) 注册连接状态回调
mms_m_out_debug_print_swicth(fd, flag) 开关调试打印
mms_m_out_do_set_yk(event) 下达遥控命令Select/Direct/Cancel
mms_m_out_read_ao_or_params(fd, type, saddr) 读取 AO/Param 值
mms_m_out_get_value(fd, cb) 注册数据回调
mms_m_out_bind_param_zone_signal(fd, saddr) 绑定定值区信号
mms_m_out_set_rcb_numbers(fd, numbers) 设置 RCB 订阅编号
mms_m_create_data_ptr(type) 创建类型对应的数据指针
mms_m_set_data_value(src, dst, type) 类型化数据赋值
mms_m_get_data_value_str(data, type, str) 类型化数据转字符串
mms_m_set_data_by_str(data, type, str) 字符串转类型化数据
mms_m_out_reason_str(reason) 原因码转字符串
mms_m_dbg_get(obj) 获取调试标志
mms_m_get_obj(fd) 按句柄获取对象指针

5. 线程安全

  • 事件队列 push/popsem_wait/sem_post 保护
  • out_cb_lists 在初始化时注册,运行时只读
  • con_state/old_con_state 仅在主线程(mms_m_run_thread)修改
  • libiec61850 的 report callback 在其他线程触发,通过 mms_m_push_event 转入主线程处理

6. 已知问题

  1. 定时器倒计数精度: 定时器基于循环次数×休眠时间,非高精度。取决于 MMS_M_THREAD_RUN_TM 大小
  2. 事件队列溢出: 容量 EVENT_QUEUE_SIZE,环形覆盖不报错
  3. RCB 编号过滤在 report_init 中执行: 一旦初始化完成,变更 rcb_numbers 需重建连接
  4. 离线时 running_init 重置: 重连后整个 ICD 发现流程重新执行