光伏-储能-负荷联合预测:给 EMS 装上“预知能力“
光伏-储能-负荷联合预测:给 EMS 装上"预知能力"
本文结合我正在维护的一套工业储能管理系统的真实工程实践,聊聊如何把预测能力嵌入 EMS 决策链路。代码片段均来自项目实际文件,不是伪代码。
一、为什么 EMS 需要预测?
我在调试这套系统的 MQTT 指令链路时,发现一个有意思的现象:EMS 的充放电决策完全依赖当前时刻的 SOC 和预设的时段计划表。翻开 ems_management.c,核心逻辑大概是这样的:
BOOL IsBatt_Plan2_StartChrg(...) {
if (pEMS->nTMSector < 0) return FALSE;
if (pPlanSectorNow->emBatt_WorkMode != BATT_WORKMODE_FIRST_CHRGING) return FALSE;
if (nBattSOC >= pPlanSectorNow->nStopSOC) return FALSE;
return TRUE;
}
逻辑很清晰:查当前时段(nTMSector),对比工作模式(emBatt_WorkMode)和停止 SOC 阈值(nStopSOC),决定充还是不充。
这套逻辑在电价稳定、负荷规律的场景下跑得很好。但现实是:光伏出力受云层影响可以在几分钟内从满功率跌到零;工厂负荷在节假日和工作日差异巨大;电网侧的峰谷价差窗口也在动态变化。
纯规则驱动的 EMS 本质上是在用昨天的地图走今天的路。
预测的价值在于:把"当前状态 → 决策"的单步反应,升级为"未来状态预估 → 提前布局"的前瞻控制。这就是 MPC(模型预测控制)的核心思想,后面会详细展开。
二、数据基础:我们手里有什么原料?
预测模型不是凭空建的,得先搞清楚系统里有哪些可用的数据。
2.1 光伏逆变器采样数据
项目里的 PV 采样模块(_inverter_sampler/)支持 Growatt、华为、固德威、阳光电源等主流逆变器品牌,通过 RS485 轮询采集。核心数据结构 PV_INVERTER_DATA(定义在 blob_model.h:4539)包含:
typedef struct {
float fOutputActivePower; // AC 侧输出有功功率 (W)
float fPVInputTotalPower; // DC 侧总输入功率 (W)
float fPVGroupVolt[MAX_PV_NUM]; // 各组串电压 (V)
float fPVGroupCurr[MAX_PV_NUM * 2]; // 各组串电流 (A)
float fDailyPowerGeneration; // 当日发电量 (kWh)
float fTotalPowerGeneration; // 累计发电量 (kWh)
int iActivePowerRate; // 有功功率限制百分比
int iWorkMode; // 0=未安装 1=等待 2=运行 3=故障
} PV_INVERTER_DATA;
汇总层面还有 A_BLOB_PV_INVERTER_DATA_TOTAL,字段 fTotalPVOutputActPower 给出全站光伏总出力(kW),这是预测模型的直接训练标签。
对预测有用的特征:fPVGroupVolt(组串电压反映辐照度变化)、iWorkMode(故障状态需要剔除异常样本)、fDailyPowerGeneration(日累计量用于校准预测偏差)。
2.2 DLT645 负荷计量数据
负荷侧通过 DLT645 协议采集电表数据(dlt645_kWmeter.c)。协议帧结构是标准的 0x68 + 地址域 + 0x68 + 控制码 + 数据域 + CS + 0x16,采集的量包括:
- 三相电压、电流、有功功率、无功功率
- 正向/反向累计电量(
WH_FRAME_TYPE_GET_TOTALWH/WH_FRAME_TYPE_GET_RVSTATALWH) - 最大需量(
WH_FRAME_TYPE_GET_PMAXDEMAND)——这个字段对需量控制预测特别有价值
数值解码用 BCD 转换:
float ConvertBCD_2WhVal(BYTE *pData, int nLen, int nDecimalPos) {
// BCD 编码 → 浮点,nDecimalPos 控制小数点位置
}
实际运行中,fGridMeter_Power[1](实时电网功率,kW)和 fCabMeter_Power[1](柜内功率)是负荷预测的核心输入,采样周期通常是 1 分钟。
2.3 储能电池状态
电池 SOC 存在 nSOC(千分位,0-1000 对应 0-100%),充放电能量分别记录在 fCalc_ChargedEnergy 和 fCalc_DisChargedEnergy。这些数据在预测框架里扮演"状态变量"角色——预测模型的输出要结合当前 SOC 才能转化为可执行的充放电指令。
三、光伏发电功率预测
3.1 NWP + 历史数据融合
光伏功率预测的难点在于它强依赖气象条件,而气象本质上是混沌系统。工程上通行的做法是两路信号融合:
NWP(数值天气预报)路:从气象局或商业 API 获取未来 24-72 小时的辐照度(GHI/DNI/DHI)、云量、温度预报。辐照度是光伏出力的物理驱动量,理论上 P p v = η ⋅ A ⋅ G P_{pv} = \eta \cdot A \cdot G Ppv=η⋅A⋅G( η \eta η 为效率, A A A 为面积, G G G 为辐照度),但实际逆变器效率随温度、老化程度变化,所以 NWP 只能给出粗粒度的趋势。
历史数据路:用过去 30-90 天的 fTotalPVOutputActPower 时序数据,学习"同类天气下的实际出力曲线"。关键是天气类型分类——晴天、多云、阴天、雨天的出力曲线形态差异极大,需要先做天气聚类再分类建模。
融合方式:NWP 预报作为外生变量(exogenous variable)输入模型,历史序列提供自回归特征。
3.2 模型选型:为什么不用 LSTM?
很多人第一反应是 LSTM,但在工业场景里我更倾向于 N-BEATS 或 PatchTST,原因如下:
N-BEATS(Neural Basis Expansion Analysis for Time Series)的核心思想是把时序分解为趋势项和季节项的叠加,每个 block 学习残差。对光伏这种有强日周期性的序列,这种归纳偏置(inductive bias)非常合适。而且 N-BEATS 是纯 MLP 结构,没有循环依赖,训练速度快,部署到嵌入式 Linux 上的推理开销也小。
PatchTST 把时序切成 patch(类似 ViT 对图像的处理),用 Transformer 的自注意力捕捉长程依赖。对于需要融合 NWP 多变量输入的场景,Transformer 的多头注意力天然适合处理异构特征。
Informer 用稀疏注意力(ProbSparse Attention)把复杂度从 O ( L 2 ) O(L^2) O(L2) 降到 O ( L log L ) O(L \log L) O(LlogL),适合预测窗口很长(比如 72 小时)的场景。
对于本项目这种 15 分钟粒度、预测 24 小时的需求,我会优先试 PatchTST,patch size 设为 16(对应 4 小时历史窗口),预测长度 96 个点。
3.3 不确定性量化:分位数回归
点预测给出的是期望值,但 EMS 决策需要知道"最坏情况下光伏出力是多少"。这就是概率预测的价值。
分位数回归(Quantile Regression)的损失函数:
L q ( y ^ , y ) = q ⋅ max ( y − y ^ , 0 ) + ( 1 − q ) ⋅ max ( y ^ − y , 0 ) \mathcal{L}_q(\hat{y}, y) = q \cdot \max(y - \hat{y}, 0) + (1-q) \cdot \max(\hat{y} - y, 0) Lq(y^,y)=q⋅max(y−y^,0)+(1−q)⋅max(y^−y,0)
同时预测 q = [ 0.1 , 0.5 , 0.9 ] q = [0.1, 0.5, 0.9] q=[0.1,0.5,0.9] 三个分位数,就得到了预测区间。EMS 在做保守决策时用 P10(下界),做激进决策时参考 P90(上界)。
实现上,只需要把模型最后一层的输出维度从 1 改为 3,损失函数换成三个分位数损失的加权和,其余架构不变。
四、负荷预测:挖掘工业负荷的周期性规律
4.1 工业负荷的三层周期结构
工业负荷不像居民负荷那样平滑,它有很强的生产节律:
- 日内周期:班次切换(早班/中班/夜班)导致功率阶跃,
fGridMeter_Power的时序上会看到明显的台阶状跳变 - 周周期:工作日 vs 周末,某些工厂周末停产,负荷降到基础维持水平(空调、照明、待机)
- 节假日模式:春节、国庆等长假期间负荷曲线完全异于平日,需要单独建模
从 DLT645 采集的历史数据里,可以用 STL 分解(Seasonal-Trend decomposition using LOESS)把这三层周期显式分离出来,再分别建模。
4.2 特征工程
除了历史负荷序列本身,以下特征对工业负荷预测有显著提升:
| 特征 | 来源 | 说明 |
|---|---|---|
| 星期几(one-hot) | 系统时钟 | 捕捉周周期 |
| 是否节假日 | 日历 API | 节假日模式切换 |
| 时段索引(0-47) | nTMSector |
对应 EMS 的 30 分钟时段划分 |
| 最大需量 | WH_FRAME_TYPE_GET_PMAXDEMAND |
反映生产强度 |
| 滞后特征(lag-24h, lag-168h) | 历史数据 | 昨天同时刻、上周同时刻 |
| 滚动均值(rolling mean 1h/4h) | 历史数据 | 平滑短期波动 |
nTMSector 这个字段在 EMS 代码里本来是用来查时段计划表的,但它同时也是一个天然的时间特征——把它直接 embedding 进模型,比用原始时间戳效果好。
4.3 多变量时序预测
负荷预测不是孤立的,光伏出力、电池 SOC、历史电价都会影响实际负荷(比如高电价时段工厂会主动降负荷)。把这些变量一起输入 Informer 或 PatchTST,做多变量 → 单变量(或多变量 → 多变量)预测,通常比纯单变量模型提升 10-20% 的 MAE。
五、预测结果注入 EMS:MPC 框架
这是整个方案最关键的一环,也是最容易被忽视的一环。很多项目做完预测就停了,预测结果没有真正改变决策逻辑。
5.1 现有 EMS 决策的局限
回到 ems_management.c 里的逻辑:IsBatt_Plan2_StartChrg() 和 IsBatt_Plan2_StopDischarge() 都是单步决策,只看当前时刻的 SOC 和时段配置。这意味着:
- 如果预测到下午 2 点光伏会大发,现在(上午 10 点)就应该少充一点,留出空间吸纳光伏;但现有逻辑不知道这件事,会按计划表继续充到
nStopSOC。 - 如果预测到晚上 8 点有一个负荷高峰,现在就应该开始储能,但现有逻辑要等到那个时段才触发。
5.2 MPC 的基本框架
MPC(Model Predictive Control,模型预测控制)的思路是:在每个控制周期,用预测模型生成未来 N N N 步的状态预估,然后求解一个优化问题,得到未来 N N N 步的最优控制序列,只执行第一步,下一个周期滚动重复。
优化目标(以经济性为例):
min u t , . . . , u t + N ∑ k = 0 N − 1 [ c k b u y ⋅ P g r i d , k + − c k s e l l ⋅ P g r i d , k − + λ ⋅ Δ u k 2 ] \min_{u_t, ..., u_{t+N}} \sum_{k=0}^{N-1} \left[ c_k^{buy} \cdot P_{grid,k}^+ - c_k^{sell} \cdot P_{grid,k}^- + \lambda \cdot \Delta u_k^2 \right] ut,...,ut+Nmink=0∑N−1[ckbuy⋅Pgrid,k+−cksell⋅Pgrid,k−+λ⋅Δuk2]
其中 c k b u y / s e l l c_k^{buy/sell} ckbuy/sell 是预测的电价, P g r i d + P_{grid}^+ Pgrid+ 是购电功率, P g r i d − P_{grid}^- Pgrid− 是售电功率, λ ⋅ Δ u k 2 \lambda \cdot \Delta u_k^2 λ⋅Δuk2 是控制平滑项(避免频繁切换充放电)。
约束条件:
S O C m i n ≤ S O C k ≤ S O C m a x SOC_{min} \leq SOC_k \leq SOC_{max} SOCmin≤SOCk≤SOCmax
P b a t t , m i n ≤ P b a t t , k ≤ P b a t t , m a x P_{batt,min} \leq P_{batt,k} \leq P_{batt,max} Pbatt,min≤Pbatt,k≤Pbatt,max
S O C k + 1 = S O C k + η c h g ⋅ P b a t t , k + − P b a t t , k − / η d i s E c a p ⋅ Δ t SOC_{k+1} = SOC_k + \frac{\eta_{chg} \cdot P_{batt,k}^+ - P_{batt,k}^- / \eta_{dis}}{E_{cap}} \cdot \Delta t SOCk+1=SOCk+Ecapηchg⋅Pbatt,k+−Pbatt,k−/ηdis⋅Δt
功率平衡约束:
P p v , k + P b a t t , k + P g r i d , k = P l o a d , k P_{pv,k} + P_{batt,k} + P_{grid,k} = P_{load,k} Ppv,k+Pbatt,k+Pgrid,k=Pload,k
这里 P p v , k P_{pv,k} Ppv,k 和 P l o a d , k P_{load,k} Pload,k 就是预测模型的输出。
5.3 与现有代码的接口设计
要把 MPC 嵌入现有系统,最小侵入的方式是:不改变底层执行逻辑,只替换决策层的参数生成。
现有系统通过 MQTT 下发 cmd/set/emu/powerCmd 来控制充放电模式,通过 cmd/set/emu/dispatchPower 下发调度计划。MPC 优化器可以作为一个独立进程运行,每 15 分钟执行一次:
- 从数据库读取最新的
fTotalPVOutputActPower、fGridMeter_Power、nSOC - 调用预测模型,生成未来 24 小时的 P ^ p v \hat{P}_{pv} P^pv 和 P ^ l o a d \hat{P}_{load} P^load
- 求解 MPC 优化问题(用
scipy.optimize或cvxpy) - 把优化结果转换为
dispatchPower的 JSON 格式,通过 MQTT 下发
注意 EMU_dispatchPower 函数(GH_DigitalEnergy_Pub.c:2582)里有时间戳校验:
// GH_DigitalEnergy_Pub.c:2631
if(ABS(Now_time - Ts_val->valueint) > 15) {
pMqtt->bDisPower = TRUE;
return -1;
}
MPC 优化器下发指令时,时间戳字段必须用设备当前时间(误差 ≤15 秒),这是一个容易踩的坑。
5.4 不确定性在 MPC 中的处理
前面提到分位数预测给出了 P10/P50/P90 三条曲线。在 MPC 里,可以用鲁棒 MPC 的思路:
- 光伏预测用 P10(保守估计),避免因高估光伏出力导致过度放电
- 负荷预测用 P90(保守估计),确保高负荷场景下有足够储能余量
- 在约束里加入 SOC 缓冲带(比如 SOC 下限从 10% 提高到 15%),为预测误差留出安全裕度
这种处理方式不需要复杂的随机优化,计算开销小,适合嵌入式 Linux 环境。
六、工程落地的几个实际问题
6.1 数据质量
DLT645 采集偶尔会有通信超时,ConvertBCD_2WhVal 返回异常值。训练数据里需要做异常检测(3σ 准则或 IQR 过滤),否则一个坏点会严重影响模型。
光伏逆变器的 iWorkMode == 3(故障)期间的数据要标记并剔除,不能用来训练"正常发电"的模式。
6.2 模型更新频率
光伏组件会随时间老化(年衰减约 0.5-0.8%),负荷模式会随生产计划变化。模型不能训练一次就永久使用,建议每月用最新 90 天数据重新微调(fine-tune),而不是从头训练。
6.3 预测失效时的降级策略
预测模型是软件,会出 bug,NWP 数据源也可能断线。系统必须有降级逻辑:当预测模块不可用时,自动回退到现有的规则驱动模式(IsBatt_Plan2_StartChrg 那套逻辑)。这个降级切换可以通过 emControlMode 字段来实现——预测驱动模式和规则模式对应不同的控制模式枚举值。
6.4 计算资源约束
项目运行在 ARM 嵌入式 Linux 上(从 src/mkenv_arm_st 可以看出是 ARM 交叉编译环境)。深度学习模型推理需要考虑资源限制:
- N-BEATS 的 MLP 结构可以用 ONNX Runtime 部署,推理一次 24 小时预测在 ARM Cortex-A 上通常在 100ms 以内
- MPC 优化问题如果是线性规划(LP),用
glpk或HiGHS求解,几十个变量的问题毫秒级可解 - 如果资源实在紧张,可以把预测和优化放到云端,设备只负责执行,通过 MQTT 接收优化后的调度计划
七、小结
从"当前 SOC + 时段表 → 充放电决策"到"未来 24 小时预测 + MPC 优化 → 前瞻调度",这不是一个简单的模型替换,而是整个决策架构的升级。
核心链路是:原始采样数据(PV 逆变器 + DLT645 电表)→ 特征工程 → 时序预测模型(PatchTST/N-BEATS)→ 概率预测区间 → MPC 优化求解 → MQTT 指令下发 → 设备执行。
每个环节都有工程细节需要处理,但最重要的是把预测结果真正接入决策闭环,而不是让它停留在离线分析报告里。现有代码里的 dispatchPower 接口已经提供了足够的控制自由度,缺的只是上游的预测驱动层。
这套方案在理论上可以把储能系统的经济收益提升 15-30%(相比纯规则调度),具体数字取决于当地电价结构和光伏/负荷的波动程度。值得投入工程资源去做。
代码引用均来自项目实际文件,文中行号以当前代码版本为准。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)