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

相关推荐
听雨·眠30 分钟前
go语言中defer使用指南
开发语言·后端·golang
丘山子2 小时前
一些鲜为人知的 IP 地址怪异写法
前端·后端·tcp/ip
CopyLower2 小时前
在 Spring Boot 中实现 WebSockets
spring boot·后端·iphone
.生产的驴3 小时前
SpringBoot 封装统一API返回格式对象 标准化开发 请求封装 统一格式处理
java·数据库·spring boot·后端·spring·eclipse·maven
景天科技苑3 小时前
【Rust】Rust中的枚举与模式匹配,原理解析与应用实战
开发语言·后端·rust·match·enum·枚举与模式匹配·rust枚举与模式匹配
追逐时光者4 小时前
MongoDB从入门到实战之Docker快速安装MongoDB
后端·mongodb
方圆想当图灵4 小时前
深入理解 AOP:使用 AspectJ 实现对 Maven 依赖中 Jar 包类的织入
后端·maven
豌豆花下猫4 小时前
Python 潮流周刊#99:如何在生产环境中运行 Python?(摘要)
后端·python·ai
嘻嘻嘻嘻嘻嘻ys4 小时前
《Spring Boot 3 + Java 17:响应式云原生架构深度实践与范式革新》
前端·后端
异常君4 小时前
线程池隐患解析:为何阿里巴巴拒绝 Executors
java·后端·代码规范