Skip to content

Commit

Permalink
Breaking changes and added classes to parse modbus messages
Browse files Browse the repository at this point in the history
  • Loading branch information
retrodaredevil committed Apr 30, 2020
1 parent 1f80dc6 commit f81ecd1
Show file tree
Hide file tree
Showing 20 changed files with 346 additions and 48 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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];
Expand Down Expand Up @@ -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));
}
Expand Down
18 changes: 18 additions & 0 deletions src/main/java/me/retrodaredevil/io/modbus/ExceptionCode.java
Original file line number Diff line number Diff line change
@@ -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;
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

}
31 changes: 26 additions & 5 deletions src/main/java/me/retrodaredevil/io/modbus/ModbusMessages.java
Original file line number Diff line number Diff line change
Expand Up @@ -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];
Expand All @@ -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];
Expand All @@ -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.
Expand All @@ -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);
Expand Down Expand Up @@ -91,10 +112,10 @@ public boolean equals(Object o) {

@Override
public String toString() {
return "DefaultModbusMessage{" +
return "DefaultModbusMessage(" +
"functionCode=" + getFunctionCode() +
", data=" + Arrays.toString(data) +
'}';
')';
}

@Override
Expand Down
6 changes: 3 additions & 3 deletions src/main/java/me/retrodaredevil/io/modbus/ModbusSlave.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ public interface ModbusSlave {
* @param message
* @return
*/
ModbusMessage sendMessage(ModbusMessage message);
ModbusMessage sendRequestMessage(ModbusMessage message);

default <T> T sendMessage(MessageHandler<T> messageHandler){
ModbusMessage response = sendMessage(messageHandler.createMessage());
default <T> T sendRequestMessage(MessageHandler<T> messageHandler){
ModbusMessage response = sendRequestMessage(messageHandler.createRequest());
return messageHandler.handleResponse(response);
}
}
12 changes: 6 additions & 6 deletions src/main/java/me/retrodaredevil/io/modbus/ModbusSlaveBus.java
Original file line number Diff line number Diff line change
Expand Up @@ -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> T sendMessage(int address, MessageHandler<T> messageHandler){
ModbusMessage response = sendMessage(address, messageHandler.createMessage());
default <T> T sendRequestMessage(int address, MessageHandler<T> messageHandler){
ModbusMessage response = sendRequestMessage(address, messageHandler.createRequest());
return messageHandler.handleResponse(response);
}
}
4 changes: 2 additions & 2 deletions src/main/java/me/retrodaredevil/io/modbus/RTUDataEncoder.java
Original file line number Diff line number Diff line change
Expand Up @@ -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];
Expand Down Expand Up @@ -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));
}
Expand Down
8 changes: 4 additions & 4 deletions src/main/java/me/retrodaredevil/io/modbus/RedundancyUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@
import me.retrodaredevil.io.modbus.ModbusMessage;

public interface MessageHandler<T> {
ModbusMessage createMessage();
ModbusMessage createRequest();
T handleResponse(ModbusMessage response);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package me.retrodaredevil.io.modbus.handling;

import me.retrodaredevil.io.modbus.ModbusMessage;

public interface MessageResponseCreator<T> extends MessageHandler<T> {
/**
* 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);
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<Void> {
import java.util.Arrays;

import static me.retrodaredevil.io.modbus.ModbusMessages.get16BitDataFrom8BitArray;

public class MultipleWriteHandler implements MessageResponseCreator<Void> {
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;
Expand All @@ -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;
Expand Down Expand Up @@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<int[]> {
public class ReadRegistersHandler implements MessageResponseCreator<int[]> {

private final int register;
private final int numberOfRegisters;
Expand All @@ -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
Expand All @@ -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);
}
}
Loading

0 comments on commit f81ecd1

Please sign in to comment.