首页
>
资源
>
用户案例

储能站数据上送:MQTT 协议与边缘网关实践

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

120260630.webp

一、边缘网关在储能站中的角色

储能站现场部署有大量电池簇、PCS、BMS、温控与消防设备。这些设备通过 RS-485/CAN/以太网接入本地网络后,需要一条稳定、安全、可观测的通道将数据送达云端时序库。边缘网关(EdgeBox)正是承担这一职责的核心节点。

以某能源公司为例,其标准储能箱配置一台 EdgeBox,负责以下工作:

  • - 协议转换:将 Modbus、CAN、BMS 私有协议统一转换为 MQTT 报文

  • - 数据汇聚:按秒级周期采集数百至上千个测点,打包压缩后上报

  • - 边缘计算:本地完成越限判断、跳变检测,减少云端计算压力

  • - 离线缓冲:网络中断时本地缓存 >3 天,恢复后自动补发

  • - 远程运维:支持 OTA 升级、配置下发、日志回传

220260630.webp

二、四遥测点详解

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

四遥定义与典型测点:

表20260630.png

设计要点 测点编码规范

每个测点在 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 对象,每个元素包含以下字段:

表220260630.png

示例报文(单条):

{
  "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 进行压缩和编码,可显著降低传输开销。

设计要点 压缩编码流程

链路回顾20260630.webp

代码示例 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 采用差异化上传策略。

三类上传策略

表320260630.png

设计要点 电芯级数据秒级变化上报

储能站电芯数量庞大(一个 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 上报的完整流程,展示各层如何协作:

压缩编码20260630-iton.webp

八、写在最后 + 下一篇预告

边缘网关是储能站数据上云的「第一跳」,其设计质量直接决定了后续时序存储、实时展示和能量调度的数据基础。本文总结的四遥分类、Topic 规则、报文格式、压缩编码与离线缓冲策略,均来自某能源公司真实场站的落地经验,已做脱敏处理。

实际部署时,建议根据场站规模调整:小型场站可降低 YC 上报周期至 30s,减少带宽;大型场站需关注 EdgeBox 本地缓存容量与补发限速,防止恢复时冲击云端。

下一篇我们将进入云端,讲解 MQTT 报文如何被转码服务解析、校验并写入 IoTDB 时序库。

下一篇预告

IoTDB-Link 转码服务:从 MQTT 报文到时序库写入
拆解云端转码服务的架构设计,包括 MQTT 订阅、Base64 解码、zlib 解压、JSON 解析、IoTDB 路径映射与批量写入,附带 Java 实现与性能调优建议。