BRCB 缓存报告机制全解析

BRCB 缓存报告机制全解析

1. 总体架构

BRCB (Buffered Report Control Block) 报告有三层:

应用层:  iec61850_put_data() → writedata_*() → mvlu_rpt_va_change2()
                                                              │
                                                              ▼
缓存层:  mvl61850_brcb_rpt_save() → 存入链表缓冲区 (BUFLIST_ENTRY)
                                              │
                                              ▼
发送层:  mvl61850_rpt_service() → mvl61850_brcb_client_service()
                 ↓  (每个主循环周期被调用)
           mvl61850_rcb_chk_state()
                 ↓
           BufTim 到期 或 IntgPd 到期
                 ↓
           mvlu_rpt_ready() → mvl61850_brcb_rpt_send() → MMS 编码 → TCP 发送

2. mvl61850_brcb_rpt_save() — 缓存写入

// mvl61850_rpt.c:501
ST_RET mvl61850_brcb_rpt_save(MVLU_RPT_CLIENT *rptClient)
{
    // Step 1: 如果是 GI 报告,先丢弃缓冲区中旧的 GI 报告
    if (rptClient->reasons_data[0] == MVLU_TRGOPS_GI)
        buflist_discard_old_gi(&rptClient->rpt_ctrl->brcbCtrl);

    // Step 2: 创建新的 BUFLIST_ENTRY,拷贝原始数据到缓冲区
    buflist_entry_create(rptClient);

    // Step 3: 检查缓冲区是否溢出,如果超限则丢弃最旧的报告
    buflist_discard_old(&rptClient->rpt_ctrl->brcbCtrl);

    // Step 4: 清理 RCB 状态,准备下次触发
    mvl61850_rcb_cleanup(rptClient);
    return SD_SUCCESS;
}

buflist_entry_create()(第 299 行)具体操作:

对于数据集中每个变化了的变量(j):
  ├─ 分配 VAR_DATA.data_ptr = malloc(data_len)          ← 深拷贝数据
  ├─ memcpy(data_ptr, var_assoc->data, data_len)         ← 保存当前值快照
  ├─ 记录 reason_for_incl = reasons_data[j]              ← 触发原因 (data/q/chg)
  └─ brcbCtrl->cur_bufsize += data_len                   ← 累加缓冲区用量

填充其他信息:
  ├─ EntryID: basrcb->EntryID[4..7]++   (自增 4 字节)    ← 每个缓存的报告唯一ID
  ├─ TimeOfEntry: u_mvlu_rpt_time_get()                   ← 取当前时间戳
  └─ asn1_len_array: 预计算每个变量的 ASN.1 编码长度       ← 为分片做准备

链入链表:
  list_add_last(&brcbCtrl->rpt_list, bufEntry)            ← 追加到链表尾部
  brcbCtrl->rpt_count++

  if (rpt_list_next == NULL)                               ← 如果链表之前空了
      rpt_list_next = bufEntry;                            ← 指向新条目(待发送)

缓冲区结构示意:

BRCB 缓冲区链表 (rpt_list):
┌──────────┐    ┌──────────┐    ┌──────────┐    ┌──────────┐
│ Entry#1  │ →  │ Entry#2  │ →  │ Entry#3  │ →  │ Entry#4  │ → NULL
│(最旧)    │    │          │    │          │    │(最新)    │
│ ID=0001  │    │ ID=0002  │    │ ID=0003  │    │ ID=0004  │
│ data[..] │    │ data[..] │    │ data[..] │    │ data[..] │
└──────────┘    └──────────┘    └──────────┘    └──────────┘
     ↑
rpt_list_next ─── 指向"下一个待发送"的条目

3. 缓冲区溢出处理

buflist_discard_old()(第 437 行):

while (cur_bufsize > brcb_bufsize || rpt_count > 500)
{
    entry = rpt_list;                       // 取最旧的条目
    if (entry == rpt_list_next)             // 如果丢弃的是"待发送"条目
    {
        BufOvfl = TRUE;                     // 置位溢出标志
        rpts_lost_count++;                  // 丢失计数++
    }
    buflist_entry_destroy(brcbCtrl, entry);  // 释放数据内存,从链表移除
}

触发条件:

  • cur_bufsize > brcb_bufsize — 总字节数超过配置上限
  • rpt_count > 500 — 条目数超过 500(硬限制)

4. 定时发送机制 — 谁触发发送?

4.1 主循环入口

test.cmain() 每个周期调用:

while (1) {
    iec61850_server_main();    // ← 这个函数内部调用 mvl61850_rpt_service()
    usleep(10000);             // 10ms 周期
}

scl_srvr.c 中:

void iec61850_server_main()
{
    mvl61850_rpt_service();    // ← 服务所有报告控制块
}

4.2 mvl61850_rpt_service() 遍历所有 RCB

// mvl61850_rpt.c:1425
void mvl61850_rpt_service()
{
    timeNow = sGetMsTime();                    // 取当前时间戳
    for (rptCtrl = g_rptCtrl; rptCtrl; ...)    // 遍历所有 RCB
    {
        if (rptCtrl->rcb_type == RCB_TYPE_IEC_BRCB)
            mvl61850_brcb_client_service(rptCtrl, &rptCtrl->only_client, timeNow);
        else  // URCB
            mvl61850_urcb_client_service(rptCtrl, &rptCtrl->only_client, timeNow);

        if (++num >= 255) break;               // 每个周期最多处理 255 个
    }
}

4.3 mvl61850_brcb_client_service() — BRCB 服务逻辑

void mvl61850_brcb_client_service(rptCtrl, rptClient, timeNow)
{
    // 阶段1: 检查保留时间 (ResvTms) 是否超时
    if (rptClient->ResvTmsExpireTime != 0.0)
    {
        if (timeNow <= ResvTmsExpireTime)
            // 客户端重连了 → 清除保留时间
        else
            // 超时 → 清除 Owner,释放 RCB
    }

    // 阶段2: 检查定时器和触发条件
    mvl61850_rcb_chk_state(rptCtrl, rptClient, timeNow);    // ← 关键

    // 阶段3: 如果缓冲区有待发送的报告 && 网络栈可用
    if (rptClient->netInfo         // 客户端已连接
        && RptEna                   // 报告已使能
        && rpt_list_next != NULL)   // 有待发送条目
    {
        if (mvl_ureq_bufs_avail(netInfo) > 0)   // MMS 栈缓冲区可用
        {
            entry = rpt_list_next;
            mvl61850_brcb_rpt_send(rptCtrl, rptClient, entry, &all_seg_done);

            if (all_seg_done) {
                rpt_list_next = list_get_next(entry);   // 指向下一个待发送
                SqNumInt16u++;                           // 序列号++
                BufOvfl = FALSE;                         // 清除溢出标志
            }
        }
    }
}

4.4 mvl61850_rcb_chk_state() — 定时器判决中心

// mvl61850.c:130
void mvl61850_rcb_chk_state(rptCtrl, rptClient, timeNow)
{
    if (RptEna || (BRCB && enabled_once))
    {
        // 判断1: 完整性周期 (IntgPd) 超时?
        mvl61850_integrity_timeout(rptClient, timeNow);

        // 判断2: 有变化的触发?
        if (rptClient->numTrgs > 0)    // ← 从 mvlu_rpt_va_change2 中累积的
        {
            // 如果 BufTim==0 (不缓存) 或者 BufTim 已到期
            if (BufTim == 0 || timeNow > buf_time_done)
            {
                mvlu_rpt_ready(rptClient, RBE);   // 决定发送方式
                // ─→ BRCB: mvl61850_brcb_rpt_save() (已缓存)
                // ─→ 此处: 不会再触发 save,因为已经 save 过了
                //          numTrgs 在 mvlu_rpt_va_change2 中已递增
                //          这里只是决定 "何时发送"
            }
        }
    }
}

4.5 mvlu_rpt_ready() 的分发决策

// mvlu_rpt.c:2683
ST_RET mvlu_rpt_ready(rptClient, rpt_type)
{
    switch (rptCtrl->rcb_type)
    {
        case RCB_TYPE_IEC_BRCB:
            // BRCB: 之前已经 save 到缓冲区了
            // 这里只是通知 BRCB "可以发送了"?
            // 不对... 实际上 mvlu_rpt_va_change2 的 _chk_reason 中
            // 当 BufTim=0 或 BufTim 超时时,
            // 才会调用 mvlu_rpt_ready → brcb_rpt_save
            // 所以 mvlu_rpt_va_change2 中:
            //   第一次变化: 启动 BufTim 定时器,设 changed_flags
            //   后续变化: 如果 BufTim 策略=立即发送 → 立即触发
            // rcb_chk_state 中:
            //   BufTim 到期 → 调用 mvlu_rpt_ready → brcb_rpt_save
            //   然后 brcb_client_service 的 阶段3 → brcb_rpt_send
            break;
        case RCB_TYPE_IEC_URCB:
            mvl61850_urcb_rpt_send(rptCtrl, rptClient, rpt_type);
            break;
        case RCB_TYPE_UCA:
            mvlu_send_report(rptClient, rpt_type);
            break;
    }
}

5. 完整时间线示例

以下是一个 BRCB 从数据变化到报告发送的完整时间线:

时间轴:
t=0ms   数据点 A 从 0→1
        iec61850_put_data()
          → mvlu_rpt_va_change2()
            → _mvlu_rpt_va_chk_reason()
              → numTrgs==0, 启动 BufTim 定时器
              → buf_time_done = now + 200ms  (假如 BufTim=200ms)
              → changed_flags[A] = 1, numTrgs=1

t=50ms  数据点 B 从 0→1
        → mvlu_rpt_va_change2()
          → numTrgs>0, changed_flags[B] 已在变化中
          → 如果 buftim_action=SEND_NOW
              立即调用 mvlu_rpt_ready()
              → mvl61850_brcb_rpt_save()     ← 条目1:包含 (A=1)
              → 重启 BufTim: buf_time_done = now + 200ms
          → changed_flags[A]=1, changed_flags[B]=1, numTrgs=2

t=100ms 数据点 A 从 1→0
        → mvlu_rpt_va_change2()
          → 第二次变化(不同于上次值)
          → changed_flags[A] 已经变化过
          → buftim_action=SEND_NOW
          → mvlu_rpt_ready()
          → mvl61850_brcb_rpt_save()         ← 条目2:包含 (A=1, B=1)
          → 重启 BufTim: buf_time_done = now + 200ms
          → changed_flags[A]=1, changed_flags[B]=1, numTrgs=3

t=300ms 主循环 -> mvl61850_rpt_service()
          → mvl61850_rcb_chk_state()
            → numTrgs>0 且 timeNow(300) > buf_time_done(300)
            → mvlu_rpt_ready()
            → mvl61850_brcb_rpt_save()       ← 条目3:包含 (A,B)

          → mvl61850_brcb_client_service()
            → rpt_list_next != NULL (条目1)
            → mvl61850_brcb_rpt_send(条目1)  ← 编码并发送
            → rpt_list_next = 条目2

t=310ms 主循环 -> mvl61850_rpt_service()
          → brcb_client_service()
            → rpt_list_next = 条目2
            → mvl61850_brcb_rpt_send(条目2)  ← 编码并发送
            → rpt_list_next = 条目3

t=320ms brcb_client_service()
          → mvl61850_brcb_rpt_send(条目3)    ← 编码并发送
          → rpt_list_next = NULL (全部发完)

6. 关键时序参数总结

参数来源作用
BufTim RCB 配置 (ms) 缓存时间窗口。变化发生后等待 BufTim ms 再生成报告,期间的变化可以合并。0=不缓存
IntgPd RCB 配置 (ms) 完整性周期。每隔 IntgPd ms 发送一次全数据集报告,不管数据是否变化
buf_time_done 运行时计算 sGetMsTime() + BufTim,第一次变化时设置
next_integ_rpt_time 运行时计算 下一次完整性报告到期时间
TrgOps RCB 配置 (位图) 触发选项:dchg(数据变化)/qchg(品质变化)/dupd(数据更新)/integrity(完整性)
RptEna 客户端写入 报告使能开关

7. BRCB vs URCB 的行为差异对比

方面BRCB (带缓存)URCB (不带缓存)
数据存储 深拷贝到链表缓冲区 直接引用实时数据区
连续变化 合并到一帧,间隔≤BufTim 每变化一次发一帧
断线重连 自动缓存,重连后补发 断线期间数据丢失
报文完整性 EntryID 编号,BufOvfl 标志
发送函数 brcb_rpt_save → brcb_rpt_send urcb_rpt_send (直接编码发送)
典型场景 重要遥信、SOE 事件 一般遥测、状态量

8. 关键代码关系总结

iec61850_put_data2()
  → writedata_*()
  → mvlu_rpt_va_change2()               [每次数据变化时触发]
    → _mvlu_rpt_va_chk_reason()         [检查 TrgOps 匹配 + BufTim 策略]
      → 记录 changed_flags + reasons_data + numTrgs
      → (若 BufTim 策略=立即发送) mvlu_rpt_ready()
        → mvl61850_brcb_rpt_save()      [深拷贝数据到链表]

主循环 (10ms 周期)                      
  → iec61850_server_main()
    → mvl61850_rpt_service()            [遍历所有 RCB]
      → mvl61850_brcb_client_service()
        → mvl61850_rcb_chk_state()      [检查 IntgPd 和 BufTim 到期]
          → (IntgPd 到期) mvlu_rpt_ready() → brcb_rpt_save()
          → (BufTim 到期 && numTrgs>0) mvlu_rpt_ready() → brcb_rpt_save()
        → (缓冲区非空) mvl61850_brcb_rpt_send()
          → 组装 MMS 报告报文 (RptID/OptFlds/SqNum/EntryID/时间戳/数据/reason)
          → mvl_info_variables()        [MMS 编码 + TCP 发送]

posted on 2026-07-30 18:07  weilai2345  阅读(22)  评论(0)    收藏  举报