diff --git a/.gitignore b/.gitignore
index a1c2a238a965f004ff76978ac1086aa6fe95caea..2a42a7d06145bffb24e465129c39a1906c74ace1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,8 @@
+# add ignore file mamal
+.idea
+modbus.iml
+target
+
# Compiled class file
*.class
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000000000000000000000000000000000000..ac94c66447abd92159db8c3ade99622f293ec7ea
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,43 @@
+
+ 4.0.0
+
+ cn.dst.modbus
+ modbus
+ 1.0-SNAPSHOT
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+ 8
+ 8
+
+
+
+
+ jar
+
+ modbus
+ http://maven.apache.org
+
+
+ UTF-8
+
+
+
+
+ junit
+ junit
+ 3.8.1
+ test
+
+
+
+ com.neuronrobotics
+ nrjavaserial
+ 5.1.1
+
+
+
diff --git a/src/main/java/cn/dst/modbus/config/ConfigUtils.java b/src/main/java/cn/dst/modbus/config/ConfigUtils.java
new file mode 100644
index 0000000000000000000000000000000000000000..e4763a01f45586d6c653671cda7288ad18e70038
--- /dev/null
+++ b/src/main/java/cn/dst/modbus/config/ConfigUtils.java
@@ -0,0 +1,13 @@
+package cn.dst.modbus.config;
+
+/**
+ * @author dongchangsong
+ * @since 2021/2/20 17:09
+ **/
+public class ConfigUtils {
+
+ public static final int DEFAULT_TIMEOUT = 1000;
+
+ private ConfigUtils() {
+ }
+}
diff --git a/src/main/java/cn/dst/modbus/core/Communication.java b/src/main/java/cn/dst/modbus/core/Communication.java
new file mode 100644
index 0000000000000000000000000000000000000000..8aa08908dbe0ec0f3b24ee98f3ffb3f4f69b574a
--- /dev/null
+++ b/src/main/java/cn/dst/modbus/core/Communication.java
@@ -0,0 +1,71 @@
+package cn.dst.modbus.core;
+
+import cn.dst.modbus.config.ConfigUtils;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.util.logging.Logger;
+
+/**
+ * @author dongchangsong
+ * @since 2021/2/26 16:21
+ **/
+public abstract class Communication implements ICommunication {
+
+ public abstract void closeCommunication();
+
+ private Logger logger = Logger.getLogger(Communication.class.getName());
+ protected int timeout = ConfigUtils.DEFAULT_TIMEOUT;
+
+ protected DataInputStream inputStream;
+ protected DataOutputStream outputStream;
+
+ protected DataReceiveCallback callback;
+
+ private final Object dataReceivedMonitor = new Object();
+
+ @Override
+ public void setDataReceiveCallback(DataReceiveCallback callback) {
+ this.callback = callback;
+ }
+
+ @Override
+ public void write(byte[] input) throws IOException {
+ StringBuilder stringBuilder = new StringBuilder();
+ stringBuilder.append("send data:");
+ for (byte item : input) {
+ stringBuilder.append(String.format(" %02X", item));
+ }
+ String msg = stringBuilder.toString();
+ logger.info(msg);
+ outputStream.write(input);
+
+ synchronized (dataReceivedMonitor) {
+ try {
+ dataReceivedMonitor.wait(timeout);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ protected void preProcessData(byte[] output, int count) {
+ byte[] data = new byte[count];
+ System.arraycopy(output, 0, data, 0, count);
+
+ if (callback != null) {
+ callback.process(data);
+ } else {
+ for (int i = 0; i < count; i++) {
+ String log = String.format("%02X ", data[i]);
+ logger.info(log);
+ }
+ logger.info("");
+ }
+
+ synchronized (dataReceivedMonitor) {
+ dataReceivedMonitor.notifyAll();
+ }
+ }
+}
diff --git a/src/main/java/cn/dst/modbus/core/DataReceiveCallback.java b/src/main/java/cn/dst/modbus/core/DataReceiveCallback.java
new file mode 100644
index 0000000000000000000000000000000000000000..b8930fd7ec170c3545de170dbc0b5b68e1b09626
--- /dev/null
+++ b/src/main/java/cn/dst/modbus/core/DataReceiveCallback.java
@@ -0,0 +1,10 @@
+package cn.dst.modbus.core;
+
+/**
+ * @author dongchangsong
+ * @since 2021/2/20 16:41
+ **/
+public interface DataReceiveCallback {
+
+ void process(byte[] data);
+}
diff --git a/src/main/java/cn/dst/modbus/core/Encodable.java b/src/main/java/cn/dst/modbus/core/Encodable.java
new file mode 100644
index 0000000000000000000000000000000000000000..584b0e4fbcc2e63ffc5d0308f50a291aa949674d
--- /dev/null
+++ b/src/main/java/cn/dst/modbus/core/Encodable.java
@@ -0,0 +1,12 @@
+package cn.dst.modbus.core;
+
+/**
+ * @author dongchangsong
+ * @since 2021/2/22 14:05
+ **/
+public interface Encodable {
+
+ boolean parseBoolean();
+
+ int parseInteger();
+}
diff --git a/src/main/java/cn/dst/modbus/core/EncodedValue.java b/src/main/java/cn/dst/modbus/core/EncodedValue.java
new file mode 100644
index 0000000000000000000000000000000000000000..a308abe65594ed53ff1f6e745f851cc73441a148
--- /dev/null
+++ b/src/main/java/cn/dst/modbus/core/EncodedValue.java
@@ -0,0 +1,34 @@
+package cn.dst.modbus.core;
+
+/**
+ * @author dongchangsong
+ * @since 2021/2/22 14:06
+ **/
+public class EncodedValue implements Encodable {
+
+ private byte[] originalValue;
+
+ public EncodedValue(byte[] value){
+ originalValue = value;
+ }
+
+ @Override
+ public boolean parseBoolean() {
+ boolean result = false;
+ result = (originalValue[0] & 0x01) == 1;
+ return result;
+ }
+
+ @Override
+ public int parseInteger() {
+ int result = 0;
+ for (int i = 0; i < originalValue.length; i++){
+ int add = 128;
+ if (originalValue[i] >> 7 == 0){
+ add = 0;
+ }
+ result += (originalValue[i] & 0x7f + add) << (originalValue.length - 1 - i) * 8;
+ }
+ return result;
+ }
+}
diff --git a/src/main/java/cn/dst/modbus/core/ICommunication.java b/src/main/java/cn/dst/modbus/core/ICommunication.java
new file mode 100644
index 0000000000000000000000000000000000000000..7e5dda7043e671fbf3fe7d11f985b5b0dde81b52
--- /dev/null
+++ b/src/main/java/cn/dst/modbus/core/ICommunication.java
@@ -0,0 +1,13 @@
+package cn.dst.modbus.core;
+
+import java.io.IOException;
+
+/**
+ * @author dongchangsong
+ * @since 2021/2/26 14:24
+ **/
+public interface ICommunication {
+
+ void write(byte[] data) throws IOException;
+ void setDataReceiveCallback(DataReceiveCallback callback);
+}
diff --git a/src/main/java/cn/dst/modbus/core/Modbus.java b/src/main/java/cn/dst/modbus/core/Modbus.java
new file mode 100644
index 0000000000000000000000000000000000000000..441baf045e7675c4457a08f2f3613667fed967fa
--- /dev/null
+++ b/src/main/java/cn/dst/modbus/core/Modbus.java
@@ -0,0 +1,232 @@
+package cn.dst.modbus.core;
+
+import cn.dst.modbus.exception.IllegalAddressException;
+import cn.dst.modbus.exception.IllegalFunctionCodeException;
+import cn.dst.modbus.specification.*;
+
+import java.io.IOException;
+import java.util.logging.Logger;
+
+import static cn.dst.modbus.specification.PublicFunctionCode.READ_COILS;
+import static cn.dst.modbus.specification.PublicFunctionCode.getPublicFunctionCode;
+
+/**
+ * @author dongchangsong
+ * @since 2021/2/20 16:33
+ **/
+public abstract class Modbus implements IModbus, DataReceiveCallback {
+
+ private Logger logger = Logger.getLogger(Modbus.class.getName());
+
+ protected abstract byte[] dataPreProcess(int dataCount, int datatype);
+
+ protected abstract void dataPreProcess();
+
+ @Deprecated
+ protected abstract void send(int functionCode, int slaveId, int startAddress, int quantity, int byteCount, byte[] outputsValue) throws IOException;
+
+ protected abstract void send(int functionCode, int slaveId, byte[] outputValues, int... params) throws IOException;
+
+ protected ICommunication communicationManager;
+
+ protected byte[] dataReceived;
+
+ protected Modbus(ICommunication communicationManager) throws IOException {
+ this.communicationManager = communicationManager;
+ communicationManager.setDataReceiveCallback(this);
+ }
+
+ @Override
+ public Encodable[] readCoils(int slaveId, int startAddress, int quantityOfCoils) throws Exception {
+ send(READ_COILS.getValue(), slaveId, startAddress, quantityOfCoils);
+ return dataProcess(quantityOfCoils, DataType.BOOLEAN);
+ }
+
+ @Override
+ public Encodable[] readDiscreteInputs(int slaveId, int startingAddress, int quantityOfInputs) throws Exception {
+ send(PublicFunctionCode.READ_DISCRETE_INPUTS.getValue(), slaveId, startingAddress, quantityOfInputs);
+ return dataProcess(quantityOfInputs, DataType.BOOLEAN);
+ }
+
+ @Override
+ public Encodable[] readHoldingRegisters(int slaveId, int startingAddress, int quantityOfRegisters, int dataType) throws Exception {
+ send(PublicFunctionCode.READ_HOLDING_REGISTERS.getValue(), slaveId, startingAddress, quantityOfRegisters);
+ return dataProcess(quantityOfRegisters, dataType);
+ }
+
+ @Override
+ public Encodable[] readInputRegisters(int slaveId, int startingAddress, int quantityOfInputRegisters, int dataType) throws Exception {
+ send(PublicFunctionCode.READ_INPUT_REGISTERS.getValue(), slaveId, startingAddress, quantityOfInputRegisters);
+ return dataProcess(quantityOfInputRegisters, dataType);
+ }
+
+ @Override
+ public boolean writeSingleCoil(int slaveId, int outputAddress, int outputValue) throws IOException {
+ send(PublicFunctionCode.WRITE_SINGLE_COIL.getValue(), slaveId, outputAddress, outputValue);
+ return true;
+ }
+
+ @Override
+ public boolean writeSingleRegister(int slaveId, int registerAddress, int registerValue) throws IOException {
+ send(PublicFunctionCode.WRITE_SINGLE_REGISTER.getValue(), slaveId, registerAddress, registerValue);
+ return true;
+ }
+
+ @Override
+ public boolean writeMultipleCoils(int slaveId, int startingAddress, int quantityOfOutputs, int byteCount, byte[] outputsValue) throws IOException {
+ send(PublicFunctionCode.WRITE_MULTIPLE_COILS.getValue(), slaveId, startingAddress, quantityOfOutputs, byteCount, outputsValue);
+ return true;
+ }
+
+ @Override
+ public boolean writeMultipleRegisters(int slaveId, int startingAddress, int quantityOfRegisters, int byteCount, byte[] registersValue) throws IOException {
+ send(PublicFunctionCode.WRITE_MULTIPLE_REGISTERS.getValue(), slaveId, startingAddress, quantityOfRegisters, byteCount, registersValue);
+ return true;
+ }
+
+ @Override
+ public boolean maskWriteRegister(int slaveId, int referenceAddress, int andMask, int orMask) throws IOException {
+ send(PublicFunctionCode.MASK_WRITE_REGISTER.getValue(), slaveId, null, referenceAddress, andMask, orMask);
+ return true;
+ }
+
+ @Override
+ public boolean readWriteMultipleRegisters(int slaveId, int readStartingAddress, int quantityToRead, int writeStartingAddress, int quantityToWrite, byte[] values) throws IOException {
+ send(PublicFunctionCode.READ_WRITE_MULTIPLE_REGISTERS.getValue(), slaveId, values, readStartingAddress, quantityToRead, writeStartingAddress, quantityToWrite);
+ return true;
+ }
+
+ @Override
+ public boolean readFifoQueue(int slaveId, int fifoPointerAddress) throws IOException {
+ send(PublicFunctionCode.READ_FIFO_QUEUE.getValue(), slaveId, null, fifoPointerAddress, 0);
+ return true;
+ }
+
+ @Override
+ public boolean encapsulatedInterfaceTransport(int slaveId, int meiType, byte[] meiTypeSpecificData) throws IOException {
+ send(PublicFunctionCode.ENCAPSULATED_INTERFACE_TRANSPORT.getValue(), slaveId, meiTypeSpecificData, meiType, 0);
+ return true;
+ }
+
+ @Override
+ public boolean readDeviceIdentification(int slaveId, int readDeviceIdCode, int objectId) throws IOException {
+ send(PublicFunctionCode.ENCAPSULATED_INTERFACE_TRANSPORT.getValue(), slaveId, null, PublicFunctionCode.SUB_CODE_READ_DEVICE_IDENTIFICATION.getValue(), readDeviceIdCode, objectId);
+ return true;
+ }
+
+ @Override
+ public void process(byte[] data) {
+ dataReceived = data;
+ StringBuilder stringBuilder = new StringBuilder();
+ stringBuilder.append("receive data: ");
+ for (byte datum : dataReceived) {
+ stringBuilder.append(String.format("%02X ", datum));
+ }
+ String msg = stringBuilder.toString();
+ logger.info(msg);
+ dataPreProcess();
+ }
+
+ private void send(int functionCode, int slaveId, int startAddress, int quantityOfCoils) throws IOException {
+ send(functionCode, slaveId, startAddress, quantityOfCoils, 0, null);
+ }
+
+ protected void dataCheck(int functionCode, int startAddress, int quantity) {
+ if (startAddress < 0x0000 || startAddress > 0xFFFF) {
+ throw new IllegalAddressException(startAddress);
+ }
+
+ assert getPublicFunctionCode(functionCode) != null;
+ switch (getPublicFunctionCode(functionCode)) {
+ case READ_COILS:
+ case READ_DISCRETE_INPUTS:
+ if (quantity > Utils.READ_COILS_QUANTITY_MAX || quantity < 1) {
+ throw new IllegalArgumentException("read coils/discrete quantity is: " + quantity + ", according to specification the max is " + Utils.READ_COILS_QUANTITY_MAX + ", and it is must be a positive integer.");
+ }
+ break;
+ case READ_HOLDING_REGISTERS:
+ case READ_INPUT_REGISTERS:
+ if (quantity > Utils.READ_REGISTER_QUANTITY_MAX || quantity < 1) {
+ throw new IllegalArgumentException("read register quantity is: " + quantity + ", according to specification the max is " + Utils.READ_REGISTER_QUANTITY_MAX + ", and it is must be a positive integer.");
+ }
+ break;
+ case WRITE_SINGLE_COIL:
+ if (quantity != Utils.ON && quantity != Utils.OFF) {
+ throw new IllegalArgumentException("write single coil, the value must be: " + String.format("0x%04X", Utils.ON) + " or " + String.format("0x%04X", Utils.OFF));
+ }
+ break;
+ case WRITE_SINGLE_REGISTER:
+ if (quantity < 0x0000 || quantity > 0xFFFF) {
+ throw new IllegalArgumentException("write single register, register value must be: not less then 0x0000 and not more than 0xFFFF");
+ }
+ break;
+ case WRITE_MULTIPLE_COILS:
+ if (quantity < 1 || quantity > Utils.WRITE_MULTIPLE_COILS_QUANTITY_MAX) {
+ throw new IllegalArgumentException("write multiple coils, the max quantity of coils is " + Utils.WRITE_MULTIPLE_COILS_QUANTITY_MAX);
+ }
+ break;
+ case WRITE_MULTIPLE_REGISTERS:
+ if (quantity < 1 || quantity > Utils.WRITE_MULTIPLE_REGISTER_QUANTITY_MAX) {
+ throw new IllegalArgumentException("write multiple registers, the max quantity of registers is " + Utils.WRITE_MULTIPLE_REGISTER_QUANTITY_MAX);
+ }
+ break;
+ case MASK_WRITE_REGISTER:
+ case READ_WRITE_MULTIPLE_REGISTERS:
+ case READ_FIFO_QUEUE:
+ case ENCAPSULATED_INTERFACE_TRANSPORT:
+
+ break;
+ default:
+ throw new IllegalFunctionCodeException(functionCode);
+ }
+ }
+
+ private Encodable[] dataProcess(int dataCount, int datatype) throws Exception {
+ if (dataReceived == null) {
+ throw new Exception("Network timeout, you can retry!");
+ }
+
+ byte[] data = dataPreProcess(dataCount, datatype);
+ Encodable[] result = new Encodable[0];
+ switch (datatype) {
+ case DataType.BOOLEAN:
+ result = parseBoolean(data, dataCount);
+ break;
+ case DataType.TWO_BYTE_INT_SIGNED:
+ result = parseInteger(dataCount);
+ break;
+ default:
+
+ break;
+ }
+
+ dataReceived = null;
+ return result;
+ }
+
+ private Encodable[] parseBoolean(byte[] data, int dataCount) {
+ Encodable[] result = new Encodable[dataCount];
+ int index = 0;
+ int dataByteCount = data[1] & 0xff;
+ for (int i = 0; i < dataByteCount; i++) {
+ int moveIndex = 0;
+ while (index < dataCount && moveIndex < 8) {
+ result[index++] = new EncodedValue(new byte[]{(byte) (data[2 + i] >> moveIndex++)});
+ }
+
+ if (index == dataCount) {
+ break;
+ }
+ }
+
+ return result;
+ }
+
+ private Encodable[] parseInteger(int dataCount) {
+ Encodable[] result = new Encodable[dataCount];
+ for (int i = 0; i < dataCount; i++) {
+ result[i] = new EncodedValue(new byte[]{dataReceived[i * 2 + 9], dataReceived[i * 2 + 1 + 9]});
+ }
+ return result;
+ }
+}
diff --git a/src/main/java/cn/dst/modbus/core/ModbusFactory.java b/src/main/java/cn/dst/modbus/core/ModbusFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..4e6b48a5bb40bdf89963dddc00912d7a6ce30e37
--- /dev/null
+++ b/src/main/java/cn/dst/modbus/core/ModbusFactory.java
@@ -0,0 +1,34 @@
+package cn.dst.modbus.core;
+
+import cn.dst.modbus.network.TcpManager;
+import cn.dst.modbus.network.TcpModbus;
+import cn.dst.modbus.serial.SerialManager;
+import cn.dst.modbus.serial.SerialModbus;
+import cn.dst.modbus.specification.IModbus;
+
+import java.io.IOException;
+
+/**
+ * @author dongchangsong
+ * @since 2021/2/26 17:40
+ **/
+public final class ModbusFactory {
+
+ public static IModbus createTcpMaster(String ip) throws IOException {
+ TcpManager tcpManager = new TcpManager(ip);
+ return new TcpModbus(tcpManager);
+ }
+
+ public static IModbus createTcpMaster(String ip, int port) throws IOException {
+ TcpManager tcpManager = new TcpManager(ip, port);
+ return new TcpModbus(tcpManager);
+ }
+
+ public static IModbus createSerialMaster(String port, int baud) throws IOException {
+ SerialManager serialManager = new SerialManager(port, baud);
+ return new SerialModbus(serialManager);
+ }
+
+ private ModbusFactory() {
+ }
+}
diff --git a/src/main/java/cn/dst/modbus/exception/IllegalAddressException.java b/src/main/java/cn/dst/modbus/exception/IllegalAddressException.java
new file mode 100644
index 0000000000000000000000000000000000000000..a2e228e70bab3e43564e8cbaaa40556aabbc5035
--- /dev/null
+++ b/src/main/java/cn/dst/modbus/exception/IllegalAddressException.java
@@ -0,0 +1,12 @@
+package cn.dst.modbus.exception;
+
+/**
+ * @author dongchangsong
+ * @since 2021/3/2 11:32
+ **/
+public class IllegalAddressException extends ModbusException {
+
+ public IllegalAddressException(int startAddress){
+ super("start address is invalidate with " + startAddress);
+ }
+}
diff --git a/src/main/java/cn/dst/modbus/exception/IllegalFunctionCodeException.java b/src/main/java/cn/dst/modbus/exception/IllegalFunctionCodeException.java
new file mode 100644
index 0000000000000000000000000000000000000000..60f6c7fbb5bc0727fc10c9d259c1443b76cc2b2d
--- /dev/null
+++ b/src/main/java/cn/dst/modbus/exception/IllegalFunctionCodeException.java
@@ -0,0 +1,12 @@
+package cn.dst.modbus.exception;
+
+/**
+ * @author dongchangsong
+ * @since 2021/3/1 12:05
+ **/
+public class IllegalFunctionCodeException extends ModbusException {
+
+ public IllegalFunctionCodeException(int functionCode){
+ super("data check rule not exist for function code: " + functionCode);
+ }
+}
diff --git a/src/main/java/cn/dst/modbus/exception/ModbusException.java b/src/main/java/cn/dst/modbus/exception/ModbusException.java
new file mode 100644
index 0000000000000000000000000000000000000000..46a9dcac3b18ce72d412e45c9c5e036244e00823
--- /dev/null
+++ b/src/main/java/cn/dst/modbus/exception/ModbusException.java
@@ -0,0 +1,11 @@
+package cn.dst.modbus.exception;
+
+/**
+ * @author dongchangsong
+ * @since 2021/3/8 14:20
+ **/
+public class ModbusException extends RuntimeException {
+ public ModbusException(String msg){
+ super(msg);
+ }
+}
diff --git a/src/main/java/cn/dst/modbus/network/TcpManager.java b/src/main/java/cn/dst/modbus/network/TcpManager.java
new file mode 100644
index 0000000000000000000000000000000000000000..aa995d773b55211d732fde86d788d7cdd41e0491
--- /dev/null
+++ b/src/main/java/cn/dst/modbus/network/TcpManager.java
@@ -0,0 +1,79 @@
+package cn.dst.modbus.network;
+
+import cn.dst.modbus.core.Communication;
+import cn.dst.modbus.core.DataReceiveCallback;
+import cn.dst.modbus.specification.Utils;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.net.SocketAddress;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * @author dongchangsong
+ * @since 2021/2/20 16:36
+ **/
+public class TcpManager extends Communication {
+
+ private Logger tcpLogger = Logger.getLogger(TcpManager.class.getName());
+
+ private String ip;
+ private int port;
+
+ private Socket socket;
+
+ public TcpManager(String ip) throws IOException {
+ this(ip, Utils.DEFAULT_PORT);
+ }
+
+ public TcpManager(String ip, int port) throws IOException {
+ this(ip, port, null);
+ }
+
+ public TcpManager(String ip, int port, DataReceiveCallback callback) throws IOException {
+ this.ip = ip;
+ this.port = port;
+ this.callback = callback;
+
+ createSocket();
+
+ startReceiveData();
+ }
+
+ private void createSocket() throws IOException {
+ SocketAddress address = new InetSocketAddress(ip, port);
+ socket = new Socket();
+ socket.connect(address);
+ inputStream = new DataInputStream(socket.getInputStream());
+ outputStream = new DataOutputStream(socket.getOutputStream());
+ }
+
+ private void startReceiveData() {
+ new Thread(() -> {
+ byte[] output = new byte[1024];
+ int count;
+ try {
+ while ((count = inputStream.read(output)) != -1) {
+ preProcessData(output, count);
+ }
+ } catch (IOException exception) {
+ tcpLogger.log(Level.SEVERE, exception.getMessage());
+ }
+ }).start();
+ }
+
+ @Override
+ public void closeCommunication() {
+ if (socket != null) {
+ try {
+ socket.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+}
diff --git a/src/main/java/cn/dst/modbus/network/TcpModbus.java b/src/main/java/cn/dst/modbus/network/TcpModbus.java
new file mode 100644
index 0000000000000000000000000000000000000000..bd8e9ef73f7d9f946b809e1151ce8a733e97d1f3
--- /dev/null
+++ b/src/main/java/cn/dst/modbus/network/TcpModbus.java
@@ -0,0 +1,55 @@
+package cn.dst.modbus.network;
+
+import cn.dst.modbus.core.ICommunication;
+import cn.dst.modbus.core.Modbus;
+import cn.dst.modbus.exception.ModbusException;
+import cn.dst.modbus.specification.Adu;
+import cn.dst.modbus.specification.ExceptionCode;
+import cn.dst.modbus.specification.Pdu;
+
+import java.io.IOException;
+import java.util.Random;
+
+/**
+ * @author dongchangsong
+ * @since 2021/2/26 14:33
+ **/
+public class TcpModbus extends Modbus {
+
+ Random random = new Random();
+
+ public TcpModbus(ICommunication communicationManager) throws IOException {
+ super(communicationManager);
+ }
+
+ @Override
+ protected byte[] dataPreProcess(int dataCount, int datatype) {
+ byte[] data = new byte[dataReceived.length - 7];
+ System.arraycopy(dataReceived, 7, data, 0, data.length);
+
+ return data;
+ }
+
+ @Override
+ protected void dataPreProcess() {
+ if ((dataReceived[7] & 0x80) == 0x80) {
+ throw new ModbusException("Modbus exception code: " + ExceptionCode.getNameByValue(dataReceived[8] & 0x7F));
+ }
+ }
+
+ @Override
+ protected void send(int functionCode, int slaveId, int startAddress, int quantity, int byteCount, byte[] outputsValue) throws IOException {
+ dataCheck(functionCode, startAddress, quantity);
+ byte[] pdu = Pdu.generatePdu(functionCode, outputsValue, startAddress, quantity, byteCount);
+ byte[] input = Adu.generateTcpIpAdu(slaveId, pdu);
+ communicationManager.write(input);
+ }
+
+ @Override
+ protected void send(int functionCode, int slaveId, byte[] outputValues, int... params) throws IOException {
+ dataCheck(functionCode, params[0], params[1]);
+ byte[] pdu = Pdu.generatePdu(functionCode, outputValues, params);
+ byte[] input = Adu.generateTcpIpAdu(slaveId, pdu);
+ communicationManager.write(input);
+ }
+}
diff --git a/src/main/java/cn/dst/modbus/serial/SerialManager.java b/src/main/java/cn/dst/modbus/serial/SerialManager.java
new file mode 100644
index 0000000000000000000000000000000000000000..4fbf34b41c4d03a2e0462327ebeb181aa056809c
--- /dev/null
+++ b/src/main/java/cn/dst/modbus/serial/SerialManager.java
@@ -0,0 +1,67 @@
+package cn.dst.modbus.serial;
+
+import cn.dst.modbus.core.Communication;
+import cn.dst.modbus.core.DataReceiveCallback;
+import gnu.io.NRSerialPort;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.util.logging.Logger;
+
+/**
+ * @author dongchangsong
+ * @since 2021/2/26 10:15
+ **/
+public class SerialManager extends Communication {
+
+ private Logger logger = Logger.getLogger(SerialManager.class.getName());
+
+ public SerialManager(String port, int baud) {
+ this(port, baud, null);
+ }
+
+ public SerialManager(String port, int baud, DataReceiveCallback callback) {
+ this.callback = callback;
+
+ NRSerialPort serialPort = new NRSerialPort(port, baud);
+ serialPort.connect();
+
+ inputStream = new DataInputStream(serialPort.getInputStream());
+ outputStream = new DataOutputStream(serialPort.getOutputStream());
+
+ startReceiveData();
+
+ }
+
+ private void startReceiveData() {
+ new Thread(() -> {
+ byte[] output = new byte[1024];
+ int count;
+ try {
+ while ((count = inputStream.read(output)) != -1) {
+ if (count == 0) {
+ logger.info("no data received, but returned!!! count:" + count);
+ continue;
+ }
+
+ // TODO:
+ if (count == 1) {
+ String log = String.format("skip slave id in response data: %02X", output[0]);
+ logger.info(log);
+ continue;
+ }
+
+ preProcessData(output, count);
+ }
+ } catch (IOException exception) {
+ logger.info(exception.getMessage());
+ }
+ }).start();
+ }
+
+ @Override
+ public void closeCommunication() {
+
+ }
+}
diff --git a/src/main/java/cn/dst/modbus/serial/SerialModbus.java b/src/main/java/cn/dst/modbus/serial/SerialModbus.java
new file mode 100644
index 0000000000000000000000000000000000000000..0f470f0b444fcb2091757a84430291b1dc45e85b
--- /dev/null
+++ b/src/main/java/cn/dst/modbus/serial/SerialModbus.java
@@ -0,0 +1,77 @@
+package cn.dst.modbus.serial;
+
+import cn.dst.modbus.core.ICommunication;
+import cn.dst.modbus.core.Modbus;
+import cn.dst.modbus.exception.ModbusException;
+import cn.dst.modbus.specification.ExceptionCode;
+import cn.dst.modbus.specification.ModbusCrc;
+
+import java.io.IOException;
+
+/**
+ * @author dongchangsong
+ * @since 2021/2/26 14:33
+ **/
+public class SerialModbus extends Modbus {
+ public SerialModbus(ICommunication communicationManager) throws IOException {
+ super(communicationManager);
+ }
+
+ @Override
+ protected byte[] dataPreProcess(int dataCount, int datatype) {
+ byte[] data = new byte[dataReceived.length - 2];
+ System.arraycopy(dataReceived, 0, data, 0, data.length);
+
+ return data;
+ }
+
+ @Override
+ protected void dataPreProcess() {
+ if ((dataReceived[0] & 0x80) == 0x80) {
+ throw new ModbusException("Modbus exception code: " + ExceptionCode.getNameByValue(dataReceived[1] & 0x7F));
+ }
+ }
+
+ @Override
+ protected void send(int functionCode, int slaveId, int startAddress, int quantity, int byteCount, byte[] outputsValue) throws IOException {
+
+ byte[] data = new byte[6];
+
+ /**
+ * slave id
+ * 从机地址
+ */
+ data[0] = (byte) slaveId;
+
+ /**
+ * function code
+ */
+ data[1] = (byte) functionCode;
+
+ /**
+ * 起始地址。高字节在前。
+ */
+ data[2] = (byte) (startAddress >> 8);
+ data[3] = (byte) startAddress;
+
+ /**
+ * 寄存器/线圈/离散量个数。高字节在前。
+ */
+ data[4] = (byte) (quantity >> 8);
+ data[5] = (byte) quantity;
+
+ ModbusCrc crc = new ModbusCrc();
+ byte[] crcValue = crc.yyy(data);
+
+ byte[] input = new byte[data.length + 2];
+ System.arraycopy(data, 0, input, 0, data.length);
+ System.arraycopy(crcValue, 0, input, data.length, crcValue.length);
+
+ communicationManager.write(input);
+ }
+
+ @Override
+ protected void send(int functionCode, int slaveId, byte[] outputValues, int... params) throws IOException {
+
+ }
+}
diff --git a/src/main/java/cn/dst/modbus/specification/Adu.java b/src/main/java/cn/dst/modbus/specification/Adu.java
new file mode 100644
index 0000000000000000000000000000000000000000..618b3157f304eb73aaa1c64540aa28ffec8a3bb7
--- /dev/null
+++ b/src/main/java/cn/dst/modbus/specification/Adu.java
@@ -0,0 +1,67 @@
+package cn.dst.modbus.specification;
+
+import java.nio.ByteBuffer;
+import java.util.Random;
+
+/**
+ * @author dongchangsong
+ * @since 2021/3/9 16:43
+ **/
+public final class Adu {
+
+ static Random random = new Random();
+
+ private static byte[] generateAdu(int type, int slaveId, byte[] pdu) {
+ ByteBuffer result = ByteBuffer.allocate(32);
+
+ if (type == Utils.SERIAL) {
+ result.put((byte) slaveId);
+ result.put(pdu);
+ result.put(ModbusCrc.yyy(result.array()));
+ } else if (type == Utils.TCP_IP) {
+ /**
+ * 第一二个字节是事务处理标识,一般是报文序号。高字节在前。
+ */
+ short index = (short) random.nextInt();
+ result.put((byte) (index >> 8));
+ result.put((byte) index);
+
+ /**
+ * 第三四个字节是协议标识,一般默认为 0x00 0x00。高字节在前。
+ */
+ result.put((byte) 0x00);
+ result.put((byte) 0x00);
+
+ /**
+ * 第五六个字节是报文长度。高字节在前。
+ * 报文长度指除去事务处理标识、协议标识、报文长度的数据长度。
+ */
+ int length = pdu.length + 1;
+ result.put((byte) (length >> 8));
+ result.put((byte) length);
+
+ /**
+ * 第七个字节是单元标识。
+ * 范围从0x00-0xFF。本机位0或1,其余用于RS485从机地址。
+ */
+ result.put((byte) slaveId);
+ result.put(pdu);
+ }
+
+ result.flip();
+ byte[] retValue = new byte[result.limit()];
+ result.get(retValue);
+ return retValue;
+ }
+
+ public static byte[] generateSerialAdu(int slaveId, byte[] pdu) {
+ return generateAdu(Utils.SERIAL, slaveId, pdu);
+ }
+
+ public static byte[] generateTcpIpAdu(int slaveId, byte[] pdu) {
+ return generateAdu(Utils.TCP_IP, slaveId, pdu);
+ }
+
+ private Adu() {
+ }
+}
diff --git a/src/main/java/cn/dst/modbus/specification/DataType.java b/src/main/java/cn/dst/modbus/specification/DataType.java
new file mode 100644
index 0000000000000000000000000000000000000000..bff0627b261480f24f2fd97bfc6caf7a4b103e22
--- /dev/null
+++ b/src/main/java/cn/dst/modbus/specification/DataType.java
@@ -0,0 +1,36 @@
+package cn.dst.modbus.specification;
+
+/**
+ * @author dongchangsong
+ * @since 2021/2/22 10:37
+ **/
+public class DataType {
+
+ public static final int BOOLEAN = 1;
+
+ /**
+ * for example: 0x1234
+ * if SWAPPED, the result is 0x3412
+ */
+ public static final int TWO_BYTE_INT_SIGNED = 2;
+ public static final int TWO_BYTE_INT_UNSIGNED = 3;
+ public static final int TWO_BYTE_INT_SIGNED_SWAPPED = 4;
+ public static final int TWO_BYTE_INT_UNSIGNED_SWAPPED = 5;
+
+ /**
+ * for example: 0x12345678
+ * if SWAPPED, the result is 0x56781234
+ * if SWAPPED SWAPPED, the result is 0x78563412
+ */
+ public static final int FOUR_BYTE_INT_SIGNED = 6;
+ public static final int FOUR_BYTE_INT_UNSIGNED = 7;
+ public static final int FOUR_BYTE_INT_SIGNED_SWAPPED = 8;
+ public static final int FOUR_BYTE_INT_UNSIGNED_SWAPPED = 9;
+ public static final int FOUR_BYTE_INT_SIGNED_SWAPPED_SWAPPED = 10;
+ public static final int FOUR_BYTE_INT_UNSIGNED_SWAPPED_SWAPPED = 11;
+
+ public static final int FOUR_BYTE_FLOAT = 12;
+
+ private DataType() {
+ }
+}
diff --git a/src/main/java/cn/dst/modbus/specification/ExceptionCode.java b/src/main/java/cn/dst/modbus/specification/ExceptionCode.java
new file mode 100644
index 0000000000000000000000000000000000000000..7017d0409276abf675f311c9d6e92b3672072c26
--- /dev/null
+++ b/src/main/java/cn/dst/modbus/specification/ExceptionCode.java
@@ -0,0 +1,48 @@
+package cn.dst.modbus.specification;
+
+/**
+ * Modbus exception response code
+ *
+ * If the server receives the request without a communication error, but cannot handle it,
+ * the server will return an exception response informing the client of the nature of the error.
+ *
+ * @author dongchangsong
+ * @since 2021/2/20 15:15
+ **/
+public enum ExceptionCode {
+ /**
+ * The function code received in the query is not an
+ * allowable action for the server. This may be
+ * because the function code is only applicable to
+ * newer devices, and was not implemented in the
+ * unit selected. It could also indicate that the server
+ * is in the wrong state to process a request of this
+ * type, for example because it is un-configured and
+ * is being asked to return register values.
+ */
+ ILLEGAL_FUNCTION(0x01),
+ ILLEGAL_DATA_ADDRESS(0x02),
+ ILLEGAL_DATA_VALUE(0x03),
+ SERVER_DEVICE_FAILURE(0x04),
+ ACKNOWLEDGE(0x05),
+ SERVER_DEVICE_BUSY(0x06),
+ MEMORY_PARITY_ERROR(0x08),
+ GATEWAY_PATH_UNAVAILABLE(0x0A),
+ GATEWAY_TARGET_DEVICE_FAILED_TO_RESPOND(0x0B);
+
+ int value;
+
+ ExceptionCode(int value) {
+ this.value = value;
+ }
+
+ public static String getNameByValue(int value) {
+ for (ExceptionCode code : ExceptionCode.values()) {
+ if (code.value == value) {
+ return code.name();
+ }
+ }
+
+ return null;
+ }
+}
diff --git a/src/main/java/cn/dst/modbus/specification/IModbus.java b/src/main/java/cn/dst/modbus/specification/IModbus.java
new file mode 100644
index 0000000000000000000000000000000000000000..6eb515fc8674990e94fcdecb5013d2783fe132f0
--- /dev/null
+++ b/src/main/java/cn/dst/modbus/specification/IModbus.java
@@ -0,0 +1,30 @@
+package cn.dst.modbus.specification;
+
+import cn.dst.modbus.core.Encodable;
+
+import java.io.IOException;
+
+/**
+ * @author dongchangsong
+ * @since 2021/2/20 16:29
+ **/
+public interface IModbus {
+
+ Encodable[] readCoils(int slaveId, int startAddress, int quantityOfCoils) throws Exception;
+ Encodable[] readDiscreteInputs(int slaveId, int startingAddress, int quantityOfInputs) throws Exception;
+ Encodable[] readHoldingRegisters(int slaveId, int startingAddress, int quantityOfRegisters, int dataType) throws Exception;
+ Encodable[] readInputRegisters(int slaveId, int startingAddress, int quantityOfInputRegisters, int dataType) throws Exception;
+
+ boolean writeSingleCoil(int slaveId, int outputAddress, int outputValue) throws IOException;
+ boolean writeSingleRegister(int slaveId, int registerAddress, int registerValue) throws IOException;
+ boolean writeMultipleCoils(int slaveId, int startingAddress, int quantityOfOutputs, int byteCount, byte[] outputsValue) throws IOException;
+ boolean writeMultipleRegisters(int slaveId, int startingAddress, int quantityOfRegisters, int byteCount, byte[] registersValue) throws IOException;
+
+// byte[] readFileRecord();
+// void writeFileRecord();
+ boolean maskWriteRegister(int slaveId, int referenceAddress, int andMask, int orMask) throws IOException;
+ boolean readWriteMultipleRegisters(int slaveId, int readStartingAddress, int quantityToRead, int writeStartingAddress, int quantityToWrite, byte[] values) throws IOException;
+ boolean readFifoQueue(int slaveId, int fifoPointerAddress) throws IOException;
+ boolean encapsulatedInterfaceTransport(int slaveId, int meiType, byte[] meiTypeSpecificData) throws IOException;
+ boolean readDeviceIdentification(int slaveId, int readDeviceIdCode, int objectId) throws IOException;
+}
diff --git a/src/main/java/cn/dst/modbus/specification/ModbusCrc.java b/src/main/java/cn/dst/modbus/specification/ModbusCrc.java
new file mode 100644
index 0000000000000000000000000000000000000000..068f2b3a99befbfff57aaf252b74ac53f2bc7251
--- /dev/null
+++ b/src/main/java/cn/dst/modbus/specification/ModbusCrc.java
@@ -0,0 +1,63 @@
+package cn.dst.modbus.specification;
+
+import java.util.logging.Logger;
+
+/**
+ * modbus crc 算法:
+ * 1) 预置 1 个 16 位的寄存器为十六进制 FFFF(即全为 1) , 称此寄存器为 CRC 寄存器。
+ * 2) 把第一个 8 位二进制数据 (通信信息帧的第一个字节) 与 16 位的 CRC 寄存器的低 8 位相异或, 把结果放于 CRC 寄存器。
+ * 3) 把 CRC 寄存器的内容右移一位 (朝低位) 用 0 填补最高位, 并检查右移后的移出位。
+ * 4) 如果移出位为 0, 重复第 3 步 (再次右移一位); 如果移出位为 1, CRC 寄存器与多项式 A001 ( 1010 0000 0000 0001) 进行异或。
+ * 5) 重复步骤 3 和步骤 4, 直到右移 8 次, 这样整个 8 位数据全部进行了处理。
+ * 6) 重复步骤 2 到步骤 5, 进行通信信息帧下一个字节的处理。
+ * 7) 将该通信信息帧所有字节按上述步骤计算完成后, 得到的 16 位 CRC 寄存器的高、低字节进行交换。
+ * 8) 最后得到的 CRC 寄存器内容即为 CRC 码。
+ *
+ * @author dongchangsong
+ * @since 2021/2/26 11:21
+ **/
+public final class ModbusCrc {
+
+ static Logger logger = Logger.getLogger(ModbusCrc.class.getName());
+
+ private static final int WIDTH = 16;
+ private static final int POLY = 0xA001;
+ private static final int INIT = 0x0000FFFF;
+ private static final int XOROUT = 0x0000;
+ private static final boolean BEFIN = true;
+ private static final boolean BEFOUT = true;
+
+ private static int crcValue = INIT;
+
+ public static void xxx(byte data) {
+ if (BEFIN) {
+ data ^= 0x00;
+ }
+
+ crcValue ^= ((int) data & 0xFF);
+ for (int i = 0; i < 8; i++) {
+ if ((crcValue & 0x0001) == 1) {
+ crcValue >>= 1;
+ crcValue ^= POLY;
+ } else {
+ crcValue >>= 1;
+ }
+ }
+
+ if (BEFOUT) {
+ crcValue ^= 0x00;
+ }
+
+ crcValue ^= XOROUT;
+ }
+
+ public static byte[] yyy(byte[] data) {
+ for (byte item : data) {
+ xxx(item);
+ }
+ String log = String.format("%X", crcValue);
+ logger.info(log);
+
+ return new byte[]{(byte) crcValue, (byte) (crcValue >> 8)};
+ }
+}
diff --git a/src/main/java/cn/dst/modbus/specification/Pdu.java b/src/main/java/cn/dst/modbus/specification/Pdu.java
new file mode 100644
index 0000000000000000000000000000000000000000..5201135317f0db8a0e804af4544c252663950851
--- /dev/null
+++ b/src/main/java/cn/dst/modbus/specification/Pdu.java
@@ -0,0 +1,106 @@
+package cn.dst.modbus.specification;
+
+import java.nio.ByteBuffer;
+
+/**
+ * @author dongchangsong
+ * @since 2021/3/9 15:55
+ **/
+public final class Pdu {
+
+ public static byte[] generatePdu(int functionCode, byte[] values, int... params) {
+ ByteBuffer byteBuffer = ByteBuffer.allocate(64);
+
+ /**
+ * 功能码
+ */
+ byteBuffer.put((byte) functionCode);
+
+ PublicFunctionCode publicFunctionCode = PublicFunctionCode.getPublicFunctionCode(functionCode);
+
+ switch (publicFunctionCode) {
+ /**
+ * two bytes of Starting/Output Address,
+ * two bytes of 'Quantity of coils/inputs/registers'/'Output Value'.
+ */
+ case READ_COILS:
+ case READ_DISCRETE_INPUTS:
+ case READ_HOLDING_REGISTERS:
+ case READ_INPUT_REGISTERS:
+ case WRITE_SINGLE_COIL:
+ case WRITE_SINGLE_REGISTER:
+ for (int i = 0; i < 2; i++) {
+ byteBuffer.put((byte) (params[i] >> 8));
+ byteBuffer.put((byte) params[i]);
+ }
+ break;
+ /**
+ * two bytes of Starting Address,
+ * two bytes of Quantity of Outputs,
+ * one byte of Byte Count,
+ * Outputs Values.
+ */
+ case WRITE_MULTIPLE_COILS:
+ case WRITE_MULTIPLE_REGISTERS:
+ for (int i = 0; i < 2; i++) {
+ byteBuffer.put((byte) (params[i] >> 8));
+ byteBuffer.put((byte) params[i]);
+ }
+ byteBuffer.put((byte) values.length);
+ byteBuffer.put(values);
+ break;
+ /**
+ * two bytes of Read Starting Address,
+ * two bytes of Quantity to Read,
+ * two bytes of Write Starting Address,
+ * two bytes of Quantity to Write,
+ * one byte of Write Byte Count,
+ * Write Registers Value.
+ */
+ case READ_WRITE_MULTIPLE_REGISTERS:
+ for (int i = 0; i < 4; i++) {
+ byteBuffer.put((byte) (params[i] >> 8));
+ byteBuffer.put((byte) params[i]);
+ }
+ byteBuffer.put((byte) values.length);
+ byteBuffer.put(values);
+ break;
+ /**
+ * two bytes of Reference Address,
+ * two bytes of And_Mask,
+ * two bytes of Or_Mask.
+ */
+ case MASK_WRITE_REGISTER:
+ for (int i = 0; i < 3; i++) {
+ byteBuffer.put((byte) (params[i] >> 8));
+ byteBuffer.put((byte) params[i]);
+ }
+ break;
+ case READ_FIFO_QUEUE:
+ byteBuffer.put((byte) (params[0] >> 8));
+ byteBuffer.put((byte) params[0]);
+ break;
+ case ENCAPSULATED_INTERFACE_TRANSPORT:
+ if (params[0] == PublicFunctionCode.SUB_CODE_CAN_OPEN_GENERAL_REFERENCE_REQUEST_AND_RESPONSE_PDU.getValue()) {
+ byteBuffer.put((byte) params[0]);
+ byteBuffer.put(values);
+ }
+ else if (params[0] == PublicFunctionCode.SUB_CODE_READ_DEVICE_IDENTIFICATION.getValue()){
+ byteBuffer.put((byte) params[0]);
+ byteBuffer.put((byte) params[1]);
+ byteBuffer.put((byte) params[2]);
+ }
+ break;
+ default:
+ // TODO:
+ break;
+ }
+ byteBuffer.flip();
+ byte[] result = new byte[byteBuffer.limit()];
+ byteBuffer.get(result);
+ return result;
+ }
+
+ private Pdu() {
+ }
+}
diff --git a/src/main/java/cn/dst/modbus/specification/PublicFunctionCode.java b/src/main/java/cn/dst/modbus/specification/PublicFunctionCode.java
new file mode 100644
index 0000000000000000000000000000000000000000..2dcb046a7e9829772a2e6bca43532ed8e92243b7
--- /dev/null
+++ b/src/main/java/cn/dst/modbus/specification/PublicFunctionCode.java
@@ -0,0 +1,72 @@
+package cn.dst.modbus.specification;
+
+/**
+ * There are there categories of MODBUS Functions codes. They are Public Function Codes,
+ * User-Defined Function Codes, Reserved Function Codes.
+ *
+ * @author dongchangsong
+ * @since 2021/2/20 15:52
+ **/
+public enum PublicFunctionCode {
+
+ ILLEGAL_FUNCTION_CODE(-1),
+
+ /**
+ * The function codes(0x01 and 0x02) is used to read from 1 to 2000 contiguous status of coils in a remote device.
+ *
+ * why 2000?
+ * analysis: modbus PDU is 253 bytes
+ * 253 - 1(byte count) - 1(function code) = 251 bytes;
+ * 251 * 8 = 2008
+ * The theoretical value could be 2008 if you calculate it.
+ * so: It is the the specification stipulation of the protocol.
+ */
+ READ_COILS(0x01),
+ READ_DISCRETE_INPUTS(0x02),
+
+ /**
+ * the max quantity of registers to be read is 125.
+ */
+ READ_HOLDING_REGISTERS(0x03),
+ READ_INPUT_REGISTERS(0x04),
+ WRITE_SINGLE_COIL(0x05),
+ WRITE_SINGLE_REGISTER(0x06),
+ WRITE_MULTIPLE_COILS(0x0F),
+ WRITE_MULTIPLE_REGISTERS(0x10),
+
+ READ_FILE_RECORD(0x14),
+ WRITE_FILE_RECORD(0x15),
+ MASK_WRITE_REGISTER(0x16),
+ READ_WRITE_MULTIPLE_REGISTERS(0x17),
+ READ_FIFO_QUEUE(0x18),
+
+ ENCAPSULATED_INTERFACE_TRANSPORT(0x2B),
+ SUB_CODE_CAN_OPEN_GENERAL_REFERENCE_REQUEST_AND_RESPONSE_PDU(0x0D),
+ SUB_CODE_READ_DEVICE_IDENTIFICATION(0x0E),
+
+ READ_EXCEPTION_STATUS(0x07),
+ DIAGNOSTICS(0x08),
+ GET_COMM_EVENT_COUNTER(0x0B),
+ GET_COMM_EVENT_LOG(0x0C),
+ REPORT_SERVER_ID(0x11);
+
+ int value;
+
+ PublicFunctionCode(int value) {
+ this.value = value;
+ }
+
+ public int getValue() {
+ return value;
+ }
+
+ public static PublicFunctionCode getPublicFunctionCode(int value) {
+ for (PublicFunctionCode code : PublicFunctionCode.values()) {
+ if (code.value == value) {
+ return code;
+ }
+ }
+ return ILLEGAL_FUNCTION_CODE;
+ }
+
+}
diff --git a/src/main/java/cn/dst/modbus/specification/Utils.java b/src/main/java/cn/dst/modbus/specification/Utils.java
new file mode 100644
index 0000000000000000000000000000000000000000..5e30305a9599fbc58e041cb38828093643d6e1f7
--- /dev/null
+++ b/src/main/java/cn/dst/modbus/specification/Utils.java
@@ -0,0 +1,24 @@
+package cn.dst.modbus.specification;
+
+/**
+ * @author dongchangsong
+ * @since 2021/2/20 15:52
+ **/
+public class Utils {
+
+ public static final int ON = 0xff00;
+ public static final int OFF = 0x0000;
+
+ public static final int READ_COILS_QUANTITY_MAX = 2000;
+ public static final int READ_REGISTER_QUANTITY_MAX = 125;
+ public static final int WRITE_MULTIPLE_COILS_QUANTITY_MAX = 0x07B0;
+ public static final int WRITE_MULTIPLE_REGISTER_QUANTITY_MAX = 0x7B;
+
+ public static final int DEFAULT_PORT = 502;
+
+ public static final int SERIAL = 0;
+ public static final int TCP_IP = 1;
+
+ private Utils() {
+ }
+}
diff --git a/src/test/java/cn/dst/modbus/SerialTest.java b/src/test/java/cn/dst/modbus/SerialTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..04a7cb97d79df925c448cce10afd0d7f0e1b28bf
--- /dev/null
+++ b/src/test/java/cn/dst/modbus/SerialTest.java
@@ -0,0 +1,37 @@
+package cn.dst.modbus;
+
+import cn.dst.modbus.core.Encodable;
+import cn.dst.modbus.core.ModbusFactory;
+import cn.dst.modbus.specification.IModbus;
+import cn.dst.modbus.specification.ModbusCrc;
+import junit.framework.TestCase;
+
+import java.io.IOException;
+
+/**
+ * @author dongchangsong
+ * @since 2021/2/25 17:36
+ **/
+public class SerialTest extends TestCase {
+
+ private IModbus modbus;
+
+ public SerialTest() throws IOException {
+ modbus = ModbusFactory.createSerialMaster("COM2", 9600);
+ }
+
+ public void testReadCoils() throws Exception {
+ Encodable[] result = modbus.readCoils(1, 0, 2);
+ for (Encodable value : result){
+ System.out.println(value.parseBoolean());
+ }
+ assertNotNull(result);
+ }
+
+ public void testModbusCrc(){
+ ModbusCrc crc = new ModbusCrc();
+ byte[] data = new byte[]{0x01, 0x01, 0x00, 0x00, 0x00, (byte) 0x80};
+ byte[] value = crc.yyy(data);
+ assertNotNull(value);
+ }
+}
diff --git a/src/test/java/cn/dst/modbus/TcpTest.java b/src/test/java/cn/dst/modbus/TcpTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..0685217d171f6d88a51d16eb162114e1b9c86aa0
--- /dev/null
+++ b/src/test/java/cn/dst/modbus/TcpTest.java
@@ -0,0 +1,182 @@
+package cn.dst.modbus;
+
+import cn.dst.modbus.core.Encodable;
+import cn.dst.modbus.core.ModbusFactory;
+import cn.dst.modbus.specification.DataType;
+import cn.dst.modbus.specification.IModbus;
+import cn.dst.modbus.specification.PublicFunctionCode;
+import cn.dst.modbus.specification.Utils;
+import junit.framework.TestCase;
+
+import java.io.IOException;
+
+/**
+ * Unit test for simple App.
+ */
+public class TcpTest extends TestCase {
+
+ private IModbus modbus;
+
+ /**
+ * Create the test case
+ *
+ * @param testName name of the test case
+ */
+ public TcpTest(String testName) throws IOException {
+ super(testName);
+ modbus = ModbusFactory.createTcpMaster("127.0.0.1");
+ }
+
+ public void testReadCoils() {
+ Encodable[] result = new Encodable[0];
+ try {
+ result = modbus.readCoils(1, 0, 3);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ for (Encodable item : result) {
+ System.out.println(item.parseBoolean());
+ }
+ assertNotNull(result);
+ }
+
+ public void testReadDiscreteInputs() {
+ Encodable[] result = new Encodable[0];
+ try {
+ result = modbus.readDiscreteInputs(1, 0, 2001);
+ } catch (Exception e) {
+ // e.printStackTrace();
+ System.out.println("exception:" + e.getMessage());
+ }
+ for (Encodable item : result) {
+ System.out.println(item.parseBoolean());
+ }
+ assertNotNull(result);
+ }
+
+ public void testReadHoldingRegisters() {
+ Encodable[] result = new Encodable[0];
+ try {
+ result = modbus.readHoldingRegisters(1, 0, 1, DataType.TWO_BYTE_INT_SIGNED);
+ } catch (Exception e) {
+ // e.printStackTrace();
+ System.out.println("exception:" + e.getMessage());
+ }
+
+ for (Encodable item : result) {
+ System.out.println(item.parseInteger());
+ }
+ assertNotNull(result);
+ }
+
+ public void testReadInputRegisters() {
+ Encodable[] result = new Encodable[0];
+ try {
+ result = modbus.readInputRegisters(1, 0, 3, DataType.TWO_BYTE_INT_SIGNED);
+ } catch (Exception e) {
+ // e.printStackTrace();
+ System.out.println("exception:" + e.getMessage());
+ }
+
+ for (Encodable item : result) {
+ System.out.println(item.parseInteger());
+ }
+ assertNotNull(result);
+ }
+
+ public void testWriteSingleCoil() {
+ boolean result = false;
+ try {
+ result = modbus.writeSingleCoil(1, 0, Utils.OFF);
+ } catch (Exception e) {
+ // e.printStackTrace();
+ System.out.println("exception:" + e.getMessage());
+ }
+ assertTrue(result);
+ }
+
+ public void testWriteSingleRegister() {
+ boolean result = false;
+ try {
+ result = modbus.writeSingleRegister(1, 0, 0xffff);
+ } catch (Exception e) {
+ // e.printStackTrace();
+ System.out.println("exception:" + e.getMessage());
+ }
+ assertTrue(result);
+ }
+
+ public void testWriteMultipleCoils() {
+ boolean result = false;
+ try {
+ result = modbus.writeMultipleCoils(1, 0, 3, 1, new byte[]{7});
+ } catch (Exception e) {
+ // e.printStackTrace();
+ System.out.println("exception:" + e.getMessage());
+ }
+ assertTrue(result);
+ }
+
+ public void testWriteMultipleRegisters() {
+ boolean result = false;
+ try {
+ result = modbus.writeMultipleRegisters(1, 0, 3, 6, new byte[]{0x1, 0x2, 0x0, 0x1, 0x1, 0x1});
+ } catch (Exception e) {
+ // e.printStackTrace();
+ System.out.println("exception:" + e.getMessage());
+ }
+ assertTrue(result);
+ }
+
+ public void testMaskWriteRegister() {
+ boolean result = false;
+ try {
+ result = modbus.maskWriteRegister(1, 0, 1, 2);
+ } catch (Exception e) {
+ // e.printStackTrace();
+ System.out.println("exception:" + e.getMessage());
+ }
+ assertTrue(result);
+ }
+
+ public void testReadWriteMultipleRegisters() {
+ boolean result = false;
+ try {
+ result = modbus.readWriteMultipleRegisters(1, 0, 1, 0, 2, new byte[]{0x01, 0x01, 0x01, 0x01});
+ } catch (Exception e) {
+ // e.printStackTrace();
+ System.out.println("exception:" + e.getMessage());
+ }
+ assertTrue(result);
+ }
+
+ public void testReadFifoQueue() {
+ boolean result = false;
+ try {
+ result = modbus.readFifoQueue(1, 1);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ assertTrue(result);
+ }
+
+ public void testEncapsulatedInterfaceTransport() {
+ boolean result = false;
+ try {
+ result = modbus.encapsulatedInterfaceTransport(1, PublicFunctionCode.SUB_CODE_CAN_OPEN_GENERAL_REFERENCE_REQUEST_AND_RESPONSE_PDU.getValue(), new byte[]{0x1, 0x1, 0x1});
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ assertTrue(result);
+ }
+
+ public void testReadDeviceIdentification() {
+ boolean result = false;
+ try {
+ result = modbus.readDeviceIdentification(1, 1, 0);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ assertTrue(result);
+ }
+}