IEC60870-5-104 采集驱动开发方案
1. 概述
1.1 协议简介
IEC60870-5-104 是电力自动化系统中常用的通信协议,基于 TCP/IP 协议实现,用于监视和控制电力系统中的各种设备(如变电站、发电机、开关等)。
1.2 功能定位
本驱动实现 IEC60870-5-104 协议的 Client 端功能,支持:
| 功能类别 |
功能描述 |
协议引用 |
| 数据采集 |
遥信(单点/双点信息) |
7.3, 7.4 |
| 数据采集 |
遥测(测量值) |
7.3, 7.4 |
| 数据采集 |
遥脉(累计量/电度召唤) |
7.8 |
| 总召唤 |
召唤设备所有数据 |
7.5 |
| 时钟同步 |
定期向设备发送时钟同步请求 |
7.6 |
| 遥控遥调 |
单点/双点遥控、步调节、设定值 |
7.7 |
| 链路管理 |
启动/停止链路、测试链路 |
5.1, 5.2, 5.3 |
1.3 设计原则
- 一致性: 遵循项目统一的驱动接口规范,与 S7、EtherNet/IP 等驱动保持一致的设计风格
- 可扩展性: 模块化设计,便于后续功能扩展
- 可靠性: 完善的错误处理和重连机制
- 可测试性: 支持依赖注入,便于单元测试
2. 技术架构
2.1 整体架构
┌─────────────────────────────────────────────────────────────────┐
│ EdgeX Foundry │
├─────────────────────────────────────────────────────────────────┤
│ Device Service Layer │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ DriverMgr │ │ Schedule │ │ ConfigMgr │ │
│ └──────┬──────┘ └─────────────┘ └─────────────┘ │
│ │ │
├──────────┼──────────────────────────────────────────────────────┤
│ Protocol Driver Layer │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ ICE104Driver │ │
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌────────┐ │ │
│ │ │ transport│◄─│ scheduler│◄─│ decoder │ │ config │ │ │
│ │ │ (TCP连接) │ │ (读写调度)│ │ (编解码) │ │ │ │ │
│ │ └────┬─────┘ └──────────┘ └──────────┘ └────────┘ │ │
│ │ │ │ │
│ └───────┼─────────────────────────────────────────────────┘ │
├───────────┼──────────────────────────────────────────────────────┤
│ Network Layer │
│ TCP/IP │
└───────────┴──────────────────────────────────────────────────────┘
2.2 模块划分
| 模块 |
文件 |
职责 |
对应参考 |
| 驱动主模块 |
ice104.go |
实现 Driver 接口,协调整体流程 |
s7.go, ethernetip.go |
| 传输层 |
transport.go |
TCP 连接管理、协议帧收发、心跳检测 |
transport.go |
| 调度器 |
scheduler.go |
批量点位读写调度、总召唤管理、遥脉召唤 |
scheduler.go |
| 解码器 |
decoder.go |
地址解析、数据编解码、TypeID 映射 |
decoder.go |
2.3 核心类/结构体设计
2.3.1 ICE104Driver(驱动主类)
type ICE104Driver struct {
config model.DriverConfig
transport *ICE104Transport
decoder *ICE104Decoder
scheduler *ICE104Scheduler
}
2.3.2 ICE104Transport(传输层)
type ICE104Transport struct {
cfg map[string]any
conn net.Conn
connected atomic.Bool
connectTime time.Time
lastDisconnectTime time.Time
reconnectCount atomic.Int32
localAddr string
remoteAddr string
// 协议定时器(IEC60870-5-104 5.1)
t0 time.Duration // 连接建立超时
t1 time.Duration // I帧响应超时
t2 time.Duration // S帧发送间隔
t3 time.Duration // 测试帧发送间隔
w int // 未确认I帧最大数量
// 心跳/定时器管理
t1Timer *time.Timer
t2Timer *time.Timer
t3Timer *time.Timer
stopTimers chan struct{}
// 帧序列号
sendSeq atomic.Uint16
recvSeq atomic.Uint16
// 接收缓冲区
recvBuf []byte
recvMu sync.Mutex
}
2.3.3 ICE104Scheduler(调度器)
type ICE104Scheduler struct {
transport *ICE104Transport
decoder *ICE104Decoder
// 总召唤配置
generalCallInterval time.Duration
lastGeneralCallTime time.Time
// 时钟同步配置
clockSyncInterval time.Duration
lastClockSyncTime time.Time
// 遥脉召唤配置
pulseInterval time.Duration
lastPulseTime time.Time
// 统计
totalRequests int64
successCount int64
failureCount int64
mu sync.Mutex
}
2.3.4 ICE104Decoder(解码器)
type ICE104Decoder struct {
// TypeID 到数据类型的映射
typeIDMap map[uint8]TypeIDInfo
}
type TypeIDInfo struct {
Name string
Direction Direction // Monitor 或 Control
DataTypes []string // 支持的数据类型
}
type Direction int
const (
DirectionMonitor Direction = iota
DirectionControl
)
3. 接口定义
3.1 驱动接口(实现 driver.Driver)
| 方法 |
功能 |
参数 |
返回值 |
Init(cfg model.DriverConfig) error |
初始化驱动 |
cfg: 驱动配置 |
error: 错误信息 |
Connect(ctx context.Context) error |
建立TCP连接 |
ctx: 上下文 |
error: 错误信息 |
Disconnect() error |
断开连接 |
无 |
error: 错误信息 |
ReadPoints(ctx, points) (map, error) |
批量读取点位 |
points: 点位列表 |
map[string]model.Value: 读取结果 |
WritePoint(ctx, point, value) error |
写入单个点位 |
point: 点位, value: 值 |
error: 错误信息 |
Health() driver.HealthStatus |
获取健康状态 |
无 |
HealthStatus: 健康状态 |
SetSlaveID(slaveID uint8) error |
设置从站ID |
slaveID: 从站地址 |
error: 错误信息 |
SetDeviceConfig(config map[string]any) error |
设置设备配置 |
config: 配置参数 |
error: 错误信息 |
GetConnectionMetrics() (...) |
获取连接指标 |
无 |
连接时长、重连次数、地址信息 |
3.2 内部接口
3.2.1 传输层接口
| 方法 |
功能 |
SendFrame(frame *Frame) error |
发送协议帧 |
RecvFrame(ctx context.Context) (*Frame, error) |
接收协议帧 |
SendTestFrame() error |
发送测试帧 |
SendACK(seq uint16) error |
发送确认帧 |
SendGeneralCall(ca uint8) error |
发送总召唤命令 |
SendClockSync() error |
发送时钟同步命令 |
SendPulseCall(ca uint8) error |
发送遥脉召唤命令 |
SendControlCommand(cmd *ControlCommand) error |
发送控制命令 |
3.2.2 调度器接口
| 方法 |
功能 |
ReadPoints(ctx, points) |
批量读取点位 |
WritePoint(ctx, point, value) |
写入点位 |
TriggerGeneralCall() |
触发总召唤 |
TriggerClockSync() |
触及时钟同步 |
TriggerPulseCall() |
触发遥脉召唤 |
GetStats() |
获取统计信息 |
3.2.3 解码器接口
| 方法 |
功能 |
ParseAddress(addr string) (*IOAInfo, error) |
解析IOA地址 |
DecodeAPDU(data []byte) (*APDU, error) |
解码APDU |
EncodeAPDU(apdu *APDU) ([]byte, error) |
编码APDU |
DecodeValue(asdu *ASDU) (interface{}, error) |
解码ASDU值 |
EncodeControlCommand(cmd *ControlCommand) (*ASDU, error) |
编码控制命令 |
4. 配置参数
4.1 设备配置项
| 参数名 |
类型 |
默认值 |
说明 |
ip |
string |
- |
设备IPv4地址(必填) |
port |
int |
2404 |
设备端口号 |
commonAddress |
uint8 |
1 |
公共地址(CA),唯一标识数据对象 |
generalCallInterval |
int |
300 |
总召唤间隔(秒),0表示禁用 |
clockSyncInterval |
int |
600 |
时钟同步间隔(秒),0表示禁用 |
pulseInterval |
int |
300 |
遥脉召唤间隔(秒),0表示禁用 |
t0 |
int |
10 |
T0超时时间(秒),TCP连接建立超时 |
t1 |
int |
15 |
T1超时时间(秒),等待I帧响应超时 |
t2 |
int |
10 |
T2超时时间(秒),S帧发送间隔 |
t3 |
int |
20 |
T3超时时间(秒),测试帧发送间隔 |
w |
int |
7 |
未确认I帧最大数量 |
maxRetries |
int |
3 |
连接重试次数 |
retryInterval |
int |
1000 |
重试间隔(毫秒) |
4.2 点位配置
4.2.1 地址格式
点位地址为纯数字 IOA(Information Object Address),范围 0-65535。
4.2.2 点位属性
| 属性 |
说明 |
适用场景 |
Read |
主动读取模式,根据采集组间隔定期读取 |
周期性数据采集 |
Subscribe |
订阅模式,设备主动上报变化数据 |
实时性要求高的数据 |
Write |
写入模式,支持EdgeX主动写入数据到设备 |
遥控遥调操作 |
注意: 104协议的读地址与写地址是分离的,一个IOA要么是读点位要么是写点位,不存在同时可读可写的点位。
4.2.3 支持的TypeIDs
监控方向(Monitor)
| TypeID |
名称 |
支持数据类型 |
说明 |
| 1 |
M_SP_NA_1 |
BIT, BOOL |
单点信息 |
| 2 |
M_SP_TB_1 |
BIT, BOOL |
带时标的单点信息 |
| 3 |
M_DP_NA_1 |
UINT8 |
双点信息 |
| 4 |
M_DP_TB_1 |
UINT8 |
带时标的双点信息 |
| 5 |
M_ST_NA_1 |
INT8 |
步位置信息 |
| 6 |
M_ST_TB_1 |
INT8 |
带时标的步位置信息 |
| 7 |
M_BO_NA_1 |
UINT32, INT32 |
32比特串 |
| 8 |
M_BO_TB_1 |
UINT32, INT32 |
带时标的32比特串 |
| 9 |
M_ME_NA_1 |
FLOAT |
测量值(归一化) |
| 10 |
M_ME_TD_1 |
FLOAT |
带时标的测量值(归一化) |
| 11 |
M_ME_NB_1 |
UINT16, INT16 |
测量值(标度化) |
| 12 |
M_ME_TE_1 |
UINT16, INT16 |
带时标的测量值(标度化) |
| 13 |
M_ME_NC_1 |
FLOAT |
测量值(短浮点) |
| 14 |
M_ME_TF_1 |
FLOAT |
带时标的测量值(短浮点) |
| 21 |
M_IT_NA_1 |
UINT32, INT32 |
累计量 |
| 23 |
M_ME_ND_1 |
FLOAT |
测量值(归一化,无品质) |
控制方向(Control)
| TypeID |
名称 |
支持数据类型 |
说明 |
| 45 |
C_SC_NA_1 |
BIT, BOOL |
单点遥控 |
| 46 |
C_DC_NA_1 |
UINT8 |
双点遥控 |
| 47 |
C_RC_NA_1 |
INT8 |
步调节命令 |
| 48 |
C_SE_NA_1 |
FLOAT (-1.0~1.0) |
设置归一化值 |
| 49 |
C_SE_NB_1 |
UINT16, INT16 |
设置标度化值 |
| 50 |
C_SE_NC_1 |
FLOAT |
设置短浮点数 |
| 51 |
C_BO_NA_1 |
UINT32, INT32 |
设置32比特串 |
注意: 不支持带时标的控制信息对象(如 C_SC_TA_1、C_DC_TA_1 等)。
5. 数据处理流程
5.1 连接建立流程
EdgeX Device (104 Server)
| |
|--- TCP Connect -------------->|
| |
|<-- TCP ACK -------------------|
| |
|--- TESTFRM (C_IA_1) --------->| T0启动
| |
|<-- TESTFRM ACK ---------------| T0停止,连接建立
| |
|--- STARTDT (C_IC_1) --------->| 请求启动数据传输
| |
|<-- STARTDT ACK ---------------| 数据传输开始
5.2 点位读取流程
5.2.1 Read 模式(主动读取)
┌─────────────────────────────────────────────────────────────┐
│ 采集组定时触发 │
└──────────────────────────┬──────────────────────────────────┘
▼
┌─────────────────────────────────────────────────────────────┐
│ Scheduler: 按 TypeID 分组点位 │
└──────────────────────────┬──────────────────────────────────┘
▼
┌─────────────────────────────────────────────────────────────┐
│ Transport: 发送 SINGLE 或 GROUP 召唤命令 │
│ (C_IC_1 / C_CI_1) │
└──────────────────────────┬──────────────────────────────────┘
▼
┌─────────────────────────────────────────────────────────────┐
│ Transport: 接收 ASDU 响应帧 │
└──────────────────────────┬──────────────────────────────────┘
▼
┌─────────────────────────────────────────────────────────────┐
│ Decoder: 解码数据,转换为 model.Value │
└──────────────────────────┬──────────────────────────────────┘
▼
┌─────────────────────────────────────────────────────────────┐
│ 返回结果到 Device Service │
└─────────────────────────────────────────────────────────────┘
5.2.2 Subscribe 模式(订阅上报)
Device (104 Server) EdgeX
| |
|--- ASDU (变化数据) --->| 设备主动上报
| |
|<-- S帧确认 ------------| 确认接收
| |
| ▼
| ┌─────────────────────────────┐
| │ Decoder: 解码数据 │
| └────────────┬────────────────┘
| ▼
| ┌─────────────────────────────┐
| │ 触发北向数据上报 │
| └─────────────────────────────┘
5.3 总召唤流程
EdgeX Device
| |
|--- C_IC_1 (GENERAL CALL) ---->|
| |
|<-- M_*_NA_1 (遥信/遥测数据) ---| 设备返回所有数据
|<-- M_*_NA_1 ... |
|<-- M_IT_NA_1 (累计量) --------|
| |
|<-- M_EI_NA_1 (结束标识) ------| 总召唤结束
| |
|--- C_CI_1 (确认) ------------>|
5.4 遥控遥调流程
EdgeX Device
| |
|--- C_SC_NA_1 (选择) --------->| SCO = 0
| |
|<-- C_SC_NA_1 (确认) ----------| SCO = 1
| |
|--- C_SC_NA_1 (执行) --------->| SCO = 2
| |
|<-- C_SC_NA_1 (执行确认) ------| SCO = 3
6. 错误处理机制
6.1 错误分类
| 错误类型 |
触发条件 |
处理策略 |
| 连接错误 |
TCP连接失败、超时 |
自动重连(带指数退避) |
| 帧错误 |
帧格式错误、校验失败 |
记录日志,丢弃错误帧 |
| 协议错误 |
TypeID不支持、参数错误 |
返回错误,不发送请求 |
| 超时错误 |
T1/T2/T3超时 |
发送确认或测试帧 |
| 设备错误 |
设备返回否定确认 |
记录日志,标记点位质量为Bad |
6.2 重连机制
// 重连策略
maxRetries int // 最大重试次数
retryInterval time.Duration // 基础重试间隔
maxBackoff time.Duration // 最大退避时间
// 重试间隔计算(指数退避)
wait = retryInterval * (2^attempt)
if wait > maxBackoff:
wait = maxBackoff
6.3 帧超时处理
| 定时器 |
超时动作 |
处理流程 |
| T0 |
连接建立超时 |
关闭连接,触发重连 |
| T1 |
I帧响应超时 |
重传未确认I帧 |
| T2 |
S帧发送超时 |
发送S帧确认 |
| T3 |
测试帧超时 |
发送测试帧验证链路 |
7. 与其他系统集成
7.1 EdgeX Device Service 集成
驱动通过 driver.RegisterDriver 注册,Device Service 通过统一接口调用:
func init() {
driver.RegisterDriver("iec60870-5-104", func() driver.Driver {
return NewICE104Driver()
})
}
7.2 数据流向
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Device │─────>│ ICE104 │─────>│ EdgeX │
│ (104 Server)│ │ Driver │ │ Core Data │
└──────────────┘ └──────────────┘ └──────────────┘
^ │ │
│ │ │
│<─────────────────────│<─────────────────────│
│ 遥控命令 │ 写入请求 │ 北向指令
7.3 北向数据格式
驱动上报的数据点包含以下信息:
| 字段 |
类型 |
说明 |
PointID |
string |
点位唯一标识 |
Value |
any |
数据值 |
Quality |
string |
品质(Good/Bad/Uncertain) |
TS |
time.Time |
时间戳 |
QualityInfo |
string |
品质描述(可选) |
PointTime |
time.Time |
点位时间(可选,来自带时标数据) |
8. 代码结构
internal/driver/ice104/
├── ice104.go # 驱动主模块
├── transport.go # 传输层(TCP连接、协议帧处理)
├── scheduler.go # 调度器(批量读写、总召唤)
├── decoder.go # 解码器(地址解析、数据编解码)
├── protocol.go # 协议常量和数据结构
├── transport_test.go # 传输层测试
├── decoder_test.go # 解码器测试
└── scheduler_test.go # 调度器测试
8.1 文件职责说明
| 文件 |
职责 |
关键功能 |
ice104.go |
驱动入口 |
实现 Driver 接口,初始化各模块 |
transport.go |
传输层 |
TCP连接管理、帧收发、定时器管理 |
scheduler.go |
调度层 |
点位分组、批量读写、定时任务 |
decoder.go |
编解码层 |
地址解析、APDU/ASDU编解码 |
protocol.go |
协议定义 |
TypeID、帧结构、常量定义 |
9. 安全性考虑
9.1 注意事项
| 风险点 |
描述 |
关联模块 |
| 未授权访问 |
无认证机制,任何人可连接 |
transport |
| 数据篡改 |
无消息完整性校验 |
decoder |
| 拒绝服务 |
恶意设备可发送大量数据 |
transport |
| 地址伪造 |
IOA地址无验证机制 |
decoder |
9.2 建议措施
- 网络层面: 部署防火墙,限制访问IP
- 传输加密: 考虑使用 TLS 加密传输(需设备支持)
- 输入验证: 严格校验 IOA 地址范围和数据类型
- 流量控制: 实现接收缓冲区限制,防止内存溢出
10. 部署与集成
10.1 驱动注册
在 cmd/driver/registry.go 中添加导入:
import (
_ "github.com/anviod/edgex/internal/driver/ice104"
)
10.2 配置示例
device:
name: "104-Device-01"
protocol: "iec60870-5-104"
config:
ip: "192.168.1.100"
port: 2404
commonAddress: 1
generalCallInterval: 300
clockSyncInterval: 600
t0: 10
t1: 15
t2: 10
t3: 20
w: 7
10.3 点位配置示例
points:
- name: "DI-001"
address: "100"
dataType: "BOOL"
attribute: "Subscribe"
typeID: "M_SP_NA_1"
- name: "AI-001"
address: "400"
dataType: "FLOAT"
attribute: "Read"
typeID: "M_ME_NA_1"
- name: "DO-001"
address: "1000"
dataType: "BOOL"
attribute: "Write"
typeID: "C_SC_NA_1"
11. 测试方案
11.1 模拟器连接测试
本章节介绍如何使用 IEC 60870-5-104 Server Simulator 进行驱动测试。该模拟器支持多种数据类型的采集以及控制写入,是开发和测试阶段的理想工具。
11.1.1 模拟器下载与安装
- 下载地址: 可在 模拟器官网 下载试用版
- 注意事项: 试用版软件每 15 分钟将会自动关闭,建议及时保存相关配置数据
- 安装步骤: 下载后解压,找到
IEC60870-5-104 Server Simulator 可执行文件,直接点击安装即可
11.1.2 模拟器配置
步骤1:创建 Server
打开 IEC 60870-5-104 Server Simulator,点击左上角 Add Server 创建一个 Server 实例。
步骤2:配置 Server 网络
在选项卡 IEC104-SERVER_1 中修改默认配置:
| 配置项 |
默认值 |
修改后 |
说明 |
| Source IP Address |
127.0.0.1 |
0.0.0.0 |
允许外部连接 |
| Port |
2404 |
2404 |
104协议默认端口 |
步骤3:添加点位
切换到 Configuration_1 选项卡,点击 Add Row 添加点位:
| 配置项 |
值 |
说明 |
| IEC 60870-5 Group to Choose |
Measured Normalized |
测量值(归一化) |
| Event Report Type ID |
M_ME_NA_1=9 |
测量值类型 |
| IOA |
1 |
信息对象地址 |
| Common Address |
1 |
公共地址 |
其他值保持默认即可。
步骤4:启动模拟器
- 点击按钮 Load Configuration 加载配置
- 进入
Data_Objects_1 选项卡
- 点击 Start Communication 启动模拟器
11.1.3 EdgeX 驱动配置
使用以下配置连接到模拟器:
device:
name: "104-Simulator"
protocol: "iec60870-5-104"
config:
ip: "127.0.0.1"
port: 2404
commonAddress: 1
generalCallInterval: 300
clockSyncInterval: 600
t0: 10
t1: 15
t2: 10
t3: 20
w: 7
points:
- name: "AI-Sim-001"
address: "1"
dataType: "FLOAT"
attribute: "Subscribe"
typeID: "M_ME_NA_1"
11.1.4 测试验证
| 测试项 |
验证方法 |
预期结果 |
| 连接测试 |
启动驱动后查看健康状态 |
HealthStatusGood |
| 总召唤测试 |
触发总召唤命令 |
成功获取所有点位数据 |
| 数据采集测试 |
修改模拟器点位值 |
EdgeX 收到变化数据 |
| 遥控测试 |
发送控制命令 |
模拟器收到并执行命令 |
11.1.5 支持的测试点位类型
| TypeID |
名称 |
Group to Choose |
IOA示例 |
| 1 |
M_SP_NA_1 |
Single Point |
100 |
| 3 |
M_DP_NA_1 |
Double Point |
101 |
| 9 |
M_ME_NA_1 |
Measured Normalized |
400 |
| 11 |
M_ME_NB_1 |
Measured Scaled |
401 |
| 21 |
M_IT_NA_1 |
Integrated Totals |
500 |
| 45 |
C_SC_NA_1 |
Single Command |
1000 |
| 48 |
C_SE_NA_1 |
Setpoint Normalized |
1400 |
附录:协议引用对照表
| 协议章节 |
功能 |
驱动实现 |
| IEC60870-5-104 5.1 |
防止数据丢失与重复 |
帧序列号管理 |
| IEC60870-5-104 5.2 |
测试链路功能 |
T3定时器、测试帧 |
| IEC60870-5-104 5.3 |
启动/停止链路功能 |
STARTDT/STOPDT |
| IEC60870-5-104 7.1 |
链路初始化 |
连接建立流程 |
| IEC60870-5-104 7.3 |
循环数据传输 |
Subscribe模式 |
| IEC60870-5-104 7.4 |
事件数据传输 |
设备主动上报 |
| IEC60870-5-104 7.5 |
总召唤 |
General Call |
| IEC60870-5-104 7.6 |
时钟同步 |
Clock Sync |
| IEC60870-5-104 7.7 |
遥控命令 |
Control Command |
| IEC60870-5-104 7.8 |
召唤累积量 |
Pulse Call |