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

672 lines
28 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 全局对象管理
```cpp
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`
```cpp
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`
```cpp
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 事件与定时器
```cpp
// 事件类型
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
```cpp
// 查询 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()` 的核心筛选逻辑:
```cpp
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](src/protocol/libmms_m/src/mms_m.cpp#L1228-L1257)
- 第1段`/` 前LD 名称,需去除 IED 名前缀
- 第2段`.` 前LN 名称
- 第3段`[` 前DO 名称
- 不匹配此格式的 member 直接跳过
#### 3.2.3 阶段三:配置并激活 RCBmms_m_rcb_init
对每个已发现的 RCB执行完整的配置和激活流程[mms_m.cpp:1158-1214](src/protocol/libmms_m/src/mms_m.cpp#L1158-L1214)
```
┌─ 步骤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完全依赖服务端默认配置 — 可能导致报告缺少 `DataReference`、`ConfRev`、`BufferOverflow` 等字段
- 使用 `singleRequest=true`,一次 MMS 写请求携带多个变量;如果服务端兼容性不好,可能需要改为 `false`
#### 3.2.4 阶段四报告接收与分发mms_m_report_callback
当 IED 发送报告时libiec61850 回调 `mms_m_report_callback()`[mms_m.cpp:1079-1153](src/protocol/libmms_m/src/mms_m.cpp#L1079-L1153)
```
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](src/protocol/libmms_m/src/mms_m.cpp#L1579-L1588)
```cpp
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](src/protocol/libmms_m/src/mms_m.cpp#L1636-L1649)
```cpp
// 每 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
```cpp
// === 生命周期 ===
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
```cpp
// 文件服务
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 激活配置缺陷**
- `setRCBValues``parametersMask` 包含 `RCB_ELEMENT_INTG_PD` 但本地未调用 `setIntgPd()`,写入的是从服务端读回的旧值
- `RCB_ELEMENT_OPT_FLDS` 未包含在 mask 中OptFlds 完全依赖服务端默认值,可能导致报告缺少 `DataReference`、`ConfRev`、`BufferOverflow` 等字段
- `mms_m_icd_report_init` 只取编号 `"01"` 的 RCB其他编号直接丢弃