协议转换网关ModbusIEC 104多协议网关光储项目

Modbus 转 IEC 104 实战:协议转换网关的 5 个工程坑

EdgeOS 工程团队2026-05-14 14 min

Modbus RTU/TCP 是设备侧的事实标准;IEC 60870-5-104 是国内电网调度的事实标准。两者之间的协议转换看似是 SDK 调几行,工程上其实卡在 点表映射、数据类型、品质位、心跳重连和召唤策略五个细节上。本文把我们在光储项目里反复踩过的坑写出来,给同行少绕路。

为什么需要这层转换

典型场景:一个 20 MWp 工商业分布式光伏电站,逆变器(华为 / 阳光 / 锦浪)走 Modbus RTU 到现场网关;网关再以 IEC 60870-5-104 协议上送省级或地市调度厅。中间这一跳 — 「**设备协议 → 调度协议**」— 看着只是协议转换,实际上承载了三件事:

  • 把厂商专有 Modbus 点表,映射到电网标准遥测 / 遥信 / 遥控点位;
  • 把 Modbus 浮点 / 整数 / 位域,转换到 IEC 104 的标准化数据类型(M_ME_NA_1 / M_SP_NA_1 等);
  • 调度通道的可用性兜底 — 心跳、断点续传、召唤、变化遥测,每一项都是出故障时被 K 的点。

坑 1 · 点表映射不是「一对一翻译」

刚开始大家会以为:Modbus 寄存器 40001 = IEC 104 点位 1,依此类推。这个思路 99% 的情况下是错的。原因:

  • Modbus 是设备视角的点表 — 一个逆变器可能有「电池簇 A 电压」「电池簇 B 电压」… 16 簇 × 14 个点 = 224 个点;
  • IEC 104 是调度视角的点表 — 调度厅只关心「总有功」「总无功」「日发电量」「停机告警」这 20-40 个汇总量;
  • 中间这一层聚合 / 选择 / 计算是协议网关的核心工程,不是 SDK 自动能做的。

所以协议适配的第一件事,是定义两份点表

point-mapping.yaml · 协议网关配置示意yaml
# 设备侧(Modbus RTU 主站读取)
device:
  brand: huawei
  model: sun2000-100ktl
  slave_id: 1
  registers:
    active_power:           # 总有功
      addr: 32080
      type: int32
      scale: 0.001          # 寄存器 → kW
      unit: kW
    daily_energy:           # 日发电
      addr: 32114
      type: uint32
      scale: 0.01
      unit: kWh
    fault_code:
      addr: 32008
      type: uint16

# 调度侧(IEC 104 从站,上送给调度主站)
scada:
  channels:
    - asdu_addr: 1
      ioa: 0x4001           # 浮点遥测起始地址
      type: M_ME_NF_1       # 短浮点
      bind: device.active_power
    - asdu_addr: 1
      ioa: 0x4002
      type: M_ME_NF_1
      bind: device.daily_energy
    - asdu_addr: 1
      ioa: 0x0001           # 单点遥信起始地址
      type: M_SP_NA_1
      bind: device.fault_code != 0

关键点:bind 字段允许写表达式(如 fault_code != 0),这让遥信不必依赖额外的设备点。我们见过太多项目把所有故障状态全部点表化暴露给调度,结果调度厅一次召唤拉回来 200 个布尔点,浪费带宽也增加运维负担。

坑 2 · 数据类型的隐式转换是 bug 大本营

Modbus 没有标准化的数据类型 — 同一个「有功」厂商可能用 uint16 ×0.1int32 ×0.001float32。IEC 104 这边对应的也有 4 种类型:

  • M_ME_NA_1 — 规一化值(-1.0 ~ 1.0),16-bit;
  • M_ME_NB_1 — 标度化值(带量纲的整数),16-bit;
  • M_ME_NC_1 — 短浮点,32-bit,最常用
  • M_ME_ND_1 — 不带品质描述符的规一化值。

实操里 90% 的项目用 M_ME_NC_1(短浮点)做遥测,因为:

  • 精度足够(IEEE 754 单精度,6-7 位有效数字);
  • 不用谈量程归一化 — 直接传工程值;
  • 调度主站一律支持,互操作好。

但是 — 不要忘记一个细节:Modbus 浮点的字节序(CDAB / ABCD / BADC / DCBA)厂商之间不统一。华为是 ABCD(大端),阳光部分型号是 CDAB(word swap),有的国产 BMS 是 BADC。在协议网关里要显式声明字节序:

byte-order exampleyaml
device:
  brand: sungrow
  model: sg110cx
  byte_order: CDAB           # 字节序:B0 B1 | B2 B3 → 实际语义 B2 B3 | B0 B1
  registers:
    active_power:
      addr: 5016
      type: float32
      unit: kW

我们见过的最痛的事故:一个 5 MW 储能项目,调度厅看到的功率值是真实值的 1/256 倍 — 跑了三天才发现是网关默认 ABCD,而 BMS 厂商用 CDAB,前 8 位和后 8 位错位。如果协议网关没有开盒即用的字节序配置,就只能在代码里改。

坑 3 · 品质位 / 时标比数值本身重要

IEC 104 每个遥测点都带 QDS(Quality Descriptor),至少要正确填这几位:

  • IV (Invalid) — 数据无效,调度看到这位会忽略;
  • NT (Not Topical) — 数据不是最新的;
  • SB (Substituted) — 数据被人工替代;
  • BL (Blocked) — 数据被锁定(一般用于人工检修)。

实操里最常被搞错的是 IVNT

  • Modbus 读取超时(CRC 错 / 设备断线)→ 必须把对应点的 IV 位置 1,不要继续上送上一次的值;
  • 采集周期 5s,但数据是 30s 之前的(因为设备慢响应)→ NT 位应该置 1;
  • 人工把点位锁定为 0(检修状态)→ BL 位置 1。

调度厅看到 IV / NT,会自动从画面剔除并触发告警逻辑。如果协议网关不做这些 — 调度看到的全是「看起来正常」的虚假数据,最后被调度运行人员投诉到设备厂。

时标(CP56Time2a)也类似 — 24 字节带毫秒精度。建议用网关时间,不用设备时间,因为:

  • 网关有 NTP 同步,设备多数没有;
  • 调度厅会拒绝带未来时间或超过 24 小时之前的报文;
  • 统一时源更容易排故。

坑 4 · 心跳 / 重连 / 召唤的三段协议

IEC 104 在 TCP 之上跑应用层心跳(U-frame TESTFR),同时有三个超时(t1 / t2 / t3)必须实现:

iec104-timers.mdtext
t1 = 15s    应答超时 —— 发出 I/U-frame 15s 没收到 ACK → 断链重连
t2 = 10s    迟延确认上限 —— 收到的报文 10s 内必须 S-frame 确认
t3 = 20s    空闲超时 —— TCP 链路上 20s 没流量 → 主动发 TESTFR

常见错误是只实现了「I-frame 收发」,没实现这三个 timer。结果:

  • 调度链路上 30 分钟没数据 → TCP 还活着但状态僵死 → 直到调度主站发起召唤才发现挂了;
  • 断链重连之后没发总召(C_IC_NA_1),调度厅看到的还是断链前的最后一帧数据 — 直到下一次变化遥测才更新。

建议的链路状态机:

iec104 link state machinetext
STOPPED            初始 / 主动断链
   │  STARTDT-act + ACK
   ▼
STARTED            正常通讯 (I/S-frame)
   │  TESTFR-act 应答 / t1 超时
   ▼
RECONNECTING       重连 (TCP 重建)
   │  连接成功
   ▼
INTROGATING        总召进行中 (C_IC_NA_1 + ACT_CONFIRM)
   │  总召完成
   ▼
STARTED            回到正常态

坑 5 · 总召 vs 变化遥测的协调

IEC 104 有两种上送模式:

  • 变化遥测(spontaneous) — 数据变化(超过死区)才上送;
  • 总召(interrogation) — 主站定期或重连后召唤一次,从站把所有点位一次性上送。

协调好的网关默认:

  • 遥测点的死区设置:有功 / 无功 ±2%,电压 / 频率 ±0.5%,温度 ±1℃;
  • 变化触发立即上送(带 spontaneous 原因码 3);
  • 主站每 15 分钟召唤一次(cyclic);
  • 重连后自动发一次总召,标记原因码 6 (act) → 7 (actcon) → 终止时 10 (actterm)。

看起来简单,但实操里两个细节经常错:

  • 死区设置过小 → 报文风暴 → 带宽吃不消(卫星 / 4G 链路尤甚);
  • 死区过大 → 调度厅看不到设备工况变化 → 调度运行人员投诉。

我们的经验值:主流量点(有功、电压、电流)2%,状态量(频率、温度)0.5%-1%,电量类不做死区(按周期上送)

实操清单:上线之前自查这 12 项

  1. 点表映射文档化 — 设备点表 + 调度点表 + 映射逻辑,三张表对齐;
  2. 字节序在配置层显式声明(不要写死代码);
  3. 所有遥测点用 M_ME_NC_1(短浮点);
  4. QDS 品质位至少正确填 IV / NT 两位;
  5. CP56Time2a 使用网关时间(NTP 同步过);
  6. t1 / t2 / t3 三个 timer 完整实现;
  7. 断链重连后自动发起总召;
  8. 变化遥测带合理死区(2% / 0.5%);
  9. 报文风暴防御 — 单位时间最大上送条数限制;
  10. 主站 / 从站双方 ASDU 地址、IOA 地址双向核对
  11. 有 X.509 证书的项目,调度通道证书与监控通道独立
  12. 所有控制类报文(C_SC_NA_1 等)启用二次确认

EdgeOS 在做什么

上面这些坑,我们在 12 个光储项目里全踩过一遍。EdgeOS 把 Modbus → IEC 104 这层做成了声明式 Protocol Pack

  • 点表映射用 YAML 写(上面的 point-mapping.yaml 是真实结构);
  • QDS / CP56Time2a / 心跳重连 / 总召都在 Runtime 层默认正确实现;
  • Protocol Pack 通过 OTA 远程下发更新,现场不必停机
  • 调度通道 X.509 证书与监控通道独立,符合 IEC 62443 思路。

如果你正在做光储调度接入项目,想看一份完整的 EdgeOS Protocol Pack 模板,欢迎通过 联系我们 留你的硬件平台和点表清单 — 5 个工作日内给一份评估报告。

GET STARTED · 启动适配评估

想看 EdgeOS 跑在你的硬件上?

提交规格 + 协议清单,5 个工作日给评估报告。