一、IEC104协议核心特性与应用场景
IEC104(IEC 60870-5-104)是电力系统中广泛使用的通信协议,基于TCP/IP实现主从站(SCADA与RTU/变电站设备)的实时数据交互。其核心功能包括:
1. 四遥操作 :
- 遥测(YC) :采集电压、电流等模拟量数据(如类型标识0x0D)。
- 遥信(YX):监测开关状态等数字量信号(如M_SP_NA_1单点遥信)。
- 遥控(YK) :远程控制断路器分合闸(如C_SC_NA_1指令)。
- 遥调(YT) :参数调节(如变压器分接头调整)。
2. 协议分层:
- APCI(应用协议控制信息):包含启动字符0x68、长度域及控制域(I/S/U帧标识)。
- ASDU(应用服务数据单元):承载业务数据(如公共地址、传送原因、信息体列表)。
二、 编解码实现
(一)解码器
js
public class IEC104FrameDecoder extends LengthFieldBasedFrameDecoder {
private static final int MAX_FRAME_LENGTH = 253;
private static final int LENGTH_FIELD_OFFSET = 1;
private static final int LENGTH_FIELD_LENGTH = 1;
public IEC104FrameDecoder() {
super(MAX_FRAME_LENGTH, LENGTH_FIELD_OFFSET, LENGTH_FIELD_LENGTH, 0, 0); // :ml-citation{ref="1,2" data="citationList"}
}
@Override
protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception {
// 校验启动字符
if (in.getByte(in.readerIndex()) != 0x68) {
ctx.close();
throw new ProtocolException("Invalid start byte 0x" +
Integer.toHexString(in.getByte(in.readerIndex()))); // :ml-citation{ref="2,5" data="citationList"}
}
// 动态分割报文
ByteBuf frame = (ByteBuf) super.decode(ctx, in);
if (frame == null) return null;
// 解析APCI
frame.readByte(); // 跳过0x68
int apduLength = frame.readUnsignedByte();
if (frame.readableBytes() < apduLength) {
return null; // 等待完整报文:ml-citation{ref="2,5" data="citationList"}
}
ByteBuf apduContent = frame.readSlice(apduLength);
return parseAPDU(apduContent);
}
private APDU parseAPDU(ByteBuf buf) {
// 解析控制域(4字节)
int ctrl1 = buf.readUnsignedByte();
int ctrl2 = buf.readUnsignedByte();
int ctrl3 = buf.readUnsignedByte();
int ctrl4 = buf.readUnsignedByte();
ControlFieldType type;
int sendSeq = 0, recvSeq = 0;
UFrameCommand uCommand = null;
// I帧(低比特位为0)
if ((ctrl1 & 0x01) == 0) { // :ml-citation{ref="2,4" data="citationList"}
type = ControlFieldType.I_FRAME;
sendSeq = ((ctrl1 & 0xFE) >> 1) | (ctrl2 << 7);
recvSeq = ((ctrl3 & 0xFE) >> 1) | (ctrl4 << 7);
}
// S帧(低2比特为01)
else if ((ctrl1 & 0x03) == 0x01) { // :ml-citation{ref="2,4" data="citationList"}
type = ControlFieldType.S_FRAME;
recvSeq = ((ctrl3 & 0xFE) >> 1) | (ctrl4 << 7);
}
// U帧(低2比特为11)
else { // :ml-citation{ref="2,4" data="citationList"}
type = ControlFieldType.U_FRAME;
uCommand = UFrameCommand.fromBytes(ctrl1, ctrl2);
}
// 解析ASDU(仅I帧携带数据)
ASDU asdu = null;
if (type == ControlFieldType.I_FRAME && buf.readableBytes() >= 6) {
asdu = new ASDU();
asdu.setTypeId(buf.readUnsignedByte()); // 类型标识:ml-citation{ref="2,3" data="citationList"}
asdu.setVariableStruct(buf.readUnsignedByte()); // 可变结构限定词:ml-citation{ref="2,3" data="citationList"}
asdu.setCause(buf.readUnsignedShortLE()); // 传送原因(小端):ml-citation{ref="2,5" data="citationList"}
asdu.setCommonAddress(buf.readUnsignedShortLE()); // 公共地址:ml-citation{ref="2,5" data="citationList"}
// 解析信息体(示例:遥测数据)
int objCount = asdu.getVariableStruct() & 0x7F; // SQ=0时取低7位:ml-citation{ref="2,3" data="citationList"}
for (int i = 0; i < objCount; i++) {
InfoObject obj = new InfoObject();
obj.setAddress(buf.readUnsignedMediumLE()); // 3字节地址小端:ml-citation{ref="2,5" data="citationList"}
if (asdu.getTypeId() == 0x0D) { // 浮点型遥测
obj.setValue(buf.readFloatLE()); // :ml-citation{ref="2,3" data="citationList"}
}
asdu.addInfoObject(obj);
}
}
return new APDU(type, sendSeq, recvSeq, uCommand, asdu);
}
}
(二) 编码器
js
public class IEC104Encoder extends MessageToByteEncoder<APDU> {
private final Recycler<ByteBuf> bufferRecycler = new Recycler<>() {
@Override
protected ByteBuf newObject(Handle<ByteBuf> handle) {
return Unpooled.buffer(512);
}
};
@Override
protected void encode(ChannelHandlerContext ctx, APDU msg, ByteBuf out) {
ByteBuf apciBuf = bufferRecycler.get();
try {
// 构建控制域
switch (msg.getType()) { // :ml-citation{ref="2,4" data="citationList"}
case I_FRAME:
apciBuf.writeByte((msg.getSendSeq() << 1) & 0xFF);
apciBuf.writeByte((msg.getSendSeq() >> 7) & 0xFF);
apciBuf.writeByte((msg.getRecvSeq() << 1) & 0xFF);
apciBuf.writeByte((msg.getRecvSeq() >> 7) & 0xFF);
break;
case S_FRAME:
apciBuf.writeByte(0x01);
apciBuf.writeByte(0x00);
apciBuf.writeByte((msg.getRecvSeq() << 1) & 0xFF);
apciBuf.writeByte((msg.getRecvSeq() >> 7) & 0xFF);
break;
case U_FRAME:
apciBuf.writeByte(msg.getUCommand().getCtrlByte1());
apciBuf.writeByte(msg.getUCommand().getCtrlByte2());
apciBuf.writeZero(2); // 填充0x00
break;
}
// 构建ASDU(I帧)
if (msg.getType() == ControlFieldType.I_FRAME && msg.getAsdu() != null) {
ASDU asdu = msg.getAsdu();
apciBuf.writeByte(asdu.getTypeId()); // :ml-citation{ref="2,3" data="citationList"}
apciBuf.writeByte(asdu.getVariableStruct());
apciBuf.writeShortLE(asdu.getCause()); // 小端写入:ml-citation{ref="2,5" data="citationList"}
apciBuf.writeShortLE(asdu.getCommonAddress());
// 信息体编码(示例:单点遥信)
for (InfoObject obj : asdu.getInfoObjects()) {
apciBuf.writeMediumLE(obj.getAddress()); // 3字节小端:ml-citation{ref="2,5" data="citationList"}
if (asdu.getTypeId() == 0x01) { // 单点状态
apciBuf.writeByte(obj.getStatus() & 0x01); // :ml-citation{ref="2,3" data="citationList"}
}
}
}
// 构建完整APDU
out.writeByte(0x68); // 启动字符:ml-citation{ref="2,5" data="citationList"}
out.writeByte(apciBuf.readableBytes()); // 长度域:ml-citation{ref="2,5" data="citationList"}
out.writeBytes(apciBuf);
} finally {
bufferRecycler.recycle(apciBuf);
}
}
}
(三) 实体类
js
// APDU实体类(对象池实现)
public class APDU implements Recyclable {
private static final Recycler<APDU> recycler = new Recycler<>() { /*...*/ };
// 控制域字段
private ControlFieldType type;
private int sendSeq;
private int recvSeq;
private UFrameCommand uCommand;
// ASDU字段
private ASDU asdu;
}
// ASDU数据结构
public class ASDU {
private int typeId; // 类型标识:ml-citation{ref="2,3" data="citationList"}
private int variableStruct; // 可变结构限定词:ml-citation{ref="2,3" data="citationList"}
private int cause; // 传送原因(低字节在前):ml-citation{ref="2,5" data="citationList"}
private int commonAddress; // 公共地址:ml-citation{ref="2,5" data="citationList"}
private List<InfoObject> infoObjects = new ArrayList<>();
}
// 信息体对象
public class InfoObject {
private int address; // 3字节地址:ml-citation{ref="2,5" data="citationList"}
private float value; // 遥测值
private byte status; // 遥信状态(0/1):ml-citation{ref="2,3" data="citationList"}
}
(四) 关键实现说明
1.动态沾包处理通过LengthFieldBasedFrameDecoder实现变长报文切割,支持最大253字节APDU报文。
2.控制域编码优化
-
I帧发送序号采用左移1位存储(避免序列号溢出)
-
U帧支持STARTDT_ACT/TESTFR_ACT等标准命令
3.数据格式处理
-
信息体地址使用3字节小端序编码(适配电力设备规范)
-
遥测值采用IEEE754浮点型编码,遥信状态用单字节存储
三、业务逻辑处理
-
总召唤流程:主站发送C_IC_NA_1指令后,从站按点表顺序返回所有遥测、遥信数据,通过多帧I帧传输(每帧最大249字节)。
-
变化数据主动上报 :从站检测到数据变化时,通过ASDU的传送原因字段(如0x03表示突发上送)触发实时推送。