本文作者来自某能源行业,基于储能场景下的IoTDB的使用体验,讲述IoTDB的端到端加厚,聚焦储能站边缘网关(EdgeBox)的MQTT上报链路。

一、边缘网关在储能站中的角色
储能站现场部署有大量电池簇、PCS、BMS、温控与消防设备。这些设备通过 RS-485/CAN/以太网接入本地网络后,需要一条稳定、安全、可观测的通道将数据送达云端时序库。边缘网关(EdgeBox)正是承担这一职责的核心节点。
以某能源公司为例,其标准储能箱配置一台 EdgeBox,负责以下工作:
- 协议转换:将 Modbus、CAN、BMS 私有协议统一转换为 MQTT 报文
- 数据汇聚:按秒级周期采集数百至上千个测点,打包压缩后上报
- 边缘计算:本地完成越限判断、跳变检测,减少云端计算压力
- 离线缓冲:网络中断时本地缓存 >3 天,恢复后自动补发
- 远程运维:支持 OTA 升级、配置下发、日志回传

二、四遥测点详解
在电力与储能自动化领域,设备数据 traditionally 按「四遥」分类。理解这四类测点的语义与上报策略,是设计 MQTT 报文格式和 Topic 规则的前提。
四遥定义与典型测点:

设计要点 测点编码规范
每个测点在 EdgeBox 本地维护一张「测点表」,云端与现场通过统一编码对齐。建议采用 {deviceCode}_{pointNo} 的命名方式,例如:
// BMS 第 1 簇电池电压
BMS01_YC_001
// PCS 运行状态(遥信)
PCS01_YX_003
// 充放电功率设定值(遥调)
EMS01_DZ_005建议:测点编号在设备出厂时固化,现场仅做映射配置,避免编码漂移导致云端解析失败。
三、MQTT Topic 规则与报文格式
EdgeBox 通过 MQTT 将数据上报到云端 Broker。Topic 设计需要同时满足「路由可读」与「权限隔离」两个目标。
设计要点 Topic 命名规则
统一采用如下 Topic 模板:
edgeData/${siteAbbr}.${SN}其中:
siteAbbr:场站缩写,如
Station_HZ(示例场站)SN:EdgeBox 序列号,如
EB20240001(示例序列号)
完整示例:
edgeData/Station_HZ.EB20240001说明:使用 edgeData/ 前缀便于 Broker 做统一 ACL 规则;siteAbbr.SN 组合确保同一 Broker 下多站点多设备的 Topic 唯一性。
设计要点 报文格式
Payload 为 JSON 数组或单个 JSON 对象,每个元素包含以下字段:

示例报文(单条):
{
"object": "BMS01",
"timestamp": 1719201600000,
"metrics": {
"YC_001": 3.285,
"YC_002": 12.45,
"YX_003": 1,
"YX_004": 0
}
}示例报文(批量数组,推荐用于高频周期上报):
[
{
"object": "BMS01",
"timestamp": 1719201600000,
"metrics": { "YC_001": 3.285, "YC_002": 12.45 }
},
{
"object": "BMS01",
"timestamp": 1719201605000,
"metrics": { "YC_001": 3.286, "YC_002": 12.46 }
},
{
"object": "PCS01",
"timestamp": 1719201600000,
"metrics": { "YC_101": 150.0, "YX_103": 1 }
}
]注意:批量数组建议单条 MQTT 报文控制在 50 KB 以内,避免 Broker 单消息过大导致内存压力或客户端 OOM。
四、安全传输:zlib 压缩 + Base64 编码
储能站现场网络环境复杂,部分场站使用 4G/5G 公网卡,带宽有限且按流量计费。对 MQTT Payload 进行压缩和编码,可显著降低传输开销。
设计要点 压缩编码流程

代码示例 Java 压缩与编码工具
import java.io.ByteArrayOutputStream;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.zip.Deflater;
public class EdgePayloadEncoder {
/**
* 将 JSON 字符串压缩并编码为 Base64,用于 MQTT 上报。
*
* @param json 原始 JSON 字符串
* @return Base64 编码后的压缩字符串
*/
public static String encode(String json) {
byte[] input = json.getBytes(StandardCharsets.UTF_8);
Deflater deflater = new Deflater();
deflater.setInput(input);
deflater.finish();
try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream(input.length)) {
byte[] buffer = new byte[1024];
while (!deflater.finished()) {
int count = deflater.deflate(buffer);
outputStream.write(buffer, 0, count);
}
deflater.end();
byte[] compressed = outputStream.toByteArray();
return Base64.getEncoder().encodeToString(compressed);
} catch (Exception e) {
deflater.end();
throw new RuntimeException("Payload encode failed", e);
}
}
/**
* 云端解码示例:Base64 解码 → zlib 解压 → JSON 字符串
*/
public static String decode(String base64Payload) {
byte[] compressed = Base64.getDecoder().decode(base64Payload);
java.util.zip.Inflater inflater = new java.util.zip.Inflater();
inflater.setInput(compressed);
try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream(compressed.length)) {
byte[] buffer = new byte[1024];
while (!inflater.finished()) {
int count = inflater.inflate(buffer);
outputStream.write(buffer, 0, count);
}
inflater.end();
byte[] result = outputStream.toByteArray();
return new String(result, StandardCharsets.UTF_8);
} catch (Exception e) {
inflater.end();
throw new RuntimeException("Payload decode failed", e);
}
}
// 使用示例
public static void main(String[] args) {
String json = "{\"object\":\"BMS01\",\"timestamp\":1719201600000," +
"\"metrics\":{\"YC_001\":3.285,\"YC_002\":12.45}}";
String encoded = encode(json);
System.out.println("Encoded length: " + encoded.length());
System.out.println("Decoded: " + decode(encoded));
}
}经验:对于高重复度的 YC 周期数据,zlib 压缩比通常可达 5:1 以上;YX 变位上报数据量小,压缩收益有限,可直接以原始 JSON 上报以节省 CPU。
五、上传策略:周期、变位与秒级变化
不同遥测类型的业务价值不同,统一按固定周期上报会造成带宽浪费,也无法满足实时告警需求。EdgeBox 采用差异化上传策略。
三类上传策略

设计要点 电芯级数据秒级变化上报
储能站电芯数量庞大(一个 100MWh 站可能超过 10 万节电芯),如果全部按固定周期上报,云端写入压力巨大。EdgeBox 在本地维护电芯数据缓存,仅当单体电压或温度变化超过设定阈值(如 ±2mV 或 ±0.1°C)时才打包上报。
// 电芯级数据秒级变化上报伪代码
public void onCellData(CellData data) {
String key = data.getCellId() + "_" + data.getMetric();
Double last = cache.get(key);
if (last == null || Math.abs(data.getValue() - last) > threshold) {
cache.put(key, data.getValue());
mqttClient.publish(buildTopic(), encode(toJson(data)), QoS.AT_LEAST_ONCE);
}
}注意:秒级变化策略需配合「全量同步」机制,例如每小时强制上报一次全量电芯数据,防止因漏报导致云端数据长期不一致。
六、离线缓冲与断网恢复
储能站现场网络不稳定(4G 信号弱、运营商割接、雷雨天气)是常态。EdgeBox 必须具备本地缓存与断网续传能力,保证数据完整性。
离线缓冲机制
本地缓存容量:
不低于 3 天全量数据(按 10s 周期、1000 测点估算约 2.5 GB)
存储介质:
eMMC/SSD 环形缓冲区,写满后按时间戳覆盖最旧数据
缓存格式:
二进制顺序文件,每条记录含时间戳、Topic、压缩后 Payload
恢复策略:
网络恢复后,按时间顺序批量补发,限速防止冲击云端
补发优先级:
YX 变位 > YK 遥控确认 > YC 周期 > 电芯级变化
设计要点 补发限速与去重
断网恢复后,EdgeBox 可能积压数万条报文。直接全速补发会导致云端 Broker 瞬时流量激增,甚至触发限流。建议采用「令牌桶」限速策略:
// Guava RateLimiter(令牌桶限速)// Maven: com.google.guava:guava:32.x
RateLimiter limiter = RateLimiter.create(100.0);
public void replayBufferedMessages() {
List<BufferedRecord> records = buffer.readAllOrdered();
for (BufferedRecord r : records) {
limiter.acquire();
mqttClient.publish(r.getTopic(), r.getPayload(), QoS.AT_LEAST_ONCE);
buffer.markAsSent(r.getOffset());
}
}关键:云端转码服务需实现幂等写入,对相同时间戳、相同设备、相同测点的重复报文做去重或覆盖,避免补发导致时序库数据重复。
七、完整链路回顾
以下是 EdgeBox 从数据采集到 MQTT 上报的完整流程,展示各层如何协作:

八、写在最后 + 下一篇预告
边缘网关是储能站数据上云的「第一跳」,其设计质量直接决定了后续时序存储、实时展示和能量调度的数据基础。本文总结的四遥分类、Topic 规则、报文格式、压缩编码与离线缓冲策略,均来自某能源公司真实场站的落地经验,已做脱敏处理。
实际部署时,建议根据场站规模调整:小型场站可降低 YC 上报周期至 30s,减少带宽;大型场站需关注 EdgeBox 本地缓存容量与补发限速,防止恢复时冲击云端。
下一篇我们将进入云端,讲解 MQTT 报文如何被转码服务解析、校验并写入 IoTDB 时序库。
下一篇预告
IoTDB-Link 转码服务:从 MQTT 报文到时序库写入
拆解云端转码服务的架构设计,包括 MQTT 订阅、Base64 解码、zlib 解压、JSON 解析、IoTDB 路径映射与批量写入,附带 Java 实现与性能调优建议。