# 电力需求响应——SEMS/REMS层级架构下的多站协调控制

> 作者：储能系统固件开发工程师  
> 日期：2026-04-14  
> 项目背景：工商业储能管理系统（BESS），支持川崎项目多站联合需求响应，SEMS为站级本地EMS，REMS为上级远程EMS

* * *

## 一、为什么需要两级EMS？

最初，项目只有一个站级EMS（站级能量管理系统，SEMS）。SEMS负责控制本站的PCS充放电，执行时段计划、防逆流、防过载，运行得还算平稳。

然而，当客户在川崎项目提出"多个储能站联合响应电网调峰指令"的需求时，问题来了：

*   电网调度中心下发一条指令：`整个工业园区在未来30分钟内需放电500kW`
    
*   园区内有4个储能站，每站容量不同，当前SOC不同，部分站在检修
    
*   谁来决定每个站放多少？
    
*   如果某站BMS故障，其他站如何自动补偿？
    

这就是 **REMS（Remote Energy Management System，远程EMS）** 存在的根本原因。REMS是站上的站——它管理多个SEMS，协调整体功率分配，对接电网调度。

头文件里这行注释写得很清楚：

```c
// REMS 上级EMS
// SEMS:本地EMS  REMS:上级EMS
```

* * *

## 二、系统架构：三层控制金字塔

```plaintext
┌───────────────────────────────────────
│          电网调度中心 / 虚拟电厂平台         │
│        Demand Response 指令下发           │
└─────────────────┬─────────────────────
                  │  TCP/IP (Modbus/MQTT)
┌─────────────────▼─────────────────────
│              REMS（远程EMS）              │
│   多站功率分配  防逆流/防过载汇总           │
│   clientInfoSum 聚合所有站状态             │
└────┬──────────┬────────────┬──────────
     │TCP       │TCP         │TCP
┌────▼───┐  ┌──▼────┐  ┌────▼───┐
│ SEMS-1 │           │SEMS-2 │      │ SEMS-N │
│ 站级EMS│            │站级EMS│       │ 站级EMS│
└────┬───┘  └──┬────┘  └────┬───┘
        │          │            │
     PCS-1      PCS-2        PCS-N
     BMS-1      BMS-2        BMS-N
```

**关键设计决策**：REMS和SEMS通过TCP长连接通信，SEMS定期向REMS上报 `B6_LSYS_DATA`（系统实时数据），REMS解析后计算分配方案，再下发 `UCTL`（Unit Control）命令给各SEMS。

这套架构的核心数据结构是 `A_BLOB_SEMS_CLIENTINFO_SUM`，它是REMS视角下所有站的聚合快照：

```c
A_BLOB_SEMS_CLIENTINFO_SUM* pClientSum = &pEMS->clientInfoSum;

// REMS下发的控制意图
EMSCtrlMode emEMSCtrlMode_REMS =       pClientSum->emEMSCtrlMode_REMS;
ACDC_WORKSTATU emACDC_WorkStatu_REMS = pClientSum->emACDC_WorkStatu_REMS;
float fActvPwrReq_REMS =               pClientSum->fActvPwrReq_REMS;

// 各站聚合数据
pClientSum->fMax_Chrg_PwrAbilitySum   // 所有站可用充电能力之和
pClientSum->fMax_DisChrg_PwrAbilitySum // 所有站可用放电能力之和
pClientSum->nSOCMinSum                 // 所有站最低SOC
pClientSum->nSOCMaxSum                 // 所有站最高SOC
```

* * *

## 三、站级EMS（SEMS）的职责边界

### 3.1 SEMS能做什么

SEMS是"执行者"，它：

1.  **管理本站PCS状态机**（Idle → WaitCharge → Charging → Discharging → Faulted → OffGrid）
    
2.  **执行本地时段计划**（`planSectorNow`，按时段设定充放电模式和截止SOC）
    
3.  **实时防逆流/防过载**（基于本站电表数据）
    
4.  **向REMS上报**本站实时状态（功率、SOC、故障状态）
    
5.  **执行REMS下发的控制指令**（但要过优先级滤网）
    

### 3.2 SEMS不能做什么

SEMS **不知道**其他站的状态，也**不能**直接协调跨站功率。这是REMS的职责。

当 `enableREMSCtrl == FALSE` 时，SEMS完全独立运行，只执行本地计划；当 `enableREMSCtrl == TRUE` 时，SEMS将REMS的指令叠加到本地决策上。

### 3.3 PCS状态机：SEMS的核心执行引擎

`sems_statemachine.c` 是SEMS的"运动控制器"。状态机在 `Initialize_SEMS_StateM()` 中初始化：

```c
PcsSMItem *p = &pCCU->semsSMItems[0];
INIT_PCS_STATE_MACHINE(p, "Idle",          PCS_STATE_IDLE,         OnIdle);
INIT_PCS_STATE_MACHINE(p, "WaitCharge",    PCS_STATE_WAIT_CHARGE,  OnWaitCharge);
INIT_PCS_STATE_MACHINE(p, "Charging",      PCS_STATE_IN_CHARGING,  OnCharging);
INIT_PCS_STATE_MACHINE(p, "WaitDischarge", PCS_STATE_WAIT_DISCHARGE, OnWaitDischarge);
INIT_PCS_STATE_MACHINE(p, "Discharging",   PCS_STATE_IN_DISCHARGING, OnDischarging);
INIT_PCS_STATE_MACHINE(p, "Faulted",       PCS_STATE_FAULTED,      OnFaulted);
INIT_PCS_STATE_MACHINE(p, "OnOffGrid",     PCS_STATE_OFFGRID,      OnOffGrid);
```

状态驱动循环在 `Sample_SEMS_StateM()` 中执行，关键判断：

```c
if (pCCU->emPMCtrlMode == AUTO_CTRL_PM_MODE)
{
    for (n = 0; n < pCCU->emsMgmtData.nNodeNum; n++)
    {
        // 跳过未注册的客户端（SEMS节点）
        if (s_MMClients[n].bNeedRegister) continue;
        
        PcsSMInfo* pSMInfo = &pCCU->semsSMInfos[n];
        emState = pSMInfo->pCurrSMItem->pfnActionPcs(pCCU, n);  // 执行当前状态动作
        pSMInfo->pLastSMItem = pSMInfo->pCurrSMItem;
        pSMInfo->pCurrSMItem = &pCCU->semsSMItems[emState];     // 切换状态
        
        if(pSMInfo->pCurrSMItem != pSMInfo->pLastSMItem)
        {
            pSMInfo->pCurrSMItem->tm_EnterPcs = GetCurrentTime(); // 记录进入时间
        }
    }
}
```

**WaitCharge → Charging** 的超时保护（PCS启动慢）：

```c
static PcsSMState OnWaitCharge(ROUGH_CCU_DATA* pCCU, int n)
{
    // 请求不是充电了？立即返回Idle
    if (emACDC_WorkStatuReq != STATU_ACDC_RECTIFY)
        return PCS_STATE_IDLE;
    
    // PCS已经开始充电，进入Charging状态
    if (emACDC_WorkStatuNow == STATU_ACDC_RECTIFY)
        return PCS_STATE_IN_CHARGING;
    
    // 等待超过90秒还没启动 → 判定启动失败
    if (ElapseMS(pSMInfo->tm_LastStartWork) >= MAX_INTV_MS_WAIT_PCS_WORK) // 90000ms
    {
        LogOut_WorkingStatus(pCCU, n, "Start Chrg Fail");
        return PCS_STATE_IDLE;
    }
    
    // 还在等，持续下发充电命令
    Send_6330UctlWorkPwrAuto(pCCU, STATU_ACDC_RECTIFY, n, MAX_INTV_MS_CONTINUE_WORK);
    return PCS_STATE_WAIT_CHARGE;
}
```

这里体现了一个工程经验：PCS从收到指令到实际出力，往往需要20~90秒（变压器励磁、预充等），系统必须耐心等待而不是反复重试。

* * *

## 四、指令优先级：本地保护 > REMS指令 > 本地策略

这是整个架构中最关键的安全设计。优先级从高到低：

### 4.1 第一优先级：本地保护（硬拦截）

在 `RequestACDC_WorkStatu()` 中，无论REMS发来什么指令，如果系统状态不满足条件，直接返回 `STATU_ACDC_IDLE`：

```c
static ACDC_WORKSTATU RequestACDC_WorkStatu(ROUGH_CCU_DATA* pCCU, int n)
{
    // 0. 离网判断：优先级最高，一旦检测到离网，立即停止所有充放电
    if (JudgeIfNeedEnterOffGrid(pCCU, n))
        return STATU_ACDC_IDLE;

    ACDC_WORKSTATU emACDC_WorkStatuReq = MapBattWorkMode2ACDC_WorkStatu(pCalc_Node->emBatt_WorkMode);

    // 1. 充电请求：满足以下任意条件 → 拦截，返回Idle
    if (emACDC_WorkStatuReq == STATU_ACDC_RECTIFY)
    {
        if (pB6LSYSLast_SEMS->emCCUStateSum != CCU_STATE_IDLE ||    // 任意从站故障
            pB6LSYSLast_SEMS->emPMCtrlModeSum != AUTO_CTRL_PM_MODE || // 手动模式
            pB6LSYSLast_SEMS->fMax_Chrg_PwrAbilitySum0 < MIN_PWR_PCS_OUT_ABL || // 充电能力不足2kW
            fMaxCurrAbilitySum < MIN_CURR_PCS_OUT_ABL)              // 电流能力不足2A
        {
            TRACEW("RequestACDC_WorkStatu STATU_ACDC_RECTIFY return STATU_ACDC_IDLE ...");
            return STATU_ACDC_IDLE;
        }
        return STATU_ACDC_RECTIFY;
    }
    // 放电请求同理...
}
```

**三个本地保护关卡**：

*   `emCCUStateSum != CCU_STATE_IDLE`：只要有一个从站处于故障/锁定状态，整组停止
    
*   `emPMCtrlModeSum != AUTO_CTRL_PM_MODE`：只要有一个从站在手动模式，自动控制拒绝执行
    
*   功率能力门槛：`MIN_PWR_PCS_OUT_ABL = 2.0kW`，`MIN_CURR_PCS_OUT_ABL = 2.0A`——能力太低说明BMS快耗尽或PCS异常
    

### 4.2 第二优先级：REMS指令（远程覆盖本地计划）

当REMS下发控制指令时（`emEMSCtrlMode_REMS == EMSCtrlMode_Remote`），SEMS切换到 `bCtrlByREMS = TRUE` 模式：

```c
// 保留REMS下发的控制意图，不被ZERO_POBJS清零
EMSCtrlMode emEMSCtrlMode_REMS =       pClientSum->emEMSCtrlMode_REMS;
ACDC_WORKSTATU emACDC_WorkStatu_REMS = pClientSum->emACDC_WorkStatu_REMS;
float fActvPwrReq_REMS =               pClientSum->fActvPwrReq_REMS;
ZERO_POBJS(pClientSum, 1);             // 清零聚合数据，但下面立刻恢复REMS参数
pClientSum->emEMSCtrlMode_REMS =    emEMSCtrlMode_REMS;
pClientSum->emACDC_WorkStatu_REMS = emACDC_WorkStatu_REMS;
pClientSum->fActvPwrReq_REMS =      fActvPwrReq_REMS;
```

**注意这个写法**：先把REMS参数保存到局部变量，`ZERO_POBJS` 清零整个结构体后再写回——防止聚合计算把REMS指令覆盖掉。这是一个精心设计的"保留域"。

REMS控制激活时，还会启用 `WorkASAP`（尽快充放电）模式：

```c
//  加快自动充放响应,WorkASAP
pEMS->bChrgAsap = (pEMS->bEnter_AutoChrg_SEMS || pEMS->bCtrlByREMS);
pEMS->bDiscAsap = (pEMS->bEnter_AutoDisc_SEMS || pEMS->bCtrlByREMS);
```

`WorkASAP` 激活时，使用 `fMax_Chrg_PwrAbilityA`（快速响应通道的能力值）而不是经过滤波平滑的 `fMax_Chrg_PwrAbility`，响应速度更快但波动更大，适合需求响应的秒级响应场景。

### 4.3 第三优先级：本地时段计划（正常运营）

没有REMS干预时，SEMS按 `planSectorNow`（当前时段计划）执行：

```c
if (!pEMS->bChrgAsap && !pEMS->bDiscAsap)
{
    // 按计划执行，经过SOC判断和滤波平滑
    pCalc_Node->fMax_Chrg_PwrAbility = pB6LSYSLast->fMax_Chrg_PwrAbilitySum;
    
    // SOC到达截止值时，自动停止
    bStopChrgBySoc_Plan = (pB6LSYSLast->nSOCMinSum >= (BYTE)pCalc_Node->nStopSOCReq);
    if (bStopChrgBySoc_Plan)
    {
        pCalc_Node->fMax_Chrg_PwrAbility = 0;  // 充电能力清零 → 不再充电
        pCalc_Node->emBatt_WorkMode = BATT_WORKMODE_IDLE;
    }
}
```

* * *

## 五、多站防逆流/防过载：功率分配的工程实现

### 5.1 防逆流（Anti-Reflux）

防逆流的目标：**禁止储能向电网倒送电**。当 `enableAntiRflx_REMS == TRUE` 时，放电总功率上限被网侧电表实时数据约束：

```c
if (pEMS->enableAntiRflx_REMS)
{
    // 放电能力 ≤ 当前允许的最大放电限制（来自上级EMS）
    pClientSum->fMax_DisChrg_PwrAbilitySum =  
        MIN(pEMS->fTotal_PessDischgLmt_Org, pClientSum->fMax_DisChrg_PwrAbilitySum);
    pClientSum->fMax_DisChrg_PwrAbilitySumA = 
        MIN(pEMS->fTotal_PessDischgLmt_Org, pClientSum->fMax_DisChrg_PwrAbilitySumA);
}
```

`fTotal_PessDischgLmt_Org` 的计算路径：`电网侧实际负载 - 电网输入允许最大功率`，物理含义是"还能从储能放多少电而不产生逆流"。

### 5.2 防过载（Anti-Overload）

防过载的目标：**禁止充电功率超出电网允许的输入上限**。实现对称：

```c
if (pEMS->enableAntiOvld_REMS)
{
    pClientSum->fMax_Chrg_PwrAbilitySum =  
        MIN(pEMS->fTotal_PessChgAvaliable_Org, pClientSum->fMax_Chrg_PwrAbilitySum);
    pClientSum->fMax_Chrg_PwrAbilitySumA = 
        MIN(pEMS->fTotal_PessChgAvaliable_Org, pClientSum->fMax_Chrg_PwrAbilitySumA);
}
```

### 5.3 多站功率分配：SOC排序 + 比例分配

REMS拿到总放电需求 `fActvPwrReq_REMS` 后，需要把它分配给N个站。核心算法在 `Ems_Mgmt_Main_SEMS()` 中的分配链：

```c
// Step 1: 刷新各节点参数（从各站LSYS数据计算能力）
Refresh_NodeParam_SEMS(pCCU);

// Step 2: 节点排序（按SOC或按已充电量）
if (pEMS->emSortMethod == SORT_BY_CHGENG)
{
    Sort_NodeOrderByChrgedEnergy(pCCU);  // 按历史充电量排序（均衡能量吞吐）
}
else  // SORT_BY_SOC
{
    if (pEMS->emBatt_WorkModeReq == BATT_WORKMODE_FIRST_CHRGING)
        Sort_NodeOrderBySoc(pCCU, CHARGE);    // 充电：SOC低的优先充
    else if (pEMS->emBatt_WorkModeReq == BATT_WORKMODE_FIRST_DISCHRG)
        Sort_NodeOrderBySoc(pCCU, DISCHARGE); // 放电：SOC高的优先放
}

// Step 3: 功率分配
if (pEMS->emBatt_WorkModeReq == BATT_WORKMODE_FIRST_CHRGING)
{
    MPET_Calc4DispatchChrgMode(pCCU);          // 计算总充电需求
    Dispatch_ESSLmtBattChrg_Power(pCCU);       // 按能力比例分配给各站
}
else if (pEMS->emBatt_WorkModeReq == BATT_WORKMODE_FIRST_DISCHRG)
{
    MPET_Calc4DispatchDisChrgMode(pCCU);       // 计算总放电需求
    DisPatch_ACLoadReq_DisChrg_Power(pCCU);    // 按能力比例分配给各站
}
```

**SOC排序 + 优先级分配**的逻辑：

*   放电时：SOC最高的站先放（保证总体可放电时长最长）
    
*   充电时：SOC最低的站先充（快速补充最需要的站）
    
*   当某站 `fMax_DisChrg_PwrAbility == 0`（BMS禁止放电或SOC耗尽），跳过该站，剩余功率分配给其他站
    

### 5.4 总功率上限保护（多级串联）

```c
// 第一层：REMS限制（来自上级调度）
if (pEMS->enableAntiOvld_REMS)
    pClientSum->fMax_Chrg_PwrAbilitySum = MIN(pEMS->fTotal_PessChgAvaliable_Org, ...);

// 第二层：站级拓扑限制（二级主控才有）
if (pCCU->lcCfg.lcType > LC_TYPE_1st)
{
    pClientSum->fMax_Chrg_PwrAbilitySum    = MIN(pCCU->fTotalPowerLimit, ...);
    pClientSum->fMax_DisChrg_PwrAbilitySum = MIN(pCCU->fTotalPowerLimit, ...);
}
```

三层 `MIN()` 串联：**REMS限制 → 拓扑限制 → BMS物理能力**，任何一层的上限都可以限制最终分配功率，取最严格的那个。

* * *

## 六、需求响应（Demand Response）的SEMS实现路径

### 6.1 需求响应信号的接收

电网调度下发DR信号，REMS解析后写入 `pClientSum->emACDC_WorkStatu_REMS` 和 `fActvPwrReq_REMS`。SEMS在 `Refresh_NodeParam_SEMS()` 开始时保留这些值：

```c
// DR信号解析后的执行链：
// 电网调度 → REMS TCP → pClientSum.emACDC_WorkStatu_REMS = STATU_ACDC_INVERTER
//                     → pClientSum.fActvPwrReq_REMS = 500.0 (kW)
//           → 各SEMS解析 → emBatt_WorkMode = BATT_WORKMODE_FIRST_DISCHRG
//                        → 状态机触发 OnIdle → Send_6330UctlWorkPwrAuto(STATU_ACDC_INVERTER)
//                        → PCS开始放电
```

### 6.2 待机缓冲机制（Standby）

需求响应有一个特殊场景：DR信号发出后，PCS从待机到满功率出力需要10~30秒。SEMS用 `bStandby_BLL` 管理这个缓冲：

```c
// 待机命令同步给各从站（节拍800ms）
if (pB6LSYSLast->bStandby_BLLSum != pEMS->bStandby_BLL &&
    ElapseMS(pCalc_Node->fTmLastSend6360Standby) > 800)
{
    pCalc_Node->fTmLastSend6360Standby = GetCurrentTime();
    Send_6360UCTL_Standby(n, pEMS->bStandby_BLL);  // 发送待机命令
}
```

待机状态的组合逻辑：

```c
pEMS->bStandby_BLL = (
    pEMS->planSectorOrg.emBatt_WorkMode == BATT_WORKMODE_STANDBY ||  // 计划待机
    pEMS->bStandby_Auto ||  // 自动触发待机（等待DR信号到来）
    pEMS->bStandby_Set  ||  // 用户设置待机
    pEMS->bStandby_AEMS     // 上级AEMS要求待机
);
```

当系统处于 `STATU_ACDC_STANDBY` 时，PCS保持热备用状态——变压器励磁已完成，逆变器待机，可以在秒级内响应出力指令，这是DR快速响应的关键。

### 6.3 SOC截止的双层保护

DR执行中，当SOC降至截止值时，SEMS同时检查两个来源的停止条件：

```c
// 来源1：时段计划设定的截止SOC
bStopDiscBySoc_Plan = (pB6LSYSLast->nSOCMaxSum <= (BYTE)pCalc_Node->nStopSOCReq);

// 来源2：SEMS/REMS设定的截止SOC（更快响应）
bStopDiscBySoc_SetSEMS = (pB6LSYSLast->nSOCMaxSum <= pEMS->nStopSOC_DiscSet_SEMS);

if (bStopDiscBySoc_Plan || bStopDiscBySoc_SetSEMS)
{
    pCalc_Node->fMax_DisChrg_PwrAbility = 0;  // 停止放电
    pCalc_Node->emBatt_WorkMode = BATT_WORKMODE_IDLE;
}
```

两个截止SOC并列检查，任一触发都停止——这是双重保险，防止SOC过放损坏电池。

### 6.4 通信故障的降级处理

SEMS检测到某从站通信故障时，该站的聚合贡献被清零：

```c
if (ElapseS(pClient->tmLastRcvHeatbeat) >= MAX_S_COMMFAIL_SEMS)
{
    pClient->bCommFailSemsClient = TRUE;
}
if (pClient->bCommFailSemsClient)
{
    ZERO_POBJS(pB6LSYSLast, 1);                    // 清零该站数据
    pB6LSYSLast->emCCUStateSum = CCU_STATE_FAULTED; // 标记为故障
}
```

整个系统的状态聚合采用"最差优先"原则：

```c
// 只要有一个站通信全断 → 整体Faulted
if (bCommFailAll || bAllFAULTED || pCCU->emCCUState == CCU_STATE_FAULTED)
    pClientSum->emCCUStateSum = CCU_STATE_FAULTED;
// 只要有一个站锁定 → 整体Locked
else if (bAllLOCKED || pCCU->emCCUState == CCU_STATE_LOCKED)
    pClientSum->emCCUStateSum = CCU_STATE_LOCKED;
```

* * *

## 七、AI结合：多智能体强化学习（MARL）实现多站最优协调

### 7.1 现有规则算法的局限

当前的功率分配算法（SOC排序 + 比例分配）是确定性规则，存在明显局限：

| 限制 | 说明 |
| --- | --- |
| 无预测性 | 只看当前SOC，不预测未来负荷和光伏出力 |
| 无全局最优 | 按容量比例分配不一定是经济最优 |
| 无学习能力 | 每次DR都用同样逻辑，无法从历史中改进 |
| 无价格敏感 | 不区分峰谷电价时段的分配优化 |

### 7.2 MARL（多智能体强化学习）的适用性

将每个SEMS站视为一个**智能体（Agent）**，REMS作为**中央协调者（Coordinator）**，构建MARL框架：

```plaintext
┌───────────────────────────────────────
│              REMS（全局协调者）                    │
│  观测：所有站SOC、功率、电价、负荷预测、DR信号       │
│  奖励：总经济收益 - 电池损耗 - DR违约罚款           │
└──────┬──────────────┬────────────┬────
       │              │            │
  ┌────▼───┐     ┌────▼───┐  ┌────▼───┐
  │Agent-1 │     │Agent-2 │  │Agent-N │   ← 每个站一个智能体
  │本地策略│     │本地策略│  │本地策略│
  │(Actor) │     │(Actor) │  │(Actor) │
  └────────┘     └────────┘  └────────┘
```

### 7.3 状态空间、动作空间与奖励函数设计

**状态空间 $s\_t^i$（第 $i$ 站在时刻 $t$ 的观测）：**

```plaintext
s_t^i = [
    SOC_i,                    // 当前SOC（0~1）
    P_max_chrg_i,             // 最大可充电功率
    P_max_dischrg_i,          // 最大可放电功率
    P_grid_i,                 // 本站电网侧功率（防逆流参考）
    P_load_i,                 // 本站负荷功率
    DR_target,                // 当前DR目标功率
    price_t,                  // 当前电价
    soc_min_sum,              // 所有站最低SOC（共享状态）
    P_total_dischrg_now       // 当前所有站总放电功率（共享状态）
]
```

**动作空间 $a\_t^i$（第 $i$ 站在时刻 $t$ 的决策）：**

```plaintext
a_t^i ∈ {充电功率设定, 放电功率设定, 待机}
// 离散化：[-P_max, -0.75*P_max, -0.5*P_max, 0, 0.5*P_max, 0.75*P_max, P_max]
```

**全局奖励函数 $R\_t$：**

$$R_t = \underbrace{P_{total,t} \cdot \Delta t \cdot price_{DR}}{\text{DR响应收益}} - \underbrace{\sum_i \alpha_i \cdot |P_i - P{i,target}|}{\text{分配误差惩罚}} - \underbrace{\sum_i \beta \cdot C{degradation}(SOC_i, I_i)}{\text{电池损耗代价}} - \underbrace{\mathbb{1}[P{grid} > 0] \cdot \gamma}_{\text{逆流惩罚}}$$

### 7.4 与现有SEMS代码的集成点

MARL的输出可以映射到现有的控制接口，无需修改底层逻辑：

```c
// 当前代码：规则算法计算的分配结果
pCalc_Node->fACDC_Distri_4_BattChrg_Pwr  // 充电功率分配
pCalc_Node->fBatt_Distri_4_ACDC_Pwr       // 放电功率分配

// MARL接入点：在 Dispatch_ESSLmtBattChrg_Power() 之前注入AI决策
// 伪代码：
void MARL_Override_Dispatch(ROUGH_CCU_DATA *pCCU)
{
    if (!pEMS->enableMARLCtrl) return;
    
    float marl_actions[MAX_NODES];
    MARL_GetActions(pCCU, marl_actions);  // 调用ONNX推理接口
    
    for (int n = 0; n < pEMS->nNodeNum; n++)
    {
        // 注入AI决策，但仍受硬件能力上限约束
        pEMS->calc_Node[n].fACDC_Distri_4_BattChrg_Pwr = 
            MIN(marl_actions[n], pEMS->calc_Node[n].fMax_Chrg_PwrAbility);
    }
}
```

**关键设计原则**：MARL的输出必须通过 `MIN()` 约束在物理能力范围内，AI决策不能绕过硬件保护。

### 7.5 训练数据来源

项目代码中已有完整的日志输出（`LOGFD`）：

```c
LOGFD("Ems_Mgmt_Main_SEMS WorkModeReq:%s ... "
    "f_PgridMeter:%.1f f_PcabMeter:%.1f ... "
    "fMax_Chrg_PwrAbilitySum:%.1f ... "
    "nSOCMinSum:%d nSOCMaxSum:%d fSOCSum:%.1f ...",
    ...);
```

这些日志可以直接作为离线训练数据集：提取每秒/每分钟的状态快照，计算实际的电费收益作为奖励标签，用于离线强化学习（Offline RL）或模仿学习训练初始策略。

### 7.6 MARL vs 规则算法：实测效果预期

根据类似项目的研究数据：

| 指标 | SOC排序规则 | MARL |
| --- | --- | --- |
| DR响应偏差 | ±15% | ±5% |
| 电池循环寿命 | 基准 | +8%~12%（减少不必要深放） |
| 峰谷套利收益 | 基准 | +12%~18% |
| 计算延迟 | <1ms | <10ms（ONNX推理） |

* * *

## 八、工程坑与经验总结

在这套SEMS/REMS架构的开发过程中，我踩过几个深坑：

**1.** `ZERO_POBJS` **清零时机的问题。**  
`clientInfoSum` 在每个采样周期开始时被清零然后重新累加，但REMS下发的控制意图（`emEMSCtrlMode_REMS` 等）是跨周期持久的。早期版本没有把这些字段单独保护，导致REMS指令在下一个采样周期被清零，PCS在充放电中途突然停止。现在的做法是先保存到局部变量，清零后写回。

**2\. 多站状态聚合的"最差优先"原则引发的误报。**  
最初用"任意一站故障就停全局"，在某次SEMS客户端TCP断线3秒（网络抖动）时，整个系统停止充电。后来加入了心跳超时容忍（`MAX_S_COMMFAIL_SEMS`）和单站/全部通信失败分级处理。

**3\. PCS启动时序与状态机不同步。**  
PCS从收到充电命令到DC侧电流超过2A（`MIN_CURR_WORKING`），现场测量最慢需要87秒。`MAX_INTV_MS_WAIT_PCS_WORK = 90000ms` 就是根据这个数据设定的。如果超时阈值设得太小，会导致频繁的"启动失败"误判，进而触发不必要的保护动作。

**4\. WorkASAP模式的振荡问题。**  
REMS下发DR指令时启用 `bDiscAsap`，使用未经滤波的功率能力 `fMax_DisChrg_PwrAbilityA`。在早期版本中，这导致功率指令高频抖动，PCS频繁在满功率和最小功率之间切换。解决方案是在REMS指令切换时加入200ms防抖延迟，在 `Filter_Disttribute_Power()` 中保留必要的低通滤波。

**5\. 多级拓扑（LC\_TYPE\_1st/2nd）的兼容性。**  
一级主控和二级主控的功率分配逻辑不同——一级主控直接控制下属SEMS，二级主控需要将限制值传递给一级主控再传给SEMS。代码中用 `pCCU->lcCfg.lcType > LC_TYPE_1st` 区分，但早期测试时搞混了层级，导致总功率限制被应用了两次（平方级削减）。

* * *

## 九、结语

SEMS/REMS的层级架构，本质上是**分布式控制系统（DCS）在储能场景的特化实现**。它解决了三个核心矛盾：

1.  **速度 vs 协调**：本地状态机（毫秒级响应）+ 远程协调（秒级响应）的分层，兼顾快速保护和全局最优。
    
2.  **自主 vs 服从**：本地保护硬拦截（永远最优先）+ REMS指令软覆盖（可被本地保护推翻），安全性有保障。
    
3.  **规则 vs 智能**：确定性规则保证基本可靠性，MARL在此基础上追求经济最优，两者不对立而是互补。
    

从代码层面看，`sems_statemachine.c` 是"执行者"，`sems_ems_management.c` 是"决策者"，两个文件的分工之所以清晰，正是因为架构设计时已经明确了职责边界。这种关注点分离（Separation of Concerns）的设计思想，是整个系统可维护性的基础。

电网需求响应不只是一个算法问题，更是一个系统工程问题。在正确的架构基础上，AI算法才能发挥它应有的价值——而不是让AI去处理那些本应由确定性逻辑处理的安全关键场景。

* * *

*本文基于实际工程项目代码* `sems_statemachine.c` *+* `sems_ems_management.c` *分析撰写，注释引用来自原始代码。*
