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); + } +}