IEC60870-5-104 采集驱动开发方案
架构对齐(2026-06-27):本驱动接入 Q3 统一采集架构 —
ChannelManager管理通道级 TCP 连接,ScanEngine按 Scan Class 调度ReadPoints,结果写入ShadowCore并经ShadowBridge扇出至 Pipeline。通道链路断开时,channel_device_state.go将同通道设备标记为 Offline。详见 TODO 规划索引。
| 项 | 内容 |
|---|---|
| 协议 ID | iec60870-5-104 |
| 实现路径 | internal/driver/ice104/ |
| M1 状态 | ✅ TCP 链路、总召唤、Subscribe 缓存、单点遥控、单测 |
| M2 待办 | 时钟同步、遥脉召唤、双点遥控/设定值、完整 S 帧窗口 |
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 Q3)
┌─────────────────────────────────────────────────────────────────┐
│ EdgeX Gateway │
├─────────────────────────────────────────────────────────────────┤
│ ChannelManager ScanEngine ShadowCore │
│ (通道/驱动/设备状态) → (Scan Class 调度) → (运行时真源) │
│ │ │ │ │
│ └────────── ICE104Driver.ReadPoints ──────────┘ │
├─────────────────────────────────────────────────────────────────┤
│ Protocol Driver Layer │
│ internal/driver/ice104/ │
│ ice104.go · transport.go · scheduler.go · decoder.go │
├─────────────────────────────────────────────────────────────────┤
│ TCP/IP :2404 │
└─────────────────────────────────────────────────────────────────┘
2.1.1 旧版参考架构(Device Service Layer)
┌─────────────────────────────────────────────────────────────────┐
│ 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 |
模拟器测试 使用开源库进行模拟测试
11.2 开源 CS104 从站联调(推荐)
Freyr GUI 与 Python-IEC104(同厂商 DLL)在 M1 联调中均为 握手通过、总召唤后读点失败;CI 与 Windows 自动化请优先使用下列开源从站。
11.2.1 方案对比
| 方案 | 仓库 | 许可 | Windows 构建 | M1 联调结论 |
|---|---|---|---|---|
| go-iecp5 从站(Go 首选) | thinkgos/go-iecp5 | GPL-3.0 | go run,无需 CMake |
✅ Connect + GI + M_ME_NA_1 IOA=1 PASS |
| c104 Python 从站(Python 首选) | Fraunhofer-FIT-DIEN/iec104-python(PyPI: c104) |
GPL-3.0 | pip install c104 |
✅ Connect + GI + M_ME_NA_1 IOA=1 PASS |
| lib60870 从站 | mz-automation/lib60870 | GPL-3.0 | 需 CMake + VS/MinGW | 源码已 vendoring,见 test/lib60870/ |
| wendy512/iec104 | wendy512/iec104 | Apache-2.0 | Go(封装 go-iecp5) | 可选,与 go-iecp5 等价 |
| py_iec104 | MaximPriyatkin/py_iec104 | MIT | 纯 Python,无依赖 | 未联测;项目较新,可作备选 |
| Freyr / pyiec104 DLL | 厂商闭源(pyiec104 / iec104x64d.dll) |
试用 | 已测 | ❌ GI 后断连、无缓存、DLL 崩溃 |
| 仓库内 mock | simulator_mock_integration_test.go |
— | 无外部依赖 | ✅ CI 默认 |
11.2.2 go-iecp5 从站(Windows 一键)
Headless 从站(M_ME_NA_1 IOA=1 CA=1,归一化值 0.5):
# 终端 1:启动从站(占用 2404)
cd test/ice104-goiecp5-server
go run . --bind 127.0.0.1 --port 2404
# 终端 2:联机测试
cd ../..
CGO_ENABLED=0 go test -tags=integration ./internal/driver/ice104/... \
-run 'TestIntegrationWithGoIecp5Server|TestIntegrationWithSimulator|TestIntegrationWithMock104Server' -v -count=1
TestIntegrationWithGoIecp5Server:测试进程内启动 go-iecp5 从站(随机端口),不依赖 2404,推荐 CI / 本地自动化。TestIntegrationWithPythonServer:测试进程内启动 c104 Python 从站(随机端口),需pip install c104。TestIntegrationWithSimulator:探测127.0.0.1:2404是否有从站(Freyr / go-iecp5 / c104 手动启动均可)。
驱动侧修复(2026-06-29):I 帧发送序号从 0 起算(原实现首帧 N(S)=1,与 go-iecp5 / lib60870 / c104 不兼容导致 GI 后断连)。
11.2.3 c104 Python 从站(开源 Python 首选)
选用理由:在 PyPI / GitHub 检索 iec60870、iec104、cs104 server python 后,排除 Freyr pyiec104(ctypes 闭源 DLL,与 GUI 模拟器同故障)。c104(iec104-python)为 Fraunhofer 维护的 lib60870-C 官方 Python 绑定(GPL-3.0),PyPI 标记 Production/Stable,Windows 有预编译 wheel,无需 CMake。
备选:MaximPriyatkin/py_iec104 为纯 Python 无依赖实现(MIT),项目较新未纳入联测;若无法安装 c104 wheel 可评估该库。
安装与 headless 从站(M_ME_NA_1 IOA=1 CA=1,归一化值 0.5):
pip install -r test/ice104-python-server/requirements.txt
# 终端 1:启动从站(占用 2404)
python test/ice104-python-server/server.py --bind 127.0.0.1 --port 2404
# 终端 2:联机测试
CGO_ENABLED=0 go test -tags=integration ./internal/driver/ice104/... \
-run 'TestIntegrationWithPythonServer|TestIntegrationWithSimulator' -v -count=1
TestIntegrationWithPythonServer:Go 测试自动exec启动server.py(随机端口),推荐 Python 侧自动化;未安装 c104 时Skip。- 可通过环境变量
PYTHON指定 Python 解释器路径。
11.2.4 lib60870 从站(C,需构建)
源码与说明见 test/lib60870/:
edgex_server.c— 最小 CS104 从站,响应总召唤返回 M_ME_NA_1 IOA=1README.md— clone lib60870 + CMake 构建步骤build-windows.bat— Windows 辅助脚本(需 PATH 中有cmake)
构建并运行后:
./edgex_server --bind 127.0.0.1 --port 2404
CGO_ENABLED=0 go test -tags=integration ./internal/driver/ice104/... \
-run TestIntegrationWithLib60870Server -v -count=1
可通过环境变量 ICE104_LIB60870_ADDR=127.0.0.1:2405 指定地址。
11.2.5 EdgeX 点位配置(与 §11.1 相同)
| 配置项 | 值 |
|---|---|
| Group / typeID | M_ME_NA_1 |
| Address / IOA | 1 |
| commonAddress | 1 |
| DataType | FLOAT 或 INT16 + Group=M_ME_NA_1 |
注意:若仅选 INT16 而不指定 Group,驱动会默认映射到 M_ME_NB_1 (TypeID=11),与模拟器 M_ME_NA_1 (9) 缓存键不一致,读点将始终为 Bad。
附录:协议引用对照表
| 协议章节 | 功能 | 驱动实现 |
|---|---|---|
| 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 |