Java接入IEC60870-5-104

1,008 阅读3分钟

开源框架

j60870 Overview – IEC 60870-5-104 – OpenMUC

默认处理了心跳、响应等基础行为,只需要实现接口ConnectionEventListener对消息、连接进行处理即可。

目前网上其他文章基本低于1.7.2版本,接口差异是ConnectionEventListener的方法均增加了Connection connection参数。

Maven

 <dependency>
    <groupId>org.openmuc</groupId>
    <artifactId>j60870</artifactId>
    <version>1.7.2</version>
</dependency>

(作为主站)连接从站

import org.openmuc.j60870.ClientConnectionBuilder;

import java.io.IOException;
import java.net.InetAddress;

public class Main {
    public static void main(String[] args) {
        try {
            String host = "127.0.0.1";
            int port = 2404;
            InetAddress address = InetAddress.getByName(host);
            new ClientConnectionBuilder(address)
                    .setPort(port) // 默认2404
                    .setConnectionTimeout(3000) // 默认20秒
                    .setReservedASduTypeDecoder(new CustomTypeDecoder()) // 自定义实现ASDU解码器
                    .setConnectionEventListener(new IEC104Listener()) // ASDU 数据接收监听
                    .build()
                    .startDataTransfer(); // 启动
        } catch (IOException e) {
            System.err.println("IEC104 连接失败:" + e.getMessage());
        }
    }
}
import org.openmuc.j60870.ASduType;
import org.openmuc.j60870.ReservedASduTypeDecoder;
import org.openmuc.j60870.ie.InformationObject;
import org.openmuc.j60870.internal.ExtendedDataInputStream;

import java.util.Collections;
import java.util.List;

/**
 * 示范:自定义解析
 */
public class CustomTypeDecoder implements ReservedASduTypeDecoder {
    @Override
    public List<ASduType> getSupportedTypes() {
        return Collections.emptyList();
    }

    @Override
    public InformationObject decode(ExtendedDataInputStream is, ASduType aSduType) {
        return null;
    }
}
import org.openmuc.j60870.*;
import org.openmuc.j60870.ie.*;

import java.io.IOException;

public class IEC104Listener implements ConnectionEventListener {

    @Override
    public void newASdu(Connection connection, ASdu aSdu) {
        // 获取基本信息
        ASduType type = aSdu.getTypeIdentification();
        CauseOfTransmission cot = aSdu.getCauseOfTransmission();
        int commonAddress = aSdu.getCommonAddress();
        System.out.println("==============");

        System.out.println("ASdu 类型: " + type);
        System.out.println("传输原因: " + cot);
        System.out.println("公共地址: " + commonAddress);

        // 解析信息对象
        InformationObject[] informationObjects = aSdu.getInformationObjects();
        for (InformationObject informationObject : informationObjects) {
            System.out.println("信息对象地址: " + informationObject.getInformationObjectAddress());
            InformationElement[][] informationElements = informationObject.getInformationElements();
            for (int i = 0; i < informationElements.length; i++) {
                InformationElement[] elements = informationElements[i];
                // 1.总召 ======================
                if (type == ASduType.C_IC_NA_1) {
                    // 总召确认
                    if (cot == CauseOfTransmission.ACTIVATION_CON) {

                    }
                    // 总召结束
                    else if (cot == CauseOfTransmission.ACTIVATION_TERMINATION) {
        
                    }

                    if (elements != null && elements.length > 0) {
                        // 总召上行的对象类型
                        if (elements[0] instanceof IeQualifierOfInterrogation) {
                            IeQualifierOfInterrogation qoi = (IeQualifierOfInterrogation) elements[0];

                        }
                    }
                }
                // 2.初始化结束 ======================
                else if (type == ASduType.M_EI_NA_1) {
                    System.out.println("初始化结束");
                }
                // 3.单点遥信 ======================
                else if (type == ASduType.M_SP_NA_1) {
                    if (elements[0] instanceof IeSinglePointWithQuality) {
                        IeSinglePointWithQuality singlePoint = (IeSinglePointWithQuality) elements[0];
                        boolean value = singlePoint.isOn();
                        System.out.println("单点遥信值:" + (value ? "闭合" : "断开"));
                    }
                }
                // 4.单点遥测 ======================
                else if (type == ASduType.M_ME_NC_1) {
                    if (elements[0] instanceof IeShortFloat) {
                        IeShortFloat shortFloat = (IeShortFloat) elements[0];
                        System.out.println(String.format("测量值:%s", shortFloat.getValue()));
                    }
                }
                // 5.遥控确认 ======================
                else if (type == ASduType.C_SC_NA_1) {
                    if (elements[0] instanceof IeSingleCommand) {
                        IeSingleCommand singleCommand = (IeSingleCommand) elements[0];
                        System.out.println("遥控确认:" + ((singleCommand).isCommandStateOn() ? "闭合" : "断开"));
                    }
                } else {
                    // todo 补充其他类型
                }

            }
        }

    }

    @Override
    public void connectionClosed(Connection connection, IOException cause) {
        System.err.println(cause.getMessage());
    }

    @Override
    public void dataTransferStateChanged(Connection connection, boolean stopped) {
        if (stopped) {
            System.out.println("数据传输:停止");
            return;
        }
        System.out.println("数据传输:启动,准备发送测试命令...");
        // ... 比如发送 总召
        callC_IC_NA_1(connection, 1);
    }

    /**
     * 总召
     */
    public void callC_IC_NA_1(Connection connection, int commonAddress) {
        try {
            connection.interrogation(
                    commonAddress, // 公共地址
                    CauseOfTransmission.ACTIVATION, // 6
                    new IeQualifierOfInterrogation(20) // 召唤限定词,默认20,表示全站总召唤
            );
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 遥控
     */
    public void callC_SC_NA_1(Connection connection, int commonAddress, int informationObjectAddress, boolean commandStateOn) {
        InformationObject informationObject = new InformationObject(
                informationObjectAddress,
                new IeSingleCommand(commandStateOn, 0, false)
        );

        ASdu asdu = new ASdu(
                ASduType.C_SC_NA_1,
                true,
                CauseOfTransmission.ACTIVATION,
                false,
                false,
                0,
                commonAddress,
                informationObject
        );

        try {
            connection.send(asdu);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

}

以下代码未验证过

遥测发送

public void callM_ME_NC_1(Connection connection, int commonAddress, int informationObjectAddress, float value) {
    InformationObject io = new InformationObject(
            informationObjectAddress,
            new IeShortFloat(value),
            new IeQuality(false, false, false, false, false)
    );

    ASdu asdu = new ASdu(
            ASduType.M_ME_NC_1,           // 短浮点遥测
            false,                       // 不连续
            CauseOfTransmission.SPONTANEOUS, // 自发
            false,
            false,
            0,
            commonAddress,
            io
    );

    try {
        connection.send(asdu);
    } catch (IOException e) {
        e.printStackTrace();
    }
}

遥测接收

忽略ASduResolverIeShortFloatEvent,是我自行封装的接口和返回值

import org.openmuc.j60870.ASdu;
import org.openmuc.j60870.ASduType;
import org.openmuc.j60870.ie.*;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

/**
 * 短浮点遥测 不带时标
 */
public class M_ME_NC_1 implements ASduResolver<IeShortFloatEvent> {
    @Override
    public ASduType support() {
        return ASduType.M_ME_NC_1;
    }

    @Override
    public IeShortFloatEvent resolve(ASdu asdu) throws IOException {
        List<IEC104Data<IeShortFloat>> list = new ArrayList<>();
        int sequenceLength = asdu.getSequenceLength();
        for (InformationObject informationObject : asdu.getInformationObjects()) {
            int informationObjectAddress = informationObject.getInformationObjectAddress();

            InformationElement[][] informationElements = informationObject.getInformationElements();
            for (int i = 0; i < informationElements.length; i++) {
                InformationElement[] ies = informationElements[i];
                int infoElementAddress = informationObjectAddress;
                if (asdu.isSequenceOfElements()) {
                    infoElementAddress += i;
                } else {
                    // todo 待确认从哪里获取信息体地址
                }
                if (ies[0] instanceof IeShortFloat) {
                    IeShortFloat shortFloat = (IeShortFloat) ies[0];
                    System.out.println(String.format("信息体地址:%d, 测量值:%s", infoElementAddress, shortFloat.getValue()));
                    list.add(new IEC104Data<>(informationObjectAddress + i, shortFloat));
                } else {
                    System.out.println("单点遥测:未处理的ie类型" + ies[0].getClass().getSimpleName());
                }

//                if (ies[1] instanceof IeQuality) {
//                    IeQuality quality = (IeQuality) ies[1];
//                    System.out.println(String.format("品质: 溢出=%s,无效=%s", quality.isOverflow(), quality.isInvalid()));
//                }
//                System.out.println("--------------");
            }
        }
        return new IeShortFloatEvent(this, list);
    }

}