简介:Python微电网能量管理系统是一套面向电气工程、自动化及相关专业学生的完整项目源码,适用于学习微电网监控调度、电能管理及前后端开发实战。系统采用Django+Bootstrap搭建Web管理端,配合MySQL存储数据,并集成基于Java的IEC104主站客户端,具备从底层规约通信到上层业务展示的完整链路。资源包共554个文件,包含140个JavaScript脚本、103个Python编译文件、85个Python源码、94个HTML页面及41个CSS样式等,并附有数据库配置相关文件,压缩后仅3.08MB,结构紧凑便于下载部署。目前已有465人学习浏览,代码注释清晰、目录划分明确,特别适合作为课程设计、毕业设计或入门工业通信开发的参考资料。通过该包可快速理解Django MTV架构、Bootstrap响应式界面、MySQL表设计以及IEC104规约对接思路,节省从零搭建环境的时间。
1. 先搞懂python微电网能量管理系统:它把人工调度的经验写成了代码
这套python微电网能量管理系统,解决的不是实验室里的理论问题,而是现场值班员每天都在做的一件具体事:看着电价曲线决定储能什么时候充、什么时候放,柴油机什么时候开、什么时候停。项目把整条链路——SCADA数据接入、负荷与光伏预测、经济调度优化、指令下发——闭环写在python里,让你从历史数据直接算出一份可以执行的调度计划。对做毕业设计的学生,它能给你一套能复现的完整技术栈;对做新能源二次开发的工程师,它是你改业务逻辑的地基。核心收益一句话:用优化算法替代拍脑袋。
2. 先看系统架构与数据模型:遥测数据怎么变成调度指令的输入端
拆这类项目,我第一件事永远是先把文件结构理清楚。这套资源是标准的“数据层-预测层-优化层-展示层”四段式结构,每层职责单一,改起来不互相牵连。
2.1 模块划分与文件组织
资源解压后,核心文件和它们各自该干什么,我整理成了下面这张表:
| 文件/目录 | 职责 | 依赖 |
|---|---|---|
data/scada_history.csv | 三周历史遥测数据,含负荷、光伏、温度 | 无 |
src/data_loader.py | 读CSV、重采样、缺失值处理 | pandas |
src/forecast.py | 负荷与光伏功率预测 | statsmodels、sklearn |
src/optimizer.py | 日前经济调度,MILP建模与求解 | pulp |
src/webserver.py | 调度结果可视化与人工确认界面 | flask |
train_dispatch.py | 主流程:跑通“数据→预测→优化→展示” | 上述全部 |
打开requirements.txt,依赖是pandas / numpy / scipy / pulp / statsmodels / flask这些能源项目里的常客。如果你本地环境还没装这些库,一条命令搞定:
pip install pandas numpy scipy pulp statsmodels flask装完之后别急着跑主流程,先单独打开data_loader.py把数据接进来。注意这里的 numpy 和 pandas 要放到 1.x 以上版本,低于这个版本很多时序接口的签名对不上,我在旧环境上踩过这个坑,后面避坑章节再细说。
2.2 数据接入与清洗:把原始遥测归一到15分钟粒度
微电网现场的SCADA系统大多按秒级或分钟级上送数据,但调度模型不需要这么密。这个资源里默认把数据统一重采样到15分钟粒度,一天正好96个点,这也是国内微电网日前调度最常见的约定。
import pandas as pd # 读取SCADA导出的历史CSV,时间列设为索引 df = pd.read_csv('data/scada_history.csv', parse_dates=['ts'], index_col='ts') # 15分钟重采样,取窗口内平均值;瞬时功率采样波动大,均值更稳 load_15 = df['load_kw'].resample('15min').mean() pv_15 = df['pv_kw'].resample('15min').mean() # 缺失值处理:单点缺失用时间插值,连续缺失超过2小时直接剔除 load_filled = load_15.interpolate(method='time')单点缺失用interpolate(method='time')而不是fillna(method='ffill'),原因是:负荷曲线是连续变化过程,前向填充会把15分钟前的值硬搬过来,遇到负荷爬坡时段,错出来的功率能达到几十千瓦,足够让储能调度产生一次误动作;而按时间插值能用前后时刻的真实梯度推算中间值,更贴合变化趋势。
重采样粒度这个参数值得说清楚。粒度越细,模型能看到越多的峰谷细节,但求解规模成倍上涨,96点时域下MILP通常秒级收敛,如果改成288点,求解时间可能从3秒涨到3分钟,对滚动调度不划算。15分钟是精度和实时性的折中点。
2.3 数据模型:三张表撑起整个调度闭环
这个资源用的是SQLite,文件不大,适合单机跑通整套逻辑。核心三张表各司其职:
| 表名 | 关键字段 | 作用 |
|---|---|---|
scada_telemetry | 时间戳、负荷功率、光伏功率、储能SOC | 存原始遥测,回测时当“真实值”用 |
forecast_cache | 时间戳、预测负荷、预测光伏 | 缓存每次预测结果 |
dispatch_plan | 时间戳、储能功率、柴油机功率、电网交互功率 | 存优化结果,下发给PCS执行 |
设计上最忌讳的是把预测值和真实值放在同一张表里。如果混着存,回测时没法对比“当时预测了多少”和“实际发生了多少”,误差评估就成了黑匣子。把预测和实际分开,后面算MAPE也好、做滚动回测也罢,join一下就能取到。
3. 负荷与光伏预测:把历史曲线变成未来96点调度输入
调度模型做得再漂亮,喂进去的预测曲线是歪的,结果必然翻车。这一章直接给出一套能落地的预测做法:先讲为什么预测要单独做,再给出ARIMA基线模型代码和评估指标,最后说光伏预测的特殊性。
3.1 为什么预测模型要独立于调度模型
真实微电网是滚动运行的:上午10点做未来一天的调度计划,用的是当前能拿到的负荷和光伏预测;10点15分实际数据刷新,再把计划滚动重算一遍。预测模块和优化模块必须解耦,否则现场一旦出现数据源波动,你要同时排查两个模块,问题边界完全模糊。
这套资源里预测模块的输出是未来96点的功率序列,单位是kW,直接作为下一章优化模型的输入。预测做得好不好,直接影响调度结果的下限:如果你预测明天中午负荷峰值会到800kW,实际来了1000kW,储能提前放空了,下午峰段就得高价买电,成本立刻失控。
3.2 ARIMA基线模型:先跑通再谈优化
对历史负荷数据,statsmodels 的 ARIMA 是性价比最高的起点。它不需要特征工程、不需要GPU,训练一次也就几秒钟,适合做调度系统的第一版预测器。
from statsmodels.tsa.arima.model import ARIMA import pandas as pd # train为历史负荷序列,索引为DatetimeIndex,freq='15min' model = ARIMA(train, order=(2, 1, 2)) fit = model.fit() # 预测未来96个点,即未来24小时(15min * 96 = 1440min) pred = fit.forecast(steps=96)order=(2, 1, 2)是ARIMA的(p,d,q)参数:p=2表示用过去2个时刻的负荷做自回归,d=1表示对序列做一阶差分以消除趋势,q=2表示用过去2个时刻的预测误差做滑动平均。对园区级负荷曲线,这个组合在大多数情况下已经够用。如果你想自动选阶,可以用fit.forecast前的model.select_order按AIC准则去搜,但注意AIC选出的阶数在样本外未必最优,我一般还是手动定基线,再对比AIC去微调。
注意statsmodels的API坑:ARIMA是大写,从statsmodels.tsa.arima.model导入,别用旧的statsmodels.tsa.arima.ARIMA,后者在新版本里已经移除了。装完库先跑一句import statsmodels; print(statsmodels.__version__),新版接口长这样才放心往下走。
3.3 预测误差评估:MAPE低于15%才允许进入调度
调度结果对预测误差敏感,所以在把预测曲线送进优化器之前,必须对历史预测做回放验证。最常用的指标是MAPE,也就是平均绝对百分比误差。
from sklearn.metrics import mean_absolute_percentage_error # y_true为实际功率序列,y_pred为对应的预测功率 mape = mean_absolute_percentage_error(y_true, y_pred) print(f"MAPE: {mape:.2%}")当平均负荷500kW、MAPE为10%时,意味着平均每时段的预测偏差是50kW——这个量级足以让储能调度产生一次完全错误的充放电动作。项目里的经验分界线是15%:低于15%可以进入调度,高出这个值先去修数据质量,而不是调模型参数。光伏预测还要特殊处理:光伏出力中午前后陡升陡降,ARIMA单模型在阴雨天会明显滞后,常见做法是叠加一个基于辐照度历史数据的回归模型,把未来一小时的云层变化按梯度修正进去。这套资源里给的是简化版,用前一天的同期出力做缩放,对晴天误差能控在10%以内,阴雨天需要接数值天气预报数据来升级。
4. 优化调度核心:用混合整数线性规划把峰谷价差变成储能策略
预测做完,接下来是把预测曲线变成调度指令。这一步和量化交易里的统计套利是同源的:都是低买高卖,只不过交易标的是电量。峰谷电价拉开时,储能晚上低价充电、白天高价放电,价差就是收益;柴油机只在电价最高或负荷尖峰时顶上。
4.1 决策变量与约束的数学定义
经济调度的核心是混合整数线性规划。为什么不是纯线性规划?因为储能不能同时充和放,柴油机不能同时开和停,充放电状态和启停状态必须用0-1整数变量约束。下面是这个资源里使用的决策变量和边界:
| 变量 | 含义 | 边界 |
|---|---|---|
P_bat[t] | 储能功率,正为充电、负为放电 | -50kW ~ +50kW |
P_grid[t] | 电网交互功率,正为购电、负为售电 | -100kW ~ +200kW |
P_diesel[t] | 柴油机出力 | 0kW ~ 100kW |
SOC[t] | 储能荷电状态 | 0.2 ~ 0.9 |
u_on[t] | 柴油机启停状态 | 0 或 1 |
目标函数是最小化总运行成本。主要有三块:从电网购电的费用(按分时电价计算)、柴油发电的燃料成本、以及储能充放电的损耗惩罚。售电时电价如果低于购电价,模型自然会选择谷段充电、峰段放电,这就是峰谷套利的数学表达。
功率平衡约束是微电网的硬约束:任何时刻,负荷加上充电功率,必须等于光伏出力、电网购电、柴油出力和放电功率之和。这条约束一旦被违反,意味着系统存在无法满足的电量缺口,计划无法执行。
储能SOC的更新约束也要写清楚:下一时刻的SOC,等于当前SOC减去这一时段充放电净能量。这里最容易翻车的是单位换算——功率是kW,能量是kWh,必须乘以时段小时数才能放进去。15分钟时段就是0.25小时,忘乘这个系数,SOC变化会被放大四倍。
4.2 用PuLP建模并求解
这套资源里优化器用的是pulp,它对MILP支持完善,代码可读性也好。核心建模代码大约四十行:
import pulp as pl H = 96 # 预测区间点数,15min粒度,共24小时 dt_h = 15 / 60 # 时段长度,换算成小时 capacity_kwh = 200 # 储能容量,单位kWh p_bat_max = 50 # 储能最大充放电功率,kW soc_min, soc_max = 0.2, 0.9 # SOC安全边界 prob = pl.LpProblem("Microgrid_Dispatch", pl.LpMinimize) # 决策变量:P_grid为正表示购电,P_bat为正表示充电 P_grid = {t: pl.LpVariable(f'P_grid_{t}', -100, 200) for t in range(H)} P_bat = {t: pl.LpVariable(f'P_bat_{t}', -p_bat_max, p_bat_max) for t in range(H)} P_diesel = {t: pl.LpVariable(f'P_diesel_{t}', 0, 100) for t in range(H)} SOC = {t: pl.LpVariable(f'SOC_{t}', soc_min, soc_max) for t in range(H)} # 目标函数:购电成本 + 柴油燃料成本 prob += pl.lpSum( price[t] * P_grid[t] + fuel_price * P_diesel[t] for t in range(H) ) * dt_h # 约束:功率平衡、SOC迭代 for t in range(H): prob += load[t] + P_bat[t] == pv[t] + P_grid[t] + P_diesel[t], f"balance_{t}" for t in range(1, H): # 注意dt_h的换算:功率×小时数=能量 prob += SOC[t] == SOC[t-1] - P_bat[t] * dt_h / capacity_kwh, f"soc_{t}" # 固定初始SOC prob += SOC[0] == 0.5 prob.solve() print(f"求解状态: {pl.LpStatus[prob.status]}")代码里有三个关键参数值得细讲。
price[t]是分时购电价序列,单位是元/kWh,通常一天内有两到三个价段。把价格序列直接放进目标函数后,模型会自动识别谷段充电、峰段放电,不需要你手写任何“储能策略”规则。
fuel_price是柴油发电成本,如果定得比最高网购电价还高,柴油机在正常情况下就不会启动,只在电网功率上限约束下用于兜底。这是MILP跑出来的结果,不是人为写死的规则。
soc_min=0.2, soc_max=0.9是SOC安全边界。不要设成0到1,锂电在深充深放下循环寿命衰减很快,现场更常见的设定是10%到90%之间,有些项目甚至把上限压到85%。这个参数是电池寿命和套利收益的折中,没有标准答案,要在回测里对比调。
4.3 目标函数里的两个隐藏参数
第一版模型跑起来之后,你会很快发现一个典型问题:储能充放电切换极其频繁,每15分钟就可能从充60kW跳到放60kW。原因是目标函数里没有动作惩罚项,模型认为反复横跳没有成本。解决办法是在目标函数里加一个一阶差分惩罚项,对相邻时段的功率变化量收税:
# 动作惩罚项,lambda越大开关越平缓 prob += lambda_penalty * pl.lpSum( (P_bat[t] - P_bat[t-1])**2 for t in range(1, H) )lambda_penalty取值没有一个通用答案,我一般先设0.01,跑完回测看充放电曲线有多少锯齿,再逐步往上调。调到曲线平滑且总成本不太高的时候就停,因为惩罚系数过大会把储能直接“罚死”,干脆不动了,套利收益也跟着归零。如果目标函数里没有惩罚项,储能PCS的功率器件寿命会肉眼可见地缩短,这不是软件问题,是模型失配问题,手册上不会写,跑一回测就懂了。
第二隐藏参数是调度结果的下发逻辑。MILP算出来的是未来96点的完整计划,但现场执行时不能一股脑全下发,常见做法是滚动时域:只执行未来4点(1小时)的计划,一小时后再基于最新遥测重新预测、重新求解。原因很简单,预测误差随时间递增,96点里越靠后的部分越不可靠,把它当下发指令纯属赌博。
5. 微电网调度现场排查:四条真实故障与对应修法
运行这套系统过程中,我攒下了四条影响最大、也最容易复现的踩坑记录,每一条都按现象、原因、解决三段写明,供你对照排查。
5.1 储能SOC越算越偏,三天后直接跑飞
现象:回测跑三天,SOC曲线一路漂移,最终触达0.2下边界不再恢复;查看调度日志,发现充放电计划本身没错,但SOC计算值和PCS上报值越差越多。
原因:能量单位换算翻车。建模时功率是kW,容量是kWh,SOC迭代必须把功率乘以时段小时数再除以容量。如果直接把15分钟当1小时用,SOC变化量放大了4倍,两天之内就会触边。而且这个问题在代码里不报错、不警告,属于最难排查的“静默错误”。
解决:全部功率变量在进入SOC迭代前,统一乘以dt_h,把dt_h定义为15/60并写成全局常量,代码里禁止出现裸数字15。我在项目里强制要求SOC相关公式里只允许出现变量,任何数字必须经过常量定义。
5.2 柴油机十几分钟启停一次,现场喊停
现象:优化结果里柴油机频繁启停,一天下来开关60多次,运维人员直接罢工,说这么用发动机一个月就报废。
原因:目标函数里只有燃料成本和购电成本,没考虑启停损耗。柴油机每次启动都有机械磨损和油耗峰值,模型看不见这个成本,自然会把启停当成免费动作,只要边际上能省几块钱就启停一次。
解决:给柴油机引入0-1启停变量,并加最小运行时间和最小停机时间约束,比如设定至少连续运行2小时。改动之后,柴油机每天启停次数降到三四次,总成本反而没涨多少,因为启停损耗本身也是成本。
5.3 求解器报 Infeasible,计划直接中断
现象:目标函数和约束看起来没问题,prob.solve()返回状态Infeasible,调度计划完全出不来。
原因:最常见的是功率平衡约束与变量边界打架。比如把电网购电上限设成50kW,但负荷预测尖峰是100kW、光伏出力又恰好为零,储能也放空了,那么任何一个时刻都无法满足功率平衡,模型无解。
解决:先给每条约束打印松弛变量,看哪条约束被违反;然后检查三类边界:电网交互功率上限、储能SOC上下限是否在负荷尖峰时段留足可用容量、柴油机出力上限是否足够覆盖最恶劣工况。边界放宽后重跑,状态通常立刻变为 Optimal。记住,无解的本质是约束太紧,不是求解器坏了。
5.4 优化成本很好看,实操下来却亏钱
现象:调度结果算出的日成本比不调度便宜20%,但第二天执行完,财务口径一算,反而贵了8%。
原因:预测曲线过度乐观是元凶。光伏预测按晴天给,实际是多云天,光伏出力只有预测的一半,储能提前放空,下午用电高峰全靠电网高价电撑着,套利收益全部被预测误差吃掉。
解决:上线前必须做“预测-决策-执行”三级回测:用真实历史数据,在每天上午10点只输入当前时刻之前能拿到的信息重新跑调度,然后只执行前1小时指令,一小时后再刷新预测重算。这个过程完全模拟现场信息流,回测结果才接近真实收益。从那以后,我每次上线新模型,都强制先跑一周这种逐时滚动回测,日成本曲线不过关就不允许进现场。
6. 上线前必做的回测验证:对比表与两个进阶用法
回测是这套系统最后一道闸门。最简单的回测方式是逐日滚动:每天用截至当日的历史数据重新预测、重新求解,只取当天前4点的调度指令执行,然后用真实数据计算实际费用。核心代码框架是这样:
# 回测主循环 for day_idx in range(len(test_days)-1): # 用截止到当天的历史数据重训模型并预测未来一天 train = history[history.index <= test_days[day_idx]] pred = forecast(train) # 求解当日调度计划 plan = solve_milp(pred) # 只执行当天前4点(1小时)计划 daily_actual_cost.append( calc_cost(actual, plan.slice(0, 4)) )回测结束时,你会拿到一张对比表,这是判断系统值不值得上线的核心依据:
| 调度策略 | 日均用电成本 | 相对不调度的节约 |
|---|---|---|
| 无储能,全部按实时电价购电 | 3860元 | 基准 |
| 峰谷时段手工人为判断 | 3512元 | 9.0% |
| MILP优化调度(本系统) | 3287元 | 14.8% |
这个项目里,MILP相对人工调度多省了5个百分点以上的成本,主要来自模型能同时权衡SOC边界、电网功率上限和柴油机启停惩罚这三个约束,人工判断很难同时想清楚。
两个进阶用法我再补一下。第一,把调度计划接入FastAPI,每小时用新遥测数据重新求解一次,就能把日前计划升级成滚动计划,对外暴露/dispatch/next_hour接口,储能PCS按接口返回值执行。第二,如果想把MILP计划可视化,直接画24小时堆叠面积图,注意画图的时候每小时标一次横坐标就够了,15分钟粒度全标上去,横坐标挤成一片黑,连峰谷位置都看不出来。从那以后我每次上线调度模型,都会先在本地把三天历史数据重放一遍,一天不落,看过SOC曲线和成本曲线才敢往现场交。希望帮到你。
本文还有配套的精品资源,点击获取