1. 项目全景与源码导览

在动手写第一行代码之前,先把整个项目"看一遍"是很有必要的。SCADA(Supervisory Control and Data Acquisition,数据采集与监视控制)能源管理系统并不是一个单纯的"采集数据画曲线"的工具,它同时承担着现场设备接入、实时监控、能耗统计、告警联动和报表输出等多重职责。本章的目标是建立一张"项目地图":搞清楚系统要解决什么业务问题、边界划在哪里、用什么技术栈实现、源码目录如何组织、模块之间如何协作。后续所有章节的编码工作,都会落在这张地图的某个具体位置上。

一、业务目标:从"看得见"到"管得住"

能源管理系统的核心业务目标可以概括为三层递进关系。第一层是可视:把分散在配电房、车间、楼宇的电力仪表、水表、气表、温湿度传感器数据实时汇聚到统一界面,让运维人员不再依赖现场抄表。第二层是可算:基于采集到的有功功率、电流、电压等原始量,按时间维度(15分钟、小时、日、月)计算用电量、峰谷平电量、单位产品能耗、同比环比等指标。第三层是可控:当某条回路电流超限、功率因数偏低或能耗异常升高时,系统能触发告警、推送通知,并在具备条件时下发控制指令(如切除非关键负载)。

以一个典型的工业园区项目为例,假设接入 120 台多功能电表、30 台水表、20 台气体流量计,采集周期为 5 秒,那么系统每天产生的原始点位数据约为 (120+30+20)×(86400/5) ≈ 294 万条。这个量级决定了系统不能简单地"来一条存一条",而必须在采集层做批量缓冲、在存储层做分级归档。业务目标直接决定了后面的技术选型,这是本章需要建立的第一条因果链。

二、功能边界:明确"做什么"与"不做什么"

功能边界不清是项目后期返工的主要原因。本系统的功能边界可以这样界定:

  • 包含:设备通信接入(Modbus TCP/RTU、IEC 104、OPC UA)、实时数据采集与缓存、实时监控画面、历史数据存储与查询、能耗统计与报表、告警产生与推送、用户权限管理、系统日志。
  • 不包含:底层 PLC 逻辑控制程序、继电保护定值整定、企业 ERP 财务核算、视频监控。这些系统通过接口与本系统交换数据,但不属于本系统的实现范围。

边界之外的部分,系统只提供"接口"而非"实现"。例如与 ERP 的对接,本系统只负责按约定格式导出能耗数据文件或提供 REST 接口,不负责 ERP 侧的入库逻辑。把这条线画清楚,源码目录中就不会出现职责混乱的模块。

三、技术栈选型:为什么是这一套

技术栈的选择必须服务于前面的业务目标和数据量级。本项目的选型如下:

层次技术选型选型理由
采集层Python + pymodbus / asyncua协议库成熟,异步 IO 适合高并发轮询
通信中间件MQTT (EMQX) / Redis Stream解耦采集与处理,削峰填谷
后端服务Java Spring Boot / Go生态完善,适合长期维护的企业级服务
实时库Redis + InfluxDBRedis 存最新值,InfluxDB 存时序历史
关系库MySQL / PostgreSQL存设备台账、用户、告警规则等结构化数据
前端Vue 3 + ECharts组态画面与曲线图表开发效率高
部署Docker Compose / K8s便于现场私有化部署与升级

需要特别说明的是"双库"设计:Redis 保存每个测点的最新值和短时缓存,供监控画面秒级刷新;InfluxDB 按时间序列保存历史数据,供趋势查询和统计计算。两者职责不重叠,避免了用关系库硬扛高频写入导致的性能瓶颈。

四、源码目录结构与模块划分

项目采用前后端分离 + 采集服务独立部署的结构。顶层目录划分如下:

scada-energy/
├── collector/          # 采集服务(Python)
│   ├── drivers/        # 协议驱动:modbus_tcp.py, iec104.py, opcua.py
│   ├── scheduler.py    # 轮询调度与任务编排
│   └── publisher.py    # 数据发布到 MQTT/Redis
├── server/             # 后端服务(Spring Boot)
│   ├── gateway/        # 接口网关与鉴权
│   ├── realtime/       # 实时数据处理与推送
│   ├── history/        # 历史查询与统计
│   ├── alarm/          # 告警规则引擎
│   └── report/         # 报表导出
├── web/                # 前端(Vue 3)
│   ├── views/          # 监控画面、报表、告警页
│   └── components/     # 组态元件、图表封装
├── deploy/             # docker-compose、配置模板
└── docs/               # 接口文档、点位表、部署说明

模块划分遵循"高内聚、低耦合"原则:采集服务只负责"把数据拿回来并发布出去",不关心数据如何展示;后端 realtime 模块只消费实时流并推送给前端,不直接访问设备;history 模块只处理已落库的数据。这样任何一个模块出问题,影响范围都是可控的。

建议在项目初期就建立一份《点位表》(Point List),字段至少包含:点位ID、设备ID、协议地址、数据类型、量纲、采集周期、是否存储、是否告警。这份表是采集、存储、展示三个模块共同的"契约",后续章节会反复用到。

五、模块协作关系全景

下面的 SVG 展示了从现场设备到最终用户的完整数据链路与模块依赖关系。图中区分了采集侧、服务侧、存储侧和展示侧四个区域,箭头方向表示数据流向,虚线表示控制/配置的反向通道。

现场设备层 采集服务层 服务与存储层 展示层 多功能电表 ×120 Modbus TCP 水表 / 气表 ×50 Modbus RTU 保护装置 / 网关 IEC 104 / OPC UA collector 采集服务 drivers 协议驱动 scheduler 轮询调度 publisher 数据发布 MQTT / Redis Stream 消息中间件(解耦缓冲) realtime 实时处理 最新值推送 / 越限判断 history 历史统计 电量计算 / 趋势查询 alarm 告警引擎 规则匹配 / 通知推送 Redis + InfluxDB + MySQL 实时缓存 / 时序历史 / 台账 监控画面 Vue 3 报表 / 告警 ECharts 控制/配置下发 数据流:设备 → 采集 → 中间件 → 服务 → 存储 → 展示 控制流(虚线):服务 → 采集 → 设备(参数下发、遥控) 数据流 控制流

从图中可以清楚看到,采集服务是整个系统的"入口",它通过协议驱动从现场设备读取数据,经调度器按周期组织后发布到消息中间件。后端服务从中间件消费数据,分三条路径处理:实时路径写入 Redis 并推送给前端;历史路径写入 InfluxDB 供统计查询;告警路径交给规则引擎判断。前端只与后端服务交互,不直接接触采集层和数据库。这种分层设计使得每一层都可以独立替换或扩容——例如采集服务从 Python 换成 Go,只要发布到 MQTT 的消息格式不变,后端完全无感知。

六、从全景到代码:后续章节的落点

有了这张全景图,后续章节的定位就非常清晰了:第2~5章会深入 collector 目录,讲解 Modbus 驱动如何封装、轮询任务如何调度、断线重连如何处理;第6~10章进入 server 的 realtime 和 history 模块,讨论实时数据如何缓存、电量如何按时间段积分计算;第11~15章围绕 alarm 和 report 展开;第16章之后进入前端组态与部署运维。每一章都会对应到本章目录结构中的某个具体文件或模块。

初学者常见的误区是"先写界面再补采集"。在 SCADA 项目中,数据链路是根基,界面只是数据的呈现。建议按照"点位表 → 采集驱动 → 数据存储 → 接口服务 → 前端展示"的顺序推进,每完成一层就用真实或模拟数据验证一次,避免到最后才发现协议对不上、量纲不一致。

本章不涉及具体编码,但建立的项目全景认知会贯穿整个课程。请对照源码目录,在本地把项目骨架跑起来(哪怕采集服务暂时用模拟数据源),确认 MQTT、Redis、InfluxDB、MySQL 四个基础组件都能正常连接。这是后续所有章节能够顺利推进的前提。