IEC104(IEC 60870-5-104)是電力系統(tǒng)中廣泛使用的通信協(xié)議,基于TCP/IP實(shí)現(xiàn)主從站(SCADA與RTU/變電站設(shè)備)的實(shí)時數(shù)據(jù)交互。其核心功能包括:
1.四遙操作:
遙測(YC):采集電壓、電流等模擬量數(shù)據(jù)(如類型標(biāo)識0x0D)。
遙信(YX):監(jiān)測開關(guān)狀態(tài)等數(shù)字量信號(如M_SP_NA_1單點(diǎn)遙信)。
遙控(YK):遠(yuǎn)程控制斷路器分合閘(如C_SC_NA_1指令)。
遙調(diào)(YT):參數(shù)調(diào)節(jié)(如變壓器分接頭調(diào)整)。
2.協(xié)議分層:
APCI(應(yīng)用協(xié)議控制信息):包含啟動字符0x68、長度域及控制域(I/S/U幀標(biāo)識)。
ASDU(應(yīng)用服務(wù)數(shù)據(jù)單元):承載業(yè)務(wù)數(shù)據(jù)(如公共地址、傳送原因、信息體列表)。
(一)解碼器
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 {
// 校驗(yàn)啟動字符
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"}
}
// 動態(tài)分割報(bào)文
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; // 等待完整報(bào)文?:ml-citation{ref="2,5" data="citationList"}
}
ByteBuf apduContent = frame.readSlice(apduLength);
return parseAPDU(apduContent);
}
private APDU parseAPDU(ByteBuf buf) {
// 解析控制域(4字節(jié))
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幀攜帶數(shù)據(jù))
ASDU asdu = null;
if (type == ControlFieldType.I_FRAME && buf.readableBytes() >= 6) {
asdu = new ASDU();
asdu.setTypeId(buf.readUnsignedByte()); // 類型標(biāo)識?:ml-citation{ref="2,3" data="citationList"}
asdu.setVariableStruct(buf.readUnsignedByte()); // 可變結(jié)構(gòu)限定詞?: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"}
// 解析信息體(示例:遙測數(shù)據(jù))
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字節(jié)地址小端?:ml-citation{ref="2,5" data="citationList"}
if (asdu.getTypeId() == 0x0D) { // 浮點(diǎn)型遙測
obj.setValue(buf.readFloatLE()); // ?:ml-citation{ref="2,3" data="citationList"}
}
asdu.addInfoObject(obj);
}
}
return new APDU(type, sendSeq, recvSeq, uCommand, asdu);
}
}
public class IEC104Encoder extends MessageToByteEncoder {
private final Recycler bufferRecycler = new Recycler<>() {
@Override
protected ByteBuf newObject(Handle handle) {
return Unpooled.buffer(512);
}
};
@Override
protected void encode(ChannelHandlerContext ctx, APDU msg, ByteBuf out) {
ByteBuf apciBuf = bufferRecycler.get();
try {
// 構(gòu)建控制域
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;
}
// 構(gòu)建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());
// 信息體編碼(示例:單點(diǎn)遙信)
for (InfoObject obj : asdu.getInfoObjects()) {
apciBuf.writeMediumLE(obj.getAddress()); // 3字節(jié)小端?:ml-citation{ref="2,5" data="citationList"}
if (asdu.getTypeId() == 0x01) { // 單點(diǎn)狀態(tài)
apciBuf.writeByte(obj.getStatus() & 0x01); // ?:ml-citation{ref="2,3" data="citationList"}
}
}
}
// 構(gòu)建完整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);
}
}
}
// APDU實(shí)體類(對象池實(shí)現(xiàn))
public class APDU implements Recyclable {
private static final Recycler recycler = new Recycler<>() { /*...*/ };
// 控制域字段
private ControlFieldType type;
private int sendSeq;
private int recvSeq;
private UFrameCommand uCommand;
// ASDU字段
private ASDU asdu;
}
// ASDU數(shù)據(jù)結(jié)構(gòu)
public class ASDU {
private int typeId; // 類型標(biāo)識?:ml-citation{ref="2,3" data="citationList"}
private int variableStruct; // 可變結(jié)構(gòu)限定詞?:ml-citation{ref="2,3" data="citationList"}
private int cause; // 傳送原因(低字節(jié)在前)?:ml-citation{ref="2,5" data="citationList"}
private int commonAddress; // 公共地址?:ml-citation{ref="2,5" data="citationList"}
private List infoObjects = new ArrayList<>();
}
// 信息體對象
public class InfoObject {
private int address; // 3字節(jié)地址?:ml-citation{ref="2,5" data="citationList"}
private float value; // 遙測值
private byte status; // 遙信狀態(tài)(0/1)?:ml-citation{ref="2,3" data="citationList"}
}
1.動態(tài)沾包處理通過LengthFieldBasedFrameDecoder實(shí)現(xiàn)變長報(bào)文切割,支持最大253字節(jié)APDU報(bào)文。
2.控制域編碼優(yōu)化
I幀發(fā)送序號采用左移1位存儲(避免序列號溢出)
U幀支持STARTDT_ACT/TESTFR_ACT等標(biāo)準(zhǔn)命令
3.數(shù)據(jù)格式處理
信息體地址使用3字節(jié)小端序編碼(適配電力設(shè)備規(guī)范)
遙測值采用IEEE754浮點(diǎn)型編碼,遙信狀態(tài)用單字節(jié)存儲
總召喚流程:主站發(fā)送C_IC_NA_1指令后,從站按點(diǎn)表順序返回所有遙測、遙信數(shù)據(jù),通過多幀I幀傳輸(每幀最大249字節(jié))。
變化數(shù)據(jù)主動上報(bào):從站檢測到數(shù)據(jù)變化時,通過ASDU的傳送原因字段(如0x03表示突發(fā)上送)觸發(fā)實(shí)時推送。