一、先搞清楚:EMS 到底在做什么
能源管理系统(Energy Management System,EMS)不是单纯"画几个能耗曲线",它的本质是:
把电、水、气、热、光伏、储能等能源数据采上来 → 存好 → 算指标 → 看板展示 → 异常告警 → 指导节能决策。
一个能用的 EMS 至少覆盖这条链路:
智能电表/水表/气表/PLC/光伏逆变器
↓ Modbus / MQTT / OPC UA
边缘采集网关(协议转换+预处理)
↓ Kafka / MQTT Broker
后端微服务(采集/清洗/计算/告警)
↓
MySQL(业务) + InfluxDB(时序)
↓
Vue3 + ECharts 大屏 / 报表 / 接口
二、总体架构设计(分层解耦)
推荐"云---边---端"五层架构:
| 层级 | 职责 | 典型技术 |
|---|---|---|
| 感知层 | 电表、水表、气表、PLC、传感器 | Modbus RTU/TCP、DL/T 645、BACnet |
| 边缘层 | 协议解析、数据清洗、断网续传 | 工业网关、Python/Go 采集程序 |
| 平台服务层 | 接收、计算、告警、报表 | Spring Boot / Flask + Kafka |
| 数据存储层 | 时序数据 + 关系数据 | InfluxDB/TimescaleDB + MySQL |
| 应用展示层 | 大屏、Web、移动端、API | Vue3 + ECharts + Grafana |
中小项目可先单机 Docker 部署;测点过万再拆微服务 + K8s。
三、技术栈选型(Java 体系为例)
后端:
-
Spring Boot 2.7+ / Spring Cloud Alibaba
-
网关:Spring Gateway
-
注册配置:Nacos
-
消息队列:Kafka 或 RabbitMQ(高频数据异步削峰)
-
时序库:InfluxDB / TimescaleDB / TDengine
-
关系库:MySQL 8.x(设备档案、用户、报表模板)
-
缓存:Redis(实时值、Token、限流)
-
实时推送:WebSocket
前端:
-
Vue3 + Element Plus + Pinia
-
ECharts(折线/柱/饼/热力/仪表盘)
-
大屏自适应方案(scale 或 vw/vh)
边缘采集:
-
Python:
pymodbus+paho-mqtt -
或 Go:高性能网关采集
-
支持断网本地缓存、网络恢复续传

云捷EMS管理系统界面
四、核心功能模块怎么拆
-
设备管理:能源类型(电/水/气/热)、计量点、所属车间、采集频率、寄存器地址
-
数据采集:Modbus 轮询 / MQTT 订阅 / OPC UA 客户端
-
数据清洗:去抖、量程校验、空值补差、重复值去重
-
指标计算:
-
总能耗、分项能耗
-
单位产值能耗 kWh/万元
-
峰谷平电量、需量
-
同比/环比、COP、碳排放(IPCC 因子)
-
-
实时监控:WebSocket 推实时功率,ECharts 画曲线
-
告警中心:阈值/突变/离线,钉钉/短信/邮件
-
报表引擎:日报/月报/碳报告,EasyExcel 导出
-
权限与租户:RBAC,园区多租户分账

五、关键代码思路(伪代码级)
1)Modbus TCP 采集(Python 边缘端)
from pymodbus.client import ModbusTcpClient
import paho.mqtt.client as mqtt
import json, time
client = ModbusTcpClient('192.168.1.50', port=502)
mqtt_cli = mqtt.Client()
mqtt_cli.connect('broker.emqx.io', 1883)
while True:
# 读保持寄存器:电压/电流/有功功率
res = client.read_holding_registers(address=0, count=10, slave=1)
if res.isError():
time.sleep(5); continue
payload = {
"meter_id": "M-001",
"power_kw": res.registers[0] / 10.0,
"ts": int(time.time() * 1000)
}
mqtt_cli.publish('ems/meters/M-001', json.dumps(payload))
time.sleep(1)
2)后端 Kafka 消费写时序库(Java)
@KafkaListener(topics = "energy-topic", groupId = "ems-core")
public void onMessage(String msg) {
EnergyPoint p = JSON.parseObject(msg, EnergyPoint.class);
// 1. 写 InfluxDB(高频时序)
influxWriter.write(p.getMeterId(), p.getTs(), p.getPowerKw());
// 2. 实时值刷 Redis(前端轮询/WS 用)
redis.set("rt:" + p.getMeterId(), p.getPowerKw());
}
3)前端 WebSocket 实时功率
const ws = new WebSocket('ws://localhost:8080/ws/realtime')
ws.onmessage = (e) => {
const d = JSON.parse(e.data)
powerChart.appendPoint(d.ts, d.powerKw)
}
六、数据库建模要点(最容易踩坑)
不要把所有秒级数据塞 MySQL!
-
MySQL:存
meter_meta(设备元信息)、alarm_rule、report_task、user -
InfluxDB measurement:
energy_power,tag=meter_id,energy_type,field=value -
聚合表:分钟/小时/天级统计单独落 MySQL 或 InfluxDB CQ(连续查询)
-
生命周期:原始 3~6 个月,聚合数据留 3~5 年
七、开发落地步骤(建议节奏)
-
需求边界:电水气?要不要光伏储能?是否对接 MES/ERP?
-
点位表梳理:哪块表、什么协议、寄存器地址、采集周期
-
先通采集:边缘程序跑通 Modbus→MQTT
-
再通存储:Kafka→InfluxDB,确认写入延迟 < 2s
-
做最小前端:总能耗 + 实时功率 + 日报表
-
加告警和分析:阈值告警、同比环比、单位产品能耗
-
接业务系统:OA 推送、碳核算、租户分账
-
上线运维:Docker Compose 起步,后期 K8s
八、工程避坑指南
-
时钟不同步:所有边缘机装 NTP,否则时序乱序聚合错
-
Modbus 轮询阻塞:单线程轮询 200 块表会卡,按网关分片并发
-
浮点量程:很多表是"寄存器值×变比",不处理会差 10 倍
-
断网丢数:边缘必须本地 SQLite/文件缓冲,恢复后续传
-
等保合规:TLS 传输、JWT 鉴权、操作审计,别等验收再补
九、想快一点?参考开源底座
-
云捷EMS:Python + React + InfluxDB,Modbus/MQTT/BACnet 全支持,适合二开
-
云捷EMS :仓库地址:https://gitee.com/guangdong122/energy-management
-
自研建议:边缘采集自写,平台层参考 MyEMS 结构
如果需要云捷EMS源码合作服务,可联系作者