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

28 KiB
Raw Permalink Blame History

libmms_m 模块工程文档

概述

libmms_m 是 RTU 项目中 IEC 61850 MMS 客户端的封装库,位于 src/protocol/libmms_m/,负责将 libiec61850 的 C 客户端 API 封装成 RTU 内部的事件驱动架构。

核心职责

  1. 连接管理:通过异步方式与远端 IED 建立/维护 MMS 连接
  2. 报告接收:通过 Report 回调接收 IED 主动上送的变化数据
  3. 数据操作总召、总召遥信遥测、AO 读写、参数(定值)读写
  4. 控制指令遥控CO的 Select/Operate/Cancel 操作
  5. 扩展服务:文件传输、服务器信息查询、定值组读取

文件结构

src/protocol/libmms_m/
├── inc/
│   ├── mms_m.h              ← 核心头文件日志宏、数据结构、API 声明)
│   ├── mms_m_ext.h          ← 扩展功能接口声明(文件/服务器/定值组)
│   └── mms_m_errstr.h       ← 错误码转字符串
└── src/
    ├── mms_m.cpp            ← 核心实现(连接/报告/总召/遥控/参数)
    ├── mms_m_errstr.cpp     ← 错误码/控制模型/AddCause 转字符串
    ├── mms_m_file.cpp       ← 文件服务实现
    ├── mms_m_server.cpp     ← 服务器身份/状态查询
    └── mms_m_sg.cpp         ← 定值组信息读取

基础类型定义位于 release/inc/myMms_m.h


1. 核心数据结构

1.1 全局对象管理

static std::map<int, stru_mms_m_obj *> g_mms_m_obj_map;

所有客户端实例通过全局 map 管理key 为 app_fd(客户端句柄,从 1 开始自增value 为对象指针。

1.2 主对象结构 stru_mms_m_obj

typedef struct {
    int obj_fd;                            // 客户端句柄(自增)
    uint32_t connectionTimeout;            // 连接超时 [毫秒]
    std::string cfg_path;                  // 配置文件路径
    stru_cfg *p_cfg;                       // XML 配置解析结果

    int debug_print_flag;                  // 调试打印开关0/1

    std::string ied_name;                  // IED 名称
    MmsValue *param_zone;                  // 定值区选择值MmsValue, 可复用)
    MmsValue *set_confirm;                 // 定值确认值boolean, 可复用)

    MMS_STR zone_saddr;                    // 关联定值区的信号 saddr如 "ao.0"
    int current_zone;                      // 当前定值区号

    stru_mms_m_run run;                    // 运行时数据
    std::vector<stru_ldev> ldevs;          // 逻辑设备树LD→LN→DO→Point
    std::vector<stru_ld_dataset> ld_datasets; // 数据集和报告配置
} stru_mms_m_obj;

1.3 运行时结构 stru_mms_m_run

typedef struct {
    std::string ip;                        // 服务器 IP
    int port;                              // 服务器端口
    bool running_init;                     // 是否已完成初始化

    sem_t sem;                             // 回调同步信号量
    pthread_t pthread_task;              // 工作线程

    IedConnection con;                     // libiec61850 连接句柄
    IedConnectionState con_state;          // 当前连接状态
    IedConnectionState old_con_state;      // 上一次连接状态

    stru_mms_m_timer timer[_MMS_M_TIMER_END]; // 4 个定时器

    stru_mms_m_event event;                // 当前处理的事件
    stru_event_queue event_queue;          // 事件队列(容量 64
    // 事件类型(按枚举):
    // [select] [operate] [cancel] ─→ 遥控
    // [mms_m_send_co_select|direct|cancel]
    // 通过 IedConnection_setRCBValues 发送总召
    mms_m_out_status_cb out_status_cb;     // 连接状态回调
    std::vector<mms_m_out_value_cb> out_cb_lists; // 数据输出回调列表
} stru_mms_m_run;

1.4 数据模型层次结构

stru_mms_m_obj
├── ldevs[]                             ← 逻辑设备列表
│   └── stru_ldev
│       ├── ld_name                     ← LD 名称(如 "TEMPLATE"
│       └── lnodes[]                    ← 逻辑节点列表
│           └── stru_lnode
│               ├── ln_name             ← LN 名称(如 "GGIO1"
│               └── dobjs[]             ← 数据对象列表
│                   └── stru_dobj
│                       ├── do_name     ← DO 名称(如 "Ind1"
│                       └── p_do_vec[]  ← 指向 stru_point_item 的指针集合
│
└── ld_datasets[]                       ← 数据集/报告配置
    └── stru_ld_dataset
        ├── ref                         ← LD/LN 引用
        ├── ld_name                     ← LD 名称
        ├── ln_name                     ← LN 名称
        ├── ln_datasets[]               ← 数据集列表
        │   └── stru_ln_dataset
        │       ├── dataset_name        ← 数据集全名
        │       ├── dataset_ref         ← 数据集引用 ($格式)
        │       └── members[]           ← 成员列表
        │           └── stru_member
        │               ├── ref         ← FCDA 引用
        │               ├── reason      ← 包含原因
        │               └── p_do_vec    ← 关联的点集合
        └── ln_rpts[]                   ← 报告列表
            └── stru_ln_rpt
                ├── rpt_ref             ← RCB 引用
                ├── rpt_ref_with_no     ← RCB 引用+编号
                ├── ds_ref              ← 关联数据集引用
                ├── type                ← URCB 或 BRCB
                ├── rcb                 ← ClientReportControlBlock 句柄
                ├── p_app               ← 指向所属 mms_m_obj
                └── p_dataset           ← 指向关联数据集

1.5 事件与定时器

// 事件类型
enum {
    _MMS_M_EVENT_ALL_CALL,    // 总召遥信遥测
    _MMS_M_EVENT_GI_CALL,     // 总召GI
    _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, // 写入参数
    _MMS_M_EVENT_END
};

// 事件结构
typedef struct {
    int app_fd;                    // 客户端句柄
    MMS_STR ied;                   // IED 名称
    MMS_STR saddr;                 // 短地址
    uint8_t value_type;            // MMS 数据类型
    char val[MMS_M_DATA_STRING_LEN]; // 值(字符串形式)
    uint8_t ctrl_type;             // 事件类型
    int set_zone;                  // 定值区号
    void (*p_func)(void *arg, int ret); // 完成回调
} stru_mms_m_event;

// 事件队列(环形缓冲区,容量 64
typedef struct {
    uint8_t w_ptr;                 // 写指针
    uint8_t r_ptr;                 // 读指针
    uint8_t size;                  // 容量
    uint8_t num;                   // 当前数量
    stru_mms_m_event event[EVENT_QUEUE_SIZE];
} stru_event_queue;

// 4 个定时器
enum { _MMS_M_TIMER_T0, _MMS_M_TIMER_T1, _MMS_M_TIMER_T2, _MMS_M_TIMER_T3, _MMS_M_TIMER_END };

// 定时器时间定义
#define MMS_M_THREAD_RUN_TM (100 * 3)    // 线程循环间隔300ms
#define MMS_M_TIMER_T0  (120 * 3)        // T0: 120s总召所有数据
#define MMS_M_TIMER_T1  (60 * 3)         // T1: 60sGI 触发)
#define MMS_M_TIMER_T2  (30 * 3)         // T2: 30sAO + 参数读取)
#define MMS_M_TIMER_T3  (20 * 3)         // T3: 20s预留未使用

2. 线程模型与主流程

2.1 生命周期

mms_m_out_init()
  ├── 分配 stru_mms_m_obj
  ├── mms_m_ied_init()       ← 构建 ldevs 树
  ├── sem_init()             ← 初始化信号量
  ├── pthread_create(mms_m_run_thread) ← 创建工作线程
  └── 加入 g_mms_m_obj_map[id]

mms_m_run_thread()             ← 工作线程入口
  ├── mms_m_timer_init()     ← 初始化4个定时器
  ├── 初始化事件队列
  ├── IedConnection_create()
  ├── IedConnection_installStateChangedHandler()
  ├── IedConnection_connectAsync()
  └── 主循环300ms 周期)
      ├── mms_m_do_comm()     ← 连接状态机
      └── if CONNECTED → mms_m_run()
           ├── mms_m_run_init()    ← 首次建连时执行
           ├── mms_m_do_send()     ← 处理事件队列
           └── mms_m_timer_running() ← 检查定时器

2.2 连接状态机 mms_m_do_comm()

状态检查: IedConnection_getState()
                                   
CLOSED/CLOSING → connectAsync()    ← 自动重连
CONNECTING →    等待                 
CONNECTED →     [首次] mms_m_control_init()  ← 创建 ControlObjectClient
                [首次] 触发 out_status_cb(ON_LINE)
                
断连检测: old=CONNECTED, new!=CONNECTED
  → running_init = false            ← 标记需要重新初始化
  → 触发 out_status_cb(OFF_LINE)

2.3 首次连接初始化 mms_m_run_init()

1. mms_m_icd_init()
   ├── getLogicalDeviceList()       ← 获取 LD 列表
   ├── 遍历每对 LD+LN:
   │   ├── mms_m_icd_dataset_init()     ← 读取该 LN 下的所有 DataSet
   │   │   └── getDataSetDirectory()    ← 读数据集成员
   │   ├── mms_m_icd_report_init(URCB)  ← 发现 URCB 报告
   │   └── mms_m_icd_report_init(BRCB)  ← 发现 BRCB 报告
   └── ld_datasets 构建完成

2. mms_m_ld_dataset_match_point_init()
   └── 将数据集成员与 ldevs 中的 point 关联(构建 p_do_vec

3. mms_m_rcb_init()
   ├── getRCBValues()               ← 获取每个 RCB 当前值
   ├── 匹配数据集引用 → p_dataset
   ├── setResv(true)                ← 预留 RCB
   ├── setTrgOps(dchg|qchg|gi)
   ├── setRptEna(true)              ← 使能报告
   ├── installReportHandler()       ← 安装回调
   ├── setRCBValues()               ← 写入配置到服务器
   └── setGI(true)                  ← 触发一次总召

3. 数据流程

3.1 周期性总召T0 定时器)

mms_m_do_call_all()       ← 每 120s 触发
  └── push _MMS_M_EVENT_ALL_CALL 事件
      └── mms_m_send_call_all()
          └── 遍历 ST + MX 数据点
              ├── IedConnection_readObject()  ← 逐一读取
              ├── mms_m_get_mmsValue()        ← MmsValue→C 类型
              ├── mms_m_put_value()           ← 通过回调输出
              └── MmsValue_delete()            ← 清理

3.2 报告控制块RCB订阅全流程

RCB 订阅是 MMS 客户端最核心的机制,负责接收 IED 主动上送的数据变化。整个流程分为发现→匹配→激活→接收四个阶段。

3.2.1 阶段一:发现 RCBmms_m_icd_init → mms_m_icd_report_init

连接成功后,遍历所有 LD→LN对每对调用 IedConnection_getLogicalNodeDirectory() 分别查询两类 RCB

// 查询 URCB (unbuffered引用名含 "RP")
mms_m_icd_report_init(obj, ld_dataset, ACSI_CLASS_URCB);
// 查询 BRCB (buffered引用名含 "BR")
mms_m_icd_report_init(obj, ld_dataset, ACSI_CLASS_BRCB);

mms_m_icd_report_init() 的核心筛选逻辑:

std::string rpt_ref_str = (URCB == acsiClass) ? "RP" : "BR";

LinkedList reports = IedConnection_getLogicalNodeDirectory(
    p_con, &err, ld_ds.ref.c_str(), acsiClass);
LinkedList report = LinkedList_getNext(reports);

while (report != NULL) {
    std::string rpt_data = (char*) report->data;

    // 提取名称和编号最后2位是编号前面是名称
    rpt_name = rpt_data.substr(0, rpt_data.length() - 2);
    rpt_no   = rpt_data.substr(rpt_data.length() - 2);

    rpt_ref = ld_ds.ref + "." + rpt_ref_str + ".";

    if (0 == rpt_no.compare("01"))  // 只取编号 "01"
    {
        ln_rpt.rpt_ref = rpt_ref + rpt_name;
        // 例: "TEMPLATE/LLN0.RP.EventsRCB"
        ln_rpt.rpt_ref_with_no = rpt_ref + rpt_name + rpt_no;
        // 例: "TEMPLATE/LLN0.RP.EventsRCB01"
        ld_ds.ln_rpts.push_back(ln_rpt);
    }
    // else: 其他编号直接丢弃

    report = LinkedList_getNext(report);
}

限制:只订阅编号末尾为 "01" 的 RCB 实例。

3.2.2 阶段二匹配数据集mms_m_ld_dataset_match_point_init

将 XML 配置加载的 ldevs 信号树与从 IED 发现的 ld_datasets 数据集树进行关联,使得每个 dataset member 知道对应哪些信号点saddr

数据集 member 的 FCDA 引用示例: "IEDNAME+TEMPLATE/GGIO1.ST.Ind1.stVal[ST]"
                                    ↓ 解析
  ld  = "IEDNAME+TEMPLATE" → 去 IED 前缀 → "TEMPLATE"
  ln  = "GGIO1"
  d_name = "Ind1"
                                    ↓ 匹配 ldevs 树
  ldevs[ld="TEMPLATE"] → lnodes[ln="GGIO1"] → dobjs[d_name="Ind1"]
                                    ↓
  member.p_do_vec = &dobjs.p_do_vec   // 建立关联

如果匹配不到(找不到对应的 LD/LN/DOmember.p_do_vec 为 NULL
该 member 的报告数据将无法输出到任何信号点。

引用字符串解析规则(mms_m.cpp:1228-1257

  • 第1段/LD 名称,需去除 IED 名前缀
  • 第2段.LN 名称
  • 第3段[DO 名称
  • 不匹配此格式的 member 直接跳过

3.2.3 阶段三:配置并激活 RCBmms_m_rcb_init

对每个已发现的 RCB执行完整的配置和激活流程mms_m.cpp:1158-1214

┌─ 步骤1getRCBValues(rcb_ref)             读服务端当前 RCB 值
│
├─ 步骤2getDataSetReference(rcb)           获取 RCB 当前关联的数据集
│           ↓ ds_ref ← "TEMPLATE/LLN0$Events"
├─ 步骤3匹配本地数据集
│           遍历 ln_datasets通过 dataset_ref 匹配
│           ln_rpt.p_dataset = &matched_dataset
│
├─ 步骤4本地设置 RCB 参数
│           setResv(true)                    预留 RCBURCB
│           setTrgOps(dchg | qchg | gi)     触发条件:数据变化+品质变化+总召
│           setDataSetReference(ds_ref)      确认数据集引用
│           setRptEna(true)                  使能报告
│           ⚠ 未调用 setOptFlds()           完全依赖服务端默认值
│
├─ 步骤5installReportHandler()             安装报告回调
│           rcbReference = rpt_ref           如 "LD/LLN0.RP.EventsRCB"
│           rptId         = 从 RCB 获取
│           handler       = mms_m_report_callback
│           parameter     = &ln_rpt          回传 RCB 上下文
│
├─ 步骤6setRCBValues()                     写入服务端
│           parametersMask = RCB_ELEMENT_RPT_ENA |
│                            RCB_ELEMENT_TRG_OPS  |
│                            RCB_ELEMENT_INTG_PD  |
│                            RCB_ELEMENT_GI
│           singleRequest = true
│           ⚠ mask 包含 INTG_PD 但未调用 setIntgPd()
│           ⚠ mask 不含 OPT_FLDSOptFlds 沿用服务端默认
│
└─ 步骤7触发总召
            setGI(true) → setRCBValues(mask=RCB_ELEMENT_GI)
            让 IED 立即上送一次完整数据

步骤 6 的潜在问题

  • parametersMask 中包含 RCB_ELEMENT_INTG_PD,但本地并未调用 setIntgPd(),写入的 IntgPd 值实际是步骤1从服务端读取的原始值
  • RCB_ELEMENT_OPT_FLDS 未包含在 mask 中,意味着不会设置服务端的 OptFlds完全依赖服务端默认配置 — 可能导致报告缺少 DataReferenceConfRevBufferOverflow 等字段
  • 使用 singleRequest=true,一次 MMS 写请求携带多个变量;如果服务端兼容性不好,可能需要改为 false

3.2.4 阶段四报告接收与分发mms_m_report_callback

当 IED 发送报告时libiec61850 回调 mms_m_report_callback()mms_m.cpp:1079-1153

ClientReport 到达
  │ parameter = &ln_rpt                        ← 阶段三步骤5传入的回调参数
  │
  ├── ClientReport_getDataSetValues(report)   → MmsValue* (MMS_ARRAY)
  │
  └── 遍历 dataset->members按数据集顺序index 从 0 递增)
      │
      ├── reason = ClientReport_getReasonForInclusion(report, index)
      │
      ├── if (reason == NOT_INCLUDED) → 跳过该成员
      │
      ├── mms_value = MmsValue_getElement(dataset_value, index)
      │     │
      │     └── mms_m_get_MmsValue(point_value, out_value, mms_value, ...)
      │           递归解包 MmsValue → C 类型
      │           ├── MMS_STRUCTURE/ARRAY     → 递归展开子元素
      │           ├── MMS_BOOLEAN              → *(uint8_t*)p_val
      │           ├── MMS_INTEGER              → *(int32_t*)p_val
      │           ├── MMS_UNSIGNED             → *(uint32_t*)p_val
      │           ├── MMS_FLOAT                → *(float*)p_val
      │           ├── MMS_BIT_STRING           → MmsValue_getBitStringAsIntegerBigEndian()
      │           ├── MMS_UTC_TIME             → 解析时间 → point_value.time
      │           └── MMS_BINARY_TIME          → 解析时间 → point_value.time
      │
      ├── 如果 mms_value 含时间戳 → tm_flag=1 → 使用报告中的时间
      │   否则 → tm_flag=0 → mms_m_get_local_time() 使用本地时间
      │
      └── 通过 member.p_do_vec 遍历关联的信号点
          for (each point_item in p_do_vec)
              out_value.name      = point.saddr
              out_value.desc      = point.desc
              out_value.reference = point.reference
              out_value.reason    = reason
              out_value.p_value   = 解析后的值指针
              out_value.time      = 时间(报告时间或本地时间)
              out_value.app_fd    = obj.obj_fd
              ↓
              mms_m_put_value(obj, out_value)   // 回调所有注册的 out_cb

数据输出链路mms_m_put_value() → 遍历 run.out_cb_lists → 回调上层注册的数据处理函数(如 libiec61850m 中的 mms_data_back)。

3.2.5 断连后的重新订阅

连接状态机在检测到断连时(mms_m.cpp:1579-1588

if (run.con_state != CONNECTED && run.old_con_state == CONNECTED) {
    obj.run.running_init = false;     // 清除已初始化标志
    out_status_cb(obj.obj_fd, OFF_LINE);
}

下一次 mms_m_run() 检测到 running_init==false 且已重连时,自动重新执行完整的四阶段订阅流程(阶段一→二→三),确保断连恢复后重新订阅所有 RCB。

3.2.6 GI 定时触发

在阶段三完成首次订阅后,定时器 T160s周期性触发 GI 总召(mms_m.cpp:1636-1649

// 每 60s 一次
event.ctrl_type = _MMS_M_EVENT_GI_CALL;
mms_m_push_event(obj, event);

// → mms_m_send_set_gi()
//   遍历所有 RCB → setGI(true) → setRCBValues(mask=RCB_ELEMENT_GI)

3.2.7 流程总览图

首次连接 / 断连重连
    │
    ▼
阶段一 ✦ 发现 ──────────────────────────────────────
mms_m_icd_init() → mms_m_icd_report_init()
    遍历 LD→LN → getLogicalNodeDirectory(URCB/BRCB)
    └── 筛选:只取编号末尾为 "01" 的 RCB
        产出: ld_datasets[].ln_rpts[] (RCB 列表)
    │
    ▼
阶段二 ✦ 匹配 ──────────────────────────────────────
mms_m_ld_dataset_match_point_init()
    解析 dataset member 的 FCDA 引用 (LD/LN/DO)
    └── 匹配 ldevs 信号树 → member.p_do_vec 关联
    │
    ▼
阶段三 ✦ 激活 ──────────────────────────────────────
mms_m_rcb_init()
    对每个 RCB:
    ├── getRCBValues()        读取服务端 RCB
    ├── setResv/setTrgOps/setRptEna  配置参数
    ├── installReportHandler() 安装回调
    ├── setRCBValues()         写入服务端
    └── setGI(true)            触发总召
    │
    ▼ (此后异步回调)
阶段四 ✦ 接收 ──────────────────────────────────────
mms_m_report_callback()
    ClientReport 到达 → 遍历 dataset members
    ├── 跳过 NOT_INCLUDED 成员
    ├── 递归解包 MmsValue → C 类型
    ├── 解析时间戳
    └── p_do_vec → mms_m_put_value() → 上层回调

周期性触发:
    T1 (60s) → GI ─────────────────────────────────→ 阶段四再次触发
    T0 (120s) → AllCall                               读取全部 ST+MX 值

3.3 遥控流程

外部调用 mms_m_out_do_set_yk(event)
  └── push 遥控事件到事件队列
      └── mms_m_send_co()                  ← 处理遥控事件
          ├── 按 saddr 匹配 co_point
          ├── 首次: ControlObjectClient_create()
          │   └── setOrigin(ORCAT_STATION_CONTROL)
          ├── 构造 set_value (MmsValue_newBoolean)
          └── 按 ctrl_type 分发:
              ├── SELECT → mms_m_send_co_select()
              │   ├── SBO_NORMAL:     select()
              │   └── SBO_ENHANCED:   selectWithValue()
              │       └── 先 setCommandTerminationHandler()
              ├── DIRECT → mms_m_send_co_direct()
              │   ├── operate()
              │   └── 按 ctrl_model 检查结果:
              │       ├── DIRECT_NORMAL:   立即回读 stVal 验证
              │       ├── DIRECT_ENHANCED: 等待 1s 后回读验证
              │       └── SBO_ENHANCED:    等待 1s不验证
              └── CANCEL → mms_m_send_co_cancel()

3.4 参数(定值)写入流程

_PARAM_WRITE 事件
  └── mms_m_send_param_write()
      ├── 匹配参数点
      ├── 构造 EditSG 引用: {LD}/LLN0.SGCB.EditSG
      ├── 构造 CnfEdit 引用: {LD}/LLN0.SGCB.CnfEdit
      ├── 步骤1: writeObject(EditSG, FC=SP)        ← 选择编辑定值组
      ├── 步骤2: writeObject(ref, FC=SE)            ← 写入新定值
      └── 步骤3: writeObject(CnfEdit, FC=SP)       ← 确认编辑

3.5 定值区自动切换

mms_m_send_read_ao() 中检测:
  如果某个 AO 信号的 saddr == zone_saddr绑定的定值区信号
    → 读值后比较 current_zone
    → 若变化 (new_zone != current_zone):
        update current_zone
        push _MMS_M_EVENT_PARAM_READ 事件   ← 自动触发参数重读

4. 对外接口

4.1 myMms_m.h 中声明的主要 API

// === 生命周期 ===
int  mms_m_out_init(stru_cfg *p_cfg, int debug_print_flag, uint32_t connectionTimeout);
// 返回 app_fd (>0 成功,-1 失败)

// === 状态回调 ===
int  mms_m_out_get_connect_status(int fd, mms_m_out_status_cb p_func);
// 连接/断开时回调: void (int app_fd, int status) // MMS_M_ON_LINE / MMS_M_OFF_LINE

// === 数据回调 ===
int  mms_m_out_get_value(int fd, mms_m_out_value_cb p_func);
// 数据到达时回调: void (stru_mms_m_out_value *p_value)

// === 遥控 ===
int  mms_m_out_do_set_yk(stru_mms_m_event *p_event);
// 发送遥控事件select/direct/cancel返回 -1 失败

// === AO/参数操作 ===
int  mms_m_out_read_ao_or_params(int app_fd, uint8_t type, const char *saddr);
// type: _MMS_M_EVENT_AO_READ 或 _MMS_M_EVENT_PARAM_READ
// saddr 为 NULL 则读取全部

// === 定值区绑定 ===
int  mms_m_out_bind_param_zone_signal(int app_fd, const char *zone_saddr);
// 绑定一个 AO 信号作为定值区指示器

// === 调试 ===
int  mms_m_out_debug_print_swicth(int id, int debug_print_flag);

// === 工具函数 ===
void *mms_m_create_data_ptr(uint8_t type);          // 按 MMS 类型创建数据指针
int   mms_m_set_data_value(void *srt, void *dst, uint8_t type);   // 复制值
int   mms_m_get_data_value_str(void *data, uint8_t type, char *str); // 转字符串
int   mms_m_set_data_by_str(void *data, uint8_t type, const char *str); // 从字符串设置
char *mms_m_out_reason_str(int reason);              // 原因码转字符串
void *mms_m_get_obj(int app_fd);                     // 获取对象(给扩展模块)

4.2 扩展接口mms_m_ext.h

// 文件服务
typedef void (*mms_m_file_read_cb)(int fd, const char *fn, const uint8_t *d, int len, bool mf, int e);
typedef void (*mms_m_file_op_cb)(int fd, const char *fn, int e);
int mms_m_read_file(int fd, const char *rf, mms_m_file_read_cb cb);
int mms_m_delete_file(int fd, const char *rf, mms_m_file_op_cb cb);

// 服务器信息查询
typedef void (*mms_m_server_cb)(int fd, const char *vendor, const char *model, const char *rev, int log_st, int phy_st, int e);
int mms_m_query_server(int fd, mms_m_server_cb cb);

// 定值组信息
typedef void (*mms_m_sg_cb)(int fd, const char *ld, int act_sg, int num_sg, int e);
int mms_m_read_sg_info(int fd, const char *ld, mms_m_sg_cb cb);

4.3 扩展回调类型汇总

回调类型 签名 用途
mms_m_out_status_cb void(int fd, int status) 连接状态
mms_m_out_value_cb void(stru_mms_m_out_value *p_value) 数据到达
mms_m_file_read_cb void(int, const char*, const uint8_t*, int, bool, int) 文件分块读取
mms_m_file_op_cb void(int fd, const char* filename, int err) 文件删除结果
mms_m_server_cb void(int, const char*, const char*, const char*, int, int, int) 服务器信息
mms_m_sg_cb void(int, const char*, int act_sg, int num_sg, int err) 定值组信息

5. 错误码mms_m_errstr.cpp

提供四种枚举值到字符串的映射:

函数 映射
mms_m_err_str(IedClientError) IED 客户端错误30 个映射)
mms_m_control_err_str(ControlLastApplError) 控制应用错误4 个映射)
mms_m_ctl_add_cause_str(ControlAddCause) 控制附加原因28 个映射)
mms_m_ctrl_model_str(ControlModel) 控制模型5 个映射)

6. 文件服务mms_m_file.cpp

实现状态

功能 状态 使用的 API
文件打开 已实现 MmsConnection_fileOpen()
分块异步读取 未完成TODO 待实现 MmsConnection_fileReadAsync()
文件删除 已实现 MmsConnection_fileDelete()

调用方式

所有文件操作通过 IedConnection_getMmsConnection() 获取底层 MmsConnection 句柄,然后调用 MMS 低层文件 API。


7. 设计要点

  1. 事件驱动:所有对外操作通过事件队列异步执行,避免阻塞调用线程
  2. 自动重连:连接断开后自动重试连接,连接恢复后重新初始化报告
  3. 数据结构三棵树
    • ldevs:按 LD→LN→DO 组织的信号点树(用于信号匹配输出)
    • ld_datasets/ln_rpts:数据集和 RCB 树(用于报告接收和 GI 触发)
    • g_mms_m_obj_map:多客户端实例管理
  4. 定值区自动跟踪:通过绑定 zone_saddr自动检测定值区变化并重读参数
  5. 调试标志:全局 debug_print_flag 控制日志输出详细程度
  6. RCB 激活配置缺陷
    • setRCBValuesparametersMask 包含 RCB_ELEMENT_INTG_PD 但本地未调用 setIntgPd(),写入的是从服务端读回的旧值
    • RCB_ELEMENT_OPT_FLDS 未包含在 mask 中OptFlds 完全依赖服务端默认值,可能导致报告缺少 DataReferenceConfRevBufferOverflow 等字段
    • mms_m_icd_report_init 只取编号 "01" 的 RCB其他编号直接丢弃