IEC104协议解析

一、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表示突发上送)触发实时推送‌。

相关推荐
冲鸭ONE9 分钟前
for循环优化方式有哪些?
后端·性能优化
刘鹏37834 分钟前
深入浅出Java中的CAS:原理、源码与实战应用
后端
Lx35238 分钟前
《从头开始学java,一天一个知识点》之:循环结构:for与while循环的使用场景
java·后端
fliter39 分钟前
RKE1、K3S、RKE2 三大 Kubernetes 发行版的比较
后端
aloha_40 分钟前
mysql 某个客户端主机在短时间内发起了大量失败的连接请求时
后端
程序员爱钓鱼41 分钟前
Go 语言高效连接 SQL Server(MSSQL)数据库实战指南
后端·go·sql server
xjz184241 分钟前
Java AQS(AbstractQueuedSynchronizer)实现原理详解
后端
Victor35642 分钟前
Zookeeper(97)如何在Zookeeper中实现分布式协调?
后端
至暗时刻darkest42 分钟前
go mod文件 项目版本管理
开发语言·后端·golang
程序员爱钓鱼42 分钟前
Go 语言高效连接 MySQL 数据库:从入门到实战
后端·mysql·go