IEC 104 原理与生态
协议简介
IEC 60870-5-104(简称 IEC 104)是国际电工委员会制定的电力系统远动通信标准,是 IEC 60870-5-101(串行远动规约)在 TCP/IP 网络上的扩展版本。广泛应用于电力调度自动化、变电站自动化、储能电站与电网调度中心的通信。
IEC 60870-5 协议族:
├── IEC 101 → 串行通信(RS-232/RS-485),平衡/非平衡传输
├── IEC 102 → 电能量计费
├── IEC 103 → 继电保护设备通信
├── IEC 104 → TCP/IP 网络传输(IEC 101 的网络版)
└── IEC 107 → 测试规程网络架构
调度中心(主站/控制中心)
│
├── 调度数据网(SDH/MPLS/专线)
│
├── 变电站 A(子站/RTU)
│ ├── 保护装置
│ ├── 测控装置
│ └── 智能电表
│
├── 储能电站 B(子站)
│ ├── EMS(能量管理系统)
│ ├── PCS(功率变换系统)
│ └── BMS(电池管理系统)
│
└── 风电场 C(子站)
连接参数:
- 传输层:TCP
- 默认端口:2404
- 连接模式:主站主动发起连接
- 每个 TCP 连接对应一个 ASDU 地址帧结构(APDU)
APDU(应用协议数据单元)结构:
┌──────────────────────────────────────────────────────┐
│ APDU │
│ ┌────────────────────────────────────────────────┐ │
│ │ APCI(应用规约控制信息) │ │
│ │ Start Byte │ APDU Length │ Control Fields(4B) │ │
│ │ 0x68 │ 1 byte │ 4 bytes │ │
│ └────────────────────────────────────────────────┘ │
│ ┌────────────────────────────────────────────────┐ │
│ │ ASDU(应用服务数据单元) │ │
│ │ Type ID │ VSQ │ COT │ CA │ IOA │ Information │ │
│ └────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────┘
APCI 控制域格式:
- I 帧(信息帧):传输数据,含发送序号 N(S) 和接收序号 N(R)
- S 帧(监视帧):确认接收,含接收序号 N(R)
- U 帧(非编号帧):控制功能(STARTDT/STOPDT/TESTFR)ASDU 关键字段
类型标识(Type ID)
| 类型 ID | 名称 | 说明 |
|---|---|---|
| 1 | M_SP_NA_1 | 单点遥信(不带时标) |
| 3 | M_DP_NA_1 | 双点遥信(不带时标) |
| 9 | M_ME_NA_1 | 归一化遥测值 |
| 11 | M_ME_NB_1 | 标度化遥测值 |
| 13 | M_ME_NC_1 | 短浮点遥测值 |
| 30 | M_SP_TB_1 | 单点遥信(带 CP56Time2a 时标) |
| 36 | M_ME_TF_1 | 短浮点遥测值(带时标) |
| 45 | C_SC_NA_1 | 单命令(遥控) |
| 46 | C_DC_NA_1 | 双命令(遥控) |
| 48 | C_SE_NA_1 | 设定命令(归一化值) |
| 50 | C_SE_NC_1 | 设定命令(短浮点值) |
| 100 | C_IC_NA_1 | 总召唤命令 |
| 101 | C_CI_NA_1 | 电能量召唤命令 |
| 103 | C_CS_NA_1 | 时钟同步命令 |
传送原因(COT)
| COT | 名称 | 说明 |
|---|---|---|
| 1 | 周期/循环 | 周期性上送 |
| 2 | 背景扫描 | 背景扫描上送 |
| 3 | 突发 | 状态变化触发 |
| 5 | 被请求 | 响应总召唤 |
| 6 | 激活 | 控制命令激活 |
| 7 | 激活确认 | 控制命令确认 |
| 8 | 停止激活 | 控制命令停止 |
| 9 | 停止激活确认 | 停止激活确认 |
| 10 | 激活终止 | 控制命令执行完成 |
| 20 | 响应站召唤 | 响应总召唤 |
| 44 | 未知类型标识 | 错误响应 |
通信流程
连接建立
主站 子站
│ │
│──── TCP Connect ──────────────►│
│◄─── TCP Accept ────────────────│
│ │
│──── STARTDT act (U帧) ────────►│ 启动数据传输
│◄─── STARTDT con (U帧) ─────────│ 确认启动
│ │
│──── 总召唤 (C_IC_NA_1) ───────►│ 请求全数据
│◄─── 激活确认 ──────────────────│
│◄─── 遥信数据 (M_SP_NA_1) ──────│ 上送所有遥信
│◄─── 遥测数据 (M_ME_NC_1) ──────│ 上送所有遥测
│◄─── 召唤结束 ──────────────────│
│ │
│◄─── 变化遥信 (突发, COT=3) ────│ 状态变化主动上送
│◄─── 变化遥测 (周期, COT=1) ────│ 周期性遥测遥控流程(选择-执行)
主站 子站
│ │
│──── 选择命令 (COT=6) ─────────►│ Select
│◄─── 选择确认 (COT=7) ──────────│ Select Confirm
│ │
│──── 执行命令 (COT=6) ─────────►│ Execute
│◄─── 执行确认 (COT=7) ──────────│ Execute Confirm
│◄─── 激活终止 (COT=10) ─────────│ Termination
│ │
│ 或: │
│──── 撤销命令 (COT=8) ─────────►│ Cancel
│◄─── 撤销确认 (COT=9) ──────────│Python 实现示例
python
# 使用 lib60870-python 库
from lib60870 import Connection, ASDU, InformationObject
from lib60870.lib60870 import *
class IEC104Client:
def __init__(self, host: str, port: int = 2404):
self.host = host
self.port = port
self.connection = None
def connect(self):
self.connection = Connection.create(self.host, self.port)
self.connection.setASDUReceivedHandler(self._on_asdu_received, None)
self.connection.setConnectionHandler(self._on_connection_event, None)
self.connection.connect()
def _on_connection_event(self, connection, event, param):
if event == ConnectionEvent.OPENED:
print("Connected to IEC 104 server")
# 发送总召唤
self.send_general_interrogation()
elif event == ConnectionEvent.CLOSED:
print("Connection closed")
def _on_asdu_received(self, connection, asdu, param):
type_id = asdu.getTypeID()
cot = asdu.getCOT()
for i in range(asdu.getNumberOfElements()):
io = asdu.getElement(i)
ioa = io.getObjectAddress()
if type_id == TypeID.M_ME_NC_1: # 短浮点遥测
value = io.getValue()
quality = io.getQuality()
print(f"IOA={ioa}, Value={value:.3f}, Quality={quality}")
elif type_id == TypeID.M_SP_NA_1: # 单点遥信
value = io.getValue()
print(f"IOA={ioa}, Status={'ON' if value else 'OFF'}")
def send_general_interrogation(self, ca: int = 1):
"""发送总召唤"""
asdu = ASDU.create(TypeID.C_IC_NA_1, False, CauseOfTransmission.ACTIVATION,
0, ca, False, False)
io = InterrogationCommand.create(0, QOI.STATION_INTERROGATION)
asdu.addInformationObject(io)
self.connection.sendASDU(asdu)
def send_single_command(self, ca: int, ioa: int, value: bool):
"""发送单命令(遥控)"""
asdu = ASDU.create(TypeID.C_SC_NA_1, False, CauseOfTransmission.ACTIVATION,
0, ca, False, False)
io = SingleCommand.create(ioa, value, False, 0)
asdu.addInformationObject(io)
self.connection.sendASDU(asdu)
def send_setpoint(self, ca: int, ioa: int, value: float):
"""发送设定值命令(浮点)"""
asdu = ASDU.create(TypeID.C_SE_NC_1, False, CauseOfTransmission.ACTIVATION,
0, ca, False, False)
io = SetpointCommandShort.create(ioa, value, 0)
asdu.addInformationObject(io)
self.connection.sendASDU(asdu)储能电站 IEC 104 点表设计
储能电站典型点表:
遥信(单点,Type 1/30):
IOA 1001: PCS 运行状态(0=停机, 1=运行)
IOA 1002: PCS 故障状态
IOA 1003: BMS 告警状态
IOA 1004: 消防系统状态
IOA 1005: 急停按钮状态
遥测(短浮点,Type 13/36):
IOA 2001: 系统总功率 (kW)
IOA 2002: 系统总电流 (A)
IOA 2003: 直流母线电压 (V)
IOA 2004: SOC (%)
IOA 2005: SOH (%)
IOA 2006: 最高电芯温度 (°C)
IOA 2007: 最低电芯温度 (°C)
IOA 2008: 累计充电量 (kWh)
IOA 2009: 累计放电量 (kWh)
遥控(单命令,Type 45):
IOA 3001: PCS 启动/停止
IOA 3002: 充电模式/放电模式切换
IOA 3003: 急停
设定值(浮点,Type 50):
IOA 4001: 有功功率设定值 (kW)
IOA 4002: 无功功率设定值 (kVar)
IOA 4003: SOC 上限设定 (%)
IOA 4004: SOC 下限设定 (%)生态工具
开发库:
├── lib60870-C → C 语言实现,跨平台
├── lib60870-Python → Python 绑定
├── j60870 → Java 实现(开源)
├── IEC60870.NET → .NET 实现
└── OpenMRTS → 开源 SCADA 平台
测试工具:
├── IEC 104 Tester → 商业测试工具
├── FreyrSCADA → 开源测试客户端
└── Wireshark → 协议分析(支持 IEC 104 解码)
商业平台:
├── SIEMENS SICAM → 变电站自动化
├── ABB MicroSCADA → 电力 SCADA
├── Schneider EcoStruxure → 能源管理
└── 南瑞 PCS-9000 → 国内主流调度系统