开源框架
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();
}
}
遥测接收
忽略ASduResolver和IeShortFloatEvent,是我自行封装的接口和返回值
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);
}
}