diff --git a/pom.xml b/pom.xml
index 82485c4..ba251d8 100644
--- a/pom.xml
+++ b/pom.xml
@@ -193,6 +193,11 @@
asn1bean
1.14.0
+
+ org.openmuc
+ j60870
+ 1.7.2
+
diff --git a/src/main/java/com/xydl/cac/iec104/Iec104Client.java b/src/main/java/com/xydl/cac/iec104/Iec104Client.java
new file mode 100644
index 0000000..ba14f0b
--- /dev/null
+++ b/src/main/java/com/xydl/cac/iec104/Iec104Client.java
@@ -0,0 +1,53 @@
+package com.xydl.cac.iec104;
+
+import lombok.extern.slf4j.Slf4j;
+import org.openmuc.j60870.*;
+import org.openmuc.j60870.ie.IeQualifierOfInterrogation;
+
+import java.io.IOException;
+import java.net.InetAddress;
+
+@Slf4j
+public class Iec104Client implements ConnectionEventListener {
+ public String ip;
+ public Integer port;
+ Connection connection;
+
+ public void connect(String _ip, Integer _port) throws Exception {
+ ip = _ip;
+ port = _port;
+ InetAddress address = InetAddress.getByName(ip);
+ ClientConnectionBuilder clientConnectionBuilder = new ClientConnectionBuilder(address)
+ .setMessageFragmentTimeout(5_000)
+ .setConnectionTimeout(20_000)
+ .setPort(port)
+ .setConnectionEventListener(this);
+ connection = clientConnectionBuilder.build();
+ }
+
+ public void disconnect() {
+ if (connection != null) {
+ connection.close();
+ }
+ }
+
+ public void interrogation() throws Exception {
+ connection.interrogation(1, CauseOfTransmission.ACTIVATION,
+ new IeQualifierOfInterrogation(20));
+ }
+
+ @Override
+ public void newASdu(Connection connection, ASdu aSdu) {
+ log.debug("Got new ASdu:" + aSdu.toString());
+ }
+
+ @Override
+ public void connectionClosed(Connection connection, IOException e) {
+
+ }
+
+ @Override
+ public void dataTransferStateChanged(Connection connection, boolean b) {
+
+ }
+}
diff --git a/src/main/java/com/xydl/cac/iec104/Iec104Server.java b/src/main/java/com/xydl/cac/iec104/Iec104Server.java
new file mode 100644
index 0000000..c8dce10
--- /dev/null
+++ b/src/main/java/com/xydl/cac/iec104/Iec104Server.java
@@ -0,0 +1,56 @@
+package com.xydl.cac.iec104;
+
+import lombok.extern.slf4j.Slf4j;
+import org.openmuc.j60870.*;
+
+import java.io.IOException;
+
+@Slf4j
+public class Iec104Server implements ServerEventListener {
+ Server _server;
+ int idCounter = 1;
+ public boolean started = false;
+ public Integer port = null;
+
+ public Iec104Server() {
+ }
+
+ public void start(int port) throws Exception {
+ if (!started) {
+ _server = Server.builder()
+ .setPort(port)
+ .setIoaFieldLength(3)
+ .setCommonAddressFieldLength(2)
+ .setCotFieldLength(2)
+ .build();
+ _server.start(this);
+ started = true;
+ this.port = port;
+ log.info("已启动IEC104服务端在" + port + "端口");
+ }
+ }
+
+ public void stop() {
+ if (_server != null) {
+ _server.stop();
+ log.info("已停止IEC104服务端于" + port + "端口");
+ }
+ started = false;
+ }
+
+
+ @Override
+ public ConnectionEventListener connectionIndication(Connection connection) {
+ return new ServerConnectionEventListener(connection, idCounter++);
+ }
+
+ @Override
+ public void serverStoppedListeningIndication(IOException e) {
+
+ }
+
+ @Override
+ public void connectionAttemptFailed(IOException e) {
+
+ }
+}
diff --git a/src/main/java/com/xydl/cac/iec104/ServerConnectionEventListener.java b/src/main/java/com/xydl/cac/iec104/ServerConnectionEventListener.java
new file mode 100644
index 0000000..7f80b5d
--- /dev/null
+++ b/src/main/java/com/xydl/cac/iec104/ServerConnectionEventListener.java
@@ -0,0 +1,94 @@
+package com.xydl.cac.iec104;
+
+import lombok.extern.slf4j.Slf4j;
+import org.openmuc.j60870.*;
+import org.openmuc.j60870.ie.*;
+
+import java.io.EOFException;
+import java.io.IOException;
+
+@Slf4j
+public class ServerConnectionEventListener implements ConnectionEventListener {
+ Connection connection;
+ int connectionId;
+ boolean selected = false;
+
+ public ServerConnectionEventListener(Connection connection, int connectionId) {
+ this.connection = connection;
+ this.connectionId = connectionId;
+ }
+
+ @Override
+ public void newASdu(Connection connection, ASdu aSdu) {
+ log.debug("Got new ASdu:" + aSdu.toString());
+ InformationObject informationObject = null;
+ try {
+ switch (aSdu.getTypeIdentification()) {
+ // interrogation command
+ case C_IC_NA_1:
+ log.debug("Got interrogation command (100). Will send scaled measured values.");
+ connection.sendConfirmation(aSdu);
+ // example GI response values
+ connection.send(new ASdu(ASduType.M_ME_NB_1, true, CauseOfTransmission.INTERROGATED_BY_STATION,
+ false, false, 0, aSdu.getCommonAddress(),
+ new InformationObject(1, new InformationElement[][]{
+ {new IeScaledValue(-32768), new IeQuality(false, false, false, false, false)},
+ {new IeScaledValue(10), new IeQuality(false, false, false, false, false)},
+ {new IeScaledValue(-5),
+ new IeQuality(false, false, false, false, false)}})));
+ connection.sendActivationTermination(aSdu);
+ break;
+ case C_SC_NA_1:
+ informationObject = aSdu.getInformationObjects()[0];
+ IeSingleCommand singleCommand = (IeSingleCommand) informationObject
+ .getInformationElements()[0][0];
+
+ if (informationObject.getInformationObjectAddress() != 5000) {
+ break;
+ }
+ if (singleCommand.isSelect()) {
+ log.debug("Got single command (45) with select true. Select command.");
+ selected = true;
+ connection.sendConfirmation(aSdu);
+ } else if (!singleCommand.isSelect() && selected) {
+ log.debug("Got single command (45) with select false. Execute selected command.");
+ selected = false;
+ connection.sendConfirmation(aSdu);
+ } else {
+ log.debug("Got single command (45) with select false. But no command is selected, no execution.");
+ }
+ break;
+ case C_CS_NA_1:
+ IeTime56 ieTime56 = new IeTime56(System.currentTimeMillis());
+ log.debug("Got Clock synchronization command (103). Send current time: " + ieTime56.toString());
+ connection.synchronizeClocks(aSdu.getCommonAddress(), ieTime56);
+ break;
+ case C_SE_NB_1:
+ log.debug("Got Set point command, scaled value (49)");
+ break;
+ default:
+ log.debug("Got unknown request: " + aSdu.toString(),
+ ". Send negative confirm with CoT UNKNOWN_TYPE_ID(44)");
+ connection.sendConfirmation(aSdu, aSdu.getCommonAddress(), true,
+ CauseOfTransmission.UNKNOWN_TYPE_ID);
+ }
+
+ } catch (EOFException e) {
+ log.debug("Will quit listening for commands on connection (" + connectionId,
+ ") because socket was closed.");
+ } catch (IOException e) {
+ log.debug("Will quit listening for commands on connection (" + connectionId, ") because of error: ",
+ e.getMessage());
+ }
+ }
+
+ @Override
+ public void connectionClosed(Connection connection, IOException e) {
+
+ }
+
+ @Override
+ public void dataTransferStateChanged(Connection connection, boolean b) {
+
+ }
+}