diff --git a/src/main/java/me/retrodaredevil/io/modbus/AsciiDataEncoder.java b/src/main/java/me/retrodaredevil/io/modbus/AsciiDataEncoder.java index 1378f64..e16b57f 100644 --- a/src/main/java/me/retrodaredevil/io/modbus/AsciiDataEncoder.java +++ b/src/main/java/me/retrodaredevil/io/modbus/AsciiDataEncoder.java @@ -27,7 +27,7 @@ public void sendMessage(OutputStream outputStream, int address, ModbusMessage me public static char[] toAscii(int address, ModbusMessage message){ byte code = message.getByteFunctionCode(); int[] data = message.getData(); - int lrc = RedundancyUtil.calculateLRC(data); + int lrc = RedundancyUtil.calculateLrc(data); int length = 9 + data.length * 2; char[] chars = new char[length]; @@ -87,7 +87,7 @@ public static ModbusMessage fromAscii(int expectedAddress, byte[] bytes){ if(address != expectedAddress){ throw new UnexpectedSlaveResponseException(expectedAddress, address); } - int actualLrc = RedundancyUtil.calculateLRC(data); + int actualLrc = RedundancyUtil.calculateLrc(data); if(expectedLrc != actualLrc){ throw new RedundancyException("LRC", expectedLrc, actualLrc, "bytes: " + Arrays.toString(bytes)); } diff --git a/src/main/java/me/retrodaredevil/io/modbus/ExceptionCode.java b/src/main/java/me/retrodaredevil/io/modbus/ExceptionCode.java new file mode 100644 index 0000000..709dcbd --- /dev/null +++ b/src/main/java/me/retrodaredevil/io/modbus/ExceptionCode.java @@ -0,0 +1,18 @@ +package me.retrodaredevil.io.modbus; + +public final class ExceptionCode { + private ExceptionCode() { throw new UnsupportedOperationException(); } + // http://www.simplymodbus.ca/exceptions.htm + + public static int ILLEGAL_FUNCTION = 1; + public static int ILLEGAL_DATA_ACCESS = 2; + public static int ILLEGAL_DATA_VALUE = 3; + public static int SLAVE_DEVICE_FAILURE = 4; + public static int ACKNOWLEDGE = 5; + public static int SLAVE_DEVICE_BUSY = 6; + public static int NEGATIVE_ACKNOWLEDGE = 7; + public static int MEMORY_PARITY_ERROR = 8; + // skip 9 for some reason + public static int GATEWAY_PATH_UNAVAILABLE = 10; + public static int GATEWAY_TARGET_DEVICE_FAILED_TO_RESPOND = 11; +} diff --git a/src/main/java/me/retrodaredevil/io/modbus/IOModbusSlaveBus.java b/src/main/java/me/retrodaredevil/io/modbus/IOModbusSlaveBus.java index cd5e5e5..4e685e5 100644 --- a/src/main/java/me/retrodaredevil/io/modbus/IOModbusSlaveBus.java +++ b/src/main/java/me/retrodaredevil/io/modbus/IOModbusSlaveBus.java @@ -21,7 +21,7 @@ public IOModbusSlaveBus(InputStream inputStream, OutputStream outputStream, IODa } @Override - public ModbusMessage sendMessage(int address, ModbusMessage message) { + public ModbusMessage sendRequestMessage(int address, ModbusMessage message) { formatter.sendMessage(outputStream, address, message); return formatter.readMessage(address, inputStream); } diff --git a/src/main/java/me/retrodaredevil/io/modbus/ImmutableAddressModbusSlave.java b/src/main/java/me/retrodaredevil/io/modbus/ImmutableAddressModbusSlave.java index 5ec197b..cedd54c 100644 --- a/src/main/java/me/retrodaredevil/io/modbus/ImmutableAddressModbusSlave.java +++ b/src/main/java/me/retrodaredevil/io/modbus/ImmutableAddressModbusSlave.java @@ -10,8 +10,8 @@ public ImmutableAddressModbusSlave(int address, ModbusSlaveBus modbus) { } @Override - public ModbusMessage sendMessage(ModbusMessage message) { - return modbus.sendMessage(address, message); + public ModbusMessage sendRequestMessage(ModbusMessage message) { + return modbus.sendRequestMessage(address, message); } } diff --git a/src/main/java/me/retrodaredevil/io/modbus/ModbusMessages.java b/src/main/java/me/retrodaredevil/io/modbus/ModbusMessages.java index 3a1c728..4873222 100644 --- a/src/main/java/me/retrodaredevil/io/modbus/ModbusMessages.java +++ b/src/main/java/me/retrodaredevil/io/modbus/ModbusMessages.java @@ -5,7 +5,14 @@ public final class ModbusMessages { private ModbusMessages(){ throw new UnsupportedOperationException(); } - + + public static boolean isFunctionCodeError(int functionCode) { + return (functionCode & 0x80) != 0; // the 8th bit is set + } + public static boolean isFunctionCodeError(byte functionCode) { + return (functionCode & 0x80) != 0; // the 8th bit is set + } + public static ModbusMessage createMessage(byte functionCode, byte[] byteData){ int length = byteData.length; int[] data = new int[length]; @@ -14,6 +21,12 @@ public static ModbusMessage createMessage(byte functionCode, byte[] byteData){ } return new DefaultModbusMessage(functionCode, data, byteData); } + + /** + * @param functionCode The function code + * @param data The array with 8 bit data + * @return A {@link ModbusMessage} with the specified function code and data + */ public static ModbusMessage createMessage(int functionCode, int[] data){ int length = data.length; byte[] byteData = new byte[length]; @@ -22,7 +35,15 @@ public static ModbusMessage createMessage(int functionCode, int[] data){ } return new DefaultModbusMessage((byte) functionCode, data, byteData); } - + + public static ModbusMessage createExceptionMessage(int functionCode, int exceptionCode) { + return createExceptionMessage((byte) functionCode, (byte) exceptionCode); + } + + public static ModbusMessage createExceptionMessage(byte functionCode, byte exceptionCode) { + return createMessage((byte) (functionCode | 0x80), new byte[] { exceptionCode }); + } + /** * NOTE: Do not call this for the CRC! * @param data16Bit An array of ints where each element represents a 16 bit number. @@ -41,7 +62,7 @@ public static int[] get8BitDataFrom16BitArray(int... data16Bit){ } return r; } - public static int[] get16BitDataFrom8BitArray(int ...data8Bit){ + public static int[] get16BitDataFrom8BitArray(int... data8Bit){ int originalLength = data8Bit.length; if(originalLength % 2 == 1){ throw new IllegalArgumentException("The length must be a multiple of two! length: " + originalLength); @@ -91,10 +112,10 @@ public boolean equals(Object o) { @Override public String toString() { - return "DefaultModbusMessage{" + + return "DefaultModbusMessage(" + "functionCode=" + getFunctionCode() + ", data=" + Arrays.toString(data) + - '}'; + ')'; } @Override diff --git a/src/main/java/me/retrodaredevil/io/modbus/ModbusSlave.java b/src/main/java/me/retrodaredevil/io/modbus/ModbusSlave.java index dd34a67..197b106 100644 --- a/src/main/java/me/retrodaredevil/io/modbus/ModbusSlave.java +++ b/src/main/java/me/retrodaredevil/io/modbus/ModbusSlave.java @@ -11,10 +11,10 @@ public interface ModbusSlave { * @param message * @return */ - ModbusMessage sendMessage(ModbusMessage message); + ModbusMessage sendRequestMessage(ModbusMessage message); - default T sendMessage(MessageHandler messageHandler){ - ModbusMessage response = sendMessage(messageHandler.createMessage()); + default T sendRequestMessage(MessageHandler messageHandler){ + ModbusMessage response = sendRequestMessage(messageHandler.createRequest()); return messageHandler.handleResponse(response); } } diff --git a/src/main/java/me/retrodaredevil/io/modbus/ModbusSlaveBus.java b/src/main/java/me/retrodaredevil/io/modbus/ModbusSlaveBus.java index f4ca632..5c912f4 100644 --- a/src/main/java/me/retrodaredevil/io/modbus/ModbusSlaveBus.java +++ b/src/main/java/me/retrodaredevil/io/modbus/ModbusSlaveBus.java @@ -9,14 +9,14 @@ public interface ModbusSlaveBus { /** * Sends a message to the specified slave - * @param address - * @param message - * @return + * @param address The address of the slave + * @param message The message to send to the slave + * @return The response from the slave */ - ModbusMessage sendMessage(int address, ModbusMessage message); + ModbusMessage sendRequestMessage(int address, ModbusMessage message); - default T sendMessage(int address, MessageHandler messageHandler){ - ModbusMessage response = sendMessage(address, messageHandler.createMessage()); + default T sendRequestMessage(int address, MessageHandler messageHandler){ + ModbusMessage response = sendRequestMessage(address, messageHandler.createRequest()); return messageHandler.handleResponse(response); } } diff --git a/src/main/java/me/retrodaredevil/io/modbus/RTUDataEncoder.java b/src/main/java/me/retrodaredevil/io/modbus/RTUDataEncoder.java index 4166452..90f21d0 100644 --- a/src/main/java/me/retrodaredevil/io/modbus/RTUDataEncoder.java +++ b/src/main/java/me/retrodaredevil/io/modbus/RTUDataEncoder.java @@ -37,7 +37,7 @@ public void sendMessage(OutputStream outputStream, int address, ModbusMessage me public static byte[] toBytes(int address, ModbusMessage message){ byte code = message.getByteFunctionCode(); byte[] data = message.getByteData(); - int crc = RedundancyUtil.calculateCRC(getCrcBytes((byte) address, code, data)); + int crc = RedundancyUtil.calculateCrc(getCrcBytes((byte) address, code, data)); byte highCrc = (byte) ((crc & 0xFF00) >> 8); byte lowCrc = (byte) (crc & 0xFF); byte[] bytes = new byte[4 + data.length]; @@ -72,7 +72,7 @@ public static ModbusMessage fromBytes(int expectedAddress, byte[] bytes){ throw new UnexpectedSlaveResponseException(expectedAddress, address); } int expectedCrc = 0xFFFF & ((highCrc << 8) | lowCrc); - int actualCrc = RedundancyUtil.calculateCRC(getCrcBytes(address, code, data)); + int actualCrc = RedundancyUtil.calculateCrc(getCrcBytes(address, code, data)); if(expectedCrc != actualCrc){ throw new RedundancyException("CRC", expectedCrc, actualCrc, "bytes: " + Arrays.toString(bytes)); } diff --git a/src/main/java/me/retrodaredevil/io/modbus/RedundancyUtil.java b/src/main/java/me/retrodaredevil/io/modbus/RedundancyUtil.java index 727df1b..c0f3436 100644 --- a/src/main/java/me/retrodaredevil/io/modbus/RedundancyUtil.java +++ b/src/main/java/me/retrodaredevil/io/modbus/RedundancyUtil.java @@ -3,14 +3,14 @@ public final class RedundancyUtil { private RedundancyUtil(){ throw new UnsupportedOperationException(); } - public static int calculateLRC(int[] bytes){ + public static int calculateLrc(int[] bytes){ int sum = 0; for(int a : bytes){ sum += a; } return 0xFF & (-((byte) (sum & 0xFF))); } - public static int calculateCRC(int... bytes){ + public static int calculateCrc(int... bytes){ int crc = 0xFFFF; for(int b : bytes){ crc ^= b & 0xFF; @@ -25,7 +25,7 @@ public static int calculateCRC(int... bytes){ } return crc; } - public static int calculateCRC(byte... bytes){ + public static int calculateCrc(byte... bytes){ int crc = 0xFFFF; for(int b : bytes){ crc ^= b & 0xFF; @@ -41,7 +41,7 @@ public static int calculateCRC(byte... bytes){ return crc; } @Deprecated - public static int flipCRC(int crc){ + public static int flipCrc(int crc){ int high = (crc & (0xFF << 8)) >> 8; int low = crc & 0xFF; return (low << 8) | high; diff --git a/src/main/java/me/retrodaredevil/io/modbus/handling/MessageHandler.java b/src/main/java/me/retrodaredevil/io/modbus/handling/MessageHandler.java index e1e3e72..4255701 100644 --- a/src/main/java/me/retrodaredevil/io/modbus/handling/MessageHandler.java +++ b/src/main/java/me/retrodaredevil/io/modbus/handling/MessageHandler.java @@ -3,6 +3,6 @@ import me.retrodaredevil.io.modbus.ModbusMessage; public interface MessageHandler { - ModbusMessage createMessage(); + ModbusMessage createRequest(); T handleResponse(ModbusMessage response); } diff --git a/src/main/java/me/retrodaredevil/io/modbus/handling/MessageResponseCreator.java b/src/main/java/me/retrodaredevil/io/modbus/handling/MessageResponseCreator.java new file mode 100644 index 0000000..e4fba16 --- /dev/null +++ b/src/main/java/me/retrodaredevil/io/modbus/handling/MessageResponseCreator.java @@ -0,0 +1,12 @@ +package me.retrodaredevil.io.modbus.handling; + +import me.retrodaredevil.io.modbus.ModbusMessage; + +public interface MessageResponseCreator extends MessageHandler { + /** + * Creates a response message with the specified data + * @param data The data to put in the returned response message + * @return A response {@link ModbusMessage} with the specified data + */ + ModbusMessage createResponse(T data); +} diff --git a/src/main/java/me/retrodaredevil/io/modbus/handling/MultipleWriteHandler.java b/src/main/java/me/retrodaredevil/io/modbus/handling/MultipleWriteHandler.java index aca5490..610a7af 100644 --- a/src/main/java/me/retrodaredevil/io/modbus/handling/MultipleWriteHandler.java +++ b/src/main/java/me/retrodaredevil/io/modbus/handling/MultipleWriteHandler.java @@ -3,11 +3,17 @@ import me.retrodaredevil.io.modbus.FunctionCode; import me.retrodaredevil.io.modbus.ModbusMessage; import me.retrodaredevil.io.modbus.ModbusMessages; +import me.retrodaredevil.io.modbus.parsing.MessageParseException; -public class MultipleWriteHandler implements MessageHandler { +import java.util.Arrays; + +import static me.retrodaredevil.io.modbus.ModbusMessages.get16BitDataFrom8BitArray; + +public class MultipleWriteHandler implements MessageResponseCreator { private final int register; private final int[] data8Bit; private final boolean checkRegister; + @Deprecated public MultipleWriteHandler(int register, int[] data8Bit, boolean checkRegister){ this.register = register; this.data8Bit = data8Bit; @@ -19,8 +25,48 @@ public MultipleWriteHandler(int register, int[] data8Bit, boolean checkRegister) public MultipleWriteHandler(int register, int[] data8Bit){ this(register, data8Bit, true); } + public static MultipleWriteHandler parseFromRequestData(int[] data) throws MessageParseException { + if (data.length % 2 != 1) { // the array's length is not odd // if it is even + throw new MessageParseException("data.length is even! It must be odd! data.length=" + data.length); + } + if (data.length <= 5) { + throw new MessageParseException("data.length must be greater than 5 and must be odd! So must be >= 7. data.length=" + data.length); + } + int register = data[0] << 8 | data[1]; + int numberOfRegisters = data[2] << 8 | data[3]; + int numberOfBytes = data[4]; + if (numberOfBytes != numberOfRegisters * 2) { + throw new MessageParseException("numberOfBytes=" + numberOfBytes + " and numberOfRegisters=" + numberOfRegisters + "! numberOfBytes must equal numberOfRegisters * 2!"); + } + if (data.length - 5 != numberOfBytes) { + throw new MessageParseException("data.length - 5 must equal numberOfBytes! data.length=" + data.length + " numberOfBytes=" + numberOfBytes); + } + int[] data8Bit = Arrays.copyOfRange(data, 5, data.length); + return new MultipleWriteHandler(register, data8Bit); + } + + /** + * @return The register to write to + */ + public int getRegister() { + return register; + } + + /** + * @return The 8 bit data array of values to write. The length is always a multiple of 2 + */ + public int[] getData8Bit() { + return data8Bit; + } + /** + * @return The 16 bit data array of values to write. + */ + public int[] getData16Bit() { + return get16BitDataFrom8BitArray(data8Bit); + } + @Override - public ModbusMessage createMessage() { + public ModbusMessage createRequest() { int[] data = new int[5 + data8Bit.length]; data[0] = (register & 0xFF00) >> 8; data[1] = register & 0xFF; @@ -52,4 +98,14 @@ public Void handleResponse(ModbusMessage response) { } return null; } + + @Override + public ModbusMessage createResponse(Void data) { + int numberOfRegisters = data8Bit.length / 2; + int[] data8Bit = new int[] { + register >> 8, register & 0xFF, + numberOfRegisters >> 8, numberOfRegisters & 0xFF + }; + return ModbusMessages.createMessage(FunctionCode.WRITE_MULTIPLE_REGISTERS, data8Bit); + } } diff --git a/src/main/java/me/retrodaredevil/io/modbus/handling/ReadRegistersHandler.java b/src/main/java/me/retrodaredevil/io/modbus/handling/ReadRegistersHandler.java index e528046..91e4853 100644 --- a/src/main/java/me/retrodaredevil/io/modbus/handling/ReadRegistersHandler.java +++ b/src/main/java/me/retrodaredevil/io/modbus/handling/ReadRegistersHandler.java @@ -3,10 +3,12 @@ import me.retrodaredevil.io.modbus.FunctionCode; import me.retrodaredevil.io.modbus.ModbusMessage; import me.retrodaredevil.io.modbus.ModbusMessages; +import me.retrodaredevil.io.modbus.parsing.MessageParseException; import static me.retrodaredevil.io.modbus.ModbusMessages.get16BitDataFrom8BitArray; +import static me.retrodaredevil.io.modbus.ModbusMessages.get8BitDataFrom16BitArray; -public class ReadRegistersHandler implements MessageHandler { +public class ReadRegistersHandler implements MessageResponseCreator { private final int register; private final int numberOfRegisters; @@ -20,9 +22,25 @@ public ReadRegistersHandler(int register, int numberOfRegisters) { this.register = register; this.numberOfRegisters = numberOfRegisters; } - + public static ReadRegistersHandler parseFromRequestData(int[] data) throws MessageParseException { + if (data.length != 4) { + throw new MessageParseException("data.length != 4! data.length=" + data.length); + } + return new ReadRegistersHandler( + data[0] << 8 | data[1], + data[2] << 8 | data[3] + ); + } + + public int getRegister() { + return register; + } + public int getNumberOfRegisters() { + return numberOfRegisters; + } + @Override - public ModbusMessage createMessage() { + public ModbusMessage createRequest() { return ModbusMessages.createMessage(FunctionCode.READ_REGISTERS, new int[] { (register & 0xFF00) >> 8, register & 0xFF, (numberOfRegisters & 0xFF00) >> 8, numberOfRegisters & 0xFF @@ -47,4 +65,20 @@ public int[] handleResponse(ModbusMessage response) { System.arraycopy(allData, 1, data, 0, data.length); return get16BitDataFrom8BitArray(data); } + + @Override + public ModbusMessage createResponse(int[] data16Bit) { + if (data16Bit.length != numberOfRegisters) { + throw new IllegalArgumentException("The array of 16 bit integers passed was not the correct length! numberOfRegisters=" + numberOfRegisters + ". The passed data should have that length."); + } + int[] data = get8BitDataFrom16BitArray(data16Bit); + int byteCount = numberOfRegisters * 2; + if (data.length != byteCount) { + throw new AssertionError("We just checked this. This is a code problem."); + } + int[] allData = new int[data.length + 1]; + allData[0] = byteCount; + System.arraycopy(data, 0, allData, 1, data.length); + return ModbusMessages.createMessage(FunctionCode.READ_REGISTERS, allData); + } } diff --git a/src/main/java/me/retrodaredevil/io/modbus/handling/SingleWriteHandler.java b/src/main/java/me/retrodaredevil/io/modbus/handling/SingleWriteHandler.java index ba4f387..dbe95f6 100644 --- a/src/main/java/me/retrodaredevil/io/modbus/handling/SingleWriteHandler.java +++ b/src/main/java/me/retrodaredevil/io/modbus/handling/SingleWriteHandler.java @@ -3,14 +3,18 @@ import me.retrodaredevil.io.modbus.FunctionCode; import me.retrodaredevil.io.modbus.ModbusMessage; import me.retrodaredevil.io.modbus.ModbusMessages; +import me.retrodaredevil.io.modbus.parsing.MessageParseException; -public class SingleWriteHandler implements MessageHandler { +import static me.retrodaredevil.io.modbus.ModbusMessages.get16BitDataFrom8BitArray; + +public class SingleWriteHandler implements MessageResponseCreator { // https://www.modbustools.com/modbus.html#function06 private final int register; private final int value; private final boolean checkRegister; - + + @Deprecated public SingleWriteHandler(int register, int value, boolean checkRegister) { this.register = register; this.value = value; @@ -19,9 +23,29 @@ public SingleWriteHandler(int register, int value, boolean checkRegister) { public SingleWriteHandler(int register, int value) { this(register, value, true); } - + public static SingleWriteHandler parseFromRequestData(int[] data) throws MessageParseException { + if (data.length != 4) { + throw new MessageParseException("data.length != 4! data.length=" + data.length); + } + int[] data16Bit = get16BitDataFrom8BitArray(data); + return new SingleWriteHandler(data16Bit[0], data16Bit[1]); + } + + /** + * @return The register to write to + */ + public int getRegister() { + return register; + } + /** + * @return The 16 bit value to write + */ + public int getValue() { + return value; + } + @Override - public ModbusMessage createMessage() { + public ModbusMessage createRequest() { return ModbusMessages.createMessage(FunctionCode.WRITE_SINGLE_REGISTER, ModbusMessages.get8BitDataFrom16BitArray(register, value)); } @@ -29,9 +53,9 @@ public ModbusMessage createMessage() { public Void handleResponse(ModbusMessage response) { int functionCode = response.getFunctionCode(); if(functionCode != FunctionCode.WRITE_SINGLE_REGISTER){ - throw new FunctionCodeException(6, functionCode); + throw new FunctionCodeException(FunctionCode.WRITE_SINGLE_REGISTER, functionCode); } - int[] data = ModbusMessages.get16BitDataFrom8BitArray(response.getData()); + int[] data = get16BitDataFrom8BitArray(response.getData()); if(data.length != 2){ throw new ResponseLengthException(2, data.length); } @@ -47,4 +71,13 @@ public Void handleResponse(ModbusMessage response) { } return null; } + + @Override + public ModbusMessage createResponse(Void data) { + int[] data16Bit = new int[] { + register, + value + }; + return ModbusMessages.createMessage(FunctionCode.WRITE_SINGLE_REGISTER, ModbusMessages.get8BitDataFrom16BitArray(data16Bit)); + } } diff --git a/src/main/java/me/retrodaredevil/io/modbus/parsing/DefaultMessageParser.java b/src/main/java/me/retrodaredevil/io/modbus/parsing/DefaultMessageParser.java new file mode 100644 index 0000000..296cb96 --- /dev/null +++ b/src/main/java/me/retrodaredevil/io/modbus/parsing/DefaultMessageParser.java @@ -0,0 +1,23 @@ +package me.retrodaredevil.io.modbus.parsing; + +import me.retrodaredevil.io.modbus.FunctionCode; +import me.retrodaredevil.io.modbus.ModbusMessage; +import me.retrodaredevil.io.modbus.handling.MessageHandler; +import me.retrodaredevil.io.modbus.handling.MultipleWriteHandler; +import me.retrodaredevil.io.modbus.handling.ReadRegistersHandler; +import me.retrodaredevil.io.modbus.handling.SingleWriteHandler; + +public class DefaultMessageParser implements MessageParser { + @Override + public MessageHandler parseRequestMessage(ModbusMessage message) throws MessageParseException { + switch(message.getFunctionCode()) { + case FunctionCode.READ_REGISTERS: + return ReadRegistersHandler.parseFromRequestData(message.getData()); + case FunctionCode.WRITE_SINGLE_REGISTER: + return SingleWriteHandler.parseFromRequestData(message.getData()); + case FunctionCode.WRITE_MULTIPLE_REGISTERS: + return MultipleWriteHandler.parseFromRequestData(message.getData()); + } + return null; + } +} diff --git a/src/main/java/me/retrodaredevil/io/modbus/parsing/MessageParseException.java b/src/main/java/me/retrodaredevil/io/modbus/parsing/MessageParseException.java new file mode 100644 index 0000000..2b25a64 --- /dev/null +++ b/src/main/java/me/retrodaredevil/io/modbus/parsing/MessageParseException.java @@ -0,0 +1,22 @@ +package me.retrodaredevil.io.modbus.parsing; + +public class MessageParseException extends Exception { + public MessageParseException() { + } + + public MessageParseException(String message) { + super(message); + } + + public MessageParseException(String message, Throwable cause) { + super(message, cause); + } + + public MessageParseException(Throwable cause) { + super(cause); + } + + public MessageParseException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } +} diff --git a/src/main/java/me/retrodaredevil/io/modbus/parsing/MessageParser.java b/src/main/java/me/retrodaredevil/io/modbus/parsing/MessageParser.java new file mode 100644 index 0000000..1a2b22b --- /dev/null +++ b/src/main/java/me/retrodaredevil/io/modbus/parsing/MessageParser.java @@ -0,0 +1,13 @@ +package me.retrodaredevil.io.modbus.parsing; + +import me.retrodaredevil.io.modbus.ModbusMessage; +import me.retrodaredevil.io.modbus.handling.MessageHandler; + +public interface MessageParser { + /** + * @param message The message from the master + * @return The {@link MessageHandler} if the function is supported, null otherwise + * @throws MessageParseException if the data in {@code message} is not valid. + */ + MessageHandler parseRequestMessage(ModbusMessage message) throws MessageParseException; +} diff --git a/src/main/java/me/retrodaredevil/io/modbus/parsing/MessageParserMultiplexer.java b/src/main/java/me/retrodaredevil/io/modbus/parsing/MessageParserMultiplexer.java new file mode 100644 index 0000000..a0c3ec9 --- /dev/null +++ b/src/main/java/me/retrodaredevil/io/modbus/parsing/MessageParserMultiplexer.java @@ -0,0 +1,28 @@ +package me.retrodaredevil.io.modbus.parsing; + +import me.retrodaredevil.io.modbus.ModbusMessage; +import me.retrodaredevil.io.modbus.handling.MessageHandler; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +public class MessageParserMultiplexer implements MessageParser { + private final List messageParsers; + + public MessageParserMultiplexer(Collection messageParsers) { + this.messageParsers = Collections.unmodifiableList(new ArrayList<>(messageParsers)); + } + + @Override + public MessageHandler parseRequestMessage(ModbusMessage message) throws MessageParseException { + for (MessageParser messageParser : messageParsers) { + MessageHandler messageHandler = messageParser.parseRequestMessage(message); + if (messageHandler != null) { + return messageHandler; + } + } + return null; + } +} diff --git a/src/main/java/me/retrodaredevil/io/modbus/parsing/package-info.java b/src/main/java/me/retrodaredevil/io/modbus/parsing/package-info.java new file mode 100644 index 0000000..081f08c --- /dev/null +++ b/src/main/java/me/retrodaredevil/io/modbus/parsing/package-info.java @@ -0,0 +1,4 @@ +/** + * Used to parse {@link me.retrodaredevil.io.modbus.ModbusMessage}s from masters. + */ +package me.retrodaredevil.io.modbus.parsing; \ No newline at end of file diff --git a/src/test/java/me/retrodaredevil/io/modbus/ModbusTest.java b/src/test/java/me/retrodaredevil/io/modbus/ModbusTest.java index 436df41..55eb948 100644 --- a/src/test/java/me/retrodaredevil/io/modbus/ModbusTest.java +++ b/src/test/java/me/retrodaredevil/io/modbus/ModbusTest.java @@ -1,8 +1,12 @@ package me.retrodaredevil.io.modbus; +import me.retrodaredevil.io.modbus.handling.MessageResponseCreator; import me.retrodaredevil.io.modbus.handling.MultipleWriteHandler; import me.retrodaredevil.io.modbus.handling.ReadRegistersHandler; import me.retrodaredevil.io.modbus.handling.SingleWriteHandler; +import me.retrodaredevil.io.modbus.parsing.DefaultMessageParser; +import me.retrodaredevil.io.modbus.parsing.MessageParseException; +import me.retrodaredevil.io.modbus.parsing.MessageParser; import org.junit.jupiter.api.Test; import java.io.ByteArrayInputStream; @@ -18,9 +22,9 @@ void testModbusMessages(){ assertEquals(133, ModbusMessages.createMessage(133, new int[]{}).getFunctionCode()); } @Test - void testLRC(){ + void testLrc(){ int[] data = new int[] { 17, 3, 0, 107, 0, 3 }; - assertEquals(0x7E, RedundancyUtil.calculateLRC(data)); + assertEquals(0x7E, RedundancyUtil.calculateLrc(data)); int sum = 0; for(int a : data){ @@ -30,12 +34,12 @@ void testLRC(){ assertEquals(0, sum & 0xFF); } @Test - void testCRC(){ + void testCrc(){ int[] data = { 0x01, 0x06, 0xE0, 0x1D, 0x00, 0x08}; // In modbus, the low byte is first then the high byte. This is why we have to flip them -// assertEquals(RedundancyUtil.flipCRC(0x2FCA), RedundancyUtil.calculateCRC(data)); - assertEquals(0xCA2F, RedundancyUtil.calculateCRC(data)); +// assertEquals(RedundancyUtil.flipCrc(0x2FCA), RedundancyUtil.calculateCRC(data)); + assertEquals(0xCA2F, RedundancyUtil.calculateCrc(data)); } @Test void testAsciiEncoding(){ @@ -56,10 +60,10 @@ void testRTUEncoding(){ OutputStream output = new ByteArrayOutputStream(); ModbusSlaveBus slave = new IOModbusSlaveBus(responseStream, output, encoder); - slave.sendMessage(1, new SingleWriteHandler(0x010A, 1, false)); + slave.sendRequestMessage(1, new SingleWriteHandler(0x010A, 1, false)); } { // test writing multiple values - int crc = RedundancyUtil.calculateCRC(1, 16, 0x01, 0x0A, 0, 2); + int crc = RedundancyUtil.calculateCrc(1, 16, 0x01, 0x0A, 0, 2); System.out.println(crc); ByteArrayInputStream responseStream = new ByteArrayInputStream(new byte[]{ 1, @@ -72,12 +76,12 @@ void testRTUEncoding(){ OutputStream output = new ByteArrayOutputStream(); ModbusSlaveBus slave = new IOModbusSlaveBus(responseStream, output, encoder); - slave.sendMessage(1, new MultipleWriteHandler(0x010A, new int[] { + slave.sendRequestMessage(1, new MultipleWriteHandler(0x010A, new int[] { 31, 71, 98, 43 }, true)); } { // test reading values - int crc = RedundancyUtil.calculateCRC(1, 3, 3 * 2, 99, 67, 85, 45, 92, 91); + int crc = RedundancyUtil.calculateCrc(1, 3, 3 * 2, 99, 67, 85, 45, 92, 91); System.out.println(crc); ByteArrayInputStream responseStream = new ByteArrayInputStream(new byte[]{ 1, @@ -88,9 +92,16 @@ void testRTUEncoding(){ }); OutputStream output = new ByteArrayOutputStream(); ModbusSlaveBus slave = new IOModbusSlaveBus(responseStream, output, encoder); - - int[] registers = slave.sendMessage(1, new ReadRegistersHandler(0x010A, 3)); // this will have a length of 3 + + ReadRegistersHandler readRegistersHandler = new ReadRegistersHandler(0x010A, 3); + ModbusMessage message = readRegistersHandler.createRequest(); + ModbusMessage response = slave.sendRequestMessage(1, message); // this will have a length of 3 + int[] registers = readRegistersHandler.handleResponse(response); assertArrayEquals(get16BitDataFrom8BitArray(99, 67, 85, 45, 92, 91), registers); + + ModbusMessage expectedResponse = readRegistersHandler.createResponse(registers); + assertEquals(expectedResponse.getFunctionCode(), response.getFunctionCode()); + assertArrayEquals(expectedResponse.getData(), response.getData()); } } /** A method to test encoding and decoding messages */ @@ -102,5 +113,28 @@ private void testDataEncoder(IODataEncoder encoder){ ModbusMessage receivedMessage = encoder.readMessage(1, input); assertEquals(message, receivedMessage); } + @Test + void testResponse() { + { + int[] exampleData = new int[] { 0x20F, 43, 0xFFF, 0x1FA0}; + assertArrayEquals(exampleData, getResponseData(new ReadRegistersHandler(0xEA0, 4), exampleData)); + } + assertNull(getResponseData(new SingleWriteHandler(0xEA0, 0xFFFF), null)); + assertNull(getResponseData(new MultipleWriteHandler(0xEA0, new int[] { 127, 127, 0, 43}), null)); + } + private T getResponseData(MessageResponseCreator messageResponseCreator, T data) { + ModbusMessage requestMessage = messageResponseCreator.createRequest(); + ModbusMessage exampleResponse = messageResponseCreator.createResponse(data); + assertEquals(requestMessage.getFunctionCode(), exampleResponse.getFunctionCode()); + return messageResponseCreator.handleResponse(exampleResponse); + } + + @Test + void testParse() throws MessageParseException { + MessageParser parser = new DefaultMessageParser(); + assertTrue(parser.parseRequestMessage(new ReadRegistersHandler(5, 1).createRequest()) instanceof ReadRegistersHandler); + assertTrue(parser.parseRequestMessage(new SingleWriteHandler(5, 2).createRequest()) instanceof SingleWriteHandler); + assertTrue(parser.parseRequestMessage(new MultipleWriteHandler(5, new int[] { 127, 127, 0, 43}).createRequest()) instanceof MultipleWriteHandler); + } }