案例:充电桩集群负载均衡
本案例演示如何仅用 Edge-EMS Builder 的通用能力——动态数据扫描、表达式计算、触发方案、限频控制——搭建出一套具备“设备数量可变、无需改脚本”能力的负载均衡策略。文中设备品牌、命名前缀均为示意,请替换为你项目中的实际命名。
一、业务背景
Section titled “一、业务背景”配电箱同时接入若干台充电桩(协议不限,可为 OCPP 或厂商私有协议)与其他必要负载(如照明、空调)。配电箱的电网侧进线容量固定,当所有充电桩同时全功率充电时,总用电量可能超过进线额定容量,导致跳闸或超容罚款。
目标:在保证必要负载优先获得供电的前提下,让所有在线充电桩共享剩余可用容量,动态调整每台桩的充电电流,任何一台桩上下线都能在数秒内完成再平衡,且新增充电桩不需要修改任何策略脚本。
二、功率模型
Section titled “二、功率模型”系统中有两个关键测量点:
Grid_total_power:电网侧实时输入总功率(含配电箱内所有负载:必要负载 + 充电桩 + 其他);- 必要负载已经体现在这个实测值里,因此只需要控制充电桩的总消耗,就能保证配电箱总输入不超过额定功率,必要负载自然被优先满足。
-- 可分配余量(kW)= (额定功率 - 实测电网总功率) × 安全系数-- 10% 安全裕量防止功率超调,实测值已含必要负载,无需单独扣除local avail_kw = (rated_kw - grid_kw) * 0.9local per_A = (avail_kw / n) * 1000 / voltage_V -- 均分为电流(A)三、动态设备发现:新增充电桩无需改脚本
Section titled “三、动态设备发现:新增充电桩无需改脚本”策略不预设充电桩数量。每次执行时,通过按前缀+后缀批量扫描的方式,自动发现所有在线充电桩:
local online = {}
-- 逐个品牌前缀扫描,可按需增减for _, brandPrefix in ipairs({"VENDORA", "VENDORB"}) do local statusMap = getLatestDataValuesByPrefixAndSuffix("Collection", brandPrefix, "_status") if statusMap then -- 返回值是以 dataKey 为键的 Table,用 pairs() 遍历;判空比较哨兵字符串 "__Null__" for dataKey, dto in pairs(statusMap) do if dto.DataValue ~= "__Null__" then table.insert(online, dto) end end endend只要新充电桩的采集点位按约定的命名规则接入(见下方“命名规则”),下一次触发时就会被自动纳入均分计算,不需要修改脚本、不需要重启服务。这正是 数据模型 与 Lua引擎 组合出来的能力,而非某个专属功能。
四、触发模型:数据刷新 + 脚本内限频
Section titled “四、触发模型:数据刷新 + 脚本内限频”如果单纯用固定周期轮询,会造成“数据没变也执行一次”的冗余写入;如果把限频条件也放进触发器,又会让条件配置变复杂。这里采用更简洁的模式:
数据刷新触发 + 脚本内时间戳限频:触发器只负责感知数据刷新,实际的指令下发频率由脚本自主控制。
数据刷新 → 满足条件 → 触发脚本 → 读取聚合点位 last_ctrl_ms(上次执行时间戳) → 距今 < 5000ms:直接返回,不下发指令 → 距今 ≥ 5000ms:执行控制 → 写入指令 → 刷新时间戳部署前置步骤:需要先手动创建一个聚合点位,作为脚本的“记忆”:
{ "dataKey": "last_ctrl_ms", "dataName": "充电控制上次执行时间戳", "expression": "return 0", "collectionCycle": -1}五、逐桩决策表
Section titled “五、逐桩决策表”每次触发后,脚本按顺序执行:读取电网参数 → 限频检查 → 扫描在线充电桩 → 计算可用电流 → 逐桩决策 → 刷新时间戳 → 返回指令列表。逐桩决策规则:
| 条件 | 充电桩状态 | 执行动作 |
|---|---|---|
| 超载 或 均分电流 < 最小电流 | 充电中 | 停止充电(stop_transaction) |
| 超载 或 均分电流 < 最小电流 | 其他 | 跳过 |
| 未超载 且 均分电流 ≥ 最小电流 | 充电中 | 下发新的限流值(set_charging_profile) |
| 未超载 且 均分电流 ≥ 最小电流 | 非充电中 | 启动充电(start_transaction,需授权 Tag 有效) |
对应的核心脚本片段(触发动作,返回写入描述数组):
local OffsetDateTime = require("OffsetDateTime")
local ratedKw = tonumber(${Grid_current} or 0) * tonumber(${Grid_voltage} or 0) * 0.001local gridKw = tonumber(${Grid_total_power} or 0)local overload = gridKw >= ratedKw
local voltage = tonumber(${Grid_voltage} or 1)local minChargePower = tonumber(${min_charge_power} or 4200)
local resultList = {}if #online > 0 then local availKw = (ratedKw - gridKw) * 0.9 local perA = math.floor((availKw / #online) * 1000 / voltage) local minA = math.floor(minChargePower / voltage)
for _, pile in ipairs(online) do local prefix = pile.DataKey:gsub("_status$", "") -- 从 dataKey 还原出充电桩前缀,如 VENDORA01 if overload or perA < minA then if pile.DataValue == "Charging" then table.insert(resultList, buildCommonWriteMap("Collection", prefix .. "_type", "stop_transaction", nil, nil, false)) end else if pile.DataValue == "Charging" then table.insert(resultList, buildCommonWriteMap("Collection", prefix .. "_periods", '[{"start":0,"limit":' .. perA .. '}]', nil, nil, false)) elseif ${id_tag} ~= "__Null__" then table.insert(resultList, buildCommonWriteMap("Collection", prefix .. "_type", "start_transaction", nil, nil, false)) end end endend
table.insert(resultList, buildCommonWriteMap("Aggregation", "last_ctrl_ms", OffsetDateTime.now():unixMilli(), nil, nil, false))return resultList六、命名规则:批量识别的前提
Section titled “六、命名规则:批量识别的前提”脚本按 {品牌前缀}{两位编号}_{属性} 的格式批量扫描充电桩采集点位,必须严格遵守:
| 部分 | 规则 | 示例 |
|---|---|---|
| 品牌前缀 | 全大写、不含下划线 | VENDORA、VENDORB(请替换为你实际使用的设备品牌标识) |
| 编号 | 两位零填充 | 01、02…10 |
| 属性 | 见下表 | status、type |
每台充电桩需要具备的采集点位(部分示例):
| 属性 | 完整 dataKey 示例 | 读/写 | 说明 |
|---|---|---|---|
_status |
VENDORA01_status |
读 | 充电桩状态,扫描依据,缺失则该桩不被发现 |
_transaction_id |
VENDORA01_transaction_id |
读 | 当前交易 ID |
_type |
VENDORA01_type |
写 | 指令类型:start/stop/set_charging_profile |
_periods |
VENDORA01_periods |
写 | 限额周期 JSON,如 [{"start":0,"limit":16}] |
新增第 6 台该品牌充电桩时,只需按上述命名接入其采集点位——VENDORA06_status、VENDORA06_type……即可自动被纳入均分逻辑。
七、如何应对未来变化
Section titled “七、如何应对未来变化”| 场景 | 是否需要改脚本 | 操作 |
|---|---|---|
| 新增同品牌充电桩 | 否 | 按命名规则新增采集点位即可 |
| 调整额定容量上限 | 否 | 修改聚合点位 Grid_current 的值 |
| 调整最小充电功率 | 否 | 修改聚合点位 min_charge_power 的值 |
| 充电桩临时下线 | 否 | 断开后 _status 变空,自动从在线列表排除 |
| 新增充电桩品牌 | 是 | 扫描列表新增一个前缀,如 {"VENDORA", "VENDORB", "VENDORC"} |
| 安全裕量系数可配置化 | 是(一次性) | 新增聚合点位承载该系数,脚本改为读取该点位 |
这张表本身也说明了 设计理念 里强调的一点:大部分业务变化只是改配置,只有少数结构性变化才需要改脚本,而且改动范围是可预期、可控的。
八、与其他能力的组合
Section titled “八、与其他能力的组合”- 若充电桩集群分布在多个子配电箱,可通过 设备协作 把各子配电箱的电网实测功率共享到主控设备,在主控上统一做全局均分。
- 若充电桩品牌型号会随项目不断增加,且不同项目的桩群结构相似,可通过 设备单元动态扩展 把“品牌前缀 + 命名规则 + 关联点位”沉淀为模板,缩短新项目的搭建时间。
- 若需要云端下发限流阈值、查看整体负载均衡状态,通过 云边协同 定期上报电网余量、各桩下发指令记录。
本图根据本页配置逻辑绘制,展示数据与控制流程。具体操作字段请参照文中的配置与脚本。