Skip to main content

Command Palette

Search for a command to run...

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

Updated
7 min read

作者:储能系统固件开发工程师
日期: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,协调整体功率分配,对接电网调度。

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

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

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

┌───────────────────────────────────────
│          电网调度中心 / 虚拟电厂平台         │
│        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视角下所有站的聚合快照:

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() 中初始化:

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() 中执行,关键判断:

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启动慢):

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

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.0kWMIN_CURR_PCS_OUT_ABL = 2.0A——能力太低说明BMS快耗尽或PCS异常

4.2 第二优先级:REMS指令(远程覆盖本地计划)

当REMS下发控制指令时(emEMSCtrlMode_REMS == EMSCtrlMode_Remote),SEMS切换到 bCtrlByREMS = TRUE 模式:

// 保留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(尽快充放电)模式:

//  加快自动充放响应,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(当前时段计划)执行:

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 时,放电总功率上限被网侧电表实时数据约束:

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)

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

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() 中的分配链:

// 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 总功率上限保护(多级串联)

// 第一层: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_REMSfActvPwrReq_REMS。SEMS在 Refresh_NodeParam_SEMS() 开始时保留这些值:

// 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 管理这个缓冲:

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

待机状态的组合逻辑:

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同时检查两个来源的停止条件:

// 来源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检测到某从站通信故障时,该站的聚合贡献被清零:

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

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

// 只要有一个站通信全断 → 整体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框架:

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

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

状态空间 \(s_t^i\)(第 $i$ 站在时刻 $t$ 的观测):

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$ 的决策):

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的输出可以映射到现有的控制接口,无需修改底层逻辑:

// 当前代码:规则算法计算的分配结果
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):

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 分析撰写,注释引用来自原始代码。

More from this blog

Integrated PV-Storage-Load Forecasting: Empowering EMS with Predictive Intelligence

光伏-储能-负荷联合预测:给 EMS 装上"预知能力" 本文结合我正在维护的一套工业储能管理系统的真实工程实践,聊聊如何把预测能力嵌入 EMS 决策链路。代码片段均来自项目实际文件,不是伪代码。 一、为什么 EMS 需要预测? 我在调试这套系统的 MQTT 指令链路时,发现一个有意思的现象:EMS 的充放电决策完全依赖当前时刻的 SOC 和预设的时段计划表。翻开 ems_management

Jun 8, 20264 min read

BMS SOC显示平滑算法:让百分比变化符合用户直觉

作者:悦悦 | 平台:S32K146 + FreeRTOS | 源文件:SocDisplay.c 前言 在储能BMS系统里,SOC(State of Charge,荷电状态)是用户最直接感知的核心指标。从EMS大屏到HMI触摸屏,SOC百分比就是电池的"油表"。 然而,算法层计算出来的SOC是一个不断跳变的原始值。把它直接推给显示层,用户看到的将是一个毫无规律、时快时慢、甚至会"倒退"的数字—

Apr 11, 20267 min read

EMS储能工程师的职业发展分析

一、你现在真实的竞争力画像 先说实话,让你有清醒认知。 从你的工程代码来看,你具备的能力是: 你最大的护城河不是C语言,而是"懂储能业务的嵌入式工程师"这个组合。 这个组合的人,全中国不超过几千人。 二、五大问题的系统回答 问题1:深度 vs 全栈,如何提升不可替代性? 核心判断:你不应该二选一,而是走"T型 + 业务锚点"路线。 什么是你的T型结构? 具体要做什么? 向下打深(6个月内优

Apr 9, 20262 min read2

BMS

12 posts