EnerOS v0.18.0
发布日期:2025年04月20日 版本代号:Scada Git Tag:v0.18.0 支持状态:稳定(Stable) Crate 总数:50(新增 4 个) 测试用例数:6920+(新增 540)
版本概述
EnerOS v0.18.0「Scada」是 EnerOS 在「现场设备接入」方向上的核心版本,本版本的核心目标是深度集成 SCADA(Supervisory Control and Data Acquisition)系统,重点增强 IEC 60870-5-104 协议栈,实现与变电站 RTU、厂站测控装置、保护装置的实时遥测采集、遥控命令下发与 SOE(Sequence of Events)事件记录,使 EnerOS 能够直接作为省级调度主站与现场设备通信。
SCADA 是电力调度的「神经末梢」,负责采集现场设备的运行状态与测量数据,并将调度指令下发至执行机构。IEC 60870-5-104 是国际标准化的远动协议(基于 TCP/IP),广泛应用于中国、欧洲、亚洲等地区的调度自动化系统。v0.9.0 曾引入基础的 Modbus 与 IEC 104 接入能力,但仅支持简单遥测读取。v0.18.0 将 IEC 104 协议栈升级为生产级实现,支持全类型遥测(TI)、遥信(DI)、电度(CI)、遥控(TC)、遥调(TI_setpoint),并内置 SOE 事件序列记录与防抖处理。
本版本引入了 eneros-scada(SCADA 核心)、eneros-scada-iec104(IEC 104 协议栈)、eneros-scada-rtu(RTU 管理)、eneros-scada-soe(SOE 事件记录)四个新 crate。设计哲学是「协议原生,数据原生」——SCADA 协议栈直接在 Rust 内核实现,避免 C 库绑定带来的安全与性能问题,采集的数据直接进入 EnerOS 时序存储与管道,无需中转。
关键数据
| 指标 | v0.9.0(基础) | v0.18.0(增强) | 提升 |
|---|---|---|---|
| IEC 104 连接数 | 32 | 1024 | 32x |
| 遥测采集延迟 | 2s | 80ms | 25x |
| 遥控执行延迟 | 5s | 150ms | 33x |
| SOE 分辨率 | 10ms | 1ms | 10x |
| 协议覆盖率 | 30% | 95% | +65pp |
新特性
1. SCADA 系统集成
引入 eneros-scada crate,作为 SCADA 子系统的统一入口,管理所有 RTU/IED 连接、点表配置、数据采集任务与控制命令队列。SCADA 子系统与 EnerOS 管道、时序存储、孪生体深度集成,采集的数据自动流入后续处理链。
SCADA 主站配置
use eneros_scada::{ScadaMaster, ScadaConfig, RtuConnection};
let mut master = ScadaMaster::new(ScadaConfig {
max_connections: 1024,
poll_interval: Duration::milliseconds(500),
command_timeout: Duration::seconds(5),
soe_buffer_size: 100_000,
auto_reconnect: true,
reconnect_backoff: Backoff::exponential(Duration::seconds(1), Duration::minutes(5)),
})?;
// 添加 RTU 连接
master.add_rtu(RtuConnection::iec104()
.id("rtu-substation-1")
.address("10.20.30.1:2404")
.common_address(1)
.timeout(Duration::seconds(10))
.time_sync(true))?;
// 启动 SCADA 主站
master.start().await?;
点表配置
use eneros_scada::{PointTable, PointType, PointId};
// 配置点表(遥测/遥信/电度)
let table = PointTable::new()
// 遥测点(TI)
.point(PointId::ti(1, 1), PointType::Float, "母线1电压", "kV")
.point(PointId::ti(1, 2), PointType::Float, "母线1频率", "Hz")
.point(PointId::ti(1, 3), PointType::Float, "线路12有功", "MW")
// 遥信点(DI)
.point(PointId::di(1, 1), PointType::Single, "断路器1状态", "")
.point(PointId::di(1, 2), PointType::Single, "隔离开关2状态", "")
// 电度点(CI)
.point(PointId::ci(1, 1), PointType::Integrated, "总有功电度", "kWh")
// 遥控点(TC)
.point(PointId::tc(1, 1), PointType::Single, "断路器1遥控", "")
// 遥调点(Setpoint)
.point(PointId::sp(1, 1), PointType::Float, "AVR电压给定", "kV");
master.load_point_table(table)?;
点类型映射
| IEC 类型 | 缩写 | 说明 | 数据格式 | 方向 |
|---|---|---|---|---|
| 遥测 | TI | 测量值 | float | 上行 |
| 遥信 | DI | 状态量 | bool | 上行 |
| 电度 | CI | 累计量 | int64 | 上行 |
| 遥控 | TC | 单点控制 | bool | 下行 |
| 遥调 | SP | 设定值 | float | 下行 |
2. IEC 60870-5-104 协议增强
引入 eneros-scada-iec104 crate,实现生产级 IEC 60870-5-104 协议栈,完整支持 APCI 帧格式、ASDU 应用服务数据单元、I 帧 / S 帧 / U 帧三种帧类型,并实现平衡式与非平衡式传输模式。
协议帧结构
use eneros_scada_iec104::{Frame, FrameType};
// I 帧(信息帧):承载数据
let i_frame = Frame::i(
send_seq: 1024,
recv_seq: 512,
asdu: Asdu::single_point_info(
common_address: 1,
ioa: 1,
value: true,
quality: Quality::ok(),
time: Some(now()),
),
);
// S 帧(监视帧):确认接收
let s_frame = Frame::s(recv_seq: 1024);
// U 帧(控制帧):启动/停止/测试
let u_frame = Frame::u(FrameType::StartDt);
ASDU 类型支持
| 类型码 | 名称 | 方向 | 说明 |
|---|---|---|---|
| <1> | 单点遥信 | 上行 | DI 单点 |
| <3> | 双点遥信 | 上行 | DI 双点 |
| <9> | 归一化遥测 | 上行 | TI 标度值 |
| <13> | 浮点遥测 | 上行 | TI IEEE 754 |
| <15> | 累计电度 | 上行 | CI |
| <30> | 单点 SOE | 上行 | 带时标 DI |
| <45> | 单点遥控 | 下行 | TC |
| <50> | 设定值 | 下行 | SP |
| <70> | 时钟同步 | 下行 | 对时 |
链路层管理
use eneros_scada_iec104::{LinkLayer, LinkConfig};
let link = LinkLayer::new(LinkConfig {
t1: Duration::seconds(15), // 等待 ACK 超时
t2: Duration::seconds(10), // 发送 ACK 延迟
t3: Duration::seconds(20), // 空闲测试间隔
k: 12, // 未确认 I 帧上限
w: 8, // 收到 I 帧后发 S 帧阈值
});
// 自动链路维护
link.on_disconnect(|rtu_id| {
log::warn!("RTU {} 链路断开", rtu_id);
alert::notify(format!("RTU {} 通信中断", rtu_id));
});
link.on_reconnect(|rtu_id| {
log::info!("RTU {} 链路恢复", rtu_id);
});
3. 实时遥测数据采集
SCADA 主站持续轮询 RTU 的遥测数据,并通过 EnerOS 管道实时发布,时序存储与孪生体同步消费。采集支持周期轮询与突变上报两种模式,兼顾实时性与带宽效率。
数据采集流程
use eneros_scada::collector::Collector;
let collector = Collector::new(&master)
// 周期采集:每 500ms 采集所有遥测
.poll(PollConfig {
interval: Duration::milliseconds(500),
strategy: PollStrategy::grouped_by_rtu,
})
// 突变上报:变化超过阈值时立即上报
.spontaneous(SpontaneousConfig {
enable: true,
deadband: Deadband::relative(0.01), // 1% 变化
min_interval: Duration::milliseconds(100),
})
// 采集回调
.on_telemetry(|point| {
// 发布到管道
pipeline.publish(
Topic::of(format!("telemetry.rtu.{}.{}", point.rtu_id, point.ioa)),
Event::telemetry(point),
)?;
Ok(())
});
collector.start().await?;
采集性能指标
| 采集模式 | RTU 数 | 点数 | 采集延迟 | 带宽占用 |
|---|---|---|---|---|
| 周期 500ms | 1024 | 50万 | 80ms | 12 Mbps |
| 周期 1s | 1024 | 50万 | 120ms | 6 Mbps |
| 突变上报 | 1024 | 50万 | 20ms | 2 Mbps |
| 混合模式 | 1024 | 50万 | 50ms | 4 Mbps |
数据质量标记
use eneros_scada::Quality;
// 每个遥测点附带质量标记
match point.quality {
Quality::Ok => { /* 数据有效 */ }
Quality::Invalid => { /* 数据无效 */ }
Quality::NonTopical => { /* 数据非当前 */ }
Quality::Substituted => { /* 数据被替换 */ }
Quality::Blocked => { /* 数据被阻塞 */ }
Quality::Overflow => { /* 数据溢出 */ }
}
4. 遥控命令下发
支持通过 IEC 104 协议向 RTU 下发遥控命令(单点遥控、双点遥控、设定值),所有命令经过 Guardian 守卫校验,并支持「选择-执行(Select-Before-Operate)」两步操作,防止误操作。
遥控操作
use eneros_scada::command::{Command, SelectOperate};
// 单点遥控:合闸
let cmd = Command::single_point(
rtu_id: "rtu-substation-1",
common_address: 1,
ioa: 1,
value: true, // 合闸
);
// 选择-执行模式(SBO)
let so = SelectOperate::new(&master)
.select(&cmd).await? // 选择
.confirm_within(Duration::seconds(10)) // 10秒内确认
.operate().await?; // 执行
match so.result {
Ok(()) => log::info!("遥控成功: {}", cmd),
Err(e) => log::error!("遥控失败: {}", e),
}
直接执行模式
// 直接执行(仅限非关键操作,需 Guardian 授权)
let cmd = Command::set_point(
rtu_id: "rtu-substation-1",
common_address: 1,
ioa: 1,
value: 110.0, // 电压给定 110kV
);
// 经 Guardian 校验后直接下发
ctx.guardian().check(&cmd).await?;
master.execute_direct(&cmd).await?;
遥控操作模式对比
| 模式 | 步骤 | 安全性 | 适用场景 |
|---|---|---|---|
| SBO | 选择→确认→执行 | 高 | 关键操作 |
| 直接执行 | 直接下发 | 中 | 设定值调整 |
| 预置执行 | 预置→执行 | 较高 | 批量操作 |
5. SOE 事件记录
引入 eneros-scada-soe crate,记录电网事件序列(SOE),支持 1ms 级事件分辨率,用于故障分析与事故追溯。SOE 事件带 GPS 时标,确保全站事件时间一致性。
SOE 记录
use eneros_scada_soe::{SoeLog, SoeEvent};
let soe = SoeLog::new(SoeConfig {
buffer_size: 1_000_000,
resolution: Duration::milliseconds(1),
time_source: TimeSource::gps(),
debounce: DebounceConfig::disabled(), // SOE 不防抖
});
// 自动记录事件
soe.on_event(|event| {
let evt = SoeEvent {
timestamp: event.time, // GPS 时标
rtu_id: event.rtu_id,
ioa: event.ioa,
value: event.value,
description: point_table.describe(event.rtu_id, event.ioa),
};
// 写入持久存储
soe.persist(&evt).await?;
// 发布到管道
pipeline.publish("soe.event", Event::soe(evt))?;
});
SOE 检索
// 检索故障期间的事件序列
let events = soe.query()
.rtu("rtu-substation-1")
.range("2025-04-20T14:30:00Z", "2025-04-20T14:35:00Z")
.order(OrderBy::Ascending)
.execute()?;
for evt in events {
println!("{:?} RTU={} IOA={} {} = {}",
evt.timestamp, evt.rtu_id, evt.ioa, evt.description, evt.value);
}
事件序列示例
| 时间戳 | RTU | IOA | 描述 | 值 |
|---|---|---|---|---|
| 14:30:00.123 | rtu-1 | 5 | 主变差动保护 | 跳闸 |
| 14:30:00.125 | rtu-1 | 12 | 主变高压侧开关 | 分 |
| 14:30:00.128 | rtu-2 | 3 | 备自投装置 | 动作 |
| 14:30:00.135 | rtu-2 | 7 | 母联开关 | 合 |
改进
- 时序存储:时序写入路径优化,支持 SCADA 高频写入(50 万点/秒)
- 孪生体:孪生体直接订阅 SCADA 管道,状态同步延迟降至 80ms
- 守卫系统:遥控命令自动经过 Guardian 校验,危险操作拦截
- 仪表盘:新增 SCADA 监控面板,实时展示 RTU 连接状态与数据质量
Bug 修复
- 修复
eneros-scada-iec104在 RTU 数量超过 256 时连接泄漏的问题(#1809) - 修复
eneros-scada遥控命令在 SBO 模式下超时未释放选择锁的问题(#1815) - 修复
eneros-scada-soe在 GPS 时钟跳变时事件乱序的问题(#1821) - 修复
eneros-scada-iec104在链路重连后 ASDU 序号错乱的问题(#1827)
破坏性变更
Gateway::add_iec104:迁移至ScadaMaster::add_rtu,原方法废弃PointTable::load:改为 async,需.awaitCommand::execute:默认改为 SBO 模式,直接执行需execute_direct
性能提升
- IEC 104 连接数从 32 提升至 1024(提升 32 倍)
- 遥测采集延迟从 2s 降至 80ms(提升 25 倍)
- 遥控执行延迟从 5s 降至 150ms(提升 33 倍)
- SOE 事件分辨率从 10ms 提升至 1ms(提升 10 倍)
贡献者
本版本由 22 位贡献者共同完成,提交 388 次。特别感谢:
- @scada-core:SCADA 主站与点表管理
- @iec104-implementer:IEC 104 协议栈 Rust 实现
- @rtu-manager:RTU 连接与链路维护
- @soe-master:SOE 事件序列与 GPS 对时
升级指南
从 v0.17.0 升级
1. 更新依赖
# Cargo.toml
[dependencies]
eneros-scada = { version = "0.18" }
eneros-scada-iec104 = { version = "0.18" }
eneros-scada-rtu = { version = "0.18" }
eneros-scada-soe = { version = "0.18" }
2. 迁移 SCADA 配置
// v0.9.0 旧写法
gateway.add_iec104("rtu-1", "10.20.30.1:2404")?;
// v0.18.0 新写法
master.add_rtu(RtuConnection::iec104()
.id("rtu-1")
.address("10.20.30.1:2404")
.common_address(1))?;
3. 配置点表
# eneros.toml
[scada]
enabled = true
max_connections = 1024
poll_interval_ms = 500
[[scada.rtu]]
id = "rtu-substation-1"
address = "10.20.30.1:2404"
common_address = 1
time_sync = true
[[scada.points]]
id = "ti:1:1"
type = "float"
name = "母线1电压"
unit = "kV"
4. 启用 SOE 记录
建议生产环境启用 SOE 记录,并配置 GPS 时钟源,确保事件序列精度满足事故分析要求:
[scada.soe]
enabled = true
buffer_size = 1000000
resolution_ms = 1
time_source = "gps"
5. 遥控安全配置
所有遥控操作建议启用 SBO 模式,并接入 Guardian 守卫校验:
let so = SelectOperate::new(&master)
.guardian(ctx.guardian()) // 接入守卫
.select(&cmd).await?
.confirm_within(Duration::seconds(10))
.operate().await?;